"The journey of a thousand miles begins with a single step."
- An Ancient Chinese proverb.
Fractal and Chaos mathematics relies heavily on numerical data and images created using computers. In this chapter we will give a behind the scenes look at some of the algorithms used in generating some of these programs such as FractaSketch™ and MandelMovie™, as well as demonstrate ways you can create your own programs using languages: C, Basic, PostScript™ and Mathematica™.
7.1
Drawing self-similar fractals: Internal algorithms and data structures
of FractaSketch™ |
Figure 7.1 A flower hedge made from the recursion of FractaSketch™.
7.1.1 Introduction
A lot of effort was done to make FractaSketch fast and useful. This section describes some of the internal algorithms and ideas behind the implementation of FractaSketch on the Macintosh. These are illustrated with simple mathematical formulas and code fragments in C and 68000 assembly code. I assume a smattering of knowledge of these languages, of the Macintosh Toolbox, and of high school mathematics. The program was developed in Think C (formerly Lightspeed C).
This chapter is divided into six parts: In the first part I explain the basic algorithm and its data structures. In the second part I relate this to Lindenmayer systems, a method for describing plant structures that has a lot in common with the way FractaSketch works. In the third part I explain various techniques for drawing faster and more accurately. In the fourth part I describe a novel programming technique that solves a small problem in the implementation. This technique is not generally presented in programming books, but it is powerful and easy to use so I include it here. In the fifth part, I explain how to calculate the dimension of a self-similar fractal, both exactly (by formula) and approximately (by counting pixels). In the sixth part I give a detailed description of the file formats that FractaSketch writes and can read.
7.1.2 The basic algorithm
The fractals drawn by FractaSketch are all self-similar. That is, a small part of the fractal, if magnified, looks exactly like the original fractal.
To draw a self-similar fractal, the main drawing routine is recursive, that is, it calls itself. This is a very natural way to draw self-similar fractals: since the program calls itself, the smaller parts of the fractal will of course be identical in form to the original fractal.
7.2.1 Turtlegraphics
The drawing routine works in a two-dimensional plane. It draws using a well-known technique called "turtlegraphics". It is as if there were a "turtle" on the plane, carrying a drawing pen, that listens to the following commands:
Forward(n) Go forward n pixels.
Back(n) Go backward n pixels.
Left(d) Turn left d degrees.
Right(d) Turn right d degrees.
Penup() Lift the pen up (stop drawing).
Pendown() Put the pen down (start drawing).
These commands do exactly what you expect them to do. They are very powerful: any self-similar fractal made of line segments can be drawn easily as a sequence of turtlegraphics commands. The program keeps track of the turtle's position, velocity, and direction with the following structure:
typedef
struct {
fixed x, y;
/* Position */
fixed vx, vy; /* Velocity */
angle direc;
/* Direction */
}
TURTLE_STATE;
This structure is the turtle state. The types fixed and angle are defined later on.
With turtlegraphics commands, a simple recursive routine to draw the Koch snowflake looks like this in C:
draw_snowflake(level,
length)
int
level; /* Level of complexity of the fractal */
float
length; /* Length of the fractal */
{
if (level<=0) {
Forward(length);
} else {
draw_snowflake(level-1, length/3);
Left(60.0);
draw_snowflake(level-1, length/3);
Right(120.0);
draw_snowflake(level-1, length/3);
Left(60.0);
draw_snowflake(level-1, length/3);
}
}
This uses the turtlegraphics commands Forward, Left, and Right. The draw_snowflake routine calls itself four times. It does not go into an infinite loop because the level variable is decremented: each recursive call draws a simpler fractal, until the level reaches zero, which draws a single line segment.
It would be tedious to have to write a new program each time you want to draw a different fractal. This is where FractaSketch helps you. It allows you to define a simple template (the "seed" of a fractal; see the FractaSketch user manual) graphically and to draw it in many ways, without writing a single line of code. In fact, the program is usable even if the keyboard is broken. [1] The template is automatically interpreted as a sequence of turtlegraphics commands. How this is done is explained in the next section.
2.2 The fractal state
All the information about a fractal is kept in a data structure called the "fractal state". The main drawing routine, DrawFractal, reads the information in the fractal state to draw a particular fractal. When a fractal is saved in a file, what is actually saved is the fractal state. If you have more than one fractal on the screen (each in its own window), the program keeps track of all of them by giving each its own fractal state. It switches state to draw another template. The fractal state data structure is defined like this:
#define
MAXPTS 100 /* Maximum number of points
in template */
#define
DIMSTR 10 /* Length of dimension string
*/
#define MAXNAME 40
/* Length of file name */
typedef
struct {
/* Information
per template */
int numpt;
/* Number of points in template
*/
angle
ddraworg; /* Origin direction
*/
double
xdraworg, ydraworg; /* Origin point */
double
drawscale; /* Drawing scale */
int drawmode;
/* Drawing
pen mode */
double
drawsize; /* Drawing pen size */
int propflag;
/* Proportional/constant flag */
int lastlevel;
/* Level
of last drawing */
char dimstr[DIMSTR];
/* Dimension
of fractal */
char filename[MAXNAME];
/* Current file name for open and save */
/* Information
per segment of a template: */
fixpoint fplst[MAXPTS]; /*
Coordinates of point, latched on grid */
Point plst[MAXPTS]; /* Coordinates of point, free */
angle absalst[MAXPTS]; /* Absolute Angle list */
angle relalst[MAXPTS]; /* Relative Angle list:
r[i]=a[i]-a[i-1] */
fixed slst[MAXPTS]; /* Segment length */
int filst[MAXPTS]; /* Index to other template */
int backflags[MAXPTS]; /* Forward/backward drawing
flag */
int botflags[MAXPTS]; /* Yes/no recursive drawing
flag */
int invflags[MAXPTS]; /* Normal/invert drawing flag
*/
int leftflags[MAXPTS]; /* Left/right drawing flag */
int propflags[MAXPTS]; /* Proportional/constant
thickness flag */
/* Other information: */
/* ... Window information ... */
/* ... Fractal editing information ... */
The global constant MAXPTS defines the maximum number of points in a template, and hence also the maximum number of segments (= points - 1). The fixpoint type is a point whose coordinates are fixed point numbers. The angle type gives the angle in units of 1/48th of a degree. Both of these types are explained in more detail later on.
The template is made up of line segments. Each of these segments can be given ten possible meanings: four orientations, four orientations with inverted drawing (invert everything under the pen instead of drawing black), solid black ("solid" means no recursive drawing), and solid white (an invisible line segment, used to make disjoint templates). These orientations correspond to particular values of the flags arrays in the fractal state. When creating the template, the orientations are shown on the screen by drawing the segments with an arrowhead and a shading.
3 The relationship with Lindenmayer
systems
It is possible to increase the variety of fractals that can be drawn beyond that of FractaSketch by adding new parameters to the ones that already exist. For readers wishing to explore this, I highly recommend the book The Algorithmic Beauty of Plants by Lindenmayer and Prusinkiewicz (Springer-Verlag). This book is clearly written and profusely illustrated, it does not shirk from defining intricate grammar formalisms, the Lindenmayer systems or L-systems.
L-systems are a mathematical way of describing plant development. An L-system describes a plant as a string, or sequence of characters, in a given language. Each character has a particular meaning, for example, a stem with a particular length, a branching point from which the plant grows in more than one direction, a change of the growth direction of the plant, a change of the character of plant growth, e.g., from stems to buds and flowers, and so forth.
Not all character strings correspond to plants. An L-system is used to generate valid strings from a set of rewriting rules and an initial string. For example, the following L-system represents the Koch snowflake:
Initial string: F
Rewrite rule: F -> F+F-F+F
The character "F" corresponds to "Move forward",
"+" to "Turn left
by
degrees", and "-"
to "Turn right by
degrees". Successive generations are:
F
F+F-F+F
F+F-F+F+F+F-F+F-F+F-F+F+F+F-F+F
F+F-F+F+F+F-F+F-F+F-F+F+F+F-F+F+F+F-F+F+F+F-F+F-F+F-F+F+F+F-F+F-F+F-F+F+F+F-F+F-F+F-F+F+F+F-F+F+F+F-F+F+F+F-F+F-F+F-F+F+F+F-F+F
...
If these strings are interpreted as graphic commands, they will draw the Koch snowflake. A grammar rule in an L-system corresponds to a template in FractaSketch. This example barely scratches the surface of the possibilities that have been explored with L-systems.
The power of FractaSketch is that the manipulation of grammar rules is done graphically and interactively. The question of how to describe graphically the more powerful formalism of L-systems is still unanswered. An answer to this question would significantly increase the power of FractaSketch while keeping it easy to understand and simple to use.
4 Techniques to increase speed
and accuracy
This section describes some of the techniques used to increase the performance of FractaSketch. First, I explain the representation of points and why it is efficient. Then, I explain the techniques used to make turtlegraphics go faster. Finally, I describe the memory management scheme and the trade-off between memory usage and speed of window refreshing.
4.1 How points are represented
Points in the plane are represented
as two fixed point numbers of 32-bits each. Each fixed point number has
a 16-bit integer part and a 16-bit fractional part. This means that each
pixel on the screen is divided up into
parts, so the resolution is
, or about
of a pixel. Because of high drawing
accuracy, high zoom, and fast arithmetic, this is a good trade-off for a
Macintosh based on a 68000 processor. As machines become faster and faster,
64-bit floating point numbers might become a reasonable alternative.
Points are always represented internally with this accuracy. They are rounded to the nearest integer only when they are drawn. This guarantees a high quality drawing. All "fringe effects" are due to the round-off inherent in pixel-based display (beginning and ending points must be on a pixel), not to the internal representation of pixels. It is possible to use the "clipping" facility of the Toolbox to reduce this error. The idea is to draw a very long line and to clip the line to a small box that corresponds exactly to the part that has to be displayed. The only problem with this technique is that it is very slow.
The 16-bit integer part means
that points range from
to
, or
to
.
[2]
The size of a drawing is limited by the
screen size, which is typically something like 512 times 342 (for a Macintosh
Classic internal screen) and can range up to 1280 times 1024 or even bigger.
This means that the maximum zoom-in factor will be around 32 to 64.
4.2 Fast turtlegraphics
To do turtlegraphics well,
one has to do the Forward
and the Left
operations as fast as possible. The Forward
operation involves the following calculation:
xnew
= xold + (vx * n);
ynew
= yold + (vy * n);
where (vx,vy) is the direction vector, that is, we have:
vx
= cos(direc);
vy
= sin(direc);
where direc is the direction angle. The Left(d) operation involves the following calculation:
direc
= direc + d;
vx
= cos(direc);
vy
= sin(direc);
In other words, to do Forward well, we need to be able to add and multiply fast, and to do Left well, we need to be able to add and calculate sines and cosines fast. Adding two fixed point numbers quickly is easy: standard integer addition will add them in a single instruction. The following two sections discuss multiplication and trigonometry, which are harder to do quickly.
4.2.1 How to go forward quickly
To draw a self-similar fractal with turtlegraphics, one needs to do a lot of multiplications. The Macintosh Toolbox provides a fixed point multiplication routine built in, called FixMul. From measurements, I found that the Toolbox calling overhead is quite high, which noticeably slows down the raw multiplication time. Therefore I wrote a version of FixMul, called FastFixMul, in 68000 assembly language. The Think C compiler accepts inline assembly language instructions with the asm statement, and it allows symbolic labels and C variables in the assembly code. Here is the routine:
#define
answer D0
#define
sign D1
#define
holdf1 A0
fixed
FastFixMul(f1, f2)
register
fixed f1, f2;
{
asm {
clr sign
/* Get f1 and f2; Set sign flag */
tst.l f1
bpl.s @posf1
neg.l f1
not sign
posf1: tst.l f2
bpl.s @posf2
neg.l f2
not sign
posf2: move.l f1,
answer /* Multiply low parts
(lf1*lf2) */
mulu f2,
answer
addi.l #32768,
answer
clr.w answer
swap answer
move.l f1,
holdf1 /* Multiply intermediate
(hf1*lf2) */
swap f1
tst.w f1
beq.s @hf1z1
mulu f2,f1
add.l f1,
answer
hf1z1: swap f2
/* Multiply intermediate
(lf1*hf2) */
tst.w f2
beq.s @hf2z
move.l holdf1,
f1
mulu f2,
f1
add.l f1,
answer
move.l holdf1,
f1 /* Multiply high parts
(hf1*hf2) */
swap f1
tst.w f1
beq.s @hf1z2
mulu f1,
f2
swap f2
clr.w f2
add.l f2,
answer
hf2z:
hf1z2: tst sign
/* Adjust sign of answer
*/
bpl.s @posans
neg.l answer
posans:
}
}
Fixed point numbers are 32-bit, and therefore fit nicely into the 68000's registers. This routine has two fixed point arguments, f1 and f2. It assumes that the function result (called answer) is stored in register D0 and that registers D1 and A0 are available as temporaries.
With this routine as the heart of the Forward operation, the main overhead is the line drawing time, that is, the time taken by the Toolbox routine LineTo. I did not replace this routine because it would be too complicated: LineTo automatically does clipping, takes the drawing mode and pensize into account, and properly interfaces with Quickdraw. The only way to make it faster would have been to write a routine to draw directly into screen memory, and this would have made the program non-portable.
4.2.2 How to turn quickly
To do the Left operation quickly, one has to be able to calculate sines and cosines quickly. I solved this problem by storing sines and cosines in a table, and doing simple table lookup when I needed their values. The table is declared as follows:
typedef
long angle; /* Angles are long integers */
#define
circ14 ((angle) 4320) /* 1/4-circle (90 degrees) */
fixed
sintab[circ14+1]; /* Table of sines */
It is enough to store the sines for a quarter-circle, since the other sines and the cosines can be derived easily from simple trigonometry:
sin(180
- x) = sin(x); /* Angles from 90
to 180 degrees */
sin(180
+ x) = - sin(x); /* Angles from 180 to 360 degrees */
sin(360
+ x) = sin(x); /* Angles more than
360 degrees */
cos(x)
= sin(90 - x); /* Angles from
0 to 90 degrees */
cos(180
- x) = - cos(x); /* Angles from 90 to 180 degrees */
cos(180
+ x) = - cos(x); /* Angles from 180 to 360 degrees */
cos(360
+ x) = cos(x); /* Angles more than
360 degrees */
Angles are represented as integers
of type angle, whose size is chosen
so that 4320 is
degrees. This means that the unit
is
degree. Why did I chose this strange
value? First, the unit had to be as small as possible, so that the roundoff
error would be small. Second, the common angles of
degrees (square) and
degrees (triangle) had to be exactly
representable. Third, the table of sines had to be as big as possible, given
the memory limitations of Think C. It turns out that the compiler only allowed
32 kilobytes of global data, which limits the table size to 8192 entries
(this is (32768 bytes) / (4 bytes per fixed point value)). Because the program
needs space for other global data, the biggest size that fits with the second
constraint is 4320.
During program startup, the
table sintab has to be initialized.
On a Macintosh Plus this takes several seconds because the Toolbox sine
routine is rather slow (it works only in Extended precision, an 80-bit floating
point type). To shorten this time, I replaced the calls to the Toolbox sine
routine by the following sine routine which calculates sines from
to
degrees much more quickly:
#define
circ11 ((angle) 17280) /* Full circle (360 degrees) */
static
double c1=411774.832/circ11;
static
double c2=2708752.4/circ11/circ11/circ11;
static
double c3=5230709.3/circ11/circ11/circ11/circ11/circ11;
/*
Fast sine for angles from 0 to circ18 (45 degrees) */
/*
Returns 65536.0*sin(a) with maximum error 0.1 */
double
FastSine(a)
angle
a;
{
double x =
(double)a;
double xx = x*x;
return (x*(c1-xx*(c2-xx*c3)));
}
The values of the constants
c1, c2,
and c3 come from a reference book
on functional approximation. Sines from
to
degrees are obtained with the formula:
sin(90
- x) = cos(x);
=
sqrt(1 - sin^2(x));
Square roots are very fast on the Mac: they execute in about the same time as a multiplication.
4.2.3 How to avoid cumulative
roundoff error during drawing
A complicated fractal consists
of a large number of short line segments. These line segments are all drawn
with the Forward or Back commands, which each updates the turtle's position.
Since the position is represented only approximately, there will be a small
error, the roundoff error, in the new position. This error has two causes: first, the position is only accurate
to about
pixel, and second, the angle is
only accurate to
degree. When the error reaches one
pixel it will become visible on the screen. If the fractal is complex, containing
thousands of line segments, it is likely that the error will become visible.
If the screen is zoomed in, the error becomes larger. If the fractal template contains s segments
and is drawn to level
, then the total number of segments is
. The cumulative roundoff error is therefore proportional to
). For example, a Koch snowflake curve has four segments. Drawing it at
level five results in
line segments.
There is a simple technique
that reduces this roundoff from s
to s times n. For all practical purposes, this removes the error completely.
For the Koch snowflake example, the error is reduced from
to
times
=
, a reduction factor of about
. For more complicated fractals, the reduction is even higher.
The technique is implemented in the drawing routine as follows: save the turtle state before drawing, then draw the fractal, then restore the state and move to the final position in a single Forward operation. This replaces the cumulative roundoff error of the whole drawing (which may have thousands of Forward operations) by the error of a single Forward operation. To implement this, two operations are added to the turtlegraphics package:
Savestate(&s) Copy the turtle state to s.
Restorestate(s)
Copy s to the turtle state.
In these two operations, s is a structure of type TURTLE_STATE and the ampersand "&" is a C operator that returns a reference (a pointer) to its argument. With these two operations, the technique is easy to program. The draw_snowflake routine given earlier becomes:
draw_snowflake(level,
length)
int
level; /* Level of complexity of the fractal */
float
length; /* Length of the fractal */
{
if (level<=0) {
Forward(length);
} else {
TURTLE_STATE
s;
Savestate(&s);
draw_snowflake(level-1, length/3);
Left(60.0);
draw_snowflake(level-1, length/3);
Right(120.0);
draw_snowflake(level-1, length/3);
Left(60.0);
draw_snowflake(level-1, length/3);
Restorestate(s);
Penup();
Forward(length);
Pendown();
}
}
The additional operations needed are shown in bold face type. The local variable s contains the turtle state. This variable is only allocated when level>0.
4.3 Memory management
When a window containing a fractal is partially covered and then uncovered, the uncovered part has to be redrawn. This process is called "refreshing the window". FractaSketch has two methods to refresh windows: either redraw the fractal in the window, or copy a saved image to the window. The first method uses almost no memory, but is very slow for complicated fractals. The second method uses lots of memory (for large windows this can be several hundred kilobytes or more), but is almost instantaneous. As long as enough memory is available, FractaSketch uses the second method. The decision which method to use is determined each time a fractal is drawn during execution. This means that closing unused windows during execution will allow the remaining windows to be refreshed quicker.
If you intend to do development of complex fractals on a big screen, performance will be greater if you increase the memory allocated to FractaSketch upon startup. This can be done by selecting the FractaSketch program from the Finder, doing GetInfo, and editing the memory partition size.
The maximum number of fractal windows that can be open at any one time is determined at launch time by the amount of available memory. A partition of 300 kilobytes allows about 10 windows to be open. One extra window can be opened for every 20 kilobytes added to the partition size. The smallest partition that works is about 150 kilobytes, which will allow a single fractal window to be opened.
5 The technique of "faking it"
If you hold down the mouse button and move the mouse, you will drag an outline of the fractal across the window. This seems simple enough, but to get it to work right requires a technique that is considered "state of the art" in programming language implementation. It is called abstract interpretation or symbolic execution and is usually associated with heavy mathematical machinery. Do not let this scare you--the idea is simple and easy to use. In fact, you have probably already used it without being aware of it.
The idea is this: You have a complicated program (like a fractal drawing program with lots and lots of ways to customize it). You would like to find out something about what you get when executing it, but without actually executing it. No sweat! Write a simple program that mimics the part of the big program that you are interested in. Then just execute the simple program. [3] There is only one rule to follow: the simple program must exactly mimic the big program.
How is this used in dragging an outline across the screen? When you drag the mouse, the program will draw over and over again a simple version of the fractal, so that you see what you are dragging. There is a problem, though: the program would like to draw as much as possible of the real fractal, but not too much or else redraw time would be too slow, and dragging would become a drag. The program has to draw the fractal at the highest drawing level that gives less than some maximum number of line segments (say, 50). This guarantees that as much as possible of the fractal will be redrawn while keeping redrawing fast.
To find out what level to draw the outline, there is a simple routine that "goes through the motions" of the full drawing routine DrawFractal, but only counts visible line segments. This routine is a simplified version of DrawFractal: all the parts that are needed to draw segments are removed, and only the parts needed to find out if a segment is visible or invisible are kept. Here it is:
/*
Count the number of visible segments with a given drawing level. */
/*
This routine does symbolic execution of the routine DrawFractal. */
int
CountSegments(level, visflag)
int
level;
register
int visflag;
{
register int vis;
register int i;
if (level==0) return visflag;
vis=0;
level-;
if (level>0)
for(i=1;i<numpt;i++)
vis += botflags[i]
? invflags[i]
: CountSegments(level, visflag^ invflags[i]);
else
for(i=1;i<numpt;i++)
vis += botflags[i]
? invflags[i]
: visflag^ invflags[i];
return vis;
}
Notice how this routine uses the arrays botflags and invflags defined in the fractal state. This routine is a lot simpler than DrawFractal, and a lot faster too, since it does not do any calculations or drawing. But it gets the job done: it counts the number of visible line segments for a given drawing level.
6 Calculating the fractal dimension
The single most interesting number that characterizes a fractal is its dimension. There are many definitions of dimensionality (see Mandelbrot's book The Fractal Geometry of Nature for a survey of the more important ones). A definition that works for a Euclidean space is the number of coordinates needed to specify the position of a point. For example, a plane is specified by two coordinates, an x and a y coordinate, so it has dimension two.
It is possible to generalize dimension to non-integer values. One's intuition about dimension then changes: dimension is no longer an indication of the number of coordinates, but a measure of a kind of extension or complexity of the shape. A square has more extension than a line segment, hence it has higher dimension (two instead of one). A Koch snowflake has more extension than a line segment, but less than a square. Hence, its dimension lies somewhere between one and two (it is about 1.2618).
One of the most powerful definitions of dimension is known as the Hausdorff-Besicovitch dimension. This definition is powerful because it assigns a dimension number to almost any set, whereas weaker definitions sometimes throw up their hands, so to speak, and give no answer.
A much weaker, but still very useful definition is called the similarity dimension. This definition only works for self-similar sets, and is easy to calculate when one knows how the sets are constructed. It is possible to prove that under some very general conditions, the similarity dimension, when it exists, is equal to the Hausdorff-Besicovitch dimension.
Two methods of calculating the dimension are implemented in FractaSketch. The first method (the "theoretical" method) calculates the similarity dimension from the fractal state. It uses the equation for similarity dimension given by Mandelbrot, and finds a numerical solution to this equation. The second method (the "empirical" method) gives an approximate value of the Hausdorff-Besicovitch dimension directly by drawing the fractal and counting how many "balls" are necessary to cover it. The number of "balls", as a function of the size of the ball, gives the dimension.
6.1 The theoretical method:
Calculating the similarity dimension
We would like to calculate
the similarity dimension of a fractal of which we know the template. The
template is a list of line segments and related information used to recursively
draw a fractal. The line segments have length
, where
ranges from
to
, where
is the number of segments in the template. The
are normalized to the length of
the template: they are divided by the length
of the template (the distance between
the first and the last point):
For the Koch snowflake, each
has value
and
ranges from
to
. The similarity dimension
satisfies the formula:
For example, for the Koch snowflake this becomes:
Solving this equation gives:
In the general case, the
have different values, so solving
the equation is not quite this easy as seen in Chapter 4. FractaSketch uses
a numerical method called Newton's
method seen in Chapter 6 or the Newton-Raphson method to find a solution.
This method is used to find a solution (a "root") of the equation:
where:
Newton's method works by iteration.
The process starts by guessing a value
for the root. This value is improved
by running it through the formula:
where
is the derivative
of
with respect to
, that is, it gives the slope of
at position
. Simple calculus gives:
where
is the natural logarithm of
.
The iteration continues until
(the "error") is less
than some given number. FractaSketch gives dimensions to four decimals of
accuracy. It stops the iteration when the error is less than
. It uses the following modifications to make sure everything runs smoothly:
•It only considers segments
with lengths between zero and one
. Segments with length zero are ignored. Segments with length one or more
cause an error.
•It takes
as the first guess.
• It makes sure that
. A
outside this range is brought back
to
or
.
• If there is a solid line
in the template, then it makes sure that
(because solid lines always have
dimension
).
• It does not divide by
when
(because division by zero is undefined).
• If the program is unable
to calculate the dimension then it returns the special value "??".
This happens if it cannot reduce the error to below
or if there exists a segment whose
length is greater than or equal to
.
This method is guaranteed to give the right value if the fractal is non-overlapping. Further discussion of calculating dimension and calculating Newton's method is found in Chapter 4 and Chapter 6 respectively.
6.2 The empirical method: Calculating
the Hausdorff-Besicovitch dimension
The most straightforward way
to calculate the dimension is to follow the definition of Hausdorff-Besicovitch
dimension. This definition is not too difficult to follow. I give an intuitive
explanation of it here, with enough detail that you will be able to program
it. It defines dimension in terms of "covering" the fractal with
balls of a certain size. As the
diameter of the balls goes to zero, the number of balls increases as a function
of the diameter. Let the balls have diameter
and let the number needed to cover
the fractal be
. Consider the following function
(where
is any positive real number):
This number is proportional
to the volume of the fractal in a
-dimensional space. This is easy to see: The number
is proportional to the volume of
a ball in a space of dimension
, and there are
balls. For example, the volume of
a circle disk, a two-dimensional ball, is
, which is proportional to
. As
goes to zero,
becomes bigger and bigger. There
is one special value of
, call it
, such that the following holds:
•For all
less than
,
tends to infinity as
tends to zero.
•For all
greater than
,
tends to zero as
tends to zero.
For
,, the value of
will tend to some particular positive
number. This number gives the size of the fractal in the dimension
. The number may be anything from zero to infinity. This definition is meaningful
because it has been proved that such a special value of
does really exist. If it did not,
then the definition would not work.
The special value
is exactly the Hausdorff-Besicovitch
dimension. The definition gives us a method to calculate it: cover the fractal
with balls of varying sizes, and count how many balls it takes of each size.
FractaSketch uses a modified method:
•It uses squares instead of balls. Squares are much faster to draw on the Macintosh than balls.
•It counts balls three times,
with three different sizes:
,
, and
. Three is the minimum value necessary to calculate a value of
.
•It counts pixels,
not balls. This means that the count is a factor of
too high with squares of side
The formula for
is therefore
) instead of
.
The value obtained with this method is crude; it is usually accurate only to one decimal.
7 Low-level information about FractaSketch
This section gives detailed information about lower-level aspects of FractaSketch for people interested in exploring the full power of the program.
7.1 File format
Fractals can be saved in three formats: a fractal format with type "PeFr", the text format with type "TEXT", and the QuickDraw format with type "PICT". In all cases, the file creator is "vRoy". Both the fractal and text formats have identical contents: a textual representation of the fractal state. Both the fractal and text formats can be read by FractaSketch. With this ability, you can save a fractal as text, edit the text file, and then reload the modified fractal.
For FractaSketch version 1.38B or greater, the contents of the text format is as follows (plain text must be there exactly as given, italics gives a variable name that has the type of what must be there):
D= dimstr
numpt
xdraworg ydraworg drawscale lastlevel drawsize drawmode
h[0] v[0] s[0] a[0] fa[0] fr[0] bk[0] bt[0] iv[0] lt[0]
...
h[n] v[n] s[n] a[n] fa[n] fr[n] bk[n] bt[n] iv[n] lt[n]
dd
propflag
Except for the flag bits, this information must be consistent, otherwise the program will not draw the fractal correctly. The rest of the file is ignored. The variables have the same type as given in FRACTAL_STATE (see earlier), and the following holds true as well:
double
falst[MAXPTS],
frlst[MAXPTS]; /* Angles in degrees */
n
= numpt-1; /* n is the number of segments */
fplst[i].h
= h[i]; /* Horizontal (x) coordinate */
fplst[i].v
= v[i]; /* Vertical (y) coordinate */
slst[i]
= s[i];
falst[i]
= fa[i];
frlst[i]
= fr[i];
backflags[i]
= bk[i];
botflags[i]
= bt[i];
invflags[i]
= iv[i];
leftflags[i]
= lt[i];
propflags[i]
= pp[i];
absalst[i]
= (angle)(falst[i]*(circ11/360.0));
relalst[i]
= (angle)(frlst[i]*(circ11/360.0));
attrlst[i]
= a[i];
backflags[i]
= bk[i] = a[i] in {2,3,6,7};
botflags[i]
= bt[i] = a[i] in {8,9,10,11};
invflags[i]
= iv[i] = a[i] in {4,5,6,7,8,10};
leftflags[i]
= lt[i] = a[i] in {0,3,4,7};
ddraworg=(angle)(radtodeg*dd)+0.5);
/* dd is in radians */
The correspondence between attrlst[i] and the four flags is given with the in statement, which checks set membership. This statement does not exist in C, but is easily programmed. For example, A in {a,b,c} returns 1 (true) if A is equal to either a, b, or c. Otherwise it returns 0 (false). The values of backflags[i], botflags[i], invflags[i], and leftflags[i] may be incorrect when reading in a fractal, as long as they exist and the value of attrlst[i] is correct.
Many different formats are possible for the same fractal. If the position or size of the template on the screen are different, then the numbers in fplst[i] will differ. One of the many possible text formats for the Koch snowflake is:
D=1.2618
5
16461bf
49cf21 bb7075 3 1 8
690000
80e760 0 0 0 0 0 0 0 1
a50000
80e760 5555 0 60 60 0 0 0 1
c30000
4cf13a 5555 0 -60 -120 0 0 0 1
e10000
80e760 5555 0 0 60 0 0 0 1
11d0000
80e760 5555 0 0 0 0 0 0 1
0
0
The number of points in the
template is 5 , which is one greater than the number of line segments. The
drawing level is 3. The drawing mode is 8, which means patCopy (pattern copy) in the Toolbox. (An other possible mode is 0, which means patXor, pattern exclusive-or.) The initial
coordinates, drawing scale, and line segment lengths are fixed point numbers,
which are printed out as 32-bit hexadecimal numbers. For example,
represents
, the length of one segment relative to the length of the whole template.
Multiplying
by
, the result is ffff, which is
almost
(the number 1 in fixed point). The
angles (
,
,
, and
) are floating point numbers representing degrees. The flags are the same
for all segments, namely 0 0 0 1 The dd (used for ddraworg)
and propflag may be left out; their
default values are 0.0 and false.
PostScript™
Code for Generating High Resolution Linear Fractals
Here is a PostScript™ program that can directly draw the output of linear fractals to a PostScript printer. It is useful because it prints a picture with a more precise resolution than FractaSketch™ does using QuickDraw™ commands which are limited to 72 dpi. One main limitation with PostScript is that it requires a PostScript printer to print.
One of the easiest ways to print the PostScript file is to save the instructions below as a text and use the Apple™ LaserWriter Utility (7.6 or greater) directly. An alternative is to change '%!' in a file to a form that can be read as an EPS file by another program, such as Microsoft Word™, see below:
Alternative to using '%!' to be read by other programs.
---------------------------------------------------------
%!PS-Adobe-2.0
%%PageBoundingBox: 30 31 582 761
---------------------------------------------------------
To draw a fractal, select the one you wish to print and remove its appropriate '%' sign, this will allow the command to be read by the PostScript language. When done make sure to replace '%' sign again before going on to another fractal. Here we have done it with the Ethiopic Cross referred to as Star.
Figure 7.2 A 'fine' example of the linear fractal Star created using PostScript.
%!
% Postscript Fractal Drawing Program
% based on the Macintosh program FractaSketch
% Copyright (C) 1988 Dynamic Software by Peter Van Roy. All rights reserved.
% This program may be copied and used for non-commercial use
% provided this copyright notice is included unchanged.
% Some example fractals:
% Remove comments from the fractal you wish to draw.
% Flowsnake:
% /numpt 8 def
% /slst [ 0.0 0.37796 0.37796 0.37796 0.37796 0.37796 0.37796 0.37796 ] def
% /relalst [ -19.1 60.0 120.0 -60.0 -120.0 0.0 -60.0 79.1 ] def
% /backflags [ false false true true false false false true ] def
% /botflags [ false false false false false false false false ] def
% /invflags [ false false false false false false false false ] def
% /leftflags [ true true true true true true true true ] def
% /drawlevel 5 def
% /linewidth 0.5 def
% /home { 100 400 moveto } def
% /drawscale 400 def
% Star:
/numpt 12 def
/slst [ 0.0 0.33333 0.23570 0.33333 0.33333 0.23570 0.23570 0.33333
0.33333 0.23570 0.33333 0.33333 ] def
/relalst [ 0 45 45 180 45 -90 45 180 45 -135 0 0 ] def
/backflags [ false false false false false false
false false false false false false ] def
/botflags [ false false false false true false
false false true false true false ] def
/invflags [ false false false false false false
false false false false false false ] def
/leftflags [ true true true true true true
true true true true true true ] def
/drawlevel 5 def
/linewidth 0 def
/home { 100 400 moveto } def
/drawscale 400 def
% Koch snowflake:
% /numpt 5 def
% /slst [ 0 0.33333 0.33333 0.33333 0.33333 ] def
% /relalst [ 0 60 -120 60 0 ] def
% /backflags [ false false false false false ] def
% /botflags [ false false false false false ] def
% /invflags [ false false false false false ] def
% /leftflags [ true true true true true ] def
% /drawlevel 6 def
% /linewidth 0 def
% /home { 150 750 moveto -90 rotate }def
% /drawscale 700 def
% Square snowflake:
% /numpt 9 def
% /slst [ 0 0.25 0.25 0.25 0.25 0.25 0.25 0.25 0.25 ] def
% /relalst [ 0 90 -90 -90 0 90 90 -90 0 ] def
% /backflags [ false false false false false false false false false ] def
% /botflags [ false false false false false false false false false ] def
% /invflags [ false false false false false false false false false ] def
% /leftflags [ true true true true true true true true true ] def
% /drawlevel 5 def
% /linewidth 0 def
% /home { 300 800 moveto -90 rotate }def
% /drawscale 800 def
% Triangle quilt:
% /numpt 10 def
% /slst [ 0.0 0.47141 0.33333 0.33333 0.33333 0.31427 0.22222 0.22222 0.22222 0.444
% /relalst [ 45 -45 180 -270 135 -45 180 -270 90 0 ] def
% /backflags [ false false false false false false false false false false ] def
% /botflags [ false false false false false false false false false false ] def
% /invflags [ false false false false false false false false false false ] def
% /leftflags [ false false false false false false false false false true ] def
% /drawlevel 6 def
% /linewidth 0 def
% /home { 100 750 moveto -90 rotate }def
% /drawscale 700 def
% Tempete (lightning storm):
% /numpt 7 def
% /slst [ 0.0 0.23570 0.16667 0.68718 0.66667 0.33333 0.23570 ] def
% /relalst [ -45 45 16 -166 90 45 -45 ] def
% /backflags [ false false false true true true true ] def
% /botflags [ false false false false false false false ] def
% /invflags [ false false false true true false false ] def
% /leftflags [ true true true false true false false ] def
% /drawlevel 6 def
% /linewidth 0.5 def
% /home { 100 400 moveto } def
% /drawscale 400 def
% Umbrella Tree:
% /numpt 9 def
% /slst [ 0.0 0.65 0.65 0.65 0.65 0.65 0.65 1.3 1.0 ] def
% /relalst [ 72.7 0 -180 0 28.6 0 180 -104.3 0 ] def
% /backflags [ false false false false false false false false false ] def
% /botflags [ false true false true true true false true true ] def
% /invflags false true false false false true false false false ] def
% /leftflags [ true false true false false false false false false ] def
% /drawlevel 14 def
% /linewidth 0 def
% /home { 100 400 moveto 300 400 lineto }def
% /drawscale 200 def
% Cacti:
% /numpt 8 def
% /slst [ 0.0 0.27182 0.30469 0.30104 0.30249 0.23633 0.23343 0.26797 ] def
% /relalst [ 4.8 75.4 -162 81.8 83.4 -163.9 74.2 6.3 ] def
% /backflags [ false false false false false false false false ] def
% /botflags [ false false false false false false false false ] def
% /invflags [ false false false false false false false false ] def
% /leftflags [ true true true true true true true true ] def
% /drawlevel 4 def
% /linewidth 0 def
% /home { 80 750 moveto -90 rotate } def
% /drawscale 700 def
% Dragon curve:
% /numpt 5 def
% /slst [ 0 0.5 0.5 0.5 0.5 ] def
% /relalst [ -90 90 90 -90 0 ] def
% /backflags [ false false true false true ] def
% /botflags [ false false false false false ] def
% /invflags [ false false false false false ] def
% /leftflags [ true true true true true ] def
% /drawlevel 7 def
% /linewidth 0 def
% /home { 370 565 moveto -90 rotate } def
% /drawscale 432 def
% Sierpinski arrowhead:
% /numpt 4 def
% /slst 0 0.5 0.5 0.5 ] def
% /relalst [ 60 -60 -60 60 ] def
% /backflags false false false false ] def
% /botflags false false false false ] def
% /invflags false false false false def
% /leftflags [ true false true false ] def
% /drawlevel 10 def
% /linewidth 0 def
% /home { 50 700 moveto -90 rotate }def
% /drawscale 600 def
% Format of the input parameters needed for creating your own fractals.
% Note: These parameters are identical to those output by FractaSketch.
% A fractal is represented as a connected sequence of line segments called
% a template. When the fractal is drawn each segment is recursively replaced
% by a scaled-down copy of the template. Each segment of the template
% has 4 single bit attributes that determine how the scaled-down version
% will be drawn.
% Scalar parameters:
% numpt = number of points in the template (equal to number of segments + 1)
% drawlevel = depth of recursion
% linewidth = width of drawing line
% Segment parameters:
% slst = lengths of the segments scaled so the template has horizontal length
% equal to 1. The first element of slst is not used, put zero here.
% relalst = relative angle between this segment and the previous one.
% backflags = true if the segment is drawn backwards.
% botflags = true if recursion stops with this segment.
% invflags = true if drawing of this segment is inverted in color.
% leftflags = true if positive angles are counterclockwise.
% Turtle graphics utilities:
/circ12 180 def
/left { rotate } def
/right { neg rotate } def
/forward { currentpoint translate { 0 lineto } { 0 moveto } ifelse } def
% Utilities allowing easy access to the local parameters of
% drawfractal (level, scale, visflag, rightflag, backflag):
% Only use these if there is nothing else on the stack:
/level { 4 index } def
/scale { 3 index } def
/visflag { 2 index } def
/rightflag { 1 index } def
/backflag { dup } def
% Use these when there is junk on the stack;
% give the number of items on the stack as an argument:
/+level { 4 add index } def
/+scale { 3 add index } def
/+visflag { 2 add index } def
/+rightflag { 1 add index } def
/+backflag { index } def
/declevel { 5 -1 roll 1 sub 5 1 roll } def
/5pops { pop pop pop pop pop } def
% The main definition of drawfractal:
/drawfractal {
level 0 le
{ scale visflag forward }
{ declevel
gsave
level 0 gt
{ backflag
{ scale false forward circ12 left } if
rightflag
{ relalst 0 get right }
{ relalst 0 get left } ifelse
1 1 numpt 1 sub
{ botflags 1 index get
{ slst 1 index get 2 +scale mul
invflags 2 index get forward
}
{ slst 1 index get 2 +scale mul
2 +level exch
3 +visflag invflags 4 index get xor
4 +rightflag leftflags 5 index get xor not
backflags 5 index get
drawfractal
} ifelse
1 +rightflag
{ relalst exch get right }
{ relalst exch get left } ifelse
} for
}
{ backflag
{ scale false forward circlZ left } if
rightflag
{ relalst 0 get right
1 1 numpt 1 sub
{ botflags 1 index get
{ slst 1 index get 2 +scale mul
invflags 2 index get forward
}
{ slst 1 index get 2 +scale mul
2 +visflag invflags 3 index get xor forward
} ifelse
relalst exch get right
} for
}
{ relalst 0 get left
1 1 numpt 1 sub
{ botflags 1 index get
{ slst 1 index get 2 +scale mul
invflags 2 index get forward
}
{ slst 1 index get 2 +scale mul
2 +visflag invflags 3 index get xor forward
} ifelse
relalst exch get left
} for
} ifelse
} ifelse
stroke
grestore
scale false forward
} ifelse
5pops } def
% Main program
newpath
linewidth setlinewidth
home
currentpoint translate
drawlevel drawscale true false false drawfractal
stroke
showpage
Algorithms
for Mandelbrot and Julia Sets
" Many arts there are which beautify the mind of man; of all other none do more garnish and beautify it than those arts which are called mathematical."
- H. Billingsley [4] 1570
Figure 7.3 Julia sets from humble beginnings.
The Mandelbrot set and the
Julia sets consist of complex numbers.
This means that the arithmetic needed to determine whether a point
belongs to a given set is complex arithmetic.
We recall that a complex number
consists of a real part
and an imaginary part
. It turns out to be a very good
idea to represent such a number graphically as an ordered pair
, or in other words, a point y units up and x units across from the origin of a coordinate system.
The French attribute this idea to Argand, the Germans to Gauss.
Actually, it was first described in a manuscript presented to the
Danish Academy of Sciences by a certain Wessel, but in one more instance
of the principle that mathematical objects are rarely named after their
discoverers, his claim has never been respected. In any event, it is this
coordinate representation which gives the Mandelbrot and Julia sets their
great visual appeal. The laws of arithmetic follow from the identity
. In terms of ordered pairs, the
formulae for addition, subtraction,
multiplication, and absolute value can be represented as follows:
It may be worth defining a structure cx consisting of two float or double variables and implementing functions plus, minus, and times of type cx, given by the above laws.
The real difficulty in computing
Mandelbrot and Julia sets lies not in the fact that the arithmetic is complex
but that it is infinite. Recall
that for a fixed complex number
, the Julia set at c consists
of all complex numbers z such
that the function
, iterated with initial value
, remains bounded forever. That
is, there exists a constant
such that
,
,
,
, .....
It is hard to see how any finite
amount of computation can settle the question of whether an infinite sequence
is or is not bounded. The problem turns out to be easier when the sequence
is not bounded. Suppose, for instance, that
and
. Then the sequence begins
Obviously, this sequence is growing without bound. In fact it is not hard to see that if
,
then each successive iteration
gives a value of greater absolute value than the one before, and in fact
the sequence grows without bound. We call this thethreshold value for
. For any point not in the Julia
set, there exists an iterate whose absolute value exceeds the threshold
value. Unfortunately, the iterate-and-test algorithm fails to terminate
when
lies in the Julia set.
In fact, there is no way for it to return a value of true. The usual
way of solving this problem is to choose an upper limit
MAXITERS and return a value of true if the first MAXITERS
iterations all yield values with absolute value less than the threshold
value of
. The higher the value of MAXITERS, the more nearly accurate the resulting
figure will be. To illustrate this,
we present the following program, which prints a 50 times 50 image of the
Julia set for a given value of
. Our picture is limited to that
part of the Julia set contained in the square
{x + iy | -2 < x < 2, -2 < y < 2}.
#include
<stdio.h>
#include
<math.h>
#define
MAXITERS 100
typedef
struct cx {
double x; /*
Real part */
double y; /*
Imaginary part */
}
cx;
cx
plus(cx z1,cx z2)
{
cx answer;
answer.x = z1.x + z2.x;
answer.y = z1.y + z2.y;
return answer;
}
cx
times(cx z1, cx z2)
{
cx answer;
answer.x = z1.x * z2.x - z1.y * z2.y;
answer.y = z1.x * z2.y + z2.x * z1.y;
return answer;
}
double
absval(cx z)
{
return sqrt(z.x * z.x + z.y * z.y);
}
double
threshval(cx c)
{
return (1 + sqrt(1 + 4 * absval(c))) / 2;
}
in_julia_set(cx
c, cx z)
{
int
iters = 0;
double thresh = threshval(c);
while(absval(z) <= thresh){
z =
plus(times(z,z),c);
if
(++iters > MAXITERS)
return 1;
}
return 0;
}
main()
{
cx c,z;
double x,y;
printf("Real part of c: ");
scanf("%F",&c.x);
printf("Imaginary part of c: ");
scanf("%F",&c.y);
for
(z.y = 1.96; z.y > -2.; z.y -= .08){
for
(z.x = -1.96; z.x < 2; z.x += .08)
if
(in_julia_set(c,z))
printf("@");
else
printf(" ");
printf("\n");
To generate the Mandelbrot set, just replace the main() procedure above by the following:
main()
{
cx c,z;
double
x,y;
z.x =
0.;
z.y =
0.;
for (c.y
= 1.96; c.y > -2.; c.y -= .08){
for
(c.x = -1.96; c.x < 2; c.x += .08)
if
(in_julia_set(c,z))
printf("@");
else
printf(" ");
printf("\n");
The algorithm for Julia sets
implemented in MandelMovie actually differs slightly from the one shown
above. Rather than using the formula
for threshold value given above, MandelMovie uses a fixed threshold of
. This gives an accurate Julia set
when
, but for larger values of
, it is not quite accurate. On the
other hand Julia sets for such large values of c are of little interest. In particular,
they are mere Cantor sets and therefore invisible. Equivalently, the whole Mandelbrot set is contained in the set
. It follows that the fixed threshold of
is perfectly valid in computing the Mandelbrot
set. A fixed threshold is considerably easier for integer arithmetic implementations.
Integer arithmetic is much faster than floating point arithmetic, especially
when the floating point is implemented via SANE calls. It is also better
suited for "infinite" precision calculations, as implemented in
MandelMovie. Obviously, there are
several trade-offs required in implementing the simple algorithm sketched
above. The greater the cut-off value
MAXITERS and the greater
the precision, the slower the computation. Experience suggests that it is
easier to recognize a deficiency in precision than in MAXITERS. The symptom of too few iterations is a rounding
of the contours of the sets in question. The results of insufficient precision are harder to describe but
obvious when they have once been seen.
The pictures which emerge are, as it were, visibly digital. There are several techniques available for
reducing the penalty associated with a large value of MAXITERS. The problem, of course, lies with those points
which actually belong to the Mandelbrot or Julia set in question. To explain
the first method, it may be useful to recall some basic dynamics. Consider
the sequence.
,
,
,
, ....
The simplest thing that can
happen is that
, in which case all the terms of the sequence are the same. A slightly more
complicated possibility is that the sequence is periodic, possibly after
the first few terms are thrown away. For example, if
,
, the sequence is
If we slightly perturb the
initial term of an eventually periodic sequence like this, we sometimes
observe an interesting phenomenon. Take
, for instance, to obtain the values
This sequence in some sense
converges to the alternating sequence above.
Often the reason that the absolute values are bounded in a sequence
of iterates is that the sequence converges in this sense to a periodic sequence. In this case, one can often see that a point belongs to the Mandelbrot
set, say, long before the MAXITERS limit is reached. The very simplest case of this phenomenon,
the case of convergence to a solution of
, is illustrated in the program below, which has MAXITERS
defined to be 10,000. One could also test for convergence of every second
term, every third term, etc.,
but already this simple test makes the program run more than three times
as quickly as the original program (again, with
MAXITERS equal to 10,000.)
#include
<stdio.h>
#include
<math.h>
#define
MAXITERS
10000
#define
CUTOFF
1.E-28
typedef
struct cx {
double
x;
/* Real part
*/
double
y;
/* Imaginary part */
} cx;
cx
plus(cx z1,cx z2)
{
cx answer;
answer.x = z1.x + z2.x;
answer.y = z1.y + z2.y;
return
answer;
}
cx
times(cx z1, cx z2)
{
cx answer;
answer.x = z1.x * z2.x - z1.y * z2.y;
answer.y = z1.x * z2.y + z2.x * z1.y;
return
answer;
}
double
absval(cx z)
{
return
sqrt(z.x * z.x + z.y * z.y);
}
double
threshval(cx c)
{
return
(1 + sqrt(1 + 4 * absval(c))) / 2;
}
in_julia_set(cx
c, cx z)
{
int iters
= 0;
cx newz;
double
dx,dy;
double
thresh = threshval(c);
while(absval(z) <= thresh){
newz
= plus(times(z,z),c);
dx
= newz.x - z.x;
dy
= newz.y - z.y;
if
(dx * dx + dy * dy < CUTOFF)
return 1;
z =
newz;
if
(++iters > MAXITERS)
return 1;
}
return
0;
}
main()
{
cx c,z;
double
x,y;
z.x =
0.;
z.y =
0.;
for (c.y
= 1.96; c.y > -2.; c.y -= .08){
for
(c.x = -1.96; c.x < 2; c.x += .08)
if
(in_julia_set(c,z))
printf("@");
else
printf(" ");
printf("\n");
A second approach which seems
to be quite effective is to compute the derivatives of the iterated functions
. In the event that the original sequence tends to infinity, the sequence
of derivatives goes to infinity. Conversely,
if the sequence of derivatives goes to zero, one can conclude that the original
sequence remains bounded. Here is a fragment of code that implements this algorithm. The chain rule for differentiation is what
makes it work:
in_julia_set(cx
c, cx z)
{
int iters
= 0;
double
prod = 1.;
double
thresh = threshval(c);
while(absval(z) <= thresh){
z =
plus(times(z,z),c);
prod
*= absval(z);
prod
+= prod;
/* Multiply by the absolute value */
/* of the derivative of z^relax2+c at z. */
if
(prod < UNDERFLOW)
return 1;
if
(++iters > MAXITERS)
return 1;
}
return
0;
There is a third approach to this whole problem, dishonest but quick, which should perhaps be mentioned. Imagine the complex plane covered with a square grid. If all four vertices of a grid square lie in the set, there is a good chance that the whole enclosed square lies in the set. This assumption works better than one might naively suppose, but it cannot really be trusted. A slightly more conservative version of the same scheme goes like this: suppose the 16 grid points on or in a 3 times 3 array of grid squares all lie in the set in question. Then we allow ourselves to assume that the center square is entirely contained in the set. Use this method at your own risk.
So far our discussion has been limited to algorithms which are only useful for generating black and white pictures. The details of graphics programming on the Mac are beyond the scope of this discussion; the interested reader is referred to Inside Macintosh. Nevertheless, any treatment of Mandelbrot set algorithms which confines itself to a world of 1-bit pixels leaves out most of the fun.
The most common variant of
the iterate-and-test algorithm described above returns the iteration number
when the threshold value is first exceeded, rather than a mere
or
. This value is then used as an entry to a color table which determines
the color of the corresponding pixel. By convention, the value infinity
(or in other words MAXITERS) is assigned the color black. Most of the famous color photographs of the
Mandelbrot set in coffee table books were generated in this way
There is a continuous analogue
of this procedure which seems particularly appropriate to 24-bit color displays.
It might also provide some interesting new effects when combined
with color table animation. I learned the trick on which it is based from
John Tate's work on heights of rational points on algebraic curves. As before,
points in the Julia set of
are assigned the value infinity
or, more realistically, MAXITERS; the difference lies in the values,
i.e., the colors, assigned to
points outside the set. Let
denote the
iterate of
under the function
. If
is not in the Julia set,
eventually grows without bound.
We set
This assigns a real value to a point outside the Julia
set rather than an integer, like the standard algorithm. To see how it works,
consider the case
. In this case, the Julia set is
the closed circular disk of radius
. If
, then
goes to infinity.
In fact,
, so
.
It follows that
In the integer coloring scheme,
we assign
to a point with
. Such a point satisfies the inequalities
);
.
Therefore,
lies between
to
. This justifies the idea that
is a continuous version of the standard
coloring function. Of course, the same method works for the Mandelbrot set.
I have not seen an implementation of this algorithm, but it is easy
to implement if floating point arithmetic is used. For example, the iteration
could continue until
or
MAXITERS.
Another alternative to the
standard coloring scheme is to assign colors not by the iteration number
on which an iterate
first exceeds the threshold value
but by the value of
itself. This
can be used even for black and white effects; for example, coloring a pixel
white if and only if
has positive
coordinate. A fancier alternative
is to place a color picture
on the complex plane and assign
the color of the pixel at
in
.
We conclude this chapter by
mentioning another, completely different, approach to computing Julia sets.
The idea is that if a point lies in a Julia set, after many iterations
of
, the resulting value is not too far from zero. We therefore reverse this
process, beginning with a small initial value, such as
or
, and iteratively subtracting
and taking square root.
At each stage there are two possible square roots. We repeat this
process
times, obtaining 2
different values. These values tend to
distribute themselves along the boundary of the Julia set. Here is a sample implementation with
:
#include
<stdio.h>
#include
<math.h>
typedef
struct cx {
double x; /*
Real part */
double y; /*
Imaginary part */
} cx;
char
symbol[50][50];
cx
minus(cx z1, cx z2)
{
cx
answer;
answer.x = z1.x - z2.x;
answer.y = z1.y - z2.y;
return answer;
}
double
absval(cx z)
{
return sqrt(z.x * z.x + z.y * z.y);
}
cx
root(cx z) /* Return a square root
*/
{
cx
answer;
double av = absval(z);
answer.x = sqrt((av + z.x)/2);
answer.y = sqrt((av - z.x)/2);
if
(answer.x * answer.y * z.y < 0)
answer.y = -answer.y;
return answer;
}
cleartable()
{
int
i,j;
for
(i = 0; i < 50; i++)
for
(j = 0; j < 50; j++)
symbol[i][j] = ' ';
}
iterate(cx
c, cx current, int levels)
{
int
x,y;
cx
new;
if
(levels == 0){
x =
25 + current.x * 12.5;
/* Convert the square */
y =
25 - current.y * 12.5;
/* -2 < x < 2, -2 < y < 2 */
if
(x < 0 || x > 49 ||
/* to a 50 x 50 array */
y <
0 || y > 49)
return;
symbol[y][x] = '@';
return;
}
new
= root(minus(current,c));
iterate(c, new, levels-1);
new.x = -new.x;
/* Take the other square root */
new.y = -new.y;
iterate(c, new, levels-1);
}
main()
{
cx
c,z;
int
i,j;
cleartable();
printf("Real part of c: ");
scanf("%F",&c.x);
printf("Imaginary part of c: ");
scanf("%F",&c.y);
z.x
= 1.; /* Random initial value */
z.y
= 0.;
iterate(c, z, 8);
for
(i = 0; i < 50; i++){
for
(j = 0; j < 50; j++)
putchar(symbol[i][j]);
printf("\n");
This is the algorithm used for the Julia Sketch feature in MandelMovie, and, implemented properly, it is fast enough to generate quite presentable Julia sets at animation speeds on a Mac II. The results look best for connected Julia sets.
C code
for plotting Newton's Method.
Figure 7.4 Newton's method plot.
Here is a C program for creating plots using Newton's method used in creating Color Plates 49-52.
Newton's Method App:
This application draws
images based on Newton's Method for finding zeroes of a (complex) polynomial.
It draws with more and more detail as it scans the image, and allows
you to quit at any time by clicking on the Close Box of the window.
The ".c" file is below.
It takes advantage of the C preprocessor so that you can examine
different functions with different parameters without having to modify the
main file.
/* NewtonMethod.c
Newton's Method for polynomial-like functions
f(z) = (z-w_1)^alpha_1 * ... * (z-w_n)^alpha_n
(C) 1993 by lichen ecological design
written by Stuart Cowan
modified for the
Macintosh Eric (doc) Kampman 1993
(C) 1993 Dynamic
Software
for THINK C
*/
/* make sure you
add the SANE library. Also, the
THINK C compiler
settings should
be set with "Native Floating Point format" checked.
*/
#include <Values.h>
/* Leave one of these include files uncommented */
/************************************************/
// #include "TwoRoots.h"
// #include "ThreeRoots.h"
#include "SixZeroes.h"
// #include "FourRoots.h"
/************************************************/
/* Metric -- leave one of these #defs below uncommented */
/************************************************************/
//#define STANDARD //
standard sqroot of sum of squares
#define MANHATTAN //
manhattan metric
/************************************************************/
/* below needed for
overflow */
#define MAXDOUBLE nextdouble(inf(),0.0)
/* define initial
resolution */
/* (pixels wide/high
per test point) */
#define REZ 16
/* maximum number of roots
*/
#define NUM 10
/* number of color bands per root */
#define BAND_MOD 8
/* Window Size */
#define WIN_WIDTH 200
#define WIN_HEIGHT 200
/* Max number of iterations.
*/
/* Optional in .h
file */
#ifndef MAXIT
#define MAXIT
100
#endif
/* global variables
*/
int maxit,FLAG;
/* values of the exponents, real and imaginary components
*/
/* each real component must be greater than 0.5 for convergence
*/
double alphare[ROOTS] = ALPHARE;
double alphaim[ROOTS] = ALPHAIM;
int roots =
ROOTS;
double rootre[ROOTS]
= ROOTRE;
double rootim[ROOTS]
= ROOTIM;
short red[ROOTS] = REDS;
short green[ROOTS] =
GREENS;
short blue[ROOTS] =
BLUES;
double xmin = XMIN;
double xmax = XMAX;
double ymin = YMIN;
double ymax = YMAX;
double deltax,deltay;
short width,height;
short i = 0; /* what
pixel we're at */
short j = 0;
short curRez = REZ;
double epsilon;
short vert[2];
double x,y;
short blackvec[3] = {0,0,0};
/* for the Macintosh
version */
Point wTopLeft;
WindowPtr myWindow;
Boolean hasColor;
Boolean endFlag
= FALSE; /* are we finished? */
/** DECLARATIONS **/
void setparameters(void);
void fractal(void);
void escapetest(int
iter);
void iteration(void);
/* Mac Specific */
void MacInit(void);
void DoMouseDown
(EventRecord *theEvent);
void ProcessEvent(void);
void EventLoop(void);
Boolean Test4Color(void);
void startup(void);
void plotpixel(short
basin,short iter,short i,short j);
main()
{
MacInit();
if (Test4Color)
{
setparameters();
startup();
EventLoop();
}
else /* Can't run -- no Color */
{
SysBeep(1);
}
}
/* this procedure sets values for the parameters used in the
program */
void setparameters()
{
/* maximum number of iterations */
maxit = MAXIT;
/* window width and height in screen pixels */
width = WIN_WIDTH;
height = WIN_HEIGHT;
wTopLeft.h = 40;
wTopLeft.v = 40;
/* deltax and deltay tell us how tall and wide one pixel is
*/
deltax = (xmax
- xmin)/((double) width);
deltay = (ymax
- ymin)/((double) height);
/* convergence test value: if distance squared from root is
less than
this value, then
we assume convergence */
#ifdef STANDARD
epsilon = 0.01*0.01;
#endif
#ifdef MANHATTAN
epsilon = 0.02;
#endif
#ifdef EPSILON /*
this is optional in .h file */
epsilon = EPSILON;
#endif
}
/* Plot the fractal: choose starting points in the complex
window,
and iterate them
according to Newton's Method until we are either
within a basin of
attraction or exceed the allowed number of
iterations. Colors
cycle in brightness according to the number
of iterations it
takes to converge */
void fractal()
{
short iter;
short shiftRez = curRez<<1;
/* Next test so we don't keep checking pixels
we've already
got colors for */
if ( (j%shiftRez
!= 0) || (i%shiftRez != 0) || (curRez == REZ) )
{
x = xmin
+ i*deltax;
y = ymin
+ j*deltay;
FLAG =
0;iter = 0;
vert[0]=i;vert[1]=j;
while(FLAG==0
&& iter < maxit)
{
escapetest(iter);
iteration();iter++;
}
}
j += curRez;
if (j>=height)
/* on to next column */
{
j = 0;
i += curRez;
if (i>=width)
{
curRez
/= 2;
if
(curRez == 0)
{
endFlag
= TRUE; /* we're finished */
SysBeep(1);
}
else
{
i
= j = 0;
}
}
}
}
/* test for convergence */
void escapetest(int iter)
{
int i;
double mag;
for(i=0;i<roots;i++)
{
#ifdef STANDARD //
the slowest metric
mag = (x-rootre[i])*(x-rootre[i])
+ (y-rootim[i])*(y-rootim[i]);
#endif
#ifdef MANHATTAN /*
best for performance/quality */
{ /* Manhattan
*/
double
tdelX,tdelY;
tdelX
= x-rootre[i]>0 ? x-rootre[i] : rootre[i]-x;
tdelY
= y-rootim[i]>0 ? y-rootim[i] : rootim[i]-y;
mag
= tdelX+tdelY;
}
#endif
if (mag<epsilon)
{
plotpixel(i,iter,(int)
vert[0],(int) vert[1]);
FLAG=1;
}
}
}
/* iterate function */
void iteration()
{
double u,v,u1,v1,mag;
double tU,tV;
Boolean signTU,signTV;
int i;
u = 0.0;v = 0.0;
for( i=0; i<roots;
i++)
{
u1 = x
- rootre[i];
v1 = y
- rootim[i];
mag = u1*u1 + v1*v1;
if (mag == 0.0)
{
tU = alphare[i]*u1 + alphaim[i]*v1;
u = (tU>0) ? (MAXDOUBLE) : -(MAXDOUBLE);
tV = alphaim[i]*u1-alphare[i]*v1;
v
= (tV>0) ? (MAXDOUBLE) : -(MAXDOUBLE);
}
else
{
u
+= (alphare[i]*u1+alphaim[i]*v1)/mag;
v
+= (alphaim[i]*u1-alphare[i]*v1)/mag;
}
}
mag = u*u + v*v;
if ( mag == 0
)
{
x = (-MAXDOUBLE);
y = (MAXDOUBLE);
}
else
{
x -= u/mag;
y += v/mag;
}
}
/* Standard initialization stuff */
void MacInit()
{
MaxApplZone();
InitGraf(&thePort);
InitFonts();
FlushEvents(everyEvent,
0);
InitWindows();
InitMenus();
TEInit();
InitDialogs(0L);
InitCursor();
}
/* Check to see if user clicked in the Close Box */
void DoMouseDown (EventRecord *theEvent)
{
WindowPtr theWindow;
switch ( FindWindow
(theEvent->where, &theWindow) )
{
case inGoAway:
if (theWindow == myWindow)
{
DisposeWindow(myWindow);
myWindow = 0L; // so
we know there's no window
}
break;
default:
/*
do nothing */
break;
}
}
/* Get events so we don't "hang" the machine while
drawing */
void ProcessEvent()
{
Boolean mine;
EventRecord theEvent;
/* SystemTask ();
uncomment to be nice to background processes
but
this will slow things down */
mine = GetNextEvent
(everyEvent, &theEvent);
if (mine)
switch
(theEvent.what)
{
case
mouseDown:
DoMouseDown(&theEvent);
break;
default:
/*
Do Nothing */
break;
}
}
/* Standard but stripped down event loop */
void EventLoop()
{
while (myWindow)
{
if (!endFlag)
fractal();
ProcessEvent();
}
}
/* Does the Mac have
Color QuickDraw? This isn't a complete
test
because the bit
depth of the monitor might be 2 and so even though
the app won't
crash the results may look terrible.
*/
Boolean Test4Color()
{
SysEnvRec sysEnv;
SysEnvirons (
1, &sysEnv );
return sysEnv.hasColorQD;
}
/* open a window of appropriate size, clear screen, and initialize
graphics */
void startup()
{
Rect wRect;
SetRect ( &wRect,
wTopLeft.h, wTopLeft.v, wTopLeft.h+width, wTopLeft.v+height );
myWindow = NewCWindow
( 0L, &wRect, "\p<--Close Box/quit", TRUE,
0L, (WindowPtr)(-1L), TRUE, 0L );
SetPort(myWindow);
PenNormal();
{
RGBColor blk;
blk.red
= blk.green = blk.blue = 0;
RGBForeColor(&blk);
OffsetRect(&wRect,-wTopLeft.h,-wTopLeft.v);
PaintRect(&wRect);
}
}
/* plot a pixel; iter is used for color choice, and i,j are
pixel
coordinates */
void plotpixel(short basin,short iter,short i,short j)
{
Point pPoint;
RGBColor pColor;
Rect pRect;
pPoint.h = i;
pPoint.v = j;
pColor.red = (red[basin]*(iter%BAND_MOD))/BAND_MOD;
pColor.green =
(green[basin]*(iter%BAND_MOD))/BAND_MOD;
pColor.blue =
(blue[basin]*(iter%BAND_MOD))/BAND_MOD;
/* The original,
SGI version, uses 8 bit values for each of the
color components.
The Macintosh RGBColor structure uses unsigned
shorts. We could have changed our .h files, but for
compatibility
with previous
data formats we'll just bit shift left by 8.
*/
pColor.red = pColor.red<<8;
pColor.green =
pColor.green<<8;
pColor.blue =
pColor.blue<<8;
SetRect(&pRect,pPoint.h,pPoint.v,pPoint.h+curRez,pPoint.v+curRez);
RGBForeColor(&pColor);
PaintRect(&pRect);
} /* end .c file */
The next .h file produces
an image of Newton's method applied to a polynomial of 6 roots, arranged
evenly around a circle.
/* SixZeroes.h */
/* Below are the
values that are required for the application
to compile.
Note that for ROOTRE, ROOTIM, REDS, GREENS,
BLUES, ALPHARE
and ALPHAIM there are as many entries in the
arrays as the
number #defined to be ROOTS (in this case 6).
*/
/* Define the number of zeroes */
#define ROOTS 6
/* Define the real
and imaginary components of
each root. */
#define ROOTRE {1.0, .5, -.5, -1.0, -.5, .5 }
#define ROOTIM {0.0, .867, .867, 0, -.867, -.867 }
/* Define the colors
associated with each root */
#define REDS {255, 127, 0, 0, 0, 127 }
#define GREENS {0, 127, 255, 127, 0, 0 }
#define BLUES {0, 0, 0, 127, 255, 127 }
/* Define the region
of the complex plane to be drawn */
#define XMIN -1
#define YMIN -1
#define XMAX 1
#define YMAX 1
/* Define the alphas associated with each root */
#define ALPHARE {.51, .51, .51, .51, .51, .51 }
#define ALPHAIM {0, 0, 0, 0, 0, 0 }
/* end .h file */
Below are the rest
of the .h files commented out near the top of the .c file. Uncomment a particular #include line to utilize
the values contained in these additional files.
/* FourRoots.h */
#define ROOTS 4
#define ROOTRE {.5,.5,-.5,-.5}
#define ROOTIM {.5,-.5,.5,-.5}
#define REDS {255,0,0,0}
#define GREENS {0,127,127,0}
#define BLUES {0,0,127,127}
#define XMIN -1
#define YMIN -1
#define XMAX 1
#define YMAX 1
#define ALPHARE {.7,.7,.7,.7}
#define ALPHAIM {-.1,0,0,-.1}
/* end FourRoots.h */
/** ThreeRoots.h **/
#define ROOTS 3
#define ROOTRE {1,-.5,-.5}
#define ROOTIM {0,.867,-.867}
#define REDS {255,0,0}
#define GREENS {0,0,255}
#define BLUES {0,255,0}
#define XMIN -2
#define YMIN -2
#define XMAX 2
#define YMAX 2
#define ALPHARE {.6,.6,.6}
#define ALPHAIM {0,0,0}
/* end ThreeRoots.h */
/** TwoRoots.h **/
#define ROOTS 2
#define ROOTRE {.25,-.25}
#define ROOTIM {.25,-.25}
#define REDS {30,0}
#define GREENS {180,0}
#define BLUES {30,255}
#define XMIN -1.5
#define YMIN -1.5
#define XMAX 1.5
#define YMAX 1.5
#define ALPHARE {.51,.51}
#define ALPHAIM {-.2,-.2}
/* end TwoRoots.h */
C code for
doing IFS transforms.
Figure 7.5 Iteration Function System plot.
Here is some C code that shows a method for IFS transformations.
/* Copyright (C) 1993 Dynamic Software by Eric (Doc) Kampman.
All rights reserved.
In the following
THINK C code fragment, the result, a TransformHdl,
is a handle to floating point constants which correspond to
a linear transform on the plane:
x' = ax + by +
e;
y' = cx + dy +
f;
such that if tHdl
is of type Transform **tHdl, then
(**tHdl)[0] =
a;
(**tHdl)[1] =
b;
(**tHdl)[2] =
c;
(**tHdl)[3] =
d;
(**tHdl)[4] =
e;
(**tHdl)[5] =
f;
The input structure
is the set of points, three source and three destination, which defines the linear transform. See chapter 6 for more details.
*/
typedef double Transform[6];
typedef struct PtArray {
Point source1,source2,source3;
Point dest1,dest2,dest3;
} PtArray,*PtArrayPtr,**PtArrayHdl;
Transform **CalcTransform(PtArrayHdl
inPts);
Transform **CalcTransform(PtArrayHdl
inPts)
{
long numXForms, countUp;
short x1,x2,x3,y1,y2,y3;
short X1,X2,X3,Y1,Y2,Y3;
double OneMTwoX,OneMTwoY,OneMTwoXP,OneMTwoYP;
double OneMThreeX,OneMThreeY,OneMThreeXP,OneMThreeYP;
double a,b,c,d,e,f;
Transform *tPtr,**tHdl;
/**
Much of the code
below could be written more compactly, but has been broken down into multiple lines for the sake of readability.
**/
/* Allocate space
for the result */
tHdl = (Transform**)NewHandle(sizeof(Transform));
/*
The next section
simply transfers data stored in the passed parameter into local variables for readability.
*/
x1 = (*inPts)->source1.h;
y1 = (*inPts)->source1.v;
x2 = (*inPts)->source2.h;
y2 = (*inPts)->source2.v;
x3 = (*inPts)->source3.h;
y3 = (*inPts)->source3.v;
X1 = (*inPts)->dest1.h;
Y1 = (*inPts)->dest1.v;
X2 = (*inPts)->dest2.h;
Y2 = (*inPts)->dest2.v;
X3 = (*inPts)->dest3.h;
Y3 = (*inPts)->dest3.v;
/* The following
sums are often used more than once. */
OneMTwoX = (double)(x1-x2);
OneMTwoY = (double)(y1-y2);
OneMThreeX = (double)(x1-x3);
OneMThreeY = (double)(y1-y3);
OneMTwoXP = (double)(X1-X2);
OneMTwoYP = (double)(Y1-Y2);
OneMThreeXP =
(double)(X1-X3);
OneMThreeYP =
(double)(Y1-Y3);
/*
The next section
simply solves for the transform constants, making sure that division by zero is avoided.
*/
if (OneMTwoY ==
0)
{
a = OneMTwoXP/OneMTwoX;
c = OneMTwoYP/OneMTwoX;
}
else
{
if (OneMThreeY
== 0)
{
a
= OneMThreeXP/OneMThreeX;
c
= OneMThreeYP/OneMThreeX;
}
else
{
a
= (OneMTwoXP/OneMTwoY - OneMThreeXP/OneMThreeY)/
(OneMTwoX/OneMTwoY
- OneMThreeX/OneMThreeY);
c
= (OneMTwoYP/OneMTwoY - OneMThreeYP/OneMThreeY)/
(OneMTwoX/OneMTwoY
- OneMThreeX/OneMThreeY);
}
}
if (OneMTwoX ==
0)
{
b = OneMTwoXP/OneMTwoY;
d = OneMTwoYP/OneMTwoY;
}
else
{
if (OneMThreeX
== 0)
{
b
= OneMThreeXP/OneMThreeY;
d
= OneMThreeYP/OneMThreeY;
}
else
{
b
= (OneMTwoXP/OneMTwoX - OneMThreeXP/OneMThreeX)/
(OneMTwoY/OneMTwoX
- OneMThreeY/OneMThreeX);
d
= (OneMTwoYP/OneMTwoX - OneMThreeYP/OneMThreeX)/
(OneMTwoY/OneMTwoX
- OneMThreeY/OneMThreeX);
}
}
e = (double)X1
- a*( (double)x1 ) - b*( (double)y1 );
f = (double)Y1
- c*( (double)x1 ) - d*( (double)y1 );
/*
Assign the results
to tHdl and pass it back to the calling
routine
*/
(**tHdl)[0] =
a;
(**tHdl)[1] =
b;
(**tHdl)[2] =
c;
(**tHdl)[3] =
d;
(**tHdl)[4] =
e;
(**tHdl)[5] =
f;
return tHdl;
}
Basic Programs
for Drawing Fractals and Creating Chaos Models.
In the following section we will give an introduction to fractal programs and chaos programs [5] written in the computer language Basic. The Basic language has been chosen here for its ease of use and interpretive capabilities. Commercial programs such as FractaSketch™ and MandelMovie™ are generally written in C, to provide a more robust implementation and quicker speed. Two good books on additional C algorithms [6] that can be used in creating fractals are Fractal Programming in C and Numerical Recipes in C, be warned however that C programs are typically longer and more difficult to write, often without much added benefit.
To run these programs you will need a Basic interpreter or a Basic compiler, two of the most common types of Basic software for the Macintosh are Microsoft Basic and True BASIC. Visual experimentation can be easily made by varying formulas and their associated perimeter values. Programs in this section will be written for a standard Macintosh screen (0,0)-(480,320) with a minimal set of commands, need for implementation. You can expand these programs by adding features such as 'Enter' commands, display axis, or other customized features, such as to accommodate a different screen size or color.
The programs we will use consist of primarily of iteration equations used in creating dynamical systems. Often in iteration plots intricate structures are found that reveal transitional changes between predictable and chaotic states along with constructions that exhibit self-similarity at different magnification levels. Sometimes these iteration functions migrate to and settle down in a specific region. If the region converges to a single point it is called an attraction point, if the region converges to a repeating set of points it is called an attraction cycle or limit cycle and if the region converges to a contained region with a chaotic -- non deterministic path we define it as a strange attractor. A primary technique used in displaying these paths is through the use of phase space.
Phase Space: Showing
Complexities in a Closed Form.
An insightful way the
behavior of a dynamical system can be observe is through the use of phase
space
[7]
. In phase space positions are plotted associating
two or more interrelated perimeters, such as an object's position vs. its
velocity ,
vs.
or an interdependent set of functions
with values comparable to lets say
,
and
. It is the collection of these trajectory paths comprising all possible
initial conditions that establishes a phase space system. By using a form
of visual representation, information can be collected about a system's
dynamics that over time can reveal important behavior characteristics about
its geometry. This method has allowed mathematics and scientist to gain
an insight into mathematical models that may not be as clearly portrayed
from a set of data points or a time series.
Figure 7.6 A time series vs. phase space.
A dynamical system is influenced by many factors. Is it linear or nonlinear, does it have a driving or damping force associated with it? Does the system coverage to a point, a limit cycle or does it wonder about as a strange attractor? Perhaps it spirals out to infinity. To initially demonstrate these dynamical systems we will use the swaying motion of a pendulum under varying conditions. Other oscillatory models such as a mass on a spring can also be used to demonstrate basic dynamical systems.
Pendulum model: equations for oscillating motion.
The pendulum model we
will use consists of a weight with mass
under a constant gravitation
, producing a force
. It is attached to a ridged rod with a negligible mass of length
, this is assumed so that while in free fall the weight maintains an equal
distant from the pivot point. The displacement value
also given as angle
indicates the position change from
the vertical equilibrium or the rest position. Later on other influences
such as a damping force
and a driving force
will also be added to the pendulum
model.
= mass
= gravity
=
=
= length of rod
=
= position
=
= angle position
=
=
= instantaneous angular velocity
=
=
=
= acceleration
= damping force
= driving force
Figure 7.7 Table of terms used for the pendulum model.
Undamped, unforced pendulum.
Type: Oscillating pendulum.
Equations:
In Newtonian notation
a pendulum can be described in terms of a second order differential equation
given by
. Then by splitting the equation into two parts,
for the angle of elevation and
for the angular velocity, a set
of interdependent equations
and
are derived that can be easily solved.
This is done by applying numerical techniques that give close approximate
solutions of first order differential equations. In this model we will use
phase space to produces plots of a pendulum's position
vs. a pendulum's velocity
.
Common values:
range.
Initial values:
,
,
.
Figure 7.8 A Pendulum model diagram.
A linear approximation.
In some cases, if we assume
the swing of the pendulum is small, a close linear approximation can be
made of the oscillation by replacing
with
. This expression is derived from the Taylor expansion series:
, which can also be used in determining the accuracy. Some calculated errors
tolerances are given here
.
Using this method
the equation attained is
, with a general solution:
, given in terms of
with
and
as constants
[8]
.
Figure 7.9 Simple pendulum's motion, time series and phase space.
Expanded nonlinear form.
When
a pendulum's swing for angle
is too great that a linear approximation will no longer produce accurate
results, the equation's solution is calculated using nonlinear methods.
Since many of these nonlinear equations have no exact solutions, their numeric
values are calculated using approximation techniques. The most commonly
of these are the Euler method and the longer more accurate Runga-Kutta method.
Figure 7.10 Pendulum swings that needs to use the expand method.
Figure 7.11 Undamped pendulum phase space plots for different swing values.
Methods for calculating derivatives numerically.
Fuction
to solve:
in terms of
.
Starting
with
.
Euler Method:
the stepsize for
held constant example
.
for calculating the change
in
.
Runga-Kutta Method:
the stepsize for
held constant example
Runga-Kutta Method using a predictor-corrector enhancement:
Continuously repeating the last five steps to plot function.
Warning for all methods: reducing the stepsize for
can keep errors from expanding to
rapidly.
Damped, unforced pendulum.
Type: Oscillating pendulum.
Equations:
Common values:
range.
Initial values:
,
,
,
.
Figure 7.12 Pendulum model with damping showing time series and phase space. .
In
some pendulum models outside forces such as air resistance and internal
friction are calculated into the equation with a damping term. Here it is
represented by
added to the general equation.
Damped, forced pendulum.
Type: Oscillating pendulum.
Equations:
Common values:
range,
can be given as a periodic or continuous force.
Initial values:
,
,
,
,
at every time period
.
Figure 7.13 Pendulum model shown a driving force
with period
.
In
certain systems external energy is added to an oscillation referred to as
a driving force. Through the use of a driving force kinetic and potential
energy is feed into the system until the rate of energy flowing in is equal
to a counter force, usually the amount of energy lost by damping. If the
driving force is not sufficiently damped or a counter force is not applied
an excessive amount of energy will accumulate often resulting in uncontrollable
oscillatory wave amplifications. Driving force
energy can be put in a system as a continual
process or at periodic stages. In Figure 7.13 we show how periodic driving
forces can migrate to phase spaces with stable orbits.
Programs for modeling a pendulum.
•
For a simple pendulum set damping force,
=
and driving force,
=
.
• For
a damped pendulum set driving force,
=
.
*' Code to model a swinging pendulum.
' Code to plot a pendulum's time series.
' Code to plot a pendulum's phase space with damping and driving forces.
Some older Basic Language Programs need line numbers see Henon Map code in the next section for example.
' Basic Chaos Programs © Copyright 1986, 1987, 1988 Dynamic Software
' written by Bernt Wahl. Programs may be used for non commercial use.
'DEFAULT VALUES
x=3:y=0
' Initial starting point try x values 0-4
F=1 ' Force values
1-5
g=0 ' Damping try g values 0-1
h=.05 ' Stepsize
Ft=0:P=3.1416/h
' Driving force force and period
ScaleX=50:ScaleY=50 ' Scale expansion for x and y
xo=245:yo=150
' Centers screen origin
u1=0:u=0:v1=0:v=0 ' Initial plot values
IterationNumber=1000 ' Number of iterations per starting point
LINE(0,yo)-(490,yo):LINE(xo,0)-(xo,300)
' Axis lines
' PROGRAM Pendulum: ' Subroutine using Runga-Kutta p-c method
kx1=y
ky1=-F*SIN(x)-g*y
kx2=y+.5*h*ky1
ky2=-F*SIN(x+.5*h*kx1)-g*(y+.5*h*ky1)
kx3=y+.5*h*ky2
ky3=-F*SIN(x+.5*h*kx2)-g*(y+.5*h*ky2)
kx4=y+.5*h*ky3
ky4=-F*SIN(x+h*kx3)-g*(y+h*ky3)
y1=y+(1/6)*h*(ky1+ky2+ky3+ky4)
x1=x+(1/6)*h*(kx1+2*kx2+2*kx3+kx4)
FOR I = 1 TO IterationNumber
xp=x+2*h*y1
yp=y-2*h*(F*SIN(x1)+g*(y1+yp))
xc=x1+.5*(y1+yp)*h
yc=y1-.5*h*(F*SIN(x1)+F*SIN(xp)+g*(y1+yp))
x2=.2*xp+.8*xc
y2=.2*yp+.8*yc
' IF I
MOD P = 0 THEN y2= y2 + Ft ' To add a periodic driving force
x=x1
y=y1
x1=x2
y1=y2
u=x*ScaleX+xo:v = yo-y*ScaleY
' Expands x and y with a downward plot.
' Time series plots x and y in terms of time
PSET
(I*h*2,x*10+50) 'x values, 10 height, start at 50 y-axis
PSET
(I*h*2,yo-y*10 + 100) 'y values, 10 height, start at 100 y-axis
' Phase space plot
PSET (u,v)
' for plotting
phase space points
'LINE(u,v)-(u1,v1):u1=u:v1=v ' for
phase space plotting lines
NEXT I
END
Van der Pol Oscillator Equation
Type: Forced, self-sustaining oscillation.
Equations:
Common values:
Initial values:
,
.
Figure 7.14 Van der Pol limit cycle for different
values.
This Van der Pol oscillator was first applied to a physical system by Balthasar van der Pol (1889-1959) in 1927 to study electric circuits constructed with vacuum tubes. In these models it was noticed that the 'relaxed' state an oscillator produced depended on its initial starting position. In a Van der Pol system all points converge to a cyclic attractor with two hesitating transient points that have chaotic periods, that is except for the origin where it is an unstable fixed point.
The
Van der Pol oscillator
[9]
, written here as a single equation, uses feed back to sustain its phase
space orbit. In this system a limit cycle settles down to an orbit characterized
by the coefficient
that automatically maintains a constant
energy equilibrium. This process is achieved by combining increases energy
(a driving force) for small oscillations with one that removes energy (a
damping force) for large oscillations.
Until recently many types of calculations could be carried out at a considerably faster rate on an analog computer than with a digital computer. This was especially true of nonlinear equations that used feed back. In Figure 7.15 you can see a set up used to program an analog computer, notice the connection plugs that need to be physically connected with wires in order to produce a working system. In Figure 7.16 you can see a circuit diagram used in creating the Van der Pol equation on an analog computer.
Figure 7.15 Analog computer set up.
Figure 7.16 Analog circuit diagram for the Van der Pol equation.
'Code for Van der Pol equation.
'DEFAULT
VALUES
x=2:y=.5
' Initial starting point try x values 0-4
g=2 ' Try g values 0-10
IterationNumber=1000
' Number of iterations per starting
point
h=.05
' Stepsize
u1=x*ScaleX+xo:v1
= yo-y*ScaleY ' Prevents first
line draw
ScaleX=30:ScaleY=30
' Scale expansion for x and y
xo=245:yo=150 '
Centers screen origin
u1=0:u=0:v1=0:v=0
' Initial plot values
LINE(0,yo)-(490,yo):LINE(xo,0)-(xo,300)
' Axis line
' PROGRAM
Van der Pol using Runga-Kutta
FOR
I = 1 TO IterationNumber
k1x = y
k1y = g*(1-x^2)*y -x
k2x = y +.5*h*k1y
k2y = g*(1-(x+.5*h*k1x)^2)*(y+.5*h*k1y)-(x+.5*h*k1x)
k3x = y +.5*h*k2y
k3y = g*(1-(x+.5*h*k2x)^2)*(y+.5*h*k2y)-(x+.5*h*k2x)
k4x = y +h*k3y
k4y = g*(1-(x+h*k3x)^2)*(y+h*k3y)-(x+h*k3x)
y1=y+(1/6)*h*(k1y+2*k2y+2*k3y+k4y)
x1=x+(1/6)*h*(k1x+2*k2x+2*k3x+k4x)
y=y1
x=x1
' Phase
space plot
u=xo+x*ScaleX:v=yo-(y*ScaleY)
PSET
(u,v) ' for plotting phase space points
'LINE(u,v)-(u1,v1):u1=u:v1=v ' for phase space plotting lines
NEXT
I
END
Duffing equation
Type: Chaotic Attractor with periodic oscillations.
Equations:
Common values:
,
,
,
,
Initial values:
Figure 7.17 Various phase space plots for different force
of the Duffing cycle.
In 1908 Georg Duffing (1861-1944) a mechanical experimentalist developed models to explain the geometric properties of dynamical systems. From his work evolved equations that use driving forces and damping forces to control a dynamical system's oscillatory behavior. We now call this system the Duffing cycle.
The Duffing cycle, given here as a second order differential
equation
, has oscillatory paths that are dependent on initial values:
,
,
,
,
and a time period
[ Figure 7.18]. In these cycles,
phase portraits are produced that in some cases settle down to be periodic,
repeating every
cycles while in other cases remain
in a chaotic state, forming cycles that never repeat.
Figure 7.18 Phase space plots for four different starting points of the
Duffing cycle.
When plotting the Duffing Cycle be sure to let a number of cycles accrue after its initial starting point. This will allow you to see if the system produces a relaxed repeating cycle or remains chaotic. The best way to do this is to clear the screen after a number of cycles have been reached.
'Code for the Duffing Cycle phase space.
'DEFAULT VALUES
x=3:y=4
' Starting point
a=0:g=.05:e=1:F=7.5:w=1:t=0 ' Constant values
IterationNumber=2000
h=.05:dt=3.14159/100
' h stepsize and dt time steps.
u1=x*ScaleX+xo:v1 = yo-y*ScaleY
' Prevents first line draw
xo=245:yo=150
ScaleX=20:ScaleY=20
' Scale values
u1=0:u=0:v1=0:v=0
' Initial line draw points
LINE(0,yo)-(490,yo):LINE(xo,0)-(xo,300)
' Axis line
' PROGRAM Duffing Cycle
phase space
FOR I = 1 TO IterationNumber
t=t+dt
k1x = y
k1y = -g*y-e*x^3+F*COS(w*t)+a*x
k2x = y+.5*h*k1y
k2y = -g*(y+.5*h*k1y)- e*(x+.5*h*k1x)^3+F*COS(w*(t+.5*dt))+a*(x+.5*h*k1x)
k3x = y+.5*h*k2y
k3y = -g*(y+.5*h*k2y)- e*(x+.5*h*k2x)^3+F*COS(w*(t+.5*dt))+a*(x+.5*h*k2x)
k4x = y+h*k3y
k4y = -g*(y+h*k3y)-e*(x*h*k3x)^3+F*COS(w*(t+dt))+a*(x*h*k3x)
y1=y+(1/6)*h*(k1y+2*k2y+2*k3y+k4y)
x1=x+(1/6)*h*(k1x+2*k2x+2*k3x+k4x)
y=y1
x=x1
u=xo+x*ScaleX
v = yo-(y*ScaleY)
' Phase space plot
u=xo+x*ScaleX:v=yo-(y*ScaleY)
PSET (u,v) ' For plotting phase space points
LINE(u,v)-(u1,v1):u1=u:v1=v
' For phase
space plotting lines
NEXT I
END
Another way to view the Duffing oscillator is through periodic
strobing. This is done by plotting only points at certain time intervals
say
for integers
. Then by changing individual values
for
,
,
,
,
we can see how a system is influenced by each constant. These are referred
to as the constants Poincaré section.
Figure 7.19 Duffing Cycle seen at strobing period
.
For some regions of the Duffing equation you will find
two distinct limit cycles for the same
, each one with an amplitude dependent on its initial
value. Here are some regions to explore:
vs.
for constants:
in the range of
.
Plotting a Duffing cycle's amplitudes
vs. frequency
for various constant values results
in areas of bifurcation called cyclic folds as seen in Figure 7.20.
Figure 7.20 Cyclic fold curve with different amplitudes
vs. frequency
.
Iteration Functions:
Taking the First Steps.
Henon Map ( shoehorn
curve )
Type: A quadratic map of a 2D Strange Attractor from iterations.
Equations:
Common values:
,
Initial values:
,
Figure 7.21 The Henon Map ( shoehorn curve ).
The Henon Map ( shoehorn curve ) [Figure 7.21] was originally formulated by Michael Henon and Yves Pomeau in 1976. This two part iteration function was first used as a simplified model to show the principles of Poincaré mapping ( 2-dimensional cross section slice ) found in the Lorenz Attractor. In these strange attractors, new positions are continually being mapped through iterations to a unique points in a contained region. Since these points never form a periodic cycle their patterns are referred to as chaotic. Upon close inspection of the Henon Map's different regions you will find bands that are comprised of continually finer layers. This layering is never ending similar in fashion to what is found in the Cantor set. Try plotting the Henon Map at different levels of magnification to explore this phenomenon yourself.
' Code for creating a Henon Map ( shoehorn curve ) plot.
' Iteration method for solving the Henon Map (shoehorn).
'DEFAULT VALUES
xn=1.5: yn=1.5 ' Initial
value for x,y
a=1.4:b=.3 ' Constants: a,b
Scale=150
xo=200:yo=150 ' Position
X,Y moved
IterationNumber=1000
' PROGRAM Henon Map (shoehorn)
FOR I =1 TO IterationNumber
x1=yn+1-(a*xn^2) ' Iteration function
y1=b*xn
xn=x1 ' Replaces the new value for x
yn=y1 ' Replaces the new value for y
PSET(xn*Scale+xo,250-(yn*Scale+yo))' Plots scale, with origin at xo,yo
NEXT I
END
Old format code used in creating Henon Map ( shoehorn curve ) plot. Notice the line numbers used in the older basic programing languages. Use this style only if you are using a basic program that does no support the other form.
' Inital Values
10 CLS '
Clears screen
20 DIM X(1001), Y(1001) ' Range for number of calculated points (1000).
30 X(1)=1.5: Y(1)=1.5 ' Initial value for x,y
40 A=1.4:B=.3 ' Constants: a,b
50 SCALE=150
60 POSITIONX=200:POSITIONY=150 ' Position X,Y moved
70 FOR
N =1 TO 1000 ' Progam Loop
80 X(N+1)=Y(N)+1-(A*X(N)^2) ' Iteration function
90 Y(N+1)=B*X(N)
100 X(N)=X(N+1)
' Replaces the new value for x
110 Y(N)=Y(N+1)
' Replaces the new value for y
120 PSET(X(N)*SCALE+POSITIONX,250-(Y(N)*SCALE+POSITIONY))
130 NEXT N
140 END
' Alternatively try making plots of
,
,
where k=1,2,3.
Henon Map ( area preserving
)
Type: 2D Iteration Function Quadratic Map.
Equations:
Common values: Degrees:
, Radians:
Initial values:
Range:
and
, or
and
Figure 7.22 The Henon Map ( area
preserving ).
The Henon Map ( area preserving variety ) [10] was originally developed by Michael Henon and his colleagues in the early 1960's to model instabilities in found in stellar and planetary orbits. In their work they noticed that for some regions, stellar and planetary orbits follow a stable predictable path, while other regions appeared to exhibit random motion. At first this instability was ignored, considered simply to be attributed to unexplainable computational error. However upon further investigation it was determined that this turbulent behavior did indeed existent and were responsible for explaining why some regions seemed to be extremely sensitive to gravitational influences. These instabilities are found in the regional gaps of planetary rings, see Color Plate 64 for a look at Saturn's rings.
Figure 7.23 Henon
Mappings (area preserving) shown for different values of degees
.
Instabilities found in planetary orbits helped revolution celestial mechanics by questioning the predictability of motion that had remain relatively unchanged for centuries. Now instead of the certainty of periodic motion, events were shown to occur that could result in total unpredictable behavior, this behavior would later become to be known as chaos.
Mathematically
the Henon Map is an iteration function whose phase portraits produce orbits,
islands and chaotic sections. In these mappings, regions are determined
by a chosen angle
ranging from
to
along with initial values
at different regions.
One of the main functions of a Henon mapping is to simulate dynamical systems undergoing disturbances. In this system you will find island regions between bands that are formed when rational orbits absorb other neighboring orbits with nearly rational values. These islands in turn will break up again to form even smaller islands. This process carried on indefinitely results in the chaotic regions found in an island's surrounding areas.
' Basic Code for creating a Henon Map (area preserving ) plot.
'DEFAULT VALUES
Scale=100
PositionX=200:PositionY=100 ' Position X,Y moved
A= 1.37
' Radian values for A, for degree values use A*180/3.141593
' PROGRAM Henon Map (area preserving )
FOR N = -3 TO 3 STEP .1 ' Calculates different orbits starting
points
XN=N:YN=0 ' "XN=0:YN=N" can
also be used for plotting y-axis
GOSUB HenonMapping
NEXT N
END
HenonMapping:
FOR OrbitPoints = 1 TO 500 ' Number of points plotted per orbit
XN1 = XN*COS(A) - (YN-XN^2)*SIN(A)
' Iteration function
YN1 = XN*SIN(A) + (YN -XN^2)*COS(A)
XN = XN1 ' Relaces the new value for x
YN = YN1 ' Relaces the new value for y
IF XN < -10 OR XN >10
OR YN < -10
OR YN > 10 THEN YN=Y0:RETURN ' Safety to prevent PSET over flow
PSET(XN*Scale+PositionX,250-(YN*Scale+PositionY))
NEXT OrbitPoints
RETURN
In plotting the Henon Map variations in its orbits can be made where the number of points plotted per orbit is reduced (typically to 2-25) and the stepsize is significantly decreased. We called this pattern the Wahl Flower [11] after the flowery pattern it produces [Figure 7.24]. This technique gives a clear display of the amount of change a point undergoes in each region along with a distinct picture where transitional boundaries between order and chaos occur. The number of converging regions ( seen as ends of the flower petals) is equal to the number points used for each orbit.
Figure 7.24 The Wahl Flower produced with 5 and 20 orbit points respectively.
Wallpaper Patterns
of Mathematical Quilts;
Type: 2D Iteration Function.
Equations:
note: if
is positive then
, if
is negative then
.
Common values:
scale 100
scale 10
scale 1
Initial values:
and
Figure 7.25 Three Wallpaper patterns created from iteration functions.
Iteration functions do not always need to be confined to a region to produce detailed patterns. In fact in September 1986, Scientific American, published iterations of this type that they thought were decorative enough to put on their cover which they entitled 'Wallpaper For the Mind'. In this article various iterative equations are given that produced continuously growing patterns with varying geometric shapes. These patterns grow at vastly different scales ( even for the same equation containing different initial constant values ) with cycles that seem to fluctuate between apparent containment and explosive expansion. In Figure 7.25 we see a few of these patterns that .
' Basic Code for creating a 'Wallpaper' Patterns.
'DEFAULT VALUES
xn=0: yn=0 ' Initial value for x,y
a=.4:b=1:c=0 ' Constants: a,bc
Scale=150
xo=200:yo=150 ' Position X,Y moved
IterationNumber=5000
' PROGRAM Wallpaper Patterns
FOR I =1 TO IterationNumber
IF xn > 0 THEN signxn = 1 ELSE signxn = -1
x1 = yn - signxn*(ABS(b*xn-c))^.5 'Iteration
function
y1 = a
- xn
xn=x1
' Relaces the new value
for x
yn=y1 ' Relaces the new value for y
PSET(xo+xn*Scale,yo-(yn*Scale))
' Plots Scale, Position at xo,yo
NEXT I
END
Gingerbread man
Type: Strange Attractor (Chaotic Attractor) from iterations.
Equations:
Common values: none
Initial values:
,
The Gingerbread man is a contribution of Robert Devaney from Science of Fractal Images.
Pickover Attractor
Type: 3D Iteration Function.
Equations:
Common values:
.
Initial values:
,
,
.
The Pickover Attractor named after Clifford A. Pickover an IBM researcher popularized its modeling is an iterative function that resides in 3-dimensions. Its image bears a close resemblance to cotton candy
Bifurcation models
period double.
Population Growth
Model.
Type: Iteration Function.
Equation:
Parameter values:
, bifurcation examples
Initial value:
Figure 7.26 Population Growth models created from iteration functions showing
a chaotic plots.
The Population Growth Model has its origins from work done by Belgian mathematician Pierre Verhulst [12] (1804-1849) in which he describes models with restrictive growth. These models would in later decades be used by others to explain Gypsy Moth populations, Measles outbreaks, predator prey models [13] , along with various other models with contained growth. One main propose behind the Population Model is to pattern growth cycles found in real world systems, systems that can not continue unconstrained for an indefinite period. In nature eventually resources such as food or land get depleted to such an extent that competition for resources result in starvation or conflicts that reduces growth or even diminishes a population. These models have variations that fluctuate greatly, if they settle down to a single value we say that it is steady state system. If it splits into a transition of periodic points we say this process has bifurcated. When this bifurcation becomes indeterminable where the slightest change cause drastically different results, it is said to have reached its chaotic stage.
Logistic Map Equation:
shown in cycle form.
Type: Iteration Function.
Equation:
Parameter values:
, bifurcation examples
Initial value:
, range
Figure 7.27 Logistic map cycle shown in its periodic and chaotic form.
The Logistic Map is an iteration function used in showing transitional stages between ordered states and chaotic states. Here it is shown in its cyclic form, where loops bifurcate showing transitions between periodic behavior and chaotic behavior. It is important to see if a path will settle down to a single attraction point, periodic cycle or chaotic loop. This is done by letting roughly 10-25 iterations cycles occur to see which pattern is forming. Notice with this technique travel lines follow paths that are dictated by where they intersect the parabola curve and the slopped line segment.
'Code for Logistic Map Loop
'DEFAULT VALUES
IterationNumber =200
scale=250
xn=.2:xn1=0
' Starting values
m=3.7 ' 0-4 plot value regions
a1=0:b1=0:a2=0:b2=0
CALL MOVETO(5,135):PRINT
"µ=";m 'Prints value
for µ
'PROGRAM Plots the logistic map outline
FOR p = 0 TO 1 STEP .005
PSET(100+p*scale,270-p*scale)
PSET (100+p*scale,270-m*p*(1-p)*scale)
NEXT p
'PROGRAM Plots logistic map
FOR I = 1 TO IterationNumber
a1=xn1:b1=xn
xn1 = m*xn*(1-xn)
LINE(b1*scale+100,300-a1*scale-30)-(b2*scale+100,300-a2*scale-30)
a2=xn1:b2=xn
LINE(b1*scale+100,300-a1*scale-30)-(b2*scale+100,300-a2*scale-30)
xn=xn1
NEXT I
END
Logistic Map Equation: cascade form.
Type: Iteration Function.
Equation:
Parameter values:
Initial value:
, choose one value from the range
.
Figure 7.28 The Logistic Map magnified to reveal a self-similar structure.
Another way to display the transitional phases of the Logistic
equation is by plotting its full map. This is done by plotting continues
values for
against cyclic values of
[Figure 7.30]. The results form
a series cascades that exhibit a self-similar pattern.
The Predator Prey
Model
Type: Orbiting Attractor
Equations:
Parameter values:
Initial values:
,
,
.
Remarks: saddle point
, cycle center
.
Figure 7.29 The Predator-Prey model drawn for different starting values.
The Predator-Prey model is a phase space diagram that produces
cycles of growth and decay dependent on initial starting values, except
for the origin
where it produces a steady state
model. This procedure has been used in modeling interacting biological systems
, biological clocks and neural networks.
' Code for Predator-Prey model.
'DEFAULT VALUES
x=1:y=2
xo=150:yo=245
a=1:b=1:c=1:d=1
h=.01
Scale = 50
u1=x*Scale+xo:v1 = yo-y*Scale
' Prevents first line draw
' PROGRAM Predator-Prey
IterationNumber=1000
' Predator-Prey iterations
k1x = (a-b*y)*x
k1y = (c*x-d)*y
k2x = (a-b*(y+.5*h*k1y))*(x+.5*h*k1x)
k2y = (c*(x+.5*h*k1x)-d)*(y+.5*h*k1y)
k3x = (a-b*(y+.5*h*k2y))*(x+.5*h*k2x)
k3y = (c*(x+.5*h*k2x)-d)*(y+.5*h*k2y)
k4x = (a-b*(y+h*k3y))*(x+h*k3x)
k4y = (c*(x+h*k3x)-d)*(y+h*k3y)
y1=y+(1/6)*h*(k1y+2*k2y+2*k3y+k4y)
x1=x+(1/6)*h*(k1x+2*k2x+2*k3x+k4x)
FOR I =1 TO IterationNumber
xp=x+2*h*(a-b*y1)*x1
yp=y+2*h*(c*x1-d)*y1
xc=x1+.5*h*(a-b*(y1+yp))*(x1+xp)
yc=y1+.5*h*(c*(x1+xp)-d)*(y1+yp)
x2=.2*xp+.8*xc
y2=.2*yp+.8*yc
x=x1
y=y1
x1=x2
y1=y2
u=x*Scale+xo:v = yo-y*Scale
' Screen scale
PSET(u,v) 'Draws points
'LINE(u,v)-(u1,v1):v1 = v: u1 = u 'Draws lines
NEXT I
END
The Discrete Predator Prey Model
Type: 2D Difference Equation Orbiting Attractor
Equations:
Parameter values:
Initial values:
,
.
Original developed to monitor the correlation between the number of animals consumed as prey in relation to their offspring [14] .
Strange Attractors
in 3 Space or Greater.
The Lorenz Attractor
Type: Orbiting Strange Attractor
Equations:
Parameter values:
Initial value:
,
,
.
Figure 7.30 The Lorenz Attractor show in its 3 major planes.
In 1963, Edward Lorenz a professor at the Massachusetts Institute of Technology published a paper Deterministic Non-periodic Flow [15] in which he describes "the feasibility of very-long-range weather prediction'. In it he describes a self-sustaining modeling technique developed using three first order ordinary differential equations to simulate the complex behavior of atmospheric convections. Up to that time the only known attractors were fixed points, limit cycles, and tori. This work and its subsequent set of equations now known as the Lorenz attractor showed how small changes in a system can, over time produce intensely differing results. This chaotic behavior was observed by Lorenz when he tried to continue computer calculations from previously calculated data. In the experiment he entered a set of values that had occurred partway through a prior process. To his surprise he found that after a short period his graphs became distinctly different. After close examination he realized that the values he had enter were only accurate to three decimal places, whereas the computer had carried calculations out to six decimal places. This 1/1000th place discrepancy had caused the difference. His discovery went pretty much unnoticed for many years, due in part because the findings were published in an obscure weather journal not often read by those outside the field. It would not be until the following decade that these 'Strange Attractors' [16] or 'Chaotic Attractor' [17] would reemerge this time appearing in all types scientific fields becoming an integral part of scientific research.
Today, weather forecasters are using these principles of chaos theory to predict the accuracy of their weather models. Models that are computed with slight variations in their perimeter values so results can be compared to determine their predictability.
Lorenz's models were originally derived from other equations used in fluid dynamics which he adapted from one in modeling atmospheric conditions. Simplifying the Navier-Stokes theorem, a set of 5 partial differential equations used describe fluid flow, Lorenz made some reductions and came up with the equations here in their original form:
where
are constants and
is the fluid's Reynolds number found
in Lorenz's weather model.
common values:
.
For
values of
it results into a steady state solution
and if
then it results into a turbulent
flow. For values of
the strange attractor produces a
trajectory that traces out two spirals in a phase space that unpredictably
alternates direction called a funnel, it can be seen in its most common
form in the Rössler Attractor later in Figure 7.32.
' Code for Lorenz Attractor.
'DEFAULT VALUES
x=3:y=3:z=2
h=.02
xo=150:yo=150:zo=150
a=10:b=28:c=8/3
Scale=5
IterationNumber=2000
' Skips
drawing first line
u1=x*Scale+xo:v1 = yo-y*Scale
' x-y viewing plane
'u1 =z*Scale+zo:v1=x*Scale+xo
' x-z viewing plane
'u1 =z*Scale+zo:v1 = yo-y*Scale '
y-z viewing plane
'For rotation in the xyz-axis
plane set values GOSUB Rotation
degx=45
degy=45
degz=30
tx=degx*3.14159/180
ty=degy*3.14159/180
tz=degz*3.14159/180
' PROGRAM Runge-Kutta p-c
for the Lorentz Attractor
kx1=a*(y-x)*h
ky1=(x*(b-z)-y)*h
kz1=(x*y-c*z)*h
kx2=.5*a*(ky1-kx1)*h
ky2=.5*(kx1*(b-kz1)-ky1)*h
kz2=.5*(kx1*ky1-c*kz1)*h
kx3=.5*a*(ky2-kx2)*h
ky3=.5*(kx2*(b-kz2)-ky2)*h
kz3=.5*(kx2*ky2-c*kz2)*h
kx4=(a*(ky3-kx3))*h
ky4=(kx3*(b-kz3)-ky3)*h
kz4=(kx3*ky3-c*kz3)*h
x1=x+(1/6)*(kx1+kx2+kx3+kx4)
y1=y+(1/6)*(ky1+ky2+ky3+ky4)
z1=z+(1/6)*(kz1+kz2+kz3+kz4)
FOR I=1 TO IterationNumber
xp=x+2*a*(y1-x1)*h
yp=y+2*(x1*(b-z1)-y1)*h
zp=z+2*((x1*y1)-c*z1)*h
xc=x1+.5*a*(y1-x1+yp-xp)*h
yc=y1+.5*(x1*(b-z1)-y1+xp*(b-zp)-yp)*h
zc=z1+.5*((x1*y1)-c*z1+(xp*yp)-c*zp)*h
x2=.2*xp+.8*xc
y2=.2*yp+.8*yc
z2=.2*zp+.8*zc
x=x1
y=y1
z=z1
x1=x2
y1=y2
z1=z2
u=x*Scale+xo:v = yo-y*Scale
' x-y viewing plane
'u =z*Scale+zo:v=x*Scale+xo
' x-z viewing plane
'u =z*Scale+zo:v = yo-y*Scale
' y-z viewing plane
'GOSUB Rotation '
Set along with x-y viewing plane for u,v.
'PSET (u,v)
' Draws points
LINE(u,v)-(u1,v1) ' Draws lines
u1=u:v1=v ' Used
to connect lines
NEXT I
END
Rotation: ' Rotation subroutine
'X
xx1=x1
yy1=y1*COS(tx)-z1*SIN(tx)
zz1=y1*SIN(tx) + z1*COS(tx)
'Y
xxx1=zz1*SIN(ty)-xx1*COS(ty)
yyy1=yy1
zzz1=zz1*COS(ty)-xx1*SIN(ty)
'Z
xxxx1=xxx1*COS(tz)-yyy1*SIN(tz)
yyyy1=xxx1*SIN(tz) + yyy1*COS(tz)
zzzz1=zzz1
u=xxxx1*Scale+xo:v = yo-yyyy1*Scale
RETURN
The Rössler Attractor
Type: Orbiting Strange Attractor 3D Differential.
Equations:
Parameter values:
.
Initial values:
,
,
.
Figure 7.31 The Rössler Attractor show in its 3 major planes.
In 1976, a German medical doctor Otto Rössler generated a strange attractor considered to be the most simplistic chaotic system produced by coupling differential equations together. This attractor inspired by his desire to study chaos in biological systems is now known as the Rössler attractor. It takes a diverging field of paths and folds them over when they reach the outer region feeding them back into the attractor. This action results in a continuous orbit produced from the same set of equations. The Rössler attractor is given by the following three first order linear equations.
Figure 7.32 The Rössler Attractor with funnel generated for
.
'Code for the Rössler Attractor
'DEFAULT VALUES
x=3:y=3:z=2
h=.05
xo=150:yo=150:zo=150
a=.2:b=.2:c=5 'Try c=28 and h=.02 for funnel
Scale=5
IterationNumber=2000
'Skips drawing of first
line
u1=x*Scale+xo:v1 = yo-y*Scale
' x-y viewing plane
'u1 =z*Scale+zo:v1=x*Scale+xo
' x-z viewing plane
'u1 =z*Scale+zo:v1 = yo-y*Scale '
y-z viewing plane
degx=45
degy=45
degz=30
tx=degx*3.14159/180
ty=degy*3.14159/180
tz=degz*3.14159/180
' PROGRAM Runge-Kutta for
the Rössler Attractor
FOR I = 1 TO IterationNumber
kx1=(-y-z)*h
ky1=(x+a*y)*h
kz1=(b+z*(x-c))*h
kx2=(-(y+.5*ky1)-(z+.5*kz1))*h
ky2=((x+.5*kx1)+a*(y+.5*ky1))*h
kz2=(b+(z+.5*kz1)*((x+.5*kx1)-c))*h
kx3=(-(y+.5*ky2)-(z+.5*kz2))*h
ky3=((x+.5*kx2)+a*(y+.5*ky2))*h
kz3=(b+(z+.5*kz2)*((x+.5*kx2)-c))*h
kx4=(-(y+ky3)-(z+kz3))*h
ky4=((x+kx3)+a*(y+ky3))*h
kz4=(b+(z+kz3)*((x+kx3)-c))*h
x1=x+(1/6)*(kx1+kx2+kx3+kx4)
y1=y+(1/6)*(ky1+ky2+ky3+ky4)
z1=z+(1/6)*(kz1+kz2+kz3+kz4)
x=x1
y=y1
z=z1
u=x*Scale+xo:v = yo-y*Scale
'x-y viewing plane
'u =z*Scale+zo:v=x*Scale+xo
'x-z viewing plane
'u =z*Scale+zo:v = yo-y*Scale
'y-z viewing plane
'GOSUB Rotation ' Set along with x-y viewing plane for
u,v.
'PSET (u,v) 'Points
LINE(u,v)-(u1,v1) 'Lines
u1=u:v1=v
NEXT I
END
Rotation:
'X
xx1=x1
yy1=y1*COS(tx)-z1*SIN(tx)
zz1=y1*SIN(tx) + z1*COS(tx)
'Y
xxx1=zz1*SIN(ty)-xx1*COS(ty)
yyy1=yy1
zzz1=zz1*COS(ty)-xx1*SIN(ty)
'Z
xxxx1=xxx1*COS(tz)-yyy1*SIN(tz)
yyyy1=xxx1*SIN(tz) + yyy1*COS(tz)
zzzz1=zzz1
u=xxxx1*Scale+xo:v = yo-yyyy1*Scale
RETURN
Poincaré Cut
Different
mappings can be made using phase space. If you take a slice of the attractor's
cross section, say plot only values of
, you are left with something called a Poincaré section.
Power Spectrum
Further analysis can be made by taking a Fourier Transform of the points calculated points and then looking at how its spectrum is distributed. The more evenly a spectrum is distributed, the more evenly the chaos distribution.
Other 3D Strange Attractors you can create on your own [18] :
Silnikov Type Attractor
Type: Orbiting Strange Attractor 3D Differential.
Equations:
Parameter values:
or
.
Initial values:
,
,
.
Derived from L.P. Silnikov 1965 work " A case of the existence of a denumerable set of periodic motions," Sov. Math Dokl. 6 pages 163-166
Here are some more attractors that you can create on your own:
Tomita and Kai attractors
Glycolytic Oscillator.
where
but close to
.
Phoenix Curves
Mathematica™ (top)
Mathematica™ provides numerous techniques to explore mathematics. If you look through many of the popular books on Mathematica you will find large sections dedicated to fractal and chaos. Some of the most helpful books are Mathematica A System for Doing Mathematics, Exploring Mathematics with Mathematica and Mathematics in Action [19] . In the next section we will give a few examples of fractal models you can create using Mathematica.
Weierstrass Curve
The Weierstrass Curve and its generating code [20] made using Mathematica.
Weierstruass[t_,n_]:=Sum[Cos[6.2^k
* t ] ,{k,1,n}]
For[
k=1, k<=12, k++,
Plot[Weierstruass[t,k],
{t,0,Pi/2} ,
PlotPoints->600,PlotStyle->{Thickness[0]}]]
Figure 7.33 The Weierstrass Curve made with Mathematica.
Linear Trees
In Mathematica you can create self-similar fractals similar to the ones that we can make in FractaSketch. Here is an example of code to create a fractal tree [21] .
Needs["Geometry`Rotations`"]
tree[nestingDepth_, rotation_,
shrinkage_, thicknessShrinkage_,
originalThickness_,
options___] :=
Block[{twinLine, doTwins},
twinLine[Line[{start_, end_}]] :=
{
Line[{
end,
end
+ shrinkage Rotate2D[
end
- start,
rotation,
{0, 0}
]
}],
Line[{
end,
end
+ shrinkage Rotate2D[
end - start,
-rotation,
{0, 0}
]
}]
};
twinLine[Thickness[th_]] :=
Thickness[th thicknessShrinkage];
doTwins[lines_] :=
Flatten[Map[twinLine, lines]];
Show[
Graphics[
NestList[
doTwins,
{
Thickness[originalThickness],
Line[{{0,0}, {0,1}}]
},
nestingDepth
]
],
options,
AspectRatio -> Automatic,
PlotRange -> All
]
]
tree [6, 0.3,
0.8, 0.7, 0.05] ;
Figure 7.34 Fractal tree made with Mathematica.
Julia and Mandelbrot
Sets
In Mathematica you can create Mandelbrot Sets and Julia Sets similar to the ones you make in MandelMovie. Here are two examples along with the Mathematica code used in to generate them.
Julia Set
juliaSet = Compile [{x,
y, bound, cx, cy},
Module[{z, numbercount = 0},
z = x + I y;
While[Abs[z] < 2.0 && numbercount
<= bound,
z = z^2 + ( cx + I cy
);
++ numbercount];
numbercount]];
DensityPlot[-juliaSet[x,
y, 50, -0.12256, 0.74486],
{x, -2.0, 2.0}, {y, -2.0, 2.0},
PlotPoints-> 100, Mesh -> False];
Figure 7.35 Julia Set made with Mathematica.
Mandelbrot Set
mandelbrotSet = Compile
[{x, y, bound},
Module[{z, numbercount = 0},
z = x + I y;
While[Abs[z] < 2.0 && numbercount
<= bound,
z = z^2 + ( x + I y
);
++ numbercount];
numbercount]];
DensityPlot[-mandelbrotSet[x,
y, 50],
{x, -2.0, 1.5}, {y, -1.5, 1.5},
PlotPoints-> 100, Mesh -> False];
Figure 7.36 Mandelbrot Set made with Mathematica
These are just a of the few ways to create fractals, you can use other programing languages such as: Logo, HyperCard™, Prolog and Maple™ just to mention a few. Explore further, you never know what you will find.
[1] At a Macintosh conference in Berkeley I was demonstrating the program and was perplexed because the keyboard did not work. I finally found the cause of the problem: there was no cable connecting the keyboard to the Mac! It was just sitting there next to the computer. Luckily, I was able to do everything with the mouse.
[2] There is one more negative integer than positive. This is because the internal binary representation is 2's complement: One bit pattern is used to represent zero, which leaves an odd number of patterns to represent positive and negative integers. So there is at least one integer whose inverse is not represented.
[3] There is a simple rule called "casting out nines" to check the results of a calculation done by hand. It is an example of symbolic execution. Do you see why?
[4] The Elements of Geometrie of the most ancient Philosopher Euclide of Megara ( London 1570).
[5] Many of the programs and text covered in this section come from Chaos: Mathematics for the 21st Century , by Bernt Wahl, Dynamic Software (1987), Dynamics The Geometry of Behavior I,II,III,IV 1982, 1983, 1985, 1988 by Ralph Abraham and Christopher Shaw from Aerial Press, Inc. Santa Cruz, California ( now published by Addison Wesley Reading, Massachusetts) Computational Mathematics using Numerical Methods (1984) andThe Art of Chaos , (1987) , by Bernt Wahl thesis University of California at Santa Cruz.
[6] Fractal Programing in C by Roger Stevens (1989), M & T Publishing Redwood City, California and Numerical Recipes in C by William Press, Brian Flannery, Saul Teukolsky and William Vetterling (1988), University Press, Cambridge, England.
[7] Sometimes called phase portrait.
[8] Time period is given as .
[9] The Van der Pol oscilator also can be written with an added driving force , , if the force is changed to the first part of the coupled equations we get the Shaw atractor: , discovered by Robert Shaw (1980) that
[10] The Henon Map was partialy based on results developed by mathematicians A.N. Kolmogorov, V.I. Arnold and J. Moser from 1954 to 1963 to explore small instabilitis found in dynamical systems. Today this model is refered to as the KAM theorm.
[11] I amusingly named this technique 'the flower' in 1987 for the flowering pattern it produces in displaying paths of transions from order to chaos.
[12] Puplished two paper s in Memoies de l'Academie Royale de Belgique (1844 and 1847).
[13] Simple mathematical models with very complicated dynamica,Nature 261 (1976) Pages 459-467.
[14] Maynard Smith published in 1968.
[15] Journal of Atmospheric Sciences (1963)
[16] Strange Attractors first use On the nature of turbulence, Comm. Mathematical Physics 20 (1971) pages 343-344 by David Ruelle and Floris Takens.
[17] Choas first used Period 3 Implies Chaos,American Mathematical Monthly 82 (1985) pages 985-992 by Tien-Yien Li and James A.Yorke
[18] An added sorce of atractor equations is PHASER: An Animator/Simulator for Dynamical Systems, by Huseyin Kocak [1985] a book on dynamical models that run under DOS.
[19] Mathematica A System for Doing Mathematics by Stephen Wolfram (1991) Addison Wesley, Exploring Mathematics with Mathematica by Theodore W. Gray and Jerry Glynn (1991) Addison Wesley and Mathematics in Action by Stan Wagon W. H. Freeman and Company.
[20] From work by Carl Hansen.
[21]
From Exploring Mathematics with Mathematica pages 78, 84-85.