comsol_surrogate_model / bioavailability.py
guminhong's picture
Upload 9 files
e955b3f verified
Raw
History Blame Contribute Delete
11 kB
"""
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
# numpy ๋ฒ„์ „ ํ˜ธํ™˜: 2.0+๋Š” trapezoid, ์ด์ „์€ trapz
_trapz = np.trapezoid if hasattr(np, "trapezoid") else np.trapz
# โ”€โ”€ SimBiology ๋ชจ๋ธ ํŒŒ๋ผ๋ฏธํ„ฐ โ”€โ”€
K_12 = 0.0992 # ์ค‘์•™ โ†’ ๋ง์ดˆ (1/hr)
K_21 = 0.3448 # ๋ง์ดˆ โ†’ ์ค‘์•™ (1/hr)
K_INPUT = 0.1920 # ๋ฆผํ”„ โ†’ ์ค‘์•™ (1/hr)
K_10 = 0.0043 # ์ค‘์•™ โ†’ ์ œ๊ฑฐ (1/hr)
DEFAULT_DOSE = 40.0 # mg
T_MAX_HR = 1000.0 # ์ ๋ถ„ ๊ตฌ๊ฐ„ (hr, โ‰ˆ42์ผ)
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
"""
# ์•ฝ๋ฌผ๋ณ„ ์ „์‹  PK ํŒŒ๋ผ๋ฏธํ„ฐ ๋กœ๋“œ
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:
# drug_pk_params.py ์—†์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’(IgG) ์‚ฌ์šฉ
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
# ๋ชจ๋ธ 1์€ ๋ฆผํ”„+ํ˜ˆ๊ด€ ํ•ฉ์‚ฐ ํก์ˆ˜๋ฅผ ๋”ฐ๋กœ ํ”ผํŒ…
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)
# โ”€โ”€ ๋ชจ๋ธ ์„ ํƒ (๋…ผ๋ฌธ Kagan 2014 ๊ธฐ์ค€) โ”€โ”€
# ๋ชจ๋ธ 1,2,4 = ์ „์‹  1๊ตฌํš(๋…ผ๋ฌธ ํšŒ์ƒ‰๋ฐ•์Šค) โ†’ IV๋„ 1๊ตฌํš
# ๋ชจ๋ธ 3 = SimBiology 2๊ตฌํš(Central+Peripheral) โ†’ IV 2๊ตฌํš
if model == 5:
# ๋ชจ๋ธ 5 = Fig 3B (7๊ตฌํš ๋น„์„ ํ˜•) โ€” ํ˜„์žฌ ๋ฐ์ดํ„ฐ๋กœ ๊ตฌํ˜„ ๋ถˆ๊ฐ€
raise NotImplementedError(
"๋ชจ๋ธ 5(7๊ตฌํš ๋น„์„ ํ˜•, Fig 3B)๋Š” ์ „ํ›„๋ฐฉ ๋ฆผํ”„ ๋ถ„๋ฆฌ ์ธก์ • ๋ฐ์ดํ„ฐ๊ฐ€ "
"ํ•„์š”ํ•˜์—ฌ ํ˜„์žฌ COMSOL ๋ฐ์ดํ„ฐ(๋ฆผํ”„ ๋‹จ์ผ ์ถœ๋ ฅ)๋กœ๋Š” ๊ตฌํ˜„ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
elif model == 1:
# Fig 1A: ๋‹จ์ผํก์ˆ˜, ์ „์‹  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:
# Fig 2A: dual, ๋ฆผํ”„๊ตฌํš ์—†์Œ, ์ „์‹  1๊ตฌํš
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:
# Fig 3A: redistribution, ๋ฆผํ”„๊ตฌํš + ์ „์‹  1๊ตฌํš
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: # model == 3 (SimBiology, ๊ธฐ๋ณธ)
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:
# ํ˜ˆ์žฅ(Central) ๊ธฐ์ค€ ์•ฝ๋™ํ•™ ์ง€ํ‘œ
sc_plasma = sc[:, 0]
cmax = float(sc_plasma.max())
peak_i = int(np.argmax(sc_plasma))
tmax = float(tt[peak_i])
# tยฝ: ์ •์  ์ดํ›„ ์ ˆ๋ฐ˜์œผ๋กœ ๊ฐ์†Œํ•˜๋Š” ์‹œ๊ฐ„ (์†Œ์‹ค ๋ฐ˜๊ฐ๊ธฐ)
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),
# ํ˜ˆ์žฅ ๋†๋„-์‹œ๊ฐ„ ๊ณก์„  (BA ๊ทธ๋ž˜ํ”„์šฉ)
"sc_curve": sc[:, 0], # SC ํ˜ˆ์žฅ ๊ณก์„ 
"iv_curve": iv[:, 0], # IV ํ˜ˆ์žฅ ๊ณก์„ 
"central_curve": sc[:, 0], # (ํ•˜์œ„ํ˜ธํ™˜)
"time_hr": tt,
# ํ˜ˆ์žฅ ๊ธฐ์ค€ PK ์ง€ํ‘œ
"Cmax": cmax,
"Tmax_hr": tmax,
"t_half_hr": t_half,
}
return float(BA)
# โ”€โ”€ ์ž์ฒด ๊ฒ€์ฆ (์›๋ณธ SimBiology BA=96.8% ์žฌํ˜„) โ”€โ”€
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)")