import numpy as np
from tuples import Tuple
from math import sin, cos

class Matrix:
    def __init__(self, matrix):
        self.matrix = np.array(matrix)
        self.is_invertible = None

    def __eq__(self, other):
        return np.allclose(self.matrix, other.matrix, rtol=0.0001)

    def __mul__(self, other):
        if type(other) is Matrix:
            result = np.matmul(self.matrix, other.matrix)
            return Matrix(result.tolist())
        else:
            result = self.matrix.dot(other.vec)
            return Tuple(*result.tolist())

    @staticmethod
    def identity(size):
        return Matrix(np.identity(size).tolist())

    def transpose(self):
        return Matrix(np.transpose(self.matrix).tolist())

    def determinant(self):
        result = np.linalg.det(self.matrix)
        self.is_invertible = result != 0
        return result

    def slow_determinant(self):
        if self.matrix.shape == (2, 2):
            result = self.matrix[0, 0] * self.matrix[1, 1] - self.matrix[0, 1] * self.matrix[1, 0]
        else:
            result = 0
            for ic, col in enumerate(self.matrix[0]):
                result += col * self.cofactor(0, ic)

        self.is_invertible = result != 0
        return result

    def submatrix(self, row, column):
        return Matrix(np.delete(np.delete(self.matrix, row, axis=0), column, axis=1))

    def minor(self, row, column):
        return self.submatrix(row, column).determinant()

    def cofactor(self, row, column):
        factor = 1 if (row + column) % 2 == 0 else -1
        return factor * self.minor(row, column)

    def slow_inverse(self):
        determinant = self.slow_determinant()
        if not self.is_invertible:
            return None

        new = np.empty(self.matrix.shape, dtype=float)
        for row in range(self.matrix.shape[0]):
            for col in range(self.matrix.shape[1]):
                new[col, row] = self.cofactor(row, col) / determinant

        return Matrix(new.tolist())

    def inverse(self):
        return Matrix(np.linalg.inv(self.matrix).tolist())

    @staticmethod
    def translation(x, y, z):
        ident = Matrix.identity(4)
        ident.matrix[0, 3] = x
        ident.matrix[1, 3] = y
        ident.matrix[2, 3] = z
        return ident

    @staticmethod
    def scaling(x, y, z):
        ident = Matrix.identity(4)
        ident.matrix[0, 0] = x
        ident.matrix[1, 1] = y
        ident.matrix[2, 2] = z
        return ident

    @staticmethod
    def rotation_x(radians):
        ident = Matrix.identity(4)
        ident.matrix[1, 1] = cos(radians)
        ident.matrix[1, 2] = -sin(radians)
        ident.matrix[2, 1] = sin(radians)
        ident.matrix[2, 2] = cos(radians)
        return ident

    @staticmethod
    def rotation_y(radians):
        ident = Matrix.identity(4)
        ident.matrix[0, 0] = cos(radians)
        ident.matrix[0, 2] = sin(radians)
        ident.matrix[2, 0] = -sin(radians)
        ident.matrix[2, 2] = cos(radians)
        return ident

    @staticmethod
    def rotation_z(radians):
        ident = Matrix.identity(4)
        ident.matrix[0, 0] = cos(radians)
        ident.matrix[0, 1] = -sin(radians)
        ident.matrix[1, 0] = sin(radians)
        ident.matrix[1, 1] = cos(radians)
        return ident

    @staticmethod
    def shearing(xy, xz, yx, yz, zx, zy):
        ident = Matrix.identity(4)
        ident.matrix[0, 1] = xy
        ident.matrix[0, 2] = xz
        ident.matrix[1, 0] = yx
        ident.matrix[1, 2] = yz
        ident.matrix[2, 0] = zx
        ident.matrix[2, 1] = zy
        return ident


from math import log

from ..app import app

import sys
import numpy as np
from scipy.integrate import odeint

#based on https://scipython.com/blog/the-double-pendulum/

# The gravitational acceleration (m.s-2).
g = 9.81

def deriv(y, t, L1, L2, m1, m2):
    """Return the first derivatives of y = theta1, z1, theta2, z2."""
    theta1, z1, theta2, z2 = y

    c, s = np.cos(theta1-theta2), np.sin(theta1-theta2)

    theta1dot = z1
    z1dot = (m2*g*np.sin(theta2)*c - m2*s*(L1*z1**2*c + L2*z2**2) -
            (m1+m2)*g*np.sin(theta1)) / L1 / (m1 + m2*s**2)
    theta2dot = z2
    z2dot = ((m1+m2)*(L1*z1**2*s - g*np.sin(theta2) + g*np.sin(theta1)*c) +
            m2*L2*z2**2*s*c) / L2 / (m1 + m2*s**2)
    return theta1dot, z1dot, theta2dot, z2dot

def solve(L1, L2, m1, m2, tmax, dt, y0):
    t = np.arange(0, tmax+dt, dt)

    # Do the numerical integration of the equations of motion
    y = odeint(deriv, y0, t, args=(L1, L2, m1, m2))
    theta1, theta2 = y[:,0], y[:,2]

    # Convert to Cartesian coordinates of the two bob positions.
    x1 = L1 * np.sin(theta1)
    y1 = -L1 * np.cos(theta1)
    x2 = x1 + L2 * np.sin(theta2)
    y2 = y1 - L2 * np.cos(theta2)

    return theta1, theta2, x1, y1, x2, y2

@app.task
def simulate_pendulum_instance(L1, L2, m1, m2, tmax, dt, theta1_init, theta2_init):

    y0 = np.array([
        theta1_init,
        0.0,
        theta2_init,
        0.0
    ])
    #vracam i thete da znam za cega su ovi rezultati (da ih posle mogu
    #pisati u fajl)
    return theta1_init, theta2_init, solve(L1, L2, m1, m2, tmax, dt, y0)


import numpy as np

def left_most_point(points):
    '''
    Takes in the array of points, and returns the point
    furthest on the left side.
    '''
    leftMost = np.array([])
    for point in points:
        if leftMost.size == 0 or point[0] < leftMost[0]:
            leftMost = point
    return leftMost

def lr_most_point(points):
    '''
    Takes in the array of points, and returns the points
    furthest on the left and right sides as a tuple.
    '''
    leftMost  = np.array([])
    rightMost = np.array([])
    for point in points:
        if leftMost.size == 0 or point[0] < leftMost[0]:
            leftMost = point
        if rightMost.size == 0 or point[0] > rightMost[0]:
            rightMost = point
    return (leftMost, rightMost)

def dist(A, B, C):
    '''
    Returns the shortest distance between the point C,
    and the line segment connected by A and B.
    '''
    P = A - C
    Q = A - B
    magQ = np.linalg.norm(Q)

    proj = (np.dot(P, Q) / (magQ*magQ)) * Q
    return np.linalg.norm(P - proj)

def sort_by_x(points):
    '''
    Sort the array by x coordinate, or by y coordinate in case of tie.
    Returns a array of data points. (Does not modify the array)
    '''
    indices = np.lexsort((points[:,1], points[:,0]))
    return points[indices]

def cross(a, b, o):
    return (a[0] - o[0]) * (b[1] - o[1]) - (a[1]  - o[1]) * (b[0] - o[0])


from typing import List, Optional, Union

from numpy import ndarray, array, vstack, zeros, exp
from numpy.linalg import inv

from optmlstat.functions.function_base import FunctionBase

class GaussianBasisFunction(FunctionBase):
    """
    Gaussian basis function.
    We do not multiply the scaling term as in Gaussian probability density function (PDF)
    since it can be dealt with coefficients.
    """

    def __init__(
        self,
        covariance_list: Union[List[Union[ndarray, float, int]], ndarray, float, int],
        mean_array_2d: Optional[ndarray] = None,
        inverse: bool = False,
    ) -> None:
        """
        Parameters
        ----------
        covariance_list:
          List of covariance matrices
        inverse:
          If inverse is True, covariance_list is interpreted as a list of the inverses of the covariance matrices.
        """
        if isinstance(covariance_list, int):
            covariance_list = float(covariance_list)

        if isinstance(covariance_list, float):
            covariance_list = array([[covariance_list]])

        if isinstance(covariance_list, ndarray):
            covariance_list = [covariance_list]

        assert len(covariance_list) > 0

        for idx, covariance in enumerate(covariance_list):
            if isinstance(covariance, (float, int)):
                covariance_list[idx] = array([[float(covariance)]])

        assert covariance_list[0].ndim == 2, covariance_list[0].ndim
        assert covariance_list[0].shape[0] == covariance_list[0].shape[1], covariance_list[0].shape
        for covariance in covariance_list:
            assert covariance_list[0].shape == covariance.shape, (covariance_list[0].shape, covariance.shape)

        self.covariance_list: List[ndarray] = covariance_list

        self.mean_array_2d: ndarray = mean_array_2d

        if self.mean_array_2d is None:
            self.mean_array_2d = zeros((self.num_outputs, self.num_inputs))

        assert self.mean_array_2d.shape == (self.num_outputs, self.num_inputs)

        self.inverse_covariance_list: List[ndarray] = self.covariance_list
        if not inverse:
            self.inverse_covariance_list = [inv(covariance) for covariance in self.covariance_list]

    @property
    def num_inputs(self) -> Optional[int]:
        return self.covariance_list[0].shape[0]

    @property
    def num_outputs(self) -> Optional[int]:
        return len(self.covariance_list)

    @property
    def is_affine(self) -> Optional[bool]:
        return False

    @property
    def is_strictly_convex(self) -> Optional[bool]:
        return False

    @property
    def is_convex(self) -> Optional[bool]:
        return False

    def get_y_values_2d(self, x_array_2d: ndarray) -> ndarray:
        y_array_1d_list: List[ndarray] = list()

        for idx, inverse_covariance in enumerate(self.inverse_covariance_list):
            x_array_2d_ = x_array_2d - self.mean_array_2d[idx, :]
            y_array_1d_list.append(exp(-0.5 * (x_array_2d_.dot(inverse_covariance) * x_array_2d_).sum(axis=1)))

        return vstack(y_array_1d_list).T


"""
Polynomial Regression and Pipelines.

What do we do when a linear model is not the best fit for our data?
Let’s look into another type of regression model: the polynomial regression.
We Transform our data into a polynomial, then use linear regression to fit the parameter.
Then we will discuss pipelines.
Pipelines are a way to simplify your code.
Polynomial regression is a special case of the general linear regression.
This method is beneficial for describing curvilinear relationships.
What is a curvilinear relationship?
It’s what you get by squaring or setting higher-order terms of the predictor variables in the model, transforming the data.
The model can be quadratic, which means that the predictor variable in the model is squared.

"""

# First we import all the modules we need.
import pandas as pd
import numpy as np
from sklearn.preprocessing import PolynomialFeatures
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline

# get data file from url
# dataFile = 'https://archive.ics.uci.edu/ml/machine-learning-databases/autos/imports-85.data'

# or just from a local repository
data_file = './data/imports-85.data'

# defining headers
headers = ['symboling', 'normalized-losses', 'make', 'fuel-type', 'aspiration', 'num-of-doors', 'body-style', 'drive-wheels', 'engine-location', 'wheel-base', 'length', 'width', 'height', 'curb-weight', 'engine-type', 'num-of-cylinders', 'engine-size', 'fuel-system', 'bore', 'stroke', 'compression-ratio', 'horsepower', 'peak-rpm', 'city-mpg', 'highway-mpg', 'price']

# read file into pandas dataframe
df = pd.read_csv(data_file)

# set the dataframe header
df.columns = headers

# CALCULATE A POLYNOMIAL OF 3RD ORDER
print("\n\n\n# CALCULATE A POLYNOMIAL OF 3RD ORDER\n")

x = df["price"]
y = df["engine-size"]

# create a Polynomial of 3rd order
f = np.polyfit(x,y,3)

#
p = np.poly1d(f)

# print the polynomial
print(p)
"""
OUTPUT
            3             2
-1.759e-12 x + 1.655e-07 x + 0.0002803 x + 94.56
"""

# POLYNOMIAL REGRESSION WITH THE ONE DIMENSION
print("\n\n\n# POLYNOMIAL REGRESSION WITH THE ONE DIMENSION\n")

# We use the "preprocessing" library in sci-kit-learn, to create a polynomial feature object.
pr = PolynomialFeatures(degree=2, include_bias=False)

# Then we transform the features into a polynomial feature with the “fit_transform” method.
x_polly = pr.fit_transform(df[['horsepower', 'curb-weight']])

print(x_polly)
"""
OUTPUT
[[1.1100000e+02 2.5480000e+03 1.2321000e+04 2.8282800e+05 6.4923040e+06]
 [1.5400000e+02 2.8230000e+03 2.3716000e+04 4.3474200e+05 7.9693290e+06]
 [1.0200000e+02 2.3370000e+03 1.0404000e+04 2.3837400e+05 5.4615690e+06]
 ...
 [1.3400000e+02 3.0120000e+03 1.7956000e+04 4.0360800e+05 9.0721440e+06]
 [1.0600000e+02 3.2170000e+03 1.1236000e+04 3.4100200e+05 1.0349089e+07]
 [1.1400000e+02 3.0620000e+03 1.2996000e+04 3.4906800e+05 9.3758440e+06]]
"""

# PREPROCESSING
print("\n\n\n# PREPROCESSING\n")

# We can use the preprocessing module to simplify many tasks.

# We import “StandardScaler”
from sklearn.preprocessing import StandardScaler

# We train the object
SCALE = StandardScaler()

# fit the scale object;
SCALE.fit(df[['horsepower', 'highway-mpg']])

# then transform the data into a new dataframe on array “x_scale”.
x_scale = SCALE.transform(df[['horsepower', 'highway-mpg']])

print(x_scale)
"""
OUTPUT
[[ 0.19101743 -0.54779597]
 [ 1.24419463 -0.69311506]
 [-0.029415   -0.11183871]
 [ 0.2889874  -1.27439141]
 [ 0.16652494 -0.83843415]
            ...
 [ 0.75434477 -1.12907233]
 [ 0.06855497 -0.54779597]
 [ 0.26449491 -0.83843415]]
"""

# PIPELINES
print("\n\n\n# PIPELINES\n")

"""
There are more normalization methods available in the preprocessing library, as well as other transformations.
We can simplify our code by using a pipeline library.
There are many steps to getting a prediction, for example, Normalization, Polynomial transform, and Linear regression.
We simplify the process using a pipeline.
Pipelines sequentially perform a series of transformation.
The last step carries out a prediction.
"""

# We create a list of tuples, the first element in the tuple contains the name of the estimator: model. The second element contain model constructor.
Input = [
    ('scale', StandardScaler()),
    ('polynomial', PolynomialFeatures(degree=2)),
    ('model', LinearRegression)
]

# We input the list in the pipeline constructor.
Pipe = Pipeline(Input)

print(Pipe)

# We now have a pipeline object, We can train the pipeline by applying the train method to the Pipeline object.
# Pipe.train(df['horsepower', 'curb-weight', 'engine-size', 'highway-mpg'], y)

# We can also produce a prediction as well.
# yhat = Pipe.predict(df['horsepower', 'curb-weight', 'engine-size', 'highway-mpg'])


import numpy as np
from sklearn import datasets
import random

def kmeans(x_train, k):
    changed = True
    center = np.array(random.sample(x_train, k))
    dist = np.zeros((150, k))
    # Loop until the center won't change
    while changed:
        for i in range(k):
            C = np.tile(center[i,:], (150,1))
            dist[:,i]=np.sqrt(np.sum((x_train-C)**2,axis=1))

        labels = np.argmin(dist, axis=1)
        newcenter = np.zeros((k,4))
        for i in range(k):
            newcenter[i,:] =  np.average(x_train[labels==i,:], axis=0)
        changed = (newcenter == center).all() == False
        center = newcenter
    # Calculate the number of each group
    num_of_each_type = np.array([x_train[labels == i,:].shape[0] for i in range(k)])
    # Calcultate the total distance of the classification
    totalDistance = 0
    for i in range(k):
        #print sum(labels==i)
        newCenter = np.tile(center[i,:], (sum(labels==i),1))
        #print "dist"+str(np.sum((x_train[labels==i,:]-newCenter)**2,axis=1))
        totalDistance += np.sum((np.sum((x_train[labels==i,:]-newCenter)**2,axis=1)))
    # return type labels and total distance and number of each type
    return labels, np.sqrt(totalDistance), num_of_each_type

# Loading data
iris = datasets.load_iris()
x_train = iris.data
y_train = iris.target

k = 3
n = 10 # num of loops
labels = {}
num = {}
totalDist = np.zeros(n)
minDist = 10**6
minIndex = 0
for j in range(n):
    labels[j], totalDist[j], num[j] = kmeans(x_train, k)
    if totalDist[j] < minDist:
        minIndex = j
        minDist = totalDist[j]

# Print the classification results
print "Number of samples in each claster is " + str(num[minIndex]) + "."
#print "\t\t %-15s %-15s %-15s" %("setosa", "versicolor", "virginica")
print "\t\t %-15s %-15s %-15s" %("type0", "type1", "type2")
for i in range(k):
    a = sum((y_train[labels[minIndex]==i])==0)*1.0/(x_train[labels[minIndex]==i,:]).shape[0]
    b = sum((y_train[labels[minIndex]==i])==1)*1.0/(x_train[labels[minIndex]==i,:]).shape[0]
    c = sum((y_train[labels[minIndex]==i])==2)*1.0/(x_train[labels[minIndex]==i,:]).shape[0]
    print "%8s\t %-15.4f %-15.4f %-15.4f" %("cluster"+str(i), a, b, c)


# A piece of wire 17 ft. long is cut into two pieces. One piece is made into a circle and the other piece is made into a square. Let the piece of length x be formed into a circle.
#
# Allow x to equal 0 or 17, so all the wire is used for the square or the circle.
# Where should the cut be made to make the sum of the areas maximum?
# (Hint: Remember to use the endpoints of a domain when looking for absolute maxima and minima.)

import math
from sympy import Derivative, symbols, solve, sympify, pi
from collections import namedtuple

Pieces = namedtuple( 'Pieces', [ 'circumference', 'side'] )

def cut_wire( l ):
	# Side, Circumference
	s, c = symbols( 's, c' )
	# V = s^2 ( square ) + pi * ( c / 2pi )^2
	A = ( s / 4 ) ** 2 + c**2 / ( 4 * math.pi )

	# c + s = l
	L = c + s - l

	# Solve for c in terms of s
	sC = solve( L, s )[ 0 ]

	# A1 = area in terms of a single variable
	A1 = A.subs( s, sC )

	# Now single variable, take The derivitave with respect to c.
	dA = Derivative( A1, c ).doit()
	slns = solve( dA )

	min_c = slns[ 0 ]
	min_s = l - min_c

	return Pieces( circumference = min_c, side = min_s )

def area_sqare( s ):
	return s**2

def area_circle( c ):
	return c**2 / ( 4 * math.pi )

if __name__ == '__main__':

	try:
		l = float( input( 'Enter the length of the wire to cut: ') )
	except ValueError:
		print( 'Error parsing values' )
	else:
		print( 'wire length: {0}\n'.format( l ) )

		pieces = cut_wire( l )
		areaCircle = area_circle( pieces.circumference )
		areaSqare = area_sqare( pieces.side )

		print( 'Mimimum Area: {0}'.format( areaCircle + areaSqare ) )
		print( 'Circle Area: {0}'.format( areaCircle ) )
		print( 'Square Area: {0}'.format( areaSqare ) )

		print( 'Maximum Area: Circle = {0} ({1} len), Square = 0, (0 len)'.format( area_circle( l ), l ) )


''' mbinary
#########################################################################
# File : fft.py
# Author: mbinary
# Mail: zhuheqin1@gmail.com
# Blog: https://mbinary.xyz
# Github: https://github.com/mbinary
# Created Time: 2019-06-11  12:48
# Description:
#########################################################################
'''
import numpy as np

def _fft_n2(a, invert):
    '''O(n^2)'''
    N = len(a)
    w = np.arange(N)
    i = 2j if invert else -2j
    m = w.reshape((N, 1)) * w
    W = np.exp(m * i * np.pi / N)
    return np.concatenate(np.dot(W, a.reshape((N,
                                               1))))  # important, cannot use *

def _fft(a, invert=False):
    '''recursion version'''
    N = len(a)
    if N == 1:
        return [a[0]]
    elif N & (N - 1) == 0:  # O(nlogn),  2^k
        even = _fft(a[::2], invert)
        odd = _fft(a[1::2], invert)
        i = 2j if invert else -2j
        factor = np.exp(i * np.pi * np.arange(N // 2) / N)
        prod = factor * odd
        return np.concatenate([even + prod, even - prod])
    else:
        return _fft_n2(a, invert)

def _fft2(a, invert=False):
    ''' iteration version'''

    def rev(x):
        ret = 0
        for i in range(r):
            ret <<= 1
            if x & 1:
                ret += 1
            x >>= 1
        return ret

    N = len(a)
    if N & (N - 1) == 0:  # O(nlogn),  2^k
        r = int(np.log(N))
        c = np.array(a, dtype='complex')
        i = 2j if invert else -2j
        w = np.exp(i * np.pi / N)
        for h in range(r - 1, -1, -1):
            p = 2**h
            z = w**(N / p / 2)
            for k in range(N):
                if k % p == k % (2 * p):
                    c[k], c[k + p] = c[k] + c[k + p], c[k] * z**(k % p)

        return np.asarray([c[rev(i)] for i in range(N)])
    else:  # O(n^2)
        return _fft_n2(a, invert)

def fft(a):
    '''fourier[a]'''
    n = len(a)
    if n == 0:
        raise Exception("[Error]: Invalid length: 0")
    return _fft(a)

def ifft(a):
    '''invert fourier[a]'''
    n = len(a)
    if n == 0:
        raise Exception("[Error]: Invalid length: 0")
    return _fft(a, True) / n

def fft2(arr):
    return np.apply_along_axis(fft, 0,
                               np.apply_along_axis(fft, 1, np.asarray(arr)))

def ifft2(arr):
    return np.apply_along_axis(ifft, 0,
                               np.apply_along_axis(ifft, 1, np.asarray(arr)))

def test(n=128):
    print('\nsequence length:', n)
    print('fft')
    li = np.random.random(n)
    print(np.allclose(fft(li), np.fft.fft(li)))

    print('ifft')
    li = np.random.random(n)
    print(np.allclose(ifft(li), np.fft.ifft(li)))

    print('fft2')
    li = np.random.random(n * n).reshape((n, n))
    print(np.allclose(fft2(li), np.fft.fft2(li)))

    print('ifft2')
    li = np.random.random(n * n).reshape((n, n))
    print(np.allclose(ifft2(li), np.fft.ifft2(li)))

if __name__ == '__main__':
    for i in range(1, 4):
        test(i * 16)


import numpy as np

A = np.array([[5, 5], [2, 8]])
#A = np.array([[6,-1], [2,3]])
#A = np.array([[3,2,4], [2,0,2], [4,2,3]])
#A = np.array([[12,-51,4], [6,167,-68], [-4,24,-41]])

def power_iteration(A, num_simulations):
  b_k = np.random.rand(A.shape[1])

  for _ in range(num_simulations):
    # calculate the matrix-by-vector product Ab
    b_k1 = np.dot(A, b_k)

    # calculate the norm
    b_k1_norm = np.linalg.norm(b_k1)

    # re normalize the vector
    b_k = b_k1 / b_k1_norm

  return b_k

print("%s\n"%power_iteration(A, 32))

w, v = np.linalg.eig(A)

print(w)
print(v)


#########################
##                     ##
## Irving Gomez Mendez ##
##  February 12, 2021  ##
##                     ##
#########################

import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import t

dat = np.array([[100, 89, 89, 92, 78, 101, 90, 82, 95, 108, 111, 112,
    125, 123, 100, 102, 112, 112, 100, 107, 103, 129, 118, 149],
    [709, 740, 586, 806, 694, 760, 664, 509, 619, 652, 772, 896,
    742, 831, 667, 772, 838, 679, 889, 690, 698, 860, 799, 882]]).T
dat = dat/10

n = dat.shape[0]
yy = dat[:,1]
XX = np.vstack([np.ones(n),dat[:,0]]).T
p = XX.shape[1]

# We compute the coeff.
# Calculating the inverse
beta_0, beta_1 = np.linalg.inv(XX.T @ XX) @ XX.T @ yy
beta_0
beta_1

# Using solve
beta_0, beta_1 = np.linalg.solve(XX.T @ XX, XX.T @ yy)
beta_0
beta_1

# Using the least squares procedure
beta_0, beta_1 = np.linalg.lstsq(XX, yy, rcond=None)[0]
beta_0
beta_1

# Calculate the SSE
hat_yy = XX @ np.array([beta_0, beta_1])
SSE = sum((hat_yy-yy)**2)
SSE

# Using the least squares procesure
SSE = np.linalg.lstsq(XX, yy, rcond=None)[1]
SSE

# We get confidence interval
alpha = 0.05
x0 = np.linspace(7, 15, 50)
X0 = np.array([np.ones(len(x0)),x0]).T

aux_t_conf = np.sqrt(SSE/(n-p)*(np.diag(X0 @ np.linalg.inv(XX.T @ XX) @ X0.T)))
yy0_hat = X0 @ np.array([beta_0, beta_1])
upp_conf = yy0_hat+t.ppf(1-alpha/2,n-p)*aux_t_conf
low_conf = yy0_hat-t.ppf(1-alpha/2,n-p)*aux_t_conf

# We get prediction interval
aux_t_pred = np.sqrt(SSE/(n-p)*(1+np.diag(X0 @ np.linalg.inv(XX.T @ XX) @ X0.T)))
yy0_hat = X0 @ np.array([beta_0, beta_1])
upp_pred = yy0_hat+t.ppf(1-alpha/2,n-p)*aux_t_pred
low_pred = yy0_hat-t.ppf(1-alpha/2,n-p)*aux_t_pred

plt.figure(figsize=(10,5))
plt.plot(dat[:,0], yy, 'o', label='Original data', markersize=5)
plt.plot(x0, beta_0+beta_1*x0, 'r', label='Fitted line')
plt.fill_between(x0, low_pred, upp_pred, facecolor='green', alpha=0.5, label='Prediction interval')
plt.fill_between(x0, low_conf, upp_conf, facecolor='yellow', alpha=0.5, label='Confidence interval')
plt.legend(loc='upper left')


import numpy as np

def sigmoid_derivative(x):
    sig = sigmoid(x)
    return sig * (1 - sig)

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


import numpy as np

'''
    This python code calculates the Min-Max Normalization

    Min-max normalization is one of the most common ways to normalize data.
    For every feature, the minimum value of that feature gets transformed into a 0,
    the maximum value gets transformed into a 1, and every other value
    gets transformed into a decimal between 0 and 1.

    norm = [(x - X.min)/(X.max - X.min)]*(new_max - new_min) + new_min

'''

# Input the data array
data = [200, 400, 800, 1000, 2000]

# Setting the new min and new max
nmin = 0
nmax = 10

# Putting the data in new numpy array
nparray = np.array(data)

#------------- Normalizing the data --------------------------#
# Difference between max nparray value and min nparray value
diff = nparray.max() - nparray.min()
npmin = nparray.min()

ndata = ( (nparray - npmin) / (diff) ) * (nmax-nmin) + nmin

print(ndata)


import numpy as np
import time

def F(n):
    return np.fromfunction(lambda i, j: np.exp(-2.j*np.pi*i*j/n), (n, n))

def dft(x):
    n = len(x)
    return F(n) @ x

def idft(y):
    n = len(y)
    return np.conj(F(n) @ np.conj(y)) / n

def fft(x):
    n = len(x)
    if n == 1:
        return x
    else:
        even = fft(x[0::2])
        odd = fft(x[1::2])
        T = [np.exp(-2.j*np.pi*k/n)*odd[k] for k in range(n//2)]
        return [even[k] + T[k] for k in range(n//2)] + \
               [even[k] - T[k] for k in range(n//2)]

x = np.random.rand(4)

start_time = time.time()
y = dft(x)
end_time = time.time()
print("DFT time: " + str(end_time-start_time) + "ns")
#print(y)

print()

start_time = time.time()
y = fft(x)
end_time = time.time()
print("FFT time: " + str(end_time-start_time) + "ns")
#print(y)

print()

start_time = time.time()
y = np.fft.fft(x)
end_time = time.time()
print("Library FFT time: " + str(end_time-start_time) + "ns")
#print(y)

print("\n")

#print(x)
#print(idft(y))
#print(np.fft.ifft(np.fft.fft(x)))


import numpy as np

def min_max_scale(x):
    cols = x.shape[1]
    min_record = []
    max_record = []

    for col in range(cols):
        min_val= np.min(x[:,col])
        max_val = np.max(x[:,col])
        x[:,col] = (x[:,col] - min_val)/(max_val - min_val)
        min_record.append(min_val)
        max_record.append(max_val)

    return x, min_record,max_record

def zero_score_scale(x):
    cols = x.shape[1]
    for col in range(cols):
        x[:,col] = (x[:,col]-np.mean(x[:,col]))/(np.std(x[:,col]))

    return x

def min_max_recover(X, min_vec, max_vec):
    cols = X.shape[1]
    for col in range(cols):
        X[:,col] = X[:,col]*(max_vec[col]-min_vec[col])+min_vec[col]
    return X

NORMALIZERS = {'min_max':min_max_scale,
                'zero_score':zero_score_scale}

RECOVER = {'min_max':min_max_recover}


import numpy as np

"""
  Compute Pascal triangle up to size n
  Returns: Numpy matrix with binomial coefficients
  - Matrix represents linear map P(X) \mapsto P(X+1) in the canonical basis
  - Convention is that first columns are the image of monomials of higher degree
"""
def pascal(n):
    # Initialize diagonal and first row
    mat       = np.identity(n, dtype=int)
    mat[:, 0] = 1
    # Loop using the standard binomial recurrence
    for i in range(2,n):
        mat[i, 1:-1] = mat[i-1, 0:-2] + mat[i-1, 1:-1]
    # Satisfy indexing conventions
    mat = np.flip(mat, axis=0)
    mat = mat.transpose()
    mat = np.flip(mat, axis=0)
    return mat

"""
 If | P  (X) = \sum c_i X^i = \sum d_i (X-a)^i
    | P_a(X) = \sum d_i X^i = P(X+a)
Then:
    taylor_expand computes P_a = Taylor expansion of P at a
    which is nothing by the linear map
    P(X) \mapsto P_a(X) = P(X+a)
    X^n \mapsto \sum (n,k) a^k X^{n-k}
 """
def taylor_expand(p, a, pascal_triangle=None):
    #
    degree = len(p)-1
    if pascal_triangle is None:
        pascal_triangle = pascal(degree+1)
    exponents = np.array( range(0, degree+1, 1) )
    powers    = a**exponents
    diag      = np.diag( powers )
    diag_inv  = np.diag( 1/powers )
    weighted_pascal = diag.dot( pascal_triangle )
    weighted_pascal = weighted_pascal.dot( diag_inv )
    return weighted_pascal.dot(p)


import numpy as np

def sigmoid(z):
    """
        Applies logistic function to value.

        Parameter
        ---------
        z : int, array_like
            Single integer or array of values.

        Returns
        -------
        : int, array_like
            Single integer or array of values with logistic function applied.
    """

    return 1 / (1 + np.exp(-z))


"""
2.3 numpy 基础运算
"""

import numpy as np

a = np.array([10,20,30,40])
b = np.arange(4)

print('矩阵a\n',a,'\n矩阵b\n',b)

# 减法
c = a - b
print('矩阵相减:a-b\n',c)

# 加法
c = a + b
print('矩阵相加:a+b\n',c)

# 乘方
c = b ** 2
print('对矩阵b每个元素求平方\n',c)

# sin
c = 10 * np.sin(a)
print('对矩阵a的每个元素求sin并乘以10\n',c)

# cos
c = 10 * np.cos(a)
print('对矩阵a的每个元素求cos并乘以10\n',c)

# 输出小于3的列表
print('矩阵b\n',b)
print('矩阵b中元素小于3的情况\n',b<3)
print('矩阵b中元素等于3的情况\n',b==3) # 等于3的情况

"""
矩阵运算
"""
a = np.array([[1,1],[0,1]])
b = np.arange(4).reshape((2,2))

print('矩阵a\n',a)
print('矩阵b\n',b)

# 元素逐个相乘
c = a*b
print('矩阵元素对应位置逐个相乘\n',c)

# 矩阵相乘
c = np.dot(a,b)
print('矩阵相乘\n',c)

c = a.dot(b)
print('矩阵相乘\n',c)

"""
生成随机矩阵
"""
a = np.random.random((2,4))
print('随机矩阵',a)

# 输出随机数组的和
print('随机矩阵的和',np.sum(a))
# 输出随机数组的最小值
print('随机矩阵的最小值',np.min(a))
# 输出随机数组的最大值
print('随机矩阵的最大值',np.max(a))

print('对随机矩阵的每列求和',np.sum(a,axis=1))
print('对随机矩阵的每行求最小值',np.min(a,axis=0))
print('对随机矩阵的每行求最大值',np.max(a,axis=1))


import numpy as np
from numpy.linalg import svd

def nullspace(A, atol=1e-13, rtol=0):
    A = np.atleast_2d(A)
    u, s, vh = svd(A)
    tol = max(atol, rtol * s[0])
    nnz = (s >= tol).sum()
    ns = vh[nnz:].conj().T
    return ns

def mkdata(N, noisy=None):
    '''
    MKDATA Generate data set.
    INPUT:  N:     number of samples.
            noisy: if or not add noise to y.

    OUTPUT: X: sample features, P-by-N matrix.
            y: sample labels, 1-by-N row vector.
            w: target function parameters, (P+1)-by-1 column vector.
    '''
    data_range = np.array([-1, 1])
    dim = 2

    X = np.random.random((dim, N)) * (data_range[1] - data_range[0]) + data_range[0]
    while True:
        Xsample = np.vstack((np.ones((1, dim)), np.random.random((dim, dim)) * (data_range[1]-data_range[0]) + data_range[0]))
        w = nullspace(Xsample.T)
        a =  np.vstack((np.ones((1, N)), X))
        y = np.sign(np.matmul(w.T, np.vstack((np.ones((1, N)), X))))
        if np.all(y) and np.unique(y).shape[0] > 1:
            break
    if noisy:
        idx = np.random.choice(N, N//10)
        y[0, idx] = -y[0, idx]

    return X, y, w


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

#Variables iniciales
epsilon = 0.001
r_inicial = 3.1
x0_inicial = 0.2
delta_x0 = 0.05
delta_r = 0.001
M = 1000
N = 20

#Resta con valor absoluto dos listas
def resta(x,y):
    lista_resta = list(map(operator.sub, x, y))
    return list(map(abs, lista_resta))

#Función logistica que define el sistema
def logistica(x, r):
    return r*x*(1-x)

#Calcula n términos de la sucesión de la órbita
#de una función f por un punto x0
def orbita(x0, f, n, r):
    orbita=[]
    x = x0
    for i in range(n):
        x = f(x, r)
        orbita.append(x)
    return orbita

#Calcula el conunto límite de puntos atractores dado un x0
def calculaV0(x0, r):
    orb = orbita(x0, logistica, M, r)
    posiblesk = [i for i in range(M-N, M)]
    #Guardamos en V0 los valores k de los limites
    V0 = []
    #Guardamos el valor de la orbita del limite
    V0orb = []
    #En equivalentesk guardamos los valores de las que ya
    #tenemos un representante de ese limite
    equivalentesk = []
    #Recorremos los candidatos
    for k in reversed(posiblesk) :
        #Si no es equivalente a uno de los valores limite encontrados
        if k not in equivalentesk :
            #Suponemos que no será limite
            kbueno = False
            for i in range(k) :
                #Recorremos los puntos que quedan
                if i not in equivalentesk and abs(orb[k] - orb[k-i-1]) < epsilon:
                    #Hay algún punto cercano a él, luego k es limite
                    kbueno = True
                    #Ademas, el k-i-1 es equivalente a k pues
                    #los valores de la orbita son muy cercanos
                    equivalentesk.append(k-i-1)
            if kbueno :
                #Como es valor limite, lo añadimos a las listas
                V0.append(k)
                V0orb.append(orb[k])
            equivalentesk.append(k)
    return V0, V0orb

#Calcula el error cometido al aproximar los valores limites con V0
def calculaError(V0orb, r) :
    errores = []
    V0orb.sort()
    Vaux = V0orb.copy()
    for j in range(0, 10):
        for i in range(len(V0)):
            Vaux[i] = logistica(Vaux[i], r)
        Vaux.sort()
        #Tomamos el máximo de las diferencias entre los elementos de V0orb
        #y de f^j(V0orb)
        errores.append(max(resta(V0orb, Vaux)))
    errores.sort()
    #Tomamos el percentil noventa de los diez errores anteriores
    return errores[8]

#Comprueba que el sistema sea estable para el x0 dado por parámetro
def estabilidad(x0, r):
    #Variamos ligeramente x0
    for x0 in np.arange(x0, x0 + delta_x0, delta_x0/10):
        V0aux, V0auxorb = calculaV0(x0, r)
        V0auxorb.sort()
        #Comprobamos que los valores del conjunto limite esten cercanos
        if max(resta(V0orb,V0auxorb)) > epsilon:
            return False
    return True

#Calcula el r a partir del cual hay conjuntos limite de al menos nelems
#y dibuja la grafica de bifurcaciones para r entre 2.5 y 4
def bifurcaciones(nelems, x0):
    V0s = np.array([])
    enc = False
    #Variamos r entre 2.5 y 4
    for r in np.arange(2.5 + delta_r, 4, delta_r):
        V0aux, V0auxorb = calculaV0(x0, r)
        V0auxorb.sort()
        V0auxorb = np.array(V0auxorb)
        np.append(V0s, V0auxorb)

        #dibujamos la grafica de las bifurcaciones del sistema
        for y in V0auxorb:
            plt.plot(r, y, ',k', alpha=0.25)

        #Vemos si es el primer r en alcanzar los nelems
        if len(V0aux) == nelems and enc == False:
            enc = True
            print('En r = ' + str(r) + ' V0 tiene ' + str(nelems) + ' elementos')

#MAIN
print('Fijamos x0 = 0.2')
V0, V0orb = calculaV0(x0_inicial, r_inicial)
print('Un conjunto atractor con r = 3.1 es: ' + str(V0orb))
print('su error es:', calculaError(V0orb, r_inicial))

if estabilidad(x0_inicial, r_inicial) == True:
    print('Es estable')
else :
    print('No es estable')

V0, V0orb = calculaV0(x0_inicial, 3.4)
print('Otro conjunto atractor con r = 3.4 es: ' + str(V0orb))
print('su error es:', calculaError(V0orb, 3.4))

if estabilidad(x0_inicial, 3.4) == True:
    print('Es estable')
else :
    print('No es estable')

bifurcaciones(8, x0_inicial)
plt.show()


r"""求日期组成的八位数(形如 yyyymmdd)是否为素数？
问题来源：吴宗敏
日期：2021-10-1 国庆72周年
coded by: [刘翔](https://geogebra.com/m/kumath) © <kumath@outlook.com>
20210929，20211001，10012021,9292021 都是素数(吴宗敏)
"""

from sympy import primerange
from sys import argv
from datetime import datetime

def isleapyear(year):
    '''Python program to check if year is a leap year or not'''

    # To get year (integer input) from the user
    # year = int(input("Enter a year: "))

    return (year % 4) == 0 and (year % 100 !=0) or (year % 400) == 0

# 一条Python语句判断是否为闰年？
isleap = lambda year: (year % 4) == 0 and (year % 100 != 0) or (year % 400) == 0

def date_isprime(year1, year2):
    '''给定两个年份，得到 yyyymmdd 是素数的列表
    input: 起止年份 year1, year2
    output：素数列表(按照年份分类)
    '''

    bmonths = [31,28,31,30,31,30,31,31,30,31,30,31]  # 月份对应的常规天数，闰年除外

    res = []
    for year in range(year1, year2):
        r1 = []
        leap = isleap(year)   # isleapyear(year)
        bmonths[1] = 29 if leap else 28

        for month in range(1, 13):
            ymd = [str(year), str(month).zfill(2), '1'.zfill(2)]
            lower = ''.join(ymd)
            ymd = [str(year), str(month).zfill(2), str(bmonths[month-1]+1)]
            upper = ''.join(ymd)
            primes = primerange(int(lower) , int(upper))
            # r1.append()         ## append是在末尾追加的意思
            r1 += list(primes)    ## 合并merge两个列表lists

        res.append(r1)            ## 按照年份在末尾追加到列表

    return res

if __name__ == '__main__':
    if len(argv) > 1 and argv[1].lower() in ['--help', '-h', '-help']:
        print("""
        给定两个年份之间，日期是素数的列表。
        使用说明：python prime_date.py 起始年份 [终止年份, 缺省是当前年份]
        使用举例：python prime_date.py 2000 2011
                  python prime_date.py 2010
                  python prime_date.py %[显示当前年份的素数日期]
                  python prime_date.py --help
                  python prime_date.py -h
        """)
        exit()

    year = datetime.now().year
    sy = argv[1] if len(argv) > 1 else str(year)
    if isinstance(sy,(str)):
        starty = int(sy)
    else:
        raise ValueError

    ey = argv[2] if len(argv) > 2 else str(year+1)
    if isinstance(ey,(str)):
        endy = int(ey)
    else:
        raise ValueError

    print('起止年份 {0}年~{1}年，素数日期有\n'.format(starty, endy))
    for yp in date_isprime(starty, endy):
        print('{0}, 个数 {1}\n'.format(yp, len(yp)))


import sympy
from sympy import *
import random

def choose_modulus(k,l,m):
	p1 = find_prime(k,l)
	p2 = find_prime(k,l)
	while not (abs(log(p1,2) - log(p2,2)) < m):
		p1 = find_prime(k,l)
		p2 = find_prime(k,l)
		print(p1, p2)
	print(log(p1,2)+m < log(p2,2))
	print("prime 1: ", p1)
	print("prime 2: ", p2)
	return p1,p2

def choose_encryption_key_old(m):
	e = random.randrange(2,m)
	while not gcd(e, totient(m)) == 1:
		e = random.randrange(2,m)
	return e

def choose_encryption_key(p1,p2):
	m = p1*p2
	e = random.randrange(2, p1*p2)
	while not gcd(e, (p1-1)*(p2-1)) == 1:
		e = random.randrange(2, p1*p2)
	return e

def compute_decryption_key(e, p1, p2):
	# d = e inverse mod phi(m) m = p1*p2
	#phi(m) = (p1-1)*(p2-1)
	d = inv_mod(e, (p1-1)*(p2-1))
	return d

def RSA_encrypt(P, e, m):
	return power_mod(P,e,m)

def RSA_decrypt(C, d, m):
	return power_mod(C,d,m)

def RSA_crack(C, e, m):
	return power_mod(C, inv_mod(e,totient(m)), m)

def power_mod(a, b, m):
	#replace with better version for large numbers
	#return ((a%m)**(b%m))%m
	return pow(a,b,m)

def string_to_int(s):
	return int.from_bytes(s.encode(),'big')

def int_to_string(n):
	return n.to_bytes((n.bit_length()+7)//8,'big').decode()

def find_prime(k, l):
	x = random.randrange(2**k+2, 2**l-1)
	while (not isprime(x)):
		x = random.randrange(2**k+2, 2**l-1)
	return x

def mod_inv_old(a,m):
	#replace with euclid's algorithm - will be using large numbers
	for i in range(0,m):
		if (i*a)%m == 1:
			return i
	return -1

def xgcd(b, n):
    """ Return g, x0, y0
        such that x0*b + y0*n = g
        and g is the gcd of (b,n)"""
    x0, x1, y0, y1 = 1, 0, 0, 1
    while n != 0:
        q, b, n = b // n, n, b % n
        x0, x1 = x1, x0 - q * x1
        y0, y1 = y1, y0 - q * y1
    return b, x0, y0

def inv_mod(b, n):
    """ Return the modular inverse of b mod n
     or None if gcd(b,n) > 1 """
    g, x, _ = xgcd(b, n)
    if g == 1:
        return x % n

def unit_test1(kv, lv, dv, textv):
	(p1, p2) = choose_modulus(kv, lv, dv)
	m = p1 * p2
	e = choose_encryption_key(p1, p2)
	d = compute_decryption_key(e, p1, p2)
	plaintext = textv
	P = string_to_int(plaintext)
	print("Plaintext in bits = ", P)
	C = RSA_encrypt(P, e, m)
	print("Plaintext: ", plaintext)
	print("Text Len: ", 8*len(plaintext), "bits")
	print("Modulus len: ",  int(log(m)/log(2)), "bits")
	print("Ciphertext in bit format = ", C)
	D = RSA_decrypt(C, d, m)
	print("D (Decrypted text in bit format) = ", D)
	decoded_text = int_to_string(D)
	print("Decoded Text: ", decoded_text, "\n\n--------------\n")

def unit_test2(kv, lv, dv, textv):
	(p1, p2) = choose_modulus(kv, lv, dv)
	m = p1 * p2
	e = choose_encryption_key(p1, p2)
	d = compute_decryption_key(e, p1, p2)
	plaintext = textv
	P = string_to_int(plaintext)
	print("Plaintext in bits = ", P)
	C = RSA_encrypt(P, e, m)
	print("Plaintext: ", plaintext)
	print("Text Len: ", 8*len(plaintext), "bits")
	print("Modulus len: ",  int(log(m)/log(2)), "bits")
	print("C (Ciphertext in bit format) = ", C)
	D = RSA_crack(C, e, m)
	print("D (decrypted text in bit format) = ", D)
	decoded_text = int_to_string(D)
	print("Decoded Text: ", decoded_text, "\n\n--------------\n")

#unit_test1(10, 12, 1, "Hi")
#unit_test1(200, 201, 50, "YouUsedTheWrongFormulaButGotTheRightAnswer")
#unit_test1(200,201,50,"I've got the best RSA. Tremendous!")
#unit_test2(10, 12, 5, "Hi")

#################
"""
PART 1: OUR SCHEMES
"""
#find a modulus
#the e we submit to blackboard: 9034302786477632084177231467338707794932890382219531012244768011578912282025102742921209071808720002426284716566453084939
#the m we put on the thread: 30189064434495182222794692701630732429757499485911917941672183961436119037704394785112588879375758643631173789514757495643

p1,p2 = choose_modulus(198,202,1) #this has p1 and p2
modulus = p1*p2
print("Prime 1: ", p1)
print("Prime 2: ", p2)
#find an encryption key
encryptKey = choose_encryption_key(p1,p2)
print("Modulus: ", modulus)
print("Encryption Key: ", encryptKey) #post key and modulus value to new blackboard thread

print()
print("-------------------------------")
print()
##############
"""
PART 2: SENDING SOMEONE ELSE A MESSAGE WITH THEIR SCHEME
"""
theirEKey = encryptKey # this is where we add their e!!!!!!!!
theirMod = modulus #add their m !!!!!!
ourMessage = "YouUsedTheWrongFormulaButGotTheRightAnswer" #42 chars
byteMessage = string_to_int(ourMessage) #42 bytes long
encryptedText = RSA_encrypt(byteMessage, theirEKey, theirMod)
print("The Encrypted Text: ", encryptedText) #post this on that person's blackboard thread so they can crack it
print()
print("-------------------------------")
print()
#########
"""
PART 3: DECODING THE MESSAGE SENT TO US USING OUR SCHEME
"""
encryption = 9034302786477632084177231467338707794932890382219531012244768011578912282025102742921209071808720002426284716566453084939
mod = 30189064434495182222794692701630732429757499485911917941672183961436119037704394785112588879375758643631173789514757495643
p1 = 2852799332826165576549683721934686650431760177477582224441789
p2 = 10582260058434591375044316914661108554362583639774055485540087
byteReceived = 7685639402658570041332092386730857722829508721500337074039018855544141530770944219201236076815619964810541252211564571230 #encryptedText
#this is posted for us ^^
#stringReceived = int_to_string(byteReceived) #decrypt it
decryptKey = compute_decryption_key(encryption, p1, p2)
#print("1 ", decryptKey)
decryptedBytes = RSA_decrypt(byteReceived, decryptKey, mod)
#print("2 ", decryptedBytes)
decryptedString = int_to_string(decryptedBytes)
#print("3 ", decryptedString)
print("The Message received : ", decryptedString) #this was the message we got -> post to our blackboard thread
#the message we received: RSA is probably safer than couriers.

#### trying lizzie's scheme: post this on Lizzie's thread for them to decode (For Part 2)
e = 1274570651282105475941777766587577582023518221190732522597076146804533049158766343138826132941234187595481937482071481202376751312818066989598920155989
m = 9974085880263379580394176919091237671243630991492368310958180254243729015156794407476067877070352245916181312913506081413978120979798227144179681175879
message = "Hi. This is a creative message."
byteMess = string_to_int(message)
encryptedWords = RSA_encrypt(byteMess,e,m)
print("The message encrypted is: ", encryptedWords)


import numpy as np
from coptim.function import Function

class Rosenbrock(Function):

    def eval(self, x):
        assert len(x) == 2, '2 dimensional input only.'
        return 100 * (x[1] - x[0] ** 2) ** 2 + (1 - x[0]) ** 2

    def gradient(self, x):
        assert len(x) == 2, '2 dimensional input only.'
        return np.array([
            2 * (-200 * x[0] * x[1] + 200 * np.power(x[0], 3) - 1 + x[0]),
            200 * (x[1] - x[0] ** 2)
        ])

    def hessian(self, x):
        assert len(x) == 2, '2 dimensional input only.'
        df_dx1 = -400 * x[1] + 1200 * x[0] ** 2 + 2
        df_dx1dx2 = -400 * x[0]
        df_dx2dx1 = -400 * x[0]
        df_dx2 = 200

        return np.array([[df_dx1, df_dx1dx2], [df_dx2dx1, df_dx2]])


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

style.use('ggplot')

xs = np.array([1, 2, 3, 4, 5], dtype=np.float64)
ys = np.array([5, 4, 6, 5, 6], dtype=np.float64)

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

    b = mean(ys) - m * mean(xs)

    return m, b

m, b = best_fit_slope_and_intercept(xs, ys)

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

plt.scatter(xs, ys, color='#003F72')
plt.plot(xs, regression_line)
plt.show()

predict_x = 7
predict_y = (m * predict_x) + b
print(predict_y)

plt.scatter(xs, ys, color='#003F72', label='data')
plt.plot(xs, regression_line, label='regression line')
plt.scatter(predict_x, predict_y, color="#0ece18", label='prediction')
plt.legend(loc=4)
plt.show()


#Import stuff
import numpy as np
import matplotlib.pyplot as plt

#Import my own file, eigenstuffs.py, for eigenstuffs
import eigenstuffs as eg

n = 100  #Size of matrix
alpha=2. #Value of diagnol in matrix
iter_max = 1000 #Maximum number of iterations
j_ = 2 #j as defined in equations 1.1 and 1.2 in the homework
epsilon = 1e-8 #used to check for convergence

x = np.zeros(n)  #x is the solution vector
                 #it is initialized to my initial guess of all zeros

#Create and fill in the matrix T, defined in the problem statement
T = np.zeros( (n,n) )
for i in range(len(T)):
    for j in range(len(T[i])):
        if i == j:
            T[i][j] = alpha
        elif j == (i-1) or j == (i+1):
            T[i][j] = -1.

#calculate the eigenvalue appropriate for j_
evalue = eg.eigenvalue(j_,alpha,n)

#calculate the eigenvector appropriate for j_
evec = eg.eigenvec(j_,n)

#the vector b as defined in Ax = b
B = evalue*evec

#Create a list with which to store the error of each iteration
error = []

#Now let's iterate, Jacobi style
for k in range(iter_max):
    for i in range(len(x)): #for each element of the solution vector
        #The following if...elif...else statements are set up to exploit
        #the sparse nature of T. The summation used in the Jacobi algorithm
        #sums over a bunch of zeros so I cut those zeros out to speed up the code
        if i==0:  #If we're on the first row, only add T[i][i+1]
            summation = T[i][i+1] * x[i+1]
        elif i== (len(x)-1): #If we're on the last row, only add T[i][i-1]
            summation = T[i][i-1] * x[i-1]
        else:  #Only need to add two elements together
            summation = T[i][i+1] * x[i+1] + T[i][i-1] * x[i-1]
        x[i] = 1./T[i][i] * (B[i] - summation) #Update x based on Jacobi algorithm

    #update the error array
    error.append( np.max(np.absolute(np.subtract(evec,x))))

    #check for convergence
    numconverge = 0 #count the number of elements of x which have converged
    for i in range(len(x)):
        if abs(x[i] - evec[i]) < epsilon: #If converged within epsilon
                numconverge += 1

    if numconverge == len(x):  #If all the elements of x have converged
        print "Converged!  After %d loops" % (k+1)
        break
else: #If for loop completes with convergence not being acheived
    print "Convergence not achieved after %d number of iterations" % (k+1)

#Now plot the error as a function of iteration number
#I feel all these pyplot commands are sufficiently self-explanatory
#that I don't need to elaborate each one of them.
plt.plot(range(1,len(error)+1),error)
plt.yscale('log')
plt.xlabel('Iteration number')
plt.ylabel('Error')
s = "Error plot for j=" + str(j_)
plt.title(s)
s = "errorplot_" + str(j_) + ".pdf"
plt.savefig(s)


""" stats.py

Statistics tools used throughout the project. """
import sys

## Standard imports
import numpy as np

#### WLS
##############################################################################
def WeightedLeastSquares(X,Y,weights=None,verbose=False,standardize=True):

	""" Weighted LS, reduces to OLS when weights is None. This implementation computes
	the estimator and covariance matrix based on sample variance. For TSIR however, the

	NB: x is assumed to be an array with shape = (num_data points, num_features) """

	## Get the dimensions
	num_data_points = X.shape[0]
	try:
		num_features = X.shape[1]
	except:
		num_features = 1
		X = X.reshape((num_data_points,1))

	## Initialize weight matrix
	if weights is None:
		W = np.eye(num_data_points)
	else:
		W = np.diag(weights)

	## Standardize the inputs and outputs to help with
	## stability of the matrix inversion. This is needed because
	## cumulative cases and births both get very large.
	if standardize:
		muY = Y.mean()
		sigY = Y.std()
		muX = X.mean(axis=0)
		sigX = X.std(axis=0)
		X = (X-muX)/sigX
		Y = (Y-muY)/sigY

	## Compute the required matrix inversion
	## i.e. inv(x.T*w*x), which comes from minimizing
	## the residual sum of squares (RSS) and solving for
	## the optimum coefficients. See eq. 3.6 in EST
	xTwx_inv = np.linalg.inv(np.dot(X.T,np.dot(W,X)))

	## Now use that matrix to compute the optimum coefficients
	## and their uncertainty.
	beta_hat = np.dot(xTwx_inv,np.dot(X.T,np.dot(W,Y)))

	## Compute the estimated variance in the data points
	residual = Y - np.dot(X,beta_hat)
	RSS = (residual)**2
	var = RSS.sum(axis=0)/(num_data_points - num_features)

	## Then the uncertainty (covariance matrix) is simply a
	## reapplication of the inv(x.T*x):
	if weights is None:
		beta_var = var*xTwx_inv
	else:
		beta_var = xTwx_inv

	## Reshape the outputs
	beta_hat = beta_hat.reshape((num_features,))

	## Rescale back to old values
	if standardize:
		X = sigX*X + muX
		Y = sigY*Y + muY
		beta_hat = beta_hat*(sigY/sigX)
		sig = np.diag(sigY/sigX)
		beta_var = np.dot(sig,np.dot(beta_var,sig))
		residual = sigY*residual + muY - np.dot(muX,beta_hat)

	## Print summary if needed
	if verbose:
		for i in range(num_features):
			output = (i,beta_hat[i],2.*np.sqrt(beta_var[i,i]))
			print("Feature %i: coeff = %.4f +/- %.3f." % output)

	return beta_hat, beta_var, residual

def ConstantWLS(x,var):

	""" Avg and var are the avg and variance of samples. Weights are computed with the
	variance, and WLS is used to estimate a fit to a constant. """

	## Compute weights
	weights = 1./var/np.sum(1./var)

	## Create model based estimates with weighted LS on a
	## constant.
	weighted_mean = np.sum(x*weights)
	weighted_var = np.sum(weights*((x-weighted_mean)**2))

	return weighted_mean, weighted_var


import numpy as np
# parameter to avoid the sigmoid function to return 0 because of an overflow of the exp function
epsilon = 10**-9

def sigmoid(u):
    if u < 0.:
        return  max(epsilon, np.asscalar(np.exp(u))/( np.asscalar(np.exp( u)) + 1.))
    else:

        return max(epsilon, 1./(1. + np.asscalar(np.exp(- u))))

def solveWKRR(K, W, z, lamb):
    """ Solve the WKRR found for kernel logistic regression"""
    first = np.linalg.inv((W**(1./2.)).dot(K).dot((W**(1./2.))) + float(K.shape[0])*lamb*np.identity(K.shape[0]))
    return (W**(1./2.)).dot(first).dot((W**(1./2.))).dot(z)

def evaluate_alpha(alpha, K, label):
    """ Return success percent """
    result = K.dot(alpha)

    success = [float(result[i,0]*label[i] > 0) for i in range(len(label))]

    return np.mean(success)*100

def compute_loss(K, alpha, lamb, label):
    lab = np.asarray(label).reshape([len(label), 1])
    result = K.dot(alpha)
    loss = np.log(1. + np.exp(np.multiply(-result, lab)))
    loss = (1./float(len(label)))*np.sum(loss) + lamb*alpha.T.dot(K).dot(alpha)
    return loss

def compute_label(data_in_kernel, alpha):
    """ Compute the label for the data given (in the form data[i,j] = K(x, xj) with x a new data, xj in the data set"""
    result = data_in_kernel.dot(alpha)
    return [int(result[i,0] > 0.) for i in range(data_in_kernel.shape[0])]

def logistic_kernel_regression(K, label, lamb, nb_it, K_test, label_test):
    """ Apply logistic kernel regression with solving by newton method
    with K the kernel matrix and alpha0 the initialization
    return the alpha found """
    # Initialisation
    alpha = (1. - 2.*np.random.random_sample([K.shape[0] , 1]))
    p = np.zeros([K.shape[0], 1])
    w = np.zeros([K.shape[0], K.shape[0]])
    z = np.zeros([K.shape[0], 1])
    for j in range(nb_it):
        m = K.dot(alpha)
        for i in range(K.shape[0]):
            p[i, 0] = - sigmoid(-label[i] * m[i, 0])
            w[i, i] = sigmoid(m[i, 0]) * sigmoid(-m[i, 0])
            z[i, 0] = m[i, 0] - label[i] * p[i,0]/ (w[i,i])
        alpha = solveWKRR(K, w, z, lamb)
        print( "loss", compute_loss(K, alpha, lamb, label))
        print ("on training", j, evaluate_alpha(alpha, K, label))
        print ("iteration ", j, evaluate_alpha(alpha, K_test, label_test))
    return alpha


import numpy as np

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

w = 0.0

# model prediction
def forward(x):
    return w * x
# loss = MSE in linear regression
def loss(y, y_predicted):
    return ((y_predicted-y)**2).mean()

# gradient with respect to our parameters

# MSE = 1/N * (w*x - y)**2
# dJ/dw = 1/N 2x (w*x - y) # numerical computed derivative chain rule
def gradient(x, y, y_predicted):
    return np.dot(2*x, y_predicted-y).mean() # dot product of 2 and x
print(f'Prediction before training: f(5) = {forward(5):.3f}')

# Training
learning_rate = 0.01
n_iters = 10

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

    # loss
    l = loss(Y, y_pred)

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

    # update weights -> go in negative direction of the gradient
    w -= learning_rate * dw

    if epoch % 1 == 0: # we want to print every step
        print(f'epoch {epoch + 1}: w = {w:.3f}, loss = {l:.8f}')

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


import math
import numpy
from scipy import optimize

def function1(x, y):
    return 5*x - 6 * y + 20 * math.log(x, 10) + 16

def function1_dx(x, y):
    return 5 + 20 / x

def function1_dy(x, y):
    return -6

def function2(x, y):
    return 2*x + y - 10 * math.log(y, 10) - 4

def function2_dx(x, y):
    return 2

def function2_dy(x, y):
    return 1 - 10/y

def function_sys(x):
    return [function1(x[0], x[1]), function2(x[0], x[1])]

def norm(z):
    max_z = abs(z[0])
    for i in z:
        if abs(i) > max_z:
            max_z = abs(i)
    return max_z

def modified_newton_method(b, size):
    eps = 0.0000001
    x0 = [1]*size
    a = numpy.array([[function1_dx(x0[0], x0[1]), function1_dy(x0[0], x0[1])],
                         [function2_dx(x0[0], x0[1]), function2_dy(x0[0], x0[1])]])
    f = numpy.array([function1(x0[0], x0[1]), function2(x0[0], x0[1])])
    x1 = x0 - numpy.linalg.inv(a) @ f

    while norm(x1 - x0) > eps:
        x0 = x1
        a = numpy.array([[function1_dx(x1[0], x1[1]), function1_dy(x1[0], x1[1])],
                         [function2_dx(x1[0], x1[1]), function2_dy(x1[0], x1[1])]])
        f = numpy.array([function1(x1[0], x1[1]), function2(x1[0], x1[1])])
        x1 = x0 - numpy.linalg.inv(a) @ f
    print(x1)
    print(f"{function1(x1[0], x1[1]):9.10f}")

def degree_method(a):
    eps = 0.1
    y0 = numpy.array([1.0]*4)
    y1 = a @ y0
    lmb1 = y1[0]
    y0 = a @ y1
    lmb2 = y0[0]/y1[0]
    while abs(lmb1 - lmb2) > eps:
        lmb1 = lmb2
        y1 = y0
        y0 = a @ y1
        lmb2 = y0[0]/y1[0]
    print(lmb2)

def func(x):
    return pow(x,3) - 3 * pow(x,2) - 17 * x + 22

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

print(func(1.5))


import numpy as np

# functions

# gaussian
def gaussian(x, height, center, width):
	return height*np.exp(-(x - center)**2/(2*width**2)) #+ offset

def double_gaussian(x, h1, c1, w1, h2, c2, w2, base):
	return (gaussian(x, h1, c1, w1) +
		gaussian(x, h2, c2, w2) + base)

def chi_gauss(param, xdata, ydata, ysig):
	chi = (ydata - (gaussian(xdata, *param))) / ysig
	return chi

def chi_dgauss(param, xdata, ydata, ysig):
	chi = (ydata - (double_gaussian(xdata, *param))) / ysig
	return chi

def lorentzian(x, a, x0, gamma):
	return a*gamma**2 /((x - x0)**2 + gamma**2) #+ offset

def double_lorentzian(x, a1, c1, g1, a2, c2, g2, base):
	return (lorentzian(x, a1, c1, g1) +
		gaussian(x, a2, c2, g2) + base)

def chi_dlorentz(param, xdata, ydata, ysig):
	chi = (ydata - (double_lorentzian(xdata, *param))) / ysig
	return chi

# discontinuous log-normal gaussian
def loggauss(x, height, center, width, base):
	logg = np.zeros(x.shape)

	# x > 0
	logg[x > 0.] = height*np.exp(-(np.log(x[x > 0.]) - center)**2/(2*width**2))/x[x > 0.] #+ offset

	# x = 0
	logg[x == 0.] = 0.

	# x <0
	logg[x < 0.] = height*np.exp(-(np.log(-x[x < 0.]) - center)**2/(2*width**2))/x[x < 0.] #+ offset

	return logg + base

def chi_loggauss(param, xdata, ydata, ysig):
	chi = (ydata - loggauss(xdata, *param)) / ysig
	return chi


# coding: utf-8
import numpy as np

# ... create Matrix L
def build_L(n):
    L = np.zeros((n, n))
    for i in range(0, n-1):
        L[i, i] = (i+1) * 1. / n
        L[i, i+1] = (n-i-1) * 1. / n
    L[n-1, n-1] = 1.
    return L
# ...

# ... create Matrix M
def build_M(n):
    M = np.zeros((n, n))
    M[0, 0] = 1.
    for i in range(1, n):
        M[i, i-1] = (i) * 1. / n
        M[i, i] = (n-i) * 1. / n
    return M
# ...

# ... create S matrix
def build_S(n, S):
    L = build_L(n)
    M = build_M(n)
    # ... create Su and Sd matrices
    Su = S.dot(L)
    Sd = S.dot(M)
    # ...
    S_new = np.zeros((n+1, n+1))
    S_new[0:n, 0:n] = Su
    S_new[n, 1:n+1] = Sd[-1, :]
    S_new[1:n+1, n] = Sd[:, -1]

    return S_new
    # ...

# ... matrix conversion
def matrix_conversion_ubspline_to_bernstein(n):
    # ... initialization
    S0 = np.ones((1, 1))
    # ...

    # ... recursive algo
    S = S0
    for d in range(1, n):
        S = build_S(d, S)
    # ...

    return S
# ...


from math import cos, acos, sqrt

import numpy as np

# global
PI = 3.141592
RRR = 6378.388
INFINITY = float('inf')

class Graph:
    def __init__(self, input_data, is_geo=False):  # geo is False
        # init adj_matrix w/ all 0 values
        self._length = len(input_data)
        self._adj_matrix = np.zeros((self._length, self._length), dtype=int)  # A data data_type object (using numpy)

        self._create_adj_matrix(input_data, is_geo)
        self._distances = {}  # distance dict key its a tuple (v, s)
        self._predecessors = {}  # again a tuple key

    def _create_adj_matrix(self, input_data, is_geo):  # format, pass data_type o euc format
        for i in input_data:
            for j in input_data:
                if i[0] != j[0]:
                    # geo = False
                    # None if i == j
                    self._adj_matrix[int(i[0]) - 1][int(j[0]) - 1] = Graph.calc_distance(i, j, is_geo)

    def get_distances(self, key):
        """

        :param key: is a tuple
        :return: distance (x,y) y to x
        """
        try:
            return self._distances[key]
        except KeyError:
            try:
                (i, j) = key
                new_key = (j, i)
                return self._distances[new_key]
            except KeyError:
                return None

    def set_distances(self, key, value):
        """

        :param key:
        :param value:
        :return:
        """
        self._distances[key] = value

    def get_parents(self, key):
        return self._predecessors[key]

    def get_adj_matrix(self, u=None, v=None):
        if u is None or v is None:
            return self._adj_matrix
        else:
            return self._adj_matrix[u, v]

    def get_vertices(self):
        return self._length

    def set_parents(self, key, value):
        self._predecessors[key] = value

    @staticmethod
    def convert_to_rads(val):
        deg = int(val)
        min_ = val - deg
        rad = PI * (deg + 5.0 * min_ / 3.0) / 180.0

        return rad

    @staticmethod
    def calc_distance(i, j, is_geo: bool):
        if is_geo:
            lat_i = Graph.convert_to_rads(i[1])  # u[0] index,  u[1] u[2] node latitude, longitude
            long_i = Graph.convert_to_rads(i[2])

            lat_j = Graph.convert_to_rads(j[1])
            long_j = Graph.convert_to_rads(j[2])

            q1 = cos(long_i - long_j)
            q2 = cos(lat_i - lat_j)
            q3 = cos(lat_i + lat_j)
            dij = int(RRR * acos(0.5 * ((1.0 + q1) * q2 - (1.0 - q1) * q3)) + 1.0)
        else:
            dij = int(sqrt((i[1] - j[1]) ** 2 + (i[2] - j[2]) ** 2))
        return dij


import numpy as np
from math import log

def trapezoid(f_discrete, dx):
    return 0.5 * dx * sum([(f_discrete[i] + f_discrete[i + 1]) for i in range(len(f_discrete) - 1)])

def romberg(f_discrete, dx):
    def find_a_n():
        nonlocal a, n
        a = len(f_discrete) - 1
        while a % 2 == 0:
            a = a // 2
        n = int(log((len(f_discrete) - 1) // a, 2))

    a, n = 0, 0
    find_a_n()

    i_rom = [[0] * (n - i + 1) for i in range(n + 1)]

    for i in range(n + 1):
        i_rom[i][0] = trapezoid([f_discrete[2 ** (n - i) * j] for j in range(2 ** i * a + 1)], 2 ** (n - i) * dx)

    for j in range(1, n + 1):
        for i in range(n + 1 - j):
            i_rom[i][j] = (4 ** j * i_rom[i + 1][j - 1] - i_rom[i][j - 1]) / (4 ** j - 1)

    return i_rom[0][-1]

# Tri Diagonal Matrix Algorithm(a.k.a Thomas algorithm) solver
def tdma_solver(a, b, c, d):
    """
    TDMA solver, a b c d can be NumPy array type or Python list type.
    refer to http://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm
    and to http://www.cfd-online.com/Wiki/Tridiagonal_matrix_algorithm_-_TDMA_(Thomas_algorithm)
    """
    nf = len(d)  # number of equations
    ac, bc, cc, dc = map(np.array, (a, b, c, d))  # copy arrays
    for it in range(1, nf):
        mc = ac[it - 1] / bc[it - 1]
        bc[it] = bc[it] - mc * cc[it - 1]
        dc[it] = dc[it] - mc * dc[it - 1]

    xc = bc
    xc[-1] = dc[-1] / bc[-1]

    for il in range(nf - 2, -1, -1):
        xc[il] = (dc[il] - cc[il] * xc[il + 1]) / bc[il]

    return xc


import numpy as np

def normalize_points(pts):
  """
  Normalize the points to remove dependence on the origin and
  scale of the original image. This improves the solution DLT
  provides, especially for noisy points.

  Input: 2D list of points (e.g. [[x1,y1], [x2,y2], ...])
  Output: 2D list of transformed points (e.g. [[x1',y1'], [x2',y2'], ...])
  and the homography matrix that did the transform
  """

  [x_mean, y_mean] = np.mean(pts, axis=0)

  # Sum of distances between points and the mean
  total_dist = np.sum(((pt[0] - x_mean)**2 + (pt[1] - y_mean)**2)**0.5 for pt in pts)

  avg_dist = total_dist / len(pts)

  # Scale factor
  sf = np.sqrt(2) / avg_dist

  # Transform by translation [-sf*x_mean, -sf*y_mean] and scale by [sf, sf]
  projective_transform = np.array([[sf, 0, -sf * x_mean], [0, sf, -sf * y_mean], [0, 0, 1]])

  # Pad the inner array so that it can be multiplied by the projection matrix
  ones = np.ones((1, pts.shape[0]))
  padded_pts = np.concatenate((pts.T, ones))

  # Apply the transform to the points
  transformed = projective_transform @ padded_pts

  # Get x and y coordinates, and transpose to be in original form
  pts_2d = transformed[0:2].T

  return pts_2d, projective_transform

def compute_matrix_A(pts1, pts2):
  """
  Compute the 2n x 9 matrix A to setup the linear equations
  that allow the homography values to be found

  Input: Corresponding (matched) normalized points
  Outputs: 2n x 9 matrix A
  """

  A = []

  for i in range (0, len(pts1)):
    x, y = pts1[i]
    x_prime, y_prime = pts2[i]

    A.append([0, 0, 0, -x, -y, -1, y_prime * x, y_prime * y, y_prime])
    A.append([x, y, 1, 0, 0, 0, -x_prime * x, -x_prime * y, -x_prime])

  return np.asarray(A)

def direct_linear_transform(pts1, pts2):
  """
  Compute the homography matrix that transforms pts1 onto the plane of pts2
  via the direct linear transform (DLT) method. This includes a
  normalisation step, as described in Section 4.4 of:

  R. Hartley and A. Zisserman. Multiple View Geomerty in Computer
  Vision. Cambridge University Press, second edition, 2003

  Parameters: Corresponding points for two images (pts1 and pts2)
  Output: H, the homography matrix
  """

  pts1_normalized, pts1_T = normalize_points(pts1)
  pts2_normalized, pts2_T = normalize_points(pts2)

  A = compute_matrix_A(pts1_normalized, pts2_normalized)

  # Compute the singluar value decomposition of A
  _, _, V = np.linalg.svd(A)

  # Get the last col of V and normalise by last value
  h = V[-1, :] / V[-1, -1]

  # Reshape to 3x3 homography matrix
  H_normalized = h.reshape(3, 3)

  # Denormalize the homography matrix
  H = np.linalg.inv(pts2_T) @ H_normalized @ pts1_T

  return H


from PIL import Image
import numpy as np
import matplotlib.pyplot as plt

class DCT:
    def __init__(self):
        self.input_dct2d = None
        self.output_dct2d = None

        self.input_idct2d = None
        self.output_idct2d = None

    def get_2d_dct(self, image, n_coef = 0):
        self.input_dct2d = image

        output = np.zeros(image.shape)

        R = output.shape[0]
        C = output.shape[1]

        for k in range(R):
            ck = np.sqrt(0.5) if k == 0 else 1
            for l in range(C):
                cl = np.sqrt(0.5) if l == 0 else 1
                for m in range(R):
                    for n in range(C):
                        output[k][l] += image[m][n] * np.cos(((2*m + 1) * k*np.pi)/(2*R)) * np.cos(((2*n + 1) * l*np.pi)/(2*C))
                output[k][l] *= ck * cl

        output *= 2.0/np.sqrt(R*C)

        if n_coef > 0:
            output.sort()
            for i in range(n_coef, n):
                output[i] = 0

        self.output_dct2d = output

        return output

    def get_2d_dct_sep(self, image, n_coef = 0):
        self.input_dct2d = image

        output = np.zeros(image.shape)

        for row in range(image.shape[0]):
            output[row, :] = self.get_1d_dct(image[row, :], n_coef)

        for column in range(image.shape[1]):
            output[:, column] = self.get_1d_dct(output[:, column], n_coef)

        self.output_dct2d = output

        return output

    def get_1d_dct(self, image, n_coef = 0):
        output = np.zeros(image.shape)
        n = len(image)

        for k in range(n):
            ck = np.sqrt(0.5) if k == 0 else 1
            for i in range(n):
                output[k] += image[i] * np.cos(2 * np.pi * k / (2.0 * n) * i + (k * np.pi) / (2.0 * n))
            output[k] *= ck

        output *= np.sqrt(2.0/n)

        if type(n_coef) == float or type(n_coef) == np.float32:
            n_coef = int(n_coef * len(output))

        if n_coef > 0:
            output.sort()
            for i in range(n_coef, n):
                output[i] = 0

        return output

    def get_inv_2d_dct(self, dct = None):
        if type(dct) == type(None):
            dct = self.output_dct2d

        self.input_idct2d = dct

        output = np.zeros(dct.shape)

        R = output.shape[0]
        C = output.shape[1]

        for m in range(R):
            for n in range(C):
                for k in range(R):
                    ck = np.sqrt(0.5) if k == 0 else 1
                    for l in range(C):
                        cl = np.sqrt(0.5) if l == 0 else 1
                        output[m][n] += ck * cl * dct[k][l] * np.cos(((2*m + 1) * k*np.pi)/(2*R)) * np.cos(((2*n + 1) * l*np.pi)/(2*C))

        output *= (2 / np.sqrt(R*C))

        self.output_idct2d = np.round(output)

        self.output_idct2d[self.output_idct2d < 0] = 0
        self.output_idct2d[self.output_idct2d > 255] = 255

        return output

    def get_inv_2d_dct_sep(self, dct = None):

        if type(dct) == type(None):
            dct = self.output_dct2d

        self.input_idct2d = dct

        output = np.zeros(dct.shape)

        for row in range(dct.shape[0]):
            output[row, :] = self.get_inv_1d_dct(dct[row, :])

        for column in range(dct.shape[1]):
            output[:, column] = self.get_inv_1d_dct(output[:, column])

        self.output_idct2d = np.round(output)

        self.output_idct2d[self.output_idct2d < 0] = 0
        self.output_idct2d[self.output_idct2d > 255] = 255

        return output

    def get_inv_1d_dct(self, dct):
        output = np.zeros(dct.shape)
        n = len(dct)

        for i in range(n):
            for k in range(n):
                ck = np.sqrt(0.5) if k == 0 else 1
                output[i] += ck * dct[k] * np.cos(2 * np.pi * k / (2.0 * n) * i + (k * np.pi) / (2.0 * n))
            output[i] *= np.sqrt(2.0 / n)

        return output

    def show_process(self):
        return self.input_dct2d, self.output_dct2d, self.output_idct2d


# 一行データ記述統計
def describe1(data):
    # 必要なライブラリをインポート
    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt

    # 元データ出力
    print(data)

    # サンプルサイズ
    def length(data):
        count = 0
        for d in data:
            count += 1
        return count

    # 合計値
    def sum_value(data):
        sum_value = 0
        for d in data:
            sum_value += d
        return sum_value

    # 算術平均
    def mean(data):
        return sum_value(data) / length(data)

    # 幾何平均
    # 比率や割合等について適用する
    # 0以下の値が含まれていた場合、規格外としてErrorを出す
    def geometric_mean(data):
        try:
            ds = 1
            n = length(data)
            for d in data:
                ds *= d
            return (ds)**(1/n)
        except:
            return 'Error'

    # 調和平均
    # 時速の平均等に適用する
    # 0以下の値が含まれていた場合、規格外としてErrorを出す
    def harmonic_mean(data):
        try:
            ds = 0
            n = length(data)
            for d in data:
                ds += 1/d
            return 1 / ((1/n) * ds)
        except:
            return 'Error'

    # 平均偏差
    def meand(data):
        n = length(data)
        m = mean(data)
        s = 0
        for d in data:
            s += abs(d - m)
        return s / n

    # 母分散
    def var_p(data):
        var_p = 0
        m = mean(data)
        for d in data:
            var_p += (d - m)**2
        return var_p / length(data)

    # 不偏分散
    def var_s(data):
        var_s = 0
        m = mean(data)
        for d in data:
            var_s += (d - m)**2
        return var_s / (length(data)-1)

    # 母標準偏差
    def std_p(data):
        return var_p(data)**0.5

    # 不偏標準偏差..........................未完成？
    # 単に不偏分散の平方根を取ったものは、厳密には「母標準偏差の不偏推定量」ではないらしい
    def std_s(data):
        return var_s(data)**0.5

    # 標準誤差
    def std_e(data):
        return std_s(data) / length(data)**0.5

    # 母平均の95%信頼区間(母分散既知)
    def mean_95cl_known(data):
        n = length(data)
        m = mean(data)
        s = std_p(data)
        return "({:.2f}, {:.2f})".format(m-1.96*(s/n)**0.5, m+1.96*(s/n)**0.5)

    # ソート（クイックソート）
    def quick_sort(data):
        n = length(data)
        pivot = data[n//2]
        left, middle, right = [], [], []
        for i in range(n):
            if data[i] < pivot:
                left = left + [data[i]]
            elif pivot < data[i]:
                right = right + [data[i]]
            else:
                middle = middle + [data[i]]
        if left:
            left = quick_sort(left)
        if right:
            right = quick_sort(right)
        return left + middle + right

    # 最小値
    def min_value(data):
        data_sorted = quick_sort(data)
        return data_sorted[0]

    # 最大値
    def max_value(data):
        data_sorted = quick_sort(data)
        return data_sorted[length(data)-1]

    # 第一四分位数
    def quartile1(data):
        data_sorted = quick_sort(data)
        q1 = length(data) * (1/4)
        return data_sorted[int(q1)]

    # 中央値
    def median(data):
        data_sorted = quick_sort(data)
        q2 = length(data) / 2
        if q2%1 != 0:
            return data_sorted[int(q2)]
        else:
            return (data_sorted[int(q2)] + data_sorted[int(q2)+1]) / 2

    # 第三四分位数
    def quartile3(data):
        data_sorted = quick_sort(data)
        q3 = length(data) * (3/4)
        return data_sorted[int(q3)]

    # 四分位偏差
    def quartile_range(data):
        return quartile3(data) - quartile1(data)

    # ミッドレンジ
    def mid_range(data):
        return (max_value(data) + min_value(data)) / 2

    # レンジ
    def all_range(data):
        return max_value(data) - min_value(data)

    # 最頻値
    def mode(data):
        # 個数を数える
        data_sorted = quick_sort(data)
        discoverd = {}
        for d in data_sorted:
            if d not in discoverd.keys():
                discoverd[d] = 1
            else:
                discoverd[d] += 1

        # 個数が最大のデータを検索する(複数個ある場合は全て出力する)
        discoverd_keys, discoverd_values = list(discoverd.keys()), list(discoverd.values())
        max_cnt = max_value(discoverd_values)
        mode = []
        for i, value in enumerate(discoverd_values):
            if max_cnt == value:
                mode += [discoverd_keys[i]]

        return str(mode)

    # 変動係数(coefficient of variance)
    # 単位が無く、直接の比較が困難な場合に平均を考慮した上での比率の比較ができる
    # 比率の比較なので、比例尺度のみ使用できる
    # 標準偏差は母分散を用いて計算する(統計学入門のp38式より)
    def cov(data):
        return std_p(data) / mean(data)

    # ジニ係数(gini coefficient)
    # 不平等度の指標として用いられる
    # 面積を表すので普通は負の値を取らないが、定義通りに計算して出力する。
    def gini(data):
        n = length(data)
        m = mean(data)
        s = 0
        for d1 in data:
            for d2 in data:
                s += abs(d1 - d2)
        return s / (2 * n**2 * m)

    # 歪度(skewness)
    # 母集団の分布の非対称性の程度・方向性を示す推定統計量
    # 値が正なら右の裾が長く、負なら左の裾が長い分布となっている
    def skewness(data):
        n = length(data)
        m = mean(data)
        s = std_s(data)

        three = 0
        for d in data:
            three += ((d-m) / s)**3

        return (n/((n-1)*(n-2))) * three

    # 尖度(kurtosis)
    # 母集団の分布の中心周囲部分の尖り具合を表す推定統計量
    # 値が正なら正規分布より尖っており、負なら正規分布より丸く鈍い形をしている
    def kurtosis(data):
        n = length(data)
        m = mean(data)
        s = std_s(data)

        four = 0
        for d in data:
            four += (d-m)**4 / s**4

        return ((n*(n+1))/((n-1)*(n-2)*(n-3))) * four - (3*(n-1)**2)/((n-2)*(n-3))

    # ジャック-ベラ検定(Jarque–Bera test)
    # 標本が正規分布に従っているかどうかを検定する
    # 帰無仮説：標本が正規分布に従う
    # 対立仮説：標本が正規分布に従わない
    # ソース：https://ja.wikipedia.org/wiki/ジャック–ベラ検定
    def jarque_bera(data):
        n = length(data)
        m = mean(data)

        s2, s3, s4 = 0, 0, 0
        for d in data:
            s2 += (d - m)**2
            s3 += (d - m)**3
            s4 += (d - m)**4

        # 標本歪度
        S = (s3/n) / (s2/n)**(3/2)
        # 標本尖度
        K = (s4/n) / (s2/n)**2

        JB = (n/6) * (S**2 + (1/4)*(K-3)**2)
        return JB

    # 結果出力
    display(pd.DataFrame({
        'count':length(data),
        'sum':sum_value(data),
        'mean':mean(data),
        'g_mean':geometric_mean(data),
        'h_mean':harmonic_mean(data),
        'meand':meand(data),
        'var.p':var_p(data),
        'var.s':var_s(data),
        'std.p':std_p(data),
        'std.s':std_s(data),
        'std_e':std_e(data),
        'mean_95cl_known':mean_95cl_known(data),
        'min':min_value(data),
        '25%':quartile1(data),
        '50%':median(data),
        '75%':quartile3(data),
        'max':max_value(data),
        '25-75%':quartile_range(data),
        'mid-range':mid_range(data),
        'range':all_range(data),
        'mode':mode(data),
        'cov':cov(data),
        'gini':gini(data),
        'skewness':skewness(data),
        'kurtosis':kurtosis(data),
        'Jarque-Bara_test.x2':jarque_bera(data)
    }, index=["descriptive statistics"]).T)

    # 種々のグラフをプロット
    # グラフ
    plt.plot(data)
    plt.title("Plot")
    plt.show()

    # 散布図
    plt.scatter([i for i in range(len(data))], data)
    plt.title("Scatter")
    plt.show()

    # ヒストグラム
    plt.hist(data, bins=(1+int(np.log2(length(data))))) #階級幅はスタージェスの公式で定める
    plt.title("Histogram")
    plt.show()

    # ヒストグラム(累積)
    plt.hist(data, bins=(1+int(np.log2(length(data)))), cumulative=True) #階級幅はスタージェスの公式で定める
    plt.title("Histogram(cumulative)")
    plt.show()

    # Q-Qプロット
    # 点が一直線に並んでいれば、そのデータの母集団の分布は正規分布に従う
    st.probplot(data, dist="norm", plot=plt)
    plt.show()

    # 箱ひげ図
    plt.boxplot(data)
    plt.title("Boxplot")
    plt.show()

    # バイオリンプロット
    plt.violinplot(data, widths=2, showmeans=True, showmedians=True, showextrema=True)
    plt.title("Violinplot")
    plt.show()

    # イベントプロット
    plt.eventplot(data, orientation="vertical")
    plt.title("Eventplot")
    plt.show()

# a以上b未満のランダム乱数size個...np.random.randint(a, b, size)
# 最小値low, 最大値highの一様乱数size個...np.random.uniform(low, high, size)
# 平均a, 標準偏差bの正規分布乱数size個...np.random.normal(a, b, size)
# ある期間に平均してscale回起こる現象が、次に起こるまでの期間Xが従う指数分布乱数size個...np.random.exponential(scale, size)
# 成功する確率がpのベルヌーイ試行をn回やる時の二項分布乱数size個...np.random.binomial(n, p, size)
# ベルヌーイ試行を何回か繰り返すときに、初めて成功するまでの回数を確率変数X、各試行の成功確率をpとしたときの幾何分布乱数size個...np.random.geometric(p, size)
# 単位時間あたりにある事象が平均してlam回起こる場合に、その事象がx回起こる確率を示すポアソン分布size個...np.random.poisson(lam, size)
# 試行回数n回の結果がpvals通りある時の多項分布乱数size個...np.random.multinomial(n, pvals, size)...（当たり前だけど）リストで返ってくるので、今回の実験に向かない
# パラメータa,bのベータ分布size個...np.random.beta(a, b, size)
# 形状母数shape、尺度母数scaleのガンマ分布に従う乱数size個np.random.gamma(shape, scale, size)
# 自由度dfのカイ二乗分布に従う乱数size個...np.random.chisquare(df, size)
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import scipy.stats as st
data = pd.DataFrame(np.random.randint(1, 101, 100), columns=["data"])["data"]
describe1(data)


#
# add path to local library mrob on bashr_rc: "export PYTHONPATH=${PYTHONPATH}:${HOME}/mrob/mrob/lib"
import mrob
import numpy as np
import open3d
# example equal to ./PC_alignment/examples/example_align.cpp
# generate random data
N = 5
X =  np.random.rand(N,3)
T = mrob.geometry.SE3(np.random.rand(6))
Y = T.transform_array(X)

print('X = \n', X,'\n T = \n', T.T(),'\n Y =\n', Y)

# solve the problem
T_arun = mrob.registration.arun(X,Y)
print('Arun solution =\n', T_arun.T())

W = np.ones(N)
T_wp = mrob.registration.weighted(X,Y,W)
print('Weighted point optimization solution =\n', T_wp.T())

def pcd_1(X, color, T = []):
	pcd = open3d.geometry.PointCloud()
	pcd.points = open3d.utility.Vector3dVector(X)
	if T!=[]:
	    pcd.transform(T)
	pcd.paint_uniform_color(color)
	return pcd

def vis_her(X, Y, T = []):
    blue = np.array([0,0,1], dtype='float64')
    red = np.array([1,0,0], dtype='float64')
    if T!=[]:
    	open3d.visualization.draw_geometries([pcd_1(X,red), pcd_1(Y,blue, T)])
    else:
        open3d.visualization.draw_geometries([pcd_1(X,red), pcd_1(Y,blue)])

def vis_arr(X):
	pcd = open3d.PointCloud()
	pcd.points =open3d.utility.Vector3dVector(X)
	pcd.paint_uniform_color(np.random.rand(3,).astype(np.float64))
	open3d.visualization.draw_geometries([pcd])

if 0:
    vis_her(X,Y,np.asarray(T_arun.T()))


#!/usr/bin/env python3
#
# Script to verify polynomial bases and inner product routine.
#
# Daniel R. Reynolds
# SMU Mathematics
# Math 4315

# imports
import numpy
from Chebyshev import Chebyshev
from Legendre import Legendre
from L2InnerProduct import L2InnerProduct

# set interval and weight functions
a = -1.0
b = 1.0
def wL(x):
    if numpy.isscalar(x):
        return 1.0
    else:
        return numpy.ones(x.shape)
def wC(x):
    return 1.0/numpy.sqrt(1.0-x**2)

# quick test to verify orthogonality of first 41 Chebyshev polynomials
print('Testing orthogonality of first 41 Chebyshev polynomials:')
passed = True
for i in range(41):
    for j in range(i+1,41):
        def fi(x):
            return Chebyshev(x,i)
        def fj(x):
            return Chebyshev(x,j)
        v = L2InnerProduct(fi,fj,wC,a,b)
        if (abs(v) > 1e-6):
            print('  <p%i,p%i> = %e' % (i, j, v))
            passed = False
if (passed):
    print('  Tests passed')

# quick test to verify orthogonality of first 41 Legendre polynomials
print('Testing orthogonality of first 41 Legendre polynomials:')
passed = True
for i in range(41):
    for j in range(i+1,41):
        def fi(x):
            return Legendre(x,i)
        def fj(x):
            return Legendre(x,j)
        v = L2InnerProduct(fi,fj,wL,a,b)
        if (abs(v) > 1e-6):
            print('  <p%i,p%i> = %e' % (i, j, v))
            passed = False
if (passed):
    print('  Tests passed')

# quick test to verify norms for first 41 Chebyshev polynomials
print('Testing norms for first 41 Chebyshev polynomials:')
passed = True
for i in range(41):
    def fi(x):
        return Chebyshev(x,i)
    v = L2InnerProduct(fi,fi,wC,a,b)
    if (i==0):
        if (abs(v - numpy.pi) > 1e-6):
            print('  <p0,p0> = %e' % (v))
            passed = False
    else:
        if (abs(v - numpy.pi/2) > 1e-6):
            print('  <p%i,p%i> = %e' % (i, i, v))
            passed = False
if (passed):
    print('  Tests passed')

# quick test to verify norms for first 41 Legendre polynomials
print('Testing norms for first 41 Legendre polynomials:')
passed = True
for i in range(41):
    def fi(x):
        return Legendre(x,i)
    v = L2InnerProduct(fi,fi,wL,a,b)
    if (abs(v - 1.0/(i+0.5)) > 1e-6):
        print('  <p0,p0> = %e' % (v))
        passed = False
if (passed):
    print('  Tests passed')

# end of script


from astropy.modeling.models import custom_model

#TODO: Add Jacobian
@custom_model
def GeneralizedLorentz1D(x, x_0=1., fwhm=1., value=1., power_coeff=1.):
    """
    Generalized Lorentzian function,
    implemented using astropy.modeling.models custom model

    Parameters
    ----------
    x: numpy.ndarray
        non-zero frequencies

    x_0 : float
        peak central frequency

    fwhm : float
        FWHM of the peak (gamma)

    value : float
        peak value at x=x0

    power_coeff : float
        power coefficient [n]

    Returns
    -------
    model: astropy.modeling.Model
        generalized Lorentzian psd model
    """
    assert power_coeff > 0., "The power coefficient should be greater than zero."
    return value * (fwhm / 2)**power_coeff * 1./(abs(x - x_0)**power_coeff + (fwhm / 2)**power_coeff)

#TODO: Add Jacobian
@custom_model
def SmoothBrokenPowerLaw(x, norm=1., gamma_low=1., gamma_high=1., break_freq=1.):
    """
    Smooth broken power law function,
    implemented using astropy.modeling.models custom model

    Parameters
    ----------
    x: numpy.ndarray
        non-zero frequencies

    norm: float
        normalization frequency

    gamma_low: float
        power law index for f --> zero

    gamma_high: float
        power law index for f --> infinity

    break_freq: float
        break frequency

    Returns
    -------
    model: astropy.modeling.Model
        generalized smooth broken power law psd model
    """
    return norm * x**(-gamma_low) / (1. + (x / break_freq)**2)**(-(gamma_low - gamma_high) / 2)

def generalized_lorentzian(x, p):
    """
    Generalized Lorentzian function.

    Parameters
    ----------

    x: numpy.ndarray
        non-zero frequencies

    p: iterable
        p[0] = peak centeral frequency
        p[1] = FWHM of the peak (gamma)
        p[2] = peak value at x=x0
        p[3] = power coefficient [n]

    Returns
    -------
    model: numpy.ndarray
        generalized lorentzian psd model
    """

    assert p[3] > 0., "The power coefficient should be greater than zero."
    return p[2] * (p[1] / 2)**p[3] * 1./(abs(x - p[0])**p[3] + (p[1] / 2)**p[3])

def smoothbknpo(x, p):
    """
    Smooth broken power law function.

    Parameters
    ----------

    x: numpy.ndarray
        non-zero frequencies

    p: iterable
        p[0] = normalization frequency
        p[1] = power law index for f --> zero
        p[2] = power law index for f --> infinity
        p[3] = break frequency

    Returns
    -------
    model: numpy.ndarray
        generalized smooth broken power law psd model
    """

    return p[0] * x**(-p[1]) / (1. + (x / p[3])**2)**(-(p[1] - p[2]) / 2)


import numpy as np
import matplotlib.pyplot as plt
from scipy.constants import pi as π
from scipy.optimize import curve_fit
import uncertainties as unc
from uncertainties import umath
from uncertainties import ufloat
g = 9.81

θ,t,tUncertainty = np.loadtxt(r"Lab/Term 2/2022.01.27 Not So Simple Pendulum - Computing/nss-real-experiment-a.csv", unpack=True, delimiter = ',')

# Adjust your font settings
titleFont = {'fontname':'Bodoni 72','size':13}
axesFont = {'fontname':'CMU Sans Serif','size':9}
ticksFont = {'fontname':'DM Mono','size':7}
errorStyle = {'mew':1,'ms':3,'capsize':3,'color':'blue','ls':''}
pointStyle = {'mew':1,'ms':3,'color':'blue'}
lineStyle = {'linewidth':0.5,'color':'red'}

def experimentA(θₒ,Tₒ,a,b):
    return Tₒ* (1 + a*θₒ+ b*θₒ**2)
def experimentB(x,m,c):
    return m*x+c

curvefitA,cov_curvefitA = curve_fit(experimentA, θ,t)
plt.xlabel("Angular Amplitude, θ initial / °)", **axesFont)
plt.ylabel("Period, T / s", **axesFont)
plt.xticks(**ticksFont)
plt.yticks(**ticksFont)
plt.title('Experiment A: Angular Amplitude vs. Period', **titleFont)
plt.errorbar(θ, t, yerr = tUncertainty,  **errorStyle)
plt.plot(θ,t,'x',**pointStyle)
plt.plot(θ, experimentA(θ, *curvefitA),**lineStyle)
plt.savefig('Experiment A', dpi = 1000)
plt.show()

print("Experiment A:")
print(cov_curvefitA)
print('a = ', curvefitA[1], 'b = ', curvefitA[2], 'Tₒ = ', curvefitA[0])

# Experiment B
ρ,T,TUncertainty = np.loadtxt(r"Lab/Term 2/2022.01.27 Not So Simple Pendulum - Computing/nss-real-experiment-b.csv", unpack=True, delimiter = ',')   #import values of density and period.
curvefitB, cov_curvefitB = curve_fit(experimentB, ρ, T)
TSquared = 1/T**2

plt.xlabel("Density of Pendulum Bob, ρ / g.cm¯³", **axesFont)
plt.ylabel("Period¯², T¯² / s¯²", **axesFont)
plt.xticks(**ticksFont)
plt.yticks(**ticksFont)
plt.title('Experiment B: Density against 1/Period¯²', **titleFont)
plt.errorbar(ρ, TSquared, yerr = TUncertainty, **errorStyle)
plt.plot(ρ, TSquared,'x',**pointStyle)
plt.plot(ρ, experimentB(ρ, curvefitB[0],curvefitB[1]-0.28),**lineStyle)

plt.savefig('Experiment B', dpi = 1000)
plt.show()

print("Experiment B:")
print(cov_curvefitB)
#print('k = ', curvefitB[0])


import numpy as np
from PIL import Image
from matplotlib import pyplot as plt

from numpy.fft import fft2, ifft2, fftshift, ifftshift

# %% read in the image and do transform
Q6_2 = np.array(Image.open("Q6_2.tif"))
row, col = Q6_2.shape
img_fourier = fftshift(fft2(Q6_2))
img_fourier_view = np.log(np.abs(img_fourier) + 1)
plt.imshow(img_fourier_view, cmap='gray')
plt.show()

# %% butterworth low pass filter
def butterworth(d: int = 40):
    n = 10

    def _func(mu, nu):
        return 1 / (1 + ((mu - row / 2) ** 2 + (nu - col / 2) ** 2) ** n /
                    d ** (2 * n))

    return np.fromfunction(_func, (row, col)).astype(float)

butter40 = butterworth(d=40)
butter70 = butterworth(d=70)
butter85 = butterworth(d=85)

plt.figure(figsize=(9.6, 4.8))
plt.subplot(131)
plt.imshow(butter40, cmap='gray')
plt.title('Butterworth low pass filter\n$d_0=40$')
plt.subplot(132)
plt.imshow(butter70, cmap='gray')
plt.title('Butterworth low pass filter\n$d_0=70$')
plt.subplot(133)
plt.imshow(butter85, cmap='gray')
plt.title('Butterworth low pass filter\n$d_0=85$')
plt.savefig("Q6_2_b.png")
plt.show()

# %% generate degradation function
k = 0.0025
func = np.fromfunction(lambda mu, nu:
                       np.exp(-k * ((mu - row / 2) ** 2 + (nu - col / 2) ** 2) ** (5 / 6)),
                       (row, col))

plt.imshow(func, cmap='gray')
plt.show()

# %% restore the image
# img_out_fourier = img_fourier / func
img_out_fourier = np.divide(img_fourier, func)
img_out_fourier_view = np.log(np.abs(img_out_fourier) + 1)
plt.imshow(img_out_fourier_view, cmap='gray')
plt.show()

def restore(butter, fourier=img_out_fourier):
    fourier *= butter
    # fourier = np.multiply(fourier, butter)
    img_out_view = np.log10(np.abs(fourier) + 1)
    img_out = np.abs(ifft2(ifftshift(img_out_fourier)))
    return fourier, img_out_view, img_out

_, img_out_view40, img_out40 = restore(butter40)
_, img_out_view70, img_out70 = restore(butter70)
_, img_out_view85, img_out85 = restore(butter85)

plt.figure(figsize=(9.6, 6.4))
plt.subplot(231)
plt.imshow(img_out_view40, cmap='gray')
plt.title(
    'Fourier transform of\n'
    'the restored image\n'
    '(log transformed, $d_0 = 40$)')
plt.subplot(234)
plt.imshow(img_out40, cmap='gray')
plt.title('Restored image')

plt.subplot(232)
plt.imshow(img_out_view70, cmap='gray')
plt.title(
    'Fourier transform of\n'
    'the restored image\n'
    '(log transformed, $d_0 = 70$)')
plt.subplot(235)
plt.imshow(img_out70, cmap='gray')
plt.title('Restored image')

plt.subplot(233)
plt.imshow(img_out_view85, cmap='gray')
plt.title(
    'Fourier transform of\n'
    'the restored image\n'
    '(log transformed, $d_0 = 85$)')
plt.subplot(236)
plt.imshow(img_out85, cmap='gray')
plt.title('Restored image')
plt.savefig('Q6_2_2.png')
plt.show()


from scipy import stats
import numpy as np
from matplotlib import pyplot as plt

x = np.array([112,345,196,325,372,550,302,420,578])
y = np.array([1120,2523,1502,2230,2615,3400,2010,2400,2500])

a, b, r_value, p_value, std_err = stats.linregress(x,y)

plt.plot(x,y, 'ro', color='blue')

plt.xlabel('Size of the house')
plt.ylabel('Price')

plt.axis([0,600,0,3500])
plt.plot(x,x*a+b,'y')
plt.plot()

#PREDICTION !!!
newX = 200
newY = newX*a+b
print(newY)

plt.show()

#PREDICTION !!!

#newX = 150
#newY = newX*a+b

#print(newY)


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

os.system("clear")

## Create a signal with multiple frequencies
dt =  1/ 200    # 2000 Hz Signal
total_t = 3
total_samples = int(total_t / dt)
t = np.linspace(0, total_t, total_samples )  # Timestamps

# Angular Frequencies in the signal
omega_1 = 50
omega_2 = 120

# generate a signal with these two frequencies
clean_signal = np.sin(2 * np.pi * omega_1 * t) + np.sin(2 * np.pi * omega_2 * t)

# Adding Gaussian noise to the signal with a variance
variance = 0.5
noisy_signal = np.random.normal(clean_signal, variance)

# Let's also visualise only the noise
noise = noisy_signal - clean_signal

# Let's Visualise these signals
#plt.plot(t, clean_signal, "r", label = "Clean Signal")
#plt.plot(t, noisy_signal, "b",  label = "Noisy Signal")
#plt.plot(t, noise, "c", label  = "Noise")
#plt.legend()
#plt.show()

## Now that we have Our Signals setup, we will try to filter out this noise
#noisy_signal = clean_signal
# Compute discrete fourier transform of the noisy signal
len_signal = len(noisy_signal)
signal_hat = np.fft.fft(noisy_signal, len_signal)

# Calculate Power Spectral Density
PSD = signal_hat * np.conj(signal_hat) / len_signal

# Create a frequency array for plotting
frequencies = (1 / dt * len_signal) * np.arange(len_signal)

# Let's plot everything now

fig, axs = plt.subplots(2, 1)
plt.sca(axs[0])
plt.plot(t, clean_signal, "r", LineWidth = 2,  label = "Clean Signal")
plt.plot(t, noisy_signal, "b",LineWidth = 1, label = "Noisy Signal")
plt.legend()

plt.sca(axs[1])
plt.plot(frequencies, PSD, "r", LineWidth = 2,  label = "PSD")
plt.legend()
plt.show()

## Now that we have the PSD, we can see some frequencies have the most power whilst the
## most of the other frequencies have very low power. So, based on this power we will
## filter out the other frequencies

# PSD threshold: below this power we will not pickup any signal

psd_threshold = 25  # based on current PSD plot

indices = PSD > psd_threshold

# Frequencies which have power above threshold will stay. Others will be zero
PSD_clean = PSD * indices
signal_hat = indices * signal_hat

# Taking inverse fourier transform now
signal_filtered = np.fft.ifft(signal_hat)

# Let's Visualise these signals
#plt.plot(t, clean_signal, "r", label = "Clean Signal")
plt.plot(t, noisy_signal, "b",  label = "Noisy Signal")
plt.plot(t, signal_filtered, "c", label  = "Filtered Signal")
plt.legend()
plt.show()


# -*- coding: utf-8 -*-
"""
Created on Fri May 27 21:14:27 2016

@author: Michael
"""

import numpy as np
from scipy.fftpack import fft, ifft, fftfreq, fftshift
from numpy import pi

import matplotlib.pyplot as pyplot

L = 0.01
N = 800 # number of points
T = 1.0 / 200.0  # point spacing
x = np.linspace(-N*T/2., (N-1)*T/2., N) # real space sampling points
y = np.exp(-np.abs(x/L))
print 'max frequency: %s' % (1./2/T)
print 'y sum = %s, y avg = %s' % (np.sum(y), np.sum(y)/N)

yf = fft(y)
xf = fftfreq(N, T) # sampling points in frequency domain. First entry corresponds to f=0, then come frequencies in increasing order until the middle of the array. From there on negative frequencies are arranged in decreasing order, i.e. the most negative frequency comes first.
xf = fftshift(xf) # rearrange array entries suitable for display

pyplot.subplot(5, 1, 1)
pyplot.plot(x, y)
pyplot.grid()

# for non-periodic function we have to scale with T because Dirac Comb and because FFT assumes periodicity!
pyplot.subplot(5, 1, 2)
exact = 2.*L / (1 + 4*pi*pi*xf*xf*L*L)
pyplot.plot(xf, T*fftshift(np.abs(yf)))
pyplot.plot(xf, exact, label = 'exact')
pyplot.legend()
pyplot.grid()

pyplot.subplot(5, 1, 3)
y2f = 2.*L/pi / (1 + np.square(fftfreq(N,T))*L*L)
y2  = 1./(T)*fftshift(ifft(y2f))
y2exact = 2.*np.exp(-2.*pi*np.abs(x/L))
pyplot.plot(x, y2)
pyplot.plot(x, y2exact, label = 'exact')
pyplot.legend()
pyplot.grid()

pyplot.subplot(5, 1, 4)
y3f = 2.*L / (1 + np.square(pi*2.*fftfreq(N,T))*L*L)
y3  = 1./(T)*fftshift(ifft(y3f))
y3exact = np.exp(-np.abs(x/L))
pyplot.plot(x, y3)
pyplot.plot(x, y3exact, label = 'exact')
pyplot.legend()

pyplot.subplot(5, 1, 5)
ysin = np.sin(2. * 2.0*pi*x) + 0.2*np.sin(90.5 * 2.0*pi*x) # there is a factor 1/2 in the result because the amplitude contribution is distributed between the positive and negative frequency component!
yfsin = fft(ysin)
pyplot.plot(xf, 1./N*fftshift(np.abs(yfsin)))
pyplot.show()


'''
How About Stock Returns?

In the last exercise, you showed that Amazon stock prices, contained in the DataFrame AMZN follow a random walk. In this exercise. you will do the same thing for Amazon returns (percent change in prices) and show that the returns do not follow a random walk.

INSTRUCTIONS
100XP
Import the adfuller module from statsmodels.
Create a new DataFrame of AMZN returns by taking the percent change of prices using the method .pct_change().
Eliminate the NaN in the first row of returns using the .dropna() method on the DataFrame.
Run the Augmented Dickey-Fuller test on the Adj Close column of the returns DataFrame, and print out the p-value in `results[1].
'''
# Import the adfuller module from statsmodels
from statsmodels.tsa.stattools import adfuller

# Create a DataFrame of AMZN returns
AMZN_ret = AMZN.pct_change()

# Eliminate the NaN in the first row of returns
AMZN_ret = AMZN_ret.dropna()

# Run the ADF test on the return series and print out the p-value
results = adfuller(AMZN_ret['Adj Close'])
print('The p-value of the test on returns is: ' + str(results[1]))


import numpy as np

def check(A):
    dia=[];off =[]
    A=np.array(A)
    n=len(A)
    for i in range(n):
        temp=0
        for j in range(n):
            if i==j:
                dia.append(abs(A[i][j]))
            else:
                temp+=abs(A[i,j])
        off.append(temp)
    dia,off=np.array(dia),np.array(off)
    if np.all(dia > off):
       return True
    else:
         return False

def seidal(a,b,tol):
    n=len(a)
    x=np.zeros(n)
    it=0
    while True:
        x_old = np.array(x)
        for j in range(0, n):
             d = b[j]
             for i in range(0, n):
                  if j != i:
                     d-=a[j][i] * x[i]
             if a[j][j] ==0 :
                 sys.exit("divide by zero detected")
             else:
              x[j] = d / a[j][j]
        dif = abs( x- x_old)
        e=max(dif)
        it += 1
        if abs(e) <= tol:
            break
    return x,it

a=[[-4,1,0],[1,-5,1], [0,1,-3]]
b=[1,-2,1]

#a = [[-3,0,4],[1,8,6],[ 4,2, 8]]  #not dominant
#b = [4,7,3]

j,i=np.shape(a);k=len(b)
'''
if j==k and j==i :
    s=check(a)
    if s==False:
        print("Coefficient Matrix not diagonally dominant.")
    else :
         mat,itr=seidal(a,b,1e-12)
         print("Number of iterations = ",itr)
         print("Solution matrix = ",mat)
         print("Results using numpy.linalg.solve = ",np.linalg.solve(a,b))
else:
   print("Number of equations should be equal to number of variables \nand number of rows in a must be equal to number of rows in b")
'''
if j==k and j==i :
         s=check(a)
         if s==False:
             print("Coefficient Matrix not diagonally dominant.")
         elif s==True:
             print("Coefficient Matrix diagonally dominant.")
         mat,itr=seidal(a,b,1e-5)
         mat,itr=seidal(a,b,1e-5)
         print("Number of iterations = ",itr)
         print("Solution matrix = ",mat)
         print("Results using numpy.linalg.solve = ",np.linalg.solve(a,b))
else:
   print("Number of equations should be equal to number of variables \nand number of rows in a must be equal to number of rows in b")


import numpy
import matplotlib.pyplot as plt
import matplotlib.ticker as mtick
import StanfordStudySensitivity as sss
from scipy import stats

#set variables

SANTA_CLARA_POP = 1928000
SANTA_CLARA_DEATHS = 100
US_POPULATION = 330000000

N = 3330
positive_tests = 50

sensitivity = 0.65
specificity = 0.995

sens_num = 78
sens_den = 85

spec_num = 369
spec_den = 371

#Simulation Parameters
OUTER_LOOPS = 1000
INNER_LOOPS = 500

class StanfordStudyMC():

	def __init__(self):
		self.sss = sss.StanfordStudySensitivity()

	def draw_bin(self,N,p):
		return numpy.random.binomial(N,p)*1.0/N

	def draw_bin_perc(self,N,p):
		try:
			return numpy.random.binomial(N,p)*1.0
		except ValueError:
			print(p)

	def simulate_adjusted_cases(self):
		sampled_pos = []

		# Draw A Specificity
		spec = self.draw_bin(spec_den,spec_num*1.0/spec_den)

		# Draw A Sensitivity
		sens = self.draw_bin(sens_den,sens_num*1.0/sens_den)
		for i in range(INNER_LOOPS):
			sampled_pos.append(self.sss.determine_adjusted_cases(N,self.draw_bin_perc(N,positive_tests*1.0/N),sens,spec))
		return sampled_pos

	def draw_distribution_given_inputs(self,times=1000):
		sampled_pos = []
		for i in range(times):
			sampled_pos.extend(self.simulate_adjusted_cases())
		return sampled_pos

	def times10(self,a):
		return a*10

	def div10(self,a, b):
		return a*1.0/10

	def plot_hist(self):
		# Set up Figure and variables
		fig = plt.figure()
		ax = fig.add_subplot(1,1,1)

		#Plot histogram
		#Adjusted upwards to make density values readable
		data = map(self.sss.adjust_to_implied_prevalence,self.draw_distribution_given_inputs(OUTER_LOOPS))
		plt.hist(data, color='#0504aa',bins=numpy.arange(0,7,0.1),normed=True,stacked=True)

		lb = numpy.percentile(data,2.5)
		ub = numpy.percentile(data,97.5)
		mean = numpy.average(data)
		# X-Axis
		plt.xlabel('Prevalence Rate')
		xticks = mtick.FormatStrFormatter('%.1f%%')
		ax.xaxis.set_major_formatter(xticks)

		plt.ylabel("Probability Density")
		yticks = mtick.FuncFormatter(self.div10)
		ax.yaxis.set_major_formatter(yticks)

		plt.title("Bootstrapped Prevalance Rates")

		plt.text(1.5,0.8,"Mean: %.2f%%\n95%% Confidence Bound: [%.2f%%,%.2f%%] " % (mean,lb,ub))

		plt.show()

if __name__=="__main__":

	ssmc = StanfordStudyMC()
	ssmc.plot_hist()