Source code for openpkpd.pk.pbpk

"""
Physiologically-based pharmacokinetic (PBPK) models.

Provides a base class wrapping ADVAN6 with organ-specific compartment naming
and a 5-organ human PBPK template (lung, liver, kidney, gut, central/blood).

Usage::

    from openpkpd.pk.pbpk import FiveOrganPBPK

    model = FiveOrganPBPK()
    sol = model.solve(pk_params, dose_events, obs_times, des_callable=my_des)
"""

from __future__ import annotations

from collections.abc import Callable

import numpy as np

from openpkpd.pk.base import PKSolution, PKSubroutine
from openpkpd.pk.ode.advan6 import ADVAN6

# G1 ── PBPK base class ────────────────────────────────────────────────────────


[docs] class PBPKModel(PKSubroutine): """ Base class for PBPK models. Wraps ADVAN6 (generic ODE solver) with organ-specific compartment naming. Subclasses define ``compartment_names`` and the DES callable describing inter-organ blood flow and metabolic/elimination processes. Attributes: compartment_names: Ordered list of compartment names (length must equal the number of ODEs in the DES block). output_compartment: Name of the compartment used to compute IPRED. Must appear in ``compartment_names``. """ compartment_names: list[str] = [] output_compartment_name: str = "central"
[docs] def __init__(self) -> None: n = len(self.compartment_names) if n == 0: raise ValueError("PBPKModel subclass must define compartment_names") self.n_compartments = n self._advan6 = ADVAN6(n_compartments=n) # index (0-based) of the output compartment try: self._output_idx = self.compartment_names.index(self.output_compartment_name) except ValueError: self._output_idx = 0
# ── Compartment helpers ──────────────────────────────────────────────────
[docs] def compartment_index(self, name: str) -> int: """Return the 1-based compartment index for the given organ name.""" return self.compartment_names.index(name) + 1
# ── Solve ────────────────────────────────────────────────────────────────
[docs] def solve( self, pk_params: dict[str, float], dose_events: list, obs_times: np.ndarray, pk_callable: Callable | None = None, des_callable: Callable | None = None, ) -> PKSolution: """ Solve the PBPK model ODE system via ADVAN6. The ``des_callable`` must define dA(n)/dt for each compartment in the order specified by ``compartment_names``. IPRED is extracted from compartment ``output_compartment_name`` divided by pk_params['V'] (or 'V_central') if present. """ # Delegate integration to ADVAN6 sol = self._advan6.solve( pk_params, dose_events, obs_times, pk_callable=pk_callable, des_callable=des_callable, ) # Override output compartment if needed if sol.amounts is not None and sol.amounts.shape[1] > self._output_idx: v_key = f"V_{self.output_compartment_name}" v = pk_params.get(v_key, pk_params.get("V", 1.0)) ipred = sol.amounts[:, self._output_idx] / max(float(v), 1e-12) ipred = np.maximum(ipred, 0.0) return PKSolution(times=sol.times, amounts=sol.amounts, ipred=ipred) return sol
[docs] def apply_trans(self, pk_params: dict, trans: int = 1) -> dict: """PBPK models use TRANS1 (micro-parameters directly).""" return pk_params
# G1 ── 5-organ human PBPK template ──────────────────────────────────────────
[docs] class FiveOrganPBPK(PBPKModel): """ Five-organ human PBPK template. Organs (compartments in order): 0: lung 1: liver 2: kidney 3: gut 4: central (blood / plasma) Typical parameters (all flows in L/h, volumes in L): Q_lung, Q_liver, Q_kidney, Q_gut — organ blood flows V_lung, V_liver, V_kidney, V_gut, V_central — organ volumes CL_liver, CL_kidney — metabolic clearances Kp_lung, Kp_liver, Kp_kidney, Kp_gut — tissue:plasma partitioning DES example (to be compiled and passed as des_callable):: DADT(1) = Q_lung * (C_central - A(1)/V_lung / Kp_lung) ; lung DADT(2) = Q_liver * (C_central - A(2)/V_liver / Kp_liver) - CL_liver*(A(2)/V_liver) DADT(3) = Q_kidney*(C_central - A(3)/V_kidney/ Kp_kidney)- CL_kidney*(A(3)/V_kidney) DADT(4) = Q_gut * (C_central - A(4)/V_gut / Kp_gut) DADT(5) = -(Q_lung+Q_liver+Q_kidney+Q_gut)*C_central \\ + Q_lung*(A(1)/V_lung/Kp_lung) \\ + Q_liver*(A(2)/V_liver/Kp_liver) \\ + Q_kidney*(A(3)/V_kidney/Kp_kidney) \\ + Q_gut*(A(4)/V_gut/Kp_gut) where C_central = A(5) / V_central. """ compartment_names = ["lung", "liver", "kidney", "gut", "central"] output_compartment_name = "central"
# ── Exports ─────────────────────────────────────────────────────────────────── __all__ = [ "PBPKModel", "FiveOrganPBPK", ]