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])
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_time_available[q] < until: idle_duration = until - qubit_time_available[q] new_dag.apply_operation_front(Delay(idle_duration, unit), [q], [])
def _pad( self, dag: DAGCircuit, qubit: Qubit, time_interval: int, next_node: DAGNode, prev_node: DAGNode, ): if not self.fill_very_end and isinstance(next_node, DAGOutNode): return dag.apply_operation_back(Delay(time_interval, dag.unit), [qubit])
def _pad( self, dag: DAGCircuit, qubit: Qubit, t_start: int, t_end: int, next_node: DAGNode, prev_node: DAGNode, ): if not self.fill_very_end and isinstance(next_node, DAGOutNode): return time_interval = t_end - t_start self._apply_scheduled_op(dag, t_start, Delay(time_interval, dag.unit), qubit)
def _pad( self, dag: DAGCircuit, qubit: Qubit, t_start: int, t_end: int, next_node: DAGNode, prev_node: DAGNode, ): # This routine takes care of the pulse alignment constraint for the DD sequence. # Note that the alignment constraint acts on the t0 of the DAGOpNode. # Now this constrained scheduling problem is simplified to the problem of # finding a delay amount which is a multiple of the constraint value by assuming # that the duration of every DAGOpNode is also a multiple of the constraint value. # # For example, given the constraint value of 16 and XY4 with 160 dt gates. # Here we assume current interval is 992 dt. # # relative spacing := [0.125, 0.25, 0.25, 0.25, 0.125] # slack = 992 dt - 4 x 160 dt = 352 dt # # unconstraind sequence: 44dt-X1-88dt-Y2-88dt-X3-88dt-Y4-44dt # constraind sequence : 32dt-X1-80dt-Y2-80dt-X3-80dt-Y4-32dt + extra slack 48 dt # # Now we evenly split extra slack into start and end of the sequence. # The distributed slack should be multiple of 16. # Start = +16, End += 32 # # final sequence : 48dt-X1-80dt-Y2-80dt-X3-80dt-Y4-64dt / in total 992 dt # # Now we verify t0 of every node starts from multiple of 16 dt. # # X1: 48 dt (3 x 16 dt) # Y2: 48 dt + 160 dt + 80 dt = 288 dt (18 x 16 dt) # Y3: 288 dt + 160 dt + 80 dt = 528 dt (33 x 16 dt) # Y4: 368 dt + 160 dt + 80 dt = 768 dt (48 x 16 dt) # # As you can see, constraints on t0 are all satified without explicit scheduling. time_interval = t_end - t_start if self._qubits and dag.qubits.index(qubit) not in self._qubits: # Target physical qubit is not the target of this DD sequence. self._apply_scheduled_op(dag, t_start, Delay(time_interval, dag.unit), qubit) return if self._skip_reset_qubits and (isinstance(prev_node, DAGInNode) or isinstance(prev_node.op, Reset)): # Previous node is the start edge or reset, i.e. qubit is ground state. self._apply_scheduled_op(dag, t_start, Delay(time_interval, dag.unit), qubit) return slack = time_interval - np.sum(self._dd_sequence_lengths[qubit]) sequence_gphase = self._sequence_phase if slack <= 0: # Interval too short. self._apply_scheduled_op(dag, t_start, Delay(time_interval, dag.unit), qubit) return if len(self._dd_sequence) == 1: # Special case of using a single gate for DD u_inv = self._dd_sequence[0].inverse().to_matrix() theta, phi, lam, phase = OneQubitEulerDecomposer( ).angles_and_phase(u_inv) if isinstance(next_node, DAGOpNode) and isinstance( next_node.op, (UGate, U3Gate)): # Absorb the inverse into the successor (from left in circuit) theta_r, phi_r, lam_r = next_node.op.params next_node.op.params = Optimize1qGates.compose_u3( theta_r, phi_r, lam_r, theta, phi, lam) sequence_gphase += phase elif isinstance(prev_node, DAGOpNode) and isinstance( prev_node.op, (UGate, U3Gate)): # Absorb the inverse into the predecessor (from right in circuit) theta_l, phi_l, lam_l = prev_node.op.params prev_node.op.params = Optimize1qGates.compose_u3( theta, phi, lam, theta_l, phi_l, lam_l) sequence_gphase += phase else: # Don't do anything if there's no single-qubit gate to absorb the inverse self._apply_scheduled_op(dag, t_start, Delay(time_interval, dag.unit), qubit) return def _constrained_length(values): return self._alignment * np.floor(values / self._alignment) # (1) Compute DD intervals satisfying the constraint taus = _constrained_length(slack * np.asarray(self._spacing)) extra_slack = slack - np.sum(taus) # (2) Distribute extra slack if self._extra_slack_distribution == "middle": mid_ind = int((len(taus) - 1) / 2) to_middle = _constrained_length(extra_slack) taus[mid_ind] += to_middle if extra_slack - to_middle: # If to_middle is not a multiple value of the pulse alignment, # it is truncated to the nearlest multiple value and # the rest of slack is added to the end. taus[-1] += extra_slack - to_middle elif self._extra_slack_distribution == "edges": to_begin_edge = _constrained_length(extra_slack / 2) taus[0] += to_begin_edge taus[-1] += extra_slack - to_begin_edge else: raise TranspilerError( f"Option extra_slack_distribution = {self._extra_slack_distribution} is invalid." ) # (3) Construct DD sequence with delays num_elements = max(len(self._dd_sequence), len(taus)) idle_after = t_start for dd_ind in range(num_elements): if dd_ind < len(taus): tau = taus[dd_ind] if tau > 0: self._apply_scheduled_op(dag, idle_after, Delay(tau, dag.unit), qubit) idle_after += tau if dd_ind < len(self._dd_sequence): gate = self._dd_sequence[dd_ind] gate_length = self._dd_sequence_lengths[qubit][dd_ind] self._apply_scheduled_op(dag, idle_after, gate, qubit) idle_after += gate_length dag.global_phase = self._mod_2pi(dag.global_phase + sequence_gphase)
def run(self, dag): """Run the DynamicalDecoupling pass on dag. Args: dag (DAGCircuit): a scheduled DAG. Returns: DAGCircuit: equivalent circuit with delays interrupted by DD, where possible. Raises: TranspilerError: if the circuit is not mapped on physical qubits. """ if len(dag.qregs) != 1 or dag.qregs.get("q", None) is None: raise TranspilerError("DD runs on physical circuits only.") if dag.duration is None: raise TranspilerError("DD runs after circuit is scheduled.") num_pulses = len(self._dd_sequence) sequence_gphase = 0 if num_pulses != 1: if num_pulses % 2 != 0: raise TranspilerError( "DD sequence must contain an even number of gates (or 1).") noop = np.eye(2) for gate in self._dd_sequence: noop = noop.dot(gate.to_matrix()) if not matrix_equal(noop, IGate().to_matrix(), ignore_phase=True): raise TranspilerError( "The DD sequence does not make an identity operation.") sequence_gphase = np.angle(noop[0][0]) if self._qubits is None: self._qubits = set(range(dag.num_qubits())) else: self._qubits = set(self._qubits) if self._spacing: if sum(self._spacing) != 1 or any(a < 0 for a in self._spacing): raise TranspilerError( "The spacings must be given in terms of fractions " "of the slack period and sum to 1.") else: # default to balanced spacing mid = 1 / num_pulses end = mid / 2 self._spacing = [end] + [mid] * (num_pulses - 1) + [end] new_dag = dag._copy_circuit_metadata() qubit_index_map = { qubit: index for index, qubit in enumerate(new_dag.qubits) } index_sequence_duration_map = {} for qubit in new_dag.qubits: physical_qubit = qubit_index_map[qubit] dd_sequence_duration = 0 for gate in self._dd_sequence: gate.duration = self._durations.get(gate, physical_qubit) dd_sequence_duration += gate.duration index_sequence_duration_map[physical_qubit] = dd_sequence_duration for nd in dag.topological_op_nodes(): if not isinstance(nd.op, Delay): new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs) continue dag_qubit = nd.qargs[0] physical_qubit = qubit_index_map[dag_qubit] if physical_qubit not in self._qubits: # skip unwanted qubits new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs) continue pred = next(dag.predecessors(nd)) succ = next(dag.successors(nd)) if self._skip_reset_qubits: # discount initial delays if pred.type == "in" or isinstance(pred.op, Reset): new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs) continue dd_sequence_duration = index_sequence_duration_map[physical_qubit] slack = nd.op.duration - dd_sequence_duration if slack <= 0: # dd doesn't fit new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs) continue if num_pulses == 1: # special case of using a single gate for DD u_inv = self._dd_sequence[0].inverse().to_matrix() theta, phi, lam, phase = OneQubitEulerDecomposer( ).angles_and_phase(u_inv) # absorb the inverse into the successor (from left in circuit) if succ.type == "op" and isinstance(succ.op, (UGate, U3Gate)): theta_r, phi_r, lam_r = succ.op.params succ.op.params = Optimize1qGates.compose_u3( theta_r, phi_r, lam_r, theta, phi, lam) sequence_gphase += phase # absorb the inverse into the predecessor (from right in circuit) elif pred.type == "op" and isinstance(pred.op, (UGate, U3Gate)): theta_l, phi_l, lam_l = pred.op.params pred.op.params = Optimize1qGates.compose_u3( theta, phi, lam, theta_l, phi_l, lam_l) sequence_gphase += phase # don't do anything if there's no single-qubit gate to absorb the inverse else: new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs) continue # insert the actual DD sequence taus = [int(slack * a) for a in self._spacing] unused_slack = slack - sum( taus) # unused, due to rounding to int multiples of dt middle_index = int( (len(taus) - 1) / 2) # arbitrary: redistribute to middle taus[ middle_index] += unused_slack # now we add up to original delay duration for tau, gate in itertools.zip_longest(taus, self._dd_sequence): if tau > 0: new_dag.apply_operation_back(Delay(tau), [dag_qubit]) if gate is not None: new_dag.apply_operation_back(gate, [dag_qubit]) new_dag.global_phase = _mod_2pi(new_dag.global_phase + sequence_gphase) return new_dag
def convert_to_target(conf_dict: dict, props_dict: dict = None, defs_dict: dict = None) -> Target: """Uses configuration, properties and pulse defaults dicts to construct and return Target class. """ name_mapping = { "id": IGate(), "sx": SXGate(), "x": XGate(), "cx": CXGate(), "rz": RZGate(Parameter("λ")), "reset": Reset(), } custom_gates = {} qubit_props = None if props_dict: qubit_props = qubit_props_from_props(props_dict) target = Target(qubit_properties=qubit_props) # Parse from properties if it exsits if props_dict is not None: # Parse instructions gates = {} for gate in props_dict["gates"]: name = gate["gate"] if name in name_mapping: if name not in gates: gates[name] = {} elif name not in custom_gates: custom_gate = Gate(name, len(gate["qubits"]), []) custom_gates[name] = custom_gate gates[name] = {} qubits = tuple(gate["qubits"]) gate_props = {} for param in gate["parameters"]: if param["name"] == "gate_error": gate_props["error"] = param["value"] if param["name"] == "gate_length": gate_props["duration"] = apply_prefix( param["value"], param["unit"]) gates[name][qubits] = InstructionProperties(**gate_props) for gate, props in gates.items(): if gate in name_mapping: inst = name_mapping.get(gate) else: inst = custom_gates[gate] target.add_instruction(inst, props) # Create measurement instructions: measure_props = {} count = 0 for qubit in props_dict["qubits"]: qubit_prop = {} for prop in qubit: if prop["name"] == "readout_length": qubit_prop["duration"] = apply_prefix( prop["value"], prop["unit"]) if prop["name"] == "readout_error": qubit_prop["error"] = prop["value"] measure_props[(count, )] = InstructionProperties(**qubit_prop) count += 1 target.add_instruction(Measure(), measure_props) # Parse from configuration because properties doesn't exist else: for gate in conf_dict["gates"]: name = gate["name"] gate_props = {tuple(x): None for x in gate["coupling_map"]} if name in name_mapping: target.add_instruction(name_mapping[name], gate_props) else: custom_gate = Gate(name, len(gate["coupling_map"][0]), []) target.add_instruction(custom_gate, gate_props) measure_props = {(n, ): None for n in range(conf_dict["n_qubits"])} target.add_instruction(Measure(), measure_props) # parse global configuration properties dt = conf_dict.get("dt") if dt: target.dt = dt * 1e-9 if "timing_constraints" in conf_dict: target.granularity = conf_dict["timing_constraints"].get("granularity") target.min_length = conf_dict["timing_constraints"].get("min_length") target.pulse_alignment = conf_dict["timing_constraints"].get( "pulse_alignment") target.aquire_alignment = conf_dict["timing_constraints"].get( "acquire_alignment") # If pulse defaults exists use that as the source of truth if defs_dict is not None: # TODO remove the usage of PulseDefaults as it will be deprecated in the future pulse_defs = PulseDefaults.from_dict(defs_dict) inst_map = pulse_defs.instruction_schedule_map for inst in inst_map.instructions: for qarg in inst_map.qubits_with_instruction(inst): sched = inst_map.get(inst, qarg) if inst in target: try: qarg = tuple(qarg) except TypeError: qarg = (qarg, ) if inst == "measure": for qubit in qarg: target[inst][(qubit, )].calibration = sched else: target[inst][qarg].calibration = sched target.add_instruction(Delay(Parameter("t")), {(bit, ): None for bit in range(target.num_qubits)}) return target
def convert_to_target( configuration: BackendConfiguration, properties: BackendProperties = None, defaults: PulseDefaults = None, ) -> Target: """Uses configuration, properties and pulse defaults to construct and return Target class. """ name_mapping = { "id": IGate(), "sx": SXGate(), "x": XGate(), "cx": CXGate(), "rz": RZGate(Parameter("λ")), "reset": Reset(), } custom_gates = {} target = None # Parse from properties if it exsits if properties is not None: qubit_properties = qubit_props_list_from_props(properties=properties) target = Target( num_qubits=configuration.n_qubits, qubit_properties=qubit_properties ) # Parse instructions gates: Dict[str, Any] = {} for gate in properties.gates: name = gate.gate if name in name_mapping: if name not in gates: gates[name] = {} elif name not in custom_gates: custom_gate = Gate(name, len(gate.qubits), []) custom_gates[name] = custom_gate gates[name] = {} qubits = tuple(gate.qubits) gate_props = {} for param in gate.parameters: if param.name == "gate_error": gate_props["error"] = param.value if param.name == "gate_length": gate_props["duration"] = apply_prefix(param.value, param.unit) gates[name][qubits] = InstructionProperties(**gate_props) for gate, props in gates.items(): if gate in name_mapping: inst = name_mapping.get(gate) else: inst = custom_gates[gate] target.add_instruction(inst, props) # Create measurement instructions: measure_props = {} for qubit, _ in enumerate(properties.qubits): measure_props[(qubit,)] = InstructionProperties( duration=properties.readout_length(qubit), error=properties.readout_error(qubit), ) target.add_instruction(Measure(), measure_props) # Parse from configuration because properties doesn't exist else: target = Target(num_qubits=configuration.n_qubits) for gate in configuration.gates: name = gate.name gate_props = ( {tuple(x): None for x in gate.coupling_map} # type: ignore[misc] if hasattr(gate, "coupling_map") else {None: None} ) gate_len = len(gate.coupling_map[0]) if hasattr(gate, "coupling_map") else 0 if name in name_mapping: target.add_instruction(name_mapping[name], gate_props) else: custom_gate = Gate(name, gate_len, []) target.add_instruction(custom_gate, gate_props) target.add_instruction(Measure()) # parse global configuration properties if hasattr(configuration, "dt"): target.dt = configuration.dt if hasattr(configuration, "timing_constraints"): target.granularity = configuration.timing_constraints.get("granularity") target.min_length = configuration.timing_constraints.get("min_length") target.pulse_alignment = configuration.timing_constraints.get("pulse_alignment") target.aquire_alignment = configuration.timing_constraints.get( "acquire_alignment" ) # If a pulse defaults exists use that as the source of truth if defaults is not None: inst_map = defaults.instruction_schedule_map for inst in inst_map.instructions: for qarg in inst_map.qubits_with_instruction(inst): sched = inst_map.get(inst, qarg) if inst in target: try: qarg = tuple(qarg) except TypeError: qarg = (qarg,) if inst == "measure": for qubit in qarg: target[inst][(qubit,)].calibration = sched else: target[inst][qarg].calibration = sched if "delay" not in target: target.add_instruction( Delay(Parameter("t")), {(bit,): None for bit in range(target.num_qubits)} ) return target