Data Unit Contract

This document fixes the unit contract expected by the simulator. The goal is to avoid ambiguity when using hourly datasets, sub-hourly datasets or real data that originally arrives as power.

Base Rule

The physical engine accounts for energy per simulation step.

Quantity

Unit

Time-series energy columns

kWh/step

Nominal power, limits, contracts and physical power actions

kW

Storage capacity

kWh

State of charge (soc)

fraction in [0, 1]

Energy prices

currency per kWh

Carbon intensity

CO2 mass per kWh

seconds_per_time_step in the schema must represent the physical cadence of the dataset. If the dataset resolution and schema resolution differ, the simulator applies the compatibility conversion and prints an explicit [CityLearn][unit-conversion] warning. This is supported, but the preferred production path is to generate the dataset at the same resolution declared by the schema.

Main Time Series

Field

Unit

Notes

energy_simulation.non_shiftable_load

kWh/step

Energy consumed in the step.

energy_simulation.cooling_demand

kWh/step

Thermal demand in the step.

energy_simulation.heating_demand

kWh/step

Thermal demand in the step.

energy_simulation.dhw_demand

kWh/step

Thermal demand in the step.

energy_simulation.solar_generation

PV profile per installed kW or kWh/step

Depends on pv.attributes.generation_mode; see PV section.

pricing.electricity_pricing

currency/kWh

Not scaled by step size.

carbon_intensity.carbon_intensity

CO2/kWh

Not scaled by step size.

weather

physical unit of each variable

Temperature, irradiance and humidity are not energy.

Equipment

Element

Typical field

Unit

Battery / electrical storage

capacity

kWh

Battery / electrical storage

nominal_power

kW

Thermal storage

capacity

kWh

Thermal storage

max_input_power, max_output_power

kW

Heat pumps, heaters, chillers

nominal_power

electrical kW

PV

nominal_power

kW

EV charger

max_charging_power, max_discharging_power

kW

Electric vehicle

battery capacity

kWh

Deferrable appliance

cycle load_profile

kWh/step

Deferrable appliance

cycle total_energy_kwh

kWh

Grid/phase/contract limits

power limits

kW

Any kW limit is converted to step energy with:

max_energy_kwh_step = limit_kw * seconds_per_time_step / 3600

Real Data Conversion

When the real source arrives as power, the converter must produce energy columns in kWh/step before generating the CityLearn dataset.

energy_kwh_step = mean_power_kw_over_step * seconds_per_time_step / 3600

Practical rules:

Source

Conversion

Power series in kW

Aggregate to simulator cadence with time mean, then convert to kWh/step.

Interval readings already in kWh

Sum intervals belonging to the simulation step.

Cumulative kWh meters

Difference consecutive readings, then aggregate.

Prices and emissions

Align or forward-fill to simulator cadence; do not multiply by step size.

Phase limits, contracted power and device ratings

Keep in kW.

EV timestamps

Convert absolute times to integer timesteps used by charger schedules.

Deferrable timestamps

Use global dataset timesteps for earliest_start_time_step, latest_start_time_step and deadline_time_step.

EV charger countdowns

electric_vehicle_departure_time and electric_vehicle_estimated_arrival_time are step counts, not clock hours.

Entity derived forecasts

Computed from future dataset values in simulator time, with horizons converted from seconds using seconds_per_time_step.

Entity action feedback

Requested/limited actions are normalized commands; requested/limited/applied power fields are kW; short-window energy fields are kWh.

Entity BESS energy capacity

max_*_energy_kwh_step is nominal power converted by step duration; available_*_energy_kwh_step is the constrained SOC/headroom/outage capacity for the same step.

Sub-minute datasets

Include optional seconds in energy_simulation whenever possible.

Quality flags like generated

Keep them in the ingestion/conversion pipeline; they are not native physical CityLearn fields.

Example:

load_kw = 55.0
seconds_per_time_step = 15
load_kwh_step = 55.0 * 15 / 3600 = 0.2291667

Small kWh/step values are physically correct at 15s. If an agent needs kW or normalized values, convert in the agent observation layer, not in the internal energy balance.

Formats and Loading

Time-series files referenced by the schema may be CSV (.csv) or Parquet (.parquet, .pq, .parq). The column and unit contract is identical in both formats.

The loader reads only the window declared by simulation_start_time_step and simulation_end_time_step, while preserving the original offset so rolling or non-zero episodes stay aligned with global dataset indices.

For large datasets, especially full-year 15s data, Parquet is usually better than CSV because it preserves types, compresses better and avoids text parsing. Parquet support requires pyarrow; without it, use CSV.

When several buildings point to the same weather, pricing or carbon_intensity file and noise_std = 0, the simulator shares the same in-memory time series. energy_simulation remains building-specific.

Deferrable Appliances

The official format for deferrable loads uses two files per appliance:

File

Purpose

cycle_profiles_file

Physical cycle profile catalog.

flexibility_schedule_file

Flexibility requests pointing to catalog entries through profile_id.

cycle_profiles_file:

Field

Unit/type

Notes

profile_id

identifier

Stable physical profile key.

duration_steps

steps

Must match load_profile length.

total_energy_kwh

kWh

Must match sum(load_profile).

load_profile

list of kWh/step

Energy consumed at each cycle step.

flexibility_schedule_file:

Field

Unit/type

Notes

cycle_id

identifier

Unique request/occurrence.

profile_id

identifier

Reference to the catalog.

earliest_start_time_step

global timestep

First timestep where the cycle may start.

latest_start_time_step

global timestep

Last timestep where the agent may start.

deadline_time_step

global timestep

Last timestep by which the cycle must be complete.

priority

ratio [0, 1]

External request priority/urgency.

must_run

bool

Service contract flag.

The RL action is simple: start. If the action exceeds the threshold, the simulator tries to start the next pending cycle. The cycle starts only inside [earliest_start_time_step, latest_start_time_step] and only if it fits before deadline_time_step. If latest_start_time_step passes without a valid start, the cycle is marked missed.

If real cycle data arrives as power (kW), the converter must generate load_profile as energy:

cycle_step_kwh = cycle_step_kw * seconds_per_time_step / 3600

Do not repeat load_profile per timestep in the dataset. Temporal repetition belongs in flexibility_schedule_file, pointing to the same profile_id.

EV Flexibility Features

In the entity interface, EV flexibility should preferably be consumed through physical/normalized derived features:

Feature

Meaning

hours_until_departure

Physical time until departure.

time_until_departure_ratio

hours_until_departure / 24, clipped to [0, 1].

energy_to_required_soc_kwh

Energy still required to reach target SOC.

required_average_power_kw

Average power required until departure.

charging_slack_kw

Margin between charger max power and required average power.

charging_priority_ratio

Urgency in [0, 1], derived from required average power versus charger max power.

max_deliverable_energy_until_departure_kwh

Energy that can still be delivered by departure under current feasible charge power.

departure_energy_margin_kwh

max_deliverable_energy_until_departure_kwh - energy_to_required_soc_kwh; negative means the target is currently infeasible.

departure_feasibility_ratio

energy_to_required_soc_kwh / max_deliverable_energy_until_departure_kwh; values above 1 indicate infeasibility under current limits.

min_required_action_normalized

Required average charger power divided by charger max charging power; values above 1 indicate a power deadline conflict.

PV

energy_simulation.solar_generation is the easiest field to misinterpret. The simulator reads this field from the dataset. For compatibility with original CityLearn, the default mode treats it as generation profile per 1 kW of installed PV and multiplies by pv.nominal_power.

Default per_kw mode:

pv_generation_kwh_step = pv.nominal_power_kw * solar_generation / 1000

For real absolute data, declare:

"pv": {
  "type": "citylearn.energy_model.PV",
  "autosize": false,
  "attributes": {
    "nominal_power": 120.0,
    "generation_mode": "absolute"
  }
}

With generation_mode = "absolute", energy_simulation.solar_generation is interpreted directly as real PV energy in kWh/step:

pv_generation_kwh_step = solar_generation

In absolute mode, pv.nominal_power remains useful as installed power or physical rating in kW, but it no longer scales the dataset series.