ABS3 Solver の使用方法

ABS3 Solverを使用して式 f を解くには、以下の 2 つのステップで行います:

  • f に対してABS3 Solver(qbpp::ABS3Solver)オブジェクトを作成します。

  • search() メンバ関数を呼び出します。パラメータは初期化子リストとして渡します。得られた解が返されます。

ABS3 Solver を用いた LABS 問題の解法

次の Hi-QUBO プログラムは、ABS3 Solver を使用して、低自己相関二進列(Low Autocorrelation Binary Sequence、LABS)問題 を解きます。

abs3-solver-usage-program1.cpp
#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 に格納されます。

プログラムは、解のエネルギーと対応する2進シーケンスを表示します。このとき、「+」は 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 で新たに得られた解のエネルギーを出力する組み込みコールバック関数を有効にしています。

基本オプション

キー

説明

time_limit

時間制限(秒)

時間制限に達したら探索を終了

target_energy

目標エネルギー値

目標エネルギーに達したら探索を終了

詳細オプション

キー

説明

enable_default_callback

"1"

エネルギーと TTS を出力する組み込みコールバックを有効化

cpu_enable

"0" または "1"

GPU と併用して CPU ソルバーを有効/無効化(デフォルト: "1")

cpu_thread_count

CPU スレッド数

CPU ソルバーのスレッド数(デフォルト: 自動)

block_count

CUDA ブロック数/GPU

ソルバーカーネルで起動される CUDA ブロック数

thread_count

CUDA ブロックごとのスレッド数

CUDA ブロック内のスレッド数

topk_sols

解の数

最も良いエネルギーの上位 K 解を返す

best_energy_sols

最大数(”0” = 無制限)

見つかった最良エネルギーを持つすべての解を返します

複数解の収集

ABS3 Solverは探索中に複数の解を収集できます。 2つのモードが利用可能です:

Top-K解 (topk_sols)

topk_sols パラメータはエネルギーの昇順にソートされたtop-K解を収集します。

params.set("topk_sols", "10");  // 最大10個の最良解を収集

最良エネルギー解 (best_energy_sols)

best_energy_sols パラメータは見つかった最良エネルギーを共有するすべての解を収集します。 より良いエネルギーが発見されると、プールがクリアされ、新しい最良エネルギーの解のみが保持されます。

params.set("best_energy_sols", "0");  // すべての最良エネルギー解を収集(無制限)

または、best_energy_sols を最大数付きで設定することもできます:

params.set("best_energy_sols", "100");  // 最大100個を収集

topk_solsbest_energy_sols は同じ内部プールを共有することに注意してください。 両方が指定された場合、最後に指定されたものが有効になります。

収集された解へのアクセス

search() メソッドは ABS3Sols オブジェクトを返し、収集された解へのアクセスを提供します:

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.best_sols()) {
  std::cout << "Energy = " << sol.energy() << " TTS = " << sol.tts() << "s" << std::endl;
}

ABS3Sols オブジェクトは以下をサポートします:

  • size() — 収集された解の数

  • sols() — 解ベクトルへのアクセス

  • operator[](i) — i番目の解へのアクセス

  • 範囲ベースforループによるイテレーション

カスタムコールバック

組み込みコールバック(enable_default_callback によって有効化)は、新しい最良解が見つかるたびにエネルギーと TTS を出力します。より詳細な制御が必要な場合は、ABS3Solver をサブクラス化して、callback() 仮想メソッドをオーバーライドすることができます。

コールバックは次のいずれかのイベントで呼び出されます。

イベント

説明

CallbackEvent::Start

search() の開始時に一度呼ばれる

CallbackEvent::BestUpdated

新しい最良解が見つかるたびに呼ばれる

CallbackEvent::Timer

設定可能な間隔で定期的に呼ばれる

コールバック内で利用可能なアクセサメソッドは次の通りです。

  • 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 コールバック 内で一度呼び出されます。また、BestUpdatedTimer コールバックの中で呼び出すことで、タイマーを動的に調整したり無効化したりすることもできます。

例: カスタムコールバック

abs3-solver-usage-program2.cpp
#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() を呼び出します。

abs3-solver-usage-program3.cpp
#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) を介して最初の解をヒントとして提供し、ソルバーの収束を大幅に高速化します。

abs3-solver-usage-program3.cpp
#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;
}

注: ヒント解は探索開始前にソルバーの内部データ構造に直接書き込まれます。 ソルバーはそのエネルギーを評価し、初期状態として使用した上で、より良い解の探索を続けます。

ABS3 Solver を使用して式 f を解くには、次の 2 つのステップを実行します。

  • f に対して ABS3Solver オブジェクトを作成する。

  • キーワード引数を指定して search() メソッドを呼び出し、得られた解を取得する。

ABS3 Solver を用いた LABS 問題の解法

次の PyQBPP プログラムは、ABS3 Solver を使用して、低自己相関二進列(Low Autocorrelation Binary Sequence、LABS)問題 を解きます。

abs3-solver-usage-program1.py
import pyqbpp as qbpp

size = 100
x = qbpp.var("x", 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)
solver.time_limit(10.0)
solver.callback(lambda energy, tts, event: print(f"TTS = {tts:.3f}s Energy = {energy}"))
sol = solver.search()
bits = "".join("-" if sol(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 を使用。

探索パラメータは 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 を参照してください。

基本オプション

キー

説明

time_limit

float

制限時間(秒)。制限時間に達すると探索を終了します。

target_energy

int

ターゲットエネルギーが達成されると探索を終了します。

詳細オプション

キー

説明

enable_default_callback

int (0 または 1)

エネルギーと TTS を出力する組み込みコールバックを有効にします。

cpu_enable

int (0 または 1)

GPU と並行して動作する CPU ソルバーの有効 / 無効(デフォルト: 1)。

cpu_thread_count

int

CPU ソルバーのスレッド数(デフォルト: 自動)。

block_count

int

GPU 当たりの CUDA ブロック数。

thread_count

int

CUDA ブロック当たりのスレッド数。

topk_sols

int

最良エネルギーの top-K 解を返します。

best_energy_sols

int

最大数(0 = 無制限)。見つかった最良エネルギーを持つすべての解を返します。

複数解の収集

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_solsbest_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() メソッド(引数なし)をオーバーライドします。

コールバックは以下のイベントのいずれかで呼び出されます:

イベント値

名前

説明

0

Start

search() の開始時に 1 回呼び出されます。

1

BestUpdated

新しい最良解が見つかるたびに呼び出されます。

2

Timer

設定可能な間隔で定期的に呼び出されます。

callback() 内では、以下のメソッドが利用可能です:

  • self.event() — コールバックを発火したイベント(int: 0=Start, 1=BestUpdated, 2=Timer)

  • 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回呼び出されて間隔を設定します。 BestUpdatedTimer コールバック中にも呼び出して、タイマーを動的に調整または無効にすることができます。

例: カスタムコールバック

abs3-solver-usage-program2.py
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 <= 0energy < prev_best * 0.99

  • 経過時間と組み合わせた条件(例:「5秒以内に改善が無ければ停止」)

  • 制約違反量と目的関数の重み付け(例: onehot_violation == 0 and objective <= threshold

  • 外部要因(GUI のキャンセルボタン、別ソルバーの完了通知など)

など、実行時に評価できる任意の条件で停止を制御できます。

同じインスタンスで再度 search() を呼び出すと、停止フラグは自動的にクリアされます。

例: energy が 0 になったら停止

target_energy を使わず、コールバックで energy == 0 を観測した瞬間に self.terminate() を呼び出します。

abs3-solver-usage-program3.py
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) を介して最初の解をヒントとして提供し、ソルバーの収束を大幅に高速化します。

abs3-solver-usage-program4.py
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")

ヒント解は探索開始前にソルバーの内部データ構造に直接書き込まれます。 ソルバーはそのエネルギーを評価し、初期状態として使用した上で、より良い解の探索を続けます。