class Matrix: #### Linear Algebra Methods #### def scale(self,i,r,**arguments): if "verbose" in arguments and arguments["verbose"]: if abs(r)>1: print("Scaling row " + str(i+1) + " by " +str (r) ) elif abs(r)<1: print("Scaling row " + str(i+1) + " by 1/"+ str(1/r)) elif r == -1: print("Negating row " + str(i+1) ) elif r == 1: print("Leaving row " + str(i+1) + " unchanged") for j in range(self.n): self.Array[i][j] = r * self.Array[i][j] def interchange(self,i1,i2,**arguments): if "verbose" in arguments and arguments["verbose"]: print("Interchanging rows " + str( i1 +1) + " and " + str( i2+1)) for j in range(self.n): temp = self.Array[i2][j] self.Array[i2][j] = self.Array[i1][j] self.Array[i1][j] = temp def replacement(self,i1, i2, r,**arguments): if "verbose" in arguments and arguments["verbose"]: print("Replacing row " + str(i1+1) + " with " +str ( r) + " times row " + str( i2 +1) ) for j in range(self.n): self.Array[i1][j] = self.Array[i1][j] + r*self.Array[i2][j] #### Support Methods #### def __init__(self,Array): self.Array = Array self.m = len(Array) if self.m == 0: print("ERROR: Empty array") self.n = len(Array[0]) for i in range(len(Array)): if len(Array[i]) != self.n: print("ERROR: inconsistent array dimension") def elt(self,i,j): if (i<0) or (i>self.m) or (j<0) or (j>self.n): print("ERROR: invalid dimension", i, " vs ,", j, "in") self.print() return self.Array[i][j] def row_lnze_index(self,i): ''' Returns index of row LNZE if it exists. Otherwise, returns -1''' for j in range(self.n): if self.elt(i,j) != 0: return j return -1 def print(self): # print("[",end="") # for i in range(self.m): # print(self.Array[i],end="") # if i != self.m-1: print(",") # print("]",end="") for i in range(self.m): print(self.Array[i]) print(".") # def print(self): # print("[",end="") # for i in range(self.m): # print(self.Array[i],end="") # if i != self.m-1: print(",") # print("]",end="") def print_latex(self): print("\\begin{bmatrix}") for i in range(self.m): for j in range(self.n): print(self.Array[i][j],end=" ") if j != self.n-1: print("& ",end="") elif i!= self.m-1: print( "\\\\ \n",end="" ) print("\n\\end{bmatrix}\n") import random def random_matrix_of_rank(m,n,rank): if (1>m) or (1>n): print("Error: invalid dimensions", m, n) return Matrix([[]]) if (rank>n) or (rank>m): print("Error: rank cannot exceed m and n", m, n) return Matrix([[]]) elif 1>rank: return Matrix([ [0 for i in range(n) ] for j in range(m) ] ) ## Pick the columns to contain pivots ## Always put a pivot in the first column if rank==1: pivot_cols=[] else: pivot_cols = random.sample( [i for i in range(1,n)] ,rank-1 ) pivot_cols.append(0) pivot_cols.sort() ## Intialize an empty array values = [0, 0, 0, 0, 0, 0, 1, 1, 1, -1,-1, 2,-2,3,-3] Array = [ [ 0 for i in range(n) ] for j in range(m) ] for row,col in enumerate(pivot_cols): if (row>m) or (col>n): print("Error") return Matrix([[]]) Array[row][col] = 1 for row , lnze in enumerate(pivot_cols): # lnze = pivot_cols[row] for col in range(lnze+1,n): if not(col in pivot_cols): Array[row][col] = random.choice(values) M = Matrix(Array) num_ops = 2*(n+m+2) row_operations = random.choices(["replacement","interchange","scaling"],k=num_ops) for operation in row_operations: if operation == "replacement": r = random.choice([-1,1]) i1,i2 = random.sample([i for i in range(m)],k=2) M.replacement(i1,i2,r) elif operation == "interchange": i1,i2 = random.sample([i for i in range(m)],k=2) M.interchange(i1,i2) elif operation == "scaling": i= random.choice([i for i in range(m)]) r = random.choice([-2,-1,1,2]) M.scale(i,r) return M def RREF_classic( A: Matrix , **arguments): ''' Row reduces the matrix using classical Gaussian elimination''' if "verbose" in arguments and arguments["verbose"]: print("Reducing matrix using classical Gaussian elimination") A.print() for i in range(A.m): # Flip rows as needed to ensure we have the leftmost LNZE for i2 in range(i+1,A.m): if ( -1 < A.row_lnze_index(i2) < A.row_lnze_index(i) ) or ( A.row_lnze_index(i) == -1 ) : A.interchange(i,i2,**arguments) A.print() # Store column of current LNZE j = A.row_lnze_index(i) if j>-1: # If there is a nonzero pivot # Convert the LNZE to 1 A.scale(i,1/A.elt(i,j),**arguments) A.print() # Use LNZE to clear out all other rows for i2 in range(A.m): if i2 != i and A.elt(i2,j)!=0 : r = - A.elt(i2,j)/A.elt(i,j) A.replacement(i2,i,r,**arguments) A.print() def gcd(a, b): '''Calculate the Greatest Common Divisor of a and b. FROM https://stackoverflow.com/questions/11175131/code-for-greatest-common-divisor-in-python#:~:text=The%20greatest%20common%20divisor%20(GCD)%20of%20a%20and%20b%20is Unless b==0, the result will have the same sign as b (so that when b is divided by it, the result comes out positive). ''' while b: a, b = b, a%b return a def RREF_lcm( A: Matrix , **arguments): ''' Uses Gaussian elimination modified to avoid division by scaling the row to be replaced so it is a multiple of the row being used. ''' if "verbose" in arguments and arguments["verbose"]: print("Reducing matrix using GCM's") A.print() for i in range(A.m): # Flip rows as needed to ensure we have the leftmost LNZE for i2 in range(i+1,A.m): if ( -1 < A.row_lnze_index(i2) < A.row_lnze_index(i) ) or ( A.row_lnze_index(i) == -1 ) : A.interchange(i,i2,**arguments) A.print() # Store column of current LNZE j = A.row_lnze_index(i) if j >-1 :# If there is a nonzero pivot # Scale row to be replaced to be a multiple of the row used. for i2 in range(A.m): if i2 != i and A.elt(i2,j)!=0 : target_lnze = A.elt(i2,j) A.scale(i2, A.elt(i,j)/gcd(A.elt(i,j),target_lnze),**arguments) A.print() A.replacement(i2, i, -target_lnze/gcd(A.elt(i,j),target_lnze), **arguments) A.print() for i in range(A.m): j = A.row_lnze_index(i) if j>-1 : if A.elt(i,j)!=0 : A.scale(i,1/(A.elt(i,j)),**arguments) A.print() def RREF_euclidean( A: Matrix , **arguments): ''' Row reduces the matrix using the Euclidean algorithm to get Echelon Form.''' if "verbose" in arguments and arguments["verbose"]: print("Reducing matrix using Euclidean algorithm:") print("At each stage") print("1. Sort columns by pivot index and size") print("2. Use the smallest magnitude pivot to reduce its position in all later rows, but without introducing fractions") print("3. Once a pivot has cleared out later rows, scale it to be 1") print("4. Once the matrix is in echelon form, then clear up") print(".") A.print() current_row = 0 # Start with row zero done_with_row = False while current_row < A.m: # Rearrange rows by LNZE for i in range(A.m): # Flip rows as needed to ensure we have the leftmost LNZE for i2 in range(i+1,A.m): if ( -1 < A.row_lnze_index(i2) < A.row_lnze_index(i) ) : A.interchange(i,i2,**arguments) A.print() elif ( (A.row_lnze_index(i) == -1) and (A.row_lnze_index(i2)>-1) ): A.interchange(i,i2,**arguments) A.print() # Sort rows by ABS VALUE of the LNZE itself for i in range(A.m): for i2 in range(i+1,A.m): if (A.row_lnze_index(i) != -1 ) and (A.row_lnze_index(i) == A.row_lnze_index(i2)): if ( abs(A.elt(i,A.row_lnze_index(i))) > abs(A.elt(i2,A.row_lnze_index(i)))): A.interchange(i,i2,**arguments) A.print() # Store column of current LNZE j = A.row_lnze_index(current_row) if j == -1: ## Since we have sorted the rows, if the current row has no LNZE, ## we are at the bottom of the matrix break elif j > -1: # Use the current nonzero pivot to reduce (and maybe zero out) # its value in later rows done_with_row = True for i2 in range(current_row+1,A.m): if i2 != current_row and A.elt(i2,j)!=0 : remainder = A.elt(i2,j) % A.elt(current_row,j) if( remainder ) == 0: # print("divisible") r = -A.elt(i2,j)/A.elt(current_row,j) else: done_with_row = False r = -(A.elt(i2,j)-remainder)/A.elt(current_row,j) A.replacement(i2,current_row,r,**arguments) A.print() if done_with_row : # print("Rescaling row") A.scale( current_row, 1/A.elt(current_row,j) , **arguments) A.print() current_row = current_row +1 print("Matrix is in Echelon form") print("Now finding Reduced Echelon form\n") for current_row in range(A.m): j = A.row_lnze_index(current_row) if j>-1: for i2 in range(current_row): if A.elt(i2,j) != 0: A.replacement(i2,current_row, -A.elt(i2,j),**arguments) A.print() print("Matrix is now in Reduced Echelon Form") m = random.randint(2,4) n = random.randint(m+1,8) rank = min( random.randint(int(n/2),n), m, n) A = random_matrix_of_rank(m,n,rank) B = Matrix([[A.Array[i][j] for j in range(n)] for i in range(m)]) C = Matrix([[A.Array[i][j] for j in range(n)] for i in range(m)]) #print("===================") # #RREF_classic(A, verbose=True) # #print("===================") # #RREF_lcm(B, verbose=True) # #print("===================") RREF_euclidean(C, verbose=True) #print("===================")