├── .gitignore ├── README.md ├── code ├── quantum │ ├── __init__.py │ ├── basis_enrichment.py │ ├── dmrg.py │ ├── hamiltonians.py │ ├── tdvp.py │ ├── time_evolution.py │ └── util.py └── tensornetwork │ ├── CMAKELists.txt │ ├── MPO.py │ ├── MPS.py │ ├── __init__.py │ ├── contraction.py │ ├── incrementalqr.cpp │ ├── incrementalqr.py │ ├── linalg.py │ ├── misc.py │ ├── rounding.py │ └── stopping.py ├── environment.yml ├── experiments ├── Fitting_failure.ipynb ├── dmrg2.ipynb └── tdvp_light_cone.ipynb ├── results ├── Figure1_final.ipynb ├── Figure2_final.ipynb ├── Figure3_final.ipynb ├── Figure4_final.ipynb ├── Figure5_final.ipynb └── data │ ├── fig1_final.csv │ ├── fig2_final.csv │ └── fig4_final.csv ├── setup_QR.sh └── util ├── __init__.py ├── benchmarking.py └── plotting.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Python cache directories 2 | __pycache__/ 3 | *.py[cod] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Randomized MPO-MPS Contraction 2 | ![image](https://github.com/user-attachments/assets/a5b459df-9637-47fc-ab68-07b71f9da004) 3 | 4 | 5 | ## A Randomized Algorithm for the Compressed MPS-MPO Product 6 | This repository contains the code and experimental results detailed in the paper [Successive randomized compression: A randomized algorithm for the compressed MPO-MPS product](https://arxiv.org/abs/2504.06475) 7 | --- 8 | 9 | ## **Installation Instructions** 10 | 11 | ### **System Requirements** 12 | This project has been tested with the following dependencies: 13 | 14 | - **LAPACK**: `3.12.0` 15 | - **OpenBLAS**: `0.3.28` 16 | 17 | Ensure these libraries are installed and available in your environment for optimal performance. 18 | 19 | --- 20 | 21 | ### **Environment Setup** 22 | To set up the required Conda environment, use the provided `environment.yml` file. Run the following commands from the project root: 23 | 24 | ```bash 25 | conda env create -f environment.yml 26 | conda activate randomTensor 27 | ``` 28 | 29 | This will create and activate the Conda environment named `tensor2` with all necessary dependencies. 30 | 31 | --- 32 | 33 | ### **(Optional) Optimized Incremental QR Build** 34 | For optimal performance, we provide a custom C++ implementation of the incremental QR decomposition. If you choose not to build it, a Python version written in `scipy` will be used (which is slower). 35 | 36 | #### **Building the Optimized Incremental QR** 37 | With the Conda environment activated, run the following command from the project root: 38 | 39 | ```bash 40 | bash setup_QR.sh 41 | ``` 42 | 43 | #### **Verifying a Successful Build** 44 | After building, you can verify that the optimized C++ implementation is being used by running: 45 | 46 | ```bash 47 | python code/tensornetwork/incrementalqr.py 48 | ``` 49 | 50 | If the build was successful, you should see the following message at the start of the output: 51 | 52 | ``` 53 | Using C++ implementation for incQR 54 | ``` 55 | 56 | If this message does not appear, the build may have failed, and the default Python implementation will be used instead. 57 | 58 | --- 59 | 60 | ### **Notes for Windows Users** 61 | Building the optimized incremental QR decomposition may require additional configuration of `cmake` and a compatible C++ compiler. Ensure you have a properly configured build system before proceeding. 62 | 63 | --- 64 | 65 | ### **Support & Contributions** 66 | If you encounter issues or have suggestions, feel free to open an issue or contribute to the project. 67 | 68 | --- 69 | -------------------------------------------------------------------------------- /code/quantum/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscamano/RandomMPOMPS/275e49b7c435a7c2352cc7e69cc4783327f893fc/code/quantum/__init__.py -------------------------------------------------------------------------------- /code/quantum/basis_enrichment.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | from tensornetwork.linalg import truncated_svd,lq 4 | from tensornetwork.stopping import Cutoff 5 | from tensornetwork.MPS import MPS 6 | import time 7 | from tensornetwork.contraction import * 8 | 9 | def expand(states, cutoff=1e-14): 10 | N = states[0].N 11 | # if len(states) == 1: 12 | # raise RuntimeError ("Only a single state was passed to basis enrichment") 13 | assert all(state.N == N for state in states), "All expansion states must have the same number of sites as the MPS." 14 | 15 | for i in range(len(states)): 16 | if states[i].canform != "Right": 17 | states[i].canonize_right() 18 | 19 | mps_out = [None] * states[0].N 20 | 21 | #============ SVD site N ============ 22 | boundary = np.vstack([state[-1] for state in states]) 23 | chi0 = states[0][-1].shape[0] 24 | L,Qt = lq(boundary) 25 | L11 = L[chi0:,chi0:] 26 | U, S, Vt = truncated_svd(L11, stop=Cutoff(cutoff), abstol=cutoff * np.linalg.norm(L)) 27 | B = np.hstack([L[:,:chi0], 28 | np.vstack([ 29 | np.zeros((chi0,U.shape[1])), 30 | U @ np.diag(S) 31 | ]) ]) 32 | 33 | mps_out[-1] = np.vstack((Qt[:chi0,:],Vt @ Qt[chi0:,:])) 34 | 35 | for i in reversed(range(1,N-1)): 36 | d = states[0][i].shape[1] 37 | left_dimension = sum([state[i].shape[0] for state in states]) 38 | left_idx = 0 39 | right_idx = 0 40 | Bnew = np.zeros((left_dimension*d, B.shape[1]),states[0][0].dtype) 41 | for state in states: #Avoids redudundant zero multiplications 42 | reshaped_state = np.reshape(state[i], (state[i].shape[0]*state[i].shape[1], -1)) 43 | Bnew[right_idx:right_idx+reshaped_state.shape[0],:] = reshaped_state @ B[left_idx:left_idx+reshaped_state.shape[1],:] 44 | left_idx += reshaped_state.shape[1] 45 | right_idx += reshaped_state.shape[0] 46 | 47 | idx = 0 48 | blocks = [] 49 | for state in states: 50 | dim = state[i].shape[0] * state[i].shape[1] 51 | block = Bnew[idx:idx+dim,:] 52 | blocks.append(block.reshape(state[i].shape[0],d,Bnew.shape[-1])) 53 | idx += dim 54 | 55 | B = np.concatenate(blocks, axis=0) 56 | B = np.reshape(B, (B.shape[0], B.shape[1]*B.shape[2])) 57 | 58 | L, Qt = lq(B) 59 | chi0 = states[0][i].shape[0] 60 | L11 = L[chi0:,chi0:] 61 | U, S, Vt = truncated_svd(L11, stop=Cutoff(cutoff), abstol=cutoff * np.linalg.norm(L)) 62 | B = np.hstack([L[:,:chi0], 63 | np.vstack([ 64 | np.zeros((chi0,U.shape[1])), 65 | U @ np.diag(S) 66 | ]) ]) 67 | mps_out[i] = np.vstack((Qt[:chi0,:],Vt @ Qt[chi0:,:])) 68 | mps_out[i] = mps_out[i].reshape(mps_out[i].shape[0],d,-1) 69 | 70 | mps_out[0] = states[0][0] @ B[:states[0][0].shape[1],:] 71 | return MPS(mps_out) 72 | 73 | def MPS_krylov(mps, mpo, basis_size=3, stop=Cutoff(1e-14), algorithm_name='random', bond_dim=None, sketch_increment=5, sketch_dim=5, fit_sweeps=1, baseline_basis=None): 74 | basis = [mps] 75 | k_vec = mps 76 | total_time = 0 77 | 78 | if algorithm_name == 'naive': 79 | for t in range(basis_size-1): 80 | start = time.time() 81 | k_vec = mps_mpo_blas(k_vec, mpo, stop=stop, round_type="dass_blas") 82 | total_time += time.time() - start 83 | k_vec.normalize() 84 | basis.append(k_vec) 85 | 86 | elif algorithm_name == 'rand_then_orth': 87 | for t in range(basis_size-1): 88 | start = time.time() 89 | k_vec = mps_mpo_blas(k_vec, mpo, stop=stop, round_type="rand_then_orth_blas", r=bond_dim) 90 | total_time += time.time() - start 91 | k_vec.normalize() 92 | basis.append(k_vec) 93 | 94 | elif algorithm_name == 'Nyst': 95 | for t in range(basis_size-1): 96 | start = time.time() 97 | k_vec = mps_mpo_blas(k_vec, mpo, stop=stop, round_type="nystrom_blas", l=bond_dim) 98 | total_time += time.time() - start 99 | k_vec.normalize() 100 | basis.append(k_vec) 101 | 102 | elif algorithm_name == 'random': 103 | for t in range(basis_size-1): 104 | start = time.time() 105 | k_vec = random_contraction_inc(mpo, k_vec, stop=stop, accuracychecks=False, 106 | finalround=None, sketchincrement=sketch_increment, sketchdim=sketch_dim) 107 | total_time += time.time() - start 108 | k_vec.normalize() 109 | basis.append(k_vec) 110 | 111 | elif algorithm_name == 'density': 112 | for t in range(basis_size-1): 113 | start = time.time() 114 | k_vec = density_matrix(mpo, k_vec, stop=stop) 115 | total_time += time.time() - start 116 | k_vec.normalize() 117 | basis.append(k_vec) 118 | 119 | elif algorithm_name == 'zipup': 120 | for t in range(basis_size-1): 121 | start = time.time() 122 | k_vec = zipup(mpo, k_vec, stop=stop, finalround=None) 123 | total_time += time.time() - start 124 | k_vec.normalize() 125 | basis.append(k_vec) 126 | 127 | elif algorithm_name == 'fit': 128 | sweeps = fit_sweeps 129 | #while True: 130 | # basis = [] 131 | # k_vec = mps 132 | # basis.append(k_vec) 133 | # total_time = 0 134 | for t in range(basis_size-1): 135 | start = time.time() 136 | k_vec = fit(mpo, k_vec, max_sweeps=sweeps, stop=stop) 137 | total_time += time.time() - start 138 | k_vec.normalize() 139 | basis.append(k_vec) 140 | 141 | # # Adaptive Fitting dropout 142 | # relative_errors = [] 143 | # for k_vec, baseline_vec in zip(basis, baseline_basis): 144 | # error = (baseline_vec - k_vec).norm() / baseline_vec.norm() 145 | # relative_errors.append(error) 146 | # avg_relative_error = sum(relative_errors) / len(relative_errors) 147 | # print(f"Fit stats: Avg Relative Error: {avg_relative_error:.4f}, Sweeps: {sweeps}") 148 | # if avg_relative_error <= 5 * avg_relative_error or sweeps == 10: 149 | # break 150 | # else: 151 | # sweeps += 1 152 | 153 | else: 154 | print("Invalid algorithm choice for", algorithm_name, "review your inputted algorithm names") 155 | return 156 | 157 | return basis, total_time -------------------------------------------------------------------------------- /code/quantum/dmrg.py: -------------------------------------------------------------------------------- 1 | from tensornetwork.stopping import Cutoff 2 | from tensornetwork.linalg import truncated_svd 3 | from tensornetwork.MPS import MPS 4 | import scipy 5 | from tqdm import tqdm 6 | import numpy as np 7 | from scipy.linalg import eigh 8 | 9 | def dmrg2(mpo,mps, sweeps=10, stop=Cutoff(1e-14), maxit=10,eig_tol=1e-8,eigensolver="Lan"): 10 | 11 | """ 12 | Einsum Indexing convention L->R: 13 | ______ ______ 14 | | | _______ | | 15 | | |-Z-|mps_c_j|-W-| | 16 | | | |_______| | | 17 | | | __|d___ | | 18 | | L[j] |-D-| mpo_j |-E-|R[N-j]| 19 | | | |_______| | | 20 | | | __|l___ | | 21 | | |-X-| mps_j |-Y-| | 22 | |______| |_______| |______| 23 | """ 24 | 25 | def compute_right_envs(mps, mpo): 26 | N = len(mps) # Assuming mps is a list or array-like object 27 | R = [None] * N 28 | 29 | # -------- Right environments -------- 30 | R[-1] = np.einsum("Ddl,xd->xDl", mpo[-1], np.conj(mps[-1])) 31 | R[-1] = np.einsum("xDl,Xl->xDX", R[-1], mps[-1]) 32 | 33 | for i in range(N-2,1,-1): 34 | R[i] = np.einsum("ydx,xDX->ydDX", np.conj(mps[i]), R[i+1]) 35 | R[i] = np.einsum("EdDl,ydDX->yElX", mpo[i], R[i]) 36 | R[i] = np.einsum("YlX,yElX->yEY", mps[i], R[i]) 37 | 38 | return R 39 | 40 | mps.canonize_left() 41 | LR = compute_right_envs(mps, mpo) 42 | Es = [] 43 | 44 | # H_eff = None 45 | approx_solution = mps.copy() 46 | 47 | def eigensolve(H_eff,psi,*args): 48 | # if eigensolver == "srr": 49 | # psi =psi.reshape(-1) 50 | # num_krylov = H_eff.shape[0] 51 | # tol = 1e-2 #literally all of them 52 | # partial = H_eff.shape[0] 53 | # stabilize = 150 54 | # V_srr, D_srr, X_srr = sRR(H_eff, num_krylov, tol, partial,b=psi, stabilize=stabilize) 55 | 56 | # return D_srr[-1],V_srr[-1] 57 | if eigensolver == "Lan": 58 | energy, psivec = scipy.sparse.linalg.eigsh(H_eff,k=1, which='SA', maxiter=maxit, v0=psi.reshape(-1),tol=eig_tol) 59 | return energy, psivec 60 | 61 | elif eigensolver == "Dense": 62 | evals, evecs = eigh(H_eff) 63 | energy = evals[0] 64 | psivec = evecs[:,0] 65 | return energy, psivec 66 | 67 | def sweep_scheduler(n, sweeps): 68 | sequence = np.concatenate([np.arange(n-1), np.arange(n-3, 0, -1)]) 69 | repeated_sequence = np.tile(sequence, sweeps) 70 | repeated_sequence = np.append(repeated_sequence, 0) 71 | return list(repeated_sequence) 72 | 73 | for k in tqdm(sweep_scheduler(mps.N,sweeps)): #Using 1 indexing 74 | #================ First Site ================ 75 | if k == 0: 76 | H_eff = np.einsum("DkEj,yEY->ykDjY",mpo[1],LR[2]) 77 | H_eff = np.einsum("dDl,ykDjY->dkyljY",mpo[0],H_eff) 78 | H_eff = H_eff.reshape(H_eff.shape[0]*H_eff.shape[1]*H_eff.shape[2], -1)# (ykd,ljy) 79 | 80 | psi = np.einsum("lr,rjY->ljY",approx_solution[0],approx_solution[1]) # (l,j,Y) 81 | 82 | energy, psivec = eigensolve(H_eff,psi,eigensolver) 83 | Es.append(energy) 84 | 85 | U, S, Vt = truncated_svd(psivec.reshape(psi.shape[0],psi.shape[1]*psi.shape[2]), stop=stop) 86 | S = np.diag(S)/np.linalg.norm(np.diag(S)) 87 | approx_solution[0] = U # l x r 88 | approx_solution[1] = (S @ Vt).reshape(Vt.shape[0],psi.shape[1],psi.shape[2]) #(r,j,Y) 89 | # approx_solution.show() 90 | 91 | LR[0] = np.einsum("dx,dDl->xDl", np.conj(U), mpo[0]) 92 | LR[0] = np.einsum("xDl,lX->xDX", LR[0], U) 93 | 94 | left_sweep = True 95 | 96 | #================ Middle Sites ================ 97 | elif 0 < k and k < mps.N-2: 98 | H_eff = np.einsum("xDX,DdEl->xdElX",LR[k-1],mpo[k]) 99 | H_eff = np.einsum("xdElX,EkFj->xdkFjlX",H_eff,mpo[k+1]) 100 | H_eff = np.einsum("xdkFjlX,yFY->xdkyXljY",H_eff,LR[k+2]) 101 | H_eff = H_eff.reshape(H_eff.shape[0]*H_eff.shape[1]*H_eff.shape[2]*H_eff.shape[3],-1) 102 | 103 | psi = np.einsum("Xlr,rjY->XljY",approx_solution[k],approx_solution[k+1]) 104 | energy, psivec = eigensolve(H_eff,psi,eigensolver) 105 | Es.append(energy) 106 | 107 | U, S, Vt = truncated_svd(psivec.reshape(psi.shape[0] * psi.shape[1], psi.shape[2] * psi.shape[3]), stop=stop) 108 | S = np.diag(S)/np.linalg.norm(np.diag(S)) 109 | 110 | if left_sweep: 111 | approx_solution[k] = U.reshape(psi.shape[0], mpo[k].shape[1], U.shape[1]) 112 | approx_solution[k+1] = (S @ Vt).reshape(Vt.shape[0],psi.shape[2],psi.shape[3]) 113 | 114 | LR[k] = np.einsum("xDX,xdz->zdDX", LR[k - 1], np.conj(approx_solution[k])) 115 | LR[k] = np.einsum("zdDX,DdEl->zElX", LR[k], mpo[k]) 116 | LR[k] = np.einsum("zElX,XlZ->zEZ", LR[k], approx_solution[k]) 117 | else: 118 | approx_solution[k] = (U @ S).reshape(psi.shape[0], mpo[k].shape[1], U.shape[1]) 119 | approx_solution[k+1] = Vt.reshape(Vt.shape[0],psi.shape[2],psi.shape[3]) 120 | 121 | LR[k+1] = np.einsum("yEY,zdy->zdEY", LR[k+2], np.conj(approx_solution[k+1])) 122 | LR[k+1] = np.einsum("zdEY,DdEl->zDlY", LR[k+1], mpo[k+1]) 123 | LR[k+1] = np.einsum("zDlY,ZlY->zDZ", LR[k+1], approx_solution[k+1]) 124 | # approx_solution.show() 125 | 126 | 127 | #================ Last Site ================ Index = N -1 and N 128 | else: # k == mps.N-2 129 | H_eff = np.einsum("xDX,DdEl->xdElX",LR[-3],mpo[-2]) 130 | H_eff = np.einsum("xdElX,Ekj->xdkXlj",H_eff,mpo[-1]) 131 | H_eff= H_eff.reshape(H_eff.shape[0]*H_eff.shape[1]*H_eff.shape[2],-1) 132 | 133 | psi = np.einsum("Xlr,rj->Xlj",approx_solution[-2],approx_solution[-1]) 134 | energy, psivec = eigensolve(H_eff,psi,eigensolver) 135 | Es.append(energy) 136 | 137 | U, S, Vt = truncated_svd(psivec.reshape(psi.shape[0] * psi.shape[1], psi.shape[2]), stop=stop) 138 | S = np.diag(S)/np.linalg.norm(np.diag(S)) 139 | approx_solution[-2] = (U @ S).reshape(psi.shape[0], approx_solution[-2].shape[1], -1) 140 | approx_solution[-1] = Vt.reshape(-1, approx_solution[-1].shape[1]) 141 | # approx_solution.show() 142 | 143 | LR[-1] = np.einsum("Ddl,yd->yDl", mpo[-1], np.conj(approx_solution[-1])) 144 | LR[-1] = np.einsum("yDl,Yl->yDY", LR[-1], approx_solution[-1]) 145 | 146 | left_sweep = False 147 | 148 | approx_solution[0] = U @ S 149 | approx_solution[1] = Vt.reshape(Vt.shape[0],psi.shape[1],psi.shape[2]) 150 | mps = MPS(approx_solution) 151 | mps.canform="Left" 152 | 153 | return mps, Es 154 | 155 | -------------------------------------------------------------------------------- /code/quantum/hamiltonians.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) 4 | sys.path.append(parent_dir) 5 | 6 | import numpy as np 7 | import numpy.linalg as la 8 | import scipy 9 | from tensornetwork.MPO import MPO 10 | from tensornetwork.MPS import MPS 11 | import time 12 | from tensornetwork.contraction import * 13 | from tensornetwork.stopping import * 14 | from tqdm import tqdm 15 | import matplotlib.pyplot as plt 16 | import itertools 17 | 18 | seed_value = 42 19 | 20 | np.random.seed(seed_value) 21 | 22 | I2 = np.eye(2) 23 | #pauli matrices 24 | X = np.array([[0,1.],[1,0]]) 25 | Y = np.array([[0,-1.j],[1j,0]]) 26 | Z = np.array([[1, 0], [0, -1]]) 27 | 28 | # spin-1/2 operators (bottom-top) 29 | ## σ^z_l 30 | sz = np.zeros((2,2)) 31 | sz[0,0] = 1/2 32 | sz[1,1] = -1/2 33 | ## σ^+_l 34 | sp = np.zeros((2,2)) 35 | sp[0,1] = 1 36 | ## σ^-_l 37 | sm = np.zeros((2,2)) 38 | sm[1,0] = 1 39 | 40 | # spin-1 operators (bottom-top) 41 | ## S^z_l 42 | Sz = np.zeros((3,3)) 43 | Sz[0,0] = 1 44 | Sz[2,2] = -1 45 | ## S^+_l 46 | Sp = np.zeros((3,3)) 47 | Sp[0,1] = np.sqrt(2) 48 | Sp[1,2] = np.sqrt(2) 49 | ## S^-_l 50 | Sm = np.zeros((3,3)) 51 | Sm[1,0] = np.sqrt(2) 52 | Sm[2,1] = np.sqrt(2) 53 | ## I_l 54 | I3 = np.eye(3) 55 | 56 | def mpo_from_fsm(graph, k, n, source = -1, target = 0): 57 | assert(len(graph) > 0) 58 | d = graph[list(graph.keys())[0]].shape[0] 59 | 60 | A = np.zeros((k,d,k,d), dtype=complex) 61 | for j, i in graph.keys(): 62 | A[i,:,j,:] = graph[(j,i)] 63 | 64 | 65 | return [A[target,:,:,:]] + (n-2)*[np.copy(A)] + [A[:,:,source,:]] 66 | 67 | 68 | #$H_{\mathrm{TC}}=-J \sum_v A_v-J \sum_p B_p, \quad J>0$. 69 | def toric_code(N): 70 | pass 71 | 72 | #======================== ALKT model ====================================== 73 | #(https://doi.org/10.1103/PhysRevLett.59.799) 74 | def ALKT(N,local_ops=False): 75 | # MPO Hamiltonian (left-bottom-right-top) 76 | ## H[l] 77 | Hl = np.zeros((14,3,14,3)) 78 | Hl[0,:,0,:] = I3 79 | Hl[1,:,0,:] = Sz 80 | Hl[2,:,0,:] = Sm 81 | Hl[3,:,0,:] = Sp 82 | Hl[4,:,0,:] = np.matmul(Sz,Sz) 83 | Hl[5,:,0,:] = np.matmul(Sz,Sm) 84 | Hl[6,:,0,:] = np.matmul(Sz,Sp) 85 | Hl[7,:,0,:] = np.matmul(Sm,Sz) 86 | Hl[8,:,0,:] = np.matmul(Sm,Sm) 87 | Hl[9,:,0,:] = np.matmul(Sm,Sp) 88 | Hl[10,:,0,:] = np.matmul(Sp,Sz) 89 | Hl[11,:,0,:] = np.matmul(Sp,Sm) 90 | Hl[12,:,0,:] = np.matmul(Sp,Sp) 91 | Hl[13,:,1,:] = Sz 92 | Hl[13,:,2,:] = 0.5*Sp 93 | Hl[13,:,3,:] = 0.5*Sm 94 | Hl[13,:,4,:] = 1/3*np.matmul(Sz,Sz) 95 | Hl[13,:,5,:] = 1/6*np.matmul(Sz,Sp) 96 | Hl[13,:,6,:] = 1/6*np.matmul(Sz,Sm) 97 | Hl[13,:,7,:] = 1/6*np.matmul(Sp,Sz) 98 | Hl[13,:,8,:] = 1/12*np.matmul(Sp,Sp) 99 | Hl[13,:,9,:] = 1/12*np.matmul(Sp,Sm) 100 | Hl[13,:,10,:] = 1/6*np.matmul(Sm,Sz) 101 | Hl[13,:,11,:] = 1/12*np.matmul(Sm,Sp) 102 | Hl[13,:,12,:] = 1/12*np.matmul(Sm,Sm) 103 | Hl[13,:,13,:] = I3 104 | ## H 105 | H = [Hl for l in range(N)] 106 | H[0] = Hl[-1:np.shape(Hl)[0],:,:,:] 107 | H[0]=H[0].reshape(H[0].shape[1],H[0].shape[2],H[0].shape[3]) 108 | 109 | H[N-1] = Hl[:,:,0:1,:] 110 | H[N-1]= H[N-1].reshape(H[N-1].shape[0],H[N-1].shape[1],H[N-1].shape[3]) 111 | return MPO(H) 112 | 113 | #======================== Majumdar-Ghosh model ====================================== 114 | # (https://doi.org/10.1063/1.1664979) 115 | 116 | def Madjumdar_Gosh(N,local_ops=False): 117 | Hl = np.zeros((8,2,8,2)) 118 | Hl[0,:,0,:] = I2 119 | Hl[1,:,0,:] = sz 120 | Hl[2,:,0,:] = sm 121 | Hl[3,:,0,:] = sp 122 | Hl[4,:,1,:] = I2 123 | Hl[5,:,2,:] = I2 124 | Hl[6,:,3,:] = I2 125 | Hl[7,:,1,:] = sz 126 | Hl[7,:,2,:] = 0.5*sp 127 | Hl[7,:,3,:] = 0.5*sm 128 | Hl[7,:,4,:] = 0.5*sz 129 | Hl[7,:,5,:] = 1/4*sp 130 | Hl[7,:,6,:] = 1/4*sm 131 | Hl[7,:,7,:] = I2 132 | ## H 133 | H = [Hl for l in range(N)] 134 | H[0] = Hl[-1:np.shape(Hl)[0],:,:,:] 135 | H[0]=H[0].reshape(H[0].shape[1],H[0].shape[2],H[0].shape[3]) 136 | 137 | H[N-1] = Hl[:,:,0:1,:] 138 | H[N-1]= H[N-1].reshape(H[N-1].shape[0],H[N-1].shape[1],H[N-1].shape[3]) 139 | 140 | if local_ops: 141 | return H 142 | 143 | return MPO(H) 144 | 145 | 146 | #======================== Spin-1 Heisenberg model with Zeeman term ====================================== 147 | 148 | def Heis_zeeman(N,J=1,h=1,local_ops=False): 149 | Hl = np.zeros((5,3,5,3)) 150 | Hl[0,:,0,:] = I3 151 | Hl[1,:,0,:] = Sz 152 | Hl[2,:,0,:] = Sm 153 | Hl[3,:,0,:] = Sp 154 | Hl[4,:,0,:] = -h*Sz 155 | Hl[4,:,1,:] = J*Sz 156 | Hl[4,:,2,:] = J/2*Sp 157 | Hl[4,:,3,:] = J/2*Sm 158 | Hl[4,:,4,:] = I3 159 | ## H 160 | H = [Hl for l in range(N)] 161 | H[0] = Hl[-1:np.shape(Hl)[0],:,:,:] 162 | H[0]=H[0].reshape(H[0].shape[1],H[0].shape[2],H[0].shape[3]) 163 | 164 | H[N-1] = Hl[:,:,0:1,:] 165 | H[N-1]= H[N-1].reshape(H[N-1].shape[0],H[N-1].shape[1],H[N-1].shape[3]) 166 | 167 | if local_ops: 168 | return H 169 | 170 | return MPO(H) 171 | 172 | 173 | #======================== XY model ====================================== 174 | 175 | # def XY_model(N,Jx=1,Jy=1,local_ops=False): 176 | # Hl = np.zeros((4,2,4,2)) 177 | # Hl[0,:,0,:] = I2 178 | # Hl[1,:,0,:] = sm 179 | # Hl[2,:,0,:] = sp 180 | # Hl[3,:,1,:] = -0.5*sp 181 | # Hl[3,:,2,:] = -0.5*sm 182 | # Hl[3,:,3,:] = I2 183 | 184 | # H = [Hl for l in range(N)] 185 | # H[0] = Hl[-1:np.shape(Hl)[0],:,:,:] 186 | # H[0]=H[0].reshape(H[0].shape[1],H[0].shape[2],H[0].shape[3]) 187 | # H[N-1] = Hl[:,:,0:1,:] 188 | # H[N-1]= H[N-1].reshape(H[N-1].shape[0],H[N-1].shape[1],H[N-1].shape[3]) 189 | 190 | # if local_ops: 191 | # local_ops = [] 192 | # local_H_template = np.zeros((2, 2, 2, 2)) 193 | # for _ in range(N-1): 194 | # H_local = local_H_template.copy() 195 | # H_local += np.reshape(-Jx*np.kron(sm, sm.T) - Jy*np.kron(sp, sp.T), (2, 2, 2, 2)) 196 | # local_ops.append(H_local) 197 | 198 | # return local_ops 199 | # return MPO(H) 200 | 201 | #======================== Isotropic Heisenberg/ X X X ====================================== 202 | 203 | def isotropic_heisenberg(N,Jx,Jy,Jz,local_ops=False): 204 | H = [np.reshape(-Jx*np.kron(X, X) - Jy*np.kron(Y, Y) - Jz*np.kron(Z, Z), (2, 2, 2, 2)) for _ in range(N-1)] 205 | if local_ops: 206 | return H 207 | return MPO(H) 208 | 209 | #======================== Transverse Field Ising Model ====================================== 210 | 211 | def transverse_ising(N, J=1, h=1, local_ops=False): 212 | fsm = { (0,0): I2, 213 | (0,1): -J*X, 214 | (1,2): X, 215 | (0,2): -h*Z, 216 | (2,2): I2 217 | } 218 | H =mpo_from_fsm(fsm,3,N,source=0,target=2) 219 | if local_ops: 220 | return H 221 | return MPO(H) 222 | 223 | #======================== Heisenberg Hamiltonian with next and next next interactions ====================================== 224 | #(FSM representation) 225 | def heisenberg(N,variant="heis",Jx=1,Jy=1,Jz=1,alpha=1,beta=1.1,gamma=1.2,local_ops=False): 226 | X = np.array([[0,1],[1,0]]) 227 | Y = np.array([[0,-1j],[1j,0]]) 228 | Z =np.array([[1,0],[0,-1]]) 229 | 230 | if variant=="heis": 231 | graph_heis = { 232 | (0, 0): np.eye(2), 233 | (0, 1): Jx * X, 234 | (0, 2): Jy * Y, 235 | (0, 3): Jz * Z, 236 | (1, 4): Jx * X, 237 | (2, 4): Jy * Y, 238 | (3, 4): Jz * Z, 239 | (4, 4): np.eye(2), 240 | } 241 | graph_max = 4 242 | H = mpo_from_fsm(graph_heis, graph_max+1, N, source=0, target=graph_max) 243 | if local_ops: 244 | return H 245 | return MPO(H) 246 | 247 | elif variant =="heis_next": 248 | graph_neighbors={ 249 | (0,0) : np.eye(2), 250 | (0,1) : X , 251 | (0,2) : Y, 252 | (0,3) : Z, 253 | (1,7) : alpha * X, 254 | (2,7) : alpha * Y, 255 | (3,7) : alpha * Z, 256 | (1,4) : beta * np.eye(2), 257 | (2,5) : beta * np.eye(2), 258 | (3,6) : beta * np.eye(2), 259 | (4,7) : X , 260 | (5,7) : Y, 261 | (6,7) : Z, 262 | (7,7) : np.eye(2), 263 | } 264 | graph_max = 7 265 | H = mpo_from_fsm(graph_neighbors, graph_max+1, N, source=0, target=graph_max) 266 | 267 | if local_ops: 268 | return H 269 | 270 | return MPO(H) 271 | 272 | elif variant== "heis_next_next": 273 | graph_neighbors_neighbors={ 274 | (0,0) : np.eye(2), 275 | 276 | (0,1) : X , 277 | (0,2) : Y, 278 | (0,3) : Z, 279 | 280 | (1,10): alpha * X, 281 | (2,10): alpha * Y, 282 | (3,10): alpha * Z, 283 | 284 | (1,4): np.eye(2), 285 | (2,5): np.eye(2) , 286 | (3,6): np.eye(2) , 287 | 288 | (4,7): gamma * np.eye(2), 289 | (5,8): gamma * np.eye(2) , 290 | (6,9): gamma * np.eye(2) , 291 | 292 | (7,10): X , 293 | (8,10): Y , 294 | (9,10): Z , 295 | 296 | (4,10): beta * X , 297 | (5,10): beta *Y , 298 | (6,10): beta *Z , 299 | 300 | (10,10) : np.eye(2), 301 | } 302 | graph_max = 10 303 | H = mpo_from_fsm(graph_neighbors_neighbors, graph_max+1, N, source=0, target=graph_max) 304 | if local_ops: 305 | return H 306 | 307 | return MPO(H) 308 | 309 | def esprit(N, interaction, stop=Cutoff(1e-8), small_eig_cutoff=1e-10): 310 | r = stop.outputdim if (not (stop.outputdim is None)) else stop.mindim 311 | r = min(r, (N-1)//2) 312 | while True: 313 | # Find bases 314 | F = np.zeros((N-r, r)) 315 | for i, j in itertools.product(range(N-r), range(r)): 316 | F[i,j] = interaction(i+j+1) 317 | W, _, _, _ = la.lstsq(F[:-1,:], F[1:,:]) 318 | bases, _ = la.eig(W) 319 | bases = np.real(bases) 320 | bases = bases[bases > small_eig_cutoff * la.norm(bases, np.inf)] 321 | 322 | # Find coefficients 323 | F = np.zeros((N-1, len(bases))) 324 | b = np.zeros(N-1) 325 | for i in range(N-1): 326 | for j in range(len(bases)): 327 | F[i,j] = np.power(bases[j],i) 328 | b[i] = interaction(i+1) 329 | coeffs, _, _, _ = la.lstsq(F, b) 330 | 331 | if (not (stop.outputdim is None) and r >= stop.outputdim) or r >= stop.maxdim or r == (N-1)//2: 332 | return bases, coeffs 333 | 334 | error = la.norm(b - F@coeffs, np.inf) / la.norm(b, np.inf) 335 | if error <= stop.cutoff: 336 | return bases, coeffs 337 | 338 | r += 1 339 | 340 | def long_range_tfim(N, h=1.0, interaction=lambda dist: dist**-2, stop=Cutoff(1e-8)): 341 | bases, coeffs = esprit(N, interaction, stop=stop) 342 | fsm = { (0,0): I2, 343 | **{(0,i+1): coeffs[i]*X for i in range(len(bases))}, 344 | **{(i+1,i+1): bases[i]*I2 for i in range(len(bases))}, 345 | **{(i+1,len(bases)+1): X for i in range(len(bases))}, 346 | (0,len(bases)+1): h*Z, 347 | (len(bases)+1,len(bases)+1): I2 } 348 | return MPO(mpo_from_fsm(fsm, len(bases)+2, N, source=0, target=len(bases)+1)) 349 | 350 | def cluster(N,K=1,h=1,local_ops=False): 351 | fsm = { 352 | (0,0): I2, 353 | (0,1): K*X, 354 | (1,2): X, 355 | (2,3): X, 356 | (0,3): h*Z, 357 | (3,3): I2 358 | } 359 | H = mpo_from_fsm(fsm,4,N,source=0,target=3) 360 | if local_ops: 361 | return H 362 | return MPO(H) 363 | 364 | def long_range_XY_model(N,J=1,alpha=1,local_ops=False,stop=Cutoff(1e-10)): 365 | bases, coeffs = esprit(N, lambda dist: dist**-alpha, stop=stop) 366 | r = len(bases) 367 | start = 2*r 368 | stp = 2*r+1 369 | fsm = { (start,start): I2, 370 | **{(start,i): J/2*coeffs[i]*X for i in range(r)}, 371 | **{(i,i): bases[i]*I2 for i in range(r)}, 372 | **{(i,stp): X for i in range(r)}, 373 | **{(start,i+r): J/2*coeffs[i]*Y for i in range(r)}, 374 | **{(i+r,i+r): bases[i]*I2 for i in range(r)}, 375 | **{(i+r,stp): Y for i in range(r)}, 376 | (stp,stp): I2 } 377 | H = mpo_from_fsm(fsm,2*r+2,N,source=start,target=stp) 378 | if local_ops: 379 | return H 380 | return MPO(H) -------------------------------------------------------------------------------- /code/quantum/tdvp.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | from tqdm import tqdm 4 | from tensornetwork.linalg import lq, lan_exp 5 | from scipy.linalg import expm 6 | 7 | def tdvp_implicit(mps, mpo,dt,sweeps,krylov_depth = 10,verbose=False,use_lanczos=True,measure=True): 8 | 9 | def compute_right_envs(mps, mpo): 10 | N = len(mps) # Assuming mps is a list or array-like object 11 | R = [None] * N 12 | 13 | # -------- Right environments -------- 14 | # R[-1] = np.einsum("Ddl,xd->xDl", mpo[-1], np.conj(mps[-1])) 15 | mpo_transposed = mpo[-1].transpose(0,2,1) #Transpose to (Dld) 16 | product = mpo_transposed.reshape(-1,mpo_transposed.shape[-1]) @ (np.conj(mps[-1])).T #Resulting shape (Dl,x) 17 | product = product.reshape(mpo[-1].shape[0],mpo[-1].shape[2],mps[-1].shape[0]) #Reshape to (D,l,x) 18 | R[-1] = product.transpose(2,0,1) 19 | 20 | # R[-1] = np.einsum("xDl,Xl->xDX", R[-1], mps[-1]) 21 | product = R[-1].reshape(-1,R[-1].shape[-1]) @ mps[-1].T #Resulting shape (xD,X) 22 | R[-1] = product.reshape(R[-1].shape[0],R[-1].shape[1],mps[-1].shape[0]) 23 | 24 | for i in range(N-2,0,-1): 25 | #R[i] = np.einsum("ydx,xDX->ydDX", np.conj(mps[i]), R[i+1]) 26 | product = np.conj(mps[i].reshape(-1,mps[i].shape[-1])) @ R[i+1].reshape(R[i+1].shape[0],-1) #Resulting shape (yd,DX) 27 | R[i] = product.reshape(mps[i].shape[0],mps[i].shape[1],R[i+1].shape[1],R[i+1].shape[2]) 28 | 29 | # R[i] = np.einsum("EdDl,ydDX->yElX", mpo[i], R[i]) 30 | mpo_transposed = mpo[i].transpose(0,3,1,2) #Transpose to (E,l,d,D) 31 | R_tranposed = R[i].transpose(1,2,0,3) #Transpose to (d,D,y,X) 32 | product = mpo_transposed.reshape(-1,mpo_transposed.shape[2]*mpo_transposed.shape[3]) @ R_tranposed.reshape(-1,R_tranposed.shape[2]*R_tranposed.shape[3]) #Resulting shape (El,yX) 33 | product = product.reshape(mpo[i].shape[0],mpo[i].shape[3],R[i].shape[0],R[i].shape[-1]) #(E,l,y,X) 34 | R[i] = product.transpose(2,0,1,3) #(y,E,l,X) 35 | 36 | R[i] = np.einsum("YlX,yElX->yEY", mps[i], R[i]) #TODO seems like a strange reshaping bug at line 2 for the case of a product state: ie (x ,1,1,x ->x^2,1 ?) 37 | # R_transposed = R[i].transpose(2,3,0,1) #Transpose to (l,X,y,E) 38 | # R_reshaped = R_transposed.reshape(-1,R_tranposed.shape[2]*R_tranposed.shape[3]) 39 | # product = mps[i].reshape(mps[i].shape[0],-1) @ R_reshaped #Resulting shape (Y,yE) 40 | # product = product.reshape(mps[i].shape[0],R[i].shape[0],R[i].shape[1]) #(Y,y,E)) 41 | # R[i]= product.transpose(1,2,0) # (y,E,Y) 42 | 43 | return R 44 | 45 | def update_A_left(H_eff,approx_solution,sweep_index): #TODO:lingering einsum 46 | 47 | if sweep_index == 0: 48 | approx_solution[0], R = np.linalg.qr(approx_solution[0]) 49 | if H_eff is None: 50 | K = None 51 | else: 52 | # K = np.einsum("dylY,dk->kylY",H_eff,np.conj(approx_solution[0])) 53 | product = H_eff.T.reshape(-1,H_eff.shape[0]) @ np.conj(approx_solution[0]) #Resulting shape (Yly,k) 54 | K = product.T.reshape(product.shape[1],H_eff.shape[1],H_eff.shape[2],H_eff.shape[3]) #Reshape to (k,y,l,Y) 55 | 56 | # K = np.einsum("kylY,lj->kyjY",K,approx_solution[0]) 57 | K_transposed = K.transpose(0,1,3,2) # Reshape to (k,y,Y,l) 58 | product = K_transposed.reshape(-1,K_transposed.shape[-1]) @ approx_solution[0] #Resulting shape (kyY,j) 59 | product = product.reshape(K.shape[0],K.shape[1],K.shape[3],approx_solution[0].shape[-1]) #Reshape to (k,y,Y,j) 60 | K = product.transpose(0,1,3,2) #Transpose to (k,y,j,Y) 61 | else: 62 | Q, R = np.linalg.qr(approx_solution[sweep_index].reshape(-1,approx_solution[sweep_index].shape[-1])) 63 | approx_solution[sweep_index] = Q.reshape(approx_solution[sweep_index].shape[0], approx_solution[sweep_index].shape[1], -1) 64 | if H_eff is None: 65 | K = None 66 | else: 67 | #K = np.einsum("xdyXlY,xdk->kyXlY",H_eff,np.conj(approx_solution[sweep_index])) 68 | H_transposed = H_eff.transpose(2,3,4,5,0,1) #Transpose to (y,X,l,Y,x,d) 69 | product = H_transposed.reshape(-1,H_transposed.shape[-2]*H_transposed.shape[-1]) @ np.conj(approx_solution[sweep_index]).reshape(-1,approx_solution[sweep_index].shape[-1]) #Resulting shape (yXlY,k) 70 | product = product.reshape(H_eff.shape[2],H_eff.shape[3],H_eff.shape[4],H_eff.shape[5],product.shape[1],) #Reshape to (y,X,l,Y,k) 71 | K= product.transpose(4,0,1,2,3) #Tranpose to (k,y,X,l,Y) 72 | 73 | #K = np.einsum("kyXlY,Xlj->kyjY",K,approx_solution[sweep_index]) 74 | K_tranposed = K.transpose(0,1,4,2,3) #Transpose to (k,y,Y,X,l) 75 | product = K_tranposed.reshape(-1,K_tranposed.shape[-2]*K_tranposed.shape[-1]) @ approx_solution[sweep_index].reshape(-1,approx_solution[sweep_index].shape[-1]) #Resulting shape (kyY,j) 76 | product = product.reshape(K.shape[0],K.shape[1],K.shape[-1],approx_solution[sweep_index].shape[-1]) #(k,y,Y,j) 77 | K = product.transpose(0,1,3,2) 78 | 79 | # Update left environment 80 | if sweep_index == 0: 81 | # LR[0] = np.einsum("dx,dDl->xDl", np.conj(approx_solution[0]), mpo[0]) 82 | product = np.conj(approx_solution[0]).T @ mpo[0].reshape(mps[0].shape[0],-1) #resulting shape (x,DL) 83 | LR[0] = product.reshape(product.shape[0],mpo[0].shape[1],mpo[0].shape[2]) 84 | 85 | # LR[0] = np.einsum("xDl,lX->xDX", LR[0], approx_solution[0]) 86 | product = LR[0].reshape(-1,LR[0].shape[-1]) @ approx_solution[0] #resulting shape (xD,X) 87 | LR[0] = product.reshape(LR[0].shape[0],LR[0].shape[1],product.shape[-1]) #reshape to (x,D,X) 88 | else: 89 | # LR[sweep_index] = np.einsum("xDX,xdz->zdDX", LR[sweep_index-1], np.conj(approx_solution[sweep_index])) 90 | LR_tranposed = LR[sweep_index-1].transpose(1,2,0) #transpose to (D,X,x) 91 | product = LR_tranposed.reshape(-1,LR_tranposed.shape[-1]) @ np.conj(approx_solution[sweep_index]).reshape(approx_solution[sweep_index].shape[0],-1) #resulting shape (DX,dz) 92 | product = product.reshape(LR[sweep_index-1].shape[-2],LR[sweep_index-1].shape[-1],approx_solution[sweep_index].shape[1],approx_solution[sweep_index].shape[2]) #reshape to (D,X,d,z) 93 | LR[sweep_index]= product.transpose(3,2,0,1) #Transpose to (z,d,D,X) 94 | 95 | # LR[sweep_index] = np.einsum("zdDX,DdEl->zElX", LR[sweep_index], mpo[sweep_index]) 96 | LR_transposed = LR[sweep_index].transpose(0,3,2,1) #Transpose to (z,X,D,d) 97 | product = LR_transposed.reshape(-1,LR_transposed.shape[-2]*LR_transposed.shape[-1]) @ mpo[sweep_index].reshape(mpo[sweep_index].shape[0]*mpo[sweep_index].shape[1],-1) #resulting shape (zX,El) 98 | product = product.reshape(LR[sweep_index].shape[0],LR[sweep_index].shape[-1],mpo[sweep_index].shape[2],mpo[sweep_index].shape[3]) #reshape to (z,X,E,l) 99 | LR[sweep_index] = product.transpose(0,2,3,1) #tranpose to (z,E,l,X) 100 | 101 | # LR[sweep_index] = np.einsum("zElX,XlZ->zEZ", LR[sweep_index], approx_solution[sweep_index]) 102 | LR_transposed = LR[sweep_index].transpose(0,1,3,2) #transpose to (z,E,X,l) 103 | product = LR_transposed.reshape(-1,LR_transposed.shape[-2]*LR_transposed.shape[-1]) @ approx_solution[sweep_index].reshape(-1,approx_solution[sweep_index].shape[-1]) #Resulting shape (zE,Z) 104 | LR[sweep_index] = product.reshape(LR[sweep_index].shape[0],LR[sweep_index].shape[1],approx_solution[sweep_index].shape[-1]) 105 | return R, K 106 | 107 | def update_A_right(H_eff,approx_solution,sweep_index): #TODO:lingering einsum 108 | 109 | if sweep_index == approx_solution.N-1: 110 | L, approx_solution[-1] = lq(approx_solution[-1]) 111 | 112 | if H_eff is None: 113 | K = None 114 | 115 | else: 116 | # K = np.einsum("ydYl,kd->ykYl",H_eff,np.conj(approx_solution[-1])) 117 | H_tranposed = H_eff.transpose(0,2,3,1) #Transpose to (y,Y,l,d) 118 | product = H_tranposed.reshape(-1,H_tranposed.shape[-1]) @np.conj(approx_solution[-1]).T #Resulting shape (yYl,k) 119 | product = product.reshape(H_eff.shape[0],H_eff.shape[2],H_eff.shape[3],approx_solution[-1].shape[0]) #Reshape to (y,Y,l,k) 120 | K = product.transpose(0,3,1,2) # Transpose to (y,k,Y,l) 121 | 122 | # K = np.einsum("ykYl,jl->ykYj",K,approx_solution[-1]) 123 | product = K.reshape(-1,K.shape[-1]) @ approx_solution[-1].T #Resulting shape (ykY,j) 124 | K = product.reshape(K.shape[0],K.shape[1],K.shape[2],approx_solution[-1].shape[0]) #reshape to (y,k,Y,j) 125 | 126 | else: 127 | L, Qt = lq(approx_solution[sweep_index].reshape(approx_solution[sweep_index].shape[0],-1)) 128 | approx_solution[sweep_index] = Qt.reshape(approx_solution[sweep_index].shape[0], approx_solution[sweep_index].shape[1], -1) 129 | 130 | if H_eff is None: 131 | K = None 132 | 133 | else: 134 | #The correct einsum is given below the blased version is incorrect will fix 135 | K = np.einsum("xdyXlY,kdy->xkXlY",H_eff,np.conj(approx_solution[sweep_index])) 136 | K = np.einsum("xkXlY,jlY->xkXj",K,approx_solution[sweep_index]) #<<<-fix!!!! 137 | 138 | # # K = np.einsum("xdyXlY,klY->xdyXk",H_eff,np.conj(approx_solution[sweep_index])) 139 | # approx_transposed = approx_solution[sweep_index].transpose(1,2,0) 140 | # product = H_eff.reshape(-1,H_eff.shape[-2]*H_eff.shape[-1]) @ approx_transposed.reshape(-1,approx_transposed.shape[-1]) #resulting shape (xdyX,k) 141 | # K = product.reshape(H_eff.shape[0],H_eff.shape[1],H_eff.shape[2],H_eff.shape[3],approx_solution[sweep_index].shape[0])# (x,d,y,X,k) 142 | 143 | # # K = np.einsum("xdyXk,jdy->xjXk",K,approx_solution[sweep_index]) 144 | # K_tranposed = K.transpose(0,3,4,1,2) #Transpose to (x,X,k,d,y) 145 | # approx_transposed = approx_solution[sweep_index].transpose(1,2,0) # Transpose to (d,y,j) 146 | # product = K_tranposed.reshape(-1,K_tranposed.shape[-2]*K_tranposed.shape[-1])@ approx_transposed.reshape(-1,approx_transposed.shape[-1]) #Resulting shape (xXk,j) 147 | # product = product.reshape(K.shape[0],K.shape[3],K.shape[4],approx_solution[sweep_index].shape[0]) # Reshape to (x,X,k,j) 148 | # K = product.transpose(0,3,1,2) #Transpose to (x,j,X,k) 149 | 150 | if sweep_index == mpo.N-1: 151 | # LR[-1] = np.einsum("xd,Ddl->xDl", np.conj(approx_solution[-1]), mpo[-1]) 152 | mpo_transposed = mpo[-1].transpose(1,0,2) #transpose to (d,D,l) 153 | product = np.conj(approx_solution[-1]) @ mpo_transposed.reshape(mpo_transposed.shape[0],-1) #resulting shape (x,Dl) 154 | LR[-1] = product.reshape(product.shape[0],mpo[-1].shape[0],mpo[-1].shape[2]) 155 | 156 | # LR[-1] = np.einsum("xDl,Xl->xDX", LR[-1], approx_solution[-1]) 157 | product = LR[-1].reshape(-1,LR[-1].shape[-1]) @ approx_solution[-1].T #resulting shape (xD,X) 158 | LR[-1] = product.reshape(LR[-1].shape[0],LR[-1].shape[1],approx_solution[-1].shape[0]) 159 | else: 160 | # LR[sweep_index] = np.einsum("yEY,zdy->zdEY", LR[sweep_index+1], np.conj(approx_solution[sweep_index])) 161 | product = LR[sweep_index+1].T.reshape(-1,LR[sweep_index+1].shape[0]) @ np.conj(approx_solution[sweep_index]).T.reshape(approx_solution[sweep_index].shape[-1],-1) #resulting shape (YE,dz) 162 | product = product.reshape(LR[sweep_index+1].shape[-1],LR[sweep_index+1].shape[-2],approx_solution[sweep_index].shape[1],approx_solution[sweep_index].shape[0]) #reshape to (Y,E,d,z) 163 | LR[sweep_index]= product.transpose(3,2,1,0) #tranpose to (z,d,E,Y) 164 | 165 | # LR[sweep_index] = np.einsum("zdEY,DdEl->zDlY", LR[sweep_index], mpo[sweep_index]) 166 | LR_transposed = LR[sweep_index].transpose(0,3,1,2) #Transpose to (z,Y,d,E) 167 | mpo_transposed = mpo[sweep_index].transpose(1,2,0,3) # Transpose to (d,E,D,l) 168 | product = LR_transposed.reshape(-1,LR_transposed.shape[-2]*LR_transposed.shape[-1]) @ mpo_transposed.reshape(-1,mpo_transposed.shape[-2]*mpo_transposed.shape[-1]) #resulting shape (zY,Dl) 169 | product = product.reshape(LR[sweep_index].shape[0],LR[sweep_index].shape[3],mpo[sweep_index].shape[0],mpo[sweep_index].shape[3]) #reshape to (z,Y,D,l) 170 | LR[sweep_index] = product.transpose(0,2,3,1) #transpose to (z,D,l,Y) 171 | 172 | # LR[sweep_index] = np.einsum("zDlY,ZlY->zDZ", LR[sweep_index], approx_solution[sweep_index]) 173 | approx_transposed = approx_solution[sweep_index].transpose(1,2,0) #transpose to (l,Y,Z) 174 | product = LR[sweep_index].reshape(-1,LR[sweep_index].shape[-2]*LR[sweep_index].shape[-1]) @ approx_transposed.reshape(-1,approx_transposed.shape[-1]) # resulting shape (zD,Z) 175 | LR[sweep_index] = product.reshape(LR[sweep_index].shape[0],LR[sweep_index].shape[1],product.shape[1]) 176 | 177 | return L, K 178 | 179 | def update_C_left(LR,C,mpo,approx_solution,sweep_index): 180 | if sweep_index == mpo.N-2: 181 | # approx_solution[-1] = np.einsum("dX,Xl->dl",C,approx_solution[-1]) 182 | approx_solution[-1] = C @ approx_solution[-1] 183 | else: 184 | # approx_solution[sweep_index+1] = np.einsum("dX,XlY->dlY",C,approx_solution[sweep_index+1]) 185 | product = C @ approx_solution[sweep_index+1].reshape(approx_solution[sweep_index+1].shape[0],-1) #Rsulting shape (d,lY) 186 | approx_solution[sweep_index+1] = product.reshape(product.shape[0],approx_solution[sweep_index+1].shape[1],approx_solution[sweep_index+1].shape[2] ) #Reshape to (d,l,Y) 187 | 188 | def update_C_right(LR,C,mpo,approx_solution,sweep_index): 189 | if sweep_index == 1: 190 | # approx_solution[0] = np.einsum("dx,xy->dy",approx_solution[0],C) 191 | approx_solution[0] = approx_solution[0] @ C 192 | else: 193 | # approx_solution[sweep_index-1] = np.einsum("xly,yd->xld",approx_solution[sweep_index-1],C) 194 | product = approx_solution[sweep_index-1].reshape(-1,approx_solution[sweep_index-1].shape[-1]) @ C 195 | approx_solution[sweep_index-1] = product.reshape(approx_solution[sweep_index-1].shape[0],approx_solution[sweep_index-1].shape[1],C.shape[1]) 196 | 197 | def time_evolve_forward(LR,mpo,approx_solution,krylov_depth,sweep_index,timestep): 198 | if sweep_index == 0: # Left boundary case 199 | if use_lanczos: 200 | def matvec(psi): 201 | tmp = psi.reshape(mpo[0].shape[2], LR[1].shape[2]) 202 | # tmp = np.einsum("lX,xDX->xDl", tmp, LR[1]) 203 | product = LR[1].reshape(-1,LR[1].shape[-1]) @ tmp.T #resulting shape xD,l 204 | tmp = product.reshape(LR[1].shape[0],LR[1].shape[1],tmp.shape[0]) 205 | 206 | # tmp = np.einsum("xDl,dDl->dx", tmp, mpo[0]) 207 | mpo_transposed = mpo[0].transpose(1,2,0) #Transpose to D,l,d 208 | product = tmp.reshape(tmp.shape[0],-1) @ mpo_transposed.reshape(-1,mpo_transposed.shape[-1]) #resulting shape xd 209 | tmp = product.T 210 | 211 | return tmp.reshape(-1) 212 | approx_solution[sweep_index] = lan_exp(approx_solution[sweep_index].reshape(-1), matvec, t=-1j * timestep, k=krylov_depth).reshape(approx_solution[sweep_index].shape) 213 | return None 214 | else: 215 | # H_eff = np.einsum("dDl,yDY->dylY",mpo[0],LR[1]) 216 | LR_transposed = LR[1].transpose(1,0,2) #transpose to (D,y,Y) 217 | mpo_transposed = mpo[0].transpose(0,2,1) #transpose to (d,l,D) 218 | product = mpo_transposed.reshape(-1,mpo_transposed.shape[-1]) @ LR_transposed.reshape(LR_transposed.shape[0],-1) #resulting shape (dl,yY) 219 | product= product.reshape(mpo[0].shape[0],mpo[0].shape[2],LR[1].shape[0],LR[1].shape[2])# reshape to (d,l,y,Y) 220 | H_eff = product.transpose(0,2,1,3) #Transpose to (d,y,l,Y) 221 | H_flat = H_eff.reshape(H_eff.shape[0]*H_eff.shape[1], -1) 222 | expH = expm(-1j * timestep * H_flat) 223 | approx_solution[sweep_index] = np.dot(expH, approx_solution[sweep_index].reshape(-1)) 224 | approx_solution[sweep_index] = approx_solution[sweep_index].reshape(H_eff.shape[0],H_eff.shape[1]) 225 | 226 | elif 0 < sweep_index < mpo.N - 1: 227 | if use_lanczos: 228 | def matvec(psi): 229 | tmp = psi.reshape((LR[sweep_index-1].shape[2],mpo[sweep_index].shape[3], LR[sweep_index+1].shape[2])) 230 | # tmp = np.einsum("xDX,XlY->xDlY", LR[sweep_index-1], tmp) 231 | product = LR[sweep_index-1].reshape(-1,LR[sweep_index-1].shape[-1]) @ tmp.reshape(tmp.shape[0],-1) #Resulting shape (xD,lY) 232 | tmp = product.reshape(LR[sweep_index-1].shape[0],LR[sweep_index-1].shape[1],tmp.shape[1],tmp.shape[2]) 233 | 234 | # tmp = np.einsum("xDlY,DdEl->xdEY",tmp,mpo[sweep_index]) 235 | tmp_transposed = tmp.transpose (0,3,1,2) #Transpose to (x,Y,D,l) 236 | mpo_transposed = mpo[sweep_index].transpose(0,3,1,2) #Transpose to (D,l,d,E) 237 | product = tmp_transposed.reshape(-1,tmp_transposed.shape[-2]*tmp_transposed.shape[-1]) @ mpo_transposed.reshape(-1,mpo_transposed.shape[-2]*mpo_transposed.shape[-1]) #Resulting shape (xY,dE) 238 | product = product.reshape(tmp.shape[0],tmp.shape[3],mpo[sweep_index].shape[1],mpo[sweep_index].shape[2]) #Reshape to (x,D,d,E) 239 | tmp = product.transpose(0,2,3,1) #Transpose to (x,d,E,Y) 240 | 241 | # tmp = np.einsum("xdEY,yEY->xdy",tmp,LR[sweep_index+1]) 242 | LR_transposed = LR[sweep_index+1].transpose (1,2,0) #Transpose to (E,Y,y)) 243 | tmp = tmp.reshape(-1,tmp.shape[-2]*tmp.shape[-1]) @ LR_transposed.reshape(-1,LR_transposed.shape[-1]) #Resulting shape (xd,y) 244 | # tmp = product. 245 | return tmp.reshape(-1) 246 | approx_solution[sweep_index] = lan_exp(approx_solution[sweep_index].reshape(-1), matvec, t=-1j * timestep, k=krylov_depth).reshape(approx_solution[sweep_index].shape) 247 | return None 248 | else: 249 | # H_eff = np.einsum("xDX,DdEl->xdElX",LR[sweep_index-1],mpo[sweep_index]) 250 | LR_transposed = LR[sweep_index-1].transpose(0,2,1) #transpose to (x,X,D) 251 | product = LR_transposed.reshape(-1,LR_transposed.shape[-1]) @ mpo[sweep_index].reshape(mpo[sweep_index].shape[0],-1) #resulting shape (xX,dEl) 252 | product = product.reshape(LR[sweep_index-1].shape[0],LR[sweep_index-1].shape[2],mpo[sweep_index].shape[1],mpo[sweep_index].shape[2],mpo[sweep_index].shape[3]) #reshape to (x,X,d,E,l) 253 | H_eff = product.transpose(0,2,3,4,1) #transpose to (x,d,E,l,X) 254 | 255 | # H_eff = np.einsum("xdElX,yEY->xdyXlY",H_eff,LR[sweep_index+1]) 256 | H_tranposed = H_eff.transpose(0,1,3,4,2) # transpose to (x,d,l,X,E) 257 | LR_transposed = LR[sweep_index+1].transpose(1,0,2) #Transpose to (E,y,Y) 258 | product = H_tranposed.reshape(-1,H_tranposed.shape[-1])@ LR_transposed.reshape(LR_transposed.shape[0],-1)#resulting shape (xdlX,yY) 259 | product = product.reshape(H_eff.shape[0],H_eff.shape[1],H_eff.shape[3],H_eff.shape[4],LR[sweep_index+1].shape[0],LR[sweep_index+1].shape[2]) #reshape to (x,d,l,X,y,Y) 260 | H_eff = product.transpose(0,1,4,3,2,5) 261 | 262 | H_flat = H_eff.reshape(H_eff.shape[0]*H_eff.shape[1]*H_eff.shape[2],-1) 263 | #approx_solution[sweep_index] = expm_multiply(-1j * timestep * H_flat, approx_solution[sweep_index].reshape(-1)) 264 | expH = expm(-1j * timestep * H_flat) 265 | approx_solution[sweep_index] = np.dot(expH, approx_solution[sweep_index].reshape(-1)) 266 | approx_solution[sweep_index] = approx_solution[sweep_index].reshape(H_eff.shape[0],H_eff.shape[1],H_eff.shape[2]).reshape(approx_solution[sweep_index].shape) 267 | 268 | else: # Right boundary case (N-1) 269 | if use_lanczos: 270 | def matvec(psi): 271 | tmp = psi.reshape(LR[-2].shape[2], mpo[-1].shape[2]) 272 | # tmp = np.einsum("xDX,Xl->xDl", LR[-2], tmp) 273 | product = LR[-2].reshape(-1,LR[-2].shape[-1]) @ tmp #Resulting shape (xD,l) 274 | tmp = product.reshape(LR[-2].shape[0],LR[-2].shape[1],tmp.shape[1]) 275 | 276 | # tmp = np.einsum("xDl,Ddl->xd", tmp, mpo[-1]) 277 | mpo_transposed = mpo[-1].transpose(0,2,1) #Tranpose to (D,l,d)) 278 | product = tmp.reshape(tmp.shape[0],-1) @ mpo_transposed.reshape(-1,mpo_transposed.shape[-1]) #Resulting shape x,d 279 | return product.reshape(-1) 280 | approx_solution[sweep_index] = lan_exp(approx_solution[sweep_index].reshape(-1), matvec, t=-1j * timestep, k=krylov_depth).reshape(approx_solution[sweep_index].shape) 281 | return None 282 | else: 283 | # H_eff = np.einsum("Ddl,yDY->ydYl",mpo[-1],LR[sweep_index-1]) 284 | mpo_transposed = mpo[-1].transpose(1,2,0) #transpose to (d,l,D) 285 | LR_transposed = LR[sweep_index-1].transpose(1,0,2) #transpose to (D,y,Y) 286 | product = mpo_transposed.reshape(-1,mpo_transposed.shape[-1]) @ LR_transposed.reshape(LR_transposed.shape[0],-1) #resulting shape (dl,yY) 287 | product = product.reshape(mpo[-1].shape[1],mpo[-1].shape[2],LR[sweep_index-1].shape[0],LR[sweep_index-1].shape[2]) #reshape to (d,l,y,Y) 288 | H_eff = product.transpose(2,0,3,1) #transpose to (y,d,Y,l) 289 | 290 | H_flat = H_eff.reshape(H_eff.shape[0]*H_eff.shape[1],-1) 291 | expH = expm(-1j * timestep * H_flat) 292 | approx_solution[sweep_index] = np.dot(expH, approx_solution[sweep_index].reshape(-1)) 293 | approx_solution[sweep_index] = approx_solution[sweep_index].reshape(H_eff.shape[0],H_eff.shape[1]) 294 | return H_eff 295 | 296 | def time_evolve_backwards(R,K,k,krylov_depth): #TODO:lingering einsum 297 | if use_lanczos: 298 | def matvec(C): 299 | tmp = C.reshape(R.shape) 300 | # tmp = np.einsum("xDX,XY->xDY",LR[k],tmp) 301 | product = LR[k].reshape(-1,LR[k].shape[-1]) @ tmp #resulting shape (xD,Y) 302 | tmp = product.reshape(LR[k].shape[0],LR[k].shape[1],tmp.shape[-1]) 303 | 304 | # tmp = np.einsum("xDY,yDY->xy",tmp,LR[k+1]) 305 | LR_transposed = LR[k+1].transpose(1,2,0) #Transpose to DY,y 306 | tmp = tmp.reshape(tmp.shape[0],-1) @ LR_transposed.reshape(-1,LR_transposed.shape[-1]) #Resulting shape x,y 307 | return tmp.reshape(-1) 308 | C_evolved = lan_exp(R.reshape(-1), matvec, t=1j * dt/2, k=krylov_depth) 309 | else: 310 | Kflat = K.reshape(K.shape[0]*K.shape[1],-1) #Flatten to Hermitian Matrix 311 | expK = expm(1j * dt/2 * Kflat) 312 | C_evolved = np.dot(expK, R.reshape(-1)) 313 | C_evolved = C_evolved.reshape(R.shape) 314 | return C_evolved 315 | 316 | def sweep_scheduler(n, sweeps): 317 | sequence = np.concatenate([np.arange(n), np.arange(n-2, 0, -1)]) 318 | repeated_sequence = np.tile(sequence, sweeps) 319 | repeated_sequence = np.append(repeated_sequence, 0) 320 | is_first_last = np.zeros(repeated_sequence.shape) 321 | is_first_last[0] = 1 322 | is_first_last[-1] = -1 323 | return zip(list(repeated_sequence), list(is_first_last)) 324 | 325 | def measure_energy_C(C,LR,sweep_index): 326 | # E = np.einsum("XY,xDX->xDY",C,LR[sweep_index]) 327 | product = LR[sweep_index].reshape(-1,LR[sweep_index].shape[-1]) @ C #resulting shape (x,D,Y) 328 | E = product.reshape(LR[sweep_index].shape[0],LR[sweep_index].shape[1],C.shape[1]) 329 | 330 | # E = np.einsum("xDY,yDY->xy",E,LR[sweep_index+1]) 331 | LR_transposed = LR[sweep_index+1].transpose(1,2,0) #resulting shape (D,Y,y) 332 | E = E.reshape(E.shape[0],-1) @ LR_transposed.reshape(-1,LR_transposed.shape[-1]) #resulting shape xy 333 | 334 | E = np.dot(C.conj().reshape(-1),E.reshape(-1)) 335 | return E 336 | 337 | 338 | if mps.canform != "Left": 339 | mps.canonize_left() 340 | approx_solution = mps.copy() 341 | LR = compute_right_envs(mps, mpo) 342 | Es_C = [] 343 | Es_A = [] 344 | sweep_schedule = sweep_scheduler(mps.N,sweeps) 345 | for k, is_first_last in (sweep_schedule): #Using 1 indexing 346 | if verbose: 347 | print("-------New epoch at site:",k) 348 | approx_solution.show() 349 | #================ First Site ================ 350 | if k == 0: 351 | if is_first_last: 352 | H_eff = time_evolve_forward(LR,mpo,approx_solution,krylov_depth,k,dt/2) 353 | else: 354 | H_eff = time_evolve_forward(LR,mpo,approx_solution,krylov_depth,k,dt) 355 | #Es_A.append(measure_energy_A(approx_solution,mpo,LR,k)) 356 | if is_first_last != -1: # not last 357 | R, K = update_A_left(H_eff, approx_solution,k) 358 | C = time_evolve_backwards(R,K,k,krylov_depth) 359 | update_C_left(LR,C,mpo,approx_solution,k) 360 | if measure: 361 | Es_C.append(measure_energy_C(C,LR,k)) 362 | left_sweep = True 363 | 364 | #================ Middle Sites ================ 365 | elif 0 < k and k < mps.N-1: 366 | #Evolve AC forward in time 367 | H_eff = time_evolve_forward(LR,mpo,approx_solution,krylov_depth,k,dt/2) 368 | #Es_A.append(measure_energy_A(approx_solution,mpo,LR,k)) 369 | 370 | if left_sweep: 371 | R, K = update_A_left(H_eff, approx_solution, k) 372 | C = time_evolve_backwards(R,K,k,krylov_depth) 373 | update_C_left(LR,C,mpo,approx_solution,k) 374 | 375 | else: 376 | R, K = update_A_right(H_eff, approx_solution, k) 377 | C = time_evolve_backwards(R,K,k-1,krylov_depth) 378 | update_C_right(LR,C,mpo,approx_solution,k) 379 | 380 | if measure: 381 | Es_C.append(measure_energy_C(C,LR,k if left_sweep else k-1)) 382 | 383 | #================ Last Site ================ Index = N -1 and N 384 | else: # k == mps.N-1 385 | H_eff = time_evolve_forward(LR,mpo,approx_solution,krylov_depth,k,dt) 386 | #Es_A.append(measure_energy_A(approx_solution,mpo,LR,k)) 387 | 388 | # Setup for right sweep 389 | R, K = update_A_right(H_eff, approx_solution, k) 390 | C = time_evolve_backwards(R,K,k-1,krylov_depth) 391 | #print(measure_energy_C(C,LR,k)) 392 | 393 | update_C_right(LR,C,mpo,approx_solution,k) 394 | if measure: 395 | Es_C.append(measure_energy_C(C,LR,k-1)) 396 | left_sweep = False 397 | 398 | approx_solution.canform = "Left" 399 | return approx_solution, np.array(Es_C), np.array(Es_A) -------------------------------------------------------------------------------- /code/quantum/time_evolution.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import numpy.linalg as la 3 | import scipy 4 | from tensornetwork.MPO import MPO 5 | from tensornetwork.MPS import MPS 6 | import time 7 | from itertools import product 8 | from tensornetwork.contraction import * 9 | # from tensornetwork.util import * 10 | from tensornetwork.stopping import * 11 | from quantum.hamiltonians import * 12 | from tqdm import tqdm 13 | import numpy as np 14 | import matplotlib.pyplot as plt 15 | import time 16 | seed_value = 42 17 | 18 | np.random.seed(seed_value) 19 | 20 | #pauli matrices 21 | X = np.array([[0,1.],[1,0]]) 22 | Y = np.array([[0,-1.j],[1j,0]]) 23 | Z = np.array([[1, 0], [0, -1]]) 24 | I = np.eye(2, dtype=np.complex64) 25 | 26 | 27 | def random_tensor(*args, a=-.5, b=1): 28 | output = a + (b-a)*np.random.rand(*args) 29 | output /= np.linalg.norm(output) 30 | return output 31 | 32 | 33 | #======================Initialization Functions======================= 34 | def initialize_system(N, m, cutoff): 35 | localops = isotropic_heisenberg(N, 1, 2, 1) 36 | psi = rand_MPS(2, m, N) 37 | psi.canonize_right() 38 | psi[-1] /= np.linalg.norm(psi[-1], 'fro') 39 | return localops, psi 40 | 41 | #==================== Trotterized Time Evolution Preparation======================== 42 | def prepare_trotterized_operators(localops, total_time, num_steps): 43 | delta = total_time / num_steps 44 | ell = num_steps - 1 45 | U, H1, H2_half = trotterize(localops, delta) 46 | return U, H1, H2_half, ell 47 | 48 | def compute_expectation(psi, operator, site_index): 49 | #TODO: FIx for non boundary cores 50 | """ 51 | Compute the expectation value of an operator at a specific site of an MPS. 52 | :param psi: MPS representing the quantum state. 53 | :param operator: The operator for which the expectation value is computed. 54 | :param site_index: Index of the site where the operator is applied. 55 | :return: Expectation value at the specified site. 56 | """ 57 | # Access the tensor at the specified site 58 | modified_tensor = np.tensordot(operator,psi[site_index], axes=[1,1]) 59 | #inner product over matrices 60 | expectation_value = np.tensordot(psi[site_index].conj(), modified_tensor, axes=[(0,1),(1,0)]) 61 | return expectation_value 62 | 63 | #======================Performance Measurement======================= 64 | def perform_time_evolution(psi, U, H1, H2_half, ell, contraction_types, baseline_states, stop): 65 | times = [] 66 | norms = [] 67 | for name in contraction_types: 68 | newpsi = psi.copy() 69 | print(f"Preforming Time evolution: Method: [{name}] ...") 70 | start_time = time.time() 71 | newpsi = time_evo_step(newpsi, U, H1, H2_half, ell, contraction_type=name, stop=stop) 72 | execution_time = time.time() - start_time 73 | newpsi.canonize_right() 74 | newpsi[-1] /= np.linalg.norm(newpsi[-1], 'fro') 75 | norm = (newpsi - baseline_states).norm() / baseline_states.norm() 76 | times.append(execution_time) 77 | norms.append(norm) 78 | return norms, times 79 | 80 | def _time_evolution_helper(local_ops, dt, parity, cutoff=1e-14): 81 | ''' 82 | Each local operation is a d * d * d * d tensor, interpreted 83 | as a Hamiltonian term acting on a pair of sites. We use 84 | the following index convention 85 | 86 | | 0 | 2 87 | _________ 88 | | | 89 | _________ 90 | | 1 | 3 91 | ''' 92 | 93 | d = local_ops[0].shape[0] 94 | n = len(local_ops)+1 95 | mpo = [] 96 | 97 | if parity == 1: 98 | site = np.eye(d,d) 99 | site = np.reshape(site, (d,1,d)) 100 | mpo.append(site) 101 | 102 | if cutoff is None: 103 | cutoff = 0 104 | 105 | for i in range(parity, len(local_ops), 2): 106 | assert(local_ops[i].shape == (d,d,d,d)) 107 | op = np.transpose(local_ops[i], (0,2,1,3)) # op(0,2,1,3) 108 | op = np.reshape(op, (d*d, d*d)) # op(0 2,1 3) 109 | op = scipy.linalg.expm(-dt * 1j * op) 110 | op = np.reshape(op, (d,d,d,d)) # op(0,2,1,3) 111 | op = np.transpose(op, (0,2,1,3)) # op(0,1,2,3) 112 | op = np.reshape(op, (d*d,d*d)) # op(0 1,2 3) 113 | U, s, Vh = np.linalg.svd(op) 114 | idx = np.nonzero(s > (max(s) * cutoff))[0] 115 | left = U[:,idx] * s[idx] 116 | right = Vh[idx, :] 117 | k = len(idx) 118 | 119 | if i == 0: 120 | left = np.reshape(left, (d,d,k)) # left(0,1,bond) 121 | left = np.transpose(left, (0,2,1)) # left(0,bond,1) 122 | else: 123 | left = np.reshape(left, (1,d,d,k)) # left(lbond,0,1,bond) 124 | left = np.transpose(left, (0,1,3,2)) # left(lbond,0,bond,1) 125 | mpo.append(left) 126 | 127 | if i == n-2: 128 | right = np.reshape(right, (k,d,d)) # right(bond,2,3) 129 | else: 130 | right = np.reshape(right, (k,d,1,d)) # right(bond,2,rbond,3) 131 | mpo.append(right) 132 | 133 | if (parity == 1 and n % 2 == 0) or (parity == 0 and n % 2 == 1): 134 | site = np.eye(d,d) 135 | site = np.reshape(site, (1,d,d)) 136 | mpo.append(site) 137 | 138 | return MPO(mpo) 139 | 140 | 141 | def trotterize(local_ops, dt, cutoff=1e-14): 142 | H1 = _time_evolution_helper(local_ops, dt, 0, cutoff=cutoff) 143 | H2 = _time_evolution_helper(local_ops, dt, 1, cutoff=cutoff) 144 | H2_half = _time_evolution_helper(local_ops, dt, 1, cutoff=cutoff) 145 | return MPO(H1 * H2), H1, H2_half 146 | 147 | 148 | def time_evo_step(psi, U, H1, H2_half,ell,finalround=None, stop= Cutoff(1e-14),contraction_type="classical",**kwargs): 149 | if contraction_type == "boundary_trick": 150 | psi.canonize_left() 151 | psi.canonize_right() 152 | #print(f"Time step starting MPS bond dimension:{psi.max_bond_dim()}\n") 153 | psi = mps_mpo_blas(psi,H2_half,stop=stop,round_type="dass_blas") #e^idt/2H2 154 | psi = mps_mpo_blas(psi,H1,stop=stop,round_type="dass_blas") #e^idtH1 155 | for l in ((range(ell))): 156 | psi = mps_mpo_blas(psi,U,stop=stop,round_type="dass_blas") 157 | #(U(dt)^l-1) 158 | #print("Performing last time evolution step e^idt/2H2 \n") 159 | psi= mps_mpo_blas(psi,H2_half,stop=stop,round_type="dass_blas") #e^idt/2H2 160 | 161 | if contraction_type == "classical": 162 | #print(f"Time step starting MPS bond dimension:{psi.max_bond_dim()}\n") 163 | psi = mps_mpo_blas(psi,H2_half,stop=stop,round_type="dass_blas") #e^idt/2H2 164 | psi = mps_mpo_blas(psi,H1,stop=stop,round_type="dass_blas") #e^idtH1 165 | for l in ((range(ell))): 166 | # print("exponentiation step:",l+1) 167 | psi = mps_mpo_blas(psi,U,stop=stop,round_type="dass_blas") #(U(dt)^l-1) 168 | #print("Performing last time evolution step e^idt/2H2 \n") 169 | psi = mps_mpo_blas(psi,H2_half,stop=stop,round_type="dass_blas") #e^idt/2H2 170 | 171 | elif contraction_type == "random": 172 | psi = rand_apply(H2_half, psi,stop=stop, **kwargs) #e^idt/2H2 173 | psi = rand_apply(H1, psi, stop=stop, **kwargs) #e^idtH1 174 | for l in tqdm(range(ell)): 175 | psi = rand_apply(U, psi, stop=stop, **kwargs) #(U(dt)^l-1) 176 | #print("Performing last time evolution step e^idt/2H2 \n") 177 | psi = rand_apply(H2_half, psi,stop=stop, **kwargs) #e^idt/2H2 178 | 179 | elif contraction_type == "Blas2.0": 180 | psi = rand_apply_blas2(H2_half, psi,stop=stop, **kwargs) #e^idt/2H2 181 | psi = rand_apply_blas2(H1, psi, stop=stop, **kwargs) #e^idtH1 182 | for l in tqdm(range(ell)): 183 | psi = rand_apply_blas2(U, psi, stop=stop) #(U(dt)^l-1) 184 | #print("Performing last time evolution step e^idt/2H2 \n") 185 | psi = rand_apply_blas2(H2_half, psi,stop=stop, **kwargs) #e^idt/2H2 186 | 187 | elif contraction_type == "Blas2.0_Heuristic": 188 | 189 | sketchdim = psi.max_bond_dim() 190 | sketchincrement=int(np.floor(0.5*psi.max_bond_dim())) 191 | psi = rand_apply_blas2(H2_half, psi, stop=stop,sketchdim=sketchdim,finalround=finalround,sketchincrement=sketchincrement) #e^idt/2H2 192 | 193 | sketchdim = psi.max_bond_dim() 194 | sketchincrement=int(np.floor(0.5*psi.max_bond_dim())) 195 | psi = rand_apply_blas2(H1, psi, stop=stop,sketchdim=sketchdim,finalround=finalround,sketchincrement=sketchincrement) #e^idtH1 196 | 197 | for l in (range(ell)): 198 | # print("exponentiation step:",l+1) 199 | sketchdim = psi.max_bond_dim() 200 | sketchincrement=int(np.floor(0.5*maxlinkdim(psi))) 201 | psi = rand_apply_blas2(U, psi, stop=stop,sketchdim=sketchdim,finalround=finalround,sketchincrement=sketchincrement) #(U(dt)^l-1) 202 | 203 | # print("Performing last time evolution step e^idt/2H2 \n") 204 | 205 | sketchdim = maxlinkdim(psi) 206 | sketchincrement=int(np.floor(0.5*maxlinkdim(psi))) 207 | psi = rand_apply_blas2(H2_half, psi, stop=stop,sketchdim=sketchdim,finalround=None,sketchincrement=sketchincrement) #e^idt/2H2 208 | 209 | return psi 210 | 211 | def generate_swap_gate(d): 212 | swap = np.zeros((d,d,d,d)) 213 | for i,j,k,l in product(range(d),range(d),range(d),range(d)): 214 | if i == l and j == k: 215 | swap[i,j,k,l] = 1 216 | return swap 217 | 218 | def apply_gates(mpo, gates): 219 | N = mpo.N 220 | assert N > 2 221 | mpo.canonize_left() 222 | mpo_out = mpo.copy() 223 | i = 0 224 | left_sweep = True 225 | 226 | for gate in gates: 227 | # ============ Left Sweep ============ 228 | if left_sweep: 229 | if i == 0: 230 | site = np.einsum("ijdk,dDl->lijkD",gate,mpo_out[i]) #(l,i,j,k,d) 231 | site = np.einsum("lijkD,DkEp->lijEp",site,mpo_out[i+1]) #(l,i,d,E,p) 232 | 233 | U,S,Vt = np.linalg.svd(site.reshape(site.shape[0]*site.shape[1],-1),full_matrices=False) #SVD((li,jEp)) 234 | 235 | U = U.reshape(site.shape[0],site.shape[1],U.shape[-1]) #(li,r)->(l,i,r) 236 | mpo_out[i] = U.transpose(1,2,0) #(l,i,r)-> (i,r,l) 237 | 238 | SV = np.diag(S) @ Vt #(r,jEp) 239 | mpo_out[i+1] = SV.reshape(S.shape[-1],site.shape[2],site.shape[3],site.shape[4]) #(rj,Ep)->(r,j,E,p) 240 | 241 | elif i == N-2: 242 | site = np.einsum("ijdk,Dkp->dijkpD",gate,mpo_out[i+1]) 243 | site = np.einsum("dijkpD,rdDl->riljp",site,mpo_out[i]) 244 | 245 | U,S,Vt= np.linalg.svd(site.reshape(site.shape[0]*site.shape[1]*site.shape[2],-1),full_matrices=False) #SVD(ril,jp) 246 | 247 | # Move orthogonalization pivot in Consider optional pivot selection in the case when 248 | # specifially n-1 gates are given and no right sweep ocurs so that the orthogonalization center ends at site N 249 | 250 | US = U @ np.diag(S) 251 | US = US.reshape(site.shape[0],site.shape[1],gate.shape[1],U.shape[-1]) # (r,i,l,x) 252 | mpo_out[i]=US.transpose(0,1,3,2) #(r,i,x,l) 253 | 254 | Vt = Vt.reshape(S.shape[-1],gate.shape[2],mpo_out[i+1].shape[2]) #(x,jp)->(x,j,p) 255 | mpo_out[i+1] = Vt 256 | left_sweep = False 257 | continue 258 | 259 | else: 260 | site = np.einsum("ijdk,rdDl->lrijkD", gate,mpo_out[i]) 261 | site = np.einsum("lrijkD,DkEp->lrijEp",site,mpo_out[i+1]) 262 | 263 | U,S,Vt= np.linalg.svd(site.reshape(site.shape[0]*site.shape[1]*site.shape[2],-1),full_matrices=False) #SVD(lri,jEp) 264 | 265 | U = U.reshape(site.shape[0],site.shape[1],site.shape[2],U.shape[-1]) # (l,r,i,x) 266 | mpo_out[i]=U.transpose(1,2,3,0) #(r,i,x,l) 267 | 268 | SV = np.diag(S) @ Vt #(x,jEp) 269 | mpo_out[i+1] = SV.reshape(S.shape[-1],site.shape[3],site.shape[4],site.shape[5]) #(r,jFp)->(r,j,F,p) 270 | 271 | i += 1 272 | 273 | # ============ Right Sweep ============ 274 | else: 275 | if i == 1: 276 | site = np.einsum("ijdk,Dkxp->dijxpD", gate,mpo_out[i]) 277 | 278 | site = np.einsum("dijxpD,dDl->lijxp",site,mpo_out[i-1]) 279 | 280 | U,S,Vt= np.linalg.svd(site.reshape(site.shape[0]*site.shape[1],-1),full_matrices=False) #SVD(li,jxp) 281 | mpo_out[i] = (np.diag(S) @ Vt).reshape(Vt.shape[0],gate.shape[2],site.shape[3],site.shape[4]) # (r,j,x,p) 282 | 283 | U = U.reshape(site.shape[0],site.shape[1],U.shape[-1])# (l,i,r) 284 | mpo_out[0] = U.transpose(1,2,0) # (r,i,l) 285 | left_sweep = True 286 | continue 287 | else: 288 | site = np.einsum("ijdk,rdDl->lrijkD", gate,mpo_out[i-1]) 289 | site = np.einsum("lrijkD,DkEp->lrijEp",site,mpo_out[i]) 290 | 291 | U,S,Vt= np.linalg.svd(site.reshape(site.shape[0]*site.shape[1]*site.shape[2],-1),full_matrices=False) #SVD(lri,jEp) 292 | 293 | US = (U @ np.diag(S)).reshape(site.shape[0],site.shape[1],site.shape[2],U.shape[-1]) # (l,r,i,x) 294 | mpo_out[i-1] = US.transpose(1,2,3,0) #(r,i,x,l) 295 | 296 | mpo_out[i] = Vt.reshape(S.shape[-1],site.shape[3],site.shape[4],site.shape[5]) #(r,jFp)->(r,j,F,p) 297 | i-=1 298 | 299 | return mpo_out 300 | 301 | 302 | #======================Plotting======================= 303 | def plot_execution_times(labels, times): 304 | plt.figure(figsize=(10, 6)) 305 | bars = plt.bar(labels, times, color=['blue', 'green', 'red', 'purple']) 306 | 307 | for bar in bars: 308 | plt.hlines(bar.get_height(), bar.get_x() + bar.get_width(), plt.gca().get_xlim()[1], colors='grey', linestyles='-', lw=2, alpha=0.5) 309 | 310 | plt.ylabel('Execution Time (s)') 311 | plt.title('Comparison of Execution Times for Different Algorithms') 312 | plt.show() 313 | 314 | -------------------------------------------------------------------------------- /code/quantum/util.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from tensornetwork.linalg import * 3 | from tensornetwork.stopping import * 4 | from tensornetwork.MPS import MPS 5 | from tensornetwork.MPO import MPO 6 | 7 | def state_vector_to_mps(vector, num_spins, stop = FixedDimension(20)): 8 | N = int(np.log(len(vector)) / np.log(num_spins)) 9 | mps_out = [] 10 | 11 | # Initial reshape of the vector 12 | tensor = vector.reshape(num_spins, -1) 13 | 14 | U, S, Vh = truncated_svd(tensor, stop=stop) 15 | mps_out.append(U) 16 | tensor = np.diag(S) @ Vh 17 | for i in range(1, N - 1): 18 | tensor = tensor.reshape(tensor.shape[0]*num_spins,-1) 19 | U, S, Vh = truncated_svd(tensor, stop=stop) 20 | # Intermediate tensors: shape (previous bond_dim, d, bond_dim) 21 | U = U.reshape(mps_out[-1].shape[-1], num_spins, U.shape[1]) 22 | mps_out.append(U) 23 | tensor = np.dot(np.diag(S), Vh) 24 | # Fina tensor: shape (bond_dim, d) 25 | tensor = tensor.reshape(mps_out[-1].shape[-1],num_spins) 26 | mps_out.append(tensor) 27 | 28 | return MPS(mps_out) 29 | 30 | def flatten_mps(mps): 31 | """Flattens the MPS into a single vector of size d^n.""" 32 | vector = mps.tensors[0] 33 | for tensor in mps.tensors[1:mps.N-1]: 34 | vector = np.einsum("dX,XkY->dkY",vector,tensor) # Absorb left site giving 35 | vector = vector.reshape(-1,vector.shape[-1]) # Reshape to (d^j,X) 36 | vector = vector @ mps.tensors[-1] # Resulting shape d^n-1 x d 37 | return vector.reshape(mps.tensors[0].shape[0]**mps.N) # Final reshape to a vector 38 | 39 | #potentially unstable ! 40 | def hamiltonian_to_mpo(matrix, num_spins, stop=Cutoff(1e-14)): 41 | n_rows, n_cols = matrix.shape 42 | assert n_rows == n_cols and n_rows == num_spins ** int(np.log(n_rows) / np.log(num_spins)), "Matrix must be square and dimensions must be powers of d." 43 | 44 | N = int(np.log(n_rows) / np.log(num_spins)) 45 | mpo_out = [] 46 | 47 | # Initial reshape of the matrix 48 | tensor = matrix.reshape(num_spins**2, -1) 49 | 50 | U, S, Vh = truncated_svd(tensor, stop=stop) 51 | mpo_out.append(U.reshape(num_spins, U.shape[1],num_spins)) 52 | tensor = np.diag(S) @ Vh 53 | for i in range(1, N - 1): 54 | tensor = tensor.reshape(tensor.shape[0] * num_spins**2, -1) 55 | U, S, Vh = truncated_svd(tensor, stop=stop) 56 | # Intermediate tensors: shape (previous bond_dim, d, bond_dim, d) 57 | U = U.reshape(mpo_out[-1].shape[-2], num_spins, U.shape[1],num_spins) 58 | mpo_out.append(U) 59 | tensor = np.dot(np.diag(S), Vh) 60 | # Final tensor: shape (bond_dim, d, d) 61 | 62 | tensor = tensor.reshape(mpo_out[-1].shape[-2], num_spins, num_spins) 63 | mpo_out.append(tensor) 64 | 65 | return MPO(mpo_out) 66 | 67 | def flatten_mpo(mpo): 68 | tmp = np.einsum("arb,rARB->aAbBR",mpo[0],mpo[1]) 69 | tmp = tmp.reshape((4,4,mpo[1].shape[2])) 70 | size = 4 71 | for i in range(2,mpo.N-1): 72 | tmp = np.einsum("abr,rARB->aAbBR",tmp,mpo[i]) 73 | size *= 2 74 | tmp = tmp.reshape((size,size,mpo[i].shape[2])) 75 | H = np.einsum("abr,rAB->aAbB",tmp,mpo[-1]) 76 | size *= 2 77 | H = H.reshape(size,size) 78 | return H 79 | 80 | def spin_up_state(N, d): 81 | spin_up_vector = np.array([1, 0]) 82 | 83 | mps_matrices = [] 84 | 85 | mps_matrices.append(spin_up_vector.reshape(d, 1)) 86 | 87 | for _ in range(1, N-1): 88 | mps_matrices.append(spin_up_vector.reshape(1, d, 1)) 89 | 90 | mps_matrices.append(spin_up_vector.reshape(1, d)) 91 | 92 | return MPS(mps_matrices) 93 | 94 | def measure_magnetizations(mps, op = np.array([[0.0,1.0],[1.0,0.0]])): 95 | mps.canonize_left() 96 | magnetizations = np.zeros(mps.N) 97 | for i in range(mps.N): 98 | if i == 0: 99 | magnetizations[0] = np.trace(mps[0].conj().T @ op @ mps[0]).real 100 | mps[0], R = np.linalg.qr(mps[0],mode="reduced") 101 | 102 | # mps[1] = np.einsum("ab,bcd->acd",R,mps[1]) 103 | prod = R @ mps[1].reshape(mps[1].shape[0],-1) #resulting shape (a,cd) 104 | mps[1] = prod.reshape(R.shape[0],mps[1].shape[1],mps[1].shape[2]) 105 | elif i < mps.N-1: 106 | # tmp = np.einsum("abc,bd->adc",mps[i], op) 107 | mps_transposed = mps[i].transpose(0,2,1) #Transpose to (a,c,b) 108 | product = mps_transposed.reshape(-1,mps_transposed.shape[-1]) @ op #Resulting shape (ac,d) 109 | product = product.reshape(mps[i].shape[0],mps[i].shape[2],op.shape[1]) #(a,c,d) 110 | tmp = product.transpose(0,2,1) #transpose to (a,d,c) 111 | 112 | #magnetizations[i] = np.einsum("adc,adc->",tmp,np.conj(mps[i])) 113 | magnetizations[i] = np.trace(np.dot(tmp.reshape(tmp.shape[0], -1), np.conj(mps[i]).reshape(mps[i].shape[0], -1).T)).real 114 | Q, R = np.linalg.qr(mps[i].reshape(-1,mps[i].shape[2]),mode="reduced") 115 | mps[i] = Q.reshape(mps[i].shape[0],mps[i].shape[1],-1) 116 | 117 | if i == mps.N-2: 118 | mps[i+1] = R @ mps[i+1] 119 | else: 120 | product = R @ mps[i+1].reshape(mps[i+1].shape[0],-1) #resulting shape a,cd 121 | mps[i+1] = product.reshape(R.shape[0],mps[i+1].shape[1],mps[i+1].shape[2]) 122 | # mps[i+1] = np.einsum("ab,bcd->acd",R,mps[i+1]) 123 | else: 124 | magnetizations[-1] = np.trace(mps[-1] @ op @ mps[-1].conj().T).real 125 | mps.canform = "Right" 126 | return magnetizations 127 | -------------------------------------------------------------------------------- /code/tensornetwork/CMAKELists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | project(incrementalqr) 3 | 4 | # Set the path to pybind11 5 | set(pybind11_DIR "/Users/pren/miniconda3/envs/tensor/lib/python3.12/site-packages/pybind11/share/cmake/pybind11") 6 | #You will have to update this path sorry 7 | 8 | # Find pybind11 9 | find_package(pybind11 REQUIRED) 10 | 11 | # Specify the path to LAPACK installation 12 | list(APPEND CMAKE_PREFIX_PATH "/opt/homebrew/opt/lapack") 13 | 14 | # Find LAPACK and LAPACKE 15 | find_package(LAPACK REQUIRED) 16 | find_path(LAPACK_INCLUDE_DIR NAMES "lapacke.h" PATHS "/opt/homebrew/lapack/include") 17 | find_library(LAPACK_LIBRARIES NAMES "lapack" PATHS "/opt/homebrew/lapack/lib") 18 | 19 | # Find BLAS 20 | set(BLAS_INCLUDE_DIR /opt/homebrew/opt/openblas/include) 21 | include_directories(${BLAS_INCLUDE_DIR}) 22 | 23 | find_package(BLAS REQUIRED) 24 | # find_path(BLAS_INCLUDE_DIR NAMES "cblss.h" PATHS "/opt/homebrew/opt/openblas/include") 25 | find_library(BLAS_LIBRARIES NAMES "blas" PATHS "/opt/homebrew/opt/openblas/lib") 26 | 27 | # Include directories for LAPACK and BLAS 28 | include_directories(${LAPACK_INCLUDE_DIR} ${BLAS_INCLUDE_DIR}) 29 | 30 | # Create the incrementalqr library 31 | add_library(incrementalqr MODULE incrementalqr.cpp) 32 | target_link_libraries(incrementalqr PRIVATE pybind11::module LAPACK::LAPACK ${LAPACK_LIBRARIES} ${BLAS_LIBRARIES}) -------------------------------------------------------------------------------- /code/tensornetwork/MPO.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import numpy.linalg as la 3 | import matplotlib.pyplot as plt 4 | import copy 5 | from .stopping import Cutoff,no_truncation 6 | from .linalg import lq,truncated_svd 7 | 8 | class MPO: 9 | def __init__(self, mpo, canform="None", rounded=False): 10 | self.tensors = mpo 11 | self.N = len(mpo) 12 | self.canform = canform 13 | self.rounded = rounded 14 | 15 | def __len__(self): 16 | return self.N 17 | 18 | def copy(self): 19 | new_mpo = copy.deepcopy(self) 20 | return new_mpo 21 | 22 | def __getitem__(self, index): 23 | return self.tensors[index] 24 | 25 | def __setitem__(self, index, value): 26 | self.tensors[index] = value 27 | 28 | def canonize(self): 29 | if "None" != self.canform: 30 | return 31 | self.canonize_right() 32 | 33 | def canonize_right(self): 34 | if "Right" == self.canform: 35 | return 36 | 37 | T = self[0] 38 | T = np.transpose(T, (0,2,1)) 39 | T = np.reshape(T, (T.shape[0]*T.shape[1], T.shape[2])) 40 | Q, R = la.qr(T) 41 | Q = np.reshape(Q, (self[0].shape[0],self[0].shape[2],Q.shape[1])) 42 | Q = np.transpose(Q, (0,2,1)) 43 | self[0] = Q 44 | 45 | for i in range(1, self.N - 1): 46 | A = np.tensordot(R, self[i], (1, 0)) 47 | A = np.transpose(A, (0,1,3,2)) 48 | Q, R = la.qr(np.reshape(A, (A.shape[0]*A.shape[1]*A.shape[2], A.shape[3]))) 49 | Q = np.reshape(Q, (A.shape[0], A.shape[1], A.shape[2], Q.shape[1])) 50 | self[i] = np.transpose(Q, (0,1,3,2)) 51 | 52 | self[-1] = np.tensordot(R , self[-1],axes=(1,0)) 53 | self.canform = "Right" 54 | 55 | right_canonize = canonize_right 56 | 57 | def canonize_left(self): 58 | if "Left" == self.canform: 59 | return 60 | 61 | T = self[-1] 62 | reshaped_tensor = np.reshape(T,(T.shape[0],T.shape[1]*T.shape[2])) 63 | L, Q = lq(reshaped_tensor) 64 | self[-1] = np.reshape(Q,(Q.shape[0],T.shape[1],T.shape[2])) 65 | 66 | for i in range(self.N - 2, 0, -1): 67 | A = np.tensordot(self[i], L, (2,0)) 68 | A = np.transpose(A, (0,1,3,2)) 69 | L, Q = lq(np.reshape(A, (A.shape[0],A.shape[1]*A.shape[2]*A.shape[3]))) 70 | self[i] = np.reshape(Q, (Q.shape[0],A.shape[1],A.shape[2],A.shape[3])) 71 | 72 | last_tensor = np.tensordot(self[0],L,axes=(1,0)) 73 | self[0] = np.transpose(last_tensor,(0,2,1)) 74 | self.canform = "Left" 75 | 76 | left_canonize = canonize_left 77 | 78 | def scale(self, scalar): 79 | self.canonize() 80 | if "Right" == self.canform: 81 | if np.iscomplexobj(scalar) and not np.iscomplexobj(self[-1]): 82 | self[-1] = self[-1].astype(np.complex128) 83 | self[-1] *= scalar 84 | else: 85 | if np.iscomplexobj(scalar) and not np.iscomplexobj(self[0]): 86 | self[0] = self[0].astype(np.complex128) 87 | self[0] *= scalar 88 | 89 | def dagger(self): 90 | """ 91 | Computes the Hermitian conjugate (dagger) of the entire MPO. 92 | just iterate though and conjugate instead. 93 | """ 94 | daggered_tensors = [np.conj(tensor) for tensor in self.tensors] 95 | return MPO(daggered_tensors, canform=self.canform, rounded=self.rounded) 96 | 97 | def norm(self): 98 | self.canonize() 99 | if "Right" == self.canform: 100 | reshaped_tensor = np.reshape(self[-1],(self[-1].shape[0],self[-1].shape[1]*self[-1].shape[2])) 101 | return np.linalg.norm(reshaped_tensor,'fro') 102 | else: 103 | reshaped_tensor = np.reshape(self[0],(self[0].shape[0]*self[0].shape[2],self[0].shape[1])) 104 | return np.linalg.norm(reshaped_tensor,'fro') 105 | 106 | def normalize(self): 107 | self.canonize() 108 | if "Right" == self.canform: 109 | self[-1] /= np.linalg.norm(self[-1].reshape(self[-1].shape[0],-1),'fro') 110 | else: 111 | self[0] /= np.linalg.norm(self[0].reshape(self[0].shape[1],-1),'fro') 112 | 113 | def __mul__(self, down): 114 | mpo = [] 115 | site = np.tensordot(self[0], down[0], (2,0)) # site(self0,self1,down1,down2) 116 | site = np.reshape(site, (site.shape[0], site.shape[1]*site.shape[2], site.shape[3])) 117 | mpo.append(site) 118 | 119 | assert(len(self) == len(down)) 120 | for i in range(1, len(self)-1): 121 | site = np.tensordot(self[i], down[i], (3,1)) # site(self0,self1,self2,down0,down2,down3) 122 | site = np.transpose(site, (0,3,1,2,4,5)) # site(self0,down0,self1,self2,down2,down3) 123 | site = np.reshape(site, (site.shape[0]*site.shape[1], site.shape[2], site.shape[3]*site.shape[4], site.shape[5])) 124 | mpo.append(site) 125 | 126 | site = np.tensordot(self[-1], down[-1], (2,1)) # site(self0,self1,down0,down2) 127 | site = np.transpose(site, (0,2,1,3)) # site(self0,down0,self1,down2) 128 | site = np.reshape(site, (site.shape[0]*site.shape[1], site.shape[2], site.shape[3])) 129 | mpo.append(site) 130 | 131 | return mpo 132 | 133 | def sub(self, mps2, **kwargs): 134 | return self.add(mps2, subtract=True, **kwargs) 135 | 136 | def __sub__(self, other): 137 | return self.sub(other, compress=True) #change to True when roudning is added 138 | 139 | def add(self, mpo2, subtract=False, compress=False, stop=Cutoff(1e-14)): 140 | mpo = [] 141 | 142 | # Left boundary 143 | assert(self[0].shape[0] == mpo2[0].shape[0]) 144 | assert(self[0].shape[2] == mpo2[0].shape[2]) 145 | 146 | m1 = self[0].shape[1] 147 | m2 = mpo2[0].shape[1] 148 | new_left_boundary = np.zeros((self[0].shape[0], m1 + m2, self[0].shape[2]), dtype="complex") 149 | new_left_boundary[:, 0:m1, :] = self[0] 150 | new_left_boundary[:, m1:, :] = mpo2[0] 151 | mpo.append(new_left_boundary) 152 | 153 | # Core tensors 154 | 155 | for i in range(1, self.N - 1): 156 | assert(self[i].shape[1] == mpo2[i].shape[1]) 157 | assert(self[i].shape[3] == mpo2[i].shape[3]) 158 | 159 | m1 = self[i].shape[0] 160 | n1 = self[i].shape[2] 161 | m2 = mpo2[i].shape[0] 162 | n2 = mpo2[i].shape[2] 163 | new_core = np.zeros((m1 + m2, self[i].shape[1], n1 + n2, self[i].shape[3]), dtype="complex") 164 | new_core[0:m1, :, 0:n1, :] = self[i] 165 | new_core[m1:, :, n1:, :] = mpo2[i] 166 | mpo.append(new_core) 167 | 168 | assert(self[-1].shape[1] == mpo2[-1].shape[1]) 169 | assert(self[-1].shape[2] == mpo2[-1].shape[2]) 170 | 171 | m1 = self[-1].shape[0] 172 | m2 = mpo2[-1].shape[0] 173 | new_right_boundary = np.zeros((m1 + m2, self[-1].shape[1], self[-1].shape[2]), dtype="complex") 174 | new_right_boundary[0:m1, :, :] = self[-1] 175 | new_right_boundary[m1:, :, :] = (-1 if subtract else 1) * mpo2[-1] 176 | mpo.append(new_right_boundary) 177 | 178 | mpo = MPO(mpo) 179 | if compress: 180 | mpo.round(stop=stop) 181 | 182 | return mpo 183 | 184 | def round(self, stop=Cutoff(1e-14)): 185 | if self.rounded: 186 | if self.canform == "Right": 187 | return 188 | else: 189 | self.canonize_right() 190 | return 191 | 192 | self.canonize_left() 193 | 194 | U, S, Vt = truncated_svd(self[0].reshape(self[0].shape[0] * self[0].shape[2], -1), stop=stop) 195 | self[0] = U.reshape(self[0].shape[0], U.shape[-1], self[0].shape[2]) 196 | 197 | for i in range(1, self.N - 1): 198 | K = np.diag(S) @ Vt 199 | A = np.einsum("kD,DdEl->kdEl", K, self[i]) 200 | U, S, Vt = truncated_svd(np.reshape(A, (A.shape[0] * A.shape[1] * A.shape[3], A.shape[2])), stop=stop) 201 | self[i] = np.reshape(U, (A.shape[0], A.shape[1], U.shape[-1], A.shape[3])) 202 | 203 | K = np.diag(S) @ Vt 204 | self[-1] = np.einsum("kD,Ddl->kdl", K, self[-1]) 205 | 206 | self.canform = "Right" 207 | self.rounded = True 208 | 209 | def __add__(self, other): 210 | return self.add(other, compress = True) 211 | 212 | def is_right_orth(self): 213 | # Right to left sweep 214 | for i in reversed(range(1, self.N)): 215 | tensor = self[i] 216 | if i == self.N-1 : 217 | reshaped_tensor = tensor.reshape(tensor.shape[0],tensor.shape[1]*tensor.shape[2]) 218 | else: 219 | reshaped_tensor = tensor.reshape(tensor.shape[0],tensor.shape[1]*tensor.shape[3]*tensor.shape[2]) 220 | product = np.dot(reshaped_tensor, reshaped_tensor.conj().T) 221 | identity = np.eye(product.shape[0]) 222 | 223 | if not np.allclose(product, identity): 224 | print(f"Tensor at index {i} is not right-orthogonal.") 225 | # Visualize the product matrix as a heatmap 226 | plt.imshow(np.abs(product), cmap='mako', interpolation='nearest') 227 | plt.title(f"Heatmap of Product Matrix at tensor {i}") 228 | plt.colorbar() 229 | plt.show() 230 | assert False, "Orthogonality test failed" 231 | print("All tensors are right-orthogonal.") 232 | 233 | def is_left_orth(self): 234 | # Left to right sweep 235 | for i in range(0,self.N-1): 236 | tensor = self[i] 237 | if i == 0 : 238 | reshaped_tensor = tensor.reshape(tensor.shape[0]*tensor.shape[2],tensor.shape[1]) 239 | else: 240 | reshaped_tensor = tensor.reshape(tensor.shape[0]*tensor.shape[1]*tensor.shape[3],tensor.shape[2]) 241 | product = np.dot(reshaped_tensor.conj().T, reshaped_tensor) 242 | identity = np.eye(product.shape[0]) 243 | if not np.allclose(product, identity): 244 | print(f"Tensor at index {i} is not left-orthogonal.") 245 | plt.imshow(np.abs(product), cmap='mako', interpolation='nearest') 246 | plt.title(f"Heatmap of Product Matrix at tensor {i}") 247 | plt.colorbar() 248 | plt.show() 249 | assert False, "Orthogonality test failed" 250 | print("All tensors are left-orthogonal.") 251 | 252 | def bond_size(self,i): 253 | if i ==0: 254 | return self[0].shape[1] 255 | else: 256 | return self[i].shape[2] 257 | 258 | def max_bond_dim(self): 259 | maxbond = 0 260 | for i, t in enumerate(self.tensors): 261 | if i == 0: 262 | maxbond = t.shape[1] 263 | elif i == self.N - 1: 264 | maxbond = max(maxbond, t.shape[0]) 265 | else: 266 | b1, b2 = t.shape[0], t.shape[2] 267 | maxbond = max(maxbond, b1, b2) 268 | return maxbond 269 | 270 | def show(self, max_width=None): 271 | from .misc import print_multi_line 272 | l1 = "" 273 | l2 = "" 274 | l3 = "" 275 | for i in range(0,self.N - 1): 276 | bdim = self.bond_size(i) 277 | strl = len(str(bdim)) 278 | l1 += f"│{bdim}" 279 | l2 += "●" + ("─" if bdim < 100 else "━") * strl 280 | l3 += "│" + " " * strl 281 | 282 | l1 += "│" 283 | l2 += "●" 284 | l3 += "│" 285 | 286 | print_multi_line(l1, l2,l3, max_width=max_width) 287 | 288 | __matmul__ = __mul__ 289 | 290 | @staticmethod 291 | def random_mpo(n, m, d=2, d2=2, dtype=float, random_tensor=np.random.randn): 292 | def random_tensor_with_dtype(*args, dtype=dtype): 293 | output = random_tensor(*args).astype(dtype) 294 | return output 295 | 296 | return MPO([random_tensor_with_dtype(d, m, d2)] + 297 | [random_tensor_with_dtype(m, d, m, d2) for _ in range(n - 2)] + 298 | [random_tensor_with_dtype(m, d, d2)]) 299 | 300 | @staticmethod 301 | def fully_random_mpo(n=10, random_tensor=np.random.randn): 302 | randint = lambda: np.random.randint(1, 7) 303 | bond_dim = randint() 304 | sites = [random_tensor(randint(), bond_dim, randint())] 305 | for i in range(n-2): 306 | new_bond_dim = randint() 307 | sites.append(random_tensor(bond_dim, randint(), new_bond_dim, randint())) 308 | bond_dim = new_bond_dim 309 | sites.append(random_tensor(bond_dim, randint(), randint())) 310 | return MPO(sites) 311 | 312 | @staticmethod 313 | def identity(d, N, m=None): 314 | """Constructs an identity MPO for a system with local dimension d and N sites.""" 315 | if m is None: 316 | m = d 317 | tensors = [] 318 | 319 | # Leftmost tensor (d x m x d) 320 | left_tensor = np.zeros((d, m, d)) 321 | for i in range(d): 322 | left_tensor[i, 0, i] = 1.0 323 | tensors.append(left_tensor) 324 | 325 | # Middle tensors (m x d x m x d) 326 | for _ in range(1, N - 1): 327 | middle_tensor = np.zeros((m, d, m, d)) 328 | for i in range(d): 329 | for j in range(m): 330 | middle_tensor[j, i, j, i] = 1.0 331 | tensors.append(middle_tensor) 332 | 333 | # Rightmost tensor (m x d x d) 334 | right_tensor = np.zeros((m, d, d)) 335 | for i in range(d): 336 | right_tensor[0, i, i] = 1.0 337 | tensors.append(right_tensor) 338 | 339 | return MPO(tensors) 340 | 341 | @staticmethod 342 | def random_incremental_mpo(N, d, seed, d2=None): 343 | if d2 is None: 344 | d2 = d 345 | mpo = [np.random.randn(d, seed, d2)] # Initialize with the first tensor, bond dimension 1 for the first tensor 346 | for i in range(0, N-2): 347 | mpo.append(np.random.randn(seed+i,d,seed+i+1,d2)) 348 | mpo.append(np.random.randn(seed+i+1, d,d2)) 349 | 350 | return MPO(mpo) 351 | -------------------------------------------------------------------------------- /code/tensornetwork/MPS.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import numpy.linalg as la 3 | import matplotlib.pyplot as plt 4 | 5 | # from .rounding import * 6 | from .MPO import MPO 7 | from .linalg import truncated_svd,lq 8 | from .stopping import Cutoff,no_truncation 9 | import copy 10 | import math 11 | 12 | class MPS: 13 | def __init__(self, mps, canform="None", rounded=False): 14 | self.tensors = mps 15 | self.N = len(mps) 16 | self.canform = canform 17 | self.rounded = rounded 18 | 19 | def copy(self): 20 | new_mps = copy.deepcopy(self) 21 | return new_mps 22 | 23 | def __len__(self): 24 | return self.N 25 | 26 | def __getitem__(self, index): 27 | return self.tensors[index] 28 | 29 | def __setitem__(self, index, value): 30 | self.tensors[index] = value 31 | 32 | #============Orthogonalization============ 33 | def canonize(self): 34 | if "None" != self.canform: 35 | return 36 | self.canonize_right() 37 | 38 | def canonize_left(self): 39 | if "Left" == self.canform: 40 | return 41 | 42 | # Qt, R = la.qr(self[-1].T) 43 | L,Q = lq(self[-1]) 44 | self[-1] = Q 45 | # L = R.T 46 | 47 | for i in range(self.N - 2, 0, -1): 48 | A = np.tensordot(self[i],L, (2,0)) 49 | Qt, R = la.qr((np.reshape(A, (A.shape[0],A.shape[1]*A.shape[2]))).T) 50 | Q = Qt.T 51 | self[i] = np.reshape(Q, (min(A.shape[0],A.shape[1]*A.shape[2]),A.shape[1],A.shape[2])) 52 | L= R.T 53 | self[0] = self[0] @ L 54 | self.canform = "Left" 55 | 56 | def canonize_left_blas(self) : 57 | 58 | if "Left" == self.canform: 59 | return 60 | 61 | Qt, R = la.qr(self[-1].T) 62 | self[-1] = Qt.T 63 | L = R.T 64 | 65 | for i in range(self.N - 2, 0, -1): 66 | # A = np.einsum("DdY,Yk->Ddk") 67 | mps_reshaped =self[i].reshape(-1,self[i].shape[2]) 68 | A = mps_reshaped @ L 69 | A = A.reshape(self[i].shape[0],self[i].shape[1],L.shape[1]) 70 | 71 | Qt, R = la.qr((np.reshape(A, (A.shape[0],A.shape[1]*A.shape[2]))).T) 72 | Q = Qt.T 73 | self[i] = np.reshape(Q, (min(A.shape[0],A.shape[1]*A.shape[2]),A.shape[1],A.shape[2])) 74 | L= R.T 75 | self[0] = self[0] @ L 76 | self.canform = "Left" 77 | 78 | def canonize_right(self): 79 | if "Right" == self.canform: 80 | return 81 | Q, R = la.qr(self[0]) 82 | self[0] = Q 83 | 84 | for i in range(1, self.N - 1): 85 | A = np.tensordot(R, self[i], (1, 0)) 86 | Q, R = la.qr(np.reshape(A, (A.shape[0] * A.shape[1], A.shape[2]))) 87 | self[i] = np.reshape(Q, (A.shape[0],A.shape[1],min(A.shape[2],A.shape[0]*A.shape[1]))) 88 | 89 | self[-1] = R @ self[-1] 90 | self.canform = "Right" 91 | 92 | #============Operations============ 93 | def eval(self, sites): 94 | A = sites[0] @ self[0] 95 | for i in range(1, self.N): 96 | A = sites[i] @ (np.tensordot(A, self[i], axes=(0, 0))) 97 | return float(A) 98 | 99 | def scale(self, scalar): 100 | self.canonize() 101 | if "Right" == self.canform: 102 | if np.iscomplexobj(scalar) and not np.iscomplexobj(self[-1]): 103 | self[-1] = self[-1].astype(np.complex128) 104 | self[-1] *= scalar 105 | else: 106 | if np.iscomplexobj(scalar) and not np.iscomplexobj(self[0]): 107 | self[0] = self[0].astype(np.complex128) 108 | self[0] *= scalar 109 | 110 | def add(self, mps2, subtract=False, compress=False, stop = Cutoff(1e-14)): 111 | mps = [] 112 | 113 | assert(self[0].shape[0] == mps2[0].shape[0]) 114 | m1 = self[0].shape[1] 115 | m2 = mps2[0].shape[1] 116 | new_site = np.zeros((self[0].shape[0], m1+m2),dtype="complex") 117 | new_site[:,0:m1] = self[0] 118 | new_site[:,m1:] = mps2[0] 119 | mps.append(new_site) 120 | 121 | for i in range(1,self.N-1): 122 | assert(self[i].shape[1] == mps2[i].shape[1]) 123 | m1 = self[i].shape[0] 124 | n1 = self[i].shape[2] 125 | m2 = mps2[i].shape[0] 126 | n2 = mps2[i].shape[2] 127 | new_site = np.zeros((m1+m2,self[i].shape[1],n1+n2),dtype="complex") 128 | new_site[0:m1,:,0:n1] = self[i] 129 | new_site[m1:,:,n1:] = mps2[i] 130 | mps.append(new_site) 131 | 132 | assert(self[-1].shape[1] == mps2[-1].shape[1]) 133 | m1 = self[-1].shape[0] 134 | m2 = mps2[-1].shape[0] 135 | new_site = np.zeros((m1+m2,self[-1].shape[1]),dtype="complex") 136 | new_site[0:m1,:] = self[-1] 137 | new_site[m1:,:] = (-1 if subtract else 1) * mps2[-1] 138 | mps.append(new_site) 139 | 140 | mps = MPS(mps) 141 | if compress: 142 | mps.round(stop=stop) 143 | 144 | return mps 145 | 146 | def sub(self, mps2, **kwargs): 147 | return self.add(mps2, subtract=True, **kwargs) 148 | 149 | def dagger(self): 150 | """ 151 | Computes the Hermitian conjugate (dagger) of the entire MPS. 152 | """ 153 | daggered_tensors = [np.conj(tensor) for tensor in self.tensors] 154 | return MPS(daggered_tensors, canform=self.canform, rounded=self.rounded) 155 | 156 | def norm(self): 157 | self.canonize() 158 | if "Right" == self.canform: 159 | return np.linalg.norm(self[-1],'fro') 160 | else: 161 | return np.linalg.norm(self[0],'fro') 162 | 163 | def normalize(self): 164 | self.canonize() 165 | if "Right" == self.canform: 166 | self[-1] /= np.linalg.norm(self[-1],'fro') 167 | else: 168 | self[0] /= np.linalg.norm(self[0],'fro') 169 | 170 | def round(self, **kwargs): #TODO add support for other rounding in randomcontraction.rounding 171 | from .rounding import round_left,round_right #TODO:possibly slow way of handling circular import exception 172 | if self.rounded: 173 | return 174 | elif "Left" == self.canform: 175 | round_right(self, **kwargs) 176 | else: 177 | round_left(self,**kwargs) 178 | 179 | def self_inner_product(self): #TODO: Blas me 180 | mps_c =self.dagger() 181 | site = np.einsum("dA,dX->AX",mps_c[0],self[0]) 182 | for i in range(1,self.N-1): 183 | temp = np.einsum("AX,XdY->AdY",site,self[i]) 184 | site = np.einsum("AdY,AdB->BY",temp,mps_c[i]) 185 | site = np.einsum("AY,Yd->Ad",site,self[-1]) 186 | inner_product = np.einsum("Ad,Ad",site,mps_c[-1]) 187 | return inner_product 188 | 189 | def inner_product(self,other): #TODO: merge fast inner product 190 | mps_c = other.dagger() 191 | site = np.einsum("dA,dX->AX",mps_c[0],self[0]) 192 | for i in range(1,self.N-1): 193 | 194 | temp = np.einsum("AX,XdY->AdY",site,self[i]) 195 | site = np.einsum("AdY,AdB->BY",temp,mps_c[i]) 196 | site = np.einsum("AY,Yd->Ad",site,self[-1]) 197 | inner_product = np.einsum("Ad,Ad",site,mps_c[-1]) 198 | return inner_product 199 | 200 | #============Operator Overloading=========== 201 | def __mul__(self, other): 202 | new_tensors = self.tensors.copy() 203 | 204 | if np.iscomplexobj(other): 205 | if other.real == 0: 206 | other_dtype = np.float64 207 | else: 208 | other_dtype = other.dtype 209 | else: 210 | other_dtype = other.dtype 211 | 212 | for i, tensor in enumerate(new_tensors): 213 | if self.canform == "Left" and i == 0: 214 | new_tensors[i] = tensor.astype(other_dtype) * other 215 | elif self.canform != "Left" and i == len(new_tensors) - 1: 216 | new_tensors[i] = tensor.astype(other_dtype) * other 217 | 218 | return MPS(new_tensors, canform=self.canform, rounded=self.rounded) 219 | 220 | def __div__(self, other): 221 | return (1.0/other) * self 222 | 223 | def __add__(self, other): 224 | return self.add(other, compress = True) 225 | 226 | def __sub__(self, other): 227 | return self.sub(other, compress=True) 228 | 229 | #============Misc============ 230 | def display_tensors(self): 231 | for t in self.tensors: 232 | print(t.shape) 233 | 234 | def bond_size(self,i): 235 | if i ==0: 236 | return self[0].shape[1] 237 | else: 238 | return self[i].shape[2] 239 | 240 | def max_bond_dim(self): 241 | maxbond = 0 242 | for i, t in enumerate(self.tensors): 243 | if i == 0: 244 | maxbond = t.shape[1] 245 | elif i == self.N - 1: 246 | maxbond = max(maxbond, t.shape[0]) 247 | else: 248 | b1, b2 = t.shape[0], t.shape[2] 249 | maxbond = max(maxbond, b1, b2) 250 | return maxbond 251 | 252 | def size(self): 253 | return self.N 254 | 255 | def show(self, max_width=None): 256 | from .misc import print_multi_line 257 | 258 | def check_orthogonality(tensor, index): 259 | if index == 0 or index == self.N - 1: 260 | reshaped_tensor = tensor 261 | else: 262 | reshaped_tensor = tensor.reshape(tensor.shape[0] * tensor.shape[1], tensor.shape[2]) 263 | 264 | left_product = np.dot(reshaped_tensor.conj().T, reshaped_tensor) 265 | right_identity = np.eye(left_product.shape[0]) 266 | 267 | left_orthogonal = np.allclose(left_product, right_identity) 268 | 269 | if index == 0 or index == self.N - 1: 270 | reshaped_tensor = tensor 271 | else: 272 | reshaped_tensor = tensor.reshape(tensor.shape[0], tensor.shape[1] * tensor.shape[2]) 273 | 274 | right_product = np.dot(reshaped_tensor, reshaped_tensor.conj().T) 275 | left_identity = np.eye(right_product.shape[0]) 276 | 277 | right_orthogonal = np.allclose(right_product, left_identity) 278 | 279 | if left_orthogonal and right_orthogonal: 280 | return 'both' 281 | elif left_orthogonal: 282 | return 'left' 283 | elif right_orthogonal: 284 | return 'right' 285 | else: 286 | return None 287 | 288 | l1 = "" 289 | l2 = "" 290 | 291 | for i in range(0, self.N - 1): 292 | bdim = self.bond_size(i) 293 | strl = len(str(bdim)) 294 | l1 += f"│{bdim}" 295 | 296 | orthogonality = check_orthogonality(self[i], i) 297 | if orthogonality == 'left': 298 | l2 += ">" + ("-" if bdim < 100 else "━") * max(strl , 1) 299 | elif orthogonality == 'right': 300 | l2 += "<" + ("-" if bdim < 100 else "━") * max(strl , 1) 301 | elif orthogonality == 'both': 302 | l2 += "■" + ("-" if bdim < 100 else "━") * max(strl , 1) 303 | else: 304 | l2 += "●" + ("-" if bdim < 100 else "━") * max(strl , 1) 305 | 306 | orthogonality = check_orthogonality(self[-1], self.N - 1) 307 | l1 += "│" 308 | 309 | if orthogonality == 'left': 310 | l2 += ">" 311 | elif orthogonality == 'right': 312 | l2 += "<" 313 | elif orthogonality == 'both': 314 | l2 += "■" 315 | else: 316 | l2 += "●" 317 | 318 | print_multi_line(l1, l2, max_width=max_width) 319 | 320 | def contract_all(self): 321 | # start with the first tensor 322 | result = self[0] 323 | 324 | # contract the tensors one by one 325 | for i in range(1,self.N): 326 | print(i) 327 | 328 | if i == 1: 329 | result = np.tensordot(result, self[i], axes=([1], [0])) # contract over the last dimension of result and the first dimension of mps[i] 330 | else: 331 | result = np.tensordot(result, self[i], axes=([-1], [0])) 332 | 333 | return result 334 | 335 | def is_left_orth(self): 336 | # Left to right sweep 337 | for i in range(0,self.N-1): 338 | tensor = self[i] 339 | if i == 0 : 340 | reshaped_tensor = tensor 341 | else: 342 | reshaped_tensor = tensor.reshape(tensor.shape[0]*tensor.shape[1],tensor.shape[2]) 343 | product = np.dot(reshaped_tensor.conj().T, reshaped_tensor) 344 | identity = np.eye(product.shape[0]) 345 | if not np.allclose(product, identity): 346 | print(f"Tensor at index {i} is not right-orthogonal.") 347 | plt.imshow(np.abs(product), cmap='mako', interpolation='nearest') 348 | plt.title(f"Heatmap of Product Matrix at tensor {i}") 349 | plt.colorbar() 350 | plt.show() 351 | assert False, "Orthogonality test failed" 352 | print("All tensors are left-orthogonal.") 353 | 354 | def is_right_orth(self): 355 | # Right to left sweep 356 | for i in reversed(range(2, self.N)): 357 | tensor = self[i] 358 | if i == self.N-1 : 359 | reshaped_tensor = tensor 360 | else: 361 | reshaped_tensor = tensor.reshape(tensor.shape[0],tensor.shape[1]*tensor.shape[2]) 362 | product = np.dot(reshaped_tensor, reshaped_tensor.conj().T) 363 | identity = np.eye(product.shape[0]) 364 | 365 | if not np.allclose(product, identity): 366 | print(f"Tensor at index {i} is not left-orthogonal.") 367 | # Visualize the product matrix as a heatmap 368 | plt.imshow(np.abs(product), cmap='mako', interpolation='nearest') 369 | plt.title(f"Heatmap of Product Matrix at tensor {i}") 370 | plt.colorbar() 371 | plt.show() 372 | assert False, "Orthogonality test failed" 373 | print("All tensors are right-orthogonal.") 374 | 375 | @staticmethod 376 | def mps_to_mpo(mps): 377 | """Converts an MPS to an MPO by adding an auxillary bond dimension of 1 """ 378 | mpo = [] 379 | # First tensor: shape (d, X, 1) 380 | first_tensor = mps.tensors[0] 381 | d, X = first_tensor.shape 382 | new_tensor = np.zeros((d, X, 1), dtype=first_tensor.dtype) 383 | new_tensor[:, :, 0] = first_tensor 384 | mpo.append(new_tensor) 385 | 386 | # Middle tensors: shape (X1, d, X2, 1) 387 | for tensor in mps.tensors[1:-1]: 388 | X1, d, X2 = tensor.shape 389 | new_tensor = np.zeros((X1, d, X2, 1), dtype=tensor.dtype) 390 | new_tensor[:, :, :, 0] = tensor 391 | mpo.append(new_tensor) 392 | 393 | # Last tensor: shape (X, d, 1) 394 | last_tensor = mps.tensors[-1] 395 | X, d = last_tensor.shape 396 | new_tensor = np.zeros((X, d, 1), dtype=tensor.dtype) 397 | new_tensor[:, :, 0] = last_tensor 398 | mpo.append(new_tensor) 399 | 400 | return MPO(mpo) 401 | 402 | @staticmethod 403 | def random_mps(n, m, d=2, random_tensor=np.random.randn, dtype=float): 404 | return MPS([random_tensor(d, m).astype(dtype)] + [random_tensor(m, d, m).astype(dtype) for _ in range(n - 2)] + [random_tensor(m, d).astype(dtype)]) 405 | 406 | @staticmethod 407 | def random_incremental_mps(n, d, seed,dtype=np.complex128): 408 | mps = [np.random.randn(d, seed).astype(dtype)] # Initialize with the first tensor, bond dimension 1 for the first tensor 409 | for i in range(0, n-2): 410 | mps.append(np.random.randn(seed+i,d,seed+i+1)) 411 | mps.append(np.random.randn(seed+i+1,d)) 412 | return MPS(mps) 413 | 414 | 415 | 416 | def norm(thing): 417 | return thing.norm() 418 | -------------------------------------------------------------------------------- /code/tensornetwork/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscamano/RandomMPOMPS/275e49b7c435a7c2352cc7e69cc4783327f893fc/code/tensornetwork/__init__.py -------------------------------------------------------------------------------- /code/tensornetwork/incrementalqr.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace py = pybind11; 9 | 10 | #include 11 | void printMatrix(double *A, int rows, int cols) { 12 | for (int i = 0; i < rows; ++i) { 13 | for (int j = 0; j < cols; ++j) { 14 | std::cout << A[j * rows + i] << " "; 15 | } 16 | std::cout << std::endl; 17 | } 18 | std::cout << std::endl; 19 | } 20 | 21 | void setup(py::array_t A_input, py::array_t tau_input, int m, int n) { 22 | auto buf = A_input.request(); 23 | double *A = static_cast(buf.ptr); 24 | 25 | buf = tau_input.request(); 26 | double *tau = static_cast(buf.ptr); 27 | 28 | int lwork = -1; 29 | int info = 0; 30 | double work_size = 0.0; 31 | 32 | // Query the optimal workspace size 33 | LAPACK_dgeqrf(&m, &n, A, &m, nullptr, &work_size, &lwork, &info); 34 | lwork = static_cast(work_size); 35 | std::vector work(lwork); 36 | 37 | // Compute QR factorization 38 | LAPACK_dgeqrf(&m, &n, A, &m, tau, work.data(), &lwork, &info); 39 | 40 | // Invert triangular part 41 | char uplo = 'U'; 42 | char diag = 'N'; 43 | LAPACK_dtrtri(&uplo, &diag, &n, A, &m, &info); 44 | } 45 | 46 | void add_cols(py::array_t A_input, py::array_t tau_input, int m, int n, int k) { 47 | auto buf = A_input.request(); 48 | double *A = static_cast(buf.ptr); 49 | double *new_data = A + m*n; 50 | 51 | buf = tau_input.request(); 52 | double *tau = static_cast(buf.ptr); 53 | double *new_tau = tau + n; 54 | 55 | // Determine size of work array 56 | char side = 'L'; 57 | char trans = 'T'; 58 | double work_size; 59 | int lwork = -1; 60 | int info; 61 | LAPACK_dormqr(&side, &trans, &m, &k, &n, A, &m, tau, new_data, &m, &work_size, &lwork, &info); 62 | lwork = static_cast(work_size); 63 | std::vector work(lwork); 64 | 65 | // Multiply new data by Q' 66 | LAPACK_dormqr(&side, &trans, &m, &k, &n, A, &m, tau, new_data, &m, work.data(), &lwork, &info); 67 | 68 | // Determine size of work array 69 | int mn = m - n; 70 | lwork = -1; 71 | LAPACK_dgeqrf(&mn, &k, new_data+n, &m, nullptr, &work_size, &lwork, &info); 72 | lwork = static_cast(work_size); 73 | if (lwork > work.size()) { 74 | work = std::vector(lwork); 75 | } 76 | 77 | // Compute QR factorization of lower right 78 | LAPACK_dgeqrf(&mn, &k, new_data+n, &m, new_tau, work.data(), &lwork, &info); 79 | 80 | // Invert bottom right of the inverse triangular matrix 81 | char uplo = 'U'; 82 | char diag = 'N'; 83 | LAPACK_dtrtri(&uplo, &diag, &k, new_data+n, &m, &info); 84 | 85 | // Compute the top right of the inverse triangular matrix inv(R)_12 = -inv(R_11) * R_12 * inv(R_22) 86 | cblas_dtrmm(CblasColMajor, CblasLeft, CblasUpper, CblasNoTrans, CblasNonUnit, n, k, -1.0, A, m, new_data, m); 87 | cblas_dtrmm(CblasColMajor, CblasRight, CblasUpper, CblasNoTrans, CblasNonUnit, n, k, 1.0, new_data+n, m, new_data, m); 88 | } 89 | 90 | void extract_q(py::array_t A_input, py::array_t tau_input, int m, int n) { 91 | auto buf = A_input.request(); 92 | double *A = static_cast(buf.ptr); 93 | 94 | buf = tau_input.request(); 95 | double *tau = static_cast(buf.ptr); 96 | 97 | double lwork_dbl; 98 | int lwork = -1; 99 | int info; 100 | LAPACK_dorgqr(&m, &n, &n, A, &m, tau, &lwork_dbl, &lwork, &info); 101 | lwork = static_cast(lwork_dbl); 102 | std::vector work(lwork); 103 | LAPACK_dorgqr(&m, &n, &n, A, &m, tau, work.data(), &lwork, &info); 104 | } 105 | 106 | double get_error_estimate(py::array_t A_input, int m, int n) { 107 | auto buf = A_input.request(); 108 | double *A = static_cast(buf.ptr); 109 | 110 | double output = 0.0; 111 | for (int row = 0; row < n; ++row) { 112 | double rownormsq = 0.0; 113 | for (int col = row; col < n; ++col) { 114 | rownormsq += A[row + m*col] * A[row + m*col]; 115 | } 116 | output += 1.0 / rownormsq / n; 117 | } 118 | return sqrt(output); 119 | } 120 | 121 | PYBIND11_MODULE(libincrementalqr, m) { 122 | m.def("setup", &setup, "Compute an in place QR decomposition of a data buffer and invert the triangular matrix"); 123 | m.def("add_cols", &add_cols, "Append columns to end of matrix, update QR factorization, and update triangular matrix inverse"); 124 | m.def("extract_q", &extract_q, "Get the Q factor"); 125 | m.def("get_error_estimate", &get_error_estimate, "Randomized QB approximation leave-one-out error estimate"); 126 | } -------------------------------------------------------------------------------- /code/tensornetwork/incrementalqr.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/env python 2 | import numpy as np 3 | import scipy as sp 4 | import sys 5 | import os 6 | 7 | #Adjust as needed 8 | os.environ['OMP_NUM_THREADS'] = '11' 9 | os.environ['OPENBLAS_NUM_THREADS'] = '11' 10 | 11 | #Adjust locally 12 | sys.path.append(r'../tensornetwork/build/') 13 | 14 | try: 15 | from libincrementalqr import setup, add_cols, extract_q, get_error_estimate 16 | print("Using C++ implementation for incQR") 17 | libincrementalqr_available = True 18 | except Exception as e: 19 | print("Using Python implementation for incQR") 20 | 21 | print("An exception occurred:", e) 22 | libincrementalqr_available = False 23 | 24 | def complex_to_real(A): 25 | rows, cols = A.shape 26 | 27 | B = np.zeros((2*rows, 2*cols), dtype=float) 28 | 29 | real_A = np.real(A) 30 | imag_A = np.imag(A) 31 | 32 | B[0::2, 0::2] = real_A 33 | B[0::2, 1::2] = -imag_A 34 | B[1::2, 0::2] = imag_A 35 | B[1::2, 1::2] = real_A 36 | 37 | return B 38 | 39 | def real_to_complex(B): 40 | rows, cols = B.shape 41 | assert rows % 2 == 0 and cols % 2 == 0, "Input matrix dimensions must be even." 42 | 43 | new_rows, new_cols = rows // 2, cols // 2 44 | 45 | real_part = B[0::2, 0::2] 46 | imag_part = B[1::2, 0::2] 47 | 48 | return real_part + 1j * imag_part 49 | 50 | class IncrementalQR(object): 51 | 52 | def _libincrementalqr_available(self): 53 | return libincrementalqr_available and self.use_cpp_if_available 54 | 55 | def _complex_to_real(self): 56 | return self._libincrementalqr_available() and self.dtype == complex 57 | 58 | def __init__(self, data, size = None, use_cpp_if_available=True): 59 | self.use_cpp_if_available = use_cpp_if_available 60 | self.dtype = data.dtype 61 | 62 | self.m = data.shape[0] 63 | self.n = data.shape[1] 64 | assert(self.m >= self.n) 65 | if self._complex_to_real(): 66 | self.m *= 2 67 | self.n *= 2 68 | 69 | 70 | self.size = size 71 | if self.size is None: 72 | self.size = 2 * self.n 73 | assert(self.size >= self.n) 74 | 75 | if self._complex_to_real(): 76 | self.data = np.zeros((self.m, self.size), order='F', dtype=float) 77 | else: 78 | self.data = np.zeros((self.m, self.size), order='F', dtype=self.dtype) 79 | if self._complex_to_real(): 80 | self.data[:,:self.n] = complex_to_real(data) 81 | else: 82 | self.data[:,:self.n] = data 83 | self.tau = np.zeros(self.size, dtype=self.data.dtype) 84 | if self._libincrementalqr_available(): 85 | setup(self.data, self.tau, self.m, self.n) 86 | else: 87 | self._setup() 88 | self.open = True 89 | 90 | def _setup(self): 91 | # QR factorization 92 | geqrf = sp.linalg.get_lapack_funcs('geqrf', dtype=self.data.dtype) 93 | self.data[:,:self.n], self.tau[:self.n], _, _ = geqrf(self.data[:,:self.n]) 94 | 95 | # Invert upper triangular part 96 | trtri = sp.linalg.get_lapack_funcs('trtri', dtype=self.data.dtype) 97 | self.data[:self.n,:self.n] = trtri(self.data[:self.n,:self.n])[0] 98 | 99 | def _resize(self, new_size = None): 100 | if new_size == None: 101 | new_size = 2 * self.size 102 | assert(new_size > self.size) 103 | 104 | tmp = self.data 105 | self.data = np.zeros((self.m, new_size), order='F', dtype=self.data.dtype) 106 | self.data[:,:self.size] = tmp 107 | 108 | tmp = self.tau 109 | self.tau = np.zeros(new_size, dtype=self.data.dtype) 110 | self.tau[:self.size] = tmp 111 | 112 | self.size = new_size 113 | 114 | def append(self, new_data): 115 | assert(self.open) 116 | if self._complex_to_real(): 117 | assert(self.m == 2*new_data.shape[0]) 118 | k = 2*new_data.shape[1] 119 | else: 120 | assert(self.m == new_data.shape[0]) 121 | k = new_data.shape[1] 122 | if self.n + k > self.size: 123 | assert(self.n + k <= self.m) 124 | self._resize(new_size=self.n + k) # Ensure enough space 125 | 126 | if self._libincrementalqr_available(): 127 | if self._complex_to_real(): 128 | # print(self.data.shape, self.n, new_data.shape) 129 | self.data[:, self.n:self.n + k] = complex_to_real(new_data) 130 | else: 131 | self.data[:, self.n:self.n + k] = new_data 132 | add_cols(self.data, self.tau, self.m, self.n, k) 133 | else: 134 | # Multiply new data by Q' 135 | ormqr = sp.linalg.get_lapack_funcs('ormqr', dtype=self.data.dtype) 136 | transpose = 'T' if np.isrealobj(self.data) else 'C' 137 | lwork = int(np.real(ormqr('L', transpose, self.data[:,:self.n], self.tau[:self.n], new_data, -1)[1][0])) 138 | self.data[:,self.n:self.n+k] = ormqr('L', transpose, self.data[:,:self.n], self.tau[:self.n], new_data, lwork)[0] 139 | 140 | # QR factorize bottom part of new data 141 | geqrf = sp.linalg.get_lapack_funcs('geqrf', dtype=self.data.dtype) 142 | self.data[self.n:,self.n:self.n+k], self.tau[self.n:self.n+k], _, _ = geqrf(self.data[self.n:,self.n:self.n+k]) 143 | 144 | # Invert triangular part of new QR factor 145 | trtri = sp.linalg.get_lapack_funcs('trtri', dtype=self.data.dtype) 146 | self.data[self.n:self.n+k,self.n:self.n+k] = trtri(self.data[self.n:self.n+k,self.n:self.n+k])[0] 147 | 148 | # Set top left part of new data 149 | self.data[:self.n,self.n:self.n+k] = -np.triu(self.data[:self.n,:self.n]) @ self.data[:self.n,self.n:self.n+k] @ np.triu(self.data[self.n:self.n+k,self.n:self.n+k]) 150 | 151 | self.n += k 152 | 153 | def error_estimate(self): 154 | assert(self.open) 155 | if self._libincrementalqr_available(): 156 | return get_error_estimate(self.data, self.m, self.n) 157 | else: 158 | return np.sqrt(sum(np.linalg.norm(np.triu(self.data[:self.n,:self.n]), axis=1) ** -2) / self.n) 159 | 160 | def get_q(self): 161 | if self.open: 162 | if self._libincrementalqr_available(): 163 | import time 164 | start = time.time() 165 | extract_q(self.data, self.tau, self.m, self.n) 166 | self.open = False 167 | if self._complex_to_real(): 168 | start = time.time() 169 | output = real_to_complex(self.data[:, :self.n]) 170 | return output 171 | else: 172 | return self.data[:, :self.n] 173 | else: 174 | orgqr = sp.linalg.get_lapack_funcs('orgqr', dtype=self.data.dtype) 175 | return orgqr(self.data[:,:self.n], self.tau[:self.n])[0] 176 | 177 | if __name__ == "__main__": 178 | from time import time 179 | import numpy as np 180 | 181 | A = np.random.randn(10000, 200) 182 | B = np.random.randn(10000, 200) 183 | 184 | # C++ implementation 185 | print('C++ implementation') 186 | total_start = time() 187 | 188 | start = time() 189 | iqr = IncrementalQR(A) 190 | total = time() - start 191 | print("Initialize\t{}".format(total)) 192 | 193 | start = time() 194 | iqr.append(B) 195 | total = time() - start 196 | print("Append\t{}".format(total)) 197 | 198 | start = time() 199 | err_cpp = iqr.error_estimate() 200 | total = time() - start 201 | print("Error estimation\t{}".format(total)) 202 | 203 | start = time() 204 | Q_cpp = iqr.get_q() 205 | total = time() - start 206 | print("Get Q\t{}".format(total)) 207 | 208 | total_time_cpp = time() - total_start 209 | print("Total time for C++ implementation\t{}".format(total_time_cpp)) 210 | print("") 211 | 212 | # Scipy implementation 213 | print('Scipy implementation') 214 | total_start = time() 215 | 216 | start = time() 217 | iqr = IncrementalQR(A, use_cpp_if_available=False) 218 | total = time() - start 219 | print("Initialize\t{}".format(total)) 220 | 221 | start = time() 222 | iqr.append(B) 223 | total = time() - start 224 | print("Append\t{}".format(total)) 225 | 226 | start = time() 227 | err_sp = iqr.error_estimate() 228 | total = time() - start 229 | print("Error estimation\t{}".format(total)) 230 | 231 | start = time() 232 | Q_sp = iqr.get_q() 233 | total = time() - start 234 | print("Get Q\t{}".format(total)) 235 | 236 | total_time_sp = time() - total_start 237 | print("Total time for Scipy implementation\t{}".format(total_time_sp)) 238 | 239 | assert(np.allclose(Q_cpp, Q_sp)) 240 | assert(np.allclose(err_cpp, err_sp)) 241 | print("Accuracy checks passed!") 242 | print("") 243 | 244 | # C++ implementation (complex) 245 | print('C++ implementation (complex)') 246 | total_start = time() 247 | 248 | start = time() 249 | iqr = IncrementalQR(A.astype(np.cdouble)) 250 | total = time() - start 251 | print("Initialize\t{}".format(total)) 252 | 253 | start = time() 254 | iqr.append(B.astype(np.cdouble)) 255 | total = time() - start 256 | print("Append\t{}".format(total)) 257 | 258 | start = time() 259 | err_cpp_C = iqr.error_estimate() 260 | total = time() - start 261 | print("Error estimation\t{}".format(total)) 262 | 263 | start = time() 264 | Q_cpp_C = iqr.get_q() 265 | total = time() - start 266 | print("Get Q\t{}".format(total)) 267 | 268 | total_time_cpp_complex = time() - total_start 269 | print("Total time for C++ implementation (complex)\t{}".format(total_time_cpp_complex)) 270 | 271 | assert(np.allclose(Q_cpp, Q_cpp_C)) 272 | assert(np.allclose(err_cpp, err_cpp_C)) 273 | print("Accuracy checks passed!") 274 | print("") 275 | 276 | # Scipy implementation (complex) 277 | print('Scipy implementation (complex)') 278 | total_start = time() 279 | 280 | start = time() 281 | iqr = IncrementalQR(A.astype(np.cdouble), use_cpp_if_available=False) 282 | total = time() - start 283 | print("Initialize\t{}".format(total)) 284 | 285 | start = time() 286 | iqr.append(B.astype(np.cdouble)) 287 | total = time() - start 288 | print("Append\t{}".format(total)) 289 | 290 | start = time() 291 | err_sp = iqr.error_estimate() 292 | total = time() - start 293 | print("Error estimation\t{}".format(total)) 294 | 295 | start = time() 296 | Q_sp = iqr.get_q() 297 | total = time() - start 298 | print("Get Q\t{}".format(total)) 299 | 300 | total_time_sp_complex = time() - total_start 301 | print("Total time for Scipy implementation (complex)\t{}".format(total_time_sp_complex)) 302 | 303 | assert(np.allclose(Q_cpp, Q_sp)) 304 | assert(np.allclose(err_cpp, err_sp)) 305 | print("Accuracy checks passed!") -------------------------------------------------------------------------------- /code/tensornetwork/linalg.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import numpy.linalg as la 3 | from .stopping import Cutoff,FixedDimension 4 | from scipy.sparse.linalg import svds 5 | 6 | def truncated_svd(A, stop = Cutoff(1e-14), abstol = 0.0): 7 | ''' 8 | Output U, s, Vt of the minimal sizes 9 | such that norm(A - U * diag(s) * Vt, 'fro') 10 | <= cutoff * norm(A, 'fro') 11 | ''' 12 | 13 | if stop.maxdim is None: 14 | stop.maxdim = np.Inf 15 | 16 | U, s, Vt = la.svd(A, full_matrices=False) 17 | if (stop.cutoff is None) and (stop.outputdim is None) and abstol == 0.0: 18 | return U, s, Vt 19 | idx = len(s) 20 | if stop.outputdim is None: 21 | snorm = la.norm(s) 22 | cutoff = (snorm * stop.cutoff + abstol) ** 2 23 | tail_norm_sq = 0.0 24 | cutoff_met = False 25 | 26 | for i in range(len(s)-1,-1,-1): 27 | tail_norm_sq += s[i] ** 2 28 | if tail_norm_sq > cutoff: 29 | idx = i+1 30 | cutoff_met = True 31 | break 32 | if cutoff_met: 33 | idx = min(idx, stop.maxdim) 34 | else: 35 | idx = 0 36 | 37 | else: 38 | idx = min(idx, stop.outputdim) 39 | 40 | return U[:,0:idx], s[0:idx], Vt[0:idx, :] 41 | 42 | def truncated_eigendecomposition(A, stop = Cutoff(1e-14)): 43 | ''' 44 | Perform a truncated eigendecomposition on matrix A such that the sum of 45 | the eigenvalues beyond the truncation point is less than the specified cutoff. 46 | ''' 47 | 48 | eigenvalues, eigenvectors = la.eigh(A) 49 | idx = np.abs(eigenvalues).argsort()[::-1] 50 | eigenvalues = eigenvalues[idx] 51 | eigenvectors = eigenvectors[:, idx] 52 | 53 | if (stop.cutoff is None) and (stop.outputdim is None): 54 | return eigenvalues, eigenvectors 55 | 56 | truncation_idx = len(eigenvalues) # Default to no truncation. 57 | if stop.outputdim is None: 58 | total_frobenius_norm = np.sqrt(np.sum(eigenvalues**2)) 59 | frobenius_norm_cutoff = total_frobenius_norm * stop.cutoff**2 60 | tail_frobenius_norm = 0.0 61 | 62 | for i in range(len(eigenvalues) - 1, -1, -1): 63 | tail_frobenius_norm += eigenvalues[i]**2 64 | if np.sqrt(tail_frobenius_norm) > frobenius_norm_cutoff: 65 | truncation_idx = i + 1 66 | break 67 | truncation_idx = min(truncation_idx, stop.maxdim) 68 | else: 69 | truncation_idx = min(truncation_idx, stop.outputdim) 70 | 71 | return eigenvalues[:truncation_idx], eigenvectors[:, :truncation_idx], np.conj(eigenvectors[:, :truncation_idx]).T 72 | 73 | def lq(A): 74 | ''' 75 | A simple wrapper for QR 76 | ''' 77 | Qt, R = la.qr(A.T) 78 | return R.T, Qt.T 79 | 80 | def lanczos_fun(v, matvec, fun=np.exp, k=5): 81 | if isinstance(matvec, np.ndarray): 82 | A = matvec 83 | matvec = lambda x: A @ x 84 | Q = np.zeros((len(v), k+1), dtype=np.complex128) 85 | gamma = np.linalg.norm(v) 86 | Q[:,0] = v / gamma 87 | T = np.zeros((k+1,k+1), dtype=np.complex128) 88 | qold = np.zeros(Q[:,0].shape) 89 | beta = 0.0 90 | for i in range(k): 91 | Q[:,i+1] = matvec(Q[:,i]) - beta * qold 92 | T[i,i] = np.real(Q[:,i+1].conj().T @ Q[:,i]) 93 | Q[:,i+1] -= T[i,i] * Q[:,i] 94 | beta = np.linalg.norm(Q[:,i+1]) 95 | if beta < 1e-12 * gamma: 96 | k = i+1 97 | break 98 | T[i,i+1] = beta 99 | T[i+1,i] = beta 100 | Q[:,i+1] = Q[:,i+1] / beta 101 | qold = Q[:,i] 102 | T = T[:k,:k] 103 | Q = Q[:,:k] 104 | Tevals, Tevecs = np.linalg.eigh(T) 105 | e1 = np.zeros(k, dtype=v.dtype) 106 | e1[0] = 1 107 | output = gamma * (Q @ (Tevecs @ (fun(Tevals) * (Tevecs.conj().T @ e1)))) 108 | return output 109 | 110 | def lan_exp(v, A, t=1, k=5): 111 | return lanczos_fun(v, A, fun=lambda x: np.exp(t*x), k = k) 112 | 113 | def krylov_matrix_exp(v, A, h=1, k=5): 114 | nrm = np.linalg.norm(v) 115 | v = v / nrm 116 | V, H = arnoldi(A, v, k) 117 | 118 | exp_hH = np.linalg.expm(h * H[:k, :k]) 119 | result = nrm * np.dot(V[:, :k], exp_hH[:, 0]) 120 | return result 121 | 122 | def arnoldi(A, v0, k): 123 | """ 124 | Arnoldi's method with partial reorthogonalization 125 | """ 126 | n = A.shape[0] 127 | V = np.zeros((n, k + 1), dtype=A.dtype) 128 | H = np.zeros((k + 1, k), dtype=A.dtype) 129 | 130 | V[:, 0] = v0 / np.linalg.norm(v0) 131 | for m in range(k): 132 | vt = A @ V[:, m] 133 | 134 | # orthogonalize vt against all previous vectors in V 135 | for j in range(m + 1): 136 | H[j, m] = np.vdot(V[:, j], vt) # np.vdot invokes complex conjugate transpose here 137 | vt -= H[j, m] * V[:, j] 138 | 139 | H[m + 1, m] = np.linalg.norm(vt) 140 | 141 | # reorthogonalize 142 | for j in range(m + 1): 143 | correction = np.vdot(V[:, j], vt) 144 | vt -= correction * V[:, j] 145 | H[j, m] += correction 146 | 147 | H[m + 1, m] = np.linalg.norm(vt) 148 | 149 | # update basis 150 | if H[m + 1, m] > 1e-10 and m != k - 1: 151 | V[:, m + 1] = vt / H[m + 1, m] 152 | 153 | return V, H 154 | -------------------------------------------------------------------------------- /code/tensornetwork/misc.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import numpy.linalg as la 3 | import scipy 4 | from .MPO import MPO 5 | from .MPS import MPS 6 | from .stopping import * 7 | from .linalg import * 8 | import time 9 | 10 | def TTSVD(tensor, epsilon): 11 | d = len(tensor.shape) 12 | norm_A = np.linalg.norm(tensor) 13 | delta = epsilon / np.sqrt(d-1) * norm_A 14 | 15 | cores = [] 16 | tensor_shape = tensor.shape 17 | C = tensor.copy() 18 | r_prev = 1 19 | 20 | for k in range(d - 1): 21 | n_k = tensor_shape[k] 22 | C = C.reshape(r_prev * n_k, -1) 23 | U, S, Vh = truncated_svd(C, stop=Cutoff(delta)) 24 | r_k = U.shape[1] 25 | 26 | G_k = U.reshape(r_prev, n_k, r_k) 27 | if k == 0: 28 | cores.append(G_k.reshape(G_k.shape[1],G_k.shape[2])) 29 | else: 30 | cores.append(G_k) 31 | C = np.dot(np.diag(S), Vh) 32 | r_prev = r_k 33 | 34 | cores.append(C) # The last core 35 | 36 | return MPS(cores) 37 | 38 | #=============================================== 39 | # Utility functions 40 | #=============================================== 41 | 42 | def print_multi_line(*lines, max_width=None): 43 | """Print multiple lines, with a maximum width. 44 | """ 45 | if max_width is None: 46 | import shutil 47 | max_width, _ = shutil.get_terminal_size() 48 | 49 | max_line_lenth = max(len(ln) for ln in lines) 50 | 51 | if max_line_lenth <= max_width: 52 | for ln in lines: 53 | print(ln) 54 | 55 | else: # pragma: no cover 56 | max_width -= 10 # for ellipses and pad 57 | n_lines = len(lines) 58 | n_blocks = (max_line_lenth - 1) // max_width + 1 59 | 60 | for i in range(n_blocks): 61 | if i == 0: 62 | for j, l in enumerate(lines): 63 | print( 64 | "..." if j == n_lines // 2 else " ", 65 | l[i * max_width:(i + 1) * max_width], 66 | "..." if j == n_lines // 2 else " " 67 | ) 68 | print(("{:^" + str(max_width) + "}").format("...")) 69 | elif i == n_blocks - 1: 70 | for ln in lines: 71 | print(" ", ln[i * max_width:(i + 1) * max_width]) 72 | else: 73 | for j, ln in enumerate(lines): 74 | print( 75 | "..." if j == n_lines // 2 else " ", 76 | ln[i * max_width:(i + 1) * max_width], 77 | "..." if j == n_lines // 2 else " ", 78 | ) 79 | print(("{:^" + str(max_width) + "}").format("...")) 80 | 81 | def check_randomized_apply(H, psi, cap, output, j, verbose=False, cutoff=1e-10): 82 | sites = sites_for_mpo(H) 83 | 84 | # sites * H * psi 85 | Hsite = np.tensordot(H[0], sites[0], (0,0)) 86 | env = np.tensordot(Hsite, psi[0], (1,0)) 87 | if 0 == j-2: 88 | mpsmpoenv = env 89 | for i in range(1,len(H)-1): 90 | Hsite = np.tensordot(H[i], sites[i], (1,0)) 91 | Hsitepsi = np.tensordot(Hsite, psi[i], (2,1)) 92 | env = np.tensordot(env, Hsitepsi, ([0,1],[0,2])) 93 | if i == j-2: 94 | mpsmpoenv = env 95 | Hsite = np.tensordot(H[-1], sites[-1], (1,0)) 96 | Hsitepsi = np.tensordot(Hsite, psi[-1], (1,1)) 97 | result = float(np.tensordot(env, Hsitepsi, ([0,1],[0,1]))) 98 | 99 | # Randomized algorithm result 100 | env = np.tensordot(mpsmpoenv, cap, ([0,1],[1,2])) 101 | for i in range(j-1,len(H)): 102 | env = np.tensordot(env, output[i], (0,0)) 103 | env = np.tensordot(env, sites[i], (0,0)) 104 | result2 = float(env) 105 | 106 | if verbose: 107 | print("Randomized error check {:6.2e} ({:6.2e}, {:6.2e})".format(np.abs(result - result2)/np.abs(result), result, result2)) 108 | 109 | if abs(result - result2) > cutoff * abs(result): 110 | raise UserWarning("Random check indicates descrepancy between answer and algorithm output ({}, {})".format(result, result2)) 111 | 112 | def maxlinkdim(M): 113 | maxbond = 0 114 | N=len(M) 115 | for i, t in enumerate(M): 116 | if i == 0: 117 | maxbond = t.shape[1] 118 | elif i == N - 1: 119 | maxbond = max(maxbond, t.shape[0]) 120 | else: 121 | b1, b2 = t.shape[0], t.shape[2] 122 | maxbond = max(maxbond, b1, b2) 123 | return maxbond 124 | -------------------------------------------------------------------------------- /code/tensornetwork/rounding.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import numpy.linalg as la 3 | import matplotlib.pyplot as plt 4 | import seaborn as sns 5 | 6 | from .linalg import truncated_svd,truncated_eigendecomposition 7 | 8 | from .MPS import MPS 9 | from .stopping import Cutoff,no_truncation 10 | import copy 11 | import math 12 | 13 | 14 | #============ Basic MPS Rounding============ 15 | def round_left(mps, **kwargs): 16 | if mps.rounded: 17 | if mps.canform == "Left": 18 | return 19 | else: 20 | mps.canonize_left() 21 | return 22 | mps.canonize_right() 23 | 24 | U, s, Vt = truncated_svd(mps[-1], **kwargs) 25 | mps[-1] = Vt 26 | 27 | for i in range(mps.N - 2, 0, -1): 28 | A = mps[i] @ (U * s) 29 | U, s, Vt = truncated_svd(np.reshape(A, (A.shape[0], A.shape[1] * A.shape[2])), **kwargs) 30 | mps[i] = np.reshape(Vt, (len(s), A.shape[1], A.shape[2])) 31 | 32 | mps[0] = mps[0] @ (U * s) 33 | 34 | mps.canform = "Left" 35 | mps.rounded = True 36 | 37 | def round_right(mps, **kwargs): 38 | if mps.rounded: 39 | if mps.canform == "Right": 40 | return 41 | else: 42 | mps.canonize_right() 43 | return 44 | 45 | mps.canonize_left() 46 | 47 | U, s, Vt = truncated_svd(mps[0], **kwargs) 48 | mps[0] = U 49 | 50 | for i in range(1, mps.N - 1): 51 | A = np.tensordot(s[:,np.newaxis] * Vt, mps[i], (1,0)) 52 | U, s, Vt = truncated_svd(np.reshape(A, (A.shape[0] * A.shape[1], A.shape[2])), **kwargs) 53 | mps[i] = np.reshape(U, (A.shape[0], A.shape[1], len(s))) 54 | 55 | mps[-1] = np.tensordot(s[:,np.newaxis] * Vt, mps[-1], (1,0)) 56 | 57 | mps.canform = "Right" 58 | mps.rounded = True 59 | 60 | #============ Rounding algoithm from https://tensornetwork.org/mps/index.html#Perez-Garcia:2007_6 =========== 61 | 62 | def density_matrix_rounding(mps,stop=Cutoff(1e-14)): 63 | mps_c = mps.dagger() 64 | N = mps.N 65 | mps_out = [None] * N 66 | E= [None] * (N-1) 67 | 68 | # ========================================== 69 | # 0. [Envs] 70 | # ========================================== 71 | # E[0] = np.einsum("dX,dA->XA",mps[0],mps_c[0]) 72 | E[0] = mps[0].T @ mps_c[0] # Resulting shape (XA) 73 | for i in range(1,N-1): 74 | # E[i] = np.einsum("XA,XdY->AdY",E[i-1],mps[i]) 75 | E[i] = E[i-1].T @ mps[i].reshape(mps[i].shape[0],-1) # Resulting shape A,dY 76 | E[i] = E[i].reshape(E[i].shape[0],mps[i].shape[1],mps[i].shape[2]) #Reshape to (A,d,Y) 77 | 78 | # E[i] = np.einsum("AdY,AdB->YB",E[i],mps_c[i]) 79 | E_transposed = E[i].transpose(2,0,1) 80 | E[i] = E_transposed.reshape(E_transposed.shape[0],-1) @ mps_c[i].reshape(-1,mps_c[i].shape[-1]) 81 | 82 | # ========================================== 83 | # 3. [First DM] 84 | # ========================================== 85 | 86 | # rho = np.einsum("XA,Xd->dA",E[-1],mps[-1]) 87 | rho = (E[-1].T @ mps[-1]).T 88 | # rho = np.einsum("dA,Ak->dk",rho,mps_c[-1]) 89 | rho = rho @ mps_c[-1] 90 | _, U, Udag = truncated_eigendecomposition(rho,stop=stop) 91 | mps_out[-1] = U 92 | 93 | # ========================================== 94 | # 3. [First Cap] 95 | # ========================================== 96 | # M_top = np.einsum("dk,Xd->Xk", Udag, mps[-1]) 97 | M_top = mps[-1] @ Udag 98 | 99 | for j in reversed(range(1, N-1)): 100 | # top = np.einsum("Yk,XdY->Xdk",M_top,mps[j]) 101 | top = mps[j].reshape(-1,mps[j].shape[-1]) @ M_top #Resuting shape (Xd,k) 102 | top = top.reshape(mps[j].shape[0],mps[j].shape[1],M_top.shape[1]) 103 | 104 | bottom = np.conj(top) 105 | 106 | # rho = np.einsum("XA,Xdk->Adk",E[j-1],top) 107 | rho = E[j-1].T @ top.reshape(top.shape[0],-1) # Resulting shape (A,dk) 108 | rho = rho.reshape(rho.shape[0],top.shape[1],top.shape[2]) 109 | 110 | # rho = np.einsum("Adk,Alj->dklj",rho,bottom) 111 | rho_transposed = rho.transpose(1,2,0) 112 | rho = rho_transposed.reshape(-1,rho_transposed.shape[2]) @ bottom.reshape(bottom.shape[0],-1) # Resulting shape dk,lj 113 | 114 | _,U,Udag = truncated_eigendecomposition(rho,stop=stop) #U is (dk,x) 115 | 116 | U = U.reshape(top.shape[1],top.shape[2],U.shape[-1]) #(d,k,x) 117 | U = U.transpose(2,0,1) #(x,d,k) 118 | mps_out[j] = U 119 | 120 | # M_top = np.einsum("xdk,Xdk->Xx",U,top) 121 | top_transposed = top.transpose(1,2,0) 122 | M_top = U.reshape(U.shape[0],-1) @ top_transposed.reshape(-1,top_transposed.shape[-1]) 123 | M_top = M_top.T 124 | 125 | # mps_out[0]= np.einsum("dX,Xx->dx",mps[0],M_top) 126 | mps_out[0] = mps[0] @ M_top 127 | return MPS(mps_out) 128 | 129 | #============ Rounding Techniques from arxiv.org/abs/2110.04393 ============ 130 | def dass_round(mps,stop=Cutoff(1e-14)): 131 | if mps.rounded==False: 132 | mps.canonize_left() 133 | 134 | if stop.cutoff!= None: 135 | normY = mps.norm() 136 | tau = stop.cutoff * normY / math.sqrt(mps.N - 1) 137 | stop = Cutoff(tau) 138 | 139 | Q,R = la.qr(mps[0], mode='reduced') 140 | U, s, Vt = truncated_svd(R, stop=stop) 141 | mps[0] = Q @ U 142 | for i in range(1, mps.N - 1): 143 | A = np.tensordot(s[:,np.newaxis] * Vt, mps[i], (1,0)) 144 | 145 | Q,R = la.qr(np.reshape(A, (A.shape[0] * A.shape[1], A.shape[2])), mode='reduced') 146 | U, s, Vt = truncated_svd(R, stop=stop) 147 | mps[i] = np.reshape(Q @ U, (A.shape[0], A.shape[1], len(s))) 148 | 149 | mps[-1] = np.tensordot(s[:,np.newaxis] * Vt, mps[-1], (1,0)) 150 | mps.canform = "left" 151 | mps.rounded = True 152 | 153 | def orth_then_rand(mps,r): 154 | mps.round_left() 155 | 156 | Z = mps[0] 157 | sketch = Z @ np.random.randn(Z.shape[1],r) 158 | 159 | Q,_ = la.qr(sketch, mode='reduced') 160 | mps[0] = Q 161 | 162 | M = Q.T @ Z 163 | mps[1] = np.tensordot(M,mps[1],axes=(1,0)) 164 | 165 | for i in range(1, mps.N-1): 166 | Z = mps[i] 167 | sketch = np.tensordot(Z ,np.random.randn(Z.shape[2],r),axes=(2,0)) 168 | 169 | Q,_ = la.qr(np.reshape(sketch,(sketch.shape[0]*sketch.shape[1],sketch.shape[2])),mode='reduced') 170 | mps[i] = np.reshape(Q,(mps[i-1].shape[-1],mps[i].shape[1],Q.shape[-1])) 171 | 172 | M = Q.T @ np.reshape(Z,(Z.shape[0]*Z.shape[1],Z.shape[2])) 173 | mps[i+1] = np.tensordot(M,mps[i+1],axes=(1,0)) 174 | 175 | def rand_then_orth(mps,r,finalround=False,stop=Cutoff(1e-14)): 176 | if r is None: 177 | r = mps[0].shape[1] 178 | #==================Environment tensors ================== 179 | N = mps.N 180 | R = MPS.random_mps(N,r,mps[1].shape[1]) 181 | W = TTpartialContractionsRL(mps,R) 182 | #==================Random Section================== 183 | Q, _ = la.qr(np.tensordot(mps[0], W[0], axes=(1, 0)), mode='reduced') 184 | M = np.tensordot(Q.T, mps[0], axes=(1, 0)) 185 | mps[0] = Q 186 | 187 | for j in range(1, N - 1): 188 | mps[j] = np.tensordot(M,mps[j],axes=(1,0)) 189 | Z = np.reshape(mps[j],(mps[j].shape[0]*mps[j].shape[1],mps[j].shape[2])) 190 | sketch = np.tensordot(Z,W[j],axes=(1,0)) 191 | 192 | Q,_ = la.qr(sketch,mode='reduced') 193 | mps[j]= np.reshape(Q,(mps[j-1].shape[-1],mps[j].shape[1],Q.shape[1])) 194 | M = np.tensordot(Q.T,Z,axes=(1,0)) 195 | 196 | mps[-1]= np.tensordot(M,mps[-1],axes=(1,0)) 197 | mps.rounded = True 198 | #==================Final rounding================== 199 | if finalround: 200 | if stop.cutoff != None: 201 | normY = np.linalg.norm(mps[-1],'fro') 202 | tau = stop.cutoff * normY / math.sqrt(mps.N - 1) 203 | stop= Cutoff(tau) 204 | 205 | Q,R = la.qr(mps[0], mode='reduced') 206 | U, s, Vt = truncated_svd(R, stop=stop) 207 | mps[0] = Q @ U 208 | 209 | for i in range(1, mps.N - 1): 210 | A = np.tensordot(s[:,np.newaxis] * Vt, mps[i], (1,0)) 211 | Q,R = la.qr(np.reshape(A, (A.shape[0] * A.shape[1], A.shape[2])), mode='reduced') 212 | U, s, Vt = truncated_svd(R, stop=stop) 213 | mps[i] = np.reshape(Q @ U, (A.shape[0], A.shape[1], len(s))) 214 | 215 | mps[-1] = np.tensordot(s[:,np.newaxis] * Vt, mps[-1], (1,0)) 216 | 217 | def nystrom_round(mps, l, stop=no_truncation()): 218 | if l is None: 219 | l = mps[0].shape[1] 220 | #================== Environment tensors ================== 221 | L = MPS.random_mps(mps.N, l, mps[1].shape[1]) 222 | rho = int(np.ceil(1.5* l)) 223 | # Heuristic choice of oversampling ranks based on Yuji- Fast and stable randomized low rank matrix approximation 2020 224 | R = MPS.random_mps(mps.N, rho, mps[1].shape[1]) 225 | 226 | WL = TTpartialContractionsLR(L,mps) 227 | WR = TTpartialContractionsRL(mps,R) 228 | 229 | # ================== Nystrom Sketch ================== 230 | U, s, Vt = truncated_svd(WL[0] @ WR[0], stop=stop) 231 | S_inv_sqrt = np.conj(np.diag(1 / np.sqrt(s))) 232 | left_factor = WR[0] @ Vt.T @ S_inv_sqrt 233 | right_factor = S_inv_sqrt @ U.T @ WL[0] 234 | 235 | mps[0] = mps[0] @ left_factor 236 | 237 | for j in range(1, mps.N - 1): 238 | U, s, Vt = truncated_svd(WL[j] @ WR[j], stop=stop) 239 | S_inv_sqrt = np.conj(np.diag(1 / np.sqrt(s))) 240 | left_factor = WR[j] @ Vt.T @ S_inv_sqrt 241 | 242 | mps[j] = np.tensordot(mps[j], left_factor, axes=(2, 0)) 243 | mps[j] = np.tensordot(right_factor, mps[j], axes=(1,0)) 244 | mps[j] = np.reshape(mps[j], (right_factor.shape[0], mps[j].shape[1], left_factor.shape[1])) 245 | 246 | #X[j] = np.reshape(Y[j],(Y[j].shape[0]*Y[j].shape[1],Y[j].shape[2])) @ left_factor 247 | #X[j] = np.reshape(X[j],(int(X[j].shape[0]/Y[j].shape[1]),X[j].shape[1]*Y[j].shape[1])) 248 | #X[j]= right_factor @ X[j] 249 | 250 | right_factor = S_inv_sqrt @ U.T @ WL[j] 251 | 252 | mps[-1] = right_factor @ mps[-1] 253 | 254 | def TTpartialContractionsLR(X,Y): 255 | WL = [None] * (Y.N-1) 256 | WL[0] = X[0].T @ Y[0] 257 | for j in range(1, Y.N - 1): 258 | WL[j] = np.tensordot(Y[j], WL[j - 1], axes=(0, 1)) 259 | WL[j] = np.tensordot(X[j], WL[j], axes=((1, 0), (0, 2))) 260 | return WL 261 | 262 | def TTpartialContractionsRL(X,Y): 263 | N = len(X) 264 | WR = [None] * (N - 1) 265 | WR[N-2] = X[-1] @ Y[-1].T 266 | 267 | for j in range(N-2, 0, -1): 268 | WR[j-1] = np.tensordot(X[j], WR[j], axes=(2, 0)) 269 | WR[j-1] = np.tensordot(WR[j-1], Y[j], axes=((1, 2), (1, 2))) 270 | return WR 271 | 272 | #============ Rounding Techniques from arxiv.org/abs/2110.04393 (BLAS version) ============ 273 | 274 | def dass_round_blas(mps,stop=Cutoff(1e-14)): 275 | if mps.rounded==False: 276 | mps.canonize_left_blas() 277 | # if stop.cutoff != None: 278 | # normY = mps.norm() 279 | # tau = stop.cutoff * normY / math.sqrt(mps.N - 1) 280 | # stop = Cutoff(tau) 281 | 282 | Q,R = la.qr(mps[0], mode='reduced') 283 | U, s, Vt = truncated_svd(R, stop=stop) 284 | mps[0] = Q @ U 285 | for i in range(1, mps.N - 1): 286 | temp = s[:,np.newaxis] * Vt 287 | # A = np.einsum("ik,klY->ilY",temp,mps[i]) 288 | mps_reshaped = mps[i].reshape(mps[i].shape[0],-1) #Reshape to (DX,l(EY)) 289 | A = temp @ mps_reshaped #resulting shape(p,l(EY)) 290 | A = A.reshape(A.shape[0],mps[i].shape[1],-1) # reshape to (p,l,EY) 291 | 292 | Q,R = la.qr(np.reshape(A, (A.shape[0] * A.shape[1], A.shape[2])), mode='reduced') 293 | U, s, Vt = truncated_svd(R, stop=stop) 294 | mps[i] = np.reshape(Q @ U, (A.shape[0], A.shape[1], len(s))) 295 | 296 | mps[-1] = (s[:,np.newaxis] * Vt) @ mps[-1] 297 | mps.canform = "Right" 298 | mps.rounded = True 299 | 300 | def orth_then_rand_blas(mps,r): 301 | mps.round_left() 302 | 303 | Z = mps[0] 304 | sketch = Z @ np.random.randn(Z.shape[1],r) 305 | 306 | Q,_ = la.qr(sketch, mode='reduced') 307 | mps[0] = Q 308 | 309 | M = Q.T @ Z 310 | # mps[1] = np.tensordot(M,mps[1],axes=(1,0)) 311 | mps_reshaped = mps[1].reshape(mps[1].shape[0],-1) 312 | temp = M @ mps_reshaped 313 | mps[1] = temp.reshape(M.shape[0],mps[1].shape[1],mps[1].shape[2]) 314 | 315 | for i in range(1, mps.N-1): 316 | Z = mps[i] 317 | # sketch = np.tensordot(Z ,np.random.randn(Z.shape[2],r),axes=(2,0)) 318 | Z_reshaped = Z.reshape(Z.shape[0]*Z.shape[1],Z.shape[2]) 319 | sketch = Z_reshaped @ np.random.randn(Z.shape[2],r) 320 | 321 | # print(sketch.shape) 322 | Q,_ = la.qr(sketch, mode='reduced') 323 | mps[i] = np.reshape(Q,(mps[i-1].shape[-1],mps[i].shape[1],Q.shape[-1])) 324 | 325 | M = Q.T @ np.reshape(Z,(Z.shape[0]*Z.shape[1],Z.shape[2])) 326 | mps[i+1] = np.tensordot(M,mps[i+1],axes=(1,0)) 327 | 328 | def rand_then_orth_blas(mps,r,finalround=False,stop=Cutoff(1e-14)): 329 | if r is None: 330 | if stop.cutoff is None and stop.outputdim is not None: 331 | r = stop.outputdim 332 | else: 333 | r = mps[0].shape[1] 334 | #==================Environment tensors ================== 335 | N = mps.N 336 | R = MPS.random_mps(N,r,mps[1].shape[1]) 337 | W = TTpartialContractionsRL_blas(mps,R) 338 | #==================Random Section================== 339 | Q, _ = la.qr(np.tensordot(mps[0], W[0], axes=(1, 0)), mode='reduced') 340 | # M = np.tensordot(Q.T, mps[0], axes=(1, 0)) 341 | M = Q @ mps[0] 342 | mps[0] = Q 343 | 344 | for j in range(1, N - 1): 345 | # mps[j] = np.tensordot(M,mps[j],axes=(1,0)) 346 | mps_reshaped = mps[j].reshape(mps[j].shape[0],-1) 347 | temp = M @ mps_reshaped 348 | mps[j] = temp.reshape(M.shape[0],mps[j].shape[1],mps[j].shape[2]) 349 | 350 | Z = np.reshape(mps[j],(mps[j].shape[0]*mps[j].shape[1],mps[j].shape[2])) 351 | # sketch = np.tensordot(Z,W[j],axes=(1,0)) 352 | sketch = Z @ W[j] 353 | Q,_ = la.qr(sketch,mode='reduced') 354 | mps[j]= np.reshape(Q,(mps[j-1].shape[-1],mps[j].shape[1],Q.shape[1])) 355 | M = Q.T @ Z 356 | 357 | mps[-1] = M @ mps[-1] 358 | mps.rounded = True 359 | # #==================Final rounding================== 360 | # if finalround: 361 | # if stop.cutoff != None: 362 | # normY = np.linalg.norm(mps[-1],'fro') 363 | # tau = stop.cutoff * normY / math.sqrt(mps.N - 1) 364 | # stop= Cutoff(tau) 365 | 366 | # Q,R = la.qr(mps[0], mode='reduced') 367 | # U, s, Vt = truncated_svd(R, stop=stop) 368 | # mps[0] = Q @ U 369 | # #Update to use dass round blas 370 | # for i in range(1, mps.N - 1): 371 | # A = np.tensordot(s[:,np.newaxis] * Vt, mps[i], (1,0)) 372 | # Q,R = la.qr(np.reshape(A, (A.shape[0] * A.shape[1], A.shape[2])), mode='reduced') 373 | # U, s, Vt = truncated_svd(R, stop=stop) 374 | # mps[i] = np.reshape(Q @ U, (A.shape[0], A.shape[1], len(s))) 375 | 376 | # mps[-1] = np.tensordot(s[:,np.newaxis] * Vt, mps[-1], (1,0)) 377 | 378 | def nystrom_round_blas(mps, l, stop=no_truncation()): 379 | if l is None: 380 | l = mps[0].shape[1] 381 | #================== Environment tensors ================== 382 | L = MPS.random_mps(mps.N, l, mps[1].shape[1]) 383 | rho = int(np.ceil(1.5* l)) 384 | # Heuristic choice of oversampling ranks based on Yuji- Fast and stable randomized low rank matrix approximation 2020 385 | R = MPS.random_mps(mps.N, rho, mps[1].shape[1]) 386 | 387 | WL = TTpartialContractionsLR_blas(L,mps) 388 | WR = TTpartialContractionsRL_blas(mps,R) 389 | 390 | # ================== Nystrom Sketch ================== 391 | U, s, Vt = truncated_svd(WL[0] @ WR[0], stop=stop) 392 | S_inv_sqrt = np.conj(np.diag(1 / np.sqrt(s))) 393 | left_factor = WR[0] @ Vt.T @ S_inv_sqrt 394 | right_factor = S_inv_sqrt @ U.T @ WL[0] 395 | 396 | mps[0] = mps[0] @ left_factor 397 | 398 | for j in range(1, mps.N - 1): 399 | U, s, Vt = truncated_svd(WL[j] @ WR[j], stop=stop) 400 | S_inv_sqrt = np.conj(np.diag(1 / np.sqrt(s))) 401 | left_factor = WR[j] @ Vt.T @ S_inv_sqrt 402 | 403 | # mps[j] = np.tensordot(mps[j], left_factor, axes=(2, 0)) 404 | mps_reshaped = mps[j].reshape(-1,mps[j].shape[2]) 405 | temp = mps_reshaped @ left_factor 406 | mps[j] = temp.reshape(mps[j].shape[0],mps[j].shape[1],left_factor.shape[1]) 407 | 408 | # mps[j] = np.tensordot(right_factor, mps[j], axes=(1,0)) 409 | mps_reshaped = mps[j].reshape(mps[j].shape[0],-1) 410 | temp = right_factor @ mps_reshaped 411 | mps[j] = temp.reshape(right_factor.shape[0],mps[j].shape[1],mps[j].shape[2]) 412 | 413 | mps[j] = np.reshape(mps[j], (right_factor.shape[0], mps[j].shape[1], left_factor.shape[1])) 414 | 415 | right_factor = S_inv_sqrt @ U.T @ WL[j] 416 | 417 | mps[-1] = right_factor @ mps[-1] 418 | 419 | def TTpartialContractionsLR_blas(X,Y): 420 | WL = [None] * (Y.N-1) 421 | WL[0] = X[0].T @ Y[0] 422 | for j in range(1, Y.N - 1): 423 | # WL[j] = np.tensordot(Y[j], WL[j - 1], axes=(0, 1)) 424 | Y_reshaped = Y[j].reshape(Y[j].shape[0],-1) 425 | temp = WL[j-1] @ Y_reshaped 426 | temp = temp.reshape(WL[j-1].shape[0],Y[j].shape[1],Y[j].shape[2]) 427 | WL[j] = temp.transpose(1,2,0) 428 | 429 | # WL[j] = np.tenspordot(X[j], WL[j], axes=((1, 0), (0, 2))) 430 | X_reshaped = X[j].reshape(-1,X[j].shape[2]) 431 | WL_transposed = WL[j].transpose(1,2,0) 432 | WL_transposed = np.ascontiguousarray(WL_transposed) #Resolves extra view 433 | WL_reshaped = WL_transposed.reshape(WL_transposed.shape[0],-1) 434 | temp = WL_reshaped @ X_reshaped 435 | WL[j]= temp.T 436 | return WL 437 | 438 | def TTpartialContractionsRL_blas(X,Y): 439 | N = len(X) 440 | WR = [None] * (N - 1) 441 | WR[N-2] = X[-1] @ Y[-1].T 442 | 443 | for j in range(N-2, 0, -1): 444 | # WR[j-1] = np.tensordot(X[j], WR[j], axes=(2, 0)) 445 | X_reshaped = X[j].reshape(-1,X[j].shape[2]) 446 | temp = X_reshaped @ WR[j] 447 | WR[j-1] = temp.reshape(X[j].shape[0],X[j].shape[1],WR[j].shape[1]) 448 | 449 | # WR[j-1] = np.tensordot(WR[j-1], Y[j], axes=((1, 2), (1, 2))) 450 | WR_reshaped = WR[j-1].reshape(WR[j-1].shape[0],-1) 451 | Y_transposed = Y[j].transpose(1,2,0) 452 | Y_reshaped = Y_transposed.reshape(-1,Y_transposed.shape[2]) 453 | WR[j-1] = WR_reshaped @ Y_reshaped 454 | return WR 455 | -------------------------------------------------------------------------------- /code/tensornetwork/stopping.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | class StoppingRule(object): 4 | 5 | def __init__(self, outputdim, mindim, maxdim, cutoff): 6 | self.outputdim = outputdim 7 | self.mindim = mindim 8 | self.maxdim = maxdim 9 | self.cutoff = cutoff 10 | 11 | def is_truncation(self): 12 | return not ((self.outputdim is None) and (self.cutoff is None)) 13 | 14 | def Cutoff(cutoff, mindim = 1, maxdim = np.inf): 15 | return StoppingRule(None, mindim, maxdim, cutoff) 16 | 17 | def FixedDimension(dim): 18 | return StoppingRule(dim, 1, np.inf, None) 19 | 20 | def no_truncation(): 21 | return StoppingRule(None, None, np.inf, None) 22 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: randomTensor 2 | channels: 3 | - conda-forge 4 | - defaults 5 | dependencies: 6 | - bzip2=1.0.8=h80987f9_6 7 | - ca-certificates=2024.3.11=hca03da5_0 8 | - expat=2.6.2=h313beb8_0 9 | - libcxx=14.0.6=h848a8c0_0 10 | - libffi=3.4.4=hca03da5_1 11 | - ncurses=6.4=h313beb8_0 12 | - openssl=3.0.14=h80987f9_0 13 | - pip=24.0=py312hca03da5_0 14 | - python=3.12.4=h99e199e_1 15 | - readline=8.2=h1a28f6b_0 16 | - setuptools=69.5.1=py312hca03da5_0 17 | - sqlite=3.45.3=h80987f9_0 18 | - tk=8.6.14=h6ba3021_0 19 | - wheel=0.43.0=py312hca03da5_0 20 | - xz=5.4.6=h80987f9_1 21 | - zlib=1.2.13=h18a0788_1 22 | - pip: 23 | - ace-tools==0.0 24 | - affine==2.4.0 25 | - anyio==4.4.0 26 | - appnope==0.1.4 27 | - apptools==5.3.0 28 | - argon2-cffi==23.1.0 29 | - argon2-cffi-bindings==21.2.0 30 | - arrow==1.3.0 31 | - asttokens==2.4.1 32 | - async-lru==2.0.4 33 | - attrs==23.2.0 34 | - autoray==0.7.0 35 | - babel==2.15.0 36 | - beautifulsoup4==4.12.3 37 | - bleach==6.1.0 38 | - branca==0.8.1 39 | - cartopy==0.24.1 40 | - certifi==2024.7.4 41 | - cffi==1.16.0 42 | - charset-normalizer==3.3.2 43 | - clarabel==0.9.0 44 | - click==8.1.7 45 | - click-plugins==1.1.1 46 | - cligj==0.7.2 47 | - cloudpickle==3.1.0 48 | - comm==0.2.2 49 | - configobj==5.0.9 50 | - contextily==1.6.2 51 | - contourpy==1.2.1 52 | - cotengra==0.6.2 53 | - cvxpy==1.6.0 54 | - cycler==0.12.1 55 | - cytoolz==1.0.0 56 | - dask==2024.12.1 57 | - debugpy==1.8.2 58 | - decorator==5.1.1 59 | - defusedxml==0.7.1 60 | - distributed==2024.12.1 61 | - docker-pycreds==0.4.0 62 | - envisage==7.0.3 63 | - executing==2.0.1 64 | - fastjsonschema==2.20.0 65 | - filelock==3.15.4 66 | - folium==0.19.4 67 | - fonttools==4.53.1 68 | - fqdn==1.5.1 69 | - fsspec==2024.6.1 70 | - geographiclib==2.0 71 | - geopandas==1.0.1 72 | - geopy==2.4.1 73 | - gitdb==4.0.11 74 | - gitpython==3.1.43 75 | - h11==0.14.0 76 | - h5py==3.11.0 77 | - httpcore==1.0.5 78 | - httpx==0.27.0 79 | - huggingface-hub==0.26.2 80 | - idna==3.7 81 | - imageio==2.35.1 82 | - ipykernel==6.29.5 83 | - ipython==8.26.0 84 | - ipywidgets==8.1.5 85 | - isoduration==20.11.0 86 | - jedi==0.19.1 87 | - jinja2==3.1.4 88 | - joblib==1.4.2 89 | - json5==0.9.25 90 | - jsonpointer==3.0.0 91 | - jsonschema==4.23.0 92 | - jsonschema-specifications==2023.12.1 93 | - jupyter-client==8.6.2 94 | - jupyter-core==5.7.2 95 | - jupyter-events==0.10.0 96 | - jupyter-lsp==2.2.5 97 | - jupyter-server==2.14.1 98 | - jupyter-server-terminals==0.5.3 99 | - jupyterlab==4.2.3 100 | - jupyterlab-pygments==0.3.0 101 | - jupyterlab-server==2.27.2 102 | - jupyterlab-widgets==3.0.13 103 | - kagglehub==0.3.4 104 | - kiwisolver==1.4.5 105 | - lazy-loader==0.4 106 | - line-profiler==4.1.3 107 | - llvmlite==0.43.0 108 | - locket==1.0.0 109 | - markupsafe==2.1.5 110 | - matplotlib==3.10.0 111 | - matplotlib-inline==0.1.7 112 | - mayavi==4.8.2 113 | - mercantile==1.2.1 114 | - mistune==3.0.2 115 | - mpmath==1.3.0 116 | - msgpack==1.1.0 117 | - nbclient==0.10.0 118 | - nbconvert==7.16.4 119 | - nbformat==5.10.4 120 | - nest-asyncio==1.6.0 121 | - networkx==3.3 122 | - notebook==7.2.1 123 | - notebook-shim==0.2.4 124 | - numba==0.60.0 125 | - numpy==2.0.0 126 | - osqp==0.6.7.post3 127 | - overrides==7.7.0 128 | - packaging==24.1 129 | - pandas==2.2.2 130 | - pandocfilters==1.5.1 131 | - parso==0.8.4 132 | - partd==1.4.2 133 | - pexpect==4.9.0 134 | - pillow==10.4.0 135 | - platformdirs==4.2.2 136 | - plotly==5.24.1 137 | - prometheus-client==0.20.0 138 | - prompt-toolkit==3.0.47 139 | - protobuf==5.27.2 140 | - psutil==6.0.0 141 | - ptyprocess==0.7.0 142 | - pure-eval==0.2.2 143 | - py4j==0.10.9.7 144 | - pyarrow==19.0.0 145 | - pybind11==2.13.1 146 | - pycparser==2.22 147 | - pyface==8.0.0 148 | - pygments==2.18.0 149 | - pynndescent==0.5.13 150 | - pyogrio==0.10.0 151 | - pyparsing==3.1.2 152 | - pyproj==3.7.0 153 | - pyshp==2.3.1 154 | - pyspark==3.5.4 155 | - python-dateutil==2.9.0.post0 156 | - python-json-logger==2.0.7 157 | - pytz==2024.1 158 | - pyyaml==6.0.1 159 | - pyzmq==26.0.3 160 | - qdldl==0.1.7.post5 161 | - quimb==1.8.4 162 | - rasterio==1.4.3 163 | - referencing==0.35.1 164 | - regex==2024.11.6 165 | - requests==2.32.3 166 | - rfc3339-validator==0.1.4 167 | - rfc3986-validator==0.1.1 168 | - rpds-py==0.19.0 169 | - safetensors==0.4.5 170 | - scikit-image==0.24.0 171 | - scikit-learn==1.5.1 172 | - scikit-misc==0.5.1 173 | - scipy==1.14.0 174 | - scs==3.2.7.post2 175 | - seaborn==0.13.2 176 | - send2trash==1.8.3 177 | - sentry-sdk==2.10.0 178 | - setproctitle==1.3.3 179 | - shapely==2.0.6 180 | - six==1.16.0 181 | - smmap==5.0.1 182 | - sniffio==1.3.1 183 | - sortedcontainers==2.4.0 184 | - soupsieve==2.5 185 | - ssgetpy==1.0rc2 186 | - stack-data==0.6.3 187 | - sympy==1.13.0 188 | - tblib==3.0.0 189 | - tenacity==9.0.0 190 | - terminado==0.18.1 191 | - threadpoolctl==3.5.0 192 | - tifffile==2024.9.20 193 | - tinycss2==1.3.0 194 | - tokenizers==0.20.3 195 | - toolz==1.0.0 196 | - torch==2.3.1 197 | - tornado==6.4.1 198 | - tqdm==4.66.4 199 | - traitlets==5.14.3 200 | - traits==6.4.3 201 | - traitsui==8.0.0 202 | - transformers==4.46.3 203 | - types-python-dateutil==2.9.0.20240316 204 | - typing-extensions==4.12.2 205 | - tzdata==2024.1 206 | - uri-template==1.3.0 207 | - urllib3==2.2.2 208 | - vtk==9.4.1 209 | - wandb==0.18.7 210 | - wcwidth==0.2.13 211 | - webcolors==24.6.0 212 | - webencodings==0.5.1 213 | - websocket-client==1.8.0 214 | - widgetsnbextension==4.0.13 215 | - xyzservices==2024.9.0 216 | - zict==3.0.0 217 | prefix: /Users/pren/miniconda3/envs/tensor 218 | -------------------------------------------------------------------------------- /results/data/fig1_final.csv: -------------------------------------------------------------------------------- 1 | Bond Dimension,random Mean Time,random Time Std,random Mean Accuracy,random Accuracy Std,random+oversample Mean Time,random+oversample Time Std,random+oversample Mean Accuracy,random+oversample Accuracy Std,zipup Mean Time,zipup Time Std,zipup Mean Accuracy,zipup Accuracy Std,fit Mean Time,fit Time Std,fit Mean Accuracy,fit Accuracy Std,rand_then_orth Mean Time,rand_then_orth Time Std,rand_then_orth Mean Accuracy,rand_then_orth Accuracy Std,naive Mean Time,naive Time Std,naive Mean Accuracy,naive Accuracy Std,density Mean Time,density Time Std,density Mean Accuracy,density Accuracy Std 2 | 5,0.1360252857208252,0.011909207069940694,0.0001693834564826053,1.8096971289467862e-05,0.35308055877685546,0.015455008221686248,2.1861536210436804e-05,3.94486874844352e-10,0.13722000122070313,0.0034006824668750338,0.00013997855390164677,0.0,0.7471326351165771,0.01836727244202122,2.1853929348989772e-05,7.920097065844975e-11,23.73751916885376,0.7408903781936326,0.00012608157129282376,4.068956884682101e-05,693.6335005760193,0.8105326980250366,2.1860174392269777e-05,0.0,123.3101312160492,0.7720130554880795,2.1793377192926954e-05,0.0 3 | 10,0.2545478343963623,0.01215744150687349,4.275071603071423e-06,3.9022834111277485e-07,0.48123674392700194,0.027044163071501266,5.583842937163461e-07,4.892087065972678e-10,0.37919058799743655,0.014493908393436014,1.350306028446192e-05,0.0,1.072817850112915,0.06659361455552013,5.552032480305656e-07,6.470299903448877e-12,23.788478565216064,0.25741684644189133,2.392782428356999e-06,1.6434120544049502e-07,693.8744314193725,4.519344435982395,5.555929984234856e-07,0.0,124.33701996803283,0.3471199062842654,5.049192033388377e-07,0.0 4 | 15,0.35980839729309083,0.01763535445279823,4.3655487460476096e-07,2.58905348402248e-08,0.6626678466796875,0.035906322030684404,6.45207087838396e-08,9.040348330973416e-10,0.5798132419586182,0.026171061734425342,2.1491286065123093e-06,0.0,1.368171501159668,0.03809003018234554,6.157911277314308e-08,6.140794743491169e-13,23.19073419570923,0.17129912021719632,2.679277756829668e-07,1.5164232896995924e-08,691.0538365840912,0.6502490121482477,6.162634605915589e-08,0.0,123.51581382751465,0.6296352432586992,8.16979537517033e-08,0.0 5 | 20,0.48346939086914065,0.024394289086932256,8.684361340137651e-08,6.061867349321801e-09,0.7549124240875245,0.05811459797163522,1.23515429262791e-08,1.518766729818694e-10,0.8025645256042481,0.019919759894783933,5.595664076512776e-07,0.0,1.582068681716919,0.09743473965650133,1.0871970480719254e-08,1.5767045312006305e-13,24.318284940719604,0.38208364212313484,4.555945055588035e-08,5.73245424719168e-09,691.2570440769196,1.849282772687869,1.0874768392854879e-08,0.0,123.15310111045838,0.30772955376497574,1.5788078259477973e-08,0.0 6 | 25,0.58887619972229,0.010379975277234414,2.6837900624489456e-08,4.069422572342079e-09,0.9972271919250488,0.04310122880797056,3.5801083935942805e-09,2.2615345652194223e-12,1.087536334991455,0.02515799336708828,2.752371645445427e-07,0.0,2.0159444332122805,0.024897121692869763,3.446015342518375e-09,1.2953228537886094e-14,25.1150972366333,0.08917030339100385,1.3815509137099161e-08,3.555721711976776e-10,691.8854130268097,2.98160223686933,3.4478855489701787e-09,4.1359030627651384e-25,124.83149495124817,0.15432620110315215,4.26069252094885e-09,0.0 7 | 30,0.6925007343292237,0.01643903743665281,6.537675398551024e-09,4.791682227681926e-10,1.1786335945129394,0.02712332771644744,9.170162011720942e-10,4.9311064491830355e-12,1.3664032459259032,0.021616834975150143,9.586645837843168e-08,0.0,2.253391170501709,0.06502104500251511,8.860602868840748e-10,1.0946788834830395e-14,25.297645568847656,0.42277570370663575,4.032947769332658e-09,1.5930255126154077e-10,679.9921334266662,1.390687449105629,8.862105698821165e-10,0.0,125.24743475914002,0.1783289879827253,1.4928658038344124e-09,0.0 8 | 35,0.8478096008300782,0.014589131303917985,2.11069716261877e-09,1.189620665883139e-10,1.4879583835601806,0.014582370427991926,2.4576411021487924e-10,4.23499462804574e-14,1.8117650032043457,0.01181345338740444,4.458222449581552e-08,0.0,2.7661908149719237,0.03260088051149551,2.456020667364228e-10,1.6160957179667366e-15,27.00044846534729,0.1781553912914921,1.091977508779365e-09,6.979287986758063e-11,679.5101387023926,1.0627786146631915,2.4562292510002214e-10,0.0,125.30990180969238,0.07603297020207628,3.875829186413414e-10,0.0 9 | 40,0.9771256446838379,0.015714690807179424,7.954316190727857e-10,6.356105513825629e-11,1.6594981670379638,0.009822716985714188,9.925640573429412e-11,9.340109466372794e-16,2.1280101776123046,0.05831512932821223,2.494750723486965e-08,0.0,3.055671453475952,0.048724292177331596,9.92430587726602e-11,4.591150582968578e-16,26.488330459594728,0.14661904627731712,4.1661336012417066e-10,3.006854830490051e-11,679.305168390274,0.7809544983827182,9.924554457916424e-11,1.2924697071141057e-26,125.54970264434814,0.1377914830081196,1.5188089238989848e-10,0.0 10 | 45,1.1477566719055177,0.01642766413285408,2.7079522493343576e-10,6.703702781295357e-12,2.0144550800323486,0.03694226683042072,3.418209586634097e-11,2.0904835096440071e-16,2.595324993133545,0.05654492305153097,1.4243816441642533e-08,0.0,3.472590970993042,0.03208449046335742,3.417908982957356e-11,8.115939674434949e-17,27.493175554275513,0.46789372258645506,1.614283109238466e-10,4.85734631157158e-12,680.1059121131897,1.4810576450358297,3.417999258359366e-11,0.0,126.03322110176086,0.11218885295624367,4.8417228992002956e-11,0.0 11 | 50,1.3885279655456544,0.04588742059115287,4.781352740793252e-11,1.8789015400066704e-12,2.283675479888916,0.025578759230656587,2.837383768788163e-12,3.632575401869915e-16,2.971077728271484,0.024315973442501096,6.756468489527579e-09,0.0,4.038574504852295,0.0371146645460846,2.8327531390758463e-12,1.679854734404888e-16,26.337515449523927,0.7261257450184679,2.590795690341134e-11,2.3968799879809043e-12,678.1655113220215,2.1607760601731703,2.834440602759066e-12,0.0,126.21589999198913,0.04814196475524236,1.15469353109839e-11,0.0 12 | 55,1.5160053730010987,0.03540276034979969,5.12812213166946e-12,2.4701727233644676e-13,2.574923372268677,0.024840399959423935,5.184712752137624e-13,2.038903341043689e-15,3.5061277866363527,0.01335732416159001,2.9370853229226058e-09,0.0,4.47995285987854,0.10606619435612565,5.13753239437885e-13,1.0535839534197551e-15,25.578778791427613,0.23526193583770713,2.9467618272020777e-12,1.2402956030282172e-13,680.3242589950562,1.2942144093173882,5.247730939150563e-13,0.0,126.48569388389588,0.10772619428721357,1.077111998399517e-12,0.0 13 | 60,1.6184418678283692,0.0764300499746494,1.720622952989428e-12,1.6298074544617482e-13,2.876320505142212,0.01763809396759967,2.688583259524164e-13,1.853186415850792e-15,3.9580805778503416,0.017051927984060158,8.216499971668888e-10,1.0339757656912846e-25,4.786772203445435,0.03622298261636402,2.6390802953397026e-13,1.1176537544878086e-15,26.387549543380736,0.28736546645800304,1.005881974964442e-12,7.19677162846642e-14,680.0675092697144,2.09401221796449,2.8086780551628584e-13,0.0,126.53504633903503,0.13418490931886587,3.6770227939659677e-13,5.048709793414476e-29 14 | 65,1.8171632289886475,0.0470640429356079,7.128699841623981e-13,2.3082404727846462e-14,3.190220022201538,0.1401300000478919,2.088265266765573e-13,6.555388236576316e-16,4.665666913986206,0.05772231080799069,3.858871865892992e-10,0.0,5.290506744384766,0.07148975895356093,2.0338171722678446e-13,2.047481147896772e-15,26.47733516693115,0.2327305819329738,4.2845891117730604e-13,6.4713384736531946e-15,680.5143068313598,0.9454754177539065,2.328046532989164e-13,0.0,127.1808093547821,0.18110944700006726,2.3728104462765905e-13,0.0 15 | 70,2.020358371734619,0.027259308131682185,4.000281793545126e-13,1.0657162484173272e-14,3.47349271774292,0.07277020102649925,1.9962173570051708e-13,7.0294966354011596e-15,4.972856760025024,0.055273240461903676,2.0727159382247513e-10,0.0,5.686983203887939,0.03672346228094234,1.9462331565782828e-13,9.547771152395203e-16,26.804541301727294,0.19766371747614747,2.7059734112447366e-13,1.1143140999351095e-14,686.3251352787017,8.042879275738791,2.2448951592471904e-13,0.0,127.26403894424439,0.10099592519861261,2.223005386159619e-13,2.524354896707238e-29 16 | 75,2.134594535827637,0.01902788765233243,2.654334953599849e-13,7.364998456425923e-15,3.7670327186584474,0.0715150129137709,1.967856567470936e-13,5.822225408066985e-15,5.452294397354126,0.06576348819925151,1.200909922076365e-10,0.0,6.145218801498413,0.07144089724839148,1.9257916856508888e-13,2.1742345199861123e-15,26.85505542755127,0.2500709898444481,2.255516440111041e-13,1.2996014189611975e-14,680.201792383194,3.6393465536666767,2.1826808763764075e-13,0.0,127.54300971031189,0.10203273782792849,2.053684164873363e-13,2.524354896707238e-29 17 | 80,2.268066883087158,0.016197586020686428,2.1452711564842293e-13,2.550211438977907e-15,3.9986939430236816,0.05862942878097709,1.9713441095812954e-13,3.677579469009332e-15,5.937506008148193,0.021857296852176022,7.66124852758119e-11,0.0,6.727081441879273,0.0727027710915157,1.9446714431777194e-13,1.4936311369397717e-15,27.038437366485596,0.266830590421643,1.9926834830795117e-13,1.290137739260422e-14,682.3862433433533,1.0225497421501588,2.1948786049638406e-13,0.0,127.73733024597168,0.124377909719202,2.0406326306747057e-13,2.524354896707238e-29 18 | 85,2.480517053604126,0.02443841482913298,1.9993969525270175e-13,7.16091493494213e-15,4.455799198150634,0.03553827506994658,1.989827363134362e-13,5.9099882022749186e-15,6.781051206588745,0.0467716687024879,5.563309916672927e-11,0.0,7.206757211685181,0.16751840746450333,1.9186389756851772e-13,2.5124686367123372e-15,27.97441382408142,0.309242099792261,1.9664845633639818e-13,6.6969493938014286e-15,683.2705794811249,2.2450416211525,2.208556984705014e-13,2.524354896707238e-29,127.91074161529541,1.0517977374338257,1.98903632628588e-13,0.0 19 | 90,2.535092496871948,0.06546824464794788,1.9442054767523288e-13,6.6563915988727996e-15,4.749341440200806,0.03677751430410238,1.9643012365374444e-13,1.908519964078931e-15,7.00760703086853,0.09353187890702915,3.766016700131982e-11,0.0,7.679675054550171,0.16925421467433696,1.9079220674911712e-13,1.9714823891066732e-15,28.17652473449707,0.21409437751685254,2.0885427806161899e-13,2.1017165401168032e-14,682.1679303646088,0.9114730946756803,2.200889753591865e-13,0.0,128.8091193675995,0.27079413153503856,2.0235249216256944e-13,2.524354896707238e-29 20 | 95,2.631281089782715,0.07815970436737271,1.8829766219278728e-13,6.7697972194131555e-15,5.052316761016845,0.057404010042379376,2.04474319560585e-13,7.7753508813586e-15,7.70246376991272,0.3215248830238179,2.744786446651749e-11,0.0,8.102066326141358,0.10720297890338273,1.9357089986568937e-13,2.6963703427758737e-15,28.794211435317994,0.2259111504023328,1.9379393360795916e-13,2.155578144596362e-14,684.5377485275269,1.953803605136126,2.1661816934709671e-13,0.0,128.62167510986328,0.670797273946183,2.010967818196577e-13,2.524354896707238e-29 21 | 100,2.8639970779418946,0.09534854807956879,1.90042717295606e-13,3.738644104148332e-15,5.340174388885498,0.1527386469095391,1.9619094751368485e-13,3.4184887078618713e-15,8.349577903747559,0.22458482256030599,2.0228727565775974e-11,0.0,8.807183218002319,0.04188962855870967,1.927820089001711e-13,3.1742061861795437e-15,29.706048202514648,0.08538090672571785,1.9219626611289032e-13,5.638290476862882e-15,686.5718564510346,0.7594388337560741,2.189902994858238e-13,0.0,128.6744665622711,1.0711202978840564,2.0159451319980336e-13,2.524354896707238e-29 22 | 23 | -------------------------------------------------------------------------------- /results/data/fig4_final.csv: -------------------------------------------------------------------------------- 1 | name,cutoff_value,mean_time,std_time,mean_accuracy,std_accuracy,mean_bond_dim,std_bond_dim 2 | naive,0.01,673.7131072044373,1.7727702298725418,0.020401782910308783,0.0,2.0,0.0 3 | naive,0.001,674.9468110084533,0.5964412612263988,0.0040499961995279195,0.0,2.0,0.0 4 | naive,0.0001,676.8527936458588,1.8230121976620217,0.000535600532514361,0.0,4.0,0.0 5 | naive,1e-05,676.5508161067962,2.1110414378914006,5.0288383469357054e-05,0.0,5.0,0.0 6 | naive,1e-06,675.9021979808807,1.1720782207549942,6.678216565624825e-06,8.470329472543003e-22,8.0,0.0 7 | naive,1e-07,676.6586974143981,1.3141379800289306,7.024620092062903e-07,0.0,13.0,0.0 8 | naive,1e-08,675.800088262558,0.6271885532898346,7.605329647902794e-08,0.0,17.0,0.0 9 | naive,1e-09,677.6928220272064,1.336046210501855,8.55494414109805e-09,0.0,27.0,0.0 10 | naive,1e-10,678.6808957099914,1.6160014914111411,8.289149954333955e-10,1.0339757656912846e-25,36.0,0.0 11 | naive,1e-11,682.2815878391266,6.084688331947456,8.444347855302576e-11,0.0,48.0,0.0 12 | naive,1e-12,680.9015124320983,0.891941215778808,7.61853556977066e-12,0.0,52.0,0.0 13 | naive,1e-13,681.185949420929,1.0642797639375086,7.961774515270695e-13,1.0097419586828951e-28,63.0,0.0 14 | naive,1e-14,682.9480363845826,1.1985198470684986,1.8518909848485177e-13,0.0,81.0,0.0 15 | density,1e-14,130.44714179039002,1.356301669788648,2.7763667212650583e-13,0.0,78.0,0.0 16 | density,1e-13,129.06419405937194,0.4496844545029843,1.2197021668900603e-12,0.0,61.0,0.0 17 | density,1e-12,129.0331558227539,0.7496235576722713,1.3854142703874788e-11,0.0,52.0,0.0 18 | density,1e-11,128.34003562927245,0.6200285646981541,1.6603013874364929e-10,0.0,48.0,0.0 19 | density,1e-10,127.85540618896485,0.8959832578695907,1.6906587827976613e-09,0.0,33.0,0.0 20 | density,1e-09,128.06332211494447,1.4821865445486422,1.4637000941525856e-08,1.6543612251060553e-24,25.0,0.0 21 | density,1e-08,127.82301335334778,2.0224644296178402,1.2803376255285483e-07,0.0,16.0,0.0 22 | density,1e-07,127.16707906723022,1.4506644616726918,7.839833973058289e-07,1.0587911840678754e-22,12.0,0.0 23 | density,1e-06,127.00532302856445,1.563162394490839,7.296400634801373e-06,0.0,8.0,0.0 24 | density,1e-05,127.31997537612915,1.612885003457408,5.030564312944459e-05,0.0,5.0,0.0 25 | density,0.0001,126.75115914344788,0.7120292435056107,0.0005897958293104756,0.0,4.0,0.0 26 | density,0.001,126.41256399154663,0.37623213342295697,0.004113249652240111,0.0,2.0,0.0 27 | density,0.01,126.36370620727538,0.20706936726798178,0.0204017828557476,0.0,2.0,0.0 28 | fit,1e-14,10.802636814117431,0.31172475999920507,2.2543240329338114e-13,2.0043828917204294e-15,81.0,0.0 29 | fit,1e-13,8.383955669403075,0.1689787134229949,8.165579064768796e-13,6.636005800433837e-16,63.0,0.0 30 | fit,1e-12,7.421751928329468,0.17401557008353627,7.619513369933106e-12,8.099748004644003e-17,52.0,0.0 31 | fit,1e-11,5.812300968170166,0.08365134958974904,8.525066585370477e-11,5.9766686604830575e-18,48.0,0.0 32 | fit,1e-10,4.0910028457641605,0.06641383902848012,8.388199252581489e-10,5.92020204425569e-19,36.0,0.0 33 | fit,1e-09,2.7595396518707274,0.11604897184125004,8.717394673331305e-09,1.938819587876968e-16,27.0,0.0 34 | fit,1e-08,2.197086715698242,0.10039502201131384,7.85891946114015e-08,4.108716245325489e-16,16.0,0.0 35 | fit,1e-07,1.5847813606262207,0.1088562729888478,7.100626708810662e-07,1.8871610017608073e-14,13.0,0.0 36 | fit,1e-06,1.2724934577941895,0.051094583931215916,7.002172076983448e-06,1.4726743839156304e-13,8.0,0.0 37 | fit,1e-05,1.111419153213501,0.084603383034584,5.0240298145083966e-05,2.0518073546996454e-11,5.0,0.0 38 | fit,0.0001,1.0573598384857177,0.12116797832367468,0.0005763932209992804,1.95766872681114e-15,4.0,0.0 39 | fit,0.001,0.7915521621704101,0.05141126270441108,0.004215475584465183,1.637913375981971e-17,2.0,0.0 40 | fit,0.01,0.7016709327697754,0.07969308687019507,0.02040178218089536,5.732467154385379e-17,2.0,0.0 41 | zipup,0.01,0.03245244026184082,0.01021043063077869,0.0204162564686553,0.0,2.0,0.0 42 | zipup,0.001,0.0628133773803711,0.017423833998661216,0.0028329836114848843,0.0,4.0,0.0 43 | zipup,0.0001,0.11255998611450195,0.010556874787198514,0.00026162587539497225,0.0,7.0,0.0 44 | zipup,1e-05,0.2579493522644043,0.010714740344744372,2.6447753490499903e-05,3.3881317890172014e-21,13.0,0.0 45 | zipup,1e-06,0.5300149917602539,0.026754890774176813,2.860615133687515e-06,0.0,19.0,0.0 46 | zipup,1e-07,0.9906779289245605,0.02528291138158129,2.6670072735786413e-07,0.0,31.0,0.0 47 | zipup,1e-08,1.8827945232391357,0.05013707705711592,2.9223075044015207e-08,0.0,48.0,0.0 48 | zipup,1e-09,3.317887210845947,0.06517246029269368,3.611242427454956e-09,4.1359030627651384e-25,64.0,0.0 49 | zipup,1e-10,4.646358633041382,0.04676317858358899,3.054071045732902e-10,0.0,88.0,0.0 50 | zipup,1e-11,6.802420520782471,0.10568999428751376,3.182844420977613e-11,0.0,118.0,0.0 51 | zipup,1e-12,12.072532653808594,0.22909218211535073,2.9923267159421055e-12,4.0389678347315804e-28,164.0,0.0 52 | zipup,1e-13,18.92551736831665,0.0662866814362385,5.127579416883239e-13,0.0,231.0,0.0 53 | zipup,1e-14,32.89581871032715,0.1989054473613818,4.661165627013559e-13,0.0,323.0,0.0 54 | random,0.01,0.16344165802001953,0.03369307788261109,0.021403852651355672,0.0004907636087563596,2.0,0.0 55 | random,0.001,0.18013648986816405,0.04337583028217711,0.005969359367946831,0.0008218820996356217,2.2,0.39999999999999997 56 | random,0.0001,0.2519683837890625,0.038069859586190434,0.0005559644071894941,9.164433893238295e-06,4.0,0.0 57 | random,1e-05,0.2977573394775391,0.023053738550493124,6.239266392331816e-05,5.276874046820017e-06,5.0,0.0 58 | random,1e-06,0.4530505180358887,0.03233253427921765,8.500001594937762e-06,1.3940170381621103e-06,8.0,0.0 59 | random,1e-07,0.6328046321868896,0.028328752339557554,9.280745339309206e-07,9.61817448621372e-08,13.0,0.0 60 | random,1e-08,0.8967170238494873,0.05331863734995351,1.1614474143380946e-07,2.4392566797774457e-08,16.6,0.4898979485566356 61 | random,1e-09,1.2978770732879639,0.07478882427822121,1.1631757756038515e-08,9.225194230480513e-10,27.0,0.0 62 | random,1e-10,1.7647655487060547,0.03848730347103952,1.2002805934779111e-09,3.096460124270124e-10,35.8,0.4 63 | random,1e-11,2.153230381011963,0.07322578854873263,1.1654683511484427e-10,1.1194749482418143e-11,48.0,0.0 64 | random,1e-12,2.6707242488861085,0.19438751630448525,1.1196355270246973e-11,2.285566230633577e-12,52.0,0.0 65 | random,1e-13,3.4005417346954347,0.10808682732746287,1.0997242077031357e-12,1.30017286410386e-13,62.8,0.4 66 | random,1e-14,5.344976568222046,0.3986604280215055,2.3757654770619364e-13,6.525934371358627e-15,81.0,0.0 67 | random+oversample,0.01,0.2207106590270996,0.023191028049393457,0.020549873189724895,0.00012076307286123274,2.0,0.0 68 | random+oversample,0.001,0.28687210083007814,0.03495581947817564,0.004066517038996991,2.963043089131893e-05,2.0,0.0 69 | random+oversample,0.0001,0.367690372467041,0.031388471853999164,0.0005422507213830883,4.564408022866737e-06,4.0,0.0 70 | random+oversample,1e-05,0.4505183219909668,0.041542838766516994,5.032867325614653e-05,3.632226705779734e-07,5.0,0.0 71 | random+oversample,1e-06,0.6697835922241211,0.06409885987850115,6.7083264053579965e-06,2.9990813755675875e-08,8.0,0.0 72 | random+oversample,1e-07,0.9336036205291748,0.04899547206700747,7.05735254266173e-07,2.6073400669957593e-09,13.0,0.0 73 | random+oversample,1e-08,1.3608731269836425,0.047427933202210754,7.656717616794534e-08,5.558695408662594e-10,17.0,0.0 74 | random+oversample,1e-09,1.7821834087371826,0.05060875604282276,8.596007248323802e-09,4.302350601145194e-11,27.0,0.0 75 | random+oversample,1e-10,2.2297303676605225,0.05551892508484105,8.316901711936713e-10,1.1757604619670999e-12,36.0,0.0 76 | random+oversample,1e-11,2.7611873149871826,0.1932640458707782,8.5011432604313e-11,2.819794817064883e-13,48.0,0.0 77 | random+oversample,1e-12,3.5657652378082276,0.23853980914620662,7.657757935928033e-12,3.9617753876984904e-14,52.0,0.0 78 | random+oversample,1e-13,4.930642938613891,0.18366147232791744,8.124388450996012e-13,7.0119855025201745e-15,63.0,0.0 79 | random+oversample,1e-14,7.839174127578735,0.20156005803492352,2.2206894185276375e-13,4.118961110863922e-15,81.0,0.0 -------------------------------------------------------------------------------- /setup_QR.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd "$(dirname "$0")" 3 | BUILD_DIR="code/tensornetwork/build" 4 | mkdir -p "$BUILD_DIR" 5 | cd "$BUILD_DIR" 6 | cmake .. 7 | make -------------------------------------------------------------------------------- /util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscamano/RandomMPOMPS/275e49b7c435a7c2352cc7e69cc4783327f893fc/util/__init__.py -------------------------------------------------------------------------------- /util/benchmarking.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import sys 3 | import time 4 | import matplotlib.pyplot as plt 5 | from IPython.display import display, clear_output 6 | import pandas as pd 7 | pd.options.display.float_format = '{:.2e}'.format 8 | from datetime import datetime 9 | 10 | sys.path.append('../code') 11 | from tensornetwork.MPS import MPS 12 | from tensornetwork.MPO import MPO 13 | from tensornetwork.contraction import * 14 | from tensornetwork.stopping import * 15 | # from tensornetwork.util import * 16 | from util.plotting import plot_accuracy,plot_times2,plot_accuracy2,plot_times_reversed,plot_accuracy_reversed 17 | from tensornetwork.incrementalqr import * 18 | 19 | 20 | 21 | 22 | # =========================================================================== 23 | # Benchmark Experiment 1 : Random Tensor Experiment (variable compressibility) 24 | # =========================================================================== 25 | 26 | def generate_baseline(N, m, a=-0.5, dtype=float): 27 | def random_tensor(*args, a=a, b=1, dtype=dtype): 28 | output = a + (b - a) * np.random.rand(*args).astype(dtype) 29 | output /= np.linalg.norm(output) 30 | return output 31 | 32 | print("Generating baseline contraction...") 33 | mpo = MPO.random_mpo(N, m, random_tensor=random_tensor,dtype=dtype) 34 | mps = MPS.random_mps(N, m, random_tensor=random_tensor,dtype=dtype) 35 | baseline = mps_mpo_blas(mps, mpo, stop=Cutoff(1e-15)) 36 | return mpo, mps, baseline 37 | 38 | def fixed_synth_tensor_experiment(mpo, mps, baseline, bond_dims, names, num_runs=1, a=-.5, b=1, highres=False, return_data=False, 39 | fit_sweeps=1, sketch_increment=2, sketch_dim=3): 40 | times = {name: [] for name in names} 41 | accs = {name: [] for name in names} 42 | std_times = {name: [] for name in names} 43 | std_accs = {name: [] for name in names} 44 | 45 | baseline.canonize() 46 | print(baseline.norm()) 47 | 48 | column_names = ['Bond Dimension'] + [f'{name} {metric}' for name in names for metric in ['Mean Time', 'Time Std', 'Mean Accuracy', 'Accuracy Std']] 49 | results_df = pd.DataFrame(columns=column_names) 50 | display(results_df) 51 | 52 | for bond_dim in bond_dims: 53 | temp_times = {name: [] for name in names} 54 | temp_accs = {name: [] for name in names} 55 | 56 | for run in range(num_runs): 57 | for name in names: 58 | print(name) 59 | start = time.time() 60 | if name == 'naive': 61 | result = mps_mpo_blas(mps, mpo, stop=FixedDimension(bond_dim), round_type="dass_blas") 62 | elif name == 'rand_then_orth': 63 | result = mps_mpo_blas(mps, mpo, stop=FixedDimension(bond_dim), round_type="rand_then_orth_blas", final_round=False) 64 | elif name == 'nyst': 65 | result = mps_mpo_blas(mps, mpo, stop=FixedDimension(bond_dim), round_type="nystrom_blas", final_round=False) 66 | elif name == 'random+oversample': 67 | result = random_contraction_inc(mpo, mps, stop=FixedDimension(max(int(np.ceil(1.5*bond_dim)),bond_dim+10)), accuracychecks=False, 68 | finalround=FixedDimension(bond_dim), sketchincrement=sketch_increment, sketchdim=sketch_dim) 69 | elif name == 'random': 70 | result = random_contraction_inc(mpo, mps, stop=FixedDimension(bond_dim), accuracychecks=False, 71 | finalround=None, sketchincrement=sketch_increment, sketchdim=sketch_dim) 72 | elif name == 'density': 73 | result = density_matrix(mpo, mps, stop=FixedDimension(bond_dim)) 74 | elif name == 'zipup': 75 | result = zipup(mpo, mps, stop=FixedDimension(bond_dim), finalround=None,conditioning=True) 76 | elif name == 'fit': 77 | result = fit(mpo, mps, max_sweeps=fit_sweeps, stop=FixedDimension(bond_dim),guess="input") 78 | else: 79 | print("Invalid algorithm choice for ", name, " review your inputted algorithm names") 80 | return 81 | temp_times[name].append(time.time() - start) 82 | # print(baseline[0].shape,result[0].shape) 83 | # for t in result.tensors: 84 | # print(t.shape) 85 | temp_accs[name].append((baseline - result).norm() / baseline.norm()) 86 | 87 | # Compute the mean and std for this bond dimension 88 | new_row_data = {'Bond Dimension': bond_dim} 89 | for name in names: 90 | times_mean = np.mean(temp_times[name]) 91 | times_std = np.std(temp_times[name]) 92 | acc_mean = np.mean(temp_accs[name]) 93 | acc_std = np.std(temp_accs[name]) 94 | 95 | new_row_data[f'{name} Mean Time'] = times_mean 96 | new_row_data[f'{name} Time Std'] = times_std 97 | new_row_data[f'{name} Mean Accuracy'] = acc_mean 98 | new_row_data[f'{name} Accuracy Std'] = acc_std 99 | 100 | times[name].append(times_mean) 101 | std_times[name].append(times_std) 102 | accs[name].append(acc_mean) 103 | std_accs[name].append(acc_std) 104 | 105 | new_row_df = pd.DataFrame([new_row_data]) 106 | results_df = pd.concat([results_df, new_row_df], ignore_index=True) 107 | clear_output(wait=True) 108 | display(results_df) 109 | 110 | # # Save data to a CSV file in the Figure2_data directory 111 | # timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") 112 | # file_name = f"figure2_data_{mps.N}_{mps.max_bond_dim()}_{timestamp}_{num_runs}.csv" 113 | # file_path = os.path.join('Figure2_data', file_name) 114 | # results_df.to_csv(file_path, index=False) 115 | # print(f"Data saved to {file_path}") 116 | 117 | if highres: 118 | # High resolution separate plots 119 | plt.figure(dpi=500) 120 | plot_times2(names, bond_dims, times, std_times) 121 | plt.tight_layout() 122 | plt.grid(True) 123 | plt.show() 124 | 125 | plt.figure(dpi=500) 126 | plot_accuracy2(names, bond_dims, accs, std_accs) 127 | plt.tight_layout() 128 | plt.grid(True) 129 | 130 | plt.show() 131 | else: 132 | # Standard multi-plot 133 | fig, axs = plt.subplots(1, 2, figsize=(12, 5)) 134 | plot_times2(names, bond_dims, times, std_times, ax=axs[0]) 135 | plot_accuracy2(names, bond_dims, accs, std_accs, ax=axs[1]) 136 | if return_data: 137 | return times, std_times, accs, std_accs 138 | 139 | def cutoff_synth_tensor_experiment(mpo,mps,baseline,cutoffs,names,a=-.5,b=1,highres=False,return_data=False, 140 | fit_sweeps=8,sketch_dim=10,sketch_increment=10): 141 | # baseline.canonize() 142 | # print(baseline.norm()) 143 | 144 | times = {name: [] for name in names} 145 | accs = {name: [] for name in names} 146 | 147 | column_names = ['Cutoff Value'] + [f'{name} {metric}' for name in names for metric in ['Time', 'Accuracy']] 148 | results_df = pd.DataFrame(columns=column_names) 149 | display(results_df) 150 | 151 | 152 | for cutoff in cutoffs: 153 | for name in names: 154 | start = time.time() 155 | if name == 'naive': 156 | result = mps_mpo_blas(mps, mpo, stop=Cutoff(cutoff),round_type = "dass_blas") 157 | elif name == 'random': 158 | result = random_contraction_inc(mpo, mps, stop=Cutoff(cutoff), accuracychecks=False, 159 | finalround=None,sketchincrement=sketch_increment,sketchdim=sketch_dim) 160 | elif name == 'density': 161 | result = density_matrix(mpo, mps,stop=Cutoff(cutoff)) 162 | elif name == 'zipup': 163 | result = zipup(mpo, mps, stop=Cutoff(cutoff),finalround=False,conditioning=True) 164 | elif name == 'fit': 165 | result = fit(mpo, mps, max_sweeps=fit_sweeps,stop=Cutoff(cutoff)) 166 | else: 167 | print("Invalid algorithm choice for ", name, " review your inputted algorithm names") 168 | return 169 | 170 | times[name].append(time.time() - start) 171 | accs[name].append((baseline - result).norm() / baseline.norm()) 172 | 173 | # Data Frame population 174 | new_row_data = {'Cutoff Value': cutoff} 175 | for name in names: 176 | new_row_data[f'{name} Time'] = times[name][-1] 177 | new_row_data[f'{name} Accuracy'] = accs[name][-1] 178 | new_row_df = pd.DataFrame([new_row_data]) 179 | results_df = pd.concat([results_df, new_row_df], ignore_index=True) 180 | clear_output(wait=True) 181 | display(results_df) 182 | 183 | if highres: 184 | # High resolution separate plots 185 | plt.figure( dpi=400) 186 | plot_times_reversed(names, cutoffs, times) 187 | plt.tight_layout() 188 | plt.grid(True) 189 | plt.show() 190 | 191 | plt.figure( dpi=400) 192 | plot_accuracy_reversed(names, cutoffs, accs) 193 | plt.tight_layout() 194 | plt.grid(True) 195 | 196 | plt.show() 197 | else: 198 | # Standard multi-plot 199 | fig, axs = plt.subplots(1, 2, figsize=(12, 5)) 200 | plot_times_reversed(names, cutoffs, times, ax=axs[0]) 201 | plot_accuracy_reversed(names, cutoffs, accs, ax=axs[1]) 202 | if return_data: 203 | return times,accs 204 | 205 | # =========================================================================== 206 | # Library Benchmark 0 : Incremental QR (Averaged ) 207 | # =========================================================================== 208 | 209 | def incQr_benchmark(max_size=500, complex_matrices=False,log_scale=False): 210 | sizes = range(100, max_size, 50) # Matrix sizes from 100 to maxsize with steps of 50 211 | results_df = pd.DataFrame(columns=['Matrix Size', 'C++ Time Mean', 'SciPy Time Mean', 'NumPy Time Mean']) 212 | 213 | cpp_times_std = [] 214 | scipy_times_std = [] 215 | numpy_times_std = [] 216 | 217 | for size in sizes: 218 | cpp_times = [] 219 | scipy_times = [] 220 | numpy_times = [] 221 | 222 | if complex_matrices: 223 | A = np.random.randn(size, int(size / 50)) + 1j * np.random.randn(size, int(size / 50)) 224 | else: 225 | A = np.random.randn(size, int(size / 50)) 226 | 227 | for _ in range(5): 228 | # Timing C++ Implementation 229 | start_time = time.time() 230 | iqr = IncrementalQR(A) 231 | Q_cpp = iqr.get_q() 232 | cpp_times.append(time.time() - start_time) 233 | 234 | # Timing SciPy Implementation 235 | start_time = time.time() 236 | iqr = IncrementalQR(A, use_cpp_if_available=False) 237 | Q_sp = iqr.get_q() 238 | scipy_times.append(time.time() - start_time) 239 | 240 | # Timing NumPy QR Decomposition 241 | start_time = time.time() 242 | Q_np, _ = np.linalg.qr(A, mode='reduced') 243 | numpy_times.append(time.time() - start_time) 244 | 245 | cpp_mean = np.mean(cpp_times) 246 | scipy_mean = np.mean(scipy_times) 247 | numpy_mean = np.mean(numpy_times) 248 | 249 | cpp_std = np.std(cpp_times) 250 | scipy_std = np.std(scipy_times) 251 | numpy_std = np.std(numpy_times) 252 | 253 | 254 | cpp_times_std.append(cpp_std) 255 | scipy_times_std.append(scipy_std) 256 | numpy_times_std.append(numpy_std) 257 | 258 | new_row_data = { 259 | 'Matrix Size': size, 260 | 'C++ Time Mean': cpp_mean, 261 | 'SciPy Time Mean': scipy_mean, 262 | 'NumPy Time Mean': numpy_mean 263 | } 264 | new_row_df = pd.DataFrame([new_row_data]) 265 | results_df = pd.concat([results_df, new_row_df], ignore_index=True) 266 | 267 | clear_output(wait=True) 268 | display(results_df) 269 | 270 | # Convert results to numpy arrays for better compatibility with matplotlib 271 | matrix_sizes = results_df['Matrix Size'].values.astype(float) 272 | cpp_time_mean = results_df['C++ Time Mean'].values.astype(float) 273 | scipy_time_mean = results_df['SciPy Time Mean'].values.astype(float) 274 | numpy_time_mean = results_df['NumPy Time Mean'].values.astype(float) 275 | cpp_times_std = np.array(cpp_times_std, dtype=float) 276 | scipy_times_std = np.array(scipy_times_std, dtype=float) 277 | numpy_times_std = np.array(numpy_times_std, dtype=float) 278 | 279 | 280 | plt.figure(figsize=(10, 6)) 281 | 282 | plt.plot(matrix_sizes, cpp_time_mean, label='C++ Incremental', marker='o', markersize=3) 283 | plt.fill_between(matrix_sizes, 284 | cpp_time_mean - cpp_times_std, 285 | cpp_time_mean + cpp_times_std, 286 | alpha=0.2) 287 | 288 | plt.plot(matrix_sizes, scipy_time_mean, label='SciPy Incremental', marker='s', markersize=3) 289 | plt.fill_between(matrix_sizes, 290 | scipy_time_mean - scipy_times_std, 291 | scipy_time_mean + scipy_times_std, 292 | alpha=0.2) 293 | 294 | plt.plot(matrix_sizes, numpy_time_mean, label='NumPy QR', marker='^', markersize=3) 295 | plt.fill_between(matrix_sizes, 296 | numpy_time_mean - numpy_times_std, 297 | numpy_time_mean + numpy_times_std, 298 | alpha=0.2) 299 | 300 | plt.xlabel('Matrix Size N x N/50') 301 | if log_scale: 302 | plt.yscale('log') 303 | plt.ylabel('Time (seconds)') 304 | plt.title('QR Decomposition Timing Comparison 5 run average') 305 | plt.legend() 306 | plt.grid(True) 307 | plt.show() 308 | 309 | def fitting_experiment(mpo, mps, baseline, bond_dims, names, num_runs=1, a=-.5, b=1, highres=False, return_data=False, 310 | fit_sweeps=8, sketch_increment=10, sketch_dim=10): 311 | times = {name: [] for name in names} 312 | accs = {name: [] for name in names} 313 | std_times = {name: [] for name in names} 314 | std_accs = {name: [] for name in names} 315 | 316 | baseline.canonize() 317 | print(baseline.norm()) 318 | 319 | column_names = ['Bond Dimension'] + [f'{name} {metric}' for name in names for metric in ['Mean Time', 'Time Std', 'Mean Accuracy', 'Accuracy Std']] 320 | results_df = pd.DataFrame(columns=column_names) 321 | display(results_df) 322 | 323 | for bond_dim in bond_dims: 324 | temp_times = {name: [] for name in names} 325 | temp_accs = {name: [] for name in names} 326 | 327 | for run in range(num_runs): 328 | for name in names: 329 | start = time.time() 330 | if name == 'naive': 331 | result = mps_mpo_blas(mps, mpo, stop=FixedDimension(bond_dim), round_type="dass_blas") 332 | elif name == 'rand_then_orth': 333 | result = mps_mpo_blas(mps, mpo, stop=FixedDimension(bond_dim), round_type="rand_then_orth_blas", final_round=False) 334 | elif name == 'nyst': 335 | result = mps_mpo_blas(mps, mpo, stop=FixedDimension(bond_dim), round_type="nystrom_blas", final_round=False) 336 | elif name == 'random+oversample': 337 | result = random_contraction_inc(mpo, mps, stop=FixedDimension(max(int(np.ceil(1.75 * bond_dim)), bond_dim + 10)), accuracychecks=False, 338 | finalround=FixedDimension(bond_dim), sketchincrement=sketch_increment, sketchdim=sketch_dim) 339 | elif name == 'random': 340 | result = random_contraction_inc(mpo, mps, stop=FixedDimension(bond_dim), accuracychecks=False, 341 | finalround=None, sketchincrement=sketch_increment, sketchdim=sketch_dim) 342 | elif name == 'density': 343 | result = density_matrix(mpo, mps, stop=FixedDimension(bond_dim)) 344 | elif name == 'zipup': 345 | result = zipup(mpo, mps, stop=FixedDimension(bond_dim), finalround=None) 346 | elif name == 'fit': 347 | result = fit(mpo, mps, max_sweeps=fit_sweeps, stop=FixedDimension(bond_dim), guess="input") 348 | elif name == "fit2": 349 | result = fit(mpo, mps, max_sweeps=2, stop=FixedDimension(bond_dim), guess="input") 350 | elif name == "fit10": 351 | result = fit(mpo, mps, max_sweeps=10, stop=FixedDimension(bond_dim), guess="input") 352 | else: 353 | print("Invalid algorithm choice for ", name, " review your inputted algorithm names") 354 | return 355 | temp_times[name].append(time.time() - start) 356 | temp_accs[name].append((baseline - result).norm() / baseline.norm()) 357 | 358 | new_row_data = {'Bond Dimension': bond_dim} 359 | for name in names: 360 | times_mean = np.mean(temp_times[name]) 361 | times_std_val = np.std(temp_times[name]) 362 | acc_mean = np.mean(temp_accs[name]) 363 | acc_std_val = np.std(temp_accs[name]) 364 | 365 | new_row_data[f'{name} Mean Time'] = times_mean 366 | new_row_data[f'{name} Time Std'] = times_std_val 367 | new_row_data[f'{name} Mean Accuracy'] = acc_mean 368 | new_row_data[f'{name} Accuracy Std'] = acc_std_val 369 | 370 | times[name].append(times_mean) 371 | std_times[name].append(times_std_val) 372 | accs[name].append(acc_mean) 373 | std_accs[name].append(acc_std_val) 374 | 375 | new_row_df = pd.DataFrame([new_row_data]) 376 | results_df = pd.concat([results_df, new_row_df], ignore_index=True) 377 | clear_output(wait=True) 378 | display(results_df) 379 | 380 | if return_data: 381 | return times, std_times, accs, std_accs 382 | 383 | 384 | def cutoff_synth_tensor_experiment4(mpo,mps,baseline,cutoffs,names,a=-.5,b=1,highres=False,return_data=False, 385 | fit_sweeps=1,sketch_dim=2,sketch_increment=3,no_plot=True): 386 | # baseline.canonize() 387 | # print(baseline.norm()) 388 | 389 | times = {name: [] for name in names} 390 | accs = {name: [] for name in names} 391 | bond_dims = {name: [] for name in names} 392 | 393 | column_names = ['Cutoff Value'] + [f'{name} {metric}' for name in names for metric in ['Time', 'Accuracy']] 394 | results_df = pd.DataFrame(columns=column_names) 395 | display(results_df) 396 | 397 | 398 | for cutoff in cutoffs: 399 | for name in names: 400 | print(name) 401 | start = time.time() 402 | if name == 'naive': 403 | result = mps_mpo_blas(mps, mpo, stop=Cutoff(cutoff),round_type = "dass_blas") 404 | elif name == 'random': 405 | result = random_contraction_inc(mpo, mps, stop=Cutoff(cutoff), accuracychecks=False, 406 | finalround=Cutoff(cutoff),sketchincrement=sketch_increment,sketchdim=sketch_dim) 407 | elif name == 'random+oversample': 408 | result = random_contraction_inc(mpo, mps, stop=Cutoff(cutoff/10), accuracychecks=False, 409 | finalround=Cutoff(cutoff), sketchincrement=sketch_increment, sketchdim=sketch_dim) 410 | elif name == 'density': 411 | result = density_matrix(mpo, mps,stop=Cutoff(cutoff)) 412 | elif name == 'zipup': 413 | result = zipup(mpo, mps, stop=Cutoff(cutoff),finalround=False,conditioning=True) 414 | elif name == 'rzipup': 415 | result = zipup_randomsvd(mpo, mps, stop=Cutoff(cutoff),finalround=False,conditioning=True) 416 | elif name == 'fit': 417 | result = fit(mpo, mps, max_sweeps=fit_sweeps,stop=Cutoff(cutoff)) 418 | else: 419 | print("Invalid algorithm choice for ", name, " review your inputted algorithm names") 420 | return 421 | 422 | times[name].append(time.time() - start) 423 | accs[name].append((baseline - result).norm() / baseline.norm()) 424 | bond_dims[name].append(result.max_bond_dim()) 425 | 426 | # Data Frame population 427 | new_row_data = {'Cutoff Value': cutoff} 428 | for name in names: 429 | new_row_data[f'{name} Time'] = times[name][-1] 430 | new_row_data[f'{name} Accuracy'] = accs[name][-1] 431 | new_row_df = pd.DataFrame([new_row_data]) 432 | results_df = pd.concat([results_df, new_row_df], ignore_index=True) 433 | clear_output(wait=True) 434 | display(results_df) 435 | if no_plot: 436 | pass 437 | elif highres: 438 | # High resolution separate plots 439 | plt.figure( dpi=400) 440 | plot_times_reversed(names, cutoffs, times) 441 | plt.tight_layout() 442 | plt.grid(True) 443 | plt.show() 444 | 445 | plt.figure( dpi=400) 446 | plot_accuracy_reversed(names, cutoffs, accs) 447 | plt.tight_layout() 448 | plt.grid(True) 449 | 450 | plt.show() 451 | else: 452 | # Standard multi-plot 453 | fig, axs = plt.subplots(1, 2, figsize=(12, 5)) 454 | plot_times_reversed(names, cutoffs, times, ax=axs[0]) 455 | plot_accuracy_reversed(names, cutoffs, accs, ax=axs[1]) 456 | if return_data: 457 | return times,accs,bond_dims --------------------------------------------------------------------------------