It's been a little bit tricky deciding how I was to approach this.
"Begin at the beginning," they say, "and go on till you come to the end.
Then stop." Which is all very well in most cases, but in this one -
well, it had two beginnings and the end is nowhere in sight. I shall
start with a salving of my conscience.
Not too long ago I argued somewhat manically that, just as the classical
Mandelbrot set serves as an index to the classical Julia sets, so too it
could serve as an index into an uncountable infinity of other sets.
And just as the two-dimensional Julias could be thought of as being
stacked up in two dimensions to form a four-dimensional Julibrot (with
the Mandelbrot set itself forming the binding), so too could these other
sets be stacked up in however many dimensions might be deemed necessary
- in which the binding itself can be pictured as being made up of stack
of sets which has the original Mandelbrot set as binding.
I gave some examples of _extensions_ to the Mandelbrot set, the
quaternions and hypercomplex numbers, some other four-dimensional
numbers I'd made up on the spot, and some further extensions to the
quaternions (specifically, octonions, decanions, and pentadecanions).
But to be honest, none of these were actually presented as being
extensions of the Julibrot set in any way - the four-dimensional
quaternion Julia sets are not the four-dimensional Julibrot. Of course,
the quaternion Julias themselves have a quaternion Mandelbrot, and the
two combine to produce an eight-dimensional quaternion Julibrot - in the
heart of which the classical Mandelbrot is still to be found. But really
there was more handwaving going on than I was really comfortable with.
So I decided to actually construct a beast that (a) gave the classical
Mandelbrot set an explicit role as an index into the associated Julias,
and (b) strongly suggested how other different beasts could be
constructed in the same way - makng it obvious that at least a countable
infinity of others could be constructed.
Where the classical Julibrot has four dimensions - two to index the Julia
sets' parameters (these being the two the Mandelbrot set lives in) and two to
carry the Julias themselves - the "Quarterblend" (to use the name I just made
up for it) Julibrot has ten. The Julia sets are still two-dimensional, but it
takes four complex parameters to specify them - so the corresponding
Mandelbrot set lives in eight dimensions. The thing is, the eight-dimensional
Mandelbrot can itself be looked upon as a Julibrot - with two-dimensional
Julias being indexed by a six-dimensional Mandelbrot. This reduction can
continue, so that six dimensions split into two and four, with the four-
dimensional index set resulting being none other than our familiar classical
Julibrot, with two-dimensional Julias being indexed by a two-dimensional
Mandelbrot.
In a sense this sort of thing can happen anyway when you have a load of
independent parameters: with n independent parameters, if you fix all but
m you specify an m-dimensional Julia, the set of which is indexed by an
(n-m)-dimensional Mandelbrot; and hence an n-dimensional Julibrot. But I
wanted to show one where the classical Mandelbrot set could be easily found.
As I said, the Quarterblend Julibrot is ten-dimensional. Since the Julia sets
I'm drawing are two-dimensional, that means I have to fix eight dimensions.
The obvious way to do that is to have four complex parameters. Without any
further ado, here's the formula for Quarterblend Julias.
quarterblend_pJJJJ{
z=pixel
if(imag(pixel)>0)
if(real(pixel)<0)
c=p1
else
c=p2
endif
else
if(real(pixel)>0)
c=p3
else
c=p4
endif
endif
:
z=sqr(z)+c
|z|<=4}
It's simple enough that it's operation should be apparent even without playing
with the thing.
Now we want to index this. In the same way that Julia sets get indexed by the
Mandelbrot:
Julia {
z = pixel, c = p1:
z = sqr(z) + c, |z| <= 4
}
Mandelbrot {
z = pixel, c = pixel:
z = sqr(z) + c, |z| <= 4
}
The only difference (apart from the name) is that where in the Julia the
parameter is specified and fixed by the user, in the Mandelbrot it's become
"pixel": each distinct point in the Mandelbrot corresponds to a distinct Julia
set. The formula for the Mandelbrot set isn't entirely truthful: to save one
iteration per pixel, the first iteration has actually be done by hand. It
should really read:
Mandelbrot {
z = 0, c = pixel:
z = sqr(z) + c, |z| <= 4
}
Where 0 is a critical point of the quadratic map z^2+c. Note that after one
iteration through the formula, z becomes equal to pixel and the situation
matches the earlier version of the Mandelbrot formula.
I mention this bit about truth because I'd like you to compare the
initialisers of the Julia and "real" Mandelbrot formulae:
z = pixel, c = p1:
z = 0, c = pixel:
Each pixel P of the Mandelbrot set is initialised to z=0, c=P. And the point 0
of the Julia set with parameter c=P is initialised to z=0, c=P.
In other words, these two points (one in the Mandelbrot, one in the Julia)
have the same initial conditions, and follow identical dynamical rules. Each
point of the Mandelbrot set is the zero point of a Julia set, and vice versa.
This is what is meant by the Mandelbrot set "indexing" the Julia set, and is
the basis of the Julibrot; with two-dimensional Julia sets lying at right
angles to the two-dimensional Mandelbrot set, all intersecting it at single
points; the whole shebang yielding the four-dimensional Julibrot.
So now we can index the quarterblend_pJJJJ formula. There are a variety of
ways to do this, some more extreme than others. You might want to build an
index in the p1 plane (the plane in which p1 takes all possible values). This
would give
quarterblend_pMJJJ{
z=pixel
if(imag(pixel)>0)
if(real(pixel)<0)
c=pixel
else
c=p2
endif
else
if(real(pixel)>0)
c=p3
else
c=p4
endif
endif
:
z=sqr(z)+c
|z|<=4}
or instead index p1 and p3 simultaneously:
quarterblend_pMJMJ{
z=pixel
if(imag(pixel)>0)
if(real(pixel)<0)
c=pixel
else
c=p2
endif
else
if(real(pixel)>0)
c=pixel
else
c=p4
endif
endif
:
z=sqr(z)+c
|z|<=4}
Both of these could be called "Mandelbrot" sets, in which a quantity is
allowed to vary that could instead be fixed at a particular value to produce
corresponding Julia sets. But equally, they could still perhaps be called
"Julia" sets, because they do still contain arbitrary parameters. Let's index
all of those parameters now:
quarterblend_pMMMM{
z=pixel
if(imag(pixel)>0)
if(real(pixel)<0)
c=pixel
else
c=pixel
endif
else
if(real(pixel)>0)
c=pixel
else
c=pixel
endif
endif
:
z=sqr(z)+c
|z|<=4}
And do some obvious simplifications of the if-else-endif logic:
quarterblend_pMMMM{
z=pixel
c=pixel
:
z=sqr(z)+c
|z|<=4}
Now that looks familiar.
The Quarterblend Julibrot is a ten-dimensional structure. An eight-dimensional
stack of two-dimensional Julias. The stack is itself a six-dimensional stack
of two-dimensional Julias. That stack in turn is a four-dimensional stack of
two-dimensional Julias. And _that_ stack is our familiar Julibrot - a two-
dimensional stack of two-dimensional Julias. All of the Julias in that last
step intersecting with a single plane at their critical points to produce a
two-dimensional Mandelbrot - index page for the two-dimensional stack. So yes,
it does look patchy, being cut into regions like that, but all the bits all
still hang together in one single consistent ten-dimensional shape.
And there you have it. Just one of what is (what should be) clearly an (at
least countable) infinity of different formulae that all have the classical
Mandelbrot set serving to index its Julia sets; just altering the placement of
the regime boundaries (real(pixel)<0, etc.), or adding more would be
sufficient.
Actually, with a little thought it's clear that the different formulae are
uncountable in number; the boundaries between the different regions could
placed in uncountably many different ways (just nudging one ever so slightly
would be enough).
Now I can start on the other beginning.
I was mulling the difference between the Mandelbrot set and Julia sets, and
one of the distinctions I saw concerned memory. Now, I don't say that this is
any sort of defining characteristic between them, but it is a difference
between them, that can be applied elsewhere.
Consider the idea of a dynamical system. We have a space, S, for the sake of
argument, and a so-called "vector field", that attaches, to every point of S,
a wee arrow which is taken to mean "go this way next". We insert a particle
into S at a particular point and then watch what happens, as it moves from
point to point, following the arrows. For escape-time fractals such as those
rendered by Fractint's formula type, we do this for virtually every point
visible in the space, colouring that point according to what happens to a
particle starting from that point "in the end" (where "in the end" is defined
in terms of bailout condition and maximum iterations). Other Fractint types
offer other views of dynamical systems.
But this can't really be the entire story. The Mandelbrot set itself looks
like its breaking the rule.
Mandelbrot {
z = 0, c = pixel:
z = sqr(z) + c, |z| <= 4
}
Let's say you've come across a freeze-frame of a Mandelbrot set in progress.
There is a particle sitting at a particular point. The question is: where is
it going to go next? Where is the little arrow pointing? The answers are: You
can't tell. The arrow isn't defined.
It's not? No, because that particle could have started out from a number of
different points. Where it goes next depends on c, which is defined to be
the point from where the particle originally started from. If you don't know
that, you don't know c, and you can't work out sqr(z)+c. This is what I meant
by "memory" - the particle remembers where it came from, and this memory
affects where it will go, in a fashion not catered for by a simple dynamical
system.
Julia sets are simple dynamical systems in this case. If we were in the
situation of trying to predict where a particle will go next, we will be able
to. It still obeys z=sqr(z)+c, but this time we know what c is - it's the
Julia set parameter. The particle itself has no need to remember it, because
it's already built into the little arrows we're again free to place at every
point.
In a sense, every single point of the Mandelbrot set represents a different
dynamical system - one for each value of c. But this is just another way of
saying that the Mandelbrot set indexes the Julia sets - by sampling all of the
different dynamical systems and providing a summary.
Now the two plot lines I've been developing here can merge.
The Kleinian Circle formulas don't have such a memory: The only per-pixel
initialisation is the last "z=pixel" for specifying where the particle was to
start. After that, the rest was predetermined - you could set up the vector
field and all its wee arrows in advance, and every particle arriving at a
particular point would all behave the same way, all following the same wee
arrow telling them where to go next. (This was why my "precalc" hack could
work.)
In contrast, my Quarterblend fractal (even the JJJJ julias) does have a
memory. Each particle remembers which region (upper/lower left/right) it
started out in. Without knowing that about a particle, you wouldn't be able to
predict where it was going next.
Something had to be done. At least, it suggested something that could be done.
So I lobotomised the particles that were supposed to be following the
Quarterblend dynamics. No longer could they remember which region they started
from; instead they were to just use whatever rule applied in whatever
region they
happend to be in at the time.
The example Quarterblend slices I gave above became:
quarterblend_iJJJJ{
z=pixel:
if(imag(z)>0)
if(real(z)<0)
c=p1
else
c=p2
endif
else
if(real(z)>0)
c=p3
else
c=p4
endif
endif
z=sqr(z)+c
|z|<=4}
quarterblend_iMJJJ{
z=pixel:
if(imag(z)>0)
if(real(z)<0)
c=pixel
else
c=p2
endif
else
if(real(z)>0)
c=p3
else
c=p4
endif
endif
z=sqr(z)+c
|z|<=4}
quarterblend_iMJMJ{
z=pixel:
if(imag(z)>0)
if(real(z)<0)
c=pixel
else
c=p2
endif
else
if(real(z)>0)
c=pixel
else
c=p4
endif
endif
z=sqr(z)+c
|z|<=4}
and
quarterblend_iMMMM{
z=pixel:
c=pixel
z=sqr(z)+c
|z|<=4}
Again, that last one looks familiar. (And now you know what that extra 'p' was
for!)
All the talk about the quarterblend_p???? fractal applies to this one as well,
so the extensions suggested there can all be carried in bulk to this one as
well. This one has the advantage that its ten-dimensional space can be filled
up with the wee arrows of a vector field in advance and looked at as a pure
dynamical system, without giving mere particles anything so pretentious as a
memory.
Plus the pictures look a bit better.
Morgan L. Owens
"Pass the dried frog pills."