Source code for renormalizer.transport.spectral_function

# -*- coding: utf-8 -*-

import logging

from renormalizer.mps.backend import np
from renormalizer.mps import Mpo, Mps
from renormalizer.utils import TdMpsJob, Quantity, EvolveConfig, CompressConfig
from renormalizer.model import Model, TI1DModel


logger = logging.getLogger(__name__)



[docs]class SpectralFunctionZT(TdMpsJob): r""" Calculate one-particle retarded Green's function at zero temperature for translational invariant one-dimensional model: .. math:: iG_{ij}(t) = \langle 0 |c_i(t) c^\dagger_j | 0 \rangle The value of the array is stored with key ``"G array"`` in the dumped output file. Because of the translational invariance, there are only two dimension in this array. The first dimension is :math:`t` and the second dimension is :math:`|i-j|`. In k-space, :math:`iG_{ij}` is transformed to: .. math:: iG_k(t) = \langle 0 |c_k(t) c^\dagger_k | 0 \rangle and :math:`G_k(t)` is saved with key ``"Gk array"`` in the dumped output file. Fourier transformation of :math:`G_k(t)` results in the spectral function: .. math:: A(k, \omega) = -\frac{1}{\pi} \rm{Im} \int_0^\infty dt e^{i \omega t} G_k(t) However, this value is not calculated directly in this class, since usually an broadening to :math:`G_k(t)` is required and an optimal range of :math:`\omega` can not be determined without additional knowledge. For finite temperature spectral function, it is recommended to use thermal field dynamics and transform the Hamiltonian. An example is included in the test case. See J. Chem.Phys.145, 224101 (2016) and Phys. Rev. Lett.123, 090402 (2019) for details. Parameters ========== model : :class:`~renormalizer.model.TI1DModel` system information. In principle should use :class:`~renormalizer.model.TI1DModel`. Using custom :class:`~renormalizer.model.Model` is possible however in this case the user should be responsible to ensure the translational invariance. It is recommended to set the number of the electronic DoFs to be an even number so that the :math:`k=\pi` point is well defined. compress_config : :class:`~renormalizer.utils.configs.CompressConfig` config when compressing MPS. evolve_config : :class:`~renormalizer.utils.configs.EvolveConfig` config when carrying out real time propagation. dump_dir : str the directory for logging and numerical result output. job_name : str the name of the calculation job which determines the file name of the logging and numerical result output. """ def __init__( self, model: TI1DModel, compress_config: CompressConfig = None, evolve_config: EvolveConfig = None, dump_dir: str = None, job_name: str = None, ): self.model: TI1DModel = model self.compress_config = compress_config if self.compress_config is None: self.compress_config = CompressConfig() # electron-addition Green's function at different $t$ assuming translational invariance self._G_array = [] self.e_occupations_array = [] self.temperature = Quantity(0) super().__init__(evolve_config=evolve_config, dump_dir=dump_dir, job_name=job_name) @property def G_array(self): """ :math:`G_{ij}(t)` as a two dimensional array. The first dimension is :math:`t` and the second dimension is :math:`|i-j|`. Returns ======= G_array : np.ndarray The Green's function array """ return np.array(self._G_array)
[docs] def init_mps(self): creation_oper = Mpo.onsite(self.model, r"a^\dagger", dof_set={self.model.e_dofs[0]}) gs = Mps.ground_state(self.model, False) self.h_mpo = Mpo(self.model, offset=Quantity(gs.expectation(Mpo(self.model)))) a_ket = creation_oper.apply(gs, canonicalise=True) a_ket.compress_config = self.compress_config a_ket.evolve_config = self.evolve_config a_ket.normalize("mps_norm_to_coeff") if self.evolve_config.is_tdvp: a_ket = a_ket.expand_bond_dimension(self.h_mpo) return (gs, a_ket)
[docs] def process_mps(self, mps): key = "a" if key not in self.model.mpos: a_opers = [Mpo.onsite(self.model, "a", dof_set={dof}) for dof in self.model.e_dofs] self.model.mpos[key] = a_opers else: a_opers = self.model.mpos[key] a_bra_mpo, a_ket_mpo = mps G = a_ket_mpo.expectations(a_opers, a_bra_mpo.conj()) / 1j self._G_array.append(G) self.e_occupations_array.append(a_ket_mpo.e_occupations)
[docs] def evolve_single_step(self, evolve_dt): prev_bra_mpdm, prev_ket_mpdm = self.latest_mps latest_ket_mpdm = prev_ket_mpdm.evolve(self.h_mpo, evolve_dt) return (prev_bra_mpdm, latest_ket_mpdm)
[docs] def get_dump_dict(self): dump_dict = dict() dump_dict['temperature'] = self.temperature.as_au() dump_dict['time series'] = self.evolve_times dump_dict["G array"] = self.G_array ne = self.model.n_edofs kpoints_distance = (2 * np.pi) / ne n_kpoints = ne // 2 + 1 ka = (np.arange(n_kpoints) * kpoints_distance).reshape(1, 1, -1) ijdiff = np.arange(ne).reshape(1, -1, 1) # Green's function in k space dump_dict["Gk array"] = np.sum(self.G_array.reshape(-1, ne, 1) * np.exp(1j * ka * ijdiff), axis=1) dump_dict["electron occupations array"] = self.e_occupations_array return dump_dict