変数クラスと式クラス¶
qbpp::Var、qbpp::Term、qbpp::Expr クラス
Hi-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;
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 + 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 オブジェクトとして作成されます.
整数の範囲:coeff_t と energy_t
型エイリアス qbpp::coeff_t と qbpp::energy_t が,式内の係数とエネルギー値に使用されるデータ型を表します.
qbpp::energy_t は qbpp::Expr オブジェクトの整数定数項のデータ型でもあります.
次の型を選択できます.
型 |
範囲 |
大きな定数の構文 |
|---|---|---|
|
±2.1×10⁹ |
|
|
±9.2×10¹⁸ |
|
|
±1.7×10³⁸ |
|
|
無制限 |
|
型 qbpp::cpp_int は任意桁数の整数を表します.
関数 qbpp::integer("...") は10進文字列を現在の coeff_t 型に解析する統一ヘルパーで,
int32_t から cpp_int までどのビルドでも同じソースコードで使用できます.
デフォルトでは coeff_t は int32_t,energy_t は int64_t です.
デフォルト以外の型を使用するには,ヘッダのインクルード前に以下のマクロの一つを定義します(またはコンパイラフラグ -D... で指定):
マクロ |
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
実数(double)係数
係数とエネルギー値には double も使用できます.ヘッダのインクルード前に次のいずれかのマクロを定義します(またはコンパイラフラグ -D... で指定):
マクロ |
内部ソルバー |
|---|---|
|
64ビット整数ソルバー |
|
64ビット整数ソルバー |
|
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 を返します.sol.has(x[i]) でその変数が残っているか確認できます.simplify_as_binary で相殺して消えた変数も同様です.より広いダイナミックレンジが必要なら DOUBLE_TYPE_C128E128 を使ってください.エネルギー範囲の真のオーバーフローは従来どおりエラーになります.
VarArray モード
MAXDEG マクロは,各項の変数の内部格納方式を制御します.
最大次数が事前に分かっている場合,固定長モードを使うとヒープ確保が不要になり性能が向上します:
マクロ |
最大次数 |
説明 |
|---|---|---|
|
無制限 |
可変長(3次以上でヒープ確保) |
|
2 |
固定長,QUBOのみ(ヒープ確保なし,最速) |
|
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ビットリテラルは, 暗黙の型変換によりどの型でもそのまま使用できます.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("...") 呼び出しはどちらのビルドでも同じ書き方になる点に注目してください.
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 を定数や係数としてそのまま使えます.大きな値を書くためのヘルパーは不要です.
以下のプログラムでは,x と y は変数,t は積の項,f は式です.
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 +2*x*y -x
注釈 式の構築にあたっては,Python の動的型付けが必要な型変換を自動的に行うため, 中間結果の型を意識する必要はありません.整数・変数・式を自由に混ぜて書けば, ライブラリが中間結果を適切な型(
int→Var→Term→Expr)に昇格してくれます.
pyqbpp.Var オブジェクトは 不変(immutable) であり,作成後に更新できません.
一方,pyqbpp.Term と pyqbpp.Expr オブジェクトは 可変(mutable) であり,複合代入演算子で更新できます.
例えば,以下のプログラムに示すように,複合代入演算子を使用して項と式を更新できます.
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 +2*x*y -x
t = 6*x*y*x
f = 1 +2*x*y -x +2*y
注釈 Python では
x = x + 1のような再束縛(rebind)は元のオブジェクトを変更するのではなく, 単にxという名前を新しいオブジェクトに向け直すだけです. したがってpyqbpp.Varがイミュータブルであっても,x = qbpp.var("x")の後にx += 1と書くと,xはこっそり新しいExprに rebind されてしまい, 通常これは意図した挙動ではありません.qbpp.var(...)の結果は変数名として純粋に保ち,累積は別の式変数に対して行うようにしてください.
エイリアスとコピー
pyqbpp.Term と pyqbpp.Expr は可変なので,Python の可変オブジェクトの標準的な
意味論に従います.すなわち,ある変数を別の変数に代入すると エイリアス が
作られ(両方の名前が同じオブジェクトを指す),独立したコピーにはなりません.
複合代入で一方を変更すると,もう一方からもその変更が見えます.
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++ フロントエンド (Hi-QUBO) は値セマンティクスを採用しているため,
Expr g = f; の時点ですでに独立したコピーが作られます.詳細は
C++ vs Python の対比を参照してください.
式の構築
Python では Expr を明示的に構築する必要はありません — 算術演算子が int / Var / Term を必要に応じて自動的に Expr に昇格させます.例えば,2 * x * y は Term ですが,その後に += で他の項を加えると Expr に自動昇格します.
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 +x +2*x*y
f = 2 +x +2*x*y
整数の範囲:係数型とエネルギー型
係数型は積項の整数係数を,エネルギー型は Expr の整数定数項とソルバーが出力するエネルギー値を支配します.
これらには次の型を指定することができます.
型 |
範囲 |
大きな定数の構文 |
|---|---|---|
32ビット |
±2.1×10⁹ |
|
64ビット |
±9.2×10¹⁸ |
|
128ビット |
±1.7×10³⁸ |
|
|
無制限 |
|
Python の整数リテラルは元々任意精度なので,大きな定数を書くための特別なヘルパーは不要です — 単にそのまま整数を書けば済みます.式を構築する際,各整数リテラルは現在の係数型またはエネルギー型に変換されます.値が選択したバリアントの係数型に収まらない場合は例外が送出されます.
デフォルトの import pyqbpp では,係数に 32ビット整数,エネルギーに 64ビット整数(c32e64)を使用します.これは大半の問題に最適な最速の型バリアントです.
デフォルト以外の型を使うには,別のサブモジュールをインポートします.
インポート |
係数 |
エネルギー |
|---|---|---|
|
32ビット |
32ビット |
|
32ビット |
64ビット |
|
32ビット |
64ビット |
|
64ビット |
64ビット |
|
64ビット |
128ビット |
|
128ビット |
128ビット |
|
無制限 |
無制限 |
係数と定数は入力時に範囲チェックされます: 選択したバリアントの coeff_t / energy_t に
収まらない整数(例: デフォルトの32ビット係数で 1654971540019 * x)は,サイレントに
折り返される代わりに OverflowError(より広いバリアントの提案付き)になります.
ライブラリ内部の演算(simplify() やエネルギー評価中の和・積)は性能のため従来どおり
非検査です — 中間値が収まる幅のバリアントを選ぶか,確実を期すなら pyqbpp.cppint を
使ってください.
実数(double)係数
係数とエネルギーには double(Python の float)も使用できます.次のいずれかのサブモジュールをインポートします.
インポート |
内部ソルバー |
|---|---|
|
64ビット整数ソルバー |
|
64ビット整数ソルバー |
|
128ビット整数ソルバー(高精度) |
import pyqbpp.d as qbpp
x = qbpp.var("x")
y = qbpp.var("y")
f = -1.5 * x - 2.5 * y + 4.0 * x * y # 実数(double)係数
式は float で構築されます.求解時には係数が自動的に整数へスケーリング・丸められ,整数ソルバーで解かれ,エネルギーは float で返されます(sol.energy は float).そのため整数バックエンドを意識せずに float のまま扱えます.2進小数の係数(1, 1/2, 1/4, …)は厳密に表現されます.
最大の係数に比べて極端に小さい係数は,スケーリングの精度を下回ると 0 として扱われ,その項は除外されます(エラーではなく短い通知が出ます).項がすべて消えた変数は目的関数に影響しない自由変数となり,解からの読み出し(sol(x),sol(x[i]))は 0 を返します.sol.has(x[i]) でその変数が残っているか確認できます.simplify_as_binary() で相殺して消えた変数も同様です.より広いダイナミックレンジが必要なら import pyqbpp.dc128e128 を使ってください.エネルギー範囲の真のオーバーフローは従来どおりエラーになります.
VarArray モード
各型バリアントには VarArray モード接尾辞も使用できます(例: import pyqbpp.c32e64m4).
モードは各項内の変数の内部格納方式を制御します.
最大次数が事前に分かっている場合,固定長モードを使うとヒープ確保が不要になり性能が向上します.
接尾辞 |
最大次数 |
説明 |
|---|---|---|
|
無制限 |
可変長(3次以上でヒープ確保) |
|
2 |
固定長,QUBOのみ(ヒープ確保なし,最速) |
|
4 |
固定長,4次まで(ヒープ確保なし) |
使用例 — 型と VarArray モードの両方を指定:
import pyqbpp.c32e32m2 as qbpp # 32ビット係数/エネルギー、QUBO専用
import pyqbpp.c32e64m4 as qbpp # デフォルト型、4次まで
import pyqbpp as qbpp # デフォルト(c32e64m0、任意次数)
選択したモジュールに基づいて,適切な共有ライブラリがインポート時に自動でロードされます.
注釈 型バリアントはインポート時に選択し,後から変更することはできません. プログラム内の全ての変数,式,ソルバーが同じ型を使用します.
大きな定数
Python では整数リテラルが元々任意精度なので,大きな値をそのまま書くだけで済みます — 例えば 12345678901234567890 * x.
各リテラルは,演算に参加する時点で現在の coeff_t 型へ変換されます.
収まらない場合は例外が送出されます.
ホットループ内で非常に大きな整数値の計算(例: 10**12 のようなべき乗)を繰り返す場合は,値を毎回作り直すのではなく,一度変数に束縛してから使用することをおすすめします.
K = 10**12 # 一度だけ計算される
for i in range(n):
f += K * x[i]
注釈 通常の Python 整数リテラルはどのバリアントでもそのまま使用できます.
qbpp::integer("...")に相当する関数は Python には存在しません. Python のintは元から任意精度を扱えるためです.
128ビット整数の例
以下のプログラムは,64ビット範囲を超える係数を持つ式を作成します.
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)の例
以下のプログラムは,非常に大きな係数と定数項を持つ式を作成します.
import pyqbpp.cppint as qbpp
x = qbpp.var("x")
f = 123456789012345678901234567890 * x + 987654321098765432109876543210
print("f =", f)
このプログラムは以下の出力を生成します.
f = 987654321098765432109876543210 +123456789012345678901234567890*x
128ビット版と cpp_int 版のソースコードの違いは import 行のみで,
残りのプログラムは下層の整数型が何であれ同一である点に注目してください.