Source code for QHyper.solvers

# This work was supported by the EuroHPC PL infrastructure funded at the
# Smart Growth Operational Programme (2014-2020), Measure 4.2
# under the grant agreement no. POIR.04.02.00-00-D014/20-00

"""
This module contains implementations of different solvers.
In QHyper exists three types of solvers: classical, quantum annealing and
gate-based.
Some of them are written from scratch based on popular algorithms, while
others are just a wrapper for existing solutions.
No solver is imported by deafult to reduce number of dependencies.
To use any solver you can import it directly like

.. code-block:: python

    from QHyper.solver.gate_based.pennylane.qaoa import QAOA

or use function :py:func:`Solvers.get` with the name, category, and platform.
Any solver that is in directory 'QHyper/custom' or 'custom' will be
also available in this function.

.. rubric:: Interface

.. autosummary::
    :toctree: generated/

    Solver  -- Base class for solvers.
    SolverResult -- Dataclass for storing results


.. rubric:: Classical Solvers

.. autosummary::
    :toctree: generated/

    classical.gurobi.Gurobi -- Gurobi solver.


.. rubric:: Quantum Annealing Solvers

.. autosummary::
    :toctree: generated/

    quantum_annealing.dwave.cqm.CQM -- CQM solver.
    quantum_annealing.dwave.dqm.DQM -- DQM solver.
    quantum_annealing.dwave.advantage.Advantage -- Advantage solver.


.. rubric:: Gate-based solvers

.. autosummary::
    :toctree: generated/

    gate_based.pennylane.qaoa.QAOA -- QAOA solver.
    gate_based.pennylane.qml_qaoa.QML_QAOA -- QML QAOA solver.
    gate_based.pennylane.wf_qaoa.WF_QAOA -- Weight Free QAOA solver.
    gate_based.pennylane.h_qaoa.H_QAOA -- Hyper QAOA solver.


.. rubric:: Hyper-optimizer

Not really a solver, but a class that can be used to optimize the hyperparameters
of another solver. It is a wrapper around the solver class.

.. autosummary::
    :toctree: generated/

    hyper_optimizer.HyperOptimizer -- Hyper-optimizer.

.. rubric:: Additional functions

.. autoclass:: Solvers
    :members:
"""

import copy
import dataclasses


from typing import Type, Any

from QHyper.problems import problem_from_config, ProblemConfigException
from QHyper.util import search_for

from QHyper.optimizers import Optimizer, create_optimizer, OptimizationParameter

from QHyper.solvers.base import (  # noqa F401
    Solver, SolverResult, SolverConfigException)
from QHyper.solvers.hyper_optimizer import HyperOptimizer


[docs] class Solvers: custom_solvers: None | dict[str, type] = None
[docs] @staticmethod def get(name: str, category: str = '', platform: str = '') -> Type[Solver]: """ Get solver class by name, category, and platform. The name is required, other paramters might be required if there would be more than one solver with the same name. The solver will be available by the 'name' attribute if defined or by the class name. Letters case doesn't matter. """ if Solvers.custom_solvers is None: Solvers.custom_solvers = ( search_for(Solver, 'QHyper/custom') | search_for(Solver, 'custom')) # In the future, the category and platform might be required for some # solvers if category == "custom": if name in Solvers.custom_solvers: return Solvers.custom_solvers[name] else: raise FileNotFoundError( f"Solver {name} not found in custom solvers" ) name_ = name.lower() if name_ in ["qaoa"]: from .gate_based.pennylane.qaoa import QAOA return QAOA elif name_ in ["qml_qaoa"]: from .gate_based.pennylane.qml_qaoa import QML_QAOA return QML_QAOA elif name_ in ["wf_qaoa"]: from .gate_based.pennylane.wf_qaoa import WF_QAOA return WF_QAOA elif name_ in ["h_qaoa"]: from .gate_based.pennylane.h_qaoa import H_QAOA return H_QAOA elif name_ in ["gurobi"]: from .classical.gurobi.gurobi import Gurobi return Gurobi elif name_ in ["cqm"]: from .quantum_annealing.dwave.cqm import CQM return CQM elif name_ in ["dqm"]: from .quantum_annealing.dwave.dqm import DQM return DQM elif name_ in ["advantage"]: from .quantum_annealing.dwave.advantage import Advantage return Advantage else: raise SolverConfigException(f"Solver {name} not found")
def solver_from_config(config: dict[str, Any]) -> Solver | HyperOptimizer: """ Alternative way of creating solver. Expect dict with two keys: - type - type of solver - args - arguments which will be passed to Solver instance Parameters ---------- problem : Problem The problem to be solved config : dict[str. Any] Configuration in form of dict Returns ------- Solver Initialized Solver object """ config = copy.deepcopy(config) try: problem_config = config.pop('problem') except KeyError: raise ProblemConfigException( 'Problem configuration was not provided') problem = problem_from_config(problem_config) error_msg = "" if 'solver' not in config: raise SolverConfigException("Solver configuration was not provided") if 'name' not in config['solver']: raise SolverConfigException("Solver name was not provided") try: solver_class = Solvers.get( config['solver']['name'], config['solver'].get('category', ''), config['solver'].get('platform', '') ) except FileNotFoundError: raise SolverConfigException( f"Solver {config['solver']['name']} not found" ) for field in dataclasses.fields(solver_class): if not field.init: continue if field.name not in config['solver']: continue if field.type == Optimizer: config['solver'][field.name] = create_optimizer( config['solver'][field.name] ) elif field.type == OptimizationParameter: config['solver'][field.name] = OptimizationParameter( **config['solver'][field.name] ) try: error_msg = "Solver configuration was not provided" solver_config = config.pop('solver') error_msg = "Solver name was not provided" solver_name = solver_config.pop('name') solver_category = solver_config.pop('category', '') solver_platform = solver_config.pop('platform', '') solver_class = Solvers.get( solver_name, solver_category, solver_platform) except KeyError: raise SolverConfigException(error_msg) solver = solver_class.from_config(problem, solver_config) hyper_optimizer_config = config.pop('hyper_optimizer', None) if hyper_optimizer_config: optimizer_config = hyper_optimizer_config.pop('optimizer') optimizer = create_optimizer(optimizer_config) hyper_optimizer = HyperOptimizer( optimizer, solver, **hyper_optimizer_config) return hyper_optimizer return solver