From c470538a0dd6873516575f271a0a89445b97a2c4 Mon Sep 17 00:00:00 2001 From: joaquimg Date: Mon, 2 Mar 2026 20:34:32 -0300 Subject: [PATCH 1/2] VectorQuadratic as VectorAffine --- .gitignore | 1 + src/MOI_wrapper.jl | 17 +++++++++++++++++ test/test_MathOptInterface.jl | 19 +++++++++++++++++++ 3 files changed, 37 insertions(+) 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..c2b0f77d 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -1107,6 +1107,23 @@ 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() From ea3d79dac176bb1a23f5e964cc2cff6056c89e62 Mon Sep 17 00:00:00 2001 From: joaquimg Date: Mon, 2 Mar 2026 20:41:50 -0300 Subject: [PATCH 2/2] format --- src/MOI_wrapper.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index c2b0f77d..8df66e5c 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -1116,9 +1116,10 @@ function MOI.add_constraint( 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, - ) + 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