# mandelbrot-prelude
A Haskell library for exploring the Mandelbrot set. Works with GHC (fast) and Hugs (low resource needs). Patching some Hugs bugs is necessary for non-trivial use, see my Hugs page for details.
Still under development, no Hackage release yet.
# 1 Download
git clone https://code.mathr.co.uk/mandelbrot-graphics.git
git clone https://code.mathr.co.uk/mandelbrot-numerics.git
git clone https://code.mathr.co.uk/mandelbrot-symbolics.git
git clone https://code.mathr.co.uk/mandelbrot-text.git
git clone https://code.mathr.co.uk/mandelbrot-prelude.git
# 2 GHC
cd mandelbrot-prelude
cabal repl --ghc-options "-interactive-print pprint"
# 3 Hugs
mandelbrot-prelude/hugs.sh
# 4 API
For full list, type :browse in Hugs or GHCi.
# 4.1 Conventions
foo_ returns a list(-like) thing.
foo' returns a Maybe.
foo returns the value, but may call error.
Omitted contexts :: ... => can be instantiated with Double.
Future work will add instances for arbitrary precision types
(e.g. using the rounded package).
# 4.2 putImage
putImage renders an image of
the boundary of the Mandelbrot set in black,
with the background in white.
The arguments are
the center of the view,
the vertical radius of the view, and
the maximum iteration count.
> :t putImage
putImage :: Complex Double -> Double -> Int -> IO ()
> putImage (-0.75) 1.25 100
(unicode block graphics image is printed to terminal)
(it shows the whole Mandelbrot set centered in the frame)
-0.75 + 0.0 i @ 1.250e0 (100 iterations)

> putImage (0:+1) 0.0001 1000
(unicode block graphics image is printed to terminal)
(it shows a branching filament in the right hand half of the frame)
0.0 + 1.0 i @ 1.000e-4 (1000 iterations)

Video recorded using OBS of Hugs on an old Atom netbook (modern machines using GHC are significantly faster):
# 4.3 Pretty-printing
Values can be formatted to strings in Plain, HTML or LaTeX formats.
The function pprint pretty-prints in Plain format.
It can be used as default print hook in GHCI.
# 4.4 Parsing
Values can be parsed from strings in Plain format using parse and parse'.
To avoid the need for type annotations, many helpers are defined:
pAngledAddress :: String -> AngledAddress
pAngledAddress' :: String -> Maybe AngledAddress
pBinaryAngle :: String -> BinaryAngle
pBinaryAngle' :: String -> Maybe BinaryAngle
pBlock :: String -> Block
pBlock' :: String -> Maybe Block
pExternalAngle :: String -> ExternalAngle
pExternalAngle' :: String -> Maybe ExternalAngle
pInternalAddress :: String -> InternalAddress
pInternalAddress' :: String -> Maybe InternalAddress
pInternalAngle :: String -> InternalAngle
pInternalAngle' :: String -> Maybe InternalAngle
pKneading :: String -> Kneading
pKneading' :: String -> Maybe Kneading
# 4.5 Periods
Hyperbolic components have an associated period.
Misiurewicz points have a preperiod and period.
Periods and preperiods are represented with the Int type.
mandelbrot-numerics can find a period for hyperbolic component given a view:
boxPeriod :: ... => Complex r -> r -> Int
boxPeriod c r -- returns a candidate best period for a region of size r around c
There are also boxPeriod_ and boxPeriod'.
mandelbrot-symbolics has a class Period for things with (pre)periods.
class Period a where
preperiod :: a -> Int
period :: a -> Int
periods :: a -> (Int,Int)
-- specify a limit to avoid potentially long running time
safePeriods :: Int -> a -> Maybe (Int,Int)
instance Period Periods
instance Period ExternalAngle
instance Period BinaryAngle
instance Period Kneading
instance Period InternalAddress
instance Period AngledAddress
mandelbrot-text has a newtype Periods for parsing and pretty-printing.
newtype Periods = Periods (Int, Int)
instance Period Periods
instance Eq Periods
instance Read Periods
instance Show Periods
instance Plain Periods
instance HTML Periods
instance LaTeX Periods
instance Parse Periods
# 4.6 Angles
Fractions between 0 and 1 are used to represent angles, measured in turns. Internal angles are related to the way circles are connected. External angles are related to the way the Mandelbrot set is embedded in the plane.
In mandelbrot-symbolics there is a class Q for rational numbers.
The types InternalAngle and ExternalAngle are instances of it.
However mandelbrot-numerics requires the standard Haskell Prelude.Rational.
You can convert between them using fromQ and toQ.
# 4.7 Parents and children
Found in mandelbrot-numerics, operations on adjacent circles and cardioids:
data Child r = Child Int (Complex r) -- period and center
Find child at internal angle t from parent with period p and center c:
child :: ... => Int -> Complex r -> Rational -> Child r
child p c t
Find a chain of descendants:
children :: ... => Int -> Complex r -> [Rational] -> [Child r]
The operation can work in reverse to find a chain of parents to the nearest cardioid:
data Parent r
= NoParent (Complex r) -- cusp
| Parent (Complex r) Rational Int (Complex r) -- bondpoint angle period nucleus
parent :: ... => Int -> Complex r -> Parent r
parents :: ... => Int -> Complex r -> [Parent r]
Examples:
> mapM_ print $ children 1 0 [1 % 2, 2 % 3, 3 % 4]
Child 2 ((-1.0) :+ 0.0)
Child 6 ((-1.13800066665096) :+ (-0.240332401262098))
Child 24 ((-1.11384727389476) :+ (-0.253483319236841))
> mapM_ print $ parents 24 ((-1.11384727389476) :+ (-0.253483319236841))
Parent ((-1.11523274434712) :+ (-0.252762839726454)) (3 % 4) 6 ((-1.13800066665096) :+ (-0.240332401262098))
Parent ((-1.125) :+ (-0.21650635094611)) (2 % 3) 2 ((-1.0) :+ 0.0)
Parent ((-0.75) :+ 1.22464679914735e-16) (1 % 2) 1 (0.0 :+ 0.0)
NoParent (0.25 :+ 0.0)
# 4.8 Angled internal addresses
Angled internal addresses identify hyperbolic components via chain of wakes. They consist of a list of periods (strictly increasing) separated by internal angles. Not all angled internal addresses are realizable in the Mandelbrot set.
As the Plain format for AngledAddress is awkward, there is ConciseAddress:
newtype ConciseAddress = ConciseAddress AngledAddress
unConciseAddress :: ConciseAddress -> AngledAddress
pConciseAddress :: String -> ConciseAddress
pConciseAddress' :: String -> Maybe ConciseAddress
pAddress :: String -> AngledAddress
pAddress' :: String -> Maybe AngledAddress
The format can omit many angles that are 1/2,
and omits periods resulting from multiplying by a denominator.
Example:
> pprint $ pAddress "1 2 3 1/3 1/2 10"
1_1/2->2_1/2->3_1/3->9_1/2->10
Angled internal addresses can be converted to and from external angles:
> pprint . addressAngles . pAddress $ "1 2 3 1/3 1/2 10"
(147/341,454/1023)
> pprint . angledAddress $ 147%341
1_1/2->2_1/2->3_1/3->9_1/2->10
There are ' versions too that return Maybe.
A final list of angles can be split from an address; it’s more efficient to trace external rays to a cardioid, then find child circles, instead of trying to trace external rays to a circle directly:
splitAddress :: AngledAddress -> (AngledAddress,[InternalAngle])
joinAddress :: AngledAddress -> [InternalAngle] -> AngledAddress
# 4.9 Tracing external rays
The ExternalAngle type is rational, while the BinaryAngle type is binary.
Convert between them with binary and rational.
Convert between pairs of them with binary2 and rational2.
External angles parameterize external rays, which can be traced from a large radius in towards the Mandelbrot set.
Example:
> mapM_ print . Prelude.take 32 . exRayIn 4 $ 4 % 15
(-6850.37736870893) :+ 65176.9869348552
(-2728.76499994794) :+ 25962.4647194314
(-541.063590680585) :+ 5147.8761946454
(-138.782724720925) :+ 1320.42942294493
(-44.2016895430127) :+ 420.550983804623
(-17.3889414599879) :+ 160.686005466489
(-8.02059022579482) :+ 71.5501804463158
(-4.30913074098224) :+ 36.2346340462539
(-2.65013204939833) :+ 20.4450457240978
(-1.82816775622328) :+ 12.6314128320829
(-1.38470278904755) :+ 8.42044008568637
(-1.12625861951356) :+ 5.98178473080862
(-0.964603724826411) :+ 4.48116235377119
(-0.856231753402985) :+ 3.50928708770253
(-0.778415830964795) :+ 2.85229235706047
(-0.71865723088116) :+ 2.39207139296338
(-0.669882666942511) :+ 2.06017448966566
(-0.628008155192409) :+ 1.81515633774386
(-0.590669186732474) :+ 1.63091221534286
(-0.556508906230607) :+ 1.49037348604655
(-0.524768699196137) :+ 1.38199595148228
(-0.495036538047215) :+ 1.29772369994397
(-0.467087475030156) :+ 1.23178289574069
(-0.440795709626534) :+ 1.17994312560235
(-0.41608633484741) :+ 1.13905153972917
(-0.392914387154241) :+ 1.10672250334482
(-0.371254651889233) :+ 1.08112142550428
(-0.351089275547205) :+ 1.0608145242068
(-0.332395858352319) :+ 1.04466649002643
(-0.315136877495458) :+ 1.03177540855617
(-0.299255931398186) :+ 1.02143127779518
(-0.284678961596266) :+ 1.01308705860628
The first parameter is the sharpness: how many steps to take within each dwell band.
The “end” of the ray can be used as
an initial guess for Newton’s method in
the nucleus or misiurewicz functions.
Usually going twice the total of the preperiod and period,
multiplied by the sharpness, steps is enough:
but this is not proven to be safe always.
Example:
> nucleus 4 . (!! (4 * 2 * 4)) . exRayIn 4 $ 4 % 15
(-0.156520166833755) :+ 1.03224710892283
> misiurewicz 3 1 . (!! (4 * 2 * 4)) . exRayIn 4 $ 1 % 4
(-0.228155493653962) :+ 1.11514250803994
Note: the preperiod 3 of the Misiurewicz point is
one more than the preperiod 2 of the angle 1 % 4:
> periods (1 % 4 :: ExternalAngle)
(2,1)
# 4.10 Enumerating components
The easiest way to enumerate components of period p is via external angles.
Try all numerators with denominator 2^p - 1. Ensure the period of the angle is p.
Two rays land on each component’s root,
so to avoid listing each component twice,
convert to angled internal address,
convert back to a pair of external angles,
and check our angle is the lower angle.
Then trace the external ray and find the nucleus.
Given the period and nucleus, the size can be calculated.
Finally render the locations.
Example: save as p5.hs, run with mandelbrot-prelude/hugs.sh p5.hs:
import Mandelbrot.Prelude
main = sequence_
[ putImage c r 1000 >>
pprint a >>
pprint (binary lo) >>
pprint (binary hi) >>
putStrLn ""
| let p = 5
, let d = 2^p - 1
, n <- [ 1 .. d - 1 ]
, let t = n % d :: ExternalAngle
, period t == p
, let a = angledAddress t
, let (lo, hi) = addressAngles a
, t == lo
, let sharpness = 8
, let e = exRayIn sharpness (fromQ t) !! (2 * sharpness * p)
, let c = nucleus p e
, let r = 2 * magnitude (size p c)
]