Quantum kNN package

qiskit_quantum_knn.qknn.qkneighborsclassifier module

The quantum KNN algorithm.

class QKNeighborsClassifier(n_neighbors: int = 3, training_dataset: Optional[numpy.ndarray] = None, training_labels: Optional[numpy.ndarray] = None, test_dataset: Optional[numpy.ndarray] = None, data_points: Optional[numpy.ndarray] = None, quantum_instance: Optional[Union[qiskit.aqua.quantum_instance.QuantumInstance, qiskit.providers.basebackend.BaseBackend]] = None)[source]

Bases: qiskit.aqua.algorithms.quantum_algorithm.QuantumAlgorithm

Quantum KNN algorithm.

Maintains the construction of a QkNN Quantumcircuit, and manages the data corresponding with this circuit by setting up training and test data and maintaining the classes and labels to the data.

Parameters
  • n_neighbors (int) – number of neighbors to perform the voting. training_dataset (array-like): data shaped (n, d), with n the number of data points, and d the dimensionality. Corresponds to the training data, which is classified and will be used to classify new data. d must be a positive power of two, n not per se, because it can be zero-padded to fit on a quantum register.

  • training_labels (array) – the labels corresponding to the training data, must be len(n).

  • test_dataset (array-like) – data shaped (m, d), with m the the number of data points, and d the dimensionality. Describes test data which is used to test the algorithm and give an accuracy score.

    TODO: this is not implemented yet, for now a test is performed manually.

  • data_points (array-like) – data shaped (k, d), with k the number of data points, and d the dimensionality of the data. This is the unlabelled data which must be classified by the algorithm.

  • quantum_instance ( – class: QuantumInstance or :class: BaseBackend): the instance which qiskit will use to run the quantum algorithm.

Example

Classify data using the Iris dataset.

from qiskit_quantum_knn.qknn import QKNeighborsClassifier
from qiskit_quantum_knn.encoding import analog
from qiskit import aqua
from sklearn import datasets
import qiskit as qk

# initialising the quantum instance
backend = qk.BasicAer.get_backend('qasm_simulator')
instance = aqua.QuantumInstance(backend, shots=10000)

# initialising the qknn model
qknn = QKNeighborsClassifier(
   n_neighbors=3,
   quantum_instance=instance
)

n_variables = 2        # should be positive power of 2
n_train_points = 4     # can be any positive integer
n_test_points = 2      # can be any positive integer

# use iris dataset
iris = datasets.load_iris()
labels = iris.target
data_raw = iris.data

# encode data
encoded_data = analog.encode(data_raw[:, :n_variables])

# now pick these indices from the data
train_data = encoded_data[:n_train_points]
train_labels = labels[:n_train_points]

test_data = encoded_data[n_train_points:(n_train_points+n_test_points), :n_variables]
test_labels = labels[n_train_points:(n_train_points+n_test_points)]

qknn.fit(train_data, train_labels)
qknn_prediction = qknn.predict(test_data)

print(qknn_prediction)
print(test_labels)
[0 0]
[0 0]
fit(X, y)[source]

Fit the model using X as training data and y as target values

Notes

There is no real “fitting” done here, since the data cannot be stored somewhere. It only assigns the values so that these cane be accessed when running.

Parameters
  • X (array-like) – Training data of shape [n_samples, n_features].

  • y (array-like) – Target values of shape [n_samples].

static construct_circuit(state_to_classify: numpy.ndarray, oracle: qiskit.circuit.instruction.Instruction, add_measurement: bool = False) → qiskit.circuit.quantumcircuit.QuantumCircuit[source]

Construct one QkNN QuantumCircuit.

The Oracle provided is mentioned in Afham et al. (2020) as the parameter \(\mathcal{W}\), and is created via the method create_oracle().

Parameters
  • state_to_classify (array-like) – array of dimension N complex values describing the state to classify via kNN.

  • oracle (qiskit Instruction) – oracle \(\mathcal{W}\) for applying training data.

  • add_measurement (bool) – controls if measurements must be added to the classical registers.

Returns

The constructed circuit.

Return type

QuantumCircuit

static construct_circuits(data_to_predict, training_data) → qiskit.circuit.quantumcircuit.QuantumCircuit[source]

Constructs all quantum circuits for each datum to classify.

Parameters
  • data_to_predict (array) – data points, 2-D array, of shape (N, D), where N is the number of data points and D is the dimensionality of the vector. D should coincide with the provided training data.

  • training_data (array) – data points which you want to know the distance of between data_to_predict.

Returns

The constructed circuits.

Return type

numpy.ndarray

Raises

AquaError – Quantum instance is not present.

static execute_circuits(quantum_instance: Union[qiskit.aqua.quantum_instance.QuantumInstance, qiskit.providers.basebackend.BaseBackend], circuits) → qiskit.result.result.Result[source]

Executes the provided circuits (type array-like).

get_circuit_results(circuits, quantum_instance: Optional[Union[qiskit.aqua.quantum_instance.QuantumInstance, qiskit.providers.basebackend.BaseBackend]] = None) → qiskit.result.result.Result[source]

Get the qiskit Results from the provided quantum circuits.

static get_all_fidelities(circuit_results: qiskit.result.result.Result)[source]

Get all contrasts.

Gets the fidelity values which are calculated via calculate_fidelities() and saves these in an array. For more about fidelities, see calculate_fidelities().

Parameters

circuit_results (qiskit.result.Result) – the results from a QkNN circuit build using QKNeighborsClassifier.

Returns

all fidelities corresponding to the QkNN.

Return type

array

static calculate_fidelities(counts: Dict[str, int]) → numpy.ndarray[source]

Calculate the fidelities \(F_i\).

Calculates fidelities \(F_i\) for each training state i in the computational basis of the kNN QuantumCircuit. The fidelity can be calculated via:

\[F_i = \frac{M}{2} \left(p_0 (i) - p_1 (i)\right) \cdot \ \left(1 - \left( p(0) - p(1) \right) ^2 \right) + \ \left( p(0) - p(1) \right).\]

The values \(p(n)\) are the probabilities that the control qubit is in state \(n\), and the values \(p_n (i)\) are the probabilities that the computational basis is in state \(i\) given the control qubit is in state \(n\).

These values can be approximated by running the circuit \(T\) times using:

\[p_n (i) \sim \bar{p}_n (i) = c_n(i) / T_n , \ p (n) \sim \bar{p} (n) = T_n / T,\]

where \(c_n(i), T_n\) are the counts of the computational basis in state \(i\) given the control qubit in state \(n\) and the control qubit in state \(n\), respectively.

Parameters

counts (dict) – counts pulled from a qiskit Result from the QkNN.

Returns

the fidelity values.

ndarray of length n_samples with each index i (representing state \(|i\rangle\) from the computational basis) the fidelity belonging to \(|i\rangle\).

Return type

array

static calculate_contrasts(counts: Dict[str, int]) → numpy.ndarray[source]

Calculate contrasts \(q(i)\).

Calculates contrasts \(q(i)\) for each training state i in the computational basis of the KNN QuantumCircuit. The contrasts are according to Afham et al. (2020).

\[\begin{split}q(i) &= p_0(i) - p_1(i) \\ &= \frac{1 + F_i} {M + \sum_{j=1}^M F_j} - \ \frac{1 - F_i} {M - \sum_{j=1}^M F_j} \\ &= \frac{2(F_i - \langle F \rangle)} {M(1 - \langle F \rangle^2)},\end{split}\]

and correspond linearly to the fidelity \(F_i\) between the unclassified datum \(\psi\) and \(\phi_i\).

Parameters

counts (dict) – counts pulled from a qiskit Result from the QkNN.

Returns

the contrasts values.

ndarray of length n_samples with each index i (representing state \(|i\rangle\) from the computational basis) the contrast belonging to \(|i\rangle\).

Return type

array

static setup_control_counts(control_counts: Dict[str, int]) → Dict[str, int][source]

Sets up control counts dict.

In Qiskit, if a certain value is not measured (or counted), it has no appearance in the counts dictionary from the Result. Thus, this method checks if this has happened and adds a value with counts set to 0.

Notes

This means that if the control_counts has both occurrences of 0 and 1, this method just returns that exact same dictionary, unmodified.

Parameters
  • control_counts (dict) – dictionary from a Result

  • representing the control qubit in the QkNN circuit.

Returns

The modified control counts.

The same control_counts dict as provided but with non-counted occurrence added as well if needed.

Return type

dict

Raises

ValueError – if the provided dictionary does not coincide with the Result from the QkNN.

majority_vote(labels: numpy.ndarray, fidelities: numpy.ndarray) → numpy.ndarray[source]

Performs majority vote with the \(k\) nearest to determine class.

Parameters
  • labels (array-like) – The labels of the training data provided to the QKNeighborsClassifier.

  • fidelities (array-like) – The fidelities calculated using :meth:`get_all_fidelities’.

Returns

The labels resulted from the majority vote.

Return type

ndarray

property ret

Returns result.

Returns

return value(s).

Return type

Dict

predict(data) → numpy.ndarray[source]

Predict the labels of the provided data.

qiskit_quantum_knn.qknn.qknn_construction module

Construction of a QkNN QuantumCircuit.

create_qknn(state_to_classify: Union[List, numpy.ndarray], classified_states: Union[List, numpy.ndarray], add_measurement: bool = False) → qiskit.circuit.quantumcircuit.QuantumCircuit[source]

Construct one QkNN QuantumCircuit.

This method creates a circuit to perform distance measurements using quantum fidelity as distance metric Afham et al. (2020). It initialises one register with a state to classify, and uses an Oracle to act as QRAM to hold the training data. This Oracle writes all training data in superposition to a register. After that, a swap-test circuit Buhrman et al. (2020) is created to perform the fidelity measurement.

Example

Creating a circuit with simple data.

from qiskit_quantum_knn.qknn.qknn_construction import create_qknn

test_data = [1, 0]

train_data = [
    [1, 0],
    [0, 1]
]

circuit = create_qknn(test_data, train_data, add_measurement=True)
print(circuit.draw())
                                         ░ ┌───┐              ┌───┐ ░ ┌─┐   
          control_0: ────────────────────░─┤ H ├────────────■─┤ H ├─░─┤M├───
                     ┌─────────────────┐ ░ └───┘            │ └───┘ ░ └╥┘   
state_to_classify_0: ┤ INIT TEST STATE ├─░──────────────────X───────░──╫────
                     └─────────────────┘ ░      ┌─────────┐ │       ░  ║    
     train_states_0: ────────────────────░──────┤0        ├─X───────░──╫────
                                         ░ ┌───┐│  oracle │         ░  ║ ┌─┐
       comp_basis_0: ────────────────────░─┤ H ├┤1        ├─────────░──╫─┤M├
                                         ░ └───┘└─────────┘         ░  ║ └╥┘
     meas_control: 1/══════════════════════════════════════════════════╩══╬═
                                                                       0  ║ 
                                                                          ║ 
  meas_comp_basis: 1/═════════════════════════════════════════════════════╩═
                                                                          0 
Parameters
  • state_to_classify (numpy.ndarray) – array of dimension N complex values describing the state to classify via KNN.

  • classified_states (numpy.ndarray) – array containing M training samples of dimension N.

  • add_measurement (bool) – controls if measurements must be added to the classical registers.

Returns

the constructed circuit.

Return type

QuantumCircuit

construct_circuit(state_to_classify: numpy.ndarray, oracle: qiskit.circuit.instruction.Instruction, add_measurement: bool) → qiskit.circuit.quantumcircuit.QuantumCircuit[source]

Setup for a QkNN QuantumCircuit.

Constructs the QkNN QuantumCircuit according to the stepwise “instructions” in Afham et al. (2020). These instructions are:

  1. Initialisation:

    creates the registers and applies the unclassified datum \(\psi\) (see initialise_qknn());

  2. State transformation:

    applies \(H\)-gates and the Oracle \(\mathcal{W}\) to the circuit, and applies the \(SWAP\)-test (see state_transformation());

  3. Adding measurments:

    add the measurement gates to the control and computational basis (see add_measurements()).

Parameters
  • state_to_classify (numpy.ndarray) – array of dimension N complex values describing the state to classify via KNN.

  • oracle (qiskit Instruction) – oracle \(\mathcal{W}\) for applying training data.

  • add_measurement (bool) – controls if measurements must be added to the classical registers.

Raises
  • ValueError – If the number of data points in state_to_classify is more than 2.

  • ValueError – If the length of the vectors in the classified_states and/or test data are not a positive power of 2.

Returns

constructed circuit.

Return type

QuantumCircuit

create_oracle(train_data: Union[List, numpy.ndarray]) → qiskit.circuit.instruction.Instruction[source]

Create an Oracle to perform as QRAM.

The oracle works as follows:

\[\mathcal{W}|i\rangle |0\rangle = |i\rangle |\phi_i\rangle\]

where the equation is from Afham et al. (2020). This oracle acts as QRAM, which holds the training dataset \(\Phi\) to assign to the register for performing a swap test. It is located in the center of the quantum circuit (see create_qknn()).

Notes

The Oracle works with controlled initializers which check the state of the computational basis. The computational basis is described by \(|i\rangle\), where \(i\) is any real number, which is then described by qubits in binary.

To check the the state of the computational basis, a network of \(X\)-gates is created to bring the computational basis systematically into all possible states. If all qubits in the register are \(|1\rangle\), the datum is assigned via the initialize. Where to apply the \(X\)-gates is determined by where_to_apply_x().

Example

Creating a simple oracle for dataset with 4 points.

from qiskit_quantum_knn.qknn.qknn_construction import create_oracle

train_data = [
     [1, 0],
     [1, 0],
     [0, 1],
     [0, 1]
]

oracle = create_oracle(train_data)

print(oracle.definition.draw())
          ┌───────┐     ┌───────┐     ┌───────┐     ┌───────┐
q_0: ─────┤ phi_0 ├─────┤ phi_1 ├─────┤ phi_2 ├─────┤ phi_3 ├
     ┌───┐└───┬───┘┌───┐└───┬───┘┌───┐└───┬───┘┌───┐└───┬───┘
q_1: ┤ X ├────■────┤ X ├────■────┤ X ├────■────┤ X ├────■────
     ├───┤    │    └───┘    │    ├───┤    │    └───┘    │    
q_2: ┤ X ├────■─────────────■────┤ X ├────■─────────────■────
     └───┘                       └───┘                       
Parameters

train_data (array-like) – List of vectors with dimension len(r_train) to initialize r_train to.

Returns

Instruction of the Oracle.

Return type

circuit.instruction.Instruction

where_to_apply_x(bin_number_length: int) → List[source]

Create an array to apply \(X\)-gates systematically to create all possible register combinations.

This method returns the indices on where to apply \(X\)-gates on a quantum register with n qubits to generate all possible binary numbers on that register.

Example

Suppose we have a register with 2 qubits. We want to make sure we check all possible states this register can be in, such that a data point can be assigned. A register with 2 qubits can be in 4 states:

\[|0\rangle = |00\rangle, |1\rangle = |01\rangle, |2\rangle = |10\rangle, |3\rangle = |11\rangle\]

So to apply \(\phi_1\), the register must be in state \(|01\rangle\), and we need to apply the \(X\)-gate only to the first qubit. The state becomes \(|11\rangle\) and the controlled initialise will trigger.

Because the algorithm will check for all states in succession, this can be reduced to prevent double placements of \(X\)-gates, and it determines where to place the \(X\)-gates via:

\[|i-1\rangle XOR |i\rangle\]

A full list of all these configurations is created by this method:

from qiskit_quantum_knn.qknn.qknn_construction import where_to_apply_x

num_qubits = 2
where_to_apply_x(num_qubits)
[[0, 1], [0], [0, 1], [0]]
Parameters

bin_number_length (int) – the length of the highest binary value (or the number of qubits).

Returns

All possible combinations.

A length 2**bin_number_length of the indices of the qubits where the \(X\)-gate must be applied to.

Return type

List

initialise_qknn(log2_dim: int, log2_n_samps: int, test_state: numpy.ndarray) → qiskit.circuit.quantumcircuit.QuantumCircuit[source]

Creates the registers and applies the unclassified datum \(\psi\).

Coincides with Step 1: the “initialisation” section in Afham et al. (2020). Initialises a QuantumCircuit with 1 + 2n + m qubits (n: log2_dimension, m: log2_samples) for a QkNN network, where qubits 1 till n are initialised in some state psi ( state_to_classify).

Example

Set up the scaffolds for a QkNN QuantumCircuit.

from qiskit_quantum_knn.qknn.qknn_construction import initialise_qknn

n_dim_qubits = 1
n_samps_qubits = 1
test_state = [0, 1]

init_circ = initialise_qknn(n_dim_qubits, n_samps_qubits, test_state)
print(init_circ.draw())
                                         ░ 
          control_0: ────────────────────░─
                     ┌─────────────────┐ ░ 
state_to_classify_0: ┤ INIT TEST STATE ├─░─
                     └─────────────────┘ ░ 
     train_states_0: ────────────────────░─
                                         ░ 
       comp_basis_0: ────────────────────░─
                                         ░ 
     meas_control: 1/══════════════════════
                                           
  meas_comp_basis: 1/══════════════════════
                                           
Parameters
  • log2_dim (int) – int, log2 value of the dimension of the test and train states.

  • log2_n_samps (int) – int, log2 value of the number of training samples M.

  • test_state (numpy.ndarray) – 2 ** log2_dimension complex values to initialise the r_1 test state in (psi).

Returns

The initialised circuit.

Return type

QuantumCircuit

state_transformation(qknn_circ: qiskit.circuit.quantumcircuit.QuantumCircuit, oracle: qiskit.circuit.instruction.Instruction) → qiskit.circuit.quantumcircuit.QuantumCircuit[source]

applies \(H\)-gates and the Oracle \(\mathcal{W}\) to the circuit, and applies the \(SWAP\)-test.

Coincides with Step 2: the “state transformation” section from Afham et al. (2020). Applies Hadamard gates and Quantum Oracle to bring \(r_1, r_2, r_3, r_4\) in the desired states.

Note

This needs the QuantumCircuit created by initialise_qknn() as a parameter in order to function properly.

Example

Apply the oracle and test data in a QuantumCircuit.

from qiskit_quantum_knn.qknn.qknn_construction import create_oracle, \
    initialise_qknn, state_transformation

n_dim_qubits = 1  # must be log(len(test_state))
n_samps_qubits = 1  # must be log(len(train_data))

test_state = [0, 1]
train_data = [
    [1, 0],
    [0, 1]
]

oracle = create_oracle(train_data)

init_circ = initialise_qknn(n_dim_qubits, n_samps_qubits, test_state)
state_circ = state_transformation(init_circ, oracle)
print(state_circ.draw())
                                         ░ ┌───┐              ┌───┐ ░ 
          control_0: ────────────────────░─┤ H ├────────────■─┤ H ├─░─
                     ┌─────────────────┐ ░ └───┘            │ └───┘ ░ 
state_to_classify_0: ┤ INIT TEST STATE ├─░──────────────────X───────░─
                     └─────────────────┘ ░      ┌─────────┐ │       ░ 
     train_states_0: ────────────────────░──────┤0        ├─X───────░─
                                         ░ ┌───┐│  oracle │         ░ 
       comp_basis_0: ────────────────────░─┤ H ├┤1        ├─────────░─
                                         ░ └───┘└─────────┘         ░ 
     meas_control: 1/═════════════════════════════════════════════════
                                                                      
  meas_comp_basis: 1/═════════════════════════════════════════════════
                                                                      
Parameters
  • qknn_circ (QuantumCircuit) – has been initialised according to initialise_qknn().

  • oracle (qiskit Instruction) – oracle W|i>|0> = W|i>|phi_i> for applying training data.

Returns

the transformed QuantumCircuit.

Return type

QuantumCircuit

add_measurements(qknn_circ: qiskit.circuit.quantumcircuit.QuantumCircuit) → qiskit.circuit.quantumcircuit.QuantumCircuit[source]

Adds measurement gates to the control and computational basis.

Performs the third and final step of the building of the QkNN circuit by adding measurements to the control qubit and the computational basis.

Note

This needs the QuantumCircuit created by state_transformation() as a parameter in order to function properly.

Example

from qiskit_quantum_knn.qknn.qknn_construction import create_oracle,                 initialise_qknn, state_transformation, add_measurements

n_dim_qubits = 1  # must be log(len(test_state))
n_samps_qubits = 1  # must be log(len(train_data))

test_state = [0, 1]
train_data = [
    [1, 0],
    [0, 1]
]

oracle = create_oracle(train_data)

init_circ = initialise_qknn(n_dim_qubits, n_samps_qubits, test_state)
state_circ = state_transformation(init_circ, oracle)
final_circ = add_measurements(state_circ)
print(final_circ.draw())
                                         ░ ┌───┐              ┌───┐ ░ ┌─┐   
          control_0: ────────────────────░─┤ H ├────────────■─┤ H ├─░─┤M├───
                     ┌─────────────────┐ ░ └───┘            │ └───┘ ░ └╥┘   
state_to_classify_0: ┤ INIT TEST STATE ├─░──────────────────X───────░──╫────
                     └─────────────────┘ ░      ┌─────────┐ │       ░  ║    
     train_states_0: ────────────────────░──────┤0        ├─X───────░──╫────
                                         ░ ┌───┐│  oracle │         ░  ║ ┌─┐
       comp_basis_0: ────────────────────░─┤ H ├┤1        ├─────────░──╫─┤M├
                                         ░ └───┘└─────────┘         ░  ║ └╥┘
     meas_control: 1/══════════════════════════════════════════════════╩══╬═
                                                                       0  ║ 
                                                                          ║ 
  meas_comp_basis: 1/═════════════════════════════════════════════════════╩═
                                                                          0 
Parameters

qknn_circ (qk.QuantumCircuit) – has been build up by first applying initialise_qknn() and state_transformation().

Returns

the QuantumCircuit with measurements applied.

Return type

QuantumCircuit

qiskit_quantum_knn.qknn.quantumgates module

swap()[source]

A self-written decomposition of the SWAP-gate.

Example

from qiskit_quantum_knn.qknn.quantumgates import swap

swap_circ = swap()
print(swap_circ.definition.draw())
          ┌───┐     
q_0: ──■──┤ X ├──■──
     ┌─┴─┐└─┬─┘┌─┴─┐
q_1: ┤ X ├──■──┤ X ├
     └───┘     └───┘
Returns

the SWAP-gate.

Return type

Instruction

fidelity_instruction()[source]

A decomposition of the SWAP-measurement.

The fidelity between the state on q_1 and the state on q_2 is defined as:

\[\mathbb{P}(q_0 = 0) - \mathbb{P}(q_0 = 1)\]

Example

from qiskit_quantum_knn.qknn.quantumgates import fidelity_instruction

fid_inst = fidelity_instruction()
print(fid_inst.definition.draw())
     ┌───┐   ┌───┐┌─┐
q_0: ┤ H ├─■─┤ H ├┤M├
     └───┘ │ └───┘└╥┘
q_1: ──────X───────╫─
           │       ║ 
q_2: ──────X───────╫─
                   ║ 
c: 1/══════════════╩═
                   0 
Returns

The Fidelity gate (swap measurement).

Return type

Instruction

init_to_state(reg_to_init: qiskit.circuit.quantumregister.QuantumRegister, init_state: numpy.ndarray, name: Optional[str] = None) → qiskit.circuit.gate.Gate[source]

Initialize a QuantumRegister to the provided state.

Parameters
  • reg_to_init (QuantumRegister) – register which needs to be initialized.

  • init_state (np.ndarray) – state to which the reg_to_init must be initialized to.

  • name (str) – optional, name for the init_gate.

Raises

ValueError – if the register and state do not have the same dimension.

Returns

The initialiser.

A gate of size reg_to_init.size which performs the initialization.

Return type

Gate

controlled_initialize(reg_to_init: qiskit.circuit.quantumregister.QuantumRegister, init_state: numpy.ndarray, num_ctrl_qubits: Optional[int] = 1, name: Optional[str] = None) → qiskit.circuit.controlledgate.ControlledGate[source]

Initialize a register to provided state with control.

This method uses init_to_state() to create the initialiser.

Parameters
  • reg_to_init (QuantumRegister) – register which needs to be initialized.

  • init_state (np.ndarray) – state to which the reg_to_init must be initialized to.

  • num_ctrl_qubits (int) – optional, number of desired controls.

  • name (str) – optional, name for the init_gate.

Returns

The produced controlled initialise.

A Gate of size reg_to_init.size + num_ctrl_qubits which performs the initialize with control.

Return type

ControlledGate

Module contents