Source code for circuit_knitting.cutting.cutqc.wire_cutting_verification

# This code is a Qiskit project.

# (C) Copyright IBM 2022.

# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""File that contains the function to verify the results of the cut circuits."""

from __future__ import annotations

import copy
import psutil
from typing import Sequence

import numpy as np
from qiskit import QuantumCircuit
from qiskit.circuit import Qubit
from qiskit.quantum_info import Statevector
from qiskit_aer import Aer
from qiskit.utils.deprecation import deprecate_func

from ...utils.conversion import quasi_to_real
from ...utils.metrics import (
    chi2_distance,
    MSE,
    MAPE,
    cross_entropy,
    HOP,
)


[docs] @deprecate_func( removal_timeline="no sooner than CKT v0.8.0", since="0.7.0", package_name="circuit-knitting-toolbox", additional_msg="Use the wire cutting or automated cut-finding functionality in the ``circuit_knitting.cutting`` package. ", ) def verify( full_circuit: QuantumCircuit, reconstructed_output: np.ndarray, ) -> tuple[dict[str, dict[str, float]], Sequence[float]]: """ Compare the reconstructed probabilities to the ground truth. Executes the original circuit, then measures the distributional differences between this exact result (ground truth) and the reconstructed result from the subcircuits. Provides a variety of metrics to evaluate the differences in the distributions. Args: full_circuit: The original quantum circuit that was cut reconstructed_output: The reconstructed probability distribution from the execution of the subcircuits Returns: A tuple containing metrics for the ground truth and reconstructed distributions """ ground_truth = _evaluate_circuit(circuit=full_circuit) metrics = {} for quasi_conversion_mode in ["nearest", "naive"]: real_probability = quasi_to_real( quasiprobability=reconstructed_output, mode=quasi_conversion_mode ) chi2 = chi2_distance(target=ground_truth, obs=real_probability) mse = MSE(target=ground_truth, obs=real_probability) mape = MAPE(target=ground_truth, obs=real_probability) ce = cross_entropy(target=ground_truth, obs=real_probability) hop = HOP(target=ground_truth, obs=real_probability) metrics[quasi_conversion_mode] = { "chi2": chi2, "Mean Squared Error": mse, "Mean Absolute Percentage Error": mape, "Cross Entropy": ce, "HOP": hop, } return metrics, ground_truth
@deprecate_func( removal_timeline="no sooner than CKT v0.8.0", since="0.7.0", package_name="circuit-knitting-toolbox", additional_msg="Use the wire cutting or automated cut-finding functionality in the ``circuit_knitting.cutting`` package. ", ) def generate_reconstructed_output( full_circuit: QuantumCircuit, subcircuits: Sequence[QuantumCircuit], unordered: np.ndarray, smart_order: Sequence[int], complete_path_map: dict[Qubit, Sequence[dict[str, int | Qubit]]], ) -> np.ndarray: """ Reorder the probability distribution. Args: full_circuit: The original uncut circuit subcircuits: The cut subcircuits unordered: The unordered results of the subcircuits smart_order: The correct ordering of the subcircuits complete_path_map: The path map of the cuts, as defined from the cutting function Returns: The reordered and reconstructed probability distribution over the full circuit """ subcircuit_out_qubits: dict[int, list[Qubit]] = { subcircuit_idx: [] for subcircuit_idx in smart_order } for input_qubit in complete_path_map: path = complete_path_map[input_qubit] output_qubit = path[-1] subcircuit_out_qubits[output_qubit["subcircuit_idx"]].append( (output_qubit["subcircuit_qubit"], full_circuit.qubits.index(input_qubit)) ) for subcircuit_idx in subcircuit_out_qubits: subcircuit_out_qubits[subcircuit_idx] = sorted( subcircuit_out_qubits[subcircuit_idx], key=lambda x: subcircuits[subcircuit_idx].qubits.index(x[0]), reverse=True, ) subcircuit_out_qubits[subcircuit_idx] = [ x[1] for x in subcircuit_out_qubits[subcircuit_idx] ] unordered_qubit: list[int] = [] for subcircuit_idx in smart_order: unordered_qubit += subcircuit_out_qubits[subcircuit_idx] reconstructed_output = np.zeros(len(unordered)) for unordered_state, unordered_p in enumerate(unordered): bin_unordered_state = bin(unordered_state)[2:].zfill(full_circuit.num_qubits) _, ordered_bin_state = zip( *sorted(zip(unordered_qubit, bin_unordered_state), reverse=True) ) ordered_bin_state_str = "".join([str(x) for x in ordered_bin_state]) ordered_state = int(ordered_bin_state_str, 2) reconstructed_output[ordered_state] = unordered_p return np.array(reconstructed_output) def _evaluate_circuit(circuit: QuantumCircuit) -> Sequence[float]: """ Compute exact probability vector of given circuit. Args: circuit: The circuit to simulate Returns: The final probability vector of the circuit """ max_memory_mb = psutil.virtual_memory().total >> 20 max_memory_mb = int(max_memory_mb / 4 * 3) simulator = Aer.get_backend( "aer_simulator_statevector", max_memory_mb=max_memory_mb ) circuit = copy.deepcopy(circuit) circuit.save_state() result = simulator.run(circuit).result() statevector = result.get_statevector(circuit) prob_vector = Statevector(statevector).probabilities() return prob_vector