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

Auxiliary qubit tracking in HighLevelSynthesis #12911

Merged
merged 7 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
fix round 1 of review comments
  • Loading branch information
Cryoris committed Aug 7, 2024
commit c87cf0b530847e25e3fd296dbd3ce4a4dd7c32a3
1 change: 0 additions & 1 deletion qiskit/synthesis/clifford/clifford_decompose_bm.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
from qiskit._accelerate.synthesis.clifford import (
synth_clifford_bm as synth_clifford_bm_inner,
)
from qiskit.transpiler.passes import Decompose


def synth_clifford_bm(clifford: Clifford) -> QuantumCircuit:
Expand Down
27 changes: 5 additions & 22 deletions qiskit/transpiler/passes/synthesis/high_level_synthesis.py
Cryoris marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -483,10 +483,7 @@ def _run(self, dag: DAGCircuit, tracker: QubitTracker) -> DAGCircuit:
# next check control flow
elif node.is_control_flow():
node.op = control_flow.map_blocks(
partial(self._run, tracker=tracker.copy()),
node.op,
# self.run,
# node.op,
partial(self._run, tracker=tracker.copy()), node.op
)

# now we are free to synthesize
Expand Down Expand Up @@ -541,11 +538,8 @@ def _run(self, dag: DAGCircuit, tracker: QubitTracker) -> DAGCircuit:
sub_node.op, tuple(qubit_map[qarg] for qarg in sub_node.qargs)
)
out.global_phase += op.global_phase
# handle different types of ops
# out.compose(op, qargs, inplace=True)
else:
raise RuntimeError(f"Unexpected synthesized type: {type(op)}")
# out.apply_operation_back(op, qubits, cargs=())
else:
out.apply_operation_back(node.op, node.qargs, node.cargs, check=False)

Expand All @@ -567,8 +561,8 @@ def _synthesize_operation(
# until no further change occurred. If there was no change, we just return ``None``.
synthesized = None

# try synthesizing via AnnotatedOperation
# if isinstance(operation, AnnotatedOperation):
# Try synthesizing via AnnotatedOperation. This is faster than an isinstance check
# but a bit less safe since someone could create operations with a ``modifiers`` attribute.
if len(modifiers := getattr(operation, "modifiers", [])) > 0:
# The base operation must be synthesized without using potential control qubits
# used in the modifiers.
Expand All @@ -577,10 +571,7 @@ def _synthesize_operation(
)
baseop_qubits = qubits[num_ctrl:] # reminder: control qubits are the first ones
baseop_tracker = tracker.copy()
# dict(zip(baseop_qubits, range(operation.base_op.num_qubits)))
# )
baseop_tracker.drop(qubits[:num_ctrl]) # no access to control qubits
# baseop_tracker.used(qubits[:num_ctrl]) # no access to control qubits

# get qubits of base operation
synthesized_base_op, _ = self._synthesize_operation(
Expand All @@ -595,7 +586,7 @@ def _synthesize_operation(

# synthesize via HLS
elif len(hls_methods := self._methods_to_try(operation.name)) > 0:
# TODO just pass qubits if use_qubit_indices is removed
# TODO once ``use_qubit_indices`` is removed from the initializer, just pass ``qubits``
indices = qubits if self._use_qubit_indices else None
synthesized = self._synthesize_op_using_plugins(
hls_methods,
Expand Down Expand Up @@ -623,17 +614,14 @@ def _synthesize_operation(
synthesized = re_synthesized
used_qubits = qubits

# if synthesized.num_qubits != len(qubits):
# raise RuntimeError("currently cannot annotate HLS stuff")

elif isinstance(synthesized, QuantumCircuit):
aux_qubits = tracker.borrow(synthesized.num_qubits - len(qubits), qubits)
used_qubits = qubits + tuple(aux_qubits)
as_dag = circuit_to_dag(synthesized, copy_operations=False)

# map used qubits to subcircuit
new_qubits = [as_dag.find_bit(q).index for q in as_dag.qubits]
qubit_map = dict(zip(used_qubits, new_qubits))
# qubit_map = {}

synthesized = self._run(as_dag, tracker.copy(qubit_map))
if synthesized.num_qubits() != len(used_qubits):
Expand Down Expand Up @@ -768,11 +756,6 @@ def _synthesize_op_using_plugins(
best_decomposition = decomposition
best_score = current_score

# if best_decomposition is None:
# raise TranspilerError(
# f"HLS failed to synthesize {op.name} given the provided methods {hls_methods}."
# )

return best_decomposition

def _apply_annotations(
Expand Down
54 changes: 25 additions & 29 deletions qiskit/transpiler/passes/synthesis/qubit_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

@dataclass
class QubitTracker:
"""Track qubits per index as and their state.
"""Track qubits (per index) and their state.

The states are distinguished into clean (meaning in state :math:`|0\rangle`) or dirty (an
unkown state).
Cryoris marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -43,51 +43,47 @@ def num_dirty(self, active_qubits: Iterable[int] | None = None):

def borrow(self, num_qubits: int, active_qubits: Iterable[int] | None = None) -> list[int]:
alexanderivrii marked this conversation as resolved.
Show resolved Hide resolved
"""Get ``num_qubits`` qubits, excluding ``active_qubits``."""
if active_qubits is None:
active_qubits = []
active_qubits = set(active_qubits or [])
available_qubits = [qubit for qubit in self.qubits if qubit not in active_qubits]

if num_qubits > (available := len(self.qubits) - len(active_qubits)):
if num_qubits > (available := len(available_qubits)):
raise RuntimeError(f"Cannot borrow {num_qubits} qubits, only {available} available.")

return [qubit for qubit in self.qubits if qubit not in active_qubits][:num_qubits]
return available_qubits[:num_qubits]

def used(self, qubits: Iterable[int], check: bool = True) -> None:
"""Set the state of ``qubits`` to used (i.e. False)."""
if check:
if len(untracked := set(qubits).difference(self.qubits)) > 0:
raise ValueError(
"Setting state of untracked qubits:", untracked, "\nTracker state:", self
)
qubits = set(qubits)

for qubit in qubits:
self.clean.discard(qubit)
if check:
if len(untracked := qubits.difference(self.qubits)) > 0:
raise ValueError(f"Setting state of untracked qubits: {untracked}. Tracker: {self}")

self.dirty.update(qubits)
self.clean -= qubits
self.dirty |= qubits

def reset(self, qubits: Iterable[int], check: bool = True) -> None:
"""Set the state of ``qubits`` to 0 (i.e. True)."""
qubits = set(qubits)

if check:
if len(untracked := set(qubits).difference(self.qubits)) > 0:
raise ValueError(
"Setting state of untracked qubits:", untracked, "\nTracker state:", self
)
if len(untracked := qubits.difference(self.qubits)) > 0:
raise ValueError(f"Setting state of untracked qubits: {untracked}. Tracker: {self}")

self.clean.update(qubits)
for qubit in qubits:
self.dirty.discard(qubit)
self.clean |= qubits
self.dirty -= qubits

def drop(self, qubits: set[int], check: bool = True) -> None:
def drop(self, qubits: Iterable[int], check: bool = True) -> None:
"""Drop qubits from the tracker, meaning that they are no longer available."""
qubits = set(qubits)

if check:
if len(untracked := set(qubits).difference(self.qubits)) > 0:
raise ValueError(
"Dropping state of untracked qubits:", untracked, "\nTracker state:", self
)
if len(untracked := qubits.difference(self.qubits)) > 0:
raise ValueError(f"Dropping untracked qubits: {untracked}. Tracker: {self}")

self.qubits = tuple(qubit for qubit in self.qubits if qubit not in qubits)
for qubit in qubits:
self.clean.discard(qubit)
self.dirty.discard(qubit)
self.clean -= qubits
self.dirty -= qubits

def copy(self, qubit_map: dict[int, int] | None = None) -> "QubitTracker":
"""Copy self.
Expand All @@ -109,7 +105,7 @@ def copy(self, qubit_map: dict[int, int] | None = None) -> "QubitTracker":
elif old_index in self.dirty:
dirty.add(new_index)
else:
raise ValueError(f"Unknown old qubit index: {old_index}.\nTracker: {self}")
raise ValueError(f"Unknown old qubit index: {old_index}. Tracker: {self}")

qubits = tuple(qubit_map.values())

Expand Down
Loading