Using Scipy curve_fit with variable number of parameters to optimize - python

Assuming we have the below function to optimize for 4 parameters, we have to write the function as below, but if we want the same function with more number of parameters, we have to rewrite the function definition.
def radius (z,a0,a1,k0,k1,):
k = np.array([k0,k1,])
a = np.array([a0,a1,])
w = 1.0
phi = 0.0
rs = r0 + np.sum(a*np.sin(k*z +w*t +phi), axis=1)
return rs
The question is if this can be done easier in a more automatic way, and more intuitive than this question suggests.
example would be as following which has to be written by hand.
def radius (z,a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,k0,k1,k2,k3,k4,k5,k6,k7,k8,k9,):
k = np.array([k0,k1,k2,k3,k4,k5,k6,k7,k8,k9,])
a = np.array([a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,])
w = 1.0
phi = 0.0
rs = r0 + np.sum(a*np.sin(k*z +w*t +phi), axis=1)
return rs

Related

Function not callable anymore after one try

I'm coding a function right now which has a really weird problem. When I define the function Psi(t) and call it to be plotted, it works fine. But, when you call it again to be plotted, it sends an error 'numpy.ndarray' object is not callable. When you click play (on Jupyter notebook) on Psi(t) to define it again then call it to be plotted, it works fine again. You'd have to define it again if you wanna change a parameter then plot Psi(t) again. I don't know if it's with the code or the software that I use for python (VS code). Anyhow, here's the code:
# Constants
m = 9.109e-31 # mass of electron in kg
L = 1e-8 # length of box in m
hbar = 1.0546e-34 # hbar in J/s
x0 = L/2 # midpoint of box
sigma = 1e-10 # width of wave packet in m
kappa = 5e10 # wave number in 1/m
N = 1000 # number of grid slices
def psi0(x):
return np.exp( -(x - x0)**2/(2*sigma**2) )*np.exp(-1j*kappa*x)
#Discrete sine transform
def dst(y):
N = len(y)
y2 = np.empty(2*N,float)
y2[0] = y2[N] = 0.0
y2[1:N] = y[1:]
y2[:N:-1] = -y[1:]
a = -np.imag(rfft(y2))[:N]
a[0] = 0.0
return a
#Inverse discrete sine transform
def idst(a):
N = len(a)
c = np.empty(N+1,complex)
c[0] = c[N] = 0.0
c[1:N] = -1j*a[1:]
y = irfft(c)[:N]
y[0] = 0.0
return y
x_n = np.zeros(N, complex)
xgrid = range(N)
for i in range(N):
x_n[i] = psi0(i*L/N)
alpha = dst(np.real(x_n))
eta = dst(np.imag(x_n))
def Psi(t):
k = np.arange(1, N+1)
energy_k = (k**2*np.pi**2*hbar)/(2*m*L**2)
cos, sin = np.cos(energy_k*t), np.sin(energy_k*t)
re_psi = alpha*cos - eta*sin
im_psi = eta*cos + alpha*sin
psi = re_psi + im_psi
return idst(psi)
Psi = Psi(2e-16)
plt.plot(xgrid,Psi)
plt.show()
I'm hoping someone can help.
On the third-to-last line:
Psi = Psi(2e-16)
You are updating the reference to Psi from the function to the return value. Upon doing so, Psi can no longer be used as a function. It is advisory to never use variables with the same names as functions or classes in your code. Solutions are to either rename the variable, or rename the function and function call.

Sweeping a parameter for an ODE function python

This isn't an ODE question, per see. It's more of a referencing issue, I think (but I might be wrong). I've copied the code below. I can't seem to sweep parameters for an ODE function that I would like to run. Any advice/insight on how to fix this problem would be greatly appreciated! Thank you very much in advance!
CONTEXTUAL CODE:
import numpy as np
from scipy.integrate import odeint # odeint allows to run ODEs
import matplotlib.pyplot as plt
def model(x, t):
# Definitions. The 'x' input is of the form [B0, G0, S0, F0]. So they need to be allocated to the appropriate variable here.
B = x[0]
G = x[1]
S = x[2]
F = x[3]
# ODEs.
dBdt = l - d*G*(B/(B+K0)) - aB*B
dGdt = pG1*(B**n/((B**n)+(K1**n))) + pG2*(S**n/((S**n)+(K2**n))) - aG*G
dSdt = pS*((F**n)/((F**n)+(K4**n))) - aS*S
dFdt = pF*((K3**n)/((K3**n)+(B**n))) - aF*F
return [dBdt, dGdt, dSdt, dFdt]
# Parameters for 'model'
# This is a list of the parameters that are used in the 'model' function above.
pG1 = 0.25
pG2 = 0.25
pF = 0.25
pS = 0.25
aB = 0.25
aG = 0.25
aF = 0.25
aS = 0.25
K0 = 0.4
K1 = 0.5
K2 = 0.3
K3 = 0.45
K4 = 0.35
n = 3
l = 0.25
d = 1.5
n1 = 3
n2 = 3
n3 = 3
n4 = 3
# Initial conditions for the ODE
model_x0 = [1,0,0,0] # this will be entered as an input to the 'model' function
#Defining the timeline of the model
model_t = np.linspace (0, 50, 200)
def sweep(param, p_low, p_high, values):
B = np.array([])
parameter_values = np.linspace(p_low, p_high, values)
for parameter_value in parameter_values:
param = parameter_value # **THIS IS THE KEY SECTION, I THINK. 'param' isn't referencing the variable that is being given in the argument of the call**
model_result = odeint(model, model_x0, model_t)
temp= np.array(model_result[:,0])
B = np.append(B, temp, axis=0)
return tuple(B)
When I test the sweep with two values for 'pG1' (they should give different outputs):
test = sweep(pG1, 0, 0.8, 2)
test1 = test[:200]
test2 = test[200:]
test1==test2
This outputs True. And it shouldn't.
Out[10]: True
Based on my understanding you basically want to sweep the variable in this scenario pG1 through your ode. THe primary mistake is that the ODE is not accepting the values. odeint allows for odeint(model,model_init,t, args=(a,b,c)) according to the docs. Seeing as you are initializing the parameters globally, it doesnt actually work since the variable isn't changed in the initial ode. I am not an expert at it but I got a working version with some changes to your code. Pretty sure there is a more elegant way of doing this which I hope someone can contribute.
import numpy as np
from scipy.integrate import odeint # odeint allows to run ODEs
import matplotlib.pyplot as plt
def model(x, t,param_value): #we modify the ode model slightly to allow the passing of parameters.
pG1 = param_value['pG1'] #since its a dict we can access the parameter like this. I used a dict because if you want to sweep for all the other parameters its much easier like this I think.
# Definitions. The 'x' input is of the form [B0, G0, S0, F0]. So they need to be allocated to the appropriate variable here.
B = x[0]
G = x[1]
S = x[2]
F = x[3]
# ODEs.
dBdt = l - d*G*(B/(B+K0)) - aB*B
dGdt = pG1*(B**n/((B**n)+(K1**n))) + pG2*(S**n/((S**n)+(K2**n))) - aG*G
dSdt = pS*((F**n)/((F**n)+(K4**n))) - aS*S
dFdt = pF*((K3**n)/((K3**n)+(B**n))) - aF*F
#print(pG1) # debugged here to see if the value actually changed
return [dBdt, dGdt, dSdt, dFdt]
# Parameters for 'model'
# This is a list of the parameters that are used in the 'model' function above.
pG1 = 0.25 #Defining the parameters like this defines it once. It does not change in your original code. model takes the values here but there is no call to change it.
pG2 = 0.25
pF = 0.25
pS = 0.25
aB = 0.25
aG = 0.25
aF = 0.25
aS = 0.25
K0 = 0.4
K1 = 0.5
K2 = 0.3
K3 = 0.45
K4 = 0.35
n = 3
l = 0.25
d = 1.5
n1 = 3
n2 = 3
n3 = 3
n4 = 3
param = {'pG1':pG1,'pG2':pG2,'pF':pF,'pS':pS,'aB':aB,'aG':aG,'aF':aF,'aS':aS,'K0':K0,'K1':K1,'K2':K2,'K3':K3,'K4':K4,'n':n,'l':l,'d':d,'n1':n1,'n2':n2,'n3':n3,'n4':n4} # Here we put all your parameters defined into a nice dict.
# Initial conditions for the ODE
model_x0 = [1,0,0,0] # this will be entered as an input to the 'model' function
#Defining the timeline of the model
model_t = np.linspace (0, 50, 200)
def sweep(p_name, p_low, p_high, values, param): #note i changed the input name. So we pass the variable name we want to sweep here.
B = np.array([])
parameter_values = np.linspace(p_low, p_high, values)
for parameter_value in parameter_values:
param[p_name] = parameter_value # Here we use the linspace values to 'sweep' the parameter value that we are manipulating
model_result = odeint(model, model_x0, model_t,args = (param,)) #here we pass the 'new' parameters into the actual ode.
temp= np.array(model_result[:,0])
B = np.append(B, temp, axis=0)
return tuple(B)
test = sweep('pG1', 0, 0.8, 2,param) #so we pass the 'default' parameters
test1 = test[:200]
test2 = test[200:]
print(test1==test2)
>>False
This is definitely a hacky way to do it but it works. I am sure someone with more experience using odeint and the numpy/scipy package can give you an easier/cleaner way to do this. You can extend this for all the parameters if you so wish. I would not recommend globals however.

fmin_slsqp returns initial guess finding the minimum of cubic spline

I am trying to find the minimum of a natural cubic spline. I have written the following code to find the natural cubic spline. (I have been given test data and have confirmed this method is correct.) Now I can not figure out how to find the minimum of this function.
This is the data
xdata = np.linspace(0.25, 2, 8)
ydata = 10**(-12) * np.array([1,2,1,2,3,1,1,2])
This is the function
import scipy as sp
import numpy as np
import math
from numpy.linalg import inv
from scipy.optimize import fmin_slsqp
from scipy.optimize import minimize, rosen, rosen_der
def phi(x, xd,yd):
n = len(xd)
h = np.array(xd[1:n] - xd[0:n-1])
f = np.divide(yd[1:n] - yd[0:(n-1)],h)
q = [0]*(n-2)
for i in range(n-2):
q[i] = 3*(f[i+1] - f[i])
A = np.zeros(((n-2),(n-2)))
#define A for j=0
A[0,0] = 2*(h[0] + h[1])
A[0,1] = h[1]
#define A for j = n-2
A[-1,-2] = h[-2]
A[-1,-1] = 2*(h[-2] + h[-1])
#define A for in the middle
for j in range(1,(n-3)):
A[j,j-1] = h[j]
A[j,j] = 2*(h[j] + h[j+1])
A[j,j+1] = h[j+1]
Ainv = inv(A)
B = Ainv.dot(q)
b = (n)*[0]
b[1:(n-1)] = B
# now we find a, b, c and d
a = [0]*(n-1)
c = [0]*(n-1)
d = [0]*(n-1)
s = [0]*(n-1)
for r in range(n-1):
a[r] = 1/(3*h[r]) * (b[r + 1] - b[r])
c[r] = f[r] - h[r]*((2*b[r] + b[r+1])/3)
d[r] = yd[r]
#solution 1 start
for m in range(n-1):
if xd[m] <= x <= xd[m+1]:
s = a[m]*(x - xd[m])**3 + b[m]*(x-xd[m])**2 + c[m]*(x-xd[m]) + d[m]
return(s)
#solution 1 end
I want to find the minimum on the domain of my xdata, so a fmin didn't work as you can not define bounds there. I tried both fmin_slsqp and minimize. They are not compatible with the phi function I wrote so I rewrote phi(x, xd,yd) and added an extra variable such that phi is phi(x, xd,yd, m). M indicates in which subfunction of the spline we are calculating a solution (from x_m to x_m+1). In the code we replaced #solution 1 by the following
# solution 2 start
return(a[m]*(x - xd[m])**3 + b[m]*(x-xd[m])**2 + c[m]*(x-xd[m]) + d[m])
# solution 2 end
To find the minimum in a domain x_m to x_(m+1) we use the following code: (we use an instance where m=0, so x from 0.25 to 0.5. The initial guess is 0.3)
fmin_slsqp(phi, x0 = 0.3, bounds=([(0.25,0.5)]), args=(xdata, ydata, 0))
What I would then do (I know it's crude), is iterate this with a for loop to find the minimum on all subdomains and then take the overall minimum. However, the function fmin_slsqp constantly returns the initial guess as the minimum. So there is something wrong, which I do not know how to fix. If you could help me this would be greatly appreciated. Thanks for reading this far.
When I plot your function phi and the data you feed in, I see that its range is of the order of 1e-12. However, fmin_slsqp is unable to handle that level of precision and fails to find any change in your objective.
The solution I propose is scaling the return of your objective by the same order of precision like so:
return(s*1e12)
Then you get good results.
>>> sol = fmin_slsqp(phi, x0=0.3, bounds=([(0.25, 0.5)]), args=(xdata, ydata))
>>> print(sol)
Optimization terminated successfully. (Exit mode 0)
Current function value: 1.0
Iterations: 2
Function evaluations: 6
Gradient evaluations: 2
[ 0.25]

How to solve a 9-equations system of non linear DE with python?

I'm desperately trying to solve (and display the graph) a system made of nine nonlinear differential equations which model the path of a boomerang. The system is the following:
All the letters on the left side are variables, the others are either constants or known functions depending on v_G and w_z
I have tried with scipy.odeint with no conclusive results (I had this issue but the workaround did not work.)
I begin to think that the problem is linked with the fact that these equations are nonlinear or that the function in denominator might cause a singularity that the scipy solver is simply unable to handle. However, I am not familiar with that sort of mathematical knowledge.
What possibilities python-wise do I have to solve this set of equations?
EDIT : Sorry if I was not clear enough. Since it models the path of a boomerang, my goal is not to solve analytically this system (ie I don't care about the mathematical expression of each function), but rather to get the values of each function for a specific time range (say, from t1 = 0s to t2 = 15s with an interval of 0.01s between each value) in order to display the graph of each function and the graph of the center of mass of the boomerang (X,Y,Z are its coordinates).
Here is the code I tried :
import scipy.integrate as spi
import numpy as np
#Constants
I3 = 10**-3
lamb = 1
L = 5*10**-1
mu = I3
m = 0.1
Cz = 0.5
rho = 1.2
S = 0.03*0.4
Kz = 1/2*rho*S*Cz
g = 9.81
#Initial conditions
omega0 = 20*np.pi
V0 = 25
Psi0 = 0
theta0 = np.pi/2
phi0 = 0
psi0 = -np.pi/9
X0 = 0
Y0 = 0
Z0 = 1.8
INPUT = (omega0, V0, Psi0, theta0, phi0, psi0, X0, Y0, Z0) #initial conditions
def diff_eqs(t, INP):
'''The main set of equations'''
Y=np.zeros((9))
Y[0] = (1/I3) * (Kz*L*(INP[1]**2+(L*INP[0])**2))
Y[1] = -(lamb/m)*INP[1]
Y[2] = -(1/(m * INP[1])) * ( Kz*L*(INP[1]**2+(L*INP[0])**2) + m*g) + (mu/I3)/INP[0]
Y[3] = (1/(I3*INP[0]))*(-mu*INP[0]*np.sin(INP[6]))
Y[4] = (1/(I3*INP[0]*np.sin(INP[3]))) * (mu*INP[0]*np.cos(INP[5]))
Y[5] = -np.cos(INP[3])*Y[4]
Y[6] = INP[1]*(-np.cos(INP[5])*np.cos(INP[4]) + np.sin(INP[5])*np.sin(INP[4])*np.cos(INP[3]))
Y[7] = INP[1]*(-np.cos(INP[5])*np.sin(INP[4]) - np.sin(INP[5])*np.cos(INP[4])*np.cos(INP[3]))
Y[8] = INP[1]*(-np.sin(INP[5])*np.sin(INP[3]))
return Y # For odeint
t_start = 0.0
t_end = 20
t_step = 0.01
t_range = np.arange(t_start, t_end, t_step)
RES = spi.odeint(diff_eqs, INPUT, t_range)
However, I keep getting the same problem as shown here and especially the error message :
Excess work done on this call (perhaps wrong Dfun type)
I am not quite sure what it means but it looks like the solver have troubles solving the system. In any case, when I try to display the 3D path thanks to the XYZ coordinates, I just get 3 or 4 points where there should be something like 2000.
So my questions are : - Am I doing something wrong in my code ?
- If not, is there an other maybe more sophisticated tool to solve this sytem ?
- If not, is it even possible to get what I want from this system of ODEs ?
Thanks in advance
There are several issues:
if I copy the code, it does not run
the workaround you mention does not work with odeint, the given
solution uses ode
The scipy reference for odeint says:"For new code, use
scipy.integrate.solve_ivp to solve a differential equation."
the call RES = spi.odeint(diff_eqs, INPUT, t_range) should be
consistent to the function head def diff_eqs(t, INP) . Mind the
order: RES = spi.odeint(diff_eqs,t_range, INPUT)
There are some issues about to mathematical formulas too:
have a look at the 3rd formula on your picture. It has no tendency term, it starts with a zero - what does that mean ?
it's hard to check wether you have translated the formula correctly into code since the code does not follow the formulas strictly.
Below I tried a solution with scipy solve_ivp. In case A I'm able to run a pendulum, but in case B no meaningful solution for the boomerang can be found. So check the maths, I guess some error in the mathematical expressions.
For the graphics use pandas to plot all variables together (see code below).
import scipy.integrate as spi
import numpy as np
import pandas as pd
def diff_eqs_boomerang(t,Y):
INP = Y
dY = np.zeros((9))
dY[0] = (1/I3) * (Kz*L*(INP[1]**2+(L*INP[0])**2))
dY[1] = -(lamb/m)*INP[1]
dY[2] = -(1/(m * INP[1])) * ( Kz*L*(INP[1]**2+(L*INP[0])**2) + m*g) + (mu/I3)/INP[0]
dY[3] = (1/(I3*INP[0]))*(-mu*INP[0]*np.sin(INP[6]))
dY[4] = (1/(I3*INP[0]*np.sin(INP[3]))) * (mu*INP[0]*np.cos(INP[5]))
dY[5] = -np.cos(INP[3])*INP[4]
dY[6] = INP[1]*(-np.cos(INP[5])*np.cos(INP[4]) + np.sin(INP[5])*np.sin(INP[4])*np.cos(INP[3]))
dY[7] = INP[1]*(-np.cos(INP[5])*np.sin(INP[4]) - np.sin(INP[5])*np.cos(INP[4])*np.cos(INP[3]))
dY[8] = INP[1]*(-np.sin(INP[5])*np.sin(INP[3]))
return dY
def diff_eqs_pendulum(t,Y):
dY = np.zeros((3))
dY[0] = Y[1]
dY[1] = -Y[0]
dY[2] = Y[0]*Y[1]
return dY
t_start, t_end = 0.0, 12.0
case = 'A'
if case == 'A': # pendulum
Y = np.array([0.1, 1.0, 0.0]);
Yres = spi.solve_ivp(diff_eqs_pendulum, [t_start, t_end], Y, method='RK45', max_step=0.01)
if case == 'B': # boomerang
Y = np.array([omega0, V0, Psi0, theta0, phi0, psi0, X0, Y0, Z0])
print('Y initial:'); print(Y); print()
Yres = spi.solve_ivp(diff_eqs_boomerang, [t_start, t_end], Y, method='RK45', max_step=0.01)
#---- graphics ---------------------
yy = pd.DataFrame(Yres.y).T
tt = np.linspace(t_start,t_end,yy.shape[0])
with plt.style.context('fivethirtyeight'):
plt.figure(1, figsize=(20,5))
plt.plot(tt,yy,lw=8, alpha=0.5);
plt.grid(axis='y')
for j in range(3):
plt.fill_between(tt,yy[j],0, alpha=0.2, label='y['+str(j)+']')
plt.legend(prop={'size':20})

Finding the roots of an equation numerically returns the initial guess

I am trying to find the root of a cubic equation using fsolve. This is my code:
from scipy import *
from scipy.optimize import fsolve
import matplotlib.pyplot as plt
import numpy as np
#These are all parameters
g = 5.61
gamma = 6.45
kappa = 6.45
J = 6.45
rs = 10.0
m = 5.0*10**(-11)
wm = 2*3.14*23.4
r2 = np.linspace(0, 0.02, 1000)
deltaW = 0
A = 1j*g**2*(kappa + 1j*deltaW)*r2*r2/(m*wm**2)
B = J**2 + (1j*deltaW - gamma)*(1j*deltaW + kappa)
C = A + B
D = abs(C)*r2 - J*np.sqrt(2*kappa)*rs
def func(x):
D = abs(C)*r2 - J*np.sqrt(2*kappa)*rs
return D
x0 = fsolve(func, 0.0)
print x0
plt.plot(r2, D)
plt.show()
I can see from the plot that there is at least one r2 that makes D zero. However, the return value x0 I get from fsolve is always the guess value I set.
Can anyone tell me why this is happening and how to fix it?
You are passing to fsolve a function that isn't a function at all: it doesn't do anything with the inputs x. Yet, fsolve needs that, because it will test a series of values and each time check the return value from the function call with that test value. In your case, func(x) never changes, so fsolve stops with an error message of
The iteration is not making good progress, as measured by the improvement from the last ten iterations.
You could see that message if you would add full_output=True in the call to fsolve.
To solve it, define your function like this:
def func(x):
A = 1j*g**2*(kappa + 1j*deltaW)*x*x/(m*wm**2)
B = J**2 + (1j*deltaW - gamma)*(1j*deltaW + kappa)
C = A + B
D = abs(C)*x - J*np.sqrt(2*kappa)*rs
return D

Categories