Skip to content

Commit dec7e24

Browse files
authored
Fix finite termination of EpsilonConstraint (#51)
1 parent 8dab844 commit dec7e24

File tree

2 files changed

+47
-1
lines changed

2 files changed

+47
-1
lines changed

src/algorithms/EpsilonConstraint.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,9 @@ function optimize_multiobjective!(
104104
MOI.GreaterThan{Float64}, left, 1.0
105105
end
106106
ci = MOI.add_constraint(model, f1, SetType(bound))
107-
while true
107+
# Set a finite upper bound on the number of iterations so that we don't loop
108+
# forever.
109+
for i in 1:ceil(Int, abs(right - left) / ε + 3)
108110
MOI.set(model, MOI.ConstraintSet(), ci, SetType(bound))
109111
MOI.optimize!(model.inner)
110112
if !_is_scalar_status_optimal(model)

test/algorithms/EpsilonConstraint.jl

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,50 @@ function test_quadratic()
318318
return
319319
end
320320

321+
function test_poor_numerics()
322+
μ = [0.006898463772627643, -0.02972609131603086]
323+
Q = [0.030446 0.00393731; 0.00393731 0.00713285]
324+
N = 2
325+
model = MOA.Optimizer(Ipopt.Optimizer)
326+
MOI.set(model, MOA.Algorithm(), MOA.EpsilonConstraint())
327+
MOI.set(model, MOA.SolutionLimit(), 10)
328+
MOI.set(model, MOI.Silent(), true)
329+
w = MOI.add_variables(model, N)
330+
sharpe = MOI.add_variable(model)
331+
MOI.add_constraint.(model, w, MOI.GreaterThan(0.0))
332+
MOI.add_constraint.(model, w, MOI.LessThan(1.0))
333+
MOI.add_constraint(model, sum(1.0 * w[i] for i in 1:N), MOI.EqualTo(1.0))
334+
variance = Expr(:call, :+)
335+
for i in 1:N, j in 1:N
336+
push!(variance.args, Expr(:call, :*, Q[i, j], w[i], w[j]))
337+
end
338+
nlp = MOI.Nonlinear.Model()
339+
MOI.Nonlinear.add_constraint(
340+
nlp,
341+
:(($(μ[1]) * $(w[1]) + $(μ[2]) * $(w[2])) / sqrt($variance) - $sharpe),
342+
MOI.EqualTo(0.0),
343+
)
344+
evaluator = MOI.Nonlinear.Evaluator(
345+
nlp,
346+
MOI.Nonlinear.SparseReverseMode(),
347+
[w; sharpe],
348+
)
349+
MOI.set(model, MOI.NLPBlock(), MOI.NLPBlockData(evaluator))
350+
f = MOI.Utilities.operate(vcat, Float64, μ' * w, sharpe)
351+
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
352+
MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f)
353+
MOI.optimize!(model)
354+
@test MOI.get(model, MOI.ResultCount()) == 1
355+
for i in 1:MOI.get(model, MOI.ResultCount())
356+
w_sol = MOI.get(model, MOI.VariablePrimal(i), w)
357+
sharpe_sol = MOI.get(model, MOI.VariablePrimal(i), sharpe)
358+
y = MOI.get(model, MOI.ObjectiveValue(i))
359+
@test y ' * w_sol, sharpe_sol]
360+
end
361+
@test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL
362+
return
363+
end
364+
321365
end
322366

323367
TestEpsilonConstraint.run_tests()

0 commit comments

Comments
 (0)