JuliaDiff / ForwardDiff.jl

Forward Mode Automatic Differentiation for Julia
Other
887 stars 141 forks source link

Forced periodicity (modulo) breaks ForwardDiff #524

Open TS-CUBED opened 3 years ago

TS-CUBED commented 3 years ago

It seems that modulo periodicity that seems to break ForwardDiff:

If I give it a very simple function that should be differentiable, but use a modulo for time, then this happens:

#+begin_src julia
function f(t)
    sin(pi * 2 * (t % 1))^2
end
#+end_src

#+RESULTS:
: f (generic function with 1 method)

#+begin_src julia
ForwardDiff.derivative(f, 1)
#+end_src

#+RESULTS:
: NaN

#+begin_src julia
derivative(f, 1)
#+end_src

#+RESULTS:
: -5.736299254432828e-15

So the result from the CD is close to zero, as expected. But the ForwardDiff gives NaN. If I take the modulo out, then AutoDiff works.

So while I took care that my functions are continuous, smooth and differentiable at the period boundaries, AutoDiff will not work for them. It does work for cosine series, which are naturally periodic, but not with piecewise continuous functions with modulo periodicity.

mcabbott commented 3 years ago

Your complaint is this:

julia> using ForwardDiff: Dual

julia> rem(Dual(0.001, 2), 3)
Dual{Nothing}(0.001,2.0)

julia> rem(Dual(0.0, 2), 3)
Dual{Nothing}(0.0,NaN)

julia> rem(Dual(3.0, 2), 3)
Dual{Nothing}(0.0,NaN)

which is defined here:

https://github.com/JuliaDiff/DiffRules.jl/blob/42b11d0d07dbb9b1f552398bdf5a79e4f403edcf/src/rules.jl#L99

rem is actually continuous at zero, unlike mod, so perhaps the first NaN above is a bug? The others seem like a choice, and you could argue they should be slope 1 everywhere. Not sure how this was decided.

TS-CUBED commented 3 years ago

That did help me in finding a solution to the problem using floor division to get the remainder - not sure why this works if rem and mod don't, but hey:

#+begin_src julia
function f(t)
    times = t ÷ 1
    t = t - times
    sin(pi * 2 * t)^2 * (t < 0.5)
end
#+end_src

#+RESULTS:
: f (generic function with 1 method)

#+begin_src julia
ForwardDiff.derivative(f, 1)
#+end_src

#+RESULTS:
: 0.0

#+begin_src julia
derivative(f, 1)
#+end_src

#+RESULTS:
: 0.00011952987976706194