Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip: implement macro for users to define their own derivatives #165

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

KristofferC
Copy link
Collaborator

@KristofferC KristofferC commented Dec 3, 2016

This is a start towards an official API for fixing #89. It provides macros for derivatives, gradients and jacobians such that user defined derivatives are easily injected:

julia> f(x) = x^2 + x;

julia> Df(x) = (println("I got called"); 2x + 1);

julia> ForwardDiff.@implement_derivative f Df
f (generic function with 2 methods)

julia> ForwardDiff.derivative(f, 2.0)
I got called
5.0

For gradients and jacobians there are two "config" objects that can be used, which cache intermediate arrays needed.

I don't think we can support user defined Hessians at the moment because of the composition of gradient and jacobian that is currently being used.

TODOs:

  • The documentation is a bit so-so. Just wanted to get something up quickly.
  • Something weird happens when I use this in a function:
julia> function test()
           h(x) = 3x
           dh(x) = 3
           ForwardDiff.@implement_derivative h dh
           ForwardDiff.derivative(h, 2.0)
       end
test (generic function with 1 method)

julia> test()
------ UndefVarError ------------------- Stacktrace (most recent call last)

 [1] — test() at REPL[2]:2

UndefVarError: dh not defined

I opened a thread on the forum: https://discourse.julialang.org/t/question-about-scoping-inside-a-function/709

Conclusion Lowering bug in julia 0.5. Need to define the derivative before the function.

@KristofferC
Copy link
Collaborator Author

@KristofferC
Copy link
Collaborator Author

I am thinking of using something like this in https://github.com/KristofferC/ContMechTensors.jl. I could basically define a bunch of known derivatives (like d/dA det(A) = det(A)inv(A)') and perhaps that would get better performance since I could reuse the det computations in the value and the derivative (a bit like the optimizations that are already being done for scalars but on a higher level). I could either form the matrix of partials on the stack or write a custom matrix multiply function that skips the value part. Will have to benchmark to see what is best.

@KristofferC KristofferC force-pushed the kc/user_defined_derivatives branch 2 times, most recently from 95a407b to 07cd718 Compare December 5, 2016 14:47
@KristofferC
Copy link
Collaborator Author

KristofferC commented Dec 5, 2016

Benchmarks at https://gist.github.com/KristofferC/930dc1de9d64a6930d3f3c81da72cae4.

The user defined functions are non allocating but consistently slower. The reason for this (I believe) is that the function turns into Core.Box when the macro is used. These Boxes are pretty annoying, it feels I get them everytime I try to exploit a closure.

julia> function bench_derivative()
          dh(x) = 3 * one(x)
          h(x) = 3x
          h2(x) = 3x
          ForwardDiff.@implement_derivative h dh
          d_user = @benchmark ForwardDiff.derivative($h, 2.0)
          d_normal = @benchmark ForwardDiff.derivative($h2, 2.0)
          return user, normal
       end

julia> @code_warntype bench_derivative()
Variables:
  #self#::#bench_derivative
  dh::#dh#74
  h2::#h2#76
  h::Core.Box
  d_user::Any
  348::Any
  d_normal::Any
  349::Any

Edit: Boxes are not the only thing, they are a bit slower in global scope as well. However, the extraction of the jacobian and calling to blas might explain that. The scalar derivatives are the same speed in global scope.

@jrevels
Copy link
Member

jrevels commented Feb 11, 2017

@KristofferC It seems the only item on your checklist is the docs, but is the main blocker here still the closure performance bug? If so, that can usually be avoided by explicitly using a callable type instead (I know it's annoying, though).

I'm curious because I'm excited for this feature, let me know if there's any way for me to help 😃

@KristofferC
Copy link
Collaborator Author

The most helpful thing would be to read the docs I have written, comment on that and try the macro out for yourself to see how you think the workflow is.

I actually don't think the closure thing is a big problem. The reason these manual implementations are slower sometimes is that the matrix multiplication (in case of jacobians) that is being done to propagate the partials could very well be more expensive than to just evaluate the function with dual numbers.

@KristofferC KristofferC force-pushed the kc/user_defined_derivatives branch from 07cd718 to d27dd78 Compare May 4, 2017 09:50
@KristofferC
Copy link
Collaborator Author

Rebased

@BCRARL
Copy link

BCRARL commented Apr 25, 2018

Is this progressing? My version of ForwardDiff does not implement these macros and Icoudl really use it :-)

@gszep
Copy link

gszep commented Oct 13, 2021

bump 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants