pyqbpp 2025.5.11__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.
- pyqbpp-2025.5.11/LICENSE +18 -0
- pyqbpp-2025.5.11/PKG-INFO +9 -0
- pyqbpp-2025.5.11/pyproject.toml +18 -0
- pyqbpp-2025.5.11/pyqbpp.egg-info/PKG-INFO +9 -0
- pyqbpp-2025.5.11/pyqbpp.egg-info/SOURCES.txt +7 -0
- pyqbpp-2025.5.11/pyqbpp.egg-info/dependency_links.txt +1 -0
- pyqbpp-2025.5.11/pyqbpp.egg-info/top_level.txt +1 -0
- pyqbpp-2025.5.11/pyqbpp.py +361 -0
- pyqbpp-2025.5.11/setup.cfg +4 -0
pyqbpp-2025.5.11/LICENSE
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Koji Nakano
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to use,
|
|
7
|
+
copy, and modify the Software for non-commercial and non-industrial purposes only.
|
|
8
|
+
|
|
9
|
+
Commercial use, including but not limited to any business use, service offering,
|
|
10
|
+
or incorporation into a commercial product, is prohibited without a separate
|
|
11
|
+
written agreement with the copyright holder.
|
|
12
|
+
|
|
13
|
+
The Software is provided "as is", without warranty of any kind, express or implied,
|
|
14
|
+
including but not limited to the warranties of merchantability, fitness for a particular
|
|
15
|
+
purpose, and noninfringement. In no event shall the authors or copyright holders
|
|
16
|
+
be liable for any claim, damages, or other liability, whether in an action of contract,
|
|
17
|
+
tort, or otherwise, arising from, out of, or in connection with the Software or the use
|
|
18
|
+
or other dealings in the Software.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyqbpp
|
|
3
|
+
Version: 2025.5.11
|
|
4
|
+
Summary: Python version of QUBO++
|
|
5
|
+
Author-email: Koji Nakano <nakano@hiroshima-u.ac.jp>
|
|
6
|
+
License-Expression: LicenseRef-PYQBPPLicense
|
|
7
|
+
Requires-Python: >=3.6
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pyqbpp"
|
|
7
|
+
version = "2025.05.11"
|
|
8
|
+
description = "Python version of QUBO++"
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "Koji Nakano", email = "nakano@hiroshima-u.ac.jp" }
|
|
11
|
+
]
|
|
12
|
+
license = "LicenseRef-PYQBPPLicense"
|
|
13
|
+
license-files = ["LICENSE"]
|
|
14
|
+
requires-python = ">=3.6"
|
|
15
|
+
dependencies = []
|
|
16
|
+
|
|
17
|
+
[tool.setuptools]
|
|
18
|
+
py-modules = ["pyqbpp"]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyqbpp
|
|
3
|
+
Version: 2025.5.11
|
|
4
|
+
Summary: Python version of QUBO++
|
|
5
|
+
Author-email: Koji Nakano <nakano@hiroshima-u.ac.jp>
|
|
6
|
+
License-Expression: LicenseRef-PYQBPPLicense
|
|
7
|
+
Requires-Python: >=3.6
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Dynamic: license-file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyqbpp
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
# @file pyqbpp.py
|
|
2
|
+
# @brief Python version of QUBO++
|
|
3
|
+
# @details This file contains the implementation of a Python version of QUBO++.
|
|
4
|
+
# @copyright Copyright (c) 2025 Koji Nakano
|
|
5
|
+
# @license Following the QUBO++ license
|
|
6
|
+
# @author Koji Nakano
|
|
7
|
+
# @version 2025.05.11
|
|
8
|
+
# @note This program is under development and compatibility with QUBO++ is limited.
|
|
9
|
+
# @date 2025-05-11
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Var:
|
|
13
|
+
num = 0
|
|
14
|
+
unnamed_num = 0
|
|
15
|
+
|
|
16
|
+
def __init__(self, *args):
|
|
17
|
+
if len(args) == 0:
|
|
18
|
+
self.name = f"{{{Var.num}}}"
|
|
19
|
+
self.index = Var.num
|
|
20
|
+
self.unnamed_index = Var.unnamed_num
|
|
21
|
+
Var.num += 1
|
|
22
|
+
Var.unnamed_num += 1
|
|
23
|
+
elif len(args) == 1:
|
|
24
|
+
self.name = args[0]
|
|
25
|
+
self.index = Var.num
|
|
26
|
+
Var.num += 1
|
|
27
|
+
else:
|
|
28
|
+
raise ValueError("Var has at most one argument")
|
|
29
|
+
|
|
30
|
+
def __eq__(self, other):
|
|
31
|
+
return isinstance(other, Var) and self.index == other.index
|
|
32
|
+
|
|
33
|
+
def __hash__(self):
|
|
34
|
+
return hash(self.index)
|
|
35
|
+
|
|
36
|
+
def __str__(self):
|
|
37
|
+
return self.name
|
|
38
|
+
|
|
39
|
+
def __repr__(self):
|
|
40
|
+
return str(self)
|
|
41
|
+
|
|
42
|
+
def __add__(self, other):
|
|
43
|
+
return Expr(self, other)
|
|
44
|
+
|
|
45
|
+
def __radd__(self, other):
|
|
46
|
+
return Expr(other, self)
|
|
47
|
+
|
|
48
|
+
def __sub__(self, other):
|
|
49
|
+
return Expr(self, Term(-1, other))
|
|
50
|
+
|
|
51
|
+
def __rsub__(self, other):
|
|
52
|
+
return Expr(other, Term(-1, self))
|
|
53
|
+
|
|
54
|
+
def __mul__(self, other):
|
|
55
|
+
if isinstance(other, int):
|
|
56
|
+
return Term(other, self)
|
|
57
|
+
elif isinstance(other, Var):
|
|
58
|
+
return Term(self, other)
|
|
59
|
+
elif isinstance(other, Term):
|
|
60
|
+
return Term(*([self] + other.var), other.coeff)
|
|
61
|
+
else:
|
|
62
|
+
return NotImplemented
|
|
63
|
+
|
|
64
|
+
def __rmul__(self, other):
|
|
65
|
+
return self.__mul__(other)
|
|
66
|
+
|
|
67
|
+
def __lt__(self, other):
|
|
68
|
+
return self.index < other.index
|
|
69
|
+
|
|
70
|
+
def __str__(self):
|
|
71
|
+
return self.name
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def var(*args):
|
|
75
|
+
if len(args) == 0:
|
|
76
|
+
return Var()
|
|
77
|
+
|
|
78
|
+
if isinstance(args[0], str):
|
|
79
|
+
var_name = args[0]
|
|
80
|
+
dims = args[1:]
|
|
81
|
+
else:
|
|
82
|
+
var_name = None
|
|
83
|
+
dims = args
|
|
84
|
+
|
|
85
|
+
if len(dims) == 0:
|
|
86
|
+
return Var(var_name)
|
|
87
|
+
|
|
88
|
+
return Vector([
|
|
89
|
+
var(
|
|
90
|
+
f"{var_name}[{i}]" if var_name is not None else None,
|
|
91
|
+
*dims[1:]
|
|
92
|
+
)
|
|
93
|
+
for i in range(dims[0])
|
|
94
|
+
])
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class Term:
|
|
98
|
+
def __init__(self, *args):
|
|
99
|
+
self.coeff = 1
|
|
100
|
+
self.vars = []
|
|
101
|
+
for arg in args:
|
|
102
|
+
if isinstance(arg, int):
|
|
103
|
+
self.coeff *= arg
|
|
104
|
+
elif isinstance(arg, Var):
|
|
105
|
+
self.vars.append(arg)
|
|
106
|
+
else:
|
|
107
|
+
raise ValueError("Term can only have int or Var arguments")
|
|
108
|
+
|
|
109
|
+
def __str__(self):
|
|
110
|
+
if not self.vars:
|
|
111
|
+
return str(self.coeff)
|
|
112
|
+
var_str = "*".join(str(v) for v in self.vars)
|
|
113
|
+
if self.coeff == 1:
|
|
114
|
+
return var_str
|
|
115
|
+
elif self.coeff == -1:
|
|
116
|
+
return "-" + var_str
|
|
117
|
+
else:
|
|
118
|
+
return str(self.coeff) + "*" + var_str
|
|
119
|
+
|
|
120
|
+
def __repr__(self):
|
|
121
|
+
return str(self)
|
|
122
|
+
|
|
123
|
+
def __add__(self, other):
|
|
124
|
+
return Expr(self, other)
|
|
125
|
+
|
|
126
|
+
def __radd__(self, other):
|
|
127
|
+
return Expr(other, self)
|
|
128
|
+
|
|
129
|
+
def __sub__(self, other):
|
|
130
|
+
if isinstance(other, Term):
|
|
131
|
+
return Expr(self, Term(-other.coeff, *other.var))
|
|
132
|
+
return Expr(self, Term(-1, other))
|
|
133
|
+
|
|
134
|
+
def __rsub__(self, other):
|
|
135
|
+
return Expr(other, Term(-1, *self.vars)).__add__(Term(-self.coeff + 1))
|
|
136
|
+
|
|
137
|
+
def __mul__(self, scalar):
|
|
138
|
+
if not isinstance(scalar, int):
|
|
139
|
+
return NotImplemented
|
|
140
|
+
return Term(self.coeff * scalar, *self.vars)
|
|
141
|
+
|
|
142
|
+
def __rmul__(self, scalar):
|
|
143
|
+
return self.__mul__(scalar)
|
|
144
|
+
|
|
145
|
+
def __lt__(self, other):
|
|
146
|
+
if len(self.vars) != len(other.vars):
|
|
147
|
+
return len(self.vars) < len(other.vars)
|
|
148
|
+
else:
|
|
149
|
+
return self.vars < other.vars
|
|
150
|
+
|
|
151
|
+
def simplify_as_binary(self):
|
|
152
|
+
if len(self.vars) <= 1:
|
|
153
|
+
return self
|
|
154
|
+
else:
|
|
155
|
+
seen = set()
|
|
156
|
+
unique_vars = []
|
|
157
|
+
for v in sorted(self.vars, key=lambda v: v.index):
|
|
158
|
+
if v.index not in seen:
|
|
159
|
+
seen.add(v.index)
|
|
160
|
+
unique_vars.append(v)
|
|
161
|
+
return Term(self.coeff, *unique_vars)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class Expr:
|
|
165
|
+
def __init__(self, *args):
|
|
166
|
+
self.const = 0
|
|
167
|
+
self.terms = []
|
|
168
|
+
for arg in args:
|
|
169
|
+
if isinstance(arg, int):
|
|
170
|
+
self.const += arg
|
|
171
|
+
elif isinstance(arg, Term):
|
|
172
|
+
self.terms.append(arg)
|
|
173
|
+
elif isinstance(arg, Var):
|
|
174
|
+
self.terms.append(Term(arg))
|
|
175
|
+
elif isinstance(arg, Expr):
|
|
176
|
+
self.const += arg.const
|
|
177
|
+
self.terms += arg.terms
|
|
178
|
+
else:
|
|
179
|
+
raise ValueError(
|
|
180
|
+
"Expr can only have int, Var, Term, or Expr arguments")
|
|
181
|
+
|
|
182
|
+
def __add__(self, other):
|
|
183
|
+
return Expr(self, other)
|
|
184
|
+
|
|
185
|
+
def __radd__(self, other):
|
|
186
|
+
return Expr(other, self)
|
|
187
|
+
|
|
188
|
+
def __str__(self):
|
|
189
|
+
first = True
|
|
190
|
+
string = ""
|
|
191
|
+
if self.const != 0:
|
|
192
|
+
string = str(self.const)
|
|
193
|
+
first = False
|
|
194
|
+
elif len(self.terms) == 0:
|
|
195
|
+
return "0"
|
|
196
|
+
for term in self.terms:
|
|
197
|
+
if first:
|
|
198
|
+
string += str(term)
|
|
199
|
+
first = False
|
|
200
|
+
else:
|
|
201
|
+
if (term.coeff < 0):
|
|
202
|
+
string += " " + str(term)
|
|
203
|
+
else:
|
|
204
|
+
string += " +" + str(term)
|
|
205
|
+
return string
|
|
206
|
+
|
|
207
|
+
def __sub__(self, other):
|
|
208
|
+
if isinstance(other, Expr):
|
|
209
|
+
negated = Expr()
|
|
210
|
+
negated.const = -other.const
|
|
211
|
+
negated.terms = [Term(-t.coeff, *t.vars) for t in other.terms]
|
|
212
|
+
return Expr(self, negated)
|
|
213
|
+
elif isinstance(other, Term):
|
|
214
|
+
return Expr(self, Term(-other.coeff, *other.vars))
|
|
215
|
+
elif isinstance(other, Var):
|
|
216
|
+
return Expr(self, Term(-1, other))
|
|
217
|
+
elif isinstance(other, int):
|
|
218
|
+
return Expr(self, -other)
|
|
219
|
+
else:
|
|
220
|
+
return NotImplemented
|
|
221
|
+
|
|
222
|
+
def __rsub__(self, other):
|
|
223
|
+
return Expr(other).__sub__(self)
|
|
224
|
+
|
|
225
|
+
def __mul__(self, other):
|
|
226
|
+
if isinstance(other, int):
|
|
227
|
+
return Expr(self.const * other,
|
|
228
|
+
*[Term(t.coeff * other, *t.vars) for t in self.terms])
|
|
229
|
+
|
|
230
|
+
elif isinstance(other, Expr):
|
|
231
|
+
result_terms = []
|
|
232
|
+
|
|
233
|
+
const_part = self.const * other.const
|
|
234
|
+
|
|
235
|
+
for t in other.terms:
|
|
236
|
+
result_terms.append(Term(t.coeff * self.const, *t.vars))
|
|
237
|
+
for t in self.terms:
|
|
238
|
+
result_terms.append(Term(t.coeff * other.const, *t.vars))
|
|
239
|
+
|
|
240
|
+
for t1 in self.terms:
|
|
241
|
+
for t2 in other.terms:
|
|
242
|
+
coeff = t1.coeff * t2.coeff
|
|
243
|
+
vars_combined = t1.vars + t2.vars
|
|
244
|
+
result_terms.append(Term(coeff, *vars_combined))
|
|
245
|
+
|
|
246
|
+
return Expr(const_part, *result_terms)
|
|
247
|
+
|
|
248
|
+
elif isinstance(other, Term):
|
|
249
|
+
return self * Expr(other)
|
|
250
|
+
|
|
251
|
+
elif isinstance(other, Var):
|
|
252
|
+
return self * Expr(other)
|
|
253
|
+
|
|
254
|
+
else:
|
|
255
|
+
return NotImplemented
|
|
256
|
+
|
|
257
|
+
def __rmul__(self, scalar):
|
|
258
|
+
return self.__mul__(scalar)
|
|
259
|
+
|
|
260
|
+
def __eq__(self, other):
|
|
261
|
+
return sqr(self - other)
|
|
262
|
+
|
|
263
|
+
def __repr__(self):
|
|
264
|
+
return str(self)
|
|
265
|
+
|
|
266
|
+
def simplify_as_binary(self):
|
|
267
|
+
simplified_terms = sorted(
|
|
268
|
+
[term.simplify_as_binary() for term in self.terms if term.coeff != 0])
|
|
269
|
+
new_terms = []
|
|
270
|
+
for i in range(len(simplified_terms)):
|
|
271
|
+
if len(simplified_terms[i].vars) == 0:
|
|
272
|
+
self.const += simplified_terms[i].coeff
|
|
273
|
+
elif len(new_terms) == 0 or new_terms[-1].vars != simplified_terms[i].vars:
|
|
274
|
+
new_terms.append(simplified_terms[i])
|
|
275
|
+
else:
|
|
276
|
+
new_terms[-1].coeff += simplified_terms[i].coeff
|
|
277
|
+
self.terms = new_terms
|
|
278
|
+
return self
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def sqr(arg):
|
|
282
|
+
if isinstance(arg, Vector):
|
|
283
|
+
return Vector([sqr(item) for item in arg])
|
|
284
|
+
else:
|
|
285
|
+
return arg * arg
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def is_nested_list(obj):
|
|
289
|
+
return (
|
|
290
|
+
isinstance(obj, Vector) and
|
|
291
|
+
any(isinstance(item, Vector) for item in obj)
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def total_sum(arg):
|
|
296
|
+
if not is_nested_list(arg):
|
|
297
|
+
raise ValueError("Argument must be a nested list")
|
|
298
|
+
return total_sum_impl(arg)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def total_sum_impl(arg):
|
|
302
|
+
result = 0
|
|
303
|
+
for item in arg:
|
|
304
|
+
if isinstance(item, (int, Var, Term, Expr)):
|
|
305
|
+
result += item
|
|
306
|
+
else:
|
|
307
|
+
result += total_sum_impl(item)
|
|
308
|
+
return result
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def vector_sum(arg):
|
|
312
|
+
if not is_nested_list(arg):
|
|
313
|
+
raise ValueError("Argument must be a nested list")
|
|
314
|
+
return vector_sum_impl(arg)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def vector_sum_impl(arg):
|
|
318
|
+
if isinstance(arg[0], (int, Var, Term, Expr)):
|
|
319
|
+
return sum(arg)
|
|
320
|
+
else:
|
|
321
|
+
return Vector([vector_sum_impl(item) for item in arg])
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def transpose(arg):
|
|
325
|
+
if not is_nested_list(arg):
|
|
326
|
+
raise ValueError("Argument must be a nested list")
|
|
327
|
+
return Vector([
|
|
328
|
+
Vector([arg[i][j] for i in range(len(arg))])
|
|
329
|
+
for j in range(len(arg[0]))
|
|
330
|
+
])
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
class Vector:
|
|
334
|
+
def __init__(self, data):
|
|
335
|
+
self.data = [
|
|
336
|
+
Vector(x) if isinstance(
|
|
337
|
+
x, list) and not isinstance(x, Vector) else x
|
|
338
|
+
for x in data
|
|
339
|
+
] if isinstance(data, list) else data
|
|
340
|
+
|
|
341
|
+
def __getitem__(self, idx):
|
|
342
|
+
return self.data[idx]
|
|
343
|
+
|
|
344
|
+
def __len__(self):
|
|
345
|
+
return len(self.data) if isinstance(self.data, list) else 1
|
|
346
|
+
|
|
347
|
+
def __iter__(self):
|
|
348
|
+
if isinstance(self.data, list):
|
|
349
|
+
return iter(self.data)
|
|
350
|
+
else:
|
|
351
|
+
return iter([self.data])
|
|
352
|
+
|
|
353
|
+
def __eq__(self, other):
|
|
354
|
+
|
|
355
|
+
if isinstance(other, int):
|
|
356
|
+
return [x == other for x in self.data]
|
|
357
|
+
else:
|
|
358
|
+
raise ValueError("Comparison with non-integer is not supported")
|
|
359
|
+
|
|
360
|
+
def __repr__(self):
|
|
361
|
+
return f"Vector({self.data})"
|