circuits-diff
Safe HaskellNone
LanguageGHC2024

Circuit.Diff.Star

Description

The Schur-complement bridge — trace solved by starMatrix.

This is where the three floors actually touch. A 'Diff knot body has a backward pass that is affine in cotangents (calculus promises linearity), so its channel self-coupling is a linear map recoverable by probing with basis cotangents. Build that map as a Matrix, take its Kleene star with starMatrix — Gaussian elimination, (I − A)⁻¹, the fourth face of the four-for-one — and the trace's pullback is the Schur complement

db = D·dc + B · star A · C·dc

recovered with one final probe at the backward fixpoint.

Cost: dim probes of backward plus one starMatrix per forward point, shared across all cotangents; each subsequent pullback is two probes and a matrix–vector product. Compare traceNFrom, which pays n probes per cotangent and is only as exact as n is large. Here the backward pass is exact whenever the star exists.

The dependency is deliberate: this module imports Circuit.Mat.Dense, making circuits-ad ⇄ star-matrix a literal edge rather than a nominal alias.

Synopsis

Polymorphic bridge

traceStarMatrix Source #

Arguments

:: forall {k} j c (p :: k) b. (StarSemiring j, MergeZero (->) c) 
=> [j]

forward seed; its length is the channel dimension

-> Int

forward iteration count

-> Diff p ([j], b) ([j], c) 
-> Diff p b c 

Trace over a vector feedback channel, backward pass solved by starMatrix.

The channel is a list [j] whose dimension is fixed by the seed. Forward iterates from the seed (no closed form for a nonlinear fixpoint). Backward:

  1. probe backward with each basis cotangent e_i to read off column i of the channel self-coupling A (in cotangent space);
  2. star A by Conway block recursion — starMatrix;
  3. per cotangent dc: C·dc = fst (backward (0, dc)), then db = snd (backward (star A · C·dc, dc)).

A and star A are forced once (lazily, on first pullback) and shared across cotangents.

Proof obligation: probing assumes every primitive's pullback is a genuinely linear map — true for honestly-constructed 'Diff prims.

Body bridge

solveStarBody :: forall s a b. Body (,) (StarChannel s) Pullback b a -> Pullback b a Source #

Solve a (,) feedback body whose channel is StarChannel in closed form.

The channel type stays exposed, so the dictionary is read directly from the StarChannel value carried on the feedback wire.

A self-coupled scalar body the lazy trace diverges on, solved exactly (dj = 0.3·dj + 2·dc, db = dj, so db/dc = 2/0.7):

>>> :{
let body (FieldStar dj, dc) = (FieldStar (0.3 * dj + 2.0 * dc), dj)
    b = Body (withStarChannel fieldStarChannel (Pullback body)) :: Body (,) (StarChannel FieldStar) Pullback Double Double
:}
>>> let solved = solveStarBody b
>>> abs (runPullback solved 1.0 - 2.0 / 0.7) < 1e-12
True

Double adapters (via FieldStar)

traceStarFromD :: forall {k} c (p :: k) b. MergeZero (->) c => Double -> Int -> Diff p (Double, b) (Double, c) -> Diff p b c Source #

traceStarFrom for a bare Double channel, routed through FieldStar so star a = recip (1 − a) is the class method, not an ad-hoc formula.

Channel x' = 0.3x + b, output c = 2x. The fixpoint is x = b/0.7, so dc/db = 2/0.7 ≈ 2.857 — the same number the Neumann iteration approaches, computed here in closed form:

>>> :{
let body = Diff (\(x, b) ->
      ( (0.3 * x + b, 2.0 * x)
      , \(dx', dc) -> (0.3 * dx' + 2.0 * dc, dx') ))
:}
>>> let (y, pb) = runDiff (traceStarFromD 0 60 body) 1.0
>>> abs (y - 2.0 / 0.7) < 1e-12
True
>>> abs (pb 1.0 - 2.0 / 0.7) < 1e-12
True

traceStarMatrixD :: forall {k} c (p :: k) b. MergeZero (->) c => [Double] -> Int -> Diff p ([Double], b) ([Double], c) -> Diff p b c Source #

traceStarMatrix for bare [Double] channels, routed through FieldStar so starMatrix performs honest (I − A)⁻¹.

A two-dimensional channel: x' = 0.5y + b, y' = 0.3x, output c = x + 2y. Solving the fixpoint by hand: x = b/0.85, y = 0.3b/0.85, c = 1.6b/0.85, so dc/db = 1.6/0.85.

>>> :{
let body2 = Diff (\([x, y], b) ->
      ( ([0.5 * y + b, 0.3 * x], x + 2.0 * y)
      , \([dx', dy'], dc) -> ([0.3 * dy' + dc, 0.5 * dx' + 2.0 * dc], dx') ))
:}
>>> let (y2, pb2) = runDiff (traceStarMatrixD [0, 0] 200 body2) 1.0
>>> abs (y2 - 1.6 / 0.85) < 1e-12
True
>>> abs (pb2 1.0 - 1.6 / 0.85) < 1e-12
True

Orphan instances

Zero (->) FieldStar Source # 
Instance details

Methods

zero :: () -> FieldStar #

Merge (->) FieldStar Source #

FieldStar as a numeric carrier for circuits' additive structure, so hand-built bodies can use structural rows at FieldStar types. (Bodies from linearizeBody never need this — their structural rows are already pointwise pullbacks.)

These instances remain necessary because structural rows (Plus, Zero) carry MergeT ZeroT constraints that resolve to Merge Zero on the wiring arrow. For 'Pullback FieldStar' those constraints bottom out in 'Merge (->) FieldStar' and 'Zero (->) FieldStar'. Deliberately orphan: this module is the federation seam between circuits and numhask-free.

Instance details