Nonlinear Constraints with Splatting

I’m trying to solve an optimization problem w/ (N+1) parameters subject to a set of N nonlinear constraints, each of which relies on the whole vector of the parameters.

A simplified version of my problem looks like this:

@variables m begin
q_b[i=1:N] >= 0.
0 <= gamma <= 0.001
end

m = Model(solver=IpoptSolver(print_level=0, tol=1e-5))

@NLobjective(m, Min, sum((q_b[i])^2 for i in 1:N)))

function my_constraint_i(q_b_index, gamma, q_b_arr…)
q_b = [q_b_arr…]

exp_of_sum = exp(gamma * (dot(q_b, X))) ## X is an N-d vector of data

lhs = q_b[q_b_i] 
rhs = exp_of_sum * y ## y is another N-d vector data

constraint_i = lhs - rhs

return constraint_i

end

JuMP.register(m, :constraint_i, (2+N), constraint_i, autodiff=true)

When I try to use the constraint macros, I get a method error complaining that it can’t parse the inputs:

foc_constraint = @NLexpression(m, [i=1:length(q_a)], foc_constraint_i(index_vector[i], gamma, q_b…) )
@NLconstraint(m, [i=1:length(q_a)], foc_constraint[i] == 0)

Note, however that if I listed the vars, q_b[1], q_b[2], etc. it does work.

I saw the thread on using splatting w/ user defined functions here and here but I’m a novice and haven’t been able to adapt the solutions to my problem. Any help would be tremendously appreciated.

I’m having a little trouble parsing what you’re doing here, and I’m not sure where your error is coming from, but one suggestion is that you can easily consolidate this code somewhat (I’m pretty sure you don’t need splatting at all, though I feel like I’m missing something about what you’re doing…). For example, you can simply specify your objective as

@NLobjective(m, Min, sum(q_b.^2))

and your constraint as

@NLconstraint(m,
             exp(γ*q_b⋅X)*y .== q_b
             )

(I think I’m reading that right, is that the correct constraint?). The unicode is me being fanciful, but I wanted to advertise how beautifully these types of problems can be written in Julia. You might try the above using the @constraint macro and see if this fixes your problem.

(By the way, if I am getting this wrong it might be helpful if you write your problem down in latex, which you can do in discourse using $.)

Thanks for the response! The trouble is that I need to have N constraints (one for each element of q_b). The reason I’m using splatting is so that I write the constraint function as a function of scalars that I can register (i.e. JuMP.register(m, :constraint_i, (2+N), constraint_i, autodiff=true)).

In my understanding, registered functions can only be scalar functions, but I need the entirety of the q_b vector for each constraint, so I need to use splatting to input each element of the vector as a separate variable. :confused:

What I have written above is N constraints (that’s the glory of JuMP and Julia, see here).

Let me continue to give the caveats that 1. I’m still not sure I’m understanding your problem correctly and 2. there may be some subtlety or trick for @NLconstraint beyond what you’d need to do for @constraint that I’m not aware of at this very moment.

Ah right, sorry - the constraint in my problem is a bit more complicated than that but I can try to vectorize it…

Assuming I do need to use splatting (say, because I need to do several transformations of q_b inside the function), my error seems to be from JuMP not parsing q_b... as q_b[1], q_b[2], .... It seems like a similar problem has been dealt with in the threads I linked to wrt nonlinear functions in the objective, but not in the constraints…

It seems highly unlikely to me that you would need to resort to splatting, but it’s hard to say for sure. I could take another stab at what I think your problem is, but if we have to go that route it’s probably just best for you to write it out algebraically. My best advice at the moment is to formulate your problem in such a way that it doesn’t require splatting (which is inefficient anyway). It must be possible to write it succinctly and put into one or more @NLconstraints like I’ve shown above.

Please:

  1. Quote your code using triple backticks as described in PSA: how to quote code with backticks to make it more readable, and
  2. Provide enough data in your code to run it by just copying and pasting it to help people to help you.

Adapting the solution from the link you posted, in particular, the comment below, you can try:

foc_constraint = @eval @NLexpression(m, [i=1:length(q_a)], foc_constraint_i(index_vector[i], gamma, $(q_b...)))

It seems that the @NLconstraint macro cannot parse the splatting because it works when you manually splat, so you can intercept the expression by the @eval macro to “pseudo-manually” splat q_b for you before passing the expression to the @NLconstraint macro to do its magic on the splatted version. I didn’t test this solution because of point 2 above, so I may very well be wrong.

Also consider doing away with this pattern completely if you can as @ExpandingMan mentioned because it simply looks ugly in my opinion and Julia gives you plenty of tools to make your code look elegant and often even be simultaneously more efficient.

1 Like

Thank you very much, @ExpandingMan and @mohamed82008. That was really helpful. I think the @eval trick solves my current problem but I’m going to see if I can refactor the way I’m approaching it.