import numpy as np
from problem_3a import inverse_iteration
from problem_3b import rayleigh_iter

def main():
	print("Inverse Iteration")
	print("-------------------\n\n")

	A = np.array([[6, 2, 1], [2, 3, 1], [1, 1, 1]])
	x0 = np.array([1, 4, 2])
	print("Initial Array:\n\t", x0, "'\n")
	l, x, iters = inverse_iteration(A, x0)

	print("Computed Eigenvalue:", l)
	print("Computed Eigenvector:\n\t", x, "'\n")
	print("Number of iterations:", iters)

	print("\n\n")

	print("Rayleigh Quotient Iteration")
	print("------------------------------\n\n")

	A = np.array([[6, 2, 1], [2, 3, 1], [1, 1, 1]])
	x0 = np.array([1, 4, 2])
	print("Initial Array:\n\t", x0, "'\n")
	l_r, x_r, iters_r = rayleigh_iter(A, x0)

	print("Computed Eigenvalue:", l_r)
	print("Computed Eigenvector:\n\t", x_r, "'\n")
	print("Number of iterations:", iters_r)
	print("\n\n")

	print("Picking Rayleigh Quotient Iteration result as true value...\n\n")

	rel_err_vec = np.linalg.norm(x - x_r, ord=2) / np.linalg.norm(x_r, ord=2)
	rel_err_val = np.abs(l - l_r) / np.abs(l_r)

	print("Relative Error for Eigenvector:", rel_err_vec)
	print("Relative Error for Eigenvalue:", rel_err_val)

if __name__ == '__main__':
	main()


import numpy as np

def dct2(x):
    """
    :param x: 1D input signal in the time domain.
    :return: DCT(x) - 1D output signal in the frequency DCT domain.
    """
    P = x.shape[-1]
    X = np.zeros(P, dtype=float)
    for k in range(P):
        out = 0
        if k == 0:
            kk = 1. / np.sqrt(2.)
        else:
            kk = 1.
        for n in range(P):
            out += kk * x[n] * np.cos(np.pi * (n + .5) * k / P)
        out *= np.sqrt(2. / P)
        X[k] = out
    return X

def dct1(Y):
    N = Y.shape[-1]
    y = np.zeros(N, dtype=float)
    for n in range(N):
        out = 0
        for k in range(N):
            if k == 0:
                kk = 1. / 2.
            else:
                kk = 1.
            out += kk * Y[k] * np.cos(np.pi * n * k / N)
        y[n] = out
    return y

def correlate(x, h):
    """
    Cross-correlation of x and y via the DCT transform.

    :param x: input signal x in the time domain
    :param h: input filter y in the time domain
    :return: the output of the correlation
    """
    N = len(x)
    L = len(h)
    M = N + L - 1
    P1 = max((L - 3), 0) // 2 + 1
    P2 = max(P1 + 1, (N - 3) // 2 + 1)
    # P2 = (N - 3) // 2
    P = max(P2 + 1, 3 * M // 2 + 1)

    x = np.pad(array=x, pad_width=(P1, P - P1 - N), mode='constant')
    h = np.flip(h, axis=0)
    h = np.pad(array=h, pad_width=(P2, P - P2 - L), mode='constant')
    x = dct2(x)
    h = dct2(h)
    y = x * h
    y = dct1(y)[P1 + P2:P1 + P2 + M]
    return y

if __name__ == "__main__":
    x = np.array([1.0, -2.0, 3.0, -4.0, 2.0, -8.0, -1.0, 5.0], dtype=float)
    # x = np.random.randn(21)
    print("x: ", x)
    h = np.array([1.0, -4.0, 2.0], dtype=float)
    h_len = h.shape[-1]
    pad = (h_len - 1) // 2
    x_pad = np.pad(x, [pad, pad], mode="constant")
    expect = np.correlate(x_pad, h, mode='valid')
    print("expect: ", expect)
    result = correlate(x, h)
    print("result: ", result)
    assert np.testing.assert_allclose(actual=result, desired=expect)


'''
Implementing the technique in "A Smarter Way To Find Pitch" by Philip McLeod
and Geoff Wyvill, which is simple, elegant, and effective
'''
import numpy as np
import matplotlib.pyplot as plt

def estimateFundamentalFreq(x, doPlot = False):
    #Step 1: Compute normalized squared difference function
    #Using variable names in the paper
    N = x.size
    W = np.int(N/2)
    t = W
    corr = np.zeros(W)
    #Do brute force f FFT because I'm lazy
    #(fine because signals are small)
    for Tau in np.arange(W):
        xdelay = x[Tau::]
        L = (W - Tau)/2
        m = np.sum(x[int(t-L):int(t+L+1)]**2) + np.sum(xdelay[int(t-L):int(t+L+1)]**2)
        r = np.sum(x[int(t-L):int(t+L+1)]*xdelay[int(t-L):int(t+L+1)])
        corr[Tau] = 2*r/m

    #Step 2: Find the ''key max''
    #Compute zero crossings
    zc = np.zeros(corr.size-1)
    zc[(corr[0:-1] < 0)*(corr[1::] > 0)] = 1
    zc[(corr[0:-1] > 0)*(corr[1::] < 0)] = -1

    #Mark regions which are admissible for key maxes
    #(regions with positive zero crossing to left and negative to right)
    admiss = np.zeros(corr.size)
    admiss[0:-1] = zc
    for i in range(1, corr.size):
        if admiss[i] == 0:
            admiss[i] = admiss[i-1]

    #Find all local maxes
    maxes = np.zeros(corr.size)
    maxes[1:-1] = (np.sign(corr[1:-1] - corr[0:-2])==1)*(np.sign(corr[1:-1] - corr[2::])==1)
    maxidx = np.arange(corr.size)
    maxidx = maxidx[maxes == 1]
    maxTau = 0
    if len(corr[maxidx]) > 0:
        maxTau = maxidx[np.argmax(corr[maxidx])]

    if doPlot:
        plt.subplot(211)
        plt.plot(x)
        plt.title("Original Signal")
        plt.subplot(212)
        plt.plot(corr)
        plt.hold(True)
        plt.plot(admiss*1.05, 'r')
        plt.ylim([-1.1, 1.1])
        plt.scatter(maxidx, corr[maxidx])
        plt.scatter([maxTau], [corr[maxTau]], 100, 'r')
        plt.title("Max Tau = %i, Clarity = %g"%(maxTau, corr[maxTau]))
    return (maxTau, corr)

if __name__ == '__main__':
    T = 60
    NPeriods = 5
    np.random.seed(50)
    t = np.linspace(0, 2*np.pi*NPeriods, T*NPeriods)
    slope = t[1] - t[0]
    t = np.cumsum(np.random.rand(t.size)*2*slope)
    x = np.cos(t) + np.cos(2*t)
    f = estimateFundamentalFreq(x, True)


# encoding=utf8

"""
Module containing functions for calculating or approximating factorials.

"""

import numpy as np

from decimal import Decimal, localcontext

def factorial(n, prec=100):
    r"""
    Function for calculating factorials using the standard approach as explained in the
    Notes section. For factorials over 100, the decimal package is used to support the
    resulting large integers.

    Parameters
    ----------
    n : int
        The desired integer to calculate the factorial.
    prec : int default 100, optional
        Defines level of precision for factorials over 100
        for use by the decimal package

    Returns
    -------
    int or Decimal
        The computed factorial of the given integer.

    Notes
    -----
    Factorials are denoted for a positive integer :math:`x` as :math:`x!` and are
    defined as:

    .. math::

        x! = (x)(x - 1)(x - 2) \cdots (2)(1)

    For example, the factorial of 5 is written as:

    .. math::

        5! = (5)(4)(3)(2)(1) = 120

    Examples
    --------
    >>> factorial(10)
    3628800.0
    >>> factorial(50)
    3.0414093201713376e+64
    # Factorials above 100 use the decimal package to handle the resulting large integers
    >>> factorial(200)
    Decimal('7.886578673647905035523632139321850622951359776871732632947425332443594499634033429203042840119846238E+374')

    References
    ----------
    Press, W., Teukolsky, S., Vetterling, W., & Flannery, B. (2007). Numerical recipes (3rd ed.).
        Cambridge: Cambridge University Press.

    Weisstein, Eric W. "Factorial." From MathWorld--A Wolfram Web Resource.
        http://mathworld.wolfram.com/Factorial.html

    """
    if n != np.floor(n):
        n = np.floor(n)

    factor = np.arange(1, n + 1)

    if n > 100:
        with localcontext() as ctx:
            ctx.prec = prec
            f = Decimal(1)
            for i in reversed(factor):
                f = Decimal(f) * i
    else:
        f = float(1)
        for i in reversed(factor):
            f = f * i

    return int(f)

def stirlingln(n, keep_log=False, prec=100):
    r"""
    Approximates the factorial of n using the approximation
    given by Ramanujan in his lost notebook (Ramanujan 1988,
    as cited in Wikipedia). Computing the factorial in logarithmic
    form is useful as it helps avoid overflow when n is large. As
    values of n increase, the approximation given becomes more
    exact.

    Parameters
    ----------
    n : int
        The desired integer to calculate the factorial.
    keep_log : bool default False
        If True, the approximation remains in logarithmic
        form. If False, converts to exponent form before
        returning the factorial approximation.
    prec : int default 100, optional
        Defines level of precision for factorials over 100.

    Returns
    -------
    int or Decimal
        The computed log factorial of the given integer.

    Notes
    -----
    It is often useful to compute the logarithmic form of the
    factorial and convert it to exponent form to avoid overflow.
    The approximation is an alternative approach given by
    Srinivasa Ramanujan (Ramanujan 1988).

    .. math::

        ln n! \approx n ln n - n + \frac{1}{6} ln(n(1 + 4n(1 + 2n))) + \frac{1}{2} ln \pi

    Examples
    --------
    # Difference between actual factorial calculation and Stirling's Approximation
    # for low values of n is practically zero
    >>> (factorial(5) - stirlingln(5)) / stirlingln(5)
    3.8020354295010749e-06
    >>> stirlingln(50)
    3.041409303897981e+64
    >>> stirlingln(100)
    9.3326215380340829e+157
    # If the keep_log flag is set to True, the output remains in logarithmic form.
    >>> stirlingln(100, True)
    363.73937555488197

    References
    ----------
    Stirling's approximation. (2017, March 8). In Wikipedia, The Free Encyclopedia.
        From https://en.wikipedia.org/w/index.php?title=Stirling%27s_approximation&oldid=769328178

    """
    if n != np.floor(n):
        n = np.floor(n)

    n = float(n)
    if n > 100:
        with localcontext() as ctx:
            ctx.prec = prec
            f = Decimal(n * np.log(n) - n + (1. / 6.) * np.log(n * (1. + 4. * n * (1. + 2. * n))) + .5 * np.log(np.pi))
    else:
        f = n * np.log(n) - n + (1. / 6.) * np.log(n * (1. + 4. * n * (1. + 2. * n))) + .5 * np.log(np.pi)

    if keep_log is False:
        return np.exp(f)

    return f

def stirling(n, prec=100):
    r"""
    Approximates a factorial of an integer :math:`n` using Stirling's Approximation.
    Specifically, the approximation is done using a method developed by Gosper.

    Parameters
    ----------
    n : int
        The desired integer to calculate the factorial.
    prec
        Defines level of precision for factorials over 100. Default 100. Optional

    Returns
    -------
    int or Decimal
        The computed factorial of the given integer.

    Notes
    -----
    Stirling's approximation is a method of approximating a factorial :math:`n!`.
    As the value of :math:`n` increases, the more exact the approximation becomes;
    however, it still yields almost exact results for small values of :math:`n`.

    The approximation used is given by Gosper, which is noted to be a better
    approximation to :math:`n!` and also results in a very close approximation to
    :math:`0! = 1`.

    .. math::

        n! \approx \sqrt{(2n + \frac{1}{3})\pi} n^n e^{-n}

    Examples
    --------
    >>> stirling(0)
    1.0233267079464885
    >>> (factorial(5) - stirling(5)) / stirling(5)
    0.00024981097589214563
    >>> stirling(5)
    119.9700301696855
    >>> stirling(50)
    3.0414009581300833e+64

    References
    ----------
    Stirling's approximation. (2017, March 8). In Wikipedia, The Free Encyclopedia.
        From https://en.wikipedia.org/w/index.php?title=Stirling%27s_approximation&oldid=769328178

    Weisstein, Eric W. "Stirling's Approximation." From MathWorld--A Wolfram Web Resource.
        http://mathworld.wolfram.com/StirlingsApproximation.html

    """
    if n != np.floor(n):
        n = np.floor(n)

    if n >= 100:
        with localcontext() as ctx:
            ctx.prec = prec
            f = Decimal(np.sqrt((2. * n + 1. / 3.) * np.pi) * n ** n * np.exp(-n))
    else:
        f = np.sqrt((2. * n + 1. / 3.) * np.pi) * n ** n * np.exp(-n)

    return f

def ramanujan(n, prec=100):
    r"""
    Approximates the factorial :math:`n!` given an integer :math:`n` using Ramanujan's formula.
    Ramanujan's formula is just as or more accurate than several other factorial approximation
    formulas.

    Parameters
    ----------
    n
        Integer to approximate factorial
    prec
        Defines level of precision for factorials over 100. Default 100. Optional

    Returns
    -------
    int or Decimal
        Factorial of :math:`n` as approximated by Ramanujan's formula.

    Notes
    -----
    Ramanujan's formula is another factorial approximation method known for its accuracy
    in comparison to other factorial approximation approaches including Stirling's and
    Gosper's approximations. Ramanujan's formula is defined as:

    .. math::

        n! \approx \sqrt{\pi} \left(\frac{n}{e}\right)^n \sqrt[6]{8n^3 + 4n^2 + n + \frac{1}{30}}

    Examples
    --------
    >>> ramanujan(10)
    3628800.3116126074
    >>> ramanujan(5)
    120.00014706585664

    References
    ----------
    Mortici, Cristinel. On Gosper's Formula for the Gamma Function. Valahia University of Targoviste,
        Department of Mathematics. Retrieved from http://files.ele-math.com/articles/jmi-05-53.pdf

    """
    if n != np.floor(n):
        n = np.floor(n)

    if n >= 100:
        with localcontext() as ctx:
            ctx.prec = prec
            f = Decimal(
                np.sqrt(np.pi) * n ** n * np.exp(-n) * (8. * n ** 3. + 4. * n ** 2. + n + 1. / 30.) ** (1. / 6.))
    else:
        f = np.sqrt(np.pi) * n ** n * np.exp(-n) * (8. * n ** 3. + 4. * n ** 2. + n + (1. / 30.)) ** (1. / 6.)

    return f

def fallingfactorial(x, n):
    r"""
    Computes the falling factorial.

    Parameters
    ----------
    x
        Integer. The value will be rounded down if the value is not an integer.
    n
        Integer. The value will be rounded down if the value is not an integer.

    Returns
    -------
    int or str
        The falling factorial for an integer :math:`n`, :math:`(x)_{n}`. If x is a
        str, the output is the symbolic representation of the falling factorial.

    Notes
    -----
    The falling factorial, denoted as :math:`(x)_{n}` (or :math:`x^{\underline{n}}`) is
    defined as the following:

    .. math::

        (x)_n = x(x - 1) \cdots (x - (n - 1))

    The first few falling factorials are then:

    ..math::

        (x)_0 = 1
        (x)_1 = x
        (x)_2 = x(x - 1)
        (x)_3 = x(x - 1)(x - 2)
        (x)_4 = x(x - 1)(x - 2)(x - 3)

    Examples
    --------
    >>> fallingfactorial(10, 5)
    30240
    >>> fallingfactorial(10, 2)
    90
    >>> fallingfactorial('x', 2)
    'x*(x - 1)'
    >>> fallingfactorial('a', 4)
    'a*(a - 1)*(a - 2)*(a - 3)'

    References
    ----------
    Falling and rising factorials. (2017, June 8). In Wikipedia, The Free Encyclopedia.
        From https://en.wikipedia.org/w/index.php?title=Falling_and_rising_factorials&oldid=784512036

    Weisstein, Eric W. "Falling Factorial." From MathWorld--A Wolfram Web Resource.
        http://mathworld.wolfram.com/FallingFactorial.html

    """
    if n != np.floor(n):
        n = np.floor(n)

    if isinstance(x, str):
        f = x

        for i in np.arange(1, np.absolute(n)):
            f = f + '*(' + str(x) + ' - ' + str(i) + ')'

        if n < 0:
            f = '1 /' + f

    else:
        if x != np.floor(x):
            x = np.floor(x)

        f = np.uint64(1.0)
        for i in np.arange(np.absolute(n)):
            f *= (x - i)

        if n < 0:
            f = 1 / f

    return f

def risingfactorial(x, n):
    r"""
    Computes the rising factorial. Also known as the Pochhammer symbol.

    Parameters
    ----------
    x
        Integer. The value will be rounded down if the value is not an integer.
    n
        Integer. The value will be rounded down if the value is not an integer.

    Returns
    -------
    int or str
        The rising factorial for an integer :math:`n`, :math:`(x)_{n}`. If x is
        a str, the output is the symbolic representation of the rising factorial.

    Notes
    -----
    The rising factorial, :math:`x^{(n)}` (sometimes denoted \langle x \rangle_n ) is
    also known as the Pochhammer symbol in other areas of mathematics.

    The rising factorial is related to the gamma function :math:`\Gamma (z)`.

    .. math::

        x^{(n)} \equiv \frac{\Gamma (x + n)}{\Gamma (n)}

    where :math:`x^(0) = 1`.

    The rising factorial is related to the falling factorial by:

    .. math::

        x^{(n)} = (-x)_n (-1)^n

    Examples
    --------
    >>> risingfactorial(10, 6)
    3603600
    >>> risingfactorial('x', 4)
    'x*(x + 1)*(x + 2)*(x + 3)'

    References
    ----------
    Falling and rising factorials. (2017, June 8). In Wikipedia, The Free Encyclopedia.
        From https://en.wikipedia.org/w/index.php?title=Falling_and_rising_factorials&oldid=784512036

    Weisstein, Eric W. "Rising Factorial." From MathWorld--A Wolfram Web Resource.
        http://mathworld.wolfram.com/RisingFactorial.html

    """
    if n != np.floor(n):
        n = np.floor(n)

    if isinstance(x, str):
        f = x

        for i in np.arange(1, np.absolute(n)):
            f = f + '*(' + str(x) + ' + ' + str(i) + ')'

        if n < 0:
            f = '1 /' + f

    else:
        if x != np.floor(x):
            x = np.floor(x)

        f = np.uint64(1.0)

        for i in np.arange(np.absolute(n)):
            f *= (x + i)

        if n < 0:
            f = 1 / f

    return f


import numpy as np
from sklearn.datasets import make_classification
from matplotlib import pyplot as plt

class Logistic_Regression:
    """
    the class to implement the Logistic Regreesion
    """

    def __init__(self, features, nums, classes):
        self.features = features
        self.nums = nums
        self.classes = classes
        self.alpha = 0.3
        self.max_epoch = 50
        self.data = self.make_data()
        self.theta = self.train()

    def make_data(self):
        """
        this is the function to make the training data
        :param features: the dimension of data features
        :param nums: the number of data
        :return:data
        """
        _dataset = make_classification(n_samples=self.nums, n_features=self.features, n_classes=self.classes)
        return _dataset

    def sigmoid(self, x):
        """
        this is the function to implement the sigmoid function
        :param x:this is (theta * x)
        :return:result: probility
        """
        result = 1/(1+np.exp(-x))
        return result

    def train(self, newton_mothod=False):
        if newton_mothod:
            pass
        else:
            theta = np.ones((self.features, 1))
            for i in range(self.max_epoch):
                grident = (1/self.nums)*np.dot(self.data[0].T, (self.sigmoid(np.dot(self.data[0], theta)) - self.data[1].reshape((20, 1))))
                theta = theta + self.alpha * grident
            return theta

    def test(self, data):
        return np.round(self.sigmoid(np.dot(self.theta.T, data.T)))


import numpy as np
import matplotlib.pyplot as plt

def LR_1D_calculator(X,Y, xlabel=None, ylabel=None):
    # We now solve the equation y = aX + b
    X_mean = X.mean()
    X_sum = X.sum()
    Y_mean = Y.mean()
    Y_sum = Y.sum()

    denominator = X.dot(X) - X_mean * X_sum
    a = ( X.dot(Y) - Y_mean * X_sum) / denominator
    b = ( Y_mean * X.dot(X) - X_mean * X.dot(Y)) / denominator

    Y_hat = a * X + b

    plt.figure()
    plt.scatter(X,Y)
    plt.plot(X, Y_hat, 'r')
    if xlabel is not None:
        plt.xlabel(xlabel)
    if ylabel is not None:
        plt.ylabel(ylabel)
    plt.show()

    R2 = r2_calculator(Y, Y_hat)

    return Y_hat, a, b, R2

def r2_calculator(Y, Y_hat):

    # We also calculate the R^2 error:

    d1 = Y - Y_hat
    d2 = Y - Y.mean()
    R2 = 1 - d1.dot(d1) / d2.dot(d2)
    print("The R^2 is: ", R2)

    return R2

if __name__ == '__main__':

    # We first load and visualise the data:

    X = []
    Y = []
    for line in open('../large_files/data_1d.csv'):
        x, y = line.split(',')
        X.append(float(x))
        Y.append(float(y))

    X = np.array(X)
    Y = np.array(Y)

    plt.figure()
    plt.scatter(X,Y)
    plt.show()

    LR_1D_calculator(X,Y)


# add() is done between two elements : return an array
# sum([]) happens over n elements : return an element

import numpy as np

arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])

newarr = np.add(arr1, arr2)

print("add():", newarr)

newarr = np.sum([arr1, arr2])
# note that sum([])

print("sum(): ", newarr)

# Summation over an axis

newarr = np.sum([arr1, arr2], axis = 1)

print("sum() over an axis", newarr)

# cummulative sum : partially adding the elements in array

# The partial sum of [1, 2, 3, 4] would be [1, 1+2, 1+2+3, 1+2+3+4] = [1, 3, 6, 10]

# cumsum()

# import numpy as np

arr = np.array([1, 2, 3])

print("cummulative Sum: ", np.cumsum(arr))


import scipy.sparse.linalg
import scipy.sparse as sp
import matplotlib.pylab as plt
import numpy as np

N = 100
e = np.ones(N, dtype=float)
A = sp.spdiags([e, -2*e, e], [-1, 0, 1], N, N, format='csc')

plt.spy(A)
plt.show()

L = 2*np.pi
x = np.linspace(0, L, N+2)
h = x[1] - x[0]
fcos = 2 * np.cos(x) / np.exp(x)
analytical_cos = -np.sin(x) / np.exp(x)
fsin = 2 * np.sin(x) / np.exp(x)
analytical_sin = np.cos(x) / np.exp(x)

# Use sparse lu decomposition, matrix is tridiagonal so no fill-in
splu = sp.linalg.splu(A / h**2)
fig, axs = plt.subplots(1, 2)
axs[0].spy(splu.L)
axs[0].set_title('L')
axs[1].spy(splu.U)
axs[1].set_title('U')
plt.show()

# Use decomposition to solve both problems
for f, a in zip([fcos, fsin], [analytical_cos, analytical_sin]):
    b = f[1:-1]
    b[0] -= a[0] / h**2
    b[-1] -= a[-1] / h**2
    v = splu.solve(b)
    plt.plot(x[1:-1], v, label='solution')
    plt.plot(x, a, label='analytical')
    plt.legend()
    plt.show()

import time
num = 100
times = np.empty(num, dtype=float)
times_dense = np.empty(num, dtype=float)
times_dense[:] = np.nan
Ns = np.logspace(0.5, 6, num=num, dtype=int)
for i, N in enumerate(Ns):
    e = np.ones(N, dtype=float)
    A = sp.spdiags([e, -2*e, e], [-1, 0, 1], N, N, format='csc')

    t0 = time.perf_counter()
    AA = A @ A
    t1 = time.perf_counter()
    times[i] = t1 - t0

    if N < 2000:
        Adense = A.toarray()
        t0 = time.perf_counter()
        AA = Adense @ Adense
        t1 = time.perf_counter()
        times_dense[i] = t1 - t0

plt.clf()
plt.loglog(Ns, times, label='sparse @')
plt.loglog(Ns, times_dense, label='dense @')
plt.xlabel('N')
plt.ylabel('time taken')
plt.legend()
plt.show()

times = np.empty(num, dtype=float)
times_dense = np.empty(num, dtype=float)
times_dense[:] = np.nan

for i, N in enumerate(Ns):
    e = np.ones(N, dtype=float)
    A = sp.spdiags([e, -2*e, e], [-1, 0, 1], N, N, format='csc')

    x = np.linspace(0, L, N+2)
    h = x[1] - x[0]
    fcos = 2 * np.cos(x) / np.exp(x)
    analytical_cos = -np.sin(x) / np.exp(x)

    A /= h**2

    b = fcos[1:-1]
    b[0] -= analytical_cos[0] / h**2
    b[-1] -= analytical_cos[-1] / h**2

    t0 = time.perf_counter()
    splu = sp.linalg.splu(A)
    v = splu.solve(b)
    t1 = time.perf_counter()
    times[i] = t1 - t0
    if N < 2000:
        Adense = A.toarray()
        t0 = time.perf_counter()
        lu = scipy.linalg.lu_factor(Adense)
        v = scipy.linalg.lu_solve(lu, b)
        t1 = time.perf_counter()
        times_dense[i] = t1 - t0

plt.clf()
plt.loglog(Ns, times, label='sparse LU')
plt.loglog(Ns, times_dense, label='dense LU')
plt.xlabel('N')
plt.ylabel('time taken')
plt.legend()
plt.show()


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

###
# Name: Trevor Kling
# Student ID: 002270716
# Email: kling109@mail.chapman.edu
# Course: PHYS220/MATH220/CPSC220 Fall 2018
# Assignment: CW08
###

import numpy as np

def s(T, t, n):
    """
    Implements a summation of a sin-based function with defined constants T and t.  The sum is computed
    up to a value n, and should converge to 1, -1, or 0 depending on the range t falls into.
    0 < t < T/2: Converges to 1.
    t = 0: Converges to 0.
    -T/2 < t < 0: Converges to -1
    """
    summationArray = np.arange(1, n+1)
    def func(k):
        return (((1)/(2*k - 1))*np.sin((2*(2*k - 1)*np.pi*t)/(T)))
    summer = np.vectorize(func)
    result = (4/np.pi)*np.sum(summer(summationArray))
    return result

def f(T, t):
    """
    Checks if t is between particular values, and returns what the above function will converge to at
    infinity for the given t value.
    """
    if 0 < t < (T/2):
        return 1
    elif t == 0:
        return 0
    elif -(T/2) < t < 0:
        return -1
    else:
        print("That is not within the given range of [-T/2, T/2]")
        return None


import numpy as np

temp_na = np.array([1, 2, 3, 4, 5, 6])
temp_na

temp_na.shape

# 基本统计
temp_na.max()
temp_na.min()
temp_na.mean()
temp_na.sum()

# np reshape 改变矩阵形状
temp_na = temp_na.reshape(2, 3)
temp_na

# tensor 切片
# 所有行的前面两列
temp_na[:, :2]

# 矩阵计算
np.random([3, 5])
na1 = np.random.rand(2, 3)
na2 = np.random.rand(3, 5)
na1
na2
np.dot(na1, na2)

# 创建各种矩阵
zeroarray = np.zeros((2, 3))
print(zeroarray)

onearray = np.ones((3, 4), dtype='int64')
print(onearray)

emptyarray = np.empty((3, 4))
print(emptyarray)

array = np.arange(10, 31, 5)
print(array)

# 矩阵的na的属性
array = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]])
print(array)
# 数组维度
print(array.ndim)
# 数组形状
print(array.shape)
# 数组元素个数
print(array.size)
# 数组元素类型
print(array.dtype)

# 改变na的形状
array1 = np.arange(6).reshape([2, 3])
print(array1)

array2 = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int64).reshape([3, 2])
print(array2)

# 矩阵的计算
arr1 = np.array([[1, 2, 3], [4, 5, 6]])
arr2 = np.ones([2, 3], dtype=np.int64)
print(arr1)
print(arr2)

# 矩阵的基本运算
print(arr1 + arr2)
print(arr1 - arr2)
print(arr1 * arr2)
print(arr1 / arr2)
# 平方
print(arr1 ** 2)

# 矩阵的乘法
arr3 = np.array([[1, 2, 3], [4, 5, 6]])
arr4 = np.ones([3, 2], dtype=np.int64)
print(arr3)
print(arr4)
print(np.dot(arr3, arr4))

# np的其他统计分析函数
print(arr3)
print(np.sum(arr3, axis=1))  # axis=1,每一行求和 axie=0,每一列求和
print(np.max(arr3))
print(np.min(arr3))
print(np.mean(arr3))
print(np.argmax(arr3))
print(np.argmin(arr3))

# 矩阵的转置
arr3_tran = arr3.transpose()
print(arr3_tran)
print(arr3.flatten())

# 矩阵的索引和切片
arr5 = np.arange(0, 6).reshape([2, 3])
print(arr5)
print(arr5[1])
print(arr5[1][2])
print(arr5[1, 2])
print(arr5[1, :])
print(arr5[:, 1])
print(arr5[1, 0:2])


import copy
import numpy as np
import numpy.linalg as la
from scipy.optimize import linprog  # TODO: REMOVE

from _errors import ConvergenceError

# ======================================================================================================================
# Root-finding Methods
# ======================================================================================================================
def secant(fun, x0, x1, args=()):
    # options ----------------------------------------------------------------------------------------------------------
    max_it = 1000
    tol = 1e-3

    # initializing loop ------------------------------------------------------------------------------------------------
    it = 0
    root = x1

    # iterating --------------------------------------------------------------------------------------------------------
    while abs(x1 - x0) > tol and it < max_it:
        f0 = fun(x0, *args)
        f1 = fun(x1, *args)
        root -= f1 * (root - x0) / (f1 - f0)

        if root in (np.inf, np.nan):
            raise ConvergenceError('division by zero')

        x0 = x1
        x1 = root
        it += 1

    return root

# ======================================================================================================================
# Least Squares Methods
# ======================================================================================================================
def residual(f, x, y, p, args=()):
    return y - f(x, *p, *args)

def lsq_obj(r):
    return 0.5 * la.norm(r) ** 2.

def d_lsq_obj(r, j):
    return j.T @ r

def jacobian_fd(x, p, f, args=()):
    m = len(p)
    j = [None for _ in range(0, m)]

    eps = 1e-8
    fx = f(x, *p, *args)

    for i in range(0, m):
        p_ = copy.deepcopy(list(p))
        p_[i] += eps
        j[i] = (f(x, *p_, *args) - fx) / eps

    return np.asarray(j).T

def nl_lsq(fun, x, y, p0, jac=None, args=()):
    # options ----------------------------------------------------------------------------------------------------------
    max_it = 1000
    max_it_bt = 100
    tol = 1e-3
    rho = 0.5
    c = 1e-4

    if jac is None:
        jac = lambda xj, *pj: jacobian_fd(xj, pj, fun, args=args)

    # initializing loop ------------------------------------------------------------------------------------------------
    it = 0
    converged = False
    p = p0
    res = residual(fun, x, y, p, args=args)
    j = jac(x, *p, *args)
    f = lsq_obj(res)
    df = d_lsq_obj(res, j)

    # iterating --------------------------------------------------------------------------------------------------------
    while not converged and it < max_it:
        # calculate optimized step
        try:

            q, r = la.qr(j)
            dp = la.solve(r, q.T @ res)

        except np.linalg.LinAlgError:
            raise ConvergenceError('Unable to find a solution due to singular matrix issues')

        # invoke backtracking
        alpha = 1.
        it_bt = 0

        p_bt = p + dp
        f_bt = lsq_obj(residual(fun, x, y, p_bt, args=args))
        csdf = -c * np.dot(dp, df)

        while f_bt >= (f + alpha * csdf) and it_bt < max_it_bt:
            p_bt = p + alpha * dp
            f_bt = lsq_obj(residual(fun, x, y, p_bt, args=args))

            alpha *= rho
            it_bt += 1

        p = p_bt

        # update parameters and check convergence
        res = residual(fun, x, y, p, args=args)
        f = lsq_obj(res)
        j = jac(x, *p_bt, *args)
        df = d_lsq_obj(res, j)

        if la.norm(df, np.inf) < tol:
            converged = True

        it += 1

    if it == max_it:
        raise ConvergenceError('Solver failed to converge within maximum number of iterations')

    return p

# ======================================================================================================================
# Linear Programming Methods
# ======================================================================================================================
def lin_ip(A, g, b):
    """
    TODO: NOT WORKING
    # Algorithm 14.3, Page 411 Nocedal & Wright

    Parameters
    ----------
    A : array_like
        system matrix of the constraints
    g : array_like
        objective function multiplier
    b : array_like
        right-hand side of the constrains

    Returns
    -------
    array_like
        optimal solution vector
    """

    converged = False
    m, n = A.shape
    max_iter = 10
    iter_ = 0
    eta = 0.99

    # initial value correction heuristic -------------------------------------------------------------------------------
    AA = A @ A.T
    x_t = A.T @ la.solve(AA, b)
    l_t = la.solve(AA, A @ g)
    s_t = g - A.T @ l_t

    dx = max(-1.5 * x_t.min(), 0.)
    ds = max(-1.5 * s_t.min(), 0.)
    x_h = x_t + dx
    s_h = s_t + ds

    xhsh = x_h.T @ s_h
    dx_h = .5 * xhsh / (np.sum(s_h))
    ds_h = .5 * xhsh / (np.sum(x_h))

    x = x_h + dx_h
    l = l_t
    s = s_h + ds_h

    # main loop --------------------------------------------------------------------------------------------------------
    r_c = A.T @ l + s - g
    r_b = A @ x - b
    mu = (x.T @ s) / n

    while (not converged) and (iter_ < max_iter):
        iter_ = iter_ + 1

        # KKT system
        kkt = np.block([[np.zeros((n, n)), A.T, np.eye(n)],
                        [A, np.zeros((m, m)), np.zeros((m, n))],
                        [np.diag(s.flatten()), np.zeros((n, m)), np.diag(x.flatten())]])

        rhs = np.vstack((-r_c, -r_b, -x * s))

        # Solving for and extracting affine variables
        # QR decompose KKT matrix, TODO: LDL decomposition instead
        q, r = la.qr(kkt)
        dv_aff = q @ la.solve(r.T, rhs)

        dx_aff = dv_aff[:n]
        ds_aff = dv_aff[(n + m):]

        # Determining indices and corresponding alpha for affine variables
        alpha_prim_aff = np.where(dx_aff < 0., -x / dx_aff, 1.).min()
        alpha_dual_aff = np.where(ds_aff < 0., -s / ds_aff, 1.).min()

        # Calculating affine mu, mu and sigma
        mu_aff = ((x + alpha_prim_aff * dx_aff).T @ (s + alpha_dual_aff * ds_aff)) / n
        sigma = (mu_aff / mu) ** 3. if mu > 1.e-10 else 0.

        rhs = np.vstack((-r_c, -r_b, -x * s - dx_aff * ds_aff + sigma * mu))

        # Solving for and extracting increments
        dv = q @ la.solve(r.T, rhs)
        dx = dv[:n]
        dl = dv[n:(n + m)]
        ds = dv[(n + m):]

        # Determining indices and corresponding alpha for x and s
        alpha_prim = np.where(dx < 0., eta * (-x / dx), 1.).min()
        alpha_dual = np.where(ds < 0., eta * (-s / ds), 1.).min()

        # updating x, l and s
        x += alpha_prim * dx
        l += alpha_dual * dl
        s += alpha_dual * ds

        print('X')
        print(x)

        # convergence check
        r_c = A.T @ l + s - g
        r_b = A @ x - b
        mu = (x.T @ s) / n

        converged = (la.norm(r_c, ord=np.inf) <= 1.e-9) and (la.norm(r_b, ord=np.inf) <= 1.e-9) and (abs(mu) <= 1.e-9)
        print('CONVERGENCE')
        print('rC', la.norm(r_c, ord=np.inf))
        print('rA', la.norm(r_b, ord=np.inf))
        print('mu', abs(mu))

    return x

# ======================================================================================================================
# Quadratic Programming Methods
# ======================================================================================================================
def nl_sqp(obj, con, x0, H0):
    """
    Non-linear SQP solver for inequality constrained problems
    TODO: Implement equality constraints
    :param obj:
    :param con:
    :param x0:
    :param H0:
    :return:
    """
    # Options ----------------------------------------------------------------------------------------------------------
    tol = 1.0e-3
    max_iter = 300
    n = x0.shape[0]

    # calculating objective function and constraint function using a numerical approximation for Jacobians
    xeval = x0
    f, df = obj(xeval)
    c, dc = con(xeval)

    m = c.size
    mu = 100.

    # assembling KKT system
    A = np.zeros((n + m, 0))  # incorrect, assemble for equality constraints
    b = np.zeros(0)  # incorrect, assemble for equality constraints
    H = np.block([[np.zeros(H0.shape), np.zeros((H0.shape[0], m))], [np.zeros((m, H0.shape[1])),  np.eye(m) * 1e-6]])
    g = np.block([np.zeros((df.shape[0], 1)), np.zeros((m, 1))])
    y = np.zeros(0)

    C = np.block([[np.zeros(dc.shape), np.zeros((m, m))], [np.zeros((m, n)), np.eye(m)]])
    d = np.zeros(2 * m)
    B = H0

    z = np.abs(la.solve(dc, df))
    s = np.ones(2 * m)
    dLold = df - dc @ z

    # Main loop iterations ---------------------------------------------------------------------------------------------
    converged = (la.norm(dLold, ord=np.inf) < tol) and (la.norm(z * c, ord=np.inf) < tol)  # z * c element wise

    rho = 0.5
    iter = 0

    while (not converged) and (iter < max_iter):
        # Updating initial guess input for the PDPCIP algorithm
        H[:n, :n] = B
        g = np.block([df, mu * np.ones(m)])
        # TODO: Missing the equality constrains here?
        C[:m, :m] = dc
        d[:m] = -c

        zpad = np.block([z, np.ones(m)])
        t = np.maximum(-(c + dc @ xeval), np.zeros(m))
        xt = np.block([xeval, t])

        # Sub problem: Solve constrained QP
        p, y, z, _ = quad_ip(H, g, A, b, C, d, xt, y, s, zpad)

        xeval = xt[:n]
        z = z[:n]
        p = p[:n]

        # Take step
        xeval += p

        # Function evaluation
        f, df = obj(xeval)
        c, dc = con(xeval)
        mu = (df.T @ p + 0.5 * p.T @ B @ p) / ((1. - rho) * la.norm(c, ord=1))

        # Lagrangian gradient, z used for inequality constraints
        dLnew = df - dc @ z

        # BFGS Hessian update
        q = dLnew - dLold
        Bp = B @ p

        if np.dot(p, q) >= 0.2 * np.dot(p, Bp):
            theta = 1.
        else:
            theta = (0.8 * np.dot(p, Bp)) / (np.dot(p, Bp) - np.dot(p, q))

        r = theta * q + (1. - theta) * Bp
        r = r.reshape((r.shape[0], 1))
        Bp = Bp.reshape((Bp.shape[0], 1))
        B += r @ r.T / np.dot(p, r) - Bp @ Bp.T / np.dot(p, Bp)

        dLold = dLnew
        iter += 1
        converged = (la.norm(dLold, np.inf) < tol) and (la.norm(z * c, np.inf) < tol)  # z * c piecewise

    info = converged
    zopt = z[:2]
    xopt = xeval
    return xopt, zopt, info

def quad_ip(H, g, A, b, C, d, x0, y0, s0, z0):
    """
    Primal Dual Predictor Corrector Interior Point Algorithm.
    :param H:
    :param g:
    :param A:
    :param b:
    :param C:
    :param d:
    :param x0:
    :param y0:
    :param s0:
    :param z0:
    :return:
    """
    # Options ----------------------------------------------------------------------------------------------------------
    epsilon = 1e-3
    max_iter = 100

    # Heuristic for initial point --------------------------------------------------------------------------------------
    mc = z0.size
    KKT0 = np.zeros((A.shape[1], A.shape[1]))

    rL = H @ x0 + g - A @ y0 - C @ z0
    rA = b - A.T @ x0
    rC = s0 + d - C.T @ x0
    rsz = s0 * z0  # element-wise

    # assemble KKT matrix
    H0 = H + (C @ np.diag((z0 / s0)) @ C.T)  # z0 / s0 element wise
    KKT = np.block([[H0, -A], [-A.T, KKT0]])

    # assemble RHS of KKT system
    rL0 = rL - C @ np.diag((z0 / s0)) @ (rC - rsz / z0)  # z0 / s0 and rsz. / z0 elementwise
    RHS = -np.block([rL0, rA])

    # QR decompose KKT matrix, TODO: LDL decomposition instead
    Q, R = la.qr(KKT)
    affvec = Q @ la.solve(R.T, RHS)

    xaff = affvec[:x0.shape[0]].T
    zaff = -np.diag((z0 / s0)) @ C.T @ xaff + np.diag((z0 / s0)) @ (rC - rsz / z0)  # z0 / s0 and rsz / z0 element wise
    saff = -rsz / z0 - np.diag((s0 / z0)) @ zaff  # rsz / z0 and s0 / z0 element wise element wise

    x = x0
    y = y0
    z = np.maximum(np.ones(z0.shape), abs(z0 + zaff))
    s = np.maximum(np.ones(s0.shape), abs(s0 + saff))
    mu0 = np.dot(z, s) / mc
    zdivs = z / s  # element wise

    # Iterations -------------------------------------------------------------------------------------------------------
    rL = H @ x + g - A @ y - C @ z
    rA = b - A.T @ x
    rC = s + d - C.T @ x
    rsz = s * z  # element wise
    mu = mu0

    iter = 0
    converged = convergence_check(rL, rA, rC, mu0, mu0, H, g, A, b, C, d, epsilon)

    while (not converged) and (iter < max_iter):
        # assemble KKT matrix
        Hbar = H + (C @ np.diag(zdivs) @ C.T)
        KKT = np.block([[Hbar, -A], [-A.T, KKT0]])

        # assemble RHS of KKT system
        rLbar = rL - C @ np.diag(zdivs) @ (rC - rsz / z)  # rsz / z element wise
        RHS = np.block([-rLbar, -rA])

        # QR decompose KKT matrix, TODO: LDL decomposition instead
        # [L, D, p] = ldl(KKT, 'lower', 'vector')
        # affvec = zeros(1, numel(RHS))
        # affvec(p) = L'\(D\(L\RHS(p)))
        Q, R = la.qr(KKT)  # may need method='complete
        affvec = Q @ la.solve(R.T, RHS)

        xaff = affvec[:x.shape[0]].T
        zaff = - np.diag(zdivs) @ C.T @ xaff + np.diag(zdivs) @ (rC - rsz / z)  # rsz / z element wise
        saff = -(rsz / z) - np.diag(s / z) @ zaff  # rsz / z and s / z element wise

        alphaaff1 = np.where(zaff < 0., -z / zaff, 1.).min()  # z / zaff element wise
        alphaaff2 = np.where(saff < 0., -s / saff, 1.).min()  # s / saff element wise
        alphaaff = np.minimum(alphaaff1, alphaaff2)

        muaff = np.dot((z + alphaaff * zaff), (s + alphaaff * saff)) / mc
        sigma = (muaff / mu) ** 3.

        # assembling updated RHS of KKT system
        rszbar = rsz + saff * zaff - sigma * mu  # saff * zaff element wise
        rLbar = rL - C @ np.diag(zdivs) @ (rC - rszbar / z)  # rszbar / z element wise
        RHS = np.block([-rLbar, -rA])

        # TODO: LDL decomposition
        # vec = np.zeros(numel(RHS), 1)
        # vec(p) = L'\(D\(L\RHS(p)))
        vec = Q @ la.solve(R.T, RHS)

        # calculate step size
        dx = vec[:x.shape[0]]
        dy = vec[x.shape[0] + 1:]
        dz = -np.diag(zdivs) @ C.T  @ dx + np.diag(zdivs) @ (rC-rszbar / z)  # rszbar / z element wise
        ds = -rszbar / z - np.diag(s / z) @ dz  # rszbar / z and s / z element wise

        alpha1 = np.where(dz < 0., -z / dz, 1.).min()  # z / dz element wise
        alpha2 = np.where(ds < 0., -s / ds, 1.).min()  # s / ds element wise
        alpha = np.minimum(alpha1, alpha2)

        # updating estimator with calculated step-size
        alphabar = 0.995 * alpha
        x = x + dx * alphabar
        y = y + dy * alphabar
        z = z + dz * alphabar
        s = s + ds * alphabar

        rL = H @ x + g - A @ y - C @ z
        rA = b - A.T @ x
        rC = s + d - C.T @ x
        rsz = s * z  # element wise
        mu = np.dot(z, s) / mc
        zdivs = z / s  # element wise
        converged = convergence_check(rL, rA, rC, mu, mu0, H, g, A, b, C, d, epsilon)
        iter += 1

    if converged:
        xopt = x
        yopt = y
        zopt = z
        sopt = s
    else:
        xopt = []
        yopt = []
        zopt = []
        sopt = []

    return xopt, yopt, zopt, sopt

def convergence_check(rL, rA, rC, mu, mu0, H, g, A, b, C, d, tol):
    conv = False
    rAcheck = True
    rCcheck = True

    # KKT system
    kkt = np.block([H, g.reshape((g.shape[0], 1)), A, C])
    rLcheck = la.norm(rL, ord=np.inf) <= tol * np.maximum(1., la.norm(kkt, ord=np.inf))

    # equality constraints
    eq = np.block([A.T, b.reshape((b.shape[0], 1))])
    if eq.size:
        rAcheck = la.norm(rA, ord=np.inf) <= tol * np.maximum(1., la.norm(eq, ord=np.inf))

    # inequality constraints
    ineq = np.block([np.eye(d.size), d.reshape((d.shape[0], 1)), C.T])
    if ineq.size:
        rCcheck = la.norm(rC, ord=np.inf) <= tol * np.maximum(1., la.norm(ineq, ord=np.inf))

    muCheck = np.abs(mu) <= tol * 1e-2 * mu0

    if rLcheck and rAcheck and rCcheck and muCheck:
        conv = True

    return conv

# QUADRATIC TEST SCRIPT ------------------------------------------------------------------------------------------------
#def func(x, a, b, c):
#    return a * np.exp(-b * x) + c

#def func_jac(x, a, b, c):
#    return np.array([np.exp(-b * x), -a * x * np.exp(-b * x), np.repeat(1., x.size)]).T

#xdata = np.linspace(0, 4, 50)
#y = func(xdata, 2.5, 1.3, 0.5)
#np.random.seed(1729)
#y_noise = 0.2 * np.random.normal(size=xdata.size)
#ydata = y + y_noise
#p = nl_lsq(func, xdata, ydata, p0=np.asarray([1., 1., 1.]), jac=func_jac)

# LINEAR TEST SCRIPT ---------------------------------------------------------------------------------------------------
# n = 2                     # Number of variables and constraints (2 for contour plot)
# A = np.random.rand(n, n)  # Generating A
# k = 2                     # Index to put in zeros
#
# xp = abs(np.random.rand(n, 1))
# xp[k:] = np.zeros((xp.size-k, 1))
# sp = abs(np.random.rand(n, 1))
# sp[:k-1] = np.zeros((sp.size-k + 1, 1))
#
# lp = np.random.rand(n, 1)   # Generating lambda
#
# g = A.T @ lp + sp      # Computing g
# b = A @ xp             # Computing b

# problem from: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.linprog.html
# A = np.zeros((2, 2))
# A[0, :] = np.array([-3., 1.])
# A[1, :] = np.array([1., 2.])
#
#
# b = np.zeros((2, 1))
# b[0, 0] = 6.
# b[1, 0] = 4.
#
# g = np.zeros((2, 1))
# g[0, 0] = -1.
# g[1, 0] = 4.
#
# # Solving problem using Linear Programming Predictor-Correction IP algorithm
# x_opt = lin_ip(A, g, b)
# #print(x_opt)
# obj = g.T @ x_opt
# #print(obj)
# print('OPTIMUM')
# print(x_opt, obj)
#
# c = [-1, 4]
# A = [[-3, 1], [1, 2]]
# b = [6, 4]
# #x0_bounds = (None, None)
# #x1_bounds = (-3, None)
# res = linprog(c, A_ub=A, b_ub=b)#, bounds=[x0_bounds, x1_bounds])
# print(res)


# --------------
#Importing header files
import pandas as pd
import scipy.stats as stats
import math
import numpy as np
import matplotlib.pyplot as plt
from statsmodels.stats.weightstats import ztest
from statsmodels.stats.weightstats import ztest
from scipy.stats import chi2_contingency

import warnings

warnings.filterwarnings('ignore')
#Sample_Size
sample_size=2000

#Z_Critical Score
z_critical = stats.norm.ppf(q = 0.95)
print('z_critical:', z_critical)
# Critical Value
critical_value = stats.chi2.ppf(q = 0.95, # Find the critical value for 95% confidence*
                      df = 6)   # Df = number of variable categories(in purpose) - 1
print('critical_value', critical_value)

#Reading file
data=pd.read_csv(path)
data_sample=data.sample(n=2000, random_state=0)
sample_mean=data_sample.installment.mean()
print('sample mean:', sample_mean)

sample_std=data_sample.installment.std()
print('sample_std:', sample_std)

margin_of_error = z_critical * (sample_std/math.sqrt(sample_size))
print("Margin of error:",margin_of_error)

confidence_interval = (sample_mean - margin_of_error, sample_mean + margin_of_error)
print("confidence interval:", confidence_interval)

true_mean = data.installment.mean()
print("True mean of data:", true_mean)

# CLT
sample_size = np.array([20,50,100])
fig, axes = plt.subplots(3,1, figsize=(10,20))

for i in range(len(sample_size)):
    m = []
    for j in range(1000):
        mean = data['installment'].sample(sample_size[i]).mean()
        m.append(mean)
    mean_series = pd.Series(m)
    axes[i].hist(mean_series)
plt.show()

data['int.rate'] = data['int.rate'].map(lambda x: str(x)[:-1])
data['int.rate'] = data['int.rate'].astype(float)/100

z_statistic_1, p_value_1 = ztest(x1 = data[data['purpose'] == 'small_business']['int.rate'], value = data['int.rate'].mean(), alternative = 'larger')

print("z-statistic is:", z_statistic_1)
print("p-value is:", p_value_1)

z_statistic_2, p_value_2 = ztest(x1 = data[data['paid.back.loan'] == 'No']['installment'], x2 = data[data['paid.back.loan'] == 'Yes']['installment'])

print("z-statistic 2 is:", z_statistic_2)
print("p-value 2 is:", p_value_2)

yes = data[data['paid.back.loan'] == 'Yes']['purpose'].value_counts()
no = data[data['paid.back.loan'] == 'No']['purpose'].value_counts()
print(yes)
print(no)
observed = pd.concat([yes.transpose(), no.transpose()], 1,keys=['Yes','No'])
print(observed)

chi2, p, dof, ex = chi2_contingency(observed)

print("Critical value is:", critical_value)

print("chi statistic is:", chi2)

#Code starts here

import numpy,import scipy,from scipy,from statsmodels


"""
Problem 12
The sequence of triangle numbers is generated by adding the natural numbers.
So the 7th triangle number would be 1 + 2 + 3 + 4 + 5 + 6 + 7 = 28.

The first ten terms would be:
1, 3, 6, 10, 15, 21, 28, 36, 45, 55, ...

Let us list the factors of the first seven triangle numbers:

     1: 1
     3: 1,3
     6: 1,2,3,6
    10: 1,2,5,10
    15: 1,3,5,15
    21: 1,3,7,21
    28: 1,2,4,7,14,28
We can see that 28 is the first triangle number to have over five divisors.

What is the value of the first triangle number to have over five hundred divisors?
"""
import numpy as np

def generate_triangle_numbers(n):
    # triangular numbers can be calculated with dynamic formula below
    # instead of summing all numbers from 1 till n...
    return int(n * (n + 1) / 2)

def number_of_divisors(n):
    factors = set()

    # a factor will always be a pair.
    # the lesser of the pairs can never be greater than sqrt(n)
    for i in range(1, int(np.sqrt(n) + 1)):
        if n % i == 0:  # finds the "lesser" factors
            factors.update([i])  # saves "lesser"
            factors.update([n // i])  # saves floor division of "lesser" - complement in the 'pair'
    return len(factors)

j = 1

while number_of_divisors(generate_triangle_numbers(j)) < 500:
    j += 1

print('winner winnner: {0}'.format(generate_triangle_numbers(j)))

Solutions/Problem 12 - Highly divisible triangular number.py


# Importing the libraries
import pandas as pd
import statistics

#import scipy as statistics

data1 = [1, 2, 3, 4, 4, 5, 7, 9]

data1_mean = statistics.mean(data1)

print("Mean is :", data1_mean)
print("Mean is : %.2f" % (data1_mean))
print("Mean is : %.3f" % (data1_mean))
print("Mean is : %.4f" % (data1_mean))
print("Mean is : %.5f" % (data1_mean))
print("Mean is : %.6f" % (data1_mean))

data1_median = statistics.median(data1)
print("Median is :", data1_mean)
data1_mode = statistics.mode(data1)
print("Mode is :", data1_mode)
data1_stdev = statistics.stdev(data1)
print("Stdev is :", data1_stdev)
data1_variance = statistics.variance(data1)
print("Variance is :", data1_variance)

# Importing the Salary .csv data set
salaryDS = pd.read_csv('Dummy_Salary_Data.csv')
print (salaryDS)

exp = salaryDS.iloc[:, 0].values
salary = salaryDS.iloc[:, 1].values

print ("exp: ", exp)

exp_mean = statistics.mean(exp)
exp_median = statistics.median(exp)
exp_mode = statistics.mode(exp)
exp_stdv = statistics.stdev(exp)
exp_variance = statistics.variance(exp)

print ("exp_mean: ", exp_mean)
print ("exp_median: ", exp_median)
print ("exp_mode: ", exp_mode)
print ("exp_stdv: ", exp_stdv)
print ("exp_variance: ", exp_variance)

print ("salary: ", salary)

salary_mean = statistics.mean(salary)
salary_median = statistics.median(salary)
salary_mode = statistics.mode(salary)
salary_stdv = statistics.stdev(salary)
salary_variance = statistics.variance(salary)

print ("salary_mean: ", salary_mean)
print ("salary_median: ", salary_median)
print ("salary_mode: ", salary_mode)
print ("salary_stdv: ", salary_stdv)
print ("salary_variance: ", salary_variance)

# Importing the Titanic .csv data file
titanicDS = pd.read_csv('Dummy_Titanic_Data.csv')
print (titanicDS)

age = titanicDS.iloc[:, 4].values

ticket_fare = gender = titanicDS.iloc[:, 7].values

print ("age: ", age)

age_mean = statistics.mean(age)
age_median = statistics.median(age)
age_mode = statistics.mode(age)
age_stdv = statistics.stdev(age)
age_variance = statistics.variance(age)

print ("age_mean: ", age_mean)
print ("age_median: ", age_median)
print ("age_mode: ", age_mode)
print ("age_stdv: ", age_stdv)
print ("age_variance: ", age_variance)

print ("ticket_fare: ", ticket_fare)

ticket_fare_mean = statistics.mean(ticket_fare)
ticket_fare_median = statistics.median(ticket_fare)
ticket_fare_mode = statistics.mode(ticket_fare)
ticket_fare_stdv = statistics.stdev(ticket_fare)
ticket_fare_variance = statistics.variance(ticket_fare)

print ("ticket_fare_mean: ", ticket_fare_mean)
print ("ticket_fare_median: ", ticket_fare_median)
print ("ticket_fare_mode: ", ticket_fare_mode)
print ("ticket_fare_stdv: ", ticket_fare_stdv)
print ("ticket_fare_variance: ", ticket_fare_variance)

Data Analysis/Some Basic and Useful Statistical Calculations with Python Anaconda Spyder.py


import scipy.stats
import numpy as np

def one_way_anova(*args):
    """One-way ANOVA to check if all groups have the same pop. mean.

    ANOVA stands for `Analysis of Variance`.

    Assumptions:
        i.i.d. x_{(1, 1)}, ..., x_{(1, m)} ~ N(mu_1, sigma^{2})
        i.i.d. x_{(2, 1)}, ..., x_{(2, m)} ~ N(mu_2, sigma^{2})
        ...
        i.i.d. x_{(n, 1)}, ..., x_{(n, m)} ~ N(mu_n, sigma^{2})

        Note 1: all groups (1 to n) has the same number of samples `m`.
        Note 2: all groups (1 to n) has the same pop. variance sigma^{2}.

    Test statistic: w = msb / msw
    where:
        msb = m * var(group_means);
        msw = mean(group_vars).

    Null distribution: W ~ F(n - 1, n * (m - 1)), where F is the F-distribution,
    `n` is the number of groups and `m` is the number of samples per group.

    H0: mu_1 = mu_2 = ... = mu_n
    HA: Exists at least a pair (i, j), 1 <= i, j <= n, such that mu_i != mu_j.
    """
    assert len(set(map(len, args))) == 1

    num_groups, num_inst_per_group = len(args), len(args[0])

    group_means = list(map(np.mean, args))
    group_vars = list(map(lambda arr: np.var(arr, ddof=1), args))

    msb = num_inst_per_group * np.var(group_means, ddof=1)
    msw = np.mean(group_vars)

    null_dist = scipy.stats.f(num_groups - 1, num_groups * (num_inst_per_group - 1))

    test_statistic = msb / msw
    p_value = null_dist.sf(test_statistic)

    return test_statistic, p_value

def f_test_for_equal_means(*args):
    """Also known as One-way ANOVA."""
    return one_way_anova(*args)

def _test():
    for n_groups in (2, 5, 10, 20):
        samples = [np.random.randn(50) for _ in range(n_groups)]
        res = one_way_anova(*samples)
        print(res)
        assert np.allclose(res, scipy.stats.f_oneway(*samples))

        samples[1] += 3 * np.random.random() - 1.5
        res = one_way_anova(*samples)
        print(res)
        assert np.allclose(res, scipy.stats.f_oneway(*samples))

if __name__ == "__main__":
    _test()


from typing import Tuple

import numpy as np

from utils.common_types import *

def wedge_mask(img_dim: Tuple[int, int], wedge_width_ratio: float = 0.2) -> NumpyArray:

    img_dimy, img_dimx = img_dim

    xx, yy = np.meshgrid(range(img_dimx), range(img_dimy))

    xx = xx / img_dimx
    yy = yy / img_dimy

    xx = xx - 1/2
    yy = yy - 1/2

    mask = np.logical_or(
        np.abs(xx) < wedge_width_ratio/2, np.abs(yy) < wedge_width_ratio/2
    )

    return mask[None, :, :]

def circle_mask(img_dim: Tuple[int, int], diameter_ratio: float = 0.5) -> NumpyArray:

    img_dimy, img_dimx = img_dim

    xx, yy = np.meshgrid(range(img_dimx), range(img_dimy))

    xx = xx / img_dimx
    yy = yy / img_dimy

    xx = xx - 1/2
    yy = yy - 1/2

    mask = xx**2 + yy**2 < (diameter_ratio/2) ** 2

    return mask[None, :, :]

def circular_strip_mask(
    img_dim: Tuple[int, int],
    outer_diameter_ratio: float = 0.2, inner_diameter_ratio: float = 0.1
) -> NumpyArray:

    outer_circle = circle_mask(img_dim, diameter_ratio=outer_diameter_ratio)
    inner_circle = circle_mask(img_dim, diameter_ratio=inner_diameter_ratio)

    strip = np.logical_xor(outer_circle, inner_circle)

    return strip

def rectangle_mask(
    img_dim: Tuple[int, int], height_ratio: float = 0.3, width_ratio: float = 0.7
) -> NumpyArray:

    img_dimx, img_dimy = img_dim

    xx, yy = np.meshgrid(range(img_dimx), range(img_dimy))

    xx = xx / img_dimx
    yy = yy / img_dimy

    xx = xx - 1/2
    yy = yy - 1/2

    mask = np.logical_and(
        np.abs(xx) < width_ratio/2, np.abs(yy) < height_ratio/2
    )

    return mask[None, :, :]

def square_mask(img_dim: Tuple[int, int], width_ratio: float = 0.5) -> NumpyArray:
    return rectangle_mask(img_dim, width_ratio, width_ratio)

def apply_mask(arr: NumpyArray, mask: NumpyArray) -> NumpyArray:

    channels = arr.shape[-3]

    if channels != 1:
        mask = np.concatenate([mask] * channels, axis=0)

    if len(arr.shape) == 3:
        return arr[mask]

    if len(arr.shape) == 4:
        return arr[:, mask]

    assert False


import numpy as np

class PCA:
    def __init__(self, threshold: float, n_components: int=None):
        if threshold == None:
            self.mode = 'number'
            self.n_components = n_components
        else:
            self.mode = 'threshold'
            self.threshold = threshold

    def fit(self, X: np.array):
        n_samples, n_dimensions = X.shape
        self.mean_vector = X.mean(0)
        self.A = X - self.mean_vector
        Sigma = np.cov(self.A, rowvar=False)
        eigen_values, eigen_vectors = np.linalg.eig(Sigma)
        indices = np.argsort(eigen_values)[::-1]
        eigen_values = eigen_values[indices]
        eigen_vectors = eigen_vectors[:, indices]

        explained_variance_ratio = eigen_values / eigen_values.sum()
        if self.mode == 'threshold':
            cumulative_expl_var = np.cumsum(explained_variance_ratio)
            self.n_components = np.where(cumulative_expl_var > self.threshold)[0][0]
        self.explained_variance_ratio = explained_variance_ratio[:self.n_components]
        self.eigen_values = eigen_values[:self.n_components]
        self.eigen_vectors = eigen_vectors[:, :self.n_components]

    def transform(self) -> np.array:
        return self.A @ self.eigen_vectors

    def fit_transform(self, X: np.array) -> np.array:
        self.fit(X)
        return self.transform()

    def reconstruct(self, X_hat) -> np.array:
        return (X_hat @ self.eigen_vectors.T) + self.mean_vector


from __future__ import print_function
from scipy.spatial.distance import cdist
import numpy as np
import matplotlib.pyplot as plt

np.random.seed(11)

means = [[2, 2], [8, 3], [3, 6]]
cov = [[1, 0], [0, 1]]
N = 500

X0 = np.random.multivariate_normal(means[0], cov, N)
X1 = np.random.multivariate_normal(means[1], cov, N)
X2 = np.random.multivariate_normal(means[2], cov, N)

X = np.concatenate((X0, X1, X2), axis = 0)
K = 3

original_label = np.asarray([0]*N + [1]*N + [2]*N).T

def kmeans_display(X, label):
    K = np.amax(label) + 1
    X0 = X[label == 0, :]
    X1 = X[label == 1, :]
    X2 = X[label == 2, :]

    plt.plot(X0[:, 0], X0[:, 1], 'b^', markersize = 4, alpha = .8)
    plt.plot(X1[:, 0], X1[:, 1], 'go', markersize = 4, alpha = .8)
    plt.plot(X2[:, 0], X2[:, 1], 'rs', markersize = 4, alpha = .8)

    plt.axis('equal')
    plt.plot()
    plt.show()

kmeans_display(X, original_label)

def kmeans_init_centers(X, k):
    # randomly pick k rows of X as initial centers
    return X[np.random.choice(X.shape[0], k, replace=False)]

def kmeans_assign_labels(X, centers):
    # calculate pairwise distances btw data and centers
    D = cdist(X, centers)
    # return index of the closest center
    return np.argmin(D, axis = 1)

def kmeans_update_centers(X, labels, K):
    centers = np.zeros((K, X.shape[1]))
    for k in range(K):
        # collect all points assigned to the k-th cluster
        Xk = X[labels == k, :]
        # take average
        centers[k,:] = np.mean(Xk, axis = 0)
    return centers

def has_converged(centers, new_centers):
    # return True if two sets of centers are the same
    return (set([tuple(a) for a in centers]) ==
        set([tuple(a) for a in new_centers]))

def kmeans(X, K):
    centers = [kmeans_init_centers(X, K)]
    labels = []
    it = 0
    while True:
        labels.append(kmeans_assign_labels(X, centers[-1]))
        new_centers = kmeans_update_centers(X, labels[-1], K)
        if has_converged(centers[-1], new_centers):
            break
        centers.append(new_centers)
        it += 1
    return (centers, labels, it)

(centers, labels, it) = kmeans(X, K)
print('Centers found by our algorithm:')
print(centers[-1])

kmeans_display(X, labels[-1])


# Naive Implementation of SUTD ISTD 2021 50.034 Introduction to Probability and Statistics Midterm Exam Final Question
# Created by James Raphael Tiovalen (2021)

import numpy as np
import matplotlib.pyplot as plt

rng = np.random.default_rng()

LAMBDA = rng.uniform(0, 1)
N = 1_000_000
DATA_POINTS = 1_000_000
BINS = 200

# Assume that each call to rand() (i.e., each sample drawn) is independent and identically distributed (i.i.d.)
def rand(num_of_samples):
    return rng.uniform(-LAMBDA, LAMBDA, num_of_samples)

# As N -> +∞, this becomes approximately closer and closer to a normal distribution with mean 0 and variance (N * (LAMBDA ** 2)) / 3
# These properties actually agree with the reformulated/reinterpreted Central Limit Theorem approximation in terms of sums of i.i.d. random variables
def gauss():
    return np.sum(rand(N))

def main():
    nums = np.array([gauss() for _ in np.arange(DATA_POINTS)])
    actual = rng.normal(0, (LAMBDA * np.sqrt(N / 3)), DATA_POINTS)

    # Capture and show all data points within three standard deviations from the mean (≈99.73%)
    bins = np.linspace(
        -3 * (LAMBDA * np.sqrt(N / 3)), 3 * (LAMBDA * np.sqrt(N / 3)), BINS
    )

    # Plot the collected data points to illustrate the normal distribution shape and compare with the actual normal distribution
    plt.style.use("seaborn-deep")
    plt.hist(nums, bins, alpha=0.5, label="nums")
    plt.hist(actual, bins, alpha=0.5, label="actual")
    plt.legend(loc="upper right")
    plt.show()

if __name__ == "__main__":
    main()


"""
Directional filtering functions
"""
import numpy as np
from scipy.fftpack import fft2, ifft2, fftfreq, fftshift, ifftshift

def dff2d(z, dx, thetalow, thetahigh):
    """
    Two-dimensional directional Fourier transform filter.

    Inputs:
    ------
        z: 2D complex array
        dx: grid spacing
        thetalow: low angle for filter (degrees CCW from E)
        thetahigh: high angle for filter (degrees CCW from E)

    Outputs:
    -----
        zf: 2D filtered complex array
    """

    # 2D fourier transfrom
    My, Mx = z.shape
    Z = fft2(z)

    # Compute zonal wavenumbers
    k = fftfreq(Mx, dx/(2*np.pi))
    dk = 1/(Mx*dx)

    # Compute meridional wavenumbers
    l = fftfreq(My, dx/(2*np.pi))
    dl = 1/(My*dx)

    # Need to re-order the FFT output because positive frequencies are output first from the numpy DFT algorithm
    k_r = fftshift(k)
    l_r = fftshift(l)

    # Create a grid for the direction
    Lx,Ly = np.meshgrid(k_r,l_r)
    theta = np.angle(Lx + 1j*Ly)

    thetadeg = np.mod(theta*180/np.pi,360)

    # Create the filter matrix
    H = np.zeros_like(thetadeg)

    if thetahigh > 360:
        filter_idx1 = (thetadeg > thetalow) & (thetadeg < 360)
        filter_idx2 = (thetadeg > 0) & (thetadeg < np.mod(thetahigh,360))
        filter_idx = filter_idx1 | filter_idx2
    else:
        filter_idx = (thetadeg > thetalow) & (thetadeg < thetahigh)

    H[filter_idx] = 1

    # Now reorder H into the original FFT ordering
    H_r = ifftshift(H,axes=1)
    H_r = ifftshift(H_r,axes=0)

    # Finally, filter
    zf = ifft2(Z*H_r)

    return zf

def hilbert_2d(z, dx, dy):
    My, Mx = z.shape
    Z = fft2(z)

    # Compute zonal frequencies
    k = fftfreq(Mx, dx/(2*np.pi))
    dk = 1/(Mx*dx)

    # Compute meridional frequencies
    l = fftfreq(Mx, dx/(2*np.pi))
    dl = 1/(My*dy)

    # Create filter matrices for each of the four quadrant
    Z_posk_posl = np.zeros_like(Z)
    Z_posk_posl[:My//2, :Mx//2] = Z[:My//2, :Mx//2]

    z_posk_posl = ifft2(Z_posk_posl)

    Z_posk_negl = np.zeros_like(Z)
    Z_posk_negl[:My//2, Mx//2::] = Z[:My//2, Mx//2::]

    z_posk_negl = ifft2(Z_posk_negl)

    Z_negk_negl = np.zeros_like(Z)
    Z_negk_negl[My//2::, Mx//2::] = Z[My//2::, Mx//2::]

    z_negk_negl = ifft2(Z_negk_negl)

    Z_negk_posl = np.zeros_like(Z)
    Z_negk_posl[My//2::, :Mx//2] = Z[My//2::, :Mx//2]

    z_negk_posl = ifft2(Z_negk_posl)

    return z_posk_posl, z_posk_negl, z_negk_negl, z_negk_posl


import numpy
import math

from base.polynomial import Polynomial
from base.matrix import Matrix
from newtons_method import Newtons_method, get_roots
from legendre_polynomial import Legendre

class GaussianQuad:
    """
    Gaussian Quadrature is a numerical approximation technique to calculate the
    the definite integral of a function.
     - It utilises a weighted-sum of function values at prescribed points within the
        definitive domain described by the integral
     - To utilise this method, the definite interval needs to be translated to
        the interval: [-1; 1]
    """

    # Define the order of Gaussian Quadrature:
    #   - n_order = number of points / order of gaussian quadrature
    order = 1                     # Int (>= 1)

    # Define the legendre polynomials up to a point
    legendre = []              # Legendre Polynomial for specified order

    # Define the Quadrature Matrix:
    #   - Size: Order x 2:
    #       [n,0] = node position
    #       [n,1] = node weight value
    quadrature = []                 # Matrix (nx2)

    def __init__(self, n_points) -> None:
        self.order = n_points
        self.quadrature = numpy.zeros((n_points, 2))

        if n_points == 1:   # If first simple case, avoid generating legendre polynomials
            self.quadrature[0, 0] = 0
            self.quadrature[0, 1] = 2
        else:
            # generate legendre equation polynomial of degree n
            self.legendre = Legendre(n_points)
            # Print Legendre polynomial info
            #print(f"legendre_polynomial:{self.legendre.co_array}, roots:{self.legendre.roots}")

            # use legendre polynomial to
            for i in range(n_points):
                self.quadrature[i, 0] = self.legendre.roots[i]
                self.quadrature[i, 1] = self.calculate_weight_function(i, self.legendre.roots[i])

    def __str__(self) -> str:
        ret_str = "{}\n".format(self.order)
        for i in range(self.order):
            ret_str += "{}:{},\n".format(self.quadrature[i,0], self.quadrature[i, 1])
        return ret_str

    # Calculate the weight value correlating to specified root of Legendre polynomial
    def calculate_weight_function(self, i, root_i) -> float:
        # w_i = 2/(1 - (x_i)^2) * 2/(P'n(x_i)^2)
        #   - see https://en.wikipedia.org/wiki/Gaussian_quadrature for details on formula.
        derivative = self.legendre.derive()
        deriv_x_i = derivative.evaluate(root_i)

        # Print root info used for calculating corresponding weight values
        #print(f"i:{i}|root_i:{root_i}, dx_i:{deriv_x_i}, deriv:{repr(derivative)}")
        return 2/((1-root_i**2)*(deriv_x_i**2))

    # Calulate definite integral (a numerical approximation) of a polynomial function
    #  between limits a and b
    #   - translates [a,b] limits to [-1,1]
    #   - calculate the weighted sum of function values at each Gaussian point
    def calculate_definite_integral(self, polynomial, a, b):
        ba = (b - a)/2
        ab = (a + b)/2
        fret = 0.0
        for p in range(self.gaussian.order):
            val = ba*self.quadrature[p, 0] + ab
            fret += self.quadrature[p, 1]*polynomial.evaluate(val)
        fret *= ba
        return fret

# Tests for the Gaussian Quadrature method
def main():
    # test function for legendre_polynomial + binomial_coefficient
    n_test = 4
    gquad = GaussianQuad(n_test)
    print("n_test:", gquad)

if __name__ == "__main__":
    main()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
ch5Python.py Statistical significance of regression.
"""
import numpy as np
from scipy import stats
import statsmodels.api as sm

x = [1.00,1.25,1.50,1.75,2.00,2.25,2.50,2.75,3.00,3.25,3.50,3.75,4.00]
y = [3.34,4.97,4.15,5.40,5.21,4.56,3.69,5.86,4.58,6.94,5.57,5.62,6.87]

# Convert data to vectors.
x = np.array(x)
y = np.array(y)
n = len(y)
xmean = np.mean(x)
ymean = np.mean(y)
# Find zero mean versions of x and y.
xzm = x - xmean
yzm = y - ymean
covxy = np.sum(xzm * yzm)/n
varx = np.var(x)
vary = np.var(y)
print("Variance of x = %0.3f." %varx) # 0.875
print("Variance of y = %0.3f." %vary) # 1.095
print("Covariance of x and y = %0.3f." %covxy) # 0.669

# Find slope b1.
b1 = covxy/varx # 0.764
# Find intercept b0.
b0 = ymean - b1*xmean # 3.225
print('\nslope b1 = %6.3f\nintercept b0 = %6.3f.' % (b1, b0))

# Find vertical projection of y onto best fitting line.
yhat = b1*x + b0

numparams = 2 # number of parameters=2 (slope and intercept).

# SLOPE
# Find sem of slope.
num = ( (1/(n-numparams)) * sum((y-yhat)**2) )**0.5 # 0.831.
den = sum((x-xmean)**2)**0.5 # 3.373
semslope = num/den
print('semslope = %6.3f.' % (semslope))

# Find t-value of slope.
tslope = b1/semslope # 3.101

# Find p-value of slope.
# two-tailed pvalue = Prob(abs(t)>tt).
pvalue = stats.t.sf(np.abs(tslope), n-numparams)*2 # 0.0101
print('\nSLOPE:\nt-statistic = %6.3f.' % tslope)
print('pvalue = %6.4f.\m' % pvalue)

# INTERCEPT
# Find sem of intercept.
a = ( (1/(n-numparams)) * sum((y-yhat)**2) )**0.5 # 0.831
b = ( (1/n) + xmean**2 / sum( xzm**2 ))**0.5 # 0.791
semintercept = a * b  # 0.658
print('\na = %6.3f\nb = %6.3f\nsemintercept = a/b = %6.3f.'
% (a, b, semintercept))

# Find t-value of intercept.
tintercept = b0 / semintercept
pintercept = stats.t.sf(np.abs(tintercept), n-numparams)*2
print('\nINTERCEPT:\nt-statistic = %6.3f.'% tintercept)
print('pvalue = %6.4f.\n'% pintercept)

# Overall model fit to data.
# Find coefficient of variation r2.
r2 = covxy * covxy / (varx * vary)
print("coefficient of variation = %0.3f." %r2) # 0.466.

# Find F ratio.
A = r2 / (numparams-1)
B = (1-r2) / (n-numparams)
F = A/B # 9.617
print("F ratio = %0.4f." % F)

pfit = stats.f.sf(F, numparams-1, n-numparams)
print("p overall fit = %0.4f." % pfit) # 0.0101.

# Run standard library regression method for comparison.
ones = np.ones(len(x))
X = [ones, x]
X = np.transpose(X)
y = np.transpose(y)
res_ols = sm.OLS(y, X).fit()
print(res_ols.summary()) # Print table of results.

# END OF FILE.

import numpy,from scipy,import statsmodels


"""
Simple implementation of classical MDS.
See http://www.stat.cmu.edu/~ryantibs/datamining/lectures/09-dim3-marked.pdf for more details.
"""

import numpy as np
import numpy.linalg as linalg

def mds(D, dim=2):
    """ Classical multidimensional scaling algorithm.
        Given a matrix of interpoint distances D, find a set of low dimensional points
        that have a similar interpoint distances.
    """
    (n,n) = D.shape
    A = (-0.5 * D**2)
    M = np.ones((n,n))/n
    I = np.eye(n)
    B = np.dot(np.dot(I-M, A),I-M)

    '''Another way to compute inner-products matrix B
    Ac = np.mat(np.mean(A, 1))
    Ar = np.mat(np.mean(A, 0))
    B = np.array(A - np.transpose(Ac) - Ar + np.mean(A))
    '''

    [U,S,V] = linalg.svd(B)
    Y = U * np.sqrt(S)
    return (Y[:,0:dim], S)

def main():
    """ @Todo: Adding simple test code for mds."""
    pass

if __name__ == '__main__':
    main()


import numpy as np

np.random.seed(12)
num_observations = 5000

x1 = np.random.multivariate_normal([0, 0], [[1, .75],[.75, 1]], num_observations)
x2 = np.random.multivariate_normal([1, 4], [[1, .75],[.75, 1]], num_observations)

simulated_separableish_features = np.vstack((x1, x2)).astype(np.float32)
simulated_labels = np.hstack((np.zeros(num_observations),
                              np.ones(num_observations)))

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

def log_likelihood(features, target, weights):
    scores = np.dot(features, weights)
    ll = np.sum( target*scores - np.log(1 + np.exp(scores)) )
    return ll

def logistic_regression(features, target, num_steps, learning_rate, add_intercept = False):
    if add_intercept:
        intercept = np.ones((features.shape[0], 1))
        features = np.hstack((intercept, features))

    weights = np.zeros(features.shape[1])

    for step in xrange(num_steps):
        scores = np.dot(features, weights)
        predictions = sigmoid(scores)

        # Update weights with gradient
        output_error_signal = target - predictions
        gradient = np.dot(features.T, output_error_signal)
        weights += learning_rate * gradient

        # Print log-likelihood every so often
        if step % 10000 == 0:
            print log_likelihood(features, target, weights)

    return weights

weights = logistic_regression(simulated_separableish_features, simulated_labels,
     num_steps = 300000, learning_rate = 5e-5, add_intercept=True)

data_with_intercept = np.hstack((np.ones((simulated_separableish_features.shape[0], 1)),
                                 simulated_separableish_features))
final_scores = np.dot(data_with_intercept, weights)
preds = np.round(sigmoid(final_scores))

print 'Accuracy from scratch: {0}'.format((preds == simulated_labels).sum().astype(float) / len(preds))
print 'Accuracy from sk-learn: {0}'.format(clf.score(simulated_separableish_features, simulated_labels))


# -*- coding: utf-8 -*-
"""
Created on Fri Dec 15 21:04:26 2017

@author: Anastasios Tzavellas
"""

import numpy as np
import matplotlib.pyplot as plt

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

def L(x, i, xValues):
    """Evaluates ith degree
    Lagrange Polynomial"""
    xi = xValues[i]
    product = 1.
    for j, xj in enumerate(xValues):
        if j != i:
            product = product * (x - xj) / (xi - xj)
    return product

def Lagrange(x, xValues, fValues):
    """Evaluates Lagrange Polynomial"""
    val = 0.
    for i, fi in enumerate(fValues):
        val = val + L(x, i, xValues) * fi
    return val

xValues = np.array([1, 1.1, 1.2, 1.3, 1.4])
fValues = f(xValues)
p = Lagrange(1.25, xValues, fValues)
print('Function Value f(1.25)=', f(1.25))
print('Lagrange polyn p(1.25)=', p)

plt.close('all')
x = np.arange(1.0, 1.41, 0.01)
ps = Lagrange(x, xValues, fValues)
plt.plot(x, f(x), '.', label='f(x)')
plt.plot(xValues, fValues, '*')
plt.plot(x, ps, '-.', label='p(x)')
plt.legend()
plt.grid()


# from sympy import
from utils import r

def secant_method(fn, x1, x0, iter=1):
    out_str = ''

    if iter == 1:
        out_str += f"Secant method is given as:\n"
        out_str += f"$$ x_{{i+1}} = x_i - \\left[\\frac{{f(x_i)(x_{{i-1}} - x_{{i}})}}{{f(x_{{i-1}}) - f(x_{{i}})}}\\right] $$\n"

    out_str += f"\\textbf{{Iteration {iter}}}\n\n"
    out_str += f"$$ x_{iter} = x_{iter - 1} - \\left[\\frac{{f(x_{iter - 1})(x_{{{iter - 2}}}-x_{iter - 1})}}{{f(x_{{{iter - 2}}}) - f(x_{{{iter - 1}}})}}\\right] $$\n"

    out_str += f"$$ f(x_{{{iter - 2}}}) = f({r(x0)}) = {r(fn(x0))} $$\n"
    out_str += f"$$ f(x_{{{iter - 1}}}) = f({r(x1)}) = {r(fn(x1))} $$\n"

    out_str += f"$$ x_{iter} = {r(x0)} - \\left[\\frac{{{r(fn(x1))}({r(x0)}-({r(x1)}))}}{{{r(fn(x0))} - ({r(fn(x1))}))}}\\right] $$\n"
    x_val = fn(x0) * (x0 - x1) / (fn(x0) - fn(x1))
    out_str += f"$$ x_{iter} = {r(x0)} - ({r(x_val)}) $$\n"
    x_val = x0 - x_val
    out_str += f"$$ x_{iter} = {r(x_val)} $$\n"

    error = None
    if iter > 1:
        out_str += "\n\\textbf{Error}\n\n"
        out_str += f"$$ \\text{{Error}} = \\frac{{\\left|\\text{{latest value}} - \\text{{previous value}}\\right|}}{{\\left|\\text{{latest value}}\\right|}} \\times 100 $$\n"
        out_str += f"$$ \\text{{Error}} = \\frac{{\\left|{r(x_val)} - ({r(x1)})\\right|}}{{\\left|{r(x_val)}\\right|}} \\times 100 $$\n"
        error = abs(x_val - x1)/abs(x_val) * 100
        out_str += f"$$ \\text{{Error}} = {r(error, 2)} \\% $$\n"

    if error is not None and error < 0.0001 or iter > 3:
        return out_str

    out_str += secant_method(fn, x_val, x1, iter+1)
    return out_str

if __name__ == '__main__':
    from sympy import lambdify, sin, cos, symbols

    x = symbols('x')
    print(secant_method(lambdify(x, sin(x) + cos(1-x**2) - 1), 3, 1))


#! /usr/bin/env python

"""
File: read_2columns.py

Copyright (c) 2016 Taylor Patti

License: MIT

This module demonstrates three different iteration techniques, lists and loops,
vectorization with default summation, and vectorization wtih numpy sumation,
through the implementation of a midpoint integration function.

"""

import numpy as np

def midpointint(f, a, b, n):
    """Uses lists and for loop to iterate through a midpoint integration function"""
    h = (b - a) / float(n)
    sum = 0
    for i in range(n):
        sum = sum + h * f(a - (h /2) + (i+1) * h)
    return sum

def midpointvecdefault(f, a, b, n):
    """Uses arrays and default summation for a midpoint integration function"""
    h = (b - a) / float(n)
    info = np.array([h * f(a - (h /2) + (i+1) * h) for i in range(n)])
    return sum(info)

def midpointvec(f, a, b, n):
    """Uses arrays and numpy summation for a midpoint integration function"""
    h = (b - a) / float(n)
    info = np.array([h * f(a - (h /2) + (i+1) * h) for i in range(n)])
    return np.sum(info)

def x_func(i):
    """The function x, for implementation in other functions."""
    return i

def test_midpointint_triangle():
    """Ensures proper integration of the straight line x from 1 to 3."""
    apt = (abs((midpointint(x_func, 1, 3, 1000)) - 4) <1e-6)
    msg = 'Unsuccessful integration.'
    assert apt, msg

def test_midpointvecdefault_triangle():
    """Ensures proper integration of the straight line x from 1 to 3."""
    apt = (abs(midpointvecdefault(x_func, 1, 3, 1000) - 4) <1e-6)
    msg = 'Unsuccessful integration.'
    assert apt, msg

def test_midpointvec_triangle():
    """Ensures proper integration of the straight line x from 1 to 3."""
    apt = (abs(midpointvec(x_func, 1, 3, 1000) - 4) <1e-6)
    msg = 'Unsuccessful integration.'
    assert apt, msg


'''
Are the Belmont Stakes results Normally distributed?
100xp

Since 1926, the Belmont Stakes is a 1.5 mile-long race of 3-year old thoroughbred horses.
Secretariat ran the fastest Belmont Stakes in history in 1973. While that was the fastest
year, 1970 was the slowest because of unusually wet and sloppy conditions. With these two
outliers removed from the data set, compute the mean and standard deviation of the Belmont
winners' times. Sample out of a Normal distribution with this mean and standard deviation
using the np.random.normal() function and plot a CDF. Overlay the ECDF from the winning
Belmont times. Are these close to Normally distributed?

Note: Justin scraped the data concerning the Belmont Stakes from the Belmont Wikipedia page.

Instructions
-Compute mean and standard deviation of Belmont winners' times with the two outliers removed.
The NumPy array belmont_no_outliers has these data.
-Take 10,000 samples out of a normal distribution with this mean and standard deviation using
np.random.normal().
-Compute the CDF of the theoretical samples and the ECDF of the Belmont winners' data, assigning
the results to x_theor, y_theor and x, y, respectively.
-Hit submit to plot the CDF of your samples with the ECDF, label your axes and show the plot.
'''
import numpy as np
import matplotlib.pyplot as plt

belmont_no_outliers = np.array([148.51,  146.65,  148.52,  150.7,  150.42,  150.88,  151.57,
                                147.54,  149.65,  148.74,  147.86,  148.75,  147.5,  148.26,
                                149.71,  146.56,  151.19,  147.88,  149.16,  148.82,  148.96,
                                152.02,  146.82,  149.97,  146.13,  148.1,  147.2,  146.,
                                146.4,  148.2,  149.8,  147.,  147.2,  147.8,  148.2,
                                149.,  149.8,  148.6,  146.8,  149.6,  149.,  148.2,
                                149.2,  148.,  150.4,  148.8,  147.2,  148.8,  149.6,
                                148.4,  148.4,  150.2,  148.8,  149.2,  149.2,  148.4,
                                150.2,  146.6,  149.8,  149.,  150.8,  148.6,  150.2,
                                149.,  148.6,  150.2,  148.2,  149.4,  150.8,  150.2,
                                152.2,  148.2,  149.2,  151.,  149.6,  149.6,  149.4,
                                148.6,  150.,  150.6,  149.2,  152.6,  152.8,  149.6,
                                151.6,  152.8,  153.2,  152.4,  152.2])

def ecdf(data):
    """Compute ECDF for a one-dimensional array of measurements."""

    # Number of data points: n
    n = len(data)

    # x-data for the ECDF: x
    x = np.sort(data)

    # y-data for the ECDF: y
    y = np.arange(1, n + 1) / n

    return x, y

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

# Compute mean and standard deviation: mu, sigma
mu = np.mean(belmont_no_outliers)
sigma = np.std(belmont_no_outliers)

# Sample out of a normal distribution with this mu and sigma: samples
samples = np.random.normal(mu, sigma, 10000)

# Get the CDF of the samples and of the data
x_theor, y_theor = ecdf(samples)
x, y = ecdf(belmont_no_outliers)

# Plot the CDFs and show the plot
_ = plt.plot(x_theor, y_theor)
_ = plt.plot(x, y, marker='.', linestyle='none')
plt.margins(0.02)
_ = plt.xlabel('Belmont winning time (sec.)')
_ = plt.ylabel('CDF')
plt.show()


import numpy as np

'''
Softmax Function is used for MultiClass Classification.
Recall that for binary classification, sigmoid function is used.

*However, when n=2, given softmax, works actually the same as the sigmoid function.

the formula for softmax is:
P(class_i) = e^(Z_i)/(e^Z_1 + e^Z_2 + ... + e^Z_n)
'''

# Write a function that takes as input a list of numbers, and returns
# the list of values given by the softmax function.
def softmax(L):
    # numerator - convert the list into exponents
    expL = np.exp(L)

    # denominator - summation of the exponential values from the list
    sumExpL = sum(expL)

    # we need answers for each of the classes or value in the list
    result = []
    for i in expL:
        result.append(i*1.0/sumExpL)
    return result

Lesson 3: Introduction to Neural Networks/2 Softmax.py


import numpy as np
from scipy import linalg

def QR(X):
    """Compute the Gram Schmidt of the column vectors in X

    The formula: x_k := x_k-<x_k, q_1>*q_1  (k=2,...,n)
    Then we normalize x_k

    label: QR
    """

    #transpose so we are dealing with rows instead of columns
    Q = X.T.copy()
    nrows, ncols = X.shape
    R = np.zeros((nrows, ncols))

    for i in xrange(nrows):
        R[i,i] = linalg.norm(Q[i])
        Q[i] = Q[i]/R[i,i]
        for j in xrange(i+1,nrows):
            R[i,j] = Q[j].dot(Q[i])
            Q[j] = Q[j]-(R[i,j]*Q[i])

    return Q.T, R

def detQR(X):
    """Computes the determinant of X using the QR decomposition
    This will give you the determinant up to, but without sign.

    The determinant of Q is +1 or -1 which may or may not change the sign of
    the determinant of R"""
    Q, R = QR(X)
    return np.diagonal(R).prod()

def leastsq(A, b):
    """Compute a least squares solution using the QR Decomposition"""
    Q, R = linalg.qr(A)

    #We solve the triangular system instead of inverting R
    return linalg.solve_triangular(R, Q.T.dot(b))

def eigvv(A, niter=50):
    Qlist = [A]
    x0 = np.random.rand(A.shape[1])
    for i in range(niter):
        Q,R = QR(Qlist[-1])
        A = Q.T.dot(A.dot(Q))
        Qlist.append(A)

    eigvals = np.diag(Qlist[-1])

    L = np.eye(Q.shape[0])
    for qm in Qlist:
        L = L.dot(qm)

    return eigvals, x0, L


import math
import numpy as np

def rigid_transform_3D(A, B):
    """ given the coordinates of GCPs in source system in array A
        and the coordinates in the destination system in array B
    """
    centroid_A = np.mean(A, 0)
    centroid_B = np.mean(B, 0)
    N = A.shape[0]
    H = (A - centroid_A).T.dot(B - centroid_B)
    U, S, V = np.linalg.svd(H)
    # rotation matrix
    R = V.T.dot(U.T)
    if np.linalg.det(R) < 0:
        R[:,3] *= -1
    # translation
    t = -R.dot(centroid_A.T) + centroid_B.T
    # scale
    sc = np.linalg.norm(B - centroid_B, 2) / np.linalg.norm(A - centroid_A, 2)
    return R, t, sc

if __name__ == "__main__":

    A = np.loadtxt('gcp.txt', delimiter=' ')
    B = np.loadtxt('gcp_photo.txt', delimiter=' ')
    R, t, sc = rigid_transform_3D(A, B)
    print(R)
    print(t)
    print(sc)
    # check
    A2 = (R.dot(A.T)).T
    for i in range(A2.shape[0]):
        A2[i,:] = A2[i,:] + t
    err = A2 - B
    err = err * err
    err = np.sum(err)
    rmse = math.sqrt(err / A.shape[0])
    print("RMSE: {:.3f}".format(err))
    print("If RMSE is near zero, the function is correct!")


import numpy as np

def cosine_sim(vec1, vec2):
    """
    Computes the cosine similarity between two vectors
    :param vec1:
    :param vec2:
    :return:
    """
    cos_sim = np.dot(vec1, vec2) / \
        (np.linalg.norm(vec1) * np.linalg.norm(vec2))
    return cos_sim

def cosine_sim_matrix(matrix, vec):
    """
    Computes the cosine similarities between a matrix and a vector
    :param matrix:
    :param vec:
    :return:
    """
    # cos_sim is array like
    cos_sim = np.dot(matrix, vec) / \
        (np.linalg.norm(matrix, axis=1) * np.linalg.norm(vec))
    return cos_sim


#!/usr/bin/env python3
# Copyright (c) 2018 Wei-Kai Lee. All rights reserved

# coding=utf-8
# -*- coding: utf8 -*-

import numpy as np

def dx(x):
    return x[1:]-x[0:x.size-1]
def yave(y):
    xszie = y.shape[-1]
    return ( y[...,1::]+y[...,0:(xszie-1):] )/2
def myNumericalIntegration(x,y):
    """
    myNumericalIntegration is a function to calculate the area of
    a y = function(x) by trapezoid method.
    x must be ascending.

    >>> x = np.linspace(0,1,100, dtype=np.float64)
    >>> y = x**2
    >>> value = myNumericalIntegration(x,y)
    >>> print(value)
    0.33335033840084355
    >>> x = np.linspace(0,1,1000, dtype=np.float64)
    >>> y = x**2
    >>> value = myNumericalIntegration(x,y)
    >>> print(value)
    0.3333335003338339
    >>> x = np.linspace(0,1,10000, dtype=np.float64)
    >>> y = x**2
    >>> value = myNumericalIntegration(x,y)
    >>> print(value)
    0.3333333350003337
    """
    x = np.array(x)
    y = np.array(y)
    return np.einsum('...i,i->...',yave(y),dx(x))
def yave2(y):
    xszie = y.shape[0]
    return ( y[1::,...]+y[0:(xszie-1):,...] )/2
def myNumericalIntegration2(x,y):
    x = np.array(x)
    y = np.array(y)
    return np.einsum('i...,i->...',yave2(y),dx(x))
def myTRAPEZOIDAL(fun,x0,x1,xPts=200):
    """
    myTRAPEZOIDAL is a function to calclate the integration of function f from
    x0 to x1 with equal spacing. (xPts: points of x)

    >>> import numpy as np
    >>> f = lambda x: [np.sin(x), np.cos(x)]
    >>> Int, xList, yListList = myTRAPEZOIDAL(f,0,np.pi)
    >>> print(Int)
    [1.99995846e+00 9.02056208e-17]
    >>> Int, xList, yListList = myTRAPEZOIDAL(f,0,np.pi,xPts=300)
    >>> print(Int)
    [1.99998160e+00 2.68882139e-16]
    >>> Int, xList, yListList = myTRAPEZOIDAL(f,0,np.pi,xPts=400)
    >>> print(Int)
    [1.99998967e+00 1.02348685e-16]
    >>> Int, xList, yListList = myTRAPEZOIDAL(f,0,np.pi,xPts=1000)
    >>> print(Int)
    [ 1.99999835e+00 -8.96418356e-16]
    """
    xList = np.linspace(x0, x1, num=int(xPts) )
    data = np.array(fun(xList))
    Int = myNumericalIntegration(xList,data)
    return Int, xList, data
def myFunIntegration(f, x0, x1, tol=1e-5, recursiveLim=1e4, xCountStart=100, intfun=myTRAPEZOIDAL):
    """
    >>> import numpy as np
    >>> f = lambda x: [np.sin(x), np.cos(x)]
    >>> Sn, err, nodes, count =  myFunIntegration(f, 0, np.pi, tol=1e-5)
    >>> print(Sn)
    [1.99999934e+00 2.22044605e-16]
    >>> print(err)
    2.781470994472901e-06
    >>> print(nodes.size)
    17
    >>> print(count)
    8
    >>> f = lambda x: [ x**2, x**3]
    >>> Sn, err, nodes, count =  myFunIntegration(f, -2, 2, tol=1e-5)
    >>> print(Sn)
    [5.333334 0.      ]
    >>> print(err)
    4.433938502017287e-06
    >>> print(count)
    24
    >>> print(nodes.size)
    49
    >>> f = lambda x: [ np.exp(x), np.exp(x**2)]
    >>> Sn, err, nodes, count =  myFunIntegration(f, -2, 2, tol=1e-5)
    >>> print(Sn)
    [ 7.25372109 32.90525734]
    >>> print(err)
    5.414152568605779e-06
    >>> print(count)
    68
    >>> print(nodes.size)
    137
    """
    S, xList, yListList = intfun(f, x0, x1, xPts=xCountStart)
    Sn, err, nodes, count = recursive_integration1(f, x0, x1, S, tol=tol, recursiveLim=recursiveLim, xCountStart=xCountStart, intfun=intfun)
    return Sn, err, nodes, count

    # S, xList, yListList = intfun(f, x0, x1, xPts=xCountStart)
    # Sn, err, xList, count = recursive_integration2(f, x0, x1, S, xList, tol=tol, recursiveLim=recursiveLim)
    # return Sn, err, count

    # S, xList, yListList = intfun(f, x0, x1, xPts=xCountStart)
    # Sn, err, xListNew, yListListNew, count, dxmin = recursive_integration3(f, S, xList, yListList, tol=tol, recursiveLim=recursiveLim, xCountStart=xCountStart, dxMin=dxMin)
    # return Sn, err, xListNew, count
def recursive_integration1(f, x0, x1, S, tol=1e-3, recursiveLim=1e4, xCountStart=100, intfun=myTRAPEZOIDAL):
    """
    f: function of f(x)
    [a,b] : the interval of integration
    S : the previous integration result
    tol : the tolerance

    This is a subfunction of adapt_simpson.
    """
    xc = float(x0+x1)/2
    SL, xListL, dataL = intfun(f,x0,xc,xPts=xCountStart)
    SR, xListR, dataR = intfun(f,xc,x1,xPts=xCountStart)
    Sn = SL+SR
    err = max( np.abs(Sn-S) )
    if err <= tol or recursiveLim==1:
        nodes = np.array([x0,xc,x1])
        count = 1
        return Sn, err, nodes, count
    fac = 0.5
    SL, err1, nodes1, countL = recursive_integration1(f, x0, xc, SL, tol=tol*fac, recursiveLim=recursiveLim-1, xCountStart=xCountStart, intfun=intfun)
    SR, err2, nodes2, countR = recursive_integration1(f, xc, x1, SR, tol=tol*(1-fac), recursiveLim=recursiveLim-1, xCountStart=xCountStart, intfun=intfun)
    err = err1 + err2
    nodes = np.append(nodes1, nodes2[1::])
    count = countL+1 if countL>=countR else countR+1 # countL+countR # countL+1 if countL>=countR else countR+1
    Sn = SL+SR
    return Sn, err, nodes, count
def myMidpointList_Integration2(f, xList, S):
    # Mid point
    xList = np.array(xList)
    xList2 = (xList[0:xList.size-1]+xList[1:xList.size])/2
    data = f(xList2)
    # Sum
    temptsum = np.einsum('...i,i->...', data, dx(xList) )
    Sn = (S+temptsum)/2
    # Merge List
    xListNew = np.zeros( xList.size+xList2.size, dtype=xList.dtype )
    xListNew[0::2] = xList
    xListNew[1::2] = xList2
    return Sn, xListNew
def recursive_integration2(f, x0, x1, S, xList, tol=1e-5, recursiveLim=1e4):
    """
    f: function of f(x)
    [a,b] : the interval of integration
    S : the previous integration result
    tol : the tolerance

    This is a subfunction of adapt_simpson.
    """
    Sn, xListNew = myMidpointList_Integration2(f, xList, S)
    err = max( np.abs(Sn-S) )
    if err <= tol or recursiveLim==1:
        count = 1
        return Sn, err, xListNew, count
    Sn, err, xListNew, count = recursive_integration2(f, x0, x1, Sn, xList=xListNew, tol=tol, recursiveLim=recursiveLim-1)
    count = count + 1
    return Sn, err, xListNew, count
def myMidpointList_Integration3(f, xList, yListList):
    # Mid point
    xList = np.array(xList)
    xList2 = (xList[0:xList.size-1]+xList[1:xList.size])/2
    data = f(xList2)
    # Sum
    temptsum = np.einsum('...i,i->...', data, dx(xList) )
    # Merge x List
    xListNew = np.zeros( xList.size+xList2.size, dtype=xList.dtype )
    xListNew[0::2] = xList
    xListNew[1::2] = xList2
    # Merge y List
    yListListNew = np.zeros( (yListList.shape[0], xListNew.size) , dtype=yListList.dtype)
    yListListNew[:,0::2] = yListList
    yListListNew[:,1::2] = data
    # Sum
    Sn = myNumericalIntegration(xListNew,yListListNew)
    return Sn, xListNew, yListListNew
def recursive_integration3(f, S, xList, yListList, tol=1e-5, recursiveLim=1e4, xCountStart=100, dxMin=None):
    """
    f: function of f(x)
    [a,b] : the interval of integration
    S : the previous integration result
    tol : the tolerance

    This is a subfunction of adapt_simpson.
    """
    indMid = int(len(xList)/2)
    SL, xListL, yListListL = myMidpointList_Integration3(f, xList[:indMid+1:], yListList[:,:indMid+1:] )
    SR, xListR, yListListR = myMidpointList_Integration3(f, xList[indMid::],   yListList[:,indMid::] )
    Sn = SL+SR
    err = max( np.abs(Sn-S) )
    # End Case
    xListNew = np.append( xListL, xListR[1::] )
    dxmin = min(dx(xListNew))
    if err <= tol or recursiveLim==1 or dxMin==None or dxmin<dxMin:
        count = 1
        yListListNew = np.append( yListListL, yListListR[:,1::] )
        return Sn, err, xListNew, yListListNew, count, dxmin
    # Iterative Case
    sL, sR = np.sum(SL), np.sum(SR)
    fac = sL/(sL+sR) if (sL+sR)!=0 else 0.5
    SL, errL, xListL, yListListL, countL, dxL = recursive_integration3(f, SL, xList=xListL, yListList=yListListL, tol=tol*fac, recursiveLim=recursiveLim-1, dxMin=dxMin)
    SR, errR, xListR, yListListR, countR, dxR = recursive_integration3(f, SR, xList=xListR, yListList=yListListR, tol=tol*(1-fac), recursiveLim=recursiveLim-1, dxMin=dxMin)
    Sn = SL+SR
    err = errL + errR
    count = countL+1 if countL>countR else countR+1 #countL+countR # countL+1 if countL>countR else countR+1
    xListNew = np.append( xListL, xListR[1::] )
    dxmin = dxL if dxL<dxR else dxR
    yListListNew = np.append( yListListL, yListListR[:,1::] )
    return Sn, err, xListNew, yListListNew, count, dxmin

if __name__ == '__main__':
    import doctest
    doctest.testmod()
    """
    x = []
    y = []
    value = myNumericalIntegration(x,y)
    """


import math
import pandas as pd
import numpy as np
import statistics
import matplotlib.pyplot as plt

a = 51749
b = 1352
m = 244944
x0 = 3

# Plots Histogram along side acutal distribution
def plotHistogram(sample, method):
    hist, bins, _ = plt.hist(sample, density=True, bins=100)

    plt.title('Sample Distribution for N = {}, {}'.format(len(sample), method))
    plt.show()

### Generates x_i's from given recurrence
def generateX(a, curr, N):
    x = []
    x.append(curr);
    for i in range (N-1):
        prev = x[len(x)-1]
        curr = (a*prev + b) % m
        x.append(curr)

    return x

### Generates u_i's from given recurrence
def generateU(x):
    ulist = []
    for i in x:
        ulist.append(i/m)
    return ulist

def LCG(N):
    curr = 3
    x = generateX(a, curr, N)
    ulist = generateU(x)

    plotHistogram(ulist, 'Linear Congruence Generator')

def generateVC(N):
    data = [i for i in range(N)]

    sample = []
    for t in data:
        binary = []
        n = t

        if n==0:
            binary.append(0)

        while n!=0:
            bit = n%2
            binary.append(bit)
            n = n//2

        curr = 0.00
        temp = 1.00/2.00
        for mult in binary:
            curr += mult*temp
            temp = temp/2

        sample.append(curr)

    df = pd.DataFrame()
    df['N'] = pd.Series([i for i in range(N)])
    df['Van der Corput(N)'] = pd.Series(sample)

    return df

def generateHalton(N):
    data = [i for i in range(N)]

    sampleX = []
    sampleY = []
    for t in data:
        binary = []
        ternary = []

        n = t
        if n==0:
            binary.append(0)

        while n!=0:
            bit = n%2
            binary.append(bit)
            n = n//2

        n = t
        if n==0:
            ternary.append(0)

        while n!=0:
            bit = n%3
            ternary.append(bit)
            n = n//3

        curr = 0.00
        temp = 1.00/2.00
        for mult in binary:
            curr += mult*temp
            temp = temp/2.00
        phi_2 = curr

        curr = 0.00
        temp = 1.00/3.00
        for mult in ternary:
            curr += mult*temp
            temp = temp/3.00
        phi_3 = curr

        sampleX.append(phi_2)
        sampleY.append(phi_3)

    return sampleX, sampleY

def partA():
    df1 = generateVC(25)
    df2 = generateVC(1000)

    print("The first 25 values of the Van der Corput sequence are: ")
    print(df1)

    xAxis = []
    yAxis = []

    for i in range(len(df2['Van der Corput(N)'])-1):
        xAxis.append(df2['Van der Corput(N)'][i])
        yAxis.append(df2['Van der Corput(N)'][i+1])

    plt.plot(xAxis, yAxis)
    plt.xlabel('x(i)')
    plt.ylabel('x(i+1)')
    plt.show()

    df3 = generateVC(100)
    plotHistogram(df3['Van der Corput(N)'], 'Van der Corput')
    LCG(100)

    df4 = generateVC(100000)
    plotHistogram(df4['Van der Corput(N)'], 'Van der Corput')
    LCG(100000)

def partB():
    H_sample_X, H_sample_Y = generateHalton(100)

    plt.plot(H_sample_X, H_sample_Y)
    plt.title('Halton Sequence Actual')
    plt.xlabel('phi_2(i)')
    plt.ylabel('phi_3(i)')
    plt.show()

    plt.scatter(H_sample_X, H_sample_Y)
    plt.title('Halton Sequence Scattered')
    plt.xlabel('phi_2(i)')
    plt.ylabel('phi_3(i)')
    plt.show()

    H_sample_X, H_sample_Y = generateHalton(10000)

    plt.plot(H_sample_X, H_sample_Y)
    plt.title('Halton Sequence Actual')
    plt.xlabel('phi_2(i)')
    plt.ylabel('phi_3(i)')
    plt.show()

    plt.scatter(H_sample_X, H_sample_Y)
    plt.title('Halton Sequence Scattered')
    plt.xlabel('phi_2(i)')
    plt.ylabel('phi_3(i)')
    plt.show()

def main():
    partA()
    partB()

if __name__ == '__main__':
    main()


__all__ = ['cholInvert']
import numpy as np

def cholInvert(A):
    """
    Return the inverse of a symmetric matrix using the Cholesky decomposition. The log-determinant is
    also returned

    Args:
        A : (N,N) matrix

    Returns:
        AInv: matrix inverse
        logDeterminant: logarithm of the determinant of the matrix
    """
    L = np.linalg.cholesky(A)
    LInv = np.linalg.inv(L)
    AInv = np.dot(LInv.T, LInv)
    logDeterminant = -2.0 * np.sum(np.log(np.diag(LInv)))   # Why the minus sign?
    return AInv, logDeterminant


import numpy as np

def gaussian_1d(height: float = 1, mu: float = 0, sigma: float = 2, offset: float = 0):
    """
    Return a parametrized 1D Gaussian function.

    Parameters:
        height: float
            Distance between the lowest and peak value of the Gaussian.
        mu: float
            Expected value of the Gaussian.
        sigma: float
            Width of the Gaussian.
        offset: float
            Shifts the Gaussian `up` or `down` i.e. the background signal.
    """
    return lambda x: offset + height * np.exp(-((x - mu) ** 2 / (2 * sigma ** 2)))

def gaussian_3d(
    height: float = 1,
    mu_z: float = 0,
    mu_y: float = 0,
    mu_x: float = 0,
    sigma_z: float = 2,
    sigma_y: float = 2,
    sigma_x: float = 2,
    offset: float = 0,
):
    """
    Return a parametrized 3D Gaussian function.

    Parameters:
        height: float
            Distance between the lowest and peak value of the Gaussian.
        mu_z: float
            Expected value of the Gaussian in Z dimension.
        mu_y: float
            Expected value of the Gaussian in Y dimension.
        mu_x: float
            Expected value of the Gaussian in X dimension.
        sigma_z: float
            Width of the Gaussian in Z dimension.
        sigma_y: float
            Width of the Gaussian in Y dimension.
        sigma_x: float
            Width of the Gaussian in X dimension.
        offset: float
            Shifts the Gaussian `up` or `down` i.e. the background signal.
    """
    return lambda z, y, x: offset + height * np.exp(
        -(
            ((z - mu_z) ** 2 / (2 * sigma_z ** 2))
            + ((y - mu_y) ** 2 / (2 * sigma_y ** 2))
            + ((x - mu_x) ** 2 / (2 * sigma_x ** 2))
        )
    )


from statistics import mean
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import style
import random

style.use('fivethirtyeight')
xs = np.array([1,2,3,4,5,6], dtype= np.float64)
ys = np.array([5,4,6,5,6,7], dtype= np.float64)

def create_dataset(hm, varinace, step=2, correlation= False):
	val = 1
	ys = []
	for i in range(hm):
		y = val + random.randrange(-varinace, varinace)
		ys.append(y)
		if correlation and correlation == 'pos':
			val += step
		elif correlation and correlation =='neg':
			val -= step

	xs = [i for i in range (len(ys))]
	return np.array(xs, dtype= np.float64), np.array(ys, dtype= np.float64)

def best_fit_slope_and_intercept(xs, ys):
	m = ( ((mean(xs) * mean(ys)) - mean(xs * ys)) /
		((mean(xs)**2) - mean(xs * xs)) )
	b = mean(ys) - m * mean(xs)
	return m,b

def squared_error(ys_orig, ys_line):
	return sum((ys_line - ys_orig) **2)

def coeffiecent_of_determination(ys_orig, ys_line):
	y_mean_line = [mean(ys_orig) for y in ys_orig]
	squared_error_regr = squared_error(ys_orig, ys_line)
	squared_error_y_mean = squared_error(ys_orig, y_mean_line)
	return 1- (squared_error_regr / squared_error_y_mean)

xs, ys = create_dataset(40, 5, 2, correlation='pos')

m,b = best_fit_slope_and_intercept(xs, ys)

regression_line = [(m*x) + b for x in xs]

predict_x = 8
predict_y = (m*predict_x) + b
r_squared = coeffiecent_of_determination(ys, regression_line)
print(r_squared)

plt.scatter(xs,ys)
plt.plot(xs, regression_line)
plt.scatter(predict_x, predict_y, s = 100 ,color ='g')
plt.show()