codexsensitive-dependence.py
sensitive-dependence.py
"""Compare contraction and sensitive logistic evolution.
Tested with Python 3.14.4, NumPy 2.3.5 and Matplotlib 3.10.7.
Writes sensitive-dependence.png to the caller's working directory.
Uses only the dependencies in the repository's pyproject.toml.
"""
from pathlib import Path
import matplotlib
import numpy as np
matplotlib.use("Agg")
import matplotlib.pyplot as plt
def orbit(r, initial, steps):
values = np.empty(steps + 1)
values[0] = initial
for n in range(steps):
values[n + 1] = r * values[n] * (1 - values[n])
return values
def main():
plt.rcParams.update({"font.size": 11, "axes.titlesize": 12})
fig, axes = plt.subplots(2, 2, figsize=(11, 6.5), layout="constrained", facecolor="white")
steps = np.arange(56)
initial = 0.2
epsilon = 1e-8
for col, r in enumerate((2.8, 4.0)):
first = orbit(r, initial, 55)
second = orbit(r, initial + epsilon, 55)
upper, lower = axes[:, col]
upper.plot(steps, first, color="#2166ac", lw=1.4, label=r"$x_0=0.2$")
upper.plot(steps, second, color="#cf651a", lw=1.2, ls="--",
label=r"$y_0=0.2+10^{-8}$")
upper.set(xlim=(0, 55), ylim=(0, 1), xlabel="Iteration n", ylabel="State",
title=f"r = {r:g}: " + ("nearby orbits converge" if col == 0 else "nearby orbits separate"))
upper.legend(loc="lower left", fontsize=10, framealpha=0.95)
separation = np.abs(first - second)
# Mask exact zeros rather than assigning them a fictitious log distance.
lower.semilogy(steps, np.where(separation > 0, separation, np.nan),
".-", ms=4, color="#2166ac", lw=1.4, label="Computed separation")
if col == 0:
lower.semilogy(steps, epsilon * 0.8 ** steps, ls="--", color="#666666", lw=1,
label=r"Reference slope: $(0.8)^n$")
else:
growth_steps = steps[steps <= 26]
lower.semilogy(growth_steps, epsilon * 2.0 ** growth_steps,
ls="--", color="#666666", lw=1,
label=r"Reference slope: $2^n$")
lower.axhline(0.1, color="#9e2929", lw=0.9, ls=":")
lower.text(54, 0.13, "separation = 0.1", color="#9e2929", ha="right", fontsize=9)
lower.set(xlim=(0, 55), ylim=(1e-15, 2), xlabel="Iteration n",
ylabel=r"Separation $|x_n-y_n|$",
title="Log scale reveals shrinking errors" if col == 0 else "Growth fluctuates, then is bounded by 1")
lower.legend(loc="lower left", fontsize=9, framealpha=0.95)
for ax in (upper, lower):
ax.set_facecolor("white")
ax.grid(alpha=0.18)
fig.savefig(Path.cwd() / "sensitive-dependence.png", dpi=120,
facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()