import numpy as np
import scipy
import sympy
from numpy import linalg as lg
from numpy.linalg import solve
from numpy.linalg import eig
from scipy.integrate import quad

# Question 1
# MC question
# Part a. C. Not defined because the columns don't match the # of entries
# Part b. C.

# Question 2
# Write the system as a vector equation and then as a matrix equation
'''
8x1 + x2 - 3x3 = 8
     2x2 + 3x3 = 0

# Vector Equation

x1 8 + x2 1 + x3 -3 = 8
   0      2       3   0

# Matrix equation

          [x1]
[8 1 -3]  [x2] = [8]
[0 2  3]  [x3]   [0]

'''

# Question 3
# Part a builds an augmented matrix

# Part b, Solve:

a1 = np.array([[1, 4, -3], [-3, -5, 2], [4, 2, 6]])
b1 = np.array([[-1, 17, -20]])

print(a1, '\n\n', b1.T, '\n')

x1 = np.linalg.solve(a1, b1.T)
print(x1, '\n') # Correct

# Question 4
# MC question
# Answer: D. Yes. When the given vectors are written as the columns of a # matrix A, A has a pivot position in every row.

# Question 5
# The coefficients of the equation are the answer
# Answer:
# X1 = -4
# X2 = -4

# Question 6
# Multiple Choice
# Part A
# Could a set of three vectors in set of real numbers R4 span all of set of real numbers R4?
# No. The matrix A whose columns are the three vectors has four rows. To have # a pivot in each row, A would have to have at least four columns (one for # each pivot.)

# Part B
# Could a set of n vectors in set of real numbers Rm span all of set of real # numbers Rm when n is less than m? Explain. Choose the correct answer
# No. The matrix A whose columns are the n vectors has m rows. To have a pivot # in each row, A would have to have at least m columns (one for each pivot.)

# Question 7
# The system has a nontrivial solution

# Question 8
a2 = np.array([[4, 4, 8], [-8, -8, -16], [0, -3, 9], [0, 0, 0]])
b2 = np.array([[0, 0, 0]])

c1 = sympy.Matrix(a2).rref()
print("The Matrix in RREF is: ", c1)

# Simplifies to:
'''
x1 +    5x3 = 0
    x2 -3x3 = 0
          0 = 0

x1 = -5x3
x2 =  3x3

    x1   -5x3   -5
x = x2 =  3x3 =  3
    x3     x3    1

x = x3[-5, 3, 1]
'''

# Question 9
# Part a
# Avh = A(w - p)
# Part b
# A(u + v) = Au + Av
# Part c
# Avh = Aw - Ap
# Part d
# Avh = b - b
#Part e
# Avh = 0

# Question 10
# Part A
# A is a 3x3 matrix with 3 pivot positions
# Does the equation Ax = 0 have a nontrivial solution?
# Answer: No

# Part B
# Does the equation Axequalsb have at least one solution for every possible b?
# Answer: Yes
'''
With three pivot positions, A has a pivot position in each of its three rows, and therefore, for each b in set of real numbers R cubed, the equation Axequalsb has a solution.'''

# Question 11
# The vector equation has only the trivial solution, so the
# vectors are linearly independent.

# Question 12
# Find the value for which the vectors are linearly dependent
'''
[ 2 6 -3]
[-2 -8 3]
[ 6 17 h]

Add row 1 to row 2:
[ 2 6 -3]
[ 0 -2 0]
[ 6 17 h]

Add -3 x row 1 to row 3:
[ 2 6 -3]
[ 0 -2 0]
[ 0 -1 h+9]

Add -0.5 x row 2 to row 3:
[ 2 6 -3]
[ 0 -2 0]
[0 0 h+9]

x3 = -9
'''

# Question 13
'''
If A is an m times n matrix, then the columns of A are linearly independent if and only if A has n pivot columns.

The columns of a matrix A are linearly independent if and only if Axequals0 has no free variables, meaning every variable is a basic variable, that is, if and only if every column of A is a pivot column
'''

# Question 14
# Multiple Choice

import numpy,from numpy,import scipy,from scipy,import sympy


import numpy as np

from isaac.stats import sigmoid

__all__ = [
    "LinearRegression",
    "LogisticRegression"
]

class LinearRegression(object):

    def __init__(self, weights):
        self.weights = weights
        self.dimension = len(weights)

    @classmethod
    def from_dimension(cls, dimension, value=None, dtype=None):
        dtype = dtype or np.float64
        if value is None:
            weights = np.ones(dimension, dtype)
        else:
            weights = np.zeros(dimension, dtype) + value
        return cls(weights)

    def predict(self, x):
        return np.dot(x, self.weights)

    def costs(self, x, y):
        '''
        Measuring the Mean Squared Error over the training set.
        '''
        return np.mean(np.power(np.dot(x, self.weights) - y, 2))

    def cost_derivative(self, x, y):
        costs = (np.dot(x, self.weights) - y)
        derivatives = np.mean(x.T * costs, axis=1)
        return derivatives

    def update_weights(self, new_weights):
        self.weights = new_weights

class LogisticRegression(LinearRegression):

    def predict(self, x):
        return 1 / (1 + np.exp((- np.dot(x, self.weights))))

    def one_cost(self, x, y):
        prediction = self.predict(Xs)
        cost = (
            (- y) * math.log(prediction) - (1 - y) * (math.log(1 - prediction))
            )
        return cost

    def costs(self, x, y):
        predictions = self.predict(x)
        # -log(self.predict(X) if Y == 1)
        # -log(1 - self.predict(X) if Y == 0)
        return np.mean(
            (- y) * np.log(predictions) - (1 - y) * (np.log(1 - predictions)))

    def cost_derivative(self, x, y):
        costs = (self.predict(x) - y)
        derivatives = np.mean(x.T * costs, axis=1)
        return derivatives

    # TODO I suspect this implementation here below
    # is not a clever one.
    def accuracy(self, x, y):
        '''
        return
            float, percentage of accuracy
        '''
        total = len(y)
        predictions = self.predict(x)
        # step the values
        predictions[predictions > 0.5] = 1
        predictions[predictions < 0.5] = 0
        # stats the accuracy
        validations = (predictions == y)
        return validations.mean()


# -*- coding: utf-8 -*-
"""
Created on Sun Apr 14 20:15:48 2019

@author:    Manuel Martinez
@project:   Mathematics for Machine Learning.
@purpose:   Week 4 programming assignment on principal component analysis.
"""
# PACKAGE: DO NOT EDIT THIS CELL
import numpy as np
import timeit

# PACKAGE: DO NOT EDIT THIS CELL
import matplotlib.pyplot as plt
plt.style.use('fivethirtyeight')

from load_data import load_mnist
MNIST = load_mnist()
images, labels = MNIST['data'], MNIST['target']

# %%===========================================================================
# main
# =============================================================================
plt.figure(figsize=(4, 4))
plt.imshow(images[0].reshape(28, 28), cmap='gray')

# %%===========================================================================
# 1. PCA
# =============================================================================

# GRADED FUNCTION: DO NOT EDIT THIS LINE
def normalize(X):
    """Normalize the given dataset X
    Args:
        X: ndarray, dataset

    Returns:
        (Xbar, mean, std): tuple of ndarray, Xbar is the normalized dataset
        with mean 0 and standard deviation 1; mean and std are the
        mean and standard deviation respectively.

    Note:
        You will encounter dimensions where the standard deviation is
        zero, for those when you do normalization the normalized data
        will be NaN. Handle this by setting using `std = 1` for those
        dimensions when doing normalization.
    """
    mu = X.mean(axis=0)
    std = np.std(X, axis=0)
    std_filled = std.copy()
    std_filled[std == 0] = 1
    Xbar = (X - mu) / std_filled
    return Xbar, mu, std

def eig(S):
    """Compute the eigenvalues and corresponding eigenvectors
        for the covariance matrix S.
    Args:
        S: ndarray, covariance matrix

    Returns:
        (eigvals, eigvecs): ndarray, the eigenvalues and eigenvectors

    Note:
        the eigenvals and eigenvecs should be sorted in descending
        order of the eigen values
    """
    eigvals, eigvecs = np.linalg.eig(S)
    desc_order = np.argsort(eigvals)[::-1]
    eigvals = eigvals[desc_order]
    eigvecs = eigvecs[:, desc_order]
    return (eigvals, eigvecs)

def projection_matrix(B):
    """Compute the projection matrix onto the space spanned by `B`
    Args:
        B: ndarray of dimension (D, M), the basis for the subspace

    Returns:
        P: the projection matrix
    """
    P = B @ np.linalg.inv(B.T @ B) @ B.T
    return P

def PCA(X, num_components):
    """
    Args:
        X: ndarray of size (N, D), where D is the dimension of the data,
           and N is the number of datapoints
        num_components: the number of principal components to use.
    Returns:
        X_reconstruct: ndarray of the reconstruction
        of X from the first `num_components` principal components.
    """
    N, D = X.shape
    S = X.T @ X / N
    _, B = eig(S)
    B = B[:, :num_components]
    P = projection_matrix(B)
    X_reconstruct = (P @ X.T).T
    return X_reconstruct

# %% Some preprocessing of the data
NUM_DATAPOINTS = 1000
X = (images.reshape(-1, 28 * 28)[:NUM_DATAPOINTS]) / 255.
Xbar, mu, std = normalize(X)

# %% take a look at how squared distance changes with n_dimensions
for num_component in range(1, 20):
    from sklearn.decomposition import PCA as SKPCA
    # We can compute a standard solution given by scikit-learn's
    # implementation of PCA
    pca = SKPCA(n_components=num_component, svd_solver='full')
    sklearn_reconst = pca.inverse_transform(pca.fit_transform(Xbar))
    reconst = PCA(Xbar, num_component)
    np.testing.assert_almost_equal(reconst, sklearn_reconst)
    print(np.square(reconst - sklearn_reconst).sum())

# %%
def mse(predict, actual):
    """Helper function for computing the mean squared error (MSE)"""
    return np.square(predict - actual).sum(axis=1).mean()

loss = []
reconstructions = []
# iterate over different number of principal components, and compute the MSE
for num_component in range(1, 100):
    reconst = PCA(Xbar, num_component)
    error = mse(reconst, Xbar)
    reconstructions.append(reconst)
#    print('n = {:d}, reconstruction_error = {:f}'.format(num_component, error))
    loss.append((num_component, error))

reconstructions = np.asarray(reconstructions)
# "unnormalize" the reconstructed image
reconstructions = reconstructions * std + mu
loss = np.asarray(loss)

# %%
fig, ax = plt.subplots()
ax.plot(loss[:, 0], loss[:, 1])
ax.axhline(100, linestyle='--', color='r', linewidth=2)
ax.xaxis.set_ticks(np.arange(1, 100, 5))
ax.set(xlabel='num_components', ylabel='MSE',
       title='MSE vs number of principal components')

# %%
# GRADED FUNCTION: DO NOT EDIT THIS LINE
# PCA for high dimensional datasets
def PCA_high_dim(X, n_components):
    """Compute PCA for small sample size but high-dimensional features.
    Args:
        X: ndarray of size (N, D), where D is the dimension of the sample,
           and N is the number of samples
        num_components: the number of principal components to use.
    Returns:
        X_reconstruct: (N, D) ndarray. the reconstruction
        of X from the first `num_components` pricipal components.
    """
    N, D = X.shape
    _, V = eig(X @ X.T)
    U = (X.T @ V)[:, :n_components]
    P = projection_matrix(U)
    X_reconstruct = (P @ X.T).T

    return X_reconstruct

# %%
np.testing.assert_almost_equal(PCA(Xbar, 2), PCA_high_dim(Xbar, 2))
print('A-OK')

# %%
def time(f, repeat=10):
    times = []
    for _ in range(repeat):
        start = timeit.default_timer()
        f()
        stop = timeit.default_timer()
        times.append(stop-start)
    return np.mean(times), np.std(times)

# %%
times_mm0 = []
times_mm1 = []

# iterate over datasets of different size
for datasetsize in np.arange(4, 784, step=20):
    XX = Xbar[:datasetsize] # select the first `datasetsize` samples in the dataset
    # record the running time for computing X.T @ X
    mu, sigma = time(lambda : XX.T @ XX)
    times_mm0.append((datasetsize, mu, sigma))

    # record the running time for computing X @ X.T
    mu, sigma = time(lambda : XX @ XX.T)
    times_mm1.append((datasetsize, mu, sigma))

times_mm0 = np.asarray(times_mm0)
times_mm1 = np.asarray(times_mm1)

fig, ax = plt.subplots()
ax.set(xlabel='size of dataset', ylabel='running time')
bar = ax.errorbar(times_mm0[:, 0], times_mm0[:, 1], times_mm0[:, 2], label="$X^T X$ (PCA)", linewidth=2)
ax.errorbar(times_mm1[:, 0], times_mm1[:, 1], times_mm1[:, 2], label="$X X^T$ (PCA_high_dim)", linewidth=2)
ax.legend();

%time Xbar.T @ Xbar
%time Xbar @ Xbar.T
# Put this here so that our output does not show result of computing `Xbar @ Xbar.T`
pass

# %%
times0 = []
times1 = []

# iterate over datasets of different size
for datasetsize in np.arange(4, 784, step=100):
    XX = Xbar[:datasetsize]
    npc = 2
    mu, sigma = time(lambda : PCA(XX, npc), repeat=10)
    times0.append((datasetsize, mu, sigma))

    mu, sigma = time(lambda : PCA_high_dim(XX, npc), repeat=10)
    times1.append((datasetsize, mu, sigma))

times0 = np.asarray(times0)
times1 = np.asarray(times1)

fig, ax = plt.subplots()
ax.set(xlabel='number of datapoints', ylabel='run time')
ax.errorbar(times0[:, 0], times0[:, 1], times0[:, 2], label="PCA", linewidth=2)
ax.errorbar(times1[:, 0], times1[:, 1], times1[:, 2], label="PCA_high_dim", linewidth=2)
ax.legend();

%time PCA(Xbar, 2)
%time PCA_high_dim(Xbar, 2)
pass


#T# radicals are simplified like regular expressions, but their variables must be defined in the set of real numbers for the simplification to work

#T# to simplify expressions with radicals, the sympy package is used
import sympy

#T# create the variable, it must be created as a variable in the set of real numbers using the real kwarg with a value of True
x = sympy.Symbol('x', real = True) # x

#T# the sqrt function of the sympy package returns the square root of its argument
expr1 = sympy.sqrt(x) # sqrt(x)

#T# the Pow function of the sympy package returns its first argument raised to the power of the second
num1 = 2
expr1 = sympy.Pow(x, num1) # x**2

#T# the powsimp function simplifies expressions with exponents
x = sympy.Symbol('x')     # x
y = sympy.Symbol('y')     # y
a = sympy.Rational('1/2') # 1/2
b = sympy.Rational('1/3') # 1/3
expr1 = x**a * x**b                        # x**(5/6)
expr2 = x**a * y**a                        # sqrt(x)*sqrt(y)
expr3 = sympy.powsimp(expr1)               # x**(5/6)
expr3 = sympy.powsimp(expr2, force = True) # sqrt(x*y) #| the force kwarg must be set to True to show the simplification


import numpy as np

###
##
#

#
##
###

t = 45.0  # t: beam thickness (y) [m]
h = 30.0  # h: beam height (z) [m]
rho = 160.0  # density of steel [kg/mˆ3]
E = 2.861e8  # E: Young's modulus of steel [N/mˆ2]
nu = 3/10  # nu: Poisson's ratio

G = E/2/(1+nu)  # G: Shear modulus [N/mˆ2]
l = 3.0  # l: beam element length
A = t*h  # beam area [mˆ2]
ASy = 5/6*A
ASz = 5/6*A  # effective area of shear
Iy = 1/12*h**3*t
Iz = 1/12*t**3*h  # second moments of area [mˆ4]
Ip = 1/12*t*h*(h**2+t**2)  # polar moment of inertia [mˆ4]
It = min([h, t])**3 * max([h, t])/7  # torsion constant [mˆ4]
Py = 12*E*Iz/(G*ASy*l**2)
Pz = 12*E*Iy/(G*ASz*l**2)

length = l
m_const = rho * A * l

###
##
#

#
##
###

def get_mass_matrix_ver1():
    '''
    VERSION 1

    NOTE: checking out alternative implementation
    according to https://mediatum.ub.tum.de/doc/1072355/file.pdf
    description and implementation seems correct
    NOTE: find out where the formulation for the mass comes from, stiffness seems standard
    '''
    M11 = np.zeros((6, 6))
    M11[0][0] = 1/3
    M11[1][1] = 13/35 + 6*Iz/(5*A*l**2)
    M11[2][2] = 13/35 + 6*Iy/(5*A*l**2)
    M11[3][3] = Ip/(3*A)
    M11[4][4] = l**2/105 + 2*Iy/(15*A)
    M11[5][5] = l**2/105 + 2*Iz/(15*A)
    M11[5][1] = 11*l/210 + Iz/(10*A*l)
    M11[1][5] = M11[5][1]
    M11[4][2] = -11*l/210-Iy/(10*A*l)
    M11[2][4] = M11[4][2]

    M22 = -M11 + 2*np.diag(np.diag(M11))

    M21 = np.zeros((6, 6))
    M21[0][0] = 1/6
    M21[1][1] = 9/70-6*Iz/(5*A*l**2)
    M21[2][2] = 9/70-6*Iy/(5*A*l**2)
    M21[3][3] = Ip/(6*A)
    M21[4][4] = -l**2/140-Iy/(30*A)
    M21[5][5] = -l**2/140-Iz/(30*A)
    M21[5][1] = -13*l/420 + Iz/(10*A*l)
    M21[1][5] = -M21[5][1]
    M21[4][2] = 13*l/420-Iy/(10*A*l)
    M21[2][4] = -M21[4][2]

    # mass values for one level
    length = l
    m_const = rho * A * length

    m_el = np.zeros((2*6, 2*6))
    # upper left
    m_el[0:6, 0:6] += m_const * M11
    # lower left
    m_el[6:12, 0:6] += m_const * M21
    # upper right
    m_el[0:6, 6:12] += m_const * np.transpose(M21)
    # lower right
    m_el[6:12, 6:12] += m_const * M22

    return m_el

def get_mass_matrix_ver2():
    '''
    VERSION 2

    NOTE: from http://homes.civil.aau.dk/jc/FemteSemester/Beams3D.pdf
    seems to be a typo in 1-105 and 1-106 as a division with l**3 instead of l**3 should take place
    implemented mass matrices similar to the stiffness one
    '''
    # define component-wise to have enable better control for various optimization parameters

    # axial inertia - along axis x - here marked as x
    m_x = m_const / 6.0
    m_x_11 = 2.
    m_x_12 = 1.
    m_el_x = m_x * np.array([[m_x_11, m_x_12],
                             [m_x_12, m_x_11]])
    # torsion inertia - around axis x - here marked as alpha - a
    m_a = m_const * Ip/A / 6.0
    m_a_11 = 2
    m_a_12 = 1
    m_el_a = m_a * np.array([[m_a_11, m_a_12],
                             [m_a_12, m_a_11]])
    # bending - inertia along axis y, rotations around axis z - here marked as gamma - g
    m_yg = m_const / 420
    #
    m_yg_11 = 156.
    m_yg_12 = 22. * length
    m_yg_13 = 54.
    m_yg_14 = -13. * length
    #
    m_yg_22 = 4 * length ** 2
    m_yg_23 = - m_yg_14
    m_yg_24 = -3 * length ** 2
    #
    m_yg_33 = m_yg_11
    m_yg_34 = -m_yg_12
    #
    m_yg_44 = m_yg_22
    #
    m_el_yg = m_yg * np.array([[m_yg_11, m_yg_12, m_yg_13, m_yg_14],
                               [m_yg_12, m_yg_22, m_yg_23, m_yg_24],
                               [m_yg_13, m_yg_23, m_yg_33, m_yg_34],
                               [m_yg_14, m_yg_24, m_yg_34, m_yg_44]])

    # bending - inertia along axis z, rotations around axis y - here marked as beta - b
    m_zb = m_const / 420
    #
    m_zb_11 = 156.
    m_zb_12 = -22. * length
    m_zb_13 = 54.
    m_zb_14 = 13. * length
    #
    m_zb_22 = 4. * length ** 2
    m_zb_23 = -m_zb_14
    m_zb_24 = -3 * length ** 2
    #
    m_zb_33 = m_zb_11
    m_zb_34 = - m_zb_12
    #
    m_zb_44 = m_zb_22
    #
    m_el_zb = m_zb * np.array([[m_zb_11, m_zb_12, m_zb_13, m_zb_14],
                               [m_zb_12, m_zb_22, m_zb_23, m_zb_24],
                               [m_zb_13, m_zb_23, m_zb_33, m_zb_34],
                               [m_zb_14, m_zb_24, m_zb_34, m_zb_44]])

    # assemble all components
    m_el = np.array([[m_el_x[0][0], 0., 0., 0., 0., 0.,                 m_el_x[0][1], 0., 0., 0., 0., 0.],
                     [0., m_el_yg[0][0], 0., 0., 0., m_el_yg[0][1],
                         0., m_el_yg[0][2], 0., 0., 0., m_el_yg[0][3]],
                     [0., 0., m_el_zb[0][0], 0., m_el_zb[0][1], 0.,
                         0., 0., m_el_zb[0][2], 0., m_el_zb[0][3], 0.],
                     [0., 0., 0., m_el_a[0][0], 0., 0.,
                         0., 0., 0., m_el_a[0][1], 0., 0.],
                     [0., 0., m_el_zb[0][1], 0., m_el_zb[1][1], 0.,
                         0., 0., m_el_zb[1][2], 0., m_el_zb[1][3], 0.],
                     [0., m_el_yg[0][1], 0., 0., 0., m_el_yg[1][1],
                         0., m_el_yg[1][2], 0., 0., 0., m_el_yg[1][3]],

                     [m_el_x[1][0], 0., 0., 0., 0., 0.,
                         m_el_x[1][1], 0., 0., 0., 0., 0.],
                     [0., m_el_yg[0][2], 0., 0., 0., m_el_yg[1][2],
                      0., m_el_yg[2][2], 0., 0., 0., m_el_yg[2][3]],
                     [0., 0., m_el_zb[0][2], 0., m_el_zb[1][2], 0.,
                      0., 0., m_el_zb[2][2], 0., m_el_zb[2][3], 0.],
                     [0., 0., 0., m_el_a[1][0], 0., 0.,
                      0., 0., 0., m_el_a[1][1], 0., 0.],
                     [0., 0., m_el_zb[0][3], 0., m_el_zb[1][3], 0.,
                      0., 0., m_el_zb[2][3], 0., m_el_zb[3][3], 0.],
                     [0., m_el_yg[0][3], 0., 0., 0., m_el_yg[1][3],     0., m_el_yg[2][3], 0., 0., 0., m_el_yg[3][3]]])

    return m_el

def get_mass_matrix_ver3():
    '''
    VERSION 3

    NOTE: from Appendix A - Straight Beam Element Matrices - page 228
    https://link.springer.com/content/pdf/bbm%3A978-3-319-56493-7%2F1.pdf

    '''

    # define component-wise to have enable better control for various optimization parameters

    # axial inertia - along axis x - here marked as x
    m_x = m_const / 6.0
    m_x_11 = 2.
    m_x_12 = 1.
    m_el_x = m_x * np.array([[m_x_11, m_x_12],
                             [m_x_12, m_x_11]])
    # torsion inertia - around axis x - here marked as alpha - a
    m_a = m_const * Ip/A / 6.0
    m_a_11 = 2
    m_a_12 = 1
    m_el_a = m_a * np.array([[m_a_11, m_a_12],
                             [m_a_12, m_a_11]])

    # bending - inertia along axis y, rotations around axis z - here marked as gamma - g
    # translation
    m_yg = m_const / 210 / (1+Py)**2
    #
    m_yg_11 = 70*Py**2 + 147*Py + 78
    m_yg_12 = (35*Py**2 + 77*Py + 44) * length / 4
    m_yg_13 = 35*Py**2 + 63*Py + 27
    m_yg_14 = -(35*Py**2 + 63*Py + 26) * length / 4
    #
    m_yg_22 = (7*Py**2 + 14*Py + 8) * length ** 2 / 4
    m_yg_23 = - m_yg_14
    m_yg_24 = -(7*Py**2 + 14*Py + 6) * length ** 2 / 4
    #
    m_yg_33 = m_yg_11
    m_yg_34 = -m_yg_12
    #
    m_yg_44 = m_yg_22
    #
    m_el_yg_trans = m_yg * np.array([[m_yg_11, m_yg_12, m_yg_13, m_yg_14],
                                     [m_yg_12, m_yg_22, m_yg_23, m_yg_24],
                                     [m_yg_13, m_yg_23, m_yg_33, m_yg_34],
                                     [m_yg_14, m_yg_24, m_yg_34, m_yg_44]])
    # rotation
    m_yg = rho*Iz / 30 / (1+Py)**2 / length
    #
    m_yg_11 = 36
    m_yg_12 = -(15*Py-3) * length
    m_yg_13 = -m_yg_11
    m_yg_14 = m_yg_12
    #
    m_yg_22 = (10*Py**2 + 5*Py + 4) * length ** 2
    m_yg_23 = - m_yg_12
    m_yg_24 = (5*Py**2 - 5*Py - 1) * length ** 2
    #
    m_yg_33 = m_yg_11
    m_yg_34 = - m_yg_12
    #
    m_yg_44 = m_yg_22
    #
    m_el_yg_rot = m_yg * np.array([[m_yg_11, m_yg_12, m_yg_13, m_yg_14],
                                   [m_yg_12, m_yg_22, m_yg_23, m_yg_24],
                                   [m_yg_13, m_yg_23, m_yg_33, m_yg_34],
                                   [m_yg_14, m_yg_24, m_yg_34, m_yg_44]])

    # sum up translation and rotation
    m_el_yg = m_el_yg_trans + m_el_yg_rot

    # bending - inertia along axis z, rotations around axis y - here marked as beta - b
    # translation
    m_zb = m_const / 210 / (1+Pz)**2
    #
    m_zb_11 = 70*Pz**2 + 147*Pz + 78
    m_zb_12 = -(35*Pz**2 + 77*Pz + 44) * length / 4
    m_zb_13 = 35*Pz**2 + 63*Pz + 27
    m_zb_14 = (35*Pz**2 + 63*Pz + 26) * length / 4
    #
    m_zb_22 = (7*Pz**2 + 14*Pz + 8) * length ** 2 / 4
    m_zb_23 = -m_zb_14
    m_zb_24 = -(7*Pz**2 + 14*Pz + 6) * length ** 2 / 4
    #
    m_zb_33 = m_zb_11
    m_zb_34 = - m_zb_12
    #
    m_zb_44 = m_zb_22
    #
    m_el_zb_trans = m_zb * np.array([[m_zb_11, m_zb_12, m_zb_13, m_zb_14],
                                     [m_zb_12, m_zb_22, m_zb_23, m_zb_24],
                                     [m_zb_13, m_zb_23, m_zb_33, m_zb_34],
                                     [m_zb_14, m_zb_24, m_zb_34, m_zb_44]])
    # rotation
    m_zb = rho*Iy / 30 / (1+Pz)**2 / length
    #
    m_zb_11 = 36
    m_zb_12 = (15*Pz-3) * length
    m_zb_13 = -m_zb_11
    m_zb_14 = m_zb_12
    #
    m_zb_22 = (10*Pz**2 + 5*Pz + 4) * length ** 2
    m_zb_23 = -m_zb_12
    m_zb_24 = (5*Pz**2 - 5*Pz - 1) * length ** 2
    #
    m_zb_33 = m_zb_11
    m_zb_34 = -m_zb_12
    #
    m_zb_44 = m_zb_22
    #
    m_el_zb_rot = m_zb * np.array([[m_zb_11, m_zb_12, m_zb_13, m_zb_14],
                                   [m_zb_12, m_zb_22, m_zb_23, m_zb_24],
                                   [m_zb_13, m_zb_23, m_zb_33, m_zb_34],
                                   [m_zb_14, m_zb_24, m_zb_34, m_zb_44]])

    # sum up translation and rotation
    m_el_zb = m_el_zb_trans + m_el_zb_rot

    # assemble all components
    m_el = np.array([[m_el_x[0][0], 0., 0., 0., 0., 0.,                 m_el_x[0][1], 0., 0., 0., 0., 0.],
                     [0., m_el_yg[0][0], 0., 0., 0., m_el_yg[0][1],
                         0., m_el_yg[0][2], 0., 0., 0., m_el_yg[0][3]],
                     [0., 0., m_el_zb[0][0], 0., m_el_zb[0][1], 0.,
                         0., 0., m_el_zb[0][2], 0., m_el_zb[0][3], 0.],
                     [0., 0., 0., m_el_a[0][0], 0., 0.,
                         0., 0., 0., m_el_a[0][1], 0., 0.],
                     [0., 0., m_el_zb[0][1], 0., m_el_zb[1][1], 0.,
                         0., 0., m_el_zb[1][2], 0., m_el_zb[1][3], 0.],
                     [0., m_el_yg[0][1], 0., 0., 0., m_el_yg[1][1],
                         0., m_el_yg[1][2], 0., 0., 0., m_el_yg[1][3]],

                     [m_el_x[1][0], 0., 0., 0., 0., 0.,
                         m_el_x[1][1], 0., 0., 0., 0., 0.],
                     [0., m_el_yg[0][2], 0., 0., 0., m_el_yg[1][2],
                      0., m_el_yg[2][2], 0., 0., 0., m_el_yg[2][3]],
                     [0., 0., m_el_zb[0][2], 0., m_el_zb[1][2], 0.,
                      0., 0., m_el_zb[2][2], 0., m_el_zb[2][3], 0.],
                     [0., 0., 0., m_el_a[1][0], 0., 0.,
                      0., 0., 0., m_el_a[1][1], 0., 0.],
                     [0., 0., m_el_zb[0][3], 0., m_el_zb[1][3], 0.,
                      0., 0., m_el_zb[2][3], 0., m_el_zb[3][3], 0.],
                     [0., m_el_yg[0][3], 0., 0., 0., m_el_yg[1][3],     0., m_el_yg[2][3], 0., 0., 0., m_el_yg[3][3]]])

    return m_el

# mass values for one level
version1_m_el = get_mass_matrix_ver1()
print("\n VERSION1 - m_el")
print(np.array2string(version1_m_el, precision=3,
                      separator=',', suppress_small=True))

version2_m_el = get_mass_matrix_ver2()
print("\n VERSION2 - m_el")
print(np.array2string(version2_m_el, precision=3,
                      separator=',', suppress_small=True))

version3_m_el = get_mass_matrix_ver3()
print("\n VERSION3 - m_el")
print(np.array2string(version3_m_el, precision=3,
                      separator=',', suppress_small=True))

# print("\n NORM - between m_el_ versions:")
# print(np.linalg.norm(version1_m_el - version3_m_el))
# print()
# wait = input("check...")

###
##
#

#
##
###

def get_stiffness_matrix_ver1():
    '''
    VERSION 1

    NOTE: checking out alternative implementation
    according to https://mediatum.ub.tum.de/doc/1072355/file.pdf
    description and implementation seems correct
    NOTE: find out where the formulation for the mass comes from, stiffness seems standard
    '''

    K11 = np.zeros((6, 6))
    K11[0][0] = E*A/l
    K11[1][1] = 12*E*Iz/(l**3*(1+Py))
    K11[2][2] = 12*E*Iy/(l**3*(1+Pz))
    K11[3][3] = G*It/l
    K11[4][4] = (4+Pz)*E*Iy/(l*(1+Pz))
    K11[5][5] = (4+Py)*E*Iz/(l*(1+Py))
    K11[1][5] = 6*E*Iz/(l**2*(1+Py))
    K11[5][1] = K11[1][5]
    K11[2][4] = -6*E*Iy/(l**2*(1+Pz))
    K11[4][2] = K11[2][4]

    K22 = -K11 + 2*np.diag(np.diag(K11))

    K21 = K11 - 2*np.diag(np.diag(K11))
    K21[4][4] = (2-Pz)*E*Iy/(l*(1+Pz))
    K21[5][5] = (2-Py)*E*Iz/(l*(1+Py))
    K21[1][5] = -K21[5][1]
    K21[2][4] = -K21[4][2]

    k_el = np.zeros((2*6, 2*6))
    # upper left
    k_el[0:6, 0:6] += K11
    # lower left
    k_el[6:12, 0:6] += K21
    # upper right
    k_el[0:6, 6:12] += np.transpose(K21)
    # lower right
    k_el[6:12, 6:12] += K22

    return k_el

def get_stiffness_matrix_ver2():
    '''
    VERSION 2

    NOTE: from http://homes.civil.aau.dk/jc/FemteSemester/Beams3D.pdf
    seems to be a typo in 1-105 and 1-106 as a division with l**3 instead of l**3 should take place
    implemented mass matrices similar to the stiffness one
    '''

    # stifness values for one level
    # define component-wise to have enable better control for various optimization parameters

    length = l
    # axial stiffness - along axis x - here marked as x
    k_x = E*A/l
    k_x_11 = 1.0
    k_x_12 = -1.0
    k_el_x = k_x * np.array([[k_x_11, k_x_12],
                             [k_x_12, k_x_11]])
    # torsion stiffness - around axis x - here marked as alpha - a
    k_a = G*It/l  # G*K/l
    k_a_11 = 1.0
    k_a_12 = -1.0
    k_el_a = k_a * np.array([[k_a_11, k_a_12],
                             [k_a_12, k_a_11]])
    # bending - displacement along axis y, rotations around axis z - here marked as gamma - g
    beta_yg = Py
    k_yg = E*Iz/(1+beta_yg)/l**3
    #
    k_yg_11 = 12.
    k_yg_12 = 6. * length
    k_yg_13 = -k_yg_11
    k_yg_14 = k_yg_12
    #
    k_yg_22 = (4.+beta_yg) * length ** 2
    k_yg_23 = -k_yg_12
    k_yg_24 = (2-beta_yg) * length ** 2
    #
    k_yg_33 = k_yg_11
    k_yg_34 = -k_yg_12
    #
    k_yg_44 = k_yg_22
    #
    k_el_yg = k_yg * np.array([[k_yg_11, k_yg_12, k_yg_13, k_yg_14],
                               [k_yg_12, k_yg_22, k_yg_23, k_yg_24],
                               [k_yg_13, k_yg_23, k_yg_33, k_yg_34],
                               [k_yg_14, k_yg_24, k_yg_34, k_yg_44]])

    # bending - displacement along axis z, rotations around axis y - here marked as beta - b
    beta_zb = Pz
    k_zb = E*Iy/(1+beta_zb)/l**3
    #
    k_zb_11 = 12.
    k_zb_12 = -6. * length
    k_zb_13 = -12.
    k_zb_14 = k_zb_12
    #
    k_zb_22 = (4.+beta_zb) * length ** 2
    k_zb_23 = -k_zb_12
    k_zb_24 = (2-beta_zb) * length ** 2
    #
    k_zb_33 = k_zb_11
    k_zb_34 = - k_zb_12
    #
    k_zb_44 = k_zb_22
    #
    k_el_zb = k_zb * np.array([[k_zb_11, k_zb_12, k_zb_13, k_zb_14],
                               [k_zb_12, k_zb_22, k_zb_23, k_zb_24],
                               [k_zb_13, k_zb_23, k_zb_33, k_zb_34],
                               [k_zb_14, k_zb_24, k_zb_34, k_zb_44]])

    # assemble all components
    k_el = np.array([[k_el_x[0][0], 0., 0., 0., 0., 0.,                 k_el_x[0][1], 0., 0., 0., 0., 0.],
                     [0., k_el_yg[0][0], 0., 0., 0., k_el_yg[0][1],
                         0., k_el_yg[0][2], 0., 0., 0., k_el_yg[0][3]],
                     [0., 0., k_el_zb[0][0], 0., k_el_zb[0][1], 0.,
                         0., 0., k_el_zb[0][2], 0., k_el_zb[0][3], 0.],
                     [0., 0., 0., k_el_a[0][0], 0., 0.,
                         0., 0., 0., k_el_a[0][1], 0., 0.],
                     [0., 0., k_el_zb[0][1], 0., k_el_zb[1][1], 0.,
                         0., 0., k_el_zb[1][2], 0., k_el_zb[1][3], 0.],
                     [0., k_el_yg[0][1], 0., 0., 0., k_el_yg[1][1],
                         0., k_el_yg[1][2], 0., 0., 0., k_el_yg[1][3]],

                     [k_el_x[1][0], 0., 0., 0., 0., 0.,
                         k_el_x[1][1], 0., 0., 0., 0., 0.],
                     [0., k_el_yg[0][2], 0., 0., 0., k_el_yg[1][2],
                      0., k_el_yg[2][2], 0., 0., 0., k_el_yg[2][3]],
                     [0., 0., k_el_zb[0][2], 0., k_el_zb[1][2], 0.,
                      0., 0., k_el_zb[2][2], 0., k_el_zb[2][3], 0.],
                     [0., 0., 0., k_el_a[1][0], 0., 0.,
                      0., 0., 0., k_el_a[1][1], 0., 0.],
                     [0., 0., k_el_zb[0][3], 0., k_el_zb[1][3], 0.,
                      0., 0., k_el_zb[2][3], 0., k_el_zb[3][3], 0.],
                     [0., k_el_yg[0][3], 0., 0., 0., k_el_yg[1][3],     0., k_el_yg[2][3], 0., 0., 0., k_el_yg[3][3]]])

    return k_el

version1_k_el = get_stiffness_matrix_ver1()
print("\n VERSION1 - k_el")
print(np.array2string(version1_k_el, precision=3,
                      separator=',', suppress_small=True))

version2_k_el = get_stiffness_matrix_ver2()
print("\n VERSION2 - k_el")
print(np.array2string(version2_k_el, precision=3,
                      separator=',', suppress_small=True))

# print("\n NORM - k_el versions:")
# print(np.linalg.norm(version1_k_el - version2_k_el))
# print()
# wait = input("check...")


import numpy as np
import matplotlib.pyplot as plt
import scipy.sparse as sp
import scipy.sparse.linalg as la
from functools import partial
import time

def make_L(Nx, Ny):
    Dx = sp.diags((Nx-1)*[1])
    Dx += sp.diags((Nx-2)*[-1],-1)
    rowx = sp.csr_matrix((1,Nx-1))
    rowx[0,-1] = -1
    Dx = sp.vstack((Dx, rowx))
    Lx = Dx.transpose().dot(Dx)

    Dy = sp.diags((Ny-1)*[1])
    Dy += sp.diags((Ny-2)*[-1],-1)
    rowy = sp.csr_matrix((1,Ny-1))
    rowy[0,-1] = -1
    Dy = sp.vstack((Dy, rowy))
    Ly = Dy.transpose().dot(Dy)
    return sp.kronsum(Lx,Ly)

def discretize(x_d, y_d, h):
    nx = int(x_d/h)
    ny = int(y_d/h)
    return make_L(nx,ny)/h/h

def get_grid(x_d, y_d, h):
    grid = np.mgrid[h:y_d:h, h:x_d:h]
    return (grid[1,:,:], grid[0,:,:])

def FE_step(u0, A, dt):
    return (sp.identity(A.shape[0])+(A)*dt)._mul_vector(u0)

def BE_step(u0, A, dt):
    return la.spsolve((sp.identity(A.shape[0])-(A)*dt), u0)

def stable_dt(h):
    return (h**2)/4

# Unsterady solver interface
def unsteady_solver(u0, A, dt, T, saves, method="FE", stabilizer=None):
    stepper = FE_step
    if method == "BE":
        stepper = BE_step
    if stabilizer is not None and method == "FE":
        dt = stabilizer()

    t = 0
    un = u0
    us = []
    if 0 in save_points:
        us.append(u0)
    while t <= T:
        t += dt
        un = stepper(un, A, dt)
        for s in saves:
            if abs(s-t) <= dt/2:
                us.append(un)

    return us

def initial(xx, yy):
    a = -5
    return np.reshape(np.exp(a*np.ones(xx.shape)*(np.square(xx-2*np.ones(xx.shape))+np.square(yy-2*np.ones(yy.shape)))), xx.shape[0]*xx.shape[1])

# Domain is [0,x] x [0,4]
x = 4
y = 4
h = 0.02
# specify  dt
dt = 0.015

grid = get_grid(x,y,h)
reshaper = lambda u: np.reshape(u, [grid[0].shape[0], grid[0].shape[1]])[::-1,:]
L = -1 * discretize(x,y,h)

# Specify where to save the solution
save_points = [0, 0.045, 0.09, 0.15]

print(stable_dt(h))

start = time.time()
# Remove the stabilizer arguemnt if you want to use dt as the timestep for FE
# Set method to either "FE" or "BE"
uno = unsteady_solver(initial(*grid), L, dt, 0.15, save_points, method="FE", stabilizer = lambda : stable_dt(h))
print(time.time()-start)
start = time.time()
dos = unsteady_solver(initial(*grid), L, dt, 0.15, save_points, method="BE")
print(time.time()-start)

# Normalization
mx = max(np.max(np.array(uno)), np.max(np.array(dos)))
mn = max(np.min(np.array(uno)), np.min(np.array(dos)))

# Plotting
fig = plt.figure()
plt.subplot(2,5,1)
plt.imshow(reshaper(uno[0]), vmin=mn, vmax=mx)
plt.title("t = 0s")
plt.subplot(2,5,2)
plt.imshow(reshaper(uno[1]), vmin=mn, vmax=mx)
plt.title("t = 0.045s")
plt.subplot(2,5,3)
plt.imshow(reshaper(uno[2]), vmin=mn, vmax=mx)
plt.title("t = 0.09s")
plt.subplot(2,5,4)
plt.imshow(reshaper(uno[3]), vmin=mn,  vmax=mx)
plt.title("t = 0.15s")

plt.subplot(2,5,6)
plt.imshow(reshaper(dos[0]), vmin=mn, vmax=mx)
plt.subplot(2,5,7)
plt.imshow(reshaper(dos[1]), vmin=mn, vmax=mx)
plt.subplot(2,5,8)
plt.imshow(reshaper(dos[2]), vmin=mn, vmax=mx)
plt.subplot(2,5,9)
im = plt.imshow(reshaper(dos[3]), vmin=mn, vmax=mx)

cbar_ax = fig.add_axes([0.85, 0.15, 0.05, 0.7])
fig.colorbar(im, cax=cbar_ax)

plt.show()


import numpy as np

def map_feature(x1, x2, degree=6):
    """
        Maps the two input features to quadratic features used in logistic regression.

        Parameters
        ----------
        x1 : array_like
            Shape (m, 1), containing one feature.

        x2 : array_like
            Shape (m, 1), containing a second feature of the same size.

        degree : int, optional
            The polynomial degree.

        Returns
        -------
        : array_like
            A matrix of m rows, and columns depending on degree of polynomial.
    """

    if x1.ndim > 0:
        out = [np.ones(x1.shape[0])]
    else:
        out = [np.ones(1)]

    for i in range(1, degree+1):
        for j in range(i + 1):
            out.append((x1 ** (i - j)) * (x2 ** j))

    if x1.ndim > 0:
        return np.stack(out, axis=1)
    else:
        return np.array(out)


import numpy as np

class Graph:
    def __init__(self, vertex, edges):
        self.vertex = vertex
        self.edges = edges
        self.adj_matrix = np.inf*np.ones((vertex, vertex))

    '''
    Dijkstra's algorithm (or Dijkstra's Shortest Path First algorithm, SPF algorithm)
    is an algorithm for finding the shortest paths between nodes in a graph,
    which may represent, for example, road networks.
    It was conceived by computer scientist Edsger W. Dijkstra in 1956 and published three years later.
    '''

    def add_edge(self, v1, v2, w):
        if v1 < self.vertex and v2 < self.vertex:
            self.adj_matrix[v1][v2] = w
            self.adj_matrix[v2][v1] = w
        else:
            print(f"Error: Vertex ({v1},{v2}) out of range.")

    def ShortestPath(self, source):
        i = 0
        vertex = source
        discovered = [source]
        while i < self.vertex - 1:
            MIN = np.inf
            idx = -1
            for j in range(self.vertex):
                if j not in discovered and MIN>self.adj_matrix[vertex][j]:
                    MIN = self.adj_matrix[vertex][j]
                    idx = j

            vertex = idx
            discovered.append(vertex)
            i+=1
        return discovered

    def show_adj_matrix(self):
        print(self.adj_matrix)

g = Graph(6, 7)
g.add_edge(1,3,13)
g.add_edge(1,0,1)
g.add_edge(1,5,2)
g.add_edge(0,5,9)
g.add_edge(2,5,5)
g.add_edge(2,3,7)
g.add_edge(0,4,4)
g.show_adj_matrix()
print(g.ShortestPath(4))


def kmeans(puntos,k):
    import numpy
    #1. Generar k centroides
    mean=[0,0]
    cov=[[1,0],[0,1]]
    centroides=numpy.random.multivariate_normal(mean,cov,k)
    print(centroides)

    iteracion=0
    #centroides_nuevos=centroides+10
    while True:
        #2.Asignar al centroide más cercano
        kmin=[]
        for x in puntos:
            distance=[]
            for y in centroides:
                distance.append(numpy.linalg.norm(x-y))
            kmin.append(numpy.argmin(distance))

        #3. Calcular la distancia promedio: nuevos centroides
        kmin=numpy.asarray(kmin)

        list_k=list(range(k))
        centroides_nuevos=numpy.zeros((k,2))
        for n in list_k:
            centroides_nuevos[n]=(numpy.mean(puntos[kmin==n,:], axis=0))

        iteracion=iteracion+1
        print('Iteración:')
        print(iteracion)
        print('Centroides:')
        print(centroides_nuevos)
        if (numpy.linalg.norm(centroides-centroides_nuevos)>1*10**-5):
            centroides=centroides_nuevos
        else:
            break

    return centroides_nuevos
    #El resultado final varía dependiendo de la semilla


import sys
import numpy as np
from sklearn.decomposition import PCA
from functools import partial
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

def pca_axes(X):
    pca = PCA(n_components = 2)
    pca.fit(X)

    eigen_vectors = pca.components_
    eigen_values = pca.explained_variance_

    major_scale = np.sqrt(2 * eigen_values[0])
    minor_scale = np.sqrt(2 * eigen_values[1])

    major_axis_unit = eigen_vectors[0, :]
    minor_axis_unit = eigen_vectors[1, :]

    major_axis = major_axis_unit * major_scale
    minor_axis = minor_axis_unit * minor_scale

    return major_axis, minor_axis

def rotate(X, theta):
    R = np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]])
    return np.dot(R, X.T).T

def ellipse(semi_major_axis, semi_minor_axis, sigma, n_points):
    t = np.linspace(0, 1, n_points, endpoint=True)
    noise = np.random.multivariate_normal([0, 0], [[sigma, 0], [0, sigma]], n_points)
    return np.c_[semi_major_axis * np.cos(2 * np.pi * t), semi_minor_axis * np.sin(2 * np.pi * t)] + noise

def draw_ellipse(theta, X, ax):
    X = rotate(X, theta)
    major_axis, minor_axis = pca_axes(X)

    plt.cla()
    plt.grid(True)
    plt.scatter(X[:, 0], X[:, 1])
    plt.quiver(0, 0, major_axis[0], major_axis[1], angles = 'xy', scale_units = 'xy', scale = 1, color = 'red')
    plt.quiver(0, 0, minor_axis[0], minor_axis[1], angles = 'xy', scale_units = 'xy', scale = 1, color = 'green')
    plt.draw()
    plt.pause(0.001)

if __name__ == '__main__':
    semi_major_axis = 2
    semi_minor_axis = 7
    n_points = 100
    sigma = 0.05
    n_animation_steps = 100

    fig = plt.figure()
    fig.set_tight_layout(True)
    fig.add_subplot(111)
    ax = fig.axes[0]
    ax.grid(color='k', linestyle='--')

    X = ellipse(semi_major_axis, semi_minor_axis, sigma, n_points)
    callback = partial(draw_ellipse, X = X, ax = ax)
    theta = np.linspace(0, 2 * np.pi, n_animation_steps)
    anim = FuncAnimation(fig, callback, frames = theta, interval=200)
    if len(sys.argv) > 1 and sys.argv[1] == 'save':
        anim.save('pca.gif', dpi=80, writer='imagemagick')
    else:
        plt.show()


from sklearn import datasets
import matplotlib.pyplot as plt
import numpy as np
from pca import PCA

#data = datasets.load_digits()
data = datasets.load_iris()
X = data.data
y = data.target

# Project the data onto the 2 primary principal components
pca = PCA(2)
pca.fit(X)
X_projected = pca.transform(X)

print('Shape of X:', X.shape)
print('Shape of transformed X:', X_projected.shape)

x1 = X_projected[:, 0]
x2 = X_projected[:, 1]

plt.scatter(x1, x2,
        c=y, edgecolor='none', alpha=0.8,
        cmap=plt.cm.get_cmap('viridis', 3))

plt.xlabel('Principal Component 1')
plt.ylabel('Principal Component 2')
plt.colorbar()
plt.show()


# A tree data structure which stores a list of degrees and can quickly retrieve the min degree element,
# or modify any of the degrees, each in logarithmic time. It works by creating a binary tree with the
# given elements in the leaves, where each internal node stores the min of its two children.
# min tree, which can fetch min value quickly
import math
import numpy as np
class MinTree:
    def __init__(self, degrees):
        degrees = self.checktype(degrees)
        self.height = int(math.ceil(math.log(len(degrees), 2)))
        self.numLeaves = 2 ** self.height  #  the operater form of pow
        self.numBranches = self.numLeaves - 1
        self.n = self.numBranches + self.numLeaves
        self.nodes = [float('inf')] * self.n
        for i in range(len(degrees)):
            self.nodes[self.numBranches + i] = degrees[i]
        for i in reversed(range(self.numBranches)):
            self.nodes[i] = min(self.nodes[2 * i + 1], self.nodes[2 * i + 2])

    def getMin(self):
        cur = 0
        for i in range(self.height):
            cur = (2 * cur + 1) if self.nodes[2 * cur + 1] <= self.nodes[2 * cur + 2] else (2 * cur + 2)  #  select one from left child and right child
        return (cur - self.numBranches, self.nodes[cur])

    def index_of(self ,idx):
        idx = self.checktype(idx)
        cur = self.numBranches + idx
        return self.nodes[cur]

    # return the renewed value of degrees[idx]
    def changeVal(self, idx, delta):
        idx = self.checktype(idx)
        delta = self.checktype(delta)
        cur = self.numBranches + idx
        if self.nodes[cur] == float('inf'):
            return float('inf')

        self.nodes[cur] = self.nodes[cur] + delta

        for i in range(self.height):
            cur = (cur - 1) // 2
            nextParent = min(self.nodes[2 * cur + 1], self.nodes[2 * cur + 2])
            if self.nodes[cur] == nextParent:
                break
            self.nodes[cur] = nextParent
        return self.nodes[cur]

    # return the renewed value of degrees[idx]
    def setVal (self, idx, new_value):
        idx = self.checktype(idx)
        new_value = self.checktype(new_value)
        cur = self.numBranches + idx
        if self.nodes[cur] == float('inf'):
            return float('inf')

        self.nodes[cur] = new_value
        for i in range(self.height):
            cur = (cur - 1) // 2
            nextParent = min(self.nodes[2 * cur + 1], self.nodes[2 * cur + 2])
            if self.nodes[cur] == nextParent:
                break
            self.nodes[cur] = nextParent
        return self.nodes[cur]

    def dump(self):
        print("numLeaves: %d, numBranches: %d, n: %d, nodes: " % (self.numLeaves, self.numBranches, self.n))
        cur = 0
        for i in range(self.height + 1):
            for j in range(2 ** i):
                print(self.nodes[cur],)
                cur += 1
            print('')

    def checktype(self, obj):
        obj_type = type(obj)
        if obj_type is np.ndarray:
            obj = obj.tolist()
            return obj
        elif isinstance(obj, list):
            obj = (np.array(obj)).tolist()
            return obj
        elif np.issubdtype(obj_type, np.integer):
            obj = int(obj)
            return obj
        elif np.issubdtype(obj_type, np.floating):
            obj = float(obj)
            return obj
        elif isinstance(obj, int):
            return obj
        elif isinstance(obj, float):
            return obj
        else: raise Exception('The type of ', obj,' is the wrong type! Type is', type(obj))


import sympy as sym
from sympy.abc import s, t, x, y, z
import numpy as np
from sympy.integrals import inverse_laplace_transform
import matplotlib.pyplot as plt

# Define inputs
# First step (up) starts at 1 sec
U1 = 2 / s * sym.exp(-s)
# Ramp (down) starts at 3 sec
U2 = -1 / s ** 2 * sym.exp(-3 * s)
# Ramp completes at 5 sec
U3 = 1 / s ** 2 * sym.exp(-5 * s)

# Transfer function
G = 5 * (s + 1) / (s + 3) ** 2

# Calculate responses
Y1 = G * U1
Y2 = G * U2
Y3 = G * U3

# Inverse Laplace Transform
u1 = inverse_laplace_transform(U1, s, t)
u2 = inverse_laplace_transform(U2, s, t)
u3 = inverse_laplace_transform(U3, s, t)
y1 = inverse_laplace_transform(Y1, s, t)
y2 = inverse_laplace_transform(Y2, s, t)
y3 = inverse_laplace_transform(Y3, s, t)
print('y1')
print(y1)

# generate data for plot
tm = np.linspace(0, 8, 100)
us = np.zeros(len(tm))
ys = np.zeros(len(tm))

# substitute numeric values for u and y
for u in [u1, u2, u3]:
    for i in range(len(tm)):
        us[i] += u.subs(t, tm[i])
for y in [y1, y2, y3]:
    for i in range(len(tm)):
        ys[i] += y.subs(t, tm[i])

# plot results
plt.figure()
plt.plot(tm, us, label='u(t)')
plt.plot(tm, ys, label='y(t)')
plt.legend()
plt.xlabel('Time')
plt.show()


from LU_Decomposition import *
import numpy

t = linspace(0,1,50)
n = 12
m = 50
A = []
for i in range(m):
    z = []
    for j in range(n):
        z.append(t[i]**j)
    A.append(z)
A = array(A)
b = array([cos(4*t)]).T

# Cholesky
print("Chlolesky")
M = A.T@A
L = cholesky(M) ## This returns a lower triangular matrix such that M = L@L.T
y = forward_substitution(L, A.T@b)
x = backward_substitution(L.T, y)
print('residual', norm(A@x - b))
print(x)

## If you want the full QR decomposition, use `mode='full'`
print("QR")
Qhat, Rhat = qr(A, mode='reduced')
y = Qhat.T@b
x = backward_substitution(Rhat, y)
print('residual', norm(A@x - b))
print(x)

## If you want the full SVD decomposition, use `full_matrices=True`
print("SVD")
Uhat, sigma, Vhat_T = svd(A, full_matrices=False) ## sigma is the vector of diagonal entries, not the matrix
Vhat = Vhat_T.T
y = (1/sigma[:, None])*(Uhat.T@b) ## this is equivalent to solving Sigma_hat@y = Uhat.T@b
x = Vhat@y
print('residual', norm(A@x - b))
print(x)


'''Implementation of the quantile loss function'''

from typing import Sequence
import numpy as np

def quantile_loss(predictions: Sequence[float],
                  targets: Sequence[float],
                  quantile: float) -> float:
    '''Quantile loss function.

    Args:
        predictions (sequence of floats):
            Model predictions, of shape [n_samples,].
        targets (sequence of floats):
            Target values, of shape [n_samples,].
        quantile (float):
            The quantile we are seeking. Must be between 0 and 1.

    Returns:
        float: The quantile loss.
    '''
    # Convert inputs to NumPy arrays
    target_arr = np.asarray(targets)
    prediction_arr = np.asarray(predictions)

    # Compute the residuals
    res = target_arr - prediction_arr

    # Compute the mean quantile loss
    loss = np.mean(np.maximum(res, np.zeros_like(res)) * quantile +
                   np.maximum(-res, np.zeros_like(res)) * (1 - quantile))

    # Ensure that loss is of type float and return it
    return float(loss)

def smooth_quantile_loss(predictions: Sequence[float],
                         targets: Sequence[float],
                         quantile: float,
                         alpha: float = 0.4) -> float:
    '''The smooth quantile loss function from [1].

    Args:
        predictions (sequence of floats):
            Model predictions, of shape [n_samples,].
        targets (sequence of floats):
            Target values, of shape [n_samples,].
        quantile (float):
            The quantile we are seeking. Must be between 0 and 1.
        alpha (float, optional):
            Smoothing parameter. Defaults to 0.4.

    Returns:
        float: The smooth quantile loss.

    Sources:
        [1]: Songfeng Zheng (2011). Gradient Descent Algorithms for
             Quantile Regression With Smooth Approximation. International
             Journal of Machine Learning and Cybernetics.
    '''
    # Convert inputs to NumPy arrays
    target_arr = np.asarray(targets)
    prediction_arr = np.asarray(predictions)

    # Compute the residuals
    residuals = target_arr - prediction_arr

    # Compute the smoothened mean quantile loss
    loss = (quantile * residuals +
            alpha * np.log(1 + np.exp(-residuals / alpha)))

    # Ensure that loss is of type float and return it
    return float(loss.mean())


import numpy as np

def lcs(a,b,c,m,n,p,t):
    if m<0 or n<0 or p<0:
        # print(t)
        return 0
    else:
        if a[m]==b[n]==c[p]:
            t=f'{t}{a[m]}'
            return 1+lcs(a,b,c,m-1,n-1,p-1,t)
        else:
            return max(
                lcs(a,b,c,m-1,n,p,t),
                lcs(a,b,c,m,n-1,p,t),
                lcs(a,b,c,m,n,p-1,t)
            )

def lcsdp(a,b,c):
    m=len(a)
    n=len(b)
    p=len(c)
    t=""
    dp=np.zeros((m+1,n+1,p+1))
    # print(dp)

    for i in range(m+1):
        for j in range(n+1):
            for k in range(p+1):
                if i==0 or j==0 or k==0:
                    continue

                if a[i-1]==b[j-1]==c[k-1]:
                    dp[i][j][k]=dp[i-1][j-1][k-1]+1
                else:
                    dp[i][j][k] = max(
                        dp[i-1][j][k],
                        dp[i][j-1][k],
                        dp[i][j][k-1]
                    )

    print(dp[m][n][p])
    print(i,m,j,n,k,p)

    str=""

    while i>0 and j>0 and k>0:
        # print(i,j,k)
        if a[i-1]==b[j-1]==c[k-1]:
            print(a[i-1])
            str=f'{a[i-1]}{str}'
            i-=1
            j-=1
            k-=1
            # print(i,j,k)
        else:
            n1=dp[i-1][j][k]
            n2=dp[i][j-1][k]
            n3=dp[i][j][k-1]
            if n1>=n2 and n1>=n3:
                i-=1
            elif n2>=n1 and n2>=n3:
                j-=1
            elif n3>=n1 and n3>=n2:
                k-=1
            else:
                print('##',n1,n2,n3)

    print(str)

if __name__ == "__main__":
    a="6662220542630314443712"
    # a="ACHCGGTCGAGTTGCGCGGAHAGCCLGGCCGAA"
    b="00078321207413782377777"
    # b="GTCXGTTCGDGAATGFCGTTGCTOTGTAAA"
    c="6664664567861104057425"
    # c="AKLGTCGTCGGAAUIOGCCGGCCGAA"

    m=len(a)
    n=len(b)
    p=len(c)
    t=""
    print(m)
    # test=lcs(a,b,c,m-1,n-1,p-1,t)
    lcsdp(a,b,c)


# -*- coding: utf-8 -*-
"""
@author: swmapthingpaul (pjulian@ufl.edu)

"""
#Statistics in python
#https://www.scipy-lectures.org/packages/statistics/index.html

#clears everything
# %reset

#Libraries
#Libraries
import os
import pandas as pd
import numpy as np
import scipy
import plotly as py
import matplotlib.pyplot as plt
%matplotlib inline

## Find working directory
os.getcwd()

# Change working directory
os.chdir('D:\\_GitHub\\learningpython\\statistics')

#Data
data=pd.read_csv("d:/_GitHub/learningpython/data/brain_size.csv",sep=";",na_values=".")

#creating from arrays
t=np.linspace(-6,6,20)
sin_t=np.sin(t)
cos_t=np.cos(t)

pd.DataFrame({'t': t, 'sin': sin_t, 'cos': cos_t})

#Manipulating data
data.shape
data.columns
print(data['Gender'])
data[data['Gender'] == 'Female']['VIQ'].mean()

groupby_gender = data.groupby('Gender')
groupby_gender['VIQ'].mean()

# Box plots of different columns for each gender
groupby_gender = data.groupby('Gender')
groupby_gender.boxplot(column=['FSIQ', 'VIQ', 'PIQ'])

# Scatter matrices for different columns
from pandas.tools import plotting
plotting.scatter_matrix(data[['Weight', 'Height', 'MRI_Count']])
plotting.scatter_matrix(data[['PIQ', 'VIQ', 'FSIQ']])

# Hypothesis testing: comparing two groups
from scipy import stats

#1-sample t-test: testing the value of a population mean
stats.ttest_1samp(data['VIQ'], 0)

#2-sample t-test: testing for difference across populations
stats.ttest_ind(data['FSIQ'], data['PIQ'])
data.boxplot(column=["FSIQ","PIQ"])

stats.ttest_rel(data['FSIQ'], data['PIQ'])

## This is equivalent to a 1-sample test on the difference:
stats.ttest_1samp(data['FSIQ'] - data['PIQ'], 0)
test=data['FSIQ'] - data['PIQ']
plt.boxplot(test)

#simple linear regression
x = np.linspace(-5, 5, 20)
np.random.seed(1)
# normal distributed noise
y = -5 + 3*x + 4 * np.random.normal(size=x.shape)
# Create a data frame containing all the relevant variables
data = pd.DataFrame({'x': x, 'y': y})

plt.figure(figsize=(5, 4))
plt.plot(data["x"],data["y"], 'o')

from statsmodels.formula.api import ols
model = ols("y ~ x", data).fit()
print(model.summary())

# Peform analysis of variance on fitted linear model
from statsmodels.stats.anova import anova_lm
anova_results = anova_lm(model)
print(anova_results)

# Retrieve the parameter estimates
beta_0, beta_1 = model._results.params
plt.plot(x, x*beta_1 + beta_0)
plt.xlabel('x')
plt.ylabel('y')

#data with linear model depicited run all lines together
plt.plot(data["x"],data["y"], 'o')
plt.plot(x, x*beta_1 + beta_0,color="black")
plt.xlabel('x')
plt.ylabel('y')

## Categorical variables: comparing groups or multiple categories
model = ols("VIQ ~ Gender + 1", data).fit()
print(model.summary())

import numpy,import scipy,from scipy,from statsmodels


#! /usr/bin/env python3
# -*- coding: utf-8 -*-

# from https://docs.scipy.org/doc/numpy-dev/user/quickstart.html

import numpy as np
a = np.arange(15).reshape(3, 5)

print("a=",a)
print("\ta.shape =",a.shape)
print("\ta.ndim =",a.ndim)
print("\ta.dtype.name =",a.dtype.name)
print("\ta.itemsize =",a.itemsize)
print("\ta.size =",a.size)
print("\ttype(a) =",type(a))

b = np.array([6, 7, 8])
print("b=",b)
print("\ttype(b)=",type(b))
print("\tb.dtype.name =",b.dtype.name)

print(np.array([(1.5,2,3), (4,5,6)]))

print(np.zeros( (3,4) ))

print(np.ones( (2,3,4), dtype=np.int32 ))

print(np.empty( (2,3) ))

x = np.linspace( 0, 2*np.pi, 5 )
f = np.sin(x)    # np.exp, np.sqrt, nop.cos, etc
print("x=",x)
print("sin(x)=",f)

# Arithmetic operators on arrays apply elementwise

A = np.array( [[1,1], [0,1]] )
B = np.array( [[2,0], [3,4]] )
print("A=", A)
print("B=", B)
print("A*B=", A*B)                         # elementwise product
print("A.dot(B)=", A.dot(B))     # matrix product

b = np.arange(12).reshape(3,4)
print("b=", b)
print("b.sum(axis=0) =", b.sum(axis=0))
print("b.min(axis=1) =", b.min(axis=1))
print("b.max(axis=0) =", b.max(axis=0))

# indexing / slicing

a = np.arange(10)+3
print("a =", a)
print("a[2] =", a[2])
print("a[2:4] =", a[2:4])     # renvoie une "view", c a d une ref
print("a[0:6:2] =", a[0:6:2])  #i=>j par pas de k
print("a[:] =",a[:])
print("a[-1] =",a[-1])

b = np.fromfunction(lambda x,y: 10*x+y,(5,4),dtype=int) #creation par fct
print("b =", b)
print("b[1:3, : ] =", b[1:3, : ])
print("b[2] =", b[2])  # 3ieme ligne

# iteration
for element in b.flat:
    print(element)

# shape

print("A.T = ", A.T)
a= np.arange(12)
print("a=", a)
a.reshape(3,4) # a n'est PAS modifié => renvoie un objet
print("a=", a)
a.resize(3,4) # a est modifié
print("a=", a)

# views/copies
d = a           # d n'est qu'une ref vers a (1 seul objet numpy)
d = a.view()    # d est un nouvel objet numpy qui pointe vers les mêmes données
                # les slices fonctionnent comme ca
d = a.copy()    # d est indep de "a"


# https://en.wikipedia.org/wiki/PERT_distribution

import matplotlib.pyplot as plt
from mpl_format.axes.axis_utils import new_axes
from numpy import linspace
from probability.distributions import PERT

x = linspace(0, 100, 1001)

def plot_wikipedia_pdfs():
    """
    https://en.wikipedia.org/wiki/PERT_distribution#/media/
    File:PERT_pdf_examples.jpg
    """
    ax = new_axes(width=10, height=10)
    PERT(0, 10, 100).pdf().plot(x=x, color='blue', ax=ax)
    PERT(0, 50, 100).pdf().plot(x=x, color='orange', ax=ax)
    PERT(0, 70, 100).pdf().plot(x=x, color='gray', ax=ax)
    ax.set_ylim(0, 0.03)
    ax.set_title('Probability density function')
    ax.legend(loc='upper center')
    plt.show()

def plot_wikipedia_cdfs():
    """
    https://en.wikipedia.org/wiki/PERT_distribution#/media/
    File:PERT_cdf_examples.jpg
    """
    ax = new_axes(width=10, height=10)
    PERT(0, 10, 100).cdf().plot(x=x, color='blue', ax=ax)
    PERT(0, 50, 100).cdf().plot(x=x, color='orange', ax=ax)
    PERT(0, 70, 100).cdf().plot(x=x, color='gray', ax=ax)
    ax.set_ylim(0, 1)
    ax.set_title('Cumulative density function')
    ax.legend(loc='upper center')
    plt.show()

if __name__ == '__main__':

    plot_wikipedia_pdfs()
    plot_wikipedia_cdfs()


"""
tests for the measure module.
"""

# imports
import pytest
import molecool
import numpy as np

def test_calculate_distance():
    r1 = np.array([0,0,0])
    r2 = np.array([0,1,0])

    expected_distance = 1

    calculated_distance = molecool.calculate_distance(r1,r2)

    assert expected_distance == calculated_distance

def test_calculate_angle():
    r1 = np.array([1,0,0])
    r2 = np.array([0,0,0])
    r3 = np.array([0,1,0])

    #expected_angle = 3.14/2
    expected_angle = 90

    calculated_angle = molecool.calculate_angle(r1,r2,r3,degrees=True)

    assert pytest.approx(expected_angle,abs=1e-2) == calculated_angle

# a decorator
@pytest.mark.parametrize("p1,p2,p3,expected_angle",[
    (np.array([np.sqrt(2)/2,np.sqrt(2)/2, 0]),np.array([0,0,0]),np.array([1,0,0]),45),
    (np.array([0,0,-1]), np.array([0,1,0]),np.array([1,0,0]),60),
])
def test_calculate_angle_many(p1,p2,p3,expected_angle):
    calculated_value = molecool.calculate_angle(p1,p2,p3, degrees=True)

    assert pytest.approx(expected_angle) == calculated_value


#  Machine Learning Online Class - Exercise 1: Linear Regression

#  Instructions
#  ------------
#
#  This file contains code that helps you get started on the
#  linear exercise. You will need to complete the following modules
#  in this exericse:
#
#     warmUpExercise.py
#     plotData.py
#     computeCost.py
#     gradientDescent.py
#
#  For this exercise, you will not need to change any code in this file,
#  or any other files other than those mentioned above.
#
# x refers to the population size in 10,000s
# y refers to the profit in $10,000s
#

#  Initialization
import numpy as np
import matplotlib.pyplot as plt

from matplotlib import cm
from matplotlib.ticker import LinearLocator
from mpl_toolkits.mplot3d import axes3d

from warmUpExercise import warmUpExercise
from plotData import plotData
from gradientDescent import gradientDescent
from computeCost import computeCost

#  ==================== Part 1: Basic Function ====================
# Complete warmUpExercise.py
print('Running warmUpExercise ...')
print('5x5 Identity Matrix:')
warmUpExercise()

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

#  ======================= Part 2: Plotting =======================
print('Plotting Data ...')
data = np.loadtxt('ex1data1.txt', delimiter=',')
X = data[:, 0]
y = data[:, 1]
m = len(y)  # number of training examples

# Plot Data
# Note: You have to complete the code in plotData.py
plotData(X, y)

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

#  =================== Part 3: Cost and Gradient descent ===================

X = np.c_[np.ones(m), X]  # Add a column of ones to x
theta = np.zeros(2)  # initialize fitting parameters

# Some gradient descent settings
iterations = 1500
alpha = 0.01

print('\nTesting the cost function ...')
# compute and display initial cost
J = computeCost(X, y, theta)
print('With theta = [0  0]\nCost computed = {:.2f}'.format(J))
print('Expected cost value (approx) 32.07')

# further testing of the cost function
J = computeCost(X, y, np.array([-1, 2]))
print('\nWith theta = [-1  2]\nCost computed = {:.2f}'.format(J))
print('Expected cost value (approx) 54.24')

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

print('\nRunning Gradient Descent ...')
# run gradient descent
theta, _ = gradientDescent(X, y, theta, alpha, iterations)

# print theta to screen
print('Theta found by gradient descent:')
print('{}'.format(theta))
print('Expected theta values (approx)')
print('[-3.6303  1.1664]')

# Plot the linear fit
plt.ion()  # keep previous plot visible
plt.plot(X[:, 1], X @ theta, '-')
plt.legend(['Training data', 'Linear regression'], loc='lower right')
plt.ioff()  # dont't overlay any more plots on this figure

# Predict values for population sizes of 35,000 and 70,000
predict1 = np.array([1, 3.5]) @ theta
print('For population = 35,000, we predict a profit of {:.4f}'.format(
    predict1 * 10000))
predict2 = np.array([1, 7]) @ theta
print('For population = 70,000, we predict a profit of {:.4f}'.format(
    predict2 * 10000))

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

#  ============= Part 4: Visualizing J(theta_0, theta_1) =============
print('Visualizing J(theta_0, theta_1) ...')

# Grid over which we will calculate J
theta0_vals = np.linspace(-10, 10, 100)
theta1_vals = np.linspace(-1, 4, 100)

# initialize J_vals to a matrix of 0's
J_vals = np.zeros((len(theta0_vals), len(theta1_vals)))

# Fill out J_vals
for i, theta0_val in enumerate(theta0_vals):
    for j, theta1_val in enumerate(theta1_vals):
        t = np.array([theta0_val, theta1_val])
        J_vals[i, j] = computeCost(X, y, t)

# Because of the way meshgrids work in the surf command, we need to
# transpose J_vals before calling surf, or else the axes will be flipped
J_vals = J_vals.T
theta0_vals, theta1_vals = np.meshgrid(theta0_vals, theta1_vals)
# Surface plot
fig = plt.figure(figsize=(10, 4))
ax = fig.add_subplot(1, 2, 1, projection='3d')
ax.plot_surface(theta0_vals, theta1_vals, J_vals, cmap=cm.jet)
ax.set_xlabel(r'$\theta_0$')
ax.set_ylabel(r'$\theta_1$')
ax.view_init(30, 50)
ax.axis([10, -10, 4, -1])
ax.xaxis.set_major_locator(LinearLocator(5))

# Contour plot
ax = fig.add_subplot(1, 2, 2)
# Plot J_vals as 15 contours spaced logarithmically between 0.01 and 100
ax.contour(theta0_vals, theta1_vals, J_vals, np.logspace(-2, 3, 20))
ax.set_xlabel(r'$\theta_0$')
ax.set_ylabel(r'$\theta_1$')
ax.xaxis.set_major_locator(LinearLocator(11))
ax.yaxis.set_major_locator(LinearLocator(11))
ax.plot(theta[0], theta[1], 'rx', markersize=10, linewidth=2)

plt.tight_layout()
plt.show(block=True)


"""
  Name     : c2_09_bsCall.py
  Book     : Python for Finance (2nd ed.)
  Publisher: Packt Publishing Ltd.
  Author   : Yuxing Yan
  Date     : 6/6/2017
  email    : yany@canisius.edu
             paulyxy@hotmail.com
"""

def bsCall(S,X,T,r,sigma):
    from scipy import log,exp,sqrt,stats
    d1=(log(S/X)+(r+sigma*sigma/2.)*T)/(sigma*sqrt(T))
    d2 = d1-sigma*sqrt(T)
    return S*stats.norm.cdf(d1)-X*exp(-r*T)*stats.norm.cdf(d2)


# Reconstruct SVD
from numpy import array
from numpy import diag
from numpy import dot
from numpy import zeros
from scipy.linalg import svd
# define a matrix
A = array([[1, 2], [3, 4], [5, 6]])
print(A)
# Singular-value decomposition
U, s, VT = svd(A)
# create m x n Sigma matrix
Sigma = zeros((A.shape[0], A.shape[1]))
# populate Sigma with n x n diagonal matrix
Sigma[:A.shape[1], :A.shape[1]] = diag(s)
# reconstruct matrix
B = U.dot(Sigma.dot(VT))
print(B)


import numpy as np

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

class LogisticRegression:
    def __init__(self, eta=0.1, epoch=1000, batch_size=1):
        """
        :param eta:         学习率
        :param epoch:       迭代次数
        :param batch_size:  batch size, 默认随机梯度下降, 如果为None则采用全部训练集
        """
        self.w = None
        self.b = None
        self.eta = eta
        self.epoch = epoch
        self.batch_size = batch_size

    def fit(self, X, y, detailed=False):
        """
        :param X:         输入矩阵, 2维numpy数组
        :param y:         输入标签, 1维numpy数组
        :param detailed:  输出迭代详细信息
        :return:
        """
        if X.shape[0] != y.shape[0]:
            print("输入格式错误")
            return

        if self.batch_size is None:
            self.batch_size = X.shape[0]

        self.w = np.ones(X.shape[1])
        self.b = np.ones(1)
        num_of_batch = X.shape[0] // self.batch_size
        if X.shape[0] % self.batch_size != 0:
            num_of_batch += 1

        for i in range(self.epoch):
            for j in range(num_of_batch):
                start = j * self.batch_size
                end = min((j + 1) * self.batch_size, X.shape[0])
                error = self.probability(X[start : end, :]) - y[start : end]
                error = error.reshape([-1, 1])
                delta_w = np.sum(error * X[start : end, :], axis = 0)
                delta_b = np.sum(error)
                self.w -= self.eta * delta_w
                self.b -= self.eta * delta_b
            if detailed:
                print("epoch:", i, " w:", self.w, " b:", self.b)

    def probability(self, x):
        return sigmoid(np.sum(self.w * x, axis=-1) + self.b)

    def predict(self, x):
        if self.probability(x) > 0.5:
            return 1
        else:
            return 0


from jet20 import Problem,quad
import numpy as np
import pytest

def test_lp_basic():
    p = Problem("test")
    x1,x2,x3,x4 = p.variables("x1,x2,x3,x4")

    p.minimize(2 * x1 + 3 * x2 + x3 + 5 * x4)

    p.constraints(x1 + x4 >= 1)
    p.constraints(x2 + x4 >= 1)
    p.constraints(x1 + x2 == 1)
    p.constraints(x2 + x3 == 1)

    p.constraints(x1 >= 0)
    p.constraints(x2 >= 0)
    p.constraints(x3 >= 0)
    p.constraints(x4 >= 0)

    print ("p.canonical:",p.canonical)

    solution = p.solve(device="cpu")
    print (solution)

    assert solution.obj_value == 5.5
    assert (solution.x == np.array([0.5,0.5,0.5,0.5])).all()

def test_lp_basic2():
    p = Problem("test")
    x1,x2,x3,x4 = p.variables("x1,x2,x3,x4",lb=0)

    p.minimize(2 * x1 + 3 * x2 + x3 + 5 * x4)

    p.constraints(x1 + x4 >= 1,
                x2 + x4 >= 1,
                x1 + x2 == 1,
                x2 + x3 == 1)

    print ("p.canonical:",p.canonical)
    solution = p.solve(device="cpu")
    print (solution)

    assert solution.obj_value == 5.5
    assert (solution.x == np.array([0.5,0.5,0.5,0.5])).all()

def test_lp_basic3():
    A1 = np.array([ [1,0,0,1],
                    [0,1,0,1]  ])
    b1 = 1

    A2 = np.array([ [1,1,0,0],
                    [0,1,1,0]  ])
    b2 = np.array([1,1])

    c = np.array([2,3,1,5])

    p = Problem("test")
    xs = p.variables("x1,x2,x3,x4",lb=0)

    p.minimize(c @ xs)
    p.constraints(A1 @ xs >= b1,
                A2 @ xs == b2)

    solution = p.solve(device="cpu")
    print (solution)

    assert solution.obj_value == 5.5
    assert (solution.x == np.array([0.5,0.5,0.5,0.5])).all()

def test_qp_basic1():
    p = Problem("test")
    x1,x2,x3,x4 = p.variables("x1,x2,x3,x4",lb=0)

    p.minimize(2*x1**2 + 3*x2**2 + x3**2 + 5*x4**2 + x1*x2 + 2*x2*x3 + 4*x1*x4)
    p.constraints(x1 + x4 >= 1,
                x2 + x4 >= 1,
                x1 + x2 == 1,
                x2 + x3 == 1)

    solution = p.solve(device="cpu")
    print (solution)
    assert solution.obj_value == 4.5

def test_qp_basic2():
    np.random.seed(42)
    A1 = np.array([ [1,0,0,1],
                    [0,1,0,1]  ])
    b1 = 1
    A2 = np.array([ [1,1,0,0],
                    [0,1,1,0]  ])
    b2 = np.array([1,1])
    c = np.array([2,3,1,5])
    Q = np.random.randn(4,4)
    Q = Q.T @ Q

    p = Problem("test")
    xs = p.variables("x1,x2,x3,x4",lb=0)
    p.minimize(quad(Q,xs) + c @ xs)
    p.constraints(A1 @ xs >= b1,
                A2 @ xs == b2)

    solution = p.solve(device="cpu",opt_tolerance=1e-8,opt_constraint_tolerance=1e-8,rouding_precision=3)
    print (solution)
    print (solution.obj_value)
    print (solution.vars)
    print (solution.status)

    assert solution.obj_value <= 12.081916655


from scipy.sparse.linalg import eigsh
from numpy.linalg import eigh
import numpy as np

import time
import json

def laplacian(A):
    return np.diag(A.sum(axis=0)) - A

def normalized_laplacian(A):
    L = laplacian(A)
    D = np.diag(1 / np.diagonal(L))
    return np.sqrt(D) @ L @ np.sqrt(D)

def first_k_eigenvectors_sparse(M, k, which='SM'):
    return eigsh(M, k, which=which)[1]

def first_k_eigenvectors(M, k):
    eig_val, eig_vec = eigh(M)
    return np.fliplr(eig_vec[:, :k])

def spectral_embeddings(matrix, k, measure_time=False, not_sparse=False):
    if not_sparse:
        sparse_length = 10000000000000000
    else:
        start_sparse = time.time()
        try:
            eigv_sparse = first_k_eigenvectors_sparse(matrix, k)
            end_sparse = time.time()
        except Exception as e:
            end_sparse = time.time() + 10000000000000000
            print(e)
        sparse_length = end_sparse - start_sparse

    matrix_dense = matrix.todense()
    start_full = time.time()
    eigv_full = first_k_eigenvectors(matrix_dense, k)
    end_full = time.time()
    full_length = end_full - start_full

    if measure_time:
        return (eigv_sparse, sparse_length, 'Sparse') if sparse_length < full_length else (eigv_full, full_length, 'Full')
    else:
        return eigv_sparse if sparse_length < full_length else eigv_full

def save_json(obj, path):
    with open(path, 'w') as _file:
        json.dump(obj, _file)

def load_json(path):
    with open(path) as _file:
        return json.load(_file)


from sympy import *
from numpy import *

x = Symbol('x')

ponto_x = 2
# Pn -> Polinomio de grau n
vet_x = array([ 1.2, 1.5])
vet_y = array([1.928, 3.875])

def L(vet_x, vet_y, ponto_x):
    l = list()
    aux_Numerado = 1
    aux_den = 1
    for k in range(len(vet_x)):
        for j in range(len(vet_x)):
            if k != j:
                aux_Numerado *= float(ponto_x - vet_x[j])
        for j in range(len(vet_x)):
            if k != j:
                aux_den *= float(vet_x[k] - vet_x[j])
        l.append(aux_Numerado / aux_den)
        aux_Numerado = 1
        aux_den = 1
    return l

l = L(vet_x, vet_y, ponto_x)
aux = 0
for k in range(len(vet_y)):
     aux += (vet_y[k] * l[k])

print(f"Resp: {aux}")

# qual e a funcao de cada L
aux_Numerado = 1
aux_den = 1
l = list()
for k in range(len(vet_x)):
    for j in range(len(vet_x)):
        if k != j:
            aux_Numerado *= (x - vet_x[j])
    for j in range(len(vet_x)):
        if k != j:
            aux_den *= (vet_x[k] - vet_x[j])
    l.append(aux_Numerado / aux_den)
    aux_Numerado = 1
    aux_den = 1

#print(l[1]) # Aqui da a funcao de cada L[n]


"""The 1D heat model."""

import numpy as np
import yaml

def solve_1d(temp, spacing, k=2e3, kappa=1e-6, Qm=0.05, time_step=1.0):
    """Solve the 1D Heat Equation in a column.

    Parameters
    ----------
    temp : ndarray
        Temperature.
    spacing : array_like
        Grid spacing in the row and column directions.
    k : float, optional
        Thermal conductivity. Default is 2e3.
    kappa : float, optional
        Thermal diffusivity. Default is 1e-6.
    Qm : float, optional
        Heat flux. Default is 0.05.
    time_step : float (optional)
        Time step.

    Returns
    -------
    result : ndarray
        The temperatures after time *time_step*.

    Examples
    --------
    >>> import numpy as np
    >>> from heat import solve_1d
    >>> spacing = [100]
    >>> dTdz = 0.01
    >>> temp = dTdz * np.arange(0, 1000, spacing[0])
    >>> temp
    array([0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
    >>> for i in range(10*365):
    ...     temp = solve_1d(temp, spacing, time_step=24*60*60)
    >>> temp
    array([0.        , 1.        , 2.        , 3.        , 4.        ,
           5.        , 5.99999996, 6.99999499, 7.99951939, 8.9690285 ])

    This is good, we expect it to cool.
    """
    Q = np.zeros_like(temp)
    dTdz = np.diff(temp) / spacing[0]

    Q[:-1] = - k * dTdz
    Q[-1] = - Qm
    dTdt = (-kappa/k) * np.diff(Q)/spacing[0]

    temp[1:] += dTdt * time_step

    return temp

class Heat(object):

    """Solve the Heat equation on a grid.

    Examples
    --------
    >>> heat = Heat()
    >>> heat.time
    0.0
    >>> heat.time_step
    250000.0
    >>> heat.advance_in_time()
    >>> heat.time
    250000.0

    >>> heat = Heat(shape=(5,))
    >>> heat.temperature = np.zeros_like(heat.temperature)
    >>> heat.temperature[2] = 1.
    >>> heat.advance_in_time()

    >>> heat = Heat(kappa=.5)
    >>> heat.time_step
    0.5
    >>> heat = Heat(kappa=.5, spacing=(2.,))
    >>> heat.time_step
    2.0
    """

    def __init__(
        self,
        shape=(110,),
        spacing=(1.0,),
        origin=(0.0,),
        kappa=1e-6,
        k=2e3,
        Qm=0.05,

    ):
        """Create a new heat model.

        Paramters
        ---------
        shape : array_like, optional
            The shape of the solution grid as (*rows*, *columns*).
        spacing : array_like, optional
            Spacing of grid rows and columns.
        origin : array_like, optional
            Coordinates of lower left corner of grid.
        k : float, optional
            Thermal conductivity. Default is 2e3.
        kappa : float, optional
            Thermal diffusivity. Default is 1e-6.
        Qm : float, optional
            Heat flux. Default is 0.05.
        """
        self._shape = shape
        self._spacing = spacing
        self._origin = origin
        self._time = 0.0
        self._kappa = kappa
        self._k = k
        self._Qm = Qm
        self._time_step = min(spacing) ** 2 / (4.0 * self._kappa)
        self._temperature = np.random.random(self._shape)

    @property
    def time(self):
        """Current model time."""
        return self._time

    @property
    def temperature(self):
        """Temperature of the plate."""
        return self._temperature

    @temperature.setter
    def temperature(self, new_temp):
        """Set the temperature of the plate.

        Parameters
        ----------
        new_temp : array_like
            The new temperatures.
        """
        self._temperature[:] = new_temp

    @property
    def time_step(self):
        """Model time step."""
        return self._time_step

    @time_step.setter
    def time_step(self, time_step):
        """Set model time step."""
        self._time_step = time_step

    @property
    def spacing(self):
        """Shape of the model grid."""
        return self._spacing

    @property
    def origin(self):
        """Origin coordinates of the model grid."""
        return self._origin

    @classmethod
    def from_file_like(cls, file_like):
        """Create a Heat object from a file-like object.

        Parameters
        ----------
        file_like : file_like
            Input parameter file.

        Returns
        -------
        Heat
            A new instance of a Heat object.
        """
        config = yaml.safe_load(file_like)
        return cls(**config)

    def advance_in_time(self):
        """Calculate new temperatures for the next time step."""
        self._temperature[:] = solve_1d(
            self._temperature,
            self._spacing,
            time_step=self._time_step,
            k=self._k, kappa=self._kappa, Qm=self._Qm,
        )
        self._time += self._time_step


import math
import numpy as np

def bound(value, lower, upper):
    """ Clips off a value which exceeds the lower or upper bounds. """
    if value < lower:
        return lower
    elif value > upper:
        return upper
    else:
        return value

def bound_vector(vect, maximum):
    """ Bounds a vector between a negative and positive maximum range. """
    xval = bound(vect[0], -maximum, maximum)
    yval = bound(vect[1], -maximum, maximum)
    return np.array([xval, yval])

def angle_difference(angle1, angle2):
    """ Computes the real difference between angles. """
    return norm_angle(angle1 - angle2)

def norm_angle(angle):
    """ Normalize the angle between -pi and pi. """
    while angle > np.pi:
        angle -= 2*np.pi
    while angle < -np.pi:
        angle += 2*np.pi
    return angle

def angle_close(angle1, angle2):
    """ Determines whether an angle1 is close to angle2. """
    return abs(angle_difference(angle1, angle2)) < np.pi/8

def angle_between(pos1, pos2):
    """ Computes the angle between two positions. """
    diff = pos2 - pos1
    # return np.arctan2(diff[1], diff[0])
    return math.atan2(diff[1], diff[0])  # faster than numpy

def angle_position(theta):
    """ Computes the position on a unit circle at angle theta. """
    return vector(np.cos(theta), np.sin(theta))

def vector(xvalue, yvalue):
    """ Returns a 2D numpy vector. """
    # return np.array([float(xvalue), float(yvalue)])
    return np.array([xvalue, yvalue], dtype=np.float64)

def vector_to_tuple(vect):
    """ Converts a numpy array to a tuple. """
    assert len(vect) == 2
    return (vect[0], vect[1])
    # return tuple(map(tuple, vect))