diff --git a/qiskit/circuit/__init__.py b/qiskit/circuit/__init__.py index b293fcdd12..e2f7936acb 100644 --- a/qiskit/circuit/__init__.py +++ b/qiskit/circuit/__init__.py @@ -279,6 +279,7 @@ Gates and Instructions InstructionSet Operation EquivalenceLibrary + SingletonGate Control Flow Operations ----------------------- @@ -366,6 +367,7 @@ from .gate import Gate # pylint: disable=cyclic-import from .controlledgate import ControlledGate +from .singleton_gate import SingletonGate from .instruction import Instruction from .instructionset import InstructionSet from .operation import Operation diff --git a/qiskit/circuit/add_control.py b/qiskit/circuit/add_control.py index 9a9837673d..46fe2de0c6 100644 --- a/qiskit/circuit/add_control.py +++ b/qiskit/circuit/add_control.py @@ -55,7 +55,9 @@ def add_control( # attempt decomposition operation._define() cgate = control(operation, num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) - cgate.base_gate.label = operation.label + if operation.label is not None: + cgate.base_gate = cgate.base_gate.to_mutable() + cgate.base_gate.label = operation.label return cgate diff --git a/qiskit/circuit/gate.py b/qiskit/circuit/gate.py index 3bfd40f159..65ca210b59 100644 --- a/qiskit/circuit/gate.py +++ b/qiskit/circuit/gate.py @@ -24,7 +24,15 @@ from .instruction import Instruction class Gate(Instruction): """Unitary gate.""" - def __init__(self, name: str, num_qubits: int, params: list, label: str | None = None) -> None: + def __init__( + self, + name: str, + num_qubits: int, + params: list, + label: str | None = None, + duration=None, + unit="dt", + ) -> None: """Create a new gate. Args: @@ -34,7 +42,7 @@ class Gate(Instruction): label: An optional label for the gate. """ self.definition = None - super().__init__(name, num_qubits, 0, params, label=label) + super().__init__(name, num_qubits, 0, params, label=label, duration=duration, unit=unit) # Set higher priority than Numpy array and matrix classes __array_priority__ = 20 diff --git a/qiskit/circuit/instruction.py b/qiskit/circuit/instruction.py index 6b74dacfaa..d2fe33d8d3 100644 --- a/qiskit/circuit/instruction.py +++ b/qiskit/circuit/instruction.py @@ -94,16 +94,43 @@ class Instruction(Operation): self._label = label # tuple (ClassicalRegister, int), tuple (Clbit, bool) or tuple (Clbit, int) # when the instruction has a conditional ("if") - self.condition = None + self._condition = None # list of instructions (and their contexts) that this instruction is composed of # empty definition means opaque or fundamental instruction self._definition = None - self._duration = duration self._unit = unit self.params = params # must be at last (other properties may be required for validation) + @property + def mutable(self) -> bool: + """Is this instance is a mutable unique instance or not. + + If this attribute is ``False`` the gate instance is a shared singleton + and is not mutable. + """ + return True + + def to_mutable(self): + """Return a mutable copy of this gate. + + This method will return a new mutable copy of this gate instance. + If a singleton instance is being used this will be a new unique + instance that can be mutated. If the instance is already mutable it + will be a deepcopy of that instance. + """ + return self.copy() + + @property + def condition(self): + """The classical condition on the instruction.""" + return self._condition + + @condition.setter + def condition(self, condition): + self._condition = condition + def __eq__(self, other): """Two instructions are the same if they have the same name, same dimensions, and same params. @@ -409,7 +436,7 @@ class Instruction(Operation): # Casting the conditional value as Boolean when # the classical condition is on a classical bit. val = bool(val) - self.condition = (classical, val) + self._condition = (classical, val) return self def copy(self, name=None): diff --git a/qiskit/circuit/instructionset.py b/qiskit/circuit/instructionset.py index 8ea32f445a..2b1a3b756d 100644 --- a/qiskit/circuit/instructionset.py +++ b/qiskit/circuit/instructionset.py @@ -132,7 +132,7 @@ class InstructionSet: if self._requester is not None: classical = self._requester(classical) for instruction in self._instructions: - instruction.operation.c_if(classical, val) + instruction.operation = instruction.operation.c_if(classical, val) return self # Legacy support for properties. Added in Terra 0.21 to support the internal switch in diff --git a/qiskit/circuit/library/standard_gates/dcx.py b/qiskit/circuit/library/standard_gates/dcx.py index 836a81ffa4..fed5ae3b44 100644 --- a/qiskit/circuit/library/standard_gates/dcx.py +++ b/qiskit/circuit/library/standard_gates/dcx.py @@ -12,13 +12,13 @@ """Double-CNOT gate.""" -from qiskit.circuit.gate import Gate +from qiskit.circuit.singleton_gate import SingletonGate from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit._utils import with_gate_array @with_gate_array([[1, 0, 0, 0], [0, 0, 0, 1], [0, 1, 0, 0], [0, 0, 1, 0]]) -class DCXGate(Gate): +class DCXGate(SingletonGate): r"""Double-CNOT gate. A 2-qubit Clifford gate consisting of two back-to-back @@ -48,9 +48,14 @@ class DCXGate(Gate): \end{pmatrix} """ - def __init__(self): + def __init__(self, label=None, duration=None, unit=None, _condition=None): """Create new DCX gate.""" - super().__init__("dcx", 2, []) + if unit is None: + unit = "dt" + + super().__init__( + "dcx", 2, [], label=label, _condition=_condition, duration=duration, unit=unit + ) def _define(self): """ diff --git a/qiskit/circuit/library/standard_gates/ecr.py b/qiskit/circuit/library/standard_gates/ecr.py index cc7b13ecc0..abff2072da 100644 --- a/qiskit/circuit/library/standard_gates/ecr.py +++ b/qiskit/circuit/library/standard_gates/ecr.py @@ -15,8 +15,8 @@ from math import sqrt import numpy as np from qiskit.circuit._utils import with_gate_array -from qiskit.circuit.gate import Gate from qiskit.circuit.quantumregister import QuantumRegister +from qiskit.circuit.singleton_gate import SingletonGate from .rzx import RZXGate from .x import XGate @@ -24,7 +24,7 @@ from .x import XGate @with_gate_array( sqrt(0.5) * np.array([[0, 1, 0, 1.0j], [1, 0, -1.0j, 0], [0, 1.0j, 0, 1], [-1.0j, 0, 1, 0]]) ) -class ECRGate(Gate): +class ECRGate(SingletonGate): r"""An echoed cross-resonance gate. This gate is maximally entangling and is equivalent to a CNOT up to @@ -84,9 +84,13 @@ class ECRGate(Gate): \end{pmatrix} """ - def __init__(self): + def __init__(self, label=None, _condition=None, duration=None, unit=None): """Create new ECR gate.""" - super().__init__("ecr", 2, []) + if unit is None: + unit = "dt" + super().__init__( + "ecr", 2, [], label=label, _condition=_condition, duration=duration, unit=unit + ) def _define(self): """ diff --git a/qiskit/circuit/library/standard_gates/h.py b/qiskit/circuit/library/standard_gates/h.py index 539383436a..7a75b60940 100644 --- a/qiskit/circuit/library/standard_gates/h.py +++ b/qiskit/circuit/library/standard_gates/h.py @@ -15,7 +15,7 @@ from math import sqrt, pi from typing import Optional, Union import numpy from qiskit.circuit.controlledgate import ControlledGate -from qiskit.circuit.gate import Gate +from qiskit.circuit.singleton_gate import SingletonGate from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit._utils import with_gate_array, with_controlled_gate_array from .t import TGate, TdgGate @@ -25,7 +25,7 @@ _H_ARRAY = 1 / sqrt(2) * numpy.array([[1, 1], [1, -1]], dtype=numpy.complex128) @with_gate_array(_H_ARRAY) -class HGate(Gate): +class HGate(SingletonGate): r"""Single-qubit Hadamard gate. This gate is a \pi rotation about the X+Z axis, and has the effect of @@ -54,9 +54,13 @@ class HGate(Gate): \end{pmatrix} """ - def __init__(self, label: Optional[str] = None): + def __init__(self, label: Optional[str] = None, duration=None, unit=None, _condition=None): """Create new H gate.""" - super().__init__("h", 1, [], label=label) + if unit is None: + unit = "dt" + super().__init__( + "h", 1, [], label=label, _condition=_condition, duration=duration, unit=unit + ) def _define(self): """ @@ -94,8 +98,7 @@ class HGate(Gate): ControlledGate: controlled version of this gate. """ if num_ctrl_qubits == 1: - gate = CHGate(label=label, ctrl_state=ctrl_state) - gate.base_gate.label = self.label + gate = CHGate(label=label, ctrl_state=ctrl_state, _base_label=self.label) return gate return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) @@ -162,10 +165,21 @@ class CHGate(ControlledGate): \end{pmatrix} """ - def __init__(self, label: Optional[str] = None, ctrl_state: Optional[Union[int, str]] = None): + def __init__( + self, + label: Optional[str] = None, + ctrl_state: Optional[Union[int, str]] = None, + _base_label=None, + ): """Create new CH gate.""" super().__init__( - "ch", 2, [], num_ctrl_qubits=1, label=label, ctrl_state=ctrl_state, base_gate=HGate() + "ch", + 2, + [], + num_ctrl_qubits=1, + label=label, + ctrl_state=ctrl_state, + base_gate=HGate(label=_base_label), ) def _define(self): diff --git a/qiskit/circuit/library/standard_gates/i.py b/qiskit/circuit/library/standard_gates/i.py index b475712906..b8742665f6 100644 --- a/qiskit/circuit/library/standard_gates/i.py +++ b/qiskit/circuit/library/standard_gates/i.py @@ -13,12 +13,12 @@ """Identity gate.""" from typing import Optional -from qiskit.circuit.gate import Gate +from qiskit.circuit.singleton_gate import SingletonGate from qiskit.circuit._utils import with_gate_array @with_gate_array([[1, 0], [0, 1]]) -class IGate(Gate): +class IGate(SingletonGate): r"""Identity gate. Identity gate corresponds to a single-qubit gate wait cycle, @@ -45,9 +45,13 @@ class IGate(Gate): └───┘ """ - def __init__(self, label: Optional[str] = None): + def __init__(self, label: Optional[str] = None, duration=None, unit=None, _condition=None): """Create new Identity gate.""" - super().__init__("id", 1, [], label=label) + if unit is None: + unit = "dt" + super().__init__( + "id", 1, [], label=label, _condition=_condition, duration=duration, unit=unit + ) def inverse(self): """Invert this gate.""" diff --git a/qiskit/circuit/library/standard_gates/iswap.py b/qiskit/circuit/library/standard_gates/iswap.py index a0cca1f484..018715ef3d 100644 --- a/qiskit/circuit/library/standard_gates/iswap.py +++ b/qiskit/circuit/library/standard_gates/iswap.py @@ -16,7 +16,7 @@ from typing import Optional import numpy as np -from qiskit.circuit.gate import Gate +from qiskit.circuit.singleton_gate import SingletonGate from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit._utils import with_gate_array @@ -24,7 +24,7 @@ from .xx_plus_yy import XXPlusYYGate @with_gate_array([[1, 0, 0, 0], [0, 0, 1j, 0], [0, 1j, 0, 0], [0, 0, 0, 1]]) -class iSwapGate(Gate): +class iSwapGate(SingletonGate): r"""iSWAP gate. A 2-qubit XX+YY interaction. @@ -85,9 +85,13 @@ class iSwapGate(Gate): \end{pmatrix} """ - def __init__(self, label: Optional[str] = None): + def __init__(self, label: Optional[str] = None, duration=None, unit=None, _condition=None): """Create new iSwap gate.""" - super().__init__("iswap", 2, [], label=label) + if unit is None: + unit = "dt" + super().__init__( + "iswap", 2, [], label=label, _condition=_condition, duration=duration, unit=unit + ) def _define(self): """ diff --git a/qiskit/circuit/library/standard_gates/s.py b/qiskit/circuit/library/standard_gates/s.py index 67b3aafbe7..07349eb963 100644 --- a/qiskit/circuit/library/standard_gates/s.py +++ b/qiskit/circuit/library/standard_gates/s.py @@ -18,7 +18,7 @@ from typing import Optional, Union import numpy from qiskit.circuit.controlledgate import ControlledGate -from qiskit.circuit.gate import Gate +from qiskit.circuit.singleton_gate import SingletonGate from qiskit.circuit.library.standard_gates.p import CPhaseGate, PhaseGate from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit._utils import with_gate_array, with_controlled_gate_array @@ -29,7 +29,7 @@ _SDG_ARRAY = numpy.array([[1, 0], [0, -1j]]) @with_gate_array(_S_ARRAY) -class SGate(Gate): +class SGate(SingletonGate): r"""Single qubit S gate (Z**0.5). It induces a :math:`\pi/2` phase, and is sometimes called the P gate (phase). @@ -59,9 +59,13 @@ class SGate(Gate): Equivalent to a :math:`\pi/2` radian rotation about the Z axis. """ - def __init__(self, label: Optional[str] = None): + def __init__(self, label: Optional[str] = None, duration=None, unit=None, _condition=None): """Create new S gate.""" - super().__init__("s", 1, [], label=label) + if unit is None: + unit = "dt" + super().__init__( + "s", 1, [], label=label, _condition=_condition, duration=duration, unit=unit + ) def _define(self): """ @@ -90,7 +94,7 @@ class SGate(Gate): @with_gate_array(_SDG_ARRAY) -class SdgGate(Gate): +class SdgGate(SingletonGate): r"""Single qubit S-adjoint gate (~Z**0.5). It induces a :math:`-\pi/2` phase. @@ -120,9 +124,13 @@ class SdgGate(Gate): Equivalent to a :math:`-\pi/2` radian rotation about the Z axis. """ - def __init__(self, label: Optional[str] = None): + def __init__(self, label: Optional[str] = None, duration=None, unit=None, _condition=None): """Create new Sdg gate.""" - super().__init__("sdg", 1, [], label=label) + if unit is None: + unit = "dt" + super().__init__( + "sdg", 1, [], label=label, _condition=_condition, duration=duration, unit=unit + ) def _define(self): """ diff --git a/qiskit/circuit/library/standard_gates/swap.py b/qiskit/circuit/library/standard_gates/swap.py index 578efc3053..5f4cc76a87 100644 --- a/qiskit/circuit/library/standard_gates/swap.py +++ b/qiskit/circuit/library/standard_gates/swap.py @@ -15,7 +15,7 @@ from typing import Optional, Union import numpy from qiskit.circuit.controlledgate import ControlledGate -from qiskit.circuit.gate import Gate +from qiskit.circuit.singleton_gate import SingletonGate from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit._utils import with_gate_array, with_controlled_gate_array @@ -24,7 +24,7 @@ _SWAP_ARRAY = numpy.array([[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1 @with_gate_array(_SWAP_ARRAY) -class SwapGate(Gate): +class SwapGate(SingletonGate): r"""The SWAP gate. This is a symmetric and Clifford gate. @@ -59,9 +59,13 @@ class SwapGate(Gate): |a, b\rangle \rightarrow |b, a\rangle """ - def __init__(self, label: Optional[str] = None): + def __init__(self, label: Optional[str] = None, duration=None, unit=None, _condition=None): """Create new SWAP gate.""" - super().__init__("swap", 2, [], label=label) + if unit is None: + unit = "dt" + super().__init__( + "swap", 2, [], label=label, _condition=_condition, duration=duration, unit=unit + ) def _define(self): """ @@ -103,8 +107,7 @@ class SwapGate(Gate): ControlledGate: controlled version of this gate. """ if num_ctrl_qubits == 1: - gate = CSwapGate(label=label, ctrl_state=ctrl_state) - gate.base_gate.label = self.label + gate = CSwapGate(label=label, ctrl_state=ctrl_state, _base_label=self.label) return gate return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) @@ -191,7 +194,12 @@ class CSwapGate(ControlledGate): |1, b, c\rangle \rightarrow |1, c, b\rangle """ - def __init__(self, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None): + def __init__( + self, + label: Optional[str] = None, + ctrl_state: Optional[Union[str, int]] = None, + _base_label=None, + ): """Create new CSWAP gate.""" super().__init__( "cswap", @@ -200,7 +208,7 @@ class CSwapGate(ControlledGate): num_ctrl_qubits=1, label=label, ctrl_state=ctrl_state, - base_gate=SwapGate(), + base_gate=SwapGate(label=_base_label), ) def _define(self): diff --git a/qiskit/circuit/library/standard_gates/sx.py b/qiskit/circuit/library/standard_gates/sx.py index a15b38787c..49fbb11c55 100644 --- a/qiskit/circuit/library/standard_gates/sx.py +++ b/qiskit/circuit/library/standard_gates/sx.py @@ -16,7 +16,7 @@ from math import pi from typing import Optional, Union import numpy from qiskit.circuit.controlledgate import ControlledGate -from qiskit.circuit.gate import Gate +from qiskit.circuit.singleton_gate import SingletonGate from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit._utils import with_gate_array, with_controlled_gate_array @@ -26,7 +26,7 @@ _SXDG_ARRAY = [[0.5 - 0.5j, 0.5 + 0.5j], [0.5 + 0.5j, 0.5 - 0.5j]] @with_gate_array(_SX_ARRAY) -class SXGate(Gate): +class SXGate(SingletonGate): r"""The single-qubit Sqrt(X) gate (:math:`\sqrt{X}`). Can be applied to a :class:`~qiskit.circuit.QuantumCircuit` @@ -64,9 +64,13 @@ class SXGate(Gate): """ - def __init__(self, label: Optional[str] = None): + def __init__(self, label: Optional[str] = None, duration=None, unit=None, _condition=None): """Create new SX gate.""" - super().__init__("sx", 1, [], label=label) + if unit is None: + unit = "dt" + super().__init__( + "sx", 1, [], label=label, _condition=_condition, duration=duration, unit=unit + ) def _define(self): """ @@ -108,14 +112,13 @@ class SXGate(Gate): ControlledGate: controlled version of this gate. """ if num_ctrl_qubits == 1: - gate = CSXGate(label=label, ctrl_state=ctrl_state) - gate.base_gate.label = self.label + gate = CSXGate(label=label, ctrl_state=ctrl_state, _base_label=self.label) return gate return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) @with_gate_array(_SXDG_ARRAY) -class SXdgGate(Gate): +class SXdgGate(SingletonGate): r"""The inverse single-qubit Sqrt(X) gate. Can be applied to a :class:`~qiskit.circuit.QuantumCircuit` @@ -148,9 +151,13 @@ class SXdgGate(Gate): ) _ARRAY.setflags(write=False) - def __init__(self, label: Optional[str] = None): + def __init__(self, label: Optional[str] = None, duration=None, unit=None, _condition=None): """Create new SXdg gate.""" - super().__init__("sxdg", 1, [], label=label) + if unit is None: + unit = "dt" + super().__init__( + "sxdg", 1, [], label=label, _condition=_condition, duration=duration, unit=unit + ) def _define(self): """ @@ -230,10 +237,21 @@ class CSXGate(ControlledGate): """ - def __init__(self, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None): + def __init__( + self, + label: Optional[str] = None, + ctrl_state: Optional[Union[str, int]] = None, + _base_label=None, + ): """Create new CSX gate.""" super().__init__( - "csx", 2, [], num_ctrl_qubits=1, label=label, ctrl_state=ctrl_state, base_gate=SXGate() + "csx", + 2, + [], + num_ctrl_qubits=1, + label=label, + ctrl_state=ctrl_state, + base_gate=SXGate(label=_base_label), ) def _define(self): diff --git a/qiskit/circuit/library/standard_gates/t.py b/qiskit/circuit/library/standard_gates/t.py index 67a14a12e1..e81d337737 100644 --- a/qiskit/circuit/library/standard_gates/t.py +++ b/qiskit/circuit/library/standard_gates/t.py @@ -17,14 +17,14 @@ from typing import Optional import numpy -from qiskit.circuit.gate import Gate +from qiskit.circuit.singleton_gate import SingletonGate from qiskit.circuit.library.standard_gates.p import PhaseGate from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit._utils import with_gate_array @with_gate_array([[1, 0], [0, (1 + 1j) / math.sqrt(2)]]) -class TGate(Gate): +class TGate(SingletonGate): r"""Single qubit T gate (Z**0.25). It induces a :math:`\pi/4` phase, and is sometimes called the pi/8 gate @@ -55,9 +55,13 @@ class TGate(Gate): Equivalent to a :math:`\pi/4` radian rotation about the Z axis. """ - def __init__(self, label: Optional[str] = None): + def __init__(self, label: Optional[str] = None, duration=None, unit=None, _condition=None): """Create new T gate.""" - super().__init__("t", 1, [], label=label) + if unit is None: + unit = "dt" + super().__init__( + "t", 1, [], label=label, _condition=_condition, duration=duration, unit=unit + ) def _define(self): """ @@ -86,7 +90,7 @@ class TGate(Gate): @with_gate_array([[1, 0], [0, (1 - 1j) / math.sqrt(2)]]) -class TdgGate(Gate): +class TdgGate(SingletonGate): r"""Single qubit T-adjoint gate (~Z**0.25). It induces a :math:`-\pi/4` phase. @@ -116,9 +120,13 @@ class TdgGate(Gate): Equivalent to a :math:`-\pi/4` radian rotation about the Z axis. """ - def __init__(self, label: Optional[str] = None): + def __init__(self, label: Optional[str] = None, duration=None, unit=None, _condition=None): """Create new Tdg gate.""" - super().__init__("tdg", 1, [], label=label) + if unit is None: + unit = "dt" + super().__init__( + "tdg", 1, [], label=label, _condition=_condition, duration=duration, unit=unit + ) def _define(self): """ diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py index b48181be80..cc17b8060e 100644 --- a/qiskit/circuit/library/standard_gates/x.py +++ b/qiskit/circuit/library/standard_gates/x.py @@ -17,7 +17,7 @@ from math import ceil, pi import numpy from qiskit.utils.deprecation import deprecate_func from qiskit.circuit.controlledgate import ControlledGate -from qiskit.circuit.gate import Gate +from qiskit.circuit.singleton_gate import SingletonGate from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit._utils import _ctrl_state_to_int, with_gate_array, with_controlled_gate_array from .h import HGate @@ -30,7 +30,7 @@ _X_ARRAY = [[0, 1], [1, 0]] @with_gate_array(_X_ARRAY) -class XGate(Gate): +class XGate(SingletonGate): r"""The single-qubit Pauli-X gate (:math:`\sigma_x`). Can be applied to a :class:`~qiskit.circuit.QuantumCircuit` @@ -76,9 +76,13 @@ class XGate(Gate): |1\rangle \rightarrow |0\rangle """ - def __init__(self, label: Optional[str] = None): + def __init__(self, label: Optional[str] = None, duration=None, unit=None, _condition=None): """Create new X gate.""" - super().__init__("x", 1, [], label=label) + if unit is None: + unit = "dt" + super().__init__( + "x", 1, [], label=label, _condition=_condition, duration=duration, unit=unit + ) def _define(self): """ @@ -115,8 +119,12 @@ class XGate(Gate): Returns: ControlledGate: controlled version of this gate. """ - gate = MCXGate(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) - gate.base_gate.label = self.label + gate = MCXGate( + num_ctrl_qubits=num_ctrl_qubits, + label=label, + ctrl_state=ctrl_state, + _base_label=self.label, + ) return gate def inverse(self): @@ -188,10 +196,21 @@ class CXGate(ControlledGate): `|a, b\rangle \rightarrow |a, a \oplus b\rangle` """ - def __init__(self, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None): + def __init__( + self, + label: Optional[str] = None, + ctrl_state: Optional[Union[str, int]] = None, + _base_label=None, + ): """Create new CX gate.""" super().__init__( - "cx", 2, [], num_ctrl_qubits=1, label=label, ctrl_state=ctrl_state, base_gate=XGate() + "cx", + 2, + [], + num_ctrl_qubits=1, + label=label, + ctrl_state=ctrl_state, + base_gate=XGate(label=_base_label), ) def control( @@ -213,8 +232,12 @@ class CXGate(ControlledGate): """ ctrl_state = _ctrl_state_to_int(ctrl_state, num_ctrl_qubits) new_ctrl_state = (self.ctrl_state << num_ctrl_qubits) | ctrl_state - gate = MCXGate(num_ctrl_qubits=num_ctrl_qubits + 1, label=label, ctrl_state=new_ctrl_state) - gate.base_gate.label = self.label + gate = MCXGate( + num_ctrl_qubits=num_ctrl_qubits + 1, + label=label, + ctrl_state=new_ctrl_state, + _base_label=self.label, + ) return gate def inverse(self): @@ -291,10 +314,21 @@ class CCXGate(ControlledGate): """ - def __init__(self, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None): + def __init__( + self, + label: Optional[str] = None, + ctrl_state: Optional[Union[str, int]] = None, + _base_label=None, + ): """Create new CCX gate.""" super().__init__( - "ccx", 3, [], num_ctrl_qubits=2, label=label, ctrl_state=ctrl_state, base_gate=XGate() + "ccx", + 3, + [], + num_ctrl_qubits=2, + label=label, + ctrl_state=ctrl_state, + base_gate=XGate(label=_base_label), ) def _define(self): @@ -359,8 +393,12 @@ class CCXGate(ControlledGate): """ ctrl_state = _ctrl_state_to_int(ctrl_state, num_ctrl_qubits) new_ctrl_state = (self.ctrl_state << num_ctrl_qubits) | ctrl_state - gate = MCXGate(num_ctrl_qubits=num_ctrl_qubits + 2, label=label, ctrl_state=new_ctrl_state) - gate.base_gate.label = self.label + gate = MCXGate( + num_ctrl_qubits=num_ctrl_qubits + 2, + label=label, + ctrl_state=new_ctrl_state, + _base_label=self.label, + ) return gate def inverse(self): @@ -380,7 +418,7 @@ class CCXGate(ControlledGate): [0, 0, 0, 1j, 0, 0, 0, 0], ] ) -class RCCXGate(Gate): +class RCCXGate(SingletonGate): """The simplified Toffoli gate, also referred to as Margolus gate. The simplified Toffoli gate implements the Toffoli gate up to relative phases. @@ -396,9 +434,13 @@ class RCCXGate(Gate): with the :meth:`~qiskit.circuit.QuantumCircuit.rccx` method. """ - def __init__(self, label: Optional[str] = None): + def __init__(self, label: Optional[str] = None, duration=None, unit=None, _condition=None): """Create a new simplified CCX gate.""" - super().__init__("rccx", 3, [], label=label) + if unit is None: + unit = "dt" + super().__init__( + "rccx", 3, [], label=label, _condition=_condition, duration=duration, unit=unit + ) def _define(self): """ @@ -675,7 +717,7 @@ class C3XGate(ControlledGate): [0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0], ] ) -class RC3XGate(Gate): +class RC3XGate(SingletonGate): """The simplified 3-controlled Toffoli gate. The simplified Toffoli gate implements the Toffoli gate up to relative phases. @@ -689,9 +731,13 @@ class RC3XGate(Gate): with the :meth:`~qiskit.circuit.QuantumCircuit.rcccx` method. """ - def __init__(self, label: Optional[str] = None): + def __init__(self, label: Optional[str] = None, duration=None, unit=None, _condition=None): """Create a new RC3X gate.""" - super().__init__("rcccx", 4, [], label=label) + if unit is None: + unit = "dt" + super().__init__( + "rcccx", 4, [], label=label, _condition=_condition, duration=duration, unit=unit + ) def _define(self): """ @@ -855,6 +901,7 @@ class MCXGate(ControlledGate): num_ctrl_qubits: Optional[int] = None, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + _base_label=None, ): """Create a new MCX instance. @@ -866,9 +913,11 @@ class MCXGate(ControlledGate): explicit: dict[int, Type[ControlledGate]] = {1: CXGate, 2: CCXGate} if num_ctrl_qubits in explicit: gate_class = explicit[num_ctrl_qubits] - gate = gate_class.__new__(gate_class, label=label, ctrl_state=ctrl_state) + gate = gate_class.__new__( + gate_class, label=label, ctrl_state=ctrl_state, _base_label=_base_label + ) # if __new__ does not return the same type as cls, init is not called - gate.__init__(label=label, ctrl_state=ctrl_state) + gate.__init__(label=label, ctrl_state=ctrl_state, _base_label=_base_label) return gate return super().__new__(cls) @@ -878,6 +927,7 @@ class MCXGate(ControlledGate): label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, _name="mcx", + _base_label=None, ): """Create new MCX gate.""" num_ancilla_qubits = self.__class__.get_num_ancilla_qubits(num_ctrl_qubits) @@ -888,7 +938,7 @@ class MCXGate(ControlledGate): num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state, - base_gate=XGate(), + base_gate=XGate(label=_base_label), ) def inverse(self): @@ -963,6 +1013,7 @@ class MCXGrayCode(MCXGate): num_ctrl_qubits: Optional[int] = None, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + _base_label=None, ): """Create a new MCXGrayCode instance""" # if 1 to 4 control qubits, create explicit gates diff --git a/qiskit/circuit/library/standard_gates/y.py b/qiskit/circuit/library/standard_gates/y.py index 65be3087c8..b4e9509903 100644 --- a/qiskit/circuit/library/standard_gates/y.py +++ b/qiskit/circuit/library/standard_gates/y.py @@ -17,7 +17,7 @@ from typing import Optional, Union # pylint: disable=cyclic-import from qiskit.circuit.controlledgate import ControlledGate -from qiskit.circuit.gate import Gate +from qiskit.circuit.singleton_gate import SingletonGate from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit._utils import with_gate_array, with_controlled_gate_array @@ -25,7 +25,7 @@ _Y_ARRAY = [[0, -1j], [1j, 0]] @with_gate_array(_Y_ARRAY) -class YGate(Gate): +class YGate(SingletonGate): r"""The single-qubit Pauli-Y gate (:math:`\sigma_y`). Can be applied to a :class:`~qiskit.circuit.QuantumCircuit` @@ -71,9 +71,13 @@ class YGate(Gate): |1\rangle \rightarrow -i|0\rangle """ - def __init__(self, label: Optional[str] = None): + def __init__(self, label: Optional[str] = None, duration=None, unit=None, _condition=None): """Create new Y gate.""" - super().__init__("y", 1, [], label=label) + if unit is None: + unit = "dt" + super().__init__( + "y", 1, [], label=label, _condition=_condition, duration=duration, unit=unit + ) def _define(self): # pylint: disable=cyclic-import @@ -108,8 +112,7 @@ class YGate(Gate): ControlledGate: controlled version of this gate. """ if num_ctrl_qubits == 1: - gate = CYGate(label=label, ctrl_state=ctrl_state) - gate.base_gate.label = self.label + gate = CYGate(label=label, ctrl_state=ctrl_state, _base_label=self.label) return gate return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) @@ -175,10 +178,21 @@ class CYGate(ControlledGate): """ - def __init__(self, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None): + def __init__( + self, + label: Optional[str] = None, + ctrl_state: Optional[Union[str, int]] = None, + _base_label=None, + ): """Create new CY gate.""" super().__init__( - "cy", 2, [], num_ctrl_qubits=1, label=label, ctrl_state=ctrl_state, base_gate=YGate() + "cy", + 2, + [], + num_ctrl_qubits=1, + label=label, + ctrl_state=ctrl_state, + base_gate=YGate(label=_base_label), ) def _define(self): diff --git a/qiskit/circuit/library/standard_gates/z.py b/qiskit/circuit/library/standard_gates/z.py index 978688d190..9d8ba19d5f 100644 --- a/qiskit/circuit/library/standard_gates/z.py +++ b/qiskit/circuit/library/standard_gates/z.py @@ -19,7 +19,7 @@ import numpy from qiskit.circuit._utils import with_gate_array, with_controlled_gate_array from qiskit.circuit.controlledgate import ControlledGate -from qiskit.circuit.gate import Gate +from qiskit.circuit.singleton_gate import SingletonGate from qiskit.circuit.quantumregister import QuantumRegister from .p import PhaseGate @@ -28,7 +28,7 @@ _Z_ARRAY = [[1, 0], [0, -1]] @with_gate_array(_Z_ARRAY) -class ZGate(Gate): +class ZGate(SingletonGate): r"""The single-qubit Pauli-Z gate (:math:`\sigma_z`). Can be applied to a :class:`~qiskit.circuit.QuantumCircuit` @@ -74,9 +74,13 @@ class ZGate(Gate): |1\rangle \rightarrow -|1\rangle """ - def __init__(self, label: Optional[str] = None): + def __init__(self, label: Optional[str] = None, duration=None, unit=None, _condition=None): """Create new Z gate.""" - super().__init__("z", 1, [], label=label) + if unit is None: + unit = "dt" + super().__init__( + "z", 1, [], label=label, _condition=_condition, duration=duration, unit=unit + ) def _define(self): # pylint: disable=cyclic-import @@ -112,8 +116,7 @@ class ZGate(Gate): ControlledGate: controlled version of this gate. """ if num_ctrl_qubits == 1: - gate = CZGate(label=label, ctrl_state=ctrl_state) - gate.base_gate.label = self.label + gate = CZGate(label=label, ctrl_state=ctrl_state, _base_label=self.label) return gate return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) @@ -160,10 +163,21 @@ class CZGate(ControlledGate): the target qubit if the control qubit is in the :math:`|1\rangle` state. """ - def __init__(self, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None): + def __init__( + self, + label: Optional[str] = None, + ctrl_state: Optional[Union[str, int]] = None, + _base_label=None, + ): """Create new CZ gate.""" super().__init__( - "cz", 2, [], label=label, num_ctrl_qubits=1, ctrl_state=ctrl_state, base_gate=ZGate() + "cz", + 2, + [], + label=label, + num_ctrl_qubits=1, + ctrl_state=ctrl_state, + base_gate=ZGate(label=_base_label), ) def _define(self): diff --git a/qiskit/circuit/random/utils.py b/qiskit/circuit/random/utils.py index b8ceb36496..d8c863a81d 100644 --- a/qiskit/circuit/random/utils.py +++ b/qiskit/circuit/random/utils.py @@ -193,7 +193,7 @@ def random_circuit( if is_cond: qc.measure(qc.qubits, cr) # The condition values are required to be bigints, not Numpy's fixed-width type. - operation.condition = (cr, int(condition_values[c_ptr])) + operation = operation.c_if(cr, int(condition_values[c_ptr])) c_ptr += 1 qc._append(CircuitInstruction(operation=operation, qubits=qubits[q_start:q_end])) else: diff --git a/qiskit/circuit/singleton_gate.py b/qiskit/circuit/singleton_gate.py new file mode 100644 index 0000000000..3d24247e31 --- /dev/null +++ b/qiskit/circuit/singleton_gate.py @@ -0,0 +1,188 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023 +# +# 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. +""" +Singleton gate classes. +""" +import copy + +from qiskit.circuit.gate import Gate +from qiskit.circuit.classicalregister import ClassicalRegister, Clbit +from qiskit.circuit.exceptions import CircuitError + + +SINGLETONGATE_ATTR_SET = frozenset( + ( + "definition", + "unit", + "duration", + "condition", + "label", + "_label", + "_condition", + "_duration", + "_unit", + "_definition", + "_name", + "_num_qubits", + "_num_clbits", + "_params", + "params", + ) +) + + +class SingletonGate(Gate): + """A base class to use for Gate objects that by default are singleton instances + + This class should be used for gate classes that have fixed definitions and + do not contain any unique state. The canonical example of something like + this is :class:`~.HGate` which has an immutable definition and any + instance of :class:`~.HGate` is the same. Using singleton gates + as a base class for these types of gate classes provides a large + advantage in the memory footprint of multiple gates. + + The exception to be aware of with this class though are the :class:`~.Gate` + attributes :attr:`.label`, :attr:`.condition`, :attr:`.duration`, and + :attr:`.unit` which can be set differently for specific instances of gates. + For :class:`~.SingletonGate` usage to be sound setting these attributes + is not available and they can only be set at creation time. If any of these + attributes are used, then instead of using a single shared global instance + of the same gate a new separate instance will be created. + """ + + _instance = None + + def __new__(cls, *args, **kwargs): + if args or ( # pylint: disable=too-many-boolean-expressions + kwargs + and ( + "label" in kwargs + or "_condition" in kwargs + or "duration" in kwargs + or "unit" in kwargs + ) + ): + return super().__new__(cls) + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + + def __init__(self, *args, _condition=None, **kwargs): + super().__init__(*args, **kwargs) + self._condition = _condition + + def c_if(self, classical, val): + if not isinstance(classical, (ClassicalRegister, Clbit)): + raise CircuitError("c_if must be used with a classical register or classical bit") + if val < 0: + raise CircuitError("condition value should be non-negative") + if isinstance(classical, Clbit): + # Casting the conditional value as Boolean when + # the classical condition is on a classical bit. + val = bool(val) + instance = type(self)( + label=self.label, _condition=(classical, val), duration=self.duration, unit=self.unit + ) + return instance + + @property + def mutable(self) -> bool: + return self is not self._instance + + def to_mutable(self): + if not self.mutable: + instance = super().__new__(type(self)) + # Coming from a shared singleton none of the arguments to + # __init__ can be set, so this is the correct behavior for + # initializing a new mutable instance + instance.__init__() + return instance + else: + return copy.deepcopy(self) + + @property + def label(self) -> str: + return self._label + + @label.setter + def label(self, label: str): + if self is self._instance: + raise NotImplementedError( + f"This gate class {type(self)} does not support manually setting a " + "label on an instance. Instead you must set the label when instantiating a new object." + ) + self._label = label + + @property + def condition(self): + return self._condition + + @condition.setter + def condition(self, condition): + if self is self._instance: + raise NotImplementedError( + f"This gate class {type(self)} does not support manually setting a " + "condition on an instance. Instead you must set the label when instantiating a new " + "object or via the .c_if() method" + ) + self._condition = condition + + @property + def duration(self): + return self._duration + + @duration.setter + def duration(self, duration): + if self is self._instance: + raise NotImplementedError( + f"This gate class {type(self)} does not support manually setting a " + "duration on an instance. Instead you must set the duration when instantiating a " + "new object." + ) + self._duration = duration + + @property + def unit(self): + return self._unit + + @unit.setter + def unit(self, unit): + if self is self._instance: + raise NotImplementedError( + f"This gate class {type(self)} does not support manually setting a " + "unit on an instance. Instead you must set the unit when instantiating a " + "new object." + ) + self._unit = unit + + def __deepcopy__(self, _memo=None): + if not self.mutable: + return self + else: + return type(self)( + label=self.label, _condition=self.condition, duration=self.duration, unit=self.unit + ) + + def __setattr__(self, name, value): + if self.mutable: + super().__setattr__(name, value) + else: + if name not in SINGLETONGATE_ATTR_SET: + raise NotImplementedError( + "Setting custom attributes is not allowed on a singleton gate" + ) + super().__setattr__(name, value) + + def copy(self, name=None): + if not self.mutable and name is None: + return self + return super().copy(name=name) diff --git a/qiskit/converters/ast_to_dag.py b/qiskit/converters/ast_to_dag.py index 76541d8c89..9fb3361d95 100644 --- a/qiskit/converters/ast_to_dag.py +++ b/qiskit/converters/ast_to_dag.py @@ -234,7 +234,8 @@ class AstInterpreter: maxidx = max([len(id0), len(id1)]) for idx in range(maxidx): cx_gate = std.CXGate() - cx_gate.condition = self.condition + if self.condition: + cx_gate = cx_gate.c_if(*self.condition) if len(id0) > 1 and len(id1) > 1: self.dag.apply_operation_back(cx_gate, [id0[idx], id1[idx]], [], check=False) elif len(id0) > 1: @@ -252,7 +253,8 @@ class AstInterpreter: ) for idx, idy in zip(id0, id1): meas_gate = Measure() - meas_gate.condition = self.condition + if self.condition: + meas_gate = meas_gate.c_if(*self.condition) self.dag.apply_operation_back(meas_gate, [idx], [idy], check=False) def _process_if(self, node): @@ -341,7 +343,8 @@ class AstInterpreter: id0 = self._process_bit_id(node.children[0]) for i, _ in enumerate(id0): reset = Reset() - reset.condition = self.condition + if self.condition: + reset = reset.c_if(*self.condition) self.dag.apply_operation_back(reset, [id0[i]], [], check=False) elif node.type == "if": @@ -398,7 +401,8 @@ class AstInterpreter: QiskitError: if encountering a non-basis opaque gate """ op = self._create_op(name, params) - op.condition = self.condition + if self.condition: + op = op.c_if(*self.condition) self.dag.apply_operation_back(op, qargs, [], check=False) def _create_op(self, name, params): diff --git a/qiskit/converters/circuit_to_instruction.py b/qiskit/converters/circuit_to_instruction.py index 7965e3a474..793362281b 100644 --- a/qiskit/converters/circuit_to_instruction.py +++ b/qiskit/converters/circuit_to_instruction.py @@ -81,7 +81,7 @@ def circuit_to_instruction(circuit, parameter_map=None, equivalence_library=None params=[*parameter_dict.values()], label=label, ) - out_instruction.condition = None + out_instruction._condition = None target = circuit.assign_parameters(parameter_dict, inplace=False) @@ -114,9 +114,9 @@ def circuit_to_instruction(circuit, parameter_map=None, equivalence_library=None if condition: reg, val = condition if isinstance(reg, Clbit): - rule.operation.condition = (clbit_map[reg], val) + rule.operation = rule.operation.c_if(clbit_map[reg], val) elif reg.size == c.size: - rule.operation.condition = (c, val) + rule.operation = rule.operation.c_if(c, val) else: raise QiskitError( "Cannot convert condition in circuit with " diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index f60223698e..e11b218efb 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -833,7 +833,10 @@ class DAGCircuit: m_cargs = [edge_map.get(x, x) for x in nd.cargs] op = nd.op.copy() if (condition := getattr(op, "condition", None)) is not None: - op.condition = variable_mapper.map_condition(condition, allow_reorder=True) + if not isinstance(op, ControlFlowOp): + op = op.c_if(*variable_mapper.map_condition(condition, allow_reorder=True)) + else: + op.condition = variable_mapper.map_condition(condition, allow_reorder=True) elif isinstance(op, SwitchCaseOp): op.target = variable_mapper.map_target(op.target) dag.apply_operation_back(op, m_qargs, m_cargs, check=False) @@ -1272,7 +1275,11 @@ class DAGCircuit: "cannot propagate a condition to an element that acts on those bits" ) new_op = copy.copy(in_node.op) - new_op.condition = new_condition + if new_condition: + if not isinstance(new_op, ControlFlowOp): + new_op = new_op.c_if(*new_condition) + else: + new_op.condition = new_condition in_dag.apply_operation_back(new_op, in_node.qargs, in_node.cargs, check=False) else: in_dag = input_dag @@ -1356,8 +1363,13 @@ class DAGCircuit: label=old_node.op.label, ) elif getattr(old_node.op, "condition", None) is not None: - m_op = copy.copy(old_node.op) - m_op.condition = variable_mapper.map_condition(m_op.condition) + m_op = old_node.op + if not isinstance(old_node.op, ControlFlowOp): + new_condition = variable_mapper.map_condition(m_op.condition) + if new_condition is not None: + m_op = m_op.c_if(*new_condition) + else: + m_op.condition = variable_mapper.map_condition(m_op.condition) else: m_op = old_node.op m_qargs = [wire_map[x] for x in old_node.qargs] @@ -1430,7 +1442,10 @@ class DAGCircuit: if (old_condition := getattr(node.op, "condition", None)) is not None: if not isinstance(op, Instruction): raise DAGCircuitError("Cannot add a condition on a generic Operation.") - op.condition = old_condition + if not isinstance(node.op, ControlFlowOp): + op = op.c_if(*old_condition) + else: + op.condition = old_condition new_wires.update(condition_resources(old_condition).clbits) if new_wires != current_wires: diff --git a/qiskit/qasm2/parse.py b/qiskit/qasm2/parse.py index 6f56d31b0a..116c6b7c9a 100644 --- a/qiskit/qasm2/parse.py +++ b/qiskit/qasm2/parse.py @@ -227,8 +227,7 @@ def from_bytecode(bytecode, custom_instructions: Iterable[CustomInstruction]): ) elif opcode == OpCode.ConditionedGate: gate_id, parameters, op_qubits, creg, value = op.operands - gate = gates[gate_id](*parameters) - gate.condition = (qc.cregs[creg], value) + gate = gates[gate_id](*parameters).c_if(qc.cregs[creg], value) qc._append(CircuitInstruction(gate, [qubits[q] for q in op_qubits])) elif opcode == OpCode.Measure: qubit, clbit = op.operands diff --git a/qiskit/qpy/binary_io/circuits.py b/qiskit/qpy/binary_io/circuits.py index 5266bbb034..91fe62f3e3 100644 --- a/qiskit/qpy/binary_io/circuits.py +++ b/qiskit/qpy/binary_io/circuits.py @@ -25,10 +25,11 @@ import numpy as np from qiskit import circuit as circuit_mod from qiskit import extensions -from qiskit.circuit import library, controlflow, CircuitInstruction +from qiskit.circuit import library, controlflow, CircuitInstruction, ControlFlowOp from qiskit.circuit.classical import expr from qiskit.circuit.classicalregister import ClassicalRegister, Clbit from qiskit.circuit.gate import Gate +from qiskit.circuit.singleton_gate import SingletonGate from qiskit.circuit.controlledgate import ControlledGate from qiskit.circuit.instruction import Instruction from qiskit.circuit.quantumcircuit import QuantumCircuit @@ -272,8 +273,10 @@ def _read_instruction(file_obj, circuit, registers, custom_operations, version, else: raise AttributeError("Invalid instruction type: %s" % gate_name) + if instruction.label_size <= 0: + label = None if gate_name in {"IfElseOp", "WhileLoopOp"}: - gate = gate_class(condition, *params) + gate = gate_class(condition, *params, label=label) elif version >= 5 and issubclass(gate_class, ControlledGate): if gate_name in { "MCPhaseGate", @@ -283,9 +286,9 @@ def _read_instruction(file_obj, circuit, registers, custom_operations, version, "MCXRecursive", "MCXVChain", }: - gate = gate_class(*params, instruction.num_ctrl_qubits) + gate = gate_class(*params, instruction.num_ctrl_qubits, label=label) else: - gate = gate_class(*params) + gate = gate_class(*params, label=label) gate.num_ctrl_qubits = instruction.num_ctrl_qubits gate.ctrl_state = instruction.ctrl_state gate.condition = condition @@ -304,10 +307,19 @@ def _read_instruction(file_obj, circuit, registers, custom_operations, version, params = [len(qargs)] elif gate_name in {"BreakLoopOp", "ContinueLoopOp"}: params = [len(qargs), len(cargs)] - gate = gate_class(*params) - gate.condition = condition - if instruction.label_size > 0: - gate.label = label + if label is not None: + if issubclass(gate_class, SingletonGate): + gate = gate_class(*params, label=label) + else: + gate = gate_class(*params) + gate.label = label + else: + gate = gate_class(*params) + if condition: + if not isinstance(gate, ControlFlowOp): + gate = gate.c_if(*condition) + else: + gate.condition = condition if circuit is None: return gate if not isinstance(gate, Instruction): diff --git a/qiskit/transpiler/passes/optimization/reset_after_measure_simplification.py b/qiskit/transpiler/passes/optimization/reset_after_measure_simplification.py index 4445e878ce..d494857840 100644 --- a/qiskit/transpiler/passes/optimization/reset_after_measure_simplification.py +++ b/qiskit/transpiler/passes/optimization/reset_after_measure_simplification.py @@ -36,8 +36,7 @@ class ResetAfterMeasureSimplification(TransformationPass): for node in dag.op_nodes(Measure): succ = next(dag.quantum_successors(node)) if isinstance(succ, DAGOpNode) and isinstance(succ.op, Reset): - new_x = XGate() - new_x.condition = (node.cargs[0], 1) + new_x = XGate().c_if(node.cargs[0], 1) new_dag = DAGCircuit() new_dag.add_qubits(node.qargs) new_dag.add_clbits(node.cargs) diff --git a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py index 7e5245b2e3..bc606a0f71 100644 --- a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py +++ b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py @@ -192,8 +192,11 @@ class DynamicalDecoupling(TransformationPass): index_sequence_duration_map = {} for physical_qubit in self._qubits: dd_sequence_duration = 0 - for gate in self._dd_sequence: + for index, gate in enumerate(self._dd_sequence): + gate = gate.to_mutable() + self._dd_sequence[index] = gate gate.duration = self._durations.get(gate, physical_qubit) + dd_sequence_duration += gate.duration index_sequence_duration_map[physical_qubit] = dd_sequence_duration diff --git a/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py index fb33361a49..2d4114f3cf 100644 --- a/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py +++ b/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py @@ -224,7 +224,7 @@ class PadDynamicalDecoupling(BasePadding): continue sequence_lengths = [] - for gate in self._dd_sequence: + for index, gate in enumerate(self._dd_sequence): try: # Check calibration. params = self._resolve_params(gate) @@ -246,6 +246,8 @@ class PadDynamicalDecoupling(BasePadding): gate_length = self._durations.get(gate, physical_index) sequence_lengths.append(gate_length) # Update gate duration. This is necessary for current timeline drawer, i.e. scheduled. + gate = gate.to_mutable() + self._dd_sequence[index] = gate gate.duration = gate_length self._dd_sequence_lengths[qubit] = sequence_lengths diff --git a/qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py b/qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py index f83aed8000..3792a149fd 100644 --- a/qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py +++ b/qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py @@ -70,6 +70,7 @@ class BaseScheduler(AnalysisPass): duration = dag.calibrations[node.op.name][cal_key].duration # Note that node duration is updated (but this is analysis pass) + node.op = node.op.to_mutable() node.op.duration = duration else: duration = node.op.duration diff --git a/qiskit/transpiler/passes/scheduling/time_unit_conversion.py b/qiskit/transpiler/passes/scheduling/time_unit_conversion.py index a176f0aa5c..c75e22f285 100644 --- a/qiskit/transpiler/passes/scheduling/time_unit_conversion.py +++ b/qiskit/transpiler/passes/scheduling/time_unit_conversion.py @@ -96,13 +96,14 @@ class TimeUnitConversion(TransformationPass): # Make units consistent for node in dag.op_nodes(): try: - node.op = node.op.copy() - node.op.duration = self.inst_durations.get( + duration = self.inst_durations.get( node.op, [dag.find_bit(qarg).index for qarg in node.qargs], unit=time_unit ) - node.op.unit = time_unit except TranspilerError: - pass + continue + node.op = node.op.to_mutable() + node.op.duration = duration + node.op.unit = time_unit self.property_set["time_unit"] = time_unit return dag diff --git a/releasenotes/notes/singletons-83782de8bd062cbc.yaml b/releasenotes/notes/singletons-83782de8bd062cbc.yaml new file mode 100644 index 0000000000..db6b761a39 --- /dev/null +++ b/releasenotes/notes/singletons-83782de8bd062cbc.yaml @@ -0,0 +1,122 @@ +--- +features: + - | + Introduced a new class :class:`~.SingletonGate` which is a subclass of + :class:`~.Gate` that uses a single instance for all objects of that type. + The intent behind this class is to minimize the memory and construction + overhead of using multiple gates in a circuit with the tradeoff of having + global shared state. For this reason this class is only applicable to + gates that do not have any unique and/or mutable state stored in an instance. + For example, the best example of this is :class:`.XGate` doesn't contain + any state and could leveerage :class:`~.SingletonGate` (and does starting in + this release), while :class:`~.RXGate` stores an angle parameter in an instance + and thus can not use :class:`~.SingletonGate` because a single shared global + instance can not represent the parameter values. + + The other potential issue to be aware of when using this class is around the + use of the :class:`~.SingletonGate` class is that the :class:`~.Gate` + data model supports some mutable state. Specifically, the + :attr:`~.Gate.label`, :attr:`~.Gate.duration`, :attr:`~.Gate.unit`, and + :attr:`~.Gate.condition` attributes are all accessible and mutable in the + :class:`~.Gate` and its direct subclasses. However, this is incompatible + with having a shared object via :class:`~.SingletonGate`. For instances of + :class:`~.SingletonGate` setting these attributes directly is not allowed + and it will raise an exception. If they are needed for a particular + instance you must set them on the constructor (or via + :meth:`~.SingletonGate.c_if` for :attr:`~.SingletonGate.condition`) when + creating a new object. When this is done the output from the constructor + will be a separate instance with the custom state instead of the globally + shared instance. You can also use the :meth:`.SingletonGate.to_mutable` + method to get a mutable copy of a gate object and then mutate the attributes + like you would on any other :class:`~.circuit.Instruction` object. + - | + The following standard library gates are now instances of + :class:`~.SingletonGate`: + + * :class:`~.DCXGate` + * :class:`~.ECRGate` + * :class:`~.HGate` + * :class:`~.IGate` + * :class:`~.iSwapGate` + * :class:`~.SGate` + * :class:`~.SdgGate` + * :class:`~.SwapGate` + * :class:`~.SXGate` + * :class:`~.SXdgGate` + * :class:`~.TGate` + * :class:`~.TdgGate` + * :class:`~.XGate` + * :class:`~.RCCXGate` + * :class:`~.RC3XGate` + * :class:`~.YGate` + * :class:`~.ZGate` + + This means that unless a ``label``, ``condition``, ``duration``, or ``unit`` + are set on the instance at creation time they will all share a single global + instance whenever a new gate object is created. This results in large reduction + in the memory overhead for > 1 object of these types and significantly faster + object construction time. + - | + Added a new method :meth`.Instruction.to_mutable` and attribute + :attr:`.Instruction.mutable` which is used to get a mutable copy and check whether + an :class:`~.circuit.Instruction` object is mutable. With the introduction + of :class:`~.SingletonGate` these methods can be used to have a unified interface + to deal with the mutablitiy of instruction objects. +upgrade: + - | + The following standard library gates: + + * :class:`~.DCXGate` + * :class:`~.ECRGate` + * :class:`~.HGate` + * :class:`~.IGate` + * :class:`~.iSwapGate` + * :class:`~.SGate` + * :class:`~.SdgGate` + * :class:`~.SwapGate` + * :class:`~.SXGate` + * :class:`~.SXdgGate` + * :class:`~.TGate` + * :class:`~.TdgGate` + * :class:`~.XGate` + * :class:`~.RCCXGate` + * :class:`~.RC3XGate` + * :class:`~.YGate` + * :class:`~.ZGate` + + no longer are able to set :attr:`~.Gate.label`, :attr:`~.Gate.condition`, + :attr:`~.Gate.duration`, or :attr:`~.Gate.unit` after instantiating an object + anymore. You will now only be able to set these attributes as arguments + when creating a new object or in the case of :attr:`~.Gate.condtion` through + the use :meth:`~.Gate.c_if`. Alternatively you can use :meth:`~.Gate.to_mutable` + to get a mutable copy of the instruction and then use the setter on that copy + instead of the original object. This change was necssary as part of converting + these classes to be :class:`~.SingletonGate` types which greatly reduces the + memory footprint of repeated instances of these gates. + - | + For anything that interacts with :class:`~.Gate`, :class:`~.Operation`, + or :class:`~.circuit.Instruction` objects or works with these as part of a + :class:`~.QuantumCircuit` or :class:`~.DAGCircuit` classes it is important + to note that the use of shared references for instances is much more common + now. Previously, it was possible to reuse and share an instance of a + a circuit operation it wasn't very commonly used and a copy would generate + a unique instance. This has changed starting in this release because of + :class:`~.SingletonGate` being made available (and a large number of standard + library gates now built off of it). If your usage of these objects is assuming + unique instances for every circuit operation there are potential issue because + of shared state that will be reused between operations of the same type (that + will persist through copy and deep copies). You can rely on the + :attr:`.Instruction.mutable` attribute to check the mutability of an object or + use :meth:`.Instruction.to_mutable` to get a mutable copy of any instruction. +fixes: + - | + Fixed an oversight in the :class:`~.ECRGate` that prevented setting an + :attr:`.ECRGate.label` attribute at object construction time. All other + :class:`~.Gate` classes and subclasses enable setting a ``label`` keyword + argument in the constructor. + - | + Fixed an oversight in the :class:`~.Gate` (and all its subclasses) constructor + where the :attr:`.Gate.duration` and :attr:`.Gate.unit` attributes could not + be set as keyword arguments during construction. The parent class + :class:`~.circuit.Instruction` supported setting this but :class:`~.Gate` was + previously not exposing this interface correctly. diff --git a/test/python/circuit/gate_utils.py b/test/python/circuit/gate_utils.py index 83e884bb72..557059c32f 100644 --- a/test/python/circuit/gate_utils.py +++ b/test/python/circuit/gate_utils.py @@ -25,7 +25,7 @@ def _get_free_params(fun, ignore=None): Returns: list[str]: The name of the free parameters not listed in ``ignore``. """ - ignore = ignore or [] + ignore = ignore or ["kwargs"] free_params = [] for name, param in signature(fun).parameters.items(): if param.default == Parameter.empty and param.kind != Parameter.VAR_POSITIONAL: diff --git a/test/python/circuit/test_circuit_load_from_qpy.py b/test/python/circuit/test_circuit_load_from_qpy.py index 1e2f5263ce..825e46a8dd 100644 --- a/test/python/circuit/test_circuit_load_from_qpy.py +++ b/test/python/circuit/test_circuit_load_from_qpy.py @@ -609,8 +609,7 @@ class TestLoadFromQPY(QiskitTestCase): def test_standard_gate_with_label(self): """Test a standard gate with a label.""" qc = QuantumCircuit(1) - gate = XGate() - gate.label = "My special X gate" + gate = XGate(label="My special X gate") qc.append(gate, [0]) qpy_file = io.BytesIO() dump(qc, qpy_file) diff --git a/test/python/circuit/test_gate_definitions.py b/test/python/circuit/test_gate_definitions.py index 0c41189fc9..9589a68c76 100644 --- a/test/python/circuit/test_gate_definitions.py +++ b/test/python/circuit/test_gate_definitions.py @@ -282,6 +282,7 @@ class TestGateEquivalenceEqual(QiskitTestCase): "PermutationGate", "Commuting2qBlock", "PauliEvolutionGate", + "SingletonGate", "_U0Gate", "_DefinedGate", } diff --git a/test/python/circuit/test_instructions.py b/test/python/circuit/test_instructions.py index 8c8e986605..f5f5e14d51 100644 --- a/test/python/circuit/test_instructions.py +++ b/test/python/circuit/test_instructions.py @@ -10,6 +10,8 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +# pylint: disable=unsubscriptable-object + """Test Qiskit's Instruction class.""" import unittest.mock @@ -22,6 +24,7 @@ from qiskit.circuit import Instruction, InstructionSet from qiskit.circuit import QuantumCircuit from qiskit.circuit import QuantumRegister, ClassicalRegister, Qubit, Clbit from qiskit.circuit.library.standard_gates.h import HGate +from qiskit.circuit.library.standard_gates.rz import RZGate from qiskit.circuit.library.standard_gates.x import CXGate from qiskit.circuit.library.standard_gates.s import SGate from qiskit.circuit.library.standard_gates.t import TGate @@ -539,21 +542,21 @@ class TestInstructions(QiskitTestCase): arbitrary :obj:`.Clbit` and `:obj:`.ClassicalRegister` instances, but rejects integers.""" with self.subTest("accepts arbitrary register"): - instruction = HGate() + instruction = RZGate(0) instructions = InstructionSet() instructions.add(instruction, [Qubit()], []) register = ClassicalRegister(2) instructions.c_if(register, 0) self.assertIs(instruction.condition[0], register) with self.subTest("accepts arbitrary bit"): - instruction = HGate() + instruction = RZGate(0) instructions = InstructionSet() instructions.add(instruction, [Qubit()], []) bit = Clbit() instructions.c_if(bit, 0) self.assertIs(instruction.condition[0], bit) with self.subTest("rejects index"): - instruction = HGate() + instruction = RZGate(0) instructions = InstructionSet() instructions.add(instruction, [Qubit()], []) with self.assertRaisesRegex(CircuitError, r"Cannot pass an index as a condition .*"): @@ -578,7 +581,7 @@ class TestInstructions(QiskitTestCase): with self.subTest("calls requester with bit"): dummy_requester.reset_mock() - instruction = HGate() + instruction = RZGate(0) instructions = InstructionSet(resource_requester=dummy_requester) instructions.add(instruction, [Qubit()], []) bit = Clbit() @@ -587,7 +590,7 @@ class TestInstructions(QiskitTestCase): self.assertIs(instruction.condition[0], sentinel_bit) with self.subTest("calls requester with index"): dummy_requester.reset_mock() - instruction = HGate() + instruction = RZGate(0) instructions = InstructionSet(resource_requester=dummy_requester) instructions.add(instruction, [Qubit()], []) index = 0 @@ -596,7 +599,7 @@ class TestInstructions(QiskitTestCase): self.assertIs(instruction.condition[0], sentinel_bit) with self.subTest("calls requester with register"): dummy_requester.reset_mock() - instruction = HGate() + instruction = RZGate(0) instructions = InstructionSet(resource_requester=dummy_requester) instructions.add(instruction, [Qubit()], []) register = ClassicalRegister(2) @@ -605,7 +608,7 @@ class TestInstructions(QiskitTestCase): self.assertIs(instruction.condition[0], sentinel_register) with self.subTest("calls requester only once when broadcast"): dummy_requester.reset_mock() - instruction_list = [HGate(), HGate(), HGate()] + instruction_list = [RZGate(0), RZGate(0), RZGate(0)] instructions = InstructionSet(resource_requester=dummy_requester) for instruction in instruction_list: instructions.add(instruction, [Qubit()], []) @@ -625,7 +628,7 @@ class TestInstructions(QiskitTestCase): Instruction("h", 1, 0, [], label=0) with self.subTest("raises when a non-string label is provided to setter"): with self.assertRaisesRegex(TypeError, r"label expects a string or None"): - instruction = HGate() + instruction = RZGate(0) instruction.label = 0 def test_deprecation_warnings_qasm_methods(self): diff --git a/test/python/circuit/test_singleton_gate.py b/test/python/circuit/test_singleton_gate.py new file mode 100644 index 0000000000..d8c80661d6 --- /dev/null +++ b/test/python/circuit/test_singleton_gate.py @@ -0,0 +1,253 @@ +# 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. + +# pylint: disable=missing-function-docstring + + +""" +Tests for singleton gate behavior +""" + +import copy + +from qiskit.circuit.library import HGate, SXGate +from qiskit.circuit import Clbit, QuantumCircuit, QuantumRegister, ClassicalRegister +from qiskit.converters import dag_to_circuit, circuit_to_dag + +from qiskit.test.base import QiskitTestCase + + +class TestSingletonGate(QiskitTestCase): + """Qiskit SingletonGate tests.""" + + def test_default_singleton(self): + gate = HGate() + new_gate = HGate() + self.assertIs(gate, new_gate) + + def test_label_not_singleton(self): + gate = HGate() + label_gate = HGate(label="special") + self.assertIsNot(gate, label_gate) + + def test_condition_not_singleton(self): + gate = HGate() + condition_gate = HGate().c_if(Clbit(), 0) + self.assertIsNot(gate, condition_gate) + + def test_raise_on_state_mutation(self): + gate = HGate() + with self.assertRaises(NotImplementedError): + gate.label = "foo" + with self.assertRaises(NotImplementedError): + gate.condition = (Clbit(), 0) + + def test_labeled_condition(self): + singleton_gate = HGate() + clbit = Clbit() + gate = HGate(label="conditionally special").c_if(clbit, 0) + self.assertIsNot(singleton_gate, gate) + self.assertEqual(gate.label, "conditionally special") + self.assertEqual(gate.condition, (clbit, 0)) + + def test_default_singleton_copy(self): + gate = HGate() + copied = gate.copy() + self.assertIs(gate, copied) + + def test_label_copy(self): + gate = HGate(label="special") + copied = gate.copy() + self.assertIsNot(gate, copied) + self.assertEqual(gate, copied) + + def test_label_copy_new(self): + gate = HGate() + label_gate = HGate(label="special") + self.assertIsNot(gate, label_gate) + self.assertNotEqual(gate.label, label_gate.label) + copied = gate.copy() + copied_label = label_gate.copy() + self.assertIs(gate, copied) + self.assertIsNot(copied, label_gate) + self.assertIsNot(copied_label, gate) + self.assertIsNot(copied_label, label_gate) + self.assertNotEqual(copied.label, label_gate.label) + self.assertEqual(copied_label, label_gate) + self.assertNotEqual(copied.label, "special") + self.assertEqual(copied_label.label, "special") + + def test_condition_copy(self): + gate = HGate().c_if(Clbit(), 0) + copied = gate.copy() + self.assertIsNot(gate, copied) + self.assertEqual(gate, copied) + + def test_condition_label_copy(self): + clbit = Clbit() + gate = HGate(label="conditionally special").c_if(clbit, 0) + copied = gate.copy() + self.assertIsNot(gate, copied) + self.assertEqual(gate, copied) + self.assertEqual(copied.label, "conditionally special") + self.assertEqual(copied.condition, (clbit, 0)) + + def test_deepcopy(self): + gate = HGate() + copied = copy.deepcopy(gate) + self.assertIs(gate, copied) + + def test_deepcopy_with_label(self): + gate = HGate(label="special") + copied = copy.deepcopy(gate) + self.assertIsNot(gate, copied) + self.assertEqual(gate, copied) + self.assertEqual(copied.label, "special") + + def test_deepcopy_with_condition(self): + gate = HGate().c_if(Clbit(), 0) + copied = copy.deepcopy(gate) + self.assertIsNot(gate, copied) + self.assertEqual(gate, copied) + + def test_condition_label_deepcopy(self): + clbit = Clbit() + gate = HGate(label="conditionally special").c_if(clbit, 0) + copied = copy.deepcopy(gate) + self.assertIsNot(gate, copied) + self.assertEqual(gate, copied) + self.assertEqual(copied.label, "conditionally special") + self.assertEqual(copied.condition, (clbit, 0)) + + def test_label_deepcopy_new(self): + gate = HGate() + label_gate = HGate(label="special") + self.assertIsNot(gate, label_gate) + self.assertNotEqual(gate.label, label_gate.label) + copied = copy.deepcopy(gate) + copied_label = copy.deepcopy(label_gate) + self.assertIs(gate, copied) + self.assertIsNot(copied, label_gate) + self.assertIsNot(copied_label, gate) + self.assertIsNot(copied_label, label_gate) + self.assertNotEqual(copied.label, label_gate.label) + self.assertEqual(copied_label, label_gate) + self.assertNotEqual(copied.label, "special") + self.assertEqual(copied_label.label, "special") + + def test_control_a_singleton(self): + singleton_gate = HGate() + gate = HGate(label="special") + ch = gate.control(label="my_ch") + self.assertEqual(ch.base_gate.label, "special") + self.assertIsNot(ch.base_gate, singleton_gate) + + def test_round_trip_dag_conversion(self): + qc = QuantumCircuit(1) + gate = HGate() + qc.append(gate, [0]) + dag = circuit_to_dag(qc) + out = dag_to_circuit(dag) + self.assertIs(qc.data[0].operation, out.data[0].operation) + + def test_round_trip_dag_conversion_with_label(self): + gate = HGate(label="special") + qc = QuantumCircuit(1) + qc.append(gate, [0]) + dag = circuit_to_dag(qc) + out = dag_to_circuit(dag) + self.assertIsNot(qc.data[0].operation, out.data[0].operation) + self.assertEqual(qc.data[0].operation, out.data[0].operation) + self.assertEqual(out.data[0].operation.label, "special") + + def test_round_trip_dag_conversion_with_condition(self): + qc = QuantumCircuit(1, 1) + gate = HGate().c_if(qc.cregs[0], 0) + qc.append(gate, [0]) + dag = circuit_to_dag(qc) + out = dag_to_circuit(dag) + self.assertIsNot(qc.data[0].operation, out.data[0].operation) + self.assertEqual(qc.data[0].operation, out.data[0].operation) + self.assertEqual(out.data[0].operation.condition, (qc.cregs[0], 0)) + + def test_round_trip_dag_conversion_condition_label(self): + qc = QuantumCircuit(1, 1) + gate = HGate(label="conditionally special").c_if(qc.cregs[0], 0) + qc.append(gate, [0]) + dag = circuit_to_dag(qc) + out = dag_to_circuit(dag) + self.assertIsNot(qc.data[0].operation, out.data[0].operation) + self.assertEqual(qc.data[0].operation, out.data[0].operation) + self.assertEqual(out.data[0].operation.condition, (qc.cregs[0], 0)) + self.assertEqual(out.data[0].operation.label, "conditionally special") + + def test_condition_via_instructionset(self): + gate = HGate() + qr = QuantumRegister(2, "qr") + cr = ClassicalRegister(1, "cr") + circuit = QuantumCircuit(qr, cr) + circuit.h(qr[0]).c_if(cr, 1) + self.assertIsNot(gate, circuit.data[0].operation) + self.assertEqual(circuit.data[0].operation.condition, (cr, 1)) + + def test_is_mutable(self): + gate = HGate() + self.assertFalse(gate.mutable) + label_gate = HGate(label="foo") + self.assertTrue(label_gate.mutable) + self.assertIsNot(gate, label_gate) + + def test_to_mutable(self): + gate = HGate() + self.assertFalse(gate.mutable) + new_gate = gate.to_mutable() + self.assertTrue(new_gate.mutable) + self.assertIsNot(gate, new_gate) + + def test_to_mutable_setter(self): + gate = HGate() + self.assertFalse(gate.mutable) + mutable_gate = gate.to_mutable() + mutable_gate.label = "foo" + mutable_gate.duration = 3 + mutable_gate.unit = "s" + clbit = Clbit() + mutable_gate.condition = (clbit, 0) + self.assertTrue(mutable_gate.mutable) + self.assertIsNot(gate, mutable_gate) + self.assertEqual(mutable_gate.label, "foo") + self.assertEqual(mutable_gate.duration, 3) + self.assertEqual(mutable_gate.unit, "s") + self.assertEqual(mutable_gate.condition, (clbit, 0)) + + def test_to_mutable_of_mutable_instance(self): + gate = HGate(label="foo") + mutable_copy = gate.to_mutable() + self.assertIsNot(gate, mutable_copy) + self.assertEqual(mutable_copy.label, gate.label) + mutable_copy.label = "not foo" + self.assertNotEqual(mutable_copy.label, gate.label) + + def test_set_custom_attr(self): + gate = SXGate() + with self.assertRaises(NotImplementedError): + gate.custom_foo = 12345 + mutable_gate = gate.to_mutable() + self.assertTrue(mutable_gate.mutable) + mutable_gate.custom_foo = 12345 + self.assertEqual(12345, mutable_gate.custom_foo) + + def test_positional_label(self): + gate = SXGate() + label_gate = SXGate("I am a little label") + self.assertIsNot(gate, label_gate) + self.assertEqual(label_gate.label, "I am a little label") diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index 7f7375178b..35576d9b43 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -477,8 +477,7 @@ class TestDagApplyOperation(QiskitTestCase): def test_apply_operation_back(self): """The apply_operation_back() method.""" - x_gate = XGate() - x_gate.condition = self.condition + x_gate = XGate().c_if(*self.condition) self.dag.apply_operation_back(HGate(), [self.qubit0], []) self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], []) self.dag.apply_operation_back(Measure(), [self.qubit1], [self.clbit1]) @@ -490,8 +489,7 @@ class TestDagApplyOperation(QiskitTestCase): def test_edges(self): """Test that DAGCircuit.edges() behaves as expected with ops.""" - x_gate = XGate() - x_gate.condition = self.condition + x_gate = XGate().c_if(*self.condition) self.dag.apply_operation_back(HGate(), [self.qubit0], []) self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], []) self.dag.apply_operation_back(Measure(), [self.qubit1], [self.clbit1]) @@ -509,8 +507,7 @@ class TestDagApplyOperation(QiskitTestCase): # Single qubit gate conditional: qc.h(qr[2]).c_if(cr, 3) - h_gate = HGate() - h_gate.condition = self.condition + h_gate = HGate().c_if(*self.condition) h_node = self.dag.apply_operation_back(h_gate, [self.qubit2], []) self.assertEqual(h_node.qargs, (self.qubit2,)) @@ -550,8 +547,7 @@ class TestDagApplyOperation(QiskitTestCase): new_creg = ClassicalRegister(1, "cr2") self.dag.add_creg(new_creg) - meas_gate = Measure() - meas_gate.condition = (new_creg, 0) + meas_gate = Measure().c_if(new_creg, 0) meas_node = self.dag.apply_operation_back(meas_gate, [self.qubit0], [self.clbit0]) self.assertEqual(meas_node.qargs, (self.qubit0,)) @@ -596,8 +592,7 @@ class TestDagApplyOperation(QiskitTestCase): # Measure targeting a clbit which _is_ a member of the conditional # register. qc.measure(qr[0], cr[0]).c_if(cr, 3) - meas_gate = Measure() - meas_gate.condition = self.condition + meas_gate = Measure().c_if(*self.condition) meas_node = self.dag.apply_operation_back(meas_gate, [self.qubit1], [self.clbit1]) self.assertEqual(meas_node.qargs, (self.qubit1,)) @@ -1153,8 +1148,7 @@ class TestDagNodeSelection(QiskitTestCase): def test_dag_collect_runs_start_with_conditional(self): """Test collect runs with a conditional at the start of the run.""" - h_gate = HGate() - h_gate.condition = self.condition + h_gate = HGate().c_if(*self.condition) self.dag.apply_operation_back(h_gate, [self.qubit0]) self.dag.apply_operation_back(HGate(), [self.qubit0]) self.dag.apply_operation_back(HGate(), [self.qubit0]) @@ -1167,8 +1161,7 @@ class TestDagNodeSelection(QiskitTestCase): def test_dag_collect_runs_conditional_in_middle(self): """Test collect_runs with a conditional in the middle of a run.""" - h_gate = HGate() - h_gate.condition = self.condition + h_gate = HGate().c_if(*self.condition) self.dag.apply_operation_back(HGate(), [self.qubit0]) self.dag.apply_operation_back(h_gate, [self.qubit0]) self.dag.apply_operation_back(HGate(), [self.qubit0]) @@ -1210,8 +1203,7 @@ class TestDagNodeSelection(QiskitTestCase): """Test collect 1q runs with a conditional at the start of the run.""" self.dag.apply_operation_back(Reset(), [self.qubit0]) self.dag.apply_operation_back(Delay(100), [self.qubit0]) - h_gate = HGate() - h_gate.condition = self.condition + h_gate = HGate().c_if(*self.condition) self.dag.apply_operation_back(h_gate, [self.qubit0]) self.dag.apply_operation_back(HGate(), [self.qubit0]) self.dag.apply_operation_back(HGate(), [self.qubit0]) @@ -1226,8 +1218,7 @@ class TestDagNodeSelection(QiskitTestCase): """Test collect_1q_runs with a conditional in the middle of a run.""" self.dag.apply_operation_back(Reset(), [self.qubit0]) self.dag.apply_operation_back(Delay(100), [self.qubit0]) - h_gate = HGate() - h_gate.condition = self.condition + h_gate = HGate().c_if(*self.condition) self.dag.apply_operation_back(HGate(), [self.qubit0]) self.dag.apply_operation_back(h_gate, [self.qubit0]) self.dag.apply_operation_back(HGate(), [self.qubit0]) @@ -1305,8 +1296,7 @@ class TestDagLayers(QiskitTestCase): qubit1 = qreg[1] clbit0 = creg[0] clbit1 = creg[1] - x_gate = XGate() - x_gate.condition = (creg, 3) + x_gate = XGate().c_if(creg, 3) dag = DAGCircuit() dag.add_qreg(qreg) dag.add_creg(creg) @@ -2135,10 +2125,8 @@ class TestDagSubstitute(QiskitTestCase): sub.cx(0, 1) sub.h(0) - conditioned_h = HGate() - conditioned_h.condition = conditioned_cz.condition - conditioned_cx = CXGate() - conditioned_cx.condition = conditioned_cz.condition + conditioned_h = HGate().c_if(*conditioned_cz.condition) + conditioned_cx = CXGate().c_if(*conditioned_cz.condition) expected = DAGCircuit() expected.add_qubits(base_qubits)