# 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.
"""
Abstract MPO terms defining the methods needed, e.g., for ground state search.
"""
import abc
from collections.abc import Iterable
from typing import Any, Callable, Sequence, TypeAlias
from qtealeaves.tensors.abstracttensor import _AbstractQteaTensor
# from qtealeaves.abstracttns.abstract_tn import _AbstractTN
from qtealeaves.tooling import QTeaLeavesError
from qtealeaves.tooling.type_alias import TnPos, TnPosKey
__all__ = [
"_AbstractEffectiveMpo",
"_AbstractEffectiveOperators",
"_AbstractEffectiveProjector",
]
_AbstractTN: TypeAlias = Any # pylint: disable=invalid-name
[docs]
class _AbstractEffectiveOperators(abc.ABC):
"""
Any effective operator or overlap.
**Details**
Effective operators should implement at least a dictionary
functionality where the keys are made of a tuple of two
entries, where each entry is the position of a tensor in
the tensor network. The key `(pos_a, pos_b)` provides
the effective operators of the tensor at `pos_a` contracted
except for the link leading to the tensor at `pos_b`.
The position itself can be implemented depending on the
needs of the tensor networks, e.g., as integer or tuple
of integers. Only each link needs a unique pair of positions.
"""
# --------------------------------------------------------------------------
# Properties
# --------------------------------------------------------------------------
@property
@abc.abstractmethod
def device(self) -> str:
"""Device where the tensor is stored."""
@property
@abc.abstractmethod
def dtype(self) -> Any:
"""Data type of the underlying arrays."""
@property
@abc.abstractmethod
def num_sites(self) -> int:
"""Return the number of sites in the underlying system."""
@property
def has_oqs(self) -> bool:
"""Return if effective operators is open system (if no support, always False)."""
return False
# --------------------------------------------------------------------------
# Overwritten operators
# --------------------------------------------------------------------------
@abc.abstractmethod
def __contains__(self, idxs: TnPosKey) -> bool:
"""Check if entry inside the effective operators."""
@abc.abstractmethod
def __getitem__(
self, idxs: TnPosKey
) -> Iterable[_AbstractQteaTensor] | _AbstractQteaTensor:
"""Get an entry from the effective operators."""
@abc.abstractmethod
def __setitem__(self, key: TnPosKey, value):
"""Set an entry from the effective operators."""
# --------------------------------------------------------------------------
# Abstract effective operator methods
# --------------------------------------------------------------------------
[docs]
@abc.abstractmethod
def contr_to_eff_op(
self, tensor, pos: TnPos, pos_links: Sequence[TnPos], idx_out: int
):
"""Calculate the effective operator along a link."""
[docs]
@abc.abstractmethod
# pylint: disable-next=too-many-arguments
def contract_tensor_lists(
self,
tensor: _AbstractQteaTensor,
pos: TnPos,
pos_links: Sequence[TnPos | None],
custom_ops: Any = None,
pre_return_hook: Callable | None = None,
) -> _AbstractQteaTensor:
"""
Linear operator to contract all the effective operators
around the tensor in position `pos`. Used in the optimization.
"""
[docs]
@abc.abstractmethod
def convert(self, dtype: Any, device: str | None) -> None:
"""
Convert underlying array to the specified data type inplace. Original
site terms are preserved.
"""
[docs]
@abc.abstractmethod
def setup_as_eff_ops(
self, tensor_network: _AbstractTN, measurement_mode: bool = False
):
"""Set this sparse MPO as effective ops in TN and initialize."""
# Potential next abstract methods (missing consistent interfaces
# in terms of permutation, i.e., whole legs or just non-MPO legs)
#
# * tensordot_with_tensor
# * tensordot_with_tensor_left
# * matrix_matrix_mult
[docs]
@abc.abstractmethod
def values(self): # -> dict_values:
"""Return the values stored in the effective ops dictionary.
Returns:
values (dict_values)
"""
# --------------------------------------------------------------------------
# Effective operator methods
# --------------------------------------------------------------------------
def _helper_contract_to_eff_op(self, pos, pos_links, idx_out, keep_none=False):
"""
Helper for contr_to_eff_op taking care of the inital steps.
Arguments
---------
pos : int, tuple (depending on TN)
Position of tensor.
pos_links : list of int, tuple (depending on TN)
Position of neighboring tensors where the links
in `tensor` lead to.
idx_out : int
Uncontracted link to be used for effective operator.
Returns
-------
ops_list : List
Effective operators for `contr_to_eff_op`.
idx_list : List[int]
Indices for `contr_to_eff_op`.
key : tuple
Key where to set newly calculated effective operator.
ikey : tuple
Inverse direction in comparison to ikey, e.g., to delete
the entry which has become obsolete.
"""
ops_list = []
idx_list = []
pos_link_out = None
is_set = False
for ii, pos_link in enumerate(pos_links):
# detect the uncontracted link
if ii == idx_out:
pos_link_out = pos_link
is_set = True
continue
if (pos_link is None) and (not keep_none):
continue
# get the tensor of the given positions
pos_jj = self[(pos_link, pos)]
ops_list.append(pos_jj)
idx_list.append(ii)
if not is_set:
raise QTeaLeavesError(
"Arguments for contraction effective operator mismatch"
f" at position {pos} and idx_out={idx_out}."
)
# Key and key for inverse direction
key = (pos, pos_link_out)
ikey = (pos_link_out, pos)
return ops_list, idx_list, key, ikey
[docs]
def print_summary(self) -> None:
"""Print summary of computational effort (by default no report)."""
# The following two classes will help in isinstance checks and for
# typing to distinguish between MPO implementations and projector
# implementations. Therefore, they can be otherwise empty.
class _AbstractEffectiveMpo(_AbstractEffectiveOperators):
"""Any effective overlap to an MPO."""
@abc.abstractmethod
def get_local_kraus_operators(self, dt: float) -> dict[int, _AbstractQteaTensor]:
"""
Constructs local Kraus operators from local Lindblad operators.
Parameters
----------
dt : float, timestep
Returns
-------
kraus_ops : dict of :py:class:`_AbstractQTeaTensor`
Dictionary, keys are site indices and elements the corresponding 3-leg kraus tensors
"""
class _AbstractEffectiveProjector(_AbstractEffectiveOperators):
"""Any effective overlap to a state, i.e., a projector."""
def __init__(self, psi0: _AbstractTN | str, _extension):
if psi0.extension == _extension:
self.psi0 = psi0
else:
raise QTeaLeavesError(
f"{self.__class__} requires psi0 in the {_extension} form, but got {type(psi0)}."
)
self._num_sites = psi0.num_sites
self._device = psi0.tensor_backend.device
self._dtype = psi0.tensor_backend.dtype
self._has_oqs = False
self._eff_ops = {}
# --------------------------------------------------------------------------
# Overwritten operators
# --------------------------------------------------------------------------
def __contains__(self, idxs: TnPosKey) -> bool:
"""Check if entry inside the effective operators."""
return idxs in self._eff_ops
def __getitem__(self, key: int):
"""Get an entry from the effective operators."""
return self._eff_ops[key]
def __setitem__(self, key: int, value: _AbstractQteaTensor):
"""Set an entry from the effective operators."""
self._eff_ops[key] = value
def values(self):
return self._eff_ops.values()
# --------------------------------------------------------------------------
# Abstract effective operator methods
# --------------------------------------------------------------------------
@property
def num_sites(self):
"""Number of sites property."""
return self._num_sites
@property
def device(self):
"""Device where the tensor is stored."""
return self._device
@property
def dtype(self):
"""Data type of the underlying arrays."""
return self._dtype
@property
def has_oqs(self):
"""Return if effective operators is open system (if no support, always False)."""
return self._has_oqs
def convert(self, dtype, device: str, stream=None):
"""
Convert the _eff_ops to the specified data type inplace.
"""
for tensor in self._eff_ops.values():
tensor.convert(dtype, device, stream=stream)