.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "api/gallery/chaos/maps/plot_smale_horseshoe.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code. .. rst-class:: sphx-glr-example-title .. _sphx_glr_api_gallery_chaos_maps_plot_smale_horseshoe.py: Smale's Horseshoe Map: Stretch, Fold, and Return ================================================= Smale's horseshoe (1965) takes the unit square :math:`Q`, squeezes it horizontally by :math:`\lambda < 1/2`, stretches it vertically by :math:`\mu > 2`, and bends the resulting strip into a U whose two legs lie back across :math:`Q`. Only two horizontal strips of the square return to it, each by an affine branch: .. math:: f(x, y) = \begin{cases} (\lambda x,\ \mu y) & y \le 1/\mu \quad (H_0) \\ (1 - \lambda x,\ \mu (1 - y)) & y \ge 1 - 1/\mu \quad (H_1) \end{cases} The middle strip lands on the bend, outside the square. The points that never leave :math:`Q`, forward or backward in time, form a Cantor set on which :math:`f` acts as the full shift on two symbols. The example uses :math:`\lambda = 1/3`, :math:`\mu = 3`. .. GENERATED FROM PYTHON SOURCE LINES 23-34 .. code-block:: Python import matplotlib.pyplot as plt import numpy as np from physicskit.chaos.systems.maps import SmaleHorseshoe from physicskit.chaos.visualizers import theme from physicskit.chaos.visualizers.dynamic_plots import animate_horseshoe_map system = SmaleHorseshoe(contraction=1.0 / 3.0, expansion=3.0) lam, mu = system.contraction, system.expansion .. GENERATED FROM PYTHON SOURCE LINES 35-44 Animation: stretch, fold, and keep what returns ------------------------------------------------ The square starts as three colored bands: :math:`H_0` (blue), the middle strip (gray) and :math:`H_1` (red). Each iteration stretches the square into a tall thin strip, folds its upper part over, and drops the gray band left on the bend. The blue and red bands become the two legs of the horseshoe, the vertical strips :math:`V_0` and :math:`V_1`. The next iteration colors what is left by band again and folds it once more: each step keeps two thirds of the area and doubles the number of strips. .. GENERATED FROM PYTHON SOURCE LINES 44-48 .. code-block:: Python anim = animate_horseshoe_map(system, n_points=20000, n_iterations=3, frames_per_iteration=30) plt.show() .. container:: sphx-glr-animation .. raw:: html .. GENERATED FROM PYTHON SOURCE LINES 49-52 To save the animation to a file, use e.g.:: anim.save("smale_horseshoe.gif", writer="pillow", fps=20) .. GENERATED FROM PYTHON SOURCE LINES 54-61 The square and its image ------------------------ Folding the outline of each band shows :math:`f(Q)` directly: a horseshoe that crosses :math:`Q` in the two strips :math:`V_0` and :math:`V_1`. :math:`H_1` lands upside down in :math:`V_1`; that reversal is the fold. :meth:`~physicskit.chaos.systems.maps.SmaleHorseshoe.fold` gives the image of any point of the square. .. GENERATED FROM PYTHON SOURCE LINES 61-111 .. code-block:: Python def band_outline(y_lo: float, y_hi: float, n: int = 400) -> np.ndarray: """Boundary of the band ``[0, 1] x [y_lo, y_hi]``, densely sampled.""" s = np.linspace(0.0, 1.0, n) y = y_lo + (y_hi - y_lo) * s return np.concatenate( [ np.column_stack([s, np.full(n, y_lo)]), np.column_stack([np.ones(n), y]), np.column_stack([s[::-1], np.full(n, y_hi)]), np.column_stack([np.zeros(n), y[::-1]]), ] ) bands = [ (0.0, 1.0 / mu, theme.PRIMARY, "$H_0$"), (1.0 / mu, 1.0 - 1.0 / mu, theme.MUTED, "middle"), (1.0 - 1.0 / mu, 1.0, theme.ACCENT, "$H_1$"), ] fig, (ax_q, ax_f) = plt.subplots(1, 2, figsize=(9, 5.5), sharey=True) for ax in (ax_q, ax_f): ax.add_patch(plt.Rectangle((0.0, 0.0), 1.0, 1.0, fill=False, edgecolor=theme.STRUCTURE, lw=1.2, zorder=3)) ax.set_aspect("equal") ax.set_xlim(-0.05, 1.05) ax.set_ylim(-0.05, 1.6) ax.set_xlabel("x") ax_q.set_ylabel("y") for y_lo, y_hi, color, label in bands: outline = band_outline(y_lo, y_hi) ax_q.fill(outline[:, 0], outline[:, 1], color=color, alpha=0.6, label=label) image = system.fold(outline) ax_f.fill(image[:, 0], image[:, 1], color=color, alpha=0.6) # Arrows along the left edge of H1 and its image show the reversal. ax_q.annotate("", xy=(0.03, 0.97), xytext=(0.03, 1.0 - 1.0 / mu + 0.03), arrowprops={"arrowstyle": "->", "lw": 1.5}) top = system.fold(np.array([0.0, 0.97])) bottom = system.fold(np.array([0.0, 1.0 - 1.0 / mu + 0.03])) ax_f.annotate("", xy=top, xytext=bottom, arrowprops={"arrowstyle": "->", "lw": 1.5}) ax_f.text(lam / 2, 0.5, "$V_0$", ha="center", va="center", fontsize=14) ax_f.text(1 - lam / 2, 0.5, "$V_1$", ha="center", va="center", fontsize=14) ax_q.set_title("The square $Q$") ax_f.set_title("Its image $f(Q)$") ax_q.legend(loc="upper right") fig.tight_layout() .. image-sg:: /api/gallery/chaos/maps/images/sphx_glr_plot_smale_horseshoe_002.png :alt: The square $Q$, Its image $f(Q)$ :srcset: /api/gallery/chaos/maps/images/sphx_glr_plot_smale_horseshoe_002.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 112-122 The invariant Cantor set ------------------------ Points that stay in :math:`Q` for :math:`n` forward iterations fill :math:`2^n` horizontal strips. Points that stay for :math:`n` backward iterations fill :math:`2^n` vertical strips. The points that do both fill :math:`4^n` small squares, each of side :math:`3^{-n}` here. :meth:`~physicskit.chaos.systems.maps.SmaleHorseshoe.survives` tests this for a grid of points. As :math:`n \to \infty` the squares shrink to the invariant set :math:`\Lambda`, the product of two middle-thirds Cantor sets. .. GENERATED FROM PYTHON SOURCE LINES 122-137 .. code-block:: Python resolution = 729 centers = (np.arange(resolution) + 0.5) / resolution grid = np.stack(np.meshgrid(centers, centers), axis=-1) fig2, axes = plt.subplots(1, 4, figsize=(15, 4)) cases = [(2, 0, "2 forward"), (0, 2, "2 backward"), (2, 2, "2 forward and backward"), (4, 4, "4 forward and backward")] for ax, (n_fwd, n_bwd, label) in zip(axes, cases): mask = system.survives(grid, n_forward=n_fwd, n_backward=n_bwd) ax.imshow(mask, origin="lower", extent=(0, 1, 0, 1), cmap="Greys", vmin=0, vmax=1) ax.set_title(f"stays in Q: {label}") ax.set_xticks([]) ax.set_yticks([]) fig2.suptitle("Points of the square that stay in it") fig2.tight_layout() .. image-sg:: /api/gallery/chaos/maps/images/sphx_glr_plot_smale_horseshoe_003.png :alt: Points of the square that stay in it, stays in Q: 2 forward, stays in Q: 2 backward, stays in Q: 2 forward and backward, stays in Q: 4 forward and backward :srcset: /api/gallery/chaos/maps/images/sphx_glr_plot_smale_horseshoe_003.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 138-148 Symbolic dynamics: one orbit per symbol sequence ------------------------------------------------ Record, for a point of :math:`\Lambda`, whether each iterate is in :math:`H_0` or :math:`H_1`. Every bi-infinite sequence of 0s and 1s occurs, and it fixes the point uniquely. A periodic sequence gives a periodic orbit, so :math:`f^n` has exactly :math:`2^n` fixed points. Each branch acts affinely on :math:`x` and :math:`y` separately, so :meth:`~physicskit.chaos.systems.maps.SmaleHorseshoe.periodic_points` computes them all exactly. Below, the period-3 points are labeled by their itineraries and drawn on the squares that survive 3 iterations. .. GENERATED FROM PYTHON SOURCE LINES 148-162 .. code-block:: Python period = 3 points = system.periodic_points(period) mask3 = system.survives(grid, n_forward=period, n_backward=period) fig3, ax_p = plt.subplots(figsize=(6.5, 6)) ax_p.imshow(mask3, origin="lower", extent=(0, 1, 0, 1), cmap="Greys", vmin=0, vmax=3) ax_p.scatter(points[:, 0], points[:, 1], color=theme.ACCENT, s=25, zorder=3) for code, (px, py) in enumerate(points): ax_p.annotate(format(code, f"0{period}b"), (px, py), xytext=(4, 4), textcoords="offset points", fontsize=8) ax_p.set_xlabel("x") ax_p.set_ylabel("y") ax_p.set_title(f"The {2**period} fixed points of $f^{period}$ and their itineraries") fig3.tight_layout() .. image-sg:: /api/gallery/chaos/maps/images/sphx_glr_plot_smale_horseshoe_004.png :alt: The 8 fixed points of $f^3$ and their itineraries :srcset: /api/gallery/chaos/maps/images/sphx_glr_plot_smale_horseshoe_004.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 163-166 The count of periodic points grows as :math:`2^n`, so the topological entropy is :math:`\ln 2`. Iterating each computed point :math:`n` times brings it back to itself, a check on the closed form. .. GENERATED FROM PYTHON SOURCE LINES 166-188 .. code-block:: Python periods = np.arange(1, 11) counts = [] for n in periods: pts = system.periodic_points(n) images = pts.copy() for _ in range(n): images = system.fold(images) counts.append(np.sum(np.linalg.norm(images - pts, axis=1) < 1e-6)) counts = np.array(counts) fig4, ax_n = plt.subplots(figsize=(7, 5)) ax_n.semilogy(periods, counts, "o", color=theme.PRIMARY, label="fixed points of $f^n$ (checked)") ax_n.semilogy(periods, np.exp(system.topological_entropy() * periods), "--", color=theme.MUTED, label=r"$e^{n \ln 2} = 2^n$") ax_n.set_xlabel("period n") ax_n.set_ylabel("number of fixed points") ax_n.set_title("Periodic orbits grow at entropy $\\ln 2$") ax_n.legend() fig4.tight_layout() plt.show() .. image-sg:: /api/gallery/chaos/maps/images/sphx_glr_plot_smale_horseshoe_005.png :alt: Periodic orbits grow at entropy $\ln 2$ :srcset: /api/gallery/chaos/maps/images/sphx_glr_plot_smale_horseshoe_005.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 189-193 Check ----- f^n has exactly 2^n fixed points (one per binary itinerary), so the topological entropy is ln 2. .. GENERATED FROM PYTHON SOURCE LINES 193-196 .. code-block:: Python assert abs(system.topological_entropy() - np.log(2)) < 1e-12 np.testing.assert_array_equal(counts, 2**periods) assert len(points) == 2**period .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 12.011 seconds) .. _sphx_glr_download_api_gallery_chaos_maps_plot_smale_horseshoe.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_smale_horseshoe.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_smale_horseshoe.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_smale_horseshoe.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_