Stuart-Landau
The Stuart-Landau oscillator z' = (μ + i ω) z - (1 + i β) |z|² z, the normal form of a Hopf bifurcation.
- Name:
stuart_landau - Field:
physics/nonlinear_oscillators - Formalism: ordinary differential equations
- References: Gros 2015; Dilão 2023
Factory
@register_model(
field='physics/nonlinear_oscillators',
tags=('limit cycle', 'ode', 'hopf bifurcation', 'oscillation'),
references=('Gros 2015', 'Dilão 2023'),
defaults={'until': 30.0, 'dt': 0.05},
)
def stuart_landau(
x: float = 0.1,
y: float = 0.0,
*,
mu: float = 1.0,
omega: float = 1.0,
beta: float = 0.0,
) -> System:
"""Return the Stuart-Landau oscillator `z' = (μ + i ω) z - (1 + i β) |z|² z`, the normal form of a Hopf bifurcation.
The complex amplitude `z = x + i y` is written as two real states. For `μ > 0` every orbit except the origin
settles on the circle of radius `sqrt(μ)`, turning at the frequency `ω - β μ`.
Args:
x:
The initial real part of the amplitude.
y:
The initial imaginary part of the amplitude.
mu:
The distance from the Hopf bifurcation.
omega:
The linear frequency.
beta:
The nonlinear frequency shift.
Returns:
system:
The model, with the real part `oscillator.x` and the imaginary part `oscillator.y` of the amplitude.
Examples:
>>> from skcomplex.models.physics.nonlinear_oscillators import stuart_landau
>>> stuart_landau(mu=0.5).elements[0].mu
0.5
"""
return System(elements=[StuartLandauElement('oscillator', x=x, y=y, mu=mu, omega=omega, beta=beta)])
Components
class StuartLandauElement(Element):
"""Stuart-Landau oscillator `z' = tau ((mu + i omega) z - kappa (1 + i beta) |z|^2 z)` of `z = x + i y`.
It is the normal form of a supercritical Hopf bifurcation. For `mu > 0` the oscillator settles on the limit cycle
of radius `sqrt(mu / kappa)`, which it turns at the frequency `tau (omega - beta mu)`, and for `mu < 0` it decays to
the fixed point 0. The twist `beta` makes the frequency depend on the amplitude, and the time scale `tau` speeds
up or slows down the whole flow.
Args:
label:
The label of the oscillator or the group.
visual:
How the oscillator is drawn, or `None` for `default_visual_`.
x:
The initial real part of `z`.
y:
The initial imaginary part of `z`.
mu:
The growth rate of the fixed point 0, the bifurcation parameter.
omega:
The frequency of the linear rotation.
beta:
The twist, the shear of the frequency with the amplitude.
kappa:
The strength of the cubic saturation.
tau:
The time scale.
Attributes:
default_visual_:
The visual of the oscillator when it sets none.
Examples:
>>> import math
>>> from skcomplex.core.system import System
>>> from skcomplex.engine.runtime import Simulation
>>> from skcomplex.models.physics.common.oscillators import StuartLandauElement
>>> system = System(elements=[StuartLandauElement('oscillator', x=0.1, mu=4.0)])
>>> trajectory = Simulation(system, dt=0.5).check().prepare().run(until=10.0).trajectory_
>>> round(math.hypot(trajectory.values('oscillator.x')[-1], trajectory.values('oscillator.y')[-1]), 4)
2.0
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
x: Annotated[float, StateSpec()] = 1.0,
y: Annotated[float, StateSpec()] = 0.0,
mu: Annotated[float, ParameterSpec()] = 1.0,
omega: Annotated[float, ParameterSpec()] = 1.0,
beta: Annotated[float, ParameterSpec()] = 0.0,
kappa: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
tau: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
) -> None:
super().__init__(label, visual=visual)
self.x = x
self.y = y
self.mu = mu
self.omega = omega
self.beta = beta
self.kappa = kappa
self.tau = tau
def _get_laws(self, namespace: SimpleNamespace) -> list[BaseLaw]:
"""Return the rates of the real and imaginary parts of `z`.
Args:
namespace:
The namespace of the symbols the laws can use.
Returns:
laws:
The rates of `x` and `y`.
"""
s = namespace
saturation = s.kappa * (s.x**2 + s.y**2)
return [
RateLaw(s.x, s.tau * (s.mu * s.x - s.omega * s.y - saturation * (s.x - s.beta * s.y))),
RateLaw(s.y, s.tau * (s.mu * s.y + s.omega * s.x - saturation * (s.y + s.beta * s.x))),
]
Equations
oscillator.x: diff(x_oscillator(t), t) == ((x_oscillator(t)^2 + y_oscillator(t)^2)*(beta_oscillator*y_oscillator(t) - x_oscillator(t))*kappa_oscillator + mu_oscillator*x_oscillator(t) - omega_oscillator*y_oscillator(t))*tau_oscillator
oscillator.y: diff(y_oscillator(t), t) == -((beta_oscillator*x_oscillator(t) + y_oscillator(t))*(x_oscillator(t)^2 + y_oscillator(t)^2)*kappa_oscillator - omega_oscillator*x_oscillator(t) - mu_oscillator*y_oscillator(t))*tau_oscillator