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.
Files changed (42) hide show
  1. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/PKG-INFO +1 -1
  2. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/__version__.py +1 -1
  3. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/constraint.py +9 -13
  4. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/data_gen.py +0 -1
  5. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/optimizer.py +15 -6
  6. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/trainer.py +5 -5
  7. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/variable.py +1 -1
  8. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt.egg-info/PKG-INFO +1 -1
  9. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/test/test_optimizer.py +72 -1
  10. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/LICENSE.txt +0 -0
  11. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/README.md +0 -0
  12. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/__init__.py +0 -0
  13. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/bb_func.py +0 -0
  14. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/data_list.py +0 -0
  15. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/history.py +0 -0
  16. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/logger.py +0 -0
  17. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/misc.py +0 -0
  18. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/model.py +0 -0
  19. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/objective_example.py +0 -0
  20. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/plot.py +0 -0
  21. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/poly.py +0 -0
  22. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/solution_type.py +0 -0
  23. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt/variables.py +0 -0
  24. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt.egg-info/SOURCES.txt +0 -0
  25. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt.egg-info/dependency_links.txt +0 -0
  26. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt.egg-info/requires.txt +0 -0
  27. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/amplify_bbopt.egg-info/top_level.txt +0 -0
  28. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/pyproject.toml +0 -0
  29. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/setup.cfg +0 -0
  30. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/setup.py +0 -0
  31. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/test/test_bbfunc.py +0 -0
  32. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/test/test_constraint.py +0 -0
  33. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/test/test_data_gen.py +0 -0
  34. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/test/test_data_list.py +0 -0
  35. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/test/test_misc.py +0 -0
  36. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/test/test_model.py +0 -0
  37. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/test/test_objective_example.py +0 -0
  38. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/test/test_poly.py +0 -0
  39. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/test/test_solution_type.py +0 -0
  40. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/test/test_trainer.py +0 -0
  41. {amplify_bbopt-0.2.2 → amplify_bbopt-0.2.4}/test/test_variable.py +0 -0
  42. {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.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>
@@ -3,4 +3,4 @@
3
3
  # This source code is licensed under the MIT license found in the
4
4
  # LICENSE file in the root directory of this source tree.
5
5
 
6
- __version__ = "0.2.2"
6
+ __version__ = "0.2.4"
@@ -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
- (1) Imposing an inequality constraint with real variables (when coefficients in the constraint formulation is likely to yield real numbers), AND
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
- - Imposing an inequality constraint with real variables (when coefficients in the constraint formulation is likely to yield real numbers), AND
176
- - The specified solver (e.g Amplify Annealing Engine) being a QUBO solver that does not support real variables,
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 and left_val <= self.right[1] # type: ignore
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:
@@ -151,4 +151,3 @@ class DatasetGenerator:
151
151
  return data_list_list[0]
152
152
 
153
153
  return tuple(data_list_list)
154
- return tuple(data_list_list)
@@ -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
- solution = self._generate_alternative_solution(solution, search_count_max, find_neighbour=True)
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
- solution = self._generate_alternative_solution(solution, search_count_max)
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
- solution = self._generate_alternative_solution(None, search_count_max)
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
- # 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
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.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