<jupyter_start><jupyter_text>Linear Algebra for CHE Laboratory 2: Matrices  ObjectivesAt the end of this activity you will be able to1.   Be familiar with matrices and their relation to linear equations.2.   Perform basic matrix operations.3.   Program and translate matrix equations and operations using Python. Discussion<jupyter_code>import numpy as np
import matplotlib.pyplot as plt
import scipy.linalg as la
%matplotlib inline<jupyter_output><empty_output><jupyter_text>Matrices The notation and use of matrices is probably one of the fundamentals of modern computing. Matrices are also handy representations of complex equations or multiple inter-related equations from 2-dimensional equations to even hundreds and thousands of them.  ***A***, ***B***, and ***C*** as system of equation. $$A = \left\{    \begin{array}\        2q + w \\         5q - 20w    \end{array}\right. \\B = \left\{    \begin{array}\        q+5w+4r \\         7q + 2w -4r \\        -4q - 5w + 12r    \end{array}\right. \\C = \left\{    \begin{array}\        13q + 5w + 7r-8t \\         5q -22w -14r + 9t \\        9q - 28w  + 18r - 16t\\        13q +22w -10r +19t    \end{array}\right. $$ We could see that ***A*** is a system of 2 equations with 2 parameters. ***B*** is a system of 3 equations with 3 parameters. And ***C*** is a system of 4 equations with 4 parameters. We can represent them as matrices: :$$A=\begin{bmatrix} 2 & 1 \\ 5 & {-20}\end{bmatrix} \\B=\begin{bmatrix} 1 & 5 & 4 \\ 7 & 2 & -4 \\ -4 & -5 & 12\end{bmatrix}\\C=\begin{bmatrix} 13 & 5 & 7 & -8 \\ 5 & -22 & -14 & 9 \\ 9 & -28 & 18 &-16\\ 13 & 22 & -10 & 19 \end{bmatrix}$$ Declaring Matrices The entities or numbers in matrices are called the elements of a matrix. These elements are arranged and ordered in rows and columns which form the list/array-like structure of matrices. And just just like arrays, these elements are indexed according to their position with respect to their rows and columns. This can be represented just like the equation below. Whereas ***A*** is a matrix consisting of elements denoted by **a**i,j*. Denoted by *i* is the number of rows in the matrix while *j* stands for the number of columns.Do note that the size of a matrix is *i x j*. $$A=\begin{bmatrix}a_{(0,0)}&a_{(0,1)}&\dots&a_{(0,j-1)}\\a_{(1,0)}&a_{(1,1)}&\dots&a_{(1,j-1)}\\\vdots&\vdots&\ddots&\vdots&\\a_{(i-1,0)}&a_{(i-1,1)}&\dots&a_{(i-1,j-1)}\end{bmatrix}$$<jupyter_code>## Since we'll keep on describing matrices. Let's make a function.
def describe_mat(matrix):
    print(f'Matrix:\n{matrix}\n\nshape:\t{matrix.shape}\nRank:\t{matrix.ndim}\n')
## Declaring a 2 x 2 matrix
A = np.array([
    [3, 8],
    [12, 4]
])
describe_mat(A)
## Declaring a 2 x 3 matrix
G = np.array([
    [5, 4, 7],
    [23, 11, 34]
])
describe_mat(G)
## Declaring a 3 x 2 matrix
H = np.array([
    [7, 8],
    [15, 21],
    [9, 18]
])
describe_mat(H)
L = np.array([9,7,5,6,12,17,19])
describe_mat(L)<jupyter_output>Matrix:
[ 9  7  5  6 12 17 19]

shape:	(7,)
Rank:	1<jupyter_text>Categorizing Matrices  There are several ways of classifying matrices. One could be according to their **shape** and another is according to their element values. We'll try to go through them. According to shape According to shape - it defines the number of rows and columns of the specific matrix Row and Column Matrices A 1-by-n matrix (a single row) is a row matrix, while an n-by-1 matrix is a column matrix (a single column). The terms "row and column matrices" and "row and column vectors" are interchangeable.<jupyter_code>## Declaring a Row Matrix

row_mat_1D = np.array([
     4, 5, 6, 8, 9                  
]) ## this is a 1-D Matrix with a shape of (5,), it's not really considered as a row matrix.
row_mat_2D = np.array([
     [4, 5, 6, 8, 9]                  
]) ## this is a 2-D Matrix with a shape of (1, 5)
describe_mat(row_mat_1D)
describe_mat(row_mat_2D)
## Declaring a Column Matrix

col_mat =np.array([
     [3], 
     [6],
     [14],
     [15],
     [9],
     [4]             
]) ## this is a 2-D Matrix with a shape of (6,1)
describe_mat(col_mat)<jupyter_output>Matrix:
[[ 3]
 [ 6]
 [14]
 [15]
 [ 9]
 [ 4]]

shape:	(6, 1)
Rank:	2<jupyter_text>Square Matrices A square matrix is a matrix with the exact number of components as its name suggests. Its order is of the form n x n since it contains an equal number of rows and columns. All matrix operations such as transpose, determinant, adjoint, and inverse and matrices' mathematical operations apply to a square matrix.<jupyter_code>def describe_mat(matrix):
    is_square = True if matrix.shape[0] == matrix.shape[1] else False
    print(f'Matrix:\n{matrix}\n\nshape:\t{matrix.shape}\nRank:\t{matrix.ndim}\nIs Square: {is_square}\n')
square_mat = np.array([
   [2,4,6],
   [5,7,8],  
   [4,2,9]            
])

non_square_mat = np.array([
   [2,4,6],    
   [5,7,8]                    
])
describe_mat(square_mat)
describe_mat(non_square_mat)<jupyter_output>Matrix:
[[2 4 6]
 [5 7 8]
 [4 2 9]]

shape:	(3, 3)
Rank:	2
Is Square: True

Matrix:
[[2 4 6]
 [5 7 8]]

shape:	(2, 3)
Rank:	2
Is Square: False<jupyter_text>According to element values According to element values - it defines what kind of matrix it will be, it can be square, null, zero, ones, diagonal, identity, upper triangular, and lower triangular matrix. Null Matrix A Null Matrix is a matrix in which each element is none. The null matrix, often known as a zero matrix, has a variable number of rows and columns. Because adding a null matrix to any other matrix produces the same outcome, a null matrix is also known as the additive identity of the provided matrix.<jupyter_code>def describe_mat(matrix):
  if matrix.size > 0:
     is_square = True if matrix.shape[0] == matrix.shape[1] else False
     print(f'Matrix:\n{matrix}\n\nshape:\t{matrix.shape}\nRank:\t{matrix.ndim}\nIs Square: {is_square}\n')
  else:
    print('Matrix is Null')
null_mat = np.array([])
describe_mat(null_mat)<jupyter_output>Matrix:
[]

shape:	(0,)
Rank:	1<jupyter_text>Zero Matrix A zero matrix is one in which most elements are equal to zero. A zero matrix is represented by 0, and if necessary, a subscript can be applied to specify the matrix's dimensions. Zero matrices play a similar role in matrices operations as zero does in real-number operations.<jupyter_code>zero_mat_row = np.zeros((3, 3))
zero_mat_sqr = np.zeros((2, 4))
zero_mat_rct = np.zeros((3, 4))

print(f'Zero Row Matrix: \n{zero_mat_row}')
print(f'Zero Square Matrix: \n{zero_mat_sqr}')
print(f'Zero Rectangular Matrix: \n{zero_mat_rct}')<jupyter_output>Zero Row Matrix: 
[[0. 0. 0.]
 [0. 0. 0.]
 [0. 0. 0.]]
Zero Square Matrix: 
[[0. 0. 0. 0.]
 [0. 0. 0. 0.]]
Zero Rectangular Matrix: 
[[0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]]<jupyter_text>Ones Matrix A matrix of ones, also known as an all-ones matrix, is a matrix in which all of the elements are equal to one.<jupyter_code>ones_mat_row = np.ones((4, 2))
ones_mat_sqr = np.ones((2, 4))
ones_mat_rct = np.ones((3, 3))

print(f'Ones Row Matrix: \n{ones_mat_row}')
print(f'Ones Square Matrix: \n{ones_mat_sqr}')
print(f'Ones Rectangular Matrix: \n{ones_mat_rct}')<jupyter_output>Ones Row Matrix: 
[[1. 1.]
 [1. 1.]
 [1. 1.]
 [1. 1.]]
Ones Square Matrix: 
[[1. 1. 1. 1.]
 [1. 1. 1. 1.]]
Ones Rectangular Matrix: 
[[1. 1. 1.]
 [1. 1. 1.]
 [1. 1. 1.]]<jupyter_text>Diagonal Matrix A diagonal matrix has both upper and bottom triangle elements. The name "diagonal matrix" comes from all the entries above and below the principal diagonal are zeros.<jupyter_code>np.array([
   [4,0,0,0],
   [0,6,0,0],
   [0,0,7,0],
   [0,0,0,2]       
])
w= np.diag([4,6,7,2])
w.shape[0] == w.shape[1]
w
w= np.diag([4,6,7,2])
np.diag(w).shape == w.shape[1] == w.shape[4]<jupyter_output><empty_output><jupyter_text>Identity Matrix An identity matrix is a square matrix for all primary diagonal elements and zeros for all other elements. The letter "In" or simply "I" is used to indicate it. The result of multiplying any matrix by the identity matrix is the provided matrix.<jupyter_code>np.eye(8)
np.identity(7)<jupyter_output><empty_output><jupyter_text>Upper Triangular Matrix All of the entries below the main diagonal are zero in the upper triangular matrix.<jupyter_code>np.array([
   [3,4,5,6],
   [0,6,7,8],
   [0,0,3,4],
   [0,0,0, 4]      
])<jupyter_output><empty_output><jupyter_text>Lower Triangular Matrix All of the entries above the main diagonal are zero in the lower triangular matrix.<jupyter_code>np.array([
   [3,0,0,0,],
   [4,4,0,0],
   [5,2,3,0],
   [1,3,7, 4]      
])<jupyter_output><empty_output><jupyter_text>Practice 1. Given the linear combination below, try to create a corresponding matrix representing it.$$\theta = 5w + 3q - 1z$$ $$\theta = \begin{bmatrix} 5 & 3 & -1 \end{bmatrix} \\$$ 2. Given he system of linear combinations below, try to encode it as a matrix. Also describe the matrix.$$A = \left\{\begin{array}1w_1 + 2w_2 +1w_3\\0+4w_2 - 1w_3\\0+0+10w_3\end{array}\right.$$ $$A=\begin{bmatrix} 1 & 2 & 1 \\ 0 & 4 & -1 \\ 0 & 0 & 10\end{bmatrix} \\$$ 3. Given the matrix below, express it as a linear combination in a markdown and a LaTeX markdown $$G = \left\{\begin{array}5x + 7y + 8z\\2x + 2y + 2z\\4x + 6y + 7z\end{array}\right.$$$$G=\begin{bmatrix} 1 & 7 & 8 \\ 2 & 2 & 2 \\ 4 & 6 & 7\end{bmatrix} \\$$<jupyter_code>G = np.array([
    [1,7,8],
    [2,2,2],
    [4,6,7]
])<jupyter_output><empty_output><jupyter_text>4. Given the matrix below, display the output as a LaTeX markdown also express it as a system of linear combinations. $$H = \left\{\begin{array}1x\\2x + 2y\\4x + 6y + 7z\end{array}\right.$$$$H=\begin{bmatrix} 1 & 0 & 0 \\ 2 & 2 & 0 \\ 4 & 6 & 7\end{bmatrix} \\$$<jupyter_code>H = np.tril(G)
H

I =np.triu(G)
I<jupyter_output><empty_output><jupyter_text>Matrix Algebra Matrix algebra is used to display graphs, calculate statistics, and conduct scientific investigations and research in various domains. Matrices may also be used to represent real-world information such as population, infant mortality rate, etc. For charting surveys, these are the finest representation approaches. Addition The operation of combining the entries of two matrices is known as matrix addition.<jupyter_code>A = np.array([
    [2,3],     
    [4,5],
    [7,2]
])
B = np.array([
    [7,3],     
    [4,8],
    [3,3]      
])
A+B
4+A ##Broadcasting
# 2*np.ones(A.shape)+A<jupyter_output><empty_output><jupyter_text>Subtraction Subtraction between two matrices is achievable if their order or dimensions are the same. To subtract two or more matrices, they must have the same number of rows and columns.<jupyter_code>A-B
5-B
5-B == 5*np.ones(B.shape)-B<jupyter_output><empty_output><jupyter_text>Element-wise Multiplication Element-wise Multiplication is an operation of multiplying two numbers but only for its corresponding element.<jupyter_code>A*B
np.multiply(A,B)
3*B<jupyter_output><empty_output><jupyter_text>Activity Task 1Create a function named mat_desc() that througouhly describes a matrix, it should:Displays the shape, size, and rank of the matrix. Displays whether the matrix is square or non-square. Displays whether the matrix is an empty matrix. Displays if the matrix is an identity, ones, or zeros matrix Use 5 sample matrices in which their shapes are not lower than . In your methodology, create a flowchart discuss the functions and methods you have done. Present your results in the results section showing the description of each matrix you have declared.<jupyter_code>def mat_desc (matrix):
  if matrix.size > 0:
    if matrix.shape [0] == matrix.shape[1]:
      m = "Square."
    else:
      m = "Non-square."
    if np.all(matrix == np.identity(matrix.shape[0])):
      mp = "Identity Matrix."
    elif np.all(matrix == np.zeros(matrix.shape)):
      mp = "Zero Matrix."
    elif np.all(matrix == np.ones(matrix.shape)):
      mp = "Ones Matrix."
    else:
      mp = "None."
    print(f'Matrix:\n{matrix}\n\nShape:\t{matrix.shape}\nSize: \t{matrix.size}\nRank:\t{matrix.ndim}\nSquare?: {m}\nSpecial Characteristics: {mp}')
  else:
    print('Matrix is Empty')
square_mat = np.array([
   [7,4,9],
   [15,6,8],  
   [14,22,5]            
])

non_square_mat = np.array([
   [4,7,12],    
   [3,4,18],
   [33,44,76], 
   [23,34,55]                  
])
mat_desc(square_mat)
mat_desc(non_square_mat)
square_mat = np.array([
   [72,5,29,17],
   [11,16,32,45],  
   [12,32,16, 24]            
])

non_square_mat = np.array([
   [42,67,25, 23],    
   [35,14,19, 29], 
   [15,17,69, 45],  
   [75,77,99, 85],                 
])
mat_desc(square_mat)
mat_desc(non_square_mat)
square_mat = np.array([
   [44,43,34,77,85],
   [21,18,33,44,97],  
   [72,42,66,55,82],
   [25,27,88,99,75],
   [45,37,98,19,15],           
])

non_square_mat = np.array([
   [72,77,25, 63],    
   [75,13,10, 89],
   [65,23,80, 79]                    
])
mat_desc(square_mat)
mat_desc(non_square_mat)<jupyter_output>Matrix:
[[44 43 34 77 85]
 [21 18 33 44 97]
 [72 42 66 55 82]
 [25 27 88 99 75]
 [45 37 98 19 15]]

Shape:	(5, 5)
Size: 	25
Rank:	2
Square?: Square.
Special Characteristics: None.
Matrix:
[[72 77 25 63]
 [75 13 10 89]
 [65 23 80 79]]

Shape:	(3, 4)
Size: 	12
Rank:	2
Square?: Non-square.
Special Characteristics: None.<jupyter_text>Identity, Ones, Zero Or Empty Matrix Null Matrix or Empty Matrix<jupyter_code>null_mat = np.array([])
mat_desc(null_mat)<jupyter_output>Matrix is Empty<jupyter_text>Zero Matrix<jupyter_code>zero_mat = np.array([
   [0,0,0,0],
   [0,0,0,0],  
   [0,0,0,0],
   [0,0,0,0]          
])
mat_desc(zero_mat)<jupyter_output>Matrix:
[[0 0 0 0]
 [0 0 0 0]
 [0 0 0 0]
 [0 0 0 0]]

Shape:	(4, 4)
Size: 	16
Rank:	2
Square?: Square.
Special Characteristics: Zero Matrix.<jupyter_text>Ones Matrix<jupyter_code>ones_mat = np.array([
   [1,1,1,1,1],
   [1,1,1,1,1],
   [1,1,1,1,1],
   [1,1,1,1,1],
   [1,1,1,1,1],          
])
mat_desc(ones_mat)<jupyter_output>Matrix:
[[1 1 1 1 1]
 [1 1 1 1 1]
 [1 1 1 1 1]
 [1 1 1 1 1]
 [1 1 1 1 1]]

Shape:	(5, 5)
Size: 	25
Rank:	2
Square?: Square.
Special Characteristics: Ones Matrix.<jupyter_text>Identity Matrix<jupyter_code>identity_mat = np.array([
   [1., 0., 0., 0., 0., 0., 0., 0.],
   [0., 1., 0., 0., 0., 0., 0., 0.],
   [0., 0., 1., 0., 0., 0., 0., 0.],
   [0., 0., 0., 1., 0., 0., 0., 0.],
   [0., 0., 0., 0., 1., 0., 0., 0.],
   [0., 0., 0., 0., 0., 1., 0., 0.],
   [0., 0., 0., 0., 0., 0., 1., 0.],
   [0., 0., 0., 0., 0., 0., 0., 1.]        
])
mat_desc(identity_mat)<jupyter_output>Matrix:
[[1. 0. 0. 0. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0. 0. 0. 0.]
 [0. 0. 1. 0. 0. 0. 0. 0.]
 [0. 0. 0. 1. 0. 0. 0. 0.]
 [0. 0. 0. 0. 1. 0. 0. 0.]
 [0. 0. 0. 0. 0. 1. 0. 0.]
 [0. 0. 0. 0. 0. 0. 1. 0.]
 [0. 0. 0. 0. 0. 0. 0. 1.]]

Shape:	(8, 8)
Size: 	64
Rank:	2
Square?: Square.
Special Characteristics: Identity Matrix.<jupyter_text>Task 2Create a function named mat_operations() that takes in two matrices a input parameters it should:1. Determines if the matrices are viable for operation and returns your  own error message if they are not viable.2. Returns the sum of the matrices.3. Returns the different of the matrices.4. Returns the element-wise multiplication of the matrices.5. Returns the element-wise division of the matrices.Use 5 sample matrices in which their shapes are not lower than . In your methodology, create a flowchart discuss the functions and methods you have done. Present your results in the results section showing the description of each matrix you have declared.<jupyter_code>def mat_operations(mat1, mat2):
    mat1 = np.array(mat1)
    mat2 = np.array(mat2)
    print('Matrix 1:\n', mat1)
    print()
    print('Matrix 2:\n', mat2)
    print()
    if(mat1.shape != mat2.shape):
        print('Since the shape of both matrices are not the same, we could not perform the operations.')
    else:
      print('Sum of the given matrices:')
      print()
      Addition = mat1 + mat2
      print(Addition)
      print()
      print('Difference of the given matrices:')
      print()
      Subtraction = mat1 - mat2
      print(Subtraction)
      print()
      print('Element-wise multiplication product of the given matrices:')
      print()
      EW_Multiplication = np.multiply(mat1, mat2)
      print(EW_Multiplication)
      print()
      print('Element-wise division quotient of the given matrices:')
      print()
      EW_Division = np.divide(mat1, mat2)
      print(EW_Division)
print('Sample 1:\n')
mat_operations([[7,4,6], [5,4,7], [1,3,9]], [[6,2,6],[8,4,1],[7,2,1]])

print()
print()

print('Sample 2:\n')
mat_operations([[4, 6, 6,1], [8,3,8,5], [2,1,3,9]], [[7,3,2,8],[4,7,4,2],[9,7,0,5]])

print()
print()

print('Sample 3:\n')
mat_operations([[3,8,0], [5,8,4], [8,1,4]], [[2,4,5],[3,5,9],[1,2,8]])

print()
print()

print('Sample 4:\n')
mat_operations([[4,2,1,6], [3,5,0,9]], [[8,4,3,7,0],[1,2,3,4,5],[6,7,8,9,10]])

print()
print()

print('Sample 5:\n')
mat_operations([[4,7,5,3,2,1], [8,3,6,5,9,7], [5,1,4,7,3,1]], [[9,3,6,8,3,2],[8,9,3,5,4,3],[4,6,9,2,1,1]])<jupyter_output>Sample 1:

Matrix 1:
 [[7 4 6]
 [5 4 7]
 [1 3 9]]

Matrix 2:
 [[6 2 6]
 [8 4 1]
 [7 2 1]]

Sum of the given matrices:

[[13  6 12]
 [13  8  8]
 [ 8  5 10]]

Difference of the given matrices:

[[ 1  2  0]
 [-3  0  6]
 [-6  1  8]]

Element-wise multiplication product of the given matrices:

[[42  8 36]
 [40 16  7]
 [ 7  6  9]]

Element-wise division quotient of the given matrices:

[[1.16666667 2.         1.        ]
 [0.625      1.         7.        ]
 [0.14285714 1.5        9.        ]]


Sample 2:

Matrix 1:
 [[4 6 6 1]
 [8 3 8 5]
 [2 1 3 9]]

Matrix 2:
 [[7 3 2 8]
 [4 7 4 2]
 [9 7 0 5]]

Sum of the given matrices:

[[11  9  8  9]
 [12 10 12  7]
 [11  8  3 14]]

Difference of the given matrices:

[[-3  3  4 -7]
 [ 4 -4  4  3]
 [-7 -6  3  4]]

Element-wise multiplication product of the given matrices:

[[28 18 12  8]
 [32 21 32 10]
 [18  7  0 45]]

Element-wise division quotient of the given matrices:

[[0.57142857 2.         3.         0.125     ]
 [2.         0.42857143 2.         2.[...]
{'fraction_non_alphanumeric': 0.1544760737563367, 'fraction_numerical': 0.07180658459138767, 'mean_word_length': 3.280400572246066, 'pattern_counts': {'":': 0, '<': 77, '<?xml version=': 0, '>': 79, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '22', 'dir': 'jupyter-structured-clean-dedup', 'id': '609784', 'license': 'Apache-2.0', 'n_tokens_mistral': 7424, 'n_tokens_neox': 6647, 'n_words': 2236, 'path': 'Laboratory_2_Matrices.ipynb', 'repo_name': 'emmataguinod/Linear-Algebra_2ndSem', 'size': '17951'}