├── .gitignore ├── tenpy_toycodes ├── __init__.py ├── lanczos.py ├── b_model.py ├── tfi_exact.py ├── h_utdvp.py ├── free_fermions_exact.py ├── c_tebd.py ├── a_mps.py ├── d_dmrg.py ├── i_uexcitations.py ├── e_tdvp.py ├── f_umps.py └── g_vumps.py ├── setup.py ├── README.md ├── LICENSE ├── exercise_tenpy.ipynb ├── exercise_2_mps.ipynb ├── solution_2_mps.ipynb ├── exercise_3_dmrg.ipynb └── exercises_uniform_toycodes.ipynb /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.egg-info 3 | __pycache__ 4 | .ipynb_checkpoints 5 | *.pdf 6 | *.png 7 | -------------------------------------------------------------------------------- /tenpy_toycodes/__init__.py: -------------------------------------------------------------------------------- 1 | # this file makes the directory a Python package that can be imported with `import toycodes` 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # this file allows to install toycodes of this repository, locally with 2 | # pip install -e . 3 | # or online with 4 | # pip install git+https://github.com/tenpy/tenpy_toycodes.git 5 | # to uninstall, simply 6 | # pip uninstall tenpy-toycodes 7 | 8 | from setuptools import setup 9 | 10 | setup(name='tenpy-toycodes', 11 | version='0.1.1', 12 | packages=['tenpy_toycodes']) 13 | -------------------------------------------------------------------------------- /tenpy_toycodes/lanczos.py: -------------------------------------------------------------------------------- 1 | """Simple Lanczos diagonalization routines. 2 | 3 | These functions can be used in d_dmrg.py and e_tdvp to replace 4 | the eigsh and expm_mulitply calls, as commented out in the code. 5 | """ 6 | # Copyright (C) TeNPy Developers, Apache license 7 | 8 | from scipy.sparse.linalg import expm 9 | import numpy as np 10 | import warnings 11 | 12 | default_k = 20 13 | 14 | def lanczos_ground_state(H, psi0, k=None): 15 | """Use lanczos to calculated the ground state of a hermitian H. 16 | 17 | If you don't know Lanczos, you can view this as a black box algorithm. 18 | The idea is to built an orthogonal basis in the Krylov space spanned by 19 | ``{ H^i |psi0> for i < k}`` and find the ground state in this sub space. 20 | It only requires an `H` which defines a matrix-vector multiplication. 21 | """ 22 | T, vecs = lanczos_iterations(H, psi0, k) 23 | E, v = np.linalg.eigh(T) 24 | result = vecs @ v[:, 0] 25 | if abs(np.linalg.norm(result) - 1.) > 1.e-5: 26 | warnings.warn("poorly conditioned lanczos. Maybe a non-hermitian H?") 27 | return E[0], result 28 | 29 | def lanczos_expm_multiply(H, psi0, dt, k=None): 30 | """Use lanczos to calculated ``expm(-i H dt)|psi0>`` for sufficiently small dt and hermitian H. 31 | 32 | If you don't know Lanczos, you can view this as a black box algorithm. 33 | The idea is to built an orthogonal basis in the Krylov space spanned by 34 | ``{ H^i |psi0> for i < k}`` and evolve only in this subspace. 35 | It only requires an `H` which defines a matrix-vector multiplication. 36 | """ 37 | T, vecs = lanczos_iterations(H, psi0, k) 38 | v0 = np.zeros(T.shape[0]) 39 | v0[0] = 1. 40 | vt = expm(-1.j*dt*T) @ v0 41 | result = vecs @ vt 42 | if abs(np.linalg.norm(result) - 1.) > 1.e-5: 43 | warnings.warn("poorly conditioned lanczos. Maybe a non-hermitian H?") 44 | return result 45 | 46 | def lanczos_iterations(H, psi0, k): 47 | """Perform `k` Lanczos iterations building tridiagonal matrix T and ONB of the Krylov space.""" 48 | if k is None: 49 | k = default_k 50 | if psi0.ndim != 1: 51 | raise ValueError("psi0 should be a vector") 52 | if H.shape[1] != psi0.shape[0]: 53 | raise ValueError("Shape of H doesn't match len of psi0.") 54 | psi0 = psi0/np.linalg.norm(psi0) 55 | vecs = [psi0] 56 | T = np.zeros((k, k)) 57 | psi = H @ psi0 58 | alpha = T[0, 0] = np.inner(psi0.conj(), psi).real 59 | psi = psi - alpha* vecs[-1] 60 | for i in range(1, k): 61 | beta = np.linalg.norm(psi) 62 | if beta < 1.e-13: 63 | # print(f"Lanczos terminated early after i={i:d} steps:" 64 | # "full Krylov space built") 65 | T = T[:i, :i] 66 | break 67 | psi /= beta 68 | vecs.append(psi) 69 | psi = H @ psi - beta * vecs[-2] 70 | alpha = np.inner(vecs[-1].conj(), psi).real 71 | psi = psi - alpha * vecs[-1] 72 | T[i, i] = alpha 73 | T[i-1, i] = T[i, i-1] = beta 74 | return T, np.array(vecs).T 75 | -------------------------------------------------------------------------------- /tenpy_toycodes/b_model.py: -------------------------------------------------------------------------------- 1 | """Toy code implementing the transverse-field ising model.""" 2 | # Copyright (C) TeNPy Developers, Apache license 3 | 4 | import numpy as np 5 | 6 | 7 | class TFIModel: 8 | r"""Simple class generating the Hamiltonian of the transverse-field Ising model. 9 | 10 | The Hamiltonian reads 11 | .. math :: 12 | H = - J \sum_{i} \sigma^z_i \sigma^z_{i+1} - g \sum_{i} \sigma^x_i 13 | 14 | Parameters 15 | ---------- 16 | L : int 17 | Number of sites. 18 | J, g : float 19 | Coupling parameters of the above defined Hamiltonian. 20 | bc : 'infinite', 'finite' 21 | Boundary conditions. 22 | 23 | Attributes 24 | ---------- 25 | L : int 26 | Number of sites. 27 | bc : 'infinite', 'finite' 28 | Boundary conditions. 29 | sigmax, sigmay, sigmaz, id : 30 | Local operators, namely the Pauli matrices and identity. 31 | H_bonds : list of np.Array[ndim=4] 32 | The Hamiltonian written in terms of local 2-site operators, ``H = sum_i H_bonds[i]``. 33 | Each ``H_bonds[i]`` has (physical) legs (i out, (i+1) out, i in, (i+1) in), 34 | in short ``i j i* j*``. 35 | H_mpo : lit of np.Array[ndim=4] 36 | The Hamiltonian written as an MPO. 37 | Each ``H_mpo[i]`` has legs (virtual left, virtual right, physical out, physical in), 38 | in short ``wL wR i i*``. 39 | """ 40 | def __init__(self, L, J, g, bc='finite'): 41 | assert bc in ['finite', 'infinite'] 42 | self.L, self.d, self.bc = L, 2, bc 43 | self.J, self.g = J, g 44 | self.sigmax = np.array([[0., 1.], [1., 0.]]) 45 | self.sigmay = np.array([[0., -1j], [1j, 0.]]) 46 | self.sigmaz = np.array([[1., 0.], [0., -1.]]) 47 | self.id = np.eye(2) 48 | self.init_H_bonds() 49 | self.init_H_mpo() 50 | 51 | def init_H_bonds(self): 52 | """Initialize `H_bonds` hamiltonian. 53 | 54 | Called by __init__(). 55 | """ 56 | X, Z, Id = self.sigmax, self.sigmaz, self.id 57 | d = self.d 58 | nbonds = self.L - 1 if self.bc == 'finite' else self.L 59 | H_list = [] 60 | for i in range(nbonds): 61 | gL = gR = 0.5 * self.g 62 | if self.bc == 'finite': 63 | if i == 0: 64 | gL = self.g 65 | if i + 1 == self.L - 1: 66 | gR = self.g 67 | H_bond = -self.J * np.kron(Z, Z) - gL * np.kron(X, Id) - gR * np.kron(Id, X) 68 | # note: kron is short-hand for outer product + grouping bra and ket legs. 69 | # H_bond has legs ``i, j, i*, j*`` 70 | H_list.append(np.reshape(H_bond, [d, d, d, d])) 71 | self.H_bonds = H_list 72 | 73 | # (note: not required for TEBD) 74 | def init_H_mpo(self): 75 | """Initialize `H_mpo` Hamiltonian. 76 | 77 | Called by __init__(). 78 | """ 79 | w_list = [] 80 | for i in range(self.L): 81 | w = np.zeros((3, 3, self.d, self.d), dtype=float) 82 | w[0, 0] = w[2, 2] = self.id 83 | w[0, 1] = self.sigmaz 84 | w[0, 2] = -self.g * self.sigmax 85 | w[1, 2] = -self.J * self.sigmaz 86 | w_list.append(w) 87 | self.H_mpo = w_list 88 | -------------------------------------------------------------------------------- /tenpy_toycodes/tfi_exact.py: -------------------------------------------------------------------------------- 1 | """Provides exact ground state (and excitation) energies for the transverse field ising model. 2 | 3 | The Hamiltonian reads 4 | .. math :: 5 | H = - J \\sum_{i} \\sigma^z_i \\sigma^z_{i+1} - g \\sum_{i} \\sigma^x_i 6 | 7 | For the exact analytical solution (in the thermodynamic limit) we use Subir Sachdev, Quantum Phase 8 | Transitions, 2nd ed, Cambridge University Press, 2011. 9 | """ 10 | # Copyright (C) TeNPy Developers, Apache license 11 | 12 | import numpy as np 13 | import scipy.sparse as sparse 14 | from scipy.sparse.linalg import eigsh 15 | import warnings 16 | import scipy.integrate 17 | 18 | 19 | def finite_gs_energy(L, J, g, return_psi=False): 20 | """For comparison: obtain ground state energy from exact diagonalization. 21 | 22 | Exponentially expensive in L, only works for small enough `L` <~ 20. 23 | """ 24 | if L >= 20: 25 | warnings.warn("Large L: Exact diagonalization might take a long time!") 26 | # get single site operaors 27 | sx = sparse.csr_matrix(np.array([[0., 1.], [1., 0.]])) 28 | sz = sparse.csr_matrix(np.array([[1., 0.], [0., -1.]])) 29 | id = sparse.csr_matrix(np.eye(2)) 30 | sx_list = [] # sx_list[i] = kron([id, id, ..., id, sx, id, .... id]) 31 | sz_list = [] 32 | for i_site in range(L): 33 | x_ops = [id] * L 34 | z_ops = [id] * L 35 | x_ops[i_site] = sx 36 | z_ops[i_site] = sz 37 | X = x_ops[0] 38 | Z = z_ops[0] 39 | for j in range(1, L): 40 | X = sparse.kron(X, x_ops[j], 'csr') 41 | Z = sparse.kron(Z, z_ops[j], 'csr') 42 | sx_list.append(X) 43 | sz_list.append(Z) 44 | H_zz = sparse.csr_matrix((2**L, 2**L)) 45 | H_x = sparse.csr_matrix((2**L, 2**L)) 46 | for i in range(L - 1): 47 | H_zz = H_zz + sz_list[i] * sz_list[(i + 1) % L] 48 | for i in range(L): 49 | H_x = H_x + sx_list[i] 50 | H = -J * H_zz - g * H_x 51 | E, V = eigsh(H, k=1, which='SA', return_eigenvectors=True, ncv=20) 52 | if return_psi: 53 | return E[0], V[:, 0] 54 | return E[0] 55 | 56 | 57 | """ 58 | By performing Jordan-Wigner, Fourier and Bogoliubov transformations, the TFI model with PBC can be 59 | diagonalized analytically. The Hamiltonian in terms of fermionic creation and annihilation operators 60 | \\gamma_{p}^{\\dagger} and \\gamma_{p} reads: 61 | 62 | H = (\\sum_{p} \\epsilon(p) \\gamma_{p}^{\\dagger}\\gamma_{p}) + E0. 63 | 64 | - Single particle excitation energy: \\epsilon(p) = 2 \\sqrt{J^2 - 2Jg\\cos(p) + g^2}. 65 | 66 | - Ground state energy: E0 = -\\sum_{p} \\epsilon(p)/2. 67 | """ 68 | 69 | def epsilon(p, J, g): 70 | return 2 * np.sqrt(J**2 - 2 * J * g * np.cos(p) + g**2) 71 | 72 | def infinite_gs_energy(J, g): 73 | """For comparison: Calculate ground state energy density from analytic formula. 74 | 75 | Compared to the above formula, we replace sum_k -> integral dk/2pi, to obtain the ground state 76 | energy density in the thermodynamic limit. 77 | """ 78 | e0_exact = -1 / (2 * np.pi) * scipy.integrate.quad(epsilon, -np.pi, np.pi, args=(J, g))[0]/2 79 | return e0_exact 80 | 81 | def infinite_excitation_dispersion(J, g): 82 | """For comparison: Calculate excitation dispersion relation from analytic formula.""" 83 | ps = np.arange(-np.pi, np.pi, 0.01) 84 | es_exact = epsilon(ps, J, g) 85 | return ps, es_exact -------------------------------------------------------------------------------- /tenpy_toycodes/h_utdvp.py: -------------------------------------------------------------------------------- 1 | """Toy code implementing the uniform time dependent variational principle (uTDVP). 2 | 3 | This implementation closely follows Laurens Vanderstraeten, Jutho Haegeman and Frank Verstraete, 4 | Tangent-space methods for uniform matrix product states, SciPost Physics Lecture Notes 007, 2019, 5 | https://arxiv.org/abs/1810.07006. 6 | """ 7 | 8 | import numpy as np 9 | from scipy.sparse.linalg import expm_multiply 10 | 11 | from .f_umps import UniformMPS 12 | from .g_vumps import Heff1, Heff0, get_Lh, get_Rh, subtract_energy_offset, get_AL_AR 13 | 14 | 15 | def utdvp_algorithm(psi0, h, dt, T): 16 | """Evolve the uMPS psi0 according to the Hamiltonian h up to time T in steps of dt.""" 17 | tdvp_engine = UTDVPEngine(psi0, h, tol=1.e-10) 18 | ts = [] 19 | Ss = [] # measure the entanglement entropy (or any other observable) 20 | t = 0. 21 | ts.append(t) 22 | Ss.append(psi0.get_entanglement_entropy()) 23 | N_steps = int(T/dt + 0.5) 24 | for i in range(N_steps): 25 | tdvp_engine.run(dt) 26 | t += dt 27 | ts.append(t) 28 | Ss.append(tdvp_engine.psi.get_entanglement_entropy()) 29 | print(f"uMPS evolved with TDVP up to time T={T} in steps of dt={dt}.") 30 | return ts, Ss 31 | 32 | 33 | class UTDVPEngine: 34 | """Simple class for the uTDVP engine to evolve uMPS psi according to h for small time step dt. 35 | 36 | Approximately integrates (d/dt)|psi(A)> = (-i * P_A * H)|psi(A)>, where P_A is the tangent-space 37 | projector and H = sum_n h_{n,n+1}. 38 | 39 | Parameters 40 | ---------- 41 | psi, h, tol: Same as attributes. 42 | 43 | Attributes 44 | ---------- 45 | psi: UniformMPS 46 | The current state to be time-evolved. 47 | h: np.array[ndim=4] 48 | The two-site Hamiltonian governing the time-evolution. 49 | tol: float 50 | Tolerance up to which the geometric sum environments Lh and Rh are computed with gmres. 51 | Lh: np.array[ndim=2] 52 | Left environment computed from geometric sum of transfer matrix TL. 53 | Rh: np.array[ndim=2] 54 | Right environment computed from geometric sum of transfer matrix TR. 55 | D, d: int 56 | The bond dimension and physical dimension of psi. 57 | """ 58 | def __init__(self, psi, h, tol): 59 | self.psi = psi.copy() 60 | self.h = subtract_energy_offset(self.psi, h, canonical_form=False) 61 | self.tol = tol 62 | self.Lh = get_Lh(self.psi, self.h, canonical_form=False, guess=None, tol=self.tol) 63 | self.Rh = get_Rh(self.psi, self.h, canonical_form=False, guess=None, tol=self.tol) 64 | 65 | def run(self, dt): 66 | """Evolve self.psi according to self.h for small time step dt. 67 | 68 | AC -> exp(-i * dt * Heff1)AC, 69 | C -> exp(-i * dt * Heff0)C, 70 | AL/AR from left/right polar decompositions of AC and C. 71 | """ 72 | H_eff_1 = Heff1(self.h, self.Lh, self.psi.AL, self.psi.AR, self.Rh) 73 | H_eff_0 = Heff0(self.h, self.Lh, self.psi.AL, self.psi.AR, self.Rh) 74 | AC_new = self.evolve_theta(Heff=H_eff_1, theta=self.psi.AC, dt=dt) 75 | C_new = self.evolve_theta(Heff=H_eff_0, theta=self.psi.C, dt=dt) 76 | AL_new, AR_new = get_AL_AR(AC_new, C_new) 77 | self.psi = UniformMPS(AL_new, AR_new, AC_new, C_new) 78 | self.h = subtract_energy_offset(self.psi, self.h, canonical_form=False) 79 | self.Lh = get_Lh(self.psi, self.h, canonical_form=False, guess=self.Lh, tol=self.tol) 80 | self.Rh = get_Rh(self.psi, self.h, canonical_form=False, guess=self.Rh, tol=self.tol) 81 | 82 | @staticmethod 83 | def evolve_theta(Heff, theta, dt): 84 | """Evolve |theta> -> exp(-i * dt * Heff)|theta>.""" 85 | theta = np.reshape(theta, Heff.shape[1]) 86 | theta_new = expm_multiply(-1.j * dt * Heff, theta, traceA=-1.j*dt*Heff.trace()) 87 | theta_new /= np.linalg.norm(theta_new) 88 | theta_new = np.reshape(theta_new, Heff.shape_theta) 89 | return theta_new -------------------------------------------------------------------------------- /tenpy_toycodes/free_fermions_exact.py: -------------------------------------------------------------------------------- 1 | r"""Provides exact entropies for time evolution of free fermion models.""" 2 | # Copyright (C) TeNPy Developers, Apache license 3 | 4 | import numpy as np 5 | import matplotlib.pyplot as plt 6 | 7 | 8 | def hopping_matrix(L, t=1., mu_staggered=0., boundary_conditions='open'): 9 | r"""Generate matrix for fermionic hopping in real-space c_i operators. 10 | 11 | Provide hopping matrix h_{ij} for 12 | 13 | .. math :: 14 | H = \sum_{ij} h_{ij} c_i^\dagger c_j 15 | = t \sum_{i} (c_i^\dagger c^\j + h.c.) + \mu_s (-1)^{i} n_i 16 | 17 | for open boundary conditions on L sites. 18 | """ 19 | assert boundary_conditions in ['open', 'periodic'] 20 | H = np.zeros((L,L)) 21 | for i in range(L - int(boundary_conditions == 'open')): 22 | H[i, (i+1) % L] = t 23 | H[(i+1) % L, i] = t 24 | for i in range(L): 25 | H[i, i] = mu_staggered*(-1)**i 26 | return H 27 | 28 | 29 | def charge_density_wave(L): 30 | """Initial state |010101010...>""" 31 | psi0 = np.mod(np.arange(0,L),2) 32 | return np.diag(psi0) 33 | 34 | 35 | def time_evolved_state(H_hop, psi0, time_list): 36 | """Evolve an initial correlation matrix with H_hop.""" 37 | E, U = np.linalg.eigh(H_hop) 38 | for time in time_list: 39 | X = np.dot(np.dot(np.conj(U),np.diag(np.exp(1j*time*E))),U.T) 40 | psi_t = np.dot(X,np.dot(psi0, np.conj(X.T))) 41 | yield psi_t # c^dagger_i c_j 42 | 43 | 44 | def entanglement_entropy(psi, bond=None): 45 | """Calculate entanglement entropy for cutting at given bond (default: half-chain).""" 46 | L = psi.shape[0] 47 | if bond is None: 48 | bond = L // 2 49 | z = np.linalg.eigvalsh(psi[:bond, :bond]) 50 | z = z[np.logical_and(z > 1.e-15, z < 1. - 1.e-15)] 51 | S = - np.sum(z * np.log(z) + (1. - z) * np.log(1. - z)) 52 | return S 53 | 54 | 55 | def XX_model_ground_state_energy(L, h_staggered, boundary_conditions='open'): 56 | """ 57 | """ 58 | H_hop = hopping_matrix(L, 2., 2.*h_staggered, boundary_conditions=boundary_conditions) 59 | E = np.linalg.eigvalsh(H_hop) 60 | E_shift = 0. if L % 2 == 0 else - h_staggered 61 | # the shift stems from the 1/2 in mapping sigmaz = (n - 1/2) for the h_s terms 62 | # which cancels out for even L due to the alternating sign of h_s 63 | return np.sum(E[:L//2]) + E_shift 64 | 65 | def XX_model_time_evolved_entropies(L, h_staggered, time_list, bond=None, 66 | boundary_conditions='open'): 67 | r"""Half-chain entanglement entropies for time evolving Neel state with XX chain. 68 | 69 | The XX chain given by the hamiltonian (here, X,Y,Z = Pauli matrices) 70 | 71 | .. math :: 72 | H = \sum_{i} (X_i X_{i+1} + Y_i Y_{i+1}) - h_s (-1)^i Z_i 73 | 74 | maps to free fermions through the Jordan-Wigner transformation. 75 | This function returns the entropies for a time evolution starting from the Neel state 76 | |up down up down ...>. 77 | """ 78 | psi_0 = charge_density_wave(L) 79 | H_hop = hopping_matrix(L, 2., h_staggered, boundary_conditions=boundary_conditions) 80 | S_list = [] 81 | for psi_t in time_evolved_state(H_hop, psi_0, time_list): 82 | S_list.append(entanglement_entropy(psi_t)) 83 | return np.array(S_list) 84 | 85 | 86 | if __name__ == "__main__": 87 | L = 100 88 | h_staggered = 1. 89 | time_list = np.linspace(0, 20., 100) 90 | for h_s in [0., 1., 2.]: 91 | S_list = XX_model_time_evolved_entropies(L, h_s, time_list) 92 | plt.plot(time_list, S_list, label="$h_s = {h_s:1.1f}$".format(h_s=h_s)) 93 | h_s = 0. 94 | S_list = XX_model_time_evolved_entropies(L, h_s, time_list, 95 | boundary_conditions='periodic') 96 | plt.plot(time_list, S_list, linestyle='--', 97 | label="$h_s = {h_s:1.1f}$, periodic".format(h_s=h_s)) 98 | S_list = XX_model_time_evolved_entropies(10, h_s, time_list) 99 | plt.plot(time_list, S_list, linestyle=':', 100 | label="$h_s = {h_s:1.1f}, L=10$".format(h_s=h_s)) 101 | 102 | plt.xlabel('$t$') 103 | plt.ylabel('$S$') 104 | plt.legend(loc='best') 105 | plt.show() 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tutorials on Tensor Networks and Matrix product states 2 | 3 | This is a set of tutorials used in various (Winter-/Summer-)Schools on Tensor Networks, e.g. from the [European Tensor Network](http://quantumtensor.eu/). Each school has it's on branch with the final notebooks used. 4 | 5 | This "main" branch keeps a general set of notebooks that can be used as an introduction and that forms the basis for the tutorials in the various schools, and is also included into the main TeNPy documentation. 6 | 7 | The tutorials are split into two parts as follows. 8 | 9 | In the first part, the `exercise_1_*.ipynb` notebooks use a set of small "toy codes", small python scripts that require only [Python](https://python.org) with [numpy](https://numpy.org) + [scipy](https://scipy.org) + [matplotlib](https://matplotlib.org). They should give you a good idea how the algorithms work without the need to understand a full library like TeNPy. 10 | These python files for this are in the folder `tenpy_toycodes`, and you need to look into them during the tutorials to see how they work. It should not be necessary to modify the python files, however; you can define all functions etc in the notebooks itself. This will ensure that the other notebooks using them keep working. 11 | The exercises itself are in interactive [jupyter notebooks](https://jupyter.org), where you can have code, output and plots together. 12 | 13 | The `exercise_tenpy.ipynb` notebook is for the second part, and uses the [TeNPy](https://github.com/tenpy/tenpy) library to demonstrate first examples of calling TeNPy rather than the toycodes. 14 | 15 | **DISCLAIMER**: The toycodes and examples used are not optimized, and we only use very small bond dimensions here to make sure everything runs quickly on a normal laptop. For state-of-the-art MPS calculations (especially for cylinders towards 2D), `chi` should be significantly larger, often on the order of several 1000s (and significantly more CPU time). 16 | 17 | ## Some References 18 | 19 | - [White, PRL 69, 2863 (1992)](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.69.2863), the original DMRG paper! 20 | - [Hauschild, Pollmann, arXiv:1805.0055](https://arxiv.org/abs/1805.0055), review with focus on TeNPy use cases 21 | - [Schollwoeck, arXiv:1008.3477](https://arxiv.org/abs/1008.3477), a classic review 22 | - [Haegeman et al, arXiv:1103.0936](https://arxiv.org/abs/1103.0936), the original application of TDVP to MPS 23 | - [Haegeman et al, arXiv:1408.5056](https://arxiv.org/abs/1408.5056), discussed in the TDVP lecture 24 | - [Vanderstraeten et al, arXiv:1810.07006](https://arxiv.org/abs/1810.07006), a good review of the tangent space for infinite MPS 25 | - [Paeckel et al, arXiv:1901.05824](https://arxiv.org/abs/1901.05824), a nice review comparing various MPS time evolution methods 26 | - [More references in the TeNPy docs](https://tenpy.readthedocs.io/en/latest/literature.html) 27 | 28 | 29 | ## Setup 30 | 31 | **Running locally**: If you have a working Python installation, feel free to solve all the exercises locally on your own computer. For the most part, you only need the `*.ipynb` notebooks and the `tenpy_toycodes/` folder. 32 | For the second part, you need to [install TeNPy](https://tenpy.readthedocs.io/en/latest/INSTALL.html), which is often just a `conda install physics-tenpy` or `pip install physics-tenpy`, depending on your setup. 33 | 34 | **Jupyter notebooks**: We recommend solving the exercises interactively with [jupyter notebooks](https//jupyter.org). You can get it with `conda install jupyterlab` or `pip install jupyterlab` and then run `jupyter-lab`, which opens an interactive coding session in your web browser. 35 | 36 | **Running notebooks on Google colab**: You can also use [Google's colab cloud service](https://colab.research.google.com) to run the jupyter notebooks **without any local installation**. Use this option if you have any trouble with your local installation. However, you have to perform addiontal installs: 37 | - For the first part, `exercise_1_*.ipynb`, you need to make sure that you not only copy the notebooks itself onto google colab, but also the `tenpy_toycodes/` folder (including the `__init__.py` file). 38 | Alternatively, install them by adding and executing a notebook cell `!pip install git+https://github.com/tenpy/tenpy_toycodes.git` at the top of the notebooks. 39 | - For the second part, `exercise_tenpy.ipynb`, you need to install TeNPy. On google colab, this can be done by adding and executing a notebook cell `!pip install physics-tenpy` at the top of the notebook. 40 | 41 | 42 | ## License 43 | 44 | All codes are released under the Apache license given in the file `LICENSE`, which means you can freely copy, share and distribute the code. 45 | They toycodes in the folder `tenpy_toycodes` have formerly been distributed directly from the [TeNPy](https://github.com/tenpy/tenpy) repository - originally under the GPL v3 license, see also [this issue on the license change](https://github.com/tenpy/tenpy/issues/462). 46 | -------------------------------------------------------------------------------- /tenpy_toycodes/c_tebd.py: -------------------------------------------------------------------------------- 1 | """Toy code implementing the time evolving block decimation (TEBD).""" 2 | # Copyright (C) TeNPy Developers, Apache license 3 | 4 | import numpy as np 5 | from scipy.linalg import expm 6 | from .a_mps import split_truncate_theta 7 | 8 | 9 | def calc_U_bonds(H_bonds, dt): 10 | """Given the H_bonds, calculate ``U_bonds[i] = expm(-dt*H_bonds[i])``. 11 | 12 | Each local operator has legs (i out, (i+1) out, i in, (i+1) in), in short ``i j i* j*``. 13 | Note that no imaginary 'i' is included, thus real `dt` means 'imaginary time' evolution! 14 | """ 15 | d = H_bonds[0].shape[0] 16 | U_bonds = [] 17 | for H in H_bonds: 18 | H = np.reshape(H, [d * d, d * d]) 19 | U = expm(-dt * H) 20 | U_bonds.append(np.reshape(U, [d, d, d, d])) 21 | return U_bonds 22 | 23 | 24 | def run_TEBD(psi, U_bonds, N_steps, chi_max, eps): 25 | """Evolve for `N_steps` time steps with TEBD.""" 26 | Nbonds = psi.L - 1 if psi.bc == 'finite' else psi.L 27 | assert len(U_bonds) == Nbonds 28 | for n in range(N_steps): 29 | for k in [0, 1]: # even, odd 30 | for i_bond in range(k, Nbonds, 2): 31 | update_bond(psi, i_bond, U_bonds[i_bond], chi_max, eps) 32 | # done 33 | 34 | 35 | def update_bond(psi, i, U_bond, chi_max, eps): 36 | """Apply `U_bond` acting on i,j=(i+1) to `psi`.""" 37 | j = (i + 1) % psi.L 38 | # construct theta matrix 39 | theta = psi.get_theta2(i) # vL i j vR 40 | # apply U 41 | Utheta = np.tensordot(U_bond, theta, axes=([2, 3], [1, 2])) # i j [i*] [j*], vL [i] [j] vR 42 | Utheta = np.transpose(Utheta, [2, 0, 1, 3]) # vL i j vR 43 | # split and truncate 44 | Ai, Sj, Bj = split_truncate_theta(Utheta, chi_max, eps) 45 | # put back into MPS 46 | Gi = np.tensordot(np.diag(psi.Ss[i]**(-1)), Ai, axes=(1, 0)) # vL [vL*], [vL] i vC 47 | psi.Bs[i] = np.tensordot(Gi, np.diag(Sj), axes=(2, 0)) # vL i [vC], [vC] vC 48 | psi.Ss[j] = Sj # vC 49 | psi.Bs[j] = Bj # vC j vR 50 | 51 | 52 | def example_TEBD_gs_tf_ising_finite(L, g, chi_max=30): 53 | print("finite TEBD, imaginary time evolution, transverse field Ising") 54 | print("L={L:d}, g={g:.2f}".format(L=L, g=g)) 55 | from . import a_mps 56 | from . import b_model 57 | model = b_model.TFIModel(L=L, J=1., g=g, bc='finite') 58 | psi = a_mps.init_FM_MPS(model.L, model.d, model.bc) 59 | for dt in [0.1, 0.01, 0.001, 1.e-4, 1.e-5]: 60 | U_bonds = calc_U_bonds(model.H_bonds, dt) 61 | run_TEBD(psi, U_bonds, N_steps=500, chi_max=chi_max, eps=1.e-10) 62 | E = np.sum(psi.bond_expectation_value(model.H_bonds)) 63 | print("dt = {dt:.5f}: E = {E:.13f}".format(dt=dt, E=E)) 64 | print("final bond dimensions: ", psi.get_chi()) 65 | mag_x = np.sum(psi.site_expectation_value(model.sigmax)) 66 | mag_z = np.sum(psi.site_expectation_value(model.sigmaz)) 67 | print("magnetization in X = {mag_x:.5f}".format(mag_x=mag_x)) 68 | print("magnetization in Z = {mag_z:.5f}".format(mag_z=mag_z)) 69 | if L < 20: # compare to exact result 70 | from .tfi_exact import finite_gs_energy 71 | E_exact = finite_gs_energy(L, 1., g) 72 | print("Exact diagonalization: E = {E:.13f}".format(E=E_exact)) 73 | print("relative error: ", abs((E - E_exact) / E_exact)) 74 | return E, psi, model 75 | 76 | 77 | def example_TEBD_gs_tf_ising_infinite(g, chi_max=30): 78 | print("infinite TEBD, imaginary time evolution, transverse field Ising") 79 | print("g={g:.2f}".format(g=g)) 80 | from . import a_mps 81 | from . import b_model 82 | model = b_model.TFIModel(L=2, J=1., g=g, bc='infinite') 83 | psi = a_mps.init_FM_MPS(model.L, model.d, model.bc) 84 | for dt in [0.1, 0.01, 0.001, 1.e-4, 1.e-5]: 85 | U_bonds = calc_U_bonds(model.H_bonds, dt) 86 | run_TEBD(psi, U_bonds, N_steps=500, chi_max=chi_max, eps=1.e-10) 87 | E = np.mean(psi.bond_expectation_value(model.H_bonds)) 88 | print("dt = {dt:.5f}: E (per site) = {E:.13f}".format(dt=dt, E=E)) 89 | print("final bond dimensions: ", psi.get_chi()) 90 | mag_x = np.mean(psi.site_expectation_value(model.sigmax)) 91 | mag_z = np.mean(psi.site_expectation_value(model.sigmaz)) 92 | print(" = {mag_x:.5f}".format(mag_x=mag_x)) 93 | print(" = {mag_z:.5f}".format(mag_z=mag_z)) 94 | print("correlation length:", psi.correlation_length()) 95 | # compare to exact result 96 | from .tfi_exact import infinite_gs_energy 97 | E_exact = infinite_gs_energy(1., g) 98 | print("Analytic result: E (per site) = {E:.13f}".format(E=E_exact)) 99 | print("relative error: ", abs((E - E_exact) / E_exact)) 100 | return E, psi, model 101 | 102 | 103 | def example_TEBD_tf_ising_lightcone(L, g, tmax, dt, chi_max=50): 104 | print("finite TEBD, real time evolution, transverse field Ising") 105 | print("L={L:d}, g={g:.2f}, tmax={tmax:.2f}, dt={dt:.3f}".format(L=L, g=g, tmax=tmax, dt=dt)) 106 | # find ground state with TEBD or DMRG 107 | # E, psi, model = example_TEBD_gs_tf_ising_finite(L, g) 108 | from .d_dmrg import example_DMRG_tf_ising_finite 109 | E, psi, model = example_DMRG_tf_ising_finite(L, g) 110 | i0 = L // 2 111 | # apply sigmaz on site i0 112 | SzB = np.tensordot(model.sigmaz, psi.Bs[i0], axes=(1, 1)) # i [i*], vL [i] vR 113 | psi.Bs[i0] = np.transpose(SzB, [1, 0, 2]) # vL i vR 114 | U_bonds = calc_U_bonds(model.H_bonds, 1.j * dt) # (imaginary dt -> realtime evolution) 115 | S = [psi.entanglement_entropy()] 116 | Nsteps = int(tmax / dt + 0.5) 117 | for n in range(Nsteps): 118 | if abs((n * dt + 0.1) % 0.2 - 0.1) < 1.e-10: 119 | print("t = {t:.2f}, chi =".format(t=n * dt), psi.get_chi()) 120 | run_TEBD(psi, U_bonds, 1, chi_max=chi_max, eps=1.e-10) 121 | S.append(psi.entanglement_entropy()) 122 | import matplotlib.pyplot as plt 123 | plt.figure() 124 | plt.imshow(S[::-1], 125 | vmin=0., 126 | aspect='auto', 127 | interpolation='nearest', 128 | extent=(0, L - 1., -0.5 * dt, (Nsteps + 0.5) * dt)) 129 | plt.xlabel('site $i$') 130 | plt.ylabel('time $t/J$') 131 | plt.ylim(0., tmax) 132 | plt.colorbar().set_label('entropy $S$') 133 | filename = 'c_tebd_lightcone_{g:.2f}_chi_{chi_max:d}.pdf'.format(g=g, chi_max=chi_max) 134 | plt.savefig(filename) 135 | print("saved " + filename) 136 | -------------------------------------------------------------------------------- /tenpy_toycodes/a_mps.py: -------------------------------------------------------------------------------- 1 | """Toy code implementing a matrix product state.""" 2 | # Copyright (C) TeNPy Developers, Apache license 3 | 4 | import numpy as np 5 | from scipy.linalg import svd 6 | 7 | import warnings 8 | 9 | 10 | class SimpleMPS: 11 | """Simple class for a matrix product state. 12 | 13 | We index sites with `i` from 0 to L-1; bond `i` is left of site `i`. 14 | We *assume* that the state is in right-canonical form. 15 | 16 | Parameters 17 | ---------- 18 | Bs, Ss, bc: 19 | Same as attributes. 20 | 21 | Attributes 22 | ---------- 23 | Bs : list of np.Array[ndim=3] 24 | The 'matrices', in right-canonical form, one for each physical site 25 | (within the unit-cell for an infinite MPS). 26 | Each `B[i]` has legs (virtual left, physical, virtual right), in short ``vL i vR``. 27 | Ss : list of np.Array[ndim=1] 28 | The Schmidt values at each of the bonds, ``Ss[i]`` is left of ``Bs[i]``. 29 | bc : 'infinite', 'finite' 30 | Boundary conditions. 31 | L : int 32 | Number of sites (in the unit-cell for an infinite MPS). 33 | nbonds : int 34 | Number of (non-trivial) bonds: L-1 for 'finite' boundary conditions, L for 'infinite'. 35 | """ 36 | def __init__(self, Bs, Ss, bc='finite'): 37 | assert bc in ['finite', 'infinite'] 38 | self.Bs = Bs 39 | self.Ss = Ss 40 | self.bc = bc 41 | self.L = len(Bs) 42 | self.nbonds = self.L - 1 if self.bc == 'finite' else self.L 43 | 44 | def copy(self): 45 | return SimpleMPS([B.copy() for B in self.Bs], [S.copy() for S in self.Ss], self.bc) 46 | 47 | def get_theta1(self, i): 48 | """Calculate effective single-site wave function on sites i in mixed canonical form. 49 | 50 | The returned array has legs ``vL, i, vR`` (as one of the Bs). 51 | """ 52 | return np.tensordot(np.diag(self.Ss[i]), self.Bs[i], [1, 0]) # vL [vL'], [vL] i vR 53 | 54 | def get_theta2(self, i): 55 | """Calculate effective two-site wave function on sites i,j=(i+1) in mixed canonical form. 56 | 57 | The returned array has legs ``vL, i, j, vR``. 58 | """ 59 | j = (i + 1) % self.L 60 | return np.tensordot(self.get_theta1(i), self.Bs[j], [2, 0]) # vL i [vR], [vL] j vR 61 | 62 | def get_chi(self): 63 | """Return bond dimensions.""" 64 | return [self.Bs[i].shape[2] for i in range(self.nbonds)] 65 | 66 | def site_expectation_value(self, op): 67 | """Calculate expectation values of a local operator at each site.""" 68 | result = [] 69 | for i in range(self.L): 70 | theta = self.get_theta1(i) # vL i vR 71 | op_theta = np.tensordot(op, theta, axes=(1, 1)) # i [i*], vL [i] vR 72 | result.append(np.tensordot(theta.conj(), op_theta, [[0, 1, 2], [1, 0, 2]])) 73 | # [vL*] [i*] [vR*], [i] [vL] [vR] 74 | return np.real_if_close(result) 75 | 76 | def bond_expectation_value(self, op): 77 | """Calculate expectation values of a local operator at each bond.""" 78 | result = [] 79 | for i in range(self.nbonds): 80 | theta = self.get_theta2(i) # vL i j vR 81 | op_theta = np.tensordot(op[i], theta, axes=([2, 3], [1, 2])) 82 | # i j [i*] [j*], vL [i] [j] vR 83 | result.append(np.tensordot(theta.conj(), op_theta, [[0, 1, 2, 3], [2, 0, 1, 3]])) 84 | # [vL*] [i*] [j*] [vR*], [i] [j] [vL] [vR] 85 | return np.real_if_close(result) 86 | 87 | def entanglement_entropy(self): 88 | """Return the (von-Neumann) entanglement entropy for a bipartition at any of the bonds.""" 89 | bonds = range(1, self.L) if self.bc == 'finite' else range(0, self.L) 90 | result = [] 91 | for i in bonds: 92 | S = self.Ss[i] 93 | S = S[S > 1.e-20] # 0*log(0) should give 0 and won't contribute to the sum 94 | # avoid warning or NaN by discarding the very small values of S 95 | S2 = S * S 96 | assert abs(np.linalg.norm(S) - 1.) < 1.e-13 97 | result.append(-np.sum(S2 * np.log(S2))) 98 | return np.array(result) 99 | 100 | def correlation_length(self): 101 | """Diagonalize transfer matrix to obtain the correlation length.""" 102 | from scipy.sparse.linalg import eigs 103 | if self.get_chi()[0] > 100: 104 | warnings.warn("Skip calculating correlation_length() for large chi: could take long") 105 | return -1. 106 | assert self.bc == 'infinite' # works only in the infinite case 107 | B = self.Bs[0] # vL i vR 108 | chi = B.shape[0] 109 | T = np.tensordot(B, np.conj(B), axes=(1, 1)) # vL [i] vR, vL* [i*] vR* 110 | T = np.transpose(T, [0, 2, 1, 3]) # vL vL* vR vR* 111 | for i in range(1, self.L): 112 | B = self.Bs[i] 113 | T = np.tensordot(T, B, axes=(2, 0)) # vL vL* [vR] vR*, [vL] i vR 114 | T = np.tensordot(T, np.conj(B), axes=([2, 3], [0, 1])) 115 | # vL vL* [vR*] [i] vR, [vL*] [i*] vR* 116 | T = np.reshape(T, (chi**2, chi**2)) 117 | # Obtain the 2nd largest eigenvalue 118 | eta = eigs(T, k=2, which='LM', return_eigenvectors=False, ncv=20) 119 | xi = -self.L / np.log(np.min(np.abs(eta))) 120 | if xi > 1000.: 121 | return np.inf 122 | return xi 123 | 124 | def correlation_function(self, op_i, i, op_j, j): 125 | """Correlation function between two distant operators on sites i < j. 126 | 127 | Note: calling this function in a loop over `j` is inefficient for large j >> i. 128 | The optimization is left as an exercise to the user. 129 | Hint: Re-use the partial contractions up to but excluding site `j`. 130 | """ 131 | assert i < j 132 | theta = self.get_theta1(i) # vL i vR 133 | C = np.tensordot(op_i, theta, axes=(1, 1)) # i [i*], vL [i] vR 134 | C = np.tensordot(theta.conj(), C, axes=([0, 1], [1, 0])) # [vL*] [i*] vR*, [i] [vL] vR 135 | for k in range(i + 1, j): 136 | k = k % self.L 137 | B = self.Bs[k] # vL k vR 138 | C = np.tensordot(C, B, axes=(1, 0)) # vR* [vR], [vL] k vR 139 | C = np.tensordot(B.conj(), C, axes=([0, 1], [0, 1])) # [vL*] [k*] vR*, [vR*] [k] vR 140 | j = j % self.L 141 | B = self.Bs[j] # vL k vR 142 | C = np.tensordot(C, B, axes=(1, 0)) # vR* [vR], [vL] j vR 143 | C = np.tensordot(op_j, C, axes=(1, 1)) # j [j*], vR* [j] vR 144 | C = np.tensordot(B.conj(), C, axes=([0, 1, 2], [1, 0, 2])) # [vL*] [j*] [vR*], [j] [vR*] [vR] 145 | return C 146 | 147 | 148 | def init_FM_MPS(L, d=2, bc='finite'): 149 | """Return a ferromagnetic MPS (= product state with all spins up)""" 150 | B = np.zeros([1, d, 1], dtype=float) 151 | B[0, 0, 0] = 1. 152 | S = np.ones([1], dtype=float) 153 | Bs = [B.copy() for i in range(L)] 154 | Ss = [S.copy() for i in range(L)] 155 | return SimpleMPS(Bs, Ss, bc=bc) 156 | 157 | 158 | def init_Neel_MPS(L, d=2, bc='finite'): 159 | """Return a Neel state MPS (= product state with alternating spins up down up down... )""" 160 | S = np.ones([1], dtype=float) 161 | Bs = [] 162 | for i in range(L): 163 | B = np.zeros([1, d, 1], dtype=float) 164 | if i % 2 == 0: 165 | B[0, 0, 0] = 1. 166 | else: 167 | B[0, -1, 0] = 1. 168 | Bs.append(B) 169 | Ss = [S.copy() for i in range(L)] 170 | return SimpleMPS(Bs, Ss, bc=bc) 171 | 172 | 173 | def split_truncate_theta(theta, chi_max, eps): 174 | """Split and truncate a two-site wave function in mixed canonical form. 175 | 176 | Split a two-site wave function as follows:: 177 | vL --(theta)-- vR => vL --(A)--diag(S)--(B)-- vR 178 | | | | | 179 | i j i j 180 | 181 | Afterwards, truncate in the new leg (labeled ``vC``). 182 | 183 | Parameters 184 | ---------- 185 | theta : np.Array[ndim=4] 186 | Two-site wave function in mixed canonical form, with legs ``vL, i, j, vR``. 187 | chi_max : int 188 | Maximum number of singular values to keep 189 | eps : float 190 | Discard any singular values smaller than that. 191 | 192 | Returns 193 | ------- 194 | A : np.Array[ndim=3] 195 | Left-canonical matrix on site i, with legs ``vL, i, vC`` 196 | S : np.Array[ndim=1] 197 | Singular/Schmidt values. 198 | B : np.Array[ndim=3] 199 | Right-canonical matrix on site j, with legs ``vC, j, vR`` 200 | """ 201 | chivL, dL, dR, chivR = theta.shape 202 | theta = np.reshape(theta, [chivL * dL, dR * chivR]) 203 | X, Y, Z = svd(theta, full_matrices=False) 204 | # truncate 205 | chivC = min(chi_max, np.sum(Y > eps)) 206 | assert chivC >= 1 207 | piv = np.argsort(Y)[::-1][:chivC] # keep the largest `chivC` singular values 208 | X, Y, Z = X[:, piv], Y[piv], Z[piv, :] 209 | # renormalize 210 | S = Y / np.linalg.norm(Y) # == Y/sqrt(sum(Y**2)) 211 | # split legs of X and Z 212 | A = np.reshape(X, [chivL, dL, chivC]) 213 | B = np.reshape(Z, [chivC, dR, chivR]) 214 | return A, S, B 215 | -------------------------------------------------------------------------------- /tenpy_toycodes/d_dmrg.py: -------------------------------------------------------------------------------- 1 | """Toy code implementing the density-matrix renormalization group (DMRG).""" 2 | # Copyright (C) TeNPy Developers, Apache license 3 | 4 | import numpy as np 5 | from .a_mps import split_truncate_theta 6 | import scipy.sparse 7 | from scipy.sparse.linalg import eigsh 8 | 9 | 10 | class SimpleDMRGEngine: 11 | """DMRG algorithm, implemented as class holding the necessary data. 12 | 13 | DMRG sweeps left-right-left through the system, moving the orthogonality center along. 14 | Here, we still just save right-canonical `B` tensors in `psi`, which requires taking inverses 15 | of the Schmidt values - this is bad practice, but it keeps two things simpler: 16 | - We don't need book keeping in the MPS class to keep track of the canonical form, and all the 17 | MPS methods (expectation values etc) can just *assume* that the MPS is in right-canonical form. 18 | - The generalization to the infinite case is straight forward. 19 | Note, however, that we only use the A and B tensors directly from the SVD (without taking 20 | inverses) to update the environments. The effective Hamiltonian does thus not suffer 21 | from taking the inverses of Schmidt values. 22 | 23 | Parameters 24 | ---------- 25 | psi, model, chi_max, eps: 26 | See attributes 27 | 28 | Attributes 29 | ---------- 30 | psi : SimpleMPS 31 | The current ground-state (approximation). 32 | model : 33 | The model of which the groundstate is to be calculated. Needs to have an `H_mpo`. 34 | chi_max, eps: 35 | Truncation parameters, see :func:`a_mps.split_truncate_theta`. 36 | LPs, RPs : list of np.Array[ndim=3] 37 | Left and right parts ("environments") of the effective Hamiltonian. 38 | ``LPs[i]`` is the contraction of all parts left of site `i` in the network ````, 39 | and similar ``RPs[i]`` for all parts right of site `i`. 40 | Each ``LPs[i]`` has legs ``vL wL* vL*``, ``RPs[i]`` has legs ``vR* wR* vR`` 41 | """ 42 | def __init__(self, psi, model, chi_max, eps): 43 | assert psi.L == model.L and psi.bc == model.bc # ensure compatibility 44 | self.H_mpo = model.H_mpo 45 | self.psi = psi 46 | self.LPs = [None] * psi.L 47 | self.RPs = [None] * psi.L 48 | self.chi_max = chi_max 49 | self.eps = eps 50 | # initialize left and right environment 51 | D = self.H_mpo[0].shape[0] 52 | chi = psi.Bs[0].shape[0] 53 | LP = np.zeros([chi, D, chi], dtype=float) # vL wL* vL* 54 | RP = np.zeros([chi, D, chi], dtype=float) # vR* wR* vR 55 | LP[:, 0, :] = np.eye(chi) 56 | RP[:, D - 1, :] = np.eye(chi) 57 | self.LPs[0] = LP 58 | self.RPs[-1] = RP 59 | # initialize necessary RPs 60 | for i in range(psi.L - 1, 1, -1): 61 | self.update_RP(i, psi.Bs[i]) 62 | 63 | def sweep(self): 64 | # sweep from left to right 65 | for i in range(self.psi.nbonds - 1): 66 | self.update_bond(i) 67 | # sweep from right to left 68 | for i in range(self.psi.nbonds - 1, 0, -1): 69 | E0 = self.update_bond(i) 70 | return E0 71 | 72 | def update_bond(self, i): 73 | j = (i + 1) % self.psi.L 74 | # get effective Hamiltonian 75 | Heff = SimpleHeff2(self.LPs[i], self.RPs[j], self.H_mpo[i], self.H_mpo[j]) 76 | # Diagonalize Heff, find ground state `theta` 77 | theta_guess = self.psi.get_theta2(i) 78 | E0, theta = self.diag(Heff, theta_guess) 79 | # split and truncate 80 | Ai, Sj, Bj = split_truncate_theta(theta, self.chi_max, self.eps) 81 | # put back into MPS 82 | Gi = np.tensordot(np.diag(self.psi.Ss[i]**(-1)), Ai, axes=(1, 0)) # vL [vL*], [vL] i vC 83 | self.psi.Bs[i] = np.tensordot(Gi, np.diag(Sj), axes=(2, 0)) # vL i [vC], [vC*] vC 84 | self.psi.Ss[j] = Sj # vC 85 | self.psi.Bs[j] = Bj # vC j vR 86 | self.update_LP(i, Ai) 87 | self.update_RP(j, Bj) 88 | return E0 89 | 90 | def diag(self, Heff, guess): 91 | """Diagonalize the effective hamiltonian with an initial guess.""" 92 | guess = np.reshape(guess, [Heff.shape[1]]) 93 | E, V = eigsh(Heff, k=1, which='SA', return_eigenvectors=True, v0=guess) 94 | return E, np.reshape(V[:, 0], Heff.theta_shape) 95 | # # alternatively, use custom lanczos implementation 96 | # from .lanczos import lanczos_ground_state 97 | # return lanczos_ground_state(H, guess) 98 | 99 | def update_RP(self, i, B): 100 | """Calculate RP environment right of site `i-1`. 101 | 102 | Uses RP right of `i` and the given, right-canonical `B` on site `i`.""" 103 | j = (i - 1) % self.psi.L 104 | RP = self.RPs[i] # vR* wR* vR 105 | # B has legs vL i vR 106 | Bc = B.conj() # vL* i* vR* 107 | W = self.H_mpo[i] # wL wR i i* 108 | RP = np.tensordot(B, RP, axes=(2, 0)) # vL i [vR], [vR*] wR* vR 109 | RP = np.tensordot(RP, W, axes=([1, 2], [3, 1])) # vL [i] [wR*] vR, wL [wR] i [i*] 110 | RP = np.tensordot(RP, Bc, axes=([1, 3], [2, 1])) # vL [vR] wL [i], vL* [i*] [vR*] 111 | self.RPs[j] = RP # vL wL vL* (== vR* wR* vR on site i-1) 112 | 113 | def update_LP(self, i, A): 114 | """Calculate LP environment left of site `i+1`. 115 | 116 | Uses the LP left of site `i` and the given, left-canonical `A` on site `i`.""" 117 | j = (i + 1) % self.psi.L 118 | LP = self.LPs[i] # vL wL vL* 119 | # A has legs vL i vR 120 | Ac = A.conj() # vL* i* vR* 121 | W = self.H_mpo[i] # wL wR i i* 122 | LP = np.tensordot(LP, A, axes=(2, 0)) # vL wL* [vL*], [vL] i vR 123 | LP = np.tensordot(W, LP, axes=([0, 3], [1, 2])) # [wL] wR i [i*], vL [wL*] [i] vR 124 | LP = np.tensordot(Ac, LP, axes=([0, 1], [2, 1])) # [vL*] [i*] vR*, wR [i] [vL] vR 125 | self.LPs[j] = LP # vR* wR vR (== vL wL* vL* on site i+1) 126 | 127 | 128 | class SimpleHeff2(scipy.sparse.linalg.LinearOperator): 129 | """Class for the effective Hamiltonian on two sites. 130 | 131 | To be diagonalized in `SimpleDMRGEnginge.diag` during the bond update. Looks like this:: 132 | 133 | .--vL* vR*--. 134 | | i* j* | 135 | | | | | 136 | (LP)----(W1)--(W2)----(RP) 137 | | | | | 138 | | i j | 139 | .--vL vR--. 140 | """ 141 | def __init__(self, LP, RP, W1, W2): 142 | self.LP = LP # vL wL* vL* 143 | self.RP = RP # vR* wR* vR 144 | self.W1 = W1 # wL wC i i* 145 | self.W2 = W2 # wC wR j j* 146 | chi1, chi2 = LP.shape[0], RP.shape[2] 147 | d1, d2 = W1.shape[2], W2.shape[2] 148 | self.theta_shape = (chi1, d1, d2, chi2) # vL i j vR 149 | self.shape = (chi1 * d1 * d2 * chi2, chi1 * d1 * d2 * chi2) 150 | self.dtype = W1.dtype 151 | 152 | def _matvec(self, theta): 153 | """Calculate the matrix-vecotr product |theta'> = H_eff |theta>. 154 | 155 | This function is used by :func:`scipy.sparse.linalg.eigsh` to diagonalize 156 | the effective Hamiltonian with a Lanczos method, withouth generating the full matrix. 157 | """ 158 | x = np.reshape(theta, self.theta_shape) # vL i j vR 159 | x = np.tensordot(self.LP, x, axes=(2, 0)) # vL wL* [vL*], [vL] i j vR 160 | x = np.tensordot(x, self.W1, axes=([1, 2], [0, 3])) # vL [wL*] [i] j vR, [wL] wC i [i*] 161 | x = np.tensordot(x, self.W2, axes=([3, 1], [0, 3])) # vL [j] vR [wC] i, [wC] wR j [j*] 162 | x = np.tensordot(x, self.RP, axes=([1, 3], [0, 1])) # vL [vR] i [wR] j, [vR*] [wR*] vR 163 | x = np.reshape(x, self.shape[0]) 164 | return x 165 | 166 | def _adjoint(self): 167 | """Define self as hermitian.""" 168 | return self 169 | 170 | def trace(self): 171 | """The trace of the operator. 172 | 173 | Only needed in e_tdvp.py in expm_multiply for scipy version > 1.9.0 to avoid warnings, 174 | but cheap to calculate anyways. 175 | """ 176 | return np.inner(np.trace(self.LP, axis1=0, axis2=2), # [vL] wL* [vL*] 177 | np.dot(np.trace(self.W1, axis1=2, axis2=3), # wL wC [i] [i*] 178 | np.dot(np.trace(self.W2, axis1=2, axis2=3), # wC wR [j] [j*] 179 | np.trace(self.RP, axis1=0, axis2=2)))) # [vR*] wR* [vR] 180 | 181 | 182 | def example_DMRG_tf_ising_finite(L, g, chi_max=20): 183 | print("finite DMRG, transverse field Ising") 184 | print("L={L:d}, g={g:.2f}".format(L=L, g=g)) 185 | from . import a_mps 186 | from . import b_model 187 | model = b_model.TFIModel(L=L, J=1., g=g, bc='finite') 188 | psi = a_mps.init_FM_MPS(model.L, model.d, model.bc) 189 | eng = SimpleDMRGEngine(psi, model, chi_max=chi_max, eps=1.e-10) 190 | for i in range(10): 191 | eng.sweep() 192 | E = np.sum(psi.bond_expectation_value(model.H_bonds)) 193 | print("sweep {i:2d}: E = {E:.13f}".format(i=i + 1, E=E)) 194 | print("final bond dimensions: ", psi.get_chi()) 195 | mag_x = np.sum(psi.site_expectation_value(model.sigmax)) 196 | mag_z = np.sum(psi.site_expectation_value(model.sigmaz)) 197 | print("magnetization in X = {mag_x:.5f}".format(mag_x=mag_x)) 198 | print("magnetization in Z = {mag_z:.5f}".format(mag_z=mag_z)) 199 | if L < 20: # compare to exact result 200 | from .tfi_exact import finite_gs_energy 201 | E_exact = finite_gs_energy(L, 1., g) 202 | print("Exact diagonalization: E = {E:.13f}".format(E=E_exact)) 203 | print("relative error: ", abs((E - E_exact) / E_exact)) 204 | return E, psi, model 205 | 206 | 207 | def example_DMRG_tf_ising_infinite(g, chi_max=30): 208 | print("infinite DMRG, transverse field Ising") 209 | print("g={g:.2f}".format(g=g)) 210 | from . import a_mps 211 | from . import b_model 212 | model = b_model.TFIModel(L=2, J=1., g=g, bc='infinite') 213 | psi = a_mps.init_FM_MPS(model.L, model.d, model.bc) 214 | eng = SimpleDMRGEngine(psi, model, chi_max=chi_max, eps=1.e-14) 215 | for i in range(20): 216 | eng.sweep() 217 | E = np.mean(psi.bond_expectation_value(model.H_bonds)) 218 | print("sweep {i:2d}: E (per site) = {E:.13f}".format(i=i + 1, E=E)) 219 | print("final bond dimensions: ", psi.get_chi()) 220 | mag_x = np.mean(psi.site_expectation_value(model.sigmax)) 221 | mag_z = np.mean(psi.site_expectation_value(model.sigmaz)) 222 | print(" = {mag_x:.5f}".format(mag_x=mag_x)) 223 | print(" = {mag_z:.5f}".format(mag_z=mag_z)) 224 | print("correlation length:", psi.correlation_length()) 225 | # compare to exact result 226 | from .tfi_exact import infinite_gs_energy 227 | E_exact = infinite_gs_energy(1., g) 228 | print("Analytic result: E (per site) = {E:.13f}".format(E=E_exact)) 229 | print("relative error: ", abs((E - E_exact) / E_exact)) 230 | return E, psi, model 231 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /exercise_tenpy.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "74eb0695", 6 | "metadata": {}, 7 | "source": [ 8 | "# Exercises similar to `exercise_toycodes.ipynb` using TeNPy" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "6c1d1329-b87c-46cc-9703-5aa6144aa292", 14 | "metadata": {}, 15 | "source": [ 16 | "Uncomment and run the cells below (removing the `#`) when running this notebook on https://colab.research.google.com.\n", 17 | "\n", 18 | "Alternatively, you can run this notebook locally with jupyter, provided that you have the `toycodes` subfolder from \n", 19 | "https://github.com/tenpy/tenpy_toycodes\n", 20 | "in the same folder as your notebook." 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "id": "cd54364b-4ac1-47bf-8c41-bd51b274eeec", 27 | "metadata": {}, 28 | "outputs": [], 29 | "source": [ 30 | "#!pip install git+https://github.com/tenpy/tenpy_toycodes.git\n", 31 | "#!pip install git+https://github.com/tenpy/tenpy.git\n", 32 | "\n", 33 | "# use `pip uninstall tenpy-toycodes physics-tenpy` to remove them again." 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "id": "e7d5c720", 39 | "metadata": {}, 40 | "source": [ 41 | "You can add your code below by inserting additional cells as neccessary and running them (press Shift+Enter).\n", 42 | "\n", 43 | "**DISCLAIMER**: Like for the toy codes, we only use very small bond dimensions here. For state-of-the-art MPS calculations (especially for cylinders towards 2D), `chi` should be significantly larger, often on the order of several 1000s." 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": null, 49 | "id": "b7d6aedd", 50 | "metadata": {}, 51 | "outputs": [], 52 | "source": [ 53 | "import numpy as np\n", 54 | "import scipy\n", 55 | "import matplotlib.pyplot as plt\n", 56 | "from pprint import pprint\n", 57 | "\n", 58 | "np.set_printoptions(precision=5, suppress=True, linewidth=100)\n", 59 | "plt.rcParams['figure.dpi'] = 150" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": null, 65 | "id": "2932ad48", 66 | "metadata": {}, 67 | "outputs": [], 68 | "source": [ 69 | "import tenpy\n", 70 | "import tenpy.linalg.np_conserved as npc\n", 71 | "from tenpy.algorithms import tebd, dmrg, tdvp\n", 72 | "from tenpy.networks.site import SpinHalfSite, SpinSite, FermionSite\n", 73 | "from tenpy.networks.mps import MPS\n", 74 | "from tenpy.models.tf_ising import TFIChain\n", 75 | "\n", 76 | "tenpy.tools.misc.setup_logging(to_stdout=\"INFO\")" 77 | ] 78 | }, 79 | { 80 | "cell_type": "markdown", 81 | "id": "9d536bdf", 82 | "metadata": {}, 83 | "source": [ 84 | "## Overview\n", 85 | "\n", 86 | "The source code of TeNPy is at https://github.com/tenpy/tenpy/; \n", 87 | "you can find links to the documentation and the forum in the Readme there.\n", 88 | "\n", 89 | "The [**documentation**](https://tenpy.readthedocs.io) is roughly split into the \"User guide\" (upper part in the left side-bar) and the reference of all the functions and classes (lower part).\n", 90 | "\n" 91 | ] 92 | }, 93 | { 94 | "cell_type": "markdown", 95 | "id": "6a01aec3", 96 | "metadata": {}, 97 | "source": [ 98 | "### Exercise(s)\n", 99 | "\n", 100 | "Read the [overview](https://tenpy.readthedocs.io/en/latest/intro/overview.html) of the TeNPy documentation.\n", 101 | "\n", 102 | "Whenever you hit an example code, try to copy it here and run it.\n", 103 | "\n", 104 | "Try to modify it slightly and try to rerun it; for example try to calculate the overlap `` in the first example." 105 | ] 106 | }, 107 | { 108 | "cell_type": "code", 109 | "execution_count": null, 110 | "id": "4f1b822f", 111 | "metadata": {}, 112 | "outputs": [], 113 | "source": [] 114 | }, 115 | { 116 | "cell_type": "markdown", 117 | "id": "337e0452", 118 | "metadata": {}, 119 | "source": [ 120 | "## Initializing a Model\n", 121 | "\n", 122 | "In TeNPy, the model defines the Hilbert space and local operators, and ultimately fixes whether charge conservation is used. Therefore, you should usually start with the initialization of the model.\n", 123 | "There are many predefined models in `tenpy.models`, that you can often just use.\n", 124 | "\n", 125 | "We will first initialize the transverse field Ising model. One advantage of TeNPy is that it can exploit (abelian) charge conservation for speedups, e.g. the transverse field Ising model preserves an overall spin parity. However, this requires the form \n", 126 | "$$ H = - J \\sum_{i} \\sigma^x_i \\sigma^x_{i+1} - g \\sum_{i} \\sigma^z_i \\textrm{ in TeNPy}$$\n", 127 | "compared to the form \n", 128 | "$$ H = - J \\sum_{i} \\sigma^z_i \\sigma^z_{i+1} - g \\sum_{i} \\sigma^x_i \\textrm{ (not suitable for charge conservation)}$$ you might be more familiar with, where X and Z are exchanged.\n", 129 | "\n", 130 | "In TeNPy, allmost all parmaters can be changed dynamically through options. Default parameters are written back into the dictionaries." 131 | ] 132 | }, 133 | { 134 | "cell_type": "code", 135 | "execution_count": null, 136 | "id": "a4f0ecc0", 137 | "metadata": {}, 138 | "outputs": [], 139 | "source": [ 140 | "model_params = {\n", 141 | " 'L': 20,\n", 142 | " 'g': 1.0,\n", 143 | " 'bc_MPS': 'finite',\n", 144 | " 'conserve': 'best'\n", 145 | "}\n", 146 | "model = TFIChain(model_params)\n", 147 | "# you can now print the default parameters used:\n", 148 | "print(\"used parameters, including default/not specified ones:\")\n", 149 | "pprint(model_params)" 150 | ] 151 | }, 152 | { 153 | "cell_type": "markdown", 154 | "id": "9fe62909", 155 | "metadata": {}, 156 | "source": [ 157 | "Given the model, one can easily initialize a product state, e.g. for the Neel state:" 158 | ] 159 | }, 160 | { 161 | "cell_type": "code", 162 | "execution_count": null, 163 | "id": "236024c3", 164 | "metadata": {}, 165 | "outputs": [], 166 | "source": [ 167 | "p_state = ['up', 'down'] * (model.lat.N_sites//2)\n", 168 | "psi = MPS.from_product_state(model.lat.mps_sites(), p_state, bc=model.lat.bc_MPS)\n" 169 | ] 170 | }, 171 | { 172 | "cell_type": "markdown", 173 | "id": "e3f4a8cf", 174 | "metadata": {}, 175 | "source": [ 176 | "Measuring expectation values is also similar to the toycode. However, we can even specify the local operators (defined in the sites) as strings:" 177 | ] 178 | }, 179 | { 180 | "cell_type": "code", 181 | "execution_count": null, 182 | "id": "adf6d283", 183 | "metadata": {}, 184 | "outputs": [], 185 | "source": [ 186 | "print(\" = \", psi.expectation_value('Sigmaz'))\n", 187 | "print(\"S = \", psi.entanglement_entropy())" 188 | ] 189 | }, 190 | { 191 | "cell_type": "markdown", 192 | "id": "017d4d67", 193 | "metadata": {}, 194 | "source": [ 195 | "### Exercise\n", 196 | "\n", 197 | "Check the [Model.bond_energies](https://tenpy.readthedocs.io/en/latest/reference/tenpy.models.model.NearestNeighborModel.html#tenpy.models.model.NearestNeighborModel.bond_energies) for the Neel state and make sure it matches what you expect.\n", 198 | "\n" 199 | ] 200 | }, 201 | { 202 | "cell_type": "code", 203 | "execution_count": null, 204 | "id": "d0ef22db", 205 | "metadata": {}, 206 | "outputs": [], 207 | "source": [ 208 | "E = model.bond_energies(psi)\n", 209 | "print(\"energy Neel:\", E)" 210 | ] 211 | }, 212 | { 213 | "cell_type": "code", 214 | "execution_count": null, 215 | "id": "99903e46", 216 | "metadata": {}, 217 | "outputs": [], 218 | "source": [] 219 | }, 220 | { 221 | "cell_type": "markdown", 222 | "id": "bd218561", 223 | "metadata": {}, 224 | "source": [ 225 | "## Running DMRG\n", 226 | "\n", 227 | "Given the model and state, running DMRG isn't hard.\n", 228 | "Again, there are many (default) parameters for fine-tuning, see [this full option list](https://tenpy.readthedocs.io/en/latest/reference/tenpy.algorithms.dmrg.TwoSiteDMRGEngine.html#cfg-config-TwoSiteDMRGEngine) for details." 229 | ] 230 | }, 231 | { 232 | "cell_type": "code", 233 | "execution_count": null, 234 | "id": "ffc67843", 235 | "metadata": {}, 236 | "outputs": [], 237 | "source": [ 238 | "p_state = ['up'] * model.lat.N_sites\n", 239 | "psi = MPS.from_product_state(model.lat.mps_sites(), p_state, bc=model.lat.bc_MPS, N_rings=model.lat.N_rings)\n", 240 | "algorithm_params = {\n", 241 | " 'trunc_params': {\n", 242 | " 'chi_max': 30,\n", 243 | " 'svd_min': 1.e-7,\n", 244 | " },\n", 245 | " 'max_sweeps': 40,\n", 246 | "}\n", 247 | "eng = dmrg.TwoSiteDMRGEngine(psi, model, algorithm_params)\n", 248 | "E, psi = eng.run()" 249 | ] 250 | }, 251 | { 252 | "cell_type": "code", 253 | "execution_count": null, 254 | "id": "3d9130aa", 255 | "metadata": {}, 256 | "outputs": [], 257 | "source": [] 258 | }, 259 | { 260 | "cell_type": "markdown", 261 | "id": "b0eee6bf", 262 | "metadata": {}, 263 | "source": [ 264 | "### Exercise\n", 265 | "\n", 266 | "Run DMRG for `'infinite'` MPS. \n", 267 | "(You need to initialize a new model, state, and DMRG engine for this.)\n" 268 | ] 269 | }, 270 | { 271 | "cell_type": "code", 272 | "execution_count": null, 273 | "id": "90538057", 274 | "metadata": {}, 275 | "outputs": [], 276 | "source": [ 277 | "from tenpy_toycodes import tfi_exact\n", 278 | "\n", 279 | "print(\"E_exact =\", tfi_exact.infinite_gs_energy(model_params['J'], model_params['g']))" 280 | ] 281 | }, 282 | { 283 | "cell_type": "code", 284 | "execution_count": null, 285 | "id": "a9503ce9", 286 | "metadata": {}, 287 | "outputs": [], 288 | "source": [] 289 | }, 290 | { 291 | "cell_type": "code", 292 | "execution_count": null, 293 | "id": "e0b86afc", 294 | "metadata": {}, 295 | "outputs": [], 296 | "source": [] 297 | }, 298 | { 299 | "cell_type": "markdown", 300 | "id": "fa17e5ea", 301 | "metadata": {}, 302 | "source": [ 303 | "### Exercise\n", 304 | "\n", 305 | "Reproduce the phase-diagram plot of the transverse field Ising model from the toy code noteboook with TeNPy.\n" 306 | ] 307 | }, 308 | { 309 | "cell_type": "code", 310 | "execution_count": null, 311 | "id": "1dfbc061", 312 | "metadata": {}, 313 | "outputs": [], 314 | "source": [] 315 | }, 316 | { 317 | "cell_type": "code", 318 | "execution_count": null, 319 | "id": "09ead35f-5675-492c-9e5d-014879d5ccf0", 320 | "metadata": {}, 321 | "outputs": [], 322 | "source": [] 323 | }, 324 | { 325 | "cell_type": "code", 326 | "execution_count": null, 327 | "id": "58e36f79-0d6a-4a48-bb9a-8eb7174eceb2", 328 | "metadata": {}, 329 | "outputs": [], 330 | "source": [] 331 | }, 332 | { 333 | "cell_type": "markdown", 334 | "id": "ad7f61d0", 335 | "metadata": {}, 336 | "source": [ 337 | "### Advanced exercises - if you're an expert and have time left ;-)\n", 338 | "\n", 339 | "These examples only scratch on the surface of what you can do with TeNPy.\n", 340 | "- There are plenty of [more examples](https://tenpy.readthedocs.io/en/latest/examples.html) in the documentation. Take a look at them!\n", 341 | "- Try to learn how to define your own model from the TeNPy documentation. Define a model for the XX Chain.\n", 342 | "- Look at the documentation how to run TEBD and TDVP and reproduce the time-evolution plot for S(t) from the toy code notebook.\n", 343 | "- Learn how to save and load data in TeNPy." 344 | ] 345 | }, 346 | { 347 | "cell_type": "code", 348 | "execution_count": null, 349 | "id": "ea502580", 350 | "metadata": {}, 351 | "outputs": [], 352 | "source": [] 353 | } 354 | ], 355 | "metadata": { 356 | "kernelspec": { 357 | "display_name": "Python 3 (ipykernel)", 358 | "language": "python", 359 | "name": "python3" 360 | }, 361 | "language_info": { 362 | "codemirror_mode": { 363 | "name": "ipython", 364 | "version": 3 365 | }, 366 | "file_extension": ".py", 367 | "mimetype": "text/x-python", 368 | "name": "python", 369 | "nbconvert_exporter": "python", 370 | "pygments_lexer": "ipython3", 371 | "version": "3.12.2" 372 | } 373 | }, 374 | "nbformat": 4, 375 | "nbformat_minor": 5 376 | } 377 | -------------------------------------------------------------------------------- /exercise_2_mps.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "2fe29049", 6 | "metadata": {}, 7 | "source": [ 8 | "# MPS and model basics\n", 9 | "\n", 10 | "In this notebook, we introduce the `SimpleMPS` class from `tenpy_toycodes/a_mps.py` \n", 11 | "and the model class from `tenpy_toycodes/b_model.py`." 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "id": "8c0a250c-dfd9-4377-bada-fb06f5e9f86c", 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "# standard imports and cosmetics\n", 22 | "\n", 23 | "import numpy as np\n", 24 | "\n", 25 | "import matplotlib.pyplot as plt\n", 26 | "\n", 27 | "np.set_printoptions(precision=5, suppress=True, linewidth=100, threshold=50)\n", 28 | "plt.rcParams['figure.dpi'] = 150" 29 | ] 30 | }, 31 | { 32 | "cell_type": "markdown", 33 | "id": "337e0452", 34 | "metadata": {}, 35 | "source": [ 36 | "## SimpleMPS class from `tenpy_toycodes/a_mps.py`\n", 37 | "\n", 38 | "The file `tenpy_toycodes/a_mps.py` defines a `SimpleMPS` class, that provides methods for expectation values and the entanglement entropy. \n", 39 | "\n", 40 | "You can initialize an inital product state MPS with the provided functions\n", 41 | "- `init_FM_MPS` to initialize the state $\\lvert \\uparrow \\uparrow \\cdots \\uparrow \\uparrow \\rangle$, and\n", 42 | "- `init_Neel_MPS` to initialize the Neel state $\\lvert \\uparrow \\downarrow \\cdots \\uparrow \\downarrow \\rangle$" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": null, 48 | "id": "03fb58d1-4bb9-464d-98e7-35c564d13e64", 49 | "metadata": {}, 50 | "outputs": [], 51 | "source": [ 52 | "import tenpy_toycodes.a_mps\n", 53 | "from tenpy_toycodes.a_mps import SimpleMPS, init_FM_MPS, init_Neel_MPS" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": null, 59 | "id": "1f2cd7eb-534e-422e-94d3-eb2abafba7ce", 60 | "metadata": {}, 61 | "outputs": [], 62 | "source": [ 63 | "psi_FM = init_FM_MPS(L=10)\n", 64 | "print(psi_FM)" 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": null, 70 | "id": "a4f0ecc0", 71 | "metadata": {}, 72 | "outputs": [], 73 | "source": [ 74 | "Z = np.diag([1., -1.])\n", 75 | "\n", 76 | "print(psi_FM.site_expectation_value(Z))" 77 | ] 78 | }, 79 | { 80 | "cell_type": "markdown", 81 | "id": "8b77ba98", 82 | "metadata": {}, 83 | "source": [ 84 | "### Exercise: expectation values and entropy\n", 85 | "\n", 86 | "- Initialize a Neel state MPS. Print the expectation values of `Z`\n" 87 | ] 88 | }, 89 | { 90 | "cell_type": "code", 91 | "execution_count": null, 92 | "id": "7905b006-9232-437a-a3aa-ba75eb6bb96a", 93 | "metadata": {}, 94 | "outputs": [], 95 | "source": [] 96 | }, 97 | { 98 | "cell_type": "markdown", 99 | "id": "540a2b68-6a99-4483-ac13-01762358ee01", 100 | "metadata": {}, 101 | "source": [ 102 | "- Print the entanglement entropy. What do you expect? Why do you get so many numbers, and not just one?\n", 103 | " *Tip*: read the code ;-)\n" 104 | ] 105 | }, 106 | { 107 | "cell_type": "code", 108 | "execution_count": null, 109 | "id": "82da0ad4-3fd6-4f08-9c70-73da0138bbe6", 110 | "metadata": {}, 111 | "outputs": [], 112 | "source": [] 113 | }, 114 | { 115 | "cell_type": "markdown", 116 | "id": "aef5ff1e-26f2-45e5-9dbb-4113723dfec5", 117 | "metadata": {}, 118 | "source": [ 119 | "- Extract the half-chain entanglement entropy, i.e., the entropy when cutting the chain into two equal-length halves.\n" 120 | ] 121 | }, 122 | { 123 | "cell_type": "code", 124 | "execution_count": null, 125 | "id": "4716bde6-4dfa-46f6-b983-65efb9bc9972", 126 | "metadata": {}, 127 | "outputs": [], 128 | "source": [] 129 | }, 130 | { 131 | "cell_type": "markdown", 132 | "id": "ca800c65-7141-469e-8b4a-fdba982bb78b", 133 | "metadata": {}, 134 | "source": [ 135 | "- Read the code of `a_mps.py` to find out how to get the correlation $\\langle \\psi| Z_1 Z_6 |\\psi \\rangle$. Try it out!" 136 | ] 137 | }, 138 | { 139 | "cell_type": "code", 140 | "execution_count": null, 141 | "id": "551155dd-0003-4d52-a162-ea9156cacff0", 142 | "metadata": {}, 143 | "outputs": [], 144 | "source": [] 145 | }, 146 | { 147 | "cell_type": "code", 148 | "execution_count": null, 149 | "id": "16498b4f-f0d7-4699-89a4-8f63f8f6b59a", 150 | "metadata": {}, 151 | "outputs": [], 152 | "source": [] 153 | }, 154 | { 155 | "cell_type": "markdown", 156 | "id": "041eb47b-53e0-4c6d-8eee-f73129a58580", 157 | "metadata": {}, 158 | "source": [ 159 | "### Exercise: `init_PM_MPS()`\n", 160 | "\n", 161 | "Write a function `init_PM_MPS` to initialize the state $\\lvert \\rightarrow \\rightarrow \\cdots \\rightarrow \\rightarrow \\rangle$,\n", 162 | "where $\\lvert \\rightarrow \\rangle = \\frac{1}{\\sqrt{2}} \\big( \\lvert\\uparrow \\rangle + \\lvert\\downarrow\\rangle \\big)$ is the spin-1/2 state pointing in plus x direction.\n", 163 | "\n", 164 | "*Tip*: the code should be similar to `init_FM_MPS`." 165 | ] 166 | }, 167 | { 168 | "cell_type": "code", 169 | "execution_count": null, 170 | "id": "5134600d-36b5-469c-b24d-30cde0948aa6", 171 | "metadata": {}, 172 | "outputs": [], 173 | "source": [] 174 | }, 175 | { 176 | "cell_type": "code", 177 | "execution_count": null, 178 | "id": "67325696-74e1-4b1b-98b1-43af1ef4e989", 179 | "metadata": {}, 180 | "outputs": [], 181 | "source": [] 182 | }, 183 | { 184 | "cell_type": "code", 185 | "execution_count": null, 186 | "id": "d4144def-6120-4a37-bbce-38f55489fa66", 187 | "metadata": {}, 188 | "outputs": [], 189 | "source": [] 190 | }, 191 | { 192 | "cell_type": "markdown", 193 | "id": "0966eb59", 194 | "metadata": {}, 195 | "source": [ 196 | "## Model class from `tenpy_toycodes/b_model.py`\n", 197 | "\n", 198 | "The file `tenpy_toycodes/b_model.py` defines a `TFIModel` class representing the transverse field Ising model \n", 199 | "$$H = - J \\sum_{i} Z_i Z_{i+1} - g \\sum_{i} X_i$$\n", 200 | "\n", 201 | "It provides the Hamiltonian both in the form of bond-terms `H_bonds` (as required for TEBD) and in the form of an MPO `H_mpo` (as required for DMRG).\n", 202 | "You can use `H_bonds` with `SimpleMPS.bond_expectation_values` to evalue the energy:\n" 203 | ] 204 | }, 205 | { 206 | "cell_type": "code", 207 | "execution_count": null, 208 | "id": "d439f112", 209 | "metadata": {}, 210 | "outputs": [], 211 | "source": [ 212 | "from tenpy_toycodes.b_model import TFIModel\n", 213 | "\n", 214 | "L = 10\n", 215 | "J = 1.\n", 216 | "g = 1.2\n", 217 | "model = TFIModel(L=L, J=J, g=g, bc='finite')\n", 218 | "\n", 219 | "print(\" = \", psi_FM.bond_expectation_value(model.H_bonds))\n", 220 | "print(\"energy:\", np.sum(psi_FM.bond_expectation_value(model.H_bonds)))\n", 221 | "# (make sure the model and state have the same length and boundary conditions!)\n", 222 | "\n", 223 | "print(\"should be\", (L-1)* (-J) * (1. * 1.) + L * (-g) * (0.) )" 224 | ] 225 | }, 226 | { 227 | "cell_type": "markdown", 228 | "id": "017d4d67", 229 | "metadata": {}, 230 | "source": [ 231 | "### Exercise\n", 232 | "\n", 233 | "- Find the code where the MPO `W` for this model is defined to be\n", 234 | "$W = \\begin{pmatrix} \\mathbb{1} & \\sigma^Z & -g \\sigma^X \\\\ & & -J \\sigma^Z \\\\ & & \\mathbb{1} \\end{pmatrix}$\n", 235 | "\n" 236 | ] 237 | }, 238 | { 239 | "cell_type": "code", 240 | "execution_count": null, 241 | "id": "916858f6-bdd6-4eee-b3ac-9ee8de19f5d4", 242 | "metadata": {}, 243 | "outputs": [], 244 | "source": [] 245 | }, 246 | { 247 | "cell_type": "markdown", 248 | "id": "1111dfbd-cbae-4f6d-9ead-70edc7ac1391", 249 | "metadata": {}, 250 | "source": [ 251 | "- Check the energies for the other initial states and make sure it matches what you expect." 252 | ] 253 | }, 254 | { 255 | "cell_type": "code", 256 | "execution_count": null, 257 | "id": "b3c564f8-ef0e-444d-a0fa-c64bd5cde49c", 258 | "metadata": {}, 259 | "outputs": [], 260 | "source": [] 261 | }, 262 | { 263 | "cell_type": "code", 264 | "execution_count": null, 265 | "id": "9b4fdc65-077e-4a1d-bea0-0e1a1eff44d6", 266 | "metadata": {}, 267 | "outputs": [], 268 | "source": [] 269 | }, 270 | { 271 | "cell_type": "code", 272 | "execution_count": null, 273 | "id": "0c91a163-8545-4478-b6af-ac26c617c62d", 274 | "metadata": {}, 275 | "outputs": [], 276 | "source": [] 277 | }, 278 | { 279 | "cell_type": "markdown", 280 | "id": "84bfd922-7e85-459c-a564-742d1ff36a72", 281 | "metadata": {}, 282 | "source": [ 283 | "### Exercises (optional, if time left - for the experts, and those who want to become them)\n", 284 | "\n", 285 | "- Write an optimized function `correlation_function_all_j(psi, op_i, i, op_j, max_j)` that returns\n", 286 | " the same values as the following, naive snippet:\n", 287 | " ```\n", 288 | " results = []\n", 289 | " for j in range(i+1, max_j):\n", 290 | " results.append(psi.correlation_function(op_i, i, op_j, j)\n", 291 | " return results\n", 292 | " ```\n", 293 | " This snippet is $\\mathcal{O}(L^2)$: for each `j` it calls the `correlation_function`, \n", 294 | " which internally also has an $\\mathcal{O}(L)$ loop for the contractions. \n", 295 | " You can get this down to a single $\\mathcal{O}(L)$ loop, if you identify and reuse the parts that are the same in the diagrams for the contractions.\n" 296 | ] 297 | }, 298 | { 299 | "cell_type": "code", 300 | "execution_count": null, 301 | "id": "8d0abb36-65cf-4537-b46a-b4634175ad1a", 302 | "metadata": {}, 303 | "outputs": [], 304 | "source": [] 305 | }, 306 | { 307 | "cell_type": "markdown", 308 | "id": "3967fdd5-ac38-4545-b22a-7888ba4e90e1", 309 | "metadata": {}, 310 | "source": [ 311 | "\n", 312 | "- For benchmarks, it's useful to consider the XX chain in a staggered field, given by the Hamiltonian\n", 313 | " $$ H = \\sum_{i=0}^{N-2} (\\sigma^x_i \\sigma^x_{i+1} + \\sigma^y_i \\sigma^y_{i+1}) - h_s \\sum_{i=0}^{N-1} (-1)^i \\sigma^z_i \n", 314 | " = 2 \\sum_{i=0}^{N-2} (\\sigma^+_i \\sigma^-_{i+1} + \\sigma^+_i \\sigma^-_{i+1}) - h_s \\sum_{i=0}^{N-1} (-1)^i \\sigma^z_i\n", 315 | " $$\n", 316 | " for the usual Pauli matrices $\\sigma^x, \\sigma^y, \\sigma^z$.\n", 317 | "\n", 318 | " A Jordan-Wigner transformation maps the XX Chain to free fermions, \n", 319 | " which we can diagonalize exactly with a few lines of python codes that are given in `tenpy_toycodes/free_fermions_exact.py`." 320 | ] 321 | }, 322 | { 323 | "cell_type": "code", 324 | "execution_count": null, 325 | "id": "1e3ba6c4-51ba-4b9b-99af-d243b75b7970", 326 | "metadata": {}, 327 | "outputs": [], 328 | "source": [ 329 | "from tenpy_toycodes.free_fermions_exact import XX_model_ground_state_energy\n", 330 | "\n", 331 | "print(\"E_exact = \", XX_model_ground_state_energy(L=10, h_staggered=0.))" 332 | ] 333 | }, 334 | { 335 | "cell_type": "markdown", 336 | "id": "8438c5a0-efd6-4b54-8268-a15b1b583bc0", 337 | "metadata": {}, 338 | "source": [ 339 | "The following code implements the model for the XX Chain hamiltonian, but the MPO lacks some terms. Fill them in!\n", 340 | "\n", 341 | "\n", 342 | "Tip: In Python, `(-1)**i` represents $(-1)^i$.\n", 343 | "\n", 344 | "Tip: For the Hamiltonian $$H = \\sum_{i=0}^{N-2} (\\sigma^x_i \\sigma^x_{i+1} + \\sigma^y_i \\sigma^y_{i+1} + \\sigma^z_i \\sigma^z_{i+1}) - h \\sum_{i=0}^{N-1} \\sigma^z_i, $$ a possible MPO matrix W looks like\n", 345 | "$$ W = \\begin{pmatrix}\n", 346 | "1 & \\sigma^x & \\sigma^y & \\sigma^z & -h \\sigma^z \\\\\n", 347 | "0 & 0 & 0 & 0 & \\sigma^x \\\\ \n", 348 | "0 & 0 & 0 & 0 & \\sigma^y \\\\\n", 349 | "0 & 0 & 0 & 0 & \\sigma^z \\\\\n", 350 | "0 & 0 & 0 & 0 & 1 \n", 351 | "\\end{pmatrix} .$$\n", 352 | "Which parts do we need here?\n", 353 | "\n", 354 | "Compare the energies of different states to check that you got it correct.\n", 355 | "To really test it well, you should also check some states with non-trivial entanglement, e.g. ground states as obtained by DMRG (obtained as discussed in the next exercise notebook), for which you can directly compare to the `XX_model_ground_state_energy`." 356 | ] 357 | }, 358 | { 359 | "cell_type": "code", 360 | "execution_count": null, 361 | "id": "08a58430-a335-4044-90c9-715e6279266b", 362 | "metadata": {}, 363 | "outputs": [], 364 | "source": [ 365 | "class XXChain:\n", 366 | " \"\"\"Simple class generating the Hamiltonian of the \n", 367 | " The Hamiltonian reads\n", 368 | " .. math ::\n", 369 | " H = - J \\\\sum_{i} \\\\sigma^x_i \\\\sigma^x_{i+1} - g \\\\sum_{i} \\\\sigma^z_i\n", 370 | " \"\"\"\n", 371 | " def __init__(self, L, hs, bc='finite'):\n", 372 | " assert bc in ['finite', 'infinite']\n", 373 | " self.L, self.d, self.bc = L, 2, bc\n", 374 | " self.hs = hs\n", 375 | " self.sigmax = np.array([[0., 1.], [1., 0.]]) # Pauli X\n", 376 | " self.sigmay = np.array([[0., -1j], [1j, 0.]]) # Pauli Y\n", 377 | " self.sigmaz = np.array([[1., 0.], [0., -1.]]) # Pauli Z\n", 378 | " self.id = np.eye(2)\n", 379 | " self.init_H_bonds()\n", 380 | " self.init_H_mpo()\n", 381 | "\n", 382 | " def init_H_bonds(self):\n", 383 | " \"\"\"Initialize `H_bonds` hamiltonian.\"\"\"\n", 384 | " sx, sy, sz, id = self.sigmax, self.sigmay, self.sigmaz, self.id\n", 385 | " d = self.d\n", 386 | " nbonds = self.L - 1 if self.bc == 'finite' else self.L\n", 387 | " H_list = []\n", 388 | " for i in range(nbonds):\n", 389 | " hL = hR = 0.5 * self.hs\n", 390 | " if self.bc == 'finite':\n", 391 | " if i == 0:\n", 392 | " hL = self.hs\n", 393 | " if i + 1 == self.L - 1:\n", 394 | " hR = self.hs\n", 395 | " H_bond = np.kron(sx, sx) + np.kron(sy, sy)\n", 396 | " H_bond = H_bond - hL * (-1)**i * np.kron(sz, id) - hR * (-1)**(i+1) * np.kron(id, sz)\n", 397 | " # H_bond has legs ``i, j, i*, j*``\n", 398 | " H_list.append(np.reshape(H_bond, [d, d, d, d]))\n", 399 | " self.H_bonds = H_list\n", 400 | "\n", 401 | " # (note: not required for TEBD)\n", 402 | " def init_H_mpo(self):\n", 403 | " \"\"\"Initialize `H_mpo` Hamiltonian.\"\"\"\n", 404 | " w_list = []\n", 405 | " for i in range(self.L):\n", 406 | " w = np.zeros((4, 4, self.d, self.d), dtype=complex)\n", 407 | " w[0, 0] = w[3, 3] = self.id\n", 408 | " \n", 409 | " raise NotImplementedError(\"add further entries here\")\n", 410 | " \n", 411 | " w_list.append(w)\n", 412 | " self.H_mpo = w_list\n", 413 | " \n", 414 | "#model = XXChain(9, 4., bc='finite')" 415 | ] 416 | }, 417 | { 418 | "cell_type": "code", 419 | "execution_count": null, 420 | "id": "5058175c-ac85-4bc9-971a-78919b3284a8", 421 | "metadata": {}, 422 | "outputs": [], 423 | "source": [] 424 | } 425 | ], 426 | "metadata": { 427 | "kernelspec": { 428 | "display_name": "Python 3 (ipykernel)", 429 | "language": "python", 430 | "name": "python3" 431 | }, 432 | "language_info": { 433 | "codemirror_mode": { 434 | "name": "ipython", 435 | "version": 3 436 | }, 437 | "file_extension": ".py", 438 | "mimetype": "text/x-python", 439 | "name": "python", 440 | "nbconvert_exporter": "python", 441 | "pygments_lexer": "ipython3", 442 | "version": "3.10.6" 443 | } 444 | }, 445 | "nbformat": 4, 446 | "nbformat_minor": 5 447 | } 448 | -------------------------------------------------------------------------------- /tenpy_toycodes/i_uexcitations.py: -------------------------------------------------------------------------------- 1 | """Toy code implementing variational plane waxe excitations on top of a uMPS ground state. 2 | 3 | This implementation closely follows Laurens Vanderstraeten, Jutho Haegeman and Frank Verstraete, 4 | Tangent-space methods for uniform matrix product states, SciPost Physics Lecture Notes 007, 2019, 5 | https://arxiv.org/abs/1810.07006. 6 | """ 7 | 8 | import numpy as np 9 | from scipy.sparse.linalg import LinearOperator, eigsh 10 | from scipy.linalg import null_space 11 | 12 | from tenpy_toycodes.f_umps import TransferMatrix 13 | from tenpy_toycodes.g_vumps import InverseGeometricSum, get_Lh, get_Rh, subtract_energy_offset 14 | 15 | 16 | class VariationalPlaneWaveExcitationEngine: 17 | """Simple class for variationally finding plane wave excitations on top of a uMPS ground state. 18 | 19 | ansatz: |phi(p,X; A)> = sum_n e^{i*p*n} ...--(AL)--(AL)--(VL)-(X)--(AR)--(AR)--..., 20 | | | | | | 21 | 22 | where p is the momentum, and X the left-gauge parametrization of the tensor 23 | 24 | --(B)-- = --(VL)-(X)--, 25 | | | 26 | 27 | perturbing the ground state |psi(A)> around site n. 28 | 29 | With = 2 * pi * delta(p-p') * , the variational 30 | optimization boils down to diagonalizing Heff(p) for a few lowest-lying eigenvalues. 31 | 32 | For a two-fold degnerate, symmetry-broken ground state, topological domain wall excitations can 33 | be targeted by taking AL and AR from the two orthogonal ground states. To fix the momentum 34 | unambiguously, we rescale AR with a phase, such that the mixed transfer matrix has a positive 35 | leading eigenvalue. 36 | 37 | Parameters 38 | ---------- 39 | psi0: UniformMPS 40 | The ground state on top of which the excited states are searched. 41 | h, p, k, tol: Same as attributes. 42 | psi0_tilde: UniformMPS or None 43 | If not None, second degnerate, symmetry-broken ground state. 44 | 45 | Attributes 46 | ---------- 47 | h: np.array[ndim=4] 48 | The two-site Hamiltonian of which the excitations are searched. 49 | p: float 50 | Momentum value between -pi and pi. 51 | k: int 52 | The number of excitations to be computed (only a few lowest-lying have physical meaning). 53 | tol: float 54 | The tolerance up to which geometric sum environments are computed with gmres. 55 | VL: np.array[ndim=3] 56 | Left-gauge tensor with legs vL p vvR of dimension D x d x D*(d-1). 57 | AL: np.array[ndim=3] 58 | Left orthonormal tensor of the ground state. 59 | Lh: np.array[ndim=2] 60 | Left environment computed from geometric sum of transfer matrix TL. 61 | AR: np.array[ndim=3] 62 | Right orthonormal tensor of the (second degenerate) ground state. 63 | Rh: np.array[ndim=2] 64 | Right environment computed from geometric sum of transfer matrix TR. 65 | IGS_RL: InverseGeometricSum 66 | Inverse geometric sum of the mixed transfer matrix of AR and AL. 67 | IGS_LR: InverseGeometricSum 68 | Inverse geometric sum of the mixed transfer matrix of AL and AR. 69 | """ 70 | def __init__(self, psi0, h, p, k, tol, psi0_tilde=None): 71 | self.h = subtract_energy_offset(psi0, h, canonical_form=True) 72 | self.p = p 73 | self.k = k 74 | self.tol = tol 75 | self.VL = self.get_VL(psi0.AL) 76 | self.AL = psi0.AL 77 | self.Lh = get_Lh(psi0, self.h, canonical_form=True, guess=None, tol=self.tol) 78 | if psi0_tilde is None: 79 | self.AR = psi0.AR 80 | self.Rh = get_Rh(psi0, self.h, canonical_form=True, guess=None, tol=self.tol) 81 | self.IGS_RL = InverseGeometricSum(psi0.AR, psi0.AL, R=np.conj(psi0.C).T, L=psi0.C.T, \ 82 | transpose=True, alpha=np.exp(-1.j * p), pseudo=True) 83 | self.IGS_LR = InverseGeometricSum(psi0.AL, psi0.AR, R=psi0.C, L=np.conj(psi0.C), \ 84 | transpose=False, alpha=np.exp(1.j * p), pseudo=True) 85 | else: 86 | AR = self.fix_momentum(psi0.AL, psi0_tilde.AR) 87 | psi0_tilde.AR = self.AR = AR 88 | self.Rh = get_Rh(psi0_tilde, self.h, canonical_form=True, guess=None, tol=self.tol) 89 | self.IGS_RL = InverseGeometricSum(psi0_tilde.AR, psi0.AL, R=None, L=None, \ 90 | transpose=True, alpha=np.exp(-1.j * p), pseudo=False) 91 | self.IGS_LR = InverseGeometricSum(psi0.AL, psi0_tilde.AR, R=None, L=None, \ 92 | transpose=False, alpha=np.exp(1.j * p), pseudo=False) 93 | 94 | def run(self): 95 | """For one momentum value self.p, compute self.k excitations.""" 96 | H_eff = Heff(self.h, self.p, self.VL, self.AL, self.AR, self.Lh, self.Rh, \ 97 | self.IGS_RL, self.IGS_LR, self.tol) 98 | es, Xs = eigsh(H_eff, k=self.k, which="SA") 99 | Xs_matrices = [] 100 | for i in range(self.k): 101 | X = Xs[:, i] 102 | Xs_matrices.append(np.reshape(X, H_eff.shape_X)) 103 | if self.k == 1: 104 | return es[0], Xs_matrices[0] 105 | return es, Xs_matrices 106 | 107 | @staticmethod 108 | def get_VL(AL): 109 | """For left orthonormal tensor AL, compute tensor VL of dimension D x d x D*(d-1), such that 110 | 111 | .--(VL)--vvR 112 | | | 113 | | | = 0 <-> vR*--(AL^{dagger})----(VL)--vvR = 0. 114 | | | | | 115 | .-(AL*)--vR* .-----------. 116 | 117 | Interpreting AL as the first D orthonormal columns of a (D*d)x(D*d) unitary matrix, 118 | VL corresponds to the remaining D*(d-1) columns thereof. 119 | """ 120 | D = np.shape(AL)[0] 121 | d = np.shape(AL)[1] 122 | AL = np.reshape(AL, (D * d, D)) # vL.p vR 123 | VL = null_space(np.conj(AL).T) # vL.p vvR 124 | VL = np.reshape(VL, (D, d, D * (d-1))) # vL p vvR 125 | return VL 126 | 127 | @staticmethod 128 | def fix_momentum(AL, AR): 129 | """Multiply AR with a phase such that TRL has a positive leading eigenvalue.""" 130 | lambda1, _ = TransferMatrix([AR], [AL]).get_leading_eigenpairs(k=1) 131 | AR *= np.conj(lambda1)/np.abs(lambda1) 132 | return AR 133 | 134 | 135 | class Heff(LinearOperator): 136 | """Class for the effective Hamiltonian acting on the parametrization X of the perturbation B. 137 | 138 | 139 | .---(B)--- .---(B)--- 140 | | | | | 141 | --(B)-- = --(VL)-(X)--, | | = 0, --(X)-- = | | 142 | | | | | | | 143 | .--(AL*)-- .--(VL*)-- 144 | 145 | 146 | ...---(AL)---(AL)---(B)---(AR)---(AR)---... 147 | | | | | | 148 | pm 149 | 150 | pn* p(n+1)* 151 | | | | | | 152 | matvec: --(B)-- -> sum_{n,m} e^{i*p*m} ... | | (----h----) | ... 153 | | | | | | | 154 | pn p(n+1) 155 | 156 | | | | | | 157 | ...--(AL*)--(AL*)-- --(AR*)--(AR*)---... 158 | 159 | """ 160 | def __init__(self, h, p, VL, AL, AR, Lh, Rh, IGS_RL, IGS_LR, tol): 161 | self.h = h 162 | self.p = p 163 | self.VL = VL 164 | self.AL = AL 165 | self.AR = AR 166 | self.Lh = Lh 167 | self.Rh = Rh 168 | self.IGS_RL = IGS_RL 169 | self.IGS_LR = IGS_LR 170 | self.tol = tol 171 | D = np.shape(self.AL)[0] 172 | d = np.shape(self.AL)[1] 173 | self.shape = (D * (d-1) * D, D * (d-1) * D) 174 | self.shape_X = (D * (d-1), D) 175 | self.dtype = self.AL.dtype 176 | 177 | def _matvec(self, X): 178 | """Perform the matvec multiplication diagrammatically shown above.""" 179 | X = np.reshape(X, self.shape_X) # vvL vR 180 | B = np.tensordot(self.VL, X, axes=(2, 0)) # vL p [vvR], [vvL] vR 181 | """m < 0""" 182 | "n < -1" 183 | lB = np.tensordot(self.AL, B, axes=(2, 0)) # vL p1 [vR], [vL] p2 vR 184 | lB = np.tensordot(self.h, lB, axes=((2, 3), (1, 2))) # p1 p2 [p1*] [p2*], vL [p1] [p2] vR 185 | lB = np.tensordot(np.conj(self.AL), lB, axes=((0, 1), (2, 0))) 186 | # [vL*] [p1*] vR*, [p1] p2 [vL] vR 187 | lB = np.tensordot(lB, np.conj(self.AL), axes=((0, 1), (0, 1))) 188 | # [vR*] [p2] vR, [vL*] [p2*] vR* 189 | LB = self.IGS_RL.multiply_geometric_sum(lB, guess=None, tol=self.tol) # vR vR* 190 | B_new = np.exp(-1.j * self.p) * np.tensordot(LB, self.AR, axes=(0, 0)) 191 | # [vR] vR*, [vL] p vR -> vL p vR 192 | lB = np.tensordot(B, self.AR, axes=(2, 0)) # vL p1 [vR], [vL] p2 vR 193 | lB = np.tensordot(self.h, lB, axes=((2, 3), (1, 2))) # p1 p2 [p1*] [p2*], vL [p1] [p2] vR 194 | lB = np.tensordot(np.conj(self.AL), lB, axes=((0, 1), (2, 0))) 195 | # [vL*] [p1*] vR*, [p1] p2 [vL] vR 196 | lB = np.tensordot(lB, np.conj(self.AL), axes=((0, 1), (0, 1))) 197 | # [vR*] [p2] vR, [vL*] [p2*] vR* 198 | LB = self.IGS_RL.multiply_geometric_sum(lB, guess=None, tol=self.tol) # vR vR* 199 | B_new += np.exp(-2.j * self.p) * np.tensordot(LB, self.AR, axes=(0, 0)) 200 | # [vR] vR*, [vL] p vR -> vL p vR 201 | lB = np.tensordot(B, self.Lh, axes=(0, 0)) # [vL] p vR, [vR] vR* 202 | lB = np.tensordot(lB, np.conj(self.AL), axes=((0, 2), (1, 0))) 203 | # [p] vR [vR*], [vL*] [p*] vR* 204 | LB = self.IGS_RL.multiply_geometric_sum(lB, guess=None, tol=self.tol) # vR vR* 205 | B_new += np.exp(-1.j * self.p) * np.tensordot(LB, self.AR, axes=(0, 0)) 206 | # [vR] vR*, [vL] p vR -> vL p vR 207 | "n = -1" 208 | b = np.tensordot(B, self.AR, axes=(2, 0)) # vL p1 [vR], [vL] p2 vR 209 | b = np.tensordot(self.h, b, axes=((2, 3), (1, 2))) # p1 p2 [p1*] [p2*], vL [p1] [p2] vR 210 | b = np.tensordot(np.conj(self.AL), b, axes=((0, 1), (2, 0))) 211 | # [vL*] [p1*] vR*, [p1] p2 [vL] vR 212 | B_new += np.exp(-1.j * self.p) * b # vL p vR 213 | "n = 0: no contribution in left gauge" 214 | "n > 0: no contribution in left gauge" 215 | """m = 0""" 216 | "n < -1" 217 | B_new += np.tensordot(self.Lh, B, axes=(0, 0)) # [vR] vR*, [vL] p vR -> vL p vR 218 | "n = -1" 219 | b = np.tensordot(self.AL, B, axes=(2, 0)) # vL p1 [vR], [vL] p2 vR 220 | b = np.tensordot(self.h, b, axes=((2, 3), (1, 2))) # p1 p2 [p1*] [p2*], vL [p1] [p2] vR 221 | b = np.tensordot(np.conj(self.AL), b, axes=((0, 1), (2, 0))) 222 | # [vL*] [p1*] vR*, [p1] p2 [vL] vR 223 | B_new += b # vL p vR 224 | "n = 0" 225 | b = np.tensordot(B, self.AR, axes=(2, 0)) # vL p1 [vR], [vL] p2 vR 226 | b = np.tensordot(self.h, b, axes=((2, 3), (1, 2))) # p1 p2 [p1*] [p2*], vL [p1] [p2] vR 227 | b = np.tensordot(b, np.conj(self.AR), axes=((1, 3), (1, 2))) 228 | # p1 [p2] vL [vR], vL* [p2*] [vR*] 229 | B_new += np.transpose(b, (1, 0, 2)) # vL p vR 230 | "n > 0" 231 | B_new += np.tensordot(B, self.Rh, axes=(2, 0)) # vL p [vR], [vL] vL* -> vL p vR 232 | """m > 0""" 233 | "n < -1" 234 | rB = np.tensordot(B, np.conj(self.AR), axes=((1, 2), (1, 2))) # vL [p] [vR], vL* [p*] [vR*] 235 | RB = self.IGS_LR.multiply_geometric_sum(rB, guess=None, tol=self.tol) # vL vL* 236 | b = np.tensordot(self.AL, RB, axes=(2, 0)) # vL p [vR], [vL] vL* 237 | b = np.tensordot(self.Lh, b, axes=(0, 0)) # [vR] vR*, [vL] p vL* 238 | B_new += np.exp(1.j * self.p) * b # vL p vR 239 | "n = -1" 240 | b = np.tensordot(self.AL, self.AL, axes=((2, 0))) # vL p1 [vR], [vL] p2 vR 241 | b = np.tensordot(self.h, b, axes=((2, 3), (1, 2))) # p1 p2 [p1*] [p2*], vL [p1] [p2] vR 242 | b = np.tensordot(np.conj(self.AL), b, axes=((0, 1), (2, 0))) 243 | # [vL*] [p1*] vR*, [p1] p2 [vL] vR 244 | B_new += np.exp(1.j * self.p) * np.tensordot(b, RB, axes=(2, 0)) 245 | # vR* p2 [vR], [vL] vL* -> vL p vR 246 | "n = 0" 247 | b = np.tensordot(self.AL, self.AL, axes=((2, 0))) # vL p1 [vR], [vL] p2 vR 248 | b = np.tensordot(self.h, b, axes=((2, 3), (1, 2))) # p1 p2 [p1*] [p2*], vL [p1] [p2] vR 249 | b = np.tensordot(b, RB, axes=(3, 0)) # p1 p2 vL [vR], [vL] vL* 250 | b = np.tensordot(b, np.conj(self.AR), axes=((1, 3), (1, 2))) 251 | # p1 [p2] vL [vL*], vL* [p2*] [vR*] 252 | B_new += np.exp(2.j * self.p) * np.transpose(b, (1, 0, 2)) # vL p vR 253 | b = np.tensordot(self.AL, B, axes=(2, 0)) # vL p1 [vR], [vL] p2 vR 254 | b = np.tensordot(self.h, b, axes=((2, 3), (1, 2))) # p1 p2 [p1*] [p2*], vL [p1] [p2] vR 255 | b = np.tensordot(b, np.conj(self.AR), axes=((1, 3), (1, 2))) 256 | # p1 [p2] vL [vR], vL* [p2*] [vR*] 257 | B_new += np.exp(1.j * self.p) * np.transpose(b, (1, 0, 2)) # vL p vR 258 | "n > 0" 259 | rb = np.tensordot(B, self.AR, axes=(2, 0)) # vL p1 [vR], [vL] p2 vR 260 | rb = np.tensordot(self.h, rb, axes=((2, 3), (1, 2))) # p1 p2 [p1*] [p2*], vL [p1] [p2] vR 261 | rb = np.tensordot(rb, np.conj(self.AR), axes=((1, 3), (1, 2))) 262 | # p1 [p2] vL [vR], vL* [p2*] [vR*] 263 | rb = np.tensordot(rb, np.conj(self.AR), axes=((0, 2), (1, 2))) 264 | # [p1] vL [vL*], vL* [p1*] [vR*] 265 | RB = self.IGS_LR.multiply_geometric_sum(rb, guess=None, tol=self.tol) # vL vL* 266 | B_new += np.exp(1.j * self.p) * np.tensordot(self.AL, RB, axes=(2, 0)) 267 | # vL p [vR], [vL] vL* -> vL p vR 268 | rb = np.tensordot(self.AL, B, axes=(2, 0)) # vL p1 [vR], [vL] p2 vR 269 | rb = np.tensordot(self.h, rb, axes=((2, 3), (1, 2))) # p1 p2 [p1*] [p2*], vL [p1] [p2] vR 270 | rb = np.tensordot(rb, np.conj(self.AR), axes=((1, 3), (1, 2))) 271 | # p1 [p2] vL [vR], vL* [p2*] [vR*] 272 | rb = np.tensordot(rb, np.conj(self.AR), axes=((0, 2), (1, 2))) 273 | # [p1] vL [vL*], vL* [p1*] [vR*] 274 | RB = self.IGS_LR.multiply_geometric_sum(rb, guess=None, tol=self.tol) # vL vL* 275 | B_new += np.exp(2.j * self.p) * np.tensordot(self.AL, RB, axes=(2, 0)) 276 | # vL p [vR], [vL] vL* -> vL p vR 277 | rB = np.tensordot(B, np.conj(self.AR), axes=((1, 2), (1, 2))) # vL [p] [vR], vL* [p*] [vR*] 278 | RB = self.IGS_LR.multiply_geometric_sum(rB, guess=None, tol=self.tol) # vL vL* 279 | rb = np.tensordot(self.AL, self.AL, axes=(2, 0)) # vL p1 [vR], [vL] p2 vR 280 | rb = np.tensordot(self.h, rb, axes=((2, 3), (1, 2))) # p1 p2 [p1*] [p2*], vL [p1] [p2] vR 281 | rb = np.tensordot(rb, RB, axes=(3, 0)) # p1 p2 vL [vR], [vL] vL* 282 | rb = np.tensordot(rb, np.conj(self.AR), axes=((1, 3), (1, 2))) 283 | # p1 [p2] vL [vL*], vL* [p2*] [vR*] 284 | rb = np.tensordot(rb, np.conj(self.AR), axes=((0, 2), (1, 2))) 285 | # [p1] vL [vL*], vL* [p1*] [vR*] 286 | RB = self.IGS_LR.multiply_geometric_sum(rb, guess=None, tol=self.tol) # vL vL* 287 | B_new += np.exp(3.j * self.p) * np.tensordot(self.AL, RB, axes=(2, 0)) 288 | # vL p [vR], [vL] vL* -> vL p vR 289 | rb = np.tensordot(B, self.Rh, axes=(2, 0)) # vL p [vR], [vL] vL* 290 | rb = np.tensordot(rb, np.conj(self.AR), axes=((1, 2), (1, 2))) 291 | # vL [p] [vL*], vL* [p*] [vR*] 292 | RB = self.IGS_LR.multiply_geometric_sum(rb, guess=None, tol=self.tol) # vL vL* 293 | B_new += np.exp(1.j * self.p) * np.tensordot(self.AL, RB, axes=(2, 0)) 294 | # vL p [vR], [vL] vL* -> vL p vR 295 | X_new = np.tensordot(np.conj(self.VL), B_new, axes=((0, 1), (0, 1))) 296 | # [vL*] [p*] vvR*, [vL] [p] vR 297 | X_new = np.reshape(X_new, self.shape[1]) # vvL.vR 298 | return X_new -------------------------------------------------------------------------------- /tenpy_toycodes/e_tdvp.py: -------------------------------------------------------------------------------- 1 | """Toy code implementing the TDVP for *finite* MPS.""" 2 | # Copyright (C) TeNPy Developers, Apache license 3 | 4 | import numpy as np 5 | import scipy.sparse.linalg 6 | from scipy.sparse.linalg import expm 7 | 8 | from .d_dmrg import SimpleHeff2 9 | from . import a_mps 10 | 11 | 12 | class SimpleTDVPEngine: 13 | """TDVP algorithm for finite systems, implemented as class holding the necessary data. 14 | 15 | Note that this class is very similar to `d_dmrg.SimpleDMRGEngine`. 16 | We could use a common base class; but to keep things maximally simple and readable, 17 | we rather duplicate the code for the `__init__`, `update_LP`, and `update_RP` methods. 18 | 19 | Also, here we generalize the sweep to temporarily change the MPS to a mixed canonical form 20 | and directly save `A` tensors in it. This means that the SimpleMPS methods (which *assume* 21 | that the tensors are all right-canonical) would give wrong results *during* the sweep; yet 22 | we recover the all-right-canonical B form on each site at the end of the sweep. 23 | 24 | Parameters 25 | ---------- 26 | psi, chi_max, eps: 27 | See attributes below. 28 | model : 29 | The model with the Hamiltonian for time evolution as `model.H_mpo`. 30 | 31 | Attributes 32 | ---------- 33 | psi : SimpleMPS 34 | The current state to be evolved. 35 | H_mpo : list of W tensors with legs ``wL wR i i*`` 36 | The Hamiltonian as an MPO. 37 | chi_max, eps: 38 | Truncation parameters, see :func:`a_mps.split_truncate_theta`. 39 | Only used when we evolve two-site wave functions! 40 | LPs, RPs : list of np.Array[ndim=3] 41 | Left and right parts ("environments") of the effective Hamiltonian. 42 | ``LPs[i]`` is the contraction of all parts left of site `i` in the network ````, 43 | and similar ``RPs[i]`` for all parts right of site `i`. 44 | Each ``LPs[i]`` has legs ``vL wL* vL*``, ``RPs[i]`` has legs ``vR* wR* vR`` 45 | """ 46 | def __init__(self, psi, model, chi_max, eps): 47 | assert psi.L == model.L and psi.bc == model.bc # ensure compatibility 48 | if psi.bc != 'finite': 49 | raise ValueError("This TDVP implementation works only for finite MPS.") 50 | self.H_mpo = model.H_mpo 51 | self.psi = psi 52 | self.LPs = [None] * psi.L 53 | self.RPs = [None] * psi.L 54 | self.chi_max = chi_max 55 | self.eps = eps 56 | # initialize left and right environment 57 | D = self.H_mpo[0].shape[0] 58 | chi = psi.Bs[0].shape[0] 59 | LP = np.zeros([chi, D, chi], dtype=float) # vL wL* vL* 60 | RP = np.zeros([chi, D, chi], dtype=float) # vR* wR* vR 61 | LP[:, 0, :] = np.eye(chi) 62 | RP[:, D - 1, :] = np.eye(chi) 63 | self.LPs[0] = LP 64 | self.RPs[-1] = RP 65 | # initialize necessary RPs 66 | for i in range(psi.L - 1, 0, -1): 67 | self.update_RP(i, psi.Bs[i]) 68 | 69 | def sweep_one_site(self, dt): 70 | """Perform one one-site TDVP sweep to evolve |psi> -> exp(-i H_mpo dt) |psi>. 71 | 72 | This does *not* grow the bond dimension of the MPS, but is strictly TDVP. 73 | """ 74 | psi = self.psi 75 | L = self.psi.L 76 | # sweep from left to right 77 | theta = self.psi.get_theta1(0) 78 | for i in range(L - 1): 79 | theta = self.evolve_one_site(i, 0.5*dt, theta) # forward 80 | Ai, theta = self.split_one_site_theta(i, theta, move_right=True) 81 | # here theta is zero-site between site i and i+1 82 | psi.Bs[i] = Ai # not in right canonical form, but expect this in right-to-left sweep 83 | self.update_LP(i, Ai) 84 | theta = self.evolve_zero_site(i, -0.5*dt, theta) # backward 85 | j = i + 1 86 | Bj = self.psi.Bs[j] 87 | theta = np.tensordot(theta, Bj, axes=(1, 0)) # vL [vL'], [vL] j vR 88 | # here theta is one-site on site j = i + 1 89 | # right boundary 90 | i = L - 1 91 | theta = self.evolve_one_site(i, dt, theta) # forward 92 | theta, Bi = self.split_one_site_theta(i, theta, move_right=False) 93 | self.psi.Bs[i] = Bi 94 | self.update_RP(i, Bi) 95 | # sweep from right to left 96 | for i in reversed(range(L - 1)): 97 | theta = self.evolve_zero_site(i, -0.5*dt, theta) # backward 98 | Ai = self.psi.Bs[i] # still in left-canonical A form from the above right-sweep! 99 | theta = np.tensordot(Ai, theta, axes=(2, 0)) # vL i [vR], [vR'] vR 100 | theta = self.evolve_one_site(i, 0.5*dt, theta) # forward 101 | theta, Bi = self.split_one_site_theta(i, theta, move_right=False) 102 | self.psi.Bs[i] = Bi 103 | self.update_RP(i, Bi) 104 | # The last `evolve_one_site` brought the tensor on site 0 in right-canonical B form, 105 | # recovering the right-canonical form on each MPS tensor (as the SimpleMPS assumes). 106 | # It splitted the very left, trivial leg off theta, 107 | # which should only have an arbitrary phase for the left, trivial singular vector, 108 | # and a singular value 1 (if the state is normalized). 109 | assert theta.shape == (1, 1) 110 | assert abs(abs(theta[0]) - 1.) < 1.e-10 111 | # To keep track of the phase, we put it back into the tensor. 112 | self.psi.Bs[0] *= theta[0, 0] 113 | 114 | def sweep_two_site(self, dt): 115 | """Perform one two-site TDVP sweep to evolve |psi> -> exp(-i H_mpo dt) |psi>. 116 | 117 | This can grow the bond dimension, but is *not* stricly TDVP. 118 | """ 119 | psi = self.psi 120 | L = self.psi.L 121 | # sweep from left to right 122 | theta = self.psi.get_theta2(0) 123 | for i in range(L - 2): 124 | j = i + 1 125 | k = i + 2 126 | Ai, S, Bj = self.evolve_split_two_site(i, 0.5*dt, theta) # forward 127 | psi.Bs[i] = Ai # not in right canonical form, but expect this in right-to-left sweep 128 | self.update_LP(i, Ai) 129 | theta = np.tensordot(np.diag(S), Bj, axes=(1, 0)) # vL [vL'], [vL] j vC 130 | # here theta is one-site on site j = i + 1 131 | theta = self.evolve_one_site(j, -0.5*dt, theta) # backward 132 | Bk = self.psi.Bs[k] 133 | theta = np.tensordot(theta, Bk, axes=(2, 0)) # vL j [vC], [vC] k vR 134 | # here theta is two-site on sites j, k = i + 1, i + 2 135 | # right boundary 136 | i = L - 2 137 | j = L - 1 138 | Ai, S, Bj = self.evolve_split_two_site(i, dt, theta) # forward 139 | theta = np.tensordot(Ai, np.diag(S), axes=(2, 0)) # vL i [vC], [vC'] vC 140 | self.psi.Bs[j] = Bj 141 | self.update_RP(j, Bj) 142 | # sweep from right to left 143 | for i in reversed(range(L - 2)): 144 | j = i + 1 145 | # here, theta is one-site on site j = i + 1 146 | theta = self.evolve_one_site(j, -0.5*dt, theta) # backward 147 | Ai = self.psi.Bs[i] # still in left-canonical A form from the above right-sweep! 148 | theta = np.tensordot(Ai, theta, axes=(2, 0)) # vL i [vR], [vR'] vR 149 | # here, theta is two-site on sites i, j = i, i + 1 150 | Ai, S, Bj = self.evolve_split_two_site(i, 0.5*dt, theta) # forward 151 | self.psi.Bs[j] = Bj 152 | self.update_RP(j, Bj) 153 | theta = np.tensordot(Ai, np.diag(S), axes=(2, 0)) # vL i vC, [vC'] vC 154 | self.psi.Bs[0] = theta # this is right-canonical, because for a finite system 155 | # the left-most virtual bond is trivial, so `theta` and `B` are the same on site 0. 156 | # So we recovered the right-canonical form on each MPS tensor (as the SimpleMPS assumes). 157 | 158 | def evolve_zero_site(self, i, dt, theta): 159 | """Evolve zero-site `theta` with SimpleHeff0 right of site `i`.""" 160 | Heff = SimpleHeff0(self.LPs[i + 1], self.RPs[i]) 161 | theta = np.reshape(theta, [Heff.shape[0]]) 162 | theta = self.expm_multiply(Heff, theta, dt) 163 | # no truncation necessary! 164 | return np.reshape(theta, Heff.theta_shape) 165 | 166 | def evolve_one_site(self, i, dt, theta): 167 | """Evolve one-site `theta` with SimpleHeff1 on site i.""" 168 | # get effective Hamiltonian 169 | Heff = SimpleHeff1(self.LPs[i], self.RPs[i], self.H_mpo[i]) 170 | theta = np.reshape(theta, [Heff.shape[0]]) 171 | theta = self.expm_multiply(Heff, theta, dt) 172 | # no truncation necessary! 173 | return np.reshape(theta, Heff.theta_shape) 174 | 175 | def evolve_split_two_site(self, i, dt, theta): 176 | """Evolve two-site `theta` with SimpleHeff2 on sites i and i + 1.""" 177 | j = i + 1 178 | # get effective Hamiltonian 179 | Heff = SimpleHeff2(self.LPs[i], self.RPs[j], self.H_mpo[i], self.H_mpo[j]) 180 | theta = np.reshape(theta, [Heff.shape[0]]) # group legs 181 | theta = self.expm_multiply(Heff, theta, dt) 182 | theta = np.reshape(theta, Heff.theta_shape) # split legs 183 | # truncation necessary! 184 | Ai, S, Bj = a_mps.split_truncate_theta(theta, self.chi_max, self.eps) 185 | self.psi.Ss[j] = S 186 | return Ai, S, Bj 187 | 188 | def split_one_site_theta(self, i, theta, move_right=True): 189 | """Split a one-site theta into `Ai, theta` (right move) or ``theta, Bi`` (left move).""" 190 | chivL, d, chivR = theta.shape 191 | if move_right: 192 | # group i to the left 193 | theta = np.reshape(theta, [chivL * d, chivR]) 194 | A, S, V = a_mps.svd(theta, full_matrices=False) # vL vC, vC, vC i vR 195 | S /= np.linalg.norm(S) 196 | self.psi.Ss[i + 1] = S 197 | chivC = len(S) # no truncation necessary! 198 | A = np.reshape(A, [chivL, d, chivC]) 199 | theta = np.tensordot(np.diag(S), V, axes=(1, 0)) # vC [vC'], [vC] vR 200 | return A, theta 201 | else: 202 | # group i to the right 203 | theta = np.reshape(theta, [chivL, d * chivR]) 204 | U, S, B = a_mps.svd(theta, full_matrices=False) # vL i vC, vC, vC vR 205 | S /= np.linalg.norm(S) 206 | self.psi.Ss[i] = S 207 | chivC = len(S) # no truncation necessary! 208 | B = np.reshape(B, [chivC, d, chivR]) 209 | theta = np.tensordot(U, np.diag(S), axes=(1, 0)) # vL [vC], [vC'] vC 210 | return theta, B 211 | 212 | def update_RP(self, i, B): 213 | """Calculate RP environment right of site `i-1`. 214 | 215 | Uses RP right of `i` and the given, right-canonical `B` on site `i`.""" 216 | j = (i - 1) % self.psi.L 217 | RP = self.RPs[i] # vR* wR* vR 218 | # B has legs vL i vR 219 | Bc = B.conj() # vL* i* vR* 220 | W = self.H_mpo[i] # wL wR i i* 221 | RP = np.tensordot(B, RP, axes=(2, 0)) # vL i [vR], [vR*] wR* vR 222 | RP = np.tensordot(RP, W, axes=([1, 2], [3, 1])) # vL [i] [wR*] vR, wL [wR] i [i*] 223 | RP = np.tensordot(RP, Bc, axes=([1, 3], [2, 1])) # vL [vR] wL [i], vL* [i*] [vR*] 224 | self.RPs[j] = RP # vL wL vL* (== vR* wR* vR on site i-1) 225 | 226 | def update_LP(self, i, A): 227 | """Calculate LP environment left of site `i+1`. 228 | 229 | Uses the LP left of site `i` and the given, left-canonical `A` on site `i`.""" 230 | j = (i + 1) % self.psi.L 231 | LP = self.LPs[i] # vL wL vL* 232 | # A has legs vL i vR 233 | Ac = A.conj() # vL* i* vR* 234 | W = self.H_mpo[i] # wL wR i i* 235 | LP = np.tensordot(LP, A, axes=(2, 0)) # vL wL* [vL*], [vL] i vR 236 | LP = np.tensordot(W, LP, axes=([0, 3], [1, 2])) # [wL] wR i [i*], vL [wL*] [i] vR 237 | LP = np.tensordot(Ac, LP, axes=([0, 1], [2, 1])) # [vL*] [i*] vR*, wR [i] [vL] vR 238 | self.LPs[j] = LP # vR* wR vR (== vL wL* vL* on site i+1) 239 | 240 | def expm_multiply(self, H, psi0, dt): 241 | from scipy.sparse.linalg import expm_multiply 242 | from packaging import version 243 | if version.parse(scipy.__version__) >= version.parse('1.9.0'): 244 | traceH = H.trace() # new argument introduced in scipy 1.9.0 245 | return expm_multiply((-1.j*dt) * H, psi0, traceA =1.j*dt*traceH) 246 | return expm_multiply((-1.j*dt) * H, psi0) 247 | # # alternatively, use custom lanczos implementation 248 | # from .lanczos import lanczos_expm_multiply 249 | # return lanczos_expm_multiply(H, psi0, dt) 250 | 251 | 252 | class SimpleHeff1(scipy.sparse.linalg.LinearOperator): 253 | """Class for the effective Hamiltonian on 1 site. 254 | 255 | Basically the same as d_dmrg.SimpleHeff2, but acts on a single site:: 256 | 257 | .--vL* vR*--. 258 | | i* | 259 | | | | 260 | (LP)----(W1)----(RP) 261 | | | | 262 | | i | 263 | .--vL vR--. 264 | """ 265 | def __init__(self, LP, RP, W1, prefactor=1.): 266 | self.LP = LP # vL wL* vL* 267 | self.RP = RP # vR* wR* vR 268 | self.W1 = W1 # wL wR i i* 269 | chi1, chi2 = LP.shape[0], RP.shape[2] 270 | d1 = W1.shape[2] 271 | self.theta_shape = (chi1, d1, chi2) # vL i vR 272 | self.shape = (chi1 * d1 * chi2, chi1 * d1 * chi2) 273 | self.dtype = W1.dtype 274 | 275 | def _matvec(self, theta): 276 | """Calculate |theta'> = H_eff |theta>.""" 277 | x = np.reshape(theta, self.theta_shape) # vL i vR 278 | x = np.tensordot(self.LP, x, axes=(2, 0)) # vL wL* [vL*], [vL] i vR 279 | x = np.tensordot(x, self.W1, axes=([1, 2], [0, 3])) # vL [wL*] [i] vR, [wL] wR i [i*] 280 | x = np.tensordot(x, self.RP, axes=([1, 2], [0, 1])) # vL [vR] [wR] i, [vR*] [wR*] vR 281 | x = np.reshape(x, self.shape[0]) 282 | return x 283 | 284 | def _adjoint(self): 285 | """Define self as hermitian.""" 286 | return self 287 | 288 | def trace(self): 289 | """The trace of the operator. 290 | 291 | Only needed for expm_multiply in scipy version > 1.9.0 to avoid warnings, 292 | but cheap to calculate anyways. 293 | """ 294 | return np.inner(np.trace(self.LP, axis1=0, axis2=2), # [vL] wL* [vL*] 295 | np.dot(np.trace(self.W1, axis1=2, axis2=3), # wL wR [i] [i*] 296 | np.trace(self.RP, axis1=0, axis2=2))) # [vR*] wR* [vR] 297 | 298 | 299 | class SimpleHeff0(scipy.sparse.linalg.LinearOperator): 300 | """Class for the effective Hamiltonian. 301 | 302 | Basically the same as d_dmrg.SimpleHeff1, but acts on the zero-site wave function:: 303 | 304 | .--vL* vR*--. 305 | | | 306 | | | 307 | (LP)----------(RP) 308 | | | 309 | | | 310 | .--vL vR--. 311 | """ 312 | def __init__(self, LP, RP, prefactor=1.): 313 | self.LP = LP # vL wL* vL* 314 | self.RP = RP # vR* wR* vR 315 | chi1, chi2 = LP.shape[0], RP.shape[2] 316 | self.theta_shape = (chi1, chi2) # vL vR 317 | self.shape = (chi1 * chi2, chi1 * chi2) 318 | self.dtype = LP.dtype 319 | 320 | def _matvec(self, theta): 321 | """Calculate |theta'> = H_eff |theta>.""" 322 | x = np.reshape(theta, self.theta_shape) # vL vR 323 | x = np.tensordot(self.LP, x, axes=(2, 0)) # vL wL* [vL*], [vL] vR 324 | x = np.tensordot(x, self.RP, axes=([1, 2], [1, 0])) # vL [wL*] [vL*] , [vR*] [wR*] vR 325 | x = np.reshape(x, self.shape[0]) 326 | return x 327 | 328 | def _adjoint(self): 329 | """Define self as hermitian.""" 330 | return self 331 | 332 | def trace(self): 333 | """The trace of the operator. 334 | 335 | Only needed for expm_multiply in scipy version > 1.9.0 to avoid warnings, 336 | but cheap to calculate anyways. 337 | """ 338 | return np.inner(np.trace(self.LP, axis1=0, axis2=2), # [vL] wL* [vL*] 339 | np.trace(self.RP, axis1=0, axis2=2)) # [vR*] wR* [vR] 340 | 341 | 342 | def example_TDVP_tf_ising_lightcone(L, g, tmax, dt, one_site=True, chi_max=50): 343 | # compare this code to c_tebd.example_TEBD_tf_ising_lightcone - it's almost the same. 344 | print("finite TEBD, real time evolution, transverse field Ising") 345 | print("L={L:d}, g={g:.2f}, tmax={tmax:.2f}, dt={dt:.3f}".format(L=L, g=g, tmax=tmax, dt=dt)) 346 | # find ground state with TEBD or DMRG 347 | # E, psi, model = example_TEBD_gs_tf_ising_finite(L, g) 348 | from .d_dmrg import example_DMRG_tf_ising_finite 349 | E, psi, model = example_DMRG_tf_ising_finite(L, g) 350 | i0 = L // 2 351 | # apply sigmax on site i0 352 | SxB = np.tensordot(model.sigmaz, psi.Bs[i0], axes=(1, 1)) # i [i*], vL [i] vR 353 | psi.Bs[i0] = np.transpose(SxB, [1, 0, 2]) # vL i vR 354 | E = np.sum(psi.bond_expectation_value(model.H_bonds)) 355 | print("E after applying Sz = {E:.13f}".format(E=E)) 356 | eng = SimpleTDVPEngine(psi, model, chi_max=chi_max, eps=1.e-7) 357 | S = [psi.entanglement_entropy()] 358 | Nsteps = int(tmax / dt + 0.5) 359 | for n in range(Nsteps): 360 | if abs((n * dt + 0.1) % 0.2 - 0.1) < 1.e-10: 361 | print("t = {t:.2f}, chi =".format(t=n * dt), psi.get_chi()) 362 | if one_site: 363 | eng.sweep_one_site(dt) 364 | else: 365 | eng.sweep_two_site(dt) 366 | S.append(psi.entanglement_entropy()) 367 | import matplotlib.pyplot as plt 368 | plt.figure() 369 | plt.imshow(S[::-1], 370 | vmin=0., 371 | aspect='auto', 372 | interpolation='nearest', 373 | extent=(0, L - 1., -0.5 * dt, (Nsteps + 0.5) * dt)) 374 | plt.xlabel('site $i$') 375 | plt.ylabel('time $t/J$') 376 | plt.ylim(0., tmax) 377 | plt.colorbar().set_label('entropy $S$') 378 | E = np.sum(psi.bond_expectation_value(model.H_bonds)) 379 | print("final E = {E:.13f}".format(E=E)) 380 | -------------------------------------------------------------------------------- /tenpy_toycodes/f_umps.py: -------------------------------------------------------------------------------- 1 | """Toy code implementing a uniform matrix product state (uMPS) in the thermodynamic limit. 2 | 3 | This implementation (and also g_vumps.py, h_utdvp.py, i_uexcitations.py) closely follow 4 | Laurens Vanderstraeten, Jutho Haegeman and Frank Verstraete, Tangent-space methods for uniform 5 | matrix product states, SciPost Physics Lecture Notes 007, 2019, https://arxiv.org/abs/1810.07006. 6 | """ 7 | 8 | import numpy as np 9 | from scipy.linalg import qr, svd 10 | from scipy.sparse.linalg import LinearOperator, eigs 11 | 12 | from .a_mps import SimpleMPS 13 | 14 | 15 | class UniformMPS: 16 | """Simple class for a uMPS with single site unit cell in the thermodynamic limit. 17 | 18 | Parameters 19 | ---------- 20 | AL, AR, AC, C: Same as attributes. 21 | 22 | Attributes 23 | ---------- 24 | AL: np.array[ndim=3] 25 | Left orthonormal tensor with legs vL p vR (virtualLeft physical virtualRight). 26 | Legs of respective complex conjugate tensor are denoted by vL* p* vR*. 27 | Contracted legs are put in square brackets. 28 | Allowed contractions: [p*][p], [vR][vL], [vR*][vL*], [vL][vL*], [vR][vR*]. 29 | AR: np.array[ndim=3] 30 | Right orthonormal tensor with legs vL p vR. 31 | AC: np.array[ndim=3] 32 | Center site tensor with legs vL p vR. 33 | Note that the canonical form AC = AL C = C AR is not fulfilled in VUMPS before convergence. 34 | C: np.array[ndim=2] 35 | Center matrix with legs vL vR. 36 | Note that C is not necessarily diagonal. 37 | Actively bring UniformMPS to diagonal C before computing entanglement entropy. 38 | D: int 39 | Bond dimension. 40 | d: int 41 | Physical dimension. 42 | """ 43 | def __init__(self, AL, AR, AC, C): 44 | self.AL = AL 45 | self.AR = AR 46 | self.AC = AC 47 | self.C = C 48 | self.D = np.shape(AL)[0] 49 | self.d = np.shape(AL)[1] 50 | 51 | @staticmethod 52 | def left_orthonormalize(A, tol=1.e-10, maxiter=10_000): 53 | """Left orthonormalize the tensor A by successive positive QR decompositions. 54 | 55 | --L(i)--A-- = --AL(i+1)--L(i+1)-- until convergence up to tolerance tol. 56 | | | 57 | """ 58 | D = np.shape(A)[0] 59 | d = np.shape(A)[1] 60 | L = np.random.normal(size=(D, D)) + 1.j*np.random.normal(size=(D, D)) 61 | for i in range(maxiter): 62 | L /= np.linalg.norm(L) # vL vR 63 | L_old = L 64 | L_A = np.tensordot(L, A, axes=(1, 0)) # vL [vR], [vL] p vR 65 | L_A = np.reshape(L_A, (D*d, D)) # vL.p vR 66 | AL, L = qr_positive(L_A) # vL.p vR, vL vR 67 | AL = np.reshape(AL, (D, d, D)) # vL p vR 68 | L /= np.linalg.norm(L) # vL vR 69 | err = np.linalg.norm(L - L_old) 70 | if err <= tol: 71 | print(f"AL, L: Converged up to tol={tol}. Final error after {i+1} iterations: {err}.") 72 | return AL, L 73 | T = TransferMatrix([A], [AL], transpose=True) # vL.vL* vR.vR* 74 | _, L = T.get_leading_eigenpairs(k=1, guess=L) # vR.vR* 75 | L = np.transpose(L, (1, 0)) # vR* vR 76 | _, L = qr_positive(L) # vL vR 77 | raise RuntimeError(f"AL, L: Did not converge up to tol={tol}. \ 78 | Final error after {maxiter} iterations: {err}.") 79 | 80 | @staticmethod 81 | def right_orthonormalize(A, tol=1.e-10, maxiter=10_000): 82 | """Right orthonormalize the tensor A by successive positive LQ decompositions. 83 | 84 | --A--R(i)-- = --R(i+1)--AR(i+1)-- until convergence up to tolerance tol. 85 | | | 86 | """ 87 | D = np.shape(A)[0] 88 | d = np.shape(A)[1] 89 | R = np.random.normal(size=(D, D)) + 1.j*np.random.normal(size=(D, D)) 90 | for i in range(maxiter): 91 | R /= np.linalg.norm(R) # vL vR 92 | R_old = R 93 | A_R = np.tensordot(A, R, axes=(2, 0)) # vL p [vR], [vL] vR 94 | A_R = np.reshape(A_R, (D, d*D)) # vL p.vR 95 | R, AR = lq_positive(A_R) # vL vR, vL p.vR 96 | AR = np.reshape(AR, (D, d, D)) # vL p vR 97 | R /= np.linalg.norm(R) # vL vR 98 | err = np.linalg.norm(R - R_old) 99 | if err <= tol: 100 | print(f"AR, R: Converged up to tol={tol}. Final error after {i+1} iterations: {err}.") 101 | return AR, R 102 | T = TransferMatrix([A], [AR]) # vL.vL* vR.vR* 103 | _, R = T.get_leading_eigenpairs(k=1, guess=R) # vL.vL* 104 | R, _ = lq_positive(R) # vL vR 105 | raise RuntimeError(f"AR, R: Did not converge up to tol={tol}. \ 106 | Final error after {maxiter} iterations: {err}.") 107 | 108 | @classmethod 109 | def to_canonical_form(cls, A, tol=1.e-10): 110 | """Bring tensor A to canonical form up to tolerance tol. 111 | 112 | 1) Left orthonormalize A to get AL, 113 | 2) Right orthonormalize AL to get AR and C, 114 | 3) To diagonal gauge via SVD. 115 | """ 116 | AL, _ = cls.left_orthonormalize(A, tol=tol) # vL p vR 117 | AR, C = cls.right_orthonormalize(AL, tol=tol) # vL p vR, vL vR 118 | U, S, V = svd(C) 119 | AL = np.tensordot(np.conj(U).T, np.tensordot(AL, U, axes=(2, 0)), axes=(1, 0)) 120 | # vL [vR], [vL] p [vR], [vL] vR 121 | AR = np.tensordot(V, np.tensordot(AR, np.conj(V).T, axes=(2, 0)), axes=(1, 0)) 122 | # vL [vR], [vL] p [vR], [vL] vR 123 | C = np.diag(S)/np.linalg.norm(S) # vL vR 124 | AC = np.tensordot(AL, C, axes=(2, 0)) # vL p [vR], [vL] vR 125 | return AL, AR, AC, C 126 | 127 | @classmethod 128 | def from_non_canonical_tensor(cls, A, tol=1.e-10): 129 | """Initialize UniformMPS instance from a (non-canonical) injective tensor A.""" 130 | AL, AR, AC, C = cls.to_canonical_form(A, tol=tol) 131 | return cls(AL, AR, AC, C) 132 | 133 | @staticmethod 134 | def get_random_tensor(D, d): 135 | """Create a random injective tensor of shape (D, d, D).""" 136 | A = np.random.normal(size=(D, d, D)) + 1.j * np.random.normal(size=(D, d, D)) # vL p vR 137 | # set largest eigenvalue of transfer matrix to 1 (normalize the corresponding MPS) 138 | T = TransferMatrix([A], [A]) 139 | lambda1, _ = T.get_leading_eigenpairs(k=1) 140 | A /= np.sqrt(np.abs(lambda1)) # vL p vR 141 | return A 142 | 143 | @classmethod 144 | def from_desired_bond_dimension(cls, D, d=2, tol=1.e-10): 145 | """Initialize UniformMPS instance from a random tensor of bond/physical dimension D/d.""" 146 | A = cls.get_random_tensor(D, d) 147 | AL, AR, AC, C = cls.to_canonical_form(A, tol=tol) 148 | return cls(AL, AR, AC, C) 149 | 150 | @classmethod 151 | def from_infinite_MPS(cls, psi): 152 | """Iff translation invariant, convert infinite MPS instance to UniformMPS instance. 153 | 154 | |psi(B1,...,BL)> = |psi(BL,B1,...,BL-1)> 155 | <-> T_{(B1,...,BL)(BL,B1,...,BL-1)}|U> = |U> with unitary U 156 | <-> |psi(B1,...,BL)> = |psi(B)> with single injective tensor B (choose B = U B_L) 157 | """ 158 | Bs = psi.Bs # [B1,...,BL] 159 | Bs_translated = Bs[-1:] + Bs[:-1] # [BL,B1,...,BL-1] 160 | T = TransferMatrix(Bs, Bs_translated) 161 | lambda1, U = T.get_leading_eigenpairs(k=1) 162 | if np.abs(np.abs(lambda1) - 1.) > 1.e-8: 163 | raise ValueError(f"Infinite MPS is not translation invariant.") 164 | else: 165 | print(f"Infinite MPS is translation invariant -> Conversion to uniform MPS.") 166 | B = np.tensordot(U, Bs[-1], axes=(1,0)) 167 | return cls.from_non_canonical_tensor(B) 168 | 169 | def to_infinite_MPS(self, L=1): 170 | """Convert UniformMPS instance to infinite MPS instance with L-site unit cell.""" 171 | self.to_diagonal_gauge() 172 | B = self.AR 173 | S = np.diag(self.C) 174 | return SimpleMPS(Bs=[B]*L, Ss=[S]*L, bc="infinite") 175 | 176 | def copy(self): 177 | """Create a copy of the UniformMPS instance.""" 178 | return UniformMPS(self.AL.copy(), self.AR.copy(), self.AC.copy(), self.C.copy()) 179 | 180 | def get_theta2(self): 181 | """Compute the effective two-site state theta2. 182 | 183 | --(theta2)-- = --(AL)--(AC)-- 184 | | | | | 185 | """ 186 | return np.tensordot(self.AL, self.AC, axes=(2, 0)) # vL p1 [vR], [vL] p2 vR 187 | 188 | def get_site_expectation_value(self, op1): 189 | """Compute the expectation value of a one-site operator op1. 190 | 191 | .--(theta1)--. .--(AC)--. 192 | | | | | | | 193 | e1 = | (op1) | = | (op1) | 194 | | | | | | | 195 | .--(theta1*)-. .--(AC*)-. 196 | """ 197 | assert np.shape(op1) == (self.d, self.d) 198 | theta1 = self.AC # vL p vR 199 | op1_theta1 = np.tensordot(op1, theta1, axes=(1, 1)) # p [p*], vL [p] vR 200 | theta1_op1_theta1 = np.tensordot(np.conj(theta1), op1_theta1, axes=((0, 1, 2), (1, 0, 2))) 201 | # [vL*] [p*] [vR*], [p] [vL] [vR] 202 | return np.real_if_close(theta1_op1_theta1) 203 | 204 | def get_bond_expectation_value(self, op2): 205 | """Compute the expectation value of a two-site operator op2. 206 | 207 | .--(theta2)--. 208 | | | | | 209 | e2 = | (op2) | 210 | | | | | 211 | .--(theta2*)-. 212 | """ 213 | assert np.shape(op2) == (self.d, self.d, self.d, self.d) 214 | theta2 = self.get_theta2() # vL p1 p2 vR 215 | op2_theta2 = np.tensordot(op2, theta2, axes=((2, 3), (1, 2))) 216 | # p1 p2 [p1*] [p2*], vL [p1] [p2] vR 217 | theta2_op2_theta2 = np.tensordot(np.conj(theta2), op2_theta2, axes=((0, 1, 2, 3), 218 | (2, 0, 1, 3))) 219 | # [vL*] [p1*] [p2*] [vR*], [p1] [p2] [vL] [vR] 220 | return np.real_if_close(theta2_op2_theta2) 221 | 222 | def test_canonical_form(self): 223 | """Test the canonical form of the UniformMPS instance. 224 | 225 | .--(AL)-- .-- --(AR)--. --. 226 | | | | | | | 227 | [1] | | = | , [2] | | = |, [3] --(AL)--(C)-- = --(AC)--, 228 | | | | | | | | | 229 | .-(AL*)-- .-- --(AR*)-. --. 230 | 231 | [4] --(C)--(AR)-- = --(AC)--. 232 | | | 233 | """ 234 | canonical_form = np.zeros(4) 235 | Id = np.eye(self.D) # vR vR* or vL vL* 236 | Id_L = np.tensordot(self.AL, np.conj(self.AL), axes=((0, 1), (0, 1))) 237 | # [vL] [p] vR, [vL*] [p*] vR* -> vR vR* 238 | Id_R = np.tensordot(self.AR, np.conj(self.AR), axes=((1, 2), (1, 2))) 239 | # vL [p] [vR], vL* [p*] [vR*] -> vL vL* 240 | canonical_form[0] = np.linalg.norm(Id_L - Id) 241 | canonical_form[1] = np.linalg.norm(Id_R - Id) 242 | AC = self.AC # vL p vR 243 | AC_L = np.tensordot(self.AL, self.C, axes=(2, 0)) # vL p [vR], [vL] vR -> vL p vR 244 | AC_R = np.tensordot(self.C, self.AR, axes=(1, 0)) # vL [vR], [vL] p vR -> vL p vR 245 | canonical_form[2] = np.linalg.norm(AC_L - AC) 246 | canonical_form[3] = np.linalg.norm(AC_R - AC) 247 | return canonical_form 248 | 249 | def to_diagonal_gauge(self): 250 | """Bring the UniformMPS instance to normalized diagonal gauge. 251 | 252 | Compute SVD C=USV and transform: --(AL)-- -> --(U^{dagger})--(AL)--(U)--, 253 | | | 254 | 255 | --(AR)-- -> --(V)--(AR)--(V^{dagger})--, 256 | | | 257 | 258 | --(C)-- -> 1/norm(S) --(S)--. 259 | """ 260 | U, S, V = svd(self.C) # vL vR 261 | self.AL = np.tensordot(np.conj(U).T, np.tensordot(self.AL, U, axes=(2, 0)), axes=(1, 0)) 262 | # vL [vR], [vL] p [vR], [vL] vR 263 | self.AR = np.tensordot(V, np.tensordot(self.AR, np.conj(V).T, axes=(2, 0)), axes=(1, 0)) 264 | # vL [vR], [vL] p [vR], [vL] vR 265 | self.C = np.diag(S)/np.linalg.norm(S) # vL vR 266 | self.AC = np.tensordot(self.AL, self.C, axes=(2, 0)) # vL p [vR], [vL] vR 267 | 268 | def get_entanglement_entropy(self): 269 | """Compute the entanglement entropy for a bipartition of the UniformMPS instance. 270 | 271 | S = -sum_{alpha=1}^D c_{alpha}^2 log(c_{alpha}^2) with c_{alpha} the singular values of C. 272 | """ 273 | _, C, _ = svd(self.C) 274 | C = C[C > 1.e-20] # 0*log(0) should give 0 and won't contribute to the sum 275 | # avoid warning or NaN by discarding the very small values of S 276 | assert abs(np.linalg.norm(C) - 1.) < 1.e-13 277 | C2 = C * C 278 | return -np.sum(C2 * np.log(C2)) 279 | 280 | def get_correlation_length(self): 281 | """Compute the correlation length by diagonalizing the transfer matrix. 282 | 283 | xi = -1/log(|lambda_2|), with |lambda_2| second largest eigenvalue magnitude. 284 | """ 285 | T = TransferMatrix([self.AL], [self.AL]) 286 | lambdas, _ = T.get_leading_eigenpairs(k=2) 287 | xi = -1./np.log(np.abs(lambdas[1])) 288 | return xi 289 | 290 | def get_correlation_functions(self, X, Y, N): 291 | """Compute the correlation functions C_XY(n) = for n = 1,...,N. 292 | 293 | .--(AC)-- --(AR)--^{n-1} --(AR)--. .-- --(AR)--^{n-1} --. 294 | | | | | | | | | 295 | C_XY(n) = | (X) | (Y) | = (LX) | (RY) 296 | | | | | | | | | 297 | .--(AC*)- --(AR*)- --(AR*)-. .-- --(AR*)- --. 298 | """ 299 | LX = np.tensordot(X, self.AC, axes=(1, 1)) # p [p*], vL [p] vR 300 | LX = np.tensordot(LX, np.conj(self.AC), axes=((1, 0), (0, 1))) 301 | # [p] [vL] vR, [vL*] [p*] vR* 302 | RY = np.tensordot(Y, self.AR, axes=(1, 1)) # p [p*], vL [p] vR 303 | RY = np.tensordot(RY, np.conj(self.AR), axes=((0, 2), (1, 2))) 304 | # [p] vL [vR], vL* [p*] [vR*] 305 | Cs = [] 306 | for n in range(N): 307 | C = np.tensordot(LX, RY, axes=((0, 1), (0, 1))) # [vR] [vR*], [vL] [vL*] 308 | Cs.append(C.item()) 309 | LX = np.tensordot(LX, self.AR, axes=(0, 0)) # [vR] vR*, [vL] p vR 310 | LX = np.tensordot(LX, np.conj(self.AR), axes=((0, 1), (0, 1))) 311 | # [vR*] [p] vR, [vL*] [p*] vR* 312 | return np.real_if_close(Cs) 313 | 314 | 315 | class TransferMatrix(LinearOperator): 316 | """Class for a transfer matrix. 317 | 318 | ---. ---(A1)-- ... --(AL)---. 319 | | | | | 320 | matvec for transpose=False: (X) -> | | (X) 321 | | | | | 322 | ---. --(B1*)- ... --(BL*)---. 323 | 324 | .--- .---(A1)-- ... --(AL)--- 325 | | | | | 326 | matvec for transpose=True: (X) -> (X) | | 327 | | | | | 328 | .--- .--(B1*)-- ... -(BL*)--- 329 | """ 330 | def __init__(self, As, Bs, transpose=False): 331 | self.As = As 332 | self.Bs = Bs 333 | self.transpose = transpose 334 | DA = np.shape(self.As[0])[0] 335 | DB = np.shape(self.Bs[0])[0] 336 | self.shape = (DA * DB, DA * DB) 337 | self.shape_X = (DA, DB) 338 | self.dtype = self.As[0].dtype 339 | 340 | def _matvec(self, X): 341 | X = np.reshape(X, self.shape_X) # vL vL* or vR vR* 342 | if not self.transpose: 343 | for A, B in zip(reversed(self.As), reversed(self.Bs)): 344 | X = np.tensordot(A, X, axes=(2, 0)) # vL p [vR], [vL] vL* 345 | X = np.tensordot(X, np.conj(B), axes=((1, 2), (1, 2))) 346 | # vL [p] [vL*], vL* [p*] [vR*] 347 | X = np.reshape(X, self.shape[1]) # vL.vL* 348 | return X 349 | else: 350 | for A, B in zip(self.As, self.Bs): 351 | X = np.tensordot(X, A, axes=(0, 0)) # [vR] vR*, [vL] p vR 352 | X = np.tensordot(X, np.conj(B), axes=((0, 1), (0, 1))) 353 | # [vR*] [p] vR, [vL*] [p*] vR* 354 | X = np.reshape(X, self.shape[0]) # vR.vR* 355 | return X 356 | 357 | def get_leading_eigenpairs(self, k=1, guess=None): 358 | """Compute the k largest eigenvalues (in magnitude) and the corresponding eigenvectors.""" 359 | if guess is not None: 360 | guess = np.reshape(guess, self.shape[1]) 361 | lambdas, Vs = eigs(self, k=k, which="LM", v0=guess) 362 | # sort the eigenvalues in decreasing order 363 | ind_sort = np.argsort(np.abs(lambdas))[::-1] 364 | lambdas = lambdas[ind_sort] 365 | Vs = Vs[:, ind_sort] # vL.vL* or vR.vR* 366 | Vs_matrices = [] 367 | for i in range(k): 368 | V = Vs[:, i] 369 | Vs_matrices.append(np.reshape(V, self.shape_X)) # vL vL* or vR vR* 370 | if k == 1: 371 | return lambdas[0], Vs_matrices[0] 372 | return lambdas, Vs_matrices 373 | 374 | 375 | def qr_positive(A): 376 | """Compute unique positive QR decomposition of MxN matrix A. 377 | 378 | A = QR with Q: MxK matrix fulfilling Q^{dagger}Q = 1 379 | R: KxN upper triangular matrix with positive diagonal elements [K=min(M,N)] 380 | """ 381 | Q, R = qr(A, mode="economic") 382 | diag_R = np.diag(R) 383 | P = np.diag(diag_R/np.abs(diag_R)) 384 | Q = np.matmul(Q, P) 385 | R = np.matmul(np.conj(P), R) 386 | return Q, R 387 | 388 | def lq_positive(A): 389 | """Compute unique positive LQ decomposition of MxN matrix A. 390 | 391 | A = LQ with Q: KxN matrix fulfilling QQ^{dagger} = 1 392 | L: MxK lower triangular matrix with positive diagonal elements [K=min(M,N)] 393 | """ 394 | Q, R = qr_positive(A.T) 395 | L = R.T 396 | Q = Q.T 397 | return L, Q -------------------------------------------------------------------------------- /solution_2_mps.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "2fe29049", 6 | "metadata": {}, 7 | "source": [ 8 | "# Solutions to MPS and model basics\n", 9 | "\n", 10 | "In this notebook, we introduce the `SimpleMPS` class from `tenpy_toycodes/a_mps.py` \n", 11 | "and the model class from `tenpy_toycodes/b_model.py`." 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 1, 17 | "id": "8c0a250c-dfd9-4377-bada-fb06f5e9f86c", 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "# standard imports and cosmetics\n", 22 | "\n", 23 | "import numpy as np\n", 24 | "\n", 25 | "import matplotlib.pyplot as plt\n", 26 | "\n", 27 | "np.set_printoptions(precision=5, suppress=True, linewidth=100, threshold=50)\n", 28 | "plt.rcParams['figure.dpi'] = 150" 29 | ] 30 | }, 31 | { 32 | "cell_type": "markdown", 33 | "id": "337e0452", 34 | "metadata": {}, 35 | "source": [ 36 | "## SimpleMPS class from `tenpy_toycodes/a_mps.py`\n", 37 | "\n", 38 | "The file `tenpy_toycodes/a_mps.py` defines a `SimpleMPS` class, that provides methods for expectation values and the entanglement entropy. \n", 39 | "\n", 40 | "You can initialize an inital product state MPS with the provided functions\n", 41 | "- `init_FM_MPS` to initialize the state $\\lvert \\uparrow \\uparrow \\cdots \\uparrow \\uparrow \\rangle$, and\n", 42 | "- `init_Neel_MPS` to initialize the Neel state $\\lvert \\uparrow \\downarrow \\cdots \\uparrow \\downarrow \\rangle$" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 2, 48 | "id": "03fb58d1-4bb9-464d-98e7-35c564d13e64", 49 | "metadata": {}, 50 | "outputs": [], 51 | "source": [ 52 | "import tenpy_toycodes.a_mps\n", 53 | "from tenpy_toycodes.a_mps import SimpleMPS, init_FM_MPS, init_Neel_MPS" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": 3, 59 | "id": "1f2cd7eb-534e-422e-94d3-eb2abafba7ce", 60 | "metadata": {}, 61 | "outputs": [ 62 | { 63 | "name": "stdout", 64 | "output_type": "stream", 65 | "text": [ 66 | "\n" 67 | ] 68 | } 69 | ], 70 | "source": [ 71 | "psi_FM = init_FM_MPS(L=10)\n", 72 | "print(psi_FM)" 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": 4, 78 | "id": "a4f0ecc0", 79 | "metadata": {}, 80 | "outputs": [ 81 | { 82 | "name": "stdout", 83 | "output_type": "stream", 84 | "text": [ 85 | "[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]\n" 86 | ] 87 | } 88 | ], 89 | "source": [ 90 | "Z = np.diag([1., -1.])\n", 91 | "\n", 92 | "print(psi_FM.site_expectation_value(Z))" 93 | ] 94 | }, 95 | { 96 | "cell_type": "markdown", 97 | "id": "8b77ba98", 98 | "metadata": {}, 99 | "source": [ 100 | "### Exercise: expectation values and entropy\n", 101 | "\n", 102 | "- Initialize a Neel state MPS. Print the expectation values of `Z`\n" 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": 5, 108 | "id": "7905b006-9232-437a-a3aa-ba75eb6bb96a", 109 | "metadata": {}, 110 | "outputs": [ 111 | { 112 | "name": "stdout", 113 | "output_type": "stream", 114 | "text": [ 115 | "[ 1. -1. 1. -1. 1. -1. 1. -1. 1. -1.]\n" 116 | ] 117 | } 118 | ], 119 | "source": [ 120 | "psi_Neel = init_Neel_MPS(L=10)\n", 121 | "print(psi_Neel.site_expectation_value(Z))" 122 | ] 123 | }, 124 | { 125 | "cell_type": "markdown", 126 | "id": "540a2b68-6a99-4483-ac13-01762358ee01", 127 | "metadata": {}, 128 | "source": [ 129 | "- Print the entanglement entropy. What do you expect? Why do you get so many numbers, and not just one?\n", 130 | " *Tip*: read the code ;-)\n" 131 | ] 132 | }, 133 | { 134 | "cell_type": "code", 135 | "execution_count": 6, 136 | "id": "82da0ad4-3fd6-4f08-9c70-73da0138bbe6", 137 | "metadata": {}, 138 | "outputs": [ 139 | { 140 | "name": "stdout", 141 | "output_type": "stream", 142 | "text": [ 143 | "[-0. -0. -0. -0. -0. -0. -0. -0. -0.]\n" 144 | ] 145 | } 146 | ], 147 | "source": [ 148 | "print(psi_Neel.entanglement_entropy()) \n", 149 | "# one value for cutting at each bond!" 150 | ] 151 | }, 152 | { 153 | "cell_type": "markdown", 154 | "id": "aef5ff1e-26f2-45e5-9dbb-4113723dfec5", 155 | "metadata": {}, 156 | "source": [ 157 | "- Extract the half-chain entanglement entropy, i.e., the entropy when cutting the chain into two equal-length halves.\n" 158 | ] 159 | }, 160 | { 161 | "cell_type": "code", 162 | "execution_count": 7, 163 | "id": "4716bde6-4dfa-46f6-b983-65efb9bc9972", 164 | "metadata": {}, 165 | "outputs": [ 166 | { 167 | "name": "stdout", 168 | "output_type": "stream", 169 | "text": [ 170 | "half-chain entropy: -0.0\n" 171 | ] 172 | } 173 | ], 174 | "source": [ 175 | "print(\"half-chain entropy: \", psi_Neel.entanglement_entropy()[(psi_Neel.L - 1)//2])\n", 176 | "# note: (L-1)//2, since we get L-1 values returned by entanglement_entropy()\n", 177 | "# a product state has no entanglement!" 178 | ] 179 | }, 180 | { 181 | "cell_type": "markdown", 182 | "id": "ca800c65-7141-469e-8b4a-fdba982bb78b", 183 | "metadata": {}, 184 | "source": [ 185 | "- Read the code of `a_mps.py` to find out how to get the correlation $\\langle \\psi| Z_1 Z_6 |\\psi \\rangle$. Try it out!" 186 | ] 187 | }, 188 | { 189 | "cell_type": "code", 190 | "execution_count": 8, 191 | "id": "551155dd-0003-4d52-a162-ea9156cacff0", 192 | "metadata": {}, 193 | "outputs": [ 194 | { 195 | "name": "stdout", 196 | "output_type": "stream", 197 | "text": [ 198 | " = -1.0\n" 199 | ] 200 | } 201 | ], 202 | "source": [ 203 | "print(\" = \", psi_Neel.correlation_function(Z, 1, Z, 6))" 204 | ] 205 | }, 206 | { 207 | "cell_type": "code", 208 | "execution_count": null, 209 | "id": "16498b4f-f0d7-4699-89a4-8f63f8f6b59a", 210 | "metadata": {}, 211 | "outputs": [], 212 | "source": [] 213 | }, 214 | { 215 | "cell_type": "markdown", 216 | "id": "041eb47b-53e0-4c6d-8eee-f73129a58580", 217 | "metadata": {}, 218 | "source": [ 219 | "### Exercise: `init_PM_MPS()`\n", 220 | "\n", 221 | "Write a function `init_PM_MPS` to initialize the state $\\lvert \\rightarrow \\rightarrow \\cdots \\rightarrow \\rightarrow \\rangle$,\n", 222 | "where $\\lvert \\rightarrow \\rangle = \\frac{1}{\\sqrt{2}} \\big( \\lvert\\uparrow \\rangle + \\lvert\\downarrow\\rangle \\big)$ is the spin-1/2 state pointing in plus x direction.\n", 223 | "\n", 224 | "*Tip*: the code should be similar to `init_FM_MPS`." 225 | ] 226 | }, 227 | { 228 | "cell_type": "code", 229 | "execution_count": 9, 230 | "id": "5134600d-36b5-469c-b24d-30cde0948aa6", 231 | "metadata": {}, 232 | "outputs": [], 233 | "source": [ 234 | "def init_PM_MPS(L, bc='finite'):\n", 235 | " \"\"\"Return a paramagnetic MPS (= product state with all spins pointing in +x direction)\"\"\"\n", 236 | " d = 2\n", 237 | " B = np.zeros([1, d, 1], dtype=float)\n", 238 | " B[0, 0, 0] = 1./np.sqrt(2)\n", 239 | " B[0, 1, 0] = 1./np.sqrt(2)\n", 240 | " S = np.ones([1], dtype=float)\n", 241 | " Bs = [B.copy() for i in range(L)]\n", 242 | " Ss = [S.copy() for i in range(L)]\n", 243 | " return SimpleMPS(Bs, Ss, bc=bc)" 244 | ] 245 | }, 246 | { 247 | "cell_type": "code", 248 | "execution_count": 10, 249 | "id": "67325696-74e1-4b1b-98b1-43af1ef4e989", 250 | "metadata": {}, 251 | "outputs": [], 252 | "source": [ 253 | "psi_PM = init_PM_MPS(L=10)" 254 | ] 255 | }, 256 | { 257 | "cell_type": "markdown", 258 | "id": "0966eb59", 259 | "metadata": {}, 260 | "source": [ 261 | "## Model class from `tenpy_toycodes/b_model.py`\n", 262 | "\n", 263 | "The file `tenpy_toycodes/b_model.py` defines a `TFIModel` class representing the transverse field Ising model \n", 264 | "$$H = - J \\sum_{i} Z_i Z_{i+1} - g \\sum_{i} X_i$$\n", 265 | "\n", 266 | "It provides the Hamiltonian both in the form of bond-terms `H_bonds` (as required for TEBD) and in the form of an MPO `H_mpo` (as required for DMRG).\n", 267 | "You can use `H_bonds` with `SimpleMPS.bond_expectation_values` to evalue the energy:\n" 268 | ] 269 | }, 270 | { 271 | "cell_type": "code", 272 | "execution_count": 11, 273 | "id": "d439f112", 274 | "metadata": {}, 275 | "outputs": [ 276 | { 277 | "name": "stdout", 278 | "output_type": "stream", 279 | "text": [ 280 | " = [-1. -1. -1. -1. -1. -1. -1. -1. -1.]\n", 281 | "energy: -9.0\n", 282 | "should be -9.0\n" 283 | ] 284 | } 285 | ], 286 | "source": [ 287 | "from tenpy_toycodes.b_model import TFIModel\n", 288 | "\n", 289 | "L = 10\n", 290 | "J = 1.\n", 291 | "g = 1.2\n", 292 | "model = TFIModel(L=L, J=J, g=g, bc='finite')\n", 293 | "\n", 294 | "print(\" = \", psi_FM.bond_expectation_value(model.H_bonds))\n", 295 | "print(\"energy:\", np.sum(psi_FM.bond_expectation_value(model.H_bonds)))\n", 296 | "# (make sure the model and state have the same length and boundary conditions!)\n", 297 | "\n", 298 | "print(\"should be\", (L-1)* (-J) * (1. * 1.) + L * (-g) * (0.) )" 299 | ] 300 | }, 301 | { 302 | "cell_type": "markdown", 303 | "id": "017d4d67", 304 | "metadata": {}, 305 | "source": [ 306 | "### Exercise\n", 307 | "\n", 308 | "- Find the code where the MPO `W` for this model is defined to be\n", 309 | "$W = \\begin{pmatrix} \\mathbb{1} & \\sigma^Z & -g \\sigma^X \\\\ & & -J \\sigma^Z \\\\ & & \\mathbb{1} \\end{pmatrix}$\n", 310 | "\n" 311 | ] 312 | }, 313 | { 314 | "cell_type": "code", 315 | "execution_count": 12, 316 | "id": "916858f6-bdd6-4eee-b3ac-9ee8de19f5d4", 317 | "metadata": {}, 318 | "outputs": [], 319 | "source": [ 320 | "# the code is in the method `init_H_mpo()` of the TFIModel class" 321 | ] 322 | }, 323 | { 324 | "cell_type": "markdown", 325 | "id": "1111dfbd-cbae-4f6d-9ead-70edc7ac1391", 326 | "metadata": {}, 327 | "source": [ 328 | "- Check the energies for the other initial states and make sure it matches what you expect." 329 | ] 330 | }, 331 | { 332 | "cell_type": "code", 333 | "execution_count": 13, 334 | "id": "b3c564f8-ef0e-444d-a0fa-c64bd5cde49c", 335 | "metadata": {}, 336 | "outputs": [ 337 | { 338 | "name": "stdout", 339 | "output_type": "stream", 340 | "text": [ 341 | "energy: 9.0\n", 342 | "should be 9.0\n" 343 | ] 344 | } 345 | ], 346 | "source": [ 347 | "psi_Neel = init_Neel_MPS(L=L)\n", 348 | "print(\"energy:\", np.sum(psi_Neel.bond_expectation_value(model.H_bonds)))\n", 349 | "print(\"should be\", (L-1)* (-J) * (1. * -1.) + L * (-g) * (0.) )" 350 | ] 351 | }, 352 | { 353 | "cell_type": "code", 354 | "execution_count": 14, 355 | "id": "9b4fdc65-077e-4a1d-bea0-0e1a1eff44d6", 356 | "metadata": {}, 357 | "outputs": [ 358 | { 359 | "name": "stdout", 360 | "output_type": "stream", 361 | "text": [ 362 | "energy: -11.999999999999993\n", 363 | "should be -12.0\n" 364 | ] 365 | } 366 | ], 367 | "source": [ 368 | "psi_PM = init_PM_MPS(L=L)\n", 369 | "print(\"energy:\", np.sum(psi_PM.bond_expectation_value(model.H_bonds)))\n", 370 | "print(\"should be\", (L-1)* (-J) * (0. * 0.) + L * (-g) * (1.) )" 371 | ] 372 | }, 373 | { 374 | "cell_type": "code", 375 | "execution_count": null, 376 | "id": "0c91a163-8545-4478-b6af-ac26c617c62d", 377 | "metadata": {}, 378 | "outputs": [], 379 | "source": [] 380 | }, 381 | { 382 | "cell_type": "markdown", 383 | "id": "84bfd922-7e85-459c-a564-742d1ff36a72", 384 | "metadata": {}, 385 | "source": [ 386 | "### Exercises (optional, if time left - for the experts, and those who want to become them)\n", 387 | "\n", 388 | "- Write an optimized function `correlation_function_all_j(psi, op_i, i, op_j, max_j)` that returns\n", 389 | " the same values as the following, naive snippet:\n", 390 | " ```\n", 391 | " results = []\n", 392 | " for j in range(i+1, max_j):\n", 393 | " results.append(psi.correlation_function(op_i, i, op_j, j)\n", 394 | " return results\n", 395 | " ```\n", 396 | " This snippet is $\\mathcal{O}(L^2)$: for each `j` it calls the `correlation_function`, \n", 397 | " which internally also has an $\\mathcal{O}(L)$ loop for the contractions. \n", 398 | " You can get this down to a single $\\mathcal{O}(L)$ loop, if you identify and reuse the parts that are the same in the diagrams for the contractions.\n" 399 | ] 400 | }, 401 | { 402 | "cell_type": "code", 403 | "execution_count": null, 404 | "id": "8d0abb36-65cf-4537-b46a-b4634175ad1a", 405 | "metadata": {}, 406 | "outputs": [], 407 | "source": [] 408 | }, 409 | { 410 | "cell_type": "markdown", 411 | "id": "3967fdd5-ac38-4545-b22a-7888ba4e90e1", 412 | "metadata": {}, 413 | "source": [ 414 | "\n", 415 | "- For benchmarks, it's useful to consider the XX chain in a staggered field, given by the Hamiltonian\n", 416 | " $$ H = \\sum_{i=0}^{N-2} (\\sigma^x_i \\sigma^x_{i+1} + \\sigma^y_i \\sigma^y_{i+1}) - h_s \\sum_{i=0}^{N-1} (-1)^i \\sigma^z_i \n", 417 | " = 2 \\sum_{i=0}^{N-2} (\\sigma^+_i \\sigma^-_{i+1} + \\sigma^+_i \\sigma^-_{i+1}) - h_s \\sum_{i=0}^{N-1} (-1)^i \\sigma^z_i\n", 418 | " $$\n", 419 | " for the usual Pauli matrices $\\sigma^x, \\sigma^y, \\sigma^z$.\n", 420 | "\n", 421 | " A Jordan-Wigner transformation maps the XX Chain to free fermions, \n", 422 | " which we can diagonalize exactly with a few lines of python codes that are given in `tenpy_toycodes/free_fermions_exact.py`." 423 | ] 424 | }, 425 | { 426 | "cell_type": "code", 427 | "execution_count": 15, 428 | "id": "1e3ba6c4-51ba-4b9b-99af-d243b75b7970", 429 | "metadata": {}, 430 | "outputs": [ 431 | { 432 | "name": "stdout", 433 | "output_type": "stream", 434 | "text": [ 435 | "E_exact = -12.053348366664542\n" 436 | ] 437 | } 438 | ], 439 | "source": [ 440 | "from tenpy_toycodes.free_fermions_exact import XX_model_ground_state_energy\n", 441 | "\n", 442 | "print(\"E_exact = \", XX_model_ground_state_energy(L=10, h_staggered=0.))" 443 | ] 444 | }, 445 | { 446 | "cell_type": "markdown", 447 | "id": "8438c5a0-efd6-4b54-8268-a15b1b583bc0", 448 | "metadata": {}, 449 | "source": [ 450 | "The following code implements the model for the XX Chain hamiltonian, but the MPO lacks some terms. Fill them in!\n", 451 | "\n", 452 | "\n", 453 | "Tip: In Python, `(-1)**i` represents $(-1)^i$.\n", 454 | "\n", 455 | "Tip: For the Hamiltonian $$H = \\sum_{i=0}^{N-2} (\\sigma^x_i \\sigma^x_{i+1} + \\sigma^y_i \\sigma^y_{i+1} + \\sigma^z_i \\sigma^z_{i+1}) - h \\sum_{i=0}^{N-1} \\sigma^z_i, $$ a possible MPO matrix W looks like\n", 456 | "$$ W = \\begin{pmatrix}\n", 457 | "1 & \\sigma^x & \\sigma^y & \\sigma^z & -h \\sigma^z \\\\\n", 458 | "0 & 0 & 0 & 0 & \\sigma^x \\\\ \n", 459 | "0 & 0 & 0 & 0 & \\sigma^y \\\\\n", 460 | "0 & 0 & 0 & 0 & \\sigma^z \\\\\n", 461 | "0 & 0 & 0 & 0 & 1 \n", 462 | "\\end{pmatrix} .$$\n", 463 | "Which parts do we need here?\n", 464 | "\n", 465 | "Compare the energies of different states to check that you got it correct.\n", 466 | "To really test it well, you should also check some states with non-trivial entanglement, e.g. ground states as obtained by DMRG (obtained as discussed in the next exercise notebook), for which you can directly compare to the `XX_model_ground_state_energy`." 467 | ] 468 | }, 469 | { 470 | "cell_type": "code", 471 | "execution_count": 16, 472 | "id": "08a58430-a335-4044-90c9-715e6279266b", 473 | "metadata": {}, 474 | "outputs": [], 475 | "source": [ 476 | "class XXChain:\n", 477 | " \"\"\"Simple class generating the Hamiltonian of the \n", 478 | " The Hamiltonian reads\n", 479 | " .. math ::\n", 480 | " H = - J \\\\sum_{i} \\\\sigma^x_i \\\\sigma^x_{i+1} - g \\\\sum_{i} \\\\sigma^z_i\n", 481 | " \"\"\"\n", 482 | " def __init__(self, L, hs, bc='finite'):\n", 483 | " assert bc in ['finite', 'infinite']\n", 484 | " self.L, self.d, self.bc = L, 2, bc\n", 485 | " self.hs = hs\n", 486 | " self.sigmax = np.array([[0., 1.], [1., 0.]]) # Pauli X\n", 487 | " self.sigmay = np.array([[0., -1j], [1j, 0.]]) # Pauli Y\n", 488 | " self.sigmaz = np.array([[1., 0.], [0., -1.]]) # Pauli Z\n", 489 | " self.id = np.eye(2)\n", 490 | " self.init_H_bonds()\n", 491 | " self.init_H_mpo()\n", 492 | "\n", 493 | " def init_H_bonds(self):\n", 494 | " \"\"\"Initialize `H_bonds` hamiltonian.\"\"\"\n", 495 | " sx, sy, sz, id = self.sigmax, self.sigmay, self.sigmaz, self.id\n", 496 | " d = self.d\n", 497 | " nbonds = self.L - 1 if self.bc == 'finite' else self.L\n", 498 | " H_list = []\n", 499 | " for i in range(nbonds):\n", 500 | " hL = hR = 0.5 * self.hs\n", 501 | " if self.bc == 'finite':\n", 502 | " if i == 0:\n", 503 | " hL = self.hs\n", 504 | " if i + 1 == self.L - 1:\n", 505 | " hR = self.hs\n", 506 | " H_bond = np.kron(sx, sx) + np.kron(sy, sy)\n", 507 | " H_bond = H_bond - hL * (-1)**i * np.kron(sz, id) - hR * (-1)**(i+1) * np.kron(id, sz)\n", 508 | " # H_bond has legs ``i, j, i*, j*``\n", 509 | " H_list.append(np.reshape(H_bond, [d, d, d, d]))\n", 510 | " self.H_bonds = H_list\n", 511 | "\n", 512 | " # (note: not required for TEBD)\n", 513 | " def init_H_mpo(self):\n", 514 | " \"\"\"Initialize `H_mpo` Hamiltonian.\"\"\"\n", 515 | " w_list = []\n", 516 | " for i in range(self.L):\n", 517 | " w = np.zeros((4, 4, self.d, self.d), dtype=complex)\n", 518 | " w[0, 0] = w[3, 3] = self.id\n", 519 | " \n", 520 | " raise NotImplementedError(\"add further entries here\")\n", 521 | " \n", 522 | " w_list.append(w)\n", 523 | " self.H_mpo = w_list\n", 524 | " \n", 525 | "#model = XXChain(9, 4., bc='finite')" 526 | ] 527 | }, 528 | { 529 | "cell_type": "code", 530 | "execution_count": null, 531 | "id": "5058175c-ac85-4bc9-971a-78919b3284a8", 532 | "metadata": {}, 533 | "outputs": [], 534 | "source": [] 535 | }, 536 | { 537 | "cell_type": "code", 538 | "execution_count": null, 539 | "id": "7062537b-097b-40f8-ad43-b079660c932b", 540 | "metadata": {}, 541 | "outputs": [], 542 | "source": [] 543 | } 544 | ], 545 | "metadata": { 546 | "kernelspec": { 547 | "display_name": "Python 3 (ipykernel)", 548 | "language": "python", 549 | "name": "python3" 550 | }, 551 | "language_info": { 552 | "codemirror_mode": { 553 | "name": "ipython", 554 | "version": 3 555 | }, 556 | "file_extension": ".py", 557 | "mimetype": "text/x-python", 558 | "name": "python", 559 | "nbconvert_exporter": "python", 560 | "pygments_lexer": "ipython3", 561 | "version": "3.10.6" 562 | } 563 | }, 564 | "nbformat": 4, 565 | "nbformat_minor": 5 566 | } 567 | -------------------------------------------------------------------------------- /exercise_3_dmrg.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "2fe29049", 6 | "metadata": {}, 7 | "source": [ 8 | "# DMRG runs\n", 9 | "\n", 10 | "In this notebook, we use the `SimpleDMRGEngine` class from `tenpy_toycodes/d_dmrg.py` to run DMRG and find MPS ground states." 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "id": "56956a97", 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "# standard imports and cosmetics\n", 21 | "\n", 22 | "import numpy as np\n", 23 | "\n", 24 | "import matplotlib.pyplot as plt\n", 25 | "\n", 26 | "np.set_printoptions(precision=5, suppress=True, linewidth=100, threshold=50)\n", 27 | "plt.rcParams['figure.dpi'] = 150" 28 | ] 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "id": "238d735d-7666-4181-bf50-481d8cc61adb", 33 | "metadata": {}, 34 | "source": [ 35 | "In previous notebooks, we learned how to initialize `SimpleMPS`..." 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": null, 41 | "id": "8b2cfaee", 42 | "metadata": {}, 43 | "outputs": [], 44 | "source": [ 45 | "from tenpy_toycodes.a_mps import SimpleMPS, init_FM_MPS, init_Neel_MPS" 46 | ] 47 | }, 48 | { 49 | "cell_type": "code", 50 | "execution_count": null, 51 | "id": "fd3a1a46-378d-4041-86ec-4ae992de0a7e", 52 | "metadata": {}, 53 | "outputs": [], 54 | "source": [ 55 | "L = 12" 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": null, 61 | "id": "a4f0ecc0", 62 | "metadata": {}, 63 | "outputs": [], 64 | "source": [ 65 | "psi_FM = init_FM_MPS(L=L, d=2, bc='finite')\n", 66 | "print(psi_FM)\n", 67 | "SigmaZ = np.diag([1., -1.])\n", 68 | "print(psi_FM.site_expectation_value(SigmaZ))" 69 | ] 70 | }, 71 | { 72 | "cell_type": "code", 73 | "execution_count": null, 74 | "id": "fe6d3e07-f52d-466e-913d-245e541a0ced", 75 | "metadata": {}, 76 | "outputs": [], 77 | "source": [ 78 | "def init_PM_MPS(L, bc='finite'):\n", 79 | " \"\"\"Return a paramagnetic MPS (= product state with all spins pointing in +x direction)\"\"\"\n", 80 | " d = 2\n", 81 | " B = np.zeros([1, d, 1], dtype=float)\n", 82 | " B[0, 0, 0] = 1./np.sqrt(2)\n", 83 | " B[0, 1, 0] = 1./np.sqrt(2)\n", 84 | " S = np.ones([1], dtype=float)\n", 85 | " Bs = [B.copy() for i in range(L)]\n", 86 | " Ss = [S.copy() for i in range(L)]\n", 87 | " return SimpleMPS(Bs, Ss, bc=bc)" 88 | ] 89 | }, 90 | { 91 | "cell_type": "markdown", 92 | "id": "bcb5c97c-5f8b-4c1c-94c6-d3c2041db75c", 93 | "metadata": {}, 94 | "source": [ 95 | "... and how to initialize the `TFIModel`." 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": null, 101 | "id": "d439f112", 102 | "metadata": {}, 103 | "outputs": [], 104 | "source": [ 105 | "from tenpy_toycodes.b_model import TFIModel\n", 106 | "\n", 107 | "g = 1.2\n", 108 | "model = TFIModel(L=L, J=1., g=g, bc='finite')\n", 109 | "\n", 110 | "print(\" = \", psi_FM.bond_expectation_value(model.H_bonds))\n", 111 | "print(\"energy:\", np.sum(psi_FM.bond_expectation_value(model.H_bonds)))\n", 112 | "# (make sure the model and state have the same length and boundary conditions!)" 113 | ] 114 | }, 115 | { 116 | "cell_type": "markdown", 117 | "id": "93368432-f304-4327-987c-b9c12a818c26", 118 | "metadata": {}, 119 | "source": [ 120 | "\n", 121 | "For small enough system size $L \\lesssim 16$, you can compare the energies to exact diagonalization in the full Hilbert space (which is exponentially expensive!):" 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": null, 127 | "id": "25871fc0-6b4f-4266-b78c-c25519210372", 128 | "metadata": {}, 129 | "outputs": [], 130 | "source": [ 131 | "from tenpy_toycodes.tfi_exact import finite_gs_energy\n", 132 | "\n", 133 | "if L <= 16:\n", 134 | " energy_exact = finite_gs_energy(L=L, J=1., g=g)" 135 | ] 136 | }, 137 | { 138 | "cell_type": "code", 139 | "execution_count": null, 140 | "id": "69be6188-dfad-4313-90dc-ca0be87793ad", 141 | "metadata": {}, 142 | "outputs": [], 143 | "source": [] 144 | }, 145 | { 146 | "cell_type": "markdown", 147 | "id": "bb7b318b", 148 | "metadata": {}, 149 | "source": [ 150 | "## The DMRG algorithm\n", 151 | "\n", 152 | "The file `tenpy_toycodes/d_dmrg.py` implements the DMRG algorithm.\n", 153 | "It can be called like this:" 154 | ] 155 | }, 156 | { 157 | "cell_type": "code", 158 | "execution_count": null, 159 | "id": "3196f562", 160 | "metadata": {}, 161 | "outputs": [], 162 | "source": [ 163 | "from tenpy_toycodes.d_dmrg import SimpleDMRGEngine, SimpleHeff2" 164 | ] 165 | }, 166 | { 167 | "cell_type": "code", 168 | "execution_count": null, 169 | "id": "ffc67843", 170 | "metadata": {}, 171 | "outputs": [], 172 | "source": [ 173 | "chi_max = 15\n", 174 | "\n", 175 | "psi = init_FM_MPS(model.L, model.d, model.bc)\n", 176 | "eng = SimpleDMRGEngine(psi, model, chi_max=chi_max, eps=1.e-10)\n", 177 | "for i in range(10):\n", 178 | " E_dmrg = eng.sweep()\n", 179 | " E = np.sum(psi.bond_expectation_value(model.H_bonds))\n", 180 | " print(\"sweep {i:2d}: E = {E:.13f}\".format(i=i + 1, E=E))\n", 181 | "print(\"final bond dimensions: \", psi.get_chi())\n", 182 | "mag_x = np.mean(psi.site_expectation_value(model.sigmax))\n", 183 | "mag_z = np.mean(psi.site_expectation_value(model.sigmaz))\n", 184 | "print(\"magnetization in X = {mag_x:.5f}\".format(mag_x=mag_x))\n", 185 | "print(\"magnetization in Z = {mag_z:.5f}\".format(mag_z=mag_z))\n", 186 | "if model.L <= 16:\n", 187 | " E_exact = finite_gs_energy(L=model.L, J=model.J, g=model.g)\n", 188 | " print(\"err in energy = {err:.3e}\".format(err=E - E_exact))" 189 | ] 190 | }, 191 | { 192 | "cell_type": "markdown", 193 | "id": "b0eee6bf", 194 | "metadata": {}, 195 | "source": [ 196 | "### Exercise: read d_dmrg.py\n", 197 | "\n", 198 | "Read the code of `tenpy_toycodes/d_dmrg.py` and try to undertstand the general structure of how it works.\n" 199 | ] 200 | }, 201 | { 202 | "cell_type": "code", 203 | "execution_count": null, 204 | "id": "97226a24-892e-493a-88f4-9394615f240a", 205 | "metadata": {}, 206 | "outputs": [], 207 | "source": [] 208 | }, 209 | { 210 | "cell_type": "markdown", 211 | "id": "2a57a415-a81d-46f1-8366-533466ff70ee", 212 | "metadata": {}, 213 | "source": [ 214 | "### Exercise: measure correlation functions\n", 215 | "\n", 216 | "Just looking at expectation values of local operators is not enough.\n", 217 | "Also measure correlation functions $\\langle Z_{L/4} Z_{3L/4} \\rangle$." 218 | ] 219 | }, 220 | { 221 | "cell_type": "code", 222 | "execution_count": null, 223 | "id": "8fc2a15b-f45d-4073-aa56-7af10cb01bdc", 224 | "metadata": {}, 225 | "outputs": [], 226 | "source": [] 227 | }, 228 | { 229 | "cell_type": "code", 230 | "execution_count": null, 231 | "id": "76d1d8ed-c18d-4a84-8faf-f5390cd7e0b4", 232 | "metadata": {}, 233 | "outputs": [], 234 | "source": [] 235 | }, 236 | { 237 | "cell_type": "markdown", 238 | "id": "4799e6ed-f16d-4189-b224-8d1e972bf14d", 239 | "metadata": {}, 240 | "source": [ 241 | "### Exercise: DMRG runs\n", 242 | "\n", 243 | "Try running DMRG for various different parameters:\n", 244 | "\n", 245 | "- Change the bond dimension `chi` and truncation threashold `eps`.\n", 246 | "- Change the system size `L`\n", 247 | "- Change the model parameter `g` (at fixed $J=1$) to both the ferromagnetic phase $g1$.\n", 249 | "- Change the initial state.\n", 250 | "\n" 251 | ] 252 | }, 253 | { 254 | "cell_type": "code", 255 | "execution_count": null, 256 | "id": "c489cca3-3b21-4602-8329-6fb9e06137ff", 257 | "metadata": {}, 258 | "outputs": [], 259 | "source": [] 260 | }, 261 | { 262 | "cell_type": "markdown", 263 | "id": "efe930da-18c3-4091-8ea9-412b562c7857", 264 | "metadata": {}, 265 | "source": [ 266 | "### Exercise: Phase diagram\n", 267 | "\n", 268 | "To map out the phase diagram, it can be convenient to define a function that just runs DMRG for a given model. Fill in the below template. Use it obtain and plot the energy, correlations and magnetizations for different $L=16, 32, 64$ against $g$." 269 | ] 270 | }, 271 | { 272 | "cell_type": "code", 273 | "execution_count": null, 274 | "id": "8157051c-7bfa-407b-a889-536e821eedf6", 275 | "metadata": {}, 276 | "outputs": [], 277 | "source": [ 278 | "def run_DMRG(model, chi_max=50):\n", 279 | " print(f\"runnning DMRG for L={model.L:d}, g={model.g:.2f}, bc={model.bc}, chi_max={chi_max:d}\")\n", 280 | " \n", 281 | " raise NotImplementedError(\"TODO: this is an Exercise!\")\n", 282 | " \n", 283 | " return psi" 284 | ] 285 | }, 286 | { 287 | "cell_type": "code", 288 | "execution_count": null, 289 | "id": "b7eb8ba4-897e-495a-a22f-dc833cb246b5", 290 | "metadata": {}, 291 | "outputs": [], 292 | "source": [ 293 | "results_all = {}\n", 294 | "# TIP: comment this out after the first run to avoid overriding your data!" 295 | ] 296 | }, 297 | { 298 | "cell_type": "code", 299 | "execution_count": null, 300 | "id": "c1d120e7-0ecc-4800-857b-f12cd3fd23e7", 301 | "metadata": {}, 302 | "outputs": [], 303 | "source": [ 304 | "L = 8\n", 305 | "\n", 306 | "mag_X = []\n", 307 | "mag_Z = []\n", 308 | "E = []\n", 309 | "corr = []\n", 310 | "max_chi = []\n", 311 | "\n", 312 | "gs = [0.1, 0.5, 0.8, 0.9, 1.0, 1.1, 1.2, 1.5]\n", 313 | "for g in gs:\n", 314 | " model = TFIModel(L=L, J=1., g=g, bc='finite')\n", 315 | " psi = run_DMRG(model)\n", 316 | " mag_X.append(np.mean(psi.site_expectation_value(model.sigmax)))\n", 317 | " mag_Z.append(np.mean(psi.site_expectation_value(model.sigmaz)))\n", 318 | " E.append(np.sum(psi.bond_expectation_value(model.H_bonds)))\n", 319 | " corr.append(psi.correlation_function(model.sigmaz, model.L//4, model.sigmaz, model.L * 3 // 4))\n", 320 | " max_chi.append(max(psi.get_chi()))\n", 321 | "\n", 322 | "results_all[L] = {\n", 323 | " 'g': gs,\n", 324 | " 'mag_X': mag_X,\n", 325 | " 'mag_Z': mag_Z,\n", 326 | " 'E': E,\n", 327 | " 'corr': corr,\n", 328 | " 'max_chi': max_chi\n", 329 | "}" 330 | ] 331 | }, 332 | { 333 | "cell_type": "code", 334 | "execution_count": null, 335 | "id": "bdc10397-269f-469d-a2c0-1bb0232dfbb0", 336 | "metadata": {}, 337 | "outputs": [], 338 | "source": [ 339 | "key = 'corr'\n", 340 | "\n", 341 | "plt.figure()\n", 342 | "for L in results_all:\n", 343 | " res_L = results_all[L]\n", 344 | " plt.plot(res_L['g'], res_L[key], marker='o', label=\"L={L:d}\".format(L=L))\n", 345 | "plt.xlabel('g')\n", 346 | "plt.ylabel(key)\n", 347 | "plt.legend(loc='best')" 348 | ] 349 | }, 350 | { 351 | "cell_type": "code", 352 | "execution_count": null, 353 | "id": "6d59b6c5-551f-4b2a-b7cd-7d8c1581de67", 354 | "metadata": {}, 355 | "outputs": [], 356 | "source": [] 357 | }, 358 | { 359 | "cell_type": "code", 360 | "execution_count": null, 361 | "id": "ea8d130a-62e8-4dd6-b3d0-68849714f444", 362 | "metadata": {}, 363 | "outputs": [], 364 | "source": [] 365 | }, 366 | { 367 | "cell_type": "code", 368 | "execution_count": null, 369 | "id": "ba570aab-8ad5-4781-927f-1c9d71906e14", 370 | "metadata": {}, 371 | "outputs": [], 372 | "source": [] 373 | }, 374 | { 375 | "cell_type": "code", 376 | "execution_count": null, 377 | "id": "ab629c07-7576-4ec5-baef-d71f20f211e4", 378 | "metadata": {}, 379 | "outputs": [], 380 | "source": [] 381 | }, 382 | { 383 | "cell_type": "markdown", 384 | "id": "b70436f7-f7c9-49a8-bdc4-c90d322681ca", 385 | "metadata": {}, 386 | "source": [ 387 | "## Infinite DMRG" 388 | ] 389 | }, 390 | { 391 | "cell_type": "markdown", 392 | "id": "78ae81b0-3ade-4133-9280-dd57b9958807", 393 | "metadata": {}, 394 | "source": [ 395 | "The given DMRG code also works with `bc='infinite'` boundary conditions of the model and state.\n", 396 | "The given `SimpleDMRG` code also allows to run infinite DMRG, simply by replacing the `bc='finite'` for both the model and the MPS. \n", 397 | "\n", 398 | "- Look at the implementation of `d_dmrg.py` (and `a_mps.py`) to see where the differences are.\n", 399 | "\n", 400 | "Again, we can compare to analytic calculations possible for the Transverse Field Ising model.\n", 401 | " " 402 | ] 403 | }, 404 | { 405 | "cell_type": "code", 406 | "execution_count": null, 407 | "id": "98e49bcb-9dc9-4dc1-aff3-f8d08fff5085", 408 | "metadata": {}, 409 | "outputs": [], 410 | "source": [ 411 | "from tenpy_toycodes.tfi_exact import infinite_gs_energy" 412 | ] 413 | }, 414 | { 415 | "cell_type": "markdown", 416 | "id": "f1ec34c0-68ac-4e4a-823c-f8d0ef7e7a63", 417 | "metadata": {}, 418 | "source": [ 419 | "\n", 420 | "The `L` parameter now just indices the number of tensors insite the unit cell of the infinite MPS.\n", 421 | "It has to be at least `2`, since we optimize 2 tensors at once in our DMRG code.\n", 422 | "Note that we now use the `mean` to calculate densities of observables instead of extensive quantities:" 423 | ] 424 | }, 425 | { 426 | "cell_type": "code", 427 | "execution_count": null, 428 | "id": "545b6a75-1550-433e-8763-968281137a41", 429 | "metadata": {}, 430 | "outputs": [], 431 | "source": [ 432 | "model = TFIModel(L=2, J=1., g=0.8, bc='infinite') # just change bc='infinite' here\n", 433 | "\n", 434 | "chi_max = 10\n", 435 | "\n", 436 | "psi = init_FM_MPS(model.L, model.d, model.bc)\n", 437 | "eng = SimpleDMRGEngine(psi, model, chi_max=chi_max, eps=1.e-7)\n", 438 | "for i in range(10):\n", 439 | " E_dmrg = eng.sweep()\n", 440 | " E = np.mean(psi.bond_expectation_value(model.H_bonds))\n", 441 | " #if i % 10 == 9:\n", 442 | " print(\"sweep {i:2d}: E/L = {E:.13f}\".format(i=i + 1, E=E))\n", 443 | "print(\"final bond dimensions: \", psi.get_chi())\n", 444 | "mag_x = np.mean(psi.site_expectation_value(model.sigmax))\n", 445 | "mag_z = np.mean(psi.site_expectation_value(model.sigmaz))\n", 446 | "print(\"magnetization density in X = {mag_x:.5f}\".format(mag_x=mag_x))\n", 447 | "print(\"magnetization density in Z = {mag_z:.5f}\".format(mag_z=mag_z))\n", 448 | "E_exact = infinite_gs_energy(model.J, model.g)\n", 449 | "print(\"err in energy = {err:.3e}\".format(err=E - E_exact))" 450 | ] 451 | }, 452 | { 453 | "cell_type": "markdown", 454 | "id": "d2ed4ee1-6b8e-448b-9a76-b2a9f2a1ef6e", 455 | "metadata": {}, 456 | "source": [ 457 | "### Exercise: Infinite DMRG\n", 458 | "\n", 459 | "Try running the infinite DMRG code. How many sweeps do you now need to perform to converge now?\n", 460 | " Does it depend on `g` and `chi`?" 461 | ] 462 | }, 463 | { 464 | "cell_type": "code", 465 | "execution_count": null, 466 | "id": "80476e8b-e84a-41b3-950e-1b378bd853ae", 467 | "metadata": {}, 468 | "outputs": [], 469 | "source": [] 470 | }, 471 | { 472 | "cell_type": "code", 473 | "execution_count": null, 474 | "id": "ca2aa6c1-19d6-4a9b-b63c-8f5706a9f6e0", 475 | "metadata": {}, 476 | "outputs": [], 477 | "source": [] 478 | }, 479 | { 480 | "cell_type": "code", 481 | "execution_count": null, 482 | "id": "292897df-7862-46ab-a1ae-1239185d9828", 483 | "metadata": {}, 484 | "outputs": [], 485 | "source": [] 486 | }, 487 | { 488 | "cell_type": "markdown", 489 | "id": "6045388b-47b8-4a5e-bc3b-bcd2dfa07be2", 490 | "metadata": {}, 491 | "source": [ 492 | "From the exercise, you should see that you need significantly more sweeps when the correlation length gets larger, in particular at the critical point! At the critical point, the [correlation length (of infinite MPS) scales](https://arxiv.org/abs/0812.2903) as $\\xi \\propto \\chi^\\kappa$, so you again need more sweeps to converge at larger bond dimensions." 493 | ] 494 | }, 495 | { 496 | "cell_type": "code", 497 | "execution_count": null, 498 | "id": "60d545ba-a00d-4461-b6db-82336a02465e", 499 | "metadata": {}, 500 | "outputs": [], 501 | "source": [] 502 | }, 503 | { 504 | "cell_type": "code", 505 | "execution_count": null, 506 | "id": "d16df3aa", 507 | "metadata": {}, 508 | "outputs": [], 509 | "source": [] 510 | }, 511 | { 512 | "cell_type": "markdown", 513 | "id": "1e99ab49", 514 | "metadata": {}, 515 | "source": [ 516 | "### Advanced exercises for the experts (and those who want to become them ;-) )\n", 517 | "\n", 518 | "- Obtain the ground state of the transverse field ising model at the critical point with DMRG for large `L`.\n", 519 | " Try to plot the corrlation function as a function of `j-i`.\n", 520 | " What form does it have? Is an MPS a good ansatz for that?\n", 521 | "\n", 522 | "\n", 523 | "\n", 524 | "\n" 525 | ] 526 | }, 527 | { 528 | "cell_type": "code", 529 | "execution_count": null, 530 | "id": "8adcb517-0981-4faa-a756-1b792a1ee00f", 531 | "metadata": {}, 532 | "outputs": [], 533 | "source": [] 534 | }, 535 | { 536 | "cell_type": "markdown", 537 | "id": "119170d8-d2bf-4d5b-b799-a08574dc7e37", 538 | "metadata": {}, 539 | "source": [ 540 | "- Compare running DMRG and imaginary time evolution with TEBD from `tenpy_toycodes/c_tebd.py` \n", 541 | " for various parameters of `L`, `J`, `g`, and `bc`. Which one is faster? Do they always agree?" 542 | ] 543 | }, 544 | { 545 | "cell_type": "code", 546 | "execution_count": null, 547 | "id": "5f1aabfc-7a3d-4450-a1fa-d94f8f2d73fe", 548 | "metadata": {}, 549 | "outputs": [], 550 | "source": [] 551 | }, 552 | { 553 | "cell_type": "code", 554 | "execution_count": null, 555 | "id": "1dfbc061", 556 | "metadata": {}, 557 | "outputs": [], 558 | "source": [] 559 | }, 560 | { 561 | "cell_type": "code", 562 | "execution_count": null, 563 | "id": "b9d25343", 564 | "metadata": {}, 565 | "outputs": [], 566 | "source": [] 567 | }, 568 | { 569 | "cell_type": "code", 570 | "execution_count": null, 571 | "id": "da649492-fe07-437f-931a-7843a3e4a6a4", 572 | "metadata": {}, 573 | "outputs": [], 574 | "source": [] 575 | }, 576 | { 577 | "cell_type": "code", 578 | "execution_count": null, 579 | "id": "97801804-24e0-4261-912f-699fc8b93228", 580 | "metadata": {}, 581 | "outputs": [], 582 | "source": [] 583 | }, 584 | { 585 | "cell_type": "code", 586 | "execution_count": null, 587 | "id": "1c5c22e8-1254-4073-86d6-8d4c68e1617b", 588 | "metadata": {}, 589 | "outputs": [], 590 | "source": [] 591 | } 592 | ], 593 | "metadata": { 594 | "kernelspec": { 595 | "display_name": "Python 3 (ipykernel)", 596 | "language": "python", 597 | "name": "python3" 598 | }, 599 | "language_info": { 600 | "codemirror_mode": { 601 | "name": "ipython", 602 | "version": 3 603 | }, 604 | "file_extension": ".py", 605 | "mimetype": "text/x-python", 606 | "name": "python", 607 | "nbconvert_exporter": "python", 608 | "pygments_lexer": "ipython3", 609 | "version": "3.10.6" 610 | } 611 | }, 612 | "nbformat": 4, 613 | "nbformat_minor": 5 614 | } 615 | -------------------------------------------------------------------------------- /tenpy_toycodes/g_vumps.py: -------------------------------------------------------------------------------- 1 | """Toy code implementing the variational uniform matrix product states (VUMPS) algorithm. 2 | 3 | This implementation closely follows Laurens Vanderstraeten, Jutho Haegeman and Frank Verstraete, 4 | Tangent-space methods for uniform matrix product states, SciPost Physics Lecture Notes 007, 2019, 5 | https://arxiv.org/abs/1810.07006. 6 | """ 7 | 8 | import numpy as np 9 | from scipy.sparse.linalg import LinearOperator, gmres, eigsh 10 | from scipy.linalg import polar 11 | 12 | from .f_umps import UniformMPS, TransferMatrix 13 | 14 | 15 | def vumps_algorithm(h, guess_psi0, tol, maxruns=1_000): 16 | """Find the uMPS ground state of the Hamiltonian h with an initial guess up to tolerance tol.""" 17 | vumps_engine = VUMPSEngine(guess_psi0, h, tol/10) 18 | for i in range(maxruns): 19 | vumps_engine.run() 20 | if vumps_engine.err <= tol: 21 | psi0 = vumps_engine.psi 22 | e0 = psi0.get_bond_expectation_value(h) 23 | var0 = vumps_engine.get_energy_variance() 24 | print(f"uMPS ground state converged with VUMPS up to tol={tol} in gradient norm. " \ 25 | + f"Final error after {i+1} iterations: {vumps_engine.err}.\n" \ 26 | + f"Ground state energy density: {e0}. \n" 27 | + f"Ground state variance density: {var0}.") 28 | return e0, psi0, var0 29 | raise RuntimeError(f"Ground state did not converge up to tol={tol}. \ 30 | Final error after {maxruns} runs: {vumps_engine.err}.") 31 | 32 | 33 | class VUMPSEngine: 34 | """Simple class for the VUMPS engine to perform the variational ground state optimization for h. 35 | 36 | For P_A the tangent-space projector and H = sum_n h_{n,n+1}, the tangent-space gradient reads 37 | P_A * H|psi(A)> = |psi(G,A)> with G = Heff1(AC) - AL Heff0(C) = Heff1(AC) - Heff0(C) AR. 38 | The variational ground state optimum (corresponding to G=0) in the uMPS manifold satisfies 39 | 1) AC ground state of Heff1, 40 | 2) C ground state of Heff0, 41 | 3) AC = AL C = C AR. 42 | 43 | Parameters 44 | ---------- 45 | psi, h, tol: Same as attributes. 46 | 47 | Attributes 48 | ---------- 49 | psi: UniformMPS 50 | The current state to be iteratively optimized towards the ground state. 51 | h: np.array[ndim=4] 52 | The two-site Hamiltonian of which the ground state is searched. 53 | tol: float 54 | Tolerance up to which the geometric sum environments Lh and Rh are computed with gmres. 55 | Lh: np.array[ndim=2] 56 | Left environment computed from geometric sum of transfer matrix TL. 57 | Rh: np.array[ndim=2] 58 | Right environment computed from geometric sum of transfer matrix TR. 59 | err: float 60 | The error measure for psi, equal to the gradient norm ||Heff_AC(AC) - AL Heff_C(C)||. 61 | """ 62 | def __init__(self, psi, h, tol): 63 | self.psi = psi.copy() 64 | self.h = subtract_energy_offset(self.psi, h, canonical_form=False) 65 | self.tol = tol 66 | self.Lh = get_Lh(self.psi, self.h, canonical_form=False, guess=None, tol=self.tol) 67 | self.Rh = get_Rh(self.psi, self.h, canonical_form=False, guess=None, tol=self.tol) 68 | self.err = self.get_gradient_norm() 69 | 70 | def run(self): 71 | """Perform one update of self.psi in the variational ground state optimization for self.h. 72 | 73 | 1) AC -> ground state of Heff1, 74 | 2) C -> ground state of Heff0, 75 | 3) AL/AR from left/right polar decompositions of AC and C. 76 | """ 77 | H_eff_1 = Heff1(self.h, self.Lh, self.psi.AL, self.psi.AR, self.Rh) 78 | H_eff_0 = Heff0(self.h, self.Lh, self.psi.AL, self.psi.AR, self.Rh) 79 | AC_new = self.get_theta_gs(Heff=H_eff_1, guess=self.psi.AC) 80 | C_new = self.get_theta_gs(Heff=H_eff_0, guess=self.psi.C) 81 | AL_new, AR_new = get_AL_AR(AC_new, C_new) 82 | self.psi = UniformMPS(AL_new, AR_new, AC_new, C_new) 83 | self.h = subtract_energy_offset(self.psi, self.h, canonical_form=False) 84 | self.Lh = get_Lh(self.psi, self.h, canonical_form=False, guess=self.Lh, tol=self.tol) 85 | self.Rh = get_Rh(self.psi, self.h, canonical_form=False, guess=self.Rh, tol=self.tol) 86 | self.err = self.get_gradient_norm() 87 | 88 | @staticmethod 89 | def get_theta_gs(Heff, guess): 90 | """Find the ground state of Heff with an initial guess.""" 91 | guess = np.reshape(guess, Heff.shape[1]) 92 | _, theta_gs = eigsh(Heff, k=1, which="SA", v0=guess) 93 | theta_gs = np.reshape(theta_gs[:, 0], Heff.shape_theta) 94 | return theta_gs 95 | 96 | def get_gradient_norm(self): 97 | """Compute the gradient norm ||Heff1(AC) - AL Heff0(C)|| for self.psi.""" 98 | H_eff_1 = Heff1(self.h, self.Lh, self.psi.AL, self.psi.AR, self.Rh) 99 | H_eff_0 = Heff0(self.h, self.Lh, self.psi.AL, self.psi.AR, self.Rh) 100 | AC = H_eff_1._matvec(np.reshape(self.psi.AC, H_eff_1.shape[1])) # vL.p.vR 101 | AC = np.reshape(AC, H_eff_1.shape_theta) # vL p vR 102 | C = H_eff_0._matvec(np.reshape(self.psi.C, H_eff_0.shape[1])) # vL.vR 103 | C = np.reshape(C, H_eff_0.shape_theta) # vL vR 104 | ALC = np.tensordot(self.psi.AL, C, axes=(2, 0)) # vL p [vR], [vL] vR 105 | gradient_norm = np.linalg.norm(AC - ALC) 106 | return gradient_norm 107 | 108 | def get_energy_variance(self): 109 | """For the Hamiltonian H = sum_n h_{n,n+1}, compute the energy variance density of self.psi. 110 | 111 | var(H) = - ^2 = )^2|psi>. 112 | 113 | Diagrammatic representation for the variance density (after h -> h - ): 114 | 115 | .----(AC)--(AR)--. .--(AL)--(AC)--(AR)--. .--(AC)--(AR)--. .--(AC)--(AR)--(AR)--. 116 | | | | | | | | | | | | | | | | | | | 117 | | (----h----) | | | (----h----) | | (----h----) | | (----h----) | | 118 | | | | | + | | | | | + | | | | + | | | | | 119 | (Lh) | | | | (----h----) | | | (----h----) | | | (----h----) | 120 | | | | | | | | | | | | | | | | | | | 121 | .---(AC*)--(AR*)-. .-(AL*)-(AC*)--(AR*)-. .-(AC*)--(AR*)-. .-(AC*)--(AR*)-(AR*)-. 122 | 123 | .--(AC)--(AR)----. 124 | | | | | 125 | | (----h----) | 126 | + | | | | 127 | | | | (Rh) 128 | | | | | 129 | .-(AC*)--(AR*)---. 130 | """ 131 | h = self.h 132 | Lh = self.Lh 133 | AL = self.psi.AL 134 | AC = self.psi.AC 135 | AR = self.psi.AR 136 | Rh = self.Rh 137 | ACAR = np.tensordot(AC, AR, axes=(2, 0)) # vL p1 [vR], [vL] p2 vR -> vL p1 p2 vR 138 | ACAR_h = np.tensordot(ACAR, h, axes=((1, 2), (2, 3))) # vL [p1] [p2] vR, p1 p2 [p1*] [p2*] 139 | ACAR_h = np.transpose(ACAR_h, (0, 2, 3, 1)) # vL p1 p2 vR 140 | var1 = np.tensordot(ACAR_h, np.conj(ACAR), axes=((1, 2, 3), (1, 2, 3))) 141 | # vL [p1] [p2] [vR], vL* [p1*] [p2*] [vR*] 142 | var1 = np.tensordot(Lh, var1, axes=((0, 1), (0, 1))) # [vR] [vR*], [vL] [vL*] 143 | var2 = np.tensordot(AL, ACAR_h, axes=(2, 0)) # vL p1 [vR], [vL] p2 p3 vR 144 | var2 = np.tensordot(h, var2, axes=((2, 3), (1, 2))) # p1 p2 [p1*] [p2*], vL [p1] [p2] p3 vR 145 | var2 = np.tensordot(var2, np.conj(AL), axes=((0, 2), (1, 0))) 146 | # [p1] p2 [vL] p3 vR, [vL*] [p1*] vR* 147 | var2 = np.tensordot(var2, np.conj(ACAR), axes=((3, 0, 1, 2), (0, 1, 2, 3))) 148 | # [p2] [p3] [vR] [vR*], [vL*] [p2*] [p3*] [vR*] 149 | var3 = np.tensordot(h, ACAR_h, axes=((2, 3), (1, 2))) # p1 p2 [p1*] [p2*], vL [p1] [p2] vR 150 | var3 = np.tensordot(var3, np.conj(ACAR), axes=((2, 0, 1, 3), (0, 1, 2, 3))) 151 | # [p1] [p2] [vL] [vR], [vL*] [p1*] [p2*] [vR*] 152 | var4 = np.tensordot(ACAR_h, AR, axes=(3, 0)) # vL p1 p2 [vR], [vL] p3 vR 153 | var4 = np.tensordot(h, var4, axes=((2, 3), (2, 3))) # p2 p3 [p2*] [p3*], vL p1 [p2] [p3] vR 154 | var4 = np.tensordot(var4, np.conj(ACAR), axes=((2, 3, 0), (0, 1, 2))) 155 | # [p2] p3 [vL] [p1] vR, [vL*] [p1*] [p2*] vR* 156 | var4 = np.tensordot(var4, np.conj(AR), axes=((2, 0, 1), (0, 1, 2))) 157 | # [p3] [vR] [vR*], [vL*] [p3*] [vR*] 158 | var5 = np.tensordot(ACAR_h, np.conj(ACAR), axes=((0, 1, 2), (0, 1, 2))) 159 | # [vL] [p1] [p2] vR, [vL*] [p1*] [p2*] vR* 160 | var5 = np.tensordot(var5, Rh, axes=((0, 1), (0, 1))) # [vR] [vR*], [vL] [vL*] 161 | var = var1 + var2 + var3 + var4 + var5 162 | return var 163 | 164 | 165 | # Classes and functions used both vor VUMPS and uTDVP 166 | 167 | class Heff1(LinearOperator): 168 | """Class for the effective Hamiltonian acting on the center site tensor AC. 169 | 170 | .---(AC)--. .--(AL)--(AC)--. .--(AC)--(AR)--. .--(AC)---. 171 | | | | | | | | | | | | | | | 172 | matvec: --(AC)-- -> (Lh) | | + | (----h----) | + | (----h----) | + | | (Rh) 173 | | | | | | | | | | | | | | | | 174 | .--- --. .-(AL*)-- --. .-- --(AR*)--. .-- ---. 175 | """ 176 | def __init__(self, h, Lh, AL, AR, Rh): 177 | self.h = h # p1 p2 p1* p2* 178 | self.Lh = Lh # vR vR* 179 | self.AL = AL # vL p vR 180 | self.AR = AR # vL p vR 181 | self.Rh = Rh # vL vL* 182 | D = np.shape(self.AL)[0] 183 | d = np.shape(self.AL)[1] 184 | self.shape = (D * d * D, D * d * D) 185 | self.shape_theta = (D, d, D) 186 | self.dtype = self.AL.dtype 187 | 188 | def _matvec(self, AC): 189 | """Perform the matvec multiplication diagrammatically shown above.""" 190 | AC = np.reshape(AC, self.shape_theta) # vL p vR 191 | AC1 = np.tensordot(self.Lh, AC, axes=(0, 0)) # [vR] vR*, [vL] p vR -> vL p vR 192 | AC2 = np.tensordot(self.AL, AC, axes=(2, 0)) # vL p1 [vR], [vL] p2 vR 193 | AC2 = np.tensordot(self.h, AC2, axes=((2, 3), (1, 2))) # p1 p2 [p1*] [p2*], vL [p1] [p2] vR 194 | AC2 = np.tensordot(np.conj(self.AL), AC2, axes=((0, 1), (2, 0))) 195 | # [vL*] [p1*] vR*, [p1] p2 [vL] vR -> vL p vR 196 | AC3 = np.tensordot(AC, self.AR, axes=(2, 0)) # vL p1 [vR], [vL] p2 vR 197 | AC3 = np.tensordot(self.h, AC3, axes=((2, 3), (1, 2))) # p1 p2 [p1*] [p2*], vL [p1] [p2] vR 198 | AC3 = np.tensordot(np.conj(self.AR), AC3, axes=((1, 2), (1, 3))) 199 | # vL* [p2*] [vR*], p1 [p2] vL [vR] -> vR p vL 200 | AC3 = np.transpose(AC3, (2, 1, 0)) # vL p vR 201 | AC4 = np.tensordot(AC, self.Rh, axes=(2, 0)) # vL p [vR], [vL] vL* -> vL p vR 202 | AC_new = AC1 + AC2 + AC3 + AC4 203 | AC_new = np.reshape(AC_new, self.shape[1]) # vL.p.vR 204 | return AC_new 205 | 206 | def _adjoint(self): 207 | return self 208 | 209 | def trace(self): 210 | """Compute the trace of Heff1 (only needed for TDVP). 211 | 212 | .---. .--(AL)-. .---. .---. .--(AR)-. .---. 213 | | | | | | | | | | | | | | | 214 | (Lh) | * d * D + | (----h----) | * D + D * | (----h----) | + D * d * | (Rh) 215 | | | | | | | | | | | | | | | 216 | .---. .-(AL*)-. .---. .---. .-(AR*)-. .---. 217 | """ 218 | trace1 = np.trace(self.Lh) * self.shape_theta[1] * self.shape_theta[2] 219 | trace2 = np.tensordot(self.AL, np.conj(self.AL), axes=((0, 2), (0, 2))) 220 | # [vL] p [vR], [vL*] p* [vR*] 221 | trace2 = np.tensordot(trace2, np.trace(self.h, axis1=1, axis2=3), axes=((0, 1), (1, 0))) 222 | # [p] [p*], [p1] [p2] [p1*] [p2*] 223 | trace2 *= self.shape_theta[2] 224 | trace3 = np.tensordot(self.AR, np.conj(self.AR), axes=((0, 2), (0, 2))) 225 | # [vL] p [vR], [vL*] p* [vR*] 226 | trace3 = np.tensordot(trace3, np.trace(self.h, axis1=0, axis2=2), axes=((0, 1), (1, 0))) 227 | # [p] [p*], [p1] [p2] [p1*] [p2*] 228 | trace3 *= self.shape_theta[0] 229 | trace4 = self.shape_theta[0] * self.shape_theta[1] * np.trace(self.Rh) 230 | trace = trace1 + trace2 + trace3 + trace4 231 | return trace 232 | 233 | 234 | class Heff0(LinearOperator): 235 | """Class for the effective Hamiltonian acting on the center matrix C. 236 | 237 | .---(C)--. .--(AL)--(C)--(AR)--. .--(C)---. 238 | | | | | | | | | 239 | matvec: --(C)-- -> (Lh) | + | (------h-------) | + | (Rh) 240 | | | | | | | | | 241 | .--- --. .-(AL*)-- --(AR*)-. .-- ---. 242 | """ 243 | def __init__(self, h, Lh, AL, AR, Rh): 244 | self.h = h # p1 p2 p1* p2* 245 | self.Lh = Lh # vR vR* 246 | self.AL = AL # vL p vR 247 | self.AR = AR # vL p vR 248 | self.Rh = Rh # vL vL* 249 | D = np.shape(self.AL)[0] 250 | self.shape = (D * D, D * D) 251 | self.shape_theta = (D, D) 252 | self.dtype = self.AL.dtype 253 | 254 | def _matvec(self, C): 255 | """Perform the matvec multiplication diagrammatically shown above.""" 256 | C = np.reshape(C, self.shape_theta) # vL vR 257 | C1 = np.tensordot(self.Lh, C, axes=(0, 0)) # [vR] vR*, [vL] vR -> vL vR 258 | C2 = np.tensordot(self.AL, C, axes=(2, 0)) # vL p1 [vR], [vL] vR 259 | C2 = np.tensordot(C2, self.AR, axes=(2, 0)) # vL p1 [vR], [vL] p2 vR 260 | C2 = np.tensordot(self.h, C2, axes=((2, 3), (1, 2))) # p1 p2 [p1*] [p2*], vL [p1] [p2] vR 261 | C2 = np.tensordot(np.conj(self.AL), C2, axes=((0, 1), (2, 0))) 262 | # [vL*] [p1*] vR*, [p1] p2 [vL] vR 263 | C2 = np.tensordot(C2, np.conj(self.AR), axes=((1, 2), (1, 2))) 264 | # vR* [p2] [vR], vL* [p2*] [vR*] -> vL vR 265 | C3 = np.tensordot(C, self.Rh, axes=(1, 0)) # vL [vR], [vL] vL* -> vL vR 266 | C_new = C1 + C2 + C3 267 | C_new = np.reshape(C_new, self.shape[1]) # vL.vR 268 | return C_new 269 | 270 | def _adjoint(self): 271 | return self 272 | 273 | def trace(self): 274 | """Compute the trace of Heff0 (only needed for TDVP). 275 | 276 | .---. .--(AL)-. .-(AR)--. .---. 277 | | | | | | | | | | | 278 | (Lh) | * D + | (------h------) | + D * | (Rh) 279 | | | | | | | | | | | 280 | .---. .-(AL*)-. .-(AR*)-. .---. 281 | """ 282 | trace1 = np.trace(self.Lh) * self.shape_theta[1] 283 | trace2 = np.tensordot(self.AL, np.conj(self.AL), axes=((0, 2), (0, 2))) 284 | # [vL] p1 [vR], [vL*] p1* [vR*] 285 | trace2 = np.tensordot(trace2, self.h, axes=((0, 1), (2, 0))) 286 | # [p1] [p1*], [p1] p2 [p1*] p2* 287 | trace2 = np.tensordot(trace2, self.AR, axes=(1, 1)) # p2 [p2*], vL [p2] vR 288 | trace2 = np.tensordot(trace2, np.conj(self.AR), axes=((1, 0, 2), (0, 1, 2))) 289 | # [p2] [vL] [vR], [vL*] [p2*] [vR*] 290 | trace3 = self.shape_theta[0] * np.trace(self.Rh) 291 | trace = trace1 + trace2 + trace3 292 | return trace 293 | 294 | 295 | class InverseGeometricSum(LinearOperator): 296 | """Class for the inverse of the geometric sum of a transfer matrix T_AB with leading right/left 297 | eigenvector |R>/ (X) - alpha | (X) [+ alpha (R) (L) (X)] 310 | | | | | | | | 311 | ---. ---. ---(B*)--. ---. .----. 312 | 313 | [iff pseudo=True] 314 | 315 | .--- .--- .---(A)--- .----. .--- 316 | | | | | | | | 317 | matvec for transpose=True: (X) -> (X) - alpha (X) | [+ alpha (X) (R) (L) ] 318 | | | | | | | | 319 | .--- .--- .--(B*)--- .----. .--- 320 | """ 321 | def __init__(self, A, B, R, L, transpose=False, alpha=1., pseudo=True): 322 | D = np.shape(A)[0] 323 | self.T_AB = TransferMatrix([A], [B], transpose=transpose) 324 | if pseudo: 325 | self.R = np.reshape(R, (D * D)) 326 | self.L = np.reshape(L, (D * D)) 327 | self.alpha = alpha 328 | self.transpose = transpose 329 | self.pseudo = pseudo 330 | self.shape = (D * D, D * D) 331 | self.shape_X = (D, D) 332 | self.dtype = A.dtype 333 | 334 | def _matvec(self, X): 335 | if not self.transpose: 336 | X1 = X 337 | X2 = -self.alpha * self.T_AB._matvec(X) 338 | if self.pseudo: 339 | X2 += self.alpha * self.R * np.inner(self.L, X) 340 | return X1 + X2 341 | else: 342 | X1 = X 343 | X2 = -self.alpha * self.T_AB._matvec(X) 344 | if self.pseudo: 345 | X2 += np.inner(X, self.R) * self.L 346 | return X1 + X2 347 | 348 | def multiply_geometric_sum(self, b, guess, tol): 349 | """Solve the linear equation self|X> = |b>.""" 350 | b = np.reshape(b, self.shape[1]) 351 | if guess is not None: 352 | guess = np.reshape(guess, self.shape[1]) 353 | X = gmres(self, b, x0=guess, rtol=tol, atol=0)[0] 354 | X = np.reshape(X, self.shape_X) # vL vL* or vR vR* 355 | return X 356 | 357 | 358 | def get_Lh(psi, h, canonical_form, guess, tol): 359 | """Compute left environment Lh from geometric sum of transfer matrix TL (psi not necessarily in 360 | canonical form). 361 | 362 | .--- .--(AL)--(AL)--- ---(AL)---^n 363 | | | | | | 364 | (Lh) = | (----h----) sum_{n=0}^{infty} | 365 | | | | | | 366 | .--- .-(AL*)-(AL*)--- --(AL*)--- 367 | """ 368 | AL = psi.AL # vL p vR 369 | D = np.shape(AL)[0] 370 | C = psi.C 371 | R = np.matmul(C, np.conj(C).T) # vL vL* 372 | if not canonical_form: 373 | TL = TransferMatrix([AL], [AL]) 374 | _, R = TL.get_leading_eigenpairs(k=1, guess=R) 375 | R /= np.trace(R) # vL vL* 376 | IGS = InverseGeometricSum(AL, AL, R=R, L=np.eye(D), transpose=True) 377 | theta2 = np.tensordot(AL, AL, axes=(2, 0)) # vL p1 p2 vR 378 | lh = np.tensordot(h, theta2, axes=((2, 3), (1, 2))) 379 | # p1 p2 [p1*] [p2*], vL [p1] [p2] vR 380 | lh = np.tensordot(lh, np.conj(theta2), axes=((2, 0, 1), (0, 1, 2))) 381 | # [p1] [p2] [vL] vR, [vL*] [p1*] [p2*] vR* 382 | Lh = IGS.multiply_geometric_sum(lh, guess, tol) # vR vR* 383 | return Lh 384 | 385 | def get_Rh(psi, h, canonical_form, guess, tol): 386 | """Compute right environment Rh from geometric sum of transfer matrix TR (psi not necessarily in 387 | canonical form). 388 | 389 | ---. ---(AR)---^n ---(AR)--(AR)--. 390 | | | | | | 391 | (Rh) = sum_{n=0}^{infty} | (----h----) | 392 | | | | | | 393 | ---. --(AR*)--- --(AR*)-(AR*)--. 394 | """ 395 | AR = psi.AR # vL p vR 396 | D = np.shape(AR)[0] 397 | C = psi.C 398 | L = np.matmul(C.T, np.conj(C)) # vR vR* 399 | if not canonical_form: 400 | TR = TransferMatrix([AR], [AR], transpose=True) 401 | _, L = TR.get_leading_eigenpairs(k=1, guess=L) 402 | L /= np.trace(L) # vR vR* 403 | IGS = InverseGeometricSum(AR, AR, R=np.eye(D), L=L) 404 | theta2 = np.tensordot(AR, AR, axes=(2, 0)) # vL p1 p2 vR 405 | rh = np.tensordot(h, theta2, axes=((2, 3), (1, 2))) 406 | # p1 p2 [p1*] [p2*], vL [p1] [p2] vR 407 | rh = np.tensordot(rh, np.conj(theta2), axes=((0, 1, 3), (1, 2, 3))) 408 | # [p1] [p2] vL [vR], vL* [p1*] [p2*] [vR*] 409 | Rh = IGS.multiply_geometric_sum(rh, guess, tol) # vL vL* 410 | return Rh 411 | 412 | 413 | def subtract_energy_offset(psi, h, canonical_form): 414 | """Subtract energy of psi from two site Hamiltonian h (psi not necessarily in canonical form). 415 | 416 | .--(AL)--(AL)--. 417 | | | | | 418 | h -> h - e * Id with e = = | (----h----) (R) 419 | | | | | 420 | .-(AL*)--(AL*)-. 421 | """ 422 | AL = psi.AL # vL p vR 423 | R = np.matmul(psi.C, np.conj(psi.C).T) # vL vL* 424 | if not canonical_form: 425 | T = TransferMatrix([AL], [AL]) 426 | _, R = T.get_leading_eigenpairs(k=1, guess=R) 427 | R /= np.trace(R) # vL vL* 428 | theta2 = np.tensordot(AL, AL, axes=(2, 0)) # vL p1 p2 vR 429 | lh = np.tensordot(h, theta2, axes=((2, 3), (1, 2))) 430 | # p1 p2 [p1*] [p2*], vL [p1] [p2] vR 431 | lh = np.tensordot(lh, np.conj(theta2), axes=((2, 0, 1), (0, 1, 2))) 432 | # [p1] [p2] [vL] vR, [vL*] [p1*] [p2*] vR* 433 | e = np.tensordot(lh, R, axes=((0, 1), (0, 1))) # [vR] [vR*], [vL] [vL*] 434 | d = np.shape(h)[0] 435 | Id = np.reshape(np.eye(d * d), (d, d, d, d)) 436 | h = h - e * Id # p1 p2 p1* p2* 437 | return h 438 | 439 | 440 | def get_AL_AR(AC, C): 441 | """From given AC and C, find AL and AR which minimize ||AC - AL C||, ||AC - C AR||. 442 | 443 | Left polar decompositions: AC = UL_AC PL_AC, C = UL_C PL_C -> AL = UL_AC UL_C^{dagger}, 444 | Right polar decompositions: AC = PR_AC UR_AC, C = PR_C UR_C -> AR = UR_C^{dagger} UR_AC. 445 | """ 446 | D = np.shape(AC)[0] 447 | d = np.shape(AC)[1] 448 | UL_AC, _ = polar(np.reshape(AC, (D * d, D))) # vL.p vR 449 | UL_C, _ = polar(C) 450 | AL = np.matmul(UL_AC, np.conj(UL_C).T) # vL.p [vR], [vL] vR 451 | AL = np.reshape(AL, (D, d, D)) # vL p vR 452 | UR_AC, _ = polar(np.reshape(AC, (D, d * D)), side="left") # vL p.vR 453 | UR_C, _ = polar(C, side="left") 454 | AR = np.matmul(np.conj(UR_C).T, UR_AC) # vL [vR], [vL] p.vR 455 | AR = np.reshape(AR, (D, d, D)) # vL p vR 456 | return AL, AR -------------------------------------------------------------------------------- /exercises_uniform_toycodes.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "20664216", 6 | "metadata": {}, 7 | "source": [ 8 | "# Uniform matrix product states in the thermodynamic limit - Exercises" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "facc4197", 14 | "metadata": {}, 15 | "source": [ 16 | "Closely following [1], we want to numerically investigate uniform matrix product states (uMPS) in the thermodynamic limit, importing the modules `f_umps.py`, `g_vumps.py`, `h_utdvp.py` and `i_uexcitations.py`. \n", 17 | "\n", 18 | "#### Part 1) Injectivity and canonical form in `f_umps.py`\n", 19 | "A uMPS $\\vert \\psi(A) \\rangle$ represents a translation invariant state of an infinite chain of $d$-dimensional quantum systems and is completely characterized by a single tensor $A \\in \\mathbb{C}^{D \\times d \\times D}$ of physical dimension $d$ and bond dimension $D$: \n", 20 | " $$ \\vert \\psi(A) \\rangle = \\sum_{\\{s\\}} \\langle v_L \\vert \\left(\\prod_{n \\in \\mathbb{Z}} A^{s_n}\\right) \\vert v_R \\rangle \\vert \\{s\\} \\rangle.$$\n", 21 | "A crucial condition for efficient computations is injectivity of $A$, meaning that counting algebraic multiplicities, the transfer matrix\n", 22 | " $$T_A = \\sum_{s=1}^d A^s \\otimes \\overline{A_s}$$\n", 23 | "has only one eigenvalue of magnitude (and value) 1 and the corresponding right and left eigenvectors are positive definite matrices [2, 3]. Non-injective tensors appear with measure zero. In 1.1) we want to show that a randomly drawn tensor is indeed injective. Within the gauge freedom $A^s \\rightarrow XA^sX^{-1}$ for invertible $X$, we can bring a uMPS in canonical form \n", 24 | " $$ \\vert \\psi(A) \\rangle = \\sum_{\\{s\\}} \\cdot\\cdot\\cdot A_L^{s_{n-2}}A_L^{s_{n-1}}A_C^{s_n}A_R^{s_{n+1}}A_R^{s_{n+2}} \\cdot\\cdot\\cdot \\vert \\{s\\} \\rangle,$$\n", 25 | "with left/right orthonormal tensors $A_L$/$A_R$ (injective with left/right leading eigenvector equal to identity matrix) and center site tensor $A_C = A_L C = C A_R$ for some center matrix $C$. These 4 tensors are the central attributes of the `UniformMPS` class. As a first physically meaningful and exactly representable uMPS, we consider the AKLT state and justify a few of its properties in 1.2). In 1.3) we make clear under which conditions a `UniformMPS` instance and a `SimpleMPS(bc=\"infinite\")` instance (implemented in `a_mps.py`) can be converted into each other.\n", 26 | "\n", 27 | "
\n", 28 | "\n", 29 | "We then investigate the variational power of the set of uMPS with a given bond dimension $D$, forming a manifold within the full Hilbert space. An element of the tangent space at point $\\vert \\psi(A) \\rangle$ in this manifold reads\n", 30 | " $$ \\vert \\psi(B; A) \\rangle = \\sum_n \\sum_{\\{s\\}} \\cdot\\cdot\\cdot A_L^{s_{n-2}}A_L^{s_{n-1}}B^{s_n}A_R^{s_{n+1}}A_R^{s_{n+2}} \\cdot\\cdot\\cdot \\vert \\{s\\} \\rangle,$$\n", 31 | "and we denote the projector onto the (orthogonal) tangent space with $P_A$. For Hamiltonians $H = \\sum_n h_{n, n+1}$ with nearest-neighbor interaction $h \\in \\mathbb{C}^{d^2 \\times d^2}$, we run the following algorithms:\n", 32 | "\n", 33 | "#### Part 2) Variational uniform matrix product states (VUMPS) in `g_vumps.py` \n", 34 | "The tangent space gradient reads \n", 35 | " $$P_A H \\vert \\psi(A) \\rangle = \\vert \\psi(G; A)\\rangle \\:\\:\\mathrm{with}\\:\\: G = H_{\\mathrm{eff},1}(A_C) - A_L H_{\\mathrm{eff},0}(C) = H_{\\mathrm{eff},1}(A_C) - H_{\\mathrm{eff},0}(C) A_R.$$ \n", 36 | "The variational ground state optimum (corresponding to $G=0$) in the uMPS manifold satisfies a) $A_C$ ground state of $H_{\\mathrm{eff},1}$, b) $C$ ground state of $H_{\\mathrm{eff},0}$, c) $A_C = A_L C = C A_R$. For this perform the following updates till convergence in gradient norm: \n", 37 | "a) $A_C$ $\\rightarrow$ ground state of $H_{\\mathrm{eff},1}$, b) $C$ $\\rightarrow$ ground state of $H_{\\mathrm{eff},0}$, c) $A_L$/$A_R$ from left/right polar decompositions of $A_C$ and $C$. [1]
\n", 38 | ">> Note that in contrast to iDMRG (implemented in `d_dmrg.py`), which successively grows the lattice by updated unit cells, VUMPS truly solves the variational problem in the sense of completely updating the state with each iteration and consequently keeping the translation invariance at any time. [4] \n", 39 | "\n", 40 | "#### Part 3) Uniform time dependent variational principle (uTDVP) in `h_utdvp.py`\n", 41 | "Approximately solves \n", 42 | " $$\\frac{d}{dt} \\vert \\psi(A) \\rangle = -i P_A H \\vert \\psi(A) \\rangle$$\n", 43 | "for a small time step $dt$ by a) $A_C \\rightarrow e^{-idtH_{\\mathrm{eff},1}}(A_C)$, b) $C \\rightarrow e^{-idtH_{\\mathrm{eff},0}}(C)$, c) $A_L$/$A_R$ from left/right polar decompositions of $A_C$ and $C$. [1]\n", 44 | "\n", 45 | "#### Part 4) Variational plane wave excitations in `i_uexcitations.py`\n", 46 | "On top of a uMPS ground state $\\vert \\psi(A) \\rangle$, we want to variationally find quasiparticle excitations in a plane wave superposition of the form\n", 47 | " $$ \\vert \\psi(p,X; A) \\rangle = \\sum_n e^{ipn} \\sum_{\\{s\\}} \\cdot\\cdot\\cdot A_L^{s_{n-2}}A_L^{s_{n-1}}(V_L^{s_n}X)A_R^{s_{n+1}}A_R^{s_{n+2}} \\cdot\\cdot\\cdot \\vert \\{s\\} \\rangle,$$\n", 48 | "where $p$ is the momentum, and $X$ the left-gauge parametrization of the tensor $B = V_L X$ ($V_L$ is orthogonal to $A_L$), perturbing the ground state around site $n$. With \n", 49 | " $$\\langle \\psi(p',X'; A) \\vert H \\vert \\psi(p,X; A) \\rangle = 2\\pi \\delta(p'-p)\\langle X' \\vert H_{\\mathrm{eff}}(p) \\vert X \\rangle,$$\n", 50 | "the optimization boils down to diagonalizing $H_{\\mathrm{eff}}(p)$ for a few lowest-lying eigenvalues. [1, 5]\n", 51 | "\n", 52 | "We benchmark our algorithms with the transverse field Ising (TFI) model, which can be diagonalized analytically in one dimension. [6]\n", 53 | "\n", 54 | "
\n", 55 | "\n", 56 | "__References__
\n", 57 | "\n", 58 | "[1] Vanderstraeten et al., Tangent-space methods for uniform matrix product states, 2019, https://arxiv.org/abs/1810.07006.
\n", 59 | "[2] Perez-Garcia et al., Matrix product state representations, 2007, https://arxiv.org/abs/quant-ph/0608197.
\n", 60 | "[3] Wolf, Quantum Channels and Operations - Guided Tour, 2012, https://mediatum.ub.tum.de/node?id=1701036.
\n", 61 | "[4] Zauner-Stauber et al., Variational optimization algorithms for uniform matrix product states, 2018, https://arxiv.org/abs/1701.07035.
\n", 62 | "[5] Haegeman et al., Variational matrix product ansatz for dispersion relations, 2012, https://arxiv.org/abs/1103.2286.
\n", 63 | "[6] Subir Sachdev, Quantum Phase Transitions, 2nd ed, Cambridge University Press, 2011." 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": null, 69 | "id": "2a6affc1", 70 | "metadata": {}, 71 | "outputs": [], 72 | "source": [ 73 | "import numpy as np\n", 74 | "import matplotlib.pyplot as plt" 75 | ] 76 | }, 77 | { 78 | "cell_type": "markdown", 79 | "id": "c1b9c9cf", 80 | "metadata": {}, 81 | "source": [ 82 | "## 1) `f_umps.py`: Random states and AKLT ground state" 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": null, 88 | "id": "e807aedc", 89 | "metadata": {}, 90 | "outputs": [], 91 | "source": [ 92 | "from tenpy_toycodes.f_umps import UniformMPS, TransferMatrix\n", 93 | "from tenpy_toycodes.a_mps import SimpleMPS" 94 | ] 95 | }, 96 | { 97 | "cell_type": "markdown", 98 | "id": "4e071fef", 99 | "metadata": {}, 100 | "source": [ 101 | "### 1.1) Transfer matrix and canonical form of random injective tensor" 102 | ] 103 | }, 104 | { 105 | "cell_type": "markdown", 106 | "id": "e0be6b6f", 107 | "metadata": {}, 108 | "source": [ 109 | "* Create a random tensor $A \\in \\mathbb{C}^{D \\times d \\times D}$ of physical dimension $d = 2$ and bond dimension $D = 4$." 110 | ] 111 | }, 112 | { 113 | "cell_type": "code", 114 | "execution_count": null, 115 | "id": "ecebc5d6", 116 | "metadata": {}, 117 | "outputs": [], 118 | "source": [] 119 | }, 120 | { 121 | "cell_type": "markdown", 122 | "id": "ae5eb45e", 123 | "metadata": {}, 124 | "source": [ 125 | "* Show that $A$ is injective, i.e. its `TransferMatrix` fulfills $T_A = \\vert R \\rangle \\langle L \\vert + \\mathcal{O}(\\vert \\lambda_2 \\vert)$ with $\\vert \\lambda_2 \\vert < 1$ and $R,L > 0$." 126 | ] 127 | }, 128 | { 129 | "cell_type": "code", 130 | "execution_count": null, 131 | "id": "b163994a", 132 | "metadata": {}, 133 | "outputs": [], 134 | "source": [] 135 | }, 136 | { 137 | "cell_type": "markdown", 138 | "id": "8e7a5f93", 139 | "metadata": {}, 140 | "source": [ 141 | "* Compute the canonical form of $A$, consisting of left/right orthonormal tensor $A_L / A_R$, center site tensor $A_C$, and center matrix $C$." 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": null, 147 | "id": "3e008d59", 148 | "metadata": {}, 149 | "outputs": [], 150 | "source": [] 151 | }, 152 | { 153 | "cell_type": "markdown", 154 | "id": "a7398c6d", 155 | "metadata": {}, 156 | "source": [ 157 | "* Show that $T_{A_L} = \\vert R \\rangle \\langle \\mathbb{1} \\vert + \\mathcal{O}(\\vert \\lambda_2 \\vert)$ with $\\vert \\lambda_2 \\vert < 1$ and $R > 0$." 158 | ] 159 | }, 160 | { 161 | "cell_type": "code", 162 | "execution_count": null, 163 | "id": "e951a918", 164 | "metadata": {}, 165 | "outputs": [], 166 | "source": [] 167 | }, 168 | { 169 | "cell_type": "markdown", 170 | "id": "714e5c0b", 171 | "metadata": {}, 172 | "source": [ 173 | "* Show that $T_{A_R} = \\vert \\mathbb{1} \\rangle \\langle L \\vert + \\mathcal{O}(\\vert \\lambda_2 \\vert)$ with $\\vert \\lambda_2 \\vert < 1$ and $L > 0$." 174 | ] 175 | }, 176 | { 177 | "cell_type": "code", 178 | "execution_count": null, 179 | "id": "84aa068a", 180 | "metadata": {}, 181 | "outputs": [], 182 | "source": [] 183 | }, 184 | { 185 | "cell_type": "markdown", 186 | "id": "3fb68aaf", 187 | "metadata": {}, 188 | "source": [ 189 | "* Show that $A_C = A_L C = C A_R$." 190 | ] 191 | }, 192 | { 193 | "cell_type": "code", 194 | "execution_count": null, 195 | "id": "d1f37696", 196 | "metadata": {}, 197 | "outputs": [], 198 | "source": [] 199 | }, 200 | { 201 | "cell_type": "markdown", 202 | "id": "53fe5435", 203 | "metadata": {}, 204 | "source": [ 205 | "* Directly create a random `UniformMPS` (for different tolerances) and test its canonical form." 206 | ] 207 | }, 208 | { 209 | "cell_type": "code", 210 | "execution_count": null, 211 | "id": "a06ed868", 212 | "metadata": {}, 213 | "outputs": [], 214 | "source": [] 215 | }, 216 | { 217 | "cell_type": "markdown", 218 | "id": "a81f992c", 219 | "metadata": {}, 220 | "source": [ 221 | "### 1.2) AKLT state" 222 | ] 223 | }, 224 | { 225 | "cell_type": "markdown", 226 | "id": "134b43b2", 227 | "metadata": {}, 228 | "source": [ 229 | "For the spin-1 matrices $S_x$, $S_y$ and $S_z$, we consider the two-site Hamiltonian matrix \n", 230 | "\n", 231 | "$$h = S_x \\otimes S_x + S_y \\otimes S_y + S_z \\otimes S_z + \\frac{1}{3}(S_x \\otimes S_x + S_y \\otimes S_y + S_z \\otimes S_z)^2 = 2P_{S=2}-2/3.$$\n", 232 | "\n", 233 | "The Hamiltonian for multiple sites then simply reads $H = \\sum_{n} h_{n, n+1}$. The eigenenergy is minimized (with a value of $-2/3$), if every bond has total spin $S = 0$ or $S = 1$. This is fulfilled by distributing spin-1/2 singlets between all neighbor-sites and projecting every site onto the $S = 1$ subspace. This is the AKLT state $\\vert \\psi_{\\mathrm{AKLT}} \\rangle$. A few lines derivation shows that it can be represented exactly by a uMPS $\\vert \\psi(A) \\rangle$ with left orthonormal tensor\n", 234 | "\n", 235 | "$$ A_L^{+1} = \\sqrt{\\frac{2}{3}}\\sigma^{+}, A_L^{0} = \\sqrt{\\frac{1}{3}}\\sigma^{z}, A_L^{-1} = -\\sqrt{\\frac{2}{3}}\\sigma^{-}. $$\n", 236 | "\n", 237 | "* Implement $h, h^2 \\in \\mathbb{C}^{3 \\times 3 \\times 3 \\times 3}$." 238 | ] 239 | }, 240 | { 241 | "cell_type": "code", 242 | "execution_count": null, 243 | "id": "b7999d07", 244 | "metadata": {}, 245 | "outputs": [], 246 | "source": [] 247 | }, 248 | { 249 | "cell_type": "markdown", 250 | "id": "b9317ef5", 251 | "metadata": {}, 252 | "source": [ 253 | "* Bring the above defined $A_L$ into canonical form and initialize $\\vert \\psi_{\\mathrm{AKLT}} \\rangle$ as a `UniformMPS`." 254 | ] 255 | }, 256 | { 257 | "cell_type": "code", 258 | "execution_count": null, 259 | "id": "c479d341", 260 | "metadata": {}, 261 | "outputs": [], 262 | "source": [] 263 | }, 264 | { 265 | "cell_type": "markdown", 266 | "id": "1b39fe00", 267 | "metadata": {}, 268 | "source": [ 269 | "Show the following properties of $\\vert \\psi_{\\mathrm{AKLT}} \\rangle$: \n", 270 | "* Ground state energy $e = \\langle h \\rangle = -2/3$, \n", 271 | "\n", 272 | "* Variance $\\langle h^2 \\rangle - \\langle h \\rangle^2 = 0$ (H is frustration free),\n", 273 | "\n", 274 | "* Entanglement entropy $S = \\ln(2) \\approx 0.6931471805599453$,\n", 275 | "\n", 276 | "* Correlation length $\\xi = -1/\\ln(\\vert \\lambda_2 \\vert) = -1/\\ln(1/3) = 1/\\ln 3 \\approx 0.9102392266268371$,\n", 277 | "\n", 278 | "* Connected correlation function $C(n) = \\langle S_z^0 S_z^n \\rangle - \\langle S_z \\rangle^2 \\rightarrow \\vert \\lambda_2 \\vert^{n-1}$." 279 | ] 280 | }, 281 | { 282 | "cell_type": "code", 283 | "execution_count": null, 284 | "id": "9f462402", 285 | "metadata": {}, 286 | "outputs": [], 287 | "source": [] 288 | }, 289 | { 290 | "cell_type": "markdown", 291 | "id": "b8a1c634", 292 | "metadata": {}, 293 | "source": [ 294 | "### 1.3) Conversions between uniform MPS and infinite MPS" 295 | ] 296 | }, 297 | { 298 | "cell_type": "markdown", 299 | "id": "e778228e", 300 | "metadata": {}, 301 | "source": [ 302 | "* Convert the `UniformMPS` (uMPS) $\\vert \\psi_{\\mathrm{AKLT}} \\rangle$ to a `SimpleMPS(bc=\"infinite\")` (iMPS) and recheck the values of $e$, $S$, $\\xi$." 303 | ] 304 | }, 305 | { 306 | "cell_type": "code", 307 | "execution_count": null, 308 | "id": "249303f7", 309 | "metadata": {}, 310 | "outputs": [], 311 | "source": [] 312 | }, 313 | { 314 | "cell_type": "markdown", 315 | "id": "976073bf", 316 | "metadata": {}, 317 | "source": [ 318 | "* Denote by $B \\in \\mathbb{C}^{2 \\times 3 \\times 2}$ the right canonical tensor of $\\vert \\psi_{\\mathrm{AKLT}} \\rangle$ and by $U \\in \\mathbb{C}^{2 \\times 2}$ a random unitary. Show that the iMPS $\\vert\\psi(B_1, B_2)\\rangle$ with $B_1 = BU$ and $B_2 = U^*B$ is translation invariant and can be converted to a uMPS. Check that this is still the AKLT state." 319 | ] 320 | }, 321 | { 322 | "cell_type": "code", 323 | "execution_count": null, 324 | "id": "a47a7ad6", 325 | "metadata": {}, 326 | "outputs": [], 327 | "source": [] 328 | }, 329 | { 330 | "cell_type": "markdown", 331 | "id": "507d3d45", 332 | "metadata": {}, 333 | "source": [ 334 | "* For random right canonical tensors $B_1 \\in \\mathbb{C}^{D \\times d \\times D}$ and $B_2 \\in \\mathbb{C}^{D \\times d \\times D}$, show that the iMPS $\\vert\\psi(B_1, B_2)\\rangle$ is in general not translation invariant and cannot be converted to a uMPS." 335 | ] 336 | }, 337 | { 338 | "cell_type": "code", 339 | "execution_count": null, 340 | "id": "d11ad991", 341 | "metadata": {}, 342 | "outputs": [], 343 | "source": [] 344 | }, 345 | { 346 | "cell_type": "markdown", 347 | "id": "51271c16", 348 | "metadata": {}, 349 | "source": [ 350 | "## Transverse field Ising model\n", 351 | "\n", 352 | "In the following we want to use uMPS methods to find the ground state, elementary excitations and time-evolved states of the transverse field Ising (TFI) model \n", 353 | "\n", 354 | "$$ H = -J \\sum_{n \\in \\mathbb{Z}} \\sigma^z_n \\sigma^z_{n+1} - g \\sum_{n \\in \\mathbb{Z}} \\sigma^x_n \\overset{J=1}{=} -\\sum_{n \\in \\mathbb{Z}} \\sigma^z_n \\sigma^z_{n+1} - g \\sum_{n \\in \\mathbb{Z}} \\sigma^x_n. $$\n", 355 | "\n", 356 | "$\\mathbb{Z}_2-\\mathrm{symmetry}$: $[H, U] = 0$ with $U = \\prod\\limits_n \\sigma^x_n$, local order parameter: magnetization density $\\langle \\sigma^z \\rangle$.
\n", 357 | "From connecting the two limits of the transverse field $g$ we expect the following quantum phase diagram:\n", 358 | "\n", 359 | "* Ferromagnetic phase $g < g_c$\n", 360 | "\t* two degenerate, symmetry broken ground states related by $U$ ($\\ket{...\\uparrow\\uparrow\\uparrow...}$ and $\\ket{...\\downarrow\\downarrow\\downarrow...}$ for $g = 0$)\n", 361 | "\t* $\\langle \\sigma^z \\rangle = \\pm m \\neq 0$ ($m = 1$ for $g = 0$)\n", 362 | "\t* elementary excitations: topological domain walls ($\\ket{...\\uparrow\\uparrow\\uparrow \\downarrow\\downarrow\\downarrow...}$ for $g = 0$)\n", 363 | "\n", 364 | "* Paramagnetic phase $g > g_c$\n", 365 | "\t* unique symmetric ground state ($\\ket{...\\rightarrow\\rightarrow\\rightarrow...}$ for $g \\to \\infty$)\n", 366 | "\t* $\\langle \\sigma^z \\rangle = 0$\n", 367 | "\t* elementary excitations: single spin flips ($\\ket{...\\rightarrow\\rightarrow \\leftarrow \\rightarrow\\rightarrow...}$ for $g \\to \\infty$)\n", 368 | "\n", 369 | "* Quantum phase transition at $g_c$\n", 370 | "\n", 371 | "\n", 372 | "By performing Jordan-Wigner, Fourier and Bogoliubov transformations, the TFI model (with PBC) can be diagonalized analytically. The Hamiltonian in terms of fermionic creation and annihilation operators $\\gamma_{p}^{\\dagger}$ and $\\gamma_{p}$ reads\n", 373 | "\n", 374 | "$$H = \\sum_{p} \\epsilon_p \\gamma_{p}^{\\dagger}\\gamma_{p} + E_0.$$\n", 375 | "\n", 376 | "Single particle excitation energy: $\\epsilon_p = 2 \\sqrt{1 - 2g\\cos(p) + g^2}$ $\\Rightarrow$ energy gap closes at quantum critical point $g_c = 1$.\n", 377 | "\n", 378 | "Ground state energy: $E_0 = -\\sum_{p} \\epsilon_p/2$ $\\Rightarrow$ ground state energy density in the thermodynamic limit: $e_0 = - \\frac{1}{2\\pi} \\int_{-\\pi}^{\\pi} \\epsilon_p/2$. [2]" 379 | ] 380 | }, 381 | { 382 | "cell_type": "markdown", 383 | "id": "8d1c962e", 384 | "metadata": {}, 385 | "source": [ 386 | "## 2) `g_vumps.py`: Variational ground state search" 387 | ] 388 | }, 389 | { 390 | "cell_type": "code", 391 | "execution_count": null, 392 | "id": "8a658a7a", 393 | "metadata": {}, 394 | "outputs": [], 395 | "source": [ 396 | "from tenpy_toycodes.b_model import TFIModel\n", 397 | "from tenpy_toycodes.tfi_exact import infinite_gs_energy\n", 398 | "\n", 399 | "from tenpy_toycodes.f_umps import UniformMPS\n", 400 | "from tenpy_toycodes.g_vumps import vumps_algorithm" 401 | ] 402 | }, 403 | { 404 | "cell_type": "markdown", 405 | "id": "4bfe5f7d", 406 | "metadata": {}, 407 | "source": [ 408 | "For the transverse field Ising model in the thermodynamic limit, we want to find the ground state using the `vumps_algorithm`. \n", 409 | "\n", 410 | "* Investigate the convergence of the ground state energy density $e_0$ with the bond dimension $D$ by comparison to the exact value $e_{0, \\mathrm{exact}}$ from `tfi_exact.py`. Consider both the paramagnetic and ferromagnetic quantum phase." 411 | ] 412 | }, 413 | { 414 | "cell_type": "code", 415 | "execution_count": null, 416 | "id": "27ef125e", 417 | "metadata": {}, 418 | "outputs": [], 419 | "source": [] 420 | }, 421 | { 422 | "cell_type": "markdown", 423 | "id": "61775480", 424 | "metadata": {}, 425 | "source": [ 426 | "* Plot the ground state magnetization $m = \\vert \\langle \\sigma^z \\rangle \\vert$ against the transverse field $g$ to locate the quantum phase transition at $g_c = 1$." 427 | ] 428 | }, 429 | { 430 | "cell_type": "code", 431 | "execution_count": null, 432 | "id": "3c8fdfa6", 433 | "metadata": {}, 434 | "outputs": [], 435 | "source": [] 436 | }, 437 | { 438 | "cell_type": "markdown", 439 | "id": "0e3a7014", 440 | "metadata": {}, 441 | "source": [ 442 | "## 3) `h_utdvp.py`: Global quench dynamics" 443 | ] 444 | }, 445 | { 446 | "cell_type": "code", 447 | "execution_count": null, 448 | "id": "4302f91a", 449 | "metadata": {}, 450 | "outputs": [], 451 | "source": [ 452 | "from tenpy_toycodes.b_model import TFIModel\n", 453 | "from tenpy_toycodes.c_tebd import example_TEBD_gs_tf_ising_infinite, calc_U_bonds, update_bond\n", 454 | "\n", 455 | "from tenpy_toycodes.f_umps import UniformMPS\n", 456 | "from tenpy_toycodes.g_vumps import vumps_algorithm\n", 457 | "from tenpy_toycodes.h_utdvp import utdvp_algorithm" 458 | ] 459 | }, 460 | { 461 | "cell_type": "markdown", 462 | "id": "54918683", 463 | "metadata": {}, 464 | "source": [ 465 | "Consider the following global quench dynamics for the TFI model: the ground state for a value $g_1$ of the transverse field is time-evolved according to a different value $g_2$,\n", 466 | "\n", 467 | "$$ \\ket{\\psi(t)} = e^{-it H(g_2)} \\ket{\\psi_0(g_1)}. $$\n", 468 | "\n", 469 | "We want to implement this time evolution using the `utdvp_algorithm`. For benchmark we use iTEBD. The `run_TEBD` function in `c_tebd.py` relies on a first order Trotter decomposition. We want to improve this to second order:\n", 470 | "\n", 471 | "$$ H = H_{\\mathrm{odd}} + H_{\\mathrm{even}} \\Rightarrow U(dt) = e^{-i dt H_{\\mathrm{odd}}/2} e^{-i dt H_{\\mathrm{even}}} e^{-i dt H_{\\mathrm{odd}}/2} + \\mathcal{O}(dt^3).$$\n", 472 | "\n", 473 | "* Implement this improved scheme in a function `run_TEBD_second_order`. \n", 474 | "\n", 475 | "* Then write a function `itebd_global_quench` performing the global quench dynamics described above. Converge the method in bond dimension $D$ and time step $dt$." 476 | ] 477 | }, 478 | { 479 | "cell_type": "code", 480 | "execution_count": null, 481 | "id": "f11ecbaa", 482 | "metadata": {}, 483 | "outputs": [], 484 | "source": [] 485 | }, 486 | { 487 | "cell_type": "markdown", 488 | "id": "ca455bc2", 489 | "metadata": {}, 490 | "source": [ 491 | "* Analogous to `itebd_global_quench`, write a function `utdvp_global_quench` and make sure they give the same dynamics." 492 | ] 493 | }, 494 | { 495 | "cell_type": "code", 496 | "execution_count": null, 497 | "id": "1a931017", 498 | "metadata": {}, 499 | "outputs": [], 500 | "source": [] 501 | }, 502 | { 503 | "cell_type": "markdown", 504 | "id": "2fa12d56", 505 | "metadata": {}, 506 | "source": [ 507 | "## 4) `i_uexcitations.py`: Variational plane wave excitations " 508 | ] 509 | }, 510 | { 511 | "cell_type": "code", 512 | "execution_count": null, 513 | "id": "52f91fbc", 514 | "metadata": {}, 515 | "outputs": [], 516 | "source": [ 517 | "from tenpy_toycodes.tfi_exact import infinite_gs_energy, infinite_excitation_dispersion\n", 518 | "from tenpy_toycodes.b_model import TFIModel\n", 519 | "\n", 520 | "from tenpy_toycodes.f_umps import UniformMPS\n", 521 | "from tenpy_toycodes.g_vumps import vumps_algorithm\n", 522 | "from tenpy_toycodes.i_uexcitations import VariationalPlaneWaveExcitationEngine" 523 | ] 524 | }, 525 | { 526 | "cell_type": "markdown", 527 | "id": "35223575", 528 | "metadata": {}, 529 | "source": [ 530 | "By running the `VariationalPlaneWaveExcitationEngine`, we want to compute the single particle excitations $\\epsilon_p = 2 \\sqrt{1 - 2g\\cos(p) + g^2}$ on top of the TFI ground state.\n", 531 | "\n", 532 | "* For this write a function `get_tfi_spectrum`, returning both the variational dispersion relation and the exact one from `tfi_exact.py`. Actively target topological domain wall excitations for transverse field values $g < 1$." 533 | ] 534 | }, 535 | { 536 | "cell_type": "code", 537 | "execution_count": null, 538 | "id": "c636f31e", 539 | "metadata": {}, 540 | "outputs": [], 541 | "source": [] 542 | } 543 | ], 544 | "metadata": { 545 | "kernelspec": { 546 | "display_name": "Python 3", 547 | "language": "python", 548 | "name": "python3" 549 | }, 550 | "language_info": { 551 | "codemirror_mode": { 552 | "name": "ipython", 553 | "version": 3 554 | }, 555 | "file_extension": ".py", 556 | "mimetype": "text/x-python", 557 | "name": "python", 558 | "nbconvert_exporter": "python", 559 | "pygments_lexer": "ipython3", 560 | "version": "3.12.7" 561 | } 562 | }, 563 | "nbformat": 4, 564 | "nbformat_minor": 5 565 | } 566 | --------------------------------------------------------------------------------