econiac.core.manifold
econiac.core.manifold
Pacioli manifold — the directed graph of institutional money flows.
The Pacioli manifold is a directed graph (V, E) where: - Nodes V are accounts/sectors (households, firms, banks, government, ...) - Edges E are flows (wages, taxes, loans, dividends, ...) - The boundary operator ∂: C₁ → C₀ maps each edge to (head - tail) - The fundamental invariant ∂²=0 encodes double-entry bookkeeping
∂²=0 means: every flow that enters a node also exits somewhere. This is not a constraint we impose — it is the definition of the manifold. A model that violates it is a type error.
The incidence matrix B (n_nodes × n_edges) encodes ∂: B[i, e] = +1 if edge e flows INTO node i B[i, e] = -1 if edge e flows OUT OF node i B[i, e] = 0 otherwise
Stock-flow consistency: B @ flows = 0 (net flow at every node is zero).
Homology
H₀ = ker(∂₀) / im(∂₁) — connected components (disconnected sectors) H₁ = ker(∂₁) / im(∂₂) — independent financial cycles
References
Buckley (2026) Topology of Conservation. doi:10.5281/zenodo.20234853 Buckley (2026) Economic Gauge Theory. doi:10.5281/zenodo.20259495 Godley & Lavoie (2007) Monetary Economics.
BalanceSheet
dataclass
A sectoral balance sheet matrix.
positions[i, j] = net position of sector i in instrument j. Positive = asset, negative = liability.
The stock-flow consistency condition
positions.sum(axis=0) ≈ 0
Every asset is someone else's liability.
Source code in src/econiac/core/manifold.py
column_sums()
is_consistent(atol=1e-06)
True iff ∂²=0: every asset has a corresponding liability.
GodleyTable
dataclass
A Godley table: the flow analogue of the BalanceSheet.
flows[i, j] = net flow into sector i from instrument j in one period. Column sums must be zero: every source of funds has a use of funds.
Updating a BalanceSheet by one period
new_positions = old_positions + flows
Source code in src/econiac/core/manifold.py
apply(balance_sheet)
Advance the balance sheet by one period.
Source code in src/econiac/core/manifold.py
HomologyGroups
Bases: NamedTuple
Homology groups of the Pacioli manifold.
Source code in src/econiac/core/manifold.py
PacioliManifold
dataclass
The Pacioli manifold: a directed graph of institutional money flows.
Encodes the flow network as an incidence matrix B (n_nodes × n_edges): B[i, e] = +1 if edge e flows into node i B[i, e] = -1 if edge e flows out of node i B[i, e] = 0 otherwise
The fundamental invariant ∂²=0 is B @ B.T having zero off-diagonal structure consistent with the chain complex — enforced at construction.
For most financial applications use PacioliManifold.from_edges() or PacioliManifold.from_godley_table().
Source code in src/econiac/core/manifold.py
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 | |
from_edges(nodes, edges)
staticmethod
Construct from a list of (name, source_node, target_node) triples.
Example
PacioliManifold.from_edges( nodes=['households', 'firms', 'banks'], edges=[ ('wages', 'firms', 'households'), ('deposits', 'households', 'banks'), ('loans', 'banks', 'firms'), ] )
Source code in src/econiac/core/manifold.py
from_godley_table(balance_sheet)
staticmethod
Derive the Pacioli manifold from a BalanceSheet.
Each instrument becomes an edge connecting the sector with the largest positive position (asset holder) to the sector with the largest negative position (liability issuer). For multi-sector instruments this is an approximation — use from_edges() for precise bilateral flow specification.
Source code in src/econiac/core/manifold.py
boundary(flows)
Apply the boundary operator ∂: C₁ → C₀.
Returns net flow at each node. Should be zero for stock-flow consistent flows (∂(flows) = 0).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
flows
|
Array
|
shape (n_edges,) — flow magnitude on each edge |
required |
Returns:
| Type | Description |
|---|---|
Array
|
shape (n_nodes,) — net inflow at each node |
Source code in src/econiac/core/manifold.py
is_consistent(flows, atol=1e-06)
True iff the given flows satisfy ∂(flows) = 0.
homology()
Compute the Betti numbers (H₀, H₁) of the manifold.
Uses the rank-nullity theorem on the incidence matrix
H₀ rank = n_nodes - rank(B) connected components H₁ rank = n_edges - rank(B) independent cycles
Source code in src/econiac/core/manifold.py
laplacian()
Graph Laplacian L = B @ B.T.
Eigenvalues of L encode the spectral geometry of the flow network. Zero eigenvalues correspond to connected components (H₀).
Source code in src/econiac/core/manifold.py
CurvedBalanceSheet
dataclass
A balance sheet on a curved Pacioli manifold.
Extends BalanceSheet with a curvature field F (field strength tensor) representing the failure of flatness:
∂²(bs) = F(bs) instead of 0
F is a skew-symmetric (n_instruments,) vector of curvature values — one per instrument column. When F=0 everywhere, this reduces to a flat BalanceSheet and the standard PCL type system applies.
Physical interpretation: - F=0: no arbitrage; the manifold is flat. - F≠0: genuine arbitrage or risk premium at this point; the manifold has non-zero field strength. The magnitude ||F|| is the arbitrage profit per unit of capital deployed on a round trip.
Relation to gauge theory
F = dA + A∧A
where A is the connection (exchange-rate gauge field). Non-zero F means parallel transport around a closed loop returns a different value — the holonomy is non-trivial.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
positions
|
Array
|
shape (n_sectors, n_instruments) |
required |
sectors
|
list[str]
|
list of sector names |
required |
instruments
|
list[str]
|
list of instrument names |
required |
curvature
|
Array
|
shape (n_instruments,) — field strength per instrument; zero recovers flat BalanceSheet behaviour |
required |
Source code in src/econiac/core/manifold.py
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 | |
column_sums()
is_consistent(atol=1e-06)
True iff column sums equal the curvature field (generalised ∂²=0).
is_flat(atol=1e-06)
True iff the curvature field is zero (standard PCL flatness).
net_worth()
to_flat()
Project to a flat BalanceSheet by zeroing the curvature.
The projection adjusts the last sector's positions so that column sums are exactly zero. Use when you need PCL combinators (which require flat inputs) but want to work near a curved manifold.
Source code in src/econiac/core/manifold.py
holonomy(computation, curved_bs)
Compute the holonomy of a computation on a curved balance sheet.
Holonomy measures path-dependence: the residual after applying
computation and returning to the start. On a flat manifold,
holonomy is zero. On a curved manifold, it equals the integrated
field strength around the loop — the arbitrage profit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
computation
|
Computation
|
a PCL Computation (BalanceSheet → BalanceSheet) |
required |
curved_bs
|
CurvedBalanceSheet
|
the base point on the curved manifold |
required |
Returns:
| Type | Description |
|---|---|
Array
|
shape (n_instruments,) — holonomy per instrument; |
Array
|
zero means no arbitrage; non-zero magnitude = arbitrage profit |
Source code in src/econiac/core/manifold.py
add_residual_sector(bs)
Extend a BalanceSheet with a _residual sector for discrepancy accounting.
The residual sector absorbs any imbalance in the existing positions, making the extended balance sheet exactly flat (∂²=0). Its initial positions are set to -column_sums(bs) so that the total column sums are zero.
Usage
bs_raw = ... # national accounts data (may not balance exactly) bs = add_residual_sector(bs_raw) assert bs.is_consistent() # guaranteed
The residual sector's net position is observable: large values indicate either data quality issues or model misspecification.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bs
|
BalanceSheet
|
the original (potentially inconsistent) balance sheet |
required |
Returns:
| Type | Description |
|---|---|
BalanceSheet
|
a new BalanceSheet with one extra sector ('_residual') that |
BalanceSheet
|
exactly absorbs any imbalance |
Source code in src/econiac/core/manifold.py
add_float_sector(bs)
Extend a BalanceSheet with a _float sector for in-transit payments.
The float sector acts as a temporary holding account for payments that have been sent but not yet received. Its initial balance is zero.
Typical usage
Payment sent (sender's books updated immediately):
send = flow("payer", "_float", "deposits", amount)
Payment received (receiver's books updated at settlement):
receive = flow("_float", "payee", "deposits", amount)
sequence(send, receive) has zero net float — the float clears.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bs
|
BalanceSheet
|
the original balance sheet |
required |
Returns:
| Type | Description |
|---|---|
BalanceSheet
|
a new BalanceSheet with one extra sector ('_float') initialised to zero |
Source code in src/econiac/core/manifold.py
residual_magnitude(bs)
Return the L2 norm of the _residual sector's positions.
Zero means perfect conservation; large values indicate data discrepancy or model misspecification. Use as a diagnostic or calibration loss term.
Returns 0.0 if the balance sheet has no _residual sector.
Source code in src/econiac/core/manifold.py
three_sector_sfc(household_deposits=100.0, firm_loans=80.0)
Minimal three-sector stock-flow consistent model.
Sectors: households, firms, banks Instruments: deposits, loans
The three flows form one closed financial cycle
wages: firms → households deposits: households → banks loans: banks → firms
H₀ = 1 (connected), H₁ = 1 (one independent cycle).
Returns a (BalanceSheet, PacioliManifold) pair ready for simulation.