変数と式

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要素)と動的確保の組み合わせで格納し、次数に上限なく任意の高次項を扱うことができます。

  • qbpp::Expr
    整数定数項と、0個以上の qbpp::Term オブジェクトから構成される展開済み式を表します。整数定数項のデータ型は qbpp::energy_t(デフォルトは int64_t)で、同じく INTEGER_TYPE_* マクロで選択します。

以下のプログラムでは、xyqbpp::Var オブジェクト、tqbpp::Term オブジェクト、fqbpp::Expr オブジェクトです。

variable-and-expression-types-program1.cpp
#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 -x +2*x*y

データ型を明示的に指定する場合は、次のように書き換えることができます。

variable-and-expression-types-program2.cpp
#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 オブジェクトを更新できます。

variable-and-expression-types-program3.cpp
#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 -x +2*x*y
t = 6*x*y*x
f = 1 -x +2*x*y +2*y

エイリアスとコピー

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

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

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

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

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

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

variable-and-expression-types-program4.cpp
#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() を用いて明示的に構築できます。

variable-and-expression-types-program5.cpp
#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;
}

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

整数の範囲:coeff_t と energy_t

型エイリアス qbpp::coeff_tqbpp::energy_t が、式内の係数とエネルギー値に使用されるデータ型を表します。 qbpp::energy_tqbpp::Expr オブジェクトの整数定数項のデータ型でもあります。 次の型を選択できます。

範囲

大きな定数の構文

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_tint32_tenergy_tint64_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

VarArray モード

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

マクロ

最大次数

説明

MAXDEG0(デフォルト)

無制限

可変長(3次以上でヒープ確保)

MAXDEG2

2

固定長、QUBOのみ(ヒープ確保なし、最速)

MAXDEG4

4

固定長、4次まで(ヒープ確保なし)

MAXDEG6

6

固定長、6次まで(ヒープ確保なし)

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

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

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

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

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

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

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

注意: 標準の整数リテラル(例: 12345)や LL サフィックス付きの64ビットリテラルは、 暗黙の型変換によりどの型でもそのまま使用できます。 qbpp::integer() が必要になるのは、値が int64_t の範囲を超える場合のみです。

128ビット整数の例

例えば、次のプログラムは、非常に大きな係数および定数項を持つ qbpp::Expr オブジェクトを生成します。

variable-and-expression-types-program6.cpp
#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 オブジェクトを作成します。

variable-and-expression-types-program7.cpp
#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("...") 呼び出しはどちらのビルドでも同じ書き方になる点に注目してください。

pyqbpp.Var、pyqbpp.Term、pyqbpp.Expr クラス

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

  • pyqbpp.Var: 変数をシンボリックに表現し、表示用の文字列が関連付けられます。内部的には32ビット符号なし整数が識別子として使用されます。

  • pyqbpp.Term: 整数係数と1つ以上の変数からなる積の項を表現します。整数係数のデータ型は選択した型バリアントの係数型(デフォルト: 32ビット)によって決まります。各項は変数を静的配列(インラインバッファ2要素)と動的確保の組み合わせで格納し、次数に上限なく任意の高次項を扱うことができます。

  • pyqbpp.Expr: 整数定数項と0個以上の積項からなる展開された式を表現します。整数定数項のデータ型は選択した型バリアントのエネルギー型(デフォルト: 64ビット)によって決まります。演算のたびに自動的に展開され、常に積項の和(積和形)として内部に保持されます(例: (x + 1) * (y + 2) はその場で 2 + x*y + 2*x + y に展開される)。

加えて、Python の組み込み int を定数や係数としてそのまま使えます。大きな値を書くためのヘルパーは不要です。

以下のプログラムでは、xy は変数、t は積の項、f は式です。

variable-and-expression-types-program1.py
import pyqbpp as qbpp

x = qbpp.var("x")
y = qbpp.var("y")
t = 2 * x * y
f = t - x + 1

print("x =", x)
print("y =", y)
print("t =", t)
print("f =", f)

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

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

注釈: 式の構築にあたっては、Python の動的型付けが必要な型変換を自動的に行うため、中間結果の型を意識する必要はありません。整数・変数・式を自由に混ぜて書けば、ライブラリが中間結果を適切な型(int Var Term Expr)に昇格してくれます。

pyqbpp.Var オブジェクトは 不変(immutable) であり、作成後に更新できません。一方、pyqbpp.Termpyqbpp.Expr オブジェクトは 可変(mutable) であり、複合代入演算子で更新できます。

例えば、以下のプログラムに示すように、複合代入演算子を使用して項と式を更新できます。

variable-and-expression-types-program2.py
import pyqbpp as qbpp

x = qbpp.var("x")
y = qbpp.var("y")
t = 2 * x * y
f = t - x + 1

print("t =", t)
print("f =", f)

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

print("t =", t)
print("f =", f)

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

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

注釈: Python では x = x + 1 のような再束縛(rebind)は元のオブジェクトを変更するのではなく、単に x という名前を新しいオブジェクトに向け直すだけです。したがって pyqbpp.Var がイミュータブルであっても、x = qbpp.var("x") の後に x += 1 と書くと、x はこっそり新しい Expr に rebind されてしまい、通常これは意図した挙動ではありません。qbpp.var(...) の結果は変数名として純粋に保ち、累積は別の式変数に対して行うようにしてください。

エイリアスとコピー

pyqbpp.Termpyqbpp.Expr は可変なので、Python の可変オブジェクトの標準的な 意味論に従います。すなわち、ある変数を別の変数に代入すると エイリアス が 作られ(両方の名前が同じオブジェクトを指す)、独立したコピーにはなりません。 複合代入で一方を変更すると、もう一方からもその変更が見えます。

variable-and-expression-types-program3.py
import pyqbpp as qbpp

x = qbpp.var("x")
y = qbpp.var("y")
f = qbpp.expr() + x
g = f               # エイリアス — f と g は同じ Expr を指す
f += y              # f が in-place で変更される。g からも同じ変更が見える
print("f =", f)     # f = x +y
print("g =", g)     # g = x +y   (連動して更新される)

独立したコピーが欲しい場合は、新しい式を組み立てるか、コンストラクタ qbpp.Expr(other) を使ってください(深いコピーになります)。

g = f + 0           # 新しい Expr
# または
g = qbpp.Expr(f)    # コンストラクタ経由の深いコピー
f += y              # f だけ変更され、g は影響を受けない

pyqbpp.Sol も同じ規則に従います。qbpp.Sol(other_sol) で独立した深いコピーを作れます。 C++ フロントエンド (QUBO++) は値セマンティクスを採用しているため、Expr g = f; の時点ですでに独立したコピーが作られます。詳細は C++ vs Python の対比を参照してください。

式の構築

Python では Expr を明示的に構築する必要はありません — 算術演算子が int / Var / Term を必要に応じて自動的に Expr に昇格させます。例えば、2 * x * yTerm ですが、その後に += で他の項を加えると Expr に自動昇格します。

variable-and-expression-types-program4.py
import pyqbpp as qbpp

x = qbpp.var("x")
y = qbpp.var("y")

t = 2 * x * y   # Term
t += x + 1      # 自動的に Expr に昇格
f = 1           # int
f += t          # 同じく Expr に昇格

print("t =", t)
print("f =", f)

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

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

整数の範囲:係数型とエネルギー型

係数型は積項の整数係数を、エネルギー型は Expr の整数定数項とソルバーが出力するエネルギー値を支配します。 これらには次の型を指定することができます。

範囲

大きな定数の構文

32ビット

±2.1×10⁹

12345(Python int リテラル)

64ビット

±9.2×10¹⁸

1234567890123456789

128ビット

±1.7×10³⁸

12345678901234567890

cpp_int

無制限

12345678901234567890...

Python の整数リテラルは元々任意精度なので、大きな定数を書くための特別なヘルパーは不要です — 単にそのまま整数を書けば済みます。式を構築する際、各整数リテラルは現在の係数型またはエネルギー型に変換されます。値が選択したバリアントの係数型に収まらない場合は例外が送出されます。

デフォルトの import pyqbpp では、係数に 32ビット整数、エネルギーに 64ビット整数(c32e64)を使用します。これは大半の問題に最適な最速の型バリアントです。 デフォルト以外の型を使うには、別のサブモジュールをインポートします。

インポート

係数

エネルギー

import pyqbpp.c32e32

32ビット

32ビット

import pyqbpp(デフォルト)

32ビット

64ビット

import pyqbpp.c32e64

32ビット

64ビット

import pyqbpp.c64e64

64ビット

64ビット

import pyqbpp.c64e128

64ビット

128ビット

import pyqbpp.c128e128

128ビット

128ビット

import pyqbpp.cppint

無制限

無制限

VarArray モード

各型バリアントには VarArray モード接尾辞も使用できます(例: import pyqbpp.c32e64m4)。 モードは各項内の変数の内部格納方式を制御します。 最大次数が事前に分かっている場合、固定長モードを使うとヒープ確保が不要になり性能が向上します。

接尾辞

最大次数

説明

m0(または接尾辞なし)

無制限

可変長(3次以上でヒープ確保)

m2

2

固定長、QUBOのみ(ヒープ確保なし、最速)

m4

4

固定長、4次まで(ヒープ確保なし)

m6

6

固定長、6次まで(ヒープ確保なし)

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

import pyqbpp.c32e32m2 as qbpp   # 32ビット係数/エネルギー、QUBO専用
import pyqbpp.c32e64m4 as qbpp   # デフォルト型、4次まで
import pyqbpp as qbpp             # デフォルト(c32e64m0、任意次数)

選択したモジュールに基づいて、適切な共有ライブラリがインポート時に自動でロードされます。

注釈: 型バリアントはインポート時に選択し、後から変更することはできません。 プログラム内の全ての変数、式、ソルバーが同じ型を使用します。

大きな定数

Python では整数リテラルが元々任意精度なので、大きな値をそのまま書くだけで済みます — 例えば 12345678901234567890 * x。各リテラルは、演算に参加する時点で現在の coeff_t 型へ変換されます。収まらない場合は例外が送出されます。

ホットループ内で非常に大きな整数文字列変換を繰り返す場合は、値を毎回作り直すのではなく、一度変数に束縛してから使用することをおすすめします。

K = 10**12   # 一度だけ計算される
for i in range(n):
    f += K * x[i]

注釈: 通常の Python 整数リテラルはどのバリアントでもそのまま使用できます。 qbpp::integer("...") に相当する関数は Python には存在しません。 Python の int は元から任意精度を扱えるためです。

128ビット整数の例

以下のプログラムは、64ビット範囲を超える係数を持つ式を作成します。

variable-and-expression-types-program5.py
import pyqbpp.c128e128 as qbpp

x = qbpp.var("x")
y = qbpp.var("y")
f = 12345678901234567890 * x + 98765432109876543210 * y
print("f =", f)

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

f = 12345678901234567890*x +98765432109876543210*y

任意精度整数(cpp_int)の例

以下のプログラムは非常に大きな係数を持つ式を作成します:

variable-and-expression-types-program6.py
import pyqbpp.cppint as qbpp

x = qbpp.var("x")
f = 123456789012345678901234567890 * x + 987654321098765432109876543210
print("f =", f)

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

f = 987654321098765432109876543210 +123456789012345678901234567890*x

128ビット版と cpp_int 版のソースコードの違いは import 行のみで、 残りのプログラムは下層の整数型が何であれ同一である点に注目してください。