diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 0000bc1f4..413a7802d 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -12,7 +12,7 @@ on: jobs: tests: - runs-on: ubuntu-latest + runs-on: macos-latest timeout-minutes: 30 steps: - uses: actions/checkout@v4 diff --git a/circuit_knitting/utils/conversion.py b/circuit_knitting/utils/conversion.py deleted file mode 100644 index 63a88ef6a..000000000 --- a/circuit_knitting/utils/conversion.py +++ /dev/null @@ -1,152 +0,0 @@ -# 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. - -""" -Code for converting types of distributions. - -.. currentmodule:: circuit_knitting.utils.conversion - -.. autosummary:: - :toctree: ../stubs/ - - quasi_to_real - nearest_probability_distribution - naive_probability_distribution - dict_to_array -""" - -import numpy as np -from qiskit.utils.deprecation import deprecate_func - - -@deprecate_func( - removal_timeline="no sooner than CKT v0.8.0", - since="0.7.0", - package_name="circuit-knitting-toolbox", -) -def quasi_to_real(quasiprobability, mode): - """ - Convert a quasi probability to a valid probability distribution. - - Args: - quasiprobability: The array of quasiprobabilities - mode: How to compute the new distribution, either 'nearest' or 'naive' - - Returns: - The converted probability distribution - """ - if mode == "nearest": - return nearest_probability_distribution(quasiprobability=quasiprobability) - elif mode == "naive": - return naive_probability_distribution(quasiprobability=quasiprobability) - else: - raise NotImplementedError("%s conversion is not implemented" % mode) - - -@deprecate_func( - removal_timeline="no sooner than CKT v0.8.0", - since="0.7.0", - package_name="circuit-knitting-toolbox", -) -def nearest_probability_distribution(quasiprobability): - """ - Convert quasiprobability distribution to the nearest probability distribution. - - Takes a quasiprobability distribution and maps - it to the closest probability distribution as defined by - the L2-norm. - - Method from Smolin et al., Phys. Rev. Lett. 108, 070502 (2012). - - Args: - quasiprobability: The input quasiprobabilities - - Returns: - The converted probability distribution - """ - sorted_probs, states = zip( - *sorted(zip(quasiprobability, range(len(quasiprobability)))) - ) - num_elems = len(sorted_probs) - new_probs = np.zeros(num_elems) - beta = 0 - diff = 0 - for state, prob in zip(states, sorted_probs): - temp = prob + beta / num_elems - if temp < 0: - beta += prob - num_elems -= 1 - diff += prob * prob - else: - diff += (beta / num_elems) * (beta / num_elems) - new_probs[state] = prob + beta / num_elems - return new_probs - - -@deprecate_func( - removal_timeline="no sooner than CKT v0.8.0", - since="0.7.0", - package_name="circuit-knitting-toolbox", -) -def naive_probability_distribution(quasiprobability): - """ - Convert quasiprobability dist to probability dist by zero-ing out negative values. - - Takes a quasiprobability distribution and does the following two steps: - 1. Update all negative probabilities to 0 - 2. Normalize - - Args: - quasiprobability: The input quasiprobabilities - - Returns: - The converted probability distribution - """ - new_probs = np.where(quasiprobability < 0, 0, quasiprobability) - new_probs /= np.sum(new_probs) - return new_probs - - -@deprecate_func( - removal_timeline="no sooner than CKT v0.8.0", - since="0.7.0", - package_name="circuit-knitting-toolbox", -) -def dict_to_array(distribution_dict, force_prob): - """ - Convert dictionary of shot results to array of distribution. - - Args: - distribution_dict: The dictionary containing the shot information - from circuit execution - force_prob: Whether to force the distribution to be normalized - - Returns: - The resulting probability information - """ - state = list(distribution_dict.keys())[0] - num_qubits = len(state) - num_shots = sum(distribution_dict.values()) - cnts = np.zeros(2**num_qubits, dtype=float) - for state in distribution_dict: - cnts[int(state, 2)] = distribution_dict[state] - if abs(sum(cnts) - num_shots) > 1: - print( - "dict_to_array may be wrong, converted counts = {}, input counts = {}".format( - sum(cnts), num_shots - ) - ) - if not force_prob: - return cnts - else: - prob = cnts / num_shots - assert abs(sum(prob) - 1) < 1e-10 - return prob diff --git a/circuit_knitting/utils/metrics.py b/circuit_knitting/utils/metrics.py deleted file mode 100644 index 354f56ca5..000000000 --- a/circuit_knitting/utils/metrics.py +++ /dev/null @@ -1,291 +0,0 @@ -# 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 - - -@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 - - -@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 - - -@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 - - -@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))) - - -@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 diff --git a/compose.yaml b/compose.yaml index d0df582b3..ada62cb74 100644 --- a/compose.yaml +++ b/compose.yaml @@ -7,7 +7,6 @@ services: notebook: build: . - platform: linux/amd64 restart: unless-stopped # The following line allows Ray to use /dev/shm rather than warn # about using /tmp diff --git a/pyproject.toml b/pyproject.toml index b7aa86a86..e383d4984 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -127,4 +127,4 @@ enable = [ [tool.pytest.ini_options] testpaths = ["./circuit_knitting/", "./test/"] -addopts = "--doctest-modules -rs --durations=10 --ignore=circuit_knitting/utils/metrics.py --ignore=circuit_knitting/cutting/cutqc" +addopts = "--doctest-modules -rs --durations=10 diff --git a/tox.ini b/tox.ini index cbca18fe4..d83e4eca8 100644 --- a/tox.ini +++ b/tox.ini @@ -52,7 +52,7 @@ commands = coverage3 run --source circuit_knitting --parallel-mode -m pytest --run-slow test/ --coverage {posargs} coverage3 combine coverage3 html - coverage3 report --fail-under=100 --show-missing --omit="circuit_knitting/cutting/cutqc/**/*,circuit_knitting/utils/conversion.py,circuit_knitting/utils/metrics.py" + coverage3 report --fail-under=100 --show-missing [testenv:docs] extras =