Solver BARON with user-defined function in nonlinear constraint

Hello dear Julia-community,

I am having an issue in using BARON for a nonlinear optimization problem. The problem includes nonlinearities in the constraints with a registered user-defined function. I define the function and its gradient.

Here is a snippet of how the constraints are defined, where mvnormcdf is from this library, inputs contains the parameters of the model, T is an index set T = [i for i in 1:24] and r, v are defined model variables:

# Define the mvnormcdf and its gradient
function distribution(j, inputs)
	norm_pdf(k) = exp(-(k^2) / 2) / sqrt(2 * pi)
	f(x...) = mvnormcdf(inputs.Σ[j:j+3, j:j+3], inputs.a[j:j+3]- inputs.μ[j:j+3], vec([x[i] for i in eachindex(x)])- inputs.μ[j:j+3])[1]
	function ∇f(g::AbstractVector{T}, x::T...) where {T}
		μ_j = inputs.μ[j:j+3]
		σ_j = inputs.σ[j:j+3]
		Σ_j = inputs.Σ[j:j+3, j:j+3]
		a_j = inputs.a[j:j+3]
		for i in eachindex(x)
			Σ_new = # new definition based on above defined variables
			μ_new = # new definition based on above defined variables
			g[i] = # new definition based on the previous two lines
		end
		return
	end
	return f, ∇f
end

# Register functions and define nonlinear constraints
for j in eachindex(T)
	register(m, Symbol("mvncdf_$j"), 4, distribution(j, inputs)[1], distribution(j, inputs)[2])
	x = []
	for i in j:j+3
		append!(x, [r[i] + v[i]]) 
	end
	# Use the mvncdf in the nonlinear constraints
	add_nonlinear_constraint(m, :($(Symbol("mvncdf_$j"))($(x...)) >= $(inputs.p)))
end

BARON’s error output is the following:

ERROR: UnrecognizedExpressionException: unrecognized function call expression: mvncdf_1(x[190] + x[136], x[191] + x[137], x[192] + x[138], x[193] + x[139])
Stacktrace:
  [1] to_str(c::Expr)
    @ BARON ~/.julia/packages/BARON/xgWzt/src/util.jl:156
  [2] (::BARON.var"#8#10")(d::Expr)
    @ BARON ./none:0
  [3] iterate(::Base.Generator{Vector{Any}, BARON.var"#8#10"})
    @ Base ./generator.jl:47
  [4] collect(itr::Base.Generator{Vector{Any}, BARON.var"#8#10"})
    @ Base ./array.jl:787
  [5] to_str(c::Expr)
    @ BARON ~/.julia/packages/BARON/xgWzt/src/util.jl:130
  [6] (::BARON.var"#11#20"{BARON.BaronModel})(fp::IOStream)
    @ BARON ~/.julia/packages/BARON/xgWzt/src/util.jl:230
  [7] open(::BARON.var"#11#20"{BARON.BaronModel}, ::String, ::Vararg{String}; kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ Base ./io.jl:384
  [8] open
    @ ./io.jl:381 [inlined]
  [9] write_bar_file
    @ ~/.julia/packages/BARON/xgWzt/src/util.jl:172 [inlined]
 [10] optimize!(model::BARON.Optimizer)
    @ BARON ~/.julia/packages/BARON/xgWzt/src/MOI_wrapper.jl:56
 [11] optimize!
    @ ~/.julia/packages/MathOptInterface/cl3eR/src/Bridges/bridge_optimizer.jl:376 [inlined]
 [12] optimize!
    @ ~/.julia/packages/MathOptInterface/cl3eR/src/MathOptInterface.jl:83 [inlined]
 [13] optimize!(m::MathOptInterface.Utilities.CachingOptimizer{MathOptInterface.Bridges.LazyBridgeOptimizer{BARON.Optimizer}, MathOptInterface.Utilities.UniversalFallback{MathOptInterface.Utilities.Model{Float64}}})
    @ MathOptInterface.Utilities ~/.julia/packages/MathOptInterface/cl3eR/src/Utilities/cachingoptimizer.jl:316
 [14] optimize!(model::Model; ignore_optimize_hook::Bool, _differentiation_backend::MathOptInterface.Nonlinear.SparseReverseMode, kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ JuMP ~/.julia/packages/JuMP/yYfHy/src/optimizer_interface.jl:480
 [15] optimize!(model::Model)
    @ JuMP ~/.julia/packages/JuMP/yYfHy/src/optimizer_interface.jl:450
 [16] top-level scope
    @ ~/project/Code/Main.jl:61

I am not sure why BARON iterates from 190 and 136 for mvncdf_1, the expression for j = 1 should be:
mvncdf_1(r[1] + v[1], r[2] + v[2], r[3] + v[3], r[4] + v[4])

Is it a problem with my definition or with how BARON interprets this user-defined function with a given first derivative?

IPOPT managed to solve the model with this exact definition of the nonlinear constraints without any issues and with fast convergence. It is when I modify my linear objective function (to include the variable r and v in a simple summation term) that IPOPT doesn’t converge. This is why I turned to BARON, but had the error described above.

I appreciate any thoughts and suggestions!

I just tested this with SCIP as well, and I have the same issue.

I also tried solving a much simpler example, also using the user-defined function mvnormcdf with BARON but it didn’t work, although it does with IPOPT and NLopt.

Is it possible that BARON and SCIP can’t deal with the mvnormcdf function? Why does it work with IPOPT and NLopt? Can I rephrase my function definition such that it is correctly interpreted by BARON or SCIP?

The difference to note this that of the different requirements between local and global nonlinear solvers.

Local solvers like Ipopt typically only require function values and gradients/hessians that they can evaluate. Hence, they will typically support any registered function.

However, global solvers like Baron, SCIP, and EAGO only support certain functions and will not support user registered functions. The reason for this is that they will evaluate a series of subproblems that are constructed by leveraging the algebraic structure of the optimization problem. Hence, they need an algebraic structure (i.e., an expression tree) with functions that they support.

For instance, information on the functions that Baron supports is available here: BARON.

Also, the information on the functions supported by SCIP is available here: SCIP Doxygen Documentation: Frequently Asked Questions (FAQ)

2 Likes

Thank you for your answer!
I’ll try to make the solve with IPOPT more efficient, by trying out the more powerful linear solvers.

1 Like

It’s not helpful now, but we’re in the process of changing how we support nonlinear, https://github.com/jump-dev/MathOptInterface.jl/pull/2059.

One outcome is that we will give much better error messages when you use a function that the solver doesn’t support, and we will provide a way to print the list of operators that a solver like BARON does support.

As a rule of thumb, solvers which use expression graphs (e.g., AmplNLWriter.jl, SCIP.jl, and BARON.jl) do not support user-defined functions.

1 Like