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

Pauli X, Y, Z measurement instructions #9269

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
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
70 changes: 70 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
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
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,70 @@ 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


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
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
27 changes: 23 additions & 4 deletions qiskit/visualization/circuit/latex.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ def __init__(
# If there is any custom instruction that uses classical bits
# then cregbundle is forced to be False.
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 @@ -300,7 +300,7 @@ def _get_image_depth(self):
self._nodes
and self._nodes[0]
and (
self._nodes[0][0].op.name == "measure"
isinstance(self._nodes[0][0].op, Measure)
or getattr(self._nodes[0][0].op, "condition", None)
)
):
Expand Down Expand Up @@ -427,7 +427,7 @@ def _build_latex_array(self):
self._nodes
and self._nodes[0]
and (
self._nodes[0][0].op.name == "measure"
isinstance(self._nodes[0][0].op, Measure)
or getattr(self._nodes[0][0].op, "condition", None)
)
):
Expand Down Expand Up @@ -579,7 +579,26 @@ def _build_symmetric_gate(self, op, gate_text, wire_list, col):
def _build_measure(self, node, col):
"""Build a meter and the lines to the creg"""
wire1 = self._wire_map[node.qargs[0]]
self._latex[wire1][col] = "\\meter"
if node.op.basis:
# Modification of `\meter` in https://www.ctan.org/tex-archive/graphics/qcircuit
# pylint: disable=invalid-name
PAULI_METER = """
*=<1.8em,1.4em>
{
\\xy ="j",
"j"-<.778em,.322em>;{"j"+<.778em,-.322em> \\ellipse ur,_{}},
"j"-<0em,.4em>;p+<.5em,.9em> **\\dir{-},
"j"+<2.2em,2.2em>*{},"j"-<2.2em,2.2em>*{},
<-.5em,.4em> *\\txt{\\tiny{%s}}
\\endxy
}
\\POS ="i",
"i"+UR;"i"+UL **\\dir{-};"i"+DL **\\dir{-};"i"+DR **\\dir{-};"i"+UR **\\dir{-},
Copy link
Member

Choose a reason for hiding this comment

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

(On my phone so I can't select properly.)

This looks like qcircuit code. That's GPL licensed, so we legally cannot include it, or code derived from it, in Qiskit. We need to be really careful when copying code blocks like this - attribution is not generally sufficient. It's legally tricky for us now even if you try and write your own implementation, because it's hard to be certain that it wouldn't be a derived work.

Perhaps we can use their \measure macro somehow?

Copy link
Member Author

@pedrorrivero pedrorrivero Dec 11, 2022

Choose a reason for hiding this comment

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

Ah! Good catch, I have definitely derived it from their code.

I did try to use their macro but it proved to be very challenging (it builds an image and you lose the reference points after wrapping it up) so I dropped down to base xypic code and recreated part of what they where doing adding the extra pieces. I have reached out to them to see if I can contribute this to their package.

Not sure how to address this otherwise. I'll think about it, thanks for bringing this up.

Copy link
Member Author

Choose a reason for hiding this comment

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

I opened a PR to contribute this piece of code to qcircuit

"i" \\qw
"""
self._latex[wire1][col] = PAULI_METER % node.op.basis.upper()
else:
self._latex[wire1][col] = "\\meter"

idx_str = ""
cond_offset = 1.5 if getattr(node.op, "condition", None) else 0.0
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
6 changes: 6 additions & 0 deletions qiskit/visualization/circuit/qcstyle.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ class DefaultStyle:
'reset': ('#000000', '#FFFFFF'),
'target': ('#FFFFFF', '#FFFFFF'),
'measure': ('#000000', '#FFFFFF'),
'measure_x': ('#000000', '#FFFFFF'),
'measure_y': ('#000000', '#FFFFFF'),
'measure_z': ('#000000', '#FFFFFF'),
}

"""
Expand Down Expand Up @@ -261,6 +264,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/bw.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"
]
}
}
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"
]
}
}
12 changes: 12 additions & 0 deletions qiskit/visualization/circuit/styles/iqx-dark.json
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,18 @@
"measure": [
"#8D8D8D",
"#000000"
],
"measure_x": [
"#8D8D8D",
"#000000"
],
"measure_y": [
"#8D8D8D",
"#000000"
],
"measure_z": [
"#8D8D8D",
"#000000"
]
}
}
Loading