Source code for mitiq.experimental.vd.vd
# Copyright (C) Unitary Foundation
#
# This source code is licensed under the GPL license (v3) found in the
# LICENSE file in the root directory of this source tree.
from collections.abc import Callable
import cirq
import numpy as np
import numpy.typing as npt
from mitiq import MeasurementResult
from mitiq.experimental.vd.vd_utils import (
_apply_diagonalizing_gate,
_copy_circuit_parallel,
)
[docs]
def construct_circuits(
circuit: cirq.Circuit,
) -> cirq.Circuit:
"""Constructs a circuit which contains two copies of the input circuit,
with an entangled gate applied between them.
Args:
circuit: The input circuit to copy.
"""
NUM_COPIES = 2
parallel_copied_circuit = _copy_circuit_parallel(circuit, NUM_COPIES)
entangled_circuit = _apply_diagonalizing_gate(
parallel_copied_circuit, NUM_COPIES
)
entangled_circuit.append(cirq.measure(entangled_circuit.all_qubits()))
return entangled_circuit
[docs]
def combine_results(
measurements: MeasurementResult,
) -> npt.NDArray[np.float64]:
r"""Process measurement results according to the virtual distillation
protocol.
Args:
measurements: Measurement results from circuit execution
Returns:
Array of error-mitigated expectation values for
:math:`\langle Z_i\rangle` observables.
"""
num_qubits = measurements.nqubits // 2
E = np.zeros(num_qubits)
D = 0
for result in measurements.asarray:
z1 = result[:num_qubits]
z2 = result[num_qubits:]
denom_product = 1
for j in range(num_qubits):
denom_product *= (
1
+ (-1) ** z1[j]
- (-1) ** z2[j]
+ (-1) ** z1[j] * (-1) ** z2[j]
)
D += (1 / (2**num_qubits)) * denom_product
for i in range(num_qubits):
num_product = 1
for j in range(num_qubits):
if j != i:
num_product *= (
1
+ (-1) ** z1[j]
- (-1) ** z2[j]
+ (-1) ** z1[j] * (-1) ** z2[j]
)
E[i] += (
(1 / (2**num_qubits))
* ((-1) ** z1[i] + (-1) ** z2[i])
* num_product
)
return E / D
[docs]
def execute_with_vd(
circuit: cirq.Circuit,
executor: Callable[[cirq.Circuit], MeasurementResult],
) -> list[float]:
r"""Given a circuit that acts on N qubits, this function returns the
expectation values of a given observable for each qubit i.
The expectation values are corrected using the virtual distillation
algorithm.
Args:
circuit: The input circuit of N qubits to execute with VD.
executor: An executor that executes a circuit and returns either a
density matrix, or a measurement result (bitstring).
Note:
Use an odd number of shots when using this technique. This prevents an
(unlikely) scenario where the normalization constant can be zero.
Returns:
A list of VD-estimated expectation values for
:math:`\langle Z_i\rangle`.
"""
vd_circuit = construct_circuits(circuit)
results = executor(vd_circuit)
return combine_results(results).tolist()