moonlight-linalg-0.1.0.0: bench/statics/GeometryStatics.hs
{-# LANGUAGE DataKinds #-}
module GeometryStatics
( geometryStaticsBenchmarks,
geometryStaticsOnceBenchmarks,
)
where
import Control.DeepSeq (NFData (..))
import Data.Bifunctor (first)
import Data.List.NonEmpty qualified as NonEmpty
import Data.Map.Strict qualified as Map
import Env (BenchmarkSelection (..))
import Fixtures
( staticsBenchmarkNetwork,
)
import Types
( BenchmarkSetup (..),
BenchmarkWeight (..),
OnceBenchmark,
PreparedBenchmarkRow (..),
eitherBenchmarkWeight,
renderPreparedBenchmark,
renderPreparedOnceBenchmark,
)
import Moonlight.LinAlg.Dense
( Matrix,
Vector,
dynMatrixToList,
dynVectorToList,
toListMatrix,
toListVector,
)
import Moonlight.LinAlg.Geometry
( AABB,
Symmetric3 (..),
Vec3 (..),
aabbRadius,
crossVec3,
dotVec3,
eigendecomposeSymmetric3,
expandAabb,
magnitudeVec3,
normalizeVec3,
symmetricAabb,
symmetric3Entries,
translateAabb,
unionAabb,
vec3ToList,
)
import Moonlight.LinAlg.Statics
( CompiledEquilibrium,
EquilibriumResult (..),
EquilibriumSolution (..),
EquilibriumViolation (..),
ForceNetwork,
assembleEquilibriumEquations,
checkEquilibrium,
compiledCoefficientMatrix,
compiledNodeOrder,
compiledRightHandSide,
compiledUnknownOrder,
)
import Test.Tasty.Bench (Benchmark, bgroup)
import Prelude
data GeometryStaticsCase = GeometryStaticsCase
{ geometryStaticsLabel :: !String,
geometryVectors :: ![Vec3],
geometryBoxes :: ![AABB],
geometrySymmetricTensors :: ![Symmetric3 Double],
geometryStaticsNetwork :: !ForceNetwork
}
instance NFData GeometryStaticsCase where
rnf benchmarkCase =
rnf (geometryStaticsLabel benchmarkCase)
`seq` rnf (vec3ToList =<< geometryVectors benchmarkCase)
`seq` rnf (aabbRadius <$> geometryBoxes benchmarkCase)
`seq` rnf (symmetric3Entries =<< geometrySymmetricTensors benchmarkCase)
`seq` geometryStaticsNetwork benchmarkCase
`seq` ()
geometryStaticsBenchmarks :: BenchmarkSelection -> Benchmark
geometryStaticsBenchmarks benchmarkSelection =
bgroup
"geometry and statics"
(geometryStaticsBenchmark <$> geometryStaticsSpans benchmarkSelection)
geometryStaticsOnceBenchmarks :: BenchmarkSelection -> [OnceBenchmark]
geometryStaticsOnceBenchmarks benchmarkSelection =
geometryStaticsOnceBenchmark =<< geometryStaticsSpans benchmarkSelection
geometryStaticsSpans :: BenchmarkSelection -> [Int]
geometryStaticsSpans benchmarkSelection =
[8]
<> [24 | includeBroadMedium benchmarkSelection || includeBroadLarge benchmarkSelection]
<> [64 | includeBroadLarge benchmarkSelection]
geometryStaticsBenchmark :: Int -> Benchmark
geometryStaticsBenchmark spanCount =
bgroup
("spans=" <> show spanCount)
(renderPreparedBenchmark (prepareGeometryStaticsCase spanCount) <$> geometryStaticsRows)
geometryStaticsOnceBenchmark :: Int -> [OnceBenchmark]
geometryStaticsOnceBenchmark spanCount =
renderPreparedOnceBenchmark ("geometry and statics.spans=" <> show spanCount <> ".") (prepareGeometryStaticsCase spanCount)
<$> geometryStaticsRows
geometryStaticsRows :: [PreparedBenchmarkRow GeometryStaticsCase]
geometryStaticsRows =
[ PurePreparedBenchmarkRow "Vec3 normalize/cross batch" vec3BatchWeight,
PurePreparedBenchmarkRow "AABB union/translate batch" aabbBatchWeight,
PurePreparedBenchmarkRow "Symmetric3 eigendecompose batch" symmetric3EigenBatchWeight,
PurePreparedBenchmarkRow "assemble equilibrium" staticsAssembleWeight,
PurePreparedBenchmarkRow "check equilibrium" staticsSolveWeight
]
prepareGeometryStaticsCase :: Int -> BenchmarkSetup GeometryStaticsCase
prepareGeometryStaticsCase spanCount =
BenchmarkSetup $ do
networkValue <- first (("statics fixture failed: " <>)) (staticsBenchmarkNetwork spanCount)
boxes <- boxFixture (spanCount * 16)
pure
GeometryStaticsCase
{ geometryStaticsLabel = "spans=" <> show spanCount,
geometryVectors = vectorFixture (spanCount * 32),
geometryBoxes = boxes,
geometrySymmetricTensors = symmetric3Fixture (spanCount * 16),
geometryStaticsNetwork = networkValue
}
vectorFixture :: Int -> [Vec3]
vectorFixture count =
[ Vec3
(1.0 + fromIntegral (indexValue `mod` 13))
(2.0 + fromIntegral (indexValue `mod` 17) / 2.0)
(3.0 + fromIntegral (indexValue `mod` 19) / 3.0)
| indexValue <- [0 .. count - 1]
]
boxFixture :: Int -> Either String [AABB]
boxFixture count =
traverse boxAt [0 .. count - 1]
where
boxAt :: Int -> Either String AABB
boxAt indexValue =
let translationVector =
Vec3
(fromIntegral indexValue * 0.01)
(fromIntegral (indexValue `mod` 11) * 0.02)
0.0
halfY = 1.0 + fromIntegral (indexValue `mod` 5) * 0.1
in maybe
(Left "AABB fixture half-extents violated constructor contract")
(Right . translateAabb translationVector)
(symmetricAabb 1.0 halfY 1.5)
symmetric3Fixture :: Int -> [Symmetric3 Double]
symmetric3Fixture count =
tensorAt <$> [0 .. count - 1]
where
tensorAt :: Int -> Symmetric3 Double
tensorAt indexValue =
let xValue = 1.0 + fromIntegral (indexValue `mod` 7) * 0.125
yValue = 2.0 + fromIntegral (indexValue `mod` 11) * 0.0625
zValue = 3.0 + fromIntegral (indexValue `mod` 13) * 0.03125
couplingScale = fromIntegral (indexValue `mod` 5) * 0.01
in Symmetric3
{ sym3XX = xValue,
sym3XY = couplingScale,
sym3XZ = -0.5 * couplingScale,
sym3YY = yValue,
sym3YZ = 0.25 * couplingScale,
sym3ZZ = zValue
}
vec3BatchWeight :: GeometryStaticsCase -> BenchmarkWeight
vec3BatchWeight benchmarkCase =
eitherBenchmarkWeight
(geometryStaticsLabel benchmarkCase <> " Vec3 normalize/cross batch")
vectorBatchChecksum
( do
normalized <- traverse normalizeVec3 (geometryVectors benchmarkCase)
pure (zipWith crossVec3 normalized (drop 1 normalized), normalized)
)
aabbBatchWeight :: GeometryStaticsCase -> BenchmarkWeight
aabbBatchWeight benchmarkCase =
eitherBenchmarkWeight
(geometryStaticsLabel benchmarkCase <> " AABB union/translate batch")
aabbRadius
( do
seedBox <- maybeToEither "seed AABB half-extents violated constructor contract" (symmetricAabb 1.0 1.0 1.0)
expandedBoxes <- traverse (maybeToEither "AABB expansion inverted a box" . expandAabb 0.05) (geometryBoxes benchmarkCase)
pure (foldr unionAabb seedBox expandedBoxes)
)
symmetric3EigenBatchWeight :: GeometryStaticsCase -> BenchmarkWeight
symmetric3EigenBatchWeight benchmarkCase =
eitherBenchmarkWeight
(geometryStaticsLabel benchmarkCase <> " Symmetric3 eigendecompose batch")
symmetric3EigenBatchChecksum
(traverse eigendecomposeSymmetric3 (geometrySymmetricTensors benchmarkCase))
staticsAssembleWeight :: GeometryStaticsCase -> BenchmarkWeight
staticsAssembleWeight benchmarkCase =
eitherBenchmarkWeight
(geometryStaticsLabel benchmarkCase <> " assemble equilibrium")
compiledEquilibriumChecksum
(assembleEquilibriumEquations (geometryStaticsNetwork benchmarkCase))
staticsSolveWeight :: GeometryStaticsCase -> BenchmarkWeight
staticsSolveWeight benchmarkCase =
eitherBenchmarkWeight
(geometryStaticsLabel benchmarkCase <> " check equilibrium")
equilibriumResultChecksum
(checkEquilibrium (geometryStaticsNetwork benchmarkCase))
vectorBatchChecksum :: ([Vec3], [Vec3]) -> Double
vectorBatchChecksum (crossVectors, normalizedVectors) =
sum (magnitudeVec3 <$> crossVectors)
+ sum (zipWith dotVec3 normalizedVectors (drop 1 normalizedVectors))
symmetric3EigenBatchChecksum :: [(Vector 3 Double, Matrix 3 3 Double)] -> Double
symmetric3EigenBatchChecksum =
sum . fmap symmetric3EigenChecksum
symmetric3EigenChecksum :: (Vector 3 Double, Matrix 3 3 Double) -> Double
symmetric3EigenChecksum (eigenvalues, eigenvectors) =
sum (abs <$> toListVector eigenvalues) + sum (abs <$> toListMatrix eigenvectors)
compiledEquilibriumChecksum :: CompiledEquilibrium -> Double
compiledEquilibriumChecksum compiledValue =
fromIntegral (length (compiledNodeOrder compiledValue))
+ fromIntegral (length (compiledUnknownOrder compiledValue))
+ sum (abs <$> dynMatrixToList (compiledCoefficientMatrix compiledValue))
+ sum (abs <$> dynVectorToList (compiledRightHandSide compiledValue))
equilibriumResultChecksum :: EquilibriumResult -> Double
equilibriumResultChecksum resultValue =
case resultValue of
InEquilibrium solutionValue -> equilibriumSolutionChecksum solutionValue
Disequilibrium violations -> sum (violationResidualMagnitude <$> NonEmpty.toList violations)
equilibriumSolutionChecksum :: EquilibriumSolution -> Double
equilibriumSolutionChecksum solutionValue =
fromIntegral (Map.size (equilibriumMemberForces solutionValue))
+ fromIntegral (Map.size (equilibriumReactionForces solutionValue))
+ fromIntegral (Map.size (equilibriumResidualForces solutionValue))
+ sum (abs <$> Map.elems (equilibriumMemberForces solutionValue))
maybeToEither :: err -> Maybe value -> Either err value
maybeToEither failureValue =
maybe (Left failureValue) Right