FeaturesΒΆ
PyADi performs first order automatic differentiation (AD) via source
transformation for any regular Python function f, regular meaning
defined with def
.
Unless a rule is defined for f in forwardad
, or
installed with setrule()
, the abstract syntax tree (AST) is
obtained with compile()
, source differentiation is performed
and the resulting function is compiled and loaded, all on the fly.
The source differentiation proceeds as usual in forward mode AD, very
much like in ADiMat for example. For
every parameter x
there will be a derivative parameter d_x
,
which holds the derivative of x
, and both are any object of the
same type and inner structure. They can also be numpy
arrays
of the same size. This will then be propagated in the code line by
line, for example:
def gplus(x, y):
s = x + y
return s
Is differentiated to:
def d_gplus(d_x, x, d_y, y):
d_s = (d_x + d_y)
s = (x + y)
return (d_s, s)
The nice thing about source code transformation in Python is that the
differentiated code is just ordinary Python code and it basically does
the same things as the original code did. So when a piece of code or a
function is type agnostic, the differentiated code will be too. In
this example in gplus
x
and y
can be floats, lists, or
arrays, and in any case, when gplus
runs, so will d_gplus
.
The multiplication operators *
and @
are differentiated of
course with the usual arithmetic rules:
def fmult(x):
r = sin(x)
s = r * x
t = s * 2
z = 2 * t
return z
Is differentiated to:
def d_fmult(d_x, x):
(d_r, r) = D(sin)((d_x, x))
d_s = ((d_r * x) + (r * d_x))
s = (r * x)
d_t = (d_s * 2)
t = (s * 2)
d_z = (2 * d_t)
z = (2 * t)
return (d_z, z)
The source differentiation decorates all function calls with the
operators D()
, and Dc()
, which perform the above
process, and cache the result. Thereby the source code
differentiation, or resolution to a rule, is performed on the fly
whenever a function is first touched and the process proceeds
recursively:
def gbabylonian(x, y=1):
if abs(y**2 - x) < 1e-7:
return y
else:
r = gbabylonian(x, (y + x / y) / 2)
return r
Is differentiated to:
def d_gbabylonian(d_x, x, d_y=0, y=1):
if (abs(((y ** 2) - x)) < 1e-07):
return (d_y, y)
else:
d_t_c0 = ((d_x / y) - ((x * d_y) / (y ** 2)))
t_c0 = (x / y)
d_t_c1 = (d_y + d_t_c0)
t_c1 = (y + t_c0)
(d_r, r) = Dc((d_gbabylonian, gbabylonian))(
(d_x, x), ((d_t_c1 / 2), (t_c1 / 2)))
return (d_r, r)
In the case of a recursive function, as in this case gbabylonian
,
the recursive call can even pass the differentiated function directly
to the Dc()
operator, which, after one small further step,
can proceed to call it directly.
All the actual differentiation, except for the arithmetic operators,
happens in the module forwardad
. This module has a
mechanism to map functions being differentiated to functions in that
module which compute the required derivatives. This is required for
any builtin function, but can also be used to override the source
transformation for any function. Users can use setrule()
to
add rules dynamically.
PyADi provides a generic mechanism for defining how functions are
differentiated. This allows users to add own rules, augmenting the
functionality or possibly redefining the entire process currently
implemented in forwardad
. This could be used to propagate
other values than derivatives within any Python program. An example is
the dummyad
which tries to act as a replacement for
forwardad
without actually computing any derivatives.
PyADi is well-suited both for practical and educational purposes. The
performance is quite good in our experience, it is relatively easy to
set up and use, it is applicable to a large portion of the Python
language and the differentiated code it produces can be displayed and
inspected. The set of Python builtin functions covered by rules in
forwardad
is quite small as of yet, since we are in an
early stage of decelopment.