Skip to content

Delayed Kuramoto

Kuramoto phase oscillators coupled all to all through signals that arrive after a delay.

  • Name: delayed_kuramoto
  • Field: physics/synchronization
  • Formalism: ordinary differential equations
  • References: Yeung and Strogatz 1999; Kuramoto 1984

Factory

@register_model(
    field='physics/synchronization',
    tags=('synchronization', 'delay', 'oscillators'),
    references=('Yeung and Strogatz 1999', 'Kuramoto 1984'),
    defaults={'until': 50.0, 'dt': 0.1},
)
def delayed_kuramoto(
    n_oscillators: int = 5,
    *,
    K: float = 2.0,
    tau: float = 1.0,
    spread: float = 0.2,
    seed: int = 0,
) -> System:
    """Return Kuramoto phase oscillators coupled all to all through signals that arrive after a delay.

    Oscillator `i` follows `theta_i' = omega_i + (K / N) sum_j sin(theta_j(t - tau) - theta_i)`, with natural
    frequencies spread evenly around 1 and initial phases spread around the circle.

    Args:
        n_oscillators:
            The number of oscillators.

        K:
            The coupling strength.

        tau:
            The delay of the coupling.

        spread:
            The width of the interval of natural frequencies around 1.

        seed:
            The seed of the initial phases.

    Returns:
        system:
            The model, with the phases `oscillator_<i>.theta`.

    Examples:
        >>> from skcomplex.models.physics.synchronization import delayed_kuramoto
        >>> len(delayed_kuramoto(n_oscillators=3).interactions)
        6
    """
    phases = [2 * math.pi * ((seed * 0.618 + index / n_oscillators) % 1.0) for index in range(n_oscillators)]
    omegas = [1.0 + spread * (index / max(n_oscillators - 1, 1) - 0.5) for index in range(n_oscillators)]
    elements = [
        PhaseOscillatorElement(f'oscillator_{index}', theta=phases[index], omega=omegas[index])
        for index in range(n_oscillators)
    ]
    interactions = [
        DelayedCouplingInteraction(
            f'coupling_{source}_{target}',
            source=f'oscillator_{source}',
            target=f'oscillator_{target}',
            K=K / n_oscillators,
            tau=tau,
        )
        for source in range(n_oscillators)
        for target in range(n_oscillators)
        if source != target
    ]
    return System(elements=elements, interactions=interactions)

Components

class PhaseOscillatorElement(Element):
    """Phase oscillator, a phase `theta` that turns at its natural frequency, `theta' = omega`.

    Couplings to other oscillators add to the rate of the phase.

    Args:
        label:
            The label of the oscillator or the group.

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

        theta:
            The initial phase.

        omega:
            The natural frequency.

    Attributes:
        default_visual_:
            The visual of the oscillator when it sets none.

    Examples:
        >>> from skcomplex.core.system import System
        >>> from skcomplex.engine.runtime import Simulation
        >>> from skcomplex.models.physics.common.oscillators import PhaseOscillatorElement
        >>> system = System(elements=[PhaseOscillatorElement('clock', omega=2.0)])
        >>> float(Simulation(system, dt=0.5).check().prepare().run(until=1.0).trajectory_.values('clock.theta')[-1])
        2.0
    """

    def __init__(
        self,
        label: str,
        visual: VisualSpec | None = None,
        *,
        theta: Annotated[float, StateSpec(role='phase')] = 0.0,
        omega: Annotated[float, ParameterSpec()] = 1.0,
    ) -> None:
        super().__init__(label, visual=visual)
        self.theta = theta
        self.omega = omega

    def _get_laws(self, namespace: SimpleNamespace) -> list[BaseLaw]:
        """Return the turning of the phase at the natural frequency.

        Args:
            namespace:
                The namespace of the symbols the laws can use.

        Returns:
            laws:
                The rate of the phase.
        """
        return [RateLaw(namespace.theta, namespace.omega)]

class DelayedCouplingInteraction(RateInteraction):
    """Delayed sine coupling of one phase oscillator to another, `theta_t' += K sin(theta_s(t - tau) - theta_t)`.

    The target reads the phase of the source as it was a delay `tau` earlier.

    Args:
        label:
            The label of the interaction, unique among the parts of its system.

        source:
            The path of the oscillator whose delayed phase the coupling reads.

        target:
            The path of the oscillator whose phase the coupling turns.

        K:
            The coupling strength.

        tau:
            The delay.

    Examples:
        >>> from types import SimpleNamespace
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.models.physics.synchronization import DelayedCouplingInteraction
        >>> coupling = DelayedCouplingInteraction('coupling', source='a', target='b').check().prepare()
        >>> namespace = SimpleNamespace(source=SimpleNamespace(theta=var('a')), target=SimpleNamespace(theta=var('b')))
        >>> [law.value for law in coupling.run(SimpleNamespace(**vars(namespace), K=var('K'), tau=1)).laws_]
        [K*sin(-b + delayed(a, 1))]
    """

    def __init__(
        self,
        label: str,
        *,
        source: Annotated[str, RoleSpec()],
        target: Annotated[str, RoleSpec()],
        K: Annotated[float, ParameterSpec()] = 1.0,
        tau: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
    ) -> None:
        super().__init__(label, target=target, state='theta')
        self.source = source
        self.K = K
        self.tau = tau

    def _get_rate(self, namespace: SimpleNamespace) -> Expression:
        """Return `K sin(theta_s(t - tau) - theta_t)`.

        Args:
            namespace:
                The namespace of the symbols the rate can use.

        Returns:
            rate:
                The rate of the phase of the target.
        """
        return namespace.K * sin(delayed(namespace.source.theta, namespace.tau) - namespace.target.theta)

Equations

oscillator_0.theta: diff(theta_oscillator_0(t), t) == K_coupling_1_0*sin(-theta_oscillator_0(t) + theta_oscillator_1(t - tau_coupling_1_0)) + K_coupling_2_0*sin(-theta_oscillator_0(t) + theta_oscillator_2(t - tau_coupling_2_0)) + K_coupling_3_0*sin(-theta_oscillator_0(t) + theta_oscillator_3(t - tau_coupling_3_0)) + K_coupling_4_0*sin(-theta_oscillator_0(t) + theta_oscillator_4(t - tau_coupling_4_0)) + omega_oscillator_0
oscillator_1.theta: diff(theta_oscillator_1(t), t) == K_coupling_0_1*sin(theta_oscillator_0(t - tau_coupling_0_1) - theta_oscillator_1(t)) + K_coupling_2_1*sin(-theta_oscillator_1(t) + theta_oscillator_2(t - tau_coupling_2_1)) + K_coupling_3_1*sin(-theta_oscillator_1(t) + theta_oscillator_3(t - tau_coupling_3_1)) + K_coupling_4_1*sin(-theta_oscillator_1(t) + theta_oscillator_4(t - tau_coupling_4_1)) + omega_oscillator_1
oscillator_2.theta: diff(theta_oscillator_2(t), t) == K_coupling_0_2*sin(theta_oscillator_0(t - tau_coupling_0_2) - theta_oscillator_2(t)) + K_coupling_1_2*sin(theta_oscillator_1(t - tau_coupling_1_2) - theta_oscillator_2(t)) + K_coupling_3_2*sin(-theta_oscillator_2(t) + theta_oscillator_3(t - tau_coupling_3_2)) + K_coupling_4_2*sin(-theta_oscillator_2(t) + theta_oscillator_4(t - tau_coupling_4_2)) + omega_oscillator_2
oscillator_3.theta: diff(theta_oscillator_3(t), t) == K_coupling_0_3*sin(theta_oscillator_0(t - tau_coupling_0_3) - theta_oscillator_3(t)) + K_coupling_1_3*sin(theta_oscillator_1(t - tau_coupling_1_3) - theta_oscillator_3(t)) + K_coupling_2_3*sin(theta_oscillator_2(t - tau_coupling_2_3) - theta_oscillator_3(t)) + K_coupling_4_3*sin(-theta_oscillator_3(t) + theta_oscillator_4(t - tau_coupling_4_3)) + omega_oscillator_3
oscillator_4.theta: diff(theta_oscillator_4(t), t) == K_coupling_0_4*sin(theta_oscillator_0(t - tau_coupling_0_4) - theta_oscillator_4(t)) + K_coupling_1_4*sin(theta_oscillator_1(t - tau_coupling_1_4) - theta_oscillator_4(t)) + K_coupling_2_4*sin(theta_oscillator_2(t - tau_coupling_2_4) - theta_oscillator_4(t)) + K_coupling_3_4*sin(theta_oscillator_3(t - tau_coupling_3_4) - theta_oscillator_4(t)) + omega_oscillator_4