packages feed

moonlight-homology-0.1.0.0: src-topology/Moonlight/Homology/Pure/Topology/Graph/Reeb.hs

module Moonlight.Homology.Pure.Topology.Graph.Reeb
  ( graphReebArcs,
    graphSingularities,
    traceArc,
    followGradient,
    arcSupportCells,
    edgeCellsBetween,
  )
where

import Data.Containers.ListUtils (nubOrd)
import Data.Function ((&))
import Data.List qualified as List
import Data.Map.Strict qualified as Map
import Data.Maybe (mapMaybe)
import Data.Ratio ((%))
import Data.Set qualified as Set
import Moonlight.Homology.Pure.Topology.Core
import Moonlight.Homology.Pure.Topology.Graph.Critical (higherNeighborEdges)
import Moonlight.Homology.Pure.Topology.Graph.Skeleton (edgeBasisCellRef, vertexBasisCellRef)
import Moonlight.Homology.Pure.Topology.MacroScaffold
  ( Monotonicity (..),
    MorseReebArc (..),
    MorseReebNode (..),
    ReebArcId (..),
    ReebNodeId,
    Singularity (..),
    SingularityId (..),
    SingularityIndex (..),
  )

graphReebArcs :: Map.Map Int Double -> Graph1Skeleton -> [MorseReebNode] -> [MorseReebArc]
graphReebArcs potentials skeleton nodes =
  let nodeIdsByVertex =
        nodes
          & fmap (\node -> (cellIndex (morseReebNodeAnchor node), morseReebNodeId node))
          & Map.fromList
      criticalKindsByVertex =
        nodes
          & fmap (\node -> (cellIndex (morseReebNodeAnchor node), morseReebNodeKind node))
          & Map.fromList
      arcSeeds =
        nodes
          >>= ( \node ->
                  let sourceVertex = cellIndex (morseReebNodeAnchor node)
                   in higherNeighborEdges potentials skeleton sourceVertex
                        & mapMaybe
                          ( \(seedEdge, seedVertex) ->
                              traceArc potentials nodeIdsByVertex criticalKindsByVertex skeleton (morseReebNodeId node) sourceVertex seedEdge seedVertex
                          )
             )
          & nubOrd
   in zip (fmap ReebArcId (enumerateFromZero (length arcSeeds))) arcSeeds
        & fmap
          ( \(arcId, (sourceNodeId, targetNodeId, monotonicityValue, supportCells)) ->
              MorseReebArc
                { morseReebArcId = arcId,
                  morseReebArcSource = sourceNodeId,
                  morseReebArcTarget = targetNodeId,
                  morseReebArcMonotonicity = monotonicityValue,
                  morseReebArcSupport = supportCells
                }
          )

graphSingularities :: [MorseReebNode] -> [MorseReebArc] -> [Singularity]
graphSingularities nodes arcs =
  let incidentArcs =
        Map.fromListWith (<>)
          ( arcs >>= \arcValue ->
              [ (morseReebArcSource arcValue, [morseReebArcId arcValue]),
                (morseReebArcTarget arcValue, [morseReebArcId arcValue])
              ]
          )
   in zip (fmap SingularityId (enumerateFromZero (length nodes))) nodes
        & fmap
          ( \(singularityIdValue, nodeValue) ->
              Singularity
                { singularityId = singularityIdValue,
                  singularityAnchor = morseReebNodeAnchor nodeValue,
                  singularityKind = morseReebNodeKind nodeValue,
                  singularityPotential = Just (morseReebNodePotential nodeValue),
                  singularityIndex = criticalKindSingularityIndex (morseReebNodeKind nodeValue),
                  singularityReebNode = Just (morseReebNodeId nodeValue),
                  singularityIncidentArcs =
                    Map.findWithDefault [] (morseReebNodeId nodeValue) incidentArcs
                }
          )

criticalKindSingularityIndex :: CriticalKind -> SingularityIndex
criticalKindSingularityIndex criticalKindValue =
  SingularityIndex
    ( case criticalKindValue of
        Basin -> 1 % 1
        Peak -> 1 % 1
        Isolated -> 1 % 1
        Merge -> (-1) % 1
        Split -> (-1) % 1
        Pass -> (-1) % 1
    )

traceArc ::
  Map.Map Int Double ->
  Map.Map Int ReebNodeId ->
  Map.Map Int CriticalKind ->
  Graph1Skeleton ->
  ReebNodeId ->
  Int ->
  GraphEdge ->
  Int ->
  Maybe (ReebNodeId, ReebNodeId, Monotonicity, [BasisCellRef])
traceArc potentials nodeIdsByVertex criticalKindsByVertex skeleton sourceNodeId sourceVertex seedEdge seedVertex =
  let pathTail = followGradientEdges potentials criticalKindsByVertex skeleton Set.empty seedVertex
      supportCells = arcSupportCellsFromEdges sourceVertex ((seedEdge, seedVertex) : pathTail)
      targetVertex =
        case reverse pathTail of
          (_, targetValue) : _ -> targetValue
          [] -> seedVertex
   in fmap
        ( \targetNodeId ->
            ( sourceNodeId,
              targetNodeId,
              Ascending,
              supportCells
            )
        )
        (Map.lookup targetVertex nodeIdsByVertex)

followGradient :: Map.Map Int Double -> Map.Map Int CriticalKind -> Graph1Skeleton -> Set.Set Int -> Int -> [Int]
followGradient potentials criticalKindsByVertex skeleton visited currentVertex =
  currentVertex : fmap snd (followGradientEdges potentials criticalKindsByVertex skeleton visited currentVertex)

followGradientEdges :: Map.Map Int Double -> Map.Map Int CriticalKind -> Graph1Skeleton -> Set.Set Int -> Int -> [(GraphEdge, Int)]
followGradientEdges potentials criticalKindsByVertex skeleton visited currentVertex =
  if Set.member currentVertex visited
    then []
    else
      case Map.lookup currentVertex criticalKindsByVertex of
        Just _ -> []
        Nothing ->
          case higherNeighborEdges potentials skeleton currentVertex of
            [] -> []
            higherOnly ->
              let nextVertex =
                    higherOnly
                      & List.find (\(_, neighborValue) -> not (Set.member neighborValue visited))
               in case nextVertex of
                    Nothing -> []
                    Just (edgeValue, nextVertexValue) ->
                      (edgeValue, nextVertexValue) : followGradientEdges potentials criticalKindsByVertex skeleton (Set.insert currentVertex visited) nextVertexValue

arcSupportCellsFromEdges :: Int -> [(GraphEdge, Int)] -> [BasisCellRef]
arcSupportCellsFromEdges sourceVertex edgePath =
  vertexBasisCellRef sourceVertex
    : ( edgePath
          >>= ( \(edgeValue, targetVertex) ->
                  [ edgeBasisCellRef (graphEdgeIndex edgeValue),
                    vertexBasisCellRef targetVertex
                  ]
              )
      )

arcSupportCells :: Graph1Skeleton -> [Int] -> [BasisCellRef]
arcSupportCells skeleton vertexPath =
  case vertexPath of
    [] -> []
    [vertexValue] -> [vertexBasisCellRef vertexValue]
    sourceVertex : targetVertex : remainingVertices ->
      [vertexBasisCellRef sourceVertex]
        <> edgeCellsBetween skeleton sourceVertex targetVertex
        <> arcSupportCells skeleton (targetVertex : remainingVertices)

edgeCellsBetween :: Graph1Skeleton -> Int -> Int -> [BasisCellRef]
edgeCellsBetween skeleton sourceVertex targetVertex =
  graphEdges skeleton
    & filter
      ( \edgeValue ->
          let edgePair = List.sort [graphEdgeSource edgeValue, graphEdgeTarget edgeValue]
              targetPair = List.sort [sourceVertex, targetVertex]
           in edgePair == targetPair
      )
    & fmap (edgeBasisCellRef . graphEdgeIndex)