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)
, withn
the number of data points, andd
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)
, withm
the the number of data points, andd
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)
, withk
the number of data points, andd
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)
, whereN
is the number of data points andD
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, seecalculate_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 indexi
(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 indexi
(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 theResult
. 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 of0
and1
, 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
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:
- Initialisation:
creates the registers and applies the unclassified datum \(\psi\) (see
initialise_qknn()
);
- State transformation:
applies \(H\)-gates and the Oracle \(\mathcal{W}\) to the circuit, and applies the \(SWAP\)-test (see
state_transformation()
);
- 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 initializer_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 byinitialise_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 bystate_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 onq_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