amplify-bbopt 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,92 @@
1
+ # Copyright (c) Fixstars Amplify Corporation.
2
+ #
3
+ # This source code is licensed under the MIT license found in the
4
+ # LICENSE file in the root directory of this source tree.
5
+
6
+ from .__version__ import __version__
7
+ from .bb_func import BlackBoxFuncBase, BlackBoxFuncList, blackbox
8
+ from .constraint import Constraint, Constraints, clamp, equal_to, greater_equal, less_equal
9
+ from .data_gen import DatasetGenerator
10
+ from .data_list import DataList, load_dataset
11
+ from .history import History
12
+ from .logger import Logger, logger
13
+ from .misc import exec_func_neat_stdout, print_to_str
14
+ from .model import ModelKernel, QUBOConvertibleBase, TorchFM
15
+ from .optimizer import FMQAOptimizer, KernelQAOptimizer, MultiObjectiveOptimizer, OptimizerBase, QAOptimizerBase
16
+ from .optimizer_bo import BayesianOptimizer
17
+ from .plot import anneal_history, plot_history
18
+ from .poly import Poly
19
+ from .solution_type import FlatSolution, FlatSolutionDict, StructuredSolution, StructuredSolutionDict
20
+ from .trainer import GramMatrixHandler, ModelKernelTrainer, PolyCoefMatrixHandler, TorchFMTrainer, TrainerBase
21
+ from .variable import (
22
+ BinaryVariable,
23
+ BinaryVariableList,
24
+ DiscreteVariable,
25
+ DiscreteVariableList,
26
+ IntegerVariable,
27
+ IntegerVariableList,
28
+ RealVariable,
29
+ RealVariableList,
30
+ RealVariableListLogUniform,
31
+ RealVariableLogUniform,
32
+ Variable,
33
+ VariableBase,
34
+ VariableListBase,
35
+ )
36
+ from .variables import Variables
37
+
38
+ __all__ = [
39
+ "BayesianOptimizer",
40
+ "BinaryVariable",
41
+ "BinaryVariableList",
42
+ "BlackBoxFuncBase",
43
+ "BlackBoxFuncList",
44
+ "Constraint",
45
+ "Constraints",
46
+ "DataList",
47
+ "DatasetGenerator",
48
+ "DiscreteVariable",
49
+ "DiscreteVariableList",
50
+ "FMQAOptimizer",
51
+ "FlatSolution",
52
+ "FlatSolutionDict",
53
+ "GramMatrixHandler",
54
+ "History",
55
+ "IntegerVariable",
56
+ "IntegerVariableList",
57
+ "KernelQAOptimizer",
58
+ "Logger",
59
+ "ModelKernel",
60
+ "ModelKernelTrainer",
61
+ "MultiObjectiveOptimizer",
62
+ "OptimizerBase",
63
+ "Poly",
64
+ "PolyCoefMatrixHandler",
65
+ "QAOptimizerBase",
66
+ "QUBOConvertibleBase",
67
+ "RealVariable",
68
+ "RealVariableList",
69
+ "RealVariableListLogUniform",
70
+ "RealVariableLogUniform",
71
+ "StructuredSolution",
72
+ "StructuredSolutionDict",
73
+ "TorchFM",
74
+ "TorchFMTrainer",
75
+ "TrainerBase",
76
+ "Variable",
77
+ "VariableBase",
78
+ "VariableListBase",
79
+ "Variables",
80
+ "__version__",
81
+ "anneal_history",
82
+ "blackbox",
83
+ "clamp",
84
+ "equal_to",
85
+ "exec_func_neat_stdout",
86
+ "greater_equal",
87
+ "less_equal",
88
+ "load_dataset",
89
+ "logger",
90
+ "plot_history",
91
+ "print_to_str",
92
+ ]
@@ -0,0 +1,6 @@
1
+ # Copyright (c) Fixstars Amplify Corporation.
2
+ #
3
+ # This source code is licensed under the MIT license found in the
4
+ # LICENSE file in the root directory of this source tree.
5
+
6
+ __version__ = "0.1.0"
@@ -0,0 +1,281 @@
1
+ # Copyright (c) Fixstars Amplify Corporation.
2
+ #
3
+ # This source code is licensed under the MIT license found in the
4
+ # LICENSE file in the root directory of this source tree.
5
+
6
+ from __future__ import annotations
7
+
8
+ import abc
9
+ import contextlib
10
+ import inspect
11
+ import time
12
+ from typing import TYPE_CHECKING, Any, Callable, get_type_hints
13
+
14
+ from .constraint import Constraint, Constraints
15
+ from .variable import VariableBase, VariableListBase
16
+ from .variables import Variables
17
+
18
+ if TYPE_CHECKING:
19
+ from collections.abc import Generator, Iterator
20
+
21
+
22
+ class BlackBoxFuncBase(abc.ABC):
23
+ """Base class to define a black box objective function class.
24
+
25
+ Args:
26
+ abc: Abstract base class.
27
+ """
28
+
29
+ def __init__(self) -> None:
30
+ """Initialize the base class."""
31
+ self._variables = Variables()
32
+ self._constraints = Constraints()
33
+ self._name = f"bb_func_{time.perf_counter_ns()}"
34
+
35
+ @property
36
+ def name(self) -> str:
37
+ """Name of the black-box objective function."""
38
+ return self._name
39
+
40
+ @name.setter
41
+ def name(self, value: str) -> None:
42
+ """Set a black-box function name. If a black-box function class is defined by using the :obj:`blackbox` decorator, the name is automatically set.
43
+
44
+ Args:
45
+ value (str): A black-box function name.
46
+ """ # noqa: E501
47
+ self._name = value
48
+
49
+ def __setattr__(self, name: str, value: Any) -> None: # noqa: ANN401
50
+ """Set decision variables.
51
+
52
+ Args:
53
+ name (str): The name of a decision variable.
54
+ value (Any): A decision variable.
55
+ """
56
+ super().__setattr__(name, value)
57
+ if isinstance(value, (VariableBase, VariableListBase)):
58
+ self._variables._set_variable(name, value) # noqa: SLF001
59
+ elif name in self._variables.var_dict:
60
+ self._variables._del_variable(name) # noqa: SLF001
61
+
62
+ def _call_objective(self, **kwargs: bool | int | float | list[bool] | list[int] | list[float]) -> float: # noqa: PYI041
63
+ """Call the objective function with input arguments to the function.
64
+
65
+ Returns:
66
+ float: The objective function value.
67
+ """
68
+
69
+ @contextlib.contextmanager
70
+ def replace_ctx() -> Generator[Any, Any, Any]:
71
+ try:
72
+ # Replace variables with values
73
+ for pred_name, pred_value in kwargs.items():
74
+ if pred_name in self._variables.var_dict:
75
+ super(BlackBoxFuncBase, self).__setattr__(pred_name, pred_value)
76
+ yield
77
+ finally:
78
+ # Replace values with variables
79
+ for name, bb_var in self._variables.var_dict.items():
80
+ self.__setattr__(name, bb_var) # noqa: PLC2801
81
+
82
+ with replace_ctx():
83
+ return self.objective()
84
+
85
+ @property
86
+ def variables(self) -> Variables:
87
+ """Decision variables associated with this black-box objective function."""
88
+ return self._variables
89
+
90
+ @abc.abstractmethod
91
+ def objective(self) -> float:
92
+ """Evaluate the objective function. An evaluator sets the class variable attributes having the names and valueas of the input.
93
+
94
+ Returns:
95
+ float: An objective function value.
96
+ """ # noqa: E501
97
+
98
+ def add_constraint(
99
+ self,
100
+ constraint: Constraint | Constraints | list[Constraint | Constraints] | list[Constraint] | list[Constraints],
101
+ ) -> None:
102
+ """Add user-defined constraints to the black-box function class instance.
103
+
104
+ Args:
105
+ constraint (Constraint | Constraints | list[Constraint | Constraints] | list[Constraint] | list[Constraints]): User-defined constraints.
106
+ """ # noqa: E501
107
+ self._constraints.append(constraint)
108
+
109
+ @property
110
+ def constraints(self) -> Constraints:
111
+ """User-defined constraints associated with this black-box function class."""
112
+ return self._constraints
113
+
114
+
115
+ def blackbox(func: Callable) -> BlackBoxFuncBase:
116
+ """Decorator function to create a black-box objective function class instance.
117
+
118
+ Args:
119
+ func (Callable): A black-box objective function.
120
+
121
+ Raises:
122
+ ValueError: If decision variables are not appropriately set for the input arguments to a black-box function.
123
+
124
+ Returns:
125
+ BlackBoxFunc: A black-box function class instance which associates a black-box function and decision variables.
126
+ """
127
+ parameters = inspect.signature(func).parameters
128
+ arguments = {v.name for v in parameters.values()}
129
+
130
+ # Set variables defined as default values
131
+ variables = Variables()
132
+ for k, v in parameters.items():
133
+ if isinstance(v.default, (VariableBase, VariableListBase)):
134
+ variables._set_variable(k, v.default) # noqa: SLF001
135
+
136
+ # Set variables defined with Annotated
137
+ annotations = get_type_hints(func, include_extras=True)
138
+ for k, v in annotations.items():
139
+ if k == "return":
140
+ continue
141
+ if (
142
+ hasattr(v, "__metadata__")
143
+ and len(v.__metadata__) == 1
144
+ and isinstance(v.__metadata__[0], (VariableBase, VariableListBase))
145
+ ):
146
+ variables._set_variable(k, v.__metadata__[0]) # noqa: SLF001
147
+
148
+ missing_args = arguments - set(variables.names)
149
+ if len(missing_args) != 0:
150
+ for arg in missing_args:
151
+ raise ValueError(
152
+ f"Argument `{arg}` of {func.__name__} must be annotated with `Annotated[..., Variable(...)`"
153
+ f" or has a default value as `Variable`."
154
+ )
155
+
156
+ class BlackBoxFunc(BlackBoxFuncBase):
157
+ def __init__(self) -> None: # type: ignore
158
+ super().__init__()
159
+ self._name = func.__name__
160
+ for name, variable in variables.var_dict.items():
161
+ setattr(self, name, variable)
162
+
163
+ def __call__(self, *args, **kwargs) -> float: # noqa: ANN002, ANN003
164
+ return func(*args, **kwargs)
165
+
166
+ def objective(self) -> float:
167
+ return func(**{k: getattr(self, k) for k in self._variables.var_dict})
168
+
169
+ return BlackBoxFunc()
170
+
171
+
172
+ class BlackBoxFuncList:
173
+ """Class handles multiple black-box objective functions for multi-objective optimization."""
174
+
175
+ def __init__(self, objectives: list[BlackBoxFuncBase], unify_variables: bool = False) -> None:
176
+ """Initialize a black-box function list class.
177
+
178
+ Args:
179
+ objectives (list[BlackBoxFuncBase]): A list of class instances of black-box functions to be considered in multi-objective optimization.
180
+ unify_variables (bool, optional): Whether to unify variables and constraints of multiple objectives in :obj:`BlackBoxFuncList.handle_duplicates`). Note that if you are performing multi-objective optimizations and want to manually operate `amplify.PolyArray` for variables (e.g. for creating custom constraints), this has to be done AFTER the last execution of :obj:`BlackBoxFuncList.handle_duplicates` with `unify_variables = True`. Since this unification is expected to be executed in the initializers of the optimizers, such operation usually should be done after the instantiation of the relevant optimizers. Defaults to `False`.
181
+ """ # noqa: E501
182
+ self._objectives: list[BlackBoxFuncBase] = []
183
+
184
+ self._constraints = Constraints()
185
+ for bb_func in objectives:
186
+ self._objectives.append(bb_func)
187
+ self._constraints.append(bb_func.constraints)
188
+ self._unify_variables = unify_variables
189
+ self.handle_duplicates()
190
+
191
+ def handle_duplicates(self) -> None:
192
+ """Handle redundancy of variables and consistent issuance of `amplify.PolyArray` for different black-box objective functions, and unify variables and constraints. Expected to call this each time there is change in the objective functions to consider. The unification happens when `unify_variables = True` in :obj:`BlackBoxFuncList.__init__`.""" # noqa: E501
193
+ # Variable dictionary
194
+ var_dict_universe: dict[str, Any] = {}
195
+ for bb_func in self._objectives:
196
+ var_dict_universe.update(bb_func.variables.var_dict)
197
+
198
+ elemental_variable_name_list: list[str] = []
199
+ for bb_func in self._objectives:
200
+ elemental_variable_name_list += bb_func.variables.flat_names
201
+ self._variable_names = list(dict.fromkeys(elemental_variable_name_list))
202
+
203
+ if self._unify_variables:
204
+ # Nullify all existing Amplify SDK's variables.
205
+ for i, bb_func in enumerate(self._objectives):
206
+ bb_func.variables.nullify_poly_array(i)
207
+ # Update Variables.var_dict and re-issue Amplify SDK variables.
208
+ variable_generator_common = self._objectives[0].variables.variable_generator
209
+ for bb_func in self._objectives:
210
+ bb_func.variables.unify_variables(var_dict_universe, variable_generator_common)
211
+ # Reconstruct constraints based on unified variables described in Variables.var_dict.
212
+ for bb_func in self._objectives:
213
+ bb_func.constraints.unify_variables(var_dict_universe)
214
+
215
+ def __getattr__(self, name: str) -> BlackBoxFuncBase:
216
+ """Get a variable in var_dict.
217
+
218
+ Raises:
219
+ ValueError: If the specified object is not found.
220
+
221
+ Returns:
222
+ BlackBoxFuncBase: A black-box function class instance.
223
+ """
224
+ names = [obj.name for obj in self._objectives]
225
+
226
+ if name not in names:
227
+ raise ValueError(f"No such objective {name} is found. [{names}].")
228
+ return self._objectives[names.index(name)]
229
+
230
+ def add_constraint(
231
+ self,
232
+ constraint: Constraint | Constraints | list[Constraint | Constraints] | list[Constraint] | list[Constraints],
233
+ ) -> None:
234
+ """Add a user-defined constraint to the black-box function class.
235
+
236
+ Args:
237
+ constraint ( constraint: Constraint | Constraints | list[Constraint | Constraints] | list[Constraint] | list[Constraints]): User-defined constraints.
238
+ """ # noqa: E501
239
+ self._constraints.append(constraint)
240
+
241
+ def append(self, bb: BlackBoxFuncBase) -> None:
242
+ """Append a black-box function class.
243
+
244
+ Args:
245
+ bb (BlackBoxFuncBase): A black-box function class instance.
246
+ """
247
+ self._objectives.append(bb)
248
+ self.handle_duplicates()
249
+
250
+ def __len__(self) -> int:
251
+ """Return a number of black-box function class instances."""
252
+ return len(self._objectives)
253
+
254
+ def __getitem__(self, i: int) -> BlackBoxFuncBase:
255
+ """Return the i-th black-box function class instance.
256
+
257
+ Args:
258
+ i (int): Index of a black-box function class instance.
259
+
260
+ Returns:
261
+ BlackBoxFuncBase: The i-th black-box function class instance.
262
+ """
263
+ return self._objectives[i]
264
+
265
+ def __iter__(self) -> Iterator[BlackBoxFuncBase]:
266
+ """The list of black-box function class instances.
267
+
268
+ Yields:
269
+ Iterator[BlackBoxFuncBase]: A black-box function class instance.
270
+ """
271
+ yield from self._objectives
272
+
273
+ @property
274
+ def constraints(self) -> Constraints:
275
+ """Constraints associated with this black-box function list."""
276
+ return self._constraints
277
+
278
+ @property
279
+ def variable_names(self) -> list[str]:
280
+ """Names of variables associated with this black-box function list."""
281
+ return self._variable_names