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

#For FIR Filter in section 3

U = np.linspace(-np.pi,np.pi,1000)
V = np.linspace(-np.pi,np.pi,1000)
u,v = np.meshgrid(U,V)
H = 1/81*(1 + np.cos(4*v) + 2*np.cos(3*v) + 2*np.cos(2*v) + 2*np.cos(v)) * (1 + np.cos(4*u) + 2*np.cos(3*u) + 2*np.cos(2*u) + 2*np.cos(u))
H_mag = np.abs(H)
fig = plt.figure()
ax = plt.axes(projection='3d')
surf = ax.plot_surface(u, v, H_mag,cmap='viridis', edgecolor='none')
ax.set_xlabel('u')
ax.set_ylabel('v')
ax.set_zlabel('|H|')
fig.colorbar(surf,location = 'left')
plt.savefig('./output/python_FIR_3.tif')
plt.savefig('./output/python_FIR_3.png')
#plt.show()

#For FIR Filter in section 4
U = np.linspace(-np.pi,np.pi,1000)
V = np.linspace(-np.pi,np.pi,1000)
u,v = np.meshgrid(U,V)
H = 1/25*(1 + 2*np.cos(2*v) + 2*np.cos(v)) * (1 + 2*np.cos(2*u) + 2*np.cos(u))
H_mag = np.abs(H)
fig = plt.figure()
ax = plt.axes(projection='3d')
surf = ax.plot_surface(u, v, H_mag,cmap='viridis', edgecolor='none')
ax.set_xlabel('u')
ax.set_ylabel('v')
ax.set_zlabel('|H|')
fig.colorbar(surf,location = 'left')
plt.savefig('./output/python_FIR_4.tif')
plt.savefig('./output/python_FIR_4.png')
#plt.show()

#For FIR Filter, sharpening filter, in section 4
L = 1.5#lambda
G = 1+L*(1-H)
G_mag = np.abs(G)
fig = plt.figure()
ax = plt.axes(projection='3d')
surf = ax.plot_surface(u, v, G_mag,cmap='viridis', edgecolor='none')
ax.set_xlabel('u')
ax.set_ylabel('v')
ax.set_zlabel('|G|')
fig.colorbar(surf,location = 'left')
plt.savefig('./output/python_FIR_4_sharpening.tif')
plt.savefig('./output/python_FIR_4_sharpening.png')
#plt.show()

#For IIR Filter, in section 5
U = np.linspace(-np.pi,np.pi,1000)
V = np.linspace(-np.pi,np.pi,1000)
u,v = np.meshgrid(U,V)
H = 0.01/(1-0.9*(np.exp(-1j*u)+np.exp(-1j*v)) + 0.81*np.exp(-1j*(u+v)))
H_mag = np.abs(H)
fig = plt.figure()
ax = plt.axes(projection='3d')
surf = ax.plot_surface(u, v, H_mag,cmap='viridis', edgecolor='none')
ax.set_xlabel('u')
ax.set_ylabel('v')
ax.set_zlabel('|H|')
fig.colorbar(surf,location = 'left')
plt.savefig('./output/python_IIR.tif')
plt.savefig('./output/python_IIR.png')
#plt.show()


'''
This script performs an A/B test on two groups, where the groups are
evaluated by some metric = distinct(devices that passed) / distinct(total devices)
where `bool passed` is a binomial random variable. The outcomes of both groups
in the experiment are simulated with some noise factor in order to be able to
observe both True and False rejection of the null hypothesis (i.e. no
statistically significant difference between the control and test groups).

With reference to: https://classroom.udacity.com/courses/ud257
'''

from random import random
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

# p_*: experiment metric: probability of binomial outcome
# as N_* gets large binomial distribution ~ normal distribution

# helper functions
p_noise = lambda p, noise: min(0.99, max(0.01, p + (random() - 0.5) * noise))
p_gen = lambda p, N: np.random.binomial(1, p, size=N)
margin_of_error = lambda z, stderr: z * stderr
confidence_interval = lambda p, moe: (p - moe, p + moe)

# simulation constants
noise = 0.1
p = random()
N = 2000
Z = {95: 1.96, 99: 2.33} # {percentile: number of std dev}
z_exp = 95 # set the confidence interval we want for this experiment

# simulate the control set
p_cont = p_noise(p, noise)
N_cont = N
stderr_cont = ((p_cont * (1 - p_cont)) / N_cont) ** (0.5)
moe_cont = margin_of_error(Z[z_exp], stderr_cont)
conf_cont = confidence_interval(p_cont, moe_cont)

# Run experiment N times of N samples
# TODO: rewrite using matrix math
set_cont = []
for _ in range(N_cont):
    set_temp = p_gen(p_cont, N_cont)
    set_cont.append(np.average(set_temp))
set_cont = np.array(set_cont)

# test set
p_test = p_noise(p, noise)
N_test = N
stderr_test = ((p_test * (1 - p_test)) / N_test) ** (0.5)
moe_test = margin_of_error(Z[z_exp], stderr_test)
conf_test = confidence_interval(p_test, moe_test)

# Run experiment N times of N samples
# TODO: rewrite using matrix math
set_test = []
for _ in range(N_test):
    set_temp = p_gen(p_test, N_test)
    set_test.append(np.average(set_temp))
set_test = np.array(set_test)

# pooled probabilities (gaussian mixture)
p_pool = ((p_cont * N_cont) + (p_test * N_test)) / (N_cont + N_test)
stderr_pool = (p_pool * (1 - p_pool) * (1/N_cont + 1/N_test)) ** 0.5
p_diff = abs(p_test - p_cont) # null hypothesis states that p_diff == 0
moe_pool = margin_of_error(Z[z_exp], stderr_pool)

# evaluate experiment results
print("\nReject null hypothesis: {}".format(p_diff > moe_pool))

# visualize results of both groups
sns.set_palette("Paired")
fig, ax = plt.subplots()
for ab in [set_cont, set_test]:
    sns.distplot(ab, ax=ax, kde=True)


import numpy as np
from numpy.core.numeric import Inf
import timeit
from scipy.optimize import linprog

# for keeping track of the computatoin time of each step, I record times in a series of arrays
enteringArray = []
exitingArray = []
basisArray = []

# this is a random seed, so as we would produce similar random numbers for the sake of traceablity
#np.random.seed(1)

def random_problem(m, n):
    A = np.concatenate((np.random.randint(2, size=(m, n)), np.identity(m)), axis=1)
    b = np.ones(shape=(m, 1))
    c = np.concatenate((np.ones(shape=(1, n)), np.random.randint(5000000, 5000001, size=(1, m))), axis=1)
    basis = [i for i in range(n, m+n)]
    nonBasis = [i for i in range(0, n)]
    B = A[:, basis]
    N = A[:, nonBasis]
    return A, b, c, basis, nonBasis, B, N

# random problem
rows = 4
columns = 14

# produce a random problem
A, b, c, basis, nonBasis, B, N = random_problem(rows, columns)

# problem defined in our assignment
# A = np.array([[1,0,1,0,0, 1, 0, 0],[0,1,1,1,0,0,1,0],[1,1,0,0,1,0,0,1]])
# b = np.array([[1],[1],[1]])
# c = np.array([[1, 1, 1, 1, 1, 50000000,50000000,50000000]])
# basis = [5,6,7]
# nonBasis = [0,1,2,3,4]
# B = np.array([[1,0,0],[0,1,0],[0,0,1]])
# N = np.array([[1,0,1,0,0], [0,1,1,1,0], [1,1,0,0,1]])

# solve using linprog
startTime = timeit.default_timer()
print(linprog(c, A_eq=A, b_eq=b, method='revised simplex'))
endTime = timeit.default_timer()
print(f"took: {endTime - startTime}")

def entering(c, B, basis, nonBasis, N):
    startEntering = timeit.default_timer()
    status = 0
    cN = c[:, nonBasis]
    cB = c[:, basis]
    reducedCosts = cN - np.matmul(np.matmul(cB, np.linalg.inv(B)), N)
    en = np.argmin(reducedCosts)
    if (reducedCosts[0][en] >= 0):
        status = 1
        endEntering = timeit.default_timer()
        t = endEntering - startEntering
        enteringArray.append(t)
        return status, en, cB, cN, t
    else:
        endEntering = timeit.default_timer()
        t = endEntering - startEntering
        enteringArray.append(t)
        return status, en, cB, cN, t

def exiting(b, B, N, enteringIndex, ):
    startExiting = timeit.default_timer()
    status = 0
    ratioTest = []
    Bb = np.matmul(np.linalg.inv(B), b)
    Ai = np.matmul(np.linalg.inv(B), N[:, enteringIndex])
    for i in range(len(Bb)):
        if (Ai[i]<0):
            ratioTest.append(Inf)
        elif (Ai[i] == 0):
            ratioTest.append(1000000)
        else:
            sth = Bb[i]/Ai[i]
            ratioTest.append(sth[0])
    ratioTest = np.array(ratioTest)
    ex = np.argmin(ratioTest)
    if (ratioTest[ex]>=0):
        endExiting = timeit.default_timer()
        t = endExiting - startExiting
        exitingArray.append(t)
        return status, ex, t
    else:
        status = 3
        endExiting = timeit.default_timer()
        t = endExiting - startExiting
        exitingArray.append(t)
        return status, ex, t

def change_basis(A, en, ex, basis, nonBasis):
    startBasis = timeit.default_timer()
    enteringValue = nonBasis[en]
    nonBasis[en] = basis[ex]
    basis[ex] = enteringValue
    B = A[:, basis]
    N = A[:, nonBasis]
    endBasis = timeit.default_timer()
    t = endBasis - startBasis
    basisArray.append(t)
    return B, N, basis, nonBasis, t

def checkInfeasblity(x, basis, rows, columns):
    for i in range(rows,rows+columns):

        if (i in basis):
            inde = basis.index(i)
            if (x[inde][0] == 0):
                continue
            else:
                return "True"
        else:
            continue
    return "False"

### main body

status = 0
justEntered = 6666

startTime = timeit.default_timer()
while (status != 1):
    status, enteringIndex, cB, cN, t = entering(c, B, basis, nonBasis, N)
    if (status == 1):
        print("Optimal")
        print(f"Function: {np.matmul(np.matmul(cB, np.linalg.inv(B)), b)}")
        x = np.matmul(np.linalg.inv(B), b)
        print(f"x: {x}")
    else:
        status, exitingIndex, t = exiting(b, B, N, enteringIndex)
        B, N, basis, nonBasis, t = change_basis(A, enteringIndex, exitingIndex, basis, nonBasis)

endTime = timeit.default_timer()
print(f"Took: {endTime - startTime}")
print(f"This problem's infeasbility: {checkInfeasblity(x, basis, rows, columns)}")
if (status == 3):
    print(f"This problem's unboundedness: True")
print(f"Average entering: {np.average(enteringArray)}")
print(f"Average exiting: {np.average(exitingArray)}")
print(f"Average changing basis: {np.average(basisArray)}")


# -*- coding: utf-8 -*-
"""Demo160_Distributions.ipynb

## Variable distributions and their effects on Models

Reference
[https://www.statisticssolutions.com/homoscedasticity/]

### Linear Regression Assumptions

- Linear relationship with the outcome Y
- Homoscedasticity
- Normality
- No Multicollinearity

## Linear Assumption

- The X variable is linearly related to the dataset
- Pearson correlation coefficient can determine the linearity magnitude  between variables

## Normality Assumption

- The variable X follows a normal or gaussian distribution

## Homoscedasticity Assumption

- Homogeneity of variance

- Homoscedasticity describes a situation in which the error term (that is, the “noise” or random disturbance in the relationship between the independent variables and the dependent variable) is the same across all values of the independent variables

### Unaffected models

- Neural Networks
- Support Vector Machines
- Trees
"""

import matplotlib.pyplot as plt
import numpy as np
import math
import seaborn as sns
sns.set()
import pandas as pd

import scipy.stats as stats
mu = 0
variance = 1
sigma = math.sqrt(variance)
x = np.linspace(mu - 5*sigma, mu + 5*sigma, 100)
plt.plot(x, stats.norm.pdf(x, mu, sigma))
plt.show()

mu = 0
variance = 1
sigma = math.sqrt(variance)
x = np.linspace(mu - 5*sigma, mu + 5*sigma, 1000)
alpha = 0
y = stats.skewnorm.pdf(x, alpha)
sns.lineplot(x,y)

mu = 0
variance = 1
sigma = math.sqrt(variance)
x = np.linspace(mu - 5*sigma, mu + 5*sigma, 1000)
alpha = 2
y = stats.skewnorm.pdf(x, alpha)
sns.lineplot(x,y)

mu = 0
variance = 1
sigma = math.sqrt(variance)
x = np.linspace(mu - 5*sigma, mu + 5*sigma, 1000)
alpha = -2
y = stats.skewnorm.pdf(x, alpha)
sns.lineplot(x,y)

mu = 0
variance = 1
sigma = math.sqrt(variance)
x = np.linspace(mu - 5*sigma, mu + 5*sigma, 1000)
fig, ax = plt.subplots()
y = stats.skewnorm.pdf(x, 0)
sns.lineplot(x,y, ax=ax)
y = stats.skewnorm.pdf(x, 3)
sns.lineplot(x,y, ax=ax)
y = stats.skewnorm.pdf(x, -3)
sns.lineplot(x,y, ax=ax)
ax.legend(["Normal", "Negative Skew", "Positive Skew"])

"""## Let's explore the distributions of the variables"""

from google.colab import drive
drive.mount('/content/gdrive')
data = pd.read_csv("gdrive/My Drive/Colab Notebooks/FeatureEngineering/train_date.csv")

data.head()

"""#### Age"""

sns.set_color_codes()
sns.distplot(data['Age'], color='r')

"""Make a QQ-plot:

- Quantiles on vertical axis
- Quantiles of distributin on horizontal axis
- The points on the graph will form a 45 degree line if gaussian
"""

fig, ax = plt.subplots()
stats.probplot(data['Age'].dropna(), dist="norm", plot=ax)
ax.set_title("QQPlot")

sns.set_color_codes()
sns.distplot(data['Age']**(1/1.5), color='g')

fig, ax = plt.subplots()
stats.probplot(data['Age'].dropna()**(1/1.5), dist="norm", plot=ax)
ax.set_title("QQPlot")

"""#### Fare"""

sns.set_color_codes()
sns.distplot(data['Fare'], color='r')

"""Make a QQ-plot:

- Quantiles on vertical axis
- Quantiles of distributin on horizontal axis
- The points on the graph will form a 45 degree line if gaussian
"""

fig, ax = plt.subplots()
stats.probplot(data['Fare'].dropna(), dist="norm", plot=ax)
ax.set_title("QQPlot")

sns.set_color_codes()
sns.distplot(data['Fare']**(1/3), color='g')

fig, ax = plt.subplots()
stats.probplot(data['Fare'].dropna()**(1/1.5), dist="norm", plot=ax)
ax.set_title("QQPlot")


import numpy as np
import numpy.random as rnd
import matplotlib.pyplot as plt

r2 = 1 ## r^2 (radius squared)
#n = int(1e5) ## number of elements
n = 10545437.0

## seed PRNG (for debugging)
#print resarsaencfunc()
k = 5
#resarsaencfunc()
#429496729
rnd.seed(k)

## generate random numbers

Xr = rnd.rand(n)

Yr = rnd.rand(n)

Len = rnd.rand(n)

m = 0.

pi = 0.

#print Xr

#print Yr

## calculate whether or not your x, y values are in the circle

for i in range (0,len(Len)):

    if Xr[i]**2 + Yr[i]**2 < r2:
      m = m+1
      pi = 4*(m/n)

## print your approximation of pi

print pi

## optional task: create a scatter plot of the points inside the circle vs the
## points outside of the circle;
## need to use np.where() for this to get the indices
## use plt.scatter() to plot


import re
import numpy as np
from math import log, sqrt
from tqdm import tqdm

def alpha_costant(m):
    if m == 16:
        return 0.673
    if m == 32:
        return 0.697
    if m == 64:
        return 0.709

    if m >= 128:
        return 0.7213/(1+1.079/m)

def leftmost_one(bits):

    result = re.search('0*1', bits)

    return result.end(0)

def get_bucket_size(e):

    m_1 = (1.04/e)**2
    k = int(log(m_1, 2)) + 1

    return 2**k, k

def HyperLogLog(stream, error):

    m, k = get_bucket_size(error)

    M = np.zeros(m, 'd')

    for x in tqdm(stream):

        x = x[:-1]

        j = int(x[:k], 2)

        M[j] = max(M[j], leftmost_one(x[k:]))

    E = alpha_costant(m)*(m**2)/sum(2**(-M))

    # corrections
    if E <= (5/2)*m:

        if 0 in M:
            E = m*log(m/sum(M == 0), 10) # small range correction

            return E, E*1.04/sqrt(m)

        else:
            return E, E*1.04/sqrt(m)

    if E <= (2**64)/30:

        return E, E*1.04/sqrt(m) # no correction

    if E > (2**64)/30:

        E = -(2**64)*log(1-E/(2**64)) # large range correction

        return E, E*1.04/sqrt(m)


# TODO: Check correctness of stationary distribution

from typing import Generic, Mapping, Sequence
import numpy as np
from scipy import linalg
# use math.fsum() instead of sum to get rid of rounding error
import math

from src.type_vars import S, SSf
from src.utils import get_all_states

# Note: if using relative path import like "from ..type_vars import S, SSf", then mp.py should be invoked using -m
# option (https://napuzba.com/a/import-error-relative-no-parent/p4)

class MP(Generic[S]):
    def __init__(self, state_transition_matrix: SSf) -> None:
        try:
            valid: bool = self.check_if_valid(state_transition_matrix)
            if not valid:
                raise ValueError()
        except ValueError:
            exit('Input is not a valid Markov Process')

        self.all_states: Sequence[S] = get_all_states(state_transition_matrix)
        self.state_transition_matrix: SSf = state_transition_matrix

    def check_if_valid(self, state_transition_matrix: SSf) -> bool:
        for s1 in state_transition_matrix:
            if math.fsum(state_transition_matrix[s1].values()) != 1.0:
                return False
            for s2 in state_transition_matrix[s1].keys():
                if state_transition_matrix.get(s1).get(s2) < 0 or state_transition_matrix.get(s1).get(s2) > 1:
                    return False
        return True

    def get_stationary_distribution(self) -> Mapping[S, float]:
        sz = len(self.all_states)
        P = np.zeros((sz, sz), dtype=float)
        # construct state transition matrix as a 2-d np array
        for i, s1 in enumerate(self.all_states):
            for j, s2 in enumerate(self.all_states):
                if self.state_transition_matrix.get(s1) is not None:
                    if self.state_transition_matrix[s1].get(s2) is not None:
                        P[i][j] = self.state_transition_matrix[s1][s2]
        # find a row vector v s.t. v * P = v && sum(v) == 1 by solving an overdetermined linear equation
        # ref: https://stephens999.github.io/fiveMinuteStats/stationary_distribution.html
        a = np.concatenate((np.transpose(P) - np.identity(sz), np.ones((1, sz))), axis=0)
        b = np.concatenate((np.zeros((sz, 1)), np.ones((1, 1))), axis=0)
        x = linalg.lstsq(a, b)[0]
        return {s: x[i][0].astype(float) for i, s in enumerate(self.all_states)}

if __name__ == '__main__':
    state_transition_matrix = {
        1: {1: 0.1, 2: 0.6, 3: 0.1, 4: 0.2},
        2: {1: 0.25, 2: 0.22, 3: 0.24, 4: 0.29},
        3: {1: 0.7, 2: 0.3},
        4: {1: 0.3, 2: 0.5, 3: 0.2}
    }
    mp_obj = MP(state_transition_matrix)
    print(mp_obj.all_states)
    print(mp_obj.get_stationary_distribution())


import numpy as np

def rectangles(interval, f, h):
    a, b = interval
    x = np.linspace(a, b, int((b - a) / h + 1))

    integral = h * np.sum([f((i + j) / 2) for i, j in zip(x, x[1:])])

    return integral

def trapezium(interval, f, h):
    a, b = interval
    x = np.linspace(a, b, int((b - a) / h + 1))
    y = [f(i) for i in x]
    integral = h * sum([i + j for i, j in zip(y[1:], y)]) / 2

    return integral

def simpson(interval, f, size, h):
    a, b = interval
    n = int((b - a) / h + 1)

    first_part = 4 * np.sum([f(a + i * h) for i in range(1, n, 2)])
    second_part = 2 * np.sum([f(a + i * h) for i in range(2, n - 1, 2)])
    integral = h * (f(a) + first_part + second_part + f(b)) / 3

    return integral

def read_interval():
    return list(map(float, input().split()))

def read_h():
    return list(map(float, input().split()))

def runge_romberg(I_h, I_2h, r, p):
    return (I_h - I_2h) / (1  - r ** p)

def main():
    wolfram_val = 0.1454
    interval = read_interval()
    h_series = read_h()
    r = h_series[-1] / h_series[0]
    n = 2
    func = lambda x: x ** 2 / (x ** 2 + 16)
    rect = []
    trap = []
    simp = []

    for h in h_series:
        rectangles_val = rectangles(interval, func, h)
        rect.append(rectangles_val)
        trapezium_val = trapezium(interval, func, h)
        trap.append(trapezium_val)
        simpson_val = simpson(interval, func, n, h)
        simp.append(simpson_val)

    print(f'h = {h_series[0]}\t{r}h = {h_series[-1]}')
    print('rectangles:')
    print(rect, runge_romberg(rect[0], rect[-1], r, n))
    print("trapezium:")
    print(trap, runge_romberg(trap[0], trap[-1], r, n))
    print("simpson:")
    print(simp, runge_romberg(simp[0], simp[-1], r, n))
    print(f'analytical val = {wolfram_val}')
main()


#/usr/bin/env python3
import numpy as np
from numpy import ndarray
from scipy.stats import norm

def condition_mean(x: ndarray, P: ndarray,
                   z: ndarray, R: ndarray, H: ndarray) -> ndarray:
    """compute conditional mean

    Args:
        x (ndarray): initial state
        P (ndarray): initial state covariance
        z (ndarray): measurement
        R (ndarray): measurement covariance
        H (ndarray): measurement matrix i.e. z = H @ x + error

    Returns:
        cond_mean (ndarray): conditioned mean (state)
    """
    return x + P@H.T@np.linalg.inv((H@P@H.T + R))@(z - H@x)

def condition_cov(P: ndarray, R: ndarray, H: ndarray) -> ndarray:
    """compute conditional covariance

    Args:
        P (ndarray): covariance of state estimate
        R (ndarray): covariance of measurement
        H (ndarray): measurement matrix

    Returns:
        ndarray: the conditioned covariance
    """
    return P - P@H.T@np.linalg.inv(H@P@H.T + R)@H@P

def get_task_2f(x_bar: ndarray, P: ndarray,
                z_c: ndarray, R_c: ndarray, H_c: ndarray,
                z_r: ndarray, R_r: ndarray, H_r: ndarray
                ):
    """get state estimates after receiving measurement c or measurement r

    Args:
        x_bar (ndarray): initial state estimate
        P (ndarray): covariance of x_bar
        z_c (ndarray): measurement c
        R_c (ndarray): covariance of measurement c
        H_c (ndarray): measurement matrix i.e. z_c = H_c @ x + error
        z_r (ndarray): measurement r
        R_r (ndarray): covariance of measurement r
        H_r (ndarray): measurement matrix i.e. z_r + H_c @ x + error

    Returns:
        x_bar_c (ndarray): state estimate after measurement c
        P_c (ndarray): covariance of x_bar_c
        x_bar_r (ndarray): state estimate after measurement r
        P_r (ndarray): covariance of x_bar_r
    """
    x_bar_c = condition_mean(x_bar, P, z_c, R_c, H_c)
    P_c     = condition_cov(P, R_c, H_c)

    x_bar_r = condition_mean(x_bar, P, z_r, R_r, H_r)
    P_r     = condition_cov(P, R_r, H_r)

    return x_bar_c, P_c, x_bar_r, P_r

def get_task_2g(x_bar_c: ndarray, P_c: ndarray,
                x_bar_r: ndarray, P_r: ndarray,
                z_c: ndarray, R_c: ndarray, H_c: ndarray,
                z_r: ndarray, R_r: ndarray, H_r: ndarray):
    """get state estimates after receiving measurement c and measurement r

    Args:
        x_bar_c (ndarray): state estimate after receiving measurement c
        P_c (ndarray): covariance of x_bar_c
        x_bar_r (ndarray): state estimate after receiving measurement r
        P_r (ndarray): covariance of x_bar_r
        z_c (ndarray): measurement c
        R_c (ndarray): covariance of measurement c
        H_c (ndarray): measurement matrix i.e. z_c = H_c @ x + error
        z_r (ndarray): measurement r
        R_r (ndarray): covariance of measurement r
        H_r (ndarray): measurement matrix i.e. z_r = H_r @ x + error

    Returns:
        x_bar_cr (ndarray): state estimate after receiving z_c then z_r
        P_cr (ndarray): covariance of x_bar_cr
        x_bar_rc (ndarray): state estimate after receiving z_r then z_c
        P_rc (ndarray): covariance of x_bar_rc
    """
    x_bar_cr = condition_mean(x_bar_c, P_c, z_r, R_r, H_r)
    P_cr = condition_cov(P_c, R_r, H_r)

    x_bar_rc = condition_mean(x_bar_r, P_r, z_c, R_c, H_c,)
    P_rc = condition_cov(P_r, R_c, H_c)

    return x_bar_cr, P_cr, x_bar_rc, P_rc

def get_task_2h(x_bar_rc: ndarray, P_rc: ndarray):
    """get the probability that the boat is above the line

    Args:
        x_bar_rc (ndarray): state
        P_rc (ndarray): covariance

    Returns:
        prob_above_line: the probability that the boat is above the line
    """
    # z = x2 - x1 => Pr(above line) = Pr(z > 5) = 1 - P(z < 5) = 1 - CDF_z(5)
    # Turning multivariate gaussian to a scalar gaussian by evaluating along the
    # normal of the line: z = [-1 1] * [x1 x2]^T
    # The area of this normal above the line is exactly Pr(above line)!

    n = np.array([-1, 1]).reshape((2, 1))

    mean = n.T@x_bar_rc
    std = np.sqrt(n.T@P_rc@n)

    prob_above_line = 1 - norm.cdf(5.0, mean, std)

    return prob_above_line.squeeze()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sun Feb 10 16:36:43 2019

@author: alankar
"""

import numpy as np
from scipy.special.orthogonal import p_roots #Legendre Polynomial roots
from scipy import constants

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

N = 100

integ = lambda theta:(np.tan(theta)**3/np.cos(theta)**2)/(np.exp(np.tan(theta))-1)

T  = 1e3
print('Using %d points Gaussian (Legendre) Quadrature (T=%.1f K):\n'%(N,T))
W = ((constants.k**4*T**4)/(4*np.pi**2*constants.c**2*constants.hbar**3))*gauss_quad(integ,0,np.pi/2,N)
error = np.abs(((constants.k**4*T**4)/(4*np.pi**2*constants.c**2*constants.hbar**3))*gauss_quad(integ,0,np.pi/2,20*N)-W)
print('W = %6e W⋅m^−2\nError Estimate: %.4e\n'%(W,error))
print('Stefan-Boltzmann Constant Calculated: %.6e W⋅m^−2⋅K^−4'%(W/T**4))
print('Stefan-Boltzmann Constant from Literature: %.e W⋅m^−2⋅K^−4\n'%constants.Stefan_Boltzmann)

"""
Output:

Using 100 points Gaussian (Legendre) Quadrature (T=1000.0 K):

W = 5.670367e+04 W⋅m^−2
Error Estimate: 6.3883e-09

Stefan-Boltzmann Constant Calculated: 5.670367e-08 W⋅m^−2⋅K^−4
Stefan-Boltzmann Constant from Literature: 6e-08 W⋅m^−2⋅K^−4
"""


import numpy as np

class LinearRegression:
    def __init__(self, alpha=0.01, n_iters=1000):
        self.alpha = alpha
        self.n_iters = n_iters
        self.theta = None
        self.bias = None

    def fit(self, X, y):
        n_samples, n_features = X.shape
        self.bias = 0
        self.theta = np.zeros((n_features, 1))

        for _ in range(self.n_iters):
            y_pred = self.bias + np.dot(self.theta, X.T)

            dw = np.dot((y_pred - y), X) / n_samples
            db = np.sum(y_pred - y) / n_samples

            self.theta -= self.alpha * dw
            self.bias -= self.alpha * db

    def predict(self, X):
        return self.bias + np.dot(self.theta, X.T)

class LogisticRegression:
    def __init__(self, alpha=0.01, n_iters=1000):
        self.alpha = alpha
        self.n_iters = n_iters
        self.theta = None
        self.bias = None

    def fit(self, X, y):
        n_samples, n_features = X.shape
        self.bias = 0
        self.theta = np.zeros((n_features, 1))

        for _ in range(self.n_iters):
            linear_model = self.bias + np.dot(self.theta, X.T)
            y_pred = self._sigmoid(linear_model)

            dw = np.dot((y_pred - y), X) / n_samples
            db = np.sum(y_pred - y) / n_samples

            self.theta -= self.alpha * dw
            self.bias -= self.alpha * db

    def predict(self, X):
        linear_model = self.bias + np.dot(self.theta, X.T)
        y_pred = self._sigmoid(linear_model)[0]
        return [1 if i > 0.5 else 0 for i in y_pred]

    def _sigmoid(self, z):
        return 1 / (1 + np.exp(-z))


import numpy as np
import matplotlib.pylab as plt

def numerical_diff(f, x):
    h = 1e-4 # 0.0001
    return (f(x+h) - f(x-h)) / (2*h)

def function_1(x):
    return 0.01*x**2 + 0.1*x

x = np.arange(0.0, 20.0, 0.1)
y = function_1(x)
plt.xlabel("x")
plt.ylabel("f(x)")
plt.plot(x,y)
plt.show()

print(numerical_diff(function_1, 5))
print(numerical_diff(function_1, 10))


import numpy as np

def qr_mgs_decompose(matrix: np.array) -> (np.array, np.array):
    """
    For n x m matrix return Q1 and R1 components of QR decomposition using
    the modified Gram-Schmidt process, where R1 is n x n upper triangular
    and Q1 is m x n and have orthogonal columns.
    """
    n = matrix.shape[1]
    q1 = np.array(matrix, dtype='float64')
    r1 = np.zeros((n, n))
    for k in range(n):
        a_k = q1[..., k]
        r1[k,k] = np.linalg.norm(a_k)
        a_k /= r1[k, k]
        for i in range(k+1, n):
            a_i = q1[..., i]
            r1[k,i] = np.transpose(a_k) @ a_i
            a_i -= r1[k, i] * a_k
    return q1, r1

def test_diagonal():
    q, r = qr_mgs_decompose(np.diag([-1, 2, 4]))
    assert np.allclose(q, np.diag([-1, 1, 1]))
    assert np.allclose(r, np.diag([1, 2, 4]))

def test_orthogonal():
    orthogonal = np.array([[1, 1, 1, 1], [1, 1, -1, -1], [1, -1, 1, -1]]).T
    q, r = qr_mgs_decompose(orthogonal)
    assert np.allclose(q, 0.5 * orthogonal)
    assert np.allclose(r, np.diag([2, 2, 2]))

def test_matrix_eq_qr_and_q_orthogonal():
    matrix = np.array([[1, 0, -1], [-2, 1, 3], [5, 4, -3], [3, 0, -4]])
    q, r = qr_mgs_decompose(matrix)
    assert np.allclose(matrix, q @ r)
    assert np.allclose(q.T @ q, np.eye(3))


'''
Logistic Regression implementation

more informations:
- http://ufldl.stanford.edu/tutorial/supervised/LogisticRegression/
- https://beckernick.github.io/logistic-regression-from-scratch/
'''

import numpy as np

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

'''
Compute log likelihood (cost function) on data set of size n
@param x - matrix (n * nb_features) matrix of features for each example
@param y - vector (n) vector of labels, on for each example
@param w - vector (nb_features) vector of weights
'''
def log_likelihood(x, y, w):
    z = np.dot(x, w)
    return - np.sum(y*z - np.log(1 + np.exp(z)))

'''
Apply stochastic gradient descent on the whole training set of size n
@param x - matrix (n * nb_features) matrix of features for each example
@param y - vector (n) vector of labels, on for each example
@param w - vector (nb_features) vector of weights
@param lr - learning rate
@return  vector(nb_features) updated weights
'''
def sgd(x, y, w, lr):

    y_hat = sigmoid(np.dot(x, w))
    dW = np.dot(x.T, y_hat - y)
    w = w - lr * dW
    return w

'''
Apply stochastic_gradient_descent on the whole training set of size n, using mini_batches
@param x - matrix (n * nb_features) matrix of features for each example
@param y - vector (n) vector of labels, on for each example
@param w - vector (nb_features) vector of weights
@param lr - learning rate
@param batch_size - size of each batch
@return  vector(nb_features) updated weights
'''
def sgd_mini_batch(X, y, w, lr, batch_size):

    n = X.shape[0]

    for k in range(0, n, batch_size):
        X_batch = X[k:k + batch_size]
        y_batch = y[k:k + batch_size]
        m = len(X_batch)

        y_hat = sigmoid(np.dot(X_batch, w))
        dW = np.dot(X_batch.T, y_hat - y_batch)
        w = w - (lr / m) * dW
    return w

def evaluate(X, y, w):

    y_hat = np.round(sigmoid(np.dot(X, w)))
    succ = int(np.sum(y_hat == y))
    total = X.shape[0]
    perc = succ * 100 / total

    print('Cost: ' + str(log_likelihood(X, y, w)))
    print('Results: {} / {} ({}%)'.format(succ, total, perc))

'''
@param X_train - matrix (n * nb_features) matrix of features (training set)
@param y_train - vector (n) vector of labels (training set)
@param x_test - matrix (n * nb_features) matrix of features (test set)
@param y_test - vector (n) vector of labels (test set)
@param epochs - number of epochs of learning
@param use_intercept - if true, add a bias to the weights
@param batch_size - size of batch size for gradient descent, if -1, gradient descent is on the full dataset

Run the training for several epochs.
After each epochs, the wieghts are tested on the training test and the test set

'''
def train(X_train, y_train, X_test, y_test, epochs, lr, use_intercept = False, batch_size = -1):

    if use_intercept:
        X_train = np.hstack((np.ones((X_train.shape[0], 1)), X_train))
        X_test = np.hstack((np.ones((X_test.shape[0], 1)), X_test))
    w = np.zeros(X_train.shape[1])

    #Training
    for i in range(1, epochs + 1):
        print('Epoch :' + str(i))
        if batch_size == -1:
            w = sgd(X_train, y_train, w, lr)
        else:
            w = sgd_mini_batch(X_train, y_train, w, lr, batch_size)
        print('Train:')
        evaluate(X_train, y_train, w)
        print('Test:')
        evaluate(X_test, y_test, w)

    return w

if __name__ == '__main__':
    X_train, y_train, X_test, y_test = dataset_mnist.load_mnist_bin()

    #train_sclearn(X_train, y_train, X_test, y_test)
    train(X_train, y_train, X_test, y_test, 30, 0.001, use_intercept = True, batch_size = -1)


import numpy as np

def calculate_area(radius):

    """
    Calculates the area of a circle

    Parameters
    ----------
    radius : number or array
        The radius of a circle or many circles

    Returns
    -------
    area : the calculated area/s

    Examples
    --------
    >>> calculate_area(1)
    3.141592653589793
    """
    area = np.pi * radius ** 2
    return area

def calculate_circ(radius):
    """
    Calculates the circumference of a circle

    Parameters
    ----------
    radius : number or array
        The radius of a circle or many circles

    Returns
    -------
    area : the calculated circumference/s

    Examples
    --------
    >>> calculate_circ(1)
    3.141592653589793
    """

    circ = 2 * np.pi * radius
    return circ


import numpy as np

def hms2dec(h, m, s):
  return 15*(h + m/60 + s/3600)

def dms2dec(d, m, s):
  if (d >= 0):
    return d + m/60 + s/3600
  else:
    return d - m/60 - s/3600

def import_bss():
  file = 'bss.dat'
  lines = np.loadtxt(file, usecols=range(1, 7))

  count=1
  result = [ ]
  for line in lines:
    result.append((count, hms2dec(line[0], line[1], line[2]), dms2dec(line[3], line[4]
, line[5])))
    count += 1
  return result

def import_super():
  file = 'super.csv'
  lines = np.loadtxt(file, delimiter=',', skiprows=1,usecols=[0,1])
  result = []
  count = 1
  for line in lines:
    result.append((count, line[0], line[1]))
    count += 1
  return result

def angular_dist(a1, d1, a2, d2):
  a1 = np.radians(a1)
  d1 = np.radians(d1)
  a2 = np.radians(a2)
  d2 = np.radians(d2)

  p1 = np.sin(abs(d1-d2)/2)**2
  p2 = np.cos(d1)*np.cos(d2)*np.sin(abs(a1-a2)/2)**2
  p3 = 2*np.arcsin(np.sqrt(p1+p2))
  return np.degrees(p3)

def crossmatch(cat1, cat2, max_dist):
  matches = []
  nomatches = []

  for cat1line in cat1:
    found = False
    best_match = (0,0,max_dist+1)

    for cat2line in cat2:
      dist = angular_dist(cat1line[1], cat1line[2], cat2line[1], cat2line[2])
      if(dist <= best_match[2]):
        best_match = (cat1line[0], cat2line[0], dist)

    if(best_match[2] <= max_dist):
      matches.append( best_match )
    else:
      nomatches.append( cat1line[0] )

  return (matches, nomatches)

# You can use this to test your function.
# Any code inside this `if` statement will be ignored by the automarker.
if __name__ == '__main__':
  bss_cat = import_bss()
  super_cat = import_super()

  # First example in the question
  max_dist = 40/3600
  matches, no_matches = crossmatch(bss_cat, super_cat, max_dist)

  print(matches[:3])
  print(no_matches[:3])

  # Second example in the question
  max_dist = 5/3600
  matches, no_matches = crossmatch(bss_cat, super_cat, max_dist)
  print(matches)
  print(matches[:3])
  print(no_matches[:3])
  print(len(no_matches))


#utf-8
#Exercício 2 do curso de Data science com Python do prof. Walisson Silva

'''''('1)Crie arrays 1D, 2D e 3D tendo como base as dimensões dos arrays apresentados na figura abaixo. Em seguida, analise cada um dos atributos mencionados na aula.

**OBS.: Note que, nos arrays 1D e 2D da figura, há um pulo do elemento 3 para o 5. Considere que o próximo elemento é o 4.**

![Figura da primeira questão](aula2-q1.png)

Observe um exemplo de resposta no que se refere aos arrays que você deve construir. Lembre-se que você ainda precisa analisar os atributos desses arrays.')'''

import numpy as np

print()
print('=' * 39)
print('Ex.1')
print(np.arange(0, 6))                  # array de 1 dimensão
print(type(np.arange(0, 6)))
print(f'Formato => {np.arange(0, 6).shape}')
print(f'Tamanho => {np.arange(0, 6).size}')
print(f'Dimensão => {np.arange(0, 6).ndim}')
print(f'\n{2}')

b = np.random.randint(3, size=(3, 4))   # array de 2 dimensões
print(b)
print(type(b))
print(f'Formato => {b.shape}')
print(f'Tamanho => {b.size}')
print(f'Dimensão => {b.ndim}')
print(f'\n{3}')

c = np.random.randint(2, size=(3, 3, 5))
print(c)
print(type(c))
print(f'Formato => {c.shape}')
print(f'Tamanho => {c.size}')
print(f'Dimensão => {c.ndim}')

'''2) Crie uma matriz identidade com dimensões 5 X 5.'''

print('=' * 39)
print('Ex.2')
print(np.identity(5, dtype=float))

'''3) Crie uma matriz 3 X 3 com todos os elementos iguais a 1.'''

print('=' * 39)
print('Ex.3')
print(np.ones((3, 3)))

'''4) Crie uma matriz $2 \times 3$ com elementos de valores aleatórios entre 0 e 20.'''

print('=' * 39)
print('Ex.4')
print(np.random.rand(2, 3))

'''5) Crie o vetor [9, 8, 7, 6, 5, 4, 3, 2, 1] utilizando as duas funções vistas na
aula que são capazes de criar um vetor como este, de forma automática.'''

print('=' * 39)
print('Ex.5')
print(np.arange(9, 0, -1))
print(np.linspace(9, 1, 9, dtype=int))


import numpy as np

def sigmoid_function(x):
    return 1.0 / (1.0 + np.exp(-x))

def sigmoid_function_derivative(x):
    return x * (1.0 - x)

def bipolar_sigmoid_function(x):
    return 2.0 / (1.0 + np.exp(-x)) - 1.0

def bipolar_sigmoid_function_derivative(x):
    return (1.0 - np.square(x)) / 2.0

def hyperbolic_tangent(x):
    return np.tanh(x)

def hyperbolic_tangent_derivative(x):
    return 1.0 - np.square(x)

def relu(x):
    return np.maximum(0, x)

def relu_derivative(x):
    x_derivative = np.copy(x)
    x_derivative[x <= 0] = 0
    x_derivative[x > 0] = 1
    return x_derivative

def softmax_function(x):
    """
    Implementation for softmax function

    See:
    http://cs231n.github.io/linear-classify/#softmax
    """

    e_x = np.exp(x - np.max(x, axis=0))
    return e_x / e_x.sum(axis=0)

def leaky_relu(alpha=0.3):
    """
    Implementation of leaky relu activation function
    """

    def leaky_relu(x):
        return np.maximum(alpha * x, x)
    return leaky_relu

def leaky_relu_derivative(alpha=0.3):
    """
    Derivative of leaky relu activation function
    """

    def leaky_relu_derivative(x):
        x_derivative = np.copy(x)
        x_derivative[x <= 0] = alpha
        x_derivative[x > 0] = 1

        return x_derivative

    return leaky_relu_derivative


from utils import r
from sympy import lambdify, symbols, sin, sqrt

formulas = {
    'forward': [
        # First Derivative
        [
            [
                'f\'(x_i)',
                '\\frac{f(x_{i+1})+f(x_i)}{h}+O(h)',
                [0, 1],
                lambda y, h: (y[1] + y[0])/h,
                '1st derivative forward divided differential with $O(h)$'
            ],
            [
                'f\'(x_i)',
                '\\frac{-f(x_{i+2})+4f(x_{i+1})-3f(x_i)}{2h}+O(h^2)',
                [0, 1, 2],
                lambda y, h: (-1*y[2] + 4*y[1] - 3*y[0])/(2*h),
                '1st derivative forward divided differential with $O(h^2)$'
            ],
        ],
        # Second Derivative
        [
            [
                'f\'\'(x_i)',
                '\\frac{f(x_{i+2})-2f(x_{i+1})+f(x_i)}{h^2}+O(h)',
                [0, 1, 2],
                lambda y, h: (y[2]-2*y[1]+y[0])/h**2,
                '2nd derivative forward divided differential with $O(h)$'
            ],
            [
                'f\'\'(x_i)',
                '\\frac{-f(x_{i+3})+4f(x_{i+2})-5f(x_{i+1})+2f(x_i)}{h^2}+O(h^2)',
                [0, 1, 2, 3],
                lambda y, h: (-1*y[3]+4*y[2]-5*y[1]+2*y[0])/(h**2),
                '2nd derivative forward divided differential with $O(h^2)$'
            ],
        ],
    ],
    'backward': [
        # First Derivative
        [
            [
                'f\'(x_i)',
                '\\frac{f(x_{i})-f(x_{i-1})}{h}+O(h)',
                [-1, 0],
                lambda y, h: (y[0] - y[-1])/h,
                '1st derivative backward divided differential with $O(h)$'
            ],
            [
                'f\'(x_i)',
                '\\frac{3f(x_i)-4f(x_{i-1})+f(x_{i-2})}{2h}+O(h^2)',
                [-2, -1, 0],
                lambda y, h: (y[-2] - 4*y[-1] + 3*y[0])/(2*h),
                '1st derivative backward divided differential with $O(h^2)$'
            ],
        ],
        # Second Derivative
        [
            [
                'f\'\'(x_i)',
                '\\frac{f(x_i)-2f(x_{i-1})+f(x_{i-2})}{h^2}+O(h)',
                [-2, -1, 0],
                lambda y, h: (y[0]-2*y[-1]+y[-2])/h**2,
                '2nd derivative backward divided differential with $O(h)$'
            ],
            [
                'f\'\'(x_i)',
                '\\frac{2f(x_i)-5f(x_{i-1})+4f(x_{i-2})-f(x_{i-3})}{h^2}+O(h^2)',
                [-3, -2, -1, 0],
                lambda y, h: (-1*y[-3]+4*y[-2]-5*y[-1]+2*y[0])/(h**2),
                '2nd derivative backward divided differential with $O(h^2)$'
            ],
        ],
    ],
    'center': [
        # First Derivative
        [
            [
                'f\'(x_i)',
                '\\frac{f(x_{i+1})-f(x_{i-1})}{2h}+O(h^2)',
                [-1, 1],
                lambda y, h: (y[1] - y[-1])/h,
                '1st derivative centered divided differential with $O(h^2)$'
            ],
            [
                'f\'(x_i)',
                '\\frac{-f(x_{i+2})+8f(x_{i+1})-8f(x_{i-1}+f(x_{i-2}))}{12h}+O(h^4)',
                [-2, -1, 1, 2],
                lambda y, h: (-1*y[2] + 8*y[1] - 8*y[-1] + y[-2])/(12*h),
                '1st derivative centered divided differential with $O(h^4)$'
            ],
        ],
        # Second Derivative
        [
            [
                'f\'\'(x_i)',
                '\\frac{f(x_{i+1})-2f(x_{i})+f(x_{i-1})}{h^2}+O(h^2)',
                [-1, 0, 1],
                lambda y, h: (y[1] - 2*y[0] + y[-1])/h**2,
                '2nd derivative centered divided differential with $O(h^2)$'
            ],
            [
                'f\'\'(x_i)',
                '\\frac{-f(x_{i+2})+16f(x_{i+1})-30f(x_i)+16f(x_{i-1})-f(x_{i-2})}{12h^2}+O(h^4)',
                [-2, -1, 0, 1, 2],
                lambda y, h: (-1*y[2] + 16*y[1] - 30*y[0] + 16*y[-1] - y[-2])/(12*h**2),
                '2nd derivative centered divided differential with $O(h^4)$'
            ],
        ],
    ],
}

def numerical_differentiation(x_vals, y_vals, e=None, i_vals=None, methods=None, d=None, o=None):
    out_str = ''

    if (i_vals == None and e == None) or (i_vals == None and x_vals.index(e) == -1):
        raise ValueError('value value of i_vals or e should be provided')

    if i_vals == None:
        i = x_vals.index(e)
        i_vals = list(range(-1*i, len(x_vals)-i))

    if methods == None:
        if i_vals[0] == 0:
            methods = ['forward']
        elif i_vals[-1] == 0:
            methods = ['backward']
        else:
             methods = ['center']

    if type(methods) is str:
        methods = [methods]

    if d == None:
        d = [0, 1]
    if type(d) is int:
        d = [d]

    if o == None:
        o = [0, 1]
    if type(o) is int:
        o = [o]

    h = x_vals[1] - x_vals[0]

    out_str += f"\\begin{{center}}\n\\begin{{tabular}}{{ c |{' c' * len(i_vals)} }}\n"
    out_str += f" $i$ & ${'$ & $'.join([str(i) for i in i_vals])}$ \\\\\n \\hline\n"
    out_str += f" $x_i$ & ${'$ & $'.join([str(i) for i in x_vals])}$ \\\\\n"
    out_str += f" $f(x_i)$ & ${'$ & $'.join([str(r(i)) for i in y_vals])}$ \\\\\n"
    out_str += "\\end{tabular}\n\\end{center}\n\n"

    for method in methods:
        derivatives = [e for i, e in enumerate(formulas[method]) if i in d]
        for derivative in derivatives:
            orders = [e for i, e in enumerate(derivative) if i in o]
            for order in orders:
                if len(set(order[2])-set(i_vals)) < 1:
                    out_str += f'\\textbf{{{order[4]}}}\n\n'
                    out_str += f'$$ {order[0]} = {order[1]} $$ \n'
                    out_str += f'$$ {order[0]} = {r(order[3](dict(zip(i_vals, y_vals)), h))} $$ \n\n'

    return out_str

def numerical_differentiation_fn(f, x, h, method, derivative, order):
    i_vals = formulas[method][derivative-1][order-1][2]
    x_vals = [x + i * h for i in range(i_vals[0], i_vals[-1]+1)]
    y_vals = [f(x) for x in x_vals]

    return numerical_differentiation(x_vals, y_vals, h, x, i_vals)

if __name__ == '__main__':
    x = symbols('x')

    t = [0, 25, 50, 75, 100, 125]
    v = [0, 32, 58, 78, 92, 100]

    t = [.4, .6, .8, 1]
    v = [lambdify((x,), sin(0.5*sqrt(x))/x)(i) for i in t]

    print(numerical_differentiation(t, v, e=1))

    # for ti in t:
    #     print(f'\n\n\\textbf{{At $t={ti}$}}\n\n')
    #     print(numerical_differentiation(t, y, e=ti))


###############################################
# Matrix inversion by LU factorization method #
#               Radin Shayanfar               #
#                   9731032                   #
###############################################

from typing import Any, Tuple

import numpy as np

def lu_factor(aMat: np.ndarray) -> Tuple[Any, Any]:
    n = aMat.shape[0]
    uMat = aMat.copy()
    lMat = np.eye(n, dtype=np.double)
    for i in range(n):
        sliced = uMat[i:, i:]

        lMat[i + 1:, i] = sliced[1:, 0] / sliced[0][0]
        for r_ind in range(1, sliced.shape[0]):
            coef = -1 * sliced[r_ind][0] / sliced[0][0]
            sliced[r_ind] = sliced[r_ind] + coef * sliced[0]

        uMat[i:, i:] = sliced

    return lMat, uMat

def forward_sub(augmented: np.ndarray) -> np.ndarray:
    for i in range(augmented.shape[0]):
        augmented[i + 1:, -1] -= augmented[i + 1:, i] * augmented[i, -1]

    return augmented[:, -1]

def backward_sub(augmented: np.ndarray) -> np.ndarray:
    for i in reversed(range(augmented.shape[0])):
        augmented[i, -1] /= augmented[i, i]
        augmented[:i, -1] -= augmented[:i, i] * augmented[i, -1]

    return augmented[:, -1]

def solve_with_lu(l: np.ndarray, u: np.ndarray, b: np.ndarray) -> np.ndarray:
    augmented = np.concatenate((l, b[..., np.newaxis]), axis=1)
    y = forward_sub(augmented)
    augmented = np.concatenate((u, y[..., np.newaxis]), axis=1)
    return backward_sub(augmented)

def inverse_matrix(l: np.ndarray, u: np.ndarray) -> np.ndarray:
    n = l.shape[0]
    I = np.eye(n)
    inv = np.zeros((n, n), dtype=np.double)
    for i in range(n):
        inv[:, i] = solve_with_lu(l, u, I[:, i])

    return inv

if __name__ == '__main__':
    n = int(input("Enter dimension:\n"))
    print("Enter A matrix:")
    a = []
    for i in range(n):
        a.append(list(map(float, input().split())))
    a = np.array(a, dtype=np.double)

    l, u = lu_factor(a)
    print(f"L matrix:\n{l}")
    print("--------------")
    print(f"U matrix:\n{u}")
    print("--------------")

    print(f"A inverse:\n{inverse_matrix(l, u)}")


def question_1():
    # packages
    import matplotlib.pyplot as plt
    from mpl_toolkits.mplot3d import axes3d
    from matplotlib import cm
    import numpy as np

    # parameters
    t = np.linspace(-1.2, 1.2, 512)
    X, Y = np.meshgrid(t, t)
    norms = {
        '$\infty$-norm': lambda x, y: max(abs(x), abs(y)),
        '1-norm': lambda x, y: abs(x)+abs(y),
        '2-norm': lambda x, y: np.sqrt(x**2+y**2),
    }
    zlim = 0, 1.2
    figsize = 9, 3

    # body
    length = len(norms)
    Z = np.zeros_like(X)
    fig = plt.figure(figsize=figsize)
    for ith, (name, func) in enumerate(norms.items()):
        for jth in range(Z.size):
            value = func(X.flat[jth], Y.flat[jth])
            Z.flat[jth] = value if value<=1 else np.nan
        ax = fig.add_subplot(1, length, ith+1, projection='3d')
        ax.plot_surface(X, Y, Z, alpha=0.3)
        ax.contour(X, Y, Z, zdir='z', offset=zlim[0], cmap=cm.coolwarm)
        ax.set_zlim(*zlim)
        ax.set_xlabel('X')
        ax.set_ylabel('Y')
        ax.set_zlabel('Z')
        ax.set_title(name)
    plt.show()

if __name__ == '__main__':
    question_1()


import math

import numpy as np

def hoeffding(n, delta, upper, lower):
    """
        Input:
            n: number of Xi's
            delta: error probability
            upper, lower:  lower <= Xi <= upper with probability 1

        Output:
            t: the number that satisfies the Hoeffding inequality:
                Pr(Sn >= t) <= {delta = exp(-t^2/{2n*sigma^2})}
                where Sn = X1 + ... + Xn.
    """
    tmp = -np.log(delta)
    half = (upper - lower) / 2
    return np.sqrt(2 * n * half ** 2 * tmp)

def adaptive_hoeffding(n, delta, upper, lower):
    """
        Adaptive Hoeffding-like Concentration Bounds
        as in Corollary 1 of Ermon 2016 NeurIPS paper.
        Valid for lower <= Xi <= upper.

        Input:
            n: number of Xi's
            delta: error probability
            upper, lower:  lower <= Xi <= upper with probability 1

        Output:
            The adaptive upper bound of Sn where Sn = X1 + ... + Xn.
    """
    b = -np.log(delta / 12.0) / 1.8
    return (upper - lower) * np.sqrt(0.6 * n * np.log(math.log(n, 1.1) + 1) + b * n)

def adaptive_hoeffding_seq(N, delta, upper, lower):
    """
        Adaptive Hoeffding-like Concentration Bounds
        as in Corollary 1 of Ermon 2016 NeurIPS paper.
        Valid for lower <= Xi <= upper.

        Input:
            N: number of Xi's
            delta: error probability
            upper, lower:  lower <= Xi <= upper with probability 1

        Output:
            A sequence of adaptive upper bounds for S1, S2, ..., SN.
    """
    b = -np.log(delta / 12.0) / 1.8
    n = np.arange(1, N + 1)
    return (upper - lower) * np.sqrt(
        0.6 * n * np.log(np.log(n) / np.log(1.1) + 1) + b * n
    )

def hrms_hoeffding_seq(N, delta, sigma=1.0):
    """
        Compute the uniform upper bound for Sn = X1 + ... + Xn, n in [N]
        using Eq 8 in Howard et al. 2018, where Xi is sigma sub-Gaussian.

        Input:
            N: number of Xi's
            delta: error probability
            sigma: subGaussian parameter. Xi is sigma-subGaussian

        Output:
            A sequence of adaptive upper bounds for S1, S2, ..., SN.
    """

    n = np.arange(1, N + 1)
    return (
        1.7 * sigma * np.sqrt(n * (np.log(np.log(2 * n)) + 0.72 * np.log(5.2 / delta)))
    )


"""

linear regression problem of mapping R_d to R, with two diffrent objective functions

Author: Zohreh Raziei - raziei.z@husky.neu.edu

"""

#1. Minimizing Sum of squared Error

# In[1]:

#Install dependency

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import use, cm
from sklearn.datasets import make_regression
from sklearn.metrics import r2_score

get_ipython().run_line_magic('matplotlib', 'inline')

# load the dataset

df = pd.read_csv('ex1data1.txt',names = ['Population','Profit'])
df.head()

#Plot the data with scaled units

plt.scatter(df['Population'],df['Profit'],c='r')
plt.xlabel('Population/(10,000)')
plt.ylabel('Profit/(10,000)')
plt.title('Original Data')
plt.show()

# making data
X = df['Population'].values
y = df['Profit'].values

X = X.reshape(-1,1)
X.shape

# No of examples
m = X.shape[0]

# No of features
n = X.shape[1] + 1

#Initialize theta
theta = np.zeros((n,1))
#theta = theta.reshape((len(theta), 1))

#adding extra feature theta0 as 1
X_ = np.append(np.ones((m, 1)), X.reshape((-1,1)),axis=1)

# Compute Cost function
def computeCost(X, y, theta):
    m  = y.size
    h = np.dot(X,theta)
    J = (np.sum(np.square(h-y))) / (2 * m)
    return J

# Calucating cost function and theta

def gradientDescent(X, y, theta, alpha, num_iters):

    # Initialize some useful values
    J_history = []
    m = y.size  # number of training examples

    for i in range(num_iters):
        h = np.dot(X, theta)
        theta = theta - ((alpha / m) * (np.dot(X.T,(h - y.reshape(-1,1)))))
        # Save the cost J in every iteration
        J_history.append(computeCost(X, y, theta))
        break
    return theta,J_history[len(J_history)-1]

final_theta,loss = gradientDescent(X_, y, theta, 0.01, 100)
print('loss :',loss)
print('final theta :',final_theta)

x_line = np.arange(1,23).reshape(-1,1)
y_line = final_theta[0] + x_line * final_theta[1]

def linefitline(x):
    return final_theta[0] + final_theta[1] * x
y_pred = linefitline(X)

print('R2 score For Dataset 1 (MSE): ',r2_score(y,y_pred))

# In[13]:

plt.scatter(X,y,c='r',label='Original')
plt.xlabel('X')
plt.ylabel('y')
plt.plot(x_line,y_line,label='predicted')
plt.legend()
plt.title('Minimizing Sum of squared Error : Dataset 1')
plt.show()

# ## 2. Minimising sum of distance

# load the dataset

df = pd.read_csv('ex1data1.txt',names = ['Population','Profit'])
df.head()

# making data
X = df['Population'].values
y = df['Profit'].values

# Total Least Squares:
def line_total_least_squares(x,y):
    n = len(x)

    x_ = np.sum(x)/n
    y_ = np.sum(y)/n

    # Calculate the x~ and y~
    x1 = x - x_
    y1 = y - y_

    # Create the matrix array
    X = np.vstack((x1, y1))
    X_t = np.transpose(X)

    # Finding A_T_A and it's Find smallest eigenvalue::
    prd = np.dot(X,X_t)
    W,V = np.linalg.eig(prd)
    small_eig_index = W.argmin()
    a,b = V[:,small_eig_index]

    # Compute C:
    c = (-1*a*x_) + (-1*b*y_)

    return a,b,c

a1,b1,c1 = line_total_least_squares(X,y)

x_line = np.arange(1,23).reshape(-1,1)
y_line = (-1*(c1/b1)) + x_line * -1*(a1/b1)

def linefitline(x):
    return -1*(c1/b1) + -1*(a1/b1) * x
y_pred = linefitline(X)

print('R2 score For Dataset 1 (MSD): ',r2_score(y,y_pred))

plt.scatter(df['Population'],df['Profit'],c='r',label='given')
plt.xlabel('Population')
plt.ylabel('Profit')
plt.plot(x_line,y_line,label='predicted')
plt.legend()
plt.title('Minimising sum of distance : Dataset 1')
plt.show()

# ## 3. Closed Form Solution

X_ = np.append(X.reshape((-1,1)),np.ones((m, 1)) ,axis=1)

# calculate coefficients using closed-form solution
from numpy.linalg import inv
coeffs = inv(X_.transpose().dot(X_)).dot(X_.transpose()).dot(y.reshape(-1,1))

coeffs

x_line = np.arange(1,23).reshape(-1,1)
y_line = (coeffs[1][0]) + x_line * coeffs[0][0]

def linefitline(x):
    return coeffs[1][0] + coeffs[0][0] * x
y_pred = linefitline(X)

print('R2 score For Dataset 1 (CFS) : ',r2_score(y,y_pred))

plt.scatter(df['Population'],df['Profit'],c='r',label='given')
plt.xlabel('Population')
plt.ylabel('Profit')
plt.plot(x_line,y_line,label='predicted')
plt.legend()
plt.title('Close Form Solution : Dataset 1')
plt.show()

# # Dataset : 2

# In[29]:

X, y = make_regression(n_samples=100,n_features=1,noise=10)

plt.scatter(X,y)
plt.show()

# ## 1. Minimizing Sum of squared Error

X = X.reshape(-1,1)
X.shape

# No of examples
m = X.shape[0]

# No of features
n = X.shape[1] + 1

#Initialize theta
theta = np.zeros((n,1))
#theta = theta.reshape((len(theta), 1))

#adding extra feature theta0 as 1
X_ = np.append(np.ones((m, 1)), X.reshape((-1,1)),axis=1)

final_theta,loss = gradientDescent(X_, y, theta, 0.6, 200)
print('loss :',loss)
print('final theta :',final_theta)

x_line = np.arange(-5,5).reshape(-1,1)
y_line = final_theta[0] + x_line * final_theta[1]

def linefitline(x):
    return final_theta[0] + final_theta[1] * x
y_pred = linefitline(X)

print('R2 score For Dataset 2 (MSE) : ',r2_score(y,y_pred))

plt.scatter(X,y,c='r',label='Original')
plt.xlabel('X')
plt.ylabel('y')
plt.plot(x_line,y_line,label='predicted')
plt.legend()
plt.title('Minimizing Sum of squared Error : Dataset 2')
plt.show()

# ## 2. Minimising sum of distance

a1,b1,c1 = line_total_least_squares(X.reshape(-1),y)

x_line = np.arange(-5,5).reshape(-1,1)
y_line = (-1*(c1/b1)) + x_line * -1*(a1/b1)

def linefitline(x):
    return -1*(c1/b1) + -1*(a1/b1) * x
y_pred = linefitline(X)

print('R2 score For Dataset 2 (MSD): ',r2_score(y,y_pred))

plt.scatter(X,y,c='r',label='Original')
plt.xlabel('X')
plt.ylabel('y')
plt.plot(x_line,y_line,label='predicted')
plt.legend()
plt.title('Minimising sum of distance : Dataset 2')
plt.show()

# ## 3. Closed Form Solution

X_ = np.append(X.reshape((-1,1)),np.ones((m, 1)) ,axis=1)

# calculate coefficients using closed-form solution
from numpy.linalg import inv
coeffs = inv(X_.transpose().dot(X_)).dot(X_.transpose()).dot(y.reshape(-1,1))

print(coeffs)

def linefitline(x):
    return coeffs[1][0] + coeffs[0][0] * x
y_pred = linefitline(X)

print('R2 score For Dataset 2 (CFS) : ',r2_score(y,y_pred))

x_line = np.arange(-3,5).reshape(-1,1)
y_line = (coeffs[1][0]) + x_line * coeffs[0][0]

plt.scatter(X,y,c='r',label='Original')
plt.xlabel('X')
plt.ylabel('y')
plt.plot(x_line,y_line,label='predicted')
plt.legend()
plt.title('Close Form Solution : Dataset 2')
plt.show()

# # Dataset : 3

# In[50]:

X, y = make_regression(n_samples=200,n_features=1,noise=20)

plt.scatter(X,y)
plt.show()

# ## 1. Minimizing Sum of squared Error

X = X.reshape(-1,1)
X.shape

# No of examples
m = X.shape[0]

# No of features
n = X.shape[1] + 1

#Initialize theta
theta = np.zeros((n,1))
#theta = theta.reshape((len(theta), 1))

#adding extra feature theta0 as 1
X_ = np.append(np.ones((m, 1)), X.reshape((-1,1)),axis=1)

final_theta,loss = gradientDescent(X_, y, theta, 0.5, 100)
print('loss :',loss)
print('final theta :',final_theta)

x_line = np.arange(-5,5).reshape(-1,1)
y_line = final_theta[0] + x_line * final_theta[1]

def linefitline(x):
    return final_theta[0] + final_theta[1] * x
y_pred = linefitline(X)

print('R2 score For Dataset 3 (MSE) : ',r2_score(y,y_pred))

plt.scatter(X,y,c='r',label='Original')
plt.xlabel('X')
plt.ylabel('y')
plt.plot(x_line,y_line,label='predicted')
plt.legend()
plt.title('Minimizing Sum of squared Error : Dataset 3')
plt.show()

# ## 2. Minimising sum of distance

a1,b1,c1 = line_total_least_squares(X.reshape(-1),y)

x_line = np.arange(-5,5).reshape(-1,1)
y_line = (-1*(c1/b1)) + x_line * -1*(a1/b1)

def linefitline(x):
    return -1*(c1/b1) + -1*(a1/b1) * x
y_pred = linefitline(X)

print('R2 score For Dataset 3 (MSD): ',r2_score(y,y_pred))

plt.scatter(X,y,c='r',label='Original')
plt.xlabel('X')
plt.ylabel('y')
plt.plot(x_line,y_line,label='predicted')
plt.legend()
plt.title('Minimising sum of distance : Dataset 3')
plt.show()

# ## 3. Closed Form Solution

X_ = np.append(X.reshape((-1,1)),np.ones((m, 1)) ,axis=1)

# calculate coefficients using closed-form solution
from numpy.linalg import inv
coeffs = inv(X_.transpose().dot(X_)).dot(X_.transpose()).dot(y.reshape(-1,1))

print(coeffs)

def linefitline(x):
    return coeffs[1][0] + coeffs[0][0] * x
y_pred = linefitline(X)

print('R2 score For Dataset 3 (CFS) : ',r2_score(y,y_pred))

x_line = np.arange(-3,5).reshape(-1,1)
y_line = (coeffs[1][0]) + x_line * coeffs[0][0]

plt.scatter(X,y,c='r',label='Original')
plt.xlabel('X')
plt.ylabel('y')
plt.plot(x_line,y_line,label='predicted')
plt.legend()
plt.title('Close Form Solution : Dataset 3')
plt.show()

# # Dataset : 4

# In[71]:

X, y = make_regression(n_samples=300,n_features=1,noise=15)

plt.scatter(X,y)
plt.show()

# ## 1. Minimizing Sum of squared Error

X = X.reshape(-1,1)
X.shape

# No of examples
m = X.shape[0]

# No of features
n = X.shape[1] + 1

#Initialize theta
theta = np.zeros((n,1))
#theta = theta.reshape((len(theta), 1))

#adding extra feature theta0 as 1
X_ = np.append(np.ones((m, 1)), X.reshape((-1,1)),axis=1)

final_theta,loss = gradientDescent(X_, y, theta, 0.5, 100)
print('loss :',loss)
print('final theta :',final_theta)

x_line = np.arange(-5,5).reshape(-1,1)
y_line = final_theta[0] + x_line * final_theta[1]

def linefitline(x):
    return final_theta[0] + final_theta[1] * x
y_pred = linefitline(X)

print('R2 score For Dataset 4 (MSE) : ',r2_score(y,y_pred))

plt.scatter(X,y,c='r',label='Original')
plt.xlabel('X')
plt.ylabel('y')
plt.plot(x_line,y_line,label='predicted')
plt.legend()
plt.title('Minimizing Sum of squared Error : Dataset 4')
plt.show()

# ## 2. Minimising sum of distance

a1,b1,c1 = line_total_least_squares(X.reshape(-1),y)

x_line = np.arange(-5,5).reshape(-1,1)
y_line = (-1*(c1/b1)) + x_line * -1*(a1/b1)

def linefitline(x):
    return -1*(c1/b1) + -1*(a1/b1) * x
y_pred = linefitline(X)

print('R2 score For Dataset 4 (MSD): ',r2_score(y,y_pred))

plt.scatter(X,y,c='r',label='Original')
plt.xlabel('X')
plt.ylabel('y')
plt.plot(x_line,y_line,label='predicted')
plt.legend()
plt.title('Minimising sum of distance : Dataset 4')
plt.show()

# ## 3. Closed Form Solution

X_ = np.append(X.reshape((-1,1)),np.ones((m, 1)) ,axis=1)

# calculate coefficients using closed-form solution
from numpy.linalg import inv
coeffs = inv(X_.transpose().dot(X_)).dot(X_.transpose()).dot(y.reshape(-1,1))

print(coeffs)

def linefitline(x):
    return coeffs[1][0] + coeffs[0][0] * x
y_pred = linefitline(X)

print('R2 score For Dataset 4 (CFS) : ',r2_score(y,y_pred))

x_line = np.arange(-3,5).reshape(-1,1)
y_line = (coeffs[1][0]) + x_line * coeffs[0][0]

plt.scatter(X,y,c='r',label='Original')
plt.xlabel('X')
plt.ylabel('y')
plt.plot(x_line,y_line,label='predicted')
plt.legend()
plt.title('Close Form Solution : Dataset 4')
plt.show()

# # Dataset : 5

# In[92]:

X, y = make_regression(n_samples=500,n_features=1,noise=25)

plt.scatter(X,y)
plt.show()

# ## 1. Minimizing Sum of squared Error

X = X.reshape(-1,1)
X.shape

# No of examples
m = X.shape[0]

# No of features
n = X.shape[1] + 1

#Initialize theta
theta = np.zeros((n,1))
#theta = theta.reshape((len(theta), 1))

#adding extra feature theta0 as 1
X_ = np.append(np.ones((m, 1)), X.reshape((-1,1)),axis=1)

final_theta,loss = gradientDescent(X_, y, theta, 0.5, 500)
print('loss :',loss)
print('final theta :',final_theta)

x_line = np.arange(-5,5).reshape(-1,1)
y_line = final_theta[0] + x_line * final_theta[1]

def linefitline(x):
    return final_theta[0] + final_theta[1] * x
y_pred = linefitline(X)

print('R2 score For Dataset 5 (MSE) : ',r2_score(y,y_pred))

plt.scatter(X,y,c='r',label='Original')
plt.xlabel('X')
plt.ylabel('y')
plt.plot(x_line,y_line,label='predicted')
plt.legend()
plt.title('Minimizing Sum of squared Error : Dataset 5')
plt.show()

# ## 2. Minimising sum of distance

a1,b1,c1 = line_total_least_squares(X.reshape(-1),y)

x_line = np.arange(-5,5).reshape(-1,1)
y_line = (-1*(c1/b1)) + x_line * -1*(a1/b1)

def linefitline(x):
    return -1*(c1/b1) + -1*(a1/b1) * x
y_pred = linefitline(X)

print('R2 score For Dataset 5 (MSD): ',r2_score(y,y_pred))

plt.scatter(X,y,c='r',label='Original')
plt.xlabel('X')
plt.ylabel('y')
plt.plot(x_line,y_line,label='predicted')
plt.legend()
plt.title('Minimising sum of distance : Dataset 5')
plt.show()

# ## 3. Closed Form Solution

X_ = np.append(X.reshape((-1,1)),np.ones((m, 1)) ,axis=1)

# calculate coefficients using closed-form solution
from numpy.linalg import inv
coeffs = inv(X_.transpose().dot(X_)).dot(X_.transpose()).dot(y.reshape(-1,1))

x_line = np.arange(-3,5).reshape(-1,1)
y_line = (coeffs[1][0]) + x_line * coeffs[0][0]

print(coeffs)

def linefitline(x):
    return coeffs[1][0] + coeffs[0][0] * x
y_pred = linefitline(X)

print('R2 score For Dataset 5 (CFS) : ',r2_score(y,y_pred))

plt.scatter(X,y,c='r',label='Original')
plt.xlabel('X')
plt.ylabel('y')
plt.plot(x_line,y_line,label='predicted')
plt.legend()
plt.title('Close Form Solution : Dataset 5')
plt.show()


# --------------
# Code starts here

import numpy as np

# Code starts here

# Adjacency matrix
adj_mat = np.array([[0, 0,0,  0,  0,  0,1/3,0],
                   [1/2,0,1/2,1/3,0,  0,0,  0],
                   [1/2,0,0,  0,  0,  0,0,  0],
                   [0,  1,0,  0,  0,  0,0,  0],
                   [0,  0,1/2,1/3,0,  0,1/3,0],
                   [0,  0,0,  1/3,1/3,0,0,1/2],
                   [0,  0,0,  0,  1/3,0,0,1/2],
                   [0,  0,0,  0,  1/3,1,1/3,0]])

# Compute eigenvalues and eigencevectrs
eigrnvalues , eigenvectors = np.linalg.eig(adj_mat)

# Eigen vector corresponding to 1
eigen_1 = abs(eigenvectors[:,0])* 1/np.linalg.norm(eigenvectors[:,0],1)

# most important page
page = np.where(np.max(eigen_1)==eigen_1)[0][0] + 1
print (page)
# Code ends here

# --------------
# Code starts here

# Initialize stationary vector I
init_I = np.array([1,0,0,0,0,0,0,0])

# Perform iterations for power method
for i in range(10):
    init_I = np.dot(adj_mat,init_I)
    init_I = init_I/np.linalg.norm(init_I,1)

power_page = np.where(np.max(init_I)==init_I)[0][0]+1
print (power_page)
# Code ends here

# --------------
# Code starts here
import numpy as np
# New Adjancency matrix
# New Adjancency matrix
new_adj_mat = np.array([[0,0,0,0,0,0,0,0],
                   [1/2,0,1/2,1/3,0,0,0,0],
                  [1/2,0,0,0,0,0,0,0],
                   [0,1,0,0,0,0,0,0],
                   [0,0,1/2,1/3,0,0,1/2,0],
                   [0,0,0,1/3,1/3,0,0,1/2],
                   [0,0,0,0,1/3,0,0,1/2],
                   [0,0,0,0,1/3,1,1/2,0]])

# Initialize stationary vector I
new_init_I = np.array([1,0,0,0,0,0,0,0])

# Perform iterations for power method
for i in range(10):
    new_init_I = np.dot(new_adj_mat,new_init_I)
    new_init_I = new_init_I / np.linalg.norm(new_init_I,1)

#display
print (new_init_I)

# Code ends here

# --------------
# Alpha value
alpha = 0.85

# Code starts here

# Modified adjancency matrix
n = len(new_adj_mat)
l = np.ones(new_adj_mat.shape)
G = alpha*new_adj_mat + (l-alpha)/n
# Initialize stationary vector I
final_init_I = np.array([1,0,0,0,0,0,0,0])

# Perform iterations for power method
for i in range(1000):
    final_init_I = np.dot(G,final_init_I)
    final_init_I = final_init_I / np.linalg.norm(final_init_I,1)

#display
print(final_init_I)
# Code ends here


#!/usr/bin/env python
__author__ = "Samuel Schmidgall"
__license__ = "MIT"
__email__ = "sschmidg@masonlive.gmu.edu"

import numpy as np

class MultipleLinear:
    def __init__(self):
        """
        Multiple Linear Regression Model
        """
        self._tuple_size = None
        self._data_set_len = None
        self._inp, self._pred, self._bias = None, None, None

    def train(self, inputs, exp_val):
        """
        Train the given model based on inputs and expected value
        :param inputs: input value which is expected to return corresponding exp_val
        :param exp_val: expected value for a given input
        """
        if len(inputs) != len(exp_val) <= 0:
            raise Exception('Invalid Data Set')
        self._tuple_size = len(inputs[0])
        self._data_set_len = len(inputs)
        self._inp, self._pred, self._bias = self._generate_init_values(inputs, exp_val)
        self._gradient_descent()

    def predict(self, predicted_values):
        """
        Compute multiple linear regression prediction
        :param predicted_values: ndarray -> values to predict
        :return: float -> predicted value
        """
        if self._inp is None or self._pred is None or self._bias is None:
            raise Exception('Model must be trained before prediction can occur')
        if type(predicted_values) == list:
            predicted_values = np.array(predicted_values)
            predicted_values = np.insert(predicted_values, 0, 1)
        return predicted_values.dot(self._bias)

    def _cost_function(self):
        """
        Cost function for regression
        """
        return np.sum((self._inp.dot(self._bias) - self._pred) ** 2) / (2 * self._data_set_len)

    def _generate_init_values(self, x, y):
        """
        Generate initial values
        :param x: list(tuple(float)) -> list of x values
        :param y: list(float) -> list of y values
        :return: ndarray -> initialized variables
        """
        return np.array([np.ones(len(x))] + [np.array([k[i] for k in x]) for i in range(self._tuple_size)]).T, \
               np.array(y), np.zeros(self._tuple_size+1)

    def _gradient_descent(self, learning_rate=0.0001, iterations=100000):
        """
        Perform gradient decent to train model
        :param learning_rate: float -> learning rate
        :param iterations: int -> training iterations
        """
        r_itr = range(iterations)
        for _ in r_itr:
            loss = self._inp.dot(self._bias) - self._pred
            gradient = self._inp.T.dot(loss) / self._data_set_len
            self._bias = self._bias - learning_rate * gradient

    def model_efficiency(self, y_prediction):
        """
        Test the efficency of trained model
        :param y_pred: ndarray -> y prediction vector
        :return: tuple(float, float) -> efficiency of given model
        """
        if type(y_prediction) == list:
            y_prediction = np.array(y_prediction)
        mean_s_err = np.sqrt(sum((self._pred - y_prediction) ** 2) / len(self._pred))
        y_mean = np.mean(self._pred)
        r2_score = 1 - (sum((self._pred - y_mean) ** 2) / sum((self._pred - y_prediction) ** 2))
        return mean_s_err, r2_score


from numpy import linalg
import numpy as np

class InconsistentSystemSolver:
    def __init__(self, method='conjugate'):
        self.method = method if method in ['lstsq', 'conjugate'] else 'conjugate'

    def conjugate_grad(self, A, b, x=None, eps=1e-5):
        """
        Description
        -----------
        Solve a linear equation Ax = b with conjugate gradient method.
        Parameters
        ----------
        A: 2d numpy.array of positive semi-definite (symmetric) matrix
        b: 1d numpy.array
        x: 1d numpy.array of initial point
        Returns
        -------
        1d numpy.array x such that Ax = b
        """
        n = A.shape[1]
        if not x:
            x = np.ones(n)
        r = A @ x - b
        p = - r
        r_k_norm = np.dot(r, r)
        for i in range(2 * n):
            Ap = np.dot(A, p)
            alpha = r_k_norm / np.dot(p, Ap)
            x += alpha * p
            r += alpha * Ap
            r_kplus1_norm = np.dot(r, r)
            beta = r_kplus1_norm / r_k_norm
            r_k_norm = r_kplus1_norm
            if r_kplus1_norm < eps:
                # print ('Itr:', i)
                break
            p = beta * p - r
        return x

    def nonlinear_conjugate_grad(self, A, b, eps=1e-5):
        return self.conjugate_grad(A.T @ A, A.T @ b, eps=eps)

    def lstsq(self, A, b):
        return linalg.lstsq(A, b)[0]

    def solve(self, A, b):
        return self.lstsq(A, b) if self.method == 'lstsq' else self.nonlinear_conjugate_grad(A, b)


import numpy as np

arr = np.arange(50).reshape((10,5))
print(arr)
'''
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]
 [15 16 17 18 19]
 [20 21 22 23 24]
 [25 26 27 28 29]
 [30 31 32 33 34]
 [35 36 37 38 39]
 [40 41 42 43 44]
 [45 46 47 48 49]]
'''

# transposition
print(arr.T)

# dot product
print(np.dot(arr.T, arr))
'''
[[7125 7350 7575 7800 8025]
 [7350 7585 7820 8055 8290]
 [7575 7820 8065 8310 8555]
 [7800 8055 8310 8565 8820]
 [8025 8290 8555 8820 9085]]
'''

# 3d array
arr3d = np.arange(50).reshape((5,5,2))
print(arr3d)
'''
[[[ 0  1]
  [ 2  3]
  [ 4  5]
  [ 6  7]
  [ 8  9]]

 [[10 11]
  [12 13]
  [14 15]
  [16 17]
  [18 19]]

 [[20 21]
  [22 23]
  [24 25]
  [26 27]
  [28 29]]

 [[30 31]
  [32 33]
  [34 35]
  [36 37]
  [38 39]]

 [[40 41]
  [42 43]
  [44 45]
  [46 47]
  [48 49]]]
'''

# 3d array transposition
print(arr3d.transpose((1,0,2)))
'''
[[[ 0  1]
  [10 11]
  [20 21]
  [30 31]
  [40 41]]

 [[ 2  3]
  [12 13]
  [22 23]
  [32 33]
  [42 43]]

 [[ 4  5]
  [14 15]
  [24 25]
  [34 35]
  [44 45]]

 [[ 6  7]
  [16 17]
  [26 27]
  [36 37]
  [46 47]]

 [[ 8  9]
  [18 19]
  [28 29]
  [38 39]
  [48 49]]]
'''

# swapaxes
arr_s = np.array([[1,2,3]])
print(arr_s)
'''
[[1 2 3]]
'''

print(arr_s.swapaxes(0,1))
'''
[[1]
 [2]
 [3]]
'''


# distance between two points in xy-coourdinate reference system
# can be calculated using euclidean, manhattan and other known
# metrices. however, distance between two points on earth has to
# be calculated using haversine distance, since earth is not flat
# and while calculating distance, the curvature has to be considered.
# the given function calculates haversine distance using `numpy` library
# and is inspired from sklearns haversine distance calculation function:
# https://scikit-learn.org/stable/modules/generated/sklearn.metrics.pairwise.haversine_distances.html

import numpy as np

def haversineDistance(lon1, lat1, lon2, lat2):
    """Calculate the Distance b/w two Geographic Points"""

    dlon = lon2 - lon1 # delta Longitude
    dlat = lat2 - lat1 # delta Latitude

    a = np.sin(dlat/2.0)**2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon/2.0)**2
    c = 2 * np.arcsin(np.sqrt(a))

    R = 6371 # radius of earth

    return R * c # distance in kilometer

if __name__ == "__main__":
    lon1 = float(input("Enter Point-1's Longitude: "))
    lat1 = float(input("Enter Point-1's Latitude: "))
    lon2 = float(input("Enter Point-2's Longitude: "))
    lat2 = float(input("Enter Point-2's Latitude: "))

    print(f"Distance b/w {lon1, lat1} and {lon2, lat2} is {haversineDistance(lon1, lat1, lon2, lat2)} km.")


import numpy as np

def distance_point_point(p1: np.ndarray, p2: np.ndarray) -> float:
    return abs(np.linalg.norm(p1 - p2))

def distance_point_segment(point, segment) -> float:
    return abs(
        np.cross(segment[1] - segment[0], point - segment[0]
        ) / np.linalg.norm(segment[1] - segment[0])
    )

def distance_point_polyline(point, polyline) -> float:
    segments = segments_of_polyline(polyline)
    return min(
        [distance_point_segment(point, segment) for segment in segments]
    )

def segments_of_polyline(polyline) -> list:
    return [
        [polyline[i], polyline[i+1]] for i in range(len(polyline) - 1)
    ]

def length_of_polyline(polyline) -> float:
    segments = segments_of_polyline(polyline)
    return sum([distance_point_point(segment[0], segment[1]) for segment in segments])

def point_at_parameter_segement(segment, param) -> np.ndarray:
    diss = segment[1] - segment[0]
    return segment[0] + param * diss

def point_at_parameter_polyline(polyline, param) -> np.ndarray:
    segments = segments_of_polyline(polyline)
    segment_lengths = [distance_point_point(segment[0], segment[1]) for segment in segments]
    entire_length = sum(segment_lengths)
    diss = param * entire_length

    def f(aim, lengths):
        for i in range(len(lengths)):
            if aim - lengths[i] <= 0:
                return [aim, i]
            else:
                aim -= lengths[i]

        return [aim, i]

    aim, index = f(diss, segment_lengths)
    segment = [polyline[index], polyline[index+1]]
    length = distance_point_point(segment[0], segment[1])
    param = aim / length
    return point_at_parameter_segement(segment, param)

def is_point_in_segment_domian(point, segment, include_intersection=True) -> bool:
    roundoff = 6
    a, b = segment
    minx = round(min(a[0], b[0]), roundoff)
    maxx = round(max(a[0], b[0]), roundoff)
    miny = round(min(a[1], b[1]), roundoff)
    maxy = round(max(a[1], b[1]), roundoff)
    x = round(point[0], roundoff)
    y = round(point[1], roundoff)

    tolerance =0.00001

    if not include_intersection:
        if (abs(point[0] - segment[0][0]) < tolerance and abs(point[1] - segment[0][1]) < tolerance) or \
                (abs(point[0] - segment[1][0]) < tolerance and abs(point[1] - segment[1][1]) < tolerance):
            return False

    if x < minx or x > maxx:
        return False

    if y < miny or y > maxy:
        return False

    return True

def is_point_in_polyline(point, polyline) -> bool:
    counter = 0
    x, y, p1 = point[0], point[1], polyline[0]

    for p2 in polyline:
        if min([p1[0], p2[0]]) < x <= max([p1[0], p2[0]]):
            if y <= max([p1[1], p2[1]]):
                if p1[0] != p2[0]:
                    xinters = (x - p1[0]) * (p2[1] - p1[1]) / (p2[0] - p1[0]) + p1[1]
                    if p1[1] == p2[1] or y <= xinters:
                        counter += 1

        p1 = p2

    if counter % 2 == 0:
        return False
    else:
        return True

if __name__ == '__mian__':
    # distance_point_point
    p1, p2 = np.array([0, 0]), np.array([1, 1])
    distance = distance_point_point(p1, p2)
    assert distance - 1.4142135623730951 < .000001

    # distance_segment_point
    p = np.array([0, 5])
    segment = [np.array([0, 0]), np.array([10, 10])]
    distance = distance_point_segment(p, segment)
    assert distance - 3.5355339059327373 < .000001

    # segments_of_polyline
    polyline = [
        np.array([0, 0]),
        np.array([10, 10]),
        np.array([20, 8]),
        np.array([30, 25]),
        np.array([45, 30])
    ]
    segments = segments_of_polyline(polyline)
    assert segments == [
        [np.array([0, 0]), np.array([10, 10])],
        [np.array([10, 10]), np.array([20,  8])],
        [np.array([20,  8]), np.array([30, 25])],
        [np.array([30, 25]), np.array([45, 30])]
    ]

    # distance_polyline_point
    distance = distance_point_polyline(p, polyline)
    assert distance - 3.5355339059327373 < .000001

    # point_at_parameter_segement
    p = point_at_parameter_segement(segment, .5)
    assert p == np.array([5, 5])

    # point_at_parameter_polyline
    p = point_at_parameter_polyline(polyline, .3)


def stiffness(n_elem, E, Iz, L, fixed_dofs):

    #Assemblage of the stiffness matrix for a 1D Euler-Bernoulli beam.
    #Is a uniformly distributed Finite Element discretization of a beam with
    #n_elem elements, length L, Young Modulus E, Flexural Inertia Iz and
    #boundary conditions fixed_dofs.

    import numpy as np

    L = L / n_elem #Unioform discretization, size of each element is total
                   # length divided by number of elements.

    k = np.array([[12 * E * Iz / L ** 3, 6 * E * Iz / L ** 2,\
                  -12 * E * Iz / L ** 3, 6 * E * Iz / L ** 2],
                 [  6 * E * Iz / L ** 2, 4 * E * Iz / L,\
                   -6 * E * Iz / L ** 2, 2 * E * Iz / L],
                 [-12 * E * Iz / L ** 3,-6 * E * Iz / L ** 2,\
                   12 * E * Iz / L ** 3,-6 * E * Iz / L ** 2],
                 [  6 * E * Iz / L ** 2, 2 * E * Iz / L,\
                   -6 * E * Iz / L ** 2 ,4 * E * Iz / L]])

    dof_node = 2 #Number of DOFs per node.

    tot_dofs = n_elem * dof_node + dof_node #Total number of DOFs.

    k_struct = np.zeros([tot_dofs,tot_dofs])

    for i in range(n_elem):
        k_struct[2*i:2*i+4, 2*i:2*i+4] += k #Assembly to structure size.

    for dof in fixed_dofs:
        for i in [0, 1]: #Removal of fixed DOFs.
            k_struct = np.delete(k_struct, dof, axis = i)

    return  k_struct

def mass_matrix(n_elem, rho, A, L, fixed_dofs):
    #Assembly of consistent mass matrix of an Euler-Bernoulli beam with density
    #rho, cross-sectional area A, lenght L and BCs fixed_dofs.

    import numpy as np

    Le = L / n_elem
    mass  = rho * A * Le #kg

    #Local mass matrix
    m = mass / 420 * np.array([[156, 22 * Le, 54, -13 * Le],
                               [22 * Le, 4 * Le ** 2, 13 * Le, -3 * Le ** 2],
                               [54, 13  * Le, 156, -22 * Le],
                               [-13 * Le, -3 * Le ** 2, -22 * Le, 4 * Le ** 2]])
    dof_node = 2
    dof_elem = 4
    tot_dofs = n_elem * dof_node + 2

    m_struct = np.zeros([tot_dofs,tot_dofs])

    for i in range(n_elem):
        m_struct[2*i:2*i+4, 2*i:2*i+4] += m #Assembly.

    for dof in fixed_dofs:
        for i in [0, 1]:
            m_struct = np.delete(m_struct, dof, axis = i)

    return  m_struct

def moment_recovery(displ, n_elem, L):

    import numpy as np

    L = L / n_elem

    B_x = lambda x: np.array([- 6 / L ** 2 + 12 * x / L ** 3, \
                              - 4 / L      +  6 * x / L ** 2, \
                                6 / L ** 2 - 12 * x / L ** 3, \
                              - 2 / L      +  6 * x / L ** 2])

    timesteps = np.arange(0, np.size(displ, 1))
    stress_elem = np.zeros([n_elem, np.size(timesteps)])

    for time in timesteps:
        displ_t = np.concatenate(([0, 0], displ[:, time], [0, 0]))
        for elem in range(n_elem):
           stress_elem[elem, time] = - B_x(L / 2) @ displ_t[2*elem:2*elem+4]

    return stress_elem

def node_mapping(x, n_elem):
    #Returns the node for a given x in [0, 1]
    import numpy as np
    import pdb

    n_nod = n_elem + 1
    dof_per_node = 2
    tot_dofs = dof_per_node * n_nod

    x_axis = np.linspace(0, 1, n_nod)

    node_nbr = np.abs(x_axis - x) < 1e-4

    grouped = np.array(list(zip(*[iter(range(tot_dofs))] * 2)))  #pairs the dof belonging to each node. i.e. [(0,1), (2,3), ...]

    return grouped[node_nbr]


# -*- coding: utf-8 -*-
"""3D functions."""
import numpy as np

def angle_between_two_vectors(v1, v2):
    """Compute the angle between the angle between v1 and v2 (can be
    of any dimension).

    :param v1:
    :param v2:
    :type v1: tuple, list, `np.array`
    :type v2: tuple, list, `np.array`
    :return: a float between [0, pi]
    """
    dot_product = sum([a*b for a, b in zip(v1, v2)])
    len_v1 = np.sqrt(np.sum([a**2 for a in v1]))
    len_v2 = np.sqrt(np.sum([a**2 for a in v2]))

    _ = dot_product / (len_v1 * len_v2)

    if _ > 1:
        _ = 1
    elif _ < -1:
        _ = -1
    else:
        pass

    return np.arccos(_)


#!/usr/bin/env python3
#coding=utf-8

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

from QR_method.QR_algorithm import QR_algorithm

def make_tridiagonal_matrix(n,alfa,beta):
    A = alfa*np.eye(n)
    for i in range (0,n-1):
        A[i,i+1] = beta
        A[i+1,i] = beta
    return A

def test_matrix_values(A,V,lamb):
    print("V = \n", V)
    print("lamb \n= ",lamb)
    new_A = V@lamb@np.transpose(V)
    new_A = np.matrix.round(new_A,3)
    print("A = \n", A)
    print("V Λ V_transposto = \n", new_A)

def assignment_a():
    erro = 1e-6
    alfa = 2
    beta = -1

    k_with_spectral_shift_list = np.array([])
    k_without_spectral_shift_list = np.array([])
    print("Assignment A: Example of using the QR algorithm in a tridiagonal matrix")
    print("For this assignment we used: \n 'n' as size of the matirx \n 'k' as number of iterations")
    n_list = np.array([])
    for i in range (2,6):
        n = 2**i
        print("For n = ", n)
        n_list = np.append(n_list,n)
        A_matrix = make_tridiagonal_matrix(n,alfa,beta)

        V,lamb,k = QR_algorithm(A_matrix,erro = erro,spectral_shift = True)
        k_with_spectral_shift_list = np.append(k_with_spectral_shift_list,k)
        print("k with spectral shift = ", k)
        #test_matrix_values(A_matrix,V,lamb)

        V,lamb,k = QR_algorithm(A_matrix,erro = erro,spectral_shift = False)
        k_without_spectral_shift_list = np.append(k_without_spectral_shift_list,k)
        print("k without spectral shift = ", k)
        #test_matrix_values(A_matrix,V,lamb)

    plt.xlim(min(n_list), max(n_list))
    plt.ylim(0, 2000)

    plt.plot(n_list,k_with_spectral_shift_list,label='k_with_spectral_shift')
    plt.plot(n_list,k_without_spectral_shift_list,label='k_without_spectral_shift')
    plt.ylabel('number of iterations')
    plt.xlabel('matrix (nxn)')

    plt.legend(loc='upper center', bbox_to_anchor=(0.5, 1.15), ncol=2, frameon=False)
    plt.show()

if __name__ == "__main__":
    assignment_a()


"""
Correlation based distances and various modifications (angular, absolute, squared) described in Cornell lecture notes:
Codependence: https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3512994&download=yes
"""

import numpy as np
from scipy.spatial.distance import squareform, pdist

# pylint: disable=invalid-name

def angular_distance(x: np.array, y: np.array) -> float:
    """
    Returns angular distance between two vectors. Angular distance is a slight modification of correlation which
    satisfies metric conditions.

    :param x: (np.array) X vector.
    :param y: (np.array) Y vector.
    :return: (float) angular distance.
    """
    corr_coef = np.corrcoef(x, y)[0][1]
    return np.sqrt(0.5 * (1 - corr_coef))

def absolute_angular_distance(x: np.array, y: np.array) -> float:
    """
    Returns a modification of angular distance where absolute value of correlation coefficient is used.

    :param x: (np.array) x vector
    :param y: (np.array) y vector
    :return: (float) absolute angular distance
    """

    corr_coef = np.corrcoef(x, y)[0][1]
    return np.sqrt(0.5 * (1 - abs(corr_coef)))

def squared_angular_distance(x: np.array, y: np.array) -> float:
    """
    Returns a modification of angular distance where square of correlation coefficient is used.

    :param x: (np.array) X vector
    :param y: (np.array) Y vector
    :return: (float) squared angular distance
    """

    corr_coef = np.corrcoef(x, y)[0][1]
    return np.sqrt(0.5 * (1 - corr_coef ** 2))

def distance_correlation(x: np.array, y: np.array) -> float:
    """
    Distance correlation captures both linear and non-linear dependencies.
    Distance correlation coefficient is described in https://en.wikipedia.org/wiki/Distance_correlation

    :param x: (np.array) X vector
    :param y: (np.array) Y vector
    :return: (float) distance correlation coefficient
    """

    x = x[:, None]
    y = y[:, None]

    x = np.atleast_2d(x)
    y = np.atleast_2d(y)

    a = squareform(pdist(x))
    b = squareform(pdist(y))

    A = a - a.mean(axis=0)[None, :] - a.mean(axis=1)[:, None] + a.mean()
    B = b - b.mean(axis=0)[None, :] - b.mean(axis=1)[:, None] + b.mean()

    d_cov_xx = (A * A).sum() / (x.shape[0] ** 2)
    d_cov_xy = (A * B).sum() / (x.shape[0] ** 2)
    d_cov_yy = (B * B).sum() / (x.shape[0] ** 2)

    coef = np.sqrt(d_cov_xy) / np.sqrt(np.sqrt(d_cov_xx) * np.sqrt(d_cov_yy))

    return coef


"""
Programming problems related to prime numbers.  A good source of fodder
for number theory and prime number problems is the youtube channel
https://www.youtube.com/user/numberphile
"""
from collections import namedtuple
from sympy import isprime

# this produces the left truncatable prime with the larges value
# there must be more graceful ways to do it
def largest_ltrunc():
	"""
	Computes the largest left truncatable prime https://youtu.be/azL5ehbw_24
	A left truncatable prime is still prime when the left most digit is
	dropped.  For example 317 -> 17 -> 7 are prime so 317 is a left
	truncatable prime

	returns 357686312646216567629137
	"""

	# Prime.value is the value of the prime number
	# Prime.k is the power of 10 needed to generate the first digit
	Prime = namedtuple('Prime',['value','k'])

	# list single digit prime numbers
	prime_list = [Prime(x,0) for x in (2,3,5,7)]

	# the largest single digit prime number
	N = max([prime.value for prime in prime_list])

	# pop from the front of the list then
	# keep adding left truncatable primes derived from it
	while prime_list:
		prime = prime_list.pop(0)

		# digits 1,...,9 are valid left additions
		for t in range(1,10):
			n = t*10**(prime.k + 1) + prime.value

			# if the number is prime it is a left truncatable prime
			# add it to the list and see if it is the largest
			if isprime(n):
				prime_list.append(Prime(n,prime.k + 1))
				N = max(n,N)

	return N

# this counts the number of left truncatable primes
def count_ltrunc():
	"""
	Counts the number of left truncatable primes https://youtu.be/azL5ehbw_24
	A left truncatable prime is still prime when the left most digit is
	dropped.  For example 317 -> 17 -> 7 are prime so 317 is a left
	truncatable prime

	returns 4260
	"""

	# Prime.value is the value of the prime number
	# Prime.k is the power of 10 needed to generate the first digit
	Prime = namedtuple('Prime',['value','k'])

	# list single digit prime numbers
	prime_list = [Prime(x,0) for x in (2,3,5,7)]

	count = 0

	# pop from the front of the list then
	# keep adding left truncatable primes derived from it
	while prime_list:
		prime = prime_list.pop(0)
		count += 1

		# digits 1,...,9 are valid left additions
		for t in range(1,10):
			n = t*10**(prime.k + 1) + prime.value

			# if the number is prime it is a left truncatable prime
			# add it to the list and see if it is the largest
			if isprime(n):
				prime_list.append(Prime(n,prime.k + 1))

	return count

# an iterator that produces left truncatable primes in increasing order
# while storing a minimal number of unused values
def ordered_ltrunc():
	"""
	Iterates through the left truncatable prime https://youtu.be/azL5ehbw_24
	A left truncatable prime is still prime when the left most digit is
	dropped.  For example 317 -> 17 -> 7 are prime so 317 is a left
	truncatable prime

	returns an iterator that goes through them in increasing order
	"""

	# Prime.value is the value of the prime number
	# Prime.k is the power of 10 needed to generate the first digit
	Prime = namedtuple('Prime',['value','k'])

	# list single digit prime numbers
	working_list = [Prime(x,0) for x in (2,3,5,7)]

	# stores the set of left truncatable primes that have one more
	# digit than the working list
	expanding_list = []

	# the expanding is built with the primes ordered
	while working_list:
		# digits 1,...,9 are valid left additions
		for t in range(1,10):
			for prime in working_list:
				n = t*10**(prime.k + 1) + prime.value
				# the left additions just got constructed in ascending order
				# if the number is prime it is a left truncatable prime
				# add it to the expanding list
				if isprime(n):
					expanding_list.append(Prime(n,prime.k + 1))

		# the expanding list is built so yield the working list one by one
		while working_list:
			yield working_list.pop(0).value

		# working list is exhausted - replace it with the expanding list
		working_list = expanding_list

		# start a new expanding list
		expanding_list = []


#!/usr/bin/pxi2thon
# -*- coding: utf-8 -*-

from sympy import *
from sympy.abc import *

xi1,xi2 = symbols('xi1 xi2')
Phi1, Phi2, Phi3 = symbols('Phi1 Phi2 Phi3')
# 3 4
# 1 2
x11, x12, x13 = symbols('x11 x12, x13')
x21, x22, x23 = symbols('x21 x22, x23')
x31, x32, x33 = symbols('x31 x32, x33')
x41, x42, x43 = symbols('x41 x42, x43')

# arbitrary 2D case

# define Phi(xi)
Phi1 = (1-xi1)*(1-xi2)*x11 + xi1*(1-xi2)*x21 + (1-xi1)*xi2*x31 + xi1*xi2*x41
Phi2 = (1-xi1)*(1-xi2)*x12 + xi1*(1-xi2)*x22 + (1-xi1)*xi2*x32 + xi1*xi2*x42
Phi3 = (1-xi1)*(1-xi2)*x13 + xi1*(1-xi2)*x22 + (1-xi1)*xi2*x33 + xi1*xi2*x43

# compute J2
dPhi1_dxi1 = diff(Phi1, xi1)
dPhi2_dxi1 = diff(Phi2, xi1)
dPhi3_dxi1 = diff(Phi3, xi1)
dPhi1_dxi2 = diff(Phi1, xi2)
dPhi2_dxi2 = diff(Phi2, xi2)
dPhi3_dxi2 = diff(Phi3, xi2)

dPhi_dxi1_squared = dPhi1_dxi1**2 + dPhi2_dxi1**2 + dPhi3_dxi1**2
dPhi_dxi2_squared = dPhi1_dxi2**2 + dPhi2_dxi2**2 + dPhi3_dxi2**2
dPhi_dxi1_dxi2 = dPhi1_dxi1*dPhi1_dxi2 + dPhi2_dxi1*dPhi2_dxi2 + dPhi3_dxi1*dPhi3_dxi2

gram_det = dPhi_dxi1_squared*dPhi_dxi2_squared - dPhi_dxi1_dxi2**2
j = sqrt(gram_det)

print "arbitrary J_2:", j

# rectangular cartesian 2D
l1, l2 = symbols('l1 l2')
Phi1 = x11 + xi1*l1
Phi2 = x12 + xi2*l2
Phi3 = 0

# compute J2
dPhi1_dxi1 = diff(Phi1, xi1)
dPhi2_dxi1 = diff(Phi2, xi1)
dPhi3_dxi1 = diff(Phi3, xi1)
dPhi1_dxi2 = diff(Phi1, xi2)
dPhi2_dxi2 = diff(Phi2, xi2)
dPhi3_dxi2 = diff(Phi3, xi2)

dPhi_dxi1_squared = dPhi1_dxi1**2 + dPhi2_dxi1**2 + dPhi3_dxi1**2
dPhi_dxi2_squared = dPhi1_dxi2**2 + dPhi2_dxi2**2 + dPhi3_dxi2**2
dPhi_dxi1_dxi2 = dPhi1_dxi1*dPhi1_dxi2 + dPhi2_dxi1*dPhi2_dxi2 + dPhi3_dxi1*dPhi3_dxi2

gram_det = dPhi_dxi1_squared*dPhi_dxi2_squared - dPhi_dxi1_dxi2**2
j = sqrt(gram_det)

print "RC J_2:", simplify(j)


from scipy.special import legendre
import numpy as np

def legendre_prime(x, n):
    """Calculate first derivative of the nth Legendre Polynomial recursively.

    Args:
        x (float,np.array) = domain.
        n (int) = degree of Legendre polynomial (L_n).
    Return:
        legendre_p (np.array) = value first derivative of L_n.
    """
    # P'_n+1 = (2n+1) P_n + P'_n-1
    # where P'_0 = 0 and P'_1 = 1
    # source: http://www.physicspages.com/2011/03/12/legendre-polynomials-recurrence-relations-ode/
    if n == 0:
        if isinstance(x, np.ndarray):
            return np.zeros(len(x))
        elif isinstance(x, (int, float)):
            return 0
    if n == 1:
        if isinstance(x, np.ndarray):
            return np.ones(len(x))
        elif isinstance(x, (int, float)):
            return 1
    legendre_p = (n * legendre(n - 1)(x) - n * x * legendre(n)(x))/(1-x**2)
    return legendre_p

def legendre_prime_lobatto(x,n):
    return (1-x**2)**2*legendre_prime(x,n)

def legendre_double_prime(x, n):
    """Calculate second derivative legendre polynomial recursively.

    Args:
        x (float,np.array) = domain.
        n (int) = degree of Legendre polynomial (L_n).
    Return:
        legendre_pp (np.array) = value second derivative of L_n.
    """
    legendre_pp = 2 * x * legendre_prime(x, n) - n * (n + 1) * legendre(n)(x)
    return legendre_pp * (1 - x ** 2)


import numpy as np
np.random.seed(10086)

def de_mean(x):
    xmean = np.mean(x)
    return [xi - xmean for xi in x]

def covariance(x, y):
    n = len(x)
    return np.dot(de_mean(x), de_mean(y)) / (n-1)

MySample = np.random.rand(10, 3)*50
MySample = np.rint(MySample)
MySample = MySample.astype(np.int8)
print(MySample)

dim1 = MySample[:,0]
dim2 = MySample[:,1]
dim3 = MySample[:,2]
cov12 = covariance(dim1, dim2)
cov13 = covariance(dim1, dim3)
cov23 = covariance(dim2, dim3)

var1 = np.var(dim1)
var2 = np.var(dim2)
var3 = np.var(dim3)
tvar1 = np.sum((dim1 - np.mean(dim1)) ** 2) / (len(dim1)-1)
tvar2 = np.sum((dim2 - np.mean(dim2)) ** 2) / (len(dim2)-1)
tvar3 = np.sum((dim3 - np.mean(dim3)) ** 2) / (len(dim3)-1)

print(
np.array(
    [
        [var1, cov12, cov13],
        [cov12, var2, cov23],
        [cov13, cov23, var3],
    ]
)
)

print(
np.array(
    [
        [tvar1, cov12, cov13],
        [cov12, tvar2, cov23],
        [cov13, cov23, tvar3],
    ]
)
)

np_covariance = np.cov(MySample, rowvar=False)
print(np_covariance)


import numpy as np
from numpy.linalg import svd
from scipy.io import loadmat
import matplotlib.pyplot as plt

def featureNormalize(X):
    """
    Normalize the dataset X

    :param X:
    :return:
    """

    mu = np.mean(X, axis=0)
    sigma = np.std(X, axis=0)

    X_normalized = (X - mu) / sigma

    return X_normalized, mu, sigma

def pca(X):
    """
    Compute eigenvectors of the covariance matrix X

    :param X:
    :return:
    """

    number_of_examples = X.shape[0]
    sigma = (1/number_of_examples) * np.dot(X.T, X)
    U, S, V = svd(sigma)

    return U, S, V

def projectData(X, U, K):
    """
    Computes the reduced data representation when projecting only onto
    the top K eigenvectors

    :param X: Dataset
    :param U: Principal components
    :param K: The desired number of dimensions to reduce
    :return:
    """

    number_of_examples = X.shape[0]
    U_reduced = U[:, :K]

    Reduced_representation = np.zeros((number_of_examples, K))

    for i in range(number_of_examples):
        for j in range(K):
            Reduced_representation[i, j] = np.dot(X[i, :], U_reduced[:, j])

    return Reduced_representation

def recoverData(Z, U, K):
    """
    Recovers an approximation of the original data when using the projected data

    :param Z: Reduced representation
    :param U: Principal components
    :param K: The desired number of dimensions to reduce
    :return:
    """

    number_of_examples = Z.shape[0]
    number_of_features = U.shape[0]

    X_recovered = np.zeros((number_of_examples, number_of_features))
    U_reduced = U[:, :K]

    for i in range(number_of_examples):
        X_recovered[i, :] = np.dot(Z[i, :], U_reduced.T)

    return X_recovered

'''
Step 0: Load the dataset
'''

dataset = loadmat("data/lab9faces.mat")
print(dataset.keys(), '\n')

X = dataset["X"]

'''
Step 1: Visualize the dataset
'''

figure1, axes1 = plt.subplots(nrows=10, ncols=10, figsize=(6, 6))

for i in range(0, 100, 10):
    for j in range(10):
        axes1[int(i / 10), j].imshow(X[i + j, :].reshape(32, 32, order="F"), cmap="gray")
        axes1[int(i / 10), j].axis("off")

'''
Step 2: Normalize the dataset and run PCA on it
'''

X_normalized = featureNormalize(X)[0]  # [0] for converting from 'tuple' to 'array'

# Run PCA
U = pca(X_normalized)[0]  # [0] for converting from 'tuple' to 'array'

# Visualize the top 100 eigenvectors found
U_reduced = U[:, :100].T
figure2, axes2 = plt.subplots(nrows=10, ncols=10, figsize=(6, 6))

for i in range(0, 100, 10):
    for j in range(10):
        axes2[int(i / 10), j].imshow(U_reduced[i + j, :].reshape(32, 32, order="F"), cmap="gray")
        axes2[int(i / 10), j].axis("off")

'''
Step 3: Reduce the dimension of the face dataset from 1024 dimension to 100 dimension
'''

reduced_representation = projectData(X_normalized, U, 400)

print("The projected dataset has a shape of: {} whereas".format(reduced_representation.shape))
print("the original dataset has a shape of: {}".format(X_normalized.shape))

X_recovered = recoverData(reduced_representation, U, 400)

# Visualize the reconstructed data
figure3, axes3 = plt.subplots(nrows=10, ncols=10, figsize=(6, 6))
for i in range(0, 100, 10):
    for j in range(10):
        axes3[int(i / 10), j].imshow(X_recovered[i + j, :].reshape(32, 32, order="F"), cmap="gray")
        axes3[int(i / 10), j].axis("off")


from numpy import log, dot, e
from numpy.random import rand

class LogisticRegression:

    def sigmoid(self, z):
        return 1 / (1 + e**(-z))

    def cost_function(self, X, y, weights):
        z = dot(X, weights)
        predict_1 = y * log(self.sigmoid(z))
        predict_0 = (1 - y) * log(1 - self.sigmoid(z))
        return -sum(predict_1 + predict_0) / len(X)

    def fit(self, X, y, epochs=25, lr=0.05):
        loss = []
        weights = rand(X.shape[1])
        N = len(X)

        for _ in range(epochs):
            # Gradient Descent
            y_hat = self.sigmoid(dot(X, weights))
            weights -= lr * dot(X.T,  y_hat - y) / N
            # Saving Progress
            loss.append(self.cost_function(X, y, weights))

        self.weights = weights
        self.loss = loss

    def predict(self, X):
        # Predicting with sigmoid function
        z = dot(X, self.weights)
        # Returning binary result
        return [1 if i > 0.5 else 0 for i in self.sigmoid(z)]