amplify-bbopt 0.2.2__tar.gz → 0.2.4__tar.gz
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.
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/PKG-INFO +1 -1
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/__version__.py +1 -1
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/constraint.py +9 -13
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/data_gen.py +0 -1
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/optimizer.py +15 -6
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/trainer.py +5 -5
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/variable.py +1 -1
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt.egg-info/PKG-INFO +1 -1
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/test/test_optimizer.py +72 -1
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/LICENSE.txt +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/README.md +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/__init__.py +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/bb_func.py +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/data_list.py +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/history.py +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/logger.py +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/misc.py +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/model.py +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/objective_example.py +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/plot.py +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/poly.py +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/solution_type.py +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/variables.py +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt.egg-info/SOURCES.txt +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt.egg-info/dependency_links.txt +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt.egg-info/requires.txt +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt.egg-info/top_level.txt +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/pyproject.toml +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/setup.cfg +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/setup.py +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/test/test_bbfunc.py +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/test/test_constraint.py +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/test/test_data_gen.py +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/test/test_data_list.py +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/test/test_misc.py +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/test/test_model.py +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/test/test_objective_example.py +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/test/test_poly.py +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/test/test_solution_type.py +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/test/test_trainer.py +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/test/test_variable.py +0 -0
- {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/test/test_variables.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: amplify-bbopt
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: An Amplify-based library to facilitate black-box optimization
|
|
5
5
|
Author-email: Fixstars Amplify Corporation <y_matsuda@fixstars.com>
|
|
6
6
|
Maintainer-email: Yoshiki Matsuda <y_matsuda@fixstars.com>, Yuki Minamoto <yuki.minamoto@fixstars.com>
|
|
@@ -158,12 +158,9 @@ class Constraint:
|
|
|
158
158
|
def penalty_formulation(self) -> str:
|
|
159
159
|
"""The Amplify SDK's `penalty_formulation` parameter.
|
|
160
160
|
|
|
161
|
-
Set this to "Relaxation" when:
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
(2) The specified solver (e.g Amplify Annealing Engine) being a QUBO solver that does not support real variables,
|
|
166
|
-
until the Amplify SDK used internally in Amplify-BBOpt supports the real-to-binary variable conversion. See: https://amplify.fixstars.com/ja/docs/amplify/v1/penalty.html#ineq-penalty
|
|
161
|
+
Set this to "Relaxation" when you:
|
|
162
|
+
(1) impose an inequality constraint with real variables (when coefficients in the constraint formulation is likely to yield real numbers), AND
|
|
163
|
+
(2) the specified solver (e.g Amplify Annealing Engine) is a QUBO solver that does not support real variables, until the Amplify SDK used internally in Amplify-BBOpt supports the real-to-binary variable conversion. See: https://amplify.fixstars.com/ja/docs/amplify/v1/penalty.html#ineq-penalty
|
|
167
164
|
""" # noqa: E501
|
|
168
165
|
return self._penalty_formulation
|
|
169
166
|
|
|
@@ -171,10 +168,9 @@ class Constraint:
|
|
|
171
168
|
def penalty_formulation(self, value: str) -> None:
|
|
172
169
|
"""Set the Amplify SDK's `penalty_formulation` parameter.
|
|
173
170
|
|
|
174
|
-
Set this to "Relaxation" when:
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
until the Amplify SDK used internally in Amplify-BBOpt supports the real-to-binary variable conversion. See: https://amplify.fixstars.com/ja/docs/amplify/v1/penalty.html#ineq-penalty
|
|
171
|
+
Set this to "Relaxation" when you:
|
|
172
|
+
(1) impose an inequality constraint with real variables (when coefficients in the constraint formulation is likely to yield real numbers), AND
|
|
173
|
+
(2) the specified solver (e.g Amplify Annealing Engine) is a QUBO solver that does not support real variables, until the Amplify SDK used internally in Amplify-BBOpt supports the real-to-binary variable conversion. See: https://amplify.fixstars.com/ja/docs/amplify/v1/penalty.html#ineq-penalty
|
|
178
174
|
|
|
179
175
|
Args:
|
|
180
176
|
value (float): A `penalty_formulation`.
|
|
@@ -196,9 +192,9 @@ class Constraint:
|
|
|
196
192
|
elif self.op == LE:
|
|
197
193
|
c = amplify.less_equal(poly, self.right, penalty_formulation=self._penalty_formulation) # type: ignore
|
|
198
194
|
elif self.op == GE:
|
|
199
|
-
c = amplify.greater_equal(poly, self.right) # type: ignore
|
|
195
|
+
c = amplify.greater_equal(poly, self.right, penalty_formulation=self._penalty_formulation) # type: ignore
|
|
200
196
|
elif self.op == CL:
|
|
201
|
-
c = amplify.clamp(poly, self.right) # type: ignore
|
|
197
|
+
c = amplify.clamp(poly, self.right, penalty_formulation=self._penalty_formulation) # type: ignore
|
|
202
198
|
else:
|
|
203
199
|
raise ValueError(f"{self.op} is invalid keyword.")
|
|
204
200
|
c.weight = self._weight
|
|
@@ -233,7 +229,7 @@ class Constraint:
|
|
|
233
229
|
if self.op == GE:
|
|
234
230
|
return left_val >= self.right # type: ignore
|
|
235
231
|
if self.op == CL:
|
|
236
|
-
return self.right[0] <= left_val
|
|
232
|
+
return self.right[0] <= left_val <= self.right[1] # type: ignore
|
|
237
233
|
raise ValueError(f"{self.op} is invalid keyword.")
|
|
238
234
|
|
|
239
235
|
def __str__(self) -> str:
|
|
@@ -493,7 +493,7 @@ class QAOptimizerBase(OptimizerBase, Generic[T, M]):
|
|
|
493
493
|
|
|
494
494
|
def _generate_alternative_solution(
|
|
495
495
|
self, solution: StructuredSolution | None, search_count_max: int, find_neighbour: bool = False
|
|
496
|
-
) -> StructuredSolution:
|
|
496
|
+
) -> StructuredSolution | None:
|
|
497
497
|
"""Generate a solution that is close to the original solution. The new solution also meets the user defined constraints when a sufficient search_count_max is given.
|
|
498
498
|
|
|
499
499
|
Args:
|
|
@@ -502,14 +502,14 @@ class QAOptimizerBase(OptimizerBase, Generic[T, M]):
|
|
|
502
502
|
find_neighbour (bool, optional): True to generate a random value neighbour to the reference value. Defaults to `False`.
|
|
503
503
|
|
|
504
504
|
Returns:
|
|
505
|
-
StructuredSolution: The (updated)) solution.
|
|
505
|
+
StructuredSolution | None: The (updated)) solution. If not found solution (that meet constraints), returns `None` (this happens when the solution is originally `None`).
|
|
506
506
|
""" # noqa: E501
|
|
507
507
|
for _ in range(search_count_max):
|
|
508
508
|
solution_new = self._objective.variables.generate_random_value(self._rng, solution, find_neighbour)
|
|
509
509
|
if self.constraints.is_satisfied(solution_new.to_flat().to_solution_dict()):
|
|
510
510
|
return solution_new
|
|
511
511
|
logger().warning("No alternative solution was found that meets the constraints. The original solution is used.")
|
|
512
|
-
assert solution is not None
|
|
512
|
+
# assert solution is not None
|
|
513
513
|
return solution
|
|
514
514
|
|
|
515
515
|
def _ensure_uniqueness(
|
|
@@ -545,15 +545,24 @@ class QAOptimizerBase(OptimizerBase, Generic[T, M]):
|
|
|
545
545
|
# if the same solution appears "frequently", alternative solution is made such that
|
|
546
546
|
# the new solution is close (in the below two levels) to the original solution.
|
|
547
547
|
if how_frequent % 2 == 0:
|
|
548
|
-
|
|
548
|
+
alternative_solution = self._generate_alternative_solution(
|
|
549
|
+
solution, search_count_max, find_neighbour=True
|
|
550
|
+
)
|
|
551
|
+
assert alternative_solution is not None
|
|
552
|
+
solution = alternative_solution
|
|
549
553
|
else:
|
|
550
|
-
|
|
554
|
+
alternative_solution = self._generate_alternative_solution(solution, search_count_max)
|
|
555
|
+
assert alternative_solution is not None
|
|
556
|
+
solution = alternative_solution
|
|
551
557
|
else:
|
|
552
558
|
# If the same solution appears not too often, generate an alternative solution randomly.
|
|
553
559
|
# If the same solution appears too often, SolutionFrequency returns is_frequent=False.
|
|
554
560
|
# This is because such solution is perhaps the result of optimizer being trapped in a local minimum.
|
|
555
561
|
# Thus, and alternative solution is generated randomly.
|
|
556
|
-
|
|
562
|
+
alternative_solution = self._generate_alternative_solution(None, search_count_max)
|
|
563
|
+
solution = original_solution.copy() if alternative_solution is None else alternative_solution
|
|
564
|
+
# "alternative_solution is None" happens when set constraints are too complex and
|
|
565
|
+
# _generate_alternative_solution could not find alternative solution.
|
|
557
566
|
|
|
558
567
|
return original_solution, False
|
|
559
568
|
|
|
@@ -127,14 +127,14 @@ class TorchFMTrainer(TrainerBase):
|
|
|
127
127
|
|
|
128
128
|
# Validation
|
|
129
129
|
with torch.no_grad():
|
|
130
|
+
loss = 0
|
|
130
131
|
for x_valid, y_valid in valid_loader:
|
|
131
|
-
loss = 0
|
|
132
132
|
y_pred = model(x_valid)
|
|
133
133
|
loss += criterion(y_pred, y_valid)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
134
|
+
# If the loss is updated, update the parameters
|
|
135
|
+
if loss < min_loss:
|
|
136
|
+
best_parameters = copy.deepcopy(model.state_dict())
|
|
137
|
+
min_loss = loss
|
|
138
138
|
if scheduler is not None:
|
|
139
139
|
scheduler.step()
|
|
140
140
|
|
|
@@ -1088,7 +1088,7 @@ class IntegerVariable(VariableBase):
|
|
|
1088
1088
|
|
|
1089
1089
|
if value < self.bounds[0] or value > self.bounds[1]:
|
|
1090
1090
|
raise ValueError(f"Given value {value} must be bounded by bounds={self.bounds}.")
|
|
1091
|
-
return value - self.bounds[0]
|
|
1091
|
+
return int(value - self.bounds[0])
|
|
1092
1092
|
|
|
1093
1093
|
def idx_to_value(self, idx: int) -> int:
|
|
1094
1094
|
"""Converts a value index to a value of the variable.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: amplify-bbopt
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: An Amplify-based library to facilitate black-box optimization
|
|
5
5
|
Author-email: Fixstars Amplify Corporation <y_matsuda@fixstars.com>
|
|
6
6
|
Maintainer-email: Yoshiki Matsuda <y_matsuda@fixstars.com>, Yuki Minamoto <yuki.minamoto@fixstars.com>
|
|
@@ -21,10 +21,14 @@ from amplify_bbopt import (
|
|
|
21
21
|
IntegerVariableList,
|
|
22
22
|
KernelQAOptimizer,
|
|
23
23
|
MultiObjectiveOptimizer,
|
|
24
|
+
RealVariableList,
|
|
24
25
|
StructuredSolution,
|
|
25
26
|
TorchFMTrainer,
|
|
26
27
|
blackbox,
|
|
28
|
+
clamp,
|
|
27
29
|
equal_to,
|
|
30
|
+
greater_equal,
|
|
31
|
+
less_equal,
|
|
28
32
|
logger,
|
|
29
33
|
)
|
|
30
34
|
|
|
@@ -60,6 +64,14 @@ def objective_func_4(
|
|
|
60
64
|
return sum(x) + c
|
|
61
65
|
|
|
62
66
|
|
|
67
|
+
@blackbox
|
|
68
|
+
def objective_func_4_real(
|
|
69
|
+
x: Annotated[list[float], RealVariableList(bounds=(0, 9), nbins=5, length=3)],
|
|
70
|
+
c: Annotated[int, IntegerVariable(bounds=(0, 19))],
|
|
71
|
+
):
|
|
72
|
+
return sum(x) + c
|
|
73
|
+
|
|
74
|
+
|
|
63
75
|
assert logger.handler is not None
|
|
64
76
|
logger.handler.setLevel(logging.ERROR)
|
|
65
77
|
|
|
@@ -112,6 +124,49 @@ def kernel_qa_optimizer_4(require_client=True):
|
|
|
112
124
|
return KernelQAOptimizer(data, objective_func_4, client, seed=0)
|
|
113
125
|
|
|
114
126
|
|
|
127
|
+
@pytest.fixture
|
|
128
|
+
def kernel_qa_optimizer_4_impossible_constraint(require_client=True):
|
|
129
|
+
client = None
|
|
130
|
+
if require_client:
|
|
131
|
+
client = amplify.FixstarsClient()
|
|
132
|
+
client.parameters.timeout = timedelta(milliseconds=1000) # 1000 ミリ秒
|
|
133
|
+
# client.token = ""
|
|
134
|
+
|
|
135
|
+
variables = objective_func_4.variables
|
|
136
|
+
assert isinstance(variables.x, IntegerVariableList)
|
|
137
|
+
c = equal_to(variables.x, 30)
|
|
138
|
+
objective_func_4.add_constraint(c)
|
|
139
|
+
|
|
140
|
+
data = DatasetGenerator(objective=objective_func_4, meet_constraints=False, seed=0).generate(num_samples=3)
|
|
141
|
+
assert isinstance(data, DataList)
|
|
142
|
+
return KernelQAOptimizer(data, objective_func_4, client, seed=0)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@pytest.fixture
|
|
146
|
+
def kernel_qa_optimizer_4_real_inequality_constraint(require_client=True):
|
|
147
|
+
client = None
|
|
148
|
+
if require_client:
|
|
149
|
+
client = amplify.FixstarsClient()
|
|
150
|
+
client.parameters.timeout = timedelta(milliseconds=1000) # 1000 ミリ秒
|
|
151
|
+
# client.token = ""
|
|
152
|
+
|
|
153
|
+
variables = objective_func_4_real.variables
|
|
154
|
+
assert isinstance(variables.x, RealVariableList)
|
|
155
|
+
c0 = less_equal(variables.x[0] + 2.5 * variables.x[1], 30)
|
|
156
|
+
c1 = greater_equal(variables.x[0] - 2.5 * variables.x[1], 0)
|
|
157
|
+
c2 = clamp(variables.x[0] - 2.5 * variables.x[1], (0, 30))
|
|
158
|
+
c0.penalty_formulation = "Relaxation"
|
|
159
|
+
c1.penalty_formulation = "Relaxation"
|
|
160
|
+
c2.penalty_formulation = "Relaxation"
|
|
161
|
+
objective_func_4_real.add_constraint(c0)
|
|
162
|
+
objective_func_4_real.add_constraint(c1)
|
|
163
|
+
objective_func_4_real.add_constraint(c2)
|
|
164
|
+
|
|
165
|
+
data = DatasetGenerator(objective=objective_func_4_real, meet_constraints=False, seed=0).generate(num_samples=3)
|
|
166
|
+
assert isinstance(data, DataList)
|
|
167
|
+
return KernelQAOptimizer(data, objective_func_4_real, client, seed=0)
|
|
168
|
+
|
|
169
|
+
|
|
115
170
|
def test_ensure_uniqueness_single_objective(kernel_qa_optimizer_4):
|
|
116
171
|
rng = np.random.default_rng(seed=0)
|
|
117
172
|
|
|
@@ -132,6 +187,19 @@ def test_ensure_uniqueness_single_objective(kernel_qa_optimizer_4):
|
|
|
132
187
|
print(f"{ave_distance=}")
|
|
133
188
|
|
|
134
189
|
|
|
190
|
+
def test_ensure_uniqueness_constraint(kernel_qa_optimizer_4_impossible_constraint):
|
|
191
|
+
# To test ensure_uniqueness returns a solution even when impossible constraints are set.
|
|
192
|
+
|
|
193
|
+
solution = FlatSolution(
|
|
194
|
+
kernel_qa_optimizer_4_impossible_constraint.objective.variables,
|
|
195
|
+
kernel_qa_optimizer_4_impossible_constraint.data.x[0],
|
|
196
|
+
).to_structured()
|
|
197
|
+
org_solution = solution.copy()
|
|
198
|
+
solution, is_modified = kernel_qa_optimizer_4_impossible_constraint._ensure_uniqueness(solution, 10) # noqa: SLF001
|
|
199
|
+
assert solution.values == org_solution.values
|
|
200
|
+
assert not is_modified
|
|
201
|
+
|
|
202
|
+
|
|
135
203
|
def test_fmqa_optimizer(fmqa_optimizer):
|
|
136
204
|
poly_array = fmqa_optimizer.objective.variables.poly_array
|
|
137
205
|
custom_obj = poly_array[0] * poly_array[1]
|
|
@@ -156,7 +224,6 @@ def test_fmqa_optimizer(fmqa_optimizer):
|
|
|
156
224
|
fmqa_optimizer._elapsed_time.append(0) # noqa: SLF001
|
|
157
225
|
fmqa_optimizer._is_de_duplication.append(True) # noqa: SLF001
|
|
158
226
|
history = fmqa_optimizer.fetch_history()
|
|
159
|
-
print(history.history_df)
|
|
160
227
|
|
|
161
228
|
assert len(history.history_df) == 4
|
|
162
229
|
assert history.num_initial_data == 4
|
|
@@ -191,6 +258,10 @@ def test_fmqa_optimizer(fmqa_optimizer):
|
|
|
191
258
|
assert "x" in fmqa_optimizer.best_solution
|
|
192
259
|
|
|
193
260
|
|
|
261
|
+
def test_kernel_qa_optimizer_4_real_inequality_constraint(kernel_qa_optimizer_4_real_inequality_constraint):
|
|
262
|
+
kernel_qa_optimizer_4_real_inequality_constraint.optimize(num_cycles=1)
|
|
263
|
+
|
|
264
|
+
|
|
194
265
|
def test_multi_objective_optimizer_dependency_variables_unification(fmqa_optimizer, kernel_qa_optimizer):
|
|
195
266
|
# two objective functions dependent via a common variable x.
|
|
196
267
|
# test to correctly unify the variables.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|