# Function fitting using gradient descent.
# This works by first getting random inputs, and then minmizing the amount of loss by moving the m and b variables in direction of least loss.

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

fig = plt.figure()
# ax = fig.add_subplot(111, projection='3d')

class OptimizerOutput:
	def __init__(self, answers, loss, meta, inputs, func):
		self.answers = answers
		self.loss = loss
		self.history = meta[0]
		self.iterations = meta[1]
		self.inputs = inputs
		self.func = func
	def plotLossMovement(self):
		ax.plot(self.history[0], self.history[1], self.history[2])
		plt.show()
	def plotFitting(self):
		for i in range(len(self.history[0])):
			if i % 3 == 0:
				vals = self.func(self.history[0][i], self.history[1][i], self.inputs[0])
				plt.plot(self.inputs[0], vals)
		plt.scatter(self.inputs[0], self.inputs[1])
		plt.show()
	def plotLossChart(self):
		plt.plot([i for i in range(len(self.history[2]))], self.history[2])
		plt.show()

class MinimizerOutput:
	def __init__(self, answers, loss, meta, func, constraints, finished):
		self.answers = answers
		self.loss = loss
		self.history = meta[0]
		self.iterations = meta[1]
		self.func = func
		self.constraints = constraints
		self.finished = finished
	def plotLossChart(self):
		plt.plot([i for i in range(len(self.history[2]))], self.history[2])
		plt.show()

def frange(_min, _max, step=1):
	output = []
	current = _min
	while current < _max:
		output.append(current)
		current += step
	return np.array(output)

def stretch(X, y, amount):
	return X/amount, y/amount

def linear(x, y, X):
	return x * X + y

def quadratic(x, y, X):
	return x * X ** 2 + y * X

def loss(xval, yval, X, y, func):
	return np.sum(np.square(func(xval, yval, X) - y)) / len(X)

def visualizeLoss(X, y, func, lossFunc=loss, xmin=-10, ymin=-10, xmax=10, ymax=10, dx=1, dy=1, vis=True):
	xs = np.ravel([[i] * len(frange(xmin, xmax, dx)) for i in frange(ymin, ymax, dy)])
	ys = np.array(list(frange(ymin, ymax, dy)) * len(frange(xmin, xmax, dx)))
	zs = []
	for xp, yp in zip(xs, ys):
		zs.append(lossFunc(xp, yp, X, y, func))
	if vis:
		ax.scatter(xs, ys, zs)
		plt.show()
	else:
		return xs, ys, zs

def visualizeFunction(func, xmin=-10, ymin=-10, xmax=10, ymax=10, dx=1, dy=1, vis=True):
	xs = np.ravel([[i] * len(frange(xmin, xmax, dx)) for i in frange(ymin, ymax, dy)])
	ys = np.array(list(frange(ymin, ymax, dy)) * len(frange(xmin, xmax, dx)))
	zs = []
	for xp, yp in zip(xs, ys):
		zs.append(func(xp, yp))
	if vis:
		ax.scatter(xs, ys, zs)
		plt.show()
	else:
		return xs, ys, zs

def visualize2D(func, xmin=-10, xmax=10, dx=1, vis=True):
	xs = frange(xmin, xmax, dx)
	ys = [func(i) for i in xs]
	if vis:
		plt.plot(xs, ys)
		plt.show()
	else:
		return xs, ys

def _findMinimalLossDirection(lossFunc, currentX, currentY, dx, dy, currentLoss, func, X, y):
	# Check all 8 possibilities by taking steps in dx and dy amounts
	lossChanges = (lossFunc(currentX + dx, currentY + dy, X, y, func) - currentLoss, # +dx, +dy
	lossFunc(currentX + dx, currentY - dy, X, y, func) - currentLoss, # +dx, -dy
	lossFunc(currentX + dx, currentY, X, y, func) - currentLoss, # +dx, 0
	lossFunc(currentX - dx, currentY + dy, X, y, func) - currentLoss, # -dx, +dy
	lossFunc(currentX - dx, currentY - dy, X, y, func) - currentLoss, # -dx, -dy
	lossFunc(currentX - dx, currentY, X, y, func) - currentLoss, # -dx, 0
	lossFunc(currentX, currentY + dy, X, y, func) - currentLoss, # 0, +dy
	lossFunc(currentX, currentY - dy, X, y, func) - currentLoss) # 0, -dy
	# Find minimum loss value
	minLoss = min(lossChanges)
	# Return direction for x and y
	possibleDirs = ((dx, dy), (dx, -dy), (dx, 0), (-dx, dy), (-dx, -dy), (-dx, 0), (0, dy), (0, -dy))
	directionToGo = possibleDirs[lossChanges.index(minLoss)]
	return directionToGo

def fit(lossFunc, X, y, func=linear, startx=np.random.rand(1)[0], starty=np.random.rand(1)[0], dx=0.01, dy=0.01, maxIter=3000, lossThresh=0.01, calculateMovement=True, verbose=False):
	# Starting values
	iteration = 0
	currentX = startx
	currentY = starty
	currentLoss = lossFunc(currentX, currentY, X, y, func)
	historyX = []
	historyY = []
	historyZ = []

	while iteration < maxIter:
		if verbose:
			print('Iteration:', iteration, 'X:', currentX, 'Y:', currentY, 'Loss:', currentLoss)
		if iteration % 20 == 0:
			historyX.append(currentX)
			historyY.append(currentY)
			historyZ.append(currentLoss)
		# Take step in x and y direction, and calculate loss increase/decrease (not slope because we don't care about magnitude), then find direction to move in
		if calculateMovement:
			movementAmountX = np.sqrt(abs(currentLoss)) * dx / 2
			movementAmountY = np.sqrt(abs(currentLoss)) * dy / 2
			moveDir = _findMinimalLossDirection(lossFunc, currentX, currentY, movementAmountX, movementAmountY, currentLoss, func, X, y)
		else:
			moveDir = _findMinimalLossDirection(lossFunc, currentX, currentY, dx, dy, currentLoss, func, X, y)
		# Move in that direction
		newX = currentX + moveDir[0]
		newY = currentY + moveDir[1]
		currentX = newX
		currentY = newY
		# Recalculate loss
		newLoss = lossFunc(currentX, currentY, X, y, func)
		currentLoss = newLoss
		if currentLoss <= lossThresh:
			break
		iteration += 1

	return OptimizerOutput((currentX, currentY), currentLoss, ((historyX, historyY, historyZ), iteration), (X, y), func)

def findAnswer(output, X, func=linear):
	return func(output.answers[0], output.answers[1], X)

def _findMinimizeDirection(x, y, function, currentLoss, dx, dy, restriction, restrictedMovements=()):
	# Check all 8 possibilities
	xyChanges = [(dx, dy), (dx, -dy), (dx, 0), (-dx, dy), (-dx, -dy), (-dx, 0), (0, dy), (0, -dy)]
	for restrictedMovement in restrictedMovements:
		xyChanges.remove(restrictedMovement)
	if len(xyChanges) == 0:
		return None
	# Calculate x and y values
	lossChanges = []
	for xyChange in xyChanges:
		lossChanges.append(function(x + xyChange[0], y + xyChange[1]))
	# Find minimum loss value
	minLoss = min(lossChanges)
	# Return direction for x and y
	directionToGo = xyChanges[lossChanges.index(minLoss)]
	# Make sure constraints work
	if restriction != None:
		if restriction(x+directionToGo[0], y+directionToGo[1], minLoss) == False:
			return _findMinimizeDirection(x, y, function, currentLoss, dx, dy, restriction, tuple(list(restrictedMovements) + [directionToGo]))

	return directionToGo

def minimize(function, goal, restriction=None, startx=np.random.rand(1)[0], starty=np.random.rand(1)[0], dx=0.01, dy=0.01, maxIter=3000, calculateMovement=True, verbose=False):
	# Starting values
	iteration = 0
	currentX = startx
	currentY = starty
	currentLoss = function(currentX, currentY)
	historyX = []
	historyY = []
	historyZ = []

	while iteration < maxIter:
		if verbose:
			print('Iteration:', iteration, 'X:', currentX, 'Y:', currentY, 'Loss:', currentLoss)
		if iteration % 20 == 0:
			historyX.append(currentX)
			historyY.append(currentY)
			historyZ.append(currentLoss)
		# Take step in x and y direction, and calculate loss increase/decrease (not slope because we don't care about magnitude), then find direction to move in
		if calculateMovement:
			movementAmountX = np.sqrt(abs(currentLoss)) * dx / 2
			movementAmountY = np.sqrt(abs(currentLoss)) * dy / 2
			moveDir = _findMinimizeDirection(currentX, currentY, function, currentLoss, movementAmountX, movementAmountY, restriction)
		else:
			moveDir = _findMinimizeDirection(currentX, currentY, function, currentLoss, dx, dx, restriction)

		if moveDir == None:
			return MinimizerOutput((currentX, currentY), currentLoss, ((historyX, historyY, historyZ), iteration), function, restriction, False)
		# Move in that direction
		newX = currentX + moveDir[0]
		newY = currentY + moveDir[1]
		currentX = newX
		currentY = newY
		# Recalculate loss
		newLoss = function(currentX, currentY)
		currentLoss = newLoss
		if currentLoss <= goal:
			break
		iteration += 1

	return MinimizerOutput((currentX, currentY), currentLoss, ((historyX, historyY, historyZ), iteration), function, restriction, True)

def _findMaximizeDirection(x, y, function, currentLoss, dx, dy, restriction, restrictedMovements=()):
	# Check all 8 possibilities
	xyChanges = [(dx, dy), (dx, -dy), (dx, 0), (-dx, dy), (-dx, -dy), (-dx, 0), (0, dy), (0, -dy)]
	for restrictedMovement in restrictedMovements:
		xyChanges.remove(restrictedMovement)
	if len(xyChanges) == 0:
		return None
	# Calculate x and y values
	lossChanges = []
	for xyChange in xyChanges:
		lossChanges.append(function(x + xyChange[0], y + xyChange[1]))
	# Find minimum loss value
	minLoss = max(lossChanges)
	# Return direction for x and y
	directionToGo = xyChanges[lossChanges.index(minLoss)]
	# Make sure constraints work
	if restriction != None:
		if restriction(x+directionToGo[0], y+directionToGo[1], minLoss) == False:
			return _findMaximizeDirection(x, y, function, currentLoss, dx, dy, restriction, tuple(list(restrictedMovements) + [directionToGo]))

	return directionToGo

def maximize(function, goal, restriction=None, startx=np.random.rand(1)[0], starty=np.random.rand(1)[0], dx=0.01, dy=0.01, maxIter=3000, calculateMovement=True, verbose=False):
	# Starting values
	iteration = 0
	currentX = startx
	currentY = starty
	currentLoss = function(currentX, currentY)
	historyX = []
	historyY = []
	historyZ = []

	while iteration < maxIter:
		if verbose:
			print('Iteration:', iteration, 'X:', currentX, 'Y:', currentY, 'Loss:', currentLoss)
		if iteration % 20 == 0:
			historyX.append(currentX)
			historyY.append(currentY)
			historyZ.append(currentLoss)
		# Take step in x and y direction, and calculate loss increase/decrease (not slope because we don't care about magnitude), then find direction to move in
		if calculateMovement:
			movementAmountX = np.sqrt(abs(currentLoss)) * dx / 2
			movementAmountY = np.sqrt(abs(currentLoss)) * dy / 2
			moveDir = _findMaximizeDirection(currentX, currentY, function, currentLoss, movementAmountX, movementAmountY, restriction)
		else:
			moveDir = _findMaximizeDirection(currentX, currentY, function, currentLoss, dx, dy, restriction)

		if moveDir == None:
			return MinimizerOutput((currentX, currentY), currentLoss, ((historyX, historyY, historyZ), iteration), function, restriction, False)
		# Move in that direction
		newX = currentX + moveDir[0]
		newY = currentY + moveDir[1]
		currentX = newX
		currentY = newY
		# Recalculate loss
		newLoss = function(currentX, currentY)
		currentLoss = newLoss
		if currentLoss >= goal:
			break
		iteration += 1

	return MinimizerOutput((currentX, currentY), currentLoss, ((historyX, historyY, historyZ), iteration), function, restriction, True)

def _findMinimizeDirection2D(x, function, currentLoss, dx):
	xChanges = [x + dx, x - dx]
	lossChanges = [function(i) for i in xChanges]
	directionToGo = xChanges[lossChanges.index(min(lossChanges))]
	return directionToGo

def minimize2D(func, goal, startx=np.random.rand(1)[0], dx=0.01, maxIter=3000, calculateMovement=True, verbose=False):
	iteration = 0
	currentX = startx
	currentLoss = func(currentX)
	historyX = []
	historyY = []

	while iteration < maxIter:
		if verbose:
			print('Iteration:', iteration, 'X:', currentX, 'Loss:', currentLoss)
		if iteration % 20 == 0:
			historyX.append(currentX)
			historyY.append(currentLoss)
		# Take step in x direction, and calculate loss increase/decrease (not slope because we don't care about magnitude), then find direction to move in
		if calculateMovement:
			movementAmountX = np.sqrt(abs(currentLoss)) * dx / 2
			moveDir = _findMinimizeDirection2D(currentX, func, currentLoss, movementAmountX)
		else:
			moveDir = _findMinimizeDirection2D(currentX, func, currentLoss, dx)
		# Move in that direction
		newX = moveDir
		currentX = newX
		# Recalculate loss
		newLoss = func(currentX)
		currentLoss = newLoss
		if currentLoss <= goal:
			break
		iteration += 1

	return MinimizerOutput((currentX), currentLoss, ((historyX, historyY), iteration), func, None, True)

def _findMaximizeDirection2D(x, function, currentLoss, dx):
	xChanges = [x + dx, x - dx]
	lossChanges = [function(i) for i in xChanges]
	directionToGo = xChanges[lossChanges.index(max(lossChanges))]
	return directionToGo

def maximize2D(func, goal, startx=np.random.rand(1)[0], dx=0.01, maxIter=3000, calculateMovement=True, verbose=False):
	iteration = 0
	currentX = startx
	currentLoss = func(currentX)
	historyX = []
	historyY = []

	while iteration < maxIter:
		if verbose:
			print('Iteration:', iteration, 'X:', currentX, 'Loss:', currentLoss)
		if iteration % 20 == 0:
			historyX.append(currentX)
			historyY.append(currentLoss)
		# Take step in x direction, and calculate loss increase/decrease (not slope because we don't care about magnitude), then find direction to move in
		if calculateMovement:
			movementAmountX = np.sqrt(abs(currentLoss)) * dx / 2
			moveDir = _findMaximizeDirection2D(currentX, func, currentLoss, movementAmountX)
		else:
			moveDir = _findMaximizeDirection2D(currentX, func, currentLoss, dx)
		# Move in that direction
		newX = moveDir
		currentX = newX
		# Recalculate loss
		newLoss = func(currentX)
		currentLoss = newLoss
		if currentLoss >= goal:
			break
		iteration += 1

	return MinimizerOutput((currentX), currentLoss, ((historyX, historyY), iteration), func, None, True)

# Testing the system

if __name__ == "__main__":
	pass

	# # Example 1 - Linear functions for finding the correlation of an mx + b function.
	# X = np.array([1, 2])
	# y = np.array([4, 7])
	#
	# # visualizeLoss(X, y, linear)
	# output = fit(loss, X, y, startx=np.random.rand(1)[0], starty=np.random.rand(1)[0], dx=0.1, dy=0.1, maxIter=3000, lossThresh=0.000001, verbose=False)
	# print(output.answers, output.loss, output.iterations)
	# output.plotLossChart()

	# # Example 2 - Quadratic functions for finding the correlation of an mx^2 + bx function.
	# X = np.array(frange(-5, 5, 0.1))
	# y = np.array(quadratic(3, 2, X))
	#
	# # visualizeLoss(X, y, quadratic)
	# output = fit(loss, X, y, func=quadratic, startx=np.random.rand(1)[0], starty=np.random.rand(1)[0], dx=0.01, dy=0.01, maxIter=3000, lossThresh=0.005, verbose=False)
	# print(output.answers, output.loss, output.iterations)
	# output.plotFitting()

	# # Example 3 - Custom Optimizations using a^2 + b^2 = 25 and a + b = 7
	# def customLoss(xval, yval, X=None, y=None, func=None):
	# 	lossA = (xval ** 2 + yval ** 2) - 25
	# 	lossB = (xval + yval) - 7
	# 	return np.sum(np.square(np.array([lossA, lossB]))) / 2
	#
	# visualizeLoss(None, None, lossFunc=customLoss)
	# output = fit(None, None, None, maxIter=4000, lossThresh=0.0001)
	# print(output.answers, output.loss, output.iterations)
	# output.plotLossChart()

	# # Example 4 - Function minimization using Himmelblau's Function (https://en.wikipedia.org/wiki/Himmelblau%27s_function)
	# def himmelblau(x, y):
	# 	return (x ** 2 + y - 11) ** 2 + (x + y ** 2 - 7) ** 2
	#
	# visualizeFunction(himmelblau, -5, -5, 5, 5, 0.5, 0.5)
	# output = minimize(himmelblau, 0.01)
	# print(output.answers, output.loss, output.iterations, output.finished)
	# output.plotLossChart()

	# # Example 5 - Function maximization using -x^2 - y^2 + 100
	# def maxFunc(x, y):
	# 	return -(x-1)**2 - y**2 + 100
	#
	# visualizeFunction(maxFunc, -5, -5, 5, 5, 0.5, 0.5)
	# output = maximize(maxFunc, 99.9999999)
	# print(output.answers, output.loss, output.iterations, output.finished)
	# output.plotLossChart()

	# # Example 6 - Function minimization with non-differentiable equations
	# def minFunc(x, y):
	# 	return abs(x - 1) + abs(y + 1)
	#
	# visualizeFunction(minFunc)
	# output = minimize(minFunc, 0.01)
	# print(output.answers, output.loss, output.iterations, output.finished)
	# output.plotLossChart()

	# # Example 7 - Function minimization using the McCormick function (https://www.sfu.ca/~ssurjano/mccorm.html)
	# def mccormick(x, y):
	# 	return math.sin(x + y) + (x - y) ** 2 - 1.5 * x + 2.5 * y + 1
	#
	# visualizeFunction(mccormick, -5, -5, 5, 5, 0.5, 0.5)
	# output = minimize(mccormick, -1.9133)
	# print(output.answers, output.loss, output.iterations, output.finished)
	# output.plotLossChart()

	# # Example 8 - Single variable optimization
	# # Question: Given a rectangle of area 25, minimize the lengths of sides to get the least perimeter.
	# def rectLoss(x):
	# 	if x == 0:
	# 		return 0
	# 	x = abs(x)
	# 	side1 = x
	# 	side2 = 25/x
	# 	return 2 * (side1 + side2)
	#
	# output = minimize2D(rectLoss, 20, maxIter=5000)
	# print(output.answers, output.loss, output.iterations, output.finished)

	# # Example 9 - Example with word problem.
	# # Question: Two people have ages with a sum of 66. One person's age is the other person's age backwards.
	# def ageLoss(x, y):
	# 	sumOfAges = 11 * x + 11 * y
	# 	return ((sumOfAges - 66) ** 2) / 2
	#
	# output = minimize(ageLoss, 0, startx=random.randint(5, 9), starty=random.randint(6, 9), dx=1, dy=1, maxIter=5000, calculateMovement=False)
	# print(output.answers, output.loss, output.iterations)
	# output.plotLossChart()


"""

 Created by akiselev on 2019-07-24

 The NumPy module also comes with a number of built-in routines for linear algebra calculations. These can be found in the sub-module linalg.

linalg.det

The linalg.det tool computes the determinant of an array.

print numpy.linalg.det([[1 , 2], [2, 1]])       #Output : -3.0

linalg.eig

The linalg.eig computes the eigenvalues and right eigenvectors of a square array.

vals, vecs = numpy.linalg.eig([[1 , 2], [2, 1]])
print vals                                      #Output : [ 3. -1.]
print vecs                                      #Output : [[ 0.70710678 -0.70710678]
                                                #          [ 0.70710678  0.70710678]]

linalg.inv

The linalg.inv tool computes the (multiplicative) inverse of a matrix.

print numpy.linalg.inv([[1 , 2], [2, 1]])       #Output : [[-0.33333333  0.66666667]
                                                #          [ 0.66666667 -0.33333333]]

Other routines can be found here

Task

You are given a square matrix A with dimensions N X N. Your task is to find the determinant. Note: Round the answer to 2 places after the decimal.

Input Format

The first line contains the integer N.
The next N lines contains the N space separated elements of array A.
"""

import numpy
n = int(input())
arr = numpy.array([input().split() for _ in range(n)], float)
print(numpy.around(numpy.linalg.det(arr), 3))


import numpy as np

def compute_numerial_gradient(cost_func, theta):
    numgrad = np.zeros(theta.size)
    perturb = np.zeros(theta.size)

    e = 1e-4

    for p in range(theta.size):
        perturb[p] = e
        loss1, grad1 = cost_func(theta - perturb)
        loss2, grad2 = cost_func(theta + perturb)

        numgrad[p] = (loss2 - loss1) / (2 * e)
        perturb[p] = 0

    return numgrad


import time
import matplotlib.pyplot as plt
import numpy as np
from numba import jit, f8

## Tri Diagonal Matrix Algorithm(a.k.a Thomas algorithm) solver
@jit(f8[:] (f8[:],f8[:],f8[:],f8[:] ))
def TDMAsolver(b, c, a, d):
    '''
    TDMA solver, a b c d can be NumPy array type or Python list type.
    refer to http://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm
    and to http://www.cfd-online.com/Wiki/Tridiagonal_matrix_algorithm_-_TDMA_(Thomas_algorithm)
    '''
    nf = len(d) # number of equations
    ac, bc, cc, dc = map(np.array, (a, b, c, d)) # copy arrays
    for it in range(1, nf):
        mc = ac[it-1]/bc[it-1]
        bc[it] = bc[it] - mc*cc[it-1]
        dc[it] = dc[it] - mc*dc[it-1]

    xc = bc
    xc[-1] = dc[-1]/bc[-1]

    for il in range(nf-2, -1, -1):
        xc[il] = (dc[il]-cc[il]*xc[il+1])/bc[il]

    return xc

def thomas(a,b,c,d):
    n= len(d)
    for i in range(1,n):
        c[i-1]=c[i-1]/a[i-1]
        a[i] = a[i]-c[i-1]*b[i-1];
        d[i]= d[i]-c[i-1]*d[i-1]
        c[i-1]=0

    #backward substitution
    x=[0 for i in range(n)]
    x[n-1]= d[n-1]/a[n-1]
    for k in range(n-1):
        i=n-2-k
        x[i] = ( d[i] + x[i+1] )/ a[i]

    #print(x)
    return(x)

def TDMASolve(b, c, a, d):
    n = len(a)
    ac, bc, cc, dc = map(np.array, (a, b, c, d))
    xc = []
    for j in range(2, n):
        if(bc[j - 1] == 0):
            ier = 1
            return
        ac[j] = ac[j]/bc[j-1]
        bc[j] = bc[j] - ac[j]*cc[j-1]
    if(b[n-1] == 0):
        ier = 1
        return
    for j in range(2, n):
        dc[j] = dc[j] - ac[j]*dc[j-1]
    dc[n-1] = dc[n-1]/bc[n-1]
    for j in range(n-2, -1, -1):
        dc[j] = (dc[j] - cc[j]*dc[j+1])/bc[j]
    return dc

def solve_plot(delx,scheme):

    def matrix_form(delx,scheme):
        rng = 1.0
        n = int((rng/delx) -1)
        #print (n)
        a = [0 for i in range(n)]
        b = [0 for i in range(n-1)]
        c = [0 for i in range(n-1)]

        t = 0.1

        xm = [0 for i in range(n+1)]
        for i in range(n):
            xm[i+1] = xm[i]+delx
        #print (x)
        if(scheme is "cds"):
            alpha = 1+ (delx/2*t)
            beta = -2-(delx*delx/t)
            gamma = 1- (delx/2*t)
        elif(scheme is "bds"):
            alpha = 1
            beta = -2-(delx*delx/t) +(delx/t)
            gamma = 1- (delx/t)
        elif(scheme is "fds"):
            alpha = 1+ (delx/t)
            beta = -2-(delx*delx/t) -(delx/t)
            gamma = 1

        for i in range(1,n-1):
            c[i-1] = gamma
            a[i] = beta
            b[i] = alpha

        #row 0
        a[0]=beta
        b[0] = alpha

        #row n-1
        a[n-1]=beta
        c[n-2]=gamma

        #rhs vector
        d = [0 for i in range(n)]
        d[0] = -gamma
        d[n-1] =  0

        # print(a)
        # print(b)
        # print(c)
        # print(d)
        return(a,b,c,d,xm,n)

    #display matrices and check
    a,b,c,d,xm,n = matrix_form(delx,scheme)
    # for i in range(n):
    #     print(a[i])
    # for i in range(n):
    #     print(d[i])

    #phi=thomas(a,b,c,d)
    # phi=TDMAsolver(a,b,c,d)
    phi = TDMASolve(a,b,c,d)

    mod_phi = [0 for i in range(n+1)]
    for i in range(len(phi)):
        mod_phi[i+1] = phi[i]
    mod_phi[0] = 1
    mod_phi[n]= 0
    print(mod_phi)
    print(xm)
    print ("\n CPU time: ", time.process_time(),'s')

    #plotting
    plt.plot(mod_phi, xm)
    plt.xlabel('x')
    plt.ylabel('phi')
    meta = str(delx)
    meta = "phi vs. x : dx = " + meta
    plt.title(meta)
    plt.show()

# solve_plot(0.1,"cds")

solve_plot(0.025,"cds")

# solve_plot(0.01,"fds")

# solve_plot(0.01,"bds")


"""Conway Game of Life"""

"""
Initially, there is a grid with some cells which may be alive or dead.
Our task to generate the next generation of cells based on the following rules:
1. Any live cell with fewer than two live neighbors dies, as if caused by under population.
2. Any live cell with two or three live neighbors lives on to the next generation.
3. Any live cell with more than three live neighbors dies, as if by overpopulation.
4. Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction.
"""

def choose_neighbours(grid,index):
    neighborhood = np.array([[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]])
    neighbours_values = np.array([0,0,0,0,0,0,0,0])
    #calculate all the index (potentially neighbours) of an element
    neighborhood[0,0],neighborhood[0,1] = index[0]-1,index[1]
    neighborhood[1,0],neighborhood[1,1] = index[0]-1,index[1]+1
    neighborhood[2,0],neighborhood[2,1] = index[0],index[1]+1
    neighborhood[3,0],neighborhood[3,1] = index[0]+1,index[1]+1
    neighborhood[4,0],neighborhood[4,1] = index[0]+1,index[1]
    neighborhood[5,0],neighborhood[5,1] = index[0]+1,index[1]-1
    neighborhood[6,0],neighborhood[6,1] = index[0],index[1]-1
    neighborhood[7,0],neighborhood[7,1] = index[0]-1,index[1]-1
    #evaluate if some neighbour doesn't make sense (neighbour index is out of the grid range)
    for i in range(8): #8 is the number of neighbours
        _x = neighborhood[i,0] #acces to x coord of the ith neighbour
        _y = neighborhood[i,1] #acces to y coord of the ith neighbour
        #if coord is out of range is not a real neighbour (0)
        if _x <0 or _x >rows-1 or _x >cols-1 or _y <0 or _y >rows-1 or _y >cols-1:
            neighbours_values[i] = 0
        else: #otherwise the grid is evaluated to define if there is a neighbour
            neighbours_values[i] = grid[_x,_y]
    return neighbours_values

def count_neighbours(grid):
    for index, x in np.ndenumerate(grid):
        neighbours[index]=sum(choose_neighbours(grid,index))
    return neighbours

def next_generation(grid,neighbours):
    for index,cell in np.ndenumerate(grid):
        if cell == 1: # Any live cell...
            #case 1. ...with fewer than two live neighbors dies and
            #case 3. ...with more than three live neighbors dies.
            if neighbours[index] < 2 or neighbours[index] > 3:
                grid[index] = 0
            #case 2. ...with two or three live neighbors lives => is the default entry case
        else:
            #case 4. Any dead cell with exactly three live neighbors becomes a live cell
            if neighbours[index] == 3:
                grid[index] = 1

"""Main script"""

import numpy as np

cols = 4
rows = 4
grid = np.array([[1,0,0,1],[0,1,0,1],[0,0,0,0],[0,0,0,1]])
neighbours = np.copy(grid) #just to keep the same grid size

neighbours = count_neighbours(grid)
print(grid)
print("********")
print(neighbours)
print("********")
next_generation(grid,neighbours)
print(grid)


"""

https://en.wikipedia.org/wiki/Abel_polynomials

"""

from sympy import *
from sympy.abc import *
import sys
import os

def memoize(f, a):
    memo = {}
    def helper(x):
        if x not in memo:
            memo[x] = expand(simplify(f(a, x)))
        return memo[x]
    return helper

def abel(a, n):
    if n < 0:
        return 0
    if n == 0:
        return 1
    if n == 1:
        return x
    return x*(x - a*n)**(n-1)

abel1 = memoize(abel, 1)
abel2 = memoize(abel, 2)

if len(sys.argv) < 2:
    print("usage: order ...")
    sys.exit()

for i in range(len(sys.argv)-1):
    n = int(sys.argv[i+1])
    print("Order {}".format(n))
    print("Abel 1")
    print(abel1(n))
    print("Abel 2")
    print(abel2(n))
    print()


# day 6 Numpy Array Function
import numpy as np
from math import *

def aggregate_operate():
    L = np.random.random(50)
    print('Array:', L, '\r\n')
    print('python sum:', sum(L))
    print('numpy sum:', np.sum(L))
    print('python min:', min(L))
    print('numpy min:', np.min(L))
    print('python max:', max(L))
    print('numpy max:', np.max(L))
    print('min:', L.min(), 'max:', L.max(), 'sum:', L.sum())

def multi_dimension_aggregate():
    M = np.random.random((4, 5))
    print('Array:', M, '\r\n')
    print('min:', M.min(), 'max:', M.max(), 'sum:', M.sum())
    print('min(axis=0):', M.min(axis=0))
    print('max(axis=1):', M.max(axis=1))

def other_aggregate_operate():
    l = np.random.randint(100, size=20)
    print('Array:', l, '\r\n')
    print("Mean :       ", l.mean())
    print("Standard deviation:", l.std())
    print("Minimum :    ", l.min())
    print("Maximum :    ", l.max())
    print("25th percentile:   ", np.percentile(l, 25))
    print("Median:            ", np.median(l))
    print("75th percentile:   ", np.percentile(l, 75))
    b = l > 50
    print("has bigger :", b.any())
    print("all bigger :", b.all())

if __name__ == '__main__':
    print('Numpy Version', np.__version__)
    # aggregate_operate()
    # multi_dimension_aggregate()
    other_aggregate_operate()


#!/usr/bin/env python3

import numpy as np
from scipy.stats import norm

import matplotlib as mpl
mpl.use('Agg')
import matplotlib.pyplot as plt
plt.style.use('bmh')
import matplotlib.mlab as mlab

np.random.seed(0)

X = np.arange(-3, 3, 0.001)
Y = norm.pdf(X, 0, 1)

fig = plt.figure()
plt.plot(X, Y)
plt.tight_layout()
plt.savefig("normal-pdf.png")

nSamples = 35
X = np.random.normal(0, 1, nSamples)
Y = np.zeros(nSamples)
fig = plt.figure(figsize=(7,3))
plt.scatter(X, Y, color='k')
plt.xlim((-3,3))
frame = plt.gca()
frame.axes.get_yaxis().set_visible(False)
plt.savefig("normal-samples.png")

delta = 0.025
x = np.arange(-3.0, 3.0, delta)
y = np.arange(-3.0, 3.0, delta)
X, Y = np.meshgrid(x, y)
Z = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)

plt.figure()
CS = plt.contour(X, Y, Z)
plt.clabel(CS, inline=1, fontsize=10)

nSamples = 200
mean = [0, 0]
cov = [[1,0], [0,1]]
X, Y = np.random.multivariate_normal(mean, cov, nSamples).T
plt.scatter(X, Y, color='k')

plt.savefig("normal-2d.png")


import numpy as np
from parameters import *

def wiener_interpolator1(x, n_0, alpha):
    """
    Wiener interpolator using all observations
    :param x: Observation vector except at n_0
    :param n_0: The time instant of interpolation
    :param alpha: Value of alpha
    :return: The predicted value at n_0 and BMSE
    """

    R = np.zeros([N - 1, N - 1])
    r = np.zeros(N - 1)
    a = np.zeros(N - 1)

    for i in range(N - 1):
        for j in range(N - 1):
            if np.abs(i - j) < n_0:
                R[i, j] = alpha ** np.abs(i - j)
            else:
                R[i, j] = alpha ** np.abs(i - j + 1)

    for i in range(N - 1):
        if i < n_0:
            r[i] = alpha ** (n_0 - i)
        else:
            r[i] = alpha ** (i - n_0 + 1)

    R_inv = np.linalg.inv(R)
    a = R_inv @ r
    x_n_0 = np.dot(a, x)

    r_0 = sigma_w ** 2 / (1 - alpha ** 2)
    BMSE = r_0 - np.transpose(r) @ R_inv @ r

    return x_n_0, BMSE

def wiener_interpolator2(x, n_0, alpha):
    """
    Wiener interpolator using observations x[n_0 - 1] and x[n_0 + 1]
    :param x: Observation vector except at n_0
    :param n_0: The time instant of interpolation
    :param alpha: Value of alpha
    :return: The predicted value at n_0 and BMSE
    """

    x_n_0 = alpha / (1 + alpha ** 2) * (x[n_0 - 1] + x[n_0 + 1])

    r_0 = sigma_w ** 2 / (1 - alpha ** 2)
    r_1 = alpha * r_0
    r_2 = alpha * r_1

    r = np.array([r_1, r_1])
    R = np.array([[r_0, r_2], [r_2, r_0]])
    R_inv = np.linalg.inv(R)

    BMSE = r_0 - np.transpose(r) @ R_inv @ r

    return x_n_0, BMSE


from sympy import *
from tait_bryan_R_utils import *
from rodrigues_R_utils import *
from quaternion_R_utils import *

px, py, pz = symbols('px py pz')
om, fi, ka = symbols('om fi ka')
#sx, sy, sz = symbols('sx sy sz')
#q0, q1, q2, q3 = symbols('q0 q1 q2 q3')
tie_px, tie_py, tie_pz = symbols('tie_px tie_py tie_pz');
cols, rows = symbols('cols rows');
pi = symbols('pi')
u_kp, v_kp = symbols('u_kp v_kp')

position_symbols = [px, py, pz]
orientation_symbols = [om, fi, ka]
#orientation_symbols = [sx, sy, sz]
#orientation_symbols = [q0, q1, q2, q3]
tie_point_symbols = [tie_px, tie_py, tie_pz]
all_symbols = position_symbols + orientation_symbols + tie_point_symbols

RT_wc = matrix44FromTaitBryan(px, py, pz, om, fi, ka)
#RT_wc = matrix44FromRodrigues(px, py, pz, sx, sy, sz)
#RT_wc = matrix44FromQuaternion(px, py, pz, q0, q1, q2, q3)
r=RT_wc[:-1,:-1]
t=Matrix([px, py, pz]).vec()

pos_w=Matrix([tie_px, tie_py, tie_pz]).vec()
bearing = r * pos_w + t;
norm = sqrt(bearing[0]*bearing[0] + bearing[1]*bearing[1] + bearing[2]*bearing[2])
bearing=bearing/norm
latitude=-asin(bearing[1])
longitude=atan2(bearing[0], bearing[2])

u=cols*(0.5 + longitude / (2.0 * pi))
v=rows*(0.5 - latitude/pi)
uv = Matrix([u, v]).vec()

target_value = Matrix([u_kp, v_kp]).vec()
model_function = Matrix([u, v]).vec()
obs_eq = target_value - model_function
obs_eq_jacobian = obs_eq.jacobian(all_symbols)

print(obs_eq)
print(obs_eq_jacobian)

with open("equirectangular_camera_colinearity_tait_bryan_wc_jacobian.h",'w') as f_cpp:
    f_cpp.write("inline void equrectangular_camera_colinearity_tait_bryan_wc(double &u, double &v, double rows, double cols, double pi, double px, double py, double pz, double om, double fi, double ka, double tie_px, double tie_py, double tie_pz)\n")
    f_cpp.write("{")
    f_cpp.write("u = %s;\n"%(ccode(uv[0,0])))
    f_cpp.write("v = %s;\n"%(ccode(uv[1,0])))
    f_cpp.write("}")
    f_cpp.write("\n")
    f_cpp.write("inline void observation_equation_equrectangular_camera_colinearity_tait_bryan_wc(Eigen::Matrix<double, 2, 1> &delta, double rows, double cols, double pi, double px, double py, double pz, double om, double fi, double ka, double tie_px, double tie_py, double tie_pz, double u_kp, double v_kp)\n")
    f_cpp.write("{")
    f_cpp.write("delta.coeffRef(0,0) = %s;\n"%(ccode(obs_eq[0,0])))
    f_cpp.write("delta.coeffRef(1,0) = %s;\n"%(ccode(obs_eq[1,0])))
    f_cpp.write("}")
    f_cpp.write("\n")
    f_cpp.write("inline void observation_equation_equrectangular_camera_colinearity_tait_bryan_wc_jacobian(Eigen::Matrix<double, 2, 9, Eigen::RowMajor> &j, double rows, double cols, double pi, double px, double py, double pz, double om, double fi, double ka, double tie_px, double tie_py, double tie_pz, double u_kp, double v_kp)\n")
    f_cpp.write("{")
    for i in range (2):
        for j in range (9):
            f_cpp.write("j.coeffRef(%d,%d) = %s;\n"%(i,j, ccode(obs_eq_jacobian[i,j])))
    f_cpp.write("}")


import numpy as np

def L2(pred: np.ndarray, gt: np.ndarray) -> np.ndarray:
    return np.linalg.norm(np.subtract(pred, gt), ord=2, axis=-1)

def L1(pred: np.ndarray, gt: np.ndarray) -> np.ndarray:
    return np.linalg.norm(np.subtract(pred, gt), ord=1, axis=-1)


"""
Coded By: Puneet Dheer
Date:4-Jan-2019
"""

import numpy as np
import pandas as pd

def multiple_linear_regression(X, Y):

    X = np.insert(X,0,np.ones(1), axis = 1) # insert one's in 1st column for intercept
    coefficients = np.linalg.inv(X.T.dot(X)).dot(X.T).dot(Y)
    predicted = X.dot(coefficients)

    return coefficients, predicted

def mse_metric(actual, predicted):
    # Mean square error

    error = actual - predicted
    mse = np.mean(np.square(error), axis = 0) # assuming the mean of error is zero ~N(0,var)

    return mse

def mae_metric(actual,predicted):

    # Mean absolute error
    error = np.abs(actual - predicted)
    mae = np.mean(error, axis = 0)

    return mae

def goodness_of_fit(actual, predicted):

    explained_variance = np.sum(np.square(predicted - np.mean(actual) )) # sum of squared regression
    unexplained_variance = np.sum(np.square(actual - predicted)) # sum of squared error(residuals)
    total_variance = explained_variance + unexplained_variance
    R_square = 1-(unexplained_variance / total_variance)

    return R_square

if __name__ == "__main__":

    dataset = pd.read_csv('Sample_Data_MLR.csv', header = 0) # dataset column wise

    X = dataset.iloc[:, 0:-1].values # independent variable
    Y = dataset.iloc[:, 4:5].values # dependent variable

    if (Y.shape==(len(Y),1)):
        pass
    else:
        Y = Y.reshape(len(Y),1)

    coefficients,predicted = multiple_linear_regression(X, Y)

    print "MS Error: \n", mse_metric(Y, predicted)
    print "RMS Error: \n", np.sqrt(mse_metric(Y, predicted))
    print "MA Error: \n", mae_metric(Y, predicted)
    print "Goodness-of-fit: \n", goodness_of_fit(Y, predicted)
    print "Coefficients: \n", coefficients


import checks
import numpy as np

def sample_gauss(covariance, samples=1):
    """
    Generate samples from a multivariate Gaussian distribution.

    :param covariance: distribution covariance (numpy.ndarray, must be
        a square matrix)
    :param samples: number of samples to generate (int)
    :return: array of samples from a multivariate Gaussian distribution
    """
    # Input must be valid
    checks.check_matrix_validity(covariance)

    ndim = covariance.shape[0]
    samples = samples if samples > 0 else 1
    return np.random.multivariate_normal(np.zeros(ndim), covariance, samples)

class MetroSampler:
    """
    Generate samples from a distribution using the Metropolis algorithm.

    The details of the algorithm are described in the following article:

        - H. Haairo, E. Saksman, and J. Tamminen, An adaptive metropolis
          algorithm, Bernoulli 7(2), 2001, pp. 223-242
    """

    def __init__(self, posterior, x_initial, covariance_initial,
                 update_freq=200, t0=1000, tb=10000, gamma=1.0, eps=1.0e-6):
        """
        Initialize the sampler.

        :param posterior: distribution to sample from (posterior.Distribution)
        :param x_initial: a state of the sampling distribution with nonzero
            probability (numpy.ndarray, must be a vector)
        :param covariance_initial: the initial covariance matrix used by the
            proposal distribution (numpy.ndarray, must be a square matrix)
        :param update_freq: frequency at which the covariance is updated (int)
        :param t0: steps before the learnt covariance matrix is used (int)
        :param tb: length of burnin period (int)
        :param gamma: scaling factor for the transitions step size (float)
        :param eps: small quantity to avoid a singularity in the covariance
            matrix of the proposal distribution (float)
        """
        # Input must be valid
        checks.check_distribution_validity(posterior)
        checks.check_vector_matrix_validity(x_initial, covariance_initial)

        self.posterior = posterior
        self.covariance = covariance_initial

        self.x_last = x_initial
        self.x_last_probability = posterior.prob(x_initial)
        self.x_mean = np.zeros(x_initial.shape)
        self.x_covariance = np.zeros(covariance_initial.shape)

        self.t0 = t0
        self.eps = eps
        self.niter = 0

        self.candidates = None
        self.update_freq = update_freq if update_freq > 0 else 1
        self.sd = (2.7 ** 2) * gamma / float(x_initial.shape[0])

        # Run Markov chain for tb steps before starting to sample
        for _ in range(tb):
            self._step()

    def _update_running_mean(self):
        """Update the state vector mean."""
        diff = self.x_last - self.x_mean
        self.x_mean = self.x_mean + diff / float(1.0 + self.niter)

    def _update_running_covariance(self):
        """Update the empirical covariance matrix."""
        if self.niter == 0:
            return

        diff = self.x_last - self.x_mean
        prod = np.outer(diff, diff) / float(self.niter + 1.0)
        fact = float(self.niter - 1.0) / float(self.niter)
        self.x_covariance = fact * self.x_covariance + prod

    def _update_covariance(self):
        """Update the covariance matrix used to generate candidate states."""
        delta = self.sd * self.eps * np.identity(self.covariance.shape[0])
        self.covariance = self.sd * self.x_covariance + delta
        self.candidates = sample_gauss(self.covariance, self.update_freq)

    def _generate_candidate(self):
        """Generate a new candidate state."""
        if self.candidates is None or self.candidates.size == 0:
            self.candidates = sample_gauss(self.covariance, self.update_freq)

        candidate = self.x_last + self.candidates[0]
        self.candidates = np.delete(self.candidates, 0, 0)
        return candidate

    def _step(self):
        """
        Implements a step of the Metropolis algorithm.

        :return: True if the proposal state is accepted, False otherwise
        """
        # Covariance depends on running mean and must be updated first
        self._update_running_covariance()
        self._update_running_mean()

        self.niter += 1
        x_candidate = self._generate_candidate()
        x_candidate_probability = self.posterior.prob(x_candidate)

        cutoff = np.random.random()
        ratio = x_candidate_probability / self.x_last_probability

        if self.niter % self.update_freq == 0 and self.niter > self.t0:
            self._update_covariance()

        candidate_feasible = ratio >= cutoff
        if candidate_feasible:
            self.x_last = x_candidate
            self.x_last_probability = x_candidate_probability
            return True

        return False

    def sample(self, samples_number=1, sample_every=200):
        """
        Generate samples from the distribution.

        :param samples_number: the number of samples to generate (int)
        :param sample_every: the sampling frequency (int)
        :return: list of samples and numbers of accepted / attempted jumps
        """
        jumps_total = 0
        jumps_accepted = 0

        samples_list = np.array([])
        samples_left = samples_number

        # Ensure sample frequency is at least one
        sample_every = sample_every if sample_every > 0 else 1
        while samples_left > 0:

            for _ in range(sample_every):
                success = self._step()
                if success:
                    jumps_accepted += 1

                jumps_total += 1

            samples_left -= 1
            samples_list = np.append(samples_list, self.x_last)

        reshape_dims = (samples_number, self.x_last.shape[0])
        samples_list = samples_list.reshape(reshape_dims)
        return samples_list, jumps_accepted, jumps_total


from this import d
import numpy as np
from numpy import diagonal, linalg as linear
import matplotlib.pyplot as plt

x1,x2,x3,x4,y1,y2,y3,y4= 5,150,150,5,5,5,150,150
xp1,xp2,xp3,xp4,yp1,yp2,yp3,yp4 = 100,200,220,100,100,80,80,200

A = np.array([[-x1,-y1,-1,0,0,0,x1*xp1,y1*xp1,xp1],
              [0,0,0,-x1,-y1,-1,x1*yp1,y1*yp1,yp1],
              [-x2,-y2,-1,0,0,0,x2*xp2,y2*xp2,xp2],
              [0,0,0,-x2,-y2,-1,x2*yp2,y2*yp2,yp2],
              [-x3,-y3,-1,0,0,0,x3*xp3,y3*xp3,xp3],
              [0,0,0,-x3,-y3,-1,x3*yp3,y3*yp3,yp3],
              [-x4,-y4,-1,0,0,0,x4*xp4,y4*xp4,xp4],
              [0,0,0,-x4,-y4,-1,x4*yp4,y4*yp4,yp4]])

def svdCalculator(A):

    # calculating U matrix
    A_transp = A.transpose()
    AA_transp = A.dot(A_transp)

    # computing eigen values and eigen vectors
    eig_values , eig_vector = linear.eig(AA_transp)

    #sorting eigen values and vectors
    eig_sort = eig_values.argsort()[::-1]
    u_sorted = eig_values[eig_sort]
    U_Matrix = eig_vector[:,eig_sort]

    # computing V transpose matrix

    A_transp_A = A_transp.dot(A)
    Veig_val,Veig_vect = linear.eig(A_transp_A)
    Vsort = Veig_val.argsort()[::-1]
    V_Matrix = Veig_vect[:,Vsort]
    V_trasp = V_Matrix.transpose()

    # flitering non positive eigen values
    for i in range(len(u_sorted)):
        if u_sorted[i]<=0:
            u_sorted[i]*=-1

    #Computing Sigma Matrix

    diagonal = np.diag((np.sqrt(u_sorted)))
    sigma_matrix = np.zeros_like(A).astype(np.float64)

    for i in range(len(sigma_matrix)):
        sigma_matrix[i][i]=diagonal[i][i]

    #computing homography
    homography = V_Matrix[:,8]
    homography = np.reshape(homography,(3,3))

    return V_trasp, U_Matrix, sigma_matrix, homography

V_star, U, sig, homo = svdCalculator(A)

print("Homography Matrix: ", homo)


import numpy as np

def numerical_extrapolation(H_0, H_t, N=100):
    def H(n):
        return H_0 + ((H_t - H_0)/N * n)

    R = 1.0
    for i in range(N):
        R *= pow(N, (1/N)) * np.exp(-0.5*(H(i) + H(i+1)))
    return R

def analytical_extrapolation(H_0, H_t, N=100):
    return  N*np.exp(-0.5*(H_0 + H_t) - ((N-1)/2) * (H_t + H_0))

for _ in range(100):
    H_0 = np.random.random()
    H_t = np.random.random()
    print(numerical_extrapolation(H_0, H_t))
    print(analytical_extrapolation(H_0, H_t))
    assert np.round(numerical_extrapolation(H_0, H_t), 10) == np.round(analytical_extrapolation(H_0, H_t), 10)


import numpy as np
import matplotlib.pyplot as plt

sigma = 0.5
x0 = 0.0
N = lambda x: 2.0/(1 + np.exp(-sigma*(x - x0))) - 1.0
U = lambda x: x**3

x = np.linspace( -10, 10, 100 )

fig, ax = plt.subplots(	1, 3 )

ax[0].plot( x, x, linewidth = 2.0 )
ax[0].set_ylabel('Drive')
ax[0].set_xlabel('State variable')
ax[0].set_title('Linear')

ax[1].plot( x, N(x), linewidth = 2. )
ax[1].set_ylabel('Drive')
ax[1].set_xlabel('State variable')
ax[1].set_title('Normalization')

ax[2].plot( x, U(N(x)), linewidth = 2. )
ax[2].set_xlabel('State variable')
ax[2].set_ylabel('Drive')
ax[2].set_title('Urgency + normalization')
#plt.plot( x, N(x) )
plt.show()


from scipy.stats import boxcox
import seaborn as sns

def log_transform(df,feature):
	bx=np.log(df[feature]+1)
	return pd.DataFrame(bx)

def reciprocal_transform(df,feature):
	bx=1/(df[feature]+1)
	return pd.DataFrame(bx)

def square_root_transform(df,feature):
	bx=df[feature]**(1/2)
	return pd.DataFrame(bx)

def exponential_transform(df,feature):
	bx=df[feature]**(1/5)
	return pd.DataFrame(bx)

def boxcox_transform(df,feature):
	bx,parameter=boxcox(df[feature])
	return bx

def box_plot(df,feature):
	sns.boxplot(df[feature])

def MinMaxScaler(df):
	for i in df:
		min1=min(df[i])
		max1=max(df[i])
		for j in df[i]:
			scale=(j-min1)/(max1-min1)
			df[i]=np.where(df[i]==j,scale,df[i])

def StandardScaler(df):
	for i in df:
		mean=df[i].mean()
		std=df[i].std()
		for j in df[i]:
			scale=(j-mean)/std
			df[i]=np.where(df[i]==j,scale,df[i])


#!/usr/bin/env python
# -*- encoding: utf-8 -*-
import scipy.sparse as sp
import numpy as np
from scipy.sparse import linalg
import torch

def calculate_symmetric_normalized_laplacian(adj):
    r"""
    Description:
    -----------
    Calculate Symmetric Normalized Laplacian.
    Assuming unnormalized laplacian matrix is `L = D - A`,
    then symmetric normalized laplacian matrix is:
    `L^{Sym} =  D^-1/2 L D^-1/2 =  D^-1/2 (D-A) D^-1/2 = I - D^-1/2 A D^-1/2`
    For node `i` and `j` where `i!=j`, L^{sym}_{ij} <=0.

    Parameters:
    -----------
    adj: np.ndarray
        Adjacent matrix A

    Returns:
    -----------
    symmetric_normalized_laplacian: np.matrix
        Symmetric normalized laplacian L^{Sym}
    """
    adj                                 = sp.coo_matrix(adj)
    D                                   = np.array(adj.sum(1))
    D_inv_sqrt = np.power(D, -0.5).flatten()    # diagonals of D^{-1/2}
    D_inv_sqrt[np.isinf(D_inv_sqrt)]    = 0.
    matrix_D_inv_sqrt                   = sp.diags(D_inv_sqrt)   # D^{-1/2}
    symmetric_normalized_laplacian      = sp.eye(adj.shape[0]) - matrix_D_inv_sqrt.dot(adj).dot(matrix_D_inv_sqrt).tocoo()
    return symmetric_normalized_laplacian

def calculate_scaled_laplacian(adj, lambda_max=2, undirected=True):
    r"""
    Description:
    -----------
    Re-scaled the eigenvalue to [-1, 1] by scaled the normalized laplacian matrix for chebyshev pol.
    According to `2017 ICLR GCN`, the lambda max is set to 2, and the graph is set to undirected.
    Note that rescale the laplacian matrix is equal to rescale the eigenvalue matrix.
    `L_{scaled} = (2 / lambda_max * L) - I`

    Parameters:
    -----------
    adj: np.ndarray
        Adjacent matrix A

    Returns:
    -----------
    L_res: np.matrix
        The rescaled laplacian matrix.
    """
    if undirected:
        adj = np.maximum.reduce([adj, adj.T])
    L       = calculate_symmetric_normalized_laplacian(adj)
    if lambda_max is None:  # manually cal the max lambda
        lambda_max, _ = linalg.eigsh(L, 1, which='LM')
        lambda_max = lambda_max[0]
    L       = sp.csr_matrix(L)
    M, _    = L.shape
    I       = sp.identity(M, format='csr', dtype=L.dtype)
    L_res   = (2 / lambda_max * L) - I
    return L_res

def symmetric_message_passing_adj(adj):
    r"""
    Description:
    -----------
    Calculate the renormalized message passing adj in `GCN`.
    A = A + I
    return D^{-1/2} A D^{-1/2}

    Parameters:
    -----------
    adj: np.ndarray
        Adjacent matrix A

    Returns:
    -----------
    mp_adj:np.matrix
        Renormalized message passing adj in `GCN`.
    """
    # add self loop
    adj         = adj + np.diag(np.ones(adj.shape[0], dtype=np.float32))
    # print("calculating the renormalized message passing adj, please ensure that self-loop has added to adj.")
    adj         = sp.coo_matrix(adj)
    rowsum      = np.array(adj.sum(1))
    d_inv_sqrt  = np.power(rowsum, -0.5).flatten()
    d_inv_sqrt[np.isinf(d_inv_sqrt)] = 0.
    d_mat_inv_sqrt  = sp.diags(d_inv_sqrt)
    mp_adj          = d_mat_inv_sqrt.dot(adj).transpose().dot(d_mat_inv_sqrt).astype(np.float32)
    return mp_adj

def transition_matrix(adj):
    r"""
    Description:
    -----------
    Calculate the transition matrix `P` proposed in DCRNN and Graph WaveNet.
    P = D^{-1}A = A/rowsum(A)

    Parameters:
    -----------
    adj: np.ndarray
        Adjacent matrix A

    Returns:
    -----------
    P:np.matrix
        Renormalized message passing adj in `GCN`.
    """
    adj = sp.coo_matrix(adj)
    rowsum = np.array(adj.sum(1)).flatten()
    d_inv = np.power(rowsum, -1).flatten()
    d_inv[np.isinf(d_inv)] = 0.
    d_mat= sp.diags(d_inv)
    # P = d_mat.dot(adj)
    P = d_mat.dot(adj).astype(np.float32).todense()
    return P


import os, sys, subprocess, argparse, re, logging
import math
import numpy as np
import mymm

def get_random_points_on_sphere(radius, numpoints):
    t = np.arange(start=-1.0+1.0/numpoints, stop=1.0+1.0/numpoints, step=2.0/numpoints)
    theta = np.arccos(t)
    phi = math.sqrt(math.pi*numpoints)*np.arcsin(t)
    points = radius* np.array([np.sin(theta)*np.cos(phi),
                               np.sin(theta)*np.sin(phi),
                               np.cos(theta)])
    return list(points.transpose())

def create_sphere_of_atoms(radius, density = 0.25, origin = [0.0, 0.0, 0.0], segid = "S"):
    newsphere = mymm.Molecule()
    if radius < 2:
        print("Error: addSphereOfAtoms only works for radius > 2!\n")
        return newsphere

    atomid = "CA"
    resid  = "SPH"
    number = 1
    resnum = 1
    radius_for_atom_set = radius-2.0
    while radius_for_atom_set >= 2.0:
        surface_area = 4.0 * math.pi * radius_for_atom_set**2
        num_points = int(surface_area * density)
        print("radius = %f, num_points =  %d\n" % (radius_for_atom_set, num_points))
        points = get_random_points_on_sphere(radius_for_atom_set,num_points)
        for curpoint in points:
            newsphere.add_atom(mymm.Atom(number, atomid, resid, segid,
                                   resnum, resnum,
                                   curpoint[0]+origin[0],
                                   curpoint[1]+origin[1],
                                   curpoint[2]+origin[2]))
            number = number + 1
            resnum = resnum + 1
        radius_for_atom_set = radius_for_atom_set - 3.0

    return newsphere


"""
Page 100
"""

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt

learning_rate = 0.01
training_epochs = 40

trX = np.linspace(-1, 1, 101)

num_coeffs = 6
trY_coeffs = [1, 2, 3, 4, 5, 6]
trY = 0
for i in range(num_coeffs):
    trY += trY_coeffs[i] * np.power(trX, i)

trY += np.random.randn(*trX.shape) * 1.5

plt.scatter(trX, trY)
plt.show()

#########################################

X = tf.placeholder(tf.float32)
Y = tf.placeholder(tf.float32)

def model(X, w):
    terms = []
    for i in range(num_coeffs):
        term = tf.multiply(w[i], tf.pow(X, i))
        terms.append(term)
    return tf.add_n(terms)

w = tf.Variable([0.0] * num_coeffs, name='parameters')
y_model = model(X, w)

cost = tf.pow(Y - y_model, 2)
train_op = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost)

sess = tf.Session()
init = tf.global_variables_initializer()
sess.run(init)

for epoch in range(training_epochs):
    for x, y in zip(trX, trY):
        sess.run(train_op, feed_dict={X: x, Y: y})

w_val = sess.run(w)
print(w_val)
sess.close()

plt.scatter(trX, trY)
trY2 = 0
for i in range(num_coeffs):
    trY2 += w_val[i] * np.power(trX, i)

plt.plot(trX, trY2, 'r')
plt.show()


import numpy as np
import tensorflow as tf
from tensorflow.python.ops import rnn, rnn_cell

class SeriesPredictor:

    def __init__(self, input_dim, seq_size, hidden_dim=10):
        # Hyperparameters
        self.input_dim = input_dim
        self.seq_size = seq_size
        self.hidden_dim = hidden_dim

        # Weight variables and input placeholders
        self.W_out = tf.Variable(tf.random_normal([hidden_dim, 1]), name='W_out')
        self.b_out = tf.Variable(tf.random_normal([1]), name='b_out')
        self.x = tf.placeholder(tf.float32, [None, seq_size, input_dim])
        self.y = tf.placeholder(tf.float32, [None, seq_size])

        # Cost optimizer
        self.cost = tf.reduce_mean(tf.square(self.model() - self.y))
        self.train_op = tf.train.AdamOptimizer().minimize(self.cost)

        # Auxiliary ops
        self.saver = tf.train.Saver()

    def model(self):
        """
        :param x: inputs of size [T, batch_size, input_size]
        :param W: matrix of fully-connected output layer weights
        :param b: vector of fully-connected output layer biases
        """
        cell = rnn_cell.BasicLSTMCell(self.hidden_dim, reuse=tf.get_variable_scope().reuse)
        outputs, states = rnn.dynamic_rnn(cell, self.x, dtype=tf.float32)
        num_examples = tf.shape(self.x)[0]
        W_repeated = tf.tile(tf.expand_dims(self.W_out, 0), [num_examples, 1, 1])
        out = tf.batch_matmul(outputs, W_repeated) + self.b_out
        out = tf.squeeze(out)
        return out

    def train(self, train_x, train_y):
        with tf.Session() as sess:
            tf.get_variable_scope().reuse_variables()
            sess.run(tf.initialize_all_variables())
            for i in range(1000):
                _, mse = sess.run([self.train_op, self.cost], feed_dict={self.x: train_x, self.y: train_y})
                if i % 100 == 0:
                    print(i, mse)
            save_path = self.saver.save(sess, 'Model.ckpt')
            print('Model saved to {}'.format(save_path))

    def test(self, test_x):
        with tf.Session() as sess:
            tf.get_variable_scope().reuse_variables()
            self.saver.restore(sess, 'Model.ckpt')
            output = sess.run(self.model(), feed_dict={self.x: test_x})
            print(output)

if __name__ == '__main__':
    predictor = SeriesPredictor(input_dim=1, seq_size=4, hidden_dim=10)
    train_x = [[[1], [2], [5], [6]],
               [[5], [7], [7], [8]],
               [[3], [4], [5], [7]]]
    train_y = [[1, 3, 7, 11],
               [5, 12, 14, 15],
               [3, 7, 9, 12]]
    predictor.train(train_x, train_y)

    test_x = [[[1], [2], [3], [4]],  # 1, 3, 5, 7
              [[4], [5], [6], [7]]]  # 4, 9, 11, 13
    predictor.test(test_x)


import scipy.stats as st
import statsmodels.stats.api as sms

from math import ceil

__author__ = 'Kirill Guliaev'

def get_sample_size_for_binomial(p0, p1, power = 0.8, significance = 0.05):
    """
    Calculate the sample size for the binomial distribution

    Parameters
    ----------
    p0 : [0, 1] current proportion value
    p1 : [0, 1] expected proportion value
    power : power of the test, e.g. 0.8,
    is one minus the probability of a type II error.
    Power is the probability that the test correctly
    rejects the Null Hypothesis if the Alternative Hypothesis is true.
    significance : significance level, e.g. 0.05, is the probability
    of a type I error, that is wrong rejections
    if the Null Hypothesis is true.

    Returns
    -------
    required_n : Number of observations per sample.
    """
    effect_size = sms.proportion_effectsize(p0, p1)
    required_n = sms.NormalIndPower().solve_power(effect_size, power=power, alpha=significance)
    return ceil(required_n)

def get_sample_size_for_continuous(mu1, mu2, standard_deviation, power = 0.8, significance = 0.05):
    """
    Calculate the sample size for the continuous distribution

    Parameters
    ----------
    mu1 : current mean value
    mu2 : expected mean value
    standard_deviation : standard deviation of the mean
    power : power of the test, e.g. 0.8,
    is one minus the probability of a type II error.
    Power is the probability that the test correctly
    rejects the Null Hypothesis if the Alternative Hypothesis is true.
    significance : significance level, e.g. 0.05, is the probability
    of a type I error, that is wrong rejections
    if the Null Hypothesis is true.

    Returns
    -------
    required_n : Number of observations per sample.
    """
    gamma = (mu1 - mu2) / standard_deviation
    z1 = st.norm(loc = 0, scale = 1).ppf(1 - significance / 2)
    z2 = st.norm(loc = 0, scale = 1).ppf(power)
    required_n = 2 * (z1 + z2) ** 2 / gamma ** 2
    return ceil(required_n)


from math import exp
from typing import Callable
from typing import Union

import numpy as np

from regimedetection.src.metrics import euclidean_distance
from regimedetection.src.metrics import squared_euclidean_distance

def make_rbf_kernel(sigma: float) -> Callable[[np.ndarray, np.ndarray], np.ndarray]:
    """
    Returns a vectorized implementation of the Radial Basis Function kernel,
    for a given value of sigma.

    Values passed to the returned kernel should be two-dimensional arrays,
    with the first axis being the batch dimension, and the second axis being
    the channels direction

    See e.g. https://en.wikipedia.org/wiki/Radial_basis_function_kernel
    for a definition of the RBF kernel
    """

    def rbf_kernel(x: np.ndarray, y: np.ndarray) -> np.ndarray:
        if x.ndim != 2 or y.ndim != 2:
            raise np.AxisError("Inputs to RBF Kernel should have dimension 2")

        if x.shape != y.shape:
            raise ValueError("Inputs to RBF Kernel should have the same shape")

        differences = x - y
        squared_distances = (differences * differences).sum(axis=1)
        powers = -squared_distances / (2 * sigma * sigma)
        return np.exp(powers)

    return rbf_kernel

def _get_kernel_sum(
    x: np.ndarray, y: np.ndarray, kernel: Callable[[np.ndarray, np.ndarray], np.ndarray]
) -> np.float:
    """
    Compute the kernel of every element of x with every
    element of y, and take the sum

    Helper function for make_mmd_estimate_metric_from_kernel;
    not expected to be called directly by users.

    The kernel is expected to be a vectorized implementation,
    acting on two 2-dimensional np arrays
    """

    lhs = np.tile(x, reps=(len(y), 1))
    rhs = np.repeat(y, len(x), axis=0)
    kernel_values = kernel(lhs, rhs)

    return kernel_values.sum()

def make_mmd_estimate_metric_from_kernel(
    kernel: Callable[[np.ndarray, np.ndarray], np.ndarray]
) -> Callable[[np.ndarray, np.ndarray], np.ndarray]:
    """
    Returns the 'MMD Estimate Metric' from a specified kernel.

    The kernel provided is expected to be a vectorized implementation,
    acting on two 2-dimensional np arrays
    """

    def mmd_estimate_metric(x: np.ndarray, y: np.ndarray) -> np.ndarray:
        x_kernel_sum = _get_kernel_sum(x, x, kernel)
        scaled_x_kernel_sum = x_kernel_sum / (len(x) * len(x))

        y_kernel_sum = _get_kernel_sum(y, y, kernel)
        scaled_y_kernel_sum = y_kernel_sum / (len(y) * len(y))

        cross_kernel_sum = _get_kernel_sum(x, y, kernel)
        scaled_cross_kernel_sum = (-2 / (len(x) * len(y))) * cross_kernel_sum

        return scaled_x_kernel_sum + scaled_y_kernel_sum + scaled_cross_kernel_sum

    return mmd_estimate_metric

# TODO: Do I want the Gaussian Kernel?
# TODO: Add documentation in the above

def gaussian_kernel(x: np.ndarray, y: np.ndarray) -> np.float:
    if x == y:
        return 1

    dot_product = np.dot(x, y)
    return exp(dot_product)


from typing import Dict, List

from yahoofinancials import YahooFinancials
import numpy as np

class AssetData(object):
    """Based on the asset ticker and price data, generate the following:

    - List of rate of returns
    - Standard deviation of returns
    - Average rate of return
    """

    def __init__(self, ticker: str, price_data: List[float]):
        """n/a

        :param ticker:
        :param price_data:
        """
        self.ticker = ticker
        self.price_data = price_data
        self.returns = self.generate_returns(price_data)
        self.std_dev = np.std(self.returns)
        self.avg_return = np.average(self.returns)

    @staticmethod
    def generate_returns(prices: List[float]) -> List[float]:
        """Generates a list of rates of returns using natural log
        from a list of asset prices

        :param prices: List[float]
        :return:
        :rtype: List[float]
        """
        ret_val = []
        for i in range(0, len(prices) - 1):
            ret_val.append(np.log(prices[i] / prices[i + 1]))
        return ret_val

    @property
    def excess_returns(self) -> List[float]:
        """
        Returns minus the mean return
        """
        return [x - self.avg_return for x in self.returns]

    def as_dict(self) -> dict:
        return {
            'ticker': self.ticker,
            'price_data': self.price_data,
            'returns': self.returns,
            'std_dev': self.std_dev,
            'avg_return': self.avg_return
        }

class AssetMatrices(object):
    def __init__(self, asset_data: List[AssetData]):
        self.asset_data = asset_data
        self.n = len(asset_data[0].returns)
        self.avg_returns_vec = self.generate_avg_returns_vec()
        self.std_dev_vec = self.generate_std_dev_vec()
        self.excess_returns_matrix = self.generate_excess_returns_matrix()
        self.x_transpose_x_matrix = self.generate_x_transpose_x_matrix()
        self.variance_covariance_matrix = self.generate_variance_covariance_matrix()
        self.std_dev_matrix = self.generate_std_dev_matrix()
        self.correlation_matrix = self.generate_correlation_matrix()

    def generate_avg_returns_vec(self) -> np.ndarray:
        return np.array([asset_data.avg_return for asset_data in self.asset_data])

    def generate_std_dev_vec(self) -> np.ndarray:
        return np.array([asset_data.std_dev for asset_data in self.asset_data])

    def generate_excess_returns_matrix(self) -> np.matrix:
        return np.matrix([asset_data.excess_returns for asset_data in self.asset_data])

    def generate_x_transpose_x_matrix(self) -> np.ndarray:
        return np.matmul(self.excess_returns_matrix, self.excess_returns_matrix.T)

    def generate_variance_covariance_matrix(self) -> np.matrix:
        return self.x_transpose_x_matrix / self.n

    def generate_std_dev_matrix(self) -> np.ndarray:
        return np.outer(self.std_dev_vec, self.std_dev_vec.T)

    def generate_correlation_matrix(self) -> np.ndarray:
        return np.divide(self.variance_covariance_matrix, self.std_dev_matrix)

    def as_dict(self) -> dict:
        return {
            'n': self.n,
            'avg_returns_vec': self.avg_returns_vec.tolist(),
            'std_dev_vec': self.std_dev_vec.tolist(),
            'x_transpose_x_matrix': self.x_transpose_x_matrix.tolist(),
            'variance_covariance_matrix': self.variance_covariance_matrix.tolist(),
            'std_dev_matrix': self.std_dev_matrix.tolist(),
            'correlation_matrix': self.correlation_matrix.tolist(),
        }

def get_data(tickers, start_date, end_date, interval='weekly'):
    yahoo_financials = YahooFinancials(tickers)
    historical_stock_prices = yahoo_financials.get_historical_price_data(start_date, end_date, interval)
    return historical_stock_prices

def transform_yahoo_finance_dict(historical_prices) -> [Dict[str, List[float]], List[str]]:
    ret_val = {}
    dates = None
    for ticker, data in historical_prices.items():
        prices = [price for price in data['prices'] if price['close'] is not None]
        ret_val[ticker] = [price['close'] for price in prices][::-1]
        temp_dates = [price['formatted_date'] for price in prices][::-1]
        if not dates:
            dates = temp_dates
    return ret_val, dates

def generate_asset_data_array(price_dict: Dict[str, List[float]]) -> List[AssetData]:
    ret_val = []
    for ticker, price_data in price_dict.items():
        ret_val.append(AssetData(ticker, price_data))
    return ret_val


import numpy as np

def fuel_part1(mass):
    return int(np.floor(mass / 3) - 2)

def fuel_part2(mass):
    total_fuel = 0
    fuel = np.max([0, fuel_part1(mass)])
    while fuel > 0:
        total_fuel += fuel
        fuel = np.max([0, fuel_part1(fuel)])

    return int(total_fuel)

x = np.genfromtxt("day1_input.txt")

print(f"Part 1: {np.sum([fuel_part1(i) for i in x])}")
print(f"Part 2: {np.sum([fuel_part2(i) for i in x])}")


import matplotlib.pyplot as plt
import numpy as np
from numpy import linalg as LA

def generateXYInRange0to10( mu, cov, samplesize  ):
    x, y = np.random.multivariate_normal(mu,cov, samplesize).T
    issueindex = (np.where((x<0) | (y<0) | (y>10) | (x>10)))[0]
    if(len(issueindex)>0):
        append_x,append_y = generateXYInRange0to10(mu, cov, len(issueindex))
        x = np.delete(x, issueindex)
        y = np.delete(y, issueindex)
        x = np.append(x,append_x)
        y = np.append(y,append_y)
    return (x,y)

def getSlopeAndInterceptfor2DLine(w,x0):
    intercept = (w[0]*x0[0] + w[1]*x0[1]) // w[1]
    slope = -(w[0])// w[1]
    return (intercept,slope)

mu1 = np.array([3, 3])
mu2 = np.array([7, 7])
commoncov = np.array([[3, 0], [0, 3]])

cov1 = np.array([[3, 1], [2, 3]])
cov2 = np.array([[7, 2], [1, 7]]),
testSamplesx = np.random.uniform(low=0, high=10, size=100)
testSamplesy = np.random.uniform(low=0, high=10, size=100)

## For Question 1
# WHen covariance is same
x1,y1 = generateXYInRange0to10( mu1,commoncov,1000)
x2,y2 = generateXYInRange0to10( mu2,commoncov,1000)
classAClassificationx1 = []
classAClassificationy1 = []
classBClassificationx1 = []
classBClassificationy1 = []
w=mu1 - mu2
x0 = 0.5*(mu1+mu2)
c = w.T.dot(x0)
m = w.T
for i in range(len(testSamplesx)):
    x_test = [testSamplesx[i], testSamplesy[i]]
    line = m.dot(x_test) - c
    if( line > 0):
        classAClassificationx1.append(testSamplesx[i])
        classAClassificationy1.append(testSamplesy[i])
    else:
        classBClassificationx1.append(testSamplesx[i])
        classBClassificationy1.append(testSamplesy[i])

x = np.linspace(-15,15,1000)
plt.subplot(2, 1, 1)
intercept,slope = getSlopeAndInterceptfor2DLine(w,x0)
print(slope)
print(intercept)
plt.plot(x,slope*x+intercept, label= "Line y =mx+c",linestyle='solid',color = "k")
plt.scatter(classAClassificationx1, classAClassificationy1, label= "classA", color= "green", s=10)
plt.scatter(classBClassificationx1, classBClassificationy1, label= "classB", color= "red", s=10)
plt.legend()
plt.title("When Covariance is same and diagonal")
#####New Next Question
inverse1 = np.linalg.inv(cov1)
inverse2 = np.linalg.inv(cov2)

W1i = -0.5 * (inverse1)
w1i = inverse1.dot(mu1)
(sign, logdet1) = np.linalg.slogdet(cov1)
w1i0 = -0.5*( mu1.T.dot(inverse1).dot(mu1) + logdet1 )

W2i = -0.5 * (inverse2)
w2i = inverse2.dot(mu2)
(sign, logdet2) = np.linalg.slogdet(cov2)
w2i0 = -0.5*( mu1.T.dot(inverse1).dot(mu1) + logdet2 )

classAClassificationx2 = []
classAClassificationy2 = []
classBClassificationx2 = []
classBClassificationy2 = []

for i in range(len(testSamplesx)):
    x_test = [testSamplesx[i], testSamplesy[i]]
    x_test = np.array(x_test)
    log_prob1 = x_test.dot(W1i).dot(x_test) + w1i.dot(x_test) + w1i0
    log_prob2 = x_test.dot(W2i).dot(x_test) + w2i.dot(x_test) + w2i0

    if( log_prob1 > log_prob2 ):
        classAClassificationx2.append(testSamplesx[i])
        classAClassificationy2.append(testSamplesy[i])
    else:
        classBClassificationx2.append(testSamplesx[i])
        classBClassificationy2.append(testSamplesy[i])

x = np.linspace(-15,15,1000)
plt.subplot(2, 1, 2)
plt.scatter(classAClassificationx2, classAClassificationy2, label= "classA", color= "green", s=10)
plt.scatter(classBClassificationx2, classBClassificationy2, label= "classB", color= "red", s=10)
plt.title("When Covariance is different")
plt.legend()
plt.show()


import numpy as np
from scipy.stats import entropy

from independence_tests.mi.utils import _check_xy, _get_bins, _mi
from independence_tests.utils import fast_rank

def var_info(x, y, bins=None, rank=False, norm=False):
    """
    Variation of Information
    https://en.wikipedia.org/wiki/Variation_of_information

    References
    - "de Prado, Marcos Lopez. Machine Learning for Asset Managers.
      Cambridge University Press, 2020."
    - Hacine-Gharbi and Ravier (2018)

    First creates a histogram of the data, and then applies a discrete
    estimators for mutual information and entropy terms.
    """
    x, y = _check_xy(x, y)

    if rank is True:
        x = fast_rank(x)
        y = fast_rank(y)

    bins = _get_bins(bins=bins, x=x, y=y)
    count_xy = np.lib.histogramdd([x, y], bins)[0]

    i_xy, count_x, count_y = _mi(count_xy=count_xy)
    h_x = entropy(count_x)
    h_y = entropy(count_y)

    v_xy = max(h_x + h_y - 2.0 * i_xy, 0.0)
    if norm:
        h_xy = max(h_x + h_y - i_xy, 1e-8)
        return v_xy / h_xy
    return v_xy


# 3.4.2 ３層ニューラルネットワークの計算

# 2=>3=>2 のニューラルネットワーク

import numpy as np

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

print('------1層目------------------------------------')

X = np.array([1.0, 0.5])
W1 = np.array( [[ 0.1, 0.3, 0.5 ], [ 0.2, 0.4, 0.6 ]] ) # 2,3format
B1 = np.array( [0.1, 0.2, 0.3] )

print( W1.shape )
print( X.shape )
print( B1.shape )

A1 = np.dot(X, W1) + B1

print(A1)
Z1 = sigmoid(A1)
print(Z1)

print('------2層目------------------------------------')

W2 = np.array( [[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]] ) # 3,2 format
B2 = np.array( [0.1, 0.2] )

A2 = np.dot( Z1, W2 ) + B2
print(A2)
Z2 = sigmoid(A2)

print( Z2 )

print('------3層目------------------------------------')

W3 = np.array( [ [0.1, 0.3], [0.2, 0.4] ] )
B3 = [0.1,0.2]

A3 = np.dot(Z2, W3) + B3
print(A3)
Z3 = sigmoid(A3)
print(Z3)


import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import sklearn.model_selection
import statsmodels.api as sm
import numpy as np

data = pd.read_csv('insurance.csv')
# Plot the correlations between each pairs
# Convert our categorical features to numerical ones.
data['sex'] = data['sex'].astype('category').cat.codes
data['smoker'] = data['smoker'].astype('category').cat.codes
region_new = {'region': {'southwest': 1, 'southeast': 2, 'northwest': 3,'northeast': 4}}
data.replace(region_new, inplace=True)
# Now we can get the correlation between the 'charges' and all the other variables.
plt.figure(figsize=(12, 10))
cor = data.corr()
sns.heatmap(cor, annot=True, cmap=plt.cm.Reds)
plt.title('Correlations')
labels = data.columns
xi = list(range(7))
plt.xticks(xi, labels)
plt.yticks(xi, labels)
plt.show()
# ----------------------------------------------------------------------------------------------------------------------
# selecting the features Age, BMi, Smoker.
# split the data set into training and testing:
#  80% of data into training data and the rest should be the test data:
training_dataset, test_dataset = sklearn.model_selection.train_test_split(data, test_size=0.2)
X_train = training_dataset[['age','bmi','smoker']]
Y_train = training_dataset['charges']
X_test = test_dataset[['age','bmi','smoker']]
Y_test = test_dataset['charges']
Linear_Model = sm.OLS(Y_train, X_train).fit()
print(Linear_Model.summary())
# the predictive performance (in terms of the root mean squared error) for the training and for the test data.
Y_pred = Linear_Model.predict(X_train)
MSE = np.mean((Y_train-Y_pred)**2)
RMSE = np.sqrt(MSE)
print("Root mean squared error for the training data: {}".format(RMSE))
Y_pred = Linear_Model.predict(X_test)
MSE = np.mean((Y_test-Y_pred)**2)
RMSE = np.sqrt(MSE)
print("Root mean squared error for the test data: {}".format(RMSE))

# ----------------------------------------------------------------------------------------------------------------------

# residual versus fitted value plot
Y_pred = Linear_Model.predict(X_test)
Y_pred = Y_pred.to_numpy()
sns.residplot(Y_pred.reshape(-1),Y_test, data=data,lowess=True,
              line_kws={'color': 'red', 'lw': 1, 'alpha': 1})
plt.xlabel("Fitted values")
plt.ylabel("Residuals")
plt.title('Residual plot')
plt.show()

# ----------------------------------------------------------------------------------------------------------------------

# Plot Cook’s distance of your model and identify influential observations:
# c is the distance and p is p-value
influence = Linear_Model.get_influence()
(c, p) = influence.cooks_distance
plt.stem(np.arange(len(c)), c, markerfmt=",",use_line_collection=True)
plt.title('Cook’s distance')
plt.xlabel("Observations")
plt.ylabel("Influence")
plt.show()


from Include.Tuple import *
import numpy as np
import functools as ft

class Matrix4:
    length = 4

    def __init__(self):
        self.m = np.array([[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], dtype=float)

    @staticmethod
    def identity_matrix():
        M = Matrix4()
        M.m = np.identity(4, dtype=float)
        return M

    def transpose(self):
        M = Matrix4()
        M.m = self.m.transpose()
        return M

    def determinant(self):
        return round(np.linalg.det(self.m))

    def submatrix(self, row, column):
        M = Matrix3()
        M.m = np.delete(np.delete(self.m, row, 0), column, 1)
        return M

    def minor(self, row, column):
        return self.submatrix(row, column).determinant()

    def cofactor(self, row, column):
        return self.minor(row, column) if (row + column) % 2 == 0 else -self.minor(row, column)

    def invertible(self):
        return self.determinant() != 0

    @ft.lru_cache(None)
    def invert(self):
        M = Matrix4()
        M.m = np.linalg.inv(self.m)
        return M

    def translate(self, x, y, z):
        M = Matrix4.identity_matrix()
        M.m[0][3] = x
        M.m[1][3] = y
        M.m[2][3] = z
        return M * self

    def scale(self, x, y, z):
        M = Matrix4.identity_matrix()
        M.m[0][0] = x
        M.m[1][1] = y
        M.m[2][2] = z
        return M * self

    def rotate_x(self, radians):
        M = Matrix4.identity_matrix()
        M.m[1][1] = math.cos(radians)
        M.m[1][2] = -math.sin(radians)
        M.m[2][1] = math.sin(radians)
        M.m[2][2] = math.cos(radians)
        return M * self

    def rotate_y(self, radians):
        M = Matrix4.identity_matrix()
        M.m[0][0] = math.cos(radians)
        M.m[0][2] = math.sin(radians)
        M.m[2][0] = -math.sin(radians)
        M.m[2][2] = math.cos(radians)
        return M * self

    def rotate_z(self, radians):
        M = Matrix4.identity_matrix()
        M.m[0][0] = math.cos(radians)
        M.m[0][1] = -math.sin(radians)
        M.m[1][0] = math.sin(radians)
        M.m[1][1] = math.cos(radians)
        return M * self

    def rotate(self, yaw: float, pitch: float, roll: float):
        return Matrix4.identity_matrix().rotate_z(yaw).rotate_y(pitch).rotate_x(roll) * self

    def shear(self, xy, xz, yx, yz, zx, zy):
        M = Matrix4.identity_matrix()
        M.m[0][1] = xy
        M.m[0][2] = xz
        M.m[1][0] = yx
        M.m[1][2] = yz
        M.m[2][0] = zx
        M.m[2][1] = zy
        return M * self

    def __eq__(self, other):
        for x in range(self.length):
            for y in range(self.length):
                if (abs(self.m[x][y] - other.m[x][y]) > EPSILON):
                    return False
        return True

    def __mul__(self, other):
        if (type(other) is Matrix4):
            M = Matrix4()
            M.m = np.dot(self.m, other.m)
            return M
        elif (type(other) is Tuple):
            return Tuple(self.m[0][0] * other.x + self.m[0][1] * other.y + self.m[0][2] * other.z + self.m[0][3] * other.w,
                         self.m[1][0] * other.x + self.m[1][1] * other.y + self.m[1][2] * other.z + self.m[1][3] * other.w,
                         self.m[2][0] * other.x + self.m[2][1] * other.y + self.m[2][2] * other.z + self.m[2][3] * other.w,
                         self.m[3][0] * other.x + self.m[3][1] * other.y + self.m[3][2] * other.z + self.m[3][3] * other.w)

    def __hash__(self):
        return hash(bytes(self.m))

class Matrix3:
    length = 3

    def __init__(self):
        self.m = np.array([[0, 0, 0], [0, 0, 0], [0, 0, 0]])

    def determinant(self):
        return round(np.linalg.det(self.m))

    def submatrix(self, row, column):
        M = Matrix2()
        M.m = np.delete(np.delete(self.m, row, 0), column, 1)
        return M

    def minor(self, row, column):
        return self.submatrix(row, column).determinant()

    def cofactor(self, row, column):
        return self.minor(row, column) if (row + column) % 2 == 0 else -self.minor(row, column)

    def __eq__(self, other):
        for x in range(self.length):
            for y in range(self.length):
                if (abs(self.m[x][y] - other.m[x][y]) > EPSILON):
                    return False
        return True

class Matrix2:
    length = 2

    def __init__(self):
        self.m = np.array([[0, 0], [0, 0]])

    def determinant(self):
        return round(np.linalg.det(self.m))

    def __eq__(self, other):
        for x in range(self.length):
            for y in range(self.length):
                if (abs(self.m[x][y] - other.m[x][y]) > EPSILON):
                    return False
        return True


import math

import numpy as np

from . import gcd, modulus

def is_irreducible_poly(poly: np.poly1d, p: int) -> bool:
    """Detect whether a polynomial is irreducible or not.

    Args:
        poly (np.poly1d): A polynomial.
        p (int): A prime number.

    Returns:
        bool: Is the polynomial irreducible.
    """
    gx = np.poly1d([1, 0])
    for _ in range(1, len(poly.coeffs)//2 + 1):
        gx = modulus.modulus_pow_poly(gx, p, p, poly)
        if (gx == np.poly1d([1, 0])):
            # if gx = x (mod poly), x is the root of a smaller polynomial.
            return False
        gcd_poly = gcd.gcd_poly(
            poly, modulus.modulus_poly_over_fp(gx + np.poly1d([p-1, 0]), p), p)
        if len(gcd_poly.coeffs) != 1:
            return False

    return True

def is_prime(value: int) -> bool:
    """Detect whether a value is prime or not.

    Args:
        value (int): value.

    Returns:
        bool: Is the value prime.
    """
    if value < 2:
        return False
    if value == 2:
        return True
    if value % 2 == 0:
        return False

    for k in range(3, int(math.sqrt(value)) + 1, 2):
        if value % k == 0:
            return False

    return True


#pca model n componentes
from sklearn.decomposition import PCA
import numpy as np
from pylab import rcParams
import matplotlib.pyplot as plt
import pandas as pd

def pca_model_n_components(df,n_components):
    '''
	Definition:
		Initialize pca with n_components
	args:
		dataframe and number of components
	returns:
		pca initialized and pca fitted and transformed

    '''

    pca = PCA(n_components)
    return pca,pca.fit_transform(df)

def pca_model(df):
    '''
	Definition:
		Initialize pca
	args:
		dataframe
	returns:
		pca initialized and pca fitted and transformed

    '''
    pca = PCA()
    return pca,pca.fit_transform(df)

def get_min_components_variance(df,retain_variance):
    '''
	Definition:
		get min components to retain variance
	args:
		dataframe and retained_variance ratio
	returns:
		number of min components to retain variance

    '''
    pca,pca_tranformed = pca_model(df)
    cumulative_sum = np.cumsum(pca.explained_variance_ratio_)
    return min(np.where(cumulative_sum>=retain_variance)[0]+1)

def plot_curve_min_components_variance(df,mode="cumulative_variance"):
    '''
	Definition:
		plot curve of variance of pca
	args:
		dataframe and mode to be plotted (cumulative_variance or variance)
	returns:
		None, only plot the curve

    '''

    rcParams['figure.figsize'] = 12, 8

    pca,pca_transformed = pca_model(df)
    fig = plt.figure()

    explained_variance = pca.explained_variance_ratio_
    cumulative_sum = np.cumsum(explained_variance)

    n_components = len(explained_variance)
    ind = np.arange(n_components)

    ax = plt.subplot(111)
    if(mode=="cumulative_variance"):
        title = "Explained Cumulative Variance per Principal Component"
        ylabel = "Cumulative Variance (%)"
        ax.plot(ind, cumulative_sum)
        mark_1 = get_min_components_variance(df,0.2)
        mark_2 = get_min_components_variance(df,0.4)
        mark_3 = get_min_components_variance(df,0.6)
        mark_4 = get_min_components_variance(df,0.8)
        mark_5 = get_min_components_variance(df,0.9)
        mark_6 = get_min_components_variance(df,0.95)
        mark_7 = get_min_components_variance(df,0.99)

        plt.hlines(y=0.2, xmin=0, xmax=mark_1, color='green', linestyles='dashed',zorder=1)
        plt.hlines(y=0.4, xmin=0, xmax=mark_2, color='green', linestyles='dashed',zorder=2)
        plt.hlines(y=0.6, xmin=0, xmax=mark_3, color='green', linestyles='dashed',zorder=3)
        plt.hlines(y=0.8, xmin=0, xmax=mark_4, color='green', linestyles='dashed',zorder=4)
        plt.hlines(y=0.9, xmin=0, xmax=mark_5, color='green', linestyles='dashed',zorder=5)
        plt.hlines(y=0.95, xmin=0, xmax=mark_6, color='green', linestyles='dashed',zorder=6)
        plt.hlines(y=0.99, xmin=0, xmax=mark_7, color='green', linestyles='dashed',zorder=6)

        plt.vlines(x=mark_1, ymin=0, ymax=0.2, color='green', linestyles='dashed',zorder=7)
        plt.vlines(x=mark_2, ymin=0, ymax=0.4, color='green', linestyles='dashed',zorder=8)
        plt.vlines(x=mark_3, ymin=0, ymax=0.6, color='green', linestyles='dashed',zorder=9)
        plt.vlines(x=mark_4, ymin=0, ymax=0.8, color='green', linestyles='dashed',zorder=10)
        plt.vlines(x=mark_5, ymin=0, ymax=0.9, color='green', linestyles='dashed',zorder=11)
        plt.vlines(x=mark_6, ymin=0, ymax=0.95, color='green', linestyles='dashed',zorder=12)
        plt.vlines(x=mark_7, ymin=0, ymax=0.99, color='green', linestyles='dashed',zorder=12)
    else:
        title = "Variance per Principal Component"
        ylabel = "Variance (%)"
        ax.plot(ind, explained_variance)

    ax.set_xlabel("Number of principal components")
    ax.set_ylabel(ylabel)
    plt.title(title)

def report_features(feature_names,pca,component_number):
    '''
	Definition:
		This function returns the weights of the original features in relation to a component number of pca
	args:
		feature_names, pca model and the component_number
	returns:
		data frame with features names and the correspondent weights

    '''

    components = pca.components_

    feature_weights = dict(zip(feature_names, components[component_number]))
    sorted_weights = sorted(feature_weights.items(), key = lambda kv: kv[1])

    data = []

    for feature, weight, in sorted_weights:
        data.append([feature,weight])

    df =  pd.DataFrame(data,columns=["feature","weight"])
    df.set_index("feature",inplace=True)
    return df


# Copyright (C) 2020-2021 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
"""Logistic Regression base functions

Provides base functionality of logistic regression based on 'Homomorphic training of 30,000 logistic regression models'
   (https://eprint.iacr.org/2019/425) by Bergamaschi et al.
"""

import numpy as np

# Sigmoid function
def sigmoid(x):
  return 1. / (1 + np.exp(-x))

# 3-degree polynomial representation of sigmoid function, effective in range of [-5, 5]
def sigmoid_poly3(x):
  return 0.5 - 1.20096 * (x / 8.) + 0.81562 * (x / 8.)**3

# 4-degree polynomial representation of log(sigmoid(x)) function, effective in range of [-5, 5]
def log_sig4(x):
  return 0.000527 * x**4 - 0.0822 * x**2 + 0.5 * x - 0.78

# Realign target to -1, 1 and calculate X@y'
def get_z(X, y, add1=True):
  if add1:
    X_ = np.concatenate([np.ones((X.shape[0], 1)), X], axis=1)
  else:
    X_ = np.array(X)
  y_ = 2 * y - 1
  z = X_ * y_[:, None]
  return np.array(z)

# Compute initial weight for logistic regression
def get_initweight(X, y, add1=True):
  n = X.shape[0]
  z = get_z(X, y, add1)
  return np.sum(z, axis=0) / n

# get evaluation metrics (accuracy, f1 score, etc.)
def get_eval_metrics(actual, predicted):
  tp = 0
  tn = 0
  fp = 0
  fn = 0
  for a, p in zip(actual, predicted):
    if a == 1 and p == 1:
      tp += 1
    elif a == 1 and p == 0:
      fn += 1
    elif a == 0 and p == 1:
      fp += 1
    else:
      tn += 1

  acc = (tp + tn) / (tp + fp + tn + fn)
  if tp + fp > 0:
    precision = tp / (tp + fp)  # correct 1s over predicted 1s
  else:
    precision = 0.

  if tp + fn > 0:
    recall = tp / (tp + fn)  # correct 1s over actual 1s
  else:
    recall = 0.

  if precision + recall == 0:
    f1 = 0.
  else:
    f1 = 2 * (precision * recall) / (precision + recall)

  return acc, precision, recall, f1

# loss/gradient descent with standard sigmoid
def get_lgd(X, y, w):
  n = X.shape[0]
  z = get_z(X, y)
  zw = z @ w

  # calculate loss
  jw = np.sum(np.log(1 + np.exp(-zw))) / n

  # calculate gradient descent
  dw = 1. / (1 + np.exp(zw))
  dzw = z * dw[:, None]
  djw = -np.sum(dzw, axis=0) / n

  return jw, djw

# loss/gradient descent with poly3 sigmoid
def get_lgd_poly3(X, y, w):
  n = X.shape[0]
  z = get_z(X, y)
  zw = z @ w

  # calculate loss
  jw = np.sum(-log_sig4(zw)) / n

  # calculate gradient descent
  dw = sigmoid_poly3(zw)
  dzw = z * dw[:, None]
  djw = -np.sum(dzw, axis=0) / n

  return jw, djw

# test standard sigmoid
def test(X, y, w, add1=True):
  if add1:
    X_ = np.concatenate([np.ones((X.shape[0], 1)), X], axis=1)
  else:
    X_ = np.array(X)

  y_ = np.array([])
  for xi in X_:
    xiw = np.inner(xi, w)
    yi = sigmoid(xiw)  #1./(1+np.exp(-xiw))
    if yi > 0.5:
      yi = 1
    else:
      yi = 0

    y_ = np.append(y_, yi)

  acc, _, recall, f1 = get_eval_metrics(y, y_)
  return np.array(y_), acc, recall, f1

# test poly3 sigmoid
def test_poly3(X, y, w, add1=True):
  if add1:
    X_ = np.concatenate([np.ones((X.shape[0], 1)), X], axis=1)
  else:
    X_ = np.array(X)

  y_ = np.array([])

  for xi in X_:
    xiw = np.inner(xi, w)
    yi = sigmoid_poly3(-xiw)  #1./(1+np.exp(-xiw))
    if yi > 0.5:
      yi = 1
    else:
      yi = 0

    y_ = np.append(y_, yi)

  acc, _, recall, f1 = get_eval_metrics(y, y_)
  return np.array(y_), acc, recall, f1


import sys, math
import re
import time
import os

import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.gridspec as gridspec
from copy import deepcopy
from scipy import linalg
from scipy.optimize import curve_fit

import warnings
warnings.filterwarnings("ignore");

SNUMBER = pow(10, -124);

#https://en.wikipedia.org/wiki/Probability_integral_transform
def expCDF(L, x):
    return 1.0 - np.exp(-L*x);
def gausCDF(mu, sig, x):
    return 0.5*(1 + math.erf((x - mu)/(sig*np.sqrt(2))));

def main():
    verbosity = 1;
    binN = 100;
    rangeX = [0.0, 3.0];

    np.random.seed(1);
    expL = 1.0;
    gausMu = 1.5;
    gausSig = 0.5;
    sampleN = 10000;
#histogram
    nbins = np.linspace(rangeX[0], rangeX[1], binN);
    expPDF = np.random.exponential(1.0/expL, sampleN);
    gausPDF = np.random.normal(gausMu, gausSig, sampleN);
    expHist = np.zeros(binN);
    gausHist = np.zeros(binN);
    exp2UniHist = np.zeros(binN);
    gaus2UniHist = np.zeros(binN);
    for x in expPDF:
        if rangeX[0] < x and x < rangeX[1]:
            expHist[int(binN*(x-rangeX[0])/(rangeX[1]-rangeX[0]))] += 1;
        uniX = expCDF(expL, x);
        if rangeX[0] < uniX and uniX < rangeX[1]:
            exp2UniHist[int(binN*(uniX-rangeX[0])/(rangeX[1]-rangeX[0]))] += 1;
    for x in gausPDF:
        if rangeX[0] < x and x < rangeX[1]:
            gausHist[int(binN*(x-rangeX[0])/(rangeX[1]-rangeX[0]))] += 1;
        uniX = gausCDF(gausMu, gausSig, x);
        if rangeX[0] < uniX and uniX < rangeX[1]:
            gaus2UniHist[int(binN*(uniX-rangeX[0])/(rangeX[1]-rangeX[0]))] += 1;
#plots
    fig = plt.figure(figsize=(18, 14));
    gs = gridspec.GridSpec(2, 2);
    ax0 = fig.add_subplot(gs[0]);
    ax1 = fig.add_subplot(gs[1]);
    ax2 = fig.add_subplot(gs[2]);
    ax3 = fig.add_subplot(gs[3]);
    #plot 0
    ax0.plot(nbins, expHist, linewidth=2, color="blue", linestyle="steps-mid");
    ax0.set_title("Exp Distr", fontsize=24, y=1.03);
    ax0.set_xlabel("x", fontsize=18);
    ax0.set_ylabel("count", fontsize=18);
    ax0.set_xlim(rangeX[0], rangeX[1]);
    ax0.set_ylim(bottom=0);
    #plot 1
    ax1.plot(nbins, gausHist, linewidth=2, color="blue", linestyle="steps-mid");
    ax1.set_title("Normal Distr", fontsize=24, y=1.03);
    ax1.set_xlabel("x", fontsize=18);
    ax1.set_ylabel("count", fontsize=18);
    ax1.set_xlim(rangeX[0], rangeX[1]);
    ax1.set_ylim(bottom=0);
    #plot 2
    ax2.plot(nbins, exp2UniHist, linewidth=2, color="blue", linestyle="steps-mid");
    ax2.set_title("Exp to Unif Distr", fontsize=24, y=1.03);
    ax2.set_xlabel("x", fontsize=18);
    ax2.set_ylabel("count", fontsize=18);
    ax2.set_xlim(rangeX[0], rangeX[1]);
    ax2.set_ylim(bottom=0);
    #plot 3
    ax3.plot(nbins, gaus2UniHist, linewidth=2, color="blue",linestyle="steps-mid");
    ax3.set_title("Normal to Unif Distr", fontsize=24, y=1.03);
    ax3.set_xlabel("x", fontsize=18);
    ax3.set_ylabel("count", fontsize=18);
    ax3.set_xlim(rangeX[0], rangeX[1]);
    ax3.set_ylim(bottom=0);
#save plots
    exepath = os.path.dirname(os.path.abspath(__file__));
    filenameFig = exepath + "/fig/uniTransSampling.png";
    gs.tight_layout(fig);
    plt.savefig(filenameFig);

    if verbosity >= 1:
        print("Creating the following files:");
        print(filenameFig);

if __name__ == "__main__":
    print("\n##############################################################Head");
    main();
    print("##############################################################Tail");


# This script solves the Lane-Emden equation (Section 3.8.1) in the TFC book
# Updated: 17 Mar 2021
####################################################################################################
# Differential Equation
#   y'' + 2/x y' + y^k = 0      where   k > 0 and x > 0
#
#   subject to: y(0) = 1, y'(0) = 0
####################################################################################################
from tfc import utfc
from tfc.utils import egrad, NLLS, LS, MakePlot
import jax.numpy as np

import numpy as onp
####################################################################################################

def runLaneEmden(N, m, basis, k, xf):
## user defined parameters: ************************************************************************
# N      - number of discretization points
# m      - number of basis function terms
# basis  - basis function type
# k      - specific problem type, k >=0 (analytical solution known for k = 0, 1, and 5)

    ## problem initial conditions: *****************************************************************
    xspan = [0., xf] # problem domain range [x0, xf], where x₀ > 0
    y0  = 1.  # y(x0)  = 1
    y0p = 0.  # y'(x0) = 0
    nC  = 2   # number of constraints

    ## construct univariate tfc class: *************************************************************
    tfc = utfc(N, nC, int(m), basis = basis, x0=xspan[0], xf=xspan[1])
    x = tfc.x

    H = tfc.H
    dH = tfc.dH
    H0 = H(x[0])
    H0p = dH(x[0])

    ## define tfc constrained expression and derivatives: ******************************************
    # switching function
    phi1 = lambda x: np.ones_like(x)
    phi2 = lambda x: x

    # tfc constrained expression
    y = lambda x,xi: np.dot(H(x),xi) + phi1(x)*(y0  - np.dot(H0,xi)) + phi2(x)*(y0p - np.dot(H0p,xi))
    yp = egrad(y)
    ypp = egrad(yp)

    ## define the loss function: *******************************************************************
    L = lambda xi: x*ypp(x,xi) + 2.*yp(x,xi) + x*y(x,xi)**k

    ## solve the problem via nonlinear least-squares ***********************************************
    xi = np.zeros(H(x).shape[1])

    # if k==0 or k==1, the problem is linear
    if k == 0 or k == 1:
        xi,time = LS(xi,L,timer=True)
        iter = 1

    else:
        xi,iter,time = NLLS(xi,L,timer=True)

    ## compute the error (if k = 0, 1, or 5): ******************************************************
    if k == 0:
        ytrue = 1. - 1./6. * x**2
    elif k == 1:
        ytrue = onp.ones_like(x)
        ytrue[1:] = np.sin(x[1:]) / x[1:]
    elif k == 5:
        ytrue = (1. + x**2/3)**(-1/2)
    else:
        ytrue = np.empty_like(x)

    err = np.abs(y(x,xi) - ytrue)

    ## compute the residual of the loss vector: ****************************************************
    res = np.abs(L(xi))

    return x, y(x,xi), err, res

####################################################################################################
####################################################################################################
## run the lane-emden for each case of k
# runLaneEmden(N, m, basis, k, x)
x_k0, sol_k0, err_k0, res_k0 = runLaneEmden(100, 2,  'CP', 0, 10.)
x_k1, sol_k1, err_k1, res_k1 = runLaneEmden(100, 22, 'CP', 1, 10.)
x_k5, sol_k5, err_k5, res_k5 = runLaneEmden(100, 62, 'CP', 5, 10.)
####################################################################################################
####################################################################################################

## plotting: ***************************************************************************************

# figure 1: solution
p1 = MakePlot(r'$x$',r'$y(x)$')
p1.ax[0].plot(x_k0,sol_k0, label='k=0')
p1.ax[0].plot(x_k1,sol_k1, label='k=1')
p1.ax[0].plot(x_k5,sol_k5, label='k=5')
p1.ax[0].grid(True)
p1.ax[0].set_xlim(0., 10.)
p1.ax[0].set_ylim(-0.5, 1)
p1.ax[0].legend()
p1.PartScreen(7.,6.)
p1.show()

# figure 2: residual
p2 = MakePlot(r'$x$',r'$|L(\xi)|$')
p2.ax[0].plot(x_k0,res_k0, '*', label='k=0')
p2.ax[0].plot(x_k1,res_k1, '*', label='k=1')
p2.ax[0].plot(x_k5,res_k5, '*', label='k=5')
p2.ax[0].grid(True)
p2.ax[0].set_yscale('log')
p2.ax[0].set_xlim(0., 10.)
p2.ax[0].legend()
p2.PartScreen(7.,6.)
p2.show()

# figure 3: error (if k = 0, 1, or 5)
p3 = MakePlot(r'$x$',r'$|y_{true} - y(x)|$')
p3.ax[0].plot(x_k0,err_k0, '*', label='k=0')
p3.ax[0].plot(x_k1,err_k1, '*', label='k=1')
p3.ax[0].plot(x_k5,err_k5, '*', label='k=5')
p3.ax[0].grid(True)
p3.ax[0].set_yscale('log')
p3.ax[0].set_xlim(0., 10.)
p3.ax[0].legend()
p3.PartScreen(7.,6.)
p3.show()


import numpy

def EditDistance(s1, s2):
    """ Computes the edit distance of two strings
    (str, str) -> (int, 2D-array) """
    ln_s1 = len(s1)
    ln_s2 = len(s2)

    # Initializing the matrix
    Matrix = numpy.zeros((ln_s1+1 , ln_s2+1))
    for i in range(ln_s2+1):
        Matrix[0][i] = i

    for i in range(ln_s1+1):
        Matrix[i][0] = i

    # Filling the matrix
    for i in range(1, ln_s1+1):
        for j in range(1, ln_s2+1):
            insertion = Matrix[i][j-1]   + 1
            deletion  = Matrix[i-1][j]   + 1
            mismatch  = Matrix[i-1][j-1] + 1
            match     = Matrix[i-1][j-1]
            if s1[i-1] == s2[j-1]:
                Matrix[i][j] = min(insertion, deletion, match)
            if s1[i-1] != s2[j-1]:
                Matrix[i][j] = min(insertion, deletion, mismatch)

    return (int(Matrix[ln_s1][ln_s2]), Matrix)

def OptimalAlignment(Matrix, s1, s2, top, bottom, i, j):
    """ Finds the optimal alignment of two strings given the edit matrix
    (2D-array, str, str, str, str, int, int) -> (str, str) """

    if i == 0 and j == 0:
        return (' '.join(top[::-1]), ' '.join(bottom[::-1]))

    if i>0 and Matrix[i][j] == Matrix[i-1][j] + 1:
        top.append(f'|{s1[i-1]}|')
        bottom.append('|-|')
        return OptimalAlignment(Matrix, s1, s2, top, bottom, i-1, j)

    elif j>0 and Matrix[i][j] == Matrix[i][j-1] + 1:
        bottom.append(f'|{s2[j-1]}|')
        top.append('|-|')
        return OptimalAlignment(Matrix, s1, s2, top, bottom, i, j-1)

    else:
        top.append(f'|{s1[i-1]}|')
        bottom.append(f'|{s2[j-1]}|')
        return OptimalAlignment(Matrix, s1, s2, top, bottom, i-1, j-1)

if __name__ == '__main__':
    s1, s2 = input(), input()
    edit_distance, Matrix = EditDistance(s1, s2)
    top, bottom = OptimalAlignment(Matrix, s1, s2, [], [], len(s1), len(s2))

    print(f'{edit_distance}')

#    print(f"Optimal alignment:\n{top}\n{bottom}")


from __future__ import print_function

import numpy as np

import pytest

EPS = 1e-8

def kmeans_cluster(x, k, max_iter=10, threshold=1e-3, verbose=False):
    # init
    centers = np.zeros([k, x.shape[-1]])
    for i in range(k):
        total_num = len(x)
        chosen_num = max(1, total_num / k)
        random_ids = np.random.choice(total_num, chosen_num, replace=False)
        centers[i, :] = np.mean(x[random_ids])

    cur_total_dist = np.float('inf')
    dist = np.zeros([k, len(x)])
    for i in range(max_iter):
        for j in range(k):
            for m, p in enumerate(x):
                dist[j, m] = np.mean((p - centers[j]) ** 2)

        min_idx = np.argmin(dist, 0)
        for j in range(k):
            ele = [x[m] for m, idx in enumerate(min_idx) if idx == j]
            centers[j, :] = np.mean(ele)

        total_dist = 0
        for j in range(k):
            dist_j = [np.mean((x[m] - centers[j]) ** 2)
                      for m, idx in enumerate(min_idx) if idx == j]
            total_dist += sum(dist_j)

        cur_total_dist, last_total_dist = total_dist, cur_total_dist
        if verbose:
            print('Iter: {}, total dist: {}'.format(i, total_dist))

        if last_total_dist - cur_total_dist < threshold:
            break

    for j in range(k):
        for m, p in enumerate(x):
            dist[j, m] = np.mean((p - centers[j]) ** 2)
    min_idx = np.argmin(dist, 0)

    return centers, min_idx

def test_kmeans_cluster():
    np.random.seed(1111)
    dims = range(1, 5)
    ks = range(2, 5)

    for dim in dims:
        for k in ks:
            print('Dim {}, K {}'.format(dim, k))
            x = np.random.random([100, dim])
            kmeans_cluster(x, k, max_iter=10, threshold=1e-2, verbose=True)

def demo():
    import matplotlib.pyplot as plt

    np.random.seed(1111)
    dim, k = 2, 2

    # generate data
    num = 50
    mean1 = np.zeros(dim)
    mean2 = np.ones(dim) * 2
    cov1 = np.eye(dim)
    cov2 = np.eye(dim) * 0.5

    x1 = np.random.multivariate_normal(mean1, cov1, [num, ])
    x2 = np.random.multivariate_normal(mean2, cov2, [num, ])
    x = np.concatenate([x1, x2], 0)

    plt.scatter(x1[:, 0], x1[:, 1], c='r')
    plt.scatter(x2[:, 0], x2[:, 1], c='g')

    centers, _ = kmeans_cluster(x, k, max_iter=10, threshold=1e-4)
    plt.scatter(centers[:, 0], centers[:, 1], c='y', s=500, marker='^')

if __name__ == '__main__':
    pytest.main([__file__, '-s'])