# -*- coding: utf-8 -*-
"""
Created on Sun Oct  4 21:38:04 2020

@author: jbobowsk
"""

# This short script will show how to take symbolic derivatives in Python.
# We will require the 'SymPy' module.
import sympy as sym

# First, let's define a symbol x.
x = sym.Symbol('x')

# Then we can define f in terms of x.
f = 2*x**2+3

# To evaluate f at a particular value of x, we can use 'subs()'.
z = f.subs(x, 2)
print('2(2)^2 + 3 =', z)

# To take the derivative of f with respect to x, we use 'diff()'.
dfdx = f.diff(x)
print(dfdx)

# Of course, we can now evaluate the derivative at a particular value of x.
print('df/dx at x = 3 is', dfdx.subs(x, 3))

# We can make our functions more complicated.  Here's a function of two variables.
y = sym.Symbol('y')
g = 2*sym.sin(x)**y**2
print(g)

# We can try to make the typeset of the output a little nicer using 'display()'.
# This assumes that you're use IPython as I am.
from IPython.display import display, Math, Latex
display(g)

# Here's how you can use 'subs()' to subsitute values for multiple variables.
z = g.subs({x:1, y:2})
print(sym.N(z))

# Here's the x-derivative of g...
display(g.diff(x))

# and here's the y-derivative.
display(g.diff(y))

# Alternatively, you can define Python functions.
def fcn(x):
    return 2*x**2 + 3

# One advantage of this method is that it is easy to now evaluate the value 
# of fcn at any x.
z = fcn(2)
print('2(2)^2 + 3 =', z)

# The derivatives of these functions are calculated in the way.
# (I haven't figured out how to put two 'display()' outputs on the same line.)
dgdx = fcn(x).diff(x)
display(Math(r'dg/dx ='), dgdx)

# Note that there is another way to call the 'diff()' function.
dgdx = sym.diff(fcn(x), x)
display(Math(r'dg/dx ='), dgdx)

# Of course, you can have functions of multiple variables.
def gcn(x, y):
    return 2*sym.sin(x)**y**2
print(sym.N(gcn(1, 2)))

# You can evaluate the partial derivatives in the way that you'd expect:
display(gcn(x, y).diff(x))
display(gcn(x, y).diff(y))