<jupyter_start><jupyter_text>Laboratory 6: Matrix Operation ObjectivesAt the end of this activity you will be able to:1. Understand the fundamental matrix operations.2. Solve intermediate equations using the procedures.3. In engineering solutions, use matrix algebra. Discussion<jupyter_code>import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline<jupyter_output><empty_output><jupyter_text>Transposition Transposition is a fundamental operation in matrix algebra and is one of its most critical applications. The transposition of a matrix is accomplished by flipping its members' values over the matrix's diagonals. The rows and columns of the original matrix will be swapped due to this. As a result, the symbol $A^T$ indicates the transpose of matrix $A$. As an illustration, consider the following: $$A = \begin{bmatrix} 2 & 5 & 8\\5 & -6 &1 \\ 10 & -8 & 3\end{bmatrix} $$ $$ A^T = \begin{bmatrix} 2 & 3 & 8\\5 & -1 &-9 \\ 7 & 10 & 5\end{bmatrix}$$ This may now be accomplished programmatically through the use of the np.transpose() function or the T method.<jupyter_code>A = np.array([
    [3 ,9, 7],
    [4, -5, 0],
    [5, -2, 5]
])
A
AT1 = np.transpose(A)
AT1
AT2 = A.T
AT2
np.array_equiv(AT1, AT2)
B = np.array([
    [2,4,6,8],
    [3,6,9,12],
])
B.shape
np.transpose(B).shape
B.T.shape<jupyter_output><empty_output><jupyter_text>Create your own matrix (you can experiment with non-squares) and use it to test transposition.<jupyter_code>## Try out your code here.
Z=np.array([
    [5,7,9],
    [4,8,10]        
])
Z.shape
np.transpose(Z).shape
Z.T.shape
ZT = Z.T
ZT<jupyter_output><empty_output><jupyter_text>Dot Product / Inner Product Assuming you are familiar with the dot product from a previous laboratory activity, we will attempt to do the same operation using matrices. The Dot Product/Inner Product will obtain the sum of products of the vectors by row-column pairs by performing a matrix dot product operation. As an example, consider the following two matrices: $X$ and $Y$.$$X = \begin{bmatrix}x_{(1,1)}&x_{(3,3)}\\ x_{(5,8)}&x_{(5,5)}\end{bmatrix}, Y = \begin{bmatrix}y_{(1,1)}&y_{(3,3)}\\ y_{(5,8)}&y_{(5,5)}\end{bmatrix}$$The dot product will then be computed as:$$X \cdot Y= \begin{bmatrix} x_{(1,1)}*y_{(1,1)} + x_{(3,3)}*y_{(5,8)} & x_{(1,1)}*y_{(3,3)} + x_{(3,3)}*y_{(5,5)} \\  x_{(5,8)}*y_{(1,1)} + x_{(5,5)}*y_{(5,8)} & x_{(5,8)}*y_{(3,3)} + x_{(5,5)}*y_{(5,5)}\end{bmatrix}$$So if we assign values to $X$ and $Y$:$$X = \begin{bmatrix}2&3\\ 1&2\end{bmatrix}, Y = \begin{bmatrix}-2&0\\ 4&4\end{bmatrix}$$ $$X \cdot Y= \begin{bmatrix} 2*-2 + 3*4 & 2*0 + 4*4 \\  0*-2 + 2*3 & 0*0 + 2*4 \end{bmatrix} = \begin{bmatrix} 8 & 16 \\ 6 & 8 \end{bmatrix}$$This could be achieved programmatically using `np.dot()`, `np.matmul()` or the `@` operator.<jupyter_code>X = np.array([
    [0,3],
    [5,8]
])
Y = np.array([
    [-5,8],
    [0,7]
])
np.array_equiv(X, Y)
np.dot(X,Y)
X.dot(Y)
X @ Y
np.matmul(X,Y)
D = np.array([
    [5,8,12],
    [2,8,3],
    [5,9,1]
])
E = np.array([
    [-5,0,9],
    [5,8,6],
    [7,1,3]
])
D @ E
D.dot(E)
np.matmul(D, E)
np.dot(D, E)<jupyter_output><empty_output><jupyter_text>When comparing matrix dot products to vector dot products, there are certain extra criteria to follow. Due to the fact that vector dot products were just in one dimension, there are less limitations. Because we are now working with Rank 2 vectors, we must take into consideration the following rules:In order for the two matrices in question to be equivalent, the inner dimensions of each matrix must be the same.Consider the following scenario: you have a matrix $A$ with the structure of $(a,b)$, where $a$ and $b$ are any integers. Suppose we want to conduct a dot product between $A$ and another matrix $B$, then matrix $B$ should have the shape $(b,c)$, where $b$ and $c$ may be any integers and $A$ can be any other integer. As an example, consider the following matrices:$$A = \begin{bmatrix}3&1\\8&-5\\1&8\end{bmatrix}, B = \begin{bmatrix}1&4\\8&6\\-2&-3\end{bmatrix}, C = \begin{bmatrix}2&9&5\\3&4&5\end{bmatrix}$$$A$ has the shape $(,)$, $B$ has the shape $(,)$, and $C$ has a shape of $(,)$ in this example. As a result, the only matrix pairings that are qualified to conduct dot product are the matrices $A cdot C$ and $B cdot C$, respectively.<jupyter_code>A = np.array([
    [3, 1],
    [8, -5],
    [1, 8]
])
B = np.array([
    [1,4],
    [8,6],
    [-2,-3]
])
C = np.array([
    [2,9,5],
    [3,4,5]
])
print(A.shape)
print(B.shape)
print(C.shape)
A @ C
B @ C<jupyter_output><empty_output><jupyter_text>If you look closely, you will note that the geometry of the dot product has changed and that it is no longer the same as any of the matrices that we utilized. In reality, the form of a dot product is generated by the shapes of the matrices that were utilized. As an example, consider the shapes of the matrices $A$ and $B$: $(a,b)$ and $(b,c)$, respectively. In this case, the shape of $A\cdot B$ should be $(a,c)$.<jupyter_code>A @ B.T
X = np.array([
    [2,4,5,10]
])
Y = np.array([
    [6,1,8,-5]
])
print(X.shape)
print(Y.shape)
Y.T @ X
X @ Y.T<jupyter_output><empty_output><jupyter_text>Moreover, you can see that when you attempt to multiply A and B, the program produces the error `ValueError` due to a mismatch in the matrix structure.  Rule 2: Dot Product has special propertiesDue to the fact that dot products are common in matrix algebra, it follows that they have numerous distinct qualities that should be taken into consideration while formulating solutions: 1. $A \cdot B \neq B \cdot A$ 2. $A \cdot (B \cdot C) = (A \cdot B) \cdot C$ 3. $A\cdot(B+C) = A\cdot B + A\cdot C$ 4. $(B+C)\cdot A = B\cdot A + C\cdot A$ 5. $A\cdot I = A$ 6. $A\cdot \emptyset = \emptyset$<jupyter_code>A = np.array([
    [2,5,5],
    [4,7,6],
    [7,4,1]
])
B = np.array([
    [3,1,9],
    [4,3,6],
    [4,8,8]
])
C = np.array([
    [9,1,4],
    [0,5,8],
    [7,5,1]
])
np.eye(3)
A.dot(np.eye(3))
np.array_equal(A@B, B@A)
E = A @ (B @ C)
E
F = (A @ B) @ C
F
np.array_equal(E, X)
np.array_equiv(E, F)
np.eye(A)
A @ E
z_mat = np.zeros(A.shape)
z_mat
a_dot_z = A.dot(np.zeros(A.shape))
a_dot_z
np.array_equal(a_dot_z,z_mat)
null_mat = np.empty(A.shape, dtype=float)
null = np.array(null_mat,dtype=float)
print(null)
np.allclose(a_dot_z,null)<jupyter_output>[[0. 0. 0.]
 [0. 0. 0.]
 [0. 0. 0.]]<jupyter_text>Determinant A determinant is a scalar value that may be obtained from a square matrix in two dimensions. Determinants are important values in matrix algebra because they are fundamental and important values. Although it will not be immediately apparent in this laboratory how it might be used in practice, it will be extensively utilized in subsequent lectures.The determinant of some matrix $A$ is denoted as $det(A)$ or $|A|$. So let's say $A$ is represented as:$$A = \begin{bmatrix}a_{(0,0)}&a_{(0,1)}\\a_{(1,0)}&a_{(1,1)}\end{bmatrix}$$We can compute for the determinant as:$$|A| = a_{(0,0)}*a_{(1,1)} - a_{(1,0)}*a_{(0,1)}$$So if we have $A$ as:$$A = \begin{bmatrix}2&5\\7&9\end{bmatrix}, |A| = $$However, you may question what happens to square matrices that are not of the form $(2,2)$. We may address this issue in a variety of ways, including utilizing co-factor expansion and the minors technique, among others. In the laboratory, we can learn how to do this in the lecture, but we can also programmatically compute the difficult calculation of high-dimensional matrices by using Python. This may be accomplished by the use of the function `np.linalg.det()`.<jupyter_code>A = np.array([
    [2,5],
    [7,9]
])
np.linalg.det(A)
B = np.array([
              [2, 1, 3],
              [3, -6 ,-7],
              [0, -1, 4]
])
np.linalg.det(B)
## Now other mathematics classes would require you to solve this by hand, 
## and that is great for practicing your memorization and coordination skills 
## but in this class we aim for simplicity and speed so we'll use programming
## but it's completely fine if you want to try to solve this one by hand.
B = np.array([
    [3,9,7,4],
    [3,6,8,3],
    [3,9,8,2],
    [7,5,5,2]
])
np.linalg.det(B)<jupyter_output><empty_output><jupyter_text>Inverse The inverse of a matrix is yet another important operation in matrix algebra that should not be overlooked. Determining the inverse of a matrix allows us to assess whether or not the matrix is solvable and has the characteristics of a system of linear equations — we'll go into more detail about this in the nect module. Another use of the inverse matrix is in the solution of the issue of divisibility between matrices between two variables. Although there is an element-by-element division method, there is no such method for splitting the whole idea of matrices. It is possible that the idea of "dividing" matrices is the same as that of "inverting" matrices in the case of inverse matrices.After that, we must go through various processes to get the inverse of the matrix we have created. So, let us suppose we have the following matrix $M$:$$M = \begin{bmatrix}1&7\\-3&5\end{bmatrix}$$First, we need to get the determinant of $M$.$$|M| = (1)(5)-(-3)(7) = 26$$Next, we need to reform the matrix into the inverse form:$$M^{-1} = \frac{1}{|M|} \begin{bmatrix} m_{(1,1)} & -m_{(0,1)} \\ -m_{(1,0)} & m_{(0,0)}\end{bmatrix}$$So that will be:$$M^{-1} = \frac{1}{26} \begin{bmatrix} 5 & -7 \\ 3 & 1\end{bmatrix} = \begin{bmatrix} \frac{5}{26} & \frac{-7}{26} \\ \frac{3}{26} & \frac{1}{26}\end{bmatrix}$$It is possible that you will need to apply co-factors, minors, adjugates, and other reduction techniques for higher-dimension matrices. We can use a computer program to address this problem.`np.linalg.inv()`.<jupyter_code>M = np.array([
    [1,7],
    [-3,5 ]
])

np.array(M @ np.linalg.inv(M), dtype=int)
P = np.array([
              [2, 5, 8],
              [3, 6, -9],
              [1, 4, 7]
])
Q = np.linalg.inv(P)
Q
P @ Q
## And now let's test your skills in solving a matrix with high dimensions:
N = np.array([
    [18,5,23,1,0,33,5],
    [0,45,0,11,2,4,2],
    [5,9,20,0,0,0,3],
    [1,6,4,4,8,43,1],
    [8,6,8,7,1,6,1],
    [-5,15,2,0,0,6,-30],
    [-2,-5,1,2,1,20,12],
])
N_inv = np.linalg.inv(N)
np.array(N @ N_inv,dtype=int)<jupyter_output><empty_output><jupyter_text>To validate the wether if the matric that you have solved is really the inverse, we follow this dot product property for a matrix $M$:$$M\cdot M^{-1} = I$$<jupyter_code>squad = np.array([
    [1.5, 1.0, 2.5],
    [0.8, 0.1, 1.9],
    [0.1, 0.5, 3.0]
])
weights = np.array([
    [1.2, 0.3, 0.9]
])
p_grade = squad @ weights.T
p_grade<jupyter_output><empty_output><jupyter_text>Activity Task 1<jupyter_code>N = np.array([
       [8, 6, 1, 5],
       [6, 7, 1, 0],
       [9, 0, 8, 8],
       [-8, 9, 2, 3]
])
T = np.array([
       [6, 6, 1, 2],
       [6, 5, 9, 2],
       [4, 9, 8, -4],
       [-1, 9, 7, 2]
])
G = np.array([
       [5, 1, -5, 2],
       [0, 0, 9, 5],
       [-4, -9, 3, 8],
       [-7, 5, 1, 0]
])<jupyter_output><empty_output><jupyter_text>$A \cdot B \neq B \cdot A$<jupyter_code>T = (N@T) !=(T@N)
T<jupyter_output><empty_output><jupyter_text>$A \cdot (B \cdot C) = (A \cdot B) \cdot C$<jupyter_code>A = N @ (T@G)
A
B = (N @ T) @ G
B<jupyter_output><empty_output><jupyter_text>$A\cdot(B+C) = A\cdot B + A\cdot C$:<jupyter_code>C = N @ (T + G)
C
D = (N@T) + (N@G)
D<jupyter_output><empty_output><jupyter_text>$(B+C)\cdot A = B\cdot A + C\cdot A$<jupyter_code>E = (T+G) @ N
E
F = (T@N) + (G@N)
F<jupyter_output><empty_output><jupyter_text>$A\cdot I = A$<jupyter_code>np.eye(4)
N.dot(np.eye(4))<jupyter_output><empty_output><jupyter_text>$A\cdot \emptyset = \emptyset$<jupyter_code>z_mat = np.zeros(G.shape)
z_mat<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.16539875203008805, 'fraction_numerical': 0.04188392170270963, 'mean_word_length': 3.8689138576779025, 'pattern_counts': {'":': 0, '<': 68, '<?xml version=': 0, '>': 68, 'https://': 0, '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': '17', 'dir': 'jupyter-structured-clean-dedup', 'id': '497857', 'license': 'Apache-2.0', 'n_tokens_mistral': 4605, 'n_tokens_neox': 4372, 'n_words': 1464, 'path': 'Assignment6.ipynb', 'repo_name': 'britjet/Linear-Algebra_ChE_2nd-sem-2021-2022', 'size': '11699'}