Skip to content

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)