ommx-openjij-adapter 2.0.0rc1__tar.gz → 2.0.0rc3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ommx_openjij_adapter
3
- Version: 2.0.0rc1
3
+ Version: 2.0.0rc3
4
4
  Summary: OMMX Adapter for OpenJij.
5
5
  Author-email: "Jij Inc." <info@j-ij.com>
6
6
  Project-URL: Repository, https://github.com/Jij-Inc/ommx
@@ -14,7 +14,7 @@ Classifier: License :: OSI Approved :: Apache Software License
14
14
  Classifier: License :: OSI Approved :: MIT License
15
15
  Requires-Python: <3.13,>=3.9
16
16
  Description-Content-Type: text/markdown
17
- Requires-Dist: ommx<3.0.0,>=2.0.0b3
17
+ Requires-Dist: ommx<3.0.0,>=2.0.0rc1
18
18
  Requires-Dist: openjij<0.10.0,>=0.9.2
19
19
 
20
20
  OMMX Adapter for OpenJij
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from ommx.v1 import Instance, State, Samples, SampleSet
3
+ from ommx.v1 import Instance, State, Samples, SampleSet, DecisionVariable, Constraint
4
4
  from ommx.adapter import SamplerAdapter
5
5
  import openjij as oj
6
6
  from typing_extensions import deprecated
@@ -10,21 +10,21 @@ import copy
10
10
 
11
11
  class OMMXOpenJijSAAdapter(SamplerAdapter):
12
12
  """
13
- Sampling QUBO with Simulated Annealing (SA) by `openjij.SASampler <https://openjij.github.io/OpenJij/reference/openjij/index.html#openjij.SASampler>`_
13
+ Sampling QUBO or HUBO with Simulated Annealing (SA) by `openjij.SASampler <https://openjij.github.io/OpenJij/reference/openjij/index.html#openjij.SASampler>`_
14
14
  """
15
15
 
16
16
  ommx_instance: Instance
17
17
  """
18
- ommx.v1.Instance representing a QUBO problem
18
+ ommx.v1.Instance representing a QUBO or HUBO problem
19
19
 
20
- The input `instance` must be a QUBO (Quadratic Unconstrained Binary Optimization) problem, i.e.
20
+ The input `instance` must be a QUBO (Quadratic Unconstrained Binary Optimization) or HUBO (Higher-order Unconstrained Binary Optimization) problem, i.e.
21
21
 
22
- - Every decision variables are binary
23
- - No constraint
24
- - Objective function is quadratic
22
+ - All decision variables are binary
23
+ - No constraints
24
+ - Objective function is quadratic (QUBO) or higher (HUBO).
25
25
  - Minimization problem
26
26
 
27
- You can convert an instance to QUBO via :meth:`ommx.v1.Instance.penalty_method` or other corresponding method.
27
+ You can convert an instance to QUBO or HUBO via :meth:`ommx.v1.Instance.penalty_method` or other corresponding method.
28
28
  """
29
29
 
30
30
  beta_min: float | None = None
@@ -36,24 +36,26 @@ class OMMXOpenJijSAAdapter(SamplerAdapter):
36
36
  num_reads: int | None = None
37
37
  """ number of reads """
38
38
  schedule: list | None = None
39
- """ list of inverse temperature """
39
+ """ list of inverse temperature (parameter only used if problem is QUBO)"""
40
40
  initial_state: list | dict | None = None
41
- """ initial state """
41
+ """ initial state (parameter only used if problem is QUBO)"""
42
42
  updater: str | None = None
43
43
  """ updater algorithm """
44
44
  sparse: bool | None = None
45
- """ use sparse matrix or not """
45
+ """ use sparse matrix or not (parameter only used if problem is QUBO)"""
46
46
  reinitialize_state: bool | None = None
47
- """ if true reinitialize state for each run """
47
+ """ if true reinitialize state for each run (parameter only used if problem is QUBO)"""
48
48
  seed: int | None = None
49
49
  """ seed for Monte Carlo algorithm """
50
50
 
51
51
  uniform_penalty_weight: Optional[float] = None
52
- """ Weight for uniform penalty, passed to ``Instance.to_qubo`` """
52
+ """ Weight for uniform penalty, passed to ``Instance.to_qubo`` or ``Instance.to_hubo`` """
53
53
  penalty_weights: dict[int, float] = {}
54
- """ Penalty weights for each constraint, passed to ``Instance.to_qubo`` """
54
+ """ Penalty weights for each constraint, passed to ``Instance.to_qubo`` or ``Instance.to_hubo`` """
55
55
  inequality_integer_slack_max_range: int = 32
56
- """ Max range for integer slack variables in inequality constraints, passed to ``Instance.to_qubo`` """
56
+ """ Max range for integer slack variables in inequality constraints, passed to ``Instance.to_qubo`` or ``Instance.to_hubo`` """
57
+
58
+ _instance_prepared: bool = False
57
59
 
58
60
  @classmethod
59
61
  def sample(
@@ -139,30 +141,123 @@ class OMMXOpenJijSAAdapter(SamplerAdapter):
139
141
  return decode_to_samples(data)
140
142
 
141
143
  @property
142
- def sampler_input(self) -> dict[tuple[int, int], float]:
143
- qubo, _offset = self.ommx_instance.to_qubo(
144
- uniform_penalty_weight=self.uniform_penalty_weight,
145
- penalty_weights=self.penalty_weights,
146
- inequality_integer_slack_max_range=self.inequality_integer_slack_max_range,
147
- )
148
- return qubo
144
+ def sampler_input(self) -> dict[tuple[int, ...], float]:
145
+ self._prepare_convert()
146
+ if self._is_hubo:
147
+ return self._hubo
148
+ else:
149
+ return self._qubo
149
150
 
150
151
  def _sample(self) -> oj.Response:
151
152
  sampler = oj.SASampler()
152
- qubo = self.sampler_input
153
- return sampler.sample_qubo(
154
- qubo, # type: ignore
155
- beta_min=self.beta_min,
156
- beta_max=self.beta_max,
157
- num_sweeps=self.num_sweeps,
158
- num_reads=self.num_reads,
159
- schedule=self.schedule,
160
- initial_state=self.initial_state,
161
- updater=self.updater,
162
- sparse=self.sparse,
163
- reinitialize_state=self.reinitialize_state,
164
- seed=self.seed,
165
- )
153
+ input = self.sampler_input
154
+ if self._is_hubo:
155
+ return sampler.sample_hubo(
156
+ input, # type: ignore
157
+ vartype="BINARY",
158
+ beta_min=self.beta_min,
159
+ beta_max=self.beta_max,
160
+ # maintaining default parameters in openjij impl if None passed
161
+ num_sweeps=self.num_sweeps or 1000,
162
+ num_reads=self.num_reads or 1,
163
+ updater=self.updater or "METROPOLIS",
164
+ seed=self.seed,
165
+ )
166
+
167
+ else:
168
+ return sampler.sample_qubo(
169
+ input, # type: ignore
170
+ beta_min=self.beta_min,
171
+ beta_max=self.beta_max,
172
+ num_sweeps=self.num_sweeps,
173
+ num_reads=self.num_reads,
174
+ schedule=self.schedule,
175
+ initial_state=self.initial_state,
176
+ updater=self.updater,
177
+ sparse=self.sparse,
178
+ reinitialize_state=self.reinitialize_state,
179
+ seed=self.seed,
180
+ )
181
+
182
+ # Manually perform the conversion process to QUBO/HUBO, instead of using
183
+ # `to_hubo` or `to_qubo`.
184
+ #
185
+ # This is so that we can manually call `as_hubo_format()`, check if the
186
+ # finalized instance is higher-order, and if not, call
187
+ # `as_qubo_format()`.
188
+ #
189
+ # We could do alternative methods like simply checking the degrees of
190
+ # the objective function and all constraints. But some instances will
191
+ # see to be higher-order but ultimately representable as QUBO after the
192
+ # conversion (eg. due to simplifying binary `x * x`). So we chose to do
193
+ # it this way.
194
+ def _prepare_convert(self):
195
+ if self._instance_prepared:
196
+ return
197
+
198
+ self.ommx_instance.as_minimization_problem()
199
+
200
+ continuous_variables = [
201
+ var.id
202
+ for var in self.ommx_instance.decision_variables
203
+ if var.kind == DecisionVariable.CONTINUOUS
204
+ ]
205
+ if len(continuous_variables) > 0:
206
+ raise ValueError(
207
+ f"Continuous variables are not supported in HUBO conversion: IDs={continuous_variables}"
208
+ )
209
+
210
+ # Prepare inequality constraints
211
+ ineq_ids = [
212
+ c.id
213
+ for c in self.ommx_instance.constraints
214
+ if c.equality == Constraint.LESS_THAN_OR_EQUAL_TO_ZERO
215
+ ]
216
+ for ineq_id in ineq_ids:
217
+ try:
218
+ self.ommx_instance.convert_inequality_to_equality_with_integer_slack(
219
+ ineq_id, self.inequality_integer_slack_max_range
220
+ )
221
+ except RuntimeError:
222
+ self.ommx_instance.add_integer_slack_to_inequality(
223
+ ineq_id, self.inequality_integer_slack_max_range
224
+ )
225
+
226
+ # Penalty method
227
+ if self.ommx_instance.constraints:
228
+ if self.uniform_penalty_weight is not None and self.penalty_weights:
229
+ raise ValueError(
230
+ "Both uniform_penalty_weight and penalty_weights are specified. Please choose one."
231
+ )
232
+ if self.penalty_weights:
233
+ pi = self.ommx_instance.penalty_method()
234
+ weights = {
235
+ p.id: self.penalty_weights[p.subscripts[0]] for p in pi.parameters
236
+ }
237
+ unconstrained = pi.with_parameters(weights)
238
+ else:
239
+ if self.uniform_penalty_weight is None:
240
+ # If both are None, defaults to uniform_penalty_weight = 1.0
241
+ self.uniform_penalty_weight = 1.0
242
+ pi = self.ommx_instance.uniform_penalty_method()
243
+ weight = pi.parameters[0]
244
+ unconstrained = pi.with_parameters(
245
+ {weight.id: self.uniform_penalty_weight}
246
+ )
247
+ self.ommx_instance.raw = unconstrained.raw
248
+
249
+ self.ommx_instance.log_encode()
250
+
251
+ hubo, _ = self.ommx_instance.as_hubo_format()
252
+ if any(len(k) > 2 for k in hubo.keys()):
253
+ self._is_hubo = True
254
+ self._hubo = hubo
255
+ else:
256
+ self._is_hubo = False
257
+ qubo, _ = self.ommx_instance.as_qubo_format()
258
+ self._qubo = qubo
259
+
260
+ self._instance_prepared = True
166
261
 
167
262
 
168
263
  @deprecated("Renamed to `decode_to_samples`")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ommx_openjij_adapter
3
- Version: 2.0.0rc1
3
+ Version: 2.0.0rc3
4
4
  Summary: OMMX Adapter for OpenJij.
5
5
  Author-email: "Jij Inc." <info@j-ij.com>
6
6
  Project-URL: Repository, https://github.com/Jij-Inc/ommx
@@ -14,7 +14,7 @@ Classifier: License :: OSI Approved :: Apache Software License
14
14
  Classifier: License :: OSI Approved :: MIT License
15
15
  Requires-Python: <3.13,>=3.9
16
16
  Description-Content-Type: text/markdown
17
- Requires-Dist: ommx<3.0.0,>=2.0.0b3
17
+ Requires-Dist: ommx<3.0.0,>=2.0.0rc1
18
18
  Requires-Dist: openjij<0.10.0,>=0.9.2
19
19
 
20
20
  OMMX Adapter for OpenJij
@@ -1,2 +1,2 @@
1
- ommx<3.0.0,>=2.0.0b3
1
+ ommx<3.0.0,>=2.0.0rc1
2
2
  openjij<0.10.0,>=0.9.2
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ommx_openjij_adapter"
7
- version = "2.0.0rc1"
7
+ version = "2.0.0rc3"
8
8
 
9
9
  description = "OMMX Adapter for OpenJij."
10
10
  authors = [{ name = "Jij Inc.", email = "info@j-ij.com" }]
@@ -22,7 +22,7 @@ classifiers = [
22
22
  "License :: OSI Approved :: Apache Software License",
23
23
  "License :: OSI Approved :: MIT License",
24
24
  ]
25
- dependencies = ["ommx >= 2.0.0b3, < 3.0.0", "openjij >= 0.9.2, < 0.10.0"]
25
+ dependencies = ["ommx >= 2.0.0rc1, < 3.0.0", "openjij >= 0.9.2, < 0.10.0"]
26
26
 
27
27
  [project.urls]
28
28
  Repository = "https://github.com/Jij-Inc/ommx"
@@ -95,6 +95,68 @@ def integer_inequality():
95
95
  return pytest.param(instance, ans, id="integer_inequality")
96
96
 
97
97
 
98
+ def hubo_binary_no_constraint_minimize():
99
+ x0 = DecisionVariable.binary(0, name="x", subscripts=[0])
100
+ x1 = DecisionVariable.binary(1, name="x", subscripts=[1])
101
+ x2 = DecisionVariable.binary(2, name="x", subscripts=[2])
102
+ instance = Instance.from_components(
103
+ decision_variables=[x0, x1, x2],
104
+ objective=x0 + x0 * x1 * x2,
105
+ constraints=[],
106
+ sense=Instance.MINIMIZE,
107
+ )
108
+ ans = {(0,): 0.0, (1,): 0.0, (2,): 0.0}
109
+ return pytest.param(instance, ans, id="hubo_binary_no_constraint_minimize")
110
+
111
+
112
+ def hubo_binary_no_constraint_maximize():
113
+ x0 = DecisionVariable.binary(0, name="x", subscripts=[0])
114
+ x1 = DecisionVariable.binary(1, name="x", subscripts=[1])
115
+ x2 = DecisionVariable.binary(2, name="x", subscripts=[2])
116
+ instance = Instance.from_components(
117
+ decision_variables=[x0, x1, x2],
118
+ objective=x0 + x0 * x1 * x2,
119
+ constraints=[],
120
+ sense=Instance.MAXIMIZE,
121
+ )
122
+ ans = {(0,): 1.0, (1,): 1.0, (2,): 1.0}
123
+ return pytest.param(instance, ans, id="hubo_binary_no_constraint_maximize")
124
+
125
+
126
+ def hubo_binary_equality():
127
+ x0 = DecisionVariable.binary(0, name="x", subscripts=[0])
128
+ x1 = DecisionVariable.binary(1, name="x", subscripts=[1])
129
+ x2 = DecisionVariable.binary(2, name="x", subscripts=[2])
130
+
131
+ instance = Instance.from_components(
132
+ decision_variables=[x0, x1, x2],
133
+ objective=x0 + 2 * x1 + 3 * x2 + x0 * x1 * x2,
134
+ constraints=[x1 * x2 == 0],
135
+ sense=Instance.MAXIMIZE,
136
+ )
137
+
138
+ # x0 = x2 = 1, x1 = 0 is maximum
139
+ ans = {(0,): 1.0, (1,): 0.0, (2,): 1.0}
140
+ return pytest.param(instance, ans, id="hubo_binary_equality")
141
+
142
+
143
+ def hubo_binary_inequality():
144
+ x0 = DecisionVariable.binary(0, name="x", subscripts=[0])
145
+ x1 = DecisionVariable.binary(1, name="x", subscripts=[1])
146
+ x2 = DecisionVariable.binary(2, name="x", subscripts=[2])
147
+
148
+ instance = Instance.from_components(
149
+ decision_variables=[x0, x1, x2],
150
+ objective=x0 + 2 * x1 + 3 * x2 + x0 * x1 * x2,
151
+ constraints=[x0 + x1 + x2 <= 2],
152
+ sense=Instance.MAXIMIZE,
153
+ )
154
+
155
+ # x1 = x2 = 1, x0 = 0 is maximum
156
+ ans = {(0,): 0.0, (1,): 1.0, (2,): 1.0}
157
+ return pytest.param(instance, ans, id="hubo_binary_inequality")
158
+
159
+
98
160
  @pytest.mark.parametrize(
99
161
  "instance, ans",
100
162
  [
@@ -104,11 +166,15 @@ def integer_inequality():
104
166
  binary_inequality(),
105
167
  integer_equality(),
106
168
  integer_inequality(),
169
+ hubo_binary_no_constraint_minimize(),
170
+ hubo_binary_no_constraint_maximize(),
171
+ hubo_binary_equality(),
172
+ hubo_binary_inequality(),
107
173
  ],
108
174
  )
109
175
  def test_sample(instance, ans):
110
176
  sample_set = OMMXOpenJijSAAdapter.sample(
111
- instance, num_reads=1, uniform_penalty_weight=3.0, seed=12345
177
+ instance, num_reads=1, uniform_penalty_weight=3.0, seed=999
112
178
  )
113
179
  assert sample_set.extract_decision_variables("x", 0) == ans
114
180
 
@@ -122,11 +188,15 @@ def test_sample(instance, ans):
122
188
  binary_inequality(),
123
189
  integer_equality(),
124
190
  integer_inequality(),
191
+ hubo_binary_no_constraint_minimize(),
192
+ hubo_binary_no_constraint_maximize(),
193
+ hubo_binary_equality(),
194
+ hubo_binary_inequality(),
125
195
  ],
126
196
  )
127
197
  def test_solve(instance, ans):
128
198
  solution = OMMXOpenJijSAAdapter.solve(
129
- instance, num_reads=1, uniform_penalty_weight=3.0, seed=12345
199
+ instance, num_reads=1, uniform_penalty_weight=3.0, seed=999
130
200
  )
131
201
  assert solution.extract_decision_variables("x") == ans
132
202
 
@@ -140,6 +210,10 @@ def test_solve(instance, ans):
140
210
  binary_inequality(),
141
211
  integer_equality(),
142
212
  integer_inequality(),
213
+ hubo_binary_no_constraint_minimize(),
214
+ hubo_binary_no_constraint_maximize(),
215
+ hubo_binary_equality(),
216
+ hubo_binary_inequality(),
143
217
  ],
144
218
  )
145
219
  def test_sample_twice(instance, ans):