packages feed

moonlight-planar-1.0.0.0: src-cell-complex/Moonlight/Hex/CellComplex.hs

-- | Integral cellular chains derived from one native hexagonal region.
module Moonlight.Hex.CellComplex
  ( HexCellComplexError (..)
  , finiteHexCellComplex
  ) where

import Data.Bifunctor (first)
import Data.List.NonEmpty qualified as NonEmpty
import Data.Set qualified as Set
import Data.Vector qualified as Vector
import Moonlight.Hex.Coordinate (HexCoord)
import Moonlight.Hex.Element
  ( HexSide
  , HexBoundarySide
  , HexVertex
  , hexBoundaryFrom
  , hexBoundaryIdentity
  , hexCellBoundary
  , hexCellSides
  , hexCellVertices
  , hexSideEndpoints
  )
import Moonlight.Hex.Region (HexRegion, hexRegionCoords)
import Moonlight.Homology.Boundary
  ( BoundaryIncidence
  , FiniteChainComplex
  , emptyBoundaryIncidence
  , emptyBoundaryIncidenceOf
  , materializeIncidenceBoundary
  , mkFiniteChainComplexChecked
  )
import Moonlight.Homology.Chain (HomologicalDegree (..), HomologyFailure)

newtype HexCellComplexError = HexCellComplexInvalid HomologyFailure
  deriving stock (Eq, Show)

-- | Interpret the selected cells and their downward closure in Homology's
-- existing finite-chain carrier. No second topology representation survives.
finiteHexCellComplex :: HexRegion -> Either HexCellComplexError (FiniteChainComplex Int)
finiteHexCellComplex region = do
  degreeOne <-
    first HexCellComplexInvalid
      (materializeIncidenceBoundary edgeBoundary edgeBasis vertexBasis)
  degreeTwo <-
    first HexCellComplexInvalid
      (materializeIncidenceBoundary faceBoundary faceBasis edgeBasis)
  let degreeZero :: BoundaryIncidence Int
      degreeZero = emptyBoundaryIncidenceOf (fromIntegral (length vertexBasis)) 0
      boundaryAt (HomologicalDegree degree) = case degree of
        0 -> degreeZero
        1 -> degreeOne
        2 -> degreeTwo
        _ -> emptyBoundaryIncidence
  first HexCellComplexInvalid
    (mkFiniteChainComplexChecked (HomologicalDegree 2) boundaryAt)
 where
  faceBasis = Vector.toList (hexRegionCoords region)
  edgeBasis =
    Set.toAscList
      (Set.fromList (faceBasis >>= NonEmpty.toList . hexCellSides))
  vertexBasis =
    Set.toAscList
      (Set.fromList (faceBasis >>= NonEmpty.toList . hexCellVertices))

edgeBoundary :: HexSide -> [(Int, HexVertex)]
edgeBoundary side =
  let (from, to) = hexSideEndpoints side
   in [(-1, from), (1, to)]

faceBoundary :: HexCoord -> [(Int, HexSide)]
faceBoundary = fmap orientedCoefficient . NonEmpty.toList . hexCellBoundary
 where
  orientedCoefficient :: HexBoundarySide -> (Int, HexSide)
  orientedCoefficient boundary =
    let side = hexBoundaryIdentity boundary
        (canonicalFrom, _) = hexSideEndpoints side
        coefficient = if hexBoundaryFrom boundary == canonicalFrom then 1 else -1
     in (coefficient, side)
{-# LANGUAGE DerivingStrategies #-}