# 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.
"""
Observable to measure tensor product observables in the system
"""
import logging
from typing import TYPE_CHECKING, Any, Self
import numpy as np
from qtealeaves.emulator import ATTN, MPS, TTN, TTO
from qtealeaves.tooling import QTeaLeavesError
from .tnobase import _TNObsBase
if TYPE_CHECKING:
from qtealeaves.abstracttns.abstract_tn import _AbstractTN
from qtealeaves.operators.tnoperators import TNOperators
else:
_AbstractTN = Any
TNOperators = Any
__all__ = ["TensorProduct"]
logger = logging.getLogger(__name__)
[docs]
class TensorProduct(_TNObsBase):
"""
Observables which are tensor product between one-site or
two-site operators.
This observable enables the computation of observables of the following
form. On a tensor network with :math:`n` sites we can measure :math:`O`
of the form:
.. math::
O = o_0 \\otimes o_2 \\otimes o_3 \\otimes \\dots \\otimes o_{n-1}
where the different local observables :math:`o_i` might be different
or even be the identity.
The output of the measurement will be a dictionary where:
- The key is the `name` of the observable
- The value is its expectation value
An example of such an observable is the Parity of the system. If we
work on a system of qubits, then to measure the parity we simply have
to use :math:`o_i=o_j=\\sigma_z \\; \\forall \\; i,j\\in\\{0, n-1\\}`, where
:math:`\\sigma_z` is the Pauli matrix.
Parameters
----------
name: str
Name to identify the observable
operators: list of str or str
Idenitifiers/names for the operators to be measured.
If str the same operator is applied to the whole MPS
sites: list of int or int
Indexes to which the operators should be applied, in the same order.
If int instead it is the size of the chain, and the operator is assumed
to be applied to each site of the tensor network
"""
_measurable_ansaetze = (MPS, TTN, TTO, ATTN)
def __init__(self, name: str, operators: list[str] | str, sites: list[int] | int):
if isinstance(operators, str) and isinstance(sites, int):
operators = [operators] * sites
sites = list(range(sites))
elif isinstance(operators, str):
raise TypeError("If operators is str sites must be int")
elif isinstance(sites, int):
raise TypeError("If sites is int operators must be str")
self.operators = [operators]
self.sites = [sites]
_TNObsBase.__init__(self, name)
[docs]
@classmethod
def empty(cls) -> Self:
"""
Documentation see :func:`_TNObsBase.empty`.
"""
obj = cls("", "", 0)
obj.name = []
obj.operators = []
obj.sites = []
return obj
def __iadd__(self, other: Any) -> Self:
"""
Documentation see :func:`_TNObsBase.__iadd__`.
"""
if isinstance(other, TensorProduct):
self.name += other.name
self.operators += other.operators
self.sites += other.sites
else:
raise QTeaLeavesError(
f"__iadd__ not defined for types {type(self)} and {type(other)}."
)
return self
[docs]
def measure(
self, state: _AbstractTN, operators: TNOperators, **kwargs: Any
) -> dict[str, np.ndarray]:
"""
Documentation see :func:`_TNObsBase.measure`.
"""
if len(self.name) == 0:
return self.results_buffer
if not self.check_measurable(state.__class__):
logger.warning("Observable %s not measurable for %s", self.name, str(state))
return self.results_buffer
# Why only the effective projectors, but not the effective operators. While
# isometrizing in meas_tensor_product, we still propagate them through? Why?
tmp_eff_proj = state.eff_proj
state.eff_proj = []
ini_iso_pos = state.iso_center
for name, ops, sites in zip(self.name, self.operators, self.sites):
tp_operators = [operators[(site, op)] for site, op in zip(sites, ops)]
self.results_buffer[name] = np.complex128(
state.meas_tensor_product(tp_operators, sites) # type: ignore[attr-defined]
)
if ini_iso_pos is not None:
state.iso_towards(ini_iso_pos)
state.eff_proj = tmp_eff_proj
return self.results_buffer