Source code for qtealeaves.observables.projective

# 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 perform the final projective measurements on the system
"""
import logging
from collections.abc import Iterator
from typing import TYPE_CHECKING, Any, Self

import h5py

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
else:
    _AbstractTN = Any

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


[docs] class Projective(_TNObsBase): """ Observable to enable the final projective measurements after the evolution. This observable is meant to give single-shot measurements: the system is **projectively** measured a number of times equal to `num_shots`, such that the user can observe a statistic of the distribution of the state. The result of the observable will be a dictionary where: - the keys are the measured state on a given basis - the values are the number of occurrences of the keys in the `num_shots` single shots measurements As an example, if we work with qubits, we measure on the computational base and we end up with the state :math:`\\frac{1}{\\sqrt{2}}(|00\\rangle+|11\\rangle)`, requesting 1000 `num_shots` we will end up with the following result: :code:`{'00' : 505, '11' : 495}`. Take into account that the measurement is probabilistic and such is will only be an approximation of the true probability distribution, that in the example case would be :math:`p_{00}=p_{11}=0.5`. Parameters ---------- num_shots : int Number of projective measurements qiskit_convention : bool, optional If you should use the qiskit convention when measuring, i.e. least significant qubit on the right. Default to False. """ _measurable_ansaetze = (MPS, TTN, TTO, ATTN) def __init__(self, num_shots: int, qiskit_convention: bool = False): self.num_shots = num_shots self.qiskit_convention = [qiskit_convention] self._measures: dict[str, int] = {} _TNObsBase.__init__(self, "projective_measurements") def __iadd__(self, other: Any) -> Self: """ Documentation see :func:`_TNObsBase.__iadd__`. """ if isinstance(other, Projective): self.num_shots += other.num_shots self.qiskit_convention += other.qiskit_convention if len(set(self.qiskit_convention)) > 1: logger.warning( "Merging Projective observables with different qiskit_convention values." "Only the first will be used." ) else: raise QTeaLeavesError( f"__iadd__ not defined for types {type(self)} and {type(other)}." ) return self
[docs] @classmethod def empty(cls) -> Self: """ Documentation see :func:`_TNObsBase.empty`. """ obj = cls(0) obj.qiskit_convention = [] return obj
[docs] def measure( self, state: _AbstractTN, operators: Any = None, **kwargs: Any ) -> dict[str, Any]: """ 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, qk_conv in zip(self.name, self.qiskit_convention): self.results_buffer[name] = state.meas_projective( nmeas=self.num_shots, qiskit_convention=qk_conv ) 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 add_trajectories(self, all_results: dict, new: dict[str, Any]) -> dict: """ Documentation see :func:`_TNObsBase.add_trajectories`. """ for name in self.name: if name not in all_results: all_results[name] = new[name] else: if len(new[name]) > 0: for key in new[name]: if key not in all_results[name]: all_results[name][key] = new[name][key] else: all_results[name][key] += new[name][key] return all_results
[docs] def avg_trajectories(self, all_results: dict, num_trajectories: int) -> dict: """ Documentation see :func:`_TNObsBase.avg_trajectories`. """ # the average is not performed for projective measurements return all_results
[docs] def read(self, fh: h5py.File, **kwargs: Any) -> Iterator[tuple[str, dict]]: """ Read the measurements of the projective measurement observable from HDF5 file. Parameters ---------- fh : h5py.File Read the information about the measurements from this HDF5 file. """ # check that group exists if str(self) not in fh: raise QTeaLeavesError("Observable group not found in file.") fg = fh[str(self)] is_measured = fg.attrs.get("is_measured", False) name = self.name[0] if not is_measured or name not in fg: yield name, {} return data = fg[name] keys = data.attrs["keys"] yield name, {key: data[ii] for ii, key in enumerate(keys)}
[docs] def write_results( self, fh: h5py.File, state_ansatz: type[_AbstractTN], **kwargs: Any ) -> None: """ See :func:`_TNObsBase.write_results`. """ is_measured = self.check_measurable(state_ansatz) fg = fh.create_group(str(self)) fg.attrs["is_measured"] = is_measured if is_measured and len(self.results_buffer.values()) > 0: # note: this observable has only one name "projective_measurements", # with no guarantee for the result buffer to have it name = self.name[0] result_dict = self.results_buffer[name] fg.create_dataset(name, data=result_dict.values()) fg.attrs["keys"] = result_dict.keys()