Source code for tno.quantum.optimization.qubo.solvers._qaoa._qaoa_result

"""This module contains the ``QAOAResult`` class."""

from __future__ import annotations

import itertools
from collections.abc import Mapping
from typing import TYPE_CHECKING, Any, SupportsFloat

from tno.quantum.optimization.qubo.components import Freq, ResultInterface
from tno.quantum.utils import BitVector
from tno.quantum.utils.validation import check_arraylike, check_ax

if TYPE_CHECKING:
    from typing import Self

    from matplotlib.axes import Axes
    from numpy.typing import ArrayLike

    from tno.quantum.optimization.qubo.components import QUBO
    from tno.quantum.utils import BackendConfig, BitVectorLike, OptimizerConfig


[docs]class QAOAResult(ResultInterface): """Implementation of `ResultInterface` for :py:class:`QAOASolver`."""
[docs] def __init__( # noqa: PLR0913 self, best_bitvector: BitVectorLike, best_value: SupportsFloat, freq: Freq, init_beta: ArrayLike, init_gamma: ArrayLike, final_beta: ArrayLike, final_gamma: ArrayLike, expval_history: ArrayLike, training_backend: BackendConfig, evaluation_backend: BackendConfig, optimizer: OptimizerConfig, ) -> None: """Init :py:class:`QAOAResult`. Args: best_bitvector: Bitvector corresponding to the best result. best_value: Objective value of the best result. freq: Frequency object with the found energies and number of occurrences. init_beta: Initial parameters for the mixer layer. init_gamma: Initial parameters for the cost layer. final_beta: Final parameters for the mixer layer. final_gamma: Final parameters for the mixer layer. expval_history: Loss values over all optimizing iterations. training_backend: Training backend used. evaluation_backend: Evaluation backend used. optimizer: Optimizer used. """ super().__init__(best_bitvector, best_value, freq) self.init_beta = check_arraylike(init_beta, "init_beta", ndim=1) self.init_gamma = check_arraylike(init_gamma, "init_gamma", ndim=1) self.final_beta = check_arraylike(final_beta, "final_beta", ndim=1) self.final_gamma = check_arraylike(final_gamma, "final_gamma", ndim=1) self.expval_history = check_arraylike(expval_history, "expval_history", ndim=1) self.training_backend = training_backend self.evaluation_backend = evaluation_backend self.optimizer = optimizer
[docs] @classmethod def from_result( cls, qubo: QUBO, raw_result: Mapping[str, int], properties: dict[str, Any] ) -> Self: """Construct :py:class:`QAOAResult` from `raw_result` for the given `qubo`. Args: qubo: QUBO to evaluate the given bitvectors. raw_result: Mapping with bitstrings as keys and frequencies as values. properties: Dictionary containing properties used to solve QUBO. Returns: A :py:class:`QAOAResult` containing the best bitvector, best value and frequency of the best bitvector of `raw_result` based on the given `qubo`. The best bitvector has the lowest energy (value) based on the given `qubo`. When there are ties, the bitvector with the highest frequency is returned. Raises: ValueError: If `raw_result` is empty. """ # Convert `raw_result` to `Freq` object with energies = QUBO evaluation bitvectors = [BitVector(s) for s in raw_result] energies = [qubo.evaluate(b) for b in bitvectors] num_occurrences = [raw_result[s] for s in raw_result] freq = Freq(bitvectors, energies, num_occurrences) # Find `best_bitvector` and `best_value` best_bitvector = None best_value = None occ_best_bitvector = None for bitvector, value, occ in freq: if ( best_value is None or occ_best_bitvector is None or value < best_value or (value == best_value and occ > occ_best_bitvector) ): best_bitvector = bitvector best_value = value occ_best_bitvector = occ if best_bitvector is None or best_value is None: msg = "Argument `raw_result` is empty" raise ValueError(msg) # Get properties from `properties` (make copies of lists) init_beta = list(properties["init_beta"]) init_gamma = list(properties["init_gamma"]) final_beta = list(properties["final_beta"]) final_gamma = list(properties["final_gamma"]) expval_history = list(properties["expval_history"]) return cls( best_bitvector=best_bitvector, best_value=best_value, freq=freq, init_beta=init_beta, init_gamma=init_gamma, final_beta=final_beta, final_gamma=final_gamma, expval_history=expval_history, training_backend=properties["training_backend"], evaluation_backend=properties["evaluation_backend"], optimizer=properties["optimizer"], )
[docs] def plot_expval_history(self, ax: Axes | None = None) -> None: """Plot the history of the expectation value of the cost function. Args: ax: Optional matplotlib ``Axes`` to draw on. If ``None`` (default) create a new figure with ``Axes`` to draw on. """ ax = check_ax(ax, "ax") ax.plot(range(len(self.expval_history)), self.expval_history) ax.set_xlabel("Iteration") ax.set_ylabel("Expectation Value")
[docs] def plot_shots_histogram(self, ax: Axes | None = None) -> None: """Plot the histogram of the output of the final circuit. Args: ax: Optional matplotlib ``Axes`` to draw on. If ``None`` (default) create a new figure with ``Axes`` to draw on. """ ax = check_ax(ax, "ax") n_bits = len(self.best_bitvector) x_values = ["".join(bits) for bits in itertools.product("01", repeat=n_bits)] height = [0 for _ in x_values] for bitvector, _, n in self.freq: i = int(str(bitvector), 2) height[i] += n ax.bar(x_values, height) ax.set_xlabel("Solution") ax.set_ylabel("Number of Shots")
[docs] def plot_parameters(self, ax: Axes | None = None) -> None: """Plot the final beta and gamma parameters. Args: ax: Optional matplotlib ``Axes`` to draw on. If ``None`` (default) create a new figure with ``Axes`` to draw on. """ ax = check_ax(ax, "ax") depth = len(self.final_beta) ax.plot(range(depth), self.final_beta, label="beta") ax.plot(range(depth), self.final_gamma, label="gamma") ax.set_xlabel("Depth") ax.set_ylabel("Rotation") ax.legend()