QUBO++入門:分割問題を解く
分割問題
分割問題とは,整数のリストが与えられたとき,それを合計がなるべく等しくなるように2つの集合に分割する問題です.例えば,次のリストが与えられたとします.
この場合の最適解は,次のように2つのリスト
一般に,整数のリスト
すなわち,
分割問題をQUBO問題として表す
この式の値を最小化する
以下はQUBO++を用いた具体的なプログラムの例です.
#include "qbpp.hpp"
#include "qbpp_exhaustive_solver.hpp"
int main() {
std::vector<int> w = {15, 10, 24, 1, 14, 12, 8, 6};
auto x = qbpp::var("x", w.size());
auto f = qbpp::expr();
for (size_t i = 0; i < w.size(); i++) {
f += w[i] * (1 - x[i]) - w[i] * x[i];
}
f *= f;
f.simplify_as_binary();
std::cout << "f = " << f << std::endl;
qbpp::exhaustive_solver::ExhaustiveSolver solver(f);
auto sol = solver.search();
std::cout << sol << std::endl;
}
このプログラムでは,std::vector
を用いて整数のリストw
を生成しています.まず,2値変数の配列x
をqbpp::var()
関数で生成します.配列の大きさはw.size()
で指定します.
次に,空の式f
をqbpp::expr()
で生成し,forループ内でQUBO問題の式を構築します.複合代入演算を使用して式を更新し,simplify_as_binary()
メンバ関数を用いて整理します.この整理された式をコンソールに出力します.
最後に,式f
を引数としてExhaustive Solverのオブジェクトを作成し,search()
メンバ関数を呼び出して最適解をsol
に格納します.この最適解もコンソールに出力されます.
このプログラムをコンパイルして実行すると,次のような出力が得られます.
f = 8100 -4500*x[0] -3200*x[1] -6336*x[2] -356*x[3] -4256*x[4] -3744*x[5] -2624*x[6] -2016*x[7] +1200*x[0]*x[1] +2880*x[0]*x[2] +120*x[0]*x[3] +1680*x[0]*x[4] +1440*x[0]*x[5] +960*x[0]*x[6] +720*x[0]*x[7] +1920*x[1]*x[2] +80*x[1]*x[3] +1120*x[1]*x[4] +960*x[1]*x[5] +640*x[1]*x[6] +480*x[1]*x[7] +192*x[2]*x[3] +2688*x[2]*x[4] +2304*x[2]*x[5] +1536*x[2]*x[6] +1152*x[2]*x[7] +112*x[3]*x[4] +96*x[3]*x[5] +64*x[3]*x[6] +48*x[3]*x[7] +1344*x[4]*x[5] +896*x[4]*x[6] +672*x[4]*x[7] +768*x[5]*x[6] +576*x[5]*x[7] +384*x[6]*x[7]
0:{{x[0],1},{x[1],1},{x[2],0},{x[3],0},{x[4],0},{x[5],1},{x[6],1},{x[7],0}}
この結果から,変数x[0]
, x[1]
, x[5]
, x[6]
が1となり,分割問題の最適解が正しく得られていることがわかります.
ベクトルを用いたQUBO++の記述
QUBO++はベクトルを扱うことができ,それを利用することでforループを用いることなく,簡潔に記述できます.以下はその具体例です.
#include "qbpp.hpp"
#include "qbpp_exhaustive_solver.hpp"
int main() {
qbpp::Vector<int> w = {15, 10, 24, 1, 14, 12, 8, 6};
auto x = qbpp::var("x", w.size());
auto f = qbpp::sqr(qbpp::sum(w * (1 - x)) - qbpp::sum(w * x));
f.simplify_as_binary();
std::cout << "f = " << f << std::endl;
qbpp::exhaustive_solver::ExhaustiveSolver solver(f);
auto sol = solver.search();
std::cout << sol << std::endl;
}
このプログラムでは,QUBO++でベクトルを扱うためにqbpp::Vector
を用いて整数リストwを生成しています.qbpp::Vector
はstd::vector
を内部的に使用したQUBO++のクラスであり,std::vector
との互換性があります.
分割問題のQUBO式f
を作成する際には,次のような関数を利用しています:
-
qbpp::sqr
:与えられた式の2乗を計算します. -
qbpp::sum
:ベクトルの合計を計算します. -
w * x
:2つのベクトルの要素ごとの積を計算します.
これにより,forループを使うことなく,分割問題の2次式を簡潔に記述することができます.
まとめ
- 分割問題のQUBO表現:分割問題は,次の式でQUBO問題として表すことができます.
- 変数ベクトル(配列)の生成:変数のベクトルは,第2引数に大きさを指定して生成します.
auto x = qbpp::var("x", w.size());
-
式の生成:空の式は関数
qbpp::expr()
を用いて生成します.
auto f = qbpp::expr();
- 式の更新(複合代入演算の利用):次のように,複合代入演算を用いて式を更新できます.
f += w[i] * (1 - x[i]) - w[i] * x[i];
f *= f;
-
ベクトル(配列)の扱い:QUBO++では,ベクトルを扱う際に,
std::vector
と互換性のあるqbpp::Vector
クラスを使用します.
qbpp::Vector<int> w = {15, 10, 24, 1, 14, 12, 8, 6};
-
変数ベクトルの型指定:前述した変数のベクトル
x
もqbpp::Vector
クラスです.auto
を使わず,明示的に型を指定して次のように記述することも可能です.
qbpp::Vector<qbpp::Var> x = qbpp::var("x", w.size());
-
2つのベクトルのアダマール積:2つのベクトルのアダマール積(要素ごとの積)は,演算子
*
を用いて計算できます.演算結果はqbpp::Vector<qbpp::Expr>
,つまりqbpp::Expr
の配列となります.
w * x
-
ベクトルの和の計算:ベクトルの和は,
qbpp::sum()
関数を使用して計算できます.計算結果はqbpp::Expr
になります.
qbpp::sum(w * (1 - x))
qbpp::sum(w * x)
- 式の二乗の計算:式の二乗は,qbpp::sqr()関数を使用して計算できます.
qbpp::sqr(qbpp::sum(w * (1 - x)) - qbpp::sum(w * x));
QUBO++情報
- QUBO++ツールとABS2 QUBO Solverの詳細情報:QUBO++に関する公式サイトです.ツールの特徴や使用方法,ABS2 QUBO Solverとの連携について詳しく説明されています.
- 【研究成果】組合せ最適化問題を解くためのプログラミング用ツールQUBO++を無償公開します:広島大学からの公式リリース情報です.QUBO++の開発背景や研究成果について記載されています.
- 利用条件:QUBO++は非商用利用や評価研究目的に限り無償で利用可能です.商用利用や定常的な業務での使用を希望する場合は,ライセンス契約が必要となります.