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 measurement #5311

Closed
wants to merge 65 commits into from
Closed
Show file tree
Hide file tree
Changes from 57 commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
d82d83c
add initial attempt at measurements
quantumjim Mar 25, 2020
da54a10
Merge pull request #1 from quantumjim/quantumjim-patch-1
quantumjim Mar 25, 2020
8cb8e7a
make x and y measurements work
quantumjim Mar 25, 2020
a86e538
implement BasisTransformationMeasurement
Cryoris Mar 25, 2020
9bf7a85
Merge pull request #2 from Cryoris/generalized-measurement
quantumjim Mar 25, 2020
3c7ed38
move to PauliMeasure, inherit from Measure
Cryoris Mar 25, 2020
7f86aa1
fix `measure_z` -> `measure`
Cryoris Mar 25, 2020
9cafdfc
wrap z measure as well for safety
Cryoris Mar 26, 2020
7caa860
Merge pull request #3 from Cryoris/generalized-measurement
quantumjim Mar 26, 2020
e3576b3
Merge branch 'master' into master
quantumjim Mar 26, 2020
4ab06b7
Merge branch 'master' into generalized-measurement
Cryoris Mar 28, 2020
1689c29
implement chris' suggestions
Cryoris Mar 28, 2020
80d78aa
remove unused import
Cryoris Mar 28, 2020
6d46013
more efficient pre/post rotation
Cryoris Mar 28, 2020
e2cecac
add comments on post rotation
Cryoris Mar 28, 2020
dcbaecf
add multi-measure capability
Cryoris Mar 29, 2020
7cbc815
Merge branch 'master' into generalized-measurement
Cryoris Mar 29, 2020
c8bb9b8
Merge pull request #4 from Cryoris/generalized-measurement
quantumjim Mar 30, 2020
c1584ce
Merge branch 'master' into master
quantumjim Mar 30, 2020
bfe68a9
Merge branch 'master' into master
quantumjim Apr 3, 2020
61ce727
fix post-rotation
quantumjim Apr 7, 2020
3c9609e
Merge branch 'master' into master
quantumjim Apr 7, 2020
d42ba56
rename PauliMeasure to MeasurePauli
quantumjim Apr 7, 2020
02a38ec
Merge branch 'master' into master
quantumjim Apr 7, 2020
5cde967
Merge branch 'master' into master
quantumjim Apr 10, 2020
712fcc0
Merge branch 'master' of github.com:quantumjim/qiskit-terra into paul…
Cryoris May 16, 2020
7c5ace0
add visualization for single measurement
Cryoris May 16, 2020
2b8afc3
add tests on circuit
Cryoris May 16, 2020
ead9d5a
add MPL drawing
Cryoris May 26, 2020
97aa500
fix deprecated import
Cryoris May 26, 2020
8b34961
fix basis color
Cryoris May 26, 2020
67b42bf
fix broadcast to mpl
Cryoris May 26, 2020
8c4f9c3
fix comments
Cryoris May 26, 2020
7290ca6
Merge branch 'master' into pauli-measure
Cryoris May 26, 2020
294694d
Merge pull request #5 from Cryoris/pauli-measure
quantumjim May 26, 2020
d253930
Merge branch 'master' into master
quantumjim May 26, 2020
5292f48
Merge branch 'master' into master
Cryoris May 27, 2020
3cce379
Merge PR 4024
yaelbh Oct 27, 2020
e31c090
Merge branch 'master' into 'meas_pauli'
yaelbh Nov 17, 2020
4f94f47
Pauli measurement as instruction parameters
yaelbh Nov 18, 2020
899a29e
changed name of pauli measure instruction
yaelbh Nov 19, 2020
be935d4
typo
yaelbh Nov 19, 2020
67046dc
changed again instruction name, from pauli_measure to measure_pauli
yaelbh Nov 19, 2020
0cc5513
take into account measure_pauli when there are conditionals
yaelbh Nov 19, 2020
2848059
do not make controlled gate with pauli_measure
yaelbh Nov 19, 2020
90b4976
a small fix
yaelbh Nov 19, 2020
c856bf9
added measure_pauli to the list of directives in dagdependency
yaelbh Nov 22, 2020
3d582ce
Merge branch 'master' into meas_pauli
yaelbh Nov 22, 2020
1226047
Merge branch 'master' into meas_pauli
yaelbh Dec 1, 2020
2ee6c91
allow mesure_pauli to measure multiple qubits
yaelbh Dec 2, 2020
c299483
pycodestyle
yaelbh Dec 3, 2020
c865d2c
lint
yaelbh Dec 3, 2020
a710a27
bug fix in tests
yaelbh Dec 3, 2020
c339189
more lint
yaelbh Dec 3, 2020
fbb40e4
Merge branch 'master' into meas_pauli
yaelbh Dec 3, 2020
3fe4cb0
Merge branch 'master' into meas_pauli
yaelbh Feb 4, 2021
62b5365
empty commit to rerun CI
yaelbh Feb 7, 2021
e730e11
Merge branch 'master' into meas_pauli
yaelbh Sep 30, 2021
1969ae8
fixing lint disable
yaelbh Sep 30, 2021
f298d0d
lint
yaelbh Oct 3, 2021
35675d6
black
yaelbh Oct 3, 2021
4c02392
fix to a valid color style
yaelbh Oct 3, 2021
e7a2449
lint disable cyclic import
yaelbh Oct 3, 2021
9e75ab4
black
yaelbh Oct 3, 2021
f12f97b
Merge branch 'main' into meas_pauli
1ucian0 Nov 22, 2021
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
3 changes: 2 additions & 1 deletion qiskit/assembler/assemble_circuits.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ def _assemble_circuit(
instruction.memory = clbit_indices
# If the experiment has conditional instructions, assume every
# measurement result may be needed for a conditional gate.
if instruction.name == "measure" and is_conditional_experiment:
if (instruction.name == "measure" or instruction.name == "measure_pauli") \
and is_conditional_experiment:
instruction.register = clbit_indices

# To convert to a qobj-style conditional, insert a bfunc prior
Expand Down
2 changes: 1 addition & 1 deletion qiskit/circuit/add_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ def _gate_to_circuit(operation):
qc = QuantumCircuit(qr, name=operation.name)
if hasattr(operation, 'definition') and operation.definition:
for rule in operation.definition.data:
if rule[0].name in {'id', 'barrier', 'measure', 'snapshot'}:
if rule[0].name in {'id', 'barrier', 'measure', 'measure_pauli', 'snapshot'}:
raise CircuitError('Cannot make controlled gate with {} instruction'.format(
rule[0].name))
qc.append(rule[0], qargs=[qr[bit.index] for bit in rule[1]], cargs=[])
Expand Down
87 changes: 83 additions & 4 deletions qiskit/circuit/measure.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,21 @@
# that they have been altered from the originals.

"""
Quantum measurement in the computational basis.
Quantum measurement
Copy link
Member

@1ucian0 1ucian0 Feb 19, 2022

Choose a reason for hiding this comment

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

I would leave this file as it is and move measure_pauli to its own file.

"""
from qiskit.circuit import QuantumRegister, ClassicalRegister
from qiskit.circuit.instruction import Instruction
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.circuit.exceptions import CircuitError


class Measure(Instruction):
"""Quantum measurement in the computational basis."""
"""Quantum measurement"""

def __init__(self):
# pylint: disable=dangerous-default-value
def __init__(self, name='measure', num_qubits=1, num_clbits=1, params=[]):
"""Create new measurement instruction."""
super().__init__("measure", 1, 1, [])
super().__init__(name, num_qubits, num_clbits, params=params)

def broadcast_arguments(self, qargs, cargs):
qarg = qargs[0]
Expand Down Expand Up @@ -57,3 +59,80 @@ def measure(self, qubit, cbit):


QuantumCircuit.measure = measure


class MeasurePauli(Measure):
Copy link
Member

Choose a reason for hiding this comment

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

See https://github.com/Qiskit/qiskit-terra/pull/5311/files#r810538319

Suggested change
class MeasurePauli(Measure):
class MeasurePauli(Instruction):

Copy link
Contributor

Choose a reason for hiding this comment

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

Strong +1. MeasurePauli should not be a subclass of Measure.

"""Perform a measurement with preceding basis change operations."""

def __init__(self, basis, num_qubits, num_clbits):
"""Create a new basis transformation measurement.

Args:
basis (str): The target measurement basis,
consists of the characters 'X', 'Y', and 'Z'.
num_qubits (integer): number of qubits to measure.
num_clbits (integer): number of classical bits.

Raises:
ValueError: If an unsupported basis is specified,
or in case of mismatch between the number of qubits,
classical bits, and basis length.
"""

params = []
for qubit_basis in basis.upper():
if qubit_basis not in ['X', 'Y', 'Z']:
raise ValueError('Unsupported measurement basis, choose either of X, Y, or Z.')
params.append(qubit_basis)

if num_qubits != num_clbits or num_qubits != len(basis):
raise ValueError('Mismatch between the number of qubits, \
classical bits, and basis length.')

super().__init__('measure_pauli', num_qubits, num_clbits, params)

def broadcast_arguments(self, qargs, cargs):
yield [qarg[0] for qarg in qargs], \
[carg[0] for carg in cargs]

def _define(self):
definition = []
q = QuantumRegister(self.num_qubits, 'q')
c = ClassicalRegister(self.num_clbits, 'c')

# pylint: disable=cyclic-import
from .library import HGate, SGate, SdgGate

for i, qubit_basis in enumerate(self.params):
if qubit_basis == 'X':
pre_rotation = post_rotation = [HGate()]
elif qubit_basis == 'Y':
# since measure and S commute, S and Sdg cancel each other
pre_rotation = [SdgGate(), HGate()]
post_rotation = [HGate(), SGate()]
else: # Z
pre_rotation = post_rotation = []

# switch to the measurement basis
for gate in pre_rotation:
definition += [(gate, [q[i]], [])]

# measure
definition += [(Measure(), [q[i]], [c[i]])]

# apply inverse basis transformation for correct post-measurement state
for gate in post_rotation:
definition += [(gate, [q[i]], [])]

qc = QuantumCircuit(q, c)
qc._data = definition
self.definition = qc


def measure_pauli(self, basis, qubits, clbits):
"""Measure in the the Pauli X, Y, or Z basis."""
return self.append(MeasurePauli(basis, len(qubits), len(clbits)),
qubits, clbits)


QuantumCircuit.measure_pauli = measure_pauli
2 changes: 1 addition & 1 deletion qiskit/dagcircuit/dagdependency.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ def add_op_node(self, operation, qargs, cargs):
qargs (list[Qubit]): list of qubits on which the operation acts
cargs (list[Clbit]): list of classical wires to attach to.
"""
directives = ['measure', 'barrier', 'snapshot']
directives = ['measure', 'measure_pauli', 'barrier', 'snapshot']
if operation.name not in directives:
qindices_list = []
for elem in qargs:
Expand Down
2 changes: 1 addition & 1 deletion qiskit/qobj/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,6 @@ def validate_qobj_against_schema(qobj):
try:
qobj.to_dict(validate=True)
except JsonSchemaException as err:
msg = ("Qobj validation failed. Specifically path: %s failed to fulfil"
msg = ("Qobj validation failed. Specifically path: %s failed to fulfill"
" %s" % (err.path, err.definition)) # pylint: disable=no-member
raise SchemaValidationError(msg)
17 changes: 14 additions & 3 deletions qiskit/visualization/matplotlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ def _line(self, xy0, xy1, lc=None, ls=None, zorder=PORDER_LINE):
color=linecolor, linewidth=self._lwidth2,
linestyle=linestyle, zorder=zorder)

def _measure(self, qxy, cxy, cid, fc=None, ec=None, gt=None, sc=None):
def _measure(self, qxy, cxy, cid, fc=None, ec=None, gt=None, sc=None, basis='z'):
qx, qy = qxy
cx, cy = cxy

Expand All @@ -533,6 +533,13 @@ def _measure(self, qxy, cxy, cid, fc=None, ec=None, gt=None, sc=None):
fontsize=0.8 * self._style['fs'], color=self._style['tc'],
clip_on=True, zorder=PORDER_TEXT)

# measurement basis label
if basis != 'z':
self._ax.text(qx - 0.4 * WID, qy + 0.25 * HIG, basis.upper(),
color=self._style['not_gate_lc'],
clip_on=True, zorder=PORDER_TEXT, fontsize=0.5 * self._style['fs'],
fontweight='bold')

def _conditional(self, xy, istrue=False):
xpos, ypos = xy

Expand Down Expand Up @@ -798,7 +805,7 @@ def _draw_ops(self, verbose=False):
# compute the layer_width for this layer
#
for op in layer:
if op.name in [*_barrier_gates, 'measure']:
if op.name in [*_barrier_gates, 'measure', 'measure_pauli']:
continue

base_name = None if not hasattr(op.op, 'base_gate') else op.op.base_gate.name
Expand Down Expand Up @@ -938,8 +945,12 @@ def _draw_ops(self, verbose=False):
# draw special gates
#
if op.name == 'measure':
basis = 'z'
if op.name == 'meaure_pauli':
basis = op.params

vv = self._creg_dict[c_idxs[0]]['index']
self._measure(q_xy[0], c_xy[0], vv, fc=fc, ec=ec, gt=gt, sc=sc)
self._measure(q_xy[0], c_xy[0], vv, fc=fc, ec=ec, gt=gt, sc=sc, basis=basis)

elif op.name in _barrier_gates:
_barriers = {'coord': [], 'group': []}
Expand Down
20 changes: 15 additions & 5 deletions qiskit/visualization/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,17 @@ class MeasureFrom(BoxOnQuWire):
bot: └╥┘ └╥┘
"""

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

self.top_pad = self.bot_pad = " "
self._mid_padding = '─'
Expand Down Expand Up @@ -991,8 +996,13 @@ def add_connected_gate(instruction, gates, layer, current_cons):
layer._set_multibox(instruction.op.label, qubits=instruction.qargs,
conditional=conditional)

# elif isinstance(instruction.op, MeasureInstruction):
if isinstance(instruction.op, MeasureInstruction):
gate = MeasureFrom()
if instruction.name == 'measure':
gate = MeasureFrom()
else: # measure_pauli
basis = instruction.params.upper()
gate = MeasureFrom(basis)
layer.set_qubit(instruction.qargs[0], gate)
if self.cregbundle:
layer.set_clbit(instruction.cargs[0], MeasureTo(str(instruction.cargs[0].index)))
Expand Down
37 changes: 37 additions & 0 deletions test/python/circuit/test_circuit_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,43 @@ def test_measure_all(self):

self.assertEqual(expected, circuit)

def test_measure_pauli(self):
"""Test measurements in Pauli basis."""
circuit = QuantumCircuit(6, 6)
circuit.measure_pauli('x', [0], [0])
circuit.measure_pauli('y', [1], [1])
circuit.measure_pauli('z', [2], [2])
circuit.measure_pauli('xyz', [3, 4, 5], [3, 4, 5])

expected = QuantumCircuit(6, 6)
# measurement in X basis
expected.h(0)
expected.measure(0, 0)
expected.h(0)

# measurement in Y basis
expected.sdg(1)
expected.h(1)
expected.measure(1, 1)
expected.h(1)
expected.s(1)

# measurement in Z basis
expected.measure(2, 2)

# multiple qubits
expected.h(3)
expected.measure(3, 3)
expected.h(3)
expected.sdg(4)
expected.h(4)
expected.measure(4, 4)
expected.h(4)
expected.s(4)
expected.measure(5, 5)

self.assertEqual(expected, circuit.decompose())

def test_measure_all_copy(self):
"""Test measure_all with inplace=False
"""
Expand Down
41 changes: 36 additions & 5 deletions test/python/compiler/test_assembler.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,25 +257,56 @@ def test_assemble_unroll_parametervector(self):
def test_measure_to_registers_when_conditionals(self):
"""Verify assemble_circuits maps all measure ops on to a register slot
for a circuit containing conditionals."""
qr = QuantumRegister(2)
qr = QuantumRegister(3)
cr1 = ClassicalRegister(2)
cr2 = ClassicalRegister(2)
qc = QuantumCircuit(qr, cr1, cr2)

qc.measure(qr[0], cr1[0]) # Measure not required for a later conditional
qc.measure_pauli('X', [qr[2]], [cr1[0]]) # Measure not required for a later conditional
qc.measure(qr[1], cr2[1]) # Measure required for a later conditional
qc.h(qr[1]).c_if(cr2, 3)

qobj = assemble(qc)

first_measure, second_measure, third_measure = [op for op
in qobj.experiments[0].instructions
if op.name == 'measure'
or op.name == 'measure_pauli']

self.assertTrue(hasattr(first_measure, 'register'))
self.assertEqual(first_measure.register, first_measure.memory)
self.assertTrue(hasattr(second_measure, 'register'))
self.assertEqual(second_measure.register, second_measure.memory)
self.assertTrue(hasattr(third_measure, 'register'))
self.assertEqual(third_measure.register, third_measure.memory)

def test_measure_pauli_to_registers_when_conditionals(self):
"""Verify assemble_circuits maps all measure ops on to a register slot
for a circuit containing conditionals because of measure_pauli."""
qr = QuantumRegister(3)
cr1 = ClassicalRegister(1)
cr2 = ClassicalRegister(2)
qc = QuantumCircuit(qr, cr1, cr2)

qc.measure(qr[0], cr1) # Measure not required for a later conditional
qc.measure(qr[1], cr2[1]) # Measure required for a later conditional
qc.measure_pauli('X', [qr[2]], [cr1[0]]) # Measure not required for a later conditional
qc.measure_pauli('X', [qr[1]], [cr2[1]]) # Measure required for a later conditional
qc.h(qr[1]).c_if(cr2, 3)

qobj = assemble(qc)
validate_qobj_against_schema(qobj)

first_measure, second_measure = [op for op in qobj.experiments[0].instructions
if op.name == 'measure']
first_measure, second_measure, third_measure = [op for op
in qobj.experiments[0].instructions
if op.name == 'measure'
or op.name == 'measure_pauli']

self.assertTrue(hasattr(first_measure, 'register'))
self.assertEqual(first_measure.register, first_measure.memory)
self.assertTrue(hasattr(second_measure, 'register'))
self.assertEqual(second_measure.register, second_measure.memory)
self.assertTrue(hasattr(third_measure, 'register'))
self.assertEqual(third_measure.register, third_measure.memory)

def test_convert_to_bfunc_plus_conditional(self):
"""Verify assemble_circuits converts conditionals from QASM to Qobj."""
Expand Down
32 changes: 27 additions & 5 deletions test/python/visualization/test_circuit_text_drawer.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,33 @@ def test_measure_to_label(self):

def test_measure_from(self):
""" MeasureFrom element. """
element = elements.MeasureFrom()
expected = ["┌─┐",
"┤M├",
"└╥┘"]
self.assertEqualElement(expected, element)
with self.subTest(msg='standard measure'):
element = elements.MeasureFrom()
expected = ["┌─┐",
"┤M├",
"└╥┘"]
self.assertEqualElement(expected, element)

with self.subTest(msg='measure X basis'):
element = elements.MeasureFrom(basis='X')
expected = ["┌───┐",
"┤M_X├",
"└─╥─┘"]
self.assertEqualElement(expected, element)

with self.subTest(msg='measure Y basis'):
element = elements.MeasureFrom(basis='Y')
expected = ["┌───┐",
"┤M_Y├",
"└─╥─┘"]
self.assertEqualElement(expected, element)

with self.subTest(msg='explicit Z basis'):
element = elements.MeasureFrom(basis='Z')
expected = ["┌─┐",
"┤M├",
"└╥┘"]
self.assertEqualElement(expected, element)

def test_text_empty(self):
""" The empty circuit."""
Expand Down