pmcore/estimation/nonparametric/
psi.rs1use anyhow::bail;
2use anyhow::Result;
3use faer::Mat;
4use ndarray::{Array2, Axis};
5use pharmsol::prelude::simulator::log_likelihood_matrix;
6use pharmsol::AssayErrorModels;
7use pharmsol::Data;
8use pharmsol::Equation;
9use serde::{Deserialize, Serialize};
10
11use super::theta::Theta;
12
13#[derive(Debug, Clone, PartialEq)]
15pub struct Psi {
16 matrix: Mat<f64>,
17 row_log_scales: Vec<f64>,
21}
22
23impl Psi {
24 pub fn new() -> Self {
25 Psi {
26 matrix: Mat::new(),
27 row_log_scales: Vec::new(),
28 }
29 }
30
31 pub fn matrix(&self) -> &Mat<f64> {
32 &self.matrix
33 }
34
35 pub(crate) fn log_scale(&self) -> f64 {
36 self.row_log_scales.iter().sum()
37 }
38
39 pub(crate) fn row_log_scales(&self) -> &[f64] {
40 &self.row_log_scales
41 }
42
43 pub(crate) fn from_log_likelihoods(mut log_likelihoods: Array2<f64>) -> Result<Self> {
44 let mut row_log_scales = Vec::with_capacity(log_likelihoods.nrows());
45
46 for mut row in log_likelihoods.axis_iter_mut(Axis(0)) {
47 let row_max = row.iter().copied().fold(f64::NEG_INFINITY, f64::max);
48 if !row_max.is_finite() {
49 bail!("Each subject must have at least one finite log-likelihood");
50 }
51
52 row_log_scales.push(row_max);
53 row.mapv_inplace(|value| (value - row_max).exp());
54 }
55
56 let matrix = Mat::from_fn(log_likelihoods.nrows(), log_likelihoods.ncols(), |i, j| {
57 log_likelihoods[(i, j)]
58 });
59 Ok(Self {
60 matrix,
61 row_log_scales,
62 })
63 }
64
65 pub fn nspp(&self) -> usize {
66 self.matrix.nrows()
67 }
68
69 pub fn nsub(&self) -> usize {
70 self.matrix.ncols()
71 }
72
73 pub fn to_ndarray(&self) -> Array2<f64> {
74 let m = &self.matrix;
75 Array2::from_shape_fn((m.nrows(), m.ncols()), |(i, j)| m[(i, j)])
76 }
77
78 pub(crate) fn filter_column_indices(&mut self, indices: &[usize]) {
79 let matrix = self.matrix.to_owned();
80
81 let new = Mat::from_fn(matrix.nrows(), indices.len(), |r, c| {
82 *matrix.get(r, indices[c])
83 });
84
85 self.matrix = new;
86 }
87
88 pub fn write(&self, path: &str) {
89 let mut writer = csv::Writer::from_path(path).unwrap();
90 for row in self.matrix.row_iter() {
91 writer
92 .write_record(row.iter().map(|x| x.to_string()))
93 .unwrap();
94 }
95 }
96
97 pub fn to_csv<W: std::io::Write>(&self, writer: W) -> Result<()> {
98 let mut csv_writer = csv::Writer::from_writer(writer);
99
100 for i in 0..self.matrix.nrows() {
101 let row: Vec<f64> = (0..self.matrix.ncols())
102 .map(|j| *self.matrix.get(i, j))
103 .collect();
104 csv_writer.serialize(row)?;
105 }
106
107 csv_writer.flush()?;
108 Ok(())
109 }
110
111 pub fn from_csv<R: std::io::Read>(reader: R) -> Result<Self> {
112 let mut csv_reader = csv::Reader::from_reader(reader);
113 let mut rows: Vec<Vec<f64>> = Vec::new();
114
115 for result in csv_reader.deserialize() {
116 let row: Vec<f64> = result?;
117 rows.push(row);
118 }
119
120 if rows.is_empty() {
121 bail!("CSV file is empty");
122 }
123
124 let nrows = rows.len();
125 let ncols = rows[0].len();
126
127 for (i, row) in rows.iter().enumerate() {
128 if row.len() != ncols {
129 bail!("Row {} has {} columns, expected {}", i, row.len(), ncols);
130 }
131 }
132
133 let mat = Mat::from_fn(nrows, ncols, |i, j| rows[i][j]);
134
135 Ok(Psi {
136 matrix: mat,
137 row_log_scales: vec![0.0; nrows],
138 })
139 }
140}
141
142impl Default for Psi {
143 fn default() -> Self {
144 Psi::new()
145 }
146}
147
148impl From<Array2<f64>> for Psi {
149 fn from(array: Array2<f64>) -> Self {
150 let nrows = array.nrows();
151 let matrix = Mat::from_fn(nrows, array.ncols(), |i, j| array[(i, j)]);
152 Psi {
153 matrix,
154 row_log_scales: vec![0.0; nrows],
155 }
156 }
157}
158
159impl From<Mat<f64>> for Psi {
160 fn from(matrix: Mat<f64>) -> Self {
161 let nrows = matrix.nrows();
162 Psi {
163 matrix,
164 row_log_scales: vec![0.0; nrows],
165 }
166 }
167}
168
169impl From<&Array2<f64>> for Psi {
170 fn from(array: &Array2<f64>) -> Self {
171 let nrows = array.nrows();
172 let matrix = Mat::from_fn(nrows, array.ncols(), |i, j| array[(i, j)]);
173 Psi {
174 matrix,
175 row_log_scales: vec![0.0; nrows],
176 }
177 }
178}
179
180impl Serialize for Psi {
181 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
182 where
183 S: serde::Serializer,
184 {
185 use serde::ser::SerializeSeq;
186
187 let mut seq = serializer.serialize_seq(Some(self.matrix.nrows()))?;
188
189 for i in 0..self.matrix.nrows() {
190 let row: Vec<f64> = (0..self.matrix.ncols())
191 .map(|j| *self.matrix.get(i, j))
192 .collect();
193 seq.serialize_element(&row)?;
194 }
195
196 seq.end()
197 }
198}
199
200impl<'de> Deserialize<'de> for Psi {
201 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
202 where
203 D: serde::Deserializer<'de>,
204 {
205 use serde::de::{SeqAccess, Visitor};
206 use std::fmt;
207
208 struct PsiVisitor;
209
210 impl<'de> Visitor<'de> for PsiVisitor {
211 type Value = Psi;
212
213 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
214 formatter.write_str("a sequence of rows (vectors of f64)")
215 }
216
217 fn visit_seq<A>(self, mut seq: A) -> std::result::Result<Self::Value, A::Error>
218 where
219 A: SeqAccess<'de>,
220 {
221 let mut rows: Vec<Vec<f64>> = Vec::new();
222
223 while let Some(row) = seq.next_element::<Vec<f64>>()? {
224 rows.push(row);
225 }
226
227 if rows.is_empty() {
228 return Err(serde::de::Error::custom("Empty matrix not allowed"));
229 }
230
231 let nrows = rows.len();
232 let ncols = rows[0].len();
233
234 for (i, row) in rows.iter().enumerate() {
235 if row.len() != ncols {
236 return Err(serde::de::Error::custom(format!(
237 "Row {} has {} columns, expected {}",
238 i,
239 row.len(),
240 ncols
241 )));
242 }
243 }
244
245 let mat = Mat::from_fn(nrows, ncols, |i, j| rows[i][j]);
246
247 Ok(Psi {
248 matrix: mat,
249 row_log_scales: vec![0.0; nrows],
250 })
251 }
252 }
253
254 deserializer.deserialize_seq(PsiVisitor)
255 }
256}
257
258pub(crate) fn calculate_psi(
259 equation: &impl Equation,
260 subjects: &Data,
261 theta: &Theta,
262 error_models: &AssayErrorModels,
263 progress: bool,
264) -> Result<Psi> {
265 let tm = theta.matrix();
266 let theta_ndarray = Array2::from_shape_fn((tm.nrows(), tm.ncols()), |(i, j)| tm[(i, j)]);
267 let log_psi =
268 log_likelihood_matrix(equation, subjects, &theta_ndarray, error_models, progress)?;
269
270 Psi::from_log_likelihoods(log_psi)
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276 use ndarray::Array2;
277
278 #[test]
279 fn log_likelihood_rows_are_scaled_before_exponentiation() -> Result<()> {
280 let log_likelihoods = Array2::from_shape_vec((2, 2), vec![-1000.0, -1001.0, -2.0, -4.0])?;
281
282 let psi = Psi::from_log_likelihoods(log_likelihoods)?;
283
284 assert_eq!(psi.log_scale(), -1002.0);
285 assert_eq!(psi.row_log_scales(), &[-1000.0, -2.0]);
286 assert_eq!(psi.matrix()[(0, 0)], 1.0);
287 assert_eq!(psi.matrix()[(1, 0)], 1.0);
288 assert!((psi.matrix()[(0, 1)] - (-1.0_f64).exp()).abs() < 1e-12);
289 assert!((psi.matrix()[(1, 1)] - (-2.0_f64).exp()).abs() < 1e-12);
290 Ok(())
291 }
292
293 #[test]
294 fn test_from_array2() {
295 let array = Array2::from_shape_vec((2, 3), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]).unwrap();
296
297 let psi = Psi::from(array.clone());
298
299 assert_eq!(psi.nspp(), 2);
300 assert_eq!(psi.nsub(), 3);
301
302 let m = psi.matrix();
303 for i in 0..2 {
304 for j in 0..3 {
305 assert_eq!(m[(i, j)], array[[i, j]]);
306 }
307 }
308 }
309
310 #[test]
311 fn test_from_array2_ref() {
312 let array =
313 Array2::from_shape_vec((3, 2), vec![10.0, 20.0, 30.0, 40.0, 50.0, 60.0]).unwrap();
314
315 let psi = Psi::from(&array);
316
317 assert_eq!(psi.nspp(), 3);
318 assert_eq!(psi.nsub(), 2);
319
320 let m = psi.matrix();
321 for i in 0..3 {
322 for j in 0..2 {
323 assert_eq!(m[(i, j)], array[[i, j]]);
324 }
325 }
326 }
327
328 #[test]
329 fn test_nspp() {
330 let array =
331 Array2::from_shape_vec((4, 2), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]).unwrap();
332 let psi = Psi::from(array);
333
334 assert_eq!(psi.nspp(), 4);
335 }
336
337 #[test]
338 fn test_nspp_empty() {
339 let psi = Psi::new();
340 assert_eq!(psi.nspp(), 0);
341 }
342
343 #[test]
344 fn test_nspp_single_row() {
345 let array = Array2::from_shape_vec((1, 3), vec![1.0, 2.0, 3.0]).unwrap();
346 let psi = Psi::from(array);
347
348 assert_eq!(psi.nspp(), 1);
349 }
350
351 #[test]
352 fn test_nsub() {
353 let array = Array2::from_shape_vec(
354 (2, 5),
355 vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0],
356 )
357 .unwrap();
358 let psi = Psi::from(array);
359
360 assert_eq!(psi.nsub(), 5);
361 }
362
363 #[test]
364 fn test_nsub_empty() {
365 let psi = Psi::new();
366 assert_eq!(psi.nsub(), 0);
367 }
368}