JuliaDiff / JuliaDiff/ForwardDiff.jl

checktag's permissive fallback silently disables tag checking (e.g. for derivative(f!, y, x, cfg))

Open
#833 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Julia
Stars
1k
Forks
160
PR merge metrics
No merged PRs in 30d

Description

A DerivativeConfig built for one function is silently accepted by another, so derivative(f!, y, x, cfg) gets no perturbation confusion protection at all:

julia> a!(y, x) = (y .= sin(x)); b!(y, x) = (y .= cos(x));

julia> y = Vector{Float64}(undef, 2);

julia> cfg = ForwardDiff.DerivativeConfig(a!, y, 0.0);

julia> ForwardDiff.derivative(b!, y, 0.0, cfg)   # cfg belongs to a!, no complaint
2-element Vector{Float64}:
 -0.0
 -0.0

gradient does raise InvalidTagException in the analogous situation. This reproduces with a real y on master, so it is unrelated to complex support.

The immediate cause is that every non-fallback checktag method requires x::AbstractArray:

checktag(::Type{Tag{FT,VT}}, f::F, x::AbstractArray{V}) where {FT,VT,F,V} = throw(InvalidTagException{...}())
checktag(::Type{Tag{F,V}}, f::F, x::AbstractArray{V}) where {F,V} = true

while derivative/derivative! call checktag(T, f!, x) with a scalar x. Dispatch lands on the catch-all checktag(z, f, x) = true, so the check argument of derivative(f!, y, x, cfg, Val{true}()) has no effect.

Adding scalar counterparts of those two methods fixes this particular case, but the more interesting question is whether the permissive checktag(z, f, x) = true default should stay. As it stands, any signature that does not match a specific method silently opts out of checking, which is how this gap went unnoticed -- the failure mode is "no error", not "no method". Defaulting to rejection and explicitly accepting only validated tags would make such gaps loud instead of silent.

Two things to note for anyone attempting that:

  • The return value is currently discarded -- every call site is CHK && checktag(...), and checktag works purely by throwing. So flipping the default to false on its own changes nothing; the call sites would have to throw on a false result.
  • Nothing needs to be accepted explicitly, since a nothing tag is documented as usable with any target function. Custom tag types currently pass via the same fallback ("custom tag: you're on your own"), so rejecting by default would be breaking for them unless they opt in by defining checktag for their tag type -- arguably the point, but worth a deprecation path.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the checktag methods and the derivative/derivative! call sites, then compare their behavior with gradient's InvalidTagException path. Done means a DerivativeConfig for the wrong function is rejected for scalar derivative calls, while documented nothing and custom-tag behavior are accounted for.

Written by the indexing model from the issue text.

Assessment

Domain
tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
50/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.