Interactive matplotlib widget not updating with scipy ode solver - python

I have been using the interactive matplotlib widgets to visualise the solution of differential equations. I have got it working with the odeint function in scipy, however have not managed to get it to update with the ode class. I would rather use the latter as it has greater control over which solver is used.
The following code is used to solve a differential that is an exponential decay. The y0 is the amplitude of the decay. The code stops working when solver.integrate(t1) is called inside the update function. I'm not sure why this is.
from scipy.integrate import ode
# solve the system dy/dt = f(t, y)
def f(t, y):
return -y / 10
# Array to save results to
def solout(t, y):
sol.append([t, *y])
solver = ode(f).set_integrator('dopri5')
solver.set_solout(solout)
# Initial conditions
y0 = [1] # Initial amplitude
t0 = 0 # Start time
t1 = 20 # End time
fig = plt.figure(figsize=(10, 6))
fig.subplots_adjust(left=0.25, bottom=0.4)
ax = plt.subplot(111)
# solve the DEs
solver.set_initial_value(y0, t0)
sol = []
solver.integrate(t1)
sol = np.array(sol)
t = sol[:, 0]
y = sol[:, 1]
l, = plt.plot(t, y, lw=2, color='red')
plt.axis([0, 20, 0, 1.1])
plt.xlabel('Time (ms)')
plt.ylabel('n1(t)')
plt.grid()
axcolor = 'lightgoldenrodyellow'
axn1 = plt.axes([0.25, 0.1, 0.65, 0.03], axisbg=axcolor)
sn1 = Slider(axn1, 'y(0)', 0, 1.0, valinit=1)
def update(val):
y0 = [sn1.val]
solver.set_initial_value(y0, t0)
sol = []
solver.integrate(t1)
sol = np.array(sol)
t = sol[:, 0]
y = sol[:, 1]
l.set_data(t, y)
plt.draw()
sn1.on_changed(update)

I guess it's always wise to separate calculations from plotting. Therefore first try to solve the ODE with some given initial conditions. Once that works, try the plotting and interactive stuff.
In your case we would build a function that solves the ODE and then use this function with different initial conditions also in the plotting updates.
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
import numpy as np
from scipy.integrate import ode
# solve the system dy/dt = f(t, y)
def f(t, y):
a = np.zeros((1,1))
a[0] = -y / 10.
return a
#define a function to solve the ODE with initial conditions
def solve(t0, t1, y0, steps=210):
solver.set_initial_value([y0], t0)
dt = (t1 - t0) / (steps - 1)
solver.set_initial_value([y0], t0)
t = np.zeros((steps, 1))
Y = np.zeros((steps, 1))
t[0] = t0
Y[0] = y0
k = 1
while solver.successful() and k < steps:
solver.integrate(solver.t + dt)
t[k] = solver.t
Y[k] = solver.y[0]
k += 1
return t, Y
# set the ODE integrator
solver = ode(f).set_integrator("dopri5")
# Initial conditions
y0 = 1. # Initial amplitude
t0 = 0. # Start time
t1 = 20. # End time
#solve once for given initial amplitude
t, Y = solve(t0, t1, y0)
fig = plt.figure(figsize=(10, 6))
fig.subplots_adjust(left=0.25, bottom=0.4)
ax = plt.subplot(111)
l, = plt.plot(t, Y, lw=2, color='red')
plt.axis([0, 20, 0, 1.1])
plt.xlabel('Time (ms)')
plt.ylabel('n1(t)')
plt.grid()
axn1 = plt.axes([0.25, 0.1, 0.65, 0.03], axisbg='#e4e4e4')
sn1 = Slider(axn1, 'y(0)', 0, 1.0, valinit=1)
def update(val):
#solve again each time
t, Y = solve(t0, t1, sn1.val)
l.set_data(t, Y)
plt.draw()
sn1.on_changed(update)
plt.show()

Related

How to create an animation with a filled 'span?

I've created an animated plot of a wavefunction, psi:
def psi(x, t):
real = 0.4*np.cos(0.4*x - 0.08*t) + 0.6*np.cos(0.6*x - 0.18*t)
imag = 0.4*np.sin(0.4*x - 0.08*t) + 0.6*np.sin(0.6*x - 0.18*t)
square = real**2 + imag**2
return real, imag, square
I've then animated it successfully, however on adding the axvspan fill I've encountered an issue:
x = np.linspace(-10, 1000, 10000)
fig, (ax1, ax2) = plt.subplots(2,1)
line1, = ax1.plot([], [])
line2, = ax1.plot([], [])
line3, = ax2.plot([], [])
line = [line1, line2, line3]
def animate(i):
y1, y2, y3 = psi(x, t=i/2)
line1.set_data(x, y1)
line2.set_data(x, y2)
line3.set_data(x, y3)
spline = UnivariateSpline(x, y3-max(y3)/2, s=0)
r1, r2 = spline.roots()
ax2.axvspan(r1, r2, facecolor='b', alpha=0.5)
plt.legend(['Max Probability = %1.3f' % (max(y3))])
return line,
anim = animation.FuncAnimation(fig, animate, frames=600, interval = 100, blit=False, repeat=False)
It starts like
and it ends like .
Every iteration of the animation function, the fill increases across the page having started off as filling half the graph (I'd include a gif but at the moment that's a struggle I'm having with anaconda). I'm working under the assumption this is because the axes don't clear properly, however with the blit=false I assumed this wouldn't be a problem?
As asked for - the full psi function is detailed below:
n = 15
amp_scale = np.linspace(0, 0.8, n)
amp_init = norm.pdf(amp_scale, 0.4, 0.2)
#normalise wavefunction to prob=1
amp = []
for i in range(n):
amp_val = amp_init[i]/sum(amp_init)
amp.append(amp_val)
k = np.linspace(1.4, 2.6, n)
def psi (x, t=1, n=1, a = 1, k = 1, m = 1):
psi_real = 0
psi_imag = 0
for i in range(n):
a_val = a[i]
k_val = k[i]
w = (k_val**2)/(2*m)
psi_real+=a_val*np.cos(k_val*x - w*t)
psi_imag+=a_val*np.sin(k_val*x - w*t)
psi_squared = psi_real**2 + psi_imag**2
return psi_real, psi_imag, psi_squared
In your current code, a new axvspan() is continually added, never removed. You could explicitly remove the old span inside animate(). Or, similarly to what happens to the lines, update the position. A span is internally represented as a polygon, of which the coordinates can be set via .set_xy().
The function psi in the post doesn't seem to be the same as the function that generated the example plots. This also made that I couldn't get to work to calculate the spline and the roots. I replaced them by some simpler positions to show how the span can be updated during the animation.
The code also adds explicit x and y limits, as they weren't set in the question's code.
import matplotlib.pyplot as plt
from matplotlib import animation
import numpy as np
from scipy.interpolate import UnivariateSpline
def psi(x, t):
# the function from the question is adapted to more resemble the plot
real = (0.4 * np.cos(0.4 * x - 0.08 * t) + 0.6 * np.cos(0.6 * x - 0.18 * t)) * np.exp(- (x - t) ** 2 / 5000)
imag = (0.4 * np.sin(0.4 * x - 0.08 * t) + 0.6 * np.sin(0.6 * x - 0.18 * t)) * np.exp(- (x - t) ** 2 / 5000)
square = real ** 2 + imag ** 2
return real, imag, square
x = np.linspace(-10, 1000, 10000)
fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True)
line1, = ax1.plot([], [])
line2, = ax1.plot([], [])
line3, = ax2.plot([], [])
line = [line1, line2, line3]
span1 = ax2.axvspan(0, 0, facecolor='b', alpha=0.2)
ax1.set_xlim(x[0], x[-1]/2)
ax1.set_ylim(-1, 1)
ax2.set_ylim(0, 1.1)
def animate(i):
y1, y2, y3 = psi(x, t=i / 2)
line1.set_data(x, y1)
line2.set_data(x, y2)
line3.set_data(x, y3)
# this didn't work for me, spline.roots() gave me a long array of values
spline = UnivariateSpline(x, y3 - max(y3) / 2, s=0)
r1, r2 = spline.roots()[[0, -1]] # [[0, -1]] takes the first and the last
# r1, r2 = i - 50, i + 50
span1.set_xy([[r1, 0], [r1, 1], [r2, 1], [r2, 0], [r1, 0]])
plt.legend(['Max Probability = %1.3f' % (max(y3))])
return line, span1, ax2.legend_,
anim = animation.FuncAnimation(fig, animate, frames=600, interval=100, blit=False, repeat=False)
plt.plot()
The resulting end frame looks like (note that a slightly different function is used):

Fit 3d coordinates into a parabola

I would like to predict a ball trajectory by fitting its 3d coordinates into a parabola. Below is my code. But instead of a parabola, I got a straight line. If you have any clue about it, please let me know. Thanks!
# draw scatter coordiante
fig = plt.figure()
ax = plt.axes(projection = '3d')
x_list = []
y_list = []
z_list = []
for x in rm_list:
x_list.append(x[0][0])
y_list.append(x[0][1])
z_list.append(x[0][2])
x = np.array(x_list)
y = np.array(y_list)
z = np.array(z_list)
ax.scatter(x, y, z)
# curve fit
def func(x, a, b, c, d):
return a * x[0]**2 + b * x[1]**2 + c * x[0] * x[1] + d
data = np.column_stack([x_list, y_list, z_list])
popt, _ = curve_fit(func, data[:,:2].T, ydata=data[:,2])
a, b, c, d = popt
print('y= %.5f * x ^ 2 + %.5f * y ^ 2 + %.5f * x * y + %.5f' %(a, b, c, d))
x1 = np.linspace(0.3, 0.4, 100)
y1 = np.linspace(0.02, 0.06, 100)
z1 = a * x1 ** 2 + b * y1 ** 2 + c * x1 * y1 + d
ax.plot(x1, y1, z1, color='green')
plt.show()
Update 1
After changing the func to ax^2 + by^2 + cxy + dx + ey + f, I got a parabola but not fitting to the coordinate.
That you have your underlying timestamp data makes the fitting procedure easier:
import matplotlib.pyplot as plt
import numpy as np
from scipy.optimize import curve_fit
from numpy.polynomial import Polynomial
# test data generation with some noise
# here read in your data
np.random.seed(123)
n = 40
x_param = [ 1, 21, -1]
y_param = [12, -3, 0]
z_param = [-3, 0, -2]
px = Polynomial(x_param)
py = Polynomial(y_param)
pz = Polynomial(z_param)
t = np.random.choice(np.linspace (-3000, 2000, 1000)/500, n)
x = px(t) + np.random.random(n)
y = py(t) + np.random.random(n)
z = pz(t) + np.random.random(n)
# here start the real calculations
# draw scatter coordinates of raw data
fig = plt.figure()
ax = plt.axes(projection = '3d')
ax.scatter(x, y, z, label="raw data")
# curve fit function
def func(t, x2, x1, x0, y2, y1, y0, z2, z1, z0):
Px=Polynomial([x2, x1, x0])
Py=Polynomial([y2, y1, y0])
Pz=Polynomial([z2, z1, z0])
return np.concatenate([Px(t), Py(t), Pz(t)])
# curve fit
# start values are not necessary for this example
# but make it your rule to always provide start values for curve_fit
start_vals = [ 1, 10, 1,
10, 1, 1,
-1, -1, -1]
xyz = np.concatenate([x, y, z])
popt, _ = curve_fit(func, t, xyz, p0=start_vals)
print(popt)
#[ 1.58003630e+00 2.10059868e+01 -1.00401965e+00
# 1.25895591e+01 -2.97374035e+00 -3.23358241e-03
# -2.44293562e+00 3.96407428e-02 -1.99671092e+00]
# regularly spaced fit data
t_fit = np.linspace(min(t), max(t), 100)
xyz_fit = func(t_fit, *popt).reshape(3, -1)
ax.plot(xyz_fit[0, :], xyz_fit[1, :], xyz_fit[2, :], color="green", label="fitted data")
ax.legend()
plt.show()
Sample output:

Kapitza oscillator animation too slow and choppy

I wrote a program to model and plot the kapitza oscillator and as an extra credit i descided to animate it, but the second animation (when the 2nd oscilator is on) is very slow and thus you cant really see whats going on. Is there anything i can do to speed this up? And what is causing it to be slow, is it simply down to computing power, bad optimasation or is there some parameter i can add to tell it to go faster. This is my first time animating anything in python so im not very familiar at all with it. Heres the code:
# Kapitza oscillator
import numpy as np
from solvers import rk4
import matplotlib.pyplot as plt
import matplotlib.animation as animation
def g(t, X):
def A(t):
return A0 * np.sin(100 * omega0 * t)
theta, omega = X
thetaDot = omega
omegaDot = A(t) * np.sin(theta) - gamma * omega - (omega0 ** 2) * np.sin(theta)
Xdot = np.array([thetaDot, omegaDot])
return Xdot
gamma, omega0 = 1, (2 * np.pi)
X0 = np.array([0.9 * np.pi, 0])
A0 = 0
solver2a = rk4(g, X0, 0.001)
ts2a = []
Xsa = []
for t, X in solver2a:
ts2a.append(t)
Xsa.append(X)
if t > 20:
break
A0 = 10 ** 4
solver2b = rk4(g, X0, 1e-4)
ts2b = []
Xsb = []
for t, X in solver2b:
ts2b.append(t)
Xsb.append(X)
if t > 20:
break
Xsa = np.array(Xsa)
Xsb = np.array(Xsb)
plt.plot(ts2a, Xsa[:, 0], label='Angular displacement for fixed A')
plt.plot(ts2b, Xsb[:, 0], label='Angular displacement for A0=10e4')
plt.legend()
#EXTRA: Tried making animations for the oscilators (only work with Qt5 graphics backbone).
#The method I used seems to be rather slow to show the kapitza oscillator as smoothly as i would like
x = ts2a
y = Xsa[:,0]
fig, ax = plt.subplots()
line, = ax.plot(x, y)
def update(num, x, y, line):
line.set_data(x[:num], y[:num])
line.axes.axis([0, 20, -2, 3])
return line,
ani = animation.FuncAnimation(fig, update, len(x), fargs=[x, y, line], interval=1, blit=True)
plt.show()
x = ts2b
y = Xsb[:,0]
fig, ax = plt.subplots()
line, = ax.plot(x, y)
def update(num, x, y, line):
line.set_data(x[:num], y[:num])
line.axes.axis([0, 20, -1, 3.75])
return line,
ani = animation.FuncAnimation(fig, update, len(x), fargs=[x, y, line], interval=1e-10, blit=True)
plt.show()
note that im using runge kutta 4 (rk4) from external solvers saved on my computer that were provided by my lecturer. Here is the code for that:
def rk4(f, x0, dt):
tn = 0
xn = x0
while True:
yield tn,xn
k1 = dt*f(tn,xn)
k2 = dt*f(tn+dt/2,xn+k1/2)
k3 = dt*f(tn+dt/2,xn+k2/2)
k4 = dt*f(tn+dt,xn+k3)
xn = xn + (k1+2*k2+2*k3+k4)/6
tn = tn + dt
By considering only every tenth element of x and y by
x = x[::10]
y = y[::10]
it runs much faster for me, but is still not very smooth. Do you have to show the animation at run time? If not, you could save it as MP4
ani.save('animation.mp4', fps=15)
which actually does run very smoothly. So the choppy run time animation is maybe due to performance issues.
EDIT: I managed to get it working smoothly by changing the interval (delay between frames in ms) to 20 instead of 1. This also seems to be a more reasonable value for an animation (the interval=1 would correspond to 1000 FPS).

Brownian Motion 3D representation

So far I have code for Brownian Motion in 1D and 2D. My 3D graph is obviously incorrect, since my x,y,and z data variables are all the same. I just don't know what to set them too. I'm trying to follow https://www.mathworks.com/matlabcentral/fileexchange/32067-brownian-motion?focused=5191300&tab=function Here's my code:
import numpy as np
from pylab import show
from math import sqrt
from scipy.stats import norm
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.axes3d import Axes3D
def brownian(x0, n, dt, delta, out=None):
# n : The number of steps to take.
# dt : time step
# delta : "speed" of motion
# out :If `out` is NOT None, it specifies the array in which to put the
# result. If `out` is None, a new numpy array is created and returned.
x0 = np.asarray(x0) #I.C
r = norm.rvs(size=x0.shape + (n,), scale=delta*sqrt(dt)) #generate n numbers for sample
if out is None: #create out array
out = np.empty(r.shape)
np.cumsum(r, axis=-1, out=out) #cumulative sum for random variables
out += np.expand_dims(x0, axis=-1)#initial condition.
return out
def main():
fig = plt.figure(1) #prepare plot
ax1 = fig.add_subplot(231)
ax2 = fig.add_subplot(232)
ax = fig.add_subplot(2, 3, 3, projection='3d')
delta = 2 # The Wiener process parameter.
T = 10.0
N = 500# Number of steps.
dt = T/N
m = 5 # Number of "lines"
x = np.empty((m,N+1))# Create an empty array to store the realizations.
x[:, 0] = 0# Initial values of x.
brownian(x[:,0], N, dt, delta, out=x[:,1:])
t = np.linspace(0.0, T, N+1)
for i in range(m):
ax1.plot(t, x[i])
ax1.set_title('1D Brownian Motion')
ax1.set_xlabel('t')
ax1.set_ylabel('x')
ax1.grid(True)
ax2.plot(x[0],x[1])
ax2.plot(x[0,0],x[1,0], 'go') #begin
ax2.plot(x[0,-1], x[1,-1], 'ro') #end
ax2.set_title('2D Brownian Motion')
ax2.set_xlabel('x')
ax2.set_ylabel('y')
ax2.axis('equal')
ax2.grid(True)
#Data for a three-dimensional line
zline = np.linspace(0, 15, 1000)
xline = np.sin(zline)
yline = np.cos(zline)
ax.plot3D(xline, yline, zline, 'gray')
# Data for three-dimensional scattered points
zdata = brownian(x[:,0], N, dt, delta, out=x[:,1:])
xdata = brownian(x[:,0], N, dt, delta, out=x[:,1:])
ydata = brownian(x[:,0], N, dt, delta, out=x[:,1:])
ax.scatter3D(xdata, ydata, zdata, c=zdata, cmap='hot');
ax.set_title('3D Brownian Motion')
show()
return
main()
The first call to brownian makes 5 lines, since x[:, 0] has shape (5,):
brownian(x[:,0], N, dt, delta, out=x[:,1:])
So you could use any 3 of them to generate a 3D Brownian motion:
xdata, ydata, zdata = x[:3,:]
import numpy as np
from pylab import show
from math import sqrt
from scipy.stats import norm
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.axes3d import Axes3D
def brownian(x0, n, dt, delta, out=None):
# n : The number of steps to take.
# dt : time step
# delta : "speed" of motion
# out :If `out` is NOT None, it specifies the array in which to put the
# result. If `out` is None, a new numpy array is created and returned.
x0 = np.asarray(x0) #I.C
r = norm.rvs(size=x0.shape + (n,), scale=delta*sqrt(dt)) #generate n numbers for sample
if out is None: #create out array
out = np.empty(r.shape)
np.cumsum(r, axis=-1, out=out) #cumulative sum for random variables
out += np.expand_dims(x0, axis=-1)#initial condition.
return out
def main():
fig = plt.figure(1) #prepare plot
ax1 = fig.add_subplot(231)
ax2 = fig.add_subplot(232)
ax = fig.add_subplot(2, 3, 3, projection='3d')
delta = 2 # The Wiener process parameter.
T = 10.0
N = 500# Number of steps.
dt = T/N
m = 5 # Number of "lines"
x = np.empty((m,N+1))# Create an empty array to store the realizations.
x[:, 0] = 0# Initial values of x.
brownian(x[:,0], N, dt, delta, out=x[:,1:])
t = np.linspace(0.0, T, N+1)
for i in range(m):
ax1.plot(t, x[i])
ax1.set_title('1D Brownian Motion')
ax1.set_xlabel('t')
ax1.set_ylabel('x')
ax1.grid(True)
ax2.plot(x[0],x[1])
ax2.plot(x[0,0],x[1,0], 'go') #begin
ax2.plot(x[0,-1], x[1,-1], 'ro') #end
ax2.set_title('2D Brownian Motion')
ax2.set_xlabel('x')
ax2.set_ylabel('y')
ax2.axis('equal')
ax2.grid(True)
xdata, ydata, zdata = x[:3,:]
ax.plot3D(xdata, ydata, zdata)
ax.set_title('3D Brownian Motion')
show()
return
main()

Sigmoidal curve fit, how to get the value of x when y=0.5

I want to solve the following function so that after fitting, I want to get the value of x when y=0.5.
The function:
import numpy as np
from scipy.optimize import curve_fit
def sigmoid(x, b, c):
y = 1 / (1 + c*np.exp(-b*x))
return y
x_data = [4, 6, 8, 10]
y_data = [0.86, 0.73, 0.53, 0.3]
popt, pcov = curve_fit(sigmoid, x_data, y_data,(28.14,-0.25))
please explain how would you carry out this using python!
Thanks!
When I run your code I get a warning, and popt is the same as your initial guess, (28.14, -0.25). If you try plotting this you'll see that it's essentially a straight line at y == 1 that doesn't fit your data well at all:
from matplotlib import pyplot as plt
x = np.linspace(4, 10, 1000)
y = sigmoid(x, *popt)
fig, ax = plt.subplots(1, 1)
ax.hold(True)
ax.scatter(x_data, y_data, s=50, zorder=20)
ax.plot(x, y, '-k', lw=2)
The problem is that you're initializing with a negative value for the b parameter. Remember that b gets negated, so you're actually exponentiating x times a positive number, which blows up your denominator. Instead you want to initialize with a positive value for b, but perhaps a negative value for c (to give you your negative slope):
popt2, pcov2 = curve_fit(sigmoid, x_data, y_data, (-0.5, 0.1))
y2 = sigmoid(x, *popt2)
ax.plot(x, y2, '-r', lw=2)
To get the value of x at y == 0.5 using nonlinear optimization you need to define an objective function, which could be the square of the difference between 0.5 and sigmoid(x, b, c):
def objective(x, b, c):
return (0.5 - sigmoid(x, b, c)) ** 2
You can then use scipy.optimize.minimize or scipy.optimize.minimize_scalar to find the value of x that minimizes the objective function:
from scipy.optimize import minimize_scalar
res = minimize_scalar(objective, bracket=(4, 10), args=tuple(popt2))
ax.annotate("$y = 0.5$", (res.x, 0.5), (30, 30), textcoords='offset points',
arrowprops=dict(facecolor='black', shrink=0.05), fontsize='x-large')

Categories

Resources