Source code for pyquil.experiment._setting

##############################################################################
# Copyright 2016-2019 Rigetti Computing
#
#    Licensed under the Apache License, Version 2.0 (the "License");
#    you may not use this file except in compliance with the License.
#    You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS,
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    See the License for the specific language governing permissions and
#    limitations under the License.
##############################################################################
"""Definition of an ExperimentSetting.

Each ExperimentSetting corresponds to preparing a collection of qubits in a TensorProductState and measuring them in a
PauliTerm-defined basis.
"""

import logging
import re
from collections.abc import Generator, Iterable
from dataclasses import dataclass
from typing import Any, Optional, cast

from pyquil.paulis import PauliTerm, sI

log = logging.getLogger(__name__)


@dataclass(frozen=True)
class _OneQState:
    """A description of a named one-qubit quantum state.

    This can be used to generate pre-rotations for quantum process tomography. For example,
    X0_14 will generate the +1 eigenstate of the X operator on qubit 14. X1_14 will generate the
    -1 eigenstate. SIC0_14 will generate the 0th SIC-basis state on qubit 14.
    """

    label: str
    index: int
    qubit: int

    def __str__(self) -> str:
        return f"{self.label}{self.index}_{self.qubit}"

    @classmethod
    def from_str(cls, s: str) -> "_OneQState":
        ma = re.match(r"\s*(\w+)(\d+)_(\d+)\s*", s)
        if ma is None:
            raise ValueError(f"Couldn't parse '{s}'")
        return _OneQState(label=ma.group(1), index=int(ma.group(2)), qubit=int(ma.group(3)))


[docs] @dataclass(frozen=True) class TensorProductState: """A description of a multi-qubit quantum state that is a tensor product of many _OneQStates states.""" states: list[_OneQState] def __init__(self, states: Optional[Iterable[_OneQState]] = None): if states is None: states = [] object.__setattr__(self, "states", list(states)) def __mul__(self, other: "TensorProductState") -> "TensorProductState": return TensorProductState(self.states + other.states) def __str__(self) -> str: return " * ".join(str(s) for s in self.states) def __repr__(self) -> str: return f"TensorProductState[{self}]" def __getitem__(self, qubit: int) -> _OneQState: """Return the _OneQState at the given qubit.""" for oneq_state in self.states: if oneq_state.qubit == qubit: return oneq_state raise IndexError() def __iter__(self) -> Generator[_OneQState, None, None]: yield from self.states def __len__(self) -> int: return len(self.states)
[docs] def states_as_set(self) -> frozenset[_OneQState]: return frozenset(self.states)
def __eq__(self, other: Any) -> bool: if not isinstance(other, TensorProductState): return False return self.states_as_set() == other.states_as_set() def __hash__(self) -> int: return hash(self.states_as_set())
[docs] @classmethod def from_str(cls, s: str) -> "TensorProductState": if s == "": return TensorProductState() return TensorProductState(list(_OneQState.from_str(x) for x in s.split("*")))
[docs] def SIC0(q: int) -> TensorProductState: return TensorProductState([_OneQState(label="SIC", index=0, qubit=q)])
[docs] def SIC1(q: int) -> TensorProductState: return TensorProductState([_OneQState(label="SIC", index=1, qubit=q)])
[docs] def SIC2(q: int) -> TensorProductState: return TensorProductState([_OneQState(label="SIC", index=2, qubit=q)])
[docs] def SIC3(q: int) -> TensorProductState: return TensorProductState([_OneQState(label="SIC", index=3, qubit=q)])
[docs] def plusX(q: int) -> TensorProductState: return TensorProductState([_OneQState(label="X", index=0, qubit=q)])
[docs] def minusX(q: int) -> TensorProductState: return TensorProductState([_OneQState(label="X", index=1, qubit=q)])
[docs] def plusY(q: int) -> TensorProductState: return TensorProductState([_OneQState(label="Y", index=0, qubit=q)])
[docs] def minusY(q: int) -> TensorProductState: return TensorProductState([_OneQState(label="Y", index=1, qubit=q)])
[docs] def plusZ(q: int) -> TensorProductState: return TensorProductState([_OneQState(label="Z", index=0, qubit=q)])
[docs] def minusZ(q: int) -> TensorProductState: return TensorProductState([_OneQState(label="Z", index=1, qubit=q)])
[docs] def zeros_state(qubits: Iterable[int]) -> TensorProductState: return TensorProductState([_OneQState(label="Z", index=0, qubit=q) for q in qubits])
[docs] @dataclass(frozen=True, init=False) class ExperimentSetting: """Input and output settings for a tomography-like experiment. Many near-term quantum algorithms take the following form: - Start in a pauli state - Prepare some ansatz - Measure it w.r.t. pauli operators Where we typically use a large number of (start, measure) pairs but keep the ansatz preparation program consistent. This class represents the (start, measure) pairs. Typically a large number of these :py:class:`ExperimentSetting` objects will be created and grouped into an :py:class:`Experiment`. :ivar additional_expectations: A list of lists, where each inner list specifies a qubit subset to calculate the joint expectation value for. This attribute allows users to extract simultaneously measurable expectation values from a single experiment setting. """ in_state: TensorProductState out_operator: PauliTerm additional_expectations: Optional[list[list[int]]] = None def __init__( self, in_state: TensorProductState, out_operator: PauliTerm, additional_expectations: Optional[list[list[int]]] = None, ): object.__setattr__(self, "in_state", in_state) object.__setattr__(self, "out_operator", out_operator) object.__setattr__(self, "additional_expectations", additional_expectations) def _in_operator(self) -> PauliTerm: # Backwards compat pt = sI() for oneq_state in self.in_state.states: if oneq_state.label not in ["X", "Y", "Z"]: raise ValueError(f"Can't shim {oneq_state.label} into a pauli term. Use in_state.") if oneq_state.index != 0: raise ValueError(f"Can't shim {oneq_state} into a pauli term. Use in_state.") new_pt = pt * PauliTerm(op=oneq_state.label, index=oneq_state.qubit) pt = cast(PauliTerm, new_pt) return pt def __str__(self) -> str: return f"{self.in_state}{self.out_operator.compact_str()}" def __repr__(self) -> str: return f"ExperimentSetting[{self}]"
[docs] def serializable(self) -> str: return str(self)
[docs] @classmethod def from_str(cls, s: str) -> "ExperimentSetting": """Opposite of str(expt).""" instr, outstr = s.split("→") return ExperimentSetting( in_state=TensorProductState.from_str(instr), out_operator=PauliTerm.from_compact_str(outstr), )