# 多次元の整数,変数および式 :::{container} prog-cpp ## 多次元変数の定義 Hi-QUBOは,関数`qbpp::var()`および`qbpp::var_int()`を使用して,任意の深さの**多次元変数**(`qbpp::Var`の配列)および**多次元整数変数**(整数変数 `qbpp::Expr` の配列)をサポートしています. 基本的な使い方は次のとおりです: - `qbpp::var("name",s1,s2,...,sd)`: 指定された`name`と形状$s1\times s2\times \cdots\times sd$を持つ`qbpp::Var`オブジェクトの配列を作成します. - `l <= qbpp::var_int("name",s1,s2,...,sd) <= u`: 指定された範囲と形状$s1\times s2\times \cdots\times sd$を持つ整数変数 `qbpp::Expr` の配列を作成します. 以下のHi-QUBOプログラムは,それぞれ次元$2\times 3\times 4$のバイナリ変数と整数変数を作成します. ```{literalinclude} /../programFiles/cppPrograms/advanced/multi-dimensional-variables-and-expressions-program1.cpp :language: cpp :caption: multi-dimensional-variables-and-expressions-program1.cpp ``` このプログラムの出力は次のとおりです: ```{include} /../programFiles/markDown/advanced/multi-dimensional-variables-and-expressions.md :start-after: :end-before: ``` **`x`** の型は **`qbpp::Array<3, qbpp::Var>`**(`qbpp::Var` の3次元配列),**`y`** の型は **`qbpp::Array<3, qbpp::Expr>`**(整数変数 `qbpp::Expr` の3次元配列)です. **`x`**内の各`qbpp::Var`オブジェクトは**`x[i][j][k]`**としてアクセスできます. **`y`**内の各整数変数 `qbpp::Expr` は**`y[i][j][k]`**としてアクセスでき, 内部的には以下の3つのバイナリ変数で表現されます: - **`y[i][j][k][0]`** - **`y[i][j][k][1]`** - **`y[i][j][k][2]`** これらは指定された範囲の整数のバイナリエンコーディングに対応しています. ## 配列ファクトリ (`qbpp::array`) **`qbpp::array`** は配列を作成する汎用ファクトリ関数です.要素型 `T` には `qbpp::coeff_t`(整数定数),`qbpp::Var`,`qbpp::Term`,`qbpp::Expr` を指定できます. 初期化子から `T` を推論できる場合はテンプレート引数を省略できます. | 呼び出し形式 | 戻り値 | 説明 | |---|---|---| | `qbpp::array(s1, s2, ..., sd)` | `qbpp::Array` | 形状指定でゼロ初期化された整数配列 | | `qbpp::array(s1, s2, ..., sd)` | `qbpp::Array` | 形状指定でゼロ初期化された式配列(`qbpp::expr(s1, s2, ..., sd)` と等価)| | `qbpp::array({v1, v2, ...})` | `qbpp::Array<1, qbpp::coeff_t>` | 1次元整数定数配列(T 推論)| | `qbpp::array({% raw %}{{a,b},{c,d}}{% endraw %})` | `qbpp::Array<2, qbpp::coeff_t>` | 2次元整数定数配列(T 推論)| | `qbpp::array({v1, v2, ...})` | `qbpp::Array<1, qbpp::Expr>` | int 値を `Expr` に昇格 | | `qbpp::array({% raw %}{{a,b},{c,d}}{% endraw %})` | `qbpp::Array<2, qbpp::Expr>` | 2次元 int 値を `Expr` に昇格 | 形状のみを指定する形(`qbpp::array(s1, s2, ...)`)では,次元数からは `T` を 推論できないためテンプレート引数の明示が必要です. [double フロントエンド](../basic/variables-and-expressions.md#実数double係数)(`DOUBLE_TYPE*`)では `coeff_t` は `double` であり, 定数配列のファクトリにも `double` 値を渡せます — 1次元・2次元の初期化子リスト (`qbpp::array({1.5, 2.5})`)と `std::vector` に対応します.整数フロントエンドで `double` 値を渡すとコンパイル時エラーになります. 整数定数配列は変数配列との要素ごとの演算に使用できます.以下のプログラムは,$2\times 2$ の整数定数行列 `c` とバイナリ変数行列 `x` の要素ごとの積を合計します: ```{literalinclude} /../programFiles/cppPrograms/advanced/multi-dimensional-variables-and-expressions-program2.cpp :language: cpp :caption: multi-dimensional-variables-and-expressions-program2.cpp ``` ここで `c` の型は **`qbpp::Array<2, qbpp::coeff_t>`**(2次元の整数定数配列),`x` の型は **`qbpp::Array<2, qbpp::Var>`** です. `c * x` は要素ごとの積を2次元の項の配列(`qbpp::Array<2, qbpp::Term>`)として返し,`qbpp::sum` がその全要素を合計して単一の `Expr` を返します.このプログラムの出力は以下の通りです: ```{include} /../programFiles/markDown/advanced/multi-dimensional-variables-and-expressions.md :start-after: :end-before: ``` ## 個別の範囲を持つ整数変数配列の作成 多次元整数変数配列を定義する場合,`l <= qbpp::var_int("name", s1, s2, ...) <= u` で作成された全要素は同じ範囲 $[l, u]$ を共有します. しかし実際の問題では,各要素に異なる範囲が必要な場合が多くあります. このような場合,まず **`qbpp::var_int("name", s1, s2, ...) == 0`** で**プレースホルダ配列**を作成し,ループ内で各要素に個別の範囲を割り当てます: ```{literalinclude} /../programFiles/cppPrograms/advanced/multi-dimensional-variables-and-expressions-program3.cpp :language: cpp :caption: multi-dimensional-variables-and-expressions-program3.cpp ``` このプログラムでは,`max_vals` の型は **`qbpp::Array<1, qbpp::coeff_t>`**(1次元の整数定数配列)です. `qbpp::var_int("x", 4) == 0` がプレースホルダとして定数ゼロの整数変数 `qbpp::Expr` 4個の配列を作成するので,`x` の型は **`qbpp::Array<1, qbpp::Expr>`** になります. 各要素は `0 <= qbpp::var_int() <= max_vals[i]` で個別の範囲に再代入されます. このテクニックは,各変数の上限が異なる**[切り出し問題](../example/real/cutting-stock.md)**などでよく使われます. > **注意** > `== 0` の構文は `min_val = max_val = 0`(定数ゼロ)の整数変数を作成するものであり,等号制約を作成するものでは**ありません**. > 任意の整数定数を使用でき,例えば `qbpp::var_int("x", 4) == 5` は定数5のプレースホルダを作成します. ## 多次元式の定義 Hi-QUBOでは,関数`qbpp::expr()`を使用して任意の深さの**多次元式**(`qbpp::Expr`オブジェクト)を定義できます: - **`qbpp::expr(s1,s2,...,sd)`**: 形状$s1\times s2\times \cdots\times sd$の`qbpp::Expr`オブジェクトの多次元配列を作成します. 以下のプログラムは,形状$2\times 3\times 4$の`qbpp::Var`オブジェクトの3次元配列**`x`**と,サイズ$2\times 3$の2次元配列`f`を定義します. 次に,3重forループを使用して,各`f[i][j]`に`x[i][j][0]`,`x[i][j][1]`,`x[i][j][2]`,`x[i][j][3]`の和を蓄積します: ```{literalinclude} /../programFiles/cppPrograms/advanced/multi-dimensional-variables-and-expressions-program4.cpp :language: cpp :caption: multi-dimensional-variables-and-expressions-program4.cpp ``` ここで `x` の型は **`qbpp::Array<3, qbpp::Var>`**,`f` の型は **`qbpp::Array<2, qbpp::Expr>`** です. `simplify_as_binary()`メンバ関数は`qbpp::Expr`オブジェクトの多次元配列に対しても適用できます. このような配列に対して呼び出された場合,各要素に対して個別に(要素ごとに)`simplify_as_binary()`が適用されます. このプログラムの出力は次のとおりです: ```{include} /../programFiles/markDown/advanced/multi-dimensional-variables-and-expressions.md :start-after: :end-before: ``` ## auto型推論による式配列の作成 **`qbpp::Expr`**オブジェクトの配列は,`qbpp::expr()`を明示的に呼び出さずに作成できます. 関数呼び出しや算術演算が配列形状の結果を返す場合,auto型推論を使用して同じ形状の`qbpp::Expr`オブジェクトの配列を定義できます. 以下のHi-QUBOプログラムは,`qbpp::Var`オブジェクトの配列**`x`**と同じ次元の`qbpp::Expr`オブジェクトの配列**`f`**を作成します: ```{literalinclude} /../programFiles/cppPrograms/advanced/multi-dimensional-variables-and-expressions-program5.cpp :language: cpp :caption: multi-dimensional-variables-and-expressions-program5.cpp ``` このプログラムでは,`x`は$2 \times 3$の`qbpp::Var`オブジェクトの配列(型は **`qbpp::Array<2, qbpp::Var>`**)として定義されています. 式`x + 1`は$2 \times 3$の`qbpp::Expr`オブジェクトの配列を生成し,auto型推論を通じて`f`の初期化に使用されるので,`f` の型は **`qbpp::Array<2, qbpp::Expr>`** になります. その後,式`x - 2`が`f`に要素ごとに加算されます. このプログラムの出力は次のとおりです: ```{include} /../programFiles/markDown/advanced/multi-dimensional-variables-and-expressions.md :start-after: :end-before: ``` ## 配列 Hi-QUBO は,次元数と要素型(`qbpp::Var`, `qbpp::Expr`, `qbpp::Term`, または整数係数型)をコンパイル時に決定する多次元配列を提供しています. 実際には配列の型を手書きする必要はほとんどありません.ファクトリ関数と `auto` による型推論が適切な具体化を選んでくれて,要素ごとの `+`, `-`, `*`, 代入などの演算が宣言された要素型と整合しているかをコンパイラが検査します. 配列は `operator[]` のチェーン(例: `x[i][j][k]`)による多次元インデックスアクセスと,要素ごとの算術演算を提供します. 配列は以下のファクトリ関数で作成します(`Dim = d` は次元数): - **`qbpp::var("name", s1, s2, ..., sd)`** → `qbpp::Array`: 多次元のバイナリ変数の配列. - **`qbpp::expr(s1, s2, ..., sd)`** → `qbpp::Array`: ゼロ初期化された多次元の式の配列(`qbpp::array(s1, s2, ..., sd)` の別名). - **`qbpp::array({v1, v2, ...})`** → `qbpp::Array<1, qbpp::coeff_t>`: 1次元の整数定数の配列(T 推論). - **`qbpp::array(s1, s2, ..., sd)`** → `qbpp::Array`: ゼロ初期化された多次元の整数配列.`qbpp::array(...)` で式配列も作成できます(`qbpp::expr(...)` と同じ). - **`l <= qbpp::var_int("name", s1, s2, ..., sd) <= u`** → `qbpp::Array`: 多次元の整数変数の配列. > **注意 — 型を明示的に書く必要がある場合** > ローカル変数であれば `auto` が適切な `qbpp::Array` の具体化を推論するので,型を手書きする必要はほとんどありません. > 例外は **非 `static` なクラスのメンバ変数**で,C++ は非 static メンバに `auto` を許さないため,`qbpp::Array<2, qbpp::Expr>` のような完全な型をメンバ宣言で明示する必要があります. 配列は以下のメンバ関数を提供しています: - **`size()`**: 最外次元のサイズを返します. - **`total()`**: 全要素数を返します. - **`ndim()`**: 次元数(`Dim` と等しい)を返します. - **`shape(d)`**: 次元 `d` のサイズを返します. - **`empty()`**: 配列が空の場合 `true` を返します. - **`operator[]`**: 要素(`Dim == 1` のとき)またはサブ配列(`Dim > 1` のとき)を返します. - **`begin()`** / **`end()`**: range-based `for` ループ用イテレータ. > **注意 — 要素型と演算結果** > 算術演算はスカラー式と同じ規則で要素型を昇格させます:変数の配列に `1` を足すと式の配列になり,変数の配列同士の要素ごとの積は項の配列になります.一方,`+=`, `-=`, `*=` のような複合代入は左辺の要素型を変えません.したがって変数の配列に `+=` で `1` を足すとコンパイルエラーになります — `auto f = x + 1;` のように新しい式の配列を作って受け取ってください. さらに,配列は要素ごとの演算のために以下の演算子をサポートしています: - **`+`**: 2つの配列,または配列とスカラーの要素ごとの加算. - **`-`**: 2つの配列,または配列とスカラーの要素ごとの減算. - **`*`**: 2つの配列,または配列とスカラーの要素ごとの乗算. - 単項 **`-`**: 配列内のすべての要素を否定します. - 単項 **`~`**(変数の配列のみ): 配列内のすべての変数リテラルを否定します. 多次元配列は,形状情報を持つ単一のフラット配列として実装されています. 例えば,以下のコードにおける `x` は shape `(2, 3)` の変数の2次元配列です: ```{include} /../programFiles/markDown/advanced/multi-dimensional-variables-and-expressions.md :start-after: :end-before: ``` 上記の要素ごとの演算は,オペランドが同じ形状を持つ場合にのみ多次元配列でサポートされます. 多次元配列は `operator[]` を使ったインデックスベースの `for` ループでアクセスできます: ```{literalinclude} /../programFiles/cppPrograms/advanced/multi-dimensional-variables-and-expressions-program6.cpp :language: cpp :caption: multi-dimensional-variables-and-expressions-program6.cpp ``` ここで,`f.total()`は全要素数,`f.ndim()`は次元数,`f.shape(d)`は次元`d`のサイズを返します. `f[i][j]`は行`i`,列`j`の要素にアクセスします. このプログラムの出力は次のとおりです: ```{include} /../programFiles/markDown/advanced/multi-dimensional-variables-and-expressions.md :start-after: :end-before: ``` ## サブ配列アクセスと操作 多次元配列から行や列などのサブ配列を取得するには,タプルインデックス(`Array::operator()`)を使います. 任意の軸に沿ったスライスや連結については **[スライス関数と連結関数](../advanced/slice-connection.md)** を参照してください. ::: :::{container} prog-python ## 多次元変数の定義 PyQBPPは,関数 `var()` を使って,任意の深さの**多次元変数**および**多次元整数変数**をサポートしています. 基本的な使い方は以下の通りです. - `var("name", shape=(s1, s2, ..., sd))`: 指定された `name` と形状 $s_1\times s_2\times \cdots\times s_d$ を持つ変数の配列を作成します. - `var("name", shape=(s1, s2, ..., sd), between=(l, u))`: 指定された範囲と形状を持つ整数変数の配列を作成します. 以下のプログラムは $2\times 3\times 4$ の次元を持つバイナリ変数を作成します. ```{literalinclude} /../programFiles/pythonPrograms/advanced/multi-dimensional-variables-and-expressions-program1.py :language: python :caption: multi-dimensional-variables-and-expressions-program1.py ``` {% raw %} **`x`** 内の各変数は **`x[i][j][k]`** としてアクセスできます. {% endraw %} ## 配列のプロパティ PyQBPP の配列は NumPy と同様のプロパティを提供します. | プロパティ | 戻り値 | 説明 | |---|---|---| | `a.shape` | タプル | 各次元のサイズ(例: `(2, 3, 4)`) | | `a.ndim` | int | 次元数 | | `a.size` | int | 全要素数(各次元のサイズの積) | | `len(a)` | int | 最外次元のサイズ(`a.shape[0]` と同じ) | ```{literalinclude} /../programFiles/pythonPrograms/advanced/multi-dimensional-variables-and-expressions-program2.py :language: python :caption: multi-dimensional-variables-and-expressions-program2.py ``` ## 定数・変数・式の配列 Python リストを **`qbpp.array(リスト)`** に渡すと,先頭要素の型から要素型が自動判別されて配列が作成されます: | 呼び出し形式 | 結果 | 説明 | |---|---|---| | `qbpp.array([1, 2, 3])` | 1次元の整数定数の配列 | 整数定数の配列 | | `qbpp.array([[1,2],[3,4]])` | 2次元の整数定数の配列 | 2次元の整数定数配列 | | `qbpp.array([v1, v2])` | 1次元のバイナリ変数の配列 | バイナリ変数の配列 | | `qbpp.array([e1, e2])` | 1次元の式の配列 | 式の配列 | [double フロントエンド](../basic/variables-and-expressions.md#実数double係数)のモジュール(`pyqbpp.d`,`pyqbpp.dc64e64`, `pyqbpp.dc128e128`)では,定数配列は `float` 値を保持し,float のリストも渡せます (`qbpp.array([1.5, 2.5])`).整数バリアントで float 要素を渡すと `TypeError` になります. また **numpy の ndarray**(整数・浮動小数点 `dtype`)を直接渡すこともでき — `qbpp.array(np_matrix)` — ネイティブコードで変換されるため,大きな行列では 入れ子の Python リストよりはるかに高速です.ndarray は要素ごとの演算子の相手 オペランドとしても(`x * W`,`W * x`,`x + W`,`W - x` など),複合代入 (`e += W`,`e -= W`,`e *= W`)でも直接使えます.自動的に変換され, 結果は通常の qbpp 配列になります.唯一サポートされないのは**左辺が ndarray** の複合代入(`W += x`)です.numpy が in-place 更新を拒否するため (float 配列は式を保持できません),代わりに `W = W + x` と書いてください (`W` は qbpp 配列に再束縛されます). 整数定数配列は変数配列との要素ごとの演算に使用できます.以下のプログラムは,$2\times 2$ の整数定数行列 `c` とバイナリ変数行列 `x` の要素ごとの積を合計します: ```{literalinclude} /../programFiles/pythonPrograms/advanced/multi-dimensional-variables-and-expressions-program3.py :language: python :caption: multi-dimensional-variables-and-expressions-program3.py ``` `c * x` は要素ごとの積を返し,`qbpp.sum` がその全要素を合計して単一の式を返します.このプログラムの出力は以下の通りです: ```{include} /../programFiles/markDown/advanced/multi-dimensional-variables-and-expressions.md :start-after: :end-before: ``` ## 個別の範囲を持つ整数変数配列の作成 多次元整数変数配列を定義する場合,`qbpp.var("name", shape=(...), between=(l, u))` で作成された全要素は同じ範囲 $[l, u]$ を共有します. しかし実際の問題では,各要素に異なる範囲が必要な場合が多くあります. これを実現するには3つの方法があります. ### 方法1: プレースホルダ配列 まず **`qbpp.var("name", shape=..., equal=val)`** で**プレースホルダ配列**を作成し,`qbpp.constrain()` で各要素に個別の範囲を割り当てます: ```{literalinclude} /../programFiles/pythonPrograms/advanced/multi-dimensional-variables-and-expressions-program4.py :language: python :caption: multi-dimensional-variables-and-expressions-program4.py ``` ここで,`qbpp.var("x", shape=4, equal=0)` は定数値0で初期化された整数変数プレースホルダ4個の可変配列を作成します. 各要素は `qbpp.var(between=(0, max_vals[i]))` が返す整数変数(無名,自動命名 `{0}`, `{1}`, ...)で個別の範囲に再代入されます. > **注釈** > `equal=` の値は0以外の任意の整数を指定できます.この構文はメモリ上に可変配列を確保し,各要素を個別に再代入できるようにします. ### 方法2: `between=` にリストを渡す `between` の境界値にPythonリストを渡すことができます. 配列の各要素にリストの対応する範囲が割り当てられます: ```{literalinclude} /../programFiles/pythonPrograms/advanced/multi-dimensional-variables-and-expressions-program5.py :language: python :caption: multi-dimensional-variables-and-expressions-program5.py ``` これが最も簡潔な方法です.`shape=` で配列の次元を指定し, `between=` がリストから要素ごとに個別の範囲を割り当てます. ### 方法3: リスト内包表記と array Python のリスト内包表記を `qbpp.array()` で包む方法もあります: ```{literalinclude} /../programFiles/pythonPrograms/advanced/multi-dimensional-variables-and-expressions-program6.py :language: python :caption: multi-dimensional-variables-and-expressions-program6.py ``` この方法ではプレースホルダなしに変数を直接作成します. 各変数に明示的な名前(例: `f"x[{i}]"`)を指定する必要があることと, 要素ごとの演算を使用するには結果を `qbpp.array()` で包む必要がある点に注意してください. ## 多次元式の定義 PyQBPPでは,関数 `expr()` を使って任意の深さの**多次元式**を定義できます. - **`expr(shape=(s1, s2, ..., sd))`**: 形状 $s_1\times s_2\times \cdots\times s_d$ を持つ式の多次元配列を作成します. 以下のプログラムは,形状 $2\times 3\times 4$ の変数の3次元配列 **`x`** と, サイズ $2\times 3$ の2次元配列 `f` を定義します. 次に,ネストされたループを使って,各 `f[i][j]` に `x[i][j][0]` から `x[i][j][3]` までの合計を蓄積します. ```{literalinclude} /../programFiles/pythonPrograms/advanced/multi-dimensional-variables-and-expressions-program7.py :language: python :caption: multi-dimensional-variables-and-expressions-program7.py ``` このプログラムの出力は以下の通りです. ```{include} /../programFiles/markDown/advanced/multi-dimensional-variables-and-expressions.md :start-after: :end-before: ``` ## 演算による式配列の作成 式の配列は,`expr()` を明示的に呼び出さなくても作成できます. 算術演算が配列形状の結果を生成する場合,同じ形状の式の配列が自動的に作成されます. ```{literalinclude} /../programFiles/pythonPrograms/advanced/multi-dimensional-variables-and-expressions-program8.py :language: python :caption: multi-dimensional-variables-and-expressions-program8.py ``` このプログラムの出力は以下の通りです. ```{include} /../programFiles/markDown/advanced/multi-dimensional-variables-and-expressions.md :start-after: :end-before: ``` ## 多次元配列のイテレーション PyQBPPの配列はPythonのイテレーションをサポートしているため,ネストされた for ループが使用できます. ```{literalinclude} /../programFiles/pythonPrograms/advanced/multi-dimensional-variables-and-expressions-program9.py :language: python :caption: multi-dimensional-variables-and-expressions-program9.py ``` このプログラムの出力は以下の通りです. ```{include} /../programFiles/markDown/advanced/multi-dimensional-variables-and-expressions.md :start-after: :end-before: ``` ## array と Python の `list` PyQBPP の array は Hi-QUBO 共有ライブラリ(`.so`)に裏打ちされた不透明オブジェクトです. Python の `list` とは異なり,Hi-QUBO の演算に最適化された専用のデータ構造です. ### Python リストから array の作成 `qbpp.array()` を使って Python リストを array に変換できます: ```{include} /../programFiles/markDown/advanced/multi-dimensional-variables-and-expressions.md :start-after: :end-before: ``` 変換後の array は,要素ごとの算術演算(`+`, `-`, `*`),`sum()` などの Hi-QUBO 関数を効率的にサポートします(`~` は変数の配列のみ,`/`・`sqr()`・`simplify()` は式の配列で使用します). ### `qbpp.array()` が不要な場合 Python リストが array との算術演算で使われる場合,自動的に変換されます. 例えば: ```{include} /../programFiles/markDown/advanced/multi-dimensional-variables-and-expressions.md :start-after: :end-before: ``` この場合,`w` を `qbpp.array()` でラップする必要はありません. ただし,`w` が複数の演算で繰り返し使われる場合は,あらかじめ `qbpp.array()` でラップしておくことで,`list` から array への変換が毎回発生するのを避け,高速化が期待できます. ### 例: `list` と array の動作の違い 以下の例は,Python の `list` と array の違いを示しています: ```{literalinclude} /../programFiles/pythonPrograms/advanced/multi-dimensional-variables-and-expressions-program10.py :language: python :caption: multi-dimensional-variables-and-expressions-program10.py ``` 出力: ```{include} /../programFiles/markDown/advanced/multi-dimensional-variables-and-expressions.md :start-after: :end-before: ``` Python の `list` である `u` では,`2 * u` は**リストの繰り返し**(8要素)になります. array である `w` では,`2 * w` は**要素ごとの乗算**(各要素が2倍)になります. ### Python `list` との主な違い | | array | Python `list` | |---|---|---| | **要素ごとの `+`** | 要素ごとの加算 | リストの連結 | | **要素ごとの `*`** | 要素ごとの乗算 | リストの繰り返し | | **`~x`** | 要素ごとの否定 | TypeError | | **`sum()`** | 全要素の合計を式として返す | Python 組み込みの sum | | **`sqr()`** | 要素ごとの二乗 | 利用不可 | | **`append()`, `pop()`** | 利用不可 | 利用可能 | | **スライス** | `x[1:3]`, `x[:n]`, `x[-n:]` | `x[1:3]` | > **注釈** > array は固定サイズの不透明コンテナです.`append()`,`pop()`,`insert()`,スライス代入などの Python リスト操作は**サポートされていません**. > 部分配列の抽出には Python スライス構文(`x[1:3]`,`x[:n]` 等)を使用してください. :::