Skip to content

Bifurcation Diagram: Logistic Map

Overview

This example demonstrates how to create bifurcation diagrams using dynlib's parameter sweep and plotting tools. Bifurcation diagrams visualize how the long-term behavior of a dynamical system changes as a parameter varies, revealing transitions between fixed points, periodic orbits, and chaotic dynamics.

The logistic map x_{n+1} = r·x_n·(1-x_n) is a classic example exhibiting the period-doubling route to chaos as the parameter r increases from 2.5 to 4.0.

Key Concepts

  • Parameter sweeps: Computing trajectories across a range of parameter values
  • Bifurcation extraction: Post-processing trajectories into scatter data for visualization
  • Extraction modes: Different strategies for capturing system behavior (all, tail, final, extrema)
  • Period-doubling cascade: The Feigenbaum scenario leading to chaos

The Logistic Map Model

The logistic map is defined as:

\[ x_{n+1} = r \cdot x_n \cdot (1 - x_n) \]

Key bifurcation points: - r < 3.0: Stable fixed point - r = 3.0: First period-doubling (period-2 orbit emerges) - r ≈ 3.449: Period-4 orbit - r ≈ 3.57: Onset of chaos (Feigenbaum point r∞ ≈ 3.5699) - r = 4.0: Fully developed chaos

Basic Example

The simplest bifurcation diagram uses the built-in logistic map model:

from dynlib import setup
from dynlib.analysis import sweep
from dynlib.plot import bifurcation_diagram, theme, fig, export
import numpy as np

# Setup model
sim = setup("builtin://map/logistic", stepper="map", jit=True)

# Parameter range
r_values = np.linspace(2.8, 4.0, 5000)

# Run parameter sweep
sweep_result = sweep.traj_sweep(
    sim,
    param="r",
    values=r_values,
    record_vars=["x"],
    N=100,           # iterations per parameter value
    transient=500,   # discard initial transient
)

# Extract bifurcation data
result = sweep_result.bifurcation("x")

# Plot
theme.use("notebook")
ax = fig.single(size=(10, 6))

bifurcation_diagram(
    result,
    xlabel="r",
    ylabel="x*",
    title="Bifurcation Diagram: Logistic Map",
    ax=ax
)

export.show()

Complete Examples in Repository

1. Basic Bifurcation

"""
Bifurcation diagram demonstration for the logistic map.

"""

from __future__ import annotations
import numpy as np
from dynlib import setup
from dynlib.analysis import sweep
from dynlib.plot import export, theme, fig, bifurcation_diagram

r0 = 2.8
rend = 4.0

sim = setup("builtin://map/logistic", stepper="map", jit=True, disk_cache=True)

r_values = np.linspace(r0, rend, 20000)

print("Computing bifurcation diagram...")
print(f"  Parameter: r ∈ [{r0}, {rend}]")
print(f"  Grid points: {len(r_values)}")

sweep_result = sweep.traj_sweep(
    sim,
    param="r",
    values=r_values,
    record_vars=["x"],
    N=100,  
    transient=500,  
    record_interval=1,  
    parallel_mode="auto",
)
# Extract bifurcation data (defaults to .all() mode - all recorded points)
result = sweep_result.bifurcation("x")
# Alternatively: result = sweep_result.bifurcation("x").tail(50)  # for tail mode

print(f"  Total points plotted: {len(result.p)}")
print("Done!")

theme.use("notebook")
theme.update(grid=True)

ax = fig.single(size=(10, 8))

bifurcation_diagram(
    result,
    color="blue",
    xlim=(r0, rend),
    ylim=(0, 1),
    xlabel="r",
    ylabel="x*",
    title="Bifurcation Diagram: Logistic Map",
    xlabel_fs=12,
    ylabel_fs=12,
    title_fs=14,
    ax=ax,
)

export.show()
# Save figure with high dpi will be more smooth
# export.savefig(ax, "bifurcation_logistic_map.png", dpi=600)

Demonstrates the standard workflow: - High-resolution parameter sweep (20,000 points) - Default extraction mode (all recorded points) - Simple plotting with minimal customization

2. Mode Comparison

"""
Bifurcation diagram comparison for the logistic map showing different modes.

This example demonstrates the three different bifurcation analysis modes:
1. "final": Shows only the final state (good for fixed points)
2. "tail": Shows multiple samples from the attractor (reveals periodic orbits)
3. "extrema": Shows local extrema (emphasizes periodic structure)
"""

from __future__ import annotations
import numpy as np
from dynlib import setup
from dynlib.analysis import sweep
from dynlib.plot import export, theme, fig

# Setup the builtin logistic map model
sim = setup("builtin://map/logistic", stepper="map", jit=True, disk_cache=True)

# Focus on the interesting region where bifurcations occur
r_values = np.linspace(2.8, 4.0, 1500)

print("Computing bifurcation diagrams in different modes...")

# Runtime sweep (do once)
sweep_result = sweep.traj_sweep(
    sim,
    param="r",
    values=r_values,
    record_vars=["x"],
    N=500,
    transient=200,
)

extractor = sweep_result.bifurcation("x")

# Mode 1: "final" - just the final value (good for convergent behavior)
result_final = extractor.final()

# Mode 2: "tail" - multiple samples from attractor
result_tail = extractor.tail(50)

# Mode 3: "extrema" - local extrema (maxima + minima)
result_extrema = extractor.extrema(tail=100, max_points=30, min_peak_distance=1)

print("Done! Creating comparison plot...")

# Configure plot theme
theme.use("notebook")
theme.update(grid=True)

# Create 3-panel comparison
from dynlib.plot import bifurcation_diagram

ax = fig.grid(rows=3, cols=1, size=(10, 10))

# Panel 1: Final mode
bifurcation_diagram(
    result_final,
    marker=".",
    ms=0.5,
    alpha=0.7,
    color="blue",
    ax=ax[0, 0],
    xlim=(2.8, 4.0),
    ylim=(0, 1),
    ylabel="x*",
    title='Mode: "final" (last recorded value)',
    title_fs=12,
)

# Panel 2: Tail mode
bifurcation_diagram(
    result_tail,
    marker=".",
    ms=0.3,
    alpha=0.5,
    color="black",
    ax=ax[1, 0],
    xlim=(2.8, 4.0),
    ylim=(0, 1),
    ylabel="x*",
    title='Mode: "tail" (attractor cloud)',
    title_fs=12,
)

# Panel 3: Extrema mode
bifurcation_diagram(
    result_extrema,
    marker=".",
    ms=0.5,
    alpha=0.6,
    color="red",
    ax=ax[2, 0],
    xlim=(2.8, 4.0),
    ylim=(0, 1),
    xlabel="r",
    ylabel="x*",
    title='Mode: "extrema" (local maxima and minima)',
    title_fs=12,
)

export.show()

Compares three extraction modes side-by-side:

extractor = sweep_result.bifurcation("x")

# Mode 1: Final value only
result_final = extractor.final()

# Mode 2: Last 50 points (attractor cloud)
result_tail = extractor.tail(50)

# Mode 3: Local extrema (maxima + minima)
result_extrema = extractor.extrema(tail=100, max_points=30)