# 初期値最適化（Optuna）仕様書

## 0. 最初のプロンプト  

コードは変更せず、方針を教えてほしい。rxnfitでは初期値を与え、データへのフィッティングにより速度定数を求めている、速度定数は10^2 ～10^(-8)程度の幅をもつ数値である。速度定数を求めるにあたり、初期値依存の影響を避けるため、optunaで最適な初期値を求め、それを用いてexdata_fit_sci.pyで解を求めたい。対象とする速度定数はbuild_ode.pyで指定されるシンボリックな定数である。微分方程式を解くにあたっては、solv_ode.pyを用いてもよい。既存の.pyは変更せず、必要に応じてこれらのclass, functionを呼び出すこととし、初期値最適化はp0_opt_fit.pyというファイルを作成して実行する。出力は、選定した初期値とその変数名、およびフィッティングにより得た解とその変数名、および残差平方和とする。また、初期値最適化におけるログを、必要に応じて出力できるようにしたい。このような実装は可能か  

## 1. 目的・背景

- rxnfit では初期値を与え、データへのフィッティングにより速度定数を求めている。速度定数はおおよそ 10^2 ～ 10^(-8) 程度の幅をもつ。
- 初期値依存の影響を避けるため、**Optuna で最適な初期値を求め**、その初期値を用いて **expdata_fit_sci** で解（フィッティング結果）を求める。
- **既存の .py は変更しない**。必要に応じて既存の class / function を呼び出す。
- 対象とする速度定数は **build_ode** で指定されるシンボリックな定数（`get_symbolic_rate_const_keys()` で取得）。微分方程式の求解には **solv_ode** を用いてもよい。

---

## 2. 実装方針

- 新規ファイル **p0_opt_fit.py** を作成し、初期値最適化を実行する。
- クラス名: **P0OptFit**（1 クラスとする）。
- ファイル名: **p0_opt_fit.py**（PEP 8 推奨。小文字・アンダースコア。p0 は初期パラメータの意）。

---

## 3. Optuna の設定

- **Optuna に設定できる引数はすべて**、P0OptFit のクラスまたはメソッドの引数として入力できるようにする。
- **指定がない場合は Optuna のデフォルトに従う。**
- **探索範囲**について、指定がない場合は **全パラメータ共通で 10^2 ～ 10^(-8)** とする。
- RDB ストレージの利用は**必須ではない**（省略時はインメモリ）。
- **プログレスバー**: 最適化中は **Optuna の show_progress_bar** を利用する。実装では、P0OptFit.optimize(..., show_progress_bar=True) をデフォルトとし、受け取った値を study.optimize() にそのまま渡す。

### 3.1 探索範囲の形

- **共通範囲**: 全パラメータに同じ (下限, 上限)。デフォルトは (1e-8, 1e2) に相当。
- **パラメータ別**: 変数名をキー、**(下限, 上限)** を値とする辞書で指定する。
  - 辞書に**存在しないパラメータ** → 共通デフォルト 10^2 ～ 10^(-8) を使用。
  - 辞書に**シンボリック速度定数に存在しないキー**（未知のキー）が含まれる → **エラー**とする。
- **Optuna への与え方**: **方法A** とする。`trial.suggest_float(変数名, 下限, 上限, log=True)` でサンプリングし、(変数名, 下限, 上限) の形で渡す。

---

## 4. 入力

| 項目 | 仕様 |
|------|------|
| 反応定義 | 反応 CSV のパス、または既に構築済みの **RxnODEbuild** インスタンスのいずれかで受け付ける。 |
| 反応 CSV の encoding | **rxn_reader が使われる経路**（RxnODEbuild）で読む。encoding は RxnODEbuild の引数として渡し、未指定時は rxn_reader と同様 **utf-8**。rxn_reader を直接呼ぶのではなく、RxnODEbuild 経由で読む。 |
| 実験データ | **DataFrame のリスト（df_list）のみ**で受け付ける。 |
| t_range | 引数として設定する。**デフォルトは (0, 全 df の第 1 列の最大値)**。 |

---

## 5. run_fit まわりの仕様

- run_fit に渡すオプション（opt_method, bounds, use_log_fit, lower_bound, verbose など）は、指定可能な引数として用意する。指定がなければ expdata_fit_sci のデフォルト（または本クラスで定めたデフォルト）に従う。
- **run_fit の bounds** は、各パラメータについて **Optuna で使う最小値** から組み立てる（Optuna 設定の最小値に従う）。

---

## 6. 出力

### 6.1 最終的な戻り値

- **形式**: **(dict, fit_metrics)** のタプルで返す。
- **dict**: `{ 変数名: (最適な初期値, フィッティング結果) }`。変数と値の対応が明確になるよう辞書形式とする。
- **fit_metrics**: 残差・決定係数などの指標をまとめた辞書。キー **'rss'**（残差二乗和）、**'tss'**（全二乗和）、**'r2'**（決定係数）を含む。
- 試行段階の情報は **トライアルログ** で扱う。

### 6.2 全トライアル失敗時

- **正常な (dict, fit_metrics) は返さず**、**エラーメッセージを返す**（実装時に例外とするかメッセージを返すかは選択可能）。

---

## 7. トライアルログ（optuna_log）

- **メソッド名**: **optuna_log**。
- このメソッドを呼ぶと、**トライアルごとのログをリスト**で返す。
- **optimize() をまだ実行していないときに optuna_log を呼んだ場合** → **空リスト []** を返す。

### 7.1 リストの各要素（1 トライアル分）の形式

| キー | 内容 |
|------|------|
| **"trial_No"** | トライアル番号（何回目か）。 |
| **"params"** | その回で Optuna が提案したパラメータ（各変数名と値の辞書）。 |
| **"rss"** | 目的関数の値（最小二乗和のみ）。失敗トライアルの場合は **None**。 |
| **"state"** | 状態（完了・失敗など）。Optuna の TrialState に合わせる（例: Complete, Fail）。 |

---

## 8. クラス・メソッド

- **クラス名**: **P0OptFit**（PEP 8 の CapWords に準拠。p0 は初期パラメータの意）。
- **最適化を実行するメソッド名**: **optimize()**。トライアルを回して最適な初期値を得る。プログレスバーは Optuna の show_progress_bar を引数で受け、study.optimize() に渡す。

---

## 9. 保留・別途議論

- **項目2（探索範囲の引数の形の詳細）**: 別途議論とする。上記 3.1 のとおり、共通範囲とパラメータ別辞書の形で規定。
- **トライアルログの細部**（メソッド名・キー名など）は上記 7 で確定。

---

## 10. 参照モジュール（変更しない）

- **build_ode**: RxnODEbuild, get_symbolic_rate_const_keys, create_ode_system_with_rate_consts, create_system_rhs 等。
- **expdata_fit_sci**: ExpDataFitSci, run_fit, solve_fit_model_multi 等。
- **solv_ode**: RxnODEsolver, SolverConfig 等（必要に応じて呼び出す）。
- **rxn_reader**: 反応 CSV 読み込みは RxnODEbuild 経由で利用（直接 import しない）。

---

## 二法比較

反応速度定数が 10^2 ～ 10^(-8) 程度のレンジであることを前提に、**Optuna + scipy（本仕様）** と **torchdiffeq を用いる形式** のメリット・デメリットを整理し、推奨を示す。

### Optuna + scipy（本仕様の方式）

| メリット | デメリット |
|----------|------------|
| **既存 rxnfit とそのまま接続できる**。build_ode / expdata_fit_sci / solv_ode を変更せず呼び出せる。 | **1 トライアルあたり scipy.minimize が 1 回走る**ため、トライアル数が多いと計算時間が長くなりやすい。 |
| **scipy.integrate.solve_ivp** は stiff 問題にも対応したメソッド（例: Radau, BDF）を選べ、桁が広い速度定数でも安定した積分が期待できる。 | 勾配を使わない（または数値微分）ため、パラメータ数が非常に多いと効率が落ちる可能性がある。 |
| **Optuna** で初期値を広い範囲（とくに log スケール）から探索できるため、局所解に陥りにくく、初期値依存性を抑えやすい。 | 並列化は Optuna の n_jobs や RDB に依存し、GPU は通常使わない。 |
| **解の解釈がしやすい**。速度定数がそのまま実数で得られ、既存の CSV／SymPy ベースのワークフローと一致する。 | 残差の勾配を陽に使わないため、torchdiffeq のような「1 回の積分で勾配まで一括計算」にはならない。 |
| 依存は **NumPy / SciPy / Optuna** が中心で、既存の科学計算スタックと相性がよい。 | — |

### torchdiffeq を用いる形式

| メリット | デメリット |
|----------|------------|
| **自動微分**により、残差に対するパラメータの勾配を 1 回の積分（adjoint 等）で得られる。勾配法（L-BFGS や Adam）で効率的にフィットできる場合がある。 | **既存 rxnfit との接続に工夫がいる**。build_ode は SymPy → NumPy であり、torchdiffeq は PyTorch のテンソルと関数を要求するため、ODE の右辺を PyTorch で再実装するか、lambdify 相当を torch 用に書き直す必要がある。 |
| **GPU 利用**で、問題サイズやバッチによっては高速化が見込める。 | 速度定数 10^2～10^(-8) の**桁の差**は、単精度や勾配のスケールで数値不安定になりやすく、スケーリングや log パラメータ化がほぼ必須。stiff な系では torchdiffeq のデフォルトソルバでも不安定になりうる。 |
| ニューラルネット等と組み合わせた拡張（ハイブリッドモデル）にしやすい。 | 依存に **PyTorch** が加わり、環境が重くなる。化学反応のみを扱う場合は過剰と感じる場合もある。 |
| 勾配ベースのため、パラメータが多くても「1 回の積分＋adjoint」で勾配が得られれば、反復あたりのコストを抑えられる可能性がある。 | **解の形式**がテンソルになり、既存の CSV／可視化や solv_ode との互換のためには変換処理が必要。 |

### 10^2 ～ 10^(-8) レンジを踏まえた整理

- **スティフネス・スケール**: 速度定数が桁で大きく違うと ODE は stiff になりやすい。scipy は Radau/BDF など stiff 対応ソルバが充実している。torchdiffeq でも stiff 対応は可能だが、スケーリングやソルバ選択の調整が求められる。
- **探索の仕方**: Optuna は「初期値」を広域からサンプリングし、そのうえで scipy でフィットするため、**初期値の取り方の影響を抑えつつ、既存のフィット処理をそのまま使える**。torchdiffeq は勾配で効率よく下るが、初期パラメータが悪いと局所解に収束しやすく、広域探索は別途（例: 複数初期値や Optuna と組み合わせ）検討が必要。
- **既存資産**: rxnfit は build_ode（SymPy）→ solve_ivp / ExpDataFitSci の流れで一貫している。この流れを活かすなら Optuna + scipy の方が自然。

### 推奨

**現状の rxnfit を前提とするなら、本仕様の Optuna + scipy 方式を推奨する。**

理由の要点は次のとおり。

1. **既存コードを変えずに**、初期値だけ Optuna で最適化する形にできる。
2. 速度定数の **10^2～10^(-8) のような桁差**に対して、scipy の stiff 対応ソルバと log スケール探索（本仕様の方法A）の組み合わせが、実装・運用ともに扱いやすい。
3. **解の解釈とワークフロー**（速度定数が実数で出る、CSV／SymPy と一致）を維持できる。
4. torchdiffeq は、勾配ベースで効率を追求したい場合や、のちに NN 等と組み合わせる場合に「別の選択肢」として検討するのがよい。その場合でも、ODE の PyTorch 化やスケーリング、既存 rxnfit とのインターフェース設計が別途必要になる。

まとめると、**まずは Optuna + scipy で初期値最適化とフィッティングを実装し、必要になった段階で torchdiffeq を併用・比較する**のが現実的である。
