Skip to content

Cubic chaotic map and its chaotic walk

Bertin's cubic chaotic map and the chaotic walk it drives, which spreads like a random walk.

  • Name: cubic_chaotic_map
  • Field: mathematics/dynamical_systems
  • Formalism: discrete map
  • References: Bertin 2021

Factory

@register_model(
    field='mathematics/dynamical_systems',
    tags=('chaos', 'discrete map', 'random walk', 'diffusion'),
    references=('Bertin 2021',),
    defaults={'until': 200, 'dt': 1},
    observables=lambda system: [
        ObservableSpec('displacement', group='walkers', variable='y'),
        ObservableSpec('square_displacement', _square_displacement, group='walkers'),
    ],
)
def cubic_chaotic_map(n_walkers: int = 100, *, lam: float = 15.0) -> System:
    """Return Bertin's cubic chaotic map and the chaotic walk it drives, which spreads like a random walk.

    Every walker follows the map `x' = λ x (x - 1/2)(x - 1) + x` on the unit interval and moves by the step
    `y' = y + 2x - 1`. The fixed point `x = 1/2` is stable for `λ < 8`, a period-two cycle follows, and the map is
    chaotic at `λ = 15`. The walkers start at `y = 0` with values of `x` spread regularly over the unit interval, so
    the mean position stays near 0 while the mean square position grows linearly in time, and the invariant density of
    `x` is not uniform.

    Args:
        n_walkers:
            The number of walkers of the ensemble.

        lam:
            The parameter `λ` of the map, between 0 and 16.

    Returns:
        system:
            The model, with the states `walkers.x` and `walkers.y` and the observables `displacement`, the mean
            position, and `square_displacement`, the mean square position.

    Examples:
        >>> from skcomplex.models.mathematics.dynamical_systems import cubic_chaotic_map
        >>> cubic_chaotic_map(4, lam=10.0).elements[0].check().prepare().size_
        4
    """
    start = (np.arange(n_walkers) + 1 / 4) / n_walkers
    walkers = GroupElement(
        'walkers',
        element=CubicWalkElement,
        size=n_walkers,
        values={'x': start, 'y': 0.0, 'lam': lam},
    )
    return System(
        elements=[walkers],
    )

Components

class CubicWalkElement(Element):
    """Walker driven by Bertin's cubic chaotic map `x' = lam x (x - 1/2)(x - 1) + x`, which steps by `2 x - 1`.

    Args:
        label:
            The label of the element.

        visual:
            How the element is drawn, or `None` for `default_visual_`.

        x:
            The initial value of the map, between 0 and 1.

        y:
            The initial position of the walker.

        lam:
            The nonlinearity of the map.

    Examples:
        >>> from skcomplex.models.mathematics.dynamical_systems import CubicWalkElement
        >>> CubicWalkElement('walker', lam=8.0).lam
        8.0
    """

    visual = VisualSpec('points', {'position': 'y', 'states': ()})

    def __init__(
        self,
        label: str,
        visual: VisualSpec | None = None,
        *,
        x: Annotated[object, StateSpec()] = 0.3,
        y: Annotated[object, StateSpec(role='position')] = 0.0,
        lam: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 15.0,
    ) -> None:
        super().__init__(label, visual=visual)
        self.x = x
        self.y = y
        self.lam = lam

    def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
        """Return the next value of the map and the next position.

        Args:
            s:
                The namespace of the symbols the laws can use.

        Returns:
            laws:
                The laws of the element.
        """
        return [
            UpdateLaw(s.x, s.lam * s.x * (s.x - 1 / 2) * (s.x - 1) + s.x),
            UpdateLaw(s.y, s.y + 2 * s.x - 1),
        ]

Equations

walkers.x[i]: x_walkers(t + 1) == lam_walkers*(x_walkers(t) - 0.5)*(x_walkers(t) - 1)*x_walkers(t) + x_walkers(t)
walkers.y[i]: y_walkers(t + 1) == 2*x_walkers(t) + y_walkers(t) - 1