Starting a project#
modelly new writes a model project that is seeded, tested and linted from its first commit:
the parameters and their seed in one place, a test that the same seed gives the same data, and
lint settings that catch reproducibility bugs and O(N²) loops as you write them.
modelly new forest-fire # the grid template, in ./forest-fire
modelly new epidemic --template network # another template
modelly new --list-templates # what each template models
python -m modelly new forest-fire # the same, without the modelly script on PATH
The commands are the same on Windows, macOS and Linux. The folder must not exist yet, or be
empty: modelly new never overwrites a file, and exits with status 2 without writing anything
when the folder has something in it.
What you get#
forest-fire/
├── pyproject.toml depends on modelly-abm; [tool.modelly.lint] and pytest settings
├── README.md how to set up, run, test, lint and profile; an ODD outline
├── .gitignore
├── forest_fire/
│ ├── __init__.py exports ForestFireModel, ForestFireScenario and SEED
│ ├── scenario.py the parameters, their defaults, and the default seed
│ ├── agents.py the agent class
│ └── model.py the model: builds the world from a scenario, steps it
└── tests/
└── test_determinism.py same seed, same data; another seed, another run
The project name becomes the package name (forest-fire gives forest_fire) and the class
names (ForestFireModel, ForestFireScenario). The project contains no shell scripts: every
command in its README is python -m ..., which runs the same everywhere.
Template |
The model |
Uses |
|---|---|---|
|
traders on a torus grid give wealth to cellmates; inequality (Gini) emerges |
|
|
an infection spreads over a random contact network (SIR) |
|
|
correlated random walkers in a wrapping space; their alignment is recorded |
|
|
colonies whose individuals are counted by age class, not one object each |
|
The seed#
scenario.py holds every parameter of a run and the default seed:
SEED = 42 # the default seed: pass rng=... for another, reproducible, run
class ForestFireScenario(Scenario):
n: int = 100
width: int = 10
height: int = 10
def __init__(self, *, rng=None, **kwargs):
super().__init__(rng=SEED if rng is None else rng, **kwargs)
ForestFireModel() runs the default scenario with SEED, so even the first run reproduces.
ForestFireModel(ForestFireScenario(rng=7, n=500)) changes the seed and a parameter; every
draw in the model comes from self.random and self.rng, which the scenario seeds.
Set up, test, lint, profile#
cd forest-fire
python -m venv .venv
source .venv/bin/activate # Windows PowerShell: .venv\Scripts\Activate.ps1
python -m pip install -e ".[dev]"
python -m pytest # the determinism test
python -m modelly lint forest_fire # no findings in a fresh project
python -m modelly profile forest_fire.model:ForestFireModel --steps 50
The determinism test runs the model twice from one seed and compares everything its
DataCollector recorded; it also checks that another seed gives another run, so a seed that is
accidentally ignored fails the test. Keep it passing as the model grows: it is the cheapest
guard against a draw from the global random module or a loop over a set.
[tool.modelly.lint] in pyproject.toml selects every rule and relaxes the O(N²) warnings in
tests/; see Checking a model for the rules, --fix and # noqa. The profiler is
described in Profiling a model.
The README and the ODD protocol#
The generated README ends with the outline of the ODD protocol (Overview, Design concepts, Details), the usual way to describe an agent-based model in a paper: purpose and patterns; entities, state variables and scales; process overview and scheduling; design concepts; initialization; input data; submodels. Fill it in as the model takes shape, and it becomes the model description of the paper.
Long project names#
The templates are formatted for names of ordinary length. With a long project name some lines
of the generated code pass the usual 88 characters; run your formatter once (ruff format .)
after creating the project.