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.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
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 macrocosim-0.1.0.tar.gz.
File metadata
- Download URL: macrocosim-0.1.0.tar.gz
- Upload date:
- Size: 2.5 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1fa059174e18a1a57fd6f4fafe5c61717255a29184081afea2a63e6dc0cf681f
|
|
| MD5 |
d436e0f086481d71f0df7ae5f222e3c1
|
|
| BLAKE2b-256 |
aa163d093ea3abb7378763e311b406a86c629a2b1adad24636c3c99e151eb59a
|
Provenance
The following attestation bundles were made for macrocosim-0.1.0.tar.gz:
Publisher:
release.yml on shsms/macrocosim
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
macrocosim-0.1.0.tar.gz -
Subject digest:
1fa059174e18a1a57fd6f4fafe5c61717255a29184081afea2a63e6dc0cf681f - Sigstore transparency entry: 2701010837
- Sigstore integration time:
-
Permalink:
shsms/macrocosim@4e175fd803a1b966711f02aa9414c73f186e5050 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/shsms
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4e175fd803a1b966711f02aa9414c73f186e5050 -
Trigger Event:
push
-
Statement type:
File details
Details for the file macrocosim-0.1.0-py3-none-win_amd64.whl.
File metadata
- Download URL: macrocosim-0.1.0-py3-none-win_amd64.whl
- Upload date:
- Size: 9.7 MB
- Tags: Python 3, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c987e11687737e8b220321948a13f7509498937dd689bfdfa1307f51527a848b
|
|
| MD5 |
b2fdec20d4b22f5d99623898958c326e
|
|
| BLAKE2b-256 |
2a6fce0ed657fbc9d13f2797e9508d0a01fdd559a5def53bac6ddbb3899caf25
|
Provenance
The following attestation bundles were made for macrocosim-0.1.0-py3-none-win_amd64.whl:
Publisher:
release.yml on shsms/macrocosim
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
macrocosim-0.1.0-py3-none-win_amd64.whl -
Subject digest:
c987e11687737e8b220321948a13f7509498937dd689bfdfa1307f51527a848b - Sigstore transparency entry: 2701010945
- Sigstore integration time:
-
Permalink:
shsms/macrocosim@4e175fd803a1b966711f02aa9414c73f186e5050 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/shsms
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4e175fd803a1b966711f02aa9414c73f186e5050 -
Trigger Event:
push
-
Statement type:
File details
Details for the file macrocosim-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: macrocosim-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 10.9 MB
- Tags: Python 3, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5ff38ca63bac9a40d95bbef938b769dc275ade52e784c5757772a4bb3001e5c6
|
|
| MD5 |
130b64f7fdc4dd0e194bfe4dcf940ff7
|
|
| BLAKE2b-256 |
25c4c3e14c112da2b8776506226eca619e6167a3e91dccf49eced9c05321407c
|
Provenance
The following attestation bundles were made for macrocosim-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
release.yml on shsms/macrocosim
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
macrocosim-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
5ff38ca63bac9a40d95bbef938b769dc275ade52e784c5757772a4bb3001e5c6 - Sigstore transparency entry: 2701010895
- Sigstore integration time:
-
Permalink:
shsms/macrocosim@4e175fd803a1b966711f02aa9414c73f186e5050 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/shsms
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4e175fd803a1b966711f02aa9414c73f186e5050 -
Trigger Event:
push
-
Statement type:
File details
Details for the file macrocosim-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: macrocosim-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 11.0 MB
- Tags: Python 3, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b0f42af0271b611b9498a3baf5c8aeb4e12f28a46d0dd51fe6c6e5b99a44d3fc
|
|
| MD5 |
34e35cdc286367c13438e8ace0ef322a
|
|
| BLAKE2b-256 |
c33da0e97d0a1cb3c917e8cdec88526c88af5551a31ca87071d608a1cdf1107c
|
Provenance
The following attestation bundles were made for macrocosim-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:
Publisher:
release.yml on shsms/macrocosim
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
macrocosim-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
b0f42af0271b611b9498a3baf5c8aeb4e12f28a46d0dd51fe6c6e5b99a44d3fc - Sigstore transparency entry: 2701010924
- Sigstore integration time:
-
Permalink:
shsms/macrocosim@4e175fd803a1b966711f02aa9414c73f186e5050 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/shsms
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4e175fd803a1b966711f02aa9414c73f186e5050 -
Trigger Event:
push
-
Statement type:
File details
Details for the file macrocosim-0.1.0-py3-none-macosx_11_0_arm64.whl.
File metadata
- Download URL: macrocosim-0.1.0-py3-none-macosx_11_0_arm64.whl
- Upload date:
- Size: 10.2 MB
- Tags: Python 3, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
39ea521c541831e977bfec040520906038aa060a3d8bf21b06b2f4f5c69d6c70
|
|
| MD5 |
29b74ef6f515c8d6c7718af8eb962cd2
|
|
| BLAKE2b-256 |
a5c7c9b49df57bea742d8f6f3da74f4a73cd73d78861f0db8865411ef61b511e
|
Provenance
The following attestation bundles were made for macrocosim-0.1.0-py3-none-macosx_11_0_arm64.whl:
Publisher:
release.yml on shsms/macrocosim
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
macrocosim-0.1.0-py3-none-macosx_11_0_arm64.whl -
Subject digest:
39ea521c541831e977bfec040520906038aa060a3d8bf21b06b2f4f5c69d6c70 - Sigstore transparency entry: 2701010971
- Sigstore integration time:
-
Permalink:
shsms/macrocosim@4e175fd803a1b966711f02aa9414c73f186e5050 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/shsms
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4e175fd803a1b966711f02aa9414c73f186e5050 -
Trigger Event:
push
-
Statement type:
File details
Details for the file macrocosim-0.1.0-py3-none-macosx_10_12_x86_64.whl.
File metadata
- Download URL: macrocosim-0.1.0-py3-none-macosx_10_12_x86_64.whl
- Upload date:
- Size: 10.6 MB
- Tags: Python 3, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eed0f92ebb10dba2f9f1f8d5cb4ee518183781022d8b09301f08f178e3830310
|
|
| MD5 |
7305a0cf225e5018c55802cbf1b366db
|
|
| BLAKE2b-256 |
41fce083fb79391073d56b5421f7bdd37e723abd5926a6d596e953671dafe376
|
Provenance
The following attestation bundles were made for macrocosim-0.1.0-py3-none-macosx_10_12_x86_64.whl:
Publisher:
release.yml on shsms/macrocosim
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
macrocosim-0.1.0-py3-none-macosx_10_12_x86_64.whl -
Subject digest:
eed0f92ebb10dba2f9f1f8d5cb4ee518183781022d8b09301f08f178e3830310 - Sigstore transparency entry: 2701010870
- Sigstore integration time:
-
Permalink:
shsms/macrocosim@4e175fd803a1b966711f02aa9414c73f186e5050 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/shsms
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4e175fd803a1b966711f02aa9414c73f186e5050 -
Trigger Event:
push
-
Statement type: