roots-0.1: src/Math/Root/Finder/Dekker.hs
{-# 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