import numpy as np
import scipy.stats
import sys

def idx_to_rank_sum(idx, n):
    """Map from an integer idx to a value in the support of
    the Wilcoxon test statistic.

    Specifically, if S is the support of the test statistic,
    then this is a map from
        {0, ..., |S| - 1} ->  S

    Parameters
    ----------
        idx : integer
            An integer from {0, ..., |S| - 1}.
        n : integer
            The number of paired samples.

    Returns
    -------
        rank_sum : integer
            The rank_sum corresponding to idx.

    Examples
    --------
        If we have n = 3 paired samples, then the
        map is given by
          idxes = {  0  1  2  3  4  5  6 }
                     |  |  |  |  |  |  |
            S   = { -6 -4 -2  0  2  4  6 }
    """
    support_size = int(n*(n+1)/2 + 1)

    if idx >= support_size:
        raise ValueError("Outside of support")

    shift = 1 if support_size % 2 == 0 else 0
    middle = (support_size-1)/2
    rank_sum = 2*(idx-np.floor(middle)) - shift
    rank_sum = int(rank_sum)
    return rank_sum

def rank_sum_to_idx(rank_sum, n):
    """Map from a rank sum in the support of the Wilcoxon
    test statistic to an integer idx.

    This is the inverse mapping of idx_to_rank_sum.

    Specifically, if S is the support of the test statistic,
    then this is a map from
        S -> {0, ..., |S| - 1}

    Parameters
    ----------
        rank_sum : integer
            An integer in the support of the test statistic
        n : integer
            The number of paired samples.

    Returns
    -------
        idx : integer
            The idx in the support corresponding to rank_sum.

    Examples
    --------
        If we have n = 3 paired samples, then the
        map is given by
            S   = { -6 -4 -2  0  2  4  6 }
                     |  |  |  |  |  |  |
          idxes = {  0  1  2  3  4  5  6 }

    """
    if rank_sum < -n*(n+1)/2 or rank_sum > n*(n+1)/2:
        raise ValueError("Outside of support")

    support_size = int(n*(n+1)/2 + 1)
    shift = 1 if support_size % 2 == 0 else 0
    middle = (support_size-1)/2
    for i in range(int(support_size)):
        if rank_sum == 2*(i-np.floor(middle)) - shift:
            return i

def compute_counts(n):
    """Recursively counts the coefficients of each term in the mgf.

    The mgf is given by
        mgf_X(t) = 1/2^n \prod_{j=1}^n (e^{-tj} + e^{tj})
                 = 1/2^n \sum_{j=1}^n c_j e^{tj}
                 = \sum_{j=1}^n Pr(X = j) e^{tj}

    This function computes c_j for a number of paired samples n.

    By expanding and collecting e^{tj} for each j, we can compute
    the pmf of the Wilcoxon test statistic. We can do this recursively
    by noticing that
        1/2^n \prod_{j=1}^n (e^{-tj} + e^{tj})
        = 1/2^n (e^{-nt} + e^{nt}) \prod_{j=1}^{n-1} (e^{-tj} + e^{tj})
    Thus, if we have the coefficients c_j for n-1, we can compute the
    coefficients c_j for n by expanding the bottom equation and counting
    e^{tj} for each j.
.

    References
    ----------
        [1] Hogg, Robert V., Joseph McKean, and Allen T. Craig. Introduction
        to Mathematical Statistics. 7th Edition. pp. 541-544.
    """
    if n == 1:
        counts = np.array([1, 1])
        return counts
    else:
        counts = compute_counts(n-1)
        support_size = int(n*(n+1)/2 + 1)
        new_counts = np.zeros(support_size)
        for i in range(counts.size):
            rank_sum = idx_to_rank_sum(i, n-1)
            new_rank_sum1 = rank_sum + n
            new_rank_sum2 = rank_sum - n

            min_rank = -n*(n+1)/2
            max_rank = n*(n+1)/2

            if counts[i] > 0:
                if new_rank_sum1 <= max_rank:
                    new_counts[rank_sum_to_idx(new_rank_sum1, n)] += counts[i]

                if new_rank_sum2 >= min_rank:
                    new_counts[rank_sum_to_idx(new_rank_sum2, n)] += counts[i]
        return new_counts

def compute_pmf(n):
    """Compute the support and pmf given n paired samples.

    Parameters
    ----------
        n : integer
            The number of paired samples
    Returns
    -------
        support : numpy array
            An array giving the support of the pmf
        pmf : numpy array
            An array giving the probability of each integer in the support
    """
    support_size = int(n*(n+1)/2 + 1)
    support = np.array([idx_to_rank_sum(i, n) for i in range(support_size)])
    pmf = compute_counts(n)/np.power(2,n)
    assert np.abs(pmf.sum() - 1) < 1E-8, pmf.sum()
    return support, pmf

def wilcoxon_exact(x, y=None, alternative="two-sided"):
    """
    Calculate the Wilcoxon signed-rank test statistic and exact p-values.

    Given matched samples, x_i and y_i, the Wilcoxon signed-rank test tests the
    null that x_i - y_i is symmetric around zero. In practice, it is used to test
    whether x_i and y_i are from the same population with different location
    parameters.

    There are several different versions of the test statistic. The one used here
    is
        T = sign(z_1) R|z_1| + ... + sign(z_n) R|z_n|
    where
        z_i = x_i - y_i       if y_i is specified
        z_i = x_i             otherwise.

    The pmf has no closed form, but for small sample sizes it is possible to compute
    the pmf by solving for the coefficients of the moment generating function.

    Parameters
    ----------
        x : array_like
            The first set of data values (if y is specified) or the difference
            between two sets of data values
        y : array_like optional
            If specified, the difference x - y is used for the test statistic
        alternative : {"two-sided", "greater", "less"}, optional
            The alternative hypothesis tested.
            If "two-sided", the test is
                x_i - y_i > 0 or x_i - y_i < 0
            If "greater", the test it
                x_i - y_i > 0
            If "less", the test is
                x_i - y_i < 0

    Returns
    -------
        T : float
            The test-statistic.
        p : float
            The p-value.

    Examples
    --------
    >>> import numpy as np
    >>> from wilcoxon_exact import wilcoxon_exact
    >>> x = np.array([1.83,  0.50,  1.62,  2.48, 1.68, 1.88, 1.55, 3.06, 1.30])
    >>> y = np.array([0.878, 0.647, 0.598, 2.05, 1.06, 1.29, 1.06, 3.14, 1.29])
    >>> wilcoxon_exact(x, y, alternative="greater")
    (35.0, 0.01953125)

    >>> x = np.array([-6.1, 4.3, 7.2, 8.0, -2.1])
    >>> wilcoxon_exact(x, alternative="two-sided")
    (7.0, 0.4375)
    """
    if alternative not in ["two-sided", "less", "greater"]:
        raise ValueError("Alternative must be either 'two-sided'", "'greater' or 'less'")

    x = np.array(x)
    if x.ndim > 1:
        raise ValueError("Sample x must be one-dimensional")

    if y is not None:
        y = np.array(y)
        if y.ndim > 1:
            raise ValueError("Sample y must be one-dimensional")
        if x.shape != y.shape:
            raise ValueError("Sample x and y must have the same length.")
        diff = x - y
    else:
        diff = x

    if np.unique(np.abs(diff)).size != diff.size:
        if y is None:
            raise ValueError("abs(x) values must be unique")
        else:
            raise ValueError("abs(x - y) values must be unique")

    ranks = scipy.stats.rankdata(np.abs(diff))
    signs = np.sign(diff)
    T = (signs*ranks).sum()

    n = diff.size
    if n > 30:
        print("warning: sample size is large for exact calculation\n" +
              "         calculation may be slow", file=sys.stderr)
    rank_sum, pmf = compute_pmf(n)

    if alternative == "less":
        idx = rank_sum <= T
        p = pmf[idx].sum()
    elif alternative == "greater":
        idx = rank_sum >= T
        p = pmf[idx].sum()
    else:
        idx = np.logical_or(rank_sum <= -np.abs(T), rank_sum >= np.abs(T))
        p = pmf[idx].sum()

    return T, p

if __name__ == "__main__":
    print("Running tests...")
    # Test the mapping from array indices to the support
    n = 1
    support_size = int(n*(n+1)/2 + 1)
    support = np.array([-1, 1])
    for i in range(support_size):
        rank_sum = idx_to_rank_sum(i, n)
        assert rank_sum == support[i], str(rank_sum) + " is not == " + str(support[i])

    n = 2
    support_size = int(n*(n+1)/2 + 1)
    support = np.array([-3, -1, 1, 3])
    for i in range(support_size):
        rank_sum = idx_to_rank_sum(i, n)
        assert rank_sum == support[i], str(rank_sum) + " is not == " + str(support[i])

    n = 3
    support_size = int(n*(n+1)/2 + 1)
    support = np.array([-6, -4, -2, 0, 2, 4, 6])
    for i in range(support_size):
        rank_sum = idx_to_rank_sum(i, n)
        assert rank_sum == support[i], str(rank_sum) + " is not == " + str(support[i])

    n = 4
    support_size = int(n*(n+1)/2 + 1)
    support = np.array([-10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10])
    for i in range(support_size):
        rank_sum = idx_to_rank_sum(i, n)
        assert rank_sum == support[i], str(rank_sum) + " is not == " + str(support[i])

    # Test the mapping from the support to indices
    n = 1
    support = np.array([-1, 1])
    for idx, rs in enumerate(support):
        i = rank_sum_to_idx(rs, n)
        assert i == idx

    n = 2
    support = np.array([-3, -1, 1, 3])
    for idx, rs in enumerate(support):
        i = rank_sum_to_idx(rs, n)
        assert i == idx

    n = 3
    support = np.array([-6, -4, -2, 0, 2, 4, 6])
    for idx, rs in enumerate(support):
        i = rank_sum_to_idx(rs, n)
        assert i == idx

    n = 4
    support = np.array([-10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10])
    for idx, rs in enumerate(support):
        i = rank_sum_to_idx(rs, n)
        assert i == idx

    # Test computing the pmf
    counts = compute_counts(1)
    expected_counts = np.array([1., 1.])
    assert np.all(counts == expected_counts)

    counts = compute_counts(2)
    expected_counts = np.array([1, 1, 1, 1])
    assert np.all(counts == expected_counts)

    counts = compute_counts(3)
    expected_counts = np.array([1, 1, 1, 2, 1, 1, 1])
    assert np.all(counts == expected_counts)

    counts = compute_counts(4)
    expected_counts = np.array([1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1])
    assert np.all(counts == expected_counts)

    rank_sum, pmf = compute_pmf(3)
    expected_rank_sum = np.array([-6, -4, -2, 0, 2, 4, 6])
    expected_pmf = compute_counts(3)/8
    assert np.all(rank_sum == expected_rank_sum)
    assert np.all(pmf == expected_pmf)

    rank_sum, pmf = compute_pmf(4)
    expected_rank_sum = np.array([-10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10])
    expected_pmf = compute_counts(4)/16
    assert np.all(rank_sum == expected_rank_sum)
    assert np.all(pmf == expected_pmf)

    # Compare p-values to those generated by R
    x = np.array([1.83,  0.50,  1.62,  2.48, 1.68, 1.88, 1.55, 3.06, 1.30])
    y = np.array([0.878, 0.647, 0.598, 2.05, 1.06, 1.29, 1.06, 3.14, 1.29])
    p_greater = 0.01953125
    p_less = 0.986328125
    p_two_tailed = 0.0390625
    assert np.abs(p_greater - wilcoxon_exact(x, y, "greater")[1]) < 1E-8
    assert np.abs(p_less - wilcoxon_exact(x, y, "less")[1]) < 1E-8
    assert np.abs(p_two_tailed - wilcoxon_exact(x, y, "two-sided")[1]) < 1E-8

    x = np.array([-6.1, 4.3, 7.2, 8.0, -2.1])
    p_greater = 0.21875
    p_less = 0.84375
    p_two_tailed = 0.4375
    assert np.abs(p_greater - wilcoxon_exact(x, alternative="greater")[1]) < 1E-8
    assert np.abs(p_less - wilcoxon_exact(x, alternative="less")[1]) < 1E-8
    assert np.abs(p_two_tailed - wilcoxon_exact(x, alternative="two-sided")[1]) < 1E-8

    x = np.array([ 0.02378057, -0.68174183,  1.11878671,  0.34532977, -0.6687004 ,
                  -0.00275748,  0.61589287, -0.28073615,  2.32557442,  0.057978  ,
                  -0.16706318,  0.94763388,  1.49308607,  0.61162639, -0.09053253,
                  -0.13645805, -0.96742671, -0.3784958 ,  1.21554404,  0.08509771,
                   0.92971309,  0.2383083 ,  0.78747881,  0.82000769,  0.53554306])

    y = np.array([-0.27826135, -0.5497125 ,  1.12381066, -0.33309764,  0.30702763,
                  -1.10632243, -0.08192293,  0.32331644,  1.04667997,  0.56002087,
                  -0.88494827,  1.84830939, -2.10739496,  0.02836235,  0.30636346,
                  -0.77036039, -0.21900135,  0.20526964,  0.55322561, -0.0153534 ,
                  -0.1933098 , -0.73011513,  0.48943407,  0.30204235, -1.29466832])
    p_greater = 0.03131321073
    p_less = 0.9706242383
    p_two_tailed = 0.06262642145
    assert np.abs(p_greater - wilcoxon_exact(x, y, "greater")[1]) < 1E-8
    assert np.abs(p_less - wilcoxon_exact(x, y, "less")[1]) < 1E-8
    assert np.abs(p_two_tailed - wilcoxon_exact(x, y, "two-sided")[1]) < 1E-8

    x = np.array([ 0.2553419 , -0.3687772 ,  1.38766691,  0.66001067,  1.06808852,
                  -0.13471264, -0.64539139, -0.53209119, -0.57985291, -0.55703202,
                   0.52719496, -0.71739911,  1.46841159,  0.83334944,  1.51441298,
                   1.78476198, -0.30565303,  0.27826273,  1.04988288,  0.41767253,
                   0.08049793, -0.39440538,  0.53596691,  0.43917537,  1.67664441,
                  -1.5416117 , -0.59672013, -0.10630783, -0.17345269, -1.3209056 ])
    p_greater = 0.1642349288
    p_less = 0.8408019599
    p_two_tailed = 0.3284698576
    assert np.abs(p_greater - wilcoxon_exact(x, alternative="greater")[1]) < 1E-8
    assert np.abs(p_less - wilcoxon_exact(x, alternative="less")[1]) < 1E-8
    assert np.abs(p_two_tailed - wilcoxon_exact(x, alternative="two-sided")[1]) < 1E-8
    print("Passed all tests!")


from numpy import exp, cos, linspace
import bokeh.plotting as plt
import os, re

def damped_vibrations(t, A, b, w):
    return A*exp(-b*t)*cos(w*t)

def compute(A, b, w, T, resolution=500):
    """Return filename of plot of the damped_vibration function."""
    t = linspace(0, T, resolution+1)
    u = damped_vibrations(t, A, b, w)

    # create a new plot with a title and axis labels
    TOOLS = "pan,wheel_zoom,box_zoom,reset,save,box_select,lasso_select"
    p = plt.figure(title="simple line example", tools=TOOLS,
                   x_axis_label='t', y_axis_label='y')

    # add a line renderer with legend and line thickness
    p.line(t, u, legend="u(t)", line_width=2)

    from bokeh.resources import CDN
    from bokeh.embed import components
    script, div = components(p)
    head = """
<link rel="stylesheet"
 href="http://cdn.pydata.org/bokeh/release/bokeh-0.9.0.min.css"
 type="text/css" />
<script type="text/javascript"
 src="http://cdn.pydata.org/bokeh/release/bokeh-0.9.0.min.js">
</script>
<script type="text/javascript">
Bokeh.set_log_level("info");
</script>
"""
    return head, script, div

if __name__ == '__main__':
    print (compute(A=1, b=0.2, w=6.28, T=4, resolution=500))


import numpy as np
from src.si.util.scale import StandardScaler
import pandas as pd

class PCA:
    def __init__(self, num_components=2, using="svd"):
        self.numcomps = num_components
        self.alg = using

    def transform(self, dataset):  # objeto Dataset
        scaler = StandardScaler()
        x_scaled = scaler.fit_transform(dataset)  # standardização dos dados usando o StandardScaler
        X = x_scaled.X
        XT = X.transpose()
        print(XT)
        self.eigen_vectors, self.eigen_values, vt = np.linalg.svd(XT)
        # sort the eigenvalues in descending order
        self.sorted_index = np.argsort(self.eigen_values)[::-1]  # devolve os indices de acordo com a importância dos valores por ordem crescente
        self.sorted_eigenvalue = self.eigen_values[self.sorted_index]  # ordernar os valores de acordo com os indices anteriores
        # similarly sort the eigenvectors
        sorted_eigenvectors = self.eigen_vectors[:,self.sorted_index]  # devolve apenas as colunas com os indices dos valores

        # select the first n eigenvectors, n is desired dimension of our final reduced data.
        # you can select any number of components.
        eigenvector_subset = sorted_eigenvectors[:, 0:self.numcomps]  # apenas devolve o numero de colunas que queremos
        x_reduced = np.dot(eigenvector_subset.transpose(), XT).transpose()
        return x_reduced

    def variance_explained(self):
        somapercent = np.sum(self.sorted_eigenvalue)
        percentagem = []
        for i in self.sorted_eigenvalue:
            percentagem.append(i/somapercent *100)
        return np.array(percentagem)

    def fit_transform(self, dataset):
        data_reduced = self.transform(dataset)
        percentagem = self.variance_explained()
        return data_reduced, percentagem


# author: Dominik Krzeminski (dokato)

import numpy as np
import matplotlib.pyplot as plt
from dfa import dfa

def power_law_noise(n, alpha, var=1):
    '''
    Generale power law noise.

    Args:
    -----
      *n* : int
        number of data points
      *alpha* : float
        DFA exponent
      *var* = 1 : float
        variance
    Returns:
    --------
      *x* : numpy.array
        generated noisy data with exponent *alpha*

    Based on:
    N. Jeremy Kasdin, Discrete simulation of power law noise (for
    oscillator stability evaluation)
    '''
    # computing standard deviation from variance
    stdev = np.sqrt(np.abs(var))

    beta = 2*alpha-1

    hfa = np.zeros(2*n)
    hfa[0] = 1
    for i in range(1,n):
        hfa[i] = hfa[i-1] * (0.5*beta + (i-1))/i

    # sample white noise
    wfa = np.hstack((-stdev +2*stdev * np.random.rand(n), np.zeros(n)))

    fh = np.fft.fft(hfa)
    fw = np.fft.fft(wfa)

    fh = fh[1:n+1]
    fw = fw[1:n+1]

    ftot = fh * fw

    # matching the conventions of the Numerical Recipes

    ftot = np.hstack((ftot, np.zeros(n-1)))

    x = np.fft.ifft(ftot)

    return np.real(x[:n])

if __name__=='__main__':
    x1 = power_law_noise(2**12, 0.5)
    x2 = power_law_noise(2**12, 0.8)
    x3 = power_law_noise(2**12, 1.2)

    plt.subplot(311)
    plt.plot(x1)
    plt.subplot(312)
    plt.plot(x2)
    plt.subplot(313)
    plt.plot(x3)

    plt.show()

    for e,xx in enumerate([x1,x2,x3]):
        scales, fluct, alpha = dfa(xx)
        print("DFA exponent {}: {}".format(e+1, alpha))


import numpy as np
import matplotlib.pyplot as plt
from pandas.io.parsers import read_csv

def load_csv(file_name):
    values = read_csv(file_name, header=None).values

    return values.astype(float)

def gradient_descent(X, Y, alpha, iterations):
    m = float(np.shape(X)[0])
    thetas = np.zeros((2, 1), dtype=float)
    costs = np.empty(iterations)
    thetasHistory = np.empty((iterations, np.shape(thetas)[0]))

    for i in range(iterations):
        H = h(X, thetas)
        thetas = thetas - (alpha * (1/m) * (X.T.dot(H - Y)))

        thetasHistory[i] = thetas.T
        costs[i] = cost(X, Y, thetas)
        print("Cost [", i, "]: ", costs[i])

    return thetasHistory, costs

def cost(X, Y, thetas):
    m = np.shape(X)[0]
    return ((h(X, thetas)-Y)**2).sum() / (2*m)

def h(X, thetas):
    return np.dot(X, thetas)

def main():
    # DATA PREPROCESSING
    datos = load_csv("data/ex1data1.csv")
    X = datos[:, :-1]
    Y = datos[:, -1][np.newaxis].T

    m = np.shape(X)[0]
    n = np.shape(X)[1]

    X = np.hstack([np.ones([m, 1]), X])
    alpha = 0.01

    # TRAINING
    thetas, costs = gradient_descent(X, Y, alpha, 1000)

    # PREDICTION
    prediction = h(X, thetas[-1,:][np.newaxis].T)

    # RESULTS
    plt.figure()
    plt.scatter(X[:,1], Y[:,0], c='red')
    plt.plot(X[:,1], prediction)
    plt.savefig('images/regresion_simple_results.png')
    plt.show()
    plt.close()

    plt.figure()
    plt.plot(costs)
    plt.savefig('images/regresion_simple_gradient_descent.png')
    plt.show()
    plt.close()

if __name__ == "__main__":
    main()


import numpy as np

class Logistic:
    def __init__(self, X, y):
        self.X = np.concatenate((np.ones(y.shape), X), axis = 1)
        self.y = y
        self.preds = np.zeros(y.shape)
        self.n = y.shape[0]

        self.loss = None
        self.lr = 0.001 # funny, it the lr is too big I don't get convergence
        self.beta = np.random.uniform(low=-1.0, high=1.0, size= (self.X.shape[1], 1))
        self.grad = None

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

    def forward(self):
            self.preds = self.sigmoid(np.dot(self.X, self.beta))

    def backward(self):
        self.grad = np.mean((self.preds - self.y)* self.X,
        axis=0, keepdims=True ).T
        assert self.beta.shape == self.grad.shape
        self.beta = self.beta -  self.lr * self.grad

    def calculate_loss(self, y, preds):
        preds = np.clip(preds, 1e-15, 1 - 1e-15)
        return np.mean(- y * np.log(preds) - (1-y)* np.log(1-preds))

    def fit(self, iters):
        for _ in range(iters):
            self.forward()
            self.backward()
            self.loss = self.calculate_loss(self.y, self.preds)

    def pred(self, X):
        return self.sigmoid(np.dot(X, self.beta))

    def accuracy(self, X, y):
        return np.mean((linear.pred(X) >=0.5) == y)

if __name__ == '__main__':
    from data_prep import Data
    data= Data()
    from sklearn import datasets
    data_ = datasets.load_breast_cancer()

    data.X = np.array(data_["data"])

    data.y = np.array(data_["target"]).reshape((len(data_["target"]), 1))

    feature_names = data_["feature_names"]

    #data.normalize()
    X_train, X_test, y_train, y_test =  data.split_data(data.X, data.y)

    linear= Logistic(X_train, y_train)
    linear.fit(iters=250)

    print('Beta from gradient descent:\n{}'.format(linear.beta))

    print('Training Binary cross entropy:{}'.format(linear.loss))
    X_test = np.concatenate((np.ones(y_test.shape), X_test), axis = 1)

    #print(X_test.shape, X_train.shape)

    print('Testing Binary cross entropy:{}'.format(
    linear.calculate_loss(y_test, linear.pred(X_test))
    ))
    print('Training accuracy:{}'.format(linear.accuracy(linear.X, linear.y)))
    print('Testing accuracy:{}'.format(linear.accuracy(X_test,y_test)))


import numpy as np

# function to simulate random decay
def simdecay(maxx_atoms, maxy_atoms, maxsteps, decay_constant):
    # Set up arrays with numbers of decayed and undecayed atoms for each step
    simatomsremaining = np.full(maxsteps+1,maxx_atoms*maxy_atoms,dtype='int')
    simatomsdecayed = np.zeros(maxsteps+1,dtype='int')

    # Set up array with undecayed atoms for each half sampleTime
    simatoms = np.ones((maxx_atoms, maxy_atoms, maxsteps + 1), dtype='int')

    # Set up array with random numbers to test against to see if atom has decayed
    randoms = np.random.rand(maxx_atoms, maxy_atoms, maxsteps + 1)
    # Set decay flag to 1 to make sure that no atoms will decay at initial time step
    randoms[:, :, 0] = 1.
    # Flag where decay occurs based on decay constant
    decay_flag = np.where(randoms >= decay_constant, 1, 0)
    for step in range(1, maxsteps+1):
        previous_step_atoms = simatoms[:, :, step - 1]
        current_step_atoms = simatoms[:, :, step]
        current_decays = decay_flag[:, :, step]
        current_step_atoms = current_step_atoms * current_decays * previous_step_atoms
        simatoms[:, :, step] = current_step_atoms
        simatomsremaining[step] = np.sum(simatoms[:, :, step])
        simatomsdecayed[step] = maxx_atoms * maxy_atoms - simatomsremaining[step]
    return simatoms, simatomsdecayed, simatomsremaining

# function to calculate analytical solution for exponential decay
def calcdecay(maxx_atoms, maxy_atoms, maxsteps, decay_constant):
    calctime = np.arange(0., float(maxsteps + 1), 0.1)
    calcdecay = float((maxx_atoms * maxy_atoms)) * np.exp(-decay_constant * calctime)
    return calctime, calcdecay


#!/usr/bin/python3
import os, sys
import torch
import math
import matplotlib.pyplot as plt
import matplotlib

# from drawnow import drawnow, figure
from random_fields import GaussianRF
from timeit import default_timer
import scipy.io

#w0: initial vorticity
#f: forcing term
#visc: viscosity (1/Re)
#T: final time
#delta_t: internal time-step for solve (descrease if blow-up)
#record_steps: number of in-time snapshots to record
def navier_stokes_2d(w0, f, visc, T, delta_t=1e-4, record_steps=1):

	#Grid size - must be power of 2
	N = w0.size()[-1]

	#Maximum frequency
	k_max = math.floor(N/2.0)

	#Number of steps to final time
	steps = math.ceil(T/delta_t)

	#Initial vorticity to Fourier space
	w_h = torch.rfft(w0, 2, normalized=False, onesided=False)

	#Forcing to Fourier space
	f_h = torch.rfft(f, 2, normalized=False, onesided=False)

	#If same forcing for the whole batch
	if len(f_h.size()) < len(w_h.size()):
		f_h = torch.unsqueeze(f_h, 0)

	#Record solution every this number of steps
	record_time = math.floor(steps/record_steps)

	#Wavenumbers in y-direction
	k_y = torch.cat((torch.arange(start=0, end=k_max, step=1, device=w0.device), torch.arange(start=-k_max, end=0, step=1, device=w0.device)), 0).repeat(N,1)
	#Wavenumbers in x-direction
	k_x = k_y.transpose(0,1)
	#Negative Laplacian in Fourier space
	lap = 4*(math.pi**2)*(k_x**2 + k_y**2)
	lap[0,0] = 1.0
	#Dealiasing mask
	dealias = torch.unsqueeze(torch.logical_and(torch.abs(k_y) <= (2.0/3.0)*k_max, torch.abs(k_x) <= (2.0/3.0)*k_max).float(), 0)

	#Saving solution and time
	sol = torch.zeros(*w0.size(), record_steps, device=w0.device)
	sol_t = torch.zeros(record_steps, device=w0.device)

	#Record counter
	c = 0
	#Physical time
	t = 0.0
	for j in range(steps):
		#Stream function in Fourier space: solve Poisson equation
		psi_h = w_h.clone()
		psi_h[...,0] = psi_h[...,0]/lap
		psi_h[...,1] = psi_h[...,1]/lap

		#Velocity field in x-direction = psi_y
		q = psi_h.clone()
		temp = q[...,0].clone()
		q[...,0] = -2*math.pi*k_y*q[...,1]
		q[...,1] = 2*math.pi*k_y*temp
		q = torch.irfft(q, 2, normalized=False, onesided=False, signal_sizes=(N,N))

		#Velocity field in y-direction = -psi_x
		v = psi_h.clone()
		temp = v[...,0].clone()
		v[...,0] = 2*math.pi*k_x*v[...,1]
		v[...,1] = -2*math.pi*k_x*temp
		v = torch.irfft(v, 2, normalized=False, onesided=False, signal_sizes=(N,N))

		#Partial x of vorticity
		w_x = w_h.clone()
		temp = w_x[...,0].clone()
		w_x[...,0] = -2*math.pi*k_x*w_x[...,1]
		w_x[...,1] = 2*math.pi*k_x*temp
		w_x = torch.irfft(w_x, 2, normalized=False, onesided=False, signal_sizes=(N,N))

		#Partial y of vorticity
		w_y = w_h.clone()
		temp = w_y[...,0].clone()
		w_y[...,0] = -2*math.pi*k_y*w_y[...,1]
		w_y[...,1] = 2*math.pi*k_y*temp
		w_y = torch.irfft(w_y, 2, normalized=False, onesided=False, signal_sizes=(N,N))

		#Non-linear term (u.grad(w)): compute in physical space then back to Fourier space
		F_h = torch.rfft(q*w_x + v*w_y, 2, normalized=False, onesided=False)

		#Dealias
		F_h[...,0] = dealias* F_h[...,0]
		F_h[...,1] = dealias* F_h[...,1]

		#Cranck-Nicholson update
		w_h[...,0] = (-delta_t*F_h[...,0] + delta_t*f_h[...,0] + (1.0 - 0.5*delta_t*visc*lap)*w_h[...,0])/(1.0 + 0.5*delta_t*visc*lap)
		w_h[...,1] = (-delta_t*F_h[...,1] + delta_t*f_h[...,1] + (1.0 - 0.5*delta_t*visc*lap)*w_h[...,1])/(1.0 + 0.5*delta_t*visc*lap)

		#Update real time (used only for recording)
		t += delta_t

		if (j+1) % record_time == 0:
			#Solution in physical space
			w = torch.irfft(w_h, 2, normalized=False, onesided=False, signal_sizes=(N,N))

			#Record solution and time
			sol[...,c] = w
			sol_t[c] = t

			c += 1

	return sol, sol_t

if '--cuda' in sys.argv:
	device = torch.device('cuda')
else:
	device = torch.device('cpu')

if '--test' in sys.argv:
	#Resolution
	s = 16
	sub = 1
	#Number of solutions to generate
	N = 4
	#Batch size
	bsize = 4
else:
	#Resolution
	s = 256
	sub = 1
	#Number of solutions to generate
	N = 20
	#Batch size
	bsize = 20

#Set up 2d GRF with covariance parameters
GRF = GaussianRF(2, s, alpha=2.5, tau=7, device=device)

#Forcing function: 0.1*(sin(2pi(x+y)) + cos(2pi(x+y)))
t = torch.linspace(0, 1, s+1, device=device)
t = t[0:-1]

X,Y = torch.meshgrid(t, t)
f = 0.1*(torch.sin(2*math.pi*(X + Y)) + torch.cos(2*math.pi*(X + Y)))

#Number of snapshots from solution
record_steps = 200

#Inputs
a = torch.zeros(N, s, s)
#Solutions
u = torch.zeros(N, s, s, record_steps)

#Solve equations in batches (order of magnitude speed-up)
c = 0
t0 =default_timer()
for j in range(N//bsize):

	#Sample random feilds
	w0 = GRF.sample(bsize)

	#Solve NS
	sol, sol_t = navier_stokes_2d(w0, f, 1e-3, 50.0, 1e-4, record_steps)

	a[c:(c+bsize),...] = w0
	u[c:(c+bsize),...] = sol

	c += bsize
	t1 = default_timer()
	print(j, c, t1-t0)

scipy.io.savemat('ns_data.mat', mdict={'a': a.cpu().numpy(), 'u': u.cpu().numpy(), 't': sol_t.cpu().numpy()})


## import numpy, panda, statsmodels
import pandas as pd
import urllib # import .dat file string
from pandas.compat import StringIO # read .dat file
import numpy as np
import scipy
from scipy.stats import f
import statsmodels.api as sm
import matplotlib as mpl
mpl.use('TkAgg') # backend adjustment
import matplotlib.pyplot as plt
from math import sqrt

## read csv dataset
url = 'https://raw.githubusercontent.com/sashaboulouds/statistics/master/data/housing.dat'
raw_data = urllib.urlopen(url)
housing_text = raw_data.read()
df = pd.read_csv(StringIO(housing_text), sep=r'\s{1,}', engine='python')

## set data
y = np.log(df['price'])
lnlotsize = np.log(df['lotsize'])
bedrms = df['bedrooms']
bathrms = df['bathrms']
airco = df['airco']
const = pd.DataFrame(np.repeat(1, len(airco)))

## concat
x = pd.concat([const, lnlotsize, bedrms, bathrms, airco], axis=1)
xprimex = x.transpose().dot(x)
xprimexinv = np.linalg.inv(xprimex)
xprimey = x.transpose().dot(y)
betahat= xprimexinv.dot(xprimey)

## regression
model1 = sm.OLS(y, x)
result1 = model1.fit()
print result1.summary()

## other matrix
epsilonhat=y-x.dot(betahat)
sigma2hat=(epsilonhat.transpose().dot(epsilonhat))/(len(y)-len(x.columns))
varbetahat = sigma2hat*xprimexinv
sebetahat= np.square(np.diag(varbetahat))

## zbetadiff
zbetadiff = (betahat[3]-betahat[2])/np.sqrt(varbetahat[3,3]+varbetahat[2,2]-2*varbetahat[2,3])

bedbath = bedrms + bathrms
variables_test = pd.concat([lnlotsize, bedbath, bathrms, airco], axis=1)
variables_test_model = sm.add_constant(variables_test)
housereg_test = sm.OLS(y, variables_test_model)
result_test = housereg_test.fit()
print result_test.summary()

## Ftest
ssru = sum(result1.resid**2)

## restricted model
airco_model = sm.add_constant(airco)
houserestrict = sm.OLS(y, airco_model)
result_houserestrict = houserestrict.fit()
ssrr = sum(result_houserestrict.resid**2)
nrestr = 3

Ftest = ((ssrr-ssru)/nrestr)/(ssru/(len(y)-len(result1.params)))

#critical value
f.ppf(0.95, nrestr, len(y)-len(result1.params))
sm.stats.anova_lm(result1, result_houserestrict)

# forecasting
lotf = np.log(5000)
bedf = 2
bathf = 2
aircof = 1

bhouse = result1.params
hforecast = bhouse[0] + bhouse[1]*lotf + bhouse[2]*bedf + bhouse[3]*bathf + bhouse[4]*aircof

lnlotsizef = lnlotsize - lotf
bedrmsf = bedrms-bedf
bathrmsf = bathrms - bathf
aircoff = airco - aircof

variables_final = pd.concat([lnlotsizef, bedrmsf, bathrmsf, aircoff], axis=1)
variables_final_model = sm.add_constant(variables_final)
houseregf = sm.OLS(y,variables_final_model)
print houseregf.fit().summary()
print result1.summary()

# to be verified
print result1.cov_HC0
print sm.stats.sandwich_covariance.cov_hac(result1)

'''
y=log(housing$price)

lnlotsize=log(housing$lotsize)
bedrms=housing$bedrooms
bathrms=housing$bathrms
airco=housing$airco
const=rep(1,length(airco))

x=cbind(const,lnlotsize,bedrms,bathrms,airco)

XprimeX=t(x)%*%x
XprimeXinv=solve(XprimeX)
XprimeY=t(x)%*%y
betahat=XprimeXinv%*%XprimeY

housereg=lm(y~lnlotsize+bedrms+bathrms+airco)
summary(housereg)

epsilonhat=y-x%*%betahat
sigma2hat=(t(epsilonhat)%*%epsilonhat)/(length(y)-ncol(x))
varbetahat=sigma2hat[1,1]*XprimeXinv
sebetahat=sqrt(diag(varbetahat))

zbetadiff=(betahat[4]-betahat[3])/sqrt(varbetahat[4,4]+varbetahat[3,3]-2*varbetahat[3,4])

bedbath=bedrms+bathrms
housereg_test=lm(y~lnlotsize+bedbath+bathrms+airco)
summary(housereg_test)

#Ftest
ssru=sum(housereg$residuals^2)

#restricted model
houserestrict=lm(y~airco)
ssrr=sum(houserestrict$residuals^2)
nrestr=3

Ftest=((ssrr-ssru)/nrestr)/(ssru/(length(y)-length(housereg$coefficients)))

#critical value
qf(0.95,nrestr,length(y)-length(housereg$coefficients))

anova(housereg,houserestrict)

#forecasting
lotf=log(5000)
bedf=2
bathf=2
aircof=1

bhouse=housereg$coefficients

hforecast=bhouse[1]+bhouse[2]*lotf+bhouse[3]*bedf+bhouse[4]*bathf+bhouse[5]*aircof

lnlotsizef=lnlotsize-lotf
bedrmsf=bedrms-bedf
bathrmsf=bathrms-bathf
aircoff=airco-aircof

houseregf=lm(y~lnlotsizef+bedrmsf+bathrmsf+aircoff)
summary(houseregf)

summary(housereg)

install.packages("sandwich")
install.packages("lmtest")

print(coeftest(housereg,vcov.=vcovHC,type=c("HC")))
print(coeftest(housereg,vcov.=NeweyWest))
'''

import numpy,import scipy,from scipy,import statsmodels


import sys
import pandas as pd
import numpy as np
import datetime
from scipy import stats
import matplotlib.pyplot as plt

OUTPUT_TEMPLATE = (
    "Initial T-test p-value: {initial_ttest_p:.3g}\n"
    "Original data normality p-values: {initial_weekday_normality_p:.3g} {initial_weekend_normality_p:.3g}\n"
    "Original data equal-variance p-value: {initial_levene_p:.3g}\n"
    "Transformed data normality p-values: {transformed_weekday_normality_p:.3g} {transformed_weekend_normality_p:.3g}\n"
    "Transformed data equal-variance p-value: {transformed_levene_p:.3g}\n"
    "Weekly data normality p-values: {weekly_weekday_normality_p:.3g} {weekly_weekend_normality_p:.3g}\n"
    "Weekly data equal-variance p-value: {weekly_levene_p:.3g}\n"
    "Weekly T-test p-value: {weekly_ttest_p:.3g}\n"
    "Mann-Whitney U-test p-value: {utest_p:.3g}"
)

def main():
    reddit_counts = sys.argv[1]
    counts = pd.read_json(sys.argv[1], lines=True)
    counts = counts[(counts['date'] >= '2012-01-01') & (counts['date'] <= '2013-12-31')] # Values in 2012 and 2013.
    counts = counts[(counts['subreddit'] == 'canada')] # Values in Canada.

    weekday = counts[(counts['date'].map(lambda x: datetime.date.weekday(x) == 0)) | (counts['date'].map(lambda x: datetime.date.weekday(x) == 1)) | (counts['date'].map(lambda x: datetime.date.weekday(x) == 2)) |
                     (counts['date'].map(lambda x: datetime.date.weekday(x) == 3)) | (counts['date'].map(lambda x: datetime.date.weekday(x) == 4))]
    weekend = counts[(counts['date'].map(lambda x: datetime.date.weekday(x) == 5)) | (counts['date'].map(lambda x: datetime.date.weekday(x) == 6))]

    initial_weekday = weekday['comment_count']
    initial_weekend = weekend['comment_count']

    # print('Weekday comments posted is %d.' %)

    # Student's T-Test.
    initial_ttest_p = stats.ttest_ind(initial_weekday, initial_weekend).pvalue
    initial_weekday_normality_p = stats.normaltest(initial_weekday).pvalue
    initial_weekend_normality_p = stats.normaltest(initial_weekend).pvalue
    initial_levene_p = stats.levene(initial_weekday, initial_weekend).pvalue
    # weekday['comment_count'].hist()
    # weekend['comment_count'].hist()
    # plt.title('Fix 1.0')
    # plt.show()

    # Fix 1: transforming data might save us.
    '''
    # Fix 1.1: log.
    log_weekday = initial_weekday.apply(np.log)
    log_weekend = initial_weekend.apply(np.log)
    weekday['comment_count'].apply(np.log).hist()
    weekend['comment_count'].apply(np.log).hist()
    plt.title('Fix 1.1: log.')
    # The above codes are adapted from https://datascience.stackexchange.com/questions/22957/am-i-doing-a-log-transformation-of-data-correctly.
    log_weekday_normality_p = stats.normaltest(log_weekday).pvalue
    log_weekend_normality_p = stats.normaltest(log_weekend).pvalue
    log_levene_p = stats.levene(log_weekday, log_weekend).pvalue
    print('Summary of Fix 1.1: log.')
    print('Weekday normality pvalue is %f.' % log_weekday_normality_p)
    print('Weekend normality pvalue is %f.' % log_weekend_normality_p)
    print('Leneve pvalue is %f.' % log_levene_p)
    print()
    '''

    '''
    # Fix 1.2: exp.
    exp_weekday = initial_weekday.apply(np.exp)
    exp_weekend = initial_weekend.apply(np.exp)
    # weekday['comment_count'].apply(np.exp).hist()
    # weekend['comment_count'].apply(np.exp).hist()
    plt.title('Fix 1.2: exp.')
    plt.show()
    # The above codes are adapted from https://datascience.stackexchange.com/questions/22957/am-i-doing-a-log-transformation-of-data-correctly.
    exp_weekday_normality_p = stats.normaltest(exp_weekday).pvalue
    exp_weekend_normality_p = stats.normaltest(exp_weekend).pvalue
    exp_levene_p = stats.levene(exp_weekday, exp_weekend).pvalue
    print('Summary of Fix 1.2: exp.')
    print('Weekday normality pvalue is %f.' % exp_weekday_normality_p)
    print('Weekend normality pvalue is %f.' % exp_weekend_normality_p)
    print('Leneve pvalue is %f.' % exp_levene_p)
    print()
    '''

    # Fix 1.3: sqrt.
    sqrt_weekday = initial_weekday.apply(np.sqrt)
    sqrt_weekend = initial_weekend.apply(np.sqrt)
    # weekday['comment_count'].apply(np.sqrt).hist()
    # weekend['comment_count'].apply(np.sqrt).hist()
    # plt.title('Fix 1.3: sqrt.')
    # plt.show()
    # The above codes are adapted from https://datascience.stackexchange.com/questions/22957/am-i-doing-a-log-transformation-of-data-correctly.
    sqrt_weekday_normality_p = stats.normaltest(sqrt_weekday).pvalue
    sqrt_weekend_normality_p = stats.normaltest(sqrt_weekend).pvalue
    sqrt_levene_p = stats.levene(sqrt_weekday, sqrt_weekend).pvalue
    # print('Summary of Fix 1.3: sqrt.')
    # print('Weekday normality pvalue is %f.' % sqrt_weekday_normality_p)
    # print('Weekend normality pvalue is %f.' % sqrt_weekend_normality_p)
    # print('Leneve pvalue is %f.' % sqrt_levene_p)
    # print()

    '''
    # Fix 1.4: squared.
    squared_weekday = initial_weekday**2
    squared_weekend = initial_weekend**2
    squared_weekday.hist()
    squared_weekend.hist()
    plt.title('Fix 1.4: squared.')
    plt.show()
    # The above codes are adapted from https://datascience.stackexchange.com/questions/22957/am-i-doing-a-log-transformation-of-data-correctly.
    squared_weekday_normality_p = stats.normaltest(squared_weekday).pvalue
    squared_weekend_normality_p = stats.normaltest(squared_weekend).pvalue
    squared_levene_p = stats.levene(squared_weekday, squared_weekend).pvalue
    print('Summary of Fix 1.4: squared.')
    print('Weekday normality pvalue is %f.' % squared_weekday_normality_p)
    print('Weekend normality pvalue is %f.' % squared_weekend_normality_p)
    print('Leneve pvalue is %f.' % squared_levene_p)
    print()
    '''

    # Fix 2: the Central Limit Theorem might save us.
    # Combine all weekdays and weekend days from each year/week pair and take the mean of their (non-transformed) counts.
    format_weekday = weekday['date'].apply(datetime.date.isocalendar)
    format_weekday = format_weekday.apply(pd.Series)
    format_weekend = weekend['date'].apply(datetime.date.isocalendar)
    format_weekend = format_weekend.apply(pd.Series)
    format_weekday = format_weekday[[0,1]]
    format_weekend = format_weekend[[0,1]]
    format_date = ['year', 'week']
    format_weekday.columns = format_date
    format_weekend.columns = format_date
    weekday = pd.concat([weekday, format_weekday], axis = 'columns', ignore_index = False)
    weekend = pd.concat([weekend, format_weekend], axis = 'columns', ignore_index = False)
    mean_weekday = weekday.groupby(format_date).mean()['comment_count']
    mean_weekend = weekend.groupby(format_date).mean()['comment_count']
    # Check these values for normality and equal variance.
    weekly_weekday_normality_p = stats.normaltest(mean_weekday).pvalue
    weekly_weekend_normality_p = stats.normaltest(mean_weekend).pvalue
    weekly_levene_p = stats.levene(mean_weekday, mean_weekend).pvalue
    weekly_ttest_p = stats.ttest_ind(mean_weekday, mean_weekend).pvalue

    # Fix 3: a non-parametric test might save us.
    utest_p = stats.mannwhitneyu(initial_weekday, initial_weekend, alternative = 'two-sided').pvalue

    print(OUTPUT_TEMPLATE.format(
        initial_ttest_p = initial_ttest_p,
        initial_weekday_normality_p = initial_weekday_normality_p,
        initial_weekend_normality_p = initial_weekend_normality_p,
        initial_levene_p = initial_levene_p,
        transformed_weekday_normality_p = sqrt_weekday_normality_p,
        transformed_weekend_normality_p = sqrt_weekend_normality_p,
        transformed_levene_p = sqrt_levene_p,
        weekly_weekday_normality_p = weekly_weekday_normality_p,
        weekly_weekend_normality_p = weekly_weekend_normality_p,
        weekly_levene_p = weekly_levene_p,
        weekly_ttest_p = weekly_ttest_p,
        utest_p = utest_p,
    ))

if __name__ == '__main__':
    main()


# Required Python Packages
import numpy as np
import matplotlib.pyplot as plt

def sigmoid(inputs):
    """
    Calculate the sigmoid for the give inputs (array)
    :param inputs:
    :return:
    """
    sigmoid_scores = [1 / float(1 + np.exp(- x)) for x in inputs]
    return sigmoid_scores

def line_graph(x, y, x_title, y_title):
    """
    Draw line graph with x and y values
    :param x:
    :param y:
    :param x_title:
    :param y_title:
    :return:
    """
    plt.plot(x, y)
    plt.xlabel(x_title)
    plt.ylabel(y_title)
    plt.show()

graph_x = range(0, 21)
graph_y = sigmoid(graph_x)

print "Graph X readings: {}".format(graph_x)
print "Graph Y readings: {}".format(graph_y)

line_graph(graph_x, graph_y, "Inputs", "Sigmoid Scores")


import numpy as np
import logging
import os
from tqdm import tqdm

logging_str = "[%(asctime)s: %(levelname)s: %(module)s] %(message)s"
log_dir = "logs"
os.makedirs(log_dir, exist_ok=True)
logging.basicConfig(filename = os.path.join(log_dir,"running_logs.log"),level=logging.INFO, format=logging_str)

class Perceptron:
  """[A Perceptron model creation class]
  """
  def __init__(self,eta,epochs,no_of_input):
    self.weights = np.random.randn(no_of_input+1) *1e-4 #small weight initialization
    logging.info(f"initial weights before training: {self.weights}")
    self.eta = eta #learning rate
    self.epochs = epochs #no of epochs

  def activationFunction(self,inputs,weights):
    z = np.dot(inputs, weights) # z = X * W
    return np.where(z > 0, 1, 0) # if >0 return 1 else 0

  def fit(self,X,y):  #training
    self.X = X
    self.y = y

    x_with_bias = np.c_[X, -np.ones((len(X),1))] #len is no of rows of X np.c_ is concatinaete
    logging.info(f"X with bias: \n{x_with_bias}")

    for epoch in tqdm(range(self.epochs), total = self.epochs, desc = "training the model"):
      logging.info("__"*10)
      logging.info(f"for epoch:{epoch}")
      logging.info("__"*10)

      y_hat = self.activationFunction(x_with_bias,self.weights) #forward propagation
      logging.info(f"predicted value after forward pass: \n{y_hat}")
      self.error = self.y - y_hat
      logging.info(f"error: \n{self.error}")
      self.weights = self.weights + self.eta * np.dot(x_with_bias.T,self.error) #backward propagation
      logging.info(f"corrected weights after epoch:{epoch}/{self.epochs} :{self.weights} ")
      logging.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>")

  def predict(self,X):
    x_with_bias = np.c_[X, -np.ones((len(X), 1))]
    return self.activationFunction(x_with_bias,self.weights)

  def total_loss(self):
    total_loss = np.sum(self.error)
    logging.info(f"Total loss : {total_loss}")

    if total_loss == 0:
      msg = "MODEL CREATION SUCCESSFUL"
    else:
      msg = "MODEL CREATION FAILED, Try with different, epochs, learning rate or a different approach altogether"
    logging.info(msg)

    logging.info(f"total loss = {str(total_loss)}\n")
    logging.info(msg+"\n")

    return total_loss


#
#  Copyright 2019 The FATE Authors. All Rights Reserved.
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
#

import numpy as np

def hard_sigmoid(x):
    y = 0.2 * x + 0.5
    return np.clip(y, 0, 1)

def softmax(x, axis=-1):
    y = np.exp(x - np.max(x, axis, keepdims=True))
    return y / np.sum(y, axis, keepdims=True)

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

def softplus(x):
    return np.log(1. + np.exp(x))

def softsign(x):
    return x / (1 + np.abs(x))

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


import numpy as np

def scale(points, scale_factor=(1, 1, 1)):
    transformation_matrix = np.eye(3)
    np.fill_diagonal(transformation_matrix, scale_factor)
    points[:, :3] = np.dot(points[:, :3], transformation_matrix)
    return points

def rotate_around_axis(points, axis, angle, center_point=None):
    """
    Return the rotation matrix associated with counterclockwise rotation about
    the given axis by angle in radians.
    https://stackoverflow.com/questions/6802577/rotation-of-3d-vector
    """
    if center_point is None:
        center_point = points[:, :3].mean(axis=0).astype(points[:, :3].dtype)
    axis = axis / np.sqrt(np.dot(axis, axis))
    a = np.cos(angle / 2.0)
    b, c, d = -axis * np.sin(angle / 2.0)
    aa, bb, cc, dd = a * a, b * b, c * c, d * d
    bc, ad, ac, ab, bd, cd = b * c, a * d, a * c, a * b, b * d, c * d
    rotation_matrix = np.array(
        [
            [aa + bb - cc - dd, 2 * (bc + ad), 2 * (bd - ac)],
            [2 * (bc - ad), aa + cc - bb - dd, 2 * (cd + ab)],
            [2 * (bd + ac), 2 * (cd - ab), aa + dd - bb - cc],
        ]
    )
    points[:, :3] = points[:, :3] - center_point
    points[:, :3] = np.dot(points[:, :3], rotation_matrix.T)
    points[:, :3] = points[:, :3] + center_point
    return points

def crop(points, x_min, y_min, z_min, x_max, y_max, z_max):
    if x_max <= x_min or y_max <= y_min or z_max <= z_min:
        raise ValueError(
            "We should have x_min < x_max and y_min < y_max and z_min < z_max. But we got"
            " (x_min = {x_min}, y_min = {y_min}, z_min = {z_min},"
            " x_max = {x_max}, y_max = {y_max}, z_max = {z_max})".format(
                x_min=x_min,
                x_max=x_max,
                y_min=y_min,
                y_max=y_max,
                z_min=z_min,
                z_max=z_max,
            )
        )
    inds = np.all(
        [
            (points[:, 0] >= x_min),
            (points[:, 0] < x_max),
            (points[:, 1] >= y_min),
            (points[:, 1] < y_max),
            (points[:, 2] >= z_min),
            (points[:, 2] < z_max),
        ],
        axis=0,
    )
    return inds

def center(points, origin=(0, 0, 0)):
    points[:, :3] -= origin + points[:, :3].mean(axis=0)
    return points

def move(points, offset=(0, 0, 0)):
    points[:, :3] = points[:, :3] + offset
    return points

def flip_coordinates(points, axis):
    axis = np.argmax(axis)
    coord_max = np.max(points[:, axis])
    points[:, axis] = coord_max - points[:, axis]
    return points


# -*- coding: utf-8 -*-
"""
Created on Sat Jan  2 2021

@author: JI YIHONG
"""
import networkx as nx
import bellmanford as bf
import numpy as np
from os.path import dirname, join
project_root = dirname(dirname(__file__))
data_path = join(project_root, 'Data', '')
data_dir = data_path + 'p082_matrix.txt'

def readDataAsArray(file):
    content = []
    with open(file,'r') as f:
        for line in f:
            content.append(line.strip().split(","))

    matrix = np.array(content).astype(int)
    return matrix

class ShortestPath:
    def __init__(self, distArray):
        self.distArray = distArray

    def createGraph(self):

        nrows, ncols = self.distArray.shape

        G = nx.DiGraph()
        # Add nodes
        G.add_node(0)  # source node
        for row_ix in range(nrows):
            for col_ix in range(ncols):
                G.add_node((row_ix, col_ix))
        G.add_node(1)  # sink node

        # Add edges

        for row_ix in range(nrows):
            G.add_edge(0, (row_ix, 0), length=self.distArray[row_ix, 0])

            for col_ix in range(ncols):
                # up
                G.add_edge((row_ix, col_ix), (max(row_ix-1, 0), col_ix),
                           length=self.distArray[max(row_ix-1, 0), col_ix])

                # down
                G.add_edge((row_ix, col_ix), (min(row_ix + 1, nrows - 1), col_ix),
                           length=self.distArray[min(row_ix + 1, nrows - 1), col_ix])

                # right
                G.add_edge((row_ix, col_ix), (row_ix, min(col_ix+1, ncols-1)),
                           length=self.distArray[row_ix, min(col_ix+1, ncols-1)])

            G.add_edge((row_ix, ncols-1), 1, length=0)

        return G

    def solve(self):
        graph = self.createGraph()
        path_length, path_nodes, negative_cycle = bf.bellman_ford(graph, source=0, target=1, weight="length")
        print("Is there a negative cycle? {0}".format(negative_cycle))
        print("Shortest path length: {0}".format(path_length))
        print("Shortest path: {0}".format(path_nodes))
        return path_length

if __name__ == '__main__':
    data = readDataAsArray(data_dir)
    solver = ShortestPath(data)
    print(solver.solve())


# Linear eq soln by Gauss-Jordan elimination. A is NxN matrix
# stored in NPxNP array. B is an input matrix of NxM containing
# the M right-hand side vectors stored in NPxMP array. Output
# A^-1 is A inverse, stored in A, solution to AX = B is stored in B
import numpy as np
from numpy import linalg as LA
from scipy.sparse.linalg import cg
from scipy.linalg import cho_factor, cho_solve
import csv

def gauss_jordan(A,N,b):
    # Custom Gauss-Jordan Elimination Algo

    # Notes from 9/11-13 2021 (Math folder)
    A_org = np.copy(A) # Original A (need for tests)
    b_org = np.copy(b)
    A_inv = np.eye(N) # A inverse initialized as identity matrix
    P_gen = np.eye(N) # General Permutation matrix
    P = np.eye(N) # Permutation matrix for this column pivot
    # Reduce to upper tridiagonal coefficient matrix
    for i in range(0, N-1):
        # Find row of pivot element in column i
        i_max = i
        for j in range(i+1,N):
          if (abs(A[i_max,i]) < abs(A[j,i])): i_max = j
        if (A[i_max,i] < 0.0):
            A[i_max,0:] = A[i_max,0:]*-1.0 # Switch signs
            A_inv[i_max,0:] = A_inv[i_max,0:]*-1.0
            b[i_max] = b[i_max]*-1.0
        # Move pivot row so column is on diagonal
        if (i_max != i): # Swap rows
            P[i_max, i_max] = 0.0
            P[i, i] = 0.0
            P[i_max, i] = 1.0 # Move row i to row i_max
            P[i, i_max] = 1.0 # Move row i_max to row i
            A = np.matmul(P,A)
            A_inv = np.matmul(P,A_inv)
            b = np.matmul(P,b)
            P_gen = np.matmul(P, P_gen) # !compt. intensive!
        # Pivot
        fac = A[i,i]
        b[i] = b[i]/fac # A[i,i] is now pivot element
        A_inv[i,0:] = A_inv[i,0:]/fac
        A[i,0:] = A[i,0:]/fac # pivot element becomes 1
        for j in range(i+1,N): # Other rows
            if  (A[j,i] != 0.0): # skip rows with column element = 0
                fac = A[j,i] # factor for multiplying pivot row
                A[j,0:] -= fac*A[i,0:]
                A_inv[j,0:] -= fac*A_inv[i,0:]
                b[j] -= fac*b[i]
    if (A[N-1,N-1] != 0.0):
        fac = A[N-1,N-1]
        b[N-1] = b[N-1]/fac # last row
        A_inv[N-1,0:] = A_inv[N-1,0:]/fac
        A[N-1,0:] = A[N-1,0:]/fac
    elif (b[N-1] != 0.0): # No solution here
        raise Exception('Singular matrix: 0*x_N != 0.0')

    # Check: Matrix is in upper tridiagonal form
    if (LA.norm(A-np.triu(A)) <= 1.0E-14):
        print('>> A reduced to upper tridiagonal matrix! <<')
    else:
        print('> Partial Reduction Failed!')
        return b, A

    # Back sub for trailing terms (full pivoting)
    for i in range(N-1,0,-1): # Start: bottom row cancels out other rows
        for j in range(i-1,-1,-1): # Other rows
            if (A[j,i] != 0.0):
                fac = A[j,i]
                A[j,0:] -= fac*A[i,0:]
                A_inv[j,0:] -= fac*A_inv[i,0:]
                b[j] -= fac*b[i]

    # Revert solutions back to original order
    P_trans = np.transpose(P)
    A_inv_perm = np.matmul(P_trans,A_inv)

    # Solution Test: norm(x - (A_inv x b)) < some precision number
    l2_res = LA.norm(b-np.matmul(A_inv,b_org))

    # Check: Full pivoting has turned A into identity matrix
    if (LA.norm(A-np.eye(N))<1.0E-14):
        return b, A_inv
    else:
        print('> Full Pivoting Failed!')
        return b, A

def gauss_seidel(A,N,b,x0,tol):
    # Gauss-Seidel: x0 is the initial solution
    # No inverse matrix to return, just iteration states

    ## Initialize
    x = np.copy(x0)
    dif = np.matmul(A,x)-b
    res = np.array([LA.norm(dif)],dtype=float)
    if (N < 10):
        # x_out = np.copy(x0).T # tracking solution
        x_out = np.reshape(x0,(N,1))
    # tol = 1E-8
    iter = 0
    max_iter = 1E5
    # print('> Gauss-Seidel Initial Residual: {}'.format(res[0]))

    # Output
    varname = []
    NVar = 0

    ## Iteration Loop
    res_len = len(res)
    while ((res[res_len-1] > tol) & (iter < max_iter)):
        iter += 1
        for i in range(0,N): # Loop through equations
            x_sum = b[i]
            for j in range(0,N):
                if (i != j): x_sum -= A[i,j]*x[j]
            x[i] = x_sum/A[i,i]

        if ((iter % 10) == 0):
            dif = np.matmul(A,x)-b
            res_iter = np.array(LA.norm(dif))
            res = np.append(res,res_iter) # check this code
            res_len = len(res)
        # print('> Gauss-Seidel Residual at iter {}: {}'.format(iter,res_iter))

        if (N < 10):
            x_out = np.hstack((x_out,np.reshape(x,(N,1))))

    if (res[res_len-1] <= tol):
        print('>> Gauss-Seidel Converged at iteration',iter,\
            '! tol =',tol,', res =',res[res_len-1])
    else:
        print('> Gauss-Seidel Divergence at iteration',iter,\
            '! tol =',tol,', res =',res[res_len-1])
    print_sol('Gauss-Seidel_Sol',A,A,x,np.matmul(A,x),N)

    # Output if not too many elements
    if (N < 10):
        print_mat4('GS_Track',3,['A','x','b'],A,x_out,b)
        print('x shape, N, iter:',x_out.shape,N, iter)
        fname = 'GS_Sol_Track.csv'.format(i) # file name
        with open(fname, 'w', newline = '') as f:
            mat_writer = csv.writer(f, delimiter = ',')
            for j in range(0,N):
                mat_writer.writerow(x_out[j,0:])

    return x, res[res_len-1], iter

def print_mat3(i, A, A_inv, P, b, N):
    # Print the A, inverse A, permutation P matrices, and b vector
    # in formatted form (CSV file). N is length of b-vector and dimensional length of every matrix

    fname = 'mat_{}.csv'.format(i) # file name
    f_header = 'A'*N + 'a'*N + 'b' + 'P'*N
    # Create writer object
    with open(fname, 'w', newline = '') as f:
        mat_writer = csv.writer(f, delimiter = ',')
        mat_writer.writerow(f_header)
        for j in range(0,N):
            mat_writer.writerow(np.concatenate((A[j,0:],np.append(A_inv[j,0:],b[j]),P[j,0:])))

    return None

def print_mat4(iname,NVar,varname,*args):
    # Print the A, inverse A, permutation P matrices, and b vector
    # in formatted form (CSV file). N is length of b-vector and dimensional length of every matrix
    # Input
    # iname: File name
    # NVar: Number of variables to print
    # varname: Variable names given in array.
    # *args: matrices/vectors to be printed to csv, in order given by varname
    # Must be same number of variable names and args as NVar

    # Check & Unpack
    if (len(args) != NVar): raise Exception('print_mat4: Wrong number of variables!')
    if (len(varname) != NVar): raise Exception('print_mat4: NVar != # of Variable Names!')
    N = len(args[0])
    Ncol = np.zeros(NVar, dtype=int) # number of columns
    mat_out = args[0] # initialize
    Ncol[0] = args[0].shape[1]
    for j in range(1,NVar):
        if ((args[j].size % N) == 0): # check array sizes are compatible
            if (args[j].ndim == 1):
                mat_out = np.hstack((mat_out,args[j].reshape(N,1)))
                Ncol[j] = 1
            elif (args[j].ndim == 2):
                if (args[j].shape[0] == N):
                    mat_out = np.hstack((mat_out,args[j]))
                    Ncol[j] = args[j].shape[1]
                elif (args[j].shape[1] == N): # assuming just transpose
                    mat_out = np.hstack((mat_out,np.transpose(args[j])))
                    Ncol[j] = np.transpose(args[j]).shape[1]
                    print('print_mat4: Check argument transpose, N = ',N)
                else:
                    mat_out = np.hstack((mat_out,\
                        args[j].reshape(N,int(args[j].size/N))))
                    Ncol[j] = args[j].reshape(N,int(args[j].size/N)).shape
                    print('print_mat4: Check argument shape, N = ',N)
        else:
            raise Exception('print_mat4: Argument size is not correct:',\
                N,args[j].shape)

    # Output
    fname = 'mat_{}.csv'.format(iname) # file name
    f_header = np.empty(0,dtype=str) # Figure this out
    for j in range(0,NVar):
        for i in range(0,Ncol[j]):
            f_header = np.append(f_header,varname[j])
    # header must know NVar and NCol for each variable
    # Create writer object
    with open(fname, 'w', newline = '') as f:
        mat_writer = csv.writer(f, delimiter = ',')
        mat_writer.writerow(f_header)
        for j in range(0,N):
            mat_writer.writerow(mat_out[j,0:])
    return None

def print_mat1(i, A, N):
    # Print the NxN-sized A matrix in formatted form (CSV file).
    # i: designation (number or name)
    # N: size of matrix
    fname = 'mat_{}.csv'.format(i) # file name
    # Create writer object
    with open(fname, 'w', newline = '') as f:
        mat_writer = csv.writer(f, delimiter = ',')
        for j in range(0,N):
            mat_writer.writerow(A[j,0:])

    return None

def print_sol(i, A, A_inv, x, b, N):
    # Print the A and inverse A matrices and x and b vectors in formatted
    # form (CSV file). N is length of b-vector and dimensional length of every matrix

    fname = 'mat_{}.csv'.format(i) # file name
    # Create writer object
    with open(fname, 'w', newline = '') as f:
        mat_writer = csv.writer(f, delimiter = ',')
        for j in range(0,N):
            mat_writer.writerow(np.concatenate(\
                (A[j,0:],A_inv[j,0:],[x[j],b[j]])))

    return None

def rand_SPD_Axb(N,gen_num):
    # Random A, x, and corresponding generation for Ax = b problem
    rng = np.random.default_rng(gen_num)
    # A: symmetric positive definite
    A_seed = rng.integers(low=-5, high=5, size=(N,N)).astype(np.float)
    A_seed_T = np.transpose(A_seed)
    A = (np.abs(A_seed) + np.abs(A_seed_T))*0.5
    A += np.diag(np.ones(N)*N)
    A_org = A
    print('Determinant of A:',LA.det(A))
    x = rng.integers(low=-N, high=N, size=N).astype(np.float)
    b = np.matmul(A,x)
    print_mat1('A_Org',A,N)
    print_mat3('Original',A,np.eye(N),np.eye(N),b,N)

    return A, x, b

def row_reduc(A_org):
    N,M = A_org.shape
    if (N != M): raise Exception('Matrix must be square! Size:',N,',',M)

    # Setup
    A = np.copy(A_org)
    A_inv = np.eye(N) # A inverse initialized as identity matrix
    P_gen = np.eye(N) # General Permutation matrix
    P = np.eye(N) # Permutation matrix for this column pivot

    # Reduce to upper tridiagonal coefficient matrix
    for i in range(0, N-1):
        # Find row of pivot element in column i
        i_max = i
        for j in range(i+1,N):
          if (abs(A[i_max,i]) < abs(A[j,i])): i_max = j
        if (A[i_max,i] < 0.0):
            A[i_max,0:] = A[i_max,0:]*-1.0 # Switch signs
            A_inv[i_max,0:] = A_inv[i_max,0:]*-1.0
        # Move pivot row so column is on diagonal
        if (i_max != i): # Swap rows
            P[i_max, i_max] = 0.0
            P[i, i] = 0.0
            P[i_max, i] = 1.0 # Move row i to row i_max
            P[i, i_max] = 1.0 # Move row i_max to row i
            A = np.matmul(P,A)
            A_inv = np.matmul(P,A_inv)
            P_gen = np.matmul(P, P_gen) # !compt. intensive!
        # Pivot
        fac = A[i,i]
        A_inv[i,0:] = A_inv[i,0:]/fac
        A[i,0:] = A[i,0:]/fac # pivot element becomes 1
        for j in range(i+1,N): # Other rows
            if  (A[j,i] != 0.0): # skip rows with column element = 0
                fac = A[j,i] # factor for multiplying pivot row
                A[j,0:] -= fac*A[i,0:]
                A_inv[j,0:] -= fac*A_inv[i,0:]
    if (A[N-1,N-1] != 0.0):
        fac = A[N-1,N-1]
        A_inv[N-1,0:] = A_inv[N-1,0:]/fac
        A[N-1,0:] = A[N-1,0:]/fac
    return A

# # Test: Create random A (10*10) and x (10*1) arrays, multiply to get b, then solve for x using A and b.
# rng2 = np.random.default_rng()
# N = rng2.integers(6,14)
# gen_num = rng2.integers(10,100000)
# A, x, b = rand_SPD_Axb(N, gen_num)
# A_org, x_org = np.copy(A), np.copy(x)

# A_red = row_reduc(A)
# print_mat1('rand_red',A_red,N)

# # Gauss-Seidel
# [x_gs, res_gs, iter_gs] = gauss_seidel(A, N, b, np.ones(N,dtype=float)*0.1,1E-8)
# print_mat3('GS_Final',A,A_org,np.diag(x),x_gs,N)
# print_mat4('GS_Final2',4,['A','b','x_true','x_gs'],A,b,x,x_gs)

# # Gauss-Jordan
# [x_gj, A_inv_gj] = gauss_jordan(A, N, b)
# print('A_inv shape:',np.shape(A_inv_gj))
# print('x shape:',np.shape(x_gj.shape))
# print_mat3('GJ_Final',A,A_inv_gj,np.diag(x),x_gj,N)

# # Conjugate Gradient
# [x_cg, info_cg] = cg(A_org, b)
# print_mat3('CG_Final',A,A_org,np.diag(x),x_cg,N)

# # Cholesky Factorization
# c_cho, low = cho_factor(A_org)
# x_cho = cho_solve((c_cho, low), b)
# print_mat3('Cho_Final',A,A_org,np.diag(x),x_cho,N)

# # Result Reporting
# print('Residuals: G-Seidel | G-Jordan | Conj. Grad | Cholesky')
# print('----------------------------------------------------------------')
# print(LA.norm(x_org-x_gs),' | ',LA.norm(x_org-x_gj),' | '\
#     ,LA.norm(x_org-x_cg),' | ',LA.norm(x_org-x_cho))


import numpy as np
import matplotlib.pyplot as plt
from problem_2a import QR_fact_iter

def back_subsitute(U, bb):
	n = U.shape[1]
	x = np.zeros(n)
	for j in range(n - 1, -1, -1):   # loop backwards over columns
		if U[j, j] == 0:
			raise RuntimeError("singular matrix")

		x[j] = bb[j] / U[j, j]
		for i in range(0, j):
			bb[i] -= U[i, j] * x[j]

	return x

def fit_poly_on_data(data, order=1):
	A = np.array([[float(i) ** j for j in range(order+1)] for i in range(1, len(data) + 1)])
#     print(A)
	b = data
	Q, R = QR_fact_iter(A)
	d = np.dot(Q.T, b)
	c = back_subsitute(R, d)
	return c

def str_poly(coeff):
	if len(coeff) > 6:
		raise RuntimeError("Degree > 5. Please handle.")

	template = {0 : "", 1 : "x", 2 : "x^2", 3 : "x^3", 4 : "x^4", 5 : "x^5",}
	letters = ["a", "b", "c", "d", "e", "f"]

	string = ""
	values = "\n\n"

	for i in range(len(coeff), 1, -1):

		string = string + letters[len(coeff) - i] + " * " + template[i - 1] + " + "
		values = values + letters[len(coeff) - i] + " = " + str(coeff[i - 1]) + "\n"

	string = string + letters[len(coeff) - 1]
	values = values + letters[len(coeff) - 1] + " = " + str(coeff[0])
	string = string + values

	return string

def get_relative_residual(A, x, b):
	return np.linalg.norm(np.dot(A, x) - b, ord=2) / np.linalg.norm(b, ord=2)

def main():
	v = np.loadtxt("petrol_price_delhi.txt", delimiter="\n")
	Y_cal = list()
	Y_true = list()

	for i in range(1, 6):
		print("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++")
		print("Results for Degree:", i)
		coeff_cal = fit_poly_on_data(v, order=i)
		A = np.array([[float(j) ** k for k in range(i+1)] for j in range(1, len(v) + 1)])
		coeff_true = np.linalg.lstsq(A, v, rcond=None)[0]
		rel_res_cal = get_relative_residual(A, coeff_cal, v)
		rel_res_true = get_relative_residual(A, coeff_true, v)
		str_poly_cal = str_poly(coeff_cal)
		str_ploy_true = str_poly(coeff_true)
		print("\n\n")
		print("------------------------------------------------------------------------")
		print("Strategy: np.linalg.lstsq")
		print("------------------------------------------------------------------------")
		print("Approximate Polynomial:\n\n")
		print(str_ploy_true)
		print("\n\n")
		print("Relative Residual:", rel_res_true)
		print("\n\n")
		print("------------------------------------------------------------------------")
		print("Strategy: get_QR()")
		print("------------------------------------------------------------------------")
		print("Approximate Polynomial:\n\n")
		print(str_poly_cal)
		print("\n\n")
		print("Relative Residual:", rel_res_cal)
		print("\n\n")
		Y_cal.append(np.dot(A, coeff_cal))
		Y_true.append(np.dot(A, coeff_true))

	plt.figure(figsize=(10, 10))
	plt.subplots_adjust(hspace=8)
	plt.subplot(2, 1, 1)
	plt.scatter(np.arange(1, len(v) + 1), v, marker="+", c="black", label="Original Data")
	# plt.plot(np.arange(1, len(v) + 1), Y_true[0], np.arange(1, len(v) + 1), Y_true[1], np.arange(1, len(v) + 1), Y_true[2], np.arange(1, len(v) + 1), Y_true[3], np.arange(1, len(v) + 1), Y_true[4])
	plt.plot(np.arange(1, len(v) + 1), Y_true[0], label="Degree = 1")
	plt.plot(np.arange(1, len(v) + 1), Y_true[1], label="Degree = 2")
	plt.plot(np.arange(1, len(v) + 1), Y_true[2], label="Degree = 3")
	plt.plot(np.arange(1, len(v) + 1), Y_true[3], label="Degree = 4")
	plt.plot(np.arange(1, len(v) + 1), Y_true[4], label="Degree = 5")
	plt.xlabel("Day")
	plt.ylabel("Price of petrol (Rs./l)")
	plt.title("Data fitted using np.linalg.lstsq()")
	plt.legend(loc=1)
	plt.subplot(2,1,2)
	plt.scatter(np.arange(1, len(v) + 1), v, marker="+", c="black", label="Original Data")
	plt.plot(np.arange(1, len(v) + 1), Y_cal[0], label="Degree = 1")
	plt.plot(np.arange(1, len(v) + 1), Y_cal[1], label="Degree = 2")
	plt.plot(np.arange(1, len(v) + 1), Y_cal[2], label="Degree = 3")
	plt.plot(np.arange(1, len(v) + 1), Y_cal[3], label="Degree = 4")
	plt.plot(np.arange(1, len(v) + 1), Y_cal[4], label="Degree = 5")
	plt.xlabel("Day")
	plt.ylabel("Price of petrol (Rs./l)")
	plt.title("Data fitted using get_QR()")
	plt.legend(loc=1)
	plt.tight_layout()
	plt.savefig("problem_2c.png")
	plt.show()

if __name__ == '__main__':
	main()


import numpy as np

def normalize(vector):
	return vector / np.sqrt(np.sum(vector**2))

def rect_corners(size, position=(0, 0)):
	x, y = position
	width, height = size

	return np.array([
		[x, y],
		[x+width, y],
		[x+width, y+height],
		[x, y+height]
	], dtype="float32")

def rotation_matrix(angle):
	sin = np.sin(angle)
	cos = np.cos(angle)

	return np.array([
		[cos, -sin],
		[sin, cos]
	], dtype="float32")

def centered_rect_corners(position, size, rotation=0):
	width = np.array([size[0], 0])
	height = np.array([0, size[1]])

	corners = np.array([
		position - width/2 - height/2,
		position + width/2 - height/2,
		position + width/2 + height/2,
		position - width/2 + height/2,
	], dtype="float32")

	if rotation != 0:
		m = rotation_matrix(np.radians(rotation))
		center = position
		for i in range(len(corners)):
			corners[i] = np.dot(m, corners[i] - center) + center

	return corners

def scale_polygon(corners, scale):
	corners = np.array(corners)
	center = np.average(corners, axis=0)
	return (corners - center) * scale + center

def line_intersection(a, b, c, d):
	ab = -a+b
	cd = -c+d

	coeffs = np.array([[ab[0], -cd[0]], [ab[1], -cd[1]]])
	right = c-a
	try:
		solution = np.linalg.solve(coeffs, right)
	except np.linalg.LinAlgError:
		return None

	if 0 <= solution[0]  and solution[0] <= 1 and 0 <= solution[1] and solution[1] <= 1:
		return a + ab*solution[0]
	else:
		return None

def line_center(a, b):
	return np.array([
		(a[0] + b[0])/2,
		(a[1] + b[1])/2,
	])

def distance_squared(a, b):
	return (b[0] - a[0])**2 + (b[1] - a[1])**2

def distance(a, b):
	return np.sqrt(distance_squared(a, b))

def get_center(corners):
	x = [c[0] for c in corners]
	y = [c[1] for c in corners]
	return (sum(x) / len(corners), sum(y) / len(corners))

def rotation_from_corners(corners):
	tl, tr, br, bl = corners
	diffs = tl - bl
	angle = np.degrees(np.arctan(diffs[0]/(diffs[1] + np.finfo(float).eps)))
	if diffs[1] < 0:
		angle += 180
	elif diffs[0] < 0:
		angle += 360

	return (180 - angle) % 360 # turn upside down


import numpy as np
import scipy.constants as sc
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import visual_solvers
import linoplib

def gaussian(x, offset, amp, std):
    return offset+amp*np.exp((-(x-x[x.shape[0]//2])**2)/(2*std**2))

def generate_f(_f, dx):
    f = _f*dx
    f[1] -= f[0]
    f[-1] -= f[-2]
    return f[1:-1]

def generate_animation(v0_freq, fname):
    n_iter = 3000
    N = 129
    x = np.linspace(0, 50, N)
    A = -linoplib.laplacian_LDO(N)
    v = np.zeros((n_iter, x.shape[0]))
    v[0] = np.sin(v0_freq*np.pi*x/x[-1])
    f = generate_f(gaussian(x, 0, 5e-2, 5), np.mean(np.diff(x)))

    v[:, 1:-1] = visual_solvers.weighted_jacobi(A, v[:, 1:-1], f, 0.667, n_iter)

    font_size = 16
    fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(10, 5))
    ax[0].set_xlabel('$x$', size=font_size)
    ax[0].set_ylabel('$f$', size=font_size)
    ax[1].set_xlabel('$x$', size=font_size)
    ax[1].set_ylabel('$v$', size=font_size)
    ax[0].plot(x[1:-1], f, '-b')
    ax[0].text(0.1, 0.2, '$f$', transform=ax[0].transAxes, color='b', fontsize=font_size)
    ax[0].text(0.05, 0.05, '$Av$', transform=ax[0].transAxes, fontsize=font_size)
    iter_text = ax[0].text(0.22, 0.90, '', transform=ax[0].transAxes, fontsize=font_size)
    line_v, = ax[1].plot(x, v[0], '-k')
    line_f, = ax[0].plot(x[1:-1], A@v[0, 1:-1], '-k')
    ax[0].set_ylim(-0.005, 0.025)
    ax[1].set_ylim(-0.5, 18)
    fig.tight_layout()

    def init():
        line_v.set_ydata([np.nan]*x.shape[0])
        iter_text.set_text('')
        line_f.set_ydata([np.nan]*x[1:-1].shape[0])
        return line_v, iter_text, line_f

    def animate(i):
        line_v.set_ydata(v[i])
        iter_text.set_text(f'Iteration: {i}')
        line_f.set_ydata(A@v[i, 1:-1])
        return line_v, iter_text, line_f

    ani = animation.FuncAnimation(fig,
                                  func=animate,
                                  frames=n_iter,
                                  init_func=init,
                                  interval=1,
                                  blit=True,
                                  save_count=n_iter)

    ani.save(fname, fps=60, dpi=200, extra_args=['-vcodec', 'libx264'])
    # plt.show()
    return

if __name__ == '__main__':
    generate_animation(0, 'jacobi_zeros.mp4')
    print('1')
    generate_animation(1, 'jacobi_lowfreq.mp4')
    print('2')
    generate_animation(10, 'jacobi_highfreq.mp4')
    print('Done!')

    # Need to call `ffmpeg -i <input filename> -filter:v "setpts=0.1*PTS" <output filename>`
    # to make it actually go fast. (This works by dropping frames.)


import numpy as np

def compute_error_for_line_given_points(b, m, points):
    # initialize error at 0
    totalError = 0

    # for every point
    for i in range(len(points)):
        # get x and y value
        x = points[i, 0]
        y = points[i, 1]

        # get the difference and square it
        totalError += (y - (m * x + b)) ** 2

    # get the average
    return totalError / float(len(points))

def gradient_descent_runner(points, starting_b, starting_m, learning_rate, num_of_iterations):
    b = starting_b
    m = starting_m

    for i in range(num_of_iterations):
        b, m = step_gradient(b, m, np.array(points), learning_rate)

    return [b, m]

def step_gradient(current_b, current_m, points, learning_rates):
    b_gradient = 0
    m_gradient = 0

    N = float(len(points))
    for i in range(len(points)):
        x = points[i, 0]
        y = points[i, 1]

        b_gradient += -(2/N) * (y - ((current_m * x) + current_b))
        m_gradient += -(2/N) * x * (y - ((current_m * x) + current_b))

    new_b = current_b - (learning_rates * b_gradient)
    new_m = current_m - (learning_rates * m_gradient)

    return [new_b, new_m]

def run():
    # collect our data
    points = np.genfromtxt('data.csv', delimiter=',')

    # define our hyperparameters
    learning_rate = 0.0001

    # slope : y = mx+b
    initial_b = 0
    initial_m = 0
    num_of_iterations = 1000
    print(f"starting gradient descent at b = {initial_b}, m = {initial_m}, "
          f"error = {compute_error_for_line_given_points(initial_b, initial_m, points)}")
    [b, m] = gradient_descent_runner(points, initial_b, initial_m, learning_rate, num_of_iterations)

    print(f"After {num_of_iterations } b = {b}, m = {m}, error = {compute_error_for_line_given_points(b, m, points)}")

if __name__ == "__main__":
    run()


# https://numba.pydata.org/
# pip install numba

import numpy as np
import numba
from numba import jit
import time

@jit(nopython=True)
def gaussians(x, means, widths):
    '''Return the value of gaussian kernels.

    x - location of evaluation
    means - array of kernel means
    widths - array of kernel widths
    '''
    n = means.shape[0]
    result = np.exp( -0.5 * ((x - means) / widths)**2 ) / widths
    return result / np.sqrt(2 * np.pi) / n

def gaussians_python(x, means, widths):
    '''Return the value of gaussian kernels.

    x - location of evaluation
    means - array of kernel means
    widths - array of kernel widths
    '''
    n = means.shape[0]
    result = np.exp( -0.5 * ((x - means) / widths)**2 ) / widths
    return result / np.sqrt(2 * np.pi) / n

def time_function(func, *args):
    start = time.time()
    func(*args)
    end = time.time()
    total = end - start
    print(total)

means = np.random.uniform(-1, 1, size=1000000)
widths = np.random.uniform(0.1, 0.3, size=1000000)

# gaussians(0.4, means, widths)
# gaussians.py_func(0.4, means, widths)

time_function(gaussians, 0.4, means, widths)
time_function(gaussians_python, 0.4, means, widths)


import pandas as pd
import numpy as np

def hours_of_daylight(date, axis=23.44, latitude=47.61):
    """Compute the hours of daylight for the given date"""
    diff = date - pd.datetime(2000, 12, 21)
    day = diff.total_seconds() / 24. / 3600
    day %= 365.25
    m = 1. - np.tan(np.radians(latitude)) * np.tan(np.radians(axis) * np.cos(day * np.pi / 182.625))
    m = max(0, min(m, 2))
    return 24. * np.degrees(np.arccos(1 - m)) / 180.

def print_rms(var):
	"""Calculates and prints the root-mean-square about the trend line"""
	rms = np.std(var)
	print('Root-mean-square about trend: {0: .0f} riders'.format(rms))

def csnap(df, fn=lambda x: x.shape, msg=None):
    """
    Custom Help function to print things in method chaining.
    Returns back the df to further use in chaining.
    """
    if msg:
        print(msg)
    display(fn(df))
    return df


def SIR_model(t, y, b, g, N):
    """Calculates the derivative of S, I, and R with respect to time at some t

    Parameters:
    t - The time at which the derivative is to be calculated
    y - Value of S, I, and R at t
    b - Parameter beta in the ODEs
    g - Parameter gamma in the ODEs
    N - Size of the population

    Returns:
    List of derivatives at time t for S, I, and R (in that order)
    """
    s, i, r = y

    return [-b*s*i/N, b*s*i/N - g*i, g*i]

def SIRD_model(t, y, b, g, l, N):
    """Gives the derivative of S, I, R, and D with respect to time at some t

    Parameters:
    t - The time at which the derivative is to be calculated
    y - Value of S, I, R, and D at t
    b - Parameter beta in the ODEs
    g - Parameter gamma in the ODEs
    l - Parameter lambda in the ODEs (see Q3 pdf)
    N - Size of the population

    Returns:
    List of derivatives at time t for S, I, R, and D (in that order)
    """
    s, i, r, d = y

    return [-b*s*i/N, b*s*i/N - g*i - l*i, g*i, l*i]

def plot(values, times, colours, labels):
    """Graphs values vs. times with their respective colours and labels

    Parameters:
    values - List of np arrays which contain the points of the function
    times - np array of corresponding times for all the functions in values
    colours - List of colours in which the functions in values will be graphed
    labels - List of labels for the functions in values

    Returns:
    Function has no return value, however, it shows the matplotlib graph
    """
    for i in range(len(values)):
        plt.plot(times, values[i], colours[i])

    plt.legend(labels)
    plt.xlabel("Time")
    plt.ylabel("Number of People")

    plt.show()

def ODE_solver(y_0, t_0, t_end, dt, params):
    """Uses Scipy's ODE solver to give the function values for S, I, and R or
    S, I, R, and D, depending on whether parameters are passed for the SIR
    model or the modified model that includes deaths

    Parameters:
    y_0 - List consisting of initial values of S, I, and R or S, I, R, and D
    t_0 - Initial time
    t_end - End time
    dt - Increment in time for ODE solver
    params - List consisting of [b, g, N] or [b, g, l, N]

    Returns:
    np array of S, I, and R or S, I, R, and D values for each corresponding
    time in the np array of times, which is also returned
    """
    times = [t_0]
    res = [y_0]

    if len(params) == 3:
        solver = ode(SIR_model).set_integrator('dopri5').set_initial_value(
            y_0, t_0).set_f_params(params[0], params[1], params[2])
    else:
        solver = ode(SIRD_model).set_integrator('dopri5').set_initial_value(
            y_0, t_0).set_f_params(params[0], params[1], params[2], params[3])

    while solver.successful() and solver.t < t_end:
        times.append(solver.t + dt)
        res.append(solver.integrate(solver.t + dt))

    return np.array(res), np.array(times)

if __name__ == '__main__':
    import numpy as np
    import matplotlib.pyplot as plt
    from scipy.integrate import ode

    # Define all parameters
    y_0 = [999, 1, 0]
    t_0 = 0.0
    b = 0.4
    g = 0.03
    N = 1000
    t_end = 200
    dt = 0.1

    res, times = ODE_solver(y_0, t_0, t_end, dt, [b, g, N])
    s, i, r = res.T

    plot([s, i, r], times, ['b', 'r', 'g'], ['Susceptible', 'Infected',
         'Recovered'])

    ''' SIR model with deaths

    # Define all parameters
    y_0 = [999, 1, 0, 0]
    t_0 = 0.0
    b = 0.2
    g = 0.07
    l = 0.03
    N = 1000
    t_end = 200
    dt = 0.1

    res, times = ODE_solver(y_0, t_0, t_end, dt, [b, g, l, N])
    s, i, r, d = res.T

    plot([s, i, r, d], times, ['b', 'r', 'g', 'k'], ['Susceptible', 'Infected',
         'Recovered', 'Dead'])
    '''


import numpy as np
import matplotlib.pyplot as plt
import scipy.integrate as integr
import numpy.random as rd

def f(x):
    return np.exp(-1*(x**2))

#integr.quad(f, 0 , 1)

def Rectangle(n):
    S=0
    h=0
    for i in range(0,n):
        S+=f(h) * (1/n)
        h+=1/n
    return S

#Rectangle(100)

def Trapezes(n):
    S=0
    h=0
    f1=f(0)
    for i in range(0,n):
        h+=1/n
        f2=f(h)
        S+=(f2+f1)* (1/2) * (1/n)
        f1=f2
    return S

#Trapezes(100)

def tracer(F):
    I,e=integr.quad(f, 0 , 1)
    x=[]
    y=[]
    n=1
    for i in range(1,5):
        x.append(i)
        val=F(n)
        lim = (10**(-i))
        while(abs(I-val)>lim):
            n+=(10**(i-1))
            val=F(n)
        y.append(n)
    plt.plot(x,y)
    plt.semilogy() # echelle log
    plt.grid()
    plt.show()

def calcul(F):
    I,e=integr.quad(f, 0 , 1)
    x=[]
    y=[]
    n=1
    for i in range(1,5):
        x.append(i)
        val=F(n)
        lim = (10**(-i))
        while(abs(I-val)>lim):
            n+=1
            val=F(n)
        y.append(n)
    return x, y

def tracer2():
    x,yt=calcul(Trapezes)
    x,yr=calcul(Rectangle)
    plt.figure()
    plt.semilogy() # echelle log
    plt.plot(x,yt, marker="o")
    plt.plot(x,yr, marker="x")
    plt.legend(('trapèzes', 'rectangles'), loc="upper left")
    plt.title("Compaire")
    plt.grid()
    plt.show()

def Test_Imshow():
    plt.figure()
    plt.imshow(np.zeros((10,10)))
    plt.show()

def Mandel(n,p):
    M=np.zeros((n,n))
    x=np.linspace(-2,2,n)
    y=np.linspace(-2,2,n)
    for i in range(n):
        for j in range(n):
            c=x[i]+1j*y[j]
            z=0
            for k in range(0,p):
                z=z**2+c
                if (abs(z)>=4):
                    M[i,j]=k
                    break
    plt.figure()
    plt.imshow(M)
    plt.show()

def Julia(n,p,c):
    M=np.zeros((n,n))
    x=np.linspace(-2,2,n)
    y=np.linspace(-2,2,n)
    for i in range(n):
        for j in range(n):
            z0=x[i]+1j*y[j]
            z=z0
            for k in range(0,p):
                z=z**2+c
                if (abs(z)>=4):
                    M[i,j]=k
                    break
    plt.figure()
    plt.imshow(M)
    plt.show()

#Julia(500,40,-0.123+1j*0.745)

def Spirale(n):
    M=np.zeros((n,n))

    i,j=(n-1)//2,n//2-1
    M[i,j]=1
    M[i,j+1]=2

    u=[0,1]

    k=3
    j+=1
    while(k<=n**2):
        if (M[i-u[1],j+u[0]]==0):
            u=[-u[1],u[0]]
        i+=u[0]
        j+=u[1]
        M[i,j]=k
        k+=1
    return M

print(Spirale(5))


# external libraries
import unittest
import numpy as np

# class to be tested
from rss.src.RSSvectors import Mom4

class Mom4TestCase(unittest.TestCase):

    def test_boosted_to_rest_frame_of(self):

        """
        Test that the boosting function works as advertised
        """
        p4 = Mom4(10,0,0,9.9)  # Alon z-axis
        print(p4[-1])
        p4_in_rest_frame = p4.boosted_to_rest_frame_of( p4 )
        self.assertAlmostEqual(p4_in_rest_frame.E, np.sqrt(10**2 - 9.9**2))
        self.assertAlmostEqual(p4_in_rest_frame.px,0)
        self.assertAlmostEqual(p4_in_rest_frame.py,0)
        self.assertAlmostEqual(p4_in_rest_frame.pz,0)

        p4 = Mom4(10,3,3,3)  # Along arbitrary axis
        p4_in_rest_frame = p4.boosted_to_rest_frame_of( p4 )
        self.assertAlmostEqual(p4_in_rest_frame.E, np.sqrt(10**2 - 3 * 3**2))
        self.assertAlmostEqual(p4_in_rest_frame.px,0)
        self.assertAlmostEqual(p4_in_rest_frame.py,0)
        self.assertAlmostEqual(p4_in_rest_frame.pz,0)

        p4 = Mom4(10,0,0,6)
        k4 = Mom4(10,0,1,6)  # Boosting a different momentum
        k4_in_rest_frame = k4.boosted_to_rest_frame_of( p4 )
        self.assertAlmostEqual(k4_in_rest_frame.E, np.sqrt(10**2 - 6**2))
        self.assertAlmostEqual(k4_in_rest_frame.px,0)
        self.assertAlmostEqual(k4_in_rest_frame.py,1)
        self.assertAlmostEqual(k4_in_rest_frame.pz,0)

    def test_boosted_away_with_momentum(self):
        """
        Test that the boosting function works as advertised
        """
        rest_mom = Mom4(1,0,0,0)
        p4 = Mom4(10,0,0,6)  # Along z-axis
        rest_mom_boosted_away = rest_mom.boosted_away_with_momentum( p4 )
        self.assertAlmostEqual(rest_mom_boosted_away.E, 10/np.sqrt(10**2-6**2) )
        self.assertAlmostEqual(rest_mom_boosted_away.px,0)
        self.assertAlmostEqual(rest_mom_boosted_away.py,0)
        self.assertAlmostEqual(rest_mom_boosted_away.pz,10*(6/10)/np.sqrt(10**2-6**2))

        moving = Mom4(10,0,-7,0)
        push = Mom4(10,0,+7,0)  # Along arbitrary axis
        moving_pushed_opposite = moving.boosted_away_with_momentum( push )
        self.assertAlmostEqual(moving_pushed_opposite.E, np.sqrt(10**2 - 7**2))
        self.assertAlmostEqual(moving_pushed_opposite.px,0)
        self.assertAlmostEqual(moving_pushed_opposite.py,0)
        self.assertAlmostEqual(moving_pushed_opposite.pz,0)


import numpy as np
from shapely.geometry import Polygon

# Pad and unpack the transformation matrix to be 3x1 or 3x3,
# necessary for it to handle both rotation and translation
pad = lambda x: np.hstack([x, np.ones((x.shape[0], 1))])
unpad = lambda x: x[:, :-1]

def norm(pri):
    pri = pri.copy()
    pri[:, 0] = pri[:, 0] - pri[:, 0].mean()
    pri[:, 0] = pri[:, 0] / pri[:, 0].max()
    pri[:, 1] = pri[:, 1] - pri[:, 1].mean()
    pri[:, 1] = pri[:, 1] / pri[:, 1].max()
    return pri

def poly_to_array(poly):

    try:
        return  np.array(poly.exterior.coords)[:-1]
    except AttributeError:
        return poly

def reorder_points(v):
    """Reorder points to ensure the shape is valid. The only works if all of the points
    are on the convex hull, which is true for our shapes. """

    from math import sqrt

    try:
        points = poly_to_array(v).tolist()
    except AttributeError:
        points = v # Hopefully already a list.

    points = poly_to_array(Polygon(v).convex_hull).tolist()

    # Find the point closest to the origin
    # Norming ensures origin finding is consistent. I guess.
    normed_points = norm(np.array(points)) + 10  # +10 to void div by zero in distance

    mp = next(iter(sorted(normed_points, key=lambda p: sqrt(p[0] ** 2 + p[1] ** 1))))

    # Rotate the list of points so the mp point is first in the list
    mpos = normed_points.tolist().index(mp.tolist())
    points = points[mpos:] + points[:mpos]

    return np.array(points)

def solve_transform(primary, secondary):

    primary = reorder_points(poly_to_array(primary))
    secondary = reorder_points(poly_to_array(secondary))

    A, res, rank, s = np.linalg.lstsq(pad(primary), pad(secondary), rcond=None)

    A[np.abs(A) < 1e-12] = 0  # Get rid of really small values.

    return A, primary, secondary

def invert_point(p):
    x,y = p

    y = -y + 2000
    return (x,y)

def invert_poly(poly):

    return Polygon( invert_point(p) for p in poly_to_array(poly))

def transform_point(A, x):
    # The extra np.array in pad() presumes this is a point, not a matrix
    # The final [0] unpacks back to a point.

    return unpad(np.dot(pad(np.array([x])), A))[0]

def transform_points(A, x):
    return unpad(np.dot(pad(x), A))

def get_transform(r):
    A, transform = solve_transform(r.source, r.dest)
    return transform

def get_matrix(r):
    A, pri, sec = solve_transform(r.source, r.dest)
    return A


#!/usr/bin/env python3
'''
Solve for optimal circle packing within a restaurant using the hexagon arrangement.
See, e.g., https://en.wikipedia.org/wiki/Circle_packing
'''

### MODULES ---
import numpy as np

### CLASSES AND DEFINITIONS ---
def hexcell(x0, y0, r):
    '''
    Return the centers of three more hexagons given the center and radius of the first hexagon.
    top   diag
     |    /
     |  /
     o __ __ right

    First, compute the distance from one hexagon center to another.
    This is equal to 2*r*cos(30 deg) =
    '''
    # Pre-solve parameters
    rad30 = np.deg2rad(30)
    cos30 = np.cos(rad30)
    sin30 = np.sin(rad30)

    # Define diameter
    d = 2*r

    # Solve for distance from the center of one hexagon to that of another
    dist = 2*cos30*d  # distance from one center to another

    # Format starting point as array
    start = np.array([x0, y0])

    # Define unit vectors
    nUpper = np.array([   0,       1])
    nDagnl = np.array([cos30,  sin30])
    nRight = np.array([cos30, -sin30])

    # Solve for centroids of other hexagons by scaling unit vectors
    upper = dist*nUpper + start  # coordinates of top hexagon
    dagnl = dist*nDagnl + start  # coordinates of diagonal hexagon
    right = dist*nRight + start  # coordinates of right hexagon

    return upper, dagnl, right

def hexmesh(x0, y0, xend, yend, r):
    '''
    Create a mesh of equilateral hexigons.
    '''
    # Pre-solve parameters
    rad30 = np.deg2rad(30)
    cos30 = np.cos(rad30)
    sin30 = np.sin(rad30)

    # Define diameter
    d = 2*r

    # Define distance from one centroid to another
    D = 2*d*cos30

    # x, y distances between hexagon centroids
    Dx = D*cos30
    Dy = D*sin30

    # Define mesh
    x = np.arange(x0, xend, Dx)
    y = np.arange(y0, yend, D)
    X, Y = np.meshgrid(x, y)

    # Offset /y
    Y[:,::2] += Dy

    return X, Y

def hexvertices(x0, y0, r):
    '''
    Find the vertices of a hexagon and return as x, y points, given the center of the hexagon and a radius parameter.
       2 -- 3
     /        \
    1           4
     \        /
       6 -- 5
    '''
    # Pre-solve parameters
    rad60 = np.deg2rad(60)
    cos60 = np.cos(rad60)
    sin60 = np.sin(rad60)

    # Format starting point as array
    start = np.array([x0, y0])

    # Convert radius to diameter
    d = 2*r

    # Unit vectors pointing to each vertex
    norms = []
    norms.append(np.array([     0,      0]))
    norms.append(np.array([    -1,      0]))
    norms.append(np.array([-cos60,  sin60]))
    norms.append(np.array([ cos60,  sin60]))
    norms.append(np.array([     1,      0]))
    norms.append(np.array([ cos60, -sin60]))
    norms.append(np.array([-cos60, -sin60]))

    # Solve for vertex coordinates
    P = np.array([d*n+start for n in norms])

    return P


import numpy as np
from matplotlib import pyplot as plt
from scipy.optimize import minimize
from scipy.optimize import Bounds
from random import random
import random
################################################################################################

def portfolio_var(w,V):
    '''
    variance = portfolio_var(w,V)

    Compute the variance given the covariance matrix and the weight vector.

    @param w: 1darray
        The weight vector.
    @param covariance: ndarray
        The covariance matrix.

    @return weights: scalar
        The variance determined by the input w,V.
    '''
    variance = w @( V @ w)
    return variance

def budget_constraint(x):
    '''
     variance = portfolio_var(w,V)

     Serve as an equality constraint to the optimization problem, commonly know as budget constraint sum(x) =1.

     @param x: ndarray
         input array that needs to meet sum(x) =1.

     @return residual: scalar
         the residual at the point x.
     '''

    return np.sum(x)-1.0

########################## minimum variance portfolio###################################################################

def minimumVariance(covariance):
    '''
    weights = minimumVariance(covariance)

    Calculate the weights for Minimum Variance Portfolio with nonnegative constraints.
    Solve the problem min{ x' * covariance *x : sum(x)=1, x>=0 }.
    @param covariance: ndarray
        The covariance matrix.

    @return weights: 1darray
        The weights vector for Minimum Variance Portfolio.
    '''
    n = len(covariance)   # number of variables/assets
    cons = ({'type': 'eq', 'fun': budget_constraint})  # sum(x) = 1 constraint
    w0 = np.ones(n) / n  # initial guess for the algorithm
    bnd = Bounds(np.zeros(n),np.ones(n))  # bounds on each variable; enforcing 0<=x<=1
    # solve the problem using optimization package
    res = minimize(portfolio_var, w0, args=covariance, method='SLSQP', bounds = bnd, constraints=cons,options={'disp': False})
    weights = res.x
    return weights

########################### inverse volatility weighted portfolio ######################################################

def inverseVolatilityWeighted(covariance):
    '''
    weights = inverseVolatilityWeighted(covariance)

    Calculate the weights for Inverse Volatility Weighted Portfolio.

    @param covariance: ndarray
        The covariance matrix.

    @return weights: ndarray
        The weights for Inverse Volatility Weighted Portfolio.
    '''
    volatility = 1 / np.sqrt(np.diag(covariance))  # take the reciprocal of the standard deviation of each asset
    sum_volatility = sum(volatility)               # normalization factor
    weights = np.array([sigma / sum_volatility for sigma in volatility])  # normalize the weights
    return weights

########################### inverse volatility weighted portfolio ######################################################

def inverseVarianceWeighted(covariance):
    '''
    weights = inverseVarianceWeighted(covariance)

    Calculate the weights for Inverse Variance Weighted Portfolio.

    @param covariance: ndarray
        The covariance matrix.

    @return weights: ndarray
        The weights for Inverse Variance Weighted Portfolio.
    '''
    volatility = 1 / np.diag(covariance)  # take the reciprocal of the standard deviation of each asset
    sum_volatility = sum(volatility)               # normalization factor
    weights = np.array([sigma / sum_volatility for sigma in volatility])  # normalize the weights
    return weights

############################ equal risk contribution portfolio #########################################################

def assetRiskContribution(w,covariance):
    '''
    RC = assetRiskContribution(w,V)

    Calculate the percentage risk of assets to total risk

    @param w: 1darray
        The weight vector.
    @param covariance: ndarray
        The covariance matrix.

     @return RC: 1darray
         the risk contribution with input w,V.
     '''
    sigma = w @ (covariance @ w)   # variance
    MRC = covariance @ w           # Marginal Risk Contribution
    RC = np.multiply(MRC,w)/sigma  # Risk Contribution
    return RC

def ercObjective(x,pars):
    '''
    ercObj = ercObjective(x,pars)

    Compute objective function for equal risk contribution portfolio

    @param x: ndarray
        The covariance matrix.
    @param pars: list
        pars[0]: covariance matrix.
        pars[1]: target weights for risk contribution.

     @return RC: scalar
        The objective function value for equal risk contribution portfolio.
     '''

    covariance = pars[0]             # covariance matrix
    target_percentage = pars[1]      # target risk in percent of portfolio risk
    asset_RC = assetRiskContribution(x,covariance)          # compute risk asset contribution
    ercObj = sum(np.square(asset_RC - target_percentage))   # sum of squared error, objective function for erc
    return ercObj

def equalRiskContribution(covariance):
    '''
    x = equalRiskContribution(covariance)

    Compute the equal risk contribution portfolio with equally weighted target.

    @param covariance: ndarray
        The covariance matrix.

     @return weights: 1darray
        The equal risk contribution portfolio given covariance matrix.
     '''
    n = covariance.shape[0]
    x_t = np.ones(n) / n        # target risk in percent of portfolio risk
    cons = ({'type': 'eq', 'fun': budget_constraint})    # constraint function
    w0 = x_t                    # initial guess
    bnd = bnd = Bounds(np.zeros(n), np.ones(n))          # bnd: lower and upper bound on each variable
    # solve the problem using optimization package
    res = minimize(ercObjective, w0, args=[covariance, x_t], method='SLSQP', bounds = bnd, constraints=cons,options={'disp': False})
    weights = res.x       # extract the solution

    return weights

############################# maximum diversification portfolio ########################################################

def diversificationRatio(w,covariance):
    '''
    DR = diversificationRatio(w,V)

    Calculate the negative of diversification ratio given weight w and covariance matrix

    @param w: 1darray
        The weight vector.
    @param covariance: ndarray
        The covariance matrix.

     @return DC: 1darray
         the negative of diversification ratio with input w,V.
     '''
    # This function computes the diversification ratio (objective function for MDP)

    w_vol = np.dot(np.sqrt(np.diag(covariance)), w.T)  # inner product between std and each weight; the numerator of DR
    port_vol = np.sqrt( w@(covariance@w) )             # portfolio volatility; the denominator of DR
    diversification_ratio = w_vol/port_vol    # obtain the diversification ratio
    return -diversification_ratio

def maximumDiversification(covariance):
    '''
    x = maximumDiversification(covariance)

    Calculate the maximum diversification portfolio given covariance matrix.

    @param covariance: ndarray
        The covariance matrix.

     @return weights: 1darray
        the maximum diversification portfolio.
     '''
    Sigma = covariance
    n = Sigma.shape[0]      # number of assets
    w0 = np.ones(n) / n     # initial guess
    bnd = bnd = Bounds(np.zeros(n),np.ones(n))           # bnd: lower and upper bound on each variable
    cons = ({'type': 'eq', 'fun': budget_constraint})    # constraint function
    # solve the problem using optimization package
    res = minimize(diversificationRatio, w0, args=Sigma, method='SLSQP', bounds=bnd, constraints=cons, options={'disp': False})
    weights = res.x      # extract the solution
    return weights

################################################################################################

def marketCapitalizationWeighted(market_price):
    '''
     x = marketCapitalizationWeighted(market_price)

     Calculate the market capitalization weighted portfolio.

     @param market_price: 1darray
         The market price for asset

     @return weights: 1darray
         The market capitalization weighted portfolio.
    '''
    sum_price = sum(market_price)               # normalization factor
    weights = np.array([market_price / sum_price for price in market_price])  # normalize the weights
    return weights

################################################################################################

def uniformlyWeighted(covariance):
    '''
    weights = uniformlyWeighted(covariance)

    Calculate the weights for 1 / N Portfolio.

    @param covariance: list/ndarray
        The covariance matrix.

    @return weights: ndarray
        The weights for 1 / N portfolio.
    '''
    covariance = np.array(covariance)
    N = len(covariance)
    weights = np.array([1 / N for i in range(N)])
    return weights

def main():
    n=5   # problem size
    #Sigma = np.eye(n)    # created an easy covariance matrix
    random.seed(9001)

    A = np.random.rand(n,n)
    Sigma = A @ A.T  # a random covariance matrix

    x_MVP = minimumVariance(Sigma)
    x_IVP = inverseVolatilityWeighted(Sigma)
    x_ERC = equalRiskContribution(Sigma)
    x_MDP = maximumDiversification(Sigma)
    x_MCWP = 0    # marketCapitalizationWeighted(market_price)
    x_1overN = uniformlyWeighted(Sigma)

    print('<Report>')
    print('x_MVP : ',x_MVP)
    print('x_IVP : ',x_IVP)
    print('x_ERC : ',x_ERC)
    print('x_MDP : ',x_MDP)
    print('x_MCWP : ',x_MCWP,'working on it')
    print('x_1overN : ',x_1overN)

if __name__ == "__main__":
    main()