using scipy solve_ivp for a function with extra arguments - python

I am trying to solve an initial value problem with solve_ivp. The problem is that my function f(x,y)=dy/dx contain extra arguments. I tried to use this:
Pass args for solve_ivp (new SciPy ODE API)
but it keeps giving me errors. Here is the code:
from numpy import arange
import math
import numpy as np
from scipy.integrate import solve_ivp
from numpy import pi
sigmav = 1.0e-9
Mpl = 1.22e19
ms=100.0
gg=100.0
gs=106.75
def Yeq(x):
return 0.145*(gg/gs)*(x)**(3/2)*np.exp(-x)
def ss(x,m_dm):
return (2/45)*((pi)**2)*gs*(m_dm**3/x**3)
def hubb(x,m_dm):
return 1.66*(gg**(1/2))*(m_dm**2/Mpl)*(1/x**2)
def derivada(x,yl,m_dm,σv):
return -(σv*ss(x,m_dm)*((yl**2)-(Yeq(x)**2)))/(x*hubb(x,m_dm))
xx=np.logspace(np.log10(3),3,100)
y00 = Yeq(xx[0])
x00 = xx[0]
sigmav = 1.7475e-9
ms=100.0
solucion1 = solve_ivp(fun=lambda x, y: derivada(x, y, args=(ms,sigmav),[np.min(xx),np.max(xx)],[y00],method='BDF',rtol=1e-10,atol=1e-10))
When I try to run the cell for solucion1, it returns the following:
File "<ipython-input-17-afcf6c3782a9>", line 6
solucion1 = solve_ivp(fun=lambda x, y: derivada(x, y, args=(ms,sigmav),[np.min(xx),np.max(xx)],[y00],method='BDF',rtol=1e-10,atol=1e-10))
^
SyntaxError: positional argument follows keyword argument
It must be something very easy but I am just starting to work with python, so please help me with this.

You simply mis-placed a closing parenthesis. As derivada has no named args, remove the args statement there, the lambda expression already binds these additional parameters. Also, the passed function is not a named argument
solucion1 = solve_ivp(lambda x, y: derivada(x, y, ms, sigmav),[np.min(xx),np.max(xx)], [y00], method='BDF', rtol=1e-10, atol=1e-10)
or in the form that you cite from the github discussion, where the parameter list is constructed elsewhere
args = ( ms, sigmav )
solucion1 = solve_ivp(lambda x, y: derivada(x, y, *args),[np.min(xx),np.max(xx)], [y00], method='BDF', rtol=1e-10, atol=1e-10)

The error message gives the answer: all positional arguments need to come before keyword arguments. In your case, I'd suggest using partial:
from functools import partial
f = partial(derivada, m_dm=ms, σv=sigmav)
solucion1 = solve_ivp(f, [np.min(xx),np.max(xx)],[y00],method='BDF',rtol=1e-10,atol=1e-10)

This issue has been resolved in SciPy 1.4+.
https://github.com/scipy/scipy/issues/8352#issuecomment-619243137
You should be able to add extra arguments to the function as you did with odeint, lambda function not required. I think this is what you intended for the solution
solucion1 = solve_ivp(fun=derivada,
t_span=[np.min(xx),np.max(xx)],
y0=[y00],
args=(ms,sigmav),
method='BDF',
rtol=1e-10,
atol=1e-10)

Related

Problem with integral calculation integral with OOP in Python

First, i would like to calculate the integral
and then i' d like to plot a function F(x) but i have the following error:
F() missing 1 required positional argument: 't'
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import quad
x=np.arange(-20,20,0.5)
class NLA():
def __init__(self,b=-20*10**-15,E=200*10**-9,w0=18*10**-6,t=50*10**-15,s=1.76,A=0.2,L=0.1):
self.b=b
self.E=E
self.w0=w0
self.t=t
self.s=s
self.A=A
self.L=L
self.I0=self.s*self.E/(self.t*np.pi*self.w0**2)
self.a0=(1/self.L)*np.log(10)*self.A
self.Leff=0.01*(1-np.exp(-self.L*self.a0))/self.a0
self.c=self.b*self.I0*self.Leff
def f(self,t,x):
return np.log(1+((self.c*np.exp(-t**2))/(1+self.x**2)))
def F(self,x,t):
return ((1+x**2)/(self.c*np.pi**(1/2)))*quad(self.f(t),-np.inf,np.inf,args=(x))[0]
nla=NLA()
T=nla.F(x)
def F(self,x,t):
return ((1+x**2)/(self.c*np.pi**(1/2)))*quad(self.f(t),-np.inf,np.inf,args=(x))[0]
change to
def F(self,x):
temp = quad(self.f,-np.inf,np.inf,args=(x,))[0]
return ((1+x**2)/(self.c*np.pi**(1/2)))*temp
quad is supposed to get a function, in this case self.f. quad will call it with the integration variable, t and the args tuple.
I split of temp, so the quad calls is easier to see (and if needed to debug).
F should not take t as an argument.
When using functions like quad, follow the documentation carefully. If anything is confusing, practice with its examples. I don't think the OOP is causing problems, except that all those "self" may be confusing you, and obscuring the basic quad calls.

Python / GPyOpt: Optimizing only one argument

I´m currently trying to find the minimum of some function f(arg1, arg2, arg3, ...) via Gaussian optimization using the GPyOpt module. While f(...) takes many input arguments, I only want to optimize a single one of them. How do you do that?
My current "solution" is to put f(...) in a dummy class and specify the not-to-be-optimized arguments while initializing it. While this is arguably the most pythonesque way of solving this problem, it`s also way more complicated than it has any right to be.
Short working example for a function f(x, y, method) with fixed y (a numeric) and method (a string) while optimizing x:
import GPyOpt
import numpy as np
# dummy class
class TarFun(object):
# fix y while initializing the object
def __init__(self, y, method):
self.y = y
self.method = method
# actual function to be minimized
def f(self, x):
if self.method == 'sin':
return np.sin(x-self.y)
elif self.method == 'cos':
return np.cos(x-self.y)
# create TarFun object with y fixed to 2 and use 'sin' method
tarFunObj = TarFun(y=2, method='sin')
# describe properties of x
space = [{'name':'x', 'type': 'continuous', 'domain': (-5,5)}]
# create GPyOpt object that will only optimize x
optObj = GPyOpt.methods.BayesianOptimization(tarFunObj.f, space)
There definitely has to be a simpler way. But all the examples I found optimize all arguments and I couldn't figure it out reading the code on github (I though i would find the information in GPyOpt.core.task.space , but had no luck).
GPyOpt supports this natively with context. You describe the whole domain of your function, and then fix values of some of the variables with a context dictionary when calling optimization routine. API looks like that:
myBopt.run_optimization(..., context={'var1': .3, 'var2': 0.4})
More details can be found in this tutorial notebook about contextual optimization.
I would check out the partial function from the functools standard library. It allows you to partially specify a function, so for example:
import GPyOpt
import numpy as np
from functools import partial
def f(x, y=0):
return np.sin(x - y)
objective = partial(f, y=2)
space = [{'name': 'x', 'type': 'continuous', 'domain': (-5, 5)}]
opt = GPyOpt.methods.BayesianOptimization(
objective, domain=space
)

TypeError: deriv() takes 2 positional arguments but 4 were given

I'm getting the above error. I understand in principle what it means, but can't really see how it applies to my code
#project starts here
import numpy as np
import scipy.integrate
import matplotlib.pyplot as plt
from numpy import pi
from scipy.integrate import odeint
def deriv(cond,t):
for q in range (0,N):
i=6*q
dydt[i]=cond[i+3]
dydt[i+1]=cond[i+4]
dydt[i+2]=cond[i+5]
r=sqrt((cond[i])**2 +(cond[i+1])**2 +(cond[i+2])**2)
dydt[i+3]=-G*M*cond[i]/(r**3)
dydt[i+4]=-G*M*cond[i+1]/(r**3)
dydt[i+5]=-G*M*cond[i+2]/(r**3)
return dydt
G=1
M=1
N=12
vmag=((G*M)/(2))**(0.5)
theta = np.linspace(0,2*pi,N)
x=2*np.cos(theta)
y=2*np.sin(theta)
vx=-vmag*np.sin(theta)
vy=vmag*np.cos(theta)
z=np.zeros(N)
vz=np.zeros(N)
t=np.linspace(0,30,100)
cond=list(item for group in zip(x,y,z,vx,vy,vz) for item in group)
sln=odeint(deriv, cond, t, args=(G,M))
Any ideas where it is coming from? I feel like I have given the correct number of arguments.
You are sending 4 arguments to deriv. Per the odeint docs, you have a function deriv whose first two arguments must be y and t. When you call odeint(deriv,cond,t,...) the cond and t are automatically sent as the first two arguments to deriv. All you need to do is to make deriv(cond,t,G,M).
If you look documantation for odeint [1], you will see that your fucntion to call in oneint must be in form func(y, t0, ...). So when you call odeint(deriv, cond, t, args=(G,M)) it actually call your function as deriv(cond,t,G,m). But your function takes just 2 argument.
[1] http://docs.scipy.org/doc/scipy-0.17.0/reference/generated/scipy.integrate.odeint.html

scipy fmin operands could not be broadcast together with shapes

i'm trying to learn about optimization in Python so i've written some code to test out the fmin function.
However i keep receiving the following error:
ValueError: operands could not be broadcast together with shapes (1,2) (100,)
I can tell the issue is to do with the dimensions of my arguments but I'm not sure how to rectify it. Rather than a lambda function I also tried to def a function but I still get the same error.
I'm sure it's something pretty basic but I can't seem to understand it. Any help wold be greatly appreciated!
import numpy as np
import pandas as pd
from scipy.stats.distributions import norm
from scipy.optimize import fmin
x = np.random.normal(size=100)
norm_1 = lambda theta,x: -(np.log(norm.pdf(x,theta[0],theta[1]))).sum()
def norm_2(theta,x):
mu = theta[0]
sigma = theta[1]
ll = np.log(norm.pdf(x,mu,sigma)).sum()
return -ll
fmin(norm_1,np.array([0,1]),x)
fmin(norm_2,np.array([0,1]),x)
The docs for fmin say:
Definition: fmin(func, x0, args=(), xtol=0.0001, ftol=0.0001, maxiter=None, maxfun=None, full_output=0, disp=1, retall=0, callback=None)
...
args : tuple, optional
Extra arguments passed to func, i.e. ``f(x,*args)``.
Therefore, the third argument, args, should be a tuple:
In [45]: fmin(norm_1,np.array([0,1]),(x,))
Warning: Maximum number of function evaluations has been exceeded.
Out[45]: array([-0.02405078, 1.0203125 ])
(x, ) is a tuple containing one element, x.
The docs say f(x, *args) gets called. Which mean in your case
norm_1(np.array([0,1]), *(x,))
will get called, which is equivalent to
norm_1(np.array([0,1]), x)

Error: [only length-1 arrays can be converted to Python scalars] when changing variable order

Dear Stackoverflow Community,
I am very new to Python and to programming in general, so please don't get mad when I don't get your answers and ask again.
I am trying to fit a curve to experimental data with scipy.optimization.curve_fit. This is my code:
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as nm
from __future__ import division
import cantera as ct
from matplotlib.backends.backend_pdf import PdfPages
import math as ma
import scipy.optimize as so
R = 8.314
T = nm.array([700, 900, 1100, 1300, 1400, 1500, 1600, 1700])
k = nm.array([289, 25695, 763059, 6358040, 14623536, 30098925, 56605969, 98832907])
def func(A, E, T):
return A*ma.exp(-E/(R*T))
popt, pcov = so.curve_fit(func, T, k)
Now this code works for me, but if I change the function to:
def func(T, A, E)
and keep the rest I get:
TypeError: only length-1 arrays can be converted to Python scalars
Also I am not really convinced by the Parameter solution of the first one.
Can anyone tell me what happens when you change the variable order?
I got the same problem and found the cause and its solution:
The problem lies on the implementation of Scipy. After the optimal parameter has been found, Scipy calls your function with the input array xdata as first argument. That is, it calls func(xdata, *args), and the function complains with a type error because xdata is not an scalar. For example:
from math import erf
erf([1, 2]) # TypeError
erf(np.array([1, 2])) # TypeError
To avoid the error, you can add custom code for supporting arrays, or better, as suggested in the answer of Joris, use numpy functions because they have support for scalars and arrays.
If the math function is not in numpy , like erf or any custom function you coded, then I recommend you instead of doing from math import erf, to do as follows:
from math import erf as math_erf # only supports scalars
import numpy as np
erf = np.vectorize(math_erf) # adds array support
def fit_func(t,s):
return 0.5*(1.0-erf(t/(np.sqrt(2)*s)))
X = np.linspace(-5,5,1000)
Y = np.array([fit_func(x,1) for x in X])
curve_fit(fit_func, X, Y)
The curve_fit function from scipy does not handle very well embedded functions from the math module. When you change the exponential function to the numpy exponential function you don't get the error:
def func(A, E, T):
return A*np.exp(-E/(R*T))
I wonder whether you data shows an exponential decay of rate. The mathematical model may not be the most suitable one.
See the doc string of curve_fit
f : callable
The model function, f(x, ...). It must take the independent variable as the first argument and the parameters to fit as separate remaining arguments.
since your formula is essentially: k=A*ma.exp(-E/(R*T)), the right order of parameters in func should be (T, A, E) or (T, E, A).
Regarding the order of A and E, they don't really matter. If you flip them, the result will get flipped as well:
>>> def func(T, A, E):
return A*ma.exp(-E/(R*T))
>>> so.curve_fit(func, T, k)
(array([ 8.21449078e+00, -5.86499656e+04]), array([[ 6.07720215e+09, 4.31864058e+12],
[ 4.31864058e+12, 3.07102992e+15]]))
>>> def func(T, E, A):
return A*ma.exp(-E/(R*T))
>>> so.curve_fit(func, T, k)
(array([ -5.86499656e+04, 8.21449078e+00]), array([[ 3.07102992e+15, 4.31864058e+12],
[ 4.31864058e+12, 6.07720215e+09]]))
I didn't get your typeerror at all.

Categories