QUBO++入門:行列操作(順列生成)
変数の行列
C++では,ネストしたstd::vector
,具体的にはstd::vector<std::vector<int>>
を使用して行列を表現することが一般的です.QUBO++も同様に,ネストしたqbpp::Vector
を用いて変数や式の行列を扱うことができます.
QUBO++では,このような行列に対して,和(sum)や積(product)を計算するための関数が用意されています.
-
qbpp::total_sum()
: 全要素の和を返します. -
qbpp::sum()
: 低次元(行)のベクトルをその和で置き換えたベクトルを返します. -
qbpp::total_product()
: 全要素の積を返します. -
qbpp::product()
: 低次元(行)のベクトルをその積で置き換えたベクトルを返します. -
qbpp::transpose()
: 行列を転置(行と列の入れ替え)します.
以下は,
この行列を使って,上述の関数の動作を説明します.
#include "qbpp.hpp"
int main() {
qbpp::Vector<qbpp::Vector<qbpp::Var>> x = qbpp::var("x", 2, 3);
qbpp::Expr total_sum = qbpp::total_sum(x).simplify_as_binary();
qbpp::Vector<qbpp::Expr> sum = qbpp::sum(x).simplify_as_binary();
std::cout << "total_sum = " << total_sum << std::endl;
std::cout << str(sum, "sum") << std::endl;
qbpp::Expr total_product = qbpp::total_product(x).simplify_as_binary();
qbpp::Vector<qbpp::Expr> product = qbpp::product(x).simplify_as_binary();
std::cout << "total_product = " << total_product << std::endl;
std::cout << str(product, "product") << std::endl;
qbpp::Vector<qbpp::Vector<qbpp::Expr>> t = qbpp::transpose(x).simplify_as_binary();
std::cout << str(t, "t") << std::endl;
qbpp::Vector<qbpp::Expr> sum_t = qbpp::sum(t).simplify_as_binary();
std::cout << str(sum_t, "sum_t") << std::endl;
}
理解を深めるため,コード中では型を明示して'qbpp::Vector<qbpp::Vectorqbpp::Var>'と記述していますが,autoを使用することで簡略化することも可能です.
-
変数の生成:
qbpp::var
を用いて,大きさ の行列を表す変数2 \times 3 x
を生成します.引数として行列のサイズを指定します. -
全要素の和と行ごとの和の計算:
qbpp::total_sum
を使って全変数の和を,qbpp::sum
を使って行ごとの和を計算し,それぞれを出力します. -
全要素の積と行ごとの積の計算:
qbpp::total_product
を使って全変数の積を,qbpp::product
を使って行ごとの積を計算し,それぞれを出力します. -
行列の転置:
行列x
を転置して新たな行列t
を作成します.さらに転置後の行列t
に対して行ごとの和を計算し出力します.これにより,元の行列x
の列ごとの和が求まります.
以下は,このプログラムを実行した場合の出力です.
total_sum = x[0][0] +x[0][1] +x[0][2] +x[1][0] +x[1][1] +x[1][2]
{sum[0],x[0][0] +x[0][1] +x[0][2]},{sum[1],x[1][0] +x[1][1] +x[1][2]}
total_product = x[0][0]*x[0][1]*x[0][2]*x[1][0]*x[1][1]*x[1][2]
{product[0],x[0][0]*x[0][1]*x[0][2]},{product[1],x[1][0]*x[1][1]*x[1][2]}
{t[0][0],x[0][0]},{t[0][1],x[1][0]},{t[1][0],x[0][1]},{t[1][1],x[1][1]},{t[2][0],x[0][2]},{t[2][1],x[1][2]}
{sum_t[0],x[0][0] +x[1][0]},{sum_t[1],x[0][1] +x[1][1]},{sum_t[2],x[0][2] +x[1][2]}
one-hotベクトルによる順列表現
組合せ最適化問題には,あらゆる順列の中から最適なものを選択するタイプの問題が多く存在します.そのような組合せ最適化問題をQUBO問題として扱うための準備として,QUBO++を用いて順列を生成するQUBO式を記述します.
QUBOでは,整数
以下の
この式は,QUBO++の等価演算子==
を用いて次のように表すことができます.
順列生成QUBO++プログラム
x
を生成し、各行および各列がone-hotベクトルのとき、エネルギー値が最小となるQUBO式f
を作成します。以下は、
#include "qbpp.hpp"
#include "qbpp_exhaustive_solver.hpp"
int main() {
auto x = qbpp::var("x", 4, 4);
auto f = qbpp::sum(qbpp::sum(x) == 1) +
qbpp::sum(qbpp::sum(qbpp::transpose(x)) == 1);
f.simplify_as_binary();
std::cout << "f = " << f << std::endl;
auto solver = qbpp::exhaustive_solver::ExhaustiveSolver(f);
auto sol = solver.search();
auto result = sol.get(x);
auto onehot = qbpp::onehot_to_int(result);
std::cout << sol << std::endl;
std::cout << str(result, "result") << std::endl;
std::cout << str(onehot, "onehot") << std::endl;
}
式f
は1行で生成されています.
-
qbpp::sum(x)
は,行列x
の各行をその和で置き換えたベクトルを返します. -
qbpp::sum(x) == 1
は,そのベクトルの各要素が1である場合に最小値0を取るQUBO式を返します. -
qbpp::sum(qbpp::sum(x) == 1)
は,そのQUBO式の和を返します. -
qbpp::sum(qbpp::sum(qbpp::transpose(x)) == 1)
は,行列x
の転置行列に対して同じ処理を行います.
以上により,式f
は各行および各列がone-hotベクトルとなる場合にエネルギーが最小値0を取るQUBO式となります.
Exhaustive Solverを用いて最適解sol
を求めます.そして,メンバ関数get()
を使用して,sol
からx
の値を取得し,それをresult
とします.x
が行列であるため,result
も同様に行列形式になります.さらに,関数qbpp::onehot_to_int()
を用いて,result
の各行をone-hot
ベクトルから整数に変換し,onehot
として格納します.最後に,std::cout
を用いてsol
,result
,およびonehot
を出力します.
このプログラムを実行すると,下の出力が得られます.
f = 8 -2*x[0][0] -2*x[0][1] -2*x[0][2] -2*x[0][3] -2*x[1][0] -2*x[1][1] -2*x[1][2] -2*x[1][3] -2*x[2][0] -2*x[2][1] -2*x[2][2] -2*x[2][3] -2*x[3][0] -2*x[3][1] -2*x[3][2] -2*x[3][3] +2*x[0][0]*x[0][1] +2*x[0][0]*x[0][2] +2*x[0][0]*x[0][3] +2*x[0][0]*x[1][0] +2*x[0][0]*x[2][0] +2*x[0][0]*x[3][0] +2*x[0][1]*x[0][2] +2*x[0][1]*x[0][3] +2*x[0][1]*x[1][1] +2*x[0][1]*x[2][1] +2*x[0][1]*x[3][1] +2*x[0][2]*x[0][3] +2*x[0][2]*x[1][2] +2*x[0][2]*x[2][2] +2*x[0][2]*x[3][2] +2*x[0][3]*x[1][3] +2*x[0][3]*x[2][3] +2*x[0][3]*x[3][3] +2*x[1][0]*x[1][1] +2*x[1][0]*x[1][2] +2*x[1][0]*x[1][3] +2*x[1][0]*x[2][0] +2*x[1][0]*x[3][0] +2*x[1][1]*x[1][2] +2*x[1][1]*x[1][3] +2*x[1][1]*x[2][1] +2*x[1][1]*x[3][1] +2*x[1][2]*x[1][3] +2*x[1][2]*x[2][2] +2*x[1][2]*x[3][2] +2*x[1][3]*x[2][3] +2*x[1][3]*x[3][3] +2*x[2][0]*x[2][1] +2*x[2][0]*x[2][2] +2*x[2][0]*x[2][3] +2*x[2][0]*x[3][0] +2*x[2][1]*x[2][2] +2*x[2][1]*x[2][3] +2*x[2][1]*x[3][1] +2*x[2][2]*x[2][3] +2*x[2][2]*x[3][2] +2*x[2][3]*x[3][3] +2*x[3][0]*x[3][1] +2*x[3][0]*x[3][2] +2*x[3][0]*x[3][3] +2*x[3][1]*x[3][2] +2*x[3][1]*x[3][3] +2*x[3][2]*x[3][3]
0:{{x[0][0],0},{x[0][1],0},{x[0][2],1},{x[0][3],0},{x[1][0],0},{x[1][1],1},{x[1][2],0},{x[1][3],0},{x[2][0],0},{x[2][1],0},{x[2][2],0},{x[2][3],1},{x[3][0],1},{x[3][1],0},{x[3][2],0},{x[3][3],0}}
{onehot[0][0],0},{onehot[0][1],0},{onehot[0][2],1},{onehot[0][3],0},{onehot[1][0],0},{onehot[1][1],1},{onehot[1][2],0},{onehot[1][3],0},{onehot[2][0],0},{onehot[2][1],0},{onehot[2][2],0},{onehot[2][3],1},{onehot[3][0],1},{onehot[3][1],0},{onehot[3][2],0},{onehot[3][3],0}
{perm[0],2},{perm[1],1},{perm[2],3},{perm[3],0}
この出力では,順列[2, 1, 3, 0]が得られています.
全ての順列を求める
Exhaustive Solverは,メンバ関数search_optimal_solutions()
を使用することで,全ての最適解を列挙することができます.以下は,この機能を利用して全ての順列を表示するQUBO++プログラムです.
#include "qbpp.hpp"
#include "qbpp_exhaustive_solver.hpp"
int main() {
auto x = qbpp::var("x", 4, 4);
auto f = qbpp::sum(qbpp::sum(x) == 1) +
qbpp::sum(qbpp::sum(qbpp::transpose(x)) == 1);
f.simplify_as_binary();
std::cout << "f = " << f << std::endl;
auto solver = qbpp::exhaustive_solver::ExhaustiveSolver(f);
auto sol = solver.search();
auto onehot = sol.get(x);
auto perm = qbpp::onehot_to_int(onehot);
std::cout << sol << std::endl;
std::cout << str(onehot, "onehot") << std::endl;
std::cout << str(perm, "perm") << std::endl;
}
search_optimal_solutions()
を使用して,全ての最適解をoptimal_sols
に保存します.それをforループで取り出し,表示しています.
プログラムを実行すると,次のような出力が得られ,全24通りの順列が表示されます.
0:{perm[0],3},{perm[1],2},{perm[2],1},{perm[3],0}
1:{perm[0],3},{perm[1],2},{perm[2],0},{perm[3],1}
2:{perm[0],3},{perm[1],1},{perm[2],2},{perm[3],0}
3:{perm[0],3},{perm[1],1},{perm[2],0},{perm[3],2}
4:{perm[0],3},{perm[1],0},{perm[2],2},{perm[3],1}
5:{perm[0],3},{perm[1],0},{perm[2],1},{perm[3],2}
6:{perm[0],2},{perm[1],3},{perm[2],1},{perm[3],0}
7:{perm[0],2},{perm[1],3},{perm[2],0},{perm[3],1}
8:{perm[0],2},{perm[1],1},{perm[2],3},{perm[3],0}
9:{perm[0],2},{perm[1],1},{perm[2],0},{perm[3],3}
10:{perm[0],2},{perm[1],0},{perm[2],3},{perm[3],1}
11:{perm[0],2},{perm[1],0},{perm[2],1},{perm[3],3}
12:{perm[0],1},{perm[1],3},{perm[2],2},{perm[3],0}
13:{perm[0],1},{perm[1],3},{perm[2],0},{perm[3],2}
14:{perm[0],1},{perm[1],2},{perm[2],3},{perm[3],0}
15:{perm[0],1},{perm[1],2},{perm[2],0},{perm[3],3}
16:{perm[0],1},{perm[1],0},{perm[2],3},{perm[3],2}
17:{perm[0],1},{perm[1],0},{perm[2],2},{perm[3],3}
18:{perm[0],0},{perm[1],3},{perm[2],2},{perm[3],1}
19:{perm[0],0},{perm[1],3},{perm[2],1},{perm[3],2}
20:{perm[0],0},{perm[1],2},{perm[2],3},{perm[3],1}
21:{perm[0],0},{perm[1],2},{perm[2],1},{perm[3],3}
22:{perm[0],0},{perm[1],1},{perm[2],3},{perm[3],2}
23:{perm[0],0},{perm[1],1},{perm[2],2},{perm[3],3}
まとめ
-
順列の表現方法:
の二値行列を用いることでn \times n の順列を表現できます.0, 1, \ldots, n-1 -
順列を求めるQUBO式:
の二値変数行列において,各行および各列が one-hot ベクトルとなるように制約を設定することで,最適解が順列を表すQUBO 式を構築できます.n \times n
QUBO++情報
- QUBO++ツールとABS2 QUBO Solverの詳細情報:QUBO++に関する公式サイトです.ツールの特徴や使用方法,ABS2 QUBO Solverとの連携について詳しく説明されています.
- 【研究成果】組合せ最適化問題を解くためのプログラミング用ツールQUBO++を無償公開します:広島大学からの公式リリース情報です.QUBO++の開発背景や研究成果について記載されています.
- 利用条件:QUBO++は非商用利用や評価研究目的に限り無償で利用可能です.商用利用や定常的な業務での使用を希望する場合は,ライセンス契約が必要となります.
Discussion