Add GroupStepwiseReserveCurve: elastic group ASDC over sub-service supply - #219
Draft
rodrigomha wants to merge 3 commits into
Draft
Add GroupStepwiseReserveCurve: elastic group ASDC over sub-service supply#219rodrigomha wants to merge 3 commits into
rodrigomha wants to merge 3 commits into
Conversation
…pply Composes GroupReserve aggregation with StepwiseCostReserve demand pricing. A ReserveDemandCurveGroup service adds one endogenous ServiceRequirementVariable priced by the group ASDC and one RequirementConstraint binding the sum of the contributing sub-services' ActivePowerReserveVariable awards to that demand, so merit order lives on the sub-services and the single clearing (one MCPC) lives on the group. - GroupStepwiseReserveCurve formulation + export - Elastic aggregation constraint (reserve_group.jl) - Group two-stage construct_service! + deferred-group dispatcher - Widen the reserve cost/expression/offer chain and the problem-template contributing-device check to the group (a Service, not a Reserve) - Storage/hybrid reserve meta-key fixes (key on the concrete service type) and the ReserveDemandCurve storage upper-bound fix - Self-contained end-to-end test: build/solve, single clearing, aggregation, sub-service merit order, no-group regression
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a new reserve formulation (GroupStepwiseReserveCurve) to model an elastic group ancillary service demand curve cleared against the aggregated awards of multiple contributing sub-services, and wires it through construction, cost plumbing, and expressions so the group has a single clearing constraint/MCPC.
Changes:
- Introduces
GroupStepwiseReserveCurveformulation + export, plus an elastic group balance constraint binding sub-service awards to a group demand variable. - Updates service construction to defer all group formulations and adds two-stage
construct_service!forReserveDemandCurveGroup. - Extends ORDC/ASDC offer + expression plumbing to support
ReserveDemandCurveGroup, and adds a dedicated test covering build/solve/clearing behavior.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| test/test_group_stepwise_reserve.jl | Adds a self-contained integration test for group ASDC clearing over sub-services. |
| src/services_models/services_constructor.jl | Defers group formulations and adds two-stage constructors for GroupStepwiseReserveCurve. |
| src/services_models/reserves.jl | Extends reserve-variable/cost dispatch to include ReserveDemandCurveGroup + group objective logic. |
| src/services_models/reserve_group.jl | Adds elastic group requirement constraint tying sub-service awards to group demand variable. |
| src/core/formulations.jl | Defines the new GroupStepwiseReserveCurve formulation type. |
| src/PowerOperationsModels.jl | Exports GroupStepwiseReserveCurve. |
| src/common_models/add_expressions.jl | Adds a cost-expression container path for ReserveDemandCurveGroup services. |
| src/common_models/add_to_expression.jl | Widens ReserveDemand* dispatch unions to include ReserveDemandCurveGroup. |
| src/common_models/market_bid_plumbing.jl | Extends get_offer_curves to accept ReserveDemandCurveGroup. |
| src/common_models/market_bid_overrides.jl | Extends PWL delta-term dispatch to accept ReserveDemandCurveGroup. |
| src/core/problem_template.jl | Exempts ReserveDemandCurveGroup from contributing-device-map validation (device-less group). |
| src/energy_storage_models/storage_models.jl | Fixes ReserveDemandCurve(*) storage reserve upper bound and aligns expression meta-keying on typeof(service). |
| src/hybrid_system_models/hybrid_systems.jl | Aligns expression meta-keying on typeof(service) to avoid unit-system param loss. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+22
to
+24
| get_variable_binary(::Type{ServiceRequirementVariable}, ::Type{<:Union{PSY.ReserveDemandCurve, PSY.ReserveDemandTimeSeriesCurve, PSY.ReserveDemandCurveGroup}}, ::Type{<:AbstractReservesFormulation}) = false | ||
| get_variable_upper_bound(::Type{ServiceRequirementVariable}, ::Union{PSY.ReserveDemandCurve, PSY.ReserveDemandTimeSeriesCurve, PSY.ReserveDemandCurveGroup}, d::PSY.Component, ::Type{<:AbstractReservesFormulation}) = PSY.get_max_active_power(d, PSY.SU) | ||
| get_variable_lower_bound(::Type{ServiceRequirementVariable}, ::Union{PSY.ReserveDemandCurve, PSY.ReserveDemandTimeSeriesCurve, PSY.ReserveDemandCurveGroup}, ::PSY.Component, ::Type{<:AbstractReservesFormulation}) = 0.0 |
Comment on lines
55
to
+58
| function get_variable_upper_bound(::Type{ActivePowerReserveVariable}, r::Union{PSY.ReserveDemandCurve, PSY.ReserveDemandTimeSeriesCurve}, d::PSY.Storage, ::Type{<:AbstractReservesFormulation}) | ||
| return PSY.get_max_output_fraction(r) * (PSY.get_output_active_power_limits(d, PSY.SU).max + PSY.get_input_active_power_limits(d, PSY.SU).max) | ||
| # ReserveDemandCurve has no `max_output_fraction` field (unlike VariableReserve/ConstantReserve), | ||
| # so the full charge+discharge range is available - matching the AncillaryServiceVariableCharge/ | ||
| # Discharge ReserveDemandCurve methods above. |
PSY now provides an abstract ReserveGroup supertype of ConstantReserveGroup and ReserveDemandCurveGroup. Dispatch on it in problem_template.jl instead of listing the concrete types: the contributing-device exemption becomes `service_type <: PSY.ReserveGroup`, and _add_services_to_device_model! now skips every group (not just ConstantReserveGroup), which also fixes a latent gap where a ReserveDemandCurveGroup relied on the empty-contributing-devices fallback. Behavior is unchanged - ReserveGroup is exactly the two concrete group types.
Scrub vendor-specific jargon from the GroupStepwiseReserveCurve comments/docstrings so POM stays market-neutral: drop the explicit ERCOT / RTC+B references, and replace the ERCOT acronyms ASDC and MCPC (neither used elsewhere in POM) with the house terms - 'demand curve'/'ORDC' and 'clearing price'. Comments only; no behavior change.
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.
Summary
Adds
GroupStepwiseReserveCurve, a reserve formulation that composesGroupReserveaggregation withStepwiseCostReservedemand pricing. It models an elastic group Ancillary Service Demand Curve (ASDC) whose single demand is met by the awards of several contributing sub-services - e.g. one RRS product cleared over PFR/FFR/UFR sub-types.A
ReserveDemandCurveGroupservice (new PSY struct, on the local psy6 branch) adds:ServiceRequirementVariable, priced by the group ASDC (a decremental benefit), andRequirementConstraintbinding the sum of the contributing sub-services'ActivePowerReserveVariableawards to that demand.So supply merit order lives on the sub-services (their per-resource offers) and the single clearing (one MCPC) lives on the group.
Changes
GroupStepwiseReserveCurveformulation type + export.reserve_group.jl) - same_group_member_variablesaggregation asGroupReserve, RHS is the demand variable rather than a fixed requirement.construct_service!+ a_is_deferred_group_formulationdispatcher so group formulations construct after their sub-services.add_reserves_variable_cost!,add_expressions!,add_to_expression!,add_pwl_term_delta!,get_offer_curves) and the problem-template contributing-device check to the group, which is aService(not aReserve).typeof(service)so the unit-system type parameter is not dropped) and theReserveDemandCurvestorage upper-bound fix.Testing
New self-contained
test/test_group_stepwise_reserve.jlonc_sys5_uc: builds + solves, asserts a single demand variable / single clearing constraint, the aggregation balance holds every hour, cheap sub-service clears while the pricey one does not, and a no-group regression procures nothing.Test.detect_ambiguitiesclean; existingtest_device_reserve_offersandtest_services_constructorstill pass.Notes for review
ReserveDemandCurveGroupstruct (not yet merged); the[sources]PowerSystems path override is a local dev-only change and is intentionally NOT part of this branch.