def evolve(self): """ Perform a Trotterized time evolution step by tau. Renormalizes state after one full Trotter-step due to compression. :return: """ overlap = self.base_overlap if not self.canonicalize_every_step: canonicalize_to(self.psi_t, to_cform=self.to_cform) for index, trotter_ui in enumerate(self.propagator.trotter_steps): self.psi_t = mp.dot(trotter_ui, self.psi_t, self.axes) if self.canonicalize_every_step: overlap *= self.psi_t.compress(**self.state_compression_kwargs) else: if index < self.len_trotter_steps-1: overlap *= self.psi_t.compress(**self.state_compression_kwargs_noc) else: if self.canonicalize_last_step: overlap *= self.psi_t.compress(**self.state_compression_kwargs) else: overlap *= self.psi_t.compress(**self.state_compression_kwargs_noc) self.pmps_compression_step += 1 if self.pmps_compression_step == self.compress_sites_step: compress_pmps_sites(self.psi_t, relerr=self.compress_sites_relerr, rank=self.compress_sites_rank, stable=self.compress_sites_stable, to_cform=self.to_cform) self.pmps_compression_step = 0 self._normalize_state() self.cumulative_overlap *= overlap self.last_overlap = overlap self.trotter_error += self.propagator.step_trotter_error self.stepno += 1
def evolve(self): """ Perform a Trotterized time evolution step by tau. Compress after each dot product or after each sweep through the chain. Renormalizes state after one full timestep due to compression. :return: """ overlap = self.base_overlap last_start_at = self.system_index if not self.canonicalize_every_step: canonicalize_to(self.psi_t, to_cform=self.to_cform) for (start_at, trotter_mpo) in self.propagator.trotter_steps: self.psi_t = mp.partialdot(trotter_mpo, self.psi_t, start_at=start_at, axes=self.axes) if self.full_compression: if start_at == self.propagator.L - 2 or start_at == 0: # Compress after a full sweep if start_at != last_start_at: overlap *= self.psi_t.compress( **self.state_compression_kwargs) else: if len(trotter_mpo) > 1: self.lc.compress(self.psi_t, start_at) if self.final_compression: overlap *= self.psi_t.compress(**self.state_compression_kwargs) else: if not self.canonicalize_every_step: canonicalize_to(self.psi_t, to_cform=self.to_cform) self._normalize_state() self.cumulative_overlap *= overlap self.last_overlap = overlap self.trotter_error += self.propagator.step_trotter_error self.stepno += 1
def reset(self, psi_0=None, state_compression_kwargs=None, psi_0_compression_kwargs=None): """ Resets selected properties of the TPMPS object If psi_0 is not None, we set the new initial state to psi_0, otherwise we keep the current state. If psi_0 is to be changed, we reset overlap and trotter error tracking, thus it is advisable to check the info object before the reset in this case. Checks if shape of passed psi_0 is compatible with the shape of the current propagator object :param psi_0: Optional new initial state, need not be normalized. Is normalized before propagation :param state_compression_kwargs: Optional. If not None, chamge the current parameters for state compression. If set None old compression parameters are kept, if empty, default compression is used. (see real time evolution factory function for details) :param psi_0_compression_kwargs: Optional compresion kwargs for the initial state (see real time evolution factory function for details) :return: """ if psi_0 is not None: if psi_0_compression_kwargs is None: canonicalize_to(psi_0, to_cform=self.to_cform) else: compress_pmps(psi_0, to_cform=self.to_cform, **psi_0_compression_kwargs) # Normalize current initial psi_t (=psi_0) self.normalize_state() self.pmps_compression_step = 0 self.last_overlap = 1 self.cumulative_overlap = 1 self.base_overlap = 1 self.trotter_error = 0 self.start = True self.stepno = 0 super().reset(psi_0=psi_0, state_compression_kwargs=state_compression_kwargs)
def _canonicalize(self, propagators): """ Canonicalizes trotter operators (depending on to_cform) :param propagators: full trotter operators """ for key, prop in propagators.items(): canonicalize_to(prop, to_cform=self.to_cform)
def fast_evolve(self, end=True): """ Perform a Trotterized time evolution step by tau. Compress after each dot product. Renormalizes state after one full Trotter-step due to compression. Without setting the end flag, the evolution does not do a complete trotter step, and the resulting state is therefore not immediately useable. Not setting the end flag allows for leaving out some unnecessary double steps and can therefore be used for propagating intermediate steps :parameter end: Flag, which can be set to false, if the current propagation only needs to produce an intermediate step, which is not used otherwise. :return: """ self.last_state_norm = self.curr_state_norm if self.start and end: trotter_steps = self.propagator.trotter_steps elif self.start and not end: trotter_steps = self.propagator.start_trotter_steps self.start = False elif not self.start and end: trotter_steps = self.propagator.end_trotter_steps self.start = True else: trotter_steps = self.propagator.step_trotter_steps if not self.canonicalize_every_step: canonicalize_to(self.psi_t, to_cform=self.to_cform) for index, (trotter_ui, trotter_ui_adj) in enumerate(trotter_steps): self.psi_t = mp.dot(trotter_ui, self.psi_t, self.axes) if self.canonicalize_every_step: self.psi_t.compress(**self.state_compression_kwargs) else: if index < self.len_trotter_steps - 1: self.psi_t.compress(**self.state_compression_kwargs_noc) else: if self.canonicalize_last_step: self.psi_t.compress(**self.state_compression_kwargs) else: self.psi_t.compress( **self.state_compression_kwargs_noc) self.psi_t = mp.dot(self.psi_t, trotter_ui.adj(), self.axes) if self.canonicalize_every_step: self.psi_t.compress(**self.state_compression_kwargs) else: if index < self.len_trotter_steps - 1: self.psi_t.compress(**self.state_compression_kwargs_noc) else: if self.canonicalize_last_step: self.psi_t.compress(**self.state_compression_kwargs) else: self.psi_t.compress( **self.state_compression_kwargs_noc) self._normalize_state() self.curr_state_norm = mp.norm(self.psi_t) self.trotter_error += 2 * self.propagator.step_trotter_error self.stepno += 1 if self.start and not end: self.start = False elif not self.start and end: self.start = True
def __init__(self, psi_0, propagator, state_compression_kwargs=None, t0=0, psi_0_compression_kwargs=None): """ Constructor for the StarTMPO class. Time evolution for quantum states represented by mpo for a star geometry. Does required setup steps for evolve to work. Uses a pre-constructed MPPropagator object :param psi_0: Initial state as MPArray. More precisely as an MPArray with two physical legs on each site, which have the same dimension (axis 0 = axis 1). Will be normalized before propagation :param propagator: Pre-constructed compatible StarMPPropagator object. (Compatibility means, that the chain shape with which it was constructed matches the shape of psi_0 and that it was constructed without ancilla sites, but with pre-calculated adjoints) :param state_compression_kwargs: Optional compression kwargs for the state (see real time evolution factory function for details) :param t0: Initial time of the propagation :param psi_0_compression_kwargs: Optional compression kwargs for the initial state (see real time evolution factory function for details) """ super().__init__(psi_0, propagator, state_compression_kwargs=state_compression_kwargs, t0=t0) self.mpa_type = 'mpo' self.system_index = propagator.system_index # Check if propagator is valid for the selected state mpa_type assert not self.propagator.ancilla_sites assert self.propagator.build_adj self.axes = (-1, 0) self.to_cform = propagator.to_cform if psi_0_compression_kwargs is None: canonicalize_to(self.psi_t, to_cform=self.to_cform) else: compress_mpa(self.psi_t, to_cform=self.to_cform, **psi_0_compression_kwargs) # trace normalize current initial psi_t (=psi_0) self.normalize_state() # If we deal with an mpo state we want to track the L2-norm of the state. # It should be approximately conserved by the trotterized evolution assuming no significant compression losses. self.init_state_norm = mp.norm(psi_0) self.last_state_norm = self.init_state_norm self.curr_state_norm = self.init_state_norm # Contains accumulated trotter errors for each timestep self.trotter_error = 0
def _compress_mpo(self, mpo): """ Compresses and optionall canonicalizes a propagator mpo :param mpo: :return: """ if self.op_compression_kwargs is not None: mpo.compress(**self.op_compression_kwargs) canonicalize_to(mpo, to_cform=self.to_cform) return mpo else: return mpo
def __init__(self, psi_0, propagator, state_compression_kwargs=None, t0=0, psi_0_compression_kwargs=None): """ Constructor for the StarTPMPS class. Time evolution for quantum states represented by purified state (pmps). Does required setup steps for evolve to work. Uses a pre-constructed MPPropagator object :param psi_0: Initial state as MPArray. More precisely as an MPArray with two legs on each site, one physical, one ancilla. Will be normalized before propagation :param propagator: Compatible Pre-constructed StarMPPropagator object. (Compatibility means, that the chain shape with which it was constructed matches the shape of psi_0) :param state_compression_kwargs: Optional compression kwargs for the state (see real time evolution factory function for details) :param t0: Initial time of the propagation :param psi_0_compression_kwargs: Optional compresion kwargs for the initial state (see real time evolution factory function for details) """ super().__init__(psi_0, propagator, state_compression_kwargs=state_compression_kwargs, t0=t0) self.mpa_type = 'pmps' self.system_index = propagator.system_index self.pmps_compression_step = 1 # Check if propagator is valid for the selected state mpa_type assert self.propagator.ancilla_sites assert not self.propagator.build_adj # Contract over all indices (physical and ancilla) self.axes = ((1, 3), (0, 1)) self.to_cform = propagator.to_cform if psi_0_compression_kwargs is None: canonicalize_to(self.psi_t, to_cform=self.to_cform) else: compress_pmps(self.psi_t, to_cform=self.to_cform, **psi_0_compression_kwargs) # Normalize current initial psi_t (=psi_0) self.normalize_state() # Contains overlap due to compression for the last timestep and for all timesteps combined respectively self.last_overlap = 1 self.cumulative_overlap = 1 self.base_overlap = 1 # Contains accumulated trotter errors for each timestep self.trotter_error = 0
def __init__(self, psi_0, propagator, state_compression_kwargs=None, t0=0, psi_0_compression_kwargs=None): """ Constructor for the TMPS class. Time evolution for quantum states represented by mps. Does required setup steps for evolve to work. Uses a pre-constructed MPPropagator object :param psi_0: Initial state as MPArray. More precisely as an MPArray with one physical leg on each site. Will be normalized before propagation :param propagator: Compatible Pre-constructed MPPropagator object. (Compatibility means, that the chain shape with which it was constructed matches the shape of psi_0) :param state_compression_kwargs: Optional compression kwargs for the state (see real time evolution factory function for details) :param t0: Initial time of the propagation :param psi_0_compression_kwargs: Optional compresion kwargs for the initial state (see real time evolution factory function for details) """ super().__init__(psi_0, propagator, state_compression_kwargs=state_compression_kwargs, t0=t0, psi_0_compression_kwargs=psi_0_compression_kwargs) self.mpa_type = 'mps' # Check if propagator is valid for the selected state mpa_type assert not self.propagator.ancilla_sites assert not self.propagator.build_adj self.axes = (-1, 0) self.to_cform = propagator.to_cform if psi_0_compression_kwargs is None: canonicalize_to(self.psi_t, to_cform=self.to_cform) else: compress_mpa(self.psi_t, to_cform=self.to_cform, **psi_0_compression_kwargs) # Normalize current initial psi_t (=psi_0) self.normalize_state() # Contains overlap due to compression for the last timestep and for all timesteps combined respectively self.last_overlap = 1 self.cumulative_overlap = 1 self.base_overlap = 1 self.len_trotter_steps = len(self.propagator.trotter_steps) self.len_start_trotter_steps = len(self.propagator.start_trotter_steps) self.len_step_trotter_steps = len(self.propagator.step_trotter_steps) self.len_end_trotter_steps = len(self.propagator.end_trotter_steps) # Contains accumulated trotter errors for each timestep self.trotter_error = 0 # Flag for the fast_evolve propagation type: self.start = True
def evolve(self): """ Perform a Trotterized time evolution step by tau. Compress after each dot product or after each sweep through the chain. Renormalize state after one full timestep due to compression. :return: """ overlap = self.base_overlap last_start_at = self.system_index if not self.canonicalize_every_step: canonicalize_to(self.psi_t, to_cform=self.to_cform) for (start_at, trotter_mpo) in self.propagator.trotter_steps: self.psi_t = mp.partialdot(trotter_mpo, self.psi_t, start_at=start_at, axes=self.axes) if self.full_compression: if (start_at == self.propagator.L - 2 or start_at == 0) and start_at != last_start_at: # Compress after a full sweep overlap *= self.psi_t.compress( **self.state_compression_kwargs) else: if len(trotter_mpo) > 1: self.lc.compress(self.psi_t, start_at) if (start_at == self.propagator.L - 2 or start_at == 0) and start_at != last_start_at: self.pmps_compression_step += 1 if self.pmps_compression_step - 1 == self.compress_sites_step: compress_pmps_sites(self.psi_t, relerr=self.compress_sites_relerr, rank=self.compress_sites_rank, stable=self.compress_sites_stable, to_cform=self.to_cform) self.pmps_compression_step = 1 last_start_at = start_at if self.final_compression: overlap *= self.psi_t.compress(**self.state_compression_kwargs) else: if not self.canonicalize_every_step: canonicalize_to(self.psi_t, to_cform=self.to_cform) self._normalize_state() self.cumulative_overlap *= overlap self.last_overlap = overlap if self.system_index == 0: # reset by one, because next propagation step starts at the edge of chain and increments counter immediately self.pmps_compression_step -= 1 self.trotter_error += self.propagator.step_trotter_error self.stepno += 1
def compress_pmps_sites(pmps, relerr=1e-10, rank=None, stable=False, to_cform=None): """ Compresses pmps local sites :param pmps: MPArray (of pmps form) :param relerr: relative error for the compression :param rank: maximum rank of the compressed state :param to_cform: target canonical form ('left', 'right' or None) :param stable: If the stable but slower variant of the svd compression should be used directly instead of trying the possibly faster one first """ for site, lt in enumerate(pmps._lt): new_lt = _compressed_lt(lt, relerr, rank, stable) pmps._lt.update(site, new_lt, canonicalization=None) canonicalize_to(pmps, to_cform=to_cform)
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')