Subcritical Hopf normal form
The subcritical Hopf normal form, where a stable focus coexists with a stable limit cycle.
- Name:
subcritical_hopf_form - Field:
mathematics/dynamical_systems - Formalism: ordinary differential equations
- References: Bertin 2021
Factory
@register_model(
field='mathematics/dynamical_systems',
tags=('bifurcation', 'normal form', 'ode', 'limit cycle', 'bistability', 'hysteresis'),
references=('Bertin 2021',),
defaults={'until': 50.0, 'dt': 0.1},
)
def subcritical_hopf_form(
x: float = 0.1,
y: float = 0.0,
*,
alpha: float = -0.1,
omega: float = 1.0,
q: float = 0.0,
gamma: float = 1.0,
s: float = 0.0,
) -> System:
"""Return the subcritical Hopf normal form, where a stable focus coexists with a stable limit cycle.
The complex amplitude `z = x + iy` follows `z' = (alpha + iω) z + (1 + iq) |z|² z - (gamma + is) |z|⁴ z`, so its
modulus follows `r' = alpha r + r³ - gamma r⁵`. The focus `z = 0` is stable for `alpha < 0`. The stable limit cycle
of radius `r² = (1 + √(1 + 4 alpha gamma)) / (2 gamma)` exists for `alpha > -1 / (4 gamma)`, and an unstable cycle
separates the two attractors while `alpha < 0`. A slow sweep of `alpha` up and down therefore jumps to the cycle at
`alpha = 0` and falls back to the focus at `alpha = -1 / (4 gamma)`, a hysteresis loop.
Args:
x:
The initial real part of `z`.
y:
The initial imaginary part of `z`.
alpha:
The bifurcation parameter `alpha`, the linear growth rate.
omega:
The angular frequency `ω` at small amplitude.
q:
The nonlinear frequency shift `q` of the cubic term.
gamma:
The stabilizing quintic coefficient `gamma`, positive.
s:
The nonlinear frequency shift `s` of the quintic term.
Returns:
system:
The model, with the states `form.x` and `form.y`.
Examples:
>>> from skcomplex.models.mathematics.dynamical_systems import subcritical_hopf_form
>>> subcritical_hopf_form(alpha=0.1).elements[0].alpha
0.1
"""
return System(
elements=[SubcriticalHopfElement('form', x=x, y=y, alpha=alpha, omega=omega, q=q, gamma=gamma, s=s)],
)
Components
class SubcriticalHopfElement(Element):
"""Subcritical Hopf normal form `z' = (alpha + i omega) z + (1 + i q) |z|^2 z - (gamma + i s) |z|^4 z`.
The complex amplitude is `z = x + i y`.
Args:
label:
The label of the element.
visual:
How the element is drawn, or `None` for `default_visual_`.
x:
The initial real part of `z`.
y:
The initial imaginary part of `z`.
alpha:
The bifurcation parameter, the linear growth rate.
omega:
The angular frequency at small amplitude.
q:
The nonlinear frequency shift of the cubic term.
gamma:
The stabilizing quintic coefficient, positive.
s:
The nonlinear frequency shift of the quintic term.
Examples:
>>> from skcomplex.models.mathematics.dynamical_systems import SubcriticalHopfElement
>>> SubcriticalHopfElement('form', alpha=0.1).alpha
0.1
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
x: Annotated[object, StateSpec()] = 0.1,
y: Annotated[object, StateSpec()] = 0.0,
alpha: Annotated[object, ParameterSpec()] = -0.1,
omega: Annotated[object, ParameterSpec()] = 1.0,
q: Annotated[object, ParameterSpec()] = 0.0,
gamma: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
s: Annotated[object, ParameterSpec()] = 0.0,
) -> None:
super().__init__(label, visual=visual)
self.x = x
self.y = y
self.alpha = alpha
self.omega = omega
self.q = q
self.gamma = gamma
self.s = s
def _get_rates(self, s: SimpleNamespace) -> tuple[Expression, Expression]:
"""Return the rates of the real and the imaginary part of the amplitude."""
radius = s.x**2 + s.y**2
real = s.alpha + radius - s.gamma * radius**2
imaginary = s.omega + s.q * radius - s.s * radius**2
return real * s.x - imaginary * s.y, imaginary * s.x + real * s.y
def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
"""Return the rates of the real and the imaginary part of the amplitude.
Args:
s:
The namespace of the symbols the laws can use.
Returns:
laws:
The laws of the element.
"""
real, imaginary = self._get_rates(s)
return [RateLaw(s.x, real), RateLaw(s.y, imaginary)]
Equations
form.x: diff(x_form(t), t) == -((x_form(t)^2 + y_form(t)^2)^2*gamma_form - x_form(t)^2 - y_form(t)^2 - alpha_form)*x_form(t) + ((x_form(t)^2 + y_form(t)^2)^2*s_form - (x_form(t)^2 + y_form(t)^2)*q_form - omega_form)*y_form(t)
form.y: diff(y_form(t), t) == -((x_form(t)^2 + y_form(t)^2)^2*s_form - (x_form(t)^2 + y_form(t)^2)*q_form - omega_form)*x_form(t) - ((x_form(t)^2 + y_form(t)^2)^2*gamma_form - x_form(t)^2 - y_form(t)^2 - alpha_form)*y_form(t)