Skip to content

Commit 75a453b

Browse files
authored
Fixes in cubic and constraint interpretation (#234)
* Fixes in cubic and constraint interpretation * fix doc test
1 parent 41740c3 commit 75a453b

File tree

5 files changed

+69
-26
lines changed

5 files changed

+69
-26
lines changed

src/MOI_wrapper.jl

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ function MOI.is_empty(model::Optimizer)
135135
# obj
136136
model.affine_objective_cache === nothing &&
137137
model.quadratic_objective_cache === nothing &&
138+
model.cubic_objective_cache === nothing &&
138139
MOI.is_empty(model.original_objective_cache) &&
139140
#
140141
isempty(model.vector_affine_constraint_cache) &&
@@ -878,7 +879,7 @@ function _add_constraint_with_parameters_on_function(
878879
end
879880
elseif model.constraints_interpretation == ONLY_CONSTRAINTS
880881
poi_ci = MOI.add_constraint(model, pf, set)
881-
elseif model.constraints_interpretation == BOUNDS_AND_CONSTRAINTS
882+
else #if model.constraints_interpretation == BOUNDS_AND_CONSTRAINTS
882883
if length(affine_variable_terms(pf)) == 1 &&
883884
isone(MOI.coefficient(affine_variable_terms(pf)[]))
884885
poi_ci = _add_vi_constraint(model, pf, set)
@@ -1886,23 +1887,25 @@ ParametricOptInterface.Optimizer{Float64, MOIU.Model{Float64}}
18861887
└ NumberOfConstraints: 0
18871888
18881889
julia> MOI.set(model, POI.ConstraintsInterpretation(), POI.ONLY_BOUNDS)
1889-
ONLY_BOUNDS::ConstraintsInterpretationCode = 1
18901890
18911891
julia> MOI.set(model, POI.ConstraintsInterpretation(), POI.ONLY_CONSTRAINTS)
1892-
ONLY_CONSTRAINTS::ConstraintsInterpretationCode = 0
18931892
18941893
julia> MOI.set(model, POI.ConstraintsInterpretation(), POI.BOUNDS_AND_CONSTRAINTS)
1895-
BOUNDS_AND_CONSTRAINTS::ConstraintsInterpretationCode = 2
18961894
```
18971895
"""
18981896
struct ConstraintsInterpretation <: MOI.AbstractOptimizerAttribute end
18991897

1898+
function MOI.get(model::Optimizer, ::ConstraintsInterpretation)
1899+
return model.constraints_interpretation
1900+
end
1901+
19001902
function MOI.set(
19011903
model::Optimizer,
19021904
::ConstraintsInterpretation,
19031905
value::ConstraintsInterpretationCode,
19041906
)
1905-
return model.constraints_interpretation = value
1907+
model.constraints_interpretation = value
1908+
return
19061909
end
19071910

19081911
#

src/cubic_objective.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ function MOI.set(
3838
"optimizer. " *
3939
"This may be due to unsupported features in the cubic " *
4040
"expression. " *
41-
"Original error: $(e.msg)",
41+
"Original error: $(sprint(showerror, e))",
4242
)
4343
end
4444

src/update_parameters.jl

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -50,24 +50,14 @@ function _update_affine_constraints!(
5050
MOI.AbstractScalarSet,
5151
},
5252
) where {F,S<:Union{MOI.LessThan,MOI.GreaterThan,MOI.EqualTo},V}
53-
# cis = MOI.ConstraintIndex{F,S}[]
54-
# sets = S[]
55-
# sizehint!(cis, length(affine_constraint_cache_inner))
56-
# sizehint!(sets, length(affine_constraint_cache_inner))
5753
for (inner_ci, pf) in affine_constraint_cache_inner
5854
delta_constant = _delta_parametric_constant(model, pf)
5955
if !iszero(delta_constant)
6056
pf.current_constant += delta_constant
6157
new_set = S(pf.set_constant - pf.current_constant)
62-
# new_set = _set_with_new_constant(set, param_constant)
6358
MOI.set(model.optimizer, MOI.ConstraintSet(), inner_ci, new_set)
64-
# push!(cis, inner_ci)
65-
# push!(sets, new_set)
6659
end
6760
end
68-
# if !isempty(cis)
69-
# MOI.set(model.optimizer, MOI.ConstraintSet(), cis, sets)
70-
# end
7161
return
7262
end
7363

@@ -192,19 +182,12 @@ function _update_quadratic_constraints!(
192182
MOI.AbstractScalarSet,
193183
},
194184
) where {F,S<:Union{MOI.LessThan,MOI.GreaterThan,MOI.EqualTo},V}
195-
# cis = MOI.ConstraintIndex{F,S}[]
196-
# sets = S[]
197-
# sizehint!(cis, length(quadratic_constraint_cache_inner))
198-
# sizehint!(sets, length(quadratic_constraint_cache_inner))
199185
for (inner_ci, pf) in quadratic_constraint_cache_inner
200186
delta_constant = _delta_parametric_constant(model, pf)
201187
if !iszero(delta_constant)
202188
pf.current_constant += delta_constant
203189
new_set = S(pf.set_constant - pf.current_constant)
204-
# new_set = _set_with_new_constant(set, param_constant)
205190
MOI.set(model.optimizer, MOI.ConstraintSet(), inner_ci, new_set)
206-
# push!(cis, inner_ci)
207-
# push!(sets, new_set)
208191
end
209192
delta_terms = _delta_parametric_affine_terms(model, pf)
210193
if !isempty(delta_terms)
@@ -213,9 +196,6 @@ function _update_quadratic_constraints!(
213196
MOI.modify(model.optimizer, cis, changes)
214197
end
215198
end
216-
# if !isempty(cis)
217-
# MOI.set(model.optimizer, MOI.ConstraintSet(), cis, sets)
218-
# end
219199
return
220200
end
221201

test/test_MathOptInterface.jl

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2181,6 +2181,40 @@ function test_name_from_bound()
21812181
return
21822182
end
21832183

2184+
function test_constraints_interpretation_get_set()
2185+
model = POI.Optimizer(MOI.Utilities.Model{Float64}())
2186+
# Default value
2187+
@test MOI.get(model, POI.ConstraintsInterpretation()) ==
2188+
POI.ONLY_CONSTRAINTS
2189+
# Round-trip for each value
2190+
MOI.set(model, POI.ConstraintsInterpretation(), POI.ONLY_BOUNDS)
2191+
@test MOI.get(model, POI.ConstraintsInterpretation()) == POI.ONLY_BOUNDS
2192+
MOI.set(model, POI.ConstraintsInterpretation(), POI.ONLY_CONSTRAINTS)
2193+
@test MOI.get(model, POI.ConstraintsInterpretation()) ==
2194+
POI.ONLY_CONSTRAINTS
2195+
MOI.set(model, POI.ConstraintsInterpretation(), POI.BOUNDS_AND_CONSTRAINTS)
2196+
@test MOI.get(model, POI.ConstraintsInterpretation()) ==
2197+
POI.BOUNDS_AND_CONSTRAINTS
2198+
return
2199+
end
2200+
2201+
function test_only_bounds_coefficient_not_one_errors()
2202+
# ONLY_BOUNDS with coefficient ≠ 1 must error.
2203+
# The function must contain a parameter so that the parametric path is taken;
2204+
# a plain ScalarAffineFunction without parameters bypasses the check entirely.
2205+
model = POI.Optimizer(MOI.Utilities.Model{Float64}())
2206+
x = MOI.add_variable(model)
2207+
p, _ = MOI.add_constrained_variable(model, MOI.Parameter(1.0))
2208+
MOI.set(model, POI.ConstraintsInterpretation(), POI.ONLY_BOUNDS)
2209+
# 2.0 * x + 1.0 * p: single variable term with coefficient 2.0 ≠ 1 — must throw
2210+
f = MOI.ScalarAffineFunction(
2211+
[MOI.ScalarAffineTerm(2.0, x), MOI.ScalarAffineTerm(1.0, p)],
2212+
0.0,
2213+
)
2214+
@test_throws ErrorException MOI.add_constraint(model, f, MOI.LessThan(5.0))
2215+
return
2216+
end
2217+
21842218
function test_get_constraint_set()
21852219
model = POI.Optimizer(MOI.Utilities.Model{Float64}())
21862220
x = MOI.add_variable(model)

test/test_cubic.jl

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1567,6 +1567,32 @@ function test_optimize_skips_update_without_parameter_change()
15671567
return
15681568
end
15691569

1570+
function test_is_empty_with_cubic_objective()
1571+
# Regression test: cubic_objective_cache must be checked in MOI.is_empty.
1572+
# Before the fix, is_empty ignored this cache and returned true incorrectly.
1573+
model = POI.Optimizer(HiGHS.Optimizer())
1574+
MOI.set(model, MOI.Silent(), true)
1575+
@test MOI.is_empty(model)
1576+
1577+
# Set a cubic objective to populate cubic_objective_cache
1578+
x = MOI.add_variable(model)
1579+
p, _ = MOI.add_constrained_variable(model, MOI.Parameter(1.0))
1580+
p_v = POI.v_idx(POI.p_idx(p))
1581+
f = MOI.ScalarNonlinearFunction(:*, Any[1.0, p_v, x, x])
1582+
MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarNonlinearFunction}(), f)
1583+
@test !MOI.is_empty(model)
1584+
1585+
# After full empty!, model must be empty
1586+
cubic_cache = model.cubic_objective_cache
1587+
MOI.empty!(model)
1588+
@test MOI.is_empty(model)
1589+
1590+
# Restore only the cubic cache — is_empty must now return false
1591+
model.cubic_objective_cache = cubic_cache
1592+
@test !MOI.is_empty(model)
1593+
return
1594+
end
1595+
15701596
function test_jump_cubic_pvv_partial_pair_update()
15711597
# Two pvv terms with different parameters: p1*x*y and p2*x*z.
15721598
# Update only p1 -> delta_quadratic has only (x,y).

0 commit comments

Comments
 (0)