Unit commitment (TSO)¶
Public API for UnitCommitmentEnv and the TSO benchmark factories. For physics, see Physics → Transmission.
UnitCommitmentEnv¶
Step contract¶
- Action:
Box(2 * n_units)in[-1, 1]=[commitment_signal (n_units) | dispatch_target (n_units)]. - Reward:
-reward_scale * (gen_cost + startup_cost + no_load_cost). - Constraint costs:
costs = (w_th * cost_thermal_overload, cost_reserve_shortfall, cost_min_updown).cost_min_updownis currently always 0 because masking guarantees feasibility.
Observation layout¶
[unit_status | time_in_state_norm | last_dispatch_norm | unit_cost_b_norm | line_flow_norm | load_norm | reserve_ratio | sin(t) | cos(t) | future_total_load_norm[t+1:t+H]]. Total dim 4 * n_units + n_lines + 4 + H, where H = forecast_horizon_steps.
This is the policy input vector, not a separate raw telemetry API. For field-by-field semantics, see Physics → Transmission.
powerzoojax.envs.grid.unit_commitment.UnitCommitmentEnv
¶
Bases: Environment
Unit Commitment environment — DC power flow with UC state machine.
Box(2 * n_units) in [-1, 1].
First n_units → commitment intent; > 0.0 → commit (if enable_uc). Last n_units → dispatch intent. Direct-PF mode: maps directly to [p_min, p_max] then clipped to ramp. DC-OPF mode : used as a preference bias — OPF cost is shifted by −dispatch_preference_weight × target_mw so the OPF solution moves toward the agent's preferred dispatch while still respecting physical constraints.
Env enforces
- Min-up / min-down constraints via commitment masking
- Ramp constraints via modified p_min/p_max in DCOPF
- Startup cost accounting (off→on transition)
- No-load cost (per ON unit per step)
- Reserve margin check
Reward: -reward_scale * (gen_cost + startup_cost + no_load_cost) Constraint costs: thermal_overload, reserve_shortfall, min_updown
reset(key, params)
¶
step(key, state, action, params)
¶
Run one UC environment step.
[commit_intent(n_units) | dispatch_intent(n_units)]
commit_intent ∈ [-1, 1]: > 0 → want to commit (if enable_uc). dispatch_intent ∈ [-1, 1]: maps to [eff_p_min, eff_p_max]. Direct-PF mode: used as dispatch setpoint. DC-OPF mode : biases OPF cost toward preferred dispatch.
observation_space(params)
¶
action_space(params)
¶
powerzoojax.envs.grid.unit_commitment.UCState
¶
Bases: GridState
Unit Commitment environment state (extends GridState).
Additional UC state-machine fields beyond GridState
unit_status: Current on/off for each unit (1=on, 0=off). time_in_state: Steps in current on/off state (≥ 1 after first step). last_dispatch: Previous step actual dispatch [MW] (0 for off units). startup_cost_accum: Episode-accumulated startup cost [$].
powerzoojax.envs.grid.unit_commitment.UCParams
¶
Bases: TransGridParams
Unit Commitment environment parameters (extends TransGridParams).
UC-specific fields
min_up_steps: Minimum consecutive ON steps per unit.
min_down_steps: Minimum consecutive OFF steps per unit.
startup_cost: One-time startup cost per unit [\(].
no_load_cost_per_step: Fixed running cost per step per unit [\)/step].
ramp_up_mw: Max ramp-up per step per unit [MW/step].
ramp_down_mw: Max ramp-down per step per unit [MW/step].
reserve_margin_frac: Required headroom above total load (e.g. 0.05).
enable_uc: If False, all units always ON — pure economic dispatch mode.
enable_reserve: If False, reserve shortfall not charged as cost.
forecast_horizon_steps: Number of future total-load forecast steps
appended to the observation. 0 keeps the legacy no-forecast layout.
init_unit_status: Initial on/off status for reset.
init_time_in_state: Initial time-in-state for reset [steps].
init_dispatch: Initial dispatch for reset [MW].
sample_start_on_reset: If True, reset samples a fresh 48-step episode
start uniformly from the bound load_profiles pool.
fixed_episode_start_idx: Deterministic start index used when sampling
is disabled (the eval/default path).
powerzoojax.envs.grid.unit_commitment.make_uc_params(case, load_profiles=None, max_steps=48, delta_t_hours=0.5, steps_per_day=48, reserve_margin_frac=0.05, enable_uc=True, enable_reserve=True, forecast_horizon_steps=0, reward_scale=0.0001, cost_thermal_weight=1.0, solver_mode=1, dcopf_max_iter=100, dcopf_tol=0.001, sample_start_on_reset=False, fixed_episode_start_idx=0)
¶
Create UCParams from a CaseData with UC fields.
Ramp conversion
unit_ramp_up / unit_ramp_down in CaseData are fractions per hour. ramp_mw_per_step = ramp_frac * p_max * delta_t_hours
min_up_time / min_down_time in CaseData are in steps (30-min res).
keep_time in CaseData is in steps; abs value = initial time-in-state.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
case
|
CaseData
|
CaseData with UC fields populated (unit_ramp_up, unit_min_up_time, unit_startup_cost, unit_no_load_cost, unit_init_state, unit_keep_time). |
required |
load_profiles
|
Array
|
(T, n_loads) MW load profiles. None → flat midpoint. |
None
|
max_steps
|
int
|
Episode length. |
48
|
delta_t_hours
|
float
|
Step duration in hours (0.5 = 30-min). |
0.5
|
steps_per_day
|
int
|
Steps per day (48 for 30-min resolution). |
48
|
reserve_margin_frac
|
float
|
Required reserve headroom fraction. |
0.05
|
enable_uc
|
bool
|
If False, all units always ON (ED mode). |
True
|
enable_reserve
|
bool
|
If False, no reserve cost. |
True
|
forecast_horizon_steps
|
int
|
Number of future total-load steps appended to the observation. |
0
|
reward_scale
|
float
|
Scalar for reward = -reward_scale * operating_cost. |
0.0001
|
cost_thermal_weight
|
float
|
Weight for thermal overload in CMDP cost. |
1.0
|
solver_mode
|
int
|
0 = direct dispatch PF, 1 = DC-OPF (recommended for SCUC). |
1
|
dcopf_max_iter
|
int
|
Max ADMM iterations for the DC-OPF redispatch. |
100
|
dcopf_tol
|
float
|
ADMM convergence tolerance for the DC-OPF redispatch. |
0.001
|
sample_start_on_reset
|
bool
|
Whether reset samples a new episode start uniformly from the bound load-profile pool. |
False
|
fixed_episode_start_idx
|
int
|
Deterministic episode start used when
|
0
|
TSO factories¶
from powerzoojax.envs import (
make_tso_case118_params,
make_tso_case14_params,
make_tso_ed_params,
make_tso_uc_params,
make_tso_scuc_params,
)
powerzoojax.tasks.tso.make_tso_case118_params(load_profiles=None, max_steps=48, delta_t_hours=0.5, reserve_margin_frac=0.05, enable_uc=True, enable_reserve=True, forecast_horizon_steps=0, reward_scale=0.0001, cost_thermal_weight=1.0, solver_mode=1, dcopf_max_iter=100, dcopf_tol=0.001, sample_start_on_reset=False, fixed_episode_start_idx=0, data_loader=None, split='train')
¶
Create UCParams for the case118 TSO-SCUC main task.
Uses IEEE 118-bus system with 54 generators and UC fields from case data. Load profiles default to synthetic net-load (intraday sinusoidal). Pass data_loader to use real GB demand / generation data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
load_profiles
|
Optional[Array]
|
Pre-built (T, n_loads) profiles [MW]. If None, generated from GB data (if data_loader given) or synthetic. |
None
|
max_steps
|
int
|
Episode length (48 steps = 24 hours at 30-min). |
48
|
delta_t_hours
|
float
|
Step duration in hours. |
0.5
|
reserve_margin_frac
|
float
|
Required reserve headroom fraction of load. |
0.05
|
enable_uc
|
bool
|
True=SCUC mode, False=pure ED. |
True
|
enable_reserve
|
bool
|
True=charge reserve shortfall as cost. |
True
|
forecast_horizon_steps
|
int
|
Number of future total-load steps appended to the observation. |
0
|
reward_scale
|
float
|
Multiplier for reward = -reward_scale * operating_cost. |
0.0001
|
cost_thermal_weight
|
float
|
Weight on thermal overload in CMDP cost. |
1.0
|
solver_mode
|
int
|
0=direct PF, 1=DC-OPF (recommended). |
1
|
dcopf_max_iter
|
int
|
Max ADMM iterations for the DC-OPF redispatch. |
100
|
dcopf_tol
|
float
|
ADMM convergence tolerance for the DC-OPF redispatch. |
0.001
|
sample_start_on_reset
|
bool
|
Whether reset samples a fresh episode start uniformly from the bound load-profile pool. |
False
|
fixed_episode_start_idx
|
int
|
Deterministic start index used when sampling is disabled. |
0
|
data_loader
|
Optional DataLoader for GB demand / wind / solar data. |
None
|
|
split
|
str
|
"train" or "iid" split for data_loader. |
'train'
|
Returns:
| Type | Description |
|---|---|
UCParams
|
UCParams ready for use with UnitCommitmentEnv. |
powerzoojax.tasks.tso.make_tso_case14_params(load_profiles=None, max_steps=48, delta_t_hours=0.5, reserve_margin_frac=0.05, enable_uc=True, enable_reserve=True, forecast_horizon_steps=0, reward_scale=0.0001, solver_mode=1, dcopf_max_iter=100, dcopf_tol=0.001)
¶
Create UCParams for the case14 small-scale sanity / debug task.
IEEE 14-bus system with 5 generators. UC fields come from create_case14()
(same table layout as case118). Suitable for small-scale UC / ED tests.
powerzoojax.tasks.tso.make_tso_ed_params(case_id='118', max_steps=48, delta_t_hours=0.5)
¶
Variant: TSO-ED — all units always ON, pure economic dispatch.
No UC constraints, no startup/no_load cost, no reserve check. Equivalent to TransGridEnv in DCOPF mode but wrapped in UCParams.
powerzoojax.tasks.tso.make_tso_uc_params(case_id='118', max_steps=48, delta_t_hours=0.5)
¶
Variant: TSO-UC — commitment + min-up/down + startup cost, NO reserve cost.
enable_uc=True, enable_reserve=False. Reserve shortfall is still tracked in info but not charged as CMDP cost.
powerzoojax.tasks.tso.make_tso_scuc_params(case_id='118', max_steps=48, delta_t_hours=0.5, reserve_margin_frac=0.05)
¶
Variant: TSO-SCUC-Safe — full SCUC with reserve cost in CMDP cost channel.
enable_uc=True, enable_reserve=True. Task-level CMDP selection consumes thermal overload + reserve shortfall. Intended for PPO-Lagrangian / SafeRLWrapper training.
TSO net-load helpers¶
powerzoojax.tasks.tso.make_tso_net_load_profiles(n_steps=48, wind_frac=0.7, solar_frac=0.5, load_d_max=None, gross_demand_mw=None, wind_mw=None, solar_mw=None, delta_t_hours=0.5)
¶
Construct (n_steps, n_loads) net-load profiles.
Net-load = gross_demand - wind_frac * wind - solar_frac * solar Then rescaled per bus by load_d_max.
When real data arrays are not provided, generates synthetic intraday profiles: - gross_demand: sinusoidal daily curve (peak at hour 18) - wind: higher at night / lower midday - solar: bell curve around noon
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_steps
|
int
|
Number of time steps. |
48
|
wind_frac
|
float
|
Fraction of wind that is must-take (default 0.7). |
0.7
|
solar_frac
|
float
|
Fraction of solar that is must-take (default 0.5). |
0.5
|
load_d_max
|
Optional[Array]
|
(n_loads,) per-bus maximum demand [MW]. If None, returns normalised profiles in [0, 1]. |
None
|
gross_demand_mw
|
Optional[Array]
|
(n_steps,) total system demand [MW]. None → synthetic. |
None
|
wind_mw
|
Optional[Array]
|
(n_steps,) wind available [MW]. None → synthetic. |
None
|
solar_mw
|
Optional[Array]
|
(n_steps,) solar available [MW]. None → synthetic. |
None
|
delta_t_hours
|
float
|
Step duration (used for synthetic profile phase). |
0.5
|
Returns:
| Type | Description |
|---|---|
Array
|
(n_steps, n_loads) float32 JAX array of load profiles [MW]. |
Array
|
If load_d_max is None, shape is (n_steps, 1) with values in [0, 1]. |
powerzoojax.tasks.tso.make_tso_net_load_profiles_from_data(data_loader, case, split='train', episode_start_idx=0, n_steps=48, wind_frac=0.7, solar_frac=0.5, allow_synthetic_fallback=False)
¶
Load net-load profiles from GB data via DataLoader.
Uses the DataLoader JAX API (load_jax_profiles) with semantic signal
names load.actual_mw, wind.available_mw, solar.available_mw.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data_loader
|
DataLoader instance. |
required | |
case
|
CaseData
|
CaseData for load_d_max scaling. |
required |
split
|
str
|
"train" or "iid" (selects GB_TRAIN_ or GB_IID_ date range). |
'train'
|
episode_start_idx
|
int
|
Starting row index within the loaded window. |
0
|
n_steps
|
int
|
Number of steps to extract. |
48
|
wind_frac
|
float
|
Must-take wind fraction (default 0.7). |
0.7
|
solar_frac
|
float
|
Must-take solar fraction (default 0.5). |
0.5
|
allow_synthetic_fallback
|
bool
|
If True, fall back to synthetic profiles when data loading fails instead of raising. Default False — callers must opt in to the fallback explicitly so failures are not silent. |
False
|
Returns:
| Type | Description |
|---|---|
Array
|
(n_steps, n_loads) float32 JAX array. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If data loading fails and allow_synthetic_fallback=False. |
Non-learning baselines¶
powerzoojax.tasks.tso.tso_all_on_rollout(env, params, key)
¶
Reference baseline: all units always ON; economic dispatch by DC-OPF.
Returns episode metrics dict.
powerzoojax.tasks.tso.tso_merit_order_rollout(env, params, key, n_commit=None)
¶
Priority-list (merit-order) commitment, then OPF dispatch (cheapest units first).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_commit
|
Optional[int]
|
Number of units to commit each step. None → choose a count from total load (commit just enough nameplate capacity to cover load + reserve). |
None
|
Metrics¶
powerzoojax.tasks.tso.compute_tso_metrics(rollout_results, baseline_results=None)
¶
Aggregate episode-level TSO metrics.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rollout_results
|
Dict[str, Any]
|
Dict from tso_*_rollout or custom policy rollout. |
required |
baseline_results
|
Optional[Dict[str, Any]]
|
Optional reference (e.g. all_on) for relative metrics. |
None
|
Returns:
| Type | Description |
|---|---|
Dict[str, float]
|
Dict of scalar metrics. |