Source code for qlauncher.base.base

  1import logging
  2import pickle
  3from abc import ABC, abstractmethod
  4from dataclasses import dataclass
  5from typing import TYPE_CHECKING, Any, Generic, Literal, TypeVar
  6
  7from qlauncher.base.models import Model
  8
  9if TYPE_CHECKING:
 10    from collections.abc import Callable
 11
 12AVAILABLE_FORMATS = Literal['hamiltonian', 'qubo', 'bqm', 'none', 'fn', 'tabular_ml']
 13
 14_Model = TypeVar('_Model', bound=Model)
 15_Backends = TypeVar('_Backends', bound='Backend')
 16
 17
[docs] 18@dataclass 19class Result: 20 best_bitstring: str 21 best_energy: float 22 most_common_bitstring: str 23 most_common_bitstring_energy: float 24 distribution: dict 25 energies: dict 26 num_of_samples: int 27 average_energy: float 28 energy_std: float 29 result: Any 30 31 def __str__(self): 32 return f'Result(bitstring={self.best_bitstring}, energy={self.best_energy})' 33 34 def __repr__(self): 35 return str(self) 36
[docs] 37 def best(self): 38 return self.best_bitstring, self.best_energy
39
[docs] 40 def most_common(self): 41 return self.most_common_bitstring, self.most_common_bitstring_energy
42
[docs] 43 @staticmethod 44 def from_counts_energies(bitstring_counts: dict[str, int], energies: dict[str, float], result: Any = None) -> 'Result': 45 """ 46 Constructs the Result object from Dictionary with bitstring to num of occurrences, 47 dictionary mapping bitstring to energy and optional result (rest) 48 """ 49 best_bitstring = min(energies, key=energies.get) 50 best_energy = energies[best_bitstring] 51 most_common_bitstring = max(bitstring_counts, key=bitstring_counts.get) 52 most_common_bitstring_energy = energies[most_common_bitstring] 53 num_of_samples = int(sum(bitstring_counts.values())) 54 55 mean_value = sum(energies[bitstring] * occ for bitstring, occ in bitstring_counts.items()) / num_of_samples 56 std = 0 57 for bitstring, occ in bitstring_counts.items(): 58 std += occ * ((energies[bitstring] - mean_value) ** 2) 59 std = (std / (num_of_samples - 1)) ** 0.5 60 return Result( 61 best_bitstring, 62 best_energy, 63 most_common_bitstring, 64 most_common_bitstring_energy, 65 {k: v / num_of_samples for k, v in bitstring_counts.items()}, 66 energies, 67 num_of_samples, 68 mean_value, 69 std, 70 result, 71 )
72 73
[docs] 74class Backend: 75 """ 76 Abstract class representing a backend for quantum computing. 77 78 Attributes: 79 name (str): The name of the backend. 80 path (str | None): The path to the backend (optional). 81 parameters (list): A list of parameters for the backend (optional). 82 83 """ 84 85 def __init__(self, name: str, parameters: list | None = None) -> None: 86 self.name: str = name 87 self.is_device = name == 'device' 88 self.path: str | None = None 89 self.parameters = parameters if parameters is not None else [] 90 self.logger: logging.Logger | None = None 91
[docs] 92 def set_logger(self, logger: logging.Logger) -> None: 93 self.logger = logger
94 95 def _get_path(self) -> str: 96 return f'{self.name}'
97 98
[docs] 99class Problem: 100 """ 101 Abstract class for defining Problems. 102 103 Attributes: 104 variant (str): The variant of the problem. The default variant is "Optimization". 105 path (str | None): The path to the problem. 106 name (str): The name of the problem. 107 instance_name (str): The name of the instance. 108 instance (any): An instance of the problem. 109 110 """ 111 112 _all_problems: dict[str, type['Problem']] = {} 113 114 def __init__(self, instance: Any, instance_name: str = 'unnamed') -> None: 115 """ 116 Initializes a Problem instance. 117 118 Params: 119 instance (any): An instance of the problem. 120 instance_name (str | None): The name of the instance. 121 122 Returns: 123 None 124 """ 125 self.instance: Any = instance 126 self.instance_name = instance_name 127 self.variant: Literal['decision', 'optimization'] = 'optimization' 128 self.path: str | None = None 129 self.name = self.__class__.__name__.lower() 130
[docs] 131 @classmethod 132 def from_file(cls: type['Problem'], path: str) -> 'Problem': 133 with open(path, 'rb') as f: 134 instance = pickle.load(f) 135 return cls(instance)
136
[docs] 137 @staticmethod 138 def from_preset(instance_name, **kwargs) -> 'Problem': 139 raise NotImplementedError()
140 141 def __init_subclass__(cls) -> None: 142 if Problem not in cls.__bases__: 143 return 144 Problem._all_problems[cls.__name__] = cls 145 cls._mapping: dict[type[Model], Callable[[], Model]] = {} 146 for method_name in cls.__dict__: 147 if method_name.startswith('to_'): 148 method = cls.__dict__[method_name] 149 cls._mapping[method.__annotations__['return']] = method 150
[docs] 151 def read_result(self, exp, log_path): 152 """ 153 Reads a result from a file. 154 155 Args: 156 exp: The experiment. 157 log_path: The path to the log file. 158 159 Returns: 160 The result. 161 """ 162 exp += exp # ?: this is perplexing 163 with open(log_path, 'rb') as file: 164 return pickle.load(file)
165
[docs] 166 def analyze_result(self, result) -> Any: 167 """ 168 Analyzes the result. 169 170 Args: 171 result: The result. 172 173 """ 174 raise NotImplementedError()
175
[docs] 176 def to(self, problem_type: type[Model]) -> Model: 177 name = problem_type.__name__.lower() 178 if hasattr(self, f'to_{name}'): 179 return getattr(self, f'to_{name}')() 180 raise TypeError
181 182
[docs] 183class OptimizationAlgorithm: ...
184 185
[docs] 186class Algorithm(ABC, Generic[_Model, _Backends]): 187 """ 188 Abstract class for Algorithms. 189 190 Attributes: 191 name (str): The name of the algorithm, derived from the class name in lowercase. 192 path (str | None): The path to the algorithm, if applicable. 193 parameters (list): A list of parameters for the algorithm. 194 alg_kwargs (dict): Additional keyword arguments for the algorithm. 195 196 Abstract methods: 197 __init__(self, **alg_kwargs): Initializes the Algorithm object. 198 _get_path(self) -> str: Returns the common path for the algorithm. 199 run(self, problem: Problem, backend: Backend): Runs the algorithm on a specific problem using a backend. 200 """ 201 202 def __init__(self, **alg_kwargs) -> None: 203 self.name: str = self.__class__.__name__.lower() 204 self.path: str | None = None 205 self.parameters: list = [] 206 self.alg_kwargs = alg_kwargs 207
[docs] 208 def parse_result_to_json(self, o: object) -> dict: 209 """Parses results so that they can be saved as a JSON file. 210 211 Args: 212 o (object): The result object to be parsed. 213 214 Returns: 215 dict: The parsed result as a dictionary. 216 """ 217 print('Algorithm does not have the parse_result_to_json method implemented') 218 return o.__dict__
219
[docs] 220 @classmethod 221 def get_class_input_format(cls) -> type[Model] | None: 222 return cls.run.__annotations__.get('problem', None)
223
[docs] 224 def get_input_format(self) -> type[Model] | None: 225 return self.get_class_input_format()
226
[docs] 227 @abstractmethod 228 def run(self, problem: _Model, backend: _Backends) -> Result: 229 """Runs the algorithm on a specific problem using a backend. 230 231 Args: 232 problem (Problem): The problem to be solved. 233 backend (Backend): The backend to be used for execution. 234 """