# 可視化

数値は*どれだけ*かを教え、絵は*どこで・なぜ*かを教えます。本章は SCF の密度と軌道を、確認できる画像 — 軌道や
実空間場の 3 次元等値面、2 次元プロット、収束曲線 — に変えます。すべてノートブックの中から、**`qc.view`** 層を
通じて行えます。

:::{tip} 可視化に必要な追加パッケージ
3 次元ビューアは **py3Dmol**、静止画エクスポートは **kaleido**、2 次元プロットは **matplotlib** を使います。
必要なものをプロジェクト venv に入れます:

```bash
uv add py3dmol kaleido --project "$UV_PROJECT"   # 3D ビューア + 静止 PNG エクスポート
```

（matplotlib は通常、基盤の科学スタックに含まれます。）各ビューアの入口はメソッド（`mychk.view3d(...)`）と
関数（`qc.view3d(mychk, ...)`）の両方で存在します。
:::

## 軌道テーブル

何かを描く前に、軌道を一覧して何を要求するか把握します。**`mychk.orbitals`** はピッキング用テーブル — 番号・
占有・エネルギー — をフロンティア軌道を印つきで表示し、ノートブックでは HTML テーブルとして描画します:

```python
import qc
water = "O 0 0 0.117; H 0 0.757 -0.469; H 0 -0.757 -0.469"
m = qc.chk.new(atom=water, ao="cc-pvdz", unit="angstrom").scf(ref="r").run()

m.orbitals
#  idx   occ        E(Eh)   label
#  ---  ----  -----------  -----
#    3   2.0     -0.56656
#    4   2.0     -0.49314   HOMO
#    5   0.0      0.18604   LUMO
#    6   0.0      0.25618
```

番号は **0 始まりの絶対値**（PySCF と同じ慣習）で、人に優しい経路はビューアも受け付ける `HOMO`/`LUMO±n` トークン
です。水/cc-pVDZ では HOMO が番号 4、LUMO が番号 5 です。

## 3 次元等値面:`view3d`

**等値面**は、スカラー場がある固定値（`isovalue`）を取る面です — 雲のような量を 3 次元で「見る」直感的な方法です。
**`view3d`** はそれをインラインで描画し、セルの最後の式が表示します:

```python
m.view3d("density")               # 電子密度の等値面
m.view3d(orbital="HOMO")          # 符号つき 2 ローブ軌道（正 赤、負 青）
m.view3d(orbitals="HOMO-2:LUMO+2")   # 小さな複数図のギャラリー
m.view3d("elf", isovalue=0.8)     # 選んだ等値での実空間場
```

### スカラー場

`view3d(field)` は qc-prop の実空間ファミリ全体を受け付けます — `density`, `spin`, `laplacian`, `rdg`,
`iri`, `elf`, `lol`, `nci`, `igm`, `esp`, `mo`, `alie`, `deformation` — それぞれ妥当な既定等値を持ちます。
描画は `isovalue=`, `color=`, `style=`（`"stick"`/`"sphere"`/`"line"`）, `width=`/`height=`, `background=`、
グリッドは `spacing=`/`margin=`（bohr）で調整します。これらの場は[分子物性スイート](properties/index.md)の視覚版
です — そこで数値として計算するのと同じ ELF・NCI・ESP です。

### 軌道の選択

軌道は**符号つき 2 ローブ**等値面として描画されます。`orbital=`（1 つ）または `orbitals=`（複数 → ギャラリー）で
選び、以下を自由に混ぜられます:

| 形式 | 例 | 意味 |
|---|---|---|
| int | `5` | 1 軌道（0 始まり絶対値） |
| 範囲文字列 | `"10:20"` | **両端含む**範囲 |
| フロンティアトークン | `"HOMO"`, `"LUMO+2"` | 占有から解決 |
| フロンティア範囲 | `"HOMO-3:LUMO+3"` | ギャップ周辺の窓 |
| 混在リスト | `["HOMO-1", "HOMO", 5]` | 任意の組み合わせ |

`spin="alpha"`（既定）または `"beta"` で UHF/ROHF のチャネルを選びます。

## キューブデータ:`mo_cube`

**絵の背後の数値**が欲しいとき — Gaussian キューブをエクスポートしたり別ツールに渡したり — `mo_cube` はキューブ
データを構築・キャッシュし、各軌道のエネルギー・占有・キューブテキストを持つハンドルを返します:

```python
handles = m.mo_cube("HOMO-1:LUMO+1")
[(h.orbital, round(h.energy, 3), h.occ) for h in handles]
# [(3, -0.567, 2.0), (4, -0.493, 2.0), (5, 0.186, 0.0), (6, 0.256, 0.0)]
```

キューブは MO 係数のハッシュをキーに**セッション中メモ化**されます — 軌道を変える SCF を再実行すると、古い
キューブは自動的に無効化されます（`m.mo_cache` / `m.mo_cache.clear()`）。

## 2 次元プロット

すべてが 3 次元向きとは限りません。3 つの matplotlib 図が一般的な平面図をカバーします:

```python
m.plot_convergence()   # SCF 収束:E, |ΔE|, RMS 勾配 をサイクルに対して
m.plot_nci()           # 2 次元 NCI プロット:縮約密度勾配 s 対 sign(λ₂)ρ
m.plot_field_plane("density", ...)   # 切断面上のスカラー場
```

`plot_convergence()` は SCF の最速の健全性チェック（しきい値への滑らかな下降）;`plot_nci()` は標準的な非共有
結合相互作用の診断（低密度でのスパイクが水素結合・ファンデルワールス接触・立体反発を示す）です。各々 matplotlib
の `Figure` を返します。

## エクスポートと共有

3 次元ビューは**自己完結**の HTML ファイルに書き出せます（3Dmol.js ライブラリが埋め込まれるので、オフラインでも
VSCode webview でも開けます）:

```python
m.view3d(orbital="HOMO").to_html("homo.html")          # 単体 HTML
m.view3d(orbital="HOMO").to_html("homo.html", self_contained=False)   # 小さく、3Dmol.js を CDN から読む
```

**kaleido** を入れると、matplotlib/plotly の図を通常どおり静止 PNG として保存できます（`fig.savefig(...)` /
plotly `write_image`）。

## 総合例:フロンティア軌道を見る

```python
import qc
water = "O 0 0 0.117; H 0 0.757 -0.469; H 0 -0.757 -0.469"
m = qc.chk.new(atom=water, ao="cc-pvdz", unit="angstrom").scf(ref="r").run()

m.orbitals                              # HOMO/LUMO を探す（番号 4 と 5）
m.view3d(orbitals="HOMO-1:LUMO+1")      # フロンティア軌道のギャラリー
m.view3d("density").to_html("water_density.html")   # 密度等値面を保存
```

:::{exercise}
:label: ex-view

1. `m.orbitals` は HOMO を番号 4（占有 2.0）、LUMO を番号 5（占有 0.0）と示します。`view3d` の呼び出しを 2 つ
   書きなさい:HOMO だけのものと、HOMO−1 から LUMO+1 までのギャラリー。
2. 同僚に、Python なしでブラウザで開けるインタラクティブな 3 次元軌道を渡したいです。どのメソッドがそれを作り、
   ファイルがオフラインで動くのはなぜですか。
3. ノートブックが `ImportError: qc.view 3D rendering needs py3Dmol` を出しました。1 行の対処は何で、2 次元の
   `plot_*` がそれなしでも動くのはなぜですか。
:::

:::{solution} ex-view
:class: dropdown

1. `m.view3d(orbital="HOMO")` と `m.view3d(orbitals="HOMO-1:LUMO+1")`（範囲文字列がギャラリーを生む）。
2. **`m.view3d(orbital="HOMO").to_html("homo.html")`** — 既定の `self_contained=True` で 3Dmol.js を
   ファイルに埋め込むので、どのブラウザでもオフラインで描画します。
3. `uv add py3dmol --project "$UV_PROJECT"`（その後カーネル再起動）。2 次元プロットは別依存の **matplotlib** を
   使うので、py3Dmol の欠如に影響されません。
:::

次は[ログと出力の章](logging-output.md)で、実行が出力するものの読み取り・再生・保存を扱います。その後、大規模な
[分子物性スイート](properties/index.md)に到達します。その実空間場こそ `view3d` が描くものです。
