変数クラスと式クラス

qbpp::Var、qbpp::Term、qbpp::Expr クラス

QUBO++は以下の基本クラスを提供します。

  • qbpp::Var: 変数をシンボリックに表現し、表示用の文字列が関連付けられます。内部的には32ビット符号なし整数が識別子として使用されます。
  • qbpp::Term: 整数係数と1つ以上の qbpp::Var オブジェクトからなる積の項を表現します。整数係数のデータ型は qbpp::coeff_t(デフォルトは int32_t)で、ビルド時に INTEGER_TYPE_* マクロで選択します(後述)。 各 qbpp::Term は変数を静的配列(インラインバッファ2要素)と動的確保の組み合わせで格納し、デフォルトの可変長モードでは次数に上限なく任意の高次項を扱うことができます(固定長の VarArray モードについては後述)。
  • qbpp::Expr: 整数定数項と0個以上の qbpp::Term オブジェクトからなる展開された式を表現します。整数定数項のデータ型は qbpp::energy_t(デフォルトは int64_t)で、同じく INTEGER_TYPE_* マクロで選択します。

以下のプログラムでは、x と y は qbpp::Var オブジェクト、t は qbpp::Term オブジェクト、f は qbpp::Expr オブジェクトです。

#include <qbpp/qbpp.hpp>

int main() {
  auto x = qbpp::var("x");
  auto y = qbpp::var("y");
  auto t = 2 * x * y;
  auto f = t - x + 1;

  std::cout << "x = " << x << std::endl;
  std::cout << "y = " << y << std::endl;
  std::cout << "t = " << t << std::endl;
  std::cout << "f = " << f << std::endl;
}

このプログラムは以下の出力を生成します。

x = x
y = y
t = 2*x*y
f = 1 +2*x*y -x

データ型を明示的に指定する場合、プログラムは以下のように書き直せます。

#include <qbpp/qbpp.hpp>

int main() {
  qbpp::Var x = qbpp::var("x");
  qbpp::Var y = qbpp::var("y");
  qbpp::Term t = 2 * x * y;
  qbpp::Expr f = t - x + 1;

  std::cout << "x = " << x << std::endl;
  std::cout << "y = " << y << std::endl;
  std::cout << "t = " << t << std::endl;
  std::cout << "f = " << f << std::endl;
}

qbpp::Var オブジェクトは 不変(immutable) であり、作成後に更新できません。 一方、qbpp::Term と qbpp::Expr オブジェクトは 可変(mutable) であり、代入によって更新できます。

例えば、以下のプログラムに示すように、複合代入演算子を使用して qbpp::Term と qbpp::Expr オブジェクトを更新できます。

#include <qbpp/qbpp.hpp>

int main() {
  qbpp::Var x = qbpp::var("x");
  qbpp::Var y = qbpp::var("y");
  qbpp::Term t = 2 * x * y;
  qbpp::Expr f = t - x + 1;

  std::cout << "t = " << t << std::endl;
  std::cout << "f = " << f << std::endl;

  t *= 3 * x;
  f += 2 * y;

  std::cout << "t = " << t << std::endl;
  std::cout << "f = " << f << std::endl;
}

このプログラムは以下の出力を生成します。

t = 2*x*y
f = 1 +2*x*y -x
t = 6*x*y*x
f = 1 +2*x*y -x +2*y

エイリアスとコピー

C++ ではデフォルトで 値セマンティクス が適用されます。qbpp::Term や qbpp::Expr を別の変数に代入すると深いコピーが行われ、二つの独立した オブジェクトが得られます。

qbpp::Expr f = x + 1;
qbpp::Expr g = f;   // 独立したコピー
f += y;
std::cout << "f = " << f << std::endl;   // f = 1 +x +y
std::cout << "g = " << g << std::endl;   // g = 1 +x   (影響を受けない)

f = f + x と f += x は観測上同じ結果になります。どちらも f を更新する だけで他のオブジェクトには影響しません。両者の違いは性能だけで、左辺が 一時オブジェクトの場合にはコンパイラが in-place な rvalue オーバーロードを 選択し、不必要なコピーを回避します。

Python フロントエンド (PyQBPP) は参照セマンティクスのため別の規則に従います。 詳細は C++ vs Python の 対比を参照してください。

ほとんどの場合、qbpp::Term オブジェクトを明示的に使用する必要はありません。 最大限のパフォーマンス最適化が必要な場合にのみ使用すべきです。

ただし、auto 型推論により qbpp::Term オブジェクトが作成される場合があり、一般的な式を格納できないことに注意してください。 例えば、以下のプログラムは、式が qbpp::Term オブジェクトに代入されるため、コンパイルエラーになります。

#include <qbpp/qbpp.hpp>

int main() {
  auto x = qbpp::var("x");
  auto y = qbpp::var("y");

  auto t = 2 * x * y;
  t = x + 1;
}

qbpp::Expr オブジェクトを意図する場合、以下に示すように qbpp::toExpr() を使用して明示的に構築できます。

#include <qbpp/qbpp.hpp>

int main() {
  auto x = qbpp::var("x");
  auto y = qbpp::var("y");
  auto t = qbpp::toExpr(2 * x * y);
  auto f = qbpp::toExpr(1);

  t += x + 1;
  f += t;

  std::cout << "t = " << t << std::endl;
  std::cout << "f = " << f << std::endl;
}

このプログラムでは、t と f の両方が qbpp::Expr オブジェクトであり、一般的な式を格納できます。 特に、f は値 1 の定数項のみを持ち、積の項を持たない qbpp::Expr オブジェクトとして作成されます。

qbpp::toExpr() は配列も受け取り、同じ形状の式の配列を返します(例: auto f = qbpp::toExpr(qbpp::var("x", 3));)。変数の配列には定義されていない演算(整数による除算や、+= などの複合代入)を使う場合に必要です(整数による除算は項の配列でも使えます)。配列の基本演算子と関数 を参照してください。

整数の範囲:coeff_t と energy_t

型エイリアス qbpp::coeff_t と qbpp::energy_t が、式内の係数とエネルギー値に使用されるデータ型を表します。 qbpp::energy_t は qbpp::Expr オブジェクトの整数定数項のデータ型でもあります。 定数を足す演算(x + c、e += c、qbpp::Expr(c))と制約の値(e == c、e <= c など)は energy_t で受け取るので、coeff_t を超える値(デフォルトで 1LL << 40 など)もそのまま書けます。変数に掛ける数(c * x)は coeff_t です。 次の型を選択できます。

型 範囲 大きな定数の構文
int32_t ±2.1×10⁹ 12345(整数リテラル)
int64_t ±9.2×10¹⁸ 1234567890123456789LL
qbpp::int128_t ±1.7×10³⁸ qbpp::integer("12345678901234567890")
qbpp::cpp_int 無制限 qbpp::integer("...")

型 qbpp::cpp_int は任意桁数の整数を表します。 関数 qbpp::integer("...") は10進文字列を現在の coeff_t 型に解析する統一ヘルパーで、 int32_t から cpp_int までどのビルドでも同じソースコードで使用できます。

デフォルトでは coeff_t は int32_t、energy_t は int64_t です。 デフォルト以外の型を使用するには、ヘッダのインクルード前に以下のマクロの一つを定義します(またはコンパイラフラグ -D... で指定):

マクロ coeff_t energy_t
INTEGER_TYPE_C32E32 int32_t int32_t
INTEGER_TYPE_C32E64(デフォルト) int32_t int64_t
INTEGER_TYPE_C64E64 int64_t int64_t
INTEGER_TYPE_C64E128 int64_t int128_t
INTEGER_TYPE_C128E128 int128_t int128_t
INTEGER_TYPE_CPP_INT cpp_int cpp_int

実数(double)係数

係数とエネルギー値には double も使用できます。ヘッダのインクルード前に次のいずれかのマクロを定義します(またはコンパイラフラグ -D... で指定):

マクロ 内部ソルバー
DOUBLE_TYPE(デフォルトの double) 64ビット整数ソルバー
DOUBLE_TYPE_C64E64 64ビット整数ソルバー
DOUBLE_TYPE_C64E128 128ビット整数ソルバー(高精度・推奨)
DOUBLE_TYPE_C128E128 128ビット整数ソルバー(最も広いダイナミックレンジ)
#define DOUBLE_TYPE
#include <qbpp/qbpp.hpp>

auto x = qbpp::var("x");
auto y = qbpp::var("y");
qbpp::Expr f = -1.5 * x - 2.5 * y + 4.0 * x * y;   // 実数(double)係数

式の構築と簡約は double で行われます。求解時には係数が自動的に整数へスケーリング・丸められ、整数ソルバーで解かれ、エネルギーは double で返されます(例えば sol.energy() は double を返します)。そのため整数バックエンドを意識せずに double のまま扱えます。2進小数の係数(1, 1/2, 1/4, …)は厳密に表現されます。

最大の係数に比べて極端に小さい係数は、スケーリングの精度を下回ると 0 として扱われ、その項は除外されます(エラーではなく短い通知が出ます)。項がすべて消えた変数は、目的関数に影響しない自由変数としてモデルに残り、解から読み出す(sol(x)、sol(x[i]))と 0 を返します。一方、simplify_as_binary で相殺して式から消えた変数はモデルに含まれないので、解から読み出すとエラーになります。sol.has(x[i]) で、その変数がモデルに含まれているか確認できます。エネルギー範囲の真のオーバーフローは従来どおりエラーになります。

どのマクロを選ぶか

既定の DOUBLE_TYPE で始めてください。ほとんどのモデルでは、これで元の double の精度をほぼそのまま保てます。

精度が足りなくなるのは、係数の大小の開きがモデル全体の規模に対して大きいときです。次のような場合は DOUBLE_TYPE_C64E128 を検討してください。

  • 項数が非常に多い(10^7 以上が目安)
  • 制約のペナルティ重みが目的関数の係数より何桁も大きい
  • 1 つのモデルの中で係数の大きさが何桁にもわたる

DOUBLE_TYPE_C64E128 は保持できる係数の精度が大きく上がる一方で、探索速度は DOUBLE_TYPE とほぼ変わりません。メモリ使用量はやや増えます。DOUBLE_TYPE_C128E128 はさらに広いダイナミックレンジを扱えますが、同じ時間で到達する解の質が落ちるため、DOUBLE_TYPE_C64E128 でも係数が収まらない場合に限って使ってください。

ソルバーが解く式を確かめる:qbpp::quantize()

求解時の整数への変換は qbpp::quantize() で取り出せます。返り値はソルバーが実際に解く式で、係数はすべて整数です。Easy Solver・Exhaustive Solver・ABS3 Solver には元の式の代わりにこの式を渡すこともでき、どちらを渡しても同じ問題が解かれます。

#define DOUBLE_TYPE
#include <qbpp/qbpp.hpp>
#include <qbpp/easy_solver.hpp>

#include <cmath>

int main() {
  auto x = qbpp::var("x", 3);
  auto f = 0.3 * x[0] + 0.5 * x[1] + 0.4 * x[2] +
           1000 * qbpp::cons(0.06 * x[0] + 0.05 * x[1] + 0.07 * x[2] >= 0.1);
  f.simplify_as_binary();

  auto g = qbpp::quantize(f);
  std::cout << "scale = 2^" << std::log2(g.scale()) << std::endl;
  std::cout << g << std::endl;

  auto sol = qbpp::EasySolver(g).search();
  std::cout << sol << std::endl;
  std::cout << "g.energy(sol) = " << g.energy(sol)
            << ", g.cons(sol) = " << g.cons(sol) << std::endl;
}

このプログラムの出力は次のとおりです。

scale = 2^58
86469112845513520*x[0] +144115188075855872*x[1] +115292150460684704*x[2]
+1024000*cons(1006633*x[0] +838861*x[1] +1174405*x[2] >= 1677722)
0.7:{{x[0],1},{x[1],0},{x[2],1}}
g.energy(sol) = 0.7, g.cons(sol) = 0
  • 目的関数の係数は g.scale()(2 のべき、この例では 258)倍して整数に丸められます。
  • qbpp::cons() の制約は、本体と範囲を目的関数とは別の倍率(この例では 224)で整数にし、ペナルティが目的関数と同じ単位になるよう重みを換算します(1000 × 258 / (224)2 = 1024000)。0.06 や 0.1 のように 2 進で割り切れない値は、相対誤差 2−20 以内に丸められます(0.1 は 1677722 / 224 になります)。この精度を保てない制約はエラーになり、DOUBLE_TYPE_C64E128 を使うか、係数と範囲を整数で書くよう案内されます。
  • 整数の係数のモデルでは g.scale() は 1 で、g は元の式と同じです。

qbpp::quantize() が返す式 g には、次の関数があります。

関数 内容
g.scale() 係数に掛けた倍率
g.energy(sol) 解のエネルギー(元の単位)。ソルバーが返した解では sol.energy() と一致します
g.energy_int(sol) 解のエネルギー(整数の単位、g.energy(sol) の g.scale() 倍)
g.cons(sol) 違反している制約の数(ソルバーと同じ判定)
g.is_feasible(sol) すべての制約を満たしていれば true
g.violations(sol) 制約ごとの値・範囲・違反量(丸めた後の値を元の単位で)
g.expr() 整数係数の qbpp::Expr

元の式 f で評価した f(sol) は、丸める前の double の値です。丸めの分だけ g.energy(sol) とわずかに異なることがあり、制約の境界ちょうどの解では f.cons(sol) と g.cons(sol) の判定が分かれることもあります。ソルバーと同じ判定が必要なときは g で評価してください。わずかな違反を許したいときは、f.cons(sol, 1e-6) のように許容誤差を指定できます(非線形関数とネイティブ制約参照)。

VarArray モード

MAXDEG マクロは、各項の変数の内部格納方式を制御します。 最大次数が事前に分かっている場合、固定長モードを使うとヒープ確保が不要になり性能が向上します:

マクロ 最大次数 説明
MAXDEG0(デフォルト) 無制限 可変長(3次以上でヒープ確保)
MAXDEG2 2 固定長、QUBOのみ(ヒープ確保なし、最速)
MAXDEG4 4 固定長、4次まで(ヒープ確保なし)

使用例 — 型と VarArray モードの両方を指定:

#define INTEGER_TYPE_C32E32
#define MAXDEG2
#include <qbpp/qbpp.hpp>

指定されたマクロに基づいて適切なライブラリが実行時に自動的にロードされます。

大きな定数:qbpp::integer()

64ビット整数の範囲を超える定数値は、関数 qbpp::integer("...") に10進文字列を渡して指定します。 この関数は現在の coeff_t(int32_t から cpp_int まで)に解析して返すので、 型を切り替えてもソースコードはそのまま動作します。 値が coeff_t の範囲を超える場合は std::out_of_range 例外が送出されます。

qbpp::integer("0")、qbpp::integer("-1") のような小さな値にも使用できます。 ただし文字列から値への変換は実行時に行われるため、int64_t の範囲(±9.2×10¹⁸)に収まる値は 通常の整数リテラル(例: 12345、1234567890123456789LL)を使う方が効率的です。 また、ホットループ内で同じ文字列を繰り返し渡すとパースコストが毎回発生するので、 一度変数に束縛してから使用することをおすすめします:

const auto K = qbpp::integer("1000000000000");  // パースは1回だけ
for (int i = 0; i < n; ++i) f += K * x[i];

注意: 標準の整数リテラル(例: 12345)や LL サフィックス付きの64ビットリテラルは、 値が受け取る側の型に収まる限り、暗黙の型変換によりどの型でもそのまま使用できます。 変数に掛ける数は coeff_t なので、デフォルト(coeff_t が int32_t)で (1LL << 40) * x のように coeff_t に収まらない値を書くと、値は黙って切り詰められます(この例では 0*x になり、コンパイラは -Woverflow の警告を出すだけです)。 そのような係数を使うときは、coeff_t が64ビット以上の型(INTEGER_TYPE_C64E64 など)を選んでください。 qbpp::integer() が必要になるのは、値が int64_t の範囲を超える場合のみです。

128ビット整数の例

以下のプログラムは、64ビット範囲を超える係数を持つ qbpp::Expr オブジェクトを作成します。

#define INTEGER_TYPE_C128E128

#include <qbpp/qbpp.hpp>

int main() {
  auto x = qbpp::var("x");
  auto y = qbpp::var("y");
  auto f = qbpp::integer("12345678901234567890") * x +
           qbpp::integer("98765432109876543210") * y;
  std::cout << "f = " << f << std::endl;
}

このプログラムは以下の出力を生成します。

f = 12345678901234567890*x +98765432109876543210*y

任意精度整数(cpp_int)の例

以下のプログラムは、非常に大きな係数と定数項を持つ qbpp::Expr オブジェクトを作成します。

#define INTEGER_TYPE_CPP_INT

#include <qbpp/qbpp.hpp>

int main() {
  auto x = qbpp::var("x");
  auto f = qbpp::integer("123456789012345678901234567890") * x +
           qbpp::integer("987654321098765432109876543210");
  std::cout << "f = " << f << std::endl;
}

このプログラムは以下の出力を生成します。

f = 987654321098765432109876543210 +123456789012345678901234567890*x

上記2つの例のソースコードの違いは INTEGER_TYPE_* マクロのみで、 qbpp::integer("...") 呼び出しはどちらのビルドでも同じ書き方になる点に注目してください。


Back to top

Page last modified: 2026.10.30.

© 2026 中野浩嗣, 広島大学