Skip to main content

skabm

coverage

Agent-based models written as equations, or in plain English, and run on a knowledge graph.

Say what an agent does, in a paper's equations or your own, and it becomes a rule. Run the rules over a population calibrated from public data, and get back every agent after every tick, as polars: any macro quantity is one expression. Agents are nodes of an RDF graph (maplib), relations are its edges, populations are polars DataFrames, and the API is scikit-learn's. Behaviour comes from published models, each cited to its tables: Poledna et al. (2023)'s Austrian economy calibrated 1:1 from Eurostat, CANVAS (Hommes et al. 2025), del Rio-Chanona et al. (2021)'s occupational mobility, Schelling (1971), and Pangallo et al. (2024)'s epidemic and economy of New York in spring 2020.

Install.

pip install "skabm[voila]"

The coding-agent skills install as a Claude Code plugin, pinned to the release you installed (/plugin marketplace add remiadon/skabm#v<version>, then /plugin install skabm@skabm): skabm-core writes a model from a paper, skabm-simulation takes a request to a working simulation with a Voilà app. They lean on Polars' own skill (/plugin marketplace add polars-inc/skills, then /plugin install polars@polars).

Watch one run. Each model below is a live page on Binder: set it up, press Run, and watch it unfold tick by tick (skabm.player). A first launch builds for a few minutes.

Model Live page Reference Engine Main plot
Schelling segregation launch Schelling (1971) RDF, SPARQL 2-D grid
Bayonne with a car-free old town launch day-to-day route choice (Horowitz 1984; Cascetta 1989), pivot-point mode choice (Koppelman 1983), BPR delays (1964), on census 2022 flows and OpenStreetMap roads RDF, SPARQL geographical map
Occupational mobility and automation launch del Rio-Chanona et al. (2021), J. R. Soc. Interface 18:20200898, on the paper's US data JAX time series, with a per-occupation scatter or the Beveridge curve
Spatial evolutionary markets launch Brock & Hommes (1998), on a lattice (implementation), on the S&P 500 RDF, SPARQL 2-D grid, with price and strategy-share curves
COVID-19 in New York launch Pangallo et al. (2024), Nat. Hum. Behav. 8:264-275, in a lighter form, on the paper's NY metro area and the authors' replication data JAX geographical map, with deaths, employment and output

Locally, serve them all with

uv run --extra voila voila notebooks

What skabm gives researchers

Behaviour as equations a rule is a dict of SymPy expressions, {Firm.price: Firm.price * (1 + inflation)}. The same rule runs on the knowledge graph and, differentiably, in JAX
Rules from a paper the skabm-core skill takes a coding agent from a model section to cited, tested rules (plugin/skills/skabm-core); skabm-simulation takes it from a request to a Voilà app (plugin/skills/skabm-simulation)
Observables in polars a run is every agent after every tick, one DataFrame, so any macro quantity, a Gini included, is a polars expression you write. Nothing is declared to the simulator
Populations from public data calibrators that fit agent rows to known joint facts while every marginal survives exactly
Citations built in each behaviour module carries its published parameter values next to their table and equation
Interventions as graph edits after a fit, the world is a graph: rewire an ownership edge, delete a bank, close a street, then keep simulating
Learned expectations agents forecast with learning rules (SAC, Hommes & Zhu 2014) that are just more rules

A first model

Schelling (1971) in a dozen lines: people of two groups on a grid, and whoever has too few neighbours like them moves to an empty cell.

import polars as pl
from maplib import Model

from skabm import template  # adds DataFrame.with_iri
from skabm.behaviour import person
from skabm.simulation import RDFSimulator
from skabm.space import moore_neighbors

# the world: a 20 x 20 grid of cells, four in five settled by someone of group 0 or 1
cells = pl.DataFrame({"x": range(20)}).join(pl.DataFrame({"y": range(20)}), how="cross")
cells = cells.with_columns(pl.col("x", "y").cast(pl.Float64), id=pl.format("cell_{}_{}", "x", "y"))
people = person.settle(cells)

world = Model()
world.map(template.cell, cells.with_iri())
world.map(template.person, people.with_iri("location"))
world.map(template.links("neighbor"), moore_neighbors(cells).with_iri("neighbor"))

# the model: a list of behaviours, run in order every tick
RULES = [person.occupancy, person.happiness, person.cell_rank, person.person_rank, person.relocate]
run = pl.concat(RDFSimulator(RULES, n_periods=10, random_seed=0).fit_iter(world))

run is every agent after every tick, one polars DataFrame. What you measure is a polars expression, written after the fact: here, how alike people's neighbours are, which rises as they sort themselves out.

run.filter(pl.col("class") == "Person").group_by("t", maintain_order=True).agg(
    alike=pl.col("share_similar").mean()
)  # about 0.52 after the first tick, 0.83 by the tenth

Behaviours, with the values of their source

A behaviour is a rule for one type of agent (firm, household, person, trader, commuter, ...), named for what it does and carrying the values of the paper that introduced it (skabm/behaviour). Override one by name:

from skabm.behaviour import defaults, person

person.PARAMETERS[person.want_similar]   # 0.375: 3 of 8 neighbours, Schelling (1971)
assert defaults()["want_similar"] == 0.375
# RDFSimulator(RULES, params={"want_similar": 0.5}) asks for more like neighbours

Each published model is a list of behaviours, checked against its paper in tests/validation and shown on its page in notebooks/.

Your own rule, on the graph and in JAX

A rule is a dict of SymPy expressions, one per field it updates, and the same dict runs on the knowledge graph and, differentiably, in JAX:

import jax
import polars as pl
import sympy as sp

from skabm.dsl import Agents, arrays, jax_tick

Firm, inflation = Agents("Firm"), sp.Symbol("inflation")
reprice = {Firm.price: Firm.price * (1 + inflation)}   # every firm's price, each tick

firms = pl.DataFrame({"id": ["firm_0", "firm_1"], "price": [1.0, 1.1]})
tick = jax_tick([reprice])

def price_level(inflation):
    state = arrays({"Firm": firms}, [reprice])
    for _ in range(4):
        state = tick(state, {"inflation": inflation})
    return state["Firm"]["price"].mean()

jax.grad(price_level)(0.02)   # 4 × 1.05 × 1.02³, the exact derivative

RDFSimulator([reprice], params={"inflation": 0.02}) runs it on the graph. The skabm-core skill (plugin/skills/skabm-core) takes a coding agent from a paper's model section to such rules, cited and tested: CANVAS's (Hommes et al. 2025) came out of it.

Where to look next
notebooks/poledna Poledna et al. (2023)'s Austrian economy, its population calibrated from Eurostat, in five chapters: 0 calibration, 1 the quarter plus an intervention, 2 a banking layer whose distress spreads a step per quarter, 3 learned expectations as running sums on the signal nodes, 4 black-it parameter calibration read as an identification test
notebooks/extraction what to do with derived state: Gini as one polars expression, an intervention that moves it, telemetry into JAX

Architecture

Package What it is
skabm the engine: templates, the rule DSL and its SPARQL and JAX compilers, the simulator, what gets measured, rules from a description
skabm.behaviour the agent behaviours, one module per type of agent, each rule named for its mechanism
skabm.space neighbourhoods on a lattice or among points, and the areas a link falls in
skabm.calibration populations fitted to public data, and parameters fitted to macro series with black-it

Limitations

The backend is pure SPARQL, deliberately: a stress test of how far a declarative rule engine carries an economic ABM. Its walls, in short: details.

  • No random sequential matching: markets clear simultaneously. Matching stated in closed form (urn-ball, one-to-one assignment) works.
  • The past is state: a rule sees one time slice, so history is kept as lags and running sums.
  • Synchronous, staged activation: all agents update at once, rules fire in order.

Roadmap

The rest in JAX the relational operators (total_by, running_sum, pick) have no JAX form yet, so traffic and Schelling run on the graph only
Rule-scoped validation a rule already names the fields it reads and writes; check them against the graph at fit time and emit SHACL shapes
Sensitivity-ranked observables rank the series by paired-seed shocks, to order the panels and detect divergence during calibration
An MCP server run a simulation on remote compute, or serve an environment (a grid, a street map), for a client that cannot run skabm itself

References

  • Poledna, Miess, Hommes & Rabitsch (2023). Economic forecasting with an agent-based model. European Economic Review 151, 104306 · del Rio-Chanona, Mealy, Beguerisse-Díaz, Lafond & Farmer (2021). Occupational mobility and automation. J. R. Soc. Interface 18(174), 20200898 · Hommes, He, Poledna, Siqueira & Zhang (2025). CANVAS: a Canadian behavioral agent-based model for monetary policy. J. Econ. Dyn. Control 172, 104986.
  • Hommes & Zhu (2014). Behavioral learning equilibria. JET 150 · Huberman & Glance (1993). Evolutionary games and computer simulations. PNAS 90(16) · Deville & Särndal (1992). Calibration estimators in survey sampling. JASA 87(418) · Fagiolo, Guerini, Lamperti, Moneta & Roventini (2019). Validation of ABMs. In Computer Simulation Validation.
  • black-it (ABM parameter calibration) · maplib (knowledge graphs, OTTR, SPARQL) · AMBER (polars-based ABM).

Metadata

Release files for skabm 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for skabm 0.1.0
File Size Uploaded Provenance
skabm-0.1.0.tar.gz 608.2 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for skabm 0.1.0
File Interpreter ABI Platform
skabm-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 690.0 kB

Release files / skabm-0.1.0.tar.gz

Download URL skabm-0.1.0.tar.gz
Size 608.2 kB
Tags Source
SHA-256 checksum
How to use checksums
e7c9add13351c784fa847a167c62b672b24370b99f8a45b788624e79226bc2b9
BLAKE2b-256 checksum
How to use checksums
6d19ccb11a951174e6dc9b04f71b37767f0959729c294129fe7816429c82f086
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.

Transparency log

Release files / skabm-0.1.0-py3-none-any.whl

Download URL skabm-0.1.0-py3-none-any.whl
Size 81.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0e0e970cf00cec175e891536a15ec1ad6d195fed99f6a5694be4807c0b1d3e6d
BLAKE2b-256 checksum
How to use checksums
b26db98d85f7aa15c59f683370090f2a349744ac5525d682268133dffc2fbcbf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.

Transparency log

Security for skabm 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for skabm-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page