""" Util Functions """
import math
import numpy as np
import random

def addition(first: int, second: int) -> int:
    """
    Returns sum of two integers.

    Parameters:
        first (int) : First Integer
        second (int) : Second Integer
    """
    return first + second

def subtraction(first: int, second: int) -> int:
    """
    Returns subtraction of two integers.

    Parameters:
        first (int) : First Integer
        second (int) : Second Integer
    """
    return first - second

def multiplication(first: int, second: int) -> int:
    """
    Returns multiplication of two integers.

    Parameters:
        first (int) : First Integer
        second (int) : Second Integer
    """
    return first * second

def division(first: int, second: int) -> float:
    """
    Returns float division of two integers.

    Parameters:
        first (int) : First Integer
        second (int) : Second Integer
    """
    return first / second

def integer_division(first: int, second: int) -> int:
    """
    Returns integer division of two integers.

    Parameters:
        first (int) : First Integer
        second (int) : Second Integer
    """
    return first // second

def power(base_int: int, power_int: int) -> int:
    """
    Returns base raised to the power.

    Parameters:
        first (int) : First Integer
        second (int) : Second Integer
    """
    return base_int ** power_int

def modulo(dividend: int, divisor: int) -> int:
    """
    Returns remainder of dividend // divisor.

    Parameters:
        first (int) : First Integer
        second (int) : Second Integer
    """
    return dividend % divisor

def log(first: int, base: int) -> float:
    """
    Returns sum of two integers.

    Parameters:
        first (int) : Value to calculate base for. Should be > 0.
        second (int) : Logarithmic base to use.
    """
    return math.log(first, base)

def sigmoid(z):
    """
    Compute the sigmoid of z

    Arguments:
    x -- A scalar or numpy array of any size.

    Return:
    s -- sigmoid(z)
    """

    ### START CODE HERE ### (≈ 1 line of code)
    s = 1 / (1 + np.exp(-z))
    ### END CODE HERE ###

    return s

def rand_between(start: int, stop: int) -> int:
    """
    Returns a random number between two integers.
    Parameters:
        start (int) : Lower limit of Random Number
        stop (int) : Upper Limit of Random Number
    """
    return random.randint(start, stop)

def mean(numbers: list) -> float:
    """
    Returns an float of the mean of numbers provided in mean 64 for more accuracy.
    Parameters:
        numbers (list) : A list of numbers
    """
    return np.mean(np.array(numbers), dtype=np.float64)

def range(first: int, second: int) -> float:
    """
    Returns the range between 2 numbers.
    Parameters:
        start (int) : First number
        end (int) : Second number
    """

    biggest = 0
    smallest = 0

    if (first < second):
        biggest = second
        return biggest - first

    return first - second

def median(numbers: list) -> float:
    """
    Returns the median of a list of numbers.
    Parameters:
         numbers (list) : A list of numbers
    """
	list_size = len(numbers)
	numbers.sort()

	if list_size % 2 == 0:
		median1 = numbers[list_size//2]
		median2 = numbers[list_size//2 - 1]
		median = (median1 + median2)/2
	else:
		median = numbers[list_size//2]

	return median

def mode(numbers: list) -> dict:
    """
    Returns the mode of a list of numbers as a dictionary.
    Parameters:
         numbers (list) : A list of numbers
    """
    numbers.sort()
    ordered_list = []

    i = 0
    while i < len(numbers):
        ordered_list.append(numbers.count(numbers[i]))
        i += 1

    count_num_dict = dict(zip(numbers, ordered_list))
    filter_dict = {k for (k, v) in count_num_dict.items() if v == max(ordered_list)}

    return int(str(filter_dict).replace("{","").replace("}",""))


#!/usr/bin/env python

"""fit_2dgaussian.py -- given

Usage: fit_2dgaussian [-h] [-v] [-s STRING] [-o] <fitsfile> <init_params>

Arguments:
    fitsfile STRING
        Containing the Gaussian you want to fit
    init_params STRING
        amplitude,x0,y0,sigma_x,sigma_y,theta (from +y axis, clockwise),offset.

Options:
    -h, --help                                  Show this screen
    -v, --verbose                               Show extra information (including fitted parameters) [default: False]
    -s STRING, --saveloc STRING                 Save model as fits file, e.g. ./model.fits
    -o, --overwrite                             Overwrite if saveloc exists.

Examples:
    python fit_2dgaussian.py gaussian2fit.fits 3,100,100,20,40,0,10
"""

import docopt
import astropy.io.fits as fits

import numpy as np
from scipy.optimize import curve_fit

def twoD_Gaussian(xytuple, amplitude, xo, yo, sigma_x, sigma_y, theta, offset):
    (x,y) = xytuple
    xo    = float(xo)
    yo    = float(yo)
    a     = (np.cos(theta)**2)/(2*sigma_x**2) + (np.sin(theta)**2)/(2*sigma_y**2)
    b     = -(np.sin(2*theta))/(4*sigma_x**2) + (np.sin(2*theta))/(4*sigma_y**2)
    c     = (np.sin(theta)**2)/(2*sigma_x**2) + (np.cos(theta)**2)/(2*sigma_y**2)
    g     = offset + amplitude*np.exp( - (a*((x-xo)**2) + 2*b*(x-xo)*(y-yo)
                            + c*((y-yo)**2)))
    return g.ravel()

def fit_2dgaussian(z,parameters_initial_guess,verbose=False):
    '''Fit 2D gaussian to z (lenx by leny array).
    The parameters_initial_guess should include:
    amplitude,
    x0,
    y0,
    sigma_x,
    sigma_y,
    theta (from +y axis, clockwise),
    offset.'''

    # Create x and y indices
    lenx,leny = np.shape(z)
    x         = np.linspace(0, lenx-1, lenx)
    y         = np.linspace(0, leny-1, leny)
    x, y      = np.meshgrid(x, y)
    xytuple   = tuple((x,y))

    # Unwrap z
    z=z.ravel()

    # Do fit
    popt, pcov = curve_fit(twoD_Gaussian, xytuple, z, p0=parameters_initial_guess)

    # Print parameters
    if verbose:
        print('The model parameters are: ',popt)
        print('amplitude: ',popt[0])
        print('x0: ',popt[1])
        print('y0: ',popt[2])
        print('sigma_x: ',popt[3])
        print('sigma_y: ',popt[4])
        print('theta (clockwise from y-axis: ',popt[5])
        print('offset: ',popt[6])

    # Calculate model (i.e. create gaussian with fitted params)
    model            = twoD_Gaussian(xytuple, *popt)
    model            = model.reshape(lenx,leny)
    model_parameters = popt

    return model, model_parameters, x, y

def fit_2dgaussian_fitsinput(fitsfile,init_params,save=False,overwrite=False,verbose=False):
    # Read in fits file
    z                 = fits.getdata(fitsfile)
    # Unwrap initial parameters and make floats, then wrap it up
    [amplitude,x0,y0,sigma_x,sigma_y,theta,offset] = [float(x) for x in init_params.split(',')]
    initial_guess     = (amplitude,x0,y0,sigma_x,sigma_y,theta,offset)
    # Do fit
    data_fitted, popt = fit_2dgaussian(z,initial_guess,verbose=verbose)
    hduP=fits.PrimaryHDU(im)
    hdulist=fits.HDUList(hduP)
    keynames = ['amplitude','x0','y0','sigma_x','sigma_y','theta','offset']
    for name, value in zip(keynames,popt):
        hdulist[0].header.update(name,value)
    if overwrite:
        hdulist.writeto(saveloc,overwrite=True)
        print('File saved (overwrite=True) ',saveloc)
    else:
        hdulist.writeto(saveloc)
        print('File saved (overwrite=False) ',saveloc)
    return

####################### BODY OF PROGRAM STARTS HERE ########################

if __name__ == "__main__":

    arguments = docopt.docopt(__doc__)

    # Mandatory argument
    fitsfile    = arguments['<fitsfile>']
    init_params = argumetns['<init_params>']

    # Non-mandatory options without arguments
    verbose     = arguments['--verbose']
    save        = arguments['--saveloc']
    overwrite   = arguments['--overwrite']

    # Perform fit
    fit_2dgaussian_fitsinput(fitsfile,init_params,save=save,overwrite=overwrite,verbose=verbose)


import sys
import numpy as np
from scipy.integrate import odeint
import csv

#based on https://scipython.com/blog/the-double-pendulum/

# The gravitational acceleration (m.s-2).
g = 9.81

def deriv(y, t, L1, L2, m1, m2):
    """Return the first derivatives of y = theta1, z1, theta2, z2."""
    theta1, z1, theta2, z2 = y

    c, s = np.cos(theta1-theta2), np.sin(theta1-theta2)

    theta1dot = z1
    z1dot = (m2*g*np.sin(theta2)*c - m2*s*(L1*z1**2*c + L2*z2**2) -
             (m1+m2)*g*np.sin(theta1)) / L1 / (m1 + m2*s**2)
    theta2dot = z2
    z2dot = ((m1+m2)*(L1*z1**2*s - g*np.sin(theta2) + g*np.sin(theta1)*c) +
             m2*L2*z2**2*s*c) / L2 / (m1 + m2*s**2)
    return theta1dot, z1dot, theta2dot, z2dot

def solve(L1, L2, m1, m2, tmax, dt, y0):
    t = np.arange(0, tmax+dt, dt)

    # Do the numerical integration of the equations of motion
    y = odeint(deriv, y0, t, args=(L1, L2, m1, m2))
    theta1, theta2 = y[:,0], y[:,2]

    # Convert to Cartesian coordinates of the two bob positions.
    x1 = L1 * np.sin(theta1)
    y1 = -L1 * np.cos(theta1)
    x2 = x1 + L2 * np.sin(theta2)
    y2 = y1 - L2 * np.cos(theta2)

    return theta1, theta2, x1, y1, x2, y2

def mappableSolve(args):
	L1, L2, m1, m2, tmax, dt, theta1_init, theta2_init = args
	y0 = np.array([
		theta1_init,
		0.0,
		theta2_init,
		0.0
	])
    #vracam i thete da znam za cega su ovi rezultati (da ih posle mogu
    #pisati u fajl)
	return theta1_init, theta2_init, solve(L1, L2, m1, m2, tmax, dt, y0)

def simulate_pendulum(theta_resolution, tmax, dt, filename, parallel):
    # Pendulum rod lengths (m), bob masses (kg).
    L1, L2 = 1.0, 1.0
    m1, m2 = 1.0, 1.0

    # Maximum time, time point spacings (all in s).
    #tmax, dt = 30.0, 0.01

    theta1_inits = np.linspace(0, 2*np.pi, theta_resolution)
    theta2_inits = np.linspace(0, 2*np.pi, theta_resolution)

    import itertools
    t1t2_inits = itertools.product(theta1_inits, theta2_inits)
    params = ((L1, L2, m1, m2, tmax, dt, t1t2_i[0], t1t2_i[1]) for t1t2_i in t1t2_inits)

    if parallel:
        from multiprocessing import Pool
        pool = Pool()
        solutions = pool.imap(mappableSolve, params)
    else:
        solutions = itertools.imap(mappableSolve, params)

    with open(filename, 'wb') as csvfile:
	    csvwriter = csv.writer(csvfile, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL)
	    csvwriter.writerow(["theta1_init", "theta2_init", "theta1_last", "theta2_last", "x1_last", "y1_last", "x2_last", "y2_last"])
	    for t1i, t2i, results in solutions:
	       theta1, theta2, x1, y1, x2, y2 = results #to je ono sto je solve izracunao
	       csvwriter.writerow([t1i, t2i, theta1[-1], theta2[-1], x1[-1], y1[-1], x2[-1], y2[-1]])

def plot_the_thing(filename):

	rows = []
	with open(filename, 'rb') as csvfile:
		csvreader = csv.reader(csvfile, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL)
		next(csvreader, None)	#skip header
		for row in csvreader:
			rows.append(row)

	x1_lasts = [float(row[4]) for row in rows]
	y1_lasts = [float(row[5]) for row in rows]
	x2_lasts = [float(row[6]) for row in rows]
	y2_lasts = [float(row[7]) for row in rows]

	from matplotlib import pyplot as plt
	#t = range(0, len(rows[0]))
	#plt.plot(t, rows[0], label="theta 1")
	plt.plot(x1_lasts, y1_lasts, 'ro', label='1')
	plt.plot(x2_lasts, y2_lasts, 'go', label='2')
	#plt.axis([-1, 1, -1, 1])
	plt.legend()
	plt.savefig("points.png")

def do_the_thing(theta_resolution, tmax, dt, filename, graph, parallel):
    simulate_pendulum(theta_resolution, tmax, dt, filename, parallel)
    if graph:
        plot_the_thing(filename)


import numpy as np

import torch
from sklearn.datasets import make_circles

def rand_cirlce2d(batch_size):
    """ This function generates 2D samples from a filled-circle distribution in a 2-dimensional space.

        Args:
            batch_size (int): number of batch samples

        Return:
            torch.Tensor: tensor of size (batch_size, 2)
    """
    r = np.random.uniform(size=(batch_size))
    theta = 2 * np.pi * np.random.uniform(size=(batch_size))
    x = r * np.cos(theta)
    y = r * np.sin(theta)
    z = np.array([x, y]).T
    return torch.from_numpy(z).type(torch.FloatTensor)

def rand_ring2d(batch_size):
    """ This function generates 2D samples from a hollowed-cirlce distribution in a 2-dimensional space.

        Args:
            batch_size (int): number of batch samples

        Return:
            torch.Tensor: tensor of size (batch_size, 2)
    """
    circles = make_circles(2 * batch_size, noise=.01)
    z = np.squeeze(circles[0][np.argwhere(circles[1] == 0), :])
    return torch.from_numpy(z).type(torch.FloatTensor)

def rand_uniform2d(batch_size):
    """ This function generates 2D samples from a uniform distribution in a 2-dimensional space

        Args:
            batch_size (int): number of batch samples

        Return:
            torch.Tensor: tensor of size (batch_size, 2)
    """
    z = 2 * (np.random.uniform(size=(batch_size, 2)) - 0.5)
    return torch.from_numpy(z).type(torch.FloatTensor)

def rand(dim_size):
    def _rand(batch_size):
        return torch.rand((batch_size, dim_size))
    return _rand

def randn(dim_size):
    def _randn(batch_size):
        return torch.randn((batch_size, dim_size))
    return _randn


import numpy as np
from numpy.linalg import norm

def myCG(A, b, tol, maxiter):
    """
    Conjugate gradient mathods. Solve Ax=b for PD matrices.
    INPUT:
        A: Positive definite matrix
        b: column vector
        tol: inexactness tolerance
        maxiter: maximum iterations
    OUTPUT:
        x: best solution x
        rel_res: relative residual
        T: iterations
    """
    b = b.reshape(len(b), 1)
    x = np.zeros((len(b),1))
    r = b
    T = 0
    rel_res_best = np.inf
    rel_res = 1

    delta = r.T.dot(r)
    p = np.copy(r)
    x = np.zeros((len(b), 1))

    while T < maxiter and rel_res > tol:
        T += 1
        Ap = Ax(A, p)
        pAp = p.T.dot(Ap)
#        if pAp < 0:
#            print('pAp =', pAp)
#            raise ValueError('pAp < 0 in myCG')
        alpha = delta/pAp
        x = x + alpha*p
        r = r - alpha*Ap
        rel_res = norm(r)/norm(b)
        if rel_res_best > rel_res:
            rel_res_best = rel_res
        prev_delta = delta
        delta = r.T.dot(r)
        p = r + (delta/prev_delta)*p
    return x, rel_res, T

def Ax(A, x):
    if callable(A):
        Ax = A(x)
    else:
        Ax = A.dot(x)
    return Ax


# 10.008 Hands-on Activity 3 Verifier Script
# Check validity of inverse-square relationship between intensity and height
# Also check validity of black-body radiation relationship between spectral irradiance and wavelength
# Created by James Raphael Tiovalen (2020)

# Import scientific libraries
import numpy as np
from scipy.optimize import curve_fit
from sklearn import metrics

# Define inverse-square law function
def inverse_square(h, k):
    return k / (h ** 2)

if __name__ == "__main__":
    heights = np.array([6.5, 9.0, 12.0, 17.0]).astype(np.float64)

    red_intensities = np.array(
        [0.8319219036, 0.4896690263, 0.249988924, 0.1288602701]
    ).astype(np.float64) # λ = 633 nm
    green_intensities = np.array(
        [0.410783097, 0.2675019527, 0.1692426359, 0.0913581608]
    ).astype(np.float64) # λ = 524 nm
    blue_intensities = np.array(
        [1.002523336, 0.5135877745, 0.398544113, 0.1931090032]
    ).astype(np.float64) # λ = 460 nm

    for intensity in [red_intensities, green_intensities, blue_intensities]:
        popt, pcov = curve_fit(inverse_square, heights, intensity, method="lm")
        r_squared = round(
            metrics.r2_score(inverse_square(heights, *popt), intensity), 3
        )

        print(f"Equation: I = {popt[0]} / h^2")
        print(f"R^2 = {r_squared}")


import numpy as np
from scipy.special import softmax

np.set_printoptions(precision=6)

def k_softmax(x):
    exp = np.exp(x)
    return exp / np.sum(exp, axis=1)

if __name__ == "__main__":
    x = np.array([[1, 4.2, 0.6, 1.23, 4.3, 1.2, 2.5]])
    print("Input Array: ", x)
    print("Softmax Array: ", k_softmax(x))
    print("Softmax Array: ", softmax(x))


from numpy import pi, cos, exp, array
from NIM.benchmarks.benchmark import Benchmark

class Easom(Benchmark):
    """dim: 2"""
    def __init__(self, lower=-100, upper=100, dimension=2):
        super(Easom, self).__init__(lower, upper, dimension)

    def get_optimum(self):
        return array([[pi, pi]]), -1

    @staticmethod
    def eval(sol):
        term1 = -cos(sol[0]) * cos(sol[1])
        term2 = exp(-(sol[0] - pi) ** 2 - (sol[1] - pi) ** 2)

        return term1 * term2


from sympy import symbols, integrate, Rational, lambdify
import matplotlib.pyplot as plt
import numpy as np

# Pollution from a factory is entering a lake. The rate of concentration of the pollutant at time t is given by
t = symbols( 't', positive = True )
dP = 91*t ** Rational( 5, 2 )

# where t is the number of years since the factory started introducing pollutants into the lake.

#  Ecologists estimate that the lake can accept a total level of pollution of 7600 units before all the fish life in the lake ends.
#  Can the factory operate for 5 years without killing all the fish in the lake?

# Yes, because:
P = integrate( dP, ( t, 0, 5 ) ).evalf()
round( P )
# is less than 7600.

# What is the polution doing?
g_xlim = [ 1, 10 ]
g_ylim = [ -5, 15 ]

lam_p = lambdify( t, integrate( dP, t ), np )

x_vals = np.linspace( g_xlim[0], g_xlim[1], 1000, endpoint=True )
y_vals = lam_p( x_vals )
plt.plot( x_vals, y_vals )
plt.show()


import math
import numpy as np

## 1a
a=123
b=321
print (a*b)

## 1b
print ("\nvec1:")
vec1 = [3, 8, 9, 10, 12]
print (vec1)

print ("\nvec2:")
vec2 = [8, 7, 7, 5, 6]
print (vec2)

print ("\nsuma wektorow:")
print ([x+y for x,y in zip (vec1, vec2)])

print ("\niloczyn wektorow (po wspolrzednych):")
vec3 = [x*y for x,y in zip (vec1, vec2)]
print (vec3)

## 1c
print ("\niloczyn skalarny:")
sum = 0
for x in vec3:
    sum = sum + x
print (sum)

print ("\ndlugosc vec1:")
vec1_len = 0
for x in vec1:
    vec1_len = x * x + vec1_len
vec1_len = math.sqrt (vec1_len)
print (vec1_len)

print ("\ndlugosc vec2:")
vec2_len = 0
for x in vec2:
    vec2_len = x * x + vec2_len
vec2_len = math.sqrt (vec2_len)
print (vec2_len)

## 1d

mat1 = np.matrix ('1 2 3; 2 2 2; 4 1 7')
print ("\nmat1:")
print (mat1)
mat2 = np.matrix ('4 5 6; 4 5 6; 4 5 6')
print ("\nmat2:")
print (mat2)

mat3 = np.multiply (mat1, mat2)
print ("\nmnozenie po wspolrzednych:")
print (mat3)

mat3 = np.dot(mat1, mat2)
print ("\nmnozenie wektorowe")
print (mat3)

## 1e
vec4 = np.random.randint(low=1, high=100, size=50)
print ("\n50 losowych liczb (vec4):")
print (vec4)

## 1f
min_vec4 = np.min (vec4)
max_vec4 = np.max (vec4)

print ("\nmin(vec4):")
print (min_vec4)
print ("\nmax(vec4):")
print (max_vec4)
print ("\nmean(vec4):")
print (np.mean(vec4))
print ("\nstd(vec4):")
print (np.std(vec4))

for i, x in np.ndenumerate(vec4):
    if vec4[i] == max_vec4:
        print ("\nindex for max in vec4: " + str(i))
        ind_max_vec4 = i

    if vec4[i] == min_vec4:
        print ("\nindex for min in vec4: " + str(i))
        ind_min_vec4 = i
## 1g
vec5 = np.zeros (shape=50)
for i, x in np.ndenumerate(vec4):
    vec5[i] = (vec4[i] - min_vec4) / (max_vec4 - min_vec4)

print ("\n vec5:")
print (vec5)

print ("\n under the max_index in new vector: " + str(vec5[ind_max_vec4[0]]))


from scipy.fft import fft, fftfreq
import numpy as np
from scipy import signal
import matplotlib.pyplot as plt
from scipy.signal import hann

# Number of sample points
N1 = 256
N2 = 512
N3 = 4096
# sample spacing
Fs = 48000
T = 1.0 / Fs
#X axis
x1 = np.linspace(0.0, N1*T, N1, endpoint=False)
x2 = np.linspace(0.0, N2*T, N2, endpoint=False)
x3 = np.linspace(0.0, N3*T, N3, endpoint=False)
#Signal
y1 = np.cos(2.0*np.pi*x1*5000)
y2 = np.cos(2.0*np.pi*x2*5000)
y3 = np.cos(2.0*np.pi*x3*5000)
#Window
w1 = hann(N1)
w2 = hann(N2)
w3 = hann(N3)
#FFT of signal with window
ywf1 = fft(y1*w1)
ywf2 = fft(y2*w2)
ywf3 = fft(y3*w3)
# Used to calculate bins
# We only grab the first half of the bins
# because we don't need the negative frequencies
xf1 = np.arange(0,Fs/2,Fs/N1)
xf2 = np.arange(0,Fs/2,Fs/N2)
xf3 = np.arange(0,Fs/2,Fs/N3)

plt.semilogy(xf1[0:int(N1/2)], 2.0/N1 * np.abs(ywf1[0:int(N1/2)]), '-r')
plt.semilogy(xf2[0:int(N2/2)], 2.0/N2 * np.abs(ywf2[0:int(N2/2)]), '-g')
plt.semilogy(xf3[0:int(N3/2)], 2.0/N3 * np.abs(ywf3[0:int(N3/2)]), '-b')

plt.legend([f'FFT size: {N1}', f'FFT size: {N2}', f'FFT size: {N3}'])

plt.grid()
plt.show()


def Poisson(lambd, N):
    """ Devuelve una lista con elementos que siguen la distribución de Poisson

    Parameters
    ----------
    lambd : int
        Es la tasa de la distribución
    N : int
        Número de puntos, tiempo trancurrido

    Returns
    -------
    list
        Lista de valores "k" para una distribución de Poisson que siga los parámetros
    """
    import numpy as np
    from math import factorial
    # Creo la distribución acumulada de Poisson, la muestreo hasta un k igual a 3 sigma de
    # Poisson
    maxk = 3*lambd
    Poisson = np.empty(maxk)
    for ij in range(maxk):
        Numerador = (lambd**(ij))*np.exp(-lambd)
        Poisson[ij] = Numerador/factorial(ij)
        ij = ij + 1
    Acum = np.cumsum(Poisson) # Distribución de probabilidad acumulada

    # Creo la distribución K que sigue la distribución de Poisson mediante "Acum"
    Nums = np.random.random(N) # Números aleatorios en [0,1]
    K = []
    ij = 0 # Recorre el arreglo Nums
    while ij<len(Nums):
        ik = 0 # Recorre el arreglo Acum
        while ik<maxk:
            if ik==0 and Nums[ij]<Acum[0]: # Caso cero
                K.append(ik)
            if ik>0 and Nums[ij]<Acum[ik] and Nums[ij]>Acum[ik-1]:
                K.append(ik)
            ik = ik + 1
        ij = ij + 1
    return K


from sympy import *

x = symbols('x[0]')
y = symbols('x[1]')
uu = cos(x)*exp(x+y)
u = diff(uu,y)
v = -diff(uu,x)
p = sin(2*pi*y)*sin(2*pi*x)
print "u:    ",u
print "\n"
print "v:    ",v
print "\n"
print "p:    ",p
print "\n"
L1 = diff(v,x,y) - diff(u,y,y)
L2 = diff(u,x,y) - diff(v,x,x)
print "Curl 1:    ",L1
print "\n"
print "Curl 2:    ",L2
print "\n"

print "\n"
P1 = diff(p,x)
P2 = diff(p,y)
print "Pressure 1:    ",P1
print "\n"
print "Pressure 2:    ",P2

 # (exp(x + y)*sin(y) + exp(x + y)*cos(y))**2 - 2*exp(2*x + 2*y)*sin(y)*cos(y)


import numpy as np
import scipy as sp
import matplotlib.pyplot as plt
import matplotlib.mlab as mlab

def updateParams(k, alpha, N,sum_log_di, x, h):

    div_xByAlpha = np.divide(x,alpha)
    powK_div_xByAlpha = np.power(div_xByAlpha, k)
    log_div_xByAlpha = np.log(div_xByAlpha)

    sum_powK_div_diByAlpha = np.sum(np.multiply(powK_div_xByAlpha, h))
    sum_prod_OF_powK_div_diByAlpha_AND_log_div_diByAlpha = np.sum(np.multiply(np.multiply(powK_div_xByAlpha,log_div_xByAlpha),h))
    sum_prod_OF_powK_div_diByAlpha_AND_logP2_div_diByAlpha = np.sum(np.multiply(np.multiply(powK_div_xByAlpha,np.power(log_div_xByAlpha,2)),h))

    #N = d.shape[0]

    hessian = np.zeros((2,2))
    hessian[0,0] = -1.0 * ((N/(k*k)) + sum_prod_OF_powK_div_diByAlpha_AND_logP2_div_diByAlpha)
    hessian[1,1] = (k/(alpha*alpha)) * (N-(k+1)*sum_powK_div_diByAlpha)
    hessian[0,1] = hessian[1,0] = (1.0/alpha)*sum_powK_div_diByAlpha + (k/alpha)*sum_prod_OF_powK_div_diByAlpha_AND_log_div_diByAlpha - N/alpha

    vec = np.zeros((2,1))
    vec[0] = -1.0 *( N/k - N*np.log(alpha) + sum_log_di - sum_prod_OF_powK_div_diByAlpha_AND_log_div_diByAlpha)
    vec[1] = -1.0 *(k/alpha * (sum_powK_div_diByAlpha - N))

    param = np.linalg.inv(hessian).dot(vec)
    return k+param[0], alpha+param[1]

if __name__ == "__main__":
    #loading histograms
    data = np.loadtxt('myspace.csv',dtype=np.object,comments='#',delimiter=',')
    h = data[:,1].astype(np.int)
    h = np.array([x for x in h if x>0])
    x = np.array([num for num in range(1, h.shape[0]+1)])

    k = 1.0
    alpha = 1.0

    N = np.sum(h)
    sum_log_di = np.sum(np.multiply(np.log(x), h))
    for i in range(0,20):
        k,alpha = updateParams(k, alpha, N, sum_log_di, x, h)
        print i
        print k
        print alpha
        print "________"

    x_1 = np.linspace(1,500,2500)
    fig = plt.figure()
    axs = fig.add_subplot(111)
    y = N * (k/alpha) * np.multiply(np.power(np.divide(x_1,alpha), k-1), np.exp(-1.0* np.power(np.divide(x_1,alpha), k)))
    axs.plot(x_1,y, 'b')
    axs.plot(x, h, 'g')
    plt.show()


import numpy as np

data_points = np.array([[0.11, 1.24],
                        [0.20, 1.40],
                        [0.49, 2.00],
                        [0.65, 2.27],
                        [0.67, 2.41],
                        [0.83, 2.52]])

a =np.random.randn(1)
b =np.random.randn(1)
print(a,b)

"""Step 2: repeatedly visit entire dataset for N epochs"""

n_epoch =1000
batch_size =2
eta =0.01  # learning rate

for epoch in range(n_epoch):
  # Step 2.1: shuffle the data point
  np.random.shuffle(data_points)

  # Step 2.2:
  for i in range(0,len(data_points),batch_size):
    points =data_points[i:i+batch_size]

    x =points[:,0]
    y =points[:,1]

    # compute MSE loss
    y_pred = a *x +b
    y_err =y - y_pred
    mse_loss = (y_err**2).mean()

    # compute the gradients w.r.t a and b
    a_grad =-2.0*((y_err *x).mean())
    b_grad =-2.0*(y_err.mean())

    # update a and b (model parameter)
    a =a -eta *a_grad
    b =b -eta *b_grad

    print('grad:{} {} {}'.format(a,b,mse_loss))


from typing import Callable
import numpy as np
from scipy import stats

def chi_square(result_distribution:np.ndarray, expected_distribution:np.ndarray) -> float:
    # https://en.wikipedia.org/wiki/Pearson%27s_chi-squared_test
    assert(len(result_distribution)==len(expected_distribution))
    not_zero = np.where(expected_distribution != 0)[0]
    return stats.chisquare(result_distribution[not_zero], expected_distribution[not_zero]).statistic

def kullback_leibler_divergence(result_distribution:np.ndarray, expected_distribution:np.ndarray) -> float:
    # https://en.wikipedia.org/wiki/Kullback%E2%80%93Leibler_divergence
    assert(len(result_distribution)==len(expected_distribution))
    return stats.entropy(result_distribution, expected_distribution)

def bhattacharyya_difference(dist_1:np.ndarray, dist_2:np.ndarray) -> float:
    # https://en.wikipedia.org/wiki/Bhattacharyya_distance
    assert(len(dist_1)==len(dist_2))
    bc = np.sum(np.sqrt(dist_1*dist_2))
    if bc == 0:
        return np.inf
    return - np.log(bc)

def fidelity(dist_1:np.ndarray, dist_2:np.ndarray) -> float:
    return np.power(np.sum(np.sqrt(dist_1*dist_2)), 2)

def metric_diff(result_dist_1:np.ndarray, result_dist_2:np.ndarray, expected_dist:np.ndarray, metric:Callable[[np.ndarray, np.ndarray], float]) -> float:
    m_1 = metric(result_dist_1, expected_dist)
    m_2 = metric(result_dist_2, expected_dist)
    return m_1 - m_2

def same_max(result_dist:np.ndarray, expected_dist:np.ndarray) -> bool:
    return np.argmax(result_dist) == np.argmax(expected_dist)

def same_order(result_dist:np.ndarray, expected_dist:np.ndarray) -> bool:
    return (np.argsort(result_dist) == np.argsort(expected_dist)).all()

if __name__ == "__main__":
    a = np.array([0,0,1,0])
    b = np.array([0,1,0,0])
    c = np.array([1,1,1,1])/4

    print("chi_square")
    print(chi_square(a, b))
    print(chi_square(a, c))
    print("kullback_leibler_divergence")
    print(kullback_leibler_divergence(a, b))
    print(kullback_leibler_divergence(a, c))
    print("bhattacharyya_difference")
    print(bhattacharyya_difference(a, b))
    print(bhattacharyya_difference(a, c))


import numpy as np
from mlfromscratch.helpers import helpers as hp

class PCA(object):

    def __init__(self, n_components=2):
        '''
        Implements the PCA algorithm.

        Parameters
        ----------
        n_components: Number of principal components
        '''
        self.n_components = n_components

    def fit(self, X):
        '''
        Calculate the first n_components eigenvectors

        Parameters
        ----------
        X: np.array
            feature matrix (m x n)
        '''
        X_scaled = hp.standardize(X)
        cov_mat = self._covariance(X_scaled)
        eig_vals, eig_vects = self._eigen_decomp(cov_mat)
        # sort the eigenvectors by their respective eigenvalues
        order = np.argsort(eig_vals)[::-1]
        self.eig_vects = eig_vects[:,order]

    def project(self, X):
        '''
        Project data on to n_components principal components

        Parameters
        ----------
        X: np.array
            feature matrix (m x n)
        '''
        X_scaled = hp.standardize(X)
        x_project = X_scaled.dot(self.eig_vects[:,:self.n_components])
        return x_project

    ## private methods ##

    def _covariance(self, X):
        '''
        Compute the covariance (n x n) matrix.

        Parameters
        ----------
        X: np.array
            feature matrix (m x n)
        '''
        return np.cov(X.T)

    def _eigen_decomp(self, X):
        '''
        Decomposes a matrix into eigenvectors and eigenvalues.

        Parameters
        ----------
        X: np.array
            feature matrix (n x n)
        '''
        assert X.shape[0] == X.shape[1], "X must be a square matrix"
        eig_vals, eig_vects = np.linalg.eig(X)
        return eig_vals, eig_vects


import numpy as np

def mean_squared_error(estimates, targets):
    """
    Mean squared error measures the average of the square of the errors (the
    average squared difference between the estimated values and what is
    estimated. The formula is:

    MSE = (1 / n) * sum_{i=1}^{n} (Y_i - Yhat_i)^2

    Args:
        estimates(np.ndarray): the estimated values (should be the same shape as targets)
        targets(np.ndarray): the ground truth values

    Returns:
        MSE(int): mean squared error calculated by above equation
    """

    MSE_matrix = []
    n = targets.shape[0]
    for i in range(n):
        MSE_matrix.append(np.linalg.norm(estimates[i] - targets[i])**2)
    MSE = sum(MSE_matrix)/n
    return MSE


import numpy as np

#1)input:directed graph and parameter beta
beta = 0.9
dic = np.loadtxt("web-Google.txt",dtype='int')
dic_1 = dic[:,0]
dic_2 = dic[:,1]
from_nodes = np.sort(dic_1)
node_ind = np.argsort(dic_1)
to_nodes = dic_2[node_ind]
dic_sorted = np.transpose(np.vstack([from_nodes, to_nodes]))

all_nodes = np.sort(dic_sorted, axis=None)
fdn, f_counts = np.unique(from_nodes,return_counts=True)
aln = np.unique(all_nodes)
array_l=len(aln)
r_old = np.ones((array_l),dtype=float)/array_l
epson = 1

#2)output :pagerank
while epson>0.0001:
	dic_count = 0
	for i in range(len(fdn)):
            print 'i =', i
	    r_new = (1-beta)*np.ones((array_l),dtype=float)/array_l
	    f_ind = np.argwhere(aln==fdn[i])
	    add = r_old[f_ind]/f_counts[i]
	    for j in range(f_counts[i]):
                print 'j = ', j
		to_ind = np.argwhere(aln==to_nodes[dic_count])
		r_new[to_ind]+=beta*add
		dic_count+=1
        diff_r = r_new - r_old
        epson = np.dot(np.transpose(diff_r),diff_r)/np.dot(np.transpose(r_old),r_old)
        r_old = r_new
        print 'epson=', epson

indice = np.argsort(r_new)

sorted_scores = np.sort(r_new)
sorted_nodes = aln[indice]
for i in range(array_l):
   index = -i-1
   print sorted_nodes[index], sorted_scores[index]


import pandas as pd
import numpy as np
import scipy.stats

# NOTE: all parameter 'a' is assumed as array-like

def max(a): return np.max(a)

def min(a): return np.min(a)

def range(a): return np.max(a) - np.min(a)

def sum(a): return np.sum(a)

def mean(a): return np.mean(a)

def var(a): return np.var(a)

def var_samp(a): return np.var(a, ddof=1)

def std(a): return np.std(a)

def skewness(a): return scipy.stats.skew(a)

def kurtosis(a): return scipy.stats.kurtosis(a)

def median(a): return np.median(a)

def percentile(a, q): return np.percentile(a, q)

def trimmed_mean(a, proportiontocut): return scipy.stats.trim_mean(a, proportiontocut)

def iqr(a): return scipy.stats.iqr(a)

def q1(a): return np.percentile(a, 25)

def q3(a): return np.percentile(a, 75)

def mode(a): return list(pd.Series(a).mode())

def num_row(a): return len(a)

def num_value(a): return np.count_nonzero(~pd.isnull(a))

def num_nan(a): return np.count_nonzero([x is np.nan for x in a])

def num_nullonly(a): return np.count_nonzero([x is None for x in a])

def num_null(a): return np.count_nonzero(pd.isnull(a))

def num_distinct(a): return np.count_nonzero(np.unique(a))


# -*- coding: utf-8 -*-
"""
Created on Wed Sep 27 20:02:04 2017

@author: USER
"""
import math
from scipy.stats import norm

def blscall(S,L,T,r,sigma):
    d1 = (math.log(S/L)+(r+0.5*sigma*sigma)*T)/(sigma*math.sqrt(T))
    d2 = d1- sigma*math.sqrt(T)
    return S*norm.cdf(d1)-L*math.exp(-r*T)*norm.cdf(d2)

def Bisection2(left,right,S,L,T,r,call,error):
    center = (left+right)/2
    if((right-left)/2<error):
        return center
    if((blscall(S,L,T,r,left)-call)*(blscall(S,L,T,r,center)-call)<0):
        return Bisection2(left,center,S,L,T,r,call,error)
    else:
        return Bisection2(center,right,S,L,T,r,call,error)

def Newton2(initsigma,S,L,T,r,call,iteration):
    sigma = initsigma
    for i in range(iteration):
        fx = blscall(S,L,T,r,sigma)-call
        fx2 = (blscall(S,L,T,r,sigma+0.00000001)-blscall(S,L,T,r,sigma-0.00000001))/0.00000002;
        sigma = sigma - fx/fx2
    return sigma

S = 10326.68
L = 10300.0
T = 21.0/365
r = 0.01065
sigma = 0.10508

print(blscall(S,L,T,r,sigma))
sigma = Bisection2(0.0000001,1,S,L,T,r,121.0,0.000000001)
print(sigma)
print(blscall(S,L,T,r,sigma))
print(Newton2(0.5,S,L,T,r,121.0,10))


import scipy.optimize

# Objective Function: 50x_1 + 80x_2
# Constraint 1: 5x_1 + 2x_2 <= 20
# Constraint 2: -10x_1 + -12x_2 <= -90

result = scipy.optimize.linprog(
    [50, 80],  # Cost function: 50x_1 + 80x_2
    A_ub=[[5, 2], [-10, -12]],  # Coefficients for inequalities
    b_ub=[20, -90],  # Constraints for inequalities: 20 and -90
)

if result.success:
    print(f"X1: {round(result.x[0], 2)} hours")
    print(f"X2: {round(result.x[1], 2)} hours")
else:
    print("No solution")

HarvardX/CS50's Intro to AI with Python/Part1/src3/production/production.py


import numpy as np

def Rx(theta):
    return np.array([[1, 0, 0 , 0],
                    [0, np.cos(theta),-np.sin(theta), 0],
                    [0, np.sin(theta), np.cos(theta), 0],
                    [0,            0,              0,  1]])

def Rz(theta):
    return np.array([[ np.cos(theta), -np.sin(theta), 0 , 0 ],
                    [ np.sin(theta), np.cos(theta) , 0 , 0],
                    [ 0           , 0            , 1 , 0],
                     [0            ,0          ,0     ,1]])

def Tz(z):
    return np.array([[1,0,0,0],
                    [0,1,0,0,],
                    [0,0,1,z],
                    [0,0,0,1]])

def Tx(x):
    return np.array([[1,0,0,x],
                    [0,1,0,0],
                    [0,0,1,0],
                    [0,0,0,1]])

def Amat(theta,d,a,alpha):
    return Rz(theta) @ Tz(d) @ Tx(a) @ Rx(alpha)

def Tmat(dh):
    T = np.eye(4)
    if dh.size < 5:
        T = Amat(dh[0], dh[1], dh[2], dh[3])
    else:
        num_frame, _ = np.shape(dh)
        for k in range(num_frame):
                T = T @ Amat(dh[k,0], dh[k,1], dh[k,2], dh[k,3])

    return T


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

def rossler(x, y, z, a, b, c):
    """ Rössler System of Ordinary Differential Equations """
    dx = - y - z
    dy = x + a*y
    dz = b + z*(x - c)

    return dx, dy, dz

def solver(x, y, z, t, a, b, c):
    """ Euler's Method to solve the ODEs associated with the Rössler Attractor """
    for i in range(len(t)-1):
        # calculate derivatives
        dx, dy, dz = rossler(x[i], y[i], z[i], a, b, c)

        x[i + 1] = x[i] + (dx * (t[i+1]-t[i]))
        y[i + 1] = y[i] + (dy * (t[i+1]-t[i]))
        z[i + 1] = z[i] + (dz * (t[i+1]-t[i]))

def plot_rossler(x_1, y_1, z_1, t, x_2, y_2, z_2):
    fig = plt.figure(figsize=(14, 7))
    fig.set_facecolor('#E8E6E6')

    ax1 = fig.add_subplot(1, 2, 1, projection='3d')
    line, = ax1.plot([], [], [], color='b', linewidth=0.5, label='$(x_1, y_1, z_1): IC = (0.1, 0., 0.1)$')
    line2, = ax1.plot([], [], [], color='r', linewidth=0.5, label='$(x_2, y_2, z_2): IC = (0.1001, 0., 0.1001)$')

    point, = ax1.plot([], [], [], marker='o', color='b', markersize=4)
    point2, = ax1.plot([], [], [], marker='o', color='r', markersize=4)

    ax1.set_facecolor('#E8E6E6')
    ax1.xaxis.set_pane_color((1.0, 1.0, 1.0, 1.0))
    ax1.yaxis.set_pane_color((1.0, 1.0, 1.0, 1.0))
    ax1.zaxis.set_pane_color((1.0, 1.0, 1.0, 1.0))

    # set point-of-view: specified by (altitude degrees, azimuth degree)
    ax1.view_init(elev=23, azim=-131)

    # set limits
    ax1.set_xlim(-10, 15)
    ax1.set_ylim(-15, 10)
    ax1.set_zlim(0, 25)

    # set axis labels
    ax1.set_xlabel('X', fontsize=12)
    ax1.set_ylabel('Y', fontsize=12)
    ax1.set_zlabel('Z', fontsize=12)

    # remove tick labels
    ax1.set_yticklabels([])
    ax1.set_xticklabels([])
    ax1.set_zticklabels([])

    # legend
    ax1.legend(loc=(0.2, -0.08), fancybox=True, facecolor='white', edgecolor='black', frameon=True)

    # set title
    ax1.set_title('Phase Space: Trajectories', fontsize=14)

    # plot x_1 and x_2 values against time
    ax2 = fig.add_subplot(2, 2, 2)
    line3, = ax2.plot([], [], 'b-', linewidth=0.8)
    line4, = ax2.plot([], [], 'r-', linewidth=0.8)

    point3, = ax2.plot([], [], marker='o', color='b', markersize=4)
    point4, = ax2.plot([], [], marker='o', color='r', markersize=4)

    ax2.set_xlim(t[0], t[len(t)-1])
    ax2.set_ylim(-10, 12.5)
    ax2.set_ylabel('x(t)', fontsize=12)
    ax2.set_title('$Solutions: x_1 (Blue), x_2 (Red)$', fontsize=14)

    # plot difference (x2-x1)
    difference = np.absolute(x_2 - x_1)
    ax3 = fig.add_subplot(2, 2, 4)
    line5, = ax3.plot([], [], 'g-', linewidth=0.8)

    point5, = ax3.plot([], [], marker='o', color='g', markersize=4)

    ax3.set_xlim(t[0], t[len(t)-1])
    ax3.set_ylim(-0.5, 8)
    ax3.set_ylabel('$|x_2 - x_1|$', fontsize=12)
    ax3.set_xlabel('time (s)', fontsize=12)
    ax3.set_title('$Difference: |x_2 - x_1|$', fontsize=14)

    plt.subplots_adjust(left=0.125, bottom=0.1, right=0.9, top=0.9, wspace=0.2, hspace=0.35)

    def update(i):
        i = 250*i
        if i >= 300000:
            i = 300000-1

        xq_1 = x_1[0:i]
        yq_1 = y_1[0:i]
        zq_1 = z_1[0:i]

        xq_2 = x_2[0:i]
        yq_2 = y_2[0:i]
        zq_2 = z_2[0:i]

        tq = t[0:i]

        line.set_data(xq_1, yq_1)
        line.set_3d_properties(zq_1)
        point.set_data(x_1[i], y_1[i])
        point.set_3d_properties(z_1[i])

        line2.set_data(xq_2, yq_2)
        line2.set_3d_properties(zq_2)
        point2.set_data(x_2[i], y_2[i])
        point2.set_3d_properties(z_2[i])

        line3.set_data(tq, xq_1)
        point3.set_data(t[i], x_1[i])

        line4.set_data(tq, xq_2)
        point4.set_data(t[i], x_2[i])

        line5.set_data(tq, difference[0:i])
        point5.set_data(t[i], difference[i])

        return line, line2, line3, line4, line5, point, point2, point3, point4, point5

    # instantiate the animator
    ani = FuncAnimation(fig, update, frames=np.size(x_1), interval=0, blit=True)

    plt.show()

    # save as mp4. This requires mplayer or ffmpeg to be installed
    # anim.save('RosslerAttractor.mp4', fps=15, extra_args=['-vcodec', 'libx264'])

def main():
    # parameters
    a = 0.2
    b = 0.2
    c = 5.7

    # time interval and the step size
    t = np.arange(0, 300, 0.001)

    # vectors for the solutions
    x_1 = np.zeros((len(t)))
    y_1 = np.zeros((len(t)))
    z_1 = np.zeros((len(t)))

    x_2 = np.zeros((len(t)))
    y_2 = np.zeros((len(t)))
    z_2 = np.zeros((len(t)))

    # initial conditions
    x_1[0], y_1[0], z_1[0] = (0.1, 0., 0.1)
    x_2[0], y_2[0], z_2[0] = (0.1001, 0., 0.1001)

    solver(x_1, y_1, z_1, t, a, b, c)
    solver(x_2, y_2, z_2, t, a, b, c)

    plot_rossler(x_1, y_1, z_1, t, x_2, y_2, z_2)

if __name__ == "__main__":
    main()


import logging

import numpy as np
from scipy.special import gamma

class AnalyticalGammaToGumbel:
    """
    Compute relevant quantities related to the Gumbel distribution arising from the
    maximum over an ensemble of iid Gamma-distributed random variables (which includes
    the chi-squared distribution as a special case).
    """

    def __init__(self, dofs=None, shape=None, scale=None, rate=None):
        """
        The single-variable probability distribution can either be described as a
        chi-squared distribution with `dofs` degrees of freedom,
        a Gamma distribution with shape and scale parameters (`shape`, `scale`),
        or a Gamma distribution with shape and rate parameters (`shape`, `rate`).

        A chi-squared distribution with `dofs` degrees of freedom corresponds to a
        Gamma distribution with `shape=dofs/2` and `scale=2`, `rate=1/2`.

        Parameters
        ----------
        dofs: float, optional
            Degrees of freedom of the underlying chi-squared distribution.
            Authomatically implies `shape=dofs/2` and `scale=2`.
            This parameter is incompatible with `shape`, `scale`, and `rate`.
        shape: float, optional
            Shape parameter of the underlying Gamma distribution.
            This parameter must be given jointly with `scale` or `rate`
            and is incompatible with `dofs`.
        scale: float, optional
            Scale parameter of the underlying gamma distribution
            (inverse of the rate parameter).
            This parameter must be given jointly with `shape` and is
            incompatible with `dofs` or `rate`.
        rate: float, optional
            Rate parameter of the underlying gamma distribution
            (inverse of the scale parameter).
            This parameter must be given jointly with `shape` and is
            incompatible with `dofs` or `scale`.
        """

        # Check the validity of the input
        if dofs:
            if shape:
                raise ValueError("Only one out of dofs and shape can be given")
            shape = dofs / 2
            scale = 2.
        elif shape:
            if not (bool(scale) ^ bool(rate)):
                raise ValueError("Only one out of scale and rate can be given")
            elif rate:
                scale = 1 / rate
        else:
            raise ValueError("Invalid input parameters: "
                    "Please enter dofs, (scale, shape) or (scale, rate)")

        self._set_shape_scale(shape=shape, scale=scale)

    def _set_shape_scale(self, shape, scale):
        self.shape = shape
        self.scale = scale

    def get_gumbel_loc_scale(self, n, improved=True):
        """
        Compute the theoretical estimation of the location and scale parameters of the
        Gumbel distribution resulting from maximizing over a batch of `n` independent
        random variables following the distribution specified by this class.

        We provide two implementations of said parameters, selected by
        switching the `improved` flag.

        If improved is falsy, location and scale parameters are taken from table 3.4.4
        in "Modelling Extremal Events for Insurance and Finance", P. Embrechts,
        C. Klüppelberg, T. Mikosch (1997) https://doi.org/10.1007/978-3-642-33483-2.

        If improved is truthy (recommended), location and scale parameters are taken
        from Eqs. (32) and (33) in  Gasull, López-Salcedo, and Utzet, TEST 24, 714–733
        (2015) https://doi.org/10.1007/s11749-015-0431-9 .

        The improved option returns better estimates for n < 1e6 (and possibly beyond).

        Parameters
        ----------
        n: int
            Number of random variables within the batch being maximized.
        improved: bool
            If True (recommended), apply the improved loc and scale parameters. See
            the main docstring for details.

        Returns
        -------+
        loc: float
            Location parameter of the resulting Gumbel distribution.
        scale: float
            Scale parameter of the resulting Gumbel distribution.
        """

        logn = np.log(n)
        gamma_shape = gamma(self.shape)

        if improved:
            logn_gamma = np.log(n / gamma_shape)
            shape_m1 = self.shape - 1
            Bn = logn_gamma + shape_m1 * np.log(shape_m1)
            logBn = np.log(Bn)

            loc = self.scale * (
                logn_gamma
                + shape_m1 * logBn
                + (shape_m1 * shape_m1 * (logBn - np.log(shape_m1)) + shape_m1) / Bn
            )
            scale = (self.scale * loc * (self.scale * shape_m1 + loc)) / (
                loc * loc - self.scale * self.scale * shape_m1 * (self.shape - 2)
            )

        else:
            loc = self.scale * (
                logn + (self.shape - 1) * np.log(logn) + np.log(gamma_shape)
            )
            scale = self.scale

        return loc, scale

    def get_gumbel_mean_std(self, n, improved=True):
        """
        Compute the theoretical estimation of the mean and standard deviation parameters
        of the Gumbel distribution resulting from maximizing over a batch of `n` independent
        random variables following the distribution specified by this class.

        These results computed from the `loc` and `scale` parameters returned by
        `self.gumbel_mean_std`. See the docstring therein for a further explanation of the
        parameters.

        Parameters
        ----------
        n: int
            Number of random variables within the batch being maximized.
        improved: bool
            If True (recommended), apply the improved loc and scale parameters. See
            the main docstring for details.

        Returns
        -------
        mean: float
            Mean of the Gumbel distribution, computed as `loc + euler_gamma * scale`,
            where `euler_gamma=0.5772...` represents the Euler-Mascheroni constant
            (sequence A001620 in the OEIS).
        std: float
            Standard deviation of the Gumbel distribution,
            computed as `pi/sqrt(6) * scale`.
        """
        loc, scale = self.get_gumbel_loc_scale(n=n, improved=improved)
        mean = loc + np.euler_gamma * scale
        std = np.pi / np.sqrt(6) * scale
        return mean, std


'''Compute decibels dB(x) = 10.*log10(|x|).'''

import numpy as np
# convert X to decibels
def dB(X,floor=None):
  if (floor != None):
    return 10.*np.log10(np.maximum(floor, np.abs(X)))
  else:
    return 10.*np.log10(np.abs(X))


# https://github.com/pymc-devs/resources/blob/master/Rethinking/Chp_02.ipynb

#%matplotlib inline
import pymc3 as pm
import numpy as np
import scipy.stats as stats
import matplotlib.pyplot as plt

#%config InlineBackend.figure_format = 'retina'
plt.style.use(['seaborn-colorblind', 'seaborn-darkgrid'])

ways = np.array([0, 3, 8, 9, 0])
ways / ways.sum()

stats.binom.pmf(6, n=9, p=0.5)

def posterior_grid_approx(grid_points=5, success=6, tosses=9):
    """
    """
    # define grid
    p_grid = np.linspace(0, 1, grid_points)

    # define prior
    prior = np.repeat(5, grid_points)  # uniform
    #prior = (p_grid >= 0.5).astype(int)  # truncated
    #prior = np.exp(- 5 * abs(p_grid - 0.5))  # double exp

    # compute likelihood at each point in the grid
    likelihood = stats.binom.pmf(success, tosses, p_grid)

    # compute product of likelihood and prior
    unstd_posterior = likelihood * prior

    # standardize the posterior, so it sums to 1
    posterior = unstd_posterior / unstd_posterior.sum()
    return p_grid, posterior

points = 20
w, n = 6, 9
p_grid, posterior = posterior_grid_approx(points, w, n)
plt.figure()
plt.plot(p_grid, posterior, 'o-', label='success = {}\ntosses = {}'.format(w, n))
plt.xlabel('probability of water', fontsize=14)
plt.ylabel('posterior probability', fontsize=14)
plt.title('{} points'.format(points))
plt.legend(loc=0);
plt.show()

data = np.repeat((0, 1), (3, 6))
with pm.Model() as normal_aproximation:
    p = pm.Uniform('p', 0, 1)
    w = pm.Binomial('w', n=len(data), p=p, observed=data.sum())
    mean_q = pm.find_MAP()
    std_q = ((1/pm.find_hessian(mean_q, vars=[p]))**0.5)[0]
mean_q['p'], std_q

norm = stats.norm(mean_q, std_q)
prob = .89
z = stats.norm.ppf([(1-prob)/2, (1+prob)/2])
pi = mean_q['p'] + std_q * z
pi

# analytical calculation
w, n = 6, 9
x = np.linspace(0, 1, 100)
plt.figure()
plt.plot(x, stats.beta.pdf(x , w+1, n-w+1),
         label='True posterior')

# quadratic approximation
plt.plot(x, stats.norm.pdf(x, mean_q['p'], std_q),
         label='Quadratic approximation')
plt.legend(loc=0, fontsize=13)

plt.title('n = {}'.format(n), fontsize=14)
plt.xlabel('Proportion water', fontsize=14)
plt.ylabel('Density', fontsize=14);
plt.show()


import numpy as np
import kmeans
import common
import naive_em
import em

X = np.loadtxt("toy_data.txt")

########## Begin: kMeans vs EM (and BIC) #############
K = [1, 2, 3, 4]    # Clusters to try
seeds = [0, 1, 2, 3, 4]     # Seeds to try

# Costs for diff. seeds
costs_kMeans = [0, 0, 0, 0, 0]
costs_EM = [0, 0, 0, 0, 0]

# Best seed for cluster based on lowest costs
best_seed_kMeans = [0, 0, 0, 0]
best_seed_EM = [0, 0, 0, 0]

# Mixtures for best seeds
mixtures_kMeans = [0, 0, 0, 0, 0]
mixtures_EM = [0, 0, 0, 0, 0]

# Posterior probs. for best seeds
posts_kMeans = [0, 0, 0, 0, 0]
posts_EM = [0, 0, 0, 0, 0]

# BIC score of cluster
bic = [0., 0., 0., 0.]

for k in range(len(K)):
    for i in range(len(seeds)):

        # Run kMeans
        mixtures_kMeans[i], posts_kMeans[i], costs_kMeans[i] = \
        kmeans.run(X, *common.init(X, K[k], seeds[i]))

        # Run Naive EM
        mixtures_EM[i], posts_EM[i], costs_EM[i] = \
        naive_em.run(X, *common.init(X, K[k], seeds[i]))

    # Print lowest cost
    print("=============== Clusters:", k+1, "======================")
    print("Lowest cost using kMeans is:", np.min(costs_kMeans))
    print("Highest log likelihood using EM is:", np.max(costs_EM))

    # Save best seed for plotting
    best_seed_kMeans[k] = np.argmin(costs_kMeans)
    best_seed_EM[k] = np.argmax(costs_EM)

    # Plot kMeans and EM results
    common.plot(X,
                mixtures_kMeans[best_seed_kMeans[k]],
                posts_kMeans[best_seed_kMeans[k]],
                title="kMeans")

    common.plot(X,
                mixtures_EM[best_seed_EM[k]],
                posts_EM[best_seed_EM[k]],
                title="EM")

    #BIC score for EM
    bic[k] = common.bic(X, mixtures_EM[best_seed_EM[k]], np.max(costs_EM))

# Print the best K based on BIC
print("================= BIC ====================")
print("Best K is:", np.argmax(bic)+1)
print("BIC for the best K is:", np.max(bic))

########## End: kMeans vs EM (and BIC) #############


# ToDo: Properly document this module
"""This module contains functions used by other modules in the pterasoftware package.

"""
import logging

import numpy as np
from numba import njit, prange

def cosspace(
    minimum,
    maximum,
    n_points=50,
    endpoint=True,
):
    """This function is used to create a array containing a specified number of
    values between a specified minimum and maximum value that are spaced via a cosine
    function.

    Citation:
        Adapted from:         geometry.cosspace in AeroSandbox
        Author:               Peter Sharpe
        Date of Retrieval:    04/28/2020

    :param minimum: float
        This is the minimum value of the range of numbers you would like spaced.
    :param maximum: float
        This is the maximum value of the range of numbers you would like spaced.
    :param n_points: int, optional
        This is the number of points to space. The default is 50.
    :param endpoint: bool, optional
        This sets whether or not the maximum value will be included in the output.
        The default is True.
    :return cosine_spaced_points: 1D array
        This is a 1D array of the points, ranging from the minimum to the maximum
        value (inclusive), spaced via a cosine function.
    """
    # Find the mean and the amplitude of the cosine function.
    mean = (maximum + minimum) / 2
    amp = (maximum - minimum) / 2

    # Space the points by applying cosine to the linspace function. Then return the
    # points.
    cosine_spaced_points = mean + amp * np.cos(
        np.linspace(np.pi, 0, n_points, endpoint=endpoint)
    )
    return cosine_spaced_points

def reflect_over_xz_plane(input_vector):
    """This function is used to flip a the y coordinate of a coordinate vector.

    Citation:
        Adapted from:         geometry.reflect_over_xz_plane in AeroSandbox
        Author:               Peter Sharpe
        Date of Retrieval:    04/28/2020

    :param input_vector: array
        This can either be a 1D array of three items, a M x 3 2D array, or a M x
        N x 3 3D array. N and
        represent arbitrary numbers of rows or columns.
    :return output vector: array
        This is a array with each vertex's y variable flipped.
    """
    # Initialize the output vector.
    output_vector = input_vector

    # Find the shape of the input vector.
    shape = np.shape(output_vector)

    # Characterize the input vector.
    if len(shape) == 1 and shape[0] == 3:
        # The input vector is a 1D array of 3 items. Flip the vertex's y variable.
        output_vector = output_vector * np.array([1, -1, 1])
    elif len(shape) == 2 and shape[1] == 3:
        # The input vector is a 2D array of shape M x 3. Where M is some arbitrary
        # number of rows. Flip each
        # vertex's y variable.
        output_vector = output_vector * np.array([1, -1, 1])
    elif len(shape) == 3 and shape[2] == 3:  # 3D MxNx3 vector
        # The input vector is a 3D array of shape M x N x 3. Where M is some
        # arbitrary number of rows, and N is
        # some arbitrary number of columns. Flip each vertex's y variable.
        output_vector = output_vector * np.array([1, -1, 1])
    else:
        # The input vector is an unacceptable shape. Throw an error.
        raise Exception("Invalid input for reflect_over_xz_plane.")

    # Return the output vector.
    return output_vector

def angle_axis_rotation_matrix(angle, axis, axis_already_normalized=False):
    """This function is used to find the rotation matrix for a given axis and angle.

    Citation:
        Adapted from:         geometry.angle_axis_rotation_matrix in AeroSandbox
        Author:               Peter Sharpe
        Date of Retrieval:    04/28/2020

    For more information on the math behind this method, see:
    https://en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle

    :param angle: float or 1D array of 3 angles
        This is the angle. Provide it in radians.
    :param axis: 1D array of 3 elements.
        This is the axis.
    :param axis_already_normalized: bool, optional
        Set this as True if the axis given is already normalized, which will skip
        internal normalization and speed up the method. If not, set as False. The
        default is False.
    :return rotation matrix: array
        This is the rotation matrix. If the given angle is a scalar, this will be a 3
        x 3 array. If the given angle is a vector, this will be a 3 x 3 x N array.
    """
    # Normalize the axis is it is not already normalized.
    if not axis_already_normalized:
        axis = axis / np.linalg.norm(axis)

    # Define constants to use when calculating the rotation matrix.
    sin_theta = np.sin(angle)
    cos_theta = np.cos(angle)
    u_x = axis[0]
    u_y = axis[1]
    u_z = axis[2]

    # Calculate the rotation matrix.
    rotation_matrix = np.array(
        [
            [
                cos_theta + u_x ** 2 * (1 - cos_theta),
                u_x * u_y * (1 - cos_theta) - u_z * sin_theta,
                u_x * u_z * (1 - cos_theta) + u_y * sin_theta,
            ],
            [
                u_y * u_x * (1 - cos_theta) + u_z * sin_theta,
                cos_theta + u_y ** 2 * (1 - cos_theta),
                u_y * u_z * (1 - cos_theta) - u_x * sin_theta,
            ],
            [
                u_z * u_x * (1 - cos_theta) - u_y * sin_theta,
                u_z * u_y * (1 - cos_theta) + u_x * sin_theta,
                cos_theta + u_z ** 2 * (1 - cos_theta),
            ],
        ]
    )

    # Return the rotation matrix.
    return rotation_matrix

@njit(cache=True)
def numba_centroid_of_quadrilateral(
    front_left_vertex, front_right_vertex, back_left_vertex, back_right_vertex
):
    """This function is used to find the centroid of a quadrilateral. It has been
    optimized for JIT compilation using Numba.

    :param front_left_vertex: 1D array of floats
        This is an array containing the x, y, and z components of the front left
        vertex of the quadrilateral.
    :param front_right_vertex: 1D array of floats
        This is an array containing the x, y, and z components of the front right
        vertex of the quadrilateral.
    :param back_left_vertex: 1D array of floats
        This is an array containing the x, y, and z components of the back left
        vertex of the quadrilateral.
    :param back_right_vertex: 1D array of floats
        This is an array containing the x, y, and z components of the back right
        vertex of the quadrilateral.
    :return: 1D array of floats
        This is an array containing the x, y, and z components of the centroid of the
        quadrilateral.
    """
    x_average = (
        front_left_vertex[0]
        + front_right_vertex[0]
        + back_left_vertex[0]
        + back_right_vertex[0]
    ) / 4
    y_average = (
        front_left_vertex[1]
        + front_right_vertex[1]
        + back_left_vertex[1]
        + back_right_vertex[1]
    ) / 4
    z_average = (
        front_left_vertex[2]
        + front_right_vertex[2]
        + back_left_vertex[2]
        + back_right_vertex[2]
    ) / 4

    return np.array([x_average, y_average, z_average])

def calculate_streamlines(solver, num_steps=25, delta_time=0.02):
    """Calculates the location of the streamlines coming off the back of the wings.

    This method is vectorized to increase performance.

    :param solver: Solver
        This is the solver object for which to calculate the streamlines.
    :param num_steps: int, optional
        This is the integer number of points along each streamline (not including the
        initial points). It can be increased for higher fidelity visuals. The default
        value is 25.
    :param delta_time: float, optional
        This is the time in seconds between each time current_step It can be
        decreased for higher fidelity visuals or to make the streamlines shorter.
        It's default value is 0.02 seconds.
    :return: None
    """
    # Initialize a array to hold this solver's matrix of streamline points.
    solver.streamline_points = np.expand_dims(solver.seed_points, axis=0)

    # Iterate through the streamline steps.
    for step in range(num_steps):
        # Get the last row of streamline points.
        last_row_streamline_points = solver.streamline_points[-1, :, :]

        # Add the freestream velocity to the induced velocity to get the total
        # velocity at each of the last row of streamline points.
        total_velocities = solver.calculate_solution_velocity(
            points=last_row_streamline_points
        )

        # Interpolate the positions on a new row of streamline points.
        new_row_streamline_points = (
            last_row_streamline_points + total_velocities * delta_time
        )

        # Stack the new row of streamline points to the bottom of the matrix of
        # streamline points.
        solver.streamline_points = np.vstack(
            (
                solver.streamline_points,
                np.expand_dims(new_row_streamline_points, axis=0),
            )
        )

def convert_logging_level_name_to_value(name):
    """This function takes in a string that represents the the logging level and
    returns the integer that can be used to set the logger to this level.

    :param name: str
        This is the string representation of the logging level. The options are
        "Debug", "Info", "Warning", "Error", and "Critical".
    :return: int
        This is the integer value that can used to set the appropriate logging level.
    """
    logging_levels = {
        "Debug": logging.DEBUG,
        "Info": logging.INFO,
        "Warning": logging.WARNING,
        "Error": logging.ERROR,
        "Critical": logging.CRITICAL,
    }
    try:
        return logging_levels[name]
    except KeyError:
        raise Exception("The name of the logging level provided is not a valid option.")

# ToDo: Update this function's documentation.
def process_steady_solver_forces(
    steady_solver, near_field_forces_geometry_axes, near_field_moments_geometry_axes
):
    """

    :param steady_solver:
    :param near_field_forces_geometry_axes:
    :param near_field_moments_geometry_axes:
    :return:
    """
    # Initialize a variable to hold the global panel position.
    global_panel_position = 0

    # Iterate through this solver's panels.
    for panel in steady_solver.panels:
        # Update the force and moment on this panel.
        panel.near_field_force_geometry_axes = near_field_forces_geometry_axes[
            global_panel_position, :
        ]
        panel.near_field_moment_geometry_axes = near_field_moments_geometry_axes[
            global_panel_position, :
        ]

        # Update the pressure on this panel.
        panel.update_pressure()

        # Increment the global panel position.
        global_panel_position += 1

    # Sum up the near field forces and moments on every panel to find the total
    # force and moment on the geometry.
    total_near_field_force_geometry_axes = np.sum(
        near_field_forces_geometry_axes, axis=0
    )
    total_near_field_moment_geometry_axes = np.sum(
        near_field_moments_geometry_axes, axis=0
    )

    # Find the total near field force in wind axes from the rotation matrix and
    # the total near field force in geometry axes.
    steady_solver.airplane.total_near_field_force_wind_axes = (
        np.transpose(
            steady_solver.operating_point.calculate_rotation_matrix_wind_axes_to_geometry_axes()
        )
        @ total_near_field_force_geometry_axes
    )

    # Find the total near field moment in wind axes from the rotation matrix and
    # the total near field moment in geometry axes.
    steady_solver.airplane.total_near_field_moment_wind_axes = (
        np.transpose(
            steady_solver.operating_point.calculate_rotation_matrix_wind_axes_to_geometry_axes()
        )
        @ total_near_field_moment_geometry_axes
    )

    # Calculate the current_airplane's induced drag coefficient
    induced_drag_coefficient = (
        -steady_solver.airplane.total_near_field_force_wind_axes[0]
        / steady_solver.operating_point.calculate_dynamic_pressure()
        / steady_solver.airplane.s_ref
    )

    # Calculate the current_airplane's side force coefficient.
    side_force_coefficient = (
        steady_solver.airplane.total_near_field_force_wind_axes[1]
        / steady_solver.operating_point.calculate_dynamic_pressure()
        / steady_solver.airplane.s_ref
    )

    # Calculate the current_airplane's lift coefficient.
    lift_coefficient = (
        -steady_solver.airplane.total_near_field_force_wind_axes[2]
        / steady_solver.operating_point.calculate_dynamic_pressure()
        / steady_solver.airplane.s_ref
    )

    # Calculate the current_airplane's rolling moment coefficient.
    rolling_moment_coefficient = (
        steady_solver.airplane.total_near_field_moment_wind_axes[0]
        / steady_solver.operating_point.calculate_dynamic_pressure()
        / steady_solver.airplane.s_ref
        / steady_solver.airplane.b_ref
    )

    # Calculate the current_airplane's pitching moment coefficient.
    pitching_moment_coefficient = (
        steady_solver.airplane.total_near_field_moment_wind_axes[1]
        / steady_solver.operating_point.calculate_dynamic_pressure()
        / steady_solver.airplane.s_ref
        / steady_solver.airplane.c_ref
    )

    # Calculate the current_airplane's yawing moment coefficient.
    yawing_moment_coefficient = (
        steady_solver.airplane.total_near_field_moment_wind_axes[2]
        / steady_solver.operating_point.calculate_dynamic_pressure()
        / steady_solver.airplane.s_ref
        / steady_solver.airplane.b_ref
    )

    steady_solver.airplane.total_near_field_force_coefficients_wind_axes = np.array(
        [induced_drag_coefficient, side_force_coefficient, lift_coefficient]
    )
    steady_solver.airplane.total_near_field_moment_coefficients_wind_axes = np.array(
        [
            rolling_moment_coefficient,
            pitching_moment_coefficient,
            yawing_moment_coefficient,
        ]
    )

# ToDo: Update this function's documentation.
def update_ring_vortex_solvers_panel_attributes(solver, global_panel_position, panel):
    """

    :param solver:
    :param global_panel_position:
    :param panel:
    :return:
    """
    # Update the solver's list of attributes with this panel's attributes.
    solver.panels[global_panel_position] = panel
    solver.panel_normal_directions[global_panel_position, :] = panel.normal_direction
    solver.panel_areas[global_panel_position] = panel.area
    solver.panel_centers[global_panel_position] = panel.center
    solver.panel_collocation_points[global_panel_position, :] = panel.collocation_point
    solver.panel_back_right_vortex_vertices[
        global_panel_position, :
    ] = panel.ring_vortex.right_leg.origin
    solver.panel_front_right_vortex_vertices[
        global_panel_position, :
    ] = panel.ring_vortex.right_leg.termination
    solver.panel_front_left_vortex_vertices[
        global_panel_position, :
    ] = panel.ring_vortex.left_leg.origin
    solver.panel_back_left_vortex_vertices[
        global_panel_position, :
    ] = panel.ring_vortex.left_leg.termination
    solver.panel_right_vortex_centers[
        global_panel_position, :
    ] = panel.ring_vortex.right_leg.center
    solver.panel_right_vortex_vectors[
        global_panel_position, :
    ] = panel.ring_vortex.right_leg.vector
    solver.panel_front_vortex_centers[
        global_panel_position, :
    ] = panel.ring_vortex.front_leg.center
    solver.panel_front_vortex_vectors[
        global_panel_position, :
    ] = panel.ring_vortex.front_leg.vector
    solver.panel_left_vortex_centers[
        global_panel_position, :
    ] = panel.ring_vortex.left_leg.center
    solver.panel_left_vortex_vectors[
        global_panel_position, :
    ] = panel.ring_vortex.left_leg.vector
    solver.panel_back_vortex_centers[
        global_panel_position, :
    ] = panel.ring_vortex.back_leg.center
    solver.panel_back_vortex_vectors[
        global_panel_position, :
    ] = panel.ring_vortex.back_leg.vector
    solver.panel_is_trailing_edge[global_panel_position] = panel.is_trailing_edge
    solver.panel_is_leading_edge[global_panel_position] = panel.is_leading_edge
    solver.panel_is_right_edge[global_panel_position] = panel.is_right_edge
    solver.panel_is_left_edge[global_panel_position] = panel.is_left_edge

    # Check if this panel is on the trailing edge.
    if panel.is_trailing_edge:
        # If it is, calculate it's streamline seed point and add it to
        # the solver's array of seed points.
        solver.seed_points = np.vstack(
            (
                solver.seed_points,
                panel.back_left_vertex
                + 0.5 * (panel.back_right_vertex - panel.back_left_vertex),
            )
        )

def calculate_steady_freestream_wing_influences(steady_solver):
    """This method finds the vector of freestream-wing influence coefficients
    associated with this problem.

    :return: None
    """
    # Take the batch dot product of the freestream velocity with each panel's
    # normal direction. This is now the problem's 1D array of freestream-wing
    # influence coefficients.
    steady_solver.freestream_wing_influences = np.einsum(
        "ij,j->i",
        steady_solver.panel_normal_directions,
        steady_solver.freestream_velocity,
    )

@njit(parallel=True, cache=True, fastmath=True)
def numba_1d_explicit_cross(vectors_1, vectors_2):
    """This function takes in two arrays, each of which contain N vectors of 3
    components. The function then calculates and returns the cross product of the two
    vectors at each position.

    Note: This function has been optimized for JIT compilation and parallel
    computation using Numba.

    Citation: Some or all of the following code was written by Jérôme Richard as a
    response to a question on Stack Overflow. The original response is here:
    https://stackoverflow.com/a/66757029/13240504.

    :param vectors_1: array of floats of size (N x 3)
        This is the first array of N vectors.
    :param vectors_2: array of floats of size (N x 3)
        This is the second array of N vectors.
    :return crosses: array of floats of size (N x 3)
        This is the cross product of the two inputted vectors at each of the N
        positions.
    """
    crosses = np.zeros(vectors_1.shape)
    for i in prange(crosses.shape[0]):
        crosses[i, 0] = (
            vectors_1[i, 1] * vectors_2[i, 2] - vectors_1[i, 2] * vectors_2[i, 1]
        )
        crosses[i, 1] = (
            vectors_1[i, 2] * vectors_2[i, 0] - vectors_1[i, 0] * vectors_2[i, 2]
        )
        crosses[i, 2] = (
            vectors_1[i, 0] * vectors_2[i, 1] - vectors_1[i, 1] * vectors_2[i, 0]
        )
    return crosses


#!/usr/bin/env python

from mpi4py import MPI
import numpy

# Function to calcualte pi that each MPI rank will use
def compute_pi(samples):
    count = 0
    for x, y in samples:
        if x**2 + y**2 <= 1:
            count += 1
    pi = 4*float(count)/len(samples)
    return pi

# Set up our MPI environment
comm = MPI.COMM_WORLD
nprocs = comm.Get_size()
myrank = comm.Get_rank()

# Processor 0 generates random samples that each processor will use
if myrank == 0:
    N = 10000000 // nprocs
    samples = numpy.random.random((nprocs, N, 2))
else:
    samples = None

# Distribute the samples amongst all processors wiht MPI_Scatter
samples = comm.scatter(samples, root=0)

# Each processors calculates their value of pi (we'll take the average)
mypi = compute_pi(samples) / nprocs

# MPI_Reduce collects all individual
pi = comm.reduce(mypi, op=MPI.SUM, root=0)

if myrank == 0:
    error = abs(pi - numpy.pi)
    print("pi is approximately %.16f, error is %.16f" % (pi, error))


import numpy                as np

class Configuration:

    def __init__(self):

        # Problem setting (in canonical form)
        # self.constraint = [
        #     # [coefficient_1, coefficient_2, ... , constant_value]
        #     [ 4,     1,     22],
        #     [ 2,    -1,      8],
        #     [ 1,     2,     10]
        # ]
        # self.object = [-1, -2, 12]   # [coefficient_1, coefficient_2, ... , constant_value]

        self.constraint = [
            # [coefficient_1, coefficient_2, ... , constant_value]
            [ 5,    -2,     11],
            [ 4,     2,     28],
            [-3,     3,      6]
        ]
        self.object = [-4, -5, 4]   # [coefficient_1, coefficient_2, ... , constant_value]

class Simplex:

    def __init__(self, cnf):

        self.cnf    = cnf

        # Make dictionary (= Basis form expression)
        stack       = np.vstack((self.cnf.constraint, self.cnf.object))
        slack       = np.eye(stack.shape[0])
        self.prob   = np.insert(stack, [-1], slack, axis=1)

        print("Initial Dictionary:")
        print(self.prob)

    def run(self):
        count = 0
        while any(self.prob[-1] < 0):   # while having space to be optimized
            # Title
            count += 1
            print("\n<Operation " + str(count) + ">")

            # Decide colmun ID with Dantzig's selection method
            col_id = self.prob[-1].argmin()

            # Decide row ID
            cand = self.prob[:,-1]/self.prob[:,col_id]
            cand[cand<0] = np.inf
            row_id = cand.argmin()

            # Calculate a variable to be assigned
            pivot_row = self.prob[row_id] / self.prob[row_id,col_id]

            # Assign
            for i in range(self.prob.shape[0]):
                if i == row_id:
                    self.prob[i] = pivot_row
                else:
                    self.prob[i] = self.prob[i] - (pivot_row * self.prob[i,col_id])

            # Show temporary result
            print(" Dictionary:")
            print(self.prob)

            # Avoid infinity loop
            if count > 100:
                print("\n Failed")
                break

        # Show final result
        if not count > 100:
            print("\n <Final result> \n Objective value = " + str(self.prob[-1,-1]) + "\n")

# run
if __name__ == '__main__':

    cnf = Configuration()

    print("\n\t\t[ Simplex Method ]\n")
    slx = Simplex(cnf)
    slx.run()


from sympy import MatrixSymbol, symbols, Identity, sin, cos
from sympy.codegen.array_utils import CodegenArrayTensorProduct, CodegenArrayPermuteDims, CodegenArrayDiagonal, \
    CodegenArrayContraction
from sympy.matrices.expressions.applyfunc import ElementwiseApplyFunction
from sympy.tensor.array.expressions.arrayexpr_derivatives import array_derive

k = symbols("k")

I = Identity(k)
X = MatrixSymbol("X", k, k)
x = MatrixSymbol("x", k, 1)

A = MatrixSymbol("A", k, k)
B = MatrixSymbol("B", k, k)
C = MatrixSymbol("C", k, k)
D = MatrixSymbol("D", k, k)

def test_arrayexpr_derivatives1():

    res = array_derive(X, X)
    assert res == CodegenArrayPermuteDims(CodegenArrayTensorProduct(I, I), [0, 2, 1, 3])

    cg = CodegenArrayTensorProduct(A, X, B)
    res = array_derive(cg, X)
    assert res == CodegenArrayPermuteDims(
        CodegenArrayTensorProduct(I, A, I, B),
        [0, 4, 2, 3, 1, 5, 6, 7])

    cg = CodegenArrayContraction(X, (0, 1))
    res = array_derive(cg, X)
    assert res == CodegenArrayContraction(CodegenArrayTensorProduct(I, I), (1, 3))

    cg = CodegenArrayDiagonal(X, (0, 1))
    res = array_derive(cg, X)
    assert res == CodegenArrayDiagonal(CodegenArrayTensorProduct(I, I), (1, 3))

    cg = ElementwiseApplyFunction(sin, X)
    res = array_derive(cg, X)
    assert res.dummy_eq(CodegenArrayDiagonal(
        CodegenArrayTensorProduct(
            ElementwiseApplyFunction(cos, X),
            I,
            I
        ), (0, 3), (1, 5)))


######################################################################
#
# Functions to calculate integration points and weights for Gaussian
# quadrature
#
# x,w = gaussxw(N) returns integration points x and integration
#           weights w such that sum_i w[i]*f(x[i]) is the Nth-order
#           Gaussian approximation to the integral int_{-1}^1 f(x) dx
# x,w = gaussxwab(N,a,b) returns integration points and weights
#           mapped to the interval [a,b], so that sum_i w[i]*f(x[i])
#           is the Nth-order Gaussian approximation to the integral
#           int_a^b f(x) dx
#
# This code finds the zeros of the nth Legendre polynomial using
# Newton's method, starting from the approximation given in Abramowitz
# and Stegun 22.16.6.  The Legendre polynomial itself is evaluated
# using the recurrence relation given in Abramowitz and Stegun
# 22.7.10.  The function has been checked against other sources for
# values of N up to 1000.  It is compatible with version 2 and version
# 3 of Python.
#
# Written by Mark Newman <mejn@umich.edu>, June 4, 2011
# You may use, share, or modify this file freely
#
######################################################################

from numpy import ones, copy, cos, tan, pi, linspace

def gaussxw(N):
    # Initial approximation to roots of the Legendre polynomial
    a = linspace(3, 4 * N - 1, N) / (4 * N + 2)
    x = cos(pi * a + 1 / (8 * N * N * tan(a)))

    # Find roots using Newton's method
    epsilon = 1e-15
    delta = 1.0
    while delta > epsilon:
        p0 = ones(N, float)
        p1 = copy(x)
        for k in range(1, N):
            p0, p1 = p1, ((2 * k + 1) * x * p1 - k * p0) / (k + 1)
        dp = (N + 1) * (p0 - x * p1) / (1 - x * x)
        dx = p1 / dp
        x -= dx
        delta = max(abs(dx))

    # Calculate the weights
    w = 2 * (N + 1) * (N + 1) / (N * N * (1 - x * x) * dp * dp)

    return x, w

def gaussxwab(N, a, b):
    x, w = gaussxw(N)
    return 0.5 * (b - a) * x + 0.5 * (b + a), 0.5 * (b - a) * w


# integrate an elliptical orbit with adaptive timestep 4th-order Runge-Kutta

import numpy
import pylab
import math
from orbit_adaptive import *

# circular orbit
o = orbit(1, 0.95)  # a, e

# period
P = o.keplerPeriod()

err = 1.e-7
histRK4 = o.intRK4(0.0005, err, P)

print "number of steps = ", len(histRK4.t)-1

# plot the orbit
pylab.plot(histRK4.x, histRK4.y, label="4th order RK", color="b")
#pylab.scatter(histRK4.x, histRK4.y, marker="x", color="b")

# mark the Sun
pylab.scatter([0],[0],s=250,marker=(20,1),color="k")
pylab.scatter([0],[0],s=200,marker=(20,1),color="y")

pylab.xlim(-1.5,1.5)
pylab.ylim(-2.5,0.5)

ax = pylab.gca()
ax.set_aspect("equal", "datalim")

pylab.savefig("orbit-rk4-adaptive.png")

# plot the energy
pylab.clf()

E = histRK4.energy()
pylab.plot(histRK4.t, E/E[0])

pylab.ylabel(r"$E(t)/E_0$")
pylab.xlabel(r"t")

ax = pylab.gca()
ax.ticklabel_format(useOffset=False)

pylab.savefig("energy-rk4-adaptive.png")

# plot the timesteps
pylab.clf()

dt = histRK4.t[1:-1] - histRK4.t[0:-2]
tmid = 0.5*(histRK4.t[1:-1] + histRK4.t[0:-2])
pylab.scatter(tmid, dt)

ax = pylab.gca()
ax.set_yscale('log')

pylab.ylabel(r"$\tau$")
pylab.xlabel(r"t")

pylab.ylim(1.e-5,1.e-1)

pylab.savefig("dt-rk4-adaptive.png")


import numpy as np
import matplotlib.pyplot as plt

from matplotlib import animation, rc
from IPython.display import HTML

class uTriangular():
    """
      documentation
    """
    def __init__(self, x_pts, num_terms=20, c_wave=1., L=1.):
        self.x_pts = x_pts
        self.num_terms = num_terms
        self.c = c_wave
        self.L = L

    def B_coeff(self, n):
        """Fourier coefficient for the nth term in the expansion, which is only
            non-zero if n is odd, or n = 2*m + 1.
        """
        if n % 2 == 1:   # n is odd
            m = (n - 1)/2
            return (-1)**m * 8. / (n * np.pi)**2
        else:  # n is even
            return 0.

    def k(self, n):
        """Wave number for n
        """
        return n * np.pi / self.L

    def u_wave(self, t):
        """Returns the wave from the sum of Fourier components.
        """
        y_pts = np.zeros(len(self.x_pts))  # define y_pts as the same size as x_pts
        for n in np.arange(0, self.num_terms):
            y_pts += self.B_coeff(n) * np.sin(self.k(n) * self.x_pts) \
                      * np.cos(self.k(n) * self.c * t)
        return y_pts

L = 1.
num_n = 40
c_wave = 1
omega_1 = np.pi * c_wave / L
tau = 2.*np.pi / omega_1

# Set up the array of x points (whatever looks good)
x_min = 0.
x_max = L
delta_x = 0.01
x_pts = np.arange(x_min, x_max, delta_x)

u_triangular_1 = uTriangular(x_pts, num_n, c_wave, L)
u_triangular_2 = uTriangular(x_pts, num_n/8, c_wave, L)

# Make a figure showing the initial wave.
t_now = 0.

fig = plt.figure(figsize=(6,4), num='Standing wave')
ax = fig.add_subplot(1,1,1)
ax.set_xlim(x_min, x_max)
gap = 0.1
ax.set_ylim(-1. - gap, 1. + gap)
ax.set_xlabel(r'$x$')
ax.set_ylabel(r'$u(x, t=0)$')
ax.set_title(rf'$t = {t_now:.1f}$')

line, = ax.plot(x_pts,
                u_triangular_1.u_wave(t_now),
                color='blue', lw=2)
line2, = ax.plot(x_pts,
                u_triangular_2.u_wave(t_now),
                color='red', lw=2)

fig.tight_layout()

t_array = tau * np.arange(0., 1.125, .125)

fig_array = plt.figure(figsize=(12,12), num='Standing wave')

for i, t_now in enumerate(t_array):
    ax_array = fig_array.add_subplot(3, 3, i+1)
    ax_array.set_xlim(x_min, x_max)
    gap = 0.1
    ax_array.set_ylim(-1. - gap, 1. + gap)
    ax_array.set_xlabel(r'$x$')
    ax_array.set_ylabel(r'$u(x, t)$')
    ax_array.set_title(rf'$t = {t_now/tau:.3f}\tau$')

    ax_array.plot(x_pts,
                  u_Triangular(x_pts, t_now, num_n, c_wave, L),
                  color='blue', lw=2)

fig_array.tight_layout()
fig_array.savefig('Taylor_Problem_16p14.png',
                   bbox_inches='tight')

# Set up the t mesh for the animation.  The maximum value of t shown in
#  the movie will be t_min + delta_t * frame_number
t_min = 0.   # You can make this negative to see what happens before t=0!
t_max = 2.*tau
delta_t = t_max / 100.
t_pts = np.arange(t_min, t_max + delta_t, delta_t)

u_triangular_1 = uTriangular(x_pts, num_n, c_wave, L)
u_triangular_2 = uTriangular(x_pts, num_n/8., c_wave, L)

fig_anim = plt.figure(figsize=(6,3), num='Triangular wave')
ax_anim = fig_anim.add_subplot(1,1,1)
ax_anim.set_xlim(x_min, x_max)
gap = 0.1
ax_anim.set_ylim(-1. - gap, 1. + gap)

# By assigning the first return from plot to line_anim, we can later change
#  the values in the line.
line_anim, = ax_anim.plot(x_pts,
                          u_Triangular(x_pts, t_min, num_n, c_wave, L),
                          color='blue', lw=2)

fig_anim.tight_layout()

def animate_wave(i):
    """This is the function called by FuncAnimation to create each frame,
        numbered by i.  So each i corresponds to a point in the t_pts
        array, with index i.
    """
    t = t_pts[i]
    y_pts = u_triangular(x_pts, t, num_n, c_wave, L)

    line_anim.set_data(x_pts, y_pts)  # overwrite line_anim with new points
    return (line_anim,)   # this is needed for blit=True to work

frame_interval = 80.  # time between frames
frame_number = 101    # number of frames to include (index of t_pts)
anim = animation.FuncAnimation(fig_anim,
                               animate_wave,
                               init_func=None,
                               frames=frame_number,
                               interval=frame_interval,
                               blit=True,
                               repeat=False)

HTML(anim.to_jshtml())


from numpy import exp, linspace, ones_like
from numpy.testing import assert_allclose

from ..characteristicstracer import CharacteristicsTracer

class TestCharacteristicsTracer:
    def test__velocity_is_constant__works_ok(self):
        RES = 32
        grid, dx = linspace(0, 1.0, num=RES+1, retstep=True)
        u = ones_like(grid)
        d = 0.0
        tfinal = 1.0
        dt = dx
        sut = CharacteristicsTracer(grid, u, d, tfinal, dt)

        x_actual, __ = sut.compute_characteristic(0.0)

        # Locations of the points on the characteristic curve must be
        # collocated with the grid points due to the chosen parameters.
        x_desired = grid.copy()

        assert_allclose(x_actual, x_desired, rtol=1e-16)

    def test__velocity_increases_linearly__trajectory_is_exponential(self):
        L = 5
        N = 200
        grid, dx = linspace(-L, 0.0, num=N+1, retstep=True)

        u = grid + 2*L
        d = 0.0

        tfinal = 1.0
        t, dt = linspace(0.0, tfinal, num=N+1, retstep=True)

        sut = CharacteristicsTracer(grid, u, d, tfinal, dt)

        x_actual, t_actual = sut.compute_characteristic(-L)
        x_desired = L * (-2 + exp(t_actual))

        # Somehow, I cannot get better match between actual and desired,
        # probably, due to the first-order Forward Euler integration in time.
        assert_allclose(x_actual, x_desired, rtol=1e-3, atol=2e-2)


import numpy as np
import matplotlib.pyplot as plt
# from numba import jit, cuda
import os

# Create a class to build perceptron layers
class SLP:
    def __init__(self, **kwargs):
        # get parameters
        self.output_size = kwargs.get('output_size')
        self.input_size = kwargs.get('input_size')
        self.activation = kwargs.get('activation')
        # Create empty input vector
        self.input_vec = None
        # Initialize weights
        self.weights = None
        self.learning_rate = None
        self.last_layer = True
        self.weight_delta_ = None
        self.momentum_alpha = kwargs.get('momentum')
        self.weight_delta = None
        self.loss = None

    # Update weights using expected input and output
    def calculate_weight_delta(self, delta):
        self.weight_delta = np.dot(delta, self.input_vec.T)

    def differentiation_of_activation(self):
        if self.activation == 'sigmoid':
            return (self._output * (1 - self._output))

    def update_weights(self, desired=None, delta_mtx=None):
        if self.last_layer:
            # I am just adding for sigmoid layer for now, maybe in the following assignments I will add more.
            if self.activation == 'sigmoid':
                error = desired - self._output
                delta = error * self.differentiation_of_activation()
                self.calculate_weight_delta(delta)
                delta = np.dot(self.weights.T, delta)
                self.weights = self.weights + self.learning_rate * self.weight_delta
                if self.momentum_alpha:
                    self.weights += self.momentum_alpha * self.weight_delta_
                self.weight_delta_ = self.weight_delta.copy()
        else:
            if self.activation == 'sigmoid':
                # Just note that the error in the last layer and delta_mtx is subject to the same calculations for
                # backpropagation, that is the idea of backpropagation.
                delta = delta_mtx[0:-1,:] * self.differentiation_of_activation()
                self.calculate_weight_delta(delta)
                delta = np.dot(self.weights.T, delta)
                self.weights = self.weights + self.learning_rate * self.weight_delta
                if self.momentum_alpha:
                    self.weights += self.momentum_alpha * self.weight_delta_
                self.weight_delta_ = self.weight_delta.copy()

        return delta

    def reset(self, learning_rate):
        self.input_vec = np.zeros((self.input_size, 1))
        # Initialize weights
        np.random.seed(35) # Easy one to converge
        # np.random.seed(27) # Hard one to converge
        # np.random.seed(15) # Also another hard one
        self.weights = np.random.uniform(-1, 1, (self.output_size, self.input_size))
        self.weight_delta = np.zeros((self.output_size, self.input_size))
        self.loss = None
        self.weight_delta_ = np.zeros((self.output_size, self.input_size))
        self.learning_rate = learning_rate

    @property
    def output(self):
        value = np.dot(self.weights, self.input_vec)
        if self.activation == 'sigmoid':
            self._output = self.sigmoid(value)
            return self._output.copy()

        elif self.activation == 'linear':
            self._output = value
            return self._output.copy()

    @staticmethod
    def sigmoid(value, a=1):
        value *= a
        return 1 / (1 + np.exp(-value))

class MLP:
    def __init__(self, **kwargs):
        self.network = []
        self.error_tolerance = kwargs.get('error_tolerance')
        self.number_of_epoch = None
        self.learning_rate = kwargs.get('learning_rate')
        self.max_epoch = 1e6
        self.name = kwargs.get('name')

    def add(self, single_layer):
        if not self.network:
            self.network.append(single_layer)
            self.network_size = len(self.network)

        else:
            self.network.append(single_layer)
            # Set the previous last layer to False
            self.network[-2].last_layer = False
            self.network_size = len(self.network)

        single_layer.input_size += 1 # To add the biases

    def backpropagation(self, desired_output):
        i = self.network_size - 1
        while i >= 0:
            if i == self.network_size - 1:
                delta = self.network[i].update_weights(desired=desired_output)
            else:
                delta = self.network[i].update_weights(delta_mtx=delta)

            i -= 1

    def output(self, input_vec):
        for count, slp in enumerate(self.network):
            if count == 0:
                slp.input_vec = np.append(input_vec.flatten(), [1], axis=0).reshape(-1,1) # Add the bias terms
            else:
                slp.input_vec = np.append(output.flatten(), [1], axis=0).reshape(-1,1) # Add the bias terms
            output = slp.output
        return output.copy()

    def sample_data(self, dataset):
        index = np.random.choice(len(dataset), len(dataset), replace=False)
        return index

    def train(self, dataset):
        # Initial call
        data_trained = np.zeros(len(dataset), dtype=np.bool)
        self.number_of_epoch = 0
        for slp in self.network:
            slp.reset(self.learning_rate)
        failed = False
        loss_vs_epoch = []

        while np.sum(data_trained) < 16:
            if self.number_of_epoch > self.max_epoch:
                failed = True
                break
            index = self.sample_data(dataset)
            # Make back propagation individually
            error_avg = np.zeros(len(dataset))
            total_loss = 0
            for idx in index:
                data = dataset[idx].copy()
                desired_output = dataset_output(data)
                output = self.output(data)
                error = desired_output - output
                abs_error = abs(error)
                total_loss += 0.5 * np.dot(error.T, error).item()
                error_avg[idx] = abs_error
                if abs_error < self.error_tolerance:
                    data_trained[idx] = True
                else:
                    data_trained[idx] = False
                # Update the weights whenever the desired state is not reached
                self.backpropagation(desired_output=desired_output)
            average_error = np.mean(error_avg)
            loss_vs_epoch.append(total_loss)
            self.number_of_epoch += 1
            # if self.number_of_epoch % 1000 == 0:
            #     print(
            #         # f"Selected input: {data.reshape((-1,))}, Output: {output.item()}, "
            #         # f"Desired Output: {desired_output}, Error: {error.item()}, "
            #         f"Number of epochs {self.number_of_epoch}, "
            #         f"trained number elements {np.sum(data_trained)}, "
            #         f"learning rate: {self.learning_rate}, "
            #         f"Total loss: {total_loss}, "
            #         f"Average Error: {average_error}\n"
            #         f"Inivididual errors: {error_avg}\n"
            #         f"{data_trained}"
            #     )

            # print(data_trained)
            # self.number_of_epoch += 1
        for i in range(self.network_size):
            with open(f"saved_weights/{self.name}_learning_rate_{self.learning_rate:.2f}_weights{i:d}.npy", 'wb') as f:
                np.save(f, self.network[i].weights)
        reason_string = "Failed" * failed + "Completed" * (1 - failed)
        print(f"Training Done for learning rate {self.learning_rate} | reason: {reason_string}")
        return self.number_of_epoch, loss_vs_epoch, reason_string, data_trained

def dataset_output(data):
    return 1 if np.sum(data) % 2 == 1 else 0

def main():
    # Save figures and weights
    if not os.path.exists('saved_weights'):
        os.makedirs('saved_weights')
    if not os.path.exists('figures'):
        os.makedirs('figures')
    # Create the network
    neural_network = MLP(error_tolerance=0.05, learning_rate=0.05, name='network1')
    neural_network.add(SLP(input_size=4, output_size=4, activation='sigmoid'))
    neural_network.add(SLP(input_size=4, output_size=1, activation='sigmoid'))

    # Create a dictionary for input-output
    dataset = []
    # Create the dataset
    for i in range(2):
        for j in range(2):
            for k in range(2):
                for iterator in range(2):
                    dataset.append(np.array([i, j, k, iterator]).reshape(4, 1))  # Create bits

    # Keep a list to visualize the training results
    list_epochs = []
    loss_vs_epoch_list = []

    for i in range(10):
        if i != 0:
            neural_network.learning_rate = 0.05 + i * 0.05
        epoch, loss_vs_epoch, reason_string, data_train_result = neural_network.train(dataset)
        loss_vs_epoch_list.append(loss_vs_epoch)
        list_epochs.append(epoch)
        plt.plot(loss_vs_epoch, color='b')
        plt.xlabel('Number of Epochs')
        plt.ylabel('Loss')
        plt.title(f"Number of Epochs against Loss, Number of Learned Inputs: {np.sum(data_train_result)}")
        # plt.show()
        plt.savefig(
            f"figures/{neural_network.name}_loss_learning_rate{neural_network.learning_rate:.2f}.png",
            bbox_inches='tight')
        plt.close()

    plt.plot([x * 0.05 for x in range(1, 10 + 1)], list_epochs, color='b')
    plt.xlabel('Learning rate')
    plt.ylabel('Epoch Time')
    plt.title(f"Number of Epochs against learning rates")
    plt.savefig(
        f"figures/{neural_network.name}_learning_rate_vs_epoch_time.png",
        bbox_inches='tight')
    plt.close()

    for count, loss in enumerate(loss_vs_epoch_list):
        learning_rate = 0.05 + count * 0.05
        plt.plot(loss,label=f"Learning_rate{learning_rate:.2f}")
    plt.xlabel('Number of Epochs')
    plt.ylabel('Loss')
    plt.legend(loc='best')
    plt.title(f"Number of Epochs against Loss, Number of Learned Inputs: {np.sum(data_train_result)}")
    plt.savefig(
        f"figures/{neural_network.name}_loss_combined.png",
        bbox_inches='tight')
    plt.close()

    # Keep lists to visualize the training results
    list_epochs = []
    loss_vs_epoch_list = []

    # Create the second network momentum now
    neural_network = MLP(error_tolerance=0.05, learning_rate=0.05, name='network2')
    neural_network.add(SLP(input_size=4, output_size=4, activation='sigmoid', momentum=0.9))
    neural_network.add(SLP(input_size=4, output_size=1, activation='sigmoid', momentum=0.9))
    for i in range(10):
        if i != 0:
            neural_network.learning_rate = 0.05 + i * 0.05
        epoch, loss_vs_epoch, reason_string, data_train_result = neural_network.train(dataset)
        list_epochs.append(epoch)
        loss_vs_epoch_list.append(loss_vs_epoch)
        plt.plot(loss_vs_epoch, color='b')
        plt.xlabel('Number of Epochs')
        plt.ylabel('Loss')
        plt.title(f"Number of Epochs against Loss, Number of Learned Inputs: {np.sum(data_train_result)}")
        plt.savefig(
            f"figures/{neural_network.name}_loss_learning_rate{neural_network.learning_rate:.2f}.png",
            bbox_inches='tight')
        plt.close()

    for count, loss in enumerate(loss_vs_epoch_list):
        learning_rate = 0.05 + count * 0.05
        plt.plot(loss,label=f"Learning_rate{learning_rate:.2f}")
    plt.xlabel('Number of Epochs')
    plt.ylabel('Loss')
    plt.legend(loc='best')
    plt.title(f"Number of Epochs against Loss, Number of Learned Inputs: {np.sum(data_train_result)}")
    plt.savefig(
        f"figures/{neural_network.name}_loss_combined.png",
        bbox_inches='tight')
    plt.close()

    plt.plot([x * 0.05 for x in range(1, 10 + 1)], list_epochs, color='b')
    plt.xlabel('Learning rate')
    plt.ylabel('Epoch Time')
    plt.title(f"Number of Epochs against learning rates")
    plt.savefig(
        f"figures/{neural_network.name}_learning_rate_vs_epoch_time.png",
        bbox_inches='tight')
    plt.close()
if __name__ == '__main__':
    main()

    # Later, if you want to use weights to do something else, use the saved weights
    # with open('test.npy', 'rb') as f:
    #
    #     a = np.load(f)
    #
    #     b = np.load(f)


import numpy as np
import pytest

import src.utilities as utilities

@pytest.fixture
def distinct_points_mock():
	point_1 = np.array([1, 2, 3])
	point_2 = np.array([10, 20, 30])
	return (point_1, point_2)

@pytest.fixture
def same_points_mock():
	point_1 = np.array([1.1, 20.4, 30.2, 300])
	return (point_1, point_1)

def test_calculate_euclidean_distance_for_distinct_points(distinct_points_mock):
	distance = utilities.calculate_euclidean_distance(distinct_points_mock[0], distinct_points_mock[1])
	assert distance == pytest.approx(33.67, 0.01)

def test_calculate_euclidean_distance_for_same_points(same_points_mock):
	distance = utilities.calculate_euclidean_distance(same_points_mock[0], same_points_mock[1])
	assert distance == 0


"""
@file orthogonal.py
@description solutions to linear algebra textbook 6.2
@author Hiukong Dan
@version 1.0 May 26, 2021
"""
#!/bin/python
from sympy import Matrix
import numpy as np
import random

def problem35():
    A = np.array([[-6,-3,6,1],[-1,2,1,-6],[3,6,3,-2],[6,-3,6,-1],[2,-1,2,3],[-3,6,3,2],[-2,-1,2,-3],[1,2,1,6]])
    for x in range(0, A.shape[1]-1):
        for y in range(x+1, A.shape[1]):
            print(Matrix(np.dot(A.T[x], A.T[y])))

def problem36():
    A = np.array([[-6,-3,6,1],[-1,2,1,-6],[3,6,3,-2],[6,-3,6,-1],[2,-1,2,3],[-3,6,3,2],[-2,-1,2,-3],[1,2,1,6]])
    U = np.array([A.T[x] / np.linalg.norm(A.T[x]) for x in range(A.shape[1])]).T
    # (a)
    # print("U^T * U", np.matmul(U.T, U))
    # print("U * U^T", np.matmul(U, U.T))

    # (b)
    y = np.array([random.random() * 18 - 9 for x in range(8)])
    print("y", y)

    p = np.matmul(U, np.matmul(U.T, y))
    print("p", p)

    z = y - p
    print("z", z)

    print("dot(z, p)", np.dot(z, p))

    # (c)
    for x in range(U.shape[1]):
        print(np.dot(U.T[x], z))

if __name__ == "__main__":
    problem36();