Springs
Helped out some students today that were implementing a system of springs and masses, can I implement something similar?
This implementation is using ipywidgets to add some interactivity, so please use the binder link if you want to try it out, otherwise keep reading and enjoy!
#collapse
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from scipy.integrate import solve_ivp
from ipywidgets import interactive
Defining a system with 3 masses connected with 2 springs:
N=3
xs0 = np.linspace(0,1,N)
ys0 = np.zeros(N)
k=1.0 # spring coefficient
m=4.0 # masses
def plot_system(ax,xs=[],ys=[]):
ln, = ax.plot(xs, ys, '-ro', ms=20, markerfacecolor='black', markeredgecolor='black')
return ln
fig,ax=plt.subplots()
plot_system(ax, xs=xs0, ys=ys0);
Mesure the original length of the springs:
lx0 = np.diff(xs0)
ly0 = np.diff(ys0)
Define a method to calculate the spring forces in x, and y direction:
def calculate_spring_forces(xs,ys):
dxs = np.diff(xs)
dys = np.diff(ys)
fxs = k*(dxs-lx0)
fys = k*(dys-ly0)
return fxs,fys
Define a method to transfer the spring forces to the connected masses. A spring create a force that acts on both masses that it is connected to in the oposite direction:
def spring_force_to_point(fs):
fs_ = np.concatenate([[0],fs,[0]])
f_1 = fs_[0:-1]
f_2 = fs_[1:]
return f_1, f_2
def calculate_point_forces(fxs,fys):
fx_left, fx_right = spring_force_to_point(fxs)
fy_bottom, fy_top = spring_force_to_point(fys)
fx = -fx_left + fx_right
fy = -fy_bottom + fy_top
return fx,fy
Here is a plot showing the spring force acting on the masses:
#collapse
def f(x0=-0.3,y0=0.5,x1=0.5,y1=0.0,x2=1.3,y2=-0.0):
fig,ax=plt.subplots()
xs = [x0,x1,x2]
ys = [y0,y1,y2]
fxs,fys = calculate_spring_forces(xs=xs, ys=ys)
fx,fy = calculate_point_forces(fxs=fxs, fys=fys)
for x,y,fx_,fy_ in zip(xs,ys,fx,fy):
ax.arrow(x,y,fx_,fy_, head_width=0.05, head_length=0.1,)
plot_system(ax,xs=xs,ys=ys)
ax.set_xlim(-0.5,1.5)
ax.set_ylim(-1,1)
plt.show()
ax.set_title('Point forces')
interactive_plot = interactive(f, x0=(-0.5,1.0), x1=(-0.5,1.0), x2=(-0.5,1.0), y0=(-1.0,1.0), y1=(-1.0,1.0), y2=(-1.0,1.0))
output = interactive_plot.children[-1]
output.layout.height = '350px'
interactive_plot
This system will now be simulated, where the following state space model will be used:
def update(t, states):
xs = states[0:N]
ys = states[N:2*N]
dxs = states[2*N:3*N]
dys = states[3*N:]
fxs,fys = calculate_spring_forces(xs=xs, ys=ys)
fx,fy = calculate_point_forces(fxs=fxs, fys=fys)
ddxs = fx/m
ddys = fy/m
dstates = np.concatenate([dxs,dys,ddxs,ddys])
return dstates
A simulation where the masses have been moved from their original positions will now be conducted:
px = np.random.rand(N)
py = np.random.rand(N)
xlim=[-0.5,1.5]
ylim=[-1,1]
xs0 = xlim[0] + px*np.diff(xlim)
ys0 = ylim[0] + py*np.diff(ylim)
dxs0 = np.zeros(N)
dys0 = np.zeros(N)
states_0 = np.concatenate([xs0,ys0,dxs0,dys0])
interval=1/25
seconds=40
Ns=int(40/interval)
t = np.linspace(0,40,Ns)
result = solve_ivp(fun = update,t_span=[t[0],t[-1]],t_eval=t, y0 = states_0)
The animation function in matplotlib can be used to visualize the solution:
#hide_output
#collapse
from matplotlib.animation import FuncAnimation
from matplotlib import animation, rc
from IPython.display import HTML
fig, ax = plt.subplots()
ln, = ax.plot([], [], '-ro', ms=20, markerfacecolor='black', markeredgecolor='black')
def init():
ax.set_xlim(-0.5,1.5)
ax.set_ylim(-1,1)
return ln,
def update_plot(frame):
xs = result.y[0:N,frame]
ys = result.y[N:2*N,frame]
ln.set_data(xs, ys)
return ln,
anim = FuncAnimation(fig, update_plot, frames=np.arange(Ns),
init_func=init, blit=True, interval=interval)
#anim
And there is the video:
#collapse
plt.rcParams["animation.html"] = "jshtml"
HTML(anim.to_jshtml())