{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-}
module Math.Root.Finder.Bisection
    ( Bisect, bisection
    ) where

import Math.Root.Finder

-- |Bisect an interval in search of a root.  At all times, @f (estimateRoot _)@
-- is less than or equal to 0 and @f (estimateRoot _ + estimateError _)@ is 
-- greater than or equal to 0.
data Bisect a b = Bisect { _bisX :: !a, _bisF :: !b , _bisDX :: !a }
    deriving (Eq, Ord, Show)

instance (Fractional a, Eq a, Ord b, Num b) => RootFinder Bisect a b where
    initRootFinder f x1 x2
        | f1 < 0    = Bisect x1 f1 (x2-x1)
        | otherwise = Bisect x2 f2 (x1-x2)
        where f1 = f x1; f2 = f x2
    
    stepRootFinder _ orig@(Bisect _ _ 0) = orig
    stepRootFinder f orig@(Bisect x fx dx)
        | x == xMid = orig
        | otherwise = case fMid `compare` 0 of
            LT ->  Bisect xMid fMid dx2
            EQ ->  Bisect xMid fMid dx2
            GT ->  Bisect x fx dx2 
        where
            dx2 = dx * 0.5
            xMid = x + dx2
            fMid = f xMid
    
    estimateRoot  (Bisect x _  _) = x
    estimateError (Bisect _ _ dx) = dx

-- |Using bisection, return a root of a function known to lie between x1 and x2.
-- The root will be refined till its accuracy is +-xacc.  If convergence fails,
-- returns the final state of the search.
bisection :: (Ord a, Fractional a, Ord b, Num b) => (a -> b) -> a -> a -> a -> Either (Bisect a b) a
bisection f x1 x2 xacc = fmap estimateRoot (findRoot f x1 x2 xacc)