From 111fd64d2c38542cf8787ef47da14df87895c643 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 16 Oct 2023 15:50:01 -0400 Subject: [PATCH] Singleton parameterless controlled gates (#10898) * Singleton parameterless controlled gates This commit adds a new class SingletonControlledGate which is very similar to the SingletonGate class, but instead can be used in place of the ControlledGate class as a parent class. This enables a controlled gate class to work as a singleton if there is no custom control state specified. This new class has a large amount of duplication with the SingletonGate class, but because of how the inheritance tree is built up here there is no avoiding this as we need to specialize for adding singleton support and SingletonGate is at the same level as ControlledGate. With the introduction of this new class all the standard library ControlledGate instances are updated so they inherit from SingletonControlledGate instead of ControlledGate. This should significantly reduce the memory footprint of repeated instances of these gates in circuits. * Fix failing test cases * Apply suggestions from code review Co-authored-by: Kevin Hartman * Fixes from rebase * Fix cyclic imports caused by eager definition population * Fix tests * Remove unused changes to ControlledGate * Expand deepcopy test assertions * Update release notes --------- Co-authored-by: Kevin Hartman --- qiskit/circuit/add_control.py | 1 + qiskit/circuit/controlledgate.py | 8 +- qiskit/circuit/library/standard_gates/h.py | 15 +- qiskit/circuit/library/standard_gates/s.py | 55 +++- qiskit/circuit/library/standard_gates/swap.py | 12 +- qiskit/circuit/library/standard_gates/sx.py | 12 +- qiskit/circuit/library/standard_gates/x.py | 157 ++++++++-- qiskit/circuit/library/standard_gates/y.py | 10 +- qiskit/circuit/library/standard_gates/z.py | 32 +- qiskit/circuit/singleton.py | 26 ++ qiskit/qpy/binary_io/circuits.py | 12 +- .../notes/singletons-83782de8bd062cbc.yaml | 68 ++++- test/python/circuit/test_controlled_gate.py | 7 + test/python/circuit/test_gate_definitions.py | 2 + test/python/circuit/test_singleton.py | 274 +++++++++++++++++- test/python/dagcircuit/test_dagcircuit.py | 18 +- 16 files changed, 635 insertions(+), 74 deletions(-) diff --git a/qiskit/circuit/add_control.py b/qiskit/circuit/add_control.py index 2f81967abd..b11fc20d4a 100644 --- a/qiskit/circuit/add_control.py +++ b/qiskit/circuit/add_control.py @@ -111,6 +111,7 @@ def control( else: basis = ["p", "u", "x", "z", "rx", "ry", "rz", "cx"] if isinstance(operation, controlledgate.ControlledGate): + operation = operation.to_mutable() operation.ctrl_state = None unrolled_gate = _unroll_gate(operation, basis_gates=basis) if unrolled_gate.definition.global_phase: diff --git a/qiskit/circuit/controlledgate.py b/qiskit/circuit/controlledgate.py index 02674f06b1..c8b5b8a187 100644 --- a/qiskit/circuit/controlledgate.py +++ b/qiskit/circuit/controlledgate.py @@ -38,6 +38,10 @@ class ControlledGate(Gate): definition: Optional["QuantumCircuit"] = None, ctrl_state: Optional[Union[int, str]] = None, base_gate: Optional[Gate] = None, + duration=None, + unit=None, + *, + _base_label=None, ): """Create a new ControlledGate. In the new gate the first ``num_ctrl_qubits`` of the gate are the controls. @@ -95,7 +99,7 @@ class ControlledGate(Gate): qc2.draw('mpl') """ self.base_gate = None if base_gate is None else base_gate.copy() - super().__init__(name, num_qubits, params, label=label) + super().__init__(name, num_qubits, params, label=label, duration=duration, unit=unit) self._num_ctrl_qubits = 1 self.num_ctrl_qubits = num_ctrl_qubits self.definition = copy.deepcopy(definition) @@ -111,7 +115,7 @@ class ControlledGate(Gate): `_definition`. """ if self._open_ctrl: - closed_gate = self.copy() + closed_gate = self.to_mutable() closed_gate.ctrl_state = None bit_ctrl_state = bin(self.ctrl_state)[2:].zfill(self.num_ctrl_qubits) qreg = QuantumRegister(self.num_qubits, "q") diff --git a/qiskit/circuit/library/standard_gates/h.py b/qiskit/circuit/library/standard_gates/h.py index ddc60d8e54..d32ff7226e 100644 --- a/qiskit/circuit/library/standard_gates/h.py +++ b/qiskit/circuit/library/standard_gates/h.py @@ -14,12 +14,9 @@ from math import sqrt, pi from typing import Optional, Union import numpy -from qiskit.circuit.controlledgate import ControlledGate -from qiskit.circuit.singleton import SingletonGate +from qiskit.circuit.singleton import SingletonGate, SingletonControlledGate from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit._utils import with_gate_array, with_controlled_gate_array -from .t import TGate, TdgGate -from .s import SGate, SdgGate _H_ARRAY = 1 / sqrt(2) * numpy.array([[1, 1], [1, -1]], dtype=numpy.complex128) @@ -104,7 +101,7 @@ class HGate(SingletonGate): @with_controlled_gate_array(_H_ARRAY, num_ctrl_qubits=1) -class CHGate(ControlledGate): +class CHGate(SingletonControlledGate): r"""Controlled-Hadamard gate. Applies a Hadamard on the target qubit if the control is @@ -165,6 +162,9 @@ class CHGate(ControlledGate): self, label: Optional[str] = None, ctrl_state: Optional[Union[int, str]] = None, + *, + duration=None, + unit="dt", _base_label=None, ): """Create new CH gate.""" @@ -176,6 +176,9 @@ class CHGate(ControlledGate): label=label, ctrl_state=ctrl_state, base_gate=HGate(label=_base_label), + duration=duration, + unit=unit, + _base_label=_base_label, ) def _define(self): @@ -193,6 +196,8 @@ class CHGate(ControlledGate): # pylint: disable=cyclic-import from qiskit.circuit.quantumcircuit import QuantumCircuit from .x import CXGate # pylint: disable=cyclic-import + from .t import TGate, TdgGate + from .s import SGate, SdgGate q = QuantumRegister(2, "q") qc = QuantumCircuit(q, name=self.name) diff --git a/qiskit/circuit/library/standard_gates/s.py b/qiskit/circuit/library/standard_gates/s.py index a0ab961e91..45768ca552 100644 --- a/qiskit/circuit/library/standard_gates/s.py +++ b/qiskit/circuit/library/standard_gates/s.py @@ -17,9 +17,7 @@ from typing import Optional, Union import numpy -from qiskit.circuit.controlledgate import ControlledGate -from qiskit.circuit.singleton import SingletonGate -from qiskit.circuit.library.standard_gates.p import CPhaseGate, PhaseGate +from qiskit.circuit.singleton import SingletonGate, SingletonControlledGate from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit._utils import with_gate_array, with_controlled_gate_array @@ -86,6 +84,8 @@ class SGate(SingletonGate): def power(self, exponent: float): """Raise gate to a power.""" + from .p import PhaseGate + return PhaseGate(0.5 * numpy.pi * exponent) @@ -147,11 +147,13 @@ class SdgGate(SingletonGate): def power(self, exponent: float): """Raise gate to a power.""" + from .p import PhaseGate + return PhaseGate(-0.5 * numpy.pi * exponent) @with_controlled_gate_array(_S_ARRAY, num_ctrl_qubits=1) -class CSGate(ControlledGate): +class CSGate(SingletonControlledGate): r"""Controlled-S gate. Can be applied to a :class:`~qiskit.circuit.QuantumCircuit` @@ -180,16 +182,35 @@ class CSGate(ControlledGate): \end{pmatrix} """ - 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, + *, + duration=None, + unit="dt", + _base_label=None, + ): """Create new CS gate.""" super().__init__( - "cs", 2, [], label=label, num_ctrl_qubits=1, ctrl_state=ctrl_state, base_gate=SGate() + "cs", + 2, + [], + label=label, + num_ctrl_qubits=1, + ctrl_state=ctrl_state, + base_gate=SGate(label=_base_label), + duration=duration, + _base_label=_base_label, + unit=unit, ) def _define(self): """ gate cs a,b { h b; cp(pi/2) a,b; h b; } """ + from .p import CPhaseGate + self.definition = CPhaseGate(theta=pi / 2).definition def inverse(self): @@ -198,11 +219,13 @@ class CSGate(ControlledGate): def power(self, exponent: float): """Raise gate to a power.""" + from .p import CPhaseGate + return CPhaseGate(0.5 * numpy.pi * exponent) @with_controlled_gate_array(_SDG_ARRAY, num_ctrl_qubits=1) -class CSdgGate(ControlledGate): +class CSdgGate(SingletonControlledGate): r"""Controlled-S^\dagger gate. Can be applied to a :class:`~qiskit.circuit.QuantumCircuit` @@ -231,7 +254,15 @@ class CSdgGate(ControlledGate): \end{pmatrix} """ - 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, + *, + duration=None, + unit="dt", + _base_label=None, + ): """Create new CSdg gate.""" super().__init__( "csdg", @@ -240,13 +271,17 @@ class CSdgGate(ControlledGate): label=label, num_ctrl_qubits=1, ctrl_state=ctrl_state, - base_gate=SdgGate(), + base_gate=SdgGate(label=_base_label), + duration=duration, + unit=unit, ) def _define(self): """ gate csdg a,b { h b; cp(-pi/2) a,b; h b; } """ + from .p import CPhaseGate + self.definition = CPhaseGate(theta=-pi / 2).definition def inverse(self): @@ -255,4 +290,6 @@ class CSdgGate(ControlledGate): def power(self, exponent: float): """Raise gate to a power.""" + from .p import CPhaseGate + return CPhaseGate(-0.5 * numpy.pi * exponent) diff --git a/qiskit/circuit/library/standard_gates/swap.py b/qiskit/circuit/library/standard_gates/swap.py index ef13d27673..48e45b944b 100644 --- a/qiskit/circuit/library/standard_gates/swap.py +++ b/qiskit/circuit/library/standard_gates/swap.py @@ -14,8 +14,7 @@ from typing import Optional, Union import numpy -from qiskit.circuit.controlledgate import ControlledGate -from qiskit.circuit.singleton import SingletonGate +from qiskit.circuit.singleton import SingletonGate, SingletonControlledGate from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit._utils import with_gate_array, with_controlled_gate_array @@ -113,7 +112,7 @@ class SwapGate(SingletonGate): @with_controlled_gate_array(_SWAP_ARRAY, num_ctrl_qubits=1) -class CSwapGate(ControlledGate): +class CSwapGate(SingletonControlledGate): r"""Controlled-SWAP gate, also known as the Fredkin gate. Can be applied to a :class:`~qiskit.circuit.QuantumCircuit` @@ -194,9 +193,14 @@ class CSwapGate(ControlledGate): self, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + *, + duration=None, + unit="dt", _base_label=None, ): """Create new CSWAP gate.""" + if unit is None: + unit = "dt" super().__init__( "cswap", 3, @@ -205,6 +209,8 @@ class CSwapGate(ControlledGate): label=label, ctrl_state=ctrl_state, base_gate=SwapGate(label=_base_label), + duration=duration, + unit=unit, ) def _define(self): diff --git a/qiskit/circuit/library/standard_gates/sx.py b/qiskit/circuit/library/standard_gates/sx.py index e6ede8c29b..6299d14f3b 100644 --- a/qiskit/circuit/library/standard_gates/sx.py +++ b/qiskit/circuit/library/standard_gates/sx.py @@ -14,8 +14,7 @@ from math import pi from typing import Optional, Union -from qiskit.circuit.controlledgate import ControlledGate -from qiskit.circuit.singleton import SingletonGate +from qiskit.circuit.singleton import SingletonGate, SingletonControlledGate from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit._utils import with_gate_array, with_controlled_gate_array @@ -104,7 +103,7 @@ class SXGate(SingletonGate): string (e.g. '110'), or None. If None, use all 1s. Returns: - ControlledGate: controlled version of this gate. + SingletonControlledGate: controlled version of this gate. """ if num_ctrl_qubits == 1: gate = CSXGate(label=label, ctrl_state=ctrl_state, _base_label=self.label) @@ -168,7 +167,7 @@ class SXdgGate(SingletonGate): @with_controlled_gate_array(_SX_ARRAY, num_ctrl_qubits=1) -class CSXGate(ControlledGate): +class CSXGate(SingletonControlledGate): r"""Controlled-√X gate. Can be applied to a :class:`~qiskit.circuit.QuantumCircuit` @@ -228,6 +227,9 @@ class CSXGate(ControlledGate): self, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + *, + duration=None, + unit="dt", _base_label=None, ): """Create new CSX gate.""" @@ -239,6 +241,8 @@ class CSXGate(ControlledGate): label=label, ctrl_state=ctrl_state, base_gate=SXGate(label=_base_label), + 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 9f80d2153e..6817203cd8 100644 --- a/qiskit/circuit/library/standard_gates/x.py +++ b/qiskit/circuit/library/standard_gates/x.py @@ -17,14 +17,9 @@ from math import ceil, pi import numpy from qiskit.utils.deprecation import deprecate_func from qiskit.circuit.controlledgate import ControlledGate -from qiskit.circuit.singleton import SingletonGate +from qiskit.circuit.singleton import SingletonGate, SingletonControlledGate 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 -from .t import TGate, TdgGate -from .u1 import U1Gate -from .u2 import U2Gate -from .sx import SXGate _X_ARRAY = [[0, 1], [1, 0]] @@ -129,7 +124,7 @@ class XGate(SingletonGate): @with_controlled_gate_array(_X_ARRAY, num_ctrl_qubits=1) -class CXGate(ControlledGate): +class CXGate(SingletonControlledGate): r"""Controlled-X gate. Can be applied to a :class:`~qiskit.circuit.QuantumCircuit` @@ -196,6 +191,9 @@ class CXGate(ControlledGate): self, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + *, + duration=None, + unit="dt", _base_label=None, ): """Create new CX gate.""" @@ -207,6 +205,9 @@ class CXGate(ControlledGate): label=label, ctrl_state=ctrl_state, base_gate=XGate(label=_base_label), + _base_label=_base_label, + duration=duration, + unit=unit, ) def control( @@ -242,7 +243,7 @@ class CXGate(ControlledGate): @with_controlled_gate_array(_X_ARRAY, num_ctrl_qubits=2, cached_states=(3,)) -class CCXGate(ControlledGate): +class CCXGate(SingletonControlledGate): r"""CCX gate, also known as Toffoli gate. Can be applied to a :class:`~qiskit.circuit.QuantumCircuit` @@ -314,6 +315,9 @@ class CCXGate(ControlledGate): self, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + *, + duration=None, + unit="dt", _base_label=None, ): """Create new CCX gate.""" @@ -325,6 +329,8 @@ class CCXGate(ControlledGate): label=label, ctrl_state=ctrl_state, base_gate=XGate(label=_base_label), + duration=duration, + unit=unit, ) def _define(self): @@ -338,6 +344,8 @@ class CCXGate(ControlledGate): """ # pylint: disable=cyclic-import from qiskit.circuit.quantumcircuit import QuantumCircuit + from .h import HGate + from .t import TGate, TdgGate # ┌───┐ # q_0: ───────────────────■─────────────────────■────■───┤ T ├───■── @@ -450,6 +458,8 @@ class RCCXGate(SingletonGate): """ # pylint: disable=cyclic-import from qiskit.circuit.quantumcircuit import QuantumCircuit + from .u1 import U1Gate + from .u2 import U2Gate q = QuantumRegister(3, "q") qc = QuantumCircuit(q, name=self.name) @@ -470,7 +480,7 @@ class RCCXGate(SingletonGate): self.definition = qc -class C3SXGate(ControlledGate): +class C3SXGate(SingletonControlledGate): """The 3-qubit controlled sqrt-X gate. This implementation is based on Page 17 of [1]. @@ -483,6 +493,10 @@ class C3SXGate(ControlledGate): self, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + *, + duration=None, + unit="dt", + _base_label=None, ): """Create a new 3-qubit controlled sqrt-X gate. @@ -491,8 +505,18 @@ class C3SXGate(ControlledGate): ctrl_state (int or str or None): control state expressed as integer, string (e.g. '110'), or None. If None, use all 1s. """ + from .sx import SXGate + super().__init__( - "c3sx", 4, [], num_ctrl_qubits=3, label=label, ctrl_state=ctrl_state, base_gate=SXGate() + "c3sx", + 4, + [], + num_ctrl_qubits=3, + label=label, + ctrl_state=ctrl_state, + base_gate=SXGate(label=_base_label), + duration=duration, + unit=unit, ) def _define(self): @@ -517,6 +541,7 @@ class C3SXGate(ControlledGate): # pylint: disable=cyclic-import from qiskit.circuit.quantumcircuit import QuantumCircuit from .u1 import CU1Gate + from .h import HGate angle = numpy.pi / 8 q = QuantumRegister(4, name="q") @@ -564,7 +589,10 @@ class C3SXGate(ControlledGate): # needlessly disruptive this late in OQ2's lifecycle. The current OQ2 exporter _always_ # outputs the `include 'qelib1.inc' line. ---Jake, 2022-11-21. old_name = self.name - self.name = "c3sqrtx" + if not self.mutable: + copy_self = self.to_mutable() + copy_self.name = "c3sqrtx" + return copy_self.qasm() try: return super().qasm() finally: @@ -572,7 +600,7 @@ class C3SXGate(ControlledGate): @with_controlled_gate_array(_X_ARRAY, num_ctrl_qubits=3, cached_states=(7,)) -class C3XGate(ControlledGate): +class C3XGate(SingletonControlledGate): r"""The X gate controlled on 3 qubits. This implementation uses :math:`\sqrt{T}` and 14 CNOT gates. @@ -582,10 +610,22 @@ class C3XGate(ControlledGate): self, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + *, + _base_label=None, + duration=None, + unit="dt", ): """Create a new 3-qubit controlled X gate.""" super().__init__( - "mcx", 4, [], num_ctrl_qubits=3, label=label, ctrl_state=ctrl_state, base_gate=XGate() + "mcx", + 4, + [], + num_ctrl_qubits=3, + label=label, + ctrl_state=ctrl_state, + base_gate=XGate(label=_base_label), + duration=duration, + unit=unit, ) # seems like open controls not hapening? @@ -752,6 +792,8 @@ class RC3XGate(SingletonGate): """ # pylint: disable=cyclic-import from qiskit.circuit.quantumcircuit import QuantumCircuit + from .u1 import U1Gate + from .u2 import U2Gate q = QuantumRegister(4, "q") qc = QuantumCircuit(q, name=self.name) @@ -782,7 +824,7 @@ class RC3XGate(SingletonGate): @with_controlled_gate_array(_X_ARRAY, num_ctrl_qubits=4, cached_states=(15,)) -class C4XGate(ControlledGate): +class C4XGate(SingletonControlledGate): """The 4-qubit controlled X gate. This implementation is based on Page 21, Lemma 7.5, of [1], with the use @@ -793,10 +835,28 @@ class C4XGate(ControlledGate): [2] Maslov, 2015. https://arxiv.org/abs/1508.03273 """ - 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, + *, + duration=None, + unit="dt", + _base_label=None, + ): """Create a new 4-qubit controlled X gate.""" + if unit is None: + unit = "dt" super().__init__( - "mcx", 5, [], num_ctrl_qubits=4, label=label, ctrl_state=ctrl_state, base_gate=XGate() + "mcx", + 5, + [], + num_ctrl_qubits=4, + label=label, + ctrl_state=ctrl_state, + base_gate=XGate(label=_base_label), + duration=duration, + unit=unit, ) # seems like open controls not hapening? @@ -830,6 +890,7 @@ class C4XGate(ControlledGate): # pylint: disable=cyclic-import from qiskit.circuit.quantumcircuit import QuantumCircuit from .u1 import CU1Gate + from .h import HGate q = QuantumRegister(5, name="q") qc = QuantumCircuit(q, name=self.name) @@ -889,6 +950,9 @@ class MCXGate(ControlledGate): num_ctrl_qubits: Optional[int] = None, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + *, + duration=None, + unit="dt", _base_label=None, ): """Create a new MCX instance. @@ -899,13 +963,19 @@ class MCXGate(ControlledGate): # The CXGate and CCXGate will be implemented for all modes of the MCX, and # the C3XGate and C4XGate will be implemented in the MCXGrayCode class. explicit: dict[int, Type[ControlledGate]] = {1: CXGate, 2: CCXGate} - if num_ctrl_qubits in explicit: - gate_class = explicit[num_ctrl_qubits] + gate_class = explicit.get(num_ctrl_qubits, None) + if gate_class is not None: 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, _base_label=_base_label) + gate.__init__( + label=label, + ctrl_state=ctrl_state, + _base_label=_base_label, + duration=duration, + unit=unit, + ) return gate return super().__new__(cls) @@ -1001,16 +1071,31 @@ class MCXGrayCode(MCXGate): num_ctrl_qubits: Optional[int] = None, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + *, + duration=None, + unit="dt", _base_label=None, ): """Create a new MCXGrayCode instance""" # if 1 to 4 control qubits, create explicit gates explicit = {1: CXGate, 2: CCXGate, 3: C3XGate, 4: C4XGate} - 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_class = explicit.get(num_ctrl_qubits, None) + if gate_class is not None: + gate = gate_class.__new__( + gate_class, + label=label, + ctrl_state=ctrl_state, + _base_label=_base_label, + duration=duration, + unit=unit, + ) # 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, + duration=duration, + unit=unit, + ) return gate return super().__new__(cls) @@ -1031,6 +1116,7 @@ class MCXGrayCode(MCXGate): # pylint: disable=cyclic-import from qiskit.circuit.quantumcircuit import QuantumCircuit from .u1 import MCU1Gate + from .h import HGate q = QuantumRegister(self.num_qubits, name="q") qc = QuantumCircuit(q, name=self.name) @@ -1116,12 +1202,24 @@ class MCXVChain(MCXGate): dirty_ancillas: bool = False, # pylint: disable=unused-argument label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + *, + duration=None, + unit="dt", + _base_label=None, ): """Create a new MCX instance. This must be defined anew to include the additional argument ``dirty_ancillas``. """ - return super().__new__(cls, num_ctrl_qubits, label=label, ctrl_state=ctrl_state) + return super().__new__( + cls, + num_ctrl_qubits, + label=label, + ctrl_state=ctrl_state, + _base_label=_base_label, + duration=duration, + unit=unit, + ) def __init__( self, @@ -1129,8 +1227,15 @@ class MCXVChain(MCXGate): dirty_ancillas: bool = False, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + _base_label=None, ): - super().__init__(num_ctrl_qubits, label=label, ctrl_state=ctrl_state, _name="mcx_vchain") + super().__init__( + num_ctrl_qubits, + label=label, + ctrl_state=ctrl_state, + _name="mcx_vchain", + _base_label=_base_label, + ) self._dirty_ancillas = dirty_ancillas def inverse(self): @@ -1150,6 +1255,8 @@ class MCXVChain(MCXGate): """Define the MCX gate using a V-chain of CX gates.""" # pylint: disable=cyclic-import from qiskit.circuit.quantumcircuit import QuantumCircuit + from .u1 import U1Gate + from .u2 import U2Gate q = QuantumRegister(self.num_qubits, name="q") qc = QuantumCircuit(q, name=self.name) diff --git a/qiskit/circuit/library/standard_gates/y.py b/qiskit/circuit/library/standard_gates/y.py index f6b07773df..2c00628b8d 100644 --- a/qiskit/circuit/library/standard_gates/y.py +++ b/qiskit/circuit/library/standard_gates/y.py @@ -16,8 +16,7 @@ from math import pi from typing import Optional, Union # pylint: disable=cyclic-import -from qiskit.circuit.controlledgate import ControlledGate -from qiskit.circuit.singleton import SingletonGate +from qiskit.circuit.singleton import SingletonGate, SingletonControlledGate from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit._utils import with_gate_array, with_controlled_gate_array @@ -118,7 +117,7 @@ class YGate(SingletonGate): @with_controlled_gate_array(_Y_ARRAY, num_ctrl_qubits=1) -class CYGate(ControlledGate): +class CYGate(SingletonControlledGate): r"""Controlled-Y gate. Can be applied to a :class:`~qiskit.circuit.QuantumCircuit` @@ -178,6 +177,9 @@ class CYGate(ControlledGate): self, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + *, + duration=None, + unit="dt", _base_label=None, ): """Create new CY gate.""" @@ -189,6 +191,8 @@ class CYGate(ControlledGate): label=label, ctrl_state=ctrl_state, base_gate=YGate(label=_base_label), + duration=duration, + unit=unit, ) def _define(self): diff --git a/qiskit/circuit/library/standard_gates/z.py b/qiskit/circuit/library/standard_gates/z.py index 1202e6490c..ee92818575 100644 --- a/qiskit/circuit/library/standard_gates/z.py +++ b/qiskit/circuit/library/standard_gates/z.py @@ -18,8 +18,7 @@ from typing import Optional, Union import numpy from qiskit.circuit._utils import with_gate_array, with_controlled_gate_array -from qiskit.circuit.controlledgate import ControlledGate -from qiskit.circuit.singleton import SingletonGate +from qiskit.circuit.singleton import SingletonGate, SingletonControlledGate from qiskit.circuit.quantumregister import QuantumRegister from .p import PhaseGate @@ -126,7 +125,7 @@ class ZGate(SingletonGate): @with_controlled_gate_array(_Z_ARRAY, num_ctrl_qubits=1) -class CZGate(ControlledGate): +class CZGate(SingletonControlledGate): r"""Controlled-Z gate. This is a Clifford and symmetric gate. @@ -163,6 +162,9 @@ class CZGate(ControlledGate): self, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + *, + duration=None, + unit="dt", _base_label=None, ): """Create new CZ gate.""" @@ -174,6 +176,8 @@ class CZGate(ControlledGate): num_ctrl_qubits=1, ctrl_state=ctrl_state, base_gate=ZGate(label=_base_label), + duration=duration, + unit=unit, ) def _define(self): @@ -200,7 +204,7 @@ class CZGate(ControlledGate): @with_controlled_gate_array(_Z_ARRAY, num_ctrl_qubits=2, cached_states=(3,)) -class CCZGate(ControlledGate): +class CCZGate(SingletonControlledGate): r"""CCZ gate. This is a symmetric gate. @@ -239,10 +243,26 @@ class CCZGate(ControlledGate): the target qubit if the control qubits are in the :math:`|11\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, + *, + duration=None, + unit="dt", + _base_label=None, + ): """Create new CCZ gate.""" super().__init__( - "ccz", 3, [], label=label, num_ctrl_qubits=2, ctrl_state=ctrl_state, base_gate=ZGate() + "ccz", + 3, + [], + label=label, + num_ctrl_qubits=2, + ctrl_state=ctrl_state, + base_gate=ZGate(label=_base_label), + duration=duration, + unit=unit, ) def _define(self): diff --git a/qiskit/circuit/singleton.py b/qiskit/circuit/singleton.py index d4f99a45e4..bb962ba4a3 100644 --- a/qiskit/circuit/singleton.py +++ b/qiskit/circuit/singleton.py @@ -200,6 +200,7 @@ import operator from .instruction import Instruction from .gate import Gate +from .controlledgate import ControlledGate def _impl_new(cls, *_args, **_kwargs): @@ -353,3 +354,28 @@ class SingletonGate(Gate, metaclass=_SingletonMeta, overrides=_SingletonGateOver apply here as well.""" __slots__ = () + + +class _SingletonControlledGateOverrides(_SingletonInstructionOverrides, ControlledGate): + """Overrides for all the mutable methods and properties of `ControlledGate` to make it immutable. + + This class just exists for the principle; there's no additional overrides required compared + to :class:`~.circuit.Instruction`. + """ + + __slots__ = () + + +class SingletonControlledGate( + ControlledGate, + metaclass=_SingletonMeta, + overrides=_SingletonControlledGateOverrides, +): + """A base class to use for :class:`.ControlledGate` objects that by default are singleton instances + + This class is very similar to :class:`SingletonInstruction`, except implies unitary + :class:`.ControlledGate` semantics as well. The same caveats around setting attributes in + that class apply here as well. + """ + + __slots__ = () diff --git a/qiskit/qpy/binary_io/circuits.py b/qiskit/qpy/binary_io/circuits.py index 2cecd7e313..81785bf781 100644 --- a/qiskit/qpy/binary_io/circuits.py +++ b/qiskit/qpy/binary_io/circuits.py @@ -309,9 +309,15 @@ def _read_instruction( gate = gate_class(*params, instruction.num_ctrl_qubits, label=label) else: gate = gate_class(*params, label=label) - gate.num_ctrl_qubits = instruction.num_ctrl_qubits - gate.ctrl_state = instruction.ctrl_state - gate.condition = condition + if ( + gate.num_ctrl_qubits != instruction.num_ctrl_qubits + or gate.ctrl_state != instruction.ctrl_state + ): + gate = gate.to_mutable() + gate.num_ctrl_qubits = instruction.num_ctrl_qubits + gate.ctrl_state = instruction.ctrl_state + if condition: + gate = gate.c_if(*condition) else: if gate_name in { "Initialize", diff --git a/releasenotes/notes/singletons-83782de8bd062cbc.yaml b/releasenotes/notes/singletons-83782de8bd062cbc.yaml index 2abc7a44fb..c6f125f7e4 100644 --- a/releasenotes/notes/singletons-83782de8bd062cbc.yaml +++ b/releasenotes/notes/singletons-83782de8bd062cbc.yaml @@ -52,6 +52,52 @@ features: all the constructor defaults, they will all share a single global instance. This results in large reduction in the memory overhead for > 1 object of these types and significantly faster object construction time. + - | + Introduced a new class :class:`~.SingletonControlledGate` which is a subclass of + :class:`~.ControlledGate` 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, a :class:`.CXGate` doesn't contain any state and thus can + leverage :class:`~.SingletonControlledGate` (and does starting in + this release). In contrast, :class:`~.CRXGate` stores an angle parameter as + part of its instance data and thus can not use :class:`~.SingletonControlledGate`. + + The other potential issue to be aware of when using + :class:`~.SingletonControlledGate` is that the original data model + of :class:`~.ControlledGate` supports mutation. Specifically, the + :attr:`~.ControlledGate.label`, :attr:`~.ControlledGate.duration`, + :attr:`~.ControlledGate.unit`, :attr:`~.ControlledGate.condition`, and + :attr:`~.ControlledGate.ctrl_state` attributes are all accessible and mutable in the + :class:`~.ControlledGate`, but mutation of these attributes on :class:`~.SingletonControlledGate` subclasses + is not allowed, and will raise an exception. These attributes can be customized but only at creation time + (i.e. via the constructor). In that case, the newly constructed gate will be a separate instance with the + custom state instead of the globally shared instance. You can also use the + :meth:`.SingletonControlledGate.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:`~.SingletonControlledGate`: + + * :class:`~.CHGate` + * :class:`~.CSGate` + * :class:`~.CSdgGate` + * :class:`~.CSwapGate` + * :class:`~.CSXGate` + * :class:`~.CXGate` + * :class:`~.CCXGate` + * :class:`~.C3SXGate` + * :class:`~.C3XGate` + * :class:`~.C4XGate` + * :class:`~.CYGate` + * :class:`~.CZGate` + + This means that unless a ``label``, ``condition``, ``duration``, ``unit``, or + ``ctrl_state`` 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. - | Added a new method :meth`.Instruction.to_mutable` and attribute :attr:`.Instruction.mutable` which is used to get a mutable copy and check whether @@ -86,18 +132,32 @@ upgrade: * :class:`~.RC3XGate` * :class:`~.YGate` * :class:`~.ZGate` + * :class:`~.CHGate` + * :class:`~.CSGate` + * :class:`~.CSdgGate` + * :class:`~.CSwapGate` + * :class:`~.CSXGate` + * :class:`~.CXGate` + * :class:`~.CCXGate` + * :class:`~.C3SXGate` + * :class:`~.C3XGate` + * :class:`~.C4XGate` + * :class:`~.CYGate` + * :class:`~.CZGate` + no longer are able to set :attr:`~.Gate.label`, :attr:`~.Gate.condition`, - :attr:`~.Gate.duration`, or :attr:`~.Gate.unit` after instantiating an object - anymore. :attr:`~.Gate.condition` can be set through + :attr:`~.Gate.duration`, or :attr:`~.Gate.unit` (and :attr:`~.ControlledGate.ctrl_state` + for :class:`~.ControlledGate` subclasses) after instantiating an object + anymore. :attr:`~.Gate.condition` can be set through the use :meth:`~.Gate.c_if`. 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. ``label``, ``duration`` and ``unit`` can be given as keyword arguments to these gates at construction time, and a mutable instance will be returned automatically. 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. + these classes to be :class:`~.SingletonGate` and :class:`~.SingletonControlledGate` + 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 diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index 98493cf759..f0c33df0a6 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -22,6 +22,7 @@ from ddt import ddt, data, unpack from qiskit import QuantumRegister, QuantumCircuit, execute, BasicAer, QiskitError from qiskit.test import QiskitTestCase from qiskit.circuit import ControlledGate, Parameter, Gate +from qiskit.circuit.singleton import SingletonControlledGate, _SingletonControlledGateOverrides from qiskit.circuit.exceptions import CircuitError from qiskit.quantum_info.operators.predicates import matrix_equal, is_unitary_matrix from qiskit.quantum_info.random import random_unitary @@ -1014,6 +1015,8 @@ class TestControlledGate(QiskitTestCase): """Test all gates in standard extensions which are of type ControlledGate and have a base gate setting. """ + if gate_class in {SingletonControlledGate, _SingletonControlledGateOverrides}: + self.skipTest("SingletonControlledGate isn't directly instantiated.") num_free_params = len(_get_free_params(gate_class.__init__, ignore=["self"])) free_params = [0.1 * i for i in range(num_free_params)] if gate_class in [MCU1Gate, MCPhaseGate]: @@ -1144,6 +1147,8 @@ class TestControlledGate(QiskitTestCase): num_ctrl_qubits = 1 for gate_class in ControlledGate.__subclasses__(): with self.subTest(i=repr(gate_class)): + if gate_class in {SingletonControlledGate, _SingletonControlledGateOverrides}: + self.skipTest("Singleton class isn't intended to be created directly.") num_free_params = len(_get_free_params(gate_class.__init__, ignore=["self"])) free_params = [0.1 * (i + 1) for i in range(num_free_params)] if gate_class in [MCU1Gate, MCPhaseGate]: @@ -1311,6 +1316,8 @@ class TestOpenControlledToMatrix(QiskitTestCase): @combine(gate_class=ControlledGate.__subclasses__(), ctrl_state=[0, None]) def test_open_controlled_to_matrix(self, gate_class, ctrl_state): """Test open controlled to_matrix.""" + if gate_class in {SingletonControlledGate, _SingletonControlledGateOverrides}: + self.skipTest("SingletonGateClass isn't intended for direct initalization") num_free_params = len(_get_free_params(gate_class.__init__, ignore=["self"])) free_params = [0.1 * i for i in range(1, num_free_params + 1)] if gate_class in [MCU1Gate, MCPhaseGate]: diff --git a/test/python/circuit/test_gate_definitions.py b/test/python/circuit/test_gate_definitions.py index 36498beef2..c12253f81f 100644 --- a/test/python/circuit/test_gate_definitions.py +++ b/test/python/circuit/test_gate_definitions.py @@ -283,9 +283,11 @@ class TestGateEquivalenceEqual(QiskitTestCase): "Commuting2qBlock", "PauliEvolutionGate", "SingletonGate", + "SingletonControlledGate", "_U0Gate", "_DefinedGate", "_SingletonGateOverrides", + "_SingletonControlledGateOverrides", } # Amazingly, Python's scoping rules for class bodies means that this is the closest we can get # to a "natural" comprehension or functional iterable definition: diff --git a/test/python/circuit/test_singleton.py b/test/python/circuit/test_singleton.py index 3d59bb7a8b..4366557e9b 100644 --- a/test/python/circuit/test_singleton.py +++ b/test/python/circuit/test_singleton.py @@ -21,7 +21,7 @@ import copy import io import pickle -from qiskit.circuit.library import HGate, SXGate +from qiskit.circuit.library import HGate, SXGate, CXGate, CZGate, CSwapGate, CHGate, CCXGate, XGate from qiskit.circuit import Clbit, QuantumCircuit, QuantumRegister, ClassicalRegister from qiskit.circuit.singleton import SingletonGate, SingletonInstruction from qiskit.converters import dag_to_circuit, circuit_to_dag @@ -325,3 +325,275 @@ class TestSingletonGate(QiskitTestCase): self.assertIsNot(esp, base) self.assertIs(base.base_class, Measure) self.assertIs(esp.base_class, ESPMeasure) + + +class TestSingletonControlledGate(QiskitTestCase): + """Qiskit SingletonGate tests.""" + + def test_default_singleton(self): + gate = CXGate() + new_gate = CXGate() + self.assertIs(gate, new_gate) + + def test_label_not_singleton(self): + gate = CXGate() + label_gate = CXGate(label="special") + self.assertIsNot(gate, label_gate) + + def test_condition_not_singleton(self): + gate = CZGate() + condition_gate = CZGate().c_if(Clbit(), 0) + self.assertIsNot(gate, condition_gate) + + def test_raise_on_state_mutation(self): + gate = CSwapGate() + with self.assertRaises(TypeError): + gate.label = "foo" + with self.assertRaises(TypeError): + gate.condition = (Clbit(), 0) + + def test_labeled_condition(self): + singleton_gate = CSwapGate() + clbit = Clbit() + gate = CSwapGate(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 = CXGate() + copied = gate.copy() + self.assertIs(gate, copied) + + def test_label_copy(self): + gate = CZGate(label="special") + copied = gate.copy() + self.assertIsNot(gate, copied) + self.assertEqual(gate, copied) + + def test_label_copy_new(self): + gate = CZGate() + label_gate = CZGate(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 = CZGate().c_if(Clbit(), 0) + copied = gate.copy() + self.assertIsNot(gate, copied) + self.assertEqual(gate, copied) + + def test_condition_label_copy(self): + clbit = Clbit() + gate = CZGate(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 = CXGate() + copied = copy.deepcopy(gate) + self.assertIs(gate, copied) + + def test_deepcopy_with_label(self): + singleton_gate = CXGate() + gate = CXGate(label="special") + copied = copy.deepcopy(gate) + self.assertIsNot(gate, copied) + self.assertEqual(gate, copied) + self.assertEqual(copied.label, "special") + self.assertTrue(copied.mutable) + self.assertIsNot(gate.base_gate, copied.base_gate) + self.assertIsNot(copied, singleton_gate) + self.assertEqual(singleton_gate, copied) + self.assertNotEqual(singleton_gate.label, copied.label) + + def test_deepcopy_with_condition(self): + gate = CCXGate().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 = CHGate(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 = CHGate() + label_gate = CHGate(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 = CHGate() + gate = CHGate(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(2) + gate = CHGate() + qc.append(gate, [0, 1]) + 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 = CHGate(label="special") + qc = QuantumCircuit(2) + qc.append(gate, [0, 1]) + 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(2, 1) + gate = CHGate().c_if(qc.cregs[0], 0) + qc.append(gate, [0, 1]) + 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(2, 1) + gate = CHGate(label="conditionally special").c_if(qc.cregs[0], 0) + qc.append(gate, [0, 1]) + 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 = CHGate() + 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 = CXGate() + self.assertFalse(gate.mutable) + label_gate = CXGate(label="foo") + self.assertTrue(label_gate.mutable) + self.assertIsNot(gate, label_gate) + + def test_to_mutable(self): + gate = CXGate() + 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 = CZGate() + 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 = CZGate(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_inner_gate_label(self): + inner_gate = HGate(label="my h gate") + controlled_gate = inner_gate.control() + self.assertTrue(controlled_gate.mutable) + self.assertEqual("my h gate", controlled_gate.base_gate.label) + + def test_inner_gate_label_outer_label_too(self): + inner_gate = HGate(label="my h gate") + controlled_gate = inner_gate.control(label="foo") + self.assertTrue(controlled_gate.mutable) + self.assertEqual("my h gate", controlled_gate.base_gate.label) + self.assertEqual("foo", controlled_gate.label) + + def test_inner_outer_label_with_c_if(self): + inner_gate = HGate(label="my h gate") + controlled_gate = inner_gate.control(label="foo") + clbit = Clbit() + conditonal_controlled_gate = controlled_gate.c_if(clbit, 0) + self.assertTrue(conditonal_controlled_gate.mutable) + self.assertEqual("my h gate", conditonal_controlled_gate.base_gate.label) + self.assertEqual("foo", conditonal_controlled_gate.label) + self.assertEqual((clbit, 0), conditonal_controlled_gate.condition) + + def test_inner_outer_label_with_c_if_deepcopy(self): + inner_gate = XGate(label="my h gate") + controlled_gate = inner_gate.control(label="foo") + clbit = Clbit() + conditonal_controlled_gate = controlled_gate.c_if(clbit, 0) + self.assertTrue(conditonal_controlled_gate.mutable) + self.assertEqual("my h gate", conditonal_controlled_gate.base_gate.label) + self.assertEqual("foo", conditonal_controlled_gate.label) + self.assertEqual((clbit, 0), conditonal_controlled_gate.condition) + copied = copy.deepcopy(conditonal_controlled_gate) + self.assertIsNot(conditonal_controlled_gate, copied) + self.assertTrue(copied.mutable) + self.assertEqual("my h gate", copied.base_gate.label) + self.assertEqual("foo", copied.label) + self.assertEqual((clbit, 0), copied.condition) + + def test_inner_outer_label_pickle(self): + inner_gate = XGate(label="my h gate") + controlled_gate = inner_gate.control(label="foo") + self.assertTrue(controlled_gate.mutable) + self.assertEqual("my h gate", controlled_gate.base_gate.label) + self.assertEqual("foo", controlled_gate.label) + with io.BytesIO() as fd: + pickle.dump(controlled_gate, fd) + fd.seek(0) + copied = pickle.load(fd) + self.assertIsNot(controlled_gate, copied) + self.assertTrue(copied.mutable) + self.assertEqual("my h gate", copied.base_gate.label) + self.assertEqual("foo", copied.label) diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index 35576d9b43..b539ff3d5d 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -2048,7 +2048,7 @@ class TestDagSubstitute(QiskitTestCase): base.add_qubits(base_qubits) base.add_clbits(base_clbits) base.apply_operation_back(HGate(), [base_qubits[0]], []) - conditioned = CZGate() + conditioned = CZGate().to_mutable() conditioned.condition = (base_clbits[0], True) target = base.apply_operation_back(conditioned, base_qubits, []) base.apply_operation_back(XGate(), [base_qubits[1]], []) @@ -2063,7 +2063,7 @@ class TestDagSubstitute(QiskitTestCase): expected.add_clbits(base_clbits) expected.apply_operation_back(HGate(), [base_qubits[0]], []) expected.apply_operation_back(HGate(), [base_qubits[0]], []) - cx = CXGate() + cx = CXGate().to_mutable() cx.condition = (base_clbits[0], True) expected.apply_operation_back(cx, base_qubits, []) expected.apply_operation_back(HGate(), [base_qubits[0]], []) @@ -2081,7 +2081,7 @@ class TestDagSubstitute(QiskitTestCase): base.add_qubits(base_qubits) base.add_creg(base_creg) base.apply_operation_back(HGate(), [base_qubits[0]], []) - conditioned = CZGate() + conditioned = CZGate().to_mutable() conditioned.condition = (base_creg, 3) target = base.apply_operation_back(conditioned, base_qubits, []) base.apply_operation_back(XGate(), [base_qubits[1]], []) @@ -2096,7 +2096,7 @@ class TestDagSubstitute(QiskitTestCase): expected.add_creg(base_creg) expected.apply_operation_back(HGate(), [base_qubits[0]], []) expected.apply_operation_back(HGate(), [base_qubits[0]], []) - cx = CXGate() + cx = CXGate().to_mutable() cx.condition = (base_creg, 3) expected.apply_operation_back(cx, base_qubits, []) expected.apply_operation_back(HGate(), [base_qubits[0]], []) @@ -2114,7 +2114,7 @@ class TestDagSubstitute(QiskitTestCase): base.add_qubits(base_qubits) base.add_clbits(base_clbits) base.apply_operation_back(HGate(), [base_qubits[0]], []) - conditioned_cz = CZGate() + conditioned_cz = CZGate().to_mutable() conditioned_cz.condition = (base_clbits[0], True) target = base.apply_operation_back(conditioned_cz, base_qubits, []) base.apply_operation_back(XGate(), [base_qubits[1]], []) @@ -2152,7 +2152,7 @@ class TestDagSubstitute(QiskitTestCase): base.add_qubits(base_qubits) base.add_clbits(base_clbits) base.apply_operation_back(HGate(), [base_qubits[0]], []) - conditioned_cz = CZGate() + conditioned_cz = CZGate().to_mutable() conditioned_cz.condition = (base_clbits[0], True) target = base.apply_operation_back(conditioned_cz, base_qubits, []) base.apply_operation_back(XGate(), [base_qubits[1]], []) @@ -2162,7 +2162,7 @@ class TestDagSubstitute(QiskitTestCase): sub.cx(0, 1).c_if(0, True) sub.h(0) - conditioned_cx = CXGate() + conditioned_cx = CXGate().to_mutable() conditioned_cx.condition = conditioned_cz.condition expected = DAGCircuit() @@ -2207,7 +2207,7 @@ class TestDagSubstitute(QiskitTestCase): expected.add_qreg(base_qreg) expected.add_creg(base_creg) expected.add_creg(added_creg) - cx = CXGate() + cx = CXGate().to_mutable() cx.condition = (added_creg, 3) expected.apply_operation_back(cx, base_qreg, []) self.assertEqual(base, expected) @@ -2248,7 +2248,7 @@ class TestDagSubstituteNode(QiskitTestCase): dag.add_qreg(qr) dag.add_creg(cr) dag.apply_operation_back(HGate(), [qr[1]]) - cx_gate = CXGate() + cx_gate = CXGate().to_mutable() cx_gate.condition = (cr, 1) node_to_be_replaced = dag.apply_operation_back(cx_gate, [qr[1], qr[0]])