diff --git a/.gitignore b/.gitignore index 225334a..8206731 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ Cargo.lock dist frames* out.mp4 - +__pycache__ diff --git a/tools/deep-zoom/LOCATIONS.md b/tools/deep-zoom/LOCATIONS.md new file mode 100644 index 0000000..679f668 --- /dev/null +++ b/tools/deep-zoom/LOCATIONS.md @@ -0,0 +1,167 @@ +# Deep-zoom locations + +Beautiful deep Mandelbrot locations, all computed with `find_deep.py` and checked with a +`--headless` render. Every one is an exact minibrot nucleus (Newton's method at +`2·depth + 60` digits), not a coordinate copied from somewhere, so the digits are right all +the way down. + +## How to use them + +Each location has two views on the same centre: + +- **Minibrot:** half-height ≈ 3× the minibrot's size. A tiny copy of the whole set sits in + concentric colour bands, and there are more bands the deeper it is. +- **Embedded Julia set:** half-height ≈ size^0.75, about ¾ of the way down in log-zoom. + This is where each location looks unique. The minibrots all look alike. + +```sh +cargo run --release -- --view=RE,IM,HALF_HEIGHT # interactive +cargo run --release -- --headless --view=RE,IM,HALF_HEIGHT --export-path shot.png +# zoom video from the full set down to a minibrot, piped to ffmpeg (see Makefile): +cargo run --release -- --headless --view=-0.75,0,1.5 --to-view=RE,IM,HALF_HEIGHT \ + --fps 60 --duration 60 --export-path - | make hevc-nvenc +``` + +Pass negative coordinates as `--view=-0.77...` (with the `=`): clap would read `--view -0.77...` as a flag. +Iterations scale with depth automatically (about 90k at 10⁻¹⁰⁰ and 270k at 10⁻³⁰⁰), which is +enough for these periods. + +## Infinite spirals (Misiurewicz points) + +These are the anchor points. Zoom straight into one and the same spiral repeats forever, +at any depth. Every minibrot below sits just beside one of them. + +| Region | Point | Preperiod, period | Looks like | +|---|---|---|---| +| Seahorse valley | `-0.7756837680090537974694835039347410457282465046158` `0.13646736829469012473327440961784876519777211159247` | 24, 2 | Two-armed spirals of seahorses, repeating at every depth. | +| Elephant valley | `0.29207386191445391799971751769220573408040189143315` `0.015770917211390118082469028230338337682600488632339` | 32, 3 | Three-armed spirals of elephant trunks. | +| Top antenna | `-0.10110375797210418675706890936004723524958119917843` `0.95629400795235409290719434118824484558046591980423` | 38, 4 | Four-armed branch points on the upper antenna, with thin dendrites instead of spirals. | + +## Seahorse valley + +### Seahorse valley, minibrot at 3.7e-41 (period 1034) + +The embedded Julia set is a pale double spiral: two S-shaped arms of tiny seahorses wind into a bright centre, with a third lace band sweeping around them. The best one for a short video, and it renders in seconds. + +```sh +# embedded Julia set +--view=-0.77568376800905379745347652613832924487504096622022,0.13646736829469012473375311880735014411233827361594,1e-30 +# minibrot +--view=-0.77568376800905379745347652613832924487504096622022,0.13646736829469012473375311880735014411233827361594,1.1e-40 +``` + +### Seahorse valley, minibrot at 1.14e-101 (period 2868) + +At the Julia depth there's a dark blue field with a glowing core and lace threads crossing it. + +```sh +# embedded Julia set +--view=-0.77568376800905379746948350393474104572824650461579342765769586746577714101553375163935420167637213972439444483,0.13646736829469012473327440961784876519777211159246218448277041663416833530840062457397927487282583495378053495,1e-76 +# minibrot +--view=-0.77568376800905379746948350393474104572824650461579342765769586746577714101553375163935420167637213972439444483,0.13646736829469012473327440961784876519777211159246218448277041663416833530840062457397927487282583495378053495,3.4e-101 +``` + +### Seahorse valley, minibrot at 1.37e-200 (period 5895) + +Two filaments cross in an X over a warm brown background, with a small dark spiral where they meet. + +```sh +# embedded Julia set +--view=-0.77568376800905379746948350393474104572824650461580039479495200140066601722281958989488315936878225494657853849784368247991666558116221905228489656259693587754973261632614186930985854124920967267440737850057557,0.13646736829469012473327440961784876519777211159246769640780019540083566597481550087544861124417865661202932094564677695883049019713653069907950852152823160218141353878979871215911794286224832089496659451498185,1e-150 +# minibrot +--view=-0.77568376800905379746948350393474104572824650461580039479495200140066601722281958989488315936878225494657853849784368247991666558116221905228489656259693587754973261632614186930985854124920967267440737850057557,0.13646736829469012473327440961784876519777211159246769640780019540083566597481550087544861124417865661202932094564677695883049019713653069907950852152823160218141353878979871215911794286224832089496659451498185,4.1e-200 +``` + +### Seahorse valley, minibrot at 1.83e-300 (period 8922) + +Faint cream threads, and a spiral wound so tight it reads as a point. Period 8922, so the minibrot needs a long render. + +```sh +# embedded Julia set +--view=-0.775683768009053797469483503934741045728246504615800394794952001400666017222819589894883159368782255028252296689050972508942991790804637379375064948276511044167168394677127466362472192356953918438738697548775738756167744421444715134058363508346750264411321139485834882343906172015818621107808449401958194265462,0.136467368294690124733274409617848765197772111592467696407800195400835665974815500875448611244178656625706823059759836009902469392551108149827753781145338163746214967880988693412636771323356061132825037923307975359146666033691080243216637006179374407866237575096209504710178751614036006531433101633993532313930,1e-225 +# minibrot +--view=-0.775683768009053797469483503934741045728246504615800394794952001400666017222819589894883159368782255028252296689050972508942991790804637379375064948276511044167168394677127466362472192356953918438738697548775738756167744421444715134058363508346750264411321139485834882343906172015818621107808449401958194265462,0.136467368294690124733274409617848765197772111592467696407800195400835665974815500875448611244178656625706823059759836009902469392551108149827753781145338163746214967880988693412636771323356061132825037923307975359146666033691080243216637006179374407866237575096209504710178751614036006531433101633993532313930,5.5e-300 +``` + +## Elephant valley + +### Elephant valley, minibrot at 1.29e-100 (period 782) + +A cream-coloured double S-spiral, like the seahorse one but with fatter, beaded arms. + +```sh +# embedded Julia set +--view=0.2920738619144539179997175176922057340804018914331633820622644536373359023331882946705674169275530423410817910,0.01577091721139011808246902823033833768260048863232968822435568049949556139801802963232184866659058960379114747,1e-75 +# minibrot +--view=0.2920738619144539179997175176922057340804018914331633820622644536373359023331882946705674169275530423410817910,0.01577091721139011808246902823033833768260048863232968822435568049949556139801802963232184866659058960379114747,3.9e-100 +``` + +### Elephant valley, minibrot at 1.43e-199 (period 1585) + +A single huge spiral arm made of blue beads curling across the whole frame. + +```sh +# embedded Julia set +--view=0.2920738619144539179997175176922057340804018914331538397786950546981063169810374784665382516408545756138262706218874924432355556243165250541700374639889797148498919340003273815669351046658761147565052417000965,0.01577091721139011808246902823033833768260048863233880759961760295375606839216565332458201188913912642954367433579737643167232896069795830226759356080847862646789481028238105648099709502522309228500630759855757,1e-149 +# minibrot +--view=0.2920738619144539179997175176922057340804018914331538397786950546981063169810374784665382516408545756138262706218874924432355556243165250541700374639889797148498919340003273815669351046658761147565052417000965,0.01577091721139011808246902823033833768260048863233880759961760295375606839216565332458201188913912642954367433579737643167232896069795830226759356080847862646789481028238105648099709502522309228500630759855757,4.3e-199 +``` + +### Elephant valley, minibrot at 3.82e-300 (period 2395) + +A spiral galaxy: several beaded arms around a pinwheel centre. One of the prettiest frames here. + +```sh +# embedded Julia set +--view=0.292073861914453917999717517692205734080401891433153839778695054698106316981037478466538251640854575589843066619974864800862332115945960921998657430606126553202998390050225413256445451049563878137878839982267258218894836912449161037745627504826537881261626243871678392881757578278459983842286413975991353319739,0.0157709172113901180824690282303383376826004886323388075996176029537560683921656533245820118891391263013273917522825544701901299061542128984468742890308365571942081484001405437827548345359998503228665872823816067983725089244499770271352539008763406229496083105636482420467114597267096082878888045800333807911146,1e-225 +# minibrot +--view=0.292073861914453917999717517692205734080401891433153839778695054698106316981037478466538251640854575589843066619974864800862332115945960921998657430606126553202998390050225413256445451049563878137878839982267258218894836912449161037745627504826537881261626243871678392881757578278459983842286413975991353319739,0.0157709172113901180824690282303383376826004886323388075996176029537560683921656533245820118891391263013273917522825544701901299061542128984468742890308365571942081484001405437827548345359998503228665872823816067983725089244499770271352539008763406229496083105636482420467114597267096082878888045800333807911146,1.1e-299 +``` + +## Top antenna + +### Top antenna, minibrot at 6.11e-101 (period 245) + +A dark branching dendrite like a frost cross, with four-way junctions. + +```sh +# embedded Julia set +--view=-0.10110375797210418675706890936004723524958119917842627217950669362738928440088634862532432662296073501093701563,0.95629400795235409290719434118824484558046591980423293884325556254155313535666457147658005358708674409029391915,1e-75 +# minibrot +--view=-0.10110375797210418675706890936004723524958119917842627217950669362738928440088634862532432662296073501093701563,0.95629400795235409290719434118824484558046591980423293884325556254155313535666457147658005358708674409029391915,1.8e-100 +``` + +### Top antenna, minibrot at 5.28e-200 (period 473) + +A chain of six-armed snowflakes linked by thin filaments. + +```sh +# embedded Julia set +--view=-0.10110375797210418675706890936004723524958119917842839398761868801597328463328677019373309617158827470368429189213012997868613724029746440432852247903869013157556141380437202406412221853553594880182186959159214,0.95629400795235409290719434118824484558046591980423399388630140415938010604675760901574969597486831722998038561988250849596064882005832681021199867736493316924923123916964900533995659228499205141785254396348248,1e-149 +# minibrot +--view=-0.10110375797210418675706890936004723524958119917842839398761868801597328463328677019373309617158827470368429189213012997868613724029746440432852247903869013157556141380437202406412221853553594880182186959159214,0.95629400795235409290719434118824484558046591980423399388630140415938010604675760901574969597486831722998038561988250849596064882005832681021199867736493316924923123916964900533995659228499205141785254396348248,1.6e-199 +``` + +### Top antenna, minibrot at 8.34e-301 (period 705) + +A single twelve-armed star, very symmetric. The antenna minibrots have short periods (245-705), so these are the fastest deep renders. + +```sh +# embedded Julia set +--view=-0.1011037579721041867570689093600472352495811991784283939876186880159732846332867701937330961715882746718394428627914481009391257007785408536971203165750666758077852384490279260184029373211737264310068845597273991782305778295187471341711226626762421910644338164121042775094463557363347706110614218633830535086184,0.9562940079523540929071943411882448455804659198042339938863014041593801060467576090157496959748683171680339080646917869452781885502668216669916185990922330667568398963038228431738928191227884090004548615469303071899797403388188723954061899156655653387543351937028936674153437083087070219108726088551230849901498,1e-225 +# minibrot +--view=-0.1011037579721041867570689093600472352495811991784283939876186880159732846332867701937330961715882746718394428627914481009391257007785408536971203165750666758077852384490279260184029373211737264310068845597273991782305778295187471341711226626762421910644338164121042775094463557363347706110614218633830535086184,0.9562940079523540929071943411882448455804659198042339938863014041593801060467576090157496959748683171680339080646917869452781885502668216669916185990922330667568398963038228431738928191227884090004548615469303071899797403388188723954061899156655653387543351937028936674153437083087070219108726088551230849901498,2.5e-300 +``` + +## Finding more + +```sh +./find_deep.py --list # known regions +./find_deep.py seahorse 250 # minibrot of size ~1e-500 near the seahorse point +./find_deep.py seahorse 100 0.2,0.7 # a different minibrot at the same depth +./find_deep.py -0.1011,0.9563,38,4 80 # any Misiurewicz point, given as re,im,k,p +``` + +A disk of radius 10⁻ᴰ around a Misiurewicz point holds a minibrot of size about 10⁻²ᴰ. +Newton sometimes fails to converge (elephant at depth 100 with the default offset did). The +script detects this from the size and asks for a different offset. Needs `mpmath`. \ No newline at end of file diff --git a/tools/deep-zoom/find_deep.py b/tools/deep-zoom/find_deep.py new file mode 100755 index 0000000..ea644a3 --- /dev/null +++ b/tools/deep-zoom/find_deep.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +"""Find deep Mandelbrot minibrots near a Misiurewicz point. + +Misiurewicz points (preperiodic c, f^(k+p)(0) = f^k(0)) have self-similar +spirals at every depth. A disk of radius r = 10^-D around one contains a +minibrot of size ~r^2, found by: + + 1. the ball-period method: the first n where the disk's image covers 0 + gives the period p of a nucleus inside it; + 2. Newton's method on f^p(0) = 0 at high precision for the nucleus; + 3. the standard size estimate (Heiland-Allen) for its scale. + +Zooming at the nucleus shows spirals, then an embedded Julia set (around +size^0.75), then the minibrot (half-height ~3x size). + +Usage: + find_deep.py REGION DEPTH [OFFSET_RE,OFFSET_IM] + find_deep.py --list + +REGION is a name from REGIONS or "re,im,k,p" for any Misiurewicz point. +OFFSET (default 0.6,0.3, in units of r) picks a different minibrot at the +same depth. If the printed size is far below 10^-(2*DEPTH), Newton didn't +converge: try another offset. + +Needs mpmath (pip install mpmath). +""" + +import sys + +from mpmath import log10, mp, mpc, mpf + +# name: (approximate seed, preperiod k, period p). The exact point is +# refined by Newton at the requested precision. +REGIONS = { + "seahorse": (("-0.77568377", "0.13646737"), 24, 2), + "elephant": (("0.2925", "0.0149"), 32, 3), + "antenna": (("-0.1011", "0.9563"), 38, 4), +} + + +def converged(d): + return abs(d) < mpf(10) ** (-mp.dps + 20) + + +def misiurewicz(c, k, p, steps=200): + """Newton on f^(k+p)(0) - f^k(0) = 0.""" + for _ in range(steps): + z = dz = mpc(0) + zk = dzk = None + for i in range(1, k + p + 1): + dz = 2 * z * dz + 1 + z = z * z + c + if i == k: + zk, dzk = z, dz + d = (z - zk) / (dz - dzk) + c -= d + if converged(d): + break + return c + + +def period_in_ball(c, r, maxit=1_000_000): + """First n where the disk of radius r around c maps onto a disk containing 0.""" + z = dz = mpc(0) + for n in range(1, maxit): + dz = 2 * z * dz + 1 + z = z * z + c + if abs(z) < abs(dz) * r: + return n + if abs(z) > 4: + return None + return None + + +def nucleus(c, p, steps=200): + """Newton on f^p(0) = 0.""" + for _ in range(steps): + z = dz = mpc(0) + for _ in range(p): + dz = 2 * z * dz + 1 + z = z * z + c + d = z / dz + c -= d + if converged(d): + break + return c + + +def size(c, p): + """Approximate size of the minibrot with nucleus c and period p.""" + z = mpc(0) + l = b = mpc(1) + for _ in range(1, p): + z = z * z + c + l = 2 * z * l + b = b + 1 / l + return abs(1 / (b * l * l)) + + +def main(argv): + if len(argv) >= 1 and argv[0] == "--list": + for name, ((re, im), k, p) in REGIONS.items(): + print(f"{name:10} ~{re}{'+' if not im.startswith('-') else ''}{im}i M({k},{p})") + return 0 + if len(argv) not in (2, 3): + print(__doc__, file=sys.stderr) + return 2 + + region, depth = argv[0], int(argv[1]) + if region in REGIONS: + (re, im), k, p = REGIONS[region] + else: + re, im, k, p = region.split(",") + k, p = int(k), int(p) + off_re, off_im = argv[2].split(",") if len(argv) == 3 else ("0.6", "0.3") + + mp.dps = 2 * depth + 60 + m = misiurewicz(mpc(re, im), k, p) + r = mpf(10) ** (-depth) + c0 = m + r * mpc(off_re, off_im) + period = period_in_ball(c0, r) + if period is None: + print("no period found (the disk escapes)", file=sys.stderr) + return 1 + n = nucleus(c0, period) + s = size(n, period) + if s < mpf(10) ** (-2 * depth - 10): + print(f"size {mp.nstr(s, 3)} is implausibly small: Newton didn't converge, " + "try another offset", file=sys.stderr) + return 1 + + digits = int(-log10(s)) + 10 + log_s = float(log10(s)) + re_s = mp.nstr(n.real, digits, strip_zeros=False) + im_s = mp.nstr(n.imag, digits, strip_zeros=False) + print(f"# {region} depth={depth} period={period} size={mp.nstr(s, 3)}") + print("# minibrot:") + print(f"--view={re_s},{im_s},{mp.nstr(3 * s, 2)}") + print(f"# embedded Julia set on the way down:") + print(f"--view={re_s},{im_s},1e{round(0.75 * log_s)}") + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:]))