Hello,
I’ve been following this tutorial from JuMP on memorization for NLP via JuMP with user-defined, vector-output functions
One issue I am having currently is that once I have a memorized function registered to the model
with
register(model, :foo_1, 2, memoized_foo[1]; autodiff = true)
I would like to add the objective by splatting the inputs, like
@NLobjective(model, Max, foo_1(x...)) # doesn't work
instead of
@NLobjective(model, Max, foo_1(x[1], x[2])) # works
The motivation for this is that my NLP would typically have hundreds of variables, so it would be very impractical to write x[1], x[2], ...
for all the variables…
Below is a minimum example that I am trying to work with. I am on Julia 1.8.5 with JuMP v1.9.0.
I’d appreciate any suggestions, thank you in advance!
using JuMP, Ipopt
function_calls = 0
function foo(z...)
x = z[1]
y = z[2]
global function_calls += 1
common_term = x^2 + y^2
term_1 = sqrt(1 + common_term)
term_2 = common_term
return term_1, term_2
end
"""
memoize(foo::Function, n_outputs::Int)
Take a function `foo` and return a vector of length `n_outputs`, where element
`i` is a function that returns the equivalent of `foo(x...)[i]`.
To avoid duplication of work, cache the most-recent evaluations of `foo`.
Because `foo_i` is auto-differentiated with ForwardDiff, our cache needs to
work when `x` is a `Float64` and a `ForwardDiff.Dual`.
"""
function memoize(foo::Function, n_outputs::Int)
last_x, last_f = nothing, nothing
last_dx, last_dfdx = nothing, nothing
function foo_i(i, x::T...) where {T<:Real}
if T == Float64
if x != last_x
last_x, last_f = x, foo(x...)
end
return last_f[i]::T
else
if x != last_dx
last_dx, last_dfdx = x, foo(x...)
end
return last_dfdx[i]::T
end
end
return [(x...) -> foo_i(i, x...) for i in 1:n_outputs]
end
# memoize function
memoized_foo = memoize(foo, 2)
# build model
model = Model(Ipopt.Optimizer)
lx = [-1,-1]
ux = [ 1, 1]
x0 = [0.2, 0.3]
@variable(model, lx[i] <= x[i=keys(lx)] <= ux[i], start=x0[I])
# register function
register(model, :foo_1, 2, memoized_foo[1]; autodiff = true)
# add registered function to model
#@NLobjective(model, Max, foo_1(x[1], x[2])) # works but tedious to write
@NLobjective(model, Max, foo_1(x...)) # doesn't work...