# Cubic splines
import time

from scipy import misc

from polynomes import *

import polynomes

# linear complexity
def cubic_splines(f, a, b, n):
    xcoords = [a + k * (b - a) / n for k in range(n + 1)]
    ycoords = [f(k) for k in xcoords]
    dycoords = [misc.derivative(f, k) for k in xcoords]
    for k in range(n):
        a0 = ycoords[k + 1] / (xcoords[k + 1] - xcoords[k])
        a1 = ycoords[k] / (xcoords[k] - xcoords[k + 1])
        a2 = (dycoords[k] - a0 - a1) / ((xcoords[k] - xcoords[k + 1]) ** 2)
        a3 = (dycoords[k + 1] - a0 - a1) / ((xcoords[k + 1] - xcoords[k]) ** 2)
        nu0 = -a0 * xcoords[k] - a1 * xcoords[k + 1] - a2 * xcoords[k] * xcoords[k + 1] ** 2 - a3 * xcoords[k] ** 2 * \
              xcoords[k + 1]
        nu1 = a0 + a1 + a2 * (xcoords[k + 1] ** 2 + 2 * xcoords[k + 1] * xcoords[k]) + a3 * (
                xcoords[k] ** 2 + 2 * xcoords[k] * xcoords[k + 1])
        nu2 = -a2 * (xcoords[k] + 2 * xcoords[k + 1]) - a3 * (xcoords[k + 1] + 2 * xcoords[k])
        nu3 = a2 + a3
        P = [nu0, nu1, nu2, nu3]
        polynomial_graph(P, xcoords[k], xcoords[k + 1])
    coordsx = np.linspace(a, b, 1000)
    coordsy = [f(x) for x in coordsx]
    plt.plot(coordsx, coordsy, "b-", label="original function")
    plt.legend(loc="best")

# same complexity
def cubic_splines2(f, a, b, n):
    xcoords = [a + k * (b - a) / n for k in range(n + 1)]
    ycoords = [f(k) for k in xcoords]
    dycoords = [misc.derivative(f, k) for k in xcoords]
    for k in range(n):
        M = np.array([(1, xcoords[k], xcoords[k] ** 2, xcoords[k] ** 3),
                      (1, xcoords[k + 1], xcoords[k + 1] ** 2, xcoords[k + 1] ** 3),
                      (0, 1, 2 * xcoords[k], 3 * xcoords[k] ** 2), (0, 1, 2 * xcoords[k + 1], 3 * xcoords[k + 1] ** 2)])
        N = np.array([(ycoords[k]), (ycoords[k + 1]), (dycoords[k]), (dycoords[k + 1])])
        O = np.dot(np.linalg.inv(M), N)
        a0, a1, a2, a3 = O[0], O[1], O[2], O[3]
        P = [a0, a1, a2, a3]
        polynomial_graph(P, xcoords[k], xcoords[k + 1])
    coordsx = np.linspace(a, b, 1000)
    coordsy = [f(x) for x in coordsx]
    plt.plot(coordsx, coordsy, "b-", label="original function")
    plt.legend(loc="best")

# Integral approximation with cubic splines
# linear complexity
def interp_splines(f, a, b, n):
    I = 0
    xcoords = [a + k * (b - a) / n for k in range(n + 1)]
    ycoords = [f(k) for k in xcoords]
    dycoords = [misc.derivative(f, k) for k in xcoords]
    for k in range(n):
        a0 = ycoords[k + 1] / (xcoords[k + 1] - xcoords[k])
        a1 = ycoords[k] / (xcoords[k] - xcoords[k + 1])
        a2 = (dycoords[k] - a0 - a1) / ((xcoords[k] - xcoords[k + 1]) ** 2)
        a3 = (dycoords[k + 1] - a0 - a1) / ((xcoords[k + 1] - xcoords[k]) ** 2)
        nu0 = -a0 * xcoords[k] - a1 * xcoords[k + 1] - a2 * xcoords[k] * xcoords[k + 1] ** 2 - a3 * xcoords[k] ** 2 * \
              xcoords[k + 1]
        nu1 = a0 + a1 + a2 * (xcoords[k + 1] ** 2 + 2 * xcoords[k + 1] * xcoords[k]) + a3 * (
                xcoords[k] ** 2 + 2 * xcoords[k] * xcoords[k + 1])
        nu2 = -a2 * (xcoords[k] + 2 * xcoords[k + 1]) - a3 * (xcoords[k + 1] + 2 * xcoords[k])
        nu3 = a2 + a3
        P = [nu0, nu1, nu2, nu3]
        I += polynomial_integral(P, xcoords[k], xcoords[k + 1])
    return I

# Comparing the temporal efficiency of both methods on common functions
def cubic_splines_test(a, b, n):
    res_f_ref = [np.sin, np.cos, np.tan, np.exp, np.cosh, np.sinh, np.tanh]
    time1, time2 = [], []
    p = len(res_f_ref)
    for i in range(p):
        # method 1
        f = res_f_ref[i]
        start1 = time.time()
        xcoords = [a + k * (b - a) / n for k in range(n + 1)]
        ycoords = [f(k) for k in xcoords]
        dycoords = [misc.derivative(f, k) for k in xcoords]
        for k in range(n):
            a0 = ycoords[k + 1] / (xcoords[k + 1] - xcoords[k])
            a1 = ycoords[k] / (xcoords[k] - xcoords[k + 1])
            a2 = (dycoords[k] - a0 - a1) / ((xcoords[k] - xcoords[k + 1]) ** 2)
            a3 = (dycoords[k + 1] - a0 - a1) / ((xcoords[k + 1] - xcoords[k]) ** 2)
            nu0 = -a0 * xcoords[k] - a1 * xcoords[k + 1] - a2 * xcoords[k] * xcoords[k + 1] ** 2 - a3 * xcoords[
                k] ** 2 * xcoords[k + 1]
            nu1 = a0 + a1 + a2 * (xcoords[k + 1] ** 2 + 2 * xcoords[k + 1] * xcoords[k]) + a3 * (
                    xcoords[k] ** 2 + 2 * xcoords[k] * xcoords[k + 1])
            nu2 = -a2 * (xcoords[k] + 2 * xcoords[k + 1]) - a3 * (xcoords[k + 1] + 2 * xcoords[k])
            nu3 = a2 + a3
            P = [nu0, nu1, nu2, nu3]
        end1 = time.time()
        time1 += [end1 - start1]
        # method 2
        start2 = time.time()
        xcoords = [a + k * (b - a) / n for k in range(n + 1)]
        ycoords = [f(k) for k in xcoords]
        dycoords = [misc.derivative(f, k) for k in xcoords]
        for k in range(n):
            M = np.array([(1, xcoords[k], xcoords[k] ** 2, xcoords[k] ** 3),
                          (1, xcoords[k + 1], xcoords[k + 1] ** 2, xcoords[k + 1] ** 3),
                          (0, 1, 2 * xcoords[k], 3 * xcoords[k] ** 2),
                          (0, 1, 2 * xcoords[k + 1], 3 * xcoords[k + 1] ** 2)])
            N = np.array([(ycoords[k]), (ycoords[k + 1]), (dycoords[k]), (dycoords[k + 1])])
            O = np.dot(np.linalg.inv(M), N)
            a0, a1, a2, a3 = O[0], O[1], O[2], O[3]
            P = [a0, a1, a2, a3]
        end2 = time.time()
        time2 += [end2 - start2]
        plt.clf()
    sigma1, sigma2 = 0, 0
    n = len(time1)
    print(time1, time2)
    for k in range(n):
        sigma1 += time1[k]
        sigma2 += time2[k]
    print(sigma1 / n, sigma2 / n)

# First method is a bit faster on reference functions


import numpy as np

def build_space(T, N):
    '''
    Build a linear space centered on the origin

    Parameters
    ----------
    T : int
        Period of linear space
    N : int
        Length of linear space

    Returns
    ----------
    linear_space: ndarray(int)
        linear space of length N
    '''
    space = np.linspace(start=T, stop=N * T, num=N)
    zero_centered_space = space - space[N // 2]
    return zero_centered_space

def sample(f, T, N):
    '''
    Function sampler

    Parameters
    ----------
    f : function
        function to sample
    T : int
        Sampling Period
    N : int
        Number of samples

    Returns
    ----------
    linear_space: ndarray(int)
        linear space of length N
    sampled_f: ndarray(int)
        sampled function of length N
    '''
    x_array = build_space(T, N)
    sampled_f = f(x_array)
    return x_array, sampled_f


import numpy as _np
import matplotlib.pyplot as _plt
from johnspythonlibrary2.Process.Misc import findNearest
import xarray as _xr

# from scipy.signal import gaussian as _gaussian

#%% Basic signal generation

def gaussian_pulse(sigma, t, amplitude=1, plot=False):
	"""
	Basic gaussian pulse with units in time
	std_time is the standard deviation of the pulse with units of t

	Example
	-------

	Example 1::

		dt=1e-9
		t=np.arange(0,0.001+dt/2,dt)
		t-=t.mean()
		std_time=1e-4
		gaussian_pulse(std_time, t=t, plot=True)

	References
	----------
	  * http://www.cse.psu.edu/~rtc12/CSE486/lecture11_6pp.pdf
	"""
	out= _xr.DataArray( _np.exp(-t**2 / (2 * sigma**2)), dims='t', coords=[t])
	if plot == True:
		out.plot()
	return out

def gaussian_1st_deriv(sigma, t, amplitude=1, plot=False):
	"""
	Basic gaussian pulse with units in time
	std_time is the standard deviation of the pulse with units of t

	Example
	-------

	Example 1::

		dt=1e-9
		t=np.arange(0,0.001+dt/2,dt)
		t-=t.mean()
		std_time=1e-4
		s=gaussian_1st_deriv(sigma = std_time, t=t, plot=True)

	References
	----------
	  * http://www.cse.psu.edu/~rtc12/CSE486/lecture11_6pp.pdf
	"""
	# dt=t[1]-t[0]
	out= _xr.DataArray( -t / (2 * sigma**2) * _np.exp(-t**2 / (2 * sigma**2)) * amplitude * sigma * 3.298, dims='t', coords=[t])
	if plot == True:
		fig,ax=_plt.subplots()
		out.plot(ax=ax)
	return out

def squareWave(	freq,
				time,
				duty_cycle=0.5,
				plot=False):
	"""

	Examples
	--------
	Example 1::

		freq=1e2
		dt=1e-5
		time=np.arange(10000)*dt

		fig,ax=plt.subplots()
		ax.plot(	squareWave(freq,time))
	"""
	from scipy import signal as sg

	y = _xr.DataArray(	sg.square(2 * _np.pi * freq * time, duty=duty_cycle),
						dims=['t'],
						coords={'t':time})

	# optional plot
	if plot==True:
		fig,ax=_plt.subplots()
		ax.plot(y)

	return y

def chirp(	t,
			tStartStop=[0, 1],
			fStartStop=[1e3, 1e4],
			phi=270,
			plot=False,
			method='logarithmic'):
	"""
	Creates an exponential (also called a "logarithmic") chirp waveform

	Wrapper for scipy function.

	Example
	-------
	::

		import numpy as np
		t=np.arange(0,.005,6e-6);
		f0=1e3
		f1=2e4
		t0=1e-3
		t1=4e-3
		y=chirp(t,[t0,t1],[f0,f1],plot=True)

	"""
	from scipy.signal import chirp

	# find range of times to modify
	iStart=findNearest(t, tStartStop[0])
	iStop=findNearest(t, tStartStop[1])

	# create chirp signal using scipy function.  times are (temporarily) shifted such that the phase offset, phi, still makes sense
	y=_np.zeros(len(t))
	y[iStart:iStop] = chirp(t[iStart:iStop] - tStartStop[0], fStartStop[0], tStartStop[1] - tStartStop[0], fStartStop[1], method, phi=phi)
	y= _xr.DataArray(	y,
						dims=['t'],
						coords={'t':t})

	# optional plot
	if plot==True:
		fig, ax = _plt.subplots()
		y.plot(ax=ax)

	return y

def gaussianNoise(t, mean=0, stdDev=1, plot=False):
	"""
	Produces Gaussian noise based on the standard deviation
	This is a wrapper for the numpy.random.normal function

	Parameters
	----------
	shape : tuple of ints
		Shape of desired noise; such as generated from np.shape()
	mean : float
		Offset value. The default is 0.
	stdDev : float
		"Amplitude" (standard deviation) of the noise. The default is 1.
	plot : bool, optional
		Optional plot of results

	Returns
	-------
	noise : numpy array
		Array containing the generated noise.

	Examples
	--------

	Example 1::

		t=np.arange(0,10e-3,2e-6)
		y1=np.sin(2*np.pi*t*1e3)
		y2=y1+gaussianNoise(t,mean=0,stdDev=0.5)
		fig,ax=plt.subplots()
		ax.plot(t,y2,label='signal with noise')
		ax.plot(t,y1,label='signal without noise')
		ax.legend()

	"""

	noise = _xr.DataArray(	_np.random.normal(mean, stdDev, t.shape),
							dims=['t'],
							coords={'t': t})

	if plot==True:
		fig, ax = _plt.subplots()
		ax.plot(noise)

	return noise

#%% More complicated functions

def tentMap(N=1000, plot=False, ICs={'x0': _np.sqrt(2) / 2.0}):
	"""
	generate fake data using a tentmap

	x=tentMap(1000,plot=True)

	References
	----------
	* https://en.wikipedia.org/wiki/Tent_map
	"""
	t = _np.arange(0, N+1)
	x = _np.zeros(t.shape, dtype=_np.float64)
	x[0] = ICs['x0']
	def tentMap(x, mu=_np.float64(2 - 1e-15)):
		for i in range(1, x.shape[0]):
			if x[i-1] < 0.5:
				x[i] = mu * x[i - 1]
			elif x[i - 1] >= 0.5:
				x[i] = mu * (1 - x[i - 1])
			else:
				raise Exception('error')
		return x

	x = tentMap(x)

	# the actual data of interest is the first difference of x
	xDelta=_xr.DataArray(	x[1:] - x[0: -1],
							dims=['t'],
							coords={'t': t[1:]},
							attrs={'units': "au",
								 'standard_name': 'Amplitude'})
	xDelta.t.attrs = {'units': 'au'}

	if plot is True:
		fig, ax = _plt.subplots(2, sharex=True)
		ax[0].plot(t, x, linestyle='-', marker='.', linewidth=0.5, markersize=3, label='x')
		xDelta.plot(ax=ax[1], linestyle='-', marker='.', linewidth=0.5, markersize=3, label='x derivative')
		ax[0].legend()
		ax[1].legend()

	return xDelta

def saved_lorentzAttractor(	N=2000,
							dt=0.05,
							ICs={	'x0': -9.38131377,
									'y0': -8.42655716 ,
									'z0': 29.30738524},
							plot=False,
							removeMean=False,
							normalize=False,
							removeFirstNPoints=500,
							args={	'sigma': 10.0,
									'b': 8.0 / 3.0,
									'r': 28.0}):

	filename='lorentz_attractor_N_%d_dt_%.6f_x0_%.3f_y0_%.3f_z0_%.3f.NetCDF' % (N, dt, ICs['x0'], ICs['y0'], ICs['z0'])

	try:
		ds = _xr.open_dataset(filename)
		print('loaded previously generated dataset')
	except:
		print('creating dataset')
		ds = lorentzAttractor(	N=N,
								dt=dt,
								ICs=ICs,
								plot=plot,
								removeMean=removeMean,
								normalize=normalize,
								removeFirstNPoints=removeFirstNPoints,
								args=args)

		ds.to_netcdf(filename)
		ds = _xr.open_dataset(filename)

	return ds

def lorentz96(N=5, F=8, t_final=30.0, dt=0.01, plot=False):
	"""

	References
	----------
	* https://en.wikipedia.org/wiki/Lorenz_96_model
	* 'a common benchmark for spatiotemporal filtering methods due to its chaotic dynamics and moderate dimensionality' : https://journals.aps.org/prx/abstract/10.1103/PhysRevX.6.011021

	Examples
	--------

	Example 1::

		x = lorentz96(plot=True)

	Example 2::

		x = lorentz96(40, t_final=50, plot=True)
	"""

	if N < 4:
		raise Exception("N must be >= 4")

	# parameters

	# library
	from scipy.integrate import odeint

	# subfunction
	def L96(x, t):
		"""Lorenz 96 model with constant forcing"""
		# Setting up vector
		d = _np.zeros(N)
		# Loops over indices (with operations and Python underflow indexing handling edge cases)
		for i in range(N):
			d[i] = (x[(i + 1) % N] - x[i - 2]) * x[i - 1] - x[i] + F
		return d

	# setup time
	t = _np.arange(0.0, float(t_final), dt)

	# setup ICs
	x0 = F * _np.ones(N)  # Initial state (equilibrium)
	x0[0] += 0.01  # Add small perturbation to the first variable

	# solve
	x = odeint(L96, x0, t)

	# finalize
	x = _xr.DataArray(x.transpose(), dims=['n', 't'], coords=[_np.arange(0, N) + 1, t])
	x.t.attrs = {'long_name': 'Time', 'units': 'au'}

	if plot is True:
		fig = _plt.figure()
		ax = fig.gca(projection="3d")
		ax.plot(x.sel(n=1), x.sel(n=2), x.sel(n=3))
		ax.set_xlabel("$x_1$")
		ax.set_ylabel("$x_2$")
		ax.set_zlabel("$x_3$")

		fig, ax = _plt.subplots()
		x.plot(ax=ax)

	return x

def lorentzAttractor(	N=2000,
						dt=0.05,
						ICs={	'x0': -9.38131377,
								'y0': -8.42655716 ,
								'z0': 29.30738524},
						plot=False,
						removeMean=False,
						normalize=False,
						removeFirstNPoints=500,
						args={	'sigma': 10.0,
								'b': 8.0 / 3.0,
								'r': 28.0}):
	"""
	Solves the lorentz attractor nonlinear ODEs.

	References
	----------
	 * https://en.wikipedia.org/wiki/Lorenz_system
	 * https://www.diasporist.org/jeroen/assets/lecture%20-%20A%20brief%20introduction%20to%20the%20Lorenz%2063%20system.pdf

	Examples
	--------
	Example 1::

		_plt.close('all')
		x,y,z=solveLorentz( 	N=5000,
						dt=0.02,
						IC=[-9.38131377, -8.42655716 , 29.30738524],
						plot='all',
						removeMean=True,
						normalize=True,
						removeFirstNPoints=500)

	"""

	from scipy.integrate import solve_ivp

	## initialize
	N += removeFirstNPoints
	T = N * dt
	ICs = _np.array(list(ICs.items()))[:, 1].astype(float)

	## Solve Lorentz system of equations
	def ODEs(t, y, *args):
		X, Y, Z = y
		sigma, b, r = args
		derivs =	[	sigma * (Y - X),
						-X * Z + r * X - Y,
						X * Y - b * Z]
		return derivs

	t_eval = _np.arange(0, T, dt)
	psoln = solve_ivp(	ODEs,
						[0, T],
						ICs,  # initial conditions
						args=args.values(),
						t_eval=t_eval
						)
	x, y, z = psoln.y

	## Cleanup data
	x = _xr.DataArray(	x,
						dims=['t'],
						coords={'t': t_eval},
						attrs={	'units': "au",
								'long_name': 'Amplitude'})
	x.t.attrs = {'units': 'au', 'long_name': 'Time'}
	y = _xr.DataArray(	y,
						dims=['t'],
						coords={'t': t_eval},
						attrs={	'units': "au",
								'long_name': 'Amplitude'})
	y.t.attrs = {'units': 'au', 'long_name': 'Time'}
	z=_xr.DataArray(	z,
						dims=['t'],
						coords={'t': t_eval},
						attrs={	'units': "au",
								'long_name': 'Amplitude'})
	z.t.attrs = {'units': 'au', 'long_name': 'Time'}

	if removeFirstNPoints > 0:
		x = x[removeFirstNPoints:]
		y = y[removeFirstNPoints:]
		z = z[removeFirstNPoints:]

	if removeMean is True:
		x -= x.mean()
		y -= y.mean()
		z -= z.mean()

	if normalize is True:
		x /= x.std()
		y /= y.std()
		z /= z.std()

	ds=_xr.Dataset({	'x':x,
						'y':y,
						'z':z})

	## optional plots
	if plot is not False:
		fig, ax = _plt.subplots(3, sharex=True)
		markersize = 2
		x.plot(ax=ax[0], marker='.', markersize=markersize)
		y.plot(ax=ax[1], marker='.', markersize=markersize)
		z.plot(ax=ax[2], marker='.', markersize=markersize)
		ax[0].set_title('Lorentz Attractor\n' + r'($\sigma$, b, r)=' + '(%.3f, %.3f, %.3f)' % (args['sigma'], args['b'], args['r']) + '\nICs = (%.3f, %.3f, %.3f)' % (ICs[0], ICs[1], ICs[2]))

	if plot == 'all':
		_plt.figure(); _plt.plot(x, y)
		_plt.figure(); _plt.plot(y, z)
		_plt.figure(); _plt.plot(z, x)

		import matplotlib as _mpl
		_mpl.rcParams.update({'figure.autolayout': False})
		fig = _plt.figure()
		ax = fig.add_subplot(121, projection='3d')
		ax.plot(x, y, zs=z)
		ax.set_xlabel('x')
		ax.set_ylabel('y')
		ax.set_zlabel('z')
		ax.set_title('Lorentz Attractor\n' + r'($\sigma$, b, r)=' + '(%.3f, %.3f, %.3f)' % (args['sigma'], args['b'], args['r']) + '\nICs = (%.3f, %.3f, %.3f)' % (ICs[0], ICs[1], ICs[2]) + '\nState space')

		ax = fig.add_subplot(122, projection='3d')
		ax.plot(x[6::3], x[3:-3:3], zs=x[:-6:3])
		ax.set_xlabel('x(t)')
		ax.set_ylabel(r'x(t-$\tau$)')
		ax.set_zlabel(r'x(t-2$\tau$)')
		ax.set_title('Lorentz Attractor\n' + r'($\sigma$, b, r)=' + '(%.3f, %.3f, %.3f)' % (args['sigma'], args['b'], args['r']) + '\nICs = (%.3f, %.3f, %.3f)' % (ICs[0], ICs[1], ICs[2]) + '\nTime-lagged state space')
		fig.tight_layout(pad=5, w_pad=5)

	return ds

def predatorPrey(	N=100000,
					T=100,
					ICs={	'x0': 0.9,
							'y0': 0.9},
					args={	'alpha': 2.0 / 3.0,
							'beta': 4.0 / 3.0,
							'delta': 1.0,
							'gamma': 1.0},
					plot=False,
					removeMean=False,
					normalize=False):
	"""
	Lotka–Volterra (classic predator-prey problem) equations

	References
	----------
	* https://en.wikipedia.org/wiki/Lotka%E2%80%93Volterra_equations

	Examples
	--------
	Example 1::
		predatorPrey(plot=True)
	"""

	from scipy.integrate import solve_ivp

	## initialize
	dt=T/N

	## Solve Lorentz system of equations
	def ODEs(t, y, *args):
		X,Y = y
		alpha, beta, delta, gamma = args
		#print(X,Y,alpha,beta,delta,gamma)
		derivs=	[	alpha * X - beta * X * Y,
					delta * X * Y - gamma * Y]
		return derivs

	t_eval = _np.arange(0, T, dt)
	psoln = solve_ivp(	ODEs,
						[0, T],
						_np.array(list(ICs.items()))[:, 1].astype(float),  # initial conditions
						args=args.values(),
						t_eval=t_eval
						)
	x, y = psoln.y

	## Cleanup data
	x = _xr.DataArray(	x,
						dims=['time'],
						coords={'time': t_eval},
						attrs={'units': "au",
								 'standard_name': 'Amplitude'})
	x.time.attrs = {'units': 'au'}
	y = _xr.DataArray(	y,
						dims=['time'],
						coords={'time': t_eval},
						attrs={'units': "au",
								 'standard_name': 'Amplitude'})
	y.time.attrs = {'units': 'au'}

	if removeMean is True:
		x -= x.mean()
		y -= y.mean()

	if normalize is True:
		x /= x.std()
		y /= y.std()

	ds = _xr.Dataset({	'x': x,
						'y': y})

	## Optional plots
	if plot is True:
		fig, ax = _plt.subplots(2, sharex=True)
		ds.x.plot(ax=ax[0])
		ds.y.plot(ax=ax[1])

		fig, ax = _plt.subplots()
		ax.plot(ds.x.values, ds.y.values)

	return ds

def coupledHarmonicOscillator(	N=10000,
								T=1,
								ICs={	'y1_0': 0,
										'x1_0': 0.9,
										'y2_0': 0,
										'x2_0': -1},
								args={	'k': 1,
										'kappa': 1,
										'm': 1e-4},
								plot=False):
	"""
	Solve a coupled harmonic oscillator problem. See reference for details.

	Examples
	--------
	Example1 ::
		x1,x2=coupledHarmonicOscillator(plot=True,N=1e5,T=10)

	References
	----------
	http://users.physics.harvard.edu/~schwartz/15cFiles/Lecture3-Coupled-Oscillators.pdf

	"""

	import matplotlib.pyplot as _plt

	dt  = T / N

	from scipy.integrate import solve_ivp

	def ODEs(t, y, *args):
		y1, x1, y2, x2 = y
		k, kappa, m = args
		derivs = [	-(k + kappa) / m * x1 + kappa / m * x2,
					y1,
 					-(k + kappa) / m * x2 + kappa / m * x1,
					y2]
		return derivs

	time = _np.arange(0, T, dt)
	psoln = solve_ivp(	ODEs,
						t_span=[0, T],
						y0=_np.array(list(ICs.values())),  # initial conditions
						args=args.values(),
						t_eval=time
					)

	y1, x1, y2, x2 = psoln.y

	x1=_xr.DataArray(	x1,
						dims=['time'],
						coords={'time': time},
						attrs={'units': "au",
								 'standard_name': 'Amplitude'})
	x1.time.attrs = {'units': 'au'}
	x2=_xr.DataArray(	x2,
						dims=['time'],
						coords={'time': time},
						attrs={'units': "au",
								 'standard_name': 'Amplitude'})
	x2.time.attrs={'units': 's'}

	if plot==True:
		fig, ax = _plt.subplots(2, sharex=True)
		x1.plot(ax=ax[0], marker='.')
		x2.plot(ax=ax[1], marker='.')
		ax[0].set_title('Coupled harmonic oscillator\n' + r'(k, kappa, m)='+'(%.3f, %.3f, %.6f)' % (args['k'], args['kappa'], args['m']) + '\nICs = (%.3f, %.3f, %.3f, %.3f)' % (ICs['y1_0'], ICs['x1_0'], ICs['y2_0'], ICs['x2_0']))

	ds=_xr.Dataset(	{'x1': x1,
					 'x2': x2})

	return ds

def coupledHarmonicOscillator_nonlinear(	N=500,
											dt=0.1,
											ICs={	'y1_0': 0,
													'x1_0': 1,
													'y2_0': 0,
													'x2_0': 0},
											args={	'f1': 45,
													'f2': 150,
													'm': 1,
													'E': 1e5},
											plot=False,
											verbose=False):
	"""
	Solve a coupled harmonic oscillator problem. See reference for details.

	Examples
	--------
	Example1 ::
		x1,x2=coupledHarmonicOscillator_nonlinear(N=10000,dt=1e-3,plot=True,verbose=True).values()
		nperseg=2000
		from johnspythonlibrary2.Process.Spectral import fft_average
		fft_average(x1,plot=True,nperseg=nperseg,noverlap=nperseg//2)

	References
	----------
	https://arxiv.org/pdf/1811.02973.pdf

	"""

	f1, f2, m, E = args.values()
	k1 = (2 * _np.pi * f1)**2 * m
	k2 = ((2 * _np.pi * f2)**2 * m - k1) / 2

	import matplotlib.pyplot as _plt

	from scipy.integrate import solve_ivp

	def ODEs(t, y, *args):
		y1, x1, y2, x2 = y
		k1, k2, m1, m2, E = args
		if verbose:
			print(-k1 * x1 / m1, +k2 * (x2 - x1) / m1, E * x1**2 / m1)
		derivs=	[	-k1 * x1 / m1 + k2 * (x2 - x1) / m1 + E * x1**2 / m1,
					y1,
 					-k1 * x2 / m2 - k2 * (x2 - x1) / m2,
					y2]
		return derivs

	time = _np.arange(0, N) * dt
	T = time[-1]
	psoln = solve_ivp(	ODEs,
						t_span=[0, T],
						y0=_np.array(list(ICs.values())),  # initial conditions
						args=(k1, k2, m, m, E),
						t_eval=time
					)

	y1, x1, y2, x2 = psoln.y

	x1=_xr.DataArray(	x1,
						dims=['t'],
						coords={'t': time},
						attrs={'units': "au",
								 'standard_name': 'Amplitude'})
	x1.t.attrs = {'units': 'au'}
	x2 = _xr.DataArray(	x2,
						dims=['t'],
						coords={'t': time},
						attrs={'units': "au",
								 'standard_name': 'Amplitude'})
	x2.t.attrs = {'units': 's'}

	if plot is True:
		fig, ax = _plt.subplots(2, sharex=True)
		x1.plot(ax=ax[0], marker='.')
		x2.plot(ax=ax[1], marker='.')
		ax[0].set_title('Coupled harmonic oscillator\n' + r'(f1, f2, m, E)=' + '(%.3f, %.3f, %.3f,%.3f)' % (f1, f2, m, E) + '\nICs = (%.3f, %.3f, %.3f, %.3f)' % (ICs['y1_0'], ICs['x1_0'], ICs['y2_0'], ICs['x2_0']))

	ds=_xr.Dataset(	{'x1': x1,
					 'x2': x2})

	return ds


import sympy as sym
from approx1D import least_squares
import scipy
import scipy.integrate

def sin_series(N):
  psi = []
  for k in range(1,N):
    psi_k = sym.sin(sym.pi*k*x)
    psi.append(psi_k)
  return psi

def taylor_series(N):
  psi = []
  for k in range(1,N):
    psi_k = x**k
    psi.append(psi_k)
  return psi

def series(series_type, N):
  if series_type=="Taylor" : return taylor_series(N)
  elif series_type=="sin"  : return sin_series(N)
  else: print("series type unknown ") # sys.exit(0)

X = scipy.arange(0, 1, 0.0001)
import pylab
def plot_solution(series_type, N, func):

  print("series_type ", series_type, " N ", N, " func ", func)

  psi = series(series_type, N)
  print(psi)
  u, c = least_squares(func, psi, Omega, False)

  u = sym.lambdify([x], u)
  U = [u(xi) for xi in X]
  pylab.plot(X, U)

def plot_u(u):

  u = sym.lambdify([x], u)
  U = [u(xi) for xi in X]
  pylab.plot(X, U)

Omega = [0, 1]
x = sym.Symbol("x")
gauss_bell = sym.exp(-(x-0.5)**2) - sym.exp(-0.5**2)
#step = sym.Piecewise( (1, 0.25 < x and x < 0.75), (0, True)  )
step = sym.Piecewise( (1, 0.25 <= x), (0, True)  )- sym.Piecewise( (1, 0.75 < x), (0, True)  )
hat = sym.Piecewise( (x, x <= 0.5), (0, True)  ) + sym.Piecewise( (1-x, 0.5 < x), (0, True)  )

print("hat (0.3) ", hat.subs(x, 0.3))

func=hat

Ns =[5, 10, 15, 20, 25]
#Ns =[2, 3, 4, 5]
for N in Ns:
#  plot_solution("sin", N, func)
  plot_solution("Taylor", N, func)
legend = ["N=%d" % N for N in Ns]
plot_u(func)
legend.append("analytical solution")
pylab.legend(legend)
pylab.show()
pylab.savefig("step.png", title="sin approximation")


from numpy import tanh,sinh,cosh,pi,abs

class DoubleExp:
    def g(self,t):
        return tanh( pi/2. * sinh(t)  )
    def dgdt(self,t):
        return pi/2. *cosh(t)/cosh( pi/2. * sinh(t)  )**2.

    def F(self,t):
        #this will be used to determine the error
        return self.func( self.g(t) )*self.dgdt(t)

    def d2Fdt(self,t,_h=1e-8):
        '''
        This will give the second derivatives we need for the error estimation.
        For the moment take derivatives numerically.
        Later I will do the derivatives of g analytically, but for the moment should be fine.
        '''
        return (self.F(t+_h )- 2 * self.F(t ) +  self.F(t -_h ))/(_h**2.)

    def __init__(self,func,_exp=1,_exp_max=15,rtol=1e-5,atol=1e-5,p=10):
        '''
        func: function to be integrated in the interval [-1,1].
        exp: initial value of h=2^-exp
        exp_min: the minimum exp, with hmin= 2^{-exp_max}
        p: precision.

        Nmax=maximum number of evaluations

        Note that x_{-j}=-x_j and w_{-j}=-w_j .
        '''
        self.func=func

        self._exp=_exp
        self._exp_max=_exp_max

        self.h=2**-_exp
        self.hmin=2**-_exp_max

        self.rtol=rtol
        self.atol=atol
        self.eps=10**(-p)

        #initialize N. Start with N=0.
        self.N=0
        self.N_init=False

        #eval will tell us if we have already evaluated the integral for given N and h (no need to sum thingswe already have)
        self.eval=True
        self.h_stop=False

        #initialize the integral and the error.
        #As you update h and N, you need to add to the sum only new values produced
        #Also, since h changes, multipy by h at the end of the evaluation.
        self.integral=self.func( self.g(0) ) *self.dgdt(0)
        self.err=self.d2Fdt(0)

    def N_start(self):
        '''
        Find an appropriate N to start.
        As you update h, just update N->N*2 (later we may use something better)
        '''

        #start from this.
        tmp_N=self.N+1
        while True:
            #remember that x_j=-x_{-j}, w_j = w_{-j}
            _x=self.g(self.h*tmp_N)
            _w=self.dgdt(self.h*tmp_N)
            _f1=_w*self.func(_x)
            _f2= _w*self.func(-_x)

            #Note that we want N to start as N>0. This way we make sure that N gets updated correctly
            #(if N starts at 0, it's not going to be updated).
            if abs(_f1)<self.eps and abs(_f2 )<self.eps and self.N>1:
                self.N_init=True
                self.eval=False
                break
            else:

                self.integral+=_f1+_f2
                self.err+=self.d2Fdt( tmp_N*self.h)

                self.N=tmp_N
                tmp_N+=1

    def evaluate(self):
        '''
        Evaluate the integral for given h and N.
        Also evaluate the error.

        Note for later: since we update h->h/2, we just need to update the sum including only the new
        addition we make. That is, you only calculate for odd j!
        '''
        j=1
        while self.eval:
            _x=self.g(self.h*j)
            _w=self.dgdt(self.h*j)

            self.integral+=_w*(self.func(_x) + self.func(-_x))
            self.err+=self.d2Fdt( j*self.h)+self.d2Fdt( -j*self.h)
            j+=2
            if j>self.N-2:
                self.eval=False
                break

    def h_control(self):
        '''
        Determines if  the error is acceptable. If not, decrese h until it is (or hmin is found).
        '''
        abs_err=abs(self.err*self.h*(self.h/(2*pi))**2.)

        _sc=self.atol + self.rtol*abs(self.integral)

        if abs_err/_sc <1 :
            self.h_stop=True
        else:
            if self.h<self.hmin:
                self.h_stop=True
            else:
                self.h=self.h/2
                self.N=self.N*2
                self.eval=True

    def integrate(self):
        if self.N_init==False:
            self.N_start()

        while self.h_stop==False:
            self.h_control()
            self.evaluate()

        self.eval=False
        return (self.integral*self.h , abs(self.err*self.h*(self.h/(2*pi))**2.)  )

if __name__ == "__main__":
    def F(x):
        return 1/((1+x)**0.5 +(1-x)**0.5 +2  )

    DE=DoubleExp(func=F,_exp=10,_exp_max=50,p=20,rtol=1e-10,atol=1e-10)

    print(
        'result: {0[0]} \nerror: {0[1]} \nexact: {1}'.format(
        DE.integrate(),4*2**0.5-pi-2)
        )


import numpy as np
from matplotlib import pyplot as plt

def amsignal(am, ap, fm, fp, mostraSpettro):
    '''
    Calcola il segnale modulato e ne disegna il grafico
    :param am: Ampiezza modulante in V
    :param ap: Ampiezza portante in V
    :param fm: Frequenza modulante in Hz
    :param fp: Frequenza portante in Hz
    :param mostraSpettro: booleano per dire se mostrare lo spettro o no
    :return:
    '''
    t = np.arange(0, 500, 0.01)
    fpMHz = fp/1e6
    fmMHz = fm/1e6
    vpt = ap * np.cos(2 * np.pi * fpMHz * t)
    vmt = am * np.cos(2 * np.pi * fmMHz * t)
    vit = [x + ap for x in vmt]
    vam = (1 / ap) * vpt * vit
    if not mostraSpettro:
        plt.plot(t, vam, color="blue", label="Segnale modulato")
        plt.plot(t, vit, color="orange", label="Segnale inviluppo")
        plt.xlabel("Tempo [us]")
        plt.ylabel("Ampiezza [V]")
        plt.grid()
        plt.legend()
        plt.show()

    else:
        spettro(am, ap, fm, fp)

def spettro(am, ap, fm, fp):
    '''
    Disegna il grafico dello spettro del segnale
    :param am: Ampiezza modulante in V
    :param ap: Ampiezza portante in V
    :param fm: Frequenza modulante in Hz
    :param fp: Frequenza portante in Hz
    '''
    fm = fm/1e3
    fp = fp/1e3
    plt.xlim(fp - fm * 2, fp + fm * 2)
    plt.ylim(0, ap + am / 2)
    plt.xlabel("Frequenza [KHz]")
    plt.ylabel("Volt [V]")
    plt.plot([fp, fp], [0, ap], color="black")
    plt.text(fp - 1, ap + 0.7, "Ap=" + str(ap))
    plt.plot([fp - fm, fp - fm], [0, am / 2], color="black")
    plt.text((fp - fm) - 1, (am / 2) + 0.7, "Am/2=" + str(am / 2))
    plt.plot([fp + fm, fp + fm], [0, am / 2], color="black")
    plt.text((fp + fm) - 1, (am / 2) + 0.7, "Am/2=" + str(am / 2))
    plt.show()

def max(*args):
    max = 0
    for x in args:
        if x > max:
            max = x
    return max


#!/usr/bin/env python

""" main module """

import time
import numpy as np
from matplotlib import pyplot as plt
from eulers import *

def my_method(x_init, x_dot_func, step, time):
    """ euer method """
    # finding count
    count = int(time / step) + 1

    # initialization
    x = np.zeros(count)
    x_dot = np.zeros(count)
    time = np.zeros(count)
    x[0] = x_init
    x_dot[0] = x_dot_func(x[0], time[0])

    for i in range(count - 1):
        time[i + 1] = time[i] + step
        k = x_dot_func(x[i], time[i]) * step
        x_dot[i + 1] = x_dot_func(x[i] + k, time[i] + 0.5 * step)
        x[i + 1] = x[i] + (x_dot[i + 1] + x_dot[i]) * 0.5 * step
        x_dot[i + 1] = x_dot_func(x[i + 1], time[i + 1])

    return x, x_dot, time, count

def real(t):
    return 1 / 20.0 * (np.exp(-t) - np.exp(-21 * t))

def x_dot(x, t):
    """ x_dot function """
    return -21 * x + np.exp(-t)

def main():
    """ main body """
    x_init = 0
    end = 4
    steps = np.array([0.01, 0.02, 0.04, 0.06, 0.08, 0.1])
    runtime = np.zeros((2, len(steps)))

    ######## Part A #########
    x = [None] * len(steps)
    x_dots = [None] * len(steps)
    time0 = [None] * len(steps)
    count = [None] * len(steps)

    for i, step in enumerate(steps):
        start = time.time()
        x[i], x_dots[i], time0[i], count[i] = euler(x_init, x_dot, step, end)
        runtime[0, i] = time.time() - start

    for i in range(len(steps)):
        plt.plot(time0[i], x[i], '-', label=f"h = {steps[i]}")
        plt.xlabel("t")
        plt.ylabel("y")
        plt.grid()
        plt.legend()
        plt.title(f"h = {steps[i]}, euler method")
        plt.savefig(f"results/euler_{steps[i]}.jpg", dpi=200, bbox_inches='tight')
        plt.close()

    ######## Part B ###########
    x1 = [None] * len(steps)
    x_dots1 = [None] * len(steps)
    time1 = [None] * len(steps)
    count1 = [None] * len(steps)

    for i, step in enumerate(steps):
        start = time.time()
        x1[i], x_dots1[i], time1[i], count1[i] = backward_euler(x_init, x_dot, step, end)
        runtime[1, i] = time.time() - start

    for i in range(len(steps)):
        plt.plot(time1[i], x1[i], '-', label=f"h = {steps[i]}")
        plt.xlabel("t")
        plt.ylabel("y")
        plt.grid()
        plt.title(f"h = {steps[i]}, backward euler")
        plt.legend()
        plt.savefig(f"results/backward_euler_{steps[i]}.jpg", dpi=200, bbox_inches='tight')
        plt.close()

    ########### Part C #############
    print("===============Euler===========")
    for i in range(len(steps)):
        error = x[i][-1] - real(time0[i][-1])
        print(f"h = {steps[i]},\t error = {error},\t runtime = {runtime[0, i]}")

    print("============= Backward Euler==========")
    for i in range(len(steps)):
        error = x1[i][-1] - real(time1[i][-1])
        print(f"h = {steps[i]},\t error = {error},\t runtime = {runtime[1, i]}")

    ######### Part D ###########
    x = [None] * len(steps)
    x_dots = [None] * len(steps)
    time0 = [None] * len(steps)
    count = [None] * len(steps)

    for i, step in enumerate(steps):
        start = time.time()
        x[i], x_dots[i], time0[i], count[i] = my_method(x_init, x_dot, step, end)
        runtime[0, i] = time.time() - start

    for i in range(len(steps)):
        plt.plot(time0[i], x[i], '-', label=f"h = {steps[i]}")
    plt.legend()
    plt.show()

    print("===============Euler===========")
    for i in range(len(steps)):
        error = x[i][-1] - real(time0[i][-1])
        print(f"h = {steps[i]},\t error = {error},\t runtime = {runtime[0, i]}")

if __name__ == "__main__":
    main()


#!/usr/bin/env python
# coding: utf-8
# vim: set et sw=4 ts=4 sts=4 fenc=utf-8
# Author: Yang Tong
# Created: 2016-07-24 10:15 SGT

#Application: This script applies cubic spline interpolation to fit lines.
#             The given x-y data should be put into a file in which there are just
#             two columns: the first one is x and the second y.
#             >>>Multiple lines can be fitted simultaneously if they have the same x grids.
#                And the x-y data for each line should be separated from each other by a empty line
#How to use: >>>python cubic_spline_interpolation.py
#History:
#       created at 2016-07-24 10:15 SGT
#       modified at 2016-9-29 15:08 SGT

from scipy import interpolate
import numpy as np
import matplotlib.pyplot as plt
import re

def read_x_y(filename):
    f = open(filename, 'r')

    x_points, y_points = [], []
    for line in f:
        x_y = re.findall("[-+0-9\.eE]+", line)
        if x_y:
            x, y = float(x_y[0]), float(x_y[1])
            if y >= 10000:
                continue
            if x not in x_points:
                x_points.append(x)
            ind = x_points.index(x)
	    if len(y_points) == ind:
	        y_points.append([])
	    if len(y_points) < ind:
	        raise Exception, "some mistake"
	    y_points[ind].append(y)
    f.close()

    assert len(x_points) == len(y_points), "The number of x data does not match that of y data. Please check!"
    length = len(y_points[0])
    for item in y_points:
        assert len(item) == length, "The provided data is not complete. Please Check!"
    y_ = []
    for ind in range(len(y_points[0])):
        y_.append([item[ind] for item in y_points])
    return (x_points, y_)

def multiple_lines_cubic_spline_interpolation(x, y_s, start, end, accuracy):
    y_ = []
    for y in y_s:
        x_new, y_new = cubic_spline_interpolation(x, y, start, end, accuracy)
        y_.append(y_new)
    return x_new, y_

def cubic_spline_interpolation(x, y, start, end, accuracy):
    tck = interpolate.splrep(x, y, s=0)

    x_new = np.arange(start, end, accuracy*0.1)
    y_new = interpolate.splev(x_new, tck, der=0)
    return x_new, y_new

def plot_and_save_fig(known_x_y, new_x_y, operation):
    '''Plot the polynomial or save the figure:
       code\toperation
       0\tquit
       1\tonly plot
       2\tonly save
       3\tplot & save'''

    if operation == '0':
        return 0

    if "y" in raw_input("Is the given data also included in the figure? (y or n): ").lower():
        for y in known_x_y[1]:
            plt.plot(known_x_y[0], y, 's') #, label="Given Data", markersize=6, linewidth=2)
    for y in new_x_y[1]:
        plt.plot(new_x_y[0], y, "r-") #, label="Cubic Spline", linewidth=2)
    plt.xlim([new_x_y[0][0]*1.1-0.1*new_x_y[0][-1], 1.10*new_x_y[0][-1]-0.1*new_x_y[0][0]])
    plt.legend(loc='best')
    plt.xlabel(raw_input("Enter the x title: "))
    plt.ylabel(raw_input("Enter the y title: "))
    plt.title(raw_input("Enter the title: "))
    if operation in ('2', '3'):
        plt.savefig("Cubic_Spline.png")
    if operation in ('1', '3'):
        plt.show()

def save_interpolated_data(x, y_s, filename):
    f = open(filename, "w")

    for y in y_s:
        for i, j in zip(x, y):
            f.write("%f\t%f\n" % (i, j))
        f.write("\n")
    f.close()

def cal_significant_figure(accuracy, figs=0):
    while accuracy < 1:
        figs += 1
        accuracy *= 10
    return figs

def input_data():
    print("\n>>>Cubic spline interpolation is employed to give an interpolating polynomial<<<\n")
    filename = raw_input("Please enter the filename where each line includes a known x-y pair: ")
    accuracy = float(raw_input("Please enter the accuracy (e.g.0.1, 0.01, 0.001, ...): accuracy="))
    sig_figs = cal_significant_figure(accuracy)
    return filename, accuracy, sig_figs

if __name__ == "__main__":

    filename, accuracy, sig_figs = input_data()
    x_points, y_points = read_x_y(filename)
    x, y = multiple_lines_cubic_spline_interpolation(x_points, y_points, x_points[0], x_points[-1], accuracy)
    if "y" in raw_input("\n>>>Print the min points for each interpolated line up to the accuary give above? (y or n): ").lower():
        output_format = ">>>For curve %d, E_min=%f at %." + str(sig_figs) + "f<<<"
        for ind, y_ in enumerate(y):
            print(output_format % (ind, y_.min(), x[list(y_).index(y_.min())]))

    if "y" in raw_input("\n>>>Save the cubic spline interpolated data? (y or n): ").lower():
        filename = raw_input("Enter the filename to store the interpolated data:")
        save_interpolated_data(x, y, filename)

    if "y" in raw_input("\n>>>Plot or save the figure? (y or n): ").lower():
        print(plot_and_save_fig.__doc__)
        code = raw_input("Enter the code: ").strip(" ")[0]
        plot_and_save_fig([x_points, y_points], [x, y], code)


# ------------- Machine Learning - Topic 7: K-means Clustering and Principal Component Analysis

#     featureNormalize
#     pca
#     drawLine
#     projectData
#     recoverData
#     displayData
#     kMeansInitCentroids
#     runkMeans
#     hsv
#     plotDataPoints

from scipy.io import loadmat
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import os, sys
sys.path.append(os.getcwd() + os.path.dirname('/ml/ex7/'))
from helpers import featureNormalize, pca, drawLine, displayData, projectData, \
    recoverData, kMeansInitCentroids, runkMeans, hsv, plotDataPoints

## ================== Part 1: Load Example Dataset  ===================
#  We start this exercise by using a small dataset that is easily to
#  visualize
#
print('Visualizing example dataset for PCA.\n');

#  The following command loads the dataset. You should now have the
#  variable X in your environment
mat = loadmat('ml/ex7/data/ex7data1.mat')
X = np.array(mat["X"])

# interactive graphs
plt.ion()

#  Visualize the example dataset
plt.close()

# kept the scatter() (vs. the plot()) version
#  because scatter() makes properly circular markers
# plt.plot(X[:, 0], X[:, 1], 'o', markersize=9, markeredgewidth=1, markeredgecolor='b', markerfacecolor='None')
plt.scatter(X[:,0], X[:,1], s=75, facecolors='none', edgecolors='b')
plt.axis([0.5, 6.5, 2, 8])
plt.gca().set_aspect('equal', adjustable='box')
plt.show(block=False)

input('Program paused. Press enter to continue.')

## =============== Part 2: Principal Component Analysis ===============
#  You should now implement PCA, a dimension reduction technique. You
#  should complete the code in pca.m
#
print('Running PCA on example dataset.\n');

#  Before running PCA, it is important to first normalize X
X_norm, mu, _ = featureNormalize(X)

#  Run PCA
U, S = pca(X_norm)

#  Compute mu, the mean of the each feature

#  Draw the eigenvectors centered at mean of data. These lines show the
#  directions of maximum variations in the dataset.
plt.hold(True)
drawLine(mu, mu + 1.5 * S[0,0] * U[:,0].T, c='k', linewidth=2)
drawLine(mu, mu + 1.5 * S[1,1] * U[:,1].T, c='k', linewidth=2)
plt.hold(False)

print('Top eigenvector: \n')
print(' U(:,1) = {:f} {:f} \n'.format(U[0,0], U[1,0]))
print('(you should expect to see -0.707107 -0.707107)')

input('Program paused. Press enter to continue.')

## =================== Part 3: Dimension Reduction ===================
#  You should now implement the projection step to map the data onto the
#  first k eigenvectors. The code will then plot the data in this reduced
#  dimensional space.  This will show you what the data looks like when
#  using only the corresponding eigenvectors to reconstruct it.
#
#  You should complete the code in projectData.m

print('Dimension reduction on example dataset.\n');

#  Plot the normalized dataset (returned from pca)
plt.close()
plt.scatter(X_norm[:,0], X_norm[:,1], s=75, facecolors='none', edgecolors='b')
plt.axis([-4, 3, -4, 3])
plt.gca().set_aspect('equal', adjustable='box')
plt.show(block=False)

#  Project the data onto K = 1 dimension
K = 1
Z = projectData(X_norm, U, K)
print('Projection of the first example: ', Z[0])
print('(this value should be about 1.481274)\n')

X_rec  = recoverData(Z, U, K)
print('Approximation of the first example: ', X_rec[0, 0], X_rec[0, 1])
print('(this value should be about  -1.047419 -1.047419)\n')

#  Draw lines connecting the projected points to the original points
plt.hold(True)
plt.scatter(X_rec[:, 0], X_rec[:, 1], s=75, facecolors='none', edgecolors='r')
for i in range(X_norm.shape[0]):
    drawLine(X_norm[i,:], X_rec[i,:], linestyle='--', color='k', linewidth=1)
plt.hold(False)

input('Program paused. Press enter to continue.')

## =============== Part 4: Loading and Visualizing Face Data =============
#  We start the exercise by first loading and visualizing the dataset.
#  The following code will load the dataset into your environment
#
print('Loading face dataset.\n');

#  Load Face dataset
mat = loadmat('ml/ex7/data/ex7faces.mat')
X = np.array(mat["X"])

#  Display the first 100 faces in the dataset
displayData(X[:100, :])

input('Program paused. Press enter to continue.')

## =========== Part 5: PCA on Face Data: Eigenfaces  ===================
#  Run PCA and visualize the eigenvectors which are in this case eigenfaces
#  We display the first 36 eigenfaces.
#
print('Running PCA on face dataset.\n(this mght take a minute or two ...)\n')

#  Before running PCA, it is important to first normalize X by subtracting
#  the mean value from each feature
X_norm, _, _ = featureNormalize(X)

#  Run PCA
U, S = pca(X_norm)

#  Visualize the top 36 eigenvectors found
displayData(U[:, :36].T)

input('Program paused. Press enter to continue.')

## ============= Part 6: Dimension Reduction for Faces =================
#  Project images to the eigen space using the top k eigenvectors
#  If you are applying a machine learning algorithm
print('Dimension reduction for face dataset.\n');

K = 100
Z = projectData(X_norm, U, K)

print('The projected data Z has a size of: ')
print('{:d} {:d}'.format(Z.shape[0], Z.shape[1]))

input('Program paused. Press enter to continue.')

## ==== Part 7: Visualization of Faces after PCA Dimension Reduction ====
#  Project images to the eigen space using the top K eigen vectors and
#  visualize only using those K dimensions
#  Compare to the original input, which is also displayed

print('Visualizing the projected (reduced dimension) faces.\n');

K = 100;
X_rec  = recoverData(Z, U, K)

# Display normalized data
plt.close()
plt.subplot(1, 2, 1)
displayData(X_norm[:100,:])
plt.title('Original faces')
plt.gca().set_aspect('equal', adjustable='box')

# Display reconstructed data from only k eigenfaces
plt.subplot(1, 2, 2)
displayData(X_rec[:100,:])
plt.title('Recovered faces')
plt.gca().set_aspect('equal', adjustable='box')

input('Program paused. Press enter to continue.')

## === Part 8(a): Optional (ungraded) Exercise: PCA for Visualization ===
#  One useful application of PCA is to use it to visualize high-dimensional
#  data. In the last K-Means exercise you ran K-Means on 3-dimensional
#  pixel colors of an image. We first visualize this output in 3D, and then
#  apply PCA to obtain a visualization in 2D.

plt.close()

# Re-load the image from the previous exercise and run K-Means on it
# For this to work, you need to complete the K-Means assignment first

# A = double(imread('bird_small.png'));
mat = loadmat('ml/ex7/data/bird_small.mat')
A = mat["A"]

# from ex7.py, part 4
A = A / 255.0
img_size = A.shape
X = A.reshape(img_size[0] * img_size[1], 3, order='F').copy()
K = 16
max_iters = 10
initial_centroids = kMeansInitCentroids(X, K)
centroids, idx = runkMeans(X, initial_centroids, max_iters)

#  Sample 1000 random indexes (since working with all the data is
#  too expensive. If you have a fast computer, you may increase this.
#  use flatten(). otherwise, Z[sel, :] yields array w shape [1000,1,2]
sel = np.floor(np.random.rand(1000, 1) * X.shape[0]).astype(int).flatten()

#  Setup Color Palette
palette = hsv(K)
colors = np.array([palette[int(i)] for i in idx[sel]])

#  Visualize the data and centroid memberships in 3D
fig1 = plt.figure(1)
ax = fig1.add_subplot(111, projection='3d')
ax.scatter(X[sel, 0], X[sel, 1], X[sel, 2], s=100, c=colors)
plt.title('Pixel dataset plotted in 3D. Color shows centroid memberships')
plt.show(block=False)

input('Program paused. Press enter to continue.\n\n')

## === Part 8(b): Optional (ungraded) Exercise: PCA for Visualization ===
# Use PCA to project this cloud to 2D for visualization

# Subtract the mean to use PCA
X_norm, _, _ = featureNormalize(X)

# PCA and project the data to 2D
U, S = pca(X_norm)
Z = projectData(X_norm, U, 2)

# Plot in 2D
fig2 = plt.figure(2)
plotDataPoints(Z[sel, :], idx[sel], K)
plt.title('Pixel dataset plotted in 2D, using PCA for dimensionality reduction');
plt.show(block=False)

input('Program paused. Press enter to continue.')


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Mon Feb 11 15:16:13 2019

@author: alankar
"""

import numpy as np
import matplotlib.pyplot as plt
from scipy.special.orthogonal import p_roots #Legendre Polynomial roots

def gauss_quad(func,a,b,n,*args):#Legendre
    [x,w] = p_roots(n+1)
    I_G = 0.5*(b-a)*np.sum(w*func(0.5*(b-a)*x+0.5*(b+a),*args))
    return I_G

a = [2,3,4]
x = np.linspace(0,5,100)

plt.figure(figsize=(13,10))
for A in a:
    plt.plot(x,x**(A-1)*np.exp(-x), label='a=%d'%A)

plt.legend(loc='best',prop={'size': 18})
plt.grid()
plt.title(r'$x^{a-1} \exp (-x)$',size=25,y=1.02)
plt.xlabel(r'$x$',size=22)
plt.ylabel(r'$f(x)$',size=22)
plt.tick_params(axis='both', which='major', labelsize=15)
plt.tick_params(axis='both', which='minor', labelsize=12)
plt.savefig('15.png')
plt.show()

def I (z,a):
    c = a-1
    x = c/(1/z-1)
    factor = c/(1-z)**2
    return np.exp((a-1)*np.log(x)-x)*factor

N = 100
gamma = gauss_quad(I,0.,1.,N,1.5)
print('Gamma(3/2) = %.3f'%gamma)

gamma = np.array([gauss_quad(I,0.,1.,N,a) for a in np.arange(2,11,1)])
for i,g in enumerate(gamma):
    print('Gamma(%d) = %d'%(i+2,g))

"""
Output:

Gamma(3/2) = 0.886
Gamma(2) = 1
Gamma(3) = 2
Gamma(4) = 6
Gamma(5) = 24
Gamma(6) = 120
Gamma(7) = 720
Gamma(8) = 5040
Gamma(9) = 40320
Gamma(10) = 362880

"""


import numpy as np

def sigmoid(z):
    g = np.zeros(z.size)

    # ===================== Your Code Here =====================
    # Instructions : Compute the sigmoid of each value of z (z can be a matrix,
    #                vector or scalar
    #
    # Hint : Do not import math
    g = 1/(1+np.exp(-z))

    return g


import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

def make_3d_grid():
    """Generate a 3d grid of evenly spaced points"""
    return np.mgrid[0:21, 0:21, 0:5]

def homo_coordinate(g):
    """Transform a numpy 3D meshgrid to 3D data points in homogeneous coordinates"""
    axes = np.vstack(list(map(np.ravel, g)))
    data_points = axes.T
    return np.insert(data_points, 3, 1, axis=1)

def plot_3d_grid(m3):
    """Plot a numpy array of 3D data points in homogeneous coordinates"""
    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d')
    x, y, z = m3.T[0], m3.T[1], m3.T[2]  # ignore the extra scale dimension
    ax.scatter(x, y, z, facecolor=(0, 0, 0, 0.5), marker='o', s=20, edgecolor='black')  # (R,G,B,A)
    ax.set_xticks([0, 5, 10, 15, 20, 25])
    ax.set_yticks([0, 5, 10, 15, 20, 25])
    ax.set_zlim(0, 15)
    ax.set_title("3D Grid of evenly spaced points")
    plt.show()

def rigid_transform(m3, theta, omega, phi, p, q, r):
    """
    Return the matrix (in homogeneous coordinates) of the rigid transform corresponding to
       1. rotation of angle theta around the x-axis
       2. rotation of angle omega around the y-axis
       3. rotation of angle phi around the z-axis
       4. translation of vector (p, q, r)
    """
    # 3D translation matrix
    t = np.array([[1, 0, 0, p],
                  [0, 1, 0, q],
                  [0, 0, 1, r],
                  [0, 0, 0, 1]])

    # 3D rotation matrix
    rxz = np.array([[np.cos(omega), 0, np.sin(omega), 0],
                    [0, 1, 0, 0],
                    [-np.sin(omega), 0, np.cos(omega), 0],
                    [0, 0, 0, 1]])

    ryz = np.array([[1, 0, 0, 0],
                    [0, np.cos(theta), -np.sin(theta), 0],
                    [0, np.sin(theta), np.cos(theta), 0],
                    [0, 0, 0, 1]])

    rxy = np.array([[np.cos(phi), -np.sin(phi), 0, 0],
                    [np.sin(phi), np.cos(phi), 0, 0],
                    [0, 0, 1, 0],
                    [0, 0, 0, 1]])

    T = t @ ryz @ rxz @ rxy

    m3_transformed = np.apply_along_axis(lambda row: T @ row.T, 1, m3)
    return m3_transformed

def affine_transform(m3, s, theta, omega, phi, p, q, r):
    """Add a scaling factor s on top of the rigid_transform()"""
    # 3D scaling matrix
    sx = sy = sz = s
    sm = np.array([[sx, 0, 0, 0],
                   [0, sy, 0, 0],
                   [0, 0, sz, 0],
                   [0, 0, 0, 1]])

    m3_transformed = rigid_transform(m3, theta, omega, phi, p, q, r)
    m3_scaled = np.apply_along_axis(lambda row: sm @ row.T, 1, m3_transformed)
    return m3_scaled

def run():
    """Test the transform functions on the 3D grid, plot to show the result"""
    data = homo_coordinate(make_3d_grid())
    plot_3d_grid(data)

    dat1 = rigid_transform(data, 0, 0, 0, 5, 5, 8)  # test translation
    dat2 = rigid_transform(data, np.pi/12, np.pi/12, -np.pi/9, 0, 0, 15)  # test rotation
    dat3 = affine_transform(data, 0.5, np.pi/2, -np.pi/4, 0, 30, 15, 30)  # test scaling

    # plot translation
    fig = plt.figure()
    ax = fig.gca(projection='3d')
    ax.scatter(data.T[0], data.T[1], data.T[2], facecolor=(0, 0, 0, 0.5), marker='o', s=20, edgecolor='black')
    ax.scatter(dat1.T[0], dat1.T[1], dat1.T[2], facecolor='#9400D3', marker='o', s=20, edgecolor='#9400D3', alpha=0.35)
    ax.set_xticks([0, 5, 10, 15, 20, 25])
    ax.set_yticks([0, 5, 10, 15, 20, 25])
    ax.set_zticks([0, 5, 10, 15, 20, 25])
    ax.set_zlim(0, 20)
    ax.set_title("test translation")
    plt.show()

    # plot rotation
    fig = plt.figure()
    ax = fig.gca(projection='3d')
    ax.scatter(data.T[0], data.T[1], data.T[2], facecolor=(0, 0, 0, 0.5), marker='o', s=20, edgecolor='black')
    ax.scatter(dat2.T[0], dat2.T[1], dat2.T[2], facecolor='tomato', marker='o', s=20, edgecolor='r', alpha=0.3)
    ax.set_xticks([0, 5, 10, 15, 20, 25])
    ax.set_yticks([0, 5, 10, 15, 20, 25])
    ax.set_zticks([0, 5, 10, 15, 20, 25])
    ax.set_zlim(0, 20)
    ax.set_title("test rotation")
    plt.show()

    # plot scaling
    fig = plt.figure()
    ax = fig.gca(projection='3d')
    ax.scatter(data.T[0], data.T[1], data.T[2], facecolor=(0, 0, 0, 0.5), marker='o', s=20, edgecolor='black')
    ax.scatter(dat3.T[0], dat3.T[1], dat3.T[2], facecolor='r', marker='o', s=20, edgecolor='r', alpha=0.1)
    ax.set_xticks([0, 5, 10, 15, 20, 25])
    ax.set_yticks([0, 5, 10, 15, 20, 25])
    ax.set_zticks([0, 5, 10, 15, 20, 25])
    ax.set_zlim(0, 20)
    ax.set_title("test scaling")
    plt.show()


import numpy as np

from Regularization import cost_l2_regularization

########################################################################################################################
"""
GROUP: Cost Function
	-Computes the cost function of your neural networks output based on it's output and the corresponding label

EXTERNAL FUNCTIONS:
					1)compute_cost: selects the method in which to compute cost, whether to have regularization at cost
					  	-FEATURE: hoping to add a cost_type function in the future and experiment with different costs
INTERNAL FUNCTIONS:
					1)cross_entropy_cost- computes cross-entropy error
					2)reg_utils_cost - cost function from reg_utils of deeplearning.ai appears similiar to cross-entropy
"""
########################################################################################################################

### EXTERNAL FUNCTIONS ###

def compute_cost(AL, Y, parameters,lambd,cost_type="cross_entropy_cost"):
	"""
	Implement the cost function based on what is passed in by cost type

	Arguments:
	AL -- post-activation, output of forward propagation, of shape (output size, number of examples)
	Y -- "true" labels vector, of shape (output size, number of examples)
	lambd -- the value used to compute l2 regularization

	Optional Arguments:
	cost_type -- the selected cost function to use.

	Returns:
	cost -- computed cost(loss) of the output
	"""

	#select the cost type based on cost_type passed in
	if(cost_type=="reg_utils_cost"):
		cost = reg_utils_cost(AL, Y)
	elif(cost_type=="cross_entropy_cost"):
		cost = cross_entropy_cost(AL, Y)
	else:
		print('ERROR: compute_cost - no cost function was selected')
		print("activation_type=" + activation_type)
		sys.exit(1)

	#if lambd is greater than zero compute l2 regularization for the cost function
	if lambd > 0:
		cost = cost + cost_l2_regularization(Y, parameters, lambd)

	return cost

### INTERNAL FUNCTIONS ###

def cross_entropy_cost(AL, Y):
	"""
	Implement the cross_entropy cost function

	Arguments:
	AL -- post-activation, output of forward propagation, of shape (output size, number of examples)
	Y -- "true" labels vector, of shape (output size, number of examples)

	Returns:
	cost -- cross-entropy cost
	"""
	m = Y.shape[1]
	# Compute loss from AL and Y.
 	try:
 		cost = -(1.0/m)*np.nansum(np.multiply(Y,np.log(AL)) + np.multiply((1-Y),np.log(1-AL+1e-8)))
 	except RuntimeWarning as w:
 		print("***cross_entropy_cost***")
 		print(w)
 		print(AL)
 		#print(sys.float_info.min)
 		print(Y)
	#cost = np.squeeze(cost)      # To make sure your cost's shape is what we expect (e.g. this turns [[17]] into 17).
	return cost

def reg_utils_cost(a3, Y):
	"""
	Implement the cost function

	Arguments:
	a3 -- post-activation, output of forward propagation
	Y -- "true" labels vector, same shape as a3

	Returns:
	cost - value of the cost function
	"""

	m = Y.shape[1]

	logprobs = np.multiply(-np.log(a3),Y) + np.multiply(-np.log(1 - a3), 1 - Y)
	logprobs[logprobs==np.inf]=0
	cost = 1./m * np.nansum(logprobs)

	return cost


import numpy as np

def calc_positions(a1, a2, a3, n1, n2, n3, basis):
    """
    Finish writing this function.
    The function must calculate the lattice point (R) and the absolute position
    of each atom in the basis, for this particular lattice point.

    Inputs:
    - a1, a2, a3: 3-component vector. The primitive lattice vectors
    - n1, n2, n3: integers. The lattice point coefficients.
    - basis: (n, 3) array. The positions of the atoms in the basis. Examples:
             "basis[0, 2]" refers to the first atoms z-component,
             "basis[2, 1]" refers to the third atoms y-component,
             "basis[1, 0]" refers to the second atoms x-component.

    Required outputs:
    - lattice_point: 3-component vector. The lattice vector for this specific
                     lattice point
    - atomic_positions: (n, 3) array. The absolute positions of the atoms in
                        the basis, for this specific lattice point.

    Sample inputs:
    a1 = [1, 0, 0],
    a2 = [0, 1, 0],
    a3 = [0, 0, 1],
    n1 = 2,
    n2 = 1,
    n3 = 3,
    basis = [[  0,   0,   0],
             [0.5, 0.5, 0.5]]

    Sample outputs:
    lattice_point = [2, 1, 3]
    atomic_positions = [[  2,   1,   3],
                        [2.5, 1.5, 3.5]]
    """

    # Mock up:
    # Calculate the current lattice point
    lattice_point = np.array((0, 0, 0))

    # Get the number of atoms in the basis
    n = basis.shape[0]

    # Calculate the atomic positions
    atomic_positions = np.zeros((n, 3))

    # Sample solution

    lattice_point = n1*a1 + n2*a2 + n3*a3
    # Reshape the lattice_point, so we can easily add it to the basis
    lattice_add = lattice_point.reshape(1, 3)
    atomic_positions = basis + lattice_add

    return lattice_point, atomic_positions

def calc_reciprocal_lattice(a1, a2, a3, m1, m2, m3):
    """
    Finish this function.
    The function must calculate the primitive lattice vectors for the
    reciprocal lattice, along with the specific reciprocal lattice point given
    by the coefficients.

    Inputs:
    - a1, a2, a3: 3-component vectors. Primitive lattice vectors of the direct
                  lattice.
    - m1, m2, m3: integers. Coefficients for the reciprocal lattice point.

    Outputs:
    - b1, b2, b3: 3-component vectors. Primitive lattice vectors of the
                  reciprocal lattice.
    - G: 3-component vector. The reciprocal lattice point.

    Sample inputs:
    a1 = [1, 0, 0]
    a2 = [0, 1, 0]
    a3 = [0, 0, 1]
    m1 = 3
    m2 = 1
    m3 = 0

    Sample outputs:
    b1 = [2*pi, 0, 0]
    b2 = [0, 2*pi, 0]
    b3 = [0, 0, 2*pi]
    G = [6*pi, 2*pi, 0]
    """

    # Mock up:
    b1 = np.array((0, 0, 0))
    b2 = np.array((0, 0, 0))
    b3 = np.array((0, 0, 0))
    G = np.array((0, 0, 0))

    # Sample Solution

    # Calculate the scale factor
    scale = (2 * np.pi) / np.dot(a1, np.cross(a2, a3))
    # Calculate the primitive lattice vectors
    b1 = scale * np.cross(a2, a3)
    b2 = scale * np.cross(a3, a1)
    b3 = scale * np.cross(a1, a2)
    # Calculate the lattice point
    G = m1*b1 + m2*b2 + m3*b3

    return b1, b2, b3, G


import numpy as np
import matplotlib.pyplot as plt

# 目标函数:y=x^2
def func(x):
    return np.square(x)

# 目标函数一阶导数也即是偏导数:dy/dx=2*x
def dfunc(x):
    return 2 * x

# Gradient Descent
def GD(x_start, df, epochs, lr):
    """
    梯度下降法。给定起始点与目标函数的一阶导函数，求在epochs次迭代中x的更新值
    :param x_start: x的起始点
    :param df: 目标函数的一阶导函数
    :param epochs: 迭代周期
    :param lr: 学习率
    :return: x在每次迭代后的位置（包括起始点），长度为epochs+1
    """
    xs = np.zeros(epochs+1)
    x = x_start
    xs[0] = x
    for i in range(epochs):
        dx = df(x)
        # v表示x要改变的幅度
        v = - dx * lr
        x += v
        xs[i+1] = x
    return xs

def demo_GD():
    # 演示如何使用梯度下降法GD()
    line_x = np.linspace(-5, 5, 100)
    line_y = func(line_x)

    x_start = -5
    epochs = 5

    lr = 0.3
    x = GD(x_start, dfunc, epochs, lr=lr)

    color = 'r'
    plt.plot(line_x, line_y, c='b')
    plt.plot(x, func(x), c=color, label='lr={}'.format(lr))
    plt.scatter(x, func(x), c=color, )
    plt.legend()
    plt.show()

if __name__ == "__main__":
    demo_GD()


# -*- coding: utf-8 -*-

import numpy as np
from sympy import *
from math import *

def regra_1_3_Simpson(fx,a,h,x):

    return ((h)*(fx.subs(x,a) + 4*fx.subs(x,a+h) + fx.subs(x,a+2*h)))/3

x = symbols('x')

fx = sympify(str(input("\n\n   Digite a função f(x): ")))
a = float(input("   Digite o começo do intervalo de integração: "))
b = float(input("   Digite o fim do intervalo de integração: "))
t = float(input("   Digite o modo de integração: (0 - sem repetição, 1 - com repetição): "))

if(t == 0):

    Ir = integrate(fx,(x,a,b))
    Ia = regra_1_3_Simpson(fx,a,abs(b-a)/2,x)
    print("   Integral aproximada: "+str(Ia)+"\n\n")

elif(t == 1):

   m = int(input("   Digite a quantidade m de intervalos: "))

   h = float(abs(b-a)/m)

   Et = -(h**5/90)*diff(diff(diff(diff(fx,x),x),x),x)

   Es = Et.subs(x,a)

   if(m%2 == 0 and m*h == (b-a)):
        Ia = 0
        for i in range(0,m-1,2):
            Es += Et.subs(x,a+h)
            Ia += regra_1_3_Simpson(fx,a,h,x)
            a += 2*h

        print("   Integral aproximada: "+str(Ia)+"\n\n")
   else:
        print("   Erro: m não é múltiplo de 2\n\n")


# numpy provides ufunc sinh(), cosh(), tanh()
# that takes values in radians and produce corresponding sinh, cosh, tanh values.
import numpy as np

y = np.array([np.pi/2, np.pi/3, np.pi/4, np.pi/5])
z = np.cosh(y)                    # cos hyperbolic function
print(z)

h = np.array([1.0, 0.2, 0.3, 0.4])
g = np.arcsinh(h)                            # finding angles from sinh, cosh, tanh values
print(g)

Python/Numpy/numpy 42 - ufunc hyperbollic function.py


import numpy as np

def mad(x, axis=None, no_scale=False):
    """
    median absolute deviation - scaled like a standard deviation

        mad = 1.4826*median(|x-median(x)|)

    Parameters
    ----------
    x: array-like
        array to take MAD of
    axis: int
        axis over which to take mode (default: None)
    no_scale: bool
        return MAD not scaled to match a Gaussian (default: False)

    Returns
    -------
    mad: float
        MAD of array x
    """
    mad = np.median(np.abs(x - np.median(x,axis=axis)),axis=axis)
    if no_scale:
        return mad
    else:
        return 1.4826*mad


#!/usr/bin/python3
import sys
import numpy as np
from scipy import optimize
from matplotlib import pyplot as plt

def catenary(x, a, p, q):
    """
    The catenary function in its most generic form.
    """
    return a * np.cosh((x-p)/a) + q

def f(a, h, v, L):
    """
    This function will be used by the Newton-Raphson algorithm to find
    a value for 'a'.
    """
    return 2 * a * np.sinh(0.5 * h/a) - np.sqrt(np.power(L, 2)-np.power(v, 2))

def fprime(a, h):
    """
    The derivative of f will also be used by the Newton-Raphson algorithm to find
    a value for 'a'.
    """
    return 2 * np.sinh(0.5 * h/a) - (h/a) * np.cosh(0.5 * h/a)

def nr_first_guess(ff, start_x, start_step, alpha):
    """
    This helper function finds a good enough first guess for the value of 'a', and
    allows the Newton-Raphson algorithm to converge.
    """
    xx = start_x
    step = start_step
    yy = ff(xx)
    yy_prev = yy

    while yy * yy_prev > 0:
        yy_prev = yy
        xx += step
        step *= alpha
        yy = ff(xx)

    # Backtrack a bit
    return xx - 0.5 * step / alpha

def get_params(p1, p2, L):
    """
    Return the curve parameters 'a', 'p', 'q' as well as the integration
    constant 'c', given the input parameters.
    """
    hv = p2 - p1
    m = p1 + p2
    def f_bind(a): return f(a, *hv, L)
    def fprime_bind(a): return fprime(a, hv[0])

    # Newton-Raphson algorithm to find a value for 'a'
    a0 = nr_first_guess(f_bind, 0.1, 0.01, 1.8)
    a = optimize.newton(f_bind, a0, fprime_bind)

    # Use our formulas to compute the rest
    p = 0.5 * (m[0] - a * np.log((L+hv[1])/(L-hv[1])))
    q = 0.5 * (m[1] - L / np.tanh(0.5 * hv[0]/a))
    c = -a * np.sinh((p1[0]-p)/a)

    return a, p, q, c

def arclen_remap(s_bar, L, a, p, c):
    """
    Map a normalized length fraction between 0 and 1 to an argument
    value to feed the catenary function
    """
    return a * np.arcsinh((s_bar * L - c) / a) + p

def main(argv):
    p1 = np.array([0.5, 0.6])
    p2 = np.array([4.1, 2.5])
    min_len = np.linalg.norm(p2-p1)

    fig, axs = plt.subplots(1, 2)

    for ii in range(1, 4):
        L = ii * min_len + 0.1
        a, p, q, c = get_params(p1, p2, L)
        # print(L, a, p, q, c)

        xp = np.linspace(p1[0], p2[0], 100)
        yp = catenary(xp, a, p, q)

        xps = np.linspace(p1[0], p2[0], 15)
        yps = catenary(xps, a, p, q)

        s_bar = np.linspace(0, 1, 15)
        xps_arclen = arclen_remap(s_bar, L, a, p, c)
        yps_arclen = catenary(xps_arclen, a, p, q)

        axs[0].plot(xp, yp, linewidth=2)
        axs[0].plot(xps, yps, linewidth=10, linestyle='solid', color='black', alpha=0.15)
        axs[0].scatter(xps, yps, linewidth=2, marker='+', s=100, color='red')

        axs[1].plot(xp, yp, linewidth=2)
        axs[1].plot(xps_arclen, yps_arclen, linewidth=10, linestyle='solid', color='black', alpha=0.15)
        axs[1].scatter(xps_arclen, yps_arclen, linewidth=2, marker='+', s=100, color='red')

    axs[0].set_xlabel('x')
    axs[0].set_ylabel('y')
    axs[0].set_title('Initial parametrization')
    axs[0].set_aspect('equal', adjustable='box')

    axs[1].set_xlabel('x')
    axs[1].set_ylabel('y')
    axs[1].set_title('Arc length parametrization')
    axs[1].set_aspect('equal', adjustable='box')

    plt.show()

if __name__ == '__main__':
    main(sys.argv[1:])


# -*- coding: utf-8 -*-
"""
Created on Sat Jun 23 21:20:02 2018

@author: SilverDoe
"""

#============== Trignometric functions ========================================
import numpy as np
a = np.array([0,30,45,60,90])

rad =a*np.pi/180 # converting to radian

deg = np.degrees(rad) # converting to degrees

# finding trigonmetric ratios
sine = np.sin(rad)
cosine = np.cos(rad)
tangent = np.tan(rad)

print(sine)
print(cosine)
print(tangent)

# finding inverse
sinv = np.arcsin(sine)
cinv = np.arccos(cosine)
tinv = np.arctan(tangent)

print(sinv)
print(cinv)
print(tinv)

#============= round, ceil, floor =============================================

import numpy as np
a = np.array([1.0,5.55, 123, 0.567, 25.532])

print(np.around(a))
print(np.around(a, decimals = 1))
print(np.around(a, decimals = -1))

print(np.floor(a))
print(np.ceil(a))

#============= Arithmetic Operations ==========================================

import numpy as np

a = np.arange(9, dtype = np.float_).reshape(3,3)
print('First array:')
print(a)
print('\n')

print('Second array:')
b = np.array([10,10,10])
print(b)
print('\n')

print('Add the two arrays:')
print(np.add(a,b))
print('\n')

print('Subtract the two arrays:')
print(np.subtract(a,b) )
print('\n')

print('Multiply the two arrays:')
print(np.multiply(a,b))
print('\n')

print('Divide the two arrays:')
print(np.divide(a,b))

#============== reciprocal, power, mod =====================================

# recoprocal
import numpy as np
a = np.array([1,2,3,4,5],dtype='float') # reciprocal of integers will be zero for any number greater than 1
print(np.reciprocal(a))

# power
import numpy as np
a = np.array([10,100,1000])
b = np.array([1,2,3])
print(np.power(a,b))

'''import numpy as np
a = np.array([1,2,3,4,5])
b = np.array([2,3,4,5,6])
print(np.power(a,2))
print(a,b)'''

# mod
import numpy as np
a = np.array([10,11,12,13,14,15])
b = np.array([2,3,4,5,6,7])
print(np.mod(a,2))
print(np.mod(a,b)) #np.remainder(a,b) also produces the same result

#============= operation on complex numbers ===================================

import numpy as np
a = np.array([-5.6j, 0.2j, 11. , 1+1j])

print(np.real(a))
print(np.imag(a))
print(np.conj(a))
print(np.angle(a))
print(np.angle(a, deg = True))

#============ Statistical functions ===========================================

import numpy as np
a = np.array([[3,7,5],[8,5,3],[2,87,9]])
print(a)
# return the minimum from the elements in the given array along the specified axis
print(np.amin(a))
print(np.amin(a, axis=0))
print(np.amin(a, axis=1))

# return the maximum from the elements in the given array along the specified axis
print(np.amax(a))
print(np.amax(a,axis=0))
print(np.amax(a,axis=1))

# ptp() returns the range (maximum-minimum) of values along an axis.
print(np.ptp(a)) # 87-2
print(np.ptp(a,axis=0)) # 8-2, 87-5, 9-3
print(np.ptp(a,axis=1)) # 7-3, 8-3, 87-2

import numpy as np
a = np.array([[90,91,94],[80,60,93],[70,90,60]])
print(a)

print(np.percentile(a,50))
print(np.percentile(a,50, axis=0))
print(np.percentile(a,50, axis=1))

# defined as the value separating the higher half of a data sample from the lower half
#The median is the 50th percentile: the point in the data where 50% of the data fall below that point, and 50% fall above it.
print(np.median(a))
print(np.median(a, axis=0))
print(np.median(a, axis=1))

print(np.mean(a))
print(np.mean(a, axis=0))
print(np.mean(a, axis=1))

#Weighted average is an average resulting from the multiplication of each component by a factor reflecting its importance.
wts = np.array([4,3,2])
print(np.average(a)) # same as mean when no weight is given
print(np.average(a,returned=True))# Returns the sum of weights, if the returned parameter is set to True.
print(np.average(a,weights = wts, axis=0))
print(np.average(a,weights = wts, axis=1))

# mean(abs(x - x.mean())**2)
print(np.var(a))
print(np.var(a, axis=0))
print(np.var(a, axis=1))

# sqrt(mean(abs(x - x.mean())**2))
print(np.std(a))
print(np.std(a, axis=0))
print(np.std(a, axis=1))


import numpy as np
import copy
try:
    from tqdm import tqdm
except ImportError:
    pass

eps = 1e-6

def jacobian(f, p, **kwargs):
    '''
    Finds the Jacobian matrix of the function f at the point p.

    Arguments
    ---------
    f : callable
    A function F^n -> F^m (F = R or C);
    takes in an n-length numpy array and returns an m-length numpy array.

    p : np.ndarray, shape (n,)
    The n-length numpy array around which we want to approximate the Jacobian.

    Returns
    -------
    jac : np.ndarray, shape (m, n)
    The Jacobian matrix (the matrix of first-order partial derivatives).
    '''
    n = len(p)
    center = f(p, **kwargs)
    rights = f(p + eps * np.eye(n), **kwargs)
    lefts = f(p - eps * np.eye(n), **kwargs)
    return .5 * ((rights.T - center) / eps + (center - lefts.T) / eps).T

class KFilter:
    '''
    Interface to a Kalman Filter or Extended Kalman Filter.

    Attributes
    ----------
    f : callable or np.ndarray
    The state-transition function or matrix.

    Q : np.ndarray, (s, s).
    The covariance of the state-transition model.

    h : callable or np.ndarray
    The measurement function or matrix.

    R : np.ndarray, (m, m)
    The covariance of the measurement model.

    s : int
    The dimension of the state.

    m : int
    The dimension of the measurement.

    state : np.ndarray, (s,)
    The state vector.

    P : np.ndarray, (s, s)
    The true state covariance.

    prev_P : np.ndarray, (s, s)
    The true state covariance one timestep ago, to check steady-state.

    steady_state : bool
    Whether the filter is in steady state (and can skip covariance updates).

    predict_chain : np.ndarray, (s, num_timesteps)
    The chain of state predictions made at each timestep.

    filter_chain : np.ndarray, (s, num_timesteps)
    The chain of MMSE state estimates at each timestep.

    Methods
    -------
    check_consistency()
    Checks whether dimensions match up.

    predict()
    Carries out a Kalman prediction.

    update(measurement:np.ndarray, (m,))
    Carries out a Kalman update.

    run_kf(signal:np.ndarray, (m, num_timesteps), progress:bool)
    Runs the filter in a predict-update cycle with the measurements in 'signal'.
    Does or doesn't use tqdm for progress based on 'progress'.

    reset()
    Resets the chains and the discovered covariance.
    '''
    def __init__(self, f, Q, h, R, state=None):
        self.f = f
        self.Q = Q
        self.h = h
        self.R = R
        self.s = Q.shape[0]
        self.m = R.shape[0]
        if state is None:
            self.state = np.zeros(self.s)
        else:
            self.state = state
        self.prev_P = np.zeros((self.s, self.s))
        self.P = np.zeros((self.s, self.s))
        self.steady_state = False
        self.predict_chain = np.empty((self.s, 0))
        self.filter_chain = np.empty((self.s, 0))
        self.check_consistency()

    def check_consistency(self):
        assert (isinstance(self.f, np.ndarray) and self.f.shape == (self.s, self.s)) or callable(self.f), "state transition is invalid"
        assert (isinstance(self.h, np.ndarray) and self.h.shape == (self.m, self.s)) or callable(self.h), "state measurement is invalid"
        assert isinstance(self.Q, np.ndarray) and self.Q.shape == (self.s, self.s), "model covariance is invalid"
        assert isinstance(self.R, np.ndarray) and self.R.shape == (self.m, self.m), "measurement covariance is invalid"

    def measure(self):
        if callable(self.h):
            return self.h(self.state)
        else:
            return self.h @ self.state

    def predict(self, **kwargs):
        self.prev_P = copy.deepcopy(self.P)
        if isinstance(self.f, np.ndarray):
            self.state = self.f @ self.state
            self.P = self.f @ self.P @ self.f + self.Q
        else:
            A = jacobian(self.f, copy.deepcopy(self.state), **kwargs)
            self.state = self.f(self.state)
            self.P = A @ self.P @ A + self.Q

    def update(self, measurement):
        if isinstance(self.h, np.ndarray):
            C = self.h
        else:
            C = jacobian(self.h, copy.deepcopy(self.state))
        innovation = measurement - self.measure()
        if not self.steady_state:
            self.K = self.P @ C.T @ np.linalg.inv(C @ self.P @ C.T + self.R)
            self.P -= self.K @ (C @ self.P)
            if np.allclose(self.P, self.prev_P):
                self.steady_state = True
        innovation = measurement - self.measure()
        self.state = self.state + self.K @ innovation

    def run_kf(self, signal, progress=True, **kwargs):
        predict_chain = np.zeros(len(signal), self.s)
        filter_chain = np.zeros(len(signal), self.s)
        if progress and 'tqdm' in globals():
            track_progress = tqdm
        else:
            track_progress = lambda x: x
        try:
            for i, m in enumerate(track_progress(signal)):
                self.predict(**kwargs)
                predict_chain[i] = self.state
                self.update(m, **kwargs)
                filter_chain[i] = self.state
        except KeyboardInterrupt:
            print("Filter terminated early, saving results.")
        for objchain, chain in zip([self.predict_chain, self.filter_chain], [predict_chain, filter_chain]):
            objchain = np.hstack((objchain, chain))

    def reset(self):
        self.predict_chain = np.empty((self.s, 0))
        self.filter_chain = np.empty((self.s, 0))
        self.steady_state = False
        self.prev_P = np.zeros_like(self.P)
        self.P = np.zeros_like(self.P)
        self.state = np.zeros_like(self.state)


import numpy as np

class Roots:

    """
    Instantiate a function.

    :param func: The lambda function to evaluate.
    :type func: Lambda function object

    :param dx: The derivative of the function passed to 'func'. Optional - Used only in Newton-Raphson method.
    :type dx: Lambda function object
    """
    def __init__(self, func, dx = None):
        self.func = func
        self.dx = dx

    def bisection(self, lbound: float, ubound: float, max_itn = 100, delta = 1e-6):
        """
        Finding the root of a function using bisection method.

        :param lbound: The lower bound of the range to evaluate.
        :type lbound: int

        :param ubound: The upper bound of the range to evaluate.
        :type ubound: int

        :param max_itn: The maximum number of iterations to evaluate.
        :type itn: int

        :param delta: The error threshhold.
        :type itn: float

        :return: Numpy array of iteration steps.
        :rtype: ndarray
        """

        a = lbound
        b = ubound
        itn = 0

        if self.func(a)*self.func(b) > 0:
            return 'Invalid upper and lower limits'

        else:
            while itn <= max_itn and abs(a-b) >= delta:
                x = (a + b)/2
                if itn == 0:
                    arr = [a, b, x, self.func(x)]
                else:
                    arr = np.vstack([arr, [a, b, x, self.func(x)]])
                if self.func(x) > 0:
                    b = x
                elif self.func(x) < 0:
                    a = x
                else:
                    return arr
                itn += 1

            return arr

    def incremental():
        return

    def false_position(self, x0: float, x1: float, max_itn = 100, delta = 1e-6):
        """
        Finding the root of a function using false position method.

        :param x0: The lower bound of the range to evaluate.
        :type lbound: int

        :param x1: The upper bound of the range to evaluate.
        :type ubound: int

        :param max_itn: The maximum number of iterations to evaluate.
        :type itn: int

        :param delta: The error threshhold.
        :type itn: float

        :return: Numpy array of iteration steps.
        :rtype: ndarray
        """

        a = x0
        b = x1
        lst = [a, b]
        i = 2
        itn = 0

        if self.func(a)*self.func(b) > 0:
            return 'Invalid upper and lower limits'

        else:
            while itn <= max_itn and abs(a - b) >= delta:
                lst.append(lst[0] - (lst[i - 1] - lst[0])*(self.func(lst[0]))/(self.func(lst[i-1]) - self.func(lst[0])))
                if itn == 0:
                    arr = [itn, i, lst[i-1], lst[i], self.func(lst[i])]
                else:
                    arr = np.vstack([arr, [itn, i, lst[i-1], lst[i], self.func(lst[i])]])
                a = lst[i - 2]
                b = lst[i - 1]
                itn += 1
                i += 1
            return arr

    def secant(self, x0: float, x1: float, max_itn = 100, delta = 1e-6):
        """
        Finding the root of a function using secant method.

        :param x0: The lower bound of the range to evaluate.
        :type lbound: int

        :param x1: The upper bound of the range to evaluate.
        :type ubound: int

        :param max_itn: The maximum number of iterations to evaluate.
        :type itn: int

        :param delta: The error threshhold.
        :type itn: float

        :return: Numpy array of iteration steps.
        :rtype: ndarray
        """

        a = x0
        b = x1
        lst = [a, b]
        i = 2
        itn = 0

        if self.func(a)*self.func(b) > 0:
            return 'Invalid upper and lower limits'

        else:
            while itn <= max_itn and abs(a - b) >= delta:
                lst.append(lst[i-1] - (self.func(lst[i - 1])*(lst[i-1] - lst[i-2]))/(self.func(lst[i-1]) - self.func(lst[i-2])))
                if itn == 0:
                    arr = [itn, i, lst[i-1], lst[i], self.func(lst[i])]
                else:
                    arr = np.vstack([arr, [itn, i, lst[i-1], lst[i], self.func(lst[i])]])
                a = lst[i - 2]
                b = lst[i - 1]
                itn += 1
                i += 1
            return arr
        return

    def newton_raphson(self, x0: float, max_itn = 100, delta = 0.0001):
        """
        Finding the root of a function using Newton-Raphson method.

        :param x0: The initial value to evaluate.
        :type x0: int

        :param max_itn: The maximum number of iterations to evaluate.
        :type itn: int

        :param delta: The error threshhold.
        :type itn: float

        :return: Numpy array of iteration steps.
        :rtype: ndarray
        """

        x = x0
        itn = 0

        while itn <= max_itn and abs(self.func(x)/self.dx(x)) >= delta:
            x = x - (self.func(x)/self.dx(x))

            if itn == 0:
                arr = [itn, x, self.func(x), self.dx(x), abs(self.func(x)/self.dx(x))]

            else:
                arr = np.vstack([arr, [itn, x, self.func(x), self.dx(x), abs(self.func(x)/self.dx(x))]])
            itn += 1

        return arr

class Solutions:
    """
        Instantiate a function.

        :param arr_a: The coefficient matrix.
        :type arr_a: Numpy ndarray

        :param arr_b: The constant matrix.
        :type arr_b: Numpy ndarray

    """

    def __init__(self, arr_a: np.ndarray, arr_b: np.ndarray):
        self.arr_a = np.array(arr_a).astype(float)
        self.arr_b = np.array(arr_b).astype(float)

    def gauss(self):
        """
            Finding the solution to a system of equations using Gauss Elimination.

            :return: The solution vector.
            :rtype: Numpy ndarray

        """
        a = self.arr_a
        b = self.arr_b
        n = len(b)
        x = [0]*n
        for k in range(0,n-1):
            for i in range(k+1,n):
                if a[i,k] != 0.0:
                    lam = a[i,k]/a[k,k]
                    a[i,k+1:n] = a[i,k+1:n] - lam*a[k,k+1:n]
                    b[i] = b[i] - lam*b[k]

        for k in range(n-1,-1,-1):
            b[k] = (b[k] - np.dot(a[k,k+1:n],b[k+1:n]))/a[k,k]

        return b

    def gauss_seidel(self, funcs: list, guess: list, delta = 0.01):

        """
            Gauss Seidel approximation of the solution to a system of equations.

            :param funcs: List of lambda functions of list.
            :type funcs: list

            :param guess: Vector of guessed values.
            :type guess: list

            :param delta: Tolerance value.
            :type delta: float

            :return: Iterated approximations of solution.
            :rtype: Numpy ndarray

        """

        funcs = funcs
        arr1 = guess
        arr2 = guess
        flag = False
        itn = 1

        while flag == False:

            temp_arr2 = []
            for i in range(len(funcs)):
                temp_arr = []
                for j in range(len(funcs)):
                    if i != j:
                        temp_arr.append(arr1[j])

                temp_arr2.append(funcs[i](temp_arr))

            arr2 = np.vstack([arr2, temp_arr2])
            arr1 = temp_arr2

            ls = [abs((arr2[itn, i] - arr2[itn-1, i])/arr2[itn, i]) for i in range(len(arr1))]

            for val in ls:
                if val < delta:
                    flag = True

            itn += 1

        return arr2

    def LUdecomp(self):
        """
            LU Decomposition of symmetric matrix.

            :param arr_a: The coefficient matrix.
            :type arr_a: Numpy ndarray

            :return: Decomposed L and U matrices.
            :rtype: Numpy ndarray, Numpy ndarray

        """

        a = self.arr_a
        n = len(a)
        for k in range(0,n-1):
            for i in range(k+1,n):
                if a[i,k] != 0.0:
                    lam = a [i,k]/a[k,k]
                    a[i,k+1:n] = a[i,k+1:n] - lam*a[k,k+1:n]
                    a[i,k] = lam

        L = np.array([[0.0]*3]*3)
        U = np.array([[0.0]*3]*3)

        for i in range(n):
            for j in range(n):
                if i > j:
                    L[i, j] = a[i, j]
                elif i == j:
                    L[i, j] = 1
                    U[i, j] = a[i, j]
                else:
                    U[i, j] = a[i, j]

        return L, U

    def LUsolve(self):
        """
            Finding the solution to a system of equations using LU Decomposition.

            :param arr_a: The coefficient matrix.
            :type arr_a: Numpy ndarray

            :param arr_b: The constant matrix.
            :type arr_b: Numpy ndarray

            :return: The solution vector.
            :rtype: Numpy ndarray

        """
        a = self.arr_a
        b = self.arr_b
        n = len(a)
        for k in range(1,n):
            b[k] = b[k] - np.dot(a[k,0:k],b[0:k])
        b[n-1] = b[n-1]/a[n-1,n-1]

        for k in range(n-2,-1,-1):
            b[k] = (b[k] - np.dot(a[k,k+1:n],b[k+1:n]))/a[k,k]

        return b

class Eigenvalue:
    def __init__(self, matrix, init_vector):
        self.matrix = matrix
        self.init_vector = init_vector

        return

    def jacobi(self):
        return

    def power(self, max_itn = 100, delta = 0.0001):
        eig_val = 0.0000
        eig_prev = 1.0000
        eig_vect = self.init_vector
        matrix = self.matrix
        itn = 1

        table = np.array(['itn', 'eig_val', 'eig_vect'], dtype = object)

        while abs(eig_val - eig_prev) >= delta and itn <= max_itn:
            eig_vect = np.dot(matrix, eig_vect)

            eig_prev = eig_val

            eig_val = abs(eig_vect).max()
            eig_vect = eig_vect/eig_vect.max()

            table = np.vstack([table, [itn, eig_val, eig_vect]])

            itn += 1

        return table

class Interpolation:

    """
    Instantiate a function.

    :param arr_a: Known 'x' values.
    :type arr_a: Numpy ndarray or list or series

    :param arr_b: Known 'y' values.
    :type arr_b: Numpy ndarray or list or series

    """
    def __init__(self, x_val, y_val):
        self.x_val = x_val
        self.y_val = y_val

    def lagrange(self, x: float) -> float:

        """
            Implementation of Lagrange's interpolation formula.

            :param x: The 'x' value in question.
            :type x: float

            :return: Interpolated 'y' value.
            :rtype: float

        """

        result = 0.0
        for i in range(len(self.x_val)):
            temp = self.y_val[i]

            for j in range(len(self.x_val)):
                if i != j:
                    temp = temp* (x - self.x_val[j])/(self.x_val[i] - self.x_val[j])

            result += temp

        return result

    def newton_dd(self, x_new: float, forward : bool = True):
        """
            Implementation of Newton's divided difference algorithm.

            :param x_new: The 'x' value in question.
            :type x_new: float

            :param forward: Selection of forward or backward algorithm
            :type forward: bool

            :return: Interpolated 'y' value.
            :rtype: float

        """

        x = self.x_val
        y = self.y_val

        n = len(y)
        coef = np.zeros([n, n])
        coef[:,0] = y
        for j in range(1,n):
            for i in range(n-j):
                coef[i][j] = (coef[i+1][j-1]-coef[i][j-1])/(x[i+j]-x[i])

        if forward:
            cf = coef[0, :]
        else:
            cf = np.flipud(coef).diagonal()

        k = 1
        p = 0
        for i in range(len(cf)):
            p += cf[i]*k
            k *= x_new - x[i]

        return coef, p

    def differentiate(self, x_new: float, h : float, second :bool = False, forward: bool = True):

        """
            Implementation of Newton's forward and backward difference differentiation algorithms.

            :param x_new: The 'x' value in question.
            :type x_new: float

            :param h: The h value - the step size
            :type h: float

            :param second: Selection of order of derivation
            :type second: bool

            :param forward: Selection of forward or backward algorithm
            :type forward: bool

            :return: Interpolated 'y' value.
            :rtype: float

        """

        x = self.x_val
        y = self.y_val

        n = len(y)

        arr = [0, 0.0, 0.9999999999217246, 0.9999999999746642, 0.9166666666919701, 0.8333333333563364, 0.7611111111427086, 0.7000000000510863, 0.6482142857779337, 0.6039682540329495, 0.5657936508246969, 0.5325396825532482, 0.5033128908190265, 0.47741702740370173, 0.45430482214953094, 0.4335416435253855, 0.4147786241242193, 0.39773282270807536, 0.3821725024909123, 0.36790611347003926, 0.35477396569519837]

        if forward:

            x_index = x.index(x_new)

            coef = np.zeros([n, n])
            coef[:,0] = y
            for j in range(1,n):
                for i in range(n-j):
                    coef[i][j] = (coef[i+1][j-1]-coef[i][j-1])

            delt = coef[x_index,:]
            val = 0

            if second:
                for i in range(2, n):
                    if i%2 == 0:
                        val += arr[i]*delt[i]
                    else:
                        val -= arr[i]*delt[i]
                dy = (1/h**2)*val

            else:

                i = 1
                while i<n:
                    if i%2 == 0:
                        val -= (1/i)*delt[i]
                    else:
                        val += (1/i)*delt[i]
                    i += 1
                dy = (1/h)*val

        else:

            x.reverse()
            y.reverse()

            x_index = x.index(x_new)

            coef = np.zeros([n, n])
            coef[:,0] = y
            for j in range(1,n):
                for i in range(n-j):
                    coef[i][j] = -(coef[i+1][j-1]-coef[i][j-1])

            delt = coef[x_index,:]
            val = 0

            if second:
                for i in range(2, n):
                    val += arr[i]*delt[i]
                dy = (1/h**2)*val

            else:

                i = 1
                while i<n:
                   val += (1/i)*delt[i]
                   i += 1
                dy = (1/h)*val

        return dy

    def trapezoidal(self, h : float):
        """
        Implementation of Trapezoidal method of Integration

        :param h: The 'h' value - step size.
        :type h: float

        """
        y = self.y_val

        return (h/2)*(2*sum(y[1: -1]) + y[0] + y[-1])

    def simpsons(self, h: float, three_eighths : bool= False ):
        """
        Implementation of Simpson's methods of Integration

        :param h: The 'h' value - step size.
        :type h: float

        :param three_eights: True - Simpson's 3/8 method, False(Default) - Simspson's 1/3 method
        :type three_eights: bool

        """
        y = self.y_val
        f = y.pop(0)
        l = y.pop(-1)

        if ~three_eighths:

            return (h/3)*(f + l + 4*sum([y[i] for i in range(len(y)) if i%2 == 0]) + 2*sum([y[i] for i in range(len(y)) if i%2 == 1]))

        else:
            return (3*h/8)*(f + l + 3*sum([y[i] for i in range(len(y)) if (i + 1)%3 != 0]) + 2*sum([y[i] for i in range(len(y)) if (i + 1)%3 == 0]))

class O_D_E:
    """
    Instantiate a function.

    :param func: Function of x and y
    :type func: Lambda function

    :param x_init: Initial x value
    :type x_init: float

    :param y_init: Initial y value
    :type y_init: float

    """
    def __init__(self, func, x_init: float, y_init: float):
        self.func = func
        self.x_init = x_init
        self.y_init = y_init

    def euler(self, x_new : float, h: float):

        """
    Implementation of Euler's method for solving Initial Value Problem.

    :param x_new: The x value as input
    :type x_new: float

    :param h: The h value - step size.
    :type h: float

    """

        fun = self.func
        x = []
        y = []

        x_val = self.x_init
        y_val = self.y_init

        while x_val < x_new:
            y_val += h*fun(x_val, y_val)
            x_val += h
            x.append(x_val)
            y.append(y_val)

        return list(zip(x, y))

    def mod_euler(self, x_new : float, h: float):

        """
    Implementation of Modified Euler's method for solving Initial Value Problem.

    :param x_new: The x value as input
    :type x_new: float

    :param h: The h value - step size.
    :type h: float

    """

        fun = self.func
        x = []
        y = []

        x_val = self.x_init
        y_val = self.y_init

        while x_val < x_new:
            y_val += h*fun(x_val + h/2, y_val + (h/2)*fun(x_val, y_val))
            x_val += h
            x.append(x_val)
            y.append(y_val)

        return list(zip(x, y))

    def runge_kutta2(self, x_new : float, h: float):

        """
    Implementation of Second Order Runge-Kutta method for solving Initial Value Problem.

    :param x_new: The x value as input
    :type x_new: float

    :param h: The h value - step size.
    :type h: float

    """

        fun = self.func
        x = []
        y = []

        x_val = self.x_init
        y_val = self.y_init

        while x_val < x_new:

            k1 = h*fun(x_val, y_val)
            k2 = h*fun(x_val + h, y_val + k1)

            y_val += (k1 + k2)/2
            x_val += h
            x.append(x_val)
            y.append(y_val)

        return list(zip(x, y))

    def runge_kutta3(self, x_new : float, h: float):

        """
    Implementation of Third Order Runge-Kutta method for solving Initial Value Problem.

    :param x_new: The x value as input
    :type x_new: float

    :param h: The h value - step size.
    :type h: float

    """

        fun = self.func
        x = []
        y = []

        x_val = self.x_init
        y_val = self.y_init

        while x_val < x_new:

            k1 = h*fun(x_val, y_val)
            k2 = h*fun(x_val + h/2, y_val + k1/2)
            k3 = h*fun(x_val + h, y_val + 2*k2 - k1)

            y_val += (1/6)*(k1 + 4*k2 + k3)
            x_val += h
            x.append(x_val)
            y.append(y_val)

        return list(zip(x, y))

    def runge_kutta4(self, x_new : float, h: float):

        """
    Implementation of Fourth Order Runge-Kutta method for solving Initial Value Problem.

    :param x_new: The x value as input
    :type x_new: float

    :param h: The h value - step size.
    :type h: float

    """

        fun = self.func
        x = []
        y = []

        x_val = self.x_init
        y_val = self.y_init

        while x_val < x_new:

            k1 = h*fun(x_val, y_val)
            k2 = h*fun(x_val + h/2, y_val + k1/2)
            k3 = h*fun(x_val + h/2, y_val + k2/2)
            k4 = h*fun(x_val + h, y_val + k3)

            y_val += (1/6)*(k1 + 2*k2 + 2*k3 + k4)
            x_val += h
            x.append(x_val)
            y.append(y_val)

        return list(zip(x, y))


from scipy import array
from scipy.special import legendre

def roots_legendre(n):
    """
    Computes the sample points and weights for Gauss-Legendre quadrature.
    The sample points are the roots of the n-th degree Legendre polynomial
    `P_n(x)`.  These sample points and weights correctly integrate
    polynomials of degree `2n - 1` or less over the interval
    `[-1, 1]` with weight function `f(x) = 1.0`.

    Parameters
    ----------
    n : int
        quadrature order

    Returns
    -------
    x : ndarray
        Sample points
    w : ndarray
        Weights
    """
    p = legendre(n + 1)
    x = legendre(n).roots
    w = 2 * (1 - x ** 2) / ((n + 1) ** 2 * p(x) ** 2)
    temp = list(zip(x, w))
    temp.sort()
    x, w = array([i for i, _ in temp]), array([i for _, i in temp])
    return x, w