Source code for circuit_knitting.utils.metrics

# 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.

"""
Functions for comparing array distances.

.. currentmodule:: circuit_knitting.utils.metrics

.. autosummary::
   :toctree: ../stubs

   chi2_distance
   MSE
   MAPE
   cross_entropy
   HOP
"""

import copy

import numpy as np

from qiskit.utils.deprecation import deprecate_func


[docs] @deprecate_func( removal_timeline="no sooner than CKT v0.8.0", since="0.7.0", package_name="circuit-knitting-toolbox", ) def chi2_distance(target, obs): # noqa: D301 r""" Measure the Chi-square distance. The Chi-Square distance is a measure of statistically correlation between two feature vectors and is defined as $ \sum_i \frac{(x_i - y_i)^2}{x_i + y_i}$. Examples: >>> chi2_distance(np.array([0.1, 0.1, 0.3, 0.5]), np.array([0.25, 0.25, 0.25, 0.25])) 0.21645021645021645 >>> chi2_distance(np.array([0.25, 0.25, 0.25, 0.25]), np.array([0.25, 0.25, 0.25, 0.25])) 0 Args: target: The target feature vector obs: The actually observed feature vector Returns: The computed distance Raises: Exception: The target is not a numpy array or dictionary """ target = copy.deepcopy(target) obs = copy.deepcopy(obs) obs = np.absolute(obs) if isinstance(target, np.ndarray): assert len(target) == len(obs) distance = 0 for t, o in zip(target, obs): if abs(t - o) > 1e-10: distance += np.power(t - o, 2) / (t + o) elif isinstance(target, dict): distance = 0 for o_idx, o in enumerate(obs): if o_idx in target: t = target[o_idx] if abs(t - o) > 1e-10: distance += np.power(t - o, 2) / (t + o) else: distance += o else: raise Exception("Illegal target type:", type(target)) return distance
[docs] @deprecate_func( removal_timeline="no sooner than CKT v0.8.0", since="0.7.0", package_name="circuit-knitting-toolbox", ) def MSE(target, obs): # noqa: D301 r""" Compute the Mean Squared Error (MSE). The MSE is a common metric in fields such as deep learning and is used to measure the squared distance between two vectors via: $\sum_i (x_i - y_i)^2$. Example: >>> MSE(np.array([0.1, 0.1, 0.3, 0.5]), np.array([0.25, 0.25, 0.25, 0.25])) 0.0275 Args: target: The target feature vector obs: The actually observed feature vector Returns: The computed MSE Raises: Exception: The target is not a dict Exception: The target and obs are not numpy arrays Exception: The target is not a numpy array and the obs are not a dict """ target = copy.deepcopy(target) obs = copy.deepcopy(obs) if isinstance(target, dict): se = 0 for t_idx in target: t = target[t_idx] o = obs[t_idx] se += (t - o) ** 2 mse = se / len(obs) elif isinstance(target, np.ndarray) and isinstance(obs, np.ndarray): target = target.reshape(-1, 1) obs = obs.reshape(-1, 1) squared_diff = (target - obs) ** 2 se = np.sum(squared_diff) mse = np.mean(squared_diff) elif isinstance(target, np.ndarray) and isinstance(obs, dict): se = 0 for o_idx in obs: o = obs[o_idx] t = target[o_idx] se += (t - o) ** 2 mse = se / len(obs) else: raise Exception("target type : %s" % type(target)) return mse
[docs] @deprecate_func( removal_timeline="no sooner than CKT v0.8.0", since="0.7.0", package_name="circuit-knitting-toolbox", ) def MAPE(target, obs): # noqa: D301 r""" Compute the Mean Absolute Percentage Error (MAPE). The MAPE is a scaled metric in the range [0, 100] defining the percentage difference between two vectors via: $ \sum_i \frac{x_i - y_i}{x_i} $. Example: >>> MAPE(np.array([0.1, 0.1, 0.3, 0.5]), np.array([0.25, 0.25, 0.25, 0.25])) 91.66666666666659 Args: target: The target feature vector obs: The actually observed feature vector Returns: The computed MAPE Raises: Exception: The target is not a dict Exception: The target and obs are not numpy arrays Exception: The target is not a numpy array and the obs are not a dict """ target = copy.deepcopy(target) obs = copy.deepcopy(obs) epsilon = 1e-16 if isinstance(target, dict): curr_sum = np.sum(list(target.values())) new_sum = curr_sum + epsilon * len(target) mape = 0 for t_idx in target: t = (target[t_idx] + epsilon) / new_sum o = obs[t_idx] mape += abs((t - o) / t) mape /= len(obs) elif isinstance(target, np.ndarray) and isinstance(obs, np.ndarray): target = target.flatten() target += epsilon target /= np.sum(target) obs = obs.flatten() obs += epsilon obs /= np.sum(obs) mape = np.abs((target - obs) / target) mape = np.mean(mape) elif isinstance(target, np.ndarray) and isinstance(obs, dict): curr_sum = np.sum(list(target.values())) new_sum = curr_sum + epsilon * len(target) mape = 0 for o_idx in obs: o = obs[o_idx] t = (target[o_idx] + epsilon) / new_sum mape += abs((t - o) / t) mape /= len(obs) else: raise Exception("target type : %s" % type(target)) return mape * 100
[docs] @deprecate_func( removal_timeline="no sooner than CKT v0.8.0", since="0.7.0", package_name="circuit-knitting-toolbox", ) def cross_entropy(target, obs): # noqa: D301 r""" Compute the cross entropy between two distributions. The cross entropy is a measure of the difference between two probability distributions, defined via: $ -\sum_i x_i \log y_i $. Example: >>> cross_entropy(np.array([0.1, 0.1, 0.3, 0.5]), np.array([0.25, 0.25, 0.25, 0.25])) 1.3862943611198906 Args: target: The target feature vector obs: The actually observed feature vector Returns: The computed cross entropy Raises: Exception: The target is not a dict Exception: The target and obs are not numpy arrays Exception: The target is not a numpy array and the obs are not a dict """ target = copy.deepcopy(target) obs = copy.deepcopy(obs) if isinstance(target, dict): CE = 0 for t_idx in target: t = target[t_idx] o = obs[t_idx] o = o if o > 1e-16 else 1e-16 CE += -t * np.log(o) return CE elif isinstance(target, np.ndarray) and isinstance(obs, np.ndarray): obs = np.clip(obs, a_min=1e-16, a_max=None) CE = np.sum(-target * np.log(obs)) return CE elif isinstance(target, np.ndarray) and isinstance(obs, dict): CE = 0 for o_idx in obs: o = obs[o_idx] t = target[o_idx] o = o if o > 1e-16 else 1e-16 CE += -t * np.log(o) return CE else: raise Exception("target type : %s, obs type : %s" % (type(target), type(obs)))
[docs] @deprecate_func( removal_timeline="no sooner than CKT v0.8.0", since="0.7.0", package_name="circuit-knitting-toolbox", ) def HOP(target, obs): """ Compute the Heavy Output Probability (HOP). The HOP is an important metric for quantum volume experiments and is defined at the probability that one measures a bitstring above the median target probability. Example: >>> HOP(np.array([0.1, 0.1, 0.3, 0.5]), np.array([0.25, 0.25, 0.25, 0.25])) 0.5 Args: target: The target feature vector obs: The actually observed feature vector Returns: The computed HOP """ target = copy.deepcopy(target) obs = copy.deepcopy(obs) target_median = np.median(target) hop = 0 for t, o in zip(target, obs): if t > target_median: hop += o return hop