Source code for qtealeaves.modeling.twobodyterm3d

# This code is part of qtealeaves.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""
Two-body interactions in a three-dimensional system.
"""

# pylint: disable=too-many-locals
# pylint: disable=too-many-branches
# pylint: disable=too-many-instance-attributes
# pylint: disable=too-many-arguments

import numpy as np

from qtealeaves.tooling import QTeaLeavesError
from qtealeaves.tooling.mapping import map_selector

from .baseterm import _ModelTerm

__all__ = ["TwoBodyTerm3D"]


[docs] class TwoBodyTerm3D(_ModelTerm): """ The term defines an interaction between two sites of the Hilbert space. For example, the tunneling term in the Bose-Hubbard model can be represented by this term. This class represents the 2d version. **Arguments** operators : list of two strings String identifier for the operators. Before launching the simulation, the python API will check that the operator is defined. shift : list of three ints Defines the distance of the interaction. In the end, we iterate over all sites and apply interactions to sites (x, y, z) and (x + shift[0], y + shift[1], z + shift[2]) strength : str, callable, numeric (optional) Defines the coupling strength of the local terms. It can be parameterized via a value in the dictionary for the simulation or a function. Default to 1. prefactor : numeric, scalar (optional) Scalar prefactor, e.g., in order to take into account signs etc. Default to 1. isotropy_xyz : bool, optional If False, the defined shift will only be applied as is. If true, we permute the defined shift to cover all spatial directions. Default to True. add_complex_conjg : bool, optional (BUG ticket #1) Aims to automatically add complex conjugated terms, e.g., for the tunneling term. has_obc : bool or list of bools, optional Defines the boundary condition along each spatial dimension. If scalar is given, the boundary condition along each spatial dimension is assumed to be equal. Default to True **Attributes** map_type : str, optional Selecting the mapping from a n-dimensional system to the 1d system required for the TTN simulations. """ def __init__( self, operators, shift, strength=1, prefactor=1, isotropy_xyz=True, add_complex_conjg=False, has_obc=True, ): super().__init__() self.operators = operators self.shift = shift self.strength = strength self.prefactor = prefactor self.isotropy_xyz = isotropy_xyz self.add_complex_conjg = add_complex_conjg # Will be set when adding Hamiltonian terms self.map_type = None if isinstance(has_obc, bool): self.has_obc = [has_obc] * 3 else: self.has_obc = has_obc
[docs] @staticmethod def check_dim(dim): """ See :func:`_ModelTerm.check_dim` """ if dim != 3: raise QTeaLeavesError("Dimension does not match.")
[docs] def collect_operators(self): """ All the required operators are returned to ensure that they are written by fortran. """ yield self.operators[0], "l" yield self.operators[1], "r" # Have to always add ... order in sites not guaranteed and # could be swapped yield self.operators[1], "l" yield self.operators[0], "r" if self.add_complex_conjg: # Only works in the b, bdagger or sigma^{+}, sigma^{-} # scenario yield self.operators[1], "l" yield self.operators[0], "r"
[docs] def iter_shifts(self): """ Return all possible shifts, which depends on the isotropy in the 3d case. """ if self.isotropy_xyz: n_coord = np.sum(np.array(self.shift) > 0) if n_coord not in [1, 2, 3]: raise QTeaLeavesError( f"Not implemented. Is this case n_coord={n_coord} useful?" ) if n_coord == 1: shifts = [ [self.shift[0], self.shift[1], self.shift[2]], [self.shift[1], self.shift[2], self.shift[0]], [self.shift[2], self.shift[0], self.shift[1]], ] elif n_coord == 2: if len(set(self.shift)) != 2: # Can be solved by adding the additional permutation # and then building the set raise QTeaLeavesError("Missing permutations.") shifts = [ [self.shift[0], self.shift[1], self.shift[2]], [self.shift[0], -self.shift[1], self.shift[2]], [self.shift[1], self.shift[2], self.shift[0]], [self.shift[1], self.shift[2], -self.shift[0]], [self.shift[2], self.shift[0], self.shift[1]], [self.shift[2], -self.shift[0], self.shift[1]], ] else: # Must be n_coords=3 now if len(set(self.shift)) != 1: # Can be solved by adding the additional permutation # and then building the set raise QTeaLeavesError("Maybe missing permutations.") shifts = [ [self.shift[0], self.shift[1], self.shift[2]], [-self.shift[0], self.shift[1], self.shift[2]], [self.shift[0], -self.shift[1], self.shift[2]], [self.shift[0], self.shift[1], -self.shift[2]], ] else: shifts = [self.shift] yield from shifts
[docs] def get_entries(self, params): """ Entries are defined based on two coordinates, the origin chosen as (0, 0, 0) and the shift. There are no site-dependent terms. **Arguments** params : dictionary Contains the simulation parameters. **Details** To-Do: complex-conjugate terms are wrong, i.e., they work for sigma_{i}^{+} sigma_{j}^{-} or b_{i}^{dagger} b_{j}, but not for terms as b_{j} sigma_{i}^{x}! """ if (self.has_obc[0] != self.has_obc[1]) or (self.has_obc[0] != self.has_obc[2]): raise QTeaLeavesError( "Cannot build dictionary with different " + "boundary conditions." ) for shift in self.iter_shifts(): coord_a = [0, 0, 0] coord_b = shift coordinates = [coord_a, coord_b] coupl_ii = { "strength": self.eval_strength(params), "operators": self.operators, "coordinates": coordinates, } yield coupl_ii if self.add_complex_conjg: coupl_ii = { "strength": self.eval_strength(params), "operators": [self.operators[1], self.operators[0]], "coordinates": coordinates, } yield coupl_ii
[docs] def get_interactions(self, ll, params, **kwargs): """ These interactions are closest to the TPO description iterating over specific sites within the 1d coordinates. **Arguments** ll : int Number of sites along each dimension, i.e., not the total number of sites. Assuming list of sites along all dimension. params : dictionary Contains the simulation parameters. """ if isinstance(self.map_type, str): map_type = self.eval_str_param(self.map_type, params) else: map_type = self.map_type map_to_1d = map_selector(3, ll, map_type) for elem in self.get_entries(params): for ix, iy, iz in self.iterate_sites(ll): coord_a, coord_b = elem["coordinates"] if np.sum(np.abs(np.array(coord_a))) != 0: raise QTeaLeavesError("Coordinate A is not the origin.") jx = ix + coord_b[0] jy = iy + coord_b[1] jz = iz + coord_b[2] if (jx >= ll[0]) and self.has_obc[0]: continue if (jx < 0) and self.has_obc[0]: continue if (jy >= ll[1]) and self.has_obc[1]: continue if (jy < 0) and self.has_obc[1]: continue if (jz >= ll[2]) and self.has_obc[2]: continue if (jz < 0) and self.has_obc[2]: continue if jx >= ll[0]: jx = jx % ll[0] if jx < 0: jx += ll[0] if jy >= ll[1]: jy = jy % ll[1] if jy < 0: jy += ll[1] if jz >= ll[2]: jz = jz % ll[2] if jz < 0: jz += ll[2] if (jx >= ll[0]) or (jx < 0): raise QTeaLeavesError("Improve handling.") if (jy >= ll[1]) or (jy < 0): raise QTeaLeavesError("Improve handling.") if (jz >= ll[2]) or (jz < 0): raise QTeaLeavesError("Improve handling.") if (ix == jx) and (iy == jy) and (iz == jz): raise QTeaLeavesError( "Same site.", (ix, iy, iz), (jx, jy, jz), coord_a, coord_b ) # Convert from python to Hilbert space index starting from 1 coords_1d = [map_to_1d[elem] for elem in [(ix, iy, iz), (jx, jy, jz)]] yield elem, coords_1d
[docs] def get_fortran_str(self, ll, params, operator_map, param_map): """ See :func:`_ModelTerm.get_fortran_str_two_body`. """ return self.get_fortran_str_twobody(ll, params, operator_map, param_map)