Generating and Solving Simultaneous ODE's in Python - python

I'm relatively new to Python, and am encountering some issues in writing a piece of code that generates and then solves a system of differential equations.
My approach to doing this was to create a set of variables and coefficients, (x0, x1, ..., xn) and (c0, c1 ,..., cn) repsectively, in a list with the function var(). Then the equations are constructed in EOM1(). The initial conditions, along with the set of equations, are all put together in EOM2() and solved using odeint.
Currently the code below runs, albeit not efficiently the reason for which I believe is because odeint runs through all the code with each interaction (that's something else I need to fix but isn't the main problem!).
import sympy as sy
from scipy.integrate import odeint
n=2
cn0list = [0.01, 0.05]
xn0list = [0.01, 0.01]
def var():
xnlist=[]
cnlist=[]
for i in range(n+1):
xnlist.append('x{0}'.format(i))
cnlist.append('c{0}'.format(i))
return xnlist, cnlist
def EOM1():
drdtlist=[]
for i in range(n):
cn1=sy.Symbol(var()[1][i])
xn0=sy.Symbol(var()[0][i])
xn1=sy.Symbol(var()[0][i+1])
eom=cn1*xn0*(1.0-xn1)-cn1*xn1-xn1
drdtlist.append(eom)
xi=sy.Symbol(var()[0][0])
xf=sy.Symbol(var()[0][n])
drdtlist[n-1]=drdtlist[n-1].subs(xf,xi)
return drdtlist
def EOM2(xn, t, cn):
x0, x1 = xn
c0, c1 = cn
f = EOM1()
output = []
for part in f:
output.append(part.evalf(subs={'x0':x0, 'x1':x1, 'c0':c0, 'c1':c1}))
return output
abserr = 1.0e-6
relerr = 1.0e-4
stoptime = 10.0
numpoints = 20
t = [stoptime * float(i) / (numpoints - 1) for i in range(numpoints)]
wsol = odeint(EOM2, xn0list, t, args=(cn0list,), atol=abserr, rtol=relerr)
My problem is that I had difficulty getting Python to treat the variables generated by Sympy appropriately. I got around this with the line
output.append(part.evalf(subs={'x0':x0, 'x1':x1, 'c0':c0, 'c1':c1}))
in EOM2(). Unfortunately, I do not know how to generalize this line to a list of variables from x0 to xn, and from c0 to cn. The same applies to the earlier line in EOM2(),
x0, x1 = xn
c0, c1 = cn
In other words I set n to an arbitrary number, is there a way for Python to interpret each element as it does with the ones I manually entered above? I have tried the following
output.append(part.evalf(subs={'x{0}'.format(j):var(n)[0][j], 'c{0}'.format(j):var(n)[1][j]}))
yet this yields the error that led me to use evalf in the first place,
TypeError: can't convert expression to float
Is there any way do what I want to, generate a set of n equations which are then solved with odeint?

Instead of using evalf you want to look into using sympy.lambdify to generate a callback for use with SciPy. You will need to create a function with the expected signature of odeint, e.g.:
y, params = sym.symbols('y:3'), sym.symbols('kf kb')
ydot = rhs(y, p=params)
f = sym.lambdify((y, t) + params, ydot)
yout = odeint(f, y0, tout, param_values)
We gave a tutorial on (among other things) how to use lambdify with odeint at the SciPy 2017 conference, the material is available here: http://www.sympy.org/scipy-2017-codegen-tutorial/
If you are open to use an external library to handle the function signatures of external solvers you may be interested in a library I've authored: pyodesys

If I understand correctly, you want to make an arbitrary number of substitutions in a SymPy expression. This is how it can be done:
n = 10
syms = sy.symbols('x0:{}'.format(n)) # an array of n symbols
expr = sum(syms) # some expression with those symbols
floats = [1/(j+1) for j in range(n)] # numbers to put in
expr.subs({symbol: value for symbol, value in zip(syms, floats)})
The result of subs is a float in this case (no evalf needed).
Note that the function symbols can directly create any number of symbols for you, via the colon notation. No need for a loop.

Related

Variables with indexes and sums with indexes in mosek

I have to find solutions to an integer programming problem:
I am using Mosek's Fusion API (Python). Now the constrains are easy to put in, I am more worried about the actual objective. The problem for me is: How can I tell mosek that I want to sum by all is, js or ks and define what they are, what are their boundaries, etc.?
This is a simplified version of a self-caching problem in the context of servers. So i here means a server, j means an object to cache, but in this version there's one object, so this I guess is not important. k means server too, so e.g. d(ik) means the distance from the server i to the server k.
But whatever I want to achieve, I don't know how to write this objective. For now I have something like this:
from mosek.fusion import Domain, Model, Expr, ObjectiveSense
alpha = 4 # alpha is the same for all i and j
demand = 1 # w is the same for all i and k
n = 6 # number of servers
distances_matrix = [[...], [...], ...]
with Model("lo1") as M:
x = M.variable("x", n, Domain.integral(Domain.inRange(0, 1)))
y = M.variable("y", n, Domain.integral(Domain.inRange(0, 1)))
alpha_times_x = Expr.mul(alpha, x)
demand_times_dist_times_y = Expr.mul(demand, distances_matrix, y)
M.objective("obj", ObjectiveSense.Minimize, )
M.solve()
print(x.level())
print(y.level())
Now of course the demand_times_dist_times_y is wrong, because I want to get the distance from i to k from the matrix. And the x above is fine since xs are: {x0, x1, x2, x3, x4, x5, x6}, but the ys would have to be {y11, y12, y13, y14, y15, y16, y21, y22, ..., y66}, so I guess I defined them wrong.
So e.g. how can I define that i,k are in {1,2,3,4,5,6} and create an Expr.sum by e.g. k? And how would I define those two sums at the beginning of the objective?
I don't know if that answers the question, but if you have, say
x = M.variable("x", n, Domain.integral(Domain.inRange(0, 1)))
then sum_i x_i is obtained with
Expr.sum(x)
Similarly, if now alpha is a numerical array of length n then sum_i (alpha_i*x_i) is obtained with
Expr.sum( Expr.mulElm(alpha,x) )
or even
Expr.dot( alpha, x )
and so on. You never explicitly specify the summation index, you are summing all entries of whatever appears inside the Expr.sum and similar methods.

How can I modify this differential equation solver to solve for a large number of variables?

I would like to modify the following code so that it can solve for thousands of variables with thousands of couple differential equations. The problem is that I would like to be able to import the variable list (y), ideally as a numpy array, but acceptably as a regular list. The list will be massive, so I don't want to define it in the function. If I do something like define a='n','c1',... and then set a=y, python complains that the variables are the wrong type. If I define a= n, c1,....python complains that I haven't defined the variables. If you have any advice on how to import the very large variable list for this function as a list or numpy array that would be great.
import numpy as np
from scipy.integrate import odeint
def kinetics(y,t,b1,b2):
n,c1=y
dydt=[(b1*n)+(b2*c1),(b1*n)-(c1)]
return dydt
b1=0.00662888
b2=0.000239997
n0=1
c1_0=1
y0=[n0,c1_0]
t = np.linspace(0, 10, 10)
sol = odeint(kinetics, y0, t, args=(b1,b2))
print(sol)
This sort of renaming and use of individual python objects for each step is likely to be slow, especially if you can use numpy array vectorization for parts of the computation. Note that your current code can be reformulated into a linear algebra problem which could be solved very efficiently using numpy, assuming this is true for the rest of the ODE structure. I warn against the path you want to take.
But, assuming that nothing can be vectorized in this way, and you want to continue down this path, you could define a class to store all your data and create an instance of said class with the current state of y at each function call. You could store the class definition in a separate python module to keep the problem definition tidy if needed.
If using python 3:
import numpy as np
from scipy.integrate import odeint
class vars:
def __init__(self, *args):
(self.n, self.c1) = args
def kinetics(y,t,b1,b2):
v = vars(*y)
dydt=[
(b1 * v.n) + (b2 * v.c1),
(b1 * v.n) - (v.c1)
]
return dydt
b1=0.00662888
b2=0.000239997
n0=1
c1_0=1
y0=[n0,c1_0]
t = np.linspace(0, 10, 10)
sol = odeint(kinetics, y0, t, args=(b1,b2))
print(sol)

How to pass the i-ith value of an array to scipy.integrate using dopri5 (runge-kutta45)?

I am trying to use sci.integrate python module to solve for V (the voltage on a capacitor) a simple first order in time ODE:
def f(t, V, Vin, vtime, R):
return (Vin[vtime==t][0]-V)/(R*get_alpha(V))
R = .5
V0 = 0.
t0, dt, tmax = vtime[0], vtime[1]-vtime[0], vtime[-1]
result, time = np.zeros_like(V), vtime
r = ode(f).set_integrator('dopri5')
r.set_initial_value(V0, t0).set_f_params(V, vtime, R)
i = 0
while (r.successful()) & (r.t < tmax):
result[i] = r.integrate(r.t+dt)[0]
i+=1
As you can see my right-hand-side functional depends on an input voltage (stored in an array Vin) and a constant resistance (R), both of which I would need to pass to the function within the solver as an argument at each time.
The example given on the scipy documentation page is not clear enough to me as I am not able to simply call r.set_f_params(Vin, R).
What is the proper way to set those parameters?
You will need to implement some interpolation formula for Vin. In the most simple case,
def getVin(t): k = int( (t-t0Vin)/dtVin ); return Vin[k];
where of course you have to provide the paramters t0Vin, dtVin of the sampling times of the Vin samples.
For more general situations use the interpolation function numpy.interp or scipy.interpolate.interp1d.
Parameters other than the time and function itself can be set by r.set_initial_value(V0, t0).set_f_params(V, vtime, R) but their names in the function have to be arg1, arg2, etc...

Scipy odeint giving index out of bounds errors

I am trying to solve a differential equation in python using Scipy's odeint function. The equation is of the form dy/dt = w(t) where w(t) = w1*(1+A*sin(w2*t)) for some parameters w1, w2, and A. The code I've written works for some parameters, but for others I get given index out of bound errors.
Here's some example code that works
import numpy as np
import scipy.integrate as integrate
t = np.arange(1000)
w1 = 2*np.pi
w2 = 0.016*np.pi
A = 1.0
w = w1*(1+A*np.sin(w2*t))
def f(y,t0):
return w[t0]
y = integrate.odeint(f,0,t)
Here's some example code that doesn't work
import numpy as np
import scipy.integrate as integrate
t = np.arange(1000)
w1 = 0.3*np.pi
w2 = 0.005*np.pi
A = 0.15
w = w1*(1+A*np.sin(w2*t))
def f(y,t0):
return w[t0]
y = integrate.odeint(f,0,t)
The only thing that changes between these is that the three parameters w1, w2, and A are smaller in the second, but the second one always gives me the following error
line 13, in f
return w[t0]
IndexError: index 1001 is out of bounds for axis 0 with size 1000
This error continues even after restarting python and running the second code first. I've tried with other parameters, some seem to work, but others give me different index out of bounds errors. Some say 1001 is out of bounds, some say 1000, some say 1008, ect.
Changing the initial condition on y (the second input for odeint, which I have as 0 on the above codes) also changes the number on the index error, so it might be that I'm misunderstanding what to put here. I wasn't told what the initial conditions should be other than that y is used as a phase of a signal, so I presumed it to be initially 0.
What you want to do is
def w(t):
return w1*(1+A*np.sin(w2*t))
def f(y,t0):
return w(t0)
Array indices are typically integers, time arguments and values of solutions of differential equations are typically real numbers. Thus there is some conceptual difficulty in invoking w[t0].
You might also try to integrate directly the function w, there is no inherent difficulty in this example.
As for coupled systems, you solve them as coupled systems.
def w(t):
return w1*(1+A*np.sin(w2*t))
def f(y,t):
wt = w(t)
return np.array([ wt, wt*sin(y[1]-y[0]) ])

Error from program using SymPy

I would like to use the SymPy packages to find the roots of a fourth-order polynomial equation. Subsequently I would like to plot these roots as a function of the parameters of the polynomial equations. I have written the piece of code below. It seems to calculate everything fine, but I cannot plot the results as I get the error "x and y are not of the same dimension". I think it has something to do with my usage of SymPy, because normally it always works like this.
from sympy import *
from math import *
from numpy import *
import pylab as lab
def RootFunc(root, m, c0, r, En):
A = 2*(m**2 - 0.25 - c0**2)/r**2 + 4
B = 8*En*c0/r
C = -4 - 4*En**2 + ((c0**2 + m**2 -.25)/r**2 + 2)**2
return root.subs([(a,A),(b,B),(c,C)])
# Define necessary symbols
x = symbols('x')
a, b, c = symbols('a b c')
En, r = symbols("En r")
# Fix constants
m = 0
c0 = -2
# Solve equation
eq = x**4 + a*x**2 + b*x + c
sol = solve(eq,x)
root1 = sol[0]
grid = linspace(1,10,10)
sol1 = [RootFunc(root1, m, c0, r, .5) for r in grid]
lab.figure(1)
lab.plot(grid,sol1)
lab.show()
Are you sure that you a running the same script that you've given us here?
I say this because I can copy and paste your example verbatim and it works with absolutely no issue.
Once you've checked, could you post which version of Python, SymPy, NumPy and Matplotlib you're using please?
Edit: I think something got slightly lost in translation when you put up your first minimal working example (MWE). The solution in your MWE was real-valued so it didn't have the same issue as your actual program. However, onto the solution:
Your main issue here is this line
sol1 = [RootFunc(root1, m, c0, help, .5) for help in grid]
RootFunc in this case returns a sympy.core.add.Add which pylab has no concept of and therefore can't plot. In your MWE you recognised that this was the issue and tried calling N() and real() on the return value. Unfortunately this just wraps the sympy.core.add.Add object in a NumPy array. When Pylab tries to plot this array it finds a sympy.core.add.Add object which it has no concept of and therefore just throws an error.
Fortunately SymPy allows you to turn a sympy.core.add.Add object into a number using int(), float() or complex(). Since your roots are complex you should use complex() on the return value and then to get the real component use .real.
So to get it too work you should just change the above line to
sol1 = [complex(RootFunc(root1, m, c0, help, .5)).real for help in grid]
Edit2: Just a quick point about style. You're using a lot of wildcard imports in your code (e.g. from numpy import *), which is fine if you're the only person using the code, it does make it neater after all.
However, if you're going to be posting on a forum like this please could you try to use qualified imports (like you've done for pylab) so that we don't have to go trudging through the documentation for all the modules you've used to try and figure out what you're doing.
One other thing: when you encounter a problem like this it really helps to execute it line by line in the python shell and examine the types (with type()) and values (with print() or repr()) of your variables. For this purpose I would strongly urge you to learn how to use IPython as it can really help.
You might be breaking some things with your imports. Can you try this:
import sympy as sy
import numpy as np
import pylab as lab
def RootFunc(root, A, B):
return root.subs([(a,A),(b,B)])
# Define necessary symbols
x = sy.symbols('x')
a, b = sy.symbols('a b')
# Solve equation
eq = x**4 + a*x**2 + b*x
sol = sy.solve(eq,x)
root1 = sol[1] # first element is trivial solution, so take second one
grid = np.linspace(1,10,10)
sol1 = [np.real(sy.N(RootFunc(root1, 1, x))) for x in grid]
lab.figure(1)
lab.plot(grid,sol1)
lab.show()

Categories