I am trying to use the 4th order Runge-Kutta method to approximate the solution to a system of 1st order ODEs. The RK4 implementation itself is correct I think, if kind of janky - the plots it produces look like the right shape anyway - but it relies on 3 constants c, d and h, and I want to see how the solution changes as I vary those constants. I could just manually change them, but I wanted to make it interactive using sliders.
I want there to eventually be a slider for each of the 3 constants; right now I can't get even a single slider, the one for c, to work correctly. It's definitely... present, I can slide it back and forth, but the graph doesn't update as the value of c changes - even though I have defined a Slider.on_changed
for it and have remembered to re-call the RK4 function with the slider value and re-set the line data:
%matplotlib notebook
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button
import math
y_inits = [95, 5, 0]
c = 1
d = 5
fStepSize = 0.01
iLower = 0
iUpper = 1
tDerivatives = [
lambda t, y1, y2, y3: -c*y1*y2,
lambda t, y1, y2, y3: (c*y1*y2) - (d*y2),
lambda t, y1, y2, y3: d*y2
]
tColors = ["blue", "red", "yellow", "green", "black", "purple"]
def RK4(c, d, fStepSize):
tY_est = [ [y_inits[i]] for i in range(len(tDerivatives)) ]
tT = [iLower]
iRange = iUpper - iLower
n = math.ceil(iRange / fStepSize)
for i in range(n+1)[1:]:
fT_last = tT[i-1]
fT_new = fT_last + fStepSize
tK = []
for j in range(len(tDerivatives)):
Derivative = tDerivatives[j]
fK1 = fStepSize * Derivative(fT_last, *[tY_est[k][i-1] for k in range(len(tY_est))])
tK.append(["y'"+str(j+1), fK1])
for j in range(len(tDerivatives)):
Derivative = tDerivatives[j]
fK2 = fStepSize * Derivative(fT_last + (fStepSize/2), *[tY_est[k][i-1] + (tK[k][1]/2) for k in range(len(tY_est))])
tK[j].append(fK2)
for j in range(len(tDerivatives)):
Derivative = tDerivatives[j]
fK3 = fStepSize * Derivative(fT_last + (fStepSize/2), *[tY_est[k][i-1] + (tK[k][2]/2) for k in range(len(tY_est))])
tK[j].append(fK3)
for j in range(len(tDerivatives)):
Derivative = tDerivatives[j]
fK4 = fStepSize * Derivative(fT_new, *[tY_est[k][i-1] + tK[k][3] for k in range(len(tY_est))])
tK[j].append(fK4)
for j in range(len(tY_est)):
fY_est_new = tY_est[j][i-1] + (( tK[j][1] + (2*tK[j][2]) + (2*tK[j][3]) + tK[j][4] )/6)
tY_est[j].append(fY_est_new)
tT.append(fT_new)
return tT, tY_est
fig, ax = plt.subplots()
plt.subplots_adjust(left=0.25, bottom=0.25)
tT_init, tY_est_init = RK4(c, d, fStepSize)
tPlots = [ ax.plot(tT_init, tY_est_init[i], marker=".", color=tColors[i]) for i in range(len(tDerivatives)) ]
plt.xlabel('t')
plt.ylabel('y(t)')
plt.title('h = '+str(fStepSize))
axfreq = fig.add_axes([0.25, 0.1, 0.65, 0.03])
c_slider = Slider(
ax=axfreq,
label='c',
valmin=0.1,
valmax=30,
valinit=c,
)
def Update(val):
tT, tY_est = RK4(c_slider.val, d, fStepSize)
for i in range(len(tPlots)):
tPlots[i].set_data(tT, tY_est[i])
fig.canvas.draw()
c_slider.on_changed(Update)
No error messages are thrown.
I should note that I have used this slider demo from the matplotlib site itself as a base for my slider implementation. And their code works just fine, in that the resultant plot lines are responsive to the slider change, even in my environment - so it's not just a matter of "matplotlib isn't interactive in Jupyter Notebook".
I've also tried fiddling with the fig.canvas.draw()
line in Update
- maybe it's supposed to be draw_idle
, or maybe I should use plt.draw()
, or some other things, but none of them seem to have any effect.
What do I need to change to make the slider responsive?
EDIT:
There turned out to be two problems, as discussed in the comments of Yacine's answer. Besides how the lines are being instantiated (see Yacine's answer), the other problem is that the above code defines RK4 to not actually pass the c_slider.val
to the RK4 subroutines that needed them. So the graph was updating... but using the exact same global c variable every time.
The full solution, with all 3 sliders I wanted, is as follows:
%matplotlib notebook
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button
import math
y_inits = [95, 5, 0]
c_init = 1
d_init = 5
fStepSize = 0.01
iLower = 0
iUpper = 1
tDerivatives = [
lambda c, d, t, y1, y2, y3: -c*y1*y2,
lambda c, d, t, y1, y2, y3: (c*y1*y2) - (d*y2),
lambda c, d, t, y1, y2, y3: d*y2
]
tColors = ["blue", "red", "yellow", "green", "black", "purple"]
def RK4(c, d, fStepSize):
tY_est = [ [y_inits[i]] for i in range(len(tDerivatives)) ]
tT = [iLower]
iRange = iUpper - iLower
n = math.ceil(iRange / fStepSize)
for i in range(n+1)[1:]:
fT_last = tT[i-1]
fT_new = fT_last + fStepSize
tK = []
for j in range(len(tDerivatives)):
Derivative = tDerivatives[j]
fK1 = fStepSize * Derivative(c, d, fT_last, *[tY_est[k][i-1] for k in range(len(tY_est))])
tK.append(["y'"+str(j+1), fK1])
for j in range(len(tDerivatives)):
Derivative = tDerivatives[j]
fK2 = fStepSize * Derivative(c, d, fT_last + (fStepSize/2), *[tY_est[k][i-1] + (tK[k][1]/2) for k in range(len(tY_est))])
tK[j].append(fK2)
for j in range(len(tDerivatives)):
Derivative = tDerivatives[j]
fK3 = fStepSize * Derivative(c, d, fT_last + (fStepSize/2), *[tY_est[k][i-1] + (tK[k][2]/2) for k in range(len(tY_est))])
tK[j].append(fK3)
for j in range(len(tDerivatives)):
Derivative = tDerivatives[j]
fK4 = fStepSize * Derivative(c, d, fT_new, *[tY_est[k][i-1] + tK[k][3] for k in range(len(tY_est))])
tK[j].append(fK4)
for j in range(len(tY_est)):
fY_est_new = tY_est[j][i-1] + (( tK[j][1] + (2*tK[j][2]) + (2*tK[j][3]) + tK[j][4] )/6)
tY_est[j].append(fY_est_new)
tT.append(fT_new)
return tT, tY_est
fig, ax = plt.subplots()
plt.subplots_adjust(left=0.25, bottom=0.25)
plt.xlabel('t')
plt.ylabel('y(t)')
plt.title('h = '+str(fStepSize))
c_slider = Slider(
ax=fig.add_axes([0.25, 0.1, 0.65, 0.03]),
label='c',
valmin=0.1,
valmax=5,
valinit=c_init,
)
d_slider = Slider(
ax=fig.add_axes([0.25, 0.06, 0.65, 0.03]),
label='d',
valmin=0.1,
valmax=10,
valinit=d_init,
)
h_slider = Slider(
ax=fig.add_axes([0.25, 0.02, 0.65, 0.03]),
label='h',
valmin=0.001,
valmax=0.1,
valinit=fStepSize,
)
tPlots = [ax.plot([], [], marker=".", color=tColors[i])[0] for i in range(len(tDerivatives))]
def Update(val):
fStepSize = h_slider.val
tT, tY_est = RK4(c_slider.val, d_slider.val, fStepSize)
for i in range(len(tPlots)):
tPlots[i].set_data(tT, tY_est[i])
fig.canvas.draw_idle()
ax.relim()
ax.autoscale_view()
c_slider.on_changed(Update)
d_slider.on_changed(Update)
h_slider.on_changed(Update)
tT_init, tY_est_init = RK4(c_init, d_init, fStepSize)
for i in range(len(tPlots)):
tPlots[i].set_data(tT_init, tY_est_init[i])
ax.relim()
ax.autoscale_view()
plt.show()