packages feed

hafar-0.1.0.0: test/Spec.hs

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE RankNTypes #-}

import Test.QuickCheck

import Control.Monad
import Text.Printf
import Data.Fixed (mod')

import qualified Numeric.Interval as IA (member, inf, sup, contains, inflate, Interval, midpoint)
import Numeric.AffineForm.Internal
import Numeric.AffineForm.Utils
import Numeric.AffineForm.ExplicitRounding

--
-- Generators and modifiers
--

data EpsV a = EpsV [a]
  deriving (Show)

instance (Real a, Arbitrary a) => Arbitrary (EpsV a) where
  arbitrary = do
    l <- listOf $ arbitrary
    let ls = (\x -> (x `mod'` 2) -1) <$> l
    return $ EpsV ls
  shrink (EpsV l) = filter validEV $ EpsV <$> (shrink l)

instance (Num a, Ord a, Arbitrary a) => Arbitrary (AF s a) where
  arbitrary = do
                x <- arbitrary
                xs <- arbitrary
                (Positive xe) <- arbitrary
                return $ AF x xs xe
  shrink (AF x xs xe) =
    [AF x' xs' xe' | (x', xs', xe') <- shrink (x, xs, xe)]

newtype SmallExponent a = SmallExponent a
  deriving (Show)

instance (Enum a, Num a, Arbitrary a) => Arbitrary (SmallExponent a) where
  arbitrary = SmallExponent <$> elements [1..4]
  shrink (SmallExponent x) = SmallExponent <$> shrink x

newtype ZerolessAF s a = ZerolessAF (AF s a)

instance (Show a) => Show (ZerolessAF s a) where
  show (ZerolessAF x) = show x

instance (Fractional a, Ord a, Arbitrary a, ExplicitRounding a) => Arbitrary (ZerolessAF s a) where
  arbitrary = do
    af <- arbitrary
    let mh = max (midpoint af) (1 + radius af)
        ml = min (midpoint af) ((negate $ radius af) - 1)
        res
          | midpoint af >= 0 = ZerolessAF $ setMidpoint mh af
          | otherwise = ZerolessAF $ setMidpoint ml af
    return res

newtype PositiveAF s a = PositiveAF (AF s a)

instance (Show a) => Show (PositiveAF s a) where
  show (PositiveAF x) = show x

instance (Fractional a, Ord a, Arbitrary a, ExplicitRounding a) => Arbitrary (PositiveAF s a) where
  arbitrary = do
    af <- arbitrary
    let m = 1/100000 + max (midpoint af) (radius af)
    return . PositiveAF $ setMidpoint m af

newtype SmallAF s a = SmallAF (AF s a)

instance (Show a) => Show (SmallAF s a) where
  show (SmallAF x) = show x

instance (Floating a, Ord a, Arbitrary a, ExplicitRounding a) => Arbitrary (SmallAF s a) where
  arbitrary = do
    size <- getSize
    af <- arbitrary
    let s = log . fromIntegral $ size + 1
        k = s / (radius af)
        m = clamp (midpoint af) (-s) s
    return . SmallAF $ setMidpoint m (k .* af)

validEV :: (Ord a, Num a) => EpsV a -> Bool
validEV (EpsV l) = all (\x -> -1 <= x && x <= 1) l

--
-- Properties
--

-- Generalized

correctnessPropUnary :: (Fractional a, Ord a, Show a, ExplicitRounding a)
  => (AF s a -> AF s a)
  -> (a -> a)
  -> [a]
  -> AF s a
  -> Property
correctnessPropUnary f g e x = withMaxSuccess 5000 $ counterexample str res
  where af = f x
        rhs = g (IA.midpoint $ fix x e)
        rhs_lo = g (IA.inf $ fix x e)
        rhs_hi = g (IA.sup $ fix x e)
        res = rhs `IA.member` interval af .&&.
              rhs_lo `IA.member` interval af .&&.
              rhs_hi `IA.member` interval af
        str = "-- RESULTS --\n"
           ++ "- LHS -\n"
           ++ "AF: " ++ (show af) ++ "\n"
           ++ "INTERVAL: " ++ (show $ interval af) ++ "\n"
           ++ "- RHS -\n"
           ++ "MID: " ++ (show rhs) ++ "\n"
           ++ "HI: " ++ (show rhs_hi) ++ "\n"
           ++ "LO: " ++ (show rhs_lo) ++ "\n"

correctnessPropBinary :: (Fractional a, Ord a, Show a, ExplicitRounding a)
  => (AF s a -> AF s a -> AF s a)
  -> (a -> a -> a)
  -> [a]
  -> AF s a
  -> AF s a
  -> Property
correctnessPropBinary f g e x y = withMaxSuccess 5000 $ counterexample str res
  where af = f x y
        rhs = g (IA.midpoint $ fix x e) (IA.midpoint $ fix y e)
        rhs_lo = g (IA.inf $ fix x e) (IA.inf $ fix y e)
        rhs_hi = g (IA.sup $ fix x e) (IA.sup $ fix y e)

        res = rhs `IA.member` interval af .&&.
              rhs_lo `IA.member` interval af .&&.
              rhs_hi `IA.member` interval af
        str = "-- RESULTS --\n"
           ++ "- LHS -\n"
           ++ "AF: " ++ (show af) ++ "\n"
           ++ "INTERVAL: " ++ (show $ interval af) ++ "\n"
           ++ "- RHS -\n"
           ++ "MID: " ++ (show rhs) ++ "\n"
           ++ "HI: " ++ (show rhs_hi) ++ "\n"
           ++ "LO: " ++ (show rhs_lo) ++ "\n"

-- RuKaS14.pdf [1102:2]
-- prop_addition :: EpsV Double -> AF Double -> AF Double -> Property
-- prop_addition (EpsV e) x y = counterexample str res
--   where lhs = (x + y) `fix` e
--         rhs = x `fix` e + y `fix` e
--         res = lhs `IA.contains` rhs
--         str = "AA: " ++ (show lhs) ++ "\nIA: " ++ (show rhs)

prop_sound_addition :: EpsV Double -> AF s Double -> AF s Double -> Property
prop_sound_addition (EpsV e) x y = correctnessPropBinary (+) (+) e x y

prop_sound_subtraction :: EpsV Double -> AF s Double -> AF s Double -> Property
prop_sound_subtraction (EpsV e) x y = correctnessPropBinary (-) (-) e x y

prop_sound_multiplication :: EpsV Double -> AF s Double -> AF s Double -> Property
prop_sound_multiplication (EpsV e) x y = correctnessPropBinary (*) (*) e x y

prop_sound_power :: EpsV Double -> AF s Double -> SmallExponent Integer -> Property
prop_sound_power (EpsV e) x (SmallExponent n) = correctnessPropUnary (^n) (^n) e x

prop_sound_recip :: EpsV Double -> ZerolessAF s Double -> Property
prop_sound_recip (EpsV e) (ZerolessAF x) = correctnessPropUnary recip recip e x

prop_sound_log :: EpsV Double -> PositiveAF s Double -> Property
prop_sound_log (EpsV e) (PositiveAF x) = correctnessPropUnary log log e x

prop_sound_exp :: EpsV Double -> SmallAF s Double -> Property
prop_sound_exp (EpsV e) (SmallAF x) = correctnessPropUnary exp exp e x

prop_sound_abs :: EpsV Double -> AF s Double -> Property
prop_sound_abs (EpsV e) x = correctnessPropUnary abs abs e x

-- prop_sin :: EpsV Double -> AF s Double -> Property
-- prop_sin (EpsV e) x = correctnessPropUnary sin sin e x

-- prop_cos :: EpsV Double -> AF s Double -> Property
-- prop_cos (EpsV e) x = correctnessPropUnary cos cos e x

--
-- Testing boilerplate
--

return [] -- This is a hack to make the quickCheckAll template work correctly

main :: IO ()
main = do
  $quickCheckAll
  return ()