pmcore/estimation/nonparametric/
predictions.rs1use std::path::Path;
2
3use anyhow::{bail, Result};
4use pharmsol::{prelude::simulator::Prediction, Censor, Data, Predictions as PredTrait};
5use rayon::prelude::*;
6use serde::{Deserialize, Serialize};
7
8use crate::{
9 estimation::nonparametric::{theta::Theta, weights::Weights},
10 estimation::nonparametric::{weighted_median, Posterior},
11};
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct NPPredictionRow {
15 id: String,
16 time: f64,
17 outeq: usize,
18 block: usize,
19 obs: Option<f64>,
20 cens: Censor,
21 pop_mean: f64,
22 pop_median: f64,
23 post_mean: f64,
24 post_median: f64,
25}
26
27impl NPPredictionRow {
28 pub fn id(&self) -> &str {
29 &self.id
30 }
31 pub fn time(&self) -> f64 {
32 self.time
33 }
34 pub fn outeq(&self) -> usize {
35 self.outeq
36 }
37 pub fn block(&self) -> usize {
38 self.block
39 }
40 pub fn obs(&self) -> Option<f64> {
41 self.obs
42 }
43 pub fn pop_mean(&self) -> f64 {
44 self.pop_mean
45 }
46 pub fn pop_median(&self) -> f64 {
47 self.pop_median
48 }
49 pub fn post_mean(&self) -> f64 {
50 self.post_mean
51 }
52 pub fn post_median(&self) -> f64 {
53 self.post_median
54 }
55
56 pub fn censoring(&self) -> Censor {
57 self.cens
58 }
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct NPPredictions {
63 predictions: Vec<NPPredictionRow>,
64}
65
66impl IntoIterator for NPPredictions {
67 type Item = NPPredictionRow;
68 type IntoIter = std::vec::IntoIter<NPPredictionRow>;
69
70 fn into_iter(self) -> Self::IntoIter {
71 self.predictions.into_iter()
72 }
73}
74
75impl Default for NPPredictions {
76 fn default() -> Self {
77 NPPredictions::new()
78 }
79}
80
81impl NPPredictions {
82 pub fn new() -> Self {
83 NPPredictions {
84 predictions: Vec::new(),
85 }
86 }
87
88 pub fn add(&mut self, row: NPPredictionRow) {
89 self.predictions.push(row);
90 }
91
92 pub fn predictions(&self) -> &[NPPredictionRow] {
93 &self.predictions
94 }
95
96 pub fn write(&self, path: &Path) -> Result<()> {
102 tracing::debug!("Writing predictions...");
103
104 super::create_parent_dir(path)?;
105
106 let mut writer = csv::WriterBuilder::new()
107 .has_headers(true)
108 .from_path(path)?;
109
110 for row in &self.predictions {
111 writer.serialize(row)?;
112 }
113
114 writer.flush()?;
115 Ok(())
116 }
117
118 pub fn calculate(
119 equation: &(impl pharmsol::prelude::simulator::Equation + Sync),
120 data: &Data,
121 theta: &Theta,
122 w: &Weights,
123 posterior: &Posterior,
124 idelta: f64,
125 tad: f64,
126 ) -> Result<Self> {
127 let data = data.clone().expand(idelta, tad);
128 let subjects = data.subjects();
129
130 if subjects.len() != posterior.matrix().nrows() {
131 bail!("Number of subjects and number of posterior means do not match");
132 };
133
134 let support_points: Vec<Vec<f64>> = theta
135 .matrix()
136 .row_iter()
137 .map(|spp| spp.iter().cloned().collect())
138 .collect();
139
140 let per_subject: Vec<Vec<NPPredictionRow>> = subjects
141 .par_iter()
142 .enumerate()
143 .map(|(subject_index, subject)| -> Result<Vec<NPPredictionRow>> {
144 let predictions: Vec<Vec<Prediction>> = support_points
145 .par_iter()
146 .map(|spp| {
147 Ok(equation
148 .simulate_subject_dense(subject, spp, None)?
149 .0
150 .get_predictions())
151 })
152 .collect::<Result<Vec<_>>>()?;
153
154 let Some(first_spp_preds) = predictions.first() else {
155 return Ok(Vec::new());
156 };
157 let n_points = first_spp_preds.len();
158
159 let mut pop_mean: Vec<f64> = vec![0.0; n_points];
160 for (i, outer_pred) in predictions.iter().enumerate() {
161 for (j, pred) in outer_pred.iter().enumerate() {
162 pop_mean[j] += pred.prediction() * w[i];
163 }
164 }
165
166 let mut pop_median: Vec<f64> = Vec::with_capacity(n_points);
167 for j in 0..n_points {
168 let mut values: Vec<f64> = Vec::new();
169 let mut weights: Vec<f64> = Vec::new();
170
171 for (i, outer_pred) in predictions.iter().enumerate() {
172 values.push(outer_pred[j].prediction());
173 weights.push(w[i]);
174 }
175
176 pop_median.push(weighted_median(&values, &weights));
177 }
178
179 let mut posterior_mean: Vec<f64> = vec![0.0; n_points];
180 for (i, outer_pred) in predictions.iter().enumerate() {
181 for (j, pred) in outer_pred.iter().enumerate() {
182 posterior_mean[j] +=
183 pred.prediction() * posterior.matrix()[(subject_index, i)];
184 }
185 }
186
187 let mut posterior_median: Vec<f64> = Vec::with_capacity(n_points);
188 for j in 0..n_points {
189 let mut values: Vec<f64> = Vec::new();
190 let mut weights: Vec<f64> = Vec::new();
191
192 for (i, outer_pred) in predictions.iter().enumerate() {
193 values.push(outer_pred[j].prediction());
194 weights.push(posterior.matrix()[(subject_index, i)]);
195 }
196
197 posterior_median.push(weighted_median(&values, &weights));
198 }
199
200 Ok(first_spp_preds
201 .iter()
202 .enumerate()
203 .map(|(j, p)| NPPredictionRow {
204 id: subject.id().clone(),
205 time: p.time(),
206 outeq: p.outeq(),
207 block: p.occasion(),
208 obs: p.observation(),
209 cens: p.censoring(),
210 pop_mean: pop_mean[j],
211 pop_median: pop_median[j],
212 post_mean: posterior_mean[j],
213 post_median: posterior_median[j],
214 })
215 .collect())
216 })
217 .collect::<Result<Vec<_>>>()?;
218
219 let mut container = NPPredictions::new();
220 for rows in per_subject {
221 for row in rows {
222 container.add(row);
223 }
224 }
225
226 Ok(container)
227 }
228}