Repository navigation
Expand file tree
/
Copy pathdrta_mrgsolve_model.R
More file actions
1346 lines (1226 loc) · 62.8 KB
/
Copy pathdrta_mrgsolve_model.R
File metadata and controls
1346 lines (1226 loc) · 62.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
## ===========================================================================
## DISTAL RENAL TUBULAR ACIDOSIS (dRTA) — QSP MODEL FOR mrgsolve
## 56 ODEs · 28 treatment scenarios · time unit = HOURS
## ===========================================================================
##
## STRUCTURAL THESIS — four claims, each of which the equations make computable
## ---------------------------------------------------------------------------
## (1) PLASMA BICARBONATE IS A RATIO, NOT A FLUX.
## The renal acid-excretion controller is an INTEGRATOR (V-ATPase
## trafficking, tau ~ days) plus a PROPORTIONAL arm (cell-pH sensing,
## tau ~ hours) driving a SATURATING ACTUATOR (VH, clamped to [0,1]).
## dRTA is ACTUATOR SATURATION. Once VH rails, the residual acid gap is
## carried indefinitely by bone, so plasma HCO3- settles wherever the BONE
## dose-response puts it. That is why one visit reads
## "HCO3- 22.0 mmol/L, near normal" and "lumbar BMD z -1.1, clearly
## abnormal" — the same lesion seen through two windows with different
## gearing.
##
## (2) THE ACID GAP IS DISPOSED OF INTO THREE SINKS ON THREE TIMESCALES.
## ECF bicarbonate (minutes) · non-bicarbonate intracellular/protein
## buffer (hours) · BONE CARBONATE (years). Only the first is measured.
## The bone term is RECTIFIED in pH — max(0, pH_ref - pH) — hence CONVEX,
## so by Jensen's inequality a spiky HCO3- profile costs more bone than a
## flat profile of the same MEAN. Schedule therefore reaches bone.
##
## (3) ALKALI EFFICIENCY IS DELIVERY-RATE MATCHING, NOT DOSE.
## Base arriving faster than endogenous acid is produced pushes HCO3- over
## the proximal reabsorptive threshold and is voided as bicarbonaturia.
## That waste is WORST AT THE THERAPEUTIC TARGET, so a schedule change
## moves the RESPONDER RATE (a threshold statistic) much more than it
## moves the MEAN — exactly the dissociation B21CS reported
## (responder rate 43% -> 90%, mean only modestly higher).
##
## (4) THE TWO ENDPOINTS WANT OPPOSITE KINETICS.
## Systemic alkalinisation wants SLOW delivery (rate matching).
## Citraturia wants FAST delivery, because NaDC1 is Tm-limited and a bolus
## ESCAPES reabsorption. A two-granule formulation — fast citrate
## (2-3 h) plus slow bicarbonate (10-12 h) — is the only schedule that
## optimises both. This model computes that optimum.
##
## Urine pH is NOT a fitted state. It is the root of the luminal proton
## balance J_H(pH) = Demand(pH), solved by warm-started bisection inside the
## derivative evaluation (see solve_urine_pH in $GLOBAL). Everything that
## depends on urine pH — NH4+ trapping, titratable acid, bicarbonaturia,
## brushite supersaturation — therefore moves self-consistently.
##
## CALIBRATION ANCHORS (see drta_references.md for the full list)
## ---------------------------------------------------------------------------
## * B21CS switch trial, n=37 (PMID 32712761): SoC oral alkalisers, median 3
## intakes/day -> ADV7103 twice daily. Responder rate on plasma
## bicarbonate 43% -> 90%; non-inferiority p<0.0001, superiority p=0.0008;
## urine Ca/citrate fell below the lithogenic threshold in 56% of previous
## SoC non-responders.
## * B22CS 24-month extension, n=30 (PMID 33635379, 36529656): plasma HCO3-
## in range at 69-86% of visits, K+ at 83-93%; adherence >=75% in 79%;
## spine BMD z-score significantly increased (p=0.024).
## * B22CS 6-year follow-up, n=30, mean age 10.6 +/- 6.0 y (PMID 40801206):
## plasma HCO3- 22.0 +/- 3.2 -> 22.6 +/- 2.5 mmol/L (NS);
## height z -0.6 +/- 1.0 -> -0.3 +/- 1.0 (p=0.03);
## eGFR 105 +/- 17 -> 104 +/- 20 mL/min/1.73m2 (NS);
## lumbar BMD z -1.1 +/- 1.0 -> -0.8 +/- 1.0 (p=0.005);
## >80% of ADULT dRTA patients carry KDIGO stage >= 2.
## * Guittet 2020, healthy adults n=16 (PMID 32811843): ADV7103 citrate
## granules release over 2-3 h, bicarbonate granules over 10-12 h; urine pH
## held above 7 for 24 h at 1.44 mEq/kg twice daily; 0.98-2.88 mEq/kg/day
## non-saturating.
## * Lemann 1999 (PMID 9873210): urinary Ca varies with net acid excretion by
## 0.035 mmol/mEq. This is a VALIDATION TARGET, not a tuned coefficient —
## the model returns 0.0357 mmol/mEq.
## * Normal minimum urine pH < 5.45 after NH4Cl loading; dRTA cannot go below
## 5.5 (PMID 34973150, 2081516, 30139458).
## * Fractional excretion of HCO3- < 3% in dRTA vs > 15% in proximal RTA
## (PMID 2081516).
##
## PROVENANCE / HONESTY NOTE
## ---------------------------------------------------------------------------
## No R runtime was available where this model was written, so all 56 equations
## were first integrated with a dependency-free Python RK4 reference
## implementation and calibrated there. Thirteen genuine defects were found
## and fixed that way; each is recorded as a "BUG FIX #n" comment at the line
## it affected, so a reader can see what the simulation caught. Fitted
## constants are marked [FITTED]; literature-anchored ones carry their PMID.
## Known misfits are listed in drta_references.md and in the directory
## README.md — they are stated, not hidden.
##
## USAGE
## ---------------------------------------------------------------------------
## library(mrgsolve); library(dplyr)
## mod <- mread("drta_mrgsolve_model.R")
## out <- sim_scenario(mod, "S07_ADV7103_BID")
## plot(out, HCO3_e + UpH_s + UCit_s + SS_s ~ time/24)
## run_all_scenarios(mod) # all 28 scenarios, summary table
## ===========================================================================
$PROB
# dRTA QSP model — saturating acid-excretion actuator, three-sink acid
# disposal, delivery-rate-matched alkali pharmacology.
## ===========================================================================
$PARAM @annotated
## ---- subject -------------------------------------------------------------
BW : 30.0 : body weight (kg)
AGE : 10.0 : age (yr)
BSA : 1.05: body surface area (m2)
SEX : 0 : 0 female, 1 male
GFR0 : 110.0 : eGFR at NEPH=1 (mL/min/1.73m2)
NINTAKE : 2.0 : prescribed alkali intakes per day (drives adherence)
## ---- the lesion: TWO numbers, and only two ------------------------------
LES : 1.0 : retained fraction of maximal distal H+ secretion (rate defect)
LES_grad: 1.0 : retained fraction of maximal blood->urine pH gradient
GENO : 0 : 0 none, 1 ATP6V1B1, 2 ATP6V0A4, 3 SLC4A1, 4 FOXI1, 5 WDR72, 6 acquired
## ---- diet ----------------------------------------------------------------
DIET : 1.0 : NEAP multiplier (0.6 low-acid, 1.6 high-protein)
f_basal : 0.35: fraction of NEAP that is non-meal
K_diet_kg: 1.15: dietary potassium (mmol/kg/day)
F_Kdiet : 0.90: fractional K absorption
f_stool_K: 0.10: fractional stool K loss
## ---- acid-base setpoint / proximal threshold ----------------------------
THR_gap : 1.05: proximal HCO3- threshold above the setpoint (mmol/L)
CL_bic_ref: 2.60: clearance of supra-threshold HCO3- (L/h per 1.73m2) [FITTED]
FE_leak : 0.0018: fractional HCO3- leak below threshold
## ---- respiratory compensation -------------------------------------------
## dPaCO2/dHCO3 ~ 1.2 anchored at the NORMAL point (40 mmHg @ 24 mmol/L).
## Winter's regression (1.5*HCO3+8) is valid only inside the acidotic range;
## applying it at HCO3 24 puts a HEALTHY subject at pCO2 44-47 and pH 7.36.
PaCO2_ref: 40.0 : normal arterial pCO2 (mmHg)
kresp : 1.20: dPaCO2 / dHCO3 (mmHg per mmol/L)
HCO3_resp: 24.0 : HCO3- anchor for the respiratory relation (mmol/L)
tau_resp: 3.0 : respiratory adaptation time constant (h)
## ---- cell pH -------------------------------------------------------------
pHi_PT0 : 7.20: proximal tubule cell pH at normal acid-base
tau_pHiPT: 2.0 : (h)
gPT : 0.055: dpHi / dHCO3 (per mmol/L)
pHi_IC0 : 7.25: alpha-intercalated cell pH at normal acid-base
tau_pHiIC: 2.0 : (h)
gIC : 0.045: dpHi / dHCO3 (per mmol/L)
## ---- V-ATPase controller: INTEGRAL + PROPORTIONAL, SATURATING ACTUATOR ---
kI_VH : 0.011: integral gain (/h per mmol/L) — trafficking, tau ~ days
kP_VH : 0.125: proportional gain (per mmol/L) — cell-pH sensing
VH_max : 1.0 : ACTUATOR CEILING — never exceeded
Jh_max_ref: 11.0: maximal distal H+ secretion (mEq/h per 1.73m2)
dpH_max : 2.95: maximal blood->urine pH gradient in health
## ---- ammoniagenesis ------------------------------------------------------
NH3P_ref: 2.20: ammoniagenic capacity (mEq/h per 1.73m2)
tau_NH3 : 72.0 : adaptation time constant (h)
kNH3_ac : 2.40: up-regulation by acidaemia
kNH3_K : 0.30: up-regulation by hypokalaemia
kNH3_load: 1.05: up-regulation by dietary protein (glutamine supply)
pK_trap : 6.62: luminal NH3 trapping midpoint (pH)
s_trap : 0.62: trapping steepness (pH units)
## ---- pendrin (beta-IC bicarbonate secretion) ----------------------------
PEND0 : 1.0 : baseline pendrin abundance
tau_pend: 96.0 : (h)
kpend : 0.45: up-regulation by alkalosis
Jpend_ref: 0.22: base secreted into the lumen (mEq/h per 1.73m2)
## ---- phosphate / titratable acid ----------------------------------------
Pi_pl0 : 1.35: plasma phosphate (mmol/L)
TRP0 : 0.86: tubular reabsorption of phosphate (fraction)
kTRP_PTH: 0.16: PTH effect on TRP
pKa_Pi : 6.80: phosphate pKa2
TAoth_ref: 0.22: other non-NH3 urinary buffers (mEq/h per 1.73m2)
pKa_oth : 5.00: pKa of the lumped other buffers
pCO2u : 48.0 : urine pCO2 (mmHg)
## ---- citrate -------------------------------------------------------------
Vd_cit_kg: 0.26: citrate volume of distribution (L/kg)
k_ox : 1.40: systemic citrate oxidation rate constant (/h)
CIT_pl0 : 0.12: baseline plasma citrate (mmol/L)
FPE_cit : 0.85: hepatic first-pass extraction of oral citrate
Tm_cit_ref: 0.895: NaDC1 Tm at NDC1=1 (mmol/h per 1.73m2) [FITTED]
Km_cit_ref: 0.28: half-saturating filtered citrate load (mmol/h per 1.73m2)
tau_ndc1: 48.0 : NaDC1 adaptation time constant (h)
kndc1 : 1.90: NaDC1 up-regulation by PT cell acidosis
FEcit_min: 0.040: floor on fractional excretion of citrate
pKa3_cit: 5.60: citrate3- pKa3
K_cacit : 0.28: Ca-citrate complexation constant (L/mmol)
## ---- alkali / drug absorption and release -------------------------------
ka_bicIR: 1.50: immediate-release bicarbonate absorption (/h)
F_bicIR : 0.86: bioavailability (CO2 lost in the stomach)
ka_citIR: 1.05: immediate-release citrate absorption (/h)
F_citIR : 0.94: bioavailability
kr_bicPR: 0.215: ADV7103 SLOW granules — bicarbonate over 10-12 h (/h)
kr_citPR: 0.90: ADV7103 FAST granules — citrate over 2-3 h (/h)
ka_K : 1.20: cation absorption (/h)
F_K : 0.90: fractional cation absorption
f_Kcat : 1.0 : cation fraction that is K+ (1 = K salt, 0 = Na salt)
f_cit_ADV: 0.35: ADV7103 alkali equivalents supplied as citrate
## ---- potassium -----------------------------------------------------------
## Plasma K+ is itself a RATIO: ~300 mmol of deficit per mmol/L, so it
## under-reports the store exactly as plasma HCO3- under-reports the acid gap.
K_pl0 : 4.20: normal plasma K+ (mmol/L)
kKdef : 4.30: deficit gearing (mmol per kg per mmol/L)
tau_K : 6.0 : (h)
nK_sec : 2.0 : exponent of the renal K+ secretion / plasma K+ relation
kK_H : 0.55: extra K+ secretion per mEq/h of unmet acid load
kK_Na : 0.030: extra K+ secretion per mEq/h of Na-based alkali
kK_aldo : 1.20: aldosterone effect on K+ secretion
kK_shift: 1.60: acidaemic K+ shift out of cells (mmol/L per pH unit)
kK_hctz : 0.45: thiazide kaliuresis
## ---- volume / aldosterone ------------------------------------------------
tau_aldo: 12.0 : (h)
kaldo_V : 3.20: volume-depletion drive
kaldo_K : 6.00: potassium drive
## ---- calcium / PTH / vitamin D ------------------------------------------
Ca_pl0 : 1.22: ionised calcium (mmol/L)
tau_Ca : 8.0 : (h)
PTH0 : 32.0 : PTH (pg/mL)
tau_PTH : 2.0 : (h)
kPTH_Ca : 48.0 : calcium sensitivity of PTH
## ---- bone ----------------------------------------------------------------
kpc_bone: 9.50: physicochemical dissolution gain (mEq/h per pH unit) [FITTED]
kcell_bone: 6.00: osteoclast-mediated gain (mEq/h per pH unit) [FITTED]
BLAB_ref: 1400.0: rapidly exchangeable bone base pool (mEq per 70 kg)
tau_blab: 240.0 : refill time constant (h)
BaseCap_ref: 52000.0: mEq base per unit BMIN (whole skeleton, 70 kg)
k_remod : 5.7e-6: basal remodelling (fraction of BaseCap per h)
rCaBase : 0.035: mmol Ca released per mEq bone base [FITTED]
tau_OC : 96.0 : osteoclast time constant (h)
kOC_ac : 14.0 : PTH-independent acid activation of osteoclasts
kOC_PTH : 0.55: PTH activation of osteoclasts
tau_OB : 168.0 : osteoblast time constant (h)
kOB_ac : 6.00: acid suppression of osteoblasts
bALP0 : 90.0 : bone alkaline phosphatase (U/L)
tau_bALP: 120.0 : (h)
kbALP : 55.0 : acid effect on bone ALP
tau_OSM : 720.0 : osteomalacia index time constant (h)
kOSM : 1.30: acid drive
kOSM_D : 0.90: vitamin-D-deficiency drive
kz_catch: 6.0e-6: BMD z-score catch-up (/h) [FITTED]
kz_loss : 1.05e-3: BMD z-score loss per (mEq/h) bone base flux [FITTED]
## ---- non-bicarbonate buffer (the apparent bicarbonate space) ------------
## normal apparent space ~0.5 L/kg = ECF 0.20 + buffer 0.30; the buffer arm
## EXPANDS as HCO3- falls, reproducing the classical 0.7-1.0 L/kg apparent
## space of severe metabolic acidosis.
Cbuf_kg : 0.30: buffer capacity (mEq per mmol/L per kg)
kbuf : 0.25: buffer exchange rate (/h)
Cbuf_exp: 0.90: capacity expansion coefficient
Cbuf_hco3: 20.0 : HCO3- below which the capacity expands (mmol/L)
## ---- urine calcium -------------------------------------------------------
## Three deliberately separated routes: dietary acid load (this is what the
## Lemann slope measures, and it is present with a NORMAL kidney), Ca liberated
## when bone buffers the unmet gap, and direct acid inhibition of distal
## TRPV5-mediated Ca reabsorption.
UCa0 : 0.055: baseline urine Ca (mmol/kg/day)
kUCa_NEAP: 0.035: mmol Ca per mEq acid load (Lemann 1999, PMID 9873210)
kUCa_bone: 1.00: mmol urine Ca per mmol Ca released from bone
kUCa_pH : 50.0 : mmol/day per pH unit of acidaemia per 70 kg [FITTED]
kUCa_hctz: 0.42: maximal thiazide hypocalciuric effect
tau_UCa : 12.0 : (h)
## ---- urine volume --------------------------------------------------------
UVol_kg : 0.035: baseline urine volume (L/kg/day)
kDI : 0.55: hypokalaemic nephrogenic DI gain
tau_UVol: 12.0 : (h)
## ---- crystallisation / nephrocalcinosis / CKD ---------------------------
Ksp_ref : 4.25: brushite normalisation — SS = 1 is the threshold
tau_SS : 12.0 : (h)
kNC : 1.05e-4: nephrocalcinosis accrual
nNC : 1.35: supersaturation exponent
kNC_res : 1.10e-5: nephrocalcinosis resolution
kSTONE : 7.0e-5: stone accrual
kSTONE_res: 2.0e-5: stone clearance
kFIB : 4.0e-4: fibrosis per unit nephrocalcinosis
tau_FIB : 2000.0: fibrosis time constant (h)
kNEPH : 1.10e-5: nephron loss per unit fibrosis (/h)
NEPH_min: 0.12: floor on functional nephron fraction
## ---- growth --------------------------------------------------------------
IGF1_ref: 220.0 : reference IGF-1 (ng/mL)
tau_IGF : 168.0 : (h)
K_ac_igf: 3.20: HCO3- deficit for half-maximal GH resistance (mmol/L)
h_igf : 1.70: Hill coefficient
kh_catch: 1.60e-5: height z-score catch-up (/h) [FITTED]
kh_drag : 7.00e-5: height z-score loss when acidotic (/h) [FITTED]
## ---- muscle / adherence / GI --------------------------------------------
tau_MUS : 24.0 : (h)
kMUS : 0.55: hypokalaemic weakness gain
ADH_ref : 0.92: adherence ceiling
tau_ADH : 720.0 : (h)
kADH_n : 0.070: adherence penalty per extra daily intake
kADH_GI : 0.22: adherence penalty from GI irritation
tau_GI : 8.0 : (h)
kGI : 0.042: GI irritation per (mEq/h)/kg of immediate-release bolus
## ---- hearing -------------------------------------------------------------
kHEAR : 0.0 : hearing-loss progression rate (/h; set by genotype)
HEAR_max: 60.0 : plateau threshold shift (dB)
## ---- thiazide / vitamin D PK --------------------------------------------
ka_hctz : 1.10: (/h)
ke_hctz : 0.088: (/h)
Vd_hctz : 3.60: (L/kg-equivalent scaling volume)
EC50_hctz: 0.09: (mg/L)
ka_vitD : 0.05: (/h)
ke_25D : 3.5e-4: (/h)
F_vitD : 6.5e-5: IU -> ng/mL conversion
## ---- readout thresholds --------------------------------------------------
RESP_MARGIN: 2.70: responder if HCO3- >= setpoint - this margin (mmol/L)
TBT_thr : 22.0 : threshold for time-below-threshold accounting (mmol/L)
## ===========================================================================
$CMT @annotated
AG_bicIR : immediate-release bicarbonate in gut (mEq)
AG_citIR : immediate-release citrate in gut (mmol citrate)
AG_bicPR : ADV7103 slow bicarbonate granules (mEq)
AG_citPR : ADV7103 fast citrate granules (mmol citrate)
AG_KCl : KCl in gut (mmol K)
AG_K : cation equivalents carried by the alkali salt, in gut (mEq)
CIT_pl : plasma citrate (mmol/L)
AG_hctz : thiazide in gut (mg)
C_hctz : thiazide plasma concentration (mg/L)
AG_vitD : cholecalciferol in gut (IU)
C_25D : 25-OH vitamin D (ng/mL)
HCO3_e : ECF bicarbonate (mmol/L) << SINK 1: minutes
BUF : non-bicarbonate buffer base donated (mEq) << SINK 2: hours
PaCO2 : arterial pCO2 (mmHg)
pHi_PT : proximal tubule intracellular pH
pHi_IC : alpha-intercalated cell intracellular pH
VH : V-ATPase controller integral state << SATURATING ACTUATOR
PEND : pendrin abundance (relative)
NH3P : ammoniagenic capacity (mEq/h)
NDC1 : NaDC1 abundance (relative)
NEPH : functional nephron fraction
FIB : tubulo-interstitial fibrosis index
K_pl : plasma potassium (mmol/L)
KDEF : total-body potassium deficit (mmol)
Cl_pl : plasma chloride (mmol/L)
VECF : ECF volume (L)
ALDO : aldosterone (relative)
Ca_pl : plasma ionised calcium (mmol/L)
PTH : parathyroid hormone (pg/mL)
Pi_pl : plasma phosphate (mmol/L)
BLAB : rapidly exchangeable bone base pool (mEq)
BMIN : bone mineral mass (fraction of age-expected) << SINK 3: years
OC : osteoclast activity (relative)
OB : osteoblast activity (relative)
bALP : bone alkaline phosphatase (U/L)
OSM : osteomalacia / unmineralised matrix index
BMDz : lumbar spine BMD z-score
CUMBASE : cumulative base withdrawn from bone (mEq)
UCa_s : urine calcium (mmol/day, smoothed)
UCit_s : urine citrate (mmol/day, smoothed)
UpH_s : urine pH (24 h mean, smoothed)
UVol_s : urine volume (L/day, smoothed)
SS_s : brushite supersaturation index (smoothed)
NC : nephrocalcinosis burden
STONE : stone burden
IGF1 : IGF-1 (ng/mL)
Hz : height z-score
MUS : muscle strength index (1 = normal)
ADH : adherence (0-1)
GI : gastrointestinal irritation index
HEAR : hearing threshold shift (dB)
TBT : cumulative time with HCO3- below TBT_thr (h)
AAC : integral of max(0, 24 - HCO3-) (mmol/L*h)
WASTE : cumulative alkali voided as urinary HCO3- (mEq)
GIVEN : cumulative alkali equivalents absorbed (mEq)
AG_acid : NH4Cl / mineral acid load in gut (mEq)
## ===========================================================================
$GLOBAL
#define _MRG_DRTA_
// --- subject-derived quantities, assigned in $MAIN, used in $ODE ----------
double HCO3_set, THR_bic, NEAP_kg, Jh_max, NH3P0, Tm_cit, Km_cit, TAoth;
double Jpend, GFRh, CL_bic, Vd_cit, VECF0, BLAB0, BaseCap, growth_pot;
double NEAP_h, NEAP_ref_h, pH_ref, CIT_end, kK_sec;
// --- reported intermediates ----------------------------------------------
double o_uph, o_pHbl, o_NAE, o_TA, o_NH4, o_HCO3u, o_NEAP, o_alk, o_gap;
double o_Jbone, o_VHc, o_SS, o_UCa, o_UCit, o_CaCit, o_FEHCO3, o_GM;
double o_Hdef, o_reserve, o_eGFR, o_UCa_mgkg, o_Pi_u, o_resp;
// --- warm start for the urine-pH root ------------------------------------
double uph_warm = 6.20;
double blood_pH(double hco3, double paco2) {
if (hco3 < 0.5) hco3 = 0.5;
if (paco2 < 8.0) paco2 = 8.0;
return 6.10 + log10(hco3 / (0.0301 * paco2));
}
double frac_prot(double pH, double pKa) { // protonated fraction
return 1.0 / (1.0 + pow(10.0, pH - pKa));
}
// -------------------------------------------------------------------------
// LUMINAL PROTON BALANCE. Urine pH is the ROOT of
//
// F(pH) = J_H_cap(pH) - Demand(pH)
//
// J_H_cap(pH) = Jmax * (1 - 10^-(pH - pH_floor)) thermodynamic ceiling
// Demand(pH) = (HCO3 delivered - HCO3 excreted) base titrated
// + titratable acid formed
// + citrate protonated
// + NH3 trapped as NH4+
//
// F is monotonically increasing in pH (the ceiling rises with pH, the demand
// falls), so the root is unique. Warm-started bracketed bisection,
// 12 iterations => +/- 0.0005 pH, which is far below any measurement
// resolution and cheap enough to sit inside the derivative evaluation.
// -------------------------------------------------------------------------
double urine_F(double pH, double Jcap, double pH_floor, double HCO3_del,
double Pi_load, double NH3_avail, double Cit_load,
double TAo, double pKaPi, double pKaOth, double pKa3,
double pco2u, double UVol_h, double pKtrap, double strap,
double pHbl) {
double jh = (pH > pH_floor)
? Jcap * (1.0 - pow(10.0, -(pH - pH_floor))) : 0.0;
double hco3u = UVol_h * 0.0301 * pco2u * pow(10.0, pH - 6.10);
double ta = Pi_load * (frac_prot(pH, pKaPi) - frac_prot(pHbl, pKaPi))
+ TAo * (frac_prot(pH, pKaOth) - frac_prot(pHbl, pKaOth));
double cith = Cit_load * (frac_prot(pH, pKa3) - frac_prot(pHbl, pKa3));
double trap = 1.0 / (1.0 + pow(10.0, (pH - pKtrap) / strap));
double nh4 = NH3_avail * trap;
return jh - ((HCO3_del - hco3u) + ta + cith + nh4);
}
double solve_urine_pH(double Jcap, double pH_floor, double HCO3_del,
double Pi_load, double NH3_avail, double Cit_load,
double TAo, double pKaPi, double pKaOth, double pKa3,
double pco2u, double UVol_h, double pKtrap, double strap,
double pHbl, double guess) {
double abs_lo = (pH_floor + 1e-4 > 4.20) ? pH_floor + 1e-4 : 4.20;
double lo = abs_lo, hi = 8.40;
if (guess > 0.0) { // warm-start bracket
double g_lo = guess - 0.45, g_hi = guess + 0.45;
if (g_lo > lo) lo = g_lo;
if (g_hi < hi) hi = g_hi;
if (lo >= hi) { lo = abs_lo; hi = 8.40; }
}
double flo = urine_F(lo, Jcap, pH_floor, HCO3_del, Pi_load, NH3_avail,
Cit_load, TAo, pKaPi, pKaOth, pKa3, pco2u, UVol_h,
pKtrap, strap, pHbl);
double fhi = urine_F(hi, Jcap, pH_floor, HCO3_del, Pi_load, NH3_avail,
Cit_load, TAo, pKaPi, pKaOth, pKa3, pco2u, UVol_h,
pKtrap, strap, pHbl);
if (flo > 0.0) { // widen downward
if (abs_lo < lo &&
urine_F(abs_lo, Jcap, pH_floor, HCO3_del, Pi_load, NH3_avail, Cit_load,
TAo, pKaPi, pKaOth, pKa3, pco2u, UVol_h, pKtrap, strap, pHbl)
<= 0.0) { lo = abs_lo; }
else return lo;
}
if (fhi < 0.0) { // widen upward
if (hi < 8.40 &&
urine_F(8.40, Jcap, pH_floor, HCO3_del, Pi_load, NH3_avail, Cit_load,
TAo, pKaPi, pKaOth, pKa3, pco2u, UVol_h, pKtrap, strap, pHbl)
>= 0.0) { hi = 8.40; }
else return hi;
}
for (int i = 0; i < 12; ++i) {
double mid = 0.5 * (lo + hi);
if (urine_F(mid, Jcap, pH_floor, HCO3_del, Pi_load, NH3_avail, Cit_load,
TAo, pKaPi, pKaOth, pKa3, pco2u, UVol_h, pKtrap, strap, pHbl)
< 0.0) lo = mid; else hi = mid;
}
return 0.5 * (lo + hi);
}
// -------------------------------------------------------------------------
// Net endogenous acid production with a meal-linked diurnal shape.
// BUG FIX #8 (found by simulation): the raised-cosine shape integrates to
// 2.0 h over a day, not 24 h, so the meal-linked arm delivered only 1/12 of
// its intended acid load and TOTAL NEAP was ~35% of the prescribed value.
// Every subject looked far less acidotic than the diet implied. The
// trailing factor of 12 restores the daily integral.
// -------------------------------------------------------------------------
double neap_rate(double t, double tot, double fb) {
double basal = tot * fb, meal = tot * (1.0 - fb);
double h = fmod(t, 24.0);
if (h < 0) h += 24.0;
double mt[3] = {7.5, 12.8, 19.2};
double fr[3] = {0.28, 0.34, 0.38};
double s = 0.0;
for (int i = 0; i < 3; ++i) {
double d = h - mt[i];
if (d < -12.0) d += 24.0;
if (d > 12.0) d -= 24.0;
if (d > -1.5 && d < 3.0)
s += fr[i] * (1.0 - cos(2.0 * M_PI * (d + 1.5) / 4.5)) / 4.5 * 2.0;
}
return basal + meal * s * 12.0;
}
## ===========================================================================
$MAIN
// -------------------------------------------------------------------------
// Subject-derived parameters. This block is IDEMPOTENT by construction:
// every size scaling reads a separate *_ref base constant rather than
// overwriting the parameter it scales. BUG FIX #7: an earlier version
// assigned e.g. Tm_cit = 0.62 * BSA/1.73 directly, which silently discarded
// caller overrides and double-scaled when the setup ran twice.
// -------------------------------------------------------------------------
double fb_ = BSA / 1.73;
double fw_ = BW / 70.0;
// normal plasma HCO3- setpoint rises with age (infant ~21.5 -> adult ~24.7)
HCO3_set = 21.0 + 4.0 * (AGE / (AGE + 3.0));
THR_bic = HCO3_set + THR_gap;
// net endogenous acid production per kg falls with age
NEAP_kg = 0.95 + 1.45 * exp(-AGE / 6.5);
Jh_max = Jh_max_ref * fb_;
NH3P0 = NH3P_ref * fb_;
Tm_cit = Tm_cit_ref * fb_;
Km_cit = Km_cit_ref * fb_;
TAoth = TAoth_ref * fb_;
Jpend = Jpend_ref * fb_;
CL_bic = CL_bic_ref * fb_;
GFRh = GFR0 * 60.0 / 1000.0 * fb_; // L/h
Vd_cit = Vd_cit_kg * BW;
VECF0 = 0.20 * BW;
BLAB0 = BLAB_ref * fw_;
BaseCap = BaseCap_ref * fw_;
growth_pot = 1.0 / (1.0 + exp((AGE - 15.5) / 1.8)); // 1 child, 0 adult
NEAP_h = NEAP_kg * BW * DIET / 24.0;
NEAP_ref_h = NEAP_kg * BW / 24.0; // DIET = 1 reference
// --- self-consistent baselines -------------------------------------------
// (a) the bone-dissolution threshold pH_ref must be the pH THIS subject
// reaches in health, or a normal toddler slowly dissolves its skeleton
double pco2_h = PaCO2_ref + kresp * (HCO3_set - HCO3_resp);
if (pco2_h > 62.0) pco2_h = 62.0;
if (pco2_h < 14.0) pco2_h = 14.0;
pH_ref = blood_pH(HCO3_set, pco2_h);
// (b) endogenous citrate appearance must hold plasma citrate at CIT_pl0
CIT_end = (k_ox * Vd_cit + GFRh) * CIT_pl0;
// (c) basal renal K+ secretion must exactly balance dietary K+ intake, so
// that any hypokalaemia comes from the LESION and not from tuning
double Kdiet_h_ = K_diet_kg * BW / 24.0;
kK_sec = ((F_Kdiet - f_stool_K) * Kdiet_h_) / (K_pl0 * BW / 24.0);
// --- adherence enters as bioavailability on every alkali compartment ------
double adh_ = ADH;
if (adh_ < 0.0) adh_ = 0.0;
if (adh_ > 1.0) adh_ = 1.0;
F_AG_bicIR = adh_;
F_AG_citIR = adh_;
F_AG_bicPR = adh_;
F_AG_citPR = adh_;
F_AG_K = adh_;
F_AG_KCl = adh_;
// --- initial conditions ---------------------------------------------------
CIT_pl_0 = CIT_pl0;
HCO3_e_0 = HCO3_set;
PaCO2_0 = PaCO2_ref; // NOTE: `<cmt>_0` is the initial condition;
// the PARAMETER is deliberately named
// PaCO2_ref to avoid that collision.
pHi_PT_0 = pHi_PT0;
pHi_IC_0 = pHi_IC0;
VH_0 = 0.60;
PEND_0 = PEND0;
NH3P_0 = NH3P0;
NDC1_0 = 1.0;
NEPH_0 = 1.0;
K_pl_0 = K_pl0;
Cl_pl_0 = 104.0;
VECF_0 = VECF0;
ALDO_0 = 1.0;
Ca_pl_0 = Ca_pl0;
PTH_0 = PTH0;
Pi_pl_0 = Pi_pl0;
BLAB_0 = BLAB0;
BMIN_0 = 1.0;
OC_0 = 1.0;
OB_0 = 1.0;
bALP_0 = bALP0;
BMDz_0 = 0.0;
UCa_s_0 = UCa0 * BW;
UCit_s_0 = 3.0;
UpH_s_0 = 6.0;
UVol_s_0 = UVol_kg * BW;
SS_s_0 = 1.0;
IGF1_0 = IGF1_ref;
MUS_0 = 1.0;
ADH_0 = ADH_ref;
C_25D_0 = 28.0;
## ===========================================================================
$ODE
double HCO3 = (HCO3_e > 1.0) ? HCO3_e : 1.0;
double pco2 = (PaCO2 > 8.0) ? PaCO2 : 8.0;
double pHbl = blood_pH(HCO3, pco2);
double neph = (NEPH > NEPH_min) ? NEPH : NEPH_min;
double gfrh = GFRh * neph;
double vecf = (VECF > 0.3 * VECF0) ? VECF : 0.3 * VECF0;
// ---------------- gut / drug absorption -----------------------------------
double a_bicIR = ka_bicIR * AG_bicIR;
double a_citIR = ka_citIR * AG_citIR;
double r_bicPR = kr_bicPR * AG_bicPR;
double r_citPR = kr_citPR * AG_citPR;
dxdt_AG_bicIR = -a_bicIR;
dxdt_AG_citIR = -a_citIR;
dxdt_AG_bicPR = -r_bicPR;
dxdt_AG_citPR = -r_citPR;
double bic_abs = F_bicIR * (a_bicIR + r_bicPR); // mEq/h
double cit_abs = F_citIR * (a_citIR + r_citPR); // mmol/h
// NH4Cl / mineral-acid loading test (the diagnostic challenge)
double a_acid = ka_bicIR * AG_acid;
dxdt_AG_acid = -a_acid;
double acid_abs = 0.95 * a_acid; // mEq/h
// AG_K holds the CATION equivalents carried by the alkali salt. f_Kcat splits
// them into K+ and Na+: NaHCO3 delivers Na+ to the distal nephron and so
// AGGRAVATES the K+ wasting it is meant to treat, whereas K-citrate / KHCO3
// repletes it. This is the whole reason dRTA alkali is potassium-based.
double a_salt = ka_K * AG_K;
double a_kcl = ka_K * AG_KCl;
dxdt_AG_K = -a_salt;
dxdt_AG_KCl = -a_kcl;
double K_abs_K = F_K * (f_Kcat * a_salt + a_kcl);
double Na_alk_h = F_K * (1.0 - f_Kcat) * a_salt;
dxdt_AG_hctz = -ka_hctz * AG_hctz;
dxdt_C_hctz = ka_hctz * AG_hctz / Vd_hctz - ke_hctz * C_hctz;
dxdt_AG_vitD = -ka_vitD * AG_vitD;
dxdt_C_25D = F_vitD * ka_vitD * AG_vitD - ke_25D * C_25D;
// ---------------- citrate disposition -------------------------------------
double CIT = (CIT_pl > 1e-6) ? CIT_pl : 1e-6;
double cit_filt = gfrh * CIT; // mmol/h
double cit_ox = k_ox * CIT * Vd_cit; // mmol/h
// Oral citrate is largely extracted on first pass through the liver and
// oxidised there; only the escaping fraction reaches the systemic circulation
// and can be FILTERED.
double cit_fp = FPE_cit * cit_abs;
double cit_sys = (1.0 - FPE_cit) * cit_abs;
dxdt_CIT_pl = (cit_sys + CIT_end - cit_ox - cit_filt) / Vd_cit;
// Every mmol of citrate3- oxidised consumes 3 H+ == yields 3 mEq HCO3-, but
// ONLY drug-derived citrate is an alkali source; endogenous citrate turnover is
// acid-base neutral. BUG FIX #5: subtracting the whole endogenous appearance
// rate CIT_end (rather than the baseline oxidation rate) fed a phantom
// -57 mEq/day of acid into every healthy subject, which drove the V-ATPase
// controller onto its ceiling at baseline.
double cit_ox_drug = k_ox * Vd_cit * (CIT - CIT_pl0);
if (cit_ox_drug < 0.0) cit_ox_drug = 0.0;
double alk_from_cit = 3.0 * (cit_fp + cit_ox_drug);
// NaDC1: Tm-limited, so a BOLUS ESCAPES reabsorption. This is the reason the
// citraturic endpoint wants FAST delivery while the systemic endpoint wants
// SLOW delivery.
double ndc1 = (NDC1 > 0.05) ? NDC1 : 0.05;
double Tm = Tm_cit * ndc1 * neph;
double cit_reab = Tm * cit_filt / (Km_cit + cit_filt);
// NaDC1 cannot reabsorb the whole filtered load: fractional excretion of
// citrate has a floor of a few percent even in severe acidosis, so
// hypocitraturia is profound but never absolute.
double UCit_h = cit_filt - cit_reab;
if (UCit_h < FEcit_min * cit_filt) UCit_h = FEcit_min * cit_filt;
// ---------------- respiratory compensation --------------------------------
double pco2_t = PaCO2_ref + kresp * (HCO3 - HCO3_resp);
if (pco2_t > 62.0) pco2_t = 62.0;
if (pco2_t < 14.0) pco2_t = 14.0;
dxdt_PaCO2 = (pco2_t - pco2) / tau_resp;
// ---------------- cell pH -------------------------------------------------
dxdt_pHi_PT = ((pHi_PT0 - gPT * (HCO3_set - HCO3)) - pHi_PT) / tau_pHiPT;
dxdt_pHi_IC = ((pHi_IC0 - gIC * (HCO3_set - HCO3)) - pHi_IC) / tau_pHiIC;
// ================= THE CONTROL LOOP ======================================
// INTEGRAL (V-ATPase trafficking, tau ~ days) + PROPORTIONAL (cell-pH
// sensing, tau ~ hours) driving a SATURATING ACTUATOR. dRTA is the
// SATURATION of this loop, not a missing pathway.
// BUG FIX #6: with integral control alone at kI = 0.22/h the integrator
// traversed its whole 0-1 range twice per day and the controller degenerated
// into a bang-bang oscillator.
// =========================================================================
double err = HCO3_set - HCO3;
double VHraw = VH + kP_VH * err;
double VHc = VHraw;
if (VHc > VH_max) VHc = VH_max;
if (VHc < 0.0) VHc = 0.0;
double dVH = kI_VH * err;
// anti-windup: stop integrating once the EFFECTIVE actuator is railed
if ((VHraw >= VH_max && dVH > 0.0) || (VHraw <= 0.0 && dVH < 0.0)) dVH = 0.0;
dxdt_VH = dVH;
// ---------------- distal H+ secretion and the urine-pH root ---------------
double uvol = (UVol_s > 0.2) ? UVol_s : 0.2;
double uvol_h = uvol / 24.0;
double HCO3_del = CL_bic * neph * ((HCO3 > THR_bic) ? (HCO3 - THR_bic) : 0.0)
+ FE_leak * gfrh * HCO3
+ Jpend * PEND;
double TRP = TRP0 - kTRP_PTH * (PTH / PTH0 - 1.0);
if (TRP > 0.95) TRP = 0.95;
if (TRP < 0.35) TRP = 0.35;
double pipl = (Pi_pl > 0.2) ? Pi_pl : 0.2;
double Pi_load = gfrh * pipl * (1.0 - TRP);
double NH3_avail = ((NH3P > 0.0) ? NH3P : 0.0) * neph;
double Jcap = Jh_max * LES * VHc * neph;
double pH_floor = pHbl - dpH_max * LES_grad;
double uph = solve_urine_pH(Jcap, pH_floor, HCO3_del, Pi_load, NH3_avail,
UCit_h, TAoth, pKa_Pi, pKa_oth, pKa3_cit,
pCO2u, uvol_h, pK_trap, s_trap, pHbl, uph_warm);
uph_warm = uph;
double HCO3_u = uvol_h * 0.0301 * pCO2u * pow(10.0, uph - 6.10); // mEq/h
double TA = Pi_load * (frac_prot(uph, pKa_Pi) - frac_prot(pHbl, pKa_Pi))
+ TAoth * (frac_prot(uph, pKa_oth) - frac_prot(pHbl, pKa_oth));
double trap = 1.0 / (1.0 + pow(10.0, (uph - pK_trap) / s_trap));
double NH4_u = NH3_avail * trap;
double NAE = TA + NH4_u - HCO3_u; // mEq/h
// ================= THREE-SINK ACID DISPOSAL ==============================
double NEAP = neap_rate(SOLVERTIME, NEAP_h, f_basal) + acid_abs;
double alk_in = bic_abs + alk_from_cit;
// --- SINK 2: non-bicarbonate buffer --------------------------------------
// BUG FIX #1 (found by simulation): this flux was SUBTRACTED from
// dxdt_HCO3_e instead of added. With the wrong sign the buffer became a
// POSITIVE feedback and HCO3_e diverged to 5e9 mmol/L within 14 h.
double Cbuf = Cbuf_kg * BW *
(1.0 + Cbuf_exp * ((Cbuf_hco3 > HCO3) ? (Cbuf_hco3 - HCO3) : 0.0) / 10.0);
double Jbuf = kbuf * ((HCO3_set - HCO3) * Cbuf - BUF);
dxdt_BUF = Jbuf;
// --- SINK 3: bone. RECTIFIED in pH => CONVEX => a spiky HCO3- profile
// costs more bone than a flat one of the same MEAN (Jensen).
double acid_pH = (pH_ref > pHbl) ? (pH_ref - pHbl) : 0.0;
double blab_av = ((BLAB > 0.0) ? BLAB : 0.0) / BLAB0;
double bmin_ = (BMIN > 0.2) ? BMIN : 0.2;
double oc_ = (OC > 0.0) ? OC : 0.0;
double Jbone = (kpc_bone * bmin_ * blab_av + kcell_bone * oc_)
* acid_pH * (BW / 70.0);
dxdt_BLAB = (BLAB0 - BLAB) / tau_blab - Jbone;
dxdt_CUMBASE = Jbone;
dxdt_BMIN = k_remod * (OB - OC) - Jbone / BaseCap;
dxdt_HCO3_e = (alk_in + NAE - NEAP + Jbuf + Jbone) / vecf;
dxdt_GIVEN = alk_in;
dxdt_WASTE = HCO3_u;
// ---------------- renal adaptation ----------------------------------------
// BUG FIX #10: ammoniagenic capacity was driven ONLY by the plasma HCO3-
// error -- but that error is small precisely BECAUSE the kidney is
// compensating, so the ammonia arm could never be recruited and the V-ATPase
// controller railed at 1.0 on a merely high-protein diet. Dietary protein
// supplies the glutamine, so NEAP itself must drive it.
double errp = (err > 0.0) ? err : 0.0;
double ac_n = errp / 6.0; if (ac_n > 1.0) ac_n = 1.0;
double kdef_n = (K_pl0 - K_pl) / 1.5; if (kdef_n < 0.0) kdef_n = 0.0;
double NH3_t = NH3P0 * (1.0 + kNH3_load * (NEAP_h / NEAP_ref_h - 1.0)
+ kNH3_ac * ac_n + kNH3_K * kdef_n);
dxdt_NH3P = (NH3_t - NH3P) / tau_NH3;
double alk_n = (err < 0.0) ? (-err) : 0.0;
dxdt_PEND = (PEND0 * (1.0 + kpend * alk_n / 3.0) - PEND) / tau_pend;
double pti = (pHi_PT0 > pHi_PT) ? (pHi_PT0 - pHi_PT) : 0.0;
dxdt_NDC1 = ((1.0 + kndc1 * pti) - NDC1) / tau_ndc1;
// ---------------- potassium ----------------------------------------------
// Hdef == the H+ the collecting duct FAILED to secrete, expressed as the unmet
// acid load. Zero in health by construction, so no parameter has to be
// re-tuned to keep a normal subject in potassium balance.
double Hdef = NEAP_h - NAE - alk_in;
if (Hdef < 0.0) Hdef = 0.0;
double Kdiet_h = K_diet_kg * BW / 24.0;
double K_in = F_Kdiet * Kdiet_h + K_abs_K;
// BUG FIX #12: renal K+ secretion was LINEAR in plasma K+ with a weak
// aldosterone arm, so plasma K+ was almost undefended -- tripling K+ intake
// with K-citrate drove it to 6.6 mmol/L and Na-based alkali drove it to 1.4.
// Real K+ homeostasis has very high loop gain.
double Ksec = kK_sec * K_pl0 * pow(K_pl / K_pl0, nK_sec) * BW / 24.0
* (1.0 + kK_aldo * (ALDO - 1.0)) * neph
+ kK_H * Hdef
+ kK_Na * Na_alk_h
+ kK_hctz * C_hctz / (EC50_hctz + C_hctz) * BW / 24.0 * 0.05;
dxdt_KDEF = Ksec + f_stool_K * Kdiet_h - K_in;
double Kpl_t = K_pl0 - KDEF / (kKdef * BW) + kK_shift * (7.40 - pHbl);
if (Kpl_t > 7.5) Kpl_t = 7.5;
if (Kpl_t < 1.4) Kpl_t = 1.4;
dxdt_K_pl = (Kpl_t - K_pl) / tau_K;
double vdep = (VECF0 - vecf) / VECF0; if (vdep < 0.0) vdep = 0.0;
double kexc = (K_pl - K_pl0); if (kexc < 0.0) kexc = 0.0;
dxdt_ALDO = ((1.0 + kaldo_V * vdep + kaldo_K * kexc) - ALDO) / tau_aldo;
dxdt_VECF = (VECF0 * (1.0 - 0.05 * errp / 6.0) - vecf) / 24.0;
dxdt_Cl_pl = ((104.0 + (HCO3_set - HCO3) * 0.85) - Cl_pl) / 6.0;
// ---------------- calcium / PTH / phosphate ------------------------------
double Ca_bone = rCaBase * Jbone; // mmol/h
double UCa_basal = UCa0 * BW / 24.0;
double hz_ = C_hctz / (EC50_hctz + C_hctz);
double UCa_h = (UCa_basal
+ kUCa_NEAP * (NEAP - alk_in - NEAP_ref_h)
+ kUCa_bone * Ca_bone
+ kUCa_pH * acid_pH * (BW / 70.0) / 24.0)
* (1.0 - kUCa_hctz * hz_);
if (UCa_h < 0.0) UCa_h = 0.0;
// serum Ca stays normal because the bone Ca is spilled into the urine --
// dRTA is hypercalciuria WITHOUT hypercalcaemia.
double ca_ex = UCa_h - UCa_basal; if (ca_ex < 0.0) ca_ex = 0.0;
double Ca_t = Ca_pl0 + 0.35 * (Ca_bone - ca_ex);
if (Ca_t > 1.55) Ca_t = 1.55;
if (Ca_t < 0.85) Ca_t = 0.85;
dxdt_Ca_pl = (Ca_t - Ca_pl) / tau_Ca;
double dlow = (25.0 - C_25D) / 25.0; if (dlow < 0.0) dlow = 0.0;
double PTH_t = PTH0 * (1.0 + kPTH_Ca * (Ca_pl0 - Ca_pl) + 0.35 * dlow);
if (PTH_t < 3.0) PTH_t = 3.0;
dxdt_PTH = (PTH_t - PTH) / tau_PTH;
dxdt_Pi_pl = ((Pi_pl0 - 0.10 * (PTH / PTH0 - 1.0)) - Pi_pl) / 12.0;
// ---------------- bone cells --------------------------------------------
double OC_t = 1.0 + kOC_ac * acid_pH + kOC_PTH * (PTH / PTH0 - 1.0);
if (OC_t < 0.2) OC_t = 0.2;
dxdt_OC = (OC_t - OC) / tau_OC;
double OB_t = 1.0 - kOB_ac * acid_pH;
if (OB_t < 0.15) OB_t = 0.15;
dxdt_OB = (OB_t - OB) / tau_OB;
dxdt_bALP = (bALP0 * (1.0 + kbALP * acid_pH * 0.5) * (1.0 + 0.5 * growth_pot)
- bALP) / tau_bALP;
double dlow2 = (20.0 - C_25D) / 20.0; if (dlow2 < 0.0) dlow2 = 0.0;
dxdt_OSM = ((kOSM * acid_pH * 12.0 + kOSM_D * dlow2) - OSM) / tau_OSM;
dxdt_BMDz = kz_catch * (0.0 - BMDz) * (0.35 + 0.65 * growth_pot)
- kz_loss * Jbone / (BW / 70.0);
// ---------------- urine --------------------------------------------------
double kdi_n = (3.8 - K_pl) / 1.0; if (kdi_n < 0.0) kdi_n = 0.0;
dxdt_UVol_s = (UVol_kg * BW * (1.0 + kDI * kdi_n) - UVol_s) / tau_UVol;
dxdt_UCa_s = (UCa_h * 24.0 - UCa_s) / tau_UCa;
dxdt_UCit_s = (UCit_h * 24.0 - UCit_s) / tau_UCa;
dxdt_UpH_s = (uph - UpH_s) / 12.0;
// brushite (CaHPO4) supersaturation: free Ca2+ x HPO4(2-) / Ksp. All three
// arms of the therapeutic trade-off appear here: Ca up, citrate down, pH up.
double Ca_c = UCa_h * 24.0 / uvol;
double Cit_c = UCit_h * 24.0 / uvol;
double Pi_u_c = Pi_load * 24.0 / uvol;
double Ca_free = Ca_c / (1.0 + K_cacit * Cit_c);
double HPO4 = Pi_u_c / (1.0 + pow(10.0, pKa_Pi - uph));
double SS = Ca_free * HPO4 / Ksp_ref;
dxdt_SS_s = (SS - SS_s) / tau_SS;
double drive = (SS > 1.0) ? pow(SS - 1.0, nNC) : 0.0;
dxdt_NC = kNC * drive - kNC_res * NC;
dxdt_STONE = kSTONE * drive - kSTONE_res * STONE;
double ncp = (NC > 0.0) ? NC : 0.0;
double FIB_t = 1.0 - exp(-kFIB * ncp * 1000.0);
dxdt_FIB = (FIB_t - FIB) / tau_FIB;
// BUG FIX #2: nephron loss was driven by the target-minus-state GAP, which
// vanishes at steady state and made established nephrocalcinosis harmless.
double fibp = (FIB > 0.0) ? FIB : 0.0;
dxdt_NEPH = -kNEPH * fibp * neph;
// ---------------- growth / muscle / adherence ---------------------------
double acid_idx = (HCO3_set > HCO3) ? (HCO3_set - HCO3) : 0.0;
double GM = 1.0 / (1.0 + pow(acid_idx / K_ac_igf, h_igf));
dxdt_IGF1 = (IGF1_ref * GM - IGF1) / tau_IGF;
dxdt_Hz = kh_catch * (0.0 - Hz) * growth_pot * GM
- kh_drag * (1.0 - GM) * growth_pot;
double kw = (3.5 - K_pl) / 1.5; if (kw < 0.0) kw = 0.0;
double MUS_t = 1.0 - kMUS * pow(kw, 1.5);
if (MUS_t < 0.05) MUS_t = 0.05;
dxdt_MUS = (MUS_t - MUS) / tau_MUS;
// BUG FIX #13: the GI-irritation drive was normalised by BW/70 and then divided
// by 25, so ANY immediate-release bolus saturated it at the min(1, GI) cap and
// gastrointestinal tolerability became a binary switch rather than a
// dose-graded penalty. The drive is now the bolus absorption rate PER KG,
// which is what a patient actually feels. Note the PROLONGED-RELEASE granules
// (r_bicPR, r_citPR) do not enter: that is the tolerability advantage.
dxdt_GI = kGI * (a_bicIR + 3.0 * a_citIR) / BW - GI / tau_GI;
double gi_c = (GI < 1.0) ? GI : 1.0;
double nx = NINTAKE - 2.0; if (nx < 0.0) nx = 0.0;
double ADH_t = ADH_ref * exp(-kADH_n * nx) * (1.0 - kADH_GI * gi_c);
dxdt_ADH = (ADH_t - ADH) / tau_ADH;
dxdt_HEAR = kHEAR * ((HEAR_max > HEAR) ? (HEAR_max - HEAR) : 0.0);
// ---------------- exposure integrals ------------------------------------
dxdt_TBT = (HCO3 < TBT_thr) ? 1.0 : 0.0;
dxdt_AAC = (24.0 > HCO3) ? (24.0 - HCO3) : 0.0;
// ---------------- reported intermediates --------------------------------
o_uph = uph; o_pHbl = pHbl;
o_NAE = NAE * 24.0; o_TA = TA * 24.0; o_NH4 = NH4_u * 24.0;
o_HCO3u = HCO3_u * 24.0; o_NEAP = NEAP * 24.0; o_alk = alk_in * 24.0;
o_gap = (NEAP - alk_in - NAE) * 24.0;
o_Jbone = Jbone * 24.0; o_VHc = VHc; o_SS = SS; o_GM = GM;
o_UCa = UCa_h * 24.0; o_UCit = UCit_h * 24.0;
o_UCa_mgkg = UCa_h * 24.0 * 40.08 / BW;
o_CaCit = (UCa_h * 24.0 * 40.08) / ((UCit_h * 24.0 * 192.12 > 0.02)
? UCit_h * 24.0 * 192.12 : 0.02);
o_FEHCO3 = 100.0 * HCO3_u / ((gfrh * HCO3 > 1e-9) ? gfrh * HCO3 : 1e-9);
o_Hdef = Hdef * 24.0;
o_reserve = (NEAP_h > 1e-9) ? (Jh_max * LES / NEAP_h) : 0.0;
o_eGFR = GFR0 * neph;
o_Pi_u = Pi_load * 24.0;
o_resp = (HCO3 >= HCO3_set - RESP_MARGIN) ? 1.0 : 0.0;
## ===========================================================================
$TABLE
double pH_blood = o_pHbl;
double urine_pH = o_uph;
double NAE_day = o_NAE;
double TA_day = o_TA;
double NH4_day = o_NH4;
double HCO3u_day = o_HCO3u;
double NEAP_day = o_NEAP;
double alkali_day = o_alk;
double acid_gap = o_gap;
double bone_day = o_Jbone;
double VH_eff = o_VHc;
double SS_inst = o_SS;
double UCa_day = o_UCa;
double UCa_mgkg = o_UCa_mgkg;
double UCit_day = o_UCit;
double CaCit = o_CaCit;
double FE_HCO3 = o_FEHCO3;
double Hdef_day = o_Hdef;
double reserve = o_reserve;
double eGFR = o_eGFR;
double Pi_urine = o_Pi_u;
double GrowthMult = o_GM;
double responder = o_resp;
double HCO3_target = HCO3_set;
double waste_frac = (GIVEN > 1.0) ? (WASTE / GIVEN) : 0.0;
$CAPTURE
pH_blood urine_pH NAE_day TA_day NH4_day HCO3u_day NEAP_day alkali_day
acid_gap bone_day VH_eff SS_inst UCa_day UCa_mgkg UCit_day CaCit FE_HCO3
Hdef_day reserve eGFR Pi_urine GrowthMult responder HCO3_target waste_frac
## ===========================================================================
## R-SIDE HELPERS AND THE 28 SCENARIOS
## (mrgsolve ignores everything after $ENV that is not a recognised block;
## the code below is sourced by the accompanying driver, and is also valid
## standalone R.)
## ===========================================================================
$ENV
## ---- compartment indices, for building events ---------------------------
CMT <- c(AG_bicIR = 1, AG_citIR = 2, AG_bicPR = 3, AG_citPR = 4,
AG_KCl = 5, AG_K = 6, AG_hctz = 8, AG_vitD = 10, AG_acid = 56)
## ---- canonical subjects -------------------------------------------------
SUBJ <- list(
infant = list(BW = 8.0, AGE = 0.75, BSA = 0.42, GFR0 = 85),
toddler= list(BW = 12.0, AGE = 2.0, BSA = 0.54, GFR0 = 105),
child = list(BW = 30.0, AGE = 10.0, BSA = 1.05, GFR0 = 115),
teen = list(BW = 52.0, AGE = 15.0, BSA = 1.55, GFR0 = 110),
adult = list(BW = 70.0, AGE = 35.0, BSA = 1.80, GFR0 = 105)
)
## ---- lesion severities --------------------------------------------------
## LES = retained H+-pump Vmax (rate defect); LES_grad = retained maximal
## blood->urine pH gradient (gradient defect). Both are dimensionless
## fractions of normal, and they are the ONLY way the lesion enters.
LESION <- list(
none = list(LES = 1.00, LES_grad = 1.00),
incomplete = list(LES = 0.34, LES_grad = 0.70), # normal HCO3, fails acid load
complete = list(LES = 0.18, LES_grad = 0.58),
severe = list(LES = 0.12, LES_grad = 0.52), # infantile ATP6V0A4-like
gradonly = list(LES = 1.00, LES_grad = 0.58), # pure gradient defect
rateonly = list(LES = 0.18, LES_grad = 1.00) # pure rate defect
)
## ---- regimen builder ----------------------------------------------------
## kind: "none" | "bicIR" | "citIR" | "ADV" | "mixed"
## cation: "K" (potassium salt) | "Na" (sodium salt) | "NaK"
make_regimen <- function(kind = "none", mEq_kg_day = 0, times = c(7, 13, 19),
cation = "K", days = 365, BW = 30, f_cit = NULL,
KCl_mmol_day = 0, hctz_mg = 0, vitD_IU = 0,
start_day = 0) {
ev <- NULL
add <- function(cmt, amt, ii_times) {
for (ct in ii_times)
ev <<- rbind(ev, data.frame(
time = (start_day:(days - 1)) * 24 + ct, cmt = cmt, amt = amt,
evid = 1))
}
if (kind != "none" && mEq_kg_day > 0) {
per <- mEq_kg_day * BW / length(times)
fc <- if (!is.null(f_cit)) f_cit else
switch(kind, bicIR = 0, citIR = 1, ADV = 0.35, mixed = 0.5, 0)