Source code for qiskit.circuit.quantumcircuitdata

# This code is part of Qiskit.
#
# (C) Copyright IBM 2019.
#
# 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.

"""A wrapper class for the purposes of validating modifications to
QuantumCircuit.data while maintaining the interface of a python list."""

from collections.abc import MutableSequence
from typing import Tuple, Iterable, Optional

from .exceptions import CircuitError
from .instruction import Instruction
from .operation import Operation
from .quantumregister import Qubit
from .classicalregister import Clbit


[docs]class CircuitInstruction: """A single instruction in a :class:`.QuantumCircuit`, comprised of the :attr:`operation` and various operands. .. note:: There is some possible confusion in the names of this class, :class:`~.circuit.Instruction`, and :class:`~.circuit.Operation`, and this class's attribute :attr:`operation`. Our preferred terminology is by analogy to assembly languages, where an "instruction" is made up of an "operation" and its "operands". Historically, :class:`~.circuit.Instruction` came first, and originally contained the qubits it operated on and any parameters, so it was a true "instruction". Over time, :class:`.QuantumCircuit` became responsible for tracking qubits and clbits, and the class became better described as an "operation". Changing the name of such a core object would be a very unpleasant API break for users, and so we have stuck with it. This class was created to provide a formal "instruction" context object in :class:`.QuantumCircuit.data`, which had long been made of ad-hoc tuples. With this, and the advent of the :class:`~.circuit.Operation` interface for adding more complex objects to circuits, we took the opportunity to correct the historical naming. For the time being, this leads to an awkward case where :attr:`.CircuitInstruction.operation` is often an :class:`~.circuit.Instruction` instance (:class:`~.circuit.Instruction` implements the :class:`.Operation` interface), but as the :class:`.Operation` interface gains more use, this confusion will hopefully abate. .. warning:: This is a lightweight internal class and there is minimal error checking; you must respect the type hints when using it. It is the user's responsibility to ensure that direct mutations of the object do not invalidate the types, nor the restrictions placed on it by its context. Typically this will mean, for example, that :attr:`qubits` must be a sequence of distinct items, with no duplicates. """ __slots__ = ("operation", "qubits", "clbits") operation: Operation """The logical operation that this instruction represents an execution of.""" qubits: Tuple[Qubit, ...] """A sequence of the qubits that the operation is applied to.""" clbits: Tuple[Clbit, ...] """A sequence of the classical bits that this operation reads from or writes to.""" def __init__( self, operation: Operation, qubits: Iterable[Qubit] = (), clbits: Iterable[Clbit] = (), ): self.operation = operation self.qubits = tuple(qubits) self.clbits = tuple(clbits)
[docs] def copy(self) -> "CircuitInstruction": """Return a shallow copy of the :class:`CircuitInstruction`.""" return self.__class__( operation=self.operation, qubits=self.qubits, clbits=self.clbits, )
[docs] def replace( self, operation: Optional[Operation] = None, qubits: Optional[Iterable[Qubit]] = None, clbits: Optional[Iterable[Clbit]] = None, ) -> "CircuitInstruction": """Return a new :class:`CircuitInstruction` with the given fields replaced.""" return self.__class__( operation=self.operation if operation is None else operation, qubits=self.qubits if qubits is None else qubits, clbits=self.clbits if clbits is None else clbits, )
def __repr__(self): return ( f"{type(self).__name__}(" f"operation={self.operation!r}" f", qubits={self.qubits!r}" f", clbits={self.clbits!r}" ")" ) def __eq__(self, other): if isinstance(other, type(self)): # Ordered from fastest comparisons to slowest. return ( self.clbits == other.clbits and self.qubits == other.qubits and self.operation == other.operation ) if isinstance(other, tuple): return self._legacy_format() == other return NotImplemented # Legacy tuple-like interface support. # # For a best attempt at API compatibility during the transition to using this new class, we need # the interface to behave exactly like the old 3-tuple `(inst, qargs, cargs)` if it's treated # like that via unpacking or similar. That means that the `parameters` field is completely # absent, and the qubits and clbits must be converted to lists. def _legacy_format(self): # The qubits and clbits were generally stored as lists in the old format, and various # places assume that they will certainly be lists. return (self.operation, list(self.qubits), list(self.clbits)) def __getitem__(self, key): return self._legacy_format()[key] def __iter__(self): return iter(self._legacy_format()) def __len__(self): return 3
class QuantumCircuitData(MutableSequence): """A wrapper class for the purposes of validating modifications to QuantumCircuit.data while maintaining the interface of a python list.""" def __init__(self, circuit): self._circuit = circuit def __getitem__(self, i): return self._circuit._data[i] def __setitem__(self, key, value): # For now (Terra 0.21), the `QuantumCircuit.data` setter is meant to perform validation, so # we do the same qubit checks that `QuantumCircuit.append` would do. if isinstance(value, CircuitInstruction): operation, qargs, cargs = value.operation, value.qubits, value.clbits else: # Handle the legacy 3-tuple format. operation, qargs, cargs = value value = self._resolve_legacy_value(operation, qargs, cargs) self._circuit._data[key] = value if isinstance(value.operation, Instruction): self._circuit._update_parameter_table(value) def _resolve_legacy_value(self, operation, qargs, cargs) -> CircuitInstruction: """Resolve the old-style 3-tuple into the new :class:`CircuitInstruction` type.""" if not isinstance(operation, Operation) and hasattr(operation, "to_instruction"): operation = operation.to_instruction() if not isinstance(operation, Operation): raise CircuitError("object is not an Operation.") expanded_qargs = [self._circuit.qbit_argument_conversion(qarg) for qarg in qargs or []] expanded_cargs = [self._circuit.cbit_argument_conversion(carg) for carg in cargs or []] if isinstance(operation, Instruction): broadcast_args = list(operation.broadcast_arguments(expanded_qargs, expanded_cargs)) else: broadcast_args = list( Instruction.broadcast_arguments(operation, expanded_qargs, expanded_cargs) ) if len(broadcast_args) > 1: raise CircuitError( "QuantumCircuit.data modification does not support argument broadcasting." ) qargs, cargs = broadcast_args[0] self._circuit._check_dups(qargs) return CircuitInstruction(operation, tuple(qargs), tuple(cargs)) def insert(self, index, value): self._circuit._data.insert(index, None) try: self[index] = value except CircuitError: del self._circuit._data[index] raise def __iter__(self): return iter(self._circuit._data) def __delitem__(self, i): del self._circuit._data[i] def __len__(self): return len(self._circuit._data) def __cast(self, other): return other._circuit._data if isinstance(other, QuantumCircuitData) else other def __repr__(self): return repr(self._circuit._data) def __lt__(self, other): return self._circuit._data < self.__cast(other) def __le__(self, other): return self._circuit._data <= self.__cast(other) def __eq__(self, other): return self._circuit._data == self.__cast(other) def __gt__(self, other): return self._circuit._data > self.__cast(other) def __ge__(self, other): return self._circuit._data >= self.__cast(other) def __add__(self, other): return self._circuit._data + self.__cast(other) def __radd__(self, other): return self.__cast(other) + self._circuit._data def __mul__(self, n): return self._circuit._data * n def __rmul__(self, n): return n * self._circuit._data def sort(self, *args, **kwargs): """In-place stable sort. Accepts arguments of list.sort.""" self._circuit._data.sort(*args, **kwargs) def copy(self): """Returns a shallow copy of instruction list.""" return self._circuit._data.copy()