{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-}
module Math.Root.Finder.Dekker (Dekker, dekker) where

import Math.Root.Finder

-- fields: a fa b fb oldB oldFb
-- invariants: 
--  1) signum fA /= signum fB
--  2) abs fB <= abs fA
--  3) (oldB-a)*(oldB-b) >= 0
data Dekker a b = Dekker !a !b !a !b !a !b  deriving (Eq, Show)

-- |@dekker f x1 x2 xacc@:  attempt to find a root of a function known to 
-- lie between x1 and x2, using Dekker's method.  The root will be refined
-- till its accuracy is +-xacc.  If convergence fails, returns the final
-- state of the search.
dekker :: RealFloat a => (a -> a) -> a -> a -> a -> Either (Dekker a a) a
dekker f x1 x2 xacc = fmap estimateRoot (findRoot f x1 x2 xacc)

instance (Fractional a, Ord a, Real b, Fractional b, Ord b) => RootFinder Dekker a b where
    initRootFinder f x0 x1 
        | signum f0 == signum f1    = error "initRootFinder/Dekker: starting points do not (obviously) bracket a root"
        | abs f0 <= abs f1          = Dekker x1 f1 x0 f0 x1 f1
        | otherwise                 = Dekker x0 f0 x1 f1 x0 f0
        where f0 = f x0; f1 = f x1
    
    stepRootFinder f orig@(Dekker a _ b fb oldB oldFb)
        | fb == 0               = orig
        |  oldFb /= fb 
        && s `between` (a,b)    = step s (f s) orig
        | otherwise             = step m (f m) orig
        where
            s = b - (b * oldB) * realToFrac (fb / (fb - oldFb))
            m = 0.5 * (a + b)
    
    estimateRoot  (Dekker _ _ b _ _ _) = b
    estimateError (Dekker a _ b _ _ _) = a - b

between :: Ord a => a -> (a,a) -> Bool
a `between` (x,y) = (a > min x y) && (a < max x y)

-- |Incorporates a new point, maintaining invariant 1, assuming invariant 3,
-- and using 'accept' to restore invariant 2.
step :: (Num b, Ord b) => a -> b -> Dekker a b -> Dekker a b
step x fx orig@(Dekker a fa b fb _ _)
    | signum fx /= signum fa    = accept a fa x fx orig
    | otherwise                 = accept x fx b fb orig

-- |Re-establishes invariant 2 (abs fb <= abs fa) without affecting invariants 1 and 3.
accept :: (Num b, Ord b) => a -> b -> a -> b -> Dekker a b -> Dekker a b
accept a fa b fb (Dekker _ _ oldB oldFb _ _)
    | abs fb <= abs fa          = Dekker a fa b fb oldB oldFb
    | otherwise                 = Dekker b fb a fa oldB oldFb