Skip to content

Commit 192f611

Browse files
authored
Add docstrings and export public symbols (#159)
1 parent 8c41215 commit 192f611

File tree

8 files changed

+141
-37
lines changed

8 files changed

+141
-37
lines changed

src/MultiObjectiveAlgorithms.jl

Lines changed: 127 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,19 @@ module MultiObjectiveAlgorithms
88
import Combinatorics
99
import MathOptInterface as MOI
1010

11+
"""
12+
struct SolutionPoint
13+
x::Dict{MOI.VariableIndex,Float64}
14+
y::Vector{Float64}
15+
end
16+
17+
The struct for representing a single solution point found by a multiobjective
18+
algorithm.
19+
20+
The field `.x` is a mapping from decision variables to their primal values.
21+
22+
The field `.y` is a vector of the corresponding objective value.
23+
"""
1124
struct SolutionPoint
1225
x::Dict{MOI.VariableIndex,Float64}
1326
y::Vector{Float64}
@@ -19,12 +32,7 @@ end
1932

2033
Base.:(==)(a::SolutionPoint, b::SolutionPoint) = a.y == b.y
2134

22-
"""
23-
dominates(sense, a::SolutionPoint, b::SolutionPoint; atol::Float64)
24-
25-
Returns `true` if point `a` dominates point `b`.
26-
"""
27-
function dominates(
35+
function _dominates(
2836
sense::MOI.OptimizationSense,
2937
a::SolutionPoint,
3038
b::SolutionPoint;
@@ -46,14 +54,27 @@ function _sort!(solutions::Vector{SolutionPoint}, sense::MOI.OptimizationSense)
4654
return sort!(solutions; by = x -> x.y, rev = sense == MOI.MAX_SENSE)
4755
end
4856

57+
"""
58+
filter_nondominated(
59+
sense::MOI.OptimizationSense,
60+
solutions::Vector{SolutionPoint};
61+
atol::Float64 = 1e-6,
62+
)::Vector{SolutionPoint}
63+
64+
Return the subset of non-dominated points from `solutions` as a new vector.
65+
66+
`atol` is used when comparing objective vectors elementwise. The use of `atol`
67+
avoids returning a large set of solution points that are practically equivalent
68+
but differ only by some small (less than `atol`) value.
69+
"""
4970
function filter_nondominated(
50-
sense,
71+
sense::MOI.OptimizationSense,
5172
solutions::Vector{SolutionPoint};
5273
atol::Float64 = 1e-6,
5374
)
5475
nondominated_solutions = SolutionPoint[]
5576
for candidate in solutions
56-
if any(test -> dominates(sense, test, candidate; atol), solutions)
77+
if any(test -> _dominates(sense, test, candidate; atol), solutions)
5778
# Point is dominated. Don't add
5879
elseif any(test -> (test.y, candidate.y; atol), nondominated_solutions)
5980
# Point already added to nondominated solutions. Don't add
@@ -111,10 +132,38 @@ function _scalarise(f::MOI.VectorNonlinearFunction, w::Vector{Float64})
111132
return MOI.ScalarNonlinearFunction(:+, scalars)
112133
end
113134

135+
"""
136+
abstract type AbstractAlgorithm end
137+
138+
The base abtract type for solution algorithms.
139+
140+
To define a new solution algorithm, define a subtype of `AbstractAlgorithm` and
141+
implement `MOA.optimize_multiobjective!`.
142+
"""
114143
abstract type AbstractAlgorithm end
115144

116145
MOI.Utilities.map_indices(::Function, x::AbstractAlgorithm) = x
117146

147+
"""
148+
Optimizer(optimizer_factory)
149+
150+
Create a new instance of a MultiObjectiveAlgorithms optimizer.
151+
152+
`optimizer_factory` must define an inner optimizer constructor that MOA can use
153+
to solve the scalar-objective subproblems. The inner optimizer is constructed
154+
with:
155+
```julia
156+
MOI.instantiate(optimizer_factory; with_cache_type = Float64)
157+
```
158+
159+
## Example
160+
161+
```julia
162+
import MultiObjectiveAlgorithms as MOA
163+
import HiGHS
164+
optimizer = () -> MOA.Optimizer(HiGHS.Optimizer)
165+
```
166+
"""
118167
mutable struct Optimizer <: MOI.AbstractOptimizer
119168
inner::MOI.AbstractOptimizer
120169
algorithm::Union{Nothing,AbstractAlgorithm}
@@ -138,7 +187,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
138187
nothing,
139188
NaN,
140189
Float64[],
141-
default(ComputeIdealPoint()),
190+
_default(ComputeIdealPoint()),
142191
0,
143192
optimizer_factory,
144193
)
@@ -224,7 +273,7 @@ const _ATTRIBUTES = Union{
224273
### Algorithm
225274

226275
"""
227-
Algorithm <: MOI.AbstractOptimizerAttribute
276+
Algorithm() <: MOI.AbstractOptimizerAttribute
228277
229278
An attribute to control the algorithm used by MOA.
230279
"""
@@ -239,7 +288,7 @@ function MOI.set(model::Optimizer, ::Algorithm, alg::AbstractAlgorithm)
239288
return
240289
end
241290

242-
default(::Algorithm) = Lexicographic()
291+
_default(::Algorithm) = Lexicographic()
243292

244293
### AbstractAlgorithmAttribute
245294

@@ -250,7 +299,7 @@ A super-type for MOA-specific optimizer attributes.
250299
"""
251300
abstract type AbstractAlgorithmAttribute <: MOI.AbstractOptimizerAttribute end
252301

253-
default(::AbstractAlgorithm, attr::AbstractAlgorithmAttribute) = default(attr)
302+
_default(::AbstractAlgorithm, attr::AbstractAlgorithmAttribute) = _default(attr)
254303

255304
function MOI.supports(model::Optimizer, attr::AbstractAlgorithmAttribute)
256305
return MOI.supports(model.algorithm, attr)
@@ -266,15 +315,15 @@ function MOI.get(model::Optimizer, attr::AbstractAlgorithmAttribute)
266315
end
267316

268317
"""
269-
SolutionLimit <: AbstractAlgorithmAttribute -> Int
318+
SolutionLimit() <: AbstractAlgorithmAttribute -> Int
270319
271320
Terminate the algorithm once the set number of solutions have been found.
272321
273322
Defaults to `typemax(Int)`.
274323
"""
275324
struct SolutionLimit <: AbstractAlgorithmAttribute end
276325

277-
default(::SolutionLimit) = typemax(Int)
326+
_default(::SolutionLimit) = typemax(Int)
278327

279328
"""
280329
ObjectivePriority(index::Int) <: AbstractAlgorithmAttribute -> Int
@@ -289,7 +338,7 @@ struct ObjectivePriority <: AbstractAlgorithmAttribute
289338
index::Int
290339
end
291340

292-
default(::ObjectivePriority) = 0
341+
_default(::ObjectivePriority) = 0
293342

294343
"""
295344
ObjectiveWeight(index::Int) <: AbstractAlgorithmAttribute -> Float64
@@ -303,7 +352,7 @@ struct ObjectiveWeight <: AbstractAlgorithmAttribute
303352
index::Int
304353
end
305354

306-
default(::ObjectiveWeight) = 1.0
355+
_default(::ObjectiveWeight) = 1.0
307356

308357
"""
309358
ObjectiveRelativeTolerance(index::Int) <: AbstractAlgorithmAttribute -> Float64
@@ -318,7 +367,7 @@ struct ObjectiveRelativeTolerance <: AbstractAlgorithmAttribute
318367
index::Int
319368
end
320369

321-
default(::ObjectiveRelativeTolerance) = 0.0
370+
_default(::ObjectiveRelativeTolerance) = 0.0
322371

323372
"""
324373
ObjectiveAbsoluteTolerance(index::Int) <: AbstractAlgorithmAttribute -> Float64
@@ -333,7 +382,7 @@ struct ObjectiveAbsoluteTolerance <: AbstractAlgorithmAttribute
333382
index::Int
334383
end
335384

336-
default(::ObjectiveAbsoluteTolerance) = 0.0
385+
_default(::ObjectiveAbsoluteTolerance) = 0.0
337386

338387
"""
339388
EpsilonConstraintStep <: AbstractAlgorithmAttribute -> Float64
@@ -344,7 +393,7 @@ Defaults to `1.0`.
344393
"""
345394
struct EpsilonConstraintStep <: AbstractAlgorithmAttribute end
346395

347-
default(::EpsilonConstraintStep) = 1.0
396+
_default(::EpsilonConstraintStep) = 1.0
348397

349398
"""
350399
LexicographicAllPermutations <: AbstractAlgorithmAttribute -> Bool
@@ -357,7 +406,7 @@ Defaults to `true`.
357406
"""
358407
struct LexicographicAllPermutations <: AbstractAlgorithmAttribute end
359408

360-
default(::LexicographicAllPermutations) = true
409+
_default(::LexicographicAllPermutations) = true
361410

362411
"""
363412
ComputeIdealPoint <: AbstractOptimizerAttribute -> Bool
@@ -375,7 +424,7 @@ can improve the performance of MOA by setting this attribute to `false`.
375424
"""
376425
struct ComputeIdealPoint <: MOI.AbstractOptimizerAttribute end
377426

378-
default(::ComputeIdealPoint) = true
427+
_default(::ComputeIdealPoint) = true
379428

380429
MOI.supports(::Optimizer, ::ComputeIdealPoint) = true
381430

@@ -431,7 +480,7 @@ function MOI.get(model::Optimizer, attr::MOI.AbstractOptimizerAttribute)
431480
end
432481

433482
function MOI.get(model::Optimizer, ::MOI.SolverName)
434-
alg = typeof(something(model.algorithm, default(Algorithm())))
483+
alg = typeof(something(model.algorithm, _default(Algorithm())))
435484
inner = MOI.get(model.inner, MOI.SolverName())
436485
return "MOA[algorithm=$alg, optimizer=$inner]"
437486
end
@@ -584,6 +633,12 @@ end
584633
585634
A function that must be called instead of `MOI.optimize!(model.inner)` because
586635
it also increments the `subproblem_count`.
636+
637+
## Usage
638+
639+
This function is part of the public developer API. You should not call it from
640+
user-facing code. You may use it when implementing new algorithms in third-party
641+
packages.
587642
"""
588643
function optimize_inner!(model::Optimizer)
589644
MOI.optimize!(model.inner)
@@ -609,6 +664,40 @@ function _compute_ideal_point(model::Optimizer, start_time)
609664
return
610665
end
611666

667+
"""
668+
minimize_multiobjective!(
669+
algorithm::AbstractAlgorithm,
670+
model::Optimizer,
671+
)::Union{MOI.TerminationStatusCode,Union{Nothing,Vector{SolutionPoint}}}
672+
673+
This function is equivalent to `optimize_multiobjective!`, except that you may
674+
assume that the problem is a minimization problem. This can make implementing
675+
new solution algorithms simpler.
676+
677+
## Usage
678+
679+
This function is part of the public developer API. You should not call it from
680+
user-facing code. You may use it when implementing new algorithms in third-party
681+
packages.
682+
"""
683+
function minimize_multiobjective! end
684+
685+
"""
686+
optimize_multiobjective!(
687+
algorithm::AbstractAlgorithm,
688+
model::Optimizer,
689+
)::Tuple{MOI.TerminationStatusCode,Union{Nothing,Vector{SolutionPoint}}}
690+
691+
Optimize `model` using `algorithm` and return a solution tuple comprised of a
692+
`MOI.TerminationStatusCode` explaining why the solver stopped, and a vector of
693+
`SolutionPoint` (or `nothing`, if something went wrong).
694+
695+
## Usage
696+
697+
This function is part of the public developer API. You should not call it from
698+
user-facing code. You may use it when implementing new algorithms in third-party
699+
packages.
700+
"""
612701
function optimize_multiobjective!(
613702
algorithm::AbstractAlgorithm,
614703
model::Optimizer,
@@ -678,7 +767,7 @@ function _optimize!(model::Optimizer)
678767
# We need to clear the ideal point prior to starting the solve. Algorithms
679768
# may update this during the solve, otherwise we will update it at the end.
680769
model.ideal_point = fill(NaN, MOI.output_dimension(model.f))
681-
algorithm = something(model.algorithm, default(Algorithm()))
770+
algorithm = something(model.algorithm, _default(Algorithm()))
682771
status, solutions = optimize_multiobjective!(algorithm, model)
683772
model.termination_status = status
684773
if solutions !== nothing
@@ -788,4 +877,19 @@ for file in readdir(joinpath(@__DIR__, "algorithms"))
788877
end
789878
end
790879

880+
# MOA exports everything except internal symbols, which are defined as those
881+
# whose name starts with an underscore. If you don't want all of these symbols
882+
# in your environment, then use `import` instead of `using`.
883+
884+
# Do not add MOA-defined symbols to this exclude list. Instead, rename them with
885+
# an underscore.
886+
const _EXCLUDE = Symbol[Symbol(@__MODULE__), :eval, :include]
887+
888+
for sym in names(@__MODULE__; all = true)
889+
if sym in _EXCLUDE || startswith("$sym", "_") || !Base.isidentifier(sym)
890+
continue
891+
end
892+
@eval export $sym
791893
end
894+
895+
end # module

src/algorithms/Dichotomy.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ function MOI.set(alg::Dichotomy, ::SolutionLimit, value)
5151
end
5252

5353
function MOI.get(alg::Dichotomy, attr::SolutionLimit)
54-
return something(alg.solution_limit, default(alg, attr))
54+
return something(alg.solution_limit, _default(alg, attr))
5555
end
5656

5757
function _solve_weighted_sum(

src/algorithms/EpsilonConstraint.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ function MOI.set(alg::EpsilonConstraint, ::SolutionLimit, value)
3838
end
3939

4040
function MOI.get(alg::EpsilonConstraint, attr::SolutionLimit)
41-
return something(alg.solution_limit, default(alg, attr))
41+
return something(alg.solution_limit, _default(alg, attr))
4242
end
4343

4444
MOI.supports(::EpsilonConstraint, ::EpsilonConstraintStep) = true
@@ -49,7 +49,7 @@ function MOI.set(alg::EpsilonConstraint, ::EpsilonConstraintStep, value)
4949
end
5050

5151
function MOI.get(alg::EpsilonConstraint, attr::EpsilonConstraintStep)
52-
return something(alg.atol, default(alg, attr))
52+
return something(alg.atol, _default(alg, attr))
5353
end
5454

5555
MOI.supports(::EpsilonConstraint, ::ObjectiveAbsoluteTolerance) = true
@@ -92,7 +92,7 @@ function minimize_multiobjective!(
9292
# Compute the epsilon that we will be incrementing by each iteration
9393
ε = MOI.get(algorithm, EpsilonConstraintStep())
9494
n_points = MOI.get(algorithm, SolutionLimit())
95-
if n_points != default(algorithm, SolutionLimit())
95+
if n_points != _default(algorithm, SolutionLimit())
9696
ε = abs(right - left) / (n_points - 1)
9797
end
9898
solutions = SolutionPoint[only(solution_1), only(solution_2)]

src/algorithms/Hierarchical.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ end
3636
MOI.supports(::Hierarchical, ::ObjectivePriority) = true
3737

3838
function MOI.get(alg::Hierarchical, attr::ObjectivePriority)
39-
return get(alg.priorities, attr.index, default(alg, attr))
39+
return get(alg.priorities, attr.index, _default(alg, attr))
4040
end
4141

4242
function _append_default(
@@ -45,7 +45,7 @@ function _append_default(
4545
x::Vector,
4646
)
4747
for _ in (1+length(x)):attr.index
48-
push!(x, default(alg, attr))
48+
push!(x, _default(alg, attr))
4949
end
5050
return
5151
end
@@ -59,7 +59,7 @@ end
5959
MOI.supports(::Hierarchical, ::ObjectiveWeight) = true
6060

6161
function MOI.get(alg::Hierarchical, attr::ObjectiveWeight)
62-
return get(alg.weights, attr.index, default(alg, attr))
62+
return get(alg.weights, attr.index, _default(alg, attr))
6363
end
6464

6565
function MOI.set(alg::Hierarchical, attr::ObjectiveWeight, value)
@@ -71,7 +71,7 @@ end
7171
MOI.supports(::Hierarchical, ::ObjectiveRelativeTolerance) = true
7272

7373
function MOI.get(alg::Hierarchical, attr::ObjectiveRelativeTolerance)
74-
return get(alg.rtol, attr.index, default(alg, attr))
74+
return get(alg.rtol, attr.index, _default(alg, attr))
7575
end
7676

7777
function MOI.set(alg::Hierarchical, attr::ObjectiveRelativeTolerance, value)

0 commit comments

Comments
 (0)