import numpy as np

def mean_square_error(y_true: np.ndarray, y_pred: np.ndarray) -> float:
    """
    Calculate MSE loss

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values

    Returns
    -------
    MSE of given predictions
    """
    return np.mean(np.square(y_true - y_pred))

def misclassification_error(y_true: np.ndarray, y_pred: np.ndarray, normalize: bool = True) -> float:
    """
    Calculate misclassification loss

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values
    normalize: bool, default = True
        Normalize by number of samples or not

    Returns
    -------
    Misclassification of given predictions
    """
    mult = y_true * y_pred
    if normalize:
        return np.sum(mult < 0, axis=0) / mult.shape[0]
    else:
        np.sum(mult < 0, axis=0)

def accuracy(y_true: np.ndarray, y_pred: np.ndarray) -> float:
    """
    Calculate accuracy of given predictions

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values

    Returns
    -------
    Accuracy of given predictions
    """
    pred_num = y_pred.shape[0]
    correct_num = len([1 for i in range(pred_num) if y_pred[i] == y_true[i]])
    return correct_num / pred_num

def cross_entropy(y_true: np.ndarray, y_pred: np.ndarray) -> float:
    """
    Calculate the cross entropy of given predictions

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values

    Returns
    -------
    Cross entropy of given predictions
    """
    raise NotImplementedError()


"""
Solve Helmholtz equation on a sphere

Using spherical coordinates

"""
import os
from shenfun import *
from shenfun.la import SolverGeneric1ND
import sympy as sp

by_parts = False

# Define spherical coordinates
r = 1
theta, phi = psi = sp.symbols('x,y', real=True, positive=True)
rv = (r*sp.sin(theta)*sp.cos(phi), r*sp.sin(theta)*sp.sin(phi), r*sp.cos(theta))

alpha = 2

# Manufactured solution
sph = sp.functions.special.spherical_harmonics.Ynm
ue = sph(6, 3, theta, phi)
#ue = sp.cos(8*(sp.sin(theta)*sp.cos(phi) + sp.sin(theta)*sp.sin(phi) + sp.cos(theta)))
#g = - ue.diff(theta, 2) - (1/sp.tan(theta))*ue.diff(theta, 1) - (1/sp.sin(theta)**2)*ue.diff(phi, 2) + alpha*ue

N, M = 60, 40
L0 = FunctionSpace(N, 'C', domain=(0, np.pi))
F1 = FunctionSpace(M, 'F', dtype='D')
T = TensorProductSpace(comm, (L0, F1), coordinates=(psi, rv, sp.Q.positive(sp.sin(theta))))

v = TestFunction(T)
u = TrialFunction(T)

# Compute the right hand side on the quadrature mesh
g = (-div(grad(u))+alpha*u).tosympy(basis=ue, psi=psi)
gj = Array(T, buffer=g)

# Take scalar product
g_hat = Function(T)
g_hat = inner(v, gj, output_array=g_hat)

# Assemble matrices.
if by_parts:
    mats = inner(grad(v), grad(u))
    mats += [inner(v, alpha*u)]

else:
    mats = inner(v, -div(grad(u))+alpha*u)

# Solve
u_hat = Function(T)
Sol1 = SolverGeneric1ND(mats)
u_hat = Sol1(g_hat, u_hat)

# Transform back to real space.
uj = u_hat.backward()
uq = Array(T, buffer=ue)
print('Error =', np.linalg.norm(uj-uq))

if 'pytest' not in os.environ:
    # Postprocess
    # Refine for a nicer plot. Refine simply pads Functions with zeros, which
    # gives more quadrature points. u_hat has NxM quadrature points, refine
    # using any higher number.
    u_hat2 = u_hat.refine([N*3, M*3])
    ur = u_hat2.backward(kind='uniform')
    from mayavi import mlab
    xx, yy, zz = u_hat2.function_space().local_cartesian_mesh(uniform=True)
    # Wrap periodic direction around
    if T.bases[1].domain == (0, 2*np.pi):
        xx = np.hstack([xx, xx[:, 0][:, None]])
        yy = np.hstack([yy, yy[:, 0][:, None]])
        zz = np.hstack([zz, zz[:, 0][:, None]])
        ur = np.hstack([ur, ur[:, 0][:, None]])
    mlab.figure(bgcolor=(1, 1, 1), size=(400, 400))
    mlab.mesh(xx, yy, zz, scalars=ur.real, colormap='jet')
    mlab.savefig('spherewhite.png')
    #mlab.show()


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

from sklearn.neighbors import KernelDensity

# For reproducibility
np.random.seed(1000)

if __name__ == '__main__':
    # Generate the dataset
    nb_samples = [1000, 800, 500, 380, 280, 150, 120, 100, 50, 30]

    ages = []

    for n in nb_samples:
        i = np.random.uniform(10, 80, size=2)
        a = np.random.uniform(i[0], i[1], size=n).astype(np.int32)
        ages.append(a)

    ages = np.concatenate(ages)

    # Train KDE with Gaussian kernels and 3 bandwidths
    kd_01 = KernelDensity(kernel='gaussian', bandwidth=0.1)
    kd_05 = KernelDensity(kernel='gaussian', bandwidth=0.5)
    kd_15 = KernelDensity(kernel='gaussian', bandwidth=1.5)

    kd_01.fit(ages.reshape(-1, 1))
    kd_05.fit(ages.reshape(-1, 1))
    kd_15.fit(ages.reshape(-1, 1))

    # Show the results
    sns.set()

    fig, ax = plt.subplots(3, 1, figsize=(14, 20), sharex=True)

    data = np.arange(10, 70, 0.05).reshape(-1, 1)

    ax[0].plot(data, np.exp(kd_01.score_samples(data)))
    ax[0].set_title('Bandwidth = 0.1', fontsize=14)
    ax[0].set_ylabel('Density', fontsize=14)

    ax[1].plot(data, np.exp(kd_05.score_samples(data)))
    ax[1].set_title('Bandwidth = 0.5', fontsize=14)
    ax[1].set_ylabel('Density', fontsize=14)

    ax[2].plot(data, np.exp(kd_15.score_samples(data)))
    ax[2].set_title('Bandwidth = 1.5', fontsize=14)
    ax[2].set_xlabel('Age', fontsize=14)
    ax[2].set_ylabel('Density', fontsize=14)

    plt.show()

    # Compute the optimal bandwidth (method 1)
    N = float(ages.shape[0])
    h = 1.06 * np.std(ages) * np.power(N, -0.2)

    print('h = {:.3f}'.format(h))

    # Compute the optimal bandwidth (method 2)
    IQR = np.percentile(ages, 75) - np.percentile(ages, 25)
    h = 0.9 * np.min([np.std(ages), IQR / 1.34]) * np.power(N, -0.2)

    print('h = {:.3f}'.format(h))

    # Train KDE with different kernels and bandwidth = 2.0
    kd_gaussian = KernelDensity(kernel='gaussian', bandwidth=2.0)
    kd_epanechnikov = KernelDensity(kernel='epanechnikov', bandwidth=2.0)
    kd_exponential = KernelDensity(kernel='exponential', bandwidth=2.0)

    kd_gaussian.fit(ages.reshape(-1, 1))
    kd_epanechnikov.fit(ages.reshape(-1, 1))
    kd_exponential.fit(ages.reshape(-1, 1))

    # Show the results
    fig, ax = plt.subplots(3, 1, figsize=(14, 20), sharex=False)

    data = np.arange(10, 70, 0.05).reshape(-1, 1)

    ax[0].plot(data, np.exp(kd_gaussian.score_samples(data)))
    ax[0].set_title('Gaussian Kernel', fontsize=14)
    ax[0].set_ylabel('Density', fontsize=14)

    ax[1].plot(data, np.exp(kd_epanechnikov.score_samples(data)))
    ax[1].set_title('Epanechnikov Kernel', fontsize=14)
    ax[1].set_ylabel('Density', fontsize=14)
    ax[1].set_xlabel('Age', fontsize=14)

    ax[2].plot(data, np.exp(kd_exponential.score_samples(data)))
    ax[2].set_title('Exponential Kernel', fontsize=14)
    ax[2].set_xlabel('Age', fontsize=14)
    ax[2].set_ylabel('Density', fontsize=14)

    plt.show()

    # Perform a sample anomaly detection
    test_data = np.array([12, 15, 18, 20, 25, 30, 40, 50, 55, 60, 65, 70, 75, 80, 85, 90]).reshape(-1, 1)

    test_densities_epanechnikov = np.exp(kd_epanechnikov.score_samples(test_data))
    test_densities_gaussian = np.exp(kd_gaussian.score_samples(test_data))

    for age, density in zip(np.squeeze(test_data), test_densities_epanechnikov):
        print('p(Age = {:d}) = {:.7f} ({})'.format(age, density, 'Anomaly' if density < 0.005 else 'Normal'))


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Fourier transform using Python (version 3.5).

This code accompanies the book: The Fourier Transform: A Tutorial Introduction by James V Stone.
https://jim-stone.staff.shef.ac.uk/Fourier

To run code, set testing to 0 to analyse data in supplied sound file or 1 to test code on synthetic data containing just two frequencies.

Note that this code is written for transparency, not speed. It does not use complex numbers. Instead the Fourier coefficients are found using sine and cosine functions. The corresponding MatLab code generated Figures 1.8a and 1.8b in the book (this Python code provides similar figures).

Spyder version: 4.1.5 None
Python version: 3.8.1 64-bit
Tested on mac OS 10.15.7 (Catalina)
@author: JimStone

Copyright: All except the sound files are public domain.
The sound files are sebinpubaudiIMG_1563.wav
# and sebinpubaudiIMG_1563.m4a.

#############################
# Simple Fourier transform key variables
#
# x       signal of N sampled values
# T       length of signal in seconds
# R       sampling rate in samples per second
# N       number of samples in signal
# nvec    vector of N ample numbers
# tvec    vector of N time values in seconds
# fundamental  vector of N elements
# containing values from 0 to 2*pi =
# fundamental frequency
# fmax  maximum frequency in Hz, chosen by user
# dt        interval in seconds between samples  = 1/R
# fmin    lowest frequency in Hz = 1/T
# nfrequencies  number of frequencies in transform
# Cs    vector of nfrequencies cosine coefficients
# Ds    vector of nfrequencies sine coefficients
# As    vector of nfrequencies Fourier amplitudes
# freqs vector of frequency values in Hz
#############################
"""

import numpy as np
import matplotlib.pyplot as plt
from scipy.io import wavfile

testing = 0
# set to 1 for synthetic data, and 0 for voice data.
fs = 10 # set fontsize for graphs.

###############################################
if testing: # make simple data to test program.
    frequency = 500;
    # insert this frequency Hz
    T = 0.5; # length of signal in seconds

    period = 1/frequency; # in seconds
    R = 44100; # set sampling rate, samples/s
    N = R*T; # number of samples in T seconds
    tvec = np.arange(0, N, 1)/R;
    # time vector in units of seconds

    # make vector containg 1 frequency = 1/period.
    x1 = 2 * np.pi * tvec/period;

    # signal in which each interval of period
    # seconds increases by 1.5*2*pi.
    x2 = 1.5*x1; # make sinusoids
    x = np.sin(x1);
    x = x + 0.5*np.sin(x2);
else: # use data from sound file
    # set name of sound file
    fname = 'sebinpubaudiIMG_1563.wav';
    # read in data from file.
    R, x = wavfile.read(fname)
    # set length of sound segment in seconds.
    segmentseconds = 0.5;
    N = int(R * segmentseconds);
    # sample number at end of segmentseconds.
    # set start and end indices in data samples.
    xmin = 0;
    xmax = xmin+N;
    x = x[xmin:xmax];
    T = N/R; # number of seconds in recording
# endif
###############################################

N = len(x); # number of samples.

# specify maximum frequency to be analysed here
fmax = 1000; # Hz

nvec = np.arange(0, N, 1);
# vector of N samples
tvec = np.arange(0, N, 1)/R;
# vector of N samples in units of seconds

# Plot first part of signal.
nforplot=500; # number of samples for graph.
plt.figure("Signal")
plt.clf()
plt.title("Signal",fontsize=fs)
plt.xlabel("Time (seconds)",fontsize=fs)
plt.ylabel("Amplitude",fontsize=fs)
plt.plot(tvec[0:nforplot],x[0:nforplot])
plt.show()

# make fundamental sinusoidal vector.
nvec = np.arange(0,N); # indices = 0->N-1
fundamental = nvec * 2 * np.pi / N;
# fundamental now spans up to 2pi.

plt.figure("Fundamental")
plt.clf()
plt.title("Fundamental + one harmonic",fontsize=fs)
plt.xlabel("Time (seconds)",fontsize=fs)
plt.ylabel("Amplitude",fontsize=fs)
plt.plot(tvec,np.sin(fundamental),label="fundamental")
plt.plot(tvec,np.sin(2*fundamental),label="first harmonic")
plt.legend()
plt.show()

dt = 1/R; # interval between samples.
T = N/R;
fmin = 1.0/T; # lowest frequency in Hz.
# number of frequencies
nfrequencies = np.ceil(fmax/fmin);
nfrequencies = int(nfrequencies)

# make arrays to store Fourier coefficients.
Cs = np.arange(nfrequencies) * 0;
Ds = np.arange(nfrequencies) * 0;
# make array to store frequency values.
freqs = np.arange(nfrequencies) * 0;

###############################################
# Here is where the work gets done.
for n in range(1, nfrequencies):

    freq = fmin*n;
    freqs[n] = freq;

    # make cosine wave at frequency freq.
    cosinewave = np.cos(fundamental*n);
    # find inner product of sound with cosine wave.
    C = x.dot(cosinewave);
    Cs[n-1] = C;

    # make sine wave at frequency freq.
    sinewave = np.sin(fundamental*n);
    # find inner product of sound with sine wave.
    D = x.dot(sinewave);
    Ds[n-1] = D;
# work now done.
###############################################

# find amplitude of components, normalise by N.
As = ((Cs**2 + Ds**2) ** 0.5) / N

# Plot amplitude spectrum
plt.figure("Amplitude spectrum")
plt.clf()
plt.title("Amplitude spectrum",fontsize=fs)
plt.plot(freqs,As)
plt.xlabel("Frequency (Hz)",fontsize=fs)
plt.ylabel("Amplitude",fontsize=fs)

print('\nNumber of frequencies = ',nfrequencies)
print('\nFourier program completed.')
###############
# END OF FILE
################


# Lab1 -> 12.10

import numpy as np
import matplotlib.pyplot as plt
import metode_numerice_ecuatii_algebrice as mnea
import sympy as sym

"""
Lab#2.Ex2.b: Să se identifice intervalele pe care funcția f admite o sol unică.
             f(x) = x^3 - 7*(x^2) + 14x - 6
             Interval (a, b) = (0, 4)
"""

def f(x):
    y = x**3 - 7*x**2 + 14*x - 6
    return y

(a, b) = (0, 4)
interval = mnea.cauta_intervale(f, a, b, 10)
print(interval)
dim = np.shape(interval)[1]  #shape: returnează dimensiunile matricei

#Desen Grafice
x_grafic = np.linspace(0, 4, 100)
y_grafic = f(x_grafic)
plt.plot(x_grafic, y_grafic, marker = 'o', color = 'blue', linewidth = 3)
plt.axvline(0, color = 'black')
plt.axhline(0, color = 'black')
plt.xlabel('X')
plt.ylabel('Y')
plt.title('Lab#2.Ex2.b)')
plt.grid(True)
for i in range(dim):
    plt.plot(interval[:, i], np.zeros((2, 1)), color = 'red', linewidth = 10)
plt.show()

"""
Lab#2.Ex2.a: Pt datele de mai sus, să se afle toate rădăcinile conform
             metodei bisecției.
"""

r = np.zeros(dim)
for i in range(dim):
    r[i], N = mnea.bisectie(f, interval[0,i], interval[1, i], 10**-3)
    print("Metoda Bisectiei")
    print("Ecuația x^3 - 7*(x^2) + 14x - 6 = 0")
    print("Intervalul [{:.3f}, {:.3f}]".format(interval[0,i], interval[1,i]))
    print("Solutia Numerica: x *= {:.3f}".format(r[i]))
    print("-----------------------------------")

plt.plot(r, f(r), 'o-', color = 'green', markersize = 10)
plt.show()

# ---------------------------------------------
# Lab2 -> 19.10

"""
Lab#2.Ex3:
"""

x = sym.symbols('X')           # var simbolica
f_expr = f(x)                  # expresia simbolica
df_expr = sym.diff(f_expr, x)
df = sym.lambdify(x, df_expr)  # functie acum
print("--------------------------------------------------------------------------------------")
print(f'Expresia: {f_expr}')
print(f'Derivata calculata: {df_expr}')
print(f'Derivata de 2: {df(2)}')

#Construim functia pe [0, 4]
(a, b) = (0, 4)
n_noduri = 100
x_grafic = np.linspace(a, b, n_noduri)
y_grafic = f(x_grafic)
plt.plot(x_grafic, y_grafic, linewidth = 3)
plt.grid()
plt.axvline(0, color = 'black') #ax vertical line
plt.axhline(0, color = 'black')

# Definim x0 = vector cu valoarea de pornire pt fiecare radacina
# Aleg x0 a.î. f(x0) * f''(x0) > 0
# f(x0) < 0 --> la stanga (pt prima radacina)
x0 = np.array([0.3, 2.5, 4])
sols = np.zeros(x0.shape)
iteratii = np.zeros(sols.shape)
eps = 10 ** (-6)
for i in range(len(x0)):
    sols[i], iteratii[i] = mnea.NewtonRap(f, df, x0[i], eps)

plt.plot(sols, f(sols), 'o', markerfacecolor = 'red', markersize = 10)
plt.show()

"""
Lab#2.Ex4:
"""

x_grafic = np.linspace(-1, 1.5, 100)
y_grafic = f(x_grafic)
plt.plot(x_grafic, y_grafic, linewidth = 3)
plt.grid()
plt.axvline(0, color = 'black') #ax vertical line
plt.axhline(0, color = 'black')
plt.show()
x0 = -1
eps = np.linspace(10 ** -13, 10 ** -3, 50)
N_bis2 = np.zeros(eps.shape)
N_NR = np.zeros(eps.shape)
for i in range(len(eps)):
    sol_bis, N_bis2[i] = mnea.bisectie2(f, -1, 1.5, eps[i])
    sol_NR, N_NR[i] = mnea.NewtonRap(f, df, x0, eps[i])

plt.plot(eps, N_bis2, 'b', linewidth = 2)
plt.plot(eps, N_NR, 'r', linewidth = 2)
plt.grid()
plt.axvline(0, color = 'black')
plt.axhline(0, color = 'black')
plt.show()

Calcul Numeric (CN)/Laborator/Laborator 1, 2/rezolvareaNumericaAEcAlg.py


"""
solve the diffusion equation:

 phi_t = k phi_{xx}

with a first-order (in time) implicit discretization

M. Zingale (2013-04-03)
"""

import numpy as np
from scipy import linalg
import matplotlib.pyplot as plt
from diffusion_explicit import Grid1d

class Simulation(object):

    def __init__(self, grid, k=1.0):
        self.grid = grid
        self.t = 0.0
        self.k = k  # diffusion coefficient

    def init_cond(self, name, *args):
        # initialize the data

        if name == "gaussian":
            t0, phi1, phi2 = args
            self.grid.phi[:] = self.grid.phi_a(0.0, self.k, t0, phi1, phi2)

    def diffuse_implicit(self, dt):
        """ diffuse phi implicitly through timestep dt """

        gr = self.grid
        phi = gr.phi

        phinew = gr.scratch_array()

        alpha = self.k*dt/gr.dx**2

        # create the RHS of the matrix
        R = phi[gr.ilo:gr.ihi+1]

        # create the diagonal, d+1 and d-1 parts of the matrix
        d = (1.0 + 2.0*alpha)*np.ones(gr.nx)
        u = -alpha*np.ones(gr.nx)
        u[0] = 0.0

        l = -alpha*np.ones(gr.nx)
        l[gr.nx-1] = 0.0

        # set the boundary conditions by changing the matrix elements

        # homogeneous neumann
        d[0] = 1.0 + alpha
        d[gr.nx-1] = 1.0 + alpha

        # solve
        A = np.matrix([u,d,l])
        phinew[gr.ilo:gr.ihi+1] = linalg.solve_banded((1,1), A, R)

        return phinew

    def evolve(self, C, tmax):
        """
        the main evolution loop.  Evolve

        phi_t = k phi_{xx}

        from t = 0 to tmax
        """

        gr = self.grid

        # time info
        dt = C*0.5*gr.dx**2/self.k

        while self.t < tmax:

            # make sure we end right at tmax
            if self.t + dt > tmax:
                dt = tmax - self.t

            # diffuse for dt
            phinew = self.diffuse_implicit(dt)

            gr.phi[:] = phinew[:]
            self.t += dt

if __name__ == "__main__":

    # reference time
    t0 = 1.e-4

    # state coeffs
    phi1 = 1.0
    phi2 = 2.0

    k = 1.0

    #-------------------------------------------------------------------------
    # normal time

    tmax = 0.005

    nx = 128

    Cs = [0.8, 2.0, 10.0]
    colors = ["b", "g", "r"]

    for C in Cs:
        gr = Grid1d(nx, ng=1)
        s = Simulation(gr, k=k)
        s.init_cond("gaussian", t0, phi1, phi2)
        s.evolve(C, tmax)

        plt.plot(gr.x[gr.ilo:gr.ihi+1], gr.phi[gr.ilo:gr.ihi+1],
                 color=colors.pop(), label="C = {}".format(C))

    # analytic solution
    plt.plot(gr.x[gr.ilo:gr.ihi+1],
               gr.phi_a(tmax, k, t0, phi1, phi2)[gr.ilo:gr.ihi+1],
               ls=":", color="0.5", label="analytic solution")

    plt.legend(frameon=False)

    plt.xlabel("$x$", fontsize="large")
    plt.ylabel(r"$\phi$", fontsize="large")
    plt.title("Backward-difference implicit diffusion, nx = {}, C = {:3.2f}, t = {:5.2g}".format(nx, C, tmax))

    plt.savefig("diffimplicit.pdf")

    #-------------------------------------------------------------------------
    # early time

    plt.clf()

    tmax = 0.0005

    nx = 128

    colors = ["b", "g", "r"]

    for C in Cs:
        gr = Grid1d(nx, ng=1)
        s = Simulation(gr, k=k)
        s.init_cond("gaussian", t0, phi1, phi2)
        s.evolve(C, tmax)

        plt.plot(gr.x[gr.ilo:gr.ihi+1], gr.phi[gr.ilo:gr.ihi+1],
                 color=colors.pop(), label="C = {}".format(C))

    # analytic solution
    plt.plot(gr.x[gr.ilo:gr.ihi+1],
               gr.phi_a(tmax, k, t0, phi1, phi2)[gr.ilo:gr.ihi+1],
               ls=":", color="0.5", label="analytic solution")

    plt.legend(frameon=False)

    plt.xlim(0.3,0.7)

    plt.xlabel("$x$", fontsize="large")
    plt.ylabel(r"$\phi$", fontsize="large")
    plt.title("Backward-difference implicit diffusion, nx = {}, C = {:3.2f}, t = {:5.2g}".format(nx, C, tmax))

    plt.savefig("diffimplicit-early.pdf")


# solutions.py
"""Volume 2 Lab 19: Interior Point 1 (Linear Programs). Solution file."""

import numpy as np
from scipy import linalg as la
from scipy.stats import linregress
from matplotlib import pyplot as plt

# Auxiliary Functions =========================================================
def startingPoint(A, b, c):
    """Calculate an initial guess to the solution of the linear program
    min c^T x, Ax = b, x>=0.
    Reference: Nocedal and Wright, p. 410.
    """
    # Calculate x, lam, mu of minimal norm satisfying both
    # the primal and dual constraints.
    B = la.inv(A.dot(A.T))
    x = A.T.dot(B.dot(b))
    lam = B.dot(A.dot(c))
    mu = c - A.T.dot(lam)

    # Perturb x and s so they are nonnegative.
    dx = max((-3./2)*x.min(), 0)
    dmu = max((-3./2)*mu.min(), 0)
    x += dx*np.ones_like(x)
    mu += dmu*np.ones_like(mu)

    # Perturb x and mu so they are not too small and not too dissimilar.
    dx = .5*(x*mu).sum()/mu.sum()
    dmu = .5*(x*mu).sum()/x.sum()
    x += dx*np.ones_like(x)
    mu += dmu*np.ones_like(mu)

    return x, lam, mu

def randomLP(m,n):
    """Generate a linear program min c^T x s.t. Ax = b, x>=0.
    First generate m feasible constraints, then add
    slack variables to convert it into the above form.
    Inputs:
        m -- positive integer >= n, number of desired constraints
        n -- dimension of space in which to optimize
    Outputs:
        A -- array of shape (m,n+m)
        b -- array of shape (m,)
        c -- array of shape (n+m,), with m trailing 0s
        v -- the solution to the LP
    """
    # generate random constraints (each row corresponds to the normal
    # vector defining a linear constraint)
    A = np.random.random((m,n))*20 - 10

    # adjust so that the normal vector of each constraint lies in the upper
    # half-space. this ensures that the constraints permit a feasible region
    A[A[:,-1]<0] *= -1

    # adjust so that the solution to the program is a prescribed point v in
    # the first quadrant.
    v = np.random.random(n)*10
    #k = np.random.randint(n,m+1)
    k = n
    b = np.zeros(m)
    b[:k] = A[:k,:].dot(v)
    b[k:] = A[k:,:].dot(v) + np.random.random(m-k)*10

    # now create the appropriate c vector, a weighted sum of the first k
    # constraints
    c = np.zeros(n+m)
    c[:n] = A[:k,:].sum(axis=0)/k

    # at this point, we should have a program max c^T x s.t. Ax <= b, x >= 0
    # we need to convert it to standard equality form by adding slack variables
    A = np.hstack((A, np.eye(m)))

    # we now have the program min -c^T x s.t. Ax = b, x>=0.
    # the optimal solution has x[:n] = v
    return A, b, -c, v

# Problems 1-4 ================================================================
def interiorPoint(A, b, c, niter=20, tol=1e-16, verbose=False):
    """Solve the linear program min c^T x, Ax = b, x>=0
    using an Interior Point method.

    Parameters:
        A ((m,n) ndarray): Equality constraint matrix with full row rank.
        b ((m, ) ndarray): Equality constraint vector.
        c ((n, ) ndarray): Linear objective function coefficients.
        niter (int > 0): The maximum number of iterations to execute.
        tol (float > 0): The convergence tolerance.

    Returns:
        x ((n, ) ndarray): The optimal point.
        val (float): The minimum value of the objective function.
    """
    m,n = A.shape
    def F(x_, l_, m_):
        """The almost-linear function that accounts for the KKT conditions."""
        return np.hstack((np.dot(A.T, l_)+m_-c, np.dot(A, x_)-b, m_*x_))

    DF = np.zeros((2*n+m, 2*n+m))
    DF[:n,n:-n] = A.T
    DF[:n,-n:] = np.eye(n)
    DF[n:-n,:n] = A

    # Get the initial point and verify the dimensions.
    x, lam, mu = startingPoint(A, b, c)
    assert len(x) == len(mu) == len(c) == n
    assert len(lam) == len(b) == m

    e = np.ones_like(mu)
    sigma = .1

    i = 0
    nu = 1 + tol
    while i < niter and nu >= tol:
        i += 1

        # Problem 3: Search Direction
        DF[-n:,:n] = np.diag(mu)
        DF[-n:,-n:] = np.diag(x)

        nu = np.dot(x, mu) / n
        nu_vec = np.hstack((np.zeros(n+m), e*nu*sigma))
        lu_piv = la.lu_factor(DF)
        direct = la.lu_solve(lu_piv, nu_vec - F(x,lam,mu))

        # Problem 4: Step Length
        dx, dlam, dmu = direct[:n], direct[n:-n], direct[-n:]

        mask = dmu < 0
        amin = np.min(-mu[mask]/dmu[mask])
        alpha = min(1, .95*min(1, amin)) if np.any(mask) else .95

        mask = dx < 0
        dmin = np.min(-x[mask]/dx[mask]).min()
        delta = min(1, .95*min(1, dmin)) if np.any(mask) else .95

        # Problem 5: Finish it up.
        x += delta*dx
        lam += alpha*dlam
        mu += alpha*dmu

        if verbose:
            print("Iteration {:0>2} nu = {}".format(i, nu))
    if i < niter and verbose:
        print("Converged in {} iterations".format(i))
    elif verbose:
        print("Maximum iterations reached")
    return x, c.dot(x)

# Tests =======================================================================
def single_test(m=7, n=5, verbose=True):
    """Test interiorPoint() a single time."""
    A, b, c, x = randomLP(m, n)
    point, value = interiorPoint(A, b, c, verbose=verbose)
    if not np.allclose(x,point[:n]):
        return 1
    return 0

def single_other_test():
    m = np.random.randint(2,10)
    n = m
    A = np.random.random((m,n))*20 - 10
    A[A[:,-1]<0] *= -1
    x = np.random.random(n)*10
    b = A.dot(x)
    c = -A.sum(axis=0)/float(n)

    point, value = interiorPoint(A, b, c, verbose=True)
    if not np.allclose(x,point):
        return 1
    return 0

def huge_test(trials=100):
    """Test interior point in high dimensions 'trials' times."""
    from sys import stdout
    failed = 0
    for i in xrange(trials):
        print "\rIteration {} of {}".format(i+1, trials),
        stdout.flush()
        n = np.random.randint(low=100, high=200)
        m = np.random.randint(low=n, high=300)
        failed += test_interiorPoint(m, n, verbose=False)
        print "({} failures)".format(failed),
    print "{}/{} failed".format(failed, trials)

# Problem 5 ===================================================================
def leastAbsoluteDeviations(save=False):
    """This code should be fairly close to what the students submit for the
    least absolute deviations problem.
    """
    data = np.loadtxt('simdata.txt')
    m = data.shape[0]
    n = data.shape[1] - 1
    c = np.zeros(3*m + 2*(n + 1))
    c[:m] = 1
    y = np.empty(2*m)
    y[::2] = -data[:, 0]
    y[1::2] = data[:, 0]
    x = data[:, 1:]
    A = np.ones((2*m, 3*m + 2*(n + 1)))
    A[::2, :m] = np.eye(m)
    A[1::2, :m] = np.eye(m)
    A[::2, m:m+n] = -x
    A[1::2, m:m+n] = x
    A[::2, m+n:m+2*n] = x
    A[1::2, m+n:m+2*n] = -x
    A[::2, m+2*n] = -1
    A[1::2, m+2*n+1] = -1
    A[:, m+2*n+2:] = -np.eye(2*m, 2*m)

    sol = interiorPoint(A, y, c, niter=10, verbose=True)[0]
    beta = (sol[m:m+n] - sol[m+n:m+2*n])[0]
    b = sol[m+2*n] - sol[m+2*n+1]

    dom = np.linspace(0,10,2)
    plt.scatter(data[:,1], data[:,0], c='k')
    plt.plot(dom, beta*dom+b, 'b-', linewidth=2, label="LAD")

    # Compare with least squares
    slope, intercept = linregress(data[:,1], data[:,0])[:2]
    lstsq_line = slope*dom + intercept
    plt.plot(dom, lstsq_line, 'g--', linewidth=2, label="LSTSQ")

    # plt.title("Problem 5")
    plt.legend()
    if save is True: # For figure file creation
        plt.savefig("LADprob.pdf")
    else:
        plt.show()
    print 'Beta:', beta
    print 'b:', b

# =============================================================================
# Solutions using the old method (Predictor Corrector) ========================
# =============================================================================
def old_interiorPoint(A, b, c, niter=20, verbose=False, starting_point=None, pts=False):
    '''
    Solve the linear programming problem min c^T x, Ax = b, x>=0
    using an Interior Point method. This code is not optimized, but
    forms the basis for a common practical approach known as the
    Predictor-Corrector Algorithm.
    Inputs:
        A -- array of shape (m,n) with linearly independent rows
        b -- array of length m
        c -- array of length n
        niter -- positive integer giving the number of iterations
        starting_point -- tuple of arrays giving the initial values for x, l, and s.
                          if unspecified, the function startingPoint is used.
    Returns:
        x -- the optimal point
        val -- the minimum value of the objective function
        (pts -- list of points traced by the algorithm, returned if pts=True)
    Ref: Nocedal and Wright, p. 411
    '''
    pts = []
    # initialize variables
    m,n = A.shape
    if starting_point:
        x, l, s = starting_point
    else:
        x,l,s = startingPoint(A,b,c)
    pts.append(x)
    N = np.zeros((n+m+n, n+m+n))
    N[:n, n:n+m] = A.T
    N[:n, n+m:] = np.eye(n)
    N[n:n+m, :n] = A
    sol = np.empty(n+m+n)
    for k in xrange(niter):
        # finish initializing parts of the step equation
        N[n+m:, :n] = np.diag(s)
        N[n+m:, n+m:] = np.diag(x)
        r_c = (A.T).dot(l)+s-c
        r_b = A.dot(x)-b
        rhs = np.hstack((-r_c.ravel(), -r_b.ravel(), -x*s))

        # solve dx_aff, dl_aff, ds_aff using LU decomposition
        lu_piv = la.lu_factor(N)
        sol[:] = la.lu_solve(lu_piv, rhs)
        dx_aff = sol[:n]
        dl_aff = sol[n:n+m]
        ds_aff = sol[n+m:]

        # calculate a_p, a_d, mu_aff
        mask1 = dx_aff < 0
        if mask1.sum() > 0:
            a_p = min(1, ((-x/dx_aff)[mask1]).min())
        else:
            a_p = 1
        mask2 = ds_aff < 0
        if mask2.sum() > 0:
            a_d = min(1, (-s/ds_aff)[mask2].min())
        else:
            a_d = 1
        mu_aff = ((x+a_p*dx_aff)*(s+a_d*ds_aff)).sum()/np.float(n)

        # calculate mu times the centering parameter sig
        mu = (x*s).sum()/n
        musig = mu_aff**3/mu**2

        # calculate dx, dl, ds
        rhs[n+m:] += - dx_aff*ds_aff + musig
        sol[:] = la.lu_solve(lu_piv, rhs)
        dx = sol[:n]
        dl = sol[n:n+m]
        ds = sol[n+m:]

        # calculate ap, ad
        nu = 1-.1/(k+1)
        mask3 = dx < 0
        if mask3.sum() > 0:
            ap_max = (-x/dx)[mask3].min()
            ap = min(1, nu*ap_max)
        else:
            ap = 1
        mask4 = ds < 0
        if mask4.sum() > 0:
            ad_max = (-s/ds)[mask4].min()
            ad = min(1, nu*ad_max)
        else:
            ad = 1

        # step to new point
        x = x + ap*dx
        l = l + ad*dl
        s = s + ad*ds
        pts.append(x)

        if verbose:
            print '{0:f} {1:f}'.format((c*x).sum(), mu)

    if pts:
        return pts
    else:
        return x, (c*x).sum()


# Name: Breno Maurício de Freitas Viana
# NUSP: 11920060
# Course Code: SCC5830
# Year/Semester: 2021/1
# Assignment 3: Filtering

import math
import numpy as np
import imageio

# ----- (1) Read Parameters

# Get the location of the reference image `I`
imgref = input().rstrip()
# Get the filtering method identifier `F`
F = int(input())

# --- Read the filter parameters
size = 0
filter = None

if F == 1:
  size = int(input().rstrip())
  filter = np.fromstring(input().rstrip(), dtype=np.int32, sep=' ')
if F == 2:
  size = int(input().rstrip())
  filter = np.empty((size, size), dtype=np.int32)
  for i in range(size):
    filter[i] = np.fromstring(input().rstrip(), dtype=np.int32, sep=' ')
if F == 3:
  size = int(input().rstrip())

# --- Load image
I = imageio.imread(imgref)

# ----- (2) - Filtering

# --- Convolution functions

def conv_point1d(f, w, x, a):
  """
  Return the convolution of a 1D point.
  """
  # Get the subarray
  sub_f = f[x - a:x + a + 1]
  # Apply filter in the point x
  return np.sum(np.multiply(sub_f, w))

def conv_point2d(f, w, x, y, a, b):
  """
  Return the convolution of a 2D point.
  """
  # Create the sub 2d array
  sub_f = f[x - a:x + a + 1, y - b:y + b + 1]
  # Apply filter in the point x, y
  return np.sum(np.multiply(sub_f, w))

# Median filter

def median(f, x, y, a, b):
  """
  Return the median value of the `size`-neighbors of the given point.
  """
  # Create the sub 2d array
  sub_f = f[x - a:x + a + 1, y - b:y + b + 1]
  # Return the median
  arr = np.sort(np.asarray(sub_f).reshape(-1))
  return np.median(arr)

# --- Filters

def f1():
  """
  Filtering 1D.
  """
  # Get center of the filter
  c = int((size - 1) / 2)
  # Pad the flatten (1D array) image with wrapping
  If = np.pad(I.flatten(), (c), 'wrap')
  # Initialize the resulting image
  Ir = np.zeros(If.shape)
  # Apply 1D convulation in the image
  for x in range(c, Ir.shape[0] - c):
    Ir[x] = conv_point1d(If, filter, x, c)
  # Remove padding
  Ir = Ir[c:-c]
  # Return the resulting image with original shape
  return Ir.reshape(I.shape)

def f2():
  """
  Filtering 2D.
  """
  # Get center of the filter
  c = int((size - 1) / 2)
  # Pad the original image with edge
  Ip = np.pad(I, (c, c), 'edge')
  # Initialize the resulting image
  Ir = np.zeros(Ip.shape)
  N, M = Ir.shape
  for x in range(c, N - c):
    for y in range(c, M - c):
      Ir[x, y] = conv_point2d(Ip, filter, x, y, c, c)
  # Remove padding
  Ir = Ir[c:-c, c:-c]
  # Return the resulting image
  return Ir.reshape(I.shape)

def f3():
  """
  Median Filter.
  """
  a = int((size - 1) / 2)
  b = int((size - 1) / 2)
  # Pad the original image with constant
  Ip = np.pad(I, (size, size), 'constant', constant_values=(0, 0))
  # Initialize the resulting image
  Ir = np.zeros(Ip.shape)
  N, M = Ir.shape
  for x in range(size, N - size):
    for y in range(size, M - size):
      Ir[x, y] = median(Ip, x, y, a, b)
  # Remove padding
  Ir = Ir[size:-size, size:-size]
  # Return the resulting image
  return Ir.reshape(I.shape)

# --- Apply the given filter
def apply_filter():
  if F == 1:
    return f1()
  if F == 2:
    return f2()
  if F == 3:
    return f3()

# Apply filter
Ir = apply_filter()

# Normalize resultinrectg image and convert the values to uint8
Ir = (Ir - Ir.min()) / (Ir.max() - Ir.min())
Ir *= 255
Ir = Ir.astype(np.uint8)

# ----- (3) Comparing `Ir` against reference image `I`

# --- Calculate the root mean squared error
def rmse(I, Ir):
  N, M = I.shape
  rmse = 0.0
  for x in range(N):
    for y in range(M):
      rmse += math.pow(float(I[x, y]) - float(Ir[x, y]), 2)
  rmse = math.sqrt(rmse / (N * M))
  return round(rmse, 4)

# --- Print the computed result of RMSE
print(rmse(I, Ir))


import numpy as np
import matplotlib
matplotlib.rcParams = matplotlib.rc_params_from_file('../../matplotlibrc')
from matplotlib import pyplot as plt

def lnseries():
    ax = plt.subplot(1, 1, 1)
    X = np.linspace(-5, 30, 17501)
    Y = np.log(10**(-X) + 1) / 10**(-X)
    plt.plot(X, Y, label="$10^x \\log(10^{-x}+1)$")
    X = np.linspace(1, 30, 291)
    poly = np.poly1d([(-1.)**(i+1)/i for i in xrange(1,30)][::-1])
    Y = poly(10**(-X))
    plt.plot(X, Y, label="Series Approximation")
    handles, labels = ax.get_legend_handles_labels()
    plt.legend(handles, labels, loc="upper left")
    plt.savefig("lnseries.pdf")
    plt.clf()

def pnorm(x, p):
    P = p.reshape((1, -1))
    X = x.reshape((-1, 1))
    return ((X**P).sum(axis=0)**(1./P)).ravel()

def pnorm_convergence():
    ax = plt.subplot(1, 1, 1)
    powers = np.linspace(1, 7, 701)
    X = np.random.rand(100)+1
    N = pnorm(X, 10**powers)
    plt.plot([1,7], [X.max()]*2, label="$\\|x\\|_{\\infty}$")
    plt.plot(powers, N, label="p-norms of $x$")
    handles, labels = ax.get_legend_handles_labels()
    plt.legend(handles, labels, loc="upper right")
    plt.xlabel("$\\log_{10} p$")
    plt.ylabel("$\\| x \\|_p$")
    plt.savefig("pnorm_convergence.pdf")
    plt.clf()

if __name__ == "__main__":
    lnseries()
    pnorm_convergence()


#!/usr/bin/python
#
# Przyklad jednoczesnego sortowania wartosci wlasnych i wektorow wlasnych.
# The eigenvalues are not necessarily ordered.
# help(numpy.argsort)
# argsort(a, axis=-1, kind='quicksort', order=None)
# Returns the indices that would sort an array.
#
# https://stackoverflow.com/questions/33037616/large-matrix-diagonalization-python

import math
import numpy

A = numpy.array([[3,1,-1], [1,3,-1], [-1,-1,5]], dtype=float)
print ( A )
# Wartosci wlasne to 2, 3, 6, ale kolejnosc moze byc rozna.
# Wektory wlasne beda ulozone kolumnami!
# The column vec[:,i] is the eigenvector corresponding to the eigenvalue w[i].
w, vec =  numpy.linalg.eig(A)
#print (w, vec)
print ("eigenvalues", w)
print ("unit eigenvectors (in columns)")
print (vec)

# Zmieniam kolejnosc wartosci wlasnych i wektorow wlasnych.
print ( "Zmiana kolejnosci ..." )
idx = w.argsort()[::-1] #large to small
# idx = w.argsort() #small to large
# idx to jest index_array : ndarray, int.
w = w[idx]   # to dziala elementwise, faktycznie sortuje w
vec = vec[:,idx]   # wiersze po kolei, kolumny wg idx
print (w, vec)
# now they are ordered and you can iterate through your results
# to write them to your file

print ( "Sprawdzenie dzialania macierzy na wektory ..." )
# Mnozenie macierzy i wektora wlasnego ma byc rowne mnozeniu
# wartosci wlasnej i wektora wlasnego.
v0 = vec[:,0] # kolejne wektory wlasne z kolumn
v1 = vec[:,1]
v2 = vec[:,2]
print ( "A v", numpy.dot(A,v0) )
print ( "lam v", w[0] * v0 )
print ( "A v", numpy.dot(A,v1) )
print ( "lam v", w[1] * v1 )
print ( "A v", numpy.dot(A,v2) )
print ( "lam v", w[2] * v2 )   # bledy maszynowe

print ( "Diagonalizacja B^T A B ..." )
print ( numpy.dot(numpy.transpose(vec), numpy.dot(A,vec)) ) # dziala!

# EOF


# Online Bayesian linear regression using Kalman Filter
# Based on: https://github.com/probml/pmtk3/blob/master/demos/linregOnlineDemoKalman.m
# Author: Gerardo Durán-Martín (@gerdm)

import superimport

import numpy as np
import matplotlib.pyplot as plt
import pyprobml_utils as pml
from numpy.linalg import inv

def kf_linreg(X, y, sigma2, mu0, Sigma0, A):
    """
    Online estimation of a linear regression
    using Kalman Filters

    Parameters
    ----------
    X: array(n_obs, dimension)
        Matrix of features
    y: array(n_obs,)
        Array of observations
    sigma2: float
        Known variance
    mu0: array(dimension)
        Prior mean
    Sigma0: array(dimesion, dimension)
        Prior covariance matrix

    Returns
    -------
    * array(n_obs, dimension)
        Online estimation of parameters
    * array(n_obs, dimension, dimension)
        Online estimation of uncertainty
    """
    n_obs, dim = X.shape
    mu_hist = np.zeros((n_obs, dim))
    Sigma_hist = np.zeros((n_obs, dim, dim))

    I = np.eye(dim)
    Sigma_t = Sigma0.copy()
    mu_t = mu0.copy()

    mu_hist[0] = mu_t
    Sigma_hist[0] = Sigma_t
    for t in range(n_obs):
        xt, yt = X[t], y[t]
        Kt = inv(inv(Sigma_t) + np.outer(xt, xt) / sigma2) @ xt / sigma2
        mu_t = A @ mu_t + Kt * (yt - xt @ mu_t)
        Sigma_t = (I - Kt[:, None] @ xt[None, :]) @ Sigma_t

        mu_hist[t] = mu_t
        Sigma_hist[t] = Sigma_t

    return mu_hist, Sigma_hist

def posterior_lreg(X, y, sigma2, mu0, Sigma0):
    """
    Compute mean and covariance matrix of a
    Bayesian Linear regression

    Parameters
    ----------
    X: array(n_obs, dimension)
        Matrix of features
    y: array(n_obs,)
        Array of observations
    sigma2: float
        Known variance
    mu0: array(dimension)
        Prior mean
    Sigma0: array(dimesion, dimension)
        Prior covariance matrix

    Returns
    -------
    * array(dimension)
        Posterior mean
    * array(n_obs, dimension, dimension)
        Posterior covariance matrix
    """
    Sn_bayes_inv = inv(Sigma0) + X.T @ X / sigma2
    Sn_bayes = inv(Sn_bayes_inv)
    mn_bayes = Sn_bayes @ (inv(Sigma0) @ mu0 + X.T @ y / sigma2)

    return mn_bayes, Sn_bayes

if __name__ == "__main__":
    plt.rcParams["axes.spines.right"] = False
    plt.rcParams["axes.spines.top"] = False

    n_obs = 21
    timesteps = np.arange(n_obs)
    x = np.linspace(0, 20, n_obs)
    X = np.c_[np.ones(n_obs), x]
    A = np.eye(2)
    mu0 = np.zeros(2)
    Sigma0 = np.eye(2) * 10.

    sigma2 = 1
    # Data from original matlab example
    y = np.array([2.4865, -0.3033, -4.0531, -4.3359, -6.1742, -5.604, -3.5069, -2.3257, -4.6377,
          -0.2327, -1.9858, 1.0284, -2.264, -0.4508, 1.1672, 6.6524, 4.1452, 5.2677, 6.3403, 9.6264, 14.7842])

    # Online estimation
    mu_hist, Sigma_hist = kf_linreg(X, y, sigma2, mu0, Sigma0, A)
    kf_var = Sigma_hist[-1, [0, 1], [0, 1]]
    w0_hist, w1_hist = mu_hist.T
    w0_err, w1_err = np.sqrt(Sigma_hist[:, [0, 1], [0, 1]].T)

    # Offline estimation
    (w0_post, w1_post), Sigma_post = posterior_lreg(X, y, sigma2, mu0, Sigma0)
    w0_std, w1_std = np.sqrt(Sigma_post[[0, 1], [0, 1]])

    # Asserting values for means and variance
    assert np.allclose(w0_hist[-1], w0_post)
    assert np.allclose(w1_hist[-1], w1_post)
    assert np.allclose(w0_err[-1], w0_std)
    assert np.allclose(w1_err[-1], w1_std)

    fig, ax = plt.subplots()
    ax.errorbar(timesteps, w0_hist, w0_err, fmt="-o", label="$w_0$", color="black", fillstyle="none")
    ax.errorbar(timesteps, w1_hist, w1_err, fmt="-o", label="$w_1$", color="tab:red")

    ax.axhline(y=w0_post, c="black", label="$w_0$ batch")
    ax.axhline(y=w1_post, c="tab:red", linestyle="--", label="$w_1$ batch")

    ax.fill_between(timesteps, w0_post - w0_std, w0_post + w0_std, color="black", alpha=0.4)
    ax.fill_between(timesteps, w1_post - w1_std, w1_post + w1_std, color="tab:red", alpha=0.4)

    plt.legend()
    ax.set_xlabel("time")
    ax.set_ylabel("weights")
    ax.set_ylim(-8, 4)
    ax.set_xlim(-0.5, n_obs)

    pml.savefig("linreg-online-kalman.pdf")
    plt.show()


import numpy as np
import linalg_toolbox as linalg_tools

def get_projection_matrix(data, ndim, whiten=False):
    """
    Performs a PCA over the data and return the projection matrix.
    data: the data to be projected. Datapoints are given by rows while features
          are given by columns.
    ndim: the dimension of the lower dimentional subspace the data should be
          projected on.
    """
    n_datapoints, n_features = data.shape
    assert ndim <= n_features, 'We can\'t project onto a higher dimentional space.'

    St = compute_St(data)

    # Getting the eigen vectors (they correspond to the projection matrix we are
    # looking for).
    projection_matrix = linalg_tools.get_largest_eigen_vectors(St, ndim)
    if whiten:
        projection_matrix = linalg_tools.normalize_data(projection_matrix.T).T
    return projection_matrix

def project_using_projection_matrix(data_to_transform, projection_matrix):
    """
    Projects given data into lower dimentional subspace using the provided
    projection_matrix.
    """
    projected_data = data_to_transform * projection_matrix;
    return projected_data

def project(data, ndim, whiten=False):
    """
    Projects an array of data onto a lower dimentional subspace.
    The procedure here is the 'naive' one (as seen in the lectures).
    data: the data to be projected. Datapoints are given by rows while features
          are given by columns.
    ndim: the dimension of the lower dimentional subspace the data should be
          projected on.
    """
    projection_matrix = get_projection_matrix(data, ndim, whiten)
    return project_using_projection_matrix(data, projection_matrix)

def compute_St(data):
    """
    Given a dataset, computes the variance matrix of its features.
    """
    n_datapoints, n_features = data.shape

    # Computing the 'mean image'. A pixel at position (x,y) in this image is the
    # mean of all the pixels at position (x,y) of the images in the dataset.
    # This corresponds to the 'mu' we have seen in the lectures.
    mu = data.mean(axis=0) # apply along the rows for each columns.
    centered_data = data - mu

    # Computing the covariance matrix
    St = (1. / n_datapoints) * (centered_data.T * centered_data)
    return St


from pylab import *
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation

c = 9
m = 1
g = 9.81
L = g / c

def harmonicAcceleration(theta):
    return -c * theta

def pendulumAcceleration(theta):
    return -c * np.sin(theta)

def harmonicEnergy(theta, dTheta):
    return 0.5 * m * L**2 * dTheta**2 + 0.5 * m * g * L * theta**2

def pendulumEnergy(theta, dTheta):
    return 0.5 * m * L**2 * dTheta**2 + m * g * L * (1. - np.cos(theta))

def velocityVerlet(A, E, dt, theta, dTheta):
    a = A(theta)
    theta += dTheta * dt + 0.5 * a * dt * dt
    aNext = A(theta)
    dTheta += 0.5 * (aNext + a) * dt

    energy = E(theta, dTheta)
    return theta, dTheta, energy

def findPeriod(A, E, dt, initialTheta, initialDTheta):
    time = 0
    theta = initialTheta
    dTheta = initialDTheta

    def iterateUntilZero():
        nonlocal time, theta, dTheta
        if A(theta) == 0 and abs(dTheta) < 1e-10 and abs(theta) < 1e-10:
            return
        currentSign = sign(theta)
        while(sign(theta) == currentSign):
            time += dt
            theta, dTheta, energy = velocityVerlet(A, E, dt, theta, dTheta)

    iterateUntilZero()
    startTime = time
    iterateUntilZero()
    iterateUntilZero()
    endTime = time

    period = endTime - startTime
    return period

initialValues = np.linspace(0.001, np.pi/2, 25)
harmonicPeriods = []
pendulumPeriods = []
perturbationPeriods = []
for value in initialValues:
    harmonicPeriods.append(findPeriod(harmonicAcceleration,
                                      harmonicEnergy, 0.001, value, 0))
    pendulumPeriods.append(findPeriod(pendulumAcceleration,
                                      pendulumEnergy, 0.001, value, 0))
    perturbationPeriods.append(
        2*np.pi * np.sqrt(1/c) * (1 + value**2/16 + 11*value**4/3072) + 173*value**6/737820)

plt.figure()
plt.plot(np.divide(initialValues, np.pi), np.array(harmonicPeriods))
plt.plot(np.divide(initialValues, np.pi), np.array(pendulumPeriods))
plt.plot(np.divide(initialValues, np.pi), np.array(perturbationPeriods))
plt.figlegend(('Harmonic oscillator', 'Pendulum',
               'Perturbation series (to power 6)'))
plt.xlabel("θ(0)/pi")
plt.ylabel("T (s)")
plt.savefig("./plots/1_2/period.png", dpi=120)


# -*- coding: utf-8 -*-
import numpy as np

import scipy
import scipy.linalg
import scipy.optimize
import scipy.spatial

def vector(x, y, z):
    """ A shortcut for creating 3D-space vectors;
    in case you need a lot of manual np.array([...]) """
    return np.array([x, y, z])

def deg2rad(deg):
    """ Convert degrees (input) to radians """
    return deg*np.pi/180.

def rad2deg(rad):
    """ convert radians (input) to degrees """
    return rad*180./np.pi

def norm(vector):
    """ a shortcut to scipy.linalg.norm() """
    return scipy.linalg.norm(vector)

def unit_vector(vector):
    """ Returns a vector of magnitude 1 with the same direction"""
    return vector / norm(vector)

def angle_between(v1, v2):
    """ Returns the angle between vectors 'v1' and 'v2', in radians:

    >>> angle_between((1, 0, 0), (0, 1, 0))
    1.5707963267948966
    >>> angle_between((1, 0, 0), (1, 0, 0))
    0.0
    >>> angle_between((1, 0, 0), (-1, 0, 0))
    3.141592653589793

    Kudos: https://stackoverflow.com/questions/2827393/
    """
    v1_u = unit_vector(v1)
    v2_u = unit_vector(v2)

    return np.arccos(np.clip(np.dot(v1_u, v2_u), -1.0, 1.0))

def arbitrary_rotation_matrix(axis, theta):
    """
    Return the rotation matrix associated with counterclockwise rotation about
    the given axis by theta radians.
    # Kudos to
    # https://stackoverflow.com/questions/6802577/rotation-of-3d-vector
    #import math
    #
    # axis = np.asarray(axis)
    # axis = axis / math.sqrt(np.dot(axis, axis))
    # a = math.cos(theta / 2.0)
    # b, c, d = -axis * math.sin(theta / 2.0)
    # aa, bb, cc, dd = a * a, b * b, c * c, d * d
    # bc, ad, ac, ab, bd, cd = b * c, a * d, a * c, a * b, b * d, c * d
    # return np.array([[aa + bb - cc - dd, 2 * (bc + ad), 2 * (bd - ac)],
    #                  [2 * (bc - ad), aa + cc - bb - dd, 2 * (cd + ab)],
    #                  [2 * (bd + ac), 2 * (cd - ab), aa + dd - bb - cc]])

    # Also Kudos to the guy with another answer for the same question (used here): """
    return scipy.linalg.expm(np.cross(np.eye(3), axis/norm(axis)*theta))

def arbitrary_rotation(point, axis, theta, origin):
    """ Rotate a point around any axis given by axis by angle theta [radians] """

    rotated_point = np.dot(arbitrary_rotation_matrix(axis, theta), point - origin)
    return rotated_point + origin

def rotate(point, angle, axis='x'):
    """ Rotate a point around a given axis by specified angle """
    if axis == 'y':
        axis = vector(0, 1, 0)
    elif axis == 'z':
        axis = vector(0, 0, 1)
    elif axis == 'x':
        axis = vector(1, 0, 0)
    else:
        raise ValueError("Rotation axis should be either 'x', 'y', or 'z' ")

    return arbitrary_rotation(point, axis, angle, vector(0, 0, 0))

def to_polar(point, axis='z'):
    """ Convert (x, y, z) point to (radius, angle, height);
    the axis of the new polar coordinate system can be chosen ('x' or 'z') """

    assert axis in ['x', 'z']

    if axis == 'z':
        radius = (point[0]**2 + point[1]**2)**0.5
        angle = np.arctan2(point[1], point[0])
        height = point[2]
    else: # axis == 'x'
        radius = (point[1]**2 + point[2]**2)**0.5
        angle = np.arctan2(point[2], point[1])
        height = point[0]

    return vector(radius, angle, height)

def to_cartesian(p, direction=1, axis='z'):
    """ Converts a point given in (r, theta, z) coordinates to
    cartesian coordinate system.

    optionally, axis can be aligned with either cartesian axis x* or z and
    rotation sense can be inverted with direction=-1

    *when axis is 'x': theta goes from 0 at y-axis toward z-axis

    """
    assert direction in [-1, 1]
    assert axis in ['x', 'z']

    radius = p[0]
    angle = direction*p[1]
    height = p[2]

    if axis == 'z':
        return vector(radius*np.cos(angle), radius*np.sin(angle), height)

    # axis == 'x'
    return vector( height, radius*np.cos(angle), radius*np.sin(angle) )

def lin_map(x, x_min, x_max, out_min, out_max, limit=False):
    """ map x that should take values from x_min to x_max
        to values out_min to out_max"""
    r = float(x - x_min) * float(out_max - out_min) / \
        float(x_max - x_min) + float(out_min)

    if limit:
        return sorted([out_min, r, out_max])[1]
    else:
        return r

def xy_line_intersection(p_1, p_2, p_3, p_4):
    """ p_1 and p_2 define the first line, p_3 and p_4 define the second;
        return a point of intersection between these two lines in x-y plane

        Kudos: https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line
    """
    # only take x and y coordinates
    x1 = p_1[0]
    y1 = p_1[1]

    x2 = p_2[0]
    y2 = p_2[1]

    x3 = p_3[0]
    y3 = p_3[1]

    x4 = p_4[0]
    y4 = p_4[1]

    def det(p1, p2, p3, p4):
        return np.linalg.det(np.array([[p1, p2], [p3, p4]]))

    Dx1 = det(x1, y1, x2, y2)
    Dx2 = det(x1,  1, x2,  1)
    Dx3 = det(x3, y3, x4, y4)
    Dx4 = det(x3,  1, x4,  1)
    Dx5 = Dx2
    Dx6 = det(y1,  1, y2,  1)
    Dx7 = Dx4
    Dx8 = det(y3,  1, y4,  1)

    # x-coordinate
    Px = det(Dx1, Dx2, Dx3, Dx4)/det(Dx5, Dx6, Dx7, Dx8)

    # y-coordinate
    Dy1 = Dx1
    Dy2 = Dx6
    Dy3 = Dx3
    Dy4 = Dx8
    Dy5 = Dx2
    Dy6 = Dx6
    Dy7 = Dx7
    Dy8 = Dx8

    Py = det(Dy1, Dy2, Dy3, Dy4)/det(Dy5, Dy6, Dy7, Dy8)
    return vector(Px, Py, 0)

    # alternative solution with vectors
    # A = np.array([
    #     [p_2[0] - p_1[0], p_4[0] - p_3[0]],
    #     [p_2[1] - p_1[1], p_4[1] - p_3[1]],
    # ])
    #
    # b = np.array([p_3[0] - p_1[0], p_3[1] - p_1[1]])
    #
    # k1k2 = np.linalg.solve(A, b)
    # k1 = k1k2[0]
    # k2 = k1k2[1]
    #
    # va = vector(
    #     p_1[0] + k1*(p_2[0] - p_1[0]),
    #     p_1[1] + k1*(p_2[1] - p_1[1]),
    #     0
    # )
    #
    # vb = vector(
    #     p_3[0] + k2*(p_4[0] - p_3[0]),
    #     p_3[1] + k2*(p_4[1] - p_3[1]),
    #     0
    # )
    #
    # print(P-va, P-vb, norm(va-vb))
    # return va

def extend_to_y(p_1, p_2, y):
    """ Return a point that lies on a line defined by p_1 and p_2 and on y=y; only in xy-plane! """
    fk_3 = lambda k: p_1[1] + k*(p_2 - p_1)[1] - y
    k_3 = scipy.optimize.newton(fk_3, 0)

    return p_1 + k_3*(p_2 - p_1)

def arc_length_3point(A, B, C):
    """ Returns length of arc defined by 3 points, A, B and C; B is the point in between """
    A = np.asarray(A)
    B = np.asarray(B)
    C = np.asarray(C)

    t = C - A
    D = A + t/2
    h = B - D

    h = norm(h)
    t = norm(t)

    if h == 0:
        return 0

    fphi = lambda phi: 2*h*np.sin(phi/2) / (1 - np.cos(phi/2)) - t
    phi = scipy.optimize.newton(fphi, 1)
    r = h / (1-np.cos(phi/2))

    return r*phi


# Test this function to perform 'n' Bernoulli trials,
# with function: perform_bernoulli_trials(n, p),
# which returns the number of successes out of n Bernoulli trials,
# each of which has a probability 'p' of success.

# Think of a Bernoulli trial as a flip of a possibly biased coin.
# Specifically, each coin flip has a
# probability 'p' of landing on heads (success)
# probability '1−p' of landing tails (failure)

import array
import numpy as np
import matplotlib.pyplot as plt

def perform_bernoulli_trials(n, p):
    """Perform n Bernoulli trials with
        probability p (success) and return number of successes"""
    # Initialize number of successes: n_success
    # Start with 0 items in the array, it's going to be filled-up
    # One call to the function will do a loop 100 times
    # Note: it's a 'numpy'-array, so this function returns a 'numpy'-array

    n_success = []
    # print('Type of \'n_success\': ', type(n_success))

    # print('Before the has started, list is', n_success)

    # Perform trials
    for i in range(n):
        # Choose random number between zero and one
        random_number = np.random.random()

        # If less than 'p', store a 'True' as 1 into 'n_success'-list
        if random_number < p :
            # print('Loop', i + 1, 'of', n, '| p:',
            #       p, '| Randomly chosen number:',  round(random_number, 5))
            n_success.append(1)

    # print('After the for-loop (\'for i in range(n)\'): ', n_success)

    return n_success

# ============================================================================

# Seed random number generator
np.random.seed(100)

# Initialize the number of defaults: n_defaults
n_defaults = np.empty(1000, dtype = np.int)

# print(perform_bernoulli_trials(100, 0.05))

# Compute the number of defaults
for i in range(1000):
    n_defaults[i] = np.int(len(perform_bernoulli_trials(100, 0.05)))

# print('Defaults':', n_defaults)

# Compute bin edges: bins

# In a histogram, the total range of data set (i.e from minimum value to maximum value)
# is divided into 8 to 15 equal parts. These equal parts are known as bins or class intervals.
bins = np.arange(0, max(n_defaults) + 1.5) - 0.5

# Plot the histogram with default number of bins; label your axes
_ = plt.title('Probability according Bernoulli trials')
_ = plt.hist(n_defaults, density = True, bins = bins)
_ = plt.xlabel('Number of \'defaults\' (\'in gebreke zijnde\' ) out of 100 provided loans')
_ = plt.ylabel('Probability')

# Show the plot
plt.show()


import math
from typing import Union

import numpy as np

def unit_vector(vector: np.ndarray):
    """ Returns the unit vector of the vector. """
    return vector / np.linalg.norm(vector)

def angle_between_two_vectors_2d(v1: Union[list, tuple, np.ndarray], v2: Union[list, tuple, np.ndarray]) -> float:
    """ Returns the angle in radians between vectors 'v1' and 'v2' """
    # v1_u = v1 / np.linalg.norm(v1)
    # v2_u = v2 / np.linalg.norm(v2)
    # r = np.arccos2(np.clip(np.dot(v1_u, v2_u), -1.0, 1.0))

    ang1 = np.arctan2(*np.array(v1)[::-1])
    ang2 = np.arctan2(*np.array(v2)[::-1])
    r = (ang1 - ang2) % (2 * np.pi)

    return r

def _test_angle_between_two_vectors():
    v1 = [0, 10]
    v2 = [10, 0]
    assert angle_between_two_vectors_2d(v1, v2) == np.pi / 2

    v1 = [1, 0]
    v2 = [-1, -1]
    print(angle_between_two_vectors_2d(v2, v1))

def rotation_meshgrid(xx: np.ndarray, yy: np.ndarray, theta_in_radians: float):
    """Rotate a mesh grid by angle theta_in_radians.

    :param xx: x values of a mesh grid.
    :param yy: y values of a mesh grid.
    :param theta_in_radians: the angle to be rotated.
    :return (xx2, yy2): rotated arrays.
    """

    # Clockwise, 2D rotation_meshgrid matrix
    R = rotation_matrix(theta_in_radians)

    return np.einsum('ji, mni -> jmn', R, np.dstack([xx, yy]))

def rotation_matrix(theta_in_radians: float):

    # Clockwise, 2D rotation_meshgrid matrix

    R = np.array(
        [
            [np.cos(theta_in_radians), np.sin(theta_in_radians)],
            [-np.sin(theta_in_radians), np.cos(theta_in_radians)]]
    )

    return R

def _test_rotation_meshgrid():

    import matplotlib.pyplot as plt

    x_span = np.arange(-10, 11, 1)
    y_span = np.arange(-10, 11, 1)
    theta = 45/180*3.1415926

    xx_1, yy_1 = np.meshgrid(x_span, y_span)

    xx_2, yy_2 = rotation_meshgrid(xx_1, yy_1, theta)

    fig, (ax1, ax2) = plt.subplots(nrows=2)

    ax1.contourf(xx_1, yy_1, np.ones_like(xx_1))
    ax1.set_aspect('equal')
    ax1.set_xlim((-20, 20))
    ax1.set_ylim((-20, 20))

    ax2.contourf(xx_2, yy_2, np.ones_like(xx_2)*2)
    ax2.set_aspect('equal')
    ax2.set_xlim((-20, 20))
    ax2.set_ylim((-20, 20))

    x, y = rotation_meshgrid([10], [0], theta)

def find_line_segment_intersection_1(pt1, pt2, ptA, ptB):
    """ this returns the intersection of Line(pt1,pt2) and Line(ptA,ptB)

        returns a tuple: (xi, yi, valid, r, s), where
        (xi, yi) is the intersection
        r is the scalar multiple such that (xi,yi) = pt1 + r*(pt2-pt1)
        s is the scalar multiple such that (xi,yi) = pt1 + s*(ptB-ptA)
            valid == 0 if there are 0 or inf. intersections (invalid)
            valid == 1 if it has a unique intersection ON the segment    """

    DET_TOLERANCE = 0.00000001

    # the first line is pt1 + r*(pt2-pt1)
    # in component form:
    x1, y1 = pt1
    x2, y2 = pt2
    dx1 = x2 - x1
    dy1 = y2 - y1

    # the second line is ptA + s*(ptB-ptA)
    x, y = ptA;   xB, yB = ptB
    dx = xB - x;  dy = yB - y

    # we need to find the (typically unique) values of r and s
    # that will satisfy
    #
    # (x1, y1) + r(dx1, dy1) = (x, y) + s(dx, dy)
    #
    # which is the same as
    #
    #    [ dx1  -dx ][ r ] = [ x-x1 ]
    #    [ dy1  -dy ][ s ] = [ y-y1 ]
    #
    # whose solution is
    #
    #    [ r ] = _1_  [  -dy   dx ] [ x-x1 ]
    #    [ s ] = DET  [ -dy1  dx1 ] [ y-y1 ]
    #
    # where DET = (-dx1 * dy + dy1 * dx)
    #
    # if DET is too small, they're parallel
    #
    DET = (-dx1 * dy + dy1 * dx)

    if math.fabs(DET) < DET_TOLERANCE: return (0 ,0 ,0 ,0 ,0)

    # now, the determinant should be OK
    DETinv = 1.0 /DET

    # find the scalar amount along the "self" segment
    r = DETinv * (-dy  * ( x -x1) +  dx * ( y -y1))

    # find the scalar amount along the input line
    s = DETinv * (-dy1 * ( x -x1) + dx1 * ( y -y1))

    # return the average of the two descriptions
    xi = (x1 + r* dx1 + x + s * dx) / 2.0
    yi = (y1 + r * dy1 + y + s * dy) / 2.0
    return (xi, yi, 1, r, s)

def _test_find_line_segment_intersection_1():

    pt1 = (10, 10)
    pt2 = (20, 20)

    pt3 = (10, 20)
    pt4 = (20, 10)

    result = find_line_segment_intersection_1(pt1, pt2, pt3, pt4)
    print(result)

def find_line_segment_intersection_2(p0, p1, p2, p3) :

    s10_x = p1[0] - p0[0]
    s10_y = p1[1] - p0[1]
    s32_x = p3[0] - p2[0]
    s32_y = p3[1] - p2[1]

    denom = s10_x * s32_y - s32_x * s10_y

    if denom == 0 : return None # collinear

    denom_is_positive = denom > 0

    s02_x = p0[0] - p2[0]
    s02_y = p0[1] - p2[1]

    s_numer = s10_x * s02_y - s10_y * s02_x

    if (s_numer < 0) == denom_is_positive : return None # no collision

    t_numer = s32_x * s02_y - s32_y * s02_x

    if (t_numer < 0) == denom_is_positive : return None # no collision

    if (s_numer > denom) == denom_is_positive or (t_numer > denom) == denom_is_positive : return None # no collision

    # collision detected

    t = t_numer / denom

    intersection_point = [ p0[0] + (t * s10_x), p0[1] + (t * s10_y) ]

    return intersection_point

def _test_find_line_segment_intersection_2():

    pt1 = (10, 10)
    pt2 = (20, 20)

    pt3 = (10, 20)
    pt4 = (20, 10)

    result = find_line_segment_intersection_2(pt1, pt2, pt3, pt4)
    print(result)

def points_in_ploy(points_xy: list, poly_xy: list):

    poly_xy_list = list(poly_xy)
    poly_xy_list.append(poly_xy_list[0])

    results = np.full((len(points_xy),), False, dtype=np.bool)

    for i, point in enumerate(points_xy):
        p1 = (-1e100, point[1])
        p2 = (1e100, point[1])

        count_intersection = 0
        for j in range(len(poly_xy_list) - 1):
            p3 = poly_xy_list[j]
            p4 = poly_xy_list[j + 1]

            if find_line_segment_intersection_2(p1, p2, p3, p4):
                count_intersection += 1

        if count_intersection == 2:
            results[i] = True

    return results

def _test_points_in_ploy():
    points_xy = (
        (0, 0),
        (1, 1),
        (2, 2),
        (3, 3),
        (4, 4),
        (5, 5)
    )

    poly_xy = [
        (1.1, 1.1),
        (3.9, 1.1),
        (3.9, 3.9),
        (1.1, 3.9)
    ]

    res = points_in_ploy(points_xy, poly_xy)

    print(res)

def ray_tracing_numpy(x, y, poly):

    n = len(poly)
    inside = np.zeros(len(x),np.bool_)
    p2x = 0.0
    p2y = 0.0
    xints = 0.0
    p1x, p1y = poly[0]
    for i in range(n+1):
        p2x, p2y = poly[i % n]
        idx = np.nonzero((y > min(p1y, p2y)) & (y <= max(p1y, p2y)) & (x <= max(p1x,p2x)))[0]
        if p1y != p2y:
            xints = (y[idx]-p1y)*(p2x-p1x)/(p2y-p1y)+p1x
        if p1x == p2x:
            inside[idx] = ~inside[idx]
        else:
            idxx = idx[x[idx] <= xints]
            inside[idxx] = ~inside[idxx]

        p1x,p1y = p2x,p2y
    return inside

if __name__ == "__main__":
    # _test_find_line_segment_intersection_1()
    # _test_find_line_segment_intersection_2()
    _test_points_in_ploy()
    _test_rotation_meshgrid()
    _test_angle_between_two_vectors()


import numba
import numpy as np

def proof_of_pi(no_of_sims):
    """
    Proof pi using random numbers
    Given a circle radius = 1
    calculate how many random points fall between 1 distance from the centre.
    multiply by 4 as we only get quarter of a circle
    """
    sum = 0
    for i in range(no_of_sims):
        x = np.random.uniform()
        y = np.random.uniform()
        if ((x ** 2) + (y ** 2)) <= 1:
            sum += 1/no_of_sims
    return sum * 4
    # return 4 * sum([(np.random.uniform() ** 2 + np.random.uniform() ** 2 <= 1) for _ in range(no_of_sims)]) / no_of_sims

def proof_of_euler(big_number):
    """
    proof euler number using exponential growth
    """
    return (1 + 1/big_number) ** big_number

if __name__ == '__main__':
    no_paths = 100000
    delegate_proof_of_pi = numba.jit(proof_of_pi)

    print('numerical pi:', delegate_proof_of_pi(no_paths))
    print('real pi:', np.pi)
    print('numerical euler:', proof_of_euler(no_paths))
    print('euler', np.exp(1))


"""
A bag contains one red disc and one blue disc. In a game of chance a player
takes a disc at random and its colour is noted. After each turn the disc is
returned to the bag, an extra red disc is added, and another disc is taken at
random.

The player pays £1 to play and wins if they have taken more blue discs than red
discs at the end of the game.

If the game is played for four turns, the probability of a player winning is
exactly 11/120, and so the maximum prize fund the banker should allocate for
winning in this game would be £10 before they would expect to incur a loss.
Note that any payout will be a whole number of pounds and also includes the
original £1 paid to play the game, so in the example given the player actually
wins £9.

Find the maximum prize fund that should be allocated to a single game in which
fifteen turns are played.

Solution comment: Very quick, ~0.5 ms (would be much less for C++ impl.).
Derived explicitly the N = 4 case above, from which the pattern emerges.  The
memoization helps a whole bunch. Would have been slightly better to use DP and
build the table directly, but memoizing a recursive solution is so much nicer.
"""

import time
import numpy as np
from functools import lru_cache

def P(n):
    """Probability of drawing a blue in the n-th round."""
    return 1 / (n + 1)

@lru_cache(maxsize=None)
def Prob(blues, n, N):
    """
    Return probability of getting some number of blues, starting on round n,
    with N rounds total.
    """
    if blues <= 0:
        return 1    # Can always get 0 blues.
    elif blues > N - n + 1:
        return 0    # Can never get more blues than draws.
    elif blues == N - n + 1:
        return np.prod(P(np.arange(n, N+1)))  # Prob of getting blue of every draw.
    else:
        # Prob is prob of getting a blue now and then blues-1 on the remaining throws,
        # or prob of getting red now and blues blues on the remaining throws.
        return P(n) * Prob(blues - 1, n + 1, N) + (1-P(n)) * Prob(blues, n + 1, N)

if __name__ == "__main__":
    t0 = time.time()
    rounds = 15
    bet = 1
    winning_prob = Prob(rounds // 2 + 1, 1, rounds)
    print('Execution time: {:.3f} ms'.format((time.time() - t0) * 1e3))
    print('Max payout with {} rounds: {}£'.format(rounds, int(bet / winning_prob)))


# coding: utf-8

# In[3]:

'''
Statistical Computing for Scientists and Engineers
Homework 4 Problem 3 a
Fall 2018
University of Notre Dame
'''
import numpy as np
import matplotlib.pyplot as plt
import math
import scipy.stats

# the true distribution
def f(v):
    return scipy.stats.gamma.pdf(v,a=4.3,scale=1/6.2)
# the proposal distribution
def q(v):
    return scipy.stats.gamma.pdf(v,a=4,scale=1/7)

# supremum of f/q
M = 2.0

#Initialization
numSamples = 50000

x= np.linspace(0,4,1001)

#Accept - Reject algorithm, Sample from laplacian
def accept_reject():
    samples = np.random.gamma(4,1/7,numSamples)
    acceptanceProb = f(samples)/(M*q(samples))
    unif_samp = np.random.rand(1,numSamples)
    accepted = unif_samp < acceptanceProb
    return samples, accepted, unif_samp

# all the samps, accepted, unif_samps
samps,accepteds,unif_samps = accept_reject()

#filter the accepted samps
Samp= np.array([])
Unif_samp = np.array([])
SumN = 0
for i in range(0,numSamples):
    if (accepteds[0][i]==1):
        Samp=np.append(Samp,[samps[i]])
        Unif_samp = np.append(Unif_samp,[unif_samps[0][i]])
        SumN=SumN+1 # SumN counts the accepted samps

#calculate the expectation
E = np.array([0.0]*SumN)
Sum = 0
List = np.array([0]*SumN)
for i in range(0,SumN):
        Sum= Sum+Samp[i];
        E[i]=Sum/(i+1);
        List[i]=i+1

#plot the expectation
plt.figure(figsize=(8,8))
plt.plot(List,E)
plt.ylabel("<E>")
plt.xlabel("Iteration")
plt.savefig('h4p3a1.png')
plt.show()

#calculate the convergence
cov = np.array([0.0]*SumN)
for i in range(0,SumN):
    cov[i]=np.mean(pow(E[0:i]-Samp[0:i],2))

#plot the covergence
plt.figure(figsize=(8,8))
plt.plot(List,cov)
plt.ylabel("COV")
plt.xlabel("Iteration")
plt.savefig('h4p3a2.png')
plt.show()

#plot the True distribution & proposal distribution & accepted samps
plt.figure(figsize=(8,8))
plt.plot(x,M*q(x),c='orange',label=u'Proposal distribution')
plt.plot(x, f(x), 'r', label=u'True distribution') # f(x) is the True distribution
plt.scatter(Samp,M*Unif_samp*q(Samp),s=2,c='blue')
plt.legend()
plt.xlim([0,4])
plt.savefig('h4p3a3.png')
plt.show()

# plot histogram & true distribution
plt.figure(figsize=(8,8))
plt.hist(Samp,bins=50, alpha=0.4, label=u'sampled histogram', normed=True)
plt.plot(x, f(x), 'r', label=u'True distribution') # f(x) is the True distribution
plt.legend()
plt.xlim([0,4])
plt.savefig('h4p3a4.png')
plt.show()


# -*- coding: utf-8 -*-
"""
Created on Wed Apr 10 20:46:58 2019

@author:    Manuel Martinez
@project:   Mathematics for Machine Learning.
@purpose:   Week 2 PCA programming assignment
"""

# PACKAGE: DO NOT EDIT THIS LINE
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import scipy

import sklearn
# from ipywidgets import interact
# from load_data import load_mnist

def distance(x0, x1):
    """Compute distance between two vectors x0, x1 using the dot product"""
    d_vec = x0 - x1
    distance = np.sqrt(d_vec @ d_vec)
    return distance

def angle(x0, x1):
    """Compute the angle between two vectors x0, x1 using the dot product"""
    dot = x0 @ x1
    l1 = np.sqrt(x0 @ x0)
    l2 = np.sqrt(x1 @ x1)
    angle = np.arccos(dot / (l1 * l2))
    return angle

# find most similar
# np.argmin([distance(images[0], images[i]) for i in range(1, len(images))]) + 1

# GRADED FUNCTION: DO NOT EDIT THIS LINE
def most_similar_image():
    """Find the index of the digit, among all MNIST digits
       that is the second-closest to the first image in the dataset
       (the first image is closest to itself trivially).
       Your answer should be a single integer.
    """
    index = 61
    return index

# GRADED FUNCTION: DO NOT EDIT THIS LINE
def pairwise_distance_matrix(X, Y):
    """Compute the pairwise distance between rows of X and rows of Y

    Arguments
    ----------
    X: ndarray of size (N, D)
    Y: ndarray of size (M, D)

    Returns
    --------
    distance_matrix: matrix of shape (N, M), each entry distance_matrix[i,j]
    is the distance between ith row of X and the jth row of Y
    (we use the dot product to compute the distance).
    """
    if len(Y.shape) == 1:
        Y = Y.reshape(1, -1)
    N, D = X.shape
    M, D = Y.shape
    xydiff = X[:, :, None] - Y[:, :, None].T
    distance_matrix = np.sqrt((xydiff * xydiff).sum(1))
    return distance_matrix

# GRADED FUNCTION: DO NOT EDIT THIS LINE
def KNN(k, X, y, x):
    """K nearest neighbors
    k: number of nearest neighbors
    X: training input locations
    y: training labels
    x: test input
    """
    if len(x.shape) == 1:
        x = x.reshape(1, -1)
    N, D = X.shape
    num_classes = len(np.unique(y))
    dist = pairwise_distance_matrix(X, x)
    if dist.shape[1] == 1:
        dist = dist.ravel()

    # Next we make the predictions
    ypred = np.zeros(num_classes)
    # find the labels of the k nearest neighbors
    classes = y[np.argsort(dist)][:k]
    for c in classes:
        ypred[c] += 1

    return np.argmax(ypred)


import numpy as np

from sdia_python.lab2.utils import get_random_number_generator

class BallWindow:
    """Represents a ball in any dimension, defined by a center and a radius"""

    def __init__(self, center, radius):
        """Initializes a ball with a center and a radius. The radius must be positive.

        Args:
            center (numpy.array): coordinates of the center point
            radius (float): float representing the radius
        """
        center = np.array(center)
        assert len(center)
        assert radius >= 0

        self.center = center
        self.radius = float(radius)

    def __str__(self):
        return f"BallWindow: ({str(self.center)}, {str(self.radius)})"

    def __len__(self):
        """Returns the dimension of the ball

        Returns:
            int : dimension of the ball
        """
        return len(self.center)

    def __contains__(self, point):
        """Tells whether a point is contained in the ball

        Args:
            point (numpy.array): the point to test

        Returns:
            boolean: if it is contained in the ball
        """
        point = np.array(point)
        assert self.dimension() == point.size

        return np.linalg.norm(point - self.center) <= self.radius

    def dimension(self):
        """Returns the dimension of the ball

        Returns:
            int : dimension of the ball
        """
        return len(self)

    def volume(self):
        """Returns the volume of the ball. The formula for the volume of an n-ball is used : https://fr.wikipedia.org/wiki/N-sph%C3%A8re

        Returns:
            float : volume of the ball
        """
        n = self.dimension()
        R = self.radius
        if n % 2 == 0:  # formula in case dimension is even
            return (((np.pi) ** (n / 2)) * R ** n) / np.math.factorial(n / 2)
        else:  # formula in case dimension is odd
            odds = [i for i in range(1, n + 1, 2)]
            product = np.product(odds)
            return 2 ** ((n + 1) / 2) * np.pi ** ((n - 1) / 2) * R ** n / product

    def indicator_function(self, points):
        """Returns true if all points are in the ball.

        Args:
            points (np.array): Array of points to test

        Returns:
            bool: True if all points are in the box.
        """
        return np.all([point in self for point in points])

    def rand(self, n=1, rng=None):
        """Generates n points in the ball.

        Args:
            n (int, optional): Number of points generated. Defaults to 1.
            rng (int, optional): Random seed. Defaults to None.

        Returns:
            numpy.array: array containing the n generated points
        """
        rng = get_random_number_generator(rng)

        directions = rng.uniform(size=(n, self.dimension()))
        directions = np.array(
            [direction / np.linalg.norm(direction) for direction in directions]
        )
        distances = rng.uniform(0, self.radius, n)
        vectors = np.array(
            [
                direction * distance
                for (direction, distance) in zip(directions, distances)
            ]
        )
        return np.array([vector + self.center for vector in vectors])


"""
Author : Achintya Gupta
Date Created : 22-09-2020
"""

"""
Problem Statement
------------------------------------------------
Q) Surprisingly there are only three numbers that can be written as the sum of fourth powers of their digits:

                1634 = 14 + 64 + 34 + 44
                8208 = 84 + 24 + 04 + 84
                9474 = 94 + 44 + 74 + 44
                As 1 = 14 is not a sum it is not included.

    The sum of these numbers is 1634 + 8208 + 9474 = 19316.
    Find the sum of all the numbers that can be written as the sum of fifth powers of their digits.
"""
from utils import timing_decorator
import numpy as np

@timing_decorator
def digit_n_power(power=5):
    # Figure out the upper limit
    llimit,ulimit=0,int(1e9)
    for i in range(1,100):
        maxpossible = i*(9**power)
        if i>=len(str(maxpossible)):
            ulimit = int("".join(['9']*i))
            print(llimit, ulimit)
            break
    nums = []
    for i in range(llimit, ulimit):
        if sum([int(k)**power for k in list(str(i))]) == i and i not in [0,1]:
            nums.append(i)

    print(f'Sum : ', sum(nums))

digit_n_power()


import numpy as np
import matplotlib.pyplot as plt

# this calculates the derivatives
def b_j(x_list, y_list, i):
    if i == 0:
        b1 = (y_list[i+1] - y_list[i])/(x_list[i+1] - x_list[i])
        b2 = (y_list[i+2] - y_list[i])/(x_list[i+2] - x_list[i])
    elif i == len(x_list) - 1:
        b1 = (y_list[i] - y_list[i-1])/(x_list[i] - x_list[i-1])
        b2 = (y_list[i + 1] - y_list[i]) / (x_list[i + 1] - x_list[i])
    else:
        b1 = (y_list[i+1] - y_list[i-1]) / (x_list[i+1] - x_list[i-1])
        b2 = (y_list[i+2] - y_list[i]) / (x_list[i+2] - x_list[i])
    return b1, b2

# calculating the v_i terms
def v(x_i, x0, x1):
    v1 = (x_i-x0)*((x1-x_i)/(x1-x0))**2
    v2 = (x_i-x1)*((x0-x_i)/(x0-x1))**2
    return v1, v2

# calculating the u_i terms
def u(x_i, x0, x1):
    u1 = (1-2*(x_i-x0)/(x0-x1))*((x_i-x1)/(x0-x1))**2  # changed (x0-x1) to (x1-x0)
    u2 = (1-2*(x1-x_i)/(x1-x0))*((x0-x_i)/(x0-x1))**2  # original
    return u1, u2

def cubic_hermite(x_i, x, y):  # x_i is a point, x is a list of points, y is a list of points
    # we are doing the interpolation for 6 points, therefore there are 5 sub-domains
    # make an array of the different coefficients
    a_arr = []
    b_arr = []

    for i in range(6):
        # add y values
        a_arr.append(y[i])
        # add b values
        if i == 0:
            b_arr.append((y[i+1] - y[i])/(x[i+1] - x[i]))
        elif i == len(x) - 1:  # big guy did smthg different
            b_arr.append((y[i] - y[i - 1]) / (x[i] - x[i - 1]))
        else:
            b_arr.append((y[i+1] - y[i-1]) / (x[i+1] - x[i-1]))
    k = 0
    # calculate where the value lies w.r.t the other arrays
    while x_i > x[k]:
        k += 1

    # Now, calculate the value of the function at that point
    u1 = (1 - 2 * (x_i - x[k-1]) / (x[k-1] - x[k])) * ((x_i - x[k]) / (x[k-1] - x[k])) ** 2
    u2 = (1 - 2 * (x_i - x[k]) / (x[k] - x[k-1])) * ((x_i - x[k-1]) / (x[k] - x[k-1])) ** 2

    v1 = (x_i - x[k-1]) * ((x_i - x[k]) / (x[k-1] - x[k])) ** 2
    v2 = (x_i - x[k]) * ((x[k-1] - x_i) / (x[k] - x[k-1])) ** 2

    val = (a_arr[k-1] * u1 + b_arr[k-1]*v1 + a_arr[k]*u2 + b_arr[k] * v2)
    return val

if __name__ == "__main__":
    B = [0, 0.2, 0.4, 0.6, 0.8, 1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9]
    H = [0, 14.7, 36.5, 71.7, 121.4, 197.4, 256.2, 348.7, 540.6, 1062.8, 2318.0, 4781.9, 8687.4, 13924.3, 22650.2]

    B_2 = [0, 1.3, 1.4, 1.7, 1.8, 1.9]
    H_2 = [0, 540.6, 1062.8, 8687.4, 13924.3, 22650.2]

    x = np.arange(0, 1.9, 0.1)
    y_extrap = []

    for l in range(len(x)):
        y_extrap.append(cubic_hermite(x[l], B_2, H_2))

    # plot the extrapolation for Q1c)
    plt.plot(x, y_extrap, label='Cubic Hermite')
    plt.plot(B, H, label='H(B) curve')
    plt.xlabel("B field (T)")
    plt.ylabel("H field (A/M)")
    plt.legend(loc="upper left")
    plt.show()


from typing import List

import numpy as np

def arithmetic_mean(values: List[float]) -> float:
    """
    Computes the arithmetic mean of a list of values.
    """
    return sum(values) / len(values)

def geometric_mean(values: List[float]) -> float:
    """
    Computes the geometric mean of a list of values.
    """
    a = np.array(values)
    return a.prod() ** (1.0 / len(a))


"""
This script generates the plots to provide graphs helping to solve exercise 3.4.2.

The differential equation is given by:

x' = r*x - sinh(x)

An obvious fixed point is x* = 0 and the remaining piece of the bifurcation diagram
can be drawn by inverting the plot of r(x) = sinh(x) / x, albeit without the stability
information.
"""
import matplotlib.pyplot as plt
import numpy as np

def r(x):
    """
    Express r in terms of the fixed point x*.

    This is done to obtain the bifurcation diagram for x* != 0.
    """
    return np.sinh(x) / x

if __name__ == "__main__":
    # Plot r as a function of the fixed points
    # Inverting the axes of this plot, we obtain the bifurcation
    # diagram, albeit without the stability information (although,
    # if we know the general appearance of sub and supercritcal
    # pitchfork bifurcations, we can infer the stability of the
    # fixed points from the information of whether they appear
    # to the right of the bifurcation (stable) or to the left (unstable)).
    x_vals = np.linspace(-1, 1, 1000)
    r_vals = r(x_vals)

    plt.plot(x_vals, r_vals)
    plt.title(r"$r$ as a function of the fixed points $x^*$")
    plt.xlabel(r"$x^*$")
    plt.ylabel(r"$r(x^*)$")
    plt.show()

    # Compare rx and sinh(x) to determine the fixed points (from their intersection)
    # as well as their stability.

    x_vals = np.linspace(-3, 3, 1000)

    def rx(x, r):
        return x*r

    plt.plot(x_vals, np.sinh(x_vals), label=r"$\sinh(x)$")
    plt.plot(x_vals, rx(x_vals, -1), label=r"r=-1")
    plt.plot(x_vals, rx(x_vals, 0), label=r"r=0")
    plt.plot(x_vals, rx(x_vals, 1), label=r"r=1")
    plt.plot(x_vals, rx(x_vals, 2), label=r"r=2")
    plt.title(r"$rx$ vs $\sinh(x)$")
    plt.xlabel("$x$")
    plt.ylabel("$f(x)$")
    plt.legend()
    plt.show()


#
# Valuation of European Call Options in BSM Model
# and Numerical Derivation of Implied Volatility
# 03_stf/BSM_imp_vol.py
#
# (c) Dr. Yves J. Hilpisch
# from Hilpisch, Yves (2014): Python for Finance, O'Reilly.
#
from math import log, sqrt, exp
from scipy import stats
from scipy.optimize import fsolve

class call_option(object):
    ''' Class for European call options in BSM Model.

    Attributes
    ==========
    S0 : float
        initial stock/index level
    K : float
        strike price
    t : datetime/Timestamp object
        pricing date
    M : datetime/Timestamp object
        maturity date
    r : float
        constant risk-free short rate
    sigma : float
        volatility factor in diffusion term

    Methods
    =======
    value : float
        return present value of call option
    vega : float
        return vega of call option
    imp_vol : float
        return implied volatility given option quote
    '''

    def __init__(self, S0, K, t, M, r, sigma):
        self.S0 = float(S0)
        self.K = K
        self.t = t
        self.M = M
        self.r = r
        self.sigma = sigma

    def update_ttm(self):
        ''' Updates time-to-maturity self.T. '''
        if self.t > self.M:
            raise ValueError("Pricing date later than maturity.")
        self.T = (self.M - self.t).days / 365.

    def d1(self):
        ''' Helper function. '''
        d1 = ((log(self.S0 / self.K)
            + (self.r + 0.5 * self.sigma ** 2) * self.T)
            / (self.sigma * sqrt(self.T)))
        return d1

    def value(self):
        ''' Return option value. '''
        self.update_ttm()
        d1 = self.d1()
        d2 = ((log(self.S0 / self.K)
            + (self.r - 0.5 * self.sigma ** 2) * self.T)
            / (self.sigma * sqrt(self.T)))
        value = (self.S0 * stats.norm.cdf(d1, 0.0, 1.0)
            - self.K * exp(-self.r * self.T) * stats.norm.cdf(d2, 0.0, 1.0))
        return value

    def vega(self):
        ''' Return Vega of option. '''
        self.update_ttm()
        d1 = self.d1()
        vega = self.S0 * stats.norm.pdf(d1, 0.0, 1.0) * sqrt(self.T)
        return vega

    def imp_vol(self, C0, sigma_est=0.2):
        ''' Return implied volatility given option price. '''
        option = call_option(self.S0, self.K, self.t, self.M,
                             self.r, sigma_est)
        option.update_ttm()
        def difference(sigma):
            option.sigma = sigma
            return option.value() - C0
        iv = fsolve(difference, sigma_est)[0]
        return iv


from scipy.optimize import fmin

def shortest_confidence_interval(dist_name, confidence_coef=0.95, **args):
    """ Cálculo del intervalo de confianza mas corto.

    :param dist_name: nombre scipy de la distribución (str)
    :param confidence_coef: coeficiente de confianza (float entre 0 y 1)
    :param args: argumentos scipy de la distribución
    :return: límite inferior y superior del intervalo (array [low, high])

    Ejemplo de uso:

    c1, c2 = shortest_confidence_interval(beta, confidence_coef=0.9, a=5, b=3)
    """
    # configuración de la distribución con los argumentos dados
    distri = dist_name(**args)
    # estimación inicial de la probabilidad (área) de la cola inferior
    low_tail_pr_ini = 1.0 - confidence_coef

    def interval_width(low_tail_pr):
        """
        Cálculo del intervalo de confianza cuando el área de la cola inferior es
        low_tail_pr, por lo que el límite inferior es F^{-1}(low_tail_pr).

        :param low_tail_pr: probabilidad de la cola inferior (float entre 0 y 1)
        :return: largo del intervalo (float)
        """
        return distri.ppf(confidence_coef + low_tail_pr) - distri.ppf(low_tail_pr)

    # Busqueda de la probabilidad de la cola inferior (low_tail_pr) que minimiza el
    # intervalo de confianza (función interval_width) fijando el coeficiente de confianza.
    low_tail_pr_short = fmin(interval_width, low_tail_pr_ini, ftol=1e-8, disp=False)[0]
    # Se retorna el intervalo como un array([low, high])
    return distri.ppf([low_tail_pr_short, confidence_coef + low_tail_pr_short])


from sympy import *
from rodrigues_R_utils import *

x_1, y_1, z_1 = symbols('x_1 y_1 z_1')
px_1, py_1, pz_1 = symbols('px_1 py_1 pz_1')
sx_1, sy_1, sz_1 = symbols('sx_1 sy_1 sz_1')
x_2, y_2, z_2 = symbols('x_2 y_2 z_2')
px_2, py_2, pz_2 = symbols('px_2 py_2 pz_2')
sx_2, sy_2, sz_2 = symbols('sx_2 sy_2 sz_2')

position_symbols_1 = [px_1, py_1, pz_1]
rodrigues_symbols_1 = [sx_1, sy_1, sz_1]
position_symbols_2 = [px_2, py_2, pz_2]
rodrigues_symbols_2 = [sx_2, sy_2, sz_2]
all_symbols = position_symbols_1 + rodrigues_symbols_1 + position_symbols_2 + rodrigues_symbols_2

point_1 = Matrix([x_1, y_1, z_1, 1]).vec()
point_2 = Matrix([x_2, y_2, z_2, 1]).vec()

transformed_point_1 = (matrix44FromRodrigues(px_1, py_1, pz_1, sx_1, sy_1, sz_1) * point_1)[:-1,:]
transformed_point_2 = (matrix44FromRodrigues(px_2, py_2, pz_2, sx_2, sy_2, sz_2) * point_2)[:-1,:]

delta=Matrix([0,0,0]).vec()-(transformed_point_1-transformed_point_2)
delta_jacobian=delta.jacobian(all_symbols)

print(delta)
print(delta_jacobian)

with open("point_to_point_rodrigues_wc_jacobian.h",'w') as f_cpp:
    f_cpp.write("inline void point_to_point_rodrigues_wc(double &delta_x, double &delta_y, double &delta_z, double px_1, double py_1, double pz_1, double sx_1, double sy_1, double sz_1, double px_2, double py_2, double pz_2, double sx_2, double sy_2, double sz_2, double x_1, double y_1, double z_1, double x_2, double y_2, double z_2)\n")
    f_cpp.write("{")
    f_cpp.write("delta_x = %s;\n"%(ccode(delta[0,0])))
    f_cpp.write("delta_y = %s;\n"%(ccode(delta[1,0])))
    f_cpp.write("delta_z = %s;\n"%(ccode(delta[2,0])))
    f_cpp.write("}")
    f_cpp.write("\n")
    f_cpp.write("inline void point_to_point_rodrigues_wc_jacobian(Eigen::Matrix<double, 3, 12, Eigen::RowMajor> &j, double px_1, double py_1, double pz_1, double sx_1, double sy_1, double sz_1, double px_2, double py_2, double pz_2, double sx_2, double sy_2, double sz_2, double x_1, double y_1, double z_1, double x_2, double y_2, double z_2)\n")
    f_cpp.write("{")
    for i in range (3):
        for j in range (12):
            f_cpp.write("j.coeffRef(%d,%d) = %s;\n"%(i,j, ccode(delta_jacobian[i,j])))
    f_cpp.write("}")


import numpy as np
from scipy.special import eval_hermite, factorial

def hermite_functions(n, x, all_n=True, move_axes=(), method="recursive"):
    """
    Calculate the Hermite functions up to the nth order at position x, psi_n(x).

    For details see:
    https://en.wikipedia.org/wiki/Hermite_polynomials#Hermite_functions

    If all_n == True, then return all Hermite functions up to n
    If all_n == False, only return nth Hermite function
    If using recursive method, then the latter is more memory efficient as it
    only stores psi_n, psi_{n-1}, and psi_{n-2}

    The 'move_axes' option causes the output dimensions to be swapped around
    using np.moveaxis.

    Uses one of three possible calculation methods:
        'recursive' - Uses recursive method. Most efficient for n > 5.
        'direct'    - Calculates directly using Hermite polynomials.
                      Inefficient due to factorial and Hermite polynomial,
                      although useful for comparison when testing
        'analytic'  - Uses analytic expressions (only for n <= 5)

    Recursion relation:
        psi_n(x) = sqrt(2/n) * x * psi_{n-1}(x) - sqrt((n-1)/n) * psi_{n-2}(x)

    Examples:

    >>> x = np.mgrid[-2:3, 0:4]
    >>> x.shape
    (2, 5, 4)
    >>> n = 5
    >>> psi = hermite_functions(n, x, all_n=False)
    >>> psi.shape
    (2, 5, 4)
    >>> psi = hermite_functions(n, x, all_n=True)
    >>> psi.shape
    (6, 2, 5, 4)
    >>> reshape = ([0, 1, 2, 3], [1, 3, 2, 0])
    >>> psi = hermite_functions(n, x, all_n=True, move_axes=reshape)
    >>> psi.shape
    (4, 6, 5, 2)

    """

    if method not in ["recursive", "analytic", "direct"]:
        raise ValueError("Method not recognized.")
    if not (issubclass(type(n), int) or issubclass(type(n), np.integer)):
        raise TypeError("n must be an integer.")
    if n < 0:
        raise ValueError("n must be non-negative.")
    if method == "analytic" and (n > 5):
        raise ValueError("n must not be greater than 5 for analytic calculation.")

    if all_n:
        psi_n = _Hermite_all_n(n, x, method)
    else:
        psi_n = _Hermite_single_n(n, x, method)

    if move_axes:
        psi_n = np.moveaxis(psi_n, move_axes[0], move_axes[1])

    return psi_n

def _Hermite_single_n(n, x, method):
    """
    Calculates psi_n(x) for a single value of n.
    """

    if method == "analytic":
        return _H_analytic(n, x)

    if method == "direct":
        return _H_direct(n, x)

    psi_m_minus_2 = _H_analytic(0, x)
    if n == 0:
        return psi_m_minus_2

    psi_m_minus_1 = _H_analytic(1, x)
    if n == 1:
        return psi_m_minus_1

    for m in range(2, n + 1):
        psi_m = _H_recursive(m, x, psi_m_minus_2, psi_m_minus_1)
        psi_m_minus_2 = psi_m_minus_1
        psi_m_minus_1 = psi_m

    return psi_m

def _Hermite_all_n(n, x, method):
    """
    Calcualtes psi_m(x) for all 0 <= m <= n.
    """

    try:
        psi_n = np.zeros((n + 1,) + x.shape)
    except AttributeError:  # x does not have property 'shape'
        psi_n = np.zeros((n + 1, 1))

    if method == "analytic":
        for m in range(n + 1):
            psi_n[m, :] = _H_analytic(m, x)
        return psi_n

    if method == "direct":
        for m in range(n + 1):
            psi_n[m, :] = _H_direct(m, x)
        return psi_n

    psi_n[0, :] = _H_analytic(0, x)
    if n == 0:
        return psi_n

    psi_n[1, :] = _H_analytic(1, x)
    if n == 1:
        return psi_n

    for m in range(2, n + 1):
        psi_n[m, :] = _H_recursive(m, x, psi_n[m - 2, :], psi_n[m - 1, :])

    return psi_n

def _H_recursive(m, x, psi_m_minus_2, psi_m_minus_1):
    """
    Calculate psi_m(x) using recursion relation.
    """
    return np.sqrt(2 / m) * x * psi_m_minus_1 - np.sqrt((m - 1) / m) * psi_m_minus_2

def _H_analytic(n, x):
    """
    Analytic expressions for psi_n(x) for 0 <= n <= 5.
    """

    if n == 0:
        return np.pi ** (-1 / 4) * np.exp(-(x ** 2) / 2)
    if n == 1:
        return np.sqrt(2) * np.pi ** (-1 / 4) * x * np.exp(-(x ** 2) / 2)
    if n == 2:
        return (
            (np.sqrt(2) * np.pi ** (1 / 4)) ** (-1)
            * (2 * x ** 2 - 1)
            * np.exp(-(x ** 2) / 2)
        )
    if n == 3:
        return (
            (np.sqrt(3) * np.pi ** (1 / 4)) ** (-1)
            * (2 * x ** 3 - 3 * x)
            * np.exp(-(x ** 2) / 2)
        )
    if n == 4:
        return (
            (2 * np.sqrt(6) * np.pi ** (1 / 4)) ** (-1)
            * (4 * x ** 4 - 12 * x ** 2 + 3)
            * np.exp(-(x ** 2) / 2)
        )
    if n == 5:
        return (
            (2 * np.sqrt(15) * np.pi ** (1 / 4)) ** (-1)
            * (4 * x ** 5 - 20 * x ** 3 + 15 * x)
            * np.exp(-(x ** 2) / 2)
        )
    raise ValueError("n must be an integer between 0 and 5")

def _H_direct(n, x):
    """
    Calculate psi_n(x) using explicit definition.
    """
    return (
        1
        / np.sqrt(2 ** n * factorial(n))
        * np.pi ** (-1 / 4)
        * np.exp(-(x ** 2) / 2)
        * eval_hermite(n, x)
    )

if __name__ == "__main__":
    import doctest

    doctest.testmod()


"""
    Author: Kelvin Tsang
    Github: https://github.com/Kelviiiin/DCT_Basis_Image

    Creates the basis images of discrete cosine tranformation.
    https://upload.wikimedia.org/wikipedia/commons/2/23/Dctjpeg.png
"""

import os
from math import cos, pi
import cv2
import numpy as np

class DCT_Basis_Image:
    def __init__(self):
        # size of the square image 8x8
        self.size = 8
        # folder name where the images are saved
        self.folderName = "BasisImage"

    def create(self):
        # create image set
        imageSet = []
        for v in range(0, self.size):
            for u in range(0, self.size):
                basisImg = self.getBasisImage(u, v)
                imageSet.append(basisImg)

        # create folder
        if (not os.path.isdir(self.folderName)):
            os.makedirs(self.folderName)

        # name of the images are numbered consecutively
        for i in range(0, len(imageSet)):
            number = None
            if(i < 10):
                number = "0" + str(i)
            else:
                number = str(i)
            # image directory
            directory = 'BasisImage/' + number + '.png'
            # save images with cv2
            cv2.imwrite(directory, imageSet[i])

        print('Done')

    # discrete cosinus transform
    def dct(self, x, y, u, v, n):
        return cos(((2 * x + 1) * (u * pi)) / (2 * n)) * cos(((2 * y + 1) * (v * pi)) / (2 * n))

    def cosValueToGrayValue(self, val):
        return int((val+1)/2 * 256)

    def getBasisImage(self, u, v):
        # for a given (u,v), make a DCT basis image
        basisImg = np.zeros((self.size, self.size))
        for y in range(0, self.size):
            for x in range(0, self.size):
                basisImg[y, x] = self.cosValueToGrayValue(self.dct(x, y, u, v, self.size))
        return basisImg

basisImage = DCT_Basis_Image()
basisImage.create()


r"""
Solve Poisson equation on (0, 2pi)x(0, 2pi)x(0, 2pi) with periodic bcs

    \nabla^2 u = f,

Use Fourier basis and find u in VxVxV such that

    (v, div(grad(u))) = (v, f)    for all v in VxVxV

where V is the Fourier basis span{exp(1jkx)}_{k=-N/2}^{N/2-1} and
VxVxV is a tensorproductspace.

"""
import os
from sympy import symbols, cos, sin, lambdify
import numpy as np
from shenfun import inner, div, grad, TestFunction, TrialFunction, FunctionSpace, \
    TensorProductSpace, Array, Function, dx, comm

# Use sympy to compute a rhs, given an analytical solution
x, y, z = symbols("x,y,z", real=True)
ue = cos(4*x) + sin(4*y) + sin(6*z)
fe = ue.diff(x, 2) + ue.diff(y, 2) + ue.diff(z, 2)

# Size of discretization
N = (14, 15, 16)

K0 = FunctionSpace(N[0], 'F', dtype='D')
K1 = FunctionSpace(N[1], 'F', dtype='D')
K2 = FunctionSpace(N[2], 'F', dtype='d')
T = TensorProductSpace(comm, (K0, K1, K2))
u = TrialFunction(T)
v = TestFunction(T)

# Get f on quad points
fj = Array(T, buffer=fe)

# Compute right hand side
f_hat = Function(T)
f_hat = inner(v, fj, output_array=f_hat)

# Solve Poisson equation
A = inner(v, div(grad(u)))
f_hat = A.solve(f_hat)

uq = T.backward(f_hat, fast_transform=True)

uj = Array(T, buffer=ue)
print(np.sqrt(dx((uj-uq)**2)))
assert np.allclose(uj, uq)

# Test eval at point
point = np.array([[0.1, 0.5], [0.5, 0.6], [0.1, 0.2]])
p = T.eval(point, f_hat)
ul = lambdify((x, y, z), ue)
assert np.allclose(p, ul(*point))
p2 = f_hat.eval(point)
assert np.allclose(p2, ul(*point))

if 'pytest' not in os.environ:
    import matplotlib.pyplot as plt
    plt.figure()
    X = T.local_mesh(True) # With broadcasting=True the shape of X is local_shape, even though the number of datapoints are still the same as in 1D
    plt.contourf(X[0][:, :, 0], X[1][:, :, 0], uq[:, :, 0])
    plt.colorbar()

    plt.figure()
    plt.contourf(X[0][:, :, 0], X[1][:, :, 0], uj[:, :, 0])
    plt.colorbar()

    plt.figure()
    plt.contourf(X[0][:, :, 0], X[1][:, :, 0], uq[:, :, 0]-uj[:, :, 0])
    plt.colorbar()
    plt.title('Error')
    plt.show()


# Solving Ax = b using steepest descent method
# Equation: Equation 1: x - y = -10
#           Equation 2: 2x + 3y = 15
# A = [1,-1; 2,3] and b = [-10; 15]

import numpy as np

A = np.zeros((2,2))         # Define A matrix
A[0,0] = 1.0
A[0,1] = -1.0
A[1,0] = 2.0
A[1,1] = 3.0

b = np.zeros((2,1))         # Define b vector
b[0] = -10.0
b[1] = 15.0

res = np.ones((2,1))        # Define the residue res = b - Ax
res[0] = 10
res[1] = 10

x = np.ones((2,1))          # Initial guess solution
x[0] = 0
x[1] = 0

count = 0
while res[0] > 0.001 or res[1] > 0.001:
    res = b - np.dot(A, x)
    p = np.dot(np.transpose(res),A)
    q = np.dot(p,res)
    alp = (np.dot((np.transpose(res)), res)) / float(q)
    x = x + alp*res
    count = count+1

print ("Actual solution is: ")
print (np.dot(np.linalg.inv(A),b))

print ("Iterative Solution x is:  ")
print (x)

print ("The final residue is:   ")
print (res)

print ("Number of iterations are:   ")
print (count)


import numpy as np
#% matplotlibinline
import matplotlib.pyplot as plt

"""
When computing the z-score for each sample on the data set a threshold must be
specified. Some good ‘thumb-rule’ thresholds can be: 2.5, 3, 3.5 or
more standard deviations.
"""

def detect_outliers_with_z_score(data):

    """
    Using Z score
    Formula for Z score = (Observation — Mean)/Standard Deviation

    z = (X — μ) / σ
    """
    outliers = []
    threshold = 2.4  # Some good ‘thumb-rule’ thresholds can be: 2.5, 3, 3.5
    mean = np.mean(data)
    std = np.std(data)

    for i in data:
        z_score = (i - mean)/std
        if np.abs(z_score) > threshold:
            outliers.append(i)
    return outliers

if __name__ == "__main__":

    #https: // github.com/krishnaik06/Finding-an-Outlier/blob/master/Finding % 20an % 20outlier % 20in % 20a % 20Dataset.ipynb
    dataset = [11, 10, 12, 14, 12, 15, 14, 13, 15, 102, 12, 14, 17, 19, 107, 10,
               13, 12, 14, 12, 108, 12, 11, 14, 13, 15, 10, 15, 12, 10, 14, 13, 15, 10]

    print(detect_outliers_with_z_score(dataset))
    pass