# スライスと連結 :::{container} prog-cpp Hi-QUBO は、変数や式のベクトルを操作するためのスライス関数と連結関数を提供しています。 このページでは、実用的な例である**ドメインウォール符号化**と**Dual-Matrix Domain Wall法**を通じてこれらの関数を紹介します。 ::: :::{container} prog-python PyQBPPはPythonスタイルのスライスとconcat()関数による配列操作をサポートしています。 このページでは、**ドメインウォール符号化**と**Dual-Matrix Domain Wall法**を通じてこれらの操作を紹介します。 :::
## スライス 変数や行列のスライス方法について解説します。 :::{container} prog-cpp - スライスしたい軸には`qbpp::slice(s, e)`を使います。`s`番目から`e-1`番目までがスライスされます。 - スライスしない軸には`qbpp::all`を使います。 - 末尾からのインデックスの指定には`qbpp::end`が便利です。 1次元の場合: ```{literalinclude} ../../programFiles/cppPrograms/advanced/slice-connection-program1.cpp :language: cpp :caption: slice-connection-program1.cpp ``` 2次元以上の場合: ```{literalinclude} ../../programFiles/cppPrograms/advanced/slice-connection-program2.cpp :language: cpp :caption: slice-connection-program2.cpp ``` ::: :::{container} prog-python PyQBPPの配列はPython標準のスライス記法をサポートしています。スライスは新しい配列を返します: ```{literalinclude} ../../programFiles/pythonPrograms/advanced/slice-connection-program1.py :language: python :caption: slice-connection-program1.py ``` 多次元配列にはタプルインデックス(NumPyスタイル)を使います: ```{literalinclude} ../../programFiles/pythonPrograms/advanced/slice-connection-program2.py :language: python :caption: slice-connection-program2.py ``` ::: ## 連結 :::{container} prog-cpp 変数や行列の連結方法について解説します。 `qbpp::concat(array1, array2, axis)`で、`array1`に`array2`を軸`axis`の方向に連結します。 ```{literalinclude} ../../programFiles/cppPrograms/advanced/slice-connection-program3.cpp :language: cpp :caption: slice-connection-program3.cpp ``` ::: :::{container} prog-python `concat()` 関数は配列の連結やスカラーの追加を行います: ```{literalinclude} ../../programFiles/pythonPrograms/advanced/slice-connection-program3.py :language: python :caption: slice-connection-program3.py ``` Pythonのアンパック演算子 * を使えば、Array() コンストラクタ内で concat() を置き換えられます: ```{literalinclude} ../../programFiles/pythonPrograms/advanced/slice-connection-program4.py :language: python :caption: slice-connection-program4.py ``` 最外次元ではアンパックの方が明快です。 内側の次元では `concat([...], axis=)` の方がネストを避けられます。 ::: ## ドメインウォール符号化 **ドメインウォール**とは、$1 \cdots 10 \cdots 0$ の形をしたバイナリパターンで、すべての1がすべての0の前に現れます。$n$ 個のバイナリ変数に対して、ドメインウォールパターンは $n+1$ 個あり(全1パターンと全0パターンを含む)、$[0, n]$ の範囲の整数を表現できます。 `concat`、スライス、`sqr` を使って、最小エネルギー解がちょうどドメインウォールパターンとなるQUBO式を構築できます。 プログラムは次のようになります: :::{container} prog-cpp ```{literalinclude} ../../programFiles/cppPrograms/advanced/slice-connection-program4.cpp :language: cpp :caption: slice-connection-program4.cpp ``` ::: :::{container} prog-python ```{literalinclude} ../../programFiles/pythonPrograms/advanced/slice-connection-program5.py :language: python :caption: slice-connection-program5.py ``` ::: ### 仕組み #### ステップ 1: `concat` によるガードビット `concat(1, concat(x, 0))` で拡張ベクトルを構築します: $$ y = (1,~x_0,~x_1,\cdots,~x_n,~0) $$ 先頭のガードビット 1 と末尾の 0 により、ドメインウォールパターンが境界で正しく制約されます。 #### ステップ 2: タプルインデックスによる隣接差分 `y(slice(0, n+1)) - y(slice(end - (n+1), end))` で連続する要素間の差分を計算します: $$ \text{diff}_i = y_i - u_{i+1}~(0 \leq i \leq n) $$ #### ステップ 3: `sqr` と `sum` によるペナルティ `sum(sqr(diff))` は $\displaystyle \sum_{i=0}^n(y_i-y_{i+1})^2$ を計算します。 各 $y_i \in \{0,1\}$ なので、各二乗差分は 0 または 1 です。この和は $u$ における遷移(0 から 1 または 1 から 0 への変化)の回数を数えます。 ドメインウォールパターンは遷移が正確に1回(1 から 0 への変化)なので、最小エネルギーは 1 であり、$n+1$ 個すべてのドメインウォールパターンがこの最小値を達成します。 出力は以下のようになります: ```{include} ../../programFiles/markDown/advanced/slice-connection-program.md :start-after: :end-before: ``` 9つの最適解はすべてドメインウォールパターンで、整数 0 から 8 を表現しています。 ## Dual-Matrix Domain Wall Dual-Matrix Domain Wall 法は、異なるサイズの2つのバイナリ行列を使用して $n \times n$ の置換行列を構築します: x( $(n-1) \times n$ : 列方向ドメインウォール)と y( $n \times (n-1)$ : 行方向ドメインウォール)にガードビットを追加して隣接差分を取ると、それぞれ $n \times n$ のone-hot行列が得られます。これらを一致させることで、各行・各列にちょうど1つの1を持つ置換行列になります。 詳細は https://arxiv.org/abs/2308.01024 を参照してください。 :::{container} prog-cpp ```{literalinclude} ../../programFiles/cppPrograms/advanced/slice-connection-program5.cpp :language: cpp :caption: slice-connection-program5.cpp ``` ::: :::{container} prog-python ```{literalinclude} ../../programFiles/pythonPrograms/advanced/slice-connection-program6.py :language: python :caption: slice-connection-program6.py ``` ::: ### 仕組み #### ステップ1 x は $(n-1) \times n$ 。`concat(1, concat(x, 0, 0), 0)` で `dim=0` に沿ってガード行を追加すると $(n+1) \times n$ となり、各列がドメインウォールになる。`xg(slice(0, n)) - xg(slice(end - n, end))` で隣接差分を取ると $n \times n$ の行列 `x_oh` が得られ、各列がone-hotになります。 #### ステップ2 y は $n \times (n-1)$。`concat(1, concat(y, 0, 1), 1)` で `dim=1` に沿ってガードビットを追加すると $n \times (n+1)$ となり、各行がドメインウォールになる。`yg(all, slice(0, n)) - yg(all, slice(end - n, end))` で隣接差分を取ると $n \times n$ の行列 `y_oh` が得られ、各行がone-hotになります。 #### ステップ3 `x_oh == y_oh` は両方 $n \times n$ なので、転置なしで直接比較できます。一致させると、各行・各列にちょうど1つの1がある置換行列になります。 出力は以下のようになります: ```{include} ../../programFiles/markDown/advanced/slice-connection-program.md :start-after: :end-before: ``` 最適エネルギーは $2n=12$ です。`x_oh` と `y_oh` は一致し、有効な $6 \times 6$ の置換行列を形成しています。 ## タプルインデックス :::{container} prog-cpp 多次元配列からサブ配列を取得するには `Array::operator()` を使います。各引数は軸ごとに次のいずれかです: | 引数 | 意味 | 次元変化 | |---|---|---| | `整数 i` | その軸を `i` に固定 | 軸が削除される | | `qbpp::all` | 全範囲 `:` | 軸を保持 | | `qbpp::slice(from, to)` | 範囲 `[from, to)` | 軸を保持 | | `qbpp::slice(i)` | 単一要素 `[i, i+1)`(`slice(i, i+1)` の短縮形) | 軸を保持 | | `qbpp::end` / `qbpp::end - n` | 軸サイズから計算される位置 | 固定または範囲端 | 指定しなかった末尾の軸は自動的に `qbpp::all` とみなされます。統合 C ABI `view` を 1 回呼ぶだけなので、結果サイズに比例した $O$(`output_size`) のコピーコストになります。 以下がその例です: ```{include} ../../programFiles/markDown/advanced/slice-connection-program.md :start-after: :end-before: ``` ### 行同士の要素毎積: ```{include} ../../programFiles/markDown/advanced/slice-connection-program.md :start-after: :end-before: ``` ### 複数軸の固定と範囲の混在 ```{include} ../../programFiles/markDown/advanced/slice-connection-program.md :start-after: :end-before: ``` ### `end` キーワード(MATLAB 風) ```{include} ../../programFiles/markDown/advanced/slice-connection-program.md :start-after: :end-before: ``` > 注意 `operator[]` は全次元を指定してスカラー値を取得するためのもので、途中の次元で止めてサブ配列を取得することはできません。 サブ配列が必要な場合は `a(...)` 形式のタプルインデックスを使ってください。 ::: :::{container} prog-python 多次元配列から特定の軸を固定値で指定してサブ配列を取得するには、Python のタプルインデックスを使います。整数インデックスはその軸を固定し(次元が減少)、スライス `:` はその軸を保持します: 以下がその例です: ```{include} ../../programFiles/markDown/advanced/slice-connection-program.md :start-after: :end-before: ``` ### 行同士の要素毎積: ```{include} ../../programFiles/markDown/advanced/slice-connection-program.md :start-after: :end-before: ``` ### 複数軸の固定と範囲の混在 ```{include} ../../programFiles/markDown/advanced/slice-connection-program.md :start-after: :end-before: ``` > 注意 Python スライス(例 `x[1:3]`、`x[:n]`)は範囲ベースのスライスで次元数を保持するのに対し、 整数インデックス(例 `x[0]`、`z[1, :, 3]`)は軸固定スライスで次元数が減少します。 :::