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