moonlight-linalg-0.1.0.0: src-laws/Moonlight/LinAlg/Effect/Harness/Geometry.hs
module Moonlight.LinAlg.Effect.Harness.Geometry
( vec3AddCommutativeAssociativeLaw,
vec3DotSymmetricLaw,
vec3NormalizeUnitLaw,
aabbUnionCommutativeAssociativeLaw,
aabbUnionContainsOperandsLaw,
aabbIntersectionCommutativeLaw,
symmetricOuterApplyAgreementLaw,
geometrySymmetricEigenReconstructsLaw,
geometrySymmetricEigenOrthonormalLaw,
)
where
import Moonlight.LinAlg
( AABB,
Symmetric3 (..),
Vec3 (..),
aabbMax,
aabbMin,
addVec3,
applySymmetric3,
diagonalSymmetric3,
dotVec3,
eigendecomposeSymmetric3,
magnitudeVec3,
mkAabb,
normalizeVec3,
outerSymmetric3,
scaleVec3,
symmetric3ToMatrix,
toListMatrix,
toListVector,
unionAabb,
)
import Moonlight.LinAlg.Effect.Harness.Core (assertApproxEqual, assertApproxList, assertRightProperty, matrix3Product)
import Test.Tasty.QuickCheck qualified as QC
newtype GeneratedVec3 = GeneratedVec3 Vec3
deriving stock (Eq, Show)
newtype NonZeroVec3 = NonZeroVec3 Vec3
deriving stock (Eq, Show)
newtype GeneratedAABB = GeneratedAABB AABB
deriving stock (Eq, Show)
newtype GeneratedSymmetric3 = GeneratedSymmetric3 (Symmetric3 Double)
deriving stock (Eq, Show)
newtype GeneratedWeight = GeneratedWeight Double
deriving stock (Eq, Show)
instance QC.Arbitrary GeneratedVec3 where
arbitrary =
GeneratedVec3
<$> (Vec3 <$> component <*> component <*> component)
instance QC.Arbitrary NonZeroVec3 where
arbitrary =
NonZeroVec3
<$> QC.suchThat
(Vec3 <$> component <*> component <*> component)
(\value -> magnitudeVec3 value > 1.0e-6)
instance QC.Arbitrary GeneratedAABB where
arbitrary = do
minX <- component
minY <- component
minZ <- component
sizeX <- nonNegativeComponent
sizeY <- nonNegativeComponent
sizeZ <- nonNegativeComponent
case mkAabb (Vec3 minX minY minZ) (Vec3 (minX + sizeX) (minY + sizeY) (minZ + sizeZ)) of
Nothing -> QC.discard
Just boxValue -> pure (GeneratedAABB boxValue)
instance QC.Arbitrary GeneratedSymmetric3 where
arbitrary =
GeneratedSymmetric3 <$> QC.oneof [repeatedSpectrum, tinyGapSpectrum, badlyScaledSpectrum, coupledSpectrum]
instance QC.Arbitrary GeneratedWeight where
arbitrary =
GeneratedWeight <$> component
vec3AddCommutativeAssociativeLaw :: QC.Property
vec3AddCommutativeAssociativeLaw =
QC.property vec3AddCommutativeAssociativeLawProperty
vec3DotSymmetricLaw :: QC.Property
vec3DotSymmetricLaw =
QC.property vec3DotSymmetricLawProperty
vec3NormalizeUnitLaw :: QC.Property
vec3NormalizeUnitLaw =
QC.property vec3NormalizeUnitLawProperty
aabbUnionCommutativeAssociativeLaw :: QC.Property
aabbUnionCommutativeAssociativeLaw =
QC.property aabbUnionCommutativeAssociativeLawProperty
aabbUnionContainsOperandsLaw :: QC.Property
aabbUnionContainsOperandsLaw =
QC.property aabbUnionContainsOperandsLawProperty
aabbIntersectionCommutativeLaw :: QC.Property
aabbIntersectionCommutativeLaw =
QC.property aabbIntersectionCommutativeLawProperty
symmetricOuterApplyAgreementLaw :: QC.Property
symmetricOuterApplyAgreementLaw =
QC.property symmetricOuterApplyAgreementLawProperty
geometrySymmetricEigenReconstructsLaw :: QC.Property
geometrySymmetricEigenReconstructsLaw =
QC.property geometrySymmetricEigenReconstructsLawProperty
geometrySymmetricEigenOrthonormalLaw :: QC.Property
geometrySymmetricEigenOrthonormalLaw =
QC.property geometrySymmetricEigenOrthonormalLawProperty
component :: QC.Gen Double
component =
fromIntegral <$> QC.chooseInt (-8, 8)
nonNegativeComponent :: QC.Gen Double
nonNegativeComponent =
fromIntegral <$> QC.chooseInt (0, 8)
vec3AddCommutativeAssociativeLawProperty :: GeneratedVec3 -> GeneratedVec3 -> GeneratedVec3 -> QC.Property
vec3AddCommutativeAssociativeLawProperty (GeneratedVec3 leftValue) (GeneratedVec3 middleValue) (GeneratedVec3 rightValue) =
QC.property
( addVec3 leftValue middleValue == addVec3 middleValue leftValue
&& addVec3 (addVec3 leftValue middleValue) rightValue == addVec3 leftValue (addVec3 middleValue rightValue)
)
vec3DotSymmetricLawProperty :: GeneratedVec3 -> GeneratedVec3 -> QC.Property
vec3DotSymmetricLawProperty (GeneratedVec3 leftValue) (GeneratedVec3 rightValue) =
QC.property (assertApproxEqual (dotVec3 leftValue rightValue) (dotVec3 rightValue leftValue))
vec3NormalizeUnitLawProperty :: NonZeroVec3 -> QC.Property
vec3NormalizeUnitLawProperty (NonZeroVec3 value) =
assertRightProperty $ do
normalized <- normalizeVec3 value
pure (assertApproxEqual 1.0 (magnitudeVec3 normalized))
aabbUnionCommutativeAssociativeLawProperty :: GeneratedAABB -> GeneratedAABB -> GeneratedAABB -> QC.Property
aabbUnionCommutativeAssociativeLawProperty (GeneratedAABB leftValue) (GeneratedAABB middleValue) (GeneratedAABB rightValue) =
QC.property
( unionAabb leftValue middleValue == unionAabb middleValue leftValue
&& unionAabb (unionAabb leftValue middleValue) rightValue == unionAabb leftValue (unionAabb middleValue rightValue)
)
aabbUnionContainsOperandsLawProperty :: GeneratedAABB -> GeneratedAABB -> QC.Property
aabbUnionContainsOperandsLawProperty (GeneratedAABB leftValue) (GeneratedAABB rightValue) =
let unionValue = unionAabb leftValue rightValue
in QC.property (aabbContains unionValue leftValue && aabbContains unionValue rightValue)
aabbIntersectionCommutativeLawProperty :: GeneratedAABB -> GeneratedAABB -> QC.Property
aabbIntersectionCommutativeLawProperty (GeneratedAABB leftValue) (GeneratedAABB rightValue) =
QC.property (intersectAabb leftValue rightValue == intersectAabb rightValue leftValue)
symmetricOuterApplyAgreementLawProperty :: GeneratedWeight -> GeneratedVec3 -> GeneratedVec3 -> QC.Property
symmetricOuterApplyAgreementLawProperty (GeneratedWeight weightValue) (GeneratedVec3 basisValue) (GeneratedVec3 inputValue) =
let tensorValue = outerSymmetric3 weightValue basisValue
expectedValue = scaleVec3 (weightValue * dotVec3 basisValue inputValue) basisValue
in QC.property (vec3Approx expectedValue (applySymmetric3 tensorValue inputValue))
geometrySymmetricEigenReconstructsLawProperty :: GeneratedSymmetric3 -> QC.Property
geometrySymmetricEigenReconstructsLawProperty (GeneratedSymmetric3 tensorValue) =
assertRightProperty $ do
matrixValue <- symmetric3ToMatrix tensorValue
(eigenvalues, eigenvectors) <- eigendecomposeSymmetric3 tensorValue
let originalRows = rows3 (toListMatrix matrixValue)
eigenvectorRows = rows3 (toListMatrix eigenvectors)
diagonalRows = diagonal3 (toListVector eigenvalues)
reconstructedRows = matrix3Product (matrix3Product eigenvectorRows diagonalRows) (transposeRows3 eigenvectorRows)
pure (and (zipWith assertApproxList originalRows reconstructedRows))
geometrySymmetricEigenOrthonormalLawProperty :: GeneratedSymmetric3 -> QC.Property
geometrySymmetricEigenOrthonormalLawProperty (GeneratedSymmetric3 tensorValue) =
assertRightProperty $ do
(_, eigenvectors) <- eigendecomposeSymmetric3 tensorValue
let eigenvectorRows = rows3 (toListMatrix eigenvectors)
gramRows = matrix3Product (transposeRows3 eigenvectorRows) eigenvectorRows
pure (and (zipWith assertApproxList identityRows3 gramRows))
repeatedSpectrum :: QC.Gen (Symmetric3 Double)
repeatedSpectrum =
pure (diagonalSymmetric3 2.0 2.0 5.0)
tinyGapSpectrum :: QC.Gen (Symmetric3 Double)
tinyGapSpectrum =
pure (diagonalSymmetric3 1.0 (1.0 + 1.0e-12) 3.0)
badlyScaledSpectrum :: QC.Gen (Symmetric3 Double)
badlyScaledSpectrum =
pure (diagonalSymmetric3 1.0e-12 1.0 1.0e12)
coupledSpectrum :: QC.Gen (Symmetric3 Double)
coupledSpectrum =
pure
Symmetric3
{ sym3XX = 3.0,
sym3XY = 1.0e-6,
sym3XZ = 2.0e-6,
sym3YY = 3.0 + 1.0e-12,
sym3YZ = -1.0e-6,
sym3ZZ = 7.0
}
intersectAabb :: AABB -> AABB -> Maybe AABB
intersectAabb leftValue rightValue =
mkAabb
(Vec3 (max lx rx) (max ly ry) (max lz rz))
(Vec3 (min lxx rxx) (min lyy ryy) (min lzz rzz))
where
Vec3 lx ly lz = aabbMin leftValue
Vec3 lxx lyy lzz = aabbMax leftValue
Vec3 rx ry rz = aabbMin rightValue
Vec3 rxx ryy rzz = aabbMax rightValue
aabbContains :: AABB -> AABB -> Bool
aabbContains outerValue innerValue =
let Vec3 ox oy oz = aabbMin outerValue
Vec3 oxx oyy ozz = aabbMax outerValue
Vec3 ix iy iz = aabbMin innerValue
Vec3 ixx iyy izz = aabbMax innerValue
in ox <= ix && oy <= iy && oz <= iz && ixx <= oxx && iyy <= oyy && izz <= ozz
vec3Approx :: Vec3 -> Vec3 -> Bool
vec3Approx (Vec3 ex ey ez) (Vec3 ax ay az) =
assertApproxList [ex, ey, ez] [ax, ay, az]
rows3 :: [a] -> [[a]]
rows3 values =
case values of
[a00, a01, a02, a10, a11, a12, a20, a21, a22] ->
[[a00, a01, a02], [a10, a11, a12], [a20, a21, a22]]
_ -> []
transposeRows3 :: [[a]] -> [[a]]
transposeRows3 rowsValue =
case rowsValue of
[[a00, a01, a02], [a10, a11, a12], [a20, a21, a22]] ->
[[a00, a10, a20], [a01, a11, a21], [a02, a12, a22]]
_ -> []
diagonal3 :: [Double] -> [[Double]]
diagonal3 values =
case values of
[xValue, yValue, zValue] ->
[[xValue, 0.0, 0.0], [0.0, yValue, 0.0], [0.0, 0.0, zValue]]
_ -> []
identityRows3 :: [[Double]]
identityRows3 =
[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]