packages feed

noether-0.0.1: test-suite/Noether/Test/Algebra.hs

{-# OPTIONS_GHC -Wno-missing-signatures #-}
{-# LANGUAGE AllowAmbiguousTypes   #-}
{-# LANGUAGE PartialTypeSignatures #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE TypeApplications      #-}
module Noether.Test.Algebra where

import           Hedgehog                   hiding (Group)
import qualified Hedgehog.Gen               as Gen
import           Hedgehog.Internal.Property hiding (Group)
import qualified Hedgehog.Internal.Property as Prop
import           Hedgehog.Internal.Show
import           Hedgehog.Internal.Source
import qualified Hedgehog.Range             as Range

import           GHC.Stack
import qualified Prelude                    as P

import           Noether.Algebra.Single
import           Noether.Algebra.Tags
import           Noether.Lemmata.Prelude
import           Noether.Lemmata.TypeFu

genDouble
  :: Monad m
  => Test m Double
genDouble = forAll $ Gen.realFloat $ Range.linearFrac (-100) 100

almostEqual
  :: (Ord a, Fractional a)
  => a -> a -> Bool
almostEqual a b = abs (a P.- b) < 0.05

(=~=)
  :: (Monad m, HasCallStack, Ord a, Fractional a, Show a)
  => a -> a -> Test m ()
(=~=) x y =
  if (x `almostEqual` y)
    then success
    else withFrozenCallStack <$>
         maybe
           (failWith
              Nothing
              (unlines ["━━━ Not Equal ━━━", showPretty x, showPretty y]))
           (\diff ->
              failWith
                (Just (Diff "Failed (" "- lhs" "=/=" "+ rhs" ")" diff))
                "")
           (valueDiff <$> mkValue x <*> mkValue y)

-- | Create a property for an 'Integral' type.
mkProp_integral
  :: forall a t.
     (Integral a, Show a)
  => t -> (a -> a -> a) -> (a -> a -> a) -> (t, Property)
mkProp_integral name preludeOp noetherOp =
  namedProperty
    name
    (do let r = forAll (Gen.integral (Range.linear (-100) 100))
        a <- r
        b <- r
        (a `preludeOp` b) === (a `noetherOp` b))

prop_prelude_add_int = mkProp_integral @Int "(+) : Int" (P.+) (+)
prop_prelude_mul_int = mkProp_integral @Int "(*) : Int" (P.*) (*)
prop_prelude_sub_int = mkProp_integral @Int "(-) : Int" (P.-) (-)

prop_prelude_add_integer = mkProp_integral @Integer "(+) : Integer" (P.+) (+)
prop_prelude_mul_integer = mkProp_integral @Integer "(*) : Integer" (P.*) (*)
prop_prelude_sub_integer = mkProp_integral @Integer "(-) : Integer" (P.-) (-)

-- | Create a property for a 'RealFloat' type.
mkProp_realFloat
  :: (RealFloat a, Ord a, Show a)
  => t
  -> (a -> a -> a)
  -> (a -> a -> a)
  -> (t, Property)
mkProp_realFloat name preludeOp noetherOp =
  namedProperty
    name
    (do let r = forAll (Gen.realFloat (Range.linearFrac (-100) 100))
        a <- r
        b <- r
        (a `preludeOp` b) =~= (a `noetherOp` b))

prop_prelude_add_float = mkProp_realFloat @Float "(+) : Float" (P.+) (+)
prop_prelude_mul_float = mkProp_realFloat @Float "(*) : Float" (P.*) (*)
prop_prelude_sub_float = mkProp_realFloat @Float "(-) : Float" (P.-) (-)
prop_prelude_div_float = mkProp_realFloat @Float "(/) : Float" (P./) (/)

prop_prelude_add_double = mkProp_realFloat @Double "(+) : Double" (P.+) (+)
prop_prelude_mul_double = mkProp_realFloat @Double "(*) : Double" (P.*) (*)
prop_prelude_sub_double = mkProp_realFloat @Double "(-) : Double" (P.-) (-)
prop_prelude_div_double = mkProp_realFloat @Double "(/) : Double" (P./) (/)

namedProperty name prop = (name, property prop)

-- | Create a property for a 'Ratio' type.
mkProp_rational
  :: forall a t.
  (Ord a, Show a, Integral a)
  => t
  -> (Ratio a -> Ratio a -> Ratio a)
  -> (Ratio a -> Ratio a -> Ratio a)
  -> (t, Property)
mkProp_rational name preludeOp noetherOp =
  namedProperty
    name
    (do let
          num = Gen.filter (/=0) (Gen.integral (Range.linear (-10) 10))
          den = Gen.filter (/=0) (Gen.integral (Range.linear (-10) 10))
          r = forAll ((%) <$> num <*> den)
        a <- r
        b <- r
        (a `preludeOp` b) =~= (a `noetherOp` b))

prop_prelude_add_rational_integer = mkProp_rational @Integer "(+) : Ratio Integer" (P.+) (+)
prop_prelude_mul_rational_integer = mkProp_rational @Integer "(*) : Ratio Integer" (P.*) (*)
prop_prelude_sub_rational_integer = mkProp_rational @Integer "(-) : Ratio Integer" (P.-) (-)
prop_prelude_div_rational_integer = mkProp_rational @Integer "(/) : Ratio Integer" (P./) (/)

prop_prelude_add_rational_int8 = mkProp_rational @Int8 "(+) : Ratio Int8" (P.+) (+)
prop_prelude_mul_rational_int8 = mkProp_rational @Int8 "(*) : Ratio Int8" (P.*) (*)
prop_prelude_sub_rational_int8 = mkProp_rational @Int8 "(-) : Ratio Int8" (P.-) (-)
prop_prelude_div_rational_int8 = mkProp_rational @Int8 "(/) : Ratio Int8" (P./) (/)

prop_prelude_add_rational_int16 = mkProp_rational @Int16 "(+) : Ratio Int16" (P.+) (+)
prop_prelude_mul_rational_int16 = mkProp_rational @Int16 "(*) : Ratio Int16" (P.*) (*)
prop_prelude_sub_rational_int16 = mkProp_rational @Int16 "(-) : Ratio Int16" (P.-) (-)
prop_prelude_div_rational_int16 = mkProp_rational @Int16 "(/) : Ratio Int16" (P./) (/)

prop_prelude_add_rational_int32 = mkProp_rational @Int32 "(+) : Ratio Int32" (P.+) (+)
prop_prelude_mul_rational_int32 = mkProp_rational @Int32 "(*) : Ratio Int32" (P.*) (*)
prop_prelude_sub_rational_int32 = mkProp_rational @Int32 "(-) : Ratio Int32" (P.-) (-)
prop_prelude_div_rational_int32 = mkProp_rational @Int32 "(/) : Ratio Int32" (P./) (/)

prop_prelude_add_rational_int64 = mkProp_rational @Int64 "(+) : Ratio Int64" (P.+) (+)
prop_prelude_mul_rational_int64 = mkProp_rational @Int64 "(*) : Ratio Int64" (P.*) (*)
prop_prelude_sub_rational_int64 = mkProp_rational @Int64 "(-) : Ratio Int64" (P.-) (-)
prop_prelude_div_rational_int64 = mkProp_rational @Int64 "(/) : Ratio Int64" (P./) (/)

tests :: IO ()
tests = do
  checkParallel'
    "Noether numerics agree with Prelude ops"
    [ prop_prelude_add_int
    , prop_prelude_sub_int
    , prop_prelude_mul_int
    , prop_prelude_add_integer
    , prop_prelude_sub_integer
    , prop_prelude_mul_integer
    , prop_prelude_add_float
    , prop_prelude_sub_float
    , prop_prelude_mul_float
    , prop_prelude_div_float
    , prop_prelude_add_double
    , prop_prelude_sub_double
    , prop_prelude_mul_double
    , prop_prelude_div_double
    , prop_prelude_add_rational_integer
    , prop_prelude_sub_rational_integer
    , prop_prelude_mul_rational_integer
    , prop_prelude_div_rational_integer
    -- , prop_prelude_add_rational_int8
    -- , prop_prelude_sub_rational_int8
    -- , prop_prelude_mul_rational_int8
    -- , prop_prelude_div_rational_int8
    -- , prop_prelude_add_rational_int16
    -- , prop_prelude_sub_rational_int16
    -- , prop_prelude_mul_rational_int16
    -- , prop_prelude_div_rational_int16
    , prop_prelude_add_rational_int32
    , prop_prelude_sub_rational_int32
    , prop_prelude_mul_rational_int32
    , prop_prelude_div_rational_int32
    , prop_prelude_add_rational_int64
    , prop_prelude_sub_rational_int64
    , prop_prelude_mul_rational_int64
    , prop_prelude_div_rational_int64
    ]
  putStrLn "asdf"
  where
    checkParallel' name tests = checkParallel (Prop.Group name tests)