Skip to content

Spring chains and guitar string

Masses joined in a row by springs of zero rest length between two anchors, a discrete string.

  • Name: spring_chain
  • Field: physics/waves
  • Formalism: ordinary differential equations
  • References: Argun et al. 2021

Factory

@register_model(
    field='physics/waves',
    tags=('waves', 'ode', 'springs', 'normal modes', 'mechanics'),
    references=('Argun et al. 2021',),
    defaults={'until': 20.0, 'dt': 0.05, 'solver': VelocityVerletSolver(dt=0.01)},
)
def spring_chain(
    n_masses: int = 15,
    *,
    length: float = 1.0,
    k: float = 1.0,
    mass: float = 0.01,
    pluck: float = 0.05,
    pluck_at: float = 0.5,
    amplitude: float = 0.0,
    omega: float = 2.0,
) -> System:
    """Return masses joined in a row by springs of zero rest length between two anchors, a discrete string.

    The masses start evenly spaced between the anchors at `0` and `length`, stretched, so the chain carries tension.
    With `pluck`, the masses start on a triangle of that height peaking at the fraction `pluck_at` of the length, the
    pluck of a guitar string. With `amplitude`, the left anchor moves transversally as `amplitude sin(omega t)` and
    sends a transverse wave along the chain. Springs of zero rest length are linear, so the transverse modes are
    independent, with frequencies `2 sqrt(k / m) sin(n pi / (2 (N + 1)))`.

    Args:
        n_masses:
            The number of masses between the anchors.

        length:
            The distance between the anchors.

        k:
            The stiffness of every spring.

        mass:
            The mass of every mass.

        pluck:
            The initial transverse displacement of the plucked point.

        pluck_at:
            The plucked point, as a fraction of the length.

        amplitude:
            The amplitude of the transverse motion of the left anchor.

        omega:
            The angular frequency of the left anchor.

    Returns:
        system:
            The model, with the masses `mass_<i>` and their positions `mass_<i>.x` in two dimensions.

    Examples:
        >>> from skcomplex.models.physics.waves import spring_chain
        >>> len(spring_chain(4).interactions)
        5
    """
    spacing = length / (n_masses + 1)
    positions = spacing * np.arange(1, n_masses + 1)
    heights = _pluck(positions, length, pluck, pluck_at) if pluck else np.zeros(n_masses)
    masses = [
        MassPointElement(f'mass_{index}', x=[float(positions[index]), float(heights[index])], x_d1=[0.0, 0.0], m=mass)
        for index in range(n_masses)
    ]
    springs = [
        SpringInteraction(f'spring_{index}', first=f'mass_{index}', second=f'mass_{index + 1}', k=k)
        for index in range(n_masses - 1)
    ]
    anchors = [
        DrivenAnchorInteraction('left_end', target='mass_0', k=k, center=[0.0, 0.0], amplitude=amplitude, omega=omega),
        DrivenAnchorInteraction('right_end', target=f'mass_{n_masses - 1}', k=k, center=[length, 0.0]),
    ]
    return System(elements=masses, interactions=[*anchors, *springs], space=ContinuousSpace(n_dim=2))

Components

class MassPointElement(PointElement):
    """Point mass, a point whose position `x` has order 2, with its velocity `x_d1` and a mass `m`.

    The forces on the point divide by the mass through their divisor `'m'`.

    Args:
        label:
            The label of the point.

        x:
            The initial position. A number applies to every component.

        x_d1:
            The initial velocity. A number applies to every component.

        visual:
            How the point is drawn, or `None` for `default_visual_`.

        m:
            The mass.

    Attributes:
        default_visual_:
            Points at the positions.

    Examples:
        >>> from skcomplex.models.physics.common.particles import MassPointElement
        >>> MassPointElement('ball', x=[0.0, 1.0], m=2.0).m
        2.0
    """

    def __init__(
        self,
        label: str,
        x: npt.ArrayLike = 0.0,
        x_d1: npt.ArrayLike = 0.0,
        visual: VisualSpec | None = None,
        *,
        m: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
    ) -> None:
        super().__init__(label, x=x, x_d1=x_d1, visual=visual)
        self.m = m

class DrivenAnchorInteraction(HarmonicPotentialInteraction):
    """Spring that ties a particle to an anchor point, which oscillates transversally as `amplitude sin(omega t)`.

    The energy is `k |x - center - (0, amplitude sin(omega t))|² / 2`, and a zero amplitude gives a fixed anchor.

    Args:
        label:
            The label of the interaction.

        target:
            The path of the particle.

        k:
            The stiffness of the spring.

        center:
            The rest position of the anchor.

        amplitude:
            The amplitude of the transverse motion of the anchor.

        omega:
            The angular frequency of the anchor.

    Examples:
        >>> from skcomplex.models.physics.waves import DrivenAnchorInteraction
        >>> DrivenAnchorInteraction('end', target='mass_0', center=[0.0, 0.0], amplitude=0.1).amplitude
        0.1
    """

    def __init__(
        self,
        label: str,
        *,
        target: Annotated[str, RoleSpec()],
        k: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
        center: Annotated[npt.ArrayLike, ParameterSpec(vector=True)] = 0.0,
        amplitude: Annotated[float, ParameterSpec()] = 0.0,
        omega: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
    ) -> None:
        super().__init__(label, target=target, k=k, center=center)
        self.amplitude = amplitude
        self.omega = omega

    def _get_center(self, s: SimpleNamespace) -> Expression:
        """Return the position `center + (0, amplitude sin(omega t))` of the anchor."""
        return s.center + vector(SR, [0, s.amplitude * sin(s.omega * s.t)])

class SpringInteraction(PotentialEnergyInteraction):
    """Spring between two particles.

    The energy is `k * (|x_first - x_second| - length)**2 / 2`, and its gradient moves both particles.

    Args:
        label:
            The label of the interaction.

        first:
            The path of the first particle.

        second:
            The path of the second particle.

        k:
            The stiffness.

        length:
            The rest length.

        states:
            The name of the vector state that holds the positions of the particles.

        divisor:
            The name of the state or parameter that holds the masses of the particles.

    Examples:
        >>> from skcomplex.models.physics.common.particles import SpringInteraction
        >>> SpringInteraction('spring', first='a', second='b', k=3.0, length=1.0).length
        1.0
    """

    def __init__(
        self,
        label: str,
        *,
        first: Annotated[str, RoleSpec()],
        second: Annotated[str, RoleSpec()],
        k: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
        length: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
        states: str = 'x',
        divisor: str = 'm',
    ) -> None:
        super().__init__(label, target=first, states=states, divisor=divisor)
        self.first = first
        self.second = second
        self.k = k
        self.length = length

    def _get_potential(self, s: SimpleNamespace) -> Expression:
        """Return `k * (|x_first - x_second| - length)**2 / 2`.

        Args:
            s:
                The namespace of the symbols the energy can use.

        Returns:
            energy:
                The potential energy as a symbolic expression.
        """
        gap = getattr(s.first, self._get_names()[0]) - getattr(s.second, self._get_names()[0])
        return s.k * (sqrt(gap.dot_product(gap)) - s.length) ** 2 / 2

Equations

mass_0.x[0]: diff(x_mass_0_0(t), t, t) == k_spring_0*(length_spring_0 - sqrt((x_mass_0_0(t) - x_mass_1_0(t))^2 + (x_mass_0_1(t) - x_mass_1_1(t))^2))*(x_mass_0_0(t) - x_mass_1_0(t))/(sqrt((x_mass_0_0(t) - x_mass_1_0(t))^2 + (x_mass_0_1(t) - x_mass_1_1(t))^2)*m_mass_0) + (center_left_end_0 - x_mass_0_0(t))*k_left_end/m_mass_0
mass_0.x[1]: diff(x_mass_0_1(t), t, t) == k_spring_0*(length_spring_0 - sqrt((x_mass_0_0(t) - x_mass_1_0(t))^2 + (x_mass_0_1(t) - x_mass_1_1(t))^2))*(x_mass_0_1(t) - x_mass_1_1(t))/(sqrt((x_mass_0_0(t) - x_mass_1_0(t))^2 + (x_mass_0_1(t) - x_mass_1_1(t))^2)*m_mass_0) + (amplitude_left_end*sin(omega_left_end*t) + center_left_end_1 - x_mass_0_1(t))*k_left_end/m_mass_0
mass_1.x[0]: diff(x_mass_1_0(t), t, t) == -k_spring_0*(length_spring_0 - sqrt((x_mass_0_0(t) - x_mass_1_0(t))^2 + (x_mass_0_1(t) - x_mass_1_1(t))^2))*(x_mass_0_0(t) - x_mass_1_0(t))/(sqrt((x_mass_0_0(t) - x_mass_1_0(t))^2 + (x_mass_0_1(t) - x_mass_1_1(t))^2)*m_mass_1) + k_spring_1*(length_spring_1 - sqrt((x_mass_1_0(t) - x_mass_2_0(t))^2 + (x_mass_1_1(t) - x_mass_2_1(t))^2))*(x_mass_1_0(t) - x_mass_2_0(t))/(sqrt((x_mass_1_0(t) - x_mass_2_0(t))^2 + (x_mass_1_1(t) - x_mass_2_1(t))^2)*m_mass_1)
mass_1.x[1]: diff(x_mass_1_1(t), t, t) == -k_spring_0*(length_spring_0 - sqrt((x_mass_0_0(t) - x_mass_1_0(t))^2 + (x_mass_0_1(t) - x_mass_1_1(t))^2))*(x_mass_0_1(t) - x_mass_1_1(t))/(sqrt((x_mass_0_0(t) - x_mass_1_0(t))^2 + (x_mass_0_1(t) - x_mass_1_1(t))^2)*m_mass_1) + k_spring_1*(length_spring_1 - sqrt((x_mass_1_0(t) - x_mass_2_0(t))^2 + (x_mass_1_1(t) - x_mass_2_1(t))^2))*(x_mass_1_1(t) - x_mass_2_1(t))/(sqrt((x_mass_1_0(t) - x_mass_2_0(t))^2 + (x_mass_1_1(t) - x_mass_2_1(t))^2)*m_mass_1)
mass_2.x[0]: diff(x_mass_2_0(t), t, t) == -k_spring_1*(length_spring_1 - sqrt((x_mass_1_0(t) - x_mass_2_0(t))^2 + (x_mass_1_1(t) - x_mass_2_1(t))^2))*(x_mass_1_0(t) - x_mass_2_0(t))/(sqrt((x_mass_1_0(t) - x_mass_2_0(t))^2 + (x_mass_1_1(t) - x_mass_2_1(t))^2)*m_mass_2) + k_spring_2*(length_spring_2 - sqrt((x_mass_2_0(t) - x_mass_3_0(t))^2 + (x_mass_2_1(t) - x_mass_3_1(t))^2))*(x_mass_2_0(t) - x_mass_3_0(t))/(sqrt((x_mass_2_0(t) - x_mass_3_0(t))^2 + (x_mass_2_1(t) - x_mass_3_1(t))^2)*m_mass_2)
mass_2.x[1]: diff(x_mass_2_1(t), t, t) == -k_spring_1*(length_spring_1 - sqrt((x_mass_1_0(t) - x_mass_2_0(t))^2 + (x_mass_1_1(t) - x_mass_2_1(t))^2))*(x_mass_1_1(t) - x_mass_2_1(t))/(sqrt((x_mass_1_0(t) - x_mass_2_0(t))^2 + (x_mass_1_1(t) - x_mass_2_1(t))^2)*m_mass_2) + k_spring_2*(length_spring_2 - sqrt((x_mass_2_0(t) - x_mass_3_0(t))^2 + (x_mass_2_1(t) - x_mass_3_1(t))^2))*(x_mass_2_1(t) - x_mass_3_1(t))/(sqrt((x_mass_2_0(t) - x_mass_3_0(t))^2 + (x_mass_2_1(t) - x_mass_3_1(t))^2)*m_mass_2)
mass_3.x[0]: diff(x_mass_3_0(t), t, t) == -k_spring_2*(length_spring_2 - sqrt((x_mass_2_0(t) - x_mass_3_0(t))^2 + (x_mass_2_1(t) - x_mass_3_1(t))^2))*(x_mass_2_0(t) - x_mass_3_0(t))/(sqrt((x_mass_2_0(t) - x_mass_3_0(t))^2 + (x_mass_2_1(t) - x_mass_3_1(t))^2)*m_mass_3) + k_spring_3*(length_spring_3 - sqrt((x_mass_3_0(t) - x_mass_4_0(t))^2 + (x_mass_3_1(t) - x_mass_4_1(t))^2))*(x_mass_3_0(t) - x_mass_4_0(t))/(sqrt((x_mass_3_0(t) - x_mass_4_0(t))^2 + (x_mass_3_1(t) - x_mass_4_1(t))^2)*m_mass_3)
mass_3.x[1]: diff(x_mass_3_1(t), t, t) == -k_spring_2*(length_spring_2 - sqrt((x_mass_2_0(t) - x_mass_3_0(t))^2 + (x_mass_2_1(t) - x_mass_3_1(t))^2))*(x_mass_2_1(t) - x_mass_3_1(t))/(sqrt((x_mass_2_0(t) - x_mass_3_0(t))^2 + (x_mass_2_1(t) - x_mass_3_1(t))^2)*m_mass_3) + k_spring_3*(length_spring_3 - sqrt((x_mass_3_0(t) - x_mass_4_0(t))^2 + (x_mass_3_1(t) - x_mass_4_1(t))^2))*(x_mass_3_1(t) - x_mass_4_1(t))/(sqrt((x_mass_3_0(t) - x_mass_4_0(t))^2 + (x_mass_3_1(t) - x_mass_4_1(t))^2)*m_mass_3)
mass_4.x[0]: diff(x_mass_4_0(t), t, t) == -k_spring_3*(length_spring_3 - sqrt((x_mass_3_0(t) - x_mass_4_0(t))^2 + (x_mass_3_1(t) - x_mass_4_1(t))^2))*(x_mass_3_0(t) - x_mass_4_0(t))/(sqrt((x_mass_3_0(t) - x_mass_4_0(t))^2 + (x_mass_3_1(t) - x_mass_4_1(t))^2)*m_mass_4) + k_spring_4*(length_spring_4 - sqrt((x_mass_4_0(t) - x_mass_5_0(t))^2 + (x_mass_4_1(t) - x_mass_5_1(t))^2))*(x_mass_4_0(t) - x_mass_5_0(t))/(sqrt((x_mass_4_0(t) - x_mass_5_0(t))^2 + (x_mass_4_1(t) - x_mass_5_1(t))^2)*m_mass_4)
mass_4.x[1]: diff(x_mass_4_1(t), t, t) == -k_spring_3*(length_spring_3 - sqrt((x_mass_3_0(t) - x_mass_4_0(t))^2 + (x_mass_3_1(t) - x_mass_4_1(t))^2))*(x_mass_3_1(t) - x_mass_4_1(t))/(sqrt((x_mass_3_0(t) - x_mass_4_0(t))^2 + (x_mass_3_1(t) - x_mass_4_1(t))^2)*m_mass_4) + k_spring_4*(length_spring_4 - sqrt((x_mass_4_0(t) - x_mass_5_0(t))^2 + (x_mass_4_1(t) - x_mass_5_1(t))^2))*(x_mass_4_1(t) - x_mass_5_1(t))/(sqrt((x_mass_4_0(t) - x_mass_5_0(t))^2 + (x_mass_4_1(t) - x_mass_5_1(t))^2)*m_mass_4)
mass_5.x[0]: diff(x_mass_5_0(t), t, t) == -k_spring_4*(length_spring_4 - sqrt((x_mass_4_0(t) - x_mass_5_0(t))^2 + (x_mass_4_1(t) - x_mass_5_1(t))^2))*(x_mass_4_0(t) - x_mass_5_0(t))/(sqrt((x_mass_4_0(t) - x_mass_5_0(t))^2 + (x_mass_4_1(t) - x_mass_5_1(t))^2)*m_mass_5) + k_spring_5*(length_spring_5 - sqrt((x_mass_5_0(t) - x_mass_6_0(t))^2 + (x_mass_5_1(t) - x_mass_6_1(t))^2))*(x_mass_5_0(t) - x_mass_6_0(t))/(sqrt((x_mass_5_0(t) - x_mass_6_0(t))^2 + (x_mass_5_1(t) - x_mass_6_1(t))^2)*m_mass_5)
mass_5.x[1]: diff(x_mass_5_1(t), t, t) == -k_spring_4*(length_spring_4 - sqrt((x_mass_4_0(t) - x_mass_5_0(t))^2 + (x_mass_4_1(t) - x_mass_5_1(t))^2))*(x_mass_4_1(t) - x_mass_5_1(t))/(sqrt((x_mass_4_0(t) - x_mass_5_0(t))^2 + (x_mass_4_1(t) - x_mass_5_1(t))^2)*m_mass_5) + k_spring_5*(length_spring_5 - sqrt((x_mass_5_0(t) - x_mass_6_0(t))^2 + (x_mass_5_1(t) - x_mass_6_1(t))^2))*(x_mass_5_1(t) - x_mass_6_1(t))/(sqrt((x_mass_5_0(t) - x_mass_6_0(t))^2 + (x_mass_5_1(t) - x_mass_6_1(t))^2)*m_mass_5)
mass_6.x[0]: diff(x_mass_6_0(t), t, t) == -k_spring_5*(length_spring_5 - sqrt((x_mass_5_0(t) - x_mass_6_0(t))^2 + (x_mass_5_1(t) - x_mass_6_1(t))^2))*(x_mass_5_0(t) - x_mass_6_0(t))/(sqrt((x_mass_5_0(t) - x_mass_6_0(t))^2 + (x_mass_5_1(t) - x_mass_6_1(t))^2)*m_mass_6) + k_spring_6*(length_spring_6 - sqrt((x_mass_6_0(t) - x_mass_7_0(t))^2 + (x_mass_6_1(t) - x_mass_7_1(t))^2))*(x_mass_6_0(t) - x_mass_7_0(t))/(sqrt((x_mass_6_0(t) - x_mass_7_0(t))^2 + (x_mass_6_1(t) - x_mass_7_1(t))^2)*m_mass_6)
mass_6.x[1]: diff(x_mass_6_1(t), t, t) == -k_spring_5*(length_spring_5 - sqrt((x_mass_5_0(t) - x_mass_6_0(t))^2 + (x_mass_5_1(t) - x_mass_6_1(t))^2))*(x_mass_5_1(t) - x_mass_6_1(t))/(sqrt((x_mass_5_0(t) - x_mass_6_0(t))^2 + (x_mass_5_1(t) - x_mass_6_1(t))^2)*m_mass_6) + k_spring_6*(length_spring_6 - sqrt((x_mass_6_0(t) - x_mass_7_0(t))^2 + (x_mass_6_1(t) - x_mass_7_1(t))^2))*(x_mass_6_1(t) - x_mass_7_1(t))/(sqrt((x_mass_6_0(t) - x_mass_7_0(t))^2 + (x_mass_6_1(t) - x_mass_7_1(t))^2)*m_mass_6)
mass_7.x[0]: diff(x_mass_7_0(t), t, t) == -k_spring_6*(length_spring_6 - sqrt((x_mass_6_0(t) - x_mass_7_0(t))^2 + (x_mass_6_1(t) - x_mass_7_1(t))^2))*(x_mass_6_0(t) - x_mass_7_0(t))/(sqrt((x_mass_6_0(t) - x_mass_7_0(t))^2 + (x_mass_6_1(t) - x_mass_7_1(t))^2)*m_mass_7) + k_spring_7*(length_spring_7 - sqrt((x_mass_7_0(t) - x_mass_8_0(t))^2 + (x_mass_7_1(t) - x_mass_8_1(t))^2))*(x_mass_7_0(t) - x_mass_8_0(t))/(sqrt((x_mass_7_0(t) - x_mass_8_0(t))^2 + (x_mass_7_1(t) - x_mass_8_1(t))^2)*m_mass_7)
mass_7.x[1]: diff(x_mass_7_1(t), t, t) == -k_spring_6*(length_spring_6 - sqrt((x_mass_6_0(t) - x_mass_7_0(t))^2 + (x_mass_6_1(t) - x_mass_7_1(t))^2))*(x_mass_6_1(t) - x_mass_7_1(t))/(sqrt((x_mass_6_0(t) - x_mass_7_0(t))^2 + (x_mass_6_1(t) - x_mass_7_1(t))^2)*m_mass_7) + k_spring_7*(length_spring_7 - sqrt((x_mass_7_0(t) - x_mass_8_0(t))^2 + (x_mass_7_1(t) - x_mass_8_1(t))^2))*(x_mass_7_1(t) - x_mass_8_1(t))/(sqrt((x_mass_7_0(t) - x_mass_8_0(t))^2 + (x_mass_7_1(t) - x_mass_8_1(t))^2)*m_mass_7)
mass_8.x[0]: diff(x_mass_8_0(t), t, t) == -k_spring_7*(length_spring_7 - sqrt((x_mass_7_0(t) - x_mass_8_0(t))^2 + (x_mass_7_1(t) - x_mass_8_1(t))^2))*(x_mass_7_0(t) - x_mass_8_0(t))/(sqrt((x_mass_7_0(t) - x_mass_8_0(t))^2 + (x_mass_7_1(t) - x_mass_8_1(t))^2)*m_mass_8) + k_spring_8*(length_spring_8 - sqrt((x_mass_8_0(t) - x_mass_9_0(t))^2 + (x_mass_8_1(t) - x_mass_9_1(t))^2))*(x_mass_8_0(t) - x_mass_9_0(t))/(sqrt((x_mass_8_0(t) - x_mass_9_0(t))^2 + (x_mass_8_1(t) - x_mass_9_1(t))^2)*m_mass_8)
mass_8.x[1]: diff(x_mass_8_1(t), t, t) == -k_spring_7*(length_spring_7 - sqrt((x_mass_7_0(t) - x_mass_8_0(t))^2 + (x_mass_7_1(t) - x_mass_8_1(t))^2))*(x_mass_7_1(t) - x_mass_8_1(t))/(sqrt((x_mass_7_0(t) - x_mass_8_0(t))^2 + (x_mass_7_1(t) - x_mass_8_1(t))^2)*m_mass_8) + k_spring_8*(length_spring_8 - sqrt((x_mass_8_0(t) - x_mass_9_0(t))^2 + (x_mass_8_1(t) - x_mass_9_1(t))^2))*(x_mass_8_1(t) - x_mass_9_1(t))/(sqrt((x_mass_8_0(t) - x_mass_9_0(t))^2 + (x_mass_8_1(t) - x_mass_9_1(t))^2)*m_mass_8)
mass_9.x[0]: diff(x_mass_9_0(t), t, t) == -k_spring_9*(length_spring_9 - sqrt((x_mass_10_0(t) - x_mass_9_0(t))^2 + (x_mass_10_1(t) - x_mass_9_1(t))^2))*(x_mass_10_0(t) - x_mass_9_0(t))/(sqrt((x_mass_10_0(t) - x_mass_9_0(t))^2 + (x_mass_10_1(t) - x_mass_9_1(t))^2)*m_mass_9) - k_spring_8*(length_spring_8 - sqrt((x_mass_8_0(t) - x_mass_9_0(t))^2 + (x_mass_8_1(t) - x_mass_9_1(t))^2))*(x_mass_8_0(t) - x_mass_9_0(t))/(sqrt((x_mass_8_0(t) - x_mass_9_0(t))^2 + (x_mass_8_1(t) - x_mass_9_1(t))^2)*m_mass_9)
mass_9.x[1]: diff(x_mass_9_1(t), t, t) == -k_spring_9*(length_spring_9 - sqrt((x_mass_10_0(t) - x_mass_9_0(t))^2 + (x_mass_10_1(t) - x_mass_9_1(t))^2))*(x_mass_10_1(t) - x_mass_9_1(t))/(sqrt((x_mass_10_0(t) - x_mass_9_0(t))^2 + (x_mass_10_1(t) - x_mass_9_1(t))^2)*m_mass_9) - k_spring_8*(length_spring_8 - sqrt((x_mass_8_0(t) - x_mass_9_0(t))^2 + (x_mass_8_1(t) - x_mass_9_1(t))^2))*(x_mass_8_1(t) - x_mass_9_1(t))/(sqrt((x_mass_8_0(t) - x_mass_9_0(t))^2 + (x_mass_8_1(t) - x_mass_9_1(t))^2)*m_mass_9)
mass_10.x[0]: diff(x_mass_10_0(t), t, t) == k_spring_10*(length_spring_10 - sqrt((x_mass_10_0(t) - x_mass_11_0(t))^2 + (x_mass_10_1(t) - x_mass_11_1(t))^2))*(x_mass_10_0(t) - x_mass_11_0(t))/(sqrt((x_mass_10_0(t) - x_mass_11_0(t))^2 + (x_mass_10_1(t) - x_mass_11_1(t))^2)*m_mass_10) + k_spring_9*(length_spring_9 - sqrt((x_mass_10_0(t) - x_mass_9_0(t))^2 + (x_mass_10_1(t) - x_mass_9_1(t))^2))*(x_mass_10_0(t) - x_mass_9_0(t))/(sqrt((x_mass_10_0(t) - x_mass_9_0(t))^2 + (x_mass_10_1(t) - x_mass_9_1(t))^2)*m_mass_10)
mass_10.x[1]: diff(x_mass_10_1(t), t, t) == k_spring_10*(length_spring_10 - sqrt((x_mass_10_0(t) - x_mass_11_0(t))^2 + (x_mass_10_1(t) - x_mass_11_1(t))^2))*(x_mass_10_1(t) - x_mass_11_1(t))/(sqrt((x_mass_10_0(t) - x_mass_11_0(t))^2 + (x_mass_10_1(t) - x_mass_11_1(t))^2)*m_mass_10) + k_spring_9*(length_spring_9 - sqrt((x_mass_10_0(t) - x_mass_9_0(t))^2 + (x_mass_10_1(t) - x_mass_9_1(t))^2))*(x_mass_10_1(t) - x_mass_9_1(t))/(sqrt((x_mass_10_0(t) - x_mass_9_0(t))^2 + (x_mass_10_1(t) - x_mass_9_1(t))^2)*m_mass_10)
mass_11.x[0]: diff(x_mass_11_0(t), t, t) == -k_spring_10*(length_spring_10 - sqrt((x_mass_10_0(t) - x_mass_11_0(t))^2 + (x_mass_10_1(t) - x_mass_11_1(t))^2))*(x_mass_10_0(t) - x_mass_11_0(t))/(sqrt((x_mass_10_0(t) - x_mass_11_0(t))^2 + (x_mass_10_1(t) - x_mass_11_1(t))^2)*m_mass_11) + k_spring_11*(length_spring_11 - sqrt((x_mass_11_0(t) - x_mass_12_0(t))^2 + (x_mass_11_1(t) - x_mass_12_1(t))^2))*(x_mass_11_0(t) - x_mass_12_0(t))/(sqrt((x_mass_11_0(t) - x_mass_12_0(t))^2 + (x_mass_11_1(t) - x_mass_12_1(t))^2)*m_mass_11)
mass_11.x[1]: diff(x_mass_11_1(t), t, t) == -k_spring_10*(length_spring_10 - sqrt((x_mass_10_0(t) - x_mass_11_0(t))^2 + (x_mass_10_1(t) - x_mass_11_1(t))^2))*(x_mass_10_1(t) - x_mass_11_1(t))/(sqrt((x_mass_10_0(t) - x_mass_11_0(t))^2 + (x_mass_10_1(t) - x_mass_11_1(t))^2)*m_mass_11) + k_spring_11*(length_spring_11 - sqrt((x_mass_11_0(t) - x_mass_12_0(t))^2 + (x_mass_11_1(t) - x_mass_12_1(t))^2))*(x_mass_11_1(t) - x_mass_12_1(t))/(sqrt((x_mass_11_0(t) - x_mass_12_0(t))^2 + (x_mass_11_1(t) - x_mass_12_1(t))^2)*m_mass_11)
mass_12.x[0]: diff(x_mass_12_0(t), t, t) == -k_spring_11*(length_spring_11 - sqrt((x_mass_11_0(t) - x_mass_12_0(t))^2 + (x_mass_11_1(t) - x_mass_12_1(t))^2))*(x_mass_11_0(t) - x_mass_12_0(t))/(sqrt((x_mass_11_0(t) - x_mass_12_0(t))^2 + (x_mass_11_1(t) - x_mass_12_1(t))^2)*m_mass_12) + k_spring_12*(length_spring_12 - sqrt((x_mass_12_0(t) - x_mass_13_0(t))^2 + (x_mass_12_1(t) - x_mass_13_1(t))^2))*(x_mass_12_0(t) - x_mass_13_0(t))/(sqrt((x_mass_12_0(t) - x_mass_13_0(t))^2 + (x_mass_12_1(t) - x_mass_13_1(t))^2)*m_mass_12)
mass_12.x[1]: diff(x_mass_12_1(t), t, t) == -k_spring_11*(length_spring_11 - sqrt((x_mass_11_0(t) - x_mass_12_0(t))^2 + (x_mass_11_1(t) - x_mass_12_1(t))^2))*(x_mass_11_1(t) - x_mass_12_1(t))/(sqrt((x_mass_11_0(t) - x_mass_12_0(t))^2 + (x_mass_11_1(t) - x_mass_12_1(t))^2)*m_mass_12) + k_spring_12*(length_spring_12 - sqrt((x_mass_12_0(t) - x_mass_13_0(t))^2 + (x_mass_12_1(t) - x_mass_13_1(t))^2))*(x_mass_12_1(t) - x_mass_13_1(t))/(sqrt((x_mass_12_0(t) - x_mass_13_0(t))^2 + (x_mass_12_1(t) - x_mass_13_1(t))^2)*m_mass_12)
mass_13.x[0]: diff(x_mass_13_0(t), t, t) == -k_spring_12*(length_spring_12 - sqrt((x_mass_12_0(t) - x_mass_13_0(t))^2 + (x_mass_12_1(t) - x_mass_13_1(t))^2))*(x_mass_12_0(t) - x_mass_13_0(t))/(sqrt((x_mass_12_0(t) - x_mass_13_0(t))^2 + (x_mass_12_1(t) - x_mass_13_1(t))^2)*m_mass_13) + k_spring_13*(length_spring_13 - sqrt((x_mass_13_0(t) - x_mass_14_0(t))^2 + (x_mass_13_1(t) - x_mass_14_1(t))^2))*(x_mass_13_0(t) - x_mass_14_0(t))/(sqrt((x_mass_13_0(t) - x_mass_14_0(t))^2 + (x_mass_13_1(t) - x_mass_14_1(t))^2)*m_mass_13)
mass_13.x[1]: diff(x_mass_13_1(t), t, t) == -k_spring_12*(length_spring_12 - sqrt((x_mass_12_0(t) - x_mass_13_0(t))^2 + (x_mass_12_1(t) - x_mass_13_1(t))^2))*(x_mass_12_1(t) - x_mass_13_1(t))/(sqrt((x_mass_12_0(t) - x_mass_13_0(t))^2 + (x_mass_12_1(t) - x_mass_13_1(t))^2)*m_mass_13) + k_spring_13*(length_spring_13 - sqrt((x_mass_13_0(t) - x_mass_14_0(t))^2 + (x_mass_13_1(t) - x_mass_14_1(t))^2))*(x_mass_13_1(t) - x_mass_14_1(t))/(sqrt((x_mass_13_0(t) - x_mass_14_0(t))^2 + (x_mass_13_1(t) - x_mass_14_1(t))^2)*m_mass_13)
mass_14.x[0]: diff(x_mass_14_0(t), t, t) == -k_spring_13*(length_spring_13 - sqrt((x_mass_13_0(t) - x_mass_14_0(t))^2 + (x_mass_13_1(t) - x_mass_14_1(t))^2))*(x_mass_13_0(t) - x_mass_14_0(t))/(sqrt((x_mass_13_0(t) - x_mass_14_0(t))^2 + (x_mass_13_1(t) - x_mass_14_1(t))^2)*m_mass_14) + (center_right_end_0 - x_mass_14_0(t))*k_right_end/m_mass_14
mass_14.x[1]: diff(x_mass_14_1(t), t, t) == -k_spring_13*(length_spring_13 - sqrt((x_mass_13_0(t) - x_mass_14_0(t))^2 + (x_mass_13_1(t) - x_mass_14_1(t))^2))*(x_mass_13_1(t) - x_mass_14_1(t))/(sqrt((x_mass_13_0(t) - x_mass_14_0(t))^2 + (x_mass_13_1(t) - x_mass_14_1(t))^2)*m_mass_14) + (amplitude_right_end*sin(omega_right_end*t) + center_right_end_1 - x_mass_14_1(t))*k_right_end/m_mass_14