diff --git a/qiskit/circuit/__init__.py b/qiskit/circuit/__init__.py index dccdcf5b4572..ae298292f218 100644 --- a/qiskit/circuit/__init__.py +++ b/qiskit/circuit/__init__.py @@ -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 diff --git a/qiskit/circuit/library/__init__.py b/qiskit/circuit/library/__init__.py index bffe75968e1e..e8f38e80303d 100644 --- a/qiskit/circuit/library/__init__.py +++ b/qiskit/circuit/library/__init__.py @@ -136,6 +136,9 @@ :toctree: ../stubs/ Measure + MeasureX + MeasureY + MeasureZ Reset Generalized Gates @@ -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 diff --git a/qiskit/circuit/library/standard_gates/__init__.py b/qiskit/circuit/library/standard_gates/__init__.py index 3dca1abb6201..6a9df2df8a59 100644 --- a/qiskit/circuit/library/standard_gates/__init__.py +++ b/qiskit/circuit/library/standard_gates/__init__.py @@ -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 @@ -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 diff --git a/qiskit/circuit/measure.py b/qiskit/circuit/measure.py index 8da0db586c0d..88f08b8606e7 100644 --- a/qiskit/circuit/measure.py +++ b/qiskit/circuit/measure.py @@ -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): """Quantum measurement in the computational basis.""" + basis: str | None = None + def __init__(self): """Create new measurement instruction.""" super().__init__("measure", 1, 1, []) @@ -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. diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 4348cc8ae2b9..e5f75c12c874 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -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: @@ -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. diff --git a/qiskit/dagcircuit/dagdependency.py b/qiskit/dagcircuit/dagdependency.py index 6a59025d3a84..f965c8598400 100644 --- a/qiskit/dagcircuit/dagdependency.py +++ b/qiskit/dagcircuit/dagdependency.py @@ -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: @@ -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 + ): qindices_list = [] for elem in qargs: qindices_list.append(self.qubits.index(elem)) diff --git a/qiskit/transpiler/passes/utils/remove_final_measurements.py b/qiskit/transpiler/passes/utils/remove_final_measurements.py index 8c907b52654f..97260534be58 100644 --- a/qiskit/transpiler/passes/utils/remove_final_measurements.py +++ b/qiskit/transpiler/passes/utils/remove_final_measurements.py @@ -12,6 +12,8 @@ """Remove final measurements and barriers at the end of a circuit.""" +from qiskit.circuit.measure import Measure +from qiskit.circuit.barrier import Barrier from qiskit.transpiler.basepasses import TransformationPass from qiskit.dagcircuit import DAGOpNode @@ -33,7 +35,7 @@ class RemoveFinalMeasurements(TransformationPass): """ def _calc_final_ops(self, dag): - final_op_types = {"measure", "barrier"} + final_op_types = (Measure, Barrier) final_ops = [] to_visit = list(next(dag.predecessors(dag.output_map[qubit])) for qubit in dag.qubits) @@ -43,7 +45,7 @@ def _calc_final_ops(self, dag): node = to_visit.pop() if not isinstance(node, DAGOpNode): continue - if node.op.name == "barrier": + if isinstance(node.op, Barrier): # Barrier is final if all children are final, so we track # how many times we still need to encounter each barrier # via a child node. @@ -56,7 +58,7 @@ def _calc_final_ops(self, dag): # Record the encounter, and bail! barrier_encounters_remaining[node] -= 1 continue - if node.name in final_op_types: + if isinstance(node.op, final_op_types): # Current node is either a measure, or a barrier with all final op children. final_ops.append(node) to_visit.extend(dag.quantum_predecessors(node)) diff --git a/qiskit/visualization/circuit/latex.py b/qiskit/visualization/circuit/latex.py index 4f97ea34fc44..ca1e8b058d34 100644 --- a/qiskit/visualization/circuit/latex.py +++ b/qiskit/visualization/circuit/latex.py @@ -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" @@ -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) ) ): @@ -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) ) ): @@ -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{-}, + "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 diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index 155afff43776..6a5ee71d7592 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -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" @@ -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], diff --git a/qiskit/visualization/circuit/qcstyle.py b/qiskit/visualization/circuit/qcstyle.py index 712e0dca9ed7..80bf242c5953 100644 --- a/qiskit/visualization/circuit/qcstyle.py +++ b/qiskit/visualization/circuit/qcstyle.py @@ -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'), } """ @@ -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"]), }, } diff --git a/qiskit/visualization/circuit/styles/bw.json b/qiskit/visualization/circuit/styles/bw.json index 9eac22617dba..3c07f380727d 100644 --- a/qiskit/visualization/circuit/styles/bw.json +++ b/qiskit/visualization/circuit/styles/bw.json @@ -197,6 +197,18 @@ "measure": [ "#000000", "#FFFFFF" + ], + "measure_x": [ + "#000000", + "#FFFFFF" + ], + "measure_y": [ + "#000000", + "#FFFFFF" + ], + "measure_z": [ + "#000000", + "#FFFFFF" ] } } \ No newline at end of file diff --git a/qiskit/visualization/circuit/styles/default.json b/qiskit/visualization/circuit/styles/default.json index 6d4946b3de65..c85142ae1217 100644 --- a/qiskit/visualization/circuit/styles/default.json +++ b/qiskit/visualization/circuit/styles/default.json @@ -197,6 +197,18 @@ "measure": [ "#000000", "#FFFFFF" + ], + "measure_x": [ + "#000000", + "#FFFFFF" + ], + "measure_y": [ + "#000000", + "#FFFFFF" + ], + "measure_z": [ + "#000000", + "#FFFFFF" ] } } \ No newline at end of file diff --git a/qiskit/visualization/circuit/styles/iqx-dark.json b/qiskit/visualization/circuit/styles/iqx-dark.json index b7bd2ab15d13..a98ee0275610 100644 --- a/qiskit/visualization/circuit/styles/iqx-dark.json +++ b/qiskit/visualization/circuit/styles/iqx-dark.json @@ -197,6 +197,18 @@ "measure": [ "#8D8D8D", "#000000" + ], + "measure_x": [ + "#8D8D8D", + "#000000" + ], + "measure_y": [ + "#8D8D8D", + "#000000" + ], + "measure_z": [ + "#8D8D8D", + "#000000" ] } } \ No newline at end of file diff --git a/qiskit/visualization/circuit/styles/iqx.json b/qiskit/visualization/circuit/styles/iqx.json index 37c7b1abc019..e22de98f4258 100644 --- a/qiskit/visualization/circuit/styles/iqx.json +++ b/qiskit/visualization/circuit/styles/iqx.json @@ -197,6 +197,18 @@ "measure": [ "#A8A8A8", "#000000" + ], + "measure_x": [ + "#A8A8A8", + "#000000" + ], + "measure_y": [ + "#A8A8A8", + "#000000" + ], + "measure_z": [ + "#A8A8A8", + "#000000" ] } } \ No newline at end of file diff --git a/qiskit/visualization/circuit/styles/textbook.json b/qiskit/visualization/circuit/styles/textbook.json index 115cf54e80aa..76bb7046448e 100644 --- a/qiskit/visualization/circuit/styles/textbook.json +++ b/qiskit/visualization/circuit/styles/textbook.json @@ -197,6 +197,18 @@ "measure": [ "#121619", "#FFFFFF" + ], + "measure_x": [ + "#121619", + "#FFFFFF" + ], + "measure_y": [ + "#121619", + "#FFFFFF" + ], + "measure_z": [ + "#121619", + "#FFFFFF" ] } } diff --git a/qiskit/visualization/circuit/text.py b/qiskit/visualization/circuit/text.py index ee927e51a5da..f56fcbb6062c 100644 --- a/qiskit/visualization/circuit/text.py +++ b/qiskit/visualization/circuit/text.py @@ -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 = "─" @@ -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" @@ -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: