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', 'none', 'fn']
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(
46 bitstring_distribution, key=bitstring_distribution.get)
47 most_common_bitstring_energy = energy_distribution[most_common_bitstring]
48 num_of_samples = int(sum(bitstring_distribution.values()))
49
50 mean_value = sum(energy_distribution[bitstring] * occ for bitstring,
51 occ in bitstring_distribution.items()) / num_of_samples
52 std = 0
53 for bitstring, occ in bitstring_distribution.items():
54 std += occ * ((energy_distribution[bitstring] - mean_value)**2)
55 std = (std/(num_of_samples-1))**0.5
56 return Result(best_bitstring, best_energy, most_common_bitstring, most_common_bitstring_energy,
57 bitstring_distribution, energy_distribution, num_of_samples, mean_value, std, result)
58
59
[docs]
60class Backend:
61 """
62 Abstract class representing a backend for quantum computing.
63
64 Attributes:
65 name (str): The name of the backend.
66 path (str | None): The path to the backend (optional).
67 parameters (list): A list of parameters for the backend (optional).
68
69 """
70
71 def __init__(self, name: str, parameters: list | None = None) -> None:
72 self.name: str = name
73 self.path: str | None = None
74 self.parameters = parameters if parameters is not None else []
75 self.logger: logging.Logger | None = None
76
[docs]
77 def set_logger(self, logger: logging.Logger):
78 self.logger = logger
79
80 def _get_path(self):
81 return f'{self.name}'
82
83
84P = TypeVar('P', bound=type['Problem'])
85
86
[docs]
87class Problem(ABC):
88 """
89 Abstract class for defining Problems.
90
91 Attributes:
92 variant (str): The variant of the problem. The default variant is "Optimization".
93 path (str | None): The path to the problem.
94 name (str): The name of the problem.
95 instance_name (str): The name of the instance.
96 instance (any): An instance of the problem.
97
98 """
99
100 _problem_id = None
101
102 def __init__(self, instance: Any, instance_name: str = 'unnamed') -> None:
103 """
104 Initializes a Problem instance.
105
106 Params:
107 instance (any): An instance of the problem.
108 instance_name (str | None): The name of the instance.
109
110 Returns:
111 None
112 """
113 self.instance: Any = instance
114 self.instance_name = instance_name
115 self.variant: str = 'Optimization'
116 self.path: str | None = None
117 self.name = self.__class__.__name__.lower()
118
[docs]
119 @classmethod
120 def from_file(cls: P, path: str) -> P:
121 with open(path, 'rb') as f:
122 instance = pickle.load(f)
123 return cls(instance)
124
[docs]
125 @staticmethod
126 def from_preset(instance_name: str, **kwargs):
127 raise NotImplementedError()
128
129 def __init_subclass__(cls) -> None:
130 if Problem not in cls.__bases__:
131 return
132 cls._problem_id = cls
133
[docs]
134 def read_result(self, exp, log_path):
135 """
136 Reads a result from a file.
137
138 Args:
139 exp: The experiment.
140 log_path: The path to the log file.
141
142 Returns:
143 The result.
144 """
145 exp += exp
146 with open(log_path, 'rb') as file:
147 res = pickle.load(file)
148 return res
149
[docs]
150 def analyze_result(self, result):
151 """
152 Analyzes the result.
153
154 Args:
155 result: The result.
156
157 """
158
159
[docs]
160class Algorithm(ABC):
161 """
162 Abstract class for Algorithms.
163
164 Attributes:
165 name (str): The name of the algorithm, derived from the class name in lowercase.
166 path (str | None): The path to the algorithm, if applicable.
167 parameters (list): A list of parameters for the algorithm.
168 alg_kwargs (dict): Additional keyword arguments for the algorithm.
169
170 Abstract methods:
171 __init__(self, **alg_kwargs): Initializes the Algorithm object.
172 _get_path(self) -> str: Returns the common path for the algorithm.
173 run(self, problem: Problem, backend: Backend): Runs the algorithm on a specific problem using a backend.
174 """
175 _algorithm_format: AVAILABLE_FORMATS = 'none'
176
177 @abstractmethod
178 def __init__(self, **alg_kwargs) -> None:
179 self.name: str = self.__class__.__name__.lower()
180 self.path: str | None = None
181 self.parameters: list = []
182 self.alg_kwargs = alg_kwargs
183
[docs]
184 def parse_result_to_json(self, o: object) -> dict:
185 """Parses results so that they can be saved as a JSON file.
186
187 Args:
188 o (object): The result object to be parsed.
189
190 Returns:
191 dict: The parsed result as a dictionary.
192 """
193 print('Algorithm does not have the parse_result_to_json method implemented')
194 return o.__dict__
195
[docs]
196 @abstractmethod
197 def run(self, problem: Problem, backend: Backend, formatter: Callable | None = None) -> Result:
198 """Runs the algorithm on a specific problem using a backend.
199
200 Args:
201 problem (Problem): The problem to be solved.
202 backend (Backend): The backend to be used for execution.
203 """