Skip to content

Commit

Permalink
Fix tests
Browse files Browse the repository at this point in the history
  • Loading branch information
manuelma committed Sep 26, 2024
1 parent d1b6046 commit 4075915
Show file tree
Hide file tree
Showing 6 changed files with 37 additions and 86 deletions.
79 changes: 14 additions & 65 deletions src/benders.jl
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,9 @@ function _init_benders_invested_available!(m_mp, m)
end

function process_master_problem_solution(m_mp, m)
_do_save_outputs!(m_mp, (:units_invested_available, :connections_invested_available, :storages_invested_available))
_do_save_outputs!(
m_mp, (:units_invested_available, :connections_invested_available, :storages_invested_available), (;)
)
_update_benders_invested_available!(m_mp)
end

Expand All @@ -206,6 +208,8 @@ function _update_benders_invested_available!(m_mp)
end

function process_subproblem_solution(m, k)
win_weight = window_weight(model=m.ext[:spineopt].instance, i=k, _strict=false)
win_weight = win_weight !== nothing ? win_weight : 1.0
_wait_for_dual_solves(m)
_do_save_outputs!(
m,
Expand All @@ -214,81 +218,27 @@ function process_subproblem_solution(m, k)
:bound_connections_invested_available,
:bound_storages_invested_available,
:unit_flow,
)
),
(;);
weight=win_weight,
)
if k == 1
m.ext[:spineopt].extras[:sp_objective_value_bi] = 0
end
m.ext[:spineopt].extras[:sp_objective_value_bi] += sum(values(m.ext[:spineopt].values[:total_costs]))
m.ext[:spineopt].extras[:sp_objective_value_bi] += win_weight * sum(values(m.ext[:spineopt].values[:total_costs]))
if _is_last_window(m, k)
m.ext[:spineopt].extras[:sp_objective_value_bi] += sum(values(m.ext[:spineopt].values[:total_costs_tail]))
m.ext[:spineopt].extras[:sp_objective_value_bi] += (
win_weight * sum(values(m.ext[:spineopt].values[:total_costs_tail]))
)
end
end

"""
_pval_by_entity(vals)
Take the given Dict, which should be a mapping from variable indices to their value,
and return another Dict mapping entities to `ParameterValue`s.
The keys in the result are the keys of the input, without the stochastic_scenario and the t (i.e., just the entity).
The values are `ParameterValue{Map}`s mapping the `stochastic_scenario` of the variable key,
to a `TimeSeries` mapping the `t` of the key, to the 'realized' variable value.
"""
function _pval_by_entity(vals, t_end=nothing)
by_ent = Dict()
for (ind, val) in vals
ent = _drop_key(ind, :stochastic_scenario, :t)
by_s = get!(by_ent, ent, Dict())
by_t = get!(by_s, ind.stochastic_scenario, Dict())
realized_val = realize(val)
if t_end !== nothing && t_end < end_(ind.t)
realized_val *= (t_end - start(ind.t)) / (end_(ind.t) - start(ind.t))
end
by_t[ind.t] = realized_val
end
Dict(
ent => parameter_value(Map(collect(keys(by_s)), [_window_time_series(by_t) for by_t in values(by_s)]))
for (ent, by_s) in by_ent
)
end

"""
_window_time_series(by_t)
A `TimeSeries` from the given `Dict` mapping `TimeSlice` to `Float64`, with an explicit NaN at the end.
The NaN is there because we want to merge marginal values from different windows of the Benders subproblem
into one `TimeSeries`.
Without the NaN, the last value of one window would apply until the next window, which wouldn't be correct
if there were gaps between the windows (as in rolling representative periods Benders).
With the NaN, the gap is correctly skipped in the Benders cuts.
"""
function _window_time_series(by_t)
time_slices, vals = collect(keys(by_t)), collect(values(by_t))
inds = start.(time_slices)
push!(inds, maximum(end_.(time_slices)))
push!(vals, NaN)
TimeSeries(inds, vals)
end

function _is_last_window(m, k)
k == m.ext[:spineopt].temporal_structure[:window_count]
end

function _save_sp_unit_flow(m)
window_values = Dict(
k => v for (k, v) in m.ext[:spineopt].values[:unit_flow] if iscontained(k.t, current_window(m))
)
pval_by_ent = _pval_by_entity(window_values)
pvals_to_node = Dict(
ent => Dict(:sp_unit_flow => pval) for (ent, pval) in pval_by_ent if ent.direction == direction(:to_node)
)
pvals_from_node = Dict(
ent => Dict(:sp_unit_flow => pval) for (ent, pval) in pval_by_ent if ent.direction == direction(:from_node)
)
add_relationship_parameter_values!(unit__to_node, pvals_to_node; merge_values=true)
add_relationship_parameter_values!(unit__from_node, pvals_from_node; merge_values=true)
function _val_by_ent(m, var_name)
_output_value_by_entity(m.ext[:spineopt].outputs[var_name], model_end(model=m.ext[:spineopt].instance))
end

function save_mp_objective_bounds_and_gap!(m_mp, m)
Expand Down Expand Up @@ -325,4 +275,3 @@ function _collect_outputs!(
save_outputs=true,
)
end

4 changes: 0 additions & 4 deletions src/constraints/constraint_mp_any_invested_cuts.jl
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,6 @@ function add_constraint_mp_any_invested_cuts!(m_mp, m)
)
end

function _val_by_ent(m, var_name)
_output_value_by_entity(m.ext[:spineopt].outputs[var_name], model_end(model=m.ext[:spineopt].instance))
end

function window_sum(ts::TimeSeries, window; init=0)
sum(v for (t, v) in ts if iscontained(t, window) && !isnan(v); init=init)
end
19 changes: 16 additions & 3 deletions src/constraints/constraint_mp_min_res_gen_to_demand_ratio_cuts.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,17 @@ sum (
"""
function add_constraint_mp_min_res_gen_to_demand_ratio_cuts!(m_mp, m)
@fetch units_invested_available, mp_min_res_gen_to_demand_ratio_slack = m_mp.ext[:spineopt].variables
benders_units_invested_available = m_mp.ext[:spineopt].downstream_outputs[:units_invested_available]
sp_unit_flow = _val_by_ent(m, :unit_flow)
merge!(
get!(m_mp.ext[:spineopt].constraints, :mp_min_res_gen_to_demand_ratio_cuts, Dict()),
Dict(
(benders_iteration=bi, commodity=comm) => @constraint(
m_mp,
+ sum(
window_sum_duration(m_mp, sp_unit_flow(unit=u, node=n, direction=d, stochastic_scenario=s), window)
window_sum_duration(
m_mp, sp_unit_flow[(unit=u, node=n, direction=d, stochastic_scenario=s)], window
)
for window in m_mp.ext[:spineopt].temporal_structure[:sp_windows]
for (u, s) in _unit_scenario(unit(is_renewable=true))
for (u, n, d) in unit__to_node(unit=u, node=node__commodity(commodity=comm), _compact=false);
Expand All @@ -43,7 +47,7 @@ function add_constraint_mp_min_res_gen_to_demand_ratio_cuts!(m_mp, m)
+ sum(
sum(
+ units_invested_available[u, s, t]
- internal_fix_units_invested_available(unit=u, stochastic_scenario=s, t=t, _default=0)
- benders_units_invested_available[(unit=u, stochastic_scenario=s)](t=t)
for (u, s, t) in units_invested_available_indices(
m_mp; unit=u, stochastic_scenario=s, t=to_time_slice(m_mp; t=window)
);
Expand Down Expand Up @@ -106,4 +110,13 @@ function _unit_scenario(unit)
for (u, ss) in units_on__stochastic_structure(unit=unit, _compact=false)
for s in stochastic_structure__stochastic_scenario(stochastic_structure=ss)
)
end
end

function window_sum_duration(m, ts::TimeSeries, window; init=0)
dur_unit = _model_duration_unit(m.ext[:spineopt].instance)
time_slice_value_iter = (
(TimeSlice(t1, t2; duration_unit=dur_unit), v) for (t1, t2, v) in zip(ts.indexes, ts.indexes[2:end], ts.values)
)
sum(v * duration(t) for (t, v) in time_slice_value_iter if iscontained(start(t), window) && !isnan(v); init=init)
end
window_sum_duration(m, x::Number, window; init=0) = x * duration(window) + init
4 changes: 2 additions & 2 deletions src/run_spineopt_basic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -849,7 +849,7 @@ function _save_outputs!(m, output_suffix)
_do_save_outputs!(m, _output_names(m), output_suffix)
end

function _do_save_outputs!(m, output_names, output_suffix=(;))
function _do_save_outputs!(m, output_names, output_suffix; weight=1)
w_start, w_end = start(current_window(m)), end_(current_window(m))
for out_name in output_names
value = get(m.ext[:spineopt].values, out_name, nothing)
Expand All @@ -861,7 +861,7 @@ function _do_save_outputs!(m, output_names, output_suffix=(;))
by_suffix = get!(m.ext[:spineopt].outputs, out_name, Dict())
by_window = get!(by_suffix, output_suffix, Dict())
by_window[w_start, w_end] = Dict(
_static(ind) => val for (ind, val) in _output_value_by_ind(m, something(value, param))
_static(ind) => weight * val for (ind, val) in _output_value_by_ind(m, something(value, param))
)
end
end
Expand Down
9 changes: 0 additions & 9 deletions src/util/misc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -163,15 +163,6 @@ function print_solution(m, variable_patterns...)
println()
end

function window_sum_duration(m, ts::TimeSeries, window; init=0)
dur_unit = _model_duration_unit(m.ext[:spineopt].instance)
time_slice_value_iter = (
(TimeSlice(t1, t2; duration_unit=dur_unit), v) for (t1, t2, v) in zip(ts.indexes, ts.indexes[2:end], ts.values)
)
sum(v * duration(t) for (t, v) in time_slice_value_iter if iscontained(start(t), window) && !isnan(v); init=init)
end
window_sum_duration(m, x::Number, window; init=0) = x * duration(window) + init

"""
align_variable_duration_unit(_duration::Union{Period, Nothing}, dt::DateTime; ahead::Bool=true)
Expand Down
8 changes: 5 additions & 3 deletions src/util/promise.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ struct ReducedCostPromise <: AbstractPromise
value::JuMP.VariableRef
end

realize(x::DualPromise) = has_duals(owner_model(x.value)) ? dual(x.value) : 0.0
realize(x::ReducedCostPromise) = has_duals(owner_model(x.value)) ? reduced_cost(x.value) : 0.0
realize(x::DualPromise, upd=nothing) = has_duals(owner_model(x.value)) ? dual(x.value) : 0.0
realize(x::ReducedCostPromise, upd=nothing) = has_duals(owner_model(x.value)) ? reduced_cost(x.value) : 0.0

Base.:+(x::X, y::Y) where {X<:AbstractPromise,Y<:AbstractPromise} = Call(+, x, y)
Base.:*(x, y::AbstractPromise) = Call(*, x, y)

Base.:+(x::AbstractPromise, y::AbstractPromise) = Call(+, x, y)

Check warning on line 37 in src/util/promise.jl

View check run for this annotation

Codecov / codecov/patch

src/util/promise.jl#L37

Added line #L37 was not covered by tests

0 comments on commit 4075915

Please sign in to comment.