testing-tensor-0.1.1: test/TestSuite/Test/Convolution.hs
module TestSuite.Test.Convolution (tests) where
import Data.List qualified as L
import Data.Type.Nat
import Data.Vec.Lazy (Vec(..))
import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.QuickCheck
import Test.Tensor (Tensor)
import Test.Tensor qualified as Tensor
import Test.Tensor.TestValue
import TestSuite.Test.Convolution.Examples3B1B
{-------------------------------------------------------------------------------
List of tests
-------------------------------------------------------------------------------}
tests :: TestTree
tests = testGroup "TestSuite.Test.Convolution.Prop" [
testGroup "Examples" [
testCase "rotate" example_rotate
, testCase "distrib_dim2" example_distrib_dim2
, testCase "subs_dim1" example_subs_dim1
, testCase "subs_dim2" example_subs_dim2
, testCase "subs_dim3" example_subs_dim3
, testCase "padWith" example_padWith
, testCase "padWith'" example_padWith'
]
, testGroup "3B1B" [
testCase "simple" example_3b1b_simple
, testCase "movingAverage" example_3b1b_movingAverage
, testCase "movingWeightedAverage" example_3b1b_movingWeightedAverage
, testCase "weightedDice" example_3b1b_weightedDice
]
, testGroup "Properties" [
testProperty "distrib_dim0" prop_distrib_dim0
, testProperty "distrib_dim1" prop_distrib_dim1
]
]
{-------------------------------------------------------------------------------
Examples
-------------------------------------------------------------------------------}
example_rotate :: Assertion
example_rotate =
assertEqual "" expected $
Tensor.rotate (Tensor.dim2 [ [1,2,3], [4,5,6] ])
where
expected :: Tensor Nat2 Integer
expected = Tensor.dim2 [ [6,5,4], [3,2,1] ]
example_distrib_dim2 :: Assertion
example_distrib_dim2 =
assertEqual "" expected $
Tensor.distrib (Tensor.size expected) input
where
input :: [Tensor Nat2 Int]
input = [
Tensor.dim2 [[111, 112, 113, 114], [121, 122, 123, 124], [131, 132, 133, 134]]
, Tensor.dim2 [[211, 212, 213, 214], [221, 222, 223, 224], [231, 232, 233, 234]]
, Tensor.dim2 [[311, 312, 313, 314], [321, 322, 323, 324], [331, 332, 333, 334]]
, Tensor.dim2 [[411, 412, 413, 414], [421, 422, 423, 424], [431, 432, 433, 434]]
, Tensor.dim2 [[511, 512, 513, 514], [521, 522, 523, 524], [531, 532, 533, 534]]
]
expected :: Tensor Nat2 [Int]
expected = Tensor.dim2 [
[ [111,211,311,411,511]
, [112,212,312,412,512]
, [113,213,313,413,513]
, [114,214,314,414,514]
]
, [ [121,221,321,421,521]
, [122,222,322,422,522]
, [123,223,323,423,523]
, [124,224,324,424,524]
]
, [ [131,231,331,431,531]
, [132,232,332,432,532]
, [133,233,333,433,533]
, [134,234,334,434,534]
]
]
example_subs_dim1 :: Assertion
example_subs_dim1 =
assertEqual "" expected $
Tensor.subs (2 ::: VNil) $
Tensor.dim1 [1,2,3]
where
expected :: Tensor Nat1 (Tensor Nat1 Int)
expected = Tensor.dim1 [ Tensor.dim1 [1,2], Tensor.dim1 [2,3] ]
example_subs_dim2 :: Assertion
example_subs_dim2 =
assertEqual "" expected $
Tensor.subs (2 ::: 2 ::: VNil) $
Tensor.dim2 [[11,12,13],[21,22,23],[31,32,33]]
where
expected :: Tensor Nat2 (Tensor Nat2 Int)
expected = Tensor.dim2 [
[ Tensor.dim2 [[11,12],[21,22]], Tensor.dim2 [[12,13],[22,23]] ]
, [ Tensor.dim2 [[21,22],[31,32]], Tensor.dim2 [[22,23],[32,33]] ]
]
example_subs_dim3 :: Assertion
example_subs_dim3 =
assertEqual "" expected $
Tensor.subs (2 ::: 2 ::: 2 ::: VNil) $
Tensor.dim3 [
[[111,112,113],[121,122,123],[131,132,133]]
, [[211,212,213],[221,222,223],[231,232,233]]
, [[311,312,313],[321,322,323],[331,332,333]]
]
where
expected :: Tensor Nat3 (Tensor Nat3 Int)
expected = Tensor.dim3 [
[ [ Tensor.dim3 [[[111,112],[121,122]],[[211,212],[221,222]]]
, Tensor.dim3 [[[112,113],[122,123]],[[212,213],[222,223]]]
]
, [ Tensor.dim3 [[[121,122],[131,132]],[[221,222],[231,232]]]
, Tensor.dim3 [[[122,123],[132,133]],[[222,223],[232,233]]]
]
]
, [ [ Tensor.dim3 [[[211,212],[221,222]],[[311,312],[321,322]]]
, Tensor.dim3 [[[212,213],[222,223]],[[312,313],[322,323]]]
]
, [ Tensor.dim3 [[[221,222],[231,232]],[[321,322],[331,332]]]
, Tensor.dim3 [[[222,223],[232,233]],[[322,323],[332,333]]]
]
]
]
example_padWith :: Assertion
example_padWith =
assertEqual "" expected $
Tensor.padWith 0 2 $ Tensor.dim2 [ [1, 2, 3], [4, 5, 6] ]
where
expected :: Tensor Nat2 Int
expected = Tensor.dim2 [
[ 0, 0, 0, 0, 0, 0, 0 ]
, [ 0, 0, 0, 0, 0, 0, 0 ]
, [ 0, 0, 1, 2, 3, 0, 0 ]
, [ 0, 0, 4, 5, 6, 0, 0 ]
, [ 0, 0, 0, 0, 0, 0, 0 ]
, [ 0, 0, 0, 0, 0, 0, 0 ]
]
example_padWith' :: Assertion
example_padWith' =
assertEqual "" expected $
Tensor.padWith' 0 ((1, 1) ::: (2, 3) ::: VNil) (Tensor.dim2 [[1]])
where
expected :: Tensor Nat2 Int
expected = Tensor.dim2 [
[0,0,0,0,0,0]
, [0,0,1,0,0,0]
, [0,0,0,0,0,0]
]
{-------------------------------------------------------------------------------
Examples from the 3B1B video
-------------------------------------------------------------------------------}
example_3b1b ::
Tensor Nat1 TestValue -- ^ Input (padded)
-> Tensor Nat1 TestValue -- ^ Kernel
-> Tensor Nat1 TestValue -- ^ Expected result
-> Assertion
example_3b1b input kernel result =
assertEqual "" result $
Tensor.convolve kernel input
example_3b1b_simple :: Assertion
example_3b1b_simple =
example_3b1b
(Tensor.padWith 0 2 $ Tensor.dim1 simpleInput)
(Tensor.dim1 simpleKernel)
(Tensor.dim1 simpleResult)
example_3b1b_weightedDice :: Assertion
example_3b1b_weightedDice =
example_3b1b
(Tensor.padWith 0 5 $ Tensor.dim1 weightedDiceInput)
(Tensor.dim1 weightedDiceKernel)
(Tensor.dim1 weightedDiceResult)
example_3b1b_movingAverage :: Assertion
example_3b1b_movingAverage =
example_3b1b
(Tensor.padWith 0 2 $ Tensor.dim1 movingAverageInput)
(Tensor.dim1 movingAverageKernel)
(Tensor.dim1 movingAverageResult)
example_3b1b_movingWeightedAverage :: Assertion
example_3b1b_movingWeightedAverage =
example_3b1b
(Tensor.padWith 0 2 $ Tensor.dim1 movingAverageInput)
(Tensor.dim1 movingWeightedAverageKernel)
(Tensor.dim1 movingWeightedAverageResult)
{-------------------------------------------------------------------------------
Properties
-------------------------------------------------------------------------------}
-- | Distribute over a list of 0-D tensor is the identity
prop_distrib_dim0 :: NonEmptyList Int -> Property
prop_distrib_dim0 (getNonEmpty -> xs) =
Tensor.toLists (Tensor.distrib size (map Tensor.scalar xs))
=== xs
where
size :: Tensor.Size Nat0
size = VNil
-- | Distribute over a list of 1-D tensor is 'transpose'
--
-- This is true only for rectangular input.
prop_distrib_dim1 :: NonEmptyList (NonEmptyList Int) -> Property
prop_distrib_dim1 (getSameLength -> xss) =
counterexample ("input: " ++ show xss) $
Tensor.toLists (Tensor.distrib size (map Tensor.dim1 xss))
=== L.transpose xss
where
size :: Tensor.Size Nat1
size = length (L.head xss) ::: VNil
{-------------------------------------------------------------------------------
Auxiliary
-------------------------------------------------------------------------------}
getNonEmpty2 :: NonEmptyList (NonEmptyList a) -> [[a]]
getNonEmpty2 = map getNonEmpty . getNonEmpty
getSameLength :: NonEmptyList (NonEmptyList a) -> [[a]]
getSameLength = aux . getNonEmpty2
where
aux :: [[a]] -> [[a]]
aux xss = map (take (minimum $ map length xss)) xss