ABS3 Solverの使い方¶
ABS3 Solverを使用して式 f を解くには,以下の2つのステップで行います:
式
fに対してABS3 Solver(qbpp::ABS3Solver)オブジェクトを作成します.**
search()**メンバ関数を呼び出します.パラメータは初期化子リストとして渡します.得られた解が返されます.
ABS3 SolverによるLABS問題の求解
以下のHi-QUBOプログラムは,ABS3 Solverを使用して**Low Autocorrelation Binary Sequence (LABS)**問題を解きます:
#include <qbpp/qbpp.hpp>
#include <qbpp/abs3_solver.hpp>
int main() {
const size_t size = 100;
auto x = qbpp::var("x", size);
auto f = qbpp::toExpr(0);
for (size_t d = 1; d < size; ++d) {
auto temp = qbpp::toExpr(0);
for (size_t i = 0; i < size - d; ++i) {
temp += (2 * x[i] - 1) * (2 * x[i + d] - 1);
}
f += qbpp::sqr(temp);
}
f.simplify_as_binary();
auto solver = qbpp::ABS3Solver(f);
auto sol = solver.search({{"time_limit", 10.0}, {"enable_default_callback", 1}});
std::cout << sol.energy() << ": ";
for (auto val : sol(x)) {
std::cout << (val == 0 ? "-" : "+");
}
std::cout << std::endl;
}
このプログラムでは,まず式 f に対してABS3 Solverオブジェクト**solverを作成します.
次に,search()**メンバ関数にパラメータを初期化子リストとして渡して呼び出します.
time_limit オプションは最大探索時間を秒単位で指定し,enable_default_callback は探索中に新たに見つかった最良解のエネルギーとTTSを出力する組み込みコールバック関数を有効にします.
この関数は指定された制限時間内に見つかった最良解を返し,sol に格納されます.
プログラムは解のエネルギーと対応するバイナリ列を出力します."+"は1を,"-"は0を表します.
このプログラムは以下の出力を生成します:
TTS = 0.002s Energy = 1218
TTS = 0.002s Energy = 1170
TTS = 0.002s Energy = 994
TTS = 0.015s Energy = 958
TTS = 0.018s Energy = 922
TTS = 0.034s Energy = 874
TTS = 4.364s Energy = 834
834: -+--+---++-++-+---++-++--+++--+-+-+++++----+++-+-+---++-+--+-----+--+----++----+-+--++++++---+------
ABS3 Solverオブジェクト
ABS3 Solver(qbpp::ABS3Solver)オブジェクトは与えられた式に対して作成されます.
ソルバーオブジェクトが構築されると,式は内部データフォーマットに変換され,GPUメモリにロードされます.
オプションの第2引数 gpu でGPUの使用を制御します:
qbpp::ABS3Solver(expression): 利用可能なすべてのGPUを自動的に使用します.GPUが利用できない場合はCPUのみモードにフォールバックします.qbpp::ABS3Solver(expression, 0): CPUのみモードを強制します(GPUは使用されません).qbpp::ABS3Solver(expression, n):n個のGPUを使用します.
探索パラメータは search() に初期化子リストとして直接渡します.
上記の例では:
"time_limit", 10.0: 制限時間を10.0秒に設定します."enable_default_callback", 1: 新たに得られた解のエネルギーを出力する組み込みコールバック関数を有効にします.
ABS3パラメータ
パラメータは search() メソッドに初期化子リストとして直接渡します.
上記のプログラムでは,"time_limit", 10.0 で制限時間を10.0秒に設定し,"enable_default_callback", 1 で新たに得られた解のエネルギーを出力する組み込みコールバック関数を有効にしています.
基本オプション
キー |
値 |
説明 |
|---|---|---|
|
制限時間(秒) |
制限時間に達すると探索を終了します |
|
ターゲットエネルギー値 |
ターゲットエネルギーが達成されると探索を終了します |
詳細オプション
キー |
値 |
説明 |
|---|---|---|
|
"1" |
エネルギーとTTSを出力する組み込みコールバックを有効にします |
|
"0" または "1" |
GPUと並行して動作するCPUソルバーの有効/無効(デフォルト: "1") |
|
CPUスレッド数 |
CPUソルバーのスレッド数(デフォルト: 自動) |
|
GPU当たりのCUDAブロック数 |
ソルバーカーネルが起動するCUDAブロック数 |
|
CUDAブロック当たりのスレッド数 |
CUDAブロック当たりのスレッド数 |
|
解の数 |
最良エネルギーのtop-K解を返します |
|
最大数("0" = 無制限) |
見つかった最良エネルギーを持つすべての解を返します |
|
乱数シード("0" = 未指定) |
初期解や提案に使う乱数列を固定します(既定は非決定).完全な実行再現は直列構成(例: |
複数解の収集
ABS3 Solverは探索中に複数の解を収集できます. 2つのモードが利用可能です:
Top-K解 (topk_sols)
topk_sols パラメータはエネルギーの昇順にソートされたtop-K解を収集します.
auto result = solver.search({{"topk_sols", 10}}); // 最大10個の最良解を収集
最良エネルギー解 (best_energy_sols)
best_energy_sols パラメータは見つかった最良エネルギーを共有するすべての解を収集します.
より良いエネルギーが発見されると,プールがクリアされ,新しい最良エネルギーの解のみが保持されます.
auto result = solver.search({{"best_energy_sols", 0}}); // すべての最良エネルギー解を収集(無制限)
または,best_energy_sols を最大数付きで設定することもできます:
auto result = solver.search({{"best_energy_sols", 100}}); // 最大100個を収集
topk_sols と best_energy_sols は同じ内部プールを共有することに注意してください.
両方が指定された場合,最後に指定されたものが有効になります.
収集された解へのアクセス
search() メソッドは ABS3SolverSol オブジェクトを返し,収集された解へのアクセスを提供します:
auto result = solver.search(params);
std::cout << "Best energy: " << result.energy() << std::endl;
std::cout << "Number of solutions: " << result.size() << std::endl;
for (const auto& sol : result.sols) {
std::cout << "Energy = " << sol.energy() << " TTS = " << sol.tts() << "s" << std::endl;
}
ABS3SolverSol オブジェクトは以下をサポートします:
size()— 収集された解の数sols— 解ベクトルへのアクセスoperator[](i)— i番目の解へのアクセス範囲ベースforループによるイテレーション
カスタムコールバック
組み込みコールバック(enable_default_callback で有効化)は,新しい最良解が見つかるたびにエネルギーとTTSを出力するだけです.
より細かい制御が必要な場合は,ABS3Solver をサブクラス化して callback() 仮想メソッドをオーバーライドできます.
コールバックは以下のイベントのいずれかで呼び出されます:
イベント |
説明 |
|---|---|
|
|
|
新しい最良解が見つかるたびに呼び出されます |
|
設定可能な間隔で定期的に呼び出されます |
コールバック内では,以下のメソッドが利用可能です:
best_sol()— 現在の最良解へのconst qbpp::Sol&を返します..energy(),.tts(),.get(var)などが使用できます.event()— このコールバックをトリガーしたイベントを返しますhint(sol)— 探索中にソルバーにヒント解を提供します(Solution Hintを参照)terminate()— 現在の探索を協調的に中断します(下記探索の中断を参照)
タイマー制御
Timer イベントはデフォルトでは有効になっていません.
定期的なタイマーコールバックを有効にするには,callback() メソッド内で timer(seconds) を呼び出します:
timer(1.0)— 1秒ごとにTimerコールバックを発火しますtimer(0)— タイマーを無効にしますtimer()が呼ばれない場合,タイマー間隔は変更されません.
通常,timer() は Start コールバック中に1回呼び出されて間隔を設定します.
BestUpdated や Timer コールバック中にも呼び出して,タイマーを動的に調整または無効にすることができます.
例: カスタムコールバック
#include <qbpp/qbpp.hpp>
#include <qbpp/abs3_solver.hpp>
class MySolver : public qbpp::ABS3Solver {
public:
using ABS3Solver::ABS3Solver;
void callback() const override {
if (event() == qbpp::CallbackEvent::Start) {
timer(1.0); // 1秒ごとのタイマーコールバックを有効化
}
if (event() == qbpp::CallbackEvent::BestUpdated) {
std::cout << "New best: energy=" << best_sol().energy()
<< " TTS=" << best_sol().tts() << "s" << std::endl;
}
}
};
int main() {
auto x = qbpp::var("x", 8);
auto f = qbpp::sum(x) == 4;
f.simplify_as_binary();
auto solver = MySolver(f);
auto sol = solver.search({{"time_limit", 5}, {"target_energy", 0}});
std::cout << "energy=" << sol.energy() << std::endl;
}
探索の中断
terminate() を呼び出すと,実行中の search() を協調的に中断させ,これまでに発見した最良解を保持したまま即座に return させることができます.
呼び出せる場所:
callback()の内部 —best_sol().energy()などを観察して停止を判断別スレッド — 外部からのキャンセル,シグナルハンドラ,ウォッチドッグなど
terminate() の利点は,現在の最良解を見て柔軟に停止条件を組み立てられる ことです.target_energy は事前に閾値を1つ固定する必要がありますが,terminate() なら:
任意の
energy条件(例:energy <= 0,energy < prev_best * 0.99)経過時間と組み合わせた条件(例:「5秒以内に改善が無ければ停止」)
制約違反量と目的関数の重み付け(例:
onehot_violation == 0 && objective <= threshold)外部要因(GUI のキャンセルボタン,別ソルバーの完了通知など)
など,実行時に評価できる任意の条件で停止を制御できます.
同じインスタンスで再度 search() を呼び出すと,停止フラグは自動的にクリアされます.
例: energy が 0 になったら停止
target_energy を使わず,コールバックで energy == 0 を観測した瞬間に terminate() を呼び出します.
#include <iostream>
#include <qbpp/qbpp.hpp>
#include <qbpp/abs3_solver.hpp>
class TerminateOnZero : public qbpp::ABS3Solver {
public:
using ABS3Solver::ABS3Solver;
void callback() const override {
if (event() == qbpp::CallbackEvent::BestUpdated) {
std::cout << "energy=" << best_sol().energy()
<< " tts=" << best_sol().tts() << "s" << std::endl;
if (best_sol().energy() == 0) {
terminate(); // 同じ energy で再び呼ばれることはないので 1 回限り
}
}
}
};
int main() {
auto x = qbpp::var("x", 10);
auto f = qbpp::sqr(qbpp::sum(x) - 5);
f.simplify_as_binary();
TerminateOnZero solver(f);
// target_energy は指定しない。callback の terminate() のみで停止する
auto sol = solver.search({{"time_limit", 60}});
std::cout << "final energy=" << sol.energy() << std::endl;
}
Solution Hint
ヒント解を使用すると,以前に見つかった解で探索をウォームスタートできます.
最も簡単な方法は,search() の前に**params.hint(sol)**を呼び出すことです:
params.hint(sol); // 探索にヒント解を提供
auto result = solver.search(params);
解は探索開始前にソルバーの内部データ構造に直接書き込まれます.
外部ソルバーを並行して実行するような高度なユースケースでは,コールバック中に**hint(sol)**を呼び出して動的に解を供給することもできます.
このシナリオでは,コールバックが定期的に呼び出されて新しい外部解をチェックできるように,定期的なタイマー(例:timer(1.0))を設定することを推奨します.
例: ヒント解の提供
以下の例は素因数分解問題を2回解きます.
1回目の実行では通常通り最適解を見つけます.
2回目の実行では params.hint(sol) を介して最初の解をヒントとして提供し,ソルバーの収束を大幅に高速化します.
#include <qbpp/qbpp.hpp>
#include <qbpp/abs3_solver.hpp>
int main() {
auto p = 2 <= qbpp::var_int("p") <= 1000;
auto q = 2 <= qbpp::var_int("q") <= 1000;
auto f = p * q == 899 * 997;
f.simplify_as_binary();
auto solver = qbpp::ABS3Solver(f);
// 実行1: 通常の探索
const auto sol1 = solver.search({{"target_energy", 0}, {"time_limit", 10}, {"enable_default_callback", 1}});
std::cout << "Run 1: p=" << sol1(p) << " q=" << sol1(q)
<< " energy=" << sol1.energy() << std::endl;
// 実行2: 前回の解をヒントとして提供
qbpp::Params params2({{"target_energy", 0}, {"time_limit", 10}, {"enable_default_callback", 1}});
params2.hint(sol1);
const auto sol2 = solver.search(params2);
std::cout << "Run 2: p=" << sol2(p) << " q=" << sol2(q)
<< " energy=" << sol2.energy()
<< " TTS=" << sol2.tts() << "s" << std::endl;
}
ヒント解は探索開始前にソルバーの内部データ構造に直接書き込まれます. ソルバーはそのエネルギーを評価し,初期状態として使用した上で,より良い解の探索を続けます.
ABS3 Solverを使用して式 f を解くには,以下の2つのステップで行います:
式
fに対してABS3Solverオブジェクトを作成する.キーワード引数を指定して
search()メソッドを呼び出し,得られた解を取得する.
ABS3 Solverを使用したLABS問題の求解
以下のプログラムは,ABS3 Solverを使用して Low Autocorrelation Binary Sequence (LABS) 問題を解きます:
import pyqbpp as qbpp
size = 100
x = qbpp.var("x", shape=size)
f = qbpp.expr()
for d in range(1, size):
temp = qbpp.expr()
for i in range(size - d):
temp += (2 * x[i] - 1) * (2 * x[i + d] - 1)
f += qbpp.sqr(temp)
f.simplify_as_binary()
solver = qbpp.ABS3Solver(f)
sol = solver.search(time_limit=10.0, enable_default_callback=1)
bits = "".join("-" if sol(x[i]) == 0 else "+" for i in range(size))
print(f"{sol.energy}: {bits}")
このプログラムでは,まず式 f に対して ABS3Solver オブジェクト solver を作成します.
次に,search() メソッドにパラメータをキーワード引数として渡して呼び出します.
time_limit は最大探索時間を秒単位で指定し,enable_default_callback=1 は探索中に新たに見つかった最良解のエネルギーとTTSを出力する組み込みコールバックを有効にします.
このメソッドは指定された制限時間内に見つかった最良解を返し,sol に格納されます.
プログラムは解のエネルギーと対応するバイナリ列を出力します."+"は1を,"-"は0を表します.
このプログラムは以下のような出力を生成します:
TTS = 0.002s Energy = 1218
TTS = 0.002s Energy = 1170
TTS = 0.002s Energy = 994
TTS = 0.015s Energy = 958
TTS = 0.018s Energy = 922
TTS = 0.034s Energy = 874
TTS = 4.364s Energy = 834
834: -+--+---++-++-+---++-++--+++--+-+-+++++----+++-+-+---++-+--+-----+--+----++----+-+--++++++---+------
ABS3 Solverオブジェクト
ABS3Solver オブジェクトは,与えられた式に対して作成されます.
コンストラクタは式を内部データフォーマットに変換してホストメモリにロードし,GPUが利用可能な場合はデバイスメモリにも転送します.
以降 search() を複数回呼び出してもこのロードは1度きりなので,同じ式に対して繰り返し探索する際のオーバーヘッドがありません.
省略可能な第2引数 gpu でGPUの使用を制御します:
ABS3Solver(f): 利用可能なすべてのGPUを自動的に使用します.GPUが利用できない場合はCPUのみモードにフォールバックします.ABS3Solver(f, 0): CPUのみモードを強制します(GPUは使用されません).ABS3Solver(f, n):n個のGPUを使用します.式はn個のGPUすべてにロードされます.
探索パラメータは search() にキーワード引数として直接渡します.
上記の例では:
time_limit=10.0: 制限時間を10.0秒に設定します.enable_default_callback=1: 新たに得られた解のエネルギーとTTSを出力する組み込みコールバック関数を有効にします.
ABS3パラメータ
パラメータは search() メソッドにキーワード引数として直接渡します.
上記のプログラムでは,time_limit=10.0 で制限時間を10.0秒に設定し,enable_default_callback=1 で新たに得られた解のエネルギーを出力する組み込みコールバック関数を有効にしています.
戻り値は解で,sol.energy(エネルギー値),sol(x)(変数値の取得),sol.info(ソルバー情報の辞書)などを提供します.詳細は QR_SOLUTION を参照してください.
基本オプション
キー |
型 |
説明 |
|---|---|---|
|
float |
制限時間(秒).制限時間に達すると探索を終了します |
|
int |
ターゲットエネルギーが達成されると探索を終了します |
詳細オプション
キー |
型 |
説明 |
|---|---|---|
|
int (0 または 1) |
エネルギーとTTSを出力する組み込みコールバックを有効にします |
|
int (0 または 1) |
GPUと並行して動作するCPUソルバーの有効/無効(デフォルト: 1) |
|
int |
CPUソルバーのスレッド数(デフォルト: 自動) |
|
int |
GPU当たりのCUDAブロック数 |
|
int |
CUDAブロック当たりのスレッド数 |
|
int |
最良エネルギーのtop-K解を返します |
|
int |
最大数( |
|
int |
乱数シード( |
複数解の収集
ABS3 Solverは探索中に複数の解を収集できます. 2つのモードが利用可能です:
Top-K解 (topk_sols)
topk_sols パラメータはエネルギーの昇順にソートされたtop-K解を収集します.
sol = solver.search(topk_sols=10) # 最大10個の最良解を収集
最良エネルギー解 (best_energy_sols)
best_energy_sols パラメータは見つかった最良エネルギーを共有するすべての解を収集します.
より良いエネルギーが発見されると,プールがクリアされ,新しい最良エネルギーの解のみが保持されます.
sol = solver.search(best_energy_sols=0) # すべての最良エネルギー解を収集(無制限)
または,best_energy_sols を最大数付きで設定することもできます:
sol = solver.search(best_energy_sols=100) # 最大100個を収集
topk_sols と best_energy_sols は同じ内部プールを共有することに注意してください.
両方が指定された場合,最後に指定されたものが有効になります.
収集された解へのアクセス
search() の戻り値は解で,sol.sols プロパティを介して収集された解のリストにアクセスできます:
sol = solver.search(topk_sols=5)
print(f"Best energy: {sol.energy}")
print(f"Number of solutions: {len(sol.sols)}")
for s in sol.sols:
print(f"energy = {s.energy} TTS = {s.tts}s")
戻り値オブジェクトは以下をサポートします:
sol.energy— 最良解のエネルギーsol.tts— 最良解が見つかるまでの時間(秒)sol.sols— 収集された解のリスト(エネルギーの昇順)sol.sols[i]— i番目の解へのアクセスlen(sol.sols)— 収集された解の数sol.info— ソルバー情報の辞書
カスタムコールバック
組み込みコールバック(enable_default_callback=1 で有効化)は,新しい最良解が見つかるたびにエネルギーとTTSを出力するだけです.
より細かい制御が必要な場合は,ABS3Solver をサブクラス化して callback() メソッド(引数なし)をオーバーライドします.
コールバックは以下のイベントのいずれかで呼び出されます:
イベント値 |
名前 |
説明 |
|---|---|---|
|
|
|
|
|
新しい最良解が見つかるたびに呼び出されます |
|
|
設定可能な間隔で定期的に呼び出されます |
callback() 内では,以下のメソッドが利用可能です:
self.event()— コールバックを発火したイベント.EVENT_*定数(EVENT_START=0,EVENT_BEST_UPDATED=1,EVENT_TIMER=2)と比較できる整数を返します.これらの定数と比較してください(例:if self.event() == self.EVENT_BEST_UPDATED:)self.best_sol()— 現在の最良解を返します..energy,.tts,.get(var)などが使用できますself.timer(seconds)— 定期的なTimerコールバックの間隔を秒単位で設定.0でタイマーを無効化(下記参照)self.hint(sol)— 探索中にソルバーにヒント解を提供(Solution Hint を参照)self.terminate()— 現在の探索を協調的に中断します(下記探索の中断を参照)
タイマー制御
Timer イベントはデフォルトでは有効になっていません.
定期的なタイマーコールバックを有効にするには,callback() メソッド内で self.timer(seconds) を呼び出します:
self.timer(1.0)— 1秒ごとにTimerコールバックを発火しますself.timer(0)— タイマーを無効にしますself.timer()が呼ばれない場合,タイマー間隔は変更されません
通常,self.timer() は Start コールバック中に1回呼び出されて間隔を設定します.
BestUpdated や Timer コールバック中にも呼び出して,タイマーを動的に調整または無効にすることができます.
例: カスタムコールバック
import pyqbpp as qbpp
class MySolver(qbpp.ABS3Solver):
def callback(self):
if self.event() == 0: # Start
self.timer(1.0) # 1秒ごとのタイマーコールバックを有効化
if self.event() == 1: # BestUpdated
sol = self.best_sol()
print(f"New best: energy={sol.energy} TTS={sol.tts:.3f}s")
x = qbpp.var("x", shape=8)
f = qbpp.sqr(qbpp.sum(x) - 4)
f.simplify_as_binary()
solver = MySolver(f)
sol = solver.search(time_limit=5, target_energy=0)
print(f"energy={sol.energy}")
探索の中断
self.terminate() を呼び出すと,実行中の search() を協調的に中断させ,これまでに発見した最良解を保持したまま即座に return させることができます.
呼び出せる場所:
callback()の内部 —self.best_sol().energyなどを観察して停止を判断別スレッド — 外部からのキャンセル,シグナルハンドラ,ウォッチドッグなど
terminate() の利点は,現在の最良解を見て柔軟に停止条件を組み立てられる ことです.target_energy は事前に閾値を1つ固定する必要がありますが,terminate() なら:
任意の
energy条件(例:energy <= 0,energy < prev_best * 0.99)経過時間と組み合わせた条件(例:「5秒以内に改善が無ければ停止」)
制約違反量と目的関数の重み付け(例:
onehot_violation == 0 and objective <= threshold)外部要因(GUI のキャンセルボタン,別ソルバーの完了通知など)
など,実行時に評価できる任意の条件で停止を制御できます.
同じインスタンスで再度 search() を呼び出すと,停止フラグは自動的にクリアされます.
例: energy が 0 になったら停止
target_energy を使わず,コールバックで energy == 0 を観測した瞬間に self.terminate() を呼び出します.
import pyqbpp as qbpp
class TerminateOnZero(qbpp.ABS3Solver):
def callback(self):
if self.event() == qbpp.ABS3Solver.EVENT_BEST_UPDATED:
sol = self.best_sol()
print(f"energy={sol.energy} tts={sol.tts:.3f}s")
if sol.energy == 0:
self.terminate() # 同じ energy で再び呼ばれることはないので 1 回限り
x = qbpp.var("x", shape=10)
f = qbpp.sqr(qbpp.sum(x) - 5)
f.simplify_as_binary()
solver = TerminateOnZero(f)
# target_energy は指定しない。callback の terminate() のみで停止する
sol = solver.search(time_limit=60)
print(f"final energy={sol.energy}")
Solution Hint
ヒント解を使用すると,以前に見つかった解で探索をウォームスタートできます.
最も簡単な方法は,search() の前に solver.hint(sol) を呼び出すことです:
solver.hint(prev_sol) # 探索にヒント解を提供
sol = solver.search(time_limit=10)
解は探索開始前にソルバーの内部データ構造に直接書き込まれます.
外部ソルバーを並行して実行するような高度なユースケースでは,コールバック内で self.hint(sol) を呼び出して動的に解を供給することもできます.
このシナリオでは,コールバックが定期的に呼び出されて新しい外部解をチェックできるように,定期的なタイマー(例: self.timer(1.0))を設定することを推奨します.
例: ヒント解の提供
以下の例は素因数分解問題を2回解きます.
1回目の実行では通常通り最適解を見つけます.
2回目の実行では solver.hint(sol1) を介して最初の解をヒントとして提供し,ソルバーの収束を大幅に高速化します.
import pyqbpp as qbpp
p = qbpp.var("p", between=(2, 1000))
q = qbpp.var("q", between=(2, 1000))
f = qbpp.sqr(p * q - 899 * 997)
f.simplify_as_binary()
solver = qbpp.ABS3Solver(f)
# 実行1: 通常の探索
sol1 = solver.search(target_energy=0, time_limit=10, enable_default_callback=1)
print(f"Run 1: p={sol1(p)} q={sol1(q)} energy={sol1.energy} TTS={sol1.tts:.3f}s")
# 実行2: 前回の解をヒントとして提供
solver.hint(sol1)
sol2 = solver.search(target_energy=0, time_limit=10, enable_default_callback=1)
print(f"Run 2: p={sol2(p)} q={sol2(q)} energy={sol2.energy} TTS={sol2.tts:.3f}s")
ヒント解は探索開始前にソルバーの内部データ構造に直接書き込まれます. ソルバーはそのエネルギーを評価し,初期状態として使用した上で,より良い解の探索を続けます.