import sys
import csv
from math import pi, erf, erfc
import numpy as np
from numpy import sqrt, exp, arctan
from matplotlib import pyplot as plt


"""
  Errors of numerial differentiation for different approxmations and h
"""


#===================
# parameters
#===================
xmin   = 0.0
xmax   = 1.0
nhscan = 18
ftype  = None


def usage():
    print("")
    print("Usage: python {} xmin xmax nhscan func_type".format(argv[0]))
    print("        func_type: [lorentz|gauss|exp]")
    print("        nhscan: Number of division for the integration range is 2^nhscan (default: 18)")

def terminate():
    usage()
    print("")
    exit()


argv = sys.argv
n = len(argv)
if n >= 2:
    xmin = float(argv[1])
if n >= 3:
    xmax = float(argv[2])
if n >= 4:
    nhscan = int(argv[3])
if n >= 5:
    ftype = argv[4]



# define function to be differentiated
def func_Lorentz(x):
    return 1.0 / (1 + x * x)

def func_exp(x):
    return exp(x)

def func_Gauss(x):
    return exp(-x*x)

# define analytical deviation of f(x)
def integ_exact_Lorentz(x):
    return arctan(x)

def integ_exact_exp(x):
    return exp(x)

def integ_exact_Gauss(x):
    return sqrt(pi) / 2.0 * erf(x)

if ftype == 'exp':
    func        = func_exp
    integ_exact = integ_exact_exp
elif ftype == 'gauss':
    func        = func_Gauss
    integ_exact = integ_exact_Gauss
else:
    func        = func_Lorentz
    integ_exact = integ_exact_Lorentz


# numerical integration
def integ_rieman(func, x, h):
    return func(x) * h

def integ_trapezoid(func, x, h):
    return (func(x) + func(x+h)) / 2.0 * h

def integ_simpson(func, x, h):
    return (func(x) + 4.0 * func(x+h) + func(x+2.0*h)) / 6.0 * 2.0 * h

def integ_simpson38(func, x, h):
    return (3.0*func(x) + 9.0*func(x+h) + 9.0*func(x+2.0*h) + 3.0*func(x+3.0*h)) / 24.0 * 3.0 * h

def integ_bode(func, x, h):
    return (14.0*func(x) + 64.0*func(x+h) + 24.0*func(x+2.0*h) 
          + 64.0*func(x+3.0*h) + 14.0*func(x+4.0*h)) / 180.0 * 4.0 * h

#===================
# main routine
#===================
def main():
    print("")
    print("Numerical integration using different approximations")
    print("Function: ", ftype)
    print("integration range: {} - {}".format(xmin, xmax))
    S_ex = integ_exact(xmax) - integ_exact(xmin)    
    print(" Exact: {}".format(S_ex))


    xrieman = []
    yrieman = []
    erieman = []
    print("")
    print("Rieman")
    print("{:8}\t{:8}\t{:14}\t{:8}".format('nh', 'h', 'S', 'error'))
    for ih in range(nhscan):
        nh = int(1 * 2**ih + 0.00001)
        h = (xmax - xmin) / nh
        nx = int((xmax - xmin) / h + 1.00001)
        S = 0.0
        for i in range(nx-1):
            S += integ_rieman(func, xmin + i * h, h)

        xrieman.append(h)
        yrieman.append(S)
        error = abs(S - S_ex)
        erieman.append(error)
        print("{:8d}\t{:8.4g}\t{:14.8g}\t{:8.4g}".format(nh, h, S, error))

    xtrapezoid = []
    ytrapezoid = []
    etrapezoid = []
    print("")
    print("Trapezoid")
    print("{:8}\t{:8}\t{:14}\t{:8}".format('nh', 'h', 'S', 'error'))
    for ih in range(nhscan):
        nh = int(1 * 2**ih + 0.00001)
        h = (xmax - xmin) / nh
        nx = int((xmax - xmin) / h + 1.00001)
        S = 0.0
        for i in range(nx-1):
            S += integ_trapezoid(func, xmin + i * h, h)

        xtrapezoid.append(h)
        ytrapezoid.append(S)
        error = abs(S - S_ex)
        etrapezoid.append(error)
        print("{:8d}\t{:8.4g}\t{:14.8g}\t{:8.4g}".format(nh, h, S, error))

    xsimpson = []
    ysimpson = []
    esimpson = []
    print("")
    print("Simpson")
    print("{:8}\t{:8}\t{:14}\t{:8}".format('nh', 'h', 'S', 'error'))
    for ih in range(nhscan - 1):
        nh = int(2 * 2**ih + 0.00001)
        h = (xmax - xmin) / nh
        S = 0.0
        for i in range(0, nh, 2):
            S += integ_simpson(func, xmin + i * h, h)

        xsimpson.append(h)
        ysimpson.append(S)
        error = abs(S - S_ex)
        esimpson.append(error)
        print("{:8d}\t{:8.4g}\t{:14.8g}\t{:8.4g}".format(nh, h, S, error))

    xbode = []
    ybode = []
    ebode = []
    print("")
    print("Bode")
    print("{:8}\t{:8}\t{:14}\t{:8}".format('nh', 'h', 'S', 'error'))
    for ih in range(nhscan - 2):
        nh = int(4 * 2**ih + 0.00001)
        h = (xmax - xmin) / nh
        S = 0.0
        for i in range(0, nh, 4):
            S += integ_bode(func, xmin + i * h, h)

        xbode.append(h)
        ybode.append(S)
        error = abs(S - S_ex)
        ebode.append(error)
        print("{:8d}\t{:8.4g}\t{:14.8g}\t{:8.4g}".format(nh, h, S, error))

#=============================
# Plot graphs
#=============================
    fig = plt.figure()

    ax1 = fig.add_subplot(2, 1, 1)
    ax2 = fig.add_subplot(2, 1, 2)

    ax1.plot(xrieman,    yrieman,    label = 'Rieman',    linewidth = 0.5, color = 'black', marker ='o', markersize = 1.5)
    ax1.plot(xtrapezoid, ytrapezoid, label = 'Trapezoid', linewidth = 0.5, color = 'red',   marker ='^', markersize = 1.5)
    ax1.plot(xsimpson,   ysimpson,   label = 'Simpson',   linewidth = 0.5, color = 'blue',  marker ='v', markersize = 1.5)
    ax1.plot(xbode,      ybode,      label = 'Bode',      linewidth = 0.5, color = 'green', marker ='s', markersize = 1.5)
    ax1.plot(ax1.get_xlim(), [S_ex, S_ex], label = 'exact', linestyle = 'dashed', linewidth = 0.5)
#    ax1.set_xscale('log')
#    ax1.set_yscale('log')
    ax1.set_xlabel("h")
    ax1.set_ylabel("S")
#    ax1.legend(bbox_to_anchor=(1.05, 1.0), loc='upper left', borderaxespad = 0)
    ax1.legend()

    ax2.plot(xrieman,    erieman,    label = 'Rieman',    linewidth = 0.5, color = 'black', marker ='o', markersize = 1.5)
    ax2.plot(xtrapezoid, etrapezoid, label = 'Trapezoid', linewidth = 0.5, color = 'red',   marker ='^', markersize = 1.5)
    ax2.plot(xsimpson,   esimpson,   label = 'Simpson',   linewidth = 0.5, color = 'blue',  marker ='v', markersize = 1.5)
    ax2.plot(xbode,      ebode,      label = 'Bode',      linewidth = 0.5, color = 'green', marker ='s', markersize = 1.5)
    ax2.set_xscale('log')
    ax2.set_yscale('log')
    ax2.set_xlabel("h")
    ax2.set_ylabel("|S - S_ex|")
#    ax2.legend(bbox_to_anchor=(1.05, 1.0), loc='upper left', borderaxespad = 0)
    ax2.legend()
    plt.tight_layout()

    plt.pause(0.1)
   
    print("Press ENTER to exit>>", end = '')
    input()

    terminate()


if __name__ == '__main__':
    main()
