Coupled map lattices on networks
Chaotic logistic maps on the nodes of a network, each pulled towards the mean of its neighbours.
- Name:
coupled_map_lattices_on_networks - Field:
physics/synchronization - Formalism: discrete map
- References: Arenas et al. 2008; Kaneko 1989
Factory
@register_model(
field='physics/synchronization',
tags=('synchronization', 'map', 'chaos', 'network'),
references=('Arenas et al. 2008', 'Kaneko 1989'),
defaults={'until': 100.0, 'dt': 1.0},
observables=lambda system: [ObservableSpec('spread', group='maps', variable='x', reduce='var')],
)
def coupled_map_lattices_on_networks(
n_nodes: int = 100,
*,
network: str = 'erdos_renyi',
degree: int = 20,
rewiring: float = 0.1,
r: float = 4.0,
epsilon: float = 0.9,
seed: int = 0,
) -> System:
"""Return chaotic logistic maps on the nodes of a network, each pulled towards the mean of its neighbours.
Node `i` follows `x_i(t + 1) = (1 - epsilon) f(x_i) + (epsilon / k_i) sum_j A_ij f(x_j)` with the logistic map
`f(x) = r x (1 - x)`, a mean over the neighbours of the network. For a coupling `epsilon` large enough against the
Lyapunov exponent of the map and the spectral gap of the network, the maps synchronize on one chaotic orbit, and
the variance of the states over the nodes falls to zero. Below it they stay incoherent.
Args:
n_nodes:
The number of maps, the nodes of the network.
network:
The generator, `'watts_strogatz'`, `'erdos_renyi'`, or `'barabasi_albert'`.
degree:
The mean degree of the network.
rewiring:
The probability of rewiring every edge of the Watts-Strogatz ring.
r:
The parameter of the logistic map, chaotic at 4.
epsilon:
The coupling between 0 and 1.
seed:
The seed of the network and the initial states.
Returns:
system:
The model, with the states `maps.x` and their variance over the nodes, `spread`.
Raises:
ValueError:
The network is unknown.
Examples:
>>> from skcomplex.models.physics.synchronization import coupled_map_lattices_on_networks
>>> coupled_map_lattices_on_networks(20).elements[0].check().prepare().size_
20
"""
space = _network(network, n_nodes, degree, rewiring, seed)
rng = np.random.default_rng(seed + 1)
maps = LatticeElement(
'maps',
element=LogisticNodeElement,
space=space,
values={'x': rng.uniform(0.0, 1.0, n_nodes), 'r': r, 'epsilon': epsilon},
visual=VisualSpec('sites', {'states': ['x'], 'low': 0.0, 'high': 1.0}),
)
coupling = LogisticCouplingInteraction('coupling', target='maps')
return System(elements=[maps], interactions=[coupling], time=DiscreteTime())
Components
class LogisticNodeElement(Element):
"""Logistic map on a node of a network, with its value, growth rate, and weight of the neighbours.
`LogisticCouplingInteraction` iterates `x_i(t + 1) = (1 - epsilon) f(x_i) + epsilon mean_j f(x_j)`, where the map
`f(x) = r x (1 - x)` of the node mixes with the mean of the maps of its neighbours `j` on the network.
Args:
label:
The label of the node or the group on the nodes.
visual:
How the node is drawn, or `None` for `default_visual_`.
x:
The initial value.
r:
The growth rate of the logistic map.
epsilon:
The weight of the neighbours.
Attributes:
default_visual_:
The visual of the node when it sets none.
Examples:
>>> from skcomplex.models.physics.synchronization import LogisticNodeElement
>>> LogisticNodeElement('node').epsilon
0.5
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
x: Annotated[float, StateSpec()] = 0.5,
r: Annotated[float, ParameterSpec()] = 4.0,
epsilon: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.5,
) -> None:
super().__init__(label, visual=visual)
self.x = x
self.r = r
self.epsilon = epsilon
class LogisticCouplingInteraction(CouplingInteraction):
"""Coupled logistic maps, `x_i(t + 1) = (1 - epsilon) f(x_i) + epsilon mean_j f(x_j)` with `f(x) = r x (1 - x)`.
The map of every node mixes with the mean of the maps of its neighbours `j` on its lattice or network, with the
parameters `r` and `epsilon` of the node. A node with no neighbours keeps `(1 - epsilon) f(x_i)`. It is the map
form of coupling, with the mean of the maps of the neighbours combined with the map of the node.
Args:
label:
The label of the interaction, unique among the parts of its system.
target:
The path of the group of maps, on the nodes of a lattice or network.
state:
The name of the state that the maps iterate.
Examples:
>>> import networkx as nx
>>> from skcomplex.core.elements import LatticeElement
>>> from skcomplex.core.spaces import DiscreteTime, NetworkSpace
>>> from skcomplex.core.system import System
>>> from skcomplex.engine.runtime import Simulation
>>> from skcomplex.models.physics.synchronization import LogisticCouplingInteraction, LogisticNodeElement
>>> maps = LatticeElement(
... 'maps',
... element=LogisticNodeElement,
... space=NetworkSpace(nx.path_graph(2)),
... values={'x': [0.25, 0.5], 'epsilon': 0.5},
... )
>>> coupling = LogisticCouplingInteraction('coupling', target='maps')
>>> system = System(elements=[maps], interactions=[coupling], time=DiscreteTime())
>>> Simulation(system).check().prepare().run(until=1).trajectory_.values('maps.x')[-1].tolist()
[0.875, 0.875]
"""
def __init__(self, label: str, *, target: Annotated[str, RoleSpec()], state: str = 'x') -> None:
super().__init__(
label,
target=target,
read=state,
write=state,
function=self._get_map,
reduce='mean',
form='map',
combine=self._get_mix,
)
self.state = state
@staticmethod
def _logistic(r: Expression, x: Expression) -> Expression:
"""Return the logistic map `r x (1 - x)`."""
return r * x * (1 - x)
def _get_map(self, namespace: SimpleNamespace) -> Expression:
"""Return the map of the neighbour, with the growth rate of the node."""
return self._logistic(namespace.member.r, getattr(namespace.other, self.state))
def _get_mix(self, namespace: SimpleNamespace, reduction: Target) -> Target:
"""Return the map of the node mixed with the mean map of its neighbours."""
member = namespace.member
own = self._logistic(member.r, getattr(member, self.state))
return (1 - member.epsilon) * own + member.epsilon * reduction
Equations
maps.x[i]: x_maps(t + 1) == (epsilon_maps - 1)*r_maps*(x_maps(t) - 1)*x_maps(t) + _neighbours_0*epsilon_maps