packages feed

gruff-0.2: src/MuAtom.hs

{-# LANGUAGE BangPatterns #-}
module MuAtom(MuAtom(..), MuProgress(..), muFromAddress, MuProgress'(..), muToAddress, MuProgress''(..), muLocate) where

import Control.Arrow ((***))
import Data.Ratio ((%))
import Data.Vec (NearZero, nearZero)

import Fractal.RUFF.Mandelbrot.Address (AngledInternalAddress, Angle, splitAddress, addressPeriod, externalAngles, angledInternalAddress)
import Fractal.RUFF.Mandelbrot.Nucleus (findNucleus, findBond, findPeriod)
import Fractal.RUFF.Mandelbrot.Ray (externalRay, externalRayOut)
import Fractal.RUFF.Types.Complex (Complex, magnitude, magnitude2, phase, mkPolar)

import Number (R)

data MuAtom = MuAtom
  { muNucleus :: !(Complex R)
  , muSize    :: !Double
  , muOrient  :: !Double
  , muPeriod  :: !Integer
  }
  deriving (Read, Show, Eq)

data MuProgress
  = MuSplitTodo
  | MuSplitDone AngledInternalAddress [Angle]
  | MuAnglesTodo
  | MuAnglesDone !Rational !Rational
  | MuRayTodo
  | MuRay !Integer
  | MuRayDone !(Complex R)
  | MuNucleusTodo
  | MuNucleus !Integer
  | MuNucleusDone !(Complex R)
  | MuBondTodo
  | MuBond !Integer
  | MuBondDone !(Complex R)
  | MuSuccess !MuAtom
  | MuFailed
  deriving (Read, Show, Eq)

muFromAddress :: AngledInternalAddress -> [MuProgress]
muFromAddress addr = MuSplitTodo :
  let (!iaddr, !caddr) = splitAddress addr
      !p = addressPeriod iaddr
  in  MuSplitDone iaddr caddr : MuAnglesTodo : case externalAngles iaddr of
    Nothing -> [MuFailed]
    Just (!lo, !hi) -> MuAnglesDone lo hi : MuRayTodo :
      let sharpness = 8
          er = 65536
          accuracy = 1e-10
          ok w = magnitude2 w < 2 * er ^ (2::Int) -- NaN -> False
          rayl = externalRay accuracy sharpness er lo
          rayh = externalRay accuracy sharpness er hi
          ray' = takeWhile (uncurry (&&) . (ok *** ok) . snd) $ [ 1 .. ] `zip` (rayl `zip` rayh)
          rgo []  _ = [MuFailed]
          rgo [_] _ = [MuFailed]
          rgo ((i, (xl, xh)):m@((_, (yl, yh)):_)) f
            | i < fromIntegral sharpness * (p + 16) = MuRay i : rgo m f
            | dl + dh > dx + dy = MuRay i : rgo m f
            | otherwise = MuRayDone x : f x
            where
              x  = 0.5 * (xl + xh)
              dl = magnitude2 (xl - yl)
              dh = magnitude2 (xh - yh)
              dx = magnitude2 (xl - xh)
              dy = magnitude2 (yl - yh)
      in  rgo ray' $ \rayend -> MuNucleusTodo :
        let nuc = findNucleus p rayend
            nuc' = takeWhile (ok . snd) $ [ 1 .. ] `zip` nuc
            ngo []  _ = [MuFailed]
            ngo [_] _ = [MuFailed]
            ngo ((i, x):m@((_, y):_)) f
              | not (nearZero (x - y)) = MuNucleus i : ngo m f
              | otherwise = MuNucleusDone x : f x
        in  ngo nuc' $ \nucleus -> MuBondTodo :
          let bnd = findBond p nucleus 0.5
              bnd' = takeWhile (ok . snd) $ [ 1.. ] `zip` bnd
              bgo []  _ = [MuFailed]
              bgo [_] _ = [MuFailed]
              bgo ((i, x):m@((_, y):_)) f
                | not (nearZero (x - y)) = MuBond i : bgo m f
                | otherwise = MuBondDone x : f x
          in  bgo bnd' $ \bond ->
            let delta  = bond - nucleus
                size   = realToFrac $ magnitude delta / 0.75
                orient = realToFrac $ phase delta
                atom   = MuAtom{ muNucleus = nucleus, muSize = size, muOrient = orient, muPeriod = p }
            in  if 10 > size && size > 0 then [MuSuccess atom] else [MuFailed]

data MuProgress'
  = MuCuspTodo
  | MuCuspDone !(Complex R)
  | MuDwellTodo
  | MuDwell !Integer
  | MuDwellDone !Integer
  | MuRayOutTodo
  | MuRayOut !Double
  | MuRayOutDone !(Complex R)
  | MuExternalTodo
  | MuExternalDone !Rational
  | MuAddressTodo
  | MuSuccess' AngledInternalAddress
  | MuFailed'
  deriving (Read, Show, Eq)

muToAddress :: MuAtom -> [MuProgress']
muToAddress mu = MuCuspTodo :
  let cusp = muNucleus mu - mkPolar (realToFrac (muSize mu)) (realToFrac ((muOrient mu)))
      er = 65536
      er2 = er * er
  in  MuCuspDone cusp : MuDwellTodo :
    let dgo z n f = MuDwell n : if magnitude2 z > er2 then f n else dgo (z * z + cusp) (n + 1) f
    in  dgo 0 0 $ \n -> MuDwellDone n : MuRayOutTodo :
      let rgo ((i,!_):izs@(_:_)) f = MuRayOut (fromIntegral i / (fromIntegral sharpness * fromIntegral n)) : rgo izs f
          rgo [(_,!z)] f | magnitude2 z > er2 = MuRayOutDone z : f z
          rgo _ _ = [MuFailed']
          accuracy = 1e-16
          sharpness = 16
          epsilon0 = realToFrac (muSize mu) * accuracy
      in  rgo ([(1 :: Integer) ..] `zip` externalRayOut (fromIntegral n + 100) epsilon0 accuracy sharpness er cusp) $ \rend -> MuExternalTodo :
        let den = 2 ^ muPeriod mu - 1
            num' = fromIntegral den * warp (phase rend / (2 * pi))
            num = round num'
            warp t
              | t > 0 = t
              | otherwise = t + 1
            angle = num % den
        in  MuExternalDone angle : MuAddressTodo : case angledInternalAddress angle of
              Nothing -> [MuFailed']
              Just addr -> if addressPeriod addr /= muPeriod mu then [MuFailed'] else [MuSuccess' addr]

data MuProgress''
  = MuScanTodo
  | MuScan
  | MuScanDone !Integer
  | MuNucleusTodo'
  | MuNucleus' !Integer
  | MuNucleusDone' !(Complex R)
  | MuBondTodo'
  | MuBond' !Integer
  | MuBondDone' !(Complex R)
  | MuSuccess'' !MuAtom
  | MuFailed''
  deriving (Read, Show, Eq)

muLocate :: Complex R -> Double -> [MuProgress'']
muLocate c r = MuScanTodo : MuScan : case findPeriod 10000000 (realToFrac r) c of
  Nothing -> [MuFailed'']
  Just p -> MuScanDone p : MuNucleusTodo' :
    let ok w = magnitude2 w < 16 -- NaN -> False
        nuc = findNucleus p c
        nuc' = takeWhile (ok . snd) $ [ 1 .. ] `zip` nuc
        ngo []  _ = [MuFailed'']
        ngo [_] _ = [MuFailed'']
        ngo ((i, x):m@((_, y):_)) f
          | not (nearZero (x - y)) = MuNucleus' i : ngo m f
          | otherwise = MuNucleusDone' x : f x
    in  ngo nuc' $ \nucleus -> MuBondTodo' :
      let bnd = findBond p nucleus 0.5
          bnd' = takeWhile (ok . snd) $ [ 1.. ] `zip` bnd
          bgo []  _ = [MuFailed'']
          bgo [_] _ = [MuFailed'']
          bgo ((i, x):m@((_, y):_)) f
            | not (nearZero (x - y)) = MuBond' i : bgo m f
            | otherwise = MuBondDone' x : f x
      in  bgo bnd' $ \bond ->
        let delta  = bond - nucleus
            size   = realToFrac $ magnitude delta / 0.75
            orient = realToFrac $ phase delta
            atom   = MuAtom{ muNucleus = nucleus, muSize = size, muOrient = orient, muPeriod = p }
        in  if 10 > size && size > 0 then [MuSuccess'' atom] else [MuFailed'']