(edu-practice-heat1d-02)= # 02. MPI 版:領域を分けて解く {ref}`01. 逐次版 ` のプログラムを、$P$ ランクで分担する `heat1d_mpi` に広げます。 領域分割とゴーストセルの交換の考え方は {ref}`第 10 章 `、MPI の関数は {ref}`第 8 章 ` で説明しています。 ## 使うもの 逐次版のものに加えて、次を使います。 | もの | ヘッダ | |---|---| | `miso::Env`(`MPI_Init` と `MPI_Finalize`)、`miso::mpi::rank()`、`mpi::size()`、`mpi::comm()` | `miso/env.hpp` | | `MPI_Sendrecv`、`MPI_Allreduce`、`MPI_Gather`、`MPI_Abort`、`MPI_PROC_NULL` | `mpi.h` | `main` の最初で `Env env(argc, argv);` を作ると MPI が初期化され、`main` を抜けるときに終了処理が行われます。 ## 作るもの ### 各ランクの格子 - 全体のセル数 $N$ をランク数 $P$ で割り、各ランクの物理セル数を $n = N/P$ とします。 $N$ が $P$ で割り切れないときは、ランク 0 が理由を表示し、`MPI_Abort` で全ランクを終了します。 - 配列は `Array1D u(n + 2)` と `u_new(n + 2)` です。 - ランク $p$ の配列の添字 `i`(1〜`n`)は、全体の番号 $j = p\,n + (i - 1)$ にあたります。 初期条件と誤差の計算では、この $j$ から $x_j = (j + 1/2)\Delta x$ を求めます。 - $\Delta x$、$n_\mathrm{steps}$、$\Delta t$、$r$ は全体の $N$ から逐次版と同じ式で求めます。 ### 隣のランク - 左隣は `rank - 1`、右隣は `rank + 1` です。 - ランク 0 の左隣と、ランク $P - 1$ の右隣は `MPI_PROC_NULL` にします。 ### 各ステップ 1. 次の 2 回の `MPI_Sendrecv` でゴーストセルを埋めます。型は `MPI_DOUBLE`、要素数は 1、コミュニケータは `mpi::comm()` です。 | 呼び出し | 送る値 → 相手 | 受け取る場所 ← 相手 | タグ | |---|---|---|---| | 1 回目 | `u[1]` → 左隣 | `u[n+1]` ← 右隣 | 0 | | 2 回目 | `u[n]` → 右隣 | `u[0]` ← 左隣 | 1 | 受信の状態は使わないので、最後の引数には `MPI_STATUS_IGNORE` を渡します。 2. 左隣が `MPI_PROC_NULL` のランクは `u[0] = -u[1]` を、右隣が `MPI_PROC_NULL` のランクは `u[n+1] = -u[n]` を入れます。 3. 逐次版と同じ式で、物理セル(`Range1D{1, n + 1}`)を更新し、`u` と `u_new` を入れ替えます。 ### 誤差と出力 - 各ランクで `reduce` により自分の物理セルの最大誤差を求め、`MPI_Allreduce`(`MPI_MAX`)で全体の最大誤差にします。 - `MPI_Gather` で各ランクの物理セル $n$ 個(`&u[1]` から)をランク 0 に集めます。ランク 0 は $N$ 要素の受信用の配列(`std::vector`)を用意します。 - ランク 0 だけが、逐次版と同じ 3 行(1 行目は `N = 64, P = 2, steps = 1024, r = 0.4000` のように $P$ も表示)を表示し、 結果を `result_N_P

.txt`(たとえば `result_N64_P2.txt`)に書き出します。 ## 確かめること 逐次版を実行したのと同じフォルダで、次を実行します。 ```bash ./build/heat1d_serial 64 for P in 1 2 4; do mpiexec -np $P ./build/heat1d_mpi 64 cmp result_N64_serial.txt result_N64_P$P.txt done ``` 1. **表示**:$P = 1, 2, 4$ のどれでも、表示される 2 つの誤差が逐次版と同じ値になります。 2. **ビット単位の一致**:`cmp` が何も表示しない(ファイルが 1 バイトも違わない)ことを確かめます。 3. **割り切れない分割**:`mpiexec -np 3 ./build/heat1d_mpi 64` で、理由を表示して終了することを確かめます。 4. **3 ランク**:$N = 120$ で $P = 1, 2, 3, 4$ を実行し、すべて逐次版の `result_N120_serial.txt` と一致することを確かめます。 このとき `max error (exact)` は `2.941414e-05` です。 逐次版と一致しないときは、次の順に調べます。 - $P = 1$ でも違うなら、初期条件の $x_j$(全体の番号 $j$ の求め方)、$\Delta t$ の求め方を調べます。 - $P = 1$ では一致し、$P = 2$ で違うなら、`MPI_Sendrecv` の送る値・受け取る場所・相手・タグを、上の表と見比べます。 - 端のセルだけ違うなら、物理境界の値を入れる条件(どのランクが入れるか)を調べます。 ファイルのどの行から違うかは、`diff result_N64_serial.txt result_N64_P2.txt | head` で分かります。 行番号から全体の番号 $j$(行番号 − 1)が分かり、どのランクの、どの端の値がおかしいかを絞り込めます。 ## 次へ進む目安 複数のランク数で逐次版とビット単位で一致したら、{ref}`03. GPU 版 ` へ進みます。 GPU がなくても、課題 3 の CPU 用ビルドまで進められます。