packages feed

moonlight-linalg-0.1.0.0: src-statics/Moonlight/LinAlg/Pure/Statics/Compile.hs

module Moonlight.LinAlg.Pure.Statics.Compile
  ( assembleEquilibriumEquations,
  )
where

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 (MoonlightError (..))
import Moonlight.LinAlg.Pure.Dense.Dynamic (mkDynMatrix, mkDynVector)
import Moonlight.LinAlg.Pure.Statics.Algebra
  ( allAxes,
    axisComponent,
    memberEndpoints,
    mkMemberRef,
    normalizeVec3,
    scaleVec3,
    subVec3,
  )
import Moonlight.LinAlg.Pure.Statics.Types
  ( CompiledEquilibrium,
    EquationRef (..),
    ForceNetwork,
    ForceNode,
    MemberRef,
    NodeRef,
    UnknownForce (..),
    Vec3,
    forceMembers,
    forceNodeLoad,
    forceNodePosition,
    forceNodeReactionAxes,
    forceNodes,
    mkCompiledEquilibrium,
  )
import Prelude

assembleEquilibriumEquations :: ForceNetwork -> Either MoonlightError CompiledEquilibrium
assembleEquilibriumEquations networkValue = do
  memberOrder <- canonicalMemberOrder (forceMembers networkValue)
  let nodeOrder = Map.keys (forceNodes networkValue)
      foundationEntries = supportedNodeEntries (forceNodes networkValue)
      foundationOrder = fst <$> foundationEntries
      reactionUnknowns =
        concatMap
          ( \(nodeRefValue, nodeValue) ->
              fmap
                (ReactionUnknown nodeRefValue)
                (forceNodeReactionAxes nodeValue)
          )
          foundationEntries
      equationOrder =
        concatMap
          (\nodeRefValue -> fmap (EquationRef nodeRefValue) allAxes)
          nodeOrder
      unknownOrder =
        fmap MemberUnknown memberOrder
          <> reactionUnknowns
  memberDirections <- Map.fromList <$> traverse (directionEntry networkValue) memberOrder
  coefficientRows <- traverse (equationCoefficients networkValue memberDirections unknownOrder) equationOrder
  rightHandSideValues <- traverse (equationRightHandSide networkValue) equationOrder
  coefficientMatrix <- mkDynMatrix (length equationOrder) (length unknownOrder) (concat coefficientRows)
  rightHandSideVector <- mkDynVector (length equationOrder) rightHandSideValues
  mkCompiledEquilibrium
    nodeOrder
    foundationOrder
    memberOrder
    memberDirections
    unknownOrder
    equationOrder
    coefficientMatrix
    rightHandSideVector

canonicalMemberOrder :: Set MemberRef -> Either MoonlightError [MemberRef]
canonicalMemberOrder memberRefs =
  fmap Set.toAscList
    ( Set.fromList
        <$> traverse
          ( \memberRefValue ->
              case memberEndpoints memberRefValue of
                (leftRef, rightRef) -> mkMemberRef leftRef rightRef
          )
          (Set.toAscList memberRefs)
    )

directionEntry :: ForceNetwork -> MemberRef -> Either MoonlightError (MemberRef, Vec3)
directionEntry networkValue memberRefValue =
  fmap ((,) memberRefValue) (memberDirection networkValue memberRefValue)

memberDirection :: ForceNetwork -> MemberRef -> Either MoonlightError Vec3
memberDirection networkValue memberRefValue =
  case memberEndpoints memberRefValue of
    (leftRef, rightRef) -> do
      leftNode <- lookupNode networkValue leftRef
      rightNode <- lookupNode networkValue rightRef
      normalizeVec3
        ( subVec3
            (forceNodePosition rightNode)
            (forceNodePosition leftNode)
        )

equationCoefficients ::
  ForceNetwork ->
  Map MemberRef Vec3 ->
  [UnknownForce] ->
  EquationRef ->
  Either MoonlightError [Double]
equationCoefficients networkValue memberDirections unknownOrder equationRefValue = do
  _ <- lookupNode networkValue (equationNodeRef equationRefValue)
  pure
    ( fmap
        (unknownCoefficient memberDirections equationRefValue)
        unknownOrder
    )

equationRightHandSide :: ForceNetwork -> EquationRef -> Either MoonlightError Double
equationRightHandSide networkValue equationRefValue = do
  nodeValue <- lookupNode networkValue (equationNodeRef equationRefValue)
  pure
    ( negate
        (axisComponent (equationAxis equationRefValue) (forceNodeLoad nodeValue))
    )

unknownCoefficient :: Map MemberRef Vec3 -> EquationRef -> UnknownForce -> Double
unknownCoefficient memberDirections equationRefValue unknownValue =
  case unknownValue of
    MemberUnknown memberRefValue ->
      maybe 0.0
        ( \directionValue ->
            maybe
              0.0
              (axisComponent (equationAxis equationRefValue))
              (memberContribution (equationNodeRef equationRefValue) memberRefValue directionValue)
        )
        (Map.lookup memberRefValue memberDirections)
    ReactionUnknown reactionNode reactionAxisValue ->
      if reactionNode == equationNodeRef equationRefValue && reactionAxisValue == equationAxis equationRefValue
        then 1.0
        else 0.0

memberContribution :: NodeRef -> MemberRef -> Vec3 -> Maybe Vec3
memberContribution nodeRefValue memberRefValue directionValue =
  case memberEndpoints memberRefValue of
    (leftRef, rightRef)
      | nodeRefValue == leftRef -> Just (scaleVec3 (-1.0) directionValue)
      | nodeRefValue == rightRef -> Just directionValue
      | otherwise -> Nothing

lookupNode :: ForceNetwork -> NodeRef -> Either MoonlightError ForceNode
lookupNode networkValue nodeRefValue =
  case Map.lookup nodeRefValue (forceNodes networkValue) of
    Nothing ->
      Left (InvariantViolation ("force network member references unknown node " <> show nodeRefValue))
    Just nodeValue -> Right nodeValue

supportedNodeEntries :: Map NodeRef ForceNode -> [(NodeRef, ForceNode)]
supportedNodeEntries =
  filter (not . null . forceNodeReactionAxes . snd) . Map.toAscList