packages feed

arrayfire-0.8.0.0: test/Main.hs

{-# LANGUAGE GeneralisedNewtypeDeriving #-}
{-# LANGUAGE ScopedTypeVariables        #-}
{-# LANGUAGE TypeApplications           #-}
module Main where

import           Prelude                 hiding (negate)
import           Control.Monad           (forM_, unless)
import           Data.IORef              (IORef, newIORef, readIORef, writeIORef)
import           Data.Proxy
import           Data.Semiring           (Semiring (..), Ring (..))
import           Spec                    (spec)
import           System.Exit             (exitFailure)
import           Test.Hspec              (hspec, after_)
import           Test.QuickCheck
import           Test.QuickCheck.Classes

import qualified ArrayFire               as A
import           ArrayFire               (Array)

import           Foreign.C.Types          (CBool (..))

-- Multi-dimensional arrays: used for eqLaws, so the Eq instance is exercised
-- on matrices and tensors, not just scalars.
instance (A.AFType a, Arbitrary a) => Arbitrary (Array a) where
  arbitrary = do
    ndim  <- choose (1, 4)
    dims  <- vectorOf ndim (choose (1, 4))
    elems <- vectorOf (product dims) arbitrary
    pure (A.mkArray dims elems)
  shrink arr =
    [ A.mkArray dims' (take (product dims') (A.toList arr))
    | dims' <- shrunkDims
    , product dims' > 0
    ]
   where
    (d0, d1, d2, d3) = A.getDims arr
    ndim             = A.getNumDims arr
    currentDims      = take ndim [d0, d1, d2, d3]
    shrunkDims =
      [ [if i == j then d - 1 else d | (j, d) <- zip [0..] currentDims]
      | i <- [0 .. ndim - 1]
      , currentDims !! i > 1
      ]
      ++ [take (ndim - 1) currentDims | ndim > 1]

-- Scalar wrapper for numLaws.
-- Num laws require: (a) binary ops succeed for any two generated values, and
-- (b) `fromInteger 0` compares equal to `0 * x`.  Both hold only when all
-- arrays are the same shape.  Scalars ([1 1 1 1]) are the minimal fixed shape
-- that makes every Num law well-typed and exact for integer element types.
newtype Scalar a = Scalar (Array a)
  deriving (Show, Eq, Num)

-- Semiring/Ring instances so we can exercise semiringLaws/ringLaws, which
-- check associativity, distributivity and annihilation explicitly (stronger
-- than numLaws).  Defined in terms of the derived Num instance; exact for the
-- integral element types these are instantiated at.
instance (A.AFType a, Num a) => Semiring (Scalar a) where
  zero          = 0
  one           = 1
  plus          = (+)
  times         = (*)
  fromNatural n = fromInteger (toInteger n)

instance (A.AFType a, Num a) => Ring (Scalar a) where
  negate x = 0 - x

instance Arbitrary CBool where
  arbitrary = CBool <$> arbitrary

instance (A.AFType a, Arbitrary a) => Arbitrary (Scalar a) where
  arbitrary = Scalar . A.scalar <$> arbitrary
  shrink (Scalar arr) = Scalar . A.scalar <$> case A.toList arr of
    x : _ -> shrink x
    []    -> []

-- Run a Laws check, print results in the same format as lawsCheck, and mark
-- the IORef False on any failure so we can call exitFailure at the end.
checkLaws :: IORef Bool -> Laws -> IO ()
checkLaws ref laws = do
  let cls = lawsTypeclass laws
  forM_ (lawsProperties laws) $ \(name, prop) -> do
    putStr $ cls ++ ": " ++ name ++ " "
    r <- quickCheckWithResult stdArgs { chatty = False } prop
    putStr (output r)
    unless (isSuccess r) (writeIORef ref False)

main :: IO ()
main = A.withArrayFire $ do
  ref <- newIORef True
  let check = checkLaws ref
  -- IEEE 754 is not an exact ring; only Eq laws for floating-point arrays.
  check (eqLaws (Proxy :: Proxy (Array Double)))
  check (eqLaws (Proxy :: Proxy (Array Float)))
  -- Complex: Eq only (IEEE 754 + gt/lt undefined for complex numbers).
  check (eqLaws (Proxy :: Proxy (Array (A.Complex Double))))
  check (eqLaws (Proxy :: Proxy (Array (A.Complex Float))))
  -- Integral types: exact ring laws via Scalar, Eq laws via multi-dim Array.
  intChecks ref (Proxy :: Proxy Int)
  intChecks ref (Proxy :: Proxy A.Int16)
  intChecks ref (Proxy :: Proxy A.Int32)
  intChecks ref (Proxy :: Proxy A.Int64)
  intChecks ref (Proxy :: Proxy A.Word8)
  intChecks ref (Proxy :: Proxy A.Word16)
  intChecks ref (Proxy :: Proxy A.Word32)
  intChecks ref (Proxy :: Proxy A.Word64)
  intChecks ref (Proxy :: Proxy Word)
  intChecks ref (Proxy :: Proxy A.CBool)
  hspec (after_ A.deviceGC spec)
  ok <- readIORef ref
  unless ok exitFailure

intChecks :: forall a. (A.AFType a, Arbitrary a, Num a, Eq a) => IORef Bool -> Proxy a -> IO ()
intChecks ref _ = do
  checkLaws ref (numLaws      (Proxy :: Proxy (Scalar a)))
  checkLaws ref (semiringLaws (Proxy :: Proxy (Scalar a)))
  checkLaws ref (ringLaws     (Proxy :: Proxy (Scalar a)))
  checkLaws ref (eqLaws       (Proxy :: Proxy (Array  a)))