import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
from matplotlib.colors import Normalize

def hist_match(input_image, spec_hist=None):
    """
    Specify a histogram for image Q3_2.tif, such that by matching the histogram of Q3_2.tif to the specified one,
    the image is enhanced. Implement the specified histogram matching to the input image Q3_2.tif. You may refer to
    the histogram given in the Lecture Notes 3 page 49, but not necessary to use the same one. in your report.
    """

    raw_img = cv.imread(input_image, cv.IMREAD_GRAYSCALE)
    L = 2 ** 8
    if spec_hist is None:
        spec_hist = np.array([1 / L] * L)
    # spec_hist = spec_hist / sum(spec_hist)

    bins = range(L + 1)

    input_hist, _ = np.histogram(raw_img.flat, bins=bins, density=True)
    s = np.array([(L - 1) * input_hist[:k + 1].sum() for k in range(L)], int)
    g = np.array([(L - 1) * spec_hist[:q + 1].sum() for q in range(L)], int)
    # the index of g is implicitly z
    # z = np.array([np.where(g == e)[0][0] if len(np.where(g == e)[0]) != 0 else 0 for e in s], int)
    z = np.array([np.abs(g - e).argmin() for e in s], int)

    # z = []
    # for e in s:
    #     index = np.where(g == e)
    #     lower = upper = e
    #     while index.size is 0:
    #         lower -= 1
    #         upper += 1
    #         index = np.append(np.where(g == lower), np.where(g == upper))

    out_img = np.array([z[r] for r in raw_img], int).reshape(raw_img.shape)
    output_hist, _ = np.histogram(out_img.flat, bins=bins, density=True)

    # %% plots
    norm = Normalize(vmin=0, vmax=255)

    plt.subplot(121)
    plt.imshow(raw_img, cmap='gray', norm=norm)
    plt.title("Raw " + input_image)

    plt.subplot(122)
    plt.imshow(out_img, cmap='gray', norm=norm)
    plt.title("Enhanced " + input_image)
    # plt.savefig(file_name + "_comparison.png")
    plt.show()

    plt.figure(figsize=(9.6, 4.8))
    plt.subplot(131)
    plt.title("Histogram of raw " + input_image)
    plt.bar(range(L), input_hist)
    # plt.xticks([])

    plt.subplot(132)
    plt.title("Specified histogram")
    plt.bar(range(L), spec_hist)
    # plt.xticks([])

    plt.subplot(133)
    plt.title("Histogram of enhanced " + input_image)
    plt.bar(range(L), output_hist)
    # plt.legend(('raw image', 'specified histogram', 'matched image'))
    # plt.savefig(file_name + "_histogram.png")
    plt.show()

    plt.plot(z)
    plt.title("Histogram matching transformation for " + input_image)
    plt.xlabel('$r_k$')
    plt.ylabel('$z_k$')
    plt.show()

    return out_img, output_hist, input_hist  # , z

# %%
out_img_1, out_hist_1, *_ = hist_match('Q3_2.tif')
# out_img_1, out_hist_1, _, z1 = hist_match('Q3_2.tif')

# %%
hist = np.concatenate((np.linspace(0, 7, 8 - 0),
                       np.linspace(7, 0.75, 16 - 8),
                       np.linspace(0.75, 0, 184 - 16),
                       np.linspace(0, 0.5, 200 - 184),
                       np.linspace(0.5, 0, 256 - 200)), axis=0)
hist = hist / hist.sum()
plt.plot(hist)
plt.title("Histogram specified")
plt.xlabel('intensity')
plt.ylabel('percentage')
plt.show()

out_img_2, *_ = hist_match('Q3_2.tif', hist)
# out_img_2, *_, z2 = hist_match('Q3_2.tif', hist)
norm = Normalize(vmin=0, vmax=255)
plt.subplot(121)
plt.imshow(out_img_1, cmap='gray', norm=norm)
plt.title("Histogram equalized")

plt.subplot(122)
plt.imshow(out_img_2, cmap='gray', norm=norm)
plt.title("Enhanced with a specified histogram")
# plt.savefig(file_name + "_comparison.png")
plt.show()

# plt.plot(z1)
# plt.plot(z2)
# plt.title("Histogram matching transformation")
# plt.legend(('equalized histogram', 'specified histogram'))
# plt.show()


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

def mvnormal_distrib_map(mu, cov, observations, padding_factor=0.2, grid_prec=0.25):
    """
    Create a discritized map of a multivariate normal distribution on domain
    defined by the observations

    :param mu: Mean of a multivariate normal ((m x 1) array)
    :param cov: Covariance matrix of a multivariate normal ((n x n array))
    :param observations: An (m x n) NumPy array or Pandas data frame,
                         where n is the dimension of the data
                         and m is the number of observations
    :param padding_factor: a factor by which the data span domain is extended
    :param grid_prec: precision of discretization (distance between two consecutive
                      grid points)
    :return:
    """

    dim = observations.shape[1]

    obs_min = np.min(observations, axis=0)
    obs_max = np.max(observations, axis=0)

    dataspan = obs_max - obs_min
    padding = dataspan * padding_factor
    domain = np.array([obs_min - padding, obs_max + padding])

    aranges = [np.arange(domain[0, j], domain[1, j], grid_prec) for j in range(dim)]
    grid_x, grid_y = np.meshgrid(*aranges)

    def make_mv_normal(mu, cov):
        def fn(grid_x, grid_y):

            distrib = np.zeros(grid_x.shape)

            for i in range(grid_x.shape[0]):
                for j in range(grid_x.shape[1]):
                    x = np.array([grid_x[i, j], grid_y[i, j]])
                    distrib[i, j] = multivariate_normal.pdf(x, mu, cov)

            return distrib

        return fn

    mv_normal = make_mv_normal(mu, cov)

    distrib_map = mv_normal(grid_x, grid_y)

    return grid_x, grid_y, distrib_map

def get_axis_extent(grid_x, grid_y):
    """
    Get a tuple of (left, right, bottom, top)
    from the meshgrid output. Used for the extent
    parameter in matplotlib
    """

    left = grid_x[0, 0]
    right = grid_x[0, -1]

    bottom = grid_y[-1, 0]
    top = grid_y[0, 0]

    return left, right, bottom, top

def plot_frozen_distrib(x0, x1, distrib, linspace_num=200, fill_alpha=0.4, **plot_kvargs):
    """
    Visualize 1D continuous distribution on the given domain range
    """

    x = np.linspace(x0, x1, num=linspace_num)
    y = distrib.pdf(x)
    plt.plot(x, y, **plot_kvargs)

    fb_kvargs = {'alpha': fill_alpha}
    if 'color' in plot_kvargs:
        fb_kvargs['color'] = plot_kvargs['color']
    plt.fill_between(x, y, **fb_kvargs)

def plot_surface(x, y, z, ax=None, **surface_kwargs):

    if ax is None:
        _, ax = plt.subplots(subplot_kw={'projection': '3d'})

    ax.plot_surface(x, y, z, **surface_kwargs)

    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.set_zlabel('z')

def plot_contour(x, y, z, ax=None, **contourf_kwargs):

    if ax is None:
        _, ax = plt.subplots()

    ax.set_aspect('equal')
    cnt = ax.contourf(x, y, z, **contourf_kwargs)
    plt.colorbar(cnt, ax=ax)

def plot_grad_as_vector_field(x_range, y_range, grad_x, grad_y, ax, **quiver_kwargs):

    ax.quiver(x_range, y_range, grad_x, grad_y, **quiver_kwargs)

def compact_plotter(n, figsize, n_cols=2):

    for start in range(0, n, n_cols):

        plt.figure(figsize=figsize)

        for i in range(n_cols):

            if start + i >= n:
                break

            ax = plt.subplot(1, n_cols, i+1)

            yield ax

def create_compact_plotter(elements, **kwargs):

    n = len(elements)
    cp = compact_plotter(n, **kwargs)

    return zip(elements, cp)


import numpy as np

from ezmodel.core.transformation import Transformation

class Plog(Transformation):

    def __init__(self) -> None:
        super().__init__()

    def forward(self, y):
        yp = np.zeros_like(y)

        larger = y >= 0
        yp[larger] = np.log(1 + y[larger])
        yp[~larger] = - np.log(1 - y[~larger])

        return yp

    def backward(self, yp):
        y = np.zeros_like(yp)

        larger = yp >= 0
        y[larger] = np.exp(yp[larger]) - 1
        y[~larger] = 1 - np.exp(-yp[~larger])

        return y


import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.neural_network import MLPRegressor
from matplotlib import pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.lines as mlines
from sklearn.metrics import mean_squared_error, mean_absolute_error

class NeuralNetworkRegressionClass:
    def __init__(self):
        self.model = MLPRegressor(hidden_layer_sizes=(10, 10, 10), max_iter=1000)

    def train(self, data_X, data_Y):
        self.model.fit(data_X, data_Y)

    def predict(self, data):
        # TODO test with and without the following line
        # data = StandardScaler().fit(data)
        return self.model.predict(data)

    def train_csv(self, file_path):
        dataset = pd.read_csv(file_path)

        X = dataset.iloc[:, 0:102].values
        y = dataset.iloc[:, 102].values

        scaler = StandardScaler()
        scaler.fit(X)

        X_train = scaler.transform(X)

        self.train(X_train, y)

    def get_total_error(self, real_value, prediction_value, meanAbsoluteError, meanSquaredError, rootMeanSquaredError):
        print('Neural Network error:')
        # Evaluating the Algorithm

        print('Mean Absolute Error:', mean_absolute_error(real_value, prediction_value))
        print('Mean Squared Error:', mean_squared_error(real_value, prediction_value))
        print('Root Mean Squared Error:', np.sqrt(mean_squared_error(real_value, prediction_value)))

        timeSlot = len(meanAbsoluteError)
        t = list(range(timeSlot))

        if timeSlot == 100 or timeSlot == 150 or timeSlot == 200 or timeSlot == 250:
            plt.plot(t, meanAbsoluteError, 'r--', t, rootMeanSquaredError, 'b^')
            # plt.plot(t, meanAbsoluteError, 'r--', t, meanSquaredError, 'bs', t, rootMeanSquaredError, 'g^')

            red_patch = mpatches.Patch(color='red', label='Mean Absolute Error')
            blue_line = mlines.Line2D([], [], color='blue', marker='^', markersize=15, label='Root Mean Squared Error')
            plt.legend(handles=[red_patch,blue_line])

            plt.xlabel("Time slot")
            plt.ylabel("Errors")
            plt.title("Neural Network_Error")
            plt.savefig("WholesalePred/plots/Regression/NeuralNetworkRegression_Error" + str(timeSlot) + "timeslot_with_legend.png")

    def get_error(self, real_value, prediction_value):
        print('\nNeural Network Regression:')
        print('Predicted value: ', prediction_value[0], '   Real value: ', real_value[0])

        # Evaluating the Algorithm

        singleMeanAbsoluteError = mean_absolute_error(real_value, prediction_value)
        print('Mean Absolute Error:', singleMeanAbsoluteError)

        singleMeanSquaredError = mean_squared_error(real_value, prediction_value)
        print('Mean Squared Error:', singleMeanSquaredError)

        singleRootMeanSquaredError = np.sqrt(mean_squared_error(real_value, prediction_value))
        print('Root Mean Squared Error:', singleRootMeanSquaredError)

        return singleMeanAbsoluteError, singleMeanSquaredError, singleRootMeanSquaredError


"""Test simple vectorized functions that use our Variable class with a numpy
backend"""
import ad
import pytest
import numpy as np

EPSILON = 0.0001

def equals(arr1, arr2):
    """Checks if arr1 and arr2 are within EPISILON distance of one another."""
    return np.power(arr1 - arr2, 2).sum() < EPSILON

""" Test simple 1D arithmetic equations for the Hessian """

def test_constant_hessian():
    """Checks the trivial case"""
    constant_5 = ad.Constant(5.1231)
    assert(constant_5.hessian({}) == 0)

def test_constant_simple_op_hessian():
    c1 = ad.Constant(50)
    c2 = ad.Constant(123)
    c3 = ad.Constant(500)
    f = c1 * c2 + c1 / c3 + (1 - c2) * (-c3)
    assert(f.hessian({}) == 0)

def test_single_variable_add_mult():
    x = ad.Variable()
    f = x * x * x * 5.0 + 5 * (-x) - x - x / 5.0
    assert(f.hessian({x: 0}) == 0)
    assert(f.hessian({x: 1}) == 30.0)

def test_single_variable_div():
    x = ad.Variable()
    f = 1.0 / (x + 1.0) + 5 - 2 * x
    assert(equals(f.hessian({x: 1}), 0.25))
    assert(equals(f.hessian({x: 0}), 2.0))

"""Testing single variable trig functions"""

def test_single_variable_trig_sin():
    """analytic hessian is -25 sin (5 * x + 3)"""
    x = ad.Variable()
    f = ad.Sin(5 * x + 3)
    assert(equals(f.hessian({x: 1}), -25 * np.sin(5 * 1 + 3)))
    assert(equals(f.hessian({x: 2}), -25 * np.sin(5 * 2 + 3)))
    assert(equals(f.hessian({x: 3}), -25 * np.sin(5 * 3 + 3)))

def test_single_variable_trig_cos():
    """analytic hessian is -25 cos (5 * x + 3)"""
    x = ad.Variable()
    f = ad.Cos(5 * x + 3)
    assert(equals(f.hessian({x: 1}), -25 * np.cos(5 * 1 + 3)))
    assert(equals(f.hessian({x: 2}), -25 * np.cos(5 * 2 + 3)))
    assert(equals(f.hessian({x: 3}), -25 * np.cos(5 * 3 + 3)))

def test_single_variable_trig_tan():
    """analytic hessian is -25 cos (5 * x + 3)"""
    x = ad.Variable()
    f = ad.Cos(5 * x + 3) * ad.Tan(x * x - 5)
    assert(equals(f.hessian({x: 1}), -48.05115800))
    assert(equals(f.hessian({x: 2}), -170.9403025))
    assert(equals(f.hessian({x: 3}), 218.2792716))

def test_single_variable_trig_hyperbolic():
    x = ad.Variable()
    f = x * x * ad.Cosh(0.01 * x + 0.1) + x * ad.Sinh(x * x - 4.0)
    assert(equals(f.hessian({x: 1}), 22.351093955))
    assert(equals(f.hessian({x: 2}), 14.02444322))
    assert(equals(f.hessian({x: 3}), 9351.7592912))

def test_single_variable_trig_hyperbolic_2():
    x = ad.Variable()
    # x^2  Cosh[Sin[x] + Tanh[Exp[3 * x] + Log[x]]]
    g = ad.Sin(x) + ad.Tanh(ad.Exp(3 * x) + ad.Log(x))
    f = x * x * ad.Cosh(g)
    assert(equals(f.hessian({x: 1}), 11.464317742))
    assert(equals(f.hessian({x: 2}), -13.704377252))

def test_single_variable_power_simple():
    x = ad.Variable()
    f = x ** 5 + x ** 3 - (x + 1.0 / x) ** 2
    assert(equals(f.hessian({x: 1}), 18.0))
    assert(equals(f.hessian({x: 2}), 169.625))
    assert(equals(f.hessian({x: 2.5}), 325.3464))

def test_double_variable_power_throws():
    x, y = ad.Variable(), ad.Variable()
    f = x ** (y + 5.0)
    g = x ** y
    with pytest.raises(NotImplementedError):
        g.hessian({x:1, y:1})
    with pytest.raises(NotImplementedError):
        f.hessian({x:1, y:1})


import math
import matplotlib.pyplot as plt
import numpy as np
import random
from .GeneralDistribution import Distribution

class Binomial(Distribution):
    """
    Binomial distribution class for calculating and visualizing a Binomial distribution.

    Attributes:
        mean (float) representing the mean value of the distribution
        stdev (float) representing the standard deviation of the distribution
        data_list (list of floats) a list of floats to be extracted from the data file
        p (float) representing the probability of an event occurring
        n (int) number of trials
    """

    def __init__(self, prob = random.uniform(0,1), size = 20):

        self.n = size
        self.p = prob
        Distribution.__init__(self, self.calculate_mean(), self.calculate_stdev())
        self.data = np.random.binomial(self.n, self.p, 500)

    def calculate_mean(self):

        """
        Function to calculate the mean from p and n

        Args:
            None

        Returns:
            float: mean of the data set

        """

        self.mean = self.p * self.n

        return self.mean

    def calculate_stdev(self):

        """
        Function to calculate the standard deviation from p and n.

        Args:
            None

        Returns:
            float: standard deviation of the data set

        """

        self.stdev = math.sqrt(self.n * self.p * (1 - self.p))

        return self.stdev

    def read_data_file(self, file_name):

        """
        Function to read in data from a txt file. The txt file should have
        one number (float) per line. The numbers are stored in the data attribute.
        After reading in the file, the mean and standard deviation are calculated

        Args:
            file_name (string): name of a file to read from

        Returns:
            float: the p value
            float: the n value

        """

        Distribution.read_data_file(self, file_name)

        try:
            npdata = np.array(self.data)
            assert np.array_equal(npdata, npdata.astype(bool)), 'data contains values other than 0 or 1'
        except AssertionError as error:
            raise

        self.n = len(self.data)
        self.p = 1.0 * sum(self.data) / self.n
        self.mean = self.calculate_mean()
        self.stdev = self.calculate_stdev()

        return self.p, self.n

    def plot_raw_data(self):

        """
        Function to output a histogram of the instance variable data using
        matplotlib pyplot library.

        Args:
            None

        Returns:
            None
        """

        plt.bar(x = ['0', '1'], height = [(1 - self.p) * self.n, self.p * self.n])
        plt.title('Bar Chart of Data')
        plt.xlabel('Outcome')
        plt.ylabel('Count')
        plt.show()

    def pmf(self, k):

        """
        Probability mass function calculator for the binomial distribution.

        Args:
            k (unsigned int): point for calculating the probability mass function. k is in support (the set{0, 1, ..., n})

        Returns:
            float: probability density function output
        """

        try:
            assert k in range(self.n + 1), 'k isn\'t in support'
        except AssertionError as error:
            raise

        n_choose_k = math.factorial(self.n) / (math.factorial(k) * (math.factorial(self.n - k)))
        prob_of_k_successes = (self.p ** k)
        prob_rest_are_failures = (1 - self.p) ** (self.n - k)

        return n_choose_k * prob_of_k_successes * prob_rest_are_failures

    def cdf(self, k):

        """
        Cumulative distribution function calculator for the binomial distribution.

        Args:
            k (unsigned float): point for calculating the cumulative distribution function.

        Returns:
            float: cumulative density function output
        """

        try:
            assert k >= 0, 'k isn\'t nonnegative'
        except AssertionError as error:
            raise

        return sum([self.pmf(x) for x in range(int(math.floor(k)) + 1)])

    def plot(self):

        """
        Function to plot the pmf and cdf of the binomial distribution

        Args:
            None

        Returns:
            list: x values for the both plot
            list: y1 values for the pmf plot
            list: y2 values for cdf plot

        """

        x = []
        y1 = []
        y2 = []

        # calculate the x values to visualize
        for i in range(self.n + 1):
            x.append(i)
            y1.append(self.pmf(i))
            y2.append(self.cdf(i))

        # make the plots
        fig, axes = plt.subplots(2,sharex=True)
        fig.subplots_adjust(hspace=.5)

        axes[0].plot(x, y1)
        axes[0].set_title('Probability Distribution of Outcomes')
        axes[0].set_ylabel('Probability Density')

        axes[1].plot(x, y2)
        axes[1].set_title('Cumulative Distribution of Outcomes')
        axes[1].set_ylabel('Probability')

        plt.show()

        return x, y1, y2

    def __add__(self, other):

        """
        Function to add together two Binomial distributions with equal p

        Args:
            other (Binomial): Binomial instance

        Returns:
            Binomial: Binomial distribution

        """

        try:
            assert self.p == other.p, 'p values are not equal'
        except AssertionError as error:
            raise

        result = Binomial()
        result.n = self.n + other.n
        result.p = self.p
        result.calculate_mean()
        result.calculate_stdev()

        return result

    def __repr__(self):

        """
        Function to output the characteristics of the Binomial instance

        Args:
            None

        Returns:
            string: characteristics of the Binomial

        """

        return "mean = {}, standard deviation = {}, p = {}, n = {}".\
        format(self.mean, self.stdev, self.p, self.n)


import numpy as np
import cv2

cam_id = 0  # camera id

# for default webcam, cam_id is usually 0
# try out other numbers (1,2,..) if this does not work

cap = cv2.VideoCapture(cam_id)

mode = 'o' # show the original image at the beginning

sigma = 5

def std_filter(I, ksize):
    F = np.ones((ksize,ksize), dtype=np.float) / (ksize*ksize);

    MI = cv2.filter2D(I,-1,F) # apply mean filter on I

    I2 = I * I; # I squared
    MI2 = cv2.filter2D(I2,-1,F) # apply mean filter on I2

    return np.sqrt(MI2 - MI * MI)

def zero_crossing(I):
    """Finds locations at which zero-crossing occurs, used for
    Laplacian edge detector"""

    Ishrx = I.copy();
    Ishrx[:,1:] = Ishrx[:,:-1]

    Ishdy = I.copy();
    Ishdy[1:,:] = Ishdy[:-1,:]

    ZC = (I==0) | (I * Ishrx < 0) | (I * Ishdy < 0); # zero crossing locations

    SI = std_filter(I, 3) / I.max()

    Mask =  ZC & (SI > .1)

    E = Mask.astype(np.uint8) * 255 # the edges

    return E

while True:
    ret, I = cap.read();
    # I = cv2.imread("agha-bozorg.jpg") # can use this for testing
    I = cv2.cvtColor(I, cv2.COLOR_BGR2GRAY) # convert to grayscale
    Ib = cv2.GaussianBlur(I, (sigma,sigma), 0); # blur the image

    if mode == 'o':
        # J = the original image
        J = I
    elif mode == 'x':
        # J = Sobel gradient in x direction
        J = np.abs(cv2.Sobel(Ib,cv2.CV_64F,1,0));

    elif mode == 'y':
        # J = Sobel gradient in y direction
        J = np.abs(cv2.Sobel(Ib,cv2.CV_64F,0,1));

    elif mode == 'm':
        # J = magnitude of Sobel gradient
        Ix = cv2.Sobel(Ib,cv2.CV_64F,1,0)
        Iy = cv2.Sobel(Ib,cv2.CV_64F,0,1)
        J = np.sqrt(Ix*Ix + Iy*Iy)

    elif mode == 's':
        # J = Sobel + thresholding edge detection
        thresh = 90
        Ix = cv2.Sobel(Ib,cv2.CV_64F,1,0)
        Iy = cv2.Sobel(Ib,cv2.CV_64F,0,1)
        X = np.sqrt(Ix*Ix + Iy*Iy)
        J = np.uint8(X > thresh)*255

    elif mode == 'l':
        # J = Laplacian edges
        J = cv2.Laplacian(Ib,cv2.CV_64F,ksize=5)
        J = zero_crossing(J);

    elif mode == 'c':
        # J = Canny edges
        lth = 50   # low threshold
        hth = 120  # high threshold
        J = cv2.Canny(Ib,lth, hth)

    # we set the image type to float and the
    # maximum value to 1 (for a better illustration)
    # notice that imshow in opencv does not automatically
    # map the min and max values to black and white.
    J = J.astype(np.float) / J.max();
    cv2.imshow("my stream", J);

    key = chr(cv2.waitKey(1) & 0xFF)

    if key in ['o', 'x', 'y', 'm', 's', 'c', 'l']:
        mode = key
    if key == '-' and sigma > 1:
        sigma -= 2
        print("sigma = %d"%sigma)
    if key in ['+','=']:
        sigma += 2
        print("sigma = %d"%sigma)
    elif key == 'q':
        break

cap.release()
cv2.destroyAllWindows()


import matplotlib.pyplot as plt
import numpy as np

def generate_coin_flips(size=1000, p_heads=0.5, seed=42):
    rng = np.random.default_rng(seed)
    p_tails = 1 - p_heads
    return rng.choice(("H", "T"), size=size, p=(p_heads, p_tails))

def plot_coin_flips(number_of_flips, p_heads):
    fig, ax = plt.subplots()

    flips = generate_coin_flips(number_of_flips, p_heads)
    proportion = np.cumsum(flips == "H") / np.arange(1, number_of_flips + 1)
    xs = np.arange(1, number_of_flips + 1)

    ax.plot(xs, proportion, linewidth=1.2)
    ax.set(xlabel="Number of coin tosses", ylabel="Proportion of heads")
    return fig

plot_coin_flips(1000, 0.3).savefig("01-21a.png", bbox_inches="tight")
plot_coin_flips(1000, 0.03).savefig("01-21b.png", bbox_inches="tight")


import numpy as np
from sklearn.svm import LinearSVC

### Functions for you to fill in ###

def one_vs_rest_svm(train_x, train_y, test_x):
    """
    Trains a linear SVM for binary classifciation

    Args:
        train_x - (n, d) NumPy array (n datapoints each with d features)
        train_y - (n, ) NumPy array containing the labels (0 or 1) for each training data point
        test_x - (m, d) NumPy array (m datapoints each with d features)
    Returns:
        pred_test_y - (m,) NumPy array containing the labels (0 or 1) for each test data point
    """
    # So c is essentially 1 over lambda.
    # So as C is large, we emphasize losses.
    # As c is small, we emphasize simpler solutions
    # that have a large margin.

    # For large values of C, the optimization will
    # choose a smaller-margin hyperplane if that
    # hyperplane does a better job of getting all
    # the training points classified correctly.
    # https://stats.stackexchange.com/questions/31066/what-is-the-influence-of-c-in-svms-with-linear-kernel
    clf = LinearSVC(max_iter=1000, random_state=0, C=0.1)
    clf.fit(train_x, train_y)
    return clf.predict(test_x)

def multi_class_svm(train_x, train_y, test_x):
    """
    Trains a linear SVM for multiclass classifciation using a one-vs-rest strategy

    Args:
        train_x - (n, d) NumPy array (n datapoints each with d features)
        train_y - (n, ) NumPy array containing the labels (int) for each training data point
        test_x - (m, d) NumPy array (m datapoints each with d features)
    Returns:
        pred_test_y - (m,) NumPy array containing the labels (int) for each test data point
    """
    clf = LinearSVC(max_iter=1000, random_state=0, C=0.1, multi_class="ovr")
    clf.fit(train_x, train_y)
    return clf.predict(test_x)

def compute_test_error_svm(test_y, pred_test_y):
    return 1 - np.mean(pred_test_y == test_y)

courses/MITx/MITx 6.86x Machine Learning with Python-From Linear Models to Deep Learning/project2/mnist/part1/svm.py


import numpy as np
from sklearn import linear_model
import sklearn.metrics as sm
from sklearn.preprocessing import PolynomialFeatures

input_file = 'data_multivar_regr.txt'

data = np.loadtxt(input_file, delimiter=',')
X, y = data[:, :-1], data[:, -1]

num_training = int(0.8 * len(X))
num_test = len(X) - num_training

X_train, y_train = X[:num_training], y[:num_training]

X_test, y_test = X[num_training:], y[num_training:]

linear_regressor = linear_model.LinearRegression()

linear_regressor.fit(X_train, y_train)

y_test_pred = linear_regressor.predict(X_test)

print("Linear Regressor performance:")
print("Mean absolute error =", round(sm.mean_absolute_error(y_test, y_test_pred), 2))
print("Mean squared error =", round(sm.mean_squared_error(y_test, y_test_pred), 2))
print("Median absolute error =", round(sm.median_absolute_error(y_test, y_test_pred), 2))
print("Explained variance score =", round(sm.explained_variance_score(y_test, y_test_pred), 2))
print("R2 score =", round(sm.r2_score(y_test, y_test_pred), 2))

polynomial = PolynomialFeatures(degree=10)
X_train_transformed = polynomial.fit_transform(X_train)
datapoint = [[7.75, 6.35, 5.56]]
poly_datapoint = polynomial.fit_transform(datapoint)

poly_linear_model = linear_model.LinearRegression()
poly_linear_model.fit(X_train_transformed, y_train)
print("\nLinear regression:\n", linear_regressor.predict(datapoint))
print("\nPolynomial regression:\n", poly_linear_model.predict(poly_datapoint))


import numpy as np

def ramp_filter(order):
    """
    Coefficients for even ordered FIR ramp filter
    """
    if order % 2 == 1:
        raise ValueError('Order is not an even number.')
    # construct ramp filter in the frequency domain
    fr = np.linspace(0, 1, order / 2 + 1)
    fr = np.concatenate((fr, fr[-2:0:-1]))
    # real part from the response
    # imaginary part due to loss of precision
    fr = np.real(np.fft.ifftshift(np.fft.ifft(fr)))
    # normalise
    return list(fr / np.max(fr))

if __name__ == '__main__':
    print ramp_filter(64)


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

"""
Multiplies two square matrices together using a *single* block of threads and
global memory only. Each thread computes one element of the resulting matrix.
"""

import numpy as np
from pycuda import driver, compiler, gpuarray, tools

# -- initialize the device
import pycuda.autoinit

kernel_code_template = """
__global__ void MatrixMulKernel(float *a, float *b, float *c)
{
    // 2D Thread ID (assuming that only *one* block will be executed)
    int tx = threadIdx.x;
    int ty = threadIdx.y;

    // Pvalue is used to store the element of the matrix
    // that is computed by the thread
    float Pvalue = 0;

    // Each thread loads one row of M and one column of N,
    //   to produce one element of P.
    for (int k = 0; k < %(MATRIX_SIZE)s; ++k) {
        float Aelement = a[ty * %(MATRIX_SIZE)s + k];
        float Belement = b[k * %(MATRIX_SIZE)s + tx];
        Pvalue += Aelement * Belement;
    }

    // Write the matrix to device memory;
    // each thread writes one element
    c[ty * %(MATRIX_SIZE)s + tx] = Pvalue;
}
"""

# define the (square) matrix size
#  note that we'll only use *one* block of threads here
#  as a consequence this number (squared) can't exceed max_threads,
#  see http://documen.tician.de/pycuda/util.html#pycuda.tools.DeviceData
#  for more information on how to get this number for your device
MATRIX_SIZE = 2

# create two random square matrices
a_cpu = np.random.randn(MATRIX_SIZE, MATRIX_SIZE).astype(np.float32)
b_cpu = np.random.randn(MATRIX_SIZE, MATRIX_SIZE).astype(np.float32)

# compute reference on the CPU to verify GPU computation
c_cpu = np.dot(a_cpu, b_cpu)

# transfer host (CPU) memory to device (GPU) memory
a_gpu = gpuarray.to_gpu(a_cpu)
b_gpu = gpuarray.to_gpu(b_cpu)

# create empty gpu array for the result (C = A * B)
c_gpu = gpuarray.empty((MATRIX_SIZE, MATRIX_SIZE), np.float32)

# get the kernel code from the template
# by specifying the constant MATRIX_SIZE
kernel_code = kernel_code_template % {
    'MATRIX_SIZE': MATRIX_SIZE
    }

# compile the kernel code
mod = compiler.SourceModule(kernel_code)

# get the kernel function from the compiled module
matrixmul = mod.get_function("MatrixMulKernel")

# call the kernel on the card
matrixmul(
    # inputs
    a_gpu, b_gpu,
    # output
    c_gpu,
    # (only one) block of MATRIX_SIZE x MATRIX_SIZE threads
    block = (MATRIX_SIZE, MATRIX_SIZE, 1),
    )

# print the results
print("-" * 80)
print("Matrix A (GPU):")
print(a_gpu.get())

print("-" * 80)
print("Matrix B (GPU):")
print(b_gpu.get())

print("-" * 80)
print("Matrix C (GPU):")
print(c_gpu.get())

print("-" * 80)
print("CPU-GPU difference:")
print(c_cpu - c_gpu.get())

np.allclose(c_cpu, c_gpu.get())


"""
Contains various functions for computing statistics over 3D volumes
"""
import numpy as np

def Dice3d(a, b):
    """
    This will compute the Dice Similarity coefficient for two 3-dimensional volumes
    Volumes are expected to be of the same size. We are expecting binary masks -
    0's are treated as background and anything else is counted as data

    Arguments:
        a {Numpy array} -- 3D array with first volume
        b {Numpy array} -- 3D array with second volume

    Returns:
        float
    """
    if len(a.shape) != 3 or len(b.shape) != 3:
        raise Exception(f"Expecting 3 dimensional inputs, got {a.shape} and {b.shape}")

    if a.shape != b.shape:
        raise Exception(f"Expecting inputs of the same shape, got {a.shape} and {b.shape}")

    # Write implementation of Dice3D. If you completed exercises in the lessons
    # you should already have it.

    intersection = np.sum((a > 0) * (b > 0))
    sum_volumes = np.sum(a > 0) + np.sum(b > 0)
    if sum_volumes == 0:
        return -1
    return 2.0 * float(intersection) / float(sum_volumes)

def Jaccard3d(a, b):
    """
    This will compute the Jaccard Similarity coefficient for two 3-dimensional volumes
    Volumes are expected to be of the same size. We are expecting binary masks -
    0's are treated as background and anything else is counted as data

    Arguments:
        a {Numpy array} -- 3D array with first volume
        b {Numpy array} -- 3D array with second volume

    Returns:
        float
    """
    if len(a.shape) != 3 or len(b.shape) != 3:
        raise Exception(f"Expecting 3 dimensional inputs, got {a.shape} and {b.shape}")

    if a.shape != b.shape:
        raise Exception(f"Expecting inputs of the same shape, got {a.shape} and {b.shape}")

    # Write implementation of Jaccard similarity coefficient. Please do not use
    # the Dice3D function from above to do the computation ;)
    intersection = np.sum((a > 0) * (b > 0))
    sum_volumes = np.sum(a > 0) + np.sum(b > 0) - intersection
    if sum_volumes == 0:
        return -1
    return float(intersection) / float(sum_volumes)

def Spec3d(a, b):
    '''
    This will compute the Sensitivity for two 3-dimensional volumes
    Volumes are expected to be of the same size. We are expecting binary masks -
    0's are treated as background and anything else is counted as data

    Arguments:
        a {Numpy array} -- prediction 3D array with first volume
        b {Numpy array} -- ground truth 3D array with second volume

    Returns:
        float
    '''

    if len(a.shape) != 3 or len(b.shape) != 3:
        raise Exception(f"Expecting 3 dimensional inputs, got {a.shape} and {b.shape}")

    if a.shape != b.shape:
        raise Exception(f"Expecting inputs of the same shape, got {a.shape} and {b.shape}")

    tn = float(np.sum((b==0)*(a==0)))
    fp = float(np.sum((b==0)*(a > 0)))

    if (tn + fp) == 0:
        return -1
    return tn / (tn + fp)

def Sens3d(a, b):
    '''
    This will compute the Sensitivity for two 3-dimensional volumes
    Volumes are expected to be of the same size. We are expecting binary masks -
    0's are treated as background and anything else is counted as data

    Arguments:
        a {Numpy array} -- prediction 3D array with first volume
        b {Numpy array} -- ground truth 3D array with second volume

    Returns:
        float
    '''

    if len(a.shape) != 3 or len(b.shape) != 3:
        raise Exception(f"Expecting 3 dimensional inputs, got {a.shape} and {b.shape}")

    if a.shape != b.shape:
        raise Exception(f"Expecting inputs of the same shape, got {a.shape} and {b.shape}")

    tp = float(np.sum((b > 0)*(a > 0)))
    fn = float(np.sum((b > 0)*(a == 0)))

    if (tp + fn) == 0:
        return -1
    return tp / (tp + fn)


import numpy as np

# method for outlier range
def outlierRange(datacolumn):
  #Sort the data in ascending order
  sorted(datacolumn)
  #GET Q1 and Q3
  Q1,Q3 = np.percentile(datacolumn, [25,75])
  #Calc IQR
  IQR = Q3 - Q1
  #Calc LowerRange
  lr = Q1 - (1.5 * IQR)
  #Calc Upper Range
  ur = Q3 + (1.5 * IQR)
  return lr, ur

# method for outlier detection
def outlierPresent(datacolumn):
  '''pass a pandas row, will return true if an outlier is present'''
  lr, ur = outlierRange(datacolumn)
  if (datacolumn.min() < lr) | (datacolumn.max() > ur):
      return True
  else:
      return False


"""
Implementing the exact Heston Scheme by Broadie and Kaya (2003) (WIP)

"""

import numpy as np

def generate_CIR_paths(n_samples, alpha, b, sigma, dt, v_0, T, seed = 2021):
    """
    Implements exact Cox-Ingersoll-Ross / square-root diffusion process
    based on Glasserman (2004)

    Exact simulation of the process V_t with dynamics given by the SDE:

    $$dV_{t} = alpha(b - V_{t}) dt + sigma sqrt(V_{t}) dW_{t}$$

    Inputs:

    + n_samples : Number of Vts to simulate
    + alpha : speed of mean reversion
    + b: mean level of variance
    + sigma: volatility of the variance
    + dt: time-increment, e.g. dt = 1 / 250 years
    + v_0: initial variance
    + T: (integer) number of time steps
    + seed: numpy seed for reproducibility

    Outputs:

    + Vts: A n_samples by (T + 1) numpy array, where in each row corresponds
    to a path V_{i, t} from 0 .. T
    """
    rng = np.random.default_rng(seed)

    Vts = np.zeros((n_samples, T + 1))
    Vts[:,0] = v_0
    d = 4 * b * alpha / sigma ** 2
    c = sigma ** 2  * (1 - np.exp(-alpha * dt)) / (4 * alpha)

    if d > 1:
        Zs = rng.random_normal(size=(n_samples, T + 1))
        chi_sqs = rng.chisquare (df = d - 1, size=(n_samples, T + 1))
    else:
        poissons = np.zeros((n_samples, T + 1))
        chi_sqs = np.zeros((n_samples, T + 1))

    for i in range(n_samples):
        for t in range(T):
            l = Vts[i, t] * np.exp(-alpha * dt) / c
            if d > 1:
                Vts[i, t + 1] = c * ((Zs[i, t] + np.sqrt(l)) ** 2 + chi_sqs[i, t])
            else:
                poissons[i, t] = rng.poisson(lam = l / 2)
                chi_sqs[i, t] = rng.chisquare(df = d + 2 * poissons[i, t])
                Vts[i, t + 1] = c * chi_sqs[i, t]
    return Vts

def generate_Heston_paths(n_samples, S0, rho, r, alpha, b, sigma, dt, v_0, T, seed = 2021):
    """
    Almost Exact Heston scheme for the process with SDE

    $$dS_{t} = rS_{t} dt + S_{t} sqrt(V_{t}) dW_{t}$$

    $$dV_{t} = alpha(b - V_{t}) dt + sigma sqrt(V_{t}) dZ_{t}$$

    $$corr(dW_{t}, dZ_{t}) = rho$$

    Inputs:

    + n_samples : Number of Vtss to simulate
    + S0: Initial Stock Price
    + rho: correlation
    + r: risk-free rate
    + alpha : speed of mean reversion
    + b: mean level of variance
    + sigma: volatility of the variance
    + dt: time-increment, e.g. dt = 1 / 250 years
    + v_0: initial variance
    + T: (integer) number of time steps
    + seed: numpy seed for reproducibility

    Outputs:

    + Vts: A n_samples by T numpy array, where in each row corresponds
    to a path
    """

    rng = np.random.default_rng(seed)
    Zs = rng.standard_normal(size = (n_samples, T)) # random normal increments
    Zs = (Zs - Zs.mean(axis= 0)) / Zs.std(axis = 0)

    # pre-generate Vts
    Vts = generate_CIR_paths(n_samples = n_samples, alpha = alpha,
                             b = b, sigma = sigma,
                             dt = dt, v_0 = v_0, T = T, seed = seed)

    # simulate the log-price process
    log_Sts = np.zeros((n_samples, T + 1))
    log_Sts[:, 0] = np.log(S0)

    k0 = (r - rho / sigma * alpha * b) * dt
    k1 = (rho * alpha / sigma - 0.5) * dt - rho / sigma
    k2 = rho / sigma

    for i in range(T):
        log_Sts[:, i + 1] = (log_Sts[:,i] + k0 + k1 * Vts[:,i] + k2 * Vts[:,i+1] +
                              np.sqrt((1.0 - rho ** 2) * Vts[:,i]) * np.sqrt(dt) * Zs[:, i])

    return np.exp(log_Sts), Vts


import sys, math
import numpy as np
from task import *
from utils import *
import matplotlib.pyplot as plt

def grade1():
	print('='*20 + ' TASK 1 - Preprocessing' + '='*20)
	X = np.arange(6).reshape(3,2).astype(float)
	Y = np.arange(3).reshape(3,1).astype(float)

	try:
		X_stud, Y_stud = preprocess(X, Y)
		X_act, Y_act =  [[ 1., -1.22474487], [ 1., 0.], [ 1.,1.22474487]], [[0.],[1.],[2.]]
		print("Your output :\n", X_stud.shape, Y_stud.shape)

		if np.allclose(X_act, X_stud) and np.allclose(Y_act, Y_stud):
			marks = 1
			print("TASK 1 Passed")
		else:
			marks = 0
			print("Your output : ", X_stud, Y_stud)
			print("Expected output : ", X_act, Y_act)
			print("TASK 1 Failed")
	except KeyError as e:
		marks = 0
		print("RunTimeError in TASK 1 : " + str(e))
		print("TASK 1 Failed")

	return marks

def grade2(trainX, trainY, testX, testY):
	print('='*20 + ' TASK 2 - Ridge Regression' + '='*20)
	try:
		_lambda = 100
		W_stud = ridge_grad_descent(trainX, trainY, _lambda)
		W_act = np.linalg.inv(trainX.T @ trainX + _lambda * np.eye(trainX.shape[1])) @ trainX.T @ trainY
		if np.linalg.norm(W_stud - W_act) < 1:
			marks = 2
			print("TASK 2 Passed")
		else:
			marks = 0
			print("SSE_Stduent", sse(trainX, trainY, W_stud))
			print("SSE_True", sse(trainX, trainY, W_act))
			print("TASK 2 Failed")

	except KeyError as e:
		marks = 0
		print("RunTimeError in TASK 2 : " + str(e))
		print("TASK 2 Failed")

	return marks

def grade3():
	print('='*20 + ' TASK 3 - K Fold Cross Validation' + '='*20)
	X = np.arange(60).reshape(20,3).astype(float)
	Y = np.arange(20).reshape(20,1).astype(float)
	lambdas = [1,2]
	def dummy(X, Y, _lambda):
		return np.linalg.inv(X.T @ X + _lambda * np.eye(X.shape[1])) @ X.T @ Y
	try:
		scores_stud = k_fold_cross_validation(X, Y, 2, lambdas, dummy)
		scores_act  = [0.12615645, 0.24206764]

		if np.allclose(scores_act, scores_stud):
			marks = 1
			print("TASK 3 Passed")
		else:
			marks = 0
			print("Your output : ", scores_stud)
			print("Expected output : ", scores_act)
			print("TASK 3 Failed")
	except KeyError as e:
		marks = 0
		print("RunTimeError in TASK 3 : " + str(e))
		print("TASK 3 Failed")

	return marks

def grade4(trainX, trainY, testX, testY):
	print('='*20 + ' TASK 4 - Lasso Regression' + '='*20)
	try:
		_lambda = 10000
		W_stud = coord_grad_descent(trainX, trainY, _lambda)
		rsse_stud = math.sqrt(sse(trainX, trainY, W_stud))
		rsse_act = 731206
		if rsse_stud - rsse_act < 1e3 and sum(W_stud == 0) > 30:
			marks = 2.5
			print("TASK 4 Passed")
		else:
			marks = 0
			print("Your output : ", rsse_stud)
			print("Expected output : ", rsse_act)
			print("TASK 4 Failed")

	except KeyError as e:
		marks = 0
		print("RunTimeError in TASK 4 : " + str(e))
		print("TASK 4 Failed")

	return marks

if __name__ == "__main__":
	X, Y = read_data("./dataset/train.csv")
	X, Y = preprocess(X, Y)
	trainX, trainY, testX, testY = separate_data(X, Y)

	if len(sys.argv) < 2:
		print('usage:\npython autograder.py [task-number]\npython autograder.py all')
		sys.exit(1)

	if sys.argv[1].lower() == 'all':
		m1 = grade1()
		m2 = grade2(trainX, trainY, testX, testY)
		m3 = grade3()
		m4 = grade4(trainX, trainY, testX, testY)
		print('='*48 + '\nFINAL GRADE: {}/6.5\n\n'.format(m1 + m2 + m3 + m4))
	elif int(sys.argv[1]) == 1:
		m1 = grade1()
	elif int(sys.argv[1]) == 2:
		m2 = grade2(trainX, trainY, testX, testY)
	elif int(sys.argv[1]) == 3:
		m3 = grade3()
	elif int(sys.argv[1]) == 4:
		m4 = grade4(trainX, trainY, testX, testY)


# -*- coding: utf-8 -*-
"""
Created on Wed Sep 18 09:59:36 2019

@author: BSWOOD9321
"""
import numpy as np
import matplotlib.pyplot as plt

x = np.arange(0,10,.01)
y = [0]
i=1
h=.01

while i <=999:
    func = x[i]**2
    y.append(func)
    i=i+1

Summation = h*np.sum(y)
print("---------------------------")
print("Function: x^2, from 0-10")
print("Limit integration: ",Summation)

Trapzsum = np.trapz(x**2,dx=.01)

print("Trapz integration: ",Trapzsum)

import scipy.integrate as ig
f = lambda x: x**2
ig.quad(f,0,10)
print("SciPy Integrate (Quadratic): ",ig.quad(f,0,10))

Simps=ig.simps(x**2,x,.01)
print("SciPy Integrate (Simpsons Rule): ",Simps)

print("The reason our summation formula is not very precise is that it is creating boxes of height x that are then added together to find the value of the integral." )
print("This means the width of the box is a finite value, instead in infinitessimal, and may cross over the function, giving us a value that is not very precise.")
print("-------------------------------------")
print("Function sin(100x) from 0-2Pi")

x1=np.arange(0,2*np.pi,np.pi/500)
y1=[]
i1=0
h1=(np.pi/500)
while i1<=999:
    func1=np.sin(100*x1[i1])
    y1.append(func1)
    i1=i1+1

Sum1=h1*np.sum(y1)
print("Limit Integration: ",Sum1)
Trapzsum1= np.trapz(np.sin(100*x1),dx=(np.pi/500))
print("Trapz Integration: ",Trapzsum1)
f1 = lambda x1: np.sin(100*x1)
print("SciPy Integrate (Quadratic): ",ig.quad(f1,0,np.pi*2))
print("SciPy Integrate (Simpsons Rule): ",ig.simps(np.sin(100*x1),x,np.pi/500))
print("--------------------------------------")

x2=np.arange(0,2*np.pi,np.pi/500)
y2=[]
i2=0
h2=(np.pi/500)
while i2<=999:
    a=x2[i2]
    func2=(np.sin(100*a))**a
    y2.append(func2)

    i2=i2+1

Sum2=h2*np.sum(np.nan_to_num(y2))
print("sin^x(100x)")
print("Limit Integration: ",Sum2)
print("This is the only type of integration I have been able to do, as I keep getting NaN and double scalar errors")
print("-------------------------------")


import os
import numpy as np
import matplotlib.pyplot as plt
from scipy.io import loadmat
from svm import SVM

#  Exercise 6 | Support Vector Machines

def linearKernel(x1, x2):
    return x1.T @ x2

def gaussianKernel(x1, x2, sigma):
    diff = x1 - x2
    return np.exp(-(diff.T @ diff)/(2*sigma*sigma))

def plotData(x, y):
    pos = np.where(y==1)
    neg = np.where(y==0)
    plt.plot(x[pos[0],0], x[pos[0],1], 'b+', lw = 1.5, ms = 4)
    plt.plot(x[neg[0],0], x[neg[0],1], 'ko', mfc = 'y', ms = 4)

def visualizeBoundaryLinear(X, y, model):
    w = model.w
    b = model.b
    xp = np.linspace(min(X[:,0]), max(X[:,0]), 100)
    yp = - (w[0] * xp + b)/w[1]
    plotData(X, y)
    plt.plot(xp, yp, '-b')

def visualizeBoundary(X, y, model):
    #VISUALIZEBOUNDARY plots a non-linear decision boundary learned by the SVM

    # Plot the training data on top of the boundary
    plotData(X, y)

    # Make classification predictions over a grid of values
    x1plot = np.linspace(min(X[:,0]), max(X[:,0]), 100)
    x2plot = np.linspace(min(X[:,1]), max(X[:,1]), 100)
    X1, X2 = np.meshgrid(x1plot, x2plot)
    vals = np.zeros(X1.shape)
    for i in range(X1.shape[1]):
        this_X = np.c_[X1[:, i], X2[:, i]]
        vals[:, i] = model.svmPredict(this_X)

    # Plot the SVM boundary
    plt.contour(X1, X2, vals, [0.5])

def dataset3Params(X, y, Xval, yval):
    Cs = np.array([0.01, 0.03, 0.1, 0.3, 1, 3, 10, 30])
    sigmas = Cs
    model = SVM()

    costs = np.zeros((Cs.size, sigmas.size))
    for i in range(Cs.size):
        for j in range(sigmas.size):
            sigma = sigmas[j]
            #need to define gaussianKernelLambda lambda here again to capture sigma
            gaussianKernelLambda = lambda x1, x2: (gaussianKernel(x1, x2, sigma))
            gaussianKernelLambda.__name__ = 'gaussianKernel'

            model.svmTrain(X, y.astype(float), Cs[i], gaussianKernelLambda)
            predictions = model.svmPredict(Xval)
            costs[i, j] = np.mean(predictions != yval)
    i, j = np.unravel_index(np.argmin(costs), costs.shape)
    return (Cs[i], sigmas[j])

## =============== Part 1: Loading and Visualizing Data ================
print('Loading and Visualizing Data ...')

# Load from ex6data1:
# You will have X, y in your environment
scriptdir = os.path.dirname(os.path.realpath(__file__))
data = loadmat(scriptdir + '//ex6data1.mat')
X = data['X']
y = data['y'].ravel()
# Plot training data
plotData(X, y)
plt.show()

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

## ==================== Part 2: Training Linear SVM ====================
#  The following code will train a linear SVM on the dataset and plot the
#  decision boundary learned.

print('\nTraining Linear SVM ...')

# You should try to change the C value below and see how the decision
# boundary varies (e.g., try C = 1000)
C = 1
model = SVM()
#we passed y.astype(float), firstly to be sure y -1 can be assigned to singed int.
#secondly we want to preserve original y to 0 and 1
model.svmTrain(X, y.astype(float), C, linearKernel, 1e-3, 20)

visualizeBoundaryLinear(X, y, model)
plt.show()
input('Program paused. Press enter to continue.\n')

## =============== Part 3: Implementing Gaussian Kernel ===============

print('\nEvaluating the Gaussian Kernel ...')

x1 = np.array([1, 2, 1])
x2 = np.array([0, 4, -1])
sigma = 2

#use lambda to be able to pass sigma
gaussianKernelLambda = lambda x1, x2: (gaussianKernel(x1, x2, sigma))
gaussianKernelLambda.__name__ = 'gaussianKernel'

sim = gaussianKernelLambda(x1, x2)

print(f'Gaussian Kernel between x1 = [1c 2, 1], x2 = [0, 4, -1], sigma = {sigma} :')
print(f'\t{sim}\n(for sigma = 2, this value should be about 0.324652)\n')

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

## =============== Part 4: Visualizing Dataset 2 ================

print('Loading and Visualizing Data ...')

# Load from ex6data2:
# You will have X, y in your environment
data = loadmat(scriptdir + '//ex6data2.mat')
X = data['X']
y = data['y'].ravel()

# Plot training data
plotData(X, y)
plt.show()

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

## ========== Part 5: Training SVM with RBF Kernel (Dataset 2) ==========

print('\nTraining SVM with RBF Kernel (this may take 1 to 2 minutes) ...')

# SVM Parameters
C = 1; sigma = 0.1

# We set the tolerance and max_passes lower here so that the code will run
# faster. However, in practi ce, you will want to run the training to
# convergence.
model.svmTrain(X, y.astype(float), C, gaussianKernelLambda)
visualizeBoundary(X, y, model)
plt.show()
input('Program paused. Press enter to continue.')

## =============== Part 6: Visualizing Dataset 3 ================

print('Loading and Visualizing Data ...')

# Load from ex6data3:
# You will have X, y in your environment
data = loadmat(scriptdir + '//ex6data3.mat')

X = data['X']
y = data['y'].ravel()
Xval = data['Xval']
yval = data['yval'].ravel()

# Plot training data
plotData(X, y)
plt.show()

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

## ========== Part 7: Training SVM with RBF Kernel (Dataset 3) ==========

#  This is a different dataset that you can use to experiment with. Try
#  different values of C and sigma here.

# Try different SVM Parameters here
C, sigma = dataset3Params(X, y, Xval, yval)
# Train the SVM
model.svmTrain(X, y.astype(float), C, gaussianKernelLambda)
visualizeBoundary(X, y, model)
plt.show()
input('Program paused. Press enter to continue.\n')


import numpy as np

def qr_householder(A):
    """
    Dekomposisi QR menguraikan matriks A berbentuk (m, n) menjadi an
    matriks ortogonal Q bentuk (m, m) dan matriks segitiga atas R dari
    bentuk (m, n). Perhatikan bahwa matriks A tidak harus persegi. Ini
    metode penguraian A menggunakan refleksi Householder, yaitu
    stabil secara numerik dan kompleksitas O(n^3).

    informasi lebih lanjut
    https://en.wikipedia.org/wiki/QR_decomposition#Using_Householder_reflections

    >>> A = np.array([[12, -51, 4], [6, 167, -68], [-5, 24, -14]], dtype=float)
    >>> Q, R = qr_householder(A)

    >>> # cek apabila dekomposisi benar
    >>> np.allclose(Q@R, A)
    True
    """
    m, n = A.shape
    t = min(m, n)
    Q = np.eye(m)
    R = A.copy()

    for k in range(t - 1):
        x = R[k:, [k]]
        e1 = np.zeros_like(x)
        e1[0] = 1.0
        alpha = np.linalg.norm(x)
        v = x + np.sign(x[0]) * alpha * e1
        v /= np.linalg.norm(v)

        Q_k = np.eye(m - k) - 2.0 * v @ v.T
        Q_k = np.block([[np.eye(k), np.zeros((k, m - k))], [np.zeros((m - k, k)), Q_k]])

        Q = Q @ Q_k.T
        R = Q_k @ R

    return Q, R

if __name__ == "__main__":
    import doctest

    doctest.testmod()


import numpy as np
import scipy.linalg as spla

def gen_diag_dom(n, a, b):
    """ Generates a diagonally dominant random matrix"""
    m = (b-a) * np.random.rand(n,n) + a
    for i in range(n):
        tmp = np.sum(np.abs(m[i,:]))
        m[i,i] = (tmp-(10*tmp)) * np.random.rand() + tmp
    return m
def gen_tridiag(n, a, b):
    m = np.zeros((n, n))
    m[0, 0:2] = (b-a) * np.random.rand(2) + a
    m[n-1, n-2:n] = (b-a) * np.random.rand(2) + a
    for i in range(1, n-1):
        m[i, i-1:i+2] = (b-a)* np.random.rand(3) + a
    return m

def gauss(a, b, allow_overwrite=False):
    """
        Solves  ax=b using gaussian elimation and returns x.

        Parameters:
        a: (n,n) array
        b: (n)  array
        allow_overwrite: Allow the code to overwrite the source matrices
    """
    n = a.shape[0]
    x = np.empty(n)
    pvtv = np.arange(0, n, 1)
    if allow_overwrite:
        a1 = a
        b1 = b
    else:
        a1 = np.copy(a)
        b1 = np.copy(b)
    s = np.amax(abs(a1), axis=1)
    #print('Maximum of each row is {0}'.format(s))
    #For each column
    for i in range(n-1):
        #print('Before elimination our matrix looks like')
        #print(a1)
        gpivot(a1, pvtv, i, s)
        #Check for division by 0
        if np.isclose(a[pvtv[i]/s[pvtv[i]], i], 0):
            #print('Coefficient a[{0}, {1}] with value {2} is too close to 0'.format(pvtv[i], i, a[pvtv[i], i]))
            raise ZeroDivisionError

        geliminate(a1, b1, pvtv, i)
        #print('After elimination our matrix looks like')
        #print(a1)
    #print('Our matrix now looks like')
    #print(a1)
    #print('Starting the substitution process')
    gsubstitute(a1, b1, pvtv, x, n-1)
    return x

def gpivot(a, pvtv, ind, s):
    #pvtv[ind] is the original row
    p = pvtv[ind]
    v = abs(a[p, ind])/s[pvtv[ind]]
    #print('The current pivot element is {0} on row {1}'.format(v, p))
    #print('Searching for a new pivot in rows {0} while excluding {1}'.format(pvtv[ind+1:], pvtv[:ind]))
    #i is the actual index in the pivot vector
    for i in range(ind+1, len(pvtv)):
        vv = abs(a[pvtv[i], ind])/s[pvtv[i]]
        #print('Comparing {0} and {1} for a new pivot'.format(vv, v))
        if vv > v:
            #print('Found a new potential pivot at row {0}'.format(pvtv[i]))
            v = vv
            p = i
    if p != pvtv[ind]:
        #print('The new pivot is row {0} and the old was {1}'.format(pvtv[p], pvtv[ind]))
        tmp = pvtv[ind]
        pvtv[ind] = pvtv[p]
        pvtv[p] = tmp
    #print('The pivot vector looks like: {0}'.format(pvtv))

def geliminate(a, b, pvtv, ind):
    for i in pvtv[ind+1:]:
        #print('Eliminating row {0} with row {1}'.format(i, pvtv[ind]))
        c = a[i, ind]/a[pvtv[ind], ind]
        a[i, ind:] -= c*a[pvtv[ind], ind:]
        b[i] -= c*b[pvtv[ind]]

def lueliminate(a, pvtv, ind):
    for i in pvtv[ind+1:]:
        #print('Eliminating row {0} with row {1}'.format(i, pvtv[ind]))
        c = a[i, ind]/a[pvtv[ind], ind]
        a[i, ind:] -= c*a[pvtv[ind], ind:]
        # print('Putting the value of c ({3}) at index a[{0},{1}] the current value is {2}'.format(i, ind, a[i,ind], c))
        a[i, ind] = c
        # print('Normally we would perform b[{0}] -= c*b[{1}]'.format(i, pvtv[ind]))

def gsubstitute(a, b, pvtv, x, n):
    x[n] = b[pvtv[n]]/a[pvtv[n], n]
    #print('The first x value is {0}'.format(x[n]))
    for i in range(n-1, -1, -1):
        #print("We're on column {0} and we're starting on row {1}".format(i, pvtv[i]))
        #print('Starting with b = {0}'.format(b[i]))
        acc = b[pvtv[i]]
        for j in range(n , i, -1):
            acc -= a[pvtv[i], j] * x[j]
        x[i] = acc/a[pvtv[i], i]
def lu_factor(a, allow_overwrite=False):
    """
        Factors a such that the returned matrix and pvtv table can be passed to lu_solve to solve for a given
        b vector.

        Parameters:
        a: (n,n) array
        allow_overwrite: Allow the code to overwrite the source matrices
    """
    n = a.shape[0]
    pvtv = np.arange(0, n, 1)
    if allow_overwrite:
        a1 = a
    else:
        a1 = np.copy(a)
    s = np.amax(abs(a1), axis=1)
    #print('Maximum of each row is {0}'.format(s))
    #For each column
    for i in range(n-1):
        #print('Before elimination our matrix looks like')
        #print(a1)
        gpivot(a1, pvtv, i, s)
        #Check for division by 0
        if np.isclose(a[pvtv[i]/s[pvtv[i]], i], 0):
            #print('Coefficient a[{0}, {1}] with value {2} is too close to 0'.format(pvtv[i], i, a[pvtv[i], i]))
            raise ZeroDivisionError

        lueliminate(a1, pvtv, i)
        #print('After elimination our matrix looks like')
        #print(a1)
    #print('Our matrix now looks like')
    #print(a1)
    #print('Starting the substitution process')
    return (a1,pvtv)
def lu_solve(a, b, pvtv, allow_overwrite=False):
        n = a.shape[0]
        x = np.empty(n)
        if allow_overwrite:
            a1 = a
            b1 = b
        else:
            a1 = np.copy(a)
            b1 = np.copy(b)
        #Eliminate the b's as we would have before
        for i in range(n-1):
            #i is our current column
            for j in range(i+1, n):
                # print('Grabbing c value from a[{0},{1}]'.format(pvtv[j], i))
                # print('Performing b[{0}] - c*b[{1}] with c = {2}]'.format(pvtv[j], pvtv[i], a[pvtv[j], i]))
                b1[pvtv[j]] -= a1[pvtv[j], i] * b1[pvtv[i]]
        # print('The b1 vector before after forward looks like {0}'.format(b1))
        #Now back substitute
        gsubstitute(a1, b1, pvtv, x, n-1)
        return x
def inv(a):
    """ Returns the inverse of the matrix a"""
    aa, pvt = lu_factor(a)
    n = a.shape[0]
    ai = np.empty(a.shape)
    for i in range(n):
        b = np.zeros(n)
        b[i] = 1
        ai[:,i] = lu_solve(aa, b, pvt)
    return ai

def gauss_seidel(A, b, x, tol, maxi, lam):
    """ x should contain initial guesses (can be 0)"""
    dim = A.shape[0]
    #Divide everything by each row by its diagnol element
    for i in range(dim):
        tmp = A[i,i]
        for j in range(dim):
            A[i,j] /= tmp
        b[i] /= tmp
        # print(A)
    for i in range(dim):
        acc = b[i]
        for j in range(dim):
            if i == j:
                # print("Skipping i = {0} and j = {1}".format(i, j))
                continue
            else:
                acc -= A[i, j] * x[j]
        # print("Old x = {0}, new x = {1}".format(x[i], acc))
        x[i] = acc
    for i in range(maxi):
        flag = 1
        for k in range(dim):
            acc = b[k]
            oldx = x[k]
            for j in range(dim):
                if k == j:
                    continue
                else:
                    # print('k = {0}, j={1}'.format(k, j))
                    acc -= (A[k,j] * x[j])
                    # print(acc)
            # print("Old x = {0}, new x = {1}".format(oldx, (lam * acc) + ((1-lam) * oldx)))
            x[k] = (lam * acc) + ((1-lam) * oldx)
            if flag ==1 and x[k] != 0:
                ea = abs((x[k] - oldx)/x[k]) * 100
                # print("Error is equal to {0}".format(ea))
                if ea > tol:
                    flag = 0
        if flag == 1:
            print('Breaking with ea = {0} and num iterations: {1}'.format(ea, i))
            break
    return x
def thomas_factor(a, allow_overwrite=False):
    """ Returns a LU matrix for a tridiagnol system"""
    n = a.shape[0]
    if allow_overwrite:
        a1 = a
    else:
        a1 = np.copy(a)
    for i in range(1,n):
        a1[i,i-1] /= a1[i-1, i-1]
        a1[i,i] -= a1[i,i-1] * a1[i-1, i]
    return a1
def thomas_solve(a, b, allow_overwrite=False):
    """Solves the factored matrix from thomas_factor for  given b vector"""
    n = a.shape[0]
    x = np.empty(n)
    if allow_overwrite:
        a1 = a
        b1 = b
    else:
        a1 = np.copy(a)
        b1 = np.copy(b)
    #Forward substitute
    for i in range(1,n):
        b1[i] -= a1[i, i-1] * b1[i-1]
    x[n-1] = b1[n-1]/a1[n-1, n-1]
    for i in range(n-2, -1, -1):
        x[i] = (b1[i]-a[i, i+1] * x[i+1])/ a1[i, i]
    return x
if __name__ == "__main__":
    n = 40
    # m = (-200) * np.random.rand(3,3) + 100
    # mm = (-200) * np.random.rand(3) + 100
    # #print("The answers are {0}".format(mm))
    # x1 = spla.solve(m,mm)
    # x2 = gauss(m,mm)
    # aa, pvt = lu_factor(m)
    # x3 = lu_solve(aa, mm, pvt)
    # print("x vector according to scipy is {0}".format(x1))
    # print("x vector according to my gauss is {0}".format(x2))
    # print('x vector according my lu is {0}'.format(x3))
    # print('Scipy gauss is close to my gauss? {0}'.format(np.isclose(x1,x2)))
    # print('Scipy gauss is close to my LU? {0}'.format(np.isclose(x1,x3)))
    a1 = gen_diag_dom(n,-100,100)
    b1 = (-200) * np.random.rand(n) + 100
    # ai = inv(m)
    # print('A * A^-1 = \\n{0}'.format(np.dot(m, ai)))
    x1 = spla.solve(a1,b1)
    x2 = gauss(a1,b1)
    aa, pvt = lu_factor(a1)
    x3 = lu_solve(aa, b1, pvt)
    x4 = gauss_seidel(a1,b1, np.zeros(n), 0.0001, 25, 1)
    # print("x vector according to scipy is {0}".format(x1))
    # print("x vector according to my gauss is {0}".format(x2))
    # print('x vector according my lu is {0}'.format(x3))
    # print('x vector according to my GS is {0}'.format(x4))
    a2 = gen_tridiag(n, -100, 100)
    print(a2)
    aa2 = thomas_factor(a2)
    print(aa2)
    x5 = thomas_solve(aa2, b1)
    x6 = spla.solve(a2,b1)
    print("x vector according to scipy is {0}".format(x6))
    print('The results of the thomas algorithm are: {0}'.format(x5))


#!/usr/bin/env python

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

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

def angle_between(v1, v2):
    """Finds angle between two vectors"""
    v1_u = unit_vector(v1)
    v2_u = unit_vector(v2)
    return np.arccos(np.clip(np.dot(v1_u, v2_u), -1.0, 1.0))

def x_rotation(vector,theta):
    """Rotates 3-D vector around x-axis"""
    R = np.array([[1,0,0],[0,np.cos(theta),-np.sin(theta)],[0, np.sin(theta), np.cos(theta)]])
    return np.dot(R,vector)

def y_rotation(vector,theta):
    """Rotates 3-D vector around y-axis"""
    R = np.array([[np.cos(theta),0,np.sin(theta)],[0,1,0],[-np.sin(theta), 0, np.cos(theta)]])
    return np.dot(R,vector)

def z_rotation(vector,theta):
    """Rotates 3-D vector around z-axis"""
    R = np.array([[np.cos(theta), -np.sin(theta),0],[np.sin(theta), np.cos(theta),0],[0,0,1]])
    return np.dot(R,vector)

def translation(vector, xyz):
    return vector + np.array(xyz)

if __name__=="__main__":
    vector = np.array([1,0,0])

    print z_rotation(vector, np.pi/4)

    print translation(vector, [1,1,1])


"""
This method creates a single instance of the FlatLambdaCDM in astropy and uses it to
evolve magnitudes with redshift (via the luminosity distance relation).

FlatLambdaCDM model with constraints of H0 = 67.6, Om0 = 0.3089 (values from SDSS BOSS, published 2016-07-13 and
        1-Omega_Lambda = 1 - 0.6911 = 0.3089 from the Planck Collaboration in 2015)
"""
from math import log10
from typing import List, Tuple, Union

from astropy import units
from astropy.cosmology import FlatLambdaCDM

__cosmo = FlatLambdaCDM( H0=67.6, Om0=0.3089 )

def luminsoity_distance_from_redshift( z: float ) -> float:
    """
    Calculates the luminosity distance (in parsecs) at redshift z using the Flat Lambda CDM

    :param z: Redshift at which to calculate luminosity distance
    :type z: float
    :return: Luminosity distance at redshift z in terms of parsecs
    :rtype: float
    """
    return __cosmo.luminosity_distance( z ).to( units.parsec ).value

def magnitude_evolution( m0: float, z0: float, zrange: tuple = (0.46, 0.82), step: float = 0.01,
                         splitLists: bool = False ) -> Union[ List[
                                                                  Tuple[ float, float, float ] ], Tuple[
                                                                  tuple, tuple, tuple ] ]:
    """
    Generates the apparent magnitude evolution of a spectrum given its magnitude m0 at redshift z0.  Default zrange = ( 0.46, 0.82 ), step size of 0.01.

    Returns a list from lowest to highest redshift of tuples ( z, apparent magnitude at z, luminosity distance )

    If splitList is True, returns a single tuple of three lists ( ( z ), ( magnitude ), ( ld ) )

    :param m0: Initial apparent magnitude
    :param z0: Initial redshift at which apparent magnitude was observed
    :param zrange: Range of redshifts to calculate evolution over.  Defaults to ( 0.46, 0.82 ).
        Will run loop from low value to high value + step such that the high value is included
    :param step: step size to iterate over zrange.  Defaults to 0.01
    :param splitLists: If True, returns a single tuple of ( ( z_data ), ( magnitude_data ), ( luminosity_distance ) ) lists
    :type m0: float
    :type z0: float
    :type zrange: tuple
    :type step: float
    :type splitLists: bool
    :return: List of tuples [ ( redshift, apparent_magnitude at z, luminosity_distance), ... ] or Tuple of tuples ( ( z ), ( mag ), ( ld ) )
    :rtype: list or tuple
    """
    from numpy import arange
    zlow, zhigh = zrange

    M_init = m0 - 5 * (log10( luminsoity_distance_from_redshift( z0 ) ) - 1)

    evolist = list()
    for z in arange( zlow, zhigh + step, step ):
        dL = luminsoity_distance_from_redshift( z )
        m = M_init + 5 * (log10( dL ) - 1)

        evolist.append( (z, m, dL) )
    if splitLists:
        return tuple( zip( *evolist ) )
    return evolist

def absolute_magnitude( m0, z0 ):
    """
    Returns absolute magnitude of a spectrum given apparent magnitude m0 at redshift z0
    via the relation

    M_abs = m_app - 5 * log10( Luminosity Distance - 1 )

    :param m0: Apparent magnitude
    :param z0: Redshift
    :type m0: float
    :type z0: float
    :return: Absolute magnitude
    :rtype: float
    """
    return m0 - 5 * (log10( luminsoity_distance_from_redshift( z0 ) ) - 1)

def apparent_magnitude_at_redshift( m0, z0, z ):
    """
    Given a known apparent magnitude m0 and redshift z0, returns the apparent magnitude of an object if
    it were moved to redshift z using the Flat Lambda CDM.

    :param m0: Known apparent magnitude
    :param z0: Known redshift
    :param z: Redshift at which apparent magnitude is desired
    :type m0: float
    :type z0: float
    :type z: float
    :return: Apparent magnetude at redshift z
    :rtype: float
    """
    return absolute_magnitude( m0, z0 ) + 5 * (log10( luminsoity_distance_from_redshift( z ) ) - 1)


# -*- coding: utf-8 -*-
"""
Created on Sat Sep 18 02:37:53 2021

@author: Mahfuz_Shazol
"""

import numpy as np

A=np.array([
    [-1,4],
    [2,-2]
    ])

lambdas,V=np.linalg.eig(A)
print('lambdas',lambdas)

#each column is separate eivactor V
print('V',V)

#A**v==Lamda**v

v=V[:,0]
print(v)
Lamda=lambdas[0]

r1=np.dot(A,v)
print(r1)

r2=np.dot(Lamda,v)
print(r2)


import math
import numpy as np

a = [ [-7,2,-3,4,-12],
        [5,-1,14,-1,13],
        [1,9,-7,13,31],
        [-12,13,-8,-4,-32]
        ]

n = len(a)
marks = [i for i in range(0,n)]

def changeRows(majorRow,k):
    for i in range(0,len(a)+1):
        aux = a[k][i]
        a[k][i] = a[majorRow][i]
        a[majorRow][i]= aux

def changeColumns(majorColumn,k):
    aux = marks[k]
    marks[k] = marks[majorColumn]
    marks[majorColumn] = aux
    for j in range(0,len(a)):
        aux = a[j][k]
        a[j][k] = a[j][majorColumn]
        a[j][majorColumn] = aux

def totalPivoting(k):
    major = 0
    majorRow = k
    majorColumn = k
    for r in range(k,n):
        for s in range (k,n):
            if (math.fabs(a[r][s]) > major):
                major = math.fabs(a[r][s])
                majorRow = r
                majorColumn = s

    if(major == 0):
        print("division 0")
        return;
    else:
        if(majorRow != k):
            changeRows(majorRow,k)
        if(majorColumn != k):
            changeColumns(majorColumn,k)

def elimination():
    for k  in range(0,n-1):
        totalPivoting(k)
        for i  in range (k + 1, n):
            multiplicator = a[i][k]/a[k][k]
            for j in range (k,n + 1):
                a[i][j] = a[i][j]-(multiplicator*a[k][j])

    sustitution()

def sustitution():
    n = len(a) -1
    x = [i for i in range(n+1)]
    x[len(x)-1] = a[n][n+1]/a[n][n]
    for i in range(0,n+1):
        summation = 0
        auxi = n - i
        summation = 0
        for p in range(auxi+1,n+1):
            summation = summation + a[auxi][p]*x[p]
        x[auxi]=(a[auxi][n+1]-summation)/a[auxi][auxi]
    marks.reverse()
    x.reverse()
    for i in range(0,len(x)):
        print("X",marks[i]," =",x[i])

elimination()
for i in a:
    print(i)


import yfinance as yf
import numpy as np

start_date = '2015-01-01'
end_date = '2021-12-31'
trading_days = 252
stock_data = yf.download('GOOGL', start_date, end_date)

################    CAGR Required    ############

def cagr(data):
    df = data.copy()
    df["daily_return"] = df["Adj Close"].pct_change()
    df["cum_return"] = (1 + df["daily_return"]).cumprod()

    n = len(df) / trading_days
    this_cagr = (df["cum_return"][-1] ** (1 / n)) - 1
    return this_cagr

print("CAGR: " + str(cagr(stock_data) * 100) + '%')
##################################################

##################    CALMAR    ##################
def max_drawdown(data):
    df = data.copy()
    df["daily_return"] = df["Adj Close"].pct_change()
    df["cum_return"] = (1 + df["daily_return"]).cumprod()
    df['cum_max'] = df['cum_return'].cummax()
    df['drawdown'] = df['cum_max'] - df['cum_return']
    df['drawdown_pct'] = df['drawdown'] / df['cum_max']
    max_dd = df['drawdown_pct'].max()
    return max_dd

def calmar_ratio(data, rf):
    df = data.copy()
    calmar = cagr(df) / max_drawdown(df)
    return calmar

print("Calmar Ratio: " + str(calmar_ratio(stock_data, 0.03)))


import numpy as np

def n_bar(wf):
	n = np.arange(1,len(wf.count)+1)
	sum_yn = np.sum(n*wf.count)
	return sum_yn / np.sum(wf.count)

def moment(wf, m):
	n_minus_nbar = wf.count - n_bar(wf)

	return np.sum(wf.count * n_minus_nbar**m) / np.sum(wf.count)

def skewness(wf):
	return moment(wf,3) / moment(wf,2)**1.5

def kurtosis(wf):
	return moment(wf,4) / moment(wf,2)**2

def mode(wf):
	peak = np.where(wf.count == wf.count.max())[0][-1]
	return wf.height[peak]

def mean(wf):
	n = np.arange(1,len(wf.count)+1)
	return np.sum(n*wf.count) / np.sum(wf.count)

def sum_squares(wf):
	n = np.arange(1,len(wf.count)+1)
	return np.sum(n**2 * wf.count)

def variance(wf):
	n = np.arange(1,len(wf.count)+1)

	mean_square = sum_squares(wf) / np.sum(wf.count)
	return mean_square - mean(wf)**2

def stdev(wf):
	return np.sqrt(variance(wf))

def median(wf):
	n_halves = np.sum(wf.count) / 2
	sub_sums = [np.sum(wf.count[:i]) for i in range(len(wf.count))]
	i = np.where(sub_sums > n_halves)[0][0]

	return i + (n_halves - sub_sums[i-1]) / wf.count[i]

def pearson_skewness(wf, n):
	if n == 1:
		return (mean(wf) - mode(wf)) / stdev(wf)
	elif n == 2:
		return 3 * (mean(wf) - median(wf)) / stdev(wf)
	else:
		raise ValueError("Pearson Skew Coefficient must be 1 or 2 (i.e. n=1 or n=2)")

def AUC(wf):
	return np.trapz(wf.count)


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

# from http://lotabout.me/2018/Logistic-Regression-Notes/

import numpy as np
import matplotlib.pyplot as plt

samples = [(-5, 1), (-20, 0), (-2, 1)]

def sigmoid(theta, x):
    sigmoid_value = 1.0 / (1 + np.exp(- theta * x))
    # print('sigmoid theta=%s x=%s result=%s' % (theta, x, sigmoid_value))
    return sigmoid_value

def cost(theta):
    diffs = [(sigmoid(theta, x) - y) for x, y in samples]
    return sum(diff * diff for diff in diffs) / len(samples) / 2

def log_loss(theta, x, y):
    tmp_sigmoid = sigmoid(theta, x)
    first = -y * np.log(tmp_sigmoid)
    second = (1 - y) * np.log(1 - tmp_sigmoid)
    # print('1-y=%s np_log 1 - s = %s' % (1 - y, np.log(1 - tmp_sigmoid)))
    # print('log loss theta=%s second=%s' % (theta, second))
    return first - second

def cost2(theta):
    diffs = [log_loss(theta, x, y) for x, y in samples]
    # print(diffs)
    return sum(diff for diff in diffs) / len(samples)

X = np.arange(-1, 1, 0.01)
Y = np.array([cost(theta) for theta in X])
plt.plot(X, Y)
plt.show()


import numpy as np
import pandas as pd
import math

def sigmoid(inX) :
    return 1.0/(1+ math.exp(-inX))

def P1(beta, x) :
    return math.exp(np.dot(beta.T, x))/(1 + math.exp(np.dot(beta.T, x)))

def P2(beta, x) :
    return 1/(1 + math.exp(np.dot(beta.T, x)))

def fx(X, Y, beta) :
    sum = 0
    for i in range(len(Y)) :
        Xi = X[:,i]
        Xi.shape = (len(X),1)
        sum += np.dot(Xi,(Y.loc[i] - P1(beta,Xi)))
    return -sum

def dx(X,Y, beta) :
    sum = 0
    for i in range(len(Y)) :
        Xi = X[:,i]
        Xi.shape = (len(X),1)

        Xx = np.dot(Xi, Xi.T)
        Xxp = np.dot(Xx, P1(beta, Xi))
        dx1 = np.dot(Xxp,(1-P1(beta, Xi)))
        sum += dx1
    return sum

filename = './data/data_Ernest/trainDataCleanByTed.csv'
testfilename = './data/data_Ernest/testDataCleanByTed.csv'
dataSet = pd.read_csv(filename)
testDataSet = pd.read_csv(testfilename)

X = dataSet.drop('Attrition', axis=1)
addOne = np.ones((len(X),1))
X = np.c_[X,addOne]

X = X.T
Y = dataSet['Attrition']
#x0 = np.array(X.loc[0],dtype=np.float)

beta = np.zeros((len(X), 1))
lastBeta = beta.copy()

for i in range(5000) :
    if i == 0 :
       fx1 = fx(X, Y, beta)
       dx1 = dx(X, Y, beta)
       newBeta = beta - np.dot(np.linalg.inv(dx1), fx1)
    else :
        if(np.linalg.norm(newBeta - lastBeta) > 0.000000001) :
            lastBeta = newBeta.copy()
            fx2 = fx(X, Y, lastBeta)
            dx2 = dx(X, Y, lastBeta)
            newBeta = lastBeta - np.dot(np.linalg.inv(dx2), fx2)
            #print(np.linalg.norm(lastBeta - newBeta))
        else :
            break

beta = newBeta

X = testDataSet.drop('Attrition', axis=1)
addOne = np.ones((len(X),1))
X = np.c_[X,addOne]

X = X.T
Y = testDataSet['Attrition']
correct = 0
List = Y.value_counts(normalize=True)

for i in range(len(Y)) :
    Xi = X[:,i]
    Xi.shape = (len(X),1)
    inX = np.dot(beta.T, Xi)
    value = sigmoid(inX)

    rate = value / (1 - value)
    reviseRate = rate * (List[1]/List[0])

    if reviseRate > 1 and Y[i] == 1:
        correct += 1
    elif reviseRate <= 1 and Y[i] == 0 :
        correct += 1

print(correct/len(Y))


import math
import numpy as np
import pylab
def harmonic_wavefunction(x, N):
    psi=[0, (math.pi)**(-1./4)*math.exp(-x**2/2.)]
    for n in range(2, N+1):
        psi+=[math.sqrt(2./n)*x*psi[n-1]-math.sqrt((n-1)/n)*psi[n-2], ]
    return psi[1:]

def E(n): return n+0.5
def Z(Beta): return 0.5/math.sinh(Beta/2.)

def harmonic_density(xmin, xmax, N, Beta):
    L=np.linspace(xmin, xmax, 15)
    l=len(L)
    rho=[[0.0 for i in range(l)] for j in range(l)]
    for i in range(l):
        for j in range(l):
            for n in range(N):
                rho[i][j]+=harmonic_wavefunction(L[i], N)[n]*harmonic_wavefunction(L[j], N)[n]*math.exp(-Beta*E(n))
    return rho

def Prob(x, xmin, xmax, N, Beta):
    L=np.linspace(xmin, xmax, 15)
    L=L.tolist()
    i=L.index(x)
    return np.array(harmonic_density(xmin, xmax, N, Beta))[i, i]/Z(Beta)
#print(Prob(0., -2., 2., 100, 8.))

X=np.linspace(-2., 2., 15)
P=[Prob(i, -2., 2., 100, 8) for i in X]
pylab.plot(X, P)
pylab.show()


import numpy as np
import scipy.stats

def gaussian_samples(n: int, mean: float = 1., sd: float = 1.) -> np.ndarray:

	return scipy.stats.norm(mean, sd).rvs(n)

def uniform_samples(n: int, left: float = 0., right: float = 2.) -> np.ndarray:

	return scipy.stats.uniform(left, right).rvs(n)

def poisson_samples(n: int, mu: float = 2) -> np.ndarray:

	return scipy.stats.poisson(mu).rvs(n)

def gamma_samples(n: int, a: float = 1.99) -> np.ndarray:

	return scipy.stats.gamma(a).rvs(n)

def exponential_samples(n: int) -> np.ndarray:

	return scipy.stats.expon().rvs(n)


import numpy as np

#############################################################################
#   CALCULATE THE EUCLIDEAN DISTANCE BETWEEN TWO POINTS

def ED(a, b):
  return np.sqrt(((a[0]-b[0])**2.0)+((a[1]-b[1])**2.0))

#############################################################################
#   CALCULATE THE ANGLE OF A VERTEX

def angle(A, vertex):
  if vertex == 1:
    p, q, r = ED(A[2],A[0]), ED(A[0],A[1]), ED(A[1],A[2])
  elif vertex == 2:
    p, q, r = ED(A[0],A[1]), ED(A[1],A[2]), ED(A[2],A[0])
  elif vertex == 3:
    p, q, r = ED(A[1],A[2]), ED(A[2],A[0]), ED(A[0],A[1])
  return np.arccos(((p**2.0)+(q**2.0)-(r**2.0))/(2.0*p*q))

#############################################################################
#   CALCULATE THE AREA OF TRIANGLE A

def area(A):
  a, b, c = ED(A[0],A[1]), ED(A[1],A[2]), ED(A[2],A[0])
  s = (a+b+c)/2.0
  return np.sqrt(s*(s-a)*(s-b)*(s-c))

#############################################################################
#   CALCULATE THE PERIMETER OF TRIANGLE A

def perimeter(A):
  return ED(A[0],A[1])+ED(A[1],A[2])+ED(A[2],A[0])

#############################################################################
#   CALCULATE THE CENTROID SIZE OF TRIANGLE A

def centroid_size(A):
  c = centroid(A)
  centroid_size = 0.0
  for vertex in A:
    centroid_size += ED(vertex, c)**2
  return np.sqrt(centroid_size)

#############################################################################
#   FIND THE CENTROID OF TRIANGLE A

def centroid(A):
  return np.array([(A[0][0]+A[1][0]+A[2][0])/3.0,(A[0][1]+A[1][1]+A[2][1])/3.0])

#############################################################################
# ROTATE TRIANGLE A SO THAT IT IS POINTING NORTH

def rotate(A):
  theta = rotation(A)
  c = centroid(A)
  return np.dot(A-c,np.array([[np.cos(theta),np.sin(theta)],[-np.sin(theta),np.cos(theta)]]))+c

#############################################################################
# RETURN THE ANGULAR DISTANCE FROM NORTH FOR TRIANGLE A BY ORIENTING SPOT

def rotation(A):
  B = A - centroid(A)
  return np.arctan2(B[0,0], B[0,1])

#############################################################################
# EQUILATERALNESS RATIO

def upper_bound_on_area(p):
  return p**2.0 / 20.784609690826528

def equilateralness(A):
  a = area(A)
  p = perimeter(A)
  return a / upper_bound_on_area(p)


import numpy as np
import time
import json

#definition of the activation function and its derivative
def sigmoid(x):
    return 1/(1+np.exp(-x))

#derivative of the sigmoid function
def d_sigmoid(x):
    return sigmoid(x)*(1-sigmoid(x))

#definition of the cost function
#squared error
def square_error(y_actual, y_expected):
    return (1/2)*np.square(np.subtract(y_actual,y_expected))

#derivative of the squared error
def d_square_error(y_actual, y_expected):
    return np.subtract(y_actual,y_expected)

#definition of the layer class so that the multi-layered neural network can be implemented
class Layer:
    #this will use a sigmoid activation function

    #this will initialize the layer object
    #if the layer is already trained, the user can input the bias, weight, and learning rate
    def __init__(self,number_inputs = None, number_neurons = None, bias = None, weights = None, learning_rate=None):

        if(number_inputs is not None and number_neurons is not None):
            # initializing the weights with random numbers between 0 to 1
            self.weights = np.random.randn(number_neurons, number_inputs)

            #initialize the bias with one
            self.bias = np.ones((number_neurons,1))

            #default learning rate is 0.5. Don't change these value pls.
            #use the training method to change the learning rate
            self.learning_rate = 0.5

        elif (bias is not None and weights is not None and learning_rate is not None):
            self.weights = weights

            self.bias = bias

            self.learning_rate = learning_rate

        else:
            raise Exception("The input arguements are not enough to initialize the layer object")

    def feedforward(self,inputs):
        #apply feedforward algorithm with equation 4 here
        self.previous_output = inputs
        self.Z = np.dot(self.weights,inputs) + self.bias
        self.A = sigmoid(self.Z)
        return self.A

    def backpropagate(self, dC_dA):
        #applying the formulas for updatting of the weights and biases
        #refer to equation 8
        dC_dZ = np.multiply(d_sigmoid(self.Z),dC_dA)

        #normalize the values
        #since the dot product and the np.sum is summed values from the samples.
        # With this code below, the results of the dot product and the summed values
        #will be average, respectively
        dC_dW = 1/ dC_dZ.shape[1] * np.dot(dC_dZ,self.previous_output.T)

        #np.sum is necessary to make the shape of the dC_dB and dC_dZ same
        #np.sum finding the sum along the column matrix to reduce it one column matrix.
        #it sums across the samples
        dC_dB =  1/ dC_dZ.shape[1] * np.sum(dC_dZ, axis=1,keepdims=True)
        #print("dC_dW : {}, dC_dB: {}, dC_dZ: {}". format(dC_dW,dC_dB,dC_dZ)) # uncomment this to see the respectively values

        #update the weights and biases using equation 7
        self.weights = self.weights - np.multiply(self.learning_rate,dC_dW)
        self.bias = self.bias - np.multiply(self.learning_rate,dC_dB)

        #return this value for the backpropagation of the different layer
        return np.dot(self.weights.T,dC_dZ)

#With the layer class, it will provide an abstraction to neural network for organization
class NeuralNetwork:

    # Documentation:
    #num_neurons_per_layer is a list that contains number of neurons per layer
    #each element represent a number of neurons for that layer
    #the minimum number of elements for num_neurons_per_layer is two
    #Ex. [1,2,3] - this signifies that there is one input in the input layer.
    # there are 2 neurons for the first idden layer and also, it has one hidden layer
    #Lastly, there are 3 neurons at the output layer
    #this constructor can load also from the json file which contains the model
    #filepath_model contains the filepath of the model to be loaded
    #By default, it will check for neural_network_model in the current relative location
    def __init__(self,num_neurons_per_layer=[],filepath_model="neural_network_model.json"):

        if(len(num_neurons_per_layer) > 0):
            #for the checking if num_neurons_per_layer have at least two elements
            assert len(num_neurons_per_layer) >= 2, "num_neurons_per_layer input should have at least two elements"

            #create a list of layers
            self.layers = [ Layer(number_inputs = num_neurons_per_layer[i], number_neurons = num_neurons_per_layer[i+1]) for i in range(len(num_neurons_per_layer) - 1)]
        else:

            #loading of json file
            with open(filepath_model,  "r") as read_file:
                model = json.load(read_file) #this holds a dictionary within a dictionary

            self.layers = []
            for nth_layer in model:

                learning_rate = model[nth_layer]["learning_rate"]
                weights = np.array(model[nth_layer]["weights"])
                bias =  np.array(model[nth_layer]["bias"])
                layer = Layer(learning_rate=learning_rate, weights=weights, bias = bias)

                self.layers.append(layer)

    def train(self, x_train, y_train, learning_rate = 0.5, epochs = 10000):

        if learning_rate != 0.5:
            #change the learning rate accrodingly for each of the layers
            for layer in self.layers:
                layer.learning_rate = learning_rate

        #TRAINING SESSION PROPER
        for epoch in range(epochs):

            #feedforward
            input = x_train
            for layer in self.layers:
                input = layer.feedforward(input)

            #print("Input: {}".format(input))

            #calculation of the derivative of the cost based on the input variable
            dCost = d_square_error(input,y_train)

            #uncomment this to know the cost for each of the epoch
            #size_y_train = y_train.shape[0]
            #cost = (1/size_y_train) * square_error(input,y_train).sum()
            #print("Output: {} Cost: {}".format(input,cost)) #uncomment this show the output

            #backpropagation
            for layer in reversed(self.layers):
                dCost = layer.backpropagate(dCost)

    def predict(self,x_predict):
        #implements the feed forward
        y_predict = x_predict
        for layer in self.layers:
                y_predict = layer.feedforward(y_predict)

        return y_predict

    #filepath input is the filepath were the model to be saved and its name
    #By default, it will save in the current relative location with neural_network_model as its filename
    #noted that it will use a json file
    def save_model(self,filepath="neural_network_model.json"):

        #this will create a dictionary for putting the text into the json file
        model ={}

        for i,layer in enumerate(self.layers):

            temp = vars(layer)#this will get the attributes of the layer object

            #this part will only get the important parts of the model
            #which is the bias, weights, and the learning rate
            layer_model =  {}

            layer_model["learning_rate"] = temp["learning_rate"]
            layer_model["weights"] = temp["weights"].tolist()
            layer_model["bias"] = temp["bias"].tolist()

            model[f"layer_{i+1}"] = layer_model

        #putting the model into a json file
        with open(filepath,'w') as nn_model:
            json.dump(model,nn_model, indent= 1)


import numpy as np
from numpy import array, dot
from math import atan2, asin, cos, sin

def quat2rpy(q):
	q_norm = (q[0]**2+q[1]**2+q[2]**2+q[3]**2)
	q = [q_i/q_norm for q_i in q]
	w = q[0]
	x = q[1]
	y = q[2]
	z = q[3]
	rpy = [atan2(2*(w*x + y*z), w*w + z*z - (x*x + y*y)),
  		   asin(2*(w*y - z*x)),
           atan2(2*(w*z + x*y), w*w + x*x - (y*y + z*z))]
	return rpy

def rotx(theta):
    c = cos(theta)
    s = sin(theta)
    M = array([[1,0,0],[0,c,-s],[0,s,c]])
    return M

def roty(theta):
    c = cos(-theta)
    s = sin(-theta)
    M = array([[c,0,-s],[0,1,0],[s,0,c]])
    return M

def rotz(theta):
    c = cos(theta)
    s = sin(theta)
    M = array([[c,-s,0],[s,c,0],[0,0,1]])
    return M

def rpy2rotmat(rpy):
    R = dot(rotz(rpy[2]),dot(roty(rpy[1]),rotx(rpy[0])))
    return R

def body2world(rpy, xyz):
    R = rpy2rotmat(rpy)
    xyz_world = dot(R,array(xyz))
    return xyz_world.tolist()

def world2body(rpy, xyz):
    R = rpy2rotmat(rpy)
    xyz_body = dot(R.T,array(xyz))
    return xyz_body.tolist()

def angularvel2rpydot(rpy, omega):
	p = rpy[1]
	y = rpy[2]
	sy = sin(y)
	cy = cos(y)
	sp = sin(p)
	cp = cos(p)
	tp = sp/cp
	Phi = array([[cy/cp, sy/cp, 0],[-sy, cy, 0],[cy*tp, tp*sy, 1]])
	rpydot = dot(Phi,omega)
	return rpydot.tolist()


"""

Gianluca Scarpellini - lab1 - Computer and Robot Vision

g.scarpellini1[at]disco.unimib.it

DLT: projection matrix estimation using SVD and MSE

"""

import numpy as np
import cv2
from matplotlib import pyplot as plt

points_im = np.array([
    (1864, 1682),
    (1572, 1862),
    (2628, 1719),
    (654, 1818),
    (1271, 1604),
    (136, 2145),
    (3096, 1678),
    (2086, 1944)
])

points_w = np.array([
    (450, 740, 45),
    (297, 591, 50),
    (510, 1190, 1),
    (252, 130, 49),
    (492, 385, 85),
    (0, 0, 0),
    (538, 1415, 13),
    (140, 870, 35)
])

H = np.zeros([16, 12])
i = 0
for point_im, point_w in (zip(points_im, points_w)):
    H[i, :] = np.array([-point_w[0], -point_w[1], -point_w[2], -1, 0, 0, 0, 0,
                        point_im[0] * point_w[0], point_im[0] * point_w[1],
                        point_im[0] * point_w[2], point_im[0]
                        ])

    H[i + 1, :] = np.array([0, 0, 0, 0, -point_w[0], -point_w[1], -point_w[2], -1,
                            point_im[1] * point_w[0], point_im[1] * point_w[1],
                            point_im[1] * point_w[2], point_im[1]
                            ])
    i += 2

_, _, vh = np.linalg.svd(H, full_matrices=True)

# Column with lower singular value
P = np.reshape(vh[-1, :], [3, 4])

results = [P @ np.array([x, y, z, 1]) for x, y, z in points_w]
results = np.array([(int(result[0] / result[2]), int(result[1] / result[2])) for result in results])

im = cv2.cvtColor(cv2.imread('./im.jpg'), cv2.COLOR_BGR2RGB)

plt.imshow(im)
for x, y in results:
    plt.plot(x, y, 'bo', markersize=3)
for u, v in points_im:
    plt.plot(u, v, 'go', markersize=3)
plt.show()

mse = (np.linalg.norm(results - points_im, axis=0)).mean(axis=0)
print(f'MSE {mse}')


import numpy as np

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

def relu(z: np.ndarray) -> np.ndarray:
    return np.maximum(0, z)

def leaky_relu(z: np.ndarray, rate: float = .01) -> np.ndarray:
    return np.maximum(z * rate, z)


from typing import List
import numpy as np
from matplotlib import pyplot as plt
from math import sin, pi

def f(x):
    return sin(2 * pi * x)

def h(x, theta):
    n = theta.shape[0] - 1
    result = 0
    for i in range(n + 1):  # from 0 to n
        result += theta[i] * x ** i
    return result

def loss(x_list, y_list, theta, lmd = 1e-8):
    sum = 0
    for x, y in zip(x_list, y_list):
        sum += (h(x, theta) - y) ** 2
    for i in range(theta.shape[0]):
        sum += lmd * abs(theta[i])
    return sum / 2

def grad_descent(x_list, y_list, theta, l):
    alpha = 1e-2
    n = theta.shape[0] - 1
    result = np.zeros(n + 1)
    for k in range(n + 1):
        for x, y in zip(x_list, y_list):
            result[k] += x ** k * (h(x, theta) - y)
    temp = theta - alpha * result
    return temp, l(x_list, y_list, temp)

def l1_grad_descent(x_list, y_list, theta, l = loss):
    alpha = 1e-2
    n = theta.shape[0] - 1
    result = np.copy(theta)
    prev = l(x_list, y_list, result)
    for k in range(n + 1):
        while True:
            result[k] += alpha
            temp_loss = l(x_list, y_list, result)
            if prev <= temp_loss:
                result[k] -= alpha
                break
            prev = temp_loss
        while True:
            result[k] -= alpha
            temp_loss = l(x_list, y_list, result)
            if prev <= temp_loss:
                result[k] += alpha
                break
            prev = temp_loss
    return result, prev

def fit(x_list: List[int], y_list: List[int], k: int = 4, descent = grad_descent, l = loss):
    count = 0
    max_round = 10000000
    theta = np.zeros(k + 1)
    while count < max_round:
        temp, cost = descent(x_list, y_list, theta, l)
        # if np.linalg.norm(temp - theta) < np.linalg.norm(theta) * eps:
        if cost <= 1:
            break
        count += 1
        theta = temp
        if count % 10000 == 0:
            print(count, cost)
    # print(theta)
    return theta

if __name__ == '__main__':
    n = 30
    x_list = np.linspace(0, 1, n)
    y_list = np.sin(2 * np.pi * x_list)
    plt.plot(x_list, y_list)
    plt.savefig('./data/sin.jpg')

    theta = fit(x_list, y_list, 30, grad_descent)
    new_y_list = h(x_list, theta)
    plt.plot(x_list, new_y_list, color='r')
    plt.savefig('./data/gradient_descent.jpg')

    theta = fit(x_list, y_list, 30, l1_grad_descent)
    new_y_list = h(x_list, theta)
    plt.plot(x_list, new_y_list, color='g')
    plt.savefig('./data/lasso.jpg')


import numpy as np

def regConvex(x, lamda, arg=None):
    """
    Returns f, g, Hv of lamda*||x||^2
    """
    f = 0.5*lamda*np.dot(x.T, x)
    if arg == 'f':
        return f

    g = lamda*x
    if arg == 'g':
        return g
    if arg == 'fg':
        return f, g

    Hv = lambda v: lamda*v

    if arg is None:
        return f, g, Hv

def regNonconvex(x, lamda, a=1, arg=None):
    """
    returns  f, g, Hv of lamda*a*x^2/(1+a*x^2)
    """
    ax2 = a*x**2
    f = lamda*sum(ax2/(1+ax2))
    if arg == 'f':
        return f

    g = lamda*2*a*x/(1+ax2)**2
    if arg == 'g':
        return g
    if arg == 'fg':
        return f, g

    Hv = lambda v: lamda*2*a*(1-3*ax2)/(1+ax2)**3*v

    if arg is None:
        return f, g, Hv


import numpy as np

# linear algebra utility functions

def normalize_cols(M):
  return M.astype(float) / M.sum(axis=0)

def normalize_rows(M):
  return normalize_cols(M.T).T

def is_col_stochastic(M, threshold=1e-8):
  return np.all(np.absolute(M.sum(axis=0) - np.ones(M.shape[1])) <= threshold)

def is_row_stochastic(M, threshold=1e-8):
  return is_col_stochastic(M.T, threshold)


import pandas as pd
import numpy as np

file_to_load = "Resources/purchase_data.csv"
pur_data = pd.read_csv(file_to_load)

total_players = len(pur_data["SN"].value_counts())

player_count = pd.DataFrame({"Total Players":[total_players]})

player_count

number_of_unique_items = len((pur_data["Item ID"]).unique())
average_price = (pur_data["Price"]).mean()
number_of_purchases = (pur_data["Purchase ID"]).count()
total_revenue = (pur_data["Price"]).sum()

summary_df = pd.DataFrame({"Number of Unique Items":[number_of_unique_items],
                           "Average Price":[average_price],
                           "Number of Purchases": [number_of_purchases],
                           "Total Revenue": [total_revenue]})

summary_df.style.format({'Average Price':"${:,.2f}",
                         'Total Revenue': '${:,.2f}'})
summary_df

gender_stats = pur_data.groupby("Gender")

total_count_gender = gender_stats.nunique()["SN"]

percentage_of_players = total_count_gender / total_players * 100

gender_demos = pd.DataFrame({"Percentage of Players": percentage_of_players, "Total Count": total_count_gender})

gender_demo.index.name = None

gender_demo.sort_values(["Total Count"], ascending = False).style.format({"Percentage of Players":"{:.2f}"})

pur_count = gender_stats["Purchase ID"].count()

avg_pur_price = gender_stats["Price"].mean()

avg_pur_total = gender_stats["Price"].sum()

avg_pur_capita = avg_purchase_total/total_count_gender

gender_demo = pd.DataFrame({"Purchase Count": pur_count,
                                    "Average Purchase Price": avg_pur_price,
                                    "Average Purchase Value":avg_pur_total,
                                    "Avg Purchase Total per Person": avg_pur_capita})

gender_demo.style.format({"Average Purchase Value":"${:,.2f}",
                                  "Average Purchase Price":"${:,.2f}",
                                  "Avg Purchase Total per Person":"${:,.2f}"})

                                  age_bins = [0, 9, 14, 19, 24, 29, 34, 39, 200]
group_names = ["<10", "10-14", "15-19", "20-24", "25-29", "30-34", "35-39", "40+"]

pur_data["Age Group"] = pd.cut(pur_data["Age"],age_bins, labels=group_names)
pur_data

age_grouped = pur_data.groupby("Age Group")

total_count_age = age_grouped["SN"].nunique()

percentage_by_age = (total_count_age/total_players) * 100

age_demo = pd.DataFrame({"Percentage of Players": percentage_by_age, "Total Count": total_count_age})

age_demo.index.name = None

age_demo.style.format({"Percentage of Players":"{:,.2f}"})

pur_count_age = age_grouped["Purchase ID"].count()
avg_pur_price_age = age_grouped["Price"].mean()
total_pur_value = age_grouped["Price"].sum()
avg_pur_capita_age = total_pur_value/total_count_age

age_demo = pd.DataFrame({"Purchase Count": pur_count_age,
                                 "Average Purchase Price": avg_pur_price_age,
                                 "Total Purchase Value":total_pur_value,
                                 "Average Purchase Total per Person": avg_pur_capita_age})

age_demo.index.name = None

age_demo.style.format({"Average Purchase Price":"${:,.2f}",
                               "Total Purchase Value":"${:,.2f}",
                               "Average Purchase Total per Person":"${:,.2f}"})

                               spender_stats = pur_data.groupby("SN")
pur_count_spender = spender_stats["Purchase ID"].count()
avg_pur_price_spender = spender_stats["Price"].mean()
pur_total_spender = spender_stats["Price"].sum()

top_spend = pd.DataFrame({"Purchase Count": pur_count_spender,
                             "Average Purchase Price": avg_pur_price_spender,
                             "Total Purchase Value":pur_total_spender})

clean_spend = top_spend.sort_values(["Total Purchase Value"], ascending=False).head()

clean_spend.style.format({"Average Purchase Total":"${:,.2f}",
                            "Average Purchase Price":"${:,.2f}",
                            "Total Purchase Value":"${:,.2f}"})

                            items = pur_data[["Item ID", "Item Name", "Price"]]
item_stats = items.groupby(["Item ID","Item Name"])
pur_count_item = item_stats["Price"].count()
pur_value = (item_stats["Price"].sum())
item_price = pur_value/pur_count_item

most_popular_items = pd.DataFrame({"Purchase Count": pur_count_item,
                                   "Item Price": item_price,
                                   "Total Purchase Value":pur_value})

popular_formatted = most_popular_items.sort_values(["Purchase Count"], ascending=False).head()

popular_formatted.style.format({"Item Price":"${:,.2f}",
                                "Total Purchase Value":"${:,.2f}"})

                                popular_formatted = most_popular_items.sort_values(["Total Purchase Value"],
                                                   ascending=False).head()
popular_formatted.style.format({"Item Price":"${:,.2f}",
                                "Total Purchase Value":"${:,.2f}"})


import numpy as np

# passed in gradient from the next layer
# for the purpose of this example we're going to use
# an array of an incremental gradient values
dvalues = np.array([[1, 1, 1],
                    [2, 2, 2],
                    [3, 3, 3]])

# we have 3 sets of inputs - samples
inputs = np.array([[1, 2, 3, 2.5],
                   [2, 5, -5, 2],
                   [-1.5, 2.7, 3.3, -0.8]])

# we have 3 sets of weights - one set for each neuron
# we have 4 inputs, thus 4 weights
# recall that we keep weights transposed
weights = np.array([[0.2, 0.8, -0.5, 1],
                    [0.5, -0.91, 0.26, -0.5],
                    [-0.26, -0.27, 0.17, 0.87]]).T

# one bias for each neuron
# biases are the row vector with a shape (1, neurons)
biases = np.array([[2, 3, 0.5]])

# forward pass
# dense layer
layer_outputs = np.dot(inputs, weights) + biases
# ReLU activation
relu_outputs = np.maximum(0, layer_outputs)

#let's optimize and test backpropagation here
# ReLU activation - simulates derivative with respect to input values
# from next layer passed to current layer during backpropagation
drelu = relu_outputs.copy()
drelu[layer_outputs<=0] = 0

# dense layer
# dinputs - multiply by weights
dinputs = np.dot(drelu, weights.T)
# dweights - multiply by inputs
dweights = np.dot(inputs.T, drelu)
# dbiases - sum values, do this over samples (first axis), keepdims
# since this by default will produce a plain list -
# we explained this in the chapter 4
dbiases = np.sum(drelu, axis=0, keepdims=True)

# update parameters
weights += -0.001 * dweights
biases += -0.001 * dbiases

print(weights)
print(biases)


"""
Computes equilibrium price and quantities, take 2.
"""
from numpy import exp
from scipy.optimize import bisect

def supply(price, b):
   return exp(b * price) - 1

def demand(price, a, epsilon):
   return a * price**(-epsilon)

def compute_equilibrium(a, b, epsilon):
    plow = 0.1
    phigh = 10.0
    def excess_supply(price):
        return supply(p, b) - demand(p, a, epsilon)

    pclear = bisect(excess_supply, plow, phigh)
    return pclear

parameter_list = ((1, 0.1, 1),
                  (2, 0.1, 1),
                  (1, 0.2, 1),
                  (1, 0.1, 2))

for parameters in parameter_list:
    print("Price = {}".format(compute_equilibrium(*parameters)))