Skip to content

Complete the device time-varying market-bid offer-curve path#141

Merged
jd-lara merged 4 commits into
mainfrom
rh/device-timevarying-marketbid
Jun 24, 2026
Merged

Complete the device time-varying market-bid offer-curve path#141
jd-lara merged 4 commits into
mainfrom
rh/device-timevarying-marketbid

Conversation

@rodrigomha

Copy link
Copy Markdown
Contributor

Summary

Completes the device time-varying market-bid offer-curve path. The time-varying PWL market-bid machinery was wired for ORDC services only; a ThermalStandard (or other device) carrying a MarketBidTimeSeriesCost cascaded through several missing device-side methods during build!. This adds the device counterparts so a 24h time-varying offer-curve UC builds and solves.

Pairs with Sienna-Platform/InfrastructureOptimizationModels.jl#rh/scalar-ts-cost-unwrap (scalar unwrap_for_param), which fixes the first crash in the chain.

What was missing (each surfaced as a separate build! failure)

  1. _get_time_series_name for Incremental/DecrementalPiecewiseLinearSlope/BreakpointParameter on a device — the generic get_time_series_names(model)[T] has no entry → KeyError. Added device methods reading the offer-curve time-series key from the device's MarketBidTimeSeriesCost (analogous to the existing ORDC-service method).
  2. calc_additional_axes for those PWL params on a device — the default device method returns (), so the parameter container was built with no tranche axis and the PWL element fell through unwrap_for_paramsize(::PiecewiseStepData). Added device methods that size the container to the batch-wide max tranches (via get_max_tranches), padding shorter per-hour curves.
  3. _get_cost_if_exists(::MarketBidTimeSeriesCost) = nothing — the generic falls to get_variable(cost) (a service/fuel accessor) → MethodError. Mirrors the existing static MarketBidCost method (TS bids aren't fuel curves).

All additions are small and additive, mirroring the existing static-MarketBidCost / ORDC-service handling; no changes to existing methods.

Verification

A 24h copper-plate UC with ThermalStandard gens (and an InterruptiblePowerLoad bid) carrying time-varying offer curves now build!s and solve!s, with the per-hour JuMP block-offer objective coefficients matching the per-hour offer-curve slopes exactly. Verified on both synthetic data and ~1 month of real ERCOT DAM offer curves (per-hour SingleTimeSeriestransform_single_time_series! → 24h Deterministic window).

Note: building a one-sided device cost (supply-only gen / demand-only load) requires marking the unused side with an empty-name time-series key (the placeholder the validation keys off). PSY exposes no helper for this — worth a follow-up to add one, analogous to the static ZERO_OFFER_CURVE.

🤖 Generated with Claude Code

The time-varying PWL market-bid machinery was wired for ORDC services only; the
device (ThermalStandard MarketBidTimeSeriesCost) path was missing its counterparts,
so build! cascaded through several gaps. Add the device-side methods:
- _get_time_series_name for incremental/decremental PWL slope+breakpoint params
  (read the offer-curve time-series key from the device's cost).
- calc_additional_axes for those params (batch-max tranche axis), so the PWL
  parameter container is sized with its tranche axis instead of empty.
- _get_cost_if_exists(::MarketBidTimeSeriesCost) = nothing (not a fuel curve),
  mirroring the static MarketBidCost method.
@jd-lara jd-lara self-requested a review June 24, 2026 00:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR completes the device-side plumbing for time-varying market-bid offer curves so that devices carrying MarketBidTimeSeriesCost can successfully build! and solve with time-varying PWL offer curves.

Changes:

  • Adds device-side _get_time_series_name overloads for incremental/decremental PWL slope & breakpoint parameters.
  • Adds device-side calc_additional_axes overloads for tranche-axis sizing of time-varying PWL slope & breakpoint parameters.
  • Fixes fuel-curve detection path by treating MarketBidTimeSeriesCost as non-fuel-curve (like MarketBidCost).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
src/common_models/add_parameters.jl Adds missing device-side time-series name resolution and tranche-axis sizing for time-varying PWL offer-curve parameters.
src/common_models/add_expressions.jl Prevents MarketBidTimeSeriesCost from falling into the fuel-curve accessor path by returning nothing for _get_cost_if_exists.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/common_models/add_parameters.jl Outdated
Comment on lines +467 to +484
function _get_time_series_name(
::Type{
<:Union{
IncrementalPiecewiseLinearSlopeParameter,
IncrementalPiecewiseLinearBreakpointParameter,
},
},
device::PSY.Component,
::DeviceModel,
)
op_cost = PSY.get_operation_cost(device)
IS.@assert_op op_cost isa TS_OFFER_CURVE_COST_TYPES
return IS.get_name(
IS.get_time_series_key(
PSY.get_value_curve(PSY.get_incremental_offer_curves(op_cost)),
),
)
end
Comment thread src/common_models/add_parameters.jl Outdated
Comment on lines +486 to +503
function _get_time_series_name(
::Type{
<:Union{
DecrementalPiecewiseLinearSlopeParameter,
DecrementalPiecewiseLinearBreakpointParameter,
},
},
device::PSY.Component,
::DeviceModel,
)
op_cost = PSY.get_operation_cost(device)
IS.@assert_op op_cost isa TS_OFFER_CURVE_COST_TYPES
return IS.get_name(
IS.get_time_series_key(
PSY.get_value_curve(PSY.get_decremental_offer_curves(op_cost)),
),
)
end
Comment thread src/common_models/add_parameters.jl Outdated
Comment on lines +622 to +634
function _device_offer_curve_ts_key(::Type{T}, device::PSY.Component) where {T <: ParameterType}
op_cost = PSY.get_operation_cost(device)
IS.@assert_op op_cost isa TS_OFFER_CURVE_COST_TYPES
curve = if T <: Union{
IncrementalPiecewiseLinearSlopeParameter,
IncrementalPiecewiseLinearBreakpointParameter,
}
PSY.get_incremental_offer_curves(op_cost)
else
PSY.get_decremental_offer_curves(op_cost)
end
return IS.get_time_series_key(PSY.get_value_curve(curve))
end
Comment on lines +636 to +662
function calc_additional_axes(
::OptimizationContainer,
::Type{T},
devices::U,
::DeviceModel{D, W},
) where {
T <: AbstractPiecewiseLinearSlopeParameter,
U <: Union{Vector{D}, IS.FlattenIteratorWrapper{D}},
W <: AbstractDeviceFormulation,
} where {D <: PSY.Component}
max_tranches = maximum(d -> get_max_tranches(d, _device_offer_curve_ts_key(T, d)), devices)
return (IOM.make_tranche_axis(max_tranches),)
end

function calc_additional_axes(
::OptimizationContainer,
::Type{T},
devices::U,
::DeviceModel{D, W},
) where {
T <: AbstractPiecewiseLinearBreakpointParameter,
U <: Union{Vector{D}, IS.FlattenIteratorWrapper{D}},
W <: AbstractDeviceFormulation,
} where {D <: PSY.Component}
max_tranches = maximum(d -> get_max_tranches(d, _device_offer_curve_ts_key(T, d)), devices)
return (IOM.make_tranche_axis(max_tranches + 1),) # one more breakpoint than tranches
end
Comment thread src/common_models/add_parameters.jl Outdated
)
end

function _get_time_series_name(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can combine these with a helper.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are also other _get_time_series_name methods with similar logic so maybe this could use a larger refactor.

Comment thread src/common_models/add_parameters.jl Outdated
function _device_offer_curve_ts_key(::Type{T}, device::PSY.Component) where {T <: ParameterType}
op_cost = PSY.get_operation_cost(device)
IS.@assert_op op_cost isa TS_OFFER_CURVE_COST_TYPES
curve = if T <: Union{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should dispatch on this instead.

Comment thread src/common_models/add_parameters.jl Outdated
)
end

# Time-varying device offer curves: the incremental/decremental PWL slope &

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this comment is excessive, the other methods don't use documentation.

@jd-lara jd-lara merged commit b22d82d into main Jun 24, 2026
4 of 6 checks passed
@jd-lara jd-lara deleted the rh/device-timevarying-marketbid branch June 24, 2026 05:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants