#numpy imports
import numpy

from numpy import zeros, diff, sqrt, eye
from numpy import pi, cos as _cos, sin as _sin,\
                  arccos as _acos, arcsin as _asin,\
                  arctan2 as _atan2, arctan as _atan

from numpy import sign
from numpy import array as mat, dot, cross, inf
from numpy import linalg
from numpy.linalg import norm

from quaternion import *

#random import
from random import random as rand
#----------------------------------------------------------------------------------------------------------#
def deg_to_rad(x):
    return x * pi / 180.0
#----------------------------------------#
def rad_to_deg(x):
    return x / pi * 180.0
#----------------------------------------#
def atan(x, unit='rad'):
    if unit == 'rad':
        return _atan(x)
    elif unit == 'deg':
        return rad_to_deg(_atan(x))
#----------------------------------------#
def atan2(y, x, unit='rad'):
    if unit == 'rad':
        return _atan2(y,x)
    elif unit == 'deg':
        return rad_to_deg(_atan2(y,x))
#----------------------------------------#
def cos(x, unit='rad'):
    if unit == 'rad':
        return _cos(x)
    elif unit == 'deg':
        return _cos( deg_to_rad(x))
#----------------------------------------#
def sin(x, unit='rad'):
    if unit == 'rad':
        return _sin(x)
    elif unit == 'deg':
        return _sin( deg_to_rad(x))
#----------------------------------------#
def acos(x, unit='rad'):
    if unit == 'rad':
        return _acos(x)
    elif unit == 'deg':
        return rad_to_deg( _acos(x) )
#----------------------------------------#
def asin(x, unit='rad'):
    if unit == 'rad':
        return _asin(x)
    elif unit == 'deg':
        return rad_to_deg( _asin(x) )
#----------------------------------------------------------------------------------------------------------#
def matmul(*matrix_factors):
    '''
        Takes a list of matrices as arguments and perform
        a series of matrix multiplications from left to right
        in the order given.

        The parameters may contain vectors and scalars as well as long as
        the matrix-vector dimensions are the same.

        Note:
        The sequence can not start with a scalar, it must start
        with either a vector or a matrix of type numpy.ndarray.
    '''
    return reduce(dot, matrix_factors, 1)
#----------------------------------------------------------------------------------------------------------#
def matmul_series(*matrix_factors):
    '''
        Takes a list of matrices as arguments and perform
        a series of matrix multiplications from left to right
        in the order given, but contrary to matmul
        it returns each partial result in a list.

        The parameters may contain vectors and scalars as well as long as
        the matrix-vector dimensions are the same.

        Note:
        The sequence can not start with a scalar, it must start
        with either a vector or a matrix of type numpy.ndarray.
    '''
    res = []
    res.append(matrix_factors[0])
    for i in xrange(1,len(matrix_factors)):
        res.append(reduce(dot, matrix_factors[:i+1], 1))
    return res
#----------------------------------------------------------------------------------------------------------#
def homogenous_matrix(*args):
    """
    Creates a homogenous matrix ( 4x4 matrix of type [[R,t],[0,1]] ),
    allows input on the forms:

    -1:   No input
    0:    R                                       (1x)
    1:    (rot, tilt, skew, x,y,z)                (3x, 3x)
    2:    (rot, tilt, skew, t)                    (3x, 1x)
    3:    (angles, x,y,z)                         (1x, 3x)
    3.1:  (R, x,y,z)                              (1x, 3x)
    4:    (R, t), where R is list or numpy.array  (1x, 1x)
    """
    l_args = len(args)
    valid_types  = [tuple, list, numpy.ndarray]

    ## TODO: separate into subcase-functions (!!!)
    if  l_args == 0: #case -1: no input
        R = eye(4)
        t = [0,0,0]
    elif  l_args == 1: #case 0
        if type(args[0]) in [list, tuple]:
            R = mat(args[0])
        else:
            R = args[0]
        m,n = R.shape
        if m == n:
            R = expand_matrix(R)
            return R
        else:
            return R
    elif  l_args == 2: #case 4
        R,t = args
    elif l_args == 4:
        if type(args[0]) in valid_types:
            if type(args[0][0] ) in valid_types: #case 3.1
                R,t = args[0], args[1:]
            else:
                (rot, tilt, skew), t = args[0], args[1:] #case 3
        else: #case 2
            (rot, tilt, skew), t = args[:3], args[3:][0]
    elif l_args == 6: #case 1
        (rot, tilt, skew), t = args[:3], args[3:]

    if not type(t) in valid_types:
        raise ArithmeticError('translation part can not be a scalar.')
    elif len(t) < 3:
        raise ArithmeticError('translation part must be of dimension 3.')

    if not 'R' in locals():
        R = rotation_matrix_rot_tilt_skew(rot, tilt, skew)

    T = zeros((4,4))
    m,n = R.shape

    T[:m, :n] = R
    T[:3, 3] = t[:3]
    T[3, :] = [0, 0, 0, 1]
    return T
#----------------------------------------------------------------------------------------------------------#
def homogenous_matrices(frames):
    """
    Creates a matrix of homogenous matrices( 4x4 matrix of type [[R,t],[0,1]] ),
    each frame in the input 'frames' can have one of following descriptions:
    0:    R                                       (1x)
    1:    (rot, tilt, skew, x,y,z)                (3x, 3x)
    2:    (rot, tilt, skew, t)                    (3x, 1x)
    3:    (angles, x,y,z)                         (1x, 3x)
    3.1:  (R, x,y,z)                              (1x, 3x)
    4:    (R, t), where R is list or numpy.array  (1x, 1x)
    """
    return mat( map(lambda x: homogenous_matrix(*x), frames) )
#----------------------------------------------------------------------------------------------------------#
def homogenous_translation_x( tx ):
    return mat([[1,        0,      0,     tx],
                [0,        1,      0,      0],
                [0,        0,      1,      0],
                [0,        0,      0,      1]])
#----------------------------------------------------------------------------------------------------------#
def homogenous_translation_z( tz ):
    return mat([[1,     0,     0,      0],
                [0,     1,     0,      0],
                [0,     0,     1,      tz],
                [0,     0,     0,      1]])
#----------------------------------------------------------------------------------------------------------#
def homogenous_rotation_x( ang ):
    #convert to radians
    c = cos(ang * pi / 180)
    s = sin(ang * pi / 180)
    return mat([[1,     0,      0,      0],
                [0,     c,     -s,     0],
                [0,     s,     c,      0],
                [0,     0,     0,      1]])
#----------------------------------------------------------------------------------------------------------#
def homogenous_rotation_z( ang ):
    #convert to radians
    c = cos(ang * pi / 180)
    s = sin(ang * pi / 180)
    return mat([[c,     -s,    0,     0],
                [s,     c,     0,     0],
                [0,     0,     1,     0],
                [0,     0,     0,     1]])
#----------------------------------------#
def expand_matrix(matrix, k=1,l=1):
    if k < 0 or l < 0:
        raise ArithmeticError('Only positive values (or zero) allowed for changing the dimension of the matrix.')
    m,n = matrix.shape
    res = numpy.eye(m+k,n+l)
    res[0:m, 0:n] = matrix
    return res
#----------------------------------------#
def rotation_matrix_z(angle):
    '''
        creates a rotation Z-mapping from subspace to worldspace
        using euler angles in degrees
    '''
    angle = deg_to_rad(angle)
    return mat([[ cos(angle), -sin(angle), 0 ],
                [ sin(angle),  cos(angle), 0 ],
                [  0   ,    0  , 1 ]])
#----------------------------------------#
def rotation_matrix_x(angle):
    '''
        creates a rotation X-mapping from subspace to worldspace
        using euler angles in degrees
    '''
    angle = deg_to_rad(angle)
    return mat([[ 1,     0     ,           0 ],
                  [ 0, cos(angle), -sin(angle) ],
                  [ 0, sin(angle),  cos(angle)]])
#----------------------------------------#
def rotation_matrix_y(angle):
    '''
        creates a rotation Y-mapping from subspace to worldspace
        using euler angles in degrees
    '''
    angle = deg_to_rad(angle)
    return mat([[ cos(angle),     0     ,   sin(angle) ],
                  [ 0,              1     ,   0 ],
                  [ -sin(angle),    0     ,   cos(angle)]])
#----------------------------------------#
def rotation_matrix_rot_tilt_skew(rot, tilt, skew):
    '''
        creates a rotation ZXZ-mapping from subspace to worldspace
        using euler angles in degrees (extrinsic mapping)

        (!) Note - rot is rotated counter-clockwise compared to ordinary euler z-rotation.
    '''
    return matmul(rotation_matrix_z(-rot), rotation_matrix_x(tilt), rotation_matrix_z(skew))
#----------------------------------------#
def rotation_matrix_x_y_z(rx,ry,rz):
    '''
        creates a rotation XYZ-mapping from subspace to worldspace
        using euler angles in degrees (extrinsic mapping)
    '''
    return matmul(rotation_matrix_x(rx), rotation_matrix_y(ry), rotation_matrix_z(rz))
#----------------------------------------#
def rotation_matrix_skew_tilt_rot(rot, tilt, skew):
    '''
        creates a rotation ZXZ-mapping from subspace to worldspace
        using euler angles in degrees (intrinsic mapping)

        (!) Note - rot is rotated counter-clockwise compared to ordinary euler z-rotation.
    '''
    return matmul(rotation_matrix_z(skew), rotation_matrix_x(tilt), rotation_matrix_z(-rot))
#----------------------------------------#
def rotation_matrices(rts):
    '''
        creates a rotation ZXZ-mapping from subspace to worldspace
        using euler angles in degrees (intrinsic mapping)

        (!) Note - rot is rotated counter-clockwise compared to ordinary euler z-rotation.
    '''
    M = lambda x: homogenous_matrix(rotation_matrix_rot_tilt_skew(*x))
    return nmap(M, rts)
#----------------------------------------#
def coordinate_system_from_two_directions(dirz, diry):
    '''
        Generates an orthogonal coordinate system from two vectors in world space,
        the vectors themselves need not be orthogonal.

        Alternative interpretation:
        creates a (general) rotation mapping from subspace to worldspace
        using vector directions in world-space
    '''
    dirz = dirz / norm(dirz)
    diry = diry / norm(diry)
    diry = gram_schmith_step(diry, dirz)
    dirx = cross(diry, dirz)
    return mat([dirx, diry, dirz]).T
#----------------------------------------#
def get_normalized_vector(*components):
    return mat(components) / norm(components)
#----------------------------------------#
def orthogonal_projection_vectors(a, b):
    '''
        Orthogonal projection of vector a onto vector b
    '''
    c = b / norm(b)
    return a.dot(c) * c
#----------------------------------------#
def gram_schmith_step(a, b):
    '''
        Calculates with the help of vector A the orthogonal component relative to the
        parallell direction vector B
    '''
    return a - orthogonal_projection_vectors(a, b)
#----------------------------------------#
# MISC
#----------------------------------------#
def rand_range(low, high):
        if low > high:
                low, high = high, low
        return low + rand()*(high-low)
#----------------------------------------#
def nmap(func, *seq):
    return mat( map(func, *seq) )

def nzip(*seq):
    return mat( zip(*seq) )

def lop(func, a,b):
    return nmap( lambda x: reduce(func, x), zip(a,b))
#----------------------------------------#
def rot_tilt_skew(R):
    zs_vector = R[2,:] #matmul(R.T, numpy.array([0, 0,  1]) )
    zp_vector = -R[:,2] #matmul(R , numpy.array([0, 0, -1]) )

    #angle between normal and zp_vector direction
    #rot  = atan2( zp_vector[0], zp_vector[1] ) * 180 / pi
    rot  = atan2( zp_vector[0], zp_vector[1]) * 180 / pi
    tilt = -atan( norm(zp_vector[0:2]) / zp_vector[2] ) * 180 / pi
    skew = -atan2( zs_vector[0], zs_vector[1] ) * 180 / pi

    if tilt < 0.01:
        x_vector = R[:,0]
        z_vector = R[:,2]
        rot = atan2(x_vector[0], x_vector[1]) * 180 / pi
    return (rot, tilt, skew)


"""
Module for doing the training
"""

from __future__ import division
import numpy as np
import math
import plot as plot
from numpy import matrix
from numpy import linalg

def trainUnweighted (train_X, train_Y):
    """
    Trains the unweighted linear regression model
    Theta = (X.T * X)-1 * X.T * Y
    """

    X_matrix = np.matrix(train_X).T
    Y_matrix = np.matrix(train_Y).T

    X_ones = np.ones((X_matrix.shape[0], 1))
    X_matrix = np.hstack((X_ones, X_matrix))

    return np.linalg.inv(X_matrix.T * X_matrix) * X_matrix.T * Y_matrix

def displayWeighted (train_X, train_Y, tau=0.8):
    """
    For each point trains the weighted model
    and plots a point
    """
    train_X.sort()
    h_theta = []
    for x_i in train_X:
        prediction = float(trainWeighted(train_X, train_Y, x_i, tau))
        h_theta.append(prediction)

    plot.linePlot(train_X, train_Y, h_theta, fileName=("Q2/plots/weighted-" + str(tau).replace('.','_')), title=("Weighted - Tau=" + str(tau)))

def trainWeighted (train_X, train_Y, x, tau):
    """
    Trains the weighted linear regression model, for a single query point
    Theta = (X.T * W * X)-1 * X.T * W * Y
    """

    X_matrix = np.matrix(train_X).T
    Y_matrix = np.matrix(train_Y).T
    x_matrix = np.matrix([1,x]).T

    X_ones = np.ones((X_matrix.shape[0], 1))
    X_matrix = np.hstack((X_ones, X_matrix))

    W_matrix = generateW (train_X, x, tau)

    # print (X_matrix.shape, W_matrix.shape, Y_matrix.shape)
    Theta = np.linalg.inv(X_matrix.T * W_matrix * X_matrix) * X_matrix.T * W_matrix * Y_matrix
    prediction = Theta.T * x_matrix
    return prediction

def generateW (train_X, x, tau):
    """
    Generate the diagonal weight matrix W for the training set train_X and query x, with parameter tau
    """

    weights = []
    for x_i in train_X:
        expo =( (x - x_i)**2) / (-2.0 * tau**2)
        weights.append(math.exp(expo))
    return np.diag(weights)


# nelder-mead optimization of noisy one-dimensional convex function
from scipy.optimize import minimize
from numpy.random import rand
from numpy.random import randn

# objective function
def objective(x):
	return (x + randn(len(x))*0.3)**2.0

# define range for input
r_min, r_max = -5.0, 5.0
# define the starting point as a random sample from the domain
pt = r_min + rand(1) * (r_max - r_min)
# perform the search
result = minimize(objective, pt, method='nelder-mead')
# summarize the result
print('Status : %s' % result['message'])
print('Total Evaluations: %d' % result['nfev'])
# evaluate solution
solution = result['x']
evaluation = objective(solution)
print('Solution: f(%s) = %.5f' % (solution, evaluation))


def haversine_np(longitude1, latitude1, longitude2, latitude2):
    """
    Return the great circle distance between two points on the earth (specified in decimal degrees). All args must be of equal length. Reference from https://stackoverflow.com/a/4913653, https://stackoverflow.com/a/29546836

    Parameters
    ----------
    longitude1: float
        Base longitude
    latitude1: float
        Base latitude
    longitude2: float
        Longitude to compare
    latitude2: float
        Latitude to compare

    Returns
    -------
    km : float
    """
    import numpy as np

    lon1, lat1, lon2, lat2 = map(
        np.radians, [longitude1, latitude1, longitude2, latitude2]
    )

    dlon = lon2 - lon1
    dlat = lat2 - lat1

    a = np.sin(dlat / 2.0) ** 2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon / 2.0) ** 2

    c = 2 * np.arcsin(np.sqrt(a))
    km = 6371 * c
    return km

if __name__ == "__main__":
    import doctest

    doctest.testmod()


import numpy as np
import matplotlib.pyplot as plt

def sigmoid(x, w, b):
    return 1/(1 + np.exp(-x*w+b))

x = np.arange(-5.0, 5.0, 0.1)
y1 = sigmoid(x, 0.5, 0)
y2 = sigmoid(x, 1, 0)
y3 = sigmoid(x, 2, 0)

plt.plot(x, y1, "r", linestyle='--')
plt.plot(x, y2, 'g')
plt.plot(x, y3, 'b', linestyle='--')
plt.plot([0, 0], [1.0, 0.0], ":")
plt.title("sigmoid function")
plt.show()


import numpy as np

# probabilities for 3 samples
softmax_outputs = np.array([[0.7, 0.1, 0.2],
                            [0.1, 0.5, 0.4],
                            [0.02, 0.9, 0.08]])

# indices of the softmax output distribution
#class_targets = np.array([0, 1, 1])
class_targets = np.array([[1, 0, 0], [0, 1, 0], [0, 1, 0]])

# probabilities for target values -
# only if categorical labels
if len(class_targets.shape) == 1:
    correct_confidences = softmax_outputs[
        range(len(softmax_outputs)),
        class_targets]

# mask values - only for one-hot encoded labels
elif len(class_targets.shape) == 2:
    correct_confidences = np.sum(
        softmax_outputs * class_targets, axis=1)

print(correct_confidences)

# losses
neg_log = -np.log(correct_confidences)

average_loss = np.mean(neg_log)
print(average_loss)


import numpy as np

from mab.base.base_mab import MultiArmedBanditProblem

class BernoulliTS(MultiArmedBanditProblem):
    def __init__(self,number_of_arms, number_of_trials,prior_alphas,prior_betas):
        super(BernoulliTS,self).__init__(number_of_arms, number_of_trials)

        if (prior_alphas < 0).any():
            raise ValueError('Prior value of alpha parameters has to be positive or zero ')
        elif (prior_betas < 0).any():
            raise ValueError('Prior value of beta parameters has to be positive or zero ')
        elif len(prior_alphas)!=len(prior_betas)!=number_of_arms:
            raise ValueError('Prior alpha and beta vectors must have the same length')

        self.alphas = prior_alphas.copy()
        self.betas = prior_betas.copy()

    def warm_up(self,seed):
        np.random.seed(seed)

        self.step_alphas = np.zeros((self.number_of_trials,self.number_of_arms))
        self.step_betas = np.zeros((self.number_of_trials,self.number_of_arms))

    def run_single_step(self, step, data):
        arms_average_rewards = np.random.beta(self.alphas,self.betas)
        selected_arm = self.get_max_index(arms_average_rewards)
        self.selected_arms[step] = selected_arm
        self.number_of_selections[selected_arm] += 1

        self.alphas[selected_arm] += data[step,selected_arm]
        self.betas[selected_arm] += (1 - data[step,selected_arm])

        self.step_alphas[step] = self.alphas
        self.step_betas[step] = self.betas

    def run_experiment(self,seed,data):

        self.warm_up(seed)

        for step in range(self.number_of_trials):
            self.run_single_step(step,data)


# -*- coding: utf-8 -*-
import sympy as sp

x = sp.symbols('x')

print("sin'(x) = {0}".format(sp.diff(sp.sin(x), x)))
print("sin'(x^2) = {0}".format(sp.diff(sp.sin(x ** 2), x)))
print("log'(x) = {0}".format(sp.diff(sp.log(x), x)))

sigmod = (1 / (1 + sp.E ** -x))

sigmod_diff = sp.diff(sigmod, x)

print("sigmod'(x) = {0}".format(sigmod_diff))

assert (sigmod * (1 - sigmod)).simplify() == sigmod_diff.simplify()

theta0, theta1, theta2 = sp.symbols('theta0 theta1 theta2')
x1, x2, y = sp.symbols('x1 x2 y')

sse = (theta0 + theta1 * x1 + theta2 * x2 - y) ** 2 / 2

print('partial(θ0) = {0}'.format(sp.diff(sse, theta0)))
print('partial(θ1) = {0}'.format(sp.diff(sse, theta1)))
print('partial(θ2) = {0}'.format(sp.diff(sse, theta2)))

p, n, k = sp.symbols('p n k')
likelihood = p ** k * (1 - p) ** (n - k)
print(sp.diff(likelihood, p).simplify())


import numpy as np

def feasible_starting_point(y, C):
    """
    Calculate feasible starting point for
    Barrier method specific for SVM dual problem.

    Parameters:
    -----------
    y : `numpy.ndarray`
        (nData,) The labels.
    C : `float`
        Regularization parameter. The strength of the regularization is
        inversely proportional to C. Must be strictly positive.
        The penalty is a squared l2 penalty.

    Returns:
    --------
    x0 : numpy.ndarray`
        (nData,1) The starting point for the feasible Newton method.
    """
    nData = len(y)
    nPos = np.sum(y==1)
    pos_neg_frac = nPos / (nData-nPos)
    x0 = np.zeros((nData,1))
    for i in range(nData):
        if y[i]==1:
            x0[i] = C*(1-nPos/nData)
        else:
            x0[i] = C*pos_neg_frac*(1-nPos/nData)
    return x0


import numpy as np
# 行向量
v = np.array([[0, 1, 2]])
print(f"行向量v1=\n{v}",v.shape)
# 列向量
v = np.array([[0], [1], [2]])
print(f"列向量v2=\n{v}",v.shape)

v=np.array([1,2,3])
print(f"真实向量v={v}",v.shape)
print(f"真实向量v={v.T}",v.shape)

# 创建矩阵
matrix = np.mat([[1, 2], [3, 4]])

print(f"矩阵matrix=\n{matrix}")
print(f"矩阵matrix的转置=\n{matrix.T}")

f = np.vectorize(lambda i: i*i)

print(f"矩阵函数:\n{f(matrix)}")
# 矩阵的秩

print(f"矩阵的秩\n{np.linalg.matrix_rank(matrix)}")

# 行列式
print(f"矩阵的行列式\n{np.linalg.det(matrix)}")

# 迹
print(f"矩阵的迹\n{matrix.trace()}")

# 特征向量和特征值
eigenvalues, eigenvectors = np.linalg.eig(matrix)
print(f"特征值和特征向量\n {eigenvalues}\n{eigenvectors}")

# 矩阵的逆
m = np.mat([[1, 2], [3, 4]])
result = np.linalg.inv(m)
print(f"矩阵的逆矩阵\n {result}")

# 矩阵的左乘
m1 = np.mat([[1, 2], [3, 4]])
m2 = np.mat([[0, 1], [1, 2]])
print(f"np.dot(m1, m2)=\n{np.dot(m1, m2)}")
print(f"矩阵的左乘\n {m1 @ m2}\n{m2 @ m1}")
print(f"矩阵的点乘\n {m1 * m2}\n {m2*m1}")

# 向量的点积
v1 = np.array([1, 0])
v2 = np.array([0, 1])
d1 = np.dot(v1, v2)
d2 = np.vdot(v1, v2)
d3 = np.inner(v1, v2)
d4 = np.outer(v1, v2)
d5 = np.cross(v1, v2)
print(f"d1= {d1}")
print(f"d2= {d2}")
print(f"d3={d3}")
print(f"d4=\n{d4}")
print(f"d5\5=\n{d5}")
print("---------------------")
# TT^-1
m = np.mat([[1, 2], [2, 3]])
m_inv = np.linalg.inv(m)
print(m @ m_inv)
print(m_inv)

# 矩阵x向量
print("----------")
m = np.array([[1, 2, 3], [3, 4, 5]])
print(m, m.shape, type(m))
# v = np.array([[1], [2], [3]])
v = np.array([1, 2, 3]).T
print(v, v.shape, type(v))
result = np.dot(m, v)
print(result, result.shape, type(result))

# 矩阵x矩阵
m1 = np.mat([[1, 2], [3, 4]])
m2 = np.mat([[2, 2], [1, 1]])
result = np.dot(m1, m2)
# result = m1 @ m2 等价
# result = m1 * m2 等价
print(result)
# 矩阵乘向量
v=np.array([1,2,3]).T
print(v,v.shape)
m=np.mat([[1,2,3],[1,0,1]])

result=np.dot(m,v)
print(f"矩阵x 向量={result} {type(result)} {result.shape}")
print("-------------")
# 向量的内积
v1 = np.array([0,2,1])
v2 = np.array([2,0,1])
inner_product = v1 @ v2
inner_product = np.inner(v1, v2)
print(inner_product)

# 向量的外积/叉积
cross_product=np.cross(v1,v2)
print(cross_product)

# 方程求解
m=np.array([[1,0],[0,1]])
y=np.array([[2,0],[0,2]])
result=np.linalg.solve(m,y)
print(result)


import sys
import math
from scipy.stats import hmean

def calInteractionAngle(v1,v2):
	return math.atan(v1/v2)*(180/math.pi)

def generateInteractionFeatures(sisVec):

	newFeatVec = []
	for i in range(len(sisVec)):
		for j in range(len(sisVec)):
			if i >= j: continue

			v1, v2 = sisVec[i], sisVec[j]
			feat1 = v1*v2
			feat2 = calInteractionAngle(v1,v2)
			feat3 = hmean([v1,v2])
			newFeatVec.extend([feat1,feat2,feat3])

	return newFeatVec

# The input matrix should have 15 SIS values and 1 fatality label at the right side of the matrix
# In the output matrix file, the newly generated 315 features will be added.
file = sys.argv[1] # input matrix file

fw = open("[your outfile matrix hame here]",'w')
fr = open(file,'r')

line = fr.readline()
m = line.rstrip().split("\t")
fw.write(m[0])
for v in m[1:-1]: fw.write("\t" + v)

for i in range(15):
	for j in range(15):
		if i >= j: continue
		label = str(i+1)+"-"+str(j+1)
		fw.write("\t"+label+"_multiply"+"\t"+label+"_atan"+"\t"+label+"_hmean")
fw.write("\t"+m[-1]+"\n")

for line in fr:
	m = line.rstrip().split("\t")
	sisVec = m[15:-1]
	for i in range(len(sisVec)): sisVec[i] = float(sisVec[i])

	newFeatVec = generateInteractionFeatures(sisVec)
	newVec = m[:-1] + newFeatVec + [m[-1]]
	fw.write(newVec[0])
	for v in newVec[1:]: fw.write("\t" + str(v))
	fw.write("\n")

fr.close()
fw.close()


# Linear Regression Gradient Descent with Single Feature

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

house_sales = pd.read_csv('house_sales.csv')

sqft_living = house_sales[['sqft_living']].values.flatten()[:, np.newaxis]
sqft_lot = house_sales[['sqft_lot']].values.flatten()[:, np.newaxis]
bedrooms = house_sales[['bedrooms']].values.flatten()[:, np.newaxis]
bathrooms = house_sales[['bathrooms']].values.flatten()[:, np.newaxis]

data_m = np.hstack((sqft_living, sqft_lot, bedrooms, bathrooms))
stdm = np.std(data_m, axis=0)
mum = np.mean(data_m, axis=0)
data_m = (data_m - mum) / stdm
ones = np.ones((len(data_m), 1))
data_m = np.hstack((ones, data_m))
print('data_m', data_m)

# setup features
data_x = house_sales[['sqft_living']].values.flatten()[:, np.newaxis]
stdx = np.std(data_x, axis=0)
mux = np.std(data_x, axis=0)
data_x = (data_x - mux) / stdx
data_x = np.hstack((np.ones_like(data_x), data_x))

print('data_x', data_x)

# setup targets
data_y = house_sales[['price']].values.flatten()[:, np.newaxis]

# separate into training and testin datasets
order = np.random.permutation(len(data_x))
portion = 10000
test_x = data_x[order[:portion]]
test_y = data_y[order[:portion]]
train_x = data_x[order[portion:]]
train_y = data_y[order[portion:]]

def get_gradient(w, x, y):
    # matrix multiply x by weights
    y_estimate = x.dot(w).flatten()

    # calculate error between actual values and estimates
    error = (y.flatten() - y_estimate)

    mse = (1 / len(x)) * np.sum(np.power(error, 2))

    # calculate the gradient
    # error.dot(x) multiplies the error for each observation
    # by the value of the feature use to predict
    gradient = -(1 / len(x)) * error.dot(x)

    return gradient, mse

# variables for use in gradient descent algorithm
w = [0, 0]
alpha = 0.5
tolerance = 1e-5

# Perform Gradient Descent
iterations = 1
while True:
    gradient, error = get_gradient(w, train_x, train_y)
    new_w = w - alpha * gradient

    # Stopping Condition
    if np.sum(abs(new_w - w)) < tolerance:
        print('converged')
        break

    # Print error every 50 iterations
    if iterations % 100 == 0:
        print("Iteration:", iterations)
        print("Error:", np.sum(error))

    iterations += 1
    w = new_w

print('w =', w)
print('Test Cost = ', get_gradient(w, test_x, test_y)[1])
print('xdotw', data_x.dot(w))
print('x', data_x[:, 1])

plt.plot(data_x[:,1], data_x.dot(w), c='g', label='Model')
plt.scatter(train_x[:,1], train_y, c='b', label='Train Set')
plt.scatter(test_x[:,1], test_y, c='r', label='Test Set')
plt.grid()
plt.legend(loc='best')
plt.xlabel('X')
plt.ylabel('Y')
plt.show()


import math
import random
import numpy as np

def vectorToDistMatrix(coords):
    '''
    Create the distance matrix
    '''
    return np.linalg.norm(coords[:, np.newaxis] - coords[np.newaxis, :], axis=2)

def nearestNeighbourSolution(dist_matrix):
    '''
    Computes the initial solution (nearest neighbour strategy)
    '''


from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
import numpy as np
import argparse
import datetime
import pickle
import csv

X, y = [], []

def load_dataset(infile):
    global X, y
    with open(infile) as fp:
        reader = csv.DictReader(fp)
        for each_line in reader:
            X.append(np.array([
                float(each_line["light luminosity"]),
                float(each_line["IR Status"]),
                float(each_line["Ultrasonic sensor status"]),
                float(each_line["time of the day"].replace(":",""))
                ]))
            y.append(each_line["predicted light luminosity"])
    X = np.array(X)

def train(outfile):
    x_train, x_test,y_train,y_test = train_test_split(X,y,test_size=0.2)
    reg = LinearRegression().fit(x_train, y_train)
    preds = reg.predict(x_test)
    # R-Squared is a statistical measure of fit that indicates how much
    # variation of a dependent variable is explained by the independent
    # variable(s) in a regression model.
    r2score = r2_score(y_test, preds)
    root_mean_sqerror = np.sqrt(mean_squared_error(y_test, preds))
    print("R squared score: ", r2score)
    pickle.dump(reg, open(outfile, 'wb'))
    print("Training complete and the model is stored at %s" % outfile)

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--input", type=str)
    parser.add_argument("--output", type=str)
    args = parser.parse_args()
    print(args.input, args.output)
    load_dataset(args.input)
    train(args.output)


import numpy

# Earth equatorial radius
Re = 6378137.0

# flattening parameter
f  = 1.0 / 298.2572235630

# polar radius
Rp = Re * (1.0 - f)

# eccentricity
e  = (Re**2 - Rp**2)/(Re**2)

def geod2cart(rlat, rlon, height):
    """
    Geodetic to Cartesian coordinate conversion

    Call
        cart = geod2cart(rlat, rlon, height)

    Input
        rlat -- NumPy float array of Geodetic latitudes
        rlon -- NumPy float array of Geodetic longitudes
        height -- NumPy float array of heights (m)

    Output
        cart -- tuple, x, y, and z coordinates in meters
    """

    flatfn = (2.0 - f) * f
    funsq = (1.0 - f)**2
    # rlat = numpy.deg2rad(lat)
    # rlon = numpy.deg2rad(lon)

    gd = Re/numpy.sqrt(1.0 - flatfn*numpy.sin(rlat)**2)

    cart =  numpy.array([numpy.cos(rlat)*numpy.cos(rlon)*(gd + height),
            numpy.cos(rlat)*numpy.sin(rlon)*(gd + height),
            numpy.sin(rlat)*(gd*funsq + height)])
    return cart

def Reff(lat):
    """
    Calculate effective radius given a latitude
    """

    a_earth = 6378137.0 #     ! semi - major    axis(m)
    flatt = 0.003352811 #     ! flattening
    gm_ratio = 0.003449787 #  ! gravitational    ratio

    sinlat2 = numpy.sin(numpy.deg2rad(lat))**2
    return a_earth / (1.0 + flatt + gm_ratio - 2.0 * flatt * sinlat2)

def curvature(lat, lon, theta):
    '''
    :param lat:    Surface point latitude
    :param lon:    Surface point longitude
    :param theta: Azimuth direction
    :return:
    r_coc centre of curvature
    roc Radius of curvature
    '''
    rlat = numpy.deg2rad(lat)
    rlon = numpy.deg2rad(lon)
    x = geod2cart(rlat, rlon, 0.0)
    atoc2 = (Re / Rp) ** 2
    xy2 = x[0] ** 2 + x[1] ** 2
    z2 = (atoc2 * x[2]) ** 2
    cphi2 = xy2 / (xy2 + z2)
    sphi2 = z2 / (xy2 + z2)
    r_EW = 1.0 / numpy.sqrt( cphi2 + (sphi2/atoc2) )  # normalised wrt Re
    r_NS = r_EW**3 / atoc2                         # normalised wrt Re
    roc = Re / ( (numpy.cos(theta)**2/r_NS) + (numpy.sin(theta)**2/r_EW) )

    n = numpy.array([numpy.cos(rlat) * numpy.cos(rlon),
         numpy.cos(rlat) * numpy.sin(rlon),
         numpy.sin(rlat)])

    r_coc = x - roc * n
    return roc, r_coc


import numpy as np
import numba as nb

@nb.jit(nopython=True)
def laplacian_LDO(N):
    """Generate the laplacian linear differential operator for a grid with N points:

        -2      1       0       0
        1       -2      1       0
        0       1       -2      1  .  .  .
                .
                .
                .

    """

    A = np.zeros((N-2, N-2))
    for i in range(1, N-3):
        A[i, i-1] = 1
        A[i, i] = -2
        A[i, i+1] = 1

    A[0, 0] = -2
    A[0, 1] = 1
    A[N-3, N-3] = -2
    A[N-3, N-4] = 1

    return A

@nb.jit(nopython=True)
def prolongation(N):
    """Generates the interpolation operator for going from a grid with (N-1)/2 points to a grid with N points:

        0.5     0       0
        1       0       0
        0.5     0.5     0   .   .   .
        0       1       0
        0       0.5     0.5
                .
                .
                .

    """

    I = np.zeros((N, int((N-1)/2)))
    I[0, 0] = 0.5
    I[-1, -1] = 0.5
    for i in range(1, N-1):
        if i % 2 == 0:
            I[i, int(i/2)] = 0.5
            I[i, int(i/2-1)] = 0.5
        else:
            I[i, int((i-1)/2)] = 1

    return I

@nb.jit(nopython=True)
def full_weighting(N):
    """Returns the restriction operator for going from a grid with 2N points to a grid with N points. This is just the
    transpose of the prolongation operator (up to a factor).
    """
    return np.transpose(prolongation(N))*0.5

def get_good_grid_sizes(N):
    """Return an array of length N containing good grid sizes to use for multigrid. If you use a grid which cannot be
    coarsened by the VMG or FMG solvers, you won't see any speedup over Jacobi iteration alone. The grid sizes given
    by this function can be coarsened multiple times, making them ideal for use with multigrid.
    """

    a = np.zeros(N+1, dtype=int)
    a[0] = 7
    for i in range(1, N+1):
        a[i] = int(2*a[i-1] + 1)
    return a+2


from __future__ import division

import math

import numpy as np

def polar2cartesian(rho, phi):
    return [rho * math.cos(phi),
            rho * math.sin(phi)]

def cartesian2polar(x, y):
    return [math.sqrt(x ** 2 + y ** 2),
            math.atan2(y, x)]

def spherical2cartesian(rho, phi, theta):
    return [rho * math.sin(theta) * math.cos(phi),
            rho * math.sin(theta) * math.sin(phi),
            rho * math.cos(theta)]

def cartesian2spherical(x, y, z):
    rho = math.sqrt(x ** 2 + y ** 2 + z ** 2)
    return [rho,
            math.atan2(y, x),
            math.acos(z / rho) if rho != 0 else 0]

def cartesian2auto(vector):
    if len(vector) == 2:
        return cartesian2polar(vector[0], vector[1])
    return cartesian2spherical(vector[0], vector[1], vector[2])

def rad2deg(rad):
    return rad / math.pi * 180

def deg2rad(deg):
    return deg / 180 * math.pi

def get_distance(x0, y0, x1, y1):
    return math.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2)

def vector_magnitude(vector):
    return np.linalg.norm(vector)

def c2d():
    return np.array([0, 0])

def rotate(vector, matrix):
    return (vector * matrix).getA()[0]


import sympy as sp

def hat(x, i, n):
    hi = sp.Piecewise(
        (0, x < (i - 1) / (n + 1)),
        (0, x > (i + 1) / (n + 1)),
        ((n + 1) * x - i + 1, x < i / (n + 1)),
        (-(n + 1) * x + i + 1, x > i / (n + 1))
    )
    return hi

def hats_sp(x, n):
    hats = []
    for i in range(1, n + 1):
        hats.append(hat(x, n, i))

    return hats

if __name__ == "__main__":
    x, i, j, n, p = sp.symbols('x, i, j, n, p')
    rsv = i - (n+1)*x + 1
    lsv = (n+1)*x - i + 2

    bi = (i / (n + 1), (i + 1) / (n + 1))
    p22 = sp.integrate(rsv ** 2 * lsv ** 2, (x, *bi))
    p31 = sp.integrate(rsv ** 3 * lsv ** 1, (x, *bi))
    p40 = 2 * sp.integrate(rsv ** 4, (x, *bi))
    p22 = sp.simplify(p22)
    p31 = sp.simplify(p31)
    p40 = sp.simplify(p40)

    print("22:", p22)
    print("31:", p31)
    print("40:", p40)

    rsvp = rsv.diff(x)
    lsvp = lsv.diff(x)

    p22p = sp.integrate(rsvp ** 2 * lsvp ** 2, (x, *bi))
    p31p = sp.integrate(rsvp ** 3 * lsvp ** 1, (x, *bi))
    p13p = sp.integrate(rsvp ** 1 * lsvp ** 3, (x, *bi))
    p40p = 2 * sp.integrate(rsvp ** 4, (x, *bi))
    p11p = sp.integrate(rsvp ** 1 * lsvp ** 1, (x, *bi))
    p20p = 2 * sp.integrate(rsvp ** 2, (x, *bi))
    p22p = sp.simplify(p22p)
    p31p = sp.simplify(p31p)
    p13p = sp.simplify(p13p)
    p40p = sp.simplify(p40p)
    p11p = sp.simplify(p11p)
    p20p = sp.simplify(p20p)

    print("22p:", p22p)
    print("31p:", p31p)
    print("13p:", p31p)
    print("40p:", p40p)
    print("11p:", p11p)
    print("20p:", p20p)


"""
In this file we compare the performance of numerical methods to solve
differential equations describing first order systems.
These include the Euler and improved-Euler as well as the Runge-Kutta
methods.

We will apply them on the example of a simple first-order system described
by the differential equation

    x' = -x

which is simple enough to be able to derive the exact solution analytically
such that it can be used to assess the performance of the numerical method.
"""
import argparse
import numpy as np
import matplotlib.pyplot as plt

def setup_cli(args):
    """
    Setup the command line interface.
    """
    parser = argparse.ArgumentParser(description="Solve the differential equation x'=-x numerically.")
    parser.add_argument(
        "--t0",
        type=float,
        default=0.0,
        help="The starting time t0 at which the initial condition is defined. Default: t0=0.0",
    )
    parser.add_argument("--x0", type=float, default=1.0, help="The initial condition x(t=t0)=x0. Default: x0=1.0")
    parser.add_argument("--step", type=float, default=0.0001, help="The step size. Default: s=0.0001")
    parser.add_argument("--end", type=float, default=1.0, help="The end point in the time evolution. Default: end=1.0")
    return parser.parse_args()

def exact_solution(t, x0=1.0):
    """
    The implementation of the exact solution to the differential equation
    x' = -x
    with the initial condition x(t=0)=`x0` (default x0=1, as in the problem).
    """
    return x0 * np.exp(-t)

def euler_method(f, t0: float, x0: float, timestep: float, end: float, exact_solution=None):
    """
    Implementation of the euler method to numerically compute the solution
    to the differential equation

                x'=f(x)

    Parameters
    ----------
    f: function
        The implementation of the function `f` appearing in the differential
        equation.
    t0: float
        The initial time.
    x0: float
        The initial condition to the differential equation, i.e. the value
        of x(t=t0).
    timestep: float
        The timestep to employ for the numerical solution of the differential
        equation.
    end: float
        The maximal time step up to which to compute the the solution.
    exact_solution: function
        The exact solution. If the value is different from `None` the exact
        solution will
        be evaluated at each time step and the corresponding values will be
        returned in order
        to be able to check the convergence of the numerical solution.

    """
    if end < t0:
        raise ValueError("Initial time is larger than the end time!")

    # Store the time steps
    time_steps = [t0]
    # Store the value at each time step
    values = [x0]
    # Store the exact values of the solutions at each time step, if the exact
    # solution is provided
    if exact_solution:
        exact_values = [exact_solution(t0)]

    # Now start solving the differential equation numerically
    t = t0
    x = x0
    while t < end:
        t = t + timestep
        time_steps.append(t)
        x = x + f(x) * timestep
        values.append(x)
        if exact_solution:
            exact_values.append(exact_solution(t))

    return np.array(time_steps), np.array(values), None if not exact_solution else np.array(exact_values)

def improved_euler_method(f, t0: float, x0: float, timestep: float, end: float, exact_solution=None):
    """
    Implementation of the improved euler method to numerically compute the solution
    to the differential equation

                x'=f(x)

    Parameters
    ----------
    f: function
        The implementation of the function `f` appearing in the differential
        equation.
    t0: float
        The initial time.
    x0: float
        The initial condition to the differential equation, i.e. the value
        of x(t=t0).
    timestep: float
        The timestep to employ for the numerical solution of the differential
        equation.
    end: float
        The maximal time step up to which to compute the the solution.
    exact_solution: function
        The exact solution. If the value is different from `None` the exact
        solution will
        be evaluated at each time step and the corresponding values will be
        returned in order
        to be able to check the convergence of the numerical solution.

    """
    if end < t0:
        raise ValueError("Initial time is larger than the end time!")

    # Store the time steps
    time_steps = [t0]
    # Store the value at each time step
    values = [x0]
    # Store the exact values of the solutions at each time step, if the exact
    # solution is provided
    if exact_solution:
        exact_values = [exact_solution(t0)]

    # Now start solving the differential equation numerically
    t = t0
    x = x0
    while t < end:
        t = t + timestep
        time_steps.append(t)
        x_tilde = x + f(x) * timestep
        x = x + 0.5 * (f(x) + f(x_tilde)) * timestep
        values.append(x)
        if exact_solution:
            exact_values.append(exact_solution(t))

    return np.array(time_steps), np.array(values), None if not exact_solution else np.array(exact_values)

def runge_kutta_method(f, t0: float, x0: float, timestep: float, end: float, exact_solution=None):
    """
    Implementation of the Runge-Kutta method to numerically compute the solution
    to the differential equation

                x'=f(x)

    Parameters
    ----------
    f: function
        The implementation of the function `f` appearing in the differential
        equation.
    t0: float
        The initial time.
    x0: float
        The initial condition to the differential equation, i.e. the value
        of x(t=t0).
    timestep: float
        The timestep to employ for the numerical solution of the differential
        equation.
    end: float
        The maximal time step up to which to compute the the solution.
    exact_solution: function
        The exact solution. If the value is different from `None` the exact
        solution will
        be evaluated at each time step and the corresponding values will be
        returned in order
        to be able to check the convergence of the numerical solution.

    """
    if end < t0:
        raise ValueError("Initial time is larger than the end time!")

    # Store the time steps
    time_steps = [t0]
    # Store the value at each time step
    values = [x0]
    # Store the exact values of the solutions at each time step, if the exact
    # solution is provided
    if exact_solution:
        exact_values = [exact_solution(t0)]

    # Now start solving the differential equation numerically
    t = t0
    x = x0
    while t < end:
        t = t + timestep
        time_steps.append(t)

        k1 = f(x) * timestep
        k2 = f(x + 0.5 * k1) * timestep
        k3 = f(x + 0.5 * k2) * timestep
        k4 = f(x + k3) * timestep

        x = x + 1.0 / 6.0 * (k1 + 2.0 * k2 + 2.0 * k3 + k4)
        values.append(x)

        if exact_solution:
            exact_values.append(exact_solution(t))

    return np.array(time_steps), np.array(values), None if not exact_solution else np.array(exact_values)

if __name__ == "__main__":
    # Imports
    import sys
    import logging

    # Setup the logger
    logging.basicConfig(level=logging.INFO)

    # Setup the command-line parser and extract the parameters
    args = setup_cli(sys.argv)
    x0 = args.x0
    t0 = args.t0
    stop = args.end
    step_size = args.step

    # Log the parameters that we employ
    logging.info(f"Using initial time      t0  = {t0}")
    logging.info(f"Using initial condition x0  = {x0}")
    logging.info(f"Using step size         s   = {step_size}")
    logging.info(f"Using end time          end = {stop}")

    # Compare the performances
    numerical_methods = {
        "Euler": euler_method,
        "Improved Euler": improved_euler_method,
        "Runge-Kutta": runge_kutta_method,
    }

    fig1, ax1 = plt.subplots()
    fig2, ax2 = plt.subplots()

    for label, f in numerical_methods.items():
        ts, vs, exact = f(lambda x: -x, t0, x0, step_size, stop, exact_solution)
        error = np.abs(vs - exact)
        ax1.plot(ts, vs, label=label.lower())
        ax2.plot(ts, error, label=label.lower())

    time_exact = np.linspace(t0, stop, 1000)
    vals_exact = exact_solution(time_exact)
    ax1.plot(time_exact, vals_exact, label="exact")

    # Cosmetics for the plot
    ax1.set_title(r"Solution to the first-order system $\dot{x}=-x$")
    ax1.set_xlabel(r"$t$")
    ax1.set_ylabel(r"$|x_{\mathrm{num}}(t) - x_{\mathrm{exact}}(t)|$")
    ax1.legend(loc="best")
    # ax1.set_yscale('log')
    # ax1.set_xscale('log')
    fig1.tight_layout()

    ax2.set_title(r"Error of the solution to the first-order system $\dot{x}=-x$")
    ax2.set_xlabel(r"$t$")
    ax2.set_ylabel(r"$|x_{\mathrm{num}}(t) - x_{\mathrm{exact}}(t)|$")
    ax2.legend(loc="best")
    ax2.set_yscale("log")
    ax2.set_xscale("log")
    fig2.tight_layout()
    plt.show()
    # fig1.savefig("compare_numerical_solvers.pdf")
    # fig2.savefig("compare_numerical_solvers_error.pdf")


import numpy as np
from scipy.linalg import svd

class my_normalizer:
    def __init__(self, norm="Min-Max", axis = 1):
        #     norm = {"L1", "L2", "Min-Max", "Standard_Score"}
        #     axis = 0: normalize rows
        #     axis = 1: normalize columns
        self.norm = norm
        self.axis = axis

    def fit(self, X):
        #     X: input matrix
        #     Calculate offsets and scalers which are used in transform()
        X_array  = np.asarray(X)
        # Write your own code below

    def transform(self, X):
        # Transform X into X_norm
        X_norm = deepcopy(np.asarray(X))
        # Write your own code below
        return X_norm

    def fit_transform(self, X):
        self.fit(X)
        return self.transform(X)

class my_pca:
    def __init__(self, n_components = 5):
        #     n_components: number of principal components to keep
        self.n_components = n_components

    def fit(self, X):
        #  Use svd to perform PCA on X
        #  Inputs:
        #     X: input matrix
        #  Calculates:
        #     self.principal_components: the top n_components principal_components
        U, s, Vh = svd(X)
        # Write your own code below

    def transform(self, X):
        #     X_pca = X.dot(self.principal_components)
        X_array = np.asarray(X)
        # Write your own code below

        return X_pca

    def fit_transform(self, X):
        self.fit(X)
        return self.transform(X)

def stratified_sampling(y, ratio, replace = True):
    #  Inputs:
    #     y: class labels
    #     0 < ratio < 1: number of samples = len(y) * ratio
    #     replace = True: sample with replacement
    #     replace = False: sample without replacement
    #  Output:
    #     sample: indices of stratified sampled points
    #             (ratio is the same across each class,
    #             samples for each class = int(np.ceil(ratio * # data in each class)) )

    if ratio<=0 or ratio>=1:
        raise Exception("ratio must be 0 < ratio < 1.")
    y_array = np.asarray(y)
    # Write your own code below

    return sample.astype(int)


"""
.. versionadded:: 0.3

This function simulates logistic map equation. This logisti map is described
by following equation

:math:`x(k+1) = r x(k) (1 - x(k))`,

where :math:`x` is population (between 0 and 1) and :math:`x` is parameter
denoted as value of interest.

The logistic map can result in logistic behaviour for some values of :math:`r`.

For more information see great page about
`logistic map on Wikipedia <https://en.wikipedia.org/wiki/Logistic_map>`_

Example Usage
===============

Simple example

.. code-block:: python

    N = 200 # number of samples
    x = signalz.logistic_map(N, r=3)

Function Documentation
======================================
"""
import numpy as np

def logistic_map(n, r=4, initial=0.5):
    """
    Logist map equation

    **Args:**

    * `n` - length of the output data (int) - how many samples will be on output

    **Kwargs:**

    * `r` - function parameter (float)

    * `initial` - initial value (float), default is 0.1,
      should be in range from 0 to 1

    **Returns:**

    * simulation output - vector of numbers between 0 and 1
    """
    x = np.zeros(n)
    x[0] = initial
    for k in range(n-1):
        x[k+1] = r*x[k]*(1-x[k])
    return x


import math
from numpy import zeros_like, random
from matplotlib import pyplot as plt

def statsnp(x):
  n = 0
  S = 0.0
  m = 0.0
  Sarr = zeros_like(x)
  Marr = zeros_like(x)
  for x_i in x:
    n += 1
    m_prev = m
    m += (x_i - m) / n
    S += (x_i - m) * (x_i - m_prev)
    Marr[n-1] = m
    Sarr[n-1] = S/n

  return Marr, Sarr

def stats(x):
  n = 0
  S = 0.0
  m = 0.0
  for x_i in x:
    n += 1
    m_prev = m
    m += (x_i - m) / n
    S += (x_i - m) * (x_i - m_prev)
  return {'mean': m, 'variance': S/n}

def naive_stats(x):
  S1 = sum(x)
  n = len(x)
  S2 = sum([x_i**2 for x_i in x])
  return {'mean': S1/n, 'variance': (S2/n - (S1/n)**2) }

x1 = [1,-1,2,3,0,4.02,5]
x2 = [x+1e9 for x in x1]

print("naive_stats:")
print(naive_stats(x1))
print(naive_stats(x2))

print("stats:")
print(stats(x1))
print(stats(x2))

print("\n\nx3:")
random.seed(7)
x3 = random.rand(10000)
print(naive_stats(x3))
print(stats(x3))
m, s = statsnp(x3)
plt.plot(s, label='std')
plt.plot(m, label='avg')
plt.legend()
plt.show()


"""
@author: Maneesh D
@date: 29-May-17
@intepreter: Python 3.6

Consider a random 10 x 2 matrix representing Cartesian coordinates, convert them to Polar coordinates.
"""
import math

import numpy

numpy.set_printoptions(precision=2)
cartesian_matrix = numpy.random.randint(0, 9, size=(10, 2))

print(cartesian_matrix.shape)
print("Cartesian Co-ordinate matrix:")
print(cartesian_matrix)

# Converting to polar co-ordinates
polar_matrix = numpy.empty(shape=cartesian_matrix.shape, dtype=float)
for i in range(cartesian_matrix.shape[0]):
    row_data = cartesian_matrix[i]
    r = ((row_data[0] ** 2) + (row_data[1] ** 2)) ** 0.5
    phi = math.atan2(row_data[1], row_data[0])
    polar_matrix[i] = [r, phi]

print("Polar co-ordinate matrix:")
print(polar_matrix)


import tensorflow as tf
import numpy as np
import random
import time
import matplotlib.pyplot as plt

def print_progress(it, acc, loss):
    # Calculate the accuracy on the training-set.
    now = time.strftime("%c")
    print("Iteration "+str(it)+" --- Accuracy: "+str(acc)+", Loss: "+str(loss)+" --- "+now);

def main():

    learning_rate = 0.001;
    max_iterations = 20000;

    # Load training and eval data
    train_x = np.linspace(0,10,50);
    train_y = 4*train_x + 1;
    train_y += np.random.uniform(low=-1,high=1,size=len(train_y));

    # Show training data
    plt.figure();
    plt.plot(train_x,train_y,'ro');
    plt.show();

    # -------------------------------------------------------------------
    # Building the computational graph
    tf.reset_default_graph();

    x = tf.placeholder(tf.float32, shape=len(train_x), name='x')
    y_true = tf.placeholder(tf.float32, shape=len(train_y), name='y_true')

    m = tf.Variable(np.random.randn(), name="m");
    tf.summary.scalar("m",m);
    b = tf.Variable(np.random.randn(), name="b");
    tf.summary.scalar("b", b);

    pred = tf.add(tf.multiply(m,x),b);

    loss = tf.losses.mean_squared_error(labels=y_true, predictions=pred)
    tf.summary.scalar("loss",loss);

    optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(loss)

    merged_summary = tf.summary.merge_all();

    print "Graph correctly created!";
    # -------------------------------------------------------------------

    session = tf.Session();
    session.run(tf.global_variables_initializer())
    folder = "linear_regression";
    train_writer = tf.summary.FileWriter("logs/" + folder, session.graph);

    i = 0;

    feed_dict_train = {x: train_x, y_true: train_y}

    print "Starting training...";

    while i <= max_iterations:

        s,train_loss,_ = session.run([merged_summary,loss,optimizer], feed_dict=feed_dict_train)

        if i % 10 == 0:
            #
            now = time.strftime("%c")
            print("Iteration " + str(i) + " --- Loss: " + str(train_loss) + " --- " + now);
            train_writer.add_summary(s,i);

        i += 1;

    print "Training ended!";

    print "Learned parameters:";
    print "m = " + str(session.run(m));
    print "b = " + str(session.run(b));

    plt.plot(train_x, train_y, 'ro', label='Original data')
    plt.plot(train_x, session.run(pred,feed_dict={x: train_x}), label='Fitted line')
    plt.legend()
    plt.show()

    session.close();

if __name__ == "__main__":
    main();


"""
Spectral differentiation from J.A.C. Weideman and S.C. Reddy 1998, ACM TOMS.
"""

import numpy as N
import numpy.ma as ma

def herroots(n):
    """Returns the roots of the Hermite polynomial of degree n."""
    # Jacobi matrix
    J = N.diag(N.arange(1, n)**0.5, 1) + N.diag(N.arange(1, n)**0.5, -1)
    return N.sort(N.linalg.eigvalsh(J)) / (2**0.5)

def herdif(n, m, b):
    """Computes differentiation matrices D1, D2, ..., Dm on Hermite points.

    Args:
        n: Number of points, which is also the order of accuracy.
        m: Number of derivative matrices to return.
        b: Scaling parameter. Real and positive.

    Returns:
        x: Array of nodes, zeros of Hermite polynomial of degree n, scaled by b.
        Dm: A list s.t. Dm[i] is the (i+1)-th derivative matrix, i=0...m-1.

    Note: 0 < m < n-1.
    """
    x = herroots(n)
    # Compute weights
    alpha = N.exp(-x**2 / 2.)
    # Set up beta matrix s.t. beta[i,j] =
    #  ( (i+1)-th derivative of alpha(x) )/alpha(x), evaluated at x = x(j).
    beta = N.zeros((m+1, x.shape[0]))
    beta[0] = 1.0
    beta[1] = -x
    for i in range(2, m+1):
        beta[i] = -x * beta[i-1] - (i-1) * beta[i-2]
    # Remove initializing row from beta
    beta = N.delete(beta, 0, 0)
    # Compute differentiation matrix (b=1).
    Dm = poldif(x, alpha=alpha, B=beta)
    # Scale nodes by the factor b.
    x = x/b
    # Adjust derivatives for b not equal to 1.
    for i in range(1, m+1):
        Dm[i-1] *= b**i

    return x, Dm

def poldif(x, m=None, alpha=None, B=None):
    """
    Computes the differentiation matrices D1, D2, ..., Dm on arbitrary nodes.

    The function is called with either keyword argument m OR
    keyword args alpha and B.
    If m is given, then the weight function is constant.
    If alpha and B are given, then the weights are defined by alpha and B.

    Args:
        x: 1D array of n distinct nodes.

    Kwargs:
        m: Number of derivatives.
        alpha: 1D array of weight values alpha[x], evaluated at x = x[k].
        B: Array of size m x n where B[i,j] = beta[i,j] = ((i+1)-th derivative
            of alpha(x))/alpha(x), evaluated at x = x[j].

    Returns:
        Dm: A list s.t. Dm[i] is the (i+1)-th derivative matrix, i=0...m-1.

    Note: 0 < m < n-1.
    """
    x = x.flatten()
    n = x.shape[0]
    if m is not None and B is None and alpha is None:
        alpha = N.ones(n)
        B = N.zeros((m, n))
    elif m is None and B is not None and alpha is not None:
       alpha = alpha.flatten()
       m = B.shape[0]
    else:
        raise RuntimeError('Keyword args to poldif are inconsistent.')

    XX = N.tile(x, (n, 1)).transpose()
    # DX contains entries x[k] - x[j].
    DX = XX - XX.transpose()
    # Put 1's one the main diagonal.
    N.fill_diagonal(DX, 1.)

    # C has entries c[k]/c[j].
    c = alpha * N.prod(DX, 1)
    C = N.tile(c, (n, 1)).transpose()
    C = C/C.transpose()

    # Z has entries 1/(x[k]-x[j])
    Z = 1./DX
    N.fill_diagonal(Z, 0.)

    # X is Z' but with the diagonal entries removed.
    X = Z.transpose()
    X = ma.array(X.transpose(), mask=N.identity(n)).compressed().\
        reshape((n, n-1)).transpose()

    # Y is matrix of cumulative sums and D is a differentiation matrix.
    Y = N.ones((n, n))
    D = N.eye(n)
    Dm = []
    for i in range(1, m+1):
        # Diagonals
        Y = N.cumsum(N.concatenate((B[i-1].reshape((1,n)),
            i * Y[0:n-1] * X), axis=0), axis=0)
        # Off-diagonals
        D = i * Z * (C * N.tile(N.diag(D), (n,1)).transpose() - D)
        # Correct the diagonal
        D.flat[::n+1] = Y[-1]
        Dm.append(D)

    return Dm


# Ben Eggers <ben.eggers36@gmail.com>
#
# A simple, gradient-descent-based linear system solver. The idea is as follows:
#
# We are given a matrix A and a vector b, and want to find x such that Ax = b.
#
# We turn this into an optimization problem, with our objective function f(x) =
# the A-norm of the error (Ax - b). We use an iterative method. At each step,
# we find the gradient at our current guess (initialized to 0), then take the
# optimal step size (computable because matrices are awesome) in the direction
# of the gradient. We repeat until the gradient = 0, which is when our guess
# will be the solution to the system.
#
# I only want to deal with SDD systems here, so the matrix input will be in
# graph form, with the matrix defined to be the Laplacian of the input graph.

import numpy as np
import time

# Get the gradient of A at x
def grad(A, b, x):
    return -2 * rhe(A, b, x)

# Error on the right-hand side. b - Ax
def rhe(A, b, x):
    return b - A.dot(x)

def get_matrix():
    ec = int(raw_input("How many edges on input graph? "))
    edges = []
    for i in xrange(ec):
        inp = raw_input("Next edge(of the form '<node_#>, <node_#>'): ")
        edges.append((int(inp.split(", ")[0]), int(inp.split(", ")[1])))

    m = int(max([max(e[0], e[1]) for e in edges]))

    ret = np.zeros((m, m), dtype = np.int)

    for e in edges:
        ret[e[0] - 1][e[1] - 1] = ret[e[1] - 1][e[0] - 1] = -1
        # These should be "+= 1" for the true Laplacian, but we need the
        # matrix to be non-singular. So...
        ret[e[0] - 1][e[0] - 1] += 2
        ret[e[1] - 1][e[1] - 1] += 2

    print ret
    return ret

def get_target(length):
    print "Please input", length, "numbers for the target vector"
    ret = []
    while len(ret) < length:
        ret.append(int(raw_input("Next number? ")))
    return np.array(ret)

def report(A, b, x):
    print "x =", x
    print "Error norm:", np.linalg.norm(rhe(A, b, x))

# ititial conditions
A = get_matrix()
b = get_target(A.shape[0])

x = np.array([0 for i in range(b.shape[0])])

# do the actual descent
g = grad(A, b, x)
# Ideally we'd go until the norm equals 0, but numerical analysis is a thing
while np.linalg.norm(g) > 0.000005:
    r = rhe(A, b, x)
    x = x + ((1. * r.dot(r)) / (r.dot(A).dot(r))) * r
    g = grad(A, b, x)

report(A, b, x)


import numpy as np
import matplotlib.pyplot at plt
from scipy import linalg as LA

### for knn
from sklearn.neighbors import NearestNeighbors

def dist_mtx(X, Y=None):
    """Return the distance matrix between rows of X and rows of Y

    Input:
        X: an array of shape (N,d)
        Y: an array of shape (M,d)
            if None, Y = X

    Output:
        the matrix [d_ij] where d_ij is the distance between
        the i-th row of X and the j-th row of Y
    """
    if isinstance(Y, np.ndarray):
        pass
    elif Y == None:
        Y = X.copy()
    else:
        raise TypeError("Y should be a NumPy array or None")
    X_col = X[:, np.newaxis, :]
    Y_row = Y[np.newaxis, :, :]
    diff = X_col - Y_row
    dist = np.sqrt(np.sum(diff**2, axis=-1))
    return dist

def inertia(X, labels, centers):
    """Return the inertia of X corresponding to its labels and cluster centers

    Input:
        X: an array of shape (N,d)
        labels: an array of shape (N,)
            that records the labels in (0, ..., k-1) of each sample
        centers: an array of shape (k,d)
            that records the cluster centers
    """
    k = centers.shape[0]
    return sum(np.sum((X[labels==i,:] - centers[i])**2)  for i in range(k))

def pca(X, r=2):
    """PCA dimensionality reduction

    Input:
        X: an array of shape (N,d)
            rows for samples and columns for features
        r: target dimention (<= d)

    Output:
        an array of shape (N,r)

    Example:
        mu = np.array([3,4])
        cov = np.array([[1.1,1],
                        [1,1.1]])
        X = np.random.multivariate_normal(mu, cov, 100)
        X_new = PCA(X)
    """
    N,d = X.shape
    X = X - X.mean(axis= 0)
    C = 1. / N * X.T.dot(X)
    vals,vecs = LA.eigh(C, subset_by_index=[d-r,d-1])
    vals = vals[::-1] ### from large to small
    U = vecs[:,::-1] ### from large to small
    return X.dot(U)

def mds(X, r=2, n_iter=100, verbose=0):
    """MDS dimensionality reduction

    Input:
        X: an array of shape (N,d)
            rows for samples and columns for features
        r: target dimention (<= d)
        n_iter: an integer for the number of iterations to run
        verbose: 0,1,2
            shows more details when higher value

    Output:
        an array of shape (N,r)

    Example:
        mu = np.array([3,4])
        cov = np.array([[1.1,1],
                        [1,1.1]])
        X = np.random.multivariate_normal(mu, cov, 100)
        X_new = MDS(X, verbose=2)
    """
    N,d = X.shape
    goal = dist_mtx(X) ### dissimilarity in sklearn.manifold._mds.py

    X0 = np.random.randn(N, r)
    dis = dist_mtx(X0)
    stress = np.sum( (dis - goal)**2 ) / 2
    if verbose >= 2:
        print("Iter %3d: stress = %f"%(0, stress))

    Xk = X0
    for k in range(1, n_iter + 1):
        s = np.zeros_like(dis)
        mask = (dis != 0)
        s[mask] = 1 / dis[mask]
        B = -goal * s
        B[np.arange(N), np.arange(N)] = -B.sum(axis=1)
        Xk = 1. / N * B.dot(Xk) ### Vdagger B(Xk) Xk
        dis = dist_mtx(Xk)
        stress = np.sum( (dis - goal)**2 ) / 2
        if verbose >= 2:
            print("Iter %3d: stress = %f"%(k, stress))

    return Xk

def k_means(X, k, init="random"):
    """k-means clustering algorithm

    Input:
        X: an array of shape (N,d)
            rows for samples and columns for features
        k: number of clusters
        init: "random" or an array of shape (k,d)
            if "random", k points are chosen randomly from X as the initial cluster centers
            if an array, the array is used as the initial cluster centers

    Output:
        (y_new, centers)
        y_new: an array of shape (N,)
            that records the labels in (0, ..., k-1) of each sample
        centers: an array of shape (k,d)
            that records the cluster centers

    Example:
        mu = np.array([3,3])
        cov = np.eye(2)
        X = np.vstack([np.random.multivariate_normal(mu, cov, 100),
                       np.random.multivariate_normal(-mu, cov, 100)])
        y_new,centers = k_means(X, 2)
    """
    N,d = X.shape

    ### initialize y and center
    if isinstance(init, np.ndarray):
        centers = init.copy()
    elif init == "random":
        inds = np.random.choice(np.arange(N), k, replace=False)
        centers = X[inds, :]
    else:
        raise TypeError("init can only be a NumPy array or 'random'")

    dist = dist_mtx(X, centers)
    y_new = dist.argmin(axis=1)

    while True:
        ### compute the new centers
        for i in range(k):
            mask = (y_new == i)
            centers[i] = X[mask].mean(axis=0)

        ### generate the new y_new
        dist = dist_mtx(X, centers)
        y_last = y_new.copy()
        y_new = dist.argmin(axis=1)

        if np.all(y_last == y_new):
            break

    return y_new, centers

def dbscan(X, eps, min_samples, draw=False):
    """DBSCAN clustering algorithm

    Input:
        X: an array of shape (N,d)
            rows for samples and columns for features
        eps: the radius used for finding neighborhood
        min_samples: a sample is considered as a core sample
            if its epsilon-ball contains at least `min_sample` samples
            (including itself)
        draw: boolean, return a illustrative figure or not

    Output:
        (y_new, core_indices, fig)  or (y_new, core_indices) depending on draw or not
        y_new: an array of shape (N,)
            that records the labels in of each sample, where -1 stands for a noise
        core_indices: an array of shape (n_core_samples,) that stores the indices of the core samples
        fig: an illustrative figure showing the data points, DFS tree, core samples, and noises

    Example:
        mu = np.array([3,3])
        cov = np.eye(2)
        X = np.vstack([np.random.multivariate_normal(mu, cov, 100),
                       np.random.multivariate_normal(-mu, cov, 100)])
        y_new,core_indices,fig = dbscan(X, 1, 5, draw=True)
    """
    N,d = X.shape
    dist = dist_mtx(X)

    ### find core samples
    adj = (dist <= eps)
    core_mask = (adj.sum(axis=1) >= min_samples)
    core_indices = np.where(core_mask)[0]
    nbrhoods = [np.where(adj[i])[0] for i in range(N)]

    ### Run DFS to label each vertex
    y_new = -np.ones((N,), dtype=int)
    label_num = 0
    tree = []
    for i in range(N):
        if y_new[i] == -1 and core_mask[i]:
            stack = [i]
            while stack != []:
                j = stack.pop()
                if y_new[j] == -1:
                    tree.append((i,j))
                    y_new[j] = label_num
                    if core_mask[j]:
                        stack += list(nbrhoods[j])
            label_num += 1

    if draw:
        fig = plt.figure()
        for i,j in tree:
            plt.plot(*zip(X[i], X[j]), c='b', zorder=-1)
        plt.scatter(X[:,0], X[:,1], c=y_new, cmap='viridis')
        components = X[core_indices, :]
        plt.scatter(components[:,0], components[:,1], c='r', s=10)
        noise = X[y_new == -1]
        plt.scatter(noise[:,0], noise[:,1], c='k', s=100, marker='x')
        plt.close()
        return y_new, core_indices, fig

    return y_new, core_indices

def linear_regression(X, y, fit_intercept=True, algorithm='projection',
                     alpha=1, learning_rate=0.01, n_iter=1000, regularization=None, verbose=False):
    """Linear Regression algorithm

    Input:
        X: an array of shape (N,d)
            rows for samples and columns for features
        y: the labels of shape (N,)
        fit_intercept: whether to calculate the intercept or not
        algorithm: "projction" or "grad_descent"
        alpha: weight for the L1 or L2 regularization
        learning_rate: learning rate for the gradient descent algorithm
        n_iter: number of iterations for the gradient descent algorithm

    Output:
        (predict, coefs, intercep)
        predict: a function that can takes some samples X_test and
            return the prediction X_test.dot(coefs) + intercept
        coef: an array of shape (d,) that stores the coefficients
        intercept: a float for the intercept

    Example:
        x = np.arange(10)
        X = np.vstack([x]).T
        y = 0.5 * x + 3 + 0.3*np.random.randn(10)
        predict,coef,intercept = linear_regression(X, y, algorithm="grad_descent",
                                                  learning_rate=0.03, regularization=None, verbose=True)
        x_sample = np.linspace(0,10,20)
        X_sample = x_sample[:,np.newaxis]
        y_new = predict(X_sample)
    """
    N,d = X.shape
    if fit_intercept:
        A = np.hstack([np.ones((N,1)), X])
    else:
        A = X.copy()
    dp = A.shape[1]

    if algorithm == "projection":
        ATAinv = np.linalg.pinv(A.T.dot(A))
        v = ATAinv.dot(A.T).dot(y)

    if algorithm == "grad_descent":
        v = np.random.randn(dp)
        if verbose:
            mse = np.mean((A.dot(v) - y)**2)
            print("Iter %3d: mes = %f"%(0, mse))
        for _ in range(n_iter):
            Av = A.dot(v)
            ### before 1 / N
            diff = Av - y ### (n_sample, ) as a row vector
            ### now do 1 / N
            grad = 1 / N * np.dot(2*diff, A)

            ### regularization gradient
            if regularization == None:
                grad_reg = np.zeros_like(grad)
            elif regularization == "L1":
                grad_reg = alpha * np.sign(v, dtype=float)
            elif regularization == "L2":
                grad_reg = alpha * 2 * v
            else:
                raise TypeError("invalid input for regularization keyword")

            v -= learning_rate * (grad + grad_reg)
            if verbose:
                mse = np.mean(diff**2)
                print("Iter %3d: mes = %f"%(_, mse))

    if fit_intercept:
        coef = v[1:]
        intercept = v[0]
    else:
        coef = v.copy()
        intercept = 0

    predict = lambda X_test: X_test.dot(coef) + intercept
    return predict, coef, intercept

def polynomial_regression(X, y, degree=2, **kwargs):
    """Polynomial Regression algorithm

    Input:
        X: an array of shape (N,1)
            rows for samples and columns for features
        y: the labels of shape (N,)
        degree: the degree of the polynomial
        **kwargs: keywords for linear_regression function

    Output:
        output of linear_regression function

    Example:
        x = np.arange(10)
        y = 0.1*x**2 + 0.2*x + 0.3 + 0.5*np.random.randn(10)
        X = x[:,np.newaxis]
        predict,coef,intercept = jeplearn.polynomial_regression(X, y, algorithm="grad_descent",
                                                               learning_rate=0.0001, regularization=None, verbose=True)
        x_sample = np.linspace(0,10,20)
        X_sample = x_sample[:,np.newaxis]
        y_new = predict(X_sample)
    """
    X_ex = X**np.arange(1, degree+1)
    predict_lin,coef,intercept = linear_regression(X_ex, y, **kwargs)
    predict = lambda X_test: (X_test**np.arange(1, degree+1)).dot(coef) + intercept
    return predict, coef, intercept

def knn(X, y, k=5, algorithm='brute'):
    """k-nearest neighbors classification algorithm

    Input:
        X: an array of shape (N,d)
            rows for samples and columns for features
        y: the labels of shape (N,)
        k: numbers of neighbors (including self) to vote
        algorithm: 'brute', 'ball_tree', or 'kd_tree'

    Output:
        (predict, k_nearest_neighbors)
        predict: a function that takes data X_sample and output their predicted labels
        k_nearest_neighbors: a function that takes data X_sample and
            return the indices of the nearest neighbors in X

    Example:
        X = np.vstack([np.random.randn(50,2) + np.array([3,3]),
                       np.random.randn(50,2) - np.array([3,3])])
        y = np.array([0]*50 + [1]*50)
        X_sample = np.random.randn(5,2)
        predict,nbrs = knn(X, y, k=5, algorithm='brute')
        y_new = predict(X_sample)
    """
    N,d = X.shape
    classes = np.unique(y)

    if algorithm=="brute":
        def k_nearest_neighbors(X_sample):
            dist = dist_mtx(X_sample, X)
            argp = dist.argpartition(k-1)
            return argp[:,:k]
    elif algorithm in ["ball_tree", "kd_tree"]:
        def k_nearest_neighbors(X_sample):
            nbr = NearestNeighbors(n_neighbors=k, algorithm=algorithm)
            nbr.fit(X)
            return nbr.kneighbors(X_sample, return_distance=False)
    else:
        raise TypeError("invalid input for the algorithm keyword")

    def predict(X_sample):
        nbrhoods = k_nearest_neighbors(X_sample)
        votes = y[nbrhoods]
        count = (votes[:,:,np.newaxis] == classes).sum(axis=1)
        y_new = classes[count.argmax(axis=1)]
        return y_new
    return predict, k_nearest_neighbors

def decision_tree(X, y, criteria="gini"):
    """Decision tree classification algorithm

    Input:
        X: an array of shape (N,d)
            rows for samples and columns for features
        y: the labels of shape (N,)
        criterion: "gini" or "entropy"

    Output:
        (predict, tree)
        predict: a function that takes data X_sample and output their predicted labels
        tree: a dictionary that contains the information same as those in model.tree_

    Example:
        mu = np.array([1,1])
        cov = np.array([[1.1,-1],
                        [-1,1.1]])
        X = np.vstack([np.random.multivariate_normal(mu, cov, 100),
                       np.random.multivariate_normal(-mu, cov, 100)])
        y = np.array([0]*100 + [1]*100)

        X_sample = 2*np.random.randn(1000,2) - np.array([1,1])
        predict,tree = decision_tree(X, y, criteria="gini")
        y_new = predict(X_sample)
    """
    N,d = X.shape
    classes = np.unique(y)

    def impurity(arr):
        dtrib = np.unique(arr, return_counts=True)[1]
        dtrib = dtrib / dtrib.sum()
        if criteria == "gini":
            return np.sum(dtrib * (1 - dtrib))
        if criteria == "entropy":
            return np.sum(-dtrib * np.log2(dtrib))

    ### every sample are in node 0
    node = np.zeros((N,), dtype=int)

    queue = [0]
    node_count = 0 ### index of the last existing node

    tree = {"children_left": [],
            "children_right": [],
            "feature": [],
            "threshold": [],
            "n_node_samples": [],
            "impurity": []}

    while queue != []:
        k = queue[0]
        queue.remove(k)

        ### all samples in node k
        mask_U = (node == k)
        U = X[mask_U]
        y_U = y[mask_U]
        NU = U.shape[0]

        imp = impurity(y_U)
        tree["impurity"].append(imp)
        tree["n_node_samples"].append(NU)
        if imp == 0:
            tree["children_left"].append(-1)
            tree["children_right"].append(-1)
            tree["feature"].append(-2)
            tree["threshold"].append(-1)
            continue

        Iprime = np.zeros_like(U)
        ### calculate the Iprime for each cut
        for j in range(d):
            for i in range(NU):
                mask = (U[:,j] <= U[i,j])
                NL, NR = mask.sum(), (~mask).sum()
                HL = impurity(y_U[mask])
                HR = impurity(y_U[~mask])
                Iprime[i,j] = (NL*HL + NR*HR) / NU

        ### note here i and j are specific choices
        ind = Iprime.argmin()
        i,j = ind//d, ind%d
        mask = (U[:,j] <= U[i,j])
        ### the following syntax does not change node itself
        ### Use np.where instead
        #  node[mask_U][mask] = node_count + 1
        #  node[mask_U][~mask] = node_count + 2

        inds = np.where(mask_U)[0]
        node[inds[mask]] = node_count + 1
        node[inds[~mask]] = node_count + 2
        queue += [node_count + 1, node_count +2]

        tree["children_left"].append(node_count + 1)
        tree["children_right"].append(node_count + 2)
        tree["feature"].append(j)
        tree["threshold"].append(U[i,j])

        node_count += 2

    def predict(X_sample):
        n_nodes = len(tree["children_left"])
        ### array of shape (n_nodes,)
        ### consisting of the class number
        ### or -3 if not leaf (-3 is just for debugging)
        node_to_class = -3*np.ones((n_nodes,), dtype=int)
        for v in range(n_nodes):
            if tree["children_left"][v] == -1:
                node_to_class[v] = classes[np.bincount(y[node==v]).argmax()]

        node_sample = np.zeros((X_sample.shape[0],), dtype=int)

        for v in range(n_nodes):
            if tree["children_left"][v] == -1:
                continue
            inds = np.where(node_sample == v)[0]
            mask = (X_sample[inds][:, tree["feature"][v]] <= tree["threshold"][v])
            node_sample[inds[mask]] = tree["children_left"][v]
            node_sample[inds[~mask]] = tree["children_right"][v]
        return node_to_class[node_sample]

    return predict, tree


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Oct 15 07:04:58 2020

Illustrates several ways to do direct polynomials fits with python

@author: zettergm
"""

# Imports
import numpy as np
import matplotlib.pyplot as plt
import sys
sys.path.append("../linear_algebra")
from elimtools import Gauss_elim,backsub

# Data for a direct polynomial fit
#x=np.array([1,2,3,4])
#y=2*x**3-3*x**2+4*x+9
x=np.array([1,2,3,4,5,6])
y=x**5-2*x**4+2*x**3-3*x**2+4*x+9

# Create a plot to show results
plt.figure(1)
plt.plot(x,y,'*',markersize=20)
plt.xlabel("x")
plt.ylabel("y")
plt.title("Illustration of direct fit methods")

# Fit using Python functions (numpy.polyfit)
n=x.size-1     # degree of polynomial to be fitted, compute from data point array size
coeffs=np.polyfit(x,y,n)
xlarge=np.linspace(x.min(),x.max(),24)
polyfun=np.poly1d(coeffs)
plt.plot(xlarge,polyfun(xlarge),'--')

# Execute direct fit using self-coded Gaussian Elimination
A=np.zeros((n+1,n+1))
for icol in range(n,-1,-1):
    newcol=x**icol
    A[:,n-icol]=newcol
[Amod,order]=Gauss_elim(A,y.reshape(-1,1),False)   #note conversion of y to column vector
coeffsGE=backsub(Amod[order,:],False)
polyfunGE=np.poly1d(coeffs)
plt.plot(xlarge,polyfunGE(xlarge),'.')
plt.legend(("original data","built-in fit","manual GE fit"))
plt.show()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sun Feb 24 15:33:13 2019

@author: alankar
"""

import numpy as np
import matplotlib.pyplot as plt

N = 1000

square = lambda n: np.piecewise(n, [n>=N/2], [1.,0.])

n = np.arange(0,N,1)

square = square(n)
sawtooth = n
modulated = np.sin(1*np.pi*n/N)*np.sin(20*np.pi*n/N)

plt.figure(figsize=(13,10))
plt.plot(n,square)
plt.grid()
plt.xlabel(r'$x$',size=18)
plt.ylabel(r'$f(x)$',size=20)
plt.title(r'Square Wave', size=21)
plt.tick_params(axis='both', which='major', labelsize=15)
plt.tick_params(axis='both', which='minor', labelsize=12)
plt.savefig('1_1.png')
plt.show()

plt.figure(figsize=(13,10))
plt.plot(n,sawtooth)
plt.grid()
plt.xlabel(r'$x$',size=18)
plt.ylabel(r'$f(x)$',size=20)
plt.title(r'Sawtooth Wave', size=21)
plt.tick_params(axis='both', which='major', labelsize=15)
plt.tick_params(axis='both', which='minor', labelsize=12)
plt.savefig('1_2.png')
plt.show()

plt.figure(figsize=(13,10))
plt.plot(n,modulated)
plt.grid()
plt.xlabel(r'$x$',size=18)
plt.ylabel(r'$f(x)$',size=20)
plt.title(r'Modulated Sine Wave', size=21)
plt.tick_params(axis='both', which='major', labelsize=15)
plt.tick_params(axis='both', which='minor', labelsize=12)
plt.savefig('1_3.png')
plt.show()

square_fft = np.abs(np.fft.fft(square))**2
sawtooth_fft = np.abs(np.fft.fft(sawtooth))**2
modulated_fft = np.abs(np.fft.fft(modulated))**2

sampling = n[1] - n[0]
f = np.linspace(0,1/sampling,N)

plt.figure(figsize=(13,10))
plt.plot(f[1:N//2],square_fft[1:N//2])
plt.grid()
plt.xlabel(r'$k$',size=18)
plt.ylabel(r'$\~f(k)$',size=20)
plt.title(r'FT[Square Wave]', size=21)
plt.tick_params(axis='both', which='major', labelsize=15)
plt.tick_params(axis='both', which='minor', labelsize=12)
plt.ylim(0,400)
plt.savefig('1_4.png')
plt.show()

plt.figure(figsize=(13,10))
plt.plot(f[1:N//2],sawtooth_fft[1:N//2])
plt.grid()
plt.xlabel(r'$k$',size=18)
plt.ylabel(r'$\~f(k)$',size=20)
plt.title(r'FT[Sawtooth Wave]', size=21)
plt.tick_params(axis='both', which='major', labelsize=15)
plt.tick_params(axis='both', which='minor', labelsize=12)
plt.savefig('1_5.png')
plt.ylim(0,1e7)
plt.show()

plt.figure(figsize=(13,10))
plt.plot(f[1:N//2],modulated_fft[1:N//2])
plt.grid()
plt.xlabel(r'$k$',size=18)
plt.ylabel(r'$\~f(k)$',size=20)
plt.title(r'FT[Modulated Sine Wave]', size=21)
plt.tick_params(axis='both', which='major', labelsize=15)
plt.tick_params(axis='both', which='minor', labelsize=12)
plt.savefig('1_6.png')
#plt.ylim(0,100)
plt.show()


import numpy as np

def fill_knapsack(current_volume, item, volumes, values, dynamic_results):
    # check for valid item count
    if item < len(volumes):
        # did we calculate the value already (dynamic programming)
        if dynamic_results[item][current_volume] != -1:
            return dynamic_results[item][current_volume]
        # Calculate result without current item
        a: int = fill_knapsack(current_volume, item + 1, volumes, values, dynamic_results)
        # Calculate result with current item
        b: int = 0
        if current_volume - volumes[item] >= 0:
            b = values[item] + fill_knapsack(current_volume - volumes[item], item + 1, volumes, values, dynamic_results)
        # The maximum of the two is saved and returned
        dynamic_results[item][current_volume] = max(a, b);
        return dynamic_results[item][current_volume]
    return 0

def backtracking(knapsack_value, dynamic_results, volumes, values):
    number_of_items = len(volumes) -1
    # Find maximum volume for optimal value (always first row)
    current_volume = np.argmax(dynamic_results[0])

    # iterate the items
    item: int = 0
    while item < number_of_items:
        # does the item fit the knapsack and if so, does the current value - item-value fit the volume-value?
        # If so; he item was selected to be optimal and we can reduce the volume and value and check the next item.
        if (current_volume - volumes[item] >= 0
                and knapsack_value - values[item] == dynamic_results[item + 1][current_volume - volumes[item]]):
            print_item(item, volumes, values)
            knapsack_value -= values[item]
            current_volume -= volumes[item]
        else:
            # Item is not in the Knapsack
            print_item(item, volumes, values, False)
        item += 1
    # If the last item was inside the kp, we didn't find it in the loop.
    print_item(number_of_items, volumes, values, knapsack_value > 0)

def print_item(item: int, volumes, values, selected: bool = True):
    out = ""
    if not selected:
        out = "NOT "
    out += "selecting item:{} (vol: {}, val: {})"
    print(out.format(item, volumes[item], values[item]))

def main(max_n=300, max_v=1000):
    rucksack_volume = 30
    volumes = np.fromstring('5, 5, 6, 8, 10, 11, 12, 15, 15, 30', dtype=np.int, sep=',')
    values = np.fromstring('8, 8, 6, 5, 10, 5, 10, 17, 20, 20', dtype=np.int, sep=',')

    dynamic_results = np.zeros((len(volumes), rucksack_volume + 1))
    dynamic_results[:] = -1

    kp_value = fill_knapsack(rucksack_volume, 0, volumes, values, dynamic_results)
    print("Value of knapsack:{}".format(kp_value))
    backtracking(kp_value, dynamic_results, volumes, values)

if __name__ == '__main__':
    main()


"""Fourier 2D shape reconstruction with epicycles representation"""
# Original version from D. Shiffman (2019), adapted for vedo by M. Musy (2022)
# https://thecodingtrain.com/CodingChallenges/130.2-fourier-transform-drawing.html
import numpy as np
import vedo

order = 30  # restrict to this nr of fourier coefficients in reconstruction

def DFT(x):
    X = []
    N = len(x)
    for freq in range(N):
        re, im = [0, 0]
        for n in range(N):
            phi = (2 * np.pi * freq * n) / N
            re += x[n] * np.cos(phi)
            im -= x[n] * np.sin(phi)
        re, im = [re/N, im/N]
        amp = np.sqrt(re*re + im*im)
        phase = np.arctan2(im, re)
        X.append([re, im, freq, amp, phase])
    return vedo.utils.sortByColumn(X, 3, invert=True)

def epicycles(time, rotation, fourier, order):
    global objs
    plt.remove(objs)
    x, y = [0, 0]
    objs, path = [], []
    for i in range(len(fourier[:order])):
        re, im, freq, amp, phase = fourier[i]
        if amp > 0.2:
            c = vedo.Circle([x,y], amp).wireframe().lw(0.1)
            objs.append(c)
        x += amp * np.cos(freq * time + phase + rotation)
        y += amp * np.sin(freq * time + phase + rotation)
        path.append([x,y])

    if len(points):
        hline = vedo.Line([x,y], points[-1], c='red5', lw=0.1)
        pline = vedo.Line(path, c='green5', lw=2)
        oline = vedo.Line(points, c='red4', lw=5)
        objs += [hline, pline, oline]
        plt.add(objs, resetcam=False)
    return [x, y]

# Load some 2D shape and make it symmetric
shape  = vedo.load(vedo.dataurl+'timecourse1d.npy')[55]
shaper = vedo.Line(shape).mirror('x').reverse()
shape = vedo.merge(shape, shaper)
x, y, _ = shape.points().T

# Compute Fourier Discrete Transform in x and y separately:
fourierX = DFT(x)
fourierY = DFT(y)

vedo.settings.defaultFont = 'Glasgo'
vedo.settings.allowInteraction = True

plt = vedo.Plotter(size=(1500,750), bg='black', axes=1, interactive=False)
txt = vedo.Text2D(f"{__doc__} (order={order})", c='red9', bg='white', pos='bottom-center')
plt.show(shape, txt, mode='image', zoom=1.9)

objs, points = [], []
times = np.linspace(0, 2*np.pi, len(fourierX), endpoint=False)
for time in times:
    x, _ = epicycles(time,       0, fourierX, order)
    _, y = epicycles(time, np.pi/2, fourierY, order)
    points.append([x, y])

plt.interactive().close()


import numpy as np

"""
A2-Part-5: Compute the magnitude spectrum (Optional)

Write a function that computes the magnitude spectrum of an input sequence x of length N. The
function should return an N point magnitude spectrum with frequency index ranging from 0 to N-1.

The input argument to the function is a numpy array x and the function should return a numpy array of the
magnitude spectrum of x.

EXAMPLE: If you run your function using x = np.array([1, 2, 3, 4]), the function should return the following
numpy array magX: [array([10.0, 2.82842712, 2.0, 2.82842712])
"""
def genMagSpec(x):
    """
    Input:
        x (numpy array) = input sequence of length N
    Output:
        The function should return a numpy array
        magX (numpy array) = The magnitude spectrum of the input sequence x
                             (length N)
    """
    ## Your code here


"""
   Algoritmo del simplesso per problema primale:

     max sum(c[j] * x[j]  for j in 1..n)
    s.t. sum(- A[i][j] * x[j] for j in 1..n) <= b[i]  for i in 1..m
         x[j] >= 0  for j in 1..n

    Autore: Bernardo Forni, bernardo.forni01@universitadipavia.it, (C) 2020.

"""

import numpy as np

# Parameters
OptimalityTol  = 1e-06
FeasibilityTol = 1e-06

# Main class
class LinearProblem:
    def __init__(self, c, A, b):
        """ Default costructor """
        self.not_base = list(range(1, len(c)+1))  # j = 1..n
        self.base = list(range(len(c)+1, len(c)+len(b)+1))  # n+i, i = 1..m

        self.D = np.row_stack(([0] + c, np.column_stack((b, A)))).astype(float)  # (m+1)*(n+1) matrix

    def __str__(self):
        """ Print current dictionary values """

        str = 'Current Basic Solution: '

        for x in list(range(1, len(self.not_base)+1)):
            str += f'x_{x}=0, ' if x in self.not_base else f'x_{x}={self.D[self.base.index(x)+1,0]:g}, '
        str += f'z={-self.D[0][0]:g}'

        return str

    def get_obj(self):
        return -self.D[0,0]

    def check_optimality(self):
        """ Check if the current basic solution is optimal """
        return not any(c > OptimalityTol for c in self.D[0, 1:])

    def check_feasibility(self):
        """ Check if the current basic solution is feasible """
        return not any(c < -FeasibilityTol for c in self.D[1:, 0])

    def select_entering(self):
        """ Select an entering variable with largest c[j]. Return index k """
        return 1 + np.argmax(self.D[0, 1:])

    def select_leaving(self, k):
        """ Select a leaving variable that maximize A[i][k] / b[i]. Return index h """
        # TODO: check for degenerate problem (b[h] == 0 or A[h][k] == 0)
        return 1 + np.argmax(self.D[1:,k] / self.D[1:,0])

    def pivoting(self, k, h):
        """ Change the current dictionary according to the pivoting operations.
            Do not return anything (void function)
        """
        no_h = np.ones(self.D.shape[0], dtype=bool)
        no_h[h] = False
        no_k = np.ones(self.D.shape[1], dtype=bool)
        no_k[k] = False

        # pivot leaving variables
        self.D[h,k] = 1 / self.D[h,k]
        self.D[h,:][no_k] = self.D[h,:][no_k] * self.D[h,k]

        # pivot other variables
        self.D[np.ix_(no_h, no_k)] -= np.outer(self.D[:,k][no_h], self.D[h,:][no_k])

        # pivot entering variables
        self.D[:,k][no_h] = - self.D[:,k][no_h] * self.D[h,k]

        # enter, leave base
        self.not_base[k-1], self.base[h-1] = self.base[h-1], self.not_base[k-1]

    def solve(self):
        """ Using the previous functions, solve the given LP """

        if not self.check_feasibility():
            print('ERROR: First Basic solution must be feasible!')
            return False

        while not self.check_optimality():
            k = self.select_entering()
            h = self.select_leaving(k)

            print(f'Pivoting: k={k}, h={h}')
            self.pivoting(k, h)

        return True

def main():
    # Exercise 2.1 https://vanderbei.princeton.edu/JAVA/pivot/simple.html
    c = [6, 8, 5, 9]  # cost
    A = [[2, 1, 1, 3],
         [1, 3, 1, 2]]  # constraint coefficients
    b = [5, 3]  # RHS

    # Exercise 2.2
    # c = [2, 1]
    # A = [[2, 1],
    #      [2, 3],
    #      [4, 1],
    #      [1, 5]]
    # b = [4, 3, 5, 1]

    # solve LP problem
    LP = LinearProblem(c, A, b)
    LP.get_obj()
    LP.solve()
    LP.get_obj()
    print(LP)

if __name__ == "__main__":
    main()


import numpy as np
from prml.nn.function import Function

class SigmoidCrossEntropy(Function):
    enable_auto_broadcast = True

    @staticmethod
    def _forward(x, t):
        return np.maximum(x, 0) - t * x + np.log1p(np.exp(-np.abs(x)))

    @staticmethod
    def _backward(delta, x, t):
        y = np.tanh(x * 0.5) * 0.5 + 0.5
        dx = delta * (y - t)
        dt = -delta * x
        return dx, dt

def sigmoid_cross_entropy(x, t):
    return SigmoidCrossEntropy().forward(x, t)


"""
Rayleigh Quotient iteration
uses inverse iteration for faster convergence

>>> A = array([[ 1.56260001, 0.22898325, 1.04332261, 0.4558478,  0.89455427],
...            [ 0.22898325, 0.54532437, 1.51480666, 0.96986641, 1.5202461 ],
...            [ 1.04332261, 1.51480666, 0.53536642, 0.77569108, 1.23270255],
...            [ 0.4558478 , 0.96986641, 0.77569108, 1.6110797 , 1.32133154],
...            [ 0.89455427, 1.5202461 , 1.23270255, 1.32133154, 0.27904888]])

>>> lam, v, iters, err = raquo(A,True)
>>> iters <= 10
True

>>> err < 1.0e-15
True
"""

from numpy import *

def raquo(A, details=False):
    n,_ = shape(A)
    v = random.rand(n)
    v = v / linalg.norm(v)
    lam = v.dot(A).dot(v)
    err = 1.0
    iters = 0
    while err > 1.0e-15 and iters < 1000:
        w = linalg.solve(A - lam*eye(n), v)
        v = w / linalg.norm(w)
        lam = v.dot(A).dot(v)
        err = linalg.norm(A.dot(v) - lam*v)
        iters += 1
    if details:
        return lam, v, iters, err
    else:
        return lam, v

if __name__ == "__main__":
    import doctest
    doctest.testmod()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Test solutions to chapter 5 exercises.

###############################################################################
# test_chapter5.py
#
# Revision:     1.00
# Date:         6/29/2021
# Author:       Alex
#
# Purpose:      Runs unit tests on all chapter 5 exercises from "Data
#               Structures and Algorithms in Python" by Goodrich et. al.
#
###############################################################################
"""

# %% Imports
# Standard system imports

# Related third party imports
import pytest
import numpy as np

# Local application/library specific imports
import dsa.chapter5_exercises as chap5

# %% Reinforcement Exercises
def test_sum_matrix():
    """Solution to exercise R-5.11.

    Use standard control structures to compute the sum of all numbers in an
    n × n data set, represented as a list of lists.

    --------------------------------------------------------------------------
    Solution:
    --------------------------------------------------------------------------
    I will create an (n x n) array, the values of which range from 1 to n^2.
    The sum of this matrix then is the sum of the first n^2 integers.
    The formula to calculate this sum is then: n^2 * (n^2 + 1) / 2.
    This will allow me to verify that my solution works as expected.
    """
    n = 10
    alist = [[i for i in range(1+n*j, n*(j+1)+1)] for j in range(0, n)]
    expected_result = n**2 * (n**2 + 1) / 2
    result = chap5.sum_matrix(alist)
    assert result == expected_result

def test_sum_matrix2():
    """Solution to exercise R-5.12.

    Describe how the built-in sum function can be combined with Python’s
    comprehension syntax to compute the sum of all numbers in an n × n data
    set, represented as a list of lists.

    --------------------------------------------------------------------------
    Solution:
    --------------------------------------------------------------------------
    I use a generator comprehension to save memory, as I am only interested in
    the resulting sum after iterating through all of the values in the matrix.
    The inner sum() function sums each list representing a row in the matrix,
    and the outer sum() function sums these sums, giving the final sum.
    """
    n = 10
    alist = [[i for i in range(1+n*j, n*(j+1)+1)] for j in range(0, n)]
    expected_result = n**2 * (n**2 + 1) / 2
    result = chap5.sum_matrix2(alist)
    assert result == expected_result

# %% Project Exercises
@pytest.fixture(name="matrices", scope="class")
def matrix_fixture():
    """Fixture to supply matrices to the TestMatrix() class."""

    class MatrixInit:
        """Fixture class to store matrices as instance variables."""

        def __init__(self):
            """Define matrices to be used for testing the Matrix() class."""
            n = 10  # Define a 10x10 square matrix
            self.n = n
            self.square = [[i for i in range(1+n*j, n*(j+1)+1)]
                           for j in range(0, n)]
            # Define 5x10 and 10x5 matrices
            self.half_row = [[i for i in range(1+n*j, n*(j+1)+1)]
                             for j in range(0, n//2)]
            self.half_col = [[i for i in range(1+n*j//2, n*(j+1)//2+1)]
                             for j in range(0, n)]
    return MatrixInit()

class TestMatrix:
    """Solution to exercise P-5.33.

    Write a Python program for a matrix class that can add and multiply two-
    dimensional arrays of numbers, assuming the dimensions agree
    appropriately for the operation.

    --------------------------------------------------------------------------
    Solution:
    --------------------------------------------------------------------------
    I used Python's dunder methods to overload the '+' and '@' operators for
    addition and matrix multiplication, respectively.  I also used the
    __getitem__ and __setitem__ dunder methods to allow the user to access the
    matrix values via 2D index.  Finally, I created representations of the
    Matrix() object using the __repr__ and __str__ dunder methods.
    """

    def test_add(self, matrices):
        """Test Matrix class __add__() method."""
        # Calculate the expected result of adding the square matrix to itself
        n = matrices.n
        add_result = [[2*i for i in range(1+n*j, n*(j+1)+1)]
                      for j in range(0, n)]
        # Instantiate the 10x10 matrices and test addition
        mat_a = chap5.Matrix(matrices.square)
        mat_b = chap5.Matrix(matrices.square)
        square_add = mat_a + mat_b
        assert square_add._matrix == add_result
        # Instantiate a 5x10 and 10x5 matrix as Matrix class and Numpy array
        half_row_mat = chap5.Matrix(matrices.half_row)
        half_col_mat = chap5.Matrix(matrices.half_col)
        half_row_np = np.array(matrices.half_row)
        half_col_np = np.array(matrices.half_col)
        # Adding half matrices to themselves and compare to Numpy results
        half_row_add = (half_row_mat + half_row_mat)._matrix
        half_col_add = (half_col_mat + half_col_mat)._matrix
        assert (np.array(half_row_add) == half_row_np + half_row_np).all()
        assert (np.array(half_col_add) == half_col_np + half_col_np).all()

    def test_matmul(self, matrices):
        """Test Matrix class __matmul__() method."""
        # Instantiate the 10x10 matrix and test matrix multiplication
        square_mat = chap5.Matrix(matrices.square)
        square_np = np.array(matrices.square)
        square_matmul = (square_mat @ square_mat)._matrix
        square_np_result = square_np @ square_np
        # Compare to the Numpy result of multiplying the matrix times itself
        assert (np.array(square_matmul) == square_np_result).all()
        # Instantiate a 5x10 and 10x5 matrix as Matrix class and Numpy array
        half_row_mat = chap5.Matrix(matrices.half_row)
        half_col_mat = chap5.Matrix(matrices.half_col)
        half_row_np = np.array(matrices.half_row)
        half_col_np = np.array(matrices.half_col)
        # Matrix multiplication amongst the 10x10, 5x10, and 10x5 matrices
        result1 = half_row_mat @ half_col_mat       # (5x10)  @ (10x5)
        exp_result1 = half_row_np @ half_col_np     # (5x10)  @ (10x5)
        result2 = half_col_mat @ half_row_mat       # (10x5)  @ (5x10)
        exp_result2 = half_col_np @ half_row_np     # (10x5)  @ (5x10)
        result3 = half_row_mat @ square_mat         # (5x10)  @ (10x10)
        exp_result3 = half_row_np @ square_np       # (5x10)  @ (10x10)
        result4 = square_mat @ half_col_mat         # (10x10) @ (10x5)
        exp_result4 = square_np @ half_col_np       # (10x10) @ (10x5)
        assert (np.array(result1._matrix) == exp_result1).all()
        assert (np.array(result2._matrix) == exp_result2).all()
        assert (np.array(result3._matrix) == exp_result3).all()
        assert (np.array(result4._matrix) == exp_result4).all()

    def test_incompatible_dimensions(self, matrices):
        """Test operations with incompatible matrix dimensions."""
        # Instantiate 5x10, 10x5, and 10x10 matrices as Matrix class
        square_mat = chap5.Matrix(matrices.square)
        half_row_mat = chap5.Matrix(matrices.half_row)
        half_col_mat = chap5.Matrix(matrices.half_col)
        # Verify Matrix class raises AssertionError for incompatible dimensions
        with pytest.raises(AssertionError):
            half_row_mat + half_col_mat         # (5x10) + (10x5)
        with pytest.raises(AssertionError):
            half_col_mat + half_row_mat         # (10x5) + (5x10)
        with pytest.raises(AssertionError):
            half_col_mat @ square_mat           # (10x5) @ (10x10)
        with pytest.raises(AssertionError):
            square_mat @ half_row_mat           # (10x10) @ (5x10)

    def test_indexing(self, matrices):
        """Test ability to access Matrix data with a 2D index."""
        # Test __getitem__ functionality
        n = matrices.n
        square_mat = chap5.Matrix(matrices.square)
        assert square_mat[(0, 0)] == 1
        # Test __setitem__ functionality
        square_mat[(n-1, n-1)] = -99
        assert square_mat[(n-1, n-1)] == -99

    def test_repr_str(self, matrices):
        """Test Matrix() __repr__ and __str__ methods."""
        square_mat = chap5.Matrix(matrices.square)
        # Test __repr__
        dict_items = ('Matrix', '_matrix', '_rows', '_cols')
        assert all(x in repr(square_mat) for x in dict_items)
        # Test __str__
        row_str = str(square_mat._matrix[0])
        assert row_str in str(square_mat)