I am a beginner/intermediate in Python. I have coded a 4th-order Runge-Kutta method (RK4) into Python. It is basically solving a pendulum, but that is not the point here.
I want to improve the RK4 method in the following way: I want to be able to pass the function f directly to the RK4 function, i.e. RK4(y_0, n, h) should become RK4(f,y_0,n,h). This would have the great advantage that I could use RK4 for other f functions that describe other systems, not just this one pendulum.
I have played around with just passing simple functions to RK4, but I am doing something wrong. How do I do this in Python?
import numpy as np
def RK4(y_0, n, h):
#4th order Runge-Kutta solver, takes as input
#initial value y_0, the number of steps n and stepsize h
#returns solution vector y and time vector t
#right now function f is defined below
t = np.linspace(0,n*h,n,endpoint = False) #create time vector t
y = np.zeros((n,len(y_0))) #create solution vector y
y[0] = y_0 #assign initial value to first position in y
for i in range(0,n-1):
#compute Runge-Kutta weights k_1 till k_4
k_1 = f(t[i],y[i])
k_2 = f(t[i] + 0.5*h, y[i] + 0.5*h*k_1)
k_3 = f(t[i] + 0.5*h, y[i] + 0.5*h*k_2)
k_4 = f(t[i] + 0.5*h, y[i] + h*k_3)
#compute next y
y[i+1] = y[i] + h / 6. * (k_1 + 2.*k_2 + 2.*k_3 + k_4)
return t,y
def f(t,vec):
theta=vec[0]
omega = vec[1]
omegaDot = -np.sin(theta) - omega + np.cos(t)
result = np.array([omega,omegaDot])
return result
test = np.array([0,0.5])
t,y = RK4(test,10,0.1)
Python functions are objects too. You can pass them around like any other object:
>>> def foo(): print 'Hello world!'
...
>>> foo
<function foo at 0x10c4685f0>
>>> foo()
Hello world!
>>> bar = foo
>>> bar()
Hello world!
Simply pass a function as an extra parameter to your RK4 function and use that as a local variable.
You can pass a function to a function in Python just as you might expect:
def call_function(f):
f()
def my_function():
print "OK"
call_function(my_function) # Prints OK
Maybe you should post your failing code?
It's very simple. Change the definition of the RK4 function like so:
def RK4(f, y_0, n, h):
Here, I have added an extra argument, the function.
Then, when you call RK4, pass the function:
t, y = RK4(f, test, 10, 0.1)
And now, of course, you can substitute different functions without having to re-write the integration code.
Functions in Python are just another kind of object. You can pass them around just as you do more prosaic objects.
Related
The aim of the program is to:
"Write a python function J(m,x) that calculates the value of Jm(x) using the trapezium rule, with N = 10000. Use your function in a program to make a plot of the Bessel functions J0(x), J1(x) and J2(x) as a function of x from x = 0 to x = 20."
Jm(x) = (1/pi) . integrate[cos(m.theta - x.sin(theta))] from 0 --> pi
and is part of an intro to python module for a 1st year physics degree.
The line the type error originates from is from my function to integrate a function using the trapezium rule:
def integrate(f, a, b, N:int):
h = (b-a) / N
s = 0
for i in range(1,N):
c = f(a + i*h)
s = s + c
Area = h*(0.5*f(a) + 0.5*f(b) + s)
return Area
The error refers to the line " c = f(a + i*h) ": "TypeError: 'numpy.float64' object is not callable".
Given this function works in other programs I've made, I'm not sure what the origin of this error is. I am aware that scipy.integrate.quad could do the integration better, however we've been asked not to use it to demonstrate learning different techniques.
One possibility is that the problem is caused by other parts of the larger program. As someone very new to programming in general it seems likely that other problems exist which I haven't run into yet as the program doesn't reach them before turning up an error. The rest of the code is as follows:
import numpy as np
#Defining how to integrate a function using the trapezium rule
def integrate(f, a, b, N:int):
h = (b-a) / N
s = 0
for i in range(1,N):
c = f(a + i*h)
s = s + c
Area = h*(0.5*f(a) + 0.5*f(b) + s)
return Area
def func(o, m, x):
return np.cos(m*o - x*np.sin(o)) #1st attempt at defining the bessel function
def J(m,x):
return (1 / np.pi) * integrate(func(0, m, x), 0, np.pi, 10000)
#Produce range of x-values from 0 to 20.
xvals = np.linspace(0,20,200)
#Calculating the value of the bessel function for each value of x and m
for i in range(200):
for j in range(3):
bessel = J(j, xvals[i])
print("x: {}, m: {}, Val: {}".format(xvals[i], j, bessel)) #Print statement to check the program is functioning correctly before moving on to the next stage
return (1 / np.pi) * integrate(func(0, m, x), 0, np.pi, 10000)
In your function J, you are giving a function call as a parameter, i.e. its return value. Change func(0, m, x) to func and it should work.
Edit:
The correct answer was to pass a lambda expression: lambda i: func(i, m, x). Credit goes to Barmar.
I have learned how to automatically find the partial derivative of a function with sympy. My problem is, I need to define a new function that returns the partial derivative of the other function.
from sympy import Symbol, Derivative
y= Symbol('y')
function = y ** 2
deriv = Derivative(function, y).doit()
def func(y):
return deriv
Something like that. Hope you all understood. Thanks!
So y is another function as in predefined like y = Symbol('x') ** 2? I believe your function need another input.
x = Symbol('x')
y = x ** 2
def func(function, symbol):
deriv = Derivative(function, symbol).doit()
return deriv
derivative = func(y, x)
You can't do it without specifying symbol - especially since this is partial derivative you need to tell which symbol it's trying to derive against.
I am trying to solve this differential equation as part of my assignment. I am not able to understand on how can i put the condition for u in the code. In the code shown below, i arbitrarily provided
u = 5.
2dx(t)dt=−x(t)+u(t)
5dy(t)dt=−y(t)+x(t)
u=2S(t−5)
x(0)=0
y(0)=0
where S(t−5) is a step function that changes from zero to one at t=5. When it is multiplied by two, it changes from zero to two at that same time, t=5.
def model(x,t,u):
dxdt = (-x+u)/2
return dxdt
def model2(y,x,t):
dydt = -(y+x)/5
return dydt
x0 = 0
y0 = 0
u = 5
t = np.linspace(0,40)
x = odeint(model,x0,t,args=(u,))
y = odeint(model2,y0,t,args=(u,))
plt.plot(t,x,'r-')
plt.plot(t,y,'b*')
plt.show()
I do not know the SciPy Library very well, but regarding the example in the documentation I would try something like this:
def model(x, t, K, PT)
"""
The model consists of the state x in R^2, the time in R and the two
parameters K and PT regarding the input u as step function, where K
is the infimum of u and PT is the delay of the step.
"""
x1, x2 = x # Split the state into two variables
u = K if t>=PT else 0 # This is the system input
# Here comes the differential equation in vectorized form
dx = [(-x1 + u)/2,
(-x2 + x1)/5]
return dx
x0 = [0, 0]
K = 2
PT = 5
t = np.linspace(0,40)
x = odeint(model, x0, t, args=(K, PT))
plt.plot(t, x[:, 0], 'r-')
plt.plot(t, x[:, 1], 'b*')
plt.show()
You have a couple of issues here, and the step function is only a small part of it. You can define a step function with a simple lambda and then simply capture it from the outer scope without even passing it to your function. Because sometimes that won't be the case, we'll be explicit and pass it.
Your next problem is the order of arguments in the function to integrate. As per the docs (y,t,...). Ie, First the function, then the time vector, then the other args arguments. So for the first part we get:
u = lambda t : 2 if t>5 else 0
def model(x,t,u):
dxdt = (-x+u(t))/2
return dxdt
x0 = 0
y0 = 0
t = np.linspace(0,40)
x = odeint(model,x0,t,args=(u,))
Moving to the next part, the trouble is, you can't feed x as an arg to y because it's a vector of values for x(t) for particular times and so y+x doesn't make sense in the function as you wrote it. You can follow your intuition from math class if you pass an x function instead of the x values. Doing so requires that you interpolate the x values using the specific time values you are interested in (which scipy can handle, no problem):
from scipy.interpolate import interp1d
xfunc = interp1d(t.flatten(),x.flatten(),fill_value="extrapolate")
#flatten cuz the shape is off , extrapolate because odeint will go out of bounds
def model2(y,t,x):
dydt = -(y+x(t))/5
return dydt
y = odeint(model2,y0,t,args=(xfunc,))
Then you get:
#Sven's answer is more idiomatic for vector programming like scipy/numpy. But I hope my answer provides a clearer path from what you know already to a working solution.
I woul like to solve an n-dimensional optimisation problem using iminuit.
So my approach is the following.
I am trying to figure out how to extend this:
def f(x,y,z):
return (x-1.)**2 + (y-2*x)**2 + (z-3.*x)**2 -1.
to a variable "x" that is a numpy.array.
I would like to do something like this:
x = [1,2,3,4,5]
y = [2,4,6,8,10]# y=2x
class StraightLineChi2:
def __init__(self,x,y):
self.x = x
self.y = y
def __call__(self,m,c): #lets try to find slope and intercept
chi2 = sum((y - m*x+c)**2 for x,y in zip(self.x,self.y))
return chi2
but in my case x is my unknown, and it is an array. Like in many optimization/minimization problems, the function is a f=f(x1,...,xn) where n can be big. x1,...,xn are the unknowns of the problem.
(These examples are taken from here)
Something similar is achieved "hacking" pyminuit2, like described here
For your example I recommend you using iminuit and probfit. Having an argument as a list of parameter is not exactly what you want to do since you will get confused which parameter is what very soon.
Here is an example taken straight from probfit tutorial. Also see the documentation
import iminuit
import probfit
x = np.linspace(0, 10, 20)
y = 3 * x + 15 + np.random.randn(len(x))
err = np.ones(len(x))
def line(x, m, c): # define it to be parabolic or whatever you like
return m * x + c
chi2 = probfit.Chi2Regression(line, x, y, err)
minuit = iminuit.Minuit(chi2)
minuit.migrad();
print(minuit.values) #{'c': 16.137947520534624, 'm': 2.8862774144823855}
In python I have a function which has many parameters. I want to fit this function to a data set, but using only one parameter, the rest of the parameters I want to supply on on my own. Here is an example:
def func(x,a,b):
return a*x*x + b
for b in xrange(10):
popt,pcov = curve_fit(func,x1,x2)
In this I want that the fitting is done only for a and the parameter b takes the value of the loop variable. How can this be done?
You can wrap func in a lambda, as follows:
def func(x, a, b):
return a*x*x + b
for b in xrange(10):
popt, pcov = curve_fit(lambda x, a: func(x, a, b), x1, x2)
A lambda is an anonymous function, which in Python can only be used for simple one line functions. Basically, it's normally used to reduce the amount of code when don't need to assign a name to the function. A more detailed description is given in the official documentation: http://docs.python.org/tutorial/controlflow.html#lambda-forms
In this case, a lambda is used to fix one of the arguments of func. The newly created function accepts only two arguments: x and a, whereas b is fixed to the value taken from the local b variable. This new function is then passed into curve_fit as an argument.
A better approach would use lmfit, which provides a higher level interface to curve-fitting. Among other features, Lmfit makes fitting parameters be first-class objects that can have bounds or be explicitly fixed (among other features).
Using lmfit, this problem might be solved as:
from lmfit import Model
def func(x,a,b):
return a*x*x + b
# create model
fmodel = Model(func)
# create parameters -- these are named from the function arguments --
# giving initial values
params = fmodel.make_params(a=1, b=0)
# fix b:
params['b'].vary = False
# fit parameters to data with various *static* values of b:
for b in range(10):
params['b'].value = b
result = fmodel.fit(ydata, params, x=x)
print(": b=%f, a=%f+/-%f, chi-square=%f" % (b, result.params['a'].value,
result.params['a'].stderr,
result.chisqr))
Instead of using the lambda function which might be less intuitive to digest I would recommend to specify the scikit curve_fit parameter bounds that will force your parameter to be searched within custom boundaries.
All you have to do is to let your variable a move between -inf and +inf and your variable b between (b - epsilon) and (b + epsilon)
In your example:
epsilon = 0.00001
def func(x,a,b):
return a*x*x + b
for b in xrange(10):
popt,pcov = curve_fit(func,x1,x2, bounds=((-np.inf,b-epsilon), (np.inf,b+epsilon))
I effectively use Anton Beloglazov's solution, though I like to avoid using lambda functions for readability so I do the following:
def func(x,a,b):
return a*x*x + b
def helper(x,a):
return func(x,a,b)
for b in xrange(10):
popt,pcov = curve_fit(helper, x1, x2)
This ends up being reminiscent of Rick Berg's answer, but I like having one function dedicated to the "physics" of the problem and a helper function to get the code to work.
Another way is to use upper and lower bounds that are identical (+ eps) as the initial value.
Using the same example with initial conditions and bounds:
def func(x,a,b):
return a*x*x + b
# free for a and b
popt,pcov = curve_fit(func, x1, x2,
p0=[1,1],
bounds=[(-inf,-inf),(inf,inf)])
# free for a; fixed for b ;
eps=1/100
popt,pcov = curve_fit(func, x1, x2,
p0=[1,1],
bounds=[(-inf,(1-eps)),(inf,(1+eps))])
Remember to insert an epsilon, otherwise, a and b must be the same
There is a simpler option if you are willing/able to edit the original function.
Redefine your function as:
def func(x,a):
return a*x*x + b
Then you can simply put it in your loop for parameter b:
for b in xrange(10):
popt,pcov = curve_fit(func, x1, x2)
Caveat: the function needs to be defined in the same script in which it is called for this to work.
Scipy's curve_fit takes three positional arguments, func, xdata and ydata.
So an alternative approach (to using a function wrapper) is to treat 'b' as xdata (i.e. independent variable) by building a matrix that contains both your original xdata (x1) and a second column for your fixed parameter b.
Assuming x1 and x2 are arrays:
def func(xdata,a):
x, b = xdata[:,0], xdata[:,1] # Extract your x and b
return a*x*x + b
for b in xrange(10):
xdata = np.zeros((len(x1),2)) # initialize a matrix
xdata[:,0] = x1 # your original x-data
xdata[:,1] = b # your fixed parameter
popt,pcov = curve_fit(func,xdata,x2) # x2 is your y-data