Skip to content

isapprox for a VectorNonlinearFunction after simple bridge modifications #2553

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
DimitriAlston opened this issue Oct 8, 2024 · 7 comments · Fixed by #2686
Closed

isapprox for a VectorNonlinearFunction after simple bridge modifications #2553

DimitriAlston opened this issue Oct 8, 2024 · 7 comments · Fixed by #2686
Labels
Submodule: Bridges About the Bridges submodule

Comments

@DimitriAlston
Copy link

I am working on adding support for MOI.ScalarNonlinearFunction to EAGO and I am running into a particular error with some tests.

I cannot provide a MWE since I have not pushed any of my changes yet, but this is how I am running my tests.

import EAGO
import MathOptInterface as MOI

model = MOI.instantiate(
        EAGO.Optimizer;
        with_bridge_type = Float64,
        with_cache_type = Float64,
)
config = MOI.Test.Config(;
             atol = 1e-3,
             rtol = 1e-3,
             exclude = Any[
                 MOI.DualObjectiveValue,
                 MOI.ConstraintBasisStatus,
                 MOI.VariableBasisStatus,
                 MOI.ConstraintDual,
             ],
)
MOI.empty!(model)

MOI.Test.test_basic_VectorNonlinearFunction_CountAtLeast(model, config)

I have excluded the other tests (which are all test_basic_VectorNonlinearFunction_) in this example, but they all return the same error.

ERROR: MethodError: no method matching filter_variables(::MathOptInterface.Utilities.var"#17#18"{Set{MathOptInterface.VariableIndex}}, ::MathOptInterface.VectorNonlinearFunction)
Closest candidates are:
  filter_variables(::F, ::Any, ::Any) where F<:Function
  filter_variables(::Function, ::MathOptInterface.VariableIndex)
  filter_variables(::Function, ::MathOptInterface.VectorOfVariables)
  ...

The way I implemented MOI.ScalarNonlinearFunction is similar to Optim.jl https://github.com/JuliaNLSolvers/Optim.jl/blob/658c39a316bda911cddf442702b11bf086f1b5ba/ext/OptimMOIExt.jl
where the functions are added to a MOI.Nonlinear.Model and then used to create a MOI.Nonlinear.Evaluator and MOI.NLPBlockData to continue using the old nonlinear interface.

I am not sure if this issue is because I am not directly storing the functions or something else, but I figured I would check here first for insight before moving forward.
Thanks in advance.

@odow
Copy link
Member

odow commented Oct 8, 2024

Yeah I hit a bunch of these with KNITRO, but MINLP solvers are pretty rare so I haven't spent much time on them 😄

Just exclude:
https://github.com/jump-dev/KNITRO.jl/blob/7aae642b14973654e6b52341be7b079edb36c4ab/test/MOI_wrapper.jl#L67-L87

@odow odow added the Submodule: Bridges About the Bridges submodule label Oct 8, 2024
@odow odow changed the title VectorNonlinearFunction Tests Various issues with bridges and VectorNonlinearFunction Oct 8, 2024
@DimitriAlston
Copy link
Author

Fair enough, I'll exclude those for now. Thanks!

@blegat
Copy link
Member

blegat commented Oct 9, 2024

The remaining issues are due to isapprox:

test_basic_VectorNonlinearFunction_HyperRectangle: Test Failed at /home/blegat/.julia/dev/MathOptInterface/src/Test/test_basic_constraint.jl:262
  Expression: isapprox(MOI.Utilities.canonical(f), constraint_function, config)
   Evaluated: isapprox(┌                                                                                                           ┐
│+(-(+(^(MOI.VariableIndex(1), (2)), ^(MOI.VariableIndex(2), (2)), ^(MOI.VariableIndex(3), (2))), 0.0), 0.0)│
│+(-(+(MOI.VariableIndex(1)), 0.0), 0.0)                                                                    │
│+(-(+(MOI.VariableIndex(2)), 0.0), 0.0)                                                                    │
└                                                                                                           ┘, ┌                                                                                           ┐
│+(^(MOI.VariableIndex(1), (2)), ^(MOI.VariableIndex(2), (2)), ^(MOI.VariableIndex(3), (2)))│
│+(MOI.VariableIndex(1))                                                                    │
│+(MOI.VariableIndex(2))                                                                    │
│+(MOI.VariableIndex(3))                                                                    │
└                                                                                           ┘, MathOptInterface.Test.Config{Float64}(0.001, 0.001, MathOptInterface.OPTIMAL, MathOptInterface.INFEASIBLE, Any[MathOptInterface.DualObjectiveValue, MathOptInterface.ConstraintBasisStatus, MathOptInterface.VariableBasisStatus, MathOptInterface.ConstraintDual]))

Stacktrace:
 [1] macro expansion
   @ ~/.julia/juliaup/julia-1.11.0+0.x64.linux.gnu/share/julia/stdlib/v1.11/Test/src/Test.jl:679 [inlined]
 [2] _basic_constraint_test_helper(model::MathOptInterface.Bridges.LazyBridgeOptimizer{MathOptInterface.Utilities.CachingOptimizer{EAGO.Optimizer{EAGO.Incremental{Cbc.Optimizer}, EAGO.Incremental{Ipopt.Optimizer}, EAGO.DefaultExt}, MathOptInterface.Utilities.UniversalFallback{MathOptInterface.Utilities.Model{Float64}}}}, config::MathOptInterface.Test.Config{Float64}, ::Type{MathOptInterface.VectorNonlinearFunction}, ::Type{MathOptInterface.HyperRectangle})
   @ MathOptInterface.Test ~/.julia/dev/MathOptInterface/src/Test/test_basic_constraint.jl:262
 [3] test_basic_VectorNonlinearFunction_HyperRectangle(model::MathOptInterface.Bridges.LazyBridgeOptimizer{MathOptInterface.Utilities.CachingOptimizer{EAGO.Optimizer{EAGO.Incremental{Cbc.Optimizer}, EAGO.Incremental{Ipopt.Optimizer}, EAGO.DefaultExt}, MathOptInterface.Utilities.UniversalFallback{MathOptInterface.Utilities.Model{Float64}}}}, config::MathOptInterface.Test.Config{Float64})
   @ MathOptInterface.Test ~/.julia/dev/MathOptInterface/src/Test/test_basic_constraint.jl:396
 [4] macro expansion
   @ ~/.julia/dev/MathOptInterface/src/Test/Test.jl:272 [inlined]
 [5] macro expansion
   @ ~/.julia/juliaup/julia-1.11.0+0.x64.linux.gnu/share/julia/stdlib/v1.11/Test/src/Test.jl:1700 [inlined]
 [6] runtests(model::MathOptInterface.Bridges.LazyBridgeOptimizer{MathOptInterface.Utilities.CachingOptimizer{EAGO.Optimizer{EAGO.Incremental{Cbc.Optimizer}, EAGO.Incremental{Ipopt.Optimizer}, EAGO.DefaultExt}, MathOptInterface.Utilities.UniversalFallback{MathOptInterface.Utilities.Model{Float64}}}}, config::MathOptInterface.Test.Config{Float64}; include::Vector{String}, exclude::Vector{String}, warn_unsupported::Bool, verbose::Bool, exclude_tests_after::VersionNumber)
   @ MathOptInterface.Test ~/.julia/dev/MathOptInterface/src/Test/Test.jl:267
test_basic_VectorNonlinearFunction_HyperRectangle: Test Failed at /home/blegat/.julia/dev/MathOptInterface/src/Test/test_basic_constraint.jl:263
  Expression: isapprox(MOI.get(model, MOI.CanonicalConstraintFunction(), c), constraint_function, config)
   Evaluated: isapprox(┌                                                                                                           ┐
│+(-(+(^(MOI.VariableIndex(1), (2)), ^(MOI.VariableIndex(2), (2)), ^(MOI.VariableIndex(3), (2))), 0.0), 0.0)│
│+(-(+(MOI.VariableIndex(1)), 0.0), 0.0)                                                                    │
│+(-(+(MOI.VariableIndex(2)), 0.0), 0.0)                                                                    │
└                                                                                                           ┘, ┌                                                                                           ┐
│+(^(MOI.VariableIndex(1), (2)), ^(MOI.VariableIndex(2), (2)), ^(MOI.VariableIndex(3), (2)))│
│+(MOI.VariableIndex(1))                                                                    │
│+(MOI.VariableIndex(2))                                                                    │
│+(MOI.VariableIndex(3))                                                                    │
└                                                                                           ┘, MathOptInterface.Test.Config{Float64}(0.001, 0.001, MathOptInterface.OPTIMAL, MathOptInterface.INFEASIBLE, Any[MathOptInterface.DualObjectiveValue, MathOptInterface.ConstraintBasisStatus, MathOptInterface.VariableBasisStatus, MathOptInterface.ConstraintDual]))

Stacktrace:
 [1] macro expansion
   @ ~/.julia/juliaup/julia-1.11.0+0.x64.linux.gnu/share/julia/stdlib/v1.11/Test/src/Test.jl:679 [inlined]
 [2] _basic_constraint_test_helper(model::MathOptInterface.Bridges.LazyBridgeOptimizer{MathOptInterface.Utilities.CachingOptimizer{EAGO.Optimizer{EAGO.Incremental{Cbc.Optimizer}, EAGO.Incremental{Ipopt.Optimizer}, EAGO.DefaultExt}, MathOptInterface.Utilities.UniversalFallback{MathOptInterface.Utilities.Model{Float64}}}}, config::MathOptInterface.Test.Config{Float64}, ::Type{MathOptInterface.VectorNonlinearFunction}, ::Type{MathOptInterface.HyperRectangle})
   @ MathOptInterface.Test ~/.julia/dev/MathOptInterface/src/Test/test_basic_constraint.jl:263
 [3] test_basic_VectorNonlinearFunction_HyperRectangle(model::MathOptInterface.Bridges.LazyBridgeOptimizer{MathOptInterface.Utilities.CachingOptimizer{EAGO.Optimizer{EAGO.Incremental{Cbc.Optimizer}, EAGO.Incremental{Ipopt.Optimizer}, EAGO.DefaultExt}, MathOptInterface.Utilities.UniversalFallback{MathOptInterface.Utilities.Model{Float64}}}}, config::MathOptInterface.Test.Config{Float64})
   @ MathOptInterface.Test ~/.julia/dev/MathOptInterface/src/Test/test_basic_constraint.jl:396
 [4] macro expansion
   @ ~/.julia/dev/MathOptInterface/src/Test/Test.jl:272 [inlined]
 [5] macro expansion
   @ ~/.julia/juliaup/julia-1.11.0+0.x64.linux.gnu/share/julia/stdlib/v1.11/Test/src/Test.jl:1700 [inlined]
 [6] runtests(model::MathOptInterface.Bridges.LazyBridgeOptimizer{MathOptInterface.Utilities.CachingOptimizer{EAGO.Optimizer{EAGO.Incremental{Cbc.Optimizer}, EAGO.Incremental{Ipopt.Optimizer}, EAGO.DefaultExt}, MathOptInterface.Utilities.UniversalFallback{MathOptInterface.Utilities.Model{Float64}}}}, config::MathOptInterface.Test.Config{Float64}; include::Vector{String}, exclude::Vector{String}, warn_unsupported::Bool, verbose::Bool, exclude_tests_after::VersionNumber)
   @ MathOptInterface.Test ~/.julia/dev/MathOptInterface/src/Test/Test.jl:267
test_basic_VectorNonlinearFunction_NormInfinityCone: Test Failed at /home/blegat/.julia/dev/MathOptInterface/src/Test/test_basic_constraint.jl:262
  Expression: isapprox(MOI.Utilities.canonical(f), constraint_function, config)
   Evaluated: isapprox(┌                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      ┐
│/(+(+(-(+(MOI.VariableIndex(1), +(^(MOI.VariableIndex(1), (2)), ^(MOI.VariableIndex(2), (2)), ^(MOI.VariableIndex(3), (2))))), +(^(MOI.VariableIndex(1), (2)), ^(MOI.VariableIndex(2), (2)), ^(MOI.VariableIndex(3), (2)))), +(-(+(MOI.VariableIndex(2), +(^(MOI.VariableIndex(1), (2)), ^(MOI.VariableIndex(2), (2)), ^(MOI.VariableIndex(3), (2))))), +(^(MOI.VariableIndex(1), (2)), ^(MOI.VariableIndex(2), (2)), ^(MOI.VariableIndex(3), (2)))), +(-(+(MOI.VariableIndex(3), +(^(MOI.VariableIndex(1), (2)), ^(MOI.VariableIndex(2), (2)), ^(MOI.VariableIndex(3), (2))))), +(^(MOI.VariableIndex(1), (2)), ^(MOI.VariableIndex(2), (2)), ^(MOI.VariableIndex(3), (2)))), +(MOI.VariableIndex(1), +(^(MOI.VariableIndex(1), (2)), ^(MOI.VariableIndex(2), (2)), ^(MOI.VariableIndex(3), (2)))), +(MOI.VariableIndex(2), +(^(MOI.VariableIndex(1), (2)), ^(MOI.VariableIndex(2), (2)), ^(MOI.VariableIndex(3), (2)))), +(MOI.VariableIndex(3), +(^(MOI.VariableIndex(1), (2)), ^(MOI.VariableIndex(2), (2)), ^(MOI.VariableIndex(3), (2))))), 6.0)│
│/(-(+(MOI.VariableIndex(1), +(^(MOI.VariableIndex(1), (2)), ^(MOI.VariableIndex(2), (2)), ^(MOI.VariableIndex(3), (2)))), +(-(+(MOI.VariableIndex(1), +(^(MOI.VariableIndex(1), (2)), ^(MOI.VariableIndex(2), (2)), ^(MOI.VariableIndex(3), (2))))), +(^(MOI.VariableIndex(1), (2)), ^(MOI.VariableIndex(2), (2)), ^(MOI.VariableIndex(3), (2))))), 2.0)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              │
│/(-(+(MOI.VariableIndex(2), +(^(MOI.VariableIndex(1), (2)), ^(MOI.VariableIndex(2), (2)), ^(MOI.VariableIndex(3), (2)))), +(-(+(MOI.VariableIndex(2), +(^(MOI.VariableIndex(1), (2)), ^(MOI.VariableIndex(2), (2)), ^(MOI.VariableIndex(3), (2))))), +(^(MOI.VariableIndex(1), (2)), ^(MOI.VariableIndex(2), (2)), ^(MOI.VariableIndex(3), (2))))), 2.0)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              │
│/(-(+(MOI.VariableIndex(3), +(^(MOI.VariableIndex(1), (2)), ^(MOI.VariableIndex(2), (2)), ^(MOI.VariableIndex(3), (2)))), +(-(+(MOI.VariableIndex(3), +(^(MOI.VariableIndex(1), (2)), ^(MOI.VariableIndex(2), (2)), ^(MOI.VariableIndex(3), (2))))), +(^(MOI.VariableIndex(1), (2)), ^(MOI.VariableIndex(2), (2)), ^(MOI.VariableIndex(3), (2))))), 2.0)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              │
└                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      ┘, ┌                                                                                                                    ┐
│+(^(MOI.VariableIndex(1), (2)), ^(MOI.VariableIndex(2), (2)), ^(MOI.VariableIndex(3), (2)))                         │
│+(MOI.VariableIndex(1), +(^(MOI.VariableIndex(1), (2)), ^(MOI.VariableIndex(2), (2)), ^(MOI.VariableIndex(3), (2))))│
│+(MOI.VariableIndex(2), +(^(MOI.VariableIndex(1), (2)), ^(MOI.VariableIndex(2), (2)), ^(MOI.VariableIndex(3), (2))))│
│+(MOI.VariableIndex(3), +(^(MOI.VariableIndex(1), (2)), ^(MOI.VariableIndex(2), (2)), ^(MOI.VariableIndex(3), (2))))│
└                                                                                                                    ┘, MathOptInterface.Test.Config{Float64}(0.001, 0.001, MathOptInterface.OPTIMAL, MathOptInterface.INFEASIBLE, Any[MathOptInterface.DualObjectiveValue, MathOptInterface.ConstraintBasisStatus, MathOptInterface.VariableBasisStatus, MathOptInterface.ConstraintDual]))

Stacktrace:
 [1] macro expansion
   @ ~/.julia/juliaup/julia-1.11.0+0.x64.linux.gnu/share/julia/stdlib/v1.11/Test/src/Test.jl:679 [inlined]
 [2] _basic_constraint_test_helper(model::MathOptInterface.Bridges.LazyBridgeOptimizer{MathOptInterface.Utilities.CachingOptimizer{EAGO.Optimizer{EAGO.Incremental{Cbc.Optimizer}, EAGO.Incremental{Ipopt.Optimizer}, EAGO.DefaultExt}, MathOptInterface.Utilities.UniversalFallback{MathOptInterface.Utilities.Model{Float64}}}}, config::MathOptInterface.Test.Config{Float64}, ::Type{MathOptInterface.VectorNonlinearFunction}, ::Type{MathOptInterface.NormInfinityCone})
   @ MathOptInterface.Test ~/.julia/dev/MathOptInterface/src/Test/test_basic_constraint.jl:262
 [3] test_basic_VectorNonlinearFunction_NormInfinityCone(model::MathOptInterface.Bridges.LazyBridgeOptimizer{MathOptInterface.Utilities.CachingOptimizer{EAGO.Optimizer{EAGO.Incremental{Cbc.Optimizer}, EAGO.Incremental{Ipopt.Optimizer}, EAGO.DefaultExt}, MathOptInterface.Utilities.UniversalFallback{MathOptInterface.Utilities.Model{Float64}}}}, config::MathOptInterface.Test.Config{Float64})
   @ MathOptInterface.Test ~/.julia/dev/MathOptInterface/src/Test/test_basic_constraint.jl:396
 [4] macro expansion
   @ ~/.julia/dev/MathOptInterface/src/Test/Test.jl:272 [inlined]
 [5] macro expansion
   @ ~/.julia/juliaup/julia-1.11.0+0.x64.linux.gnu/share/julia/stdlib/v1.11/Test/src/Test.jl:1700 [inlined]
 [6] runtests(model::MathOptInterface.Bridges.LazyBridgeOptimizer{MathOptInterface.Utilities.CachingOptimizer{EAGO.Optimizer{EAGO.Incremental{Cbc.Optimizer}, EAGO.Incremental{Ipopt.Optimizer}, EAGO.DefaultExt}, MathOptInterface.Utilities.UniversalFallback{MathOptInterface.Utilities.Model{Float64}}}}, config::MathOptInterface.Test.Config{Float64}; include::Vector{String}, exclude::Vector{String}, warn_unsupported::Bool, verbose::Bool, exclude_tests_after::VersionNumber)
   @ MathOptInterface.Test ~/.julia/dev/MathOptInterface/src/Test/Test.jl:267

@blegat blegat changed the title Various issues with bridges and VectorNonlinearFunction isapprox for a VectorNonlinearFunction after simple bridge modifications Oct 9, 2024
@odow
Copy link
Member

odow commented Oct 9, 2024

Yeah I think this is where I got stuck and gave up with Xpress...

@odow
Copy link
Member

odow commented Mar 4, 2025

So MOI.Nonlinear.SymbolicAD.simplify gets us some of the way there, but not for bridges like this:

function MOI.Bridges.map_function(
::Type{<:NormInfinityBridge{T}},
func,
) where {T}
scalars = MOI.Utilities.eachscalar(func)
t, x = scalars[1], scalars[2:end]
# Create f_new = [-x; x].
f_new = MOI.Utilities.operate(vcat, T, MOI.Utilities.operate(-, T, x), x)
# Add +t to each row of x
for i in 1:(2*(length(scalars)-1))
MOI.Utilities.operate_output_index!(+, T, i, f_new, t)
end
return f_new
end
function MOI.Bridges.inverse_map_function(
::Type{<:NormInfinityBridge{T}},
func,
) where {T}
# func is [t - x; t + x]
scalars = MOI.Utilities.eachscalar(func)
sum_f = MOI.Utilities.operate(+, T, scalars...)
# Get t by `(t - x) + (t + x)`, then dividing by the number of rows.
t = MOI.Utilities.operate!(/, T, sum_f, T(length(scalars)))
d = div(length(scalars), 2)
# Get x by (t + x) - (t - x) = 2x
x = MOI.Utilities.operate!(-, T, scalars[(d+1):end], scalars[1:d])
x = MOI.Utilities.operate!(/, T, x, T(2))
return MOI.Utilities.operate(vcat, T, t, x)
end

because we don't detect that ((t + x) - (t - x)) / 2 == x.

I wonder if we just need to simplify each problematic bridge?

@odow
Copy link
Member

odow commented Mar 4, 2025

using Test
import MathOptInterface as MOI
config = MOI.Test.Config()

@testset "All" begin
    @testset "SplitHyperRectangle" begin
        model = MOI.Bridges.Constraint.SplitHyperRectangle{Float64}(MOI.Utilities.Model{Float64}())
        MOI.Test.test_basic_VectorNonlinearFunction_HyperRectangle(model, config)
    end
    @testset "NormInfinity" begin
        model = MOI.Bridges.Constraint.NormInfinity{Float64}(MOI.Utilities.Model{Float64}())
        MOI.Test.test_basic_VectorNonlinearFunction_NormInfinityCone(model, config)
    end
    @testset "NormOne" begin
        model = MOI.Bridges.Constraint.NormOne{Float64}(MOI.Utilities.Model{Float64}())
        MOI.Test.test_basic_VectorNonlinearFunction_NormOneCone(model, config)
    end
end

@odow
Copy link
Member

odow commented Mar 5, 2025

I'm not actually sure we should try to make this work.

function MOI.get(
model::MOI.ModelLike,
::MOI.ConstraintFunction,
c::NormOneBridge{T,F,G},
) where {T,F,G}
nn_f = MOI.get(model, MOI.ConstraintFunction(), c.nn_index)
nn_func = MOI.Utilities.eachscalar(nn_f)
sum_nn_func = MOI.Utilities.operate(+, T, nn_func[1], nn_func...)
t = MOI.Utilities.operate!(/, T, sum_nn_func, T(2))
d = div(length(nn_func) - 1, 2)
x = MOI.Utilities.operate!(
/,
T,
MOI.Utilities.operate!(-, T, nn_func[(d+2):end], nn_func[2:(d+1)]),
T(2),
)
return MOI.Utilities.convert_approx(
G,
MOI.Utilities.remove_variable(
MOI.Utilities.operate(vcat, T, t, x),
c.y,
),
)
end

Is it reasonable to be able to simplify this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Submodule: Bridges About the Bridges submodule
Development

Successfully merging a pull request may close this issue.

3 participants