gargamelle-sheaf-0.1.0.0: src/Gargamelle/Sheaf/Diffusion.hs
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Gargamelle.Sheaf.Diffusion
( -- * Cellular Sheaf
CellularSheaf(..)
, symmetricSheaf
, directionalSheaf
, liftEdge
-- * Cochains
, Cochain0(..)
, Cochain1(..)
, lookupCochain0
, lookupCochain1
, fromListCochain0
, fromListCochain1
-- * Store Comonad Cellular Diffusion
, cochainToStore
, diffuseLocalRule
, stepDiffusion
, diffuseUntilHarmonic
, diffuseTrace
, meet
, join
) where
import Algebra.Lattice (Lattice(..), BoundedMeetSemiLattice(..))
import Control.Comonad (extend, extract)
import Control.Comonad.Store (Store, store, pos, peek)
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Gargamelle.Sheaf.Complex (CellComplex(..), incidentEdges, Orientation(..))
-- | Infix meet operator synonym.
meet :: Lattice a => a -> a -> a
meet = (/\)
-- | Infix join operator synonym.
join :: Lattice a => a -> a -> a
join = (\/)
{- Note [Cellular Sheaf Restriction Maps and Stalk Functors]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[tag:note-cellular-sheaf-stalks]
In applied algebraic topology and distributed systems verification, a software
architecture is formalized as a 1-dimensional cell complex (graph complex) G = (V, E):
- 0-cells (vertices v ∈ V) represent local component scopes, module stalks, or actor states.
- 1-cells (edges e ∈ E) represent shared communication interfaces, network channels, or contracts.
A Cellular Sheaf F over G consists of:
1. Stalk Assignments:
- For each vertex v, a stalk F(v) containing local contract or model data.
- For each edge e = (u, v), an edge stalk F(e) representing the shared interface contract.
2. Restriction Maps:
Along face inclusions u ⊴ e and v ⊴ e, directional restriction morphisms:
ρ_{u ⊴ e} : F(u) -> F(e)
ρ_{v ⊴ e} : F(v) -> F(e)
project vertex-level internal invariants into the shared interface language.
3. Adjoint Lifting Maps:
Adjoint or Galois dual maps lift edge-level interface contracts back into vertex stalks:
liftSrc : F(e) -> F(u)
liftTgt : F(e) -> F(v)
Operational Role:
- Symmetric Sheaves ('symmetricSheaf'): Useful when communication channels are non-directional
and contracts are symmetric across client and server.
- Directional Sheaves ('directionalSheaf'): Essential when interfaces distinguish caller
assumptions from callee guarantees.
-}
-- | A cellular sheaf F over a cell complex G = (V, E).
-- Assigns stalks to 0-cells and 1-cells, restriction maps along cell face inclusions,
-- and comonadic diffusion lifting maps back to the vertex stalks.
--
-- See Note [Cellular Sheaf Restriction Maps and Stalk Functors] [ref:note-cellular-sheaf-stalks].
data CellularSheaf vId eId stalk edgeStalk = CellularSheaf
{ restrictSrc :: eId -> stalk -> edgeStalk -- ^ \rho_{u \trianglelefteq e} (source restriction)
, restrictTgt :: eId -> stalk -> edgeStalk -- ^ \rho_{v \trianglelefteq e} (target restriction)
, liftSrc :: eId -> edgeStalk -> stalk -- ^ Lift edge stalk back to source vertex
, liftTgt :: eId -> edgeStalk -> stalk -- ^ Lift edge stalk back to target vertex
}
-- | Backward-compatible alias for lifting an edge stalk to a vertex stalk.
liftEdge :: CellularSheaf vId eId stalk edgeStalk -> eId -> edgeStalk -> stalk
liftEdge = liftSrc
-- | Construct a cellular sheaf where source and target restrictions/lifts are symmetric.
symmetricSheaf
:: (eId -> stalk -> edgeStalk)
-> (eId -> edgeStalk -> stalk)
-> CellularSheaf vId eId stalk edgeStalk
symmetricSheaf restr lift_ = CellularSheaf
{ restrictSrc = restr
, restrictTgt = restr
, liftSrc = lift_
, liftTgt = lift_
}
-- | Construct a cellular sheaf with explicit directional restriction and lifting maps.
directionalSheaf
:: (eId -> stalk -> edgeStalk)
-> (eId -> stalk -> edgeStalk)
-> (eId -> edgeStalk -> stalk)
-> (eId -> edgeStalk -> stalk)
-> CellularSheaf vId eId stalk edgeStalk
directionalSheaf = CellularSheaf
-- | 0-Cochain: an assignment of stalk elements across 0-cells (vertices).
newtype Cochain0 vId stalk = Cochain0
{ unCochain0 :: Map vId stalk
} deriving (Eq, Show, Functor)
-- | 1-Cochain: an assignment of edge stalk elements across 1-cells (edges).
newtype Cochain1 eId edgeStalk = Cochain1
{ unCochain1 :: Map eId edgeStalk
} deriving (Eq, Show, Functor)
instance (Ord vId, Lattice stalk) => Lattice (Cochain0 vId stalk) where
(/\) (Cochain0 m1) (Cochain0 m2) = Cochain0 (Map.unionWith (/\) m1 m2)
(\/) (Cochain0 m1) (Cochain0 m2) = Cochain0 (Map.unionWith (\/) m1 m2)
-- | Safe lookup in a 0-cochain.
lookupCochain0 :: (Ord vId) => vId -> Cochain0 vId stalk -> Maybe stalk
lookupCochain0 v (Cochain0 m) = Map.lookup v m
-- | Safe lookup in a 1-cochain.
lookupCochain1 :: (Ord eId) => eId -> Cochain1 eId edgeStalk -> Maybe edgeStalk
lookupCochain1 e (Cochain1 m) = Map.lookup e m
-- | Create a 0-cochain from an association list.
fromListCochain0 :: (Ord vId) => [(vId, stalk)] -> Cochain0 vId stalk
fromListCochain0 = Cochain0 . Map.fromList
-- | Create a 1-cochain from an association list.
fromListCochain1 :: (Ord eId) => [(eId, edgeStalk)] -> Cochain1 eId edgeStalk
fromListCochain1 = Cochain1 . Map.fromList
-- | Represent a 0-cochain as a Store comonad focused at an initial vertex position.
-- Lookups outside the cochain return the provided fallback default.
cochainToStore :: (Ord vId) => stalk -> Cochain0 vId stalk -> vId -> Store vId stalk
cochainToStore defaultStalk (Cochain0 m) =
store (\v -> Map.findWithDefault defaultStalk v m)
{- Note [Store Comonad Cellular Diffusion and Harmonic Consensus]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[tag:note-store-comonad-diffusion]
Traditional graph algorithms process networks via imperative mutation or iterative loops.
Gargamelle models cellular space comonadically using 'Control.Comonad.Store':
data Store s a = Store (s -> a) s
In cellular diffusion:
- Focus 's = vId': The focused vertex currently under evaluation ('pos w').
- Local Value: The current stalk contract at that vertex ('extract w').
- Neighborhood Access: Neighboring contract stalks across incident cell boundaries
are queried via 'peek other w'.
Parallel Global Diffusion via Comonadic 'extend':
The local rule 'diffuseLocalRule' inspects the focused vertex and takes the meet (/\)
of its current contract with all incoming restricted and lifted neighbor contracts.
By invoking:
diffusedStore = extend (diffuseLocalRule complex sheaf) baseStore
the comonad simultaneously computes updated stalks across all vertices in the cell
complex in a single pure, parallel evaluation step.
Knaster-Tarski Fixed-Point Convergence:
Because the meet operation (/\) in a meet-semi-lattice is monotonic and contracting:
c_{t+1} ⩽ c_t
the sequence of 0-cochains produced by 'diffuseUntilHarmonic':
c_0 ⩾ c_1 ⩾ c_2 ⩾ ...
is guaranteed by the Knaster-Tarski fixed-point theorem to converge to the greatest
lower bound (the most permissive common contract refinement that satisfies all interface
boundary constraints simultaneously).
-}
-- | Local cellular diffusion step evaluated comonadically at the current focused vertex.
-- Uses 'pos' to determine the active vertex, 'extract' for its current stalk value,
-- and 'peek' to inspect neighboring stalks across incident cell boundaries.
--
-- See Note [Store Comonad Cellular Diffusion and Harmonic Consensus] [ref:note-store-comonad-diffusion].
diffuseLocalRule
:: forall vId eId stalk edgeStalk.
(Ord vId, BoundedMeetSemiLattice stalk)
=> CellComplex vId eId
-> CellularSheaf vId eId stalk edgeStalk
-> Store vId stalk
-> stalk
diffuseLocalRule complex sheaf w =
let v = pos w
curr = extract w
incident = incidentEdges complex v
incomingStalks =
[ case orient of
Forward ->
-- v is source of e; neighbor 'other' is target.
-- Restrict other's stalk via restrictTgt, then lift to v via liftSrc.
liftSrc sheaf e (restrictTgt sheaf e (peek other w))
Backward ->
-- v is target of e; neighbor 'other' is source.
-- Restrict other's stalk via restrictSrc, then lift to v via liftTgt.
liftTgt sheaf e (restrictSrc sheaf e (peek other w))
| (e, orient, other) <- incident
]
in foldr meet curr incomingStalks
-- | Advance diffusion by one discrete comonadic step across all vertices in the complex.
-- Uses 'extend' on the Store comonad to simultaneously compute new values everywhere.
stepDiffusion
:: forall vId eId stalk edgeStalk.
(Ord vId, BoundedMeetSemiLattice stalk)
=> CellComplex vId eId
-> CellularSheaf vId eId stalk edgeStalk
-> Cochain0 vId stalk
-> Cochain0 vId stalk
stepDiffusion complex sheaf c0@(Cochain0 m) = case vertices complex of
[] -> c0
(v0:_) ->
let baseStore = cochainToStore top c0 v0
diffusedStore = extend (diffuseLocalRule complex sheaf) baseStore
newM = Map.fromList [ (v, peek v diffusedStore) | v <- vertices complex ]
in Cochain0 (Map.union newM m)
-- | Knaster-Tarski fixed-point iteration via Store comonad diffusion.
-- Iterates until a fixed point is reached (new state == current state)
-- or the step limit is reached.
--
-- See Note [Store Comonad Cellular Diffusion and Harmonic Consensus] [ref:note-store-comonad-diffusion].
diffuseUntilHarmonic
:: (Ord vId, BoundedMeetSemiLattice stalk, Eq stalk)
=> Int -- ^ Maximum iterations
-> CellComplex vId eId
-> CellularSheaf vId eId stalk edgeStalk
-> Cochain0 vId stalk
-> Cochain0 vId stalk
diffuseUntilHarmonic maxSteps complex sheaf = go 0
where
go step c0
| step >= maxSteps = c0
| otherwise =
let cNext = stepDiffusion complex sheaf c0
in if cNext == c0
then c0
else go (step + 1) cNext
-- | Trace diffusion steps until a fixed point is reached or the limit is reached.
-- Produces the sequence of intermediate 0-cochains.
diffuseTrace
:: (Ord vId, BoundedMeetSemiLattice stalk, Eq stalk)
=> Int
-> CellComplex vId eId
-> CellularSheaf vId eId stalk edgeStalk
-> Cochain0 vId stalk
-> [Cochain0 vId stalk]
diffuseTrace maxSteps complex sheaf = go 0
where
go step c0
| step >= maxSteps = [c0]
| otherwise =
let cNext = stepDiffusion complex sheaf c0
in if cNext == c0
then [c0]
else c0 : go (step + 1) cNext