Programmaticly build expression for JuMP NLconstraint

I’m trying to define a nonlinear constraint programmatically via expressions. Specifically, I have a function f that takes in arguments x (a JuMP Variable), i (the index of the variable), and p (some parameters). The real function f is somewhat involved, so I’d like to be able to pass it indices/parameters and return an expression that I can use in JuMP constraints. So far, the below attempts haven’t worked for me. What I’m hung up on is splicing the JuMP Variable into the expression.

using JuMP

##
## parameter and function
##
p     = Dict()
p[:a] = 10.0

function f(x, i, p)
    return :( $(x[i])^2 + $(p[:a]) )
end

The final constraint expression I’m trying to build must look something like

:( $(x[i])^2 + 10.0 == 0)

I first tried using the @NLconstraint macro

##
## attempt
##

M = Model()
@variable(M, x[1:3])

for i in 1:3
    @NLconstraint(M, :(0 == $(Expr(:call, f, x, $(i), p))))
end

but got the error

ERROR: LoadError: in @NLconstraint ($(Expr(:quote, :(0 == $(Expr(:$, :(Expr(:call, f, x, $(Expr(:$, :i)), p)))))))): constraints must be in one of the following forms:
       expr1 <= expr2
       expr1 >= expr2
       expr1 == expr2
Stacktrace:
 [1] error(::String) at ./error.jl:33
 [2] @NLconstraint(::LineNumberNode, ::Module, ::Any, ::Any, ::Vararg{Any,N} where N) at /Users/jakeroth/.julia/packages/JuMP/PbnIJ/src/macros.jl:1375
in expression starting at REPL[18]:2

The issue with how I’ve written the constraint is that the JuMP Variable x isn’t spliced in, that is, I need the final expression to contain :( $(x[i]) ... ) but I only have :( x[i] ... ).

It seems that my function f is to blame – is there a way to rewrite it to get $ to stop interpolating in f? Alternatively, is there a better way to define programmatic constraints still using expressions?

See Procedural Nonlinear Constaint Generation - String to NLconstraint There have been a few similar questions asked over the previous few days. Pull requests to improve the documentation appreciated.

Why not declare f as a user-defined function? https://www.juliaopt.org/JuMP.jl/stable/nlp/#User-defined-Functions-1

1 Like

@odow:

Thanks for the link, I didn’t see that before!

In my actual case, f is a vector valued function and x is 2D, and I want something like f(x[:,i]) + x[:,i] == 0. Registering f will be fine, but it I think it will mean that I have to define many subfunctions f_i that are scalar functions. I think this will become cumbersome since I’ll need many separate @NLconstraints for each i since I can’t create single flexible expression that both include f_i and the appropriate indices of x. Also, I just tested a case registering the different f_i and using autodiff=true and it was about 10x slower than when I coded the expressions directly in JuMP, so I was hoping to use JuMP’s AD.

Even if I’ve registered f_is, the issue I seem to be encountering is building an expression which mixes calls to f_i and the actual variables, e.g., like @NLconstraint(M, 0 == :( x[k,i] + $(Expr(:call, f1, x[:,i]...,p)) ))