From 7b97c8f23ae83c6ed741f9ad9c9354d35bf9dcd3 Mon Sep 17 00:00:00 2001 From: odow Date: Fri, 8 Aug 2025 13:53:26 +1200 Subject: [PATCH 1/5] Add SubproblemCount attribute --- README.md | 4 +++ ext/MultiObjectiveAlgorithmsPolyhedraExt.jl | 6 ++-- src/MultiObjectiveAlgorithms.jl | 32 ++++++++++++++++++++- src/algorithms/Chalmet.jl | 10 +++---- src/algorithms/Dichotomy.jl | 2 +- src/algorithms/DominguezRios.jl | 6 ++-- src/algorithms/EpsilonConstraint.jl | 2 +- src/algorithms/Hierarchical.jl | 2 +- src/algorithms/KirlikSayin.jl | 8 +++--- src/algorithms/Lexicographic.jl | 2 +- src/algorithms/RandomWeighting.jl | 4 +-- src/algorithms/TambyVanderpooten.jl | 8 +++--- test/algorithms/Chalmet.jl | 1 + test/algorithms/Dichotomy.jl | 1 + test/algorithms/Hierarchical.jl | 1 + test/algorithms/Lexicographic.jl | 1 + test/algorithms/RandomWeighting.jl | 1 + test/algorithms/Sandwiching.jl | 1 + test/problems.jl | 4 +++ test/vOptLib.jl | 1 + 20 files changed, 71 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index d7352fc..b3aef5d 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,10 @@ the solution process. * `MOA.SolutionLimit()` * `MOI.TimeLimitSec()` +Query the number of scalar subproblems that were solved using + + * `MOA.SubproblemCount()` + ## Ideal point By default, MOA will compute the ideal point, which can be queried using the diff --git a/ext/MultiObjectiveAlgorithmsPolyhedraExt.jl b/ext/MultiObjectiveAlgorithmsPolyhedraExt.jl index b68034f..d61a6df 100644 --- a/ext/MultiObjectiveAlgorithmsPolyhedraExt.jl +++ b/ext/MultiObjectiveAlgorithmsPolyhedraExt.jl @@ -53,7 +53,7 @@ function MOA.minimize_multiobjective!( yI, yUB = zeros(n), zeros(n) for (i, f_i) in enumerate(scalars) MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f_i)}(), f_i) - MOI.optimize!(model.inner) + MOA.optimize_inner!(model) status = MOI.get(model.inner, MOI.TerminationStatus()) if !MOA._is_scalar_status_optimal(model) return status, nothing @@ -63,7 +63,7 @@ function MOA.minimize_multiobjective!( yI[i] = Y[i] anchors[Y] = X MOI.set(model.inner, MOI.ObjectiveSense(), MOI.MAX_SENSE) - MOI.optimize!(model.inner) + MOA.optimize_inner!(model) status = MOI.get(model.inner, MOI.TerminationStatus()) if !MOA._is_scalar_status_optimal(model) MOA._warn_on_nonfinite_anti_ideal(algorithm, MOI.MIN_SENSE, i) @@ -113,7 +113,7 @@ function MOA.minimize_multiobjective!( # would not terminate when precision is set to 0 new_f = sum(w[i] * (scalars[i] + u[i]) for i in 1:n) # w' * (f(x) + u) MOI.set(model.inner, MOI.ObjectiveFunction{typeof(new_f)}(), new_f) - MOI.optimize!(model.inner) + MOA.optimize_inner!(model) status = MOI.get(model.inner, MOI.TerminationStatus()) if !MOA._is_scalar_status_optimal(model) return status, nothing diff --git a/src/MultiObjectiveAlgorithms.jl b/src/MultiObjectiveAlgorithms.jl index 5f69cec..0bc59b1 100644 --- a/src/MultiObjectiveAlgorithms.jl +++ b/src/MultiObjectiveAlgorithms.jl @@ -123,6 +123,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer solve_time::Float64 ideal_point::Vector{Float64} compute_ideal_point::Bool + subproblem_count::Int function Optimizer(optimizer_factory) return new( @@ -135,6 +136,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer NaN, Float64[], default(ComputeIdealPoint()), + 0, ) end end @@ -146,6 +148,7 @@ function MOI.empty!(model::Optimizer) model.termination_status = MOI.OPTIMIZE_NOT_CALLED model.solve_time = NaN empty!(model.ideal_point) + model.subproblem_count = 0 return end @@ -394,6 +397,20 @@ end MOI.get(model::Optimizer, ::ComputeIdealPoint) = model.compute_ideal_point +### SubproblemCount + +""" + SubproblemCount <: AbstractModelAttribute -> Int + +A result attribute for querying the total number of subproblem solves by an +algorithm. +""" +struct SubproblemCount <: MOI.AbstractModelAttribute end + +MOI.is_set_by_optimize(::SubproblemCount) = true + +MOI.get(model::Optimizer, ::SubproblemCount) = model.subproblem_count + ### RawOptimizerAttribute function MOI.supports(model::Optimizer, attr::MOI.RawOptimizerAttribute) @@ -573,6 +590,18 @@ function MOI.delete(model::Optimizer, ci::MOI.ConstraintIndex) return end +""" + optimize_inner!(model::Optimizer) + +A function that must be called instead of `MOI.optimize!(model.inner)` because +it also increments the `subproblem_count`. +""" +function optimize_inner!(model::Optimizer) + MOI.optimize!(model.inner) + model.subproblem_count += 1 + return +end + function _compute_ideal_point(model::Optimizer, start_time) for (i, f) in enumerate(MOI.Utilities.eachscalar(model.f)) if _time_limit_exceeded(model, start_time) @@ -582,7 +611,7 @@ function _compute_ideal_point(model::Optimizer, start_time) continue # The algorithm already updated this information end MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f)}(), f) - MOI.optimize!(model.inner) + optimize_inner!(model) status = MOI.get(model.inner, MOI.TerminationStatus()) if _is_scalar_status_optimal(status) model.ideal_point[i] = MOI.get(model.inner, MOI.ObjectiveValue()) @@ -619,6 +648,7 @@ function MOI.optimize!(model::Optimizer) start_time = time() empty!(model.solutions) model.termination_status = MOI.OPTIMIZE_NOT_CALLED + model.subproblem_count = 0 if model.f === nothing model.termination_status = MOI.INVALID_MODEL empty!(model.ideal_point) diff --git a/src/algorithms/Chalmet.jl b/src/algorithms/Chalmet.jl index 9837b27..b4e61cf 100644 --- a/src/algorithms/Chalmet.jl +++ b/src/algorithms/Chalmet.jl @@ -29,7 +29,7 @@ function _solve_constrained_model( MOI.set(model.inner, MOI.ObjectiveFunction{typeof(g)}(), g) sets = MOI.LessThan.(rhs .- 1) c = MOI.Utilities.normalize_and_add_constraint.(model.inner, f, sets) - MOI.optimize!(model.inner) + optimize_inner!(model) status = MOI.get(model.inner, MOI.TerminationStatus()) if !_is_scalar_status_optimal(status) MOI.delete.(model, c) @@ -54,7 +54,7 @@ function minimize_multiobjective!(algorithm::Chalmet, model::Optimizer) f1, f2 = MOI.Utilities.scalarize(model.f) y1, y2 = zeros(2), zeros(2) MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f2)}(), f2) - MOI.optimize!(model.inner) + optimize_inner!(model) status = MOI.get(model.inner, MOI.TerminationStatus()) if !_is_scalar_status_optimal(status) return status, nothing @@ -66,7 +66,7 @@ function minimize_multiobjective!(algorithm::Chalmet, model::Optimizer) f2, MOI.LessThan(y1[2]), ) - MOI.optimize!(model.inner) + optimize_inner!(model) status = MOI.get(model.inner, MOI.TerminationStatus()) if !_is_scalar_status_optimal(status) return status, nothing @@ -75,7 +75,7 @@ function minimize_multiobjective!(algorithm::Chalmet, model::Optimizer) MOI.delete(model.inner, y1_constraint) push!(solutions, SolutionPoint(x1, y1)) MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f1)}(), f1) - MOI.optimize!(model.inner) + optimize_inner!(model) status = MOI.get(model.inner, MOI.TerminationStatus()) if !_is_scalar_status_optimal(status) return status, nothing @@ -90,7 +90,7 @@ function minimize_multiobjective!(algorithm::Chalmet, model::Optimizer) f1, MOI.LessThan(y2[1]), ) - MOI.optimize!(model.inner) + optimize_inner!(model) status = MOI.get(model.inner, MOI.TerminationStatus()) if !_is_scalar_status_optimal(status) return status, nothing diff --git a/src/algorithms/Dichotomy.jl b/src/algorithms/Dichotomy.jl index 19634d0..d357056 100644 --- a/src/algorithms/Dichotomy.jl +++ b/src/algorithms/Dichotomy.jl @@ -61,7 +61,7 @@ function _solve_weighted_sum( ) f = _scalarise(model.f, weights) MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f)}(), f) - MOI.optimize!(model.inner) + optimize_inner!(model) status = MOI.get(model.inner, MOI.TerminationStatus()) if !_is_scalar_status_optimal(status) return status, nothing diff --git a/src/algorithms/DominguezRios.jl b/src/algorithms/DominguezRios.jl index 815dc12..566b582 100644 --- a/src/algorithms/DominguezRios.jl +++ b/src/algorithms/DominguezRios.jl @@ -153,7 +153,7 @@ function minimize_multiobjective!(algorithm::DominguezRios, model::Optimizer) for (i, f_i) in enumerate(scalars) MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f_i)}(), f_i) MOI.set(model.inner, MOI.ObjectiveSense(), MOI.MIN_SENSE) - MOI.optimize!(model.inner) + optimize_inner!(model) status = MOI.get(model.inner, MOI.TerminationStatus()) if !_is_scalar_status_optimal(status) return status, nothing @@ -162,7 +162,7 @@ function minimize_multiobjective!(algorithm::DominguezRios, model::Optimizer) yI[i] = Y model.ideal_point[i] = Y MOI.set(model.inner, MOI.ObjectiveSense(), MOI.MAX_SENSE) - MOI.optimize!(model.inner) + optimize_inner!(model) status = MOI.get(model.inner, MOI.TerminationStatus()) if !_is_scalar_status_optimal(status) _warn_on_nonfinite_anti_ideal(algorithm, MOI.MIN_SENSE, i) @@ -206,7 +206,7 @@ function minimize_multiobjective!(algorithm::DominguezRios, model::Optimizer) # * the `ϵ` term is already covered in `w` new_f = t_max + ϵ * sum(w[i] * (scalars[i] - yI[i]) for i in 1:n) MOI.set(model.inner, MOI.ObjectiveFunction{typeof(new_f)}(), new_f) - MOI.optimize!(model.inner) + optimize_inner!(model) if _is_scalar_status_optimal(model) X, Y = _compute_point(model, variables, model.f) obj = MOI.get(model.inner, MOI.ObjectiveValue()) diff --git a/src/algorithms/EpsilonConstraint.jl b/src/algorithms/EpsilonConstraint.jl index 0f230f5..e1ed5f0 100644 --- a/src/algorithms/EpsilonConstraint.jl +++ b/src/algorithms/EpsilonConstraint.jl @@ -116,7 +116,7 @@ function minimize_multiobjective!( break end MOI.set(model, MOI.ConstraintSet(), ci, MOI.LessThan{Float64}(bound)) - MOI.optimize!(model.inner) + optimize_inner!(model) if !_is_scalar_status_optimal(model) break end diff --git a/src/algorithms/Hierarchical.jl b/src/algorithms/Hierarchical.jl index f7783f1..6e87824 100644 --- a/src/algorithms/Hierarchical.jl +++ b/src/algorithms/Hierarchical.jl @@ -100,7 +100,7 @@ function minimize_multiobjective!(algorithm::Hierarchical, model::Optimizer) new_vector_f = objectives[indices] new_f = _scalarise(new_vector_f, weights[indices]) MOI.set(model.inner, MOI.ObjectiveFunction{typeof(new_f)}(), new_f) - MOI.optimize!(model.inner) + optimize_inner!(model) status = MOI.get(model.inner, MOI.TerminationStatus()) if !_is_scalar_status_optimal(status) return status, nothing diff --git a/src/algorithms/KirlikSayin.jl b/src/algorithms/KirlikSayin.jl index d60d09e..c5f242e 100644 --- a/src/algorithms/KirlikSayin.jl +++ b/src/algorithms/KirlikSayin.jl @@ -93,7 +93,7 @@ function minimize_multiobjective!(algorithm::KirlikSayin, model::Optimizer) for (i, f_i) in enumerate(scalars) # Ideal point MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f_i)}(), f_i) - MOI.optimize!(model.inner) + optimize_inner!(model) status = MOI.get(model.inner, MOI.TerminationStatus()) if !_is_scalar_status_optimal(status) return status, nothing @@ -102,7 +102,7 @@ function minimize_multiobjective!(algorithm::KirlikSayin, model::Optimizer) model.ideal_point[i] = yI[i] = Y # Nadir point MOI.set(model.inner, MOI.ObjectiveSense(), MOI.MAX_SENSE) - MOI.optimize!(model.inner) + optimize_inner!(model) status = MOI.get(model.inner, MOI.TerminationStatus()) if !_is_scalar_status_optimal(status) # Repair ObjectiveSense before exiting @@ -143,7 +143,7 @@ function minimize_multiobjective!(algorithm::KirlikSayin, model::Optimizer) ) push!(ε_constraints, ci) end - MOI.optimize!(model.inner) + optimize_inner!(model) if !_is_scalar_status_optimal(model) _remove_rectangle(L, _Rectangle(_project(yI, k), uᵢ)) MOI.delete.(model, ε_constraints) @@ -160,7 +160,7 @@ function minimize_multiobjective!(algorithm::KirlikSayin, model::Optimizer) scalars[k], MOI.EqualTo(zₖ), ) - MOI.optimize!(model.inner) + optimize_inner!(model) if !_is_scalar_status_optimal(model) MOI.delete.(model, ε_constraints) MOI.delete(model, zₖ_constraint) diff --git a/src/algorithms/Lexicographic.jl b/src/algorithms/Lexicographic.jl index aef20c8..52cd974 100644 --- a/src/algorithms/Lexicographic.jl +++ b/src/algorithms/Lexicographic.jl @@ -104,7 +104,7 @@ function _solve_in_sequence( end f = scalars[i] MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f)}(), f) - MOI.optimize!(model.inner) + optimize_inner!(model) status = MOI.get(model.inner, MOI.TerminationStatus()) primal_status = MOI.get(model.inner, MOI.PrimalStatus()) if _is_scalar_status_feasible_point(primal_status) diff --git a/src/algorithms/RandomWeighting.jl b/src/algorithms/RandomWeighting.jl index e163cd6..2d2f8ef 100644 --- a/src/algorithms/RandomWeighting.jl +++ b/src/algorithms/RandomWeighting.jl @@ -46,7 +46,7 @@ function optimize_multiobjective!(algorithm::RandomWeighting, model::Optimizer) variables = MOI.get(model.inner, MOI.ListOfVariableIndices()) f = _scalarise(model.f, ones(P)) MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f)}(), f) - MOI.optimize!(model.inner) + optimize_inner!(model) status = MOI.get(model.inner, MOI.TerminationStatus()) if _is_scalar_status_optimal(status) X, Y = _compute_point(model, variables, model.f) @@ -66,7 +66,7 @@ function optimize_multiobjective!(algorithm::RandomWeighting, model::Optimizer) weights = rand(P) f = _scalarise(model.f, weights) MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f)}(), f) - MOI.optimize!(model.inner) + optimize_inner!(model) status = MOI.get(model.inner, MOI.TerminationStatus()) if _is_scalar_status_optimal(status) X, Y = _compute_point(model, variables, model.f) diff --git a/src/algorithms/TambyVanderpooten.jl b/src/algorithms/TambyVanderpooten.jl index 85ac16f..0bd5b32 100644 --- a/src/algorithms/TambyVanderpooten.jl +++ b/src/algorithms/TambyVanderpooten.jl @@ -99,7 +99,7 @@ function minimize_multiobjective!( for (i, f_i) in enumerate(scalars) MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f_i)}(), f_i) MOI.set(model.inner, MOI.ObjectiveSense(), MOI.MIN_SENSE) - MOI.optimize!(model.inner) + optimize_inner!(model) status = MOI.get(model.inner, MOI.TerminationStatus()) if !_is_scalar_status_optimal(status) return status, nothing @@ -108,7 +108,7 @@ function minimize_multiobjective!( yI[i] = Y model.ideal_point[i] = Y MOI.set(model.inner, MOI.ObjectiveSense(), MOI.MAX_SENSE) - MOI.optimize!(model.inner) + optimize_inner!(model) status = MOI.get(model.inner, MOI.TerminationStatus()) if !_is_scalar_status_optimal(status) _warn_on_nonfinite_anti_ideal(algorithm, MOI.MIN_SENSE, i) @@ -157,7 +157,7 @@ function minimize_multiobjective!( end end end - MOI.optimize!(model.inner) + optimize_inner!(model) if !_is_scalar_status_optimal(model) MOI.delete.(model, ε_constraints) return status, nothing @@ -170,7 +170,7 @@ function minimize_multiobjective!( scalars[k], MOI.EqualTo(y_k), ) - MOI.optimize!(model.inner) + optimize_inner!(model) if !_is_scalar_status_optimal(model) MOI.delete.(model, ε_constraints) MOI.delete(model, y_k_constraint) diff --git a/test/algorithms/Chalmet.jl b/test/algorithms/Chalmet.jl index 6a9c9a9..5e91b40 100644 --- a/test/algorithms/Chalmet.jl +++ b/test/algorithms/Chalmet.jl @@ -252,6 +252,7 @@ function test_single_point() @test MOI.get(model, MOI.ResultCount()) == 1 @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT @test ≈(MOI.get(model, MOI.VariablePrimal(), x), [1.0, 1.0]; atol = 1e-6) + @test MOI.get(model, MOA.SubproblemCount()) >= 1 return end diff --git a/test/algorithms/Dichotomy.jl b/test/algorithms/Dichotomy.jl index ade5c24..e423904 100644 --- a/test/algorithms/Dichotomy.jl +++ b/test/algorithms/Dichotomy.jl @@ -406,6 +406,7 @@ function test_vector_of_variables_objective() MOI.add_constraint(model, sum(1.0 * xi for xi in x), MOI.GreaterThan(1.0)) MOI.optimize!(model) @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL + @test MOI.get(model, MOA.SubproblemCount()) >= 1 return end diff --git a/test/algorithms/Hierarchical.jl b/test/algorithms/Hierarchical.jl index ca7b59f..c9330cb 100644 --- a/test/algorithms/Hierarchical.jl +++ b/test/algorithms/Hierarchical.jl @@ -57,6 +57,7 @@ function test_knapsack() @test ≈(x_sol, [0.9, 0, 0.9, 0.2]; atol = 1e-3) y_sol = MOI.get(model, MOI.ObjectiveValue()) @test ≈(y_sol, P * x_sol .+ [0.0, 0.0, 0.0, 1_000.0]; atol = 1e-4) + @test MOI.get(model, MOA.SubproblemCount()) >= 1 return end diff --git a/test/algorithms/Lexicographic.jl b/test/algorithms/Lexicographic.jl index 14993e9..74fa36c 100644 --- a/test/algorithms/Lexicographic.jl +++ b/test/algorithms/Lexicographic.jl @@ -45,6 +45,7 @@ function test_knapsack() @test ≈(x_sol, [0.9, 1, 0, 0.1]; atol = 1e-3) y_sol = MOI.get(model, MOI.ObjectiveValue()) @test ≈(y_sol, P * x_sol .+ [0.0, 0.0, 0.0, 1_000.0]; atol = 1e-4) + @test MOI.get(model, MOA.SubproblemCount()) == 8 return end diff --git a/test/algorithms/RandomWeighting.jl b/test/algorithms/RandomWeighting.jl index a3239dd..5c3b9a0 100644 --- a/test/algorithms/RandomWeighting.jl +++ b/test/algorithms/RandomWeighting.jl @@ -78,6 +78,7 @@ function test_knapsack_min() [0, 0, 1, 1, 1, 0, 1, 1, 1, 1] => [-2854, -4636], ] @test MOI.get(model, MOI.ResultCount()) == length(results) + @test MOI.get(model, MOA.SubproblemCount()) >= 3 for (i, (x_sol, y_sol)) in enumerate(results) @test ≈(x_sol, MOI.get(model, MOI.VariablePrimal(i), x); atol = 1e-6) @test ≈(y_sol, MOI.get(model, MOI.ObjectiveValue(i)); atol = 1e-6) diff --git a/test/algorithms/Sandwiching.jl b/test/algorithms/Sandwiching.jl index 5f6f64b..b496475 100644 --- a/test/algorithms/Sandwiching.jl +++ b/test/algorithms/Sandwiching.jl @@ -57,6 +57,7 @@ function _test_molp(C, A, b, results, sense) MOI.get(model, MOI.ObjectiveValue(i)) for i in 1:N ]) @test N == length(results) + @test MOI.get(model, MOA.SubproblemCount()) >= length(results) for (sol, res) in zip(solutions, results) x_sol, y_sol = sol x_res, y_res = res diff --git a/test/problems.jl b/test/problems.jl index 192afe0..decd00f 100644 --- a/test/problems.jl +++ b/test/problems.jl @@ -391,6 +391,7 @@ function test_problem_assignment_max_p3(model) [0 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 1 0 0 0 1 0 0] => [50, 40, 32], ]) @test MOI.get(model, MOI.ResultCount()) == length(results) + @test MOI.get(model, MOA.SubproblemCount()) >= length(results) for (i, (x_sol, y_sol)) in enumerate(results) x_primal = MOI.get(model, MOI.VariablePrimal(i), vec(x)) @test ≈(vec(x_sol), x_primal; atol = 1e-6) @@ -428,6 +429,7 @@ function test_problem_issue_105(model) MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) MOI.optimize!(model) @test MOI.get(model, MOI.ResultCount()) == 6 + @test MOI.get(model, MOA.SubproblemCount()) >= 6 for (i, y) in enumerate([ [2380.0, 81.0, 18.0], [2440.0, 78.0, 18.0], @@ -469,6 +471,7 @@ function test_issue_122(model) end MOI.optimize!(model) @test MOI.get(model, MOI.ResultCount()) == 42 + @test MOI.get(model, MOA.SubproblemCount()) >= 42 return end @@ -493,6 +496,7 @@ function test_issue_133(model) MOI.optimize!(model) @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL @test MOI.get(model, MOI.ResultCount()) == 95 + @test MOI.get(model, MOA.SubproblemCount()) >= 95 return end diff --git a/test/vOptLib.jl b/test/vOptLib.jl index 74b6af9..77937e4 100644 --- a/test/vOptLib.jl +++ b/test/vOptLib.jl @@ -40,6 +40,7 @@ function _test_vOptLib_instance(model, instance; complete::Bool = true) end min_solutions = complete ? length(solutions) : 1 @test MOI.get(model, MOI.ResultCount()) >= min_solutions + @test MOI.get(model, MOA.SubproblemCount()) >= min_solutions for i in 1:MOI.get(model, MOI.ResultCount()) Y = round.(Int, MOI.get(model, MOI.ObjectiveValue(i))) @test haskey(solutions, Y) From ba3956d3f3edf6c755644eadeea16c6354b32806 Mon Sep 17 00:00:00 2001 From: odow Date: Fri, 8 Aug 2025 14:05:27 +1200 Subject: [PATCH 2/5] Update --- test/vOptLib.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/vOptLib.jl b/test/vOptLib.jl index 77937e4..b43d224 100644 --- a/test/vOptLib.jl +++ b/test/vOptLib.jl @@ -8,6 +8,7 @@ module vOptLib using Test import JSON import MathOptInterface as MOI +import MultiObjectiveAlgorithms as MOA function run_tests(model::MOI.ModelLike; kwargs...) for name in names(@__MODULE__; all = true) From f229368a5697f7dc1fc547b7fbdcb0736b783da8 Mon Sep 17 00:00:00 2001 From: odow Date: Fri, 8 Aug 2025 14:19:02 +1200 Subject: [PATCH 3/5] Update --- test/problems.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/problems.jl b/test/problems.jl index decd00f..1b64aff 100644 --- a/test/problems.jl +++ b/test/problems.jl @@ -7,6 +7,7 @@ module Problems using Test import MathOptInterface as MOI +import MultiObjectiveAlgorithms as MOA function run_tests(model::MOI.ModelLike) for name in names(@__MODULE__; all = true) From 1fcf5493f996cb0eac26236310414d158c576ab1 Mon Sep 17 00:00:00 2001 From: odow Date: Fri, 8 Aug 2025 15:13:51 +1200 Subject: [PATCH 4/5] Update --- test/test_model.jl | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/test_model.jl b/test/test_model.jl index 5560684..6902f52 100644 --- a/test/test_model.jl +++ b/test/test_model.jl @@ -206,6 +206,28 @@ function test_ideal_point() return end +function test_SubproblemCount() + model = MOA.Optimizer(HiGHS.Optimizer) + @test MOI.get(model, MOA.SubproblemCount()) == 0 + @test MOI.is_set_by_optimize(MOA.SubproblemCount()) + MOI.set(model, MOI.Silent(), true) + x = MOI.add_variables(model, 2) + MOI.add_constraint.(model, x, MOI.GreaterThan(0.0)) + MOI.add_constraint(model, x[2], MOI.LessThan(3.0)) + MOI.add_constraint(model, 3.0 * x[1] - 1.0 * x[2], MOI.LessThan(6.0)) + MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) + f = MOI.Utilities.vectorize([ + 3.0 * x[1] + x[2], + -1.0 * x[1] - 2.0 * x[2], + ]) + MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) + MOI.optimize!(model) + @test MOI.get(model, MOA.SubproblemCount()) > 0 + MOI.empty!(model) + @test MOI.get(model, MOA.SubproblemCount()) == 0 + return +end + end # module TestModel.run_tests() From 3fc044ce7458fc2dcc20cb0e66e3f07b00121a4a Mon Sep 17 00:00:00 2001 From: odow Date: Fri, 8 Aug 2025 15:17:47 +1200 Subject: [PATCH 5/5] Update --- test/test_model.jl | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/test_model.jl b/test/test_model.jl index 6902f52..3c1f310 100644 --- a/test/test_model.jl +++ b/test/test_model.jl @@ -216,10 +216,7 @@ function test_SubproblemCount() MOI.add_constraint(model, x[2], MOI.LessThan(3.0)) MOI.add_constraint(model, 3.0 * x[1] - 1.0 * x[2], MOI.LessThan(6.0)) MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) - f = MOI.Utilities.vectorize([ - 3.0 * x[1] + x[2], - -1.0 * x[1] - 2.0 * x[2], - ]) + f = MOI.Utilities.vectorize([3.0 * x[1] + x[2], -1.0 * x[1] - 2.0 * x[2]]) MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) MOI.optimize!(model) @test MOI.get(model, MOA.SubproblemCount()) > 0