Source code for dc_qiskit_qml.distance_based.hadamard.state._QmlGenericStateCircuitBuilder

# -*- coding: utf-8 -*-

# Copyright 2018 Carsten Blank
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
r"""
QmlGenericStateCircuitBuilder
==============================

.. currentmodule:: dc_qiskit_qml.distance_based.hadamard.state._QmlGenericStateCircuitBuilder

The generic state circuit builder classically computes the necessary quantum state vector (sparse) and will use a
state preparing quantum routine that takes the state vector and creates a circuit. This state preparing quantum routine
must implement :py:class:`QmlSparseVectorFactory`.

.. autosummary::
    :nosignatures:

    QmlGenericStateCircuitBuilder

QmlGenericStateCircuitBuilder
##############################

.. autoclass:: QmlGenericStateCircuitBuilder
    :members:

"""
import logging
from typing import List, Optional

import numpy as np
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from scipy import sparse

from ._QmlStateCircuitBuilder import QmlStateCircuitBuilder
from .sparsevector import QmlSparseVectorStatePreparation

log = logging.getLogger('StateVectorClassifierStateCircuit')


[docs]class QmlGenericStateCircuitBuilder(QmlStateCircuitBuilder): """ From generic training and testing data creates the quantum state vector and applies a quantum algorithm to create a circuit. """ def __init__(self, state_preparation): # type: (QmlGenericStateCircuitBuilder, QmlSparseVectorStatePreparation) -> None """ Create a new object, use the state preparation routine :param state_preparation: The quantum state preparation routine to encode the quantum state """ self.state_preparation = state_preparation self._last_state_vector = None # type: sparse.dok_matrix
[docs] @staticmethod def get_binary_representation(sample_index, sample_label, entry_index, is_input, index_qb_len, label_qb_len, data_qb_len): # type: (int, int, int, bool, int, int, int) -> str """ Computes the binary representation of the quantum state as `str` given indices and qubit lengths :param sample_index: the training data sample index :param sample_label: the training data label :param entry_index: the data sample vector index :param is_input: True if the we encode the input instead of the training vector :param index_qb_len: total qubits needed for the index register :param label_qb_len: total qubits needed for the label register :param data_qb_len: total qubits needed for the data register :return: binary representation of which the quantum state being addressed """ sample_index_b = "{0:b}".format(sample_index).zfill(index_qb_len) sample_label_b = "{0:b}".format(sample_label).zfill(label_qb_len) ancillary_b = '0' if is_input else '1' entry_index_b = "{0:b}".format(entry_index).zfill(data_qb_len) # Here we compose the qubit, the ordering will be essential # However keep in mind, that the order is LSB qubit_composition = [ sample_label_b, entry_index_b, sample_index_b, ancillary_b ] return "".join(qubit_composition)
[docs] def build_circuit(self, circuit_name, X_train, y_train, X_input): # type: (QmlGenericStateCircuitBuilder, str, List[sparse.dok_matrix], any, sparse.dok_matrix) -> QuantumCircuit """ Build a circuit that encodes the training (samples/labels) and input data sets into a quantum circuit. It does so by iterating through the training data set with labels and constructs upon sample index and vector position the to be modified amplitude. The state vector is stored into a sparse matrix of shape (n,1) which is stored and can be accessed through :py:func:`get_last_state_vector` for debugging purposes. Then the routine uses a :py:class:`QmlSparseVectorStatePreparation` routine to encode the calculated state vector into a quantum circuit. :param circuit_name: The name of the quantum circuit :param X_train: The training data set :param y_train: the training class label data set :param X_input: the unclassified input data vector :return: The circuit containing the gates to encode the input data """ log.debug("Preparing state.") log.debug("Raw Input Vector: %s" % X_input) count_of_samples, sample_space_dimension = len(X_train), X_train[0].get_shape()[0] count_of_distinct_classes = len(set(y_train)) index_of_samples_qubits_needed = (count_of_samples - 1).bit_length() sample_space_dimensions_qubits_needed = (sample_space_dimension - 1).bit_length() ancilla_qubits_needed = 1 label_qubits_needed = (count_of_distinct_classes - 1).bit_length() if count_of_distinct_classes > 1 else 1 total_qubits_needed = index_of_samples_qubits_needed + ancilla_qubits_needed \ + sample_space_dimensions_qubits_needed + label_qubits_needed log.info("Qubit map: index=%d, ancillary=%d, feature=%d, label=%d", index_of_samples_qubits_needed, ancilla_qubits_needed, sample_space_dimensions_qubits_needed, label_qubits_needed) state_vector = sparse.dok_matrix((2 ** total_qubits_needed, 1), dtype=complex) # type: sparse.dok_matrix factor = 2 * count_of_samples * 1.0 for index_sample, (sample, label) in enumerate(zip(X_train, y_train)): for (i, j), sample_i in sample.items(): qubit_state = QmlGenericStateCircuitBuilder.get_binary_representation(index_sample, label, i, is_input=False, index_qb_len=index_of_samples_qubits_needed, label_qb_len=label_qubits_needed, data_qb_len=sample_space_dimensions_qubits_needed) state_index = int(qubit_state, 2) log.debug("Sample Entry: %s (%d): %.2f.", qubit_state, state_index, sample_i) state_vector[state_index, 0] = sample_i for (i, j), input_i in X_input.items(): qubit_state = QmlGenericStateCircuitBuilder.get_binary_representation(index_sample, label, i, is_input=True, index_qb_len=index_of_samples_qubits_needed, label_qb_len=label_qubits_needed, data_qb_len=sample_space_dimensions_qubits_needed ) state_index = int(qubit_state, 2) log.debug("Input Entry: %s (%d): %.2f.", qubit_state, state_index, input_i) state_vector[state_index, 0] = input_i state_vector = state_vector * (1 / np.sqrt(factor)) ancilla = QuantumRegister(ancilla_qubits_needed, "a") index = QuantumRegister(index_of_samples_qubits_needed, "i") data = QuantumRegister(sample_space_dimensions_qubits_needed, "f^S") qlabel = QuantumRegister(label_qubits_needed, "l^q") clabel = ClassicalRegister(label_qubits_needed, "l^c") branch = ClassicalRegister(1, "b") qc = QuantumCircuit(ancilla, index, data, qlabel, clabel, branch, name=circuit_name) # type: QuantumCircuit self._last_state_vector = state_vector return self.state_preparation.prepare_state(qc, state_vector)
[docs] def is_classifier_branch(self, branch_value): # type: (QmlGenericStateCircuitBuilder, int) -> bool """ As each state preparation algorithm uses a unique layout. Here the :py:class:`QmlSparseVectorFactory` is asked how the branch for post selection can be identified. :param branch_value: The measurement of the branch :return: True is the branch is containing the classification, False if not """ return self.state_preparation.is_classifier_branch(branch_value)
[docs] def get_last_state_vector(self): # type: (QmlGenericStateCircuitBuilder) -> Optional[sparse.dok_matrix] """ From the last call of :py:func:`build_circuit` the computed (sparse) state vector. :return: a sparse vector (shape (n,0)) if present """ return self._last_state_vector