Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Fixed

- Stop myopic multistage runs immediately when a stage has no primal solution,
reporting the solver statuses instead of failing in a later stage (#918).

## [0.4.6] - 2026-01-06

### Fixed
Expand Down
15 changes: 13 additions & 2 deletions src/multi_stage/myopic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ returns:
* models\_d – Dictionary which contains a JuMP model for each model stage, modified by this method.
* inputs\_d – Dictionary of inputs for each model stage, generated by the load\_inputs() method, modified by this method.
"""
function _assert_myopic_stage_has_solution(model::Model, stage::Integer)
has_values(model) && return nothing

error(
"Myopic multistage stage $stage has no primal solution " *
"(termination_status=$(termination_status(model)), " *
"primal_status=$(primal_status(model)), raw_status=$(raw_status(model))). " *
"Aborting before subsequent stages.",
)
end

function run_myopic_multistage(outpath::AbstractString, models_d::Dict, setup::Dict, inputs_d::Dict)
settings_d = setup["MultiStageSettingsDict"]
@assert settings_d["Myopic"] == 1 "run_myopic_multistage is only valid for myopic models. update your settings to have Myopic: 1."
Expand Down Expand Up @@ -49,6 +60,7 @@ function run_myopic_multistage(outpath::AbstractString, models_d::Dict, setup::D
# Solve the model at time t
@objective(models_d[t], Min, models_d[t][:eObj])
models_d[t], inputs_d[t]["solve_time"] = solve_model(models_d[t], setup)
_assert_myopic_stage_has_solution(models_d[t], t)

if write_intermittent_outputs
outpath_cur = joinpath(outpath, "results_p$t")
Expand All @@ -57,5 +69,4 @@ function run_myopic_multistage(outpath::AbstractString, models_d::Dict, setup::D
end

return models_d, inputs_d

end
end
51 changes: 51 additions & 0 deletions test/test_multistage.jl
Original file line number Diff line number Diff line change
Expand Up @@ -257,4 +257,55 @@ with_logger(ConsoleLogger(stderr, Logging.Error)) do
test_can_retire_validation()
end

function test_myopic_stage_solution_guard()
@testset "Myopic run stops at a stage without a solution" begin
stage_1 = Model(HiGHS.Optimizer)
set_silent(stage_1)
@variable(stage_1, x)
@constraint(stage_1, x >= 1)
@constraint(stage_1, x <= 0)
@expression(stage_1, eObj, x)

models = Dict(1 => stage_1, 2 => Model(HiGHS.Optimizer))
setup = Dict(
"ComputeConflicts" => 0,
"NetworkExpansion" => 0,
"MultiStageSettingsDict" => Dict(
"Myopic" => 1,
"NumStages" => 2,
"WriteIntermittentOutputs" => 0
)
)
stage_1_inputs = Dict(
"STOR_ALL" => Int[],
"STOR_ASYMMETRIC" => Int[],
"VRE_STOR" => Int[],
"Z" => 1
)
inputs = Dict(1 => stage_1_inputs, 2 => Dict{String, Any}())

caught_error = try
with_logger(ConsoleLogger(stderr, Logging.Error)) do
redirect_stdout(devnull) do
run_myopic_multistage("", models, setup, inputs)
end
end
nothing
catch error
error
end

@test caught_error isa ErrorException
if caught_error isa ErrorException
message = sprint(showerror, caught_error)
@test occursin("Myopic multistage stage 1 has no primal solution", message)
@test occursin("termination_status=INFEASIBLE", message)
@test occursin("primal_status=NO_SOLUTION", message)
@test occursin("raw_status=", message)
end
end
end

test_myopic_stage_solution_guard()

end # module TestMultiStage