from math import sin, cos

def func(x):
    y = x
    for i in range(30):
        y = sin(x + y)

    return y

"""
Explanation: Symbolic Differentiation vs Automatic Differentiation
Consider the function below that, at least computationally, is very simple.
End of explanation
"""


from sympy import diff, Symbol, sin
from __future__ import print_function

x = Symbol('x')
dexp = diff(func(x), x)
print(dexp)

"""
Explanation: We can compute a derivative symbolically, but it is of course horrendous (see below).  Think of how much worse it would be if we chose a function with products, more dimensions, or iterated more than 20 times.
End of explanation
"""


xpt = 0.1

dfdx = dexp.subs(x, xpt)

print('dfdx =', dfdx)

"""
Explanation: We can now evaluate the expression.
End of explanation
"""


from algopy import UTPM, sin

x_algopy = UTPM.init_jacobian(xpt)
y_algopy = func(x_algopy)
dfdx = UTPM.extract_jacobian(y_algopy)
    
print('dfdx =', dfdx)

"""
Explanation: Let's compare with automatic differentiation using operator overloading:
End of explanation
"""


def funcad(x):
    xd = 1.0
    yd = xd
    y = x
    for i in range(30):
        yd = (xd + yd)*cos(x + y)
        y = sin(x + y)
    return yd

dfdx = funcad(xpt)

print('dfdx =', dfdx)

"""
Explanation: Let's also compare to AD using a source code transformation method (I used Tapenade in Fortran)
End of explanation
"""


