packages feed

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

module Language.Halide.ExprSpec (spec) where

import Control.Monad (unless, when)
import Data.Int
import Data.Text (Text)
import Data.Word
import Language.Halide
import Test.Hspec
import Test.Hspec.QuickCheck
import Test.QuickCheck
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 `shouldEvaluateToApprox`

shouldEvaluateToApprox :: (Ord a, IsHalideType a, HasEpsilon a, Show a) => Expr a -> a -> Expectation
shouldEvaluateToApprox expr expected = do
  e <- evaluate expr
  e `shouldBeApprox` expected

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
    prop "Bool" $ \x ->
      mkExpr (x :: Bool) `shouldEvaluateTo` x

  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, Show a) => a -> a -> Expectation
        p x y = do
          whenNotOverflowing (+) x y $
            mkExpr x + mkExpr y `shouldEvaluateTo` x + y
          whenNotOverflowing (-) x y $
            mkExpr x - mkExpr y `shouldEvaluateTo` x - y
          whenNotOverflowing (*) x y $
            mkExpr x * mkExpr y `shouldEvaluateTo` x * y
          unless (x == -128) $
            abs (mkExpr x) `shouldEvaluateTo` abs x
          negate (mkExpr x) `shouldEvaluateTo` 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, Show a) => a -> a -> Expectation
        p x y = do
          unless (x == 0 && y == 0) $
            mkExpr x / mkExpr y `shouldEvaluateTo` x / y
    prop "Float" $ property (p @Float)
    prop "Double" $ property (p @Double)

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

  describe "printed" $
    it "prints expressions when evaluated" $ do
      printed (1 :: Expr Int32) `shouldEvaluateTo` 1
      printed (1 :: Expr Int32) ("<- when" :: String) ("haha" :: String) `shouldEvaluateTo` 1
      let x :: Expr Float
          x = 1
       in printed (sin x) ("<- sin(" :: Text) x (")" :: Text) `shouldEvaluateToApprox` sin 1

  -- describe "Show" $
  --   it "shows 123" $ do
  --     show (123 :: Expr Int32) `shouldBe` "123"
  describe "testWriteToStderr" $ do
    it "shows 123" $ do
      testWriteToStderr