Source code for qtealeaves.observables.local

# 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.

"""
Local observable
"""
import logging
from typing import TYPE_CHECKING, Any, Iterator, Self, cast

import numpy as np

from qtealeaves.emulator import ATTN, LPTN, MPS, TTN, TTO, StateVector
from qtealeaves.mpos import ITPO, DenseMPO, DenseMPOList, MPOSite
from qtealeaves.tooling import QTeaLeavesError

from .tnobase import _TNObsBase

if TYPE_CHECKING:
    from qtealeaves.abstracttns.abstract_tn import _AbstractTN
    from qtealeaves.mpos.disentangler import DELayer
    from qtealeaves.operators import TNOperators
    from qtealeaves.tensors import TensorBackend

else:
    _AbstractTN = Any
    DELayer = Any
    TNOperators = Any
    TensorBackend = Any

__all__ = ["Local"]
logger = logging.getLogger(__name__)


[docs] class Local(_TNObsBase): """ The local observable will measure an operator defined on a single site across the complete system. This means that, if the single site has a dimension :math:`d` we expect the local observable to be a matrix of dimension :math:`d\\times d`. For example, if we are working with qubits, i.e. :math:`d=2`, a local observable can be the Pauli matrix :math:`\\sigma_z`. The local observable will be measured on *each* site of the tensor network. As such, the output of this measurement will be a dictionary where: - the key is the `name` of the observable - the value is a list of length :math:`n`, with :math:`n` the number of sites of the tensor network. The :math:`i`-th element of the list will contain the expectation value of the local observable on the :math:`i`-th site. **Arguments** name : str Define a label under which we can find the observable in the result dictionary. operator : str Identifier/string for the operator to be measured. """ # mypy triggered because of StateVector _measurable_ansaetze = (MPS, TTN, TTO, ATTN, LPTN, StateVector) # type: ignore[assignment] def __init__(self, name: str, operator: str): super().__init__(name) self.operator = [operator]
[docs] @classmethod def empty(cls) -> Self: """ Documentation see :func:`_TNObsBase.empty`. """ obj = cls("x", "x") obj.name = [] obj.operator = [] return obj
def __len__(self) -> int: """ Provide appropriate length method. """ return len(self.name) def __iadd__(self, other: Any) -> Self: """ Documentation see :func:`_TNObsBase.__iadd__`. """ if isinstance(other, Local): self.name += other.name self.operator += other.operator 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, Any]: """ Run the measurement of the local observable for a given state and save it in the results buffer. Args: state : instance of class from :py:mod:`qtealeaves.emulator` The state to measure the observable on. """ if len(self.name) == 0: return self.results_buffer if not self.check_measurable(state.__class__): logger.warning( "Observable %s not measurable for TN type %s", self.name, str(state), ) return self.results_buffer params = kwargs.get("params", None) # Copy the effective projectors and remove them from the state. # They are reinstated after the measurements. # 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 if state.has_de: # help the type checker state = cast(ATTN, state) if params is None: raise QTeaLeavesError( "Parameters are needed for disentangling layers, but not provided." ) # if aTTN, then local entries on sites with disentanglers are not # local anymore state.iso_towards(state.default_iso_pos, keep_singvals=True, trunc=True) hmpo = state.eff_op # get the iTPO of these non-local terms and contract with DE layer itpo = self.to_itpo( operators, state.tensor_backend, state.num_sites, de_layer=state.de_layer, ) itpo = state.de_layer.contract_de_layer(itpo, state.tensor_backend, params) # measure iTPO itpo.set_meas_status(do_measurement=True) state.eff_op = None # type: ignore[assignment] itpo.setup_as_eff_ops(state, measurement_mode=True) dict_by_tpo_id = itpo.collect_measurements() idx = -1 # restore back the original hamiltonian as effective operator state.eff_op = hmpo for jj, name_jj in enumerate(self.name): local_meas = state.meas_local( [operators[(kk, self.operator[jj])] for kk in range(state.num_sites)] ) # if aTTN, rewrite the values for sites with disentanglers if state.has_de: # help the type checker state = cast(ATTN, state) for kk in range(state.num_sites): if kk in state.de_layer.de_sites: # disentangler on a site idx += 1 local_meas[kk] = np.real(dict_by_tpo_id[idx]) self.results_buffer[name_jj] = local_meas # restore the effective projectors if ini_iso_pos is not None: state.iso_towards(ini_iso_pos) state.eff_proj = tmp_eff_proj return self.results_buffer
[docs] def collect_operators(self) -> Iterator[tuple[str, None]]: """ Documentation see :func:`_TNObsBase.collect_operators`. """ for elem in self.operator: yield (elem, None)
# pylint: disable-next=too-many-locals
[docs] def to_itpo( self, operators: TNOperators, tensor_backend: TensorBackend, num_sites: int, de_layer: DELayer, ) -> ITPO: """ For measurements of aTTN when local entries on sites with disentanglers are not local anymore, returns an ITPO with these non-local terms for measurement. The resulting terms are stored in order of looping over observable names and number of sites. Parameters ---------- operators: TNOperators The operator class tensor_backend: instance of `TensorBackend` Tensor backend of the simulation num_sites: int Number of sites of the state de_layer : DELayer Disentangler layer for which the iTPO layer is created Returns ------- ITPO: The `ITPO` class iTPO with the non-local terms for measurement. """ dense_mpo_list = DenseMPOList() for kk, _ in enumerate(self.name): key_loc = self.operator[kk] key_id = None # get local operator and corresponding identity for oset_name in operators.set_names: op = operators[(oset_name, key_loc)] # the matching identity (is it not always in operators?) op_identity = op.eye_like(op.links[1]) op_identity.attach_dummy_link(0, is_outgoing=False) op_identity.attach_dummy_link(3, is_outgoing=True) key_id = str(id(op_identity)) if key_id is None else key_id operators[(oset_name, key_id)] = op_identity # fill the itpo with correct operators on correct sites for ii in range(num_sites): if ii in de_layer.de_sites: # appropriate identity operator needs # to be added on the other site of the disentangler where = np.where(de_layer.de_sites == ii) # we assume max 1 disentangler per site ind = [where[0][0], where[1][0]] position_identity = de_layer.de_sites[ind[0], abs(ind[1] - 1)] site_a = MPOSite( ii, key_loc, 1.0, 1.0, operators=operators, params={} ) site_b = MPOSite( position_identity, key_id, 1.0, 1.0, operators=operators, params={}, ) # Checked manually, must be a linter-problem with double-inheritance # pylint: disable-next=abstract-class-instantiated dense_mpo = DenseMPO( [site_a, site_b], tensor_backend=tensor_backend ) dense_mpo_list.append(dense_mpo) # Sites within dense mpos are not ordered and we have to make links match anyways dense_mpo_list = dense_mpo_list.sort_sites() itpo = ITPO(num_sites) itpo.add_dense_mpo_list(dense_mpo_list) itpo.set_meas_status(do_measurement=True) return itpo