use std::fmt::Debug;
pub mod analytical;
pub mod meta;
pub mod ode;
pub mod sde;
pub use analytical::*;
pub use meta::*;
pub use ode::*;
pub use sde::*;
use crate::{
error_model::AssayErrorModels,
simulator::{Fa, Lag},
Covariates, Event, Infusion, Observation, PharmsolError, Subject,
};
use super::likelihood::Prediction;
pub trait State {
fn add_bolus(&mut self, input: usize, amount: f64);
}
pub trait Predictions: Default {
fn new(_nparticles: usize) -> Self {
Default::default()
}
fn squared_error(&self) -> f64;
fn get_predictions(&self) -> Vec<Prediction>;
fn log_likelihood(&self, error_models: &AssayErrorModels) -> Result<f64, PharmsolError>;
}
pub trait EquationTypes {
type S: State + Debug;
type P: Predictions;
}
pub(crate) trait EquationPriv: EquationTypes {
fn lag(&self) -> &Lag;
fn fa(&self) -> &Fa;
fn get_nstates(&self) -> usize;
fn get_ndrugs(&self) -> usize;
fn get_nouteqs(&self) -> usize;
fn solve(
&self,
state: &mut Self::S,
support_point: &[f64],
covariates: &Covariates,
infusions: &[Infusion],
start_time: f64,
end_time: f64,
) -> Result<(), PharmsolError>;
fn nparticles(&self) -> usize {
1
}
#[allow(dead_code)]
fn is_sde(&self) -> bool {
false
}
#[allow(clippy::too_many_arguments)]
fn process_observation(
&self,
support_point: &[f64],
observation: &Observation,
error_models: Option<&AssayErrorModels>,
time: f64,
covariates: &Covariates,
x: &mut Self::S,
likelihood: &mut Vec<f64>,
output: &mut Self::P,
) -> Result<(), PharmsolError>;
fn initial_state(
&self,
support_point: &[f64],
covariates: &Covariates,
occasion_index: usize,
) -> Self::S;
#[allow(clippy::too_many_arguments)]
fn simulate_event(
&self,
support_point: &[f64],
event: &Event,
next_event: Option<&Event>,
error_models: Option<&AssayErrorModels>,
covariates: &Covariates,
x: &mut Self::S,
infusions: &mut Vec<Infusion>,
likelihood: &mut Vec<f64>,
output: &mut Self::P,
) -> Result<(), PharmsolError> {
match event {
Event::Bolus(bolus) => {
if bolus.input() >= self.get_ndrugs() {
return Err(PharmsolError::InputOutOfRange {
input: bolus.input(),
ndrugs: self.get_ndrugs(),
});
}
x.add_bolus(bolus.input(), bolus.amount());
}
Event::Infusion(infusion) => {
infusions.push(infusion.clone());
}
Event::Observation(observation) => {
self.process_observation(
support_point,
observation,
error_models,
event.time(),
covariates,
x,
likelihood,
output,
)?;
}
}
if let Some(next_event) = next_event {
self.solve(
x,
support_point,
covariates,
infusions,
event.time(),
next_event.time(),
)?;
}
Ok(())
}
}
#[allow(private_bounds)]
pub trait Equation: EquationPriv + 'static + Clone + Sync {
#[deprecated(
since = "0.23.0",
note = "Use estimate_log_likelihood() instead for better numerical stability"
)]
fn estimate_likelihood(
&self,
subject: &Subject,
support_point: &[f64],
error_models: &AssayErrorModels,
) -> Result<f64, PharmsolError>;
fn estimate_log_likelihood(
&self,
subject: &Subject,
support_point: &[f64],
error_models: &AssayErrorModels,
) -> Result<f64, PharmsolError>;
fn kind() -> EqnKind;
fn estimate_predictions(
&self,
subject: &Subject,
support_point: &[f64],
) -> Result<Self::P, PharmsolError> {
Ok(self.simulate_subject(subject, support_point, None)?.0)
}
fn nouteqs(&self) -> usize {
self.get_nouteqs()
}
fn nstates(&self) -> usize {
self.get_nstates()
}
fn simulate_subject(
&self,
subject: &Subject,
support_point: &[f64],
error_models: Option<&AssayErrorModels>,
) -> Result<(Self::P, Option<f64>), PharmsolError> {
let mut output = Self::P::new(self.nparticles());
let mut likelihood = Vec::new();
for occasion in subject.occasions() {
let covariates = occasion.covariates();
let mut x = self.initial_state(support_point, covariates, occasion.index());
let mut infusions = Vec::new();
let events = occasion.process_events(
Some((self.fa(), self.lag(), support_point, covariates)),
true,
);
for (index, event) in events.iter().enumerate() {
self.simulate_event(
support_point,
event,
events.get(index + 1),
error_models,
covariates,
&mut x,
&mut infusions,
&mut likelihood,
&mut output,
)?;
}
}
let ll = error_models.map(|_| likelihood.iter().product::<f64>());
Ok((output, ll))
}
}
#[repr(C)]
#[derive(Clone, Debug)]
pub enum EqnKind {
ODE = 0,
Analytical = 1,
SDE = 2,
}
impl EqnKind {
pub fn to_str(&self) -> &'static str {
match self {
Self::ODE => "EqnKind::ODE",
Self::Analytical => "EqnKind::Analytical",
Self::SDE => "EqnKind::SDE",
}
}
}
#[inline(always)]
fn spphash(spp: &[f64]) -> u64 {
use std::hash::{Hash, Hasher};
let mut hasher = ahash::AHasher::default();
for &value in spp {
let bits = if value == 0.0 { 0u64 } else { value.to_bits() };
bits.hash(&mut hasher);
}
hasher.finish()
}