diff --git a/.gitignore b/.gitignore index 6137d672..71fbc57c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ docs/build docs/src/release_notes.md Manifest.toml .DS_Store +*.cov diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 5070b918..8df66e5c 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -1107,6 +1107,24 @@ function MOI.add_constraint( set::MOI.AbstractVectorSet, ) where {T} if !_has_parameters(f) + # The user might construct a VectorQuadraticFunction with no actual + # quadratic terms (e.g. from a JuMP expression that simplifies to + # affine). Convert to VectorAffineFunction while still recording the + # outer_to_inner map so that get(ConstraintFunction) can return the + # original VectorQuadraticFunction type. + if _is_vector_affine(f) + fa = MOI.VectorAffineFunction(f.affine_terms, f.constants) + inner_ci = MOI.add_constraint(model.optimizer, fa, set) + model.last_vec_quad_add_added += 1 + outer_ci = + MOI.ConstraintIndex{MOI.VectorQuadraticFunction{T},typeof(set)}( + model.last_vec_quad_add_added, + ) + model.vector_quadratic_outer_to_inner[outer_ci] = inner_ci + model.constraint_outer_to_inner[outer_ci] = inner_ci + model.vector_quadratic_constraint_cache_set[inner_ci] = set + return outer_ci + end return _add_constraint_direct_and_cache_map!(model, f, set) else return _add_constraint_with_parameters_on_function(model, f, set) diff --git a/test/test_MathOptInterface.jl b/test/test_MathOptInterface.jl index bc15cf5b..f3bb060a 100644 --- a/test/test_MathOptInterface.jl +++ b/test/test_MathOptInterface.jl @@ -2346,6 +2346,25 @@ function test_constraint_primal_start_get_for_parameter() return end +function test_vector_quadratic_no_parameters_affine_get_constraint_function() + # A VectorQuadraticFunction with no parameters and no quadratic terms + # (empty quadratic_terms) should use the affine fast path. The outer + # constraint index is a VectorQuadraticFunction index (preserving the + # original type), but the inner optimizer stores it as VectorAffine. + # get(ConstraintFunction) must reconstruct the original VQF. + model = POI.Optimizer(MOI.Utilities.Model{Float64}()) + x = MOI.add_variable(model) + quadratic_terms = MOI.VectorQuadraticTerm{Float64}[] + affine_terms = [MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(2.0, x))] + constants = [1.0] + f = MOI.VectorQuadraticFunction(quadratic_terms, affine_terms, constants) + ci = MOI.add_constraint(model, f, MOI.Zeros(1)) + @test MOI.is_valid(model, ci) + f2 = MOI.get(model, MOI.ConstraintFunction(), ci) + @test canonical_compare(f, f2) + return +end + end # module TestMathOptInterfaceTests.runtests()