def cXY_local_terms(nr_sites, gamma): r"""Local terms of the cyclic XY model (MPOs) :param nr_sites: Number of spin one-half sites :param gamma: Asymmetry parameter :returns: List :code:`terms` of length :code:`nr_sites` (MPOs) The term :code:`terms[i]` acts on spins :code:`(i, i + 1)` and spin :code:`nr_sites` is the same as the first spin. The Hamiltonian of the cyclic XY model is given by [:any:`LSM61 <LSM61>`, Eq. 2.1]: .. math:: H_\gamma = \sum_{i=1}^{N} (1+\gamma) S^x_i S^x_{i+1} + (1-\gamma) S^y_i S^y_{i+1} with :math:`S^j_{N+1} = S^j_{1}`. The function :func:`cXY_E0` returns the exact ground state energy of this Hamiltonian. """ local = ((1 + gamma) * 0.25 * mp.chain([mpo_X, mpo_X]) + (1 - gamma) * 0.25 * mp.chain([mpo_Y, mpo_Y])) return (local, ) * nr_sites
def cXY_local_terms(nr_sites, gamma): r"""Local terms of the cyclic XY model (MPOs) :param nr_sites: Number of spin one-half sites :param gamma: Asymmetry parameter :returns: List :code:`terms` of length :code:`nr_sites` (MPOs) The term :code:`terms[i]` acts on spins :code:`(i, i + 1)` and spin :code:`nr_sites` is the same as the first spin. The Hamiltonian of the cyclic XY model is given by [:any:`LSM61 <LSM61>`, Eq. 2.1]: .. math:: H_\gamma = \sum_{i=1}^{N} (1+\gamma) S^x_i S^x_{i+1} + (1-\gamma) S^y_i S^y_{i+1} with :math:`S^j_{N+1} = S^j_{1}`. The function :func:`cXY_E0` returns the exact ground state energy of this Hamiltonian. """ local = ((1 + gamma) * 0.25 * mp.chain([mpo_X, mpo_X]) + (1 - gamma) * 0.25 * mp.chain([mpo_Y, mpo_Y])) return (local,) * nr_sites
def next(self): # generate a random unitary and convert to MPO mpu = mp.MPArray.from_array_global(random_U( self.max_size, num=1).reshape([2] * (self.max_size * 2)), ndims=2) # number of subsystems we need to appl subsys = int(self.N // 2) - self.max_size # iterate if still have remaining subsystems while subsys > 0: # generate another unitary dim = min([subsys, self.max_size]) mpo_rand = mp.MPArray.from_array_global(random_U( dim, num=1).reshape([2] * (dim * 2)), ndims=2) # chain mpu = mp.chain([mpu, mpo_rand]) # compress matrix product mpu.compress("svd", relerr=relerr) # remove computed subsystem dimensions from count subsys -= dim # chain with identity MPO mpu = mp.chain([mpu, mp.chain([mpo_dict["id"]] * (self.N // 2))]) # perform in-place compression on output operator mpu.compress("svd", relerr=self.relerr) return mpu
def _u_list_to_mpo_even(dims, u_even, compr): """ Transforms list of matrices on even-odd sites to MPO acting on full state. So the list of u_even :math:`\\{u_{j,j+1} : j \\text{ even}\\}`, which are ``numpy.ndarrays``, is transformed into :math:`\\bigotimes_j u_{j,j+1} : j \\text{ even}` of the type ``mpnum.MPArray``. Args: dims (list): List of dimensions of each site u_even (list): List of time evolution operators acting on even adjacent sites compr (dict): Parameters for the compression which is executed on every MPA during the calculations, except for the Trotter calculation where trotter_compr is used Returns: mpnum.MPArray: The MPO for the full state acting on even-odd adjacent sites """ if len(dims) % 2 == 0: last_h = u_even[-1] u_even = u_even[:-1] even = mp.chain( matrix_to_mpo(u, [[dims[2 * i + 1]] * 2, [dims[2 * i + 2]] * 2], compr) for i, u in enumerate(u_even[1::])) even = mp.chain([matrix_to_mpo(u_even[0], [[dims[0]] * 2], compr), even]) if len(dims) % 2 == 0: even = mp.chain([even, matrix_to_mpo(last_h, [[dims[-1]] * 2], compr)]) return even
def _u_list_to_mpo_even(dims, u_even, compr): """ Transforms list of matrices on odd-even sites to MPOs acting on full state .. todo:: Give explicit form of the final MPO (tensor product of input matrices) Args: dims (list): List of dimensions of each site u_even (list): List of time evolution operators acting on even adjacent sites compr (dict): Parameters for the compression which is executed on every MPA during the calculations, except for the Trotter calculation where trotter_compr is used Returns: mpnum.MPArray: The MPO for the full state acting on even-odd adjacent sites """ if len(dims) % 2 == 0: last_h = u_even[-1] u_even = u_even[:-1] even = mp.chain( matrix_to_mpo(u, [[dims[2 * i + 1]] * 2, [dims[2 * i + 2]] * 2], compr) for i, u in enumerate(u_even[1::])) even = mp.chain([matrix_to_mpo(u_even[0], [[dims[0]] * 2], compr), even]) if len(dims) % 2 == 0: even = mp.chain([even, matrix_to_mpo(last_h, [[dims[-1]] * 2], compr)]) return even
def _u_list_to_mpo_odd(dims, u_odd, compr): """ Transforms list of matrices on odd-even sites to MPOs acting on full stateself. .. todo:: Give explicit form of the final MPO (tensor product of input matrices) Args: dims (list): List of dimensions of each site u_odd (list): List of time evolution operators acting on odd adjacent sites compr (dict): Parameters for the compression which is executed on every MPA during the calculations, except for the Trotter calculation where trotter_compr is used Returns: mpnum.MPArray: The MPO for the full state acting on odd-even adjacent sites """ # if len(dims) % 2 == 1: last_h = u_odd[-1] u_odd = u_odd[:-1] # build tensor product of local propagators after transforming the matrices # to MPOs odd = mp.chain( matrix_to_mpo(u, [[dims[2 * i]] * 2, [dims[2 * i + 1]] * 2], compr) for i, u in enumerate(u_odd)) if len(dims) % 2 == 1: odd = mp.chain([odd, matrix_to_mpo(last_h, [[dims[-1]] * 2], compr)]) return odd
def corr(self, site1, site2, a, b): #making two points observable in the form of mpo left = [mp.MPArray.from_kron([idm] * (site1 - 1))] if (site1 - 1) > 0 else [] right = [mp.MPArray.from_kron([idm] * (self.sites - site2))] \ if (self.sites - site2) > 0 else [] mid = [mp.MPArray.from_kron([idm] * (site2 - site1 - 1))] \ if (site2 - site1 - 1) > 0 else [] mpo = mp.chain(left + [mp.MPArray.from_array_global(a)] + mid + \ [mp.MPArray.from_array_global(b)] + right) if site1 != site2 else\ mp.chain(left + [mp.MPArray.from_array_global(a.dot(b))]\ + right) #returning matrix element return mp.dot(mp.dot(self.psi.conj(), mpo), self.psi).to_array()
def _build_odd_propagator(self, hi_list, tau, idL): """ Builds propagator for odd bonds (even i in hi): e^(-j*H_{odd}*\tau) For an odd number of sites we append an identity at the end. Even/odd assumes indices start at 1, so we start with the first hi (python index 0) :param hi_list: List/Tuple of all terms in the Hamiltonian H = sum_i hi, where hi is local to one bond :param tau: timestep for propagator :param idL: Identity mpo for the last site :return: e^(-j*H_{odd}*\tau) as MPArray (mpo) """ odd_generator = self._generate_bond_propagator_mpos(hi_list, tau, 0) if self.L % 2 == 0: return mp.chain(odd_generator) else: # Odd number of sites -> need identity at last site! return mp.chain(chain(odd_generator, [idL]))
def bell_gen(N=2): """ Generates the matrix product state of an N qubit Bell state |psi^+>. Nice bit of code. """ # first generate an N qubit state in |0>^\otimes N mps = mp.MPArray.from_kron([np.array([1, 0])] * N) # generate entanglement operator in MPO form hadamard_mpo = mporep(mpo_dict["h"], mpo_dict["id"], reps=N - 1) cx_mpo = mp.chain([mpo_dict["id"]] * N) for i in range(0, N - 1): # construct selector for iteration selector = [0] * (N - 1) selector[i] = 1 # construct CX stage cx_mpo_stage = mpojob([mpo_dict["id"], mpo_dict["cx"]], selector) # add to cx sequence cx_mpo = mp.dot(cx_mpo_stage, cx_mpo) # define entangling operation in MPO form entangle_op = mp.dot(cx_mpo, hadamard_mpo) # compress to minimise memory overhead (TODO: must add overlap check) entangle_op.compress("svd", relerr=1e-6) # compute Bell state in MPS form bell = mp.dot(entangle_op, mps) return bell
def _local_sum(dims, hi_mpos, compression_relerr=1e-15): """ Embeds the sum of bond local terms (nearest neighbor coupling operators) on a chain as MPArrays :param dims: Dimensions of the sites of the chain (axis 0 legs of mpnum shape) as tuple or list :param hi_mpos: Two-site operators represented by MPArrays, which are to be embedded on a chain :param compression_relerr: If not None, the result of the sum is compressed using svd compression for the specified relative error :return: MPArray of hi_mpos embedded on a chain of shape *shape* """ nof_sites = len(dims) # Nearest neighbor operators occupy 2 sites each op_width = 2 local_terms = [] for startpos, hi_mpo in enumerate(hi_mpos): left = [mp.factory.eye(startpos, dims[:startpos]) ] if startpos > 0 else [] right = [mp.factory.eye(nof_sites - startpos - op_width, dims[startpos+op_width:])] \ if nof_sites - startpos - op_width > 0 else [] h_at_startpos = mp.chain(left + [hi_mpo] + right) local_terms.append(h_at_startpos) H = local_terms[0] for local_term in local_terms[1:]: H += local_term if compression_relerr is not None: H.compress(method='svd', relerr=1e-15) return H
def get_spin_boson_finiteT_chain_initial_state(theta, beta, h_site, h_bond, bath_local_dim, nof_coefficients, mpa_type='pmps', nof_steps=None, state_compression_kwargs=None, op_compression_kwargs=None, second_order_trotter=False, psi_0_compression_kwargs=None, residual=True, force_pmps_evolution=True, verbose=True): """ Computes the initial state for the finite temperature spin_boson model in chain geometry. The bath state is computed via imaginary time evolution. :param theta: Spin parameter for psi_0 = cos(theta) |1> + sin(theta) |0> :param beta: Inverse temperature of the bath :param h_site: Bath local Hamiltonian list :param h_bond: Bath nearest neighbor coupling Hamiltonian list :param bath_local_dim: Local dimension of the bath :param nof_coefficients: Number of bath sites :param mpa_type: MPS type of the chain (mps, mpo, pmps) :param nof_steps: Number of steps for the imaginary time evolution :param state_compression_kwargs: Keyword args for the imaginary time evolution compression :param op_compression_kwargs: Keyword args for the imaginary time evolution operator pre-compression :param second_order_trotter: Set True for second order trotter based imaginary time evolution :param psi_0_compression_kwargs: Keyword args for the imaginary time evolution initial state compression :param residual: Set True to compute List of populations in the highest energy state of each bath mode. :param force_pmps_evolution: Set True to always use pmps for the imaginary time evolution :param verbose: Set true to make imaginary time evolution verbose :return: Initial state of system and bath as mps, mpo or pmps, info dict """ assert mpa_type == 'mpo' or mpa_type == 'pmps' if nof_steps is None: nof_steps = int(beta*100) t0_wall = time.clock() t0_proc = time.perf_counter() if isinstance(bath_local_dim, int): dims = [bath_local_dim] * nof_coefficients else: raise AssertionError('Unsupported data type for fixed_dim') psi_0, info = tmps.chain.thermal.from_hamiltonian(beta, mpa_type, h_site, h_bond, nof_steps=nof_steps, state_compression_kwargs=state_compression_kwargs, op_compression_kwargs=op_compression_kwargs, second_order_trotter=second_order_trotter, psi_0_compression_kwargs=psi_0_compression_kwargs, force_pmps_evolution=force_pmps_evolution, verbose=verbose) tf_proc = time.perf_counter() - t0_proc tf_wall = time.clock() - t0_wall info['walltime'] = tf_wall info['cpu_time'] = tf_proc info['bath_dims'] = dims if residual: res = _compute_finiteT_chain_residual(psi_0, mpa_type, dims) max_res = np.max(res) info['res'] = res info['max_res'] = max_res else: info['res'] = None info['max_res'] = None print('Finite T ground state residual ', info['res']) print('Finite T ground state max. residual: ', info['max_res']) sys_psi_0 = get_spin_initial_state(theta, mpa_type=mpa_type) return mp.chain([sys_psi_0, psi_0]), info
def test_mppovm_embed_expectation( nr_sites, local_dim, rank, startsite, width, rgen): if hasattr(local_dim, '__iter__'): local_dim2 = local_dim else: local_dim2 = [local_dim] * nr_sites local_dim2 = list(zip(local_dim2, local_dim2)) # Create a local POVM `red_povm`, embed it onto a larger chain # (`full_povm`), and go back to the reduced POVM. red_povm = mp.chain( mp.povm.MPPovm.from_local_povm(mp.povm.pauli_povm(d), 1) for d, _ in local_dim2[startsite:startsite + width] ) full_povm = red_povm.embed(nr_sites, startsite, local_dim) axes = [(1, 2) if i < startsite or i >= startsite + width else None for i in range(nr_sites)] red_povm2 = mp.partialtrace(full_povm, axes, mp.MPArray) red_povm2 = mp.prune(red_povm2, singletons=True) red_povm2 /= np.prod([d for i, (d, _) in enumerate(local_dim2) if i < startsite or i >= startsite + width]) assert_almost_equal(mp.normdist(red_povm, red_povm2), 0.0) # Test with an arbitrary random MPO instead of an MPDO mpo = mp.factory.random_mpa(nr_sites, local_dim2, rank, rgen, dtype=np.complex_, normalized=True) mpo_red = next(mp.reductions_mpo(mpo, width, startsites=[startsite])) ept = mp.prune(full_povm.pmf(mpo, 'mpdo'), singletons=True).to_array() ept_red = red_povm.pmf(mpo_red, 'mpdo').to_array() assert_array_almost_equal(ept, ept_red)
def test_mppovm_sample( method, n_samples, nr_sites, startsite, local_dim, rgen): """Check that probability estimates from samples are reasonable accurate""" rank = 3 eps = 1e-10 mps = factory.random_mps(nr_sites, local_dim, rank, rgen) mps.canonicalize() local_x = povm.x_povm(local_dim) local_y = povm.y_povm(local_dim) xx = povm.MPPovm.from_local_povm(local_x, 2) y = povm.MPPovm.from_local_povm(local_y, 1) mpp = mp.chain([xx, povm.MPPovm.eye([local_dim]), y]) \ .embed(nr_sites, startsite, local_dim) pmf_exact = mpp.pmf_as_array(mps, 'mps', eps) if n_samples > 100: n_gr = 5 elif local_dim == 3: n_gr = 2 else: n_gr = 3 samples = mpp.sample(rgen, mps, n_samples, method, n_gr, 'mps', eps=eps) pmf_est = mpp.est_pmf(samples) assert abs(pmf_est.sum() - 1.0) <= eps assert abs(pmf_exact - pmf_est).max() <= 3 / n_samples**0.5
def get_spin_boson_0T_chain_initial_state(theta, bath_local_dim, nof_coefficients): """ Returns the full initial state (vacuum state) for 0T chain with nof_coefficients sites and a local dimension of bath_local_dim. """ sys_psi_0 = get_spin_initial_state(theta) bath_psi_0 = broadcast_number_ground_state(bath_local_dim, nof_coefficients) return mp.chain([sys_psi_0, bath_psi_0])
def test_mppovm_est_pmf_from( method, n_samples, nr_sites, startsite, local_dim, rgen): """Check that probability estimates from samples are reasonable accurate""" rank = 3 eps = 1e-10 mps = factory.random_mps(nr_sites, local_dim, rank, rgen) mps.canonicalize() lx = povm.x_povm(local_dim) ly = povm.y_povm(local_dim) lp = povm.pauli_povm(local_dim) x = povm.MPPovm.from_local_povm(lx, 1) y = povm.MPPovm.from_local_povm(ly, 1) pauli = povm.MPPovm.from_local_povm(lp, 1) xy = mp.chain((x, y)) mpp = mp.chain((xy,) * (nr_sites // 2)) if (nr_sites % 2) == 1: mpp = mp.chain((mpp, x)) small_mpp = mp.chain((pauli, povm.MPPovm.eye([local_dim]), pauli, pauli)) \ .embed(nr_sites, startsite, local_dim) x_given = np.arange(len(lp)) < len(lx) y_given = ((np.arange(len(lp)) >= len(lx)) & (np.arange(len(lp)) < len(lx) + len(ly))) given_sites = [x_given if ((startsite + i) % 2) == 0 else y_given for i in (0, 2, 3)] given_expected = np.einsum('i, j, k -> ijk', *given_sites) pmf_exact = small_mpp.pmf_as_array(mps, 'mps', eps) if n_samples > 100: n_gr = 5 elif local_dim == 3: n_gr = 2 else: n_gr = 3 samples = mpp.sample(rgen, mps, n_samples, method, n_gr, 'mps', eps=eps) est_pmf, est_n_samples = small_mpp.est_pmf_from(mpp, samples) # In this case, we use all the samples from `mpp`. assert est_n_samples == n_samples given = ~np.isnan(est_pmf) assert (given == given_expected).all() assert abs(pmf_exact[given].sum() - est_pmf[given].sum()) <= eps assert abs(pmf_exact[given] - est_pmf[given]).max() <= 1 / n_samples**0.5
def _build_even_propagator(self, hi_list, tau, id0, idL): """ Builds propagator for even bonds (even i in hi): e^(-j*H_{even}*tau) For an even number of sites we append identities at the start and at the end. For an odd number of sites we append an identity at the start. Even/odd assumes indices start at 1, so we start with the second hi (python index 1) :param hi_list: List/Tuple of all terms in the Hamiltonian H = sum_i hi, where hi is local to one bond :param tau: timestep for propagator :param id0: identity mpo for the first site :param idL: identity mpo for the last site :return: e^(-j*H_{even}*\tau) as MPArray (mpo) """ even_generator = self._generate_bond_propagator_mpos(hi_list, tau, 1) if self.L % 2 == 0: # Even number of sites -> need identity at last site! return mp.chain(chain([id0], even_generator, [idL])) else: return mp.chain(chain([id0], even_generator))
def get_spin_boson_finiteT_star_initial_state(theta, beta, system_index, xi, mpa_type='pmps', fixed_dim=None, high_energy_pop=1e-20, sitewise=False, residual=True): """ Computes the initial state for the finite temperature spin_boson model in star geometry. The bath state is computed via imaginary time evolution. :param theta: Spin parameter for psi_0 = cos(theta) |1> + sin(theta) |0> :param beta: Inverse temperature of the bath :param system_index: Impurity position in the auxiliary chain :param xi: Star geometry bath energies :param mpa_type: Type: mps, mpo or pmps of the initial state :param fixed_dim: Uses this fixed dimension for the star evolution :param high_energy_pop: Chooses local dimension, such that the population in the highest energy of each bath mode stays below this threshold :param sitewise: If set False the local dimension is chosen uniformly for all sites to be the highest local dimension from the high_energy_pop calculation. :param residual: Computes list of populations in the highest energy state of each mode :return: Initial state of system and bath as mps, mpo or pmps, info dict """ assert mpa_type == 'mpo' or mpa_type == 'pmps' t0_wall = time.clock() t0_proc = time.perf_counter() dims = get_star_local_dims(beta, xi, fixed_dim=fixed_dim, high_energy_pop=high_energy_pop, sitewise=sitewise) ops = [xi[i] * np.arange(dim) for i, dim in enumerate(dims)] if system_index > 0: left_state = get_thermal_state(beta, mpa_type, ops[:system_index], to_cform=None) right_state = get_thermal_state(beta, mpa_type, ops[system_index:], to_cform=None) else: left_state = None right_state = get_thermal_state(beta, mpa_type, ops, to_cform=None) tf_proc = time.perf_counter() - t0_proc tf_wall = time.clock() - t0_wall info = dict() info['walltime'] = tf_wall info['cpu_time'] = tf_proc info['bath_dims'] = dims if residual: info['res'] = _compute_finite_T_star_residual(beta, xi, dims) info['max_res'] = np.max(info['res']) else: info['res'] = None info['max_res'] = None sys_psi_0 = get_spin_initial_state(theta, mpa_type=mpa_type) return mp.chain([left_state, sys_psi_0, right_state]) if left_state is not None else \ mp.chain([sys_psi_0, right_state]), info
def one_site_mean(self, site, a): #making one point observable in the form of mpo left = [mp.MPArray.from_kron([idm] * (site - 1))] if (site - 1) > 0 else [] right = [mp.MPArray.from_kron([idm] * (self.sites - site))] \ if (self.sites - site) > 0 else [] mpo = mp.chain(left + [mp.MPArray.from_array_global(a)] + right) #returning matrix element return mp.dot(mp.dot(self.psi.conj(), mpo), self.psi).to_array()
def test_mppovm_match_elems_bell(eps=1e-10): """Test match_elems() for a non-product MPPovm""" # Four Bell states (basis: |00>, |01>, |10>, |11>) bell = np.array(( [(1/3)**0.5, 0, 0, (1/3)**0.5], # (0, 0): |00> + |11> (proj. weight 1/3) [0, 1, 1, 0], # (0, 1): |01> + |10> (proj. weight 1) [(2/3)**0.5, 0, 0, -(2/3)**0.5], # (0, 2): |00> - |11> (proj. weight 2/3) [0, 1, -1, 0], # (1, 0): |01> - |10> (proj. weight 1) [(1/3)**0.5, 0, 0, -(1/3)**0.5], # (1, 1): |00> - |11> (proj. weight 1/3) [(2/3)**0.5, 0, 0, (2/3)**0.5], # (1, 2): |00> + |11> (proj. weight 2/3) )) / 2**0.5 bell_proj = np.einsum('ij, ik -> ijk', bell, bell.conj()) bell_proj = bell_proj.reshape((2, 3) + (2,) * 4) # Four Bell states and two product states vecs = np.array(( [0, 1, -1, 0], # (0, 0): |01> - |10> (proj. weight 0.5) [0, 2**0.5, 0, 0], # (0, 1): |01> (proj. weight 0.5) [0, 0, 2**0.5, 0], # (1, 0): |10> (proj. weight 0.5) [2**0.5, 0, 0, -2**0.5], # (1, 1): |00> - |11> (proj. weight 1) [0, 1, 1, 0], # (2, 0): |01> + |10> (proj. weight 0.5) [2**0.5, 0, 0, 2**0.5], # (2, 1): |00> + |11> (proj. weight 1) )) / 2 proj = np.einsum('ij, ik -> ijk', vecs, vecs.conj()) proj = proj.reshape((3,) + (2,) * 5) # Big POVM: The four Bell states (repeated two times) big = povm.MPPovm.from_array_global(bell_proj, ndims=3) big = mp.chain([big, big]) # Small POVM: Two of the Bell states and four product states (on # the last two sites) small = povm.MPPovm.from_array_global(proj, ndims=3).embed(4, 2, 2) # Check that the POVM is normalized: elements must sum to the identity for mppovm in big, small: element_sum = sum(x.to_array_global().reshape(16, 16) for x in mppovm.elements) assert_array_almost_equal(element_sum, np.eye(16)) match, prefactors = small.match_elems(big, eps=eps) # Verify the correspondence which can be read off above want = np.zeros((3, 2, 2, 3), dtype=bool) want[0, 0, 1, 0] = True # |01> - |10> want[2, 0, 0, 1] = True # |01> + |10> want[1, 1, 0, 2] = True # |00> - |11> want[1, 1, 1, 1] = True # |00> - |11> want[2, 1, 0, 0] = True # |00> + |11> want[2, 1, 1, 2] = True # |00> + |11> assert (match == want).all() assert abs(prefactors[0, 0, 1, 0] - 0.5) <= eps assert abs(prefactors[2, 0, 0, 1] - 0.5) <= eps assert abs(prefactors[1, 1, 0, 2] - 1.5) <= eps assert abs(prefactors[1, 1, 1, 1] - 3) <= eps assert abs(prefactors[2, 1, 0, 0] - 3) <= eps assert abs(prefactors[2, 1, 1, 2] - 1.5) <= eps assert np.isnan(prefactors[~match]).all()
def _system_site_mpo(self, h, tau): """ :param h: System site local operator :param tau: timestep :return: trotterized exponential in mpo form for the system site and the ancilla """ if h is None: return mp.chain([ mp.eye(1, self.shape[self.system_index][0]), mp.eye(1, self.shape[self.system_index][1]) ]) propagator = expm(-1j * tau * h) propagator = propagator.reshape(self.shape[self.system_index][0], self.shape[self.system_index][0]) # Add identity to ancilla bond mpo = mp.chain([ mp.MPArray.from_array_global(propagator, ndims=2), mp.eye(1, self.shape[self.system_index][1]) ]) return self._compress_mpo(mpo)
def get_boson_boson_0T_chain_initial_state(alpha, nof_coefficients, cutoff_dim): """ Initial state for the Boson-Boson model in chain geometry (see Sec. 4.4.3 of the thesis) :param alpha: accuracy alpha for the impurity coherent state :param nof_coefficients: Number of bath sites :param cutoff_dim: Local dimension of the system and impurity :return: Initial state in MPS form """ pop = lambda x: np.exp(-np.abs(alpha) ** 2 / 2) * alpha ** x / np.sqrt(factorial(x)) sys_psi_0 = convert.to_mparray(pop(np.arange(cutoff_dim)), 'mps') bath_psi_0 = broadcast_number_ground_state(cutoff_dim, nof_coefficients) return mp.chain([sys_psi_0, bath_psi_0])
def get_spin_boson_0T_star_initial_state(theta, system_index, bath_local_dim, nof_coefficients): """ Returns the full initial state (vacuum state) for 0T star with nof_coefficients sites and a local dimension of bath_local_dim. The impurity is located at system_index. """ sys_psi_0 = get_spin_initial_state(theta) # Initial states of the bath sites left and right of the system: left_bath_psi_0, right_bath_psi_0 = tmps.utils.broadcast_number_ground_state(bath_local_dim, system_index), \ tmps.utils.broadcast_number_ground_state(bath_local_dim, nof_coefficients - system_index) return mp.chain([left_bath_psi_0, sys_psi_0, right_bath_psi_0] if left_bath_psi_0 is not None else [sys_psi_0, right_bath_psi_0])
def ham(sites=3, ta=1., la=0.): #two-site zz part h_mpo = mp.MPArray.from_kron([z, -z]) #an empty list will be filled by local terms h = [] #adding zz terms to the hamiltonian for startpos in range(sites - 1): left = [mp.MPArray.from_kron([idm] * startpos)] if startpos > 0 else [] right = [mp.MPArray.from_kron([idm] * (sites - 2 - startpos))] \ if sites - 2 - startpos > 0 else [] h_at_startpos = mp.chain(left + [h_mpo] + right) h.append(h_at_startpos) #adding x terms to the hamiltonian for startpos in range(sites): left = [mp.MPArray.from_kron([idm] * startpos)] if startpos > 0 else [] right = [mp.MPArray.from_kron([idm] * (sites - 1 - startpos))] \ if sites - 1 - startpos > 0 else [] h_at_startpos = mp.chain(left + [mp.MPArray.from_array_global(-ta * x)] + right) h.append(h_at_startpos) #adding z terms to the hamiltonian for startpos in range(sites): left = [mp.MPArray.from_kron([idm] * startpos)] if startpos > 0 else [] right = [mp.MPArray.from_kron([idm] * (sites - 1 - startpos))] \ if sites - 1 - startpos > 0 else [] h_at_startpos = mp.chain(left + [mp.MPArray.from_array_global(-la * z)] + right) h.append(h_at_startpos) H = h[0] for local_term in h[1:]: H = H + local_term out, _ = H.compression(method='svd', relerr=1e-6) return out
def mpojob(mpas, selector, relerr=1e-6): """ stacks the set of mpas in the order specified by selector """ prod_chain = [] for sel in selector: prod_chain.append(mpas[sel]) # compute tensor chain chained = mp.chain(iter(prod_chain)) # compress output rank to within tolerance chained.compress("svd", relerr=relerr) return chained
def mporep(mpa, mpb, reps, relerr=1e-6): """ constructs the MPA [mpa, mpb_1, mpb_2....mpb_reps] """ # construct MPA iterable prod_chain = [mpb] * reps prod_chain.insert(0, mpa) # construct tensor chain chained = mp.chain(iter(prod_chain)) # compress chained.compress("svd", relerr=relerr) return chained
def get_boson_boson_0T_star_initial_state(alpha, system_index, nof_coefficients, cutoff_dim): """ Initial state for the Boson-Boson model in star geometry (see Sec. 4.4.3 of the thesis) :param alpha: accuracy alpha for the impurity coherent state :param system_index: Index of the impurity in the auxiliary chain :param nof_coefficients: Number of bath sites :param cutoff_dim: Local dimension of the system and impurity :return: Initial state in MPS form """ pop = lambda x: np.exp(-np.abs(alpha) ** 2 / 2) * alpha ** x / np.sqrt(factorial(x, exact=True)) sys_psi_0 = convert.to_mparray(pop(np.arange(cutoff_dim)), 'mps') # Initial states of the bath sites left and right of the system: left_bath_psi_0, right_bath_psi_0 = tmps.utils.broadcast_number_ground_state(cutoff_dim, system_index), \ tmps.utils.broadcast_number_ground_state(cutoff_dim, nof_coefficients - system_index) return mp.chain([left_bath_psi_0, sys_psi_0, right_bath_psi_0] if left_bath_psi_0 is not None else [sys_psi_0, right_bath_psi_0])
def _mpo_from_hi(self, hi, tau, bond): """ Convenience function, which allows to split mpo generation for 0T and finite T. Generates mpos for e^(-j*\tau*hi) as matrix exponential (uses scipy expm) For mps and mpo we just build e^(-j*\tau*hi) for the physical legs and generate the mpo. For pmps we first generate U^((s1, s2, s3), (s1', s2', s3')) = U^((s1, s3), (s1', s3')) * delta^(s2, s2') for each hi with U^((s1, s3), (s1', s3')) = e^(-j*tau*hi) with s2/s2' ancilla site. And then append a delta^(s4, s4') at the end for the second ancilla. :param hi: Bond operator to generate matrix exponential from :param tau: timestep for propagator :param bond: Bond index (i) for hi :return: e^(-j*tau*hi) in mpo form. For mps/mpo propagation is a two site two legs each operator for pmps propagation is a two site four legs (two physical, two ancilla on each site) ready for application to a state. Pre-compresses the operator if op_compression_kwargs not None """ # Generate e^(-j*tau*hi) physical_legs_exp = expm(-1j * tau * hi) # Tensorial shape of hi for the two physical sites i and i+1 in global form physical_legs_tensor_shape = (self.shape[bond][0], self.shape[bond + 1][0], self.shape[bond][0], self.shape[bond + 1][0]) physical_legs_exp = physical_legs_exp.reshape(physical_legs_tensor_shape) if not self.ancilla_sites: # Need only consider physical legs here mpo = mp.MPArray.from_array_global(physical_legs_exp, ndims=2) else: # Here we need to consider that there is an ancilla between the physical sites for which # physical_legs_exp was constructed. ldim_first_ancilla = self.shape[bond][1] ldim_second_ancilla = self.shape[bond + 1][1] # U^((s1, s3), (s1', s3')) * delta^(s2, s2') physical_and_first_ancilla = np.tensordot(physical_legs_exp, np.eye(ldim_first_ancilla), axes=0) # Slide indices s2 and s2' between s1 and s3/1' and s3' respectively physical_and_first_ancilla = np.moveaxis(physical_and_first_ancilla, [-2, -1], [1, 4]) # Add identity for second ancilla bond mpo = mp.chain([mp.MPArray.from_array_global(physical_and_first_ancilla, ndims=2), mp.eye(1, ldim_second_ancilla)]).group_sites(2) if self.op_compression_kwargs is not None: mpo.compress(**self.op_compression_kwargs) return mpo
def test_mppovm_pmf_as_array_pmps( nr_sites, local_dim, rank, startsite, width, rgen): if hasattr(local_dim, '__len__'): pdims = [d for d, _ in local_dim] mppaulis = mp.chain( povm.MPPovm.from_local_povm(povm.pauli_povm(d), 1) for d in pdims[startsite:startsite + width] ) else: pdims = local_dim local_dim = (local_dim, local_dim) mppaulis = povm.MPPovm.from_local_povm(povm.pauli_povm(pdims), width) mppaulis = mppaulis.embed(nr_sites, startsite, pdims) pmps = factory.random_mpa(nr_sites, local_dim, rank, dtype=np.complex_, randstate=rgen, normalized=True) rho = mpsmpo.pmps_to_mpo(pmps) expect_rho = mppaulis.pmf_as_array(rho, 'mpdo') for impl in ['default', 'pmps-ltr', 'pmps-symm']: expect_pmps = mppaulis.pmf_as_array(pmps, 'pmps', impl=impl) assert_array_almost_equal(expect_rho, expect_pmps, err_msg=impl)
def _mpo_from_hi(self, hi, tau, lbond, rbond): """ Generates U^{(s1, s2, s3), (s1', s2', s3')} = U^{(s1, s3), (s1', s3')} * delta^{s2, s2'} for each hi with U^{(s1, s3), (s1', s3')} = e^(-1j*\tau*hi) with s2/s2' ancilla site. And then appends a delta^{s4, s4'} at the end for the second ancilla. :param hi: Bond operator (tuple of (Eigvals, Eigvecs)) :param tau: timestep for propagator :param lbond: Bond index (i) for the left site in hi :param rbond: Bond index for the right site in hi :return: e^(-1j*\tau*hi) in mpo form. A two site four legs (two physical, two ancilla on each site) ready for application to a state """ # Generate e^(-j*tau*hi) physical_legs_exp = expm(-1j * tau * hi) # Tensorial shape of hi for the two physical sites i and i+1 in global form physical_legs_tensor_shape = (self.shape[lbond][0], self.shape[rbond][0], self.shape[lbond][0], self.shape[rbond][0]) physical_legs_exp = physical_legs_exp.reshape( physical_legs_tensor_shape) # Here we need to consider that there is an ancilla between the physical sites for which # physical_legs_exp was constructed. ldim_first_ancilla = self.shape[lbond][1] ldim_second_ancilla = self.shape[rbond][1] # U^((s1, s3), (s1', s3')) * delta^(s2, s2') physical_and_first_ancilla = np.tensordot(physical_legs_exp, np.eye(ldim_first_ancilla), axes=0) # Slide indices s2 and s2' between s1 and s3/1' and s3' respectively physical_and_first_ancilla = np.moveaxis(physical_and_first_ancilla, [-2, -1], [1, 4]) # Add identity for second ancilla bond mpo = mp.chain([ mp.MPArray.from_array_global(physical_and_first_ancilla, ndims=2), mp.eye(1, ldim_second_ancilla) ]) return self._compress_mpo(mpo)
def test_mppovmlist_est_pmf_from( method, n_samples, nr_sites, local_dim, rank, measure_width, local_width, nonuniform, splitpauli, rgen, eps=1e-10): """Verify that estimated probabilities from MPPovmList.est_pmf_from() are reasonable accurate """ mps = factory.random_mps(nr_sites, local_dim, rank, rgen) mps.canonicalize() x, y = (povm.MPPovm.from_local_povm(p, 1) for p in povm.pauli_parts(local_dim)[:2]) # POVM list with global support g_povm = povm.pauli_mpps(measure_width, local_dim).repeat(nr_sites) if nonuniform: add_povm = mp.chain((nr_sites - 1) * (x,) + (y,)) g_povm = povm.MPPovmList(g_povm.mpps + (add_povm,)) # POVM list with local support l_povm = povm.pauli_mpps if splitpauli else povm.pauli_mpp l_povm = l_povm(local_width, local_dim).block(nr_sites) samples = tuple(g_povm.sample( rgen, mps, n_samples, method, mode='mps', eps=eps)) est_prob, n_samples = zip(*l_povm.est_pmf_from(g_povm, samples, eps)) exact_prob = tuple(l_povm.pmf_as_array(mps, 'mps', eps)) # Consistency check on n_samples: All entries should be equal # unless `nonuniform` is True. all_n_sam = np.concatenate(n_samples) assert (not (all_n_sam == all_n_sam[0]).all()) == nonuniform for n_sam, est, exact, mpp in zip( n_samples, est_prob, exact_prob, l_povm.mpps): assert est.shape == mpp.nsoutdims assert est.shape == exact.shape assert n_sam.shape == exact.shape # Compare against exact probabilities assert (abs(est - exact) / (3 / n_sam**0.5)).max() <= 1
def get_thermal_state(beta, h_site, mpa_type='mpo', as_type='mparray', to_cform=None): """ Generates a thermal state e^(-beta*H_i)/Z in pmps form or mpo form site local Hamiltonians (H_i) without couplings. :param beta: Inverse temperature :param h_site: local operator(s) of Hamiltonian as single numpy array or iterable of numpy arrays for each site. If passed as 2d arrays, they are treated as full matrices. If they are passed as vectors, they are treated as diagonal elements of a matrix :param mpa_type: Type of mpa to evolve (allowed are 'pmps' and 'mpo') :param as_type: 'mparray' means as one combined mparray. 'mparray_list' as list of individual mparrays in the same order as the h_site were passed 'ndarray' returns the states as a list of ndarrays in the same order as the h_site were passed :param to_cform: Desired canonical form of the mparray (if as_type was selected to be 'mparray') :return: thermal state in pmps or mpo form (Any ancilla sites have the same dimension as the physical ones), info object from the propagation (if beta was set 0 it returns an empty dict) """ assert mpa_type == 'mpo' or mpa_type == 'pmps' thermal_states = [] if isinstance(h_site, np.ndarray): if len(h_site.shape) == 2: if not isdiag(h_site): state = expm(-beta * h_site) else: state = np.diag(np.exp(-beta * np.diag(h_site))) elif len(h_site.shape) == 1: state = np.diag(np.exp(-beta * h_site)) else: raise AssertionError( 'Passed numpy array must either be of matrix or vector shape') thermal_states.append(state) else: try: for site in h_site: if len(site.shape) == 2: if not isdiag(site): state = expm(-beta * site) thermal_states.append(state / np.trace(state)) else: state = np.diag(np.exp(-beta * np.diag(site))) thermal_states.append(state / np.trace(state)) elif len(site.shape) == 1: state = np.diag(np.exp(-beta * site)) thermal_states.append(state / np.trace(state)) else: raise AssertionError( 'Passed numpy array(s) must either be of matrix or vector shape' ) except TypeError: raise AssertionError( 'h_site must be single numpy array or iterable of numpy arrays' ) if as_type == 'ndarray': return thermal_states elif as_type == 'mparray': if mpa_type == 'pmps': state = mp.chain(purify_states(thermal_states, to='mparray')) elif mpa_type == 'mpo': state = mp.chain([ mp.MPArray.from_array_global(state, ndims=2) for state in thermal_states ]) else: raise AssertionError('Invalid mpa_type') canonicalize_to(state, to_cform=to_cform) return state elif as_type == 'mparray_list': if mpa_type == 'pmps': return purify_states(thermal_states, to='mparray') elif mpa_type == 'mpo': return [ mp.MPArray.from_array_global(state, ndims=2) for state in thermal_states ] else: raise AssertionError('Invalid mpa_type') else: raise AssertionError('Unrecognized return type')
def mp_from_array_repeat(array, nr_sites): """Generate a MPA representation of the `nr_sites`-fold tensor product of array. """ mpa = mp.MPArray.from_array(array) return mp.chain(it.repeat(mpa, nr_sites))
def test_mppovm_est( method, n_samples, nr_sites, startsite, local_dim, rgen): """Check that estimates from .est_pmf() and .est_lfun() are reasonably accurate """ rank = 3 eps = 1e-10 mps = factory.random_mps(nr_sites, local_dim, rank, rgen) mps.canonicalize() local_x = povm.x_povm(local_dim) local_y = povm.y_povm(local_dim) xx = povm.MPPovm.from_local_povm(local_x, 2) y = povm.MPPovm.from_local_povm(local_y, 1) mpp = mp.chain([xx, povm.MPPovm.eye([local_dim]), y]) \ .embed(nr_sites, startsite, local_dim) p_exact = mpp.pmf_as_array(mps, 'mps', eps) p_exact = project_pmf(p_exact, eps, eps) cov_p_exact = np.diag(p_exact.flat) - np.outer(p_exact.flat, p_exact.flat) samples = mpp.sample(rgen, mps, n_samples, method, 4, 'mps', eps=eps) p_est = mpp.est_pmf(samples) ept, cov = mpp.est_lfun(None, None, samples, None, eps) ept_ex, single_cov_ex = mpp.lfun(None, None, mps, 'mps', eps) # The two exact values must match assert abs(ept_ex - p_exact.ravel()).max() <= eps # The two exact values must match assert abs(cov_p_exact - single_cov_ex).max() <= eps # The two estimates must match. This verifies that we have chosen # our estimator will be unbiased. (There are many other things we # might want to know about our estimator.) assert (ept == p_est.ravel()).all() # The estimate must be close to the true value assert abs(p_exact - p_est).max() <= 3 / n_samples**0.5 cov_ex = cov_p_exact / n_samples # The covariances of the sample means (which we estimate here) # decrease by 1/n_samples, so we multiply with n_samples before # comparing to the rule-of-thumb for the estimation error. assert abs(cov - cov_ex).max() * n_samples <= 1 / n_samples**0.5 funs = [] nsoutdims = mpp.nsoutdims out = np.unravel_index(range(np.prod(nsoutdims)), nsoutdims) out = np.array(out).T[:, None, :].copy() for ind in range(np.prod(nsoutdims)): funs.append(lambda s, ind=ind: (s == out[ind]).all(1)) # All probabilities sum to one, and we can estimate that well. coeff = np.ones(len(funs), dtype=float) # Test with dummy weights weights = np.ones(n_samples, dtype=float) sum_ept, sum_var = mpp.est_lfun(coeff, funs, samples, weights, eps) assert abs(sum_ept - 1.0) <= eps assert sum_var <= eps # Check a sum of probabilities with varying signs. coeff = ((-1)**rgen.choice(2, len(funs))).astype(float) sum_ept, sum_var = mpp.est_lfun(coeff, funs, samples, None, eps) ex_sum = np.inner(coeff, p_exact.flat) ex_var = np.inner(coeff, np.dot(cov_ex, coeff)) assert abs(sum_ept - ex_sum) <= 5 / n_samples**0.5 assert abs(sum_var - ex_var) * n_samples <= 5 / n_samples**0.5 # Convert samples to counts and test again counts = mpp.est_pmf(samples, normalize=False, eps=eps) assert counts.sum() == n_samples count_samples = np.array(np.unravel_index(range(np.prod(mpp.nsoutdims)), mpp.nsoutdims)).T weights = counts.ravel() sum_ept2, sum_var2 = mpp.est_lfun(coeff, funs, count_samples, weights, eps) assert abs(sum_ept - sum_ept2) <= eps assert abs(sum_var - sum_var2) <= eps