packages feed

gmndl-0.4.0.2: MuAtom.hs

{-# LANGUAGE BangPatterns, RecordWildCards, Rank2Types, FlexibleContexts #-}

{-

    gmndl -- Mandelbrot Set explorer
    Copyright (C) 2010,2011,2014  Claude Heiland-Allen <claude@mathr.co.uk>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

-}

module MuAtom (muAtom, refineNucleus) where

import Data.Ratio (numerator, denominator)
import Data.List (genericIndex, genericSplitAt)
import Data.Vec (NearZero())
import Numeric.QD (QuadDouble())
import Roots (root2, root4, lift, FF)
import Complex (Complex((:+)), magnitude, phase, cis, mkPolar, normalize, Turbo)

type N = Integer
type Q = Rational
type R = QuadDouble
type C = Complex R

-- a Mandelbrot Set mu-atom

data Atom = Atom{ nucleus :: !C, period :: !N, root :: !C, cardioid :: !Bool }
  deriving Show

continent :: Atom
continent = Atom 0 1 1 True

-- finding bond points

fdf :: (Integral i, Num c) => i -> c -> c -> (c, c)
fdf !n !z !c = let (fzs, fz:_) = genericSplitAt n $ iterate (\w -> w * w + c) z
               in  (fz, 2 ^ n * product fzs)  -- [ f i z c | i <- [0 .. n - 1] ]

bondIter :: (Real r, Floating r) => Integer -> Complex r -> FF [] [] r
bondIter !n !(br:+bi) [x0, x1, x2, x3] =
  let !z = x0:+x1
      !c = x2:+x3
      !b = lift br :+ lift bi
      (!fz, !dfz) = fdf n z c
      !(y0 :+ y1) =  fz - z -- f n z c - z
      !(y2 :+ y3) = dfz - b -- df n z c - (lift br :+ lift bi)
  in  [y0, y1, y2, y3]
bondIter _ _ _ = error "MuAtom.bondIter: internal error"

-- finding nucleus

l :: (Integral i, Num c) => i -> c -> c
l !n !c = (`genericIndex` n) . iterate (\z -> z * z + c) $ 0

nucleusIter :: (Real r, Floating r) => Integer -> FF [] [] r
nucleusIter !n [x0, x1] =
  let !c = x0 :+ x1
      !(y0 :+ y1) = l n c
  in  [y0, y1]
nucleusIter _ _ = error "MuAtom.nucleusIter: internal error"


refineNucleus :: (NearZero r, Real r, Floating r, Turbo r) => Integer -> Complex r -> (r, r, r)
refineNucleus p root@(gr :+ gi) =
  let eps = 1e-20 -- FIXME
      [cr, ci] = root2 eps (nucleusIter p) [gr, gi]
      [_, _, b0r, b0i] = root4 eps (bondIter p ( 1)) [cr, ci, cr, ci]
      [_, _, b1r, b1i] = root4 eps (bondIter p (-1)) [cr, ci, cr, ci]
      bond0 = b0r :+ b0i
      bond1 = b1r :+ b1i
      r = magnitude (bond1 - bond0)
  in  (cr, ci, r)

-- finding descendants

muChild :: Atom -> Q -> Atom
muChild !Atom{..} !address =
  let -- some properties of the parent and its relation to the child
      !size = magnitude (root - nucleus)
      !address' = fromIntegral (numerator address) / fromIntegral (denominator address)
      !angle = 2 * pi * address'
      !(bar :+ bai) = cis angle
      !bondAngle = bar :+ bai
      !child = period * denominator address
      -- perturb from the stable nucleus to help ensure convergence to the bond point
      !_initial@(ir :+ ii) = nucleus + mkPolar (size / 2) (phase (root - nucleus) + angle)
      [_, _, br, bi] = {-# SCC "bond" #-} root4 eps (bondIter period bondAngle) [ ir, ii, ir, ii ]
      !bondPoint = br :+ bi
      -- estimate where the nucleus will be
      !radiusEstimate
        | cardioid  = size / m2 * sin (pi * address')
        | otherwise = size / m2
        where m2 = fromIntegral (denominator address) ^ (2 :: N)
      !deltaEstimate = bondPoint - nucleus
      !_guess@(gr :+ gi) = bondPoint + (radiusEstimate :+ 0) * normalize deltaEstimate
      -- refine the nucleus estimate
      [cr, ci] = {-# SCC "nucleus" #-} root2 eps (nucleusIter child) [gr, gi]
      !childNucleus = cr :+ ci
      eps = radiusEstimate / 10000000
  in  Atom childNucleus child bondPoint False

muChildren :: Atom -> [Q] -> [Atom]
muChildren !a [] = [a]
muChildren !a (q:qs) = let b = muChild a q in a : muChildren b qs

-- interface to the outside world

muAtom :: [Q] -> (R, R, R, N)
muAtom qs =
  let Atom{..} = last $ muChildren continent qs
      r :+ i = nucleus
      s = magnitude (nucleus - root)
      p = period
  in (r, i, s, p)