Running Simulations
This page is the operational reference for installing the simulator, creating environments, running episodes, using the CLI and understanding the main execution parameters.
Installation
Case |
Command |
Notes |
|---|---|---|
Standard install |
|
The Python import path is |
Parquet datasets |
|
Required when schemas or exports use Parquet. |
CLI and Stable Baselines3 |
|
The current CLI imports this library; use Python 3.11+ for CLI train/evaluate. |
PV autosizing |
|
Required only for EPW/PySAM autosizing. |
Local development |
|
Use the repo |
Python Quickstart
import numpy as np
from citylearn.citylearn import CityLearnEnv
schema = "citylearn_challenge_2022_phase_all_plus_evs"
env = CityLearnEnv(schema, interface="flat", episode_time_steps=24, render_mode="none")
observations, info = env.reset()
terminated = truncated = False
while not (terminated or truncated):
actions = [np.zeros(space.shape, dtype="float32") for space in env.action_space]
observations, reward, terminated, truncated, info = env.step(actions)
kpis_v2 = env.evaluate_v2()
Entity Interface Quickstart
from citylearn.citylearn import CityLearnEnv
env = CityLearnEnv(
"citylearn_three_phase_dynamic_topology_demo",
interface="entity",
topology_mode="dynamic",
)
obs, info = env.reset()
specs = env.entity_specs
actions = {
"tables": {
"building": env.action_space["tables"]["building"].sample(),
"charger": env.action_space["tables"]["charger"].sample(),
"deferrable_appliance": env.action_space["tables"]["deferrable_appliance"].sample(),
}
}
obs, reward, terminated, truncated, info = env.step(actions)
Multi-Community Quickstart
Use MultiCommunityEnv when one training loop should step several independent communities in lockstep. Each child keeps its own physics, KPIs and demand-response files.
from citylearn.multi_community import MultiCommunityEnv
env = MultiCommunityEnv(
communities=[
{
"community_id": "community_a",
"schema": "citylearn_challenge_2022_phase_all_plus_evs",
"env_kwargs": {"interface": "entity", "episode_time_steps": 48},
"weight": 1.0,
},
{
"community_id": "community_b",
"schema": "citylearn_challenge_2022_phase_all",
"env_kwargs": {"interface": "entity", "episode_time_steps": 48},
"weight": 1.0,
},
]
)
observations, info = env.reset(seed=0)
All communities must share seconds_per_time_step, effective episode length, interface and central_agent mode. evaluate_v2() returns local rows with community_id plus portfolio rows with level="portfolio". See multi_community_reference.md.
CityLearnEnv Parameters
Parameter |
Type |
Default |
Purpose |
Notes |
|---|---|---|---|---|
|
|
required |
Dataset name, schema path or preloaded dict. |
Relative paths are resolved from |
|
path |
schema |
Base folder for dataset files. |
Overrides schema value. |
|
list |
schema |
Subset of buildings to load. |
Names or indices. |
|
list |
schema |
Subset of EVs to load. |
Usually from |
|
int |
schema |
First global timestep. |
Inclusive. |
|
int |
schema |
Last global timestep. |
Inclusive. |
|
int/list |
schema |
Episode size or explicit windows. |
Can be used with rolling/random splits. |
|
bool |
schema |
Sequential window episodes. |
Useful for training. |
|
bool |
schema |
Random window episodes. |
Uses |
|
float |
schema |
Physical duration of each step. |
Examples: 15, 60, 300, 900, 3600. |
|
int/float |
inferred |
Ratio between control step and dataset spacing. |
Normally dataset and schema should match. |
|
class/path |
schema |
Reward used by |
Supports |
|
dict |
|
Constructor kwargs for the reward. |
Pass-through. |
|
bool |
schema |
Single controller for all buildings. |
|
|
list |
schema |
Shared observations in central mode. |
Included once in central vectors. |
|
list/list[list] |
schema |
Enable only these observations. |
Global or per-building override. |
|
list/list[list] |
schema/building |
Disable observations. |
Applied after active selection. |
|
list/list[list] |
schema |
Enable only these actions. |
Global or per-building override. |
|
list/list[list] |
schema/building |
Disable actions. |
Applied after active selection. |
|
bool |
schema/building |
Enable outage simulation. |
Uses data series or stochastic model. |
|
bool |
schema |
Compatibility switch for solar generation. |
Kept for original CityLearn compatibility. |
|
int |
schema |
Random seed. |
Affects splits and stochastic attributes. |
|
bool |
|
Disable network fallbacks. |
Requires local datasets. |
|
|
schema/flat |
Observation/action contract. |
Entity returns tables and edges. |
|
|
schema/static |
Enable dynamic topology events. |
Dynamic requires |
|
date/string |
schema/2024-01-01 |
Base date for render/export timestamps. |
Does not change physics. |
|
|
|
CSV export policy. |
|
|
string |
schema/None |
Export session subfolder. |
Must be relative. |
|
bool |
render flag |
Export KPIs at episode end. |
Can be enabled without full render. |
Extra **kwargs
Parameter |
Type |
Default |
Purpose |
|---|---|---|---|
|
path |
internal output |
Base export folder. |
|
string |
|
Legacy export folder name. |
|
bool |
derived |
Legacy render switch. |
|
bool |
schema/False |
Runtime timing logs. |
|
bool |
schema/False |
Validate observations against estimated bounds. |
|
bool |
schema/False |
Run physical invariant checks at runtime. |
|
int |
schema/0 |
Runtime metric log cadence. |
Reward Observation Payloads
step() builds a smaller reward observation payload when the reward function declares which observation names it needs. Built-in rewards already do this. Custom rewards can opt in with one of these compatible forms:
class MyReward:
required_observation_names = ("net_electricity_consumption",)
def calculate(self, observations):
return [-sum(o["net_electricity_consumption"] for o in observations)]
The alias required_observations and method get_required_observation_names() are also supported. If a custom reward does not declare requirements, CityLearn falls back to full include_all observations for backward compatibility.
Macro-Steps / Action Repeat
step_many() repeats one selected action across multiple internal simulator steps and returns one macro transition for RL replay buffers:
obs, rewards, terminated, truncated, info = env.step_many(
action,
repeat_steps=20,
stop_on_done=True,
return_substeps=False,
)
The simulator still advances every internal step at seconds_per_time_step resolution. Constraints, EV charging/departures, batteries, deferrables, phases/headroom, rewards, KPIs and render/export time series are updated exactly as they are for repeated step() calls. The returned observation is only the final observation after the executed substeps, and rewards is the per-agent reward sum.
info["executed_steps"] is always present so RL code can discount macro transitions correctly:
gamma_macro = gamma ** info["executed_steps"]
When return_substeps=True, info also includes substep_rewards, substep_infos and substep_actions_applied for debugging. Keep it disabled in long training runs.
CLI
The current CLI requires Stable Baselines3 even for its listing commands:
install stable-baselines3==2.3.2 alongside CityLearn. Use Python 3.11 or newer
for simulate ... train/evaluate, whose timestamp code uses datetime.UTC.
The Python simulation loop above works on supported earlier Python versions
without Stable Baselines3.
citylearn --version
citylearn list_datasets
citylearn list_default_time_series_variables
citylearn simulate data/datasets/my_dataset/schema.json train -e 3
citylearn simulate data/datasets/my_dataset/schema.json evaluate
Option |
Example |
Purpose |
|---|---|---|
|
dataset name or |
Dataset to run. |
|
|
Agent class path. |
|
|
JSON kwargs for |
|
|
JSON kwargs for the agent. |
|
wrapper class paths |
Gymnasium wrappers. |
|
|
Series stored after evaluation. |
|
|
Output naming ID. |
|
|
Load/save agent path. |
|
|
Output folder. |
|
|
Evaluation window. Can repeat. |
|
flag |
Do not overwrite existing output. |
|
|
Seed. |
|
flag |
Require local files. |
|
|
Number of training episodes. |
|
flag |
Save agent at the end. |
|
flag |
Evaluate after training. |
|
subcommand |
Deterministic evaluation. |
Render and Export
|
Runtime cost |
Output |
Use case |
|---|---|---|---|
|
lowest |
No render CSV |
Training. |
|
high |
Writes rows each step |
Short debugging runs. |
|
medium |
Writes full episode at end |
Long episodes with final CSV output. |
Set render_file_format="parquet" to write render, KPI and BAU time-series exports as chunked parquet part files instead of CSV. render_chunk_size controls the number of rows per parquet part; the default is 50000 for parquet and 100000 for CSV.
KPI and BAU exports are episode-scoped, so training loops can keep export disabled and turn it on only for the final episode. If normal time-series outputs are needed on that final episode, create the environment with render_mode="end" and toggle render_enabled; for KPI-only output, leave render disabled and call export_final_kpis() manually after the final episode.
for episode in range(episodes):
last_episode = episode == episodes - 1
env.render_enabled = last_episode
env.export_kpis_on_episode_end = last_episode
observations, info = env.reset()
# run episode...
export_final_kpis() controls the BAU cost separately:
Call |
Output |
BAU sidecar cost |
|---|---|---|
|
KPI file only |
no |
|
KPI file with BAU rows |
yes |
|
KPI file with BAU rows and BAU time-series file |
yes |
Normal episode time series are controlled by render_mode/render_enabled, not by export_final_kpis().
For exact final-episode choices, keep export_kpis_on_episode_end=False and call export_final_kpis() yourself after the episode terminates.
Recommended Validation
.venv/bin/pytest -q
.venv/bin/python scripts/audit/audit_entity_contract.py --strict
.venv/bin/python scripts/audit/audit_physics.py
.venv/bin/python -m ruff check citylearn tests scripts/manual scripts/ci --select E9,F821
For large 15s datasets, always smoke-test a short window first:
.venv/bin/python - <<'PY'
import numpy as np
from citylearn.citylearn import CityLearnEnv
env = CityLearnEnv(
"citylearn_three_phase_electrical_service_demo_15s_parquet",
simulation_start_time_step=0,
simulation_end_time_step=120,
episode_time_steps=120,
)
obs, info = env.reset()
for _ in range(100):
if isinstance(env.action_space, list):
actions = [np.zeros(space.shape, dtype="float32") for space in env.action_space]
else:
actions = {
"tables": {
name: np.zeros(space.shape, dtype="float32")
for name, space in env.action_space["tables"].spaces.items()
}
}
obs, reward, terminated, truncated, info = env.step(actions)
if terminated or truncated:
break
print(env.evaluate_v2().head())
PY