Skip to content

Commit

Permalink
Add ability to set a classical condition on a list of bits (Qiskit#7653)
Browse files Browse the repository at this point in the history
* Get mpl and utils conditions working

* Almost on text drawer

* Finish text drawer

* Cleanup

* Finish latex and fix tests

* Working on measure with condition in utils

* Lint

* More lint

* Finish 7248 and 7284 plus measure issues

* Add mpl tests

* Fix mpl tests

* Add latex and text tests and bug fixes

* Lint and update image

* Reno and fix image

* Image

* Fix in mpl drawer

* Finish after merge main

* Lint and reno

* Lint again

* Convert mpl and latex to using find_bit

* Fix reverse bits with registerless in 3 drawers, refactor latex to use bundle_bits_dict

* Fix consistent naming for class vars in latex

* Refactor text and latex drawers for bit primacy

* Finish mpl bit condition changes

* Cleanup lint and creg displays

* Lint

* More lint

* Cleanup and docs

* Update register bit index

* Fix idle wires

* Lint

* Incorporate 6018 Tharrma changes

* Allow mpl drawer to display condition on a list of bits

* Add list option to c_if and update circuit drawers

* Add drawer tests

* Remove test_compose dag_circuit test and cleanup

* Lint and release note

* Add get_register function

* Comment fixes and update image refs

* Switch from bit to wire and other cleanup

* Add deprecations for qregs, cregs, global_phase, and calibrations

* Lint

* Add circuit=None warning

* Deprecations for Drawer classes

* Lint

* Update anchors fetch

* Lint

* Fix anchor fetch

* Run black

* Update mpl refs

* Fix instruction test

* Remove reverse_bits from cond_label call

* Lint

* Simplify val_bits calc

* Fix sidetext tests and add Tharrma

Co-authored-by: Tharrmashastha SAPV <[email protected]>

* Move cregbundle checks to utils

* Lint

* Fix up cregbundle check for efficiency

* Lint

* Lint and update sidetext ref image

* testing true/false

* testing: less than 2 ^ number of bits

* black

Co-authored-by: Tharrmashastha SAPV <[email protected]>
Co-authored-by: Luciano Bello <[email protected]>
  • Loading branch information
3 people authored Mar 20, 2022
1 parent 7f8947e commit 592b9ac
Show file tree
Hide file tree
Showing 25 changed files with 340 additions and 239 deletions.
19 changes: 16 additions & 3 deletions qiskit/circuit/instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,17 +391,30 @@ def inverse(self):

def c_if(self, classical, val):
"""Set a classical equality condition on this instruction between the register or cbit
``classical`` and value ``val``.
or list of cbits, ``classical`` and value ``val``.
.. note::
This is a setter method, not an additive one. Calling this multiple times will silently
override any previously set condition; it does not stack.
"""
if not isinstance(classical, (ClassicalRegister, Clbit)):
raise CircuitError("c_if must be used with a classical register or classical bit")
if not isinstance(classical, (ClassicalRegister, Clbit)) and not all(
isinstance(cbit, Clbit) for cbit in classical
):
raise CircuitError(
"c_if must be used with a classical register, a bit or a list of classical bits"
)
if val < 0:
raise CircuitError("condition value should be non-negative")

if (isinstance(classical, ClassicalRegister) and val >= 2**classical.size) or (
isinstance(classical, list) and val >= 2 ** len(classical)
):
raise CircuitError("condition value should be less than 2 ^ number of bits")

if isinstance(classical, Clbit) and int(val) > 1:
raise CircuitError("condition value should be 0/1 or True/False")

if isinstance(classical, Clbit):
# Casting the conditional value as Boolean when
# the classical condition is on a classical bit.
Expand Down
5 changes: 5 additions & 0 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -1156,6 +1156,11 @@ def _resolve_classical_resource(self, specifier):
return self._clbits[specifier]
except IndexError:
raise CircuitError(f"Classical bit index {specifier} is out-of-range.") from None
if isinstance(specifier, list):
for bit in specifier:
if bit not in self._clbit_indices:
raise CircuitError(f"Clbit {specifier} is not present in this circuit.")
return specifier
raise CircuitError(f"Unknown classical resource specifier: '{specifier}'.")

def append(
Expand Down
64 changes: 28 additions & 36 deletions qiskit/dagcircuit/dagcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,15 +410,16 @@ def _check_condition(self, name, condition):
Args:
name (string): used for error reporting
condition (tuple or None): a condition tuple (ClassicalRegister, int) or (Clbit, bool)
condition (tuple or None): a condition tuple (ClassicalRegister, int)
or (Clbit, bool) or (list[Clbit], int)
Raises:
DAGCircuitError: if conditioning on an invalid register
"""
if (
condition is not None
and condition[0] not in self.clbits
and condition[0].name not in self.cregs
and any(clbit not in self.clbits for clbit in condition[0])
):
raise DAGCircuitError("invalid creg in condition for %s" % name)

Expand All @@ -443,7 +444,8 @@ def _bits_in_condition(self, cond):
"""Return a list of bits in the given condition.
Args:
cond (tuple or None): optional condition (ClassicalRegister, int) or (Clbit, bool)
cond (tuple or None): optional condition (ClassicalRegister, int)
or (Clbit, bool) or (list[Clbit], int)
Returns:
list[Clbit]: list of classical bits
Expand All @@ -459,6 +461,9 @@ def _bits_in_condition(self, cond):
elif isinstance(cond[0], Clbit):
# Returns a singleton list of the conditional cbit.
return [cond[0]]
elif all(isinstance(cbit, Clbit) for cbit in cond[0]):
# Returns the list of cbits as is.
return cond[0]
else:
raise CircuitError("Condition must be used with ClassicalRegister or Clbit.")

Expand Down Expand Up @@ -651,7 +656,7 @@ def _map_condition(wire_map, condition, target_cregs):
Args:
wire_map (dict): a map from source wires to destination wires
condition (tuple or None): (ClassicalRegister,int)
condition (tuple or None): (ClassicalRegister,int) or (Clbit,bool) or (list[Clbit],int)
target_cregs (list[ClassicalRegister]): List of all cregs in the
target circuit onto which the condition might possibly be mapped.
Returns:
Expand All @@ -676,41 +681,28 @@ def _map_condition(wire_map, condition, target_cregs):
cond_val = condition[1]
new_cond_val = 0
new_creg = None
bits_in_condcreg = [bit for bit in wire_map if bit in cond_creg]
bits_in_condcreg = []
for bit in cond_creg:
if bit not in wire_map:
raise DAGCircuitError(
"Did not find creg containing mapped clbit in conditional."
)
bits_in_condcreg.append(bit)
for bit in bits_in_condcreg:
if is_reg:
try:
candidate_creg = next(
creg for creg in target_cregs if wire_map[bit] in creg
)
except StopIteration as ex:
raise DAGCircuitError(
"Did not find creg containing mapped clbit in conditional."
) from ex
if new_creg is None:
new_creg = []
new_creg.append(wire_map[bit])
else:
# If cond is on a single Clbit then the candidate_creg is
# the target Clbit to which 'bit' is mapped to.
candidate_creg = wire_map[bit]
if new_creg is None:
new_creg = candidate_creg
elif new_creg != candidate_creg:
# Raise if wire_map maps condition creg on to more than one
# creg in target DAG.
raise DAGCircuitError(
"wire_map maps conditional register onto more than one creg."
)

if not is_reg:
# If the cond is on a single Clbit then the new_cond_val is the
# same as the cond_val since the new_creg is also a single Clbit.
new_cond_val = cond_val
elif 2 ** (cond_creg[:].index(bit)) & cond_val:
# If the conditional values of the Clbit 'bit' is 1 then the new_cond_val
# is updated such that the conditional value of the Clbit to which 'bit'
# is mapped to in new_creg is 1.
new_cond_val += 2 ** (new_creg[:].index(wire_map[bit]))
if new_creg is None:
raise DAGCircuitError("Condition registers not found in wire_map.")
new_creg = wire_map[bit]
new_cond_val = cond_val
for creg in target_cregs:
if set(creg) == set(new_creg):
new_cond_val = 0
for (i, bit) in enumerate(creg):
new_cond_val |= ((cond_val >> new_creg.index(bit)) & 1) << i
new_creg = creg
break
new_condition = (new_creg, new_cond_val)
return new_condition

Expand Down
14 changes: 13 additions & 1 deletion qiskit/dagcircuit/dagnode.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,9 +303,21 @@ def semantic_eq(node1, node2, bit_indices1=None, bit_indices2=None):
if "barrier" == node1.op.name == node2.op.name:
return set(node1_qargs) == set(node2_qargs)

def cond_eq(cond1, cond2):
if cond1 == cond2:
return True
if len(cond1[0]) != len(cond2[0]):
return False
cond_len = len(cond1[0])
cond1_pairs = {(cond1[0][i], (cond1[1] >> i) & 1) for i in range(cond_len)}
cond2_pairs = {(cond2[0][i], (cond2[1] >> i) & 1) for i in range(cond_len)}
if cond1_pairs == cond2_pairs:
return True
return False

if node1_qargs == node2_qargs:
if node1_cargs == node2_cargs:
if node1.op.condition == node2.op.condition:
if cond_eq(node1.op.condition, node2.op.condition):
if node1.op == node2.op:
return True
elif (isinstance(node1, DAGInNode) and isinstance(node2, DAGInNode)) or (
Expand Down
96 changes: 40 additions & 56 deletions qiskit/visualization/latex.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from qiskit.visualization.qcstyle import load_style
from qiskit.circuit.tools.pi_check import pi_check
from .utils import (
check_cregbundle,
get_gate_ctrl_text,
get_param_str,
get_wire_map,
Expand Down Expand Up @@ -181,16 +182,7 @@ def __init__(
self._initial_state = initial_state
self._cregbundle = cregbundle
self._global_phase = circuit.global_phase

# If there is any custom instruction that uses classical bits
# then cregbundle is forced to be False.
for layer in self._nodes:
for node in layer:
if node.op.name not in {"measure"} and node.cargs:
self._cregbundle = False

self._wire_map = get_wire_map(circuit, qubits + clbits, self._cregbundle)
self._img_width = len(self._wire_map)
self._wire_map = {}

self._style, _ = load_style(style)

Expand Down Expand Up @@ -299,6 +291,8 @@ def _get_image_depth(self):
current_max = 0
for node in layer:
op = node.op
if self._cregbundle:
self._cregbundle = check_cregbundle(node, self._circuit)
# useful information for determining wire spacing
boxed_gates = [
"u1",
Expand Down Expand Up @@ -359,6 +353,8 @@ def _get_image_depth(self):
# the wires poking out at the ends is 2 more
sum_column_widths = sum(1 + v / 3 for v in max_column_widths)

self._wire_map = get_wire_map(self._circuit, self._qubits + self._clbits, self._cregbundle)
self._img_width = len(self._wire_map)
max_wire_name = 3
for wire in self._wire_map:
if isinstance(wire, (Qubit, Clbit)):
Expand Down Expand Up @@ -619,56 +615,44 @@ def _add_controls(self, wire_list, ctrlqargs, ctrl_state, col):

def _add_condition(self, op, wire_list, col):
"""Add a condition to the _latex list"""
# cwire - the wire number for the first wire for the condition register
# or if cregbundle, wire number of the condition register itself
# gap - the number of wires from cwire to the bottom gate qubit

label, val_bits = get_condition_label_val(
op.condition, self._circuit, self._cregbundle, self._reverse_bits
cond_label, cond_list = get_condition_label_val(
op.condition, self._circuit, self._cregbundle
)
cond_is_bit = isinstance(op.condition[0], Clbit)
cond_reg = op.condition[0]
if cond_is_bit:
register = get_bit_register(self._circuit, op.condition[0])
if register is not None:
cond_reg = register

if self._cregbundle:
cwire = self._wire_map[cond_reg]
else:
cwire = self._wire_map[op.condition[0] if cond_is_bit else cond_reg[0]]
# make a list of (wire_number, value) pairs from the (bit, value) pairs
cwire_list = []
for bit, val in cond_list:
if self._cregbundle and not isinstance(bit, ClassicalRegister):
register = get_bit_register(self._circuit, bit)
if register is not None:
cwire_list.append((self._wire_map[register], val))
else:
cwire_list.append((self._wire_map[bit], val))
else:
cwire_list.append((self._wire_map[bit], val))

gap = cwire - max(wire_list)
# sort the cwire_list so we can put the value label below
# the bottommost wire
cwire_list = sorted(cwire_list)
prev_wire = max(wire_list)
meas_offset = -0.3 if isinstance(op, Measure) else 0.0

# Print the condition value at the bottom and put bullet on creg line
if cond_is_bit or self._cregbundle:
control = "\\control" if op.condition[1] else "\\controlo"
self._latex[cwire][col] = f"{control}" + " \\cw^(%s){^{\\mathtt{%s}}} \\cwx[-%s]" % (
meas_offset,
label,
str(gap),
)
else:
cond_len = op.condition[0].size - 1
# If reverse, start at highest reg bit and go down to 0
if self._reverse_bits:
cwire -= cond_len
gap -= cond_len
# Iterate through the reg bits down to the lowest one
for i in range(cond_len):
control = "\\control" if val_bits[i] == "1" else "\\controlo"
self._latex[cwire + i][col] = f"{control} \\cw \\cwx[-" + str(gap) + "]"
gap = 1
# Add (hex condition value) below the last cwire
control = "\\control" if val_bits[cond_len] == "1" else "\\controlo"
self._latex[cwire + cond_len][col] = (
f"{control}" + " \\cw^(%s){^{\\mathtt{%s}}} \\cwx[-%s]"
) % (
meas_offset,
label,
str(gap),
)
# go throught the cwire_list from top to bottommost wire less 1
for cwire, val in cwire_list[:-1]:
control = "\\control" if val == "1" else "\\controlo"
gap = cwire - prev_wire
self._latex[cwire][col] = f"{control} \\cw \\cwx[-" + str(gap) + "]"
prev_wire = cwire

# Add (hex condition value) below the last cwire
control = "\\control" if cwire_list[-1][1] == "1" else "\\controlo"
gap = cwire_list[-1][0] - prev_wire
self._latex[cwire_list[-1][0]][col] = (
f"{control}" + " \\cw^(%s){^{\\mathtt{%s}}} \\cwx[-%s]"
) % (
meas_offset,
cond_label,
str(gap),
)

def _truncate_float(self, matchobj, ndigits=4):
"""Truncate long floats."""
Expand Down
Loading

0 comments on commit 592b9ac

Please sign in to comment.