# 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 倍にしても収束せず，補正の必要性を裏付ける

## 結論と推奨 (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`）．

## 参照 (References)

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