Skip to content

Commit

Permalink
Introduce user_constraint_slack_penalty
Browse files Browse the repository at this point in the history
Re #781
  • Loading branch information
manuelma committed Oct 25, 2023
1 parent 16cafe9 commit 21ffbc8
Show file tree
Hide file tree
Showing 10 changed files with 254 additions and 50 deletions.
1 change: 1 addition & 0 deletions src/SpineOpt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ include("variables/variable_node_voltage_angle.jl")
include("variables/variable_binary_gas_connection_flow.jl")
include("variables/variable_sp_objective_upperbound.jl")
include("variables/variable_mp_min_res_gen_to_demand_ratio_slack.jl")
include("variables/variable_user_constraint_slack.jl")
include("objective/variable_om_costs.jl")
include("objective/fixed_om_costs.jl")
include("objective/taxes.jl")
Expand Down
97 changes: 68 additions & 29 deletions src/constraints/constraint_user_constraint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ function add_constraint_user_constraint!(m::Model)
storages_invested,
storages_invested_available,
connections_invested,
connections_invested_available
connections_invested_available,
user_constraint_slack_pos,
user_constraint_slack_neg
) = m.ext[:spineopt].variables
t0 = _analysis_time(m)
m.ext[:spineopt].constraints[:user_constraint] = Dict(
Expand Down Expand Up @@ -249,7 +251,13 @@ function add_constraint_user_constraint!(m::Model)
m; node=n, stochastic_scenario=s, t=t_in_t(m; t_long=t)
);
init=0,
),
)
+ expr_sum(
user_constraint_slack_pos[uc, s, t] - user_constraint_slack_neg[uc, s, t]
for (uc, s, t) in user_constraint_slack_indices(m; user_constraint=uc, stochastic_scenario=s, t=t);
init=0,
)
,
constraint_sense(user_constraint=uc),
+ expr_sum(
right_hand_side[(user_constraint=uc, stochastic_scenario=s, analysis_time=t0, t=t)] for s in s;
Expand All @@ -267,17 +275,7 @@ function constraint_user_constraint_indices(m::Model)
(user_constraint=uc, stochastic_path=path, t=t)
for uc in user_constraint()
for (t, path) in t_lowest_resolution_path(
m,
Iterators.flatten((
_constraint_user_constraint_unit_flow_indices(m, uc),
_constraint_user_constraint_units_on_indices(m, uc),
_constraint_user_constraint_connection_flow_indices(m, uc),
_constraint_user_constraint_node_state_indices(m, uc),
_constraint_user_constraint_node_stochastic_time_indices(m, uc),
_constraint_user_constraint_units_invested_indices(m, uc),
_constraint_user_constraint_connections_invested_indices(m, uc),
_constraint_user_constraint_storages_invested_indices(m, uc)
))
m, Iterators.flatten(user_constraint_all_indices(m; user_constraint=uc))
)
)
end
Expand All @@ -300,52 +298,93 @@ function constraint_user_constraint_indices_filtered(
filter(f, constraint_user_constraint_indices(m))
end

function _constraint_user_constraint_unit_flow_indices(m, uc)
function user_constraint_all_indices(
m::Model; user_constraint=anything, stochastic_scenario=anything, t=anything, temporal_block=anything
)
(
_user_constraint_unit_flow_indices(m, user_constraint, stochastic_scenario, t, temporal_block),
_user_constraint_units_on_indices(m, user_constraint, stochastic_scenario, t, temporal_block),
_user_constraint_connection_flow_indices(m, user_constraint, stochastic_scenario, t, temporal_block),
_user_constraint_node_state_indices(m, user_constraint, stochastic_scenario, t, temporal_block),
_user_constraint_node_stochastic_time_indices(m, user_constraint, stochastic_scenario, t, temporal_block),
_user_constraint_units_invested_indices(m, user_constraint, stochastic_scenario, t, temporal_block),
_user_constraint_connections_invested_indices(m, user_constraint, stochastic_scenario, t, temporal_block),
_user_constraint_storages_invested_indices(m, user_constraint, stochastic_scenario, t, temporal_block)
)
end

function _user_constraint_unit_flow_indices(m, uc, s, t, tb)
(
ind
for (unit__node__user_constraint, d) in (
(unit__from_node__user_constraint, :from_node), (unit__to_node__user_constraint, :to_node)
)
for (u, n) in unit__node__user_constraint(user_constraint=uc)
for ind in unit_flow_indices(m; unit=u, node=n, direction=direction(d))
for ind in unit_flow_indices(
m; unit=u, node=n, direction=direction(d), stochastic_scenario=s, t=t, temporal_block=tb
)
)
end

function _constraint_user_constraint_units_on_indices(m, uc)
(ind for u in unit__user_constraint(user_constraint=uc) for ind in units_on_indices(m; unit=u))
function _user_constraint_units_on_indices(m, uc, s, t, tb)
(
ind
for u in unit__user_constraint(user_constraint=uc)
for ind in units_on_indices(m; unit=u, stochastic_scenario=s, t=t, temporal_block=tb)
)
end

function _constraint_user_constraint_connection_flow_indices(m, uc)
function _user_constraint_connection_flow_indices(m, uc, s, t, tb)
(
ind
for (connection__node__user_constraint, d) in (
(connection__from_node__user_constraint, :from_node), (connection__to_node__user_constraint, :to_node)
)
for (c, n) in connection__node__user_constraint(user_constraint=uc)
for ind in connection_flow_indices(m; connection=c, node=n, direction=direction(d))
for ind in connection_flow_indices(
m; connection=c, node=n, direction=direction(d), stochastic_scenario=s, t=t, temporal_block=tb
)
)
end

function _constraint_user_constraint_node_state_indices(m, uc)
(ind for n in node__user_constraint(user_constraint=uc) for ind in node_state_indices(m; node=n))
function _user_constraint_node_state_indices(m, uc, s, t, tb)
(
ind
for n in node__user_constraint(user_constraint=uc)
for ind in node_state_indices(m; node=n, stochastic_scenario=s, t=t, temporal_block=tb)
)
end

function _constraint_user_constraint_units_invested_indices(m, uc)
(ind for u in unit__user_constraint(user_constraint=uc) for ind in units_invested_available_indices(m; unit=u))
function _user_constraint_units_invested_indices(m, uc, s, t, tb)
(
ind
for u in unit__user_constraint(user_constraint=uc)
for ind in units_invested_available_indices(m; unit=u, stochastic_scenario=s, t=t, temporal_block=tb)
)
end

function _constraint_user_constraint_connections_invested_indices(m, uc)
function _user_constraint_connections_invested_indices(m, uc, s, t, tb)
(
ind
for c in connection__user_constraint(user_constraint=uc)
for ind in connections_invested_available_indices(m; connection=c)
for ind in connections_invested_available_indices(
m; connection=c, stochastic_scenario=s, t=t, temporal_block=tb
)
)
end

function _constraint_user_constraint_storages_invested_indices(m, uc)
(ind for n in node__user_constraint(user_constraint=uc) for ind in storages_invested_available_indices(m; node=n))
function _user_constraint_storages_invested_indices(m, uc, s, t, tb)
(
ind
for n in node__user_constraint(user_constraint=uc)
for ind in storages_invested_available_indices(m; node=n, stochastic_scenario=s, t=t, temporal_block=tb)
)
end

function _constraint_user_constraint_node_stochastic_time_indices(m, uc)
(ind for n in node__user_constraint(user_constraint=uc) for ind in node_stochastic_time_indices(m; node=n))
function _user_constraint_node_stochastic_time_indices(m, uc, s, t, tb)
(
ind
for n in node__user_constraint(user_constraint=uc)
for ind in node_stochastic_time_indices(m; node=n, stochastic_scenario=s, t=t, temporal_block=tb)
)
end
33 changes: 27 additions & 6 deletions src/data_structure/stochastic_structure.jl
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,23 @@ function _stochastic_scenarios(m::Model, stoch_struct::Object, t::TimeSlice, sce
intersect(scenario_lookup[stoch_struct, t], scenarios)
end

"""
_generate_any_stochastic_scenario_weight(m::Model, all_stochastic_dags::Dict)
Generate the `any_stochastic_scenario_weight` parameter for the `model` for easier access to the scenario weights.
"""
function _generate_any_stochastic_scenario_weight(m::Model, all_stochastic_dags::Dict)
any_stochastic_scenario_weight_values = Dict(
scen => Dict(:any_stochastic_scenario_weight => parameter_value(spec.weight))
for ss in model__stochastic_structure(model=m.ext[:spineopt].instance)
for (scen, spec) in all_stochastic_dags[ss]
)
add_object_parameter_values!(stochastic_scenario, any_stochastic_scenario_weight_values)
m.ext[:spineopt].stochastic_structure[:any_stochastic_scenario_weight] = Parameter(
:any_stochastic_scenario_weight, [stochastic_scenario]
)
end

"""
_generate_node_stochastic_scenario_weight(m::Model, all_stochastic_dags::Dict)
Expand All @@ -165,8 +182,7 @@ function _generate_node_stochastic_scenario_weight(m::Model, all_stochastic_dags
node_stochastic_scenario_weight_values,
)
m.ext[:spineopt].stochastic_structure[:node_stochastic_scenario_weight] = Parameter(
:node_stochastic_scenario_weight,
[node__stochastic_scenario],
:node_stochastic_scenario_weight, [node__stochastic_scenario]
)
end

Expand All @@ -189,8 +205,7 @@ function _generate_unit_stochastic_scenario_weight(m::Model, all_stochastic_dags
unit_stochastic_scenario_weight_values,
)
m.ext[:spineopt].stochastic_structure[:unit_stochastic_scenario_weight] = Parameter(
:unit_stochastic_scenario_weight,
[unit__stochastic_scenario],
:unit_stochastic_scenario_weight, [unit__stochastic_scenario]
)
end

Expand All @@ -213,8 +228,7 @@ function _generate_connection_stochastic_scenario_weight(m::Model, all_stochasti
connection_stochastic_scenario_weight_values,
)
m.ext[:spineopt].stochastic_structure[:connection_stochastic_scenario_weight] = Parameter(
:connection_stochastic_scenario_weight,
[connection__stochastic_scenario],
:connection_stochastic_scenario_weight, [connection__stochastic_scenario]
)
end

Expand Down Expand Up @@ -255,6 +269,7 @@ function generate_stochastic_structure!(m::Model)
_generate_node_stochastic_scenario_weight(m, all_stochastic_dags)
_generate_unit_stochastic_scenario_weight(m, all_stochastic_dags)
_generate_connection_stochastic_scenario_weight(m, all_stochastic_dags)
_generate_any_stochastic_scenario_weight(m, all_stochastic_dags)
_generate_active_stochastic_paths(m)
end

Expand Down Expand Up @@ -436,9 +451,15 @@ end
function node_stochastic_scenario_weight(m; kwargs...)
m.ext[:spineopt].stochastic_structure[:node_stochastic_scenario_weight][(; kwargs...)]
end

function unit_stochastic_scenario_weight(m; kwargs...)
m.ext[:spineopt].stochastic_structure[:unit_stochastic_scenario_weight][(; kwargs...)]
end

function connection_stochastic_scenario_weight(m; kwargs...)
m.ext[:spineopt].stochastic_structure[:connection_stochastic_scenario_weight][(; kwargs...)]
end

function any_stochastic_scenario_weight(m; kwargs...)
m.ext[:spineopt].stochastic_structure[:any_stochastic_scenario_weight][(; kwargs...)]
end
15 changes: 13 additions & 2 deletions src/objective/objective_penalties.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ Create an expression for objective penalties.
"""
# TODO: find a better name for this; objective penalities is not self-speaking
function objective_penalties(m::Model, t_range)
@fetch node_slack_pos, node_slack_neg = m.ext[:spineopt].variables
@fetch (
node_slack_pos, node_slack_neg, user_constraint_slack_pos, user_constraint_slack_neg
) = m.ext[:spineopt].variables
t0 = _analysis_time(m)
@expression(
m,
expr_sum(
+ expr_sum(
(node_slack_neg[n, s, t] + node_slack_pos[n, s, t])
* duration(t)
* prod(weight(temporal_block=blk) for blk in blocks(t))
Expand All @@ -37,5 +39,14 @@ function objective_penalties(m::Model, t_range)
for (n, s, t) in node_slack_indices(m; t=t_range);
init=0,
)
+ expr_sum(
(user_constraint_slack_neg[uc, s, t] + user_constraint_slack_pos[uc, s, t])
* duration(t)
* prod(weight(temporal_block=blk) for blk in blocks(t))
* user_constraint_slack_penalty[(user_constraint=uc, stochastic_scenario=s, analysis_time=t0, t=t)]
* any_stochastic_scenario_weight(m; stochastic_scenario=s)
for (uc, s, t) in user_constraint_slack_indices(m; t=t_range);
init=0,
)
)
end
2 changes: 2 additions & 0 deletions src/run_spineopt_standard.jl
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ function _add_variables!(m; add_user_variables=m -> nothing, log_level=3)
add_variable_node_pressure!,
add_variable_node_voltage_angle!,
add_variable_binary_gas_connection_flow!,
add_variable_user_constraint_slack_pos!,
add_variable_user_constraint_slack_neg!,
)
name = name_from_fn(add_variable!)
@timelog log_level 3 "- [$name]" add_variable!(m)
Expand Down
6 changes: 2 additions & 4 deletions src/variables/variable_node_slack_pos.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@

"""
node_state_indices(filtering_options...)
A set of tuples for indexing the `node_state` variable. Any filtering options can be specified
for `node`, `s`, and `t`.
"""
function node_slack_indices(
m::Model;
Expand All @@ -32,7 +29,8 @@ function node_slack_indices(
)
inds = NamedTuple{(:node, :stochastic_scenario, :t),Tuple{Object,Object,TimeSlice}}[
(node=n, stochastic_scenario=s, t=t)
for n in intersect(node_with_slack_penalty(), node) for (n, s, t) in node_stochastic_time_indices(
for n in intersect(node_with_slack_penalty(), node)
for (n, s, t) in node_stochastic_time_indices(
m; node=n, stochastic_scenario=stochastic_scenario, t=t, temporal_block=temporal_block
)
]
Expand Down
53 changes: 53 additions & 0 deletions src/variables/variable_user_constraint_slack.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#############################################################################
# Copyright (C) 2017 - 2023 Spine Project
#
# This file is part of SpineOpt.
#
# SpineOpt is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# SpineOpt is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#############################################################################

"""
user_constraint_slack_indices(filtering_options...)
"""
function user_constraint_slack_indices(
m::Model;
user_constraint=anything,
stochastic_scenario=anything,
t=anything,
temporal_block=temporal_block(representative_periods_mapping=nothing),
)
inds = NamedTuple{(:user_constraint, :stochastic_scenario, :t),Tuple{Object,Object,TimeSlice}}[
(user_constraint=uc, stochastic_scenario=ind.stochastic_scenario, t=ind.t)
for uc in indices(user_constraint_slack_penalty; user_constraint=user_constraint)
for inds in user_constraint_all_indices(
m; user_constraint=uc, stochastic_scenario=stochastic_scenario, t=t, temporal_block=temporal_block
)
for ind in inds
]
unique!(inds)
end

"""
add_variable_user_constraint_slack_pos!(m::Model)
"""
function add_variable_user_constraint_slack_pos!(m::Model)
add_variable!(m, :user_constraint_slack_pos, user_constraint_slack_indices; lb=Constant(0))
end

"""
add_variable_user_constraint_slack_neg!(m::Model)
"""
function add_variable_user_constraint_slack_neg!(m::Model)
add_variable!(m, :user_constraint_slack_neg, user_constraint_slack_indices; lb=Constant(0))
end
3 changes: 2 additions & 1 deletion templates/spineopt_template.json
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,8 @@
["unit", "is_renewable", false, "boolean_value_list", "Whether the unit is renewable - used in the minimum renewable generation constraint within the Benders master problem"],
["user_constraint", "constraint_sense", "==", "constraint_sense_list", "A selector for the sense of the `user_constraint`."],
["user_constraint", "is_active", true, "boolean_value_list", "If false, the object is excluded from the model if the tool filter object activity control is specified"],
["user_constraint", "right_hand_side", 0.0, null, "The right-hand side, constant term in a `user_constraint`. Can be time-dependent and used e.g. for complicated efficiency approximations."]
["user_constraint", "right_hand_side", 0.0, null, "The right-hand side, constant term in a `user_constraint`. Can be time-dependent and used e.g. for complicated efficiency approximations."],
["user_constraint", "user_constraint_slack_penalty", null, null, "A penalty for violating a user constraint."]
],
"relationship_parameters": [
["connection__from_node", "connection_capacity", null, null, "Limits the `connection_flow` variable from the `from_node`. `from_node` can be a group of `nodes`, in which case the sum of the `connection_flow` is constrained."],
Expand Down
Loading

0 comments on commit 21ffbc8

Please sign in to comment.