変数配列と配列関数¶
Hi-QUBOは変数配列と配列演算をサポートしています.
変数配列の定義
2値変数の配列は qbpp::var() 関数を使って作成できます.
qbpp::var("name", size)は,指定したnameを持つsize個の変数の配列を返します.
以下のプログラムは,名前 x を持つ5個の変数の配列を定義します.
x を std::cout で出力すると,5つの変数 x[0],x[1],x[2],x[3],x[4] が含まれていることを確認できます.
次に,qbpp::toExpr(0) を型推論とともに使用して,初期値が 0 の qbpp::Expr オブジェクト f を作成します.
i = 0 から 4 までのforループで,各変数 x[i] が複合演算子 += を使って f に加算されます.
最後に,f が簡約化され,std::cout で出力されます.
#include <qbpp/qbpp.hpp>
int main() {
auto x = qbpp::var("x", 5);
std::cout << x << std::endl;
auto f = qbpp::toExpr(0);
for (int i = 0; i < 5; ++i) {
f += x[i];
}
std::cout << "f = " << f.simplify_as_binary() << std::endl;
}
このプログラムの出力は以下の通りです.
{x[0],x[1],x[2],x[3],x[4]}
f = x[0] +x[1] +x[2] +x[3] +x[4]
注意
qbpp::var(name, size)はqbpp::Var型のsize個の要素を含む1次元の変数の配列を返します. 厳密な型はqbpp::Array<1, qbpp::Var>で,1は次元数,qbpp::Varは要素の型を表します.autoを使えばコンパイラがこの型を推論してくれるので,明示的に書く必要があるのは,配列を非staticなクラスのメンバ変数として保持する場合だけです(C++ では非 static メンバにautoが使えないため). 配列の型は,要素に対する要素ごとの演算をサポートするオーバーロードされた演算子を提供します.
注意 — インデックスの範囲 C++ では
x[i]は範囲チェックを行いません(std::vector::operator[]と同じ規約).範囲外や負のインデックスは未定義動作です.インデックスは[0, size)の範囲に収めてください.Python では範囲外インデックスに対してx[i]がIndexErrorを送出します. 配列の要素ごとの演算は同一の shape を要求します.異なる shape の配列を組み合わせるのはエラーです(C++ では致命的エラー,Python では例外送出).
sum 関数
配列ユーティリティ関数 qbpp::sum() を使用すると,2値変数の配列の合計を取得できます.
以下のプログラムは qbpp::sum() を使用して,配列 x のすべての変数の合計を計算します.
#include <qbpp/qbpp.hpp>
int main() {
auto x = qbpp::var("x", 5);
std::cout << x << std::endl;
auto f = qbpp::sum(x);
std::cout << "f = " << f.simplify_as_binary() << std::endl;
}
このプログラムの出力は,前のプログラムとまったく同じです.
One-hot制約のQUBO
2値変数の配列が one-hot であるとは,ちょうど1つの要素が1に等しい,すなわち要素の合計が1に等しいことを意味します. \(X = (x_0, x_1, \ldots, x_{n-1})\) を \(n\) 個の2値変数の配列とします. 以下の式 \(f(X)\) は,\(X\) がone-hotである場合にのみ最小値0をとります.
以下のプログラムは式 \(f\) を作成し,すべての最適解を見つけます.
#include <qbpp/qbpp.hpp>
#include <qbpp/exhaustive_solver.hpp>
int main() {
auto x = qbpp::var("x", 5);
auto f = qbpp::sqr(qbpp::sum(x) - 1);
std::cout << "f = " << f.simplify_as_binary() << std::endl;
auto solver = qbpp::ExhaustiveSolver(f);
auto sol = solver.search({{"best_energy_sols", 0}});
for (size_t k = 0; k < sol.size(); ++k) {
std::cout << "(" << k << ") " << sol.sols[k] << std::endl;
}
}
関数 qbpp::sum() は配列内のすべての変数の合計を計算します.
関数 qbpp::sqr() は引数の2乗を計算します.
Exhaustive Solverはエネルギー値0のすべての最適解を見つけ,std::cout で以下のように出力されます.
f = 1 -x[0] -x[1] -x[2] -x[3] -x[4] +2*x[0]*x[1] +2*x[0]*x[2] +2*x[0]*x[3] +2*x[0]*x[4] +2*x[1]*x[2] +2*x[1]*x[3] +2*x[1]*x[4] +2*x[2]*x[3] +2*x[2]*x[4] +2*x[3]*x[4]
(0) 0:{{x[0],1},{x[1],0},{x[2],0},{x[3],0},{x[4],0}}
(1) 0:{{x[0],0},{x[1],1},{x[2],0},{x[3],0},{x[4],0}}
(2) 0:{{x[0],0},{x[1],0},{x[2],1},{x[3],0},{x[4],0}}
(3) 0:{{x[0],0},{x[1],0},{x[2],0},{x[3],1},{x[4],0}}
(4) 0:{{x[0],0},{x[1],0},{x[2],0},{x[3],0},{x[4],1}}
5つの最適解がすべて表示されます.
PyQBPPは変数の配列と配列演算をサポートしています.
変数配列の定義
バイナリ変数の配列は var() 関数を使って作成できます.
var("name", shape=size)は,指定されたnameを持つsize個の変数からなる配列を返します.
以下のプログラムは,x という名前の5個の変数からなる配列を定義します.
x を表示すると,5つの変数 x[0],x[1],x[2],x[3],x[4] が含まれていることが確認できます.
次に,f = 0 から始め,i = 0 から 4 までの for ループで,各変数 x[i] を複合演算子 += で f に加えます.
最後に,f を簡約化して表示します.
import pyqbpp as qbpp
x = qbpp.var("x", shape=5)
print(x)
f = 0
for i in range(5):
f += x[i]
print("f =", f.simplify_as_binary())
このプログラムの出力は以下の通りです.
{x[0],x[1],x[2],x[3],x[4]}
f = x[0] +x[1] +x[2] +x[3] +x[4]
注意 —
x[i]は範囲外インデックスに対してIndexErrorを送出します.配列の要素ごとの演算は同一の shape を要求し,異なる shape の配列を組み合わせると例外を送出します.
Sum 関数
ユーティリティ関数 sum() を使って,バイナリ変数配列の合計を取得できます.
以下のプログラムは sum() を使って配列 x のすべての変数の合計を計算します.
import pyqbpp as qbpp
x = qbpp.var("x", shape=5)
print(x)
f = qbpp.sum(x)
print("f =", f.simplify_as_binary())
このプログラムの出力は,前のプログラムとまったく同じです.
One-hot 制約の QUBO
バイナリ変数の配列が one-hot であるとは,ちょうど1つの要素が1に等しいこと,すなわち要素の合計が1に等しいことを意味します. \(X = (x_0, x_1, \ldots, x_{n-1})\) を \(n\) 個のバイナリ変数の配列とします. 以下の式 \(f(X)\) は,\(X\) が one-hot であるとき,かつそのときに限り最小値 0 をとります.
以下のプログラムは式 \(f\) を作成し,すべての最適解を求めます.
import pyqbpp as qbpp
x = qbpp.var("x", shape=5)
f = qbpp.sqr(qbpp.sum(x) - 1)
print("f =", f.simplify_as_binary())
solver = qbpp.ExhaustiveSolver(f)
result = solver.search(best_energy_sols=0)
for i, sol in enumerate(result.sols):
print(f"({i}) {sol}")
関数 sum() は配列内のすべての変数の合計を計算します.
関数 sqr() は引数の二乗を計算します.
Exhaustive Solver は,エネルギー値 0 のすべての最適解を以下のように求めます.
f = 1 -x[0] -x[1] -x[2] -x[3] -x[4] +2*x[0]*x[1] +2*x[0]*x[2] +2*x[0]*x[3] +2*x[0]*x[4] +2*x[1]*x[2] +2*x[1]*x[3] +2*x[1]*x[4] +2*x[2]*x[3] +2*x[2]*x[4] +2*x[3]*x[4]
(0) Sol(energy=0, {x[0]: 0, x[1]: 0, x[2]: 0, x[3]: 0, x[4]: 1})
(1) Sol(energy=0, {x[0]: 0, x[1]: 0, x[2]: 0, x[3]: 1, x[4]: 0})
(2) Sol(energy=0, {x[0]: 0, x[1]: 0, x[2]: 1, x[3]: 0, x[4]: 0})
(3) Sol(energy=0, {x[0]: 0, x[1]: 1, x[2]: 0, x[3]: 0, x[4]: 0})
(4) Sol(energy=0, {x[0]: 1, x[1]: 0, x[2]: 0, x[3]: 0, x[4]: 0})
5つの最適解がすべて表示されます.