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])
Beispiel #2
0
 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], [])
Beispiel #3
0
    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])
Beispiel #4
0
    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