packages feed

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

module Moonlight.Homology.Pure.Topology.Harmonic
  ( attachDiscoveredHarmonicLoops,
    discoverHarmonicLoops,
    harmonicBasisAt,
  )
where

import Data.Function ((&))
import Data.IntMap.Strict qualified as IntMap
import Data.Set qualified as Set
import Moonlight.Homology.Boundary.Finite
  ( FiniteChainComplex,
    incidenceMatrixAt,
  )
import Moonlight.Homology.Pure.Chain
  ( HarmonicBasisElement (..),
    HomologicalDegree (..),
    incrementDegree,
    RepresentativeChain (..),
    RepresentativeCycle,
    TopologyWitness (..),
  )
import Moonlight.Homology.Pure.Carrier (BasisCellRef (..))
import Moonlight.Homology.Pure.Filtration (enumerateFromZero)
import Moonlight.Homology.Pure.Matrix.Shape (cellCountAtDegree)
import Moonlight.Homology.Pure.Topology.MacroScaffold
  ( HarmonicLoop (..),
    HarmonicLoopId (..),
    HarmonicLoopPeriod (..),
    HarmonicLoopWeight (..),
    MacroScaffoldIR (..),
    MorseReebArc (..),
    MorseReebScaffold (..),
    ReebArcId,
  )
import Moonlight.Homology.Pure.Matrix.SparseLinAlg
  ( SparseMatrix (..),
    SparseRow,
    sparseBoundaryMatrix,
    sparseKernelBasisOf,
    sparseTransposeMatrix,
  )

attachDiscoveredHarmonicLoops ::
  Integral r =>
  FiniteChainComplex r ->
  TopologyWitness MacroScaffoldIR spectral persistence Rational Int ->
  TopologyWitness MacroScaffoldIR spectral persistence Rational Int
attachDiscoveredHarmonicLoops finite witnessValue =
  let harmonicDegree = HomologicalDegree 1
      harmonicRows = harmonicBasisAt finite harmonicDegree
   in witnessValue
        { topologyMacroScaffold =
            fmap
              (attachLoopsWhenMissing harmonicRows)
              (topologyMacroScaffold witnessValue),
          topologyHarmonicBasis =
            if null (topologyHarmonicBasis witnessValue)
              then fmap (harmonicBasisElement harmonicDegree) harmonicRows
              else topologyHarmonicBasis witnessValue
        }

harmonicBasisAt ::
  Integral r =>
  FiniteChainComplex r ->
  HomologicalDegree ->
  [SparseRow]
harmonicBasisAt finite degreeValue =
  let ambientDimension = cellCountAtDegree finite degreeValue
   in if ambientDimension == 0
        then []
        else
          sparseKernelBasisOf
            ambientDimension
            (harmonicConstraintMatrix finite degreeValue ambientDimension)

harmonicConstraintMatrix ::
  Integral r =>
  FiniteChainComplex r ->
  HomologicalDegree ->
  Int ->
  SparseMatrix
harmonicConstraintMatrix finite degreeValue ambientDimension =
  SparseMatrix
    { smRows =
        boundaryRowsAt finite degreeValue
          <> coboundaryRowsAt finite (incrementDegree degreeValue),
      smColumnCount = ambientDimension
    }

boundaryRowsAt ::
  Integral r =>
  FiniteChainComplex r ->
  HomologicalDegree ->
  [SparseRow]
boundaryRowsAt finite degreeValue =
  smRows (sparseBoundaryMatrix (incidenceMatrixAt finite degreeValue))

coboundaryRowsAt ::
  Integral r =>
  FiniteChainComplex r ->
  HomologicalDegree ->
  [SparseRow]
coboundaryRowsAt finite degreeValue =
  smRows
    ( sparseTransposeMatrix
        (sparseBoundaryMatrix (incidenceMatrixAt finite degreeValue))
    )

attachLoopsWhenMissing ::
  [SparseRow] ->
  MacroScaffoldIR ->
  MacroScaffoldIR
attachLoopsWhenMissing harmonicRows scaffoldValue =
  if null (macroScaffoldHarmonicLoops scaffoldValue)
    then
      scaffoldValue
        { macroScaffoldHarmonicLoops =
            discoverHarmonicLoops scaffoldValue harmonicRows
        }
    else scaffoldValue

discoverHarmonicLoops ::
  MacroScaffoldIR ->
  [SparseRow] ->
  [HarmonicLoop]
discoverHarmonicLoops scaffoldValue harmonicRows =
  zip (fmap HarmonicLoopId (enumerateFromZero (length harmonicRows))) harmonicRows
    & fmap
      ( \(loopIdValue, harmonicRow) ->
          harmonicLoopFromRow scaffoldValue loopIdValue harmonicRow
      )

harmonicLoopFromRow ::
  MacroScaffoldIR ->
  HarmonicLoopId ->
  SparseRow ->
  HarmonicLoop
harmonicLoopFromRow scaffoldValue loopIdValue harmonicRow =
  let representativeValue =
        sparseRowToRepresentative (HomologicalDegree 1) harmonicRow
      periodValue =
        harmonicRow
          & IntMap.foldl' (\accumulator coefficientValue -> accumulator + coefficientValue * coefficientValue) 0
          & fromRational
      weightValue =
        harmonicRow
          & IntMap.foldl' (\accumulator coefficientValue -> accumulator + abs coefficientValue) 0
          & fromRational
   in HarmonicLoop
        { harmonicLoopId = loopIdValue,
          harmonicLoopDegree = HomologicalDegree 1,
          harmonicLoopCycle = representativeValue,
          harmonicLoopCocycle = representativeValue,
          harmonicLoopWeight = HarmonicLoopWeight weightValue,
          harmonicLoopPeriod =
            if periodValue == 0
              then Nothing
              else Just (HarmonicLoopPeriod periodValue),
          harmonicLoopSupport = supportArcIds scaffoldValue representativeValue
        }

sparseRowToRepresentative ::
  HomologicalDegree ->
  SparseRow ->
  RepresentativeCycle Rational BasisCellRef
sparseRowToRepresentative degreeValue harmonicRow =
  RepresentativeChain
    { representativeDegree = degreeValue,
      representativeTerms =
        IntMap.toAscList harmonicRow
          & filter ((/= 0) . snd)
          & fmap
            ( \(basisIndexValue, coefficientValue) ->
                ( coefficientValue,
                  BasisCellRef
                    { cellDegree = degreeValue,
                      cellIndex = basisIndexValue
                    }
                )
            )
    }

harmonicBasisElement :: HomologicalDegree -> SparseRow -> HarmonicBasisElement Rational Int
harmonicBasisElement degreeValue harmonicRow =
  HarmonicBasisElement
    { harmonicDegree = degreeValue,
      harmonicRepresentative =
        RepresentativeChain
          { representativeDegree = degreeValue,
            representativeTerms =
              IntMap.toAscList harmonicRow
                & filter ((/= 0) . snd)
                & fmap (\(basisIndexValue, coefficientValue) -> (coefficientValue, basisIndexValue))
          }
    }

supportArcIds ::
  MacroScaffoldIR ->
  RepresentativeCycle Rational BasisCellRef ->
  [ReebArcId]
supportArcIds scaffoldValue cycleValue =
  let cycleSupport =
        representativeTerms cycleValue
          & fmap snd
          & Set.fromList
      arcSupport arcValue =
        morseReebArcSupport arcValue
          & Set.fromList
   in morseReebArcs (macroScaffoldReeb scaffoldValue)
        & filter (\arcValue -> not (Set.null (Set.intersection cycleSupport (arcSupport arcValue))))
        & fmap morseReebArcId