예제 #1
0
    def apply_readout_noise(
        self,
        noise: Union[Type[Noise], Iterable[Type[Noise]]],
        target_qubits: Optional[QubitSetInput] = None,
    ) -> Circuit:
        """Apply `noise` right before measurement in every qubit (default) or target_qubits`.

        Only when `target_qubits` is given can the noise be applied to an empty circuit.

        When `noise.qubit_count` > 1, the number of qubits in target_qubits must be equal
        to `noise.qubit_count`.

        Args:
            noise (Union[Type[Noise], Iterable[Type[Noise]]]): Noise channel(s) to be applied
            to the circuit.
            target_qubits (Union[QubitSetInput, optional]): Index or indices of qubit(s).
                Default=None.

        Returns:
            Circuit: self

        Raises:
            TypeError:
                If `noise` is not Noise type.
                If `target_qubits` has non-integers.
            IndexError:
                If applying noise to an empty circuit.
            ValueError:
                If `target_qubits` has negative integers.
                If `noise.qubit_count` > 1 and the number of qubits in target_qubits is
                not the same as `noise.qubit_count`.

        Examples:
            >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0,1)
            >>> print(circ)

            >>> noise = Noise.Depolarizing(probability=0.1)
            >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0,1)
            >>> print(circ.apply_initialization_noise(noise))

            >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0,1)
            >>> print(circ.apply_initialization_noise(noise, target_qubits = 1))

            >>> circ = Circuit()
            >>> print(circ.apply_initialization_noise(noise, target_qubits = [0, 1]))

        """
        if (len(self.qubits) == 0) and (target_qubits is None):
            raise IndexError(
                "target_qubits must be provided in order to apply the readout noise \
to an empty circuit.")

        if target_qubits is None:
            target_qubits = self.qubits
        else:
            if not isinstance(target_qubits, list):
                target_qubits = [target_qubits]
            if not all(isinstance(q, int) for q in target_qubits):
                raise TypeError("target_qubits must be integer(s)")
            if not all(q >= 0 for q in target_qubits):
                raise ValueError(
                    "target_qubits must contain only non-negative integers.")
            target_qubits = QubitSet(target_qubits)

        # make noise a list
        noise = wrap_with_list(noise)
        for noise_channel in noise:
            if not isinstance(noise_channel, Noise):
                raise TypeError("Noise must be an instance of the Noise class")
            if noise_channel.qubit_count > 1 and noise_channel.qubit_count != len(
                    target_qubits):
                raise ValueError(
                    "target_qubits needs to be provided for this multi-qubit noise channel, and \
the number of qubits in target_qubits must be the same as defined by the multi-qubit noise channel."
                )

        return apply_noise_to_moments(self, noise, target_qubits, "readout")
예제 #2
0
    def apply_gate_noise(
        self,
        noise: Union[Type[Noise], Iterable[Type[Noise]]],
        target_gates: Optional[Union[Type[Gate], Iterable[Type[Gate]]]] = None,
        target_unitary: np.ndarray = None,
        target_qubits: Optional[QubitSetInput] = None,
    ) -> Circuit:
        """Apply `noise` to the circuit according to `target_gates`, `target_unitary` and
        `target_qubits`.

        For any parameter that is None, that specification is ignored (e.g. if `target_gates`
        is None then the noise is applied after every gate in `target_qubits`).
        If `target_gates` and `target_qubits` are both None, then `noise` is
        applied to every qubit after every gate.

        Noise is either applied to `target_gates` or `target_unitary`, so they cannot be
        provided at the same time.

        When `noise.qubit_count` == 1, ie. `noise` is single-qubit, `noise` is added to all
        qubits in `target_gates` or `target_unitary` (or to all qubits in `target_qubits`
        if `target_gates` is None).

        When `noise.qubit_count` > 1 and `target_gates` is not None, the number of qubits of
        any gate in `target_gates` must be the same as `noise.qubit_count`.

        When `noise.qubit_count` > 1, `target_gates` and `target_unitary` is None, noise is
        only applied to gates with the same qubit_count in target_qubits.

        Args:
            noise (Union[Type[Noise], Iterable[Type[Noise]]]): Noise channel(s) to be applied
            to the circuit.
            target_gates (Union[Type[Gate], Iterable[Type[Gate]], optional]): Gate class or
                List of Gate classes which `noise` is applied to. Default=None.
            target_unitary (np.ndarray): matrix of the target unitary gates. Default=None.
            target_qubits (Union[QubitSetInput, optional]): Index or indices of qubit(s).
                Default=None.

        Returns:
            Circuit: self

        Raises:
            TypeError:
                If `noise` is not Noise type.
                If `target_gates` is not a Gate type, Iterable[Gate].
                If `target_unitary` is not a np.ndarray type.
                If `target_qubits` has non-integers or negative integers.
            IndexError:
                If applying noise to an empty circuit.
                If `target_qubits` is out of range of circuit.qubits.
            ValueError:
                If both `target_gates` and `target_unitary` are provided.
                If `target_unitary` is not a unitary.
                If `noise` is multi-qubit noise and `target_gates` contain gates
                with the number of qubits not the same as `noise.qubit_count`.
            Warning:
                If `noise` is multi-qubit noise while there is no gate with the same
                number of qubits in `target_qubits` or in the whole circuit when
                `target_qubits` is not given.
                If no `target_gates` or  `target_unitary` exist in `target_qubits` or
                in the whole circuit when they are not given.

        Examples:
            >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0,1)
            >>> print(circ)
            T  : |0|1|2|

            q0 : -X-Z-C-
                      |
            q1 : -Y-X-X-

            T  : |0|1|2|

            >>> noise = Noise.Depolarizing(probability=0.1)
            >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0,1)
            >>> print(circ.apply_gate_noise(noise, target_gates = Gate.X))
            T  : |     0     |     1     |2|

            q0 : -X-DEPO(0.1)-Z-----------C-
                                          |
            q1 : -Y-----------X-DEPO(0.1)-X-

            T  : |     0     |     1     |2|

            >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0,1)
            >>> print(circ.apply_gate_noise(noise, target_qubits = 1))
            T  : |     0     |     1     |     2     |

            q0 : -X-----------Z-----------C-----------
                                          |
            q1 : -Y-DEPO(0.1)-X-DEPO(0.1)-X-DEPO(0.1)-

            T  : |     0     |     1     |     2     |

            >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0,1)
            >>> print(circ.apply_gate_noise(noise,
            ...                             target_gates = [Gate.X,Gate.Y],
            ...                             target_qubits = [0,1])
            ... )
            T  : |     0     |     1     |2|

            q0 : -X-DEPO(0.1)-Z-----------C-
                                          |
            q1 : -Y-DEPO(0.1)-X-DEPO(0.1)-X-

            T  : |     0     |     1     |2|

        """
        # check whether gate noise is applied to an empty circuit
        if not self.qubits:
            raise IndexError(
                "Gate noise cannot be applied to an empty circuit.")

        # check if target_gates and target_unitary are both given
        if (target_unitary is not None) and (target_gates is not None):
            raise ValueError(
                "target_unitary and target_gates cannot be input at the same time."
            )

        # check target_qubits
        target_qubits = check_noise_target_qubits(self, target_qubits)
        if not all(qubit in self.qubits for qubit in target_qubits):
            raise IndexError(
                "target_qubits must be within the range of the current circuit."
            )

        # make noise a list
        noise = wrap_with_list(noise)

        # make target_gates a list
        if target_gates is not None:
            target_gates = wrap_with_list(target_gates)
            # remove duplicate items
            target_gates = list(dict.fromkeys(target_gates))

        for noise_channel in noise:
            if not isinstance(noise_channel, Noise):
                raise TypeError("Noise must be an instance of the Noise class")
                # check whether target_gates is valid
            if target_gates is not None:
                check_noise_target_gates(noise_channel, target_gates)
            if target_unitary is not None:
                check_noise_target_unitary(noise_channel, target_unitary)

        if target_unitary is not None:
            return apply_noise_to_gates(self, noise, target_unitary,
                                        target_qubits)
        else:
            return apply_noise_to_gates(self, noise, target_gates,
                                        target_qubits)