circuits-mat
Safe HaskellNone
LanguageGHC2024

Circuit.Mat

Description

Matrices over a semiring as a category with biproducts.

Overview

Matrices over a semiring are not a metaphor for category theory — they are the morphisms of a category with biproducts. Objects are finite index types; a morphism i -> j is one scalar per input/output pair. Either gives block matrices and is simultaneously product and coproduct — a biproduct. This is the matrix calculus, and it is a categorical fact, not an analogy.

The encoding is initial: Mat is the fully-applied leaf, Id / Fun / Par / Comp are symbolic constructors, and runMat pays only boundary evidence to collect results.

Evaluation is by pushing a sparse formal sum through the term. Fun needs no finite evidence; Mat carries its own dictionary; Par is structural recursion; Comp is composition of formal sums. This is total and terminating by construction — the free-semimodule semantics of the category.

Monoidal structure

TensorEither: tensor places two matrices on disjoint wires (block diagonal). unitl / unitr witness that Void (the uninhabited type) is the tensor unit, reflecting the fact that a 0 × n or m × 0 matrix has no entries. Unit laws hold: tensor (unitl . unitl') id = id.

ActionEither: braid permutes index positions — the symmetry of the biproduct. The slide slide :: Either a (Either b c) -> Either b (Either a c) is derivable as assoc . tensor braid id . assoc'.

Biproduct structure and the 2-category

The additive monoid on each hom-set gives matrices over a semiring the structure of a locally posetal 2-category when the semiring is idempotent:

  • 0-cells: finite types (the index sets).
  • 1-cells: matrices i -> j, elements of the free semimodule.
  • 2-cells: for idempotent semirings (Bool, Tropical), the additive monoid + induces a partial order:
  m ≤ n   iff   m + n = n
  

This is the natural 2-categorical structure: matrix inequality is a 2-cell, and composition is monotone in both arguments.

The biproduct structure means Either is simultaneously product and coproduct. For any finite types a, b there exist:

  • projections p1 :: a ⊕ b -> a, p2 :: a ⊕ b -> b (implemented as pattern-matching on Left/Right);
  • injections i1 :: a -> a ⊕ b, i2 :: b -> a ⊕ b (the Left and Right constructors).

satisfying the biproduct equations:

p1 . i1 = id,    p2 . i2 = id,
p1 . i2 = 0,     p2 . i1 = 0,
i1 . p1 + i2 . p2 = id

where 0 is the zero matrix and + is pointwise addition. Every matrix decomposes into its components via these projections and injections.

Kleene star as closure

The Kleene star starM computes the reflexive-transitive closure of a square matrix in the 2-category. For idempotent semirings, this is the least fixed point of X ↦ I + A · X, i.e. the closure operator of the locally posetal 2-category:

  • Bool: transitive closure of a graph (reachability).
  • Tropical (min-plus): all-pairs shortest paths (Floyd-Warshall).
  • StarSemiring String: regular expression for all paths.

These carriers are not merely semirings; the canonical ones are quantales — complete join-semilattices with monoidal multiplication distributing over arbitrary joins. See NumHask.Algebra.Quantale. In that setting matrix addition is pointwise join and the Kleene star is the least fixed point I + A + A² + …. starM uses the StarSemiring fragment, which is exactly the iterative-join part of a quantale.

Kleene's algorithm enumerates intermediate indices: O(|V|³) in the number of elements of the Finite type.

Trace as Schur-complement

traceMat computes the trace (feedback loop) of a block matrix:

  ┌     ┐
  │ a c │   :  a ⊕ b  →  a ⊕ c
  │ b d │
  └     ┘

  traceMat  →  d + c · a* · b   :  b → c

This is exactly the Schur-complement formula for biproduct categories. The trace satisfies the standard traced-monoidal laws (naturality, sliding, vanishing, yanking) — see the docspec examples.

Traced instance

With local Category carrying Ob (Mat s) a = Finite a, the feedback channel of trace carries Finite evidence. traceMat is therefore a lawful Traced Either instance (Schur-complement / star-closure).

Relation to harpie

harpie provides statically-shaped arrays (Harpie.Fixed.Array) with type-level dimensions. Where Finite enumerates a type's inhabitants at runtime, a harpie Array [n] s knows its shape at compile time via KnownNat n. A matrix i -> j could be a harpie array of shape [i, j] with indices drawn from Fin n@ (harpie's type-level finite set). The trace formula would still be the Schur-complement; the difference is that the index set is a Nat rather than a type-class dictionary.

Benchmarking (Finite enumeration vs matrix ops) shows that universe enumeration cost is linear in the number of inhabitants and typically dominated by the O(|V|³) star-closure computation for non-trivial matrices. See the enumeration spike section below for details.

Synopsis

Finite index types

class Eq a => Finite a where Source #

A finite index type: enumerable and comparable.

Methods

universe :: [a] Source #

Instances

Instances details
Finite Void Source # 
Instance details

Defined in Circuit.Mat

Methods

universe :: [Void] Source #

Finite () Source # 
Instance details

Defined in Circuit.Mat

Methods

universe :: [()] Source #

Finite Bool Source # 
Instance details

Defined in Circuit.Mat

Methods

universe :: [Bool] Source #

Finite a => Finite (Dual a) Source # 
Instance details

Defined in Circuit.Mat

Methods

universe :: [Dual a] Source #

KnownNat n => Finite (F n) Source #

Enumerate all F n values.

>>> universe :: [F 3]
[0,1,2]
Instance details

Defined in Circuit.Mat.Harpie

Methods

universe :: [F n] Source #

(Finite a, Finite b) => Finite (Either a b) Source # 
Instance details

Defined in Circuit.Mat

Methods

universe :: [Either a b] Source #

KnownNats s => Finite (Fins s) Source # 
Instance details

Defined in Circuit.Mat.Array

Methods

universe :: [Fins s] Source #

(Finite a, Finite b) => Finite (a, b) Source # 
Instance details

Defined in Circuit.Mat

Methods

universe :: [(a, b)] Source #

Matrices

data Mat s i j where Source #

Matrix over a semiring s, indexed by i (rows) and j (columns).

  • Mat is the concrete, fully-applied leaf; it carries Finite dictionaries so that push can enumerate indices when needed.
  • Id, Fun, Par, and Comp are symbolic; they are evaluated by push.

Note: Par does not require Finite — it is a structural constructor whose Finite evidence is supplied by the subterms or by Mat leaves. The Tensor instance uses Par for tensor.

Constructors

Mat :: forall i j s. (Finite i, Finite j) => (i -> j -> s) -> Mat s i j 
Id :: forall s i. Mat s i i 
Fun :: forall i j s. (i -> j) -> Mat s i j 
Par :: forall s a b c d. Mat s a b -> Mat s c d -> Mat s (Either a c) (Either b d) 
Comp :: forall s j1 j i. Mat s j1 j -> Mat s i j1 -> Mat s i j 

Instances

Instances details
(Additive s, Multiplicative s) => Channel Either (Mat s :: Type -> Type -> Type) Source #

Arrow-level monoidal structure for Either inside Mat.

Instance details

Defined in Circuit.Mat

Methods

assoc :: Mat s (Either (Either a b) c) (Either a (Either b c)) #

assoc' :: Mat s (Either a (Either b c)) (Either (Either a b) c) #

slide :: Mat s (Either a (Either b c)) (Either b (Either a c)) #

(Additive s, Multiplicative s) => Strength Either (Mat s :: Type -> Type -> Type) Source #

Tensorial strength for the Either tensor on Mat.

Instance details

Defined in Circuit.Mat

Methods

strength :: Mat s b c -> Mat s (Either a b) (Either a c) #

Action Either (Mat s :: Type -> Type -> Type) Source #

Coproduct symmetry on Mat.

braid permutes index positions on the Either sum.

>>> runMat (braid :: Mat Int (Either Node Node) (Either Node Node)) (Left A) (Right A)
1
>>> runMat braid (Right A) (Left A)
1
Instance details

Defined in Circuit.Mat

Methods

braid :: Mat s (Either a b) (Either b a) #

Tensor Either (Mat s :: Type -> Type -> Type) Source # 
Instance details

Defined in Circuit.Mat

Methods

tensor :: Mat s a b -> Mat s c d -> Mat s (Either a c) (Either b d) #

Unital Either (Mat s :: Type -> Type -> Type) Source #

Coproduct tensor action on Mat.

tensor places two matrices on disjoint Either wires (block diagonal). Unit laws use Void as the tensor unit — a matrix indexed by Void has no entries, so the unitor is the identity on the payload.

>>> let a = Mat (\i j -> if i == j then (1 :: Int) else 0) :: Mat Int Node Node
>>> let b = Mat (\i j -> (2 :: Int)) :: Mat Int Node Node
>>> runMat (tensor a b) (Left A) (Left A)
1
>>> runMat (tensor a b) (Right A) (Right A)
2
Instance details

Defined in Circuit.Mat

Methods

unitl :: Mat s (Either (Unit Either) a) a #

unitl' :: Mat s a (Either (Unit Either) a) #

unitr :: Mat s (Either a (Unit Either)) a #

unitr' :: Mat s a (Either a (Unit Either)) #

Category (Mat s :: Type -> Type -> Type) Source # 
Instance details

Defined in Circuit.Mat

Methods

id :: Mat s a a #

(.) :: Mat s b c -> Mat s a b -> Mat s a c #

LiftFun (Mat r) Source # 
Instance details

Defined in Circuit.Mat.Array

Methods

liftFun :: (i -> j) -> Mat r i j Source #

Tabular (Mat r) Source # 
Instance details

Defined in Circuit.Mat.Array

Methods

tabulateArr :: TabulateOb (Mat r) i j => (i -> j) -> Mat r i j Source #

indexArr :: TabulateOb (Mat r) i j => Mat r i j -> i -> j Source #

type TabulateOb (Mat r) i j Source # 
Instance details

Defined in Circuit.Mat.Array

type TabulateOb (Mat r) i j = (Finite i, Finite j, Eq j, Additive r, Multiplicative r, Eq r)

runMat :: (Additive s, Multiplicative s, Eq j) => Mat s i j -> i -> j -> s Source #

Run a matrix at a single input/output pair.

Equality evidence is required only at the boundary (the output index).

Closure

starM :: (StarSemiring s, Additive s, Multiplicative s, Finite a) => Mat s a a -> Mat s a a Source #

Reflexive-transitive closure of a square matrix.

Implements Kleene's algorithm: for each intermediate index k, update all entries m(i,j) := m(i,j) + m(i,k) * star(m(k,k)) * m(k,j). The closure is the original matrix with the loop applied, plus the identity at the end, so starM m = I + m + m² + ... for arbitrary star semirings.

For idempotent semirings, this is the closure operator of the locally posetal 2-category: starM computes the least fixed point of X ↦ I + A · X.

>>> let g = Mat (\i j -> case (i, j) of (A, B) -> True; (B, C) -> True; _ -> False) :: Mat Bool Node Node
>>> runMat (starM g) A C
True
>>> let w = Mat (\i j -> case (i, j) of (A, B) -> MinPlus 1; (B, C) -> MinPlus 2; (A, C) -> MinPlus 10; _ -> zero) :: Mat (MinPlus Double) Node Node
>>> getMinPlus (runMat (starM w) A C)
3.0

Trace (explicit finite-channel form)

traceMat :: (StarSemiring s, Additive s, Multiplicative s, Finite a, Finite b, Finite c) => Mat s (Either a b) (Either a c) -> Mat s b c Source #

Trace over an explicit finite feedback channel a.

For a block matrix [[a, a->c], [b->a, b->c]], the trace is the Schur-complement:

traceMat  [[a, c], [b, d]]  =  d + c · a* · b

This is the standard trace for biproduct categories, satisfying the traced-monoidal laws.

Naturality

Post-composing the data output with g before tracing equals tracing then post-composing with g. The blocks are coupled so the loop is live.

>>> let fFun (Left ()) (Left ()) = False; fFun (Left ()) (Right B) = True; fFun (Right A) (Left ()) = True; fFun _ _ = False
>>> let f = Mat fFun :: Mat Bool (Either () Node) (Either () Node)
>>> let g = Mat (\i j -> case (i, j) of (B, C) -> True; _ -> False) :: Mat Bool Node Node
>>> sameMat (traceMat (Comp (Par Id g) f)) (Comp g (traceMat f))
True

Sliding

Moving g through the feedback wire. Feedback channel is Bool, g is the swap permutation, and the blocks are coupled so the loop is live.

>>> let gSwap = Mat (\i j -> i /= j) :: Mat Bool Bool Bool
>>> let fSlideFun (Left False) (Left True) = True; fSlideFun (Left True) (Right B) = True; fSlideFun (Right A) (Left False) = True; fSlideFun (Right B) (Right C) = True; fSlideFun _ _ = False
>>> let fSlide = Mat fSlideFun :: Mat Bool (Either Bool Node) (Either Bool Node)
>>> sameMat (traceMat (Comp (Par gSwap Id) fSlide)) (traceMat (Comp fSlide (Par gSwap Id)))
True

Vanishing

Tracing over the unit channel () collapses to the Schur-complement formula mbc + mac * star maa * mba.

>>> let blockFun (Left ()) (Left ()) = False; blockFun (Left ()) (Right B) = True; blockFun (Right A) (Left ()) = True; blockFun _ _ = False
>>> let block = Mat blockFun :: Mat Bool (Either () Node) (Either () Node)
>>> let expected = Mat (\i j -> case (i, j) of (A, B) -> True; _ -> False) :: Mat Bool Node Node
>>> sameMat (traceMat block) expected
True

Regression: nested Comp terminates

The old reduceComp fallthrough looped on m . m . m.

>>> let m = Mat (\i j -> case (i, j) of (A, A) -> True; (A, B) -> True; (B, B) -> True; _ -> False) :: Mat Bool Node Node
>>> runMat (Comp m (Comp m m)) A B
True
>>> runMat (Comp m (Comp m m)) B A
False

Contraction

dot :: (StarSemiring s, Additive s, Multiplicative s, Eq a, Finite a) => (a -> s) -> (a -> s) -> s Source #

Categorical dot product of two vectors over the same finite index set.

Both vectors are given as functions from the index set to the semiring. The construction uses only Par, the coproduct swap, and traceMat — no row/column naming, no dimension indices.

The feedback channel in the trace is the shared index set a; because the block on that channel is zero, star reduces to one and the Schur complement becomes the sum of products.

Linear implication (explicit finite-channel form)

evalMat :: (Additive s, Multiplicative s, Eq a, Eq b, Finite a, Finite b) => Mat s (a, (a, b)) b Source #

Compact-closed implication on index pairs: A ⊸ B is the space of matrix entries (a, b). curry/uncurry reshape; eval contracts the repeated index (transpose + product).

These are named constrained combinators rather than Lolli class methods because the unconstrained tower no longer carries object evidence.

curryMat :: (Additive s, Multiplicative s, Eq c, Finite a, Finite b, Finite c) => Mat s (a, b) c -> Mat s a (b, c) Source #

uncurryMat :: (Additive s, Multiplicative s, Eq b, Finite a, Finite b, Finite c) => Mat s a (b, c) -> Mat s (a, b) c Source #

Involution structure (Ex6: reversibility, duality, conjugation)

newtype Dual a Source #

Object-level duality marker. Dual i is the dual index set of i; it carries the same inhabitants but is a distinct type, so duality is visible at the object level.

Constructors

Dual a 

Instances

Instances details
Finite a => Finite (Dual a) Source # 
Instance details

Defined in Circuit.Mat

Methods

universe :: [Dual a] Source #

Eq a => Eq (Dual a) Source # 
Instance details

Defined in Circuit.Mat

Methods

(==) :: Dual a -> Dual a -> Bool #

(/=) :: Dual a -> Dual a -> Bool #

Ord a => Ord (Dual a) Source # 
Instance details

Defined in Circuit.Mat

Methods

compare :: Dual a -> Dual a -> Ordering #

(<) :: Dual a -> Dual a -> Bool #

(<=) :: Dual a -> Dual a -> Bool #

(>) :: Dual a -> Dual a -> Bool #

(>=) :: Dual a -> Dual a -> Bool #

max :: Dual a -> Dual a -> Dual a #

min :: Dual a -> Dual a -> Dual a #

Show a => Show (Dual a) Source # 
Instance details

Defined in Circuit.Mat

Methods

showsPrec :: Int -> Dual a -> ShowS #

show :: Dual a -> String #

showList :: [Dual a] -> ShowS #

transposeMat :: (Additive s, Multiplicative s, Finite i, Finite j, Eq i, Eq j) => Mat s i j -> Mat s j i Source #

Reversibility: matrix transpose.

This is the dagger/reversibility involution. It is contravariant and identity-on-objects: a morphism i -> j becomes j -> i, but the object labels themselves do not change.

dualMat :: (Additive s, Multiplicative s, Finite i, Finite j, Eq i, Eq j) => Mat s i j -> Mat s (Dual j) (Dual i) Source #

Duality: the dual of a linear map f :: i -> j is f* :: j* -> i*.

This is not identity-on-objects: it tags both source and target with Dual. Over a field it is the transpose carried by the dual basis.

class Conjugate s where Source #

Scalar involution. For Complex this is adj / complex conjugation.

Methods

conjugateS :: s -> s Source #

Instances

Instances details
Conjugate Double Source # 
Instance details

Defined in Circuit.Mat

(Distributive s, Subtractive s) => Conjugate (Complex s) Source # 
Instance details

Defined in Circuit.Mat

Methods

conjugateS :: Complex s -> Complex s Source #

conjugateMat :: (Conjugate s, Additive s, Multiplicative s, Finite i, Finite j, Eq j) => Mat s i j -> Mat s i j Source #

Conjugation: the composite of duality and reversibility, expressed with the standard identification of a finite-dimensional space with its dual.

It is a covariant endofunctor on objects and, over Complex, is entrywise complex conjugation. It is distinct from both transposeMat (identity-on-objects contravariant) and dualMat (not identity-on-objects).

Dense field calculus (re-exported from Circuit.Mat.Field)

type MatrixM (n :: Nat) a = Matrix n n a Source #

A square matrix of statically-known size.

cholM :: forall (n :: Nat) a. (KnownNat n, KnownNats '[n, n], ExpField a) => MatrixM n a -> MatrixM n a Source #

Cholesky decomposition using the Cholesky-Crout algorithm.

>>> e = F.array @[3,3] @Double [4,12,-16,12,37,-43,-16,-43,98]
>>> pretty (cholM e)
[[2.0,0.0,0.0],
 [6.0,1.0,0.0],
 [-8.0,5.0,3.0]]
>>> F.mult (cholM e) (F.transpose (cholM e)) == e
True

invtriM :: forall (n :: Nat) a. (KnownNat n, KnownNats '[n, n], Subtractive a, Divisive a) => MatrixM n a -> MatrixM n a Source #

Inversion of a triangular matrix.

>>> t = F.array @[3,3] @Double [1,0,1,0,1,2,0,0,1]
>>> pretty (invtriM t)
[[1.0,0.0,-1.0],
 [0.0,1.0,-2.0],
 [0.0,0.0,1.0]]
>>> F.ident @[3,3] == F.mult t (invtriM t)
True

inverseM :: forall (n :: Nat) a. (KnownNat n, KnownNats '[n, n], ExpField a) => MatrixM n a -> MatrixM n a Source #

Inverse of a square matrix via Cholesky decomposition.

>>> e = F.array @[3,3] @Double [4,12,-16,12,37,-43,-16,-43,98]
>>> pretty (inverseM e)
[[49.36111111111111,-13.555555555555554,2.1111111111111107],
 [-13.555555555555554,3.7777777777777772,-0.5555555555555555],
 [2.1111111111111107,-0.5555555555555555,0.1111111111111111]]

newtype MatField (n :: Nat) a Source #

Newtype wrapper that gives square matrices a multiplicative (and, over a field, divisible) structure.

The unit is the identity matrix; multiplication is the usual matrix product. Division uses Cholesky-based inversion.

Constructors

MatField 

Fields

Instances

Instances details
Eq a => Eq (MatField n a) Source # 
Instance details

Defined in Circuit.Mat.Field

Methods

(==) :: MatField n a -> MatField n a -> Bool #

(/=) :: MatField n a -> MatField n a -> Bool #

Show a => Show (MatField n a) Source # 
Instance details

Defined in Circuit.Mat.Field

Methods

showsPrec :: Int -> MatField n a -> ShowS #

show :: MatField n a -> String #

showList :: [MatField n a] -> ShowS #

(KnownNat n, KnownNats '[n, n], Additive a, Multiplicative a, ExpField a) => Divisive (MatField n a) Source # 
Instance details

Defined in Circuit.Mat.Field

Methods

recip :: MatField n a -> MatField n a #

(/) :: MatField n a -> MatField n a -> MatField n a #

(KnownNat n, KnownNats '[n, n], Additive a, Multiplicative a) => Multiplicative (MatField n a) Source # 
Instance details

Defined in Circuit.Mat.Field

Methods

(*) :: MatField n a -> MatField n a -> MatField n a #

one :: MatField n a #