From 4e249f1eb3299b655184940db84e375683ce95c5 Mon Sep 17 00:00:00 2001 From: odow Date: Sat, 21 Jun 2025 11:59:01 +0100 Subject: [PATCH 1/3] Add tests for solve failures in Chalmet --- src/algorithms/Chalmet.jl | 8 +++++ test/algorithms/Chalmet.jl | 64 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/src/algorithms/Chalmet.jl b/src/algorithms/Chalmet.jl index 1882988..9837b27 100644 --- a/src/algorithms/Chalmet.jl +++ b/src/algorithms/Chalmet.jl @@ -67,6 +67,10 @@ function minimize_multiobjective!(algorithm::Chalmet, model::Optimizer) MOI.LessThan(y1[2]), ) MOI.optimize!(model.inner) + status = MOI.get(model.inner, MOI.TerminationStatus()) + if !_is_scalar_status_optimal(status) + return status, nothing + end x1, y1[1] = _compute_point(model, variables, f1) MOI.delete(model.inner, y1_constraint) push!(solutions, SolutionPoint(x1, y1)) @@ -87,6 +91,10 @@ function minimize_multiobjective!(algorithm::Chalmet, model::Optimizer) MOI.LessThan(y2[1]), ) MOI.optimize!(model.inner) + status = MOI.get(model.inner, MOI.TerminationStatus()) + if !_is_scalar_status_optimal(status) + return status, nothing + end x2, y2[2] = _compute_point(model, variables, f2) MOI.delete(model.inner, y2_constraint) push!(solutions, SolutionPoint(x2, y2)) diff --git a/test/algorithms/Chalmet.jl b/test/algorithms/Chalmet.jl index 883f27c..1a85fa6 100644 --- a/test/algorithms/Chalmet.jl +++ b/test/algorithms/Chalmet.jl @@ -253,6 +253,70 @@ function test_single_point() return end +function _solve_mock(mock) + highs = HiGHS.Optimizer() + MOI.set(highs, MOI.Silent(), true) + index_map = MOI.copy_to(highs, mock) + MOI.optimize!(highs) + x = [index_map[xi] for xi in MOI.get(mock, MOI.ListOfVariableIndices())] + MOI.Utilities.mock_optimize!( + mock, + MOI.get(highs, MOI.TerminationStatus()), + MOI.get(highs, MOI.VariablePrimal(), x), + ) + obj = MOI.get(highs, MOI.ObjectiveValue()) + MOI.set(mock, MOI.ObjectiveValue(), obj) + return +end + +function mock_optimizer(fail_after::Int) + return () -> begin + model = MOI.Utilities.MockOptimizer( + MOI.Utilities.Model{Float64}(), + ) + MOI.Utilities.set_mock_optimize!( + model, + ntuple(i -> _solve_mock, fail_after)..., + mock -> MOI.Utilities.mock_optimize!(mock, MOI.NUMERICAL_ERROR), + ) + return model + end +end + +function test_solve_failures() + m, n = 2, 10 + p1 = [5.0 1 10 8 3 5 3 3 7 2; 10 6 1 6 8 3 2 10 6 1] + p2 = [4.0 6 4 3 1 6 8 2 9 7; 8 8 8 2 4 8 8 1 10 1] + w = [5.0 9 3 5 10 5 7 10 7 8; 4 8 8 6 10 8 10 7 5 1] + b = [34.0, 33.0] + for fail_after in 0:3 + model = MOA.Optimizer(mock_optimizer(fail_after)) + MOI.set(model, MOA.Algorithm(), MOA.Chalmet()) + x_ = MOI.add_variables(model, m * n) + x = reshape(x_, m, n) + # MOI.add_constraint.(model, x, MOI.ZeroOne()) + MOI.add_constraint.(model, x, MOI.Interval(0.0, 1.0)) + f = MOI.Utilities.operate( + vcat, + Float64, + sum(p1 .* x), + sum(p2 .* x), + ) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) + for i in 1:m + f_i = sum(w[i, j] * x[i, j] for j in 1:n) + MOI.add_constraint(model, f_i, MOI.LessThan(b[i])) + end + for j in 1:n + MOI.add_constraint(model, sum(1.0 .* x[:, j]), MOI.EqualTo(1.0)) + end + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == MOI.NUMERICAL_ERROR + end + return +end + end # module TestChalmet TestChalmet.run_tests() From 5d1afa5887b69305ef270e7b24a254c0056e954a Mon Sep 17 00:00:00 2001 From: odow Date: Sat, 21 Jun 2025 12:03:19 +0100 Subject: [PATCH 2/3] Fix format --- test/algorithms/Chalmet.jl | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/test/algorithms/Chalmet.jl b/test/algorithms/Chalmet.jl index 1a85fa6..d9c0b53 100644 --- a/test/algorithms/Chalmet.jl +++ b/test/algorithms/Chalmet.jl @@ -271,9 +271,7 @@ end function mock_optimizer(fail_after::Int) return () -> begin - model = MOI.Utilities.MockOptimizer( - MOI.Utilities.Model{Float64}(), - ) + model = MOI.Utilities.MockOptimizer(MOI.Utilities.Model{Float64}()) MOI.Utilities.set_mock_optimize!( model, ntuple(i -> _solve_mock, fail_after)..., @@ -296,12 +294,7 @@ function test_solve_failures() x = reshape(x_, m, n) # MOI.add_constraint.(model, x, MOI.ZeroOne()) MOI.add_constraint.(model, x, MOI.Interval(0.0, 1.0)) - f = MOI.Utilities.operate( - vcat, - Float64, - sum(p1 .* x), - sum(p2 .* x), - ) + f = MOI.Utilities.operate(vcat, Float64, sum(p1 .* x), sum(p2 .* x)) MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) for i in 1:m From 9fe66f9b6f12478bab0296e050e8f55ffa4bb62d Mon Sep 17 00:00:00 2001 From: odow Date: Sat, 21 Jun 2025 12:34:29 +0100 Subject: [PATCH 3/3] Update --- test/algorithms/Chalmet.jl | 31 ++------------------------- test/algorithms/Dichotomy.jl | 30 ++++++++++++++++++++++++++ test/algorithms/EpsilonConstraint.jl | 29 +++++++++++++++++++++++++ test/mock_optimizer.jl | 32 ++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 29 deletions(-) create mode 100644 test/mock_optimizer.jl diff --git a/test/algorithms/Chalmet.jl b/test/algorithms/Chalmet.jl index d9c0b53..6a9c9a9 100644 --- a/test/algorithms/Chalmet.jl +++ b/test/algorithms/Chalmet.jl @@ -11,6 +11,8 @@ import HiGHS import MultiObjectiveAlgorithms as MOA import MultiObjectiveAlgorithms: MOI +include(joinpath(dirname(@__DIR__), "mock_optimizer.jl")) + function run_tests() for name in names(@__MODULE__; all = true) if startswith("$name", "test_") @@ -253,34 +255,6 @@ function test_single_point() return end -function _solve_mock(mock) - highs = HiGHS.Optimizer() - MOI.set(highs, MOI.Silent(), true) - index_map = MOI.copy_to(highs, mock) - MOI.optimize!(highs) - x = [index_map[xi] for xi in MOI.get(mock, MOI.ListOfVariableIndices())] - MOI.Utilities.mock_optimize!( - mock, - MOI.get(highs, MOI.TerminationStatus()), - MOI.get(highs, MOI.VariablePrimal(), x), - ) - obj = MOI.get(highs, MOI.ObjectiveValue()) - MOI.set(mock, MOI.ObjectiveValue(), obj) - return -end - -function mock_optimizer(fail_after::Int) - return () -> begin - model = MOI.Utilities.MockOptimizer(MOI.Utilities.Model{Float64}()) - MOI.Utilities.set_mock_optimize!( - model, - ntuple(i -> _solve_mock, fail_after)..., - mock -> MOI.Utilities.mock_optimize!(mock, MOI.NUMERICAL_ERROR), - ) - return model - end -end - function test_solve_failures() m, n = 2, 10 p1 = [5.0 1 10 8 3 5 3 3 7 2; 10 6 1 6 8 3 2 10 6 1] @@ -292,7 +266,6 @@ function test_solve_failures() MOI.set(model, MOA.Algorithm(), MOA.Chalmet()) x_ = MOI.add_variables(model, m * n) x = reshape(x_, m, n) - # MOI.add_constraint.(model, x, MOI.ZeroOne()) MOI.add_constraint.(model, x, MOI.Interval(0.0, 1.0)) f = MOI.Utilities.operate(vcat, Float64, sum(p1 .* x), sum(p2 .* x)) MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) diff --git a/test/algorithms/Dichotomy.jl b/test/algorithms/Dichotomy.jl index eeb486b..ade5c24 100644 --- a/test/algorithms/Dichotomy.jl +++ b/test/algorithms/Dichotomy.jl @@ -12,6 +12,8 @@ import Ipopt import MultiObjectiveAlgorithms as MOA import MultiObjectiveAlgorithms: MOI +include(joinpath(dirname(@__DIR__), "mock_optimizer.jl")) + function run_tests() for name in names(@__MODULE__; all = true) if startswith("$name", "test_") @@ -407,6 +409,34 @@ function test_vector_of_variables_objective() return end +function test_solve_failures() + m, n = 2, 10 + p1 = [5.0 1 10 8 3 5 3 3 7 2; 10 6 1 6 8 3 2 10 6 1] + p2 = [4.0 6 4 3 1 6 8 2 9 7; 8 8 8 2 4 8 8 1 10 1] + w = [5.0 9 3 5 10 5 7 10 7 8; 4 8 8 6 10 8 10 7 5 1] + b = [34.0, 33.0] + for fail_after in 0:3 + model = MOA.Optimizer(mock_optimizer(fail_after)) + MOI.set(model, MOA.Algorithm(), MOA.Dichotomy()) + x_ = MOI.add_variables(model, m * n) + x = reshape(x_, m, n) + MOI.add_constraint.(model, x, MOI.Interval(0.0, 1.0)) + f = MOI.Utilities.operate(vcat, Float64, sum(p1 .* x), sum(p2 .* x)) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) + for i in 1:m + f_i = sum(w[i, j] * x[i, j] for j in 1:n) + MOI.add_constraint(model, f_i, MOI.LessThan(b[i])) + end + for j in 1:n + MOI.add_constraint(model, sum(1.0 .* x[:, j]), MOI.EqualTo(1.0)) + end + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == MOI.NUMERICAL_ERROR + end + return +end + end # module TestDichotomy TestDichotomy.run_tests() diff --git a/test/algorithms/EpsilonConstraint.jl b/test/algorithms/EpsilonConstraint.jl index 7252d6e..0119113 100644 --- a/test/algorithms/EpsilonConstraint.jl +++ b/test/algorithms/EpsilonConstraint.jl @@ -12,6 +12,7 @@ import Ipopt import MultiObjectiveAlgorithms as MOA import MultiObjectiveAlgorithms: MOI +include(joinpath(dirname(@__DIR__), "mock_optimizer.jl")) include(joinpath(dirname(@__DIR__), "vOptLib.jl")) function run_tests() @@ -502,6 +503,34 @@ function test_too_many_objectives() return end +function test_solve_failures() + m, n = 2, 10 + p1 = [5.0 1 10 8 3 5 3 3 7 2; 10 6 1 6 8 3 2 10 6 1] + p2 = [4.0 6 4 3 1 6 8 2 9 7; 8 8 8 2 4 8 8 1 10 1] + w = [5.0 9 3 5 10 5 7 10 7 8; 4 8 8 6 10 8 10 7 5 1] + b = [34.0, 33.0] + for fail_after in 0:3 + model = MOA.Optimizer(mock_optimizer(fail_after)) + MOI.set(model, MOA.Algorithm(), MOA.EpsilonConstraint()) + x_ = MOI.add_variables(model, m * n) + x = reshape(x_, m, n) + MOI.add_constraint.(model, x, MOI.Interval(0.0, 1.0)) + f = MOI.Utilities.operate(vcat, Float64, sum(p1 .* x), sum(p2 .* x)) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) + for i in 1:m + f_i = sum(w[i, j] * x[i, j] for j in 1:n) + MOI.add_constraint(model, f_i, MOI.LessThan(b[i])) + end + for j in 1:n + MOI.add_constraint(model, sum(1.0 .* x[:, j]), MOI.EqualTo(1.0)) + end + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == MOI.NUMERICAL_ERROR + end + return +end + end # module TestEpsilonConstraint TestEpsilonConstraint.run_tests() diff --git a/test/mock_optimizer.jl b/test/mock_optimizer.jl new file mode 100644 index 0000000..8c4279c --- /dev/null +++ b/test/mock_optimizer.jl @@ -0,0 +1,32 @@ +# Copyright 2019, Oscar Dowson and contributors +# This Source Code Form is subject to the terms of the Mozilla Public License, +# v.2.0. If a copy of the MPL was not distributed with this file, You can +# obtain one at http://mozilla.org/MPL/2.0/. + +function _solve_mock(mock) + highs = HiGHS.Optimizer() + MOI.set(highs, MOI.Silent(), true) + index_map = MOI.copy_to(highs, mock) + MOI.optimize!(highs) + x = [index_map[xi] for xi in MOI.get(mock, MOI.ListOfVariableIndices())] + MOI.Utilities.mock_optimize!( + mock, + MOI.get(highs, MOI.TerminationStatus()), + MOI.get(highs, MOI.VariablePrimal(), x), + ) + obj = MOI.get(highs, MOI.ObjectiveValue()) + MOI.set(mock, MOI.ObjectiveValue(), obj) + return +end + +function mock_optimizer(fail_after::Int) + return () -> begin + model = MOI.Utilities.MockOptimizer(MOI.Utilities.Model{Float64}()) + MOI.Utilities.set_mock_optimize!( + model, + ntuple(i -> _solve_mock, fail_after)..., + mock -> MOI.Utilities.mock_optimize!(mock, MOI.NUMERICAL_ERROR), + ) + return model + end +end