convert active argument in case of a preparation mismatch
I understand that the argument type has to match the prepared backend. However, instead of erroring, in some situations it would make sense to just convert to the right type. Eg in the example below, the last line could work, in accordance with the robustness principle (with the understanding that it allocates, is suboptimal, etc).
julia> import ForwardDiff
julia> import DifferentiationInterface as DI
julia> backend = DI.AutoForwardDiff()
AutoForwardDiff()
julia> test(x) = x .+ 1
test (generic function with 2 methods)
julia> x = ones(3);
julia> prep = prepare_jacobian(test, backend, x);
julia> DI.value_and_jacobian(test, prep, backend, x)
([2.0, 2.0, 2.0], [1.0 0.0 0.0; 0.0 1.0 0.0; 0.0 0.0 1.0])
julia> DI.value_and_jacobian(test, prep, backend, Float32.(x))
ERROR: PreparationMismatchError (inconsistent types between preparation and execution):
- f: ✅
- backend: ✅
- x: ❌
- prep: Vector{Float64}
- exec: Vector{Float32}
- contexts: ✅
If you are confident that this check is superfluous, you can disable it by running preparation with the keyword argument `strict=Val(false)` inside DifferentiationInterface.
I would allow this always, but I understand that some users would just want to catch this performance issue as an error. So maybe an option, or a wrapper-like API
DI.value_and_jacobian(test, prep, backend, AutoConvert(Float32.(x)))
would make sense.
I'm conflicted about this. I see the point but I also see the people who will shoot themselves in the foot.
Note that it would probably happen through a third possible value for the strict kwarg during preparation: true / false / ... :convert maybe?
@adrhill thoughts?
also see the people who will shoot themselves in the foot
Can you please explain how? At worst, you get a bit of allocations. For arguments of nontrivial length, the cost of these is dominated by AD.
Because this "strict" preparation mode is a good way to spot code smells. If you're preparing on one type but executing on another type, more often that not it reveals a bug in your program rather than something DI should handle more gracefully
One of DI's early design decisions was to make the interface as explicit as possible.
A convert flag seems reasonable, but then again it just takes Float32.(x) for the user to convert it themselves. In comparison, strict=Val(:convert) seems less user friendly.
If both Vector{Float64} and Vector{Float32} are needed, you could prepare both a prep64 and a prep32.
One thing we could easily do without added complexity is expose a function to query the types with which a prep object was made. Would that be helpful in your case?