-
Notifications
You must be signed in to change notification settings - Fork 1
fix(renal): account for passive tubular reabsorption #110
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,174 @@ | ||
| { | ||
| "date": "2026-09-22", | ||
| "change": "renal CL: filtration-only -> filtration + passive tubular reabsorption", | ||
| "model": "CL_renal = fup*UF*(GFR+PS)/(UF+PS), PS = peff*TSA", | ||
| "source": { | ||
| "ref": "Scotcher et al. 2016, Eur J Pharm Sci 94:59-71", | ||
| "doi": "10.1016/j.ejps.2016.03.018", | ||
| "note": "one-region reduction of the published 5-compartment model; verified algebraically identical to fup*GFR*(1-F_reabs), F_reabs=F'(1-UF/GFR), F'=PS/(PS+UF)" | ||
| }, | ||
| "constants": { | ||
| "GFR_L_PER_H": 7.5, | ||
| "URINE_FLOW_L_PER_H": 0.06, | ||
| "TUBULAR_SURFACE_AREA_CM2": 57.8, | ||
| "tsa_provenance": "back-calculated from the paper's F'=0.5 anchor (Caco-2 Papp 14.8e-6 cm/s) through adme._CACO2_TO_INVIVO_OFFSET; effective lumped area, NOT fitted to Cmax loss (Invariant #8)" | ||
| }, | ||
| "renal_cl_vs_clinical": { | ||
| "caffeine": { | ||
| "fup": 0.7009, | ||
| "peff": 9.105, | ||
| "clinical_cl_renal_l_h": 0.1, | ||
| "old_l_h": 5.257, | ||
| "new_l_h": 1.296, | ||
| "old_fold": 52.57, | ||
| "new_fold": 12.96 | ||
| }, | ||
| "acetaminophen": { | ||
| "fup": 0.7585, | ||
| "peff": 5.303, | ||
| "clinical_cl_renal_l_h": 0.6, | ||
| "old_l_h": 5.688, | ||
| "new_l_h": 2.033, | ||
| "old_fold": 9.48, | ||
| "new_fold": 3.39 | ||
| }, | ||
| "antipyrine": { | ||
| "fup": 0.5113, | ||
| "peff": 11.448, | ||
| "clinical_cl_renal_l_h": 0.15, | ||
| "old_l_h": 3.835, | ||
| "new_l_h": 0.796, | ||
| "old_fold": 25.56, | ||
| "new_fold": 5.31 | ||
| }, | ||
| "theophylline": { | ||
| "fup": 0.5796, | ||
| "peff": 6.569, | ||
| "clinical_cl_renal_l_h": 0.4, | ||
| "old_l_h": 4.347, | ||
| "new_l_h": 1.35, | ||
| "old_fold": 10.87, | ||
| "new_fold": 3.38 | ||
| }, | ||
| "midazolam": { | ||
| "fup": 0.0318, | ||
| "peff": 7.563, | ||
| "clinical_cl_renal_l_h": 0.02, | ||
| "old_l_h": 0.239, | ||
| "new_l_h": 0.067, | ||
| "old_fold": 11.94, | ||
| "new_fold": 3.36 | ||
| }, | ||
| "atenolol": { | ||
| "fup": 0.3394, | ||
| "peff": 0.812, | ||
| "clinical_cl_renal_l_h": 8.0, | ||
| "old_l_h": 2.546, | ||
| "new_l_h": 1.991, | ||
| "old_fold": 0.32, | ||
| "new_fold": 0.25 | ||
| }, | ||
| "gabapentin": { | ||
| "fup": 0.946, | ||
| "peff": 4.819, | ||
| "clinical_cl_renal_l_h": 6.0, | ||
| "old_l_h": 7.095, | ||
| "new_l_h": 2.691, | ||
| "old_fold": 1.18, | ||
| "new_fold": 0.45 | ||
| }, | ||
| "metformin": { | ||
| "fup": 0.8573, | ||
| "peff": 0.229, | ||
| "clinical_cl_renal_l_h": 31.0, | ||
| "old_l_h": 6.43, | ||
| "new_l_h": 5.961, | ||
| "old_fold": 0.21, | ||
| "new_fold": 0.19 | ||
| }, | ||
| "lisinopril": { | ||
| "fup": 0.1461, | ||
| "peff": 0.605, | ||
| "clinical_cl_renal_l_h": 6.0, | ||
| "old_l_h": 1.096, | ||
| "new_l_h": 0.907, | ||
| "old_fold": 0.18, | ||
| "new_fold": 0.15 | ||
| } | ||
| }, | ||
| "holdout_107_public_clone": { | ||
| "note": "local stack reproduces the committed cache bit-identically", | ||
| "baseline": { | ||
| "meta": { | ||
| "aafe": 2.7428, | ||
| "pct_2fold": 44.9, | ||
| "pct_3fold": 63.6 | ||
| }, | ||
| "engine": { | ||
| "aafe": 4.2779, | ||
| "pct_2fold": 27.1, | ||
| "pct_3fold": 43.9 | ||
| }, | ||
| "ml": { | ||
| "aafe": 2.9981, | ||
| "pct_2fold": 43.0, | ||
| "pct_3fold": 58.9 | ||
| } | ||
| }, | ||
| "renal": { | ||
| "meta": { | ||
| "aafe": 2.7416, | ||
| "pct_2fold": 44.9, | ||
| "pct_3fold": 63.6 | ||
| }, | ||
| "engine": { | ||
| "aafe": 4.2548, | ||
| "pct_2fold": 27.1, | ||
| "pct_3fold": 43.0 | ||
| }, | ||
| "ml": { | ||
| "aafe": 2.9981, | ||
| "pct_2fold": 43.0, | ||
| "pct_3fold": 58.9 | ||
| } | ||
| }, | ||
| "delta_meta": -0.0012, | ||
| "delta_engine": -0.0232, | ||
| "in_domain_n": 81, | ||
| "in_domain_meta": [ | ||
| 2.7906, | ||
| 2.7894 | ||
| ], | ||
| "n_engine_cmax_moved": 37, | ||
| "max_move": "<=4%" | ||
| }, | ||
| "pkdb_curve_fold_error": { | ||
| "note": "open-licence PK-DB timecourses, old vs new renal on the same canonical engine", | ||
| "caffeine_oral": [ | ||
| 28.04, | ||
| 24.91 | ||
| ], | ||
| "acetaminophen_iv": [ | ||
| 32.67, | ||
| 29.2 | ||
| ], | ||
| "acetaminophen_oral": [ | ||
| 22.45, | ||
| 21.36 | ||
| ], | ||
| "midazolam_iv": [ | ||
| 1.96, | ||
| 1.95 | ||
| ], | ||
| "midazolam_oral": [ | ||
| 2.01, | ||
| 2.01 | ||
| ] | ||
| }, | ||
| "not_modelled": [ | ||
| "active tubular secretion", | ||
| "pH/ionisation-dependent reabsorption", | ||
| "peff uncertainty in the returned cv" | ||
| ], | ||
| "cache_regen": "required on the canonical Linux stack; delta is within the +/-0.020 pin so the existing cache still passes" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -42,6 +42,26 @@ | |
| # GFR: glomerular filtration rate (L/h) | ||
| _GFR_L_PER_H = 7.5 # ~125 mL/min = 7.5 L/h | ||
|
|
||
| # Urine flow at the end of the nephron (L/h). Scotcher et al. 2016, | ||
| # Eur J Pharm Sci 94:59-71 (doi:10.1016/j.ejps.2016.03.018) assume 1 mL/min. | ||
| _URINE_FLOW_L_PER_H = 0.06 # 1 mL/min | ||
|
|
||
| # Effective lumped tubular surface area (cm^2) for scaling peff -> PS. | ||
| # Back-calculated from the Scotcher 2016 mechanistic-model calibration point: | ||
| # their 5-compartment model reaches F' = 0.5 (half of urine/plasma equilibrium) | ||
| # at Caco-2 Papp = 14.8e-6 cm/s under a pH 6.5/7.4 gradient. In the | ||
| # one-compartment reduction used below F' = PS/(PS + UF), so F' = 0.5 means | ||
| # PS = UF = 0.06 L/h. Sisyphus `peff` is in-vivo-calibrated Caco-2 | ||
| # (peff = 10**1.29 * Papp ~= 19.5x, adme._CACO2_TO_INVIVO_OFFSET), putting the | ||
| # half-reabsorption point at peff = 2.886e-4 cm/s, hence | ||
| # TSA = 0.06 L/h * 1000 cm3/L / (2.886e-4 cm/s * 3600 s/h) = 57.8 cm^2. | ||
| # This is an EFFECTIVE area, well below the geometric proximal-tubule area: a | ||
| # well-stirred tubule sees fully concentrated filtrate everywhere and so | ||
| # over-reabsorbs relative to Scotcher's 5-region plug-flow-with-water-removal | ||
| # structure, and the lumping difference is absorbed here. Anchored to the | ||
| # published renal model ONLY — never fitted to Sisyphus Cmax loss (Invariant #8). | ||
| _TUBULAR_SURFACE_AREA_CM2 = 57.8 | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Enzyme abundances (pmol, total organ) — from reference_man.yaml | ||
| # --------------------------------------------------------------------------- | ||
|
|
@@ -539,23 +559,46 @@ def _compute_all_kp( | |
| # --------------------------------------------------------------------------- | ||
|
|
||
|
|
||
| def _estimate_renal_clearance(fup: Distribution, profile: MolecularProfile) -> Distribution: | ||
| """Estimate renal clearance from GFR and fraction unbound. | ||
| def _estimate_renal_clearance(fup: Distribution, peff: Distribution) -> Distribution: | ||
| """Estimate renal clearance from filtration and passive tubular reabsorption. | ||
|
|
||
| One-compartment (well-stirred) tubule mass balance: drug enters by | ||
| glomerular filtration (``fup * GFR``), leaves in urine (``UF * C_tubule``), | ||
| and exchanges passively across the tubular wall in both directions | ||
| (``PS * (C_tubule - fup * C_plasma)``). At steady state this gives | ||
|
|
||
| CL_renal = fup * UF * (GFR + PS) / (UF + PS) | ||
|
|
||
| For drugs undergoing glomerular filtration without active secretion: | ||
| CL_renal = GFR * fup | ||
| algebraically identical to the Scotcher et al. 2016 formulation | ||
| ``CL = fup * GFR * (1 - F_reabs)``, ``F_reabs = F' * (1 - UF/GFR)``, | ||
| ``F' = PS / (PS + UF)``, reduced to a single tubular region. | ||
|
|
||
| Active secretion/reabsorption would require transporter data. | ||
| Limits: ``PS -> 0`` recovers ``fup * GFR`` (impermeable; filtration only), | ||
| ``PS -> inf`` gives ``fup * UF`` (equilibrates with plasma, so the drug | ||
| leaves only as fast as water does). The previous model was the ``PS = 0`` | ||
| limit applied to *every* drug, which over-predicted renal clearance for | ||
| permeable, high-fup compounds (e.g. caffeine, paracetamol) by ~an order of | ||
| magnitude — they are filtered freely but almost entirely reabsorbed. | ||
|
|
||
| NOT modelled: active secretion (so secretion-dominant drugs are still | ||
| under-predicted), pH/ionisation-dependent reabsorption, and peff | ||
| uncertainty — the returned cv tracks fup only. | ||
|
|
||
| Args: | ||
| fup: Fraction unbound in plasma. | ||
| profile: Molecular profile (reserved for future use, e.g. charge-based | ||
| reabsorption estimates). | ||
| peff: Effective permeability (x10^-4 cm/s). | ||
|
|
||
| Returns: | ||
| Renal clearance (L/h) as Distribution. | ||
| """ | ||
| cl_renal = _GFR_L_PER_H * fup.mean | ||
| # peff (x10^-4 cm/s) * TSA (cm^2) -> L/h | ||
| ps_l_per_h = peff.mean * 1e-4 * 3600.0 * _TUBULAR_SURFACE_AREA_CM2 / 1000.0 | ||
| cl_renal = ( | ||
| fup.mean | ||
| * _URINE_FLOW_L_PER_H | ||
| * (_GFR_L_PER_H + ps_l_per_h) | ||
| / (_URINE_FLOW_L_PER_H + ps_l_per_h) | ||
| ) | ||
| return Distribution(mean=cl_renal, cv=fup.cv) | ||
|
|
||
|
|
||
|
|
@@ -748,7 +791,7 @@ def build_drug_on_graph( | |
| particle_radius = _estimate_particle_radius(adme.solubility) | ||
|
|
||
| # Estimate renal clearance | ||
| renal_cl = _estimate_renal_clearance(adme.fup, profile) | ||
| renal_cl = _estimate_renal_clearance(adme.fup, adme.peff) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The shipped surrogate and its training generator still use filtration-only clearance: Useful? React with 👍 / 👎. |
||
|
|
||
| # Set administration node based on route | ||
| if route == "oral": | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When SBI or TDM varies Peff,
apply_theta_to_drug,_build_drug_from_3d, and_apply_multipliersreplacedrug.peffwhile deliberately retaining the nominalrenal_clearance. After making clearance depend onpeff.meanhere, those posterior particles therefore simulate absorption with the updated Peff but elimination with clearance derived from a different Peff, biasing inference for high-fup or renally cleared drugs. Recompute renal clearance whenever these paths update Peff, or derive it dynamically from the current drug parameters.Useful? React with 👍 / 👎.