ベクトル変数とベクトル関数¶
QUBO++ は、変数ベクトルおよびベクトル演算をサポートしています。
ベクトル変数について
バイナリ変数のベクトルは、qbpp::var() 関数を用いて作成できます。
qbpp::var("name", size) は、指定した名前を持つ size 個の変数からなるベクトルを返します。
次のプログラムでは、名前 x を持つ 5 個の変数のベクトルを定義しています。
std::cout を用いて x を出力することで、x[0], x[1], x[2], x[3], x[4] の 5 つの変数が含まれていることを確認できます。
続いて、型推論を用いて qbpp::expr() 関数を呼び出し、初期値が 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が使えないため)。 配列の型は、要素に対する要素ごとの演算をサポートするオーバーロードされた演算子を提供します。
Sum関数
ベクトル用ユーティリティ関数 qbpp::sum() を使用すると、バイナリ変数ベクトルの総和を求めることができます。
次のプログラムでは、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;
}
このプログラムの出力は、前のプログラムとまったく同じです。
QUBOのone-hot制約
バイナリ変数のベクトルが one-hot であるとは、ちょうど 1 つの要素のみが 1 であり、他はすべて 0 であることを意味します。すなわち、その要素の総和が 1 であることを意味します。
\(d\) 個のバイナリ変数からなるベクトル \(X=(x_0,\cdots,x_{d-1})\) を考えます。
次の式
は、\(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", 1}});
std::cout << sol << std::endl;
}
qbpp::sum() 関数は、ベクトル内のすべての変数の総和を計算します。
qbpp::sqr() 関数は、引数の二乗を計算します。
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],0},{x[1],0},{x[2],0},{x[3],0},{x[4],1}}
(1) 0:{{x[0],0},{x[1],0},{x[2],0},{x[3],1},{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],1},{x[2],0},{x[3],0},{x[4],0}}
(4) 0:{{x[0],1},{x[1],0},{x[2],0},{x[3],0},{x[4],0}}
5 個すべての最適解が表示されています。
PyQBPP は、変数ベクトルおよびベクトル演算をサポートしています。
ベクトル変数について
バイナリ変数のベクトルは、var() 関数を用いて作成できます。
var("name", size) は、指定した名前を持つ size 個の変数からなるVectorを返します。
次のプログラムでは、名前 x を持つ 5 個の変数のベクトルを定義しています。
x を出力すると、x[0], x[1], x[2], x[3], x[4] の 5 つの変数が含まれていることを確認できます。
続いて、expr() 関数を呼び出し、初期値が 0 の Expr オブジェクト f を作成します。
i = 0 から 4 までの for ループ内で、各変数 x[i] を複合代入演算子 += を使って f に加算します。
最後に、f を簡約化して出力します。
import pyqbpp as qbpp
x = qbpp.var("x", shape=5)
print(x)
f = qbpp.expr()
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]
NOTE
var(name, size) は、型 Var の要素を size 個含む Vector オブジェクトを返します。
Vector クラスは、要素に対するベクトル演算をサポートするために、演算子がオーバーロードされています。
WARNING: Vector と Python の list の違い
変数配列はPythonのリスト内包表記でも作成できます:
x = [qbpp.var(f"x[{i}]") for i in range(5)]
これは同じ名前の変数を作成しますが、結果は Vector ではなくPythonの list になります。 重要な違いは、Vector は要素ごとの演算(+, -, *, ~)をサポートしますが、list はサポートしないことです。 例えば、Vector での x + x は要素ごとの和を含む新しい Vector を返しますが、list では長さ10の連結リストを返します。 ベクトル演算が必要な場合は、常に qbpp.var("x", size) を使用してください。
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())
このプログラムの出力は、前のプログラムとまったく同じです。
QUBOのone-hot制約
バイナリ変数のベクトルが one-hot であるとは、ちょうど 1 つの要素のみが 1 であり、他はすべて 0 であることを意味します。すなわち、その要素の総和が 1 であることを意味します。
\(d\) 個のバイナリ変数からなるベクトル \(X=(x_0,\cdots,x_{d-1})\) を考えます。
次の式
は、\(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) 0:{{x[0],0},{x[1],0},{x[2],0},{x[3],0},{x[4],1}}
(1) 0:{{x[0],0},{x[1],0},{x[2],0},{x[3],1},{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],1},{x[2],0},{x[3],0},{x[4],0}}
(4) 0:{{x[0],1},{x[1],0},{x[2],0},{x[3],0},{x[4],0}}
5 個すべての最適解が表示されています。