<jupyter_start><jupyter_text>Lab 9 &ndash; DATA 3401 (Fall 2021) Lab Date: 11/19 Due Date 12/3 (at the **end** of lab) Lab DescriptionThe purpose of this lab is for you to become more familiar object oriented programming by wrapping up the matrix functions you did in Lab 5.**Important Note!!!** You are **not** allowed to use numpy in this lab. The purpose of the lab is to give you experience writing a class yourself and to appreciate what numpy does in the background for you automatically.You may use either your solution to Lab 5, or if your solution had errors, you may use the code given at the end of this Lab.  Exercise 1 &ndash; Matrix Class1. Create a matrix class with the following properties:    1. Matrices will be represented as lists of lists as we have done before    1. The class can be initialized in 2 ways:        1. with arguments `n` and `m`, the size of the matrix. A newly instanciated matrix will contain all zeros.        1. with a list of lists of values. Note that since we are using lists of lists to implement matrices, it is possible that not all rows have the same number of columns. Test explicitly that the matrix is properly specified.    1. Matrix instances M can be indexed with `M[i][j]` and `M[i,j]`.    1. Matrix assignment works in 2 ways:        1. If `M_1` and `M_2` are `matrix` instances `M_1=M_2` sets the values of `M_1` to those of `M_2`, if they are the same size. Error otherwise.        1. Assignment should also work if `M_2` is a list of lists of correct size.<jupyter_code># Write your class here
import numpy as np

import random

# matrix class

class Matrix:

def __init__(self, n, m):

self.matrix = self.make_matrix(n, m)

# makes a random matrix

def make_matrix(self, n, m):

num = 1

matrix = [[None for j in range(m)] for i in range(n)]

for i in range(len(matrix)):

for j in range(len(matrix[i])):

matrix[i][j] = random.randint(0, 100)

return np.array(matrix)

# The desired functions

def scalarmul(self, c):

for i in range(len(self.matrix)):

for j in range(len(self.matrix[0])):

self.matrix[i][j] = self.matrix[i][j]*c

def add(self, N):

if((len(self.matrix) == len(N)) and len(self.matrix[0]) == len(N[0])):

for i in range(len(self.matrix)):

for j in range(len(self.matrix[0])):

self.matrix[i][j] = self.matrix[i][j] + N[i][j]

else:

print('Size mismatched')

def sub(self, N):

if((len(self.matrix) == len(N)) and len(self.matrix[0]) == len(N[0])):

for i in range(len(self.matrix)):

for j in range(len(self.matrix[0])):

self.matrix[i][j] = self.matrix[i][j] - N[i][j]

else:

print("Size mismatched")

def mat_mult(self, N):

if(len(self.matrix[0]) == len(N)):

result = np.zeros([len(self.matrix),len(N[0])])

for i in range(len(self.matrix)):

for j in range(len(N[0])):

for k in range(len(N)):

result[i][j] += self.matrix[i][k] * N[k][j]

return result

else:

print("Size mismatched")

def element_mult(self, N):

if((len(self.matrix) == len(N)) and len(self.matrix[0]) == len(N[0])):

result = np.zeros([len(N),len(N[0])])

for i in range(len(self.matrix)):

for j in range(len(self.matrix[0])):

result[i][j] = self.matrix[i][j] * N[i][j]

return result

else:

print('Size mismatched')

def equals(self, N):

equal = True

if((len(self.matrix) == len(N)) and len(self.matrix[0]) == len(N[0])):

for i in range(len(self.matrix)):

for j in range(len(self.matrix[0])):

if(self.matrix[i][j] != N[i][j]):

equal = False

break

if(not equal):

break

else:

equal = False

return equal

#returns the matrix

def get_matrix(self):

return self.matrix

# Demonstration the function of the methods

m1 = Matrix(2,3)

m2 = Matrix(2,3)

m3 = Matrix(3,4)

m1.scalarmul(4)

m1.add(m2.get_matrix())

m1.sub(m2.get_matrix())

print(m1.mat_mult(m3.get_matrix()))

print(m1.element_mult(m2.get_matrix()))

print(m1.equals(m3.get_matrix()))

print(m1.get_matrix())
# Test your class here -- be sure to test all possible instantiations and cases for matrix assignment<jupyter_output><empty_output><jupyter_text>Exercise 2 &ndash; Internal methodsAdd the following methods to your class (note you may call your functions from Lab 5 or those below in these methods). Make sure to appropriately test the dimensions of the matrices to make sure the operations are correct.1. `M.scalarmul(c)`: a matrix that is scalar product $cM$, where every element of $M$ is multiplied by $c$.1. `M.add(N)`: adds two matrices $M$ and $N$. Don’t forget to test that the sizes of the matrices are compatible for this and all other operations.1. `M.sub(N)`: subtracts two matrices $M$ and $N$.1. `M.mat_mult(N)`: returns a matrix that is the matrix product of two matrices $M$ and $N$.1. `M.element_mult(N)`: returns a matrix that is the element-wise product of two matrices $M$ and $N$.1. `M.equals(N)`: returns true/false if $M==N$.<jupyter_code># Add these to your class here (copy and paste the class from Exercise 1)

# Test your functions thoroughly<jupyter_output><empty_output><jupyter_text>Exercise 3Modify the functions `zeros`, `ones`, and `constant` (of your own or those below) to create objects of your matrix class. Note these should be functions outside of your class.<jupyter_code># Write your functions here

# Test your functions here<jupyter_output><empty_output><jupyter_text>Exercise 4Overload python operators to appropriately use your functions in 4 and allow expressions like:- 2*M- M*2- M+N- M-N- M*N- M==N- M=NYou may find this tutorial useful [https://realpython.com/operator-function-overloading/]. I will also discuss overloading in Lecture Monday 11/22.Demonstrate the basic properties of matrices with your matrix class by creating two 2 by 2 example matrices using your Matrix class and illustrating the following:1. $(AB)C = A(BC)$1. $A(B+C)=AB+AC$1. $AB \neq BA$1. $AI = A$1. $AB = 0$ if $B$ is the 0 matrix<jupyter_code># Modify your class here (copy and paste it from Exercise 2)

# Test your functions here<jupyter_output><empty_output><jupyter_text>Functions from Lab 5You can use these freely<jupyter_code>def zeros(m,n):
    return [[0]*n for x in range(m)]

def constant(m,n,c):
    return [[c]*n for x in range(m)]

def ones(m,n,c):
    return constant(m,n,1)

def eye(n):
    x = zeros(n,n)
    for i in range(n):
        x[i][i]=1
    return x

def is_matrix(matrix):
    if type(matrix)!=list:
        print("Not a matrix")
        return False
    if type(matrix[0])!=list:
        print("Not a matrix")
        return False
    _ = len(matrix[0])
    for i in matrix:
        if len(i)!=_:
            print("Not a matrix")
            return False
    return True

def shape(matrix):
    if not is_matrix(matrix):
        return
    row_len = len(matrix)
    col_len = len(matrix[0])
    return (row_len,col_len)

def is_square_matrix(matrix):
    x,y = matrix_shape(matrix)
    if x != y:
        print("Not a square matrix")
        return False
    return True

def matrix_add(a,b):
    if not is_matrix(a): 
        print("A is not a matrix")
        return 
    if not is_matrix(b):
        print("B is not a matrix")
        return 
    if matrix_shape(a)!=matrix_shape(b):
        print("Matrices must be the same shape")
        return 
    row,col = matrix_shape(a)
    return_matrix = zero_matrix(row,col)
    for i in range(len(a)):
        for j in range(len(a[i])):
            return_matrix[i][j] = a[i][j]+b[i][j]
    return return_matrix

def matrix_neg(matrix):  
    return matrix_scalar_multiply(-1,matrix)

def matrix_sub(a,b):
    return matrix_add(a,matrix_neg(b))

def transpose(matrix):
    if not is_matrix(matrix): 
        print("Error. Not a matrix")
        return 
    col,row = matrix_shape(matrix)
    return_matrix = zero_matrix(row,col)
    for i in range(row):
        for j in range(col):
            return_matrix[i][j]=matrix[j][i]
    return return_matrix

def matrix_scalar_multiply(scalar,matrix):
    if not is_matrix(matrix): 
        print("Error. Not a matrix")
        return 
    row,col = matrix_shape(matrix)
    return_matrix = zero_matrix(row,col)
    for i in range(row):
        for j in range(col):
            return_matrix[i][j]=matrix[i][j]*scalar
    return return_matrix

def matrix_multiply(M1,M2):
    m1,n1=matrix_shape(M1)
    m2,n2=matrix_shape(M2)
    
    if n1==m2:
        
        M3=zero_matrix(m1,n2)
        
        for i in range(m1):
            for j in range(n2):
                # Compute the element
                for k in range(n1):
                    M3[i][j]+=M1[i][k]*M2[k][j]
        return M3
    else:
        print("Error, matrix dimensions do not match.")
    
def elementwise_multiply(M1,M2):
    m1,n1=matrix_shape(M1)
    m2,n2=matrix_shape(M2)
    
    if m1==m2 and n1==n2:
        M3 = zero_matrix(m1,n1)
        for i in range(m1):
            for j in range(n1):
                M3[i][j] = M1[i][j]*M2[i][j]
        return M3
    else:
        print("Error: matrices must be the same shape.")
        
def dot_product(M1,M2):
    m1,n1=matrix_shape(M1)
    m2,n2=matrix_shape(M2)
    
    if m1==m2 and n1==n2:
        dotprod = 0
        for i in range(m1):
            for j in range(n1):
                dotprod += M1[i][j]*M2[i][j]
        return dotprod
    else:
        print("Error: matrices must be the same shape.")<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12742980561555076, 'fraction_numerical': 0.018898488120950324, 'mean_word_length': 3.4163090128755367, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 21, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '5', 'dir': 'jupyter-structured-clean-dedup', 'id': '76456', 'license': 'MIT', 'n_tokens_mistral': 3260, 'n_tokens_neox': 3179, 'n_words': 1065, 'path': 'lab9/.ipynb_checkpoints/DATA3401.2021.Fall.Lab9-checkpoint.ipynb', 'repo_name': 'AngelaDonguia/DumpyRepo', 'size': '9260'}