exchangealgebra: Exchange Algebra for bookkeeping and economic simulation

[ accounting, finance, library, math ] [ Propose Tags ] [ Report a vulnerability ]

Exchange Algebra is an algebraic description of bookkeeping developed by Hiroshi Deguchi. This package represents bookkeeping entries as elements of a redundant algebra, where increases and decreases are expressed by Hat and Not bases rather than by sign. It provides journals with notes, accounting operations such as closing, transfers, trial balances, and financial statements, CSV input and output, and a state-space framework for economic simulation. See the README for a guide and the changelog for migration notes.


[Skip to Readme]

Modules

[Last Documentation]

  • ExchangeAlgebra
    • ExchangeAlgebra.Accounting
      • ExchangeAlgebra.Accounting.Account
      • ExchangeAlgebra.Accounting.Adjustment
      • ExchangeAlgebra.Accounting.Closing
      • ExchangeAlgebra.Accounting.Consolidation
      • ExchangeAlgebra.Accounting.Entries
      • ExchangeAlgebra.Accounting.Equivalence
      • ExchangeAlgebra.Accounting.Exchange
      • ExchangeAlgebra.Accounting.PostingPolicy
      • ExchangeAlgebra.Accounting.Statements
        • ExchangeAlgebra.Accounting.Statements.Group
        • ExchangeAlgebra.Accounting.Statements.Metric
        • ExchangeAlgebra.Accounting.Statements.Presentation
      • ExchangeAlgebra.Accounting.Transaction
      • ExchangeAlgebra.Accounting.Transfer
      • ExchangeAlgebra.Accounting.TrialBalance
        • ExchangeAlgebra.Accounting.TrialBalance.Balance
        • ExchangeAlgebra.Accounting.TrialBalance.Validation
    • ExchangeAlgebra.Algebra
      • ExchangeAlgebra.Algebra.Base
        • Account
          • ExchangeAlgebra.Algebra.Base.Account.JcciAliases
          • ExchangeAlgebra.Algebra.Base.Account.Registry
          • ExchangeAlgebra.Algebra.Base.Account.Types
        • ExchangeAlgebra.Algebra.Base.Element
      • ExchangeAlgebra.Algebra.Core
      • ExchangeAlgebra.Algebra.Element
      • ExchangeAlgebra.Algebra.Internal
      • ExchangeAlgebra.Algebra.Posting
      • ExchangeAlgebra.Algebra.Transfer
        • ExchangeAlgebra.Algebra.Transfer.Rule
      • ExchangeAlgebra.Algebra.Value
    • ExchangeAlgebra.Assist
      • ExchangeAlgebra.Assist.Descriptions
    • ExchangeAlgebra.Bookkeeping
    • Consolidation
      • ExchangeAlgebra.Consolidation.Worksheet
    • ExchangeAlgebra.Convert
      • ExchangeAlgebra.Convert.Checked
      • ExchangeAlgebra.Convert.Csv
    • IO
      • ExchangeAlgebra.IO.Input
        • ExchangeAlgebra.IO.Input.Admission
        • ExchangeAlgebra.IO.Input.Assist
        • ExchangeAlgebra.IO.Input.Csv
      • ExchangeAlgebra.IO.Output
        • ExchangeAlgebra.IO.Output.Csv
        • ExchangeAlgebra.IO.Output.Statements
    • ExchangeAlgebra.Journal
      • ExchangeAlgebra.Journal.Core
      • ExchangeAlgebra.Journal.Transfer
        • ExchangeAlgebra.Journal.Transfer.Rule
    • ExchangeAlgebra.Optimize
      • ExchangeAlgebra.Optimize.Annealing
      • ExchangeAlgebra.Optimize.GA
    • ExchangeAlgebra.Posting
    • Render
      • ExchangeAlgebra.Render.Bookkeeping
      • ExchangeAlgebra.Render.Csv
      • ExchangeAlgebra.Render.Simulation
    • Reporting
      • ExchangeAlgebra.Reporting.Group
      • ExchangeAlgebra.Reporting.Metric
      • ExchangeAlgebra.Reporting.Presentation
    • ExchangeAlgebra.Simulate
      • ExchangeAlgebra.Simulate.Analysis
      • ExchangeAlgebra.Simulate.Engine
      • ExchangeAlgebra.Simulate.Lite
      • ExchangeAlgebra.Simulate.Network
      • ExchangeAlgebra.Simulate.Policy
      • ExchangeAlgebra.Simulate.Random
      • ExchangeAlgebra.Simulate.Spill
      • ExchangeAlgebra.Simulate.Visualize
    • Simulation
      • ExchangeAlgebra.Simulation.Analysis
      • ExchangeAlgebra.Simulation.Network
        • ExchangeAlgebra.Simulation.Network.Csv
        • ExchangeAlgebra.Simulation.Network.Flows
        • ExchangeAlgebra.Simulation.Network.Journal
      • ExchangeAlgebra.Simulation.Optimize
        • ExchangeAlgebra.Simulation.Optimize.Annealing
        • ExchangeAlgebra.Simulation.Optimize.GA
      • ExchangeAlgebra.Simulation.Output
    • TrialBalance
      • ExchangeAlgebra.TrialBalance.Balance
      • ExchangeAlgebra.TrialBalance.Validation
    • ExchangeAlgebra.Value

Flags

Manual Flags

NameDescriptionDefault
visualize

Build ExchangeAlgebra.Simulate.Visualize, which needs Chart, Chart-cairo and a cairo / pango toolchain. Enable with --flag exchangealgebra:visualize to write PNG charts.

Disabled

Use -f <flag> to enable a flag, or -f -<flag> to disable that flag. More info

Downloads

Note: This package has metadata revisions in the cabal description newer than included in the tarball. To unpack the package including the revisions, use 'cabal get'.

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 0.4.0.0, 0.4.1.0, 0.4.1.1, 0.4.1.2, 0.5.0.0, 0.5.1.0, 0.5.2.0, 0.5.3.0, 0.5.3.1, 0.5.3.2, 0.6.0.0, 0.6.0.1
Change log ChangeLog.md
Dependencies array (>=0.5.5 && <0.6), async (>=2.2 && <2.3), base (>=4.20 && <5), binary (>=0.8 && <0.9), bytestring (>=0.11 && <0.13), containers (>=0.6 && <0.8), Decimal (>=0.5 && <0.6), deepseq (>=1.4 && <1.6), hashable (>=1.4 && <1.6), numeric-prelude (>=0.4.4 && <0.5), parallel (>=3.2 && <3.3), random (>=1.2 && <1.3), scientific (>=0.3.7 && <0.4), text (>=2.0 && <2.2), time (>=1.12 && <1.15), unicode-show (>=0.1 && <0.2), unordered-containers (>=0.2.19 && <0.3), vector (>=0.13 && <0.14) [details]
License LicenseRef-OtherLicense
Copyright 2018-2026 Kaya Akagi
Author Kaya Akagi
Maintainer yakagika@icloud.com
Uploaded by yakagika at 2026-11-06T02:22:55Z
Revised Revision 1 made by yakagika at 2026-11-09T01:45:18Z
Category Accounting, Finance, Math
Home page https://github.com/yakagika/ExchangeAlgebra#readme
Bug tracker https://github.com/yakagika/ExchangeAlgebra/issues
Source repo head: git clone https://github.com/yakagika/ExchangeAlgebra
Distributions
Downloads 119 total (48 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs not available [build log]
All reported builds failed as of 2026-11-06 [all 2 reports]

Readme for exchangealgebra-0.6.0.0

[back to package description]

exchangealgebra

exchangealgebra is a Haskell library for Exchange Algebra, an algebraic description of bookkeeping systems developed by Hiroshi Deguchi. It treats bookkeeping entries as elements of a scaled basis algebra, so journaling, closing, transfer, and simulation can be written as function composition and projection.

The library is used in the preprint Accounting State Space as the Minimal Unit for Economic Agent-Based Modeling (Akagi, 2026) as the minimal unit for economic agent-based simulation and ripple-effect analysis. See the Publications section for citation details.

Installation

The package is on Hackage. Pin a specific version in your Stack project:

# stack.yaml
extra-deps:
  - exchangealgebra-0.6.0.0
# package.yaml (your project)
dependencies:
  - exchangealgebra

If you need an unreleased revision, you can also point extra-deps at a specific Git commit instead:

extra-deps:
  - git: https://github.com/yakagika/ExchangeAlgebra.git
    commit: <commit-sha>

Requirements:

  • GHC 9.10 (tested with Stackage lts-24.4)
  • Cabal 3.0 or later
  • The plotting module ExchangeAlgebra.Simulate.Visualize is off by default. To use it, install the Cairo / Pango / Freetype system libraries (on macOS: brew install cairo pango) and enable visualize with stack build --flag exchangealgebra:visualize or set visualize: true under flags → exchangealgebra in stack.yaml. The fixed-price-io and fixed-price-io-spill examples import this module; examples/stack.yaml enables the flag.

How to consume this package

Three common use cases:

1. Use it as a library dependency

Add exchangealgebra to your project's build-depends via the Hackage extra-deps pin above (or the Git pin for unreleased revisions). The examples/ directory is not needed for this; it is only shipped in this Git repository.

2. Run the bundled examples

Clone the repository and build from the root:

git clone https://github.com/yakagika/ExchangeAlgebra.git
cd ExchangeAlgebra
stack build
stack exec -- fixed-price-io        # or ebex1, ebex2, ...

See the examples directory for the full catalog and runtime prerequisites (uv for Python plots, output directories, etc.).

3. Copy or fork a single example

If you want to start from one example without pulling the whole repository, degit (or its maintained successor tiged) lets you grab just the subtree without git history:

npx degit yakagika/ExchangeAlgebra/examples my-examples
cd my-examples
# then edit freely as a starting point

A standalone examples/stack.yaml (pinned to the Hackage release) is checked in, so cd examples && stack build works after a sparse-clone or degit without needing the rest of the repository.

Module Overview

The following entry points describe the 0.6 API. The layers are Algebra, Journal, Accounting, IO, and Simulation.

Umbrella

The recommended entry point collects the common single-period bookkeeping API.

  • ExchangeAlgebra — exports named selections of generic algebra, account vocabulary and exchange operations, transfers and closing, trial-balance balances and findings, financial-statement presentation, CSV and bookkeeping output, and value types.
  • ExchangeAlgebra.Accounting — re-exports the Definitions 7-9 accounting basis, exchange decomposition, and transfer core.

Account vocabulary

These modules define basis elements and the canonical account metadata vocabulary.

  • ExchangeAlgebra.Accounting.Account — defines account titles, classifications, registry metadata, posting eligibility, and PIMO direction helpers.
  • ExchangeAlgebra.Accounting.Exchange — defines ExBaseClass, AccountBase, Exchange, debit/credit decomposition, balance checks, and account projections.
  • ExchangeAlgebra.Algebra.Element — defines generic wildcard-aware basis components.
  • ExchangeAlgebra.Algebra.Base — defines generic basis classes, Hat/Not bases, and basis display helpers.

Core algebra

These modules implement exchange-algebra values, transformations, and selectable numeric representations.

  • ExchangeAlgebra.Algebra — defines Alg, exchange-algebra operations, projections, and aggregation.
  • ExchangeAlgebra.Algebra.Internal — exposes the representation behind Alg for advanced use (all constructors, cache fields, rebuild helpers), but carries no stability guarantee.
  • ExchangeAlgebra.Algebra.Transfer — generic coordinate rules that return additional entries to add to the original algebra.
  • ExchangeAlgebra.Algebra.Value — provides fast typed and exact non-negative value types for Alg and Journal.

Wildcards: queries versus ledger entries

In a ledger entry's base, # means that the axis does not apply. It is one value, not a pattern. For example, a Cash entry can have no product axis. In a projection query or a transfer's from pattern, # matches any value, including # in a ledger entry. In a transfer's to pattern, # preserves the source coordinate. Use mapBasePart or another mapping function to collapse an axis.

Matching is one way: a pattern p matches a ledger value e when p = # or p = e, on each axis. A concrete query does not select a ledger #:

import ExchangeAlgebra

ledger = 10 .@ Not :< (Cash, (.#)) :: Alg Double (HatBase (AccountTitles, CountUnit))
concreteAmount = norm (proj [Not :< (Cash, Yen)] ledger)  -- 0.0
wildcardAmount = norm (proj [Not :< (Cash, (.#))] ledger) -- 10.0

Canonical ordering treats # as an ordinary value. The .== operator remains symmetric; use projection or ExchangeAlgebra.Algebra.Transfer for one-way matching against ledger entries.

Journal

These modules attach notes to postings.

  • ExchangeAlgebra.Journal — defines metadata-bearing Journal values, indexed projections, and journal aggregation.
  • ExchangeAlgebra.Journal.Transfer — carries entries across notes and reads side totals. Use Accounting.Transfer.transferEntriesBy for accounting transfers.

Simulation: Simulation.* and Simulate.*

Simulation code lives under two module prefixes with different futures.

  • ExchangeAlgebra.Simulation.* holds components that a model uses besides the runner: trade networks, matrix analysis, optimization, and CSV output of results. These modules are kept after 0.6.
  • ExchangeAlgebra.Simulate.* holds the simulation runners and their support: the classic state-space engine, the Lite front-end, ledger policies, spill files, random helpers, and Chart plotting.

Components (Simulation.*):

  • ExchangeAlgebra.Simulation.Network — separates sparse trade-network topology from input coefficients; .Flows, .Csv, and .Journal generate industrial flows, read network CSV files, and sum flows into a Journal.
  • ExchangeAlgebra.Simulation.Analysis — computes a Leontief inverse and ripple effects.
  • ExchangeAlgebra.Simulation.Output — writes simulation results as CSV without Chart. writeFuncResults and writeFuncResultsWithContext take the classic engine's StateTime.
  • ExchangeAlgebra.Simulation.Optimize — see Optimization below.

Runners (Simulate.*):

  • ExchangeAlgebra.Simulate — provides the classic state-space engine, spill support, and scenario execution, and re-exports the ripple utilities.
  • ExchangeAlgebra.Simulate.Engine — re-exports the classic state-space protocol and runners without spill configuration or analysis helpers.
  • ExchangeAlgebra.Simulate.Lite — provides the product-HKD, BSP front-end with declarative field rules and stages.
  • ExchangeAlgebra.Simulate.Policy — declares retention, spill, and compaction policy for long simulations.
  • ExchangeAlgebra.Simulate.Spill — provides spill chunk writers, checked readers, and ledger restoration shared by simulation and reporting layers.
  • ExchangeAlgebra.Simulate.Random — re-exports simulation random-number helpers.
  • ExchangeAlgebra.Simulate.Visualize — renders simulation grids and time series with Chart/Cairo.

Planned changes to simulation modules

The next major version (0.7) is planned to move simulation into a separate package, exchangealgebra-simulation, whose runner is a new model evaluator. The plan affects the 0.6 modules as follows:

  • Simulation.* and Simulate.Spill move to the new package. The writers that take StateTime (writeFuncResults, writeFuncResultsWithContext) are redesigned for the evaluator or removed.
  • The classic engine (Simulate, Simulate.Engine, Simulate.Random), Simulate.Lite, and Simulate.Policy are removed and not moved. Their successor is the model evaluator.
  • Simulate.Visualize either moves to the new package behind Chart or is removed; the choice is made in 0.7.

The 0.6 modules do not emit deprecation warnings, because the successor package has not been released. Changes to this plan will be recorded in the changelog.

Which simulation runner to use in 0.6

ExchangeAlgebra.Simulate.Lite provides Generic-derived world records, term-boundary field rules, stages, and runLite / runLiteWithPolicy. ExchangeAlgebra.Simulate.Engine is the lower-level classic state-space API (StateTime, Updatable, UpdatePattern, and the runSimulation family), retained for existing models and reproducibility. Both runners are planned for removal in 0.7 (see above). For a new model built on 0.6, Lite needs less code; keep model logic separate from runner code so that it can be moved to the successor evaluator.

Bookkeeping and reporting

These modules build adjustments, validate reporting boundaries, and produce statements and output.

  • ExchangeAlgebra.Accounting.Entries — builds period-end adjustment postings from explicit external amounts.
  • ExchangeAlgebra.IO.Output.Statements — writes journals, account ledgers, trial balances, worksheets, and financial statements as CSV.
  • ExchangeAlgebra.IO.Output.Csv — provides CSV serialization independently of document layouts.
  • ExchangeAlgebra.Accounting.Statements.Group — defines presentation groups and contra-account netting policy.
  • ExchangeAlgebra.Accounting.TrialBalance.Balance — the shared AccountBalance type and debit-then-credit netting primitives.
  • ExchangeAlgebra.Accounting.TrialBalance.Validation — reports trial-balance findings and reclassification instructions.
  • ExchangeAlgebra.Accounting.Statements.Metric — derives typed, read-only metrics without inserting posting coordinates.
  • ExchangeAlgebra.Accounting.Statements.Presentation — presents a trial balance and its stage as JGAAP financial statements.
  • ExchangeAlgebra.Accounting.Consolidation — validates consolidation adjustments and preserves worksheet provenance.

Conversion and assistance

These modules convert external postings safely and expose deterministic account-selection guidance.

  • ExchangeAlgebra.IO.Input — converts side, account-name, and amount data, and validates externally generated entries before constructing journals.
  • ExchangeAlgebra.IO.Input.Csv — reads a fixed journal CSV schema into normalized external postings.
  • ExchangeAlgebra.IO.Input.Assist — provides LLM-facing account metadata, suggestions, and validation explanations.

Optimization

These modules define a common solver interface and two concrete stochastic strategies.

  • ExchangeAlgebra.Simulation.Optimize — defines the strategy-agnostic, monadic Solver interface.
  • ExchangeAlgebra.Simulation.Optimize.Annealing — implements simulated annealing over arbitrary candidate types.
  • ExchangeAlgebra.Simulation.Optimize.GA — implements a real-coded genetic algorithm over numeric vectors.

Spill writing, checked reading, and ledger restoration live in Simulate.Spill. Import Simulation.Output for simulation writers; these functions are separate from the root bookkeeping API.

Importing both ExchangeAlgebra and ExchangeAlgebra.Journal unqualified causes name collisions on sigma, fromList, map, filter, and friends. See the recommended import patterns below.

Choosing a value type

The value parameter v in Alg v b / Journal n v b is selectable. Pick per workload. The bare Double is the default; ExchangeAlgebra.Algebra.Value adds two newtype money types — MoneyDouble (fast, FP) and MoneyDecimal (exact).

Double (default) MoneyDouble (…Value) MoneyDecimal (…Value)
Representation IEEE-754 binary float newtype over Double (identical bits) exact non-negative base-10 decimal (wraps Data.Decimal)
Distinct money type ✗ — a bare numeric type ✓ — can't be silently mixed with coefficients / draws ✓
Decimal prices / ratios approximate approximate exact
Tax / proration (*, /) rounding noise rounding noise exact intermediates; round explicitly with bankersRound / ceilingRound
Construction-order independence ✗ — addition is non-associative, so the order same-base postings are summed shifts the last-ULP of norm / bar ✗ — identical to Double ✓ — addition is exact & associative, so norm / bar / balance are identical regardless of build order
Determinism / auditability not bit-reproducible across reorderings not bit-reproducible bit-reproducible
Memory per value ~16 B ~16 B (zero-cost wrapper) ~40 B + indirection (~2.5×)
Speed fastest fastest (= Double) slower (boxed Integer mantissa arithmetic)
Typical use numeric methods (Leontief inverse, optimization), quick ABM ABM where you want a typed money value distinct from raw Double audited ledgers, bookkeeping, anywhere a total must be reproducible
import ExchangeAlgebra

entry :: Alg MoneyDecimal (HatBase AccountTitles)
entry = 10.5 .@ Not :< Cash .+ 10.5 .@ Not :< Sales

FP vs exact, made concrete (measured). In the bundled fixed-price-io, the same stock value matches both types where it lands on an exactly-representable result (stock(t=1, c=6) = 30.0 under MoneyDouble and MoneyDecimal), but diverges in the last ULP once FP rounding accumulates over terms (stock(t=100, c=6): 767.960563480499 under MoneyDouble vs 767.9605634804993 under MoneyDecimal). The cost of that exactness is real and was measured: in the 200-company model (all-pairs purchases over a spill-to-disk ledger; fixed-price-io-spill, and sim2, its MoneyDecimal version removed in 0.6.0.0), MoneyDecimal ran ~5–7× slower in wall-clock and used ~15% more memory than MoneyDouble (consistent at EA_LASTC/EA_LASTTERM of 40/30 and 80/50 — e.g. 23.3 s → 4.4 s and 1.42 GB → 1.19 GB at 80/50). The boxed Integer-mantissa decimal arithmetic dominates the runtime, so this is the exactness ↔ speed trade-off in concrete numbers. MoneyDouble is exactly as fast as bare Double (a zero-cost wrapper) and runs everywhere Double does — its subtraction is signed, so the negative intermediates that arise inside bar / (.-) are fine. (Number.NonNegative.Double is not a usable value type for this reason: its (-) errors on a negative result, which the algebra's netting produces constantly. Its HatVal instance was deprecated in 0.5.0.0 and removed in 0.6 — use MoneyDouble for a non-negative-by-convention FP money type instead.)

Boundary pattern (simulations). Keep ABM parameters, input coefficients and random draws as Double, and convert (realToFrac) only where a value enters the ledger; convert reported stocks/profits back to Double for visualization. The ledger arithmetic in between is then exact. The bundled bookkeeping and simulation examples follow this pattern.

Large scale: precision × memory trade-off. Retaining every posting is O(n) regardless of value type; MoneyDecimal adds a constant factor (~2.5× per value box) on top. For very large simulations weigh exactness against that overhead — or use the spill-to-disk path (below) to keep memory constant.

fromList ordering contract. EJ.fromList is a strict O(N) left fold. It preserves the multiset of postings exactly. When two postings collide on the same note and base they land in one ordered sequence; that sequence's order is observable through Eq / Show / toAlg / Binary, and for Double through the last-ULP of norm / bar. For MoneyDecimal the order never affects norm / bar / balance. (Integer is intentionally not offered — it cannot represent the fractional relative prices the ABM work depends on.)

Migrating to 0.6.0.0

0.6.0.0 is a major (breaking) release. Update imports, transfer composition, value instances, and plotting configuration as described here. See the changelog for the full per-name migration tables.

  • Transfers add entries. Transfer and closing functions return additional entries without rewriting the original postings. Add the returned entries with (.+). ExchangeAlgebra.Algebra.Transfer and ExchangeAlgebra.Journal.Transfer replace their contents under the same module names; see Removed in the changelog.
  • Root exports are explicit. ExchangeAlgebra has an explicit export list. Import names such as writeTermIO from their owning modules; see Breaking changes without a compatibility shim in the changelog.
  • Accounting names moved. ExchangeAlgebra.Algebra, ExchangeAlgebra.Algebra.Base, ExchangeAlgebra.Algebra.Internal, and ExchangeAlgebra.Journal no longer re-export accounting names. Import their accounting owners, or use import ExchangeAlgebra, which still provides all these accounting names.
  • Module moves. Deprecated compatibility modules remain in 0.6 and are removed in 0.7.0.0. See Deprecated in the changelog for the new paths.
  • Exact variants are merged. The *Exact functions and ExchangeAlgebra.Algebra.Exact and ExchangeAlgebra.Journal.Exact modules are removed. Ordinary functions provide the same readouts; for example, replace normExact with norm, which returns the value directly.
  • Text instead of String. Public API fields carrying human-readable text now use Text. Enable OverloadedStrings or pass text with Data.Text.pack; see the changelog for the affected fields and the exceptions.
  • Plotting is opt-in. The visualize flag is off by default. Enable it to use ExchangeAlgebra.Simulate.Visualize as described in Installation.
  • Custom HatVal instances. Implement valueError, Accum, all eight accumulator methods, and roundRational. Missing methods fail at run time. See the Custom HatVal instances implement item in the changelog for the signatures, helper functions, and laws.

Migrating to 0.5.0.0

0.5.0.0 is a major (breaking) release. Most users need no changes — Double ledgers keep working and render identically. Four things to know:

  • Custom HatVal instances: HatVal dropped its RealFloat superclass and added showValue :: n -> String. If you defined your own HatVal instance, add a showValue (how the value prints inside Alg's Show). If you relied on HatVal n => RealFloat n in a signature, add the RealFloat constraint explicitly. The built-in Double / NN.Double instances are unchanged.
  • fromList accumulation order: now a strict left fold. The multiset is preserved, but if you compared Show / Eq / serialized output of a fromList-built journal byte-for-byte, the same-(note,base) sequence order may differ. Switch such ledgers to MoneyDecimal for order-independent results, or compare via norm / bar / balanceBy rather than raw structure.
  • Abstract Alg / Journal: pattern-match Alg on Zero and (:@) only and build values with (.@), (.+), fromList, mkJournal, and (.|). Code that constructed Liner directly must import ExchangeAlgebra.Algebra.Internal (the exposed Alg representation module, which carries no stability guarantee). Rebuild journals with mkJournal, (.|), or fromList.
  • Spill files are validated: readBinarySpillFile and restoreJournalFromBinarySpill now raise on a truncated, stale or gapped file instead of merging a partial ledger; use the ...Checked variants for an Either. Simulate.Lite truncates its spill file when a run opens it.

(0.5.0.0 also includes the union zero-base correctness fix first shipped in 0.4.1.1; see the changelog.)

Alg and Journal are abstract: construct them with (.@), (.+), fromList, mkJournal, and (.|), and match Alg on Zero / (:@) only. The multi-posting representation and its cache fields live in the exposed ExchangeAlgebra.Algebra.Internal module, which carries no stability guarantee.

Simple single-period bookkeeping

import ExchangeAlgebra

main :: IO ()
main = do
    let entry = 100 .@ Not :< Cash .+ 100 .@ Not :< Sales
            :: Alg MoneyDecimal (HatBase AccountTitles)
        context = jcciSecondGradeContext Standalone
    case present context BeforeClosing entry of
        Left issues -> print issues
        Right statements ->
            print (financialStatementRows [CurrentAssetsSection, RevenueSection] statements)

Journal-based work (multi-period, notes, simulation)

import           ExchangeAlgebra.Journal
import           ExchangeAlgebra.Accounting.Account
import           ExchangeAlgebra.Accounting.Exchange
import qualified ExchangeAlgebra.Algebra           as EA
import qualified ExchangeAlgebra.Journal           as EJ
import qualified ExchangeAlgebra.Accounting.Transfer as Transfer
import qualified ExchangeAlgebra.Simulate          as ES
import           ExchangeAlgebra.Simulate          -- unqualified, for StateSpace, Updatable, etc.
import           ExchangeAlgebra.Simulation.Output -- writeTermIO / writeIOMatrix

Even in Journal-centric code you will frequently reach into the Algebra layer (for the Alg type or EA.proj, for example). Using Journal as the unqualified umbrella and pulling the Algebra layer in as EA qualified is the idiomatic style for this library.

Modules to import directly (not re-exported by the umbrellas)

ExchangeAlgebra.Accounting.Entries, ExchangeAlgebra.Simulate.Lite, ExchangeAlgebra.Simulation.Network, and ExchangeAlgebra.Simulate.Policy are by design imported directly rather than re-exported from the ExchangeAlgebra umbrella. They introduce names that would otherwise collide with the Algebra layer (or with one another), so the package keeps them off the umbrella and expects an explicit (usually qualified) import:

import           ExchangeAlgebra
import qualified ExchangeAlgebra.Accounting.Entries     as BK
import qualified ExchangeAlgebra.Simulate.Lite   as Lite

Large-scale simulations (constant memory)

For new Lite simulations, set spillTo in a LedgerPolicy and run with runLiteWithPolicy.

runSimulation keeps the entire world state in memory for the whole run, so peak memory grows with the number of terms. For long horizons or large agent populations, use the spill-to-disk variants instead — they periodically write ledger chunks to disk and evict old terms, so peak memory becomes independent of the number of terms:

import qualified ExchangeAlgebra.Simulate as ES

opts :: ES.SpillOptions Term World Transaction
opts = (ES.mkBinarySpillOptions everyNTerms spillPath extractPayload)
         -- keep only the most recent N terms resident; older terms live on disk
         { ES.spillDeletePolicy = ES.KeepRecentTerms 2 }

main = do
    _world <- ES.runSimulationWithSpill opts gen env
    -- restore with ES.readBinarySpillFile, or import
    -- restoreJournalFromBinarySpill from ExchangeAlgebra.Simulate.Spill
    pure ()
  • ES.runSimulationWithSpill / ES.runScenariosWithSpill are drop-in replacements for runSimulation / runScenarios that add periodic spilling.
  • ES.SpillDeletePolicy bounds resident memory: KeepRecentTerms n keeps a sliding window, DeleteSpilledChunk evicts each chunk right after it is written, NoDelete keeps everything.
  • Restore spilled data with ES.readBinarySpillFile (binary format) or the restoreJournalFromBinarySpill helper from ExchangeAlgebra.Simulate.Spill.

For policy-driven long runs, ExchangeAlgebra.Simulate.Policy keeps FullAudit as the default to preserve the full audit trail, but a declared LedgerPolicy can opt into CompressClosedTerms so only closed terms are compressed; norm / balance stay unchanged, the in-progress term keeps its full history, and because the choice is explicit it is not the forbidden implicit bar / compress shortcut. Pair it with RetainRecent 2 and spillTo = Just ... to keep recent terms resident while older terms are restorable from disk; local measurements of that pattern reduced residency by about 15x. See the Tuning long simulations Haddock section in ExchangeAlgebra.Simulate.Policy for the LedgerPolicy record-syntax example.

A runnable end-to-end example (multi-scenario run with binary spill, KeepRecentTerms, and restore) is examples/basic/fixedPriceIOSpill.hs (the fixed-price-io-spill executable).

A note on visualization

ExchangeAlgebra.Simulate.Visualize provides Chart-based PNG rendering, but we recommend writing CSV output and visualizing it from a separate Python script for production-quality plotting.

Why

  • Chart / Chart-cairo transitively pull in cairo / pango / freetype system libraries, which makes the build environment heavier to set up.
  • For academic work, matplotlib / seaborn / pandas are more flexible than Chart.
  • Using CSV as an intermediate format cleanly separates "compute" from "plot".
  • The same CSVs can be reused with R / Julia / Excel if you need them.
-- Haskell side: write the simulation outputs to CSV
import           ExchangeAlgebra.Simulation.Output (writeIOMatrix)
import qualified ExchangeAlgebra.Simulate.Visualize as ESV
                                                  -- qualified access to writeFuncResults etc.

main = do
    -- ... run the simulation ...
    writeIOMatrix "result/io.csv" matrix
    ESV.writeFuncResults header range world "result/profit.csv"
# Plotting side: run a standalone script with uv + PEP 723 inline deps
uv run --script visualize.py
# visualize.py
# /// script
# requires-python = ">=3.10"
# dependencies = ["pandas>=2.0", "matplotlib>=3.7"]
# ///
import pandas as pd, matplotlib.pyplot as plt
df = pd.read_csv("result/profit.csv")
df.plot(); plt.savefig("result/profit.png")

Concrete runnable examples of this pattern live under the examples/ sub-package (see the examples directory on GitHub).

If you still want to plot from Haskell

If keeping everything in Haskell is important to your workflow, plotLineVector, plotMultiLines, and plotWldsDiffLine in ExchangeAlgebra.Simulate.Visualize write PNGs directly and work without any Python setup.

Examples

Runnable usage examples are collected in the examples/ sub-package on GitHub. See the examples directory and its README for details.

stack build
stack exec -- ebex1      # Introductory bookkeeping example
stack exec -- fixed-price-io       # 100-term simulation (+ Python visualization)
stack exec -- fixed-price-io-spill   # spill-to-disk simulation (constant memory, binary spill + restore)

Documentation

License

Dual licensed under MIT and the Open World License (OWL). See LICENSE for details.

Publications using this library

  • Kaya Akagi. Accounting State Space as the Minimal Unit for Economic Agent-Based Modeling: Advancing Ripple Effect Analysis in Real-Time Economy. Research Square, preprint (Version 1), posted 5 January 2026. DOI: 10.21203/rs.3.rs-8485050/v1

    The ripple-effect simulations reported in this preprint are driven by ExchangeAlgebra.Simulate and the ripple examples preserved in tag archive/research-examples-pre-p4.

If you use this library in academic work, please cite the preprint above. A CITATION.cff file at the repository root provides BibTeX and plain-text forms via GitHub's "Cite this repository" button.

References

  • Hiroshi Deguchi and Bunpei Nakano. Axiomatic Foundations of Vector Accounting. Systems Research, Vol. 3, No. 1, pp. 31–39, 1986. Pergamon Press. DOI: 10.1002/sres.3850030105

    The axiomatic origin of Exchange Algebra. This paper formalizes double-entry bookkeeping as an accounting vector space over the extended basis Γ = Λ ∪ Λ̂ (account titles and their dual hats), introduces the five transaction axioms, and derives the debit/credit partition and the balance principle (|y_L| = |y_R|) purely algebraically.

  • Hiroshi Deguchi. Economics as an Agent-Based Complex System: Toward Agent-Based Social Systems Sciences. Springer, 2004. ISBN 978-4-431-20985-0. https://openlibrary.org/isbn/9784431209850

  • Hiroshi Deguchi. Exchange Algebra (PDF). https://repository.kulib.kyoto-u.ac.jp/dspace/bitstream/2433/82987/1/0809-7.pdf