File: Sum.hs

package info (click to toggle)
haskell-math-functions 0.3.4.4-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,136 kB
  • sloc: haskell: 2,675; python: 121; makefile: 2
file content (101 lines) | stat: -rw-r--r-- 3,302 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
{-# OPTIONS_GHC -fno-warn-orphans #-}

module Tests.Sum (tests) where

import Control.Applicative ((<$>))
import Numeric.Sum as Sum
import Numeric.MathFunctions.Comparison
import Prelude hiding (sum)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.QuickCheck
import Test.QuickCheck (Arbitrary(..))
import qualified Prelude

-- Test that summation result is same as exact sum. That should pass
-- if we're effectively working with quad precision
t_sum :: ([Double] -> Double) -> [Double] -> Property
t_sum f xs
  = counterexample ("APPROX = " ++ show approx)
  $ counterexample ("EXACT  = " ++ show exact)
  $ counterexample ("DELTA  = " ++ show (approx - exact))
  $ counterexample ("ULPS   = " ++ show (ulpDistance approx exact))
  $ approx == exact
  where
    approx = f xs
    exact  = trueSum xs

-- Test that summation has smaller error than naive summation or no
-- worse than given number of ulps. If we're close enough to exact
-- answer naive may get ahead
t_sum_error :: ([Double] -> Double) -> [Double] -> Property
t_sum_error f xs
  = counterexample ("APPROX  = " ++ show approx)
  $ counterexample ("NAIVE   = " ++ show naive)
  $ counterexample ("EXACT   = " ++ show exact)
  $ counterexample ("A-EXACT = " ++ show (approx - exact))
  $ counterexample ("N-EXACT = " ++ show (naive  - exact))
  $ counterexample ("ULPS[A] = " ++ show (ulpDistance approx exact))
  $ counterexample ("ULPS[N] = " ++ show (ulpDistance naive  exact))
  $ abs (exact - approx) <= abs (exact - naive)
  where
    naive  = Prelude.sum xs
    approx = f xs
    exact  = trueSum xs

t_sum_shifted :: ([Double] -> Double) -> [Double] -> Property
t_sum_shifted f = t_sum_error f . zipWith (+) badvec

trueSum :: (Fractional b, Real a) => [a] -> b
trueSum xs = fromRational . Prelude.sum . map toRational $ xs

badvec :: [Double]
badvec = cycle [1, 1e14, -1e14]

tests :: TestTree
tests = testGroup "Summation"
  [ testGroup "Kahan" [
      -- Kahan summation only beats naive summation when truly
      -- catastrophic cancellation occurs
      testProperty "t_sum_shifted" $ t_sum_shifted (sum kahan)
    ]
  , testGroup "KBN" [
      testProperty "t_sum"         $ t_sum         (sum kbn)
    , testProperty "t_sum_error"   $ t_sum_error   (sum kbn)
    , testProperty "t_sum_shifted" $ t_sum_shifted (sum kbn)
    ]
  , testGroup "KB2" [
      testProperty "t_sum"         $ t_sum         (sum kb2)
    , testProperty "t_sum_error"   $ t_sum_error   (sum kb2)
    , testProperty "t_sum_shifted" $ t_sum_shifted (sum kb2)
    ]
  ]

instance Arbitrary KahanSum where
    arbitrary = toKahan <$> arbitrary
    shrink = map toKahan . shrink . fromKahan

toKahan :: (Double, Double) -> KahanSum
toKahan (a,b) = KahanSum a b

fromKahan :: KahanSum -> (Double, Double)
fromKahan (KahanSum a b) = (a,b)

instance Arbitrary KBNSum where
    arbitrary = toKBN <$> arbitrary
    shrink = map toKBN . shrink . fromKBN

toKBN :: (Double, Double) -> KBNSum
toKBN (a,b) = KBNSum a b

fromKBN :: KBNSum -> (Double, Double)
fromKBN (KBNSum a b) = (a,b)

instance Arbitrary KB2Sum where
    arbitrary = toKB2 <$> arbitrary
    shrink = map toKB2 . shrink . fromKB2

toKB2 :: (Double, Double, Double) -> KB2Sum
toKB2 (a,b,c) = KB2Sum a b c

fromKB2 :: KB2Sum -> (Double, Double, Double)
fromKB2 (KB2Sum a b c) = (a,b,c)