Skip to content
This repository has been archived by the owner on Jul 24, 2024. It is now read-only.

Add dynamic circuits scheduling pass #365

Merged
merged 65 commits into from
Aug 24, 2022
Merged
Changes from 1 commit
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
450922b
Fix lint for Qiskit main branch.
taalexander Jul 6, 2022
5c61448
Fix sphinx language configuration.
taalexander Jul 6, 2022
0658c7a
Add initial scaffolding for dynamic_circuits module.
taalexander Jun 17, 2022
4dbe92a
Add a dummy test.
taalexander Jun 17, 2022
44a7afb
Add reno.
taalexander Jun 17, 2022
8aa3365
Add initial qiskit DynamicalDecoupling scheduling scaffolding and tests.
taalexander Jun 17, 2022
06f9d86
Refactor scheduler with visitor pattern.
taalexander Jun 19, 2022
a518281
Refactor nodes to no longer return. Remove special handling for class…
taalexander Jun 19, 2022
bfcfe2e
Add block based scheduling and updated tests for dynamic circuits typ…
taalexander Jun 27, 2022
9a0955c
Refactor padder class.
taalexander Jul 5, 2022
1744ae4
Base padding is working.
taalexander Jul 5, 2022
4340be0
Remove debug statements.
taalexander Jul 5, 2022
9e2d5ae
Update non conditional tests to pass.
taalexander Jul 5, 2022
3cd3859
c_if scheduling tests are working.
taalexander Jul 5, 2022
b48951d
All tests passing.
taalexander Jul 5, 2022
3efa7da
All dynamic circuits tests passing.
taalexander Jul 6, 2022
96e02fb
Black formatting.
taalexander Jul 6, 2022
9dddf1e
Move the dynamic_circuits module to a generic transpiler module.
taalexander Jul 6, 2022
2442d1e
Blackify
taalexander Jul 6, 2022
7e144e9
Linting.
taalexander Jul 6, 2022
42c8bc8
Blackify.
taalexander Jul 6, 2022
615f03c
Reach black and pylint fixed point.
taalexander Jul 6, 2022
70ae09d
Mypyify the code.
taalexander Jul 6, 2022
36c611e
Merge branch 'main' into add-scheduling-pass
rathishcholarajan Jul 6, 2022
b4f8f9b
Add initial scaffolding for dynamic_circuits module.
taalexander Jun 17, 2022
af86472
Add a dummy test.
taalexander Jun 17, 2022
b42cf0c
Add reno.
taalexander Jun 17, 2022
0bef47b
Add initial qiskit DynamicalDecoupling scheduling scaffolding and tests.
taalexander Jun 17, 2022
f97fa79
Refactor scheduler with visitor pattern.
taalexander Jun 19, 2022
e9db084
Refactor nodes to no longer return. Remove special handling for class…
taalexander Jun 19, 2022
eab1fdb
Add block based scheduling and updated tests for dynamic circuits typ…
taalexander Jun 27, 2022
dc4ea34
Refactor padder class.
taalexander Jul 5, 2022
6e173a5
Base padding is working.
taalexander Jul 5, 2022
f490053
Remove debug statements.
taalexander Jul 5, 2022
ccbad76
Update non conditional tests to pass.
taalexander Jul 5, 2022
25bba97
c_if scheduling tests are working.
taalexander Jul 5, 2022
65760f3
All tests passing.
taalexander Jul 5, 2022
d7cbdd5
All dynamic circuits tests passing.
taalexander Jul 6, 2022
2be8de8
Black formatting.
taalexander Jul 6, 2022
eb33397
Move the dynamic_circuits module to a generic transpiler module.
taalexander Jul 6, 2022
8deed38
Blackify
taalexander Jul 6, 2022
bf0807b
Linting.
taalexander Jul 6, 2022
8b4a602
Blackify.
taalexander Jul 6, 2022
5ac8693
Reach black and pylint fixed point.
taalexander Jul 6, 2022
8f49cb1
Mypyify the code.
taalexander Jul 6, 2022
53ad4b4
Merge remote-tracking branch 'origin/add-scheduling-pass' into add-sc…
taalexander Jul 6, 2022
209cc8e
Rename BasePadding to BlockBasePadder.
taalexander Jul 6, 2022
89e7fba
Fix broken tests by Qiskit upgrade.
taalexander Jul 6, 2022
ee823a5
Move tests into unit.
taalexander Jul 6, 2022
4c33ca7
linting.
taalexander Jul 6, 2022
aea5473
Update documentation.
taalexander Jul 6, 2022
ea37d66
Fix requirements dev for matplotlib drawer./
taalexander Jul 6, 2022
69cbab4
Chain qargs and cargs.
taalexander Jul 6, 2022
b1943a6
Update pulse block comment.
taalexander Jul 6, 2022
2b971b3
Update measurement comment.
taalexander Jul 6, 2022
44a1905
Add t0 comment.
taalexander Jul 6, 2022
2d995df
Move scheduler unit tests.
taalexander Jul 6, 2022
931fe00
Make scheduler idempotent.
taalexander Jul 7, 2022
783d608
Operations on qubits already measured trigger block termination.
taalexander Jul 7, 2022
b777249
Black.
taalexander Jul 7, 2022
81596b1
Update comment on _visit_measure qargs.
taalexander Aug 11, 2022
d50b28b
Update comment typo.
taalexander Aug 11, 2022
97e5bcd
Update comment on neighbor ordering.
taalexander Aug 11, 2022
b5d9e31
Add comments.
taalexander Aug 11, 2022
ff25a88
Merge branch 'main' into add-scheduling-pass
kt474 Aug 24, 2022
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
Refactor padder class.
taalexander committed Jul 6, 2022
commit dc4ea3476de839fb15a9e3468741bd151a3988a0
201 changes: 112 additions & 89 deletions qiskit_ibm_provider/dynamic_circuits/base_padding.py
Original file line number Diff line number Diff line change
@@ -49,6 +49,16 @@ class BasePadding(TransformationPass):
which may result in violation of hardware alignment constraints.
"""

def __init__(self):
self._node_start_time = None
self._idle_after = None
self._dag = None
self._circuit_duration = 0
self._current_block_idx = 0

super().__init__()


def run(self, dag: DAGCircuit):
"""Run the padding pass on ``dag``.

@@ -64,88 +74,50 @@ def run(self, dag: DAGCircuit):
"""
self._pre_runhook(dag)

node_start_time = self.property_set["node_start_time"].copy()
self._node_start_time = self.property_set["node_start_time"].copy()
self._idle_after = {bit: 0 for bit in dag.qubits}
self._current_block_idx = 0

new_dag = DAGCircuit()
# Prepare DAG to pad
self._dag = DAGCircuit()
for qreg in dag.qregs.values():
new_dag.add_qreg(qreg)
self._dag.add_qreg(qreg)
for creg in dag.cregs.values():
new_dag.add_creg(creg)
self._dag.add_creg(creg)

# Update start time dictionary for the new_dag.
# This information may be used for further scheduling tasks,
# but this is immediately invalidated becasue node id is updated in the new_dag.
self.property_set["node_start_time"].clear()

new_dag.name = dag.name
new_dag.metadata = dag.metadata
new_dag.unit = self.property_set["time_unit"]
new_dag.calibrations = dag.calibrations
new_dag.global_phase = dag.global_phase
self._dag.name = dag.name
self._dag.metadata = dag.metadata
self._dag.unit = self.property_set["time_unit"]
self._dag.calibrations = dag.calibrations
self._dag.global_phase = dag.global_phase

idle_after = {bit: 0 for bit in dag.qubits}

# Compute fresh circuit duration from the node start time dictionary and op duration.
# Note that pre-scheduled duration may change within the alignment passes, i.e.
# if some instruction time t0 violating the hardware alignment constraint,
# the alignment pass may delay t0 and accordingly the circuit duration changes.
circuit_duration = 0
self._circuit_duration = 0
for node in dag.topological_op_nodes():
if node in node_start_time:
block_idx, t0 = node_start_time[node]
t1 = t0 + node.op.duration
circuit_duration = max(circuit_duration, t1)

if node in self._node_start_time:
if isinstance(node.op, Delay):
# The padding class considers a delay instruction as idle time
# rather than instruction. Delay node is removed so that
# we can extract non-delay predecessors.
dag.remove_op_node(node)
continue

for bit in node.qargs:

# Fill idle time with some sequence
if t0 - idle_after[bit] > 0:
# Find previous node on the wire, i.e. always the latest node on the wire
prev_node = next(new_dag.predecessors(new_dag.output_map[bit]))
self._pad(
dag=new_dag,
block_idx=block_idx,
qubit=bit,
t_start=idle_after[bit],
t_end=t0,
next_node=node,
prev_node=prev_node,
)

idle_after[bit] = t1

self._apply_scheduled_op(new_dag, block_idx, t0, node.op, node.qargs, node.cargs)
self._visit_delay(node)
else:
self._visit_generic(node)

else:
raise TranspilerError(
f"Operation {repr(node)} is likely added after the circuit is scheduled. "
"Schedule the circuit again if you transformed it."
)

# Add delays until the end of circuit.
for bit in new_dag.qubits:
if circuit_duration - idle_after[bit] > 0:
node = new_dag.output_map[bit]
prev_node = next(new_dag.predecessors(node))
self._pad(
dag=new_dag,
block_idx=block_idx,
qubit=bit,
t_start=idle_after[bit],
t_end=circuit_duration,
next_node=node,
prev_node=prev_node,
)

new_dag.duration = circuit_duration
self._dag.duration = self._circuit_duration

return new_dag
return self._dag

def _pre_runhook(self, dag: DAGCircuit):
"""Extra routine inserted before running the padding pass.
@@ -162,38 +134,8 @@ def _pre_runhook(self, dag: DAGCircuit):
f"before running the {self.__class__.__name__} pass."
)

def _apply_scheduled_op(
self,
dag: DAGCircuit,
block_idx: int,
t_start: int,
oper: Instruction,
qubits: Union[Qubit, List[Qubit]],
clbits: Optional[Union[Clbit, List[Clbit]]] = None,
):
"""Add new operation to DAG with scheduled information.

This is identical to apply_operation_back + updating the node_start_time propety.

Args:
dag: DAG circuit on which the sequence is applied.
block_idx: Execution block index for this node.
t_start: Start time of new node.
oper: New operation that is added to the DAG circuit.
qubits: The list of qubits that the operation acts on.
clbits: The list of clbits that the operation acts on.
"""
if isinstance(qubits, Qubit):
qubits = [qubits]
if isinstance(clbits, Clbit):
clbits = [clbits]

new_node = dag.apply_operation_back(oper, qargs=qubits, cargs=clbits)
self.property_set["node_start_time"][new_node] = (block_idx, t_start)

def _pad(
self,
dag: DAGCircuit,
block_idx: int,
qubit: Qubit,
t_start: int,
@@ -219,7 +161,6 @@ def _pad(
might be unexpected due to erroneous scheduling.

Args:
dag: DAG circuit that sequence is applied.
block_idx: Execution block index for this node.
qubit: The wire that the sequence is applied on.
t_start: Absolute start time of this interval.
@@ -228,3 +169,85 @@ def _pad(
prev_node: Node ahead of the sequence.
"""
raise NotImplementedError

def _visit_delay(self, node):
"""The padding class considers a delay instruction as idle time
rather than instruction. Delay node is not added so that
we can extract non-delay predecessors.
"""
pass

def _visit_generic(self, node):
"""Visit a generic node to pad."""
block_idx, t0 = self._node_start_time[node]

# Trigger the end of a block
if block_idx > self._current_block_idx:
self._pad_until_block_end(self._circuit_duration, self._current_block_idx)

# Now set the current block index.
self._current_block_idx = block_idx


t1 = t0 + node.op.duration
self._circuit_duration = max(self._circuit_duration, t1)
for bit in node.qargs:

# Fill idle time with some sequence
if t0 - self._idle_after[bit] > 0:
# Find previous node on the wire, i.e. always the latest node on the wire
prev_node = next(self._dag.predecessors(self._dag.output_map[bit]))
self._pad(
block_idx=block_idx,
qubit=bit,
t_start=self._idle_after[bit],
t_end=t0,
next_node=node,
prev_node=prev_node,
)

self._idle_after[bit] = t1

self._apply_scheduled_op(block_idx, t0, node.op, node.qargs, node.cargs)

def _pad_until_block_end(self, block_duration, block_idx):
# Add delays until the end of circuit.
for bit in self._dag.qubits:
if block_duration - self._idle_after[bit] > 0:
node = self._dag.output_map[bit]
prev_node = next(self._dag.predecessors(node))
self._pad(
block_idx=block_idx,
qubit=bit,
t_start=self._idle_after[bit],
t_end=block_duration,
next_node=node,
prev_node=prev_node,
)

def _apply_scheduled_op(
self,
block_idx: int,
t_start: int,
oper: Instruction,
qubits: Union[Qubit, List[Qubit]],
clbits: Optional[Union[Clbit, List[Clbit]]] = None,
):
"""Add new operation to DAG with scheduled information.

This is identical to apply_operation_back + updating the node_start_time propety.

Args:
block_idx: Execution block index for this node.
t_start: Start time of new node.
oper: New operation that is added to the DAG circuit.
qubits: The list of qubits that the operation acts on.
clbits: The list of clbits that the operation acts on.
"""
if isinstance(qubits, Qubit):
qubits = [qubits]
if isinstance(clbits, Clbit):
clbits = [clbits]

new_node = self._dag.apply_operation_back(oper, qargs=qubits, cargs=clbits)
self.property_set["node_start_time"][new_node] = (block_idx, t_start)
3 changes: 1 addition & 2 deletions qiskit_ibm_provider/dynamic_circuits/pad_delay.py
Original file line number Diff line number Diff line change
@@ -62,7 +62,6 @@ def __init__(self, fill_very_end: bool = True):
def _pad(
self,
block_idx: int,
dag: DAGCircuit,
qubit: Qubit,
t_start: int,
t_end: int,
@@ -73,4 +72,4 @@ def _pad(
return

time_interval = t_end - t_start
self._apply_scheduled_op(dag, block_idx, t_start, Delay(time_interval, dag.unit), qubit)
self._apply_scheduled_op(block_idx, t_start, Delay(time_interval, self._dag.unit), qubit)
47 changes: 38 additions & 9 deletions qiskit_ibm_provider/dynamic_circuits/schedule.py
Original file line number Diff line number Diff line change
@@ -12,6 +12,8 @@

"""Scheduler for dynamic circuit backends."""

import itertools

import qiskit
from qiskit.circuit import Measure
from qiskit.transpiler.exceptions import TranspilerError
@@ -51,6 +53,7 @@ def __init__(self, durations: qiskit.transpiler.instruction_durations.Instructio

self._node_start_time = None
self._idle_after = None
self._current_block_measures = None
self._bit_indices = None

super().__init__(durations)
@@ -70,6 +73,8 @@ def run(self, dag):
for node in dag.topological_op_nodes():
self._visit_node(node)

for node, start_time in self._node_start_time.items():
print(repr(node), start_time)
self.property_set["node_start_time"] = self._node_start_time

def _init_run(self, dag):
@@ -85,6 +90,7 @@ def _init_run(self, dag):

self._node_start_time = dict()
self._idle_after = {q: (0, 0) for q in dag.qubits + dag.clbits}
self._current_block_measures = set()
self._bit_indices = {q: index for index, q in enumerate(dag.qubits)}

def _get_node_duration(self, node):
@@ -143,7 +149,7 @@ def _visit_conditional_node(self, node):
t1c = t0c + self._conditional_latency
for bit in node.op.condition_bits:
# Lock clbit until state is read
self._idle_after[bit] = (0, t1c)
self._idle_after[bit] = (self._current_block_idx, t1c)
# It starts after register read access
t0 = max(t0q, t1c)

@@ -164,15 +170,31 @@ def _visit_measure(self, node):
"""
op_duration = self._get_node_duration(node)

# measure instruction handling is bit tricky due to clbit_write_latency
t0q = max(self._idle_after[q][1] for q in node.qargs)
t0 = t0q
t1 = t0 + op_duration
for clbit in node.cargs:
self._idle_after[clbit] = (0, t1)
current_block_measure_qargs = self._current_block_measure_qargs()
measure_qargs = set(node.qargs)

t0q = max(self._idle_after[q][1] for q in measure_qargs)

# If the measurement qubits overlap, we need to start a new scheduling block.
if current_block_measure_qargs & measure_qargs:
self._begin_new_circuit_block()
t0q = 0
# Otherwise we need to increment all measurements to start at the same time within the block.
else:
t0q = max(itertools.chain([t0q], (self._node_start_time[measure][1] for measure in self._current_block_measures)))

# Insert this measure into the block
self._current_block_measures.add(node)

# now update all measure qarg times.

self._current_block_measures.add(node)

for measure in self._current_block_measures:
t0 = t0q
t1 = t0 + self._get_node_duration(measure)
self._update_idles(measure, t0, t1)

self._update_idles(node, t0, t1)
#self._begin_new_circuit_block()

def _visit_generic(self, node):
"""Visit a generic node such as a gate or barrier."""
@@ -187,10 +209,17 @@ def _update_idles(self, node, t0, t1):
for bit in node.qargs:
self._idle_after[bit] = (self._current_block_idx, t1)

for bit in node.cargs:
self._idle_after[bit] = (self._current_block_idx, t1)

self._node_start_time[node] = (self._current_block_idx, t0)

def _begin_new_circuit_block(self):
"""Create a new timed circuit block completing the previous block.

"""
self._current_block_idx += 1
self._current_block_measures = set()

def _current_block_measure_qargs(self):
return set(qarg for measure in self._current_block_measures for qarg in measure.qargs)
Loading