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)
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
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)
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)]))
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)
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))
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
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
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
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
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)
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)