Source code for mindquantum.algorithm.error_mitigation.virtual_distillation

# Copyright 2021 Huawei Technologies Co., Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ============================================================================
"""Virtual Distillation Algorithm for error mitigation."""

from typing import Callable, Dict
from mindquantum.core.circuit import Circuit, apply
from mindquantum.core.gates import Givens, SWAP
import numpy as np


[docs]def virtual_distillation( circ: Circuit, executor: Callable[[Circuit], Dict[str, int]], little_endian: bool = True, **kwargs ) -> np.ndarray: """ Error mitigation algorithm based on virtual distillation (arXiv:2011.07064). The algorithm calculates error-mitigated expectation values of Z_i Pauli operators for each qubit i. To measure expectation values of other Pauli operators (X_i or Y_i), appropriate basis rotation gates should be added at the end of the input circuit: - For X_i measurements: Add H gate on qubit i - For Y_i measurements: Add RX(π/2) gate on qubit i Args: circ (Circuit): The quantum circuit to be executed executor (Callable[[Circuit], dict[str, int]]): A callable object that executes quantum circuits and returns a dictionary mapping measurement result bitstrings to their counts. Note: executor must be able to handle twice the number of qubits as the input circuit little_endian (bool): Whether the bitstring returned by executor is little-endian. Default: True **kwargs: Additional arguments to be passed to the executor. Returns: np.ndarray, Error-mitigated expectation values <Z_i> for each qubit i. To obtain expectation values of other Pauli operators, add appropriate basis rotation gates to the input circuit before calling this function. Examples: >>> circ = Circuit([X.on(0), RY(1).on(1)]) >>> sim = Simulator('mqvector', 4) # Double number of qubits >>> def executor(circ): ... res_dict = sim.sampling(circ, shots=10000).data ... return res_dict >>> result = virtual_distillation(circ, executor) # Returns <Z_i> >>> # To measure <X_1>: >>> circ_x = circ + H.on(1) >>> result_x = virtual_distillation(circ_x, executor) """ circ_ = circ.remove_measure() num_qubits = circ_.n_qubits # Build extended circuit extended_circuit = Circuit() extended_circuit += apply(circ_, list(range(num_qubits))) extended_circuit += apply(circ_, [i + num_qubits for i in range(num_qubits)]) # Add Givens rotations and SWAP gates for qubit_idx in range(num_qubits): extended_circuit += Givens(np.pi / 4).on([qubit_idx, qubit_idx + num_qubits]) extended_circuit += SWAP([qubit_idx, qubit_idx + num_qubits]) extended_circuit.measure_all() expectations = np.zeros(num_qubits) denominator = 0 # Execute measurements and statistics counts_dict = executor(extended_circuit, **kwargs) # Parse measurement results for bitstring, count in counts_dict.items(): if little_endian: z1 = np.array([1 if bit == '0' else -1 for bit in bitstring[num_qubits - 1 :: -1]]) z2 = np.array([1 if bit == '0' else -1 for bit in bitstring[2 * num_qubits - 1 : num_qubits - 1 : -1]]) else: z1 = np.array([1 if bit == '0' else -1 for bit in bitstring[:num_qubits]]) z2 = np.array([1 if bit == '0' else -1 for bit in bitstring[num_qubits:]]) # Calculate expectation values for i in range(num_qubits): parity = 1 for j in range(num_qubits): if j != i: parity *= (1 + z1[j] - z2[j] + z1[j] * z2[j]) / 2 expectations[i] += (z1[i] + z2[i]) * parity * count / 2 parity_i = (1 + z1[num_qubits - 1] - z2[num_qubits - 1] + z1[num_qubits - 1] * z2[num_qubits - 1]) / 2 denominator += parity * parity_i * count return expectations / denominator