From bdc76c2572ed9a216f56ed386d1da0dec1c3f88f Mon Sep 17 00:00:00 2001 From: odow Date: Mon, 25 Mar 2024 18:30:58 +1300 Subject: [PATCH 01/20] WIP: Support MOI.ScalarNonlinearFunction --- Project.toml | 2 +- src/MOI/MOI_wrapper.jl | 207 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 199 insertions(+), 10 deletions(-) diff --git a/Project.toml b/Project.toml index ca6b7b9a..4f1b6ac1 100644 --- a/Project.toml +++ b/Project.toml @@ -10,7 +10,7 @@ MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" [compat] Libdl = "<0.0.1, 1.6" -MathOptInterface = "1" +MathOptInterface = "1.26" Test = "<0.0.1, 1.6" Xpress_jll = "=8.13.4, =9.3.0" julia = "1.6" diff --git a/src/MOI/MOI_wrapper.jl b/src/MOI/MOI_wrapper.jl index ac3e6e02..7560daac 100644 --- a/src/MOI/MOI_wrapper.jl +++ b/src/MOI/MOI_wrapper.jl @@ -19,7 +19,13 @@ const CleverDicts = MOI.Utilities.CleverDicts EQUAL_TO, ) -@enum(ObjectiveType, SINGLE_VARIABLE, SCALAR_AFFINE, SCALAR_QUADRATIC) +@enum( + ObjectiveType, + SINGLE_VARIABLE, + SCALAR_AFFINE, + SCALAR_QUADRATIC, + SCALAR_NONLINEAR, +) @enum(CallbackState, CB_NONE, CB_GENERIC, CB_LAZY, CB_USER_CUT, CB_HEURISTIC) @@ -1092,6 +1098,7 @@ function MOI.supports( MOI.VariableIndex, MOI.ScalarAffineFunction{Float64}, MOI.ScalarQuadraticFunction{Float64}, + MOI.ScalarNonlinearFunction, }, } return true @@ -1128,6 +1135,8 @@ function MOI.set( if model.objective_type == SCALAR_QUADRATIC # We need to zero out the existing quadratic objective. @checked Lib.XPRSdelqmatrix(model.inner, -1) + elseif model.objective_type == SCALAR_NONLINEAR + @checked Lib.XPRSnlpdelobjformula(model.inner) end num_vars = length(model.variable_info) # We zero all terms because we want to gurantee that the old terms @@ -1184,7 +1193,7 @@ function MOI.set( ::MOI.ObjectiveFunction{F}, f::F, ) where {F<:MOI.ScalarQuadraticFunction{Float64}} - # setting linear part also clears the existing quadratic terms + # setting linear part also clears the existing quadratic and nonlinear terms MOI.set( model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), @@ -1278,6 +1287,155 @@ function MOI.modify( return end +function MOI.set( + model::Optimizer, + ::MOI.ObjectiveFunction{MOI.ScalarNonlinearFunction}, + f::MOI.ScalarNonlinearFunction, +) + parsed, type, value = Cint(1), Cint[], Cdouble[] + _reverse_polish(model, f, type, value) + reverse!(type) + reverse!(value) + push!(type, Lib.XPRS_TOK_EOF) + push!(value, 0.0) + @show type, value + @checked Lib.XPRSnlpdelobjformula(model.inner) + @checked Lib.XPRSnlpchgobjformula(model.inner, parsed, type, value) + model.objective_type = SCALAR_NONLINEAR + model.is_objective_set = true + return +end + +const _FUNCTION_MAP = Dict( + # Handled explicitly: Lib.XPRS_OP_UMINUS + :^ => (Lib.XPRS_TOK_OP, Lib.XPRS_OP_EXPONENT), + :* => (Lib.XPRS_TOK_OP, Lib.XPRS_OP_MULTIPLY), + :/ => (Lib.XPRS_TOK_OP, Lib.XPRS_OP_DIVIDE), + :+ => (Lib.XPRS_TOK_OP, Lib.XPRS_OP_PLUS), + :- => (Lib.XPRS_TOK_OP, Lib.XPRS_OP_MINUS), + # const XPRS_DEL_COMMA = 1 + # const XPRS_DEL_COLON = 2 + :log => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_LOG), + :log10 => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_LOG10), + # const XPRS_IFUN_LN = 15 + :exp => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_EXP), + :abs => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_ABS), + :sqrt => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_SQRT), + :sin => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_SIN), + :cos => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_COS), + :tan => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_TAN), + :asin => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_ARCSIN), + :acos => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_ARCCOS), + :atan => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_ARCTAN), + :max => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_MIN), + :min => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_MAX), + # const XPRS_IFUN_PWL = 35 + # Handled explicitly: XPRS_IFUN_SUM + # Handled explicitly: XPRS_IFUN_PROD + :sign => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_SIGN), + :erf => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_ERF), + :erfc => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_ERFC), +) + +function _reverse_polish( + model::Optimizer, + f::MOI.ScalarNonlinearFunction, + type::Vector{Cint}, + value::Vector{Cdouble}, +) + if f.head == :- && length(f.args) == 1 + # Special handling for unary negation + push!(type, Lib.XPRS_TOK_OP) + push!(value, Lib.XPRS_OP_UMINUS) + for arg in reverse(f.args) + _reverse_polish(model, arg, type, value) + end + return + elseif f.head in (:+, :*) && length(f.args) != 2 + # Special handling for non-binary sum and product + push!(type, Lib.XPRS_TOK_IFUN) + push!(value, f.head == :+ ? Lib.XPRS_IFUN_SUM : Lib.XPRS_IFUN_PROD) + push!(type, Lib.XPRS_TOK_LB) + push!(value, 0.0) + for arg in reverse(f.args) + _reverse_polish(model, arg, type, value) + end + push!(type, Lib.XPRS_TOK_RB) + push!(value, 0.0) + return + end + ret = get(_FUNCTION_MAP, f.head, nothing) + if ret === nothing + throw(MOI.UnsupportedNonlinearOperator(f.head)) + elseif ret[1] == Lib.XPRS_TOK_OP + push!(type, ret[1]) + push!(value, ret[2]) + for arg in reverse(f.args) + _reverse_polish(model, arg, type, value) + end + else + @assert ret[1] == Lib.XPRS_TOK_IFUN + push!(type, ret[1]) + push!(value, ret[2]) + # XPRS_TOK_LB is not needed. Implied by XPRS_TOK_IFUN + for arg in reverse(f.args) + _reverse_polish(model, arg, type, value) + end + push!(type, Lib.XPRS_TOK_RB) + push!(value, 0.0) + end + return +end + +function _reverse_polish( + ::Optimizer, + f::Real, + type::Vector{Cint}, + value::Vector{Cdouble}, +) + push!(type, Lib.XPRS_TOK_CON) + push!(value, Cdouble(f)) + return +end + +function _reverse_polish( + model::Optimizer, + x::MOI.VariableIndex, + type::Vector{Cint}, + value::Vector{Cdouble}, +) + push!(type, Lib.XPRS_TOK_COL) + push!(value, _info(model, x).column - 1) + return +end + +function _reverse_polish( + model::Optimizer, + f::MOI.AbstractScalarFunction, + type::Vector{Cint}, + value::Vector{Cdouble}, +) + _reverse_polish(model, convert(MOI.ScalarNonlinearFunction, f), type, value) + return +end + +function MOI.get( + model::Optimizer, + ::MOI.ObjectiveFunction{MOI.ScalarNonlinearFunction}, +) + p_ntypes = Ref{Cint}() + @checked( + Lib.XPRSnlpgetobjformula(model.inner, 1, 0, p_ntypes, C_NULL, C_NULL) + ) + maxtype = p_ntypes[] + type, value = zeros(Cint, maxtype), zeros(Cdouble, maxtype) + @checked( + Lib.XPRSnlpgetobjformula(model.inner, 1, maxtype, p_ntypes, type, value) + ) + error("FIXME") + return +end + ## ## VariableIndex-in-Set constraints. ## @@ -2890,11 +3048,13 @@ function MOI.optimize!(model::Optimizer) _set_MIP_start(model) end start_time = time() - if is_mip(model) - @checked Lib.XPRSmipoptimize(model.inner, model.solve_method) - else - @checked Lib.XPRSlpoptimize(model.inner, model.solve_method) - end + solvestatus, solstatus = Ref{Cint}(), Ref{Cint}() + @checked Lib.XPRSoptimize( + model.inner, + model.solve_method, + solvestatus, + solstatus, + ) model.cached_solution.solve_time = time() - start_time check_cb_exception(model) # Should be almost a no-op if not needed. Might have minor overhead due to @@ -3366,8 +3526,9 @@ end function MOI.get(model::Optimizer, attr::MOI.ObjectiveValue) _throw_if_optimize_in_progress(model, attr) MOI.check_result_index_bounds(model, attr) - attr = is_mip(model) ? Lib.XPRS_MIPOBJVAL : Lib.XPRS_LPOBJVAL - return @_invoke Lib.XPRSgetdblattrib(model.inner, attr, _)::Float64 + return @_invoke( + Lib.XPRSgetdblattrib(model.inner, Lib.XPRS_OBJVAL, _)::Float64 + ) end #= @@ -4616,3 +4777,31 @@ function MOI.set(model::Optimizer, ::MOI.AbsoluteGapTolerance, value::Float64) setcontrol!(model.inner, "XPRS_MIPABSSTOP", value) return end + +#= + ScalarNonlinearFunction +=# + +# function MOI.supports_constraint( +# ::Optimizer, +# ::MOI.ScalarNonlinearFunction, +# ::Union{ +# MOI.LessThan{Float64}, +# MOI.GreaterThan{Float64}, +# MOI.EqualTo{Float64}, +# }, +# ) +# return true +# end + +# function MOI.add_constraint( +# ::Optimizer, +# ::MOI.ScalarNonlinearFunction, +# ::Union{ +# MOI.LessThan{Float64}, +# MOI.GreaterThan{Float64}, +# MOI.EqualTo{Float64}, +# }, +# ) +# return true +# end From 0bc5f28949010ef9f7683d9bd2bae6e47a8a8b80 Mon Sep 17 00:00:00 2001 From: odow Date: Wed, 3 Apr 2024 10:34:51 +1300 Subject: [PATCH 02/20] Update --- src/MOI/MOI_wrapper.jl | 336 ++++++++++++++++++----------------------- 1 file changed, 144 insertions(+), 192 deletions(-) diff --git a/src/MOI/MOI_wrapper.jl b/src/MOI/MOI_wrapper.jl index 7560daac..2787ec91 100644 --- a/src/MOI/MOI_wrapper.jl +++ b/src/MOI/MOI_wrapper.jl @@ -19,13 +19,7 @@ const CleverDicts = MOI.Utilities.CleverDicts EQUAL_TO, ) -@enum( - ObjectiveType, - SINGLE_VARIABLE, - SCALAR_AFFINE, - SCALAR_QUADRATIC, - SCALAR_NONLINEAR, -) +@enum(ObjectiveType, SINGLE_VARIABLE, SCALAR_AFFINE, SCALAR_QUADRATIC) @enum(CallbackState, CB_NONE, CB_GENERIC, CB_LAZY, CB_USER_CUT, CB_HEURISTIC) @@ -1098,7 +1092,6 @@ function MOI.supports( MOI.VariableIndex, MOI.ScalarAffineFunction{Float64}, MOI.ScalarQuadraticFunction{Float64}, - MOI.ScalarNonlinearFunction, }, } return true @@ -1135,8 +1128,6 @@ function MOI.set( if model.objective_type == SCALAR_QUADRATIC # We need to zero out the existing quadratic objective. @checked Lib.XPRSdelqmatrix(model.inner, -1) - elseif model.objective_type == SCALAR_NONLINEAR - @checked Lib.XPRSnlpdelobjformula(model.inner) end num_vars = length(model.variable_info) # We zero all terms because we want to gurantee that the old terms @@ -1287,155 +1278,6 @@ function MOI.modify( return end -function MOI.set( - model::Optimizer, - ::MOI.ObjectiveFunction{MOI.ScalarNonlinearFunction}, - f::MOI.ScalarNonlinearFunction, -) - parsed, type, value = Cint(1), Cint[], Cdouble[] - _reverse_polish(model, f, type, value) - reverse!(type) - reverse!(value) - push!(type, Lib.XPRS_TOK_EOF) - push!(value, 0.0) - @show type, value - @checked Lib.XPRSnlpdelobjformula(model.inner) - @checked Lib.XPRSnlpchgobjformula(model.inner, parsed, type, value) - model.objective_type = SCALAR_NONLINEAR - model.is_objective_set = true - return -end - -const _FUNCTION_MAP = Dict( - # Handled explicitly: Lib.XPRS_OP_UMINUS - :^ => (Lib.XPRS_TOK_OP, Lib.XPRS_OP_EXPONENT), - :* => (Lib.XPRS_TOK_OP, Lib.XPRS_OP_MULTIPLY), - :/ => (Lib.XPRS_TOK_OP, Lib.XPRS_OP_DIVIDE), - :+ => (Lib.XPRS_TOK_OP, Lib.XPRS_OP_PLUS), - :- => (Lib.XPRS_TOK_OP, Lib.XPRS_OP_MINUS), - # const XPRS_DEL_COMMA = 1 - # const XPRS_DEL_COLON = 2 - :log => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_LOG), - :log10 => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_LOG10), - # const XPRS_IFUN_LN = 15 - :exp => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_EXP), - :abs => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_ABS), - :sqrt => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_SQRT), - :sin => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_SIN), - :cos => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_COS), - :tan => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_TAN), - :asin => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_ARCSIN), - :acos => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_ARCCOS), - :atan => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_ARCTAN), - :max => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_MIN), - :min => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_MAX), - # const XPRS_IFUN_PWL = 35 - # Handled explicitly: XPRS_IFUN_SUM - # Handled explicitly: XPRS_IFUN_PROD - :sign => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_SIGN), - :erf => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_ERF), - :erfc => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_ERFC), -) - -function _reverse_polish( - model::Optimizer, - f::MOI.ScalarNonlinearFunction, - type::Vector{Cint}, - value::Vector{Cdouble}, -) - if f.head == :- && length(f.args) == 1 - # Special handling for unary negation - push!(type, Lib.XPRS_TOK_OP) - push!(value, Lib.XPRS_OP_UMINUS) - for arg in reverse(f.args) - _reverse_polish(model, arg, type, value) - end - return - elseif f.head in (:+, :*) && length(f.args) != 2 - # Special handling for non-binary sum and product - push!(type, Lib.XPRS_TOK_IFUN) - push!(value, f.head == :+ ? Lib.XPRS_IFUN_SUM : Lib.XPRS_IFUN_PROD) - push!(type, Lib.XPRS_TOK_LB) - push!(value, 0.0) - for arg in reverse(f.args) - _reverse_polish(model, arg, type, value) - end - push!(type, Lib.XPRS_TOK_RB) - push!(value, 0.0) - return - end - ret = get(_FUNCTION_MAP, f.head, nothing) - if ret === nothing - throw(MOI.UnsupportedNonlinearOperator(f.head)) - elseif ret[1] == Lib.XPRS_TOK_OP - push!(type, ret[1]) - push!(value, ret[2]) - for arg in reverse(f.args) - _reverse_polish(model, arg, type, value) - end - else - @assert ret[1] == Lib.XPRS_TOK_IFUN - push!(type, ret[1]) - push!(value, ret[2]) - # XPRS_TOK_LB is not needed. Implied by XPRS_TOK_IFUN - for arg in reverse(f.args) - _reverse_polish(model, arg, type, value) - end - push!(type, Lib.XPRS_TOK_RB) - push!(value, 0.0) - end - return -end - -function _reverse_polish( - ::Optimizer, - f::Real, - type::Vector{Cint}, - value::Vector{Cdouble}, -) - push!(type, Lib.XPRS_TOK_CON) - push!(value, Cdouble(f)) - return -end - -function _reverse_polish( - model::Optimizer, - x::MOI.VariableIndex, - type::Vector{Cint}, - value::Vector{Cdouble}, -) - push!(type, Lib.XPRS_TOK_COL) - push!(value, _info(model, x).column - 1) - return -end - -function _reverse_polish( - model::Optimizer, - f::MOI.AbstractScalarFunction, - type::Vector{Cint}, - value::Vector{Cdouble}, -) - _reverse_polish(model, convert(MOI.ScalarNonlinearFunction, f), type, value) - return -end - -function MOI.get( - model::Optimizer, - ::MOI.ObjectiveFunction{MOI.ScalarNonlinearFunction}, -) - p_ntypes = Ref{Cint}() - @checked( - Lib.XPRSnlpgetobjformula(model.inner, 1, 0, p_ntypes, C_NULL, C_NULL) - ) - maxtype = p_ntypes[] - type, value = zeros(Cint, maxtype), zeros(Cdouble, maxtype) - @checked( - Lib.XPRSnlpgetobjformula(model.inner, 1, maxtype, p_ntypes, type, value) - ) - error("FIXME") - return -end - ## ## VariableIndex-in-Set constraints. ## @@ -3048,13 +2890,11 @@ function MOI.optimize!(model::Optimizer) _set_MIP_start(model) end start_time = time() - solvestatus, solstatus = Ref{Cint}(), Ref{Cint}() - @checked Lib.XPRSoptimize( - model.inner, - model.solve_method, - solvestatus, - solstatus, - ) + if is_mip(model) + @checked Lib.XPRSmipoptimize(model.inner, model.solve_method) + else + @checked Lib.XPRSlpoptimize(model.inner, model.solve_method) + end model.cached_solution.solve_time = time() - start_time check_cb_exception(model) # Should be almost a no-op if not needed. Might have minor overhead due to @@ -3526,9 +3366,8 @@ end function MOI.get(model::Optimizer, attr::MOI.ObjectiveValue) _throw_if_optimize_in_progress(model, attr) MOI.check_result_index_bounds(model, attr) - return @_invoke( - Lib.XPRSgetdblattrib(model.inner, Lib.XPRS_OBJVAL, _)::Float64 - ) + attr = is_mip(model) ? Lib.XPRS_MIPOBJVAL : Lib.XPRS_LPOBJVAL + return @_invoke Lib.XPRSgetdblattrib(model.inner, attr, _)::Float64 end #= @@ -4782,26 +4621,139 @@ end ScalarNonlinearFunction =# -# function MOI.supports_constraint( -# ::Optimizer, -# ::MOI.ScalarNonlinearFunction, -# ::Union{ -# MOI.LessThan{Float64}, -# MOI.GreaterThan{Float64}, -# MOI.EqualTo{Float64}, -# }, -# ) -# return true -# end - -# function MOI.add_constraint( -# ::Optimizer, -# ::MOI.ScalarNonlinearFunction, -# ::Union{ -# MOI.LessThan{Float64}, -# MOI.GreaterThan{Float64}, -# MOI.EqualTo{Float64}, -# }, -# ) -# return true -# end +function MOI.supports_constraint( + ::Optimizer, + ::MOI.ScalarNonlinearFunction, + ::Union{ + MOI.LessThan{Float64}, + MOI.GreaterThan{Float64}, + MOI.EqualTo{Float64}, + }, +) + return true +end + +const _FUNCTION_MAP = Dict( + # Handled explicitly: Lib.XPRS_OP_UMINUS + :^ => (Lib.XPRS_TOK_OP, Lib.XPRS_OP_EXPONENT), + :* => (Lib.XPRS_TOK_OP, Lib.XPRS_OP_MULTIPLY), + :/ => (Lib.XPRS_TOK_OP, Lib.XPRS_OP_DIVIDE), + :+ => (Lib.XPRS_TOK_OP, Lib.XPRS_OP_PLUS), + :- => (Lib.XPRS_TOK_OP, Lib.XPRS_OP_MINUS), + # const XPRS_DEL_COMMA = 1 + # const XPRS_DEL_COLON = 2 + :log => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_LOG), + :log10 => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_LOG10), + # const XPRS_IFUN_LN = 15 + :exp => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_EXP), + :abs => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_ABS), + :sqrt => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_SQRT), + :sin => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_SIN), + :cos => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_COS), + :tan => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_TAN), + :asin => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_ARCSIN), + :acos => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_ARCCOS), + :atan => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_ARCTAN), + :max => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_MIN), + :min => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_MAX), + # const XPRS_IFUN_PWL = 35 + # Handled explicitly: XPRS_IFUN_SUM + # Handled explicitly: XPRS_IFUN_PROD + :sign => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_SIGN), + :erf => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_ERF), + :erfc => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_ERFC), +) + +function _reverse_polish( + model::Optimizer, + f::MOI.ScalarNonlinearFunction, + type::Vector{Cint}, + value::Vector{Cdouble}, +) + if f.head == :- && length(f.args) == 1 + # Special handling for unary negation + push!(type, Lib.XPRS_TOK_OP) + push!(value, Lib.XPRS_OP_UMINUS) + for arg in reverse(f.args) + _reverse_polish(model, arg, type, value) + end + return + elseif f.head in (:+, :*) && length(f.args) != 2 + # Special handling for non-binary sum and product + push!(type, Lib.XPRS_TOK_IFUN) + push!(value, f.head == :+ ? Lib.XPRS_IFUN_SUM : Lib.XPRS_IFUN_PROD) + push!(type, Lib.XPRS_TOK_LB) + push!(value, 0.0) + for arg in reverse(f.args) + _reverse_polish(model, arg, type, value) + end + push!(type, Lib.XPRS_TOK_RB) + push!(value, 0.0) + return + end + ret = get(_FUNCTION_MAP, f.head, nothing) + if ret === nothing + throw(MOI.UnsupportedNonlinearOperator(f.head)) + elseif ret[1] == Lib.XPRS_TOK_OP + push!(type, ret[1]) + push!(value, ret[2]) + for arg in reverse(f.args) + _reverse_polish(model, arg, type, value) + end + else + @assert ret[1] == Lib.XPRS_TOK_IFUN + push!(type, ret[1]) + push!(value, ret[2]) + # XPRS_TOK_LB is not needed. Implied by XPRS_TOK_IFUN + for arg in reverse(f.args) + _reverse_polish(model, arg, type, value) + end + push!(type, Lib.XPRS_TOK_RB) + push!(value, 0.0) + end + return +end + +function _reverse_polish( + ::Optimizer, + f::Real, + type::Vector{Cint}, + value::Vector{Cdouble}, +) + push!(type, Lib.XPRS_TOK_CON) + push!(value, Cdouble(f)) + return +end + +function _reverse_polish( + model::Optimizer, + x::MOI.VariableIndex, + type::Vector{Cint}, + value::Vector{Cdouble}, +) + push!(type, Lib.XPRS_TOK_COL) + push!(value, _info(model, x).column - 1) + return +end + +function _reverse_polish( + model::Optimizer, + f::MOI.AbstractScalarFunction, + type::Vector{Cint}, + value::Vector{Cdouble}, +) + _reverse_polish(model, convert(MOI.ScalarNonlinearFunction, f), type, value) + return +end + +function MOI.add_constraint( + ::Optimizer, + ::MOI.ScalarNonlinearFunction, + ::Union{ + MOI.LessThan{Float64}, + MOI.GreaterThan{Float64}, + MOI.EqualTo{Float64}, + }, +) + return true +end From 4d34f4184131ad3c254aefaffa0e6d663326c7c1 Mon Sep 17 00:00:00 2001 From: odow Date: Wed, 3 Apr 2024 11:09:57 +1300 Subject: [PATCH 03/20] Update --- src/MOI/MOI_wrapper.jl | 125 +++++++++++++++++++++++++++++++++++---- test/test_MOI_wrapper.jl | 22 +++++++ 2 files changed, 134 insertions(+), 13 deletions(-) diff --git a/src/MOI/MOI_wrapper.jl b/src/MOI/MOI_wrapper.jl index 2787ec91..5be40d5e 100644 --- a/src/MOI/MOI_wrapper.jl +++ b/src/MOI/MOI_wrapper.jl @@ -7,7 +7,16 @@ const CleverDicts = MOI.Utilities.CleverDicts @enum(VariableType, CONTINUOUS, BINARY, INTEGER, SEMIINTEGER, SEMICONTINUOUS) -@enum(ConstraintType, AFFINE, INDICATOR, QUADRATIC, SOC, RSOC, SOS_SET) +@enum( + ConstraintType, + AFFINE, + INDICATOR, + QUADRATIC, + SOC, + RSOC, + SOS_SET, + SCALAR_NONLINEAR, +) @enum( BoundType, @@ -239,6 +248,8 @@ mutable struct Optimizer <: MOI.AbstractOptimizer params::Dict{Any,Any} + has_nlp_constraints::Bool + function Optimizer(; kwargs...) model = new() model.params = Dict{Any,Any}() @@ -334,6 +345,7 @@ function MOI.empty!(model::Optimizer) for (name, value) in model.params MOI.set(model, name, value) end + model.has_nlp_constraints = false return end @@ -2890,7 +2902,9 @@ function MOI.optimize!(model::Optimizer) _set_MIP_start(model) end start_time = time() - if is_mip(model) + if model.has_nlp_constraints + @checked Lib.XPRSnlpoptimize(model.inner, model.solve_method) + elseif is_mip(model) @checked Lib.XPRSmipoptimize(model.inner, model.solve_method) else @checked Lib.XPRSlpoptimize(model.inner, model.solve_method) @@ -2906,7 +2920,15 @@ function MOI.optimize!(model::Optimizer) model.primal_status = _cache_primal_status(model) model.dual_status = _cache_dual_status(model) # TODO: add @checked here - must review statuses - if is_mip(model) + if model.has_nlp_constraints + Lib.XPRSgetnlpsol( + model.inner, + model.cached_solution.variable_primal, + model.cached_solution.linear_primal, + model.cached_solution.linear_dual, + model.cached_solution.variable_dual, + ) + elseif is_mip(model) # TODO @checked (only works if not in [MOI.NO_SOLUTION, MOI.INFEASIBILITY_CERTIFICATE, MOI.INFEASIBLE_POINT]) Lib.XPRSgetmipsol( model.inner, @@ -3019,6 +3041,33 @@ const _LPSTATUS = Dict( ), ) +const _NLPSTATUS = Dict( + Lib.XPRS_NLPSTATUS_UNSTARTED => ( + "0 Optimization unstarted ( XSLP_NLPSTATUS_UNSTARTED)", + MOI.OPTIMIZE_NOT_CALLED, + ), + Lib.XPRS_NLPSTATUS_SOLUTION => + ("1 Solution found ( XSLP_NLPSTATUS_SOLUTION)", MOI.LOCALLY_SOLVED), + Lib.XPRS_NLPSTATUS_OPTIMAL => + ("2 Globally optimal ( XSLP_NLPSTATUS_OPTIMAL)", MOI.OPTIMAL), + Lib.XPRS_NLPSTATUS_NOSOLUTION => + ("3 No solution found ( XSLP_NLPSTATUS_NOSOLUTION)", MOI.OTHER_ERROR), + Lib.XPRS_NLPSTATUS_INFEASIBLE => + ("4 Proven infeasible ( XSLP_NLPSTATUS_INFEASIBLE)", MOI.INFEASIBLE), + Lib.XPRS_NLPSTATUS_UNBOUNDED => ( + "5 Locally unbounded ( XSLP_NLPSTATUS_UNBOUNDED)", + MOI.DUAL_INFEASIBLE, + ), + Lib.XPRS_NLPSTATUS_UNFINISHED => ( + "6 Not yet solved to completion ( XSLP_NLPSTATUS_UNFINISHED)", + MOI.OTHER_ERROR, + ), + Lib.XPRS_NLPSTATUS_UNSOLVED => ( + "7 Could not be solved due to numerical issues ( XSLP_NLPSTATUS_UNSOLVED)", + MOI.NUMERICAL_ERROR, + ), +) + const _STOPSTATUS = Dict( Lib.XPRS_STOP_NONE => ("no interruption - the solve completed normally", MOI.OPTIMAL), @@ -3035,7 +3084,14 @@ function MOI.get(model::Optimizer, attr::MOI.RawStatusString) _throw_if_optimize_in_progress(model, attr) stop = @_invoke Lib.XPRSgetintattrib(model.inner, Lib.XPRS_STOPSTATUS, _)::Int - if is_mip(model) + if model.has_nlp_constraints + stat = @_invoke Lib.XPRSgetintattrib( + model.inner, + Lib.XPRS_NLPSTATUS, + _, + )::Int + return _NLPSTATUS[stat][1] * " - " * _STOPSTATUS[stop][1] + elseif is_mip(model) stat = @_invoke Lib.XPRSgetintattrib( model.inner, Lib.XPRS_MIPSTATUS, @@ -3057,6 +3113,10 @@ function _cache_termination_status(model::Optimizer) @_invoke Lib.XPRSgetintattrib(model.inner, Lib.XPRS_STOPSTATUS, _)::Int if stop != Lib.XPRS_STOP_NONE && stop != Lib.XPRS_STOP_MIPGAP return _STOPSTATUS[stop][2] + elseif model.has_nlp_constraints + nlpstatus = Lib.XPRS_NLPSTATUS + stat = @_invoke Lib.XPRSgetintattrib(model.inner, nlpstatus, _)::Int + return _NLPSTATUS[stat][2] elseif is_mip(model) mipstatus = Lib.XPRS_MIPSTATUS stat = @_invoke Lib.XPRSgetintattrib(model.inner, mipstatus, _)::Int @@ -3092,7 +3152,9 @@ function _cache_primal_status(model) if _has_primal_ray(model) return MOI.INFEASIBILITY_CERTIFICATE end - dict, attr = if is_mip(model) + dict, attr = if model.has_nlp_constraints + _NLPSTATUS, Lib.XPRS_NLPSTATUS + elseif is_mip(model) _MIPSTATUS, Lib.XPRS_MIPSTATUS else _LPSTATUS, Lib.XPRS_LPSTATUS @@ -3584,6 +3646,7 @@ end _function_enums(::Type{<:MOI.ScalarAffineFunction}) = (AFFINE,) _function_enums(::Type{<:MOI.ScalarQuadraticFunction}) = (QUADRATIC,) +_function_enums(::Type{<:MOI.ScalarNonlinearFunction}) = (SCALAR_NONLINEAR,) _function_enums(::Type{<:MOI.VectorAffineFunction}) = (INDICATOR,) _function_enums(::Type{<:MOI.VectorOfVariables}) = (SOC, RSOC) @@ -3594,6 +3657,7 @@ function MOI.get( F<:Union{ MOI.ScalarAffineFunction{Float64}, MOI.ScalarQuadraticFunction{Float64}, + MOI.ScalarNonlinearFunction, MOI.VectorAffineFunction{Float64}, MOI.VectorOfVariables, }, @@ -3666,12 +3730,14 @@ function MOI.get(model::Optimizer, ::MOI.ListOfConstraintTypesPresent) ) elseif info.type == SOC push!(constraints, (MOI.VectorOfVariables, MOI.SecondOrderCone)) - else - @assert info.type == RSOC + elseif info.type == RSOC push!( constraints, (MOI.VectorOfVariables, MOI.RotatedSecondOrderCone), ) + else + @assert info.type == SCALAR_NONLINEAR + push!(constraints, (MOI.ScalarNonlinearFunction, typeof(info.set))) end end for info in values(model.sos_constraint_info) @@ -4642,9 +4708,9 @@ const _FUNCTION_MAP = Dict( :- => (Lib.XPRS_TOK_OP, Lib.XPRS_OP_MINUS), # const XPRS_DEL_COMMA = 1 # const XPRS_DEL_COLON = 2 - :log => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_LOG), + # const XPRS_IFUN_LOG = 13 :log10 => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_LOG10), - # const XPRS_IFUN_LN = 15 + :log => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_LN), :exp => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_EXP), :abs => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_ABS), :sqrt => (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_SQRT), @@ -4747,13 +4813,46 @@ function _reverse_polish( end function MOI.add_constraint( - ::Optimizer, - ::MOI.ScalarNonlinearFunction, - ::Union{ + model::Optimizer, + f::MOI.ScalarNonlinearFunction, + s::Union{ MOI.LessThan{Float64}, MOI.GreaterThan{Float64}, MOI.EqualTo{Float64}, }, ) - return true + F, S = typeof(f), typeof(s) + model.last_constraint_index += 1 + row = length(model.affine_constraint_info) + 1 + model.affine_constraint_info[model.last_constraint_index] = + ConstraintInfo(row, s, SCALAR_NONLINEAR) + sense, rhs = _sense_and_rhs(s) + @checked Lib.XPRSaddrows( + model.inner, + 1, + 0, + Ref{UInt8}(sense), + Ref(rhs), + C_NULL, + Cint[0], + Cint[], + Cdouble[], + ) + parsed, type, value = Cint(1), Cint[], Cdouble[] + _reverse_polish(model, f, type, value) + reverse!(type) + reverse!(value) + push!(type, Lib.XPRS_TOK_EOF) + push!(value, 0.0) + @checked Lib.XPRSnlpaddformulas( + model.inner, + 1, # ncoefs, + Cint[row - 1], # rowind, + Cint[0, length(type)], # formulastart, + parsed, + type, + value, + ) + model.has_nlp_constraints = true + return MOI.ConstraintIndex{F,S}(model.last_constraint_index) end diff --git a/test/test_MOI_wrapper.jl b/test/test_MOI_wrapper.jl index 2a20a2fa..aa460048 100644 --- a/test/test_MOI_wrapper.jl +++ b/test/test_MOI_wrapper.jl @@ -2491,6 +2491,28 @@ function test_conflict_infeasible_bounds() return end +function test_nlp_constraint_log() + model = Xpress.Optimizer() + MOI.set(model, MOI.Silent(), true) + x = MOI.add_variable(model) + t = MOI.add_variable(model) + MOI.add_constraint(model, x, MOI.LessThan(2.0)) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + f = 1.0 * t + MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) + g = MOI.ScalarNonlinearFunction( + :-, + Any[MOI.ScalarNonlinearFunction(:log, Any[x]), t], + ) + MOI.add_constraint(model, g, MOI.GreaterThan(0.0)) + MOI.optimize!(model) + x_val = MOI.get(model, MOI.VariablePrimal(), x) + t_val = MOI.get(model, MOI.VariablePrimal(), t) + @test ≈(x_val, 2.0; atol = 1e-6) + @test ≈(t_val, log(x_val); atol = 1e-6) + return +end + end # TestMOIWrapper TestMOIWrapper.runtests() From b5f575842b021069e9f4af664c4dc01b3a460df3 Mon Sep 17 00:00:00 2001 From: odow Date: Wed, 3 Apr 2024 11:13:51 +1300 Subject: [PATCH 04/20] Update --- src/MOI/MOI_wrapper.jl | 10 ++++++++-- test/test_MOI_wrapper.jl | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/MOI/MOI_wrapper.jl b/src/MOI/MOI_wrapper.jl index 5be40d5e..5f95296e 100644 --- a/src/MOI/MOI_wrapper.jl +++ b/src/MOI/MOI_wrapper.jl @@ -1196,7 +1196,7 @@ function MOI.set( ::MOI.ObjectiveFunction{F}, f::F, ) where {F<:MOI.ScalarQuadraticFunction{Float64}} - # setting linear part also clears the existing quadratic and nonlinear terms + # setting linear part also clears the existing quadratic terms MOI.set( model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), @@ -3428,7 +3428,13 @@ end function MOI.get(model::Optimizer, attr::MOI.ObjectiveValue) _throw_if_optimize_in_progress(model, attr) MOI.check_result_index_bounds(model, attr) - attr = is_mip(model) ? Lib.XPRS_MIPOBJVAL : Lib.XPRS_LPOBJVAL + attr = if model.has_nlp_constraints + Lib.XPRS_NLPOBJVAL + elseif is_mip(model) + Lib.XPRS_MIPOBJVAL + else + Lib.XPRS_LPOBJVAL + end return @_invoke Lib.XPRSgetdblattrib(model.inner, attr, _)::Float64 end diff --git a/test/test_MOI_wrapper.jl b/test/test_MOI_wrapper.jl index aa460048..96ad6efa 100644 --- a/test/test_MOI_wrapper.jl +++ b/test/test_MOI_wrapper.jl @@ -2510,6 +2510,7 @@ function test_nlp_constraint_log() t_val = MOI.get(model, MOI.VariablePrimal(), t) @test ≈(x_val, 2.0; atol = 1e-6) @test ≈(t_val, log(x_val); atol = 1e-6) + @test ≈(MOI.get(model, MOI.ObjectiveValue()), t_val; atol = 1e-6) return end From 9953e83009bac4a8523a35dc5eb3b04dbc9f323e Mon Sep 17 00:00:00 2001 From: odow Date: Wed, 3 Apr 2024 12:55:58 +1300 Subject: [PATCH 05/20] Update --- src/MOI/MOI_wrapper.jl | 4 +++- test/test_MOI_wrapper.jl | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/MOI/MOI_wrapper.jl b/src/MOI/MOI_wrapper.jl index 5f95296e..662b1b68 100644 --- a/src/MOI/MOI_wrapper.jl +++ b/src/MOI/MOI_wrapper.jl @@ -4693,6 +4693,8 @@ end ScalarNonlinearFunction =# +_supports_nonlinear() = get_version() >= v"41" + function MOI.supports_constraint( ::Optimizer, ::MOI.ScalarNonlinearFunction, @@ -4702,7 +4704,7 @@ function MOI.supports_constraint( MOI.EqualTo{Float64}, }, ) - return true + return get_version() >= v"41" end const _FUNCTION_MAP = Dict( diff --git a/test/test_MOI_wrapper.jl b/test/test_MOI_wrapper.jl index 96ad6efa..b3ab7dc5 100644 --- a/test/test_MOI_wrapper.jl +++ b/test/test_MOI_wrapper.jl @@ -1045,7 +1045,7 @@ function test_name_empty_names() end function test_dummy_nlp() - if Xpress.get_version() < v"41" + if Xpress._supports_nonlinear() return end model = Xpress.Optimizer(; OUTPUTLOG = 0) @@ -2492,6 +2492,9 @@ function test_conflict_infeasible_bounds() end function test_nlp_constraint_log() + if Xpress._supports_nonlinear() + return + end model = Xpress.Optimizer() MOI.set(model, MOI.Silent(), true) x = MOI.add_variable(model) From b15435ba0e0b6185c2cadffa8b1f791e01d07e3d Mon Sep 17 00:00:00 2001 From: odow Date: Wed, 3 Apr 2024 12:59:32 +1300 Subject: [PATCH 06/20] Fix formatting --- src/MOI/MOI_wrapper.jl | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/MOI/MOI_wrapper.jl b/src/MOI/MOI_wrapper.jl index 662b1b68..371fc24c 100644 --- a/src/MOI/MOI_wrapper.jl +++ b/src/MOI/MOI_wrapper.jl @@ -3050,10 +3050,14 @@ const _NLPSTATUS = Dict( ("1 Solution found ( XSLP_NLPSTATUS_SOLUTION)", MOI.LOCALLY_SOLVED), Lib.XPRS_NLPSTATUS_OPTIMAL => ("2 Globally optimal ( XSLP_NLPSTATUS_OPTIMAL)", MOI.OPTIMAL), - Lib.XPRS_NLPSTATUS_NOSOLUTION => - ("3 No solution found ( XSLP_NLPSTATUS_NOSOLUTION)", MOI.OTHER_ERROR), - Lib.XPRS_NLPSTATUS_INFEASIBLE => - ("4 Proven infeasible ( XSLP_NLPSTATUS_INFEASIBLE)", MOI.INFEASIBLE), + Lib.XPRS_NLPSTATUS_NOSOLUTION => ( + "3 No solution found ( XSLP_NLPSTATUS_NOSOLUTION)", + MOI.OTHER_ERROR, + ), + Lib.XPRS_NLPSTATUS_INFEASIBLE => ( + "4 Proven infeasible ( XSLP_NLPSTATUS_INFEASIBLE)", + MOI.INFEASIBLE, + ), Lib.XPRS_NLPSTATUS_UNBOUNDED => ( "5 Locally unbounded ( XSLP_NLPSTATUS_UNBOUNDED)", MOI.DUAL_INFEASIBLE, @@ -4855,7 +4859,7 @@ function MOI.add_constraint( @checked Lib.XPRSnlpaddformulas( model.inner, 1, # ncoefs, - Cint[row - 1], # rowind, + Cint[row-1], # rowind, Cint[0, length(type)], # formulastart, parsed, type, From c244fccb503ca715053c5e559ededec7874ac0f3 Mon Sep 17 00:00:00 2001 From: odow Date: Wed, 3 Apr 2024 13:14:32 +1300 Subject: [PATCH 07/20] Update --- test/test_MOI_wrapper.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_MOI_wrapper.jl b/test/test_MOI_wrapper.jl index b3ab7dc5..c4b29e02 100644 --- a/test/test_MOI_wrapper.jl +++ b/test/test_MOI_wrapper.jl @@ -1045,7 +1045,7 @@ function test_name_empty_names() end function test_dummy_nlp() - if Xpress._supports_nonlinear() + if !Xpress._supports_nonlinear() return end model = Xpress.Optimizer(; OUTPUTLOG = 0) @@ -2492,7 +2492,7 @@ function test_conflict_infeasible_bounds() end function test_nlp_constraint_log() - if Xpress._supports_nonlinear() + if !Xpress._supports_nonlinear() return end model = Xpress.Optimizer() From d5d7688d1faa27b9175b59de4d0f9b92338778cd Mon Sep 17 00:00:00 2001 From: odow Date: Wed, 3 Apr 2024 14:08:33 +1300 Subject: [PATCH 08/20] Add more tests --- src/MOI/MOI_wrapper.jl | 3 +- test/test_MOI_wrapper.jl | 76 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/src/MOI/MOI_wrapper.jl b/src/MOI/MOI_wrapper.jl index 371fc24c..893de327 100644 --- a/src/MOI/MOI_wrapper.jl +++ b/src/MOI/MOI_wrapper.jl @@ -4760,8 +4760,7 @@ function _reverse_polish( # Special handling for non-binary sum and product push!(type, Lib.XPRS_TOK_IFUN) push!(value, f.head == :+ ? Lib.XPRS_IFUN_SUM : Lib.XPRS_IFUN_PROD) - push!(type, Lib.XPRS_TOK_LB) - push!(value, 0.0) + # XPRS_TOK_LB is not needed. Implied by XPRS_TOK_IFUN for arg in reverse(f.args) _reverse_polish(model, arg, type, value) end diff --git a/test/test_MOI_wrapper.jl b/test/test_MOI_wrapper.jl index c4b29e02..e7dd452d 100644 --- a/test/test_MOI_wrapper.jl +++ b/test/test_MOI_wrapper.jl @@ -2507,16 +2507,90 @@ function test_nlp_constraint_log() :-, Any[MOI.ScalarNonlinearFunction(:log, Any[x]), t], ) - MOI.add_constraint(model, g, MOI.GreaterThan(0.0)) + c = MOI.add_constraint(model, g, MOI.GreaterThan(0.0)) MOI.optimize!(model) + F, S = MOI.ScalarNonlinearFunction, MOI.GreaterThan{Float64} + @test MOI.supports_constraint(model, F, S) + @test (F, S) in MOI.get(model, MOI.ListOfConstraintTypesPresent()) + @test c in MOI.get(model, MOI.ListOfConstraintIndices{F,S}()) x_val = MOI.get(model, MOI.VariablePrimal(), x) t_val = MOI.get(model, MOI.VariablePrimal(), t) + @test MOI.get(model, MOI.RawStatusString()) == + "1 Solution found ( XSLP_NLPSTATUS_SOLUTION) - no interruption - the solve completed normally" @test ≈(x_val, 2.0; atol = 1e-6) @test ≈(t_val, log(x_val); atol = 1e-6) @test ≈(MOI.get(model, MOI.ObjectiveValue()), t_val; atol = 1e-6) return end +function test_nlp_constraint_unsupported_nonlinear_operator() + if !Xpress._supports_nonlinear() + return + end + model = Xpress.Optimizer() + x = MOI.add_variable(model) + f = MOI.ScalarNonlinearFunction(:foo, Any[x]) + @test_throws( + MOI.UnsupportedNonlinearOperator(:foo), + MOI.add_constraint(model, f, MOI.GreaterThan(0.0)), + ) + return +end + +function test_nlp_constraint_unary_negation() + if !Xpress._supports_nonlinear() + return + end + model = Xpress.Optimizer() + MOI.set(model, MOI.Silent(), true) + x = MOI.add_variable(model) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + f = 1.0 * x + MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) + g = MOI.ScalarNonlinearFunction(:-, Any[x]) + MOI.add_constraint(model, g, MOI.GreaterThan(-2.0)) + MOI.optimize!(model) + @test ≈(MOI.get(model, MOI.VariablePrimal(), x), 2.0; atol = 1e-3) + @test ≈(MOI.get(model, MOI.ObjectiveValue()), 2.0; atol = 1e-3) + return +end + +function test_nlp_constraint_scalar_affine_function() + if !Xpress._supports_nonlinear() + return + end + model = Xpress.Optimizer() + MOI.set(model, MOI.Silent(), true) + x = MOI.add_variable(model) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + f = 1.2 * x + 1.3 + MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) + g = MOI.ScalarNonlinearFunction(:-, Any[1.5 * x + 2.0]) + MOI.add_constraint(model, g, MOI.GreaterThan(-6.0)) + MOI.optimize!(model) + @test ≈(MOI.get(model, MOI.VariablePrimal(), x), 2 + 2 / 3; atol = 1e-3) + @test ≈(MOI.get(model, MOI.ObjectiveValue()), 4.5; atol = 1e-3) + return +end + +function test_nlp_constraint_product() + if !Xpress._supports_nonlinear() + return + end + model = Xpress.Optimizer() + MOI.set(model, MOI.Silent(), true) + x = MOI.add_variable(model) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + f = 1.0 * x + MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) + g = MOI.ScalarNonlinearFunction(:*, Any[x, 2.0, x]) + MOI.add_constraint(model, g, MOI.LessThan(3.0)) + MOI.optimize!(model) + @test ≈(MOI.get(model, MOI.VariablePrimal(), x), sqrt(3 / 2); atol = 1e-3) + @test ≈(MOI.get(model, MOI.ObjectiveValue()), sqrt(3 / 2); atol = 1e-3) + return +end + end # TestMOIWrapper TestMOIWrapper.runtests() From 159c4f00a8bc28c752bdbef4334fdbd35c145235 Mon Sep 17 00:00:00 2001 From: odow Date: Wed, 3 Apr 2024 14:14:11 +1300 Subject: [PATCH 09/20] Update --- src/MOI/MOI_wrapper.jl | 40 +++++++++++++--------------------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/src/MOI/MOI_wrapper.jl b/src/MOI/MOI_wrapper.jl index 893de327..26740d87 100644 --- a/src/MOI/MOI_wrapper.jl +++ b/src/MOI/MOI_wrapper.jl @@ -4748,43 +4748,29 @@ function _reverse_polish( type::Vector{Cint}, value::Vector{Cdouble}, ) - if f.head == :- && length(f.args) == 1 - # Special handling for unary negation + if f.head == :- && length(f.args) == 1 # Special handling for unary - push!(type, Lib.XPRS_TOK_OP) push!(value, Lib.XPRS_OP_UMINUS) for arg in reverse(f.args) _reverse_polish(model, arg, type, value) end return - elseif f.head in (:+, :*) && length(f.args) != 2 - # Special handling for non-binary sum and product - push!(type, Lib.XPRS_TOK_IFUN) - push!(value, f.head == :+ ? Lib.XPRS_IFUN_SUM : Lib.XPRS_IFUN_PROD) - # XPRS_TOK_LB is not needed. Implied by XPRS_TOK_IFUN - for arg in reverse(f.args) - _reverse_polish(model, arg, type, value) - end - push!(type, Lib.XPRS_TOK_RB) - push!(value, 0.0) - return end ret = get(_FUNCTION_MAP, f.head, nothing) if ret === nothing throw(MOI.UnsupportedNonlinearOperator(f.head)) - elseif ret[1] == Lib.XPRS_TOK_OP - push!(type, ret[1]) - push!(value, ret[2]) - for arg in reverse(f.args) - _reverse_polish(model, arg, type, value) - end - else - @assert ret[1] == Lib.XPRS_TOK_IFUN - push!(type, ret[1]) - push!(value, ret[2]) - # XPRS_TOK_LB is not needed. Implied by XPRS_TOK_IFUN - for arg in reverse(f.args) - _reverse_polish(model, arg, type, value) - end + elseif f.head == :+ && length(f.args) != 2 # Special handling for n-ary + + ret = (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_SUM) + elseif f.head == :* && length(f.args) != 2 # Special handling for n-ary * + ret = (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_PROD) + end + push!(type, ret[1]) + push!(value, ret[2]) + for arg in reverse(f.args) + _reverse_polish(model, arg, type, value) + end + if ret[1] == Lib.XPRS_TOK_IFUN + # XPRS_TOK_LB is not needed because it is implied by XPRS_TOK_IFUN push!(type, Lib.XPRS_TOK_RB) push!(value, 0.0) end From b34a6c88264e3bb81e43932972a6e9a831eece67 Mon Sep 17 00:00:00 2001 From: odow Date: Wed, 3 Apr 2024 14:21:38 +1300 Subject: [PATCH 10/20] Fix --- src/MOI/MOI_wrapper.jl | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/MOI/MOI_wrapper.jl b/src/MOI/MOI_wrapper.jl index 26740d87..b1569891 100644 --- a/src/MOI/MOI_wrapper.jl +++ b/src/MOI/MOI_wrapper.jl @@ -4701,11 +4701,13 @@ _supports_nonlinear() = get_version() >= v"41" function MOI.supports_constraint( ::Optimizer, - ::MOI.ScalarNonlinearFunction, - ::Union{ - MOI.LessThan{Float64}, - MOI.GreaterThan{Float64}, - MOI.EqualTo{Float64}, + ::Type{MOI.ScalarNonlinearFunction}, + ::Type{ + <:Union{ + MOI.LessThan{Float64}, + MOI.GreaterThan{Float64}, + MOI.EqualTo{Float64}, + }, }, ) return get_version() >= v"41" From cfe6349b1b5bef80a14cbe66aefb0946eb4ee902 Mon Sep 17 00:00:00 2001 From: odow Date: Wed, 3 Apr 2024 14:30:08 +1300 Subject: [PATCH 11/20] Update --- src/MOI/MOI_wrapper.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MOI/MOI_wrapper.jl b/src/MOI/MOI_wrapper.jl index b1569891..47e0a53e 100644 --- a/src/MOI/MOI_wrapper.jl +++ b/src/MOI/MOI_wrapper.jl @@ -2110,6 +2110,7 @@ function MOI.is_valid( F<:Union{ MOI.ScalarAffineFunction{Float64}, MOI.ScalarQuadraticFunction{Float64}, + MOI.ScalarNonlinearFunction, MOI.VectorAffineFunction{Float64}, MOI.VectorOfVariables, }, @@ -2117,9 +2118,8 @@ function MOI.is_valid( info = get(model.affine_constraint_info, c.value, nothing) if info === nothing return false - else - return typeof(info.set) == S end + return info.type in _function_enum(F) && typeof(info.set) == S end function MOI.add_constraint( From 8108f7c53c80c7dc0a719e97f0e8418df8cc71a6 Mon Sep 17 00:00:00 2001 From: odow Date: Wed, 3 Apr 2024 15:15:21 +1300 Subject: [PATCH 12/20] Update --- src/MOI/MOI_wrapper.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/MOI/MOI_wrapper.jl b/src/MOI/MOI_wrapper.jl index 47e0a53e..5de27829 100644 --- a/src/MOI/MOI_wrapper.jl +++ b/src/MOI/MOI_wrapper.jl @@ -2092,6 +2092,7 @@ function _info( F<:Union{ MOI.ScalarAffineFunction{Float64}, MOI.ScalarQuadraticFunction{Float64}, + MOI.ScalarNonlinearFunction, MOI.VectorAffineFunction{Float64}, MOI.VectorOfVariables, }, @@ -2119,7 +2120,7 @@ function MOI.is_valid( if info === nothing return false end - return info.type in _function_enum(F) && typeof(info.set) == S + return info.type in _function_enums(F) && typeof(info.set) == S end function MOI.add_constraint( From ac39cda350dd5de69f58f9dab06799476ac3d653 Mon Sep 17 00:00:00 2001 From: odow Date: Wed, 3 Apr 2024 15:28:26 +1300 Subject: [PATCH 13/20] Update --- src/MOI/MOI_wrapper.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/MOI/MOI_wrapper.jl b/src/MOI/MOI_wrapper.jl index 5de27829..3a0c5a0e 100644 --- a/src/MOI/MOI_wrapper.jl +++ b/src/MOI/MOI_wrapper.jl @@ -2334,6 +2334,7 @@ function MOI.supports( MOI.VectorAffineFunction{Float64}, MOI.ScalarAffineFunction{Float64}, MOI.ScalarQuadraticFunction{Float64}, + MOI.ScalarNonlinearFunction, MOI.VectorOfVariables, }, } @@ -2349,6 +2350,7 @@ function MOI.get( MOI.VectorAffineFunction{Float64}, MOI.ScalarAffineFunction{Float64}, MOI.ScalarQuadraticFunction{Float64}, + MOI.ScalarNonlinearFunction, MOI.VectorOfVariables, }, } @@ -2365,6 +2367,7 @@ function MOI.set( MOI.VectorAffineFunction{Float64}, MOI.ScalarAffineFunction{Float64}, MOI.ScalarQuadraticFunction{Float64}, + MOI.ScalarNonlinearFunction, MOI.VectorOfVariables, }, } From 9a0c9ffa49d1f18126ead76c27dda6d23d818740 Mon Sep 17 00:00:00 2001 From: odow Date: Wed, 3 Apr 2024 15:29:58 +1300 Subject: [PATCH 14/20] Update --- src/MOI/MOI_wrapper.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/MOI/MOI_wrapper.jl b/src/MOI/MOI_wrapper.jl index 3a0c5a0e..12012b58 100644 --- a/src/MOI/MOI_wrapper.jl +++ b/src/MOI/MOI_wrapper.jl @@ -2215,6 +2215,7 @@ function MOI.delete( MOI.VectorAffineFunction{Float64}, MOI.ScalarAffineFunction{Float64}, MOI.ScalarQuadraticFunction{Float64}, + MOI.ScalarNonlinearFunction, }, } row = _info(model, c).row From 6f41119a66934c2a0ec10065ec2320842f0c9908 Mon Sep 17 00:00:00 2001 From: odow Date: Wed, 3 Apr 2024 17:17:24 +1300 Subject: [PATCH 15/20] Update --- src/MOI/MOI_wrapper.jl | 48 ++++++++++++++++++++++++++++++++++++---- test/test_MOI_wrapper.jl | 27 +++++++++++++++++----- 2 files changed, 65 insertions(+), 10 deletions(-) diff --git a/src/MOI/MOI_wrapper.jl b/src/MOI/MOI_wrapper.jl index 12012b58..3f448b46 100644 --- a/src/MOI/MOI_wrapper.jl +++ b/src/MOI/MOI_wrapper.jl @@ -2215,7 +2215,6 @@ function MOI.delete( MOI.VectorAffineFunction{Float64}, MOI.ScalarAffineFunction{Float64}, MOI.ScalarQuadraticFunction{Float64}, - MOI.ScalarNonlinearFunction, }, } row = _info(model, c).row @@ -2434,9 +2433,11 @@ function _rebuild_name_to_constraint_index_util(model::Optimizer, dict) MOI.VectorAffineFunction{Float64} elseif info.type == SOC MOI.VectorOfVariables - else - @assert info.type == RSOC + elseif info.type == RSOC MOI.VectorOfVariables + else + @assert info.type == SCALAR_NONLINEAR + MOI.ScalarNonlinearFunction end model.name_to_constraint_index[info.name] = MOI.ConstraintIndex{F,typeof(info.set)}(index) @@ -3188,7 +3189,7 @@ function _cache_dual_status(model) return MOI.NO_SOLUTION end term_stat = MOI.get(model, MOI.TerminationStatus()) - if term_stat == MOI.OPTIMAL + if term_stat in (MOI.OPTIMAL, MOI.LOCALLY_SOLVED) return MOI.FEASIBLE_POINT elseif term_stat == MOI.INFEASIBLE if _has_dual_ray(model) @@ -4860,3 +4861,42 @@ function MOI.add_constraint( model.has_nlp_constraints = true return MOI.ConstraintIndex{F,S}(model.last_constraint_index) end + +function MOI.delete( + model::Optimizer, + c::MOI.ConstraintIndex{MOI.ScalarNonlinearFunction,<:Any}, +) + row = _info(model, c).row + @checked Lib.XPRSnlpdelformulas(model.inner, 1, Ref{Cint}(row - 1)) + @checked Lib.XPRSdelrows(model.inner, 1, Ref{Cint}(row - 1)) + for (key, info) in model.affine_constraint_info + if info.row > row + info.row -= 1 + end + end + delete!(model.affine_constraint_info, c.value) + model.name_to_constraint_index = nothing + return +end + +# function MOI.get( +# model::Optimizer, +# attr::MOI.ConstraintDual, +# c::MOI.ConstraintIndex{MOI.ScalarNonlinearFunction,<:Any}, +# ) +# _throw_if_optimize_in_progress(model, attr) +# MOI.check_result_index_bounds(model, attr) +# row = _info(model, c).row +# return _dual_multiplier(model) * model.cached_solution.linear_dual[row] +# end + +function MOI.get( + model::Optimizer, + attr::MOI.ConstraintPrimal, + c::MOI.ConstraintIndex{MOI.ScalarNonlinearFunction,<:Any}, +) + _throw_if_optimize_in_progress(model, attr) + MOI.check_result_index_bounds(model, attr) + row = _info(model, c).row + return model.cached_solution.linear_primal[row] +end diff --git a/test/test_MOI_wrapper.jl b/test/test_MOI_wrapper.jl index e7dd452d..d5e81d4d 100644 --- a/test/test_MOI_wrapper.jl +++ b/test/test_MOI_wrapper.jl @@ -51,13 +51,12 @@ function test_Basic_Parameters() end function test_runtests() - optimizer = Xpress.Optimizer(; OUTPUTLOG = 0) - model = MOI.Bridges.full_bridge_optimizer(optimizer, Float64) + model = MOI.instantiate(Xpress.Optimizer; with_bridge_type = Float64) MOI.set(model, MOI.Silent(), true) MOI.Test.runtests( model, MOI.Test.Config(; atol = 1e-3, rtol = 1e-3); - exclude = String[ + exclude = [ # tested with PRESOLVE=0 below "_SecondOrderCone_", "test_constraint_PrimalStart_DualStart_SecondOrderCone", @@ -65,11 +64,27 @@ function test_runtests() "_GeometricMeanCone_", # Xpress cannot handle nonconvex quadratic constraint "test_quadratic_nonconvex_", + # Nonlinear tests + "test_nonlinear_duals", + "test_nonlinear_expression_", ], ) - - optimizer_no_presolve = Xpress.Optimizer(; OUTPUTLOG = 0, PRESOLVE = 0) - model = MOI.Bridges.full_bridge_optimizer(optimizer_no_presolve, Float64) + MOI.Test.runtests( + model, + MOI.Test.Config(; + atol = 1e-3, + rtol = 1e-3, + exclude = Any[MOI.ConstraintDual, MOI.ConstraintPrimal], + optimal_status = MOI.LOCALLY_SOLVED, + ); + include = [ + "test_nonlinear_duals", + "test_nonlinear_expression_", + ], + # This test is actually MOI.OPTIMAL. It's okay to ignore for now. + exclude = ["test_nonlinear_expression_overrides_objective"], + ) + MOI.set(model, MOI.RawOptimizerAttribute("PRESOLVE"), 0) MOI.Test.runtests( model, MOI.Test.Config(; From e92cd80dda944a7ecaa85a394e719697f2b949eb Mon Sep 17 00:00:00 2001 From: odow Date: Wed, 3 Apr 2024 17:53:21 +1300 Subject: [PATCH 16/20] Update --- src/MOI/MOI_wrapper.jl | 2 ++ test/test_MOI_wrapper.jl | 42 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/MOI/MOI_wrapper.jl b/src/MOI/MOI_wrapper.jl index 3f448b46..e63cd0f4 100644 --- a/src/MOI/MOI_wrapper.jl +++ b/src/MOI/MOI_wrapper.jl @@ -4879,6 +4879,8 @@ function MOI.delete( return end +# This seemed to return wrong answers, likely because it is locally solving to +# the local SLP solution, not the dual solution expected by, e.g., Ipopt? # function MOI.get( # model::Optimizer, # attr::MOI.ConstraintDual, diff --git a/test/test_MOI_wrapper.jl b/test/test_MOI_wrapper.jl index d5e81d4d..94a79db4 100644 --- a/test/test_MOI_wrapper.jl +++ b/test/test_MOI_wrapper.jl @@ -64,7 +64,7 @@ function test_runtests() "_GeometricMeanCone_", # Xpress cannot handle nonconvex quadratic constraint "test_quadratic_nonconvex_", - # Nonlinear tests + # Nonlinear tests because these return LOCALLY_SOLVED "test_nonlinear_duals", "test_nonlinear_expression_", ], @@ -74,7 +74,7 @@ function test_runtests() MOI.Test.Config(; atol = 1e-3, rtol = 1e-3, - exclude = Any[MOI.ConstraintDual, MOI.ConstraintPrimal], + exclude = Any[MOI.ConstraintDual], optimal_status = MOI.LOCALLY_SOLVED, ); include = [ @@ -2606,6 +2606,44 @@ function test_nlp_constraint_product() return end +function test_nlp_get_constraint_by_name() + if !Xpress._supports_nonlinear() + return + end + model = Xpress.Optimizer() + MOI.set(model, MOI.Silent(), true) + x = MOI.add_variable(model) + g = MOI.ScalarNonlinearFunction(:*, Any[x, 2.0, x]) + c = MOI.add_constraint(model, g, MOI.LessThan(3.0)) + MOI.set(model, MOI.ConstraintName(), c, "c") + d = MOI.get(model, MOI.ConstraintIndex, "c") + @test d == c + return +end + +function test_nlp_constraint_delete() + if !Xpress._supports_nonlinear() + return + end + model = Xpress.Optimizer() + MOI.set(model, MOI.Silent(), true) + x = MOI.add_variable(model) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + f = 1.0 * x + MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) + g_bad = MOI.ScalarNonlinearFunction(:exp, Any[x]) + c_bad = MOI.add_constraint(model, g, MOI.GreaterThan(20.0)) + g = MOI.ScalarNonlinearFunction(:*, Any[x, 2.0, x]) + MOI.add_constraint(model, g, MOI.LessThan(3.0)) + @test MOI.is_valid(model, c_bad) + MOI.delete(model, c_bad) + @test !MOI.is_valid(model, c_bad) + MOI.optimize!(model) + @test ≈(MOI.get(model, MOI.VariablePrimal(), x), sqrt(3 / 2); atol = 1e-3) + @test ≈(MOI.get(model, MOI.ObjectiveValue()), sqrt(3 / 2); atol = 1e-3) + return +end + end # TestMOIWrapper TestMOIWrapper.runtests() From 7dfc743a8967517245c50f92c4e06e2b2e58455f Mon Sep 17 00:00:00 2001 From: odow Date: Wed, 3 Apr 2024 17:57:32 +1300 Subject: [PATCH 17/20] Update --- src/MOI/MOI_wrapper.jl | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/MOI/MOI_wrapper.jl b/src/MOI/MOI_wrapper.jl index e63cd0f4..b8bc1047 100644 --- a/src/MOI/MOI_wrapper.jl +++ b/src/MOI/MOI_wrapper.jl @@ -4756,21 +4756,15 @@ function _reverse_polish( type::Vector{Cint}, value::Vector{Cdouble}, ) - if f.head == :- && length(f.args) == 1 # Special handling for unary - - push!(type, Lib.XPRS_TOK_OP) - push!(value, Lib.XPRS_OP_UMINUS) - for arg in reverse(f.args) - _reverse_polish(model, arg, type, value) - end - return - end ret = get(_FUNCTION_MAP, f.head, nothing) if ret === nothing throw(MOI.UnsupportedNonlinearOperator(f.head)) - elseif f.head == :+ && length(f.args) != 2 # Special handling for n-ary + + elseif f.head == :+ && length(f.args) != 2 # Special handling for n-ary + ret = (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_SUM) - elseif f.head == :* && length(f.args) != 2 # Special handling for n-ary * + elseif f.head == :* && length(f.args) != 2 # Special handling for n-ary * ret = (Lib.XPRS_TOK_IFUN, Lib.XPRS_IFUN_PROD) + elseif f.head == :- && length(f.args) == 1 # Special handling for unary - + ret = (Lib.XPRS_TOK_OP, Lib.XPRS_OP_UMINUS) end push!(type, ret[1]) push!(value, ret[2]) From 76f615da58cfb7991345d358b69648e7982a9e05 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Wed, 3 Apr 2024 18:05:18 +1300 Subject: [PATCH 18/20] Update test_MOI_wrapper.jl --- test/test_MOI_wrapper.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_MOI_wrapper.jl b/test/test_MOI_wrapper.jl index 94a79db4..771b919a 100644 --- a/test/test_MOI_wrapper.jl +++ b/test/test_MOI_wrapper.jl @@ -2632,7 +2632,7 @@ function test_nlp_constraint_delete() f = 1.0 * x MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) g_bad = MOI.ScalarNonlinearFunction(:exp, Any[x]) - c_bad = MOI.add_constraint(model, g, MOI.GreaterThan(20.0)) + c_bad = MOI.add_constraint(model, g_bad, MOI.GreaterThan(20.0)) g = MOI.ScalarNonlinearFunction(:*, Any[x, 2.0, x]) MOI.add_constraint(model, g, MOI.LessThan(3.0)) @test MOI.is_valid(model, c_bad) From 19f582f370aee3cd188b6ea81790670166d2b965 Mon Sep 17 00:00:00 2001 From: odow Date: Wed, 3 Apr 2024 20:34:01 +1300 Subject: [PATCH 19/20] Update --- src/MOI/MOI_wrapper.jl | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/MOI/MOI_wrapper.jl b/src/MOI/MOI_wrapper.jl index b8bc1047..6a58b46d 100644 --- a/src/MOI/MOI_wrapper.jl +++ b/src/MOI/MOI_wrapper.jl @@ -4820,7 +4820,6 @@ function MOI.add_constraint( MOI.EqualTo{Float64}, }, ) - F, S = typeof(f), typeof(s) model.last_constraint_index += 1 row = length(model.affine_constraint_info) + 1 model.affine_constraint_info[model.last_constraint_index] = @@ -4843,17 +4842,15 @@ function MOI.add_constraint( reverse!(value) push!(type, Lib.XPRS_TOK_EOF) push!(value, 0.0) - @checked Lib.XPRSnlpaddformulas( + @checked Lib.XPRSnlpchgformula( model.inner, - 1, # ncoefs, - Cint[row-1], # rowind, - Cint[0, length(type)], # formulastart, + row - 1, parsed, type, value, ) model.has_nlp_constraints = true - return MOI.ConstraintIndex{F,S}(model.last_constraint_index) + return MOI.ConstraintIndex{typeof(f),typeof(s)}(model.last_constraint_index) end function MOI.delete( From 7d78822bc65545dd08c64649ad542bac03b166d1 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Thu, 4 Apr 2024 08:08:16 +1300 Subject: [PATCH 20/20] Update MOI_wrapper.jl --- src/MOI/MOI_wrapper.jl | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/MOI/MOI_wrapper.jl b/src/MOI/MOI_wrapper.jl index 6a58b46d..0f96b3d1 100644 --- a/src/MOI/MOI_wrapper.jl +++ b/src/MOI/MOI_wrapper.jl @@ -4842,13 +4842,7 @@ function MOI.add_constraint( reverse!(value) push!(type, Lib.XPRS_TOK_EOF) push!(value, 0.0) - @checked Lib.XPRSnlpchgformula( - model.inner, - row - 1, - parsed, - type, - value, - ) + @checked Lib.XPRSnlpchgformula(model.inner, row - 1, parsed, type, value) model.has_nlp_constraints = true return MOI.ConstraintIndex{typeof(f),typeof(s)}(model.last_constraint_index) end