# スライス関数と連結関数 :::{container} prog-cpp Hi-QUBO は,変数や式のベクトルを操作するためのスライス関数と連結関数を提供しています. このページでは,実用的な例である**ドメインウォール符号化**を通じてこれらの関数を紹介します. ## ドメインウォール符号化 **ドメインウォール**とは,$1\cdots 1\, 0\cdots 0$ の形をしたバイナリパターンで, すべての1がすべての0の前に現れます. $n$ 個のバイナリ変数に対して,ドメインウォールパターンは正確に $n+1$ 個あり (全1パターンと全0パターンを含む), $[0, n]$ の範囲の整数を表現できます. `concat`,タプルインデックス (`a(slice(...))`),`sqr` を使って, 最小エネルギー解がちょうどドメインウォールパターンとなるQUBO式を構築できます. ## Hi-QUBO プログラム ```{literalinclude} /../programFiles/cppPrograms/advanced/slice-connection-program1.cpp :language: cpp :caption: slice-connection-program1.cpp ``` ### 仕組み **ステップ 1: `concat` によるガードビット** `concat(1, concat(x, 0))` で拡張ベクトルを構築します: $$ y = (1,\; x_0,\; x_1,\; \ldots,\; x_{n-1},\; 0) $$ 先頭のガードビット 1 と末尾の 0 により,ドメインウォールパターンが境界で正しく制約されます. **ステップ 2: タプルインデックスによる隣接差分** `y(slice(0, n+1)) - y(slice(end - (n+1), end))` で連続する要素間の差分を計算します: $$ \text{diff}_i = y_i - y_{i+1} \quad (0 \le i \le n) $$ **ステップ 3: `sqr` と `sum` によるペナルティ** `sum(sqr(diff))` は $\sum_{i=0}^{n} (y_i - y_{i+1})^2$ を計算します. 各 $y_i \in \{0, 1\}$ なので,各二乗差分は 0 または 1 です. この和は $y$ における遷移(0 から 1 または 1 から 0 への変化)の回数を数えます. ドメインウォールパターンは遷移が正確に**1回**(1 から 0 への変化)なので, 最小エネルギーは **1** であり,$n+1$ 個すべてのドメインウォールパターンがこの最小値を達成します. ### 出力 ```{include} /../programFiles/markDown/advanced/slice-connection.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](https://arxiv.org/abs/2308.01024) を参照してください. ```{literalinclude} /../programFiles/cppPrograms/advanced/slice-connection-program2.cpp :language: cpp :caption: slice-connection-program2.cpp ``` ### 仕組み 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.md :start-after: :end-before: ``` 最適エネルギーは $2n = 12$ です.`x_oh` と `y_oh` は一致し,有効な $6 \times 6$ の置換行列を形成しています. ## タプルインデックス `a(...)` 多次元配列からサブ配列を取得するには `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` とみなされます.内部では単一の呼び出しで処理され,コストは結果サイズに比例した **O(output_size)** のコピーコストになります. ### 例 ```{include} /../programFiles/markDown/advanced/slice-connection.md :start-after: :end-before: ``` 行同士の要素毎積: ```{include} /../programFiles/markDown/advanced/slice-connection.md :start-after: :end-before: ``` ### 複数軸の固定と範囲の混在 ```{include} /../programFiles/markDown/advanced/slice-connection.md :start-after: :end-before: ``` ### `end` キーワード(MATLAB 風) ```{include} /../programFiles/markDown/advanced/slice-connection.md :start-after: :end-before: ``` > **注意** > 途中の次元で止めた `operator[]` は軽量な**ビュー**を返します.ビューは表示や要素ごとの演算には使えますが, > `qbpp::sum()` などのテンプレート関数へ直接渡すことはできません. > 独立したサブ配列が必要な場合は `a(...)` 形式のタプルインデックスを使ってください. ::: :::{container} prog-python PyQBPPはPythonスタイルのスライスと`concat()`関数による配列操作をサポートしています. このページでは,**ドメインウォール符号化**と**Dual-Matrix Domain Wall**法を通じてこれらの操作を紹介します. ## スライス PyQBPPの配列はPython標準のスライス記法をサポートしています.スライスは新しい配列を返します: ```{literalinclude} /../programFiles/pythonPrograms/advanced/slice-connection-program1.py :language: python :caption: slice-connection-program1.py ``` 多次元配列にはタプルインデックス(NumPyスタイル)を使います: ```{include} /../programFiles/markDown/advanced/slice-connection.md :start-after: :end-before: ``` Python のスライス構文で,指定した次元に沿って範囲スライスを行えます(次元数は保持されます): ```{include} /../programFiles/markDown/advanced/slice-connection.md :start-after: :end-before: ``` ## 連結 (concat) `concat()` 関数は配列の連結やスカラーの追加を行います: ```{literalinclude} /../programFiles/pythonPrograms/advanced/slice-connection-program2.py :language: python :caption: slice-connection-program2.py ``` ### `*`(アンパック演算子)によるPythonic な代替 Pythonのアンパック演算子 `*` を使えば,`qbpp.array()` ファクトリ内で `concat()` を置き換えられます: ```{include} /../programFiles/markDown/advanced/slice-connection.md :start-after: :end-before: ``` 最外次元ではアンパックの方が明快です. 内側の次元では `concat([...], axis=)` の方がネストを避けられます. ## ドメインウォール符号化 **ドメインウォール**とは,$1\cdots 1\, 0\cdots 0$ の形をしたバイナリパターンで, すべての1がすべての0の前に現れます. $n$ 個のバイナリ変数に対して,ドメインウォールパターンは正確に $n+1$ 個あり (全1パターンと全0パターンを含む), $[0, n]$ の範囲の整数を表現できます. `concat`,Pythonスライス,`sqr` を使って, 最小エネルギー解がちょうどドメインウォールパターンとなるQUBO式を構築できます. ## PyQBPP プログラム ```{literalinclude} /../programFiles/pythonPrograms/advanced/slice-connection-program3.py :language: python :caption: slice-connection-program3.py ``` ### 仕組み **ステップ 1: `concat` によるガードビット** `qbpp.concat([1, x, 0])` で拡張ベクトルを構築します: $$ y = (1,\; x_0,\; x_1,\; \ldots,\; x_{n-1},\; 0) $$ 先頭のガードビット 1 と末尾の 0 により,ドメインウォールパターンが境界で正しく制約されます. **ステップ 2: Pythonスライスによる隣接差分** `y[:n+1] - y[-(n+1):]` で連続する要素間の差分を計算します: $$ \text{diff}_i = y_i - y_{i+1} \quad (0 \le i \le n) $$ **ステップ 3: `sqr` と `sum` によるペナルティ** `qbpp.sum(qbpp.sqr(diff))` は $\sum_{i=0}^{n} (y_i - y_{i+1})^2$ を計算します. 各 $y_i \in \{0, 1\}$ なので,各二乗差分は 0 または 1 です. この和は $y$ における遷移(0 から 1 または 1 から 0 への変化)の回数を数えます. ドメインウォールパターンは遷移が正確に**1回**(1 から 0 への変化)なので, 最小エネルギーは **1** であり,$n+1$ 個すべてのドメインウォールパターンがこの最小値を達成します. ### 出力 ```{include} /../programFiles/markDown/advanced/slice-connection.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](https://arxiv.org/abs/2308.01024) を参照してください. ```{literalinclude} /../programFiles/pythonPrograms/advanced/slice-connection-program4.py :language: python :caption: slice-connection-program4.py ``` ### 仕組み 1. **`x`** は $(n{-}1) \times n$.`qbpp.concat([ones, x, zeros], axis=0)` で `axis=0` に沿ってガード行を追加すると $(n{+}1) \times n$ となり,各列がドメインウォール.`xg[:n] - xg[-n:]`(先頭 $n$ 行と末尾 $n$ 行の差)で $n \times n$ の行列 `x_oh` を得て,各**列**がone-hotになります. 2. **`y`** は $n \times (n{-}1)$.`qbpp.concat([ones, y, zeros], axis=1)` で `axis=1` に沿ってガードビットを追加すると $n \times (n{+}1)$ となり,各行がドメインウォール.`yg[:, :n] - yg[:, -n:]`(先頭 $n$ 列と末尾 $n$ 列の差)で $n \times n$ の行列 `y_oh` を得て,各**行**がone-hotになります. 3. **`x_oh == y_oh`**: 両方 $n \times n$ なので,転置なしで直接比較できます.一致させると,各行・各列にちょうど1つの1がある**置換行列**になります. ### 主要な操作 - **`x[:n]` / `x[-n:]`**: Python スライスで先頭/末尾の要素を取得. - **`x[:, :n]` / `x[:, -n:]`**: タプルインデックスで内側の次元をスライス. - **`qbpp.concat([1, x, 0], axis=0)`**: 全1のガード行を上に追加. - **`concat(1, x, 1)`**(`axis=1`): 各行の先頭に1を追加. ### 出力 ```{include} /../programFiles/markDown/advanced/slice-connection.md :start-after: :end-before: ``` 最適エネルギーは $2n = 12$ です.`x_oh` と `y_oh` は一致し,有効な $6 \times 6$ の置換行列を形成しています. ## 軸固定スライス(タプルインデックス) 多次元配列から特定の軸を**固定値**で指定してサブ配列を取得するには,Python のタプルインデックスを使います.整数インデックスはその軸を固定し(**次元が減少**),スライス `:` はその軸を保持します: ```{include} /../programFiles/markDown/advanced/slice-connection.md :start-after: :end-before: ``` 行同士の要素毎積: ```{include} /../programFiles/markDown/advanced/slice-connection.md :start-after: :end-before: ``` 複数の軸を同時に固定することもできます: ```{include} /../programFiles/markDown/advanced/slice-connection.md :start-after: :end-before: ``` > **注釈** > Python スライス(例 `x[1:3]`,`x[:n]`)は**範囲ベース**のスライスで次元数を保持するのに対し, > 整数インデックス(例 `x[0]`,`z[1, :, 3]`)は**軸固定**スライスで次元数が減少します. :::