Skip to content

Commit

Permalink
Convert quadratic to affine even when there is no parameters (#166)
Browse files Browse the repository at this point in the history
* Convert quadratic to affine even when there is no parameters

* Fix format

* Add test

* Fixes
  • Loading branch information
blegat authored Jan 22, 2025
1 parent 42d5d6b commit 58c316e
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 3 deletions.
40 changes: 37 additions & 3 deletions src/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -492,13 +492,22 @@ function MOI.set(
end

function MOI.get(
model::Optimizer,
model::Optimizer{T},
attr::MOI.ConstraintFunction,
ci::MOI.ConstraintIndex,
)
) where {T}
if haskey(model.quadratic_outer_to_inner, ci)
inner_ci = model.quadratic_outer_to_inner[ci]
return _original_function(model.quadratic_constraint_cache[inner_ci])
if haskey(model.quadratic_constraint_cache, inner_ci)
return _original_function(
model.quadratic_constraint_cache[inner_ci],
)
else
return convert(
MOI.ScalarQuadraticFunction{T},
MOI.get(model.optimizer, attr, inner_ci),
)
end
elseif haskey(model.affine_outer_to_inner, ci)
inner_ci = model.affine_outer_to_inner[ci]
return _original_function(model.affine_constraint_cache[inner_ci])
Expand Down Expand Up @@ -781,6 +790,31 @@ function MOI.add_constraint(
set::MOI.AbstractScalarSet,
) where {T}
if !_has_parameters(f)
# The user might construct the expression `*(f::Vector{AffExpr}, p)`
# where `p` is a parameter. This results in a `Vector{QuadExpr}`
# and hence in `ScalarQuadraticFunction` constraints.
# If some entries of `f` are zero, then `has_parameters` will be zero for
# the resulting constraint. We should however still turn it into an affine
# function like the other entries.
if _is_affine(f)
fa = MOI.ScalarAffineFunction(f.affine_terms, f.constant)
inner_ci = MOI.Utilities.normalize_and_add_constraint(
model.optimizer,
fa,
set,
)
model.last_quad_add_added += 1
outer_ci =
MOI.ConstraintIndex{MOI.ScalarQuadraticFunction{T},typeof(set)}(
model.last_quad_add_added,
)
model.quadratic_outer_to_inner[outer_ci] = inner_ci
model.constraint_outer_to_inner[outer_ci] = inner_ci
model.quadratic_constraint_cache_set[inner_ci] = set
return outer_ci
else
return _add_constraint_direct_and_cache_map!(model, f, set)
end
return _add_constraint_direct_and_cache_map!(model, f, set)
else
return _add_constraint_with_parameters_on_function(model, f, set)
Expand Down
20 changes: 20 additions & 0 deletions test/moi_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1899,3 +1899,23 @@ function test_getters()
) isa POI.ParametricQuadraticFunction{Float64}
return
end

function test_no_quadratic_terms()
optimizer = POI.Optimizer(GLPK.Optimizer())
MOI.set(optimizer, MOI.Silent(), true)
x = MOI.add_variable(optimizer)
func = MOI.Utilities.canonical(1.0 * x * x + 1.0 * x - 1.0 * x * x)
set = MOI.LessThan(0.0)
c = MOI.add_constraint(optimizer, func, set)
@test c isa MOI.ConstraintIndex{typeof(func),typeof(set)}
@test MOI.get(optimizer, MOI.ConstraintFunction(), c) func
@test MOI.get(optimizer, MOI.ConstraintSet(), c) == set
MOI.set(optimizer, MOI.ConstraintName(), c, "name")
@test MOI.get(optimizer, MOI.ConstraintName(), c) == "name"
MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MAX_SENSE)
obj_func = 1.0 * x
MOI.set(optimizer, MOI.ObjectiveFunction{typeof(obj_func)}(), obj_func)
MOI.optimize!(optimizer)
@test MOI.get(optimizer, MOI.ConstraintDual(), c) -1 atol = ATOL
return
end

0 comments on commit 58c316e

Please sign in to comment.