A universal orchestrator for dynamic modular simulations.
Project description
๐ Moirai
A universal orchestrator for dynamic, modular simulations.
Moirai is a lightweight, extensible Python framework for orchestrating heterogeneous simulations. A central Simulation runtime manages pluggable Module components โ each responsible for its own domain logic.
Use it for: agent-based models, ECS systems, weather simulation, economic models, epidemiology, social dynamics, city simulators, power grids, supply chains, and any other dynamic system you can imagine.
โจ Philosophy
Simulation โ universal runtime
Module โ pluggable domain logic
EventBus โ loose coupling between modules
Metrics โ observable simulation state
Resources โ shared data between modules
Clock โ real-world time mapping
Scheduler โ deferred & recurring callbacks
Snapshot โ serialize state to JSON
๐ Quick Start
Installation
pip install moirai-sim
Or from source:
git clone https://github.com/yourname/moirai
cd moirai
pip install -e ".[dev]"
Basic Usage
from moirai import Simulation
from moirai.clock import Clock
from moirai.examples.weather import WeatherModule
from moirai.examples.population import PopulationModule
sim = Simulation(seed=42, clock=Clock(time_step=1.0, unit="day"))
sim.attach(WeatherModule(initial_temp=15.0))
sim.attach(PopulationModule(initial_mood=100.0))
sim.run(365)
print(sim.summary())
๐งฉ Writing a Module
from moirai.module import Module
class EconomyModule(Module):
phase = "economy" # execution phase (lexicographic order)
priority = 0 # within-phase ordering (lower = earlier)
enabled = True # set False to skip update() each tick
def setup(self, sim) -> None:
sim.resources.set("gdp", 1_000_000.0)
def update(self, sim) -> None:
growth = sim.rng.uniform(0.98, 1.03)
gdp = sim.resources.get("gdp")
sim.resources.set("gdp", gdp * growth)
sim.metrics.track("gdp", sim.resources.get("gdp"))
sim.eventbus.emit("gdp_updated", value=sim.resources.get("gdp"))
def teardown(self, sim) -> None:
pass
๐ Event Bus
# Standard subscription
sim.eventbus.subscribe("gdp_updated", lambda value, **_: print(f"GDP: {value:,.0f}"))
# One-shot (auto-unsubscribes after first call)
sim.eventbus.once("run_start", lambda **_: print("Simulation started!"))
# Wildcard โ receives every event
sim.eventbus.subscribe("*", lambda **kw: log.debug("event received"))
# Decorator shorthand on the simulation
@sim.on("temperature_changed")
def log_temp(value, **_):
print(f"[{sim.clock.format_time(sim.ticks)}] Temp: {value:.1f} ยฐC")
โฑ Clock
from datetime import datetime
from moirai.clock import Clock
# 1 tick = 1 day, anchored to a calendar date
clock = Clock(time_step=1.0, unit="day", start_date=datetime(2025, 1, 1))
sim = Simulation(seed=42, clock=clock)
sim.run(90)
print(sim.clock.format_time(sim.ticks)) # "2025-04-01"
print(sim.time) # 90.0
print(clock.ticks_for(365)) # 365
๐ Scheduler
# One-shot callback at tick 10
sim.scheduler.schedule_at(10, lambda: print("Tick 10!"))
# Recurring every 7 ticks (weekly event)
handle = sim.scheduler.schedule_every(7, lambda: print("Weekly checkpoint"))
# Cancel
sim.scheduler.cancel(handle)
๐ Metrics
# Track values
sim.metrics.track("population", 5_000)
# Query
print(sim.metrics.history("population")) # full history
print(sim.metrics.latest("population")) # most recent value
# Statistical summary
s = sim.metrics.summary("population")
print(s.mean, s.std, s.min, s.max, s.count)
# Export to JSON
sim.metrics.export_json("results/metrics.json")
# pandas-compatible dict
import pandas as pd
df = pd.DataFrame(sim.metrics.to_frame())
๐ชฃ Resources
# Set, get, update
sim.resources.set("gdp", 1_000_000.0)
sim.resources.update({"inflation": 0.02, "unemployment": 0.05})
# Type-safe get โ raises ResourceTypeError on mismatch
gdp = sim.resources.get_typed("gdp", float)
# set-if-absent
sim.resources.setdefault("mortality_rate", 0.01)
โธ Pause & Resume
class CheckpointModule(Module):
phase = "control"
def setup(self, sim): pass
def teardown(self, sim): pass
def update(self, sim):
if sim.ticks == 100:
sim.pause() # stops the loop after this tick
sim.attach(CheckpointModule())
ticks_run = sim.run(365) # returns early at tick 100
print(sim.state) # SimulationState.PAUSED
sim.run(265) # resume for remaining ticks
๐ธ Snapshots
sim.run(100)
# Save
snap = sim.snapshot()
snap.save("checkpoint.json")
# Load & inspect
from moirai.snapshot import Snapshot
loaded = Snapshot.load("checkpoint.json")
print(loaded.tick) # 100
print(loaded.resources) # {"temperature": ...}
print(loaded.summary())
๐ฌ Built-in Examples
| Module | Domain | Key resources |
|---|---|---|
WeatherModule |
Climate | temperature |
PopulationModule |
Social | population_mood |
EpidemicModule |
Epidemiology (SIR) | sir_S, sir_I, sir_R |
EconomyModule |
Macroeconomics | gdp, inflation, unemployment |
from moirai.examples.epidemic import EpidemicModule
sim = Simulation(seed=0)
sim.eventbus.subscribe("epidemic_ended", lambda tick, **_: print(f"Ended at tick {tick}"))
sim.attach(EpidemicModule(population=100_000, initial_infected=10, beta=0.3, gamma=0.1))
sim.run(500)
๐ Project Structure
moirai/
โโโ pyproject.toml
โโโ README.md
โโโ CHANGELOG.md
โโโ src/
โ โโโ moirai/
โ โโโ __init__.py # Public API
โ โโโ simulation.py # Orchestrator + SimulationState
โ โโโ module.py # Abstract Module base
โ โโโ eventbus.py # Pub/sub (once, wildcard)
โ โโโ metrics.py # Time-series + MetricSummary
โ โโโ resources.py # Typed resource container
โ โโโ clock.py # Time-unit mapping
โ โโโ scheduler.py # Deferred/recurring callbacks
โ โโโ snapshot.py # JSON checkpoint
โ โโโ exceptions.py # Exception hierarchy
โ โโโ py.typed # PEP 561
โ โโโ examples/
โ โโโ weather.py
โ โโโ population.py
โ โโโ epidemic.py # SIR model
โ โโโ economy.py # GDP / inflation / unemployment
โโโ tests/
โโโ conftest.py
โโโ test_simulation.py # 68 tests
โโโ test_modules.py # 85 tests
โโโ test_clock.py # 17 tests
โโโ test_scheduler.py # 20 tests
โโโ test_snapshot.py # 15 tests
๐งช Running Tests
pytest
pytest --cov=moirai --cov-report=term-missing
๐ License
MIT โ see LICENSE for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file moirai_sim-0.3.0.tar.gz.
File metadata
- Download URL: moirai_sim-0.3.0.tar.gz
- Upload date:
- Size: 41.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6eab16b182293a11d4d34873531d42d728e1c36c554bc2787d2b7c94e1993962
|
|
| MD5 |
95c8094a1709927902b5828a5f40dfe0
|
|
| BLAKE2b-256 |
b5330928f0577cb249a66cda60cfe7a3dd1da54e75345b92d1aa59a246c2e2b8
|
File details
Details for the file moirai_sim-0.3.0-py3-none-any.whl.
File metadata
- Download URL: moirai_sim-0.3.0-py3-none-any.whl
- Upload date:
- Size: 39.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c181d3b0f2bfe6841557939f2c2b02f4321c6bcc59671f5551ae0d29ee502012
|
|
| MD5 |
cfbeb6ed577a8b9c152023f4ad71901f
|
|
| BLAKE2b-256 |
dbc8c6a07aeeeeb4cb173cd5c847ab00d5427db5e02d0fb6df7da23d41c45fa5
|