# Check README.md for instruction to run

import numpy as np
import argparse
import sys

#####################################################
# Function to calculate the machine precision/epsilon
# start_value - value from where we will start the iteration,
# i.e., we will keep on halving the start_value until 1+start_value == 1
# data_type - data type for which we need to find the machine precision.
#####################################################
def get_machine_precision(start_value = 1, data_type = np.float16):

	num_iterations = 0
	machine_precision = data_type(start_value)
	while True:
		num_iterations += 1
		if data_type(1+machine_precision/2) != data_type(1):
			machine_precision = data_type(machine_precision/2)
		else:
			break

	return machine_precision, num_iterations

###############################################
# I have created a parser to accept command line argument
# This code can accept two command line arguments -
# 1. precision: can be single (32 bit float) or double (64 bit float)
# 2. start_value: starting value of the algorithm used to find the machine precision

# If no command line argument is passed, default value of precision is single (32 bit float)
# and defalut value of start_value is 1.
################################################
if __name__ == '__main__':

	parser = argparse.ArgumentParser(description='Data Type and Starting Value is expected')

	parser.add_argument("-p", "--precision", type = str, default = "single", help = "Data Type")
	parser.add_argument("-s", "--start_value", type = float, default = 1, help = "Starting Value of the iteration to find machine precision")

	args = parser.parse_args()
	if args.precision != "single" and args.precision != "double":
		print("Invalid Precision")
		sys.exit()

	# Dictionary to map precision to the corresponding data type.
	data_type_dictionary = {
	"single" : np.float32,
	"double" : np.float64
	}

	machine_precision, num_iterations = get_machine_precision(args.start_value, data_type_dictionary[args.precision])

	print("Data Type : ", data_type_dictionary[args.precision])
	print("Machine Precision : ", machine_precision)
	print("Number of iterations required to get to the precision from start value ({}) = {}".format(args.start_value, num_iterations))


import numpy as np

def theta2rotx(theta: np.ndarray) -> np.ndarray:
    """
    Rx = [[1, 0, 0],
          [0, c(t), -s(t)],
          [0, s(t), c(t)]]
    :param theta: angle(s) in degrees, positive is counterclockwise
    :return: rotation_matrices
    """
    theta = np.deg2rad(np.asarray(theta).reshape(-1))
    rotation_matrices = np.zeros((theta.shape[0], 3, 3), dtype=float)
    cos_theta = np.cos(theta)
    sin_theta = np.sin(theta)
    rotation_matrices[:, 0, 0] = 1
    rotation_matrices[:, (1, 2), (1, 2)] = cos_theta[:, np.newaxis]
    rotation_matrices[:, 1, 2] = -sin_theta
    rotation_matrices[:, 2, 1] = sin_theta
    return rotation_matrices

def theta2roty(theta: np.ndarray) -> np.ndarray:
    """
    Ry = [[c(t), 0, s(t)],
          [0, 1, 0],
          [-s(t), 0, c(t)]]
    :param theta: angle(s) in degrees, positive is counterclockwise
    :return: rotation_matrices
    """
    theta = np.deg2rad(np.asarray(theta).reshape(-1))
    rotation_matrices = np.zeros((theta.shape[0], 3, 3), dtype=float)
    cos_theta = np.cos(theta)
    sin_theta = np.sin(theta)
    rotation_matrices[:, 1, 1] = 1
    rotation_matrices[:, (0, 2), (0, 2)] = cos_theta[:, np.newaxis]
    rotation_matrices[:, 0, 2] = sin_theta
    rotation_matrices[:, 2, 0] = -sin_theta
    return rotation_matrices

def theta2rotz(theta: np.ndarray) -> np.ndarray:
    """
    Rz = [[c(t), -s(t), 0],
          [s(t), c(t), 0],
          [0, 0, 1]]
    :param theta: angle(s) in degrees, positive is counterclockwise
    :return: rotation_matrices
    """
    theta = np.deg2rad(np.asarray(theta).reshape(-1))
    rotation_matrices = np.zeros((theta.shape[0], 3, 3), dtype=float)
    cos_theta = np.cos(theta)
    sin_theta = np.sin(theta)
    rotation_matrices[:, 2, 2] = 1
    rotation_matrices[:, (0, 1), (0, 1)] = cos_theta[:, np.newaxis]
    rotation_matrices[:, 0, 1] = -sin_theta
    rotation_matrices[:, 1, 0] = sin_theta
    return rotation_matrices

def theta2rotm(theta: np.ndarray, axis: str):
    """
    Convert values for theta into rotation matrices around a given axis 'x', 'y' or 'z'
    :param theta: angle(s) in degrees, positive is counterclockwise
    :param axis: 'x', 'y' or 'z'
    :return: rotation_matrices
    """
    axis = axis.strip().lower()
    if axis not in ('x', 'y', 'z'):
        raise ValueError(f"Axis must be one of 'x', 'y' or 'z''")
    elif axis == 'x':
        rotation_matrices = theta2rotx(theta)
    elif axis == 'y':
        rotation_matrices = theta2roty(theta)
    elif axis == 'z':
        rotation_matrices = theta2rotz(theta)
    if rotation_matrices.shape[0] == 1:
        rotation_matrices = rotation_matrices.reshape((3, 3))
    return rotation_matrices


from numpy import *
from math import sqrt

# Input: expects Nx3 matrix of points
# Returns R,t
# R = 3x3 rotation matrix
# t = 3x1 column vector

def rigid_transform_3D(A, B):
    assert len(A) == len(B)

    N = A.shape[0];  # total points

    centroid_A = mean(A, axis=0)
    centroid_B = mean(B, axis=0)

    # centre the points
    AA = A - tile(centroid_A, (N, 1))
    BB = B - tile(centroid_B, (N, 1))

    # dot is matrix multiplication for array
    H = transpose(AA) * BB

    U, S, Vt = linalg.svd(H)

    R = Vt.T * U.T

    # special reflection case
    if linalg.det(R) < 0:
        print "Reflection detected"
        Vt[2, :] *= -1
        R = Vt.T * U.T

    t = -R * centroid_A.T + centroid_B.T

    print t

    return R, t

# Test with random data

# Random rotation and translation
R = mat(random.rand(3, 3))
t = mat(random.rand(3, 1))

# make R a proper rotation matrix, force orthonormal
U, S, Vt = linalg.svd(R)
R = U * Vt

# remove reflection
if linalg.det(R) < 0:
    Vt[2, :] *= -1
    R = U * Vt

# number of points
n = 10

A = mat(random.rand(n, 3));
B = R * A.T + tile(t, (1, n))
B = B.T;

# recover the transformation
ret_R, ret_t = rigid_transform_3D(A, B)

A2 = (ret_R * A.T) + tile(ret_t, (1, n))
A2 = A2.T

# Find the error
err = A2 - B

err = multiply(err, err)
err = sum(err)
rmse = sqrt(err / n);

print "Points A"
print A
print ""

print "Points B"
print B
print ""

print "Rotation"
print R
print ""

print "Translation"
print t
print ""

print "RMSE:", rmse
print "If RMSE is near zero, the function is correct!"


import numpy as np
import math

# labels_dict : {ind_label: count_label}
# mu : parameter to tune

def create_class_weight(labels_dict,mu=0.15):
    total = np.sum(list(labels_dict.values()))
    keys = labels_dict.keys()
    class_weight = dict()

    for key in keys:
        score = math.log(mu*total/float(labels_dict[key]))
        class_weight[key] = score if score > 1.0 else 1.0

    return class_weight

# random labels_dict
labels_dict = {0: 17288, 1: 5392, 2: 886, 3: 2003, 4: 1429, 5: 1330, 6: 972, 7: 1379, 8: 659, 9: 2250, 10: 921, 11: 29, 12: 13, 13: 25}

print(create_class_weight(labels_dict))

# label_to_pair_type = {'NO_RELATION': 0,
#                       'INHIBITOR': 1,
#                       'PART-OF': 2,
#                       'SUBSTRATE': 3,
#                       'ACTIVATOR': 4,
#                       'INDIRECT-DOWNREGULATOR': 5,
#                       'ANTAGONIST': 6,
#                       'INDIRECT-UPREGULATOR': 7,
#                       'AGONIST': 8,
#                       'DIRECT-REGULATOR': 9,
#                       'PRODUCT-OF': 10,
#                       'AGONIST-ACTIVATOR': 11,
#                       'AGONIST-INHIBITOR': 12,
#                       'SUBSTRATE_PRODUCT-OF': 13}


#!/usr/bin/env python
# coding: utf-8

# # Attention Basics
# In this notebook, we look at how attention is implemented. We will focus on implementing attention in isolation from a larger model. That's because when implementing attention in a real-world model, a lot of the focus goes into piping the data and juggling the various vectors rather than the concepts of attention themselves.
#
# We will implement attention scoring as well as calculating an attention context vector.
#
# ## Attention Scoring
# ### Inputs to the scoring function
# Let's start by looking at the inputs we'll give to the scoring function. We will assume we're in the first step in the decoging phase. The first input to the scoring function is the hidden state of decoder (assuming a toy RNN with three hidden nodes -- not usable in real life, but easier to illustrate):

# In[1]:

dec_hidden_state = [5,1,20]

# Let's visualize this vector:

# In[2]:

get_ipython().run_line_magic('matplotlib', 'inline')
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# Let's visualize our decoder hidden state
plt.figure(figsize=(1.5, 4.5))
sns.heatmap(np.transpose(np.matrix(dec_hidden_state)), annot=True, cmap=sns.light_palette("purple", as_cmap=True), linewidths=1)

# Our first scoring function will score a single annotation (encoder hidden state), which looks like this:

# In[3]:

annotation = [3,12,45] #e.g. Encoder hidden state

# In[4]:

# Let's visualize the single annotation
plt.figure(figsize=(1.5, 4.5))
sns.heatmap(np.transpose(np.matrix(annotation)), annot=True, cmap=sns.light_palette("orange", as_cmap=True), linewidths=1)

# ### IMPLEMENT: Scoring a Single Annotation
# Let's calculate the dot product of a single annotation. Numpy's [dot()](https://docs.scipy.org/doc/numpy/reference/generated/numpy.dot.html) is a good candidate for this operation

# In[12]:

def single_dot_attention_score(dec_hidden_state, enc_hidden_state):

    # TODO: return the dot product of the two vectors
    return np.dot(dec_hidden_state, enc_hidden_state)

single_dot_attention_score(dec_hidden_state, annotation)

#
# ### Annotations Matrix
# Let's now look at scoring all the annotations at once. To do that, here's our annotation matrix:

# In[6]:

annotations = np.transpose([[3,12,45], [59,2,5], [1,43,5], [4,3,45.3]])

# And it can be visualized like this (each column is a hidden state of an encoder time step):

# In[7]:

# Let's visualize our annotation (each column is an annotation)
ax = sns.heatmap(annotations, annot=True, cmap=sns.light_palette("orange", as_cmap=True), linewidths=1)

# ### IMPLEMENT: Scoring All Annotations at Once
# Let's calculate the scores of all the annotations in one step using matrix multiplication. Let's continue to us the dot scoring method
#
#
#
# To do that, we'll have to transpose `dec_hidden_state` and [matrix multiply](https://docs.scipy.org/doc/numpy/reference/generated/numpy.matmul.html) it with `annotations`.

# In[23]:

def dot_attention_score(dec_hidden_state, annotations):
    # TODO: return the product of dec_hidden_state transpose and enc_hidden_states

    return np.matmul(np.transpose(dec_hidden_state), annotations)

attention_weights_raw = dot_attention_score(dec_hidden_state, annotations)
attention_weights_raw

# Looking at these scores, can you guess which of the four vectors will get the most attention from the decoder at this time step?
#
# ## Softmax
# Now that we have our scores, let's apply softmax:
#

# In[24]:

def softmax(x):
    x = np.array(x, dtype=np.float128)
    e_x = np.exp(x)
    return e_x / e_x.sum(axis=0)

attention_weights = softmax(attention_weights_raw)
attention_weights

# Even when knowing which annotation will get the most focus, it's interesting to see how drastic softmax makes the end score become. The first and last annotation had the respective scores of 927 and 929. But after softmax, the attention they'll get is 0.12 and 0.88 respectively.
#
# # Applying the scores back on the annotations
# Now that we have our scores, let's multiply each annotation by its score to proceed closer to the attention context vector. This is the multiplication part of this formula (we'll tackle the summation part in the latter cells)
#
#

# In[25]:

def apply_attention_scores(attention_weights, annotations):
    # TODO: Multiple the annotations by their weights
    return attention_weights * annotations

applied_attention = apply_attention_scores(attention_weights, annotations)
applied_attention

# Let's visualize how the context vector looks now that we've applied the attention scores back on it:

# In[26]:

# Let's visualize our annotations after applying attention to them
ax = sns.heatmap(applied_attention, annot=True, cmap=sns.light_palette("orange", as_cmap=True), linewidths=1)

# Contrast this with the raw annotations visualized earlier in the notebook, and we can see that the second and third annotations (columns) have been nearly wiped out. The first annotation maintains some of its value, and the fourth annotation is the most pronounced.
#
# # Calculating the Attention Context Vector
# All that remains to produce our attention context vector now is to sum up the four columns to produce a single attention context vector
#

# In[27]:

def calculate_attention_vector(applied_attention):
    return np.sum(applied_attention, axis=1)

attention_vector = calculate_attention_vector(applied_attention)
attention_vector

# In[28]:

# Let's visualize the attention context vector
plt.figure(figsize=(1.5, 4.5))
sns.heatmap(np.transpose(np.matrix(attention_vector)), annot=True, cmap=sns.light_palette("Blue", as_cmap=True), linewidths=1)

# Now that we have the context vector, we can concatinate it with the hidden state and pass it through a hidden layer to produce the the result of this decoding time step.


# Uses python3

def evalt(a, b, op):
    if op == '+':
        return a + b
    elif op == '-':
        return a - b
    elif op == '*':
        return a * b
    else:
        assert False

def MinMax(i, j, op, m, M):
    import numpy as np
    m_min = np.inf
    m_max = -np.inf

    for k in range(i, j):
        a = evalt(M[i][k], M[k+1][j], op[k])
        b = evalt(M[i][k], m[k+1][j], op[k])
        c = evalt(m[i][k], M[k+1][j], op[k])
        d = evalt(m[i][k], m[k+1][j], op[k])

        m_min = min(m_min, a, b, c, d)
        m_max = max(m_max, a, b, c, d)

    return(m_min, m_max)

def get_maximum_value(dataset):
    op = dataset[1 : len(dataset) : 2]
    digits = dataset[0 : len(dataset)+1 : 2]

    n = len(digits)
    m = [[0]*n for _ in range(n)]
    M = [[0]*n for _ in range(n)]

    for i in range(n):
        m[i][i] = int(digits[i])
        M[i][i] = int(digits[i])

    for s in range(1, n):
        for i in range(n-s):
            j = i + s
            m[i][j], M[i][j] = MinMax(i, j, op, m, M)

    return M[0][n-1]

if __name__ == "__main__":
    print(get_maximum_value(input()))


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

info_list = []

def PowerMethod(A, norm_indicator=True, converge_range=0.0001):

    r, c = A.shape

    if r != c:
        raise Exception("not a square matrix")

    #initialize eigenvectors
    v_list = []
    lambda_list = []
    diff_list = []

    idx = 0
    v_init = np.zeros(r)
    v_init[-1] = 1
    v_list.append(v_init)

    #initialize eigenvalues
    lambda_init = v_init.dot(A.dot(v_init))
    lambda_list.append(lambda_init)

    diff_init = float("inf")
    diff_list.append(diff_init)

    while diff_list[idx] > converge_range:
        #new vector
        v_new = A.dot(v_list[idx])

        if norm_indicator:
            v_new = v_new / np.linalg.norm(v_new)

        v_list.append(v_new)
        lambda_new = v_new.dot(A.dot(v_new))
        lambda_list.append(lambda_new)
        diff = np.abs(lambda_new - lambda_list[idx])
        diff_list.append(diff)
        idx = idx + 1

    print_log(idx, v_list, lambda_list, diff_list)

    #plot lambda_list
    x = [i for i in range(idx+1)]
    if len(x) > 20:
        x = x[:21]
        diff_list = diff_list[:21]

    plt.plot(x, diff_list)
    plt.savefig("difference_list_with" + str(norm_indicator) + "normalization")
    plt.show()
    return v_list[-1], lambda_list[-1]

def print_log(idx, v_list, lambda_list, diff_list):
    info_list = [[i,v_list[i],lambda_list[i],diff_list[i]] for i in range(idx)]
    print(tabulate(info_list, headers=["iteration","eigenvector", "eigenvalue","lambda_diff"]))


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

def error(line, data):
    '''
    Compute error between given model and observed data.

    Parameters:
    -----------
    line: tuple, list, array - C0 (slope) and C1 is Y-intercept
    data: 2D array
    '''
    x = data[:, 0]
    y = data[:, 1]
    C0 = line[0]
    C1 = line[1]
    error = np.sum((y - (C0 * x + C1)) ** 2)
    return error

def generate_line(C0, C1, sigma):
    '''
    Generate a line with noise.

    Parameters:
    -----------
    C0: slope
    C1: Y_intercept
    sigma: noise
    '''
    print ("Original Line: C0 = {}, C1 = {}".format(C0, C1))
    x_orig = np.linspace(0,10,21)
    y_orig = C0 * x_orig + C1
    plt.plot(x_orig,y_orig, 'b--', linewidth = 2.0, label = 'Original Line')

    ### Generate Noisy Points
    noise_sigma = sigma
    noise = np.random.normal(0, noise_sigma, y_orig.shape)
    data = np.asarray([x_orig,y_orig + noise]).T
    return data

def fit_line(data, error_func):
    ''' Fit a line to given data, using a supplied error function.
    Parameters
    ----------
    data: 2D array where each row is a point (X0,Y)
    error: function that computes the error between a line and observed data

    Returns a line that minimizes the error function
    '''

    # Intializing as a horizontal line with y = y.mean
    l_init = np.float32([0, np.mean(data[:, 1])])

    # Plotting the initial guess
    X_min = -1
    X_max = 5
    X_range = np.float32([X_min, X_max ])
    C0_init = l_init[0]
    C1_init = l_init[1]
    Y_init = C0_init * X_range + C1_init
    plt.plot(X_range, Y_init, 'm--', label = 'Initial guess')

    # Minimizing error function
    result = spo.minimize(error, l_init, args = (data,), method = 'SLSQP', options = {'disp': True})

    return result.x

def run():
    data = generate_line(1, 2, 2)
    fited_line = fit_line(data,error)
    C0_fited = fited_line[0]
    C1_fited = fited_line[1]
    X_data = data[:, 0]
    Y_data = data[:, 1]
    plt.plot(X_data, Y_data, 'go', label = 'Data Points')
    plt.plot(X_data,C0_fited * X_data + C1_fited, 'r--', label = 'fitted line')
    plt.legend(loc = 'upper left')
    plt.show()
    print ('Fitted Line: C0 = {}, C1 = {}'.format(C0_fited, C1_fited))

if __name__ == "__main__":
    run()


import sympy as sp
from sympy.parsing.sympy_parser import *

class num_method():
    def __init__(self, fx=0, itr_no=50, ees=0.00001, prec=0, err_relative=True, tab=11):
        self.fx = fx
        self.itr_no = itr_no

        if not prec:
            self.ees = ees
            self.generate_precision()
        else:
            self.prec = prec
            self.generate_epsilon()

        # flag to know if we want the absolute error or the relative error
        self.err_relative = err_relative
        # spaces in our table format
        self.tab = tab
        # to treat "x" as a variable
        self.x = sp.Symbol("x")

    def _components(self):
        print()
        print("fx: ", self.fx)
        print("itr_no: ", self.itr_no)
        print("tabs: ", self.tab)
        print("ees: ", self.ees)
        print("prec: ", self.prec)
        print()

    def generate_precision(self):
        import math
        self.prec = math.floor(2 - (math.log10((self.ees*100)*2)))

    def generate_epsilon(self):
        self.ees = (0.5*10**(2-self.prec))

    def convert_float(self, num):
        # used for rounding a number to n decimal places
        return float("{:.{prec}f}".format(num, prec=self.prec))

    def err_bound(self, alpha, xi):
        delta = alpha - xi
        dfa = sp.diff(self.fx)
        ddfa = sp.diff(dfa)

        ddfa = float(sp.N(ddfa.subs(self.x, alpha)))
        dfa = float(sp.N(dfa.subs(self.x, alpha)))

        delta_new = ((-ddfa)/(2*dfa)) * (delta**2)
        return abs(delta_new)

    def err_bound_bracket(self,xl,xu,n):
        return abs((xu-xl)/(2**n))

    def bisection(self, xl, xu):
        print()
        print()
        print("Bisection :")
        print("_______________________")
        print()

        # creating the table format for printing
        table_format = ["i", "xl", "xu", "xr", "f(xr)"]
        print("|{:>{tab}}|{:>{tab}}|{:>{tab}}|{:>{tab}}|{:>{tab}}".format(
            *table_format, tab=2*self.tab))

        fxl = float(sp.N(self.fx.subs(self.x, xl)))
        fxu = float(sp.N(self.fx.subs(self.x, xu)))

        if fxl*fxu > 0:
            print("No roots !!!")
            return -1, xl, False

        xr = float((xu + xl) / 2)
        fxr = float(sp.N(self.fx.subs(self.x, xr)))

        # creating the data format for printing
        data = [1, xl, xu, xr, fxr, self.ees]
        print("|{:>{tab}}|{:>{tab}}|{:>{tab}}|{:>{tab}}|{:>{tab}}|Ɛes={:>{tab}}".format(
            *data, tab=2*self.tab))

        for i in range(2, self.itr_no+1):
            # determine which sub-interval our root lies in
            if fxl*fxr == 0:
                print(f"root x{i} = {xr}")
            elif fxl*fxr < 0:
                xu = xr
            else:
                xl = xr

            # store xr to be used later in calculating our approx. error
            xr_old = xr

            xr = (xu + xl) / 2
            fxr = float(sp.N(self.fx.subs(self.x, xr)))

            if self.err_relative:
                ea = abs((xr - xr_old)/xr) if xr != 0 else 0.001
            else:
                ea = abs(xr - xr_old)

            # creating the data format for printing
            data = [i, xl, xu, xr, fxr, ea]

            # check our stopping condition
            if ea <= self.ees:
                print("|{:>{tab}}|{:>{tab}}|{:>{tab}}|{:>{tab}}|{:>{tab}}|Ɛa ={:>{tab}} <=".format(
                    *data, tab=2*self.tab))
                print()
                print(f"root x{i} = {xr}")
                return i, xr, self.err_bound_bracket(xu,xl,i)
            else:
                print("|{:>{tab}}|{:>{tab}}|{:>{tab}}|{:>{tab}}|{:>{tab}}|Ɛa ={:>{tab}} > ".format(
                    *data, tab=2*self.tab))

        print()
        print("Max number of allowed iterations reached !!!")
        print(f"root x{i} = {xr}")
        print("_________________")
        return i, xr, self.err_bound_bracket(xu,xl,i)

    def false_position(self, xl, xu):
        print()
        print()
        print("False-position :")
        print("_______________________")
        print()

        # creating the table format for printing
        table_format = ["i", "xl", "xu", "f(xl)", "f(xu)", "xr", "f(xr)"]
        print("|{:>{tab}}|{:>{tab}}|{:>{tab}}|{:>{tab}}|{:>{tab}}|{:>{tab}}|{:>{tab}}".format(
            *table_format, tab=2*self.tab))

        fxl = float(sp.N(self.fx.subs(self.x, xl)))
        fxu = float(sp.N(self.fx.subs(self.x, xu)))

        if fxl*fxu > 0:
            print("No roots !!!")
            return -1, xl

        xr = float((xl*fxu - xu*fxl) / (fxu - fxl))
        # xr = float(((xu-xl)*fxl) / (fxu - fxl))
        fxr = float(sp.N(self.fx.subs(self.x, xr)))

        # creating the data format for printing
        data = [1, xl, xu, fxl, fxu, xr, fxr, self.ees]
        print("|{:>{tab}}|{:>{tab}}|{:>{tab}}|{:>{tab}}|{:>{tab}}|{:>{tab}}|{:>{tab}}|Ɛes={:>{tab}}".format(
            *data, tab=2*self.tab))

        for i in range(0, self.itr_no+1):
            # determine which sub-interval our root lies in
            if fxl*fxr == 0:
                print(f"root x{i} = {xr}")
            elif fxl*fxr < 0:
                xu = xr
            else:
                xl = xr

            # store xr to be used later in calculating our approx. error
            xr_old = xr

            xr = float((xl*fxu - xu*fxl) / (fxu - fxl))
            # xr = float(((xu-xl)*fxl) / (fxu - fxl))
            fxr = float(sp.N(self.fx.subs(self.x, xr)))

            fxl = float(sp.N(self.fx.subs(self.x, xl)))
            fxu = float(sp.N(self.fx.subs(self.x, xu)))

            if self.err_relative:
                ea = abs((xr - xr_old)/xr) if xr != 0 else 0.001
            else:
                ea = abs(xr - xr_old)

            # creating the data format for printing
            data = [i, xl, xu, fxl, fxu, xr, fxr, ea]

            # check our stopping condition
            if ea <= self.ees:
                print("|{:>{tab}}|{:>{tab}}|{:>{tab}}|{:>{tab}}|{:>{tab}}|{:>{tab}}|{:>{tab}}|Ɛa ={:>{tab}} <=".format(
                    *data, tab=2*self.tab))
                print()
                print(f"root x{i} = {xr}")
                return i, xr
            else:
                print("|{:>{tab}}|{:>{tab}}|{:>{tab}}|{:>{tab}}|{:>{tab}}|{:>{tab}}|{:>{tab}}|Ɛa ={:>{tab}} > ".format(
                    *data, tab=2*self.tab))

        print()
        print("Max number of allowed iterations reached !!!")
        print(f"root x{i} = {xr}")
        print("_________________")
        return i, xr

    def newton_raphson(self, xi, alpha=False):
        print()
        print()
        print("Newton Raphson :")
        print("_______________________")
        print()

        # creating the table format for printing
        table_format = ["i", "xi", "x(i+1)"]
        print("|{:>{tab}}|{:>{tab}}|{:>{tab}}|".format(
            *table_format, tab=2*self.tab))

        for i in range(self.itr_no+1):
            # evaluate f(xi) and f'(xi)
            fxi = float(sp.N(self.fx.subs(self.x, xi)))
            dfxi = float(sp.N(sp.diff(self.fx).subs(self.x, xi)))
            # if we want to add delta
            # delta δ = alpha - xi

            # estimate new value of x(i+1)
            xi_new = float(xi - (fxi/dfxi))

            if self.err_relative:
                ea = abs((xi_new - xi)/xi_new) if xi_new != 0 else 0.001
            else:
                ea = abs(xi_new - xi)

            # creating the data format for printing
            data = [i, xi, xi_new, ea]

            # check our stopping condition
            if ea <= self.ees:
                print("|{:>{tab}}|{:>{tab}}|{:>{tab}}|Ɛa ={:>{tab}} <=".format(
                    *data, tab=2*self.tab))
                print()
                print(f"root x{i+1} = {xi_new}")
                if alpha != False:
                    return i+1, xi_new,self.err_bound(alpha,xi)
                else:
                    return i+1, xi_new,False

            else:
                print("|{:>{tab}}|{:>{tab}}|{:>{tab}}|Ɛa ={:>{tab}} > ".format(
                    *data, tab=2*self.tab))

            # replace xi with x(i+1)
            xi = xi_new

        print()
        print("Max number of allowed iterations reached !!!")
        print(f"root x{i+1} = {xi_new}")
        print("_________________")
        if alpha != False:
            return i+1, xi_new,self.err_bound(alpha,xi)
        else:
            return i+1, xi_new,False

    #SECANT
    def secant(self, xi, xi_1):
        print()
        print()
        print("Secant :")
        print("_______________________")
        print()

        # creating the table format for printing
        table_format = ["i","x(i-1)","x(i)", "x(i+1)"]
        print("|{:>{tab}}|{:>{tab}}|{:>{tab}}|{:>{tab}}|".format(*table_format, tab=2 * self.tab))

        # if self.fx.subs(self.x,xi) * self.fx.subs(self.x,xi_1) >= 0:
        #     print("Secant method won't work")
        #     return

        for i in range(self.itr_no+1):
            fxi = float(sp.N(self.fx.subs(self.x,xi)))
            fxi_1 = float(sp.N(self.fx.subs(self.x,xi_1)))

            #Computing the estimated root
            xi_new = float(xi - fxi * ((xi_1 - xi)/(fxi_1 - fxi)))

            if self.err_relative:
                ea = abs((xi_new - xi)/xi_new) if xi_new != 0 else 0.001
            else:
                ea = abs(xi_new - xi)

            # creating the data format for printing
            data = [i, xi_1, xi, xi_new, ea]

            # check our stopping condition
            if ea <= self.ees:
                print("|{:>{tab}}|{:>{tab}}|{:>{tab}}|{:>{tab}}|Ɛa ={:>{tab}} <=".format(*data, tab=2 * self.tab))
                print()
                print(f"root x{i + 1} = {xi_new}")
                return i+1, xi_new
            else:
                print("|{:>{tab}}|{:>{tab}}|{:>{tab}}|{:>{tab}}|Ɛa ={:>{tab}} > ".format(*data, tab=2 * self.tab))

            #Replacing values
            xi_1 = xi
            xi = xi_new

            # if xi_1 == xi:
            #     print("Can't Divide by Zero")
            #     return -1, xi

        print()
        print("Max number of allowed iterations reached !!!")
        print(f"root x{i + 1} = {xi_new}")
        print("_________________")
        return i+1, xi_new

    def fixed_point(self, x1, x2=None):
        print()
        print()
        print("Fixed-Point :")
        print("_______________________")
        print()

        if x2:
            xi = (x1 + x2)/2
        else:
            xi = x1

        # evaluate g'(xi)
        dgx = float(sp.N(sp.diff(self.fx).subs(self.x, xi)))

        if dgx > 1:
            print(f"g'{xi} = {dgx} > 1    Divergence !!!")
            return -1, xi

        # creating the table format for printing
        table_format = ["i", "xi", "x(i+1)"]
        print("|{:>{tab}}|{:>{tab}}|{:>{tab}}|".format(
            *table_format, tab=2*self.tab))

        for i in range(1, self.itr_no+1):
            # estimate new value of x(i+1)
            xi_new = float(sp.N(self.fx.subs(self.x, xi)))

            if self.err_relative:
                ea = abs((xi_new - xi)/xi_new) if xi_new != 0 else 0.001
            else:
                ea = abs(xi_new - xi)

            # creating the data format for printing
            data = [i, xi, xi_new, ea]

            # check our stopping condition
            if ea <= self.ees:
                print("|{:>{tab}}|{:>{tab}}|{:>{tab}}|Ɛa ={:>{tab}} <=".format(
                    *data, tab=2*self.tab))
                print()
                print(f"root x{i} = {xi_new}")
                return i, xi_new
            else:
                print("|{:>{tab}}|{:>{tab}}|{:>{tab}}|Ɛa ={:>{tab}} > ".format(
                    *data, tab=2*self.tab))

            # replace xi with x(i+1)
            xi = xi_new

        print()
        print("Max number of allowed iterations reached !!!")
        print(f"root x{i} = {xi_new}")
        print("_________________")
        return i, xi_new

def function_parser(fx):

    #Define trasnformation
    transformations = (standard_transformations + (implicit_multiplication_application,) \
                    + (function_exponentiation,) + (implicit_application,) + (implicit_multiplication,) \
                    + (convert_xor,) + (factorial_notation,) + (auto_number,))

    my_eq = parse_expr(fx, transformations=transformations)
    x = sp.Symbol("x")
    e = sp.Symbol("e")

    my_eq = my_eq.subs(e,sp.exp(1))

    return my_eq

if __name__ == "__main__":
    # the objective function
    x = sp.Symbol("x")
    fx = x**4 - 2*(x**3) - 4*(x**2) + 4*x + 4

    # the number of iterations
    itr_no = 10

    # the Epsilon
    ees = 0.01

    # initializing our numerical methods class
    num_method = num_method(fx, itr_no=itr_no, ees=ees)

    # our interval
    # [xl, xu]
    xl = -2
    xu = -1

    # Calling our Bisection Method
    num_method.bisection(xl, xu)

    # if want to add sin use sp.sin(x)
    fx = sp.exp(-x) - x

    x0 = 0
    num_method.fx = fx
    num_method.ees = 10**(-6)
    num_method.err_relative = False
    num_method.newton_raphson(x0)

    fx = (2*x - 1)/x
    x1 = 1
    x2 = 4
    num_method.fx = fx
    num_method.ees = 0.1
    num_method.err_relative = False
    num_method.fixed_point(x1, x2)

    fx = x**2 - x + 1
    x1 = 2.5
    num_method.fx = fx
    num_method.ees = 0.1
    num_method.err_relative = False
    num_method.fixed_point(x1)

    num_method.fx = 2*x**3 - 2*x - 5
    num_method.ees = 10**-2
    num_method.err_relative = True
    num_method.false_position(1, 2)

    # printing our class components for debugging
    num_method._components()


# encoding=utf8

import numpy as np

def bernoulli(p=0.5, size=1):
    r"""
    Generates random samples from the Bernoulli distribution.

    Parameters
    ----------
    p : probability of success, default 0.5
        The probability of success of a Bernoulli trial (Yes/No, Pass/Fail, etc.). Must be between
        0 and 1
    size : int, default 1
        The number of random Bernoulli samples to generate.

    Returns
    -------
    list or boolean
        List containing the generated Bernoulli random samples represented by boolean True/False values.
        If size is 1, the single generated sample is returned as a boolean.

    Notes
    -----

    See Also
    --------
    Bernoulli : class
        Class containing several methods related to estimating Bernoulli random variates.

    References
    ----------

    """
    if 0.0 > p or p > 1.0:
        raise ValueError('p must be between 0 and 1')
    if size < 1:
        raise ValueError('size parameter must be at least 1')
    if np.floor(size) != size:
        size = np.floor(size)

    b = []
    for _ in np.arange(size):
        b.append(continuous_uniform() < p)

    if len(b) == 1:
        b = b[0]

    return b

def binomial(n=5, p=0.5, size=1):
    if 0.0 > p or p > 1.0:
        raise ValueError('p must be between 0 and 1')
    if n < 0:
        raise ValueError('n must be at least 0')
    if np.floor(n) != n:
        n = np.floor(n)
    if size < 1:
        raise ValueError('size parameter must be at least 1')
    if np.floor(size) != size:
        size = np.floor(size)

    bi = []

    for _ in np.arange(size):
        s = 0

        for _ in np.arange(n):
            s += bernoulli(p)

        bi.append(s)

    if len(bi) == 1:
        bi = bi[0]

    return bi

def continuous_uniform(a=0.0, b=1.0, size=1):
    if a > b:
        raise ValueError('parameter b must be greater than a')
    if size < 1:
        raise ValueError('size parameter must be at least 1')
    if np.floor(size) != size:
        size = np.floor(size)

    u = []
    for _ in np.arange(size):
        u.append(a + (b - a) * np.random.random_sample())

    if len(u) == 1:
        u = u[0]

    return u

# def hypergeometric(n, N, K):
#     if n < 1:
#         raise ValueError('number of trials must be at least 1')
#     elif n > N:
#         raise ValueError('number of trials must be higher than population of elements')
#     elif N < 1:
#         raise ValueError('population of elements must be at least 1')
#     elif K < 0:
#         raise ValueError('number of successes must be at least 0')
#
#     h = 0
#
#     for _ in np.arange(n):
#         p = K / N
#
#         if bernoulli(p):
#             h += 1


import numpy as np
import scipy as sc
import util
import log

from scipy import special
from scipy import stats

def mleRegression(x_data, t_data, std):
    '''
    Completes a MLE regression for the given data
    Args:
        x_data (np.array): array of input data
        y_data (np.array): array of training data
    '''
    log.log("Calculating MLE regression")
    (N,K) = x_data.shape
    x = np.zeros((N,K+1)) + 1
    x[:,1:] = x_data #Expanding with a colomn of ones for bias terms
    #(X'*X)^(-1)*(X')*Y
    xtx_i = np.linalg.inv(x.T.dot(x))
    beta_hat = xtx_i.dot(x.T).dot(t_data)
    #Standard variance
    y_star = (t_data - x.dot(beta_hat))

    s2 = y_star.dot(y_star)
    sig_hat = 1.0/(N-K-1)*s2
    std_err = (np.sqrt(np.eye(K+1)*sig_hat*xtx_i)).dot(np.zeros(K+1)+1)

    t = (beta_hat/std_err)
    p_right = 1.0-sc.stats.t.cdf(np.abs(t),N-K-1)
    p_left = sc.stats.t.cdf(-np.abs(t),N-K-1)
    p = p_right+p_left

    log.sucess("MLE Regression complete")
    return beta_hat, std_err, t, p

def priorExpectations(x_data, t_data, beta_hat, beta_tilde, a, b, c):
    '''
    Computes the expecation and variance of the beta_hats for conjugate priors
    @params:
        x_data (np.array): array of input data
        t_data (np.array): array of training data
        beta_hat (np.array): MLE linear regression weights
        beta_tilde (np.array): prior hyperparameter
        a (float): hyper-parameter
        b (float): hyper-parameter
        c (float): hyper-parameter
    '''
    log.log("Computing exp and var for conjugate priors")
    log.log("Hyper-parameters a:%.2f b:%.2f c:%.2f"%(a, b, c))

    (N,K) = x_data.shape
    x = np.zeros((N,K+1)) + 1
    x[:,1:] = x_data #Expanding with a colomn of ones for bias terms

    y_star = (t_data - x.dot(beta_hat))
    s2 = y_star.dot(y_star)
    #(X'*X)^(-1)*(X')*Y
    xtx_i = np.linalg.inv(x.T.dot(x))
    M = np.eye(K+1)/c

    c1 = np.linalg.inv(np.linalg.inv(M)+np.linalg.inv(x.T.dot(x)))
    c1 = (beta_tilde - beta_hat).T.dot(c1).dot(beta_tilde - beta_hat)
    c2 = np.linalg.inv(M+x.T.dot(x))

    exp_sig2 = (2*b + s2 + c1)/(N + 2*a - 2)
    exp_beta = c2.dot(x.T.dot(x).dot(beta_hat) + M.dot(beta_tilde))
    covar_beta = (exp_sig2)*c2
    var_beta = (np.eye(K+1)*(covar_beta)).dot(np.zeros(K+1)+1) #Get the variance (diagonal) elements

    log.sucess("MLE Regression complete")
    return exp_sig2, exp_beta, var_beta

def gPriorExpectations(x_data, t_data, beta_hat, beta_tilde, c, c0):
    '''
    Computes the expecation and variance along with the bayes factor for Zellner's
    informative g-prior
    @params:
        x_data (np.array): array of input data
        t_data (np.array): array of training data
        beta_hat (np.array): MLE linear regression weights
        beta_tilde (np.array): prior hyperparameter
        c (float): hyper-parameter
        c0 (float): model comparision hyper-parameter
    '''
    log.log("Computing exp, var and bayes factor for Zellner's informative G-prior")
    log.log("Hyper-parameters c:%.2f"%(c))

    (N,K) = x_data.shape
    x = np.zeros((N,K+1)) + 1
    q = 1
    x[:,1:] = x_data #Expanding with a colomn of ones for bias terms

    y_star = (t_data - x.dot(beta_hat))
    s2 = y_star.dot(y_star)
    #(X'*X)^(-1)*(X')*Y
    xtx = x.T.dot(x)
    yty = t_data.T.dot(t_data)

    exp_beta = 1.0/(c+1)*(beta_tilde + c*beta_hat)
    c1 = (s2 + (beta_tilde - beta_hat).T.dot(xtx).dot(beta_tilde-beta_hat)/(c+1))
    covar_beta = (c/(c+1.0))*(c1)/(N-2)*np.linalg.inv(xtx)
    var_beta = (np.eye(K+1)*(covar_beta)).dot(np.zeros(K+1)+1)

    b_coeff = ((c0+1)**((K+1-q)/2.0))/((c+1)**((K+1)/2.0))

    b_dem = yty - c/(c+1.0)*t_data.T.dot(x).dot(np.linalg.inv(xtx)).dot(x.T).dot(t_data) + \
             1/(c+1.0)*beta_tilde.T.dot(xtx).dot(beta_tilde) - 2/(c+1.0)*t_data.T.dot(x).dot(beta_tilde)

    #Comput the bayes factors for models excluding a specific input
    b_num = np.zeros(K+1)
    for i in range(K+1):
        x0 = np.delete(x, i, 1)
        beta_tilde0 = np.delete(beta_tilde, i, 0)
        xtx = x0.T.dot(x0)

        b_num[i] = yty - c0/(c0+1.0)*t_data.T.dot(x0).dot(np.linalg.inv(xtx)).dot(x0.T).dot(t_data) + \
            1/(c0+1.0)*beta_tilde0.T.dot(xtx).dot(beta_tilde0)-2/(c0+1.0)*t_data.T.dot(x0).dot(beta_tilde0)

    b_10 = b_coeff*(b_num/b_dem)**(0.5*N)

    return exp_beta, var_beta, b_10

def getHPD(x_data, t_data, beta_hat, beta_tilde, alpha):
    '''
    Get the HPD region
    @params:
        x_data (np.array): array of input data
        t_data (np.array): array of training data
        beta_hat (np.array): MLE linear regression weights
        beta_tilde (np.array): prior hyperparameter
        alpha (float): HPD range = (1-alpha)
    '''
    log.log("Computing HPD region")

    (N,K) = x_data.shape
    x = np.zeros((N,K+1)) + 1
    x[:,1:] = x_data #Expanding with a colomn of ones for bias terms

    xtx = x.T.dot(x)
    w = (np.eye(K+1)*np.linalg.inv(xtx)).dot(np.zeros(K+1)+1)

    y_star = (t_data - x.dot(beta_hat))
    s2 = y_star.dot(y_star)

    f = sc.stats.t.ppf(1-(alpha/2.0), N-K-1, loc=0, scale=1)
    hpd_interval = f*np.sqrt(w*s2/(N-K-1.0))

    hpd = np.zeros((K+1,2))
    hpd[:,0] = beta_hat[:] - hpd_interval[:]
    hpd[:,1] = beta_hat[:] + hpd_interval[:]

    return hpd

def zellnerNonInfoGPrior(x_data, y, beta_hat, beta_tilde):
    '''
    Computes the expectation and variance of Zellner's non-informative Gprior
    @params:
        x_data (np.array): array of input data
        t_data (np.array): array of training data
        beta_hat (np.array): MLE linear regression weights
        beta_tilde (np.array): prior hyperparameter
    '''
    log.log("Computing exp and var for Zellner's non-informative G-prior")

    clim = 101
    (N,K) = x_data.shape
    x = np.zeros((N,K+1)) + 1
    x[:,1:] = x_data #Expanding with a colomn of ones for bias terms
    #General commonly used numbers
    xtxi = np.linalg.inv(x.T.dot(x))
    yty = y.T.dot(y)
    c1 = y.T.dot(x).dot(xtxi).dot(x.T).dot(y)
    c2 = beta_hat.T.dot(x.T.dot(x)).dot(beta_hat)

    y_star = (y - x.dot(beta_hat))
    s2 = y_star.dot(y_star)

    #For non-informative we must marginalize over c
    exp_num = 0; exp_dem = 0;
    for c in range(1,clim):
        fxy0 = (1.0/c)*(c+1)**(-(K+1)/2.0)*(yty-(c*c1)/(c+1.0))**(-N/2.0)
        exp_num += c/(c+1.0)*fxy0
        exp_dem += fxy0
    exp_beta = (exp_num/exp_dem)*beta_hat

    var_num1 = 0; var_num2 = 0;
    for c in range(1,clim):
        fxyc0 = (c+1)**(-(K+1)/2.0)*(yty-(c*c1)/(c+1.0))**(-N/2.0)
        var_num1 += fxyc0/((N-2.0)*(c+1.0))*(s2+c2/(c+1.0))
        var_num2 += (c/(c+1.0) - (exp_num/exp_dem))**2 * fxyc0*(1.0/c)

    covar_beta = (var_num1/exp_dem)*(xtxi) + np.outer(beta_hat,(var_num2/exp_dem)*beta_hat.T)
    var_beta = (np.eye(K+1)*(covar_beta)).dot(np.zeros(K+1)+1)

    return exp_beta, var_beta


import numpy as np

from sympy import symbols, factorial, simplify, integrate, diff, exp
from sympy import cos, sin, sqrt, pi, I
from sympy import legendre
from sympy.abc import theta, phi, x

# with(orthopoly):

def intSpherik(f):
	"""
	Compute the integral of:
	.. math :: \int_0^{\pi}\int_0^{2\pi} f \mathrm{d}\phi \mathrm{d}\theta

	Parameters
	----------
	f

	Returns
	-------

	Notes
	-----
	theta, phi are sympy symbols
	"""
	# g = int(f, phi=0..2*pi);
	g = integrate(f, (phi, 0, 2*pi))
	# simplify(int_(g * sin(theta), theta=0..Pi))
	return simplify(integrate(g * sin(theta), (theta, 0, pi)))

def associatedLegendre(l: int, m: int):
	"""
	Compute the associated legendre as (with replacing x by cos(theta) in the derivative:

	.. math :: (-1)^m  \frac{1}{(2^l\cdot l!)} (\sin(\theta))^m
				\frac{\mathrm{d}^m P_n(x)}{\mathrm{d} x^m}\\
				x = \cos(\theta)

	Parameters
	----------
	l : int
	m : int

	Returns
	-------
	associated_legendre :

	Notes
	-----
	Result is simplify compared to the maple script

	x, theta are sympy symbols

	Raises
	------
	ArithemticError if m < 0

	"""
	# TODO: look into sympy.assoc_legendre it seems behave the same, and it is way faster
	if l == 0:
		return 1
	if m < 0:
		raise ArithmeticError(f"m should be non negative, m is {m}")

	associated_legendre = (-1) ** m * 1 / (2 ** l * factorial(l)) * (sin(theta)) ** m * \
	diff(((x**2 - 1)**l), x, l+m) .subs(x, cos(theta))
	# subs(x=cos(theta), diff((x ^ 2 - 1) ^ l, seq(x, i=1..(l + m))))
	# f.subs(x, cos(theta))

	return associated_legendre

def YY(l: int, m: int):
	"""

	Parameters
	----------
	l
	m

	Returns
	-------

	Notes
	-----
	x, theta are sympy symbols
	"""
	# TODO: check
	if l == 0 and m == 0:
		return 1 / (4 * np.pi)

	mm = abs(m)

	res = I ** (m + mm) * \
		simplify(sqrt((2*l + 1) * factorial(l - mm) / factorial(l + mm) / 4 / pi) * 1/2 ** l / factorial(l)) * \
		sin(theta) ** mm * exp(I * m * phi) * \
		diff(((x**2 - 1) ** l), x, l + mm).subs(x, cos(theta))
	# subs(x=cos(theta), diff( (x * 2 - 1) ** l, x$(l + mm)))
	# f.subs(x, cos(theta))

	return res

def SS(l: int, m: int):
	"""

	Parameters
	----------
	l
	m

	Returns
	-------

	Notes
	-----
	phi is a sympy symbol
	"""

	if m == 0:
		return sqrt((2 * l + 1) / (4 * pi)) * legendre(l, cos(theta))

	cte = sqrt((2 * + l + 1) * factorial(l - abs(m)) / factorial(l + abs(m)) / 2 / pi)

	if m > 0:
		# note : it was associatedLegendre(l, m, theta) in the maple code
		return cte * associatedLegendre(l, m) * cos(m * phi)
	return cte * associatedLegendre(l, -m) * sin(-m * phi)


'''
Linear Optimization assignment 1. Finding solution to a system of linear equation by Gaussian elimination method
'''
import numpy as np
import math

#To find column ith of matrix
#Used in printing the solution when the system is consistent
def column(matrix, i):
    return [row[i] for row in matrix]

# Helper function
def myfunction(x):
    if x < math.pow(10, -8):
        return 0

'''
Function which implements main algorithm of Gaussian Elimination to find the reduced row echelon form.
'''
def reducedRowEchelonForm(M):
    # Base case
    if not M:
        return

    # Find rows, columns
    no_rows = len(M)
    no_columns = len(M[0])
    front = 0

    #iterate through the rows
    for r in range(no_rows):
        if no_columns <= front:
            return
        i = r
        while M[i][front] == 0:
            i = i + 1
            if no_rows == i:
                i = r
                front = front + 1
                if no_columns == front:
                    return

        M[i],M[r] = M[r],M[i] #Swap rows i and r
        temp = M[r][front]

        if temp != 0:
            M[r] = [ mrtemp / float(temp) for mrtemp in M[r]]

        for i in range(no_rows):
            if i != r:
                temp = M[i][front] #subtract temp multiplied by row r from row i
                M[i] = [ itemp - temp*rtemp for itemp,rtemp in zip(M[i],M[r])]

        front = front + 1

#read input from file
f = open('input.txt', 'r')
mtx = [ map(int,line.split(',')) for line in f ]

#function call
reducedRowEchelonForm(mtx)

#print np.apply_along_axis(myfunction, axis=1, arr=mtx)
a = np.array(mtx);
b = a[:, :-1]
bound = 0.000000001

#print the reduced row echelon matrix
print "The RREF is:"
for rw in mtx:
  print ', '.join( (str(rv) for rv in rw) )

ans = 0
ans1 = 0
rows = (a != 0).sum(1)
rows1 = (b != 0).sum(1)

#print rows
#print rows1

#Loop to find rank of augmented matrix
for r in rows:
    if r == 0:
        ans = ans + 1

#Loop to find rank of original matrix
for r in rows1:
    if r == 0:
        ans1 = ans1 + 1

#print ans
#print ans1

ans = len(mtx) - ans
ans1 = len(mtx) - ans1

print "The rank of augmented matrix is", ans
print "The rank of original matrix is", ans1

if ans != ans1:
    print "The system has no solution"

check = len(mtx[0]) - 1
diff = check - ans

solution = column(a, check)

if ans < check:
    print "The system has infinite solutions, described by an affine subspace of dimension ",diff
'''Print solution
If the system is consistent then the last column of the RREF is the solution
'''
if ans == check:
    print "The system has a unique solution: "
    for s in solution:
        if abs(s) < bound:
            s = 0
        print s


import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
from matplotlib import cm
import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Variable
import torch
import os

# Implementing relu()
def relu(z):
    if z > 0:
        return z
    else:
        return 0

# Define linewidth and fontsize to control the aesthetics of the plots easily
linewidth = 4
fontsize = 20

# Define a range of values for the inputs of relu(z)
z_range = np.arange(-5,5, 0.01)

plt.figure(figsize=(16,9))
# For each z in x_range compute relu(z)
y_relu = [relu(z) for z in z_range]
plt.plot(z_range, y_relu, c='b', linewidth= linewidth, label='Relu(z)')
plt.ylim(-5, 5)
plt.xlim(-5, 5)
plt.grid()
plt.legend(fontsize=fontsize, loc=2)
plt.show()

def grad_relu(z):
    if z > 0:
        return 1
    else:
        return 0

### The gradients of relu
y_relu = [relu(z) for z in z_range]
grad_y_relu = [grad_relu(z) for z in z_range]

plt.figure(figsize=(16, 9))
# The relu
plt.subplot(1,2,1)
plt.plot(z_range, y_relu, c='b',linewidth= linewidth, label='Relu(z)')
plt.legend(fontsize=fontsize,loc=2)
plt.grid()

### The gradients of relu
plt.subplot(1,2,2)
plt.plot(z_range, grad_y_relu, c='r',linewidth= linewidth, label='d Relu(z)/dz')
plt.legend(fontsize=fontsize,loc=2)
plt.grid()
plt.show()

# Demonstrating the flexibility of relu: relu(z),relu(-z),-relu(z),-relu(-z)
z_range = np.arange(-5,5, 0.01)

plt.figure(figsize=(16,9))
plt.suptitle('The Flexibility of Relu(z)', fontsize=fontsize)

plt.subplot(2,2,1)
y_relu = [relu(z) for z in z_range]
plt.plot(z_range, y_relu, c='b', linewidth= linewidth, label='Relu(z)')
plt.ylim(-5,5)
plt.xlim(-5,5)
plt.grid()
plt.legend(fontsize=fontsize, loc=2)

plt.subplot(2,2,2)
y_relu = [relu(-z) for z in z_range]
plt.plot(z_range, y_relu, c='k', linewidth= linewidth,label='Relu(-z)')
plt.ylim(-5,5)
plt.xlim(-5,5)
plt.legend(fontsize=fontsize,loc=1)
plt.grid()

plt.subplot(2,2,3)
y_relu = [-relu(z) for z in z_range]
plt.plot(z_range, y_relu, c='r', linewidth= linewidth,label='-Relu(z)')
plt.ylim(-5,5)
plt.xlim(-5,5)
plt.legend(fontsize=fontsize,loc=2)
plt.grid()

plt.subplot(2,2,4)
y_relu = [-relu(-z) for z in z_range]
plt.plot(z_range, y_relu, c='g', linewidth= linewidth,label='-Relu(-z)')
plt.ylim(-5,5)
plt.xlim(-5,5)
plt.legend(fontsize=fontsize,loc=1)
plt.grid()

plt.show()

# The rotation of the slope in relu
w_range = np.arange(0.5, 3.5, 0.5)
plt.figure(figsize=(16, 9))
plt.suptitle('Changing the slope of Relu(w*z) using a coefficient w', fontsize=fontsize)
for idx, w in enumerate(w_range):
    plt.subplot(2,3,idx+1)
    y_relu = [relu(w*z) for z in z_range]
    plt.plot(z_range, y_relu, c='b', linewidth=linewidth, label='w = %.2f' % w)
    plt.ylim(-1, 5)
    plt.xlim(-5, 5)
    plt.grid()
    plt.legend(fontsize=fontsize, loc=2)
plt.show()

# Shifting the relu horizontally
bias = np.arange(0.5, 3.5, 0.5)
plt.figure(figsize=(16, 9))
plt.suptitle('Shifting Relu(z+b) horizontally using a bias term b inside Relu()', fontsize=fontsize)
for idx, b in enumerate(bias):
    plt.subplot(2,3, idx+1)
    y_relu = [relu(z+b) for z in z_range]
    plt.plot(z_range, y_relu, c='b', linewidth=linewidth, label='b = %.2f' % b)
    plt.ylim(-1, 5)
    plt.xlim(-4, 4)
    plt.grid()
    plt.legend(fontsize=fontsize, loc=2)
plt.show()

# Shifting the relu vertically
bias = np.arange(0.5, 3.5, 0.5)
plt.figure(figsize=(16, 9))
plt.suptitle('Shifting Relu(z) + b vertically using a bias term b outside Relu()', fontsize=fontsize)
for idx, b in enumerate(bias):
    plt.subplot(2,3, idx+1)
    y_relu = [relu(z)+b for z in z_range]
    plt.plot(z_range, y_relu, c='b', linewidth=linewidth, label='b = %.2f' % b)
    plt.ylim(-1, 5)
    plt.xlim(-4, 4)
    plt.grid()
    plt.legend(fontsize=fontsize, loc=2)
plt.show()

# Defining the data and the ground-truth
x = torch.unsqueeze(torch.linspace(-10, 10, 300), dim=1)
y = x.pow(3)

# Setting the available device
use_gpu = torch.cuda.is_available()
device = torch.device("cuda" if use_gpu else "cpu")
print("Device", device)

# Build a regression model clas
class Regressor(nn.Module):
    def __init__(self, n_hidden=2):
        super(Regressor, self).__init__()
        self.hidden = torch.nn.Linear(1, n_hidden)  # hidden layer
        self.predict = torch.nn.Linear(n_hidden, 1)  # output layer

    def forward(self, x):
        x = F.relu(self.hidden(x))
        x = self.predict(x)
        return x

# number of relu() units
n_hidden = 7
# total number of epochs
n_epochs = 4000
# Building an object from the regressor class while  passing
# n_hidden and setting the model to train() mode
regressor = Regressor(n_hidden=n_hidden).train()
# Defining the optimizer
optimizer = torch.optim.SGD(regressor.parameters(), lr=0.0001)
# Defining MSE as the appropriate los function
# For regression.
loss_func = torch.nn.MSELoss()

plt.figure(figsize=(16, 9))
for epoch in range(n_epochs):
    # Put the model in training mode
    regressor.train()
    # This is there to clear the previous plot in the animation
    # After each epoch
    plt.clf()
    # input x to the regressor and receive the predicion
    y_hat = regressor(x)
    # Compute the loss between y_hat and the actual
    # Value of the ground-truth curve, y
    loss = loss_func(y_hat, y)
    # Compute the gradients w.r.t all the parameters
    loss.backward()
    # Update the parameters
    optimizer.step()
    # Zero out all the gradients before inputing the next data point
    # Into the regressor model
    optimizer.zero_grad()

    # Every 100 epoch evaluate do some plotting
    if epoch % 100 == 0:
        print('Epoch %d --- Loss %.5f' % (epoch+1, loss.data.numpy()))
        # Bbefore evaluation, put the model back to evaluation mode
        regressor.eval()
        # At this very moment of training, grab the current biases and weights
        # From the model object, namely, b_0, b_1, w_0, and w_1
        biases_0 = regressor.hidden.bias.cpu().detach().numpy()
        weights_0 = regressor.hidden.weight.squeeze(0).cpu().detach().numpy()
        biases_1 = regressor.predict.bias.cpu().detach().numpy() # This has ONLY 1 value
        weights_1 = regressor.predict.weight.squeeze(0).cpu().detach().numpy()

        # For the purpose of plotting consider the current range of
        # x as the inputs to EACH relu() individualy
        data = x.detach().numpy()
        # This will hold the UNLIMATE
        # prediction, that is, relu(input*w_0+b_0)*w_1 + b_1
        # We reset it before plotting the current status of the model
        # And the learned relu() functions
        sum_y_relu = []
        # For each relu() unit do the following
        for idx in range(n_hidden):

            plt.suptitle('Epoch=%d --- MSE loss= %.2f' % (epoch+1, loss.data.numpy()), fontsize=fontsize)
            # Plot output of the current relu() unit
            plt.subplot(1,3,1)
            plt.title('Relu(w_0*x + b_0)', fontsize=fontsize)
            y_relu = [relu(d*weights_0[idx]+biases_0[idx]) for d in data]
            plt.plot(data, y_relu)
            plt.ylim(-1,40)
            plt.grid()

            plt.subplot(1, 3, 2)
            # Plot output of the current relu(), multiplied by its
            # corresponding weight, w_1, and summed with the bias b_1
            plt.title('Relu(w_0*x + b_0)*w_1 + b_1',fontsize=fontsize)
            y_relu = [relu(d*weights_0[idx]+biases_0[idx])*weights_1[idx] + biases_1[0] for d in data]
            plt.plot(data,y_relu)
            plt.ylim(-500,900)
            plt.grid()

            # Kee adding the Relu(w_0*x + b_0)*w_1 + b_1 for each relu to the
            # sum_y_relu list. We will sum them up later to plot
            # The ULTIMATE predction of the model y_hat
            sum_y_relu.append([relu(d*weights_0[idx]+biases_0[idx])*weights_1[idx] + biases_1[0] for d in data])

        # Sum it all up
        sum_y_relu = np.sum(np.array(sum_y_relu),axis=0)
        plt.subplot(1, 3, 3)
        plt.title('y_hat)', fontsize=fontsize)
        plt.plot(x.data.numpy(), y.data.numpy(), color="k", label='Ground-truth')
        plt.plot(data,sum_y_relu, c='r', label='Prediction')
        plt.legend()
        plt.grid()

        # A slight delay in the animation
        plt.pause(0.1)


# PROJECT: Softmax Function
# PROGRAMMER: Carlos Mertens - Udacity Student
#				Udacity Instructors (Machine Learning Engineer Nanodegree)
# DATE CREATED: (DD/MM/YY) 10/01/2019
# REVISED DATE: (DD/MM/YY)
# PURPOSE: Udacity instructors show the algorithm to implement Softmax Function
#			From the scratch.
# USAGE: ...

import numpy as np

# Write a function that takes as input a list of numbers, and returns
# the list of values given by the softmax function.
def my_softmax(L):
    exp_list = np.exp(L)
    sum_exp_list = sum(exp_list)
    result = []
    for i in exp_list:
        result.append(i * 1.0 / sum_exp_list)
    return result

my_list = [5,6,7]

# Call function to get the softmax from my_list
my_list_softmax = my_softmax(my_list)

print(my_list_softmax)


import math as m
import numpy as np

from src.discrete.bernoulli import BernDist
from src.prob_distribution import ProbDist
from src.spaces.spaces1d_leafs import DiscreteSpace

class BinDist(ProbDist):
    """Simple binomial distribution."""

    def __init__(self, n = 1, p = 0.5):
        """Create Bin(n, p) distribution.

        :param n Which should be summed
        :param p Probability that random var is true
        """

        # save params
        assert 0 <= p <= 1
        assert 1 <= n
        self.p = p
        self.n = n

        # create distribution for sampling
        self.BG = BernDist(p)
        super().__init__(DiscreteSpace(np.arange(n + 1)))

    def expectation(self):
        """Calculates the expectations for that distribution.

        :returns The expectation of the distribution"""

        return self.n * self.p

    def var(self):
        """Calculates the variance for that distribution.

        :returns The variance of the distribution"""

        return self.n * self.p * (1 - self.p)

    def sample(self, num_samples = 1):
        """Generate random numbers from Bin(n, p).

        :param num_samples How many random numbers should be generated.
        :returns Random numbers x ~ Bin(n, p).
        """

        X = np.zeros(num_samples)
        for _ in range(self.n):
            X += self.BG.sample(num_samples)

        return X

    def __density(self, x):
        """This method calculates the mass Bin(n, p).

        :param x Which value should be evaluated.
        :returns The probability this element occurs.
        """

        assert all([e >= 0 for e in x])
        prev = m.factorial(self.n) / (m.factorial(x) * m.factorial(self.n - x))
        return prev * np.power(self.p, x) * np.power(1 - self.p, np.subtract(self.n, x))


# This contains code that enables decibel math

import numpy as np

"""
summation = add_decibels(dB, coherent = False)

Take multiple decibel values and add them together. Incoherence is assumed but can be switched to coherence.

Input:
    dB = list or numpy array of decibel values
    coherent = boolean, whether the decibel values are coherent with each other

Output:
    summation = the sum total of the decibel values in the input
"""

def add_decibels(dB,coherent = False):

    dB = np.asarray(dB)

    if coherent:
        summation = 20 * np.log10(sum(10**(dB/20)))
    else:
        summation = 10 * np.log10(sum(10**(dB/10)))

    return summation

def dB_to_pressure(dB,reference,squared = True):

    dB = np.asarray(dB)

    if squared:
        value = reference * 10**(dB/20)
    else:
        value = reference * 10**(dB/10)

    return value


import numpy as np

def numerical_diff(fn, x):
    """ Numerical Differential
    함수 fn과 점 x가 주어졌을 때, x에서의 함수 fn의 미분(도함수) 값"""
    h = 1e-4  # 0.0001
    return (fn(x + h) - fn(x - h)) / (2 * h)

def f1(x):
    return 0.001 * x **2 + 0.01 * x

def f1_prime(x):
    """근사값을 사용하지 않은 함수 f1의 도함수"""
    return 0.002 * x + 0.01

def f2(x):
    """x = [x0, x1, ... ]"""
    return np.sum(x**2)  # x0**2 + x1**2 + ...

def partial_gradient(fn, x):
    """ndarray x = [x0, x1, ..., xn]에서의 함수 fn = fn(x0, x1, ..., xn)의
    각 편미분 값들의 배열을 리턴"""
    x = x.astype(np.float)  # 실수 타입으로 변경
    # gradient: np.zeros(shape=x.shape) >> 원소 n개의 0으로 된 array 생성
    gradient = np.zeros_like(x)
    h = 1e-4  # 0.0001
    for i in range(x.size):
        ith_val=x[i]
        x[i] = ith_val+h
        fh1 = fn(x)
        x[i] = ith_val-h
        fh2 = fn(x)
        gradient[i] = (fh1 - fh2) / (2*h)
    return gradient

def f3(x):
    return x[0] + (x[1] ** 2) + (x[2] ** 3)

def f4(x):
    return x[0]**2 + 2*x[0]*x[1] + x[1]*2

if __name__ == '__main__':
    estimate = numerical_diff(f1, 3)
    print('근사값:', estimate)
    real = f1_prime(3)
    print('실제값:', real)

    # f2 함수의 점(3, 4)에서의 편미분 값
    estimate_1 = numerical_diff(lambda x: x**2 + 4**2, 3)
    print(estimate_1)
    estimate_2 = numerical_diff(lambda x: 3**2 + x**2, 4)
    print(estimate_2)

    # f2 함수의 점(3, 4)에서의 편미분 값
    gradient = partial_gradient(f2, np.array([3, 4]))
    print(gradient)

    # f3 = x0 + x1 ** 2 + x2 ** 3
    # 점 (1, 1, 1) 에서의 각 편미분들의 값
    # df/dx0 = 1, df/dx1 = 2, df/dx3 = 3
    gradient2 = partial_gradient(f3, np.array([1, 1, 1]))
    print(gradient2)

    # f4 = x0**2 + 2 * x0 * x1 + x1**2
    # 점 (1, 2)에서의 df/dx0 = ?, df/dx1 =?
    gradient3 = partial_gradient(f4, np.array([1, 2]))
    print(gradient3)


#!/usr/bin/env python
'''This module contains routines to perform Gram-Schmidt orthonormalization on
a sequence of vectors.

'''
import numpy as np
import numpy.linalg as la

def gso(A, overwrite=False, out=None):
    '''Performs Gram-Schmidt orthonormalization on a sequence of vectors.

    Parameters
    ----------
    A : ndarray
        (M x N) ndarray with M <= N. The rows of A contain the sequence of
        vectors.
    overwrite : bool, optional
        If `True`, the matrix A is overwritten.
    out : ndarray, optional
        (M x N) ndarray with M <= N. The rows of `out` contain the sequence of
        orthonormal vectors. If `overwrite = True`, `out` is neglected.

    Returns
    -------
    output : ndarray
        (M x N) ndarray with M <= N. The rows of `out` contain the sequence of
        orthonormal vectors.

    Notes
    -----
    See Golub and Van Loan, Matrix Computations, 3rd edition, Section 5.2.8,
    Algorithm 5.2.5, p. 231.

    '''
    assert A.shape[0] <= A.shape[1]
    M = A.shape[0]
    if overwrite:
        output = A
    else:
        if out is not None:
            output = out
        else:
            output = np.zeros_like(A)
            output[:,:] = A
    for i in range(M):
        output[i,:] = output[i,:]/la.norm(output[i,:])
        for j in range(i+1, M):
            output[j,:] = output[j,:] - np.dot(output[j,:], output[i,:])*output[i,:]
    return output

if __name__ == '__main__':
    A = np.random.random((6,6))
    print('A')
    print(A)
    out = gso(A)
    print('\n')
    print(out)
    print('\n')
    print(np.dot(out.T, out))
    for i in range(A.shape[0]):
        for j in range(A.shape[0]):
            print(i, j, np.dot(out[i,:], out[j,:]))
        print('\n')


from scipy import stats
import numpy as np

### Print Normal Random Variables
print('\nNomal Random Variables:\n',stats.norm.rvs(size = 10))

import matplotlib.pyplot as plt

# Create some test data
dx = .01
X  = np.arange(-2,2,dx)
Y  = np.exp(-X**2)

# Normalize the data to a proper PDF
Y /= (dx*Y).sum()

# Compute the CDF
CY = np.cumsum(Y*dx)

# Plot both
plt.plot(X,Y)
plt.plot(X,CY,'r--')

plt.show()

### Compute the Normal CDF of certain values.
print('\nThe Normal CDF of certain Values:\n',stats.norm.cdf(np.array([1,-1., 0, 1, 3, 4, -2, 6])))

##############################################################
#                 Descriptive Statistics                     #
##############################################################

np.random.seed(282629734)

# Generate 1000 Student’s T continuous random variables.
x = stats.t.rvs(10, size=1000)

# Do some descriptive statistics
print('\nDescriptive Statistics')
print('\nx.min() equivalent `np.min(x)`\n',x.min())   # equivalent to np.min(x)

print('\nx.max() equivalent `np.max(x)`\n',x.max())   # equivalent to np.max(x)

print('\nx.mean() equivalent `np.mean(x)`\n',x.mean())  # equivalent to np.mean(x)

print('\nx.var() equivalent `np.var(x)`\n',x.var())   # equivalent to np.var(x))

print('\nOverall Statistics Parameters compare whit above dta:\n',stats.describe(x))

#########################################
# Later in the course, we will discuss  #
# distributions and statistical tests   #
# such as a T-Test. SciPy has built in  #
# functions for these operations.       #
#########################################

Understanding_and_Visualizing_Data_With_Python/Univariate Data/Python for Analysis of Univariate Data/Scipy.py


#Find the norm of the vector [3, 9, 5, 4] using the actual formula above. You should write a function find_norm(v1) that returns this value as a float and then call it on the provided variable n1. You should not use scipy, but you may use the math module.

import math
import numpy as np
A = [3, 9, 5, 4]
def find_norm(v1):
    list_ = [i**2 for i in v1]
    return math.sqrt(sum(list_))

find_norm(A)


import numpy as np
import math as m
import matplotlib.pyplot as plt

# P(Z = 1) = sum(1,n) of P(Z = 1|Y = i)P(Y = i) = k/n * (sum(k+1,n) of (1/(i-1))) ~ (k/n) * ln(n/k)

# Fix n = 30 and simulate P(Z = 1)
N1 = 30
K1 = [k for k in range(1, N1+1)]
Y1 = [k / N1 * m.log(N1 / k) for k in K1]
plt.plot(K1, Y1, color="blue", label="P(Z = 1| N = 30)")

# Fix n = 40 and simulate P(Z = 1)
N2 = 40
K2 = [k for k in range(1, N2+1)]
Y2 = [k / N2 * m.log(N2 / k) for k in K2]
plt.plot(K2, Y2, color="red", label="P(Z = 1| N = 40)")

# Interpretation: The probability of selecting the best secretary, when n is fixed to 30, rises as one approaches
# k ~ 11 and decreases as one approaches k = 30. The tendency is analogous when n is fixed to 40, but this time the
# maximum probability is reached when k ~ 15.

#Bruk print-f-funksjon for å skrive ut max y-verdi i begge tilfeller.
plt.grid(True)
plt.ylabel("P(Z = 1)");plt.xlabel("k"); plt.title("Probability of Choosing the Best Candidate, Given k")
#plt.savefig('p1p1f1.png', transparent=True, bbox_inches='tight',)

print(Y1.index(max(Y1)))
print(max(Y1))
print(Y2.index(max(Y2)))
print(max(Y2))
plt.show()


import math
from typing import List, Optional, Tuple

import numpy as np

def leggauss_2d(order: int) -> Optional[Tuple[np.ndarray, np.ndarray]]:
    """Generate Gauss-Legendre points and weights of the given order on the reference square [-1,1].
    """
    if math.floor(math.sqrt(order))**2 != order:
        print('order has to be a square')
        return None
    points_1d, weights_1d = np.polynomial.legendre.leggauss(int(math.sqrt(order)))
    xv, yv = np.meshgrid(points_1d, points_1d)
    points = np.stack((xv, yv), axis=2).reshape(order, 2)
    weights = np.outer(weights_1d, weights_1d).flatten()
    return points, weights

def ref_to_element_1d(x1: float, x2: float, num_points: int) -> Tuple[np.ndarray, np.ndarray]:
    ref_points, ref_weights = np.polynomial.legendre.leggauss(num_points)
    points: np.ndarray = ref_points*0.5*abs(x2 - x1) + 0.5*(x2 + x1)
    weights: np.ndarray = ref_weights*0.5*(x2 - x1)
    return points, weights

def ref_to_element_2d(x1: float, x2: float, y1: float, y2: float, num_points: int) -> Tuple[np.ndarray, np.ndarray]:
    ref_element: Optional[Tuple[np.ndarray, np.ndarray]] = leggauss_2d(num_points)
    assert ref_element is not None
    ref_points, ref_weights = ref_element
    points: np.ndarray = np.array([[x*(x2 - x1)*0.5 + (x1 + x2)*0.5, y*(y2 - y1)*0.5 + (y1 + y2)*0.5] for x, y in ref_points])
    weights: np.ndarray = ref_weights*(x2 - x1)*(y2 - y1)*0.25
    return points, weights

def generate_points_weights(elements: List[List[float]], num_points: int, dim: int) -> Tuple[np.ndarray, np.ndarray]:
    points_list: List[np.ndarray] = []
    weights_list: List[np.ndarray] = []
    for element in elements:
        if dim == 1:
            points, weights = ref_to_element_1d(*element, num_points)
        elif dim == 2:
            points, weights = ref_to_element_2d(*element, num_points)
        points_list.append(points)
        weights_list.append(weights)
    return np.concatenate(points_list), np.concatenate(weights_list)

def quadtree(x_start, x_end, y_start, y_end, elements, nodes) -> None:
    pass


#!/usr/bin/env python
# coding: utf-8

# # Dina's table seating arrangements

# In[1]:

# Install Pyomo and solvers for Google Colab
import sys
if "google.colab" in sys.modules:
    get_ipython().system('wget -N -q https://raw.githubusercontent.com/jckantor/MO-book/main/tools/install_on_colab.py ')
    get_ipython().run_line_magic('run', 'install_on_colab.py')

# ## Problem
#
# Caroline takes her associates and their families out to a dinner at Dina’s restaurant to celebrate the successes brought to her material planning by Mathematical Optimization.
#
#
# Caroline puts some additional requirements: to increase social interaction, the different families should sit at tables so that no more than $k$ members of the same family are seated at the same table.
#
# Dina has the following data, plus the desired threshold of at most $k$ family members per table:
#
#  * Family $f$ has $m(f)$ members.
#  * At the restaurant, there are multiple tables, where table $t$ has capacity $c(t)$.
#
# Model this problem in order to find a seating arrangement that satisfies Caroline's requirement.

# # Resolution

# In[2]:

import pyomo.environ as pyo
from IPython.display import display
import networkx as nx
import matplotlib.pyplot as plt
import pandas as pd

# In[3]:

solver = pyo.SolverFactory('cbc')

# In[4]:

# %%writefile tableseat_1.py
def TableSeat( members, capacity, k, domain=pyo.NonNegativeReals ):
    m   = pyo.ConcreteModel("Dina's seat plan")
    m.F = pyo.Set( initialize=range( len(members) ) )
    m.T = pyo.Set( initialize=range( len(capacity) ) )
    m.M = pyo.Param( m.F, initialize=members )
    m.C = pyo.Param( m.T, initialize=capacity )
    m.x = pyo.Var( m.F, m.T, bounds=(0,k), domain=domain )

    @m.Objective( sense=pyo.maximize )
    def goal(m):
        return 0

    @m.Constraint( m.T )
    def capacity( m, t ):
        return pyo.quicksum( m.x[f,t] for f in m.F  ) <= m.C[t]

    @m.Constraint( m.F )
    def seat( m, f ):
        return pyo.quicksum( m.x[f,t] for t in m.T ) == m.M[f]

    return m

# In[5]:

def TableSeatAsMaxFlow( members, capacity, k, domain=pyo.NonNegativeReals ):
    m   = pyo.ConcreteModel("Dina's seat plan")
    m.F = pyo.Set( initialize=range( len(members) ) )
    m.T = pyo.Set( initialize=range( len(capacity) ) )
    m.M = pyo.Param( m.F, initialize=members )
    m.C = pyo.Param( m.T, initialize=capacity )
    m.x = pyo.Var( m.F, m.T, bounds=(0,k), domain=domain )

    @m.Objective( sense=pyo.maximize )
    def goal(m):
        return pyo.quicksum( m.x[f,t] for t in m.T for f in m.F )

    @m.Constraint( m.T )
    def capacity( m, t ):
        return pyo.quicksum( m.x[f,t] for f in m.F  ) <= m.C[t]

    @m.Constraint( m.F )
    def seat( m, f ):
        return pyo.quicksum( m.x[f,t] for t in m.T ) <= m.M[f]

    return m

# In[6]:

def Reset( model ) -> None:
    for v in model.component_data_objects(ctype=pyo.Var, descend_into=True):
        v.set_value(None)

def GetSolution( model ):
    import pandas as pd
    sol = pd.DataFrame()
    for idx,x in model.x.items():
        sol.loc[idx]=x()
    return sol

def Report( model, results, type=float ):
    print(results.solver.status, results.solver.termination_condition )
    if results.solver.termination_condition == 'optimal':
        sol = GetSolution(model).astype(type)
        display( sol )
        print('objective       ', pyo.value( seatplan.goal ) )
        print('places at table ', list(sol.sum(axis=0)))
        print('members seated  ', list(sol.sum(axis=1)))

# In[7]:

seatplan = TableSeat( [6,8,2,9,13,1], [8,8,10,4,9], 3 )

get_ipython().run_line_magic('time', 'results = solver.solve(seatplan)')

Report( seatplan,results )

# In[8]:

import pyperclip
pyperclip.copy( GetSolution(seatplan).astype(int).style.to_latex() )

# In[ ]:

def TableSeatMinimizeMaxGroupAtTable( members, capacity, nature=pyo.NonNegativeReals ):
    m   = pyo.ConcreteModel("Dina's seat plan")
    m.F = pyo.Set( initialize=range( len(members) ) )
    m.T = pyo.Set( initialize=range( len(capacity) ) )
    m.M = pyo.Param( m.F, initialize=members )
    m.C = pyo.Param( m.T, initialize=capacity )
    m.x = pyo.Var( m.F, m.T, domain=nature )
    m.k = pyo.Var( domain=nature )

    @m.Objective( sense=pyo.minimize )
    def goal(m):
        return m.k

    @m.Constraint( m.T )
    def capacity( m, t ):
        return pyo.quicksum( m.x[f,t] for f in m.F  ) <= m.C[t]

    @m.Constraint( m.F )
    def seat( m, f ):
        return pyo.quicksum( m.x[f,t] for t in m.T ) == m.M[f]

    @m.Constraint( m.F, m.T )
    def bound( m, f, t ):
        return m.x[f,t] <= m.k

    return m

# In[ ]:

seatplan = TableSeatMinimizeMaxGroupAtTable( [6,8,2,9,13,1], [8,8,10,4,9], nature=pyo.NonNegativeReals )

get_ipython().run_line_magic('time', 'results = solver.solve(seatplan)')

Report( seatplan, results )

# In[ ]:

pyperclip.copy( GetSolution(seatplan).astype(float).style.format(precision=2).to_latex() )

# In[ ]:

def TableSeatMinimizeNumberOfTables( members, capacity, k, nature=pyo.NonNegativeReals ):
    m   = pyo.ConcreteModel("Dina's seat plan")
    m.F = pyo.Set( initialize=range( len(members) ) )
    m.T = pyo.Set( initialize=range( len(capacity) ) )
    m.M = pyo.Param( m.F, initialize=members )
    m.C = pyo.Param( m.T, initialize=capacity )
    m.x = pyo.Var( m.F, m.T, bounds=(0,k), domain=nature )
    m.y = pyo.Var( m.T, within=pyo.Binary )

    @m.Objective( sense=pyo.minimize )
    def goal(m):
        return pyo.quicksum(m.y[t] for t in m.T)

    @m.Constraint( m.T )
    def capacity( m, t ):
        return pyo.quicksum( m.x[f,t] for f in m.F  ) <= m.C[t]*m.y[t]

    @m.Constraint( m.F )
    def seat( m, f ):
        return pyo.quicksum( m.x[f,t] for t in m.T ) == m.M[f]

    return m

# In[ ]:

seatplan = TableSeatMinimizeNumberOfTables( [6,8,2,9,13,1], [8,8,10,4,9], 3, pyo.NonNegativeIntegers )

get_ipython().run_line_magic('time', 'results = solver.solve(seatplan)')

Report( seatplan, results, int )

# # Note: this is an example of a max flow!

# In[9]:

get_ipython().run_line_magic('matplotlib', 'inline')

# https://stackoverflow.com/questions/17687213/how-to-obtain-the-same-font-style-size-etc-in-matplotlib-output-as-in-latex
params = {'text.usetex' : True,
          'font.size'   : 10, # the book seems to be in 10pt, change if needed
          'font.family' : 'lmodern',
          }

plt.rcParams.update(params)
default_size_inches = (3.54,3.54)
plt.rcParams['figure.figsize'] = default_size_inches

# In[10]:

def ModelAsNetwork( members, capacity, k ):
    families = [f'f{i}' for i in range(len(members))]
    tables = [f't{j}' for j in range(len(capacity))]
    G = nx.DiGraph()
    G.add_node('door',layer=0)
    for f in families:
        G.add_node(f,layer=1)
    for t in tables:
        G.add_node(t,layer=2)
    G.add_node('seat',layer=3)
    for f,n in zip(families,members):
        G.add_edge('door', f, capacity=n)
    for f in families:
        for t in tables:
            G.add_edge(f,t, capacity=k)
    for t,n in zip(tables,capacity):
        G.add_edge(t, 'seat', capacity=n)
    return G

# In[11]:

G = ModelAsNetwork( [6,8,2,9,13,1], [8,8,10,4,9], 3 )

# In[12]:

pos = nx.multipartite_layout(G, subset_key='layer')

# In[13]:

labels = { (e[0],e[1]) : e[2] for e in G.edges(data='capacity') }

# In[14]:

plt.rcParams['text.usetex'] =False
with plt.xkcd():
    fig = plt.figure(figsize=(13,8))
    ax = fig.add_subplot(111)
    nx.draw_networkx(G,pos=pos,ax=ax,node_size=800,with_labels=True,alpha=.6)
    _=nx.draw_networkx_edge_labels(G,pos=pos,ax=ax,edge_labels=labels,font_color='black',rotate=False,alpha=1)
    fig.savefig( 'net_flow.pdf', bbox_inches='tight', pad_inches=0 )

# In[15]:

get_ipython().run_line_magic('time', "flow_value, flow_dict = nx.maximum_flow(G, 'door', 'seat')")

# In[16]:

members, capacity = [6,8,2,9,13,1], [8,8,10,4,9]
families = [f'f{i}' for i in range(len(members))]
tables = [f't{j}' for j in range(len(capacity))]
pd.DataFrame(flow_dict).loc[tables,families]

# In[17]:

flow_edges = [(a,b) for a,B in flow_dict.items() for b,v in B.items() if v>0 and a != 'door' and b != 'seat']
flow_nodes = [n for n in G.nodes if n.startswith('f') or n.startswith('t')]

# In[18]:

with plt.xkcd():
    fig = plt.figure(figsize=(8,5))
    nx.draw_networkx(G,ax=fig.add_subplot(111),pos=pos,node_size=300,edge_color='blue',edgelist=flow_edges,nodelist=flow_nodes)

# In[19]:

fig.savefig( 'flow.pdf', bbox_inches='tight', pad_inches=0 )

# In[20]:

cbc    = pyo.SolverFactory('cbc')
gurobi = pyo.SolverFactory('gurobi_direct')

# In[21]:

from pathlib import Path
if Path('dina_times.xlsx').is_file():
    df = pd.read_excel('dina_times.xlsx').set_index('Unnamed: 0')
else:
    from tqdm.notebook import tqdm
    from time import perf_counter as pc
    import numpy as np
    np.random.seed(2022)
    k = 3
    nmax = 500
    mmax = 2*nmax
    sizes = list(zip(range(10,nmax,10),range(20,mmax,20)))
    df = pd.DataFrame(index=['cbc','gurobi','nx'],columns=sizes)
    for n,m in tqdm(sizes):
        members, capacity = np.random.randint(1,10,n), np.random.randint(3,8,m)
        model = TableSeatAsMaxFlow(members,capacity,k)
        t=pc()
        cbc.solve(model)
        df.loc['cbc'][(n,m)] = pc()-t
        Reset(model)
        t=pc()
        gurobi.solve(model)
        df.loc['gurobi'][(n,m)] = pc()-t
        G = ModelAsNetwork(members,capacity,k)
        t = pc()
        nx.maximum_flow(G, 'door', 'seat')
        df.loc['nx'][(n,m)] = pc()-t

    df.to_excel('dina_times.xlsx')

# In[22]:

aux = df.T

# In[23]:

df

# In[24]:

aux

# In[25]:

import numpy as np
plt.rcParams['text.usetex'] =True
fig = plt.figure(figsize=(13,5))
ax=fig.add_subplot(111)
aux.plot(ax=ax)
plt.xticks(np.arange(len(df.columns)),df.columns,rotation = 45)
plt.show()

# In[26]:

fig.savefig( 'dina_times.pdf', bbox_inches='tight', pad_inches=0 )

# In[ ]:

# In[ ]:

import numpy,import networkx,import pyomo


# -*- coding: utf-8 -*-
"""
References
  numpy.matmul¶
    https://docs.scipy.org/doc/numpy-1.14.0/reference/generated/numpy.matmul.html
"""

import numpy as np

# Examples

# For 2-D arrays it is the matrix product:
a = [[1,0], [0,1]]
b = [[4,1], [2,2]]
np.matmul(a,b)

# For 2-D mixed with 1-D, the result is the usual.
a = [[1,0],[0,1]]
b = [1,2]
np.matmul(a,b)
np.matmul(b,a)

# Broadcasting is conventional for stacks of arrays

a = np.arange(2*2*4).reshape((2,2,4))
"""
np.arange(2*2*4)
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15])
np.arange(2*2*4).reshape((2,2,4))
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7]],

       [[ 8,  9, 10, 11],
        [12, 13, 14, 15]]])
"""

b = np.arange(2*2*4).reshape((2,4,2))
"""
array([[[ 0,  1],
        [ 2,  3],
        [ 4,  5],
        [ 6,  7]],

       [[ 8,  9],
        [10, 11],
        [12, 13],
        [14, 15]]])
"""

# 2x2x4 X 2x4x2
np.matmul(a,b).shape
"""(2, 2, 2)"""

np.matmul(a,b)
"""
array([[[ 28,  34],
        [ 76,  98]],

       [[428, 466],
        [604, 658]]])
"""

np.matmul(a,b)[0,1,1]
"""98"""
"""
a[0,1,:] = array([4, 5, 6, 7])
b[0,:,1] = array([1, 3, 5, 7])
* is a piece-wise multiplication?
a[0,1,:] * b[0,:,1]
array([ 4, 15, 30, 49])
"""

sum( a[0,1,:] * b[0,:,1])
"""98"""

# Vector, vector returns the scalar inner product, but neither argument is complex-conjugated:
np.matmul([2j,3j], [2j,3j])

''' (1x2) X (1x2) I don't understand this part.
'''
a = [2j,3j]
b = [2j,3j]
np.matmul(a,b)
'''(-13+0j)'''

a = [[2j,3j]]
b = [[2j,3j]]
np.matmul(a,b)
# ValueError: shapes (1,2) and (1,2) not aligned: 2 (dim 1) != 1 (dim 0)

np.matmul([1,2], 3)
'''
Traceback (most recent call last):

  File "<ipython-input-38-a054dc69a9a6>", line 1, in <module>
    np.matmul([1,2], 3)

ValueError: Scalar operands are not allowed, use '*' instead
'''


########## 6.8. Métricas de pares, afinidades e kernels ##########

    # O submódulo sklearn.metrics.pairwise implementa utilitários para avaliar distâncias de pares ou afinidade de conjuntos de amostras.

    # Este módulo contém métricas de distância e kernels. Um breve resumo é dado sobre os dois aqui.

    # Métricas de distância são funções d(a, b) tais que d(a, b) < d(a, c) se os objetos a e b forem considerados “mais semelhantes” que os objetos a e c. Dois objetos exatamente iguais teriam uma distância de zero. Um dos exemplos mais populares é a distância euclidiana. Para ser uma métrica "verdadeira", ela deve obedecer às quatro condições a seguir:

        # 1. d(a, b) >= 0, para todos a e b
        # 2. d(a, b) == 0, se e somente se a = b, definitude positiva
        # 3. d(a, b) == d(b, a), simetria
        # 4. d(a, c) <= d(a, b) + d(b, c), a desigualdade triangular

    # Kernels são medidas de similaridade, ou seja, s(a, b) > s(a, c) se os objetos a e b são considerados “mais semelhantes” que os objetos a e c. Um kernel também deve ser positivo semidefinido.

    # Há várias maneiras de converter entre uma métrica de distância e uma medida de similaridade, como um kernel. Seja D a distância e S o kernel:

        # S = np.exp(-D * gamma), onde uma heurística para escolher gama é 1 / num_features

        # S = 1. / (D / np.max(D))

    # As distâncias entre os vetores de linha de X e os vetores de linha de Y podem ser avaliadas usando pairwise_distances. Se Y for omitido, as distâncias aos pares dos vetores de linha de X são calculadas. Da mesma forma, pairwise.pairwise_kernels pode ser usado para calcular o kernel entre X e Y usando diferentes funções do kernel. Consulte a referência da API para obter mais detalhes.

import numpy as np
from sklearn.metrics import pairwise_distances
from sklearn.metrics.pairwise import pairwise_kernels
X = np.array([[2, 3], [3, 5], [5, 8]])
Y = np.array([[1, 0], [2, 1]])
pairwise_distances(X, Y, metric='manhattan')
#array([[ 4.,  2.],
#       [ 7.,  5.],
#       [12., 10.]])
pairwise_distances(X, metric='manhattan')
#array([[0., 3., 8.],
#       [3., 0., 5.],
#       [8., 5., 0.]])
pairwise_kernels(X, Y, metric='linear')
#array([[ 2.,  7.],
#       [ 3., 11.],
#       [ 5., 18.]])


# !/usr/bin/env python3
# -*-coding:utf-8-*-
# @file: test_sigmoid.py
# @brief:
# @author: Changjiang Cai, ccai1@stevens.edu, caicj5351@gmail.com
# @version: 0.0.1
# @creation date: 14-08-2019
# @last modified: Thu 15 Aug 2019 09:51:02 AM EDT

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

import numpy as np

def sigmoid(x, alpha):
    f = (1.0 - np.exp(- alpha* x)) / (1.0 + np.exp(- alpha * x))
    return f

def arc_sigmoid(f, alpha):
    x = (-1.0/alpha)*np.log((1.0-f)/(1.0+f))
    return x

if __name__ == "__main__":
    x_min = -400*1.0e-3
    x_max = 400*1.0e-3

    x_min = -400
    x_max = 400
    c = 1.0e-3
    c = 1.0e-2
    x = np.array(range(x_min, x_max)) * c
    x_min *= c
    x_max *= c
    alpha = 0.2*100
    alpha = 1.0
    f = sigmoid(x, alpha)

    f0 = np.array([0.001, 0.01, 0.1, 0.5, 0.99])
    x0 = arc_sigmoid(f0, alpha)
    np.set_printoptions(precision=3, suppress=True)
    print ("x0 = {}".format(x0))
    print ("f0 = {}".format(f0))

    x1 = np.array([0.01, 0.1, 0.2, 0.25, 0.3, 0.35, 0.4, 0.5, 4.0, 5.0])
    f1 = sigmoid(x1, alpha)
    print ("x1 = {}".format(x1))
    print ("f1 = {}".format(f1))

    plt.figure(1)
    plt.clf
    plt.subplot(111)
    plt.title('sigmoid func alpha = %.3f' % alpha)
    plt.plot(x, f)
    colrs = ['r', 'b', 'k', 'y', 'b']

    for i in range(x0.shape[0]):
        # draw horizontal line
        x_ = (x_min, x0[i])
        y_ = (f0[i], f0[i])
        plt.plot(x_, y_, color= colrs[i], linestyle='--')
        # draw vertical line
        x_ = (x0[i], x0[i])
        y_ = (-1, f0[i])
        plt.plot(x_, y_, color= colrs[i], linestyle='--')

    plt.savefig('../results/sigmoid_alpha_%.3f.png' % alpha)
    #print('f(200, alpha = {}) = {}'.format(0.2, sigmoid(200.0, 0.2)))
    #print('f(200, alpha = {}) = {}'.format(0.02, sigmoid(200.0, 0.02)))


"""Check if state is pure."""
from typing import List, Union

import numpy as np

def is_pure(state: Union[List[np.ndarray], np.ndarray]) -> bool:
    r"""
    Determine if a given state is pure or list of states are pure [WikIsPure]_.

    A state is said to be pure if it is a density matrix with rank equal to 1. Equivalently, the
    state :math:`\rho` is pure if there exists a unit vector :math:`u` such that:

    .. math::
        \rho = u u^*.

    Examples
    ==========

    Consider the following Bell state:

    .. math::
        u = \frac{1}{\sqrt{2}} \left( |00 \rangle + |11 \rangle \right) \in \mathcal{X}.

    The corresponding density matrix of :math:`u` may be calculated by:

    .. math::
        \rho = u u^* = \frac{1}{2} \begin{pmatrix}
                         1 & 0 & 0 & 1 \\
                         0 & 0 & 0 & 0 \\
                         0 & 0 & 0 & 0 \\
                         1 & 0 & 0 & 1
                       \end{pmatrix} \in \text{D}(\mathcal{X}).

    Calculating the rank of :math:`\rho` yields that the :math:`\rho` is a pure state. This can be
    confirmed in :code:`toqito` as follows:

    >>> from toqito.states import bell
    >>> from toqito.state_props import is_pure
    >>> u = bell(0)
    >>> rho = u * u.conj().T
    >>> is_pure(rho)
    True

    It is also possible to determine whether a set of density matrices are pure. For instance, we
    can see that the density matrices corresponding to the four Bell states yield a result of
    :code:`True` indicating that all states provided to the function are pure.

    >>> from toqito.states import bell
    >>> from toqito.state_props import is_pure
    >>> u0, u1, u2, u3 = bell(0), bell(1), bell(2), bell(3)
    >>> rho0 = u0 * u0.conj().T
    >>> rho1 = u1 * u1.conj().T
    >>> rho2 = u2 * u2.conj().T
    >>> rho3 = u3 * u3.conj().T
    >>>
    >>> is_pure([rho0, rho1, rho2, rho3])
    True

    References
    ==========
    .. [WikIsPure] Wikipedia: Quantum state - Pure states
        https://en.wikipedia.org/wiki/Quantum_state#Pure_states

    :param state: The density matrix representing the quantum state or a list
                  of density matrices representing quantum states.
    :return: :code:`True` if state is pure and :code:`False` otherwise.
    """
    # Allow the user to enter a list of states to check.
    if isinstance(state, list):
        for rho in state:
            eigs, _ = np.linalg.eig(rho)
            if not np.allclose(np.max(np.diag(eigs)), 1):
                return False
        return True

    # Otherwise, if the user just put in a single state, check that.
    eigs, _ = np.linalg.eig(state)
    return np.allclose(np.max(np.diag(eigs)), 1)


# Question 07, Lab 07
# AB Satyaprakash, 180123062

# imports
import pandas as pd
import numpy as np

# functions

def f(t, y):
    return y - t**2 + 1

def F(t):
    return (t+1)**2 - 0.5*np.exp(t)

def RungeKutta4(t, y, h):
    k1 = f(t, y)
    k2 = f(t+h/2, y+h*k1/2)
    k3 = f(t+h/2, y+h*k2/2)
    k4 = f(t+h, y+h*k3)
    return y + h*(k1 + 2*k2 + 2*k3 + k4)/6

def AdamsBashforth(t, y, h):
    return y[-1] + h*(55*f(t[-1], y[-1]) - 59*f(t[-2], y[-2]) + 37*f(t[-3], y[-3]) - 9*f(t[-4], y[-4]))/24

def AdasmMoulton(t, y, h):
    t1 = t[-1]+h
    y1 = AdamsBashforth(t, y, h)
    return y[-1] + h*(9*f(t1, y1) + 19*f(t[-1], y[-1]) - 5*f(t[-2], y[-2]) + f(t[-3], y[-3]))/24

# program body
t = [0]
y = [0.5]
h = 0.2
t.append(round(t[-1]+h, 1))
y.append(RungeKutta4(t[-1], y[-1], h))
t.append(round(t[-1]+h, 1))
y.append(RungeKutta4(t[-1], y[-1], h))
t.append(round(t[-1]+h, 1))
y.append(RungeKutta4(t[-1], y[-1], h))
yact = []

while t[-1] < 2:
    y.append(AdasmMoulton(t, y, h))
    t.append(round(t[-1]+h, 1))
for T in t:
    yact.append(F(T))

df = pd.DataFrame()
df["Adam's Predictor-Corrector Method"] = pd.Series(y)
df['Actual Value'] = pd.Series(yact)
df.set_index(pd.Series(t), inplace=True)
print(df)

Semester 6/MA 322 (Scientific Computing Theory)/Labs/Lab 7/Code/q7.py


import numpy as np
import math

def dot_product(a, b):
    """Implement dot product between the two vectors: a and b.

    (optional): While you can solve this using for loops, we recommend
    that you look up `np.dot()` online and use that instead.

    Args:
        a: numpy array of shape (x, n)
        b: numpy array of shape (n, x)

    Returns:
        out: numpy array of shape (x, x) (scalar if x = 1)
    """
    out = np.dot(a,b)
    return out

def complicated_matrix_function(M, a, b):
    """Implement (a * b) * (M * a.T).

    (optional): Use the `dot_product(a, b)` function you wrote above
    as a helper function.

    Args:
        M: numpy matrix of shape (x, n).
        a: numpy array of shape (1, n).
        b: numpy array of shape (n, 1).

    Returns:
        out: numpy matrix of shape (x, 1).
    """
    out = np.dot(a,b) * np.dot(M,a.transpose())

    return out

def eigen_decomp(M):
    """Implement eigenvalue decomposition.

    (optional): You might find the `np.linalg.eig` function useful.

    Args:
        matrix: numpy matrix of shape (m, n)

    Returns:
        w: numpy array of shape (m, m) such that the column v[:,i] is the eigenvector corresponding to the eigenvalue w[i].
        v: Matrix where every column is an eigenvector.
    """

    w, v = np.linalg.eig(M)
    return w, v

def euclidean_distance_native(u, v):
    """Computes the Euclidean distance between two vectors, represented as Python
    lists.

    Args:
        u (List[float]): A vector, represented as a list of floats.
        v (List[float]): A vector, represented as a list of floats.

    Returns:
        float: Euclidean distance between `u` and `v`.
    """
    # First, run some checks:
    assert isinstance(u, list)
    assert isinstance(v, list)
    assert len(u) == len(v)

    # Compute the distance!
    # Notes:
    #  1) Try breaking this problem down: first, we want to get
    #     the difference between corresponding elements in our
    #     input arrays. Then, we want to square these differences.
    #     Finally, we want to sum the squares and square root the
    #     sum.

    distance = 0
    for i in range(len(u)):
        distance += (u[i] - v[i])**2

    distance = math.sqrt(distance)
    return distance

def euclidean_distance_numpy(u, v):
    """Computes the Euclidean distance between two vectors, represented as NumPy
    arrays.

    Args:
        u (np.ndarray): A vector, represented as a NumPy array.
        v (np.ndarray): A vector, represented as a NumPy array.

    Returns:
        float: Euclidean distance between `u` and `v`.
    """
    # First, run some checks:
    assert isinstance(u, np.ndarray)
    assert isinstance(v, np.ndarray)
    assert u.shape == v.shape

    # Compute the distance!
    # Note:
    #  1) You shouldn't need any loops
    #  2) Some functions you can Google that might be useful:
    #         np.sqrt(), np.sum()
    #  3) Try breaking this problem down: first, we want to get
    #     the difference between corresponding elements in our
    #     input arrays. Then, we want to square these differences.
    #     Finally, we want to sum the squares and square root the
    #     sum.

    distance = np.sum((u-v)**2)
    distance = np.sqrt(distance)
    return distance

def get_eigen_values_and_vectors(M, k):
    """Return top k eigenvalues and eigenvectors of matrix M. By top k
    here we mean the eigenvalues with the top ABSOLUTE values (lookup
    np.argsort for a hint on how to do so.)

    (optional): Use the `eigen_decomp(M)` function you wrote above
    as a helper function

    Args:
        M: numpy matrix of shape (m, m).
        k: number of eigen values and respective vectors to return.

    Returns:
        eigenvalues: list of length k containing the top k eigenvalues
        eigenvectors: list of length k containing the top k eigenvectors
            of shape (m,)
    """
    eigenvalues = []
    eigenvectors = []

    w,v = eigen_decomp(M)
    index = np.argsort(np.abs(w))

    eigenvalues = w[index[:k]]
    eigenvectors = v[index[:k]]
    return eigenvalues, eigenvectors


#!/usr/bin/env python
# coding: utf-8

# In[55]:

# multiple_linear_regression_problem.m
# Kiran Gunnam

import matplotlib.pyplot as plt
import numpy as np
import numpy.linalg as lin
import matplotlib.patches as mpatches
from scipy.io import loadmat

#[Acceleration  Cylinders Displacement Horsepower Model_Year Weight  MPG  ]
car_data= loadmat('car_mpg_data.mat')
data=car_data['data']
x= data[:,0:6] # x independent variable/experimental variable/predictor variable,
y = data[:,6]           # y dependent variable/outcome variable
n = np.size(y)          # data size

# In[56]:

# Plot the data

plt.plot(y)

plt.ylabel('MPG');          # Set the y-axis label
plt.xlabel('Data index');

# In[57]:

plt.subplot(1,6,1)
plt.plot(x[:,0],y,'rd');   #Plot the data; marker 'rd' =red diamonds
plt.xlabel('MPG')
plt.ylabel('Accerleration')

plt.subplot(1,6,2)
plt.plot(x[:,1],y,'rd');   #Plot the data; marker 'rd' =red diamonds
plt.xlabel('MPG')
plt.ylabel('Cylinders')

plt.subplot(1,6,3)
plt.plot(x[:,2],y,'rd');   #Plot the data; marker 'rd' =red diamonds
plt.xlabel('MPG')
plt.ylabel('Displacement')

plt.subplot(1,6,4)
plt.plot(x[:,3],y,'rd');   #Plot the data; marker 'rd' =red diamonds
plt.xlabel('MPG')
plt.ylabel('Horsepower')

plt.subplot(1,6,5)
plt.plot(x[:,4],y,'rd');   #Plot the data; marker 'rd' =red diamonds
plt.xlabel('MPG')
plt.ylabel('Model Year')

plt.subplot(1,6,6)
plt.plot(x[:,5],y,'rd');   #Plot the data; marker 'rd' =red diamonds
plt.xlabel('MPG')
plt.ylabel('Weight')

plt.subplots_adjust(left=None, bottom=None, right=1.5, top=None,
                wspace=0.6, hspace=None)

# In[58]:

# Now, we want to allow a non-zero intercept for our linear equation.
# That is, we don't want to require that our fitted equation go through the origin.
# In order to do this, we need to add a column of all ones to our x column.

# To make sure that regression line does not go through origin
# add a column of all ones (intercept term) to x

choice= int(input("Enter your choice "))

     # choice 1
if (choice == 1) :
    X =np.hstack([np.ones((n,1)), x[:,3:4]])
    num_predictors=1 #since we are using 1 terms as predictor

# Is it possible to improve above predictor choice?
# consider the following 5 additional choices.

   #choice 2
elif (choice ==2) :
    X = np.hstack([np.ones((n,1)),x[:,3:6]])
    num_predictors=2 #since we are using 2 terms as predictors

    #choice 3
elif (choice==3) :
    X = np.hstack([np.ones((n,1)), x[:,3:4], x[:,5:6], x[:,3:4]*x[:,5:6]])
    num_predictors=3 #since we are using 3 terms as predictors

#choice 4
elif  (choice==4) :
    X = np.hstack([np.ones((n,1)), x[:,3:6]])
    num_predictors=3 #since we are using 3 terms as predictors

#choice 5
elif (choice ==5) :
    X = np.hstack([np.ones((n,1)),x]);
    num_predictors=6 #since we are using 6 terms as predictors

#choice 6
elif (choice ==6 ) :
    X = np.hstack([np.ones((n,1)), x, x[:,3:4]*x[:,5:6]]);
    num_predictors=7 #since we are using 7 terms as predictors

# In[59]:

# Given a matrix equation
# X * theta=y,
# the normal equation is that which minimizes the sum of the square differences
# between the left and right sides:
# X'*X*theta=X'*y.
# It is called a normal equation because y-X*theta is normal to the range of X.
# Here, X'*X is a normal matrix.

# Putting that into Octave:
# Calculate theta
# theta = (pinv(X'*X))*X'*y

# Or simply use backward slice operator
# theta =  X\y  %%minimize norm(X*theta-y) via a QR factorization
# we can also use the equivalent command
# theta=mldivide(X,y)
# You should get theta = [ 325.57342
#                          0.26343].
# This means that our fitted equation is as follows:
# y = 0.26343x + 325.57342.

theta= lin.lstsq(X,y)[0]

# You can also use regress command to compute theta
# pkg load statistics
# theta= regress(y,X)

# y_est=X*theta;
y_est=np.matmul(X,theta)

# Now, let's plot our fitted equation (prediction) on top
# of the training data, to see if our fitted equation makes
# sense.

# Plot the fitted equation we got from the regression
# figure
# plotmatrix(x, y_est, 'b*')

plt.subplot(1,6,1)
plt.plot(x[:,0],y_est,'b*');   #Plot the data; marker 'rd' =red diamonds
plt.xlabel('MPG')
plt.ylabel('Accerleration')

plt.subplot(1,6,2)
plt.plot(x[:,1],y_est,'b*');   #Plot the data; marker 'rd' =red diamonds
plt.xlabel('MPG')
plt.ylabel('Cylinders')

plt.subplot(1,6,3)
plt.plot(x[:,2],y_est,'b*');   #Plot the data; marker 'rd' =red diamonds
plt.xlabel('MPG')
plt.ylabel('Displacement')

plt.subplot(1,6,4)
plt.plot(x[:,3],y_est,'b*');   #Plot the data; marker 'rd' =red diamonds
plt.xlabel('MPG')
plt.ylabel('Horsepower')

plt.subplot(1,6,5)
plt.plot(x[:,4],y_est,'b*');   #Plot the data; marker 'rd' =red diamonds
plt.xlabel('MPG')
plt.ylabel('Model Year')

plt.subplot(1,6,6)
plt.plot(x[:,5],y_est,'b*');   #Plot the data; marker 'rd' =red diamonds
plt.xlabel('MPG')
plt.ylabel('Weight')

plt.subplots_adjust(left=None, bottom=None, right=1.5, top=None,
                wspace=0.6, hspace=None)

# In[63]:

# Evaluate each fit you make in the context of your data. For example,
# if your goal of fitting the data is to extract coefficients that have
# physical meaning, then it is important that your model reflect the
# physics of the data. Understanding what your data represents,
# how it was measured, and how it is modeled is important when evaluating
# the goodness of fit.

# One measure of goodness of fit is the coefficient of determination,
# or R^2 (pronounced r-square). This statistic indicates how closely
# values you obtain from fitting a model match the dependent variable
# the model is intended to predict. Statisticians often define R^2
# using the residual variance from a fitted model:
# R^2 = 1 – SSresid / SStotal
# SSresid is the sum of the squared residuals from the regression.
# SStotal is the sum of the squared differences from the mean
# of the dependent variable (total sum of squares). Both are positive scalars.
# Residuals are the difference between the observed values of the response (dependent)
# variable and the values that a model predicts. When you fit a model that is
# appropriate for your data, the residuals approximate independent random errors.
# That is, the distribution of residuals ought not to exhibit a discernible pattern.
# Producing a fit using a linear model requires minimizing the sum of the squares
# of the residuals. This minimization yields what is called a least-squares fit.
# You can gain insight into the "goodness" of a fit by visually examining a plot
# of the residuals. If the residual plot has a pattern (that is, residual data
# points do not appear to have a random scatter), the randomness indicates
# that the model does not properly fit the data.

# The higher the value of R-square , the better the model is at predicting the data.
# Say if Rsq is 0.7, we can say that 70% of the variation in dependent
# variable is explained by the independent variable.
residuals=y-y_est;
Rsq = 1 - sum(residuals**2)/sum((y - np.mean(y))**2)

# adjust Rsq based on number of predictors used
Rsq_adj =  1 - ((1-Rsq)* ((n-1)/(n-num_predictors-1)))

# also examine mean and standard deviation of residuals.
mean_residuals=np.mean(residuals)
std_residuals=np.std(residuals)

# now examine scatter plot
# The residuals show the difference between the acutal
# and fitted values of the model. They are spread out
# across the dynamic range and distributed equally about the 0,
# indicating the model is a good fit!

plt.figure
x_range= range(0,n)
ref=np.ones(n)
plt.plot(x_range,residuals,'bo',x_range,ref,'r-')
plt.xlabel('data index')
plt.ylabel('residuals')
plt.legend(('residuals','zero line'), loc= 'upper left')
plt.show()

plt.figure
plt.plot(y,'r')
plt.plot(y_est,'b')
plt.ylabel('MPG'); # Set the y-axis label
plt.xlabel('Data index');
plt.legend(('actual','fitted'),loc='upper left');
plt.show()