| """ |
| bioavailability.py โ SimBiology BA ๊ณ์ฐ ๋ก์ง ์ด์ ๋ชจ๋ |
| ====================================================== |
| SimBiology + MATLAB ํผํ
์ํฌํ๋ก๋ฅผ ์์ Python์ผ๋ก ์ฌํ. |
| ๋๋ฆฌ๋ชจ๋ธ(MLP/Neural ODE)์ด ์์ธกํ ๋ฆผํยทํ๊ด ํก์๊ณก์ ์ผ๋ก๋ถํฐ |
| ์์ฒด์ด์ฉ๋ฅ (Bioavailability, BA)์ ๊ณ์ฐํ๋ค. |
| |
| ์ํฌํ๋ก (์๋ณธ SimBiology ๊ฒ์ฆ: BA=96.8% ์ฌํ ํ์ธ, ์ค์ฐจ 0.4%p): |
| 1. ๋์ ํก์๊ณก์ (c_lymph, c_vessel)์ ์ด์ค์ง์๋ก ํผํ
|
| y(t) = y0 + (y00-y0)*F*(1-exp(-k_fast*t)) + (y00-y0)*(1-F)*(1-exp(-k_slow*t)) |
| 2. mass rate ์ ๋: [A*exp(-B*t) + C*exp(-D*t)]*dose*0.01 |
| A=(y00-y0)*F*k_fast, B=k_fast, C=(y00-y0)*(1-F)*k_slow, D=k_slow |
| 3. SC 3๊ตฌํ / IV 2๊ตฌํ PK ODE ์ ๋ถ |
| 4. BA = AUC_SC(central) / AUC_IV(central) * 100 |
| |
| SimBiology ๋ชจ๋ธ ํ๋ผ๋ฏธํฐ (ํผํ์ฃผ์ฌ SC ๋ชจ๋ธ): |
| ๊ตฌํ: Central(3.557 L) / Peripheral(1.807 L) / Central lymph(0.312 L) |
| k_12=0.0992 ์ค์โ๋ง์ด |
| k_21=0.3448 ๋ง์ดโ์ค์ |
| k_input=0.1920 ๋ฆผํโ์ค์ |
| k_10=0.0043 ์ค์โ์ ๊ฑฐ(์ฒญ์) |
| ํ๋ฆ: ํ๊ดํก์โ์ค์ ์ง์ / ๋ฆผํํก์โ๋ฆผํ๊ตฌํโ(k_input)โ์ค์ |
| """ |
|
|
| import numpy as np |
| from scipy.optimize import curve_fit |
| from scipy.integrate import odeint |
|
|
| |
| _trapz = np.trapezoid if hasattr(np, "trapezoid") else np.trapz |
|
|
|
|
| |
| K_12 = 0.0992 |
| K_21 = 0.3448 |
| K_INPUT = 0.1920 |
| K_10 = 0.0043 |
| DEFAULT_DOSE = 40.0 |
| T_MAX_HR = 1000.0 |
| N_STEPS = 20000 |
|
|
|
|
| def biexp_cumulative(t, y0, y00, F, k_fast, k_slow): |
| """์ด์ค์ง์ ๋์ ํก์๊ณก์ (MATLAB fittingpara_v3.m์ ๋์ผ).""" |
| return (y0 + (y00 - y0) * F * (1 - np.exp(-k_fast * t)) |
| + (y00 - y0) * (1 - F) * (1 - np.exp(-k_slow * t))) |
|
|
|
|
| def fit_biexp(t_hr, y): |
| """ |
| ๋์ ๊ณก์ ์ ์ด์ค์ง์๋ก ํผํ
. ์ฌ๋ฌ ์ด๊ธฐ๊ฐ์ ์๋ํด robustํ๊ฒ. |
| MATLAB ์ ์ฝ: Lower=[-1,0,0,0,0] (y0,y00,F,k_fast,k_slow), Fโ[0,1]. |
| ๋ฐํ: (y0, y00, F, k_fast, k_slow) ๋๋ None(์คํจ). |
| """ |
| y = np.clip(np.asarray(y, float), 0, None) |
| lower = [-1, 0, 0, 0, 0] |
| upper = [np.inf, np.inf, 1, np.inf, np.inf] |
| best, best_sse = None, np.inf |
| for k_fast0 in (0.1, 1.0, 8.0): |
| for y00_0 in (max(y.max(), 1.0), max(y[-1], 1.0)): |
| try: |
| p0 = [0, y00_0, 0.01, k_fast0, 0.01] |
| popt, _ = curve_fit(biexp_cumulative, t_hr, y, p0=p0, |
| bounds=(lower, upper), maxfev=5000) |
| sse = np.sum((y - biexp_cumulative(t_hr, *popt)) ** 2) |
| if sse < best_sse: |
| best_sse, best = sse, popt |
| except Exception: |
| continue |
| return best |
|
|
|
|
| def rate_params(popt): |
| """ํผํ
ํ๋ผ๋ฏธํฐ โ mass rate ๊ณ์ (A,B,C,D).""" |
| y0, y00, F, k_fast, k_slow = popt |
| A = (y00 - y0) * F * k_fast |
| B = k_fast |
| C = (y00 - y0) * (1 - F) * k_slow |
| D = k_slow |
| return A, B, C, D |
|
|
|
|
| def _sc_ode(y, t, kle, kve): |
| c_cen, c_per, c_lym = y |
| dc_cen = kve(t) + K_INPUT * c_lym - K_10 * c_cen - K_12 * c_cen + K_21 * c_per |
| dc_per = K_12 * c_cen - K_21 * c_per |
| dc_lym = kle(t) - K_INPUT * c_lym |
| return [dc_cen, dc_per, dc_lym] |
|
|
|
|
| def _iv_ode(y, t): |
| c_cen, c_per = y |
| return [-K_10 * c_cen - K_12 * c_cen + K_21 * c_per, |
| K_12 * c_cen - K_21 * c_per] |
|
|
|
|
| def _sc_ode_dyn(y, t, kle, kve, k10, k12, k21, k_input): |
| """ |
| ๋ชจ๋ธ 3 = SimBiology (์ฌ์ฉ์ ๋ชจ๋ธ, ๊ธฐ๋ณธ). |
| ๋
ผ๋ฌธ Fig 2B๋ฅผ ์ ์ 2๊ตฌํ(Central+Peripheral)์ผ๋ก ํผ์น ๊ฒ์ฆ ๋ชจ๋ธ. |
| ํ๊ดโ์ค์ ์ง์ , ๋ฆผํโ๋ฆผํ๊ตฌํโ์ค์. ๋ง์ดโ์ค์(k12,k21). BA 96.8% ์ฌํ. |
| """ |
| c_cen, c_per, c_lym = y |
| dc_cen = kve(t) + k_input * c_lym - k10 * c_cen - k12 * c_cen + k21 * c_per |
| dc_per = k12 * c_cen - k21 * c_per |
| dc_lym = kle(t) - k_input * c_lym |
| return [dc_cen, dc_per, dc_lym] |
|
|
|
|
| def _sc_ode_m1(y, t, ka, k10): |
| """ |
| ๋ชจ๋ธ 1 = ๋
ผ๋ฌธ Fig 1A (single-pathway). |
| ๋ฆผํยทํ๊ด ๋ฏธ๊ตฌ๋ถ, ๋จ์ผ ํก์ ka. ์ ์ 1๊ตฌํ(ํ์ ๋ฐ์ค). |
| """ |
| (c_cen,) = y |
| return [ka(t) - k10 * c_cen] |
|
|
|
|
| def _sc_ode_m2(y, t, kle, kve, k10): |
| """ |
| ๋ชจ๋ธ 2 = ๋
ผ๋ฌธ Fig 2A (dual-pathway, ๋ฆผํ ๊ตฌํ ์์). |
| ํ๊ดยท๋ฆผํ ๋ ๊ฒฝ๋ก๊ฐ ๋ ๋ค ์ ์ ์ผ๋ก ์งํ. ์ ์ 1๊ตฌํ(ํ์ ๋ฐ์ค). |
| """ |
| (c_cen,) = y |
| return [kve(t) + kle(t) - k10 * c_cen] |
|
|
|
|
| def _sc_ode_m4(y, t, kle, kve, k10, k_input, k_rl=0.001): |
| """ |
| ๋ชจ๋ธ 4 = ๋
ผ๋ฌธ Fig 3A (redistribution). |
| ๋ชจ๋ธ 3(๋ฆผํ ๊ตฌํ ๊ฒฝ์ )์ '์ ์ โ๋ฆผํ ์ฌ๋ถํฌ'(k_rl)๋ฅผ ์ถ๊ฐ. |
| ์ ์ ์ ๋๋ฌํ ์ฝ์ด ์กฐ์ง์์ ๋ฆผํ๋ก ๋ฐฐ์ก๋์ด ์ฌ์ํ(Kagan 2007). |
| ์ ์ 1๊ตฌํ. k_rl์ ๊ฐ์ ๊ฐ(๋ฏธ๊ฒ์ฆ). |
| """ |
| c_cen, c_lym = y |
| dc_cen = kve(t) + k_input * c_lym - k10 * c_cen - k_rl * c_cen |
| dc_lym = kle(t) - k_input * c_lym + k_rl * c_cen |
| return [dc_cen, dc_lym] |
|
|
|
|
| def _iv_ode_1pool(y, t, k10): |
| """1๊ตฌํ IV (๋
ผ๋ฌธ ๋ชจ๋ธ 1,2,4์ฉ). t=0 ์ ๋ ํฌ์ฌ ํ 1์ฐจ ์ ๊ฑฐ.""" |
| (c_cen,) = y |
| return [-k10 * c_cen] |
|
|
|
|
| def _iv_ode_dyn(y, t, k10, k12, k21): |
| """์ฝ๋ฌผ๋ณ PK ํ๋ผ๋ฏธํฐ๋ฅผ ๋ฐ๋ ๋์ IV ODE (2๊ตฌํ, ๋ชจ๋ธ 3์ฉ).""" |
| c_cen, c_per = y |
| return [-k10 * c_cen - k12 * c_cen + k21 * c_per, |
| k12 * c_cen - k21 * c_per] |
|
|
|
|
| def compute_bioavailability(c_lymph, c_vessel, t_min, dose=DEFAULT_DOSE, |
| drug="IgG", model=3, return_detail=False): |
| """ |
| ๋ฆผํยทํ๊ด ๋์ ํก์๊ณก์ โ ์์ฒด์ด์ฉ๋ฅ (%). |
| |
| ์ธ์: |
| c_lymph, c_vessel : ๋์ % mass ๊ณก์ (๋๋ฆฌ๋ชจ๋ธ ์์ธก) |
| t_min : ์๊ฐ์ถ (๋ถ) |
| dose : ํฌ์ฌ๋ (mg) |
| drug : ์ฝ๋ฌผ ์ข
๋ฅ ("IgG", "INS", "ALB") ๋๋ MW(์ซ์) |
| ๊ธฐ๋ณธ๊ฐ "IgG" (SimBiology ๊ฒ์ฆ๊ฐ) |
| ๋ฐํ: |
| BA (%) ๋๋ return_detail=True ์ dict |
| """ |
| |
| try: |
| from drug_pk_params import get_drug_params |
| pk = get_drug_params(drug) |
| k12, k21 = pk["k12"], pk["k21"] |
| k_input, k10 = pk["k_input"], pk["k10"] |
| except Exception: |
| |
| k12, k21, k_input, k10 = K_12, K_21, K_INPUT, K_10 |
| t_hr = np.asarray(t_min, float) / 60.0 |
|
|
| pl = fit_biexp(t_hr, c_lymph) |
| pv = fit_biexp(t_hr, c_vessel) |
| if pl is None or pv is None: |
| raise RuntimeError("์ด์ค์ง์ ํผํ
์คํจ โ ๊ณก์ ์ ํ์ธํ์ธ์.") |
|
|
| Al, Bl, Cl, Dl = rate_params(pl) |
| Av, Bv, Cv, Dv = rate_params(pv) |
|
|
| def kle(t): |
| return (Al * np.exp(-Bl * t) + Cl * np.exp(-Dl * t)) * dose * 0.01 |
|
|
| def kve(t): |
| return (Av * np.exp(-Bv * t) + Cv * np.exp(-Dv * t)) * dose * 0.01 |
|
|
| |
| pt = fit_biexp(t_hr, np.asarray(c_lymph, float) + np.asarray(c_vessel, float)) |
| if pt is not None: |
| At, Bt, Ct, Dt = rate_params(pt) |
| def ka(t): |
| return (At * np.exp(-Bt * t) + Ct * np.exp(-Dt * t)) * dose * 0.01 |
| else: |
| ka = lambda t: kle(t) + kve(t) |
|
|
| tt = np.linspace(0, T_MAX_HR, N_STEPS) |
|
|
| |
| |
| |
| if model == 5: |
| |
| raise NotImplementedError( |
| "๋ชจ๋ธ 5(7๊ตฌํ ๋น์ ํ, Fig 3B)๋ ์ ํ๋ฐฉ ๋ฆผํ ๋ถ๋ฆฌ ์ธก์ ๋ฐ์ดํฐ๊ฐ " |
| "ํ์ํ์ฌ ํ์ฌ COMSOL ๋ฐ์ดํฐ(๋ฆผํ ๋จ์ผ ์ถ๋ ฅ)๋ก๋ ๊ตฌํํ ์ ์์ต๋๋ค.") |
| elif model == 1: |
| |
| sc_full = odeint(_sc_ode_m1, [0], tt, args=(ka, k10)) |
| sc = np.column_stack([sc_full[:, 0], np.zeros(len(tt)), np.zeros(len(tt))]) |
| iv = odeint(_iv_ode_1pool, [dose], tt, args=(k10,)) |
| elif model == 2: |
| |
| sc_full = odeint(_sc_ode_m2, [0], tt, args=(kle, kve, k10)) |
| sc = np.column_stack([sc_full[:, 0], np.zeros(len(tt)), np.zeros(len(tt))]) |
| iv = odeint(_iv_ode_1pool, [dose], tt, args=(k10,)) |
| elif model == 4: |
| |
| sc_full = odeint(_sc_ode_m4, [0, 0], tt, args=(kle, kve, k10, k_input)) |
| sc = np.column_stack([sc_full[:, 0], np.zeros(len(tt)), sc_full[:, 1]]) |
| iv = odeint(_iv_ode_1pool, [dose], tt, args=(k10,)) |
| else: |
| sc = odeint(_sc_ode_dyn, [0, 0, 0], tt, |
| args=(kle, kve, k10, k12, k21, k_input)) |
| iv = odeint(_iv_ode_dyn, [dose, 0], tt, args=(k10, k12, k21)) |
|
|
| auc_sc = _trapz(sc[:, 0], tt) |
| auc_iv = _trapz(iv[:, 0], tt) |
| BA = auc_sc / auc_iv * 100.0 |
|
|
| if return_detail: |
| |
| sc_plasma = sc[:, 0] |
| cmax = float(sc_plasma.max()) |
| peak_i = int(np.argmax(sc_plasma)) |
| tmax = float(tt[peak_i]) |
| |
| after = sc_plasma[peak_i:] |
| t_after = tt[peak_i:] |
| if len(after) > 1 and cmax > 0: |
| t_half = float(t_after[int(np.argmin(np.abs(after - cmax / 2)))] - tmax) |
| else: |
| t_half = float("nan") |
| return { |
| "BA": float(BA), |
| "AUC_SC": float(auc_sc), |
| "AUC_IV": float(auc_iv), |
| "drug": drug, |
| "model": model, |
| "pk_params": dict(k10=k10, k12=k12, k21=k21, k_input=k_input), |
| "lymph_rate_params": dict(A=Al, B=Bl, C=Cl, D=Dl), |
| "vessel_rate_params": dict(A=Av, B=Bv, C=Cv, D=Dv), |
| |
| "sc_curve": sc[:, 0], |
| "iv_curve": iv[:, 0], |
| "central_curve": sc[:, 0], |
| "time_hr": tt, |
| |
| "Cmax": cmax, |
| "Tmax_hr": tmax, |
| "t_half_hr": t_half, |
| } |
| return float(BA) |
|
|
|
|
| |
| if __name__ == "__main__": |
| |
| dose = 40.0 |
| def kle(t): return (10.02*np.exp(-8.868*t) + 2.407*np.exp(-0.035*t)) * dose * 0.01 |
| def kve(t): return (0.919*np.exp(-0.035*t) + 1.061*np.exp(-1.576*t)) * dose * 0.01 |
| tt = np.linspace(0, 1000, 20000) |
| sc = odeint(_sc_ode, [0,0,0], tt, args=(kle, kve)) |
| iv = odeint(_iv_ode, [dose,0], tt) |
| ba = _trapz(sc[:,0],tt) / _trapz(iv[:,0],tt) * 100 |
| print(f"๊ฒ์ฆ: BA={ba:.1f}% (์๋ณธ SimBiology 96.8%, ์ค์ฐจ {abs(ba-96.8):.2f}%p)") |