# 多次元の変数と式 ## 多次元変数の定義 :::{container} prog-cpp Hi-QUBO は、多次元の変数(`qbpp::Var` オブジェクト)および多次元の整数変数(`qbpp::VarInt` オブジェクト)を、任意の次元数で定義できます。それぞれ `qbpp::var()` および `qbpp::var_int()` を使用します。 基本的な使用方法は次のとおりです。 - `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` 指定した範囲 `[l, u]` と形状 $s1 \times s2 \times \cdots \times sd$ を持つ `qbpp::VarInt` オブジェクトの多次元配列を作成します。 次の Hi-QUBO プログラムは、次元 $2 \times 3 \times 4$ の2値変数と整数変数をそれぞれ作成します。 ```{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-program.md :start-after: :end-before: ``` `x` の各 `qbpp::Var` オブジェクトは `x[i][j][k]` のようにアクセスできます。 `y` の各 `qbpp::VarInt` オブジェクトも `y[i][j][k]` のようにアクセスできますが、内部的には次の3つの2値変数で表現されています。 ```{include} ../../programFiles/markDown/advanced/multi-dimensional-variables-and-expressions-program.md :start-after: :end-before: ``` これらは、指定された整数範囲を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({{a,b},{c,d}})` | `qbpp::Array<2, qbpp::coeff_t>` | 2次元整数定数配列(T 推論) | | `qbpp::array({v1, v2, ...})` | `qbpp::Array<1, qbpp::Expr>` | `int` 値を `Expr` に昇格 | | `qbpp::array({{a,b},{c,d}})` | `qbpp::Array<2, qbpp::Expr>` | 2次元 `int` 値を `Expr` に昇格 | 形状のみを指定する形(`qbpp::array(s1, s2, ...)`)では、次元数からは `T` を 推論できないためテンプレート引数の明示が必要です。 整数定数配列は変数配列との要素ごとの演算に使用できます。以下のプログラムは、$2 \times 2$ の整数定数行列 `c` とバイナリ変数行列 `x` の要素ごとの積を合計します: ```{literalinclude} ../../programFiles/cppPrograms/advanced/multi-dimensional-variables-and-expressions-program6.cpp :language: cpp :caption: multi-dimensional-variables-and-expressions-program6.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-program.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-program2.cpp :language: cpp :caption: multi-dimensional-variables-and-expressions-program2.cpp ``` このプログラムでは、`qbpp::var_int("x", 4) == 0` がプレースホルダとして定数 0 の `VarInt` オブジェクト4個の配列を作成します。 各要素は `0 <= qbpp::var_int() <= max_vals[i]` で個別の範囲に再代入されます。 このテクニックは、各変数の上限が異なる切り出し問題などでよく使われます。 **NOTE** `== 0` の構文は `min_val = max_val = 0`(定数 0 )の `VarInt` を作成するものであり、等号制約を作成するものではありません。 任意の整数定数を使用でき、例えば `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$ の3次元変数配列 `x` と、サイズ $2 \times 3$ の2次元式配列 `f` を定義しています。 三重ループを用いて、各 `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-program3.cpp :language: cpp :caption: multi-dimensional-variables-and-expressions-program3.cpp ``` `simplify_as_binary()` は、多次元の `qbpp::Expr` 配列にも適用できます。 この場合、各要素に対して **要素ごと(element-wise)** に `simplify_as_binary()` が適用されます。 このプログラムは次の出力を生成します。 ```{include} ../../programFiles/markDown/advanced/multi-dimensional-variables-and-expressions-program.md :start-after: :end-before: ``` ## auto 型推論による式配列の作成 `qbpp::Expr` オブジェクトの配列は、明示的に `qbpp::expr()` を呼び出さなくても作成できます。 関数呼び出しや算術演算の結果が配列形状を持つ場合、`auto` 型推論を用いることで、同じ形状を持つ `qbpp::Expr` の配列を定義できます。 次の QUBO++ プログラムでは、`qbpp::Var` オブジェクトの配列 `x` と同じ次元を持つ `qbpp::Expr` 配列 `f` を作成しています。 ```{literalinclude} ../../programFiles/cppPrograms/advanced/multi-dimensional-variables-and-expressions-program4.cpp :language: cpp :caption: multi-dimensional-variables-and-expressions-program4.cpp ``` このプログラムでは、`x` は $2 \times 3$ の `qbpp::Var` 配列として定義されています。 - 式 `x + 1` は $2 \times 3$ の `qbpp::Expr` 配列を生成します。 - それが `auto` 型推論によって `f` に代入されます。 - その後、`x - 2` が要素ごとに `f` に加算されます。 - `simplify_as_binary()` によって各要素が2値変数仮定のもとで簡約されます。 このプログラムの出力は次のとおりです。 ```{include} ../../programFiles/markDown/advanced/multi-dimensional-variables-and-expressions-program.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>` のような完全な型をメンバ宣言で明示する必要があります。 ### `qbpp::Vector` の主なメンバ関数 `qbpp::Vector` は、`std::vector` と互換性を持つ以下のメンバ関数を提供します。 - `size()` : ベクトルの要素数を返します。 - `resize()` : ベクトルの要素数を変更します。 - `reserve()` : ベクトルのためのメモリ領域を確保します。 - `push_back()` : 要素をベクトルの末尾に追加します。 - `emplace_back()` : 要素を構築し、末尾に追加します。 - `empty()` : ベクトルが空であれば `true` を返します。 - `operator[]` : 指定したインデックスの要素を返します。 - `begin()`, `end()` : 要素へアクセス・操作するためのイテレータを返します。 > 注意 — 要素型と演算結果 算術演算はスカラー式と同じ規則で要素型を昇格させます:変数の配列に `1` を足すと式の配列になり、変数の配列同士の要素ごとの積は項の配列になります。一方、`+=`, `-=`, `*=` のような複合代入は左辺の要素型を変えません。したがって変数の配列に `+=` で `1` を足するとコンパイルエラーになります — `auto f = x + 1;` のように新しい式の配列を作って受け取ってください。 ### 要素ごとの演算子 `std::vector` とは異なり、`qbpp::Vector` は次の演算子を**要素ごと(element-wise)**にサポートしています。 - `+` : ベクトル同士、またはベクトルとスカラーの要素ごとの加算 - `-` : ベクトル同士、またはベクトルとスカラーの要素ごとの減算 - `*` : ベクトル同士、またはベクトルとスカラーの要素ごとの乗算 - 単項 `-` : ベクトル内の全要素の符号を反転 さらに、多次元配列は `qbpp::Vector` の入れ子インスタンスとして実装されています。例えば、以下のコードでの `x` のデータ型は `qbpp::Vector>` です: ```{include} ../../programFiles/markDown/advanced/multi-dimensional-variables-and-expressions-program.md :start-after: :end-before: ``` 上で説明した要素ごとの演算は、オペランドが同じ形状を持つ場合に限り、多次元配列でもサポートされます。 `qbpp::Vector` はイテレータをサポートしているため、`auto` 型推論を用いた範囲ベースの `for` ループを以下のように使用できます: ```{literalinclude} ../../programFiles/cppPrograms/advanced/multi-dimensional-variables-and-expressions-program5.cpp :language: cpp :caption: multi-dimensional-variables-and-expressions-program5.cpp ``` 外側の `for` ループでは、`qbpp::Vector>` オブジェクト `f` に含まれる各 `qbpp::Vector` オブジェクトが順に `vec` に参照されます。内側の `for` ループでは、`vec` に含まれる各 `qbpp::Expr` オブジェクトが順に `element` に参照されます。 このプログラムは以下を出力します: ```{include} ../../programFiles/markDown/advanced/multi-dimensional-variables-and-expressions-program.md :start-after: :end-before: ``` ## サブ配列アクセスと操作 多次元配列から行や列などのサブ配列を取得するには、タプルインデックス(`Array::operator()`)を使います。 任意の軸に沿ったスライスや連結については スライス関数と連結関数 を参照してください。 ::: :::{container} prog-python PyQBPP は、多次元の変数(`Var` オブジェクト)および多次元の整数変数(`VarInt` オブジェクト)を、任意の次元数で定義できます。 それぞれ `var()` および `var_int()` を使用します。 基本的な使用方法は次のとおりです。 - `var("name", s1, s2, ..., sd)` 指定した名前`name`と形状 $s1 \times s2 \times \cdots \times sd$ を持つ `Var` オブジェクトの多次元配列を作成します。 - `between(var_int("name", s1, s2, ..., sd), l, u)` 指定した範囲 `[l, u]` と形状 $s1 \times s2 \times \cdots \times sd$ を持つ `VarInt` オブジェクトの多次元配列を作成します。 次の PyQBPP プログラムは、次元 $2 \times 3 \times 4$ の2値変数と整数変数をそれぞれ作成します。 ```{literalinclude} ../../programFiles/pythonPrograms/advanced/multi-dimensional-variables-and-expressions-program1.py :language: python :caption: multi-dimensional-variables-and-expressions-program1.py ``` このプログラムは次の出力を生成します。 ```{include} ../../programFiles/markDown/advanced/multi-dimensional-variables-and-expressions-program.md :start-after: :end-before: ``` `x` の各 `Var` オブジェクトは `x[i][j][k]` のようにアクセスできます。 `y` の各 `VarInt` オブジェクトも `y[i][j][k]` のようにアクセスできますが、内部的には次の3つの2値変数で表現されています。 ```{include} ../../programFiles/markDown/advanced/multi-dimensional-variables-and-expressions-program.md :start-after: :end-before: ``` これらは、指定された整数範囲を2進数でエンコードしたものに対応します。 ## 配列のプロパティ 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-program7.py :language: python :caption: multi-dimensional-variables-and-expressions-program7.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次元の式の配列 | 式の配列 | 整数定数配列は変数配列との要素ごとの演算に使用できます。以下のプログラムは、$2 \times 2$ の整数定数行列 `c` とバイナリ変数行列 `x` の要素ごとの積を合計します: ```{literalinclude} ../../programFiles/pythonPrograms/advanced/multi-dimensional-variables-and-expressions-program8.py :language: python :caption: multi-dimensional-variables-and-expressions-program8.py ``` `c * x` は要素ごとの積を返し、`qbpp.sum` がその全要素を合計して単一の式を返します。このプログラムの出力は以下の通りです: ```{include} ../../programFiles/markDown/advanced/multi-dimensional-variables-and-expressions-program.md :start-after: :end-before: ``` ## 個別の範囲を持つ整数変数配列の作成 多次元整数変数配列を定義する場合、`qbpp.var("name", shape=(...), between=(l, u))` で作成された全要素は同じ範囲 $[l, u]$ を共有します。 しかし実際の問題では、各要素に異なる範囲が必要な場合が多くあります。これを実現するには2つの方法があります。 ### 方法1: プレースホルダ配列 まず `qbpp.var("name", shape=..., equal=val)` で**プレースホルダ配列**を作成し、ループ内で各要素に個別の範囲を割り当てます: ```{literalinclude} ../../programFiles/pythonPrograms/advanced/multi-dimensional-variables-and-expressions-program2.py :language: python :caption: multi-dimensional-variables-and-expressions-program2.py ``` このプログラムでは、`qbpp.var("x", shape=4, equal=0)` がプレースホルダとして定数 0 の `VarInt` オブジェクト4個の配列を作成します。 各要素は `qbpp.constrain(x[i], between=(0, max_vals[i]))` で個別の範囲に再代入されます。`qbpp.constrain()` はプレースホルダから名前を自動的に引き継ぐため、明示的な名前の指定は不要です。 > 注釈 equal= の値は0以外の任意の整数を指定できます。この構文はメモリ上に可変配列を確保し、各要素を個別に再代入できるようにします。 ## 方法2: `between=` にリストを渡す `between` の境界値にPythonリストを渡すことができます。 配列の各要素にリストの対応する範囲が割り当てられます: ```{literalinclude} ../../programFiles/pythonPrograms/advanced/multi-dimensional-variables-and-expressions-program9.py :language: python :caption: multi-dimensional-variables-and-expressions-program9.py ``` これが最も簡潔な方法です。`shape=` で配列の次元を指定し、 `between=` がリストから要素ごとに個別の範囲を割り当てます。 ### 方法3: リスト内包表記と array Python のリスト内包表記を `qbpp.array()` で包む方法もあります: ```{literalinclude} ../../programFiles/pythonPrograms/advanced/multi-dimensional-variables-and-expressions-program3.py :language: python :caption: multi-dimensional-variables-and-expressions-program3.py ``` この方法ではプレースホルダなしに変数を直接作成します。 各変数に明示的な名前(例: `f"x[{i}]"`)を指定する必要があることと、 要素ごとの演算を使用するには結果を `qbpp.array()` で包む必要がある点に注意してください。` ## 多次元式の定義 PyQBPP では、`expr()` を使用して任意の深さの多次元式(`Expr` オブジェクト)を定義できます。 - `expr(shape=(s1, s2, ..., sd))` 形状 $s1 \times s2 \times \cdots \times sd$ を持つ `Expr` の多次元配列を作成します。 次のプログラムでは、形状 $2 \times 3 \times 4$ の3次元変数配列 `x` と、サイズ $2 \times 3$ の2次元式配列 `f` を定義しています。 三重ループを用いて、各 `f[i][j]` に `x[i][j][0]`, `x[i][j][1]`, `x[i][j][2]`, `x[i][j][3]` の和を加算しています。 ```{literalinclude} ../../programFiles/pythonPrograms/advanced/multi-dimensional-variables-and-expressions-program4.py :language: python :caption: multi-dimensional-variables-and-expressions-program4.py ``` `simplify_as_binary()` は、多次元の `Expr` 配列にも適用できます。 この場合、各要素に対して **要素ごと(element-wise)** に `simplify_as_binary()` が適用されます。 このプログラムは次の出力を生成します。 ```{include} ../../programFiles/markDown/advanced/multi-dimensional-variables-and-expressions-program.md :start-after: :end-before: ``` ## 演算による式配列の作成 `Expr` オブジェクトの配列は、明示的に `expr()` を呼び出さなくても作成できます。 算術演算が配列形状の結果を生成する場合、同じ形状の Expr オブジェクトの配列が自動的に作成されます。 ```{literalinclude} ../../programFiles/pythonPrograms/advanced/multi-dimensional-variables-and-expressions-program5.py :language: python :caption: multi-dimensional-variables-and-expressions-program5.py ``` このプログラムの出力は次のとおりです。 ```{include} ../../programFiles/markDown/advanced/multi-dimensional-variables-and-expressions-program.md :start-after: :end-before: ``` ### 多次元配列のイテレーション PyQBPPのベクトルはPythonのイテレーションをサポートしているため、ネストされた for ループが使用できます。 ```{literalinclude} ../../programFiles/pythonPrograms/advanced/multi-dimensional-variables-and-expressions-program6.py :language: python :caption: multi-dimensional-variables-and-expressions-program6.py ``` このプログラムは以下を出力します: ```{include} ../../programFiles/markDown/advanced/multi-dimensional-variables-and-expressions-program.md :start-after: :end-before: ``` ## array と Python の `list` PyQBPP の `array` は Hi-QUBO 共有ライブラリ(`.so`)に裏打ちされた不透明オブジェクトです。 Python の `list` とは異なり、Hi-QUBO の演算に最適化された専用のデータ構造です。 ## Python `list` から array の作成 `qbpp.array()` を使って Python リストを `array` に変換できます: ```{include} ../../programFiles/markDown/advanced/multi-dimensional-variables-and-expressions-program.md :start-after: :end-before: ``` 変換後の `array` は、要素ごとの算術演算(`+`, `-`, `*`, `/`, `~`)、`sum()`、`sqr()`、`simplify()` などの Hi-QUBO 関数を効率的にサポートします。 ## `qbpp.array()` が不要な場合 Python リストが `array` との算術演算で使われる場合、自動的に変換されます。 例えば: ```{include} ../../programFiles/markDown/advanced/multi-dimensional-variables-and-expressions-program.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-program7.py :language: python :caption: multi-dimensional-variables-and-expressions-program7.py ``` 出力は以下の通りです: ```{include} ../../programFiles/markDown/advanced/multi-dimensional-variables-and-expressions-program.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]` 等)を使用してください。 :::