K I Martin recently popularized a perturbation technique to accelerate Mandelbrot set rendering in his SuperFractalThing program. I wrote up some of the mathematics behind it, extending Martin's description to handle interior distance estimation too. Unfortunately it's very easy to get glitchy images that are wrong in sometimes subtle ways.

The most obvious reference point is usually in a central minibrot, which means it is strictly periodic:

-1.760732891182472726272e+00 + 1.302137831089206469511e-02 i 4.0194366942304651e-14 @ -1.760732891182472889620498413132e+00 +R 1.302137831089204904674633295328e-02 iR

The current version of mightymandel computes an error estimate and shades worse errors redder. A better reference point for this image is in a non-central minibrot near the tip of a solid red patch:

-1.760732891182472726272e+00 + 1.302137831089206469511e-02 i 4.0194366942304651e-14 @ -1.76073289118248636633168329119453103e+00 +R 1.30213783108675495217125732772512438e-02 iR

Nearby higher period non-central minibrots tend to work even better, and their limit is a pre-periodic point - one that becomes periodic after a finite number of iterations. I explored a bit the basins of attraction of preperiodic points for a couple of embedded Julia sets (which are the features that are most often glitchy).

-1.7607328089719322109e+00 + 1.3021307542195548201e-02 i 1.5258789062500003e-05 @ 1 1/2 2 1/2 3 1/3 6 4/5 35 A

The saturated dots at the tips and spirals in this period 35 embedded Julia set are the preperiodic points of interest. They have period 3 (matching the outer influencing island) and preperiods 35 (matching the inner influencing island) and 36.

Then zooming deeper to the near the period 35 island and into one of its hairs finds a doubly-embedded Julia set between the period 35 outer influencing island and a period 177 inner influencing island. Rendering the Newton Basins now needs more than double precision floating point, and my Haskell code using qd's DoubleDouble took almost 4 hours on a quad core. This time the points of interest have period 35 (matching the outer island) and preperiods 177 (matching the inner island) and 178.

-1.76073288182181309054484516839 + 0.01302138541499395659022491468 i 2.2737367544323211e-13 @

Here's the same view rendered with mightymandel, first with the central minibrot as reference:

-1.76073288182181252065e+00 + 1.30213854149941790026e-02 i 2.2737367544323211e-13 @

Then a non-central minibrot:

-1.76073288182181252e+00 + 1.302138541499417901e-02 i 2.2737367544323211e-13 @ -1.760732881821779248788320301573183e+00 +R 1.302138541488527885806724080050218e-02 iR

And finally a limiting pre-periodic reference point:

-1.76073288182181252e+00 + 1.302138541499417901e-02 i 2.2737367544323211e-13 @ -1.7607328818218582927504155115035552 +R 1.3021385414920574393027950216090353e-2 iR

No single reference point gives a red-free image, but combining multiple reference points, each appropriate to various parts of the image, looks like it would be a promising approach - and the knowledge about where the view is in relation to outer and inner influencing islands and their (potentially echoed) embedded Julia sets could perhaps be used to calculate a few candidate reference points automatically.

]]>I finally got around to postprocessing the photos I took of the pages in my notebook written on topics concerning the Mandelbrot set. You can see it here: Mandelbrot Notebook

One of the (too-many) long term projects I have is to write a book about the Mandelbrot set that bridges the gap between popular science books ("wow fractals are cool") and mathematical texts ("theorem: something hard and obscure"). I've never written a book before and found myself getting distracted by irrelevancies (like page layout, fonts etc) and re-editing text over and over instead of actually adding new content, so I've been experimenting with git version control commit messages:

git clone https://git.gitorious.org/maximus/book.git cd book git log --color -u --reverse

The working title is How to write a book about the Mandelbrot set.

]]>The Buddhabrot fractal is related to the Mandelbrot set: for each
point \(c\) that is **not** in the Mandelbrot set, plot
all its iterates \(z_m\) where \(z_0 = 0\) and \(z_{n+1} = z_n^2 + c\).
The anti-Buddhabrot flips this, plotting all the iterates for points
that **are** in the Mandelbrot set. For the Buddhabrot,
the points not in the Mandelbrot set will escape to \(\infty\), so we
know when to stop iterating. Points in the interior don't escape, but
almost all converge to a periodic cycle. In the limit of plotting a
very large number of iterations, the iterates in these cycles will
strongly dominate due to the periodic repetition, such that the earlier
iterates will be invisible. Define the **ultimate**
anti-Buddhabrot to be the iterate plots of these limit cycles.

Now, a point \(z_c\) in the limit cycle for \(c\) of period \(p\)
satisfies \( F^p(z_c, c) = z_c \). There will be \(p\) different
\(z_c\) for each \(c\), but we just need one, and we can find it
numerically using Newton's method. Given an arbitrary \(c\) we need
to find its period first, which we can do by checking the interior
distance estimate for each **partial**. Define a partial
as a \(q\) such that \(|z_q| \lt |z_m|, 1 \le m \lt q\). If the interior
distance for \(c\) is negative, then \(c\) is not inside a component
of the Mandelbrot set of that period. Once we have \(z_c\), we can
plot the \(p\) iterates in the cycle.

Buddhabrot colouring commonly uses several monochrome image planes with different iteration limits, brighter where more iterates hit each pixel, which are combined into a colour image. With the ultimate anti-Buddhabrot, we can accumulate a colour image: each period is associated to an RGB value, and we can plot RGBA pixels (with A set to 1) with additive blending. The final A channel indicates how many iterates hit the pixel, but we also have the accumulated colours that can show which periods were involved. Post-processing the high dynamic range accumulation buffer to bring it down to something that can be displayed on a screen can bring out more of the details.

Calculation can be sped up using recursive subdivision. The root of the recursion passes over the Mandelbrot set parameter plane. There are a few different cases that can occur at each point:

- exterior
- Compute the exterior distance estimate: if it's so large that all of the subdivided child pixels would be exterior, bail out now, otherwise subdivide and recurse without plotting iterates.
- interior
- If the interior distance is so large that all of the subdivided child pixels would be interior to the same component, subdivide and recurse with the now-known period, otherwise recurse with the general method described above; plot the iterates in both cases.
- unknown
- If the iteration limit is reached, bail out without recursing.

The key speedup is switching to a simpler algorithm that just calculates \(z_c\) and plots the iterates with a known period. Another optimisation exploits the symmetry of the Mandelbrot set about the real axis. Finally it's possible to parallelize using OpenMP, with atomic pragmas to avoid race conditions when accumulating pixels.

Source code in C99: Ultimate Anti-Buddhabrot reference implementation. Runtime just under 20mins on a 3GHz quadcore CPU.

]]>Figure 4.22 on pages 204-205 of **The Science Of Fractal Images**
is presented with this description:

A region along the cardioid is continuously blown up and stretched out, so that the respective segment of the cardioid becomes a line segment. ... Our blow-up factor is chosen accordingly to the result that all disks in Figure 4.22 have the same size.

While waiting for a train the other day, I remembered this image, and wanted to recreate it, which required deriving the equations (they were not presented in the book).

A Möbius transformation of the complex plane is a rational function of the form

\[ f(z) = \frac{a z + b}{c z + d} \]

with \( a d − b c \ne 0 \). It has inverse

\[ f^{-1}(z) = \frac{d z - b}{-c z + a} \]

Möbius transformations are conformal, preserving angles but not lengths, and map generalized circles to generalized circles (generalized circles include lines as the special case of a circle through infinity). A Möbius transformation can be constructed from 3 points and their images. For our purposes, we can choose the images of \(P_0, P_1, P_\infty\) to be \(0, 1, \infty\), which gives

\[ F(z) = \frac {(z-P_0)(P_1-P_\infty)}{(z-P_\infty)(P_1-P_0)} \]

This \(F\) stretches and flattens a circle through \(P_0, P_1, P_\infty\) into a straight line, with the blow-up factor increasing towards \(\infty\). If \(P_\infty\) is chosen as a cusp, all the primary discs along the path to the cusp get stretched to be roughly the same size.

So far this works for circles, but cardioids require another conformal map to transform them into circles so that the Möbius transformation can work its magic. A cardioid with its cusp at \(0\) and its front at \(4\) is transformed into a unit circle centered at \(1\) by \(f(z) = \sqrt{z}\) (with the branch cut of the square root along the negative real axis). So to transform the period 1 cardioid of the Mandelbrot set (which has cusp at \(1/4\) and front at \(-3/4\)) to a unit circle centered at \(0\) the resulting conformal map is

\[ G(z) = \sqrt{1 - 4 z} - 1 \]

with inverse

\[ G^{-1}(z) = \frac{1 - (z+1)^2}{4} \]

Now, given \(P_0, P_1, P_\infty\) on the cardioid, form the Möbius transformation \(F\) for \(G(P_0), G(P_1), G(P_\infty)\), then \(F(G(c))\) maps \(c\) near the boundary of the Mandelbrot set cardioid to a flattened strip near the real axis. But when rendering images, we start from a rectangle with coordinates \([0..\text{width})\times[0..\text{height})\), so we have to work backwards to find the corresponding \(c\) in the parameter plane. Inverting \(k = F(G(c))\) gives \(G^{-1}(F^{-1}(k)) = c\).

Moreover, for distance estimation colouring it's useful to colour pixels according to the distance estimate relative to the pixel spacing, which is no longer fixed when applying a conformal mapping with non-constant scaling. The scale factor associated with a conformal mapping is the absolute value of its derivative, and the relevant derivatives are

\[ \begin{aligned} \frac{\partial}{\partial z} \frac{a z + b}{c z + d} &= \frac{a d - b c}{(c z + d)^2} \\ \frac{\partial}{\partial z} \frac{1 - (z+1)^2}{4} &= \frac{-(z+1)}{2} \\ \frac{\partial}{\partial z} f(g(z)) &= \frac{\partial f}{\partial z}(g(z)) \frac{\partial g}{\partial z}(z) \end{aligned} \]

The third equation is the chain rule for derivative of function composition, which can be intuitively expressed as "multiply all the derivatives at each step".

Putting together all the transformations and their derivatives and choosing the preimages \(P_0, P_1, P_\infty\) as bond points and cusps on the boundary of the Mandelbrot set, gives some stretched cusp pictures.

*Map of strip locations.*

*Elephant valley (cyan).*

*Seahorse valley (magenta).*

*"Double rabbit valley" (yellow).*

The Mandelbrot set is defined by iterations of a quadratic polynomial:

\[ \begin{aligned} F(z,c) &= z^2 + c \\ F^{n+1}(z, c) &= F^{n}(F(z,c),c) \end{aligned} \]

Atom domain
representation for the Mandelbrot set colours points according to
"near-miss" periods. The definition I'll use for \(a(c)\)
**atom period** of a point is:

\[ a(c) = q \text{ where } 1 \le q \le p \text{ minimizes } |F^q(0,c)| \]

taking \(p\) to be the period of the hyperbolic component containing \(c\) for interior points, or \(\infty\) for exterior points.

The atom periods of nearby points are likely to be the same, and
the **restricted atom periods** (limiting the maximum
iteration count to the desired atom period) form connected
**atom domains**. These domains may be expressed as:

\[ \{ c : |F^p(0,c)| \lt \min_{q=1}^{p-1} \{ |F^q(0,c) \} \} \]

The interior of each restricted atom domain with period \(p\) contains a nucleus \(c_0\) of period \(p\) with \(F^p(0,c_0) = 0\). On the boundary of the domain, \( |F^p(0,c)| = |F^q(0,c)| \) for some \(1 \le q \lt p \), which implies \(1 = |F^p(0,c)/F^q(0,c)| =: |G(c)| \). Assuming \(q\) to be constant throughout the whole domain, and approximating \(G\) by a power series at \(c = c_0 + h\) near the nucleus \(c_0\), gives:

\[ G(c) = G(c_0) + h \frac{\partial G}{\partial c}(c_0) + O(h^2) \]

Using the quotient rule \( (f/g)' = (f' g - f g')/g^2 \) and observiing that \(G(c_0) = 0\) results in

\[ \frac{\partial G}{\partial c}(c) = \frac{ \frac{\partial F^p}{\partial c} (0,c) }{ F^q(0, c) } \]

Taking \(|G(c)| = 1\) and assuming \(h\) is small enough to ignore the \(O(h^2)\) tail of the series gives an estimate for the size \(R_a\) of the atom domain:

\[ R_a = |h| = \left|\frac{ F^q(0,c_0) }{ \frac{\partial F^p}{\partial c}(0,c0) }\right| \]

So, given a nucleus \(c\) and its period \(p\) the size estimate of its atom domain can easily be calculated:

real_t atom_domain_size_estimate(complex_t c, int_t p) { complex_t z = c; complex_t dc = 1; real_t abszq = cabs(z); for (int_t q = 2; q <= p; ++q) { dc = 2 * z * dc + 1; z = z * z + c; real_t abszp = cabs(z); if (abszp < abszq && q < p) { abszq = abszp; } } return abszq / cabs(dc); }

But, how good is this approximate estimate? I checked it graphically. In these images, I used interior and exterior distance estimation (wiggly grey boundary of the mandelbrot set), and edge detection filter on the (unrestricted) atom domains (smooth grey arcs in the exterior of the set). I calculated the nucleus and period for each of the larger domains, and drew a circle around the nucleus (labeled with its period) with radius of the size estimate. A rectangle shows the location of the next image.

They seem to match up pretty well!

]]>In a previous post I explored patterns of periods in the Mandelbrot set and promised a future post elaborating on external angles.

Here are some external rays landing on tips relative to the period 1 continent of the Mandelbrot set, with their angles labeled in turns using a finite binary expansion:

Actually, a finite binary expansion is really an infinite binary expansion, ending in an infinite string of 0s. But there's another infinite binary expansion for each number ending in infinite 0s, that ends in an infinite string of 1s: .xyz1000... = .xyz0111.... Writing down an infinite string is boring, so I write the repeated part in (parentheses).

Now we can get more abstract, replace "0" with "-" and "1" with "+":

The tuning algorithm for external angles works like this: start with a pair of periodic external angles (.(-),.(+)) whose rays land at the root of the same hyperbolic component H, and another external angle E. Now replace every 0 in E with -, and every 1 in E with +, to form the external angle F. Then F's ray lands near H in the same way that E's ray lands near the period 1 continent.

For example, the root of the period 3 island has external angles (.(011),.(100)), and the 1/2 bulb of the period 1 cardioid has external angles (.(01), (.10)). Applying the tuning algorithm, the 1/2 bulb of the period 3 cardioid has external angles (.(011 100),.(100 011)).

Now the two binary representations come in to play: for each tip, applying tuning gives two unequal angles with rays that land on the same point. Between the two angles there must be more of the Mandelbrot set, so it must be quite a hairy beast.

Some examples of hairiness:

There are islands in the hairs (because there are islands everywhere, though they get very small very quickly), and in the previous post I noted that around a period P island the periods in each successive layer of hairs seems to increase by P each time. Now I make that more precise.

Suppose we have an island I0 with angles (.(-),.(+)). Then the tip of its antenna has angles (.-(+),.+(-)). There will be more islands in the wake of the antenna. Suppose I1 is the lowest period island visible in the wake (visible means it isn't separated from the viewpoint by a ray-pair of lower period). I1 is in fact defined uniquely:

Assume there are two lowest-period visible islands with p = period(I1a) = period(I1b). So there are 4 rays of period p landing on I1a and I1b in the wake. Neighbouring rays of period p are separated by 1/(2^p - 1) so the total width of the wake must be more than 3/(2^p - 1). But: 3/(2^p - 1) is more than 1/(2^(p-1) - 1) when p > 2 so there is room for a period (p-1) island in the wake, which contradicts the assumption.

So, the width W of the wake satisfies: 1/(2^p-1) < W < 1/(2^(p-1)-1), and p > 2. The lower bound is .0...01(0...01) and the upper bound is .0...1(00...1), where the group of 3 digits surrounds the p'th digit. Because of the strict inequalities, the position of the first 1 in the digits of W must be at the p'th digit - if it was any different it would be outside the bounds.

Brief recap: given the roots of an island, find the wake of the tip, then the lowest period island has period of the first index of 1 in the digits of the width of the wake. Call this period p, and the angles of the wake (W-,W+) then the island in the hair has angles (ceil[(2^p-1)(W-)]/(2^p-1), floor[(2^p-1)(W+)]/ (2^p-1)) (Proof left as an exercise because I don't feel like typing it up: I used the fact that (2^p-1)(W-) is not an integer).

So far this is just the hair of the antenna, but there are hairs all over. Because of tuning, the width of the wakes of each successive layer of hairs on a period P island is 1/2^P times the previous layer. The lowest period in the antenna hair is p, and call the period in the next layer of hairs p'. Now algebraic manipulation shows that if the wake W is wide enough for p then the wake W' in the next level is wide enough for an island of period p+P, and a proof by contradiction shows that W' is not wide enough for an island of period p+P-1. Combining with the earlier uniqueness, the lowest period islands in the next level of hairs must have period p' = p + P.

]]>(skip to conclusions and music video link)

In this post I'll be using the west-most period **5** island as an example,
but the patterns also occur for some (most? all?) other islands. The island
has angled internal address **1 1/2 2 1/2 3 1/2 4 1/2 5**.

Getting close to the island some hairs are visible. The lowest period islands in each hair are near the tip: see the period 11 marked at the top and bottom of the following image. These periods also occur in the longer hairs (the 11 marked at left and right). Going deeper to the next layer of hairs, the lowest period in the inner hairs is 16.

The sequence **11, 16, 21, 26, ...** increases by 5 at each
level of hairs. The period of the inner atom is 5.

This pattern continues indefinitely. But what of the structure around
each of those islands? I'll use a period **131** island
in the positive cusp of the 1/3 bulb of the period 5 island as an example,
but the patterns also occur for some (most? all?) other low period islands
in the hairs.

Getting closer to the island there is an embedded Julia set. The periods in the hairs outside the embedded Julia set also increase by 5.

Within the embedded Julia set, periods also increase in steps of 5. Moving outwards towards the tips, the periods increase by 5: 131, 136, 141, 146, 151, ... Rotating clockwise about a spiral, the periods increase by 5: 136, 141, 146, ... Moving inwards along a spiral is the same as rotating 3 times (for the 3 spokes) and the periods increase by 15: 136, 151, 166, ...

Going deeper into the embedded Julia set, at the next level the periods increase by 131 relative to the outer level: 136 + 131 = 267, etc...

Going deeper into the embedded Julia set, at the next level the periods increase by 131 relative to the outer level: 267 + 131 = 398, etc...

The pattern seems to be:

- outer period P
- (example: 5)
- inner period Q at offset K and depth N (giving 2^N islands)
- Q = N * P + K
- embedded Julia set period J at depth M (giving 2^M-fold symmetry)
- J = P + M * Q

So our period 131 island has P = 5, Q = 131, K = 1, N = 26. The 2-fold embdedded Julia set has period 5 + 131 = 136; 4-fold, 5 + 2 * 131 = 267; 8-fold, 5 + 3 * 131 = 398; 16-fold, 529; ...

In a future post I'll describe some patterns in the external angles of these islands in the hairs and embedded Julia sets, which allows for the creation of non-standard (by which I mean not just a continuous zoom) Mandelbrot set videos. Here's one I prepared earlier: Hairy Julias.

]]>Here are some notes on some thoughts I've had over the past couple of years, many of them sparked by email exchanges with Robert Munafo.

- Checking if the point is exterior by testing if iteration escapes to infinity won't terminate if it isn't exterior.
- Checking if the point is interior by testing if it is inside a hyperbolic component won't terminate if it isn't interior.
- Solution? Race the tests: perform both checks in parallel and pick the first one that terminates, then kill the other (non-terminating) check.
- Problem: the race will never end when the point is exactly on the boundary.
- Luckily most exact boundary points are not representable exactly in binary floating point, and most of the ones that are probably have sufficient noise from rounding errors to tip the balance to termination as interior or exterior.
- So far the only "pathological" boundary points I've found are on the real axis or at 0±i; inserting special case hacks for these are not too problematic.

- Exterior distance estimates are relatively easy.
- Interior distance estimates are doable if you know the period.
- A method for computing interior distance estimate for a point
**c**:period = 1 loop { compute z0 such that f^p(z0,c) = z0 using Newton's method; if (Newton's method failed, for example, z escaped) { continue; } if (|d/dz f^p(z0, c)| < 1) { compute interior distance estimate using p, c, z0; if (interior distance estimate > 0) { return (p, distance); } } period++; }

- This method requires neither finding nuclei and tracing boundaries nor awkward numerical period detection and iteration (to find p and z0).
- Classify pixels as boring if (interior or exterior) distance estimate > 4 * pixel spacing.

- Recursively subdividing an image is a win.
- 1/4 of the pixels need not be recomputed compared to the parent layer.
- Spatial coherency can be used to optimize calculations.
- Boring subpixels (those with only boring neighbours all of the same period) need not be calculated either: estimate its distance estimate by the geometric mean of its neighbours' distance estimates.
- But in any case, with the racing technique new tiles can be calculated without a reference to a parent, for exploring deep zooms without having to render the whole chain back up to the root.

- Square tiles with power-of-two dimensions are good (OpenGL etc).
- Boring subtiles (consisting entirely of boring pixels) can be skipped.
- Problem: tile edges - subdividing an MxN tile gives a (2M-1)x(2N-1) tile.
- Rejected solution: depend on neighbouring tiles in the parent layer (too complicated).
- Adopted solution: use power-of-two-plus-one tiles with one pixel overlap between neighbours.
- Edge subpixels will be computed twice but this is a small cost when tiles aren't tiny.
- Raw tiles can be cropped by 1 pixel at the bottom and left sides before saving as images, to avoid having to remove overlap when displaying.

- Only calculate the upper half-plane of the set, the other half is symmetrical.
- The root tile has southwest coordinate -4+0i and a size around 8, with a pixel spacing of 2^-N for some integer N.
- The bottom row of pixels on the x-axis is filled with dummy data (period -1, distance estimate +inf).
- Successive layers get closer and closer to the x-axis, while the axis itself is never calculated.
- Also necessary to ensure 0+i has the same dummy data.

- Directory structure:
lock/ boring.txt todo/*.raw done/*.raw tile/*.{png,jpg} work/[clientID]/boring.txt work/[clientID]/todo/*.raw work/[clientID]/done/*.raw work/[clientID]/tile/*.{png,jpg}

- Initially all directories are empty apart from the root tile in todo/M.raw, boring is an empty file, lock is an empty directory. Now each renderer:
- Creates its working directories work/[clientID]/{todo,done,tile}.
- Attempts to create a file in lock, failing if it already exists, retrying after a timeout.
- Moves the oldest (by file modification date) file in todo/*.raw to its own todo/ directory.
- Removes the lock file.
- Loads its todo/*.raw, subdivides it, saves its non-boring subtiles to its done/*.raw and image versions to its tile/*.{png,jpg}, logs the boring subtiles to its boring.txt.
- Acquires the lock.
- Moves work/[clientID]/done/*.raw to todo/*.raw; moves work/[clientID]/todo/*.raw to done/*.raw; moves work/[clientID]/tile/*.{png,jpg} to tile/*.{png,jpg}; appends work/[clientID]/boring.txt to boring.txt.
- Releases the lock.
- Locking should allow multiple renderers to cooperate and not clobber the cache, even across different machines with network file systems.

- Concept: like openstreetmap.org, providing a scrollable zoomable interface.
- Initial implementation: no server-side code, use client-side scripting.
- Server index.html contains embedded javascript.
- Client-side javascript downloads the boring.txt and parses it into an associative array.
- Also parses any ?query=url¶meters=when they exist to set up the viewing parameters (x, y, r).
- Generates an HTML table with an img in each cell referencing the required url.
- Boring cells (those with any tile parent boring, parents are prefixes of the tile identifier) reference urls based on the boring period.
- Interesting cells reference urls based on the tile identifier itself.
- Server should return a blank tile image for non-existing tile urls, independent of client-side script.
- Navigating within the page (with buttons on the table frame for up/down/left/right/zoom-out, and clicking on the image for zooming in) doesn't reload the page, this is handled in the client-side script. New images may be fetched if required.
- A 'permalink' button freezes the current view state into an URL with appropriate ?query=parameters.

- Add server-side code to allow remote rendering clients to 'checkout' todo tiles and upload the results when done.
- Each rendering client is linked to a user account, users get magic beans for subdividing tiles.
- Validation of correct results might be done by comparing results from different users.
- Server keeps track of which unrendered tiles are most demanded, bumping priority in the todo queue.
- Maintain a database of user-curated "interesting things to see", with annotations / links to articles and prettier images.
- Link with a "feature database" of angled internal addresses etc.

- Multiple servers running same (or different if the API is fixed) code, allowing experimental features to be developed easily on your own machine.
- RSS feeds of new tiles, with daily / weekly batch downloads.
- Graceful handling of servers going offline in case someone wants a mirror on a laptop; also servers coming back online.

There is an algorithm to find points on the boundary of the Mandelbrot set, given a particular hyperbolic component and the desired internal angle. It involves Newton's method in two complex variables to solve

\[ F^p(z,c) = z \\ \frac{\partial}{\partial z} F^p(z,c) = b \]

where \(F^0(z,c) = z\) and \(F^{q+1}(z,c) = F^q(F(z,c)^2+c)\), \(p\) is the period of the target component, and \(b=e^{2 \pi i \theta}\) with the \(\theta\) the desired internal angle. The resulting \(c\) is the coordinates of the point on the boundary. It can also be modified to find points in the interior, simply set \(b = r e^{2 \pi i \theta}\) with \(\left|r\right| \le 1\).

After seeing this fractal Mandelbrot in an IRC channel, I wondered if it was possible to invert the algorithm, namely find \(b\) given \(c\), and use \(b\) as polar texture coordinates to place copies of an image inside the Mandelbrot fractal. The answer is yes, and in some sense it's actually easier than the other direction.

The algorithm I developed works like this:

- When \(c\) is outside the Mandelbrot set, give up now;
- For each period \(p\), starting from \(1\) and increasing:
- Find \(z_0\) such that \(F^p(z_0,c) = z_0\) using Newton's method in one complex variable;
- Find \(b\) by evaluating \(\frac{\partial}{\partial z} F^p(z,c)\) at \(z_0\);
- If \(\left|b\right| \le 1\) then return \(b\), otherwise continue with the next \(p\).

I wrote this implementation.

]]>I previously wrote about
Mandelbrot set Newton basins
in the context of finding islands, whose nuclei are periodic points. A
periodic point of a function **g** satisfies
**g ^{p} = g^{0}**, where

Newton's method tries to find a root of a function **h(x) = 0**
by iterating **x → x - h(x) / h'(x)** where **h' = dh/dx**,
namely the differential of **h** with respect to **x**.
Here the quadratic function **f** in the Mandelbrot set is
considered as a function of **c**, with **f' = df/dc**
and we want to find a preperiodic **c _{0}** satisfying

Now, **f** and **f'** can be computed by
recurrence relations:

F_{c}^{0}= 0

F'_{c}^{0}= 0

F_{c}^{n+1}= (F_{c}^{n})² + c

F'_{c}^{n+1}= 2 F_{c}^{n}F'_{c}^{n}+ 1

Applying Newton's method gives:

c → c - (F_{c}^{p+k}- F_{c}^{k}) / (F'_{c}^{p+k}- F'_{c}^{k})

But solving this isn't the whole story - it might converge to a preperiodic
point with a preperiod less than **k**, say **k'**.
(Even the target period **p** may be a multiple of the true period,
say **p'**.) The next step is to find the true preperiod of the
resulting **c _{0}**, which can be done by finding the
smallest

Enough of how (for full details read the source linked below), here are some images, each with a certain fixed period and coloured according to the true preperiod of the root converged to at each pixel.

Image *a* shows the whole Mandelbrot set with some preperiodic
basins of period 1 highlighted. You can see they surround some terminal
and branch points, but not all. Images *b* and *c* show
enlarged regions near the 1/3 and 2/5 bulbs. Image *d* starts to
get interesting - this is zoomed in near the 1/3 child of the 1/2 bulb.
Notice how only the outer filaments have basins attached. Compare with
image *e* which increases the target period to 2: here the inner
filaments have basins attached.

This leads me to conjecture that multiplicative *tuning* is at
work: the inner filaments near a child atom will have preperiodic branch
points that have a period a multiple of the parent atom's period, compared
to the corresponding preperiodic branch points at the root. This seems
to be supported by the remaining images: *f*, *g*, *h*
with periods 1, 2, 3 highlighted near the period 3 island; *i*
near a period 4 island with period 4 highlighted, and *j* near a
period 5 island with period 5 highlighted. Note the inner filaments
being highlighted when the periods match.

Newton's root finding method has a few applications when exploring the Mandelbrot set. You can use it to find the center of a component given a reasonable location estimate, or find a particular point on the boundary of a component given its center, and it is also used when tracing external rays from infinity inwards towards the boundary.

Tracing external rays is a slow process, needing many steps with many iterations of Newton's method for each step. When tracing rays to a particular component, it would be desireable to switch to Newton's method for center finding as soon as possible. A rough heuristic (ie, I haven't proved that it works everywhere) might be to trace a few rays in parallel, and check the atom domain at the ray end points, switching when all ray end points are in an atom domain of the target period using the average of the endpoints as initial estimate.

The images show fractal basins of convergence for Newton's method for a particular period, with the atom domains of the target period highlighted, overlayed with the boundary of the Mandelbrot set. It seems that the atom domain is wholy within its own Newton basin, and also significantly larger than the corresponding component.

]]>