Exact bid-based SCED¶
Public API for the exact primal-dual interior-point security-constrained economic dispatch (SCED) solver. For physics, see Physics → Markets.
Overview¶
offer_sced solves the offer-based SCED LP exactly via a primal-dual interior-point method (PD-IPM). It is JIT-compatible and vmap-able. LMPs are recovered from IPM duals and match HiGHS LP marginals to within < 1e-4 $/MWh on tested cases.
The setup-time entry point is prepare_offer_sced(case, n_segments=...) which builds the segment widths, base prices, line capacity, and PTDF-based mapping matrices. The runtime entry point is offer_sced(setup, load_mw, offer_prices, p_min_rt=None, p_max_rt=None).
API¶
powerzoojax.envs.market.offer_sced.prepare_offer_sced(case, n_segments=3)
¶
Prepare OfferSCEDSetup from CaseData. Called once at setup time.
Uses pure float32 arithmetic in the PD-IPM solver (no x64 required). Numerical stability is achieved via adaptive regularization + diagonal equilibration in the KKT solve (see offer_sced for details).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
case
|
CaseData
|
CaseData instance. |
required |
n_segments
|
int
|
Number of offer-curve segments per generator. |
3
|
Returns:
| Type | Description |
|---|---|
OfferSCEDSetup
|
OfferSCEDSetup ready for |
powerzoojax.envs.market.offer_sced.offer_sced(setup, node_load_mw, offer_prices=None, p_min_rt=None, p_max_rt=None, max_iter=20, tol=1e-05)
¶
Solve bid-based SCED via primal-dual interior point method (float32).
Uses adaptive regularization + diagonal equilibration to stabilize the (n_dim+1)×(n_dim+1) KKT solve in float32 arithmetic. The regularization reg = max(1e-8, 1e-4 * mean(D_L)) is proportional to the mean line dual, which stays small relative to H diagonal (reg/H_ii ~ 1e-4) so the LP optimum is not significantly perturbed. Equilibration further reduces the effective condition number by row/column scaling.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
setup
|
OfferSCEDSetup
|
OfferSCEDSetup (from prepare_offer_sced). |
required |
node_load_mw
|
Array
|
(n_nodes,) nodal load [MW]. |
required |
offer_prices
|
Array
|
(n_units, n_segments) offer prices [$/MWh]. If None, uses setup.base_seg_prices (truthful bids). |
None
|
p_min_rt
|
Array
|
(n_units,) runtime lower bounds [MW] for ramp coupling. If None, uses setup.p_min (static case, backward compatible). |
None
|
p_max_rt
|
Array
|
(n_units,) runtime upper bounds [MW] for ramp coupling. If None, uses setup.p_max (static case, backward compatible). When provided together with p_min_rt, segment widths are scaled proportionally to the available headroom (p_max_rt - p_min_rt), enforcing ramp constraints in the LP without changing problem shape. |
None
|
max_iter
|
int
|
PD-IPM iterations (uses lax.fori_loop with early stopping). Default 20: case5 converges in 10-12 iterations for typical GB demand; remaining iterations are frozen no-ops. Early stopping also prevents float32 post-convergence drift (mu rebounds after ~iter 12 for congested cases without it). |
20
|
tol
|
float
|
Convergence tolerance on duality gap (μ = sᵀλ / m). Default 1e-5 (rather than 1e-6) because the KKT Schur complement becomes ill-conditioned before the gap closes below ~1.5e-6 for congested LPs; the frozen last-valid iterate typically has μ~1e-6, well within physical accuracy requirements. |
1e-05
|
Returns:
| Type | Description |
|---|---|
OfferSCEDResult
|
OfferSCEDResult with nodal LMP computed from IPM duals. |
powerzoojax.envs.market.offer_sced.OfferSCEDSetup
¶
Precomputed data for bid-based single-period SCED.
Setup-time fields (frozen, passed to JIT-compiled solver). All line limits have 0 replaced by ±1e5 (unconstrained).
powerzoojax.envs.market.offer_sced.OfferSCEDResult
¶
Output of exact bid-based SCED solver.
All numerical assertions require converged == True as precondition.
Usage example¶
import jax
import jax.numpy as jnp
from powerzoojax.case import load_case
from powerzoojax.envs import prepare_offer_sced, offer_sced
case = load_case("5")
setup = prepare_offer_sced(case, n_segments=3)
load_mw = jnp.full((case.n_loads,), 100.0)
nodal_load = case.nodes_loads_map @ load_mw
offer_prices = setup.base_seg_prices # truthful
result = offer_sced(setup, nodal_load, offer_prices)
print("dispatch:", result.unit_power)
print("lmp:", result.lmp)
print("converged:", result.converged)
To enforce per-step ramp bounds at the LP level (used by MarketMARLEnv), pass p_min_rt and p_max_rt overrides: