def trid(mat_in, mode='givens'): """ Tri-diagonalization of symmetrical real matrix\n # input requirement\n mat_in: symmetrical real matrix, use FLOAT data type, or will emerge huge roundoff error\n mode: 'givens' or 'householder', 'householder' works faster than 'givens' for 'givens' will seperately work on every off-tri-diagonal element in an iterative manner, not recommended for use other than diagonalization, although there are more efficient diagonalization method\n # output description\n [tridiag, U]\n tridiag: tri-diagonalized matrix\n U: unitary operator\n # formula\n mat_in = U·tridiag·U', where U' denotes transpose of U """ if not mlib.symm_check(mat=mat_in): print('***error*** symmetric matrix is demanded.') exit() if mode == 'givens': [tridiag, U] = givens_tdiag(mat_in=mat_in, verbosity='silent') elif mode == 'householder': nline = len(mat_in) mat_op_on = deepcopy(mat_in) mat_op_on_T = mlib.transpose(mat_op_on) U = mlib.eye(nline) for iline in range(nline - 1): vec_op_on = mat_op_on_T[iline][iline + 1::] reflect_op = hh(vec_in=vec_op_on, verbosity='silent') identi_op = mlib.eye(iline + 1) op = mlib.combine_block(identi_op, reflect_op) #mat_op_on = mlib.dot(op, mat_op_on) mat_op_on = mlib.unitary_transform(op, mat_op_on) mat_op_on_T = mlib.transpose(mat_op_on) U = mlib.dot(op, U) U = mlib.transpose(U) tridiag = mat_op_on else: exit() return [tridiag, U]
def qr(mat_in, mode='householder', verbosity='silent'): """ QR decomposition\n A = QR, where R is upper-triangonal matrix\n # input requirement\n mat_in: matrix to perform QR decomposition, only squared matrix is supported\n mode: 'householder' (recommended) or 'schmidt' (not recommended, bug exists, unsolved)\n # output description\n [original matrix, Q matrix, R matrix] """ nline = len(mat_in) mat_op_on = deepcopy(mat_in) if mode == 'householder': mat_op_on_T = mlib.transpose(mat_op_on) op_log = [] for iline in range(nline): vec_op_on = mat_op_on_T[iline][iline::] reflect_op = hh(vec_in=vec_op_on, verbosity=verbosity) identi_op = mlib.eye(iline) op = mlib.combine_block(identi_op, reflect_op) if verbosity == 'debug': print('QR| (householder) matrix before operation:\n{}'.format( mat_op_on)) mat_op_on = mlib.dot(op, mat_op_on) if verbosity == 'debug': print( 'QR| (householder) matrix after operation:\n{}\n OPERATION:\n{}' .format(mat_op_on, op)) mat_op_on_T = mlib.transpose(mat_op_on) op_log.append(op) Q = mlib.eye(nline) for iop in op_log: Q = mlib.dot(iop, Q) Q = mlib.transpose(Q) return [mat_in, Q, mat_op_on] elif mode == 'schmidt': #print('QR| ***warning*** There seems one bug that has not been discovered yet, although Gram-Schmidt works well.') [_, Q] = gs(mat_in, mode='column', verbosity=verbosity) Q_t = mlib.transpose(Q) R = mlib.dot(Q_t, mat_op_on) return [mat_in, Q, R]
def ijacobi(sym_mat_in, tri_diag=False, skipthr=1E-10, verbosity='silent'): nline = len(sym_mat_in) mat_op_on = deepcopy(sym_mat_in) op_accum = mlib.eye(n=nline) if tri_diag: row_skip = 1 else: row_skip = 0 for icol in range(nline - 1): for irow in range(icol + 1 + row_skip, nline): if abs(mat_op_on[irow][icol] / nline**2) < skipthr: continue # guarantee irow >= icol, i.e., only concentrate on lower triangonal part sub_mat = [[mat_op_on[icol][icol], mat_op_on[icol][irow]], [mat_op_on[irow][icol], mat_op_on[irow][irow]]] [diag_mat, Uso2] = orthso2(sub_mat) if verbosity == 'debug': print( '\nJacobi (sequential 2-dimensional) diagonalization report\n' + '-' * 50) print('Present matrix:') mlib.matrix_print(mat_op_on, decimal=4) print('Matrix to diagonalize:') mlib.matrix_print(sub_mat, decimal=4) print('Diagonalized matrix:') mlib.matrix_print(diag_mat, decimal=4) print('Unitary operator at present step:') mlib.matrix_print(Uso2, decimal=4) #mat_op_on[icol][icol] = diag_mat[0][0] #mat_op_on[icol][irow] = diag_mat[0][1] #mat_op_on[irow][icol] = diag_mat[1][0] #mat_op_on[irow][irow] = diag_mat[1][1] op = op_gen(size=nline, U=Uso2, irow=irow, icol=icol) mat_op_on = mlib.unitary_transform(U=op, mat=mat_op_on) op_accum = mlib.dot(op_accum, op) if verbosity == 'debug': print('Unitary operator that operates on whole matrix:') mlib.matrix_print(op, decimal=4) print('Accumulative unitary operator:') mlib.matrix_print(op_accum, decimal=4) if verbosity == 'debug': print('-' * 30) print('JACOBI-DIAG| Final report:\nDiagonalized matrix:') mlib.matrix_print(mat_op_on, decimal=4) print('Accumulative unitary operator:') mlib.matrix_print(op_accum, decimal=4) return [sym_mat_in, mat_op_on, op_accum]
def op_gen(size, U, irow, icol): # generate one Jacobi operator by reading one SO(2) operator # and one (irow, icol)-pair, where irow >= icol P = mlib.eye(n=size) P[icol][icol] = U[0][0] P[icol][irow] = U[0][1] P[irow][icol] = U[1][0] P[irow][irow] = U[1][1] return P
def ql(mat_in, verbosity = 'silent'): # use householder by default mat_op_on = deepcopy(mat_in) nline = len(mat_op_on) Qt = mlib.eye(nline) for iline in range(nline): mat_op_on_T = mlib.transpose(mat_op_on) vec_in = mat_op_on_T[-iline-1][:(nline-iline)] norm_vec = mlib.mod_of_vec(vec_in) vec_desti = mlib.zeros(n = 1, m = nline-iline)[0][:] vec_desti[-1] = norm_vec reflect_op = hh(vec_in, vec_desti, mode = 'L', verbosity = verbosity) identi_op = mlib.eye(iline) op = mlib.combine_block(reflect_op, identi_op) if verbosity == 'debug': print('QL| vector read-in: {}\nQL| vector reflected to: {}\n'.format(vec_in, vec_desti)) print('QL| Reflection operator:') mlib.matrix_print(reflect_op, decimal = 4) print('QL| Integrated Householder operator:') mlib.matrix_print(op, decimal = 4) print('QL| Present matrix before operation:') mlib.matrix_print(mat_op_on, decimal = 4) Qt = mlib.dot(op, Qt) mat_op_on = mlib.dot(op, mat_op_on) if verbosity == 'debug': print('Present matrix after operation:') mlib.matrix_print(mat_op_on, decimal = 4) Q = mlib.transpose(Qt) return [Q, mat_op_on]
def householder(vec_in, vec_desti=[], mode='reduce', verbosity='silent'): """ Householder algorithm\n Householder build a reflection operator that can transform one vector to one wanted direction\n # input requirement\n vec_in: one vector that want to reflect, SHOULD BE INPUT AS <|, 1d list\n vec_desti: one vector whose direction will vec_in be reflected onto, if not given explicitly, program will quit unless KEYWORD mode is set to 'reduce'\n mode: 'reduce' or anything else. 'reduce' mode means to reflect vec_in to direction along x-axis, i.e., [1, 0, 0, ...]\n # output description\n Householder operator P """ len_vec = len(vec_in) mod_vec_in = mlib.mod_of_vec(vec_in) if mode == 'reduce': vec_desti = mlib.zeros(n=1, m=len_vec)[0][:] vec_desti[0] = mod_vec_in u = mlib.minus(vec_in, vec_desti) mod_u = mlib.mod_of_vec(u) v = mlib.zeros(n=1, m=len_vec)[0][:] for i in range(len(u)): if (u[i] == 0) and (mod_u == 0): v[i] = 1.0 # normalize manually else: v[i] = u[i] / mod_u # v = [iterm/mod_u for iterm in u] I = mlib.eye(len_vec) vvT = mlib.ketbra(v, v, mode='2bra', amplify=2) P = mlib.minus(I, vvT) if verbosity == 'debug': print('HOUSEHOLDER| Comprehensive report\n' + 'input check:\n' + 'vector = {}\n'.format(vec_in) + 'destination = {}\n'.format(vec_desti) + 'norm vector (original) = {}\n'.format(u) + 'normalized norm vector = {}'.format(v)) print('2|v><v| tensor product =') mlib.matrix_print(vvT) print('identity operator generated:\n{}\n'.format(I) + 'Householder operator:\n{}'.format(P)) return P
def tri_diag(mat_in, verbosity='silent'): """ Tri-diagonalization (Givens method)\n # input requirement\n mat_in: symmetrical, real matrix, FLOAT data type\n # output description\n [tdiag, U]\n tdiag: tri-diagonalized matrix\n U: unitary operator (accumulative Givens operator, in present context)\n # formulation\n see LATEX formatted text at the end of present function and\n mat_in = U·tdiag·U', where U' denotes transpose of unitary operator """ nline = len(mat_in) mat_op_on = deepcopy(mat_in) op_accum = mlib.eye(nline) for irow in range(1, nline - 1): for icol in range(irow + 1, nline): s = mat_op_on[irow - 1][icol] / sqrt(mat_op_on[irow - 1][irow]**2 + mat_op_on[irow - 1][icol]**2) if s == 0: c = 1.0 else: c = -(mat_op_on[irow - 1][irow] / mat_op_on[irow - 1][icol]) * s P = [[c, s], [-s, c]] op = op_gen(size=nline, U=P, irow=icol, icol=irow) op_accum = mlib.dot(op_accum, op) mat_op_on = mlib.unitary_transform(U=op, mat=mat_op_on) if verbosity == 'debug': print('\nGivens tri-diagonalization comprehensive report\n' + '-' * 50 + '\nGivens operator (2x2) P({}, {}) print:'.format( irow, icol)) mlib.matrix_print(P, decimal=4) print('Embedded Givens operator print:') mlib.matrix_print(op, decimal=4) print('Matrix print:') mlib.matrix_print(mat_op_on, decimal=4) return [mat_op_on, op_accum]
def ludecomp(mat_in, pivot=False, iexpr=False): """ Lower triangonal-Upper triangonal matrix decomposition, Crout Algorithm\n # input requirement\n mat_in: squared matrix\n pivot (not implemented): for actual use, there may be zero diagonal element that will cause numerical failure, use pivot to swap rows of original matrix, therefore it is possible when zero diagonal element emerges, program will return a result of LU decomposition of row-swapped original matrix, not that of original matrix. If so, program will pop a warning on this unexpected condition\n iexpr: only available if mode == 'recursive', print expression of every element of lower and upper triangonal matrices\n # output description\n [original matrix, Lower triangonal matrix, Upper triangonal matrix] """ # in principle, should check if singular in advance. if mlib.det(mat_in=mat_in) == 0: print( '***error*** Singular matrix! LU decomposition doesn\'t support present matrix!' ) exit() # deepcopy = save as mat_in_bak = deepcopy(mat_in) nline = len(mat_in) L = mlib.eye(nline) U = mlib.zeros(nline) if iexpr: print( '-' * 80 + '\n' + '>> Lower-Upper triangonal matrix decomposition (recursive mode): EXPRESSIONS <<\n' + '-' * 80) for i in range(nline): for j in range(i, nline): term_Aij = 0 expr_str_U = 'U({}, {}) = A({}, {})'.format(i, j, i, j) for k in range(i): term_Aij += L[i][k] * U[k][j] word = ' - L({}, {})*U({}, {})'.format(i, k, k, j) expr_str_U += word mat_in[i][j] -= term_Aij U[i][j] = mat_in[i][j] if iexpr: print(expr_str_U) for j in range(i, nline): if j != i: term_Aji = 0 expr_str_L_off_diag = 'L({}, {}) = [A({}, {})'.format( j, i, j, i) for k in range(i): term_Aji += L[j][k] * U[k][i] word = ' - L({}, {})*U({}, {})'.format(j, k, k, i) expr_str_L_off_diag += word mat_in[j][i] -= term_Aji L[j][i] = mat_in[j][i] / U[i][i] expr_str_L_off_diag = expr_str_L_off_diag + ']/U({}, {})'.format( i, i) if iexpr: print(expr_str_L_off_diag) if iexpr: print( 'Numerical results can be collected by line: [A0, L, U] = ludecomp(A, mode = \'recursive\', iexpr = True)' ) return [mat_in_bak, L, U]
def rayleigh_ritz_diag(mat_in, num_eigen, preconditioner='full', diag_mode='householder', dj_solver='lu', sort_eigval='lowest', batch_size=-1, conv_thr=1E-6, conv_calc_mode='norm1', max_iter=50, verbosity='silent'): """ Subspace diagonalization (Rayleigh-Ritz subspace method)\n # input requirement\n mat_in: must be SQUARED matrix and in FLOAT data type\n num_eigen: number of eigen vectors and values want to find\n preconditioner: preconditioner of residual vector, avaliable options: 'full', 'single', 'dj' or 'none'.\n >For 'full' mode (recommended, most stable), (D_A - theta_i*I)|t_i> = |r_i>\n >, where DA is diagonal matrix that only has non-zero element on its diagonal, D_A[i][i] = A[i][i]\n >For 'single' mode (simple but always cannot converge), (A[i][i] - theta_i)|t_i> = |r_i>\n >For 'dj' (Davidson-Jacobi) mode (accurate but singluarity-unstable):\n > (I-|y_i><y_i|)(D_A - theta_i*I)(I-|y_i><y_i|)|t_i> = |r_i>\n > |t_i> will be solved by LU-decomposition and forward/back substitution method, relatively time-costly\n >For 'none' mode, preconditioner won't be used, i.e.: |t_i> = |r_i>\n diag_mode: 'jacobi', 'householder' or 'np' (numpy integrated). Basic algorithm for diagonalize matrix in subspace\n dj_solver: 'lu' or 'np', the most two fast algorithm for solving linear equation\n >For 'lu', use LU-decompsition and forward/backsubstitution\n >For 'np', use numpy.linalg.solve function\n batch_size: total number of dimensions of subspace, only will be read-in if mode is set to 'batch'\n sort_eigval: 'lowest', 'highest' or 'None'\n >>For 'lowest', sort eigenvalues in an increasing order\n >>For 'highest', sort eigenvalues in an decreasing order\n >>For 'None', will not sort eigenvalues\n conv_thr: convergence threshold of eigen values for 'batch' mode, has no effect in other modes\n conv_calc_mode: 'abs', 'sqr', 'sum', 'norm1' or 'norm2', for measuring lists of eigenvalues of adjacent two iteration steps\n >>For 'abs', use absolute value of difference between element in old list and corresponding one in the new list\n >>For 'sqr', use squared value of ...\n >>For 'sum', just sum up all differences\n >>For 'norm1', use norm of difference between two lists that treated as vectors\n >>For 'norm2', only measure difference between norms of vectorized old and new list\n max_iter: maximum number of iterations for 'batch' mode, has no effect in other modes\n # output description\n [eigval_list, eigvec_list]\n eigval_list: list of eigenvalues\n eigvec_list: list of eigenvectors, arranged according to eigval_list\n """ dim = len(mat_in) mat_op_on = deepcopy(mat_in) I = np.eye(dim) buffer_state = 'YES' if batch_size < num_eigen: batch_size = num_eigen buffer_state = 'NO' U = mlib.eye(n=dim, m=batch_size) eigval_list0 = mlib.zeros(n=1, m=num_eigen)[0][:] eigval_list = mlib.zeros(n=1, m=num_eigen)[0][:] eigval_conv = 1 if (verbosity == 'high') or (verbosity == 'debug'): print('Diagonalization in subspace Initialization Information\n' + '-' * 50 + '\n' + 'Preconditioner: {}\n'.format( preconditioner) + 'Number of eigenvalue-vector pairs to find: {}\n'.format( num_eigen) + 'If buffer vectors used for batch mode: {}\n'.format( buffer_state)) istep = 0 while (istep < max_iter) and (eigval_conv > conv_thr): # --------------------subspace generation-------------------- submat = mlib.unitary_transform(U=U, mat=mat_op_on) # ----------------------------------------------------------- # -----------------subspace diagonalization------------------ if diag_mode == 'np': [eigval_list, eigvec_set] = np.linalg.eig(submat) else: [eigval_list, eigvec_set] = hdiag(hmat_in=submat, mode=diag_mode, eigval_format='list', conv_level=8, max_iter=50, verbosity=verbosity) # ----------------------------------------------------------- # ---------subspace eigenvalues and vectors sorting--------- # sort eigenvalues if sort_eigval == 'None': # will not sort eigenvalues... pass elif (sort_eigval == 'lowest') or (sort_eigval == 'highest'): # sort eigenvalues anyway... sort_idx = np.argsort(a=eigval_list, axis=-1) # np.argsort will return a list of indices of elements in list to sort but # in a sorted order, ascendent as default if sort_eigval == 'highest': sort_idx = sort_idx[::-1] # reverse the indices list # ----------------------------------------------------------- # --------------------eigenvalues storage-------------------- templist = [eigval_list[idx] for idx in sort_idx] eigval_list = templist eigval_conv = mlib.list_diff(list_old=eigval_list0, list_new=eigval_list[0:num_eigen], mode=conv_calc_mode) eigval_list0 = eigval_list[0:num_eigen] # ----------------------------------------------------------- # -----------------------preprocessing----------------------- # rearrange of eigenvectors in subspace for idim in range(batch_size): templist = [eigvec_set[idim][idx] for idx in sort_idx] eigvec_set[idim][:] = templist U_new = [] for ivec in range(batch_size): s = eigvec_set[:][ivec] # no. ivec subspace eigenvector, bra y = mlib.dot( U, mlib.bra2ket(s), mode='matket') # no. ivec original space Ritz vector, ket Resi_mat = mlib.minus( mat_op_on, mlib.eye(n=dim, m=dim, amplify=eigval_list[ivec])) r = mlib.dot( Resi_mat, y, mode='matket') # no. ivec residual vector, ket if preconditioner == 'full': t = [] for icompo in range(len(r)): ti = r[icompo][0] / (mat_op_on[icompo][icompo] - eigval_list[ivec]) t.append(ti) # new vector to append to U, bra elif preconditioner == 'single': orig_idx = sort_idx[ivec] t = [ -ri[0] / (mat_op_on[orig_idx][orig_idx] - eigval_list[ivec]) for ri in r ] # bra elif preconditioner == 'dj': r = [[-r[idim][0]] for idim in range(dim)] # (I-|y_i><y_i|)(D_A - theta_i*I)(I-|y_i><y_i|)|t_i> = |r_i> perp_op = mlib.minus( I, mlib.ketbra(ket=y, bra=y, mode='2ket', amplify=1.0)) # preconditioner of full mode full_prcdtnr = mlib.zeros(dim, dim) for idim in range(dim): full_prcdtnr[idim][ idim] = mat_op_on[idim][idim] - eigval_list[ivec] # final assembly dj_op = mlib.unitary_transform(U=perp_op, mat=full_prcdtnr) # solve D|t> = |r> if dj_solver == 'lu': if verbosity == 'high': print( 'RAYLEIGH-RITZ| Start LU-decomposition...\nRAYLEIGH-RITZ| LU-decomposition carried out on matrix:' ) [_, L_dj, U_dj] = lu(mat_in=dj_op) if verbosity == 'high': print('RAYLEIGH-RITZ| Start forwardsubstitution...') y_dj = sbssolv(triang_mat=L_dj, b=mlib.ket2bra(r), mode='lower') if verbosity == 'high': print('RAYLEIGH-RITZ| Start backsubstitution...') t = sbssolv(triang_mat=U_dj, b=y_dj, mode='upper' ) # new vector to append to U, bra elif dj_solver == 'np': t = np.linalg.solve(dj_op, mlib.ket2bra(r)) if verbosity == 'high': print('RAYLEIGH-RITZ| New |t> generated!') elif preconditioner == 'none': t = mlib.ket2bra(r) else: print( 'RAYLEIGH-RITZ| ***WARNING***: preconditioner required is not recognized, use \'none\' instead.' ) t = mlib.ket2bra(r) t = mlib.normalize(t) U_new.append(t) U_new = mlib.transpose(U_new) #[_, U, _] = qr(mat_in = U_new, mode = 'householder', verbosity = verbosity) #[U, _] = np.linalg.qr(U_new) [_, U] = gs(U_new) istep += 1 if verbosity != 'silent': print('RAYLEIGH-RITZ| Step {}: conv = {}, conv_thr = {}'.format( istep, eigval_conv, conv_thr)) return [eigval_list[0:num_eigen], U[:][0:num_eigen]]
def gauss_jordan(mat_in, mode='inversion', b=[], verbosity='silent'): """ Gauss-Jordan inversion\n # input requirement\n mat_in: squared matrix in 2-dimension\n mode: 'inversion' or 'backsubstitution', for the former, will return an inversed mat_in while for the latter, will return semi-inversed mat_in, transformed b-vector\n b: if mode == 'backsubstitution', this keyword must be specified explicitly. However, if mode == 'inversion' and b is given correctly, equation Ax = b will be solved by x = A-1b, x will also be returned.\n # output description\n Has been introduced in section "input description" """ mat_op_on = deepcopy(mat_in) if det(mat_in=mat_op_on): # main text of this function if verbosity == 'debug': print( 'GAU-JOR INV| non-singluar matrix, safe to calculate inverse...' ) nline = len(mat_op_on) record = eye(n=nline) zero_diag = 0 for iline in range(nline): if mat_op_on[iline][iline] == 0: zero_diag += 1 if zero_diag > 0: print( 'GAU-JOR INV| zero diagonal element(s) detected: {}\nPartial pivot method will be used...' .format(zero_diag)) # pivot for irow in range(nline): # OPERATION 1: PIVOT SWAP if mat_op_on[irow][irow] == 0: print( 'GAU-JOR INV| zero diagonal element encounted at row {}, try to swap with other row...' .format(irow)) pivot = 0 row2swap = irow while pivot == 0 and row2swap <= nline: if mat_op_on[row2swap][irow] != 0: pivot = mat_op_on[row2swap][irow] if verbosity == 'debug': print( 'GAU-JOR INV| [OPERATION 1] exchange row {} with row {}' .format(row2swap, irow)) temp = mat_op_on[row2swap][:] mat_op_on[row2swap][:] = mat_op_on[irow][:] mat_op_on[irow][:] = temp # same exchange operates on element matrix... temp = record[row2swap][:] record[row2swap][:] = record[irow][:] record[irow][:] = temp if mode == 'backsubstitution': temp = b[row2swap] b[row2swap] = b[irow] b[irow] = temp else: row2swap += 1 else: # OPERATION 2: NORMALIZE DIAGONAL ELEMENT diag = mat_op_on[irow][irow] for icol in range(nline): if verbosity == 'debug': print( 'GAU-JOR INV| [OPERATION 2] STATUS: normalize row {} with factor {}.' .format(irow, diag)) print('GAU-JOR INV| [OPERATION 2] row {}: {}'.format( irow, mat_op_on[irow][:])) mat_op_on[irow][icol] /= diag if verbosity == 'debug': print('GAU-JOR INV| [OPERATION 2] RESULTANT row: {}'. format(mat_op_on[irow][:])) record[irow][icol] /= diag if mode == 'backsubstitution': b[irow] /= diag # OPERATION 3: ELIMINATE ALL OFF-DIAGONAL ELEMENT TO 0 if mode == 'backsubstitution': line2sub_start = irow else: line2sub_start = 0 for irow2sub in range(line2sub_start, nline): if irow2sub == irow: continue else: factor = mat_op_on[irow2sub][irow] if verbosity == 'debug': print( 'GAU-JOR INV| [OPERATION 3] STATUS: subtracting row {} from row {}' .format(mat_op_on[irow][:], mat_op_on[irow2sub][:])) print( 'GAU-JOR INV| [OPERATION 3] row number: {}, {}' .format(irow, irow2sub)) print('GAU-JOR INV| [OPERATION 3] FACTOR = {}'. format(factor)) for icol in range(nline): mat_op_on[irow2sub][ icol] -= mat_op_on[irow][icol] * factor record[irow2sub][ icol] -= record[irow][icol] * factor if mode == 'backsubstitution': b[irow2sub] -= b[irow] * factor if verbosity == 'debug': print( 'GAU-JOR INV| [OPERATION 3] RESULTANT ROW: {}'. format(mat_op_on[irow2sub][:])) print( 'GAU-JOR INV| [OPERATION 3] RESULTANT MATRIX: {}' .format(mat_op_on)) if mode == 'inversion': if len(b) != nline: return record else: b_in_2d = [[b[i]] for i in range(nline)] x = dot(record, b_in_2d) x_in_1d = [x[i][0] for i in range(nline)] return [record, x_in_1d] elif mode == 'backsubstitution': return [mat_op_on, b] else: print( 'Singular matrix! Not suitable for Gauss-Jordan method to find its inverse, quit.' ) exit()