Note: This Windows desktop application needs a display with a width of at least 1200 pixels. More is better. Less will be frustrating.
Introduction
There seems to be no real consensus where fractal articles, or Mandelbrot articles in particular, belong. I think General Programming >> Algorithms & Recipes is fair territory and there are some fine neighbors here. So being here, I think its time we consider the Bad Mandelbrot set.
Benoit Mandelbrot discovered the 'good' set now named after him in 1979. This set is approximated by an amazingly simple iterative process where, starting from Z = (0, 0i), for any given point C in the complex plane:
Z' = Z2 + C (or = C + Z2)
Once a new point Z' is found, you repeat the process by adding the original point C plus the square of the last Z found. It is an iterative process but at some point (less than infinity) you have to decide whether Z' is greater than 2 in magnitude or if you consider the point C to be in the set. Typically, if a point is in the set, you color it black.
Background
In 1991, I had a PC with a real 80387 co-processor and of course, one of the things I looked at was the Mandelbrot set. You know, the interesting product in complex math is i * i = -1. And for any real numbers n and m, n * mi = nmi (magnitude nm times imaginary i).
Complex math is a little tricky and mistakes are sometimes made. What if you decided n * mi = -nmi. It's a mistake. It's bad. You would never do this, but if you did, you would see something like this:
Now I'm not saying this is the first light of day for the Bad Mandelbrot set, but stranger timelines have happened. Since 1991 I haven't seen or read anything depicting the "Bad" Mandelbrot set. If you have a reference, let us know. UPDATE: see Jeremy Thomson's comment below.
Not Quaternions
This badness is a little like quaternions which are super complex numbers where an imaginary part j multiplied by an imaginary part i has a product -k. i, j, and k are such that:
i2 = j2 = k2 = ijk = −1
But quarternion multiplication is not commutative. j * i = -k, but i * j = k. Bad Mandelbrot math is commutative so that:
1*i = i*1 = -i
Personally, I haven't found quaternions to be all that productive in creating 3D or 4D fractals. Maybe messing with some of the rules might show something interesting, but I haven't had that much time.
Using the code
The downloadable solution can be built with Visual Studio Community 2015. Other versions are usable with maybe a little futzing. I should have placed the following in a help screen, but you are reading it here:
The title bar shows the current magnification scale. The application starts out at Scale = 1.0.
The coordinates of the main left tile's center are displayed under it. The application starts with center = (-0.6, 0.0i). You will also see the complex coordinates of the cursor when it moves over the main left tile.
Left clicking on a point in the main left tile centers that point. This is how you move around.
'z
' and Shift-'Z
' zoom out and in on the set. You can also click the 'z
'/Shift-'Z
' button.
Turning the Map
on suspends displaying the zoomed area in the main left tile to show the set at Scale = 1.0 and the location of the Mtile
s (described next). Turnning Map
off reloads any zoomed display.
Tri-part Symmetry
One of the amazing things (to me) about the Bad Mandelbrot set is its perfect tri-axis symmetry. The Good Mandelbrot set is only symmetric about the (real) X-axis. The Bad Mandelbrot set is symmetric about three rays: the ray from the origin along the negative X axis, the ray from the origin inclined from the positive X axis by 60 degrees, and the ray from the origin declined by 60 degrees.
How did the Mandelbrot gods do this? There is nothing obvious in Bad Mandelbrot math that says, "find and use the angles +/- 60 degrees."
For any center chosen that is close to, but above the negative X axis, there is a reflected point with reflected topography on the other side of the negative X axis. AND there are two points along the 60 degree inclined ray with the same and reflected topography, and the same along the declined ray.
The button labeled 'Symm. Rotate
' shows this in the six Mtile
s (or 'sub tiles') displayed to the right of the main left tile. These tiles map as indicated in the following diagram:
When you click a center in the main left tile, you are also setting the same center for tUL
. All the other sub tiles are set relative to that. If you click a center in a different quadrant, all the other sub tile centers are set relative to that point.
Clicking Symm. Rotate
to On
does the following;
- Rotates the axis from the origin to the center of the
Mtile
so it points at 12 o'clock. The Mtile
is aligned (rotated) to this. - Flip the topography of the three reflected
Mtile
s (Lower Left, Upper Center, Lower Right) so they appear right-to-left for left-to-right to better show similarity.
Are We There Yet?
In any Mandelbrot set viewer, good or bad, you will eventually get to a zoom factor where you aren't sure if what you are looking at is a miniature of the full-scale approximation (the sine qua non of fractals is self-similarity at finer detail) or a region with something more complicated. To resolve this you have to repeat the iterative process more times than currently chosen. '-
' or Shift-'+
' decrements or increments this limit. You can also click the 'dec iter limit
' / Shift-'INC ITER LIMIT
' button.
Of course the problem with increasing the limit is that it takes longer to determine black pixels from colored pixels.
As an aid, this application colors the background of the iteration limit TextBox first GoldenRod
while updating the main left tile, then Khaki
while the six Mtile
s are updated, returning to near-white when the display is completely updated.
You can always hit z
or Z
or -
or Shift-+
while updating is in progress. This application will always try to complete the display for the last action taken.
If you just want to get back to a starting position, click Restart
. Everything but the iteration limit will reset to initial conditions.
If you right-click a point in the main left tile you can watch how that point jumps through iterations to its way out of the magnitude 2 circle or dances around within the circle for all iterations.
Click the Stop
button to cancel an active dance.
Finally, jumping and dancing leaves tracks. You can click the Restore
button to regenerate the main left tile's display.
Points of Interest
In 1991, double
precision was really something. Boy, you could zoom forever (with an incredible 20MHz clock speed - oooh) and never see the last detail (can't anyway but that's not the point). Now, using C# double
s with a super-duper i7 quad core processor running at 3GHz, you really can't zoom forever without hitting a wall. I found that I ran out of mantissa room rather quickly with this application. What to do? There are free math libraries that buy you more than double
's 52-bit mantissas but I took the easy way out and chose to retrofit Microsoft's decimal
type (96-bit mantissa). It's in the language, it has way more precision than 8087, and it's terrifically ... slow (can't have everything).
As an aid, this application emboldens and colors the iteration limit Maroon whenever decimal
type is chosen by the application for accuracy in displaying the main left tile. Zooming out (-
) will revert back to double
(Normal, Black iteration limit).
What I didn't expect was how pervasive different processing for decimal
carried. I had to find a square root routine for decimal
(thank you Google and stackoverflow):
public static decimal Sqrt(decimal x, decimal epsilon = 0.0M)
{
if (x < 0) throw new OverflowException("Cannot calculate square root from a negative number");
decimal current = (decimal)Math.Sqrt((double)x), previous;
do {
if (current == 0.0M)
return current;
previous = current;
current = (previous + x / previous) / 2;
} while (Math.Abs(previous - current) > epsilon);
return current;
}
I had to duplicate tile center Point
's with a new class, DecimalPoint
(not to mention DecimalVector
):
public class DecimalPoint
{
public decimal X;
public decimal Y;
public DecimalPoint(decimal x, decimal y)
{ X = x; Y = y; }
public DecimalPoint(Point p)
{ X = (decimal)p.X; Y = (decimal)p.Y; }
public static DecimalPoint Add(DecimalPoint dp, DecimalVector dv)
{ return new DecimalPoint(dp.X + dv.X, dp.Y + dv.Y); }
public static DecimalPoint Subtract(DecimalPoint dp, DecimalVector dv)
{ return new DecimalPoint(dp.X - dv.X, dp.Y - dv.Y); }
public static DecimalVector operator -(DecimalPoint m, DecimalPoint s)
{ return new DecimalVector(m.X - s.X, m.Y = s.Y); }
public Point ToPoint()
{ return new Point((double)X, (double)Y); }
}
Later I abandoned dual precision centers for decimal
only centers. It took me a while to understand that any user interactions can be performed slowly and precisely while only the iterative process needs as-fast-as-you-can math.
Even with a fair amount of refactoring, centering the sub tiles gets a little wonky near the limits of double
precision. The problem is most apparent when the main left tile has switched to decimal
while the sub tiles are still at double
. Switching on Symm. Rotate
also shows some unwanted displacements.
When the Scale factor gets a bit above 1014, you know further zooming will take the main left tile over to decimal
. The sub tiles, covering the same extents in the complex plane, but with fewer pixels, take longer to go into decimal
territory.
If the main left tile is using decimal
but a sub tile is not, the sub tile will be outlined with White/Black while it is being updated. If both main left tile and sub tile are using decimal
, the sub tile is outlined in White/Maroon during update. Not all sub tiles go over to decimal
at the same increased Scale, even if they are not 'angled' by Symm. Rotate On
. If you look at the methods SameCoord
and CheckZoom
in the Mtile
class you would think all not-angled sub tiles should switch at the same increased Scale.
I would also like to mention creating the Clipper
project (part of the downloadable solution, not set as the Startup project). There were two reasons for Clipper
. One was to see if the DrawLineAa
method in the WriteableBitmapEx
package handled clipping. This is needed to draw sub tile boundaries on the Map
where sub tile corners can be off the main left tile's display. Clipping is also needed when dancing a right-clicked point. I am happy to report, the package handles clipping, mostly.
The other reason for inventing Clipper
was to prove who was making AccessViolationExceptions
when I turned Map
on right after the initial starting display. I am sad to report the bug is in version 1.50 of the WriteableBitmapEx
package. This bug can be avoided by drawing anti-aliased lines with a strokeThickness
of 1 (vs. > 1). This bug is avoided in the Startup project by calling WriteableBitmapEx1_50Bug
. I have submitted an issue for this. You can cause this condition in the Clipper
program by setting it as the Startup project, starting it and clicking 'Next
' until a line is displayed above, beneath or inside (just not to the sides of) the inner box, and entering 384
into the 2nd and 4th TextBoxes from the left at the bottom of the window.
Bad Dancing in decimal
One problem I haven't solved is generating OverflowExceptions
in the method itemizeDecimalPoints
. This is used to find DrawLineAa
coordinates of two Z' points in the iteration process. The math works fine in the double
-based itemizePoints
. I know the problem is not the use of the + .5M
round-to-nearest adjustments. In decimal
space I just return the two Z' points if OverflowException
is thrown. Little blips are drawn on the main left tile, but I'm not sure their locations are valid. I hope to solve this in an update. But for now, I didn't want to hold up article submission. Maybe I can use the 'left as an exercise' ploy.
Some Thoughts
I hate it when an article says 'left as an exercise' so here goes: Is a single sign mistake the whole difference between Bad and Good sets?
I'll say mostly. Try changing methods DecimalTravel
and Travel
to generate goodness. Do we see the familiar Good Mandelbrot set?
Another exercise. Good Julia sets based on the Good Mandelbrot set are pretty. They are point symmetric unless C is some value r + 0i. Then they are symmetric at the origin and across both the real and imaginary axes. Do Bad Julia sets exist? What do Bad Julia sets look like? Is there anything different about their symmetry?
Also, have we noticed something strange about the Bad Mandelbrot set? When you zoom in a bit you see something like:
Perfectly expected. Self-similarity shows the Badness continues down the rabbit hole. But what of this region:
Zoom in a bit and you see:
Basically the Mandelbrot gods are so good, they turn areas of the Bad set into the Good set. No? Come on. That's a bit surprising. Now for the next exercise. Try doing this by thinking it through without exploring deeper. In an area of the Bad Mandelbrot set that has turned good, should you see any miniature Bad knots when you zoom in?
BONUS exercise. Imagine there is a line of demarcation, a curve, between the Bad areas and the Good areas. We know the Good Mandelbrot set is connected.
I suspect the Bad Mandelbrot set is also connected. Prove which of the following is true about the area outside the Bad Mandelbrot set:
Bad regions and Good regions are connected. There is only one Bad region and one Good region.
There is more than one Bad region or Good region.
History
Submitted December, 2015