mod distributions;
mod matrix;
mod prediction;
mod progress;
mod subject;
pub use matrix::log_likelihood_matrix;
#[allow(deprecated)]
pub use matrix::log_psi;
#[allow(deprecated)]
pub use matrix::psi;
pub use prediction::Prediction;
pub use subject::{PopulationPredictions, SubjectPredictions};
use ndarray::{Array2, Axis};
use rayon::prelude::*;
use crate::{Data, Equation, PharmsolError, Predictions, Subject};
pub fn log_likelihood_batch(
equation: &impl Equation,
subjects: &Data,
parameters: &Array2<f64>,
residual_error_models: &crate::ResidualErrorModels,
) -> Result<Vec<f64>, PharmsolError> {
let subject_slice = subjects.subjects_slice();
let n_subjects = subject_slice.len();
if parameters.nrows() != n_subjects {
return Err(PharmsolError::OtherError(format!(
"parameters has {} rows but there are {} subjects",
parameters.nrows(),
n_subjects
)));
}
let score_subject = |subject: &Subject, parameter_row: &[f64]| {
let predictions = match equation.estimate_predictions_dense(subject, parameter_row) {
Ok(preds) => preds,
Err(_) => return f64::NEG_INFINITY,
};
let obs_pred_pairs = predictions
.get_predictions()
.into_iter()
.filter_map(|pred| {
pred.observation()
.map(|obs| (pred.outeq(), obs, pred.prediction()))
});
residual_error_models.total_log_likelihood(obs_pred_pairs)
};
let results: Vec<f64> = if let Some(flat_parameters) = parameters.as_slice() {
let width = parameters.ncols();
subject_slice
.par_iter()
.enumerate()
.map(|(i, subject)| {
let start = i * width;
score_subject(subject, &flat_parameters[start..start + width])
})
.collect()
} else {
let parameter_rows = parameters
.axis_iter(Axis(0))
.map(|row| row.to_vec())
.collect::<Vec<_>>();
subject_slice
.par_iter()
.enumerate()
.map(|(i, subject)| score_subject(subject, ¶meter_rows[i]))
.collect()
};
Ok(results)
}
pub fn log_likelihood_subject(
equation: &impl Equation,
subject: &Subject,
params: &crate::Parameters,
residual_error_models: &crate::ResidualErrorModels,
) -> f64 {
let predictions = match equation.estimate_predictions_dense(subject, params.as_slice()) {
Ok(preds) => preds,
Err(_) => return f64::NEG_INFINITY,
};
let obs_pred_pairs = predictions
.get_predictions()
.into_iter()
.filter_map(|pred| {
pred.observation()
.map(|obs| (pred.outeq(), obs, pred.prediction()))
});
residual_error_models.total_log_likelihood(obs_pred_pairs)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::data::error_model::{AssayErrorModel, ErrorPoly};
use crate::data::event::Observation;
use crate::Censor;
#[test]
fn test_log_likelihood_equals_log_of_likelihood() {
let prediction = Prediction {
time: 1.0,
observation: Some(10.0),
prediction: 10.5,
outeq: 0,
errorpoly: None,
state: vec![10.5],
occasion: 0,
censoring: Censor::None,
};
let error_models = crate::AssayErrorModels::empty()
.add(
0,
AssayErrorModel::additive(ErrorPoly::new(0.0, 1.0, 0.0, 0.0), 0.0),
)
.unwrap();
#[allow(deprecated)]
let lik = prediction.likelihood(&error_models).unwrap();
let log_lik = prediction.log_likelihood(&error_models).unwrap();
let expected_log_lik = lik.ln();
assert!(
(log_lik - expected_log_lik).abs() < 1e-10,
"log_likelihood ({}) should equal ln(likelihood) ({})",
log_lik,
expected_log_lik
);
}
#[test]
fn test_subject_predictions_log_likelihood() {
let predictions = vec![
Prediction {
time: 1.0,
observation: Some(10.0),
prediction: 10.1,
outeq: 0,
errorpoly: None,
state: vec![10.1],
occasion: 0,
censoring: Censor::None,
},
Prediction {
time: 2.0,
observation: Some(8.0),
prediction: 8.2,
outeq: 0,
errorpoly: None,
state: vec![8.2],
occasion: 0,
censoring: Censor::None,
},
];
let subject_predictions = SubjectPredictions::from(predictions);
let error_models = crate::AssayErrorModels::empty()
.add(
0,
AssayErrorModel::additive(ErrorPoly::new(0.0, 1.0, 0.0, 0.0), 0.0),
)
.unwrap();
#[allow(deprecated)]
let lik = subject_predictions.likelihood(&error_models).unwrap();
let log_lik = subject_predictions.log_likelihood(&error_models).unwrap();
let expected_log_lik = lik.ln();
assert!(
(log_lik - expected_log_lik).abs() < 1e-10,
"Subject log_likelihood ({}) should equal ln(likelihood) ({})",
log_lik,
expected_log_lik
);
}
#[test]
fn test_empty_predictions_have_neutral_log_likelihood() {
let preds = SubjectPredictions::default();
let errors = crate::AssayErrorModels::empty();
assert_eq!(preds.log_likelihood(&errors).unwrap(), 0.0); }
#[test]
fn test_log_likelihood_combines_observations() {
let mut preds = SubjectPredictions::default();
let obs = Observation::new(0.0, Some(1.0), 0, None, 0, Censor::None);
preds.add_prediction(obs.to_prediction(1.0, vec![]));
let error_model = AssayErrorModel::additive(ErrorPoly::new(1.0, 0.0, 0.0, 0.0), 0.0);
let errors = crate::AssayErrorModels::empty()
.add(0, error_model)
.unwrap();
let log_lik = preds.log_likelihood(&errors).unwrap();
assert!(log_lik.is_finite());
assert!(log_lik <= 0.0); }
#[test]
fn test_lognormpdf_direct() {
use super::distributions::lognormpdf;
let obs = 0.0;
let pred = 0.0;
let sigma = 1.0;
let log_pdf = lognormpdf(obs, pred, sigma);
let expected = -0.5 * distributions::LOG_2PI;
assert!(
(log_pdf - expected).abs() < 1e-12,
"lognormpdf at mean should be -0.5*ln(2π)"
);
}
}