Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions src/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ function MOI.is_empty(model::Optimizer)
# obj
model.affine_objective_cache === nothing &&
model.quadratic_objective_cache === nothing &&
model.cubic_objective_cache === nothing &&
MOI.is_empty(model.original_objective_cache) &&
#
isempty(model.vector_affine_constraint_cache) &&
Expand Down Expand Up @@ -878,7 +879,7 @@ function _add_constraint_with_parameters_on_function(
end
elseif model.constraints_interpretation == ONLY_CONSTRAINTS
poi_ci = MOI.add_constraint(model, pf, set)
elseif model.constraints_interpretation == BOUNDS_AND_CONSTRAINTS
else #if model.constraints_interpretation == BOUNDS_AND_CONSTRAINTS
if length(affine_variable_terms(pf)) == 1 &&
isone(MOI.coefficient(affine_variable_terms(pf)[]))
poi_ci = _add_vi_constraint(model, pf, set)
Expand Down Expand Up @@ -1886,23 +1887,25 @@ ParametricOptInterface.Optimizer{Float64, MOIU.Model{Float64}}
└ NumberOfConstraints: 0

julia> MOI.set(model, POI.ConstraintsInterpretation(), POI.ONLY_BOUNDS)
ONLY_BOUNDS::ConstraintsInterpretationCode = 1

julia> MOI.set(model, POI.ConstraintsInterpretation(), POI.ONLY_CONSTRAINTS)
ONLY_CONSTRAINTS::ConstraintsInterpretationCode = 0

julia> MOI.set(model, POI.ConstraintsInterpretation(), POI.BOUNDS_AND_CONSTRAINTS)
BOUNDS_AND_CONSTRAINTS::ConstraintsInterpretationCode = 2
```
"""
struct ConstraintsInterpretation <: MOI.AbstractOptimizerAttribute end

function MOI.get(model::Optimizer, ::ConstraintsInterpretation)
return model.constraints_interpretation
end

function MOI.set(
model::Optimizer,
::ConstraintsInterpretation,
value::ConstraintsInterpretationCode,
)
return model.constraints_interpretation = value
model.constraints_interpretation = value
return
end

#
Expand Down
2 changes: 1 addition & 1 deletion src/cubic_objective.jl
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ function MOI.set(
"optimizer. " *
"This may be due to unsupported features in the cubic " *
"expression. " *
"Original error: $(e.msg)",
"Original error: $(sprint(showerror, e))",
)
end

Expand Down
20 changes: 0 additions & 20 deletions src/update_parameters.jl
Original file line number Diff line number Diff line change
Expand Up @@ -50,24 +50,14 @@ function _update_affine_constraints!(
MOI.AbstractScalarSet,
},
) where {F,S<:Union{MOI.LessThan,MOI.GreaterThan,MOI.EqualTo},V}
# cis = MOI.ConstraintIndex{F,S}[]
# sets = S[]
# sizehint!(cis, length(affine_constraint_cache_inner))
# sizehint!(sets, length(affine_constraint_cache_inner))
for (inner_ci, pf) in affine_constraint_cache_inner
delta_constant = _delta_parametric_constant(model, pf)
if !iszero(delta_constant)
pf.current_constant += delta_constant
new_set = S(pf.set_constant - pf.current_constant)
# new_set = _set_with_new_constant(set, param_constant)
MOI.set(model.optimizer, MOI.ConstraintSet(), inner_ci, new_set)
# push!(cis, inner_ci)
# push!(sets, new_set)
end
end
# if !isempty(cis)
# MOI.set(model.optimizer, MOI.ConstraintSet(), cis, sets)
# end
return
end

Expand Down Expand Up @@ -192,19 +182,12 @@ function _update_quadratic_constraints!(
MOI.AbstractScalarSet,
},
) where {F,S<:Union{MOI.LessThan,MOI.GreaterThan,MOI.EqualTo},V}
# cis = MOI.ConstraintIndex{F,S}[]
# sets = S[]
# sizehint!(cis, length(quadratic_constraint_cache_inner))
# sizehint!(sets, length(quadratic_constraint_cache_inner))
for (inner_ci, pf) in quadratic_constraint_cache_inner
delta_constant = _delta_parametric_constant(model, pf)
if !iszero(delta_constant)
pf.current_constant += delta_constant
new_set = S(pf.set_constant - pf.current_constant)
# new_set = _set_with_new_constant(set, param_constant)
MOI.set(model.optimizer, MOI.ConstraintSet(), inner_ci, new_set)
# push!(cis, inner_ci)
# push!(sets, new_set)
end
delta_terms = _delta_parametric_affine_terms(model, pf)
if !isempty(delta_terms)
Expand All @@ -213,9 +196,6 @@ function _update_quadratic_constraints!(
MOI.modify(model.optimizer, cis, changes)
end
end
# if !isempty(cis)
# MOI.set(model.optimizer, MOI.ConstraintSet(), cis, sets)
# end
return
end

Expand Down
34 changes: 34 additions & 0 deletions test/test_MathOptInterface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2181,6 +2181,40 @@ function test_name_from_bound()
return
end

function test_constraints_interpretation_get_set()
model = POI.Optimizer(MOI.Utilities.Model{Float64}())
# Default value
@test MOI.get(model, POI.ConstraintsInterpretation()) ==
POI.ONLY_CONSTRAINTS
# Round-trip for each value
MOI.set(model, POI.ConstraintsInterpretation(), POI.ONLY_BOUNDS)
@test MOI.get(model, POI.ConstraintsInterpretation()) == POI.ONLY_BOUNDS
MOI.set(model, POI.ConstraintsInterpretation(), POI.ONLY_CONSTRAINTS)
@test MOI.get(model, POI.ConstraintsInterpretation()) ==
POI.ONLY_CONSTRAINTS
MOI.set(model, POI.ConstraintsInterpretation(), POI.BOUNDS_AND_CONSTRAINTS)
@test MOI.get(model, POI.ConstraintsInterpretation()) ==
POI.BOUNDS_AND_CONSTRAINTS
return
end

function test_only_bounds_coefficient_not_one_errors()
# ONLY_BOUNDS with coefficient ≠ 1 must error.
# The function must contain a parameter so that the parametric path is taken;
# a plain ScalarAffineFunction without parameters bypasses the check entirely.
model = POI.Optimizer(MOI.Utilities.Model{Float64}())
x = MOI.add_variable(model)
p, _ = MOI.add_constrained_variable(model, MOI.Parameter(1.0))
MOI.set(model, POI.ConstraintsInterpretation(), POI.ONLY_BOUNDS)
# 2.0 * x + 1.0 * p: single variable term with coefficient 2.0 ≠ 1 — must throw
f = MOI.ScalarAffineFunction(
[MOI.ScalarAffineTerm(2.0, x), MOI.ScalarAffineTerm(1.0, p)],
0.0,
)
@test_throws ErrorException MOI.add_constraint(model, f, MOI.LessThan(5.0))
return
end

function test_get_constraint_set()
model = POI.Optimizer(MOI.Utilities.Model{Float64}())
x = MOI.add_variable(model)
Expand Down
26 changes: 26 additions & 0 deletions test/test_cubic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1567,6 +1567,32 @@ function test_optimize_skips_update_without_parameter_change()
return
end

function test_is_empty_with_cubic_objective()
# Regression test: cubic_objective_cache must be checked in MOI.is_empty.
# Before the fix, is_empty ignored this cache and returned true incorrectly.
model = POI.Optimizer(HiGHS.Optimizer())
MOI.set(model, MOI.Silent(), true)
@test MOI.is_empty(model)

# Set a cubic objective to populate cubic_objective_cache
x = MOI.add_variable(model)
p, _ = MOI.add_constrained_variable(model, MOI.Parameter(1.0))
p_v = POI.v_idx(POI.p_idx(p))
f = MOI.ScalarNonlinearFunction(:*, Any[1.0, p_v, x, x])
MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarNonlinearFunction}(), f)
@test !MOI.is_empty(model)

# After full empty!, model must be empty
cubic_cache = model.cubic_objective_cache
MOI.empty!(model)
@test MOI.is_empty(model)

# Restore only the cubic cache — is_empty must now return false
model.cubic_objective_cache = cubic_cache
@test !MOI.is_empty(model)
return
end

function test_jump_cubic_pvv_partial_pair_update()
# Two pvv terms with different parameters: p1*x*y and p2*x*z.
# Update only p1 -> delta_quadratic has only (x,y).
Expand Down
Loading