Rotating swing
A rotating swing: chairs on chains hanging from the rods of a stand that turns about a vertical axis.
- Name:
rotating_swing - Field:
physics/mechanics - Formalism: ordinary differential equations
- References: Ryzhov et al. 2021
Factory
@register_model(
field='physics/mechanics',
tags=('mechanics', 'ode', 'second order', 'rotation', 'lagrangian'),
references=('Ryzhov et al. 2021',),
defaults={'until': 20.0, 'dt': 0.05},
)
def rotating_swing(
omega: float = 2.0,
theta: float = 0.1,
*,
n_chairs: int = 8,
J: float = 10.0,
m: float = 1.0,
a: float = 1.0,
l: float = 1.0,
g: float = 9.81,
torque: float = 0.0,
friction: float = 0.0,
) -> System:
"""Return a rotating swing: chairs on chains hanging from the rods of a stand that turns about a vertical axis.
The stand of inertia `J` turns by the angle `φ`, and the chairs, all deflected by the same angle `θ`, follow the
Lagrange equations `[J + n m (a + l sin θ)²] φ'' + 2 n m l φ' θ' (a + l sin θ) cos θ = M - c φ'` and
`l θ'' - φ'² (a + l sin θ) cos θ + g sin θ = 0`. At a steady speed `ω` the chairs settle at the angle where
`g tan θ = ω² (a + l sin θ)`. Without torque and friction the angular momentum of the swing is conserved.
Args:
omega:
The initial angular velocity of the stand.
theta:
The initial angle of the chains from the vertical.
n_chairs:
The number of chairs.
J:
The moment of inertia of the stand.
m:
The mass of every chair.
a:
The length of the rods.
l:
The length of the chains.
g:
The gravitational acceleration.
torque:
The torque that drives the stand.
friction:
The friction coefficient of the stand's axis.
Returns:
system:
The model, with the angle of the stand `swing.phi` and of the chains `swing.theta`, both of order 2.
Examples:
>>> from skcomplex.models.physics.mechanics import rotating_swing
>>> rotating_swing(omega=1.0).elements[0].phi_d1
1.0
"""
return System(
elements=[
RotatingSwingElement(
'swing',
phi_d1=omega,
theta=theta,
n_chairs=n_chairs,
J=J,
m=m,
a=a,
l=l,
g=g,
torque=torque,
friction=friction,
),
],
interactions=[RotatingSwingInteraction('motion', target='swing')],
)
Components
class RotatingSwingElement(Element):
"""Rotating swing: chairs on chains hanging from the rods of a stand that turns about a vertical axis.
The angle `phi` of the stand and the angle `theta` of the chains from the vertical have order 2. The motion comes
from `RotatingSwingInteraction`.
Args:
label:
The label of the swing.
visual:
How the swing is drawn, or `None` for none.
phi:
The initial angle of the stand.
phi_d1:
The initial angular velocity of the stand.
theta:
The initial angle of the chains.
theta_d1:
The initial angular velocity of the chains.
J:
The moment of inertia of the stand.
m:
The mass of every chair.
n_chairs:
The number of chairs.
a:
The length of the rods.
l:
The length of the chains.
g:
The gravitational acceleration.
torque:
The torque that drives the stand.
friction:
The friction coefficient of the stand's axis.
Examples:
>>> from skcomplex.models.physics.mechanics import RotatingSwingElement
>>> RotatingSwingElement('swing', n_chairs=4.0).n_chairs
4.0
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
phi: Annotated[float, StateSpec(period=2 * pi, order=2)] = 0.0,
phi_d1: float = 0.0,
theta: Annotated[float, StateSpec(order=2)] = 0.1,
theta_d1: float = 0.0,
J: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 10.0,
m: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
n_chairs: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 8.0,
a: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
l: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
g: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 9.81,
torque: Annotated[float, ParameterSpec()] = 0.0,
friction: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
) -> None:
super().__init__(label, visual=visual)
self.phi = phi
self.phi_d1 = phi_d1
self.theta = theta
self.theta_d1 = theta_d1
self.J = J
self.m = m
self.n_chairs = n_chairs
self.a = a
self.l = l
self.g = g
self.torque = torque
self.friction = friction
class RotatingSwingInteraction(EulerLagrangeInteraction):
"""Motion of a rotating swing, the Euler-Lagrange equations with a torque and friction on the stand.
The Lagrangian is `[J + n m (a + l sin θ)²] φ'² / 2 + n m l² θ'² / 2 + n m g l cos θ`, the torque drives the angle
`φ` of the stand, and the Rayleigh dissipation `c φ'² / 2` adds the friction of its axis.
Args:
label:
The label of the interaction.
target:
The path of the swing.
Examples:
>>> from skcomplex.models.physics.mechanics import RotatingSwingInteraction
>>> RotatingSwingInteraction('motion', target='swing').states
('phi', 'theta')
"""
def __init__(self, label: str, *, target: Annotated[str, RoleSpec()]) -> None:
super().__init__(
label,
target=target,
states=('phi', 'theta'),
dissipation=self._get_friction,
forcing=self._get_torques,
)
@staticmethod
def _get_friction(namespace: SimpleNamespace) -> Expression:
"""Return the Rayleigh dissipation `c φ'² / 2` of the stand's axis."""
return namespace.target.friction * derivative(namespace.target.phi, 1) ** 2 / 2
@staticmethod
def _get_torques(namespace: SimpleNamespace) -> list[Expression]:
"""Return the generalized forces, the torque on the stand and none on the chains."""
return [namespace.target.torque, 0]
def _get_lagrangian(self, namespace: SimpleNamespace) -> Expression:
"""Return the Lagrangian of the stand and the chairs.
Args:
namespace:
The namespace of the symbols the Lagrangian can use.
Returns:
lagrangian:
The Lagrangian.
"""
p = namespace.target
chairs = p.n_chairs * p.m
radius = p.a + p.l * sin(p.theta)
kinetic = (p.J + chairs * radius**2) * derivative(p.phi, 1) ** 2 / 2
kinetic += chairs * p.l**2 * derivative(p.theta, 1) ** 2 / 2
return kinetic + chairs * p.g * p.l * cos(p.theta)
Equations
swing.phi: diff(phi_swing(t), t, t) == -(2*(l_swing*sin(theta_swing(t)) + a_swing)*l_swing*m_swing*n_chairs_swing*cos(theta_swing(t))*diff(phi_swing(t), t)*diff(theta_swing(t), t) + friction_swing*diff(phi_swing(t), t) - torque_swing)/((l_swing*sin(theta_swing(t)) + a_swing)^2*m_swing*n_chairs_swing + J_swing)
swing.theta: diff(theta_swing(t), t, t) == ((l_swing*sin(theta_swing(t)) + a_swing)*l_swing*m_swing*n_chairs_swing*cos(theta_swing(t))*diff(phi_swing(t), t)^2 - g_swing*l_swing*m_swing*n_chairs_swing*sin(theta_swing(t)))/(l_swing^2*m_swing*n_chairs_swing)