# encoding=utf8

"""
Module containing functions for evaluating and interpolating polynomials.

"""

import numpy as np
from sympy import Symbol, Poly, simplify
from sympy.parsing.sympy_parser import parse_expr

from mathpy._lib import _create_array

def horner_eval(f, x0):
    r"""
    Evaulates a polynomial function using Horner's method.

    Parameters
    ----------
    f : function
        The polynomial function to be evaluated. For example, to evaluate the
        polynomial :math:`f(x) = 3x^3 - 5x^2 + 2x - 1`, f should be similar to:

        .. code-block:: python

            def f(x):
                # The variable in function f must be set as x.
                return 3 * x^3 - 5 * x^2 + 2 * x - 1

    x0 : int, float
        The point at which to evaluate the function f.

    Returns
    -------
    poly_eval : int or float
        The evaluated polynomial function f at the given point x0.

    Notes
    -----
    Given a polynomial, such as:

    .. math [1]

        a_0 + a_1 x + a_2 x^2 + a_3 x^3 + \cdots + a_n x^n

    Horner's method evaluates the polynomial at a given point, such
    as :math:`x_0` by converting the polynomial into a 'nested'
    form by defining a new sequence. This is done by starting with the
    leading coefficient :math:`a_n`, multiplying it :math:`x` and adding
    the next coefficient. For example, using Horner's method to evaluate
    the polynomial :math:`f(x) = 3x^3 - 5x^2 + 2x - 1`, at :math:`x = 3`
    Horner's method proceeds as follows:

    .. math::

        b_3 = 3

    .. math::

        b_2 = 3x - 5 = 3(3) - 5 = 4

    .. math::

        b_1 = (4)x + 2 = (4)(3) + 2 = 14

    .. math::

        b_0 = (14)x - 1 = (14)(3) - 1 = 41

    Examples
    --------
    >>> def f(x): return 3 * x ** 3 - 5 * x ** 2 + 2 * x - 1
    >>> horner_eval(f, 3)
    41
    >>> def f2(x): return 4 * x ** 4 - 6 * x ** 3 + 3 * x - 5
    >>> horner_eval(f2, 10)
    34025

    References
    ----------
    Cormen, T., Leiserson, C., Rivest, R., & Stein, C. (2009).
        Introduction to algorithms (3rd ed.). Cambridge (Inglaterra): Mit Press.

    Horner's method. (2017, April 12). In Wikipedia, The Free Encyclopedia.
        From https://en.wikipedia.org/w/index.php?title=Horner%27s_method&oldid=775072621

    """
    if callable(f) is False:
        raise TypeError('f must be a function')

    x = Symbol('x')
    fx_coefs = Poly(f(x), x).all_coeffs()

    poly_eval = 0
    for i in fx_coefs:
        poly_eval = poly_eval * x0 + i

    return poly_eval

def lagrange_interpolate(x, y):
    r"""
    Interpolates a polynomial given a set of equal-length x and y values using
    Lagrangian interpolation.

    Parameters
    ----------
    x
        One-dimensional array of x values. Can be a pandas DataFrame or Series,
        list, dictionary (first key-value pair is used if there are more than one),
        or numpy array. Must be same length as y.
    y
        One-dimensional array of y values. Can be a pandas DataFrame or Series,
        list, dictionary (first key-value pair is used if there are more than one),
        or numpy array. Must be same length as y.

    Returns
    -------
    Symbolic representation of interpolating polynomial.

    Notes
    -----
    The Lagrangian method of polynomial interpolation uses Lagrangian polynomials
    to fit a polynomial to a given set of data points. The Lagrange interpolating
    polynomial is given by the following theorem:

    For a set of data points :math:`(x_0, y_0), (x_1, y_1), \cdots, (x_n, y_n)`
    with no duplicate $x$ and there exists a function $f$ which evaluates to these
    points, then there is a unique polynomial $P(x)$ with degree $\leq n$ also
    exists. The polynomial is given by:

    .. math::

        P(x) = f(x_o)L_{n,0}(x) + \cdots + f(x_n)L_{n,n}(x) = \sum^n_{k=0} f(x_k) L_{n,k}(x)

    Where each :math:`k` in :math:`k = 0, 1, \cdots, n` is:

    .. math::

        L_{n,k} = \frac{(x - x_0)(x - x_1) \cdots (x - x_{k-1})(x - x_{k+1})
        \cdots (x - x_n)}{(x_k - x_0)(x_k - x_1) \cdots (x_k - x_{k-1})(x_k - x_{k+1})
        \cdots (x_k - x_n)} = \underset{i \neq k}{\prod^n_{i=0}} \frac{(x - x_i)}{(x_k - x_i)}

    Examples
    --------
    >>> x, y = [0, 2, 3, 4], [7, 11, 28, 63]
    >>> lagrange_interpolate(x, y)
    x**3 - 2*x + 7

    References
    ----------
    Burden, R. L., & Faires, J. D. (2011). Numerical analysis (9th ed.).
        Boston, MA: Brooks/Cole, Cengage Learning.

    Cheney, E. W., & Kincaid, D. (2013). Numerical mathematics and computing (6th ed.).
        Boston, MA: Brooks/Cole, Cengage Learning.

    """
    x, y = _create_array(x)[0], _create_array(y)[0]

    if len(x) != len(y):
        raise ValueError('x and y must be the same length to evaluate polynomial')

    l = []

    for i in np.arange(len(x)):
        num = 1
        denom = 1

        p = np.delete(x, i)

        for j in p:
            num = str(num) + '*' + '(' + 'x' + ' - ' + str(j) + ')'
            denom = str(denom) + '*' + '(' + str(x[i]) + ' - ' + str(j) + ')'

        l.append('(' + num + ')' + '/' + '(' + denom + ')')

    poly = 0

    for i in np.arange(len(l)):
        poly = str(poly) + '+' + str(y[i]) + '*' + str(l[i])

    return simplify(poly)

def neville(x, y, x0):
    r"""
    Evaluates an interpolated polynomial at a particular :math:`x` value given a set of
    :math:`x` and corresponding :math:`y` values.

    Parameters
    ----------
    x
        One-dimensional array of x values. Can be a pandas DataFrame or Series,
        list, dictionary (first key-value pair is used if there are more than one),
        or numpy array. Must be same length as y.
    y
        One-dimensional array of y values. Can be a pandas DataFrame or Series,
        list, dictionary (first key-value pair is used if there are more than one),
        or numpy array. Must be same length as y.
    x0
        Desired value at which to interpolate and approximate poynomial.

    Returns
    -------
    tuple
        Contains the approximated value of the interpolated polynomial evaluated at the point :math:`x` as float
        and a numpy array representing the iterated Neville table with intermediate values generated recursively.

    Notes
    -----
    Neville's method evaluates a polynomial that passes through a given set of :math:`x` and :math:`y` points
    for a particular :math:`x` value using the Newton polynomial form. Neville's method is similar to a
    now defunct procedure named Aitken's algorithm and is based on the divided differences recursion
    relation.

    It was stated before in a previous post on Lagrangian polynomial interpolation that there exists
    a Lagrange polynomial that passes through points :math:`y_1, y_2, \cdots, y_k` where each is a
    distinct integer and :math:`0 \leq y_i \leq n` at corresponding x values :math:`x_0, x_1, x_2, \cdots, x_n`.
    The :math:`k` points :math:`y_1, y_2, \cdots, y_k` are denoted :math:`P_{y_1, y_2, \cdots, y_k}(x)`.
    Neville's method can be stated as follows:

    Let a function :math:`f` be defined at points :math:`x_0, x_1, \cdots, x_k` where :math:`x_j` and
    :math:`x_i` are two distinct members. For each :math:`k`, there exists a Lagrange polynomial :math:`P`
    that interpolates the function :math:`f` at the :math:`k + 1` points :math:`x_0, x_1, \cdots, x_k`.
    The :math:`k`th Lagrange polynomial is defined as:

    .. math::

        P(x) = \frac{(x - x_j) P_{0,1,\cdots,j-1,j+1,\cdots,k}(x) - (x - x_i)
        P_{0,1,\cdots,i-1,i+1,\cdots,k}(x)}{(x_i - x_j)}

    The :math:`P_{0,1,\cdots,j-1,j+1,\cdots,k}` and :math:`P_{0,1,\cdots,i-1,i+1,\cdots,k}` are often
    denoted :math:`\hat{Q}` and :math:`Q`, respectively, for ease of notation.

    .. math::

        P(x) = \frac{(x - x_j) \hat{Q}(x) - (x - x_i) Q(x)}{(x_i - x_j)}

    Examples
    --------
    >>> x, y = [8.1, 8.3, 8.6, 8.7], [16.9446, 17.56492, 18.50515, 18.82091]
    >>> neville(x, y, 8.4)
    17.8770925

    References
    ----------
    Burden, R. L., & Faires, J. D. (2011). Numerical analysis (9th ed.).
        Boston, MA: Brooks/Cole, Cengage Learning.

    Cheney, E. W., & Kincaid, D. (2013). Numerical mathematics and computing (6th ed.).
        Boston, MA: Brooks/Cole, Cengage Learning.

    Neville's algorithm. (2016, January 2). In Wikipedia, The Free Encyclopedia.
        From https://en.wikipedia.org/w/index.php?title=Neville%27s_algorithm&oldid=697870140

    """
    x, y = _create_array(x)[0], _create_array(y)[0]

    if len(x) != len(y):
        raise ValueError('x and y must be the same length to evaluate polynomial')

    n = len(x)

    q = np.zeros((n, n))
    q[:, 0] = y

    for i in np.arange(1, n):
        for j in np.arange(i, n):
            q[j, i] = ((x0 - x[j - i]) * q[j, i - 1] - (x0 - x[j]) * q[j - 1, i - 1]) / (x[j] - x[j - i])

    return float(q[n - 1, n - 1]), q

def divided_differences(x, y, x0=None):
    r"""
    Constructs an interpolating polynomial that passes through given x and y points using
    the divided differences method.

    Parameters
    ----------
    x
        One-dimensional array of x values. Can be a pandas DataFrame or Series,
        list, dictionary (first key-value pair is used if there are more than one),
        or numpy array. Must be same length as y.
    y
        One-dimensional array of y values. Can be a pandas DataFrame or Series,
        list, dictionary (first key-value pair is used if there are more than one),
        or numpy array. Must be same length as y.
    x0
        Optional. Desired value to interpolate poynomial and approximate.

    Returns
    -------
    dict
        dict object containing the following entries:
        Approximated value of the intepolated polynomial (if given)
        The interpolated polynomial
        Divided Differences Table

    Notes
    -----
    The divided differences method is a numerical procedure for interpolating a polynomial
    given a set of points. Unlike Neville's method, which is used to approximate the value
    of an interpolating polynomial at a given point, the divided differences method
    constructs the interpolating polynomial in Newton form.

    Assume that :math:`P_n(x)` is the :math:`nth` Lagrangian polynomial that corresponds with
    the function :math:`f` at a set of :math:`x` data points. The polynomial :math:`P_n(x)` can be expressed
    using the divided differences of the function :math:`f` with respect to the :math:`x`-values.

    .. math::

        P_n(x) = a_0 + a_1(x - x_0) + a_2(x - x_0)(x - x_1) + \cdots + a_n(x - x_0) \cdots (x - x_{n-1})

    Therefore the constants :math:`a_0, a_1, \cdots, a_n` must be found to construct the polynomial. To
    find these constants, the divided differences are recursively generated until :math:`n` iterations
    have been completed. We start with the zeroth divided difference of the function :math:`f` with
    respect to :math:`x_i`, which is the value of :math:`f` at that point. Bracket notation is introduced
    to distinguish the divided differences.

    .. math::

        f[x_i] = f(x_i)

    The first divided difference is then the function :math:`f` with respect to the values :math:`x_i`
    and :math:`x_{i+1}`.

    .. math::

        f[x_i, x_{i+1}] = \frac{f[x_{i+1}] - f[x_i]}{x_{i+1 - x_i}}

    The second divided difference follows:

    .. math::

        f[x_i, x_{i+1}, x_{i+2}] = \frac{f[x_{i+1},x_{i+2}] - f[x_i, x_{i+1}]}{x_{i+2} - x_i}

    This iteration continues until the :math:`n`th divided difference:

    .. math::

        f[x_0, x_1, \cdots, x_n] = \frac{f[x_1, x_2, \cdots, x_n] - f[x_0, x_1, \cdots, x_n]}{x_n - x_0}

    Thus the interpolating polynomial resulting from the divided differences method takes the form:

    .. math::

        P_n(x) = f[x_0] + f[x_0, x_1](x - x_0) + f[x_0, x_1, x_2](x - x_0)(x - x_1) + \cdots +
        f[x_0, x_1, x_2, \cdots, x_n](x - x_0)(x - x_1) \cdots (x - x_{n-1})

    Examples
    --------
    >>> x, y = [8.1, 8.3, 8.6, 8.7], [16.9446, 17.56492, 18.50515, 18.82091]
    >>> divided_differences(x, y, 8.4)
    {'Approximated Value of Interpolated Polynomial': 17.8770925200000,
     'Divided Differences Table': array([[  1.69446000e+01,   0.00000000e+00,   0.00000000e+00,
               0.00000000e+00],
            [  1.75649200e+01,   3.10160000e+00,   0.00000000e+00,
               0.00000000e+00],
            [  1.85051500e+01,   3.13410000e+00,   6.50000000e-02,
               0.00000000e+00],
            [  1.88209100e+01,   3.15760000e+00,   5.87500000e-02,
              -1.04166667e-02]]),
     'Interpolated Function': '16.9446 + 3.1016*(x - 8.1) + 0.065*(x - 8.1)*(x - 8.3) + -0.01042*(x - 8.1)*(x - 8.3)*(x - 8.6)'}

    References
    ----------
    Burden, R. L., & Faires, J. D. (2011). Numerical analysis (9th ed.).
        Boston, MA: Brooks/Cole, Cengage Learning.

    """
    x, y = _create_array(x)[0], _create_array(y)[0]

    if len(x) != len(y):
        raise ValueError('x and y must be the same length to evaluate polynomial')

    n = len(x)

    q = np.zeros((n, n))
    q[:, 0] = y

    f = str(np.round(q[0, 0], 5))
    fi = ''

    for i in np.arange(1, n):
        for j in np.arange(i, n):
            q[j, i] = (q[j, i-1] - q[j-1, i-1]) / (x[j] - x[j-i])

        fi = fi + '*(x - ' + str(x[i-1]) + ')'
        f = f + ' + ' + str(np.round(q[i,i], 5)) + fi

    x = Symbol('x')
    if x0 is None:
        raise ValueError('x0 must be provided to approximate polynomial')
    else:
        approx = parse_expr(f).evalf(subs={x: x0})

    res = {'Approximated Value of Interpolated Polynomial': approx,
           'Interpolated Function': f,
           'Divided Differences Table': q}

    return res

# def taylorseries(f, x0):
#     r"""
#
#     Parameters
#     ----------
#     f
#     x0
#
#     Returns
#     -------
#
#     Examples
#     --------
#     >>> def f(x): return 3 * x ** 5 - 2 * x ** 4 + 15 * x ** 3 + 13 * x ** 2 - 12 * x - 5
#
#     """
#     x = Symbol('x')
#     a = f(x)
#
#     deg = degree(a)
#     coefs = []
#
#     for i in range(deg + 1):
#         coefs.append(float(a.evalf(subs={x: x0})))
#         a = diff(a)
#
#     taylorf = int(coefs[0])
#     for i in range(1, len(coefs)):
#         taylorf = str(f) + ' + ' + str(int(coefs[i]  / factorial(i))) + '(x - ' + str(2) + ') **m ' + str(i)
#
#     return taylorf


import matplotlib.pyplot as plt
import numpy as np
import custom_tools.fftplot as fftplot
import scipy.signal as sig
import scipy.fftpack as fft
from numpy.random import randn

# A/D Conversion, Sampling
nsamps = 2 ** 12
# generate time vector
fs = 500e6
ftone = 10e6
t = np.arange(nsamps) * 1 / fs

tone2 = 20e6
noise = 1 * randn(nsamps)

noise[0:int(nsamps/2)] = np.cos(2 * np.pi * tone2 * t[0:int(nsamps/2)])

# noise = np.cos(2 * np.pi * tone2 * t)

noise1 = 1 * randn(nsamps)

s = np.cos(2 * np.pi * ftone * t)

delayed_noise = sig.lfilter([0, 0, 0, 1], 1, noise)

adc_out = s + delayed_noise

# using cusomized fft module
x, y = fftplot.winfft(adc_out, fs=fs)
plt.figure()
fftplot.plot_spectrum(x, y)
plt.title(f'Output Spectrum - {ftone / 1e6} MHz Tone')

plt.figure()
plt.plot(t, adc_out)

# windowing with kaiser
win_len = nsamps
beta = 12
win = sig.kaiser(win_len, beta)
winscale = np.sum(win)

win_data = win * adc_out

plt.figure()
plt.plot(t, win_data)

# using cusomized fft module
# x, y = fftplot.winfft(win_data, fs=fs)
plt.figure()
fftplot.plot_spectrum(*fftplot.winfft(win_data, fs=fs))
plt.title(f'Output Spectrum - {ftone / 1e6} MHz Tone')

nTaps = 20
w0 = np.zeros(nsamps + 1)
w1 = np.zeros(nsamps + 1)

w = np.zeros([nTaps, nsamps + 1])
d = adc_out
x = noise
y = np.zeros(nsamps)
e = np.zeros(nsamps)

plt.figure()
plt.subplot(5, 1, 1)
plt.plot(t, s)
plt.ylabel('s')

plt.subplot(5, 1, 2)
plt.plot(t, x)
plt.ylabel('x')

plt.subplot(5, 1, 3)
plt.plot(t, d)
plt.ylabel('d')

plt.subplot(5, 1, 4)
plt.plot(t, e)
plt.ylabel('e')

plt.subplot(5, 1, 5)
plt.plot(np.transpose(w))
plt.ylabel('w')

mu = 0.001
for i in range(nsamps):

    sum1 = 0
    for k in range(nTaps):
        sum1 = sum1 + w[k][i] * x[i - k]

    y[i] = sum1

    e[i] = d[i] - y[i]

    for k in range(nTaps):
        w[k][i + 1] = w[k][i] + 2 * mu * e[i] * x[i - k]

    # # one tap filter
    # y[i] = w[i] * x[i]
    # e[i] = d[i] - y[i]
    # w[i + 1] = w[i] + 0.005 * e[i] * x[i]

plt.figure()
plt.subplot(6, 1, 1)
plt.plot(t, s)
plt.ylabel('s')

plt.subplot(6, 1, 2)
plt.plot(t, x)
plt.ylabel('x')

plt.subplot(6, 1, 3)
plt.plot(t, d)
plt.ylabel('d')

plt.subplot(6, 1, 4)
plt.plot(t, e)
plt.ylabel('e')

plt.subplot(6, 1, 5)
plt.ylabel('w')
# plt.plot(np.transpose(w))
for k in range(nTaps):
    # plt.legend(f'{k}')
    plt.plot(w[k], label=f'w{k}')
plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0.)

plt.subplot(6, 1, 6)
plt.plot(t, s-e)
plt.ylabel('s - e')

# using cusomized fft module
plt.figure()
fftplot.plot_spectrum(*fftplot.winfft(e, fs=fs))
plt.title(f'Output Spectrum - {ftone / 1e6} MHz Tone')

# example from book Digital Signal Processing (Third Edition) Fundamentals and Applications
# table 9.1

d = [-0.2947, 1.0017, 2.5827, -1.6019, 0.5622, 0.4456, -4.2674, -0.8418, -0.3862, 1.2274, 0.6021, 1.1647, 0.963,
     -1.5065, -0.1329, 0.8146]
x = [-0.5893, 0.5893, 3.1654, -4.6179, 1.1244, 2.3054, -6.5348, -0.2694, -0.7724, 1.0406, -0.7958, 0.9152, 1.926,
     -1.5988, 1.7342, 3.0434]
s = [0, 0.7071, 1, 0.7071, 0, -0.7071, -1, -0.7071, 0, 0.7071, 1, 0.7071, 0, -0.7071, -1, -0.7071]
lenSig = len(d)
w = np.zeros(lenSig + 1)
w[0] = 0.3
y = np.zeros(lenSig)
e = np.zeros(lenSig)

for i in range(lenSig):
    y[i] = w[i] * x[i]
    e[i] = d[i] - y[i]
    w[i + 1] = w[i] + 0.01 * e[i] * x[i]

plt.figure()
plt.subplot(5, 1, 1)
plt.plot(s)

plt.subplot(5, 1, 2)
plt.plot(x)

plt.subplot(5, 1, 3)
plt.plot(d)

plt.subplot(5, 1, 4)
plt.plot(e)

plt.subplot(5, 1, 5)
plt.plot(w)

# autocorrelation (correlation vs delay for same function)

# noise signal
N = nsamps
lag = np.arange(-N + 1, N)
# noise = randn(N) + 1j * randn(N)

corr = sig.correlate(noise, noise)
sd_n = np.std(noise)
m_n = np.mean(noise)

plt.figure()
plt.plot(lag, np.abs(corr))
plt.xlabel("Lag [samples]")
plt.ylabel("Real Magnitude")
plt.title(f"AWGN Autocorrelation {N} Samples")

plt.show()


"""
Filename: mc_tools.py
Authors: John Stachurski and Thomas J. Sargent
"""

import numpy as np
from discrete_rv import DiscreteRV

def mc_compute_stationary(P):
    """
    Computes the stationary distribution of Markov matrix P.

    Parameters
    ===========
        P : a square 2D NumPy array

    Returns: A flat array giving the stationary distribution
    """
    n = len(P)                               # P is n x n
    I = np.identity(n)                       # Identity matrix
    B, b = np.ones((n, n)), np.ones((n, 1))  # Matrix and vector of ones
    A = np.transpose(I - P + B)
    solution = np.linalg.solve(A, b)
    return solution.flatten()                # Return a flat array

def mc_sample_path(P, init=0, sample_size=1000):
    """
    Generates one sample path from a finite Markov chain with (n x n) Markov
    matrix P on state space S = {0,...,n-1}.

    Parameters
    ==========
        P : A nonnegative 2D NumPy array with rows that sum to 1

        init : Either an integer in S or a nonnegative array of length n
                with elements that sum to 1

        sample_size : int

    If init is an integer, the integer is treated as the determinstic initial
    condition.  If init is a distribution on S, then X_0 is drawn from this
    distribution.

    Returns
    ========
        A NumPy array containing the sample path

    """
    # === set up array to store output === #
    X = np.empty(sample_size, dtype=int)
    if isinstance(init, int):
        X[0] = init
    else:
        X[0] = DiscreteRV(init).draw()

    # === turn each row into a distribution === #
    # In particular, let P_dist[i] be the distribution corresponding to the
    # i-th row P[i,:]
    n = len(P)
    P_dist = [DiscreteRV(P[i,:]) for i in range(n)]

    # === generate the sample path === #
    for t in range(sample_size - 1):
        X[t+1] = P_dist[X[t]].draw()
    return X


import bjCommon
import numpy as np
import matplotlib.pyplot as plt
from scipy import rand
import time

#Problem 1
def lcg(n,a=1103515245,c=12345,mod=2**31-1,seed=4329):
    """
    Return an array of random numbers using the parameter specified
    """
    X = np.zeros(n)
    X[0] = (a*seed + c)%mod
    for i in range(1,n) :
        X[i] = (a*X[i-1] + c)%mod
    return X/(mod-1)

#Problem 2
def between(n,x1,x2,a=1103515245,c=12345,mod=2**31-1,seed=4329):
    """
    Scale n to be between x1 and x2
    """
    return (lcg(n,a,c,mod,seed)*(x2-x1)+x1).astype(int)

def problem3():
    """
    Graph the randoms from our lcg and also from scipy.rand
    """

    plt.subplot(211)
    size = 512
    rands = between(size**2,0,255,a=11035,c=1004,mod=2**15)
    rands = rands.reshape((size,size))
    plt.imshow(rands)
    plt.show()
	plt.close()

    plt.subplot(212)
    plt.imshow(rand(512,512)*255)
    plt.show()
	plt.close()

#Problem 4
def getSweepsEasy(games,n):
    """
    Return an n x games x 52 Array that represents the shuffles for <games> for
    the first n shuffles
    """
    #mine returns an np.array just we can use array slicing
    return np.array([bjCommon.shuffle(games,2521,13,2**16,seed) for seed in xrange(n)])

#Problem 5
def crackBlackJack(sweeps,cardTuples):
    """
    Return the sweeps whose first three cards from the nth game match the nth
    3-tuple in cardTuples

    For example, if cardTuples is a 2 x 3 list of tuples crackBlackJack should
    return the sweeps in "sweeps" whose first 3 cards in the first game match
    cardTyples[0] and whose first 3 cards in the second game match cardTuples[1]
    """
    for game in range(len(cardTuples)):
        hits = bjCommon.findSeedMatch(sweeps,game,cardTuples[game])
        sweeps = sweeps[hits]
    return bjCommon.convertToName(sweeps)

#Problem 6
def getSweepsHard(games,time,approx=120):
    """
    Return an n x games x 52 Array that represents the shuffles for <games> for
    the n possible seeds from the 2*approx second interval (above and below time)
    """
    #Create a list of seeds based on the time interval
    seeds = np.arange(time-approx,time+approx)
    return np.array([bjCommon.shuffle(games,25214903917, 11,2**48,seed) for seed in seeds])


from copy import deepcopy
import numpy as np
from PIL import Image
from scipy.special import binom
from scipy.ndimage import convolve
import matplotlib.pyplot as plt

def loadimg(path):
    """ Load image file
    Args:
        path: path to image file
    Returns:
        image as (H, W) np.array normalized to [0, 1]
    """

    #
    # You code here
    #
    img = Image.open(path)
    # why not normalized?
    img = np.array(img)
    return img

def gauss2d(sigma, fsize):
    """ Create a 2D Gaussian filter

    Args:
        sigma: width of the Gaussian filter
        fsize: (W, H) dimensions of the filter
    Returns:
        *normalized* Gaussian filter as (H, W) np.array
    """

    #
    # You code here
    #

    m, n = fsize
    # Just make matrixes
    x = np.arange(-m/2+0.5,m/2)
    y = np.arange(-n/2+0.5,n/2)
    # https://numpy.org/doc/stable/reference/generated/numpy.meshgrid.html
    # Expand rank
    X,Y = np.meshgrid(x, y, sparse = True)
    g = np.exp(-(X**2 + Y**2)/(2*sigma**2))
    # Normalization
    return g/np.sum(g)

def binomial2d(fsize):
    """ Create a 2D binomial filter

    Args:
        fsize: (W, H) dimensions of the filter
    Returns:
        *normalized* binomial filter as (H, W) np.array
    """

    #
    # You code here
    #

    def binomialfactors(n):
        a = np.array([[binom(n-1,i) for i in range(0, n)]])
        return a

    x = binomialfactors(fsize[0])
    y = binomialfactors(fsize[1])
    f = y.T * x

    return f/np.sum(f)

def downsample2(img, f):
    """ Downsample image by a factor of 2
    Filter with Gaussian filter then take every other row/column

    Args:
        img: image to downsample
        f: 2d filter kernel
    Returns:
        downsampled image as (H, W) np.array
    """

    #
    # You code here
    #

    # filter with Gaussian filter
    img_result = convolve(img,f,mode = "mirror")

    # take every other row/column
    return img_result[::2,::2]

def upsample2(img, f):
    """ Upsample image by factor of 2

    Args:
        img: image to upsample
        f: 2d filter kernel
    Returns:
        upsampled image as (H, W) np.array
    """

    #
    # You code here
    #

    up = np.zeros(2 * np.array(img.shape))
    up[::2,::2] = img
    # a scale factor is 4
    up = 4 * convolve(up, f, mode="mirror")

    return up

def gaussianpyramid(img, nlevel, f):
    """ Build Gaussian pyramid from image

    Args:
        img: input image for Gaussian pyramid
        nlevel: number of pyramid levels
        f: 2d filter kernel
    Returns:
        Gaussian pyramid, pyramid levels as (H, W) np.array
        in a list sorted from fine to coarse
    """

    #
    # You code here
    #

    pyramid = [img]
    for i in range(0, nlevel-1):
        pyramid.append(downsample2(pyramid[i],f))

    return pyramid

def laplacianpyramid(gpyramid, f):
    """ Build Laplacian pyramid from Gaussian pyramid

    Args:
        gpyramid: Gaussian pyramid
        f: 2d filter kernel
    Returns:
        Laplacian pyramid, pyramid levels as (H, W) np.array
        in a list sorted from fine to coarse
    """

    #
    # You code here
    #

    # reverse order of gaussian pyramid
    gpyramid = gpyramid[::-1]

    # build lapacian pyramid from coarse to fine
    lpyramid = [gpyramid[0]]
    for i in range(1, len(gpyramid)):
        # Vorlesung l3-edges_pyramids Seite 62
        lpyramid.append(gpyramid[i] - upsample2(gpyramid[i-1], f))

    # reverse lapalacian pyramid to correct order
    return lpyramid[::-1]

def reconstructimage(lpyramid, f):
    """ Reconstruct image from Laplacian pyramid

    Args:
        lpyramid: Laplacian pyramid
        f: 2d filter kernel
    Returns:
        Reconstructed image as (H, W) np.array clipped to [0, 1]
    """

    #
    # You code here
    #

    # start with coarsest level of Laplacian pyramid
    img = lpyramid[-1]

    # upsample and add next finer level
    for l in lpyramid[-2::-1]:
        img = l + upsample2(img, f)

    # make sure values of reconstructed image are inside bounds
    img = img.clip(0, 1)

    return img

def amplifyhighfreq(lpyramid, l0_factor=1.0, l1_factor=1.0):
    """ Amplify frequencies of the finest two layers of the Laplacian pyramid

    Args:
        lpyramid: Laplacian pyramid
        l0_factor: amplification factor for the finest pyramid level
        l1_factor: amplification factor for the second finest pyramid level
    Returns:
        Amplified Laplacian pyramid, data format like input pyramid
    """

    #
    # You code here
    #
    lpyramid = deepcopy(lpyramid)
    lpyramid[0] += l0_factor
    lpyramid[1] += l1_factor

    return lpyramid

def createcompositeimage(pyramid):
    """ Create composite image from image pyramid
    Arrange from finest to coarsest image left to right, pad images with
    zeros on the bottom to match the hight of the finest pyramid level.
    Normalize each pyramid level individually before adding to the composite image

    Args:
        pyramid: image pyramid
    Returns:
        composite image as (H, W) np.array
    """

    #
    # You code here
    #

    def normalize(img):
        return (img - img.min())/(img.max() - img.min())

    h = pyramid[0].shape[0]
    composite = [
        # array of rank 2 expands array
        np.pad(normalize(img), ((0, h - img.shape[0]), (0,0))) for img in pyramid
    ]
    composite = np.concatenate(composite, axis=1)

    return composite


# -*- coding: utf-8 -*-
"""
Ejercicios 1 y 2: Cálculo del polinomio de aproximación (caso discreto).
Utilizamos la matriz de Vandermonde (ver ejercicio1.py del Interpolación).

-----------------------------------------------------------------------------
Función Vandermonde_g: Halla la matriz de Vandermonde a partir
de un vector x (nodos) para construir un polinomio de aproximación de grado g.

Argumentos de entrada:
    x:  vector que contiene los nodos (array numpy).
    g:  grado del polinomio de aproximación (número entero).

Argumentos de salida:
    V:  matriz de Vandermonde con g+1 columnas (array numpy).

Ejemplos:
    x = np.array([1,2,3,4,5.])
    print('Matriz de Vandermonde con 4 columnas =\n', Vandermonde_g(x,3))
    Salida:
        Matriz de Vandermonde con 4 columnas =
        [[  1.   1.   1.   1.]
        [  1.   2.   4.   8.]
        [  1.   3.   9.  27.]
        [  1.   4.  16.  64.]
        [  1.   5.  25. 125.]]
"""

import numpy as np
import matplotlib.pyplot as plt
np.set_printoptions(precision = 2)   # solo dos decimales
np.set_printoptions(suppress = True) # no usar notación exponencial

# Función Vandermonde_g
#--------------------------------------------------------
def Vandermonde_g(x,g):
    n = len(x)        # dimensión de x
    V = np.ones((n, g+1)) # la matriz de Vandermonde tiene g+1 columnas
    for i in range(1, g+1):
        V[:,i] = V[:,i-1]*x # multiplicamos cada colunma por el vector x para
                            # generar la siguiente columna
    return V

#--------------------------------------
# Ejemplos
#--------------------------------------

# Ejemplo de prueba
x = np.array([1,2,3,4,5.])
print('Matriz de Vandermonde con 4 columnas =\n', Vandermonde_g(x,3))

#------------------------------------------------------------------------------
# Ejercicio1
#------------------------------------------------------------------------------
# Datos
f = lambda x : np.cos(x)         # función
x = np.array([-1,-0.5,0,0.5,1])  # nodos
y = f(x)  # valores en los nodos

# Calculamos el polinomio de aproximación de grado 2

# Calculamos la matriz de Vandermonde
V = Vandermonde_g(x,2)
# La traspuesta de V es V.T
A = np.dot(V.transpose(),V)       # multiplicamos V traspuesa por V (matriz del sistema)
b = np.dot(V.transpose(),y.transpose())       # multiplicamos V traspuesa por y traspuesto (vector de términos independientes)
print('Matriz del sistema =\n', A)    # mostramos la matriz A
print('Término independiente =', b)   # mostramos el vector b

# Resolvemos el sistema
p = np.linalg.solve(A, b)
print('Solución del sistema =',p) # p es el polinomio de aproximación

# Reordenamos los coeficientes del polinomio p
# para que parezcan de mayor a menor potencia de x
p = p[::-1]

# Para hacer la representación gráfica del polinomio
plt.close('all')

xx = np.linspace(-1,1)   # para hacer la gráfica del polinomio p  en [-1,1]
yy = np.polyval(p,xx) # evaluamos p en xx

plt.figure(1)
plt.plot(x,y,'ro')      # dibujamos los puntos (en rojo con o)
plt.plot(xx,yy, label = 'p')      # dibujamos el polinomio
plt.grid()
plt.legend()
plt.show()

#------------------------------------------------------------------------------
# Ejercicio 2 (se resuelve como el caso anterior pero con los datos del ejercicio 2)
#------------------------------------------------------------------------------
# Datos
f = lambda x : np.cos(np.arctan(x)) - np.exp(x**2)*np.log(x+2)
x = np.linspace(-1,1,10)
y = f(x)

# Calculamos el polinomio de aproximación de grado 4

# Calculamos la matriz de Vandermonde
V = Vandermonde_g(x,4)
# La traspuesta de V es V.T
A = np.dot(V.transpose(),V)       # multiplicamos V traspuesa por V (matriz del sistema)
b = np.dot(V.transpose(),y.transpose())       # multiplicamos V traspuesa por y traspuesto (vector de términos independientes)
print('Matriz del sistema =\n', A)    # mostramos la matriz A
print('Término independiente =', b)   # mostramos el vector b

# Resolvemos el sistema
p = np.linalg.solve(A, b)
print('Solución del sistema =',p) # p es el polinomio de aproximación

# Reordenamos los coeficientes del polinomio p
# para que parezcan de mayor a menor potencia de x
p = p[::-1]

# Para hacer la representación gráfica del polinomio
plt.close('all')

xx = np.linspace(-1,1)   # para hacer la gráfica del polinomio p  en [-1,1]
yy = np.polyval(p,xx) # evaluamos p en xx

plt.figure(1)
plt.plot(x,y,'ro')      # dibujamos los puntos (en rojo con o)
plt.plot(xx,yy, label = 'p')      # dibujamos el polinomio
plt.grid()
plt.legend()
plt.show()


import math
import numpy as np

def info_gain(examples, attribute, target_index=-1):
    if attribute.type_ == 'discrete':
        return info_gain_discrete(examples, attribute.index, target_index)
    if attribute.type_ == 'continuous':
        return info_gain_continuous(examples, attribute.index, target_index)
    raise ValueError('invalid attibute type')

def info_gain_discrete(examples, attribute_index, target_index=-1):
    subsets_dict = {}
    for example in examples:
        key = example[attribute_index]
        if key in subsets_dict:
            subsets_dict[key].append(example)
        else:
            subsets_dict[key] = [example]

    gain = entropy(examples, target_index)
    for subset in subsets_dict.values():
        proportion = 1.0 * len(subset) / len(examples)
        gain -= proportion * entropy(subset)
    return gain, None

def info_gain_continuous(examples, attribute_index, target_index=-1):
    values = sorted(set([example[attribute_index] for example in examples]))
    if len(values) == 1:
        return -1, 0

    thresholds = []
    for i in range(1, len(values)):
        thresholds.append(0.5 * (values[i-1] + values[i]))

    gains = []
    for threshold in thresholds:
        subsets = [[], []]
        for example in examples:
            if example[attribute_index] >= threshold:
                subsets[0].append(example)
            else:
                subsets[1].append(example)

        gain = entropy(examples, target_index)
        for subset in subsets:
            proportion = 1.0 * len(subset) / len(examples)
            gain -= proportion * entropy(subset)

        gains.append(gain)

    index = np.argmax(gains)
    return gains[index], thresholds[index]

def entropy(examples, target_index=-1):
    target_counts = {}
    for example in examples:
        target = example[target_index]
        if target in target_counts:
            target_counts[target] += 1
        else:
            target_counts[target] = 1

    result = 0
    for count in target_counts.values():
        proportion = 1.0 * count / len(examples)
        result -= proportion * math.log(proportion, 2)
    return result


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

# =====================generate Ellipse=====================

# angle for rotating
theta = np.pi * 30 / 180

# ========================rotate============================
# ------------rotating matrix(Anti-clockwise)---------------
M = [[np.cos(theta), -np.sin(theta)],
     [np.cos(theta), np.cos(theta)]]
# -----------------------rotating---------------------------

# A = np.array(M)
A = [[1, 2, 2], [2, 1, 2], [2, 2, 1]]
# A = [[1, 0], [0, 1]]
# A = [[1, 1], [0, 1]]
# A = [[2, 0], [0, 1]]
print(A)
# ==================Eigenvalue decomposition================

evalues, evectors = np.linalg.eig(A)

print("A: ", A)
print("evalues: ", evalues)
print("evectors: ", evectors)

# =====================plot eigen vector=====================

colorlines = ['-r', '-b']
pytool.plot_vectors2d(evalues*evectors, title='eigen vectors', colorlines=colorlines)
# pytool.plot_vectors2d(evectors, title='eigen vectors', colorlines=colorlines)
plt.legend(['eigen vector1', 'eigen vector2'])
plt.show()


#!/usr/bin/python3
from sympy import *

tx,ty,gamma = symbols('tx ty gamma')
x,y = symbols('x y')

R_z = Matrix([[cos(gamma),-sin(gamma)],[sin(gamma), cos(gamma)]])

v = Matrix([[x],[y]])
t = Matrix([[tx],[ty]])

v_prime = (R_z * v) + t

d = Matrix([tx,ty,gamma])
J = v_prime.jacobian(d)

init_printing()

pprint(v_prime)
pprint(J)

print(latex(v_prime[0]))
print("-------------------------")
print(latex(v_prime[1]))
print("-------------------------")
print(latex(J.col(0)))
#print("-------------------------")
#print(latex(J.jacobian(d)))

hessian = []
for i in range(3):
    print("-------------------------")
    print(latex(J.col(i).jacobian(d)))
    hessian.append(J.col(i).jacobian(d))
pprint(hessian)


import numpy as np
from scipy.linalg import eigh
from matplotlib import pyplot as plt

# enter number of stories
story = int(input("Enter the number of stories: "))

# enter the story stiffnesses
k_stories = []
for item in range(1,story+1):
  k_ = float(input(f"Enter the k{item}: "))
  k_stories.append(k_)

#enter the story masses
m_stories = []
for item in range(1,story+1):
  m_ = float(input(f"Enter the m{item}: "))
  m_stories.append(m_)

# creation of mass matrix of the system
I = np.identity(story)
M_system = I * m_stories

#creation of stiffness matrix of the system
K_system = np.zeros((story,story))
for i in range(story-1):
    for j in range(story):
        if i == j :
            K_system[i,j]= k_stories[i] + k_stories[i+1]
        elif i - j == 1 :
            K_system[i,j]= -1* k_stories[i]
        elif j-i == 1:
            K_system[i,j]= -1* k_stories[j]
        else:
            K_system[i,j] = 0

K_system[story-1,story-1] = k_stories[-1]
K_system[story-1,story-2] = -1*k_stories[-1]

# calculation of eigenvalues and eigenvectors;
evals, evecs = eigh(K_system,M_system)

# calculation of natural frequencies and periods
frequencies = np.sqrt(evals)
periods = 2*np.pi/frequencies
print(f"natural frequencies: {frequencies}")
print(50*"=")
print(f"periods: {periods}")
print(50*"=")

# determination of the modal matrix
mods = evecs.copy()
for j in range(story):
  mods[0::,j] = evecs[0::,j]/evecs[-1,j]
print(f"MODAL MATRIX: \n{mods}")
print(50*"=")

# Plotting of the mode shapes
  # arrangement for modal matrix to plot
mod_g = np.zeros((story+1,story))
mod_g[1::,0::] = mods
mods_list = {}
for item in range(1,story+1):
  mods_list[item] = mod_g[0::,item-1]

  #Plotting
for item in range(1,story+1):
  plt.figure(figsize=(4,9))
  plt.grid(color= "r", axis="y")
  plt.plot(mods_list[item],range(0,story+1,1),"k*-")

  ax = plt.gca()
  ax.axes.yaxis.set_ticklabels([])

  for a,b in zip(mods_list[item], range(0,story+1,1)):
    plt.text(a, b, str(np.around(a,4)), fontsize=20,bbox=dict(facecolor='w', alpha=0.5))

  plt.title(f'Mod {item}', fontsize=20, bbox=dict(facecolor='r', alpha=0.50))
  plt.show()


import numpy as np
import math
from sklearn.decomposition import PCA
''' Multiview 2D projection (M2DP) descriptor.

 Input:
       data        n*3     Point cloud. Each row is [x y z]
 Output:
       desM2DP     192*1   M2DP descriptor of the input cloud data
       A           64*128  Signature matrix

 Introduction:
 M2DP is a global descriptor of input point cloud. Details of M2DP can be
 found in the following paper:

 Li He, Xiaolong Wang and Hong Zhang, M2DP: A Novel 3D Point Cloud
 Descriptor and Its Application in Loop Closure Detection, IROS 2016.

 Li He, Dept. of Computing Science, University of Alberta
 lhe2@ualberta.ca
'''

def cart2sph(x,y,z):
    azimuth = np.arctan2(y,x)
    elevation = np.arctan2(z,np.sqrt(x**2 + y**2))
    r = np.sqrt(x**2 + y**2 + z**2)
    return azimuth, elevation, r

def sph2cart(azimuth,elevation,r):
    x = r * np.cos(elevation) * np.cos(azimuth)
    y = r * np.cos(elevation) * np.sin(azimuth)
    z = r * np.sin(elevation)
    return x, y, z
def cart2pol(x, y):
    rho = np.sqrt(x**2 + y**2)
    phi = np.arctan2(y, x)
    return(rho, phi)

def pol2cart(rho, phi):
    x = rho * np.cos(phi)
    y = rho * np.sin(phi)
    return(x, y)
def GetSignatureMatrix(azimuthList,elevationList, cloud_pca,
                       numT, numR, maxRho):
    shape_A=(azimuthList.shape[0]*elevationList.shape[0],numT*numR)
    thetaList=np.linspace(-math.pi,math.pi,numT+1)
    #rho list
    rhoList = np.linspace(0,np.sqrt(maxRho),numR+1)
    rhoList = np.square(rhoList)
    rhoList[-1] = rhoList[-1]+.001 #make sure all points in bins
    n=0
    A=[]
    #loop on azimuth
    for azm in azimuthList:
        #loop on evevation
        for elv in elevationList:
            #normal vector vecN of the selected 2D plane
            x,y,z=sph2cart(azm,elv,1)
            vecN=np.array([x,y,z])
            #distance of vector [1,0,0] to the surface with normal vector vecN
            op= np.array([1,0,0])
            h = op@vecN.T
            #a new vector, c = h*vecN, so that vector [1,0,0]-c is the
            # projection of x-axis onto the plane with normal vector vecN
            c= h*vecN
            #x-axis - c, the projection
            px= op-c
            #given the normal vector vecN and the projected x-axis px, the
            # y- axis is cross(vecN,px)
            py= np.cross(vecN,px)
            #projection of data onto space span{px,py}
            pcx=cloud_pca@px.T
            pcy=cloud_pca@py.T
            #pdata = np.array([pcx,pcy])
            #represent data in polar coordinates
            rho,theta=cart2pol(pcx,pcy)

            #main function, count points in bins
            hist= np.histogram2d(theta.T,rho.T,[thetaList,rhoList])[0]
            hist=hist/cloud_pca.shape[0]
            A.append(hist.T.ravel())
    return np.array(A)

def M2DP(cloud):
    #key parameter
    # number of bins in theta, the 't' in paper
    numT = 16
    #number of bins in rho, the 'l' in paper
    numR = 8
    # number of azimuth angles, the 'p' in paper
    numP = 4
    # number of elevation angles, the 'q' in paper
    numQ = 16

    '''3D rotate input data so that
    x-axis and y-axis are the
    1st and 2nd PCs of data respectively
    rotation invariant'''
    pca=PCA()
    cloud_pca=pca.fit_transform(cloud)
    np.negative(cloud_pca[:,1])
    np.negative(cloud_pca[:,2])
    half_pi=math.pi/2
    azimuthList =np.linspace(-half_pi,half_pi,numP)
    elevationList = np.linspace(0,half_pi,numQ)
    #get the farthest point distance
    rho2=np.sum(np.square(cloud_pca),axis=1)
    maxRho=np.sqrt(rho2.max())
    #main function, get the signature matrix A
    A=GetSignatureMatrix(azimuthList, elevationList, cloud_pca, numT, numR, maxRho)
    #run SVD on A and use [u0,v0] as the final output
    u,s,v = np.linalg.svd(A);
    desM2DP = np.append(u[:,0],v.T[:,0])
    return desM2DP,A


import matplotlib.pyplot as plt
import numpy as np

Ar = .8
fr = 2.0
phi = np.pi/2
fs = 100
t = np.arange(-1, 1, 1.0/fs)
x = Ar * np.cos(2*np.pi*fr*t+phi)

plt.figure(1, figsize=(5, 3))
plt.plot(t, x, 'b', lw=1.5)
plt.axis([-1, 1,-.8,.8])
plt.xlabel('time (seconds)')
plt.ylabel('amplitude')

plt.tight_layout()
plt.savefig('sine-synthesis.png')
plt.show()


import LinearAlgebraPurePython as la
import MachineLearningPurePy as ml

from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
import numpy as np

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

import random
import sys

# Section 1: Fake data prep and visualization
X = [[x for x in range(2,9)],
     [2**(x/1.0)  for x in range(0,7)]]

def y_of_x(xa):
    return 0.2*xa[0]**2 + 0.03*xa[0]*xa[1] + \
           0.7*xa[0] + 0.01*xa[1]**2 + \
           0.04*xa[1] + 2.0
           #+ random.uniform(-2.0,2.0)
Y = []
for xa in la.transpose(X):
    Y.append(y_of_x(xa))

fig = plt.figure()
ax = plt.axes(projection='3d')
ax.scatter3D(X[0], X[1], Y)

# Section 2: Get fake data in correct format
X = la.transpose(X)
Y = la.transpose([Y])

# Section 3: Pure Python Tools Fit
poly_pp = ml.Poly_Features_Pure_Py(order=2)
Xpp = poly_pp.fit_transform(X)
ls_pp = ml.Least_Squares(add_ones_column=False)
ls_pp.fit(Xpp, Y)

# Section 4: SciKit Learn Fit
poly_sk = PolynomialFeatures(degree = 2)
Xps = poly_sk.fit_transform(X)
ls_sk = LinearRegression()
ls_sk.fit(Xps, Y)

# Section 5: Coefficients Comparison
tmp_ls_pp_coefs = sorted(ls_pp.coefs)
rounded_ls_pp_coefs = [round(x,8)+0
for x in la.transpose(tmp_ls_pp_coefs)[0]] #
print('PurePy  LS coefficients:',
        rounded_ls_pp_coefs)

tmp_ls_sk_coefs = ls_sk.intercept_.tolist() + \
                  ls_sk.coef_[0][1:].tolist()
tmp_ls_sk_coefs = sorted(tmp_ls_sk_coefs)
rounded_ls_sk_coefs = [round(x,8)+0
                        for x in tmp_ls_sk_coefs] #
print('SKLearn LS coefficients:',
        rounded_ls_sk_coefs)

print('Coef Deltas:', [rounded_ls_pp_coefs[i] -
        rounded_ls_sk_coefs[i]
        for i in range(len(rounded_ls_pp_coefs))])

# Section 6: Create Fake Test Data
XLS = [[x/2.0 for x in range(4,18)],
       [2**(x/2.0)  for x in range(0,14)]]

# Section 6.1: Predict with Fake Test Data
#   Using Pure Python Tools
XLSpp = poly_pp.transform(la.transpose(XLS))
YLSpp = ls_pp.predict(XLSpp)
YLSpp = la.transpose(YLSpp)[0]

# Section 6.2: Predict with Fake Test Data
#   Using SciKit Learn Tools
XLSsk = poly_sk.transform(la.transpose(XLS))
YLSsk = ls_sk.predict(XLSsk)
YLSsk = YLSsk.tolist()
YLSsk = la.transpose(YLSsk)[0]

# Section 7: Calculate Prediction Differences
YLSpp.sort()
YLSsk.sort()
deltas = [ YLSpp[i] - YLSsk[i]
            for i in range(len(YLSpp)) ]
print( '\nPrediction Deltas:', deltas , '\n')

# Section 8: Plot Both Methods
ax.plot3D(XLS[0], XLS[1], YLSpp)
ax.plot3D(XLS[0], XLS[1], YLSsk)
ax.set_xlabel('X0 Values')
ax.set_ylabel('X1 Values')
ax.set_zlabel('Y Values')
Title = 'Pure Py Lst Sqrs Fit /w 2 x Deg 2 Inputs'
ax.set_title(Title)
plt.show()


#!/usr/bin/python

#############################################################
# Logistic regression #
# Sk. Mashfiqur Rahman #
# collect data from http://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data #
#############################################################

import numpy as np
from scipy.optimize import minimize

def flower_to_float(s):
    d = {b'Iris-setosa': 0., b'Iris-versicolor': 1., b'Iris-virginica': 2.}
    return d[s]

def phi(_x, _i):
    phi1 = np.ones(5)
    phi1[1:5] = _x[_i,1:5]
    return phi1

def f(_w, training_x, training_y, prior):
    N = training_x.shape[0]
    K = 3
    log_likelihood= 0.
    for n in range(N):
        ll2 = 0.
        ll3 = 0.
        for k in range(K):
            ll2 += training_y[n, k] * _w[5*k:(5*k)+5].T.dot(phi(training_x,n))
        for l in range(K):
            ll3 += np.exp(_w[5*l:(5*l)+5].T.dot(phi(training_x,n)))
        log_likelihood += ll2
        log_likelihood -= np.log(ll3)
        #  forced normalize the overflow encountered in exp
        if log_likelihood == -np.inf:
            log_likelihood = 0.
    return prior - log_likelihood

irises = np.loadtxt('iris.txt', delimiter=',', converters={4:flower_to_float})
M = irises.shape[0]
K = 3
x = np.array(irises[:, :4])
x = np.concatenate((np.ones(shape=(M, 1)), x), axis=1)
y = np.zeros(shape=(M, K))
for i in range(M):
    y[i, int(irises[i,4])] = 1

r = np.arange(M)
np.random.shuffle(r)
x = np.array(x[r.reshape(-1)])
y = np.array(y[r.reshape(-1)])

training_x = x[:int(M/2)]
training_y = y[:int(M/2)]
test_x = x[int(M/2):]
test_y = y[int(M/2):]

alpha = 0.0031257
w_init = np.ones(15)
prior = (alpha/2.) * w_init.T.dot(w_init)

w_hat = minimize(f, w_init, args=(training_x, training_y, prior)).x

b = []
c = []
for i in range(test_x.shape[0]):
    z = np.zeros(K)
    for k in range(K):
        z[k] = np.exp(w_hat[5*k:(5*k)+5].T.dot(phi(test_x,i)))
        #  forced normalize the overflow encountered in exp
        if z[k] == np.inf:
            z[k] = 1000.
    s = z / np.sum(z)
    q = np.amax(s)
    b.append((s.tolist()).index(q))
for i in range(test_y.shape[0]):
    s = test_y[i,:]
    q = np.amax(s)
    c.append((s.tolist()).index(q))
k = np.equal(b,c)
print("overall classification accuracy = ", float(np.sum(k) / len(test_y))*100., '%')


import numpy as np
from jeeautodiff.autodiff import Node
from jeeautodiff.reverse_mode import Node_b

def sin(N):
    if isinstance(N, Node):
        val = np.sin(N.val)
        der = np.cos(N.val) * N.der
        return Node(val, der)

    if isinstance(N, Node_b):
        z = Node_b(np.sin(N.val))
        z.parents = [(N, np.cos(N.val))]
        return z

    try:
        float(N)
        return np.sin(N)
    except:
        raise ValueError("{}should either be a Node instance or a number".format(N))

def cos(N):
    if isinstance(N, Node):
        val = np.cos(N.val)
        der = -(np.sin(N.val) * N.der)
        return Node(val, der)

    if isinstance(N, Node_b):
        z = Node_b(np.cos(N.val))
        z.parents = [(N, -np.sin(N.val))]
        return z

    try:
        float(N)
        return np.cos(N)
    except:
        raise ValueError("{}should either be a Node instance or a number".format(N))

def tan(N):
    if isinstance(N, Node):
        val = np.tan(N.val)
        der = 1 / (np.cos(N.val) ** 2) * N.der
        return Node(val, der)
    if isinstance(N, Node_b):
        z = Node_b(np.tan(N.val))
        z.parents = [(N, 1 / (np.cos(N.val) ** 2))]
        return z

    try:
        float(N)
        return np.tan(N)
    except:
        raise ValueError("{}should either be a Node instance or a number".format(N))

def exp(N):
    if isinstance(N, Node):
        val = np.exp(N.val)
        der = np.exp(N.val) * N.der
        return Node(val, der)
    if isinstance(N, Node_b):
        z = Node_b(np.exp(N.val))
        z.parents = [(N, np.exp(N.val))]
        return z

    try:
        float(N)
        return np.exp(N)
    except:
        raise ValueError("{}should either be a Node instance or a number".format(N))

def log(N):
    if isinstance(N, Node):
        val = np.log(N.val)
        der = 1 / N.val * N.der
        return Node(val, der)

    if isinstance(N, Node_b):
        z = Node_b(np.log(N.val))
        z.parents = [(N,1 / N.val)]
        return z

    try:
        float(N)
        return np.log(N)
    except:
        raise ValueError("{}should either be a Node instance or a number".format(N))

def arcsin(N):
    if isinstance(N, Node):
        if N.val > 1 or N.val < -1:
            raise ValueError("{} should have values and derivatives -1 <= x <= 1 for arcsin use".format(N))
        val = np.arcsin(N.val)
        der = (1 / np.sqrt(1 - N.val**2)) * N.der
        return Node(val, der)
    if isinstance(N, Node_b):
        if N.val > 1 or N.val < -1:
            raise ValueError("{} should have values and derivatives -1 <= x <= 1 for arcsin use".format(N))
        z = Node_b(np.arcsin(N.val))
        z.parents = [(N, (1 / np.sqrt(1 - N.val**2)))]
        return z

    try:
        float(N)
        return np.arcsin(N)
    except:
        raise ValueError("{}should either be a Node instance or a number".format(N))

def arccos(N):
    if isinstance(N, Node):
        if N.val > 1 or N.val < -1 :
            raise ValueError("{} should have values and derivatives -1 <= x <= 1 for arccos use".format(N))
        val = np.arccos(N.val)
        der = (-1 / np.sqrt(1 - N.val**2)) * N.der
        return Node(val, der)

    if isinstance(N, Node_b):
        if N.val > 1 or N.val < -1:
            raise ValueError("{} should have values and derivatives -1 <= x <= 1 for arcsin use".format(N))
        z = Node_b(np.arccos(N.val))
        z.parents = [(N, (-1 / np.sqrt(1 - N.val ** 2)))]
        return z

    try:
        float(N)
        return np.arccos(N)
    except:
        raise ValueError("{}should either be a Node instance or a number".format(N))

def arctan(N):
    if isinstance(N, Node):
        val = np.arctan(N.val)
        der = 1 / (N.val**2 + 1) * N.der
        return Node(val, der)

    if isinstance(N, Node_b):
        z = Node_b(np.arctan(N.val))
        z.parents = [ (N,1 / (N.val**2 + 1)) ]
        return z
    try:
        float(N)
        return np.arctan(N)
    except:
        raise ValueError("{}should either be a Node instance or a number".format(N))

def sinh(N):
    if isinstance(N, Node):
        val = np.sinh(N.val)
        der = np.cosh(N.val) * N.der
        return Node(val, der)

    if isinstance(N, Node_b):
        z = Node_b(np.sinh(N.val))
        z.parents = [(N, np.cosh(N.val))]
        return z
    try:
        float(N)
        return np.sinh(N)
    except:
        raise ValueError("{}should either be a Node instance or a number".format(N))

def cosh(N):
    if isinstance(N, Node):
        val = np.cosh(N.val)
        der = np.sinh(N.val) * N.der
        return Node(val, der)

    if isinstance(N, Node_b):
        z = Node_b(np.cosh(N.val))
        z.parents = [(N, np.sinh(N.val))]
        return z
    try:
        float(N)
        return np.cosh(N)
    except:
        raise ValueError("{}should either be a Node instance or a number".format(N))

def tanh(N):
    if isinstance(N, Node):
        val = np.tanh(N.val)
        der = (1-np.tanh(N.val)**2) * N.der
        return Node(val, der)

    if isinstance(N, Node_b):
        z = Node_b(np.tanh(N.val))
        z.parents = [(N, (1-np.tanh(N.val)**2))]
        return z

    try:
        float(N)
        return np.tanh(N)
    except:
        raise ValueError("{}should either be a Node instance or a number".format(N))

def sqrt(N):
    if isinstance(N, Node):
        val = np.sqrt(N.val)
        der = 0.5 * (N.val ** -0.5) * N.der
        return Node(val, der)

    if isinstance(N, Node_b):
        z = Node_b(np.sqrt(N.val))
        z.parents = [(N, 0.5 * (N.val ** -0.5))]
        return z

    try:
        float(N)
        return np.sqrt(N)
    except:
        raise ValueError("{}should either be a Node instance or a number".format(N))

def logistic(N):
    if isinstance(N, Node):
        val = np.exp(N.val) / (np.exp(N.val) + 1)
        der = ( np.exp(-N.val) / (1+np.exp(-N.val))**2 ) * N.der
        return Node(val, der)

    if isinstance(N, Node_b):
        z = Node_b( np.exp(N.val) / (np.exp(N.val) + 1))
        z.parents = [(N, ( np.exp(-N.val) / (1+np.exp(-N.val))**2 ))]
        return z

    try:
        float(N)
        return np.exp(N) / (np.exp(N) + 1)
    except:
        raise ValueError("{}should either be a Node instance or a number".format(N))

def logb(b, N):
    try:
        float(b)
        if isinstance(N,Node):
            new_val = np.log(N.val)/np.log(b)
            new_der = (1/(np.log(b)*N.val))*N.der
            return Node(val=new_val,der=new_der)

        if isinstance(N, Node_b):
            z = Node_b(np.log(N.val)/np.log(b))
            z.parents = [(N, (1/(np.log(b)*N.val)))]
            return z
        try:
            float(N)
            new_val = np.log(N)/np.log(b)
            return new_val
        except:
            raise ValueError("{}should either be a Node instance or a number".format(N))
    except:
        raise ValueError("the base of a logb should be an int or a float")

def power(b,N):
    try:
        float(b)
        if isinstance(N,Node):
            val=b**N.val
            der=val*log(b)*N.der
            return Node(val, der)

        if isinstance(N, Node_b):
            val = b ** N.val
            z = Node_b(val)
            z.parents = [(N, val*log(b))]
            return z
        try:
            float(N)
            return b**N
        except:
            raise ValueError("{}should either be a Node instance or a number".format(N))
    except:
        raise ValueError("the base of a power should be an int or a float")


import math
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.io import loadmat
import scipy.optimize as opt

data = loadmat('data/ex3data1.mat')
theta = loadmat('data/ex4weights.mat')
theta1 = theta['Theta1']
theta2 = theta['Theta2']

x = data['X']
y_prime = data['y'].transpose()[0]
y_prime = np.array([elem if elem != 10 else 0 for elem in y_prime])
y = np.zeros((y_prime.shape[0], 10))
for i in range(y_prime.shape[0]):
	y[i][y_prime[i]] = 1

def g(z):
	#sigmoid function
	return np.float64(1.0) / (1.0 + math.pow(math.e, -z))

g = np.vectorize(g, otypes=[np.float64])
#now, when g is applied to a vector, it is applied to each cell

def unroll(thetas):
	unrolled_theta = np.concatenate(tuple([theta.flatten() for theta in thetas]), axis=0)
	return unrolled_theta

def roll(theta, layers):
	#returns a list of rolled theta matrices
	rolled_theta = []
	offset = 0
	for i in range(1, len(layers)):
		m, n = layers[i], layers[i - 1] + 1
		rolled_theta.append(theta[offset: offset + m * n].reshape((m, n)))
		offset += m * n
	return rolled_theta

def randomInit(layers):
	rolled_theta = []
	for i in range(1, len(layers)):
		m, n = layers[i], layers[i - 1] + 1
		rolled_theta.append((np.random.rand(m, n) - 0.5 * np.ones((m, n))))
	unrolled_theta = unroll(rolled_theta)
	return unrolled_theta

def cost(theta, h, y, layers, λ):
	#ith row of h or y is the hypothesis or expected output for the ith training set
	cost, m = 0, y.shape[0]
	for i in range(m):
		cost += y[i] @ np.log(h[i]) + (np.ones((y[i].shape)) - y[i]) @ np.log(np.ones(h[i].shape) - h[i])
	cost = -cost / m
	rolled_theta = roll(theta, layers)
	for theta_i in rolled_theta:
		cost += (λ / (2 * m)) * (np.sum(np.square(theta_i[:,1:])))
	return cost

def classifier(h):
	tmp = np.argmax(h, axis=1)
	output = np.zeros((h.shape), dtype='float64')
	for i in range(tmp.shape[0]):
		output[i][tmp[i]] = 1
	return output

def feedForward(theta, x, layers):
	#returns a list or np.ndarrays where list[i][j] is the
	#activator vector for the ith layer and jth dataset
	rolled_theta = roll(theta, layers)
	a = [x.transpose(),]
	for i in range(len(layers) - 1):
		a[-1] = np.insert(a[-1], [0], np.ones((a[-1].shape[1],), dtype=np.float64), axis=0)
		a.append(g(rolled_theta[i] @ a[-1]))
	for i in range(len(a)):
		a[i] = a[i].transpose()
	return a

def gradient(theta, x, y, layers, λ):
	print("running gradient descent")
	Δ = [0 for el in range(len(layers) - 1)]
	m = x.shape[0]
	a = feedForward(theta, x, layers)
	rolled_theta = roll(theta, layers)
	#list of np.ndarrays where list[i][j] contains δ for ith layer and jth dataset
	#δ fr ith layer is a matrix of m rows, each row for one training set
	δ = [0 for el in layers]
	δ[-1] = a[-1] - y
	for i in range(len(layers) - 2, -1, -1):
		g_prime = (a[i] * (np.ones(a[i].shape) - a[i]))
		tmp = np.array([rolled_theta[i].transpose() @ row for row in δ[i + 1]])
		#no error term for i = 0, but here we are calculating just for fun!
		#also no error term for bias units which is why we discard the first column of δ[i]
		δ[i] = (tmp * g_prime)[:,1:]
		Δ[i] = sum([np.outer(δ[i + 1][j], a[i][j]) for j in range(m)])
	for i in range(len(layers) - 1):
		tmp = np.insert(rolled_theta[i][:,1:], 0, np.zeros((rolled_theta[i].shape[0],)), axis=1)
		Δ[i] = Δ[i] / np.float64(m) + (λ / np.float64(m)) * tmp
	Δ_unrolled = unroll(Δ)
	return Δ_unrolled

def grad_check(theta, x, y, layers, λ):
	epsilon = 0.0001
	Δ_unrolled = np.zeros((theta.shape), dtype=np.float64)
	for i in range(len(theta) -1, 0, -1):
		tmp = np.zeros((theta.shape))
		tmp[i] = epsilon
		theta_up = theta + tmp
		theta_lo = theta - tmp
		cost_up = cost(theta_up, feedForward(theta_up, x, layers)[-1], y, layers, λ)
		cost_lo = cost(theta_lo, feedForward(theta_lo, x, layers)[-1], y, layers, λ)
		Δ_unrolled[i] = (cost_up - cost_lo) / (2 * epsilon)
		print(i, Δ_unrolled[i])
	return Δ_unrolled

def findAccuracy(theta, x, y, layers):
	y_pred = classifier(feedForward(theta, x, layers)[-1])
	correct = [1 if np.all(a == b) else 0 for (a, b) in zip(y_pred, y)]
	accuracy = (sum(map(int, correct)) / float(len(correct)))
	return accuracy

layers = [400, 25, 10]
#theta = unroll((theta1, theta2))
#randomly initialize a theta
theta = randomInit(layers)
λ = 1
#h = feedForward(theta, x, layers)[-1]
#print(cost(theta, h, y, layers, λ))
'''
grad = gradient(theta, x, y, layers, λ)
for i in range(grad.shape[0]):
	print(i, grad[i])
print("//")
print(grad_check(theta, x, y, layers, λ))
'''
#a = np.array([[[1, 2], [3, 4]], [[5, 6, 7], [8, 9, 10], [11, 12, 13]]])
#print(a.shape)
#out = classifier(h)
#print(out[4003])
#print(y[4003])

def backPropagate(theta, x, y, layers, λ):
	j = cost(theta, feedForward(theta, x, layers)[-1], y, layers, λ)
	grad = gradient(theta, x, y, layers, λ)
	return j, grad

fmin = opt.minimize(fun=backPropagate, x0=theta, args=(x, y, layers, λ), method='TNC', jac=True, options={'maxiter': 250})
theta = fmin.x
np.set_printoptions(threshold=np.nan)
print(fmin)
print(findAccuracy(theta, x, y, layers))


__all__ = [ 'sphericalToCartesian', 'cartesianToSpherical', 'normalTriad', 'elementaryRotationMatrix',
'phaseSpaceToAstrometry', 'astrometryToPhaseSpace']

from numpy import cos, sin, sqrt, arctan2, zeros_like, isscalar
from numpy import array, transpose, dot, any

# Astronomical Unit in meter, IAU constant and defining length
_auInMeter = 149597870700.0

# AU expressed in mas*pc or muas*kpc
_auMasParsec = 1000.0

# Number of seconds in Julian year
_julianYearSeconds = 365.25 * 86400.0

# AU expressed in km*yr/s
_auKmYearPerSec = _auInMeter/(_julianYearSeconds*1000.0)

def sphericalToCartesian(r, phi, theta):
  """
  Convert spherical to Cartesian coordinates. The input can be scalars or 1-dimensional numpy arrays.
  Note that the angle coordinates follow the astronomical convention of using elevation (declination,
  latitude) rather than its complement (pi/2-elevation), where the latter is commonly used in the
  mathematical treatment of spherical coordinates.

  Parameters
  ----------

  r     - length of input Cartesian vector.
  phi   - longitude-like angle (e.g., right ascension, ecliptic longitude) in radians
  theta - latitide-like angle (e.g., declination, ecliptic latitude) in radians

  Returns
  -------

  The Cartesian vector components x, y, z
  """
  ctheta=cos(theta)
  x=r*cos(phi)*ctheta
  y=r*sin(phi)*ctheta
  z=r*sin(theta)
  return x, y, z

def cartesianToSpherical(x, y, z):
  """
  Convert Cartesian to spherical coordinates. The input can be scalars or 1-dimensional numpy arrays.
  Note that the angle coordinates follow the astronomical convention of using elevation (declination,
  latitude) rather than its complement (pi/2-elevation), which is commonly used in the mathematical
  treatment of spherical coordinates.

  Parameters
  ----------

  x - Cartesian vector component along the X-axis
  y - Cartesian vector component along the Y-axis
  z - Cartesian vector component along the Z-axis

  Returns
  -------

  The spherical coordinates r=sqrt(x*x+y*y+z*z), longitude phi, latitude theta.

  NOTE THAT THE LONGITUDE ANGLE IS BETWEEN -PI AND +PI. FOR r=0 AN EXCEPTION IS RAISED.
  """
  rCylSq=x*x+y*y
  r=sqrt(rCylSq+z*z)
  if any(r==0.0):
    raise Exception("Error: one or more of the points is at distance zero.")
  return r, arctan2(y,x), arctan2(z,sqrt(rCylSq))

def normalTriad(phi, theta):
  """
  Calculate the so-called normal triad [p, q, r] which is associated with a spherical coordinate system .
  The three vectors are:

  p - The unit tangent vector in the direction of increasing longitudinal angle phi.
  q - The unit tangent vector in the direction of increasing latitudinal angle theta.
  r - The unit vector toward the point (phi, theta).

  Parameters
  ----------

  phi   - longitude-like angle (e.g., right ascension, ecliptic longitude) in radians
  theta - latitide-like angle (e.g., declination, ecliptic latitude) in radians

  Returns
  -------

  The normal triad as the vectors p, q, r
  """
  sphi=sin(phi)
  stheta=sin(theta)
  cphi=cos(phi)
  ctheta=cos(theta)
  p=array([-sphi, cphi, zeros_like(phi)])
  q=array([-stheta*cphi, -stheta*sphi, ctheta])
  r=array([ctheta*cphi, ctheta*sphi, stheta])
  return p, q, r

def elementaryRotationMatrix(axis, rotationAngle):
  """
  Construct an elementary rotation matrix describing a rotation around the x, y, or z-axis.

  Parameters
  ----------

  axis          - Axis around which to rotate ("x", "y", or "z")
  rotationAngle - the rotation angle in radians

  Returns
  -------

  The rotation matrix

  Example usage
  -------------

  rotmat = elementaryRotationMatrix("y", pi/6.0)
  """
  if (axis=="x" or axis=="X"):
    return array([[1.0, 0.0, 0.0], [0.0, cos(rotationAngle), sin(rotationAngle)], [0.0,
      -sin(rotationAngle), cos(rotationAngle)]])
  elif (axis=="y" or axis=="Y"):
    return array([[cos(rotationAngle), 0.0, -sin(rotationAngle)], [0.0, 1.0, 0.0], [sin(rotationAngle),
      0.0, cos(rotationAngle)]])
  elif (axis=="z" or axis=="Z"):
    return array([[cos(rotationAngle), sin(rotationAngle), 0.0], [-sin(rotationAngle),
      cos(rotationAngle), 0.0], [0.0, 0.0, 1.0]])
  else:
    raise Exception("Unknown rotation axis "+axis+"!")

def phaseSpaceToAstrometry(x, y, z, vx, vy, vz):
  """
  From the given phase space coordinates calculate the astrometric observables, including the radial
  velocity, which here is seen as the sixth astrometric parameter. The phase space coordinates are
  assumed to represent barycentric (i.e. centred on the Sun) positions and velocities.

  This function has no mechanism to deal with units. The velocity units are always assumed to be km/s,
  and the code is set up such that for positions in pc, the return units for the astrometry are radians,
  milliarcsec, milliarcsec/year and km/s. For positions in kpc the return units are: radians,
  microarcsec, microarcsec/year, and km/s.

  NOTE that the doppler factor k=1/(1-vrad/c) is NOT used in the calculations. This is not a problem for
  sources moving at typical velocities of Galactic stars.

  Parameters
  ----------

  x - The x component of the barycentric position vector (in pc or kpc).
  y - The y component of the barycentric position vector (in pc or kpc).
  z - The z component of the barycentric position vector (in pc or kpc).
  vx - The x component of the barycentric velocity vector (in km/s).
  vy - The y component of the barycentric velocity vector (in km/s).
  vz - The z component of the barycentric velocity vector (in km/s).

  Returns
  -------

  phi       - The longitude-like angle of the position of the source (radians).
  theta     - The latitude-like angle of the position of the source (radians).
  parallax  - The parallax of the source (in mas or muas, see above)
  muphistar - The proper motion in the longitude-like angle, multiplied by cos(theta) (mas/yr or muas/yr,
  see above)
  mutheta   - The proper motion in the latitude-like angle (mas/yr or muas/yr, see above)
  vrad      - The radial velocity (km/s)
  """
  u, phi, theta = cartesianToSpherical(x, y, z)
  parallax = _auMasParsec/u
  p, q, r = normalTriad(phi, theta)
  velocitiesArray=array([vx,vy,vz])
  if  isscalar(u):
    muphistar=dot(p,velocitiesArray)*parallax/_auKmYearPerSec
    mutheta=dot(q,velocitiesArray)*parallax/_auKmYearPerSec
    vrad=dot(r,velocitiesArray)
  else:
    muphistar=zeros_like(parallax)
    mutheta=zeros_like(parallax)
    vrad=zeros_like(parallax)
    for i in xrange(parallax.size):
      muphistar[i]=dot(p[:,i],velocitiesArray[:,i])*parallax[i]/_auKmYearPerSec
      mutheta[i]=dot(q[:,i],velocitiesArray[:,i])*parallax[i]/_auKmYearPerSec
      vrad[i]=dot(r[:,i],velocitiesArray[:,i])

  return phi, theta, parallax, muphistar, mutheta, vrad

def astrometryToPhaseSpace(phi, theta, parallax, muphistar, mutheta, vrad):
  """
  From the input astrometric parameters calculate the phase space coordinates. The output phase space
  coordinates represent barycentric (i.e. centred on the Sun) positions and velocities.

  This function has no mechanism to deal with units. The code is set up such that for input astrometry
  with parallaxes and proper motions in mas and mas/yr, and radial velocities in km/s, the phase space
  coordinates are in pc and km/s. For input astrometry with parallaxes and proper motions in muas and
  muas/yr, and radial velocities in km/s, the phase space coordinates are in pc and km/s. Only positive
  parallaxes are accepted, an exception is thrown if this condition is not met.

  NOTE that the doppler factor k=1/(1-vrad/c) is NOT used in the calculations. This is not a problem for
  sources moving at typical velocities of Galactic stars.

  THIS FUNCTION SHOULD NOT BE USED WHEN THE PARALLAXES HAVE RELATIVE ERRORS LARGER THAN ABOUT 10-15 PER
  CENT. For astrometric data with relatively large parallax errors you should consider doing your
  analysis in the data space and use forward modelling of some kind.

  Parameters
  ----------

  phi       - The longitude-like angle of the position of the source (radians).
  theta     - The latitude-like angle of the position of the source (radians).
  parallax  - The parallax of the source (in mas or muas, see above)
  muphistar - The proper motion in the longitude-like angle, multiplied by cos(theta) (mas/yr or muas/yr,
  see above)
  mutheta   - The proper motion in the latitude-like angle (mas/yr or muas/yr, see above)
  vrad      - The radial velocity (km/s)

  Returns
  -------

  x - The x component of the barycentric position vector (in pc or kpc).
  y - The y component of the barycentric position vector (in pc or kpc).
  z - The z component of the barycentric position vector (in pc or kpc).
  vx - The x component of the barycentric velocity vector (in km/s).
  vy - The y component of the barycentric velocity vector (in km/s).
  vz - The z component of the barycentric velocity vector (in km/s).
  """
  if any(parallax<=0.0):
    raise Exception("One or more of the input parallaxes is non-positive")
  x, y, z = sphericalToCartesian(_auMasParsec/parallax, phi, theta)
  p, q, r = normalTriad(phi, theta)
  transverseMotionArray = array([muphistar*_auKmYearPerSec/parallax, mutheta*_auKmYearPerSec/parallax,
    vrad])
  if isscalar(parallax):
    velocityArray=dot(transpose(array([p, q, r])),transverseMotionArray)
    vx = velocityArray[0]
    vy = velocityArray[1]
    vz = velocityArray[2]
  else:
    vx = zeros_like(parallax)
    vy = zeros_like(parallax)
    vz = zeros_like(parallax)
    for i in xrange(parallax.size):
      velocityArray = dot(transpose(array([p[:,i], q[:,i], r[:,i]])), transverseMotionArray[:,i])
      vx[i] = velocityArray[0]
      vy[i] = velocityArray[1]
      vz[i] = velocityArray[2]
  return x, y, z, vx, vy, vz


#!/usr/bin/env python

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.metrics import mean_squared_error, r2_score

m = 500
np.random.seed(seed = 5)
X = 6 * np.random.random(m).reshape(-1, 1) - 3
y = 0.5 * (X ** 5) - (X ** 3) - (X ** 2) + 2 + (5 * np.random.randn(m, 1))

# Split X data into train and test data
X_train = X[:300]
X_test = X[-200:]

# Split y data into train and test data
y_train = y[:300]
y_test = y[-200:]

# Linear Regression
model_linear = LinearRegression()
model_linear.fit(X_train, y_train)

y_train_linear_pred = model_linear.predict(X_train)
y_test_linear_pred = model_linear.predict(X_test)

# Polynomial Regression
plot_degrees = [2, 5, 8, 10, 20]
plot_data = []

loss_train_values = []
loss_test_values = []

degree_best = None;
loss_average_best = None;

for degree in range(2, 26):
  poly_features = PolynomialFeatures(degree = degree)
  X_train_poly = poly_features.fit_transform(X_train)
  X_test_poly = poly_features.fit_transform(X_test)

  model_poly = LinearRegression()
  model_poly.fit(X_train_poly, y_train)

  y_train_poly_pred = model_poly.predict(X_train_poly)
  y_test_poly_pred = model_poly.predict(X_test_poly)

  loss_train = mean_squared_error(y_train, y_train_poly_pred)
  loss_test = mean_squared_error(y_test, y_test_poly_pred)

  loss_train_values.append(loss_train)
  loss_test_values.append(loss_test)

  loss_average = (loss_train + loss_test) / 2

  if (loss_average_best is None) or (loss_average < loss_average_best):
    degree_best = degree
    loss_average_best = loss_average

  if degree in plot_degrees:
    plot_data.append({
      'degree': degree,
      'y_train_poly_pred': y_train_poly_pred,
      'y_test_poly_pred': y_test_poly_pred,
      'loss_train': loss_train,
      'loss_test': loss_test
    })

# Plot prediction figures for each degree in plot_degrees
for data in plot_data:
  plt.figure()
  plt.title(f"Degree = {data['degree']} | Train Loss = {data['loss_train']:#0.1f} | Test Loss = {data['loss_test']:#0.1f}", fontweight = 'bold')
  plt.xlim(-3.5, 3.5)
  plt.xlabel('x', fontstyle = 'italic')
  plt.ylim(-115, 100)
  plt.ylabel('y', fontstyle = 'italic', rotation = 'horizontal', labelpad = 10)
  plt.scatter(X_test, y_test, color = 'black')
  plt.plot(X_test, y_test_linear_pred, color = 'blue', linewidth = 3, label = 'Train')
  plt.plot(X_test, data['y_test_poly_pred'], color = 'red', linewidth = 3, label = 'Test')
  plt.legend()

# Plot degree vs loss figure
plt.figure()
plt.title('Degree vs Loss', fontweight = 'bold')
plt.xlim(0, 27)
plt.xlabel('Degree', fontstyle = 'italic')
plt.ylim(0, 350)
plt.ylabel('Loss', fontstyle = 'italic', rotation = 'horizontal', labelpad = 20)
plt.plot(np.arange(2, 26, 1), loss_train_values, color = 'green', linewidth = 3, label = 'Train')
plt.plot(np.arange(2, 26, 1), loss_test_values, color = 'blue', linewidth = 3, label = 'Test')
plt.plot([degree_best], [loss_average_best], 'o', ms = 4, color = 'red', label = "Best")
plt.annotate(f"Degree = {degree_best}, Avg Loss = {loss_average_best:#0.1f}", xy = (degree_best, loss_average_best), xytext = (degree_best + 0.2, loss_average_best + 10))
plt.legend()

# Fit data to figure and display
plt.tight_layout()
plt.show()


#!bin/usr/env python

import nose as n
import numpy as np

"""
Module creates an array containing the evaluation of some h(x), defined here as 1 / sqrt(2 * pi) * e ** (-1 / 2 * x ** 2), at a series of 41 points in the interval [-4,4]
"""

import numpy as np

def fill_array(a, b, n):
    """
    Fills array with equally spaced points, taking the beginning point, the end point, and the number of desired coordinates
    """
    return np.linspace(a, b, n, endpoint=True, dtype=float)

def h(x):
    """
    A definition for the function h(x), returning the value of h evaluated at the point x
    """
    return 1.0 / np.sqrt(2.0 * np.pi) * np.exp(-1.0 / 2.0 * x ** 2.0)

def main():
    for x in range(41):
        print '%-0.5f %-0.5f' % (fill_array(-4, 4, 41)[x], h(fill_array(-4, 4, 41)[x]))

def test_h():
    """
    Test for function default h(x) defined above, using the initial point x = 0.0
    """
    test = h(0.0)
    n.tools.assert_almost_equals(test, (1 / np.sqrt(2 * np.pi)), places = 5)

if __name__ == "__main__":
    main()


import numpy as np
from matplotlib import pyplot as plt
from matplotlib.axes import Axes

from pymatviz.utils import NumArray

def marchenko_pastur_pdf(x: float, gamma: float, sigma: float = 1) -> float:
    """The Marchenko-Pastur probability density function describes the
    distribution of singular values of large rectangular random matrices.

    See https://wikipedia.org/wiki/Marchenko-Pastur_distribution.

    By comparing the eigenvalue distribution of a correlation matrix to this
    PDF, one can gauge the significance of correlations.

    Args:
        x (float): Position at which to compute probability density.
        gamma (float): Also referred to as lambda. The distribution's main parameter
            that measures how well sampled the data is.
        sigma (float, optional): Standard deviation of random variables assumed
            to be independent identically distributed. Defaults to 1 as
            appropriate for correlation matrices.

    Returns:
        float: Marchenko-Pastur density for given gamma at x
    """
    lambda_m = (sigma * (1 - np.sqrt(1 / gamma))) ** 2  # Largest eigenvalue
    lambda_p = (sigma * (1 + np.sqrt(1 / gamma))) ** 2  # Smallest eigenvalue

    prefac = gamma / (2 * np.pi * sigma**2 * x)
    root = np.sqrt((lambda_p - x) * (x - lambda_m))
    unit_step = x > lambda_p or x < lambda_m

    return prefac * root * (0 if unit_step else 1)

def marchenko_pastur(
    matrix: NumArray,
    gamma: float,
    sigma: float = 1,
    filter_high_evals: bool = False,
    ax: Axes = None,
) -> Axes:
    """Plot the eigenvalue distribution of a symmetric matrix (usually a correlation
    matrix) against the Marchenko Pastur distribution.

    The probability of a random matrix having eigenvalues >= (1 + sqrt(gamma))^2 in the
    absence of any signal is vanishingly small. Thus, if eigenvalues larger than that
    appear, they correspond to statistically significant signals.

    Args:
        matrix (Array): 2d array
        gamma (float): The Marchenko-Pastur ratio of random variables to observation
            count. E.g. for N=1000 variables and p=500 observations of each,
            gamma = p/N = 1/2.
        sigma (float, optional): Standard deviation of random variables. Defaults to 1.
        filter_high_evals (bool, optional): Whether to filter out eigenvalues larger
            than theoretical random maximum. Useful for focusing the plot on the area
            of the MP PDF. Defaults to False.
        ax (Axes, optional): matplotlib Axes on which to plot. Defaults to None.

    Returns:
        ax: The plot's matplotlib Axes.
    """
    if ax is None:
        ax = plt.gca()

    # use eigvalsh for speed since correlation matrix is symmetric
    evals = np.linalg.eigvalsh(matrix)

    lambda_m = (sigma * (1 - np.sqrt(1 / gamma))) ** 2  # Largest eigenvalue
    lambda_p = (sigma * (1 + np.sqrt(1 / gamma))) ** 2  # Smallest eigenvalue

    if filter_high_evals:
        # Remove eigenvalues larger than those expected in a purely random matrix
        evals = evals[evals <= lambda_p + 1]

    ax.hist(evals, bins=50, edgecolor="black", density=True)

    # Plot the theoretical density
    mp_pdf = np.vectorize(lambda x: marchenko_pastur_pdf(x, gamma, sigma))
    x = np.linspace(max(1e-4, lambda_m), lambda_p, 200)
    ax.plot(x, mp_pdf(x), linewidth=5)

    # Compute and display matrix rank
    # A ratio less than one indicates an undersampled set of RVs
    rank = np.linalg.matrix_rank(matrix)
    n_rows = len(matrix)

    plt.text(
        *[0.95, 0.9],
        f"rank deficiency: {rank}/{n_rows} {'(None)' if n_rows == rank else ''}",
        transform=ax.transAxes,
        ha="right",
    )

    return ax


"""
This code computes the gradient descent for Gaussian mixture model and plots
result of fitting
"""

import scipy.optimize as opt
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(-3, 3, 200)
y = np.exp( -(x ** 2)/2  ) - np.exp( -(x ** 2)  )*2 + np.exp( -(x ** 2)/0.25  ) *2.5- np.exp( -(x ** 2)/0.125  )*1.6

# deviation / mean / output weights
#W = np.array([ [0.2], [1.2], [1] ])# best model by enumeration (by hand :D )
W = np.array([ [1,0.3,0.3,1], [-2,-0.3,0.3,2], [1,1,1,1] ])# bad initialization
#W = np.array([ [1.7,1,0.5,0.35], [0,0,0,0], [1,-2,2.5,-1.6] ])# initialization that leads to global optimum
#W = np.array([ [1,0.3,0.3,1, 0.3, 0.3, 0.2], [-2,-0.3,0.3,2, -0.7, 0.7, 0], [1,1,1,1,1,1,1] ])# better results with more neurons

# Here I translate the parameters to more useful form
tW = np.abs(W[0,])
W[0,] = 1 / tW
W[1,] = W[1,] / tW

shapeOfW = W.shape
W0 = np.squeeze(np.asarray(W))

# this is required for optimization procedure
def Unflatten(Win):
    return np.reshape(Win, shapeOfW)

def ComputeGaussians(W, X):
    return np.dot( np.exp( -(np.outer(X, W[0,:]) - W[1,]) ** 2 ) , W[2,])

def Objective(Win):
    return np.linalg.norm( ComputeGaussians(Unflatten(Win), x) - y) ** 2 # Sq. L2 norm

sol = opt.minimize(Objective, W0)
W = Unflatten(sol.x)
print "solution objective:", Objective(W)

yp = ComputeGaussians(W, x);

fig,ax = plt.subplots(figsize=(10,4))
# plot data and predictions
ax.plot(x, y,'*')
ax.plot(x, yp,'-')
# plot Gaussian positions
xp = W[1,] / W[0,] # location of Gaussian

ax.plot(xp, xp*0,'o')

# plot separate gaussians, if necessary

"""
for i in range(4):
    ax.plot(x, ComputeGaussians(W[:,[i]], x), '-') """

ax.set_xlabel('x')
ax.set_ylabel('y')
fig.show()
plt.grid()
plt.show()


import sys
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import median_abs_deviation
from common.functions import mae, fit_transform, get_imputer, load_data, nan_strategy_methods

def iqr(column : pd.Series) -> pd.Series:
    q1 = column.quantile(.25)
    q3 = column.quantile(.75)
    return (column >= (q1 - q1 * 1.5)) & (column <= (q3 + q3 * 1.5))

def percentile_01_09(column : pd.Series) -> pd.Series:
    return (column >= column.quantile(.1)) & (column <= column.quantile(.9))

def log_transform(df: pd.DataFrame) -> pd.DataFrame:
    df_new= pd.DataFrame()
    for col in df.columns:
        df_new[col] = df[col].map(lambda i: np.log(i) if i > 0 else 0)
    return df_new

def z_score(column : pd.Series) -> pd.Series:
    return np.abs(column - column.mean()) <= (3 * column.std())

def modified_z_score_outlier(column: pd.Series) -> pd.Series:
    mad_column = median_abs_deviation(column)
    median = np.median(column)
    mad_score = np.abs(0.6745 * (column - median) / mad_column)
    return mad_score <= 3.5

def get_args() -> tuple:
    if len(sys.argv) > 1 and sys.argv[1] in nan_strategy_methods():
        nan_strategy = sys.argv[1]
    else:
        nan_strategy = None

    separate_column = True if ((len(sys.argv) > 2 and sys.argv[2] == '1') or
                               (len(sys.argv) < 3 and nan_strategy is None)) else False

    log_transfom_enable = True if len(sys.argv) > 3 and sys.argv[3] == '1' else False

    return (nan_strategy, separate_column,log_transfom_enable)

nan_strategy, separate_column, log_transfom_enable = get_args()

df = load_data(config={"usecols": ['YearBuilt', 'BuildingArea', 'Price', 'Car']})
df = fit_transform(df, get_imputer(nan_strategy)) if nan_strategy is not None else df
df = log_transform(df) if log_transfom_enable else df

print(df.describe())
df.boxplot()
plt.show()

Y_COL = 'Price'
rows_cnt = df.shape[0]
for func in [iqr, percentile_01_09, modified_z_score_outlier, z_score]:
    print("\nFunction " + func.__name__)
    mask = df.apply(lambda x: func(x))

    print("\nNumber of outliers:")
    print(mask.apply(lambda x: rows_cnt - x.sum()), "\n")

    x_cols = df.drop([Y_COL], axis=1).columns
    if separate_column:
        for col in x_cols:
            print(col + " Mae: " + str(mae(df[mask], [col], Y_COL)))

    print("\nMae: " + str(mae(df[mask], x_cols, Y_COL)))


import numpy as np
import matplotlib.pyplot as plt

def box_muller(rand_gen, num_samples):
    """
    Implementation of Box-Muller
    :param rand_gen:
    :param num_samples:
    :return:
    """
    z1 = []
    z2 = []
    for _ in range(num_samples):
        z1.append(next(rand_gen))
        z2.append(next(rand_gen))

    z1 = np.asarray(z1)
    z2 = np.asarray(z2)

    x1 = np.cos(2 * np.pi * z2) * np.sqrt((-2) * np.log(z1))
    return x1

def map_to_guass(x, u, sigma):
    # First change variance

    x = x * sigma

    # then change the mean
    x = x + u

    return x

def one_b(rand_gen):
    """
    Box Muller Method
    :param rand_gen:
    :return:
    """
    sigma = 2.4
    u = 3
    gauss = box_muller(rand_gen, 1000)
    gauss = map_to_guass(gauss, u=u, sigma=sigma)

    def gaussian(x, u=0, variance=1.):
        """
        Equation for the Gaussian PDF
        :param x:
        :param u:
        :param variance:
        :return:
        """
        return 1 / (np.sqrt(2 * np.pi * variance)) * np.exp(-(x - u) ** 2 / (2 * variance))

    pdf_x = np.linspace(u - 5 * sigma, u + 5 * sigma, 10000)
    pdf_y = gaussian(pdf_x, u, sigma * sigma)

    plt.plot(pdf_x, pdf_y, c='r', label='Gaussian PDF')
    plt.hist(gauss, density=True, bins=12, label='Box-Muller')
    plt.axvline(x=u + sigma, c='r', ls='dashed', label='1$\sigma$')
    plt.axvline(x=u - sigma, c='r', ls='dashed')
    plt.axvline(x=u - 2 * sigma, c='purple', ls='dashed', label='2$\sigma$')
    plt.axvline(x=u - 3 * sigma, c='g', ls='dashed', label='3$\sigma$')
    plt.axvline(x=u - 4 * sigma, c='y', ls='dashed', label='4$\sigma$')
    plt.axvline(x=u + 2 * sigma, c='purple', ls='dashed')
    plt.axvline(x=u + 3 * sigma, c='g', ls='dashed')
    plt.axvline(x=u + 4 * sigma, c='y', ls='dashed')
    plt.xlim(u - 5 * sigma, u + 5 * sigma)
    plt.xlabel("Random Number")
    plt.ylabel("Normalized Count")
    plt.legend(loc='best')
    plt.savefig("./plots/box_gauss.png", dpi=300)
    plt.cla()
    plt.close()


# --------------
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# code starts here
##Using read.csv() to load the data onto a dataframe variable
df = pd.read_csv(path)

##Computing the probability that the FICO score is above 700
df1 = df[df.fico > 700]
p_a = len(df1)/len(df)

##Computing the probability that the purpose of the loan is debt consolidation
df2 = df[df.purpose == 'debt_consolidation']
p_b = len(df2)/len(df)

##Computing the probability that the purpose of the loan is debt consolidation given a FICO score of above 700
df3 = df1[df1.purpose == 'debt_consolidation']
p_a_b = len(df3)/len(df1)

##Checking for independency
result = bool(p_a_b == p_b)

##Outputting the independency result
print(result)

# code ends here

# --------------
# code starts here
##Computing the probability that the loan has been paid back
df4 = df[df["paid.back.loan"] == "Yes"]
prob_lp = len(df4)/len(df)

##Computing the probability that the underwriting criteria of LendingClub.com are met
df5 = df[df["credit.policy"] == "Yes"]
prob_cs = len(df5)/len(df)

##Subsetting a new database comprising only those entries where the loan has been paid back [recreating the first subset to meet GreyAtom checks on the code]
new_df = df[df["paid.back.loan"] == "Yes"]

##Computing the probability that the the loan has been paid back given the underwriting criteria of LendingClub.com are met
df6 = df4[df4["credit.policy"] == "Yes"]
prob_pd_cs = len(df6)/len(df4)

##Applying Bayes theorem
bayes = prob_pd_cs*prob_lp/prob_cs

##Outputting the conditional probability
print(bayes)

# code ends here

# --------------
# code starts here
##Subsetting a new database comprising only those entries where the loan has not been paid back
df1 = df[df["paid.back.loan"] == "No"]

##Using value_counts() on purpose in the subsetted dataframe, and outputting the same
purpose_vc = df1["purpose"].value_counts()

##Plotting the bar chart for purpose
purpose_vc.plot.bar()

# code ends here

# --------------
# code starts here
##Computing the median and mean for installment
inst_median = df.installment.median()
inst_mean = df.installment.mean()

##Plotting the histogram for installment
df_inst = df.installment
hist_inst = df_inst.plot.hist(bins = 30)
print(hist_inst)

##Plotting the histogram for log of annual income
df_loginc = df["log.annual.inc"]
hist_loginc = df_loginc.plot.hist(bins = 30)
print(hist_loginc)

# code ends here


import symengine as sm
import itertools
import math

def mv_series(function_expression,
              variable_list,
              evaluation_point,
              degree):
    """
    Multivariate version of sympy series() function.
    https://stackoverflow.com/questions/22857162/multivariate-taylor-approximation-in-sympy
    """

    n_var = len(variable_list)

    # dict of variables and their evaluation_point coordinates, ready for the subs() method
    point_coordinates = {i : j for i, j in zip(variable_list, evaluation_point) }

    # list with exponents of the partial derivatives
    deriv_orders = list(itertools.product(range(degree + 1), repeat=n_var))

    # Discarding some higher-order terms
    deriv_orders = [deriv_orders[i] for i in range(len(deriv_orders)) if sum(deriv_orders[i]) <= degree]
    n_terms = len(deriv_orders)

    # same as before, but now ready for the diff() method
    deriv_orders_as_input = [list(sum(list(zip(variable_list, deriv_orders[i])), ())) for i in range(n_terms)]

    polynomial = 0
    for i in range(n_terms):
        partial_derivatives_at_point = function_expression.diff(*deriv_orders_as_input[i]).subs(point_coordinates)
        # e.g. df/(dx*dy**2) evaluated at (x0,y0)

        denominator = math.prod([math.factorial(j) for j in deriv_orders[i]])
        # e.g. (1! * 2!)

        distances_powered = math.prod([(sm.Matrix(variable_list) - sm.Matrix(evaluation_point))[j] ** deriv_orders[i][j] for j in range(n_var)])
        # e.g. (x-x0)*(y-y0)**2

        polynomial += partial_derivatives_at_point / denominator * distances_powered
    return polynomial

def compare_degrees(degrees0, degrees1):
    def find_first_hit_pos(degrees):
        for i,degree in enumerate(degrees):
            if degree != 0:
                return i
        return -1

    def compare(a, b):
        if a == b:
            return 0
        if a > b:
            return 1
        else:
            return -1

    var_size = len(degrees0) // 2

    xs0 = degrees0[:var_size]
    xs1 = degrees1[:var_size]
    xdots0 = degrees0[var_size:]
    xdots1 = degrees1[var_size:]

    x_max_degree0 = max(xs0)
    x_max_degree1 = max(xs1)
    xdot_max_degree0 = max(xdots0)
    xdot_max_degree1 = max(xdots1)

    x_sum_degree0 = sum(xs0)
    x_sum_degree1 = sum(xs1)
    xdot_sum_degree0 = sum(xdots0)
    xdot_sum_degree1 = sum(xdots1)

    x_first_hit0 = find_first_hit_pos(xs0)
    x_first_hit1 = find_first_hit_pos(xs1)
    xdot_first_hit0 = find_first_hit_pos(xdots0)
    xdot_first_hit1 = find_first_hit_pos(xdots1)

    ret = compare(xdot_sum_degree0, xdot_sum_degree1)
    if ret != 0:
        return ret

    ret = compare(x_sum_degree0, x_sum_degree1)
    if ret != 0:
        return ret

    ret = compare(x_first_hit0, x_first_hit1)
    if ret != 0:
        return ret

    ret = compare(x_max_degree0, x_max_degree1)
    if ret != 0:
        return -ret

    ret = compare(xdot_sum_degree0, xdot_sum_degree1)
    if ret != 0:
        return ret

    ret = compare(xdot_first_hit0, xdot_first_hit1)
    if ret != 0:
        return ret

    ret = compare(xdot_max_degree0, xdot_max_degree1)
    if ret != 0:
        return -ret

    return 0

def get_term_name(degrees):
    if sum(degrees) == 0:
        return '1'

    ret = ''
    for i,degree in enumerate(degrees):
        if degree > 0:
            if i < len(degrees) // 2:
                ret += f'x{i}'
            else:
                ret += f'xd{i-len(degrees)//2}'
            if degree >= 2:
                ret += f'^{degree}'
    return ret

def get_poly_dict(p, var_list):
    """
    When making poly symengine expression, error occurs with var list arguemnt poly(expr, var_list),
    so we made workaround to reorder coeffs.
    """
    def reoder_key(key, indices):
        ret = [0] * len(var_list)
        for degree,index in zip(key, indices):
            ret[index] = degree
        return tuple(ret)

    poly_vars = p.args[1:]
    indices = []
    for poly_var in poly_vars:
        index = var_list.index(poly_var)
        indices.append(index)
    d = p.as_dict()
    new_d = {}
    for key,value in d.items():
        new_key = reoder_key(key, indices)
        new_d[new_key] = value
    return new_d

import numpy as np

def get_target_coeffs(target_coeff_dicts, degrees_list):
    target_coeffs = []

    for coeff_dict in target_coeff_dicts:
        coeffs = []
        for degrees in degrees_list:
            if degrees in coeff_dict:
                coeffs.append(coeff_dict[degrees])
            else:
                coeffs.append(0)
        target_coeffs.append(coeffs)

    target_coeffs = np.array(target_coeffs, dtype=np.float64)
    return target_coeffs

import joblib
import sympy as smp
from functools import cmp_to_key

def process_poly(expr,
                 x_xdot_list,
                 x_xdot_zero,
                 series_degree):
    r_ex = mv_series(expr,
                     x_xdot_list,
                     x_xdot_zero,
                     series_degree)
    # Using sympy instead of symengine for poly().
    p_c = smp.poly(r_ex)
    return get_poly_dict(p_c, x_xdot_list)

def calc_coeffs(expr,
                x_xdot_list,
                x_xdot_zero_list,
                series_degree,
                verbose=0):
    r_dim = len(expr)

    coeff_dicts = joblib.Parallel(
        n_jobs=-1,
        verbose=verbose)( [joblib.delayed(process_poly)(
            expr[r_index, 0],
            x_xdot_list,
            x_xdot_zero_list,
            series_degree) for r_index in range(r_dim)])

    degrees_sets = set()

    for coeff_dict in coeff_dicts:
        for degrees in coeff_dict.keys():
            degrees_sets.add(degrees)

    degrees_list = sorted(list(degrees_sets),
                          key=cmp_to_key(compare_degrees))

    all_coeffs = []
    for coeff_dict in coeff_dicts:
        coeffs = []
        for degrees in degrees_list:
            if degrees in coeff_dict:
                coeffs.append(coeff_dict[degrees])
            else:
                coeffs.append(0)
        all_coeffs.append(coeffs)

    all_coeffs = np.array(all_coeffs, dtype=np.float64)
    return all_coeffs, degrees_list


import numpy as np

from happyml import datasets
from happyml.graphs.core import Input, Parameter, Add, Prod, Square, \
                                Dot, Max, ReduceSum, backward_all, \
                                forward_all, check_gradients
from happyml.graphs.viz import graph2dot
from happyml.graphs.optimize import SGD
from happyml import plot

def train_and_plot(x_input, y_input, h, loss, dataset):
    # Visualize graph using graphviz.
    g = graph2dot(h, filename="graph", format="png")
    g.render()
    plot.figure(figsize=(15, 3))
    plot.subplot(131)
    plot.imshow("graph.png",
                title="Computational Graph: %s" % str(h))

    # Visualize dataset and initial predictions.
    plot.subplot(132)
    dataset.plot()
    X, Y, Z = plot.predict_area(h.to_model())
    plot.binary_margins(X, Y, Z, title="Before training",
                        limits=[-1, 1, -1, 1])

    # Checking gradients before training.
    check_gradients(loss)

    # Using a SGD optimizer for minimizing loss.
    optimizer = SGD(learning_rate=0.1)
    optimizer.minimize(loss, dataset,
                       feed={"x": x_input, "y": y_input},
                       epochs=50,
                       batch_size=1)

    # Visualize dataset and final predictions.
    plot.subplot(133)
    dataset.plot()
    X, Y, Z = plot.predict_area(h.to_model())
    plot.binary_margins(X, Y, Z, title="After training",
                        limits=[-1, 1, -1, 1])

    plot.show()

############
# Dataset. #
############
dataset = datasets.load("linearly_separable.csv")

#############################
# Linear computation graph. #
#############################
x = Input(shape=(2,), name="x")
y = Input(name="y")
w = Parameter(shape=(2,), name="w")
b = Parameter(name="b")
h = Dot(w, x) + b
# Hinge loss + l2 loss
lambda_ = 0.001
l2_loss = lambda_ * ReduceSum(w ** 2)
loss = Max(0, 1 - y * h) + l2_loss

# Plot, fit and plot.
train_and_plot(x, y, h, loss, dataset)

##############################
# Squared computation graph. #
##############################
x = Input(shape=(2,), name="x")
y = Input(name="y")
w1 = Parameter(shape=(2,), name="w1")
b1 = Parameter(name="b1")
a1 = w1.dot(x) + b1
w2 = Parameter(shape=(2,), name="w2")
b2 = Parameter(name="b2")
a2 = w2.dot(x ** 2) + b2
h = a1 + a2
# Hinge loss + l2 loss
lambda_ = 0.001
l2_loss = ReduceSum(w1 ** 2) * lambda_ + ReduceSum(w2 ** 2) * lambda_
loss = Max(0, 1 - y * h) + l2_loss

# Plot, fit and plot.
train_and_plot(x, y, h, loss, dataset)


import numpy as np
import matplotlib.pyplot as plt

x_data = [1.0, 2.0, 3.0]
y_data = [2.0, 4.0, 6.0]

# our model for the forward pass
def forward(x):
    return x * w

# The zip fucntion allows you to iterate in parallel. It The takes iterables (can be zero or more), aggregates them in a tuple, and returns it.
print(list(zip(x_data, y_data)))

# Loss function
def loss(x, y):
    y_pred = forward(x)
    return (y_pred - y) * (y_pred - y)

# List of weights/Mean square Error (Mse) for each input
w_list = []
mse_list = []

for w in np.arange(0.0, 4.1, 0.1):
    # Print the weights and initialize the lost
    print("w=", w)
    l_sum = 0

    for x_val, y_val in zip(x_data, y_data):
        # For each input and output, calculate y_hat
        # Compute the total loss and add to the total error
        y_pred_val = forward(x_val)
        l = loss(x_val, y_val)
        l_sum += l
        print("\t", x_val, y_val, y_pred_val, l)
    # Now compute the Mean squared error (mse) of each
    # Aggregate the weight/mse from this run
    print("MSE=", l_sum / len(x_data))
    w_list.append(w)
    mse_list.append(l_sum / len(x_data))

# Plot it all
plt.plot(w_list, mse_list)
plt.ylabel('Loss')
plt.xlabel('w')
# plt.show()
plt.savefig("1_Basics.png")


# Take 100 samples
# chose a k = 10 for now
# divide sample
import numpy as np
import random
import matplotlib.pyplot as plt

def linearregression(A,b):
    ATA = A.T.dot(A)
    ATb = A.T.dot(b)
    parameters = np.linalg.lstsq(ATA, ATb)[0]
    # print(parameters)
    # print(  np.linalg.solve(ATA, ATb))
    return parameters

def kfold(k,sample_size,x,y):
    ksets = []
    i = 0
    while i < sample_size:
        ksets.append([i,i+k-1])
        i = i+k
    # print(ksets)
    ones = np.ones(sample_size-k)
    errors = np.array([])
    for i in range(sample_size//k):
        todeleteindex = ksets[i]
        index = range(todeleteindex[0],todeleteindex[1]+1)
        a = np.delete(x, index)
        A = np.column_stack((a,ones))
        b = np.delete(y,index)
        parameters = linearregression(A,b)
        error = (np.abs(b - (parameters[0] * (a) + parameters[1])))
        errors = np.append(errors,error)
    # errors = np.array(errors)
    return errors

mu = 0
sigma = 1
sample_size = 100
noise = np.random.normal(mu, sigma, sample_size)

x = np.linspace(-10, 10, num=100)
y = [  (np.sin(x[i]) + noise[i] ) for i in range(len(x))]
y = np.array(y)

k = range(2,100)
meansCorrespondingToK = np.array([])
varianceCorrespondingToK = np.array([])

for i in k:
    print('i in k',i)
    errors = kfold(i,sample_size,x,y)
    mean = np.mean(errors)
    var = np.var(errors)
    meansCorrespondingToK = np.append(meansCorrespondingToK,mean)
    varianceCorrespondingToK = np.append(varianceCorrespondingToK,var)

plt.subplot(2,1,1)
plt.plot(k,meansCorrespondingToK, label= "Mean of Error",linestyle='solid')
plt.plot(k,varianceCorrespondingToK, label= "Variance of Error",linestyle='solid')
plt.legend()
plt.title("Plot for 100 points")
plt.legend()

#### 100000

sample_size1 = 10000
noise1 = np.random.normal(mu, sigma, sample_size1)

x1 = np.linspace(-1000, 1000, num=10000)
print(len(x1))
y1 = [  (np.sin(x1[i]) + noise1[i] ) for i in range(len(x1))]
y1 = np.array(y1)

k1 = range(2,1000)
meansCorrespondingToK1 = np.array([])
varianceCorrespondingToK1 = np.array([])

for i in k1:
    print('k',i)
    errors1 = kfold(i,sample_size1,x1,y1)
    mean1 = np.mean(errors1)
    var1 = np.var(errors1)
    meansCorrespondingToK1 = np.append(meansCorrespondingToK1,mean1)
    varianceCorrespondingToK1 = np.append(varianceCorrespondingToK1,var1)

plt.subplot(2,1,2)
plt.plot(k1,meansCorrespondingToK1, label= "Mean of Error",linestyle='solid')
plt.plot(k1,varianceCorrespondingToK1, label= "Variance of Error",linestyle='solid')
plt.legend()
plt.title("Plot for 100,000 points")
plt.legend()
plt.show()