# 変数クラスと式クラス :::{container} prog-cpp ## 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` オブジェクトです. ```{literalinclude} /../programFiles/cppPrograms/advanced/variable-and-expression-types-program1.cpp :language: cpp :caption: variable-and-expression-types-program1.cpp ``` このプログラムは以下の出力を生成します. ```{include} /../programFiles/markDown/advanced/variable-and-expression-types.md :start-after: :end-before: ``` データ型を明示的に指定する場合,プログラムは以下のように書き直せます. ```{literalinclude} /../programFiles/cppPrograms/advanced/variable-and-expression-types-program2.cpp :language: cpp :caption: variable-and-expression-types-program2.cpp ``` `qbpp::Var` オブジェクトは **不変(immutable)** であり,作成後に更新できません. 一方,`qbpp::Term` と `qbpp::Expr` オブジェクトは **可変(mutable)** であり,代入によって更新できます. 例えば,以下のプログラムに示すように,複合代入演算子を使用して `qbpp::Term` と `qbpp::Expr` オブジェクトを更新できます. ```{literalinclude} /../programFiles/cppPrograms/advanced/variable-and-expression-types-program3.cpp :language: cpp :caption: variable-and-expression-types-program3.cpp ``` このプログラムは以下の出力を生成します. ```{include} /../programFiles/markDown/advanced/variable-and-expression-types.md :start-after: :end-before: ``` ### エイリアスとコピー C++ ではデフォルトで **値セマンティクス** が適用されます.`qbpp::Term` や `qbpp::Expr` を別の変数に代入すると深いコピーが行われ,二つの独立した オブジェクトが得られます. ```{include} /../programFiles/markDown/advanced/variable-and-expression-types.md :start-after: :end-before: ``` `f = f + x` と `f += x` は観測上同じ結果になります.どちらも `f` を更新する だけで他のオブジェクトには影響しません.両者の違いは性能だけで,左辺が 一時オブジェクトの場合にはコンパイラが in-place な rvalue オーバーロードを 選択し,不必要なコピーを回避します. Python フロントエンド (PyQBPP) は参照セマンティクスのため別の規則に従います. 詳細は [C++ vs Python](../cppVsPy/cpp-vs-python.md#オブジェクトのコピーとエイリアス) の 対比を参照してください. ほとんどの場合,`qbpp::Term` オブジェクトを明示的に使用する必要はありません. 最大限のパフォーマンス最適化が必要な場合にのみ使用すべきです. ただし,`auto` 型推論により `qbpp::Term` オブジェクトが作成される場合があり,一般的な式を格納できないことに注意してください. 例えば,以下のプログラムは,式が `qbpp::Term` オブジェクトに代入されるため,コンパイルエラーになります. ```{literalinclude} /../programFiles/cppPrograms/advanced/variable-and-expression-types-program4.cpp :language: cpp :caption: variable-and-expression-types-program4.cpp ``` `qbpp::Expr` オブジェクトを意図する場合,以下に示すように **`qbpp::toExpr()`** を使用して明示的に構築できます. ```{literalinclude} /../programFiles/cppPrograms/advanced/variable-and-expression-types-program5.cpp :language: cpp :caption: variable-and-expression-types-program5.cpp ``` このプログラムでは,**`t`** と **`f`** の両方が `qbpp::Expr` オブジェクトであり,一般的な式を格納できます. 特に,`f` は値 `1` の定数項のみを持ち,積の項を持たない `qbpp::Expr` オブジェクトとして作成されます. ## 整数の範囲:coeff_t と energy_t 型エイリアス **`qbpp::coeff_t`** と **`qbpp::energy_t`** が,式内の係数とエネルギー値に使用されるデータ型を表します. `qbpp::energy_t` は `qbpp::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_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_C128E128` | 128ビット整数ソルバー(高精度) | ```{literalinclude} /../programFiles/cppPrograms/advanced/variable-and-expression-types-program6.cpp :language: cpp :caption: variable-and-expression-types-program6.cpp ``` 式の構築と簡約は `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` マクロは,各項の変数の内部格納方式を制御します. 最大次数が事前に分かっている場合,固定長モードを使うとヒープ確保が不要になり性能が向上します: | マクロ | 最大次数 | 説明 | |---|---|---| | `MAXDEG0`(デフォルト) | 無制限 | 可変長(3次以上でヒープ確保) | | `MAXDEG2` | 2 | 固定長,QUBOのみ(ヒープ確保なし,最速) | | `MAXDEG4` | 4 | 固定長,4次まで(ヒープ確保なし) | 使用例 — 型と VarArray モードの両方を指定: ```{literalinclude} /../programFiles/cppPrograms/advanced/variable-and-expression-types-program7.cpp :language: cpp :caption: variable-and-expression-types-program7.cpp ``` 指定されたマクロに基づいて適切なライブラリが実行時に自動的にロードされます. ### 大きな定数: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`)を使う方が効率的です. また,ホットループ内で同じ文字列を繰り返し渡すとパースコストが毎回発生するので, 一度変数に束縛してから使用することをおすすめします: ```{include} /../programFiles/markDown/advanced/variable-and-expression-types.md :start-after: :end-before: ``` > **注意**: > 標準の整数リテラル(例: `12345`)や `LL` サフィックス付きの64ビットリテラルは, > 暗黙の型変換によりどの型でもそのまま使用できます. > `qbpp::integer()` が必要になるのは,値が `int64_t` の範囲を超える場合のみです. ### 128ビット整数の例 以下のプログラムは,64ビット範囲を超える係数を持つ `qbpp::Expr` オブジェクトを作成します. ```{literalinclude} /../programFiles/cppPrograms/advanced/variable-and-expression-types-program8.cpp :language: cpp :caption: variable-and-expression-types-program8.cpp ``` このプログラムは以下の出力を生成します. ```{include} /../programFiles/markDown/advanced/variable-and-expression-types.md :start-after: :end-before: ``` ### 任意精度整数(cpp_int)の例 以下のプログラムは,非常に大きな係数と定数項を持つ `qbpp::Expr` オブジェクトを作成します. ```{literalinclude} /../programFiles/cppPrograms/advanced/variable-and-expression-types-program9.cpp :language: cpp :caption: variable-and-expression-types-program9.cpp ``` このプログラムは以下の出力を生成します. ```{include} /../programFiles/markDown/advanced/variable-and-expression-types.md :start-after: :end-before: ``` 上記2つの例のソースコードの違いは `INTEGER_TYPE_*` マクロのみで, `qbpp::integer("...")` 呼び出しはどちらのビルドでも同じ書き方になる点に注目してください. ::: :::{container} prog-python ## 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`** は式です. ```{literalinclude} /../programFiles/pythonPrograms/advanced/variable-and-expression-types-program1.py :language: python :caption: variable-and-expression-types-program1.py ``` このプログラムは以下の出力を生成します. ```{include} /../programFiles/markDown/advanced/variable-and-expression-types.md :start-after: :end-before: ``` > **注釈** > 式の構築にあたっては,Python の動的型付けが必要な型変換を自動的に行うため, > 中間結果の型を意識する必要はありません.整数・変数・式を自由に混ぜて書けば, > ライブラリが中間結果を適切な型(`int` → `Var` → `Term` → `Expr`)に昇格してくれます. `pyqbpp.Var` オブジェクトは **不変(immutable)** であり,作成後に更新できません. 一方,`pyqbpp.Term` と `pyqbpp.Expr` オブジェクトは **可変(mutable)** であり,複合代入演算子で更新できます. 例えば,以下のプログラムに示すように,複合代入演算子を使用して項と式を更新できます. ```{literalinclude} /../programFiles/pythonPrograms/advanced/variable-and-expression-types-program2.py :language: python :caption: variable-and-expression-types-program2.py ``` このプログラムは以下の出力を生成します. ```{include} /../programFiles/markDown/advanced/variable-and-expression-types.md :start-after: :end-before: ``` > **注釈** > Python では `x = x + 1` のような再束縛(rebind)は元のオブジェクトを変更するのではなく, > 単に `x` という名前を新しいオブジェクトに向け直すだけです. > したがって `pyqbpp.Var` がイミュータブルであっても,`x = qbpp.var("x")` の後に > `x += 1` と書くと,`x` はこっそり新しい `Expr` に rebind されてしまい, > 通常これは意図した挙動ではありません. > `qbpp.var(...)` の結果は変数名として純粋に保ち,累積は別の式変数に対して行うようにしてください. ### エイリアスとコピー `pyqbpp.Term` と `pyqbpp.Expr` は可変なので,Python の可変オブジェクトの標準的な 意味論に従います.すなわち,ある変数を別の変数に代入すると **エイリアス** が 作られ(両方の名前が同じオブジェクトを指す),独立したコピーにはなりません. 複合代入で一方を変更すると,もう一方からもその変更が見えます. ```{literalinclude} /../programFiles/pythonPrograms/advanced/variable-and-expression-types-program3.py :language: python :caption: variable-and-expression-types-program3.py ``` 独立したコピーが欲しい場合は,新しい式を組み立てるか,コンストラクタ `qbpp.Expr(other)` を使ってください(深いコピーになります). ```{include} /../programFiles/markDown/advanced/variable-and-expression-types.md :start-after: :end-before: ``` `pyqbpp.Sol` も同じ規則に従います.`qbpp.Sol(other_sol)` で独立した深いコピーを 作れます. C++ フロントエンド (Hi-QUBO) は値セマンティクスを採用しているため, `Expr g = f;` の時点ですでに独立したコピーが作られます.詳細は [C++ vs Python](../cppVsPy/cpp-vs-python.md#オブジェクトのコピーとエイリアス) の対比を参照してください. ## 式の構築 Python では `Expr` を明示的に構築する必要はありません — 算術演算子が `int` / `Var` / `Term` を必要に応じて自動的に `Expr` に昇格させます.例えば,`2 * x * y` は `Term` ですが,その後に `+=` で他の項を加えると `Expr` に自動昇格します. ```{literalinclude} /../programFiles/pythonPrograms/advanced/variable-and-expression-types-program4.py :language: python :caption: variable-and-expression-types-program4.py ``` このプログラムは以下の出力を生成します. ```{include} /../programFiles/markDown/advanced/variable-and-expression-types.md :start-after: :end-before: ``` ## 整数の範囲:係数型とエネルギー型 係数型は積項の整数係数を,エネルギー型は `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` | 無制限 | 無制限 | 係数と定数は**入力時に範囲チェック**されます: 選択したバリアントの `coeff_t` / `energy_t` に 収まらない整数(例: デフォルトの32ビット係数で `1654971540019 * x`)は,サイレントに 折り返される代わりに `OverflowError`(より広いバリアントの提案付き)になります. ライブラリ内部の演算(`simplify()` やエネルギー評価中の和・積)は性能のため従来どおり 非検査です — 中間値が収まる幅のバリアントを選ぶか,確実を期すなら `pyqbpp.cppint` を 使ってください. ### 実数(double)係数 係数とエネルギーには **`double`**(Python の `float`)も使用できます.次のいずれかのサブモジュールをインポートします. | インポート | 内部ソルバー | |---|---| | `import pyqbpp.d` または `import pyqbpp.double` | 64ビット整数ソルバー | | `import pyqbpp.dc64e64` | 64ビット整数ソルバー | | `import pyqbpp.dc128e128` | 128ビット整数ソルバー(高精度) | ```{literalinclude} /../programFiles/pythonPrograms/advanced/variable-and-expression-types-program5.py :language: python :caption: variable-and-expression-types-program5.py ``` 式は `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`). モードは各項内の変数の内部格納方式を制御します. 最大次数が事前に分かっている場合,固定長モードを使うとヒープ確保が不要になり性能が向上します. | 接尾辞 | 最大次数 | 説明 | |---|---|---| | `m0`(または接尾辞なし) | 無制限 | 可変長(3次以上でヒープ確保) | | `m2` | 2 | 固定長,QUBOのみ(ヒープ確保なし,最速) | | `m4` | 4 | 固定長,4次まで(ヒープ確保なし) | 使用例 — 型と VarArray モードの両方を指定: ```{literalinclude} /../programFiles/pythonPrograms/advanced/variable-and-expression-types-program6.py :language: python :caption: variable-and-expression-types-program6.py ``` 選択したモジュールに基づいて,適切な共有ライブラリがインポート時に自動でロードされます. > **注釈** > 型バリアントはインポート時に選択し,後から変更することはできません. > プログラム内の全ての変数,式,ソルバーが同じ型を使用します. ### 大きな定数 Python では整数リテラルが元々任意精度なので,大きな値をそのまま書くだけで済みます — 例えば `12345678901234567890 * x`. 各リテラルは,演算に参加する時点で現在の `coeff_t` 型へ変換されます. 収まらない場合は例外が送出されます. ホットループ内で非常に大きな整数値の計算(例: `10**12` のようなべき乗)を繰り返す場合は,値を毎回作り直すのではなく,一度変数に束縛してから使用することをおすすめします. ```{include} /../programFiles/markDown/advanced/variable-and-expression-types.md :start-after: :end-before: ``` > **注釈** > 通常の Python 整数リテラルはどのバリアントでもそのまま使用できます. > `qbpp::integer("...")` に相当する関数は Python には存在しません. > Python の `int` は元から任意精度を扱えるためです. ### 128ビット整数の例 以下のプログラムは,64ビット範囲を超える係数を持つ式を作成します. ```{literalinclude} /../programFiles/pythonPrograms/advanced/variable-and-expression-types-program7.py :language: python :caption: variable-and-expression-types-program7.py ``` このプログラムは以下の出力を生成します. ```{include} /../programFiles/markDown/advanced/variable-and-expression-types.md :start-after: :end-before: ``` ### 任意精度整数(cpp_int)の例 以下のプログラムは,非常に大きな係数と定数項を持つ式を作成します. ```{literalinclude} /../programFiles/pythonPrograms/advanced/variable-and-expression-types-program8.py :language: python :caption: variable-and-expression-types-program8.py ``` このプログラムは以下の出力を生成します. ```{include} /../programFiles/markDown/advanced/variable-and-expression-types.md :start-after: :end-before: ``` 128ビット版と `cpp_int` 版のソースコードの違いは `import` 行のみで, 残りのプログラムは下層の整数型が何であれ同一である点に注目してください. :::