ruff-0.2: Fractal/RUFF/Mandelbrot/Nucleus.hs
{-# LANGUAGE BangPatterns #-}
{- |
Module : Fractal.RUFF.Mandelbrot.Nucleus
Copyright : (c) Claude Heiland-Allen 2011
License : BSD3
Maintainer : claudiusmaximus@goto10.org
Stability : unstable
Portability : portable
Mu-atom period, nucleus and bond point finding.
-}
module Fractal.RUFF.Mandelbrot.Nucleus (findPeriod, findNucleus, findBond, findInternal) where
import Data.List (genericIndex)
import Data.Maybe (listToMaybe)
import Fractal.RUFF.Types.Complex (Complex((:+)), mkPolar, magnitude2)
-- | Given the period and approximate location, successively refine
-- this estimate to a nucleus.
--
-- The algorithm is based on Robert Munafo's page
-- /Newton-Raphson method/
-- <http://mrob.com/pub/muency/newtonraphsonmethod.html>.
--
findNucleus :: (Floating r, Fractional r) => Integer {- ^ period -} -> Complex r {- ^ estimate -} -> [Complex r]
findNucleus p g = iterate go g
where
go !c =
let step (!z, !d) = (z * z + c, 2 * z * d + 1)
(zn, dn) = iterate step (0, 0) `genericIndex` p
in c - zn / dn
-- | Given the period and nucleus, find succesive refinements to the
-- bond point at a given internal angle.
--
-- The algorithm is based on ideas from
-- <http://mrob.com/pub/muency/derivative.html>.
--
findBond :: (Floating r, Fractional r) => Integer {- ^ period -} -> Complex r {- ^ nucleus -} -> r {- ^ angle -} -> [Complex r]
findBond p c0 a0 = findInternal p c0 1 a0
-- | Given the period and nucleus, find an interior point at a given internal
-- angle and radius in (0,1].
--
findInternal :: (Floating r, Fractional r) => Integer {- ^ period -} -> Complex r {- ^ nucleus -} -> r {- ^ radius -} -> r {- ^ angle -} -> [Complex r]
findInternal p c0 r0 a0 = snd `map` iterate go (c0, c0)
where
b0 = mkPolar r0 (2 * pi * a0)
go (!z1, !c1) =
let step (!a, !b, !c, !d, !e) =
( a * a + c1
, 2 * a * b
, 2 * (b * b + a * c)
, 2 * a * d + 1
, 2 * (a * e + b * d)
)
(an, bn, cn, dn, en) = iterate step (z1, 1, 0, 0, 0) `genericIndex` p
y0 = z1 - an
y1 = b0 - bn
bn1 = bn - 1
m = bn1 * en - dn * cn
d0 = (y0 * en - dn * y1) / m
d1 = (bn1 * y1 - y0 * cn) / m
in (z1 + d0, c1 + d1)
-- | Find the period of the lowest period nucleus inside a square.
--
-- The algorithm is based on Robert Munafo's page,
-- /Finding the Period of a mu-Atom/
-- <http://mrob.com/pub/muency/period.html>.
--
findPeriod :: (Floating r, Ord r) => Integer {- ^ maximum period -} -> r {- ^ radius -} -> Complex r {- ^ center -} -> Maybe Integer
findPeriod m r c =
let cs = [ c + (r:+r), c + (r:+(-r)), c + ((-r):+(-r)), c + ((-r):+r) ]
zs = iterate (zipWith (\cc z -> z * z + cc) cs) [0,0,0,0]
-- kludge = if r > 0 then 1 else 2 -- fixes space leak from long literal list (CAF?)
in fmap fst . listToMaybe . dropWhile (not . straddlesOrigin . snd) . takeWhile (all ((< 65536) . magnitude2) . snd) . zip [{-kludge + 0 - kludge-} 0 .. m ] $ zs
straddlesOrigin :: (Ord r, Num r) => [Complex r] -> Bool
straddlesOrigin ps = odd . length . filter id . zipWith positiveReal ps $ (drop 1 ps ++ take 1 ps)
positiveReal :: (Ord r, Num r) => Complex r -> Complex r -> Bool
positiveReal (u:+v) (x:+y)
| v < 0 && y < 0 = False
| v > 0 && y > 0 = False
| (u * (y - v) - v * (x - u)) * (y - v) > 0 = True
| otherwise = False