Skip to content
Open
2 changes: 1 addition & 1 deletion qiskit/circuit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@
from .operation import Operation
from .barrier import Barrier
from .delay import Delay
from .measure import Measure
from .measure import Measure, MeasureX, MeasureY, MeasureZ
from .reset import Reset
from .parameter import Parameter
from .parametervector import ParameterVector
Expand Down
5 changes: 4 additions & 1 deletion qiskit/circuit/library/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@
:toctree: ../stubs/

Measure
MeasureX
MeasureY
MeasureZ
Reset

Generalized Gates
Expand Down Expand Up @@ -471,7 +474,7 @@
from .standard_gates import *
from .templates import *
from ..barrier import Barrier
from ..measure import Measure
from ..measure import Measure, MeasureX, MeasureY, MeasureZ
from ..reset import Reset


Expand Down
5 changes: 4 additions & 1 deletion qiskit/circuit/library/standard_gates/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def get_standard_gate_name_mapping():
"""Return a dictionary mapping the name of standard gates and instructions to an object for
that name."""
from qiskit.circuit.parameter import Parameter
from qiskit.circuit.measure import Measure
from qiskit.circuit.measure import Measure, MeasureX, MeasureY, MeasureZ
from qiskit.circuit.delay import Delay
from qiskit.circuit.reset import Reset

Expand Down Expand Up @@ -169,6 +169,9 @@ def get_standard_gate_name_mapping():
ZGate(),
Delay(Parameter("t")),
Measure(),
MeasureX(),
MeasureY(),
MeasureZ(),
]
name_mapping = {gate.name: gate for gate in gates}
return name_mapping
71 changes: 71 additions & 0 deletions qiskit/circuit/measure.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,21 @@
Quantum measurement in the computational basis.
"""

from __future__ import annotations

import warnings

from qiskit.circuit.instruction import Instruction
from qiskit.circuit.exceptions import CircuitError


# Measure class kept for backwards compatibility, and repurposed
# as a common parent class for all measurement instructions.
class Measure(Instruction):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's possible that we could accept an inheritance relationship here, but I don't think it's for certain the correct way of doing things, over (for example) a set of flags defined Instruction or Operation. This particular inheritance is definitely not safe, though - Measure is, and needs to remain, a measure in the Z basis, so MeasureX isn't an instance of it. There's also the false abstraction here that the base class is only abstract as far as a single-qubit Pauli-basis measurement, which isn't the most general type.

Aside from those abstraction/false-hierarchy issues, another thing to consider with inheritance is that we would need to work out how external / further subclasses of BaseMeasure (or whatever) would be safely serialised/deserialised by QPY - this PR adds an inheritance structure to instructions that is deliberately avoided in our data model elsewhere. This has some wide-ranging implications that I'm too "on holiday" to think through properly right now (sorry).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I see 🤔 let's pick this up again once you come back then! Thanks for the detailed explanation

"""Quantum measurement in the computational basis."""

basis: str | None = None

def __init__(self):
"""Create new measurement instruction."""
super().__init__("measure", 1, 1, [])
Expand All @@ -41,6 +47,71 @@ def broadcast_arguments(self, qargs, cargs):
raise CircuitError("register size error")


class MeasureX(Measure):
"""Quantum measurement in the X basis."""

basis: str | None = "X"

def __init__(self):
"""Create new X measurement instruction."""
# pylint: disable=bad-super-call
super(Measure, self).__init__("measure_x", 1, 1, [])

def _define(self):
# pylint: disable=cyclic-import
from qiskit.circuit.quantumcircuit import QuantumCircuit

qc = QuantumCircuit(1, 1)
qc.h(0)
qc.measure_z(0, 0)
qc.h(0)

self.definition = qc


class MeasureY(Measure):
"""Quantum measurement in the Y basis."""

basis: str | None = "Y"

def __init__(self):
"""Create new Y measurement instruction."""
# pylint: disable=bad-super-call
super(Measure, self).__init__("measure_y", 1, 1, [])

def _define(self):
# pylint: disable=cyclic-import
from qiskit.circuit.quantumcircuit import QuantumCircuit

qc = QuantumCircuit(1, 1)
qc.sdg(0)
qc.measure_x(0, 0)
qc.s(0)

self.definition = qc


class MeasureZ(Measure):
"""Quantum Z measurement in the Z basis."""

basis: str | None = "Z"

def __init__(self):
"""Create new measurement instruction."""
# pylint: disable=bad-super-call
super(Measure, self).__init__("measure_z", 1, 1, [])

def _define(self):
# pylint: disable=cyclic-import
from qiskit.circuit.quantumcircuit import QuantumCircuit

qc = QuantumCircuit(1, 1)
qc.measure(0, 0)

self.definition = qc


# TODO: deprecated ? delete : update
def measure(circuit, qubit, clbit):
"""Measure a quantum bit into classical bit.

Expand Down
47 changes: 46 additions & 1 deletion qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
from .bit import Bit
from .quantumcircuitdata import QuantumCircuitData, CircuitInstruction
from .delay import Delay
from .measure import Measure
from .measure import Measure, MeasureX, MeasureY, MeasureZ
from .reset import Reset

try:
Expand Down Expand Up @@ -2259,6 +2259,51 @@ def measure(self, qubit: QubitSpecifier, cbit: ClbitSpecifier) -> InstructionSet
"""
return self.append(Measure(), [qubit], [cbit])

def measure_x(self, qubit: QubitSpecifier, cbit: ClbitSpecifier) -> InstructionSet:
"""Measure quantum bit into classical bit (tuples), in the X basis.

Args:
qubit: qubit to measure.
cbit: classical bit to place the measurement in.

Returns:
qiskit.circuit.InstructionSet: handle to the added instructions.

Raises:
CircuitError: if arguments have bad format.
"""
return self.append(MeasureX(), [qubit], [cbit])

def measure_y(self, qubit: QubitSpecifier, cbit: ClbitSpecifier) -> InstructionSet:
"""Measure quantum bit into classical bit (tuples), in the Y basis.

Args:
qubit: qubit to measure.
cbit: classical bit to place the measurement in.

Returns:
qiskit.circuit.InstructionSet: handle to the added instructions.

Raises:
CircuitError: if arguments have bad format.
"""
return self.append(MeasureY(), [qubit], [cbit])

def measure_z(self, qubit: QubitSpecifier, cbit: ClbitSpecifier) -> InstructionSet:
"""Measure quantum bit into classical bit (tuples), in the Z basis.

Args:
qubit: qubit to measure.
cbit: classical bit to place the measurement in.

Returns:
qiskit.circuit.InstructionSet: handle to the added instructions.

Raises:
CircuitError: if arguments have bad format.
"""
return self.append(MeasureZ(), [qubit], [cbit])

def measure_active(self, inplace: bool = True) -> Optional["QuantumCircuit"]:
"""Adds measurement to all non-idle qubits. Creates a new ClassicalRegister with
a size equal to the number of non-idle qubits being measured.
Expand Down
11 changes: 8 additions & 3 deletions qiskit/dagcircuit/dagdependency.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@

from qiskit.circuit.quantumregister import QuantumRegister, Qubit
from qiskit.circuit.classicalregister import ClassicalRegister, Clbit
from qiskit.circuit.commutation_checker import CommutationChecker
from qiskit.circuit.measure import Measure
from qiskit.dagcircuit.exceptions import DAGDependencyError
from qiskit.dagcircuit.dagdepnode import DAGDepNode
from qiskit.circuit.commutation_checker import CommutationChecker


# ToDo: DagDependency needs to be refactored:
Expand Down Expand Up @@ -382,8 +383,12 @@ def _create_op_node(self, operation, qargs, cargs):
Returns:
DAGDepNode: the newly added node.
"""
directives = ["measure"]
if not getattr(operation, "_directive", False) and operation.name not in directives:
directives = []
if (
not getattr(operation, "_directive", False)
and not isinstance(operation, Measure)
and operation.name not in directives
):
Comment on lines -385 to +391
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

After introducing the isinstance check to harness subclassing, directives became empty. Should it be removed? If kept, I would suggest turning it into a set instead of a list.

qindices_list = []
for elem in qargs:
qindices_list.append(self.qubits.index(elem))
Expand Down
13 changes: 12 additions & 1 deletion qiskit/visualization/circuit/matplotlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ def __init__(
self._calibrations = self._circuit.calibrations

for node in itertools.chain.from_iterable(self._nodes):
if node.cargs and node.op.name != "measure":
if node.cargs and not isinstance(node.op, Measure):
if cregbundle:
warn(
"Cregbundle set to False since an instruction needs to refer"
Expand Down Expand Up @@ -992,6 +992,17 @@ def _measure(self, node):
linewidth=self._lwidth2,
zorder=PORDER_GATE,
)
if node.op.basis:
self._ax.text(
qx - 0.4 * WID,
qy + 0.25 * HIG,
node.op.basis.upper(),
color=self._data[node]["gt"],
clip_on=True,
zorder=PORDER_TEXT,
fontsize=0.5 * self._style["fs"],
fontweight="bold",
)
# arrow
self._line(
self._data[node]["q_xy"][0],
Expand Down
3 changes: 3 additions & 0 deletions qiskit/visualization/circuit/qcstyle.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,9 @@ def __init__(self):
"reset": (colors["black"], colors["white"]),
"target": (colors["white"], colors["white"]),
"measure": (colors["black"], colors["white"]),
"measure_x": (colors["black"], colors["white"]),
"measure_y": (colors["black"], colors["white"]),
"measure_z": (colors["black"], colors["white"]),
},
}

Expand Down
12 changes: 12 additions & 0 deletions qiskit/visualization/circuit/styles/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,18 @@
"measure": [
"#000000",
"#FFFFFF"
],
"measure_x": [
"#000000",
"#FFFFFF"
],
"measure_y": [
"#000000",
"#FFFFFF"
],
"measure_z": [
"#000000",
"#FFFFFF"
]
}
}
17 changes: 11 additions & 6 deletions qiskit/visualization/circuit/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,12 +210,17 @@ class MeasureFrom(BoxOnQuWire):
bot: └╥┘ └╥┘
"""

def __init__(self):
def __init__(self, basis=None):
super().__init__()
self.top_format = self.mid_format = self.bot_format = "%s"
self.top_connect = "┌─┐"
self.mid_content = "┤M├"
self.bot_connect = "└╥┘"
if basis:
self.top_connect = "┌───┐"
self.mid_content = "┤M_{}├".format(basis.lower())
self.bot_connect = "└─╥─┘"
else:
self.top_connect = "┌─┐"
self.mid_content = "┤M├"
self.bot_connect = "└╥┘"

self.top_pad = self.bot_pad = " "
self._mid_padding = "─"
Expand Down Expand Up @@ -682,7 +687,7 @@ def __init__(
self._wire_map = {}

for node in itertools.chain.from_iterable(self.nodes):
if node.cargs and node.op.name != "measure":
if node.cargs and not isinstance(node.op, Measure):
if cregbundle:
warn(
"Cregbundle set to False since an instruction needs to refer"
Expand Down Expand Up @@ -1073,7 +1078,7 @@ def add_connected_gate(node, gates, layer, current_cons):
current_cons.append((actual_index, gate))

if isinstance(op, Measure):
gate = MeasureFrom()
gate = MeasureFrom(op.basis)
layer.set_qubit(node.qargs[0], gate)
register, _, reg_index = get_bit_reg_index(self._circuit, node.cargs[0])
if self.cregbundle and register is not None:
Expand Down