import scipy
import scipy.stats as ss
import numpy as np
import matplotlib
import pandas as pd
import random
import math

def iqr_threshold_method(scores, margin):
    q1 = np.percentile(scores, 25, interpolation='midpoint')
    q3 = np.percentile(scores, 75, interpolation='midpoint')
    iqr = q3-q1
    lower_range = q1 - (1.5 * iqr)
    upper_range = q3 + (1.5 * iqr)
    lower_range = lower_range - margin
    upper_range = upper_range + margin
    return lower_range, upper_range

def angle(point1, point2, point3):
    v21 = np.subtract(point2, point1)
    v31 = np.subtract(point3, point1)
    dot_product = (v21*v31).sum()
    normalization = np.linalg.norm(v21)*np.linalg.norm(v31)
    acute_angle = np.arccos(dot_product/normalization)
    return acute_angle

def eval_angle_point(point, data):
    angles_data = []
    for index_b, b in enumerate(data):
        if (np.array_equal(b, point)):
            continue

        for c in data[index_b + 1:]:
            if (np.array_equal(c, point)) or (np.array_equal(c, b)):
                continue
            angles_data.append(angle(point, b, c))
    return angles_data

def AngleOutlier(data, margin=0):
    """Returns numpy array with data points labelled as outliers
    Parameters
    ----------
    data: numpy 2d array like data points

    margin: int, default=0
        Margin of error
    """
    no_of_data_point = data.shape[0]
    variance_of_each_datapoint = []

    for i in range(0, no_of_data_point):
        point = data[i]
        temp = eval_angle_point(point, data)
        variance_of_each_datapoint.append(np.var(temp))

    lower_range, upper_range = iqr_threshold_method(variance_of_each_datapoint, margin)

    outlier_points = []

    for i in range(0, no_of_data_point):
        if variance_of_each_datapoint[i] < lower_range or variance_of_each_datapoint[i] > upper_range:
            outlier_points.append(data[i])

    return outlier_points, lower_range, upper_range, variance_of_each_datapoint

if __name__=='__main__':
    np.random.seed(16)

    normal_mean = np.array([1.0, 2.0])
    normal_covariance = np.array([[0.2, 0.0], [0.0, 0.1]])
    normal_data = np.random.multivariate_normal(normal_mean, normal_covariance, 10)

    anomaly_mean = np.array([6.0, 8.0])
    anomaly_covariance = np.array([[2.0, 0.0], [0.0, 4.0]])
    anomaly_data = np.random.multivariate_normal(anomaly_mean, anomaly_covariance, 10)
    all_data = np.concatenate((normal_data, anomaly_data), axis=0)
    print(all_data)
    print(all_data.shape)
#    point = all_data[0]
    #print(point)
    #res = eval_angle_point(point, all_data)
    res = AngleOutlier(all_data)
    print(res)
    #print(res)


import random
import matplotlib.pyplot as plt
import numpy as np
import math
from typing import List

def modelo(x):
    return 2 * math.exp(0.3 * x) + 3 * random.random()

# a, b = 0, 10
# x = a + (b - a) * np.random.rand(50)
# x.sort()
# y = [modelo(xi) for xi in x]

x = [0.0251, 0.6401, 0.8416, 1.1842, 1.72, 2.0679, 2.3642, 2.854]
y = [2.2251, 4.7429, 5.4953, 6.3624, 11.3061, 15.0322, 19.9302, 31.3503]

def best_exp(x: List[float], y: List[float]):
    sum_x = sum(x)
    sum_x2 = sum(xi ** 2 for xi in x)
    A = [[len(x), sum(x)], [sum_x, sum_x2]]
    y_ = [math.log(yi) for yi in y]
    sum_xy = sum(xi * yi for xi, yi in zip(x, y_))
    B = [sum(y_), sum_xy]
    a0, a1 = np.linalg.solve(A, B)
    a, b = math.exp(a0), a1
    return a, b

a, b = best_exp(x, y)

def bexp(x):
    return a * math.exp(b * x)

if __name__ == '__main__':

    print(a, b)
    t = np.linspace(min(x), max(x), 100)
    bexpt = [bexp(i) for i in t]
    plt.plot(t, bexpt)
    plt.scatter(x, y)
    plt.savefig('pontos_exp.png')


import sympy
'''
@alt(シンボル|記号|変数)
@prefix(z;[変数|パラメータ])
@prefix(e;[数式|数値])

[代数計算|シンボル計算|数式処理][|モジュール|ライブラリ]を使う
'''

s = 'z'

sympy.Symbol(s)
'''
sをシンボルに変換する
sを代数計算の変数に変換する
sの変数名を数式に変換する
'''

z = sympy.Symbol(s)
'''
sを[シンボル|変数][に|化]して、zにする
'''

e = e2 = sympy.Symbol(s)
n = 2
oo = sympy.oo

e.subs(z, n)
'''
eのzにnを代入する
'''

e.subs(z, e2)
'''
eのzにe2を代入する
eのzをe2で置き換える
'''

sympy.expand(e)
'''
eを展開する
eの展開を行う
'''

sympy.factor(e)
'''
eを因数分解する
eの因数分解を行う
'''

sympy.sympify(e)
'''
eを簡単[に|化]する
eを簡略[に|化]する
eの簡[略|単]化を行う
'''

sympy.apart(e)
'''
eを部分分数[に|として]展開する
eの部分分数化[を行う|する]
'''
# Expand `e` into partial fractions

sympy.solve(e)
'''
{eを|方程式として}解く
方程式の解[|を求める]
'''

# Solve a formula as an equation
# Solve a equation `e`
# Find solutions to equations

sympy.solve(e, z)
'''
{方程式として|eのzの解を}求める
'''

sympy.solve([e, e2])
'''
連立方程式の解を求める
連立方程式を解く
'''

# Solve simultaneous equations
# Find solutions to simultaneous equations

sympy.limit(e, z, 0)
'''
@alt(とき|時|場合|際)

zが0に近づくとき[の|、]eの極限値を求める
'''

sympy.limit(e, z, oo)
'''
zが無限大に近づくとき[の|、]eの極限値を求める
'''

sympy.limit(e, z, -oo)
'''
zがマイナス無限大に近づくとき[の|、]eの極限値を求める
'''

sympy.diff(e)
'''
eを微分する
eの微分を求める
'''

sympy.diff(e, z)
'''
zについてeの微分を[行う|求める]
{eを|zについて}微分する
'''

sympy.diff(e, z, n)
'''
{eを|zについて}n階微分する
eの[zについての|]n階微分を[求める|行う]
'''

sympy.integrate(e)
'''
eを積分する
eの[積分|インテグラル]を[求める|行う]
'''

sympy.integrate(e, z)
'''
zについてeを積分する
zについてeの[積分|インテグラル]を[求める|行う]
'''

e = sympy.E
float(e)
'''
eの数値[を求める|]
eを数値計算する
eを[数値|浮動小数点数]に変換する
'''

sympy.sqrt(e)
'''
eの平方根を求める
'''

# sympy.E**(sympy.I * sympy.pi) == -1
# '''
# オイラーの等式を使う
# '''

# sympy.summation(e, (z, 1, N))
# '''
# @test(import sympy;z,N=sympy.Symbol('z N');e=z**2;$$)
# eの総和[|を求める]
# '''


import numpy as np
import pandas as pd

class OLS_Simu:
    """Generate an OLS object with randomized data

    Parameters
    ----------
    n, k, beta: int, int, list
        n is the sample size, k is the number of parameters including constant,
        beta is a list of predetermined OLS parameters.
    """
    def __init__(self, n, k, beta):

        self.n = n
        self.k = k

        # generate X matrix
        self.const = np.ones(self.n)
        self.const = self.const[np.newaxis, :]
        self.X_inde = np.random.randn(self.k-1, self.n)
        self.X = np.concatenate((self.const.T, self.X_inde.T), axis=1)

        # set beta a vertical vector
        self.beta = beta
        if len(self.beta) == self.k:
            self.beta_array = np.array(self.beta)
            self.beta_array = self.beta_array[np.newaxis, :].T
        else:
            raise Exception('length of beta is not equal to k!')

        # generate set disturbance term as a vertical vector
        self.u = np.random.randn(self.n)
        self.u = self.u[np.newaxis, :].T

        # generate y
        self.y = self.X@self.beta_array + self.u

        # estimates and residuals
        self.beta_hat = np.linalg.inv(self.X.T@self.X)@self.X.T@self.y
        self.resid =  self.y - self.X@self.beta_hat

#################################################
    def ols_estimates(self):
        """Estimate the model and return the beta_hat and residuals."""
        self.beta_hat = np.linalg.inv(self.X.T@self.X)@self.X.T@self.y
        self.resid =  self.y - self.X@self.beta_hat
        return self.beta_hat, self.resid

#################################################
    def anova(self):
        """ANOVA decomposition"""
        self.TSS = np.linalg.norm(self.y)
        self.ESS = np.linalg.norm(self.X@self.beta_hat)
        self.RSS = np.linalg.norm(self.y)-np.linalg.norm(self.X@self.beta_hat)
        return self.TSS, self.ESS, self.RSS

#################################################
    def proj_mat_P(self):
        """Projection matrix P"""
        self.proj_matP = self.X@np.linalg.inv(self.X.T@self.X)@self.X.T
        return self.proj_matP
#################################################
    def proj_mat_M(self):
        """Projection matrix M"""
        self.proj_matM =  np.eye(self.X.shape[0]) - self.X@np.linalg.inv(self.X.T@self.X)@self.X.T
        return self.proj_matM
#################################################
    def s_sqr(self):
        """Unbiased estimator of sigma square."""
        return np.sum(self.resid**2)/(len(self.resid)-self.k)
#################################################
    def cov_beta_hat(self):
        """Covariance matrix of estimated coefficients beta, standar"""
        self.cov_beta_hat = np.sum(self.resid**2)/(len(self.resid)-self.k)*np.linalg.inv(self.X.T@self.X)
        self.cov_beta_hat_prin_diag = self.cov_beta_hat.diagonal()
        return (self.cov_beta_hat, self.cov_beta_hat_prin_diag)

##################################################
##################################################
##################################################
##################################################
def gen_X(n, k):
    """
    Parameters
    ----------
    n, k: int
        generate a random explanatory matrxi with constant term of size n by k, n is the sample size,
        k is the number of parameters.
    Returns
    ----------
    X: ndarray
        matrix styled n by k random array generated by np.random.randn().
    """
    const = np.ones(n)
    const = const[np.newaxis, :]
    X_inde = np.random.randn(k-1, n)
    X = np.concatenate((const.T, X_inde.T), axis=1)
    return X

#################################################
def gen_beta(params = [2, 3, 4, 5]):
    """
    Parameters
    ----------
    params: list
        input a list of beta parameters

    Returns
    ----------
    beta_array: ndnarry
        an array of beta coefficients, no nothing input, default value
        params = [2, 3, 4, 5]
    """
    beta_array = np.array(params)
    beta_array = beta_array[np.newaxis, :].T
    return beta_array

#################################################
def gen_u(n):
    """
    Parameters
    ----------
    params: int
        input a integer for the size of disturbance term

    Returns
    ----------
    u: ndnarry
        an array of disturbance term
    """
    u = np.random.randn(n)
    u = u[np.newaxis, :].T
    return u

#################################################
def ols(y, X):
    """
    Parameters
    ----------
    y, x: ndarray
        input ndarrays produced by gen_X

    Returns
    ----------
    beta_hat: ndnarry
        an array of estimated coefficients
    """
    beta_hat = np.linalg.inv(X.T@X)@X.T@y
    return beta_hat

    # TSS = np.linalg.norm(y)
    # ESS = np.linalg.norm(X@beta_hat)
    # RSS = np.linalg.norm(y-X@beta_hat)
#################################################
def proj_mat_P(X):
    """computing projection matrix P. Input X represents regressors.
    Parameters
    ----------
    X: ndarray
        input ndarrays produced by gen_X

    Returns
    ----------
    proj_matP: ndnarry
        Projection matrix P
    """
    proj_matP = X@np.linalg.inv(X.T@X)@X.T
    return proj_matP
#################################################
def proj_mat_M(X):
    """computing projection matrix M. Input X represents regressors.
    Parameters
    ----------
    X: ndarray
        input ndarrays produced by gen_X

    Returns
    ----------
    proj_matM: ndnarry
        Projection matrix M
    """
    proj_matM =  np.eye(X.shape[0]) - X@np.linalg.inv(X.T@X)@X.T
    return proj_matM
#################################################
def s_qr(resid, k):
    """
    Unbiased estimator of sigma square.
    Parameters
    ----------
    params: ndarray
        input an matrix-form ndarray of residuals

    Returns
    ----------
    beta_array: float
        return a float number
    """
    return np.sum(resid**2)/(len(resid)-k)

#################################################
def cov_beta_hat(resid, k, X):
    """
    Covariance matrix of estimated coefficients beta, standard
    Parameters
    ----------
    resid, k, X: ndarray, integer, ndarray

    Returns
    ----------
    cov_beta_hat, prin_diag: ndarray, ndarray

    """
    cov_beta_hat = np.sum(resid**2)/(len(resid)-k)*np.linalg.inv(X.T@X)
    cov_beta_hat_prin_diag = cov_beta_hat.diagonal()
    return (cov_beta_hat, cov_beta_hat_prin_diag)


# Problem: https://projecteuler.net/problem=213

"""
    . Let N = 30

    . Let squares = { s_0, s_1, ..., s_899 } be the set of all squares in the grid,
        where s_i indicates the square at (i//N, i%N).

    . Let fleas = { f_0, f_1, ..., f_899 } be the set of all fleas
        where f_0 indicates the flea that is at position (i//N, i%N) at the beginning.

    . To simplify the notation, we let "index idx" indicate "the position (idx // N, idx % N)"

    . Due to the linearity of expectation, the expected number of empty squares is the sum of expected number of empty squares of EACH square; i.e.,
        E(N_empty) = sum_{s \in squares} {1 * P(s is empty) + 0 * [1 - P(s is empty)}
                   = sum_{s \in squares} P(s is empty)

    . To determine the probability of square s being empty,
        P(s is empty) = product_{t \in fleas} P(flea t is not in s after 50 iterations)

    . We use Markov process to determine the probability of each flea being at each square after 50 iterations. For each flea k:
        . Let T be the transistion matrix where
            T[i][j] = the probability of a flea at index i jumps to index j.
            so, T[i][j] = 1 / n_neighbors
        . Let S be the probability matrix where S[i] indicate the probability of flea k being at index i.
            So, at iteration 0, S[i] = 1 for i = k, and S[i] = 0 otherwise.
        . Let S_50 be the probability matrix where S[i] indicate the probability of flea k being at index i after 50 iterations.
            So, S_50 = S * matrix_power(T, 50)

    . So, P(s is empty) = (1 - S_50[s] of flea 0) * (1 - S_50[s] of flea 1) * ... * (1 - S_50[s] of flea 899)
"""

import numpy as np

N = 30
M = 50

def index_to_position(idx):
    return idx // N, idx % N

def position_to_index(x, y):
    return x * N + y

def get_neighbors(idx):
    x, y = index_to_position(idx)

    available_positions_idx = []
    for dx, dy in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
        fx, fy = x + dx, y + dy
        if fx >= 0 and fx < N and fy >= 0 and fy < N:
            available_positions_idx.append(position_to_index(fx, fy))

    return available_positions_idx

if __name__ == "__main__":

    ans = 0

    T = np.zeros((N*N, N*N), dtype = np.float)
    for idx in range(N*N):
        neighbors_idx = get_neighbors(idx)
        n_neighbors_idx = len(neighbors_idx)
        for f_idx in neighbors_idx:
            T[idx][f_idx] = 1 / n_neighbors_idx

    TpM = np.linalg.matrix_power(T, M)

    P_empty = np.ones(N*N, dtype = np.float)

    S = np.zeros((1, N*N), dtype = np.float)

    for flea_idx in range(N*N):
        S = np.zeros((1, N*N), dtype = np.float)
        S[0][flea_idx] = 1.0
        S = S.dot(TpM)
        P_empty = np.multiply(P_empty, 1.0 - S)

    ans = np.sum(P_empty)

    print(ans)


#!/usr/bin/env python

import math
import random

import numpy as np

def _ext_euc_alg(a, b):
    '''
    Performs Extended Euclidean Algorithm on two integers

            Parameters:
                    a : An integer
                    b : Another integer

            Returns:
                    (gcd, x, y) : tuple of GCD of (a,b) and (x,y) orthogonal to (a,b)
    '''
    if a == 0:
        return b, 0, 1

    gcd,x1,y1 = _ext_euc_alg(b % a, a)

    # Update x and y using results of recursive call
    x = y1 - (b // a) * x1
    y = x1

    return gcd,x,y

def _inv_mult(a, m):
    '''
    Find modular inverses using EEA

            Parameters:
                    a : Inverse to be found
                    m : Modulus – must have (a, m) == 1

            Returns:
                    inv : Modular inverse of a mod m
    '''
    gcd, x, _ = _ext_euc_alg(a, m)

    if gcd == 1:
        return (x + m) % m

def _mod_pow(a, e, m):
    '''
    Compute modular exponentation under modular arithmetic

            Parameters:
                    a : Base
                    e : Exponent
                    m : Modulus

            Returns:
                    b : Value of a^e % m
    '''
    a, b = a % m, 1

    if a == 0: return 0

    while e > 0:
        if (e & 1) == 1:
            b = (a * b) % m

        e >>= 1
        a = (a * a) % m

    return b

def RSA_setup(p,q):
    '''
    Sets up parameters for RSA algorithm

            Parameters:
                    p : Large prime
                    q : Another large prime

            Returns:
                    (n, e, d) : RSA modulus, public key, and private key
    '''
    n = p * q
    phi_n = (p - 1) * (q - 1)

    # select public key s.t. (e, phi_n) == 1
    found = False

    while not found:
        e = random.randint(1, phi_n)
        if math.gcd(e, phi_n) == 1:
            found = True

    d = _inv_mult(e, phi_n)

    return n, e, d

def RSA_encrypt(m, e, n):
    '''
    Encrypts messages with RSA algorithm

            Parameters:
                    m : Integer representation of message – must be between 1 and n
                    e : Public key – selected as per RSA_setup
                    n : Modulus – selected as per RSA_setup

            Returns:
                    M : Encrypted message
    '''
    return _mod_pow(m, e, n)

def RSA_decrypt(M, d, n):
    '''
    Decrypts messages with RSA algorithm

            Parameters:
                    M : Encrypted message – calculated as per RSA_encrypt
                    d : Private key – selected as per RSA_setup
                    n : Modulus – selected as per RSA_setup

            Returns:
                    m : Plaintext message
    '''
    return _mod_pow(M, d, n)

class PairValEnc:
    def __init__(self, p, q):
        '''
        Initializes Pair Value Encryption class – performs same setup as RSA algorithm

                Parameters:
                        p : Large prime
                        q : Another large prime

                Returns:
                        None
        '''
        n = p * q
        phi_n = (p - 1) * (q - 1)

        found = False

        while not found:
            e = random.randint(1, phi_n)
            if math.gcd(e, phi_n) == 1:
                found = True

        d = _inv_mult(e, phi_n)

        self.n = n
        self.phi_n = phi_n
        self.e = e
        self.d = d

    def load(self, values, mapping):
        '''
        Store problem space and mapping from problem to plaintext space in object

                Parameters:
                        values : list equal to problem space
                        mapping : dict object converting values in problem space to plaintext space

                Returns:
                        None
        '''
        self.values = values
        self.mapping = mapping
        self.inv_mapping = {v:k for k, v in mapping.items()}

    def encrypt(self, x):
        '''
        Map an input from the problem space to the plaintext space, and encrypt it

                Parameters:
                        x : Value in problem space

                Returns:
                        <enc> : Encrypted value of input
        '''
        if x in self.values:
            return _mod_pow(self.mapping[x], self.e, self.n)

    def compare(self, x, y):
        '''
        Compare pairs of encrypted values

                Parameters:
                        x : Encrypted value
                        y : Another encrypted value

                Returns:
                        <cmp> : Binary comparison, returning True if the inputs are matchable and False if not
        '''
        return ((x * y) % self.n) == 1

    def decrypt(self, x, d):
        '''
        Verify the decryption key and decrypt a value from the encryption space to the problem space

                Parameters:
                        x : Encrypted value
                        d : Private key

                Returns:
                        <dec> : Decrypted value of input
        '''
        if d == self.d:
            return self.inv_mapping[_mod_pow(x, self.d, self.n)]

def prep_example():
	'''
	Create the problem space and problem to plaintext mapping necessary for example in paper

			Parameters:
					None

			Returns:
					(X, X_to_Z) : Problem space as <list>, and problem to plaintext mapping as <dict>
	'''
	X = list(range(16))
	match = lambda x, y: int(x ^ y == 15)

	n = 33

	# Step 1, find a list of matches in X/~
	X_matches = []
	x = len(X)
	for i in range(x):
	    for j in range(i,x):
	        if match(i,j):
	            X_matches.append((i,j))

	# Step 2, find |X/~| pairs of modular inverses from Z/33Z
	Z_matches = []

	i = 1
	while len(Z_matches) < len(X_matches):
	    j = _inv_mult(i,n)

	    if j is None:
	        i += 1
	        continue

	    Z_matches.append((i,j))
	    i += 1

	# Step 3, map values over
	X_to_Z = {}

	for i, (x,y) in enumerate(X_matches):
	    X_to_Z[x] = Z_matches[i][0]
	    X_to_Z[y] = Z_matches[i][1]

	return X, X_to_Z


import logging
import numpy
import math
import sys

"""
Return true if x is less than or equal to y (with respect to tolerance tol).
"""
def float_le(x, y, tol):
    #TODO: handle degenerate cases (inf e nan)
    return (x == y) or (x-y) <= max(abs(x), abs(y))*tol

"""
lam = lambda
mu = mu
m = number of servers
"""
def p0(lam,mu,c):

    sum = 0.0
    for x in range(0,c):
        f1 = math.pow((lam/mu),x)
        f2 = math.factorial(x)
        val = f1*f2
        sum += val
    f3 = 1.0/math.factorial(c)
    f4 = math.pow((lam/mu),c)
    f5 = (c*mu)/(c*mu-lam)
    sum += f3*f4*f5

    prob0 = 1.0/sum
    return prob0

def pQueue(lam,mu,c):

    f1 = c*math.pow(lam/mu,c)
    f2 = math.factorial(c)
    f3 = c-(lam/mu)
    f4 = p0(lam,mu,c)
    pQ = 1-(f1/(f2*f3))*f4
    #print "PQueue - f1:",f1,"f2:",f2,"f3:",f3,"f4:",f4,"pQ:",pQ
    return pQ

def responseTimeCDF(lam,mu,c,t):

    rho = lam/(mu*c)
    if rho <= 0 or rho >=1 :
        logging.warning( "With c = %d, rho is %f which is unfeasible", c, rho)
        return 0
    retval = 0.0
    f1 = (lam-c*mu+mu*pQueue(lam,mu,c))/(lam-(c-1.0)*mu)
    f2 = 1-math.exp(-mu*t)
    f3 = ((1.0-pQueue(lam,mu,c))*mu)/(lam-(c-1)*mu)
    f4 = 1-math.exp(-(c*mu-lam)*t)
    retval = f1*f2+f3*f4
    #print "responseTimeCDF - f1:",f1,"f2:",f2,"f3:",f3,"f4:",f4,"retval:",retval
    return retval

def responseTimeCDF2(lam,mu,c,t):
    pQ = pQueue(lam,mu,c)
    f1 = pQ*(1-math.exp(-mu*t))
    f2 = ((c*mu-lam)/((c-1)*mu-lam))*(1-math.exp(-mu*t))
    f3 = ((mu)/((c-1)*mu-lam))*(1-math.exp(-(c*mu-lam)*t))
    f4 = (1-pQ)*(f2-f3)
    retval = f1+f4
    return retval

def pi0(lam,mu,m):
    rho = lam/(m*mu)
    part1 = (math.pow(m*rho,m)/math.factorial(m))*(1/(1-rho))
    part2=0.0
    for k in range(0,m):
        part2 +=math.pow(m*rho,k)/math.factorial(k)
    val = 1/(part1+part2)
    return val

def Pm(lam,mu,m):
    rho = lam/(m*mu)
    pai0 = pi0(lam,mu,m)
    pm = (math.pow(m*rho,m)/(math.factorial(m)*(1-rho)))*pai0
    return pm

"""
Compute the average response time for an M/M/m model:
 \frac{C(m, \lambda/\mu)}{m \mu - \lambda} + \frac{1}{\mu}
where:
- C(m, \lambda/\mu) is the Erlang's C formula:
  C(m,\lambda/\mu) = \frac{\left(\frac{(m\rho)^{m}}{m!}\right)\left(\frac{1}{1-\rho}\right)}{\sum_{k=0}^{m-1}\frac{(m\rho)^{k}}{k!} + \left(\frac{(m\rho)^m}{m!}\right)\left(\frac{1}{1-\rho}\right)}
- \rho is the system utilization:
  \rho = \frac{\lambda}{m\mu}
.

Parameters:
- arrival rate \lambda=lam,
- service rate \mu=mu, and
- number of server m=m
.
"""
def avgRespTime_MMC(lam,mu,m):

    rho = lam / (m * mu)
    if m==1:
        val = (1.0/mu)/(1.0-rho)
    else:
        pm = Pm(lam,mu,m)
        avgK = m*rho+(rho/(1-rho))*pm
        val = avgK/lam
    return val

def getMu_MM1(lam,r,perc):

    val = (lam*r-math.log(1-perc))/r
    return val

def respTimeCDF_MM1(lam,mu,r):
    return (1-math.exp(-mu*(1-lam/mu)*r))

def compute(lam, mu, r, tol):

    #error = 0.0000005

    #set initial mu

    # applies the formula with an increasing number m of
    # servers, all having the same service rate mu, until
    # the resoponse time val is <= than the limit r
    m = 0
    #for m in range(1,101):
    while True:
        m += 1

        # Check for stability
        if (lam/(m*mu)) >= 1:
            #print "[percentileTrivedi] model NOT stable"
            continue

        val = avgRespTime_MMC(lam,mu,m)

        # Check for target response time
        #if val <= r:
        #if (r-val)/r <= tol:
        if float_le(val, r, tol):
            logging.debug("lambda: %f, mu: %f, m: %d, RT: %f, limit: %f", lam, mu, m, val, r)
            break
        logging.debug("lambda: %f, mu: %f, m: %d --> RT: %f > limit: %f", lam, mu, m, val, r)

    return m

#if __name__ == "__main__":
def main(lam,mu,r,tol,debug=True):
   # if(len(sys.argv)<4):
   #     print "not enough input parameters"
   #     sys.exit()

    #lam = float(sys.argv[1])
    #mu = float(sys.argv[2])
    #r = float(sys.argv[3])

    logging.basicConfig(stream=sys.stderr, format='[%(filename)s:%(funcName)s:%(levelname)s>> %(message)s', level=logging.DEBUG if debug else logging.INFO)

    logging.debug("Input parameters: lambda = %f, mu =%f, r = %f, tol = %f", lam, mu, r, tol)

    #if((int(lam)/mu)>=1):
    if(r < 1/mu):
        logging.warning("model NOT feasible: response time < service time")
        #sys.exit()
        return -1

    return compute(lam,mu,r,tol)


import numpy as np

# P -> N
def p2n(p):
    '''Compute the expected first return time from state i to state j given the transition probabilities.'''
    # n_xy = p_xy + sum_{z ≠ y} p_xz (n_zy + 1)
    # n_xy - sum_{z ≠ y} p_xz n_zy = 1
    # Flatten n_xy by mapping (x, y) to x * d + y
    d = p.shape[0]
    a = np.eye(d * d)
    for x in range(d):
        for y in range(d):
            i = x * d + y
            for z in range(d):
                if z != y:
                    j = z * d + y
                    a[i, j] -= p[x, z]
    b = np.ones(d * d)
    # print(a)
    r = np.linalg.solve(a, b)
    return r.reshape((d, d))

# N -> P
def n2p(n):
    '''Compute the transition probability from state i to state j given the expected first return times.'''
    return n @ np.linalg.inv(n - np.diagflat(np.diagonal(n)) + 1)

if __name__ == '__main__':
    import math

    # Transition
    p0 = np.array([
        [0.9, 0.1],
        [0.01, 0.99],
    ])

    # First return times
    n0 = np.array([
        [0.9 * 1 + 0.1 * (100 + 1), 10],
        [100, 0.99 * 1 + 0.01 * (10 + 1)],
    ])

    print(np.abs(p2n(p0) - n0).max())
    print(np.abs(n2p(n0) - p0).max())

    # Random transitions
    # d = 5
    # p = np.random.dirichlet([1] * d, d)
    # Simple deterministic transitions
    p1 = np.array([
        [0, 1, 0],
        [0, 0, 1],
        [1, 0, 0],
    ])
    print(p2n(p1))
    print(np.abs(n2p(p2n(p1)) - p1).max())

    # Singular matrix => Some n_xy are inf
    # p = np.array([
    #     [0, 1, 0],
    #     [1, 0, 0],
    #     [1, 0, 0],
    # ])
    # n = np.array([
    #     [2, 1, math.inf],
    #     [1, 2, math.inf],
    #     [1, 2, math.inf],
    # ])
    # print(n2p(n))
    # p = np.array([
    #     [0, 1, 0],
    #     [1, 0, 0],
    #     [0, 0, 1],
    # ])
    # n = np.array([
    #     [2, 1, math.inf],
    #     [1, 2, math.inf],
    #     [math.inf, math.inf, 1],
    # ])
    # print(p2n(p))
    # print(n2p(n))


import sympy
import math
from prettytable import PrettyTable
x = sympy.Symbol('x')
table = PrettyTable()

iters = []
xn = []
funcF = []
funcD1 = []
funcD2 = []
errorTot = []

def secant(tol,x0,niter):
    if(tol >= 0):
        if (ite > 0):
            fx0 = f.evalf().subs({x:x0})
            if(fx0 != 0):
                fx1 = f.evalf().subs({x:x0}).evalf()
                funcF.append(fx1)
                error = tol+1
                errorTot.append(0)
                iters.append(0)
                xn.append(x0)
                cont = 0
                aux0 = x0
                while(fx1 != 0 and den != 0 and error > tol and cont < niter):
                    xn.append(aux1)
                    aux2 = aux1-f.evalf().subs({x:aux1}).evalf()*(aux1-aux0)/den
                    error = abs(aux2-aux1)
                    errorTot.append(error)
                    aux0 = aux1
                    fx0 = fx1
                    aux1 = aux2
                    fx1 = f.evalf().subs({x:aux1}).evalf()
                    funcF.append(fx1)
                    cont = cont + 1
                    iters.append(cont)
                if ( fx1 == 0):
                    print(str(aux1)+" Is a root")
                elif (error < tol):
                    print(str(aux1)+" Is an aproximate root")
                else:
                    print("The method failed")
                table.add_column("n",iters)
                table.add_column("xn",xn)
                table.add_column("f(xn)",funcF)
                table.add_column("error",errorTot)
                print(table)
            else:
                print("x0 Is a root",x0)
        else:
            print("Wrong iterates")
    else:
        print("Tolerance < 0")

if __name__ == "__main__":
    x = sympy.Symbol('x')
    symbols = {'e':math.e,'cos':sympy.cos,'sin':sympy.sin,'ln':sympy.ln}
    function  = input("Enter the function f(x) = ")
    function = sympy.sympify(function,locals = symbols)
    f = function
    fd1 = sympy.diff(f)
    fd2 = sympy.diff(fd1)
    x0 = float(input("Enter the x0 point: "))
    tol = float(input("Enter the tolerance: "))
    ite = int(input("Enter N iteraters: "))
    secant(tol,x0,ite)


import math
import numpy as np
from sklearn.decomposition import PCA

def ci95_ellipse(data, type="pop"):
    """ Construct a 95% confidence ellipse using PCA.

    Parameters
    ----------
    data : array-like, shape = [n_samples, 2]
        data[:,0] must represent x coordinates
        data[:,1] must represent y coordinates

    type : string, optional (default='pop')
        It must be 'pop' or 'mean'

    Returns
    -------
    ellipse: array-like, shape = [100, 2]
        ellipse[:,0] represents x coordinates of ellipse
        ellipse[:,1] represents y coordinates of ellipse

    outside: array-like, shape = [n_samples, 1]
        returns an 1d array (of 0/1) with length n_samples
        0 : ith sample is outside of ellipse
        1 : ith sample is inside of ellipse
    """

    # Build and fit PCA model
    pca = PCA()
    pca.fit(data)
    coeff = pca.components_
    score = pca.transform(data)
    eigvals = pca.explained_variance_

    # Calculate rotation angle
    phi = math.atan2(coeff[0, 1], coeff[0, 0])

    # This angle is between -pi and pi.
    # Let's shift it such that the angle is between 0 and 2pi
    if phi < 0:
        phi += 2 * math.pi

    # Get the coordinates of the data mean
    n = len(data)
    m = np.mean(data, axis=0)
    x0 = m[0]
    y0 = m[1]

    # Get the 95% confidence interval error ellipse
    # inverse of the chi-square cumulative distribution for  p = 0.05 & 2 d.f. = 5.9915
    chisquare_val = 5.9915
    if type is "pop":
        a = math.sqrt(chisquare_val * eigvals[0])
        b = math.sqrt(chisquare_val * eigvals[1])
    elif type is "mean":
        a = math.sqrt(chisquare_val * eigvals[0] / n)
        b = math.sqrt(chisquare_val * eigvals[1] / n)
    else:
        raise ValueError("type has to be 'pop' or 'mean'.")

    # the ellipse in x and y coordinates
    theta_grid = np.linspace(0, 2 * math.pi, num=100)
    ellipse_x_r = a * np.cos(theta_grid)
    ellipse_y_r = b * np.sin(theta_grid)

    # Define a rotation matrix
    R = np.array([[np.cos(phi), np.sin(phi)], [-np.sin(phi), np.cos(phi)]])
    # let's rotate the ellipse to some angle phi
    r_ellipse = np.dot(np.vstack((ellipse_x_r, ellipse_y_r)).T, R)

    # Draw the error ellipse
    x = r_ellipse[:, 0] + x0
    y = r_ellipse[:, 1] + y0
    ellipse = np.stack((x, y), axis=1)

    outside = []
    for i in range(len(score)):
        metric = (score[i, 0] / a) ** 2 + (score[i, 1] / b) ** 2
        if metric > 1:
            outside.append(1)
        else:
            outside.append(0)

    return ellipse, outside


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sun Jun 17 17:09:49 2018

@author: deepayanbhadra
"""
import numpy as np
def buildmat(m,n,condNumber):
    A = np.random.randn(m, n)
    np.linalg.cond(A)
    U, S, V = np.linalg.svd(A)
    S = np.array([[S[j] if i==j else 0 for j in range(n)] for i in range(m)])
    S[S!=0]= np.linspace(condNumber,1,min(m,n))
    A=U.dot(S).dot(V)
    return A
# For a 3x5 matrix

m,n,condNumber = 3,5,2
print("The 3x5 matrix A and the condition no. are\n")
A = buildmat(m,n,condNumber)
print(np.matrix(A),"\n")
print(np.linalg.cond(A),"\n")

# For a 5x4 matrix

m,n,condNumber = 5,4,4
print("The 5x4 matrix A and the condition no. are\n")
A = buildmat(m,n,condNumber)
print(np.matrix(A),"\n")
print(np.linalg.cond(A),"\n")


import numpy as np

M_LAPLACIEN_S = np.array([[-1,-1,-1],[-1,8,-1],[-1,-1,-1]])
M_LAPLACIEN = np.array([[0,1,0],[1,-4,1],[0,1,0]])
M_LAPLACIEN_DIAG = np.array([[1,1,1],[1,-8,1],[1,1,1]])
M_LAPLACIEN_OPP = -M_LAPLACIEN
M_LAPLACIEN_DIAG_OPP = -M_LAPLACIEN_DIAG
M_SOBEL_1 = np.array([[-1,-2,-1],[0,0,0],[1,2,1]])
M_SOBEL_2 = np.array([[-1,0,1],[-2,0,2],[-1,0,1]])

def D(u, v, M, N):
    return np.sqrt( np.power(u - (M/2), 2) + np.power(v - (N/2), 2) )

def M_masque_moyenneur(n):
    return np.ones((n,n))/9

def H_passe_bas_ideal(M, N, D0 = 1):
    H = np.zeros((M,N))
    for u in range(M):
        for v in range(N):
            H[u,v] = 1 if D(u, v, M, N) <= D0 else 0
    return H

def H_passe_bas_butterworth(M, N, D0 = 1, n = 1):
    H = np.zeros((M,N))
    for u in range(M):
        for v in range(N):
            H[u,v] = 1 / (1 + np.power(D(u, v, M, N) / D0, 2*n))
    return H

def H_passe_bas_gaussien(M, N, D0 = 1):
    H = np.zeros((M,N))
    for u in range(M):
        for v in range(N):
            H[u,v] = np.exp(-(D(u, v, M, N)**2) / (2 * (D0**2)))
    return H

def H_filtre_laplacien(M, N):
    H = np.zeros((M,N))
    for u in range(M):
        for v in range(N):
            H[u,v] = -((u - (M/2))**2 + (v - (N/2))**2)
    return H

def get_filtre_rehaussement(H, a, b, M, N, *args, **kwargs):
    if a < 0:
        raise Exception ("a compris généralement entre 0.25 et 0.5")
    if b < a:
        raise Exception(" b has to be greater than a")
    if a < 0.25 or a > 0.5:
        print("Attention ! a est compris généralement entre 0.25 et 0.5")
    if b < 1.5 or b > 2 :
        print("Attention ! b est compris généralement entre 1.5 et 2")
    return a + (b * (1-H(M, N,*args, **kwargs))) # 1 - H car mes H sont des passe bas


"""
bayesian analysis for population of marked items
Known total population: Nt We sample Nb, and find Nc of Nb marked
what is total population of marked items? based on same hypergeometric distribution as TagAndRelease
"""
import numpy as np
import matplotlib.pyplot as plt
from math import lgamma,exp
from SciInf_utilities import *
import sys
#-------------------------------
#
print("\n bayesian analysis for population of marked items")
print(" Known total population: Nt We sample Nb, and find Nc of Nb marked")
print(" what is total population of marked items? based on same hypergeometric distribution as TagAndRelease \n")
#
# test data
#n_tot = 15
#n_got = 10
#n_lab = 3
if(len(sys.argv) == 4):
  n_tot = int(sys.argv[1])
  n_got = int(sys.argv[2])
  n_lab = int(sys.argv[3])
else:
  n_tot = int(input('# total> '))
  n_got = int(input('# sampled> '))
  n_lab = int(input('# of samples that are marked> '))
print('from total of: {:8d}   # sampled: {:8d}   of which {:8d}   are marked'.format(n_tot,n_got,n_lab))
#
# range of n_lab to explore
#
n_min = n_lab
n_not = n_tot - n_got
n_max = n_lab + n_not
n_range = n_max + 1 - n_min
print('unsampled: {:8d}  # marked min: {:8d} and max: {:8d} '.format(n_not,n_min,n_max))
#
# generate pdf, cdf
#
n_axis = np.zeros(n_range,int)
n_pdf = np.zeros(n_range)
n = n_min
for i in range(n_range):
  n_axis[i] = n
  n1 = n + 1
  n2 = (n_tot - n) + 1
  n3 = (n - n_lab) + 1
  n4 = (n_max - n) + 1
  lpdf = lgamma(n1) + lgamma(n2) - lgamma(n3) - lgamma(n4)
  n_pdf[i] = lpdf
  n += 1
lpdf_max = max(n_pdf)
n_pdf = n_pdf - lpdf_max
n_pdf = np.exp(n_pdf)
#
n_cdf = pdf_to_cdf(n_axis,n_pdf,discrete=True)
f_lab = float(n_lab)/float(n_got)
n_mle = int(n_tot*f_lab)
print('fraction of sample marked: {:12.5f} Max. Like. Est of total Marked: {:6d}'.format(f_lab,n_mle))
summarize(n_axis,n_pdf,n_cdf,discrete=True,title='total # marked')

if(MAKEPLOT):
  plt.figure()
  plt.scatter(n_axis,n_pdf,color='green',marker='o')
  plt.scatter(n_axis,n_cdf,color='red',marker='s')
  plt.ylim((0.,1.2))
  plt.xlabel('N')
  plt.ylabel('prob(N)')
  #plt.title('posterior pdf,cdf for size of marked population')
  #plt.xscale('log')
  plt.grid(True)
  plt.show()


#!/usr/bin/python3

from sympy import symbols, cos, sin, pi, simplify
from sympy.matrices import Matrix
import numpy as np

### Create symbols for joint variables which are commonly represented by "q"
### Joint variable "q" is equal to "th" or "d" depending if the joint is revolute or prismatic

q1, q2, q3, q4 = symbols('q1:5') # remember slices do not include the end value

# unrelated symbols can be defined like this:
A, R, O, C = symbols('A R O C')

# Conversion Factors
rtd = 180./np.pi # radians to degrees
dtr = np.pi/180. # degrees to radians

R_x = Matrix([[ 1,              0,        0],
              [ 0,        cos(q1), -sin(q1)],
              [ 0,        sin(q1),  cos(q1)]])

R_y = Matrix([[ cos(q2),        0,  sin(q2)],
              [       0,        1,        0],
              [-sin(q2),        0,  cos(q2)]])

R_z = Matrix([[ cos(q3), -sin(q3),        0],
              [ sin(q3),  cos(q3),        0],
              [ 0,              0,        1]])

print("Rotation about the X-axis by 45-degrees")
print(R_x.evalf(subs={q1: 45*dtr}))
print("Rotation about the y-axis by 45-degrees")
print(R_y.evalf(subs={q2: 45*dtr}))
print("Rotation about the Z-axis by 30-degrees")
print(R_z.evalf(subs={q3: 30*dtr}))


from __future__ import division
import numpy as np
import scipy as sp
import matplotlib.pyplot as plt
import pandas as pd

def matmul(A, B):
    # multiply two 2-D square matrices together
    n = A.shape[0]
    U = np.array([[0] * n for i in range(n)], dtype=np.float64)
    for i in range(0, n):
        for j in range(0, n):
            U[i, j] = np.dot(A[i], B.T[j])
    return U

def rel_residual(A, x_cap, x, b):
    # returns 2-norm relative residual of a linear system
    res = np.linalg.norm(b - np.array([np.dot(A[i], x_cap) for i in range(n)]), ord=2)
    rel_res = res / (np.linalg.norm(A, ord=2) * np.linalg.norm(x, ord=2))
    return rel_res

def rel_error(x_cap, x):
    # returns 2-norm relative error
    return np.linalg.norm(x_cap - x, ord=2) / np.linalg.norm(x, ord=2)

n = 100
np.random.seed(1729)
A = np.random.randn(n, n)

x = np.ones(n)
b = np.array([np.dot(A[i], x) for i in range(n)])

DA = A
Db = b
x_cap = np.linalg.solve(DA, Db)
rel_err_1 = rel_error(x_cap, x)
rel_res_1 = rel_residual(DA, x_cap, x, Db)
cno_1 = np.linalg.cond(DA, p=2)

D = np.diag(2 * np.ones(n))
DA = matmul(D, A)
Db = np.array([np.dot(D[i], b) for i in range(n)])
x_cap = np.linalg.solve(DA, Db)
rel_err_2 = rel_error(x_cap, x)
rel_res_2 = rel_residual(DA, x_cap, x, Db)
cno_2 = np.linalg.cond(DA, p=2)

D = np.diag(np.linspace(1, 100, 100))
DA = matmul(D, A)
Db = np.array([np.dot(D[i], b) for i in range(n)])
x_cap = np.linalg.solve(DA, Db)
rel_err_3 = rel_error(x_cap, x)
rel_res_3 = rel_residual(DA, x_cap, x, Db)
cno_3 = np.linalg.cond(DA, p=2)

D = np.diag(np.linspace(1, 10000, 100))
DA = matmul(D, A)
Db = np.array([np.dot(D[i], b) for i in range(n)])
x_cap = np.linalg.solve(DA, Db)
rel_err_4 = rel_error(x_cap, x)
rel_res_4 = rel_residual(DA, x_cap, x, Db)
cno_4 = np.linalg.cond(DA, p=2)

D = np.diag(2**-np.arange(-n//2, n//2, dtype=np.float64))
DA = matmul(D, A)
Db = np.array([np.dot(D[i], b) for i in range(n)])
x_cap = np.linalg.solve(DA, Db)
rel_err_5 = rel_error(x_cap, x)
rel_res_5 = rel_residual(DA, x_cap, x, Db)
cno_5 = np.linalg.cond(DA, p=2)

data = pd.DataFrame({"(i)" : [rel_err_1, rel_res_1, cno_1], "(ii)" : [rel_err_2, rel_res_2, cno_2],\
              "(iii)" : [rel_err_3, rel_res_3, cno_3], "(iv)" : [rel_err_4, rel_res_4, cno_4],\
              "(v)" : [rel_err_5, rel_res_5, cno_5]}, index=["Relative Error", "Relative Residual",\
                                                             "Condition Number"]).T
data.index.name = "Part"
data.columns.name = "Statistic"

print(data.to_string())

# print(data.to_latex())


import numpy as np
import matplotlib.pyplot as plt

def create_2d_data_points():
    x = np.linspace(-2, 2, 100)
    y = x**5 - 6 * x**3 + 6 * x + 4 * np.cos(x*6) + 8
    return x, y

def print_poly_cobf_formula(x, y, degree):
    degree += 1
    x_extended = add_column_with_ones(x)

    for i in range(2, degree):
        x_extended = np.c_[x_extended, x**i]

    x_trans = x_extended.transpose()
    w = np.linalg.solve(x_trans.dot(x_extended), x_trans.dot(y))

    degree = len(w)
    print(f"\nCurve of best fit with degree {degree-1}:\ny = ", end = "")

    for i in range(degree):
        print(f"{w[i]:.3f}", end = "")
        if i >= 1:
            print("x", end = "")
        if i >= 2:
            print(f"^{i}", end = "")
        if i < degree - 1:
                print(" + ", end = "")
    print()

def add_column_with_ones(x):
    if len(x.shape) == 1:
        x = np.reshape(x, (x.shape[0], 1))
    return np.hstack((np.ones((x.shape[0], 1)).astype(int), x))

def poly_curve_of_best_fit(x, y, degree):
    degree += 1
    x_extended = add_column_with_ones(x)

    for i in range(2, degree):
        x_extended = np.c_[x_extended, x**i]

    x_trans = x_extended.transpose()
    w = np.linalg.solve(x_trans.dot(x_extended), x_trans.dot(y))

    return sum([w[i]*x_extended[:,i] for i in range(x_extended.shape[1])])

def mae(yhat, y):
    return (np.absolute(yhat - y)).mean()

def rmse(yhat, y):
    diff = yhat - y
    return diff.dot(diff)**0.5

def r_squared(yhat, y):
    diff_hat = yhat - y
    diff_bar = y.mean() - y
    return 1 - (diff_hat.dot(diff_hat) / diff_bar.dot(diff_bar))

def r_squared_adj(yhat, y, degree):
    n = 100
    p = degree
    return 1 - ((1 - r_squared(yhat, y)) * (n - 1)) / (n - p - 1)

def plot_metric(x, y, method):
    values = np.empty((14,))

    for degree in range(1, 15):
        cobf = poly_curve_of_best_fit(x, y, degree)
        if method == "MAE":
            values[degree-1] = (mae(cobf, y))
        elif method == "RMSE":
            values[degree-1] = (rmse(cobf, y))
        elif method == "R squared":
            values[degree-1] = (r_squared(cobf, y))
        elif method == "Adjusted R squared":
            values[degree-1] = (r_squared_adj(cobf, y, degree))

    plt.title(f"Method: {method}")
    plt.plot(np.linspace(1, 14, 14), values)
    plt.show()

def plot_all_metrics(x, y):
    values = [np.empty((14,)) for _ in range(4)]
    label_text = ["MAE", "RMSE", "R2", "Adjusted R2"]

    for degree in range(1, 15):
        cobf = poly_curve_of_best_fit(x, y, degree)
        values[0][degree-1] = (mae(cobf, y))
        values[1][degree-1] = (rmse(cobf, y))
        values[2][degree-1] = (r_squared(cobf, y))
        values[3][degree-1] = (r_squared_adj(cobf, y, degree))

    for i in range(2):
        values[i] -= 2 * (values[i] - values[i].mean())
        values[i] /= max(values[i])

    for i in range(4):
        plt.plot(np.linspace(1, 14, 14), values[i], label=label_text[i])

    plt.legend()
    plt.show()

def plot_data_points(x, y):
    plt.plot(x, y)
    plt.ylim((-4, 20))
    plt.show()

def plot_all_curves(x, y):
    curves = [poly_curve_of_best_fit(x, y, degree) for degree in range(1, 15)]
    plt.plot(x, y)
    for cobf in curves:
        plt.plot(x, cobf)
    plt.ylim((-4, 20))
    plt.show()

def plot_curve_with_metrics(x, y, degree):
    cobf = poly_curve_of_best_fit(x, y, degree)
    plt.text(1.07, 15, "MAE:\nRMSE:\nR2:\nR2-adj:              ", bbox={'facecolor': 'grey', 'alpha': 0.1, 'pad': 5})
    plt.text(1.53, 15, calc_metrics(cobf, y, degree))
    plt.text(-0.75, 20.5, f"Polynomial with degree {degree}")
    plt.plot(x, y)
    plt.plot(x, cobf)
    plt.ylim((-4, 20))
    plt.show()

def calc_metrics(cobf, y, degree):
    text = f"{mae(cobf, y):.5}\n{rmse(cobf, y):.5}\n{r_squared(cobf, y):.5}\n{r_squared_adj(cobf, y, degree):.5}"
    return text

def print_metrics(x, y):
    for degree in range(1, 15):
        print("*********************************")
        print(f"Calculating model with degree {degree}.\n")
        cobf = poly_curve_of_best_fit(x, y, degree)

        print(f"MAE:\t{mae(cobf, y):.5}")
        print(f"RMSE:\t{rmse(cobf, y):.5}")
        print(f"R2:\t{r_squared(cobf, y):.5}")
        print(f"R2-adj:\t{r_squared_adj(cobf, y, degree):.5}\n")

x, y = create_2d_data_points()

print_metrics(x, y)

plot_data_points(x, y)
plot_all_curves(x, y)
for degree in range(1, 15):
    plot_curve_with_metrics(x, y, degree)

print_poly_cobf_formula(x, y, 6)
print_poly_cobf_formula(x, y, 7)

plot_metric(x, y, "MAE")
plot_metric(x, y, "RMSE")
plot_metric(x, y, "R squared")
plot_metric(x, y, "Adjusted R squared")

plot_all_metrics(x, y)


import numpy as np

# This function maps an index in the range [0, count*(count - 1) // 2]
# to a tuple of integers in the range [0,count). The mapping will cover
# all pairs if you use all indices between [0, count*(count - 1) // 2].
def pair_to_index(p1, p2):
    if (p1 < p2): p1, p2 = p2, p1
    return (p1 * (p1 - 1) // 2) + p2

# This function maps an index in the range [0, count*(count - 1) // 2]
# to a tuple of integers in the range [0,count). The mapping will cover
# all pairs if you use all indices between [0, count*(count - 1) // 2].
def index_to_pair(index):
    val = int(((1/4 + 2*index)**(1/2) + 1/2))
    remainder = index - val*(val - 1)//2
    return (val, remainder)

# Compute the number of original elements when given the number of pairs.
# This is done by realizing the following:
#
#      n(n-1) / 2 = f(n)
#   (n^2 - n) / 2 = f(n)
#         n^2 - n = 2 f(n)
#   n^2 - n + 1/4 = 2 f(n) + 1/4
#     (n - 1/2)^2 = 2 f(n) + 1/4
#         n - 1/2 = (2 f(n) + 1/4)^{1/2}
#               n = (2 f(n) + 1/4)^{1/2} + 1/2
#              2n = (8 f(n) + 1)^{1/2} + 1
#               n = ((8 f(n) + 1)^{1/2} + 1) / 2
#
def num_from_pairs(num_pairs):
    return int(round( ((8*num_pairs + 1)**(1/2) + 1) / 2 ))

# Define a custom error for passing arrays of the wrong shape to pairwise.
class ShapeError(Exception): pass

# Compute the distance between all pairs of elements in a single list,
# or the distance of all combined pairs between two lists.
def pairwise_distance(x1, x2=None):
    # Define an error for having the wrong array shape.
    if (len(x1.shape) != 2): raise(ShapeError("Only 2D NumPy arrays are allowed."))
    # Determine use case.
    if (type(x2) == type(None)):
        # Compute the pairwise distances.
        x1_sq = np.sum(x1**2, axis=1, keepdims=True)
        d = (x1_sq + x1_sq[:,0]) - 2 * np.matmul(x1, x1.T)
        # Protect against the errors thta will occur along the diagonal.
        d[np.diag_indices(len(d))] = 1.0
        d[:,:] = np.sqrt(d[:,:])
        d[np.diag_indices(len(d))] = 0.0
        return d
    else:
        if (len(x2.shape) != 2): raise(ShapeError("Only 2D NumPy arrays are allowed."))
        # Compute the pairwise distance between memebers of each set.
        x1_sq = np.sum(x1**2, axis=1, keepdims=True)
        x2_sq = np.sum(x2**2, axis=1)
        return np.sqrt(x1_sq + x2_sq - 2 * np.matmul(x1, x2.T))

# ============================================================
#                          Test Cases
# ============================================================

def _test_pairwise_distance(display=False):
    x = np.random.random((100,30))
    y = np.random.random((10,30))

    # Verify that the pairwise distance (between points in two sets) works.
    dists = pairwise_distance(x, y)
    for i in range(len(x)):
        for j in range(len(y)):
            truth = np.linalg.norm(x[i] - y[j])
            assert(abs(dists[i,j] - truth) < 2**(-32))

    # Verify that the pairwise distance (between points in one set) works.
    dists = pairwise_distance(x)
    for i in range(len(x)):
        for j in range(i+1, len(x)):
            ij = dists[i,j]
            ji = dists[j,i]
            truth = np.linalg.norm(x[i] - x[j])
            assert(ij == ji)
            assert(abs(ij - truth) < 2**(-32))

    if display:
        from scipy.spatial.distance import pdist, cdist
        from util.system import Timer

        x = np.random.random((500,4000))
        y = np.random.random((5000,4000))

        t = Timer()
        d = pdist(x)
        t.stop()
        print("scipy  ", d.shape, t.total)

        t = Timer()
        d = pairwise_distance(x)
        t.stop()
        print("mine   ", d.shape, t.total)

        t = Timer()
        x2 = np.sum(x**2, axis=1, keepdims=True)
        d = np.sqrt((x2 + x2[:,0]) - 2 * np.matmul(x, x.T))
        t.stop()
        print("numpy  ", d.shape, t.total)

        print()

        t = Timer()
        d = cdist(x,y)
        t.stop()
        print("scipy  ", d.shape, t.total)

        t = Timer()
        d = pairwise_distance(x, y)
        t.stop()
        print("mine   ", d.shape, t.total)

        t = Timer()
        x2 = np.sum(x**2, axis=1, keepdims=True)
        y2 = np.sum(y**2, axis=1)
        p = 2 * np.matmul(x, y.T)
        d = np.sqrt(x2 + y2 - p)
        t.stop()
        print("numpy  ", d.shape, t.total)

if __name__ == "__main__":
    _test_pairwise_distance()


#!/usr/bin/env python
# coding: utf-8

# In[1]:

import numpy as np
import sympy
import matplotlib.pyplot as plt
from scipy import integrate
from scipy.special import legendre
from numpy.polynomial.legendre import Legendre
import matplotlib.ticker as mtick
import itertools

# Legendre polynomial
def leg(n, x):
    return Legendre(np.concatenate((np.zeros(n), np.array([1]))))(x)

def hbasis(i,x):
#  Evaluates the function Ni at x
    if i==0:
        Ni=0.5 *(1-x)
    elif i==1:
        Ni=0.5 *(1+x)
    else:
        Ni=(np.sqrt(1/(4*(i+1)-6)))*(leg(i,x)-leg(i-2,x))
    return Ni

def stifness_matrix(p):
#evaluates the elemental stifness matrix of size (p+1)x(p+1)
    K=np.zeros((p+1,p+1))
    K[0,0]=K[1,1]=0.5
    K[0,1]=K[1,0]=-0.5
    if p>=1:
        for i in range(2,p+1):
            K[i,i]=1
    return K

def stifness_matrix(p):
#evaluates the elemental stifness matrix of size (p+1)x(p+1)
    K=np.zeros((p+1,p+1))
    K[0,0]=K[1,1]=0.5
    K[0,1]=K[1,0]=-0.5
    if p>=1:
        for i in range(2,p+1):
            K[i,i]=1
    return K

def mass_matrix(p):
# Evaluates the elemental mass matrix of size (p+1)x(p+1)
    G=np.zeros((p+1,p+1))
    G[0,0]=G[1,1]=2/3
    G[0,1]=G[1,0]=1/3
    if p>=2:
        G[0,2]=G[1,2]=G[2,0]=G[2,1]=-1/np.sqrt(6)
        for i in range(2,p+1):
            G[i,i]=2/((2*(i+1)-1)*((2*(i+1)-5)))
    if p>=3:
        G[0,3]=G[3,0]=1/3*np.sqrt(10)
        G[1,3]=G[3,1]=-1/3*np.sqrt(10)
        for i in range(2,p+1):
            if i+2<p+1:
                G[i,i+2]=G[i+2,i]=(-1)/(((2*(i+1)-1)*np.sqrt(((2*(i+1)-3)* \
                ((2*(i+1)+1))))))
    return G

def load_vector(x_k,x_kk,p_k,f):
#Evaluates the elemental load vector
    vals=[]
    for i in range(p_k+1):
        g = lambda t : f((1-t)*x_k/2+(1+t)*x_kk/2)*hbasis(i,t)
        vals.append(integrate.quad(g,-1,1)[0])
    b=np.array(vals)
    return b

def el_stiff(x_k,x_kk,p_k):
    h_k=x_kk-x_k
    Kk=(2/h_k)*stifness_matrix(p_k)
    return Kk

def el_mass(x_k,x_kk,p_k):
    h_k=x_kk-x_k
    Gk=(h_k/2)*mass_matrix(p_k)
    return Gk

def pointer(M,p):
# [P] = pointer(M,p)
#
# Calculates the pointer matrix P, such that
# P(i,j)=k means that over the ith element
# the jth local basis function corresponds
# to the kth global basis function.
#
# M is the number of elements
# p is the degree vector of size M
# P is M by (max(p)+1)
#
    pmax=max(p)
    P=np.zeros((M,pmax+1))
    P=P.astype(int)
    for i in range(M):
        P[i,0] = i
        P[i,1] = i+1
    P[M-1,1]=0
    for i in range(M):
        for j in range(2,1+p[i]):
            P[i,j] = M
            M=M+1
    return P

def global_matrix(x,p):
    #Evaluates the elemental matrices, stiffnes(Kk) and mass(Gk) and then
    #the global matrix (G+K)
    P=pointer(len(x)-1,p)
    K=np.zeros((sum(p)-1,sum(p)-1))
    G=np.zeros((sum(p)-1,sum(p)-1))
    for k in range(len(x)-1):
        Kk=np.zeros((p[k]+1,p[k]+1))
        Gk=np.zeros((p[k]+1,p[k]+1))
        Kk=el_stiff(x[k],x[k+1],p[k])
        Gk=el_mass(x[k],x[k+1],p[k])
        for i in range(p[k]+1):
            l=P[k,i]
            for j in range(p[k]+1):
                m=P[k,j]
                if l!=0 and m!=0:
                    K[l-1,m-1]=K[l-1,m-1]+Kk[i,j]
                    G[l-1,m-1]=G[l-1,m-1]+Gk[i,j]
    GL=K+G
    return GL

def el_load(x_k,x_kk,p_k,f):
#evaluates the elemental load vector
    h_k=x_kk-x_k
    Fk=(h_k/2)*load_vector(x_k,x_kk,p_k,f)
    return Fk

def global_load_vector(x,p,f):
#Evaluates the global load vector
    P=pointer(len(x)-1,p)
    F=np.zeros((sum(p)-1))
    for k in range(len(x)-1):
        Fk=el_load(x[k],x[k+1],p[k],f)
        for i in range(p[k]+1):
            l=P[k,i]
            if l!=0:
                F[l-1]=F[l-1]+Fk[i]
    return F

def fem_solution(x,p,f):
# Evaluates the finite element method solution and the coifficients that
#generate the solution
    A=global_matrix(x,p)
    b=global_load_vector(x,p,f)
    a=np.linalg.solve(A,b)
    return a

def energy_norm(x,p,f,n):
# Evaluates the energy norm of the finite element method solution and it finds
# the percentage of the error
    a=fem_solution(x,p,f)
    DOF=len(a)
    enorm = np.dot(a, np.array(global_load_vector(x,p,f)))
    y = lambda z : (z**n-z)*f(z)
    enorm_uex=integrate.quad(y,0,1)[0]
    relE=100*np.sqrt(abs(enorm-enorm_uex)/abs(enorm_uex))
    return enorm,relE,DOF

def pairwise(iterable):
    a, b = itertools.tee(iterable)
    next(b, None)
    return zip(a,b)

def solid1d(y,x,M,p,f):
#Evaluates the finite element method solution for every y that belongs to
#[a,b] interval
    x_intervals = list(pairwise(x))
    k = [0]
    for point in y[1:]:
        for i, interval in enumerate(x_intervals[k[-1]:]):
            if point>=interval[0] and point<interval[1]:
                k.append(k[-1]+i)
                break
    k.append(len(x_intervals)-1)
    ksi=[]
    for k_val, y_i in zip(k,y):
        ksi.append((2*y_i-x_intervals[k_val][0]-x_intervals[k_val][1])/ \
                   (x_intervals[k_val][1]-x_intervals[k_val][0]))
    P=pointer(M,p)
    ufe=[]
    c=fem_solution(x,p,f)
    for k_val, ksi_i in zip(k,ksi):
        proxy=[]
        for i in range(1,p[k_val]+2):
            l=P[k_val,i-1]
            if l!=0:
                proxy.append(c[l-1]*hbasis(i-1,ksi_i))
            else:
                proxy.append(0)
        ufe.append(sum(proxy))
    return ufe

def h_unif(M,pmax,a,b):
#It creates a list of the uniform mesh and a list with the degrees of the
#polynomials which are constant.
    x=[]
    p=[]
    h=(b-a)/M
    for i in range(M+1):
        z=a+i*h
        x.append(z)
    for j in range(M):
        p.append(pmax)
    return x,p

def h_radical(M,pmax,s,a,b):
#It creates a list of the root-s mesh and a list with the degrees of the
#polynomials which are constant.
    p=[]
    x=[]
    for i in range(M+1):
        z=a+(b-a)*((i/M)**s)
        x.append(z)
    for j in range(M):
        p.append(pmax)
    return x,p

def p_unif(M,pmax,a,b):
#It creates a list of the uniform mesh which is constant and a list with
#the degrees of the polynomials that goes 1:pmax.
    p=[]
    x=[]
    h=(b-a)/M
    for i in range(M+1):
        z=a+i*h
        x.append(z)
    for j in range(1,pmax+1):
        p.append(j)
    return x,p

def p_geom(M,pmax,q,a,b):
#It creates a list of the geometric-q mesh which is constant and a list with
# the degrees of the polynomials that goes 1:pmax.
    p=[]
    x=[a]
    for i in range(2,M+2):
        z=a+(b-a)*(q**(M-i+1))
        x.append(z)
    for j in range(1,pmax+1):
        p.append(j)
    return x,p

def hp_geom(M,q,a,b):
#It creates a list of the geometric-q mesh and a list with the degrees of the
# polynomials that goes 1:M, M:=number of elements.
    x=[a]
    p=[]
    pmax=M
    for i in range(2,M+2):
        z=a+(b-a)*(q**(M-i+1))
        x.append(z)
    for j in range(1,pmax+1):
        p.append(j)
    return x,p

# In[2]:

#f(x)=x**n-x
#n=7.1,a=0,b=1
#Graphs the error against DOF  in logaritmic axes
#First it uses h fem with uniform mesh for polynomial degrees 1 and then 2
a=0
b=1
n=7.1
N=5
f = lambda x : (x**n)-x-n*(n-1)*(x**(n-2))
fig, axes = plt.subplots(1,4, figsize=(15,7))
fig.tight_layout()
fig.subplots_adjust(wspace=0.3)
for j in range(1,3):
    pmax=j
    errors=[]
    DOF=[]
    for i in range(1,N+1):
        M=2**i
        x=h_unif(M,pmax,a,b)[0]
        p=h_unif(M,pmax,a,b)[1]
        errors.append(energy_norm(x,p,f,n)[1])
        DOF.append(energy_norm(x,p,f,n)[2])
    axes[j-1].loglog(DOF,errors)
    axes[j-1].set_xlabel("Degrees of freedom")
    axes[j-1].set_ylabel("Error in energy norm(%)")
    axes[j-1].set_title(f"Error for l=7.1, h_unif with N=5,p={j}")
    print(f"the slope is:",(np.log(errors[-1])-np.log(errors[-2]))/ \
          (np.log(DOF[-1])-np.log(DOF[-2])))

#Second it uses p fem with uniform mesh with 1 element and then two elements
#for polynomial degrees 1,....,6
P_uni_list=[1,4]
for j,Mp in enumerate(P_uni_list):
    pmax=6
    errors=[]
    DOF=[]
    x=p_unif(Mp,pmax,a,b)[0]
    p=p_unif(Mp,pmax,a,b)[1]
    for i in p:
        p_list=[]
        for k in range(Mp):
            p_list.append(i)
        errors.append(energy_norm(x,p_list,f,n)[1])
        DOF.append(energy_norm(x,p_list,f,n)[2])
    axes[j+2].loglog(DOF,errors)
    axes[j+2].set_xlabel("Degrees of freedom")
    axes[j+2].set_ylabel("Error in energy norm(%)")
    axes[j+2].set_title(f"Error for l=7.1,p_unif with M={Mp},pmax=6")
    print(f"the slope is:",(np.log(errors[-4])-np.log(errors[-5]))/ \
          (np.log(DOF[-4])-np.log(DOF[-5])))

# In[3]:

#f(x)=x**n-x
#n=2.1,a=0,b=1
#Graphs the error against DOF  in logaritmic axes
#First it uses h fem with uniform mesh for polynomial degrees 1 and then 2
a=0
b=1
n=2.1
N=5
f = lambda x : (x**n)-x-n*(n-1)*(x**(n-2))
fig, axes2 = plt.subplots(1,3, figsize=(15,7))
fig.tight_layout()
fig.subplots_adjust(wspace=0.3)
for j in range(1,3):
    pmax=j
    errors=[]
    DOF=[]
    for i in range(1,N+1):
        M=2**i
        x=h_unif(M,pmax,a,b)[0]
        p=h_unif(M,pmax,a,b)[1]
        errors.append(energy_norm(x,p,f,n)[1])
        DOF.append(energy_norm(x,p,f,n)[2])
    axes2[j-1].loglog(DOF,errors)
    axes2[j-1].set_xlabel("Degrees if freedom")
    axes2[j-1].set_ylabel("Error in energy norm(%)")
    axes2[j-1].set_title(f"Error for l=2.1, h_unif with N=5,p={j}")
    print(f"the slope is:",(np.log(errors[-1])-np.log(errors[-2]))/ \
          (np.log(DOF[-1])-np.log(DOF[-2])))
#Second it uses p fem with uniform mesh with 1 element and then two elements
#for polynomial degrees 1,....,6
M=1
pmax=6
errors=[]
DOF=[]
x=p_unif(M,pmax,a,b)[0]
p=p_unif(M,pmax,a,b)[1]
for i in p:
    p_list=[]
    for k in range(M):
        p_list.append(i)
    errors.append(energy_norm(x,p_list,f,n)[1])
    DOF.append(energy_norm(x,p_list,f,n)[2])
axes2[2].loglog(DOF,errors)
axes2[2].set_xlabel("Degrees of freedom")
axes2[2].set_ylabel("Error in energy norm(%)")
axes2[2].set_title(f"Error for l=2.1,p_unif with M={M},pmax=6")
print(f"the slope is:",(np.log(errors[-1])-np.log(errors[-2]))/ \
      (np.log(DOF[-1])-np.log(DOF[-2])))

# In[4]:

#f(x)=x**n-x
#n=2.1,a=0,b=1
#Graphs the error against DOF  in logaritmic axes
a=0
b=1
n=2.1
f = lambda x : (x**n)-x-n*(n-1)*(x**(n-2))
fig, axes3 = plt.subplots(1,4, figsize=(15,7))
fig.tight_layout()
fig.subplots_adjust(wspace=0.3)
j=0

#It uses h radical fem for N=5, s=0.15 for polynomial degrees 2
N=5
s=0.15
pmax=2
errors=[]
DOF=[]
for i in range(1,N+1):
    M=2**i
    x=h_radical(M,pmax,s,a,b)[0]
    p=h_radical(M,pmax,s,a,b)[1]
    errors.append(energy_norm(x,p,f,n)[1])
    DOF.append(energy_norm(x,p,f,n)[2])
axes3[j].loglog(DOF,errors)
axes3[j].set_xlabel("Degrees of freedom")
axes3[j].set_ylabel("Error in energy norm(%)")
axes3[j].set_title(f"Error for l=2.1,h-radical with N=5,s=0.15,p=2")
print(f"the slope is:",(np.log(errors[-1])-np.log(errors[-2]))/ \
      (np.log(DOF[-1])-np.log(DOF[-2])))
#It uses p fem with geometric mesh for M=4, q=0.15 for polynomial degrees
#p=1,....,6
M=4
q=0.15
pmax=6
errors=[]
DOF=[]
x=p_geom(M,pmax,q,a,b)[0]
p=p_geom(M,pmax,q,a,b)[1]
for i in p:
    p_list=[]
    for k in range(M):
        p_list.append(i)
    errors.append(energy_norm(x,p_list,f,n)[1])
    DOF.append(energy_norm(x,p_list,f,n)[2])
axes3[j+1].loglog(DOF,errors)
axes3[j+1].set_xlabel("Degrees of freedom")
axes3[j+1].set_ylabel("Error in energy norm(%)")
axes3[j+1].set_title(f"Error for l=2.1,p-geom with M=4,q=0.15,p=6")
print(f"the slope is:",(np.log(errors[-5])-np.log(errors[-6]))/ \
      (np.log(DOF[-5])-np.log(DOF[-6])))
#It uses p fem with geometric mesh for M=4, q=0.15 for polynomial degrees 1,2
M=4
q=0.15
pmax=2
errors=[]
DOF=[]
x=p_geom(M,pmax,q,a,b)[0]
p=p_geom(M,pmax,q,a,b)[1]
for i in p:
    p_list=[]
    for k in range(M):
        p_list.append(i)
    errors.append(energy_norm(x,p_list,f,n)[1])
    DOF.append(energy_norm(x,p_list,f,n)[2])
axes3[j+2].loglog(DOF,errors)
axes3[j+2].set_xlabel("Degrees of freedom")
axes3[j+2].set_ylabel("Error in energy norm(%)")
axes3[j+2].set_title(f"Error for l=2.1,p-geom with M=4,q=0.15,p=2")
print(f"the slope is:",(np.log(errors[-1])-np.log(errors[-2]))/ \
      (np.log(DOF[-1])-np.log(DOF[-2])))

#It uses hp fem with geometric mesh for N=5, q=0.15 for polynomial degrees p=M
N=5
q=0.15
errors=[]
DOF=[]
for i in range(1,N+1):
    M=2**i
    x=hp_geom(M,q,a,b)[0]
    p=hp_geom(M,q,a,b)[1]
    for z in p:
        plist=[]
        for k in range(M):
            plist.append(z)
        errors.append(energy_norm(x,plist,f,n)[1])
        DOF.append(energy_norm(x,plist,f,n)[2])
axes3[j+3].semilogy(DOF,errors)
axes3[j+3].set_xlabel("Degrees of freedom")
axes3[j+3].set_ylabel("Error in energy norm(%)")
axes3[j+3].set_title(f"Error for l=2.1,hp-geom with N=6,q=0.15")
print(f"the slope is:",(np.log(errors[-4])-np.log(errors[-5]))/ \
      (np.log(DOF[-4])-np.log(DOF[-5])))

# In[5]:

import warnings
warnings.filterwarnings('ignore')

#n=2.1
#a=0,b=1
#Graphs the absolute error between the finite element solution(uFE)and the
#exact solution (uEX)

a = 0
b = 1
n = 2.1
M = 10
q = 0.15
f = lambda x : (x**n)-x-n*(n-1)*(x**(n-2))
y = np.linspace(0,1)
u_Ex_fun = lambda x : (x-x**n)

#First;y I use the h fem with uniform mesh for M=10,p=1
x=h_unif(M,1,a,b)[0]
p=h_unif(M,1,a,b)[1]
u_Fe = -np.array(solid1d(y,x,M,p,f))
u_Ex_values = np.array([u_Ex_fun(val) for val in y])
fig , axes4 =plt.subplots(figsize=(10,10))
axes4.plot(y,abs(u_Ex_values-u_Fe)/abs(u_Ex_values),color='black')
axes4.set_xlabel("x")
axes4.set_ylabel("y")
axes4.set_title(r'Error ${|u_{FE}-u_{EX}|/|u_{EX}|}$')
#Graphs the uFE against uEX
fig , axes5 =plt.subplots(figsize=(10,10))
axes5.plot(y,u_Fe, color="red")
axes5.plot(y,u_Ex_values, color="blue")
axes5.set_xlabel("x")
axes5.set_ylabel("y")
axes5.set_title(r'Plots of $u_{FE}$ and $u_{EX}$')
axes5.legend([r'$u_{FE}$','$u_{EX}$'])

#Secondly I use the h fem with uniform mesh for M=1,p=6
x=h_unif(1,6,a,b)[0]
p=h_unif(1,6,a,b)[1]
u_Fe = -np.array(solid1d(y,x,1,p,f))
u_Ex_values = np.array([u_Ex_fun(val) for val in y])
fig , axes6 =plt.subplots(figsize=(10,10))
axes6.plot(y,abs(u_Ex_values-u_Fe)/abs(u_Ex_values),color='black')
axes6.set_xlabel("x")
axes6.set_ylabel("y")
axes6.set_title(r'Error ${|u_{FE}-u_{EX}|/|u_{EX}|}$')
#Graphs the uFE against uEX
fig , axes7 =plt.subplots(figsize=(10,10))
axes7.plot(y,u_Fe, color="red")
axes7.plot(y,u_Ex_values, color="blue")
axes7.set_xlabel("x")
axes7.set_ylabel("y")
axes7.set_title(r'Plots of $u_{FE}$ and $u_{EX}$')
axes7.legend([r'$u_{FE}$','$u_{EX}$'])

#Thirdly I use the p fem with geometric mesh for M=10,p=2 for all elements
x=p_geom(M,2,q,a,b)[0]
p=[]
for r in range(M):
    p.append(2)
u_Fe = -np.array(solid1d(y,x,M,p,f))
u_Ex_values = np.array([u_Ex_fun(val) for val in y])
fig , axes8 =plt.subplots(figsize=(10,10))
axes8.plot(y,abs(u_Ex_values-u_Fe)/abs(u_Ex_values),color='black')
axes8.set_xlabel("x")
axes8.set_ylabel("y")
axes8.set_title(r'Error ${|u_{FE}-u_{EX}|/|u_{EX}|}$')
#Graphs the uFE against uEX
fig , axes9 =plt.subplots(figsize=(10,10))
axes9.plot(y,u_Fe, color="red")
axes9.plot(y,u_Ex_values, color="blue")
axes9.set_xlabel("x")
axes9.set_ylabel("y")
axes9.set_title(r'Plots of $u_{FE}$ and $u_{EX}$')
axes9.legend([r'$u_{FE}$','$u_{EX}$'])

#Fourthly I use the p fem with geometric mesh for M=10,p=4 for all elements
x=p_geom(M,4,q,a,b)[0]
p=[]
for r in range(M):
    p.append(4)
u_Fe = -np.array(solid1d(y,x,M,p,f))
u_Ex_values = np.array([u_Ex_fun(val) for val in y])
fig , axes10 =plt.subplots(figsize=(10,10))
axes10.plot(y,abs(u_Ex_values-u_Fe)/abs(u_Ex_values),color='black')
axes10.set_xlabel("x")
axes10.set_ylabel("y")
axes10.set_title(r'Error ${|u_{FE}-u_{EX}|/|u_{EX}|}$')
#Graphs the uFE against uEX
fig , axes11 =plt.subplots(figsize=(10,10))
axes11.plot(y,u_Fe, color="red")
axes11.plot(y,u_Ex_values, color="blue")
axes11.set_xlabel("x")
axes11.set_ylabel("y")
axes11.set_title(r'Plots of $u_{FE}$ and $u_{EX}$')
axes11.legend([r'$u_{FE}$','$u_{EX}$'])

import numpy,from numpy,from scipy,import sympy


import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib import animation
import numpy as np

fig, ax = plt.subplots(2, 2, figsize=(9, 8.5))

topend = 50
angle_list = []
int_angle = []
count = []
sidelengths = []
area_list = []

for x in range(3,topend):    #finds regular angle of an n-gon (where n = 3-topend)
    degrees = 180*(x-2)
    angle = degrees/x
    angle_list.append(angle)

    if angle.is_integer():    #checks if angle is an integer
        int_angle.append(angle)
        count.append(x)

    length = 2*np.cos(angle*np.pi/(180*2))
    height = np.sin(angle*np.pi/(180*2))
    sidelengths.append(length)
    area = 2*x*(1/2 * length/2 * height)
    area_list.append(area)

    if x == topend-1:    #approximates pi using the area of the largest n-gon
        print('pi is approx: ' + str(area))

#creates circle
circle = mpatches.Circle([0.5,0.5], 0.5,
                         ec="none",
                         facecolor = 'r',
                         alpha = 0.7
                         )
#creates polygon (triangle)
polygon = mpatches.RegularPolygon([0.5,0.5],3,0.5)

#Creates each frame for bottom right graph
def update_vector(i, polygon):
    ax[1,1].clear()
    polygon = mpatches.RegularPolygon([0.5,0.5],i+3,
                                      0.5,
                                      alpha=0.5
                                      )
    ax[1,1].add_patch(circle)
    ax[1,1].add_patch(polygon)

    return polygon,

#hides x,y axis ticks for bottom right graph
ax[1,1].axes.xaxis.set_visible(False)
ax[1,1].axes.yaxis.set_visible(False)

#animates bottom right frame
anim = animation.FuncAnimation(fig, update_vector,
                               fargs = (polygon,),
                               frames=20,    #sets max n-gon in animation
                               interval=650,
                               )

#creates each graph
ax[0,0].scatter(range(3,topend),angle_list)
ax[0,0].scatter(count,int_angle)
ax[0,0].set(xlabel='Number of Sides', ylabel= 'Degree of Angle')
ax[0,1].scatter(range(3,topend),area_list)
ax[0,1].set(xlabel='Number of Sides', ylabel= 'Area')
ax[1,0].scatter(range(3,topend),sidelengths)
ax[1,0].set(xlabel='Number of Sides', ylabel= 'Lenght of Side')
#sets title
fig.suptitle('Approaching the Area of a Circle', fontsize = 20)

plt.show()

# #**Uncomment to save file as mp4 or gif**
# import matplotlib
# matplotlib.use("Agg")
# # Set up formatting for the movie files
# Writer = animation.writers['ffmpeg']
# writer = Writer(fps=6, metadata=dict(artist='Me'), bitrate=1800)

# anim.save('app_circle_graphic.mp4', writer=writer)  #save as mp4, requires ffmpeg
# anim.save('app_circle_graphic.gif', writer='imagemagick', fps=6)  # save as gif requries, imagemagick and wand


import numpy as np

def two_f(A, a, b, c):
    import sys
    sys.stdout = open('2f.txt', 'w')

    def sat_equation(r, A, num_satallites=100):
        return A * num_satallites * (r / b) ** (a - 3) * np.exp(-(r / b) ** c)

    # Since we are integrating the sat_equation in 3D spherical integral, but only a dependence on r, integral
    # corresponds to sat_equation times dV, or the derivative of the volume of the sphere, so 4 * pi * r^2
    def three_d_integral(r, A, num_sats):
        volume_of_sphere = 4 * np.pi * r ** 2
        return volume_of_sphere * sat_equation(r, A, num_sats)

    def n(x):
        return three_d_integral(x, A, 100)

    def root_finder(bracket=[1e-8, 5], epsilon=0.001, max_iter=500, root_to_find=1 / 2):
        """
        First bracket the root, and see where the function changes sign -> that is the root

        Guaranteed to work (according to the slides), unlike secant method or others

        We are finding two roots, need to bracket on one side with bracket of [0,Nmax], other with [Nmax,5]

        Uses root_to_find by subtracting that value from the rest of the stuff, so that the root wanted is at 0

        :return:
        """

        for i in range(max_iter):
            mid_point = (bracket[0] + bracket[1]) / 2.
            # Now check to see which half the root is in
            # Is the midpoint the root?
            mid_value = n(mid_point) - root_to_find
            f_a = n(bracket[0]) - root_to_find
            if f_a * mid_value < 0:  # They have opposite signs
                bracket[1] = mid_point
            elif -1. * epsilon < mid_value < epsilon:  # This is the root within epsilon
                return mid_point
            else:
                # Other two have opposite signs
                bracket[0] = mid_point
            # Now check if it has converged, that is if the space between the brackets is within epsilon
        # If it gets to here, then no root found after the max iterations
        print("Root not found in {} iterations".format(max_iter))
        return (bracket[0] + bracket[1]) / 2.

    Nmax = n(np.arange(1e-4, 5, 0.001))
    max_index = list(Nmax).index(max(Nmax))
    max_val = np.arange(1e-4, 5, 0.001)[max_index]

    # There are two roots in this problem, as half the max happens on either side of the max
    # Starts at 1e-8 because of problems with divide by 0 otherwise
    lower_root = root_finder(bracket=[1e-8, max_val], root_to_find=n(max_val) / 2.)
    upper_root = root_finder(bracket=[max_val, 5], root_to_find=n(max_val) / 2.)
    print("Roots = {}, {}".format(lower_root, upper_root))


import math
import string
import sys

import numpy as np
from sympy import Matrix

def menu():
    while True:
        print("---- Hill Cipher ----\n")
        print("1) Encrypt a Message.")
        print("2) Decipher a Message.")
        print("3) Force a Ciphertext (Known Plaintext Attack).")
        print("4) Quit.\n")
        try:
            choice = int(input("Select a function to run: "))
            if 1 <= choice <= 4:
                return choice
            else:
                print("\nYou must enter a number from 1 to 4\n")
        except ValueError:
            print("\nYou must enter a number from 1 to 4\n")
        input("Press Enter to continue.\n")

# Create two dictionaries, english alphabet to numbers and numbers to english alphabet, and returns them
def get_alphabet():
    alphabet = {}
    for character in string.ascii_uppercase:
        alphabet[character] = string.ascii_uppercase.index(character)

    reverse_alphabet = {}
    for key, value in alphabet.items():
        reverse_alphabet[value] = key

    return alphabet, reverse_alphabet

# Get input from the user and checks if respects the alphabet
def get_text_input(message, alphabet):
    while True:
        text = input(message)
        text = text.upper()
        if all(keys in alphabet for keys in text):
            return text
        else:
            print("\nThe text must contain only characters from the english alphabet ([A to Z] or [a to z]).")

# Check if the key is a square in length
def is_square(key):
    key_length = len(key)
    if 2 <= key_length == int(math.sqrt(key_length)) ** 2:
        return True
    else:
        return False

# Create the matrix k for the key
def get_key_matrix(key, alphabet):
    k = list(key)
    m = int(math.sqrt(len(k)))
    for (i, character) in enumerate(k):
        k[i] = alphabet[character]

    return np.reshape(k, (m, m))

# Create the matrix of m-grams of a text, if needed, complete the last m-gram with the last letter of the alphabet
def get_text_matrix(text, m, alphabet):
    matrix = list(text)
    remainder = len(text) % m
    for (i, character) in enumerate(matrix):
        matrix[i] = alphabet[character]
    if remainder != 0:
        for i in range(m - remainder):
            matrix.append(25)

    return np.reshape(matrix, (int(len(matrix) / m), m)).transpose()

# Encrypt a Message and returns the ciphertext matrix
def encrypt(key, plaintext, alphabet):
    m = key.shape[0]
    m_grams = plaintext.shape[1]

    # Encrypt the plaintext with the key provided k, calculate matrix c of ciphertext
    ciphertext = np.zeros((m, m_grams)).astype(int)
    for i in range(m_grams):
        ciphertext[:, i] = np.reshape(np.dot(key, plaintext[:, i]) % len(alphabet), m)
    return ciphertext

# Transform a matrix to a text, according to the alphabet
def matrix_to_text(matrix, order, alphabet):
    if order == 't':
        text_array = np.ravel(matrix, order='F')
    else:
        text_array = np.ravel(matrix)
    text = ""
    for i in range(len(text_array)):
        text = text + alphabet[text_array[i]]
    return text

# Check if the key is invertible and in that case returns the inverse of the matrix
def get_inverse(matrix, alphabet):
    alphabet_len = len(alphabet)
    if math.gcd(int(round(np.linalg.det(matrix))), alphabet_len) == 1:
        matrix = Matrix(matrix)
        return np.matrix(matrix.inv_mod(alphabet_len))
    else:
        return None

# Decrypt a Message and returns the plaintext matrix
def decrypt(k_inverse, c, alphabet):
    return encrypt(k_inverse, c, alphabet)

def get_m():
    while True:
        try:
            m = int(input("Insert the length of the grams (m): "))
            if m >= 2:
                return m
            else:
                print("\nYou must enter a number m >= 2\n")
        except ValueError:
            print("\nYou must enter a number m >= 2\n")

# Force a Ciphertext (Known Plaintext Attack)
def plaintext_attack(c, p_inverse, alphabet):
    return encrypt(c, p_inverse, alphabet)

def main():
    while True:
        # Ask the user what function wants to run
        choice = menu()

        # Get two dictionaries, english alphabet to numbers and numbers to english alphabet
        alphabet, reverse_alphabet = get_alphabet()

        # Run the function selected by the user
        if choice == 1:
            # Asks the user the plaintext and the key for the encryption and checks the input
            plaintext = get_text_input("\nInsert the text to be encrypted: ", alphabet)
            key = get_text_input("Insert the key for encryption: ", alphabet)

            if is_square(key):
                # Get the key matrix k
                k = get_key_matrix(key, alphabet)
                print("\nKey Matrix:\n", k)

                # Get the m-grams matrix p of the plaintext
                p = get_text_matrix(plaintext, k.shape[0], alphabet)
                print("Plaintext Matrix:\n", p)

                input("\nPress Enter to begin te encryption.")
                # Encrypt the plaintext
                c = encrypt(k, p, alphabet)

                # Transform the ciphertext matrix to a text of the alphabet
                ciphertext = matrix_to_text(c, "t", reverse_alphabet)

                print("\nThe message has been encrypted.\n")
                print("Generated Ciphertext: ", ciphertext)
                print("Generated Ciphertext Matrix:\n", c, "\n")
            else:
                print("\nThe length of the key must be a square and >= 2.\n")

        elif choice == 2:
            # Asks the user the ciphertext and the key for the encryption and checks the input
            ciphertext = get_text_input("\nInsert the ciphertext to be decrypted: ", alphabet)
            key = get_text_input("Insert the key for decryption: ", alphabet)

            if is_square(key):
                # Get the key matrix k
                k = get_key_matrix(key, alphabet)

                # Check if the key is invertible and in that case returns the inverse of the matrix
                k_inverse = get_inverse(k, alphabet)

                if k_inverse is not None:
                    # Get the m-grams matrix c of the ciphertext
                    c = get_text_matrix(ciphertext, k_inverse.shape[0], alphabet)

                    print("\nKey Matrix:\n", k)
                    print("Ciphertext Matrix:\n", c)

                    input("\nPress Enter to begin the decryption.")

                    # Decrypt the ciphertext
                    p = decrypt(k_inverse, c, alphabet)

                    # Transform the ciphertext matrix to a text of the alphabet
                    plaintext = matrix_to_text(p, "t", reverse_alphabet)

                    print("\nThe message has been decrypted.\n")
                    print("Generated Plaintext: ", plaintext)
                    print("Generated Plaintext Matrix:\n", p, "\n")
                else:
                    print("\nThe matrix of the key provided is not invertible.\n")
            else:
                print("\nThe key must be a square and size >= 2.\n")

        elif choice == 3:
            # Asks the user the text and the ciphertext to use them for the plaintext attack
            plaintext = get_text_input("\nInsert the plaintext for the attack: ", alphabet)
            ciphertext = get_text_input("Insert the ciphertext of the plaintext for the attack: ", alphabet)

            # Asks the user the length of the grams
            m = get_m()

            if len(plaintext) / m >= m:
                # Get the m-grams matrix p of the plaintext and takes the firsts m
                p = get_text_matrix(plaintext, m, alphabet)
                p = p[:, 0:m]

                # Check if the matrix of the plaintext is invertible and in that case returns the inverse of the matrix
                p_inverse = get_inverse(p, alphabet)

                if p_inverse is not None:
                    # Get the m-grams matrix c of the ciphertext
                    c = get_text_matrix(ciphertext, m, alphabet)
                    c = c[:, 0:m]

                    if c.shape[1] == p.shape[0]:
                        print("\nCiphertext Matrix:\n", c)
                        print("Plaintext Matrix:\n", p)

                        input("\nPress Enter to begin the attack.")

                        # Force the ciphertext provided
                        k = plaintext_attack(c, p_inverse, alphabet)

                        # Transform the key matrix to a text of the alphabet
                        key = matrix_to_text(k, "k", reverse_alphabet)

                        print("\nThe key has been found.\n")
                        print("Generated Key: ", key)
                        print("Generated Key Matrix:\n", k, "\n")
                    else:
                        print("\nThe number of m-grams for plaintext and ciphertext are different.\n")
                else:
                    print("\nThe matrix of the plaintext provided is not invertible.\n")
            else:
                print("\nThe length of the plaintext must be compatible with the length of the grams (m).\n")
        elif choice == 4:
            sys.exit(0)
        input("Press Enter to continue.\n")

if __name__ == '__main__':
    main()


import numpy as np

def sor_solver(A, b, omega, initial_guess, convergence_criteria,T,k):
  count = 0
  phi = initial_guess[:]
  residual = np.linalg.norm(np.matmul(A, phi) - b) #Initial residual

  while residual > convergence_criteria and count<=k:
        for i in range(A.shape[0]):
          sigma = 0
          for j in range(A.shape[1]):
            if j != i:
              sigma += A[i][j] * phi[j]
              T[i][j] = sigma
          phi[i] = (1 - omega) * phi[i] + (omega / A[i][i]) * (b[i] - sigma)

        print('Iteración:{0}'.format(count))
        print(T)
        residual = np.linalg.norm(np.matmul(A, phi) - b)
        print('{0}'.format(phi))
        print('Error: {0:10.9g}\n'.format(residual))
        count+=1

  return phi

#An example case that mirrors the one in the Wikipedia article
residual_convergence = 10**-16
omega = 0.41#Relaxation factor

A = np.ones((3, 3))
A[0][0] = 1
A[0][1] = 3
A[0][2] = -1

A[1][0] = 4
A[1][1] = -1
A[1][2] = 1

A[2][0] = 1
A[2][1] = 1
A[2][2] = 7

b = np.ones(3)
b[0] = 18.0
b[1] = 27.34
b[2] = 16.2

initial_guess = np.zeros(3)
T = np.ones((3,3))
phi = sor_solver(A, b, omega, initial_guess, residual_convergence,T,1000)
print("Respuesta: ",phi)


import numpy as np

class LinearRegression:
    """
    A class that represents a Linear Regression Model

    Attributes
    ----------
    iterations : int
        number of training iterations
    learning_rate : float
        alpha multiplier for the strength of the gradient descent
    seed : int, list, etc. : Default - None
        random seed for reproducibility

    Methods
    -------
    train(X, y):
        Trains the model based on the data and labels

    predict(X):
        Predict the Labels for each provided X input data
    """
    def __init__(self, iterations=1000, learning_rate=0.01, seed=None):
        if seed is not None:
            np.random.seed(seed=seed)

        self.iterations = iterations
        self.learning_rate = learning_rate

    def _calculate_gradient(self, X, y):
        """
        Calculate the Gradient Vector for gradient descent
        """
        grad = []
        for i in range(X.shape[1]):
            grad.append(self._calculate_cost_der(X, y, i, X.shape[1]))
        return np.array(grad)

    def _calculate_cost(self, X, y, m):
        """
        Calculate the MSE / Cost Function based on the current theta values
        """
        result = (X@self.theta - y)@(X@self.theta - y)
        result *= (1/(2*m))
        return result

    def _calculate_cost_der(self, X, y, j, m):
        """
        Calculate the Partial Derivative of the Cost Function
        """
        result = (X@self.theta - y) @ X[:, j]
        result *= (1/m)
        return result

    def train(self, X, y):
        """
        Trains the model to learn the parameters
        """
        self.theta = np.random.rand(X.shape[1])

        for i in range(self.iterations):
            grad = self._calculate_gradient(X, y)
            self.theta -= self.learning_rate * grad

    def predict(self, X):
        """
        Predict the Labels for each provided X input data
        """
        preds = []
        for d in X:
            preds.append((self.theta @ d)[0])
        return np.array(preds)


# note: documentation not written yet

import numpy as np

import matplotlib.pyplot as plt
import seaborn as sns

sns.set_style("whitegrid")
prop_cycle = plt.rcParams["axes.prop_cycle"]
colors = prop_cycle.by_key()["color"]
import ipywidgets as widgets

def solow_equation(k, alpha, delta, s):

    saving = s * k ** alpha
    depreciation = delta * k
    k_plus = k + saving - depreciation
    return k_plus, saving, depreciation

def simulate_solow_model(k0, alpha, delta, s, T):

    k_path = [k0]
    saving_path = []
    depreciation_path = []

    for t in range(1, T):

        k_plus, saving, depreciation = solow_equation(k_path[t - 1], alpha, delta, s)

        k_path.append(k_plus)
        saving_path.append(saving)
        depreciation_path.append(depreciation)

    return k_path, saving_path, depreciation_path

def simulate(k_max=10, T=150):

    widgets.interact(
        simulate_,
        k0=widgets.FloatSlider(
            description="$k_0$", min=0, max=k_max, step=0.05, value=2
        ),
        alpha=widgets.FloatSlider(
            description="$\\alpha$", min=0.01, max=0.99, step=0.01, value=0.3
        ),
        delta=widgets.FloatSlider(
            description="$\\delta$", min=0.01, max=0.50, step=0.01, value=0.1
        ),
        s=widgets.FloatSlider(
            description="$s$", min=0.01, max=0.99, step=0.01, value=0.3
        ),
        T=widgets.fixed(T),
        k_max=widgets.fixed(k_max),
    )

def simulate_(k0, alpha, delta, s, T, k_max):

    k_path, saving_path, depreciation_path = simulate_solow_model(
        k0, alpha, delta, s, T * 5
    )

    fig = plt.figure(figsize=(16, 6), dpi=100)
    ax1 = fig.add_subplot(1, 2, 1)

    ax1.plot(k_path[:T], lw=2)
    ax1.set_title("Capital ($k_t$)")
    ax1.set_xlim([0, T])
    ax1.set_ylim([0, k_max])

    k_path = np.array(k_path)
    I = np.abs(np.log(k_path) - np.log(k_path[-1])) < 0.01
    t_converge = T * 5 - k_path[I].size
    ax1.plot(
        [t_converge, t_converge],
        [0, k_max],
        ls="--",
        color="black",
        label="$|\\log(k_t)-\\log(k^\\ast)| < 0.01$",
    )

    ax1.legend(loc="lower right")

    ax2 = fig.add_subplot(1, 2, 2)
    ax2.plot(saving_path[:T], label="saving")
    ax2.plot(depreciation_path[:T], label="depreciation")
    ax2.set_title("Saving ($sk^\\alpha$) and depreciation ($\\delta k_t$)")
    ax2.set_xlim([0, T])
    ax2.set_ylim([0, 0.5 * k_max])

    ax2.legend(loc="upper left")


from sympy import *

# A mailing service places a limit of 66 in. on the combined length and girth of
# (distance around) a package to be sent parcel post.
#
# What dimensions of a rectangular box with square cross-section will contain the largest
# volume that can be mailed? (Hint: There are two different girths.)

L = 42
x = symbols( 'x', positive = True )

V = x**2 * ( L - 4*x )
dV = diff( V, x )

max_x = solve( dV, x )[ 0 ]

print( 'Dim {0}, {1}'.format( max_x, max_x * 2 ) )


"""Build an Artificial Neural Network by implementing the Backpropagation algorithm and test the same using
appropriate data sets """

import numpy as np

X = np.array(([2, 9], [1, 5], [3, 6]), dtype=float)
y = np.array(([92], [86], [89]), dtype=float)
X = X / np.amax(X, axis=0)  # maximum of X array longitudinally
y = y / 100

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

# Derivative of Sigmoid Function
def derivatives_sigmoid(x):
    return x * (1 - x)

# Variable initialization
epoch = 7000  # Setting training iterations
lr = 0.1  # Setting learning rate
inputlayer_neurons = 2  # number of features in data set
hiddenlayer_neurons = 3  # number of hidden layers neurons
output_neurons = 1  # number of neurons at output layer
# weight and bias initialization
wh = np.random.uniform(size=(inputlayer_neurons, hiddenlayer_neurons))
bh = np.random.uniform(size=(1, hiddenlayer_neurons))
wout = np.random.uniform(size=(hiddenlayer_neurons, output_neurons))
bout = np.random.uniform(size=(1, output_neurons))
# draws a random range of numbers uniformly of dim x*y
for i in range(epoch):
    # Forward Propogation
    hinp1 = np.dot(X, wh)
    hinp = hinp1 + bh
    hlayer_act = sigmoid(hinp)
    outinp1 = np.dot(hlayer_act, wout)
    outinp = outinp1 + bout
    output = sigmoid(outinp)
    # Backpropagation
    EO = y - output
    outgrad = derivatives_sigmoid(output)
    d_output = EO * outgrad
    EH = d_output.dot(wout.T)
    hiddengrad = derivatives_sigmoid(hlayer_act)
    d_hiddenlayer = EH * hiddengrad
    wout += hlayer_act.T.dot(d_output) * lr
    # bout += np.sum(d_output, axis=0,keepdims=True) *lr
    wh += X.T.dot(d_hiddenlayer) * lr
    # bh += np.sum(d_hiddenlayer, axis=0,keepdims=True) *lr
print("Input: \n" + str(X))
print("Actual Output: \n" + str(y))
print("Predicted Output: \n", output)

########################################################################################################################
# OUTPUT:
# Ignore single quotes at beginning and end
########################################################################################################################

''''Input:
[[0.66666667 1.        ]
 [0.33333333 0.55555556]
 [1.         0.66666667]]
Actual Output:
[[0.92]
 [0.86]
 [0.89]]
Predicted Output:
 [[0.89613503]
 [0.87647952]
 [0.89699621]]
'''


# function
# Old version of buckling_load function

import sympy as sp
import numpy as np
from scipy import linalg
import matplotlib.pyplot as plt
from ._plot_surface_function import _plot_surface

def critical_buckling(a, b, D, method = 'sin_series', n = 3, shape_plot = False):
    '''
    ===========================================
    This is the old version of the buckling_load function.
    Both functions provide same results, but buckling_load is optmized.
    ===========================================
    '''

    #Creating shape function
    Nw = [[]]
    x = sp.symbols('x')
    y = sp.symbols('y')

    if method == 'sin_series':
        for i in range(n):
            for j in range(n):
                Nw[0].append(sp.sin((i+1)*sp.pi*x/a)*sp.sin((j+1)*sp.pi*y/b))

    if method =='polynomial':
        for i in range(n):
            for j in range(n):
                Nw[0].append(x**(i+1)*y**(j+1)*(x-a)*(y-b))

    #Shape function differentiation
    Nw_values = sp.Matrix(Nw)
    Nw_dx = sp.diff(Nw_values,x)
    Nw_dxx = sp.diff(Nw_values,x,x)
    Nw_dy = sp.diff(Nw_values,y)

    print('it may take a while...')

    #Matrices construction
    Ds = sp.Matrix(D)
    Nww_values = sp.Matrix([sp.diff(Nw_values,x,x),sp.diff(Nw_values,y,y),2*sp.diff(Nw_values,x,y)])
    print('Shape function ready!')
    KGx_values = np.array(sp.integrate(sp.integrate(sp.diff(Nw_values,x).T*sp.diff(Nw_values,x),(x,0,a)),(y,0,b)))
    print('[KGx] ready')
    #KGy_values = np.array(sp.integrate(sp.integrate(sp.diff(Nw_values,y).T*sp.diff(Nw_values,y),(x,0,a)),(y,0,b)))
    #KGxy_values = np.array(sp.integrate(sp.integrate(Nw_dx.T*Nw_dy + Nw_dy.T*Nw_dx,(x,0,a)),(y,0,b)))
    K_values = np.array(sp.integrate(sp.integrate(Nww_values.T*Ds*Nww_values,(x,0,a)),(y,0,b)))
    print('[K] ready')

    #eigenproblem
    A=np.array(K_values).astype('float')
    B=np.array(KGx_values).astype('float')
    eig_values, eig_vectors = linalg.eig(A,B)

    #finding the min eigenvalue
    min_eigen_value = min((eig_values.real**2)**(1/2))
    Nx = min_eigen_value*b
    print(f'buckling load is {Nx}')

    #Finding the corresponding C vector
    i = np.where(eig_values == min_eigen_value)
    C_vector = eig_vectors[::,i[0]].real

    #ploting function
    if shape_plot:
        _plot_surface(a, b, C_vector, Nw_values)

    return Nx


import numpy as np

def multivariate_gaussian(x, mean, cov):
    '''Calculate the probability density of a multivariate
    gaussian distribution at x.
    '''
    n = x.shape[0]

    return 1 / (np.power(2 * np.pi, n / 2.0) * np.absolute(np.power(np.linalg.det(cov), 0.5))) \
            * np.exp(-0.5 * np.dot(x - mean, np.dot(np.linalg.inv(cov), x - mean)))

def gaussian(x, mean, std):
    '''Calculate the probability density of a gaussian distribution
    at x.
    '''
    return multivariate_gaussian(np.array([x]), np.array([mean]), np.array([[std]]))


import os
import argparse
import numpy as np
import matplotlib.pyplot as plt

class LogisticRegressionClassifier:
  def __init__(self, dims, learning_rate=0.1):
    self.w = np.zeros((dims,))
    self.b = np.zeros((1,))
    self.learning_rate = learning_rate

  def sigmoid(self, z):
    return np.clip(1.0/(1.0 + np.exp(-z)), 1e-8, 1-(1e-8))

  def forward(self, X):
    return self.sigmoid(np.matmul(X, self.w) + self.b)

  def accuracy(self, y_pred, y_label):
    return 1 - np.mean(np.abs(np.round(y_pred) - y_label))

  def corss_entropy_loss(self, y_pred, y_label):
    return - np.dot(y_label, np.log(y_pred)) - np.dot(1-y_label, np.log(1-y_pred))

  def update_gradient(self, X, y_label, step=1):
    y_pred = self.forward(X)
    y_error = y_label - y_pred
    w_grad = -np.sum(y_error * X.T, 1)
    b_grad = -np.sum(y_error)
    self.w -= self.learning_rate/np.sqrt(step) * w_grad
    self.b -= self.learning_rate/np.sqrt(step) * b_grad

def shuffle(X, Y):
  randomsize = np.arange(X.shape[0])
  np.random.shuffle(randomsize)
  return X[randomsize], Y[randomsize]

if __name__ == "__main__":
  # parser args
  parser = argparse.ArgumentParser()
  parser.add_argument("X_train_path", metavar="X_train", help="X_train")
  parser.add_argument("Y_train_path", metavar="Y_train", help="Y_train")
  parser.add_argument("X_test_path", metavar="X_test", help="X_test")
  parser.add_argument("output_file", metavar="Output_test", help="Output")
  args = parser.parse_args()

  # load data
  with open(args.X_train_path, 'r') as f:
    next(f)
    X_train = np.array([line.strip('\n').split(',')[1:] for line in f], dtype='float')
  with open(args.Y_train_path, 'r') as f:
    next(f)
    Y_train = np.array([line.strip('\n').split(',')[1] for line in f], dtype='float')
  with open(args.X_test_path, 'r') as f:
    next(f)
    X_test = np.array([line.strip('\n').split(',')[1:] for line in f], dtype='float')
  print(X_train.shape, Y_train.shape, X_test.shape)

  # nomalize features
  X_mean = np.mean(X_train, axis=0).reshape(1, -1)
  X_std = np.std(X_train, axis=0).reshape(1, -1)
  X_train = (X_train - X_mean) / (X_std + 1e-8)
  X_test = (X_test - X_mean) / (X_std + 1e-8)
  print(X_train.shape, X_test.shape, X_mean.shape, X_std.shape)

  # split dev
  train_size = int(0.9 * X_train.shape[0])
  X_dev = X_train[train_size:]
  Y_dev= Y_train[train_size:]
  X_train = X_train[:train_size]
  Y_train = Y_train[:train_size]
  print('train {}; dev {}; test {}; dim {}'.format(
    X_train.shape[0], X_dev.shape[0], X_test.shape[0], X_train.shape[1]))

  # build model
  size, dims = X_train.shape
  lr_clf = LogisticRegressionClassifier(dims)

  # train model
  ## hyperparameters
  max_iter = 10
  batch_size = 32
  learning_rate = 0.2

  ## loss and acc
  train_loss, dev_loss, train_acc, dev_acc = [], [], [], []

  ## loop epoch
  step = 1
  for epoch in range(max_iter):
    X_train, Y_train = shuffle(X_train, Y_train)

    ### loop minibatch
    for idx in range(int(size/ batch_size)+1):
      X = X_train[idx*batch_size:(idx+1)*batch_size]
      Y = Y_train[idx*batch_size:(idx+1)*batch_size]

      #### update gradient with decay learning rate
      lr_clf.update_gradient(X, Y, step)
      step += 1

    ### compute loss and acc
    train_pred = lr_clf.forward(X_train)
    train_loss.append(lr_clf.corss_entropy_loss(train_pred, Y_train) / size)
    train_acc.append(lr_clf.accuracy(train_pred, Y_train))

    dev_pred = lr_clf.forward(X_dev)
    dev_loss.append(lr_clf.corss_entropy_loss(dev_pred, Y_dev) / X_dev.shape[0])
    dev_acc.append(lr_clf.accuracy(dev_pred, Y_dev))

  print('train loss {} and acc {}; dev loss {} and acc {}'.format(
    train_loss[-1], train_acc[-1], dev_loss[-1], dev_acc[-1]
  ))

  # plot loss and acc curve during training
  fig, axes = plt.subplots(1, 2, figsize=(12, 6))
  axes[0].plot(train_loss)
  axes[0].plot(dev_loss)
  axes[0].set_title('Loss')
  axes[0].legend(['train', 'dev'])
  axes[1].plot(train_acc)
  axes[1].plot(dev_acc)
  axes[1].set_title('Acc')
  axes[1].legend(['train', 'dev'])
  plt.savefig('train_loss_acc.png')

  # print most significant weights
  index = np.argsort(np.abs(lr_clf.w))[::-1]
  with open(args.X_test_path, 'r') as f:
    content = f.readline().strip('\n').split(',')
    features = np.array(content)
  for i in index[0:10]:
    print(features[i], lr_clf.w[i])

  # predict test
  test_pred = np.round(lr_clf.forward(X_test)).astype(np.int)
  with open(args.output_file, 'w') as f:
    f.write('id,label\n')
    for i,label in enumerate(test_pred):
      f.write('{},{}\n'.format(i, label))


import numpy as np
import sympy as sp
from sympy.core import S, Dummy
from sympy.functions.special.gamma_functions import gamma
from sympy.polys.orthopolys import (legendre_poly, laguerre_poly,
                                    hermite_poly, jacobi_poly)
from sympy.polys.rootoftools import RootOf

def gauss_gen_laguerre(n, alpha, n_digits):
    r"""
    Computes the generalized Gauss-Laguerre quadrature [1]_ points and weights.

    The generalized Gauss-Laguerre quadrature approximates the integral:

    .. math::
        \int_{0}^\infty x^{\alpha} e^{-x} f(x)\,dx \approx
            \sum_{i=1}^n w_i f(x_i)

    The nodes `x_i` of an order `n` quadrature rule are the roots of
    `L^{\alpha}_n` and the weights `w_i` are given by:

    .. math::
        w_i = \frac{\Gamma(\alpha+n)}
                {n \Gamma(n) L^{\alpha}_{n-1}(x_i) L^{\alpha+1}_{n-1}(x_i)}

    Parameters
    ==========

    n : the order of quadrature

    alpha : the exponent of the singularity, `\alpha > -1`

    n_digits : number of significant digits of the points and weights to return

    Returns
    =======

    (x, w) : the ``x`` and ``w`` are lists of points and weights as Floats.
             The points `x_i` and weights `w_i` are returned as ``(x, w)``
             tuple of lists.

    Examples
    ========

    >>> from sympy import S
    >>> from sympy.integrals.quadrature import gauss_gen_laguerre
    >>> x, w = gauss_gen_laguerre(3, -S.Half, 5)
    >>> x
    [0.19016, 1.7845, 5.5253]
    >>> w
    [1.4493, 0.31413, 0.00906]

    >>> x, w = gauss_gen_laguerre(4, 3*S.Half, 5)
    >>> x
    [0.97851, 2.9904, 6.3193, 11.712]
    >>> w
    [0.53087, 0.67721, 0.11895, 0.0023152]

    See Also
    ========

    gauss_legendre, gauss_laguerre, gauss_hermite, gauss_chebyshev_t, gauss_chebyshev_u, gauss_jacobi, gauss_lobatto

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Gauss%E2%80%93Laguerre_quadrature
    .. [2] http://people.sc.fsu.edu/~jburkardt/cpp_src/gen_laguerre_rule/gen_laguerre_rule.html
    """
    x = Dummy("x")
    p = laguerre_poly(n, x, alpha=alpha, polys=True)
    p1 = laguerre_poly(n-1, x, alpha=alpha, polys=True)
    p2 = laguerre_poly(n-1, x, alpha=alpha+1, polys=True)
    xi = []
    w = []
    for r in p.real_roots():
        if isinstance(r, RootOf):
            r = r.eval_rational(S(1)/10**(n_digits+2))
        xi.append(r.n(n_digits))
        w.append((gamma(alpha+n) /
                 (n*gamma(n)*p1.subs(x, r)*p2.subs(x, r))).n(n_digits))
    return xi, w

alpha = 0
digits = 36
npoints = list(range(1,17))

for n in npoints:
    xi, wi = gauss_gen_laguerre(n, alpha, digits)

    print('\nN = %3d'%(n))
    for i in range(0,n):
        print('%3d %40.36f %40.36f'%(i+1,xi[i],wi[i]))