import numpy as np
import matplotlib.pyplot as pl
from scipy.sparse import spdiags, linalg

def five_pt_laplacian_sparse(m):
    e = np.ones(m ** 2)
    e2 = ([1] * (m - 1) + [0]) * m
    e3 = ([0] + [1] * (m - 1)) * m
    h = 1. / (m + 1)
    A = spdiags([-4 * e, e2, e3, e, e], [0, -1, 1, -m, m], m ** 2, m ** 2)
    A /= h ** 2
    return A

# Define the RHS function:
f = lambda x, y: (2 - (np.pi ** 2) * (x ** 2)) * np.cos(np.pi * y);

#let calc rate of convergence and all the solutions and error

normMaxValues = np.zeros(8);
hValues = np.zeros(len(normMaxValues));
for i in range(0, len(normMaxValues)):
    m = 9 + 2 ** i;
    h = 1. / (m + 1);
    x = np.linspace(0, 1, m + 2); x = x[1:-1];
    y = np.linspace(0, 1, m + 2); y = y[1:-1];
    hValues[i] = x[1] - x[0];
    X, Y = np.meshgrid(x, y);

    #Set up and solve the linear system
    A = five_pt_laplacian_sparse(m).tocsr();

    F = f(X, Y);#.reshape([m**2]);
    #y index is inversed! and don't forget to divide by h^2
    F[:, 0] -= 0;
    F[:, -1] -= np.cos(np.pi * y) / h ** 2;
    F[-1, :] -= -x ** 2 / h ** 2;
    F[0, :] -= x ** 2 / h ** 2;
    F = F.reshape([m ** 2]);

    U = linalg.spsolve(A, F)
    U = U.reshape([m, m]);

    U_exact = X ** 2 * np.cos(np.pi * Y)
    #U_exact = U_exact.reshape([m**2]);
    error = U - U_exact
    #don't forget to reshape, otherwise norm will be wrong
    normMaxValues[i] = np.linalg.norm(error.reshape([m ** 2]), np.inf);

pl.clf();
pl.subplots_adjust(hspace=1);

pl.subplot(4, 1, 1);
pl.pcolor(X, Y, U)
pl.colorbar();
pl.xlabel('x');
pl.ylabel('y');
pl.title('Figure 7a. Numerical solution');

pl.subplot(4, 1, 2);
pl.pcolor(X, Y, U_exact);
pl.xlabel('x');
pl.ylabel('y');
pl.title('Figure 7b. Exact solution');
pl.colorbar();

pl.subplot(4, 1, 3);
pl.pcolor(X, Y, error);
pl.xlabel('x');
pl.ylabel('y');
pl.title('Figure 7c. Error');
pl.colorbar();

# pl.subplot(4, 1, 4);
# pl.pcolor(X, Y, F.reshape([m,m]));
# pl.xlabel('x');
# pl.ylabel('y');
# pl.title('Figure 7c. forcing');
# pl.colorbar();

pl.subplot(4, 1, 4);
pl.loglog(hValues, normMaxValues, label='Max norm');
pl.xlabel('h');
pl.ylabel('Max norm');
pl.legend(loc='best');
pl.title('Figure 7d. Convergence');

pl.show();


import sys
import scipy.io as sio
from pprint import pprint
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D

mat_fname = "data_logistic.mat"
mat_dict = sio.loadmat(mat_fname)

samples = mat_dict["logistic_data"]

# m = features number
# n = samples number
# hθ = sigmond(θT x)

# J(θ) = -1/m * sum(Y(i) * log(hθ(X(i)) - (1 - Y(i)) * log(1 - hθ(X(i))))) + L * sum(θj ^ 2)
# θj :=  θj * (1 - (alpha * L) /m) - alpha/m * (hθ(X(i) - Y(i)) * X(i)j
# j = 0,1,..,n

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

def loss(h, y):
    return (-y * np.log(h) - (1 - y) * np.log(1 - h)).mean()

X = []
Y = []
m = 0
for sample in samples:
	X.append(sample[0:2])
	Y.append(sample[2])
	m += 1
X = np.array(X)
Y = np.array(Y)

alpha = 0.01
num_iter = 10000
theta = np.zeros(X.shape[1])
L = 0.01 #L2-norm effect here

for i in range(num_iter):
	z = np.dot(X, theta)
	h = sigmoid(z)
	gradient = np.dot(X.T, (h - Y)) / m
	theta = theta * (1 - (alpha * L) / m) - alpha * gradient  #L2-norm effect here

z = np.dot(X, theta)
h = sigmoid(z)

term = 0
for t in theta:
	term = t**2
term = L * term #L2-norm effect here

print(' L:', L, '\n\n Optimal Loss:', loss(h, Y) + term, '\n')
print(' Theta: ', theta)


import sys
import random
import math
import numpy as np

# num_shoots is N from the exercise sheet.
def shoot_pi(num_shoots = 10, verbose = True):

    if verbose:
        print('Shooting PI with ' + str(num_shoots) + ' samples...')

    # shoot randomly (uniform) at 1x1 area at count hits of inner quarter circle.
    num_hits = 0
    for _ in range(num_shoots):
        x = random.random()
        y = random.random()
        point = np.array([x, y])
        if (np.linalg.norm(point) < 1):
            num_hits += 1

    # compute pi as ratio of hits to all samples
    # (factor 4 necessary, since we're only shooting at a quarter circle)
    pi = (num_hits / num_shoots) * 4

    if verbose:
        print('PI is approximately ', str(pi), '!', sep='')

    return pi

if __name__ == '__main__':
    if len(sys.argv) == 2:
        shoot_pi(int(sys.argv[1]))
    else:
        shoot_pi()


'''
'''
# Standard library imports
from math import pi

# Third party imports
import numpy as np

def rebar_coordinates(r_rebars, n_rebars):
    '''
    Return evenly spaced rebars on a circle perimeter.

    Parameters
    ----------
    r_rebars : ...
        ...

    Returns
    -------
        ...
    '''

    theta = np.linspace(0, 2*pi-2*pi/n_rebars, n_rebars)

    return r_rebars * np.cos(theta), r_rebars * np.sin(theta)

def neutral_axis_locs(bounds, n_locations, traverse_upwards=True):
    '''
    Parameters
    ----------

    Yields
    ------
    number
        ...

    Todo
    ----
    '''
    # Unpack boundary values for traversal
    ymin, ymax = bounds

    # Generate points over the interval
    y_locations = np.linspace(ymin, ymax, n_locations)

    y_locations[0] = -99999
    y_locations[-1] = 99999

    return y_locations if traverse_upwards else np.flip(y_locations)


import numpy as np

def mean_square_error(labels, predictions,derivative=False):
  if derivative==False:
    mse=np.mean((labels-predictions)**2)
    return mse
  else:
    dmse=-2*np.mean(labels-predictions)
    return dmse

def binary_cross_entropy(labels,predictions, derivative=False):
  if derivative==False:
    bce=-np.sum(labels*np.log(predictions) + (1-labels)*np.log(1-predictions))
    return bce
  else:
    dbce=-np.sum(labels/predictions + (labels-1)/(1-predictions))
    return dbce

def categorical_cross_entropy(labels, predictions):
  pass


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

# Cost function
def cost(m, x, y, z):
	err=0.0
	for i in range(0, m):
		err += ((hypothesis(i, x, z) - y[i])**2)
	err = err/(2*m)
	return float(err)

# Hypothesis
def hypothesis(i, x, z):
	h =  z[0]*1+z[1]*x[i]
	return h

# Generate linear function
def graph_gen(low, high, size):
	g=np.arange(size, dtype=float)
	for i in range(0 , size):
		g[i] = i + random.uniform(low,high)
	return g

# Input used for tranning
#x=np.array([0,1,2,3,4,5])
x=np.arange(100,dtype=float)

# Output used for tranning
#y=np.array([0,1,2,3,4,5])
y=np.random.normal(5,1,100)
#y=graph_gen(0,2,100)

# Initiaize theta
z=np.array([0,0])

# Range of theta value
theta_range=50

# Size of rows or Columns
theta_sz=2*theta_range+1 #1 for including 0 value

# Number of input elements
m=x.size

# Results of Squared Error
result_squared_err = np.ones(((2*theta_range)+1, (2*theta_range)+1), dtype=float)

# calculate squared error in order to find minimal squared error value
for z0 in range(-theta_range ,theta_range+1):
	z[0] = z0
	for z1 in range(-theta_range ,theta_range+1):
		z[1] = z1
		result_squared_err[z0+theta_range][z1+theta_range] = cost(m, x, y, z)

# Find the position of minimal squared error
pos_theta = np.unravel_index(result_squared_err.argmin(),result_squared_err.shape)

# Transfrom to one-dimensional arrays
result_squared_err_1d = result_squared_err.reshape((1,theta_sz*theta_sz))

print "***** Mininal Squared Error *****"
print "---------------------------------"
print "Position of theta : %s           " %(str(pos_theta))
print "---------------------------------"
print "minimal squared error   : %f"  %(result_squared_err.min())
print "Theta(%d,%d)            : %f" %(pos_theta [0]-theta_range, pos_theta [1]-theta_range, result_squared_err[pos_theta [0]][pos_theta [1]])

#print result_squared_err
#print result_squared_err_1d

# Plot original data
plt.plot(x,y,'rs')

# Plot linear regression function
x_grph=np.arange(m);
plt.plot(x_grph, 1*(pos_theta [0]-theta_range)+x_grph*(pos_theta [1]-theta_range), 'b-')

# Plot squared error
# new_row = ((row+1)*row_sz)-(row_sz-(col+1)))
#row_sz = 2*theta_range+1
#px = ((pos_theta [0]+1)*row_sz)-(row_sz-(pos_theta [1]+1))
#plt.plot(result_squared_err_1d[0], 'go', px,result_squared_err.min(),'rs')

# Display Graph
plt.axis([0.,100.,0.,100.])
plt.show()


import numpy as np
import pandas as pd
import logging
import matplotlib.pyplot as plt
from numpy.linalg import inv, multi_dot

logger = logging.getLogger(__name__)

def load_dataset(csv_path):
    return pd.read_csv(csv_path)

class BatchLeastSquares:
    """バッチ型最小２乗法.

    Attributes:
        estimated_x (float): 推定値x.
    """
    def __init__(self, z, h, r):
        """
        Args:
            z (array_like, 1-D): 観測値リスト.
            h (array_like, 2-D): 変換行列.
            r (array_like, 1-D): 共分散リスト.
        """
        z = z.reshape(-1, 1)
        r = np.diag(r)
        self.estimated_x = multi_dot([inv(multi_dot([h.T, inv(r), h])), h.T, inv(r), z])

class IterLeastSquares:
    """逐次型最小２乗法.

    Attributes:
        estimated_x (float): 最終的な推定値x.
        p (array_like, 2-D): 最終的な推定誤差共分散p.
        x_list (array_like, 2-D): 各時刻における推定値xのリスト.
    """
    def __init__(self, z, h, r, init_x, init_p):
        """
        Args:
            z (array_like, 1-D): 観測値リスト.
            h (array_like, 2-D): 変換行列.
            r (array_like, 1-D): 共分散リスト.
            init_x (array_like, 1-D): 推定値xの初期値.
            init_p (array_like, 2-D): 推定誤差共分散pの初期値.
        """
        xi = init_x.reshape(-1, 1)
        pi = init_p

        self.x_list = []

        for i in range(z.size):
            hi = h[i].reshape(1, -1)  # shape (1, 3)
            ri = r[i]  # scalar
            zi = z[i]  # scalar
            si = multi_dot([hi, pi, hi.T]) + ri  # shape (1, 1)
            wi = multi_dot([pi, hi.T, inv(si)])  # shape (3, 1)
            xi = xi + np.dot(wi, (zi - np.dot(hi, xi)))  # shape (3, 1)
            pi = pi - multi_dot([wi, si, wi.T])  # shape (3, 3)

            self.x_list.append(xi.T[0])

        self.x_list = np.array(self.x_list)
        self.estimated_x = xi
        self.p = pi

def main():
    # logger設定
    logging.basicConfig(
        level=logging.INFO,
        filemode='w',
        filename='log/least_squares.log'
    )
    # データセットの読み込み
    dataset_path = 'dataset.csv'
    dataset_df = load_dataset(dataset_path)

    # 行列生成
    k = dataset_df['k'].to_numpy()
    z = dataset_df['z'].to_numpy()
    h = np.stack([np.ones(k.size), k, k*k], axis=1)
    r = np.where(k % 2 == 0, 4, 1)
    logger.info(f'K: \n{k}\n')
    logger.info(f'Z: \n{z}\n')
    logger.info(f'H: \n{h}\n')
    logger.info(f'R: \n{r}\n')

    """
    ************
    --- （1) ---
    ************
    """
    # バッチ型最小２乗法
    x = BatchLeastSquares(z, h, r).estimated_x
    print(f'推定値x（バッチ型）: \n{x}\n')
    logger.info(f'推定値x（バッチ型）: \n{x}\n')

    # 逐次型最小２乗法
    init_x = np.zeros(3)
    init_p = np.identity(3) * 10**6
    x = IterLeastSquares(z, h, r, init_x, init_p).estimated_x
    print(f'推定値x（逐次型）: \n{x}\n')
    logger.info(f'init X: \n{init_x}\n')
    logger.info(f'init P: \n{init_p}\n')
    logger.info(f'推定値x（逐次型）: \n{x}\n')

    """
    ************
    --- （2）---
    ************
    """
    # 推定誤差共分散pの初期値を変えて，逐次型最小２乗法を適用
    label_list = ['10**3', '10**0',  '10**-3']
    init_p_list = [np.identity(3) * 10**3, np.identity(3) * 10**0, np.identity(3) * 10**-3]
    x_list = []
    for init_p in init_p_list:
        x_list.append(IterLeastSquares(z, h, r, init_x, init_p).x_list)

    # グラフ作成
    for i in range(3):
        fig, ax = plt.subplots()
        ax.set_xlabel('k')  # x軸ラベル
        ax.set_ylabel(f'x{i}')  # y軸ラベル
        ax.set_title('iter least squares')

        for j, x in enumerate(x_list):
            ax.plot(k, x[:, i], label=label_list[j])

        ax.legend(loc=0)
        plt.savefig(f'graph/iter_least_squares_graph_x{i}.png')

if __name__ == '__main__':
    main()


import numpy
from numba import autojit

@autojit(nopython=True)
def poisson1d_GS_SingleItr(nx, dx, p, b):
    '''
    Gauss-Seidel method for 1D Poisson eq. with Dirichlet BCs at both
    ends. Only a single iteration is executed. **blitz** is used.

    Parameters:
    ----------
    nx: int, number of grid points in x direction
    dx: float, grid spacing in x
    p: 1D array of float, approximated soln. in last iteration
    b: 1D array of float, 0th-order derivative term in Poisson eq.

    Returns:
    -------
    p: 1D array of float, approximated soln. in current iteration
    '''

    for i in range(1,len(p)-1):
        p[i] = 0.5 * (p[i+1] + p[i-1] - dx**2 * b[i])

    return p

def RMS(p):
    '''
    Return the root mean square of p.

    Parameters:
    ----------
    p:   array

    Returns:
    -------
    Root mean square of p
    '''
    return numpy.sqrt(numpy.sum(p**2) / p.size)

def residual(dx, pn, b, r):
    '''
    Calculate the residual for the 1D Poisson equation.

    Parameters:
    ----------
    pn: 1D array, approximated solution at a certain iteration n
    b:  1D array, the b(x) in the Poisson eq.

    Return:
    ----------
    The residual r
    '''

    # r[0] = 0
    r[1:-1] = b[1:-1] - (pn[:-2] - 2 * pn[1:-1] + pn[2:]) / dx**2
    # r[-1] = 0

    return r

def full_weighting_1d(vF, vC):
    '''
    Transfer a vector on a fine grid to a coarse grid with full weighting
    .  The number of elements (not points) of the coarse grid is
    half of that of the fine grid.

    Parameters:
    ----------
    vF: 1D numpy array, the vector on the fine grid
    vC: 1D numpy array, the vector on the coarse grid,
        size(vC) = (size(vF) + 1) / 2

    Output: vC
    '''

    vC[0] = vF[0]
    vC[1:-1] = 0.25 * (vF[1:-3:2] + 2. * vF[2:-2:2] + vF[3:-1:2])
    vC[-1] = vF[-1]

    return vC

def interpolation_1d(vC, vF):
    '''
    Transfer a vector on a coarse grid to a fine grid by linear
    interpolation. The number of elements (not points) of the coarse
    grid is a half of that of the fine grid.

    Parameters:
    ----------
    vC: 1D numpy array, the vector on the coarse grid,
    vF: 1D numpy array, the vector on the fine grid
        size(vF) = size(vC) * 2 - 1

    Output: vF
    '''

    vF[::2] = vC[:];
    vF[1:-1:2] = 0.5 * (vC[:-1] + vC[1:])

    return vF


# Week 1: Analysis of Algorithms
# Question 1

"""
(seed = 932025)
Suppose that you time a program as a function of N and produce
the following table.

        N   seconds
-------------------
    16384     0.000
    32768     0.000
    65536     0.001
   131072     0.004
   262144     0.009
   524288     0.025
  1048576     0.068
  2097152     0.181
  4194304     0.482
  8388608     1.287
 16777216     3.436
 33554432     9.171
 67108864    24.490
134217728    65.380
268435456   174.542
536870912   465.976

Estimate the order of growth of the running time as a function of N.
Assume that the running time obeys a power law T(N) ~ a N^b. For your
answer, enter the constant b. Your answer will be marked as correct
if it is within 1% of the target answer - we recommend using
two digits after the decimal separator, e.g., 2.34.
"""
from pylab import *

data = """    16384     0.000
    32768     0.000
    65536     0.001
   131072     0.004
   262144     0.009
   524288     0.025
  1048576     0.068
  2097152     0.181
  4194304     0.482
  8388608     1.287
 16777216     3.436
 33554432     9.171
 67108864    24.490
134217728    65.380
268435456   174.542
536870912   465.976"""

vals = data.split()

N = array(map(int, vals[::2]))
T = array(map(float, vals[1::2]))

from scipy.optimize import curve_fit

def func(x, a, b, c):
    return a * np.power(x, b) + c

popt, pcov = curve_fit(func, N, T)

a, b, c = popt

print "b =", b

plot(N, T, 'r.')
plot(N, a * N ** b + c )

show()


import numpy as np

def L1Loss(y_predicted, y_ground_truth, reduction="None"):
    """returns l1 loss between two arrays

    :param y_predicted: array of predicted values
    :type y_predicted: ndarray
    :param y_ground_truth: array of ground truth values
    :type y_ground_truth: ndarray
    :param reduction: reduction mode, defaults to "mean"
    :type reduction: str, optional
    :return: l1-loss
    :rtype: scalar if reduction is sum or mean, else ndarray
    """
    # Calculate the absolute difference array
    absolute_difference = np.abs(y_predicted - y_ground_truth)
    # L1 distance is the reduced form of the absolute difference array
    if reduction == "sum":
        # Reduction can be done by summing up all the values in the difference array (this is known as "L1-Loss")
        l1_distance = np.sum(absolute_difference)
        return l1_distance
    elif reduction == "mean":
        # Reduction can also be done by taking the mean (this is known as "Mean Absolute Error")
        mean_absolute_error = np.mean(absolute_difference)
        return mean_absolute_error
    elif reduction == "None":
        return absolute_difference
    else:
        print('ValueError: reduction should be "sum" / "mean" / "None"')

def main():
    print("Initializing predicted and ground truth arrays:\n")
    print('(NOTE: Enter the values in a space-separated format. Ex: "5.36 1.02 2.03")')
    y_predicted = [
        float(item) for item in input("Enter the predicted values: ").split()
    ]
    y_ground_truth = [
        float(item)
        for item in input("Enter the corresponding ground truth values: ").split()
    ]
    assert len(y_predicted) == len(
        y_ground_truth
    ), "Number of predicted values {} and ground truth {} values should match".format(
        len(y_predicted), len(y_ground_truth)
    )
    y_predicted = np.array(y_predicted)
    y_ground_truth = np.array(y_ground_truth)
    reduction = str(input('Enter the reduction mode: "sum" / "mean" / "None": '))
    loss = L1Loss(y_predicted, y_ground_truth, reduction=reduction)
    print("L1-Loss with {}-reduction: {}".format(reduction, loss))

if __name__ == "__main__":
    main()


"""
Module: libfmp.c1.c1s3_audio_rep
Author: Meinard Müller
License: The MIT license, https://opensource.org/licenses/MIT

This file is part of the FMP Notebooks (https://www.audiolabs-erlangen.de/FMP)
"""
import numpy as np
from matplotlib import pyplot as plt
import librosa
import IPython.display as ipd

def f_pitch(p):
    """Compute center frequency for (single or array of) MIDI note numbers

    Notebook: C1/C1S3_FrequencyPitch.ipynb

    Args:
        p (float or np.ndarray): MIDI note numbers

    Returns:
        freq_center (float or np.ndarray): Center frequency
    """
    freq_center = 2 ** ((p - 69) / 12) * 440
    return freq_center

def difference_cents(freq_1, freq_2):
    """Difference between two frequency values specified in cents

    Notebook: C1/C1S3_FrequencyPitch.ipynb

    Args:
        freq_1 (float): First frequency
        freq_2 (float): Second frequency

    Returns:
        delta (float): Difference in cents
    """
    delta = np.log2(freq_1 / freq_2) * 1200
    return delta

def generate_sinusoid(dur=5, Fs=1000, amp=1, freq=1, phase=0):
    """Generation of sinusoid

    Notebook: C1/C1S3_FrequencyPitch.ipynb

    Args:
        dur (float): Duration (in seconds) (Default value = 5)
        Fs (scalar): Sampling rate (Default value = 1000)
        amp (float): Amplitude of sinusoid (Default value = 1)
        freq (float): Frequency of sinusoid (Default value = 1)
        phase (float): Phase of sinusoid (Default value = 0)

    Returns:
        x (np.ndarray): Signal
        t (np.ndarray): Time axis (in seconds)

    """
    num_samples = int(Fs * dur)
    t = np.arange(num_samples) / Fs
    x = amp * np.sin(2*np.pi*(freq*t-phase))
    return x, t

def compute_power_db(x, Fs, win_len_sec=0.1, power_ref=10**(-12)):
    """Computation of the signal power in dB

    Notebook: C1/C1S3_Dynamics.ipynb

    Args:
        x (np.ndarray): Signal (waveform) to be analyzed
        Fs (scalar): Sampling rate
        win_len_sec (float): Length (seconds) of the window (Default value = 0.1)
        power_ref (float): Reference power level (0 dB) (Default value = 10**(-12))

    Returns:
        power_db (np.ndarray): Signal power in dB
    """
    win_len = round(win_len_sec * Fs)
    win = np.ones(win_len) / win_len
    power_db = 10 * np.log10(np.convolve(x**2, win, mode='same') / power_ref)
    return power_db

def compute_equal_loudness_contour(freq_min=30, freq_max=15000, num_points=100):
    """Computation of the equal loudness contour

    Notebook: C1/C1S3_Dynamics.ipynb

    Args:
        freq_min (float): Lowest frequency to be evaluated (Default value = 30)
        freq_max (float): Highest frequency to be evaluated (Default value = 15000)
        num_points (int): Number of evaluation points (Default value = 100)

    Returns:
        equal_loudness_contour (np.ndarray): Equal loudness contour (in dB)
        freq_range (np.ndarray): Evaluated frequency points
    """
    freq_range = np.logspace(np.log10(freq_min), np.log10(freq_max), num=num_points)
    freq = 1000
    # Function D from https://bar.wikipedia.org/wiki/Datei:Acoustic_weighting_curves.svg
    h_freq = ((1037918.48 - freq**2)**2 + 1080768.16 * freq**2) / ((9837328 - freq**2)**2 + 11723776 * freq**2)
    n_freq = (freq / (6.8966888496476 * 10**(-5))) * np.sqrt(h_freq / ((freq**2 + 79919.29) * (freq**2 + 1345600)))
    h_freq_range = ((1037918.48 - freq_range**2)**2 + 1080768.16 * freq_range**2) / ((9837328 - freq_range**2)**2
                                                                                + 11723776 * freq_range**2)
    n_freq_range = (freq_range / (6.8966888496476 * 10**(-5))) * np.sqrt(h_freq_range / ((freq_range**2 + 79919.29) *
                                                                         (freq_range**2 + 1345600)))
    equal_loudness_contour = 20 * np.log10(np.abs(n_freq / n_freq_range))
    return equal_loudness_contour, freq_range

def generate_chirp_exp(dur, freq_start, freq_end, Fs=22050):
    """Generation chirp with exponential frequency increase

    Notebook: C1/C1S3_Dynamics.ipynb

    Args:
        dur (float): Length (seconds) of the signal
        freq_start (float): Start frequency of the chirp
        freq_end (float): End frequency of the chirp
        Fs (scalar): Sampling rate (Default value = 22050)

    Returns:
        x (np.ndarray): Generated chirp signal
        t (np.ndarray): Time axis (in seconds)
        freq (np.ndarray): Instant frequency (in Hz)
    """
    N = int(dur * Fs)
    t = np.arange(N) / Fs
    freq = np.exp(np.linspace(np.log(freq_start), np.log(freq_end), N))
    phases = np.zeros(N)
    for n in range(1, N):
        phases[n] = phases[n-1] + 2 * np.pi * freq[n-1] / Fs
    x = np.sin(phases)
    return x, t, freq

def generate_chirp_exp_equal_loudness(dur, freq_start, freq_end, Fs=22050):
    """Generation chirp with exponential frequency increase and equal loudness

    Notebook: C1/C1S3_Dynamics.ipynb

    Args:
        dur (float): Length (seconds) of the signal
        freq_start (float): Starting frequency of the chirp
        freq_end (float): End frequency of the chirp
        Fs (scalar): Sampling rate (Default value = 22050)

    Returns:
        x (np.ndarray): Generated chirp signal
        t (np.ndarray): Time axis (in seconds)
        freq (np.ndarray): Instant frequency (in Hz)
        intensity (np.ndarray): Instant intensity of the signal
    """
    N = int(dur * Fs)
    t = np.arange(N) / Fs
    intensity, freq = compute_equal_loudness_contour(freq_min=freq_start, freq_max=freq_end, num_points=N)
    amp = 10**(intensity / 20)
    phases = np.zeros(N)
    for n in range(1, N):
        phases[n] = phases[n-1] + 2 * np.pi * freq[n-1] / Fs
    x = amp * np.sin(phases)
    return x, t, freq, intensity

def compute_adsr(len_A=10, len_D=10, len_S=60, len_R=10, height_A=1.0, height_S=0.5):
    """Computation of idealized ADSR model

    Notebook: C1/C1S3_Timbre.ipynb

    Args:
        len_A (int): Length (samples) of A phase (Default value = 10)
        len_D (int): Length (samples) of D phase (Default value = 10)
        len_S (int): Length (samples) of S phase (Default value = 60)
        len_R (int): Length (samples) of R phase (Default value = 10)
        height_A (float): Height of A phase (Default value = 1.0)
        height_S (float): Height of S phase (Default value = 0.5)

    Returns:
        curve_ADSR (np.ndarray): ADSR model
    """
    curve_A = np.arange(len_A) * height_A / len_A
    curve_D = height_A - np.arange(len_D) * (height_A - height_S) / len_D
    curve_S = np.ones(len_S) * height_S
    curve_R = height_S * (1 - np.arange(1, len_R + 1) / len_R)
    curve_ADSR = np.concatenate((curve_A, curve_D, curve_S, curve_R))
    return curve_ADSR

def compute_envelope(x, win_len_sec=0.01, Fs=4000):
    """Computation of a signal's envelopes

    Notebook: C1/C1S3_Timbre.ipynb

    Args:
        x (np.ndarray): Signal (waveform) to be analyzed
        win_len_sec (float): Length (seconds) of the window (Default value = 0.01)
        Fs (scalar): Sampling rate (Default value = 4000)

    Returns:
        env (np.ndarray): Magnitude envelope
        env_upper (np.ndarray): Upper envelope
        env_lower (np.ndarray): Lower envelope
    """
    win_len_half = round(win_len_sec * Fs * 0.5)
    N = x.shape[0]
    env = np.zeros(N)
    env_upper = np.zeros(N)
    env_lower = np.zeros(N)
    for i in range(N):
        i_start = max(0, i - win_len_half)
        i_end = min(N, i + win_len_half)
        env[i] = np.amax(np.abs(x)[i_start:i_end])
        env_upper[i] = np.amax(x[i_start:i_end])
        env_lower[i] = np.amin(x[i_start:i_end])
    return env, env_upper, env_lower

def compute_plot_envelope(x, win_len_sec, Fs, figsize=(6, 3), title=''):
    """Computation and subsequent plotting of a signal's envelope

    Notebook: C1/C1S3_Timbre.ipynb

    Args:
        x (np.ndarray): Signal (waveform) to be analyzed
        win_len_sec (float): Length (seconds) of the window
        Fs (scalar): Sampling rate
        figsize (tuple): Size of the figure (Default value = (6, 3))
        title (str): Title of the figure (Default value = '')

    Returns:
        fig (mpl.figure.Figure): Generated figure
    """
    t = np.arange(x.size)/Fs
    env, env_upper, env_lower = compute_envelope(x, win_len_sec=win_len_sec, Fs=Fs)
    fig = plt.figure(figsize=figsize)
    plt.plot(t, x, color='gray', label='Waveform')
    plt.plot(t, env_upper, linewidth=2, color='cyan', label='Upper envelope')
    plt.plot(t, env_lower, linewidth=2, color='blue', label='Lower envelope')
    plt.plot(t, env, linewidth=2, color='red', label='Magnitude envelope')
    plt.title(title)
    plt.xlabel('Time (seconds)')
    plt.ylabel('Amplitude')
    plt.xlim([t[0], t[-1]])
    plt.ylim([-0.7, 0.7])
    plt.legend(loc='lower right')
    plt.show()
    ipd.display(ipd.Audio(data=x, rate=Fs))
    return fig

def generate_sinusoid_vibrato(dur=5, Fs=1000, amp=0.5, freq=440, vib_amp=1, vib_rate=5):
    """Generation of a sinusoid signal with vibrato

    Notebook: C1/C1S3_Timbre.ipynb

    Args:
        dur (float): Duration (in seconds) (Default value = 5)
        Fs (scalar): Sampling rate (Default value = 1000)
        amp (float): Amplitude of sinusoid (Default value = 0.5)
        freq (float): Frequency (Hz) of sinusoid (Default value = 440)
        vib_amp (float): Amplitude (Hz) of the frequency oscillation (Default value = 1)
        vib_rate (float): Rate (Hz) of the frequency oscillation (Default value = 5)

    Returns:
        x (np.ndarray): Generated signal
        t (np.ndarray): Time axis (in seconds)

    """
    num_samples = int(Fs * dur)
    t = np.arange(num_samples) / Fs
    freq_vib = freq + vib_amp * np.sin(t * 2 * np.pi * vib_rate)
    phase_vib = np.zeros(num_samples)
    for i in range(1, num_samples):
        phase_vib[i] = phase_vib[i-1] + 2 * np.pi * freq_vib[i-1] / Fs
    x = amp * np.sin(phase_vib)
    return x, t

def generate_sinusoid_tremolo(dur=5, Fs=1000, amp=0.5, freq=440, trem_amp=0.1, trem_rate=5):
    """Generation of a sinusoid signal with tremolo

    Notebook: C1/C1S3_Timbre.ipynb

    Args:
        dur (float): Duration (in seconds) (Default value = 5)
        Fs (scalar): Sampling rate (Default value = 1000)
        amp (float): Amplitude of sinusoid (Default value = 0.5)
        freq (float): Frequency (Hz) of sinusoid (Default value = 440)
        trem_amp (float): Amplitude of the amplitude oscillation (Default value = 0.1)
        trem_rate (float): Rate (Hz) of the amplitude oscillation (Default value = 5)

    Returns:
        x (np.ndarray): Generated signal
        t (np.ndarray): Time axis (in seconds)
    """
    num_samples = int(Fs * dur)
    t = np.arange(num_samples) / Fs
    amps = amp + trem_amp * np.sin(t * 2 * np.pi * trem_rate)
    x = amps * np.sin(2*np.pi*(freq*t))
    return x, t

def generate_tone(p=60, weight_harmonic=np.ones([16, 1]), Fs=11025, dur=2):
    """Generation of a tone with harmonics

    Notebook: C1/C1S3_Timbre.ipynb

    Args:
        p (float): MIDI pitch of the tone (Default value = 60)
        weight_harmonic (np.ndarray): Weights for the different harmonics (Default value = np.ones([16, 1])
        Fs (scalar): Sampling frequency (Default value = 11025)
        dur (float): Duration (seconds) of the signal (Default value = 2)

    Returns:
        x (np.ndarray): Generated signal
        t (np.ndarray): Time axis (in seconds)
    """
    freq = 2 ** ((p - 69) / 12) * 440
    num_samples = int(Fs * dur)
    t = np.arange(num_samples) / Fs
    x = np.zeros(t.shape)
    for h, w in enumerate(weight_harmonic):
        x = x + w * np.sin(2 * np.pi * freq * (h + 1) * t)
    return x, t

def plot_spectrogram(x, Fs=11025, N=4096, H=2048, figsize=(4, 2)):
    """Computation and subsequent plotting of the spectrogram of a signal

    Notebook: C1/C1S3_Timbre.ipynb

    Args:
        x: Signal (waveform) to be analyzed
        Fs: Sampling rate (Default value = 11025)
        N: FFT length (Default value = 4096)
        H: Hopsize (Default value = 2048)
        figsize: Size of the figure (Default value = (4, 2))

    """
    N, H = 2048, 1024
    X = librosa.stft(x, n_fft=N, hop_length=H, win_length=N, window='hanning')
    Y = np.abs(X)
    plt.figure(figsize=figsize)
    librosa.display.specshow(librosa.amplitude_to_db(Y, ref=np.max),
                             y_axis='linear', x_axis='time', sr=Fs, hop_length=H, cmap='gray_r')
    plt.ylim([0, 3000])
    # plt.colorbar(format='%+2.0f dB')
    plt.xlabel('Time (seconds)')
    plt.ylabel('Frequency (Hz)')
    plt.tight_layout()
    plt.show()


'''
Created on Mar 12, 2021

@author: vladislavkargin

A collection of functions for Dyck paths.
A Dyck path is an array of +1 and -1, such that the total sum is zero and
the partial sums are always non-negative.
'''
import numpy as np
import matplotlib.pyplot as plt

def randomDyckPath(n, seed = None):
    '''
     * Generates a random Dyck path with 2n steps
     *
     * @param n half-size of path
     * @return a sequence of -1 and 1 steps.
    '''

    if seed != None:
        np.random.seed(seed)
    path = [1] * n + [-1] * (n + 1)
    #shuffle randomly
    np.random.shuffle(path)

    #do a cyclic shift.
    #Find the first occurrence of the minimum
    S = np.cumsum(np.array([0] + path))
    '''
    fig1, ax1 = plt.subplots(nrows=1, ncols=1)
    ax1.plot(S)
    ax1.grid(True)
    '''
    m = np.min(S)
    positions = np.where(S == m)[0]
    pos = positions[0]
    #print(pos)
    path1 = path[pos:] + path[:pos]
    del path1[-1]
    return path1

def plotDyckPath(path, ax = None, method = "upperLatticePath"):
    ''' plots a Dyck path. Path is either list of 1, -1 or numpy array
    of 1, - 1'''
    if isinstance(path, list):
        n = int(len(path)/2)
    else:
        n = int(path.shape[0]/2)
    X, Y = buildPlanePath(path, method = method)
    if ax == None:
        fig = plt.figure()
        ax = fig.add_subplot(1, 1, 1)
    major_ticks = np.arange(0, n + 1, 1)
    #minor_ticks = np.arange(0, 101, 5)
    ax.set_xticks(major_ticks)
    #ax.set_xticks(minor_ticks, minor=True)
    ax.set_yticks(major_ticks)
    #ax.set_yticks(minor_ticks, minor=True)
    # And a corresponding grid
    ax.grid(which='both')
    ax.plot(X, Y)
    Z = range(n + 1)
    ax.plot(Z)
    return ax

def areaDyckPath(path):
    ''' calculate the area of the given Dyck path, defined as the number
    of unit squares under the path but above the line y = x'''
    if isinstance(path, list):
        n = int(len(path)/2)
    else:
        n = int(path.shape[0]/2)
    x = 0
    y = 0
    S = [(x, y)]
    for i in range(2 * n):
        if path[i] == 1:
            y += 1
        else:
            x += 1
        S.append((x,y))
    area = 0
    y_prev = 0
    for p in S: #every time as y increases, we add y - x - 1 to the area
        if p[1] > y_prev:
            area += p[1] - p[0]- 1
            y_prev = p[1]
    return area

def valleys(path):
    '''counts the number of valleys of the given Dyck path,
    and the list of valleys '''
    if isinstance(path, list):
        n = len(path)//2
    else:
        n = path.shape[0]//2
    valleys = []
    for i in range(2 * n - 1):
        if path[i] == - 1 and path[i + 1] == 1:
            valleys.append(i)
    return valleys

def peaks(path):
    '''find peaks of the given Dyck path,
    and returns their list'''
    if isinstance(path, list):
        n = len(path)//2
    else:
        n = path.shape[0]//2
    peaks = []
    for i in range(2 * n - 1):
        if path[i] == 1 and path[i + 1] == -1:
            peaks.append(i)
    return peaks

def buildPlanePath(path, method = 'upperLatticePath'):
    ''' creates the path in the plane '''
    if isinstance(path, list):
        n = int(len(path)/2)
    else:
        n = int(path.shape[0]/2)
    x = 0
    y = 0
    S = [(x, y)]
    if method == "upperLatticePath":
        for i in range(2 * n):
            if path[i] == 1:
                y += 1
            else:
                x += 1
            S.append((x,y))
    else:
        for i in range(2 * n):
            if path[i] == 1:
                x += 1
            else:
                y += 1
            S.append((x,y))
    #print(S)
    X, Y = list(zip(*S))

    return X, Y

def pathTo231perm(path):
    '''converts a Dyck path to 231 avoiding permutation
    I am using the description of the bijection from Peterson's "Eulerian paths"
    '''
    #TODO
    #first build the reflected path
    X, Y = buildPlanePath(path, method = 'lowerLatticePath')
    n = (len(X) - 1)//2
    #One-by-one, for every column from right to left,
    #Find the lowest unoccupied row
    perm = [0] * n
    used_rows = []
    for x in reversed(range(1, n + 1)):
        y = Y[X.index(x)] # this is the bound from below that the path gives us
        while y in used_rows:
            y += 1
        used_rows.append(y)
        perm[x - 1] = y
    return perm

'''
For testing methods
'''
def main():

    n = 5
    seed = 3
    path = randomDyckPath(n, seed = seed)
    peaks = peaks(path)
    print(path)
    print(peaks)
    plotDyckPath(path, method = 'upperLatticePath')
    perm = pathTo231perm(path)
    print(perm)

    plt.show()

    pass

if __name__ == '__main__':
    main()


# -*- coding: utf-8 -*-
"""
Solution to Project Euler problem 587

https://projecteuler.net/problem=587

Author: Jaime Liew
https://github.com/jaimeliew1/Project_Euler_Solutions

First the point of intersection between the circle and the line is found (x0, y0)

Next, the area under the lower circle is found up to a point, say, x.

The area below the straight line is also found up to x.

The difference of these two areas is found to give the answer. x is chosen to be y0,
which essentially flips the problem on its side to give a single integral.

The integral was solved analytically.
"""

from numpy import pi, arcsin, sqrt

def AreaUnderCurve(N, x0):
    # calculates the area under the curve from 0 to x0, where x0 is the point
    # where the line intersects the first circle. Assumes radius of 1
    r = 1
    a = 1/N # slope of the straight line

    return r*x0 - r**2/2*(arcsin((x0-r)/r) + (x0-r)*sqrt(2*x0*r - x0**2)/r**2 + pi/2)

def AreaUnderLine(N, x0):
    # actually, this is the area to the left of the line
    a = 1/N
    return 1/(a*2)*x0**2

def IntersectionPoint(N):
    a = 1/N
    r = 1
    x0 = 2*r*(a+1) - sqrt(4*r**2*(a+1)**2 - 4*r**2*(1+a**2))
    x0 = x0/(2*(1+a**2))

    y0 = x0/N

    return x0, y0

def areaPerc(N):
    r = 1

    x0, y0 = IntersectionPoint(N)
    # by passing y0 instead of x0, the problem is turned on its side.
    Ac = AreaUnderCurve(N, y0)
    Al = AreaUnderLine(N, y0)

    Atotal = r**2 - pi*r**2/4

    return (Ac-Al)/Atotal

def run():
    N = 1
    while areaPerc(N) > 0.001:
        N += 1
    return N

if __name__ == "__main__":
    print(run())


''' Statistics helper-functions
'''
import numpy as np

def iqr(values, percentiles=[75, 25]):
    '''
    Compute the inter-quartile range. This is the (default) window in which half of the datapoints reside.
    The iqr is to the median, what the standard deviation is to the mean.

    Parameters
    ----------
    values : list
        data to compute the iqr for
    percentiles : list
        2-list with the top and bottom percentile

    Returns
    -------
    float
        the iqr value for the given data
    '''
    q3, q1 = np.percentile(values, percentiles)
    return q3 - q1

def rmse(values, cmp=0):
    '''
    Root Mean Square Error-like value for a given fixed mean
    (often 0 if we compare the value to a desired symmetry value of 0)

    Parameters
    ----------
    values : list
        data
    cmp : float
        expected outcome value in the sample

    Returns
    -------
    float
        the error value
    '''
    return np.sqrt(np.mean((cmp - values)**2))

def mae(values, cmp=0):
    '''
    Mean Absolute Error-like value for a given fixed mean
    (often 0 if we compare the value to a desired symmetry value of 0)

    Parameters
    ----------
    values : list
        data
    cmp : float
        expected outcome value in the sample

    Returns
    -------
    float
        the error value
    '''
    return np.mean(np.abs(cmp - values))

def autocorrelate(data, unbiased=True):
    '''
    Compute the autocorrelation for given data.

    Parameters
    ----------
    data : list
        The data to compute the autocorrelation for
    unbiased : bool
        Whether the biased or unbiased autocorrelation should be computed.
        The biased version tapers off with lag, while the unbiased version does not.

    Returns
    -------
    list
        List of autocorrelation values with 0 and positive lags
    '''
    import numpy as np

    autocorrelation = np.correlate(data, data, 'full')
    N = autocorrelation.size

    if (unbiased):
        autocorrelation /= (N - np.arange(N))

    # normalize and cut to lagged correlation coefficients
    autocorrelation_lags = autocorrelation[N // 2:] / autocorrelation[N // 2]

    return autocorrelation_lags


# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: percent
#       format_version: '1.3'
#       jupytext_version: 1.13.7
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# %% [markdown]
# # Erzeugen von NumPy Arrays

# %% pycharm={"name": "#%%\n"}
import numpy as np

# %% [markdown]
# Erzeugen Sie folgende NumPy Arrays:

# %% [markdown]
# ```python
# array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
# ```

# %% pycharm={"name": "#%%\n"}
np.arange(10)

# %% [markdown]
# ```python
# array([0.  , 1.25, 2.5 , 3.75, 5.  ])
# ```

# %% pycharm={"name": "#%%\n"}
np.linspace(0,5,5)

# %% [markdown]
# ```python
# array([ 1,  3, 12, 92])
# ```

# %% pycharm={"name": "#%%\n"}
np.array([1, 3, 12, 92])

# %% [markdown]
# ```python
# array([[1, 2, 3, 4],
#        [5, 6, 7, 8]])
# ```

# %% pycharm={"name": "#%%\n"}
np.array([[1, 2, 3, 4], [5, 6, 7, 8]])

# %%
np.array([np.arange(1, 5), np.arange(5, 9)])

# %%
np.arange(1, 9).reshape((2, 4))

# %% [markdown]
# ```python
# array([[[0, 2, 4, 6],
#         [1, 3, 5, 7]],
#
#        [[1, 2, 3, 4],
#         [5, 6, 7, 8]],
#
#        [[9, 8, 7, 6],
#         [5, 4, 3, 2]]])
# ```

# %% pycharm={"name": "#%%\n"}
np.array([[[0, 2, 4, 6], [1, 3, 5, 7]],
          [[1, 2, 3, 4], [5, 6, 7, 8]],
          [[9, 8, 7, 6], [5, 4, 3, 2]]])

# %%
np.array([[range(0, 7, 2), range(1, 8, 2)],
          [range(1, 5), range(5, 9)],
          [range(9, 5, -1), range(5, 1, -1)]])

# %% [markdown]
# Ein $2\times 8$ Array, das gleichverteilte Zufallszahlen in $[0, 1)$ enthält.

# %% pycharm={"name": "#%%\n"}
np.random.rand(2, 8)

# %% [markdown]
# ```python
# array([[1., 0., 0.],
#        [0., 1., 0.],
#        [0., 0., 1.]])
# ```

# %% pycharm={"name": "#%%\n"}
np.eye(3)

# %% [markdown]
# Einen Vektor der Länge 5, der standard-normalverteilte Zahlen enthält.

# %% pycharm={"name": "#%%\n"}
np.random.standard_normal(5)

# %% [markdown]
# Ein $3 \times 4$ Array, das normalverteilte Zahlen mit Mittelwert $5$ und Standardabweichung $0.5$ enthält.

# %% pycharm={"name": "#%%\n"}
np.random.normal(5, 0.5, (3, 4))

# %% [markdown] pycharm={"name": "#%%\n"}
# ## Gleichungssysteme
#
# Lösen Sie folgendes Gleichungssystem:
#
# $x_1 - x_2 + 2x_3 = 6$
#
# $2x_1 + 3x_2 + 2x_3 = 8$
#
# $3x_1 + 2x_2 + x_3 = 8$

# %%
a = np.array([[1., -1., 2.],
              [2.,  3., 2.],
              [3.,  2., 1.]])
b = np.array([6., 8., 8.])

# %%
import scipy.linalg
lu = scipy.linalg.lu_factor(a)

# %%
x = scipy.linalg.lu_solve(lu, b)

# %%
x

# %%
a.dot(x)

# %% [markdown]
# Einfacher:

# %%
np.linalg.solve(a, b)

# %% [markdown] pycharm={"name": "#%% md\n"}
# # Erzeugen von NumPy Arrays 2
#
# Erzeugen Sie das folgende NumPy Array:
#
# ```python
# array([[ 0,  2,  4,  6],
#        [ 8, 10, 12, 14],
#        [16, 18, 20, 22],
#        [24, 26, 28, 30],
#        [32, 34, 36, 38]])
# ```

# %% pycharm={"name": "#%%\n"}
np.arange(0, 40, 2).reshape(5, 4)

# %% [markdown] pycharm={"name": "#%% md\n"}
# Erzeugen Sie das folgende NumPy Array:
#
# ```python
# array([[10, 19, 28, 37, 46, 55],
#        [13, 22, 31, 40, 49, 58],
#        [16, 25, 34, 43, 52, 61]])
# ```

# %% pycharm={"name": "#%%\n"}
np.arange(10, 64, 3).reshape(3, 6, order="F")

# %%
np.arange(10, 64, 3).reshape((6, 3)).T

# %% [markdown]
# # Regression mit KNN
#
# Sampeln Sie 150 Werte der Funktion $cos(x) + 0.2x$ im Intervall $[0, 12]$, mit normalverteiltem Fehler mit `size`$=0.5$.  Führen Sie eine Regression mit dem KNN-Verfahren durch. Verwenden Sie dabei sowohl 'uniform' als auch 'distance' als `weight` und experimentieren Sie mit verschiedenen Werten von $k$. Plotten Sie die Regressionskurve und berechnen Sie den Mean Square Error.

# %% slideshow={"slide_type": "subslide"}
import numpy as np
import matplotlib.pyplot as plt
from sklearn.neighbors import KNeighborsRegressor
from sklearn.metrics import mean_squared_error

# %% slideshow={"slide_type": "-"}
np.random.seed(12)
NUM_SAMPLES = 50
MAX_X = 12
RANDOM_SCALE=0.5

# %% slideshow={"slide_type": "subslide"}
X = np.sort(MAX_X * np.random.random(NUM_SAMPLES))
X = X.reshape(-1, 1)
X[:3]

# %%
def fun(x):
    return np.cos(x) + 0.2 * x

# %% slideshow={"slide_type": "subslide"}
y = fun(X) + np.random.normal(size=X.shape, scale=RANDOM_SCALE)
y = y.reshape(-1)
y[:3]

# %%
n_neighbors = 5
knn = KNeighborsRegressor(n_neighbors, weights='uniform')
knn.fit(X, y)

# %% slideshow={"slide_type": "subslide"}
Xs = np.linspace(0, MAX_X, 500).reshape(-1, 1)
ys = knn.predict(Xs)
ys[:3]

# %% slideshow={"slide_type": "subslide"}
true_ys = fun(Xs)
true_ys[:3]

# %%
mean_squared_error(true_ys, ys)

# %% slideshow={"slide_type": "subslide"}
def plot_prediction(ys):
    plt.figure(figsize=(15,6))
    plt.scatter(X, y, color='orange', label='samples')
    plt.plot(Xs, ys, color='blue', label='predictions')
    plt.plot(Xs, true_ys, color='goldenrod', label='true_values')
    plt.legend()
plot_prediction(ys);

# %%
knn_dist = KNeighborsRegressor(n_neighbors, weights='distance')
knn_dist.fit(X, y)

# %%
Xs_dist = np.linspace(0, MAX_X, 500).reshape(-1, 1)
ys_dist = knn_dist.predict(Xs_dist)
ys_dist[:3]

# %%
mean_squared_error(true_ys, ys_dist)

# %%
plt.figure(figsize=(15,6))
plt.scatter(X, y, color='orange', label='samples')
plt.plot(Xs_dist, ys_dist, color='blue', label='predictions')
plt.legend()

# %% [markdown] slideshow={"slide_type": "slide"}
# # Bilderkennung mit KNNs
#
# Da der MNIST-Datensatz für viele moderne Algorithmen zu einfach ist, wurde der Fashion-MNIST entwickelt, der die gleiche Struktur wie MNIST hat (60000 Trainingsbilder und 10000 Testbilder der Größe $32 \times 32$ Pixel in Graustufen. Im Gegensatz zu MNIST sind bei Fashion-MNIST aber 10 Kategorien von Kleidungsstücken zu Klassifizieren.
#
# Fashion MNIST ist in Tensorflow als `tensorflow.keras.datasets.fashion_mnist` verfügbar.
#
# Verwenden Sie KNN um einen Klassifizierer für Fashion MNIST zu erstellen. Evaluieren Sie Accuracy, Precision und Recall. Wie sind die Werte im Vergleich zu MNIST?

# %% pycharm={"name": "#%%\n"}
import numpy as np
from matplotlib import pyplot as plt
from tensorflow.keras.datasets import fashion_mnist
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score, precision_score, recall_score

# %% pycharm={"name": "#%%\n"}
(X_train_in, y_train), (X_test_in, y_test) = fashion_mnist.load_data()

# %% pycharm={"name": "#%%\n"} slideshow={"slide_type": "subslide"}
def plot_random_datapoint():
    sample = np.random.randint(0, X_train_in.shape[0])

    plt.figure(figsize = (10,10))
    mnist_img = X_train_in[sample]
    plt.imshow(mnist_img, cmap="Greys")

    # Get the `Axes` instance on the current figure
    ax = plt.gca()

    plt.tick_params(
        axis='both', which='major', bottom=True, left=True,
        labelbottom=False, labelleft=False)

    plt.tick_params(
        axis='both', which='minor', bottom=False, left=False,
        labelbottom=True, labelleft=True)

    ax.set_xticks(np.arange(-.5, 28, 1))
    ax.set_yticks(np.arange(-.5, 28, 1))

    ax.set_xticks(np.arange(0, 28, 1), minor=True);
    ax.set_xticklabels([str(i) for i in np.arange(0, 28, 1)], minor=True);
    ax.set_yticks(np.arange(0, 28, 1), minor=True);
    ax.set_yticklabels([str(i) for i in np.arange(0, 28, 1)], minor=True);

    ax.grid(color='black', linestyle='-', linewidth=1.5)
    plt.colorbar(fraction=0.046, pad=0.04, ticks=[0,32,64,96,128,160,192,224,255])

# %% pycharm={"name": "#%%\n"} slideshow={"slide_type": "subslide"}
plot_random_datapoint()

# %% pycharm={"name": "#%%\n"}
def preprocess_data(data):
    return data.reshape(-1, 28 * 28)

# %% pycharm={"name": "#%%\n"} slideshow={"slide_type": "subslide"}
X_train = preprocess_data(X_train_in)
X_test = preprocess_data(X_test_in)

# %% pycharm={"name": "#%%\n"} slideshow={"slide_type": "subslide"}
def shuffle(X, y):
    Xs = np.column_stack((X, y))
    np.random.shuffle(Xs)
    return Xs[:, :-1], Xs[:, -1]

# %%
X_train, y_train = shuffle(X_train, y_train)
X_test, y_test = shuffle(X_test, y_test)

# %%
X_train, y_train = X_train[:5000], y_train[:5000]
X_test, y_test = X_test[:1000], y_test[:1000]

# %% pycharm={"name": "#%%\n"}
X_train.shape, X_test.shape, y_train.shape, y_test.shape

# %%
n_neighbors = 5
knn = KNeighborsClassifier(n_neighbors)
knn.fit(X_train, y_train)

# %%
y_pred = knn.predict(X_test)
y_pred[:10], y_test[:10]

# %%
(accuracy_score(y_test, y_pred),
 precision_score(y_test, y_pred, average='macro'),
 recall_score(y_test, y_pred, average='macro'))

# %% [markdown]
# # Bildverarbeitung mit Ensembles und Entscheidungsbäumen
#
# Verwenden Sie verschiedene Ensemble-Techniken und Endscheidungsbaum-Varianten um das im letzten Workshop eingeführte Bilderkennungsproblem zu lösen.

# %%

# %% [markdown] pycharm={"name": "#%% md\n"}
# # Extrema
#
# Erzeugen Sie einen Vektor der Länge 100 mit Zufallswerten, die im Intervall
# $[10, 20)$ gleichverteilt sind.
#
# Berechnen Sie Minimum und Maximum der im Vektor
# enthaltenen Werte sowie die Positionen von Minimum und Maximum.

# %% pycharm={"name": "#%%\n"}
vec = np.random.random(100) * 10 + 10
vec[:10]

# %% pycharm={"name": "#%%\n"}
vec.min()

# %% pycharm={"name": "#%%\n"}
vec.argmin()

# %% pycharm={"name": "#%%\n"}
vec.max()

# %% pycharm={"name": "#%%\n"}
vec.argmax()

# %% [markdown] pycharm={"name": "#%% md\n"}
# # Mittelwert
#
# Erzeugen Sie ein $6 \times 8$ Array, das standardverteilten Zahlen mit
# Mittelwert $2$ und Standardabweichung $1$ enthält.
#
#

# %% pycharm={"name": "#%%\n"}
my_array = np.random.normal(2.0, 1.0, (6, 8))

# %% [markdown] pycharm={"name": "#%% md\n"}
# Berechnen Sie den Mittelwert aller darin vorkommenden Werte.
#

# %% pycharm={"name": "#%%\n"}
my_array.mean()

# %% [markdown] pycharm={"name": "#%% md\n"}
# Berechnen Sie die zeilen- und spaltenweisen Mittelwerte.

# %% pycharm={"name": "#%%\n"}
my_array.mean(axis=0)

# %% pycharm={"name": "#%%\n"}
my_array.mean(axis=1)

# %% [markdown] pycharm={"name": "#%% md\n"}
# Berechnen Sie den Mittelwert aller vorkommenden Werte ohne Verwendung der
# Methode `mean()`.

# %% pycharm={"name": "#%%\n"}
mean = my_array.sum() / my_array.size
mean

# %% [markdown] pycharm={"name": "#%% md\n"}
# Berechnen Sie die zeilen- und spaltenweisen Mittelwerte ohne Verwendung der
# Methode `mean()`.

# %% pycharm={"name": "#%%\n"}
my_array.sum(axis=0) / my_array.shape[0]

# %% pycharm={"name": "#%%\n"}
my_array.sum(axis=1) / my_array.shape[1]

# %% [markdown]
# # Roulette
#
# Analysieren Sie die Gewinnerwartung eines Spielers in folgender vereinfachter Form eines Roulettespiels mittels Monte Carlo Simulation:
#
# - Der Kessel ist in 36 Zahlen unterteilt.
# - Der Spieler wählt eine der Zahlen 1 bis 36 und wettet 1 Euro.
# - Fällt die Kugel auf die gewählte Zahl, so erhält der Spieler seinen Einsatz plus 35 Euro zurück.
# - Andernfalls verliert der Spieler seinen Einsatz.
#
# Schreiben Sie eine Version der Simulation mit `for`-Schleife in Python und testen Sie die Performance dieser Version vor und nach Kompilierung mit Numba. Schreiben Sie dann eine vektorisierte Version und testen Sie deren Performance in beiden Fällen.
#
# *Hinweise:*
# - Die NumPy Bibliothek enthält eine Funktion `np.random.randint(low, high, size=None)`, mit der Sie ein Array mit Shape `size` erzeugen können, das gleichverteilte Zufallszahlen zwischen `low` (inklusive) und `high` (exklusive) enthält.
# - Wird `np.random.randint()` mit nur zwei Argumenten aufgerufen, so gibt es eine einzige Zahl zurück.
# - Die NumPy Bibliothek enthält eine Funtion `np.random.binomial(n, p, size=None)`, mit der Sie binomialverteilte Zufallszahlen erzeugen können.

# %%
import numpy as np

# %%
def roulette1(n):
    # Wir können davon ausgehen, dass der Spieler immer auf die 1 wettet
    money_spent = 0
    money_won = 0
    for i in range(n):
        money_spent += 1
        if np.random.randint(1, 37) == 1:
            money_won += 36
    return (money_won - money_spent) / n

# %%
def test_roulette(roulette):
    np.random.seed(123)
    for n in [1000, 100_000, 1_000_000]:
        # %time print(f"Gewinnerwartung ist {100 * roulette(n):.1f}% ({n} Versuche)")

# %%
test_roulette(roulette1)

# %%
import numba
roulette1_nb = numba.jit(roulette1)

# %%
test_roulette(roulette1_nb)

# %%
def roulette2(n):
    money_spent = np.ones(n)
    money_won = np.random.binomial(1, 1.0/36.0, n) * 36
    return (money_won - money_spent).sum() / n

# %%
test_roulette(roulette2)

# %%
roulette2_nb = numba.jit(roulette2)

# %%
test_roulette(roulette2_nb)

# %%
def roulette3(n):
    money_spent = n
    money_won = np.random.binomial(n, 1.0/36.0) * 36
    return (money_won - money_spent) / n

# %%
test_roulette(roulette3)

# %%
roulette3_nb = numba.jit(roulette3)

# %%
test_roulette(roulette3_nb)

# %%
roulette3(100_000_000)

# %%


import numpy as np
from numpy import cos, sin, pi

Ho = 5000  # ocean depth in meters
nx = 62    # number of gridpoints in x-direction
ny = 62    # number of gridpoints in y-direction
xo = 0     # origin in x,y for ocean domain
yo = 0     # (i.e. southwestern corner of ocean domain)
dx = 20    # grid spacing in x (km)
dy = 20    # grid spacing in y (km)
xeast  = xo + (nx-2)*dx   # eastern extent of ocean domain
ynorth = yo + (ny-2)*dy   # northern extent of ocean domain

# Flat bottom at z=-Ho
h = -Ho * np.ones((ny, nx))

# Walls (surrounding domain); generate bathymetry file
h[:, [0,-1]] = 0   # set ocean depth to zero at east and west walls
h[[0,-1], :] = 0   # set ocean depth to zero at south and north walls
# save as single-precision (float32) with big-endian byte ordering
h.astype('>f4').tofile('bathy.bin')

# Ocean domain extends from (xo,yo) to (xeast,ynorth)
# (i.e. the ocean spans nx-2, ny-2 grid cells)
# out-of-box-config: xo=yo=0, dx=dy=20 km, ocean extent (0,0)-(1200,1200) km
# model domain includes a land cell surrounding the ocean domain
# The full model domain cell centers are located at:
#    XC[0,:] = -10, +10, ..., +1210 (km)
#    YC[:,0] = -10, +10, ..., +1210 (km)
# and full model domain cell corners are located at:
#    XG[0,:] = -20, 0, ..., 1200 [, 1220] (km)
#    YG[:,0] = -20, 0, ..., 1200 [, 1220] (km)
# where the last value in brackets is not included in the MITgcm grid variable
# and reflects the eastern and northern edge of the model domain respectively.
# See section 2.11.4 of the MITgcm users manual.

# Zonal wind-stress, located at u-points (see section 2.11.4)
# here we non-dimensionalize: 0 at southern and western ocean boundary
# to 1.0 at eastern and northern ocean boundary
# for the purpose of applying sinusoidal-shaped wind stress curve
tauMax = 0.1  # wind stress maximum
x = (np.arange(nx)-1) / (nx-2)   # x-coordinate, located at XG points
y = (np.arange(ny)-.5) / (ny-2)  # y-coordinate, located at YC points
Y, X = np.meshgrid(y, x, indexing='ij')

tau = -tauMax * cos(Y*pi) # generate file for -cos(y) profile between 0-1200km
tau.astype('>f4').tofile('windx_cosy.bin')
tau = tauMax * sin(Y*pi) # generate file for +sin(y) profile between 0-1200km
tau.astype('>f4').tofile('windx_siny.bin')

# Meridional wind-stress, if desired, would be located at v-points (XC, YG)


import argparse
import math
import random
import numpy as np
from scipy.stats import norm
import seaborn as sns
import matplotlib.pyplot as plt
from .mh import MH

def main(args):

    std = math.sqrt(args.var)

    def p_eval(x):

        return 0.3 * math.exp(- 0.2 * x ** 2) + 0.7 * math.exp(- 0.2 * (x - 10) ** 2)

    def p_eval_real(x):

        return 0.3 * (1 / math.sqrt(math.pi * (1 / 0.2))) * math.exp(- 0.2 * x ** 2) + \
               0.7 * (1 / math.sqrt(math.pi * (1 / 0.2))) * math.exp(- 0.2 * (x - 10) ** 2)

    def q_eval(x1, x2):

        return norm.pdf(x1, loc=x2, scale=std)

    def q_sample(x):

        return random.gauss(x, std)

    # run metropolis-hastings
    mh = MH(p_eval, q_eval, q_sample, 0)

    samples = []

    for i in range(args.iters):
        samples.append(mh.step())

    # plot real distribution
    plt.title("{:d} iterations, {:.1f} variance".format(args.iters, args.var))
    colors = sns.color_palette()

    x = np.linspace(-10, 20, num=1000)
    y = [p_eval_real(xx) for xx in x]
    plt.plot(x, y, c=colors[0])

    # plot samples
    sns.distplot(samples, bins=30, hist=True, norm_hist=True, kde=False, color=colors[0])

    if args.save is not None:
        plt.savefig(args.save, figsize=(4, 3), dpi=60)

    if not args.no_show:
        plt.show()

if __name__ == "__main__":

    parser = argparse.ArgumentParser()

    parser.add_argument("--iters", type=int, default=5000)
    parser.add_argument("--var", type=float, default=100)
    parser.add_argument("--save")
    parser.add_argument("--no-show", default=False, action="store_true")

    parsed = parser.parse_args()
    main(parsed)


# solutions.py
"""Volume 2A: Wavelets. Solutions file."""

import numpy as np
from scipy.misc import imread
from scipy.signal import fftconvolve
from matplotlib import pyplot as plt
import pywt

# Problem 1
def dwt(X, L, H, n):
    """Compute the Discrete Wavelet Transform of X using filters L and H.

    Parameters:
        X (1D ndarray): The signal to be processed.
        L (1D ndarray): The low-pass filter.
        H (1D ndarray): The high-pass filter.
        n (int > 0):    Controls the degree of transformation.

    Returns:
        a list of the wavelet decomposition arrays.
    """
    i = 0                                    #Some initialization steps
    A = X
    D = []
    while i < n:
        D.append(fftconvolve(A, H)[1::2])    #High-pass filtering
        A = (fftconvolve(A, L)[1::2])        #Low-pass filtering
        i += 1
    D.append(A)
    D = D[::-1]
    return D

# Problem 1
def plot(X, L, H, n):
    """Plot the results of dwt with the given inputs.
    Your plot should be very similar to Figure 2.

    Parameters:
        X (1D ndarray): The signal to be processed.
        L (1D ndarray): The low-pass filter.
        H (1D ndarray): The high-pass filter.
        n (int > 0): Controls the degree of transformation.
    """

    coeffs = dwt(X, L, H, n)
    plt.subplot(n+2,1,1)
    plt.plot(X)
    for i in xrange(len(coeffs)):
        plt.subplot(n+2,1,i+2)
        plt.plot(coeffs[i])
    plt.show()

# Problem 2
def idwt(coeffs, L, H):
    """ Compute the Inverse Wavelet Transform using thw wavelet coefficients
    and filters L and H.

    Parameters:
        coeffs (list): a list of wavelet decomposition arrays.
        L (1D ndarray): The low-pass filter.
        H (1D ndarray): The high-pass filter.
    Returns:
        The reconstructed signal (as a 1D ndarray).
    """
    n = len(coeffs) - 1
    A = coeffs[0]
    coeffs = coeffs[1:]
    for i in xrange(n):
        D = coeffs[i]
        print len(D)
        up_A = np.zeros(2*A.size)
        up_A[::2] = A
        up_D = np.zeros(2*D.size)
        up_D[::2] = D
        print len(up_A),len(L),len(up_D),len(H)
        # now convolve and add, but discard last entry
        A = fftconvolve(up_A,L)[:-1] + fftconvolve(up_D,H)[:-1]
    return A

# Problem 3
def subbands(filename):
    """Plot the wavelet subbands for the image contained in filename.

    Parameters:
        filename (str): The name of the image file to be analyzed.
    Plots:
        Four subplots, of the wavelet subbands of the image.
    """
    image = image = imread(filename,True)
    # use the db4 wavelet with periodic extension
    lw = pywt.dwt2(image, 'db4', mode='per')
    plt.subplot(221)
    plt.imshow(np.abs(lw[0]), cmap=plt.cm.Greys_r, interpolation='none')
    plt.subplot(222)
    plt.imshow(np.abs(lw[1][0]), cmap=plt.cm.Greys_r, interpolation='none')
    plt.subplot(223)
    plt.imshow(np.abs(lw[1][1]), cmap=plt.cm.Greys_r, interpolation='none')
    plt.subplot(224)
    plt.imshow(np.abs(lw[1][2]), cmap=plt.cm.Greys_r, interpolation='none')
    plt.show()

# Problem 4
def clean_image(filename, show=False):
    """Clean the high-frequency noise out of the grayscale image contained
    in filename using the PyWavelets implementation of the 2-D Wavelet
    Transform.

    Parameters:
        filename (str): The name of the image file to be analyzed.

    Returns:
        The cleaned image (as an ndarray).
    """
    image = imread(filename,True)
    wavelet = pywt.Wavelet('haar')
    WaveletCoeffs = pywt.wavedec2(image,wavelet)
    new_image = pywt.waverec2(WaveletCoeffs[:-1], wavelet)

    if show:
        plt.subplot(121)
        plt.imshow(image, cmap='gray')  # visualize noisy image
        plt.subplot(122)
        plt.imshow(new_image, cmap='gray')  # visualize clean image
        plt.show()
    return new_image

#################### TEST DRIVER PREPARATION #########################

def test_prob1():
    """Tests Problem 1 as suggested in the lab."""
    L = np.ones(2)/np.sqrt(2)
    H = np.array([-1,1])/np.sqrt(2)
    n = 4
    domain = np.linspace(0,4*np.pi, 1024)
    noise = np.random.randn(1024)*.1
    X = np.sin(domain) + noise
    plot(X, L, H, n)

def test_prob2():
    """Tests Problem 2."""
    L = np.ones(2)/np.sqrt(2)
    H = np.array([-1,1])/np.sqrt(2)
    n = 4

    domain = np.linspace(0,4*np.pi, 1024)
    noise = np.random.randn(1024)*.1
    X = np.sin(domain) + noise
    coeffs = dwt(X, L, H, n)

    Ln = [1/np.sqrt(2),1/np.sqrt(2)]
    Hn = [1/np.sqrt(2),-1/np.sqrt(2)]
    A = idwt(coeffs, Ln, Hn)
    plt.plot(A)
    return np.allclose(X, A)


import numpy as np

def binaryStep(x):
    if x<0:
        return 0
    else:
        return 1

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

def sigmoid_p(x):                      # sigmoid prime
    return sigmoid(x)*(1-sigmoid(x))

def relu(x):                           # rectified linear unit
    if x>0:
        return x
    else:
        return 0

def softmax(x):
    z = np.exp(x)
    z_ = z/z.sum()
    return z_

def tanh(x):
    return (2/(1 + np.exp(-2*x))) -1

def elu(x, a):                          # exponential linear unit
    if x<0:
        return a*(np.exp(x)-1)
    else:
        return x


# -*- coding: utf-8 -*-
"""
Catalog of simplicial complexes

There are two main types: manifolds and examples related to graph
theory.

For manifolds, there are functions defining the `n`-sphere for any
`n`, the torus, `n`-dimensional real projective space for any `n`, the
complex projective plane, surfaces of arbitrary genus, and some other
manifolds, all as simplicial complexes.

Aside from surfaces, this file also provides functions for
constructing some other simplicial complexes: the simplicial complex
of not-`i`-connected graphs on `n` vertices, the matching complex on n
vertices, the chessboard complex for an `n` by `i` chessboard, and
others.  These provide examples of large simplicial complexes; for
example, ``simplicial_complexes.NotIConnectedGraphs(7,2)`` has over a
million simplices.

All of these examples are accessible by typing
``simplicial_complexes.NAME``, where ``NAME`` is the name of the example.

- :meth:`~sage.homology.examples.BarnetteSphere`
- :meth:`~sage.homology.examples.BrucknerGrunbaumSphere`
- :meth:`~sage.homology.examples.ChessboardComplex`
- :meth:`~sage.homology.examples.ComplexProjectivePlane`
- :meth:`~sage.homology.examples.DunceHat`
- :meth:`~sage.homology.examples.FareyMap`
- :meth:`~sage.homology.examples.K3Surface`
- :meth:`~sage.homology.examples.KleinBottle`
- :meth:`~sage.homology.examples.MatchingComplex`
- :meth:`~sage.homology.examples.MooreSpace`
- :meth:`~sage.homology.examples.NotIConnectedGraphs`
- :meth:`~sage.homology.examples.PoincareHomologyThreeSphere`
- :meth:`~sage.homology.examples.PseudoQuaternionicProjectivePlane`
- :meth:`~sage.homology.examples.RandomComplex`
- :meth:`~sage.homology.examples.RandomTwoSphere`
- :meth:`~sage.homology.examples.RealProjectivePlane`
- :meth:`~sage.homology.examples.RealProjectiveSpace`
- :meth:`~sage.homology.examples.RudinBall`
- :meth:`~sage.homology.examples.ShiftedComplex`
- :meth:`~sage.homology.examples.Simplex`
- :meth:`~sage.homology.examples.Sphere`
- :meth:`~sage.homology.examples.SumComplex`
- :meth:`~sage.homology.examples.SurfaceOfGenus`
- :meth:`~sage.homology.examples.Torus`
- :meth:`~sage.homology.examples.ZieglerBall`

You can also get a list by typing ``simplicial_complexes.`` and hitting the
TAB key.

EXAMPLES::

    sage: S = simplicial_complexes.Sphere(2) # the 2-sphere
    sage: S.homology()
    {0: 0, 1: 0, 2: Z}
    sage: simplicial_complexes.SurfaceOfGenus(3)
    Triangulation of an orientable surface of genus 3
    sage: M4 = simplicial_complexes.MooreSpace(4)
    sage: M4.homology()
    {0: 0, 1: C4, 2: 0}
    sage: simplicial_complexes.MatchingComplex(6).homology()
    {0: 0, 1: Z^16, 2: 0}
"""

from sage.homology.examples import (Sphere, Simplex, Torus, ProjectivePlane,
        RealProjectivePlane, KleinBottle, FareyMap, SurfaceOfGenus,
        MooreSpace,
        ComplexProjectivePlane, PseudoQuaternionicProjectivePlane,
        PoincareHomologyThreeSphere, RealProjectiveSpace, K3Surface,
        BarnetteSphere, BrucknerGrunbaumSphere, NotIConnectedGraphs,
        MatchingComplex, ChessboardComplex, RandomComplex, SumComplex,
        RandomTwoSphere, ShiftedComplex, RudinBall, ZieglerBall, DunceHat)


__all__ = ["shapeRectangleByCenter"]

import numpy as np
from LevelSetPy.Utilities.matlab_utils import *
from .rect_corners import shapeRectangleByCorners

def shapeRectangleByCenter(grid, center=None, widths=None):
    """
     shapeRectangleByCenter: implicit surface function for a (hyper)rectangle.

       data = shapeRectangleByCenter(grid, center, widths)

     Creates an implicit surface function (close to signed distance) for a
     coordinate axis aligned (hyper)rectangle specified by its center and
     widths in each dimension.

     Can be used to create intervals and slabs by choosing components of the
     widths as +Inf.

     The default parameters for shapeRectangleByCenter and
     shapeRectangleByCorners produce different rectangles.

     Inp.t Parameters:

       grid: Grid structure (see processGrid.m for details).

       center: Vector specifying center of rectangle.  May be a scalar, in
       which case the scalar is multiplied by a vector of ones of the
       appropriate length.  Defaults to 0 (eg centered at the origin).

       widths: Vector specifying (full) widths of each side of the rectangle.
       May be a scalar, in which case all dimensions have the same width.
       Defaults to 1.

     Output Parameters:

       data: Output data array (of size grid.size) containing the implicit
       surface function.

     Copyright 2004 Ian M. Mitchell (mitchell@cs.ubc.ca).
     This software is used, copied and distributed under the licensing
       agreement contained in the file LICENSE in the top directory of
       the distribution.

     Ian Mitchell, 6/23/04
     $Date: 2009-09-03 16:34:07 -0700 (Thu, 03 Sep 2009) $
     $Id: shapeRectangleByCenter.m 44 2009-09-03 23:34:07Z mitchell $

     Lekan Molu, Sept 07, 2021
    """
    #Default parameter values.
    if not np.any(center):
        center = zeros(grid.dim, 1)
    elif(numel(center) == 1):
        center *= ones(grid.dim, 1)

    if not np.any(widths):
        widths = ones(grid.dim, 1);
    elif numel(widths) == 1:
        widths *= ones(grid.dim, 1)

    #---------------------------------------------------------------------------
    # Implicit surface function calculation.
    #   This is basically the intersection (by max operator) of halfspaces.
    #   While each halfspace is generated by a signed distance function,
    #   the resulting intersection is not quite a signed distance function.

    # For the computation, we really want the lower and upper corners.
    lower = zeros(grid.dim, 1);
    upper = zeros(grid.dim, 1);
    for i in range(grid.dim):
        lower[i] = center[i] - 0.5 * widths[i]
        upper[i] = center[i] + 0.5 * widths[i]

    data = shapeRectangleByCorners(grid, lower, upper);

    #---------------------------------------------------------------------------
    # Warn the user if there is no sign change on the grid
    #  (ie there will be no implicit surface to visualize).
    if(np.all(data.flatten() < 0) or (np.all(data.flatten() > 0))):
        logger.warn(f'Implicit surface not visible because function has '
                'single sign on grid')

    return data


#!/usr/bin/env python
import numpy as np
import time
import argparse
from scipy.linalg import cho_factor, cho_solve

parser = argparse.ArgumentParser(prog="least_sqaures_cpu.py", description="Demo of fitting a line with least squares.")
parser.add_argument("--Nsamples", type=int, default=5000)
args = parser.parse_args()

np.random.seed(123456)

#First, build the "true" dataset with N=50 datapoints from a line model y=mx+b.
true_m, true_b = 0.5, -0.25
N = args.Nsamples
x = np.linspace(-5, 5, N)
y = true_m * x + true_b

#Introduce some noise with both measurement uncertainties
#   and non-trivial correlated errors.
yerr = 0.1 + 0.4 * np.random.rand(N)
yerr_hom = 0.4*np.ones(N)
hom_cov = np.diag(yerr_hom ** 2)
iid_cov = np.diag(yerr ** 2)
true_cov = 0.5 * np.exp(-0.5 * (x[:, None]-x[None, :])**2 / 1.3**2) + np.diag(yerr ** 2)
y_noised = np.random.multivariate_normal(y, true_cov)
#np.save('y_fake.npy', y)
#y = np.load('y_fake.npy')

#Linear algebra
A = np.vander(x, 2)
AT= A.T
C = iid_cov

t0 = time.time()
factor = cho_factor(C, overwrite_a=True)
S_inv = np.dot(AT, cho_solve(factor, A))
S = np.linalg.inv(S_inv)
ls_m, ls_b = np.dot(S, np.dot(AT, cho_solve(factor, y_noised)))
t1 = time.time()

net_time = t1-t0
print(" m: {:.2f} \n b: {:.2f} \n time: {}".format(ls_m, ls_b, net_time))


from numpy import exp,sqrt

def simpson(f,a,b,n):
    h = (b-a)/n
    simp = 0.0
    for i in range(1,n):
        x = a + i*h
        if (i%2 == 1):
            simp = simp + 4.0*f(x)
        else:
            simp = simp + 2.0*f(x)
    simp = h*(f(a) + simp + f(b))/3.0
    return simp

def f(x):
    return x**2*exp(-x)/sqrt(1.0 + x**2)

a = -1.0
b = 1.0
n = 128
T = simpson(f,a,b,n)
print('T = %8.4f' % T)


import numpy as np
import sympy as s

def f_a(a, b, x):
    return a * np.sin(2 * x) + b * np.cos(2 * x) - (x / 4.0) * np.cos(2 * x)

def f_da(a, b):
    x = s.Symbol('x')
    f = a * s.sin(2 * x) + b * s.cos(2 * x) - (x / 4.0) * s.cos(2 * x)

    f_prime = f.diff(x)
    print(f_prime)

    l = s.lambdify(x, f_prime)
    return l

def f_b(a, b, x):
    return a * np.sin(2 * x) + b * np.cos(2 * x) - (x / 4.0) * np.sin(2 * x)

def f_db(a, b):
    x = s.Symbol('x')
    f = a * s.sin(2 * x) + b * s.cos(2 * x) - (x / 4.0) * s.sin(2 * x)

    f_prime = f.diff(x)
    print(f_prime)

    l = s.lambdify(x, f_prime)
    return l

def f_c(a, b, x):
    return a * np.sin(2 * x) + b * np.cos(2 * x)

def f_dc(a, b):
    x = s.Symbol('x')
    f = a * s.sin(2 * x) + b * s.cos(2 * x)

    f_prime = f.diff(x)
    print(f_prime)

    l = s.lambdify(x, f_prime)
    return l

def f_d(a, b, x):
    return (a + b * x) * np.e ** (-2 * x) - (x / 4.0) * np.cos(2 * x)

def f_dd(a, b):
    x = s.Symbol('x')
    f = (a + b * x) * s.exp(-2 * x) - (x / 4.0) * s.cos(2 * x)

    f_prime = f.diff(x)
    print(f_prime)

    l = s.lambdify(x, f_prime)
    return l

# print(f_a(2, 2, 0))
# # 1
# print(f_b(2, 2, 0))
# # 1
# print(f_c(2, 2, 0))
# # 1
# print(f_d(2, 2, 0))
# 1

print(f_da(1, 1)(0))
print(f_db(1, 1)(0))
print(f_dc(1, 1)(0))
print(f_dd(1, 1)(0))


#solving the heat equation using finite difference method
import numpy as np
from tqdm.notebook import tqdm_notebook
import shutil
import os

def heatBC(a,b,c,d,T,dx,dy):

    # 'N' refers to Neumann boundary condition where the heat flux is specified on the boundary
    # 'D' refers to Dirichlet boundary condition where the temperature is specified on the boundary

    #left
    if a[0]=='N':
        T[:, 0]=a[1]*dx+T[:, 1]
    elif a[0]=='D':
        T[:,0]=a[1]
    else:
        return('Please enter a valid BC type')

    if b[0]=='N':
        T[:, -1]=b[1]*dx+T[:, -2]
    elif b[0]=='D':
        T[:, -1]=b[1]
    else:
        return('Please enter a valid BC type')

    #top
    if c[0]=='N':
        T[-1,:]=c[1]*dy+T[-2, :]
    elif c[0]=='D':
        T[-1,:]=c[1]
    else:
        return('Please enter a valid BC type')

    #bottom
    if d[0]=='N':
        T[0,:]=d[1]*dy+T[1, :]
    elif d[0]=='D':
        T[0,:]=d[1]
    else:
        return('Please enter a valid BC type')

def heat_equation(T,dx,dy,alpha,dt,ds,nt,TBCs):
    Tn = np.empty_like(T)
    i=0
    for n in tqdm_notebook(range(nt)):
        Tn = T.copy()
        i=i+1
        T[1:-1,1:-1]=Tn[1:-1,1:-1]+(alpha*dt/dx**2)*(Tn[2:,1:-1]-2*Tn[1:-1,1:-1]+Tn[0:-2,1:-1])+(alpha*dt/dy**2)*(Tn[1:-1,2:]- 2 * Tn[1:-1, 1:-1] + Tn[1:-1, 0:-2])

        if ((n+1)%ds==0):
            np.savetxt('T'+str(i)+'.csv', T, delimiter=',')

        TLeft=TBCs[0]
        TRight=TBCs[1]
        TTop=TBCs[2]
        TBottom=TBCs[3]

        heatBC(TLeft,TRight,TTop,TBottom,T,dx,dy)
    return T

def clearResults():
    fileList=os.listdir('./')

    if os.path.isdir('./Results'):
        shutil.rmtree('./Results')
        os.mkdir('Results')
    else:
        os.mkdir('Results')

    for file in fileList:
        if file.endswith('.csv'):
            shutil.move(file,'Results/.')

"""def heatBC(a,b,c,d,T,dx,dy):
    #left
    if a[0]=='N':
        T[0,:]=a[1]*dy+T[1, :]
    elif a[0]=='D':
        T[0,:]=a[1]
    else:
        return('Please enter a valid BC type')

    if b[0]=='N':
        T[-1,:]=b[1]*dy+T[-2, :]
    elif b[0]=='D':
        T[-1,:]=b[1]
    else:
        return('Please enter a valid BC type')

    #top
    if c[0]=='N':
        T[:, -1]=c[1]*dx+T[:, -2]
    elif c[0]=='D':
        T[:, -1]=c[1]
    else:
        return('Please enter a valid BC type')

    #bottom
    if d[0]=='N':
        T[:, 0]=d[1]*dx+T[:, 1]
    elif d[0]=='D':
        T[:, 0]=d[1]
    else:
        return('Please enter a valid BC type')
"""


import torch as th
import numpy as np
'''
prediction:
gradient computation:
loss computation:
parameter updates:
'''

'''All Manual with numpy'''
# f = w*x
# f = 2.x

X = np.array([1,2,3,4],dtype=np.float32)
Y = np.array([2,4,6,8],dtype=np.float32)

w = 0.0

def forward(x):
    return w*x
def loss(y,yp):
    return np.mean((y-yp)**2)
def gradient(x,y,yp):
    #MSE = 1/N*(wx-y)**2
    #dJ/dw = 1/N*2x*(w*x - y)
    return np.dot(2*x, yp-y).mean()

print(f'prediction before training f(5)= {forward(5):.3f}')

# training
lr = 0.01
n_iters = 100

for epoch in range(n_iters):
    # prediction
    y_pred = forward(X)

    # loss
    l = loss(Y,y_pred)

    #gradient
    dw = gradient(X,Y,y_pred)

    # Update weights
    w-=lr*dw
    if epoch%10==0:
        print(f'epoch {epoch+1}: w: {w:.8f}, loss = {l:.8f}')

print(f'Prediction after training: f(5) = {forward(5):.3f}')


import numpy as np
from nayzakflow.utils import _onehot

def MSE(y,yhat):
    a=np.square(yhat-y)
    a=np.sum(a)
    b= 1/(2*y.shape[1])
    return a*b

def _diff_MSE(y,yhat):
    return (yhat-y)

def MAE(y,yhat):
    return (1/(y.shape[1]))* np.sum(np.abs(yhat-y))

def _diff_MAE(y,yhat):
    diff= np.array((yhat-y)>0 , dtype="int")
    diff= ((1-diff)*-1)+diff

    return diff

def PerceptronCriteria(y,yhat):
    return (1/(y.shape[1]))* np.sum(np.maximum(0, -1*y*yhat))

def _diff_PerceptronCriteria(y,yhat):
    diff = np.array( (y*yhat)<=0, dtype="int" )
    diff*= (-1*y)
    return diff

def HingeLoss(y,yhat):
    return (1/(y.shape[1]))* np.sum(np.maximum(0, 1-y*yhat))

def _diff_HingeLoss(y,yhat):
    diff = np.array( (y*yhat)<=1, dtype="int" )
    diff*= (-1*y)
    return diff

def SigmoidLogLikelihood(y,yhat):
    return (1/(y.shape[1]))*np.sum(-1*np.log(np.abs((y/2)-0.5+yhat)))

def _diff_SigmoidLogLikelihood(y,yhat):
    return -1* y/ ( (((1+y)/2)*yhat) + (((1-y)/2)*(1-yhat))  )

def IdentityLogLikelihood(y,yhat):
    return (1/(y.shape[1]))*np.sum(np.log(1+np.exp(-1*y*yhat)))

def _diff_IdentityLogLikelihood(y,yhat):
    return (-1*y*np.exp(-1*y*yhat))/(1+np.exp(-1*y*yhat))

def SoftmaxLogLikelihood(y,yhat):
    onehotY= _onehot(y,yhat.shape[0])
    yhat_r = np.max(onehotY*yhat, axis=0,keepdims=True)
    return (1/(y.shape[1]))*-1*np.sum(np.log(yhat_r))

def SoftmaxLogLikelihood_OneHotEncoded(y,yhat):
    onehotY= y
    yhat_r = np.max(onehotY*yhat, axis=0,keepdims=True)
    return (1/(y.shape[1]))*-1*np.sum(np.log(yhat_r))

def MultiClassPerceptronCriteria(y,yhat):
    onehotY= _onehot(y,yhat.shape[0])
    yhat_r = np.max(onehotY*yhat, axis=0,keepdims=True)

    d= np.maximum(yhat-yhat_r,0)
    return (1/(y.shape[1]))*np.sum(np.max(d, axis=0))

def _diff_MultiClassPerceptronCriteria(y,yhat):
    onehotY= _onehot(y,yhat.shape[0])
    max_index=np.argmax(yhat,axis=0)
    temp=np.zeros_like(yhat)
    for i in range(temp.shape[1]): temp[max_index[i],i]=1
    return  temp+ (-1* onehotY)

def MultiClassHingeLoss(y,yhat):
    onehotY= _onehot(y,yhat.shape[0])
    yhat_r = np.max(onehotY*yhat, axis=0,keepdims=True)
    z=1+(yhat-yhat_r)
    z= (1-onehotY)*z
    d= np.maximum(z,0)
    return (1/(y.shape[1]))*np.sum(np.sum(d, axis=0))

def _diff_MultiClassHingeLoss(y,yhat):
    onehotY= _onehot(y,yhat.shape[0])
    yhat_r = np.max(onehotY*yhat, axis=0,keepdims=True)
    z=1+(yhat-yhat_r)
    z= (1-onehotY)*z
    d= np.maximum(z,0)
    de = np.sum(d>0,axis=0)
    out = np.zeros_like(yhat)
    out[d>0]=1
    out = out+ onehotY*(-de)
    return out

def MultiClassPerceptronCriteria_OneHotEncoded(y,yhat):
    onehotY= y
    yhat_r = np.max(onehotY*yhat, axis=0,keepdims=True)

    d= np.maximum(yhat_r-yhat,0)
    return (1/(y.shape[1]))*np.sum(np.max(d, axis=0))

def MultiClassHingeLoss_OneHotEncoded(y,yhat):
    onehotY= y
    yhat_r = np.max(onehotY*yhat, axis=0,keepdims=True)
    z=1+(yhat_r-yhat)
    z= (1-onehotY)*z
    d= np.maximum(z,0)
    return (1/(y.shape[1]))*np.sum(d)

def get_diffs():
    return {MSE:_diff_MSE , MAE:_diff_MAE, PerceptronCriteria:_diff_PerceptronCriteria, HingeLoss:_diff_HingeLoss,
            SigmoidLogLikelihood: _diff_SigmoidLogLikelihood , IdentityLogLikelihood:_diff_IdentityLogLikelihood,
            MultiClassPerceptronCriteria:_diff_MultiClassPerceptronCriteria , MultiClassHingeLoss:_diff_MultiClassHingeLoss}


import numpy as np
from itertools import product
from math import floor, sqrt, exp

def pres_value_per_year(payments, r, immediate=True):
    if immediate:
        s = [payments[k]/(1+r)**k for k in range(len(payments))]
        s = sum(s)
    else:
        s = [payments[k] / (1 + r) ** k for k in range(1, len(payments))]
        s = payments[0] + sum(s)
    s = round(s, 2)
    print("Present Value:", s)
    return s

def comp_int(money, r, n, quartely=False):
    if quartely:
        per_year = 4
        s = money * (1 + r/per_year) ** (per_year*n)
        print("Get {} after {} months".format(s, n))
    else:
        s = money*(1+r)**n
        print("Get {} after {} months".format(s, n))
    return s

def bound_perpetuity(A, r_lend, r_borrow):
    pv_borrow = A / r_borrow
    pv_lend = A / r_lend

    print("{} <= p <= {}".format(pv_borrow, pv_lend))
    return pv_borrow, pv_lend

def discount_rate(r, n, semi=False):
    if semi:
        d = 1 / (1 + r / 2) ** n
    else:
        d = 1 / (1 + r) ** n
    return d

def swap_int_rate(rates, years):
    num = 1 - discount_rate(rates[years-1], years)
    disc = [discount_rate(rates[i], i+1) for i in range(years)]
    den = sum(disc)

    return num/den

def futures_hedge_ratio(corr, needed_std, alt_std):
    cov = corr * needed_std * alt_std
    return cov/(needed_std*needed_std)

def options_binomial_model(S0, u, d, R, K, periods=1):
    S = [[]]*(periods+1)
    S[0] = [S0]
    changes = {
        'u': u,
        'd': d
    }

    for i in range(1, len(S)):
        S[i] = [0]*2**i
        j = 0
        changes_len = int(sqrt(len(S[i])))
        for p in product('ud' * changes_len, repeat=changes_len):
            for char in p:
                S[i][j] = S[i - 1][floor(j/2)] * changes[char]
                j += 1

    # Ax = b

    a = np.array([
        [S[1][0], R],
        [S[1][1], R]
    ])

    b = np.array([S[1][0]-K, 0])

    solution, resid, rank, s = np.linalg.lstsq(a, b)

    print("x: {}"
          "\ny: {}".format(round(solution[0], 3), round(solution[1], 3)))

    value = solution[0] * S0 + solution[1] * 1

    return value

def black_scholes_calibration(T, r, sigma, c, n):
    R = exp(r * T/n)
    u = exp(sigma * sqrt(T/n))
    d = 1/u
    q = (exp((r - c)*T/n) - d) / (u - d)

    return R, u, d, q


import numpy as np

def haversine(p1, p2):
    """
    Calculate the great circle distance between two points
    on the earth (specified in decimal degrees)

    All args must be of equal length.
    # Points in lat lon order
    """
    p1, p2 = map(np.radians, [p1, p2])

    dlat = p1[0] - p2[0]
    dlon = p1[1] - p2[1]

    a = np.sin(dlat/2.0)**2 + np.cos(p1[0]) * np.cos(p2[0]) * np.sin(dlon/2.0)**2

    c = 2 * np.arcsin(np.sqrt(a))
    dist = 6371000 * c # [m]
    return dist

def haversineForGrid(grid):
    """
    This function is used to obtain vertical and horizontal distances inside a grid
    :param grid:
    :return:
    """
    grid_rad = list(map(np.radians, grid))
    lat_rad = grid_rad[1]
    lon_rad = grid_rad[0]

    dlat = lat_rad[1:,:] - lat_rad[:-1,:]
    dlon = lon_rad[:,1:] - lon_rad[:,:-1]

    # We are creating horizontal and vertical distances
    out_dims = (2, grid[0].shape[0]-1,grid[0].shape[1]-1)
    output = np.zeros(out_dims)

    # Filling by cols
    for c_col in range(out_dims[2]):
        output[0,:,c_col] = np.sin(dlat[:,c_col]/2.0)**2
        output[1,:,c_col] = (np.cos(lat_rad[:,c_col+1]) * np.cos(lat_rad[:,c_col]) * np.sin(dlon[:,c_col]/2.0)**2)[:-1]

    c = 2 * np.arcsin(np.sqrt(output))
    dist = (6371000 * c)  # [m]
    return dist

if __name__ == "__main__":

    # ------ For two points
    # p1 = [0, 0]
    # p2 = [0, 1]
    # d = haversine(p1, p2)
    # print(F"Distance between {p1}(lat,lon) and {p2}(lat,lon) is {d:.3f} m")

    # ------ For grid
    lat = np.linspace(-10,10,21)
    lon = np.linspace(-20,20,41)
    grid = np.meshgrid(lon, lat)


import numpy as np

"""
Turns vector into a diagonal matrix.
This is used especially for eigenvalue/vector calculations.
"""
def as_singular(sigma, rank=None):
    sigma_len = len(sigma)
    if rank is None:
        rank = sigma_len
    array = np.zeros((sigma_len, sigma_len))
    for i in range(rank):
        array[i][i] = sigma[i] if sigma[i] >= 0 else 0
    return array

def eig(A, output="all", k=1):
    e, U = np.linalg.eigh(A)
    if output == "all":
        return e, U
    elif output == "max":
        new_e = np.argsort(e)
        return new_e[:k], U[:,new_e[:k]]
    elif output == "min":
        new_e = np.argsort(-e)
        return new_e[:k], U[:,new_e[:k]]

def generalized_eig(A, B, output="all", k=1):
    # Calculate EVD of B
    e, U = np.linalg.eigh(B)
    E = as_singular(e ** -0.5)
    C = E @ U.T @ A @ U @ E

    # Calculating EVD of C
    return eig(C, output, k)


from numpy import matrix, array, arange
from numpy.linalg import solve

def regression_quadratic(data):
    n = len(data)
    sx = sum(x for [x,y] in data)
    sx2 = sum(x**2 for [x,y] in data)
    sx3 = sum(x**3 for [x,y] in data)
    sx4 = sum(x**4 for [x,y] in data)
    sy = sum(y for [x,y] in data)
    sxy = sum(x*y for [x,y] in data)
    sx2y = sum(x**2*y for [x,y] in data)

    A = matrix([
        [sx4,sx3,sx2],
        [sx3,sx2,sx],
        [sx2,sx,n]
    ])
    v = array([sx2y,sxy,sy])
    [a,b,c] = solve(A,v)
    return {
        "a": a, "b": b, "c": c,
        "f": lambda x: a*x**2+b*x+c
    }

def regression(data,X,step):
    import matplotlib.pyplot as plot

    t = regression_quadratic(data)

    print("a = {}".format(t["a"]))
    print("b = {}".format(t["b"]))
    print("c = {}".format(t["c"]))

    [x,y] = zip(*data)
    plot.plot(x,y,'o',color='black')

    x = arange(X[0],X[1],step)
    plot.plot(x,t["f"](x),color='black')

    plot.show()

data = [
    [0,2], [1,2.5], [1,3], [2,3.4],
    [2.5,4], [3,4.2], [4,4.2], [5,4.0]
]
regression(data,X=[0,6],step=0.1)


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

# The host will reveal a door that doesn't contain the prize
def get_non_prize_door(host, num_doors, player_choice):
  i = 1
  while (i == host or i== player_choice ):
    i = (i+1)%(num_doors)

  return i

# Have the player switch to the other unopened door
def switch_function(shown_door, num_doors, player_choice):
  i = 1
  while (i == shown_door or i== player_choice ):
    i = (i+1)%(num_doors)

  return i

# Play the game
def monty_hall_game(switch, num_tests):
  win_switch_cnt = 0
  win_no_switch_cnt = 0
  lose_switch_cnt = 0
  lose_no_switch_cnt = 0

  # Get the doors
  doors = [0,1,2]

  # Get the number of doors
  num_doors = len(doors)

  for i in range(0,num_tests):
    # Randomly choose the door with the wanted prize
    door_with_prize = random.randint(0, num_doors-1)

    # The host knows which door has the prize
    host = door_with_prize

    # The player initially chooses a random door that they believe has the prize
    player_choice = random.randint(0, num_doors-1)
    original_player_choice = player_choice
    shown_door = get_non_prize_door(host, num_doors, player_choice)

    if switch == True:
      player_choice = switch_function(shown_door,num_doors, player_choice)

    if player_choice == host and switch == False:
      # The player wins from not switching
      print('Player Wins (No Switch) - The player chose door: ', player_choice,' Original choice: ',original_player_choice ,', Door with prize:', door_with_prize, ', Shown Door: ',shown_door )
      win_no_switch_cnt = win_no_switch_cnt + 1

    elif player_choice == host and switch == True:
      # The player wins from switching
      print('Player Wins (Switch) - The player chose door: ', player_choice,' Original choice: ',original_player_choice , ', Door with prize:', door_with_prize, ', Shown Door: ',shown_door )
      win_switch_cnt = win_switch_cnt +1

    elif player_choice != host and switch == False:
      # The player loses from not switching
      print('Player Lost (No Switch) - The player chose door: ', player_choice,' Original choice: ',original_player_choice , ', Door with prize:', door_with_prize, ', Shown Door: ',shown_door )
      lose_no_switch_cnt = lose_no_switch_cnt + 1

    elif player_choice != host and switch == True:
      # The player loses from switching
      print('Player Lost (Switch) - The player chose door: ', player_choice,' Original choice: ',original_player_choice , ', Door with prize:', door_with_prize, ', Shown Door: ',shown_door )
      lose_switch_cnt = lose_switch_cnt + 1

  return win_no_switch_cnt,win_switch_cnt,lose_no_switch_cnt,lose_switch_cnt, num_tests

# Play the game
num_tests = []
win_percentage = []
switch = True

for i in range(1,100):
  num_tests.append(i)
  y = monty_hall_game(switch, i)
  win_percentage.append(y[1]/ y[4])

# Visually show the number of tests and the win percentage

# Expected winning porbability
x = np.linspace(0, 100, 100000)

@np.vectorize
def constant_function(xyz):
    return 0.66

plt.plot(x,constant_function(x), dashes=[6, 2], color = "red")

plt.plot( num_tests, win_percentage  )
plt.title('Monty Hall Problem')
plt.xlabel('Number of Tests',fontsize=18)
plt.ylabel('Win Percentage',fontsize=18)
plt.show()

print('The win percentage for test playing ', y[4], ' games is:' ,y[1]/ y[4])


# Based on http://d2l.ai/chapter_computer-vision/transposed-conv.html

import torch
from torch import nn
import numpy as np

def trans_conv(X, K):
    h, w = K.shape
    Y = torch.zeros((X.shape[0] + h - 1, X.shape[1] + w - 1))
    for i in range(X.shape[0]):
        for j in range(X.shape[1]):
            Y[i:i + h, j:j + w] += X[i, j] * K
    return Y

# Example from D2L fig 13.10.1
X = torch.tensor([[0., 1], [2, 3]])
K = torch.tensor([[0., 1], [2, 3]])
Y = trans_conv(X, K)
print(Y)

X, K = X.reshape(1, 1, 2, 2), K.reshape(1, 1, 2, 2)
tconv = nn.ConvTranspose2d(1, 1, kernel_size=2, bias=False)
tconv.weight.data = K
Y2 = tconv(X)
#print(Y2)
assert torch.allclose(Y, Y2)

'''
X, K = X.reshape(1, 1, 2, 2), K.reshape(1, 1, 2, 2)
tconv = nn.ConvTranspose2d(1, 1, kernel_size=2, padding = 1, bias=False)
tconv.weight.data = K
Y2 = tconv(X)
print('Y2', Y2)
'''

# Transposed Matrix multiplication

K = torch.tensor([[1,2], [3, 4]])

def kernel2matrix(K):
    k, W = torch.zeros(5), torch.zeros((4, 9))
    k[:2], k[3:5] = K[0, :], K[1, :]
    W[0, :5], W[1, 1:6], W[2, 3:8], W[3, 4:] = k, k, k, k
    return W

W = kernel2matrix(K)

X = torch.tensor([[0.0, 1], [2, 3]])
Y = trans_conv(X, K)
Y2 = torch.mv(W.T, X.reshape(-1)).reshape(3, 3)
assert torch.allclose(Y, Y2)

# Example from Geron fig 14.27

X = torch.ones((2,3))
K = torch.ones(3,3)
X, K = X.reshape(1, 1, 2, 3), K.reshape(1, 1, 3, 3)
tconv = nn.ConvTranspose2d(1, 1, kernel_size=3, stride=2, bias=False)
tconv.weight.data = K
Y2 = tconv(X)
print(Y2.shape)


from math import *
import numpy as np
import matplotlib.pyplot as plt
import os
import re

def calculate_damping_coefficient(stiffness, restitution, m1, m2):
    ln_rest = log(restitution)
    return -2 * ln_rest * (get_reduced_particle_mass(m1, m2) * stiffness / (pi ** 2 + ln_rest ** 2)) ** 0.5

def get_mass(density, diameter):
    return density * pi * diameter ** 3 / 6

def get_reduced_particle_mass(m1, m2):
    return m1 * m2 / (m1 + m2)

def get_pos(time, mass, friction_coeff, u_0, g):
    return - friction_coeff * g * time ** 2 / 2 + u_0 * time

def get_vel(time, mass, friction_coeff, u_0, g):
    return - friction_coeff * g * time + u_0

def plot_pos_and_vel():
    stiffness = 1e5
    density = 2000
    diameter = 0.1
    mass = get_mass(density, diameter)
    restitution = 0.8
    friction_coeff = 0.6
    g = 9.81
    u_0 = 1

    damping_coeff = calculate_damping_coefficient(stiffness, restitution, mass, mass)

    col_duration = pi * sqrt(mass / stiffness)

    # Generate Analytic Solution Data
    t_max = u_0 / (friction_coeff * g)  # From Velocity equation
    # print("t_max = " + str(t_max))
    times = np.arange(0, t_max, col_duration / 16)
    positions = []
    velocities = []

    for t in times:
        positions.append(get_pos(t, mass, friction_coeff, u_0, g))
        velocities.append(get_vel(t, mass, friction_coeff, u_0, g))
    times = np.append(times, 0.5)
    positions.append(positions[-1])
    velocities.append(0)

    # Get Simulation Data
    data = []
    increments = []
    data_dir = os.listdir("data")
    for filename in data_dir:
        name_match = re.match("1_friction_(\d+)_\d+.txt", filename)
        if name_match:
            i = int(name_match.group(1))
            if increments.count(i) == 0:
                increments.append(i)
    increments.sort()
    for i in increments:
        timestep_data = np.array([]).reshape((0, 3))  # Times, Positions, Velocities
        for filename in data_dir:
            name_match = re.match("1_friction_" + str(i) + "_(\d+).txt", filename)
            if name_match:
                time = int(name_match.group(1)) / 1e6
                file = open("data/" + filename, "r")
                line = file.readlines()[0]
                data_match = re.match("(-?\d+\.\d+),(-?\d+\.\d+),(-?\d+\.\d+),(-?\d+\.\d+),(-?\d+\.\d+),(-?\d+\.\d+)",
                                      line)
                if data_match:
                    timestep_data = np.append(timestep_data,
                                              [[time, float(data_match.group(1)), float(data_match.group(4))]], axis=0)
        timestep_data = timestep_data[timestep_data[:, 0].argsort()]  # Sort by time
        data.append(timestep_data)

    # Normalize Data
    # Normalize Data
    for incr_data in data:
        for n in range(len(incr_data)):
            incr_data[n, 0] = incr_data[n, 0] / col_duration  # Normalize time with collision duration
            incr_data[n, 1] = incr_data[n, 1] / diameter  # Normalize position with particle diameter
            incr_data[n, 2] = incr_data[n, 2] / u_0  # Normalize velocity with initial velocity

    for n in range(len(positions)):
        times[n] = times[n] / col_duration
        positions[n] = positions[n] / diameter
        velocities[n] = velocities[n] / u_0

    # Plot Data
    fig = plt.figure(figsize=(8, 10))
    plt.rc('axes', labelsize=15)
    fig.patch.set_facecolor('white')
    ax1 = fig.add_subplot(211)
    ax1_lines = []
    analytic_line, = ax1.plot(times, positions, 'k', label="Analytic")
    ax1_lines.append(analytic_line)
    for i in range(len(data)):
        timestep_data = data[i]
        timestep_label = "$t_{collision} / " + str(increments[i]) + "$"
        line, = ax1.plot(timestep_data[:, 0], timestep_data[:, 1], '--', label=timestep_label)
        ax1_lines.append(line)
    ax1.set_xlabel('$t / t_{collision}$')
    ax1.set_ylabel('$x / d_p$')
    ax1.legend(handles=ax1_lines, loc=4)

    ax2 = fig.add_subplot(212)
    ax2_lines = []
    analytic_line, = ax2.plot(times, velocities, 'k', label="Analytic")
    ax2_lines.append(analytic_line)
    for i in range(len(data)):
        timestep_data = data[i]
        timestep_label = "$t_{collision} / " + str(increments[i]) + "$"
        line, = ax2.plot(timestep_data[:, 0], timestep_data[:, 2], '--', label=timestep_label)
        ax2_lines.append(line)
    ax2.set_ylabel('$u / u_0$')
    ax2.set_xlabel('$t / t_{collision}$')
    ax2.legend(handles=ax2_lines, loc=1)

    fig.tight_layout()
    plt.show()

plot_pos_and_vel()


from anim import *
import numpy as np
from scipy.integrate import odeint
import random

class LorenzAttractor(Scene):
    def __init__(self, width, height, xrange, yrange, color=BLACK,
            sigma=10, rho=28, beta=8/3):
        super().__init__(width, height, xrange, yrange, color)
        self.sigma = sigma
        self.rho = rho
        self.beta = beta

    def deltaX(self, x, y, sigma):
        return sigma*y - sigma*x

    def deltaY(self, x, y, z, rho):
        return rho*x - x*z - y

    def deltaZ(self, x, y, z, beta):
        return x*y - beta*z

    def nextPoint(self, state, t):
        x, y, z = state
        x1 = self.deltaX(x, y, self.sigma)
        y1 = self.deltaY(x, y, z, self.rho)
        z1 = self.deltaZ(x, y, z, self.beta)
        return x1, y1, z1

    def graph(self, state0, t_end=40, t_step=0.01, t_start=0, trailLength=100, frame_skip=40, radius=2, color=PINK, opacity=255):
        self.graphFunction(self.nextPoint, state0, t_end, t_step, t_start, trailLength, frame_skip, radius, color, opacity)

    def graphRandomParticles(self, particles, t_end=40, t_step=0.01, t_start=0,
            trailLength=100, frame_skip=40, radii_min=0.5, radii_max=1.75, color=None,
            opacity=255):
        self.graphFunctionWithRandomParticles(self.nextPoint, particles, t_end, t_step, t_start,
            trailLength, frame_skip, radii_min, radii_max, color,
            opacity)


# Impoting Numpy library
import numpy as np

# Initialising arrays
x = np.array([[1,2,3],[3,2,1],[1,0,-1]])
y = np.array([[-4,-3,2],[1,2,1],[2,4,2]])

# Printing rank of x
print("rank of x: {}".format(np.linalg.matrix_rank(x)))

# Storing and Finding eigen vector and value of X Using eig function of Numpy
eigen_valuesX,eigen_vectorX= np.linalg.eig(x)

# Printing eigen value and vector of X
print("\nEIGEN VALUES OF X:\n{}".format(eigen_valuesX))
print("\nEIGEN VECTOR OF X:\n{}".format(eigen_vectorX))

# Printing rank of y
print("rank of y : {}".format(np.linalg.matrix_rank(y)))

# Storing and Finding eigen vector and value of Y Using eig function of Numpy
eigen_valuesY,eigen_vectorY= np.linalg.eig(y)

# Printing eigen value and vector of Y
print("\nEIGEN VALUES OF Y:\n{}".format(eigen_valuesY))
print("\nEIGEN VECTOR OF Y:\n{}".format(eigen_vectorY))

#*************** OUTPUT ************************#
# rank of x: 2

# EIGEN VALUES OF X:
# [ 4.31662479e+00 -2.31662479e+00  1.93041509e-17]

# EIGEN VECTOR OF X:
# [[ 0.58428153  0.73595785  0.40824829]
#  [ 0.80407569 -0.38198836 -0.81649658]
#  [ 0.10989708 -0.55897311  0.40824829]]

# rank of y : 2

# EIGEN VALUES OF Y:
# [-4.12310563e+00  4.12310563e+00 -7.83686841e-16]

# EIGEN VECTOR OF Y:
# [[ 0.96413818 -0.05497126  0.66742381]
#  [-0.11869083 -0.44653738 -0.57207755]
#  [-0.23738167 -0.89307476  0.47673129]]
#*************** END OF OUTPUT ************************#


from .coord_transform import CoordTransform as _CoordTransform
class Cylindrical2Cartesian(_CoordTransform):
    def __init__(self, syms=None):
        from sympy import symbols, Array, cos, sin
        if syms is None:
            _R = symbols('R', positive=True)
            _Z, _phi = symbols('Z, phi', real=True)
            syms = (_R, _Z, _phi)
        R, Z, phi = syms[0], syms[1], syms[2]
        _CoordTransform.__init__(self, [R*cos(phi), R*sin(phi), Z], syms)

class Cartesian2Cylindrical(_CoordTransform):
    def __init__(self, syms=None):
        from sympy import symbols, Array, cos, sin, sqrt, atan2
        if syms is None:
            syms = symbols('x, y, z', real=True)
        x, y, z = syms[0], syms[1], syms[2]
        _CoordTransform.__init__(self, [sqrt(x**2+y**2), z, atan2(y, x)], syms)