@@ -8,6 +8,19 @@ module MultiObjectiveAlgorithms
88import Combinatorics
99import 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+ """
1124struct SolutionPoint
1225 x:: Dict{MOI.VariableIndex,Float64}
1326 y:: Vector{Float64}
1932
2033Base.:(== )(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)
4755end
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+ """
4970function 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)
112133end
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+ """
114143abstract type AbstractAlgorithm end
115144
116145MOI. 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+ """
118167mutable 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
229278An attribute to control the algorithm used by MOA.
230279"""
@@ -239,7 +288,7 @@ function MOI.set(model::Optimizer, ::Algorithm, alg::AbstractAlgorithm)
239288 return
240289end
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"""
251300abstract type AbstractAlgorithmAttribute <: MOI.AbstractOptimizerAttribute end
252301
253- default (:: AbstractAlgorithm , attr:: AbstractAlgorithmAttribute ) = default (attr)
302+ _default (:: AbstractAlgorithm , attr:: AbstractAlgorithmAttribute ) = _default (attr)
254303
255304function MOI. supports (model:: Optimizer , attr:: AbstractAlgorithmAttribute )
256305 return MOI. supports (model. algorithm, attr)
@@ -266,15 +315,15 @@ function MOI.get(model::Optimizer, attr::AbstractAlgorithmAttribute)
266315end
267316
268317"""
269- SolutionLimit <: AbstractAlgorithmAttribute -> Int
318+ SolutionLimit() <: AbstractAlgorithmAttribute -> Int
270319
271320Terminate the algorithm once the set number of solutions have been found.
272321
273322Defaults to `typemax(Int)`.
274323"""
275324struct 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
290339end
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
304353end
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
319368end
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
334383end
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"""
345394struct 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"""
358407struct 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"""
376425struct ComputeIdealPoint <: MOI.AbstractOptimizerAttribute end
377426
378- default (:: ComputeIdealPoint ) = true
427+ _default (:: ComputeIdealPoint ) = true
379428
380429MOI. supports (:: Optimizer , :: ComputeIdealPoint ) = true
381430
@@ -431,7 +480,7 @@ function MOI.get(model::Optimizer, attr::MOI.AbstractOptimizerAttribute)
431480end
432481
433482function 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 ]"
437486end
584633
585634A function that must be called instead of `MOI.optimize!(model.inner)` because
586635it 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"""
588643function optimize_inner! (model:: Optimizer )
589644 MOI. optimize! (model. inner)
@@ -609,6 +664,40 @@ function _compute_ideal_point(model::Optimizer, start_time)
609664 return
610665end
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+ """
612701function 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
789878end
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
791893end
894+
895+ end # module
0 commit comments