packages feed

halide-haskell-0.0.1.0: test/Language/Halide/ExprSpec.hs

module Language.Halide.ExprSpec (spec) where

import Control.Monad (unless, when)
import Data.Int
import Data.Word
import Language.Halide
import Test.Hspec
import Test.Hspec.QuickCheck
import Test.QuickCheck (Property)
import Test.QuickCheck.Monadic (PropertyM, assert, monadicIO, run)
import Type.Reflection
import Utils

isOverflowing :: Typeable a => (Integer -> Integer -> Integer) -> a -> a -> Bool
isOverflowing op x y
  | Just HRefl <- eqTypeRep (typeOf x) (typeRep @Int32) =
      op (toInteger x) (toInteger y) > toInteger (maxBound @Int32)
        || op (toInteger x) (toInteger y) < toInteger (minBound @Int32)
  | Just HRefl <- eqTypeRep (typeOf x) (typeRep @Int64) =
      op (toInteger x) (toInteger y) > toInteger (maxBound @Int64)
        || op (toInteger x) (toInteger y) < toInteger (minBound @Int64)
  | otherwise = False

infix 1 `evaluatesTo`

evaluatesTo :: (Eq a, IsHalideType a) => Expr a -> a -> PropertyM IO ()
evaluatesTo expr expected =
  assert . (expected ==) =<< (run . evaluate) expr

infix 1 `evaluatesToApprox`

evaluatesToApprox :: (Ord a, IsHalideType a, HasEpsilon a) => Expr a -> a -> PropertyM IO ()
evaluatesToApprox expr expected =
  assert . approx' expected =<< (run . evaluate) expr

infix 1 `shouldEvaluateTo`

shouldEvaluateTo :: (Eq a, IsHalideType a, Show a) => Expr a -> a -> Expectation
shouldEvaluateTo expr expected =
  evaluate expr `shouldReturn` expected

spec :: Spec
spec = do
  describe "mkExpr" $ modifyMaxSuccess (const 10) $ do
    let p :: forall a. (IsHalideType a, Eq a) => a -> Property
        p x = monadicIO $ mkExpr x `evaluatesTo` x
    prop "Bool" $ p @Bool

  describe "Num Expr" $ modifyMaxSuccess (const 10) $ do
    let whenNotOverflowing op x y check
          | isOverflowing op x y = pure ()
          | otherwise = check
        p :: forall a. (IsHalideType a, Eq a, Num a, Typeable a) => a -> a -> Property
        p x y =
          monadicIO $ do
            whenNotOverflowing (+) x y $
              mkExpr x + mkExpr y `evaluatesTo` x + y
            whenNotOverflowing (-) x y $
              mkExpr x - mkExpr y `evaluatesTo` x - y
            whenNotOverflowing (*) x y $
              mkExpr x * mkExpr y `evaluatesTo` x * y
            -- Temporary disable: see https://github.com/halide/Halide/issues/7365
            when (x /= -128) $
              abs (mkExpr x) `evaluatesTo` abs x
            negate (mkExpr x) `evaluatesTo` negate x
    prop "Int8" $ p @Int8
    prop "Int16" $ p @Int16
    prop "Int32" $ p @Int32
    prop "Int64" $ p @Int64
    prop "Word8" $ p @Word8
    prop "Word16" $ p @Word16
    prop "Word32" $ p @Word32
    prop "Word64" $ p @Word64
    prop "Float" $ p @Float
    prop "Double" $ p @Double

  describe "Fractional Expr" $ modifyMaxSuccess (const 10) $ do
    let p :: forall a. (IsHalideType a, Eq a, Fractional a) => a -> a -> Property
        p x y =
          monadicIO $
            unless (x == 0 && y == 0) $
              mkExpr x / mkExpr y `evaluatesTo` x / y
    prop "Float" $ p @Float
    prop "Double" $ p @Double

  describe "Floating Expr" $ modifyMaxSuccess (const 10) $ do
    let p :: forall a. (IsHalideType a, Ord a, Floating a, HasEpsilon a) => a -> Property
        p x = monadicIO $ do
          when (x > 0) $ do
            log (mkExpr x) `evaluatesToApprox` log x
            sqrt (mkExpr x) `evaluatesToApprox` sqrt x
          exp (mkExpr x) `evaluatesToApprox` exp x
          sin (mkExpr x) `evaluatesToApprox` sin x
          cos (mkExpr x) `evaluatesToApprox` cos x
          tan (mkExpr x) `evaluatesToApprox` tan x
          when (-1 <= x && x <= 1) $ do
            asin (mkExpr x) `evaluatesToApprox` asin x
            acos (mkExpr x) `evaluatesToApprox` acos x
            atan (mkExpr x) `evaluatesToApprox` atan x
          sinh (mkExpr x) `evaluatesToApprox` sinh x
          cosh (mkExpr x) `evaluatesToApprox` cosh x
          tanh (mkExpr x) `evaluatesToApprox` tanh x
          asinh (mkExpr x) `evaluatesToApprox` asinh x
          when (x >= 1) $
            acosh (mkExpr x) `evaluatesToApprox` acosh x
          when (-1 <= x && x <= 1) $
            atanh (mkExpr x) `evaluatesToApprox` atanh x
    prop "Float" $ p @Float
    prop "Double" $ p @Double
    it "defines pi" $ do
      (pi :: Expr Float) `shouldEvaluateTo` pi
      (pi :: Expr Double) `shouldEvaluateTo` pi