Source code for qlearnkit.algorithms.qknn.qknn_base

import numbers
from abc import ABC
from typing import List, Dict, Optional, Union

import numpy as np
import logging

from qiskit import QuantumCircuit
from qiskit.providers import BaseBackend, Backend
from qiskit.result import Result
from qiskit.tools import parallel_map
from qiskit.utils import QuantumInstance

from ..quantum_estimator import QuantumEstimator
from ...circuits import SwaptestCircuit
from ...encodings import EncodingMap

logger = logging.getLogger(__name__)


[docs]class QNeighborsBase(QuantumEstimator, ABC): def __init__(self, n_neighbors: int = 3, encoding_map: Optional[EncodingMap] = None, quantum_instance: Optional[Union[QuantumInstance, BaseBackend, Backend]] = None): """ Base class for Nearest Neighbors algorithms Args: n_neighbors: number of neighbors participating in the majority vote encoding_map: map to classical data to quantum states. This class does not impose any constraint on it. quantum_instance: the quantum instance to set. Can be a :class:`~qiskit.utils.QuantumInstance`, a :class:`~qiskit.providers.Backend` or a :class:`~qiskit.providers.BaseBackend` """ super().__init__(encoding_map, quantum_instance) if n_neighbors <= 0: raise ValueError(f"Expected n_neighbors > 0. Got {n_neighbors}") elif not isinstance(n_neighbors, numbers.Integral): raise TypeError( "n_neighbors does not take %s value, enter integer value" % type(n_neighbors) ) self.n_neighbors = n_neighbors
[docs] def fit(self, X, y): """ Fits the model using X as training dataset and y as training labels Args: X: training dataset y: training labels """ self.X_train = np.asarray(X) self.y_train = np.asarray(y) logger.info("setting training data: ") for _X, _y in zip(X, y): logger.info("%s: %s", _X, _y)
def _compute_fidelity(self, counts: Dict[str, int]): r""" Computes the fidelity, used as a measure of distance, from a dictionary of counts, which refers to the swap test circuit having a test datapoint and a train datapoint as inputs employing the following formula .. math:: \sqrt{\abs{\frac{counts[0] - counts[1]}{n\_shots}}} Args: counts: the counts resulting after the simulation Returns: the computed fidelity """ counts_0 = counts.get('0', 0) counts_1 = counts.get('1', 0) return np.sqrt(np.abs(counts_0 - counts_1)/self._quantum_instance.run_config.shots) def _get_fidelities(self, results: Result, test_size: int) -> np.ndarray: r""" Retrieves the list of all fidelities given the circuit results, computed via the :func:`calculate_fidelities` method Args: results: the simulation results test_size: the size of the test dataset Returns: numpy ndarray of all fidelities """ train_size = self.X_train.shape[0] all_counts = results.get_counts() # List[Dict(str, int)] fidelities = np.empty( shape=(test_size, train_size) ) for i, (counts) in enumerate(all_counts): fidelity = self._compute_fidelity(counts) # the i-th subarray of the ndarray `fidelities` contains # the values that we will use for the majority voting to # predict the label of the i-th test input data fidelities[i // train_size][i % train_size] = fidelity return fidelities @staticmethod def _construct_circuit(feature_vector_1: np.ndarray, feature_vector_2: np.ndarray, encoding_map: EncodingMap = None) -> QuantumCircuit: r""" Constructs a swap test circuit employing a controlled swap. For instance .. parsed-literal:: ┌───┐ ┌───┐┌─┐ q_0: ────┤ H ├─────■─┤ H ├┤M├ ┌───┴───┴───┐ │ └───┘└╥┘ q_1: ┤ circuit-0 ├─X───────╫─ ├───────────┤ │ ║ q_2: ┤ circuit-1 ├─X───────╫─ └───────────┘ ║ c: 1/══════════════════════╩═ 0 where feature_vector_1 = [1,0], feature_vector_2 = [0, 1] A swap test circuit allows to measure the fidelity between two quantum states, which can be interpreted as a distance measure of some sort. In other words, given two quanutm states :math:`|\alpha\rangle, \ |\beta\rangle` it measures how symmetric the state :math:`|\alpha\rangle \otimes |\beta\rangle` is Args: feature_vector_1: first feature vector feature_vector_2: second feature vector encoding_map: the mapping to quantum state to extract a :class:`~qiskit.QuantumCircuit` Returns: swap test circuit """ if len(feature_vector_1) != len(feature_vector_2): raise ValueError("Input state vectors must have same length to" "perform swap test. Lengths were:" f"{len(feature_vector_1)}" f"{len(feature_vector_2)}") if encoding_map is None: raise ValueError("encoding map must be specified to construct" "swap test circuit") qc_1 = encoding_map.circuit(feature_vector_1) qc_2 = encoding_map.circuit(feature_vector_2) qc_swap = SwaptestCircuit(qc_1, qc_2) return qc_swap def _construct_circuits(self, X_test: np.ndarray) -> List[QuantumCircuit]: """ Creates the circuits to be executed on the gated quantum computer for the classification process Args: X_test: the unclassified input data """ logger.info("Starting parallel circuits construction ...") circuits = [] for i, xtest in enumerate(X_test): # computing distance of xtest with respect to # each point in X_train circuits_line = parallel_map( QNeighborsBase._construct_circuit, self.X_train, task_args=[xtest, self._encoding_map] ) circuits = circuits + circuits_line logger.info("Done.") return circuits def _kneighbors(self, y_train: np.ndarray, fidelities: np.ndarray, *, return_indices=False): """ Retrieves the training labels associated to the :math:`k` nearest neighbors and (optionally) their indices Args: y_train: the training labels fidelities: the fidelities array return_indices: (bool) weather to return the indices or not Returns: neigh_labels: ndarray of shape (n_queries, n_neighbors) Array representing the labels of the :math:`k` nearest points neigh_indices: ndarray of shape (n_queries, n_neighbors) Array representing the indices of the :math:`k` nearest points, only present if return_indices=True. """ if np.any(fidelities < -0.2) or np.any(fidelities > 1.2): raise ValueError("Detected fidelities values not in range 0<=F<=1:" f"{fidelities[fidelities < -0.2]}" f"{fidelities[fidelities > 1.2]}") # sklearn naming n_queries, _ = fidelities.shape # extracting indices of the k nearest neighbors # from the sorted fidelities neigh_indices = np.argsort(fidelities, axis=1)[:, -self.n_neighbors:] neigh_labels = y_train[neigh_indices] if return_indices: return neigh_labels, neigh_indices else: return neigh_labels