ベクトル変数とベクトル関数

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 を用いて出力します。

vector-variables-and-functions-program1.cpp
#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 内のすべての変数の和を計算しています。

vector-variables-and-functions-program2.cpp
#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})\) を考えます。
次の式

\[ f(X) = \left( 1-\sum_{i=0}^{d-1}x_i \right)^2 \]

は、\(X\) が one-hot である場合に限り、最小値 \(0\) を取ります。 次のプログラムでは、式 \(f\) を作成し、すべての最適解を求めます。

vector-variables-and-functions-program3.cpp
#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 を簡約化して出力します。

vector-variables-and-functions-program1.py
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 内のすべての変数の和を計算しています。

vector-variables-and-functions-program2.py
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})\) を考えます。
次の式

\[ f(X) = \left( 1-\sum_{i=0}^{d-1}x_i \right)^2 \]

は、\(X\) が one-hot である場合に限り、最小値 \(0\) を取ります。 次のプログラムでは、式 \(f\) を作成し、すべての最適解を求めます。

vector-variables-and-functions-program3.py
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 個すべての最適解が表示されています。