# -*- coding: utf-8 -*-
"""
Created on Fri Sep 25 14:09:12 2020

@author: jbobowsk
"""
# This tutorial discusses how to define functions within a MATLAB script.
# Once defined, the functions can be evaluated, differentiated, integrated,
# plotted, ...  

import math
import numpy as np
import matplotlib.pyplot as plt

# The notation used to define a function is the following:
def f(x):
    return np.sin(x)

# You can all your function anything, it doesn't have to be f.  Also, the 
# variables are defined within the function and do not need to be declared
# outside of the function.
    
# Once defined, it's easy to evaluate the function at any value of x.
print(f(0))
print(f(math.pi/4))
print(f(math.pi/2))

# We can also input a vector of x-values and evaluate the function at each
# value of x contained in the array.
xx = np.arange(0,2*math.pi,0.1)
y = f(xx)
print(y)

# These data can then easily be plotted.
plt.plot(xx, f(xx), 'bo')
plt.xlabel('x')
plt.ylabel('f(x) = sin x')

# If you wanted to do symbolic math using a function, you could use 'sym.sin()'
# instead of 'np.sin()'.  This requires the module 'sympy'.
import sympy as sym
def f1(x):
    return sym.sin(x)

# Now we can take the x-derivative.  First, we now do need to specify 'x' as
# a symbol.
x = sym.Symbol('x')  
z = sym.diff(f1(x), x)
print(z)

# Of course, we can integrate too. 
z = sym.integrate(f1(x), x)
print(z)


# Here is another function:
def g(x):
    return x**2

# We can nest the two functions:
print(f(g(1.41)))
# which is equivalent to sin(1.41^2). 

# We should be able to get Python to do chain rule for us:
z = sym.diff(g(f1(x)), x)
print(z)

# The integral:
z = sym.integrate(g(f1(x)), x)
print(z)

# We should be able to go back to sin^2x:
print(sym.simplify(sym.diff(z, x)))

# Everything above was for a function of a single variable.  You can, of
# course, have a function of any number of variables.  Here's a simple
# function of two variables:
def k(x, y):
    return x**2 + y**2
print(k(1, 2))

# Some more symbolic derivatives
z = sym.diff(k(x, f1(x)), x)
print(z)