Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into rusty-sabre
Browse files Browse the repository at this point in the history
  • Loading branch information
mtreinish committed Aug 19, 2022
2 parents 08f6a47 + 93e2fc5 commit 935452a
Show file tree
Hide file tree
Showing 19 changed files with 308 additions and 46 deletions.
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
41 changes: 33 additions & 8 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -835,7 +835,8 @@ def compose(
this can be anything that :obj:`.append` will accept.
qubits (list[Qubit|int]): qubits of self to compose onto.
clbits (list[Clbit|int]): clbits of self to compose onto.
front (bool): If True, front composition will be performed (not implemented yet).
front (bool): If True, front composition will be performed. This is not possible within
control-flow builder context managers.
inplace (bool): If True, modify the object. Otherwise return composed circuit.
wrap (bool): If True, wraps the other circuit into a gate (or instruction, depending on
whether it contains only unitary instructions) before composing it onto self.
Expand All @@ -844,12 +845,18 @@ def compose(
QuantumCircuit: the composed circuit (returns None if inplace==True).
Raises:
CircuitError: if composing on the front.
QiskitError: if ``other`` is wider or there are duplicate edge mappings.
CircuitError: if no correct wire mapping can be made between the two circuits, such as
if ``other`` is wider than ``self``.
CircuitError: if trying to emit a new circuit while ``self`` has a partially built
control-flow context active, such as the context-manager forms of :meth:`if_test`,
:meth:`for_loop` and :meth:`while_loop`.
CircuitError: if trying to compose to the front of a circuit when a control-flow builder
block is active; there is no clear meaning to this action.
Examples::
Examples:
.. code-block:: python
lhs.compose(rhs, qubits=[3, 2], inplace=True)
>>> lhs.compose(rhs, qubits=[3, 2], inplace=True)
.. parsed-literal::
Expand All @@ -869,6 +876,19 @@ def compose(
lcr_1: 0 ═══════════ lcr_1: 0 ═══════════════════════
"""
if inplace and front and self._control_flow_scopes:
# If we're composing onto ourselves while in a stateful control-flow builder context,
# there's no clear meaning to composition to the "front" of the circuit.
raise CircuitError(
"Cannot compose to the front of a circuit while a control-flow context is active."
)
if not inplace and self._control_flow_scopes:
# If we're inside a stateful control-flow builder scope, even if we successfully cloned
# the partial builder scope (not simple), the scope wouldn't be controlled by an active
# `with` statement, so the output circuit would be permanently broken.
raise CircuitError(
"Cannot emit a new composed circuit while a control-flow context is active."
)

if inplace:
dest = self
Expand Down Expand Up @@ -962,8 +982,9 @@ def compose(
mapped_instrs += dest.data
dest.data.clear()
dest._parameter_table.clear()
append = dest._control_flow_scopes[-1].append if dest._control_flow_scopes else dest._append
for instr in mapped_instrs:
dest._append(instr)
append(instr)

for gate, cals in other.calibrations.items():
dest._calibrations[gate].update(cals)
Expand Down Expand Up @@ -2842,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 @@ -2868,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
16 changes: 13 additions & 3 deletions qiskit/qpy/binary_io/circuits.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,9 @@ def _parse_custom_operation(custom_operations, gate_name, params, version, vecto
base_gate = _read_instruction(
base_gate_obj, None, registers, custom_operations, version, vectors
)
if ctrl_state < 2**num_ctrl_qubits - 1:
# If open controls, we need to discard the control suffix when setting the name.
gate_name = gate_name.rsplit("_", 1)[0]
inst_obj = ControlledGate(
gate_name,
num_qubits,
Expand Down Expand Up @@ -623,14 +626,21 @@ def _write_custom_operation(file_obj, name, operation, custom_operations):
has_definition = True
data = common.data_to_binary(operation, _write_pauli_evolution_gate)
size = len(data)
elif operation.definition is not None:
elif type_key == type_keys.CircuitInstruction.CONTROLLED_GATE:
# For ControlledGate, we have to access and store the private `_definition` rather than the
# public one, because the public one is mutated to include additional logic if the control
# state is open, and the definition setter (during a subsequent read) uses the "fully
# excited" control definition only.
has_definition = True
data = common.data_to_binary(operation.definition, write_circuit)
data = common.data_to_binary(operation._definition, write_circuit)
size = len(data)
if type_key == type_keys.CircuitInstruction.CONTROLLED_GATE:
num_ctrl_qubits = operation.num_ctrl_qubits
ctrl_state = operation.ctrl_state
base_gate = operation.base_gate
elif operation.definition is not None:
has_definition = True
data = common.data_to_binary(operation.definition, write_circuit)
size = len(data)
if base_gate is None:
base_gate_raw = b""
else:
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

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):
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')
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
fixes:
- |
:meth:`.QuantumCircuit.compose` will now function correctly when used with
the ``inplace=True`` argument within control-flow builder contexts.
Previously the instructions would be added outside the control-flow scope.
Fixed `#8433 <https://github.com/Qiskit/qiskit-terra/issues/8433>`__.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
fixes:
- |
Fixed QPY serialisation and deserialisation of :class:`.ControlledGate`
with open controls (*i.e.* those whose ``ctrl_state`` is not all ones).
Fixed `#8549 <https://github.com/Qiskit/qiskit-terra/issues/8549>`__.
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)
11 changes: 11 additions & 0 deletions test/python/circuit/test_circuit_load_from_qpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -998,6 +998,17 @@ def test_controlled_gate(self):
new_circuit = load(qpy_file)[0]
self.assertEqual(qc, new_circuit)

def test_controlled_gate_open_controls(self):
"""Test a controlled gate with open controls round-trips exactly."""
qc = QuantumCircuit(3)
controlled_gate = DCXGate().control(1, ctrl_state=0)
qc.append(controlled_gate, [0, 1, 2])
qpy_file = io.BytesIO()
dump(qc, qpy_file)
qpy_file.seek(0)
new_circuit = load(qpy_file)[0]
self.assertEqual(qc, new_circuit)

def test_nested_controlled_gate(self):
"""Test a custom nested controlled gate."""
custom_gate = Gate("black_box", 1, [])
Expand Down
Loading

0 comments on commit 935452a

Please sign in to comment.