import numpy as np

EPSILON = 1e-10

def _error(actual: np.ndarray, predicted: np.ndarray):
    """ Simple error """
    return actual - predicted

def _percentage_error(actual: np.ndarray, predicted: np.ndarray):
    """
    Percentage error

    Note: result is NOT multiplied by 100
    """
    return _error(actual, predicted) / (actual + EPSILON)

def _naive_forecasting(actual: np.ndarray, seasonality: int = 1):
    """ Naive forecasting method which just repeats previous samples """
    return actual[:-seasonality]

def _relative_error(
    actual: np.ndarray, predicted: np.ndarray, benchmark: np.ndarray = None
):
    """ Relative Error """
    if benchmark is None or isinstance(benchmark, int):
        # If no benchmark prediction provided - use naive forecasting
        if not isinstance(benchmark, int):
            seasonality = 1
        else:
            seasonality = benchmark
        return _error(actual[seasonality:], predicted[seasonality:]) / (
            _error(actual[seasonality:], _naive_forecasting(actual, seasonality))
            + EPSILON
        )

    return _error(actual, predicted) / (_error(actual, benchmark) + EPSILON)

def mse(actual: np.ndarray, predicted: np.ndarray):
    """ Mean Squared Error """
    return np.mean(np.square(_error(actual, predicted)))

def rmse(actual: np.ndarray, predicted: np.ndarray):
    """ Root Mean Squared Error """
    return np.sqrt(mse(actual, predicted))

def mae(actual: np.ndarray, predicted: np.ndarray):
    """ Mean Absolute Error """
    return np.mean(np.abs(_error(actual, predicted)))

def wape(actual: np.ndarray, predicted: np.ndarray):
    """ Weighted Absolute Percentage Error """
    return mae(actual, predicted) / (np.mean(actual) + EPSILON)

def mape(actual: np.ndarray, predicted: np.ndarray):
    """
    Mean Absolute Percentage Error

    Properties:
        + Easy to interpret
        + Scale independent
        - Biased, not symmetric
        - Undefined when actual[t] == 0

    Note: result is NOT multiplied by 100
    """
    return np.mean(np.abs(_percentage_error(actual, predicted)))

def smape(actual: np.ndarray, predicted: np.ndarray):
    """
    Symmetric Mean Absolute Percentage Error

    Note: result is NOT multiplied by 100
    """
    return 100 * np.mean(
        2.0 * np.abs(actual - predicted) / ((np.abs(actual) + np.abs(predicted)))
    )

def mase(actual: np.ndarray, predicted: np.ndarray, seasonality: int = 1):
    """
    Mean Absolute Scaled Error

    Baseline (benchmark) is computed with naive forecasting (shifted by @seasonality)
    """
    return mae(actual, predicted) / (
        mae(actual[seasonality:], _naive_forecasting(actual, seasonality))
    )

def std_ae(actual: np.ndarray, predicted: np.ndarray):
    """ Normalized Absolute Error """
    __mae = mae(actual, predicted)
    return np.sqrt(
        np.sum(np.square(_error(actual, predicted) - __mae)) / (len(actual) - 1)
    )

def std_ape(actual: np.ndarray, predicted: np.ndarray):
    """ Normalized Absolute Percentage Error """
    __mape = mape(actual, predicted)
    return np.sqrt(
        np.sum(np.square(_percentage_error(actual, predicted) - __mape))
        / (len(actual) - 1)
    )

def mre(actual: np.ndarray, predicted: np.ndarray, benchmark: np.ndarray = None):
    """ Mean Relative Error """
    # return np.mean( np.abs(_error(actual, predicted)) / (actual + EPSILON))
    return np.mean(_relative_error(actual, predicted, benchmark))

def rae(actual: np.ndarray, predicted: np.ndarray):
    """ Relative Absolute Error (aka Approximation Error) """
    return np.sum(np.abs(actual - predicted)) / (
        np.sum(np.abs(actual - np.mean(actual))) + EPSILON
    )

def mrae(actual: np.ndarray, predicted: np.ndarray, benchmark: np.ndarray = None):
    """ Mean Relative Absolute Error """
    return np.mean(np.abs(_relative_error(actual, predicted, benchmark)))

METRICS = {
    "mse": mse,
    "rmse": rmse,
    "mae": mae,
    "wape": wape,
    # "mape": mape,
    # "smape": smape,
    "mase": mase,
    # "std_ae": std_ae,
    # "std_ape": std_ape,
    # "rae": rae,
    #'mre': mre,
    #'mrae': mrae
}

import matplotlib.pyplot as plt

def evaluate(
    actual: np.ndarray,
    predicted: np.ndarray,
    metrics=("mae", "mse", "mase", "smape", "wape"),
):
    results = {}
    for name in metrics:
        try:
            res = []

            for y, o in zip(actual, predicted):
                all_zeros = name in ["mape", "wape", "std_ape", "rae"] and np.all(
                    y == 0.0
                )
                all_identical = name in ["mase", "wape", "mape"] and np.all(
                    np.abs(y - np.mean(y)) < 0.001
                )
                if not all_zeros and not all_identical:
                    res.append(METRICS[name](y, o))

                if (
                    False
                    and not all_zeros
                    and not all_identical
                    and METRICS[name](y, o) > 100000
                ):
                    plt.plot(y, label="real")
                    plt.plot(o, label="predicted")
                    plt.title("{}: {}".format(name, METRICS[name](y, o)))
                    plt.legend()
                    plt.show()

            results[name] = np.mean(res)

        except Exception as err:
            results[name] = np.nan
            print("Unable to compute metric {0}: {1}".format(name, err))

    return results

def evaluate_all(actual: np.ndarray, predicted: np.ndarray):
    return evaluate(actual, predicted, metrics=set(METRICS.keys()))


'''
- Physical constants: h, c, k, sigma, G, N_avogadro, Rstart
- Plank function
- Demonstration of how to convert differnt units, using the package *pint*.

License
-------
BSD 3-clause (see https://www.w3.org/Consortium/Legal/2008/03-bsd-license.html)
'''

import numpy as np
from scipy import constants

# Get basic physical and thermodynamic constants

# Physical constants
h     = constants.h     #Planck's constant
c     = constants.c     #Speed of light
k     = constants.k     #Boltzman thermodynamic constant
sigma = constants.sigma #Stefan-Boltzman constant
G     = constants.G     #Gravitational constant

# Thermodynamic constants

# Following will come out in J/(deg kmol), so that dividing Rstar by molecular
# weight gives gas constant appropriate for mks units
N_avogadro = constants.Avogadro  #Avogadro's number
Rstar = 1000. * k * N_avogadro   #Universal gas constant

#----------------Radiation related functions-------------
def B(nu,T):
    '''Planck function (of frequency)
    Density of blackbody radiation.

    Parameters
    ----------
        nu : float
            Frequency [Hz]
        T : float
            Temperature [K]

    Return
    ------
        B : float
            Corresponding blackbody radiation density

    .. math::
        B(\\nu, T) = \\frac{2 k^3 T^3}{h^2 c^2} \\frac{u^3}{e^u -1}

    Example
    -------
    >>> import matplotlib.pyplot as plt
    >>>
    >>> nu = np.linspace(1e12, 1e14, 1001)
    >>> T = 300
    >>> blackbody_radiation = B(nu, T)
    >>>
    >>> plt.plot(nu, blackbody_radiation)
    >>> plt.xlabel(r'$\\nu [Hz]$')
    >>> plt.ylabel(r'$B(\\nu)$')
    >>> plt.title('Blackbody Radiation')
    >>> plt.show()

    '''

    u = h*nu/(k*T)
    u = np.atleast_1d(u)
    u[u>500] = 500           #To prevent overflow
    B = 2.*h*nu**3/c**2 / (np.exp(u)-1.)
    if len(B)==1:
        B = B[0]

    return B

if __name__=='__main__':
    # Example for working with units -----------------------
    # While the example is for "temperature", most common physical units
    # can be converted.
    import pint

    # Select a temperature
    units = pint.UnitRegistry()
    Quantity = units.Quantity

    temp_deg = 30                                # Temperature in deg Celsius
    Temperature = Quantity(temp_deg, units.degC)  # Temperature-object
    temp_K = Temperature.to(units.K).magnitude    # Temperature in deg Kelvin

    print('{0:5.1f} deg C = {1:5.1f} deg K'.format(temp_deg, temp_K))

    # Show how to use the Planck-function
    import matplotlib.pyplot as plt

    nu = np.linspace(1e12, 1e14, 1001)
    T = 300
    print('B_max at nu= {0:6.3e} Hz'.format(58.8e9*T))
    blackbody_radiation = B(nu, T)
    plt.plot(nu, blackbody_radiation)
    plt.xlabel(r'$\nu [Hz]$')
    plt.ylabel(r'$B(\nu)$')
    plt.title('Blackbody Radiation')
    plt.show()

    nu = 1.5e13
    print('The radiation density at {0:5.3e} Hz and {1} K is: {2:8.6e}'.format(nu, T, B(nu, T)))


from sympy import Symbol
from sympy import integrate
from decimal import *
"""
    Estos seran los datos de entrada de nuestro programa.
        f(x) representa la funcion que queremos integrar
        [a,b] es el intervalo donde queremos obtener el valor de la integral
        m son los intervalos que utilizaremos.
"""
x=Symbol('x')
f = 1/(1+x**2)

a=-4
b=4
m=10

"""
    A partir de aqui, comienza el codigo de nuestro programa.
    Primero de todo, calcularemos el valor de h y los nodos que utilizaremos.
"""
n=2*m

h=Decimal((b-a))/Decimal(n)

nodos=[]
for i in range(n+1):
    nodos.append(a+i*h)
    """print(nodos[i])"""

"""
    A continuacion, calcularemos el valor de la integral.
"""
suma1=0
suma2=0
for i in range(1,m+1):
    suma1=suma1+4*f.subs(x,nodos[2*i-1])
    if(i>=2):
        suma2=suma2+2*f.subs(x,nodos[2*i-2])
"""print("\n")
print(f.subs(x,nodos[0]))
print(suma1)
print(suma2)
print(f.subs(x,nodos[n]))

print("\n")
print(h)
print(h/3)
print(f.subs(x,nodos[0])+suma1+suma2+f.subs(x,nodos[n]))"""
integral=(h/3)*(f.subs(x,nodos[0])+suma1+suma2+f.subs(x,nodos[n]))

print(integral)


import numpy as np
import matplotlib.pyplot as plt

from IPython.html.widgets import  interactive, IntSlider, widget, FloatText, FloatSlider, Checkbox, ToggleButtons

sigmin = 0.01
sigmax = 0.1

sigma_0 = 0 # conductivity of the air
h_1 = 1.
h_boom = 0.
h_boom_max = 2.

zmax = 4.

z = np.linspace(0.,zmax,1000)

phi_v = lambda z: (4.*z) / (4.*z**2 + 1.)**(3./2.)
phi_h = lambda z: 2 - (4.*z) / (4.*z**2 + 1.)**(1./2.)

R_v = lambda z: 1./(4.*z**2. + 1.)**(1./2.)
R_h = lambda z: (4.*z**2 + 1.)**(1./2.) - 2.*z

sigma_av = lambda h_boom, h_1, sigma_1, sigma_2: sigma_0*(1.-R_v(h_boom)) + sigma_1*(R_v(h_boom) - R_v(h_1+h_boom)) + sigma_2*R_v(h_1+h_boom)
sigma_ah = lambda h_boom, h_1, sigma_1, sigma_2: sigma_0*(1.-R_h(h_boom)) + sigma_1*(R_h(h_boom) - R_h(h_1+h_boom)) + sigma_2*R_h(h_1+h_boom)

def plot_ResponseFct(h_boom,h_1,sigma_1,sigma_2,orientation='vertical'):

    sigvec = sigma_1*np.ones(z.shape)
    sigvec[z > h_1] = sigma_2

    if orientation is 'vertical':
        phi = phi_v(z + h_boom)
        sig_a = sigma_av(h_boom,h_1,sigma_1,sigma_2)
        phi_title = '$\phi_V$'
    elif orientation is 'horizontal':
        phi = phi_h(z + h_boom)
        sig_a = sigma_ah(h_boom,h_1,sigma_1,sigma_2)
        phi_title = '$\phi_H$'

    phisig = phi*sigvec

    fig, ax = plt.subplots(1,3,figsize=(11,6))

    fs = 13

    ax[0].plot(sigvec,z,'r',linewidth=1.5)
    ax[0].set_xlim([0.,sigmax*1.1])
    ax[0].invert_yaxis()
    ax[0].set_ylabel('z/s',fontsize = fs)
    ax[0].set_title('$\sigma$', fontsize = fs+4, position=[.5, 1.02])
    ax[0].set_xlabel('Conductivity (S/m)', fontsize=fs)
    ax[0].grid(which='both',linewidth=0.6,color=[0.5,0.5,0.5])

    ax[1].plot(phi,z,linewidth=1.5)
    ax[1].set_xlim([0.,2.])
    ax[1].invert_yaxis()
    ax[1].set_title('%s'%(phi_title), fontsize = fs+4, position=[.5, 1.02])
    ax[1].set_xlabel('Response Function', fontsize=fs)
    ax[1].grid(which='both',linewidth=0.6,color=[0.5,0.5,0.5])

    ax[2].plot(phisig,z,color='k',linewidth=1.5)
    ax[2].fill_betweenx(z,phisig,color='k',alpha=0.5)
    ax[2].invert_yaxis()
    ax[2].set_title('$\sigma \cdot$ %s'%(phi_title), fontsize = fs+4, position=[.5, 1.02])
    ax[2].set_xlabel('Weighted Conductivity (S/m)', fontsize=fs)
    ax[2].set_xlim([0.,sigmax*2.])
    ax[2].grid(which='both',linewidth=0.6,color=[0.5,0.5,0.5])

    props = dict(boxstyle='round', facecolor='grey', alpha=0.3)

    # place a text box in upper left in axes coords
    textstr = '$\sigma_a=%.3f$ S/m'%(sig_a)
    ax[2].text(sigmax*0.9, 3.75, textstr, fontsize=fs+2,
            verticalalignment='bottom', bbox=props)

    plt.tight_layout()
    plt.show()

    return None

def interactive_responseFct():
	app = interactive(plot_ResponseFct,h_boom = FloatSlider(min=h_boom, max = h_boom_max, step = 0.1, value = h_boom, continuous_update=False),
                  h_1 = FloatSlider(min=0., max=zmax,value=0.1, step = 0.1, continuous_update=False),
                  sigma_1 = FloatSlider(min=sigmin, max = sigmax,value=sigmin, step = sigmin, continuous_update=False),
                  sigma_2 = FloatSlider(min=sigmin, max = sigmax,value=sigmin, step = sigmin, continuous_update=False),
                  orientation=ToggleButtons(options=['vertical','horizontal']))
	return app

if __name__ == '__main__':
	plot_ResponseFct(1., sigmin, sigmax)


# coding=utf-8
# MSC means Multiple Spectral Clustering
import numpy as np
import scipy as sp
import scipy.linalg as linalg
import networkx as nx
import matplotlib.pyplot as plt

def getNormLaplacian(W):
    """input matrix W=(w_ij)
    "compute D=diag(d1,...dn)
    "and L=D-W
    "and Lbar=D^(-1/2)LD^(-1/2)
    "return Lbar
    """
    d = [np.sum(row) for row in W]
    D = np.diag(d)
    L = D - W
    # Dn=D^(-1/2)
    Dn = np.power(np.linalg.matrix_power(D, -1), 0.5)
    Lbar = np.dot(np.dot(Dn, L), Dn)
    return Lbar

def getKSmallestEigVec(Lbar, k):
    """input
    "matrix Lbar and k
    "return
    "k smallest eigen values and their corresponding eigen vectors
    """
    eigval, eigvec = linalg.eig(Lbar)
    dim = len(eigval)

    # 查找前k小的eigval
    dictEigval = dict(zip(eigval, range(0, dim)))
    kEig = np.sort(eigval)[0:k]
    ix = [dictEigval[k] for k in kEig]
    return eigval[ix], eigvec[:, ix]

def checkResult(Lbar, eigvec, eigval, k):
    """
    "input
    "matrix Lbar and k eig values and k eig vectors
    "print norm(Lbar*eigvec[:,i]-lamda[i]*eigvec[:,i])
    """
    check = [np.dot(Lbar, eigvec[:, i]) - eigval[i] * eigvec[:, i] for i in range(0, k)]
    length = [np.linalg.norm(e) for e in check] / np.spacing(1)
    print("Lbar*v-lamda*v are %s*%s" % (length, np.spacing(1)))

g = nx.karate_club_graph()
nodeNum = len(g.nodes())
m = nx.to_numpy_matrix(g)
Lbar = getNormLaplacian(m)
k = 2
kEigVal, kEigVec = getKSmallestEigVec(Lbar, k)
print("k eig val are %s" % kEigVal)
print("k eig vec are %s" % kEigVec)
checkResult(Lbar, kEigVec, kEigVal, k)

# 跳过k means，用最简单的符号判别的方法来求点的归属

clusterA = [i for i in range(0, nodeNum) if kEigVec[i, 1] > 0]
clusterB = [i for i in range(0, nodeNum) if kEigVec[i, 1] < 0]

# draw graph
colList = dict.fromkeys(g.nodes())
for node, score in colList.items():
    if node in clusterA:
        colList[node] = 0
    else:
        colList[node] = 0.6
plt.figure(figsize=(8, 8))
pos = nx.spring_layout(g)
nx.draw_networkx_edges(g, pos, alpha=0.4)

nx.draw_networkx_labels(g, pos, font_size=10, font_family='sans-serif')
plt.axis('off')
plt.title("karate_club spectral clustering")
plt.savefig("spectral_clustering_result.png")
plt.show()

import numpy,import scipy,import networkx


# -*- coding: utf-8 -*-
"""
stochastic chain rule

    dX = (α-X) dt + β*sqrt(X) dW, X(0) = X0

this equation is integrated using the Euler-Maruyama method, first directly
and a second time using V = sqrt(X), which yields (after applying the
stochastic chain rule):
    dV = ((4*α-β**2)/(8*V) - V/2) dt + β/2 dW
"""

import numpy as np
from numpy.random import randn
import matplotlib.pyplot as plt

np.random.seed(100)

α = 2; β = 1; X0 = 1
T = 1; N = 200; dt = T / N
t = np.arange(0, T+dt, dt)

dW = np.sqrt(dt)*randn(N)

# direct
X = np.zeros(N+1)
X[0] = X0
# chain rule
V = np.zeros(N+1)
V[0] = X0**2

for i in range(1, N+1):
    X[i] = X[i-1] + (α - X[i-1]) * dt \
            + β * np.sqrt(np.abs(X[i-1])) * dW[i-1]
    V[i] = V[i-1] + ((4*α-β**2) / (8*V[i-1]) - .5*V[i-1]) * dt \
            + .5*β*dW[i-1]

plt.plot(t, np.sqrt(X), 'b-', label='direct')
plt.plot(t, V, 'r:', label='chain rule')
plt.xlabel('t')
plt.ylabel('V(X)')
plt.legend()

print("err: %f" % np.max(np.abs(np.sqrt(X)-V)))


"""krylov methods"""

import numpy as np
import math

def cg(x, A, b):
    """conjugate gradient"""
    n = b.size

    r = b - A.dot(x)
    p = np.copy(r)

    r2 = r.dot(r)
    error = math.sqrt(r2)

    Ap = np.zeros(n)

    while error > 0:
        Ap[:] = A.dot(p)
        alpha = r2 / p.dot(Ap)

        x += alpha * p
        r -= alpha * Ap

        old_r2 = r2
        r2 = r.dot(r)

        error = math.sqrt(r2)

        yield error, r

        beta = r2 / old_r2
        p[:] = r + beta * p

def cr(x, A, b):
    """conjugate residual"""

    r = b - A.dot(x)
    p = np.copy(r)

    Ar = A.dot(r)
    Ap = np.copy(Ar)

    rAr = r.dot(Ar)
    r2 = r.dot(r)

    error = math.sqrt(r2)

    while error > 0:
        alpha = rAr / Ap.dot(Ap)

        x += alpha * p
        r -= alpha * Ap

        r2 = r.dot(r)
        error = math.sqrt(r2)

        yield error, r

        Ar[:] = A.dot(r)
        old_rAr = rAr

        rAr = r.dot(Ar)

        beta = rAr / old_rAr

        p[:] = r + beta * p
        Ap[:] = Ar + beta * Ap

# TODO pcg, pcr


# Author: Jintao Huang
# Email: hjt_study@qq.com
# Date:

from numba import njit
import random
from typing import Callable

@njit
def func(x: float) -> float:
    return x ** 2 - 1

def func_integral(x: float) -> float:
    return x ** 3 / 3 - x

@njit
def HitorMiss2(f: Callable[[float], float],
               a: float, b: float, c: float, d: float,
               n: int) -> float:
    """f: [a, b]->[c, d]"""
    k = 0  # 命中
    c, d = min(c, 0), max(d, 0)
    S = (d - c) * (b - a)
    #
    for _ in range(n):
        x = random.uniform(a, b)
        y = random.uniform(c, d)
        fx = f(x)
        #
        if 0 < y <= fx:  # (x, y)在阴影处
            k += 1
        if fx <= y < 0:
            k -= 1
    return k / n * S

a, b, c, d = -1, 3, func(0), func(3)
print("准确解: %f" % (func_integral(b) - func_integral(a)))
#
n = int(1e8)
res = HitorMiss2(func, a, b, c, d, n)
print("n: %d, 计算的积分值: %f" % (n, res))

"""Out
准确解: 5.333333
n: 100000000, 计算的积分值: 5.337512
"""


import numpy as np

def population_correlation(data_matrix, x_index, y_index):
    """
    data_matrix is a numpy multi-dimensional array (matrix)
    x_index and y_index are the index for the first and second variables respectively
    it returns the correlation between two variables in a data_matrix
    """
    transposed_data = data_matrix.transpose()
    x_population = transposed_data[x_index]
    x_mean = np.mean(x_population)
    x_std = np.std(x_population)
    y_population = transposed_data[y_index]
    y_mean = np.mean(y_population)
    y_std = np.std(y_population)

    # To calculate the expectation means to calculate the cov(x_population, y_population)
    # This can also be done using numpy. For that use: np.cov(x_population, y_population, bias=True)
    # bias=True indicates that we are calculating the population covariance
    # np.cov returns a bxb matrix, where b is the amount of vectors passed as parameter, in our case b=2
    expectation = np.mean((x_population - x_mean) * (y_population - y_mean))
    std_product = x_std * y_std

    return expectation/std_product

def sample_correlation(data_matrix, x_index, y_index):
    # Sample information
    sample_len = data_matrix[0].size
    sample_sum = data_matrix.sum(axis=0)

    # Feature information
    transposed_data = data_matrix.transpose()
    x_population = transposed_data[x_index]
    y_population = transposed_data[y_index]
    xy = np.multiply(x_population, y_population)
    x_pow = np.multiply(x_population, x_population)
    y_pow = np.multiply(y_population, y_population)

    # Sample correlation calculation
    numerator = (sample_len * xy.sum()) - (sample_sum[x_index] * sample_sum[y_index])
    x_denominator = (sample_len * x_pow.sum()) - (x_population.sum() ** 2)
    y_denominator = (sample_len * y_pow.sum()) - (y_population.sum() ** 2)
    # TODO: Fix runtimeWarning: overflow encountered in long_scalars and output value
    denominator = (x_denominator * y_denominator) ** (1/2)

    return numerator/denominator


import numpy as np

def nichols_grid(gmin,pmin,pmax,cm=None,cp=None):

    # Round Gmin from below to nearest multiple of -20dB,
    # and Pmin,Pmax to nearest multiple of 360

    gmin = min(-20,20*np.floor(gmin/20))
    pmax = 360*np.ceil(pmax/360);
    pmin = min(pmax-360,360*np.floor(pmin/360));

    if cp is None:
        p1 = np.array([1,5,10,20,30,50,90,120,150,180])
    else:
        p1 = cp

    g1_part1 = np.array([6,3,2,1,.75,.5,.4,.3,.25,.2,.15,.1,.05,0,-.05,-.1,-.15,-.2,-.25,-.3,-.4,-.5,-.75,-1,-2,-3,-4,-5,-6,-9,-12,-16])
    g1_part2 =np.arange(-20,max(-40,gmin)-1,-10)

    if gmin >-40:
        g1 = np.hstack([g1_part1,g1_part2])
    else:
        g1 = np.hstack([g1_part1,g1_part2,gmin])

    # Compute gains GH and phases PH in H plane
    [p,g] = np.meshgrid((np.pi/180)*p1,10**(g1/20))
    z = g* np.exp(1j*p)
    H = z/(1-z)
    gH = 20*np.log10(np.abs(H))
    pH = np.remainder((180/np.pi)*np.angle(H)+360,360)

    # Add phase lines for angle between 180 and 360 (using symmetry)
    p_name = ["%.2f deg" % p1_temp for p1_temp in np.hstack([-360+p1,-p1])]
    gH = np.hstack([gH,gH])
    pH = np.hstack([pH,360-pH])
    phase_lines = []
    for indice in range(gH.shape[1]):
        phase_lines.append({"y": gH[:,indice],"x": pH[:,indice]-360,"name":p_name[indice]})

    # (2) Generate isogain lines for following gain values:
    if cm is None:
        g2_part1 = np.array([6,3,1,.5,.25,0,-1,-3,-6,-12,-20])
        g2_part2 = np.arange(-40,-20,gmin-1)
        g2 = np.hstack([g2_part1,g2_part2])
    else:
        g2 = cm

    #% Phase points
    p2 = np.array([1,2,3,4,5,7.5,10,15,20,25,30,45,60,75,90,105,120,135,150,175,180]);
    p2 = np.hstack([p2,np.flip(360-p2[:-1])])

    [g,p] = np.meshgrid(10**(g2/20),(np.pi/180)*p2)  # mesh in H/(1+H) plane
    z = g* np.exp(1j*p)
    H = z/(1-z)
    gH = 20*np.log10(np.abs(H))
    pH = np.remainder((180/np.pi)*np.angle(H)+360,360)

    # add gain line using symmetry
    g_name = ["%.2f dB" % g2_temp for g2_temp in g2]
    pH = pH-360

    mag_lines = []
    for indice in range(pH.shape[1]):
        mag_lines.append({"y": gH[:,indice],"x": pH[:,indice],"name":g_name[indice]})

    return mag_lines,phase_lines

def rlocus_grid(rad_max):

    data = []

    # add frequency line
    wn_vect = np.linspace(0,rad_max,10)
    theta_vect = np.linspace(np.pi/2,3*np.pi/2,30)
    for index in range(len(wn_vect)):
        wn = wn_vect[index]
        name = "{:.3f} rad/s".format(wn)
        x = np.ravel(wn*np.cos(theta_vect))
        y = np.ravel(wn*np.sin(theta_vect))
        data_temp = {"x": x,"y":y,"name":name}
        data.append(data_temp)

    #add damping line
    wn_vect = np.linspace(0,rad_max,30)
    theta_vect = (np.pi/2)+np.pi*np.arange(20)/20
    for index in range(len(theta_vect)):
        theta = theta_vect[index]
        m = np.abs(np.cos(-theta))
        name = "m={:.3f}".format(m)
        x = np.ravel(wn_vect*np.cos(theta))
        y = np.ravel(wn_vect*np.sin(theta))
        data_temp = {"x": x,"y":y,"name":name}
        data.append(data_temp)

    return data

def drlocus_grid(angle_list=np.arange(0.1,1.1,0.1),m_list=np.arange(0,1,0.1)):

    data = []

    # add angular frequency line
    for index in range(len(angle_list)):
        wn = angle_list[index]*np.pi
        m_vect = np.linspace(0,1,25)
        dpole = np.exp(-m_vect*wn)*np.exp(1j*np.sqrt(1-m_vect**2)*wn)
        dpole = np.hstack([dpole,np.flip(np.conj(dpole))])
        name = "{:.1f} pi /dt".format(angle_list[index])
        x = np.real(dpole)
        y = np.imag(dpole)
        data_temp = {"x": x,"y":y,"name":name}
        data.append(data_temp)

    #add damping line
    for index in range(len(m_list)):
        m = m_list[index]
        wn_vect = np.linspace(0,np.pi/np.sqrt(1-m**2),25)
        dpole = np.exp(-m*wn_vect)*np.exp(1j*np.sqrt(1-m**2)*wn_vect)
        dpole = np.hstack([dpole,np.flip(np.conj(dpole))])
        name = "m={:.3f}".format(m)
        x = np.real(dpole)
        y = np.imag(dpole)
        data_temp = {"x": x,"y":y,"name":name}
        data.append(data_temp)

    return data


#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time     : 2018/11/09 19:02
# @Author   : Iydon
# @File     : 6.1.py

import numpy as np

def gauss_jordan_method(A, b, num_dig:int):
    np_result = A**-1 * b
    print(np_result.T)
    n = A.shape[0]
    x = np.zeros((n,1))
    for k in range(n):
        for i in range(n):
            if k == i:
                continue
            m = A[i,k] / A[k,k]
            A[i,:] = np.round( A[i,:] - m*A[k,:], num_dig )
            b[i,0] = np.round( b[i,0] - m*b[k,0], num_dig )
            print(np.hstack((A,b)))
            print()
    for i in range(n):
    	x[i,0] = np.round( b[i,0]/A[i,i], num_dig )
    return x

A = np.matrix([[4.,-1,1],[2,5,2],[1,2,4]])
b = np.matrix([[8.],[3],[11]])
result = gauss_jordan_method(A, b, num_dig=2)
print(result.T)


# https://www.superprof.co.uk/resources/academic/maths/linear-algebra/linear-programming/linear-programming-examples.html
# f(x,y)= 50x + 40y
# 2x + 3y <= 1500
# 2x + y <= 1000
# x >= 0
# y >= 0

import numpy as np
import scipy.optimize as opt

def example_1():

  cost = np.array([-50, -40])

  A = [np.array([2, 3]), np.array([2, 1])]
  B = [1500, 1000]
  C = [(0, None), (0, None)]

  obj_fun = lambda x: np.sum(x*cost)
  cond = (
    {'type': 'ineq', 'fun': lambda x: B[0] - np.sum(A[0]*x)},
    {'type': 'ineq', 'fun': lambda x: B[1] - np.sum(A[1]*x)}
  )

  guess = [1, 1]

  result = opt.minimize(obj_fun, guess, method = 'SLSQP', bounds = C, constraints = cond)
  # result = opt.minimize(obj_fun, guess, method = 'Nelder-Mead', bounds = C, constraints = cond)

  print(result)
  print(f'Result is {(result.x)[0]} - {(result.x)[1]}')

  print('Completed')

if __name__ == '__main__':
  example_1()


#!/usr/bin/env python
"""
Demonstrates limits of IEEE754 floating point precision

see Wikipedia "unit in the last place" "machine epsilon"
"Machine epsilon is defined as the smallest number that, when added to one, yields a result different from one."

Compare with:

Matlab/Octave
-------------
>> eps(double(1)) =>    2.2204e-16

>> eps(single(1)) =>    1.1921e-07

https://blogs.mathworks.com/cleve/2017/05/22/quadruple-precision-128-bit-floating-point-arithmetic/

Matlab and Octave can support quad precision using external libraries, with great slowdowns in computing.
I would consider Fortran for a project needing quad precision

Fortran
-------
 bits of precision for 32-bit float          24
 machine epsilon: 32-bit float   1.19209290E-07
 bits of precision for 64-bit float          53
 machine epsilon: 64-bit float   2.2204460492503131E-016
 bits of precision for 128-bit float         113
 machine epsilon: 128-bit float   1.92592994438723585305597794258492732E-0034

Half precision:
---------------
GCC: currently only on ARM
https://gcc.gnu.org/onlinedocs/gcc/Half-Precision.html
Matlab: via external libraries.
Python: Numpy
"""
import numpy as np

# %% half prec
ph = 0
h = np.float16(1)

while h != h + np.float16(1):
    h *= np.float16(2)
    ph += 1

eps16 = 2 ** (-(ph - 1))
print("bits of precision for 16-bit float", ph)
print("machine epsilon: 16-bit float", eps16)

# %% single prec
ps = 0
s = np.float32(1)

while s != s + np.float32(1):
    s *= np.float32(2)
    ps += 1

eps32 = 2 ** (-(ps - 1))
print("bits of precision for 32-bit float", ps)
print("machine epsilon: 32-bit float", eps32)
# %% double prec
pd = 0
d = 1.0

while d != d + 1:
    d *= 2.0
    pd += 1

eps64 = 2 ** (-(pd - 1))
print("bits of precision for 64-bit float", pd)
print("machine epsilon: 64-bit float", eps64)
# %% quad prec
"""
caveats on Numpy "long double":
https://docs.scipy.org/doc/numpy-dev/user/basics.types.html#extended-precision
"""
pq = 0
q = np.float128(1.0)

while q != q + np.float128(1):
    q *= np.float128(2.0)
    pq += 1

eps128 = 2 ** (-(pq - 1))
print("bits of precision for long double", pq)
print("machine epsilon: long double", eps128)


import numpy as np
import matplotlib.pyplot as plt

from .ExceptionHandler import ConvergencePointsValueError

class GradientDescent():
    """Class containing differents methods for applying gradient descent method
    for single variable function

    Attributes:
        name (string): name of the optmizer
        f (function): function for optimization
        df (function): first derivation of the function
        x_t (float): starting variable for analysis
        learning_rate (float): learning rate
        tolerance (int): tolerance for the distance between two consecutive
        estimates in a subsequence that converges
        max_iterations (int): maximum number of iterations
        convergence_points (list): list to store the history of points during
        optimization n_iterations (int): number of iterations for convegence
    """
    def __init__(self, f, df, x_t, learning_rate=1e-3, tolerance=1e-6,
                 max_iterations=1000, n_history_points=1000):
        """Constructor

        Args:
            f (function): function for optimization
            df (function): first derivation of the function
            x_t (float): starting variable for analysis
            learning_rate (float, optional): learning rate
            tolerance (int, optional): tolerance for the distance between two
            consecutive estimates in a subsequence that converges
            max_iterations (int, optional): maximum number of iterations
            n_history_points (int, optional): total amount of history points
            to be saved during optization

        Returns:
            None
        """
        self.name = 'Gradient Descent'
        self.f = f
        self.df = df
        self.x_t = x_t
        self.learning_rate = learning_rate
        self.tolerance = tolerance
        self.max_iterations = max_iterations
        self.convergence_points = np.array([None]*n_history_points)
        self.n_iterations = 0

    def _update_parameter(self, x_t):
        """Computes the current update vector for GradientDescent

        Args:
            x_t (float): point for calculation

        Returns:
            (float): update amount
        """
        return self.learning_rate*self.df(x_t)

    def fit(self):
        """Gradient Descent for Optimization Algorithm

        Args:
            None

        Returns:
            (float) : local minimum measured by the algorithm
        """
        # Compute First Interation
        # Set new x_{t+1} = x_{t} - lambda*f'(x_{t})
        self.n_iterations = 1  # iteration step
        n_convergence_points = 0  # iteration step of list convergence_points

        x_t = self.x_t
        x_t_1 = x_t - self._update_parameter(x_t)

        self.convergence_points[n_convergence_points] = x_t

        while (np.abs(x_t_1 - x_t) > self.tolerance) and \
                (self.n_iterations <= self.max_iterations):
            try:
                # Update x_t
                x_t = x_t_1
                x_t_1 = x_t - self._update_parameter(x_t)

                # Stores some convergence points. Stores points that are
                # divisible by max_iterations*0.01.
                # Only some random points are stored in order not to create
                # a too big list and ending up
                # slowing down performance.
                if (self.n_iterations % (self.max_iterations*0.01) == 0):
                    n_convergence_points += 1
                    self.convergence_points[n_convergence_points] = x_t
                    if(n_convergence_points ==
                       (len(self.convergence_points) - 1)):
                        n_convergence_points = 0

                self.n_iterations += 1

            except OverflowError as err:
                print('Overflowed exception raised. The value of the function \
                    exploded. Try reducing the learning_rate')
                return err

        # If convergence_points list isn't completed, select only
        # the non-null values.
        self.convergence_points = \
            self.convergence_points[self.convergence_points is not None]\
            .astype(float)

        return x_t_1

    def get_n_iteration(self):
        """Get numbers of iterations required for optimization

        Args:
            None

        Returns:
            (int): number of iterations required
        """
        return self.n_iterations

    def plot_optimization(self, x1=None, x2=None, n_points=100):
        """Plotting function between interval (x1,x2)

        Args:
            x1 (float, optional): start of interval
            x2 (float, optional): end of interval
            n_points (int, optional): number of point to be plotted
            between x1 and x2

        Returns:
            None
        """
        try:
            if (len(self.convergence_points[self.convergence_points is None])):
                raise ConvergencePointsValueError('Empty x-axis. \
                    No Convergence Points')

            x1 = x1 or min(self.convergence_points)
            x2 = x2 or max(self.convergence_points)

            x_axis = np.linspace(x1, x2, n_points)
            y_x = self.f(x_axis)

            plt.plot(x_axis, y_x, color='blue')
            plt.ylabel('$f(x)$')
            plt.xlabel('$x$')
            plt.title(f'{self.name}')

            # Plot points on f(x)
            plt.plot(self.convergence_points, self.f(self.convergence_points),
                     'bo', color='red')
            plt.show()

        except ConvergencePointsValueError as err:
            print('Please, run the fit() method first: ', err)


# -*- coding: utf-8 -*-
"""
Created on Fri Aug  7 01:23:34 2015

@author: hdragon689
"""

from six.moves import range
import numpy

def gs(X, row_vecs=True, norm = True):
  '''
  Gram-Schmidt orthogonalization

  Syntax: Y = gs(X, row_vecs=True, norm = True)

  Input:
  X           m by n      a 2D array with data to be orthogonalized
  row_vecs    bool        True if X is row-wise; otherwise, it is column-wise
                          default is True
  norm        bool

  Remark:
  Row-wise data implies X=[[g1],[g2],[g3],...] where gi vector has shape of 1 by n
  Column-wise data implies X=[[g1,g2,g3,]] where gi vector has shape of n by 1

  The original code is downloaded from https://gist.github.com/iizukak/1287876
  '''
  if not row_vecs:
    X = X.T
  Y = X[0:1,:].copy()
  for i in range(1, X.shape[0]):
    proj = numpy.diag((X[i,:].dot(Y.T)/numpy.linalg.norm(Y,axis=1)**2).flat).dot(Y)
  #  print(proj)
    Y = numpy.vstack((Y, X[i,:] - proj.sum(0)))
  if norm:
    Y = numpy.diag(1/numpy.linalg.norm(Y,axis=1)).dot(Y)
  if row_vecs:
    return Y
  else:
    return Y.T

def wqr(X, col_vecs=True, norm = True, wt=None):
  '''
  Weighted QR decomposition by Gram-Schmidt process

  Syntax: Q,R = wqr(X, col_vecs=True, norm = True, wt=None)

  Input:
  X           m by n      a 2D array with data to be orthogonalized
  col_vecs    bool        True if X is column-wise; otherwise, it is row-wise
                          default is True (column-wise)
  norm        bool        True to provide the normalized orhtogonal vectors
  wt          m by 1      a 2D array with weighting function for orthogonalization process.
                          Default considers the uniform weights for the vectors

  Return:
  Q           m by n      a 2D array of orthogalized vectors
  R           n by n      the upper triangular matrix such that X=QR

  Remark:
  1) Row-wise data implies X=[[g1],[g2],[g3],...] where gi vector has shape of 1 by n
     Column-wise data implies X=[[g1,g2,g3,]] where gi vector has shape of n by 1
  2) If row-wise data is provided, the inversion relationship will be A=R*Q

  '''
  if not col_vecs:
    X = X.T

  m,n=X.shape

  if wt is None:
    wt=numpy.ones(X[:,0:1].shape)
  else:
    wt=wt.reshape(m,1)
  wtdlg=numpy.diag(wt.flat)

  R=numpy.zeros((n,n))
  Rt=numpy.zeros((n,n))
  Y = X[:,0:1].copy()
  for i in range(1, n):
    Rt[i,0:i]=numpy.dot(Y.T.dot(wtdlg),X[:,i:i+1]).T
    proj= (numpy.diag((Rt[i,0:i]/numpy.dot(wt.T,Y*Y)).flat).dot(Y.T)).T
    Rt[i,0:i]=Rt[i,0:i]/numpy.sqrt(numpy.dot(wt.T,Y*Y))# /numpy.sqrt(numpy.dot(wt.T,Y[:,i-1:i]*Y[:,i-1:i]))
    Y = numpy.hstack((Y, X[:,i:i+1] - proj.sum(1).reshape(m,1)))
  R=Rt.T
  for i in range(n):
    R[i,:]=R[i,:]/numpy.sqrt(numpy.dot(wt.T,Y[:,i:i+1]*Y[:,i:i+1]))
  # numpy.fill_diagonal(R,numpy.sqrt(numpy.dot(wt.T,Y*Y)).flat)
    numpy.fill_diagonal(R,numpy.ones(n))
  if norm:
    R = numpy.dot(numpy.diag(numpy.sqrt(numpy.dot(wt.T,Y*Y)).flat),R)
    Y = numpy.dot(Y,numpy.diag((numpy.sqrt(1/numpy.dot(wt.T,Y*Y)).flat)))
  if col_vecs:
    return Y,R
  else:
    return Y.T,R.T

if __name__ == "__main__":
  X=numpy.random.rand(5,10)
  Q1=gs(X)
  Q2,R2=wqr(X.T)
  print(numpy.allclose(Q1.T,Q2))
  print(numpy.allclose(X.T,Q2.dot(R2)))
  I=numpy.diag(numpy.ones(X.shape[0]))
  print(numpy.allclose(I,(Q2.T).dot(Q2)))


###############################################################################
#                                                                             #
# Copyright (C) 2010 Edward d'Auvergne                                        #
#                                                                             #
# This file is part of the program relax.                                     #
#                                                                             #
# relax is free software; you can redistribute it and/or modify               #
# it under the terms of the GNU General Public License as published by        #
# the Free Software Foundation; either version 2 of the License, or           #
# (at your option) any later version.                                         #
#                                                                             #
# relax is distributed in the hope that it will be useful;                    #
# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
# GNU General Public License for more details.                                #
#                                                                             #
# You should have received a copy of the GNU General Public License           #
# along with relax; if not, write to the Free Software                        #
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA   #
#                                                                             #
###############################################################################

# Module docstring.
"""Module for transforming between different coordinate systems."""

# Python module imports.
from math import acos, atan2, cos, sin
from numpy import array, float64, zeros
from numpy.linalg import norm

def cartesian_to_spherical(vector):
    """Convert the Cartesian vector [x, y, z] to spherical coordinates [r, theta, phi].

    The parameter r is the radial distance, theta is the polar angle, and phi is the azimuth.

    @param vector:  The Cartesian vector [x, y, z].
    @type vector:   numpy rank-1, 3D array
    @return:        The spherical coordinate vector [r, theta, phi].
    @rtype:         numpy rank-1, 3D array
    """

    # The radial distance.
    r = norm(vector)

    # Unit vector.
    unit = vector / r

    # The polar angle.
    theta = acos(unit[2])

    # The azimuth.
    phi = atan2(unit[1], unit[0])

    # Return the spherical coordinate vector.
    return r, theta, phi

def spherical_to_cartesian(spherical_vect):
    """Convert the spherical coordinate vector [r, theta, phi] to the Cartesian vector [x, y, z].

    The parameter r is the radial distance, theta is the polar angle, and phi is the azimuth.

    @param spherical_vect:  The spherical coordinate vector [r, theta, phi].
    @type spherical_vect:   3D array or list
    @param cart_vect:       The Cartesian vector [x, y, z].
    @type cart_vect:        3D array or list
    """

    # Trig alias.
    sin_theta = sin(spherical_vect[1])

    # The vector.
    x = spherical_vect[0] * cos(spherical_vect[2]) * sin_theta
    y = spherical_vect[0] * sin(spherical_vect[2]) * sin_theta
    z = spherical_vect[0] * cos(spherical_vect[1])
    return x, y, z

# print cartesian_to_spherical(array([1.4,1.3,0.2]))


import numpy as np

with open("input.txt") as f:
  lines = f.readlines()

inlist = [list(l.strip()) for l in lines]
A = np.array(inlist, dtype=int)
assert A.shape[0] == A.shape[1]
bound = A.shape[0]

# Repeat input incrementing values block-wise
A = np.tile(A,(5,5))
for x,y in np.ndindex(5,5):
  A[bound*x:bound*(x+1), bound*y:bound*(y+1)] += x+y
A[A > 9] -= 9
bound = A.shape[0]
print(A.shape)

# Modified Dijkstra's algorithm - only track intermediate vals that have been visited
source = (0,0)
dest = (bound-1, bound-1)
dist = {}
visited = set()

dist[source] = 0

while dist:
  min_dist_key = min(dist, key=dist.get)
  min_dist_val = dist.pop(min_dist_key)
  visited.add(min_dist_key)
  print(len(dist))

  if min_dist_key == dest:
    print("DONE")
    print(min_dist_val)
    break

  # For unvisited neighbours of current location
  newC = np.repeat([min_dist_key],4,axis=0) + np.array([[1,0], [-1,0], [0,1], [0,-1]])
  for i in newC:
    if any(i<0) or any(i>=bound):
      continue
    i = tuple(i)
    cand_dist = min_dist_val + A[i]
    if i in visited:
      continue
    if i not in dist:
      dist[i] = cand_dist
    else:
      if cand_dist < dist[i]:
        dist[i] = cand_dist


# Description:
#   Exercise3 utils.py.
#
# Copyright (C) 2018 Santiago Cortes, Juha Ylioinas
#
# This software is distributed under the GNU General Public
# Licence (version 2 or later); please refer to the file
# Licence.txt, included with the software, for details.

from __future__ import division

import numpy as np
from types import *
from scipy.ndimage.interpolation import map_coordinates

# convert from rgb to grayscale image
def rgb2gray(rgb):

    r, g, b = rgb[:,:,0], rgb[:,:,1], rgb[:,:,2]
    gray = 0.2125 * r + 0.7154 * g + 0.0721 * b

    return gray

# salt-and-pepper noise generator
def imnoise(img, mode, prob):
    imgn = img.copy()
    if mode == 'salt & pepper':
        assert (prob >= 0 and prob <= 1), "prob must be a scalar between 0 and 1"
        h, w = imgn.shape
        prob_sp = np.random.rand(h, w)
        imgn[prob_sp < prob] = 0
        imgn[prob_sp > 1 - prob] = 1

    return imgn

# Gaussian noise generator
def add_gaussian_noise(img, noise_sigma):
    temp_img = np.copy(img)
    h, w = temp_img.shape
    noise = np.random.randn(h, w) * noise_sigma
    noisy_img = temp_img + noise
    return noisy_img

# 2d Gaussian filter
def gaussian2(sigma, N=None):

    if N is None:
        N = 2*np.maximum(4, np.ceil(6*sigma))+1

    k = (N - 1) / 2.

    xv, yv = np.meshgrid(np.arange(-k, k+1), np.arange(-k, k+1))

    # 2D gaussian filter
    g = 1/(2 * np.pi * sigma**2) * np.exp(-(xv**2 + yv**2) / (2 * sigma ** 2))

    # 1st order derivatives
    gx = -xv / (2 * np.pi * sigma**4) * np.exp(-(xv**2 + yv**2) / (2 * sigma ** 2))
    gy = -yv / (2 * np.pi * sigma**4) * np.exp(-(xv**2 + yv**2) / (2 * sigma**2))

    # 2nd order derivatives
    gxx = (-1 + xv**2 / sigma**2) * np.exp(-(xv**2 + yv**2) / (2*sigma**2)) / (2 * np.pi * sigma**4)
    gyy = (-1 + yv**2 / sigma**2) * np.exp(-(xv**2 + yv**2) / (2*sigma**2)) / (2 * np.pi * sigma**4)
    gxy = (xv * yv) / (2 * np.pi * sigma**6) * np.exp(-(xv**2 + yv**2) / (2*sigma**2))

    return g, gx, gy, gxx, gyy, gxy

# fit an affine model between two 2d point sets
def affinefit(x, y):
    # Ordinary least squares (check wikipedia for further details):
    #
    # Y                          = P*X_aug            % X_aug is in homogenous coords (one sample per col)
    # Y'                         = X_aug'*P'          % take transpose from both sides
    # X_aug*Y'                   = X_aug*X_aug'*P'    % multiply both sides from left by X_aug
    # inv(X_aug*X_aug')*X_aug*Y' = P'                 % multiply both sides from left by the inverse of X_aug*X_aug'
    n = x.shape[0]
    x = x.T
    y = y.T
    x_aug = np.concatenate((x, np.ones((1, n))), axis=0)
    y_aug = np.concatenate((y, np.ones((1, n))), axis=0)
    xtx = np.dot(x_aug, x_aug.T)
    xtx_inv = np.linalg.inv(xtx)
    xtx_inv_x = np.dot(xtx_inv, x_aug)
    P = np.dot(xtx_inv_x, y_aug.T)
    A = P.T[0:2, 0:2]
    b = P.T[0:2, 2]

    return A, b


from typing import Tuple
import numpy as np

from ..misc.utils import isType

def firstOrderSpline(x: np.ndarray, y: np.ndarray):
    """
    Computes the coefficients of a first order spline, according to the formulas:

    .. math::
        a_k=\\frac{y_{k+1}-y_k}{x_{k+1}-x_k}
    .. math::
        b_k=y_k-a_k*x_k

    :param x: function arguments (x axis)
    :param y: function values (y axis)

    :return: the coefficients of the linear function in the tuple of shape (a0, a1)"""

    if not isType(x, np.ndarray) or not isType(y, np.ndarray) or len(x) != len(y):
        return None

    a = []
    b = []

    for k in range(len(x) - 1):
        a.append((y[k + 1] - y[k]) / (x[k + 1] - x[k]))
        b.append(y[k] - a[k] * x[k])

    return (a, b)

def cubicSpline(x: np.ndarray, y: np.ndarray) -> Tuple[float, float, float, float]:
    """
    Computes the coefficients of a third order (cubic) spline

    :param x: function arguments (x axis)
    :param y: function values (y axis)

    :return: the coefficients in the tuple of shape (a0, a1, a2, a3)"""

    if not isType(x, np.ndarray) or not isType(y, np.ndarray) or len(x) != len(y):
        return np.nan, np.nan, np.nan, np.nan

    h = np.zeros(len(y))
    d = np.zeros(len(y))
    lmb = np.zeros(len(y))
    rho = np.zeros(len(y))
    m = np.zeros(len(y))
    b3 = np.zeros(len(y) - 1)
    b2 = np.zeros(len(y) - 1)
    b1 = np.zeros(len(y) - 1)
    b0 = np.zeros(len(y) - 1)

    tgtLen = len(x) - 1

    for k in range(tgtLen):
        h[k] = x[k + 1] - x[k]
        d[k] = (y[k + 1] - y[k]) / h[k]

    for k in range(tgtLen):
        lmb[k + 1] = h[k + 1] / (h[k] + h[k + 1])
        rho[k + 1] = h[k] / (h[k] + h[k + 1])

    for k in range(tgtLen - 1):
        m[k + 1] = 3 * (d[k + 1] - d[k]) / (h[k] + h[k + 1]) - \
            m[k] * rho[k + 1] / 2 - m[k + 2] * lmb[k + 1] / 2

    for k in range(tgtLen):
        b0[k] = y[k]
        b1[k] = d[k] - h[k] * (2 * m[k] + m[k + 1]) / 6
        b2[k] = m[k] / 2
        b3[k] = (m[k + 1] - m[k]) / 6 * h[k]

    xCut = x[:-1]
    a3 = b3
    a2 = b2 - 3 * b3 * xCut
    a1 = b1 - 2 * b2 * xCut + 3 * b3 * xCut**2
    a0 = b0 - b1 * xCut + b2 * xCut**2 - b3 * xCut**3

    return (a0, a1, a2, a3)


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

def mat_multiply(ax, bx):
    # 二维矩阵乘法实现
    try:
        ax, bx = np.asmatrix(ax), np.asmatrix(bx)
    except Exception as e:
        raise e
    m, p = ax.shape
    q, n = bx.shape
    if p == q:
        res = np.zeros([m, n])
        for i in range(m):
            for j in range(n):
                for k in range(p):
                    res[i, j] += (ax[i, k] * bx[k, j])
        return res
    else:
        print(p, q, 'shapes', ax.shape, 'and', bx.shape, 'not aligned')

def demo_function_convolution(p0=[1, 2, 3], p1=[4, 5, 6]):
    a = np.array(p0)
    b = np.array(p1)
    c = np.convolve(a, b)
    print('使用np.convolve函数卷积：\n', c)

    c = scipy.signal.convolve(a, b)
    print('使用scipy.signal.convolve函数卷积：\n', c)

    c = []
    for iii in range(len(a)+len(b)-1):
        s = 0
        for jjj in range(iii+1):
            if 0+jjj < len(a) and iii-jjj < len(b):
                s += a[0+jjj] * b[iii-jjj]
        c.append(s)
    print('使用反褶循环位移乘法实现卷积：\n', c)

def demo_function_matrix_multiply(mat0=[[1, 2], [3, 4]], mat1=[[6], [5]]):
    print('自定义函数实现矩阵乘法：\n', mat_multiply(mat0, mat1))
    print('使用numpy.dot函数矩阵乘法：\n', np.dot(mat0, mat1))

if __name__ == "__main__":
    # demo_function_convolution([1, 3, 6], np.arange(10))
    demo_function_matrix_multiply()


#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Módulo bissecção: Método da bissecção ou dicotomia para resolver equações

import math

import numpy as np

# Resolve equação pelo método da bissecção.
# Entradas: função, extremos do chute inicial, tolerancia e criticidade de parada
# Retorno: solução, número de iterações
def bissec(f, m1, m2, tol, crit_parada, prin=False):
    tab = [["iter", "m1", "m2", "alpha", "f(alpha)"]]
    itera = 1
    bool_val = True
    # Executa iterações enquanto bool_val for verdadeiro
    while bool_val:
        alpha = 0.5 * (m1 + m2)
        linha = [itera, m1, m2, alpha, f(alpha)]
        tab.append(linha)
        if f(alpha) * f(m1) < 0:
            m2 = alpha
        if f(alpha) * f(m1) > 0 or f(alpha) * f(m1) == 0:
            m1 = alpha
        itera += 1
        # Verifica os critérios de parada
        if crit_parada == 0:
            bool_val = (0.25 * abs(m1 - m2) > tol)
        elif crit_parada == 1:
            bool_val = (f(alpha - tol) * f(alpha + tol) > 0)
    if prin:
        print(np.matrix(tab))
    return alpha, itera

# ----------------teste----------------
if __name__ == "__main__":

    print("teste1")

    def f(x):
        return 3 * x ** 3 - 2 * x ** 2 + 3 * x - 2

    tol = 10 ** (-2)

    m1 = -1
    m2 = 1

    bissec(f, m1, m2, tol, 0, True)

    bissec(f, m1, m2, tol, 1, True)

    print("teste2")

    def f(x):
        return math.sin(x) - math.cos(x)

    tol = 10 ** (-10)

    m1 = 0
    m2 = math.pi / 2  # a raíz da equação no intervalo desejado é pi/4

    bissec(f, m1, m2, tol, 0, True)

    bissec(f, m1, m2, tol, 1, True)

    # definindo novos limites
    m1 = 0
    m2 = 2

    bissec(f, m1, m2, tol, 0, True)

    bissec(f, m1, m2, tol, 1, True)


"""
Written by Ivan Debono
mail [at] ivandebono [dot] eu

23 April 2017

PURPOSE Output the Copeland-Erdős constant up to desired nth prime
        (See http://mathworld.wolfram.com/Copeland-ErdosConstant.html)

INPUT   n: position of last prime (e.g. last=10 means up to 10th prime)

OUTPUT  copeland_erdos_str: string representation of Copeland-Erdős constant
        copeland_erdos: Copeland-Erdős constant, in Python Decimal format
        (i.e. floating point with any user-defined precision),
        to same number of primes as n
"""

from decimal import getcontext, Decimal
import numpy as np
import sympy

def copelanderdos(n):

    # First generate list of primes up to n
    primes=[]
    for prime in np.arange(1,n+1): primes.append(sympy.ntheory.generate.prime(int(prime)))

    copeland_erdos_str='0.'+''.join(str(p) for p in primes)
    getcontext().prec = len(copeland_erdos_str)
    copeland_erdos_flt=Decimal(copeland_erdos_str)

    return copeland_erdos_str,copeland_erdos_flt

"""
Written by Ivan Debono
mail [at] ivandebono [dot] eu

October 2016
Modified 22 April 2017

PURPOSE Output the Champernowne constant up to desired digit
        (See http://mathworld.wolfram.com/ChampernowneConstant.html)

INPUT   last: last digit
        base: base representation for digit. Default = 10

OUTPUT  champernowne_str: string representation of Champernowne's constant
        champernowne_flt: Champernowne's constant, in Python Decimal format
        (i.e. floating point with any user-defined precision),
        to same number of digits as last digit
"""

def champernowne(last,base=10):

    champernowne_str = "0."
    for c in range(1,last+1):
        champernowne_str += str(np.base_repr(c,base=base))
    getcontext().prec = len(champernowne_str)
    champernowne_flt=Decimal(champernowne_str)
    return champernowne_str,champernowne_flt

"""
Written by Ivan Debono
mail [at] ivandebono [dot] eu

22 June 2020

PURPOSE Output the Thue-Morse sequence T_n up to the desired n

INPUT   n: length of sequence

OUTPUT  s: string representation of Thue-Morse sequence

"""

def negation(s):
    return s.replace('1', '2').replace('0', '1').replace('2', '0')

def thuemorse(n):
    s=str(0)
    for i in np.arange(n):
        s=str(s+negation(s))
    return s


"""Implements Cholesky Matrix Factorization A = LL.T, if A is a positive defined matrix
"""
import numpy as np

def main():
    # Matrix example
    A = np.array([[1, 1, 3],
                  [1, 5, 5],
                  [3, 5, 19]], dtype=float)
    L = cholesky(A)
    print(f"L = \n {L}")
    print(f"L@L.T = \n{L@L.T}")
    print(f"A = \n{A}")

def cholesky(A):
    L = A.copy()
    for i in range(len(L)):
        L[i, i] = np.sqrt(L[i, i])
        L[i+1:, i] /= L[i, i]
        L[i, i+1:] *= 0
        L[i+1:, i+1:] -= np.outer(L[i+1:, i], L[i+1:, i])
    return L

if __name__ == '__main__':
    main()


import numpy as np
from .part1 import jointhist

def ssd(i, j):
    """Calculate the sum squared difference between two images of the same size"""
    li, lj = np.ravel(i), np.ravel(j)
    assert len(li) == len(lj), "images have different sizes, please scale your data first!"
    return np.sum(np.square(li - lj))

def corr(i, j):
    """Calculate the pearson correlation coefficient between two images of the same size"""
    li, lj = np.ravel(i), np.ravel(j)
    assert len(li) == len(lj), "images have different sizes, please scale your data first!"
    i_bar, j_bar = np.mean(li), np.mean(lj)
    i_dev, j_dev = li - i_bar, lj - j_bar
    numerator = np.sum(i_dev * j_dev)
    denominator = np.sqrt(np.sum(i_dev ** 2)) * np.sqrt(np.sum(j_dev ** 2))
    return numerator / denominator

def mi(i, j):
    """Calculate the mutual information between two images of the same size"""
    H = jointhist(i, j)  # joint histogram
    H_norm = H / np.sum(H)  # normalized joint histogram
    row_m = np.sum(H_norm, axis=1, keepdims=True, dtype=np.float64)
    col_m = np.sum(H_norm, axis=0, keepdims=True, dtype=np.float64)
    P = row_m * col_m  # product matrix
    P[P == 0] = 1  # avoid the divide by zero error
    log_term = np.log(H_norm / P)
    log_term[~np.isfinite(log_term)] = 0  # avoid the divide by zero error in log
    return np.sum(H_norm * log_term)

def test(i_vec, j_vec):
    """Test the three functions"""
    def r_corr(i, j):
        numerator = np.mean((i - i.mean()) * (j - j.mean()))
        denominator = i.std() * j.std()
        if denominator == 0:
            return 0
        else:
            return numerator / denominator

    def r_mi(i, j):
        from sklearn.metrics import mutual_info_score
        H, _, _ = np.histogram2d(np.ravel(i), np.ravel(j), bins=256)
        return mutual_info_score(None, None, contingency=H)

    for index in range(6):
        print(f"(I{index + 1}, J{index + 1}) real pearson corr = {r_corr(i_vec[index], j_vec[index])}")
        print(f"(I{index + 1}, J{index + 1}) real mutual info = {r_mi(i_vec[index], j_vec[index])}")
        print("---------------------------------------------------")

def run(i_vec, j_vec):
    """Compare the results of the three functions above on (I1,J1) ~ (I6,J6)"""
    for index in range(6):
        print(f"(I{index + 1}, J{index + 1}) sum sqr diff = {ssd(i_vec[index], j_vec[index])}")
        print(f"(I{index + 1}, J{index + 1}) pearson corr = {corr(i_vec[index], j_vec[index])}")
        print(f"(I{index + 1}, J{index + 1}) mutual info = {mi(i_vec[index], j_vec[index])}")
        print("---------------------------------------------------")


import numpy as np
import math
import utils

# decryption function
def decrypt(key_matrix_inv, cipher_text):
    """
    Arguments: key matrix inverse, cipher text
    Returns: plain text
    """
    print("Matrix Inverse is: \n", key_matrix_inv)
    dimensions = len(cipher_text)

    # create cipher text matrix (ASCII Values - 65 to get from 0 to X)

    cipher_text_matrix = []
    for i in range(dimensions):
        cipher_text_matrix.append(ord(cipher_text[i]) - 65)

    cipher_text_matrix = np.array(cipher_text_matrix)
    print("Cipher Key Matrix: \n", cipher_text_matrix)

    # multiply inverse with cipher text matrix
    result = np.array(np.dot(key_matrix_inv, cipher_text_matrix))

    # BUG
    # print(result[0][1], int(result[0][1]))
    print("Decrypted Matrix\n", result)

    # create empty string for plain text
    plain_text = ""
    # convert result matrix to plain text by using chr()
    for i in range(dimensions):
        plain_text += chr(int(round(result[0][i], 0) % 26 + 65))

    # return the decrypted plain text
    return plain_text

if __name__ == "__main__":
    # take input from the user
    plain_text = str(input("Plain Text: "))

    # dimensions of the matrix = length(plain text) x length(plain text)
    dimensions = len(plain_text)

    # plain text matrix
    plain_text_matrix = []

    # creating a column matrix for plain text characters
    for i in range(dimensions):
        plain_text_matrix.append(ord(plain_text[i]) - 65)

    plain_text_matrix = np.array(plain_text_matrix)

    print("Plain Text Matrix\n", plain_text_matrix)

    print("Enter values for the key: ")

    # take values for the key matrix
    key_matrix = []
    for i in range(dimensions):
        row_ = []
        for j in range(dimensions):
            value = int(input(str(i) + ", " + str(j) + " value: "))
            row_.append(value)
        key_matrix.append(row_)

    print("Key Matrix: \n")

    # for encryption
    key_matrix = np.array(key_matrix)
    # for decryption
    # key_matrix_inv = (np.linalg.inv(np.matrix(key_matrix)) % 26)
    # key_matrix_inv = utils.multi_inverse(np.linalg.det(key_matrix), 26) * \
    #        np.matrix(key_matrix).getH()
    # print(np.matrix(key_matrix).getH())

    # calculate key matrix inverse using modulo multiplicative inverse
    key_matrix_inv = np.linalg.inv(np.matrix(key_matrix)) * \
            np.linalg.det(key_matrix) * \
            utils.multi_inverse(np.linalg.det(key_matrix), 26) % 26

    print(key_matrix)

    print("Inverse Key Matrix: \n", key_matrix_inv)

    result = key_matrix.dot(plain_text_matrix)

    print("Cipher Matrix: \n", result)

    cipher_text = ""

    for i in range(dimensions):
        cipher_text += chr(result[i] % 26 + 65)

    print("Cipher Text: \n", cipher_text)

    decrypted_plain_text = decrypt(key_matrix_inv, cipher_text)

    print("Decrypted plain text: ", decrypted_plain_text)


#! /usr/bin/env python3
import numpy as np

def vec_linear_mse(x, y, theta):
    if type(x) != np.ndarray:
        return None
    if type(x) != type(y) or type(theta) != type(x):
        return None
    if x.size == 0 or y.size == 0 or theta.size == 0:
        return None
    if x.shape[0] != y.shape[0] or x.shape[1] != theta.shape[0]:
        return None
    return np.dot((x.dot(theta) - y).transpose(), x.dot(theta) - y) / y.size

"""
if __name__ == "__main__":

    X = np.array([
        [ -6,  -7,  -9],
            [ 13,  -2,  14],
            [ -7,  14,  -1],
            [ -8,  -4,   6],
            [ -5,  -9,   6],
            [  1,  -5,  11],
            [  9, -11,   8]])
    Y = np.array([2, 14, -13, 5, 12, 4, -19])
    Z = np.array([3, 0.5, -6])

    print(vec_linear_mse(X, Y, Z))
    # 2641.0

    W = np.array([0, 0, 0])

    print(vec_linear_mse(X, Y, W))
    # 130.71428571
"""


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

def draw_curve():
    x = np.linspace(-2, 2, 100)
    y = x**4 - 4*x**2

    plt.plot(x, y, color="blue")
    plt.title("Curve incl. local minima")
    plt.xlim((-2, 2))
    plt.ylim((-4.5, 0.2))
    plt.annotate("", xy=(-(2**0.5), -4), xytext=(-1, -4.2), arrowprops=dict(facecolor="red", shrink=0.05))
    plt.plot([-(2**0.5)], [-4], 'o', color="black")
    plt.annotate("", xy=(2**0.5, -4), xytext=(1, -4.2), arrowprops=dict(facecolor="red", shrink=0.05))
    plt.plot([2**0.5], [-4], 'o', color="black")
    plt.show()

def draw_plane():
    x, y = np.meshgrid(np.linspace(-2, 2, 25), np.linspace(-2, 2, 25))
    z = x**4 - 4*x**2 + y**4 - 2*y**2

    ax = plt.figure().gca(projection='3d')
    ax.plot_wireframe(x, y, z)
    ax.plot([-(2**0.5)], [1], [-5], 'o', color="black")
    ax.plot([(2**0.5)], [1], [-5], 'o', color="black")
    ax.plot([-(2**0.5)], [-1], [-5], 'o', color="black")
    ax.plot([(2**0.5)], [-1], [-5], 'o', color="black")
    plt.title("3d plane with four local minima")
    ax.set_xlabel("x")
    ax.set_xticks(range(-2, 3))
    ax.set_ylabel("y")
    ax.set_yticks(range(-2, 3))
    ax.set_zlabel("z")
    ax.set_zticks(range(-5, 9, 2))
    plt.show()

def draw_example_steps_2d(steps):
    x = np.linspace(0, 2, 50)
    y = x**4 - 4*x**2

    plt.plot(x, y, color="blue")
    plt.title("6 steps towards the local minimum")
    plt.xlim((0.25, 1.75))
    plt.ylim((-4.5, 0))
    for i in range(6):
        plt.plot([steps[i]], [steps[i]**4 - 4*steps[i]**2], "o", color = "red")
        plt.text(steps[i]-0.02, steps[i]**4 - 4*steps[i]**2-0.25, i, color = "black")
    plt.show()

def draw_example_steps_3d(steps):
    x, y = np.meshgrid(np.linspace(0.25, 1.75, 25), np.linspace(0.25, 1.6, 25))
    z = x**4 - 4*x**2 + y**4 - 2*y**2

    ax = plt.figure().gca(projection="3d")
    ax.plot_wireframe(x, y, z, alpha = 0.6)
    plt.title("10 steps towards the local minimum")

    for i in range(10):
        ax.plot([steps[i][0]], steps[i][1], [steps[i][0]**4 - 4*steps[i][0]**2 + steps[i][1]**4 - 2*steps[i][1]**2], 'o', color="red")
        ax.text(steps[i][0], steps[i][1], steps[i][0]**4 - 4*steps[i][0]**2 + steps[i][1]**4 - 2*steps[i][1]**2 + 0.25, i, color = "black")

    ax.set_xlabel("x")
    ax.set_ylabel("y")
    ax.set_zlabel("z")
    ax.set_xlim((0.25, 1.75))
    ax.set_ylim((0.25, 1.6))
    plt.show()

def example_gradient_descent_2d():
    x  = 0.5
    lr = 0.1
    steps = []

    for i in range(100):
        steps.append(x)
        print(f"Iteration {i+1:>3}: x = {x}")
        x -= lr * (4*x**3 - 8*x)

    draw_example_steps_2d(steps)

def example_gradient_descent_3d():
    x  = 0.5
    y  = 1.5
    lr = 0.03
    steps = []

    for i in range(100):
        steps.append((x, y))
        print(f"Iteration {i+1:>3}: x = {x} / y = {y}")
        x -= lr * (4*x**3 - 8*x)
        y -= lr * (4*y**3 - 4*y)

    draw_example_steps_3d(steps)

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

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

def gradient_descent_polynomial(degree = 1, lr = 0.001, iterations = 100):
    x = np.linspace(-2, 2, 100)
    y = x**5 - 6 * x**3 + 6 * x + 4 * np.cos(x*6) + 8

    x_extended = add_column_with_ones(x)

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

    w = np.random.rand(degree+1) - 0.5
    for _ in range(iterations):
        gradients = x_extended.T.dot(x_extended.dot(w) - y)
        w = w - lr * gradients

    yhat = x_extended.dot(w)

    plt.text(1.07, 15, "MAE:\nRMSE:\nR2:\nR2-adj:              ", bbox={'facecolor': 'grey', 'alpha': 0.1, 'pad': 5})
    plt.text(1.53, 15, calc_metrics(yhat, y, degree))
    plt.ylim((-4, 20))
    plt.title(f"Gradient Descent Polynomial with degree {degree}", fontsize=9)
    plt.plot(x, y)
    plt.plot(x, yhat)
    plt.show()

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

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

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

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

draw_curve()
draw_plane()

example_gradient_descent_2d()
example_gradient_descent_3d()

gradient_descent_polynomial(3)


"""
Uncomment to see the flow of Operation.
"""

from math import sqrt
from numpy import inf

dist = lambda p1,p2: sqrt((p2[1] - p1[1])**2 + (p2[0] - p1[0])**2)

def bfd(alist):
    """
    Given a presorted list in increasing order by x axis,
    returns the minimum distance and its pair among the points.
    alist: a list of (x,y) points
    """
    ln = len(alist)
    min_d = inf

    for pre_indx in xrange(ln-1):
        for post_indx in xrange(pre_indx+1, ln):
            p1, p2 = alist[pre_indx], alist[post_indx]
            ds = dist(p1, p2)
            if ds < min_d:
                min_d = ds
                pair = p1, p2
    #print "min_d, pair: {} {}\nbfd list: {}\n".format(min_d, pair, alist)
    return min_d, pair

def alist_merge(left_list, right_list):
    """
    returns the distance between the adjoining joints of the two lists and its pair
    """
    ds = dist(left_list[-1], right_list[0])
    return ds, (left_list[-1], right_list[0])

def split_conquer(alist):
    """
    gets a presorted list
    returns the minimum distance and its resp. pair points.
    A pair is a Pair of 2 points.
    """
    ln = len(alist)
    #divide recursively
    mid = ln//2

    #until len is not greater than 3 - Base Case
    if ln <= 3:
        #find the min distance between the 2 points using brute force
        #and return the min_d and point pair
        #print "list going in bfd {}\n".format(alist)
        min_d, pair = bfd(alist)
        return min_d, pair
    else:
        #print "Splitting {}\n".format(alist)
        #Every Recursive Call IS LOG(N)
        left_list = alist[:mid]
        left_min_d, left_pair = split_conquer(left_list)
        right_list = alist[mid:]
        right_min_d, right_pair = split_conquer(right_list)

        #merge two lists of len 2 or less
        #find the min distance between these two merged lists at the mid joint point
        joint_min, joint_pair = alist_merge(left_list, right_list)

        #find overall min distance - delta
        delta = min(left_min_d, joint_min, right_min_d)
        if delta == left_min_d:
            delta_pair = left_pair
        elif delta == right_min_d:
            delta_pair = right_pair
        else:
            delta_pair = joint_pair

        ## Whole Point of Above is to get the "mid point" AND "delta" min val ##
        #Now we find xbase_delta i.e. for mid +/- xbase_delta
        xbase_delta = (delta_pair[1][0] - delta_pair[0][0])

        #Filter all the points within (mid +/- xbase_delta) by x
        #Using Brute Force, find the min of all those filtered points
        #and previous min
        #print "Points being filtered {}\n".format(alist)
        #print "mid - delta {} mid + delta {}\n".format(alist[mid][0] - xbase_delta, alist[mid][0] + xbase_delta)
        #runs in Order k <= N -time - On every recursive call it IS O(k) -list size
        filtered_pts = filter(lambda points: \
        ((alist[mid][0] - xbase_delta) <= points[0] <= alist[mid][0])\
        or (alist[mid][0] < points[0] <= alist[mid][0] + xbase_delta), alist)

        #print "filtered_pts:{}\n".format(filtered_pts)
        if len(filtered_pts)==1:
            return inf, filtered_pts[0]

        filtered_min, pair = bfd(filtered_pts)

        #print "current MINIMUM {}\n".format(filtered_min)

        #return this min distance and the point pair
        return (filtered_min, pair) \
        if min(filtered_min, delta) == filtered_min else (delta, delta_pair)

if __name__ == "__main__":
    from numpy.random import randint
    from cProfile import run
    import time
    from matplotlib.pyplot import plot
    #alist = [(9, 0), (7, 0), (6, 2), (3, 6), (4, 2), (4, 3), (2, 0), (4, 5), (1, 3), (2, 6)]
    tmp = []
    for n in xrange(11, 100):
        up_bound = randint(1,n)
        alist = [(randint(0,up_bound),randint(0,up_bound)) for _ in xrange(n)]
        x,y = zip(*sorted(alist, key =lambda i: i[0]))
        alist = zip(x,y)
        #print bfd(alist)
        #print "sorted list: {}\n".format(alist)
        start = time.time()
        #run("split_conquer(alist)")
        split_conquer(alist)
        end=time.time()-start
        #print "finished in {}\n".format(end)
        tmp.append((len(alist), end))

    size, tym = zip(*tmp)
    #interestingly it did scale
    plot(size, tym)


from numpy import array as mat, sin,\
                  arccos, dot, linspace, sqrt, \
                  sign
from numpy.linalg import norm

def rot_to_quat(R):

    r11, r12, r13,\
    r21, r22, r23,\
    r31, r32, r33 = R.reshape(9)

    q0 = ( r11 + r22 + r33 + 1.0) / 4.0
    q1 = ( r11 - r22 - r33 + 1.0) / 4.0
    q2 = (-r11 + r22 - r33 + 1.0) / 4.0
    q3 = (-r11 - r22 + r33 + 1.0) / 4.0
    if(q0 < 0.0):
        q0 = 0.0
    if(q1 < 0.0):
        q1 = 0.0
    if(q2 < 0.0):
        q2 = 0.0
    if(q3 < 0.0):
        q3 = 0.0
    q0 = sqrt(q0)
    q1 = sqrt(q1)
    q2 = sqrt(q2)
    q3 = sqrt(q3)
    if((q0 >= q1) and (q0 >= q2) and (q0 >= q3)):
        q0 *= +1.0
        q1 *= sign(r32 - r23)
        q2 *= sign(r13 - r31)
        q3 *= sign(r21 - r12)
    elif((q1 >= q0) and (q1 >= q2) and (q1 >= q3)):
        q0 *= sign(r32 - r23)
        q1 *= +1.0
        q2 *= sign(r21 + r12)
        q3 *= sign(r13 + r31)
    elif((q2 >= q0) and (q2 >= q1) and (q2 >= q3)):
        q0 *= sign(r13 - r31);
        q1 *= sign(r21 + r12);
        q2 *= +1.0
        q3 *= sign(r32 + r23);
    elif(q3 >= q0 and q3 >= q1 and q3 >= q2):
        q0 *= sign(r21 - r12);
        q1 *= sign(r31 + r13);
        q2 *= sign(r32 + r23);
        q3 *= +1.0
    else:
        print("coding error\n")

    q = mat( (q0, q1, q2, q3) )
    q = q / norm(q)

    return q

def quat_to_rot(q):

    q0, q1, q2, q3 = q

    r1 = [
        q0**2 + q1**2 - q2**2 - q3**2,
        2*(q1*q2 + q0*q3),
        2*(q1*q3 - q0*q2)
        ]

    r2 = [
        2*(q1*q2 - q0*q3),
        q0**2 - q1**2 + q2**2 - q3**2,
        2*(q2*q3 + q0*q1)
        ]

    r3 = [
        2*(q1*q3 + q0*q2),
        2*(q2*q3 - q0*q1),
        q0**2 - q1**2 - q2**2 + q3**2
        ]

    R = mat(zip(r1,r2,r3))
    return R

def quat_conj(q):
    return mat((q[0], -q[1], -q[2], -q[3]))

def quat_unit(q):
    return (mat(q) / norm(q))

def quat_inv(q):
    return(quat_conj(q) / (norm(q)**2))

def quat_mul(q1, q2):
    """
    q1*q2 = (w1,x1,y1,z1)*(w2,x2,y2,z2)
          = ( w1*w2-x1*x2-y1*y2-z1*z2 ,
              w1*x2+x1*w2+y1*z2-z1*y2 ,
              w1*y2+y1*w2+z1*x2-x1*z2 ,
              w1*z2+z1*w2+x1*y2-y1*x2  )
    """
    result = ( q1[0] * q2[0] - q1[1] * q2[1] - q1[2] * q2[2] - q1[3] * q2[3],
               q1[0] * q2[1] + q1[1] * q2[0] + q1[2] * q2[3] - q1[3] * q2[2],
               q1[0] * q2[2] + q1[2] * q2[0] + q1[3] * q2[1] - q1[1] * q2[3],
               q1[0] * q2[3] + q1[3] * q2[0] + q1[1] * q2[2] - q1[2] * q2[1] )
    return mat(result)

def quat_rot_v(q,v):
    v = mat([0] + list(v))
    products = (quat_inv(q), v , q)
    result = reduce(quat_mul, products)
    return mat(result)

def slerp(q1, q2, u):
    """
    u : [0, ..., 1]
    """
    theta = arccos(dot(q1, q2))
    V = sin((1-u)*theta) / sin(theta)
    W = sin(u*theta) / sin(theta)
    return mat(V*mat(q1) + W*mat(q2))

def lerp(p1, p2, u):
    """
    u : [0, ..., 1]
    """
    return mat(p1)*(1-u) + u*mat(p2)

def point_lerp(p1, p2, num_points=50):
    interp = linspace(0, 1, num_points)
    result = mat(map(lambda u: lerp(p1, p2, u), interp))
    return result

def quat_slerp(q1, q2, num_points=50):
    interp = linspace(0, 1, num_points)
    result = mat(map(lambda u: slerp(q1, q2, u), interp))
    return result


import numpy

def solve_tridiagonal_system(N, a, b, c, r):
  """Efficiently solve a tridiagonal system.

  For example if,

   x +  y = 3
   y +  z = 5
   y + 2z = 8

  then,

  A = 3x3
      [   1    1    0
          0    1    1
          0    1    2 ]

  and r = [ 3, 5, 8 ]' for which the expected
  result is x = [1, 2, 3].

  >>> a, b, c = [None, 0, 1], [1, 1, 2], [1, 1, None]
  >>> r =[3, 5, 8]
  >>> print solve_tridiagonal_system(3, a, b, c, r)
  [ 1.  2.  3.]

  """
  u, gam = numpy.zeros(N), numpy.zeros(N)
  bet = b[0]
  if bet == 0.0:
    raise RuntimeError, "Solve diagonal system error"
  u[0] = r[0]/bet
  for j in range(1, N):
    gam[j] = c[j - 1]/bet
    bet = b[j]- a[j]*gam[j]
    if bet == 0.0:
      raise RuntimeError, "Solve diagonal system error"
    u[j] = (r[j] - a[j]*u[j - 1])/bet
  for j in range(N - 2, -1, -1):
    u[j] -= gam[j + 1]*u[j + 1]

  return u

def solve_upper_diagonal_system(a, b):
  """Efficiently solve an upper diagonal system.

  For example, if

    A = 3 x 3
        [  1.75   1.5   -2.5
           0     -0.5    0.65
           0      0      0.25 ]
  and

    b = [  0.5   -1      3.5],

  the expected result is x = [2.97142857  20.2  14].

  >>> from numpy import *
  >>> A = matrix(array(
  ... [[1.75, 1.5, -2.5],
  ... [0.0, -0.5, 0.65],
  ... [0.0, 0.0, 0.25]], float))
  >>> A
  matrix([[ 1.75,  1.5 , -2.5 ],
          [ 0.  , -0.5 ,  0.65],
          [ 0.  ,  0.  ,  0.25]])
  >>> b = array([0.5, -1.0, 3.5])
  >>> b
  array([ 0.5, -1. ,  3.5])
  >>> x = solve_upper_diagonal_system(A, b)
  >>> x = matrix(x).transpose() # column vector
  >>> x
  matrix([[  2.97142857],
          [ 20.2       ],
          [ 14.        ]])
  >>> A*x  #matrix vector product
  matrix([[ 0.5],
          [-1. ],
          [ 3.5]])

  """
  if len(a.shape) <> 2:
    raise RuntimeError, "Expected 'a' to be a matrix"
  if a.shape[0] <> a.shape[1]:
    raise RuntimeError, "Expected 'a' to be a square matrix"
  if len(b.shape) <> 1:
    raise RuntimeError, "Expected 'b' to be a column vector"
  if b.shape[0] <> a.shape[0]:
    raise RuntimeError, "Expected 'b' to be a column vector"
  N = a.shape[0]
  for i in range(N):
    if a[i, i] == 0.0:
      raise RuntimeError, "Singular upper diagonal matrix"
    for j in range(0, i):
      if a[i, j] <> 0.0: raise RuntimeError, "Matrix not upper diagonal"

  x = numpy.zeros(N)
  for i in range(N-1, -1, -1):
    tmp = 0.0
    for j in range(i+1, N):
      tmp += a[i, j]*x[j]
    x[i] = (b[i]-tmp)/a[i, i]

  return x

def singular_value_decomposition_back_substitution(u, w, v, b):
  """Solve an upper diagonal system using svd.

  For example, if

    A = 3 x 3
        [  1.75   1.5   -2.5
           0     -0.5    0.65
           0      0      0.25 ]
  and

    b = [  0.5   -1      3.5],

  the expected result is x = [2.97142857  20.2  14].
  >>> from numpy import *
  >>> from numpy.linalg import svd
  >>> A = matrix(array(
  ... [[1.75, 1.5, -2.5],
  ... [0.0, -0.5, 0.65],
  ... [0.0, 0.0, 0.25]], float))
  >>> A
  matrix([[ 1.75,  1.5 , -2.5 ],
          [ 0.  , -0.5 ,  0.65],
          [ 0.  ,  0.  ,  0.25]])
  >>> b = array([0.5, -1.0, 3.5])
  >>> b
  array([ 0.5, -1. ,  3.5])
  >>> u, w, v = svd(A)
  >>> x = singular_value_decomposition_back_substitution(u, w, v, b)
  >>> x = matrix(x).transpose() # column vector
  >>> x
  matrix([[  2.97142857],
          [ 20.2       ],
          [ 14.        ]])
  """

  if len(u.shape) <> 2:
    raise RuntimeError, "Expected 'u' to be a matrix"
  if len(w.shape) <> 1:
    raise RuntimeError, "Expected 'w' to be a column vector"
  if len(v.shape) <> 2:
    raise RuntimeError, "Expected 'v' to be a matrix"
  if len(b.shape) <> 1:
    raise RuntimeError, "Expected 'b' to be a column vector"

  m = u.shape[0]
  n = u.shape[1]

  if w.shape[0] <> n:
    raise RuntimeError, "'w' column vector has incorrect size"
  if b.shape[0] <> m:
    raise RuntimeError, "'b' column vector has incorrect size"
  if v.shape[0] <> n or v.shape[1] <> n:
    raise RuntimeError, "'v' matrix has incorrect size"

  tmp = numpy.zeros(n)
  for j in range(n):
    s = 0.0
    if w[j] <> 0:
      for i in range(m):
        s += u[i, j]*b[i]
      s /= w[j]
    tmp[j] = s
  x = numpy.zeros(n)
  for j in range(n):
    s = 0.0
    for jj in range(n):
      s += v[jj, j]*tmp[jj]
    x[j] = s
  return x

def _test():
  import doctest
  doctest.testmod()

if __name__ == '__main__':
    _test()


from context import fe621

import numpy as np
import pandas as pd

# Portfolio Metadata
port_init_val = 1e7  # Portfolio value
port_weights = np.array([.4, .3, .3])  # IBM, 10 yr Treasury, Yuan
initial_prices = np.array([80, 90000, 6.1])
asset_labels = ['IBM Equity', '10-Year T-Bill', 'CNY/USD ForEx']

# Portfolio initial stats
# Inverting CNY/USD rate as we're buying in USD
initial_prices_corrected = np.append(initial_prices[:-1], 1 / initial_prices[2])
# Flooring and casting to int as we can't buy fractional units
port_positions = np.floor((port_weights * port_init_val
    / initial_prices_corrected)).astype(int)

# Simulation data
sim_count = int(3e6)
dt = 0.001
t = 10 / 252
eval_count = int(np.ceil(t / dt))

# Processes
n_xt = lambda xt, w: xt + ((0.01 * xt * dt) + (0.3 * xt * np.sqrt(dt) * w))
n_yt = lambda yt, w, t: yt + (100 * (90000 + (1000 * t) - yt) * dt + (np.sqrt(yt)
    * np.sqrt(dt) * w))
n_zt = lambda zt, w: zt + ((5 * (6 - zt) * dt) + (0.01 * np.sqrt(zt)
    * np.sqrt(dt) * w))

# Function to compute portfolio value, given asset prices
def portfolioValue(asset_prices: np.array):
    # Making copy of asset prices (to not edit original array)
    asset_prices = np.copy(asset_prices)
    # Inverting last current price value (it is CNY/USD; we're buying in USD)
    asset_prices[2] = 1 / asset_prices[2]

    # Returning portfolio value (sum of elem-wise product across positions)
    return np.sum(np.multiply(asset_prices, port_positions))

# Simulation function
def sim_func(x: np.array) -> float:
    # Input: (3 x eval_count) matrix
    # Isolating initial prices (only need to maintain last observation)
    current_prices = np.copy(initial_prices)
    # Iterating over columns (i.e. in 3x1 chunks)
    for idx, w_vec in enumerate(x.T):
        current_prices = np.array([
            n_xt(current_prices[0], w_vec[0]),
            n_yt(current_prices[1], w_vec[1], dt * (idx + 1)),
            n_zt(current_prices[2], w_vec[2])
        ])

    return portfolioValue(current_prices)

# Running simulation
sim_data = fe621.monte_carlo.monteCarloSkeleton(
    sim_count=sim_count,
    eval_count=eval_count,
    sim_func=sim_func,
    sim_dimensionality=3
)

def exportInitialData():
    """Function to export initial portfolio data (answering question 2(a))
    """
    # Building output dictionary with necessary data
    # Specifically, positions, USD value, and CNY value
    output = dict()
    output['Positions'] = list(port_positions) + ['-']
    output['Position Value (USD)'] = np.append(np.multiply(
        initial_prices_corrected,
        port_positions
    ), portfolioValue(initial_prices))
    output['Position Value (CNY)'] = output['Position Value (USD)'] * initial_prices[2]

    # Building output dataframe, formatting and saving to CSV
    out_df = pd.DataFrame(output, index=[*asset_labels, 'Total'])
    out_df.to_csv('Homework 4/bin/q2_port_data.csv', float_format='%.0f')

def performRiskAnalytics():
    """Function to compute and export the portfolio VaR and CVaR (2(b) & 2(c))
    """

    # Output data dictionary
    output = dict()

    # VaR config
    N = 10
    alpha = 0.01

    # Computing simulation stats
    sim_stats = fe621.monte_carlo.monteCarloStats(mc_output=sim_data)

    # Computing value at risk (VaR) using the quantile method
    var = sim_stats['estimate'] - np.quantile(sim_data, alpha)
    var_daily = var / np.sqrt(N)
    output['VaR ($)'] = [var, var_daily]
    output['VaR (%)'] = np.array(output['VaR ($)']) / port_init_val * 100

    # Isolating portfolios that perform worse than the VaR risk threshold
    shortfall_ports = sim_data[sim_data <= np.quantile(sim_data, alpha)]
    # Computing conditional value at risk (cVaR) using the quantile method
    cvar = np.mean(sim_stats['estimate'] - shortfall_ports)
    cvar_daily = cvar / np.sqrt(N)
    output['CVaR ($)'] = [cvar, cvar_daily]
    output['CVaR (%)'] = np.array(output['CVaR ($)']) / port_init_val * 100

    # Building output dataframe, formatting and outputting to CSV
    out_df = pd.DataFrame(output, index=['10 Day', '1 Day']).T

    out_df.to_csv('Homework 4/bin/q2_risk_analytics.csv', float_format='%.4f')

if __name__ == '__main__':
    # Part (1)
    # exportInitialData()

    # Part (2)
    performRiskAnalytics()


# -*- coding: utf-8 -*-
"""
Es1
A triangolare sup
"""
import numpy as np

def Usolve(U, b):
    m,n = U.shape
    x = np.zeros((m,1))
    if m != n or np.all(np.diag(U)) != True:
        print("Matrice non risolvibile")
        return [], 1
    for k in reversed(range(n)):
        x[k] = (b[k] - np.dot(U[k, k+1:n], x[k+1:n])) / U[k,k]
    return x, 0

def Lsolve(L,b):
    m,n = L.shape
    if m != n or np.all(np.diag(L)) != True:
        print("Matrice non risolvibile")
        return [], 1
    x = np.zeros((n, 1))
    for k in range(n):
        x[k] = (b[k] - np.dot(L[k, 0:k], x[0:k])) / L[k,k]
    return x, 0

"""
    ESERCIZIO 3
    Parto da PAx = b
    ottengo Ax = Pb
    nonchè LUx = Pb
    pongo Ux = y
    Ottenendo
    { Ux = y
     Ly = Pb
    }               """
def LUsolve(P, L, U, b):
    # Risultati Permutati
    Pb = np.dot(P,b)
    y = Lsolve(L, Pb)
    x = Usolve(U, y)
    return x

""" ESERCIZIO 4 """

def LUnoPivot(A):
    m,n = A.shape
    if m != n:
        print("Matrice non quadrata")
        return [],[],[], 1
    if np.all(np.diag(A)) != True:
        print("Elemento diagonale nullo")
        return [], [], [], 1
    U = A.copy()
    for k in range(n-1):
        if U[k,k] == 0:
            print("Elemento diagonale nullo")
            return [], [], [], 1
        for i in range(k+1, n-1):
            U[k,i] = U[k,i] / U[k,k]
            for j in range(k+1, n-1):
                U[i,j] = U[i,j] - np.dot(U[i,k], U[k,j])
    L = np.ltril(U,-1) + np.identity(m)
    U = np.utril(U)
    P = np.identity(m)
    return P,L,U,0

def LUsolveNoPivotVett(A):
    m, n = A.shape
    if m != n:
        print("Matrice non quadrata")
        return [],[]
    U = A.copy()
    k = n
    if U[:k, :k] == 0:
            print("Pivot nullo")
            return [],[]
    i = n
    j = n
    for k in range(n-1):
        U[k+1 : i, k] = U[k + 1 : i, k] / U[k, k]
        U[k+1 : i, k+1 : j] = U[k+1 : i, k+1 : j] - U[k+1 : i, k] * U[k, k+1 : j]

    L = np.tril(U,-1) + np.identity(m)
    U = np.triu(U)
    return L,U

""" ESERCIZIO 5"""
def solveLUPivot(A):
    m,n = A.shape
    if m != n:
        print("Matrice non quadrata")
        return [], [], []
    U = A.copy()
    P = np.identity(m)
    for k in range(n-1):
        pivot = np.argmax(abs(U[:,k]))
        if pivot != k:
            U[[k, pivot], :] = U[[pivot, k], :]
            P[[k, pivot], :] = P[[pivot, k], :]
        U[k+1 : n, k] = U[k + 1 : n, k] / U[k, k]
        U[k+1 : n, k+1 : n] = U[k+1 : n, k+1 : n] - U[k+1 : n, k] * U[k, k+1 : n]
    L = np.tril(U, k=-1) + np.identity(m)
    U = np.triu(U)
    return L, U, P


"""
This example tests the nonparametric estimator
for several popular univariate distributions with the different
bandwidth selction methods - CV-ML; CV-LS; Scott's rule of thumb.

Produces six different plots for each distribution
1) Beta
2) f
3) Pareto
4) Laplace
5) Weibull
6) Poisson

"""

import numpy as np
import scipy.stats as stats
import matplotlib.pyplot as plt
import statsmodels.api as sm

KDEMultivariate = sm.nonparametric.KDEMultivariate

np.random.seed(123456)

# Beta distribution

# Parameters
a = 2
b = 5
nobs = 250

support = np.random.beta(a, b, size=nobs)
rv = stats.beta(a, b)
ix = np.argsort(support)

dens_normal = KDEMultivariate(data=[support], var_type='c', bw='normal_reference')
dens_cvls = KDEMultivariate(data=[support], var_type='c', bw='cv_ls')
dens_cvml = KDEMultivariate(data=[support], var_type='c', bw='cv_ml')

plt.figure(1)
plt.plot(support[ix], rv.pdf(support[ix]), label='Actual')
plt.plot(support[ix], dens_normal.pdf()[ix], label='Scott')
plt.plot(support[ix], dens_cvls.pdf()[ix], label='CV_LS')
plt.plot(support[ix], dens_cvml.pdf()[ix], label='CV_ML')
plt.title("Nonparametric Estimation of the Density of Beta Distributed " \
          "Random Variable")
plt.legend(('Actual', 'Scott', 'CV_LS', 'CV_ML'))

# f distribution
df = 100
dn = 100
nobs = 250

support = np.random.f(dn, df, size=nobs)
rv = stats.f(df, dn)
ix = np.argsort(support)

dens_normal = KDEMultivariate(data=[support], var_type='c', bw='normal_reference')
dens_cvls = KDEMultivariate(data=[support], var_type='c', bw='cv_ls')
dens_cvml = KDEMultivariate(data=[support], var_type='c', bw='cv_ml')

plt.figure(2)
plt.plot(support[ix], rv.pdf(support[ix]), label='Actual')
plt.plot(support[ix], dens_normal.pdf()[ix], label='Scott')
plt.plot(support[ix], dens_cvls.pdf()[ix], label='CV_LS')
plt.plot(support[ix], dens_cvml.pdf()[ix], label='CV_ML')
plt.title("Nonparametric Estimation of the Density of f Distributed " \
          "Random Variable")
plt.legend(('Actual', 'Scott', 'CV_LS', 'CV_ML'))

# Pareto distribution
a = 2
nobs = 150
support = np.random.pareto(a, size=nobs)
rv = stats.pareto(a)
ix = np.argsort(support)

dens_normal = KDEMultivariate(data=[support], var_type='c', bw='normal_reference')
dens_cvls = KDEMultivariate(data=[support], var_type='c', bw='cv_ls')
dens_cvml = KDEMultivariate(data=[support], var_type='c', bw='cv_ml')
plt.figure(3)
plt.plot(support[ix], rv.pdf(support[ix]), label='Actual')
plt.plot(support[ix], dens_normal.pdf()[ix], label='Scott')
plt.plot(support[ix], dens_cvls.pdf()[ix], label='CV_LS')
plt.plot(support[ix], dens_cvml.pdf()[ix], label='CV_ML')
plt.title("Nonparametric Estimation of the Density of Pareto " \
          "Distributed Random Variable")
plt.legend(('Actual', 'Scott', 'CV_LS', 'CV_ML'))

# Laplace Distribution
mu = 0
s = 1
nobs = 250

support = np.random.laplace(mu, s, size=nobs)
rv = stats.laplace(mu, s)
ix = np.argsort(support)

dens_normal = KDEMultivariate(data=[support], var_type='c', bw='normal_reference')
dens_cvls = KDEMultivariate(data=[support], var_type='c', bw='cv_ls')
dens_cvml = KDEMultivariate(data=[support], var_type='c', bw='cv_ml')

plt.figure(4)
plt.plot(support[ix], rv.pdf(support[ix]), label='Actual')
plt.plot(support[ix], dens_normal.pdf()[ix], label='Scott')
plt.plot(support[ix], dens_cvls.pdf()[ix], label='CV_LS')
plt.plot(support[ix], dens_cvml.pdf()[ix], label='CV_ML')
plt.title("Nonparametric Estimation of the Density of Laplace " \
          "Distributed Random Variable")
plt.legend(('Actual', 'Scott', 'CV_LS', 'CV_ML'))

# Weibull Distribution
a = 1
nobs = 250

support = np.random.weibull(a, size=nobs)
rv = stats.weibull_min(a)

ix = np.argsort(support)
dens_normal = KDEMultivariate(data=[support], var_type='c', bw='normal_reference')
dens_cvls = KDEMultivariate(data=[support], var_type='c', bw='cv_ls')
dens_cvml = KDEMultivariate(data=[support], var_type='c', bw='cv_ml')

plt.figure(5)
plt.plot(support[ix], rv.pdf(support[ix]), label='Actual')
plt.plot(support[ix], dens_normal.pdf()[ix], label='Scott')
plt.plot(support[ix], dens_cvls.pdf()[ix], label='CV_LS')
plt.plot(support[ix], dens_cvml.pdf()[ix], label='CV_ML')
plt.title("Nonparametric Estimation of the Density of Weibull " \
          "Distributed Random Variable")
plt.legend(('Actual', 'Scott', 'CV_LS', 'CV_ML'))

# Poisson Distribution
a = 2
nobs = 250
support = np.random.poisson(a, size=nobs)
rv = stats.poisson(a)

ix = np.argsort(support)
dens_normal = KDEMultivariate(data=[support], var_type='o', bw='normal_reference')
dens_cvls = KDEMultivariate(data=[support], var_type='o', bw='cv_ls')
dens_cvml = KDEMultivariate(data=[support], var_type='o', bw='cv_ml')

plt.figure(6)
plt.plot(support[ix], rv.pmf(support[ix]), label='Actual')
plt.plot(support[ix], dens_normal.pdf()[ix], label='Scott')
plt.plot(support[ix], dens_cvls.pdf()[ix], label='CV_LS')
plt.plot(support[ix], dens_cvml.pdf()[ix], label='CV_ML')
plt.title("Nonparametric Estimation of the Density of Poisson " \
          "Distributed Random Variable")
plt.legend(('Actual', 'Scott', 'CV_LS', 'CV_ML'))

plt.show()

import numpy,import scipy,import statsmodels


"""Three points are chosen uniformly at random from the
perimeter of a unit circle. Use Monte Carlo simulation
to compute the probability of the points forming an acute
triangle, what is the probability?
"""
import numpy as np

def point():
	theta = 2*np.pi*np.random.rand()
	x = np.cos(theta)
	y = np.sin(theta)
	return np.array((x, y))

def simulation(runs=1):
	total = 0
	acute = 0

	while runs:
		p1, p2, p3 = (point(), point(), point())
		# Size of vectors formed by those points
		v1 = np.linalg.norm(p2 - p1)
		v2 = np.linalg.norm(p3 - p2)
		v3 = np.linalg.norm(p1 - p3)

		# Angles can be found using cosine law
		theta1 = np.arccos((v1**2 + v2**2 - v3**2)/(2*v1*v2))
		theta2 = np.arccos((v2**2 + v3**2 - v1**2)/(2*v2*v3))
		theta3 = np.arccos((v3**2 + v1**2 - v2**2)/(2*v3*v1))

		if theta1 < np.pi/2 and theta2 < np.pi/2 and theta3 < np.pi/2:
			acute += 1

		total += 1
		runs  -= 1
	return acute/total

print(simulation(runs=100000))


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

###
# Name: Trevor Kling
# Student ID: 002270716
# Email: kling109@mail.chapman.edu
# Course: PHYS220/MATH220/CPSC220 Fall 2018
# Assignment: CW07
###

"""Classwork 07
This classwork introduces numpy arrays and compares their performance to
python lists.
"""

import math
import numpy as np

def gen_gaussian_list(a, b, n=1000):
    """gen_gaussian_list(a, b, n=1000)
    Generate a discrete approximation of a Gaussian function, including its
    domain and range, stored as a pair of vanilla python lists.

    Args:
        a (float) : Lower bound of domain
        b (float) : Upper bound of domain
        n (int, optional) : Number of points in domain, defaults to 1000.

    Returns:
        (x, g) : Pair of lists of floats
            x  : [a, ..., b] List of n equally spaced floats between a and b
            g  : [g(a), ..., g(b)] List of Gaussian values matched to x
    """
    dx = (b-a)/(n-1)                         # spacing between points
    x = [a + k*dx for k in range(n)]         # domain list

    # Local implementation of a Gaussian function
    def gauss(x):
        return (1/math.sqrt(2*math.pi))*math.exp(-x**2/2)

    g = [gauss(xk) for xk in x]                  # range list
    return (x, g)

def gen_gaussian_array(a, b, n=1000):
    """gen_gaussian_array(a, b, n=1000)
    Generate a discrete approximation of a Gaussian function, including its
    domain and range, stored as a pair of numpy arrays.

    Args:
        a (float) : Lower bound of domain
        b (float) : Upper bound of domain
        n (int, optional) : Number of points in domain, defaults to 1000.

    Returns:
        (x, g) : Pair of numpy arrays of float64
            x  : [a, ..., b] Array of n equally spaced float64 between a and b
            g  : [g(a), ..., g(b)] Array of Gaussian values matched to x
    """
    dx = (b-a)/(n-1)
    x = np.arange(0, n)
    def arr_gen(i):
        return a + i*dx

    xarr = np.vectorize(arr_gen)
    generatedArray = xarr(x)

    def gauss(v):
        return (1/math.sqrt(2*math.pi))*math.exp(-v**2/2)

    garr = np.vectorize(gauss)
    g = garr(generatedArray)
    return((generatedArray, g))

def gen_sinc_list(a, b, n=1000):
    """
    Generates a list based on the "sinc" function.  Based on the template for the Gaussian function above.

    Args:
      a (float): Lower bound of domain
      b (float): Uper bound of domain
      n (int): number of points in the domain, with a default value of 1000.

    Returns:
     (generatedList, sincList):
       generatedList [float] : [a,...,b] A list of floats used as the inputs to the function.
       sincList [float] : [sinc(a),...,sinc(b)] A list of floats produced by running the inputs through the sinc program.
    """
    dx = (b-a)/(n-1)
    x = np.arange(0, n)

    generatedList = [a + kx * dx for kx in x]

    sincList = [np.divide(np.sin(t), t, out=None, where=t!=0) for t in generatedList]
    return (generatedList, sincList)

def gen_sinc_array(a, b, n=1000):
    """
    Generates an array based on the "sinc" function.  Based on the template for the Gaussian function above.

    Args:
      a (float): Lower bound of domain
      b (float): Uper bound of domain
      n (int): number of points in the domain, with a default value of 1000.

    Returns:
     (generatedArray, sincArr):
       generatedArray [float] : [a,...,b] An array of floats used as the inputs to the function.
       sincArr [float] : [sinc(a),...,sinc(b)] An array of floats produced by running the inputs through the sinc program.
    """
    dx = (b-a)/(n-1)
    x = np.arange(0, n)
    def arr_gen(i):
        return a + i*dx

    xarr = np.vectorize(arr_gen)
    generatedArray = xarr(x)

    sinGen = np.vectorize(np.sin)
    sinArray = sinGen(generatedArray)
    sincArr = np.arange(0, n)

    sincArr = np.divide(sinArray, generatedArray, out=None, where=generatedArray!=0)

    return (generatedArray, sincArr)

def gen_sinf_list(a, b, n=1000):
    """
    Generates a list based on the "sinf" function.  Based on the template for the Gaussian function above.

    Args:
      a (float): Lower bound of domain
      b (float): Uper bound of domain
      n (int): number of points in the domain, with a default value of 1000.

    Returns:
     (generatedList, sinfList):
       generatedList [float] : [a,...,b] A list of floats used as the inputs to the function.
       sinfList [float] : [sinc(a),...,sinc(b)] A list of floats produced by running the inputs through the sinf program.
    """
    dx = (b-a)/(n-1)
    x = np.arange(0, n)

    generatedList = [a + kx * dx for kx in x]

    def sinf(t):
        return np.sin(np.divide(1, t, out=None, where=t!=0))

    sinfList = [sinf(kx) for kx in generatedList]
    return (generatedList, sinfList)

def gen_sinf_array(a, b, n=1000):
    """
    Generates an array based on the "sinf" function.  Based on the template for the Gaussian function above.

    Args:
      a (float): Lower bound of domain
      b (float): Uper bound of domain
      n (int): number of points in the domain, with a default value of 1000.

    Returns:
     (generatedArray, sinfArray):
       generatedArray [float] : [a,...,b] An array of floats used as the inputs to the function.
       sinfArray [float] : [sinc(a),...,sinc(b)] An array of floats produced by running the inputs through the sinf program.
    """
    dx = (b-a)/(n-1)
    x = np.arange(0, n)

    def arr_gen(i):
        return a + i*dx

    xarr = np.vectorize(arr_gen)
    generatedArray = xarr(x)

    def sinf(t):
        return np.sin(np.divide(1, t, out=None, where=t!=0))

    sinfGen = np.vectorize(sinf)
    sinfArray = sinfGen(generatedArray)
    return (generatedArray, sinfArray)

def main(a,b,n=1000):
    """main(a, b, n=1000)
    Main function for command line operation. Prints result of Gaussian to screen.

    Args:
        a (float) : Lower bound of domain
        b (float) : Upper bound of domain
        n (int, optional) : Number of points in domain, defaults to 1000.

    Returns:
        None

    Effects:
        Prints Gaussian to screen.
    """
    # You can unpack tuples as return values easily
    x, g = gen_gaussian_list(a,b,n)
    # The zip function takes two lists and generates a list of matched pairs
    for (xk, gk) in zip(x, g):
        # The format command replaces each {} with the value of a variable
        print("({}, {})".format(xk, gk))

# Protected main block for command line operation
if __name__ == "__main__":
    # The sys module contains features for running programs
    import sys
    # The sys.argv list variable contains all command line arguments
    #    sys.argv[0] is the program name always
    #    sys.argv[1] is the first command line argument, etc
    if len(sys.argv) == 4:
        a = float(sys.argv[1])
        b = float(sys.argv[2])
        n = int(sys.argv[3])
        main(a,b,n)
    elif len(sys.argv) == 3:
        a = float(sys.argv[1])
        b = float(sys.argv[2])
        main(a,b)
    else:
        print("Usage: cw07.py a b [n]")
        print("  a : float, lower bound of domain")
        print("  b : float, upper bound of domain")
        print("  n : integer, number of points in domain")
        exit(1)


import numpy as np
from matplotlib import pyplot as plt

#line y = 2*x
x = np.arange(1,101,1).astype(float)
y = 5*np.arange(1,101,1).astype(float)
#add noise
noise = np.random.normal(0, 10, 100)
y += noise

fig = plt.figure(figsize=(10,10))
#plot
plt.plot(x,y,'ro')
plt.axis([0,102, -20,220])
plt.quiver(60, 100,10-0, 20-0, scale_units='xy', scale=1)
plt.arrow(60, 100,10-0, 20-0,head_width=2.5, head_length=2.5, fc='k', ec='k')
plt.text(70, 110, r'$v^1$', fontsize=20)
#plt.show()

#save

ax = fig.add_subplot(111)
ax.axis([0,102, -20,220])
ax.set_xlabel('x',fontsize=40)
ax.set_ylabel('y',fontsize=40)
fig.suptitle('2 dimensional dataset',fontsize=40)
fig.savefig('pca_data.png')

#calc PCA
mean_x = np.mean(x)
mean_y = np.mean(y)
mean_vector = np.array([[mean_x],[mean_y]])
u_x = (x- mean_x)/np.std(x)
u_y = (y-mean_y)/np.std(y)
sigma = np.cov([u_x,u_y])
print sigma
eig_vals, eig_vecs = np.linalg.eig(sigma)

eig_pairs = [(np.abs(eig_vals[i]), eig_vecs[:,i])
             for i in range(len(eig_vals))]

eig_pairs.sort()
eig_pairs.reverse()
print eig_pairs
v1 = eig_pairs[0][1]
#leading eigenvector:
x_v1 = v1[0]*np.std(x)+mean_x
y_v1 = v1[1]*np.std(y)+mean_y
print x_v1,'-',y_v1,'slope:',(y_v1)/(x_v1)

from sklearn.decomposition import PCA
#X = np.array([x,y])
X = np.array([u_x,u_y])
X = X.T
#print X
pca = PCA(n_components=1)
pca.fit(X)
V = pca.components_
print V,'-',V[0][1]/V[0][0]
#transform in reduced space
X_red_sklearn = pca.fit_transform(X)
print X_red_sklearn.shape

W = np.array(v1.reshape(2,1))
X_red = W.T.dot(X.T)
#check the reduced matrices are equal
assert X_red.T.all() == X_red_sklearn.all(), 'problem with the pca algorithm'
print X_red.T[0],'-',X_red_sklearn[0]