Beispiel #1
0
def test_representation_coeff_of_nonexistant_operation():
    qbit = cirq.LineQubit(0)
    ideal = cirq.Circuit(cirq.X(qbit))

    noisy_xop = NoisyOperation.from_cirq(circuit=cirq.X,
                                         channel_matrix=np.zeros(shape=(4, 4)))

    decomp = OperationRepresentation(ideal=ideal,
                                     basis_expansion=dict([(noisy_xop, 0.5)]))

    noisy_zop = NoisyOperation.from_cirq(circuit=cirq.Z,
                                         channel_matrix=np.zeros(shape=(4, 4)))
    with pytest.raises(ValueError, match="does not appear in the basis"):
        decomp.coeff_of(noisy_zop)
Beispiel #2
0
def test_print_operation_representation_two_qubits_neg():
    qreg = cirq.LineQubit.range(2)
    ideal = cirq.Circuit(cirq.CNOT(*qreg))

    noisy_a = NoisyOperation.from_cirq(
        circuit=cirq.Circuit(cirq.H.on_each(qreg[0]), cirq.CNOT(
            *qreg), cirq.H.on_each(qreg[1])))
    noisy_b = NoisyOperation.from_cirq(
        circuit=cirq.Circuit(cirq.Z.on_each(qreg[1])))
    decomp = OperationRepresentation(
        ideal=ideal,
        basis_expansion={
            noisy_a: -0.5,
            noisy_b: 1.5,
        },
    )
    print(str(decomp))
    expected = f"""
0: ───@───
      │
1: ───X─── ={" "}

-0.500
0: ───H───@───────
          │
1: ───────X───H───

+1.500
1: ───Z───"""
    # Remove initial newline
    expected = expected[1:]
    assert str(decomp) == expected
Beispiel #3
0
def _represent_operation_with_amplitude_damping_noise(
    ideal_operation: Circuit,
    noise_level: float,
) -> OperationRepresentation:
    r"""Returns the quasi-probability representation of the input
    single-qubit ``ideal_operation`` with respect to a basis of noisy
    operations.

    Any ideal single-qubit unitary followed by local amplitude-damping noise
    of equal ``noise_level`` is assumed to be in the basis of implementable
    operations.

    The representation is based on the analytical result presented
    in :cite:`Takagi2020`.

    Args:
        ideal_operation: The ideal operation (as a QPROGRAM) to represent.
        noise_level: The noise level of each amplitude damping channel.

    Returns:
        The quasi-probability representation of the ``ideal_operation``.

    .. note::
        The input ``ideal_operation`` is typically a QPROGRAM with a single
        gate but could also correspond to a sequence of more gates.
        This is possible as long as the unitary associated to the input
        QPROGRAM, followed by a single final amplitude damping channel, is
        physically implementable.

    .. note::
        The input ``ideal_operation`` must be a ``cirq.Circuit``.
    """

    if not isinstance(ideal_operation, Circuit):
        raise NotImplementedError(
            "The input ideal_operation must be a cirq.Circuit.", )

    qubits = ideal_operation.all_qubits()

    if len(qubits) == 1:
        q = tuple(qubits)[0]

        eta_0 = (1 + np.sqrt(1 - noise_level)) / (2 * (1 - noise_level))
        eta_1 = (1 - np.sqrt(1 - noise_level)) / (2 * (1 - noise_level))
        eta_2 = -noise_level / (1 - noise_level)
        etas = [eta_0, eta_1, eta_2]
        post_ops = [[], Z(q), reset(q)]

    else:
        raise ValueError(  # pragma: no cover
            "Only single-qubit operations are supported."  # pragma: no cover
        )  # pragma: no cover

    # Basis of implementable operations as circuits
    imp_op_circuits = [ideal_operation + Circuit(op) for op in post_ops]

    # Build basis_expantion
    expansion = {NoisyOperation(c): a for c, a in zip(imp_op_circuits, etas)}

    return OperationRepresentation(ideal_operation, expansion)
Beispiel #4
0
def test_representation_bad_type_for_basis_expansion():
    ideal = cirq.Circuit(cirq.H(cirq.LineQubit(0)))

    noisy_xop = NoisyOperation.from_cirq(circuit=cirq.X,
                                         channel_matrix=np.zeros(shape=(4, 4)))

    with pytest.raises(TypeError, match="All keys of `basis_expansion` must"):
        OperationRepresentation(ideal=ideal,
                                basis_expansion=dict([(1.0, noisy_xop)]))
Beispiel #5
0
def find_optimal_representation(
    ideal_operation: QPROGRAM,
    noisy_basis: NoisyBasis,
    tol: float = 1.0e-8,
    initial_guess: Optional[np.ndarray] = None,
) -> OperationRepresentation:
    r"""Returns the ``OperationRepresentaiton`` of the input ideal operation
    which minimizes the one-norm of the associated quasi-probability
    distribution.

    More precicely, it solve the following optimization problem:

    .. math::
        \min_{{\eta_\alpha}} = \sum_\alpha |\eta_\alpha|,
        \text{ such that }
        \mathcal G = \sum_\alpha \eta_\alpha \mathcal O_\alpha,

    where :math:`\{\mathcal O_j\}` is the input basis of noisy operations.

    Args:
        ideal_operation: The ideal operation to represent.
        noisy_basis: The ``NoisyBasis`` in which the ``ideal_operation``
            should be represented. It must contain ``NoisyOperation`` objects
            which are initialized with a numerical superoperator matrix.
        tol: The error tolerance for each matrix element
            of the represented operation.
        initial_guess: Optional initial guess for the coefficients
            :math:`\{ \eta_\alpha \}``.

    Returns: The optimal OperationRepresentation.
    """
    ideal_cirq_circuit, _ = convert_to_mitiq(ideal_operation)
    ideal_matrix = kraus_to_super(
        cast(List[np.ndarray], kraus(ideal_cirq_circuit)))
    basis_set = noisy_basis.elements

    try:
        basis_matrices = [noisy_op.channel_matrix for noisy_op in basis_set]
    except ValueError as err:
        if str(err) == "The channel matrix is unknown.":
            raise ValueError(
                "The input noisy_basis should contain NoisyOperation objects"
                " which are initialized with a numerical superoperator matrix."
            )
        else:
            raise err  # pragma no cover

    # Run numerical optimization problem
    quasi_prob_dist = minimize_one_norm(
        ideal_matrix,
        basis_matrices,
        tol=tol,
        initial_guess=initial_guess,
    )

    basis_expansion = {op: eta for op, eta in zip(basis_set, quasi_prob_dist)}
    return OperationRepresentation(ideal_operation, basis_expansion)
Beispiel #6
0
def test_representation_sample_zero_coefficient():
    ideal = cirq.Circuit(cirq.H(cirq.LineQubit(0)))

    noisy_xop = NoisyOperation.from_cirq(circuit=cirq.X,
                                         channel_matrix=np.zeros(shape=(4, 4)))
    noisy_zop = NoisyOperation.from_cirq(circuit=cirq.Z,
                                         channel_matrix=np.zeros(shape=(4, 4)))

    decomp = OperationRepresentation(
        ideal=ideal,
        basis_expansion={
            noisy_xop: 0.5,
            noisy_zop: 0.0,  # This should never be sampled.
        },
    )

    random_state = np.random.RandomState(seed=1)
    for _ in range(500):
        noisy_op, sign, coeff = decomp.sample(random_state=random_state)
        assert sign == 1
        assert coeff == 0.5
        assert np.allclose(noisy_op.ideal_unitary, cirq.unitary(cirq.X))
Beispiel #7
0
def test_equal_method_of_representations():
    q = cirq.LineQubit(0)
    ideal = cirq.Circuit(cirq.H(q))
    noisy_xop_a = NoisyOperation(
        ideal=cirq.Circuit(cirq.X(q)), real=np.zeros(shape=(4, 4)),
    )
    noisy_zop_a = NoisyOperation(
        ideal=cirq.Circuit(cirq.Z(q)), real=np.zeros(shape=(4, 4)),
    )
    rep_a = OperationRepresentation(
        ideal=ideal, basis_expansion={noisy_xop_a: 0.5, noisy_zop_a: 0.5},
    )
    noisy_xop_b = NoisyOperation(
        ideal=cirq.Circuit(cirq.X(q)), real=np.ones(shape=(4, 4)),
    )
    noisy_zop_b = NoisyOperation(
        ideal=cirq.Circuit(cirq.Z(q)), real=np.ones(shape=(4, 4)),
    )
    rep_b = OperationRepresentation(
        ideal=ideal, basis_expansion={noisy_xop_b: 0.5, noisy_zop_b: 0.5},
    )
    # Equal representation up to real superoperators
    assert rep_a == rep_b
    # Different ideal
    ideal_b = cirq.Circuit(cirq.X(q))
    rep_b = OperationRepresentation(
        ideal=ideal_b, basis_expansion={noisy_xop_b: 0.5, noisy_zop_b: 0.5},
    )
    assert rep_a != rep_b
    # Different type
    q_b = qiskit.QuantumRegister(1)
    ideal_b = qiskit.QuantumCircuit(q_b)
    ideal_b.x(q_b)
    rep_b = OperationRepresentation(
        ideal=ideal_b, basis_expansion={noisy_xop_b: 0.5, noisy_zop_b: 0.5},
    )
    assert rep_a != rep_b
    # Different length
    rep_b = OperationRepresentation(
        ideal=ideal, basis_expansion={noisy_xop_b: 0.5},
    )
    assert rep_a != rep_b
    # Different operations
    noisy_diff = NoisyOperation(ideal=cirq.Circuit(cirq.H(q)))
    rep_b = OperationRepresentation(
        ideal=ideal, basis_expansion={noisy_xop_b: 0.5, noisy_diff: 0.5},
    )
    assert rep_a != rep_b
    # Different coefficients
    rep_b = OperationRepresentation(
        ideal=ideal, basis_expansion={noisy_xop_b: 0.7, noisy_zop_b: 0.5},
    )
    assert rep_a != rep_b
Beispiel #8
0
def get_test_representation():
    ideal = cirq.Circuit(cirq.H(cirq.LineQubit(0)))

    noisy_xop = NoisyOperation.from_cirq(
        ideal=cirq.X, real=np.zeros(shape=(4, 4))
    )
    noisy_zop = NoisyOperation.from_cirq(
        ideal=cirq.Z, real=np.zeros(shape=(4, 4))
    )

    decomp = OperationRepresentation(
        ideal=ideal, basis_expansion={noisy_xop: 0.5, noisy_zop: -0.5}
    )
    return ideal, noisy_xop, noisy_zop, decomp
Beispiel #9
0
def test_print_cirq_operation_representation():
    ideal = cirq.Circuit(cirq.H(cirq.LineQubit(0)))

    noisy_xop = NoisyOperation.from_cirq(circuit=cirq.X,
                                         channel_matrix=np.zeros(shape=(4, 4)))
    noisy_zop = NoisyOperation.from_cirq(circuit=cirq.Z,
                                         channel_matrix=np.zeros(shape=(4, 4)))
    # Positive first coefficient
    decomp = OperationRepresentation(
        ideal=ideal,
        basis_expansion={
            noisy_xop: 0.5,
            noisy_zop: 0.5,
        },
    )
    expected = r"0: ───H─── = 0.500*(0: ───X───)+0.500*(0: ───Z───)"
    assert str(decomp) == expected
    # Negative first coefficient
    decomp = OperationRepresentation(
        ideal=ideal,
        basis_expansion={
            noisy_xop: -0.5,
            noisy_zop: 1.5,
        },
    )
    expected = r"0: ───H─── = -0.500*(0: ───X───)+1.500*(0: ───Z───)"
    assert str(decomp) == expected
    # Empty representation
    decomp = OperationRepresentation(ideal=ideal, basis_expansion={})
    expected = r"0: ───H─── = 0.000"
    assert str(decomp) == expected
    # Small coefficient approximation
    decomp = OperationRepresentation(
        ideal=ideal,
        basis_expansion={
            noisy_xop: 1.00001,
            noisy_zop: 0.00001
        },
    )
    expected = r"0: ───H─── = 1.000*(0: ───X───)"
    assert str(decomp) == expected
    # Small coefficient approximation different position
    decomp = OperationRepresentation(
        ideal=ideal,
        basis_expansion={
            noisy_xop: 0.00001,
            noisy_zop: 1.00001
        },
    )
    expected = r"0: ───H─── = 1.000*(0: ───Z───)"
    # Small coefficient approximation different position
    decomp = OperationRepresentation(
        ideal=ideal,
        basis_expansion={noisy_xop: 0.00001},
    )
    expected = r"0: ───H─── = 0.000"
    assert str(decomp) == expected
Beispiel #10
0
def test_print_cirq_operation_representation():
    ideal = cirq.Circuit(cirq.H(cirq.LineQubit(0)))

    noisy_xop = NoisyOperation.from_cirq(
        ideal=cirq.X, real=np.zeros(shape=(4, 4))
    )
    noisy_zop = NoisyOperation.from_cirq(
        ideal=cirq.Z, real=np.zeros(shape=(4, 4))
    )

    decomp = OperationRepresentation(
        ideal=ideal, basis_expansion={noisy_xop: 0.5, noisy_zop: 0.5, },
    )

    expected = r"0: ───H─── = 0.500*0: ───X───+0.500*0: ───Z───"
    assert str(decomp) == expected
Beispiel #11
0
def represent_operation_with_global_depolarizing_noise(
        ideal_operation: QPROGRAM,
        noise_level: float) -> OperationRepresentation:
    r"""As described in [Temme2017]_, this function maps an
    ``ideal_operation`` :math:`\mathcal{U}` into its quasi-probability
    representation, which is a linear combination of noisy implementable
    operations :math:`\sum_\alpha \eta_{\alpha} \mathcal{O}_{\alpha}`.

    This function assumes a depolarizing noise model and, more precicely,
    that the following noisy operations are implementable
    :math:`\mathcal{O}_{\alpha} = \mathcal{D} \circ \mathcal P_\alpha
    \circ \mathcal{U}`, where :math:`\mathcal{U}` is the unitary associated
    to the input ``ideal_operation`` acting on :math:`k` qubits,
    :math:`\mathcal{P}_\alpha` is a Pauli operation and
    :math:`\mathcal{D}(\rho) = (1 - \epsilon) \rho + \epsilon I/2^k` is a
    depolarizing channel (:math:`\epsilon` is a simple function of
    ``noise_level``).

    For a single-qubit ``ideal_operation``, the representation is as
    follows:

    .. math::
         \mathcal{U}_{\beta} = \eta_1 \mathcal{O}_1 + \eta_2 \mathcal{O}_2 +
                               \eta_3 \mathcal{O}_3 + \eta_4 \mathcal{O}_4

    .. math::
        \eta_1 =1 + \frac{3}{4} \frac{\epsilon}{1- \epsilon},
        \qquad \mathcal{O}_1 = \mathcal{D} \circ \mathcal{I} \circ \mathcal{U}

        \eta_2 =- \frac{1}{4}\frac{\epsilon}{1- \epsilon} , \qquad
        \mathcal{O}_2 = \mathcal{D} \circ \mathcal{X} \circ \mathcal{U}

        \eta_3 =- \frac{1}{4}\frac{\epsilon}{1- \epsilon} , \qquad
        \mathcal{O}_3 = \mathcal{D} \circ \mathcal{Y} \circ \mathcal{U}

        \eta_4 =- \frac{1}{4}\frac{\epsilon}{1- \epsilon} , \qquad
        \mathcal{O}_4 = \mathcal{D} \circ \mathcal{Z} \circ \mathcal{U}

    It was proven in [Takagi2020]_ that, under suitable assumptions,
    this representation is optimal (minimum 1-norm).

    Args:
        ideal_operation: The ideal operation (as a QPROGRAM) to represent.
        noise_level: The noise level (as a float) of the depolarizing channel.

    Returns:
        The quasi-probability representation of the ``ideal_operation``.

    .. note::
        This representation is based on the ideal assumption that one
        can append Pauli gates to a noisy operation without introducing
        additional noise. For a beckend which violates this assumption,
        it remains a good approximation for small values of ``noise_level``.

    .. note::
        The input ``ideal_operation`` is typically a QPROGRAM with a single
        gate but could also correspond to a sequence of more gates.
        This is possible as long as the unitary associated to the input
        QPROGRAM, followed by a single final depolarizing channel, is
        physically implementable.
    """
    circ, in_type = convert_to_mitiq(ideal_operation)

    post_ops: List[List[Operation]]
    qubits = circ.all_qubits()

    # The single-qubit case: linear combination of 1Q Paulis
    if len(qubits) == 1:
        q = tuple(qubits)[0]

        epsilon = 4 / 3 * noise_level
        alpha_pos = 1 + ((3 / 4) * epsilon / (1 - epsilon))
        alpha_neg = -(1 / 4) * epsilon / (1 - epsilon)

        alphas = [alpha_pos] + 3 * [alpha_neg]
        post_ops = [[]]  # for alpha_pos, we do nothing, rather than I
        post_ops += [[P(q)] for P in [X, Y, Z]]  # 1Q Paulis

    # The two-qubit case: linear combination of 2Q Paulis
    elif len(qubits) == 2:
        q0, q1 = qubits

        epsilon = 16 / 15 * noise_level
        alpha_pos = 1 + ((15 / 16) * epsilon / (1 - epsilon))
        alpha_neg = -(1 / 16) * epsilon / (1 - epsilon)

        alphas = [alpha_pos] + 15 * [alpha_neg]
        post_ops = [[]]  # for alpha_pos, we do nothing, rather than I x I
        post_ops += [[P(q0)] for P in [X, Y, Z]]  # 1Q Paulis for q0
        post_ops += [[P(q1)] for P in [X, Y, Z]]  # 1Q Paulis for q1
        post_ops += [[Pi(q0), Pj(q1)] for Pi in [X, Y, Z]
                     for Pj in [X, Y, Z]]  # 2Q Paulis

    else:
        raise ValueError("Can only represent single- and two-qubit gates."
                         "Consider pre-compiling your circuit.")

    # Basis of implementable operations as circuits
    imp_op_circuits = [circ + Circuit(op) for op in post_ops]

    # Convert back to input type
    imp_op_circuits = [convert_from_mitiq(c, in_type) for c in imp_op_circuits]

    # Build basis_expantion
    expansion = {NoisyOperation(c): a for c, a in zip(imp_op_circuits, alphas)}

    return OperationRepresentation(ideal_operation, expansion)
Beispiel #12
0
def represent_operation_with_local_depolarizing_noise(
        ideal_operation: QPROGRAM,
        noise_level: float) -> OperationRepresentation:
    r"""As described in [Temme2017]_, this function maps an
    ``ideal_operation`` :math:`\mathcal{U}` into its quasi-probability
    representation, which is a linear combination of noisy implementable
    operations :math:`\sum_\alpha \eta_{\alpha} \mathcal{O}_{\alpha}`.

    This function assumes a (local) single-qubit depolarizing noise model even
    for multi-qubit operations. More precicely, it assumes that the following
    noisy operations are implementable :math:`\mathcal{O}_{\alpha} =
    \mathcal{D}^{\otimes k} \circ \mathcal P_\alpha \circ \mathcal{U}`,
    where :math:`\mathcal{U}` is the unitary associated
    to the input ``ideal_operation`` acting on :math:`k` qubits,
    :math:`\mathcal{P}_\alpha` is a Pauli operation and
    :math:`\mathcal{D}(\rho) = (1 - \epsilon) \rho + \epsilon I/2` is a
    single-qubit depolarizing channel (:math:`\epsilon` is a simple function
    of ``noise_level``).

    More information about the quasi-probability representation for a
    depolarizing noise channel can be found in:
    :func:`represent_operation_with_global_depolarizing_noise`.

    Args:
        ideal_operation: The ideal operation (as a QPROGRAM) to represent.
        noise_level: The noise level of each depolarizing channel.

    Returns:
        The quasi-probability representation of the ``ideal_operation``.

    .. note::
        The input ``ideal_operation`` is typically a QPROGRAM with a single
        gate but could also correspond to a sequence of more gates.
        This is possible as long as the unitary associated to the input
        QPROGRAM, followed by a single final depolarizing channel, is
        physically implementable.

    .. [Temme2017] : Kristan Temme, Sergey Bravyi, Jay M. Gambetta,
        "Error mitigation for short-depth quantum circuits,"
        *Phys. Rev. Lett.* **119**, 180509 (2017),
        (https://arxiv.org/abs/1612.02058).
    """
    circ, in_type = convert_to_mitiq(ideal_operation)

    qubits = circ.all_qubits()

    if len(qubits) == 1:
        return represent_operation_with_global_depolarizing_noise(
            ideal_operation,
            noise_level,
        )

    # The two-qubit case: tensor product of two depolarizing channels.
    elif len(qubits) == 2:
        q0, q1 = qubits

        # Single-qubit representation coefficients.
        epsilon = noise_level * 4 / 3
        c_neg = -(1 / 4) * epsilon / (1 - epsilon)
        c_pos = 1 - 3 * c_neg

        imp_op_circuits = []
        alphas = []

        # The zero-pauli term in the linear combination
        imp_op_circuits.append(circ)
        alphas.append(c_pos * c_pos)

        # The single-pauli terms in the linear combination
        for qubit in qubits:
            for pauli in [X, Y, Z]:
                imp_op_circuits.append(circ + Circuit(pauli(qubit)))
                alphas.append(c_neg * c_pos)

        # The two-pauli terms in the linear combination
        for pauli_0, pauli_1 in product([X, Y, Z], repeat=2):
            imp_op_circuits.append(circ + Circuit(pauli_0(q0), pauli_1(q1)))
            alphas.append(c_neg * c_neg)

    else:
        raise ValueError("Can only represent single- and two-qubit gates."
                         "Consider pre-compiling your circuit.")

    # Convert back to input type
    imp_op_circuits = [convert_from_mitiq(c, in_type) for c in imp_op_circuits]

    # Build basis_expantion
    expansion = {NoisyOperation(c): a for c, a in zip(imp_op_circuits, alphas)}

    return OperationRepresentation(ideal_operation, expansion)