diff --git a/test/Bridges/ComplementsVectorizeBridge.jl b/test/Bridges/ComplementsVectorizeBridge.jl deleted file mode 100644 index 313deb0..0000000 --- a/test/Bridges/ComplementsVectorizeBridge.jl +++ /dev/null @@ -1,131 +0,0 @@ -# Copyright (c) 2025 François Pacaud, Benoît Legat, and contributors -# -# Use of this source code is governed by an MIT-style license that can be found -# in the LICENSE.md file or at https://opensource.org/licenses/MIT. - -using Test -using JuMP -using MathOptComplements - -@testset "ComplementsVectorizeBridge" begin - @testset "GreaterThan → Nonnegatives" begin - MOI.Bridges.runtests( - MathOptComplements.Bridges.ComplementsVectorizeBridge{Float64}, - model -> begin - x, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) - y, _ = MOI.add_constrained_variable( - model, - MOI.GreaterThan(3.0), - ) - MOI.add_constraint( - model, - MOI.VectorOfVariables([x, y]), - MathOptComplements.ComplementsWithSetType{ - MOI.GreaterThan{Float64}, - }( - 2, - ), - ) - end, - model -> begin - x, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) - y, _ = MOI.add_constrained_variable( - model, - MOI.GreaterThan(3.0), - ) - f = MOI.VectorAffineFunction{Float64}( - [ - MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, x)), - MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, y)), - ], - [0.0, -3.0], - ) - MOI.add_constraint( - model, - f, - MathOptComplements.ComplementsWithSetType{MOI.Nonnegatives}( - 2, - ), - ) - end; - cannot_unbridge = true, - ) - end - - @testset "LessThan → Nonpositives" begin - MOI.Bridges.runtests( - MathOptComplements.Bridges.ComplementsVectorizeBridge{Float64}, - model -> begin - x, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) - y, _ = - MOI.add_constrained_variable(model, MOI.LessThan(-2.0)) - MOI.add_constraint( - model, - MOI.VectorOfVariables([x, y]), - MathOptComplements.ComplementsWithSetType{ - MOI.LessThan{Float64}, - }( - 2, - ), - ) - end, - model -> begin - x, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) - y, _ = - MOI.add_constrained_variable(model, MOI.LessThan(-2.0)) - f = MOI.VectorAffineFunction{Float64}( - [ - MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, x)), - MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, y)), - ], - [0.0, 2.0], - ) - MOI.add_constraint( - model, - f, - MathOptComplements.ComplementsWithSetType{MOI.Nonpositives}( - 2, - ), - ) - end; - cannot_unbridge = true, - ) - end - - @testset "EqualTo → Zeros" begin - MOI.Bridges.runtests( - MathOptComplements.Bridges.ComplementsVectorizeBridge{Float64}, - model -> begin - x, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) - y, _ = - MOI.add_constrained_variable(model, MOI.EqualTo(2.0)) - MOI.add_constraint( - model, - MOI.VectorOfVariables([x, y]), - MathOptComplements.ComplementsWithSetType{ - MOI.EqualTo{Float64}, - }( - 2, - ), - ) - end, - model -> begin - x, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) - y, _ = - MOI.add_constrained_variable(model, MOI.EqualTo(2.0)) - f = MOI.Utilities.operate( - vcat, - Float64, - 1.0 * x, - 1.0 * y - 2.0, - ) - MOI.add_constraint( - model, - f, - MathOptComplements.ComplementsWithSetType{MOI.Zeros}(2), - ) - end; - cannot_unbridge = true, - ) - end -end diff --git a/test/Bridges/FlipSignBridge.jl b/test/Bridges/FlipSignBridge.jl deleted file mode 100644 index 9d92684..0000000 --- a/test/Bridges/FlipSignBridge.jl +++ /dev/null @@ -1,154 +0,0 @@ -# Copyright (c) 2025 François Pacaud, Benoît Legat, and contributors -# -# Use of this source code is governed by an MIT-style license that can be found -# in the LICENSE.md file or at https://opensource.org/licenses/MIT. - -using Test -using JuMP -using MathOptComplements - -@testset "FlipSignBridge" begin - @testset "Nonnegatives → Nonpositives" begin - MOI.Bridges.runtests( - MathOptComplements.Bridges.FlipSignBridge, - model -> begin - x, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) - y, _ = MOI.add_constrained_variable( - model, - MOI.GreaterThan(0.0), - ) - MOI.add_constraint( - model, - MOI.VectorOfVariables([x, y]), - MathOptComplements.ComplementsWithSetType{MOI.Nonnegatives}( - 2, - ), - ) - end, - model -> begin - x, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) - y, _ = MOI.add_constrained_variable( - model, - MOI.GreaterThan(0.0), - ) - f = MOI.Utilities.operate(vcat, Float64, -1.0 * x, 1.0 * y) - MOI.add_constraint( - model, - f, - MathOptComplements.ComplementsWithSetType{MOI.Nonpositives}( - 2, - ), - ) - end; - cannot_unbridge = true, - ) - end - - @testset "Nonpositives → Nonnegatives" begin - MOI.Bridges.runtests( - MathOptComplements.Bridges.FlipSignBridge, - model -> begin - x, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) - y, _ = - MOI.add_constrained_variable(model, MOI.LessThan(0.0)) - MOI.add_constraint( - model, - MOI.VectorOfVariables([x, y]), - MathOptComplements.ComplementsWithSetType{MOI.Nonpositives}( - 2, - ), - ) - end, - model -> begin - x, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) - y, _ = - MOI.add_constrained_variable(model, MOI.LessThan(0.0)) - f = MOI.Utilities.operate(vcat, Float64, -1.0 * x, 1.0 * y) - MOI.add_constraint( - model, - f, - MathOptComplements.ComplementsWithSetType{MOI.Nonnegatives}( - 2, - ), - ) - end; - cannot_unbridge = true, - ) - end - - @testset "GreaterThan → LessThan" begin - MOI.Bridges.runtests( - MathOptComplements.Bridges.FlipSignBridge, - model -> begin - x, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) - y, _ = MOI.add_constrained_variable( - model, - MOI.GreaterThan(3.0), - ) - MOI.add_constraint( - model, - MOI.VectorOfVariables([x, y]), - MathOptComplements.ComplementsWithSetType{ - MOI.GreaterThan{Float64}, - }( - 2, - ), - ) - end, - model -> begin - x, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) - y, _ = MOI.add_constrained_variable( - model, - MOI.GreaterThan(3.0), - ) - f = MOI.Utilities.operate(vcat, Float64, -1.0 * x, 1.0 * y) - MOI.add_constraint( - model, - f, - MathOptComplements.ComplementsWithSetType{ - MOI.LessThan{Float64}, - }( - 2, - ), - ) - end; - cannot_unbridge = true, - ) - end - - @testset "LessThan → GreaterThan" begin - MOI.Bridges.runtests( - MathOptComplements.Bridges.FlipSignBridge, - model -> begin - x, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) - y, _ = - MOI.add_constrained_variable(model, MOI.LessThan(-2.0)) - MOI.add_constraint( - model, - MOI.VectorOfVariables([x, y]), - MathOptComplements.ComplementsWithSetType{ - MOI.LessThan{Float64}, - }( - 2, - ), - ) - end, - model -> begin - x, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) - y, _ = - MOI.add_constrained_variable(model, MOI.LessThan(-2.0)) - f = MOI.Utilities.operate(vcat, Float64, -1.0 * x, 1.0 * y) - MOI.add_constraint( - model, - f, - MathOptComplements.ComplementsWithSetType{ - MOI.GreaterThan{Float64}, - }( - 2, - ), - ) - end; - cannot_unbridge = true, - ) - end -end diff --git a/test/Bridges/NonlinearBridge.jl b/test/Bridges/NonlinearBridge.jl deleted file mode 100644 index da2a8f4..0000000 --- a/test/Bridges/NonlinearBridge.jl +++ /dev/null @@ -1,325 +0,0 @@ -# Copyright (c) 2025 François Pacaud, Benoît Legat, and contributors -# -# Use of this source code is governed by an MIT-style license that can be found -# in the LICENSE.md file or at https://opensource.org/licenses/MIT. - -using Test -using JuMP -using MathOptComplements - -@testset "NonlinearBridge" begin - @testset "Nonnegatives (lower bound)" begin - MOI.Bridges.runtests( - MathOptComplements.Bridges.NonlinearBridge, - model -> begin - x1, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) - x2, _ = MOI.add_constrained_variable( - model, - MOI.GreaterThan(0.0), - ) - MOI.add_constraint( - model, - MOI.VectorOfVariables([x1, x2]), - MathOptComplements.ComplementsWithSetType{MOI.Nonnegatives}( - 2, - ), - ) - end, - model -> begin - x1, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) - x2, _ = MOI.add_constrained_variable( - model, - MOI.GreaterThan(0.0), - ) - # x1 * (x2 - 0) <= 0 - MOI.add_constraint(model, x1 * (x2 - 0.0), MOI.LessThan(0.0)) - end; - cannot_unbridge = true, - ) - end - - @testset "Nonpositives (upper bound)" begin - MOI.Bridges.runtests( - MathOptComplements.Bridges.NonlinearBridge, - model -> begin - x1, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) - x2, _ = - MOI.add_constrained_variable(model, MOI.LessThan(0.0)) - MOI.add_constraint( - model, - MOI.VectorOfVariables([x1, x2]), - MathOptComplements.ComplementsWithSetType{MOI.Nonpositives}( - 2, - ), - ) - end, - model -> begin - x1, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) - x2, _ = - MOI.add_constrained_variable(model, MOI.LessThan(0.0)) - # x1 * (x2 - 0) <= 0 - MOI.add_constraint(model, x1 * (x2 - 0.0), MOI.LessThan(0.0)) - end; - cannot_unbridge = true, - ) - end - - @testset "GreaterThan with non-zero bound" begin - MOI.Bridges.runtests( - MathOptComplements.Bridges.NonlinearBridge, - model -> begin - x1, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) - x2, _ = MOI.add_constrained_variable( - model, - MOI.GreaterThan(3.0), - ) - MOI.add_constraint( - model, - MOI.VectorOfVariables([x1, x2]), - MathOptComplements.ComplementsWithSetType{ - MOI.GreaterThan{Float64}, - }( - 2, - ), - ) - end, - model -> begin - x1, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) - x2, _ = MOI.add_constrained_variable( - model, - MOI.GreaterThan(3.0), - ) - # x1 * (x2 - 3) <= 0 - MOI.add_constraint(model, x1 * (x2 - 3.0), MOI.LessThan(0.0)) - end; - cannot_unbridge = true, - ) - end - - @testset "Nonnegatives with unbounded x1 (lower bound)" begin - inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) - model = MOI.Bridges.Constraint.SingleBridgeOptimizer{ - MathOptComplements.Bridges.NonlinearBridge{Float64}, - }( - inner, - ) - x1 = MOI.add_variable(model) - x2, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) - MOI.add_constraint( - model, - MOI.VectorOfVariables([x1, x2]), - MathOptComplements.ComplementsWithSetType{MOI.Nonnegatives}(2), - ) - MOI.Bridges.final_touch(model) - target = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) - x1t, _ = MOI.add_constrained_variable(target, MOI.GreaterThan(0.0)) - x2t, _ = MOI.add_constrained_variable(target, MOI.GreaterThan(0.0)) - MOI.add_constraint(target, x1t * (x2t - 0.0), MOI.LessThan(0.0)) - MOI.Bridges._test_structural_identical(target, inner) - end - - @testset "Nonpositives with unbounded x1 (upper bound)" begin - inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) - model = MOI.Bridges.Constraint.SingleBridgeOptimizer{ - MathOptComplements.Bridges.NonlinearBridge{Float64}, - }( - inner, - ) - x1 = MOI.add_variable(model) - x2, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) - MOI.add_constraint( - model, - MOI.VectorOfVariables([x1, x2]), - MathOptComplements.ComplementsWithSetType{MOI.Nonpositives}(2), - ) - MOI.Bridges.final_touch(model) - target = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) - x1t, _ = MOI.add_constrained_variable(target, MOI.LessThan(0.0)) - x2t, _ = MOI.add_constrained_variable(target, MOI.LessThan(0.0)) - MOI.add_constraint(target, x1t * (x2t - 0.0), MOI.LessThan(0.0)) - MOI.Bridges._test_structural_identical(target, inner) - end - - @testset "LessThan with non-zero bound" begin - MOI.Bridges.runtests( - MathOptComplements.Bridges.NonlinearBridge, - model -> begin - x1, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) - x2, _ = - MOI.add_constrained_variable(model, MOI.LessThan(-2.0)) - MOI.add_constraint( - model, - MOI.VectorOfVariables([x1, x2]), - MathOptComplements.ComplementsWithSetType{ - MOI.LessThan{Float64}, - }( - 2, - ), - ) - end, - model -> begin - x1, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) - x2, _ = - MOI.add_constrained_variable(model, MOI.LessThan(-2.0)) - # x1 * (x2 - (-2)) <= 0 - MOI.add_constraint(model, x1 * (x2 + 2.0), MOI.LessThan(0.0)) - end; - cannot_unbridge = true, - ) - end - - @testset "FB: Nonnegatives with unbounded x1" begin - inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) - model = MOI.Bridges.Constraint.SingleBridgeOptimizer{ - MathOptComplements.Bridges.NonlinearBridge{Float64}, - }( - inner, - ) - x1 = MOI.add_variable(model) - x2, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) - ci = MOI.add_constraint( - model, - MOI.VectorOfVariables([x1, x2]), - MathOptComplements.ComplementsWithSetType{MOI.Nonnegatives}(2), - ) - relax = MathOptComplements.FischerBurmeisterRelaxation(1e-8) - MOI.set( - model, - MathOptComplements.ComplementarityReformulation(), - ci, - relax, - ) - MOI.Bridges.final_touch(model) - @test MOI.get( - inner, - MOI.NumberOfConstraints{ - MOI.VariableIndex, - MOI.GreaterThan{Float64}, - }(), - ) == 2 - @test MOI.get( - inner, - MOI.NumberOfConstraints{ - MOI.ScalarNonlinearFunction, - MOI.LessThan{Float64}, - }(), - ) == 1 - end - - @testset "FB: Nonpositives with unbounded x1" begin - inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) - model = MOI.Bridges.Constraint.SingleBridgeOptimizer{ - MathOptComplements.Bridges.NonlinearBridge{Float64}, - }( - inner, - ) - x1 = MOI.add_variable(model) - x2, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) - ci = MOI.add_constraint( - model, - MOI.VectorOfVariables([x1, x2]), - MathOptComplements.ComplementsWithSetType{MOI.Nonpositives}(2), - ) - relax = MathOptComplements.FischerBurmeisterRelaxation(1e-8) - MOI.set( - model, - MathOptComplements.ComplementarityReformulation(), - ci, - relax, - ) - MOI.Bridges.final_touch(model) - @test MOI.get( - inner, - MOI.NumberOfConstraints{MOI.VariableIndex,MOI.LessThan{Float64}}(), - ) == 2 - @test MOI.get( - inner, - MOI.NumberOfConstraints{ - MOI.ScalarNonlinearFunction, - MOI.GreaterThan{Float64}, - }(), - ) == 1 - end - - @testset "KS: Nonnegatives with unbounded x1" begin - inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) - model = MOI.Bridges.Constraint.SingleBridgeOptimizer{ - MathOptComplements.Bridges.NonlinearBridge{Float64}, - }( - inner, - ) - x1 = MOI.add_variable(model) - x2, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) - ci = MOI.add_constraint( - model, - MOI.VectorOfVariables([x1, x2]), - MathOptComplements.ComplementsWithSetType{MOI.Nonnegatives}(2), - ) - relax = MathOptComplements.KanzowSchwarzRelaxation(1e-8) - MOI.set( - model, - MathOptComplements.ComplementarityReformulation(), - ci, - relax, - ) - MOI.Bridges.final_touch(model) - @test MOI.get( - inner, - MOI.NumberOfConstraints{ - MOI.VariableIndex, - MOI.GreaterThan{Float64}, - }(), - ) == 2 - end - - @testset "KS: Nonpositives with unbounded x1" begin - inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) - model = MOI.Bridges.Constraint.SingleBridgeOptimizer{ - MathOptComplements.Bridges.NonlinearBridge{Float64}, - }( - inner, - ) - x1 = MOI.add_variable(model) - x2, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) - ci = MOI.add_constraint( - model, - MOI.VectorOfVariables([x1, x2]), - MathOptComplements.ComplementsWithSetType{MOI.Nonpositives}(2), - ) - relax = MathOptComplements.KanzowSchwarzRelaxation(1e-8) - MOI.set( - model, - MathOptComplements.ComplementarityReformulation(), - ci, - relax, - ) - MOI.Bridges.final_touch(model) - @test MOI.get( - inner, - MOI.NumberOfConstraints{MOI.VariableIndex,MOI.LessThan{Float64}}(), - ) == 2 - end - - @testset "Zeros (equality)" begin - MOI.Bridges.runtests( - MathOptComplements.Bridges.NonlinearBridge, - model -> begin - x1 = MOI.add_variable(model) - x2 = MOI.add_variable(model) - MOI.add_constraint( - model, - MOI.VectorOfVariables([x1, x2]), - MathOptComplements.ComplementsWithSetType{MOI.Zeros}(2), - ) - end, - model -> begin - x1 = MOI.add_variable(model) - x2 = MOI.add_variable(model) - # Range relaxation with lb=0, ub=0: two constraints - MOI.add_constraint(model, x1 * (x2 - 0.0), MOI.LessThan(0.0)) - MOI.add_constraint(model, x1 * (x2 - 0.0), MOI.LessThan(0.0)) - end; - cannot_unbridge = true, - ) - end -end diff --git a/test/Bridges/SpecifySetTypeBridge.jl b/test/Bridges/SpecifySetTypeBridge.jl deleted file mode 100644 index ae35ec6..0000000 --- a/test/Bridges/SpecifySetTypeBridge.jl +++ /dev/null @@ -1,200 +0,0 @@ -# Copyright (c) 2025 François Pacaud, Benoît Legat, and contributors -# -# Use of this source code is governed by an MIT-style license that can be found -# in the LICENSE.md file or at https://opensource.org/licenses/MIT. - -using Test -using JuMP -using MathOptComplements - -@testset "SpecifySetTypeBridge" begin - @testset "Lower bound (Nonnegatives)" begin - MOI.Bridges.runtests( - MathOptComplements.Bridges.SpecifySetTypeBridge, - model -> begin - x, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) - y, _ = MOI.add_constrained_variable( - model, - MOI.GreaterThan(0.0), - ) - MOI.add_constraint( - model, - MOI.VectorOfVariables([x, y]), - MOI.Complements(2), - ) - end, - model -> begin - x, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) - y, _ = MOI.add_constrained_variable( - model, - MOI.GreaterThan(0.0), - ) - MOI.add_constraint( - model, - MOI.VectorOfVariables([x, y]), - MathOptComplements.ComplementsWithSetType{MOI.Nonnegatives}( - 2, - ), - ) - end; - cannot_unbridge = true, - ) - end - - @testset "Lower bound (GreaterThan)" begin - MOI.Bridges.runtests( - MathOptComplements.Bridges.SpecifySetTypeBridge, - model -> begin - x = MOI.add_variable(model) - y, _ = MOI.add_constrained_variable( - model, - MOI.GreaterThan(3.0), - ) - MOI.add_constraint( - model, - MOI.VectorOfVariables([x, y]), - MOI.Complements(2), - ) - end, - model -> begin - x, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) - y, _ = MOI.add_constrained_variable( - model, - MOI.GreaterThan(3.0), - ) - MOI.add_constraint( - model, - MOI.VectorOfVariables([x, y]), - MathOptComplements.ComplementsWithSetType{ - MOI.GreaterThan{Float64}, - }( - 2, - ), - ) - end; - cannot_unbridge = true, - ) - end - - @testset "Upper bound (Nonpositives)" begin - MOI.Bridges.runtests( - MathOptComplements.Bridges.SpecifySetTypeBridge, - model -> begin - x = MOI.add_variable(model) - y, _ = - MOI.add_constrained_variable(model, MOI.LessThan(0.0)) - MOI.add_constraint( - model, - MOI.VectorOfVariables([x, y]), - MOI.Complements(2), - ) - end, - model -> begin - x, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) - y, _ = - MOI.add_constrained_variable(model, MOI.LessThan(0.0)) - MOI.add_constraint( - model, - MOI.VectorOfVariables([x, y]), - MathOptComplements.ComplementsWithSetType{MOI.Nonpositives}( - 2, - ), - ) - end; - cannot_unbridge = true, - ) - end - - @testset "Upper bound (LessThan)" begin - MOI.Bridges.runtests( - MathOptComplements.Bridges.SpecifySetTypeBridge, - model -> begin - x = MOI.add_variable(model) - y, _ = - MOI.add_constrained_variable(model, MOI.LessThan(-2.0)) - MOI.add_constraint( - model, - MOI.VectorOfVariables([x, y]), - MOI.Complements(2), - ) - end, - model -> begin - x, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) - y, _ = - MOI.add_constrained_variable(model, MOI.LessThan(-2.0)) - MOI.add_constraint( - model, - MOI.VectorOfVariables([x, y]), - MathOptComplements.ComplementsWithSetType{ - MOI.LessThan{Float64}, - }( - 2, - ), - ) - end; - cannot_unbridge = true, - ) - end - - @testset "Range (Interval)" begin - MOI.Bridges.runtests( - MathOptComplements.Bridges.SpecifySetTypeBridge, - model -> begin - x = MOI.add_variable(model) - y, _ = MOI.add_constrained_variable( - model, - MOI.Interval(0.0, 1.0), - ) - MOI.add_constraint( - model, - MOI.VectorOfVariables([x, y]), - MOI.Complements(2), - ) - end, - model -> begin - x = MOI.add_variable(model) - y, _ = MOI.add_constrained_variable( - model, - MOI.Interval(0.0, 1.0), - ) - MOI.add_constraint( - model, - MOI.VectorOfVariables([x, y]), - MathOptComplements.ComplementsWithSetType{ - MOI.Interval{Float64}, - }( - 2, - ), - ) - end; - cannot_unbridge = true, - ) - end - - @testset "Free variable (Zeros)" begin - MOI.Bridges.runtests( - MathOptComplements.Bridges.SpecifySetTypeBridge, - model -> begin - x = MOI.add_variable(model) - y = MOI.add_variable(model) - MOI.add_constraint( - model, - MOI.VectorOfVariables([x, y]), - MOI.Complements(2), - ) - end, - model -> begin - x = MOI.add_variable(model) - y = MOI.add_variable(model) - # x1 must be zero - MOI.add_constraint(model, 1.0 * x, MOI.EqualTo(0.0)) - MOI.add_constraint( - model, - MOI.VectorOfVariables([x, y]), - MathOptComplements.ComplementsWithSetType{MOI.Zeros}(2), - ) - end; - cannot_unbridge = true, - ) - end -end diff --git a/test/Bridges/SplitIntervalBridge.jl b/test/Bridges/SplitIntervalBridge.jl deleted file mode 100644 index 5dfb092..0000000 --- a/test/Bridges/SplitIntervalBridge.jl +++ /dev/null @@ -1,137 +0,0 @@ -# Copyright (c) 2025 François Pacaud, Benoît Legat, and contributors -# -# Use of this source code is governed by an MIT-style license that can be found -# in the LICENSE.md file or at https://opensource.org/licenses/MIT. - -using Test -using JuMP -using MathOptComplements - -@testset "SplitIntervalBridge" begin - @testset "VectorOfVariables input" begin - MOI.Bridges.runtests( - MathOptComplements.Bridges.SplitIntervalBridge, - model -> begin - x = MOI.add_variable(model) - y, _ = MOI.add_constrained_variable( - model, - MOI.Interval(0.0, 1.0), - ) - MOI.add_constraint( - model, - MOI.VectorOfVariables([x, y]), - MathOptComplements.ComplementsWithSetType{ - MOI.Interval{Float64}, - }( - 2, - ), - ) - end, - model -> begin - x = MOI.add_variable(model) - y, _ = MOI.add_constrained_variable( - model, - MOI.Interval(0.0, 1.0), - ) - xp, _ = MOI.add_constrained_variable( - model, - MOI.GreaterThan(0.0), - ) - xn, _ = - MOI.add_constrained_variable(model, MOI.LessThan(0.0)) - # x == xp + xn → x - xp - xn == 0 - MOI.add_constraint( - model, - 1.0 * x - 1.0 * xp - 1.0 * xn, - MOI.EqualTo(0.0), - ) - MOI.add_constraint( - model, - MOI.VectorOfVariables([xp, y]), - MathOptComplements.ComplementsWithSetType{ - MOI.GreaterThan{Float64}, - }( - 2, - ), - ) - MOI.add_constraint( - model, - MOI.VectorOfVariables([xn, y]), - MathOptComplements.ComplementsWithSetType{ - MOI.LessThan{Float64}, - }( - 2, - ), - ) - end; - cannot_unbridge = true, - ) - end - - @testset "VectorAffineFunction input" begin - MOI.Bridges.runtests( - MathOptComplements.Bridges.SplitIntervalBridge, - model -> begin - x = MOI.add_variable(model) - y, _ = MOI.add_constrained_variable( - model, - MOI.Interval(0.0, 1.0), - ) - f = MOI.VectorAffineFunction{Float64}( - [ - MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(2.0, x)), - MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, y)), - ], - [1.0, 0.0], - ) - MOI.add_constraint( - model, - f, - MathOptComplements.ComplementsWithSetType{ - MOI.Interval{Float64}, - }( - 2, - ), - ) - end, - model -> begin - x = MOI.add_variable(model) - y, _ = MOI.add_constrained_variable( - model, - MOI.Interval(0.0, 1.0), - ) - xp, _ = MOI.add_constrained_variable( - model, - MOI.GreaterThan(0.0), - ) - xn, _ = - MOI.add_constrained_variable(model, MOI.LessThan(0.0)) - # 2x + 1 == xp + xn → 2x - xp - xn == -1 - MOI.add_constraint( - model, - 2.0 * x - 1.0 * xp - 1.0 * xn, - MOI.EqualTo(-1.0), - ) - MOI.add_constraint( - model, - MOI.VectorOfVariables([xp, y]), - MathOptComplements.ComplementsWithSetType{ - MOI.GreaterThan{Float64}, - }( - 2, - ), - ) - MOI.add_constraint( - model, - MOI.VectorOfVariables([xn, y]), - MathOptComplements.ComplementsWithSetType{ - MOI.LessThan{Float64}, - }( - 2, - ), - ) - end; - cannot_unbridge = true, - ) - end -end diff --git a/test/Bridges/VerticalBridge.jl b/test/Bridges/VerticalBridge.jl deleted file mode 100644 index 7a7525b..0000000 --- a/test/Bridges/VerticalBridge.jl +++ /dev/null @@ -1,101 +0,0 @@ -# Copyright (c) 2025 François Pacaud, Benoît Legat, and contributors -# -# Use of this source code is governed by an MIT-style license that can be found -# in the LICENSE.md file or at https://opensource.org/licenses/MIT. - -using Test -using JuMP -using MathOptComplements - -@testset "VerticalBridge" begin - @testset "Unbounded RHS with constant in LHS (Complements)" begin - MOI.Bridges.runtests( - MathOptComplements.Bridges.VerticalBridge, - model -> begin - x = MOI.add_variable(model) - z = MOI.add_variable(model) - y = MOI.add_variable(model) - w, _ = MOI.add_constrained_variable( - model, - MOI.GreaterThan(0.0), - ) - f = MOI.VectorAffineFunction{Float64}( - [ - MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(2.0, x)), - MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, z)), - MOI.VectorAffineTerm(3, MOI.ScalarAffineTerm(1.0, y)), - MOI.VectorAffineTerm(4, MOI.ScalarAffineTerm(1.0, w)), - ], - [3.0, 0.0, 0.0, 0.0], - ) - MOI.add_constraint(model, f, MOI.Complements(4)) - end, - model -> begin - x = MOI.add_variable(model) - z = MOI.add_variable(model) - y = MOI.add_variable(model) - w, _ = MOI.add_constrained_variable( - model, - MOI.GreaterThan(0.0), - ) - # 2x + 3 = 0 → 2x in EqualTo(-3.0) (y is unbounded) - MOI.add_constraint(model, 2.0 * x, MOI.EqualTo(-3.0)) - MOI.add_constraint( - model, - MOI.VectorOfVariables([z, w]), - MOI.Complements(2), - ) - end; - cannot_unbridge = true, - ) - end - - @testset "Expression LHS with constant (ComplementsWithSetType)" begin - MOI.Bridges.runtests( - MathOptComplements.Bridges.VerticalBridge, - model -> begin - x = MOI.add_variable(model) - y, _ = MOI.add_constrained_variable( - model, - MOI.GreaterThan(0.0), - ) - f = MOI.VectorAffineFunction{Float64}( - [ - MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(2.0, x)), - MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, y)), - ], - [3.0, 0.0], - ) - MOI.add_constraint( - model, - f, - MathOptComplements.ComplementsWithSetType{MOI.Nonnegatives}( - 2, - ), - ) - end, - model -> begin - x = MOI.add_variable(model) - y, _ = MOI.add_constrained_variable( - model, - MOI.GreaterThan(0.0), - ) - x1 = MOI.add_variable(model) - # 2x + 3 = x1 → 2x - x1 in EqualTo(-3.0) - MOI.add_constraint( - model, - 2.0 * x - 1.0 * x1, - MOI.EqualTo(-3.0), - ) - MOI.add_constraint( - model, - MOI.VectorOfVariables([x1, y]), - MathOptComplements.ComplementsWithSetType{MOI.Nonnegatives}( - 2, - ), - ) - end; - cannot_unbridge = true, - ) - end -end diff --git a/test/Bridges/runtests.jl b/test/Bridges/runtests.jl deleted file mode 100644 index 29f3660..0000000 --- a/test/Bridges/runtests.jl +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) 2025 François Pacaud, Benoît Legat, and contributors -# -# Use of this source code is governed by an MIT-style license that can be found -# in the LICENSE.md file or at https://opensource.org/licenses/MIT. - -using Test - -files_to_exclude = ["runtests.jl"] -for file in readdir(@__DIR__) - if isdir(joinpath(@__DIR__, file)) - include(joinpath(@__DIR__, file, "runtests.jl")) - end - if !endswith(file, ".jl") || any(isequal(file), files_to_exclude) - continue - end - @testset "$(file)" begin - include(joinpath(@__DIR__, file)) - end -end diff --git a/test/Bridges/test_ComplementsVectorizeBridge.jl b/test/Bridges/test_ComplementsVectorizeBridge.jl new file mode 100644 index 0000000..9ade496 --- /dev/null +++ b/test/Bridges/test_ComplementsVectorizeBridge.jl @@ -0,0 +1,126 @@ +# Copyright (c) 2025 François Pacaud, Benoît Legat, and contributors +# +# Use of this source code is governed by an MIT-style license that can be found +# in the LICENSE.md file or at https://opensource.org/licenses/MIT. + +module TestComplementsVectorizeBridge + +using Test + +import MathOptComplements +import MathOptInterface as MOI + +function runtests() + is_test(name) = startswith("$name", "test_") + @testset "$name" for name in filter(is_test, names(@__MODULE__; all = true)) + getfield(@__MODULE__, name)() + end + return +end + +function test_GreaterThan_Nonnegatives() + MOI.Bridges.runtests( + MathOptComplements.Bridges.ComplementsVectorizeBridge{Float64}, + model -> begin + x, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + y, _ = + MOI.add_constrained_variable(model, MOI.GreaterThan(3.0)) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x, y]), + MathOptComplements.ComplementsWithSetType{ + MOI.GreaterThan{Float64}, + }( + 2, + ), + ) + end, + model -> begin + x, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + y, _ = + MOI.add_constrained_variable(model, MOI.GreaterThan(3.0)) + f = MOI.VectorAffineFunction{Float64}( + [ + MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, x)), + MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, y)), + ], + [0.0, -3.0], + ) + MOI.add_constraint( + model, + f, + MathOptComplements.ComplementsWithSetType{MOI.Nonnegatives}(2), + ) + end; + cannot_unbridge = true, + ) + return +end + +function test_LessThan_Nonpositives() + MOI.Bridges.runtests( + MathOptComplements.Bridges.ComplementsVectorizeBridge{Float64}, + model -> begin + x, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) + y, _ = MOI.add_constrained_variable(model, MOI.LessThan(-2.0)) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x, y]), + MathOptComplements.ComplementsWithSetType{MOI.LessThan{Float64}}( + 2, + ), + ) + end, + model -> begin + x, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) + y, _ = MOI.add_constrained_variable(model, MOI.LessThan(-2.0)) + f = MOI.VectorAffineFunction{Float64}( + [ + MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, x)), + MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, y)), + ], + [0.0, 2.0], + ) + MOI.add_constraint( + model, + f, + MathOptComplements.ComplementsWithSetType{MOI.Nonpositives}(2), + ) + end; + cannot_unbridge = true, + ) + return +end + +function test_EqualTo_Zeros() + MOI.Bridges.runtests( + MathOptComplements.Bridges.ComplementsVectorizeBridge{Float64}, + model -> begin + x, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + y, _ = MOI.add_constrained_variable(model, MOI.EqualTo(2.0)) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x, y]), + MathOptComplements.ComplementsWithSetType{MOI.EqualTo{Float64}}( + 2, + ), + ) + end, + model -> begin + x, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + y, _ = MOI.add_constrained_variable(model, MOI.EqualTo(2.0)) + f = MOI.Utilities.operate(vcat, Float64, 1.0 * x, 1.0 * y - 2.0) + MOI.add_constraint( + model, + f, + MathOptComplements.ComplementsWithSetType{MOI.Zeros}(2), + ) + end; + cannot_unbridge = true, + ) + return +end + +end # TestComplementsVectorizeBridge + +TestComplementsVectorizeBridge.runtests() diff --git a/test/Bridges/test_FlipSignBridge.jl b/test/Bridges/test_FlipSignBridge.jl new file mode 100644 index 0000000..21a7b3c --- /dev/null +++ b/test/Bridges/test_FlipSignBridge.jl @@ -0,0 +1,146 @@ +# Copyright (c) 2025 François Pacaud, Benoît Legat, and contributors +# +# Use of this source code is governed by an MIT-style license that can be found +# in the LICENSE.md file or at https://opensource.org/licenses/MIT. + +module TestFlipSignBridge + +using Test +import MathOptComplements +import MathOptInterface as MOI + +function runtests() + is_test(name) = startswith("$name", "test_") + @testset "$name" for name in filter(is_test, names(@__MODULE__; all = true)) + getfield(@__MODULE__, name)() + end + return +end + +function test_Nonnegatives_Nonpositives() + MOI.Bridges.runtests( + MathOptComplements.Bridges.FlipSignBridge, + model -> begin + x, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + y, _ = + MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x, y]), + MathOptComplements.ComplementsWithSetType{MOI.Nonnegatives}(2), + ) + end, + model -> begin + x, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + y, _ = + MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + f = MOI.Utilities.operate(vcat, Float64, -1.0 * x, 1.0 * y) + MOI.add_constraint( + model, + f, + MathOptComplements.ComplementsWithSetType{MOI.Nonpositives}(2), + ) + end; + cannot_unbridge = true, + ) + return +end + +function test_Nonpositives_Nonnegatives() + MOI.Bridges.runtests( + MathOptComplements.Bridges.FlipSignBridge, + model -> begin + x, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) + y, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x, y]), + MathOptComplements.ComplementsWithSetType{MOI.Nonpositives}(2), + ) + end, + model -> begin + x, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) + y, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) + f = MOI.Utilities.operate(vcat, Float64, -1.0 * x, 1.0 * y) + MOI.add_constraint( + model, + f, + MathOptComplements.ComplementsWithSetType{MOI.Nonnegatives}(2), + ) + end; + cannot_unbridge = true, + ) + return +end + +function test_GreaterThan_LessThan() + MOI.Bridges.runtests( + MathOptComplements.Bridges.FlipSignBridge, + model -> begin + x, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + y, _ = + MOI.add_constrained_variable(model, MOI.GreaterThan(3.0)) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x, y]), + MathOptComplements.ComplementsWithSetType{ + MOI.GreaterThan{Float64}, + }( + 2, + ), + ) + end, + model -> begin + x, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + y, _ = + MOI.add_constrained_variable(model, MOI.GreaterThan(3.0)) + f = MOI.Utilities.operate(vcat, Float64, -1.0 * x, 1.0 * y) + MOI.add_constraint( + model, + f, + MathOptComplements.ComplementsWithSetType{MOI.LessThan{Float64}}( + 2, + ), + ) + end; + cannot_unbridge = true, + ) + return +end + +function test_LessThan_GreaterThan() + MOI.Bridges.runtests( + MathOptComplements.Bridges.FlipSignBridge, + model -> begin + x, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) + y, _ = MOI.add_constrained_variable(model, MOI.LessThan(-2.0)) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x, y]), + MathOptComplements.ComplementsWithSetType{MOI.LessThan{Float64}}( + 2, + ), + ) + end, + model -> begin + x, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) + y, _ = MOI.add_constrained_variable(model, MOI.LessThan(-2.0)) + f = MOI.Utilities.operate(vcat, Float64, -1.0 * x, 1.0 * y) + MOI.add_constraint( + model, + f, + MathOptComplements.ComplementsWithSetType{ + MOI.GreaterThan{Float64}, + }( + 2, + ), + ) + end; + cannot_unbridge = true, + ) + return +end + +end # TestFlipSignBridge + +TestFlipSignBridge.runtests() diff --git a/test/Bridges/test_NonlinearBridge.jl b/test/Bridges/test_NonlinearBridge.jl new file mode 100644 index 0000000..66dd8d3 --- /dev/null +++ b/test/Bridges/test_NonlinearBridge.jl @@ -0,0 +1,305 @@ +# Copyright (c) 2025 François Pacaud, Benoît Legat, and contributors +# +# Use of this source code is governed by an MIT-style license that can be found +# in the LICENSE.md file or at https://opensource.org/licenses/MIT. + +module TestNonlinearBridge + +using Test + +import MathOptComplements +import MathOptInterface as MOI + +function runtests() + is_test(name) = startswith("$name", "test_") + @testset "$name" for name in filter(is_test, names(@__MODULE__; all = true)) + getfield(@__MODULE__, name)() + end + return +end + +function test_Nonnegatives_lower_bound() + MOI.Bridges.runtests( + MathOptComplements.Bridges.NonlinearBridge, + model -> begin + x1, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + x2, _ = + MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x1, x2]), + MathOptComplements.ComplementsWithSetType{MOI.Nonnegatives}(2), + ) + end, + model -> begin + x1, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + x2, _ = + MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + # x1 * (x2 - 0) <= 0 + MOI.add_constraint(model, x1 * (x2 - 0.0), MOI.LessThan(0.0)) + end; + cannot_unbridge = true, + ) + return +end + +function test_Nonpositives_upper_bound() + MOI.Bridges.runtests( + MathOptComplements.Bridges.NonlinearBridge, + model -> begin + x1, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) + x2, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x1, x2]), + MathOptComplements.ComplementsWithSetType{MOI.Nonpositives}(2), + ) + end, + model -> begin + x1, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) + x2, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) + # x1 * (x2 - 0) <= 0 + MOI.add_constraint(model, x1 * (x2 - 0.0), MOI.LessThan(0.0)) + end; + cannot_unbridge = true, + ) + return +end + +function test_GreaterThan_with_nonzero_bound() + MOI.Bridges.runtests( + MathOptComplements.Bridges.NonlinearBridge, + model -> begin + x1, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + x2, _ = + MOI.add_constrained_variable(model, MOI.GreaterThan(3.0)) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x1, x2]), + MathOptComplements.ComplementsWithSetType{ + MOI.GreaterThan{Float64}, + }( + 2, + ), + ) + end, + model -> begin + x1, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + x2, _ = + MOI.add_constrained_variable(model, MOI.GreaterThan(3.0)) + # x1 * (x2 - 3) <= 0 + MOI.add_constraint(model, x1 * (x2 - 3.0), MOI.LessThan(0.0)) + end; + cannot_unbridge = true, + ) + return +end + +function test_Nonnegatives_with_unbounded_x1_lower_bound() + inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) + model = MOI.Bridges.Constraint.SingleBridgeOptimizer{ + MathOptComplements.Bridges.NonlinearBridge{Float64}, + }( + inner, + ) + x1 = MOI.add_variable(model) + x2, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x1, x2]), + MathOptComplements.ComplementsWithSetType{MOI.Nonnegatives}(2), + ) + MOI.Bridges.final_touch(model) + target = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) + x1t, _ = MOI.add_constrained_variable(target, MOI.GreaterThan(0.0)) + x2t, _ = MOI.add_constrained_variable(target, MOI.GreaterThan(0.0)) + MOI.add_constraint(target, x1t * (x2t - 0.0), MOI.LessThan(0.0)) + MOI.Bridges._test_structural_identical(target, inner) + return +end + +function test_Nonpositives_with_unbounded_x1_upper_bound() + inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) + model = MOI.Bridges.Constraint.SingleBridgeOptimizer{ + MathOptComplements.Bridges.NonlinearBridge{Float64}, + }( + inner, + ) + x1 = MOI.add_variable(model) + x2, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x1, x2]), + MathOptComplements.ComplementsWithSetType{MOI.Nonpositives}(2), + ) + MOI.Bridges.final_touch(model) + target = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) + x1t, _ = MOI.add_constrained_variable(target, MOI.LessThan(0.0)) + x2t, _ = MOI.add_constrained_variable(target, MOI.LessThan(0.0)) + MOI.add_constraint(target, x1t * (x2t - 0.0), MOI.LessThan(0.0)) + MOI.Bridges._test_structural_identical(target, inner) + return +end + +function test_LessThan_with_nonzero_bound() + MOI.Bridges.runtests( + MathOptComplements.Bridges.NonlinearBridge, + model -> begin + x1, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) + x2, _ = MOI.add_constrained_variable(model, MOI.LessThan(-2.0)) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x1, x2]), + MathOptComplements.ComplementsWithSetType{MOI.LessThan{Float64}}( + 2, + ), + ) + end, + model -> begin + x1, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) + x2, _ = MOI.add_constrained_variable(model, MOI.LessThan(-2.0)) + # x1 * (x2 - (-2)) <= 0 + MOI.add_constraint(model, x1 * (x2 + 2.0), MOI.LessThan(0.0)) + end; + cannot_unbridge = true, + ) + return +end + +function test_FB_Nonnegatives_with_unbounded_x1() + inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) + model = MOI.Bridges.Constraint.SingleBridgeOptimizer{ + MathOptComplements.Bridges.NonlinearBridge{Float64}, + }( + inner, + ) + x1 = MOI.add_variable(model) + x2, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + ci = MOI.add_constraint( + model, + MOI.VectorOfVariables([x1, x2]), + MathOptComplements.ComplementsWithSetType{MOI.Nonnegatives}(2), + ) + relax = MathOptComplements.FischerBurmeisterRelaxation(1e-8) + MOI.set(model, MathOptComplements.ComplementarityReformulation(), ci, relax) + MOI.Bridges.final_touch(model) + @test MOI.get( + inner, + MOI.NumberOfConstraints{MOI.VariableIndex,MOI.GreaterThan{Float64}}(), + ) == 2 + @test MOI.get( + inner, + MOI.NumberOfConstraints{ + MOI.ScalarNonlinearFunction, + MOI.LessThan{Float64}, + }(), + ) == 1 + return +end + +function test_FB_Nonpositives_with_unbounded_x1() + inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) + model = MOI.Bridges.Constraint.SingleBridgeOptimizer{ + MathOptComplements.Bridges.NonlinearBridge{Float64}, + }( + inner, + ) + x1 = MOI.add_variable(model) + x2, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) + ci = MOI.add_constraint( + model, + MOI.VectorOfVariables([x1, x2]), + MathOptComplements.ComplementsWithSetType{MOI.Nonpositives}(2), + ) + relax = MathOptComplements.FischerBurmeisterRelaxation(1e-8) + MOI.set(model, MathOptComplements.ComplementarityReformulation(), ci, relax) + MOI.Bridges.final_touch(model) + @test MOI.get( + inner, + MOI.NumberOfConstraints{MOI.VariableIndex,MOI.LessThan{Float64}}(), + ) == 2 + @test MOI.get( + inner, + MOI.NumberOfConstraints{ + MOI.ScalarNonlinearFunction, + MOI.GreaterThan{Float64}, + }(), + ) == 1 + return +end + +function test_KS_Nonnegatives_with_unbounded_x1() + inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) + model = MOI.Bridges.Constraint.SingleBridgeOptimizer{ + MathOptComplements.Bridges.NonlinearBridge{Float64}, + }( + inner, + ) + x1 = MOI.add_variable(model) + x2, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + ci = MOI.add_constraint( + model, + MOI.VectorOfVariables([x1, x2]), + MathOptComplements.ComplementsWithSetType{MOI.Nonnegatives}(2), + ) + relax = MathOptComplements.KanzowSchwarzRelaxation(1e-8) + MOI.set(model, MathOptComplements.ComplementarityReformulation(), ci, relax) + MOI.Bridges.final_touch(model) + @test MOI.get( + inner, + MOI.NumberOfConstraints{MOI.VariableIndex,MOI.GreaterThan{Float64}}(), + ) == 2 + return +end + +function test_KS_Nonpositives_with_unbounded_x1() + inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) + model = MOI.Bridges.Constraint.SingleBridgeOptimizer{ + MathOptComplements.Bridges.NonlinearBridge{Float64}, + }( + inner, + ) + x1 = MOI.add_variable(model) + x2, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) + ci = MOI.add_constraint( + model, + MOI.VectorOfVariables([x1, x2]), + MathOptComplements.ComplementsWithSetType{MOI.Nonpositives}(2), + ) + relax = MathOptComplements.KanzowSchwarzRelaxation(1e-8) + MOI.set(model, MathOptComplements.ComplementarityReformulation(), ci, relax) + MOI.Bridges.final_touch(model) + @test MOI.get( + inner, + MOI.NumberOfConstraints{MOI.VariableIndex,MOI.LessThan{Float64}}(), + ) == 2 + return +end + +function test_Zeros_equality() + MOI.Bridges.runtests( + MathOptComplements.Bridges.NonlinearBridge, + model -> begin + x1 = MOI.add_variable(model) + x2 = MOI.add_variable(model) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x1, x2]), + MathOptComplements.ComplementsWithSetType{MOI.Zeros}(2), + ) + end, + model -> begin + x1 = MOI.add_variable(model) + x2 = MOI.add_variable(model) + # Range relaxation with lb=0, ub=0: two constraints + MOI.add_constraint(model, x1 * (x2 - 0.0), MOI.LessThan(0.0)) + MOI.add_constraint(model, x1 * (x2 - 0.0), MOI.LessThan(0.0)) + end; + cannot_unbridge = true, + ) + return +end + +end # TestNonlinearBridge + +TestNonlinearBridge.runtests() diff --git a/test/Bridges/test_SpecifySetTypeBridge.jl b/test/Bridges/test_SpecifySetTypeBridge.jl new file mode 100644 index 0000000..edb0bd4 --- /dev/null +++ b/test/Bridges/test_SpecifySetTypeBridge.jl @@ -0,0 +1,195 @@ +# Copyright (c) 2025 François Pacaud, Benoît Legat, and contributors +# +# Use of this source code is governed by an MIT-style license that can be found +# in the LICENSE.md file or at https://opensource.org/licenses/MIT. + +module TestSpecifySetTypeBridge + +using Test + +import MathOptComplements +import MathOptInterface as MOI + +function runtests() + is_test(name) = startswith("$name", "test_") + @testset "$name" for name in filter(is_test, names(@__MODULE__; all = true)) + getfield(@__MODULE__, name)() + end + return +end + +function test_lower_bound_Nonnegatives() + MOI.Bridges.runtests( + MathOptComplements.Bridges.SpecifySetTypeBridge, + model -> begin + x, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + y, _ = + MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x, y]), + MOI.Complements(2), + ) + end, + model -> begin + x, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + y, _ = + MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x, y]), + MathOptComplements.ComplementsWithSetType{MOI.Nonnegatives}(2), + ) + end; + cannot_unbridge = true, + ) + return +end + +function test_lower_bound_GreaterThan() + MOI.Bridges.runtests( + MathOptComplements.Bridges.SpecifySetTypeBridge, + model -> begin + x = MOI.add_variable(model) + y, _ = + MOI.add_constrained_variable(model, MOI.GreaterThan(3.0)) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x, y]), + MOI.Complements(2), + ) + end, + model -> begin + x, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + y, _ = + MOI.add_constrained_variable(model, MOI.GreaterThan(3.0)) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x, y]), + MathOptComplements.ComplementsWithSetType{ + MOI.GreaterThan{Float64}, + }( + 2, + ), + ) + end; + cannot_unbridge = true, + ) + return +end + +function test_upper_bound_Nonpositives() + MOI.Bridges.runtests( + MathOptComplements.Bridges.SpecifySetTypeBridge, + model -> begin + x = MOI.add_variable(model) + y, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x, y]), + MOI.Complements(2), + ) + end, + model -> begin + x, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) + y, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x, y]), + MathOptComplements.ComplementsWithSetType{MOI.Nonpositives}(2), + ) + end; + cannot_unbridge = true, + ) + return +end + +function test_upper_bound_LessThan() + MOI.Bridges.runtests( + MathOptComplements.Bridges.SpecifySetTypeBridge, + model -> begin + x = MOI.add_variable(model) + y, _ = MOI.add_constrained_variable(model, MOI.LessThan(-2.0)) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x, y]), + MOI.Complements(2), + ) + end, + model -> begin + x, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) + y, _ = MOI.add_constrained_variable(model, MOI.LessThan(-2.0)) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x, y]), + MathOptComplements.ComplementsWithSetType{MOI.LessThan{Float64}}( + 2, + ), + ) + end; + cannot_unbridge = true, + ) + return +end + +function test_range_Interval() + MOI.Bridges.runtests( + MathOptComplements.Bridges.SpecifySetTypeBridge, + model -> begin + x = MOI.add_variable(model) + y, _ = + MOI.add_constrained_variable(model, MOI.Interval(0.0, 1.0)) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x, y]), + MOI.Complements(2), + ) + end, + model -> begin + x = MOI.add_variable(model) + y, _ = + MOI.add_constrained_variable(model, MOI.Interval(0.0, 1.0)) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x, y]), + MathOptComplements.ComplementsWithSetType{MOI.Interval{Float64}}( + 2, + ), + ) + end; + cannot_unbridge = true, + ) + return +end + +function test_Free_variable_Zeros() + MOI.Bridges.runtests( + MathOptComplements.Bridges.SpecifySetTypeBridge, + model -> begin + x = MOI.add_variable(model) + y = MOI.add_variable(model) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x, y]), + MOI.Complements(2), + ) + end, + model -> begin + x = MOI.add_variable(model) + y = MOI.add_variable(model) + # x1 must be zero + MOI.add_constraint(model, 1.0 * x, MOI.EqualTo(0.0)) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x, y]), + MathOptComplements.ComplementsWithSetType{MOI.Zeros}(2), + ) + end; + cannot_unbridge = true, + ) + return +end + +end # TestSpecifySetTypeBridge + +TestSpecifySetTypeBridge.runtests() diff --git a/test/Bridges/test_SplitIntervalBridge.jl b/test/Bridges/test_SplitIntervalBridge.jl new file mode 100644 index 0000000..af112cd --- /dev/null +++ b/test/Bridges/test_SplitIntervalBridge.jl @@ -0,0 +1,130 @@ +# Copyright (c) 2025 François Pacaud, Benoît Legat, and contributors +# +# Use of this source code is governed by an MIT-style license that can be found +# in the LICENSE.md file or at https://opensource.org/licenses/MIT. + +module TestSplitIntervalBridge + +using Test + +import MathOptComplements +import MathOptInterface as MOI + +function runtests() + is_test(name) = startswith("$name", "test_") + @testset "$name" for name in filter(is_test, names(@__MODULE__; all = true)) + getfield(@__MODULE__, name)() + end + return +end + +function test_VectorOfVariables_input() + MOI.Bridges.runtests( + MathOptComplements.Bridges.SplitIntervalBridge, + model -> begin + x = MOI.add_variable(model) + y, _ = + MOI.add_constrained_variable(model, MOI.Interval(0.0, 1.0)) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x, y]), + MathOptComplements.ComplementsWithSetType{MOI.Interval{Float64}}( + 2, + ), + ) + end, + model -> begin + x = MOI.add_variable(model) + y, _ = + MOI.add_constrained_variable(model, MOI.Interval(0.0, 1.0)) + xp, _ = + MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + xn, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) + # x == xp + xn → x - xp - xn == 0 + MOI.add_constraint( + model, + 1.0 * x - 1.0 * xp - 1.0 * xn, + MOI.EqualTo(0.0), + ) + MOI.add_constraint( + model, + MOI.VectorOfVariables([xp, y]), + MathOptComplements.ComplementsWithSetType{ + MOI.GreaterThan{Float64}, + }( + 2, + ), + ) + MOI.add_constraint( + model, + MOI.VectorOfVariables([xn, y]), + MathOptComplements.ComplementsWithSetType{MOI.LessThan{Float64}}( + 2, + ), + ) + end; + cannot_unbridge = true, + ) + return +end + +function test_VectorAffineFunction_input() + MOI.Bridges.runtests( + MathOptComplements.Bridges.SplitIntervalBridge, + model -> begin + x = MOI.add_variable(model) + y, _ = + MOI.add_constrained_variable(model, MOI.Interval(0.0, 1.0)) + f = MOI.VectorAffineFunction{Float64}( + [ + MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(2.0, x)), + MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, y)), + ], + [1.0, 0.0], + ) + MOI.add_constraint( + model, + f, + MathOptComplements.ComplementsWithSetType{MOI.Interval{Float64}}( + 2, + ), + ) + end, + model -> begin + x = MOI.add_variable(model) + y, _ = + MOI.add_constrained_variable(model, MOI.Interval(0.0, 1.0)) + xp, _ = + MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + xn, _ = MOI.add_constrained_variable(model, MOI.LessThan(0.0)) + # 2x + 1 == xp + xn → 2x - xp - xn == -1 + MOI.add_constraint( + model, + 2.0 * x - 1.0 * xp - 1.0 * xn, + MOI.EqualTo(-1.0), + ) + MOI.add_constraint( + model, + MOI.VectorOfVariables([xp, y]), + MathOptComplements.ComplementsWithSetType{ + MOI.GreaterThan{Float64}, + }( + 2, + ), + ) + MOI.add_constraint( + model, + MOI.VectorOfVariables([xn, y]), + MathOptComplements.ComplementsWithSetType{MOI.LessThan{Float64}}( + 2, + ), + ) + end; + cannot_unbridge = true, + ) + return +end + +end # TestSplitIntervalBridge + +TestSplitIntervalBridge.runtests() diff --git a/test/Bridges/ToSOS1Bridge.jl b/test/Bridges/test_ToSOS1Bridge.jl similarity index 74% rename from test/Bridges/ToSOS1Bridge.jl rename to test/Bridges/test_ToSOS1Bridge.jl index ca810af..a09931f 100644 --- a/test/Bridges/ToSOS1Bridge.jl +++ b/test/Bridges/test_ToSOS1Bridge.jl @@ -3,11 +3,22 @@ # Use of this source code is governed by an MIT-style license that can be found # in the LICENSE.md file or at https://opensource.org/licenses/MIT. +module TestToSOS1Bridge + using Test -using JuMP -using MathOptComplements -@testset "ToSOS1Bridge" begin +import MathOptComplements +import MathOptInterface as MOI + +function runtests() + is_test(name) = startswith("$name", "test_") + @testset "$name" for name in filter(is_test, names(@__MODULE__; all = true)) + getfield(@__MODULE__, name)() + end + return +end + +function test_ToSOS1Bridge() MOI.Bridges.runtests( MathOptComplements.Bridges.ToSOS1Bridge, model -> begin @@ -32,4 +43,9 @@ using MathOptComplements end; cannot_unbridge = true, ) + return end + +end # TestToSOS1Bridge + +TestToSOS1Bridge.runtests() diff --git a/test/Bridges/test_VerticalBridge.jl b/test/Bridges/test_VerticalBridge.jl new file mode 100644 index 0000000..b4daab6 --- /dev/null +++ b/test/Bridges/test_VerticalBridge.jl @@ -0,0 +1,100 @@ +# Copyright (c) 2025 François Pacaud, Benoît Legat, and contributors +# +# Use of this source code is governed by an MIT-style license that can be found +# in the LICENSE.md file or at https://opensource.org/licenses/MIT. + +module TestVerticalBridge + +using Test + +import MathOptComplements +import MathOptInterface as MOI + +function runtests() + is_test(name) = startswith("$name", "test_") + @testset "$name" for name in filter(is_test, names(@__MODULE__; all = true)) + getfield(@__MODULE__, name)() + end + return +end + +function test_Unbounded_RHS_with_constant_in_LHS_Complements() + MOI.Bridges.runtests( + MathOptComplements.Bridges.VerticalBridge, + model -> begin + x = MOI.add_variable(model) + z = MOI.add_variable(model) + y = MOI.add_variable(model) + w, _ = + MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + f = MOI.VectorAffineFunction{Float64}( + [ + MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(2.0, x)), + MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, z)), + MOI.VectorAffineTerm(3, MOI.ScalarAffineTerm(1.0, y)), + MOI.VectorAffineTerm(4, MOI.ScalarAffineTerm(1.0, w)), + ], + [3.0, 0.0, 0.0, 0.0], + ) + MOI.add_constraint(model, f, MOI.Complements(4)) + end, + model -> begin + x = MOI.add_variable(model) + z = MOI.add_variable(model) + y = MOI.add_variable(model) + w, _ = + MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + # 2x + 3 = 0 → 2x in EqualTo(-3.0) (y is unbounded) + MOI.add_constraint(model, 2.0 * x, MOI.EqualTo(-3.0)) + MOI.add_constraint( + model, + MOI.VectorOfVariables([z, w]), + MOI.Complements(2), + ) + end; + cannot_unbridge = true, + ) + return +end + +function test_Expression_LHS_with_constant_ComplementsWithSetType() + MOI.Bridges.runtests( + MathOptComplements.Bridges.VerticalBridge, + model -> begin + x = MOI.add_variable(model) + y, _ = + MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + f = MOI.VectorAffineFunction{Float64}( + [ + MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(2.0, x)), + MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, y)), + ], + [3.0, 0.0], + ) + MOI.add_constraint( + model, + f, + MathOptComplements.ComplementsWithSetType{MOI.Nonnegatives}(2), + ) + end, + model -> begin + x = MOI.add_variable(model) + y, _ = + MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + x1 = MOI.add_variable(model) + # 2x + 3 = x1 → 2x - x1 in EqualTo(-3.0) + MOI.add_constraint(model, 2.0 * x - 1.0 * x1, MOI.EqualTo(-3.0)) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x1, y]), + MathOptComplements.ComplementsWithSetType{MOI.Nonnegatives}(2), + ) + end; + cannot_unbridge = true, + ) + return +end + +end # TestVerticalBridge + +TestVerticalBridge.runtests() diff --git a/test/Project.toml b/test/Project.toml index 0de5dc7..f58bb32 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,5 +1,6 @@ [deps] MathOptComplements = "3144fb79-1414-4d28-b1d1-63dadd798e24" +MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" diff --git a/test/runtests.jl b/test/runtests.jl index 3453cc2..73393c7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -853,4 +853,8 @@ end @test value(y) ≈ -1.0 atol = 1e-7 end -include(joinpath(@__DIR__, "Bridges", "runtests.jl")) +is_test(f) = startswith(f, "test_") && endswith(f, ".jl") + +@testset "$file" for file in filter(is_test, readdir("Bridges")) + include(joinpath(@__DIR__, "Bridges", file)) +end