Hopf
The Hopf normal form, whose focus gives birth to a limit cycle of radius √(-μ / a) as μ crosses 0.
- Name:
hopf_form - Field:
mathematics/dynamical_systems - Formalism: ordinary differential equations
- References: Gros 2015; Dilão 2023
Factory
@register_model(
field='mathematics/dynamical_systems',
tags=('bifurcation', 'normal form', 'ode', 'limit cycle'),
references=('Gros 2015', 'Dilão 2023'),
defaults={'until': 50.0, 'dt': 0.1},
)
def hopf_form(x: float = 0.1, y: float = 0.0, *, mu: float = 0.25, omega: float = 1.0, a: float = -1.0) -> System:
"""Return the Hopf normal form, whose focus gives birth to a limit cycle of radius `√(-μ / a)` as μ crosses 0.
`x' = μx - ωy + a x (x² + y²)` and `y' = ωx + μy + a y (x² + y²)`, supercritical for `a < 0`.
Args:
x:
The initial first coordinate.
y:
The initial second coordinate.
mu:
The bifurcation parameter.
omega:
The angular frequency.
a:
The cubic coefficient, the sign of the first Lyapunov coefficient.
Returns:
system:
The model, with the states `form.x` and `form.y`.
Examples:
>>> from skcomplex.models.mathematics.dynamical_systems import hopf_form
>>> hopf_form(mu=-0.1).elements[0].mu
-0.1
"""
return System(elements=[HopfElement('form', x=x, y=y, mu=mu, omega=omega, a=a)])
Components
class HopfElement(Element):
"""Normal form of the Hopf bifurcation in Cartesian coordinates, with the radial law `r' = mu r + a r^3`.
Args:
label:
The label of the element.
visual:
How the element is drawn, or `None` for `default_visual_`.
x:
The initial real part.
y:
The initial imaginary part.
mu:
The bifurcation parameter.
omega:
The angular frequency.
a:
The cubic coefficient, negative for the supercritical bifurcation.
Examples:
>>> from skcomplex.models.mathematics.dynamical_systems import HopfElement
>>> HopfElement('form', mu=0.5).mu
0.5
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
x: Annotated[object, StateSpec()] = 0.1,
y: Annotated[object, StateSpec()] = 0.0,
mu: Annotated[object, ParameterSpec()] = 0.25,
omega: Annotated[object, ParameterSpec()] = 1.0,
a: Annotated[object, ParameterSpec()] = -1.0,
) -> None:
super().__init__(label, visual=visual)
self.x = x
self.y = y
self.mu = mu
self.omega = omega
self.a = a
def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
"""Return the rates of the real and the imaginary part.
Args:
s:
The namespace of the symbols the laws can use.
Returns:
laws:
The laws of the element.
"""
return [
RateLaw(s.x, s.mu * s.x - s.omega * s.y + s.a * s.x * (s.x**2 + s.y**2)),
RateLaw(s.y, s.omega * s.x + s.mu * s.y + s.a * s.y * (s.x**2 + s.y**2)),
]
Equations
form.x: diff(x_form(t), t) == (x_form(t)^2 + y_form(t)^2)*a_form*x_form(t) + mu_form*x_form(t) - omega_form*y_form(t)
form.y: diff(y_form(t), t) == (x_form(t)^2 + y_form(t)^2)*a_form*y_form(t) + omega_form*x_form(t) + mu_form*y_form(t)