History / version 7QZC4A10 · saved

Cite this version

@software{mandelbrot_zoom_2026,
  author = {ScienceHub},
  title = {Mandelbrot zoom},
  version = {7QZC4A10},
  year = {2026},
  publisher = {ScienceHub},
  url = {https://sciencehub.sh/sciencehub-team/mandelbrot-zoom/history/ver_01M4SGFGFWSASQB7EH7QZC4A10}
}
ScienceHub (2026). Mandelbrot zoom (Version 7QZC4A10) [Computer software]. ScienceHub. https://sciencehub.sh/sciencehub-team/mandelbrot-zoom/history/ver_01M4SGFGFWSASQB7EH7QZC4A10
cff-version: 1.2.0
message: "If you use this version, cite it as below."
title: "Mandelbrot zoom"
type: software
authors:
  - name: "ScienceHub"
version: "7QZC4A10"
date-released: "2026-10-13"
url: "https://sciencehub.sh/sciencehub-team/mandelbrot-zoom/history/ver_01M4SGFGFWSASQB7EH7QZC4A10"

This address names exactly this version. The project page cites all versions.

mandelbrot-zoom.pythe first version, as saved
import marimo

__generated_with = "0.25.1"
app = marimo.App(width="medium")


@app.cell(hide_code=True)
def _(mo):
    mo.md(
        r"""
        # Zooming into the Mandelbrot set

        Take a complex number $c$, start from $z = 0$, and repeat
        $z \mapsto z^2 + c$. If $z$ stays bounded forever, $c$ belongs to the
        Mandelbrot set. That one rule draws a boundary with unlimited detail:
        however far you zoom, new structure appears, including small copies of
        the whole set. Mitsuhiro Shishikura proved in 1998 that the boundary is
        so convoluted that its Hausdorff dimension is 2, the same as a filled
        area.

        Pick a place and zoom in. Each step of the slider magnifies ten times.
        Ordinary 64-bit numbers run out of precision near a magnification of
        $10^{13}$; beyond that, deep-zoom programs use arbitrary precision.
        """
    )
    return


@app.cell
def _():
    import marimo as mo
    import numpy as np
    import matplotlib.pyplot as plt
    return mo, np, plt


@app.cell
def _(mo):
    PLACES = {
        "Seahorse valley": complex(-0.743643887037151, 0.131825904205330),
        "Elephant valley": complex(0.285, 0.0115),
        "A small copy of the set": complex(-1.7685736562, 0.0007),
        "Spiral": complex(-0.761574, -0.0847596),
        "Whole set": complex(-0.6, 0.0),
    }
    place = mo.ui.dropdown(list(PLACES), value="Seahorse valley", label="Place")
    zoom = mo.ui.slider(0, 12, step=0.25, value=3, label="Magnification (powers of ten)")
    mo.vstack([place, zoom])
    return PLACES, place, zoom


@app.cell
def _(np):
    def escape_time(center, width, pixels=(480, 320), max_iter=500):
        """Smooth iteration count for each pixel; NaN inside the set.

        Iterates only the points that have not escaped yet, which keeps deep
        zooms fast where most of the frame is inside the set.
        """
        nx, ny = pixels
        x = center.real + (np.arange(nx) - nx / 2) * width / nx
        y = center.imag + (np.arange(ny) - ny / 2) * width / nx
        c = (x[None, :] + 1j * y[::-1, None]).ravel()
        z = np.zeros_like(c)
        result = np.full(c.shape, np.nan)
        active = np.arange(c.size)
        for n in range(max_iter):
            z = z * z + c[active]
            escaped = np.abs(z) > 256
            if escaped.any():
                done = active[escaped]
                result[done] = n + 1 - np.log2(np.log(np.abs(z[escaped])))
                active, z = active[~escaped], z[~escaped]
            if active.size == 0:
                break
        return result.reshape(ny, nx)

    return (escape_time,)


@app.cell
def _(PLACES, escape_time, mo, np, place, plt, zoom):
    width = 3.2 / 10**zoom.value
    iterations = int(300 + 150 * zoom.value)
    with mo.status.spinner(title=f"Iterating up to {iterations:,} times per pixel"):
        counts = escape_time(PLACES[place.value], width, max_iter=iterations)
    shade = np.sqrt(np.nan_to_num(counts, nan=0.0))
    fig, ax = plt.subplots(figsize=(7.2, 4.8))
    ax.imshow(np.where(np.isnan(counts), np.nan, np.sin(0.35 * shade) ** 2), cmap="twilight_shifted", interpolation="bilinear")
    ax.set_facecolor("black")
    ax.set_axis_off()
    ax.set_title(f"{place.value}, magnified {10**zoom.value:,.0f}×, frame width {width:.2e}")
    fig.tight_layout()
    fig
    return


@app.cell(hide_code=True)
def _(mo):
    mo.md(
        r"""
        ## How much of the plane is inside?

        The area of the Mandelbrot set is not known in closed form. A Monte
        Carlo estimate samples random points in a box around the set and
        counts how many stay bounded. The error shrinks like
        $1/\sqrt{N}$, so each extra digit costs a hundred times more samples.
        Published estimates put the area near 1.5066. A point that escapes
        only after more iterations than the limit is counted as inside, so a
        short run overestimates the area; raise the limit in your fork to see
        the estimate fall.
        """
    )
    return


@app.cell
def _(mo):
    samples = mo.ui.slider(10_000, 400_000, step=10_000, value=100_000, label="Random samples")
    samples
    return (samples,)


@app.cell
def _(mo, np, samples):
    rng = np.random.default_rng(0)
    c = rng.uniform(-2.0, 0.5, samples.value) + 1j * rng.uniform(-1.25, 1.25, samples.value)
    z = np.zeros_like(c)
    inside = np.ones(c.size, dtype=bool)
    for _ in range(400):
        z[inside] = z[inside] ** 2 + c[inside]
        inside &= np.abs(z) <= 2
    box = 2.5 * 2.5
    fraction = inside.mean()
    area = box * fraction
    error = box * np.sqrt(fraction * (1 - fraction) / c.size)
    mo.md(f"Estimated area: **{area:.4f} ± {error:.4f}** from {c.size:,} samples and 400 iterations.")
    return


@app.cell(hide_code=True)
def _(mo):
    mo.md(
        r"""
        ---
        **Method and limits.** Escape radius 256 with the normalised
        iteration count $n + 1 - \log_2 \log |z|$ for smooth colour; points
        that have not escaped within the iteration limit are drawn black and
        may still lie outside the set. 64-bit floating point throughout.
        **References.** B. Mandelbrot, *Ann. N.Y. Acad. Sci.* 357 (1980) 249;
        M. Shishikura, *Ann. of Math.* 147 (1998) 225.
        Fork it to colour by a different rule or to add perturbation theory
        for deeper zooms.
        """
    )
    return


if __name__ == "__main__":
    app.run()