packages feed

moonlight-linalg-0.1.0.0: src-laws/Moonlight/LinAlg/Effect/Harness/Core.hs

module Moonlight.LinAlg.Effect.Harness.Core
  ( approxTolerance,
    orthonormalTolerance,
    residualTolerance,
    matrix3Product,
    assertApproxEqual,
    assertApproxEqualWith,
    assertApproxList,
    assertApproxListWith,
    assertRightBool,
    assertRightProperty,
    exactRightProperty,
    matrixRows3,
    matrix3VectorProduct,
    maxAbsDifference,
    vectorDot,
    vectorNorm,
  )
where

import Test.Tasty.QuickCheck qualified as QC

approxTolerance :: Double
approxTolerance =
  1.0e-8

residualTolerance :: Double
residualTolerance =
  1.0e-5

orthonormalTolerance :: Double
orthonormalTolerance =
  1.0e-6

assertApproxEqual :: Double -> Double -> Bool
assertApproxEqual expected actual =
  assertApproxEqualWith approxTolerance expected actual

assertApproxEqualWith :: Double -> Double -> Double -> Bool
assertApproxEqualWith tolerance expected actual =
  abs (expected - actual) <= tolerance

assertApproxList :: [Double] -> [Double] -> Bool
assertApproxList expected actual =
  assertApproxListWith approxTolerance expected actual

assertApproxListWith :: Double -> [Double] -> [Double] -> Bool
assertApproxListWith tolerance expected actual =
  length expected == length actual
    && and (zipWith (assertApproxEqualWith tolerance) expected actual)

assertRightBool :: Either failure Bool -> Bool
assertRightBool =
  either (const False) id

exactRightProperty :: (Eq value, Show failure, Show value) => Either failure value -> Either failure value -> QC.Property
exactRightProperty left right =
  case (left, right) of
    (Right leftValue, Right rightValue) ->
      QC.counterexample (show (leftValue, rightValue)) (leftValue == rightValue)
    (Left leftFailure, _) ->
      QC.counterexample (show leftFailure) False
    (_, Left rightFailure) ->
      QC.counterexample (show rightFailure) False

assertRightProperty :: Show failure => Either failure Bool -> QC.Property
assertRightProperty result =
  case result of
    Left failure ->
      QC.counterexample (show failure) False
    Right accepted ->
      QC.property accepted

matrixRows3 :: [a] -> [[a]]
matrixRows3 values =
  case values of
    [a00, a01, a02, a10, a11, a12, a20, a21, a22] ->
      [[a00, a01, a02], [a10, a11, a12], [a20, a21, a22]]
    _ -> []

matrix3VectorProduct :: [[Double]] -> [Double] -> [Double]
matrix3VectorProduct rows vectorValue =
  fmap (`vectorDot` vectorValue) rows

matrix3Product :: [[Double]] -> [[Double]] -> [[Double]]
matrix3Product leftRows rightRows =
  let rightColumns = transpose3 rightRows
   in fmap (\leftRow -> fmap (vectorDot leftRow) rightColumns) leftRows

vectorDot :: [Double] -> [Double] -> Double
vectorDot left right =
  sum (zipWith (*) left right)

vectorNorm :: [Double] -> Double
vectorNorm values =
  sqrt (sum ((\entryValue -> entryValue * entryValue) <$> values))

maxAbsDifference :: [Double] -> [Double] -> Double
maxAbsDifference expected actual =
  maximum (0.0 : zipWith (\leftValue rightValue -> abs (leftValue - rightValue)) expected actual)

transpose3 :: [[a]] -> [[a]]
transpose3 rows =
  case rows of
    [[a00, a01, a02], [a10, a11, a12], [a20, a21, a22]] ->
      [[a00, a10, a20], [a01, a11, a21], [a02, a12, a22]]
    _ -> []