Skip to content

Change MOA.SolutionLimit to be an alias for MOA.SolutionLimit #150

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
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
6 changes: 3 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Combinatorics = "1"
HiGHS = "1"
Ipopt = "1"
JSON = "0.21"
MathOptInterface = "1.19"
MathOptInterface = "1.21"
Polyhedra = "0.8"
Test = "1"
julia = "1.10"
Expand All @@ -27,8 +27,8 @@ julia = "1.10"
HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b"
Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9"
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Polyhedra = "67491407-f73d-577b-9b50-8179a7c68029"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["HiGHS", "Ipopt", "JSON", "Test", "Polyhedra"]
test = ["HiGHS", "Ipopt", "JSON", "Polyhedra", "Test"]
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import HiGHS
import MultiObjectiveAlgorithms as MOA
model = JuMP.Model(() -> MOA.Optimizer(HiGHS.Optimizer))
set_attribute(model, MOA.Algorithm(), MOA.Dichotomy())
set_attribute(model, MOA.SolutionLimit(), 4)
set_attribute(model, MOI.SolutionLimit(), 4)
```

For worked examples, see the [Simple multi-objective examples](https://jump.dev/JuMP.jl/stable/tutorials/linear/multi_objective_examples/)
Expand Down Expand Up @@ -82,7 +82,7 @@ the solution process.
* `MOA.ObjectivePriority(index::Int)`
* `MOA.ObjectiveRelativeTolerance(index::Int)`
* `MOA.ObjectiveWeight(index::Int)`
* `MOA.SolutionLimit()`
* `MOI.SolutionLimit()`
* `MOI.TimeLimitSec()`

Query the number of scalar subproblems that were solved using
Expand Down
31 changes: 20 additions & 11 deletions src/MultiObjectiveAlgorithms.jl
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
solutions::Vector{SolutionPoint}
termination_status::MOI.TerminationStatusCode
time_limit_sec::Union{Nothing,Float64}
solution_limit::Union{Nothing,Int}
solve_time::Float64
ideal_point::Vector{Float64}
compute_ideal_point::Bool
Expand All @@ -134,6 +135,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
SolutionPoint[],
MOI.OPTIMIZE_NOT_CALLED,
nothing,
nothing,
NaN,
Float64[],
default(ComputeIdealPoint()),
Expand Down Expand Up @@ -187,6 +189,24 @@ function MOI.set(model::Optimizer, ::MOI.TimeLimitSec, ::Nothing)
return
end

### SolutionLimit

const SolutionLimit = MOI.SolutionLimit

MOI.supports(::Optimizer, ::MOI.SolutionLimit) = true

MOI.get(model::Optimizer, ::MOI.SolutionLimit) = model.solution_limit

function MOI.set(model::Optimizer, ::MOI.SolutionLimit, value::Integer)
model.solution_limit = Int(value)
return
end

function MOI.set(model::Optimizer, ::MOI.SolutionLimit, ::Nothing)
model.solution_limit = nothing
return
end

### SolveTimeSec

function MOI.get(model::Optimizer, ::MOI.SolveTimeSec)
Expand Down Expand Up @@ -263,17 +283,6 @@ function MOI.get(model::Optimizer, attr::AbstractAlgorithmAttribute)
return MOI.get(model.algorithm, attr)
end

"""
SolutionLimit <: AbstractAlgorithmAttribute -> Int

Terminate the algorithm once the set number of solutions have been found.

Defaults to `typemax(Int)`.
"""
struct SolutionLimit <: AbstractAlgorithmAttribute end

default(::SolutionLimit) = typemax(Int)

"""
ObjectivePriority(index::Int) <: AbstractAlgorithmAttribute -> Int

Expand Down
23 changes: 4 additions & 19 deletions src/algorithms/Dichotomy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,9 @@ Science 25(1), 73-78.
* `MOI.TimeLimitSec()`: terminate if the time limit is exceeded and return the
list of current solutions.

* `MOA.SolutionLimit()`: terminate once this many solutions have been found.
* `MOI.SolutionLimit()`: terminate once this many solutions have been found.
"""
mutable struct Dichotomy <: AbstractAlgorithm
solution_limit::Union{Nothing,Int}

Dichotomy() = new(nothing)
end
mutable struct Dichotomy <: AbstractAlgorithm end

"""
NISE()
Expand All @@ -39,21 +35,10 @@ trade‐offs: An algorithm for bicriterion problems. Water Resources Research,

## Supported optimizer attributes

* `MOA.SolutionLimit()`
* `MOI.SolutionLimit()`
"""
NISE() = Dichotomy()

MOI.supports(::Dichotomy, ::SolutionLimit) = true

function MOI.set(alg::Dichotomy, ::SolutionLimit, value)
alg.solution_limit = value
return
end

function MOI.get(alg::Dichotomy, attr::SolutionLimit)
return something(alg.solution_limit, default(alg, attr))
end

function _solve_weighted_sum(
model::Optimizer,
::Dichotomy,
Expand Down Expand Up @@ -100,7 +85,7 @@ function optimize_multiobjective!(algorithm::Dichotomy, model::Optimizer)
if !(solutions[0.0] ≈ solutions[1.0])
push!(queue, (0.0, 1.0))
end
limit = MOI.get(algorithm, SolutionLimit())
limit = something(MOI.get(model, MOI.SolutionLimit()), typemax(Int))
status = MOI.OPTIMAL
while length(queue) > 0 && length(solutions) < limit
if (ret = _check_premature_termination(model, start_time)) !== nothing
Expand Down
30 changes: 11 additions & 19 deletions src/algorithms/EpsilonConstraint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,20 @@ bi-objective programs.
default is `1`, so that for a pure integer program this algorithm will
enumerate all non-dominated solutions.

* `MOA.SolutionLimit()`: if this attribute is set then, instead of using the
* `MOI.SolutionLimit()`: if this attribute is set then, instead of using the
`MOA.EpsilonConstraintStep`, with a slight abuse of notation,
`EpsilonConstraint` divides the width of the first-objective's domain in
objective space by `SolutionLimit` to obtain the epsilon to use when
iterating. Thus, there can be at most `SolutionLimit` solutions returned, but
there may be fewer.
objective space by `MOI.SolutionLimit` to obtain the epsilon to use when
iterating. Thus, there can be at most `MOI.SolutionLimit` solutions returned,
but there may be fewer.

* `MOI.TimeLimitSec()`: terminate if the time limit is exceeded and return the
list of current solutions.
"""
mutable struct EpsilonConstraint <: AbstractAlgorithm
solution_limit::Union{Nothing,Int}
atol::Union{Nothing,Float64}

EpsilonConstraint() = new(nothing, nothing)
end

MOI.supports(::EpsilonConstraint, ::SolutionLimit) = true

function MOI.set(alg::EpsilonConstraint, ::SolutionLimit, value)
alg.solution_limit = value
return
end

function MOI.get(alg::EpsilonConstraint, attr::SolutionLimit)
return something(alg.solution_limit, default(alg, attr))
EpsilonConstraint() = new(nothing)
end

MOI.supports(::EpsilonConstraint, ::EpsilonConstraintStep) = true
Expand Down Expand Up @@ -91,8 +82,9 @@ function minimize_multiobjective!(
model.ideal_point .= min.(solution_1[1].y, solution_2[1].y)
# Compute the epsilon that we will be incrementing by each iteration
ε = MOI.get(algorithm, EpsilonConstraintStep())
n_points = MOI.get(algorithm, SolutionLimit())
if n_points != default(algorithm, SolutionLimit())
n_points = MOI.get(model, MOI.SolutionLimit())
if n_points === nothing
n_points = typemax(Int)
ε = abs(right - left) / (n_points - 1)
end
solutions = SolutionPoint[only(solution_1), only(solution_2)]
Expand Down
70 changes: 21 additions & 49 deletions src/algorithms/RandomWeighting.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,67 +14,39 @@ random weights.
* `MOI.TimeLimitSec()`: terminate if the time limit is exceeded and return the
list of current solutions.

* `MOA.SolutionLimit()`: terminate once this many solutions have been found.
* `MOI.SolutionLimit()`: terminate once this many solutions have been found.

At least one of these two limits must be set.
"""
mutable struct RandomWeighting <: AbstractAlgorithm
solution_limit::Union{Nothing,Int}
RandomWeighting() = new(nothing)
end

MOI.supports(::RandomWeighting, ::SolutionLimit) = true

function MOI.set(alg::RandomWeighting, ::SolutionLimit, value)
alg.solution_limit = value
return
end

function MOI.get(alg::RandomWeighting, attr::SolutionLimit)
return something(alg.solution_limit, default(alg, attr))
end
mutable struct RandomWeighting <: AbstractAlgorithm end

function optimize_multiobjective!(algorithm::RandomWeighting, model::Optimizer)
if MOI.get(model, MOI.TimeLimitSec()) === nothing &&
algorithm.solution_limit === nothing
error("At least `MOI.TimeLimitSec` or `MOA.SolutionLimit` must be set")
error("At least `MOI.TimeLimitSec` or `MOI.SolutionLimit` must be set")
end
start_time = time()
solutions = SolutionPoint[]
sense = MOI.get(model, MOI.ObjectiveSense())
P = MOI.output_dimension(model.f)
variables = MOI.get(model.inner, MOI.ListOfVariableIndices())
f = _scalarise(model.f, ones(P))
MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f)}(), f)
optimize_inner!(model)
status = MOI.get(model.inner, MOI.TerminationStatus())
if _is_scalar_status_optimal(status)
X, Y = _compute_point(model, variables, model.f)
push!(solutions, SolutionPoint(X, Y))
else
return status, nothing
end
# This double loop is a bit weird:
# * the inner loop fills up SolutionLimit number of solutions. Then we cut
# it back to nondominated.
# * then the outer loop goes again
while length(solutions) < MOI.get(algorithm, SolutionLimit())
while length(solutions) < MOI.get(algorithm, SolutionLimit())
ret = _check_premature_termination(model, start_time)
if ret !== nothing
return ret, filter_nondominated(sense, solutions)
end
weights = rand(P)
f = _scalarise(model.f, weights)
MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f)}(), f)
optimize_inner!(model)
status = MOI.get(model.inner, MOI.TerminationStatus())
if _is_scalar_status_optimal(status)
X, Y = _compute_point(model, variables, model.f)
push!(solutions, SolutionPoint(X, Y))
end
limit = something(MOI.get(model, MOI.SolutionLimit()), typemax(Int))
status = MOI.OPTIMAL
while length(solutions) < limit
if (ret = _check_premature_termination(model, start_time)) !== nothing
status = ret
break
end
weights = rand(MOI.output_dimension(model.f))
f = _scalarise(model.f, weights)
MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f)}(), f)
optimize_inner!(model)
if _is_scalar_status_optimal(model)
X, Y = _compute_point(model, variables, model.f)
push!(solutions, SolutionPoint(X, Y))
end
if length(solutions) == limit
solutions = filter_nondominated(sense, solutions)
end
solutions = filter_nondominated(sense, solutions)
end
return MOI.OPTIMAL, filter_nondominated(sense, solutions)
return status, filter_nondominated(sense, solutions)
end
13 changes: 5 additions & 8 deletions test/algorithms/Dichotomy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,10 @@ end
function test_Dichotomy_SolutionLimit()
model = MOA.Optimizer(HiGHS.Optimizer)
MOI.set(model, MOA.Algorithm(), MOA.Dichotomy())
@test MOI.supports(MOA.Dichotomy(), MOA.SolutionLimit())
@test MOI.supports(model, MOA.SolutionLimit())
@test MOI.get(model, MOA.SolutionLimit()) ==
MOA.default(MOA.SolutionLimit())
MOI.set(model, MOA.SolutionLimit(), 1)
@test MOI.get(model, MOA.SolutionLimit()) == 1
@test MOI.supports(model, MOI.SolutionLimit())
@test MOI.get(model, MOI.SolutionLimit()) == nothing
MOI.set(model, MOI.SolutionLimit(), 1)
@test MOI.get(model, MOI.SolutionLimit()) == 1
return
end

Expand Down Expand Up @@ -343,7 +341,6 @@ function test_deprecated()
nise = MOA.NISE()
dichotomy = MOA.Dichotomy()
@test nise isa typeof(dichotomy)
@test nise.solution_limit === dichotomy.solution_limit
return
end

Expand Down Expand Up @@ -371,7 +368,7 @@ function test_quadratic()
N = 2
model = MOA.Optimizer(Ipopt.Optimizer)
MOI.set(model, MOA.Algorithm(), MOA.Dichotomy())
MOI.set(model, MOA.SolutionLimit(), 10)
MOI.set(model, MOI.SolutionLimit(), 10)
MOI.set(model, MOI.Silent(), true)
w = MOI.add_variables(model, N)
MOI.add_constraint.(model, w, MOI.GreaterThan(0.0))
Expand Down
14 changes: 7 additions & 7 deletions test/algorithms/EpsilonConstraint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function test_biobjective_knapsack()
w = [80, 87, 68, 72, 66, 77, 99, 85, 70, 93, 98, 72, 100, 89, 67, 86, 91]
model = MOA.Optimizer(HiGHS.Optimizer)
MOI.set(model, MOA.Algorithm(), MOA.EpsilonConstraint())
MOI.set(model, MOA.SolutionLimit(), 100)
MOI.set(model, MOI.SolutionLimit(), 100)
MOI.set(model, MOI.Silent(), true)
x = MOI.add_variables(model, length(w))
MOI.add_constraint.(model, x, MOI.ZeroOne())
Expand Down Expand Up @@ -170,7 +170,7 @@ function test_biobjective_knapsack_min()
w = [80, 87, 68, 72, 66, 77, 99, 85, 70, 93, 98, 72, 100, 89, 67, 86, 91]
model = MOA.Optimizer(HiGHS.Optimizer)
MOI.set(model, MOA.Algorithm(), MOA.EpsilonConstraint())
MOI.set(model, MOA.SolutionLimit(), 100)
MOI.set(model, MOI.SolutionLimit(), 100)
MOI.set(model, MOI.Silent(), true)
x = MOI.add_variables(model, length(w))
MOI.add_constraint.(model, x, MOI.ZeroOne())
Expand Down Expand Up @@ -215,8 +215,8 @@ function test_biobjective_knapsack_min_solution_limit()
w = [80, 87, 68, 72, 66, 77, 99, 85, 70, 93, 98, 72, 100, 89, 67, 86, 91]
model = MOA.Optimizer(HiGHS.Optimizer)
MOI.set(model, MOA.Algorithm(), MOA.EpsilonConstraint())
@test MOI.supports(model, MOA.SolutionLimit())
MOI.set(model, MOA.SolutionLimit(), 3)
@test MOI.supports(model, MOI.SolutionLimit())
MOI.set(model, MOI.SolutionLimit(), 3)
MOI.set(model, MOI.Silent(), true)
x = MOI.add_variables(model, length(w))
MOI.add_constraint.(model, x, MOI.ZeroOne())
Expand Down Expand Up @@ -315,7 +315,7 @@ function test_quadratic()
N = 2
model = MOA.Optimizer(Ipopt.Optimizer)
MOI.set(model, MOA.Algorithm(), MOA.EpsilonConstraint())
MOI.set(model, MOA.SolutionLimit(), 10)
MOI.set(model, MOI.SolutionLimit(), 10)
MOI.set(model, MOI.Silent(), true)
w = MOI.add_variables(model, N)
MOI.add_constraint.(model, w, MOI.GreaterThan(0.0))
Expand Down Expand Up @@ -343,7 +343,7 @@ function test_poor_numerics()
N = 2
model = MOA.Optimizer(Ipopt.Optimizer)
MOI.set(model, MOA.Algorithm(), MOA.EpsilonConstraint())
MOI.set(model, MOA.SolutionLimit(), 10)
MOI.set(model, MOI.SolutionLimit(), 10)
MOI.set(model, MOI.Silent(), true)
w = MOI.add_variables(model, N)
sharpe = MOI.add_variable(model)
Expand Down Expand Up @@ -387,7 +387,7 @@ function test_vectornonlinearfunction()
N = 2
model = MOA.Optimizer(Ipopt.Optimizer)
MOI.set(model, MOA.Algorithm(), MOA.EpsilonConstraint())
MOI.set(model, MOA.SolutionLimit(), 10)
MOI.set(model, MOI.SolutionLimit(), 10)
MOI.set(model, MOI.Silent(), true)
w = MOI.add_variables(model, N)
MOI.add_constraint.(model, w, MOI.GreaterThan(0.0))
Expand Down
Loading
Loading