pharmsol/simulator/equation/analytical/
mod.rs1pub mod one_compartment_models;
2pub mod three_compartment_models;
3pub mod two_compartment_models;
4
5pub use one_compartment_models::*;
6pub use three_compartment_models::*;
7pub use two_compartment_models::*;
8
9use crate::PharmsolError;
10use crate::{
11 data::Covariates, simulator::*, Equation, EquationPriv, EquationTypes, Observation, Subject,
12};
13use cached::proc_macro::cached;
14use cached::UnboundCache;
15
16#[derive(Clone, Debug)]
21pub struct Analytical {
22 eq: AnalyticalEq,
23 seq_eq: SecEq,
24 lag: Lag,
25 fa: Fa,
26 init: Init,
27 out: Out,
28 neqs: Neqs,
29}
30
31impl Analytical {
32 pub fn new(
43 eq: AnalyticalEq,
44 seq_eq: SecEq,
45 lag: Lag,
46 fa: Fa,
47 init: Init,
48 out: Out,
49 neqs: Neqs,
50 ) -> Self {
51 Self {
52 eq,
53 seq_eq,
54 lag,
55 fa,
56 init,
57 out,
58 neqs,
59 }
60 }
61}
62
63impl EquationTypes for Analytical {
64 type S = V;
65 type P = SubjectPredictions;
66}
67
68impl EquationPriv for Analytical {
69 #[inline(always)]
90 fn lag(&self) -> &Lag {
91 &self.lag
92 }
93
94 #[inline(always)]
95 fn fa(&self) -> &Fa {
96 &self.fa
97 }
98
99 #[inline(always)]
100 fn get_nstates(&self) -> usize {
101 self.neqs.0
102 }
103
104 #[inline(always)]
105 fn get_nouteqs(&self) -> usize {
106 self.neqs.1
107 }
108 #[inline(always)]
109 fn solve(
110 &self,
111 x: &mut Self::S,
112 support_point: &Vec<f64>,
113 covariates: &Covariates,
114 infusions: &Vec<Infusion>,
115 ti: f64,
116 tf: f64,
117 ) -> Result<(), PharmsolError> {
118 if ti == tf {
119 return Ok(());
120 }
121
122 let mut ts = Vec::new();
124 ts.push(ti);
125 ts.push(tf);
126 for inf in infusions {
127 let t0 = inf.time();
128 let t1 = t0 + inf.duration();
129 if t0 > ti && t0 < tf {
130 ts.push(t0)
131 }
132 if t1 > ti && t1 < tf {
133 ts.push(t1)
134 }
135 }
136 ts.sort_by(|a, b| a.partial_cmp(b).unwrap());
137 ts.dedup_by(|a, b| (*a - *b).abs() < 1e-12);
138
139 let mut current_t = ts[0];
141 for &next_t in &ts[1..] {
142 let mut sp = V::from_vec(support_point.to_owned());
144 let mut rateiv = V::from_vec(vec![0.0; 3]);
145 for inf in infusions {
146 let s = inf.time();
147 let e = s + inf.duration();
148 if current_t >= s && next_t <= e {
149 rateiv[inf.input()] += inf.amount() / inf.duration();
150 }
151 }
152
153 (self.seq_eq)(&mut sp, next_t, covariates);
155
156 let dt = next_t - current_t;
158 *x = (self.eq)(x, &sp, dt, rateiv, covariates);
159
160 current_t = next_t;
161 }
162
163 Ok(())
164 }
165
166 #[inline(always)]
167 fn process_observation(
168 &self,
169 support_point: &Vec<f64>,
170 observation: &Observation,
171 error_models: Option<&ErrorModels>,
172 _time: f64,
173 covariates: &Covariates,
174 x: &mut Self::S,
175 likelihood: &mut Vec<f64>,
176 output: &mut Self::P,
177 ) -> Result<(), PharmsolError> {
178 let mut y = V::zeros(self.get_nouteqs());
179 let out = &self.out;
180 (out)(
181 x,
182 &V::from_vec(support_point.clone()),
183 observation.time(),
184 covariates,
185 &mut y,
186 );
187 let pred = y[observation.outeq()];
188 let pred = observation.to_prediction(pred, x.as_slice().to_vec());
189 if let Some(error_models) = error_models {
190 likelihood.push(pred.likelihood(error_models)?);
191 }
192 output.add_prediction(pred);
193 Ok(())
194 }
195 #[inline(always)]
196 fn initial_state(&self, spp: &Vec<f64>, covariates: &Covariates, occasion_index: usize) -> V {
197 let init = &self.init;
198 let mut x = V::zeros(self.get_nstates());
199 if occasion_index == 0 {
200 (init)(&V::from_vec(spp.to_vec()), 0.0, covariates, &mut x);
201 }
202 x
203 }
204}
205
206impl Equation for Analytical {
207 fn estimate_likelihood(
208 &self,
209 subject: &Subject,
210 support_point: &Vec<f64>,
211 error_models: &ErrorModels,
212 cache: bool,
213 ) -> Result<f64, PharmsolError> {
214 _estimate_likelihood(self, subject, support_point, error_models, cache)
215 }
216 fn kind() -> crate::EqnKind {
217 crate::EqnKind::Analytical
218 }
219}
220fn spphash(spp: &[f64]) -> u64 {
221 spp.iter().fold(0, |acc, x| acc + x.to_bits())
222}
223
224#[inline(always)]
225#[cached(
226 ty = "UnboundCache<String, SubjectPredictions>",
227 create = "{ UnboundCache::with_capacity(100_000) }",
228 convert = r#"{ format!("{}{}", subject.id(), spphash(support_point)) }"#,
229 result = "true"
230)]
231fn _subject_predictions(
232 ode: &Analytical,
233 subject: &Subject,
234 support_point: &Vec<f64>,
235) -> Result<SubjectPredictions, PharmsolError> {
236 Ok(ode.simulate_subject(subject, support_point, None)?.0)
237}
238
239fn _estimate_likelihood(
240 ode: &Analytical,
241 subject: &Subject,
242 support_point: &Vec<f64>,
243 error_models: &ErrorModels,
244 cache: bool,
245) -> Result<f64, PharmsolError> {
246 let ypred = if cache {
247 _subject_predictions(ode, subject, support_point)
248 } else {
249 _subject_predictions_no_cache(ode, subject, support_point)
250 }?;
251 ypred.likelihood(error_models)
252}