diff --git a/src/constraints/constraint_common.jl b/src/constraints/constraint_common.jl index 6555b20fe9..25ca036abd 100644 --- a/src/constraints/constraint_common.jl +++ b/src/constraints/constraint_common.jl @@ -51,6 +51,23 @@ function t_lowest_resolution_path(m, indices, extra_indices...) ((t, path) for (t, scens) in scens_by_t for path in active_stochastic_paths(m, scens)) end +function t_highest_resolution_path(m, indices, extra_indices...) + isempty(indices) && return () + if length(stochastic_scenario()) == 1 + s = only(stochastic_scenario()) + return ((t, [s]) for t in t_highest_resolution!(m, unique(x.t for x in indices))) + end + scens_by_t = t_highest_resolution_sets!(m, _scens_by_t(indices)) + extra_scens_by_t = _scens_by_t(Iterators.flatten(extra_indices)) + for (t, scens) in scens_by_t + for t_short in t_in_t(m; t_short=t) + union!(scens, get(extra_scens_by_t, t_short, ())) + end + end + ((t, path) for (t, scens) in scens_by_t for path in active_stochastic_paths(m, scens)) +end + + function _popfirst!(arr, default) try popfirst!(arr) catch default end end diff --git a/src/constraints/constraint_ratio_out_in_connection_flow.jl b/src/constraints/constraint_ratio_out_in_connection_flow.jl index 8263414101..0628d82c88 100644 --- a/src/constraints/constraint_ratio_out_in_connection_flow.jl +++ b/src/constraints/constraint_ratio_out_in_connection_flow.jl @@ -58,14 +58,14 @@ function _build_constraint_ratio_out_in_connection_flow(m::Model, conn, ng_out, @fetch connection_flow = m.ext[:spineopt].variables build_sense_constraint( + sum( - + connection_flow[conn, n_out, d, s, t_short] * duration(t_short) + + connection_flow[conn, n_out, d, s, t_short] for (conn, n_out, d, s, t_short) in connection_flow_indices( m; connection=conn, node=ng_out, direction=direction(:to_node), stochastic_scenario=s_path, - t=t_in_t(m; t_long=t), + t=t_in_t(m; t_short=t), ); init=0, ), @@ -73,8 +73,7 @@ function _build_constraint_ratio_out_in_connection_flow(m::Model, conn, ng_out, + sum( + connection_flow[conn, n_in, d, s_past, t_past] * ratio_out_in(m; connection=conn, node1=ng_out, node2=ng_in, stochastic_scenario=s_past, t=t_past) - * weight - for (conn, n_in, d, s_past, t_past, weight) in _past_connection_input_flow_indices( + for (conn, n_in, d, s_past, t_past) in _past_connection_input_flow_indices( m, conn, ng_out, ng_in, s_path, t ); init=0, @@ -111,17 +110,16 @@ end function constraint_ratio_out_in_connection_flow_indices(m::Model, ratio_out_in) ( - (connection=conn, node1=ng_out, node2=ng_in, stochastic_path=path, t=t) + (connection=conn, node1=ng_out, node2=ng_in, stochastic_path=path, t=t) for (conn, ng_out, ng_in) in indices(ratio_out_in) if !_has_simple_fix_ratio_out_in_connection_flow(conn, ng_out, ng_in) - for (t, path_out) in t_lowest_resolution_path( + for (t_out, path_out) in t_highest_resolution_path( m, connection_flow_indices(m; connection=conn, node=ng_out, direction=direction(:to_node)) ) - for path in active_stochastic_paths( - m, - Iterators.flatten( + for (t, path) in t_highest_resolution_path( + m, Iterators.flatten( ( - ((stochastic_scenario=s,) for s in path_out), + connection_flow_indices(m; connection=conn, node=ng_out, direction=direction(:to_node)), ( ind for s in path_out @@ -130,20 +128,20 @@ function constraint_ratio_out_in_connection_flow_indices(m::Model, ratio_out_in) connection=conn, node=ng_in, direction=direction(:from_node), - t=to_time_slice(m; t=_t_look_behind(conn, ng_out, ng_in, (s,), t)), + t=to_time_slice(m; t=_t_look_behind(conn, ng_out, ng_in, (s,), t_out)), temporal_block=anything, ) ), ) ) - ) + ) ) end function _past_connection_input_flow_indices(m, conn, ng_out, ng_in, s_path, t) t_look_behind = _t_look_behind(conn, ng_out, ng_in, s_path, t) ( - (; ind..., weight=overlap_duration(ind.t, t_look_behind)) + (; ind...) for ind in connection_flow_indices( m; connection=conn, diff --git a/test/constraints/constraint_connection.jl b/test/constraints/constraint_connection.jl index 03132a7aff..9e42865096 100644 --- a/test/constraints/constraint_connection.jl +++ b/test/constraints/constraint_connection.jl @@ -862,8 +862,8 @@ function test_contraints_ptdf_lodf_duration() end end -function test_constraint_ratio_out_in_connection_flow() - @testset "constraint_ratio_out_in_connection_flow" begin +function test_constraint_ratio_out_in_connection_flow_highest() + @testset "constraint_ratio_out_in_connection_flow_highest" begin flow_ratio = 0.8 model_end = Dict("type" => "date_time", "data" => "2000-01-01T04:00:00") class = "connection__node__node" @@ -871,61 +871,57 @@ function test_constraint_ratio_out_in_connection_flow() object_parameter_values = [["model", "instance", "model_end", model_end]] relationships = [[class, relationship]] senses_by_prefix = Dict("min" => >=, "fix" => ==, "max" => <=) - @testset for conn_flow_minutes_delay in (150, 180, 225) - connection_flow_delay = Dict("type" => "duration", "data" => string(conn_flow_minutes_delay, "m")) - h_delay = div(conn_flow_minutes_delay, 60) - rem_minutes_delay = (conn_flow_minutes_delay % 60) / 60 - @testset for p in ("min", "fix", "max") - url_in = _test_constraint_connection_setup() - sense = senses_by_prefix[p] - ratio = string(p, "_ratio_out_in_connection_flow") - relationship_parameter_values = [ - [class, relationship, "connection_flow_delay", connection_flow_delay], - [class, relationship, ratio, flow_ratio], - ] - SpineInterface.import_data( - url_in; - relationships=relationships, - object_parameter_values=object_parameter_values, - relationship_parameter_values=relationship_parameter_values, + h_delay = 2 + connection_flow_delay = Dict("type" => "duration", "data" => string(h_delay, "h")) + @testset for p in ("min", "fix", "max") + p = "fix" + url_in = _test_constraint_connection_setup() + sense = senses_by_prefix[p] + ratio = string(p, "_ratio_out_in_connection_flow") + relationship_parameter_values = [ + [class, relationship, "connection_flow_delay", connection_flow_delay], + [class, relationship, ratio, flow_ratio], + ] + SpineInterface.import_data( + url_in; + relationships=relationships, + object_parameter_values=object_parameter_values, + relationship_parameter_values=relationship_parameter_values, + ) + m = run_spineopt(url_in; log_level=0, optimize=false) + var_connection_flow = m.ext[:spineopt].variables[:connection_flow] + constraint = m.ext[:spineopt].constraints[Symbol(ratio)] + + @test length(constraint) == 4 + conn = connection(:connection_ab) + n_from = node(:node_a) + n_to = node(:node_b) + d_from = direction(:from_node) + d_to = direction(:to_node) + scenarios_from = [repeat([stochastic_scenario(:child)], 3); repeat([stochastic_scenario(:parent)], 3)] + time_slices_from = [ + reverse(time_slice(m; temporal_block=temporal_block(:hourly))) + reverse(history_time_slice(m; temporal_block=temporal_block(:hourly))) + ] + time_slices_to = reverse(time_slice(m; temporal_block=temporal_block(:two_hourly))) + s_to = stochastic_scenario(:parent) + @testset for (j, t_con) in enumerate(reverse(time_slice(m; temporal_block=temporal_block(:hourly)))) + s_from = scenarios_from[h_delay+j] # get the scenario before the delay + t_from = time_slices_from[h_delay+j] # get the time slice before the delay + + var_conn_flow_from = var_connection_flow[conn, n_from, d_from, s_from, t_from] + t_to = time_slices_to[(j+1)รท2] + var_conn_flow_to = var_connection_flow[conn, n_to, d_to, s_to, t_to] + + expected_con = SpineOpt.build_sense_constraint( + var_conn_flow_to, + sense, + flow_ratio * var_conn_flow_from, ) - m = run_spineopt(url_in; log_level=0, optimize=false) - var_connection_flow = m.ext[:spineopt].variables[:connection_flow] - constraint = m.ext[:spineopt].constraints[Symbol(ratio)] - @test length(constraint) == 2 - conn = connection(:connection_ab) - n_from = node(:node_a) - n_to = node(:node_b) - d_from = direction(:from_node) - d_to = direction(:to_node) - scenarios_from = [repeat([stochastic_scenario(:child)], 3); repeat([stochastic_scenario(:parent)], 5)] - time_slices_from = [ - reverse(time_slice(m; temporal_block=temporal_block(:hourly))) - reverse(history_time_slice(m; temporal_block=temporal_block(:hourly))) - ] - s_to = stochastic_scenario(:parent) - @testset for (j, t_to) in enumerate(reverse(time_slice(m; temporal_block=temporal_block(:two_hourly)))) - coeffs = (1 - rem_minutes_delay, 1, rem_minutes_delay) - i = 2 * j - 1 - a = i + h_delay - b = min(a + 2, length(time_slices_from)) - s_set = scenarios_from[a:b] - t_set = time_slices_from[a:b] - vars_conn_flow_from = ( - var_connection_flow[conn, n_from, d_from, s_from, t_from] - for (s_from, t_from) in zip(s_set, t_set) - ) - var_conn_flow_to = var_connection_flow[conn, n_to, d_to, s_to, t_to] - expected_con = SpineOpt.build_sense_constraint( - 2 * var_conn_flow_to, - sense, - flow_ratio * sum(c * v for (c, v) in zip(coeffs, vars_conn_flow_from)), - ) - path = reverse(unique(s_set)) - con_key = (conn, n_to, n_from, path, t_to) - observed_con = constraint_object(constraint[con_key...]) - @test _is_constraint_equal(observed_con, expected_con) - end + path = unique([s_to; s_from]) + con_key = (conn, n_to, n_from, path, t_con) + observed_con = constraint_object(constraint[con_key...]) + @test _is_constraint_equal(observed_con, expected_con) end end end @@ -1751,7 +1747,7 @@ end test_constraint_connection_intact_flow_ptdf() test_constraint_connection_flow_lodf() test_contraints_ptdf_lodf_duration() - test_constraint_ratio_out_in_connection_flow() + test_constraint_ratio_out_in_connection_flow_highest() test_constraint_connections_invested_transition() test_constraint_connections_invested_transition_mp() test_constraint_connection_lifetime() diff --git a/test/run_examples.jl b/test/run_examples.jl index 1c258f1daf..beff7500fb 100644 --- a/test/run_examples.jl +++ b/test/run_examples.jl @@ -5,6 +5,11 @@ objective_function_reference_values = Dict( "simple_system.json" => 160714.28571428574, "unit_commitment.json" => 98637.42857142858, "rolling_horizon.json" => 65164.8571429, + "multi-year_investment_with_econ_params_with_milestones" => 41025092.819187716, + "multi-year_investment_with_econ_params_without_milestones" => 71720404.84378022, + "multi-year_investment_without_econ_params" => 203360142.85714287, + "capacity_planning" => 352127000, + "stochastic" => 126964.2857142857, ) @testset for path in readdir(joinpath(dirname(@__DIR__), "examples"); join=true) diff --git a/test/runtests.jl b/test/runtests.jl index e326d357e3..5274ed5d03 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -131,9 +131,9 @@ function _dismember_function(func) end @testset begin - include("data_structure/check_economic_structure.jl") include("data_structure/migration.jl") include("data_structure/check_data_structure.jl") + include("data_structure/check_economic_structure.jl") include("data_structure/preprocess_data_structure.jl") include("data_structure/temporal_structure.jl") include("data_structure/stochastic_structure.jl")