Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

The Bad Mandelbrot Set

4.89/5 (35 votes)
10 Dec 2015CPOL11 min read 39.6K   629  
To barrow an idiom, even bad Mandelbrot is good

Sample Image - maximum
        width is 600 pixels

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:

Image 2

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 Mtiles (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 Mtiles (or 'sub tiles') displayed to the right of the main left tile. These tiles map as indicated in the following diagram:

Image 3

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;

  1. 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.
  2. Flip the topography of the three reflected Mtiles (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 Mtiles 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# doubles 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):

// From stackoverflow with one change
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; // could just break
    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 // bit of a retrofit here.  96 vs. 52 mantissa bits.  Use faster double unless decimal necessary
{
  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) // p-p returns v
  { 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:

Image 4

Perfectly expected. Self-similarity shows the Badness continues down the rabbit hole. But what of this region:

Image 5

Zoom in a bit and you see:

Image 6

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)