I want to solve this differential equations with the given initial conditions:
(3x-1)y''-(3x+2)y'+(6x-8)y=0, y(0)=2, y'(0)=3
the ans should be
y=2*exp(2*x)-x*exp(-x)
here is my code:
def g(y,x):
y0 = y[0]
y1 = y[1]
y2 = (6*x-8)*y0/(3*x-1)+(3*x+2)*y1/(3*x-1)
return [y1,y2]
init = [2.0, 3.0]
x=np.linspace(-2,2,100)
sol=spi.odeint(g,init,x)
plt.plot(x,sol[:,0])
plt.show()
but what I get is different from the answer.
what have I done wrong?
There are several things wrong here. Firstly, your equation is apparently
(3x-1)y''-(3x+2)y'-(6x-8)y=0; y(0)=2, y'(0)=3
(note the sign of the term in y). For this equation, your analytical solution and definition of y2 are correct.
Secondly, as the #Warren Weckesser says, you must pass 2 parameters as y to g: y[0] (y), y[1] (y') and return their derivatives, y' and y''.
Thirdly, your initial conditions are given for x=0, but your x-grid to integrate on starts at -2. From the docs for odeint, this parameter, t in their call signature description:
odeint(func, y0, t, args=(),...):
t : array
A sequence of time points for which to solve for y. The initial
value point should be the first element of this sequence.
So you must integrate starting at 0 or provide initial conditions starting at -2.
Finally, your range of integration covers a singularity at x=1/3. odeint may have a bad time here (but apparently doesn't).
Here's one approach that seems to work:
import numpy as np
import scipy as sp
from scipy.integrate import odeint
import matplotlib.pyplot as plt
def g(y, x):
y0 = y[0]
y1 = y[1]
y2 = ((3*x+2)*y1 + (6*x-8)*y0)/(3*x-1)
return y1, y2
# Initial conditions on y, y' at x=0
init = 2.0, 3.0
# First integrate from 0 to 2
x = np.linspace(0,2,100)
sol=odeint(g, init, x)
# Then integrate from 0 to -2
plt.plot(x, sol[:,0], color='b')
x = np.linspace(0,-2,100)
sol=odeint(g, init, x)
plt.plot(x, sol[:,0], color='b')
# The analytical answer in red dots
exact_x = np.linspace(-2,2,10)
exact_y = 2*np.exp(2*exact_x)-exact_x*np.exp(-exact_x)
plt.plot(exact_x,exact_y, 'o', color='r', label='exact')
plt.legend()
plt.show()
Related
I have a plot like this, plotting a semicircle with x and y
I want to add arrows at each point like so (ignore the horrible paint job):
Is there an easy way to add arrows perpendicular to the plot?
Current code:
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
x = np.linspace(x0,x1,9)
y = k + np.sqrt(r**2 - (x-h)**2)
plt.scatter(x,y)
plt.xlim(-4,4)
plt.ylim(-4,4)
PERPENDICULAR TO THE TANGENT OF THE CURVE I'M SORRY I FORGOT TO ADD THIS
A point in space has no idea what "perpendicular" means, but assuming your y is some function of x that has a derivate, you can think of the derivate of the function at some point to be the tangent of the curve at that point, and to get a perpendicular vector you just need to rotate the vector counter-clockwise 90 degrees:
x1, y1 = -y0, x0
We know that these points come from a circle. So given three points we can easily find the center using basic geometry notions. If you need a refresher, take a look here.
For this particular case, the center is at the origin. Knowing the center coordinates, the normal at each point is just the vector from the center to the point itself. Since the center is the origin, the normals' components are just given by the coordinates of the points themselves.
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
x = np.linspace(x0, x1, 9)
y = k + np.sqrt(r**2 - (x-h)**2)
center = np.array([0.0, 0.0])
plt.scatter(x, y)
plt.quiver(x, y, x, y, width=0.005)
plt.xlim(-4, 4)
plt.ylim(-4, 4)
plt.show()
If you are in a hurry and you do not have time to implement equations, you could use the scikit-spatial library in the following way:
from skspatial.objects import Circle, Vector, Points
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
x = np.linspace(x0, x1, 9)
y = k + np.sqrt(r**2 - (x-h)**2)
points = Points(np.vstack((x, y)).T)
circle = Circle.best_fit(np.vstack((x, y)).T)
center = circle.point
normals = np.array([Vector.from_points(center, point) for point in points])
plt.scatter(x, y)
plt.quiver(x, y, normals[:, 0], normals[:, 1], width=0.005)
plt.xlim(-4, 4)
plt.ylim(-4, 4)
plt.show()
Postulate of blunova's and simon's answers is correct, generally speaking: points have no normal, but curve have; so you need to rely on what you know your curve is. Either, as blunova described it, by the knowledge that it is a circle, and computing those normal with ad-hoc computation from that knowledge.
Or, as I am about to describe, using the function f such as y=f(x). and using knowledge on what is the normal to such a (x,f(x)) chart.
Here is your code, written with such a function f
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
x = np.linspace(x0,x1,9)
def f(x):
return k + np.sqrt(r**2 - (x-h)**2)
y=f(x)
plt.scatter(x,y)
plt.xlim(-4,4)
plt.ylim(-4,4)
So, all I did here is rewriting your line y=... in the form of a function.
From there, it is possible to compute the normal to each point of the chart (x,f(x)).
The tangent to a point (x,f(x)) is well known: it is vector (1,f'(x)), where f'(x) is the derivative of f. So, normal to that is (-f'(x), 1).
Divided by √(f'(x)²+1) to normalize this vector.
So, just use that as entry to quiver.
First compute a derivative of your function
dx=0.001
def fprime(x):
return (f(x+dx)-f(x-dx))/(2*dx)
Then, just
plt.quiver(x, f(x), -fprime(x), 1)
Or, to have all vector normalized
plt.quiver(x, f(x), -fprime(x)/np.sqrt(fprime(x)**2+1), 1/np.sqrt(fprime(x)**2+1))
(note that fprime and the normalization part are all vectorizable operation, so it works with x being a arange)
All together
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
def f(x):
return k+ np.sqrt(r**2 - (x-h)**2)
dx=0.001
x = np.linspace(x0+dx,x1-dx,9)
y = f(x)
def fprime(x):
return (f(x+dx)-f(x-dx))/(2*dx)
plt.scatter(x,y)
plt.quiver(x,f(x), -fprime(x)/np.sqrt(fprime(x)**2+1), 1/np.sqrt(fprime(x)**2+1))
plt.xlim(-4,4)
plt.ylim(-4,4)
plt.show()
That is almost an exact copy of your code, but for the quiver line, and with the addition of fprime.
One other slight change, specific to your curve, is that I changed x range to ensure the computability of fprime (if first x is x0, then fprime need f(x0-dx) which does not exist because of sqrt. Likewise for x1. So, first x is x0+dx, and last is x1-dx, which is visually the same)
That is the main advantage of this solution over blunova's: it is your code, essentially. And would work if you change f, without assuming that f is a circle. All that is assume is that f is derivable (and if it were not, you could not define what those normal are anyway).
For example, if you want to do the same with a parabola instead, just change f
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
def f(x):
return x**2
dx=0.001
x = np.linspace(x0+dx,x1-dx,9)
y = f(x)
def fprime(x):
return (f(x+dx)-f(x-dx))/(2*dx)
plt.scatter(x,y)
plt.quiver(x,f(x), -fprime(x)/np.sqrt(fprime(x)**2+1), 1/np.sqrt(fprime(x)**2+1))
plt.xlim(-4,4)
plt.ylim(-2,5)
plt.show()
All I changed here is the f formula. Not need for a new reasoning to compute the normal.
Last remark: an even more accurate version (not forcing the approximate computation of fprime with a dx) would be to use sympy to define f, and then compute the real, symbolic, derivative of f. But that doesn't seem necessary for your case.
I am trying to use scipy to numerically solve the following differential equation
x''+x=\sum_{k=1}^{20}\delta(t-k\pi), y(0)=y'(0)=0.
Here is the code
from scipy.integrate import odeint
import numpy as np
import matplotlib.pyplot as plt
from sympy import DiracDelta
def f(t):
sum = 0
for i in range(20):
sum = sum + 1.0*DiracDelta(t-(i+1)*np.pi)
return sum
def ode(X, t):
x = X[0]
y = X[1]
dxdt = y
dydt = -x + f(t)
return [dxdt, dydt]
X0 = [0, 0]
t = np.linspace(0, 80, 500)
sol = odeint(ode, X0, t)
x = sol[:, 0]
y = sol[:, 1]
plt.plot(t,x, t, y)
plt.xlabel('t')
plt.legend(('x', 'y'))
# phase portrait
plt.figure()
plt.plot(x,y)
plt.plot(x[0], y[0], 'ro')
plt.xlabel('x')
plt.ylabel('y')
plt.show()
However what I got from python is zero solution, which is different from what I got from Mathematica. Here are the mathematica code and the graph
so=NDSolve[{x''(t)+x(t)=\sum _{i=1}^{20} DiraDelta (t-i \pi ),x(0)=0,x'(0)=0},x(t),{t,0,80}]
It seems to me that scipy ignores the Dirac delta function. Where am I wrong? Any help is appreciated.
Dirac delta is not a function. Writing it as density in an integral is still only a symbolic representation. It is, as mathematical object, a functional on the space of continuous functions. delta(t0,f)=f(t0), not more, not less.
One can approximate the evaluation, or "sifting" effect of the delta operator by continuous functions. The usual good approximations have the form N*phi(N*t) where N is a large number and phi a non-negative function, usually with a somewhat compact shape, that has integral one. Popular examples are box functions, tent functions, the Gauß bell curve, ... So you could take
def tentfunc(t): return max(0,1-abs(t))
N = 10.0
def rhs(t): return sum( N*tentfunc(N*(t-(i+1)*np.pi)) for i in range(20))
X0 = [0, 0]
t = np.linspace(0, 80, 1000)
sol = odeint(lambda x,t: [ x[1], rhs(t)-x[0]], X0, t, tcrit=np.pi*np.arange(21), atol=1e-8, rtol=1e-10)
x,v = sol.T
plt.plot(t,x, t, v)
which gives
Note that the density of the t array also influences the accuracy, while the tcrit critical points did not do much.
Another way is to remember that delta is the second derivative of max(0,x), so one can construct a function that is the twice primitive of the right side,
def u(t): return sum(np.maximum(0,t-(i+1)*np.pi) for i in range(20))
so that now the equation is equivalent to
(x(t)-u(t))'' + x(t) = 0
set y = x-u then
y''(t) + y(t) = -u(t)
which now has a continuous right side.
X0 = [0, 0]
t = np.linspace(0, 80, 1000)
sol = odeint(lambda y,t: [ y[1], -u(t)-y[0]], X0, t, atol=1e-8, rtol=1e-10)
y,v = sol.T
x=y+u(t)
plt.plot(t,x)
odeint :
does not handle sympy symbolic objects
it's unlikely it can ever handle Dirac Delta terms.
The best bet is probably to turn dirac deltas into boundary conditions: assume that the function is continuous at the location of the Dirac delta, but the first derivative jumps. Integrating over infinitesimal interval around the location of the delta function gives you the boundary condition for the derivative just left and just right from the delta.
I want to solve the equation in python over the time Interval I = [0,10] with initial condition (x_0, y_0) = (1,0) and the parameter values μ ∈ {-2, -1, 0, 1, 2} using the function
scipy.integrate.odeint
Then I want to plot the solutions (x(t;x_0,y_0), y(t;x_0,y_0)) in the xy-plane.
The originally given linear system is
dx/dt = y, x(0) = x_0
dy/dt = - x - μy, y(0) = y_0
Please see my code below:
import numpy as np
from scipy.integrate import odeint
sol = odeint(myode, y0, t , args=(mu,1)) #mu and 1 are the coefficients when set equation to 0
y0 = 0
myode(y, t, mu) = -x-mu*y
def t = np.linspace(0,10, 101) #time interval
dydt = [y[1], -y[0] - mu*y[1]]
return dydt
Could anyone check if I defined the callable function myode correctly? This function evaluates the right hand side of the ODE.
Also an syntax error message showed up for this line of code
def t = np.linspace(0,10, 101) #time interval
saying there is invalid syntax. Should I somehow use
for * in **
to get rid of the error message? If yes, how exactly?
I am very new to Python and ODE. Could anyone help me with this question? Thank you very much!
myode should be a function definition, thus
def myode(u, t, mu): x,y = u; return [ y, -x-mu*y]
The time array is a simple variable declaration/assignment, there should be no def there. As the system is two-dimensional, the initial value also needs to have dimension two
sol = odeint(myode, [x0,y0], t, args=(mu,) )
Thus a minimal modification of your script is
def myode(u, t, mu): x,y = u; return [ y, -x-mu*y]
t = np.linspace(0,10, 101) #time interval
x0,y0 = 1,0 # initial conditions
for mu in [-2,-1,0,1,2]:
sol = odeint(myode, [x0,y0], t, args=(mu,) )
x,y = sol.T
plt.plot(x,y)
a=5; plt.xlim(-a,a); plt.ylim(-a,a)
plt.grid(); plt.show()
giving the plot
Try using the solve_ivp method.
from scipy.integrate import solve_ivp
import matplotlib.pyplot as plt
import numpy as np
i = 0
u = [-2,-1,0,1,2]
for x in u:
def rhs2(t,y):
return [y[1], -1*y[0] - u[x]*y[1]]
value = u[i]
res2 = solve_ivp(rhs2, [0,10], [1,0] , t_eval=[0,1,2,3,4,5,6,7,8,9,10], method = 'RK45')
t = np.array(res2.t[1:-1])
x = np.array(res2.y[0][1:-1])
y = np.array(res2.y[1][1:-1])
fig = plt.figure()
plt.plot(t, x, 'b-', label='X(t)')
plt.plot(t, y, 'g-', label='Y(t)')
plt.title("u = {}".format(value))
plt.legend(loc='lower right')
plt.show()
i = i + 1
Here is the solve_ivp method Documentation
Here is a very similar problem with a better explanation.
In Python we solve a differential equation OD_H with an initial point y0 = od0 in a specific point z similar to the following code
def OD_H(od, z, c, b):
....
return ....
od = solve_ivp(lambda od, z: OD_H(od, z, c, b), t_span = [z1, z], y0 = [od0])['y'][-1][-1]
or
od = odeint(OD_H, od0, [0, z], args=(c, b))[-1]
So we have
answer of ODE OD_H(y0 = 0.69, z=0.153) = 0.59
My question is,
Now If I have the answer of OD_H = 0.59 and y0 = 0.69, how could I obtain z? It should be 0.153 but consider we don't know its value and we cannot do trial and error to find it.
I appreciate your help.
In this case, you are proposing a root problem where the solver function evaluated minus the desired answer is the function f(x) where f(x)=0.
Because ODE solver returns point arrays and not callable functions, you need to interpolate first the solution points. Then, this is used in root finding problem.
from scipy.integrate import solve_ivp # Recommended initival value problem solver
from scipy.interpolate import interp1d # 1D interpolation
from scipy.optimize import brentq # Root finding method in an interval
exponential_decay = lambda t, y: -0.5 * y # dy/dt = f(t, y)
t_span = [0, 10] # Interval of integration
y0 = [2] # Initial state: y(t=t_span[0])=2
desired_answer = 0.59
sol_ode = solve_ivp(exponential_decay, t_span, y0) # IVP solution
f_sol_ode = interp1d(sol_ode.t, sol_ode.y) # Build interpolated function
brentq(lambda x: f_sol_ode(x) - desired_answer, t_span[0], t_span[1])
I am trying to fit the differential equation ay' + by''=0 to a curve by varying a and b The following code does not work. The problem with curve_fit seems to be that lack of initial guess results in failure in fitting. I also tried leastsq. Can anyone suggest me other ways to fit such differential equation? If I don't have good guess curve_fit fails!
from scipy.integrate import odeint
from scipy.optimize import curve_fit
from numpy import linspace, random, array
time = linspace(0.0,10.0,100)
def deriv(time,a,b):
dy=lambda y,t : array([ y[1], a*y[0]+b*y[1] ])
yinit = array([0.0005,0.2]) # initial values
Y=odeint(dy,yinit,time)
return Y[:,0]
z = deriv(time, 2, 0.1)
zn = z + 0.1*random.normal(size=len(time))
popt, pcov = curve_fit(deriv, time, zn)
print popt # it only outputs the initial values of a, b!
Let's rewrite the equation:
ay' + by''=0
y'' = -a/b*y'
So this equation may be represented in this way
dy/dt = y'
d(y')/dt = -a/b*y'
The code in Python 2.7:
from scipy.integrate import odeint
from pylab import *
a = -2
b = -0.1
def deriv(Y,t):
'''Get the derivatives of Y at the time moment t
Y = [y, y' ]'''
return array([ Y[1], -a/b*Y[1] ])
time = linspace(0.0,1.0,1000)
yinit = array([0.0005,0.2]) # initial values
y = odeint(deriv,yinit,time)
figure()
plot(time,y[:,0])
xlabel('t')
ylabel('y')
show()
You may compare the resultant plots with the plots in WolframAlpha
If your problem is that the default initial guesses, read the documentation curve_fit to find out how to specify them manually by giving it the p0 parameter. For instance, curve_fit(deriv, time, zn, p0=(12, 0.23)) if you want a=12 and b=0.23 be the initial guess.