moonlight-linalg-0.1.0.0: src-statics/Moonlight/LinAlg/Pure/Statics/Network.hs
{-# LANGUAGE StrictData #-}
module Moonlight.LinAlg.Pure.Statics.Network
( NetworkDeclaration,
NetworkBuildError (..),
nodeRef,
nodeRefLabel,
joint,
support,
supportOn,
load,
member,
network,
nodePosition,
nodeLoad,
nodeSupportAxes,
nodeReactionAxes,
networkNodeMap,
networkMemberSet,
)
where
import Control.Monad (foldM)
import Data.Foldable (traverse_)
import Data.Kind (Type)
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Set (Set)
import qualified Data.Set as Set
import Moonlight.Core (fieldValueValid)
import Moonlight.LinAlg.Pure.Geometry.Vec3
( Axis,
Vec3 (..),
magnitudeVec3,
normalizeVec3,
subVec3,
)
import Moonlight.LinAlg.Pure.Statics.Types
( ForceNetwork (..),
ForceNode,
MemberRef,
NodeRef (..),
SupportAxes,
fixedSupportAxes,
forceMembers,
forceNodeLoad,
forceNodePosition,
forceNodeReactionAxes,
forceNodeSupportAxes,
forceNodes,
memberEndpoints,
mkMemberRef,
mkSupportAxes,
supportedForceNode,
supportAxesList,
)
import Prelude
type NetworkDeclaration :: Type
data NetworkDeclaration
= JointDeclaration !String !Vec3
| SupportDeclaration !String !Vec3 !SupportAxes
| LoadDeclaration !String !Vec3 !Vec3
| MemberDeclaration !String !String
deriving stock (Eq, Show)
type NetworkBuildError :: Type
data NetworkBuildError
= EmptyNodeLabel
| NonFiniteNodePosition !String !Vec3
| NonFiniteNodeLoad !String !Vec3
| MissingNodePosition !String
| ConflictingNodePosition !String !Vec3 !Vec3
| NonFiniteAccumulatedLoad !String !Vec3
| UnknownMemberEndpoint !String
| SelfMember !String
| NonFiniteMemberGeometry !String !String
| DegenerateMember !String !String
deriving stock (Eq, Show)
nodeRef :: String -> Either NetworkBuildError NodeRef
nodeRef labelValue
| null labelValue = Left EmptyNodeLabel
| otherwise = Right (NodeRef labelValue)
nodeRefLabel :: NodeRef -> String
nodeRefLabel (NodeRef labelValue) =
labelValue
joint :: String -> Vec3 -> NetworkDeclaration
joint =
JointDeclaration
support :: String -> Vec3 -> NetworkDeclaration
support labelValue positionValue =
SupportDeclaration labelValue positionValue fixedSupportAxes
supportOn :: String -> Vec3 -> SupportAxes -> NetworkDeclaration
supportOn =
SupportDeclaration
load :: String -> Vec3 -> Vec3 -> NetworkDeclaration
load =
LoadDeclaration
member :: String -> String -> NetworkDeclaration
member =
MemberDeclaration
network :: [NetworkDeclaration] -> Either NetworkBuildError ForceNetwork
network declarations = do
accumulatedNetwork <-
foldM
collectDeclaration
emptyNetworkAccumulator
declarations
finalizedNodes <-
Map.traverseWithKey
finalizePartialNode
(networkAccumulatorNodes accumulatedNetwork)
traverse_
(validateMemberGeometry finalizedNodes)
(Set.toAscList (networkAccumulatorMembers accumulatedNetwork))
Right
ForceNetwork
{ forceNodes = finalizedNodes,
forceMembers = networkAccumulatorMembers accumulatedNetwork
}
nodePosition :: ForceNode -> Vec3
nodePosition =
forceNodePosition
nodeLoad :: ForceNode -> Vec3
nodeLoad =
forceNodeLoad
nodeSupportAxes :: ForceNode -> SupportAxes
nodeSupportAxes =
forceNodeSupportAxes
nodeReactionAxes :: ForceNode -> [Axis]
nodeReactionAxes =
forceNodeReactionAxes
networkNodeMap :: ForceNetwork -> Map NodeRef ForceNode
networkNodeMap =
forceNodes
networkMemberSet :: ForceNetwork -> Set MemberRef
networkMemberSet =
forceMembers
type ExactVec3 :: Type
data ExactVec3 = ExactVec3
{ exactVec3X :: !Rational,
exactVec3Y :: !Rational,
exactVec3Z :: !Rational
}
deriving stock (Eq, Show)
zeroExactVec3 :: ExactVec3
zeroExactVec3 =
ExactVec3 0 0 0
exactVec3FromVec3 :: Vec3 -> ExactVec3
exactVec3FromVec3 (Vec3 xValue yValue zValue) =
ExactVec3
(toRational xValue)
(toRational yValue)
(toRational zValue)
addExactVec3 :: ExactVec3 -> ExactVec3 -> ExactVec3
addExactVec3 leftValue rightValue =
ExactVec3
{ exactVec3X = exactVec3X leftValue + exactVec3X rightValue,
exactVec3Y = exactVec3Y leftValue + exactVec3Y rightValue,
exactVec3Z = exactVec3Z leftValue + exactVec3Z rightValue
}
type PartialNode :: Type
data PartialNode = PartialNode
{ partialNodePositions :: !(Set Vec3),
partialNodeLoadExact :: !ExactVec3,
partialNodeSupportAxes :: !(Set Axis)
}
deriving stock (Eq, Show)
emptyPartialNode :: PartialNode
emptyPartialNode =
PartialNode
{ partialNodePositions = Set.empty,
partialNodeLoadExact = zeroExactVec3,
partialNodeSupportAxes = Set.empty
}
type NetworkAccumulator :: Type
data NetworkAccumulator = NetworkAccumulator
{ networkAccumulatorNodes :: !(Map NodeRef PartialNode),
networkAccumulatorMembers :: !(Set MemberRef)
}
deriving stock (Eq, Show)
emptyNetworkAccumulator :: NetworkAccumulator
emptyNetworkAccumulator =
NetworkAccumulator
{ networkAccumulatorNodes = Map.empty,
networkAccumulatorMembers = Set.empty
}
collectDeclaration ::
NetworkAccumulator ->
NetworkDeclaration ->
Either NetworkBuildError NetworkAccumulator
collectDeclaration accumulator declarationValue =
case declarationValue of
JointDeclaration labelValue positionValue ->
collectNodeDeclaration
labelValue
positionValue
Nothing
Set.empty
accumulator
SupportDeclaration labelValue positionValue supportAxes ->
collectNodeDeclaration
labelValue
positionValue
Nothing
(Set.fromList (supportAxesList supportAxes))
accumulator
LoadDeclaration labelValue positionValue loadValue ->
collectNodeDeclaration
labelValue
positionValue
(Just loadValue)
Set.empty
accumulator
MemberDeclaration leftLabel rightLabel ->
collectMemberDeclaration
leftLabel
rightLabel
accumulator
collectNodeDeclaration ::
String ->
Vec3 ->
Maybe Vec3 ->
Set Axis ->
NetworkAccumulator ->
Either NetworkBuildError NetworkAccumulator
collectNodeDeclaration labelValue positionValue maybeLoadValue supportAxes accumulator = do
nodeReference <- nodeRef labelValue
if finiteVec3 positionValue
then Right ()
else Left (NonFiniteNodePosition labelValue positionValue)
traverse_ validateLoad maybeLoadValue
let currentPartialNode =
Map.findWithDefault
emptyPartialNode
nodeReference
(networkAccumulatorNodes accumulator)
loadContribution =
maybe zeroExactVec3 exactVec3FromVec3 maybeLoadValue
updatedPartialNode =
currentPartialNode
{ partialNodePositions =
Set.insert positionValue (partialNodePositions currentPartialNode),
partialNodeLoadExact =
addExactVec3 (partialNodeLoadExact currentPartialNode) loadContribution,
partialNodeSupportAxes =
Set.union (partialNodeSupportAxes currentPartialNode) supportAxes
}
Right
accumulator
{ networkAccumulatorNodes =
Map.insert
nodeReference
updatedPartialNode
(networkAccumulatorNodes accumulator)
}
where
validateLoad loadValue
| finiteVec3 loadValue = Right ()
| otherwise = Left (NonFiniteNodeLoad labelValue loadValue)
collectMemberDeclaration ::
String ->
String ->
NetworkAccumulator ->
Either NetworkBuildError NetworkAccumulator
collectMemberDeclaration leftLabel rightLabel accumulator = do
leftReference <- nodeRef leftLabel
rightReference <- nodeRef rightLabel
if leftReference == rightReference
then Left (SelfMember leftLabel)
else
case mkMemberRef leftReference rightReference of
Left _ -> Left (SelfMember leftLabel)
Right memberReference ->
Right
accumulator
{ networkAccumulatorMembers =
Set.insert
memberReference
(networkAccumulatorMembers accumulator)
}
finalizePartialNode ::
NodeRef ->
PartialNode ->
Either NetworkBuildError ForceNode
finalizePartialNode nodeReference partialNode = do
positionValue <-
case Set.toAscList (partialNodePositions partialNode) of
[] -> Left (MissingNodePosition (nodeRefLabel nodeReference))
[singlePosition] -> Right singlePosition
firstPosition : secondPosition : _ ->
Left
( ConflictingNodePosition
(nodeRefLabel nodeReference)
firstPosition
secondPosition
)
loadValue <-
finalizeExactLoad
(nodeRefLabel nodeReference)
(partialNodeLoadExact partialNode)
let supportAxes = mkSupportAxes (Set.toAscList (partialNodeSupportAxes partialNode))
Right (supportedForceNode positionValue loadValue supportAxes)
finalizeExactLoad :: String -> ExactVec3 -> Either NetworkBuildError Vec3
finalizeExactLoad labelValue exactLoad =
let loadValue =
Vec3
(fromRational (exactVec3X exactLoad))
(fromRational (exactVec3Y exactLoad))
(fromRational (exactVec3Z exactLoad))
in if finiteVec3 loadValue
then Right loadValue
else Left (NonFiniteAccumulatedLoad labelValue loadValue)
validateMemberGeometry ::
Map NodeRef ForceNode ->
MemberRef ->
Either NetworkBuildError ()
validateMemberGeometry nodeValues memberReference = do
let (leftReference, rightReference) = memberEndpoints memberReference
leftLabel = nodeRefLabel leftReference
rightLabel = nodeRefLabel rightReference
leftNode <- requireNode leftLabel leftReference nodeValues
rightNode <- requireNode rightLabel rightReference nodeValues
let displacement =
subVec3
(forceNodePosition rightNode)
(forceNodePosition leftNode)
displacementMagnitude = magnitudeVec3 displacement
if not (finiteVec3 displacement) || not (fieldValueValid displacementMagnitude)
then Left (NonFiniteMemberGeometry leftLabel rightLabel)
else
case normalizeVec3 displacement of
Left _ -> Left (DegenerateMember leftLabel rightLabel)
Right _ -> Right ()
requireNode ::
String ->
NodeRef ->
Map NodeRef ForceNode ->
Either NetworkBuildError ForceNode
requireNode labelValue nodeReference nodeValues =
case Map.lookup nodeReference nodeValues of
Nothing -> Left (UnknownMemberEndpoint labelValue)
Just nodeValue -> Right nodeValue
finiteVec3 :: Vec3 -> Bool
finiteVec3 (Vec3 xValue yValue zValue) =
fieldValueValid xValue && fieldValueValid yValue && fieldValueValid zValue