easytensor-1.0.0.0: test/Numeric/DataFrame/Arbitraries.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE PartialTypeSignatures #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
-- | Provide instance of Arbitrary for all DataFrame types.
-- Also, this module is an example of fancy type inference and DataFrame
-- traversals with monadic actions.
module Numeric.DataFrame.Arbitraries where
import Test.QuickCheck
import Numeric.DataFrame
import Numeric.Dimensions
import Numeric.PrimBytes
instance (Arbitrary t, PrimBytes t, Dimensions ds)
=> Arbitrary (DataFrame t (ds :: [Nat])) where
arbitrary
| -- First, we need to find out exact array implementation to use
-- inside this DataFrame.
-- We need to do that whenever exact value of ds is not known
E <- inferASing' @t @ds
-- Then, we need to get basic byte manipulation type classes, such as
-- PrimBytes and PrimArray.
, E <- inferPrim' @t @ds
-- After that, GHC can infer all necessary fancy things like SubSpace
-- to do complex operations on sub-dimensions of a DataFrame.
--
-- Note, we could put SubSpace into constraints of this instance as well.
-- That would render the above lines unnecessary, but would make
-- inference more difficult later.
= arbitrary >>= elementWise @_ @_ @ds f . ewgen . scalar
where
f :: Arbitrary a => Scalar a -> Gen (Scalar a)
f _ = scalar <$> arbitrary
shrink
| E <- inferASing' @t @ds
, E <- inferPrim' @t @ds
= elementWise @_ @_ @ds f
where
-- Unfortunately, Scalar is not a proper second-rank data type
-- (it is just type alias for DataFrame t []).
-- So it cannot be functor or traversable.
f :: Arbitrary a => Scalar a -> [Scalar a]
f = fmap scalar . shrink . unScalar
instance (All Arbitrary ts, All PrimBytes ts, RepresentableList ts, Dimensions ds)
=> Arbitrary (DataFrame ts (ds :: [Nat])) where
-- We create arbitrary MultiFrame by combining several SingleFrames.
-- SingleFrames are "variables" or "columns" of a MultiFrame that are
-- independent byte arrays bounded by a common dimensions type signature.
arbitrary = -- Use RepresentableList to find out how many columns are there.
case tList @_ @ts of
-- Zero columns, empty MultiFrame
U -> return Z
-- Cons-like construction.
-- Note, pattern matching TypeList brings RepresentableList evidence
-- for Tail ts.
_ :* (TypeList :: TypeList ts') -> do
at <- arbitrary
ats' <- arbitrary @(DataFrame ts' ds)
return (at :*: ats')
shrink Z = []
-- MultiFrame is a newtype wrapper on a TypedList.
-- Thus, we can always recover RepresentableList ts by using function @types@
shrink (at :*: ats@(MultiFrame ats'))
| TypeList <- types ats'
= (:*:) <$> shrink at <*> shrink ats
maxDims :: Word
maxDims = 5
maxDimSize :: Word
maxDimSize = 7
instance KnownDim a => Arbitrary (Dim (N a)) where
arbitrary = return $ Dn (dim @_ @a)
shrink _ = []
instance KnownDim m => Arbitrary (Dim (XN m)) where
arbitrary = do
dimN <- choose (dimVal' @m, maxDims)
case constrain @m (someDimVal dimN) of
Nothing -> error "impossible argument"
Just d -> return d
shrink _ = []
instance Arbitrary SomeDims where
arbitrary = do
dimN <- choose (0, maxDims) :: Gen Word
wdims <- mapM (\_ -> choose (2, maxDimSize) :: Gen Word) [1..dimN]
return $ someDimsVal wdims
shrink (SomeDims U) = []
shrink (SomeDims (_ :* ds)) = [SomeDims ds]
instance Arbitrary (Dims '[]) where
arbitrary = return U
shrink _ = []
instance (KnownDim n, Arbitrary (Dims xs)) => Arbitrary (Dims (N n ': xs)) where
arbitrary = (:*) <$> arbitrary <*> arbitrary
shrink _ = []
instance (KnownDim m, Arbitrary (Dims xs)) => Arbitrary (Dims (XN m ': xs)) where
arbitrary = (:*) <$> arbitrary <*> arbitrary
shrink _ = []
instance (Arbitrary t, PrimBytes t)
=> Arbitrary (SomeDataFrame t) where
arbitrary = do
-- Generate random dimension list
-- and pattern-match against it with Dims pattern.
-- This gives Dimensions ds evidence immediately.
SomeDims (Dims :: Dims ds) <- arbitrary
-- We also need to figure out an array implementation...
case inferASing' @t @ds of
-- ... and generating a random DataFrame becomes a one-liner
E -> SomeDataFrame <$> arbitrary @(DataFrame t ds)
shrink _ = []
-- All same as above, just change constraints a bit
instance (All Arbitrary ts, All PrimBytes ts, RepresentableList ts)
=> Arbitrary (SomeDataFrame ts) where
arbitrary = do
SomeDims (Dims :: Dims ds) <- arbitrary
case inferASing' @ts @ds of
E -> SomeDataFrame <$> arbitrary @(DataFrame ts ds)
shrink _ = []
instance ( Arbitrary t, PrimBytes t
, Arbitrary (Dims xs), All KnownXNatType xs)
=> Arbitrary (DataFrame t (xs :: [XNat])) where
arbitrary = do
XDims (_ :: Dims ds) <- arbitrary @(Dims xs)
case inferASing' @t @ds of
E -> XFrame <$> arbitrary @(DataFrame t ds)
shrink (XFrame df) = XFrame <$> shrink df
instance ( All Arbitrary ts, All PrimBytes ts, RepresentableList ts
, Arbitrary (Dims xs), All KnownXNatType xs)
=> Arbitrary (DataFrame ts (xs :: [XNat])) where
arbitrary = do
XDims (_ :: Dims ds) <- arbitrary @(Dims xs)
case inferASing' @ts @ds of
E -> XFrame <$> arbitrary @(DataFrame ts ds)
shrink (XFrame df) = XFrame <$> shrink df