Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Market bid cost with pwl cost curve #1182

Open
wants to merge 22 commits into
base: market_bid_cost
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/core/constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,13 @@ See [Piecewise linear cost functions](@ref pwl_cost) for more information.
"""
struct PieceWiseLinearBlockOfferConstraint <: ConstraintType end

"""
Struct to create the PieceWiseLinearBlockDecrementalOfferConstraint associated with a specified variable.

See [Piecewise linear cost functions](@ref pwl_cost) for more information.
"""
struct PieceWiseLinearBlockDecrementalOfferConstraint <: ConstraintType end

"""
Struct to create the PieceWiseLinearUpperBoundConstraint associated with a specified variable.

Expand Down
8 changes: 8 additions & 0 deletions src/core/variables.jl
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,13 @@ Docs abbreviation: ``\\delta``
"""
struct PieceWiseLinearBlockOffer <: SparseVariableType end

"""
Struct to dispatch the creation of piecewise linear block decremental offer variables for objective function

Docs abbreviation: ``\\delta_d``
"""
struct PieceWiseLinearBlockDecrementalOffer <: SparseVariableType end

"""
Struct to dispatch the creation of Interface Flow Slack Up variables

Expand Down Expand Up @@ -315,6 +322,7 @@ const START_VARIABLES = (HotStartVariable, WarmStartVariable, ColdStartVariable)

should_write_resulting_value(::Type{PieceWiseLinearCostVariable}) = false
should_write_resulting_value(::Type{PieceWiseLinearBlockOffer}) = false
should_write_resulting_value(::Type{PieceWiseLinearBlockDecrementalOffer}) = false
should_write_resulting_value(::Type{HVDCPiecewiseLossVariable}) = false
should_write_resulting_value(::Type{HVDCPiecewiseBinaryLossVariable}) = false
convert_result_to_natural_units(::Type{ActivePowerVariable}) = true
Expand Down
243 changes: 232 additions & 11 deletions src/devices_models/devices/common/objective_function/market_bid.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@ function _add_pwl_variables!(
component_name::String,
time_period::Int,
cost_data::PSY.PiecewiseStepData,
) where {T <: PSY.Component}
var_container = lazy_container_addition!(container, PieceWiseLinearBlockOffer(), T)
::Type{U},
) where {T <: PSY.Component, U <: Union{PieceWiseLinearBlockOffer, PieceWiseLinearBlockDecrementalOffer}}
var_container = lazy_container_addition!(container, U(), T)
# length(PiecewiseStepData) gets number of segments, here we want number of points
break_points = PSY.get_x_coords(cost_data)
pwlvars = Array{JuMP.VariableRef}(undef, length(break_points))
for i in 1:(length(break_points) - 1)
pwlvars[i] =
var_container[(component_name, i, time_period)] = JuMP.@variable(
get_jump_model(container),
base_name = "PieceWiseLinearBlockOffer_$(component_name)_{pwl_$(i), $time_period}",
base_name = "$(string(U))_$(component_name)_{pwl_$(i), $time_period}",
lower_bound = 0.0,
)
end
Expand All @@ -43,17 +44,21 @@ function _add_pwl_constraint!(
::U,
break_points::Vector{Float64},
period::Int,
) where {T <: PSY.Component, U <: VariableType}
::Type{V},
::Type{W},
) where {T <: PSY.Component, U <: VariableType,
V <: Union{PieceWiseLinearBlockOffer, PieceWiseLinearBlockDecrementalOffer},
W <: Union{PieceWiseLinearBlockOfferConstraint, PieceWiseLinearBlockDecrementalOfferConstraint}}
variables = get_variable(container, U(), T)
const_container = lazy_container_addition!(
container,
PieceWiseLinearBlockOfferConstraint(),
W(),
T,
axes(variables)...,
)
len_cost_data = length(break_points) - 1
jump_model = get_jump_model(container)
pwl_vars = get_variable(container, PieceWiseLinearBlockOffer(), T)
pwl_vars = get_variable(container, V(), T)
name = PSY.get_name(component)
const_container[name, period] = JuMP.@constraint(
jump_model,
Expand Down Expand Up @@ -133,9 +138,10 @@ function _get_pwl_cost_expression(
time_period::Int,
cost_data::PSY.PiecewiseStepData,
multiplier::Float64,
) where {T <: PSY.Component}
::Type{U}
) where {T <: PSY.Component, U <: Union{PieceWiseLinearBlockOffer, PieceWiseLinearBlockDecrementalOffer}}
name = PSY.get_name(component)
pwl_var_container = get_variable(container, PieceWiseLinearBlockOffer(), T)
pwl_var_container = get_variable(container, U(), T)
gen_cost = JuMP.AffExpr(0.0)
y_coords_cost_data = PSY.get_y_coords(cost_data)
for (i, cost) in enumerate(y_coords_cost_data)
Expand Down Expand Up @@ -176,9 +182,42 @@ function _get_pwl_cost_expression(
time_period,
cost_data_normalized,
dt,
PieceWiseLinearBlockOffer,
)
end

function _get_pwl_cost_expression_decremental(container::OptimizationContainer,
Copy link
Contributor

Choose a reason for hiding this comment

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

Can this be implemented in a way that does not duplicate as much code from the incremental version? Maybe refactor both functions to call a helper function with the common code?

component::T,
time_period::Int,
cost_function::PSY.MarketBidCost,
::PSY.PiecewiseStepData,
::U,
::V) where {T <: PSY.Component, U <: VariableType,
V <: AbstractDeviceFormulation}
decremental_curve = PSY.get_decremental_offer_curves(cost_function)
value_curve = PSY.get_value_curve(decremental_curve)
power_units = PSY.get_power_units(decremental_curve)
cost_component = PSY.get_function_data(value_curve)
base_power = get_base_power(container)
device_base_power = PSY.get_base_power(component)
cost_data_normalized = get_piecewise_incrementalcurve_per_system_unit(
cost_component,
power_units,
base_power,
device_base_power,
)
resolution = get_resolution(container)
dt = Dates.value(resolution) / MILLISECONDS_IN_HOUR
multiplier = OBJECTIVE_FUNCTION_NEGATIVE * dt
return _get_pwl_cost_expression(container,
component,
time_period,
cost_data_normalized,
multiplier,
PieceWiseLinearBlockDecrementalOffer,
)
end

"""
Get cost expression for StepwiseCostReserve
"""
Expand Down Expand Up @@ -275,6 +314,20 @@ end
######## MarketBidCost: Fixed Curves ##########
###############################################

"""
Check if deceremental pwl offer curve is monotonically decreasing.
"""
function _is_convex_decremental(pwl:: PSY.PiecewiseStepData)
Copy link
Contributor

Choose a reason for hiding this comment

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

Ideally this function would go in the same place as the existing is_convex, in IS/PSY. Maybe is_concave?

y_coords = PSY.get_y_coords(pwl)
for ix in 1:(length(y_coords) - 1)
if y_coords[ix] < y_coords[ix + 1]
@debug y_coords
return false
end
end
return true
end

"""
Add PWL cost terms for data coming from the MarketBidCost
with a fixed incremental offer curve
Expand Down Expand Up @@ -311,15 +364,52 @@ function _add_pwl_term!(
time_steps = get_time_steps(container)
pwl_cost_expressions = Vector{JuMP.AffExpr}(undef, time_steps[end])
for t in time_steps
_add_pwl_variables!(container, T, name, t, data)
_add_pwl_constraint!(container, component, U(), break_points, t)
_add_pwl_variables!(container, T, name, t, data, PieceWiseLinearBlockOffer)
_add_pwl_constraint!(container, component, U(), break_points, t, PieceWiseLinearBlockOffer, PieceWiseLinearBlockOfferConstraint)
pwl_cost =
_get_pwl_cost_expression(container, component, t, cost_function, data, U(), V())
pwl_cost_expressions[t] = pwl_cost
end
return pwl_cost_expressions
end

function _add_pwl_term_decremental!(container::OptimizationContainer,
Copy link
Contributor

Choose a reason for hiding this comment

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

Same note about code duplication here

component::T,
cost_function::PSY.MarketBidCost,
::PSY.CostCurve{PSY.PiecewiseIncrementalCurve},
::U,
::V) where {T <: PSY.Component, U <: VariableType,
V <: AbstractDeviceFormulation}
name = PSY.get_name(component)
decremental_offer_curve = PSY.get_decremental_offer_curves(cost_function)
value_curve = PSY.get_value_curve(decremental_offer_curve)
cost_component = PSY.get_function_data(value_curve)
base_power = get_base_power(container)
device_base_power = PSY.get_base_power(component)
power_units = PSY.get_power_units(decremental_offer_curve)

data = get_piecewise_incrementalcurve_per_system_unit(cost_component,
power_units,
base_power,
device_base_power)

cost_is_convex = _is_convex_decremental(data)
if !cost_is_convex
error("MarketBidCost for component $(name) is non-convex")
end

break_points = PSY.get_x_coords(data)
time_steps = get_time_steps(container)
pwl_cost_expressions = Vector{JuMP.AffExpr}(undef, time_steps[end])
for t in time_steps
_add_pwl_variables!(container, T, name, t, data, PieceWiseLinearBlockDecrementalOffer)
_add_pwl_constraint!(container, component, U(), break_points, t, PieceWiseLinearBlockDecrementalOffer, PieceWiseLinearBlockDecrementalOfferConstraint)
pwl_cost = _get_pwl_cost_expression_decremental(container, component, t, cost_function, data, U(), V())
pwl_cost_expressions[t] = pwl_cost
end
return pwl_cost_expressions
end

##################################################
########## PWL for StepwiseCostReserve ##########
##################################################
Expand Down Expand Up @@ -476,7 +566,7 @@ function _add_variable_cost_to_objective!(
initial_time = get_initial_time(container)
incremental_cost_curves = PSY.get_incremental_offer_curves(cost_function)
decremental_cost_curves = PSY.get_decremental_offer_curves(cost_function)
if isnothing(decremental_cost_curves)
if !isnothing(decremental_cost_curves)
error("Component $(component_name) is not allowed to participate as a demand.")
end
#=
Expand Down Expand Up @@ -544,6 +634,81 @@ function _add_variable_cost_to_objective!(
return
end

function _add_variable_cost_to_objective!(container::OptimizationContainer,
::T,
component::PSY.Component,
cost_function::PSY.MarketBidCost,
::U) where {T <: VariableType,
U <: AbstractControllablePowerLoadFormulation}
component_name = PSY.get_name(component)
@debug "Market Bid" _group = LOG_GROUP_COST_FUNCTIONS component_name
time_steps = get_time_steps(container)
initial_time = get_initial_time(container)
incremental_cost_curves = PSY.get_incremental_offer_curves(cost_function)
decremental_cost_curves = PSY.get_decremental_offer_curves(cost_function)
if !(isnothing(incremental_cost_curves))
error("Component $(component_name) is not allowed to participate as a supply.")
end
#=
variable_cost_forecast = PSY.get_variable_cost(
component,
op_cost;
start_time = initial_time,
len = length(time_steps),
)
variable_cost_forecast_values = TimeSeries.values(variable_cost_forecast)
parameter_container = _get_cost_function_parameter_container(
container,
CostFunctionParameter(),
component,
T(),
U(),
eltype(variable_cost_forecast_values),
)
=#
pwl_cost_expressions = _add_pwl_term_decremental!(container,
component,
cost_function,
decremental_cost_curves,
T(),
U())
jump_model = get_jump_model(container)
for t in time_steps
#=
set_multiplier!(
parameter_container,
# Using 1.0 here since we want to reuse the existing code that adds the mulitpler
# of base power times the time delta.
1.0,
component_name,
t,
)
set_parameter!(
parameter_container,
jump_model,
variable_cost_forecast_values[t],
component_name,
t,
)
=#
add_to_expression!(container,
ProductionCostExpression,
pwl_cost_expressions[t],
component,
t)
add_to_objective_variant_expression!(container, pwl_cost_expressions[t])
end

# Service Cost Bid
#=
ancillary_services = PSY.get_ancillary_service_offers(op_cost)
for service in ancillary_services
_add_service_bid_cost!(container, component, service)
end
=#
return
end

function _add_service_bid_cost!(
container::OptimizationContainer,
component::PSY.Component,
Expand Down Expand Up @@ -583,3 +748,59 @@ function _add_service_bid_cost!(
end

function _add_service_bid_cost!(::OptimizationContainer, ::PSY.Component, ::PSY.Service) end

function _add_vom_cost_to_objective!(
container::OptimizationContainer,
::T,
component::PSY.Component,
op_cost::PSY.MarketBidCost,
::U,
) where {T <: VariableType, U <: AbstractDeviceFormulation}
incremental_cost_curves = PSY.get_incremental_offer_curves(op_cost)
decremental_cost_curves = PSY.get_decremental_offer_curves(op_cost)
power_units = PSY.get_power_units(incremental_cost_curves)
vom_cost = PSY.get_vom_cost(incremental_cost_curves)
multiplier = 1.0 # VOM Cost is always positive
cost_term = PSY.get_proportional_term(vom_cost)
iszero(cost_term) && return
base_power = get_base_power(container)
device_base_power = PSY.get_base_power(component)
cost_term_normalized = get_proportional_cost_per_system_unit(cost_term,
power_units,
base_power,
device_base_power)
for t in get_time_steps(container)
exp = _add_proportional_term!(container, T(), d, cost_term_normalized * multiplier,
t)
add_to_expression!(container, ProductionCostExpression, exp, d, t)
end
return
end


function _add_vom_cost_to_objective!(container::OptimizationContainer,
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the difference between these two _add_vom_cost_to_objective! methods?

::T,
component::PSY.Component,
op_cost::PSY.MarketBidCost,
::U) where {T <: VariableType,
U <: AbstractControllablePowerLoadFormulation}
incremental_cost_curves = PSY.get_incremental_offer_curves(op_cost)
decremental_cost_curves = PSY.get_decremental_offer_curves(op_cost)
power_units = PSY.get_power_units(decremental_cost_curves)
vom_cost = PSY.get_vom_cost(decremental_cost_curves)
multiplier = 1.0 # VOM Cost is always positive
cost_term = PSY.get_proportional_term(vom_cost)
iszero(cost_term) && return
base_power = get_base_power(container)
device_base_power = PSY.get_base_power(component)
cost_term_normalized = get_proportional_cost_per_system_unit(cost_term,
power_units,
base_power,
device_base_power)
for t in get_time_steps(container)
exp = _add_proportional_term!(container, T(), d, cost_term_normalized * multiplier,
t)
add_to_expression!(container, ProductionCostExpression, exp, d, t)
end
return
end
4 changes: 4 additions & 0 deletions src/devices_models/devices/thermal_generation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ function proportional_cost(container::OptimizationContainer, cost::PSY.ThermalGe
return onvar_cost(container, cost, S, T, U, t) + PSY.get_constant_term(PSY.get_vom_cost(PSY.get_variable(cost))) + PSY.get_fixed(cost)
end

function proportional_cost(container::OptimizationContainer, cost::PSY.MarketBidCost, S::OnVariable, T::PSY.ThermalGen, U::AbstractThermalFormulation, t::Int)
return proportional_cost(cost, S, T, U)
end

proportional_cost(cost::PSY.MarketBidCost, ::OnVariable, ::PSY.ThermalGen, ::AbstractThermalFormulation) = PSY.get_no_load_cost(cost)

proportional_cost(::Union{PSY.MarketBidCost, PSY.ThermalGenerationCost}, ::Union{RateofChangeConstraintSlackUp, RateofChangeConstraintSlackDown}, ::PSY.ThermalGen, ::AbstractThermalFormulation) = CONSTRAINT_VIOLATION_SLACK_COST
Expand Down
4 changes: 4 additions & 0 deletions src/utils/powersystems_utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -383,3 +383,7 @@ function get_deterministic_time_series_type(sys::PSY.System)
)
end
end


"""Overload get_variable for MarketBidCost. Returns nothing"""
PSY.get_variable(value::PSY.MarketBidCost) = nothing
Copy link
Contributor

@GabrielKS GabrielKS Jan 28, 2025

Choose a reason for hiding this comment

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

What is this for? If it is needed, it would ideally be put in PowerSystems. See here on "type piracy," better definition here.

Loading
Loading