ommx-openjij-adapter 1.9.4__tar.gz → 2.0.0__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: 1.9.4
3
+ Version: 2.0.0
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,8 +14,8 @@ 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<2.0.0,>=1.9.0rc1
18
- Requires-Dist: openjij>=0.9.2
17
+ Requires-Dist: ommx<3.0.0,>=2.0.0rc1
18
+ Requires-Dist: openjij<0.10.0,>=0.9.2
19
19
 
20
20
  OMMX Adapter for OpenJij
21
21
  =========================
@@ -0,0 +1,380 @@
1
+ from __future__ import annotations
2
+
3
+ from ommx.v1 import (
4
+ Instance,
5
+ State,
6
+ Samples,
7
+ SampleSet,
8
+ Solution,
9
+ DecisionVariable,
10
+ Constraint,
11
+ )
12
+ from ommx.adapter import SamplerAdapter
13
+ import openjij as oj
14
+ from typing_extensions import deprecated
15
+ from typing import Optional
16
+ import copy
17
+
18
+
19
+ class OMMXOpenJijSAAdapter(SamplerAdapter):
20
+ """
21
+ Sampling QUBO or HUBO with Simulated Annealing (SA) by `openjij.SASampler <https://openjij.github.io/OpenJij/reference/openjij/index.html#openjij.SASampler>`_
22
+ """
23
+
24
+ ommx_instance: Instance
25
+ """
26
+ ommx.v1.Instance representing a QUBO or HUBO problem
27
+
28
+ The input `instance` must be a QUBO (Quadratic Unconstrained Binary Optimization) or HUBO (Higher-order Unconstrained Binary Optimization) problem, i.e.
29
+
30
+ - All decision variables are binary
31
+ - No constraints
32
+ - Objective function is quadratic (QUBO) or higher (HUBO).
33
+ - Minimization problem
34
+
35
+ You can convert an instance to QUBO or HUBO via :meth:`ommx.v1.Instance.penalty_method` or other corresponding method.
36
+ """
37
+
38
+ beta_min: float | None = None
39
+ """ minimal value of inverse temperature """
40
+ beta_max: float | None = None
41
+ """ maximum value of inverse temperature """
42
+ num_sweeps: int | None = None
43
+ """ number of sweeps """
44
+ num_reads: int | None = None
45
+ """ number of reads """
46
+ schedule: list | None = None
47
+ """ list of inverse temperature (parameter only used if problem is QUBO)"""
48
+ initial_state: list | dict | None = None
49
+ """ initial state (parameter only used if problem is QUBO)"""
50
+ updater: str | None = None
51
+ """ updater algorithm """
52
+ sparse: bool | None = None
53
+ """ use sparse matrix or not (parameter only used if problem is QUBO)"""
54
+ reinitialize_state: bool | None = None
55
+ """ if true reinitialize state for each run (parameter only used if problem is QUBO)"""
56
+ seed: int | None = None
57
+ """ seed for Monte Carlo algorithm """
58
+
59
+ uniform_penalty_weight: Optional[float] = None
60
+ """ Weight for uniform penalty, passed to ``Instance.to_qubo`` or ``Instance.to_hubo`` """
61
+ penalty_weights: dict[int, float] = {}
62
+ """ Penalty weights for each constraint, passed to ``Instance.to_qubo`` or ``Instance.to_hubo`` """
63
+ inequality_integer_slack_max_range: int = 32
64
+ """ Max range for integer slack variables in inequality constraints, passed to ``Instance.to_qubo`` or ``Instance.to_hubo`` """
65
+
66
+ _instance_prepared: bool = False
67
+
68
+ def __init__(
69
+ self,
70
+ ommx_instance: Instance,
71
+ *,
72
+ beta_min: float | None = None,
73
+ beta_max: float | None = None,
74
+ num_sweeps: int | None = None,
75
+ num_reads: int | None = None,
76
+ schedule: list | None = None,
77
+ initial_state: list | dict | None = None,
78
+ updater: str | None = None,
79
+ sparse: bool | None = None,
80
+ reinitialize_state: bool | None = None,
81
+ seed: int | None = None,
82
+ uniform_penalty_weight: Optional[float] = None,
83
+ penalty_weights: dict[int, float] = {},
84
+ inequality_integer_slack_max_range: int = 32,
85
+ ):
86
+ self.ommx_instance = copy.deepcopy(ommx_instance)
87
+ self.beta_min = beta_min
88
+ self.beta_max = beta_max
89
+ self.num_sweeps = num_sweeps
90
+ self.num_reads = num_reads
91
+ self.schedule = schedule
92
+ self.initial_state = initial_state
93
+ self.updater = updater
94
+ self.sparse = sparse
95
+ self.reinitialize_state = reinitialize_state
96
+ self.seed = seed
97
+ self.uniform_penalty_weight = uniform_penalty_weight
98
+ self.penalty_weights = penalty_weights
99
+ self.inequality_integer_slack_max_range = inequality_integer_slack_max_range
100
+
101
+ @classmethod
102
+ def sample(
103
+ cls,
104
+ ommx_instance: Instance,
105
+ *,
106
+ beta_min: float | None = None,
107
+ beta_max: float | None = None,
108
+ num_sweeps: int | None = None,
109
+ num_reads: int | None = None,
110
+ schedule: list | None = None,
111
+ initial_state: list | dict | None = None,
112
+ updater: str | None = None,
113
+ sparse: bool | None = None,
114
+ reinitialize_state: bool | None = None,
115
+ seed: int | None = None,
116
+ uniform_penalty_weight: Optional[float] = None,
117
+ penalty_weights: dict[int, float] = {},
118
+ inequality_integer_slack_max_range: int = 32,
119
+ ) -> SampleSet:
120
+ sampler = cls(
121
+ ommx_instance,
122
+ beta_min=beta_min,
123
+ beta_max=beta_max,
124
+ num_sweeps=num_sweeps,
125
+ num_reads=num_reads,
126
+ schedule=schedule,
127
+ initial_state=initial_state,
128
+ updater=updater,
129
+ sparse=sparse,
130
+ reinitialize_state=reinitialize_state,
131
+ seed=seed,
132
+ uniform_penalty_weight=uniform_penalty_weight,
133
+ penalty_weights=penalty_weights,
134
+ inequality_integer_slack_max_range=inequality_integer_slack_max_range,
135
+ )
136
+ response = sampler._sample()
137
+ return sampler.decode_to_sampleset(response)
138
+
139
+ @classmethod
140
+ def solve(
141
+ cls,
142
+ ommx_instance: Instance,
143
+ *,
144
+ beta_min: float | None = None,
145
+ beta_max: float | None = None,
146
+ num_sweeps: int | None = None,
147
+ num_reads: int | None = None,
148
+ schedule: list | None = None,
149
+ initial_state: list | dict | None = None,
150
+ updater: str | None = None,
151
+ sparse: bool | None = None,
152
+ reinitialize_state: bool | None = None,
153
+ seed: int | None = None,
154
+ uniform_penalty_weight: Optional[float] = None,
155
+ penalty_weights: dict[int, float] = {},
156
+ inequality_integer_slack_max_range: int = 32,
157
+ ) -> Solution:
158
+ sample_set = cls.sample(
159
+ ommx_instance,
160
+ beta_min=beta_min,
161
+ beta_max=beta_max,
162
+ num_sweeps=num_sweeps,
163
+ num_reads=num_reads,
164
+ schedule=schedule,
165
+ initial_state=initial_state,
166
+ updater=updater,
167
+ sparse=sparse,
168
+ reinitialize_state=reinitialize_state,
169
+ seed=seed,
170
+ uniform_penalty_weight=uniform_penalty_weight,
171
+ penalty_weights=penalty_weights,
172
+ inequality_integer_slack_max_range=inequality_integer_slack_max_range,
173
+ )
174
+ return sample_set.best_feasible
175
+
176
+ def decode_to_sampleset(self, data: oj.Response) -> SampleSet:
177
+ samples = decode_to_samples(data)
178
+ return self.ommx_instance.evaluate_samples(samples)
179
+
180
+ def decode_to_samples(self, data: oj.Response) -> Samples:
181
+ """
182
+ Convert `openjij.Response <https://openjij.github.io/OpenJij/reference/openjij/index.html#openjij.Response>`_ to :class:`Samples`
183
+
184
+ There is a static method :meth:`decode_to_samples` that does the same thing.
185
+ """
186
+ return decode_to_samples(data)
187
+
188
+ @property
189
+ def sampler_input(self) -> dict[tuple[int, ...], float]:
190
+ self._prepare_convert()
191
+ if self._is_hubo:
192
+ return self._hubo
193
+ else:
194
+ return self._qubo
195
+
196
+ @property
197
+ def solver_input(self) -> dict[tuple[int, ...], float]:
198
+ return self.sampler_input
199
+
200
+ def decode(self, data: oj.Response) -> Solution:
201
+ sample_set = self.decode_to_sampleset(data)
202
+ return sample_set.best_feasible
203
+
204
+ def _sample(self) -> oj.Response:
205
+ sampler = oj.SASampler()
206
+ input = self.sampler_input
207
+ if self._is_hubo:
208
+ return sampler.sample_hubo(
209
+ input, # type: ignore
210
+ vartype="BINARY",
211
+ beta_min=self.beta_min,
212
+ beta_max=self.beta_max,
213
+ # maintaining default parameters in openjij impl if None passed
214
+ num_sweeps=self.num_sweeps or 1000,
215
+ num_reads=self.num_reads or 1,
216
+ updater=self.updater or "METROPOLIS",
217
+ seed=self.seed,
218
+ )
219
+
220
+ else:
221
+ return sampler.sample_qubo(
222
+ input, # type: ignore
223
+ beta_min=self.beta_min,
224
+ beta_max=self.beta_max,
225
+ num_sweeps=self.num_sweeps,
226
+ num_reads=self.num_reads,
227
+ schedule=self.schedule,
228
+ initial_state=self.initial_state,
229
+ updater=self.updater,
230
+ sparse=self.sparse,
231
+ reinitialize_state=self.reinitialize_state,
232
+ seed=self.seed,
233
+ )
234
+
235
+ # Manually perform the conversion process to QUBO/HUBO, instead of using
236
+ # `to_hubo` or `to_qubo`.
237
+ #
238
+ # This is so that we can manually call `as_hubo_format()`, check if the
239
+ # finalized instance is higher-order, and if not, call
240
+ # `as_qubo_format()`.
241
+ #
242
+ # We could do alternative methods like simply checking the degrees of
243
+ # the objective function and all constraints. But some instances will
244
+ # see to be higher-order but ultimately representable as QUBO after the
245
+ # conversion (eg. due to simplifying binary `x * x`). So we chose to do
246
+ # it this way.
247
+ def _prepare_convert(self):
248
+ if self._instance_prepared:
249
+ return
250
+
251
+ self.ommx_instance.as_minimization_problem()
252
+
253
+ continuous_variables = [
254
+ var.id
255
+ for var in self.ommx_instance.decision_variables
256
+ if var.kind == DecisionVariable.CONTINUOUS
257
+ ]
258
+ if len(continuous_variables) > 0:
259
+ raise ValueError(
260
+ f"Continuous variables are not supported in HUBO conversion: IDs={continuous_variables}"
261
+ )
262
+
263
+ # Prepare inequality constraints
264
+ ineq_ids = [
265
+ c.id
266
+ for c in self.ommx_instance.constraints
267
+ if c.equality == Constraint.LESS_THAN_OR_EQUAL_TO_ZERO
268
+ ]
269
+ for ineq_id in ineq_ids:
270
+ try:
271
+ self.ommx_instance.convert_inequality_to_equality_with_integer_slack(
272
+ ineq_id, self.inequality_integer_slack_max_range
273
+ )
274
+ except RuntimeError:
275
+ self.ommx_instance.add_integer_slack_to_inequality(
276
+ ineq_id, self.inequality_integer_slack_max_range
277
+ )
278
+
279
+ # Penalty method
280
+ if self.ommx_instance.constraints:
281
+ if self.uniform_penalty_weight is not None and self.penalty_weights:
282
+ raise ValueError(
283
+ "Both uniform_penalty_weight and penalty_weights are specified. Please choose one."
284
+ )
285
+ if self.penalty_weights:
286
+ pi = self.ommx_instance.penalty_method()
287
+ weights = {
288
+ p.id: self.penalty_weights[p.subscripts[0]] for p in pi.parameters
289
+ }
290
+ unconstrained = pi.with_parameters(weights)
291
+ else:
292
+ if self.uniform_penalty_weight is None:
293
+ # If both are None, defaults to uniform_penalty_weight = 1.0
294
+ self.uniform_penalty_weight = 1.0
295
+ pi = self.ommx_instance.uniform_penalty_method()
296
+ weight = pi.parameters[0]
297
+ unconstrained = pi.with_parameters(
298
+ {weight.id: self.uniform_penalty_weight}
299
+ )
300
+ self.ommx_instance.raw = unconstrained.raw
301
+
302
+ self.ommx_instance.log_encode()
303
+
304
+ hubo, _ = self.ommx_instance.as_hubo_format()
305
+ if any(len(k) > 2 for k in hubo.keys()):
306
+ self._is_hubo = True
307
+ self._hubo = hubo
308
+ else:
309
+ self._is_hubo = False
310
+ qubo, _ = self.ommx_instance.as_qubo_format()
311
+ self._qubo = qubo
312
+
313
+ self._instance_prepared = True
314
+
315
+
316
+ @deprecated("Renamed to `decode_to_samples`")
317
+ def response_to_samples(response: oj.Response) -> Samples:
318
+ """
319
+ Deprecated: renamed to :meth:`decode_to_samples`
320
+ """
321
+ return decode_to_samples(response)
322
+
323
+
324
+ def decode_to_samples(response: oj.Response) -> Samples:
325
+ """
326
+ Convert `openjij.Response <https://openjij.github.io/OpenJij/reference/openjij/index.html#openjij.Response>`_ to :class:`Samples`
327
+ """
328
+ # Create empty samples and append each state with its sample IDs
329
+ # Since OpenJij does not issue the sample ID, we need to generate it in the responsibility of this OMMX Adapter
330
+ samples = Samples({}) # Create empty samples
331
+ sample_id = 0
332
+
333
+ num_reads = len(response.record.num_occurrences)
334
+ for i in range(num_reads):
335
+ sample = response.record.sample[i]
336
+ state = State(entries=zip(response.variables, sample))
337
+ # `num_occurrences` is encoded into sample ID list.
338
+ # For example, if `num_occurrences` is 2, there are two samples with the same state, thus two sample IDs are generated.
339
+ ids = []
340
+ for _ in range(response.record.num_occurrences[i]):
341
+ ids.append(sample_id)
342
+ sample_id += 1
343
+ samples.append(ids, state)
344
+
345
+ return samples
346
+
347
+
348
+ @deprecated("Use `OMMXOpenJijSAAdapter.sample` instead")
349
+ def sample_qubo_sa(
350
+ instance: Instance,
351
+ *,
352
+ beta_min: float | None = None,
353
+ beta_max: float | None = None,
354
+ num_sweeps: int | None = None,
355
+ num_reads: int | None = None,
356
+ schedule: list | None = None,
357
+ initial_state: list | dict | None = None,
358
+ updater: str | None = None,
359
+ sparse: bool | None = None,
360
+ reinitialize_state: bool | None = None,
361
+ seed: int | None = None,
362
+ ) -> Samples:
363
+ """
364
+ Deprecated: Use :meth:`OMMXOpenJijSAAdapter.sample` instead
365
+ """
366
+ sampler = OMMXOpenJijSAAdapter(
367
+ instance,
368
+ beta_min=beta_min,
369
+ beta_max=beta_max,
370
+ num_sweeps=num_sweeps,
371
+ num_reads=num_reads,
372
+ schedule=schedule,
373
+ initial_state=initial_state,
374
+ updater=updater,
375
+ sparse=sparse,
376
+ reinitialize_state=reinitialize_state,
377
+ seed=seed,
378
+ )
379
+ response = sampler._sample()
380
+ return decode_to_samples(response)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ommx_openjij_adapter
3
- Version: 1.9.4
3
+ Version: 2.0.0
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,8 +14,8 @@ 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<2.0.0,>=1.9.0rc1
18
- Requires-Dist: openjij>=0.9.2
17
+ Requires-Dist: ommx<3.0.0,>=2.0.0rc1
18
+ Requires-Dist: openjij<0.10.0,>=0.9.2
19
19
 
20
20
  OMMX Adapter for OpenJij
21
21
  =========================
@@ -0,0 +1,2 @@
1
+ ommx<3.0.0,>=2.0.0rc1
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 = "1.9.4"
7
+ version = "2.0.0"
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>=1.9.0rc1,<2.0.0","openjij>=0.9.2"]
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):
@@ -1,231 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from ommx.v1 import Instance, State, Samples, SampleSet
4
- from ommx.adapter import SamplerAdapter
5
- import openjij as oj
6
- from typing_extensions import deprecated
7
- from typing import Optional
8
- import copy
9
-
10
-
11
- class OMMXOpenJijSAAdapter(SamplerAdapter):
12
- """
13
- Sampling QUBO with Simulated Annealing (SA) by `openjij.SASampler <https://openjij.github.io/OpenJij/reference/openjij/index.html#openjij.SASampler>`_
14
- """
15
-
16
- ommx_instance: Instance
17
- """
18
- ommx.v1.Instance representing a QUBO problem
19
-
20
- The input `instance` must be a QUBO (Quadratic Unconstrained Binary Optimization) problem, i.e.
21
-
22
- - Every decision variables are binary
23
- - No constraint
24
- - Objective function is quadratic
25
- - Minimization problem
26
-
27
- You can convert an instance to QUBO via :meth:`ommx.v1.Instance.penalty_method` or other corresponding method.
28
- """
29
-
30
- beta_min: float | None = None
31
- """ minimal value of inverse temperature """
32
- beta_max: float | None = None
33
- """ maximum value of inverse temperature """
34
- num_sweeps: int | None = None
35
- """ number of sweeps """
36
- num_reads: int | None = None
37
- """ number of reads """
38
- schedule: list | None = None
39
- """ list of inverse temperature """
40
- initial_state: list | dict | None = None
41
- """ initial state """
42
- updater: str | None = None
43
- """ updater algorithm """
44
- sparse: bool | None = None
45
- """ use sparse matrix or not """
46
- reinitialize_state: bool | None = None
47
- """ if true reinitialize state for each run """
48
- seed: int | None = None
49
- """ seed for Monte Carlo algorithm """
50
-
51
- uniform_penalty_weight: Optional[float] = None
52
- """ Weight for uniform penalty, passed to ``Instance.to_qubo`` """
53
- penalty_weights: dict[int, float] = {}
54
- """ Penalty weights for each constraint, passed to ``Instance.to_qubo`` """
55
- inequality_integer_slack_max_range: int = 32
56
- """ Max range for integer slack variables in inequality constraints, passed to ``Instance.to_qubo`` """
57
-
58
- @classmethod
59
- def sample(
60
- cls,
61
- ommx_instance: Instance,
62
- *,
63
- beta_min: float | None = None,
64
- beta_max: float | None = None,
65
- num_sweeps: int | None = None,
66
- num_reads: int | None = None,
67
- schedule: list | None = None,
68
- initial_state: list | dict | None = None,
69
- updater: str | None = None,
70
- sparse: bool | None = None,
71
- reinitialize_state: bool | None = None,
72
- seed: int | None = None,
73
- uniform_penalty_weight: Optional[float] = None,
74
- penalty_weights: dict[int, float] = {},
75
- inequality_integer_slack_max_range: int = 32,
76
- ) -> SampleSet:
77
- sampler = cls(
78
- ommx_instance,
79
- beta_min=beta_min,
80
- beta_max=beta_max,
81
- num_sweeps=num_sweeps,
82
- num_reads=num_reads,
83
- schedule=schedule,
84
- initial_state=initial_state,
85
- updater=updater,
86
- sparse=sparse,
87
- reinitialize_state=reinitialize_state,
88
- seed=seed,
89
- uniform_penalty_weight=uniform_penalty_weight,
90
- penalty_weights=penalty_weights,
91
- inequality_integer_slack_max_range=inequality_integer_slack_max_range,
92
- )
93
- response = sampler._sample()
94
- return sampler.decode_to_sampleset(response)
95
-
96
- def __init__(
97
- self,
98
- ommx_instance: Instance,
99
- *,
100
- beta_min: float | None = None,
101
- beta_max: float | None = None,
102
- num_sweeps: int | None = None,
103
- num_reads: int | None = None,
104
- schedule: list | None = None,
105
- initial_state: list | dict | None = None,
106
- updater: str | None = None,
107
- sparse: bool | None = None,
108
- reinitialize_state: bool | None = None,
109
- seed: int | None = None,
110
- uniform_penalty_weight: Optional[float] = None,
111
- penalty_weights: dict[int, float] = {},
112
- inequality_integer_slack_max_range: int = 32,
113
- ):
114
- self.ommx_instance = copy.deepcopy(ommx_instance)
115
- self.beta_min = beta_min
116
- self.beta_max = beta_max
117
- self.num_sweeps = num_sweeps
118
- self.num_reads = num_reads
119
- self.schedule = schedule
120
- self.initial_state = initial_state
121
- self.updater = updater
122
- self.sparse = sparse
123
- self.reinitialize_state = reinitialize_state
124
- self.seed = seed
125
- self.uniform_penalty_weight = uniform_penalty_weight
126
- self.penalty_weights = penalty_weights
127
- self.inequality_integer_slack_max_range = inequality_integer_slack_max_range
128
-
129
- def decode_to_sampleset(self, data: oj.Response) -> SampleSet:
130
- samples = decode_to_samples(data)
131
- return self.ommx_instance.evaluate_samples(samples)
132
-
133
- def decode_to_samples(self, data: oj.Response) -> Samples:
134
- """
135
- Convert `openjij.Response <https://openjij.github.io/OpenJij/reference/openjij/index.html#openjij.Response>`_ to :class:`Samples`
136
-
137
- There is a static method :meth:`decode_to_samples` that does the same thing.
138
- """
139
- return decode_to_samples(data)
140
-
141
- @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
149
-
150
- def _sample(self) -> oj.Response:
151
- 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
- )
166
-
167
-
168
- @deprecated("Renamed to `decode_to_samples`")
169
- def response_to_samples(response: oj.Response) -> Samples:
170
- """
171
- Deprecated: renamed to :meth:`decode_to_samples`
172
- """
173
- return decode_to_samples(response)
174
-
175
-
176
- def decode_to_samples(response: oj.Response) -> Samples:
177
- """
178
- Convert `openjij.Response <https://openjij.github.io/OpenJij/reference/openjij/index.html#openjij.Response>`_ to :class:`Samples`
179
- """
180
- # Filling into ommx.v1.Samples
181
- # Since OpenJij does not issue the sample ID, we need to generate it in the responsibility of this OMMX Adapter
182
- sample_id = 0
183
- entries = []
184
-
185
- num_reads = len(response.record.num_occurrences)
186
- for i in range(num_reads):
187
- sample = response.record.sample[i]
188
- state = State(entries=zip(response.variables, sample)) # type: ignore
189
- # `num_occurrences` is encoded into sample ID list.
190
- # For example, if `num_occurrences` is 2, there are two samples with the same state, thus two sample IDs are generated.
191
- ids = []
192
- for _ in range(response.record.num_occurrences[i]):
193
- ids.append(sample_id)
194
- sample_id += 1
195
- entries.append(Samples.SamplesEntry(state=state, ids=ids))
196
- return Samples(entries=entries)
197
-
198
-
199
- @deprecated("Use `OMMXOpenJijSAAdapter.sample` instead")
200
- def sample_qubo_sa(
201
- instance: Instance,
202
- *,
203
- beta_min: float | None = None,
204
- beta_max: float | None = None,
205
- num_sweeps: int | None = None,
206
- num_reads: int | None = None,
207
- schedule: list | None = None,
208
- initial_state: list | dict | None = None,
209
- updater: str | None = None,
210
- sparse: bool | None = None,
211
- reinitialize_state: bool | None = None,
212
- seed: int | None = None,
213
- ) -> Samples:
214
- """
215
- Deprecated: Use :meth:`OMMXOpenJijSAAdapter.sample` instead
216
- """
217
- sampler = OMMXOpenJijSAAdapter(
218
- instance,
219
- beta_min=beta_min,
220
- beta_max=beta_max,
221
- num_sweeps=num_sweeps,
222
- num_reads=num_reads,
223
- schedule=schedule,
224
- initial_state=initial_state,
225
- updater=updater,
226
- sparse=sparse,
227
- reinitialize_state=reinitialize_state,
228
- seed=seed,
229
- )
230
- response = sampler._sample()
231
- return decode_to_samples(response)
@@ -1,2 +0,0 @@
1
- ommx<2.0.0,>=1.9.0rc1
2
- openjij>=0.9.2