def run(self, dag: DAGCircuit): """Run the measurement alignment pass on `dag`. Args: dag (DAGCircuit): DAG to be checked. Returns: DAGCircuit: DAG with consistent timing and op nodes annotated with duration. Raises: TranspilerError: If circuit is not scheduled. """ time_unit = self.property_set["time_unit"] if not _check_alignment_required(dag, self.alignment, Measure): # return input as-is to avoid unnecessary scheduling. # because following procedure regenerate new DAGCircuit, # we should avoid continuing if not necessary from performance viewpoint. return dag # if circuit is not yet scheduled, schedule with ALAP method if dag.duration is None: raise TranspilerError( f"This circuit {dag.name} may involve a delay instruction violating the " "pulse controller alignment. To adjust instructions to " "right timing, you should call one of scheduling passes first. " "This is usually done by calling transpiler with scheduling_method='alap'." ) # the following lines are basically copied from ASAPSchedule pass # # * some validations for non-scheduled nodes are dropped, since we assume scheduled input # * pad_with_delay is called only with non-delay node to avoid consecutive delay new_dag = dag._copy_circuit_metadata() qubit_time_available = defaultdict(int) # to track op start time qubit_stop_times = defaultdict(int) # to track delay start time for padding clbit_readable = defaultdict(int) clbit_writeable = defaultdict(int) def pad_with_delays(qubits: List[int], until, unit) -> None: """Pad idle time-slots in ``qubits`` with delays in ``unit`` until ``until``.""" for q in qubits: if qubit_stop_times[q] < until: idle_duration = until - qubit_stop_times[q] new_dag.apply_operation_back(Delay(idle_duration, unit), [q]) for node in dag.topological_op_nodes(): # choose appropriate clbit available time depending on op clbit_time_available = ( clbit_writeable if isinstance(node.op, Measure) else clbit_readable ) # correction to change clbit start time to qubit start time delta = node.op.duration if isinstance(node.op, Measure) else 0 start_time = max( itertools.chain( (qubit_time_available[q] for q in node.qargs), (clbit_time_available[c] - delta for c in node.cargs + node.op.condition_bits), ) ) if isinstance(node.op, Measure): if start_time % self.alignment != 0: start_time = ((start_time // self.alignment) + 1) * self.alignment if not isinstance(node.op, Delay): # exclude delays for combining consecutive delays pad_with_delays(node.qargs, until=start_time, unit=time_unit) new_dag.apply_operation_back(node.op, node.qargs, node.cargs) stop_time = start_time + node.op.duration # update time table for q in node.qargs: qubit_time_available[q] = stop_time if not isinstance(node.op, Delay): qubit_stop_times[q] = stop_time for c in node.cargs: # measure clbit_writeable[c] = clbit_readable[c] = stop_time for c in node.op.condition_bits: # conditional op clbit_writeable[c] = max(start_time, clbit_writeable[c]) working_qubits = qubit_time_available.keys() circuit_duration = max(qubit_time_available[q] for q in working_qubits) pad_with_delays(new_dag.qubits, until=circuit_duration, unit=time_unit) new_dag.name = dag.name new_dag.metadata = dag.metadata # set circuit duration and unit to indicate it is scheduled new_dag.duration = circuit_duration new_dag.unit = time_unit return new_dag