packages feed

moonlight-homology-0.1.0.0: src-public/Moonlight/Homology/Presentation.hs

module Moonlight.Homology.Presentation
  ( ChainSpec (..),
    ChainBuildError (..),
    compileChain,
  )
where

import Data.Bifunctor (first)
import Data.Foldable (traverse_)
import Data.Kind (Type)
import qualified Data.Map.Strict as Map
import Moonlight.Core (Semiring)
import Moonlight.Homology.Boundary.Finite
  ( FiniteChainComplex,
    mkFiniteChainComplexChecked,
  )
import Moonlight.Homology.Boundary.LinAlg
  ( BoundaryIncidence,
    BoundaryIncidenceShapeError,
    emptyBoundaryIncidence,
    emptyBoundaryIncidenceOf,
    mkBoundaryEntryFromInts,
    mkBoundaryIncidence,
  )
import Moonlight.Homology.Pure.Degree (HomologicalDegree (..))
import Moonlight.Homology.Pure.Failure (HomologyFailure)

type ChainSpec :: Type -> Type
data ChainSpec r = ChainSpec
  { chainCellCounts :: [Int],
    chainBoundaries :: [[(Int, Int, r)]]
  }
  deriving stock (Eq, Show)

type ChainBuildError :: Type
data ChainBuildError
  = ChainBuildEmptySpec
  | ChainBuildNegativeCellCount Int Int
  | ChainBuildBoundaryCountMismatch Int Int
  | ChainBuildIncidenceFault Int BoundaryIncidenceShapeError
  | ChainBuildComplexFault HomologyFailure
  deriving stock (Eq, Show)

compileChain ::
  (Eq r, Num r, Semiring r) =>
  ChainSpec r ->
  Either ChainBuildError (FiniteChainComplex r)
compileChain spec = do
  cellCounts <- declaredCellCounts spec
  incidences <- incidenceTable cellCounts (chainBoundaries spec)
  first
    ChainBuildComplexFault
    ( mkFiniteChainComplexChecked
        (HomologicalDegree (length cellCounts - 1))
        (incidenceFor cellCounts incidences)
    )

declaredCellCounts :: ChainSpec r -> Either ChainBuildError [Int]
declaredCellCounts spec =
  case chainCellCounts spec of
    [] -> Left ChainBuildEmptySpec
    cellCounts -> do
      traverse_
        ( \(degreeIndex, cellCount) ->
            if cellCount < 0
              then Left (ChainBuildNegativeCellCount degreeIndex cellCount)
              else Right ()
        )
        (zip [0 ..] cellCounts)
      let expectedBoundaryCount = length cellCounts - 1
          observedBoundaryCount = length (chainBoundaries spec)
      if observedBoundaryCount == expectedBoundaryCount
        then Right cellCounts
        else Left (ChainBuildBoundaryCountMismatch expectedBoundaryCount observedBoundaryCount)

incidenceTable ::
  (Eq r, Semiring r) =>
  [Int] ->
  [[(Int, Int, r)]] ->
  Either ChainBuildError (Map.Map Int (BoundaryIncidence r))
incidenceTable cellCounts boundaries =
  Map.fromList
    <$> traverse
      ( \(degreeIndex, (sourceCount, targetCount), entryTriples) ->
          (,) degreeIndex
            <$> first
              (ChainBuildIncidenceFault degreeIndex)
              ( mkBoundaryIncidence
                  (fromIntegral sourceCount)
                  (fromIntegral targetCount)
                  ( fmap
                      ( \(sourceValue, targetValue, coefficientValue) ->
                          mkBoundaryEntryFromInts sourceValue targetValue coefficientValue
                      )
                      entryTriples
                  )
              )
      )
      (zip3 [1 ..] (zip (drop 1 cellCounts) cellCounts) boundaries)

incidenceFor ::
  [Int] ->
  Map.Map Int (BoundaryIncidence r) ->
  HomologicalDegree ->
  BoundaryIncidence r
incidenceFor cellCounts incidences (HomologicalDegree degreeIndex)
  | degreeIndex == 0 =
      case cellCounts of
        zeroCellCount : _ -> emptyBoundaryIncidenceOf (fromIntegral zeroCellCount) 0
        [] -> emptyBoundaryIncidence
  | otherwise = Map.findWithDefault emptyBoundaryIncidence degreeIndex incidences