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