Kuramoto on networks
Kuramoto oscillators coupled along the edges of a network, theta_i' = omega_i + K sum_j A_ij sin(...).
- Name:
kuramoto_on_networks - Field:
physics/synchronization - Formalism: ordinary differential equations
- References: Arenas et al. 2008; Jensen 2023
Factory
@register_model(
field='physics/synchronization',
tags=('synchronization', 'oscillators', 'phase', 'network'),
references=('Arenas et al. 2008', 'Jensen 2023'),
defaults={'until': 40.0, 'dt': 0.2},
observables=lambda system: _order_parameter('order', 'oscillators'),
)
def kuramoto_on_networks(
n_oscillators: int = 200,
*,
network: str = 'watts_strogatz',
degree: int = 6,
rewiring: float = 0.1,
K: float = 1.0,
sigma: float = 0.5,
seed: int = 0,
) -> System:
"""Return Kuramoto oscillators coupled along the edges of a network, `theta_i' = omega_i + K sum_j A_ij sin(...)`.
Oscillator `i` follows `theta_i' = omega_i + (K / <k>) sum_j w_ij sin(theta_j - theta_i)` over its neighbours `j`,
where the weights `w_ij = 1` live on a group on the edges and `<k>` is the mean degree. The coupling runs once per
edge. The natural frequencies are drawn from a normal distribution of width `sigma`, and the phases uniformly from
the circle. Above a critical coupling of the order of the all-to-all value `2 sigma sqrt(2 / pi)`, the order
parameter `r = |<exp(i theta)>|` grows from about `1 / sqrt(N)` towards 1, and small-world shortcuts lower it.
Args:
n_oscillators:
The number of oscillators, the nodes of the network.
network:
The generator, `'watts_strogatz'`, `'erdos_renyi'`, or `'barabasi_albert'`.
degree:
The mean degree of the network, even for the Watts-Strogatz ring.
rewiring:
The probability of rewiring every edge of the Watts-Strogatz ring.
K:
The coupling strength, divided by the mean degree.
sigma:
The standard deviation of the natural frequencies.
seed:
The seed of the network, the frequencies, and the phases.
Returns:
system:
The model, with the phases `oscillators.theta`, the weights `links.w`, and the order parameter `order`.
Raises:
ValueError:
The network is unknown.
Examples:
>>> from skcomplex.models.physics.synchronization import kuramoto_on_networks
>>> system = kuramoto_on_networks(20, degree=4)
>>> system.elements[1].check().prepare().size_
40
"""
space = _network(network, n_oscillators, degree, rewiring, seed)
rng = np.random.default_rng(seed + 1)
mean_degree = 2 * space.graph.number_of_edges() / n_oscillators
oscillators = LatticeElement(
'oscillators',
element=PhaseOscillatorElement,
space=space,
values={'theta': rng.uniform(0, 2 * math.pi, n_oscillators), 'omega': rng.normal(0.0, sigma, n_oscillators)},
visual=VisualSpec('sites', {'states': ['theta'], 'low': 0.0, 'high': 2 * math.pi}),
)
links = GroupElement('links', element=LinkElement, space=space, on='edges')
coupling = EdgeCouplingInteraction('coupling', target='oscillators', links='links', K=K / mean_degree)
return System(elements=[oscillators, links], interactions=[coupling])
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 LinkElement(Element):
"""Edge of a network with a weight `w`, which couplings along the edge read.
Args:
label:
The label of the edge or the group on the edges.
visual:
How the edge is drawn, or `None` for `default_visual_`.
w:
The weight of the edge.
Attributes:
default_visual_:
The visual of the edge when it sets none.
Examples:
>>> from skcomplex.models.physics.synchronization import LinkElement
>>> LinkElement('link', w=0.5).w
0.5
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
w: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
) -> None:
super().__init__(label, visual=visual)
self.w = w
class EdgeCouplingInteraction(KuramotoCouplingInteraction):
"""Kuramoto coupling along the weighted edges of a network, `theta_i' += K w_ij sin(theta_j - theta_i)`.
The coupling runs once per edge of the network of its oscillators and reads the weight `w` of the edge from the
group on the edges.
Args:
label:
The label of the interaction, unique among the parts of its system.
target:
The path of the group of oscillators on the nodes.
links:
The path of the group on the edges, whose parameter `w` weighs every edge.
K:
The coupling strength.
alpha:
The phase lag.
Examples:
>>> from skcomplex.models.physics.synchronization import EdgeCouplingInteraction
>>> EdgeCouplingInteraction('coupling', target='nodes', links='edges', K=0.5).weights
'w'
"""
def __init__(
self,
label: str,
*,
target: Annotated[str, RoleSpec()],
links: Annotated[str, RoleSpec()],
K: float = 1.0,
alpha: float = 0.0,
) -> None:
super().__init__(label, target=target, K=K, alpha=alpha, weights='w')
self.links = links
Equations