Coef_SC_Model / bioavailability.py
guminhong's picture
Upload 16 files
35c683c verified
Raw History Blame Contribute Delete
27.1 kB
"""
bioavailability.py β€” SimBiology BA 계산 둜직 이식 λͺ¨λ“ˆ (λ¬Έν—Œ 5λͺ¨λΈ μ •λ ¬νŒ)
========================================================================
SimBiology + MATLAB ν”ΌνŒ… μ›Œν¬ν”Œλ‘œλ₯Ό 순수 Python으둜 μž¬ν˜„.
λŒ€λ¦¬λͺ¨λΈ(MLP/Neural ODE)이 μ˜ˆμΈ‘ν•œ λ¦Όν”„Β·ν˜ˆκ΄€ ν‘μˆ˜κ³‘μ„ μœΌλ‘œλΆ€ν„°
μƒμ²΄μ΄μš©λ₯ (Bioavailability, BA)을 κ³„μ‚°ν•œλ‹€.
이 버전은 5개 ODE λͺ¨λΈμ˜ μ „μ‹ (systemic) ꡬ획 ꡬ쑰λ₯Ό Kagan (2014,
DMD 42:1890) μ’…μ„€μ˜ κ·Έλ¦Όκ³Ό "λ™μΌν•˜κ²Œ" 맞좘 것이닀.
model 1 = Fig 1A λ‹¨μΌκ²½λ‘œ, μ „μ‹  1ꡬ획
model 2 = Fig 2A μ΄μ€‘κ²½λ‘œ(λ¦Όν”„ ꡬ획 μ—†μŒ), μ „μ‹  1ꡬ획
model 3 = Fig 2B μ΄μ€‘κ²½λ‘œ(별도 λ¦Όν”„ ꡬ획), μ „μ‹  2ꡬ획 ← μ‚¬μš©μž PPT/SimBiology λͺ¨λΈ
model 4 = Fig 3A μž¬λΆ„ν¬(λ§μ΄ˆμ‘°μ§β†’λ¦Όν”„), μ „μ‹  2ꡬ획
model 5 = Fig 3B 7ꡬ획 λΉ„μ„ ν˜•(Dahlberg 2014, trastuzumab)
흑수 ꡬ동 방식(μ‚¬μš©μž 선택 = 곑선 ꡬ동):
models 1–4 λŠ” λŒ€λ¦¬λͺ¨λΈμ΄ μ˜ˆμΈ‘ν•œ λˆ„μ  ν‘μˆ˜κ³‘μ„ (c_lymph, c_vessel)을
μ΄μ€‘μ§€μˆ˜λ‘œ ν”ΌνŒ…β†’λ―ΈλΆ„ν•œ μ‹œκ°„μ˜μ‘΄ μ§ˆλŸ‰μœ μž…λ₯  kle(t)/kve(t)λ₯Ό μ „μ‹  ODE에
μ†ŒμŠ€ν•­μœΌλ‘œ μ£Όμž…ν•œλ‹€. λ¬Έν—Œμ˜ kaΒ·SC(t)(예: Fig 2A/2B eq.5,9)μ—μ„œ SC
μ €μž₯κ³ λ₯Ό λͺ…μ‹œμ μœΌλ‘œ ν’€μ§€ μ•Šκ³  흑수λ₯Ό κ³‘μ„ μœΌλ‘œ 미리 계산해 넣은 λ“±κ°€ ν˜•νƒœλ‹€.
presystemic 손싀(λ¬Έν—Œ k_loss / PPT k_decay)은 ν‘μˆ˜κ³‘μ„ μ— 이미 λ°˜μ˜λœλ‹€.
model 5 (Fig 3B)λŠ” 흑수 μžμ²΄κ°€ λͺ…μ‹œμ  SC κ΅¬νšμ—μ„œμ˜ k14 + Michaelis–
Menten(Vmax,Km) μˆ˜μ†‘μ΄λΌ 곑선 ꡬ동과 ν˜Έν™˜λ˜μ§€ μ•ŠλŠ”λ‹€. λ”°λΌμ„œ λ¬Έν—Œ ꡬ쑰
κ·ΈλŒ€λ‘œ λͺ…μ‹œμ  SC ꡬ획 + μ†λ„μƒμˆ˜ κ΅¬λ™μœΌλ‘œ μ λΆ„ν•œλ‹€(μ•„λž˜ 주석 μ°Έμ‘°).
μ›Œν¬ν”Œλ‘œ (원본 SimBiology 검증: BA=96.8% μž¬ν˜„ 확인):
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 / IV PK ODE 적뢄 (λͺ¨λΈλ³„ ꡬ획 ꡬ쑰)
4. BA = AUC_SC(central) / AUC_IV(central) * 100
SimBiology λͺ¨λΈ νŒŒλΌλ―Έν„° (ν”Όν•˜μ£Όμ‚¬ SC λͺ¨λΈ, PPT '241127 to SG'):
ꡬ획: Central(3.557 L) / Peripheral(1.807 L) / Central lymph(0.312 L) / SC(0.0031 L)
k_12=0.0992 μ€‘μ•™β†’λ§μ΄ˆ
k_21=0.3448 λ§μ΄ˆβ†’μ€‘μ•™
k_input=0.1920 림프→쀑앙
k_10=0.0043 μ€‘μ•™β†’μ œκ±°(μ²­μ†Œ)
k_decay SC μ €μž₯κ³  λΆ„ν•΄(=λ¬Έν—Œ k_loss); 곑선 κ΅¬λ™μ—μ„œλŠ” 곑선에 내포
흐름: ν˜ˆκ΄€ν‘μˆ˜β†’μ€‘μ•™ 직접 / λ¦Όν”„ν‘μˆ˜β†’λ¦Όν”„κ΅¬νšβ†’(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 λͺ¨λΈ νŒŒλΌλ―Έν„° (PPT '241127 to SG') ──
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
# ── model 4 (Fig 3A) μž¬λΆ„ν¬ νŒŒλΌλ―Έν„° ──
# μ „μ‹  말초쑰직 β†’ λ¦Όν”„κ³„λ‘œμ˜ μž¬λ°°μ•‘(redistribution) 1μ°¨ μƒμˆ˜.
# Kagan et al. (2007)이 μ œμ•ˆν•œ 경둜. 값은 κ°€μ •κ°’(미검증).
K_RL_DEFAULT = 0.001
# ── model 5 (Fig 3B, Dahlberg 2014) νŒŒλΌλ―Έν„° ──
# λ…Όλ¬Έ 본문은 κ°œλ³„ 수치λ₯Ό ν‘œλ‘œ μ œκ³΅ν•˜μ§€ μ•ŠμœΌλ―€λ‘œ μ•„λž˜λŠ” ꡬ쑰 μ‹œμ—°μš©
# κ°€μ • κΈ°λ³Έκ°’(placeholder, 미검증)이닀. λ…Όλ¬Έμ˜ λŒ€μΉ­ μ œμ•½μ„ κ·ΈλŒ€λ‘œ 반영:
# k45 = k67, k36 = k34, k24 = k26, k52 = k72
M5_DEFAULTS = dict(
Vmax=5.0, # SC→쀑앙 Michaelis–Menten μ΅œλŒ€μ†λ„ (mg/hr) [κ°€μ •]
Km=1.0, # MM μƒμˆ˜ (mg) [κ°€μ •]
k14=0.05, # SC β†’ ν›„λ°© λ§μ΄ˆλ¦Όν”„(comp4) (1/hr) [κ°€μ •]
CLd=0.10, # 쀑앙 ↔ 말초 λΆ„ν¬μ²­μ†Œ 상당 1μ°¨μƒμˆ˜ (1/hr) [κ°€μ •]
k24=0.02, # 쀑앙 β†’ λ§μ΄ˆλ¦Όν”„(ν›„λ°© k24 = μ „λ°© k26) (1/hr) [κ°€μ •]
k34=0.02, # 말초 β†’ λ§μ΄ˆλ¦Όν”„(ν›„λ°© k34 = μ „λ°© k36) (1/hr) [κ°€μ •]
k45=0.30, # λ§μ΄ˆλ¦Όν”„ β†’ 쀑앙림프(ν›„λ°© k45 = μ „λ°© k67) (1/hr) [κ°€μ •]
k52=0.20, # 쀑앙림프 β†’ μ€‘μ•™κ΅¬νš(ν›„λ°© k52 = μ „λ°© k72) (1/hr) [κ°€μ •]
)
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
# ══════════════════════════════════════════════════════════════════
# SC 흑수 λͺ¨λΈ ODE (λ¬Έν—Œ 5λͺ¨λΈκ³Ό 동일 ꡬ쑰)
# ══════════════════════════════════════════════════════════════════
def _sc_ode_m1(y, t, ka, k10):
"""
λͺ¨λΈ 1 = λ…Όλ¬Έ Fig 1A (single-pathway).
λ¦Όν”„Β·ν˜ˆκ΄€ 미ꡬ뢄, 단일 흑수. μ „μ‹  1ꡬ획(νšŒμƒ‰ λ°•μŠ€).
λ¬Έν—Œ Fig 1A: ν‘μˆ˜λŠ” kaΒ·F둜 μ •μ„± 기술(식 번호 μ—†μŒ).
dC/dt = (흑수 μœ μž…) βˆ’ k10Β·C
곑선 ꡬ동: 흑수 μœ μž… = ka(t) = (λ¦Όν”„+ν˜ˆκ΄€ ν•©μ‚° 곑선)의 μœ μž…λ₯ .
"""
(c_cen,) = y
return [ka(t) - k10 * c_cen]
def _sc_ode_m2(y, t, kle, kve, k10):
"""
λͺ¨λΈ 2 = λ…Όλ¬Έ Fig 2A (dual-pathway, λ¦Όν”„ ꡬ획 μ—†μŒ).
ν˜ˆκ΄€Β·λ¦Όν”„ 두 κ²½λ‘œκ°€ λ‘˜ λ‹€ μ „μ‹ μœΌλ‘œ 직행. μ „μ‹  1ꡬ획(νšŒμƒ‰ λ°•μŠ€).
λ¬Έν—Œ Fig 2A(McLennan 2003, λ ™ν‹΄): dC/dt = (k_blood+k_lymph)Β·SC/Vc Β± disp,
k_lossλŠ” μ£Όμ‚¬λΆ€μœ„ presystemic λΆ„ν•΄(곑선에 내포).
dC/dt = kve(t) + kle(t) βˆ’ k10Β·C
"""
(c_cen,) = y
return [kve(t) + kle(t) - k10 * c_cen]
def _sc_ode_m3(y, t, kle, kve, k10, k12, k21, k_input):
"""
λͺ¨λΈ 3 = λ…Όλ¬Έ Fig 2B (별도 λ¦Όν”„ ꡬ획) = μ‚¬μš©μž PPT/SimBiology λͺ¨λΈ.
ν˜ˆκ΄€β†’μ€‘μ•™ 직접, λ¦Όν”„β†’λ¦Όν”„κ΅¬νšβ†’(k_input)→쀑앙. λ§μ΄ˆβ‡„μ€‘μ•™(k12,k21). μ „μ‹  2ꡬ획.
λ¬Έν—Œ Fig 2B (McLennan 2005a, Kota 2007) eq.9–11 및 PPT '241127 to SG':
dm_SC/dt = βˆ’αΉ_le βˆ’ ṁ_ve βˆ’ k_decayΒ·m_SC (곑선 κ΅¬λ™μ—μ„œλŠ” μƒλž΅)
dm_CL/dt = ṁ_le βˆ’ k_inputΒ·m_CL
dm_CC/dt = ṁ_ve βˆ’ (k12Β·m_CC βˆ’ k21Β·m_PC) + k_inputΒ·m_CL βˆ’ k10Β·m_CC
dm_PC/dt = k12Β·m_CC βˆ’ k21Β·m_PC
μ—¬κΈ°μ„œ ṁ_ve = kve(t), ṁ_le = kle(t) (곑선 μœ μž…λ₯ ).
μƒνƒœ μˆœμ„œ: [쀑앙 CC, 말초 PC, 쀑앙림프 CL].
"""
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]
# ν•˜μœ„ν˜Έν™˜ 별칭 (ꡬ버전 이름)
_sc_ode = _sc_ode_m3
_sc_ode_dyn = _sc_ode_m3
def _sc_ode_m4(y, t, kle, kve, k10, k12, k21, k_input, k_rl=K_RL_DEFAULT):
"""
λͺ¨λΈ 4 = λ…Όλ¬Έ Fig 3A (redistribution, Kagan 2007).
Fig 2B ꡬ쑰 + 'μ „μ‹  말초쑰직 β†’ 림프계' μž¬λ°°μ•‘(μž¬μˆœν™˜).
λ¬Έν—Œ Fig 3A 도식: SCβ†’λ¦Όν”„κ³„Β·ν˜ˆμž₯, λ¦Όν”„κ³„β†’ν˜ˆμž₯(k_input),
혈μž₯ ↔ 말초쑰직(k12,k21), **말초쑰직 β†’ 림프계(k_rl)**, 혈μž₯ μ†Œμ‹€(k10).
μ „μ‹  2ꡬ획(혈μž₯ + 말초쑰직).
dc_lym = kle(t) βˆ’ k_inputΒ·c_lym + k_rlΒ·c_per (λ§μ΄ˆμ‘°μ§β†’λ¦Όν”„ μž¬λΆ„ν¬)
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 βˆ’ k_rlΒ·c_per
μƒνƒœ μˆœμ„œ: [쀑앙 c_cen, 말초쑰직 c_per, 림프계 c_lym].
"""
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 - k_rl * c_per
dc_lym = kle(t) - k_input * c_lym + k_rl * c_per
return [dc_cen, dc_per, dc_lym]
def _sc_ode_m5(y, t, k10, p):
"""
λͺ¨λΈ 5 = λ…Όλ¬Έ Fig 3B (Dahlberg 2014, trastuzumab, 7ꡬ획 λΉ„μ„ ν˜•).
ꡬ획 번호(λ…Όλ¬Έκ³Ό 동일):
1: SC μ£Όμ‚¬λΆ€μœ„
2: Central(혈μž₯) 3: Peripheral(말초쑰직)
4: ν›„λ°© λ§μ΄ˆλ¦Όν”„ 5: ν›„λ°© 쀑앙림프
6: μ „λ°© λ§μ΄ˆλ¦Όν”„ 7: μ „λ°© 쀑앙림프
흑수: SC(1)β†’ν›„λ°© λ§μ΄ˆλ¦Όν”„(4) 1μ°¨(k14) + SC(1)→쀑앙(2) Michaelis–Menten(Vmax,Km).
μž¬λΆ„ν¬: 쀑앙(2)β†’λ§μ΄ˆλ¦Όν”„(k24=k26), 말초(3)β†’λ§μ΄ˆλ¦Όν”„(k34=k36).
λ¦Όν”„ 배좜: λ§μ΄ˆλ¦Όν”„β†’μ€‘μ•™λ¦Όν”„(k45=k67), 쀑앙림프→쀑앙(k52=k72).
μ „μ‹  뢄포: 쀑앙 ↔ 말초 (CLd), 쀑앙 제거 k10(=CL/V).
λŒ€μΉ­ μ œμ•½(λ…Όλ¬Έ): k45=k67, k36=k34, k24=k26, k52=k72.
dm1 = βˆ’k14Β·m1 βˆ’ VmaxΒ·m1/(Km+m1)
dm2 = VmaxΒ·m1/(Km+m1) + k52Β·m5 + k72Β·m7 βˆ’ k10Β·m2 βˆ’ CLdΒ·m2 + CLdΒ·m3 βˆ’ (k24+k26)Β·m2
dm3 = CLdΒ·m2 βˆ’ CLdΒ·m3 βˆ’ (k34+k36)Β·m3
dm4 = k14Β·m1 + k24Β·m2 + k34Β·m3 βˆ’ k45Β·m4
dm5 = k45Β·m4 βˆ’ k52Β·m5
dm6 = k26Β·m2 + k36Β·m3 βˆ’ k67Β·m6
dm7 = k67Β·m6 βˆ’ k72Β·m7
주의: 이 λͺ¨λΈμ˜ ν‘μˆ˜λŠ” λͺ…μ‹œμ  SC ꡬ획 + MM μˆ˜μ†‘μ΄λΌ λŒ€λ¦¬λͺ¨λΈ 곑선
ꡬ동과 ν˜Έν™˜λ˜μ§€ μ•Šμ•„ μ†λ„μƒμˆ˜ κ΅¬λ™μœΌλ‘œ μ λΆ„ν•œλ‹€. λ˜ν•œ λŒ€λ¦¬λͺ¨λΈμ€ λ¦Όν”„
좜λ ₯이 단일 채널뿐이라 μ „λ°©/ν›„λ°© 림프계λ₯Ό μ‹€μΈ‘ 뢄리할 수 μ—†λ‹€(식별 λΆˆκ°€).
μ•„λž˜ M5_DEFAULTSλŠ” ꡬ쑰 μ‹œμ—°μš© κ°€μ •κ°’(placeholder)이닀.
"""
m1, m2, m3, m4, m5, m6, m7 = y
Vmax, Km = p["Vmax"], p["Km"]
k14, CLd = p["k14"], p["CLd"]
k24 = k26 = p["k24"] # λŒ€μΉ­ μ œμ•½
k34 = k36 = p["k34"]
k45 = k67 = p["k45"]
k52 = k72 = p["k52"]
mm = Vmax * m1 / (Km + m1)
dm1 = -k14 * m1 - mm
dm2 = mm + k52 * m5 + k72 * m7 - k10 * m2 - CLd * m2 + CLd * m3 - (k24 + k26) * m2
dm3 = CLd * m2 - CLd * m3 - (k34 + k36) * m3
dm4 = k14 * m1 + k24 * m2 + k34 * m3 - k45 * m4
dm5 = k45 * m4 - k52 * m5
dm6 = k26 * m2 + k36 * m3 - k67 * m6
dm7 = k67 * m6 - k72 * m7
return [dm1, dm2, dm3, dm4, dm5, dm6, dm7]
# ══════════════════════════════════════════════════════════════════
# IV λͺ¨λΈ ODE (SC λͺ¨λΈμ˜ μ „μ‹  ꡬ획 ꡬ쑰와 일치)
# ══════════════════════════════════════════════════════════════════
def _iv_ode_1pool(y, t, k10):
"""1ꡬ획 IV (λͺ¨λΈ 1,2용). t=0 μ „λŸ‰ νˆ¬μ—¬ ν›„ 1μ°¨ 제거."""
(c_cen,) = y
return [-k10 * c_cen]
def _iv_ode_2pool(y, t, k10, k12, k21):
"""2ꡬ획 IV (λͺ¨λΈ 3,4용): 쀑앙 ↔ 말초, 쀑앙 제거."""
c_cen, c_per = y
return [-k10 * c_cen - k12 * c_cen + k21 * c_per,
k12 * c_cen - k21 * c_per]
def _iv_ode_m5(y, t, k10, p):
"""
λͺ¨λΈ 5 IV: SC ꡬ획(1) 없이 IV doseλ₯Ό 쀑앙(2)에 직접 νˆ¬μ—¬.
λ‚˜λ¨Έμ§€ 6ꡬ획(2,3,4,5,6,7)의 μ „μ‹  λΆ„ν¬Β·μž¬λΆ„ν¬Β·λ¦Όν”„ λ°°μΆœμ€ SC와 동일.
μƒνƒœ μˆœμ„œ: [m2, m3, m4, m5, m6, m7].
"""
m2, m3, m4, m5, m6, m7 = y
CLd = p["CLd"]
k24 = k26 = p["k24"]
k34 = k36 = p["k34"]
k45 = k67 = p["k45"]
k52 = k72 = p["k52"]
dm2 = k52 * m5 + k72 * m7 - k10 * m2 - CLd * m2 + CLd * m3 - (k24 + k26) * m2
dm3 = CLd * m2 - CLd * m3 - (k34 + k36) * m3
dm4 = k24 * m2 + k34 * m3 - k45 * m4
dm5 = k45 * m4 - k52 * m5
dm6 = k26 * m2 + k36 * m3 - k67 * m6
dm7 = k67 * m6 - k72 * m7
return [dm2, dm3, dm4, dm5, dm6, dm7]
# ν•˜μœ„ν˜Έν™˜ 별칭
_iv_ode = lambda y, t: _iv_ode_2pool(y, t, K_10, K_12, K_21)
_iv_ode_dyn = _iv_ode_2pool
def compute_bioavailability(c_lymph, c_vessel, t_min, dose=DEFAULT_DOSE,
drug="IgG", model=3, m5_params=None,
return_detail=False):
"""
λ¦Όν”„Β·ν˜ˆκ΄€ λˆ„μ  ν‘μˆ˜κ³‘μ„  β†’ μƒμ²΄μ΄μš©λ₯ (%).
인자:
c_lymph, c_vessel : λˆ„μ  % mass 곑선 (λŒ€λ¦¬λͺ¨λΈ 예츑)
t_min : μ‹œκ°„μΆ• (λΆ„)
dose : νˆ¬μ—¬λŸ‰ (mg)
drug : μ•½λ¬Ό μ’…λ₯˜ ("IgG","INS","ALB") λ˜λŠ” MW(숫자)
model : 1=Fig1A, 2=Fig2A, 3=Fig2B(PPT), 4=Fig3A, 5=Fig3B
m5_params : model 5 μ†λ„μƒμˆ˜ dict (μ—†μœΌλ©΄ M5_DEFAULTS)
λ°˜ν™˜:
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:
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 κΈ°μ€€) ──
if 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(λ§μ΄ˆμ‘°μ§β†’λ¦Όν”„), μ „μ‹  2ꡬ획
sc = odeint(_sc_ode_m4, [0, 0, 0], tt,
args=(kle, kve, k10, k12, k21, k_input, K_RL_DEFAULT))
iv = odeint(_iv_ode_2pool, [dose, 0], tt, args=(k10, k12, k21))
elif model == 5:
# Fig 3B: 7ꡬ획 λΉ„μ„ ν˜•. μ†λ„μƒμˆ˜ ꡬ동(곑선 ꡬ동과 λΉ„ν˜Έν™˜).
p = dict(M5_DEFAULTS)
if m5_params:
p.update(m5_params)
sc7 = odeint(_sc_ode_m5, [dose, 0, 0, 0, 0, 0, 0], tt, args=(k10, p))
# 쀑앙(comp2)만 BA에 μ‚¬μš©
sc = np.column_stack([sc7[:, 1], sc7[:, 2], np.zeros(len(tt))])
iv6 = odeint(_iv_ode_m5, [dose, 0, 0, 0, 0, 0], tt, args=(k10, p))
iv = np.column_stack([iv6[:, 0], iv6[:, 1]])
else: # model == 3 (Fig 2B, PPT/SimBiology, κΈ°λ³Έ)
sc = odeint(_sc_ode_m3, [0, 0, 0], tt,
args=(kle, kve, k10, k12, k21, k_input))
iv = odeint(_iv_ode_2pool, [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)
def compute_bioavailability_ci(c_lymph, c_vessel, t_min, oof_resid,
dose=DEFAULT_DOSE, drug="IgG", model=3,
m5_params=None, n_mc=300, levels=(95.0, 99.0),
seed=42, lymph_idx=0, vessel_idx=1):
"""
λŒ€λ¦¬λͺ¨λΈ λΆˆν™•μ‹€μ„±μ„ μ „νŒŒν•œ BA μ˜ˆμΈ‘κ΅¬κ°„(신뒰ꡬ간).
BAλŠ” λŒ€λ¦¬λͺ¨λΈμ΄ μ˜ˆμΈ‘ν•œ 농도곑선(c_lymph, c_vessel)으둜 κ³„μ‚°λ˜λŠ”λ°, κ·Έ κ³‘μ„ μ—λŠ”
honest OOF μž”μ°¨λ‘œ μ •λŸ‰ν™”λœ λΆˆν™•μ‹€μ„±(conformal μ˜ˆμΈ‘κ΅¬κ°„)이 μžˆλ‹€. 이 ν•¨μˆ˜λŠ” κ·Έ
μž”μ°¨λ₯Ό 곑선에 λͺ¬ν…ŒμΉ΄λ₯Όλ‘œλ‘œ μ£Όμž…ν•΄ μ—¬λŸ¬ 개의 κ·ΈλŸ΄λ“―ν•œ 곑선을 λ§Œλ“€κ³ , 각 κ³‘μ„ μ˜
BAλ₯Ό 계산해 BA 뢄포 β†’ percentile μ˜ˆμΈ‘κ΅¬κ°„μ„ μ–»λŠ”λ‹€.
인자:
c_lymph, c_vessel : λˆ„μ  % mass 곑선 (λŒ€λ¦¬λͺ¨λΈ base 예츑), 각 (n_t,)
t_min : μ‹œκ°„μΆ• (λΆ„)
oof_resid : OOF μž”μ°¨ λ°°μ—΄ (n_cases, n_t, n_out) = μ‹€μ œ βˆ’ μ•™μƒλΈ”μ˜ˆμΈ‘.
μ‹œμ  κ°„ 상관을 λ³΄μ‘΄ν•˜κΈ° μœ„ν•΄ μž”μ°¨ 'ν–‰'(μΌ€μ΄μŠ€) 전체λ₯Ό
λΆ€νŠΈμŠ€νŠΈλž© μƒ˜ν”Œλ§ν•œλ‹€.
dose, drug, model, m5_params : compute_bioavailability와 동일
n_mc : λͺ¬ν…ŒμΉ΄λ₯Όλ‘œ ν‘œλ³Έ 수
levels : μ˜ˆμΈ‘κ΅¬κ°„ μˆ˜μ€€(%) νŠœν”Œ, 예 (95, 99)
seed : λ‚œμˆ˜ μ‹œλ“œ
lymph_idx,vessel_idx : oof_resid의 ꡬ획 μΆ•μ—μ„œ lymphΒ·vessel μœ„μΉ˜
λ°˜ν™˜:
dict: base, median, std, ci({level:(lo,hi)}), samples, n_valid,
curve_bands({time_hr, sc_base, iv_curve, sc_lo95/hi95, sc_lo99/hi99})
β€” curve_bandsλŠ” SC 혈μž₯κ³‘μ„ μ˜ μ‹œμ λ³„ 95%/99% 예츑 λ°΄λ“œ(κ·Έλž˜ν”„μš©).
"""
c_lymph = np.asarray(c_lymph, float)
c_vessel = np.asarray(c_vessel, float)
oof_resid = np.asarray(oof_resid, float)
n_cases = oof_resid.shape[0]
def _detail(cl, cv):
return compute_bioavailability(cl, cv, t_min, dose=dose, drug=drug,
model=model, m5_params=m5_params,
return_detail=True)
base_d = _detail(c_lymph, c_vessel)
base = base_d["BA"]
time_hr = np.asarray(base_d["time_hr"], float) # SC/IV 곑선 μ‹œκ°„μΆ•(hr)
iv_curve = np.asarray(base_d["iv_curve"], float)
sc_base = np.asarray(base_d["sc_curve"], float)
rng = np.random.default_rng(seed)
il = rng.integers(0, n_cases, n_mc)
iv = rng.integers(0, n_cases, n_mc)
samples = []
sc_curves = [] # ν‘œλ³Έλ³„ SC 혈μž₯곑선
for i in range(n_mc):
cl = np.clip(c_lymph + oof_resid[il[i], :, lymph_idx], 0.0, None)
cv = np.clip(c_vessel + oof_resid[iv[i], :, vessel_idx], 0.0, None)
try:
d = _detail(cl, cv)
except Exception:
continue
b = d["BA"]
if np.isfinite(b):
samples.append(b)
sc_curves.append(np.asarray(d["sc_curve"], float))
samples = np.asarray(samples)
sc_curves = np.asarray(sc_curves) # (n_valid, n_tt)
ci = {}
for lv in levels:
a = (100.0 - lv) / 2.0
ci[lv] = (float(np.percentile(samples, a)),
float(np.percentile(samples, 100.0 - a)))
# μ‹œμ λ³„ SC 혈μž₯곑선 λ°΄λ“œ (예츑 κ·Έλž˜ν”„μ™€ λ™μΌν•œ 95%/99% ν‘œν˜„)
curve_bands = {"time_hr": time_hr, "sc_base": sc_base, "iv_curve": iv_curve}
if len(sc_curves):
for lv in levels:
a = (100.0 - lv) / 2.0
curve_bands[f"sc_lo{int(lv)}"] = np.percentile(sc_curves, a, axis=0)
curve_bands[f"sc_hi{int(lv)}"] = np.percentile(sc_curves, 100.0 - a, axis=0)
return {
"base": float(base),
"median": float(np.median(samples)),
"std": float(samples.std()),
"ci": ci,
"samples": samples,
"n_valid": int(len(samples)),
"curve_bands": curve_bands,
}
def compute_pk_metrics_ci(c_lymph, c_vessel, t_min, conf_half,
dose=DEFAULT_DOSE, drug="IgG", model=3,
m5_params=None):
"""
conformal μ˜ˆμΈ‘λ°΄λ“œλ₯Ό κ·ΈλŒ€λ‘œ μ „νŒŒν•œ 약동학 μ§€ν‘œ ꡬ간 (λͺ¬ν…ŒμΉ΄λ₯Όλ‘œ μ—†μŒ, 빠름).
농도 예츑 κ·Έλž˜ν”„μ— μ“°λŠ” 것과 'λ™μΌν•œ' μ‹œμ λ³„ conformal 반폭(conf_half)을
λ¦Όν”„Β·ν˜ˆκ΄€ 곑선에 μ μš©ν•΄ μƒν•œ/ν•˜ν•œ ν‘μˆ˜κ³‘μ„ μ„ λ§Œλ“€κ³ , 각각으둜 SC 혈μž₯곑선을
μ λΆ„ν•œλ‹€. κ·Έ κ²°κ³Όκ°€ BA κ·Έλž˜ν”„μ— λ³΄μ΄λŠ” SC λ°΄λ“œ(sc_lo/hi)λ‹€. μ§€ν‘œλŠ”:
- BA, Tmax : μƒΒ·ν•˜ν•œ κ³‘μ„ μ˜ detailμ—μ„œ 직접
- Ka, tΒ½ : μƒΒ·ν•˜ν•œ SC '혈μž₯곑선'을 흑수-μ†Œμ‹€ μ΄μ€‘μ§€μˆ˜λ‘œ ν”ΌνŒ…ν•΄ μΆ”μΆœ
(SC 혈μž₯곑선을 ν”ΌνŒ…ν•˜λ―€λ‘œ ν‘μˆ˜κ³‘μ„  ν”ΌνŒ…κ³Ό 달리 μ•ˆμ •μ )
λͺ¨λ“  ꡬ간은 base 값을 ν¬ν•¨ν•˜λ„λ‘ [min(base,lo,hi), max(base,lo,hi)]둜 λ§Œλ“ λ‹€
(λ°΄λ“œκ°€ μ‹œκ°„μ— 따라 λΉ„λŒ€μΉ­μ΄λΌ μƒΒ·ν•˜ν•œ μ§€ν‘œκ°€ ν•œμͺ½μœΌλ‘œ 쏠릴 수 μžˆμœΌλ―€λ‘œ).
인자:
c_lymph, c_vessel : λŒ€λ¦¬λͺ¨λΈ base 예츑 곑선 (n_t,)
t_min : μ‹œκ°„μΆ• (λΆ„)
conf_half : {level(%): (q_lymph, q_vessel)} μ‹œμ λ³„ 반폭 λ°°μ—΄ dict.
dose, drug, model, m5_params : compute_bioavailability와 동일
λ°˜ν™˜:
dict:
base : base κ³‘μ„ μ˜ detail
metrics : {"BA","Ka","Tmax_hr","t_half_hr"} 각각 base 점좔정 κ°’
ci : {metric: {level: (lo, hi)}} β€” 4개 μ§€ν‘œ λͺ¨λ‘ ꡬ간 λΆ€μ—¬
curve_bands : {time_hr, sc_base, iv_curve, sc_lo/hi95, sc_lo/hi99}
"""
c_lymph = np.asarray(c_lymph, float)
c_vessel = np.asarray(c_vessel, float)
def _detail(cl, cv):
return compute_bioavailability(cl, cv, t_min, dose=dose, drug=drug,
model=model, m5_params=m5_params,
return_detail=True)
def _sc_fit(t_hr_curve, sc):
"""SC 혈μž₯곑선을 흑수-μ†Œμ‹€ μ΄μ€‘μ§€μˆ˜ A*(e^{-ke t}-e^{-ka t})둜 ν”ΌνŒ….
λ°˜ν™˜ (Ka=ka, t_half=ln2/ke). μ‹€νŒ¨ μ‹œ (nan, nan)."""
def _biexp(t, A, ka, ke):
return A * (np.exp(-ke * t) - np.exp(-ka * t))
ip = int(np.argmax(sc)); cmax = float(sc[ip])
if cmax <= 0:
return float("nan"), float("nan")
try:
popt, _ = curve_fit(_biexp, t_hr_curve, sc,
p0=[cmax * 2, 0.1, 0.01], maxfev=10000,
bounds=([0, 1e-4, 1e-5], [np.inf, 50, 10]))
_, ka, ke = popt
ka, ke = max(ka, ke), min(ka, ke) # ka(흑수) > ke(μ†Œμ‹€)
return float(ka), float(np.log(2) / ke)
except Exception:
return float("nan"), float("nan")
base_d = _detail(c_lymph, c_vessel)
t_hr_curve = np.asarray(base_d["time_hr"], float)
Ka0, th0 = _sc_fit(t_hr_curve, np.asarray(base_d["sc_curve"], float))
base_metrics = {"BA": base_d["BA"], "Ka": Ka0,
"Tmax_hr": base_d["Tmax_hr"], "t_half_hr": th0}
metric_names = ["BA", "Ka", "Tmax_hr", "t_half_hr"]
ci = {m: {} for m in metric_names}
curve_bands = {"time_hr": t_hr_curve, "sc_base": base_d["sc_curve"],
"iv_curve": base_d["iv_curve"]}
for lv, (qL, qV) in conf_half.items():
qL = np.asarray(qL, float); qV = np.asarray(qV, float)
cl_lo = np.clip(c_lymph - qL, 0, None); cl_hi = c_lymph + qL
cv_lo = np.clip(c_vessel - qV, 0, None); cv_hi = c_vessel + qV
d_lo = _detail(cl_lo, cv_lo) # ν‘μˆ˜κ³‘μ„  ν•˜ν•œ β†’ SC 혈μž₯곑선 ν•˜ν•œ
d_hi = _detail(cl_hi, cv_hi) # ν‘μˆ˜κ³‘μ„  μƒν•œ β†’ SC 혈μž₯곑선 μƒν•œ
sc_lo = np.asarray(d_lo["sc_curve"], float)
sc_hi = np.asarray(d_hi["sc_curve"], float)
Ka_lo, th_lo = _sc_fit(t_hr_curve, sc_lo) # SC λ°΄λ“œ 곑선 직접 ν”ΌνŒ…
Ka_hi, th_hi = _sc_fit(t_hr_curve, sc_hi)
vals = {
"BA": (d_lo["BA"], d_hi["BA"]),
"Ka": (Ka_lo, Ka_hi),
"Tmax_hr": (d_lo["Tmax_hr"], d_hi["Tmax_hr"]),
"t_half_hr": (th_lo, th_hi),
}
for m in metric_names:
a, b = vals[m]
base_v = base_metrics[m]
cand = [x for x in (a, b, base_v) if np.isfinite(x)] # base 포함
ci[m][lv] = (float(min(cand)), float(max(cand)))
curve_bands[f"sc_lo{int(lv)}"] = sc_lo
curve_bands[f"sc_hi{int(lv)}"] = sc_hi
return {"base": base_d, "metrics": base_metrics,
"ci": ci, "curve_bands": curve_bands}
# ── 자체 검증 (원본 SimBiology BA=96.8% μž¬ν˜„, model 3 / Fig 2B) ──
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_m3, [0, 0, 0], tt,
args=(kle, kve, K_10, K_12, K_21, K_INPUT))
iv = odeint(_iv_ode_2pool, [dose, 0], tt, args=(K_10, K_12, K_21))
ba = _trapz(sc[:, 0], tt) / _trapz(iv[:, 0], tt) * 100
print(f"검증(model 3, Fig 2B): BA={ba:.1f}% (원본 SimBiology 96.8%, 였차 {abs(ba-96.8):.2f}%p)")