macrocosim (Python)
Python integration-testing client for the macrocosim microgrid simulator.
Build a topology, launch the simulator, drive its environment, inject
faults, and assert on the resulting grid state — from inside pytest,
talking to it the same way a downstream control app would (the gRPC
Microgrid API + the HTTP /api control plane).
from datetime import timedelta
from frequenz.quantities import Energy, Percentage, Power
import macrocosim as mc
async def test_limiter_holds_import_cap():
bat = mc.battery(id=4, capacity=Energy.from_kilowatt_hours(92),
initial_soc=Percentage.from_percent(50))
inv = mc.battery_inverter(id=3, successors=[bat],
rated=(Power.from_kilowatts(-5), Power.from_kilowatts(5)))
mg = mc.Microgrid(id=1,
topology=mc.grid(id=1, successors=[mc.meter(id=2, successors=[inv])]))
with mc.launch(mg) as site:
site.component(inv).status(health=mc.Health.ERROR) # fault injection
await site.component(inv).expect.active_power(
approx=Power.from_watts(0), tol=Power.from_watts(100))
await site.component(bat).expect.soc(
within=(Percentage.from_percent(45), Percentage.from_percent(55)))
Install
Released as a platform wheel that bundles the macrocosim + macroctl
binaries (built from the Rust crate via maturin), so a downstream install
just works — no separate binary to fetch:
pip install 'macrocosim[grpc]' # or: uv add 'macrocosim[grpc]'
launch() finds the bundled binaries automatically (in the interpreter's
scripts directory, even off PATH). Override with MACROCOSIM_BIN /
MACROCTL_BIN, or bin= on launch, to point at a local build.
Development
This package is uv-native; the build backend
is maturin, so uv sync compiles the binaries from the repo's Rust crate
(needs the Rust toolchain + git submodule update --init).
uv sync # venv from uv.lock; builds the binaries
uv run pytest # tests; ruff / ty also via `uv run`
Point tests at a fast local cargo build instead of the wheel's binary:
MACROCOSIM_BIN=../target/debug/macrocosim uv run pytest
The surface
Build a topology — the spec is the graph (nested successors),
rendering 1:1 to macrocosim's (make-*) Lisp:
from frequenz.quantities import Energy, Percentage, Power
P = Power.from_kilowatts
mg = mc.Microgrid(id=1, topology=mc.grid(id=1, successors=[
mc.meter(id=2, successors=[
mc.battery_inverter(id=3, rated=(P(-5), P(5)), successors=[
mc.battery(id=4, capacity=Energy.from_kilowatt_hours(100),
initial_soc=Percentage.from_percent(50))]),
mc.solar_inverter(id=5, sunlight=Percentage.from_percent(80)),
mc.meter(id=6, power=Power.from_watts(1000))])]))
Constructors: grid, meter, battery_inverter, solar_inverter,
battery, ev_charger, chp, steam_boiler. Kwargs mirror the plist keys
(snake_case → :kebab-case): rated=(lo, hi) Power bounds, capacity
an Energy, initial_soc / sunlight a Percentage; mc.raw("(lambda () …)")
splices Lisp.
Typed throughout — no bare numbers or unit strings. Knobs take enums
(mc.Health, mc.CommandMode, mc.TelemetryMode, scenario mc.Metric,
mc.Schedule); quantities are
frequenz-quantities
(Power, Energy, Percentage, Frequency), imported from it directly;
times are datetime (timedelta, or a datetime.time for an absolute scenario clock).
mc.to_lisp_atom(v) shows the Lisp literal any of them emits.
Or use an existing config: mc.launch("topology.lisp").
Launch and get a Site (a context manager; tears the process down):
with mc.launch(mg) as site:
site.grpc # first microgrid's gRPC address ("host:port")
site.grpc_url # ... as a "grpc://host:port" URL a client connects with
site.eval("(...)") # raw Lisp escape hatch
Read — component telemetry over gRPC (what the app sees), aggregates over the graph-derived formulas:
site.active_power(3) # Power | None (component, gRPC)
site.soc(4) # Percentage | None (battery SoC, gRPC)
site.grid_power() # Power | None (/ pv_power() / consumer_power() / …)
Energy — the simulator integrates each power aggregate into a cumulative energy stream (server-side), so you can judge what an app did over a run:
site.grid_energy() # Energy | None (cumulative, import positive)
site.battery_energy() # Energy | None (/ consumer_energy() / pv_energy())
Assert on them through expect (a one-shot check — energy accumulates, so it
isn't a settling value to poll):
await site.expect.grid_energy(max=Energy.from_kilowatt_hours(15)) # held import down
await site.expect.battery_energy(approx=Energy.from_kilowatt_hours(-8),
tol=Energy.from_kilowatt_hours(1)) # discharge total
Per-component energy is a first-class metric too, assertable from Lisp and the
scenario framework: (check "15m" :component 2 :metric 'energy :max 15000.0) or
metric=mc.Metric.ENERGY on a Python Scenario.check.
Mutate — reach a component with site[id] (or site.component(id)), then
act by intent:
inv = site[3]
inv.command(active_power=Power.from_kilowatts(2), # app command (gRPC gateway)
lifetime=timedelta(seconds=30))
inv.command(bounds=(Power.from_kilowatts(-1),
Power.from_kilowatts(1))) # narrow the envelope
inv.status(health=mc.Health.ERROR) # inject a fault
site[6].drive(power=Power.from_megawatts(2)) # drive the environment
site[5].drive(sunlight=Percentage.from_percent(30))
command goes through the real gRPC gateway, so an out-of-envelope value
raises mc.SetpointRejected (the production behaviour under test); status /
drive are test-side stimuli.
Assert — settle-aware expect, on a component (site[id].expect) or a
microgrid aggregate (site.expect). The settle-aware assertions are async
(they await between polls, so an app under test on the same event loop keeps
running); the cumulative-energy ones are one-shot but async too, for a
uniform surface:
await site[3].expect.active_power(
approx=Power.from_kilowatts(2), tol=Power.from_watts(300),
timeout=timedelta(seconds=15))
await site.expect.grid_power(
max=Power.from_megawatts(1), for_=timedelta(seconds=30))
await site[4].expect.soc(
within=(Percentage.from_percent(45), Percentage.from_percent(55)))
await expect.<metric>(…) polls until the matcher holds; pass for_= to
require it on every sample across a duration instead. Matchers: approx+tol,
within, max, min.
Async core (v2): signals — macrocosim.aio is the async-native
core: every read, write, and wait is a coroutine on your event loop (no
background threads). Its unit is the signal: once aio.launch binds
the topology, the builder objects are the live handles, and every
observable quantity is an object with up to three verbs — read
(returns the plain quantity; raises NoSample), expect (takes one
typed matcher), and set where the simulator allows it. Capability
lives in the type:
load = mc.meter(id=5, power=Power.zero())
bat = mc.battery(id=4, capacity=Energy.from_kilowatt_hours(100),
initial_soc=Percentage.from_percent(60))
async with mc.aio.launch(mg) as site:
await load.power.set(Power.from_kilowatts(20)) # drive the world
await site.grid_power.expect(mc.at_most(Power.from_kilowatts(13)))
await bat.soc.set(Percentage.from_percent(11)) # teleport state
await bat.soc.expect(mc.between(Percentage.from_percent(10),
Percentage.from_percent(12)))
stored = await bat.stored_energy.read() # state (SoC×capacity)
await site.battery_energy.expect( # flow (∫ battery_power)
mc.at_most(Energy.from_watt_hours(-1)))
await inv.health.set(mc.Health.ERROR) # fault injection
Matchers: near(x, tol=…), between(lo, hi), at_most(x),
at_least(x) — one per expect, typed by the signal's quantity. An
inverter's power has no .set (commanding it is site[inv].command()
through the real gateway), and a cumulative signal's expect has no
hold_for — the distinctions are in the types, not runtime errors.
Every *_energy site aggregate is the integral of its *_power; energy
stored in a battery is bat.stored_energy. Stimuli go over typed JSON
control endpoints; rejections raise ControlRejected. See
../docs/python-api-redesign.org for the design.
Scenarios — author in Python, or run a registered Lisp scenario:
scn = mc.Scenario("cloud-fade", length=timedelta(minutes=4))
scn.at(timedelta(seconds=30), pv.sunlight, Percentage.from_percent(20))
scn.check(timedelta(seconds=110), inv.power,
mc.near(Power.from_megawatts(1.5), tol=Power.from_kilowatts(300)))
site.define_scenario(scn).run(wait=True).assert_passed()
# deterministic, serverless gate (no app under test):
mc.run_scenario_stepped([mg, scn], "cloud-fade")
pytest — the plugin auto-loads; provide a macrocosim_config fixture:
@pytest.fixture
def macrocosim_config():
return mg
async def test_grid_holds(macrocosim):
await macrocosim.expect.grid_power(
approx=Power.from_kilowatts(7), tol=Power.from_watts(500))
@pytest.mark.macrocosim_scenario("cloud-fade") # runs + gates after the test
def test_scenario(macrocosim): ...
The expect assertions are async, so awaiting them needs pytest-asyncio
(installed with the grpc extra) and asyncio_mode = "auto" in your pytest
config — otherwise an async def test is collected but never awaited, and the
assertion silently never runs.
Status
Early but functional end to end — see todo.org §Y in the macrocosim repo
for the design and roadmap. Building, launching, reading and mutating are
synchronous; the settle-aware expect assertions are async (so they compose
with an app under test on the same event loop). Runnable examples/ cover each
piece; examples/pytest_demo/ is a live suite.
License
The Python package is MIT. The wheel also bundles the
macrocosim and macroctl binaries, which are
GPL-3.0-only; their source is the
macrocosim repository at the
release's git tag: v0.1.0 for wheel version 0.1.0, and for a pre-release
the semver form of the PEP 440 version, so v0.1.0-alpha.1 for 0.1.0a1.
Metadata
Release files for macrocosim 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| macrocosim-0.1.0.tar.gz | 2.5 MB | With provenance | Details for macrocosim-0.1.0.tar.gz |
Built distributions (wheels)
| File | Actions Reset | |||
|---|---|---|---|---|
| macrocosim-0.1.0-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details for macrocosim-0.1.0-py3-none-win_amd64.whl |
| macrocosim-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details for macrocosim-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
| macrocosim-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | Python 3 | none | Linux glibc 2.17+ ARM64 | Details for macrocosim-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
| macrocosim-0.1.0-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details for macrocosim-0.1.0-py3-none-macosx_11_0_arm64.whl |
| macrocosim-0.1.0-py3-none-macosx_10_12_x86_64.whl | Python 3 | none | macOS 10.12+ x86-64 | Details for macrocosim-0.1.0-py3-none-macosx_10_12_x86_64.whl |
Total release size: 54.9 MB
Release files / macrocosim-0.1.0.tar.gz
| Download URL | macrocosim-0.1.0.tar.gz |
|---|---|
| Size | 2.5 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1fa059174e18a1a57fd6f4fafe5c61717255a29184081afea2a63e6dc0cf681f
|
|
BLAKE2b-256 checksum How to use checksums |
aa163d093ea3abb7378763e311b406a86c629a2b1adad24636c3c99e151eb59a
|
| 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 Sep 3, 2026.
Transparency logRelease files / macrocosim-0.1.0-py3-none-win_amd64.whl
| Download URL | macrocosim-0.1.0-py3-none-win_amd64.whl |
|---|---|
| Size | 9.7 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
c987e11687737e8b220321948a13f7509498937dd689bfdfa1307f51527a848b
|
|
BLAKE2b-256 checksum How to use checksums |
2a6fce0ed657fbc9d13f2797e9508d0a01fdd559a5def53bac6ddbb3899caf25
|
| 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 Sep 3, 2026.
Transparency logRelease files / macrocosim-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | macrocosim-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 10.9 MB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
5ff38ca63bac9a40d95bbef938b769dc275ade52e784c5757772a4bb3001e5c6
|
|
BLAKE2b-256 checksum How to use checksums |
25c4c3e14c112da2b8776506226eca619e6167a3e91dccf49eced9c05321407c
|
| 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 Sep 3, 2026.
Transparency logRelease files / macrocosim-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | macrocosim-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 11.0 MB |
| Tags | Linux glibc 2.17+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
b0f42af0271b611b9498a3baf5c8aeb4e12f28a46d0dd51fe6c6e5b99a44d3fc
|
|
BLAKE2b-256 checksum How to use checksums |
c33da0e97d0a1cb3c917e8cdec88526c88af5551a31ca87071d608a1cdf1107c
|
| 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 Sep 3, 2026.
Transparency logRelease files / macrocosim-0.1.0-py3-none-macosx_11_0_arm64.whl
| Download URL | macrocosim-0.1.0-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 10.2 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
39ea521c541831e977bfec040520906038aa060a3d8bf21b06b2f4f5c69d6c70
|
|
BLAKE2b-256 checksum How to use checksums |
a5c7c9b49df57bea742d8f6f3da74f4a73cd73d78861f0db8865411ef61b511e
|
| 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 Sep 3, 2026.
Transparency logRelease files / macrocosim-0.1.0-py3-none-macosx_10_12_x86_64.whl
| Download URL | macrocosim-0.1.0-py3-none-macosx_10_12_x86_64.whl |
|---|---|
| Size | 10.6 MB |
| Tags | Python 3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
eed0f92ebb10dba2f9f1f8d5cb4ee518183781022d8b09301f08f178e3830310
|
|
BLAKE2b-256 checksum How to use checksums |
41fce083fb79391073d56b5421f7bdd37e723abd5926a6d596e953671dafe376
|
| 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 Sep 3, 2026.
Transparency logSecurity for macrocosim 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 6 files in this release have been uploaded with provenance metadata.
Attestation for macrocosim-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 Sep 3, 2026.
Transparency logA 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.