Repository navigation
Expand file tree
/
Copy pathpnm_mrgsolve_model.R
More file actions
563 lines (481 loc) · 22.4 KB
/
Copy pathpnm_mrgsolve_model.R
File metadata and controls
563 lines (481 loc) · 22.4 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
################################################################################
# Pneumoconiosis (진폐증) — Quantitative Systems Pharmacology Model
# mrgsolve ODE-based PK/PD Simulation
#
# Disease: Pneumoconiosis (Silicosis / Coal Worker's Pneumoconiosis / Asbestosis)
# Compartments: 20 ODE compartments (Drug PK + Disease PD)
# Treatment Scenarios: 6 (Control + 5 interventions)
#
# Key References:
# Leung CC et al. Lancet 2012; Chen W et al. Occup Environ Med 2005
# Yu M et al. Sci Total Environ 2021; Castranova V, Vallyathan V. Environ Health Perspect 2000
# Dinarello CA. N Engl J Med 2009 (IL-1β/NLRP3); Wuyts WA et al. ERJ 2013 (IPF/fibrosis)
#
# Parameters calibrated to:
# - Silicosis cohort data (Doll 1959; Buchanan 1989; Miller 2002)
# - Pirfenidone IPF trials (CAPACITY, ASCEND) for anti-fibrotic calibration
# - Nintedanib INPULSIS trial data for kinase inhibitor effect
################################################################################
library(mrgsolve)
library(dplyr)
library(ggplot2)
library(tidyr)
# ==============================================================================
# MODEL DEFINITION
# ==============================================================================
pnm_model <- mrgsolve::mcode("pneumoconiosis_qsp", '
$PROB
Pneumoconiosis QSP Model
Dust-induced pulmonary fibrosis with PK/PD for 4 therapeutic agents
$PARAM
// ---- Dust Exposure Parameters ----
D_in = 0.5 // dust deposition rate (mg/month, occupational)
k_clr = 0.05 // mucociliary clearance rate constant (/month)
k_phag = 0.10 // macrophage phagocytic clearance (/month)
D_overload = 20.0 // dust overload threshold (mg)
// ---- Macrophage Parameters ----
AM0_base = 100.0 // baseline resting AM (normalized units)
k_AM_act = 0.04 // rate of AM activation by dust (/mg/month)
k_AM_rec = 0.02 // AM recovery to resting state (/month)
k_AM_pyr = 0.01 // AM pyroptosis rate (/month)
k_mono = 0.03 // monocyte recruitment rate (CXCL chemokine-driven)
// ---- Inflammasome/Cytokine Parameters ----
k_NLRP3 = 0.08 // NLRP3 activation rate by silica/ROS (/month)
k_NLRP3_d = 0.15 // NLRP3 deactivation rate (/month)
k_IL1b_syn = 5.0 // IL-1β synthesis rate (pg/mL/month per NLRP3 unit)
k_IL1b_deg = 0.3 // IL-1β degradation rate (/month)
k_TNFa_syn = 3.0 // TNF-α synthesis rate
k_TNFa_deg = 0.4 // TNF-α degradation rate
k_IL10_syn = 1.0 // IL-10 synthesis rate (anti-inflammatory)
k_IL10_deg = 0.25 // IL-10 degradation rate
// ---- Oxidative Stress Parameters ----
k_ROS_syn = 0.12 // ROS production rate per activated AM
k_ROS_deg = 0.20 // ROS scavenging/degradation rate
GSH_base = 10.0 // baseline GSH (mM normalized)
k_GSH_syn = 0.08 // GSH synthesis rate
k_GSH_dep = 0.06 // GSH depletion by ROS
// ---- Fibrotic Signaling Parameters ----
k_TGFb_syn = 2.0 // TGF-β1 synthesis per activated AM and IL-1β
k_TGFb_deg = 0.20 // TGF-β1 degradation
k_Fibro_syn = 0.05 // fibroblast proliferation rate (per TGF-β)
k_Fibro_deg = 0.03 // fibroblast apoptosis rate
k_MyoFib = 0.08 // myofibroblast differentiation rate (TGF-β driven)
k_MyoFib_d = 0.02 // myofibroblast apoptosis rate
k_Coll_syn = 0.10 // collagen synthesis rate per myofibroblast
k_Coll_deg = 0.01 // collagen degradation rate (MMP activity)
// ---- Pulmonary Function Parameters ----
FVC_0 = 100.0 // baseline FVC (% predicted)
FVC_min = 30.0 // minimum FVC (severe disease)
k_FVC_loss = 0.008 // FVC decline per unit collagen accumulation
k_PVR_inc = 0.005 // PVR increase per unit collagen (pulm hypertension)
PVR_0 = 1.0 // baseline PVR (Wood Units, normalized)
// ---- Neutrophil Parameters ----
k_Neut_rec = 0.15 // neutrophil recruitment per IL-8 unit
k_Neut_d = 0.20 // neutrophil clearance rate
// ---- NAC PK Parameters (N-Acetylcysteine) ----
NAC_ka = 1.5 // absorption rate constant (/h)
NAC_CL = 15.0 // clearance (L/h)
NAC_Vd = 25.0 // volume of distribution (L)
NAC_Emax = 0.80 // maximum GSH replenishment effect
NAC_EC50 = 5.0 // concentration at half-maximal effect (µg/mL)
// ---- Pirfenidone PK Parameters ----
Pirf_ka = 2.0 // absorption rate (/h)
Pirf_CL = 3.5 // clearance (L/h)
Pirf_Vd = 70.0 // volume of distribution (L)
Pirf_Emax = 0.75 // TGF-β inhibition efficacy
Pirf_EC50 = 2.0 // EC50 (µg/mL) for TGF-β inhibition
// ---- Nintedanib PK Parameters ----
Nint_ka = 0.8 // absorption rate (/h)
Nint_CL = 1.3 // clearance (L/h)
Nint_Vd = 640.0 // volume of distribution (L)
Nint_Emax = 0.70 // PDGFR/FGFR/VEGFR inhibition
Nint_EC50 = 0.05 // EC50 (µg/mL, potent kinase inhibitor)
// ---- Tetrandrine PK Parameters ----
Tetra_ka = 1.2 // absorption rate (/h)
Tetra_CL = 8.0 // clearance (L/h)
Tetra_Vd = 120.0 // volume of distribution (L)
Tetra_Emax = 0.65 // NF-κB inhibition efficacy
Tetra_EC50 = 0.3 // EC50 (µg/mL)
$INIT
// Dust compartments
D_alv = 0.0 // alveolar dust burden (mg)
D_clr = 0.0 // cleared dust
// Macrophage compartments
AM_rest = 100.0 // resting alveolar macrophages
AM_act = 0.0 // activated macrophages (M1 phenotype)
// Inflammasome
NLRP3 = 0.0 // NLRP3 activation index
// Cytokines (pg/mL)
IL1b = 0.5 // IL-1β baseline
TNFa = 1.0 // TNF-α baseline
TGFb = 2.0 // TGF-β1 baseline
IL10 = 2.0 // IL-10 baseline (anti-inflammatory)
// Oxidative stress
ROS = 1.0 // ROS index (normalized)
GSH = 10.0 // glutathione (mM normalized)
// Cellular compartments
Neutro = 5.0 // neutrophil infiltration index
Fibro = 1.0 // fibroblast proliferation index
MyoFib = 0.0 // myofibroblast differentiation index
// Structural remodeling
Coll = 1.0 // collagen accumulation index
// Clinical endpoints
FVC = 100.0 // FVC % predicted
PVR = 1.0 // pulmonary vascular resistance (normalized)
// Drug PK compartments
C_NAC = 0.0 // NAC plasma concentration (µg/mL)
C_Pirf = 0.0 // pirfenidone plasma concentration
C_Nint = 0.0 // nintedanib plasma concentration
C_Tetra = 0.0 // tetrandrine plasma concentration
$ODE
// ------------------------------------------------------------------
// Dust Deposition & Clearance
// ------------------------------------------------------------------
// Overload factor: when dust exceeds threshold, clearance is impaired
double overload_factor = 1.0 / (1.0 + D_alv / D_overload);
dxdt_D_alv = D_in - k_clr * D_alv * overload_factor
- k_phag * AM_rest * D_alv / (D_alv + 5.0);
dxdt_D_clr = k_clr * D_alv * overload_factor
+ k_phag * AM_rest * D_alv / (D_alv + 5.0);
// ------------------------------------------------------------------
// Alveolar Macrophage Dynamics
// ------------------------------------------------------------------
double D_effect = k_AM_act * D_alv; // dust-driven activation
double Pirfenidone_inh_TGFb = Pirf_Emax * C_Pirf / (Pirf_EC50 + C_Pirf);
double Tetra_inh_NFkB = Tetra_Emax * C_Tetra / (Tetra_EC50 + C_Tetra);
dxdt_AM_rest = k_mono * 10.0 - D_effect * AM_rest - k_AM_pyr * AM_act;
dxdt_AM_act = D_effect * AM_rest - k_AM_rec * AM_act - k_AM_pyr * AM_act;
// ------------------------------------------------------------------
// NLRP3 Inflammasome
// ------------------------------------------------------------------
// Activated by silica, ROS; inhibited by tetrandrine/IL-10
double NLRP3_drive = k_NLRP3 * AM_act * ROS / (1.0 + IL10 / 5.0);
NLRP3_drive = NLRP3_drive * (1.0 - Tetra_inh_NFkB);
dxdt_NLRP3 = NLRP3_drive - k_NLRP3_d * NLRP3;
// ------------------------------------------------------------------
// Cytokine Dynamics
// ------------------------------------------------------------------
// IL-1β: produced by NLRP3 activation
dxdt_IL1b = k_IL1b_syn * NLRP3 - k_IL1b_deg * IL1b;
// TNF-α: produced by activated macrophages and IL-1β feedback
double Tetra_inh_TNF = Tetra_Emax * C_Tetra / (Tetra_EC50 + C_Tetra);
dxdt_TNFa = k_TNFa_syn * AM_act * (1.0 + 0.5 * IL1b / 10.0)
* (1.0 - Tetra_inh_TNF)
- k_TNFa_deg * TNFa;
// TGF-β1: key fibrotic driver (macrophages, Th2)
// Pirfenidone inhibits TGF-β synthesis/signaling
double Nint_inh = Nint_Emax * C_Nint / (Nint_EC50 + C_Nint);
dxdt_TGFb = k_TGFb_syn * (AM_act + 0.3 * IL1b / 5.0)
* (1.0 - Pirfenidone_inh_TGFb)
* (1.0 - 0.3 * Nint_inh)
- k_TGFb_deg * TGFb;
// IL-10: anti-inflammatory, produced by M2 macrophages
dxdt_IL10 = k_IL10_syn * (AM_act * 0.5 + 2.0) - k_IL10_deg * IL10;
// ------------------------------------------------------------------
// Oxidative Stress
// ------------------------------------------------------------------
// ROS produced by activated macrophages and neutrophils
// NAC increases GSH, reducing ROS
double NAC_eff = NAC_Emax * C_NAC / (NAC_EC50 + C_NAC);
dxdt_ROS = k_ROS_syn * (AM_act + 0.3 * Neutro)
- k_ROS_deg * ROS * (1.0 + GSH / GSH_base)
- 0.5 * NAC_eff * ROS;
// Glutathione dynamics
dxdt_GSH = k_GSH_syn * GSH_base * (1.0 + NAC_eff)
- k_GSH_dep * ROS * GSH
- 0.02 * GSH;
// ------------------------------------------------------------------
// Neutrophil Recruitment
// ------------------------------------------------------------------
dxdt_Neutro = k_Neut_rec * (IL1b + TNFa) / 10.0
- k_Neut_d * Neutro;
// ------------------------------------------------------------------
// Fibroblast & Myofibroblast Dynamics
// ------------------------------------------------------------------
// Pirfenidone and nintedanib both suppress fibroblast proliferation
double Pirf_inh_Fibro = Pirf_Emax * 0.5 * C_Pirf / (Pirf_EC50 + C_Pirf);
double Nint_inh_Fibro = Nint_Emax * 0.4 * C_Nint / (Nint_EC50 + C_Nint);
dxdt_Fibro = k_Fibro_syn * TGFb * (1.0 - Pirf_inh_Fibro - Nint_inh_Fibro)
- k_Fibro_deg * Fibro;
dxdt_MyoFib = k_MyoFib * TGFb * Fibro * (1.0 - Pirfenidone_inh_TGFb)
* (1.0 - 0.5 * Nint_inh)
- k_MyoFib_d * MyoFib;
// ------------------------------------------------------------------
// Collagen Accumulation (Structural Remodeling)
// ------------------------------------------------------------------
// Net collagen = synthesis (myofibroblasts) - degradation (MMPs)
// Pirfenidone reduces collagen synthesis
dxdt_Coll = k_Coll_syn * MyoFib * (1.0 - 0.6 * Pirfenidone_inh_TGFb)
- k_Coll_deg * Coll;
// ------------------------------------------------------------------
// Pulmonary Function (FVC % predicted)
// ------------------------------------------------------------------
// FVC declines as collagen accumulates; cannot go below minimum
double FVC_target = FVC_0 - k_FVC_loss * (Coll - 1.0) * 100.0;
if(FVC_target < FVC_min) FVC_target = FVC_min;
dxdt_FVC = 0.1 * (FVC_target - FVC);
// ------------------------------------------------------------------
// Pulmonary Vascular Resistance
// ------------------------------------------------------------------
dxdt_PVR = k_PVR_inc * (Coll - 1.0) + 0.02 * (100.0 - FVC) / 100.0 - 0.01 * (PVR - 1.0);
// ------------------------------------------------------------------
// Drug PK — 1-compartment first-order absorption
// ------------------------------------------------------------------
dxdt_C_NAC = NAC_ka * ALAG_NAC - (NAC_CL / NAC_Vd) * C_NAC;
dxdt_C_Pirf = Pirf_ka * ALAG_Pirf - (Pirf_CL / Pirf_Vd) * C_Pirf;
dxdt_C_Nint = Nint_ka * ALAG_Nint - (Nint_CL / Nint_Vd) * C_Nint;
dxdt_C_Tetra = Tetra_ka * ALAG_Tetra - (Tetra_CL / Tetra_Vd) * C_Tetra;
$CAPTURE
// Capture derived variables for output
double FEV1 = FVC * 0.80; // FEV1 roughly tracks FVC
double DLCO = 100.0 - 0.7 * (100.0 - FVC); // DLCO declines faster
double mPAP = 15.0 + (PVR - 1.0) * 12.0; // mean PAP (mmHg)
double RV_load = (PVR - 1.0) * 30.0; // RV strain index
double KL6 = 200.0 + Coll * 150.0; // KL-6 biomarker (U/mL)
double dyspnea = (100.0 - FVC) / 14.0; // mMRC dyspnea score (0-4)
double overload_factor = 1.0 / (1.0 + D_alv / D_overload);
double Pirf_inh = Pirf_Emax * C_Pirf / (Pirf_EC50 + C_Pirf);
double Nint_inh = Nint_Emax * C_Nint / (Nint_EC50 + C_Nint);
double NAC_eff = NAC_Emax * C_NAC / (NAC_EC50 + C_NAC);
$PARAM ALAG_NAC=0, ALAG_Pirf=0, ALAG_Nint=0, ALAG_Tetra=0
')
# ==============================================================================
# SIMULATION SCENARIOS
# ==============================================================================
# Time: 0–120 months (10 years) with units in months
sim_time <- seq(0, 120, by = 0.5)
# Dosing events (convert to hours for PK, or scale appropriately)
# Note: dust exposure modeled as continuous input (D_in parameter)
# --- Scenario 1: Disease Natural History (No Treatment) ---
scenario1 <- function(mod) {
mod %>%
param(D_in = 0.5) %>%
mrgsim(end = 120, delta = 0.5) %>%
as_tibble() %>%
mutate(Scenario = "1. No Treatment\n(Natural History)")
}
# --- Scenario 2: NAC Monotherapy (600 mg TID oral) ---
scenario2 <- function(mod) {
nac_dose <- ev(amt = 600 * 0.044, cmt = "C_NAC", # dose in µg/mL·L units
time = 0, ii = 0.333, addl = 359 * 3) # TID for 10 years
mod %>%
param(D_in = 0.5, NAC_Emax = 0.80) %>%
ev(nac_dose) %>%
mrgsim(end = 120, delta = 0.5) %>%
as_tibble() %>%
mutate(Scenario = "2. NAC 600 mg TID\n(Antioxidant)")
}
# --- Scenario 3: Pirfenidone Monotherapy (2403 mg/day) ---
scenario3 <- function(mod) {
pirf_dose <- ev(amt = 801 * 0.025, cmt = "C_Pirf", # 801mg TID
time = 0, ii = 0.333, addl = 359 * 3)
mod %>%
param(D_in = 0.5, Pirf_Emax = 0.75) %>%
ev(pirf_dose) %>%
mrgsim(end = 120, delta = 0.5) %>%
as_tibble() %>%
mutate(Scenario = "3. Pirfenidone 2403 mg/d\n(Anti-fibrotic)")
}
# --- Scenario 4: Nintedanib Monotherapy (300 mg/day) ---
scenario4 <- function(mod) {
nint_dose <- ev(amt = 150 * 0.0017, cmt = "C_Nint", # 150mg BID
time = 0, ii = 0.5, addl = 360 * 2)
mod %>%
param(D_in = 0.5, Nint_Emax = 0.70) %>%
ev(nint_dose) %>%
mrgsim(end = 120, delta = 0.5) %>%
as_tibble() %>%
mutate(Scenario = "4. Nintedanib 300 mg/d\n(Anti-fibrotic)")
}
# --- Scenario 5: Dust Cessation + NAC (Removal from Exposure) ---
scenario5 <- function(mod) {
nac_dose <- ev(amt = 600 * 0.044, cmt = "C_NAC",
time = 0, ii = 0.333, addl = 359 * 3)
mod %>%
param(D_in = 0.0, # dust exposure cessation
NAC_Emax = 0.80) %>%
ev(nac_dose) %>%
mrgsim(end = 120, delta = 0.5) %>%
as_tibble() %>%
mutate(Scenario = "5. Dust Cessation\n+ NAC (Removal)")
}
# --- Scenario 6: Combination Pirfenidone + NAC ---
scenario6 <- function(mod) {
nac_dose <- ev(amt = 600 * 0.044, cmt = "C_NAC", time = 0, ii = 0.333, addl = 359*3)
pirf_dose <- ev(amt = 801 * 0.025, cmt = "C_Pirf", time = 0, ii = 0.333, addl = 359*3)
mod %>%
param(D_in = 0.5, NAC_Emax = 0.80, Pirf_Emax = 0.75) %>%
ev(nac_dose + pirf_dose) %>%
mrgsim(end = 120, delta = 0.5) %>%
as_tibble() %>%
mutate(Scenario = "6. Pirfenidone + NAC\n(Combination)")
}
# Run all scenarios
cat("Running Pneumoconiosis QSP simulations...\n")
res1 <- scenario1(pnm_model)
res2 <- scenario2(pnm_model)
res3 <- scenario3(pnm_model)
res4 <- scenario4(pnm_model)
res5 <- scenario5(pnm_model)
res6 <- scenario6(pnm_model)
all_results <- bind_rows(res1, res2, res3, res4, res5, res6)
# ==============================================================================
# KEY SIMULATION PLOTS
# ==============================================================================
theme_pnm <- theme_bw() +
theme(
legend.position = "bottom",
legend.title = element_blank(),
plot.title = element_text(face = "bold", size = 12),
strip.background = element_rect(fill = "steelblue"),
strip.text = element_text(color = "white", face = "bold")
)
pal <- c("#2C3E50", "#E74C3C", "#27AE60", "#3498DB", "#F39C12", "#8E44AD")
# --- Plot 1: FVC Trajectory (Primary Endpoint) ---
p_fvc <- ggplot(all_results, aes(x = time, y = FVC, color = Scenario)) +
geom_line(linewidth = 1.1) +
geom_hline(yintercept = 70, linetype = "dashed", color = "red", alpha = 0.5) +
annotate("text", x = 5, y = 68, label = "Severe (<70%)", color = "red", size = 3) +
scale_color_manual(values = pal) +
labs(title = "FVC Decline Over Time (Pneumoconiosis QSP Model)",
x = "Time (months)", y = "FVC (% predicted)") +
theme_pnm
# --- Plot 2: Collagen Accumulation (Fibrosis Marker) ---
p_coll <- ggplot(all_results, aes(x = time, y = Coll, color = Scenario)) +
geom_line(linewidth = 1.1) +
scale_color_manual(values = pal) +
labs(title = "Collagen Accumulation (Fibrosis Index)",
x = "Time (months)", y = "Collagen Index (normalized)") +
theme_pnm
# --- Plot 3: TGF-β1 Dynamics ---
p_tgfb <- ggplot(all_results, aes(x = time, y = TGFb, color = Scenario)) +
geom_line(linewidth = 1.1) +
scale_color_manual(values = pal) +
labs(title = "TGF-β1 (Key Fibrotic Driver)",
x = "Time (months)", y = "TGF-β1 Concentration (pg/mL)") +
theme_pnm
# --- Plot 4: Pulmonary Vascular Resistance ---
p_pvr <- ggplot(all_results, aes(x = time, y = PVR, color = Scenario)) +
geom_line(linewidth = 1.1) +
scale_color_manual(values = pal) +
labs(title = "Pulmonary Vascular Resistance (PAH Risk)",
x = "Time (months)", y = "PVR (normalized, Wood Units)") +
theme_pnm
# --- Plot 5: Oxidative Stress (ROS & GSH) ---
p_ros <- all_results %>%
select(time, ROS, GSH, Scenario) %>%
pivot_longer(c(ROS, GSH), names_to = "Variable", values_to = "Value") %>%
ggplot(aes(x = time, y = Value, color = Scenario, linetype = Variable)) +
geom_line(linewidth = 1.0) +
scale_color_manual(values = pal) +
labs(title = "Oxidative Stress (ROS & GSH Dynamics)",
x = "Time (months)", y = "Normalized concentration") +
theme_pnm
# --- Plot 6: KL-6 Biomarker ---
p_kl6 <- ggplot(all_results, aes(x = time, y = KL6, color = Scenario)) +
geom_line(linewidth = 1.1) +
geom_hline(yintercept = 500, linetype = "dashed", color = "darkred", alpha=0.6) +
annotate("text", x = 5, y = 520, label = "Clinical threshold 500 U/mL",
color = "darkred", size = 3) +
scale_color_manual(values = pal) +
labs(title = "KL-6 Biomarker (Interstitial Lung Disease Activity)",
x = "Time (months)", y = "KL-6 (U/mL)") +
theme_pnm
# Print summary statistics at 5-year and 10-year marks
cat("\n===== 5-Year (60-month) Outcomes =====\n")
all_results %>%
filter(abs(time - 60) < 0.3) %>%
group_by(Scenario) %>%
slice(1) %>%
select(Scenario, FVC, DLCO, Coll, TGFb, KL6, mPAP, dyspnea) %>%
print()
cat("\n===== 10-Year (120-month) Outcomes =====\n")
all_results %>%
filter(abs(time - 120) < 0.3) %>%
group_by(Scenario) %>%
slice(1) %>%
select(Scenario, FVC, DLCO, Coll, TGFb, KL6, mPAP, dyspnea) %>%
print()
# ==============================================================================
# SENSITIVITY ANALYSIS: Key Parameter Impact on FVC at 10 years
# ==============================================================================
sensitivity_analysis <- function(param_name, values, base_mod = pnm_model) {
purrr::map_dfr(values, function(val) {
mod <- base_mod %>% param(!!sym(param_name) := val)
res <- mrgsim(mod, end = 120, delta = 2) %>% as_tibble()
data.frame(
param = param_name,
param_val = val,
FVC_10yr = res$FVC[nrow(res)],
Coll_10yr = res$Coll[nrow(res)]
)
})
}
# Run sensitivity for key parameters
sens_k_TGFb_syn <- sensitivity_analysis("k_TGFb_syn", c(0.5, 1.0, 2.0, 4.0, 8.0))
sens_k_Coll_syn <- sensitivity_analysis("k_Coll_syn", c(0.02, 0.05, 0.10, 0.20, 0.40))
sens_D_in <- sensitivity_analysis("D_in", c(0.1, 0.3, 0.5, 1.0, 2.0))
sens_all <- bind_rows(sens_k_TGFb_syn, sens_k_Coll_syn, sens_D_in)
p_sens <- ggplot(sens_all, aes(x = param_val, y = FVC_10yr, color = param, group = param)) +
geom_line(linewidth = 1.2) +
geom_point(size = 3) +
facet_wrap(~param, scales = "free_x", nrow = 1) +
labs(title = "Sensitivity Analysis: FVC at 10 Years vs Key Parameters",
x = "Parameter Value", y = "FVC at 10 years (%)") +
theme_bw() +
theme(legend.position = "none",
strip.background = element_rect(fill = "steelblue"),
strip.text = element_text(color = "white", face = "bold"))
# ==============================================================================
# VIRTUAL PATIENT POPULATION (Monte Carlo)
# ==============================================================================
set.seed(42)
n_patients <- 200
# Generate population with variability in key PD parameters (30% CV)
patient_params <- tibble(
ID = 1:n_patients,
k_TGFb_syn = rlnorm(n_patients, log(2.0), 0.3),
k_Coll_syn = rlnorm(n_patients, log(0.10), 0.3),
k_FVC_loss = rlnorm(n_patients, log(0.008), 0.3),
D_in = rlnorm(n_patients, log(0.5), 0.4),
FVC_0 = rnorm(n_patients, mean = 100, sd = 8),
Scenario = sample(c("No Treatment", "Pirfenidone"), n_patients, replace = TRUE)
)
# Run virtual patient simulation
run_vp <- function(row) {
Pirf_dose_amt <- if (row$Scenario == "Pirfenidone") 801 * 0.025 else 0
pirf_ev <- ev(amt = Pirf_dose_amt, cmt = "C_Pirf", time = 0, ii = 0.333, addl = 359*3)
pnm_model %>%
param(k_TGFb_syn = row$k_TGFb_syn,
k_Coll_syn = row$k_Coll_syn,
k_FVC_loss = row$k_FVC_loss,
D_in = row$D_in,
FVC_0 = row$FVC_0) %>%
init(FVC = row$FVC_0) %>%
ev(pirf_ev) %>%
mrgsim(end = 120, delta = 6) %>%
as_tibble() %>%
mutate(ID = row$ID, Scenario = row$Scenario)
}
cat("\nRunning Virtual Patient Population Simulation (n=200)...\n")
vp_results <- patient_params %>%
split(1:nrow(.)) %>%
purrr::map_dfr(run_vp)
p_vp <- vp_results %>%
group_by(time, Scenario) %>%
summarise(
FVC_med = median(FVC),
FVC_q5 = quantile(FVC, 0.05),
FVC_q95 = quantile(FVC, 0.95),
.groups = "drop"
) %>%
ggplot(aes(x = time, y = FVC_med, color = Scenario, fill = Scenario)) +
geom_ribbon(aes(ymin = FVC_q5, ymax = FVC_q95), alpha = 0.2, color = NA) +
geom_line(linewidth = 1.3) +
scale_color_manual(values = c("No Treatment" = "#E74C3C", "Pirfenidone" = "#27AE60")) +
scale_fill_manual(values = c("No Treatment" = "#E74C3C", "Pirfenidone" = "#27AE60")) +
labs(title = "Virtual Patient Population: FVC (Median ± 90% PI, n=200)",
x = "Time (months)", y = "FVC (% predicted)",
subtitle = "Ribbon = 5th–95th percentile") +
theme_pnm
cat("\nAll simulations complete. Use Shiny app (pnm_shiny_app.R) for interactive exploration.\n")