11

I'm new to Python. I am writing a script that will numerically integrate a set of ordinary differential equations using a Runge-Kutta method. Since the Runge-Kutta method is a useful mathematical algorithm, I've put it in its own .py file, rk4.py.

def rk4(x,dt):
    k1=diff(x)*dt
    k2=diff(x+k1/2)*dt
    k3=diff(x+k2/2)*dt
    k4=diff(x+k3)*dt
    return x+(k1+2*k2+2*k3+k4)/6

The method needs to know the set of equations that the user is working with in order to perform the algorithm, so it calls a function diff(x) that will find give rk4 the derivatives it needs to work. Since the equations will change by use, I want diff() to be defined in the script that will run the particular problem. In this case the problem is the orbit of mercury, so I wrote mercury.py. (This isn't how it will look in the end, but I've simplified it for the sake of figuring out what I'm doing.)

from rk4 import rk4
import numpy as np

def diff(x):
    return x

def mercury(u0,phi0,dphi):
    x=np.array([u0,phi0])
    dt=2
    x=rk4(x,dt)
    return x

mercury(1,1,2)

When I run mercury.py, I get an error:

  File "PATH/mercury.py", line 10, in mercury
    x=rk4(x,dt)
  File "PATH/rk4.py", line 2, in rk4
    k1=diff(x)*dt
NameError: global name 'diff' is not defined

I take it since diff() is not a global function, when rk4 runs it knows nothing about diff. Obviously rk4 is a small piece of code and I could just shove it into whatever script I'm using at the time, but I think a Runge-Kutta integrator is a fundamental mathematical tool, just like the array defined in NumPy, and so it makes sense to make it a function that is called rather one that is defined in every script that uses it (which may be many). But I also can't go telling rk4.py to import a particular diff from a particular .py file, because that ruins the generality of rk4 that I want in the first place.

Is there a way to define diff globally within a script like mercury.py so that when rk4 is called, it will know about diff?

5
  • 1
    By the way, the whitespace in the block quotes is not accurate to my code, but I'm not sure how to get the block quotes to generate a new line without the extra whitespace. Commented Oct 2, 2012 at 14:12
  • 1
    Use the { } icon in the question editor to mark large blocks of source code. Commented Oct 2, 2012 at 14:13
  • Thanks, it should be fixed now. Commented Oct 2, 2012 at 14:15
  • 2
    Another tip, if I may: PEP8, the official style guide for Python code, suggests to always put spaces around operators (like +, /, etc. in your case). This makes for much more readable code. (For this question it's just fine, but in your own code I'd change it). Commented Oct 2, 2012 at 14:32
  • 1
    This is one hell of a first question by the way: Concise title, "This is what I want to do", "this is what I tried", "this is what happened", including (relevant) code, imports and backtrace. Nice! :) Commented Oct 2, 2012 at 15:14

2 Answers 2

11

Accept the function as an argument:

def rk4(diff,  # accept an argument of the function to call
        x, dt)
    k1=diff(x)*dt
    k2=diff(x+k1/2)*dt
    k3=diff(x+k2/2)*dt
    k4=diff(x+k3)*dt
    return x+(k1+2*k2+2*k3+k4)/6

Then, when you call rk4, simply pass in the function to be executed:

from rk4 import rk4
import numpy as np

def diff(x):
    return x

def mercury(u0,phi0,dphi):
    x=np.array([u0,phi0])
    dt=2
    x=rk4(diff,  # here we send the function to rk4
          x, dt)
    return x
mercury(1,1,2)

It might be a good idea for mercury to accept diff as an argument too, rather than getting it from the closure (the surrounding code). You then have to pass it in as usual - your call to mercury in the last line would read mercury(diff, 1, 1, 2).

Functions are 'first-class citizens' in Python (as is nearly everything, including classes and modules), in the sense that they can be used as arguments, be held in lists, be assigned to names in namespaces, etc etc.

Sign up to request clarification or add additional context in comments.

7 Comments

@JoranBeasley "Not very Pythonic"? No way! This is the only Pythonic solution to the problem. The standard library is absolutely full of functions that accept and return other functions, and the @decorator syntax would make no sense if this weren't a major language feature.
What's not pythonic about that? Making use of functions being first class objects is very pythonic.
Thanks! This is similar to how I've seen it done in languages like IDL that have native rk4 routines, and seems to let me preserve the structure I'd like.
@JoranBeasley the OP says "I also can't go telling rk4.py to import a particular diff from a particular .py file, because that ruins the generality of rk4 that I want in the first place." He wants each call to rk4 to be able to define its own diff. The only way to do this is to pass it in as an argument. from...import breaks reusability.
@JoranBeasley Don't be squeamish about passing functions around. It's a perfectly natural and Pythonic thing to do. As I said, the standard library has loads of functions-that-accept-functions. Take a look at the functools module.
|
3

diff is already a global in the module mercury.py. But in order to use it in rk4.py you would need to import it like this:

from mercury import diff

That's the direct answer to your question.

However, passing the diff function to rk4 as suggested by @poorsod is much more elegant and also avoids a circular dependency between mercury.py and rk4.py, so I suggest you do it that way.

5 Comments

I think this is the more elegant solution imho ... passing functions around seems like a dangerous practice to me... (granted this solution requires a code refactor to avoid the circular dependency)
@JoranBeasley Yes, since diff is only used in rk4.py it would probably make sense to refactor the code to put it there. But still, especially for a diff-function the pattern of passing in function objects lends itself very naturally. And I honestly don't see how it could be dangerous.
yes I can see the argument for it ... It just still seems sketchy to me :P ... Ive done stuff like this in my code(passing functions) and 99% of the time im passing always the same function...(with the exceptions being the builtins like sum/max/sorted/etc...)
In this case I'm sure that won't be the case, as I'm going to be using different diff several times in the course of completing this assignment, and in my research in the past (I'm a physics student) I've had to apply this rk4 method to diverse systems with different applications, often using their own unique definitions of diff.
@DylanB You also stated in your question But I also can't go telling rk4.py to import a particular diff from a particular .py file, I first missed that. So passing the diff-function is absolutely the right way to go here. Good question BTW!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.