Skip to content

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, from Element

Interactions:

  • AggregateAveragingInteraction, from MapInteraction
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