Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite singleton handling including SingletonInstruction #11014

Merged
merged 5 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/apidoc/circuit_singleton.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.. _qiskit-circuit-singleton:

.. automodule:: qiskit.circuit.singleton
:no-members:
:no-inherited-members:
:no-special-members:
1 change: 1 addition & 0 deletions docs/apidoc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ API Reference
circuit
circuit_library
circuit_classical
circuit_singleton
compiler
execute
visualization
Expand Down
4 changes: 2 additions & 2 deletions qiskit/circuit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,6 @@
InstructionSet
Operation
EquivalenceLibrary
SingletonGate

Control Flow Operations
-----------------------
Expand Down Expand Up @@ -359,6 +358,7 @@

.. autoexception:: CircuitError
"""

from .exceptions import CircuitError
from .quantumcircuit import QuantumCircuit
from .classicalregister import ClassicalRegister, Clbit
Expand All @@ -367,7 +367,7 @@

# pylint: disable=cyclic-import
from .controlledgate import ControlledGate
from .singleton_gate import SingletonGate
from . import singleton
from .instruction import Instruction
from .instructionset import InstructionSet
from .operation import Operation
Expand Down
5 changes: 4 additions & 1 deletion qiskit/circuit/controlledgate.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,10 @@ def params(self, parameters):
CircuitError: If controlled gate does not define a base gate.
"""
if self.base_gate:
self.base_gate.params = parameters
if self.base_gate.mutable:
self.base_gate.params = parameters
elif parameters:
raise CircuitError("cannot set parameters on immutable base gate")
else:
raise CircuitError("Controlled gate does not define base gate for extracting params")

Expand Down
49 changes: 45 additions & 4 deletions qiskit/circuit/instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@
The circuit itself keeps this context.
"""

from __future__ import annotations

import copy
from itertools import zip_longest
from typing import List
from typing import List, Type

import numpy

Expand Down Expand Up @@ -103,6 +105,38 @@ def __init__(self, name, num_qubits, num_clbits, params, duration=None, unit="dt

self.params = params # must be at last (other properties may be required for validation)

@property
def base_class(self) -> Type[Instruction]:
"""Get the base class of this instruction. This is guaranteed to be in the inheritance tree
of ``self``.
The "base class" of an instruction is the lowest class in its inheritance tree that the
object should be considered entirely compatible with for _all_ circuit applications. This
typically means that the subclass is defined purely to offer some sort of programmer
convenience over the base class, and the base class is the "true" class for a behavioural
perspective. In particular, you should *not* override :attr:`base_class` if you are
defining a custom version of an instruction that will be implemented differently by
hardware, such as an alternative measurement strategy, or a version of a parametrised gate
with a particular set of parameters for the purposes of distinguishing it in a
:class:`.Target` from the full parametrised gate.
This is often exactly equivalent to ``type(obj)``, except in the case of singleton instances
of standard-library instructions. These singleton instances are special subclasses of their
base class, and this property will return that base. For example::
>>> isinstance(XGate(), XGate)
True
>>> type(XGate()) is XGate
False
>>> XGate().base_class is XGate
True
In general, you should not rely on the precise class of an instruction; within a given
circuit, it is expected that :attr:`Instruction.name` should be a more suitable
discriminator in most situations.
"""
return type(self)

@property
def mutable(self) -> bool:
"""Is this instance is a mutable unique instance or not.
Expand Down Expand Up @@ -141,8 +175,9 @@ def __eq__(self, other):
Returns:
bool: are self and other equal.
"""
if (
type(self) is not type(other)
if ( # pylint: disable=too-many-boolean-expressions
not isinstance(other, Instruction)
or self.base_class is not other.base_class
or self.name != other.name
or self.num_qubits != other.num_qubits
or self.num_clbits != other.num_clbits
Expand Down Expand Up @@ -366,7 +401,13 @@ def reverse_ops(self):
qiskit.circuit.Instruction: a new instruction with
sub-instructions reversed.
"""
if not self._definition:
# A single `Instruction` cannot really determine whether it is a "composite" instruction or
# not; it depends on greater context whether it needs to be decomposed. The `_definition`
# not existing is flaky; all that means is that nobody has _yet_ asked for its definition;
# for efficiency, most gates define this on-the-fly. The checks here are a very very
# approximate check for an "atomic" instruction, that are mostly just this way for
# historical consistency.
if not self._definition or not self.mutable:
return self.copy()

reverse_inst = self.copy(name=self.name + "_reverse")
Expand Down
11 changes: 3 additions & 8 deletions qiskit/circuit/library/standard_gates/dcx.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

"""Double-CNOT gate."""

from qiskit.circuit.singleton_gate import SingletonGate
from qiskit.circuit.singleton import SingletonGate
from qiskit.circuit.quantumregister import QuantumRegister
from qiskit.circuit._utils import with_gate_array

Expand Down Expand Up @@ -48,14 +48,9 @@ class DCXGate(SingletonGate):
\end{pmatrix}
"""

def __init__(self, label=None, duration=None, unit=None, _condition=None):
def __init__(self, label=None, *, duration=None, unit="dt"):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree making these keyword only makes sense, we should have done this in #10314. It'd be good to leave a comment on #11013 for @hunterkemeny to adopt this signature in that PR too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, this actually was a bit of a mistake that I changed what you originally had - I was just writing it on autopilot, mechanically and manually undoing the mistake I made where I'd removed all the label and duration arguments. But yeah, since we've got the opportunity, let's just do it.

"""Create new DCX gate."""
if unit is None:
unit = "dt"

super().__init__(
"dcx", 2, [], label=label, _condition=_condition, duration=duration, unit=unit
)
super().__init__("dcx", 2, [], label=label, duration=duration, unit=unit)

def _define(self):
"""
Expand Down
10 changes: 3 additions & 7 deletions qiskit/circuit/library/standard_gates/ecr.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

from qiskit.circuit._utils import with_gate_array
from qiskit.circuit.quantumregister import QuantumRegister
from qiskit.circuit.singleton_gate import SingletonGate
from qiskit.circuit.singleton import SingletonGate
from .rzx import RZXGate
from .x import XGate

Expand Down Expand Up @@ -84,13 +84,9 @@ class ECRGate(SingletonGate):
\end{pmatrix}
"""

def __init__(self, label=None, _condition=None, duration=None, unit=None):
def __init__(self, label=None, *, duration=None, unit="dt"):
"""Create new ECR gate."""
if unit is None:
unit = "dt"
super().__init__(
"ecr", 2, [], label=label, _condition=_condition, duration=duration, unit=unit
)
super().__init__("ecr", 2, [], label=label, duration=duration, unit=unit)

def _define(self):
"""
Expand Down
10 changes: 3 additions & 7 deletions qiskit/circuit/library/standard_gates/h.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from typing import Optional, Union
import numpy
from qiskit.circuit.controlledgate import ControlledGate
from qiskit.circuit.singleton_gate import SingletonGate
from qiskit.circuit.singleton 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
Expand Down Expand Up @@ -54,13 +54,9 @@ class HGate(SingletonGate):
\end{pmatrix}
"""

def __init__(self, label: Optional[str] = None, duration=None, unit=None, _condition=None):
def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"):
"""Create new H gate."""
if unit is None:
unit = "dt"
super().__init__(
"h", 1, [], label=label, _condition=_condition, duration=duration, unit=unit
)
super().__init__("h", 1, [], label=label, duration=duration, unit=unit)

def _define(self):
"""
Expand Down
10 changes: 3 additions & 7 deletions qiskit/circuit/library/standard_gates/i.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"""Identity gate."""

from typing import Optional
from qiskit.circuit.singleton_gate import SingletonGate
from qiskit.circuit.singleton import SingletonGate
from qiskit.circuit._utils import with_gate_array


Expand Down Expand Up @@ -45,13 +45,9 @@ class IGate(SingletonGate):
└───┘
"""

def __init__(self, label: Optional[str] = None, duration=None, unit=None, _condition=None):
def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"):
"""Create new Identity gate."""
if unit is None:
unit = "dt"
super().__init__(
"id", 1, [], label=label, _condition=_condition, duration=duration, unit=unit
)
super().__init__("id", 1, [], label=label, duration=duration, unit=unit)

def inverse(self):
"""Invert this gate."""
Expand Down
10 changes: 3 additions & 7 deletions qiskit/circuit/library/standard_gates/iswap.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import numpy as np

from qiskit.circuit.singleton_gate import SingletonGate
from qiskit.circuit.singleton import SingletonGate
from qiskit.circuit.quantumregister import QuantumRegister
from qiskit.circuit._utils import with_gate_array

Expand Down Expand Up @@ -85,13 +85,9 @@ class iSwapGate(SingletonGate):
\end{pmatrix}
"""

def __init__(self, label: Optional[str] = None, duration=None, unit=None, _condition=None):
def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"):
"""Create new iSwap gate."""
if unit is None:
unit = "dt"
super().__init__(
"iswap", 2, [], label=label, _condition=_condition, duration=duration, unit=unit
)
super().__init__("iswap", 2, [], label=label, duration=duration, unit=unit)

def _define(self):
"""
Expand Down
18 changes: 5 additions & 13 deletions qiskit/circuit/library/standard_gates/s.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import numpy

from qiskit.circuit.controlledgate import ControlledGate
from qiskit.circuit.singleton_gate import SingletonGate
from qiskit.circuit.singleton 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
Expand Down Expand Up @@ -59,13 +59,9 @@ class SGate(SingletonGate):
Equivalent to a :math:`\pi/2` radian rotation about the Z axis.
"""

def __init__(self, label: Optional[str] = None, duration=None, unit=None, _condition=None):
def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"):
"""Create new S gate."""
if unit is None:
unit = "dt"
super().__init__(
"s", 1, [], label=label, _condition=_condition, duration=duration, unit=unit
)
super().__init__("s", 1, [], label=label, duration=None, unit="dt")

def _define(self):
"""
Expand Down Expand Up @@ -124,13 +120,9 @@ class SdgGate(SingletonGate):
Equivalent to a :math:`-\pi/2` radian rotation about the Z axis.
"""

def __init__(self, label: Optional[str] = None, duration=None, unit=None, _condition=None):
def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"):
"""Create new Sdg gate."""
if unit is None:
unit = "dt"
super().__init__(
"sdg", 1, [], label=label, _condition=_condition, duration=duration, unit=unit
)
super().__init__("sdg", 1, [], label=label, duration=None, unit="dt")

def _define(self):
"""
Expand Down
10 changes: 3 additions & 7 deletions qiskit/circuit/library/standard_gates/swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from typing import Optional, Union
import numpy
from qiskit.circuit.controlledgate import ControlledGate
from qiskit.circuit.singleton_gate import SingletonGate
from qiskit.circuit.singleton import SingletonGate
from qiskit.circuit.quantumregister import QuantumRegister
from qiskit.circuit._utils import with_gate_array, with_controlled_gate_array

Expand Down Expand Up @@ -59,13 +59,9 @@ class SwapGate(SingletonGate):
|a, b\rangle \rightarrow |b, a\rangle
"""

def __init__(self, label: Optional[str] = None, duration=None, unit=None, _condition=None):
def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"):
"""Create new SWAP gate."""
if unit is None:
unit = "dt"
super().__init__(
"swap", 2, [], label=label, _condition=_condition, duration=duration, unit=unit
)
super().__init__("swap", 2, [], label=label, duration=duration, unit=unit)

def _define(self):
"""
Expand Down
18 changes: 5 additions & 13 deletions qiskit/circuit/library/standard_gates/sx.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from math import pi
from typing import Optional, Union
from qiskit.circuit.controlledgate import ControlledGate
from qiskit.circuit.singleton_gate import SingletonGate
from qiskit.circuit.singleton import SingletonGate
from qiskit.circuit.quantumregister import QuantumRegister
from qiskit.circuit._utils import with_gate_array, with_controlled_gate_array

Expand Down Expand Up @@ -63,13 +63,9 @@ class SXGate(SingletonGate):
"""

def __init__(self, label: Optional[str] = None, duration=None, unit=None, _condition=None):
def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"):
"""Create new SX gate."""
if unit is None:
unit = "dt"
super().__init__(
"sx", 1, [], label=label, _condition=_condition, duration=duration, unit=unit
)
super().__init__("sx", 1, [], label=label, duration=duration, unit=unit)

def _define(self):
"""
Expand Down Expand Up @@ -146,13 +142,9 @@ class SXdgGate(SingletonGate):
"""

def __init__(self, label: Optional[str] = None, duration=None, unit=None, _condition=None):
def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"):
"""Create new SXdg gate."""
if unit is None:
unit = "dt"
super().__init__(
"sxdg", 1, [], label=label, _condition=_condition, duration=duration, unit=unit
)
super().__init__("sxdg", 1, [], label=label, duration=duration, unit=unit)

def _define(self):
"""
Expand Down
Loading