# 変数と式 :::{container} prog-cpp ## 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_*` マクロで選択します。 以下のプログラムでは、`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-program.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-program.md :start-after: :end-before: ``` ## エイリアスとコピー C++ ではデフォルトで 値セマンティクス が適用されます。`qbpp::Term` や `qbpp::Expr` を別の変数に代入すると深いコピーが行われ、二つの独立した オブジェクトが得られます。 ```{include} ../../programFiles/markDown/advanced/variable-and-expression-types-program.md :start-after: :end-before: ``` `f = f + x` と `f += x` は観測上同じ結果になります。どちらも `f` を更新する だけで他のオブジェクトには影響しません。両者の違いは性能だけで、左辺が 一時オブジェクトの場合にはコンパイラが in-place な rvalue オーバーロードを 選択し、不必要なコピーを回避します。 Python フロントエンド (PyQBPP) は参照セマンティクスのため別の規則に従います。 詳細は C++ vs Python の 対比を参照してください。 ほとんどの場合、`qbpp::Term` オブジェクトを明示的に使用する必要はありません。最大限の性能最適化が必要な場合にのみ使用すべきです。 ただし、`auto` 型推論によって `qbpp::Term` オブジェクトが生成される場合があることに注意してください。`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` | ## VarArray モード `MAXDEG` マクロは、各項の変数の内部格納方式を制御します。 最大次数が事前に分かっている場合、固定長モードを使うとヒープ確保が不要になり性能が向上します: | マクロ | 最大次数 | 説明 | |---------------------|----------|-------------------------------------------| | `MAXDEG0`(デフォルト) | 無制限 | 可変長(3次以上でヒープ確保) | | `MAXDEG2` | 2 | 固定長、QUBOのみ(ヒープ確保なし、最速) | | `MAXDEG4` | 4 | 固定長、4次まで(ヒープ確保なし) | | `MAXDEG6` | 6 | 固定長、6次まで(ヒープ確保なし) | 使用例 — 型と VarArray モードの両方を指定: ```{include} ../../programFiles/markDown/advanced/variable-and-expression-types-program.md :start-after: :end-before: ``` 指定されたマクロに基づいて適切なライブラリが実行時に自動的にロードされます。 ## 大きな定数: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-program.md :start-after: :end-before: ``` > 注意: 標準の整数リテラル(例: `12345`)や LL サフィックス付きの64ビットリテラルは、 暗黙の型変換によりどの型でもそのまま使用できます。 `qbpp::integer()` が必要になるのは、値が `int64_t` の範囲を超える場合のみです。 ## 128ビット整数の例 例えば、次のプログラムは、非常に大きな係数および定数項を持つ `qbpp::Expr` オブジェクトを生成します。 ```{literalinclude} ../../programFiles/cppPrograms/advanced/variable-and-expression-types-program6.cpp :language: cpp :caption: variable-and-expression-types-program6.cpp ``` このプログラムは、次の出力を生成します。 ```{include} ../../programFiles/markDown/advanced/variable-and-expression-types-program.md :start-after: :end-before: ``` ## 任意精度整数(cpp_int)の例 以下のプログラムは、非常に大きな係数と定数項を持つ `qbpp::Expr` オブジェクトを作成します。 ```{literalinclude} ../../programFiles/cppPrograms/advanced/variable-and-expression-types-program7.cpp :language: cpp :caption: variable-and-expression-types-program7.cpp ``` このプログラムは以下の出力を生成します。 ```{include} ../../programFiles/markDown/advanced/variable-and-expression-types-program.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-program.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-program.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-program.md :start-after: :end-before: ``` `pyqbpp.Sol` も同じ規則に従います。`qbpp.Sol(other_sol)` で独立した深いコピーを作れます。 C++ フロントエンド (`QUBO++`) は値セマンティクスを採用しているため、`Expr g = f;` の時点ですでに独立したコピーが作られます。詳細は `C++ vs Python` の対比を参照してください。 ## 式の構築 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-program.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` | 無制限 | 無制限 | ## VarArray モード 各型バリアントには VarArray モード接尾辞も使用できます(例: `import pyqbpp.c32e64m4`)。 モードは各項内の変数の内部格納方式を制御します。 最大次数が事前に分かっている場合、固定長モードを使うとヒープ確保が不要になり性能が向上します。 | 接尾辞 | 最大次数 | 説明 | |--------|----------|-------------------------------------------| | `m0`(または接尾辞なし) | 無制限 | 可変長(3次以上でヒープ確保) | | `m2` | 2 | 固定長、QUBOのみ(ヒープ確保なし、最速) | | `m4` | 4 | 固定長、4次まで(ヒープ確保なし) | | `m6` | 6 | 固定長、6次まで(ヒープ確保なし) | 使用例 — 型と VarArray モードの両方を指定: ```{include} ../../programFiles/markDown/advanced/variable-and-expression-types-program.md :start-after: :end-before: ``` 選択したモジュールに基づいて、適切な共有ライブラリがインポート時に自動でロードされます。 > 注釈: 型バリアントはインポート時に選択し、後から変更することはできません。 プログラム内の全ての変数、式、ソルバーが同じ型を使用します。 ## 大きな定数 Python では整数リテラルが元々任意精度なので、大きな値をそのまま書くだけで済みます — 例えば `12345678901234567890 * x`。各リテラルは、演算に参加する時点で現在の `coeff_t` 型へ変換されます。収まらない場合は例外が送出されます。 ホットループ内で非常に大きな整数文字列変換を繰り返す場合は、値を毎回作り直すのではなく、一度変数に束縛してから使用することをおすすめします。 ```{include} ../../programFiles/markDown/advanced/variable-and-expression-types-program.md :start-after: :end-before: ``` > 注釈: 通常の Python 整数リテラルはどのバリアントでもそのまま使用できます。 `qbpp::integer("...")` に相当する関数は Python には存在しません。 Python の `int` は元から任意精度を扱えるためです。 ## 128ビット整数の例 以下のプログラムは、64ビット範囲を超える係数を持つ式を作成します。 ```{literalinclude} ../../programFiles/pythonPrograms/advanced/variable-and-expression-types-program5.py :language: python :caption: variable-and-expression-types-program5.py ``` このプログラムは以下の出力を生成します。 ```{include} ../../programFiles/markDown/advanced/variable-and-expression-types-program.md :start-after: :end-before: ``` ## 任意精度整数(cpp_int)の例 以下のプログラムは非常に大きな係数を持つ式を作成します: ```{literalinclude} ../../programFiles/pythonPrograms/advanced/variable-and-expression-types-program6.py :language: python :caption: variable-and-expression-types-program6.py ``` このプログラムは、次の出力を生成します。 ```{include} ../../programFiles/markDown/advanced/variable-and-expression-types-program.md :start-after: :end-before: ``` 128ビット版と `cpp_int` 版のソースコードの違いは `import` 行のみで、 残りのプログラムは下層の整数型が何であれ同一である点に注目してください。 :::