Phase C: merged ORDC cost params + per-device reserve offers (stacked on #207) - #210
Closed
rodrigomha wants to merge 9 commits into
Closed
Phase C: merged ORDC cost params + per-device reserve offers (stacked on #207)#210rodrigomha wants to merge 9 commits into
rodrigomha wants to merge 9 commits into
Conversation
…ner per type
Migrate the ORDC (StepwiseCostReserve) slope/breakpoint ObjectiveFunctionParameter
containers off per-service meta=name to one merged container per service type (names
axis + tranche axis, empty meta), mirroring the device offer path:
- calc_additional_axes gains a batched Vector{<:ReserveDemandTimeSeriesCurve} form
sizing the tranche axis to the batch-wide max across the type's services (covariant
Vector{<:...} sidesteps the invariant-Vector unification issue that forced only()).
- _calc_additional_axes now forwards the batch for services too (drops only()).
- the ORDC PWL _add_parameters!/add_parameters! take a Vector of services, empty meta.
- process_stepwise_cost_reserve_parameters! + _maybe_process_stepwise take the vector;
the StepwiseCostReserve constructor calls them once, out of the per-service loop.
- the delta-PWL read drops meta=name (_get_pwl_data reads name-keyed, like devices).
The per-service/per-hour tranche padding (unwrap_for_param) now pads to the batch-wide
max; the build&solve ORDC test with two different-tranche ORDCs stays green and asserts
the merged (empty-meta) param container. POM-only, no IOM change.
Phase A moved the TransmissionInterface flow-limit params to the merged vector path and C1 uses the ObjectiveFunctionParameter path, so the single-service TimeSeriesParameter add_parameters!/_add_parameters! (service::U, ServiceModel) have no remaining callers in src. Remove both. Every add_parameters! call site now passes a vector.
A device can offer a PWL price/quantity curve for providing a reserve, stored via the PSY set_service_bid! path (a PiecewiseStepData time series named after the service, on a device with an OfferCurveCost; membership in MarketBidCost.ancillary_service_offers). This is the (service, device, segment, time) 4D cost structure - one PWL offer per (device, service) per hour, on top of the already-3D (service, device, time) reserve award. add_device_reserve_offers! builds such a fixture; the tests pin the data model (get_services_bid returns a per-device offer curve) and that the fixture builds and solves. Today these offers are NOT consumed - the reserve is priced flat (DEFAULT_RESERVE_COST) and no reserve-offer cost term is built; that consumer is the next step.
rodrigomha
force-pushed
the
rh/dev_refactor_services_pt2
branch
from
July 30, 2026 16:45
2423224 to
9b3b8e2
Compare
Consume the per-device reserve OFFER curves (set via PSY `set_service_bid!`,
retrieved with `get_services_bid`) instead of pricing every reserve award at the
flat `DEFAULT_RESERVE_COST`. This is the (service, device, segment, time) 4D cost
structure sitting on top of the 3D `(service, device, time)` reserve award.
- `PiecewiseLinearBlockReserveOffer` (SparseVariableType) + `ReserveOfferLinkingConstraint`.
- `add_reserve_offer_costs!` builds the delta/block-offer formulation per offering
device: segment vars `δ_k >= 0`, `δ_k <= P_{k+1}-P_k`, `Σ_k δ_k == award`,
`objective += Σ_k slope_k · δ_k · dt`. Reuses the device-offer PWL machinery
(`_get_raw_pwl_data`, `get_piecewise_curve_per_system_unit`,
`get_pwl_cost_expression_delta`). Returns the priced device names.
- `add_to_objective_function!` prices offering devices here, then the flat-cost pass
skips them via a new `skip_devices` kwarg on `add_reserves_proportional_cost!`.
The 4D block var is created through the axes+`sparse=true` container path so the key
type is `(String,String,Int,Int)`; the SparseVariableType default is hardcoded 3-tuple.
Test asserts the block var, the award-linking constraint (±1 coefficients), and the
offer-slope objective coefficient (segment-2/segment-1 ratio = 1.5).
…atch predicate
Addresses the four review items on the C2 consumer:
1. Proper 4D container. Consume a new IOM trait `sparse_variable_key_type` (default 3D
`(device, segment, time)`) overridden for `PiecewiseLinearBlockReserveOffer` to the 4D
`(service, device, segment, time)` key, so the SparseVariableType auto-path builds the
right container. Replaces the `1:1` axis key-type-carrier workaround.
2. Magnitude assertion. The test now checks the objective coefficient equals
`slope * base_p * dt` (an independent reference), not just the 1.5x segment ratio -
this pins the natural->system-base conversion, catching a silent wrong-magnitude cost.
3. Dispatch instead of `isa`. `_has_reserve_offer` dispatches on the operation-cost type
(`OfferCurveCost` offers; `OperationalCost` fallback returns false).
Also restores the `names` binding that had silently resolved to `Base.names`, which had
widened the linking-constraint key to `Tuple{String, Any, Int}`; now `Tuple{String, String, Int}`.
Reserve-offer PWL block vars remain unexported in results (verified export is structurally
correct - sum(delta)*base_p == award, cheapest segment fills first - but delta exports in pu,
mismatched against the MW-natural award, so it stays off).
Depends on an IOM change adding `sparse_variable_key_type` (rh/dev_service_refactor).
rodrigomha
force-pushed
the
rh/dev_refactor_services_pt3
branch
from
July 30, 2026 16:45
cd265bf to
ddbe923
Compare
Add a short explanation to the 'Attach a per-device reserve offer' section covering the two persistent effects of set_service_bid! (attach the named offer time series, register the service in ancillary_service_offers) plus its validation, and why they let the model price the reserve award by the per-device curve.
rodrigomha
marked this pull request as ready for review
July 30, 2026 18:36
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part 3 of the service-container refactor. Stacked on #207 (Phase A+B) → base branch
rh/dev_refactor_services_pt2.Review only the commits above #207; GitHub will show the full stack until #207 merges.
What this adds (Phase C)
C1 — merged ORDC PWL cost params. Fold the per-service
TimeSeriesParameterslope/breakpointPWL cost containers into one merged container per service type (covariant
Vector{<:ReserveDemandTimeSeriesCurve}axis computation, batch-wide max-tranche padding). Retires the now-dead single-service
add_parameters!/_add_parameters!path.C2 — per-device ancillary-service reserve offers. A contributing device can OFFER a PWL
price/quantity curve for a reserve service (PSY
set_service_bid!/get_services_bid); it is nowpriced by that curve instead of the flat
DEFAULT_RESERVE_COST. This is the 4D(service, device, segment, time)cost structure on top of the 3D(service, device, time)reserve award.PiecewiseLinearBlockReserveOffer(SparseVariableType) +ReserveOfferLinkingConstraint.add_reserve_offer_costs!builds the delta/block-offer formulation per offering device and pricesits segments; the flat-cost pass skips priced devices via a
skip_deviceskwarg.sparse_variable_key_typetrait (Per-type service models and merged sparse/dense reserve containers InfrastructureOptimizationModels.jl#141).Tests
test_device_reserve_offers.jl: data model + a fixture that builds/solves and asserts the 4D blockvar, the award-linking constraint (±1 coefficients), and the offer-slope objective coefficient
(
≈ slope · base_p · dt, an independent unit-checked reference).Full POM suite green (105,495 assertions), 0 method ambiguities.
Cross-package dependency
Depends on Sienna-Platform/InfrastructureOptimizationModels.jl#141 (adds
sparse_variable_key_type).The
[sources]IOM pin here already tracksrh/dev_service_refactor.🤖 Generated with Claude Code
https://claude.ai/code/session_01W1bssU5YnY8eZWQD9apDBM