Aggregate-averaging synchronization of logistic and delay-coupled Bernoulli maps
Two chaotic maps coupled by aggregate averaging, which synchronize their chaos for strong coupling.
- Name:
aggregate_averaging_synchronization - Field:
physics/synchronization - Formalism: discrete map
- References: Gros 2015
Select this system
from skcomplex.systems import create_simulation, create_system, describe
system = create_system('aggregate_averaging_synchronization')
entry = describe('aggregate_averaging_synchronization')
trajectory = create_simulation('aggregate_averaging_synchronization').prepare().run().trajectory_
Factory
@register_system(
field='physics/synchronization',
tags=('synchronization', 'chaos', 'map', 'logistic map', 'bernoulli map', 'delay'),
references=('Gros 2015',),
defaults={'until': 50, 'dt': 1},
)
def aggregate_averaging_synchronization(
x1: float = 0.3,
x2: float = 0.6,
*,
r: float = 4.0,
kappa: float = 0.4,
kind: str = 'logistic',
delay: int = 0,
) -> System:
"""Return two chaotic maps coupled by aggregate averaging, which synchronize their chaos for strong coupling.
Each map iterates on the aggregate of its own value and the other's, `x_i(t + 1) = f(a_i)` with `a_1 = (1 - kappa)
x_1(t) + kappa x_2(t - T)` and `a_2 = (1 - kappa) x_2(t) + kappa x_1(t - T)`, for the delay `T`. The logistic map is
`f(a) = r a (1 - a)`. Without delay, its synchronized orbit is stable for `kappa > (r - 1) / (2 r)`, which is `3 /
8` for `r = 4`, and the maps agree in one step for `kappa = 1 / 2`. The Bernoulli shift `f(a) = r a mod 1` has the
constant slope `r`, so a difference `d` of the maps follows `d(t + 1) = r ((1 - kappa) d(t) - kappa d(t - T))`
exactly, and the maps synchronize when every root of `z^(T + 1) - r (1 - kappa) z^T + r kappa` lies in the unit
disk, which for `T = 0` is `|r (1 - 2 kappa)| < 1`. The delay shrinks the window of synchronizing couplings: for `r
= 1.5` it is `0.17 < kappa < 0.83` without delay, `0.17 < kappa < 0.66` for `T = 1`, and `0.17 < kappa < 0.44` for
`T = 2`, and it closes for `T >= 5`. Before the start, the delayed values equal the initial values. An integer slope
such as 2 loses one binary digit of the value per step in floating point and falls onto 0, so a slope that is not an
integer, such as 1.5, keeps the chaos.
Args:
x1:
The initial value of the first map.
x2:
The initial value of the second map.
r:
The growth rate of the logistic map.
kappa:
The coupling strength.
kind:
The map, `'logistic'` or `'bernoulli'`.
delay:
The delay `T` of the coupling, in steps.
Returns:
system:
The system, with the values `first.x` and `second.x`, and their lags `first.x_lag<j>` and
`second.x_lag<j>` for a delay.
Examples:
>>> from skcomplex.systems.physics.synchronization import aggregate_averaging_synchronization
>>> aggregate_averaging_synchronization(kappa=0.1).interactions[0].kappa
0.1
"""
if delay:
units: list[Element] = [
DelayedUnitElement('first', value=x1, delay=delay),
DelayedUnitElement('second', value=x2, delay=delay),
]
else:
units = [LogisticUnitElement('first', x=x1), LogisticUnitElement('second', x=x2)]
averaging = [
AggregateAveragingInteraction(
f'averaging_{target}',
source=source,
target=target,
r=r,
kappa=kappa,
kind=kind,
delay=delay,
)
for source, target in (('second', 'first'), ('first', 'second'))
]
return System(elements=units, interactions=averaging, time=DiscreteTime())
Components
Elements:
LogisticUnitElement, fromElement
Interactions:
AggregateAveragingInteraction, fromMapInteraction
class LogisticUnitElement(Element):
"""Value `x` in `[0, 1]` of a logistic map, which maps of the coupling iterate.
Args:
label:
The label of the unit.
visual:
How the unit is drawn, or `None` for `default_visual_`.
x:
The initial value.
Attributes:
default_visual_:
The visual of the unit when it sets none.
Examples:
>>> from skcomplex.systems.physics.synchronization import LogisticUnitElement
>>> LogisticUnitElement('unit').x
0.3
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
x: Annotated[float, StateSpec()] = 0.3,
) -> None:
super().__init__(label, visual=visual)
self.x = x
class AggregateAveragingInteraction(MapInteraction):
"""Logistic or Bernoulli map of an aggregate of the value of a target and the value of a source, possibly delayed.
The map sets `x_t(t + 1) = f(a)` with the aggregate `a = (1 - kappa) x_t(t) + kappa x_s(t - delay)`, and with the
logistic map `f(a) = r a (1 - a)` or the Bernoulli shift `f(a) = r a mod 1`. A delayed source keeps `delay` lags.
Args:
label:
The label of the interaction, unique among the parts of its system.
source:
The path of the unit whose value the aggregate reads.
target:
The path of the unit whose value the map sets.
r:
The growth rate of the logistic map.
kappa:
The weight of the source in the aggregate.
kind:
The map, `'logistic'` or `'bernoulli'`.
delay:
The number of steps by which the source is delayed.
Raises:
ValidationError:
The map is unknown, or the delay is not a nonnegative integer.
Examples:
>>> from types import SimpleNamespace
>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.systems.physics.synchronization import AggregateAveragingInteraction
>>> averaging = AggregateAveragingInteraction('averaging', source='a', target='b').prepare()
>>> source, target = SimpleNamespace(x=var('u')), SimpleNamespace(x=var('v'))
>>> namespace = SimpleNamespace(source=source, target=target, r=4, kappa=var('kappa'))
>>> [law.value.factor() for law in averaging.run(namespace).laws_]
[-4*(kappa*u - kappa*v + v)*(kappa*u - kappa*v + v - 1)]
"""
def __init__(
self,
label: str,
*,
source: Annotated[str, RoleSpec()],
target: Annotated[str, RoleSpec()],
r: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 4.0,
kappa: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.4,
kind: str = 'logistic',
delay: int = 0,
) -> None:
super().__init__(label, target=target, state='x', function=self._average)
self.source = source
self.r = r
self.kappa = kappa
self.kind = kind
self.delay = delay
def _check(self) -> None:
"""Raise an error for an unknown map or a delay that is not a nonnegative integer.
Raises:
ValidationError:
The map is unknown, or the delay is not a nonnegative integer.
"""
super()._check()
if self.kind not in MAPS:
error_msg = f'Unknown map {self.kind!r}. Use one of {", ".join(MAPS)}.'
raise ValidationError(error_msg)
if isinstance(self.delay, bool) or not isinstance(self.delay, int) or self.delay < 0:
error_msg = f'The delay of {self.label!r} is {self.delay!r}. Give a nonnegative integer.'
raise ValidationError(error_msg)
def _average(self, namespace: SimpleNamespace) -> Expression:
"""Return the map of the aggregate `a = (1 - kappa) x_t + kappa x_s(t - delay)`."""
source = lag(namespace.source.x, self.delay)
aggregate = (1 - namespace.kappa) * namespace.target.x + namespace.kappa * source
if self.kind == 'bernoulli':
return mod(namespace.r * aggregate, 1)
return namespace.r * aggregate * (1 - aggregate)
Equations
first.x: x_first(t + 1) == -((kappa_averaging_first - 1)*x_first(t) - kappa_averaging_first*x_second(t) + 1)*((kappa_averaging_first - 1)*x_first(t) - kappa_averaging_first*x_second(t))*r_averaging_first
second.x: x_second(t + 1) == -(kappa_averaging_second*x_first(t) - (kappa_averaging_second - 1)*x_second(t))*(kappa_averaging_second*x_first(t) - (kappa_averaging_second - 1)*x_second(t) - 1)*r_averaging_second