Skip to content
Merged
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions ext/MultiObjectiveAlgorithmsPolyhedraExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down
32 changes: 31 additions & 1 deletion src/MultiObjectiveAlgorithms.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -135,6 +136,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
NaN,
Float64[],
default(ComputeIdealPoint()),
0,
)
end
end
Expand All @@ -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

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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())
Expand Down Expand Up @@ -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)
Expand Down
10 changes: 5 additions & 5 deletions src/algorithms/Chalmet.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/algorithms/Dichotomy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions src/algorithms/DominguezRios.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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())
Expand Down
2 changes: 1 addition & 1 deletion src/algorithms/EpsilonConstraint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/algorithms/Hierarchical.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions src/algorithms/KirlikSayin.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/algorithms/Lexicographic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions src/algorithms/RandomWeighting.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions src/algorithms/TambyVanderpooten.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down
1 change: 1 addition & 0 deletions test/algorithms/Chalmet.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions test/algorithms/Dichotomy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions test/algorithms/Hierarchical.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions test/algorithms/Lexicographic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions test/algorithms/RandomWeighting.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions test/algorithms/Sandwiching.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading