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 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 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 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 jacobi_diag(mat_in, tri_diag=False, conv_level=3, max_iter=10, verbosity='default'): """ Jacobi diagonalization (sequential SO2-diagonalization, cyclic, iterative)\n # input requirement\n mat_in: real, symmetric, squared matrix\n tri_diag: if set to True, will return a tri-diagonalized matrix, rather than fully diagonalized one\n conv_level: convergence level, an positive integar is wanted. The higher this value is set, the clearer the off-diagonal parts of produced matrix will be\n max_iter: although this algorithm is promised to converge, maximum iteration step is still used to prohibit a high time-consuming but in vain calculation\n verbosity: if set to anything except 'silent', iteration information will be printed on screen. if set to 'debug', huge amount of information will be printed, use with caution!\n # output description\n [diag, U]\n diag: diagonalized matrix\n U: unitary operator\n # formula\n mat_in = U·diag·U', where U' denotes transpose of U """ if not mlib.symm_check(mat=mat_in): raise TypeError if tri_diag: tri_diag_str = '-' * 25 + 'Tri-diagonalization mode' + '-' * 25 else: tri_diag_str = '' conv_thr = 10.0**(-conv_level) sod_log = [] # s.o.d: sum of off-diagonal elements isweep = 0 [_, rawDiag, op] = ijacobi(sym_mat_in=mat_in, tri_diag=tri_diag, skipthr=conv_thr, verbosity=verbosity) op_accum = op sod = _off_diag_abs_sum(mat=rawDiag) conv = sod sod_log.append(sod) if verbosity != 'silent': print('JACOBI-DIAG| Iteration {}:\n'.format(isweep) + ' Sum of all off-diagonal elements S = {}\n'.format( sod) + ' Convergence: {}\n'.format( conv) + ' Convergence threshold: {}\n'.format( conv_thr) + tri_diag_str) while (conv > conv_thr) and (isweep < max_iter): isweep += 1 [_, rawDiag, op] = ijacobi(sym_mat_in=rawDiag, tri_diag=tri_diag, verbosity=verbosity) sod = _off_diag_abs_sum(mat=rawDiag) conv = abs(sod - sod_log[-1]) sod_log.append(sod) op_accum = mlib.dot(op_accum, op) if verbosity != 'silent': print('JACOBI-DIAG| Iteration {}:\n'.format(isweep) + ' Sum of all off-diagonal elements S = {}\n'. format(sod) + ' Convergence: {}\n'. format(conv) + ' Convergence threshold: {}\n'. format(conv_thr) + tri_diag_str) if (conv > conv_thr) and (isweep == max_iter): print('JACOBI-DIAG| ***WARNING*** Diagonalization non-converged!') return [rawDiag, op_accum]
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()