1from typing import Literal
2
3import numpy as np
4
5from qlauncher.base import Algorithm, Result
6from qlauncher.base.models import QUBO
7from qlauncher.exceptions import DependencyError
8from qlauncher.routines.orca.backends import OrcaBackend
9
10try:
11 from ptseries.algorithms.binary_solvers import BinaryBosonicSolver
12except ImportError as e:
13 raise DependencyError(e, install_hint='orca', private=True) from e
14
15
[docs]
16class BBS(Algorithm[QUBO, OrcaBackend]):
17 """
18 Binary Bosonic Solver algorithm class.
19
20 This class represents the Binary Bosonic Solver (BBS) algorithm. BBS is a quantum-inspired algorithm that
21 solves optimization problems by mapping them onto a binary bosonic system. It uses a training process
22 to find the optimal solution.
23
24 ### Attributes:
25
26 - algorithm_format ('qubo', 'fn'), optional): If the algorithm input is a function or a qubo matrix. Defaults to 'qubo'.
27 - input_state (list[int] | None, optional): Photonic circuit input state provided to the ORCA computer. If None defaults to [1,0,1,0,1...]. Defaults to None.
28 - n_samples (int, optional): Number of samples. Defaults to 100.
29 - gradient_mode (str, optional): Gradient mode. Defaults to "parameter-shift".
30 - gradient_delta (float, optional): Gradient Delta. Defaults to np.pi/6.
31 - sampling_factor (int, optional): Number of times quantum samples are passed through the classical flipping layer. Defaults to 1.
32 - learning_rate (float, optional): Learning rate of the algorithm. Defaults to 5e-2.
33 - learning_rate_flip (float, optional): Bit flip learning rate. Defaults to 1e-1.
34 - updates (int, optional): Number of epochs. Defaults to 100.
35
36 """
37
38 def __init__(
39 self,
40 algorithm_format: Literal['qubo', 'fn'] = 'qubo',
41 input_state: list[int] | None = None,
42 n_samples: int = 100,
43 gradient_mode: str = 'parameter-shift',
44 gradient_delta: float = np.pi / 6,
45 sampling_factor: int = 1,
46 learning_rate: float = 5e-2,
47 learning_rate_flip: float = 1e-1,
48 updates: int = 100,
49 ):
50 super().__init__()
51 self._algorithm_format = algorithm_format
52 self.bbs_params = {
53 'n_samples': n_samples,
54 'gradient_mode': gradient_mode,
55 'gradient_delta': gradient_delta,
56 'sampling_factor': sampling_factor,
57 }
58 self.training_params = {
59 'learning_rate': learning_rate,
60 'learning_rate_flip': learning_rate_flip,
61 'updates': updates,
62 }
63 self.input_state = input_state
64
[docs]
65 def run(self, problem: QUBO, backend: OrcaBackend) -> Result:
66 if self.input_state is None:
67 self.input_state = [(i + 1) % 2 for i in range(len(problem.matrix))]
68
69 bbs = BinaryBosonicSolver(
70 pb_dim=len(self.input_state),
71 objective=problem.matrix,
72 input_state=self.input_state,
73 tbi=backend.get_tbi(),
74 **self.bbs_params,
75 )
76
77 bbs.solve(**self.training_params)
78
79 return self.construct_results(bbs, problem.offset)
80
[docs]
81 def get_bitstring(self, result: list[float]) -> str:
82 return ''.join(map(str, map(int, result)))
83
[docs]
84 def construct_results(self, solver: BinaryBosonicSolver, offset: float) -> Result:
85 # TODO: add support for distribution (probably with different logger)
86 best_bitstring = ''.join(map(str, map(int, solver.config_min_encountered)))
87 best_energy = solver.E_min_encountered + offset
88 num_of_samples = solver.n_samples
89 #! Todo: instead of None attach relevant info from 'results'
90 # results fail to pickle correctly btw
91 return Result(best_bitstring, best_energy, None, None, None, None, num_of_samples, None, None, None) # type: ignore