def test_w_noise(self):
        """ with noise test """
        # build noise model
        # Asymmetric readout error on qubit-0 only
        try:
            from qiskit.providers.aer.noise import NoiseModel
            from qiskit import Aer
            self.backend = Aer.get_backend('qasm_simulator')
        except ImportError as ex:
            self.skipTest("Aer doesn't appear to be installed. Error: '{}'".format(str(ex)))
            return

        probs_given0 = [0.9, 0.1]
        probs_given1 = [0.3, 0.7]
        noise_model = NoiseModel()
        noise_model.add_readout_error([probs_given0, probs_given1], [0])

        quantum_instance = QuantumInstance(self.backend, seed_transpiler=self.random_seed,
                                           seed_simulator=self.random_seed, shots=1024,
                                           noise_model=noise_model)
        res_w_noise = quantum_instance.execute(self.qc).get_counts(self.qc)

        quantum_instance.skip_qobj_validation = True
        res_w_noise_skip_validation = quantum_instance.execute(self.qc).get_counts(self.qc)
        self.assertTrue(_compare_dict(res_w_noise, res_w_noise_skip_validation))

        # BasicAer should fail:
        with self.assertRaises(QiskitError):
            _ = QuantumInstance(BasicAer.get_backend('qasm_simulator'),
                                noise_model=noise_model)

        with self.assertRaises(QiskitError):
            quantum_instance = QuantumInstance(BasicAer.get_backend('qasm_simulator'))
            quantum_instance.set_config(noise_model=noise_model)
Пример #2
0
    def get_output(self,
                   quantum_instance: QuantumInstance,
                   params: Optional[np.ndarray] = None,
                   shots: Optional[int] = None) -> Tuple[List, List]:
        """
        Get classical data samples from the generator.
        Running the quantum generator circuit results in a quantum state.
        To train this generator with a classical discriminator, we need to sample classical outputs
        by measuring the quantum state and mapping them to feature space defined by the training
        data.

        Args:
            quantum_instance: Quantum Instance, used to run the generator
                circuit.
            params: array or None, parameters which should
                be used to run the generator, if None use self._params
            shots: if not None use a number of shots that is different from the
                number set in quantum_instance

        Returns:
            generated samples, array: sample occurrence in percentage
        """
        instance_shots = quantum_instance.run_config.shots
        q = QuantumRegister(sum(self._num_qubits), name='q')
        qc = QuantumCircuit(q)
        if params is None:
            params = cast(np.ndarray, self._bound_parameters)
        qc.append(self.construct_circuit(params), q)
        if quantum_instance.is_statevector:
            pass
        else:
            c = ClassicalRegister(sum(self._num_qubits), name='c')
            qc.add_register(c)
            qc.measure(q, c)

        if shots is not None:
            quantum_instance.set_config(shots=shots)

        result = quantum_instance.execute(qc)

        generated_samples = []
        if quantum_instance.is_statevector:
            result = result.get_statevector(qc)
            values = np.multiply(result, np.conj(result))
            values = list(values.real)
            keys = []
            for j in range(len(values)):
                keys.append(np.binary_repr(j, int(sum(self._num_qubits))))
        else:
            result = result.get_counts(qc)
            keys = list(result)
            values = list(result.values())
            values = [float(v) / np.sum(values) for v in values]
        generated_samples_weights = values
        for i, _ in enumerate(keys):
            index = 0
            temp = []
            for k, p in enumerate(self._num_qubits):
                bin_rep = 0
                j = 0
                while j < p:
                    bin_rep += int(keys[i][index]) * 2**(int(p) - j - 1)
                    j += 1
                    index += 1
                if len(self._num_qubits) > 1:
                    temp.append(self._data_grid[k][int(bin_rep)])
                else:
                    temp.append(self._data_grid[int(bin_rep)])
            generated_samples.append(temp)

        # self.generator_circuit._probabilities = generated_samples_weights
        if shots is not None:
            # Restore the initial quantum_instance configuration
            quantum_instance.set_config(shots=instance_shots)
        return generated_samples, generated_samples_weights
Пример #3
0
class QGAN:
    """The Quantum Generative Adversarial Network algorithm.

    The qGAN [1] is a hybrid quantum-classical algorithm used for generative modeling tasks.

    This adaptive algorithm uses the interplay of a generative
    :class:`~qiskit_machine_learning.neural_networks.GenerativeNetwork` and a
    discriminative :class:`~qiskit_machine_learning.neural_networks.DiscriminativeNetwork`
    network to learn the probability distribution underlying given training data.

    These networks are trained in alternating optimization steps, where the discriminator tries to
    differentiate between training data samples and data samples from the generator and the
    generator aims at generating samples which the discriminator classifies as training data
    samples. Eventually, the quantum generator learns the training data's underlying probability
    distribution. The trained quantum generator loads a quantum state which is a model of the
    target distribution.

    **References:**

    [1] Zoufal et al.,
        `Quantum Generative Adversarial Networks for learning and loading random distributions
        <https://www.nature.com/articles/s41534-019-0223-2>`_
    """
    def __init__(
        self,
        data: Union[np.ndarray, List],
        bounds: Optional[Union[np.ndarray, List]] = None,
        num_qubits: Optional[Union[np.ndarray, List]] = None,
        batch_size: int = 500,
        num_epochs: int = 3000,
        seed: int = 7,
        discriminator: Optional[DiscriminativeNetwork] = None,
        generator: Optional[GenerativeNetwork] = None,
        tol_rel_ent: Optional[float] = None,
        snapshot_dir: Optional[str] = None,
        quantum_instance: Optional[Union[QuantumInstance, Backend]] = None,
        penalty: Optional[bool] = False,
    ) -> None:
        """

        Args:
            data: Training data of dimension k
            bounds: k min/max data values [[min_0,max_0],...,[min_k-1,max_k-1]]
                if univariate data: [min_0,max_0]
            num_qubits: k numbers of qubits to determine representation resolution,
                i.e. n qubits enable the representation of 2**n values
                [num_qubits_0,..., num_qubits_k-1]
            batch_size: Batch size, has a min. value of 1.
            num_epochs: Number of training epochs
            seed: Random number seed
            discriminator: Discriminates between real and fake data samples
            generator: Generates 'fake' data samples
            tol_rel_ent: Set tolerance level for relative entropy.
                If the training achieves relative entropy equal or lower than tolerance it finishes.
            snapshot_dir: Directory in to which to store cvs file with parameters,
                if None (default) then no cvs file is created.
            quantum_instance: Quantum Instance or Backend
            penalty : enable or not the gradient penalty in the discriminator
        Raises:
            QiskitMachineLearningError: invalid input
        """
        validate_min("batch_size", batch_size, 1)
        self._quantum_instance = None
        if quantum_instance:
            self.quantum_instance = quantum_instance
        if data is None:
            raise QiskitMachineLearningError("Training data not given.")
        self._data = np.array(data)
        if bounds is None:
            bounds_min = np.percentile(self._data, 5, axis=0)
            bounds_max = np.percentile(self._data, 95, axis=0)
            bounds = []
            for i, _ in enumerate(bounds_min):
                bounds.append([bounds_min[i], bounds_max[i]])
        if np.ndim(data) > 1:
            if len(bounds) != (len(num_qubits) or len(data[0])):
                raise QiskitMachineLearningError(
                    "Dimensions of the data, the length of the data bounds "
                    "and the numbers of qubits per "
                    "dimension are incompatible.")
        else:
            if (np.ndim(bounds) or len(num_qubits)) != 1:
                raise QiskitMachineLearningError(
                    "Dimensions of the data, the length of the data bounds "
                    "and the numbers of qubits per "
                    "dimension are incompatible.")
        self._bounds = np.array(bounds)
        self._num_qubits = num_qubits
        # pylint: disable=unsubscriptable-object
        if np.ndim(data) > 1:
            if self._num_qubits is None:
                self._num_qubits = np.ones[len(data[0])] * 3  # type: ignore
        else:
            if self._num_qubits is None:
                self._num_qubits = np.array([3])
        (
            self._data,
            self._data_grid,
            self._grid_elements,
            self._prob_data,
        ) = discretize_and_truncate(
            self._data,
            self._bounds,
            self._num_qubits,
            return_data_grid_elements=True,
            return_prob=True,
            prob_non_zero=True,
        )
        self._batch_size = batch_size
        self._num_epochs = num_epochs
        self._snapshot_dir = snapshot_dir
        self._g_loss = []  # type: List[float]
        self._d_loss = []  # type: List[float]
        self._rel_entr = []  # type: List[float]
        self._tol_rel_ent = tol_rel_ent
        self._penalty = penalty

        self._random_seed = seed

        if generator is None:
            self.set_generator()
        else:
            self._generator = generator
        if discriminator is None:
            self.set_discriminator()
        else:
            self._discriminator = discriminator

        self.seed = self._random_seed

        self._ret = {}  # type: Dict[str, Any]

    @property
    def random(self):
        """Return a numpy random."""
        return algorithm_globals.random

    def run(
        self,
        quantum_instance: Optional[Union[QuantumInstance, Backend]] = None,
        **kwargs,
    ) -> Dict:
        """Execute the algorithm with selected backend.
        Args:
            quantum_instance: the experimental setting.
            **kwargs (dict): kwargs
        Returns:
            dict: results of an algorithm.
        Raises:
            QiskitMachineLearningError: If a quantum instance or
                                        backend has not been provided
        """
        if quantum_instance is None and self.quantum_instance is None:
            raise QiskitMachineLearningError(
                "A QuantumInstance or Backend must be supplied to run the quantum algorithm."
            )
        if isinstance(quantum_instance, Backend):
            self.set_backend(quantum_instance, **kwargs)
        else:
            if quantum_instance is not None:
                self.quantum_instance = quantum_instance

        return self._run()

    @property
    def quantum_instance(self) -> Optional[QuantumInstance]:
        """Returns quantum instance."""
        return self._quantum_instance

    @quantum_instance.setter
    def quantum_instance(
            self, quantum_instance: Union[QuantumInstance, Backend]) -> None:
        """Sets quantum instance."""
        if isinstance(quantum_instance, Backend):
            quantum_instance = QuantumInstance(quantum_instance)
        self._quantum_instance = quantum_instance

    def set_backend(self, backend: Backend, **kwargs) -> None:
        """Sets backend with configuration."""
        self.quantum_instance = QuantumInstance(backend)
        self.quantum_instance.set_config(**kwargs)

    @property
    def backend(self) -> Backend:
        """Returns backend."""
        return self.quantum_instance.backend

    @backend.setter
    def backend(self, backend: Backend):
        """Sets backend without additional configuration."""
        self.set_backend(backend)

    @property
    def seed(self):
        """Returns random seed"""
        return self._random_seed

    @seed.setter
    def seed(self, s):
        """
        Sets the random seed for QGAN and updates the algorithm_globals seed
        at the same time

        Args:
            s (int): random seed
        """
        self._random_seed = s
        algorithm_globals.random_seed = self._random_seed
        self._discriminator.set_seed(self._random_seed)

    @property
    def tol_rel_ent(self):
        """Returns tolerance for relative entropy"""
        return self._tol_rel_ent

    @tol_rel_ent.setter
    def tol_rel_ent(self, t):
        """
        Set tolerance for relative entropy

        Args:
            t (float): or None, Set tolerance level for relative entropy.
                If the training achieves relative
                entropy equal or lower than tolerance it finishes.
        """
        self._tol_rel_ent = t

    @property
    def generator(self):
        """Returns generator"""
        return self._generator

    @property
    def penalty(self):
        """Returns penalty parameter for the discriminator training"""
        return self._penalty

    # pylint: disable=unused-argument
    def set_generator(
        self,
        generator_circuit: Optional[QuantumCircuit] = None,
        generator_init_params: Optional[np.ndarray] = None,
        generator_optimizer: Optional[Optimizer] = None,
        generator_gradient: Optional[Union[Callable, Gradient]] = None,
    ):
        """Initialize generator.

        Args:
            generator_circuit: parameterized quantum circuit which sets
                the structure of the quantum generator
            generator_init_params: initial parameters for the generator circuit
            generator_optimizer: optimizer to be used for the training of the generator
            generator_gradient: A Gradient object, or a function returning partial
                derivatives of the loss function w.r.t. the generator variational
                params.
        Raises:
            QiskitMachineLearningError: invalid input
        """
        if generator_gradient:
            if not isinstance(generator_gradient, (Gradient, FunctionType)):
                raise QiskitMachineLearningError(
                    "Please pass either a Gradient object or a function as "
                    "the generator_gradient argument.")
        self._generator = QuantumGenerator(
            self._bounds,
            self._num_qubits,
            generator_circuit,
            generator_init_params,
            generator_optimizer,
            generator_gradient,
            self._snapshot_dir,
        )

    @property
    def discriminator(self):
        """Returns discriminator"""
        return self._discriminator

    def set_discriminator(self, discriminator=None):
        """
        Initialize discriminator.

        Args:
            discriminator (Discriminator): discriminator
        """

        if discriminator is None:
            self._discriminator = NumPyDiscriminator(len(self._num_qubits))
        else:
            self._discriminator = discriminator
        self._discriminator.set_seed(self._random_seed)

    @property
    def g_loss(self) -> List[float]:
        """Returns generator loss"""
        return self._g_loss

    @property
    def d_loss(self) -> List[float]:
        """Returns discriminator loss"""
        return self._d_loss

    @property
    def rel_entr(self) -> List[float]:
        """Returns relative entropy between target and trained distribution"""
        return self._rel_entr

    def get_rel_entr(self) -> float:
        """Get relative entropy between target and trained distribution"""
        samples_gen, prob_gen = self._generator.get_output(
            self._quantum_instance)
        temp = np.zeros(len(self._grid_elements))
        for j, sample in enumerate(samples_gen):
            for i, element in enumerate(self._grid_elements):
                if sample == element:
                    temp[i] += prob_gen[j]
        prob_gen = [1e-8 if x == 0 else x for x in temp]
        rel_entr = entropy(prob_gen, self._prob_data)
        return rel_entr

    def _store_params(self, e, d_loss, g_loss, rel_entr):
        with open(os.path.join(self._snapshot_dir, "output.csv"),
                  mode="a",
                  encoding="utf8") as csv_file:
            fieldnames = [
                "epoch",
                "loss_discriminator",
                "loss_generator",
                "params_generator",
                "rel_entropy",
            ]
            writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
            writer.writerow({
                "epoch": e,
                "loss_discriminator": np.average(d_loss),
                "loss_generator": np.average(g_loss),
                "params_generator": self._generator.parameter_values,
                "rel_entropy": rel_entr,
            })
        self._discriminator.save_model(
            self._snapshot_dir)  # Store discriminator model

    def train(self):
        """
        Train the qGAN

        Raises:
            QiskitMachineLearningError: Batch size bigger than the number of
                                        items in the truncated data set
        """
        if self._snapshot_dir is not None:
            with open(os.path.join(self._snapshot_dir, "output.csv"),
                      mode="w",
                      encoding="utf8") as csv_file:
                fieldnames = [
                    "epoch",
                    "loss_discriminator",
                    "loss_generator",
                    "params_generator",
                    "rel_entropy",
                ]
                writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
                writer.writeheader()

        if len(self._data) < self._batch_size:
            raise QiskitMachineLearningError(
                "The batch size needs to be less than the "
                f"truncated data size of {len(self._data)}")

        for e in range(self._num_epochs):
            algorithm_globals.random.shuffle(self._data)
            index = 0
            while (index + self._batch_size) <= len(self._data):
                real_batch = self._data[index:index + self._batch_size]
                index += self._batch_size
                generated_batch, generated_prob = self._generator.get_output(
                    self._quantum_instance, shots=self._batch_size)

                # 1. Train Discriminator
                ret_d = self._discriminator.train(
                    [real_batch, generated_batch],
                    [
                        np.ones(len(real_batch)) / len(real_batch),
                        generated_prob
                    ],
                    penalty=self._penalty,
                )
                d_loss_min = ret_d["loss"]

                # 2. Train Generator
                self._generator.discriminator = self._discriminator
                ret_g = self._generator.train(self._quantum_instance,
                                              shots=self._batch_size)
                g_loss_min = ret_g["loss"]

            self._d_loss.append(np.around(float(d_loss_min), 4))
            self._g_loss.append(np.around(g_loss_min, 4))

            rel_entr = self.get_rel_entr()
            self._rel_entr.append(np.around(rel_entr, 4))
            self._ret["params_d"] = ret_d["params"]
            self._ret["params_g"] = ret_g["params"]
            self._ret["loss_d"] = np.around(float(d_loss_min), 4)
            self._ret["loss_g"] = np.around(g_loss_min, 4)
            self._ret["rel_entr"] = np.around(rel_entr, 4)

            if self._snapshot_dir is not None:
                self._store_params(
                    e,
                    np.around(d_loss_min, 4),
                    np.around(g_loss_min, 4),
                    np.around(rel_entr, 4),
                )
            logger.debug("Epoch %s/%s...", e + 1, self._num_epochs)
            logger.debug("Loss Discriminator: %s",
                         np.around(float(d_loss_min), 4))
            logger.debug("Loss Generator: %s", np.around(g_loss_min, 4))
            logger.debug("Relative Entropy: %s", np.around(rel_entr, 4))

            if self._tol_rel_ent is not None:
                if rel_entr <= self._tol_rel_ent:
                    break

    def _run(self):
        """
        Run qGAN training

        Returns:
            dict: with generator(discriminator) parameters & loss, relative entropy
        Raises:
            QiskitMachineLearningError: invalid backend
        """
        if self._quantum_instance.backend_name == ("unitary_simulator"
                                                   or "clifford_simulator"):
            raise QiskitMachineLearningError(
                "Chosen backend not supported - "
                "Set backend either to statevector_simulator, qasm_simulator"
                " or actual quantum hardware")
        self.train()

        return self._ret
Пример #4
0
class VQC(VariationalAlgorithm):
    """The Variational Quantum Classifier algorithm.

    Similar to :class:`QSVM`, the VQC algorithm also applies to classification problems.
    VQC uses the variational method to solve such problems in a quantum processor.  Specifically,
    it optimizes a parameterized quantum circuit to provide a solution that cleanly separates
    the data.

    .. note::

        The VQC stores the parameters of `var_form` and `feature_map` sorted by name to map the
        values provided by the optimizer to the circuit. This is done to ensure reproducible
        results, for example such that running the optimization twice with same random seeds yields
        the same result.

    """

    def __init__(
            self,
            optimizer: Optimizer,
            feature_map: QuantumCircuit,
            var_form: Union[QuantumCircuit, VariationalForm],
            training_dataset: Dict[str, np.ndarray],
            test_dataset: Optional[Dict[str, np.ndarray]] = None,
            datapoints: Optional[np.ndarray] = None,
            max_evals_grouped: int = 1,
            minibatch_size: int = -1,
            callback: Optional[Callable[[int, np.ndarray, float, int], None]] = None,
            use_sigmoid_cross_entropy: bool = False,
            quantum_instance: Optional[
                Union[QuantumInstance, BaseBackend, Backend]] = None) -> None:
        """
        Args:
            optimizer: The classical optimizer to use.
            feature_map: The QuantumCircuit instance to use.
            var_form: The variational form instance.
            training_dataset: The training dataset, in the format
                {'A': np.ndarray, 'B': np.ndarray, ...}.
            test_dataset: The test dataset, in same format as `training_dataset`.
            datapoints: NxD array, N is the number of data and D is data dimension.
            max_evals_grouped: The maximum number of evaluations to perform simultaneously.
            minibatch_size: The size of a mini-batch.
            callback: a callback that can access the intermediate data during the optimization.
                Four parameter values are passed to the callback as follows during each evaluation.
                These are: the evaluation count, parameters of the variational form,
                the evaluated value, the index of data batch.
            use_sigmoid_cross_entropy: whether to use sigmoid cross entropy or not.
            quantum_instance: Quantum Instance or Backend

        Note:
            We use `label` to denote numeric results and `class` the class names (str).

        Raises:
            QiskitMachineLearningError: Missing feature map or missing training dataset.
        """
        # VariationalForm is not deprecated on level of the VariationalAlgorithm yet as UCCSD still
        # derives from there, therefore we're adding a warning here
        if isinstance(var_form, VariationalForm):
            warnings.warn("""
            The {} object as input for the VQC is deprecated as of 0.7.0 and will
            be removed no earlier than 3 months after the release.
            You should pass a QuantumCircuit object instead.
            See also qiskit.circuit.library.n_local for a collection
            of suitable circuits.""".format(type(feature_map)),
                          DeprecationWarning, stacklevel=2)

        super().__init__(
            var_form=var_form,
            optimizer=optimizer,
            cost_fn=self._loss,
            quantum_instance=quantum_instance
        )
        self._batches = None
        self._label_batches = None
        self._batch_index = None
        self._eval_time = None
        self.batch_num = None
        self._optimizer.set_max_evals_grouped(max_evals_grouped)

        self._callback = callback

        if use_sigmoid_cross_entropy:
            self.cost_function = cost_estimate_sigmoid
        else:
            self.cost_function = cost_estimate

        if feature_map is None:
            raise QiskitMachineLearningError('Missing feature map.')
        if training_dataset is None:
            raise QiskitMachineLearningError('Missing training dataset.')
        self._training_dataset, self._class_to_label = split_dataset_to_data_and_labels(
            training_dataset)
        self._label_to_class = {label: class_name for class_name, label
                                in self._class_to_label.items()}
        self._num_classes = len(list(self._class_to_label.keys()))

        if test_dataset is not None:
            self._test_dataset = split_dataset_to_data_and_labels(test_dataset,
                                                                  self._class_to_label)
        else:
            self._test_dataset = test_dataset

        if datapoints is not None and not isinstance(datapoints, np.ndarray):
            datapoints = np.asarray(datapoints)
            if len(datapoints) == 0:  # pylint: disable=len-as-condition
                datapoints = None
        self._datapoints = datapoints
        self._minibatch_size = minibatch_size

        self._eval_count = 0
        self._ret = {}  # type: Dict[str, Any]
        self._parameterized_circuits = None

        self.feature_map = feature_map

    @property
    def random(self):
        """Return a numpy random."""
        return algorithm_globals.random

    def run(self,
            quantum_instance: Optional[
                Union[QuantumInstance, Backend, BaseBackend]] = None,
            **kwargs) -> Dict:
        """Execute the algorithm with selected backend.
        Args:
            quantum_instance: the experimental setting.
            kwargs (dict): kwargs
        Returns:
            dict: results of an algorithm.
        Raises:
            QiskitMachineLearningError: If a quantum instance or
                                        backend has not been provided
        """
        if quantum_instance is None and self.quantum_instance is None:
            raise QiskitMachineLearningError(
                "A QuantumInstance or Backend "
                "must be supplied to run the quantum algorithm.")
        if isinstance(quantum_instance, (BaseBackend, Backend)):
            self.set_backend(quantum_instance, **kwargs)
        else:
            if quantum_instance is not None:
                self.quantum_instance = quantum_instance

        return self._run()

    @property
    def quantum_instance(self) -> Optional[QuantumInstance]:
        """ Returns quantum instance. """
        return self._quantum_instance

    @quantum_instance.setter
    def quantum_instance(self, quantum_instance: Union[QuantumInstance,
                                                       BaseBackend, Backend]) -> None:
        """ Sets quantum instance. """
        if isinstance(quantum_instance, (BaseBackend, Backend)):
            quantum_instance = QuantumInstance(quantum_instance)
        self._quantum_instance = quantum_instance

    def set_backend(self, backend: Union[Backend, BaseBackend], **kwargs) -> None:
        """ Sets backend with configuration. """
        self.quantum_instance = QuantumInstance(backend)
        self.quantum_instance.set_config(**kwargs)

    @property
    def backend(self) -> Union[Backend, BaseBackend]:
        """ Returns backend. """
        return self.quantum_instance.backend

    @backend.setter
    def backend(self, backend: Union[Backend, BaseBackend]):
        """ Sets backend without additional configuration. """
        self.set_backend(backend)

    def construct_circuit(self, x, theta, measurement=False):
        """Construct circuit based on data and parameters in variational form.

        Args:
            x (numpy.ndarray): 1-D array with D dimension
            theta (list[numpy.ndarray]): list of 1-D array, parameters sets for variational form
            measurement (bool): flag to add measurement

        Returns:
            QuantumCircuit: the circuit

        Raises:
            QiskitMachineLearningError: If ``x`` and ``theta`` share parameters with the same name.
        """
        # check x and theta do not have parameters of the same name
        x_names = [param.name for param in x if isinstance(param, ParameterExpression)]
        theta_names = [param.name for param in theta if isinstance(param, ParameterExpression)]
        if any(x_name in theta_names for x_name in x_names):
            raise QiskitMachineLearningError(
                            'Variational form and feature map are not allowed to share parameters '
                            'with the same name!')

        qr = QuantumRegister(self._num_qubits, name='q')
        cr = ClassicalRegister(self._num_qubits, name='c')
        qc = QuantumCircuit(qr, cr)

        if isinstance(self.feature_map, QuantumCircuit):
            param_dict = dict(zip(self._feature_map_params, x))
            circuit = self._feature_map.assign_parameters(param_dict, inplace=False)
            qc.append(circuit.to_instruction(), qr)
        else:
            qc += self._feature_map.construct_circuit(x, qr)

        if isinstance(self.var_form, QuantumCircuit):
            param_dict = dict(zip(self._var_form_params, theta))
            circuit = self._var_form.assign_parameters(param_dict, inplace=False)
            qc.append(circuit.to_instruction(), qr)
        else:
            qc += self._var_form.construct_circuit(theta, qr)

        if measurement:
            qc.barrier(qr)
            qc.measure(qr, cr)
        return qc

    def _get_prediction(self, data, theta):
        """Make prediction on data based on each theta.

        Args:
            data (numpy.ndarray): 2-D array, NxD, N data points, each with D dimension
            theta (list[numpy.ndarray]): list of 1-D array, parameters sets for variational form

        Returns:
            Union(numpy.ndarray or [numpy.ndarray], numpy.ndarray or [numpy.ndarray]):
                list of NxK array, list of Nx1 array
        """
        from qiskit_machine_learning.circuit.library import RawFeatureVector
        circuits = []

        num_theta_sets = len(theta) // self._var_form.num_parameters
        theta_sets = np.split(theta, num_theta_sets)

        def _build_parameterized_circuits():
            var_form_support = isinstance(self._var_form, QuantumCircuit) \
                or self._var_form.support_parameterized_circuit
            feat_map_support = isinstance(self._feature_map, QuantumCircuit) \
                or self._feature_map.support_parameterized_circuit

            # cannot transpile the RawFeatureVector
            if isinstance(self._feature_map, RawFeatureVector):
                feat_map_support = False

            if var_form_support and feat_map_support and self._parameterized_circuits is None:
                parameterized_circuits = self.construct_circuit(
                    self._feature_map_params, self._var_form_params,
                    measurement=not self._quantum_instance.is_statevector)
                self._parameterized_circuits = \
                    self._quantum_instance.transpile(parameterized_circuits)[0]

        _build_parameterized_circuits()
        for thet in theta_sets:
            for datum in data:
                if self._parameterized_circuits is not None:
                    curr_params = dict(zip(self._feature_map_params, datum))
                    curr_params.update(dict(zip(self._var_form_params, thet)))
                    circuit = self._parameterized_circuits.assign_parameters(curr_params)
                else:
                    circuit = self.construct_circuit(
                        datum, thet, measurement=not self._quantum_instance.is_statevector)
                circuits.append(circuit)

        results = self._quantum_instance.execute(
            circuits, had_transpiled=self._parameterized_circuits is not None)

        circuit_id = 0
        predicted_probs = []
        predicted_labels = []
        for _ in theta_sets:
            counts = []
            for _ in data:
                if self._quantum_instance.is_statevector:
                    temp = results.get_statevector(circuit_id)
                    outcome_vector = (temp * temp.conj()).real
                    # convert outcome_vector to outcome_dict, where key
                    # is a basis state and value is the count.
                    # Note: the count can be scaled linearly, i.e.,
                    # it does not have to be an integer.
                    outcome_dict = {}
                    bitstr_size = int(math.log2(len(outcome_vector)))
                    for i, _ in enumerate(outcome_vector):
                        bitstr_i = format(i, '0' + str(bitstr_size) + 'b')
                        outcome_dict[bitstr_i] = outcome_vector[i]
                else:
                    outcome_dict = results.get_counts(circuit_id)

                counts.append(outcome_dict)
                circuit_id += 1

            probs = return_probabilities(counts, self._num_classes)
            predicted_probs.append(probs)
            predicted_labels.append(np.argmax(probs, axis=1))

        if len(predicted_probs) == 1:
            predicted_probs = predicted_probs[0]
        if len(predicted_labels) == 1:
            predicted_labels = predicted_labels[0]

        return predicted_probs, predicted_labels

    # Breaks data into minibatches. Labels are optional,
    # but will be broken into batches if included.
    def batch_data(self, data, labels=None, minibatch_size=-1):
        """ batch data """
        label_batches = None

        if 0 < minibatch_size < len(data):
            batch_size = min(minibatch_size, len(data))
            if labels is not None:
                shuffled_samples, shuffled_labels = \
                    shuffle(data, labels, random_state=algorithm_globals.random_seed)
                label_batches = np.array_split(shuffled_labels, batch_size)
            else:
                shuffled_samples = shuffle(data, random_state=algorithm_globals.random_seed)
            batches = np.array_split(shuffled_samples, batch_size)
        else:
            batches = np.asarray([data])
            label_batches = np.asarray([labels])
        return batches, label_batches

    def is_gradient_really_supported(self):
        """ returns is gradient really supported """
        return self.optimizer.is_gradient_supported and not self.optimizer.is_gradient_ignored

    def train(self, data, labels, quantum_instance=None, minibatch_size=-1):
        """Train the models, and save results.

        Args:
            data (numpy.ndarray): NxD array, N is number of data and D is dimension
            labels (numpy.ndarray): Nx1 array, N is number of data
            quantum_instance (QuantumInstance): quantum backend with all setting
            minibatch_size (int): the size of each minibatched accuracy evaluation
        """
        self._quantum_instance = \
            self._quantum_instance if quantum_instance is None else quantum_instance
        minibatch_size = minibatch_size if minibatch_size > 0 else self._minibatch_size
        self._batches, self._label_batches = self.batch_data(data, labels, minibatch_size)
        self._batch_index = 0

        if self.initial_point is None:
            self.initial_point = self.random.standard_normal(self._var_form.num_parameters)

        self._eval_count = 0

        grad_fn = None
        if minibatch_size > 0 and self.is_gradient_really_supported():  # we need some wrapper
            grad_fn = self._gradient_function_wrapper

        result = self.find_minimum(initial_point=self.initial_point,
                                   var_form=self.var_form,
                                   cost_fn=self._loss,
                                   optimizer=self.optimizer,
                                   gradient_fn=grad_fn)

        # TODO remove - mimics former VariationalAlgorithm result dict so it can be extended
        self._ret = {}
        self._ret['num_optimizer_evals'] = result.optimizer_evals
        self._ret['min_val'] = result.optimal_value
        self._ret['opt_params'] = result.optimal_point
        self._ret['eval_time'] = result.optimizer_time

        if self._ret['num_optimizer_evals'] is not None and \
                self._eval_count >= self._ret['num_optimizer_evals']:
            self._eval_count = self._ret['num_optimizer_evals']
        self._eval_time = self._ret['eval_time']
        logger.info('Optimization complete in %s seconds.\nFound opt_params %s in %s evals',
                    self._eval_time, self._ret['opt_params'], self._eval_count)
        self._ret['eval_count'] = self._eval_count

        del self._batches
        del self._label_batches
        del self._batch_index

        self._ret['training_loss'] = self._ret['min_val']

    # temporary fix: this code should be unified with the gradient api in optimizer.py
    def _gradient_function_wrapper(self, theta):
        """Compute and return the gradient at the point theta.

        Args:
            theta (numpy.ndarray): 1-d array

        Returns:
            numpy.ndarray: 1-d array with the same shape as theta. The  gradient computed
        """
        epsilon = 1e-8
        f_orig = self._loss(theta)
        grad = np.zeros((len(theta),), float)
        for k, _ in enumerate(theta):
            theta[k] += epsilon
            f_new = self._loss(theta)
            grad[k] = (f_new - f_orig) / epsilon
            theta[k] -= epsilon  # recover to the center state
        if self.is_gradient_really_supported():
            self._batch_index += 1  # increment the batch after gradient callback
        return grad

    def _loss(self, theta):
        """Compute the loss over the current batch given a set of parameter
        values.

        Args:
            theta (numpy.ndarray): array of parameter values.

        Returns:
            list: list of loss values.
        """
        batch_index = self._batch_index % len(self._batches)
        predicted_probs, _ = self._get_prediction(self._batches[batch_index], theta)
        total_cost = []
        if not isinstance(predicted_probs, list):
            predicted_probs = [predicted_probs]
        for i, _ in enumerate(predicted_probs):
            curr_cost = self.cost_function(predicted_probs[i],
                                           self._label_batches[batch_index])
            total_cost.append(curr_cost)
            if self._callback is not None:
                self._callback(
                    self._eval_count,
                    theta[i * self._var_form.num_parameters:(i + 1)
                          * self._var_form.num_parameters],
                    curr_cost,
                    self._batch_index
                )
            self._eval_count += 1
        if not self.is_gradient_really_supported():
            self._batch_index += 1  # increment the batch after eval callback

        logger.debug('Intermediate batch cost: %s', sum(total_cost))
        return total_cost if len(total_cost) > 1 else total_cost[0]

    def test(self, data, labels, quantum_instance=None, minibatch_size=-1, params=None):
        """Predict the labels for the data, and test against with ground truth labels.

        Args:
            data (numpy.ndarray): NxD array, N is number of data and D is data dimension
            labels (numpy.ndarray): Nx1 array, N is number of data
            quantum_instance (QuantumInstance): quantum backend with all setting
            minibatch_size (int): the size of each minibatched accuracy evaluation
            params (list): list of parameters to populate in the variational form

        Returns:
            float: classification accuracy
        """
        # minibatch size defaults to setting in instance variable if not set
        minibatch_size = minibatch_size if minibatch_size > 0 else self._minibatch_size

        batches, label_batches = self.batch_data(data, labels, minibatch_size)
        self.batch_num = 0
        if params is None:
            params = self.optimal_params
        total_cost = 0
        total_correct = 0
        total_samples = 0

        self._quantum_instance = \
            self._quantum_instance if quantum_instance is None else quantum_instance
        for batch, label_batch in zip(batches, label_batches):
            predicted_probs, _ = self._get_prediction(batch, params)
            total_cost += self.cost_function(predicted_probs, label_batch)
            total_correct += np.sum((np.argmax(predicted_probs, axis=1) == label_batch))
            total_samples += label_batch.shape[0]
            int_accuracy = \
                np.sum((np.argmax(predicted_probs, axis=1) == label_batch)) / label_batch.shape[0]
            logger.debug('Intermediate batch accuracy: {:.2f}%'.format(int_accuracy * 100.0))
        total_accuracy = total_correct / total_samples
        logger.info('Accuracy is {:.2f}%'.format(total_accuracy * 100.0))
        self._ret['testing_accuracy'] = total_accuracy
        self._ret['test_success_ratio'] = total_accuracy
        self._ret['testing_loss'] = total_cost / len(batches)
        return total_accuracy

    def predict(self, data, quantum_instance=None, minibatch_size=-1, params=None):
        """Predict the labels for the data.

        Args:
            data (numpy.ndarray): NxD array, N is number of data, D is data dimension
            quantum_instance (QuantumInstance): quantum backend with all setting
            minibatch_size (int): the size of each minibatched accuracy evaluation
            params (list): list of parameters to populate in the variational form

        Returns:
            list: for each data point, generates the predicted probability for each class
            list: for each data point, generates the predicted label (that with the highest prob)
        """

        # minibatch size defaults to setting in instance variable if not set
        minibatch_size = minibatch_size if minibatch_size > 0 else self._minibatch_size
        batches, _ = self.batch_data(data, None, minibatch_size)
        if params is None:
            params = self.optimal_params
        predicted_probs = None
        predicted_labels = None

        self._quantum_instance = \
            self._quantum_instance if quantum_instance is None else quantum_instance
        for i, batch in enumerate(batches):
            if len(batches) > 0:  # pylint: disable=len-as-condition
                logger.debug('Predicting batch %s', i)
            batch_probs, batch_labels = self._get_prediction(batch, params)
            if not predicted_probs and not predicted_labels:
                predicted_probs = batch_probs
                predicted_labels = batch_labels
            else:
                predicted_probs = np.concatenate((predicted_probs, batch_probs))
                predicted_labels = np.concatenate((predicted_labels, batch_labels))
        self._ret['predicted_probs'] = predicted_probs
        self._ret['predicted_labels'] = predicted_labels
        return predicted_probs, predicted_labels

    def _run(self):
        self.train(self._training_dataset[0], self._training_dataset[1])

        if self._test_dataset is not None:
            self.test(self._test_dataset[0], self._test_dataset[1])

        if self._datapoints is not None:
            _, predicted_labels = self.predict(self._datapoints)
            self._ret['predicted_classes'] = map_label_to_class_name(predicted_labels,
                                                                     self._label_to_class)
        self.cleanup_parameterized_circuits()
        return self._ret

    def get_optimal_cost(self):
        """ get optimal cost """
        if 'opt_params' not in self._ret:
            raise QiskitMachineLearningError(
                            "Cannot return optimal cost before running the "
                            "algorithm to find optimal params.")
        return self._ret['min_val']

    def get_optimal_circuit(self):
        """ get optimal circuit """
        if 'opt_params' not in self._ret:
            raise QiskitMachineLearningError(
                            "Cannot find optimal circuit before running "
                            "the algorithm to find optimal params.")
        if isinstance(self._var_form, QuantumCircuit):
            param_dict = dict(zip(self._var_form_params, self._ret['opt_params']))
            return self._var_form.assign_parameters(param_dict)
        return self._var_form.construct_circuit(self._ret['opt_params'])

    def get_optimal_vector(self):
        """ get optimal vector """
        # pylint: disable=import-outside-toplevel
        from qiskit.utils.run_circuits import find_regs_by_name

        if 'opt_params' not in self._ret:
            raise QiskitMachineLearningError(
                            "Cannot find optimal vector before running "
                            "the algorithm to find optimal params.")
        qc = self.get_optimal_circuit()
        if self._quantum_instance.is_statevector:
            ret = self._quantum_instance.execute(qc)
            self._ret['min_vector'] = ret.get_statevector(qc, decimals=16)
        else:
            c = ClassicalRegister(qc.width(), name='c')
            q = find_regs_by_name(qc, 'q')
            qc.add_register(c)
            qc.barrier(q)
            qc.measure(q, c)
            ret = self._quantum_instance.execute(qc)
            self._ret['min_vector'] = ret.get_counts(qc)
        return self._ret['min_vector']

    @property
    def feature_map(self) -> Optional[QuantumCircuit]:
        """Return the feature map."""
        return self._feature_map

    @feature_map.setter
    def feature_map(self, feature_map: QuantumCircuit):
        """Set the feature map.

        Also sets the number of qubits, the internally stored feature map parameters and,
        the order of the parameters.
        """
        # patch the feature dimension to the circuit
        feature_map.feature_dimension = len(feature_map.parameters)

        # store the parameters
        self._num_qubits = feature_map.num_qubits
        self._feature_map_params = sorted(feature_map.parameters, key=lambda p: p.name)
        self._feature_map = feature_map

        if self._feature_map.feature_dimension == 0:
            warnings.warn('The feature map has no parameters that can be optimized to represent '
                          'the data. This will most likely cause the VQC to fail.')

    @property
    def optimal_params(self):
        """ returns optimal parameters """
        if 'opt_params' not in self._ret:
            raise QiskitMachineLearningError(
                "Cannot find optimal params before running the algorithm.")
        return self._ret['opt_params']

    @property
    def ret(self):
        """ returns result """
        return self._ret

    @ret.setter
    def ret(self, new_value):
        """ sets result """
        self._ret = new_value

    @property
    def label_to_class(self):
        """ returns label to class """
        return self._label_to_class

    @property
    def class_to_label(self):
        """ returns class to label """
        return self._class_to_label

    def load_model(self, file_path):
        """ load model """
        model_npz = np.load(file_path, allow_pickle=True)  # pylint: disable=unexpected-keyword-arg
        self._ret['opt_params'] = model_npz['opt_params']

    def save_model(self, file_path):
        """ save model """
        model = {'opt_params': self._ret['opt_params']}
        np.savez(file_path, **model)

    @property
    def test_dataset(self):
        """ returns test dataset """
        return self._test_dataset

    @property
    def training_dataset(self):
        """ returns training dataset """
        return self._training_dataset

    @property
    def datapoints(self):
        """ return data points """
        return self._datapoints
Пример #5
0
class QSVM:
    """Quantum SVM algorithm.

    A key concept in classification methods is that of a kernel. Data cannot typically be
    separated by a hyperplane in its original space. A common technique used to find such a
    hyperplane consists on applying a non-linear transformation function to the data.
    This function is called a *feature map*, as it transforms the raw features, or measurable
    properties, of the phenomenon or subject under study. Classifying in this new feature space
    – and, as a matter of fact, also in any other space, including the raw original one – is
    nothing more than seeing how close data points are to each other. This is the same as
    computing the inner product for each pair of data in the set. In fact we do not need to
    compute the non-linear feature map for each datum, but only the inner product of each pair
    of data points in the new feature space. This collection of inner products is called the
    **kernel** and it is perfectly possible to have feature maps that are hard to compute but
    whose kernels are not.

    The QSVM algorithm applies to classification problems that require a feature map for which
    computing the kernel is not efficient classically. This means that the required computational
    resources are expected to scale exponentially with the size of the problem.
    QSVM uses a Quantum processor to solve this problem by a direct estimation of the kernel in
    the feature space. The method used falls in the category of what is called
    **supervised learning**, consisting of a **training phase** (where the kernel is calculated
    and the support vectors obtained) and a **test or classification phase** (where new data
    without labels is classified according to the solution found in the training phase).

    Internally, QSVM will run the binary classification or multiclass classification
    based on how many classes the data has. If the data has more than 2 classes then a
    *multiclass_extension* is required to be supplied. Machine Learning provides several
    :mod:`~qiskit_machine_learning.multiclass_extensions`.

    See also https://arxiv.org/abs/1804.11326
    """

    BATCH_SIZE = 1000

    def __init__(
        self,
        feature_map: QuantumCircuit,
        training_dataset: Optional[Dict[str, np.ndarray]] = None,
        test_dataset: Optional[Dict[str, np.ndarray]] = None,
        datapoints: Optional[np.ndarray] = None,
        multiclass_extension: Optional[MulticlassExtension] = None,
        lambda2: float = 0.001,
        quantum_instance: Optional[Union[QuantumInstance, BaseBackend,
                                         Backend]] = None
    ) -> None:
        """
        Args:
            feature_map: Feature map module, used to transform data
            training_dataset: Training dataset.
            test_dataset: Testing dataset.
            datapoints: Prediction dataset.
            multiclass_extension: If number of classes is greater than 2 then a multiclass scheme
                must be supplied, in the form of a multiclass extension.
            lambda2: L2 norm regularization factor
            quantum_instance: Quantum Instance or Backend

        Raises:
            QiskitMachineLearningError: Multiclass extension not supplied when number of classes > 2
        """
        self._quantum_instance = None
        if quantum_instance:
            self.quantum_instance = quantum_instance
        # check the validity of provided arguments if possible
        if training_dataset is not None:
            is_multiclass = get_num_classes(training_dataset) > 2
            if is_multiclass:
                if multiclass_extension is None:
                    raise QiskitMachineLearningError(
                        'Dataset has more than two classes. '
                        'A multiclass extension must be provided.')
            else:
                if multiclass_extension is not None:
                    logger.warning(
                        "Dataset has just two classes. "
                        "Supplied multiclass extension will be ignored")

        self.training_dataset = None
        self.test_dataset = None
        self.datapoints = None
        self.class_to_label = None
        self.label_to_class = None
        self.num_classes = None

        self.setup_training_data(training_dataset)
        self.setup_test_data(test_dataset)
        self.setup_datapoint(datapoints)
        self.lambda2 = lambda2

        self.feature_map = feature_map
        self.num_qubits = self.feature_map.num_qubits

        # patch the feature dimension attribute to the circuit
        self.feature_map.feature_dimension = len(feature_map.parameters)
        if not hasattr(feature_map, 'ordered_parameters'):
            self.feature_map.ordered_parameters = sorted(
                feature_map.parameters, key=lambda p: p.name)
        self.feature_map_params_x = ParameterVector(
            'x', self.feature_map.feature_dimension)
        self.feature_map_params_y = ParameterVector(
            'y', self.feature_map.feature_dimension)

        qsvm_instance: Optional[Union[_QSVM_Binary, _QSVM_Multiclass]] = None
        if multiclass_extension is None:
            qsvm_instance = _QSVM_Binary(self)
        else:
            multiclass_extension.set_estimator(_QSVM_Estimator,
                                               [feature_map])  # type: ignore
            qsvm_instance = _QSVM_Multiclass(self, multiclass_extension)

        self.instance = qsvm_instance

    @property
    def random(self):
        """Return a numpy random."""
        return algorithm_globals.random

    def run(self,
            quantum_instance: Optional[Union[QuantumInstance, Backend,
                                             BaseBackend]] = None,
            **kwargs) -> Dict:
        """Execute the algorithm with selected backend.
        Args:
            quantum_instance: the experimental setting.
            kwargs (dict): kwargs
        Returns:
            dict: results of an algorithm.
        Raises:
            QiskitMachineLearningError: If a quantum instance or
                                        backend has not been provided
        """
        if quantum_instance is None and self.quantum_instance is None:
            raise QiskitMachineLearningError(
                "A QuantumInstance or Backend "
                "must be supplied to run the quantum algorithm.")
        if isinstance(quantum_instance, (BaseBackend, Backend)):
            self.set_backend(quantum_instance, **kwargs)
        else:
            if quantum_instance is not None:
                self.quantum_instance = quantum_instance

        return self._run()

    @property
    def quantum_instance(self) -> Optional[QuantumInstance]:
        """ Returns quantum instance. """
        return self._quantum_instance

    @quantum_instance.setter
    def quantum_instance(
        self, quantum_instance: Union[QuantumInstance, BaseBackend,
                                      Backend]) -> None:
        """ Sets quantum instance. """
        if isinstance(quantum_instance, (BaseBackend, Backend)):
            quantum_instance = QuantumInstance(quantum_instance)
        self._quantum_instance = quantum_instance

    def set_backend(self, backend: Union[Backend, BaseBackend],
                    **kwargs) -> None:
        """ Sets backend with configuration. """
        self.quantum_instance = QuantumInstance(backend)
        self.quantum_instance.set_config(**kwargs)

    @property
    def backend(self) -> Union[Backend, BaseBackend]:
        """ Returns backend. """
        return self.quantum_instance.backend

    @backend.setter
    def backend(self, backend: Union[Backend, BaseBackend]):
        """ Sets backend without additional configuration. """
        self.set_backend(backend)

    @staticmethod
    def _construct_circuit(x,
                           feature_map,
                           measurement,
                           is_statevector_sim=False):
        """If `is_statevector_sim` is True, we only build the circuits for Psi(x1)|0> rather than
        Psi(x2)^dagger Psi(x1)|0>.
        """
        x1, x2 = x
        if len(x1) != len(x2):
            raise ValueError("x1 and x2 must be the same dimension.")

        q = QuantumRegister(feature_map.num_qubits, 'q')
        c = ClassicalRegister(feature_map.num_qubits, 'c')
        qc = QuantumCircuit(q, c)

        # write input state from sample distribution
        psi_x1 = _assign_parameters(feature_map, x1)
        qc.append(psi_x1.to_instruction(), qc.qubits)

        if not is_statevector_sim:
            # write input state from sample distribution
            psi_x2_dag = _assign_parameters(feature_map, x2)
            qc.append(psi_x2_dag.to_instruction().inverse(), qc.qubits)

            if measurement:
                qc.barrier(q)
                qc.measure(q, c)
        return qc

    @staticmethod
    def _compute_overlap(idx, results, is_statevector_sim, measurement_basis):
        if is_statevector_sim:
            i, j = idx
            # TODO: qiskit-terra did not support np.int64 to lookup result
            v_a = results.get_statevector(int(i))
            v_b = results.get_statevector(int(j))
            # |<0|Psi^daggar(y) x Psi(x)|0>|^2, take the amplitude
            tmp = np.vdot(v_a, v_b)
            kernel_value = np.vdot(tmp, tmp).real  # pylint: disable=no-member
        else:
            result = results.get_counts(idx)
            kernel_value = result.get(measurement_basis, 0) / sum(
                result.values())
        return kernel_value

    def construct_circuit(self, x1, x2, measurement=False):
        """
        Generate inner product of x1 and x2 with the given feature map.

        The dimension of x1 and x2 must be the same.

        Args:
            x1 (numpy.ndarray): data points, 1-D array, dimension is D
            x2 (numpy.ndarray): data points, 1-D array, dimension is D
            measurement (bool): add measurement gates at the end
        Returns:
            QuantumCircuit: constructed circuit
        """
        return QSVM._construct_circuit((x1, x2), self.feature_map, measurement)

    @staticmethod
    def get_kernel_matrix(quantum_instance: QuantumInstance,
                          feature_map: QuantumCircuit,
                          x1_vec: np.ndarray,
                          x2_vec: Optional[np.ndarray] = None,
                          enforce_psd: bool = True) -> np.ndarray:
        """
        Construct kernel matrix, if x2_vec is None, self-innerproduct is conducted.

        Notes:
            When using `statevector_simulator`,
            we only build the circuits for Psi(x1)|0> rather than
            Psi(x2)^dagger Psi(x1)|0>, and then we perform the inner product classically.
            That is, for `statevector_simulator`,
            the total number of circuits will be O(N) rather than
            O(N^2) for `qasm_simulator`.

        Args:
            quantum_instance: quantum backend with all settings
            feature_map: a feature map that maps data to feature space
            x1_vec: data points, 2-D array, N1xD, where N1 is the number of data,
                                    D is the feature dimension
            x2_vec: data points, 2-D array, N2xD, where N2 is the number of data,
                                    D is the feature dimension
            enforce_psd: enforces that the kernel matrix is positive semi-definite by setting
                                negative eigenvalues to zero. This is only applied in the symmetric
                                case, i.e., if `x2_vec == None`.
        Returns:
            2-D matrix, N1xN2
        """
        use_parameterized_circuits = True
        if x2_vec is None:
            is_symmetric = True
            x2_vec = x1_vec
        else:
            is_symmetric = False

        is_statevector_sim = quantum_instance.is_statevector

        measurement = not is_statevector_sim
        measurement_basis = '0' * feature_map.num_qubits
        mat = np.ones((x1_vec.shape[0], x2_vec.shape[0]))

        # get all indices
        if is_symmetric:
            mus, nus = np.triu_indices(x1_vec.shape[0],
                                       k=1)  # remove diagonal term
        else:
            mus, nus = np.indices((x1_vec.shape[0], x2_vec.shape[0]))
            mus = np.asarray(mus.flat)
            nus = np.asarray(nus.flat)

        if is_statevector_sim:
            if is_symmetric:
                to_be_computed_data = x1_vec
            else:
                to_be_computed_data = np.concatenate((x1_vec, x2_vec))

            if use_parameterized_circuits:
                # build parameterized circuits, it could be slower for building circuit
                # but overall it should be faster since it only transpile one circuit
                feature_map_params = ParameterVector(
                    'x', feature_map.feature_dimension)
                parameterized_circuit = QSVM._construct_circuit(
                    (feature_map_params, feature_map_params),
                    feature_map,
                    measurement,
                    is_statevector_sim=is_statevector_sim)
                parameterized_circuit = quantum_instance.transpile(
                    parameterized_circuit)[0]
                circuits = [
                    parameterized_circuit.assign_parameters(
                        {feature_map_params: x}) for x in to_be_computed_data
                ]
            else:
                #  the second x is redundant
                to_be_computed_data_pair = [(x, x)
                                            for x in to_be_computed_data]
                if logger.isEnabledFor(logging.DEBUG):
                    logger.debug("Building circuits:")
                    TextProgressBar(sys.stderr)
                circuits = parallel_map(
                    QSVM._construct_circuit,
                    to_be_computed_data_pair,
                    task_args=(feature_map, measurement, is_statevector_sim),
                    num_processes=algorithm_globals.num_processes)

            results = quantum_instance.execute(
                circuits, had_transpiled=use_parameterized_circuits)

            if logger.isEnabledFor(logging.DEBUG):
                logger.debug("Calculating overlap:")
                TextProgressBar(sys.stderr)

            offset = 0 if is_symmetric else len(x1_vec)
            matrix_elements = parallel_map(
                QSVM._compute_overlap,
                list(zip(mus, nus + offset)),
                task_args=(results, is_statevector_sim, measurement_basis),
                num_processes=algorithm_globals.num_processes)

            for i, j, value in zip(mus, nus, matrix_elements):
                mat[i, j] = value
                if is_symmetric:
                    mat[j, i] = mat[i, j]
        else:
            for idx in range(0, len(mus), QSVM.BATCH_SIZE):
                to_be_computed_data_pair = []
                to_be_computed_index = []
                for sub_idx in range(idx, min(idx + QSVM.BATCH_SIZE,
                                              len(mus))):
                    i = mus[sub_idx]
                    j = nus[sub_idx]
                    x1 = x1_vec[i]
                    x2 = x2_vec[j]
                    if not np.all(x1 == x2):
                        to_be_computed_data_pair.append((x1, x2))
                        to_be_computed_index.append((i, j))

                if use_parameterized_circuits:
                    # build parameterized circuits, it could be slower for building circuit
                    # but overall it should be faster since it only transpile one circuit
                    feature_map_params_x = ParameterVector(
                        'x', feature_map.feature_dimension)
                    feature_map_params_y = ParameterVector(
                        'y', feature_map.feature_dimension)
                    parameterized_circuit = QSVM._construct_circuit(
                        (feature_map_params_x, feature_map_params_y),
                        feature_map,
                        measurement,
                        is_statevector_sim=is_statevector_sim)
                    parameterized_circuit = quantum_instance.transpile(
                        parameterized_circuit)[0]
                    circuits = [
                        parameterized_circuit.assign_parameters({
                            feature_map_params_x:
                            x,
                            feature_map_params_y:
                            y
                        }) for x, y in to_be_computed_data_pair
                    ]
                else:
                    if logger.isEnabledFor(logging.DEBUG):
                        logger.debug("Building circuits:")
                        TextProgressBar(sys.stderr)
                    circuits = parallel_map(
                        QSVM._construct_circuit,
                        to_be_computed_data_pair,
                        task_args=(feature_map, measurement),
                        num_processes=algorithm_globals.num_processes)

                results = quantum_instance.execute(
                    circuits, had_transpiled=use_parameterized_circuits)

                if logger.isEnabledFor(logging.DEBUG):
                    logger.debug("Calculating overlap:")
                    TextProgressBar(sys.stderr)
                matrix_elements = parallel_map(
                    QSVM._compute_overlap,
                    range(len(circuits)),
                    task_args=(results, is_statevector_sim, measurement_basis),
                    num_processes=algorithm_globals.num_processes)

                for (i, j), value in zip(to_be_computed_index,
                                         matrix_elements):
                    mat[i, j] = value
                    if is_symmetric:
                        mat[j, i] = mat[i, j]

        if enforce_psd and is_symmetric and not is_statevector_sim:
            # Find the closest positive semi-definite approximation to kernel matrix, in case it is
            # symmetric. The (symmetric) matrix should always be positive semi-definite by
            # construction, but this can be violated in case of noise, such as sampling noise, thus,
            # the adjustment is only done if NOT using the statevector simulation.
            D, U = np.linalg.eig(mat)
            mat = U @ np.diag(np.maximum(0, D)) @ U.transpose()

        return mat

    def construct_kernel_matrix(self,
                                x1_vec,
                                x2_vec=None,
                                quantum_instance=None):
        """
        Construct kernel matrix, if x2_vec is None, self-innerproduct is conducted.

        Notes:
            When using `statevector_simulator`, we only build
            the circuits for Psi(x1)|0> rather than
            Psi(x2)^dagger Psi(x1)|0>, and then we perform the inner product classically.
            That is, for `statevector_simulator`, the total number
            of circuits will be O(N) rather than
            O(N^2) for `qasm_simulator`.

        Args:
            x1_vec (numpy.ndarray): data points, 2-D array, N1xD, where N1 is the number of data,
                                    D is the feature dimension
            x2_vec (numpy.ndarray): data points, 2-D array, N2xD, where N2 is the number of data,
                                    D is the feature dimension
            quantum_instance (QuantumInstance): quantum backend with all settings

        Returns:
            numpy.ndarray: 2-D matrix, N1xN2

        Raises:
            QiskitMachineLearningError: Quantum instance is not present.
        """
        self._quantum_instance = self._quantum_instance \
            if quantum_instance is None else quantum_instance
        if self._quantum_instance is None:
            raise QiskitMachineLearningError(
                "Either setup quantum instance or provide it in the parameter."
            )

        return QSVM.get_kernel_matrix(self._quantum_instance, self.feature_map,
                                      x1_vec, x2_vec)

    def train(self, data, labels, quantum_instance=None):
        """
        Train the svm.

        Args:
            data (numpy.ndarray): NxD array, where N is the number of data,
                                  D is the feature dimension.
            labels (numpy.ndarray): Nx1 array, where N is the number of data
            quantum_instance (QuantumInstance): quantum backend with all setting

        Raises:
            QiskitMachineLearningError: Quantum instance is not present.
        """
        self._quantum_instance = self._quantum_instance \
            if quantum_instance is None else quantum_instance
        if self._quantum_instance is None:
            raise QiskitMachineLearningError(
                "Either setup quantum instance or provide it in the parameter."
            )
        self.instance.train(data, labels)

    def test(self, data, labels, quantum_instance=None):
        """
        Test the svm.

        Args:
            data (numpy.ndarray): NxD array, where N is the number of data,
                                  D is the feature dimension.
            labels (numpy.ndarray): Nx1 array, where N is the number of data
            quantum_instance (QuantumInstance): quantum backend with all setting

        Returns:
            float: accuracy

        Raises:
            QiskitMachineLearningError: Quantum instance is not present.
        """

        self._quantum_instance = self._quantum_instance \
            if quantum_instance is None else quantum_instance
        if self._quantum_instance is None:
            raise QiskitMachineLearningError(
                "Either setup quantum instance or provide it in the parameter."
            )
        return self.instance.test(data, labels)

    def predict(self, data, quantum_instance=None):
        """
        Predict using the svm.

        Args:
            data (numpy.ndarray): NxD array, where N is the number of data,
                                  D is the feature dimension.
            quantum_instance (QuantumInstance): quantum backend with all setting

        Returns:
            numpy.ndarray: predicted labels, Nx1 array

        Raises:
            QiskitMachineLearningError: Quantum instance is not present.
        """
        self._quantum_instance = self._quantum_instance \
            if quantum_instance is None else quantum_instance
        if self._quantum_instance is None:
            raise QiskitMachineLearningError(
                "Either setup quantum instance or provide it in the parameter."
            )
        return self.instance.predict(data)

    def _run(self):
        return self.instance.run()

    @property
    def ret(self):
        """ returns result """
        return self.instance.ret

    @ret.setter
    def ret(self, new_value):
        """ sets result """
        self.instance.ret = new_value

    def load_model(self, file_path):
        """Load a model from a file path.

        Args:
            file_path (str): the path of the saved model.
        """
        self.instance.load_model(file_path)

    def save_model(self, file_path):
        """Save the model to a file path.

        Args:
            file_path (str): a path to save the model.
        """
        self.instance.save_model(file_path)

    def setup_training_data(self, training_dataset):
        """Setup training data, if the data were there, they would be overwritten.

        Args:
            training_dataset (dict): training dataset.
        """
        if training_dataset is not None:
            self.training_dataset, self.class_to_label = \
                split_dataset_to_data_and_labels(training_dataset)
            self.label_to_class = {
                label: class_name
                for class_name, label in self.class_to_label.items()
            }
            self.num_classes = len(list(self.class_to_label.keys()))

    def setup_test_data(self, test_dataset):
        """Setup test data, if the data were there, they would be overwritten.

        Args:
            test_dataset (dict): test dataset.
        """
        if test_dataset is not None:
            if self.class_to_label is None:
                logger.warning(
                    "The mapping from the class name to the label is missed, "
                    "regenerate it but it might be mismatched to previous mapping."
                )
                self.test_dataset, self.class_to_label = \
                    split_dataset_to_data_and_labels(test_dataset)
            else:
                self.test_dataset = \
                    split_dataset_to_data_and_labels(test_dataset, self.class_to_label)

    def setup_datapoint(self, datapoints):
        """Setup data points, if the data were there, they would be overwritten.

        Args:
            datapoints (numpy.ndarray): prediction dataset.
        """
        if datapoints is not None:
            if not isinstance(datapoints, np.ndarray):
                datapoints = np.asarray(datapoints)
            self.datapoints = datapoints
class TestTPBGroupedWeightedPauliOperator(QiskitOpflowTestCase):
    """TPBGroupedWeightedPauliOperator tests."""

    def setUp(self):
        super().setUp()
        seed = 1
        aqua_globals.random_seed = seed

        self.num_qubits = 3
        paulis = [Pauli.from_label(pauli_label)
                  for pauli_label in itertools.product('IXYZ', repeat=self.num_qubits)]
        weights = aqua_globals.random.random(len(paulis))
        self.qubit_op = WeightedPauliOperator.from_list(paulis, weights)
        self.var_form = EfficientSU2(self.qubit_op.num_qubits, reps=1)

        qasm_simulator = BasicAer.get_backend('qasm_simulator')
        self.quantum_instance_qasm = QuantumInstance(qasm_simulator, shots=65536,
                                                     seed_simulator=seed, seed_transpiler=seed)

        statevector_simulator = BasicAer.get_backend('statevector_simulator')
        self.quantum_instance_statevector = \
            QuantumInstance(statevector_simulator, shots=1,
                            seed_simulator=seed, seed_transpiler=seed)

    def test_sorted_grouping(self):
        """Test with color grouping approach."""
        num_qubits = 2
        paulis = [Pauli.from_label(pauli_label)
                  for pauli_label in itertools.product('IXYZ', repeat=num_qubits)]
        weights = aqua_globals.random.random(len(paulis))
        op = WeightedPauliOperator.from_list(paulis, weights)
        grouped_op = op_converter.to_tpb_grouped_weighted_pauli_operator(
            op, TPBGroupedWeightedPauliOperator.sorted_grouping)

        # check all paulis are still existed.
        for g_p in grouped_op.paulis:
            passed = False
            for pauli in op.paulis:
                if pauli[1] == g_p[1]:
                    passed = pauli[0] == g_p[0]
                    break
            self.assertTrue(passed,
                            "non-existed paulis in grouped_paulis: {}".format(g_p[1].to_label()))

        # check the number of basis of grouped
        # one should be less than and equal to the original one.
        self.assertGreaterEqual(len(op.basis), len(grouped_op.basis))

    def test_unsorted_grouping(self):
        """Test with normal grouping approach."""

        num_qubits = 4
        paulis = [Pauli.from_label(pauli_label)
                  for pauli_label in itertools.product('IXYZ', repeat=num_qubits)]
        weights = aqua_globals.random.random(len(paulis))
        op = WeightedPauliOperator.from_list(paulis, weights)
        grouped_op = op_converter.to_tpb_grouped_weighted_pauli_operator(
            op, TPBGroupedWeightedPauliOperator.unsorted_grouping)

        for g_p in grouped_op.paulis:
            passed = False
            for pauli in op.paulis:
                if pauli[1] == g_p[1]:
                    passed = pauli[0] == g_p[0]
                    break
            self.assertTrue(passed,
                            "non-existed paulis in grouped_paulis: {}".format(g_p[1].to_label()))

        self.assertGreaterEqual(len(op.basis), len(grouped_op.basis))

    def test_chop(self):
        """ chop test """
        paulis = [Pauli.from_label(x) for x in ['IIXX', 'ZZXX', 'ZZZZ', 'XXZZ', 'XXXX', 'IXXX']]
        coeffs = [0.2, 0.3, 0.4, 0.5, 0.6, 0.7]
        op = WeightedPauliOperator.from_list(paulis, coeffs)
        grouped_op = op_converter.to_tpb_grouped_weighted_pauli_operator(
            op, TPBGroupedWeightedPauliOperator.sorted_grouping)

        original_num_basis = len(grouped_op.basis)
        chopped_grouped_op = grouped_op.chop(0.35, copy=True)
        self.assertLessEqual(len(chopped_grouped_op.basis), 3)
        self.assertLessEqual(len(chopped_grouped_op.basis), original_num_basis)
        # ZZXX group is remove
        for b, _ in chopped_grouped_op.basis:
            self.assertFalse(b.to_label() == 'ZZXX')

        chopped_grouped_op = grouped_op.chop(0.55, copy=True)
        self.assertLessEqual(len(chopped_grouped_op.basis), 1)
        self.assertLessEqual(len(chopped_grouped_op.basis), original_num_basis)

        for b, _ in chopped_grouped_op.basis:
            self.assertFalse(b.to_label() == 'ZZXX')
            self.assertFalse(b.to_label() == 'ZZZZ')
            self.assertFalse(b.to_label() == 'XXZZ')

    def test_evaluate_qasm_mode(self):
        """ evaluate qasm mode test """
        wave_function = self.var_form.assign_parameters(
            np.array(aqua_globals.random.standard_normal(self.var_form.num_parameters)))
        wave_fn_statevector = \
            self.quantum_instance_statevector.execute(wave_function).get_statevector(wave_function)
        reference = self.qubit_op.copy().evaluate_with_statevector(wave_fn_statevector)

        shots = 65536 // len(self.qubit_op.paulis)
        self.quantum_instance_qasm.set_config(shots=shots)
        circuits = self.qubit_op.construct_evaluation_circuit(wave_function=wave_function,
                                                              statevector_mode=False)
        result = self.quantum_instance_qasm.execute(circuits)
        pauli_value = self.qubit_op.evaluate_with_result(result=result, statevector_mode=False)
        grouped_op = op_converter.to_tpb_grouped_weighted_pauli_operator(
            self.qubit_op, TPBGroupedWeightedPauliOperator.sorted_grouping)
        shots = 65536 // grouped_op.num_groups
        self.quantum_instance_qasm.set_config(shots=shots)
        circuits = grouped_op.construct_evaluation_circuit(wave_function=wave_function,
                                                           statevector_mode=False)
        grouped_pauli_value = grouped_op.evaluate_with_result(
            result=self.quantum_instance_qasm.execute(circuits), statevector_mode=False)

        self.assertGreaterEqual(reference[0].real,
                                grouped_pauli_value[0].real - 3 * grouped_pauli_value[1].real)
        self.assertLessEqual(reference[0].real,
                             grouped_pauli_value[0].real + 3 * grouped_pauli_value[1].real)
        # this check assure the std of grouped pauli is
        # less than pauli mode under a fixed amount of total shots
        self.assertLessEqual(grouped_pauli_value[1].real, pauli_value[1].real)

    def test_equal(self):
        """ equal test """
        gop_1 = op_converter.to_tpb_grouped_weighted_pauli_operator(
            self.qubit_op, TPBGroupedWeightedPauliOperator.sorted_grouping)
        gop_2 = op_converter.to_tpb_grouped_weighted_pauli_operator(
            self.qubit_op, TPBGroupedWeightedPauliOperator.unsorted_grouping)

        self.assertEqual(gop_1, gop_2)
Пример #7
0
class Grover:
    r"""Grover's Search algorithm.

    Grover's Search [1, 2] is a well known quantum algorithm for that can be used for
    searching through unstructured collections of records for particular targets
    with quadratic speedup compared to classical algorithms.

    Given a set :math:`X` of :math:`N` elements :math:`X=\{x_1,x_2,\ldots,x_N\}`
    and a boolean function :math:`f : X \rightarrow \{0,1\}`, the goal of an
    unstructured-search problem is to find an element :math:`x^* \in X` such
    that :math:`f(x^*)=1`.

    The search is called *unstructured* because there are no guarantees as to how
    the database is ordered.  On a sorted database, for instance, one could perform
    binary search to find an element in :math:`\mathbb{O}(\log N)` worst-case time.
    Instead, in an unstructured-search problem, there is no prior knowledge about
    the contents of the database. With classical circuits, there is no alternative
    but to perform a linear number of queries to find the target element.
    Conversely, Grover's Search algorithm allows to solve the unstructured-search
    problem on a quantum computer in :math:`\mathcal{O}(\sqrt{N})` queries.

    To carry out this search a so-called oracle is required, that flags a good element/state.
    The action of the oracle :math:`\mathcal{S}_f` is

    .. math::

        \mathcal{S}_f |x\rangle = (-1)^{f(x)} |x\rangle,

    i.e. it flips the phase of the state :math:`|x\rangle` if :math:`x` is a hit.
    The details of how :math:`S_f` works are unimportant to the algorithm; Grover's
    search algorithm treats the oracle as a black box.

    This class supports oracles in form of :class:`~qiskit.QuantumCircuit`

    With oracle at hand, Grover's Search constructs the Grover operator to amplify the amplitudes
    of the good states:

    .. math::

        \mathcal{Q} = H^{\otimes n} \mathcal{S}_0 H^{\otimes n} \mathcal{S}_f
                    = D \mathcal{S}_f,

    where :math:`\mathcal{S}_0` flips the phase of the all-zero state and acts as identity
    on all other states. Sometimes the first three operands are summarized as diffusion operator,
    which implements a reflection over the equal superposition state.

    If the number of solutions is known, we can calculate how often :math:`\mathcal{Q}` should be
    applied to find a solution with very high probability, see the method
    `optimal_num_iterations`. If the number of solutions is unknown, the algorithm tries different
    powers of Grover's operator, see the `iterations` argument, and after each iteration checks
    if a good state has been measured using `good_state`.

    The generalization of Grover's Search, Quantum Amplitude Amplification [3] uses a modified
    version of :math:`\mathcal{Q}` where the diffusion operator does not reflect about the
    equal superposition state, but another state specified via an operator :math:`\mathcal{A}`:

    .. math::

        \mathcal{Q} = \mathcal{A} \mathcal{S}_0 \mathcal{A}^\dagger \mathcal{S}_f.

    For more information, see the :class:`~qiskit.circuit.library.GroverOperator` in the
    circuit library.

    References:
        [1]: L. K. Grover (1996), A fast quantum mechanical algorithm for database search,
            `arXiv:quant-ph/9605043 <https://arxiv.org/abs/quant-ph/9605043>`_.
        [2]: I. Chuang & M. Nielsen, Quantum Computation and Quantum Information,
            Cambridge: Cambridge University Press, 2000. Chapter 6.1.2.
        [3]: Brassard, G., Hoyer, P., Mosca, M., & Tapp, A. (2000).
            Quantum Amplitude Amplification and Estimation.
            `arXiv:quant-ph/0005055 <http://arxiv.org/abs/quant-ph/0005055>`_.

    """
    @name_args([('oracle', ), ('state_preparation', {
        bool: 'incremental'
    }), ('iterations', ), ('post_processing', {
        float: 'lam'
    }), ('grover_operator', {
        list: 'rotation_counts'
    }), ('quantum_instance', {
        str: 'mct_mode'
    }),
                ('incremental', {
                    (Backend, BaseBackend, QuantumInstance): 'quantum_instance'
                })],
               skip=1)  # skip the argument 'self'
    def __init__(
        self,
        oracle: Union[QuantumCircuit, Statevector],
        good_state: Optional[Union[Callable[[str], bool], List[str], List[int],
                                   Statevector]] = None,
        state_preparation: Optional[QuantumCircuit] = None,
        iterations: Union[int, List[int]] = 1,
        sample_from_iterations: bool = False,
        post_processing: Callable[[List[int]], List[int]] = None,
        grover_operator: Optional[QuantumCircuit] = None,
        quantum_instance: Optional[Union[QuantumInstance, Backend,
                                         BaseBackend]] = None,
        incremental: bool = False,
        num_iterations: Optional[int] = None,
        lam: Optional[float] = None,
        rotation_counts: Optional[List[int]] = None,
        mct_mode: Optional[str] = None,
    ) -> None:
        # pylint: disable=line-too-long
        r"""
        Args:
            oracle: The oracle to flip the phase of good states, :math:`\mathcal{S}_f`.
            good_state: A callable to check if a given measurement corresponds to a good state.
                For convenience, a list of bitstrings, a list of integer or statevector can be
                passed instead of a function. If the input is a list of bitstrings, each bitstrings
                in the list represents a good state. If the input is a list of integer,
                each integer represent the index of the good state to be :math:`|1\rangle`.
                If it is a :class:`~qiskit.quantum_info.Statevector`, it represents a superposition
                of all good states.
            state_preparation: The state preparation :math:`\mathcal{A}`. If None then Grover's
                 Search by default uses uniform superposition.
            iterations: Specify the number of iterations/power of Grover's operator to be checked.
                It the number of solutions is known, this should be an integer specifying the
                optimal number of iterations (see ``optimal_num_iterations``). Alternatively,
                this can be a list of powers to check.
            sample_from_iterations: If True, instead of taking the values in ``iterations`` as
                powers of the Grover operator, a random integer sample between 0 and smaller value
                than the iteration is used as a power, see [1], Section 4.
            post_processing: An optional post processing applied to the top measurement. Can be used
                e.g. to convert from the bit-representation of the measurement `[1, 0, 1]` to a
                DIMACS CNF format `[1, -2, 3]`.
            grover_operator: A circuit implementing the Grover operator :math:`\mathcal{Q}`.
                If None, the operator is constructed automatically using the
                :class:`~qiskit.circuit.library.GroverOperator` from the circuit library.
            quantum_instance: A Quantum Instance or Backend to run the circuits.
            incremental: DEPRECATED, use ``iterations`` instead.
                Whether to use incremental search mode (True) or not (False).
                Supplied *num_iterations* is ignored when True and instead the search task will
                be carried out in successive rounds, using circuits built with incrementally
                higher number of iterations for the repetition of the amplitude amplification
                until a target is found or the maximal number :math:`\log N` (:math:`N` being the
                total number of elements in the set from the oracle used) of iterations is
                reached. The implementation follows Section 4 of [2].
            num_iterations: DEPRECATED, use ``iterations`` instead.
                How many times the marking and reflection phase sub-circuit is
                repeated to amplify the amplitude(s) of the target(s). Has a minimum value of 1.
            lam: DEPRECATED, use ``iterations`` instead.
                For incremental search mode, the maximum number of repetition of amplitude
                amplification increases by factor lam in every round,
                :math:`R_{i+1} = lam \times R_{i}`. If this parameter is not set, the default
                value lam = 1.34 is used, which is proved to be optimal [1].
            rotation_counts: DEPRECATED, use ``iterations`` instead.
                For incremental mode, if rotation_counts is defined, parameter *lam*
                is ignored. rotation_counts is the list of integers that defines the number of
                repetition of amplitude amplification for each round.
            mct_mode: DEPRECATED, pass a custom ``grover_operator`` instead.
                Multi-Control Toffoli mode ('basic' | 'basic-dirty-ancilla' |
                'advanced' | 'noancilla')

        Raises:
            TypeError: If ``init_state`` is of unsupported type or is of type ``InitialState` but
                the oracle is not of type ``Oracle``.
            TypeError: If ``oracle`` is of unsupported type.


        References:
            [1]: Boyer et al., Tight bounds on quantum searching
                 `<https://arxiv.org/abs/quant-ph/9605034>`_
        """
        self._quantum_instance = None
        if quantum_instance:
            self.quantum_instance = quantum_instance

        _check_deprecated_args(mct_mode, rotation_counts, lam, num_iterations)

        if mct_mode is None:
            mct_mode = 'noancilla'

        self._oracle = oracle

        # Construct GroverOperator circuit
        if grover_operator is not None:
            self._grover_operator = grover_operator
        else:
            # wrap in method to hide the logic of handling deprecated arguments, can be simplified
            # once the deprecated arguments are removed
            self._grover_operator = _construct_grover_operator(
                oracle, state_preparation, mct_mode)

        max_iterations = np.ceil(
            2**(len(self._grover_operator.reflection_qubits) / 2))
        if incremental:  # TODO remove 3 months after 0.8.0
            if rotation_counts is not None:
                iterations = rotation_counts
                self._sample_from_iterations = False
            else:
                if lam is None:
                    lam = 1.34

                iterations = []
                self._sample_from_iterations = True
                power = 1.0
                while power < max_iterations:
                    iterations.append(int(power))
                    power = lam * power

        elif num_iterations is not None:  # TODO remove 3 months after 0.8.0
            iterations = [num_iterations]
        elif not isinstance(iterations, list):
            iterations = [iterations]
        # else: already a list

        # cutoff if max_iterations is exceeded (legacy code, should considered for removal?)
        self._iterations = []
        for iteration in iterations:
            self._iterations += [iteration]
            if iteration > max_iterations:
                break

        # check the type of good_state
        _check_is_good_state(good_state)

        self._is_good_state = good_state
        self._sample_from_iterations = sample_from_iterations
        self._post_processing = post_processing
        self._incremental = incremental
        self._lam = lam
        self._rotation_counts = rotation_counts

        if incremental or (isinstance(iterations, list)
                           and len(iterations) > 1):
            logger.debug('Incremental mode specified, \
                ignoring "num_iterations" and "num_solutions".')

        self._ret = GroverResult()

    @property
    def quantum_instance(self) -> Optional[QuantumInstance]:
        """ Returns quantum instance. """
        return self._quantum_instance

    @quantum_instance.setter
    def quantum_instance(
        self, quantum_instance: Union[QuantumInstance, BaseBackend,
                                      Backend]) -> None:
        """ Sets quantum instance. """
        if isinstance(quantum_instance, (BaseBackend, Backend)):
            quantum_instance = QuantumInstance(quantum_instance)
        self._quantum_instance = quantum_instance

    @staticmethod
    def optimal_num_iterations(num_solutions: int, num_qubits: int) -> int:
        """Return the optimal number of iterations, if the number of solutions is known.

        Args:
            num_solutions: The number of solutions.
            num_qubits: The number of qubits used to encode the states.

        Returns:
            The optimal number of iterations for Grover's algorithm to succeed.
        """
        return math.floor(np.pi * np.sqrt(2**num_qubits / num_solutions) / 4)

    def _run_experiment(self, power):
        """Run a grover experiment for a given power of the Grover operator."""
        if self._quantum_instance.is_statevector:
            qc = self.construct_circuit(power, measurement=False)
            result = self._quantum_instance.execute(qc)
            statevector = result.get_statevector(qc)
            num_bits = len(self._grover_operator.reflection_qubits)
            # trace out work qubits
            if qc.width() != num_bits:
                rho = partial_trace(statevector, range(num_bits, qc.width()))
                statevector = np.diag(rho.data)
            max_amplitude = max(statevector.max(), statevector.min(), key=abs)
            max_amplitude_idx = np.where(statevector == max_amplitude)[0][0]
            top_measurement = np.binary_repr(max_amplitude_idx, num_bits)

        else:
            qc = self.construct_circuit(power, measurement=True)
            measurement = self._quantum_instance.execute(qc).get_counts(qc)
            self._ret.measurement = dict(measurement)
            top_measurement = max(measurement.items(),
                                  key=operator.itemgetter(1))[0]

        self._ret.top_measurement = top_measurement

        # as_list = [int(bit) for bit in top_measurement]
        # return self.post_processing(as_list), self.is_good_state(top_measurement)
        return self.post_processing(top_measurement), self.is_good_state(
            top_measurement)

    def is_good_state(self, bitstr: str) -> bool:
        """Check whether a provided bitstring is a good state or not.

        Args:
            bitstr: The measurement as bitstring.

        Returns:
            True if the measurement is a good state, False otherwise.
        """
        if callable(self._is_good_state):
            return self._is_good_state(bitstr)
        elif isinstance(self._is_good_state, list):
            if all(
                    isinstance(good_bitstr, str)
                    for good_bitstr in self._is_good_state):
                return bitstr in self._is_good_state
            else:
                return all(bitstr[good_index] == '1'  # type:ignore
                           for good_index in self._is_good_state)
        # else isinstance(self._is_good_state, Statevector) must be True
        return bitstr in self._is_good_state.probabilities_dict()

    def post_processing(self, measurement: List[int]) -> List[int]:
        """Do the post-processing to the measurement result

        Args:
            measurement: The measurement as list of int.

        Returns:
            Do the post-processing based on the post_processing argument.
            Just return the input bitstr
        """
        if self._post_processing is not None:
            return self._post_processing(measurement)

        return measurement

    def construct_circuit(self,
                          power: Optional[int] = None,
                          measurement: bool = False) -> QuantumCircuit:
        """Construct the circuit for Grover's algorithm with ``power`` Grover operators.

        Args:
            power: The number of times the Grover operator is repeated. If None, this argument
                is set to the first item in ``iterations``.
            measurement: Boolean flag to indicate if measurement should be included in the circuit.

        Returns:
            QuantumCircuit: the QuantumCircuit object for the constructed circuit
        """
        if power is None:
            power = self._iterations[0]

        qc = QuantumCircuit(self._grover_operator.num_qubits,
                            name='Grover circuit')
        qc.compose(self._grover_operator.state_preparation, inplace=True)
        if power > 0:
            qc.compose(self._grover_operator.power(power), inplace=True)

        if measurement:
            measurement_cr = ClassicalRegister(
                len(self._grover_operator.reflection_qubits))
            qc.add_register(measurement_cr)
            qc.measure(self._grover_operator.reflection_qubits, measurement_cr)

        self._ret.circuit = qc
        return qc

    def run(self,
            quantum_instance: Optional[Union[QuantumInstance, Backend,
                                             BaseBackend]] = None,
            **kwargs) -> 'GroverResult':
        """Execute the algorithm with selected backend.

        Args:
            quantum_instance: the experimental setting.
            kwargs (dict): kwargs
        Returns:
            results of an algorithm.
        Raises:
            AlgorithmError: If a quantum instance or backend has not been provided
        """
        if quantum_instance is None and self.quantum_instance is None:
            raise AlgorithmError(
                "A QuantumInstance or Backend "
                "must be supplied to run the quantum algorithm.")
        if isinstance(quantum_instance, (BaseBackend, Backend)):
            self.quantum_instance = QuantumInstance(quantum_instance)
            self.quantum_instance.set_config(**kwargs)
        else:
            if quantum_instance is not None:
                self.quantum_instance = quantum_instance

        return self._run()

    def _run(self) -> 'GroverResult':
        # If ``rotation_counts`` is specified, run Grover's circuit for the powers specified
        # in ``rotation_counts``. Once a good state is found (oracle_evaluation is True), stop.
        for power in self._iterations:
            if self._sample_from_iterations:
                power = algorithm_globals.random.integers(power)
            assignment, oracle_evaluation = self._run_experiment(power)
            if oracle_evaluation:
                break

        self._ret.assignment = assignment
        self._ret.oracle_evaluation = oracle_evaluation
        return self._ret

    @property
    def grover_operator(self) -> QuantumCircuit:
        """Returns grover_operator."""
        return self._grover_operator