Slice and Concat
PyQBPP supports Python-style slicing and a concat() function for manipulating arrays. This page demonstrates these operations through domain wall encoding and the Dual-Matrix Domain Wall method.
Slicing
PyQBPP arrays support standard Python slice notation. Slicing returns a new Array:
import pyqbpp as qbpp
x = qbpp.var("x", 8)
print(x[:3]) # first 3: [x[0], x[1], x[2]]
print(x[-3:]) # last 3: [x[5], x[6], x[7]]
print(x[2:5]) # range: [x[2], x[3], x[4]]
For multi-dimensional arrays, use tuple indexing (similar to NumPy):
x = qbpp.var("x", 3, 5)
print(x[:, :3]) # first 3 columns of each row
print(x[:, -2:]) # last 2 columns of each row
print(x[1:3, 1:4]) # rows 1-2, columns 1-3
x = qbpp.var("x", 2, 3, 4)
print(x[:, :, :2]) # first 2 elements along the 3rd dimension
Concat
The concat() function joins arrays or prepends/appends scalars:
import pyqbpp as qbpp
x = qbpp.var("x", 4)
# 1D: scalar + array, array + scalar
y = qbpp.concat(1, qbpp.concat(x, 0))
# y = [1, x[0], x[1], x[2], x[3], 0]
# 2D with dim parameter
z = qbpp.var("z", 3, 4)
zg0 = qbpp.concat(1, qbpp.concat(z, 0, 0), 0) # dim=0: guard rows -> 5 x 4
zg1 = qbpp.concat(1, qbpp.concat(z, 0, 1), 1) # dim=1: guard cols -> 3 x 6
Pythonic alternative using * (unpack operator)
Python’s unpack operator * can replace concat() by unpacking an Array inside an Array() constructor:
# 1D: equivalent to concat(1, concat(x, 0))
y = qbpp.Array([1, *x, 0])
# 2D dim=0: equivalent to concat(1, concat(z, 0, 0), 0)
ones = qbpp.Array([1] * 4)
zeros = qbpp.Array([0] * 4)
zg0 = qbpp.Array([ones, *z, zeros])
# 2D dim=1: equivalent to concat(1, concat(z, 0, 1), 1)
zg1 = qbpp.Array([qbpp.Array([1, *row, 0]) for row in z])
For the outermost dimension, the unpack style is often clearer. For inner dimensions, concat(scalar, x, dim) avoids nested list comprehensions.
Domain Wall Encoding
A domain wall is a binary pattern $1\cdots 1\, 0\cdots 0$. For $n$ variables, there are $n+1$ such patterns, representing integers $0$ through $n$.
import pyqbpp as qbpp
n = 8
x = qbpp.var("x", n)
# y = (1, x[0], ..., x[n-1], 0)
y = qbpp.concat(1, qbpp.concat(x, 0))
# Adjacent difference
diff = y[:n+1] - y[-(n+1):]
# Penalty: minimum 1 iff domain wall
f = qbpp.sum(qbpp.sqr(diff))
f.simplify_as_binary()
print("f =", f)
solver = qbpp.ExhaustiveSolver(f)
sol = solver.search({"best_energy_sols": 0})
print("energy =", sol.energy)
print("solutions =", len(sol.all_solutions()))
for s in sol.all_solutions():
bits = "".join(str(s(x[i])) for i in range(n))
print(f" {bits} (sum = {s(qbpp.sum(x))})")
Output
f = 1 +2*x[1] +2*x[2] +2*x[3] +2*x[4] +2*x[5] +2*x[6] +2*x[7] -2*x[0]*x[1] -2*x[1]*x[2] -2*x[2]*x[3] -2*x[3]*x[4] -2*x[4]*x[5] -2*x[5]*x[6] -2*x[6]*x[7]
energy = 1
solutions = 9
00000000 (sum = 0)
10000000 (sum = 1)
11000000 (sum = 2)
11100000 (sum = 3)
11110000 (sum = 4)
11111000 (sum = 5)
11111100 (sum = 6)
11111110 (sum = 7)
11111111 (sum = 8)
Dual-Matrix Domain Wall
The Dual-Matrix Domain Wall method constructs an $n \times n$ permutation matrix using two binary matrices: x of size $(n{-}1) \times n$ and y of size $n \times (n{-}1)$. Adding guard bits and taking adjacent differences produces two $n \times n$ one-hot matrices. Matching them yields a permutation matrix — without explicit loops. For details, see: https://arxiv.org/abs/2308.01024
import pyqbpp as qbpp
n = 6
x = qbpp.var("x", n - 1, n) # (n-1) x n
y = qbpp.var("y", n, n - 1) # n x (n-1)
# x: guard rows (dim=0), diff -> n x n (column one-hot)
xg = qbpp.concat(1, qbpp.concat(x, 0, 0), 0)
x_oh = xg[:n] - xg[-n:]
x_dw = qbpp.sum(qbpp.sqr(x_oh))
# y: guard cols (dim=1), diff -> n x n (row one-hot)
yg = qbpp.concat(1, qbpp.concat(y, 0, 1), 1)
y_oh = yg[:, :n] - yg[:, -n:]
y_dw = qbpp.sum(qbpp.sqr(y_oh))
# Match: x_oh == y_oh
match = qbpp.sum(x_oh - y_oh == 0)
f = x_dw + y_dw + match
f.simplify_as_binary()
solver = qbpp.EasySolver(f)
sol = solver.search({"target_energy": 2 * n})
print("energy =", sol.energy)
print("permutation:")
for i in range(n):
print(" ", "".join(str(sol(x_oh[i][j])) for j in range(n)))
Key operations
x[:n]/x[-n:]: Python slice notation replaces C++head()/tail().x[:, :n]/x[:, -n:]: Tuple indexing for slicing along inner dimensions.concat(1, x, 0)(dim=0): Adds a guard row of 1s at the top.concat(1, x, 1)(dim=1): Prepends 1 to each row.
Comparison with C++ QUBO++
| C++ QUBO++ | PyQBPP |
|---|---|
qbpp::head(v, n) | v[:n] |
qbpp::tail(v, n) | v[-n:] |
qbpp::slice(v, from, to) | v[from:to] |
qbpp::head(v, n, 1) | v[:, :n] |
qbpp::tail(v, n, 1) | v[:, -n:] |
qbpp::concat(1, v) | qbpp.concat(1, v) |
qbpp::concat(1, v, 0) | qbpp.concat(1, v, 0) |
qbpp::concat(1, v, 1) | qbpp.concat(1, v, 1) |
Output
energy = 12
permutation:
000001
100000
000100
010000
000010
001000
スライスと連結
PyQBPPはPythonスタイルのスライスとconcat()関数による配列操作をサポートしています。 このページでは、ドメインウォール符号化とDual-Matrix Domain Wall法を通じてこれらの操作を紹介します。
スライス
PyQBPPの配列はPython標準のスライス記法をサポートしています。スライスは新しいArrayを返します:
import pyqbpp as qbpp
x = qbpp.var("x", 8)
print(x[:3]) # 先頭3つ: [x[0], x[1], x[2]]
print(x[-3:]) # 末尾3つ: [x[5], x[6], x[7]]
print(x[2:5]) # 範囲: [x[2], x[3], x[4]]
多次元配列にはタプルインデックス(NumPyスタイル)を使います:
x = qbpp.var("x", 3, 5)
print(x[:, :3]) # 各行の先頭3列
print(x[:, -2:]) # 各行の末尾2列
print(x[1:3, 1:4]) # 1-2行, 1-3列
x = qbpp.var("x", 2, 3, 4)
print(x[:, :, :2]) # 3次元目の先頭2要素
連結 (concat)
concat() 関数は配列の連結やスカラーの追加を行います:
import pyqbpp as qbpp
x = qbpp.var("x", 4)
# 1D: スカラー + 配列、配列 + スカラー
y = qbpp.concat(1, qbpp.concat(x, 0))
# y = [1, x[0], x[1], x[2], x[3], 0]
# 2D: dimパラメータ付き
z = qbpp.var("z", 3, 4)
zg0 = qbpp.concat(1, qbpp.concat(z, 0, 0), 0) # dim=0: ガード行 -> 5 x 4
zg1 = qbpp.concat(1, qbpp.concat(z, 0, 1), 1) # dim=1: ガードビット -> 3 x 6
*(アンパック演算子)によるPythonic な代替
Pythonのアンパック演算子 * を使えば、Array() コンストラクタ内で concat() を置き換えられます:
# 1D: concat(1, concat(x, 0)) と等価
y = qbpp.Array([1, *x, 0])
# 2D dim=0: concat(1, concat(z, 0, 0), 0) と等価
ones = qbpp.Array([1] * 4)
zeros = qbpp.Array([0] * 4)
zg0 = qbpp.Array([ones, *z, zeros])
# 2D dim=1: concat(1, concat(z, 0, 1), 1) と等価
zg1 = qbpp.Array([qbpp.Array([1, *row, 0]) for row in z])
最外次元ではアンパックの方が明快です。 内側の次元では concat(scalar, x, dim) の方がネストを避けられます。
ドメインウォール符号化
ドメインウォールとは $1\cdots 1\, 0\cdots 0$ のバイナリパターンです。 $n$ 個の変数に対して $n+1$ 個のパターンがあり、整数 $0$ から $n$ を表現できます。
import pyqbpp as qbpp
n = 8
x = qbpp.var("x", n)
# y = (1, x[0], ..., x[n-1], 0)
y = qbpp.concat(1, qbpp.concat(x, 0))
# 隣接差分
diff = y[:n+1] - y[-(n+1):]
# ペナルティ: ドメインウォールのとき最小値 1
f = qbpp.sum(qbpp.sqr(diff))
f.simplify_as_binary()
print("f =", f)
solver = qbpp.ExhaustiveSolver(f)
sol = solver.search({"best_energy_sols": 0})
print("energy =", sol.energy)
print("solutions =", len(sol.all_solutions()))
for s in sol.all_solutions():
bits = "".join(str(s(x[i])) for i in range(n))
print(f" {bits} (sum = {s(qbpp.sum(x))})")
出力
f = 1 +2*x[1] +2*x[2] +2*x[3] +2*x[4] +2*x[5] +2*x[6] +2*x[7] -2*x[0]*x[1] -2*x[1]*x[2] -2*x[2]*x[3] -2*x[3]*x[4] -2*x[4]*x[5] -2*x[5]*x[6] -2*x[6]*x[7]
energy = 1
solutions = 9
00000000 (sum = 0)
10000000 (sum = 1)
11000000 (sum = 2)
11100000 (sum = 3)
11110000 (sum = 4)
11111000 (sum = 5)
11111100 (sum = 6)
11111110 (sum = 7)
11111111 (sum = 8)
Dual-Matrix Domain Wall
Dual-Matrix Domain Wall 法は、2つのバイナリ行列 x($(n{-}1) \times n$)と y($n \times (n{-}1)$)を使って $n \times n$ の置換行列を構築します。 ガードビットを追加して隣接差分を取ると、それぞれ $n \times n$ のone-hot行列が得られます。 これらを一致させることで、ループなしで置換行列を表現できます。 詳細は https://arxiv.org/abs/2308.01024 を参照してください。
import pyqbpp as qbpp
n = 6
x = qbpp.var("x", n - 1, n) # (n-1) x n
y = qbpp.var("y", n, n - 1) # n x (n-1)
# x: ガード行追加 (dim=0)、差分 -> n x n(各列one-hot)
xg = qbpp.concat(1, qbpp.concat(x, 0, 0), 0)
x_oh = xg[:n] - xg[-n:]
x_dw = qbpp.sum(qbpp.sqr(x_oh))
# y: ガードビット追加 (dim=1)、差分 -> n x n(各行one-hot)
yg = qbpp.concat(1, qbpp.concat(y, 0, 1), 1)
y_oh = yg[:, :n] - yg[:, -n:]
y_dw = qbpp.sum(qbpp.sqr(y_oh))
# 一致制約: x_oh == y_oh
match = qbpp.sum(x_oh - y_oh == 0)
f = x_dw + y_dw + match
f.simplify_as_binary()
solver = qbpp.EasySolver(f)
sol = solver.search({"target_energy": 2 * n})
print("energy =", sol.energy)
print("permutation:")
for i in range(n):
print(" ", "".join(str(sol(x_oh[i][j])) for j in range(n)))
主要な操作
x[:n]/x[-n:]: Pythonスライスで C++ のhead()/tail()に相当。x[:, :n]/x[:, -n:]: タプルインデックスで内側の次元をスライス。concat(1, x, 0)(dim=0): 全1のガード行を上に追加。concat(1, x, 1)(dim=1): 各行の先頭に1を追加。
C++ QUBO++ との比較
| C++ QUBO++ | PyQBPP |
|---|---|
qbpp::head(v, n) | v[:n] |
qbpp::tail(v, n) | v[-n:] |
qbpp::slice(v, from, to) | v[from:to] |
qbpp::head(v, n, 1) | v[:, :n] |
qbpp::tail(v, n, 1) | v[:, -n:] |
qbpp::concat(1, v) | qbpp.concat(1, v) |
qbpp::concat(1, v, 0) | qbpp.concat(1, v, 0) |
qbpp::concat(1, v, 1) | qbpp.concat(1, v, 1) |
出力
energy = 12
permutation:
000001
100000
000100
010000
000010
001000