0

I am using the wonderful sympy in python to generate the augmented system of an ODE system to calculate the forward sensitivities of the states with respect to to states. My goal is to optimize a system of ODEs. If I have a system of x1...x10 and parameters a1...a5, then the augmented system would have 10+10*5 states. The sympy code I have generates the extra states and assigns them to variables x1...x60.

Later I am using integration in numpy to solve the augmented system. So I have to code a function that returns a the rhs() of the ODE-system - something like this code (if some of you out there is developer at numpy, please correct the bug in two_springs.py, line 29 - m2 is missing).

And my my problem: I want inside a function to assign the variables x1...x_end dynamically (the total number of states would change depending how many parameter I use for the sensitivities). I was thrilled then I found the built-in locals() function in python. From this post I thought this should work:

def test_l(w, t):
for ii in range(len(w)):
    varStr="a%d" % (ii)
    locals()[varStr]=w[ii]
return a1*t+a0
w0 = [1.0, 1.0]
t0 = 1.0
f_x=test_l(w0, t0)
print "func res-> %1.4f\n" % (f_x)

Running the script I am getting global name 'a1' is not defined. Later I found out that localst() is actually read only. This what confuses me is that if I debug the function with pde on in ipython, then the variables a1 and a0 actually exist inside the function... Running the code with 'pdb on' I am still getting an error, the program execution stops at the return(), but a1 and a0 actually exist in the function's workspace.

ipdb> a1 1.0

ipdb> a0 1.0

How comes that locals() is read-only, but when debugging with pdb the dictionay can be actually changed?

PS: I solved my problem in this way:

 for ii in range(len(w)):
    #varStr="a%d" % (ii)
    #locals()[varStr]=w[ii]
    varStr="a%d=w[%d]" % (ii, ii)
    exec(varStr)
 return a1*t+a0
Community
  • 1
  • 1
Ivan Angelov
  • 353
  • 1
  • 5
  • Why, just why do you want to create those variables dynamically? It has a reason that this kind of stuff is not as easy in Python as it is in some other languages. – Niklas B. Feb 23 '12 at 21:41
  • 3
    I have seen this kind of question more than once on here, regarding someone trying to create dynamically-named variables, and it always ends with them just having approached the problem incorrectly. There really is no good reason to do this. – jdi Feb 23 '12 at 21:48
  • @NiklasB. Because if I change the number of parameters, then I get a different length of the state vector for example. I want the length of the state vector to be self-adjusting, so I do not have to care about this. – Ivan Angelov Feb 23 '12 at 21:49
  • But why wouldn't you just use a normal dictionary if you already know the variable names you will want to access? Why do you need them to be anonymous variables to which you have no handle? – jdi Feb 23 '12 at 21:51
  • @jdi I am aware of the fact that there are many similar questions. I am asking if there is a difference how locals() work if used inside or outside an debugging environment. I just wanted to explain how I came to this problem... – Ivan Angelov Feb 23 '12 at 21:52
  • @jdi I could not figure out how to use dictionaries as symbolic variables in sympy. That's why I am trying to create later the variables I need dynamically. – Ivan Angelov Feb 23 '12 at 21:56
  • 1
    @Ivan Angelov: You can just do something like `d = { 'a%d' % i:sympy.var('a%d' % i) for i in xrange(100)}; d['a78'] + d['a98']`. What's the problem? The differences you describe regarding the use of `locals()` are probably due to different Python versions. It's not meant to be written to, so it's not surprising that it doesn't work like you expect. – Niklas B. Feb 23 '12 at 22:09
  • @NiklasB. - Thats exactly what I was getting at as well in my answer. I used the Symbol class since I didn't know any difference but using this sympy.var function would be exactly the same idea. +1 – jdi Feb 23 '12 at 22:13
  • @NiklasB. Thank you for the suggestion. I am coming from the the Maple&Matlab world, so I could not come so easily to you solution. +1 – Ivan Angelov Feb 23 '12 at 22:53

2 Answers2

4

Let me start by disclaiming that I know nothing about sympy. I only took a glance at the docs in response to your comment that you couldn't create Symbols from a dictionary...

locals(), as you suggested, is read-only. Quoting from the python docs:

Note The contents of this dictionary should not be modified; changes may not affect the values of local and free variables used by the interpreter.

I take that to mean that modifying it under any circumstance would be unpredictable. Thus, if the variables are alive during your debugging, I'm assuming there is a difference in the garbage collection under those circumstances. Or... unpredictability.

As for the Symbolic variables in sympy. They just seem to be a standard python class. Wouldn't this work to create Symbol objects from a dict that you dynamically created?

from sympy import *
myVars = {'a1':1.0, 'a2':1.0 }
print [Symbol(k) for k in myVars.iterkeys()]
# [a₁, a₂]

I'm not entirely sure about the usage of the values without more knowledge of sympy, but I'm sure you can do whatever you need to using a similar approach. The need for dynamic variable names on the fly can pretty much always be solved with some other approach.

jdi
  • 90,542
  • 19
  • 167
  • 203
  • 3
    I often find myself writing things like `xx = [Symbol("x%d" % i) for i in range(10)]` and then `f = sum(c*x for c,x in zip(coeffs, xx))`, etc. Using a list has a few advantages over a dict because it makes ordering a bit easier, but YMMV. I've never once needed to hack locals(). – DSM Feb 23 '12 at 22:14
  • Yea, like I was saying, I have no idea about sympy really. So I wasn't sure what the actual objects were that he needed to create. But the theory is the same regardless of what you want the key and value to be. +1 to you too! We are double-teaming this one! – jdi Feb 23 '12 at 22:16
  • @DSM - Yes exactly. Whether it be a list, or a dict, the point that we can all agree on is that there isn't a reason to create dynamically named variables in python. Hence the reason `locals()` is read-only. – jdi Feb 23 '12 at 22:17
  • @jdi thanks for pointing out that changing the locals() would result in an unpredictable behavior. This answers my question about creating the local variables when or when not debugging. Also thank you for the suggestion how I could create my variables in sympy. I shall test it, I hope I can find a way to differentiate an expression with a respect to dictionary. This would REALLY solve my problem as I am not happy with hacking locals() or using exec() too. – Ivan Angelov Feb 23 '12 at 22:50
3

The reason for the warning in the Python documentation against writing to locals() that jdi mentions is that most of the time, things you change in locals() don't get propagated back to the actual local variables. That's because locals can come from more than one place. The reason locals() is a function in the first place, rather than a dictionary, is so that there can be some code that gathers all the local variables from the places where they exist into one place where they can be read conveniently.

There are occasions when writing to the dictionary returned by locals() works. If you include an exec statement anywhere in your function, even if it's never executed (i.e. is after a return statement), writing to locals() will work. However, local variables will then be slower than otherwise because Python can't use the "fast" opcodes to access local variables by index when it doesn't know at "compile" time the names of all the local variables.

def foo(val):    # circuitously returns value passed in
    locals["b"] = val
    return b
    exec ""

Also, you will then find that closures (sharing of variables between an inner and outer function) don't work.

def foo(a):   # can't define this function
    def bar():
       return a
    return bar
    exec ""

I believe that the Python interpreter also does some extra magic with locals() while a trace function is active.

This is all specific to CPython; Jython and IronPython probably behave differently.

kindall
  • 178,883
  • 35
  • 278
  • 309