Computing common components in KNITRO callbacks

I am using KNITRO.jl to solve a particular optimization problem. In my optimization problem, there is a common calculation done across the constraint, jacobian for the constraint, and the hessian for the constraint.

Currently, I have this calculation done multiple times across each individual callback function. I wish to my code more efficient, and move the common calculation to its own function (or something similar), so that it is only being done for each updated x iterate. Does anyone have any tips for how to do this? Some example code is below:
Essentially, I wish to have f(x) be precomputed to avoid this line, since in my case, f is quite slow. One issue I have been running into is that I am not able to mutate userParams after it is created, so I do not think I will be able to pass it there.

function callbackEvalC(kc, cb, evalRequest, evalResult, userParams)
   

    x = evalRequest.x 
    y = f(x)
    
    evalResult.c[1] = y
end 

function callbackEvalCGrad(kc, cb, evalRequest, evalResult, userParams)
   

    x = evalRequest.x 
    y = f(x)
    
    evalResult.jac[1] = 2 * y
end 

Thanks!

What I do for things like this is create a struct that carries around the extra things I want to store, and then give that structs the necessary methods so that I can pass it in in place of the straight callback functions as you write them here.

Here is my own KNITRO interface. I don’t cache things like function evaluations, although I’m sure it wouldn’t hurt to do. This code probably isn’t worth copying, but the point is that you could probably make an object like a

mutable struct MyModel
  xeval::Vector{Float64}
  feval::Float64
  [...]
end

and give it methods (for extra wrapper structs) like

struct Objective{M}
  m::M
end

function (obj::Objective{M})(kc, cb, evalRequest, evalResult, userParams) where{M<:MyModel}
  # now check if you already computed f(x) for evalRequest.x by doing something like this:
  if isapprox(evalRequest.x, obj.m.x)
    # skip the computation and use your stored result
  else
    # compute the new thing (and update the values in your obj.m!)
  end
end

This is probably just a clunkier version of what JuMP already does, so maybe @odow will chime in and say “don’t do that” and offer a better solution. But creating structs and giving them the necessary method is a general concept that will probably be useful for solving problems like these.

2 Likes

You could use the caching trick that is shown here: Nested optimization problems · JuMP

(Note also that i just released KNITRO.jl v0.14, which brings a number of breaking changes to the C API. The new API now much more closely matches the official KNITRO API.)

Is there any reason to use the low-level API instead of JuMP? Manually computing derivatives is a pretty common source of hard to diagnose bugs.

1 Like

Ha, we replied at the same time. Its the same caching trick so I approve.

1 Like

Hey, alright! Best part of my day. Thanks for the note on the new release, also—I’ll have to go un-break my little wrapper.

1 Like

The KNITRO low level API is now a lot simpler, in the sense that there is fewer bits of Julia-specific magic and helper methods. The downside is that some calls are more verbose.

The changes to the examples in Rewrite the C wrapper by odow · Pull Request #268 · jump-dev/KNITRO.jl · GitHub give a good sense of what to update.

(I also didnt notice your wrapper because it is not registered, sorry for the churn.)

1 Like

No apologies necessary! This looks like a great set of improvements, and it is definitely only my responsibility to keep up with the low level API stuff that serious users like you are developing.

1 Like

Thanks for all the help! I think creating the mutable struct will work for my project.

1 Like