packages feed

moonlight-homology-0.1.0.0: src-topology/Moonlight/Homology/Pure/Topology/MacroScaffold/Compose/Stitch.hs

module Moonlight.Homology.Pure.Topology.MacroScaffold.Compose.Stitch
  ( StitchBoundarySide (..),
    StitchSupportSelection (..),
    StitchSupportRefinement (..),
    StitchSemantics (..),
    StitchRoute (..),
    StitchRouteKey (..),
    MacroScaffoldStitchError (..),
    stitchRoutePair,
    uniqueRoutePairs,
  )
where

import Data.Containers.ListUtils (nubOrd)
import Data.Function ((&))
import Data.Kind (Type)
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as NonEmpty
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 (note)
import Moonlight.Homology.Pure.Carrier
  ( BasisCellRef,
  )
import Moonlight.Homology.Pure.Topology.MacroScaffold
  ( MacroScaffoldIR (..),
  )
import Moonlight.Homology.Pure.Topology.MacroScaffold.Potential
  ( PotentialValue,
  )
import Moonlight.Homology.Pure.Topology.MacroScaffold.Reeb
  ( Monotonicity (..),
    MorseReebArc (..),
    MorseReebNode (..),
    MorseReebScaffold (..),
    ReebArcId (..),
  )

type StitchBoundarySide :: Type
data StitchBoundarySide
  = LowerBoundary
  | UpperBoundary
  deriving stock (Eq, Ord, Show)

type StitchSupportSelection :: Type
data StitchSupportSelection
  = BoundarySupport
  | AnchorSupport
  deriving stock (Eq, Ord, Show)

type StitchSupportRefinement :: Type
data StitchSupportRefinement
  = KernelSupportRefinement
  | BoundaryEnvelopeRefinement
  | RegionalEnvelopeRefinement
  deriving stock (Eq, Ord, Show)

type StitchSemantics :: Type
data StitchSemantics = StitchSemantics
  { ssSourceBoundary :: StitchBoundarySide,
    ssTargetBoundary :: StitchBoundarySide,
    ssSupportSelection :: StitchSupportSelection,
    ssSupportRefinement :: StitchSupportRefinement
  }
  deriving stock (Eq, Ord, Show)

type StitchRoute :: Type -> Type -> Type
data StitchRoute route label = StitchRoute
  { stitchRouteKind :: route,
    stitchRouteRegions :: NonEmpty label
  }
  deriving stock (Eq, Show)

type StitchRouteKey :: Type -> Type -> Type
data StitchRouteKey route label = StitchRouteKey
  { srkRouteKind :: route,
    srkSourceRegion :: label,
    srkTargetRegion :: label
  }
  deriving stock (Eq, Ord, Show)

type MacroScaffoldStitchError :: Type -> Type
data MacroScaffoldStitchError label
  = MissingStitchRegion label
  | MissingBoundaryNode label StitchBoundarySide
  deriving stock (Eq, Show)

stitchRoutePair ::
  (Ord route, Ord label) =>
  (route -> StitchSemantics) ->
  Map label (Set BasisCellRef) ->
  MacroScaffoldIR ->
  (Int, [MorseReebArc], Map (StitchRouteKey route label) (Set BasisCellRef)) ->
  StitchRouteKey route label ->
  Either (MacroScaffoldStitchError label) (Int, [MorseReebArc], Map (StitchRouteKey route label) (Set BasisCellRef))
stitchRoutePair semanticsFor regionScopeMap scaffoldValue (nextArcId, stitchedArcs, stitchScopes) stitchKey
  | sourceLabel == targetLabel =
      Right (nextArcId, stitchedArcs, stitchScopes)
  | otherwise = do
      let semantics = semanticsFor routeKind
      (sourceRegionBasisRefs, sourceNode, sourceSupport) <-
        boundaryNodeAndSupport regionScopeMap scaffoldValue sourceLabel (ssSourceBoundary semantics)
      (targetRegionBasisRefs, targetNode, targetSupport) <-
        boundaryNodeAndSupport regionScopeMap scaffoldValue targetLabel (ssTargetBoundary semantics)
      let supportRefs =
            combineSupportRefs
              (ssSupportSelection semantics)
              (ssSupportRefinement semantics)
              sourceRegionBasisRefs
              targetRegionBasisRefs
              sourceSupport
              targetSupport
              sourceNode
              targetNode
          stitchedArc =
            MorseReebArc
              { morseReebArcId = ReebArcId nextArcId,
                morseReebArcSource = morseReebNodeId sourceNode,
                morseReebArcTarget = morseReebNodeId targetNode,
                morseReebArcMonotonicity = monotonicityBetween sourceNode targetNode,
                morseReebArcSupport = Set.toAscList supportRefs
              }
      Right
        ( nextArcId + 1,
          stitchedArc : stitchedArcs,
          Map.insertWith Set.union stitchKey supportRefs stitchScopes
        )
  where
    routeKind = srkRouteKind stitchKey
    sourceLabel = srkSourceRegion stitchKey
    targetLabel = srkTargetRegion stitchKey

uniqueRoutePairs :: (Ord route, Ord label) => [StitchRoute route label] -> [StitchRouteKey route label]
uniqueRoutePairs = nubOrd . concatMap routeTransitions

routeTransitions :: StitchRoute route label -> [StitchRouteKey route label]
routeTransitions routeValue =
  go firstLabel remainingLabels
  where
    routeKind = stitchRouteKind routeValue
    firstLabel :| remainingLabels = stitchRouteRegions routeValue
    go _ [] = []
    go previousLabel (nextLabel : rest) =
      StitchRouteKey routeKind previousLabel nextLabel : go nextLabel rest

boundaryNodeAndSupport ::
  Ord label =>
  Map label (Set BasisCellRef) ->
  MacroScaffoldIR ->
  label ->
  StitchBoundarySide ->
  Either (MacroScaffoldStitchError label) (Set BasisCellRef, MorseReebNode, Set BasisCellRef)
boundaryNodeAndSupport regionScopeMap scaffoldValue labelValue boundarySide = do
  regionBasisRefs <-
    note (MissingStitchRegion labelValue) (Map.lookup labelValue regionScopeMap)
  boundaryNodes <-
    boundaryNodesForRegion regionBasisRefs scaffoldValue boundarySide
      & note (MissingBoundaryNode labelValue boundarySide)
  let supportRefs =
        boundarySupportRefs regionBasisRefs scaffoldValue boundaryNodes
      representativeNode = selectRepresentativeNode boundaryNodes
  Right (regionBasisRefs, representativeNode, supportRefs)

boundaryNodesForRegion ::
  Set BasisCellRef ->
  MacroScaffoldIR ->
  StitchBoundarySide ->
  Maybe (NonEmpty MorseReebNode)
boundaryNodesForRegion regionBasisRefs scaffoldValue boundarySide =
  let regionNodes =
        filter
          (\nodeValue -> Set.member (morseReebNodeAnchor nodeValue) regionBasisRefs)
          (morseReebNodes (macroScaffoldReeb scaffoldValue))
   in case regionNodes of
        [] -> Nothing
        firstNode : restNodes ->
          let boundaryPotential =
                foldr
                  (selectPotential boundarySide . morseReebNodePotential)
                  (morseReebNodePotential firstNode)
                  restNodes
              matchingNodes =
                filter
                  ((== boundaryPotential) . morseReebNodePotential)
                  regionNodes
           in NonEmpty.nonEmpty matchingNodes

selectPotential :: StitchBoundarySide -> PotentialValue -> PotentialValue -> PotentialValue
selectPotential boundarySide candidate current =
  case boundarySide of
    LowerBoundary ->
      min candidate current
    UpperBoundary ->
      max candidate current

boundarySupportRefs ::
  Set BasisCellRef ->
  MacroScaffoldIR ->
  NonEmpty MorseReebNode ->
  Set BasisCellRef
boundarySupportRefs regionBasisRefs scaffoldValue boundaryNodes =
  let boundaryNodeIds =
        Set.fromList (fmap morseReebNodeId (NonEmpty.toList boundaryNodes))
      incidentArcSupport =
        morseReebArcs (macroScaffoldReeb scaffoldValue)
          & foldr
            ( \arcValue supportRefs ->
                if Set.member (morseReebArcSource arcValue) boundaryNodeIds
                  || Set.member (morseReebArcTarget arcValue) boundaryNodeIds
                  then
                    Set.union
                      supportRefs
                      ( Set.fromList
                          ( filter (`Set.member` regionBasisRefs) (morseReebArcSupport arcValue) )
                      )
                  else supportRefs
            )
            Set.empty
      anchorSupport =
        Set.fromList (fmap morseReebNodeAnchor (NonEmpty.toList boundaryNodes))
   in Set.union anchorSupport incidentArcSupport

selectRepresentativeNode :: NonEmpty MorseReebNode -> MorseReebNode
selectRepresentativeNode (firstNode :| remainingNodes) =
  foldr
    (\nodeValue selectedNode -> if morseReebNodeId nodeValue < morseReebNodeId selectedNode then nodeValue else selectedNode)
    firstNode
    remainingNodes

combineSupportRefs ::
  StitchSupportSelection ->
  StitchSupportRefinement ->
  Set BasisCellRef ->
  Set BasisCellRef ->
  Set BasisCellRef ->
  Set BasisCellRef ->
  MorseReebNode ->
  MorseReebNode ->
  Set BasisCellRef
combineSupportRefs supportSelection supportRefinement sourceRegionBasisRefs targetRegionBasisRefs sourceSupport targetSupport sourceNode targetNode =
  case supportRefinement of
    KernelSupportRefinement ->
      kernelSupport
    BoundaryEnvelopeRefinement ->
      Set.union kernelSupport boundarySupport
    RegionalEnvelopeRefinement ->
      Set.unions [kernelSupport, boundarySupport, sourceRegionBasisRefs, targetRegionBasisRefs]
  where
    kernelSupport =
      case supportSelection of
        BoundarySupport ->
          boundarySupport
        AnchorSupport ->
          Set.fromList [morseReebNodeAnchor sourceNode, morseReebNodeAnchor targetNode]
    boundarySupport =
      Set.union sourceSupport targetSupport

monotonicityBetween :: MorseReebNode -> MorseReebNode -> Monotonicity
monotonicityBetween sourceNode targetNode =
  if morseReebNodePotential sourceNode <= morseReebNodePotential targetNode
    then Ascending
    else Descending