【化学でPython】SciPy:複雑な積分や最適化問題を解く
はじめに
この「化学でPython」シリーズでは、化学の分野で有用な Python ライブラリを紹介しています。
今回紹介するのは、SciPy です。
SciPy とは?
SciPy は、NumPy を基盤とした科学技術計算のための総合ライブラリです。
積分・最適化・統計・線形代数など、高度な数値計算機能を提供します。
インストール
pip で簡単にインストールできます。
pip install scipy
基本的な使い方
まずは、基本的な使い方を見てみましょう。
SciPy は機能ごとにサブモジュールに分かれていて、必要なものだけをインポートして使います。
import numpy as np
from scipy import optimize, integrate, stats
# 例1: 簡単な関数の最小値を求める
def f(x):
return x**2 + 5*x + 6 # 2次関数
# 最小値を探索(初期値 x=0 から開始)
result = optimize.minimize(f, x0=0)
print(f"最小値: {result.fun:.3f}")
print(f"最小値を与えるx: {result.x[0]:.3f}")
# 例2: 定積分を計算する
# ∫[0,1] x^2 dx を計算
integral_result, error = integrate.quad(lambda x: x**2, 0, 1)
print(f"\n積分結果: {integral_result:.3f}")
print(f"誤差推定: {error:.2e}")
最小値: -0.250
最小値を与えるx: -2.500
積分結果: 0.333
誤差推定: 3.70e-15
このように、SciPy を使えば複雑な数値計算を数行のコードで実行できます。
実践例①: 反応速度論のシミュレーション
実践的な例として、「一次反応の速度論シミュレーション」 をやってみます。
化学反応 A → B において、反応物 A の濃度変化を常微分方程式(ODE)で解き、時間変化をグラフ化します。
1. 反応速度式の定義
一次反応の速度式は以下のように表されます:
ここで、
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
# 反応速度式を定義(微分方程式)
def reaction_rate(C, t, k):
"""
一次反応 A → B の速度式
C: 濃度 [A]
t: 時間
k: 反応速度定数
"""
dCdt = -k * C # d[A]/dt = -k[A]
return dCdt
# パラメータ設定
k = 0.5 # 反応速度定数 [1/s]
C0 = 1.0 # 初期濃度 [mol/L]
t = np.linspace(0, 10, 100) # 0〜10秒を100点で分割
# 常微分方程式を数値的に解く
C = odeint(reaction_rate, C0, t, args=(k,))
# 結果をプロット
plt.figure(figsize=(8, 5))
plt.plot(t, C, 'b-', linewidth=2, label='[A] (Numerical Solution)')
# 理論解(解析解)と比較
C_analytical = C0 * np.exp(-k * t)
plt.plot(t, C_analytical, 'r--', linewidth=2, label='[A] (Analytical Solution)')
plt.xlabel('Time (s)', fontsize=12)
plt.ylabel('Concentration [A] (mol/L)', fontsize=12)
plt.title('Concentration Change of First-Order Reaction A → B', fontsize=14)
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
print(f"半減期: {np.log(2)/k:.2f} 秒")

半減期: 1.39 秒
数値解(青線)と解析解(赤破線)がぴったり重なることが確認できますね。
これにより、scipy.integrate.odeint が正確に微分方程式を解いていることがわかります。
実践例②: スペクトルデータのピークフィッティング
次に、「UV-Visスペクトルのガウシアンピークフィッティング」 を行います。
実験で得られたスペクトルデータから、ピークの位置(波長)、高さ、幅を定量的に求めます。
1. 模擬スペクトルデータの作成
まず、ノイズを含む模擬的なスペクトルデータを作成します。
import numpy as np
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt
# ガウシアン関数の定義
def gaussian(x, amplitude, center, width):
"""
ガウシアン関数
amplitude: ピークの高さ
center: ピークの中心位置(波長)
width: ピークの幅(標準偏差)
"""
return amplitude * np.exp(-(x - center)**2 / (2 * width**2))
# 模擬スペクトルデータの生成
np.random.seed(42)
wavelength = np.linspace(400, 700, 300) # 波長 400-700 nm
# 真のパラメータ(これを推定するのが目標)
true_amplitude = 1.5
true_center = 550 # nm
true_width = 30 # nm
# ノイズを含むスペクトルデータ
absorbance = gaussian(wavelength, true_amplitude, true_center, true_width)
absorbance += np.random.normal(0, 0.05, wavelength.shape) # ノイズ追加
print("=== 模擬スペクトルデータを生成しました ===")
print(f"真のピーク位置: {true_center} nm")
print(f"真のピーク高さ: {true_amplitude:.2f}")
print(f"真のピーク幅: {true_width} nm")
=== 模擬スペクトルデータを生成しました ===
真のピーク位置: 550 nm
真のピーク高さ: 1.50
真のピーク幅: 30 nm
2. カーブフィッティングの実行
scipy.optimize.curve_fit を使って、実験データからパラメータを推定します。
# 初期推定値(適当な値でOK)
initial_guess = [1.0, 500, 50]
# カーブフィッティング実行
popt, pcov = curve_fit(gaussian, wavelength, absorbance, p0=initial_guess)
# フィッティング結果
fitted_amplitude, fitted_center, fitted_width = popt
# 標準誤差の計算
perr = np.sqrt(np.diag(pcov))
print("\n=== フィッティング結果 ===")
print(f"推定ピーク位置: {fitted_center:.1f} ± {perr[1]:.1f} nm")
print(f"推定ピーク高さ: {fitted_amplitude:.3f} ± {perr[0]:.3f}")
print(f"推定ピーク幅: {fitted_width:.1f} ± {perr[2]:.1f} nm")
# フィッティング曲線の生成
fitted_curve = gaussian(wavelength, *popt)
# プロット
plt.figure(figsize=(10, 6))
plt.plot(wavelength, absorbance, 'o', markersize=3, alpha=0.5, label='Experimental Data (with noise)')
plt.plot(wavelength, fitted_curve, 'r-', linewidth=2, label='Fitted Curve')
plt.axvline(fitted_center, color='green', linestyle='--', alpha=0.7, label=f'Peak Position: {fitted_center:.1f} nm')
plt.xlabel('Wavelength (nm)', fontsize=12)
plt.ylabel('Absorbance', fontsize=12)
plt.title('Gaussian Fitting of UV-Vis Spectrum', fontsize=14)
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
=== フィッティング結果 ===
推定ピーク位置: 549.8 ± 0.7 nm
推定ピーク高さ: 1.497 ± 0.012
推定ピーク幅: 30.1 ± 0.7 nm

ノイズを含むデータからでも、真の値にかなり近いパラメータを推定できました。
実践例③: 吸着等温線のフィッティング
最後に、「Langmuir吸着等温線のフィッティング」 を行います。
吸着実験のデータから、最大吸着量と吸着平衡定数を求めます。
import numpy as np
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt
# Langmuir吸着等温線の式
def langmuir(C, qmax, K):
"""
Langmuir吸着等温線
C: 平衡濃度 [mg/L]
qmax: 最大吸着量 [mg/g]
K: Langmuir定数 [L/mg]
"""
return qmax * K * C / (1 + K * C)
# 実験データ(模擬)
C_exp = np.array([0.5, 1.0, 2.0, 5.0, 10.0, 20.0, 50.0, 100.0]) # 平衡濃度 [mg/L]
q_exp = np.array([2.3, 4.1, 6.8, 12.5, 18.2, 24.1, 31.5, 35.8]) # 吸着量 [mg/g]
# カーブフィッティング
# 適切な初期推定値を与える (qmaxは最大吸着量付近、Kは正の値)
initial_guess_langmuir = [40.0, 0.1] # qmax=40 mg/g, K=0.1 L/mg として初期推定
popt, pcov = curve_fit(langmuir, C_exp, q_exp, p0=initial_guess_langmuir)
qmax_fit, K_fit = popt
# フィッティング曲線
C_fit = np.linspace(0, 100, 200)
q_fit = langmuir(C_fit, qmax_fit, K_fit)
# 結果表示
print("=== Langmuir吸着等温線フィッティング結果 ===")
print(f"最大吸着量 qmax: {qmax_fit:.2f} mg/g")
print(f"Langmuir定数 K: {K_fit:.4f} L/mg")
# 決定係数(R²)の計算
residuals = q_exp - langmuir(C_exp, qmax_fit, K_fit)
ss_res = np.sum(residuals**2)
ss_tot = np.sum((q_exp - np.mean(q_exp))**2)
r_squared = 1 - (ss_res / ss_tot)
print(f"Coefficient of Determination R²: {r_squared:.4f}")
# プロット
plt.figure(figsize=(10, 6))
plt.plot(C_exp, q_exp, 'o', markersize=8, label='Experimental Data')
plt.plot(C_fit, q_fit, 'r-', linewidth=2, label=f'Langmuir Fit (qmax={qmax_fit:.1f}, K={K_fit:.3f})')
plt.xlabel('Equilibrium Concentration C (mg/L)', fontsize=12)
plt.ylabel('Adsorption Amount q (mg/g)', fontsize=12)
plt.title('Langmuir Adsorption Isotherm', fontsize=14)
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
=== Langmuir吸着等温線フィッティング結果 ===
最大吸着量 qmax: 38.86 mg/g
Langmuir定数 K: 0.0908 L/mg
決定係数 R²: 0.9967

決定係数 R² = 0.9967 と非常に高い値が得られ、Langmuirモデルがこのデータによく適合していることがわかります。
まとめ
今回は SciPy を紹介しました。
- Point 1: 積分・最適化・統計など、化学研究に必要な高度な数値計算を簡単に実行できます
- Point 2: 反応速度論のシミュレーション、スペクトル解析、吸着等温線フィッティングなど、実践的な化学計算に幅広く応用できます
- Point 3: NumPyとの組み合わせで、データ処理から高度な解析まで一貫して行えます
SciPy は「数値計算の万能ツール」。実験データの解析、理論モデルの検証、反応機構の解明など、あらゆる場面で活躍するはず。
ぜひ試してみてください。
Discussion