From 084448f83103092362bbfa1155a70c2fe2d3c9f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Mon, 21 Sep 2026 08:21:08 +0200 Subject: [PATCH] Implement conversion to VectorNonlinearFunction --- src/functions.jl | 12 ++++ .../test_ScalarFunctionizeBridge.jl | 30 ++++++++ test/General/test_functions.jl | 71 +++++++++++++++++++ 3 files changed, 113 insertions(+) diff --git a/src/functions.jl b/src/functions.jl index 23cac64f07..0dd6328135 100644 --- a/src/functions.jl +++ b/src/functions.jl @@ -1469,6 +1469,18 @@ function Base.convert( ) end +# VectorNonlinearFunction + +function Base.convert( + ::Type{VectorNonlinearFunction}, + f::Union{VectorOfVariables,VectorAffineFunction,VectorQuadraticFunction}, +) + rows = ScalarNonlinearFunction[ + convert(ScalarNonlinearFunction, row) for row in Utilities.eachscalar(f) + ] + return VectorNonlinearFunction(rows) +end + for f in ( :ScalarAffineTerm, :ScalarAffineFunction, diff --git a/test/Bridges/Constraint/test_ScalarFunctionizeBridge.jl b/test/Bridges/Constraint/test_ScalarFunctionizeBridge.jl index 6f5e5fbf7a..eec295bef8 100644 --- a/test/Bridges/Constraint/test_ScalarFunctionizeBridge.jl +++ b/test/Bridges/Constraint/test_ScalarFunctionizeBridge.jl @@ -333,6 +333,36 @@ function test_FunctionConversionBridge() return end +function test_FunctionConversionBridge_VectorNonlinearFunction() + inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) + BT = MOI.Bridges.Constraint.FunctionConversionBridge{ + Float64, + MOI.VectorNonlinearFunction, + } + model = MOI.Bridges.Constraint.SingleBridgeOptimizer{BT}(inner) + x, y = MOI.add_variables(model, 2) + set = MOI.PositiveSemidefiniteConeTriangle(2) + for f in ( + MOI.VectorOfVariables([x, y, x]), + MOI.Utilities.vectorize([1.0 * x, 2.0 * y, 3.0 * x + 1.0]), + MOI.Utilities.vectorize([1.0 * x * x, 2.0 * x * y, 3.0 * y * y + 1.0]), + ) + @test MOI.supports_constraint(model, typeof(f), typeof(set)) + ci = MOI.add_constraint(model, f, set) + bridge = MOI.get(model, MOI.Bridges.FirstBridge(), ci) + @test MOI.get(inner, MOI.ConstraintFunction(), bridge.constraint) ≈ + convert(MOI.VectorNonlinearFunction, f) + @test MOI.get(model, MOI.ConstraintSet(), ci) == set + g = MOI.Utilities.eachscalar(f)[[3, 2, 1]] + MOI.set(model, MOI.ConstraintFunction(), ci, g) + @test MOI.get(inner, MOI.ConstraintFunction(), bridge.constraint) ≈ + convert(MOI.VectorNonlinearFunction, g) + MOI.delete(model, ci) + @test !MOI.is_valid(inner, bridge.constraint) + end + return +end + function test_canonical_constraint_function() inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) model = MOI.Bridges.Constraint.ScalarFunctionize{Float64}(inner) diff --git a/test/General/test_functions.jl b/test/General/test_functions.jl index adbb29e140..2a40a0ee35 100644 --- a/test/General/test_functions.jl +++ b/test/General/test_functions.jl @@ -491,6 +491,77 @@ function test_convert_VectorAffineFunction_VectorQuadraticFunction() return end +function test_convert_VectorNonlinearFunction_VectorOfVariables() + x, y = MOI.VariableIndex.(1:2) + for variables in ([y, x, y], MOI.VariableIndex[]) + f = MOI.VectorOfVariables(variables) + g = @inferred convert(MOI.VectorNonlinearFunction, f) + @test MOI.output_dimension(g) == length(variables) + @test g ≈ MOI.VectorNonlinearFunction([ + MOI.ScalarNonlinearFunction(:+, Any[v]) for v in variables + ]) + end + return +end + +function test_convert_VectorNonlinearFunction_VectorAffineFunction() + x, y = MOI.VariableIndex.(1:2) + for T in (Int, Float64, Rational{Int}) + f = MOI.Utilities.vectorize([ + T(2) * x + T(3) * y + T(4), + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{T}[], zero(T)), + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{T}[], T(5)), + ]) + g = @inferred convert(MOI.VectorNonlinearFunction, f) + @test MOI.output_dimension(g) == 3 + @test MOI.Utilities.eval_variables( + v -> T(v.value), + MOI.Utilities.Model{T}(), + g, + ) == T[12, 0, 5] + for (row, scalar) in zip(g.rows, MOI.Utilities.scalarize(f)) + @test row ≈ convert(MOI.ScalarNonlinearFunction, scalar) + end + empty_f = MOI.VectorAffineFunction(MOI.VectorAffineTerm{T}[], T[]) + empty_g = @inferred convert(MOI.VectorNonlinearFunction, empty_f) + @test isempty(empty_g.rows) + end + return +end + +function test_convert_VectorNonlinearFunction_VectorQuadraticFunction() + x, y = MOI.VariableIndex.(1:2) + for T in (Int, Float64, Rational{Int}) + f = MOI.Utilities.vectorize([ + T(2) * x * x + T(3) * x * y + T(4) * y + T(5), + MOI.ScalarQuadraticFunction( + MOI.ScalarQuadraticTerm{T}[], + MOI.ScalarAffineTerm{T}[], + zero(T), + ), + T(2) * y * y + T(1), + ]) + g = @inferred convert(MOI.VectorNonlinearFunction, f) + @test MOI.output_dimension(g) == 3 + @test MOI.Utilities.eval_variables( + v -> T(v.value), + MOI.Utilities.Model{T}(), + g, + ) == T[21, 0, 9] + for (row, scalar) in zip(g.rows, MOI.Utilities.scalarize(f)) + @test row ≈ convert(MOI.ScalarNonlinearFunction, scalar) + end + empty_f = MOI.VectorQuadraticFunction( + MOI.VectorQuadraticTerm{T}[], + MOI.VectorAffineTerm{T}[], + T[], + ) + empty_g = @inferred convert(MOI.VectorNonlinearFunction, empty_f) + @test isempty(empty_g.rows) + end + return +end + function test_copy_ScalarNonlinearFunction() N = 10_000 x = MOI.VariableIndex.(1:N)