packages feed

linear-massiv-0.1.0.0: test/Test/BLAS.hs

{-# LANGUAGE AllowAmbiguousTypes #-}

module Test.BLAS (blasTests) where

import Test.Tasty
import Test.Tasty.QuickCheck
import Test.Tasty.HUnit
import qualified Data.Massiv.Array as M

import Numeric.LinearAlgebra.Massiv.Types
import Numeric.LinearAlgebra.Massiv.Internal
import Numeric.LinearAlgebra.Massiv.BLAS.Level1
import Numeric.LinearAlgebra.Massiv.BLAS.Level2
import Numeric.LinearAlgebra.Massiv.BLAS.Level3
import Numeric.LinearAlgebra.Massiv.Norms (normFrob)
import Test.Types (genMatrix, genVector, hilbertMatrix, (~=), matApproxEq, vecApproxEq)
import Test.Residuals (machineEps)

blasTests :: TestTree
blasTests = testGroup "BLAS"
  [ testGroup "Level 1"
    [ testProperty "dot product commutative" prop_dotCommutative
    , testProperty "dot with zero vector" prop_dotZero
    , testCase "dot Hilbert columns" test_dotHilbertColumns
    , testProperty "axpy identity" prop_axpyIdentity
    , testProperty "scal by 1" prop_scalIdentity
    , testProperty "nrm2 non-negative" prop_nrm2NonNeg
    ]
  , testGroup "Level 2"
    [ testProperty "matvec with identity" prop_matvecIdentity
    , testProperty "gemv alpha=1 beta=0" prop_gemvSimple
    ]
  , testGroup "Level 3"
    [ testProperty "matMul with identity (left)" prop_matMulIdentityLeft
    , testProperty "matMul with identity (right)" prop_matMulIdentityRight
    , testProperty "transpose involution" prop_transposeInvolution
    , testProperty "mAdd commutative" prop_mAddCommutative
    , testProperty "matMul associative 5x5" prop_matMulAssociative5
    , testProperty "matMul with identity 10x10" prop_matMulIdentity10
    , testCase "3x3 matmul known" test_matMulKnown
    , testCase "gemm larger 3x3" test_gemmLarger
    ]
  ]

-- Level 1 properties

prop_dotCommutative :: Property
prop_dotCommutative = forAll ((,) <$> genVector @4 <*> genVector @4) $ \(x, y) ->
  dot x y ~= dot y x

prop_dotZero :: Property
prop_dotZero = forAll (genVector @4) $ \x ->
  let z = zeroVector @4 @M.P :: Vector 4 M.P Double
  in dot x z ~= 0

prop_axpyIdentity :: Property
prop_axpyIdentity = forAll (genVector @4) $ \x ->
  let z = zeroVector @4 @M.P :: Vector 4 M.P Double
  in vecApproxEq @4 (axpy 1 z x) x

prop_scalIdentity :: Property
prop_scalIdentity = forAll (genVector @4) $ \x ->
  vecApproxEq @4 (scal 1 x) x

prop_nrm2NonNeg :: Property
prop_nrm2NonNeg = forAll (genVector @4) $ \x ->
  nrm2 x >= (0 :: Double)

-- Level 2 properties

prop_matvecIdentity :: Property
prop_matvecIdentity = forAll (genVector @3) $ \x ->
  let eye = identityMatrix @3 @M.P :: Matrix 3 3 M.P Double
  in vecApproxEq @3 (matvec eye x) x

prop_gemvSimple :: Property
prop_gemvSimple = forAll ((,) <$> genMatrix @3 @3 <*> genVector @3) $ \(a, x) ->
  let z = zeroVector @3 @M.P :: Vector 3 M.P Double
      result = gemv 1.0 a x 0.0 z
      expected = matvec a x
  in vecApproxEq @3 result expected

-- Level 3 properties

prop_matMulIdentityLeft :: Property
prop_matMulIdentityLeft = forAll (genMatrix @3 @3) $ \a ->
  let eye = identityMatrix @3 @M.P :: Matrix 3 3 M.P Double
  in matApproxEq @3 @3 (matMul eye a) a

prop_matMulIdentityRight :: Property
prop_matMulIdentityRight = forAll (genMatrix @3 @3) $ \a ->
  let eye = identityMatrix @3 @M.P :: Matrix 3 3 M.P Double
  in matApproxEq @3 @3 (matMul a eye) a

prop_transposeInvolution :: Property
prop_transposeInvolution = forAll (genMatrix @3 @4) $ \a ->
  matApproxEq @3 @4 (transpose (transpose a)) a

prop_mAddCommutative :: Property
prop_mAddCommutative = forAll ((,) <$> genMatrix @3 @3 <*> genMatrix @3 @3) $ \(a, b) ->
  matApproxEq @3 @3 (mAdd a b) (mAdd b a)

-- | (AB)C ≈ A(BC) for random 5×5 matrices.
prop_matMulAssociative5 :: Property
prop_matMulAssociative5 =
  forAll ((,,) <$> genMatrix @5 @5 <*> genMatrix @5 @5 <*> genMatrix @5 @5) $ \(a, b, c) ->
    let lhs = matMul (matMul a b) c
        rhs = matMul a (matMul b c)
    in normFrob (mSub lhs rhs) / (normFrob lhs + 1e-15) < 1e-6

-- | I·A = A for 10×10 matrices.
prop_matMulIdentity10 :: Property
prop_matMulIdentity10 = forAll (genMatrix @10 @10) $ \a ->
  let eye = identityMatrix @10 @M.P :: Matrix 10 10 M.P Double
  in matApproxEq @10 @10 (matMul eye a) a

-- | Dot product of columns 0 and 1 of hilbertMatrix @5.
-- col0 = [1, 1/2, 1/3, 1/4, 1/5]
-- col1 = [1/2, 1/3, 1/4, 1/5, 1/6]
-- dot  = sum_{k=0}^{4} 1/((k+1)*(k+2)) = 1/2 + 1/6 + 1/12 + 1/20 + 1/30 = 50/60 = 5/6
test_dotHilbertColumns :: Assertion
test_dotHilbertColumns = do
  let h = hilbertMatrix @5 :: Matrix 5 5 M.P Double
      col0 = makeVector @5 @M.P $ \k -> h ! (k, 0)
      col1 = makeVector @5 @M.P $ \k -> h ! (k, 1)
      result = dot col0 col1
      expected = 5 / 6 :: Double
  assertBool ("dot of Hilbert cols 0,1 = 5/6, got " ++ show result)
    $ abs (result - expected) < 1e-12

-- Known-value test for 3×3 matrix multiplication
test_matMulKnown :: Assertion
test_matMulKnown = do
  -- A = [[1,2],[3,4]], B = [[5,6],[7,8]]
  -- AB = [[19,22],[43,50]]
  let a = makeMatrix @2 @2 @M.P $ \i j -> case (i,j) of
            (0,0) -> 1; (0,1) -> 2; (1,0) -> 3; (1,1) -> 4; _ -> 0 :: Double
      b = makeMatrix @2 @2 @M.P $ \i j -> case (i,j) of
            (0,0) -> 5; (0,1) -> 6; (1,0) -> 7; (1,1) -> 8; _ -> 0 :: Double
      c = matMul a b
  assertBool "c(0,0) = 19" $ (c ! (0,0)) ~= 19
  assertBool "c(0,1) = 22" $ (c ! (0,1)) ~= 22
  assertBool "c(1,0) = 43" $ (c ! (1,0)) ~= 43
  assertBool "c(1,1) = 50" $ (c ! (1,1)) ~= 50

-- | GEMM with α=2.0, β=0.5 on known 3×3 matrices.
-- A = [[1,2,3],[4,5,6],[7,8,9]]
-- B = [[9,8,7],[6,5,4],[3,2,1]]
-- C = [[1,0,0],[0,1,0],[0,0,1]]
-- Result = α*A*B + β*C
--
-- A*B = [[30,24,18],[84,69,54],[138,114,90]]
-- α*A*B = [[60,48,36],[168,138,108],[276,228,180]]
-- β*C   = [[0.5,0,0],[0,0.5,0],[0,0,0.5]]
-- Final = [[60.5,48,36],[168,138.5,108],[276,228,180.5]]
test_gemmLarger :: Assertion
test_gemmLarger = do
  let a = makeMatrix @3 @3 @M.P $ \i j ->
            fromIntegral (i * 3 + j + 1) :: Double
      b = makeMatrix @3 @3 @M.P $ \i j ->
            fromIntegral (9 - (i * 3 + j)) :: Double
      c = identityMatrix @3 @M.P :: Matrix 3 3 M.P Double
      result = gemm 2.0 a b 0.5 c
  assertBool "gemm(0,0) = 60.5" $ (result ! (0,0)) ~= 60.5
  assertBool "gemm(0,1) = 48"   $ (result ! (0,1)) ~= 48
  assertBool "gemm(0,2) = 36"   $ (result ! (0,2)) ~= 36
  assertBool "gemm(1,0) = 168"  $ (result ! (1,0)) ~= 168
  assertBool "gemm(1,1) = 138.5"$ (result ! (1,1)) ~= 138.5
  assertBool "gemm(1,2) = 108"  $ (result ! (1,2)) ~= 108
  assertBool "gemm(2,0) = 276"  $ (result ! (2,0)) ~= 276
  assertBool "gemm(2,1) = 228"  $ (result ! (2,1)) ~= 228
  assertBool "gemm(2,2) = 180.5"$ (result ! (2,2)) ~= 180.5