Skip to content

Commit e29c451

Browse files
committed
Add tests
1 parent e8b0e45 commit e29c451

File tree

2 files changed

+144
-49
lines changed

2 files changed

+144
-49
lines changed

src/Utilities/penalty_relaxation.jl

Lines changed: 59 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,43 @@ function _change_sense_to_min_if_necessary(
8181
return MOI.MIN_SENSE
8282
end
8383

84+
function _add_penalty_to_objective(
85+
model::MOI.ModelLike,
86+
::Type{F},
87+
penalty::T,
88+
x::Vector{MOI.VariableIndex},
89+
) where {T,F<:MOI.VariableIndex}
90+
g = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(penalty, x), zero(T))
91+
f = MOI.get(model, MOI.ObjectiveFunction{F}())
92+
push!(g.terms, MOI.ScalarAffineTerm(one(T), f))
93+
MOI.set(model, MOI.ObjectiveFunction{typeof(g)}(), g)
94+
return MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(one(T), x), zero(T))
95+
end
96+
97+
function _add_penalty_to_objective(
98+
model::MOI.ModelLike,
99+
::Type{F},
100+
penalty::T,
101+
x::Vector{MOI.VariableIndex},
102+
) where {T,F<:Union{MOI.ScalarAffineFunction{T},MOI.ScalarQuadraticFunction{T}}}
103+
obj = MOI.ObjectiveFunction{F}()
104+
for xi in x
105+
MOI.modify(model, obj, MOI.ScalarCoefficientChange(xi, penalty))
106+
end
107+
return MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(one(T), x), zero(T))
108+
end
109+
110+
function _add_penalty_to_objective(
111+
::MOI.ModelLike,
112+
::Type{F},
113+
::T,
114+
::Vector{MOI.VariableIndex},
115+
) where {T,F}
116+
return error(
117+
"Cannot perform `ScalarPenaltyRelaxation` with an objective function of type `$F`",
118+
)
119+
end
120+
84121
function MOI.modify(
85122
model::MOI.ModelLike,
86123
ci::MOI.ConstraintIndex{F,<:MOI.AbstractScalarSet},
@@ -93,13 +130,9 @@ function MOI.modify(
93130
MOI.add_constraint(model, z, MOI.GreaterThan(zero(T)))
94131
MOI.modify(model, ci, MOI.ScalarCoefficientChange(y, one(T)))
95132
MOI.modify(model, ci, MOI.ScalarCoefficientChange(z, -one(T)))
96-
scale = sense == MOI.MIN_SENSE ? one(T) : -one(T)
97-
a = scale * relax.penalty
133+
penalty = sense == MOI.MIN_SENSE ? relax.penalty : -relax.penalty
98134
O = MOI.get(model, MOI.ObjectiveFunctionType())
99-
obj = MOI.ObjectiveFunction{O}()
100-
MOI.modify(model, obj, MOI.ScalarCoefficientChange(y, a))
101-
MOI.modify(model, obj, MOI.ScalarCoefficientChange(z, a))
102-
return one(T) * y + one(T) * z
135+
return _add_penalty_to_objective(model, O, penalty, [y, z])
103136
end
104137

105138
function MOI.modify(
@@ -112,12 +145,9 @@ function MOI.modify(
112145
y = MOI.add_variable(model)
113146
MOI.add_constraint(model, y, MOI.GreaterThan(zero(T)))
114147
MOI.modify(model, ci, MOI.ScalarCoefficientChange(y, one(T)))
115-
scale = sense == MOI.MIN_SENSE ? one(T) : -one(T)
116-
a = scale * relax.penalty
148+
penalty = sense == MOI.MIN_SENSE ? relax.penalty : -relax.penalty
117149
O = MOI.get(model, MOI.ObjectiveFunctionType())
118-
obj = MOI.ObjectiveFunction{O}()
119-
MOI.modify(model, obj, MOI.ScalarCoefficientChange(y, a))
120-
return one(T) * y
150+
return _add_penalty_to_objective(model, O, penalty, [y])
121151
end
122152

123153
function MOI.modify(
@@ -130,42 +160,28 @@ function MOI.modify(
130160
z = MOI.add_variable(model)
131161
MOI.add_constraint(model, z, MOI.GreaterThan(zero(T)))
132162
MOI.modify(model, ci, MOI.ScalarCoefficientChange(z, -one(T)))
133-
scale = sense == MOI.MIN_SENSE ? one(T) : -one(T)
134-
a = scale * relax.penalty
163+
penalty = sense == MOI.MIN_SENSE ? relax.penalty : -relax.penalty
135164
O = MOI.get(model, MOI.ObjectiveFunctionType())
136-
obj = MOI.ObjectiveFunction{O}()
137-
MOI.modify(model, obj, MOI.ScalarCoefficientChange(z, a))
138-
return one(T) * z
165+
return _add_penalty_to_objective(model, O, penalty, [z])
139166
end
140167

141168
function MOI.modify(
142169
model::MOI.ModelLike,
143-
ci::MOI.ConstraintIndex{MOI.ScalarNonlinearFunction,<:MOI.AbstractScalarSet},
170+
ci::MOI.ConstraintIndex{MOI.ScalarNonlinearFunction,S},
144171
relax::ScalarPenaltyRelaxation{T},
145-
) where {T}
172+
) where {T,S<:MOI.AbstractScalarSet}
146173
sense = _change_sense_to_min_if_necessary(T, model)
147-
y = MOI.add_variable(model)
148-
z = MOI.add_variable(model)
149-
MOI.add_constraint(model, y, MOI.GreaterThan(zero(T)))
150-
MOI.add_constraint(model, z, MOI.GreaterThan(zero(T)))
151-
func = MOI.get(model, MOI.ConstraintFunction(), ci)
152-
newfunc = MOI.ScalarNonlinearFunction(:+, [func, (one(T) * y - one(T) * z)])
153-
MOI.set(model, MOI.ConstraintFunction(), ci, newfunc)
154-
scale = sense == MOI.MIN_SENSE ? one(T) : -one(T)
155-
a = scale * relax.penalty
174+
y, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(zero(T)))
175+
z, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(zero(T)))
176+
f = MOI.get(model, MOI.ConstraintFunction(), ci)
177+
f = MOI.ScalarNonlinearFunction(
178+
:+,
179+
Any[f, y, MOI.ScalarNonlinearFunction(:-, Any[z])],
180+
)
181+
MOI.set(model, MOI.ConstraintFunction(), ci, f)
182+
penalty = sense == MOI.MIN_SENSE ? relax.penalty : -relax.penalty
156183
O = MOI.get(model, MOI.ObjectiveFunctionType())
157-
obj = MOI.ObjectiveFunction{O}()
158-
# This breaks if the objective is a VariableIndex or ScalarNonlinearFunction
159-
MOI.modify(model, obj, MOI.ScalarCoefficientChange(y, a))
160-
MOI.modify(model, obj, MOI.ScalarCoefficientChange(z, a))
161-
# The following causes problems with other methods trying to modify the objective
162-
# function. We would have to branch/dispatch on objective type
163-
# To support existing nonlinear objectives as well as linear/quadratic objectives,
164-
# we just turn any objective into a ScalarNonlinearFunction
165-
#obj = MOI.get(model, MOI.ObjectiveFunction{O}())
166-
#newobj = MOI.ScalarNonlinearFunction(:+, [obj, a * y + a * z])
167-
#MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarNonlinearFunction}(), newobj)
168-
return one(T) * y + one(T) * z
184+
return _add_penalty_to_objective(model, O, penalty, [y, z])
169185
end
170186

171187
function MOI.modify(
@@ -179,12 +195,9 @@ function MOI.modify(
179195
func = MOI.get(model, MOI.ConstraintFunction(), ci)
180196
newfunc = MOI.ScalarNonlinearFunction(:+, [func, y])
181197
MOI.set(model, MOI.ConstraintFunction(), ci, newfunc)
182-
scale = sense == MOI.MIN_SENSE ? one(T) : -one(T)
183-
a = scale * relax.penalty
198+
penalty = sense == MOI.MIN_SENSE ? relax.penalty : -relax.penalty
184199
O = MOI.get(model, MOI.ObjectiveFunctionType())
185-
obj = MOI.ObjectiveFunction{O}()
186-
MOI.modify(model, obj, MOI.ScalarCoefficientChange(y, a))
187-
return one(T) * y
200+
return _add_penalty_to_objective(model, O, penalty, [y])
188201
end
189202

190203
function MOI.modify(
@@ -198,12 +211,9 @@ function MOI.modify(
198211
func = MOI.get(model, MOI.ConstraintFunction(), ci)
199212
newfunc = MOI.ScalarNonlinearFunction(:-, [func, z])
200213
MOI.set(model, MOI.ConstraintFunction(), ci, newfunc)
201-
scale = sense == MOI.MIN_SENSE ? one(T) : -one(T)
202-
a = scale * relax.penalty
214+
penalty = sense == MOI.MIN_SENSE ? relax.penalty : -relax.penalty
203215
O = MOI.get(model, MOI.ObjectiveFunctionType())
204-
obj = MOI.ObjectiveFunction{O}()
205-
MOI.modify(model, obj, MOI.ScalarCoefficientChange(z, a))
206-
return one(T) * z
216+
return _add_penalty_to_objective(model, O, penalty, [z])
207217
end
208218

209219
"""

test/Utilities/penalty_relaxation.jl

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,23 @@ function test_relax_no_warn()
8484
return
8585
end
8686

87+
function test_relax_variable_index_objective()
88+
_test_roundtrip(
89+
"""
90+
variables: x, y
91+
minobjective: x
92+
c1: x + y <= 1.0
93+
""",
94+
"""
95+
variables: x, y, a
96+
minobjective: 1.0 * x + 1.0 * a
97+
c1: x + y + -1.0 * a <= 1.0
98+
a >= 0.0
99+
""",
100+
)
101+
return
102+
end
103+
87104
function test_relax_affine_lessthan()
88105
_test_roundtrip(
89106
"""
@@ -238,6 +255,58 @@ function test_relax_quadratic_greaterthanthan()
238255
return
239256
end
240257

258+
function test_relax_scalarnonlinear_lessthan()
259+
_test_roundtrip(
260+
"""
261+
variables: x
262+
maxobjective: 1.0 * x
263+
c1: ScalarNonlinearFunction(log(x)) <= 1.0
264+
""",
265+
"""
266+
variables: x, a
267+
maxobjective: 1.0 * x + -1.0 * a
268+
c1: ScalarNonlinearFunction(log(x) - a) <= 1.0
269+
a >= 0.0
270+
""",
271+
)
272+
return
273+
end
274+
275+
function test_relax_scalarnonlinear_greaterthan()
276+
_test_roundtrip(
277+
"""
278+
variables: x
279+
maxobjective: 1.0 * x
280+
c1: ScalarNonlinearFunction(log(x)) >= 1.0
281+
""",
282+
"""
283+
variables: x, a
284+
maxobjective: 1.0 * x + -1.0 * a
285+
c1: ScalarNonlinearFunction(log(x) + a) >= 1.0
286+
a >= 0.0
287+
""",
288+
)
289+
return
290+
end
291+
292+
function test_relax_scalarnonlinear_equalto()
293+
_test_roundtrip(
294+
"""
295+
variables: x
296+
minobjective: 1.0 * x
297+
c1: ScalarNonlinearFunction(log(x)) == 1.0
298+
""",
299+
"""
300+
variables: x, a, b
301+
minobjective: 1.0 * x + 1.0 * a + 1.0 * b
302+
c1: ScalarNonlinearFunction(+(log(x), a, -b)) == 1.0
303+
a >= 0.0
304+
b >= 0.0
305+
""",
306+
)
307+
return
308+
end
309+
241310
function test_penalty_dict()
242311
model = MOI.Utilities.Model{Float64}()
243312
x = MOI.add_variable(model)
@@ -373,6 +442,22 @@ function test_scalar_penalty_relaxation()
373442
return
374443
end
375444

445+
function test_scalar_penalty_relaxation_vector_objective()
446+
model = MOI.Utilities.Model{Float64}()
447+
x = MOI.add_variable(model)
448+
c = MOI.add_constraint(model, 1.0 * x, MOI.LessThan(2.0))
449+
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
450+
f = MOI.VectorOfVariables([x])
451+
MOI.set(model, MOI.ObjectiveFunction{MOI.VectorOfVariables}(), f)
452+
@test_throws(
453+
ErrorException(
454+
"Cannot perform `ScalarPenaltyRelaxation` with an objective function of type `$(typeof(f))`",
455+
),
456+
MOI.modify(model, c, MOI.Utilities.ScalarPenaltyRelaxation(2.0)),
457+
)
458+
return
459+
end
460+
376461
end # module
377462

378463
TestPenaltyRelaxation.runtests()

0 commit comments

Comments
 (0)