-
Notifications
You must be signed in to change notification settings - Fork 67
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
base: market_bid_cost
Are you sure you want to change the base?
Changes from all commits
cc627b1
a241db0
8dd0f79
a631516
4d40be9
6836f7d
c3c27c2
e05a616
d8bdcc7
c965a3d
70b0f02
d560f3d
c785199
5c58681
2f20ccd
558e956
04ca469
931a1ee
2b7ac7d
e737acb
6106f7a
1af8948
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -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, | ||
|
@@ -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) | ||
|
@@ -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, | ||
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 | ||
""" | ||
|
@@ -275,6 +314,20 @@ end | |
######## MarketBidCost: Fixed Curves ########## | ||
############################################### | ||
|
||
""" | ||
Check if deceremental pwl offer curve is monotonically decreasing. | ||
""" | ||
function _is_convex_decremental(pwl:: PSY.PiecewiseStepData) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ideally this function would go in the same place as the existing |
||
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 | ||
|
@@ -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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 ########## | ||
################################################## | ||
|
@@ -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 | ||
#= | ||
|
@@ -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, | ||
|
@@ -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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the difference between these two |
||
::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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
There was a problem hiding this comment.
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?