horde-ad-0.3.0.0: test/tool/EqEpsilon.hs
{-# LANGUAGE UndecidableInstances #-}
-- | Operations for comparing values up to a tolerance margin, to be used
-- for tests.
module EqEpsilon
( EqEpsilon, setEpsilonEq
, AssertEqualUpToEpsilon(..)
, assertEqualUpToEpsilonWithMark, assertEqualUpToEpsilon
, assertCloseElem, assertClose, (@?~)
) where
import Prelude
import Data.Int (Int16, Int32, Int64, Int8)
import Data.IORef
import Foreign.C (CInt)
import System.IO.Unsafe (unsafePerformIO)
import Test.Tasty.HUnit
import Test.Tasty.Options
import Shared
newtype EqEpsilon = EqEpsilon Rational
instance IsOption EqEpsilon where
defaultValue = EqEpsilon eqEpsilonDefault
parseValue s = fmap (EqEpsilon . toRational)
((safeRead :: String -> Maybe Double) s)
optionName = return "eq-epsilon"
optionHelp = return $ "Epsilon to use for floating point comparisons: abs(a-b) <= epsilon. Default: " ++ show (fromRational eqEpsilonDefault :: Double)
-- | Default value for eqEpsilonRef.
eqEpsilonDefault :: Rational
eqEpsilonDefault = 1e-6
-- | Global epsilon used to compare floating point values.
eqEpsilonRef :: IORef Rational
{-# NOINLINE eqEpsilonRef #-}
eqEpsilonRef = unsafePerformIO $ newIORef eqEpsilonDefault
-- | Global epsilon setter (to be called once).
setEpsilonEq :: EqEpsilon -> IO ()
setEpsilonEq (EqEpsilon x) = atomicWriteIORef eqEpsilonRef x
-- * Helper functions
assert_list :: forall a. HasCallStack
=> (a -> a -> Assertion)
-- ^ The function used to make an assertion
-- on two elements (expected, actual)
-> [a] -- ^ The expected value
-> [a] -- ^ The actual value
-> Assertion
assert_list make_assert expected actual =
if lenE == lenA then
go_assert_list expected actual
else
assertFailure $ "List too " ++ (if lenE < lenA then "long" else "short")
++ ": expected " ++ show lenE ++ " elements, but got: "
++ show lenA
where
lenE :: Int = length expected
lenA :: Int = length actual
go_assert_list :: [a] -- The expected value
-> [a] -- The actual value
-> Assertion
go_assert_list [] [] = assertBool "" True
go_assert_list [] (_:_) = assertFailure "More list elements than expected!"
go_assert_list (_:_) [] = assertFailure "Less list elements than expected!"
go_assert_list (head_exp:tail_exp) (head_act:tail_act) =
make_assert head_exp head_act >> go_assert_list tail_exp tail_act
assert_shape
:: forall a b. (HasShape a, Linearizable a b, HasCallStack)
=> (b -> b -> Assertion) -- ^ The function used to make an assertion
-- on two elements (expected, actual)
-> a -- ^ The expected value
-> a -- ^ The actual value
-> Assertion
assert_shape make_assert expected actual =
if shapeE == shapeA then
assert_list make_assert (linearize expected) (linearize actual)
else
assertFailure $ "Expected shape: " ++ show shapeE ++ ", but got: "
++ show shapeA
where
shapeE = shapeL expected
shapeA = shapeL actual
-- * Generic comparisons with explicit error margin
-- | Asserts that the specified actual floating point value is close
-- to the expected value. The output message will contain the prefix,
-- the expected value, and the actual value.
--
-- If the prefix is the empty string (i.e., @\"\"@), then the prefix is omitted
-- and only the expected and actual values are output.
assert_close_eps :: (Real a, Show a, HasCallStack)
=> String -- ^ The message prefix
-> String -- ^ The message suffix
-> Rational -- ^ The error margin
-> a -- ^ The expected value
-> a -- ^ The actual value
-> Assertion
assert_close_eps preface epilogue eqEpsilon expected actual = do
assertBool (message eqEpsilon)
(realToFrac (abs (expected - actual)) <= eqEpsilon)
where
msg = "expected: " ++ show expected ++ "\n but got: " ++ show actual
message errorMargin =
(if null preface then "" else preface ++ "\n")
++ msg ++ "\n (maximum margin of error: "
++ show (realToFrac errorMargin :: Double) ++ ")"
++ (if null epilogue
|| (lowercase epilogue == lowercase preface)
|| (lowercase epilogue == lowercase msg)
then ""
else "\n" ++ epilogue)
-- * AssertEqualUpToEpsilon class
class Show a => AssertEqualUpToEpsilon a where
assertEqualUpToEpsilonWithMsg
:: String -- ^ message suffix
-> Rational -- ^ error margin (i.e., the epsilon)
-> a -- ^ expected value
-> a -- ^ actual value
-> Assertion
assertEqualUpToEpsilonWithMark
:: (AssertEqualUpToEpsilon a, HasCallStack)
=> String -- ^ message suffix's prefix
-> Rational -- ^ error margin (i.e., the epsilon)
-> a -- ^ expected value
-> a -- ^ actual value
-> Assertion
assertEqualUpToEpsilonWithMark mark error_margin expected actual =
let prefix = if null mark then "" else "*In " ++ mark ++ "*\n"
in assertEqualUpToEpsilonWithMsg
(prefix ++ "Expected: " ++ show expected
++ "\n but got: " ++ show actual)
error_margin
expected actual
assertEqualUpToEpsilon
:: (AssertEqualUpToEpsilon a, HasCallStack)
=> Rational -- ^ error margin (i.e., the epsilon)
-> a -- ^ expected value
-> a -- ^ actual value
-> Assertion
assertEqualUpToEpsilon = assertEqualUpToEpsilonWithMark ""
instance AssertEqualUpToEpsilon Double where
assertEqualUpToEpsilonWithMsg = assert_close_eps ""
instance AssertEqualUpToEpsilon Float where
assertEqualUpToEpsilonWithMsg = assert_close_eps ""
instance AssertEqualUpToEpsilon Int64 where
assertEqualUpToEpsilonWithMsg = assert_close_eps ""
instance AssertEqualUpToEpsilon Int32 where
assertEqualUpToEpsilonWithMsg = assert_close_eps ""
instance AssertEqualUpToEpsilon Int16 where
assertEqualUpToEpsilonWithMsg = assert_close_eps ""
instance AssertEqualUpToEpsilon Int8 where
assertEqualUpToEpsilonWithMsg = assert_close_eps ""
instance AssertEqualUpToEpsilon Int where
assertEqualUpToEpsilonWithMsg = assert_close_eps ""
instance AssertEqualUpToEpsilon CInt where
assertEqualUpToEpsilonWithMsg = assert_close_eps ""
instance (AssertEqualUpToEpsilon a,
AssertEqualUpToEpsilon b)
=> AssertEqualUpToEpsilon (a,b) where
assertEqualUpToEpsilonWithMsg msg eqEpsilon (e1,e2) (a1,a2) =
assertEqualUpToEpsilonWithMsg msg eqEpsilon e1 a1 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e2 a2
instance (AssertEqualUpToEpsilon a,
AssertEqualUpToEpsilon b,
AssertEqualUpToEpsilon c)
=> AssertEqualUpToEpsilon (a,b,c) where
assertEqualUpToEpsilonWithMsg msg eqEpsilon (e1,e2,e3) (a1,a2,a3) =
assertEqualUpToEpsilonWithMsg msg eqEpsilon e1 a1 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e2 a2 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e3 a3
instance (AssertEqualUpToEpsilon a,
AssertEqualUpToEpsilon b,
AssertEqualUpToEpsilon c,
AssertEqualUpToEpsilon d)
=> AssertEqualUpToEpsilon (a,b,c,d) where
assertEqualUpToEpsilonWithMsg msg eqEpsilon (e1,e2,e3,e4) (a1,a2,a3,a4) =
assertEqualUpToEpsilonWithMsg msg eqEpsilon e1 a1 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e2 a2 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e3 a3 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e4 a4
instance (AssertEqualUpToEpsilon a,
AssertEqualUpToEpsilon b,
AssertEqualUpToEpsilon c,
AssertEqualUpToEpsilon d,
AssertEqualUpToEpsilon e)
=> AssertEqualUpToEpsilon (a,b,c,d,e) where
assertEqualUpToEpsilonWithMsg msg eqEpsilon (e1,e2,e3,e4,e5)
(a1,a2,a3,a4,a5) =
assertEqualUpToEpsilonWithMsg msg eqEpsilon e1 a1 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e2 a2 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e3 a3 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e4 a4 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e5 a5
instance (AssertEqualUpToEpsilon a,
AssertEqualUpToEpsilon b,
AssertEqualUpToEpsilon c,
AssertEqualUpToEpsilon d,
AssertEqualUpToEpsilon e,
AssertEqualUpToEpsilon f)
=> AssertEqualUpToEpsilon (a,b,c,d,e,f) where
assertEqualUpToEpsilonWithMsg msg eqEpsilon (e1,e2,e3,e4,e5,e6)
(a1,a2,a3,a4,a5,a6) =
assertEqualUpToEpsilonWithMsg msg eqEpsilon e1 a1 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e2 a2 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e3 a3 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e4 a4 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e5 a5 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e6 a6
instance (AssertEqualUpToEpsilon a,
AssertEqualUpToEpsilon b,
AssertEqualUpToEpsilon c,
AssertEqualUpToEpsilon d,
AssertEqualUpToEpsilon e,
AssertEqualUpToEpsilon f,
AssertEqualUpToEpsilon g)
=> AssertEqualUpToEpsilon (a,b,c,d,e,f,g) where
assertEqualUpToEpsilonWithMsg msg eqEpsilon (e1,e2,e3,e4,e5,e6,e7)
(a1,a2,a3,a4,a5,a6,a7) =
assertEqualUpToEpsilonWithMsg msg eqEpsilon e1 a1 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e2 a2 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e3 a3 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e4 a4 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e5 a5 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e6 a6 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e7 a7
instance (AssertEqualUpToEpsilon a,
AssertEqualUpToEpsilon b,
AssertEqualUpToEpsilon c,
AssertEqualUpToEpsilon d,
AssertEqualUpToEpsilon e,
AssertEqualUpToEpsilon f,
AssertEqualUpToEpsilon g,
AssertEqualUpToEpsilon h)
=> AssertEqualUpToEpsilon (a,b,c,d,e,f,g,h) where
assertEqualUpToEpsilonWithMsg msg eqEpsilon (e1,e2,e3,e4,e5,e6,e7,e8)
(a1,a2,a3,a4,a5,a6,a7,a8) =
assertEqualUpToEpsilonWithMsg msg eqEpsilon e1 a1 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e2 a2 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e3 a3 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e4 a4 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e5 a5 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e6 a6 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e7 a7 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e8 a8
instance (AssertEqualUpToEpsilon a,
AssertEqualUpToEpsilon b,
AssertEqualUpToEpsilon c,
AssertEqualUpToEpsilon d,
AssertEqualUpToEpsilon e,
AssertEqualUpToEpsilon f,
AssertEqualUpToEpsilon g,
AssertEqualUpToEpsilon h,
AssertEqualUpToEpsilon i)
=> AssertEqualUpToEpsilon (a,b,c,d,e,f,g,h,i) where
assertEqualUpToEpsilonWithMsg msg eqEpsilon (e1,e2,e3,e4,e5,e6,e7,e8,e9)
(a1,a2,a3,a4,a5,a6,a7,a8,a9) =
assertEqualUpToEpsilonWithMsg msg eqEpsilon e1 a1 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e2 a2 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e3 a3 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e4 a4 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e5 a5 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e6 a6 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e7 a7 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e8 a8 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e9 a9
instance (AssertEqualUpToEpsilon a,
AssertEqualUpToEpsilon b,
AssertEqualUpToEpsilon c,
AssertEqualUpToEpsilon d,
AssertEqualUpToEpsilon e,
AssertEqualUpToEpsilon f,
AssertEqualUpToEpsilon g,
AssertEqualUpToEpsilon h,
AssertEqualUpToEpsilon i,
AssertEqualUpToEpsilon j)
=> AssertEqualUpToEpsilon (a,b,c,d,e,f,g,h,i,j) where
assertEqualUpToEpsilonWithMsg msg eqEpsilon (e1,e2,e3,e4,e5,e6,e7,e8,e9,e10)
(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10) =
assertEqualUpToEpsilonWithMsg msg eqEpsilon e1 a1 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e2 a2 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e3 a3 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e4 a4 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e5 a5 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e6 a6 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e7 a7 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e8 a8 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e9 a9 >>
assertEqualUpToEpsilonWithMsg msg eqEpsilon e10 a10
instance {-# OVERLAPPABLE #-}
(Show a, HasShape a, Linearizable a b, AssertEqualUpToEpsilon b)
=> AssertEqualUpToEpsilon a where
assertEqualUpToEpsilonWithMsg msg eqEpsilon =
assert_shape (assertEqualUpToEpsilonWithMsg msg eqEpsilon)
-- * Generic comparisons without explicit error margin
-- | Asserts that the specified actual floating point value is close
-- to at least one of the expected values.
assertCloseElem :: forall a. (Real a, Fractional a, Show a, HasCallStack)
=> String -- ^ The message prefix
-> [a] -- ^ The expected values
-> a -- ^ The actual value
-> Assertion
assertCloseElem preface expected actual = do
eqEpsilon <- readIORef eqEpsilonRef
go_assert eqEpsilon expected
where
msg = (if null preface then "" else preface ++ "\n")
++ "wrong result: " ++ show actual
++ " is expected to be a member of " ++ show expected
go_assert :: Rational -> [a] -> Assertion
go_assert _ [] = assertFailure msg
go_assert eqEps (h:t) =
if abs (h-actual) <= fromRational eqEps
then assert_close_eps msg "" (fromRational eqEps) h actual
else go_assert eqEps t
assertClose :: (AssertEqualUpToEpsilon a, HasCallStack)
=> a -- ^ The expected value
-> a -- ^ The actual value
-> Assertion
assertClose expected actual = do
eqEpsilon <- readIORef eqEpsilonRef
assertEqualUpToEpsilon (fromRational eqEpsilon) expected actual
infix 1 @?~
(@?~) :: (AssertEqualUpToEpsilon a, HasCallStack)
=> a -- ^ The actual value
-> a -- ^ The expected value
-> Assertion
(@?~) = flip assertClose