packages feed

numeric-tools 0.1.0.1 → 0.2.0.0

raw patch · 5 files changed

+463/−40 lines, 5 filesdep +HUnitdep +numeric-toolsdep +primitivedep ~basePVP ok

version bump matches the API change (PVP)

Dependencies added: HUnit, numeric-tools, primitive

Dependency ranges changed: base

API changes (from Hackage documentation)

+ Numeric.ApproxEq: eqAbsolute :: (Num a, Ord a) => a -> a -> a -> Bool
+ Numeric.ApproxEq: eqRelCompl :: (RealFloat a, Ord a) => a -> Complex a -> Complex a -> Bool
+ Numeric.ApproxEq: eqRelative :: (Fractional a, Ord a) => a -> a -> a -> Bool
+ Numeric.ApproxEq: within :: Int -> Double -> Double -> Bool
+ Numeric.Tools.Equation: NotBracketed :: Root a
+ Numeric.Tools.Equation: Root :: a -> Root a
+ Numeric.Tools.Equation: SearchFailed :: Root a
+ Numeric.Tools.Equation: data Root a
+ Numeric.Tools.Equation: fromRoot :: a -> Root a -> a
+ Numeric.Tools.Equation: instance Alternative Root
+ Numeric.Tools.Equation: instance Applicative Root
+ Numeric.Tools.Equation: instance Eq a => Eq (Root a)
+ Numeric.Tools.Equation: instance Functor Root
+ Numeric.Tools.Equation: instance Monad Root
+ Numeric.Tools.Equation: instance MonadPlus Root
+ Numeric.Tools.Equation: instance Read a => Read (Root a)
+ Numeric.Tools.Equation: instance Show a => Show (Root a)
+ Numeric.Tools.Equation: instance Typeable1 Root
+ Numeric.Tools.Equation: solveNewton :: Double -> (Double, Double) -> (Double -> Double) -> (Double -> Double) -> Root Double
+ Numeric.Tools.Equation: solveRidders :: Double -> (Double, Double) -> (Double -> Double) -> Root Double
+ Numeric.Tools.Integration: quadBestEst :: QuadRes -> Double
- Numeric.Tools.Equation: solveBisection :: Double -> (Double, Double) -> (Double -> Double) -> Maybe Double
+ Numeric.Tools.Equation: solveBisection :: Double -> (Double, Double) -> (Double -> Double) -> Root Double
- Numeric.Tools.Integration: QuadRes :: Maybe Double -> Double -> Int -> QuadRes
+ Numeric.Tools.Integration: QuadRes :: Maybe Double -> Double -> Double -> Int -> QuadRes

Files

+ Numeric/ApproxEq.hs view
@@ -0,0 +1,81 @@+-- |+-- Module    : Numeric.ApproxEq+-- Copyright : (c) 2011 Aleksey Khudyakov, Bryan O'Sullivan+-- License   : BSD3+--+-- Maintainer  : Aleksey Khudyakov <alexey.skladnoy@gmail.com>+-- Stability   : experimental+-- Portability : portable+--+-- Different implementations of approximate equality for floating+-- point values. There are multiple ways to implement approximate+-- equality. They have different semantics and it's up to programmer+-- to choose right one.+module Numeric.ApproxEq (+    eqRelative+  , eqRelCompl+  , eqAbsolute+  , within+  ) where++import Control.Monad.ST         (runST)++import Data.Complex             (Complex(..))+import Data.Primitive.ByteArray (newByteArray, readByteArray, writeByteArray)+import Data.Int                 (Int64)++++-- | Relative difference between two numbers are less than predefined+--   value.  For example 1 is approximately equal to 1.0001 with 1e-4+--   precision. Same is true for 10000 and 10001.+--+--   This method of camparison doesn't work for numbers which are+--   approximately 0. 'eqAbsolute' should be used instead.+eqRelative :: (Fractional a, Ord a)+           => a            -- ^ Relative precision+           -> a -> a -> Bool+eqRelative eps a b = abs (a - b) <= abs (eps * a)+{-# INLINE eqRelative #-}++-- | Relative equality for comlex numbers. +eqRelCompl :: (RealFloat a, Ord a)+           => a                 -- ^ Relative precision+           -> Complex a -> Complex a -> Bool+eqRelCompl eps a b = d <= eps * r+  where+    (d :+ _) = abs (a - b)+    (r :+ _) = abs a+{-# INLINE eqRelCompl #-}++-- | Difference between values is less than specified precision.+eqAbsolute :: (Num a, Ord a)+           => a                 -- ^ Absolute precision+           -> a -> a -> Bool+eqAbsolute eps a b = abs (a - b) <= eps+{-# INLINE eqAbsolute #-}+++-- | Compare two 'Double' values for approximate equality, using+-- Dawson's method.+--+-- The required accuracy is specified in ULPs (units of least+-- precision).  If the two numbers differ by the given number of ULPs+-- or less, this function returns @True@.+--+-- Algorithm is based on Bruce Dawson's \"Comparing floating point+-- numbers\":+-- <http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm>+within :: Int                   -- ^ Number of ULPs of accuracy desired.+       -> Double -> Double -> Bool+within ulps a b = runST $ do+  buf <- newByteArray 8+  ai0 <- writeByteArray buf 0 a >> readByteArray buf 0+  bi0 <- writeByteArray buf 0 b >> readByteArray buf 0+  let big  = 0x8000000000000000 :: Int64+      ai | ai0 < 0   = big - ai0+         | otherwise = ai0+      bi | bi0 < 0   = big - bi0+         | otherwise = bi0+  return $! abs (ai - bi) <= fromIntegral ulps+
Numeric/Tools/Equation.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DeriveDataTypeable #-} -- | -- Module    : Numeric.Tools.Equation -- Copyright : (c) 2011 Aleksey Khudyakov@@ -9,36 +11,204 @@ -- -- Numerical solution of ordinary equations. module Numeric.Tools.Equation ( -    solveBisection+    -- * Data type+    Root(..)+  , fromRoot+    -- * Equations solversv+  , solveBisection+  , solveRidders  +  , solveNewton+  -- * References+  -- $references   ) where -import Numeric.IEEE (epsilon)+import Numeric.ApproxEq +import Control.Applicative+import Control.Monad       (MonadPlus(..), ap)+import Data.Typeable       (Typeable)  --- | Solve equation @f(x) = 0@ using bisection method. Function is---   must be continous. If function has different signs at the ends of---   initial interval answer is always returned. 'Nothing' is returned---   if function fails to find an answer.++-- | The result of searching for a root of a mathematical function.+data Root a = NotBracketed+            -- ^ The function does not have opposite signs when+            -- evaluated at the lower and upper bounds of the search.+            | SearchFailed+            -- ^ The search failed to converge to within the given+            -- error tolerance after the given number of iterations.+            | Root a+            -- ^ A root was successfully found.+              deriving (Eq, Read, Show, Typeable)++instance Functor Root where+  fmap _ NotBracketed = NotBracketed+  fmap _ SearchFailed = SearchFailed+  fmap f (Root a)     = Root (f a)++instance Monad Root where+  NotBracketed >>= _ = NotBracketed+  SearchFailed >>= _ = SearchFailed+  Root a       >>= m = m a+  return = Root++instance MonadPlus Root where+  mzero = SearchFailed+  r@(Root _) `mplus` _ = r+  _          `mplus` p = p++instance Applicative Root where+  pure  = Root+  (<*>) = ap++instance Alternative Root where+  empty = SearchFailed+  r@(Root _) <|> _ = r+  _          <|> p = p++-- | Returns either the result of a search for a root, or the default+-- value if the search failed.+fromRoot :: a                   -- ^ Default value.+         -> Root a              -- ^ Result of search for a root.+         -> a+fromRoot _ (Root a) = a+fromRoot a _        = a+++-- | Use bisection method to compute root of function.+--+-- The function must have opposite signs when evaluated at the lower+-- and upper bounds of the search (i.e. the root must be bracketed). solveBisection :: Double             -- ^ Required absolute precision                -> (Double,Double)    -- ^ Range                -> (Double -> Double) -- ^ Equation-               -> Maybe Double-solveBisection eps (a,b) f-  | a >= b      = Nothing-  | fa * fb > 0 = Nothing-  | otherwise   = Just $ bisectionWorker (abs eps) f a b fa fb+               -> Root Double+solveBisection eps (lo,hi) f+  | flo * fhi > 0 = NotBracketed+  | flo == 0      = Root lo+  | fhi == 0      = Root hi+  | flo < 0       = worker 0 lo hi+  | otherwise     = worker 0 hi lo   where-    fa = f a-    fb = f b+    flo = f lo+    fhi = f hi+    -- Worker function. Preconditions:+    --   f a < 0+    --   f b > 0+    worker i a b+      | within 1 a b       = Root a+      | abs (b - a) <= eps = Root c+      | fc == 0            = Root c+      | i >= (100 :: Int)  = SearchFailed+      | fc < 0             = worker (i+1) c b+      | otherwise          = worker (i+1) a c+      where+        c  = 0.5 * (a + b)+        fc = f c -bisectionWorker :: Double -> (Double -> Double) -> Double -> Double -> Double -> Double -> Double-bisectionWorker eps f a b fa fb-  | (b - a)     <= eps     = c-  | (b - a) / b <= epsilon = c-  | fa * fc < 0            = bisectionWorker eps f a c fa fc-  | otherwise              = bisectionWorker eps f c b fc fb++-- | Use the method of Ridders to compute a root of a function.+--+-- The function must have opposite signs when evaluated at the lower+-- and upper bounds of the search (i.e. the root must be bracketed).+solveRidders :: Double               -- ^ Absolute error tolerance.+             -> (Double,Double)      -- ^ Lower and upper bounds for the search.+             -> (Double -> Double)   -- ^ Function to find the roots of.+             -> Root Double+solveRidders eps (lo,hi) f+    | flo == 0    = Root lo+    | fhi == 0    = Root hi+    | flo*fhi > 0 = NotBracketed -- root is not bracketed+    | otherwise   = go lo flo hi fhi 0   where-    c  = 0.5 * (a + b)-    fc = f c+    go !a !fa !b !fb !i+        -- Root is bracketed within 1 ulp. No improvement could be made+        | within 1 a b       = Root a+        -- Root is found. Check that f(m) == 0 is nessesary to ensure+        -- that root is never passed to 'go'+        | fm == 0            = Root m+        | fn == 0            = Root n+        | d < eps            = Root n+        -- Too many iterations performed. Fail+        | i >= (100 :: Int)  = SearchFailed+        -- Ridder's approximation coincide with one of old+        -- bounds. Revert to bisection+        | n == a || n == b   = case () of+          _| fm*fa < 0 -> go a fa m fm (i+1)+           | otherwise -> go m fm b fb (i+1)+        -- Proceed as usual+        | fn*fm < 0          = go n fn m fm (i+1)+        | fn*fa < 0          = go a fa n fn (i+1)+        | otherwise          = go n fn b fb (i+1)+      where+        d    = abs (b - a)+        dm   = (b - a) * 0.5+        !m   = a + dm+        !fm  = f m+        !dn  = signum (fb - fa) * dm * fm / sqrt(fm*fm - fa*fb)+        !n   = m - signum dn * min (abs dn) (abs dm - 0.5 * eps)+        !fn  = f n+    !flo = f lo+    !fhi = f hi+++-- | Solve equation using Newton-Raphson method. Root must be+--   bracketed. If Newton's step jumps outside of bracket or do not+--   converge sufficiently fast function reverts to bisection.+solveNewton :: Double             -- ^ Absolute error tolerance+            -> (Double,Double)    -- ^ Lower and upper bounds for root+            -> (Double -> Double) -- ^ Function+            -> (Double -> Double) -- ^ Function's derivative+            -> Root Double+solveNewton eps (lo,hi) f f'+  | flo == 0      = Root lo+  | fhi == 0      = Root hi+  | flo * fhi > 0 = NotBracketed+  | otherwise     = let d   = 0.5*(hi-lo)+                        mid = 0.5*(lo+hi)+                        fun = worker 0 d mid (f mid) (f' mid)+                    in if flo < 0 then fun lo hi+                                  else fun hi lo     +  where+    flo = f lo+    fhi = f hi+    -- Worker function+    worker i dxOld x fx fx' a b+      -- Convergence achieved+      | fx == 0           = Root x  -- x is a root+      | within 1 a b      = Root x  -- Bracket is too tight. No improvements could be made+      | abs (a - b) < eps = Root x  --+      | abs dx      < eps = Root x' -- Precision achieved+      | within 0 x x'     = Root x' -- Newton step doesn't improve solution+      -- Too many iterations+      | i > (100::Int)    = SearchFailed+      -- Newton step jumped out of range or step decreases too slow.+      -- Revert to bisection.+      -- NOTE this guard is selected if dx evaluated to NaN or ±∞.+      | not ((a - x')*(b - x') < 0) || abs (dxOld / dx) < 2 =+          let dx' = 0.5 * (b - a)+              mid = 0.5 * (a + b)+          in if fx < 0 then step dx' mid+                       else step dx' mid+      -- Normal newton step+      | otherwise =+          if fx < 0 then step dx x' +                    else step dx x'+      where+        dx = fx / fx'+        x' = x - dx+        -- Perform one step+        step dy y+          | fy < 0    = worker (i+1) dy  y fy fy'  y b+          | otherwise = worker (i+1) dy  y fy fy'  a y+          where fy  = f  y+                fy' = f' y+{-# INLINABLE solveNewton #-}+++-- $references+--+-- * Ridders, C.F.J. (1979) A new algorithm for computing a single+--   root of a real continuous function.+--   /IEEE Transactions on Circuits and Systems/ 26:979&#8211;980. 
Numeric/Tools/Integration.hs view
@@ -42,6 +42,7 @@ import qualified Data.Vector.Unboxed         as U import qualified Data.Vector.Unboxed.Mutable as M +import qualified Numeric.IEEE                as IEEE   ----------------------------------------------------------------@@ -75,6 +76,7 @@  -- | Result of numeric integration. data QuadRes = QuadRes { quadRes     :: Maybe Double -- ^ Integraion result+                       , quadBestEst :: Double       -- ^ Best estimate of integral                        , quadPrecEst :: Double       -- ^ Rough estimate of attained precision                        , quadNIter   :: Int          -- ^ Number of iterations                        }@@ -99,14 +101,15 @@     eps  = quadPrecision param  -- Requred precision     maxN = maxIter param        -- Maximum allowed number of iterations     worker n nPoints q-      | n > 5 && d < eps = ret (Just q')-      | n >= maxN        = ret Nothing-      | otherwise        = worker (n+1) (nPoints*2) q'+      | n > 5 && converged eps q q' = done True+      | n >= maxN                   = done False+      | otherwise                   = worker (n+1) (nPoints*2) q'       where         q'  = nextTrapezoid a b nPoints f q -- New approximation-        d   = abs (q' - q) / abs q          -- Precision estimate-        ret = \x -> QuadRes x d n+        done True  = QuadRes (Just q') q' (estimatePrec q q') n+        done False = QuadRes Nothing   q' (estimatePrec q q') n + -- | Integration using Simpson rule. It should be more efficient than --   'quadTrapezoid' if function being integrated have finite fourth --   derivative.@@ -119,14 +122,14 @@     eps  = quadPrecision param  -- Requred precision     maxN = maxIter param        -- Maximum allowed number of points for evaluation     worker n nPoints s st-      | n > 5 && d < eps = ret (Just s')-      | n >= maxN        = ret Nothing-      | otherwise        = worker (n+1) (nPoints*2) s' st'+      | n > 5 && converged eps s s' = done True+      | n >= maxN                   = done False+      | otherwise                   = worker (n+1) (nPoints*2) s' st'       where         st' = nextTrapezoid a b nPoints f st         s'  = (4*st' - st) / 3-        d   = abs (s' - s) / abs s-        ret = \x -> QuadRes x d n+        done True  = QuadRes (Just s') s' (estimatePrec s s') n+        done False = QuadRes Nothing   s' (estimatePrec s s') n  -- | Integration using Romberg rule. For sufficiently smooth functions --   (e.g. analytic) it's a fastest of three.@@ -151,11 +154,12 @@     let worker n nPoints st s = do           let st' = nextTrapezoid a b nPoints f st           s' <- M.write arr 0 st >> nextAppr n st'-          let d = abs (s' - s) / abs s+          let done True  = return $ QuadRes (Just s') s' (estimatePrec s s') n+              done False = return $ QuadRes Nothing   s' (estimatePrec s s') n           case () of-            _ | n > 5 && d < eps -> return $ QuadRes (Just s') d n-              | n >= maxN        -> return $ QuadRes Nothing   d n-              | otherwise        -> worker (n+1) (nPoints*2) st' s'+            _ | n > 5 && converged eps s s' -> done True+              | n >= maxN                   -> done False+              | otherwise                   -> worker (n+1) (nPoints*2) st' s'     -- Calculate integral     worker 1 1 st0 st0 where  st0 = trapGuess a b f @@ -182,3 +186,26 @@     sep = (b - a) / fromIntegral n                  -- Separation between points     x0  = a + 0.5 * sep                             -- Starting point     s   = U.sum $ U.map f $ U.iterateN n (+sep) x0  -- Sum of all points+++-- Check for convergence. Convergence to zero is tricky case since we+-- use relative error and checking for convergence to zero requires+-- absolute one. Instead iteration have converged if two successive+-- operations produced zero+converged :: Double -> Double -> Double -> Bool+converged eps q q'+  -- Iterations yielded same numbers.+  | q == q'                 = True+  -- Both numbers have identical IEEE representation It's not the same+  -- as previous clause because of excess precision. And previous+  -- clause is required because of negative zero.+  | IEEE.identicalIEEE q q' = True+  -- Usual check for convergence.+  | otherwise = abs (q' - q) < eps * abs q+{-# INLINE converged #-}++-- Estimate precision+estimatePrec :: Double -> Double -> Double+estimatePrec q q'+  | q == 0 && q' == 0 = 0+  | otherwise         = abs (q - q') / abs q
numeric-tools.cabal view
@@ -1,6 +1,6 @@ Name:           numeric-tools-Version:        0.1.0.1-Cabal-Version:  >= 1.6+Version:        0.2.0.0+Cabal-Version:  >= 1.8 License:        BSD3 License-File:   LICENSE Author:         Aleksey Khudyakov <alexey.skladnoy@gmail.com>@@ -15,21 +15,43 @@   Package provides function to perform numeric integration and   differentiation, function interpolation.   .-  Changes in 0.1.0.1+  Changes in 0.2.0.0   .-  * Fixed bug in quadRomberg which caused crash if algorithm fails to-    converge+  * Equation solvers now use custom return type.+  .+  * Function to solve equations using Ridder and Newton methods.+  .+  * New function to test approximate equality for doubles.+  .+  * QuadRes contains best approximation achieved even if required+    accuracy is not obtained+  .+  * Improve convergence test when integral converges to+    zero. Convergence is still poor  + source-repository head   type:     hg   location: https://bitbucket.org/Shimuuar/numeric-tools +test-suite tests+  type:           exitcode-stdio-1.0+  hs-source-dirs: test+  main-is:        test.hs+  ghc-options:    -Wall -threaded -rtsopts+  build-depends:+    base,+    numeric-tools,+    HUnit+ Library   Build-Depends:   base >=3 && <5,                    ieee754 >= 0.7.3,+                   primitive,                    vector >= 0.7.0.1   Exposed-modules: Control.Monad.Numeric+                   Numeric.ApproxEq                    Numeric.Classes.Indexing                    Numeric.Tools.Equation                    Numeric.Tools.Differentiation
+ test/test.hs view
@@ -0,0 +1,123 @@++import Test.HUnit+import Text.Printf++import Numeric.Tools.Integration+import Numeric.Tools.Differentiation+import Numeric.Tools.Equation+import Numeric.ApproxEq+++----------------------------------------------------------------+-- Integration+----------------------------------------------------------------++-- Functions together with indefinite integral and list of ranges+type FunctionInt = ( Double -> Double  -- Function+                   , Double -> Double  -- Integral+                   , [(Double,Double)] -- List of ranges+                   , String            -- Name+                   )++-- Test integrator+integratorTest :: String+               -> (QuadParam -> (Double,Double) -> (Double -> Double) -> QuadRes)+               -> QuadParam+               -> FunctionInt+               -> [Test]+integratorTest name quad param (f,f',ranges,fname) =+  [ case quadRes $ quad param (a,b) f of+      Nothing   -> TestCase $ assertFailure (printf "%s: convergence for %s failed (%f,%f)" name fname a b)+      Just appr -> +        let exact = f' b - f' a+        in TestCase $ assertBool (printf "%s: poor convergence for %s (%f,%f) %g instead of %g"+                                    name fname a b appr exact)+                                 (eqRelative (quadPrecision param) exact appr)+  | (a,b) <- ranges      +  ]+++testIntegration :: [Test]+testIntegration = concat +  [ integratorTest "Trapeze" quadTrapezoid defQuad =<< [funBlamg,funExp,funLog]+  , integratorTest "Simpson" quadSimpson   defQuad =<< [funBlamg,funExp,funLog]+  , integratorTest "Romberg" quadRomberg   defQuad =<< [funBlamg,funExp,funLog]+  ]+  where+    funBlamg = ( \x -> x^4 * log(x + sqrt (x*x + 1))+               , \x -> 1/5*x^5 * log(x + sqrt (x*x + 1)) - 1/75*sqrt(x*x+1)*(3*x^4 - 4*x^2 + 8)+               , [(0,2), (1,3), (-2,3)]+               , "x^4·log(x + sqrt(x^2 + 1))"+               )+    funExp   = ( exp, exp+               , [(0,2), (1,3), (-2,3)] +               , "exp"+               )+    funLog   = ( log+               , \x -> x * (log x - 1)+               , [(1,2), (0.3,3)]+               , "log"+               )++++----------------------------------------------------------------+--+----------------------------------------------------------------++type FunctionDiff = ( Double -> Double  -- Function+                    , Double -> Double  -- Derivative+                    , [(Double,Double)] -- Points and delta to evaluate+                    , String            -- Name+                    )++differentiationTest :: String+                    -> ((Double -> Double) -> Double -> Double -> DiffRes)+                    -> FunctionDiff+                    -> [Test]+differentiationTest name diff (f,f',xs,fname) = +  [ let DiffRes appr err = diff f h x+        exact            = f' x+    in TestCase $ +         assertBool+         (printf "%s: poor precision for %s, got %g instead of %g" name fname appr exact)+         (eqRelative 1e-13 appr exact)+  | (x,h) <- xs+  ]++testDifferentiation :: [Test]+testDifferentiation = concat+  [ differentiationTest "richardson" diffRichardson =<< [funSqr,funExp,funLog]+  ]+  where+    funSqr = ( \x -> x*x+             , \x -> 2*x+             , zip ([-10 .. -1]++[1..10]) (repeat 1)+             , "square"+             )+    funExp = ( exp, exp+             , zip [-10..10] (repeat 1)+             , "exp"+             )  +    funLog = ( log, recip+             , map (\x -> (x,x/3)) [0.1,0.2 .. 2] +             , "log"+             )++testEquation :: [Test]+testEquation = +  [ TestCase $ assertBool "Bisection" $ ok (pi/2) (solveBisection 0 (1,2) cos)+  , TestCase $ assertBool "Ridders"   $ ok (pi/2) $ solveRidders   0 (1,2) cos+  , TestCase $ assertBool "Newton"    $ ok (pi/2) $ solveNewton    0 (1,2) cos sin+  ]+  where+    ok exact (Root x) = within 1 exact x+    ok _     _        = False++main :: IO ()+main = do +  res <- runTestTT $ TestList $ concat [ testDifferentiation+                                       , testIntegration+                                       , testEquation+                                       ]+  print res