{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "bed4637d-8e0c-42b4-9331-8a39ca2381a6",
   "metadata": {},
   "source": [
    "Copyright (c) 2025 Mitsuru Ohno  \n",
    "Use of this source code is governed by a BSD-3-style  \n",
    "license that can be found in the LICENSE file.  "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "911d53c0",
   "metadata": {},
   "source": [
    "## 当ノートブックのワークフロー  \n",
    "ある素反応の速度定数が、別の素反応の速度定数の関数で表現される場合の事例。  \n",
    "1. 未知の速度定数を含む、csvに書き込んだ反応式を読み込む。  \n",
    "2. 化学種の濃度の経時変化の実験データを読み込む。実験データには欠損を含んでもよい。  \n",
    "3. RxnODEbuild で化学種ごとの微分型の反応速度式を作成する。速度定数が未知の素反応にはシンボリックな変数が割り振られる。  \n",
    "4. 作成した微分方程式を、数値解析可能な形式にする。  \n",
    "5. **P0OptFit**（Optuna）で初期パラメータ p0 を探索し、最適な p0 で **ExpDataFitSci** により速度定数をフィッティングする。  \n",
    "6. 経時変化を図示する。  \n",
    "\n",
    "もしエラーが発生した場合は、debug_ode_system()メソッドで詳細な情報を確認できる。  \n",
    "\n",
    "\n",
    "## 引用文献  \n",
    "2) 永井俊. 物理化学実験 「酢酸エステルの加水分解速度測定」 の問題点と改良法. 日本医科大学基礎科学紀要= The Bulletin of liberal arts & sciences, Nippon Medical School/日本医科大学基礎科学紀要編集委員会 編, 2015, 44: 1-24.  https://www.nms.ac.jp/library/college/pdf/kenkyujoho/katsudo/kiyou/no44/44thebulletin_takashi_nagai.pdf  \n",
    "\n",
    "- 文献中2.2 疑二次法のデータ、Table 3, Table 4、および2.2.6記載の平衡定数を用いた。  \n",
    "- 平衡反応で、何らかの方法で平衡定数が既知の状況を想定し、逆反応の速度定数は、正反応の速度定数に係数をかけて表現した。  \n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f793b762-0cca-45f9-ad15-a0c092ac36e6",
   "metadata": {},
   "source": [
    "## 反応式を記載したcsvファイルを指定する  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "92b6caf1-fbe4-47c8-9bf7-15dc223b3b28",
   "metadata": {},
   "outputs": [],
   "source": [
    "file_path_rxn = './sample_data/ref2/sample_rxn_ref2b.csv'  # CSVファイルのパスを指定"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "95324112-077e-4f27-9529-2a86aaa179f2",
   "metadata": {},
   "source": [
    "## 反応速度式をscipy.integrate.solve_ivpで処理できる連立微分方程式にする  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "75e2680b-f29a-4109-8707-97deb1943716",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "from rxnfit import RxnODEbuild, SolverConfig, RxnODEsolver\n",
    "from rxnfit.expdata_reader import expdata_read\n",
    "from rxnfit.expdata_fit_sci import ExpDataFitSci\n",
    "from rxnfit.p0_opt_fit import P0OptFit\n",
    "import time\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e182c934",
   "metadata": {},
   "source": [
    "# 反応速度式の作成"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "aeb55f6f-83d3-4689-9921-c0f9c3c7216f",
   "metadata": {},
   "outputs": [],
   "source": [
    "builded_rxnode = RxnODEbuild(file_path_rxn)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "dc5a5d88-0b5d-4f67-bd1c-81209bae4d0a",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "number of species: 4\n",
      "unique species: ['AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "rate constant: {'k1': k1, 'k2': 5.88235294117647*k1}\n",
      "\n",
      "=== debug info ===\n",
      "Successfully created function for AcOEt with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Successfully created function for H2O with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Successfully created function for AcOH with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Successfully created function for EtOH with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "order of args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "system of ODE: {'AcOEt': '-1/1*k1*AcOEt(t)*H2O(t)+1/1*k2*AcOH(t)*EtOH(t)', 'H2O': '-1/1*k1*AcOEt(t)*H2O(t)+1/1*k2*AcOH(t)*EtOH(t)', 'AcOH': '1/1*k1*AcOEt(t)*H2O(t)-1/1*k2*AcOH(t)*EtOH(t)', 'EtOH': '1/1*k1*AcOEt(t)*H2O(t)-1/1*k2*AcOH(t)*EtOH(t)'}\n"
     ]
    }
   ],
   "source": [
    "builded_rxnode.get_ode_info(debug_info=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "e98f2a67",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Successfully created function for AcOEt with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Successfully created function for H2O with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Successfully created function for AcOH with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Successfully created function for EtOH with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "[Eq(Derivative(AcOEt(t), t), -k1*AcOEt(t)*H2O(t) + 5.88235294117647*k1*AcOH(t)*EtOH(t)),\n",
       " Eq(Derivative(H2O(t), t), -k1*AcOEt(t)*H2O(t) + 5.88235294117647*k1*AcOH(t)*EtOH(t)),\n",
       " Eq(Derivative(AcOH(t), t), k1*AcOEt(t)*H2O(t) - 5.88235294117647*k1*AcOH(t)*EtOH(t)),\n",
       " Eq(Derivative(EtOH(t), t), k1*AcOEt(t)*H2O(t) - 5.88235294117647*k1*AcOH(t)*EtOH(t))]"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 作成した微分方程式\n",
    "builded_rxnode.get_ode_system()[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "1cfb1b8a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'k1': k1, 'k2': 5.88235294117647*k1}\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "[sympy.core.symbol.Symbol, sympy.core.mul.Mul]"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 速度定数の確認\n",
    "print(builded_rxnode.rate_consts_dict)\n",
    "\n",
    "check_type = [v for v in builded_rxnode.rate_consts_dict.values()]\n",
    "[type(e) for e in check_type]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a0f9e382",
   "metadata": {},
   "source": [
    "## 経時変化の実験データを読み込み　　\n",
    "### データフレーム化  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "6d74af85",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[([array([   0.  ,    5.13,   12.95,   18.15,   29.08,   40.18,   51.15,\n",
       "            61.7 ,   74.75,   91.33,  106.58,  127.57,  147.88,  181.  ,\n",
       "           210.7 ,  240.85,  300.72,  367.65,  420.97,  486.52,  555.48,\n",
       "          1569.3 , 1712.15]),\n",
       "   array([   0.  ,    5.13,   12.95,   18.15,   29.08,   40.18,   51.15,\n",
       "            61.7 ,   74.75,   91.33,  106.58,  127.57,  147.88,  181.  ,\n",
       "           210.7 ,  240.85,  300.72,  367.65,  420.97,  486.52,  555.48,\n",
       "          1569.3 , 1712.15]),\n",
       "   array([   0.  ,    5.13,   12.95,   18.15,   29.08,   40.18,   51.15,\n",
       "            61.7 ,   74.75,   91.33,  106.58,  127.57,  147.88,  181.  ,\n",
       "           210.7 ,  240.85,  300.72,  367.65,  420.97,  486.52,  555.48,\n",
       "          1569.3 , 1712.15]),\n",
       "   array([   0.  ,    5.13,   12.95,   18.15,   29.08,   40.18,   51.15,\n",
       "            61.7 ,   74.75,   91.33,  106.58,  127.57,  147.88,  181.  ,\n",
       "           210.7 ,  240.85,  300.72,  367.65,  420.97,  486.52,  555.48,\n",
       "          1569.3 , 1712.15])],\n",
       "  [array([0.5578, 0.5257, 0.5063, 0.481 , 0.453 , 0.425 , 0.394 , 0.374 ,\n",
       "          0.341 , 0.305 , 0.28  , 0.246 , 0.218 , 0.178 , 0.153 , 0.127 ,\n",
       "          0.101 , 0.074 , 0.058 , 0.051 , 0.041 , 0.03  , 0.03  ]),\n",
       "   array([51.63  , 51.5979, 51.5785, 51.5532, 51.5252, 51.4972, 51.4662,\n",
       "          51.4462, 51.4132, 51.3772, 51.3522, 51.3182, 51.2902, 51.2502,\n",
       "          51.2252, 51.1992, 51.1732, 51.1462, 51.1302, 51.1232, 51.1132,\n",
       "          51.1022, 51.1022]),\n",
       "   array([0.013 , 0.0321, 0.0515, 0.0768, 0.1048, 0.1328, 0.1638, 0.1838,\n",
       "          0.2168, 0.2528, 0.2778, 0.3118, 0.3398, 0.3798, 0.4048, 0.4308,\n",
       "          0.4568, 0.4838, 0.4998, 0.5068, 0.5168, 0.5278, 0.5278]),\n",
       "   array([0.013 , 0.0321, 0.0515, 0.0768, 0.1048, 0.1328, 0.1638, 0.1838,\n",
       "          0.2168, 0.2528, 0.2778, 0.3118, 0.3398, 0.3798, 0.4048, 0.4308,\n",
       "          0.4568, 0.4838, 0.4998, 0.5068, 0.5168, 0.5278, 0.5278])])]"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "file_path_data = './sample_data/ref2/sample_timecourse_ref2.csv' # データファイルのパス\n",
    "df1 = pd.read_csv(file_path_data)\n",
    "\n",
    "expdata_read([df1,])  # 複数データフレームのリストを渡すとまとめて読み込み可能"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6ef7e98f",
   "metadata": {},
   "source": [
    "### フィッティング  \n",
    "P0OptFit に RxnODEbuild のインスタンスと df_list を渡し、Optuna で初期値 p0 を最適化したうえで ExpDataFitSci によりフィッティングする。処理時間（CPU）を計測・表示する。  \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "a303dde2",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\u001b[32m[I 2026-03-14 11:12:22,391]\u001b[0m A new study created in memory with name: no-name-1a0cd790-d6bf-476b-9575-5e43627eb54d\u001b[0m\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "ebd65e228494431eb3b9ca7be55139cc",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "  0%|          | 0/5 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Successfully created function for AcOEt with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Successfully created function for H2O with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Successfully created function for AcOH with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Successfully created function for EtOH with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "\u001b[32m[I 2026-03-14 11:12:45,595]\u001b[0m Trial 0 finished with value: 0.005379495008984417 and parameters: {'k1': 1.2698181749580044e-08}. Best is trial 0 with value: 0.005379495008984417.\u001b[0m\n",
      "Successfully created function for AcOEt with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Successfully created function for H2O with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Successfully created function for AcOH with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Successfully created function for EtOH with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "\u001b[32m[I 2026-03-14 11:12:45,905]\u001b[0m Trial 1 finished with value: 0.005379495637347123 and parameters: {'k1': 0.001044109197577308}. Best is trial 0 with value: 0.005379495008984417.\u001b[0m\n",
      "Successfully created function for AcOEt with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Successfully created function for H2O with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Successfully created function for AcOH with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Successfully created function for EtOH with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "\u001b[32m[I 2026-03-14 11:12:46,120]\u001b[0m Trial 2 finished with value: 0.005379495662004213 and parameters: {'k1': 0.0009072624390917647}. Best is trial 0 with value: 0.005379495008984417.\u001b[0m\n",
      "Successfully created function for AcOEt with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Successfully created function for H2O with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Successfully created function for AcOH with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Successfully created function for EtOH with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "\u001b[33m[W 2026-03-14 11:13:09,211]\u001b[0m Trial 3 failed with parameters: {'k1': 2.179190964779532e-07} because of the following error: RuntimeError('run_fit did not converge: ABNORMAL: ').\u001b[0m\n",
      "Traceback (most recent call last):\n",
      "  File \u001b[35m\"C:\\Users\\mt_oh\\DataScience\\venvs\\rxnfit314_sphinx\\Lib\\site-packages\\optuna\\study\\_optimize.py\"\u001b[0m, line \u001b[35m206\u001b[0m, in \u001b[35m_run_trial\u001b[0m\n",
      "    value_or_values = func(trial)\n",
      "  File \u001b[35m\"C:\\Users\\mt_oh\\DataScience\\venvs\\rxnfit314_sphinx\\Lib\\site-packages\\rxnfit\\p0_opt_fit.py\"\u001b[0m, line \u001b[35m186\u001b[0m, in \u001b[35m_objective\u001b[0m\n",
      "    raise RuntimeError(\n",
      "        f\"run_fit did not converge: {getattr(result, 'message', '')}\"\n",
      "    )\n",
      "\u001b[1;35mRuntimeError\u001b[0m: \u001b[35mrun_fit did not converge: ABNORMAL: \u001b[0m\n",
      "\u001b[33m[W 2026-03-14 11:13:09,214]\u001b[0m Trial 3 failed with value None.\u001b[0m\n",
      "Successfully created function for AcOEt with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Successfully created function for H2O with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Successfully created function for AcOH with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Successfully created function for EtOH with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "\u001b[32m[I 2026-03-14 11:13:31,725]\u001b[0m Trial 4 finished with value: 0.005379495547488592 and parameters: {'k1': 2.6370044006372845e-07}. Best is trial 0 with value: 0.005379495008984417.\u001b[0m\n",
      "Successfully created function for AcOEt with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Successfully created function for H2O with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Successfully created function for AcOH with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Successfully created function for EtOH with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Optimization success: True\n",
      "Fitted rate constants:\n",
      "  k1 = 0.00012224\n",
      "Residual sum of squares: 0.0053795  R²: 0.998093\n",
      "Successfully created function for AcOEt with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Successfully created function for H2O with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Successfully created function for AcOH with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Successfully created function for EtOH with args: ['t', 'AcOEt', 'H2O', 'AcOH', 'EtOH']\n",
      "Optimization success: True\n",
      "Fitted rate constants:\n",
      "  k1 = 0.00012224\n",
      "Residual sum of squares: 0.0053795  R²: 0.998093\n",
      "CPU time: 00:01:52\n"
     ]
    }
   ],
   "source": [
    "# P0OptFit: RxnODEbuild インスタンスと df_list を渡す（他は最小限）\n",
    "t_cpu_start = time.process_time()\n",
    "opt = P0OptFit(builded_rxnode, [df1], seed=9)\n",
    "result_dict, fit_metrics = opt.optimize(n_trials=5)\n",
    "\n",
    "# プロット用に最適 p0 で ExpDataFitSci を1回実行し、config を取得\n",
    "keys = builded_rxnode.get_symbolic_rate_const_keys()\n",
    "p0_best = [result_dict[k][0] for k in keys]\n",
    "t_range = (0, float(df1.iloc[:, 0].max()))\n",
    "fit_sci = ExpDataFitSci(builded_rxnode, [df1], t_range)\n",
    "fit_sci.run_fit(p0_best)\n",
    "\n",
    "t_cpu_end = time.process_time()\n",
    "elapsed = t_cpu_end - t_cpu_start\n",
    "h, r = divmod(int(elapsed), 3600)\n",
    "m, s = divmod(r, 60)\n",
    "print(f\"CPU time: {h:02d}:{m:02d}:{s:02d}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "4b1e4ca9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== Time-course plot ===\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 35588 characters]",
      "text/plain": [
       "<Figure size 600x400 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== Concentration at the final time point ===\n",
      "0:\n",
      "  AcOEt: 0.033262\n",
      "  AcOH: 0.537538\n"
     ]
    }
   ],
   "source": [
    "# 経時変化を図示（plot_fitted_solution を使用）\n",
    "fit_sci.plot_fitted_solution(expdata_df=df1, species=['AcOEt', 'AcOH'])"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.14.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
