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.
- {ommx_openjij_adapter-1.9.4 → ommx_openjij_adapter-2.0.0}/PKG-INFO +3 -3
- ommx_openjij_adapter-2.0.0/ommx_openjij_adapter/__init__.py +380 -0
- {ommx_openjij_adapter-1.9.4 → ommx_openjij_adapter-2.0.0}/ommx_openjij_adapter.egg-info/PKG-INFO +3 -3
- ommx_openjij_adapter-2.0.0/ommx_openjij_adapter.egg-info/requires.txt +2 -0
- {ommx_openjij_adapter-1.9.4 → ommx_openjij_adapter-2.0.0}/pyproject.toml +2 -2
- {ommx_openjij_adapter-1.9.4 → ommx_openjij_adapter-2.0.0}/tests/test_sample.py +76 -2
- ommx_openjij_adapter-1.9.4/ommx_openjij_adapter/__init__.py +0 -231
- ommx_openjij_adapter-1.9.4/ommx_openjij_adapter.egg-info/requires.txt +0 -2
- {ommx_openjij_adapter-1.9.4 → ommx_openjij_adapter-2.0.0}/README.md +0 -0
- {ommx_openjij_adapter-1.9.4 → ommx_openjij_adapter-2.0.0}/ommx_openjij_adapter.egg-info/SOURCES.txt +0 -0
- {ommx_openjij_adapter-1.9.4 → ommx_openjij_adapter-2.0.0}/ommx_openjij_adapter.egg-info/dependency_links.txt +0 -0
- {ommx_openjij_adapter-1.9.4 → ommx_openjij_adapter-2.0.0}/ommx_openjij_adapter.egg-info/top_level.txt +0 -0
- {ommx_openjij_adapter-1.9.4 → ommx_openjij_adapter-2.0.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ommx_openjij_adapter
|
3
|
-
Version:
|
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<
|
18
|
-
Requires-Dist: openjij
|
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)
|
{ommx_openjij_adapter-1.9.4 → ommx_openjij_adapter-2.0.0}/ommx_openjij_adapter.egg-info/PKG-INFO
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ommx_openjij_adapter
|
3
|
-
Version:
|
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<
|
18
|
-
Requires-Dist: openjij
|
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
|
=========================
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "ommx_openjij_adapter"
|
7
|
-
version = "
|
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>=
|
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=
|
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=
|
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)
|
File without changes
|
{ommx_openjij_adapter-1.9.4 → ommx_openjij_adapter-2.0.0}/ommx_openjij_adapter.egg-info/SOURCES.txt
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|