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

Add barrier labels and display in 3 circuit drawers #8440

Merged
merged 19 commits into from
Aug 19, 2022
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
15 changes: 12 additions & 3 deletions qiskit/circuit/barrier.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,18 @@ class Barrier(Instruction):

_directive = True

def __init__(self, num_qubits):
"""Create new barrier instruction."""
super().__init__("barrier", num_qubits, 0, [])
def __init__(self, num_qubits, label=None):
"""Create new barrier instruction.

Args:
num_qubits (int): the number of qubits for the barrier type [Default: 0].
label (str): the barrier label

Raises:
TypeError: if barrier label is invalid.
"""
self._label = label
super().__init__("barrier", num_qubits, 0, [], label=label)

def inverse(self):
"""Special case. Return self."""
Expand Down
8 changes: 6 additions & 2 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -2863,10 +2863,14 @@ def _rebind_definition(
inner.operation.params[idx] = param.bind({parameter: value})
self._rebind_definition(inner.operation, parameter, value)

def barrier(self, *qargs: QubitSpecifier) -> InstructionSet:
def barrier(self, *qargs: QubitSpecifier, label=None) -> InstructionSet:
"""Apply :class:`~qiskit.circuit.Barrier`. If qargs is empty, applies to all qubits in the
circuit.

Args:
qargs (QubitSpecifier): Specification for one or more qubit arguments.
label (str): The string label of the barrier.

Returns:
qiskit.circuit.InstructionSet: handle to the added instructions.
"""
Expand All @@ -2889,7 +2893,7 @@ def barrier(self, *qargs: QubitSpecifier) -> InstructionSet:
else:
qubits.append(qarg)

return self.append(Barrier(len(qubits)), qubits, [])
return self.append(Barrier(len(qubits), label=label), qubits, [])

def delay(
self,
Expand Down
20 changes: 0 additions & 20 deletions qiskit/extensions/simulator/snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,26 +60,6 @@ def snapshot_type(self):
"""Return snapshot type"""
return self._snapshot_type

@property
def label(self):
"""Return snapshot label"""
return self._label

@label.setter
def label(self, name):
"""Set snapshot label to name

Args:
name (str or None): label to assign unitary

Raises:
TypeError: name is not string or None.
"""
if isinstance(name, str):
self._label = name
else:
raise TypeError("label expects a string")

def c_if(self, classical, val):
raise QiskitError("Snapshots are simulator directives and cannot be conditional.")

Expand Down
5 changes: 4 additions & 1 deletion qiskit/visualization/latex.py
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,10 @@ def _build_barrier(self, node, col):
first = last = index
pos = self._wire_map[self._qubits[first]]
self._latex[pos][col - 1] += " \\barrier[0em]{" + str(last - first) + "}"
self._latex[pos][col] = "\\qw"
if node.op.label is not None:
pos = indexes[0]
label = node.op.label.replace(" ", "\\,")
self._latex[pos][col] = "\\cds{0}{^{\\mathrm{%s}}}" % label
Comment on lines +606 to +609
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps it would make more sense to wrap the label in \text instead of \mathrm - the latter still uses math-mode spacing/kerning rules (hence the \,), whereas \text swaps back to the full text font. It depends on amsmath being available (I think), but that's part of the de facto LaTeX standard library. Users would be able to have parts of the label rendered in math-mode by including the $ ... $ manually.

That's just on the basis that I would expect "label" to be a text field, not an equation one. If we feel like it's more of an "equation" field, then the current math-mode way makes sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

\text doesn't work on my TeX Live version of LaTex. I don't think we'd want to do another usepackage for amsmath just for this, so I'd suggest we leave the \mathrm as is.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, that's fair enough. I'm surprised that nothing is already loading amsmath, since it's about as common as import numpy in mathematical Python code, but fair enough. If it's not already loaded, yeah, let's not add the extra dependency.


def _add_controls(self, wire_list, ctrlqargs, ctrl_state, col):
"""Add one or more controls to a gate"""
Expand Down
30 changes: 26 additions & 4 deletions qiskit/visualization/matplotlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,9 @@ def _get_layer_widths(self):
self._data[node] = {}
self._data[node]["width"] = WID
num_ctrl_qubits = 0 if not hasattr(op, "num_ctrl_qubits") else op.num_ctrl_qubits
if getattr(op, "_directive", False) or isinstance(op, Measure):
if (
getattr(op, "_directive", False) and (not op.label or not self._plot_barriers)
) or isinstance(op, Measure):
enavarro51 marked this conversation as resolved.
Show resolved Hide resolved
self._data[node]["raw_gate_text"] = op.name
continue

Expand Down Expand Up @@ -1013,11 +1015,16 @@ def _measure(self, node):

def _barrier(self, node):
"""Draw a barrier"""
for xy in self._data[node]["q_xy"]:
for i, xy in enumerate(self._data[node]["q_xy"]):
xpos, ypos = xy
# For the topmost barrier, reduce the rectangle if there's a label to allow for the text.
if i == 0 and node.op.label is not None:
ypos_adj = -0.35
else:
ypos_adj = 0.0
self._ax.plot(
[xpos, xpos],
[ypos + 0.5, ypos - 0.5],
[ypos + 0.5 + ypos_adj, ypos - 0.5],
linewidth=self._lwidth1,
linestyle="dashed",
color=self._style["lc"],
Expand All @@ -1026,7 +1033,7 @@ def _barrier(self, node):
box = self._patches_mod.Rectangle(
xy=(xpos - (0.3 * WID), ypos - 0.5),
width=0.6 * WID,
height=1,
height=1.0 + ypos_adj,
fc=self._style["bc"],
ec=None,
alpha=0.6,
Expand All @@ -1035,6 +1042,21 @@ def _barrier(self, node):
)
self._ax.add_patch(box)

# display the barrier label at the top if there is one
if i == 0 and node.op.label is not None:
dir_ypos = ypos + 0.65 * HIG
self._ax.text(
xpos,
dir_ypos,
node.op.label,
ha="center",
va="top",
fontsize=self._fs,
color=self._data[node]["tc"],
clip_on=True,
zorder=PORDER_TEXT,
)

def _gate(self, node, xy=None):
"""Draw a 1-qubit gate"""
if xy is None:
Expand Down
11 changes: 6 additions & 5 deletions qiskit/visualization/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,18 +376,18 @@ def __init__(self, label=""):


class Barrier(DirectOnQuWire):
"""Draws a barrier.
"""Draws a barrier with a label at the top if there is one.

::

top: ░
top: ░ label
mid: ─░─ ───░───
bot: ░ ░
"""

def __init__(self, label=""):
super().__init__("░")
self.top_connect = "░"
self.top_connect = label if label else "░"
self.bot_connect = "░"
self.top_connector = {}
self.bot_connector = {}
Expand Down Expand Up @@ -1089,9 +1089,10 @@ def add_connected_gate(node, gates, layer, current_cons):
if not self.plotbarriers:
return layer, current_cons, current_cons_cond, connection_label

for qubit in node.qargs:
for i, qubit in enumerate(node.qargs):
if qubit in self.qubits:
layer.set_qubit(qubit, Barrier())
label = op.label if i == 0 else ""
layer.set_qubit(qubit, Barrier(label))

elif isinstance(op, SwapGate):
# swap
Expand Down
18 changes: 18 additions & 0 deletions releasenotes/notes/add-barrier-label-8e677979cb37461e.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
features:
- |
Added a ``label`` parameter to the :class:`.Barrier` which now allows
a user to enter a label for the ``barrier`` directive and the label
will be printed at the top of the ``barrier`` in the `mpl`, `latex`,
and `text` circuit drawers. Printing of existing ``snapshot`` labels
to the 3 circuit drawers was also added.

.. code-block:: python

from qiskit import QuantumCircuit

circuit = QuantumCircuit(2)
circuit.h(0)
circuit.h(1)
circuit.barrier(label="After H")
circuit.draw('mpl')
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions test/ipynb/mpl/circuit/test_circuit_matplotlib_drawer.py
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,17 @@ def test_wire_order(self):
filename="wire_order.png",
)

def test_barrier_label(self):
"""Test the barrier label"""
circuit = QuantumCircuit(2)
circuit.x(0)
circuit.y(1)
circuit.barrier()
circuit.y(0)
circuit.x(1)
circuit.barrier(label="End Y/X")
self.circuit_drawer(circuit, filename="barrier_label.png")


if __name__ == "__main__":
unittest.main(verbosity=1)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
\documentclass[border=2px]{standalone}

\usepackage[braket, qm]{qcircuit}
\usepackage{graphicx}

\begin{document}
\scalebox{1.0}{
\Qcircuit @C=1.0em @R=0.2em @!R { \\
\nghost{{q}_{0} : } & \lstick{{q}_{0} : } & \gate{\mathrm{X}} \barrier[0em]{1} & \qw & \gate{\mathrm{Y}} \barrier[0em]{1} & \cds{0}{^{\mathrm{End\,Y/X}}} & \qw & \qw\\
\nghost{{q}_{1} : } & \lstick{{q}_{1} : } & \gate{\mathrm{Y}} & \qw & \gate{\mathrm{X}} & \qw & \qw & \qw\\
\\ }}
\end{document}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
\begin{document}
\scalebox{1.0}{
\Qcircuit @C=1.0em @R=0.2em @!R { \\
\nghost{{q}_{0} : } & \lstick{{q}_{0} : } & \gate{\mathrm{H}} \barrier[0em]{1} & \qw & \qw \barrier[0em]{1} & \qw & \qw & \qw\\
\nghost{{q}_{0} : } & \lstick{{q}_{0} : } & \gate{\mathrm{H}} \barrier[0em]{1} & \qw & \qw \barrier[0em]{1} & \cds{0}{^{\mathrm{sn\,1}}} & \qw & \qw\\
\nghost{{q}_{1} : } & \lstick{{q}_{1} : } & \qw & \qw & \gate{\mathrm{H}} & \qw & \qw & \qw\\
\nghost{\mathrm{{c} : }} & \lstick{\mathrm{{c} : }} & \lstick{/_{_{2}}} \cw & \cw & \cw & \cw & \cw & \cw\\
\\ }}
Expand Down
18 changes: 17 additions & 1 deletion test/python/visualization/test_circuit_latex.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ def test_plot_barriers(self):
# this import appears to be unused, but is actually needed to get snapshot instruction
import qiskit.extensions.simulator # pylint: disable=unused-import

circuit.snapshot("1")
circuit.snapshot("sn 1")

# check the barriers plot properly when plot_barriers= True
circuit_drawer(circuit, filename=filename1, output="latex_source", plot_barriers=True)
Expand All @@ -265,6 +265,22 @@ def test_no_barriers_false(self):

self.assertEqualToReference(filename)

def test_barrier_label(self):
"""Test the barrier label"""
filename = self._get_resource_path("test_latex_barrier_label.tex")
qr = QuantumRegister(2, "q")
circuit = QuantumCircuit(qr)
circuit.x(0)
circuit.y(1)
circuit.barrier()
circuit.y(0)
circuit.x(1)
circuit.barrier(label="End Y/X")

circuit_drawer(circuit, filename=filename, output="latex_source")

self.assertEqualToReference(filename)

def test_big_gates(self):
"""Test large gates with params"""
filename = self._get_resource_path("test_latex_big_gates.tex")
Expand Down
22 changes: 22 additions & 0 deletions test/python/visualization/test_circuit_text_drawer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1052,6 +1052,28 @@ def test_text_justify_right_barrier(self):
circuit.h(qr1[1])
self.assertEqual(str(_text_circuit_drawer(circuit, justify="right")), expected)

def test_text_barrier_label(self):
"""Show barrier label"""
expected = "\n".join(
[
" ┌───┐ ░ ┌───┐ End Y/X ",
"q_0: |0>┤ X ├─░─┤ Y ├────░────",
" ├───┤ ░ ├───┤ ░ ",
"q_1: |0>┤ Y ├─░─┤ X ├────░────",
" └───┘ ░ └───┘ ░ ",
]
)

qr = QuantumRegister(2, "q")
circuit = QuantumCircuit(qr)
circuit.x(0)
circuit.y(1)
circuit.barrier()
circuit.y(0)
circuit.x(1)
circuit.barrier(label="End Y/X")
self.assertEqual(str(_text_circuit_drawer(circuit)), expected)

def test_text_overlap_cx(self):
"""Overlapping CX gates are drawn not overlapping"""
expected = "\n".join(
Expand Down