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:
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: