I’d like to report a mistake that some new users like me might make when using PreallocationTools. One should not use DiffCache
to store non-variables such as parameters. It makes perfect sense in a hindsight, because the code will not yield correct residuals when Dual variables are not multiplied by the proper parameter constants.
I’m posting here just for others’ reference. I prefer it to fail explicitly, if there is a way.
using PreallocationTools
using ForwardDiff
using ForwardDiff: Dual
using FiniteDiff
import PreallocationTools: get_tmp
get_tmp(dc::AbstractArray, u) = dc;
get_tmp(dc::Float64, u) = dc;
struct InputCache{T}
x::T
end
struct OutputCache{T}
v::T
end
function f!(out, xy, p, input_cache, output_cache)
# Step 1: copy variables to an input cache
input_cache = get_tmp(input_cache.x, xy)
input_cache .= xy
# Step 2: calculate residuals and save to output cache
p = get_tmp(p, xy) # `p` is for parameters. Probably `get_tmp` should never be applied to parameters?
output_cache = get_tmp(output_cache.v, xy)
output_cache[1] = 2 * input_cache[1] * p[1]
output_cache[2] = 2 * input_cache[1] * input_cache[2]
# Step 3: copy from output cache to the final `out` vector
out .= output_cache
# The three steps above demonstrate the calculation phases in a large
# program, where they can be separated into three functions.
nothing
end
output_cache = OutputCache(dualcache(zeros(2)));
input_cache = InputCache(dualcache(zeros(2)));
xy = [1., 2];
p = dualcache([1.], 2); # <- if one mistakenly made `p` a DiffCache
wrap_f! = (out, xy) -> f!(out, xy, p, input_cache, output_cache)
# FiniteDiff works ok
jout = zeros(2, 2);
FiniteDiff.finite_difference_jacobian!(jout, wrap_f!, xy)
@show jout
# incorrect if `p` is DiffCache
out = [0, 0]
ForwardDiff.jacobian(wrap_f!, out, xy)
# correct if `p` is a Vector
p = [1.] # <- This fixes it, but are there ways to make it failsafe, or at least issue some warning?
wrap_f! = (out, xy) -> f!(out, xy, p, input_cache, output_cache)
ForwardDiff.jacobian(wrap_f!, out, xy)