# spp と計算時間・ノイズ・指標の安定性 (Samples per Pixel vs Time, Noise, and Metric Stability)

TEST_18 までのレンダは spp=1024 固定で，大きいキャビティほど生の輝度が低く相対ノイズが
7〜26% に達していた（指標はノイズ補正で救っている）．研究室サーバの GPU で 1 条件 5 秒に
なったので，spp を 256〜8192 で振り，**(1) 計算時間のうち GPU が何秒か，(2) ノイズが
1/√spp で下がるか，(3) 各指標がどの spp で安定するか**を測って今後の標準 spp を決めた
（2026-09-14）．結論: **GPU 時間は spp に厳密に比例し固定費は 0.8 秒．ノイズは 1/√spp 則に
正確に乗る．構造 CoV は 1024 で十分，画像全体 min/max 比を暗い条件で比べるなら 2048〜4096**．

関連:

- 高速化の内訳と `--workers`: [ENV_04 開発コマンド](../02_ENV/ENV_04_開発コマンド.md)「研究室サーバでの実行」
- 指標の定義（構造 CoV・画像全体 min/max 比）: [SPEC_04 評価仕様](../04_SPEC/SPEC_04_評価仕様.md)，[TEST_18](TEST_18_照明配置依存性の球と立方体の比較.md)「均一性の指標」
- 実行記録: [TEST_02_シミュレーション実行記録](TEST_02_シミュレーション実行記録.md)

## 実験条件 (Setup)

- 条件: TEST_18 の 6 条件の YAML を流用（spp と seed だけ書き換え）．球 φ340 の軸上と θ=90°，
  立方体 L=234 の軸上と θ=90°，ノイズが最も大きい球 φ500 θ=90° と立方体 L=340 θ=90°．
  1 灯・板向き・バッフル付き，旧材質（ρ=0.85 blendbsdf），512²，max_depth=16
- spp: 256 / 512 / 1024 / 2048 / 4096 / 8192（2 倍刻み）× seed 2 つ＝72 レンダ．16384 は
  ユーザー都合で未実施（12 レンダ・約 17 分）
- 計測: 段階別の壁時計（YAML 読込 / PLY 書き出し / シーン読込 / `mi.render` / 保存 / 評価）．
  `mi.render` は `dr.sync_thread()` で GPU の完了を待ってから測る（投げっぱなしで短く見える
  ことを防ぐ）．GPU 率 = `mi.render` ÷ 全体
- 指標: 残差ノイズ CoV（生 − 平滑化）・構造 CoV・輪帯平均 min/max・画像全体 min/max
  （ノイズ補正あり／なし）・ノイズ場の極値
- 環境: Newton GPU 0（RTX A6000）・cuda_ad_rgb・mitsuba 3.7.1・`SM_DRJIT_THREADS=16`・逐次実行

## 結果 1 — 計算時間: GPU は spp に比例，固定費は 0.8 秒 (Time)

6 条件 × 2 seed の平均．

| spp | 全体 [s] | GPU（`mi.render`）[s] | CPU 固定費 [s] | GPU の割合 |
| --- | --- | --- | --- | --- |
| 256 | 2.0 | 1.2 | 0.8 | 60% |
| 512 | 3.2 | 2.5 | 0.8 | 76% |
| 1024 | 5.9 | 5.1 | 0.8 | 87% |
| 2048 | 11.1 | 10.3 | 0.8 | 93% |
| 4096 | 20.9 | 20.1 | 0.8 | 96% |
| 8192 | 40.6 | 39.8 | 0.7 | 98% |

- **GPU 時間は spp に厳密に比例**（1024 spp あたり 5.0 秒．条件による差は ±10%．球がやや遅い）
- **固定費は 0.75〜0.8 秒で spp に依らず一定**．内訳は PLY 書き出し 0.34 秒・評価（図なし）0.29 秒・
  保存 0.12 秒・YAML 読込とシーン読込（OptiX の BVH 構築を含む）は各 0.01 秒
- spp ≥ 1024 で固定費は 1 割以下．CPU 側をこれ以上削る意味は薄い．256 では 4 割を占めるので，
  低 spp を多数回す用途なら筐体メッシュの PLY キャッシュが効く

## 結果 2 — ノイズは 1/√spp 則に正確に乗る (Noise)

| 条件 | 生 mean | ノイズ CoV（1024） | ノイズ × √spp（6 段階の範囲） |
| --- | --- | --- | --- |
| 球 φ340 軸上 | 0.355 | 0.092 | 2.93〜2.99 |
| 球 φ340 θ=90° | 0.323 | 0.145 | 4.63〜4.67 |
| 立方体 L=234 軸上 | 0.387 | 0.104 | 3.30〜3.32 |
| 立方体 L=234 θ=90° | 0.356 | 0.156 | 4.97〜5.08 |
| 球 φ500 θ=90° | 0.153 | 0.189 | 5.84〜6.05 |
| 立方体 L=340 θ=90° | 0.174 | 0.200 | 6.23〜6.38 |

- ノイズ × √spp は全条件で ±1〜2% 以内で一定＝**MC ノイズだけで，レンダ側に別のノイズ源は無い**
- ノイズ量は生の輝度で決まる（暗いほど大きい）が，同じ明るさでも軸上より θ=90° が 1.5 倍大きい
  （オフ軸は一次照射キャップの経路分散が大きい）．大キャビティを球 φ340 軸上と同じノイズにするには
  spp を 4〜5 倍要る
- 画像全体 min/max のノイズ補正量（ノイズ場の max − min）も 1/√spp で減り，8192 で平均の 1% 以下

## 結果 3 — 指標の安定性 (Metric Stability)

`spp_noise.png` 下段．誤差棒は seed 2 つの差の半分．

| 指標 | 1024 での状態 | 安定する spp |
| --- | --- | --- |
| 構造 CoV | 256→1024 で 5〜15% 低下後ほぼ一定．seed 差 ≤0.0005 | **1024 で十分**（8192 との差 ≤0.002） |
| 画像全体 min/max（補正あり） | 明るい条件は安定（seed 差 0.003）．暗い φ500・L=340 は 0.93〜0.94 で seed 差 0.01 | **2048〜4096**（暗い条件が 0.95〜0.96 に収束し seed 差 0.002） |
| 画像全体 min/max（補正なし） | どの条件も spp とともに上昇し続ける | 8192 でも収束しない（ノイズ補正は必須） |
| 輪帯平均 min/max | θ=90° で 0.97〜0.99 に張り付き，seed 差 0.003〜0.01 | spp では改善しない（方位平均の問題） |

- **spp=1024 は構造 CoV には十分だが，画像全体 min/max 比は暗い条件で 0.01〜0.02 過小評価**していた
  （TEST_18 の min の値のうち球 φ500・立方体 L=340 はその分だけ低めに出ている．順位は変わらない）
- 補正なしの平滑化 min/max は spp を 32 倍にしても収束せず，補正の必要性を裏付ける

## 追試 — LED を軸上にそろえた 6 形状での計算時間 (Follow-up: Six Shapes)

発表スライドで「6 種類の形状の平均」と書くと，上の 6 条件は実際には 4 形状 × LED 位置 2 通り
（θ=0°/90°）の混在なので説明と合わない．そこで LED を θ=0°（軸上）にそろえ，形状だけを 6 種類に
変えて計算時間を測り直した（2026-10-05）．

- 形状: 球 φ270・φ340・φ500（r=135/170/250）+ 立方体 L=187・L=234・L=340．上下で同じ内面積の組
  （TEST_18 の対応）．φ は球の内径，L は立方体の一辺 [mm]
- それ以外（1 灯・板向き・バッフル付き・旧材質・512²・max_depth=16・spp 256〜8192 × seed 2・
  Newton GPU 0・逐次）は本編と同じ．72 レンダ
- `sweep_spp_noise.py --set shapes6` で実行．出力は `output/spp_noise_shapes6/`（本編の結果は上書きしない）

平均±標準偏差（n=12＝6 形状 × seed 2）．

| spp | 全体 [s] | GPU（`mi.render`）[s] | CPU 固定費 [s] | GPU の割合 |
| --- | --- | --- | --- | --- |
| 256 | 1.9±0.3 | 1.2±0.1 | 0.8±0.2 | 60% |
| 512 | 3.1±0.2 | 2.4±0.2 | 0.7±0.1 | 77% |
| 1024 | 5.7±0.5 | 5.0±0.4 | 0.7±0.1 | 88% |
| 2048 | 10.7±1.1 | 10.0±1.0 | 0.7±0.1 | 93% |
| 4096 | 20.4±1.9 | 19.7±1.9 | 0.7±0.1 | 97% |
| 8192 | 40.1±3.2 | 39.4±3.1 | 0.7±0.1 | 98% |

- **本編の結論はそのまま成り立つ**: GPU 時間は spp に比例（1024 で 5.0 秒），固定費は 0.7〜0.8 秒で一定
- **GPU 時間の標準偏差は平均の 8〜10%**．ばらつきのほとんどは球と立方体の差で，**球が立方体より
  15〜20% 遅い**（1024 で球 5.3〜5.5 秒・立方体 4.6〜4.7 秒）．同じ形状ならサイズを変えても時間は
  ほぼ変わらない（立方体 L=187〜340 で 4.58〜4.67 秒）．球のメッシュの三角形が多いことが一因と考えられる（未検証）
- スライド用に 6 形状の 3D 配置図（`shapes6_geometry_panel.png`）とレンダ結果（`shapes6_render_panel.png`，
  spp=8192）を 2×3 で並べた図を作った．配置図は各形状が自動で拡大されるので，サイズの違いはラベルで示す

## 結論と推奨 (Conclusions)

- **標準 spp**: 構造 CoV・方位フーリエ・重心で比べる実験は **1024** のまま（5.9 秒/条件）．画像全体
  min/max 比を暗い条件（生 mean < 0.2）まで含めて比べる実験は **2048〜4096**（11〜21 秒/条件．
  255 条件で 50〜90 分）
- **CPU 側の最適化はここまで**．固定費 0.8 秒は spp ≥ 1024 で 1 割以下
- ノイズは 1/√spp 則に乗るので，必要な spp は「目標ノイズ ÷ 1024 でのノイズ」の 2 乗で見積もれる

## 限界・注意点 (Limitations)

- 16384 は未実施．傾向は 8192 までで単調に収束しているため結論は変わらない見込み
- nvidia-smi のサンプリングは，ジョブ再起動時の `pkill` がサンプラー自身を止めたため記録が無い．
  GPU 時間は `dr.sync_thread()` で同期した実測で取れているので結論には影響しない
- 条件は 6 つ（1 灯・板向き）．多灯やバッフル無しは未測定だが，ノイズは生の輝度で決まるので
  同じ関係が成り立つはず
- 時間は逐次実行の値．`--workers 2` では 1 条件あたり 1 割ほど短くなる（ENV_04）

## 失敗パターン・方法論の学び (Pitfalls and Methodology)

- **`mi.render` の時間は同期してから測る**．cuda variant では非同期に GPU へ投げられるので，
  `dr.eval` + `dr.sync_thread()` を挟まないと数十 ms に見える
- **サーバへの push は `--only scripts` だけだと足りないことがある**．新しい src モジュール
  （`evaluation/fullimage.py`）を使うスクリプトは，全体を push してから回す
- **併走プロセスの `pkill -f` は再起動時に自分も殺す**．サンプラーを再起動するなら先に止めてから
  起動する順序を守るか，PID ファイルで管理する

## 再現コマンド (Reproduction)

```bash
# YAML 生成（TEST_18 の _tmp_led_placement を流用するので先に sweep_led_placement.py --prepare-only）
uv run python scripts/sweep_spp_noise.py --prepare-only

# サーバで実行（GPU 0）．spp を絞るなら --spps 256 512 1024
uv run sm-remote push
uv run sm-remote run --name spp --gpu 0 --threads 16 -- python scripts/sweep_spp_noise.py
uv run sm-remote pull spp_noise_sweep

# 解析（図と表）
uv run python scripts/build_spp_noise_comparison.py
```

出力: `output/spp_noise_sweep/`（レンダ別 72 ディレクトリ + `summary.csv` + `spp_summary_table.csv` +
`spp_noise.png`）．

追試（6 形状）:

```bash
# YAML 生成（本編と同じく _tmp_led_placement が前提）
uv run python scripts/sweep_spp_noise.py --set shapes6 --prepare-only

# サーバで実行（_tmp は push 対象外なので --include で送る）
uv run sm-remote push --include configs/_tmp_led_placement --include configs/_tmp_spp_noise_shapes6
uv run sm-remote run --name shapes6 --gpu 0 --threads 16 -- python scripts/sweep_spp_noise.py --set shapes6 --spps 256 512 1024 2048 4096 8192
uv run sm-remote pull spp_noise_shapes6

# 配置図の撮影（6 形状．c = sphere_d270 sphere_d340 sphere_d500 cube_L187 cube_L234 cube_L340）
uv run sm-visualize --scene configs/_tmp_spp_noise_shapes6/scene_${c}_th000_spp1024_s0.yaml --no-bake --style slide --screenshot output/spp_noise_shapes6/geometry/${c}.png

# 表（平均±標準偏差）とパネル図
uv run python scripts/build_spp_shapes6_summary.py
```

出力: `output/spp_noise_shapes6/`（レンダ別 72 ディレクトリ + `summary.csv` + `spp_time_table_mean_sd.csv` +
`shapes6_geometry_panel.png` + `shapes6_render_panel.png` + `geometry/`）．

## 参照 (References)

- [TEST_18_照明配置依存性の球と立方体の比較](TEST_18_照明配置依存性の球と立方体の比較.md): 流用した条件と指標の定義
- [SPEC_04_評価仕様](../04_SPEC/SPEC_04_評価仕様.md): 画像全体 min/max 比の定義と注意
- [ENV_04_開発コマンド](../02_ENV/ENV_04_開発コマンド.md): 高速化の内訳と `--workers`
