cellular_raza_concepts/
reactions.rs

1use crate::CalcError;
2use crate::Position;
3
4/// Setter and Getter for intracellular values of a cellagent.
5pub trait Intracellular<Ri> {
6    /// Sets the current intracellular values.
7    fn set_intracellular(&mut self, intracellular: Ri);
8    /// Obtains the current intracellular values.
9    fn get_intracellular(&self) -> Ri;
10}
11
12/// Describes purely intracellular reactions of a cellagent.
13///
14/// In the most simple case, intracellular values can be assumed to have a homogeneous distribution
15/// throughout the entire cell.
16/// We can then describe them by a list of values $\vec{u}=(u_0,\dots,u_N)$.
17// TODO implement random contributions
18pub trait Reactions<Ri/*, Float = f64*/>: Intracellular<Ri> {
19    /// Calculates the purely intracellular reaction increment.
20    /// Users who implement this trait should always use the given argument instead of relying on
21    /// values obtained via `self`.
22    fn calculate_intracellular_increment(&self, intracellular: &Ri) -> Result<Ri, CalcError>;
23}
24
25/// This trait models extracellular reactions which interact with agents.
26pub trait ReactionsExtra<Ri, Re> {
27    /// TODO add description
28    fn calculate_combined_increment(
29        &self,
30        intracellular: &Ri,
31        extracellular: &Re,
32    ) -> Result<(Ri, Re), CalcError>;
33}
34
35/// Reactions between cells which are in direct contact
36pub trait ReactionsContact<Ri, Pos, Float = f64, RInf = ()> {
37    /// Obtains information about the other cells
38    fn get_contact_information(&self) -> RInf;
39
40    /// Calculates the combined increment
41    fn calculate_contact_increment(
42        &self,
43        own_intracellular: &Ri,
44        ext_intracellular: &Ri,
45        own_pos: &Pos,
46        ext_pos: &Pos,
47        rinf: &RInf,
48    ) -> Result<(Ri, Ri), CalcError>;
49}
50
51/// Mathematical abstraction similar to the well-known `axpy` method.
52///
53/// ```
54/// # use cellular_raza_concepts::Xapy;
55/// let a = 2.0;
56/// let x = 33.0;
57/// let y = 234.0;
58/// assert_eq!(x*a + y, x.xapy(a, &y));
59/// assert_eq!((x*a + y)*a+y, x.xapy(a, &y).xapy(a, &y));
60/// ```
61pub trait Xapy<F> {
62    /// Abstraction over the common `a*x + y` mathematical function.
63    fn xapy(&self, a: F, y: &Self) -> Self;
64
65    /// Abstraction over scalar multiplication `a*x`.
66    fn xa(&self, a: F) -> Self;
67}
68
69impl<F, X> Xapy<F> for X
70where
71    X: for<'a> core::ops::Add<&'a X, Output = X>,
72    for<'a> &'a X: core::ops::Mul<F, Output = X>,
73{
74    fn xapy(&self, a: F, y: &Self) -> Self {
75        self * a + y
76    }
77
78    fn xa(&self, a: F) -> Self {
79        self * a
80    }
81}
82
83#[allow(unused)]
84fn solver_euler_extra<F, C, Ri, E>(
85    dt: F,
86    cell: &mut C,
87    extracellular: &mut E,
88) -> Result<(), Box<dyn std::error::Error>>
89where
90    C: ReactionsExtra<Ri, E>,
91    C: Intracellular<Ri>,
92    F: num::Zero + num::One + Clone,
93    Ri: Xapy<F>,
94    E: Xapy<F>,
95{
96    let intra = cell.get_intracellular();
97    let (dintra, dextra) = cell.calculate_combined_increment(&intra, extracellular)?;
98    cell.set_intracellular(dintra.xapy(dt.clone(), &intra));
99    *extracellular = dextra.xapy(dt, extracellular);
100    Ok(())
101}
102
103#[allow(unused)]
104fn solver_runge_kutta_4th_combined<F, C, Ri, E>(
105    dt: F,
106    cell: &mut C,
107    extracellular: &mut E,
108) -> Result<(), Box<dyn std::error::Error>>
109where
110    C: ReactionsExtra<Ri, E>,
111    C: Intracellular<Ri>,
112    F: num::Float,
113    Ri: Xapy<F> + num::Zero,
114    E: Xapy<F> + num::Zero,
115{
116    let intra = cell.get_intracellular();
117
118    let two = F::one() + F::one();
119    let (dintra1, dextra1) = cell.calculate_combined_increment(&intra, extracellular)?;
120    let (dintra2, dextra2) = cell.calculate_combined_increment(
121        &dintra1.xapy(dt / two, &intra),
122        &dextra1.xapy(dt / two, &extracellular),
123    )?;
124    let (dintra3, dextra3) = cell.calculate_combined_increment(
125        &dintra2.xapy(dt / two, &intra),
126        &dextra2.xapy(dt / two, &extracellular),
127    )?;
128    let (dintra4, dextra4) = cell.calculate_combined_increment(
129        &dintra3.xapy(dt, &intra),
130        &dextra3.xapy(dt, &extracellular),
131    )?;
132    let six = two + two + two;
133    let dintra = dintra1.xapy(
134        F::one() / six,
135        &dintra2.xapy(
136            two / six,
137            &dintra3.xapy(two / six, &dintra4.xapy(F::one() / six, &Ri::zero())),
138        ),
139    );
140    let dextra = dextra1.xapy(
141        F::one() / six,
142        &dextra2.xapy(
143            two / six,
144            &dextra3.xapy(two / six, &dextra4.xapy(F::one() / six, &E::zero())),
145        ),
146    );
147    cell.set_intracellular(dintra.xapy(dt, &intra));
148    *extracellular = dextra.xapy(dt, extracellular);
149    Ok(())
150}
151
152mod test_plain_float {
153    use super::*;
154
155    #[allow(unused)]
156    #[derive(Clone)]
157    struct MyCell {
158        // Intracellular properties
159        intracellular: f64,
160        production: f64,
161        degradation: f64,
162        // For contact reactions
163        pos: [f64; 2],
164        exchange_term: f64,
165        reaction_range: f64,
166        // Extracellular reactions
167        secretion_rate: f64,
168    }
169
170    impl Intracellular<f64> for MyCell {
171        fn set_intracellular(&mut self, intracellular: f64) {
172            self.intracellular = intracellular;
173        }
174
175        fn get_intracellular(&self) -> f64 {
176            self.intracellular
177        }
178    }
179
180    impl Reactions<f64> for MyCell {
181        fn calculate_intracellular_increment(&self, intracellular: &f64) -> Result<f64, CalcError> {
182            Ok(self.production - self.degradation * intracellular)
183        }
184    }
185
186    impl ReactionsExtra<f64, f64> for MyCell {
187        fn calculate_combined_increment(
188            &self,
189            intracellular: &f64,
190            _extracellular: &f64,
191        ) -> Result<(f64, f64), CalcError> {
192            let secretion = self.secretion_rate * intracellular;
193            Ok((-secretion, secretion))
194        }
195    }
196
197    impl Position<[f64; 2]> for MyCell {
198        fn pos(&self) -> [f64; 2] {
199            self.pos
200        }
201
202        fn set_pos(&mut self, pos: &[f64; 2]) {
203            self.pos = *pos;
204        }
205    }
206
207    impl ReactionsContact<f64, [f64; 2]> for MyCell {
208        fn calculate_contact_increment(
209            &self,
210            own_intracellular: &f64,
211            ext_intracellular: &f64,
212            own_pos: &[f64; 2],
213            ext_pos: &[f64; 2],
214            _rinf: &(),
215        ) -> Result<(f64, f64), CalcError> {
216            let dist =
217                ((own_pos[0] - ext_pos[0]).powf(2.0) + (own_pos[1] - ext_pos[1]).powf(2.0)).sqrt();
218            if dist < self.reaction_range {
219                let exchange = self.exchange_term * (ext_intracellular - own_intracellular);
220                Ok((exchange, -exchange))
221            } else {
222                Ok((0.0, 0.0))
223            }
224        }
225
226        fn get_contact_information(&self) -> () {}
227    }
228
229    #[test]
230    fn euler_reactions_contact() -> Result<(), Box<dyn std::error::Error>> {
231        // We engineer these cells such that
232        let mut c1 = MyCell {
233            pos: [0.0; 2],
234            intracellular: 1.0,
235            production: 0.0,
236            degradation: 0.0,
237            exchange_term: 0.1,
238            reaction_range: 3.0,
239            secretion_rate: 0.0,
240        };
241        let mut c2 = c1.clone();
242        c2.intracellular = 0.0;
243        c2.pos = [0.25; 2];
244
245        let dt = 0.02;
246        for _ in 0..10_000 {
247            // Calculate combined increments
248            let p1 = c1.pos.clone();
249            let r1 = c1.intracellular;
250            let p2 = c2.pos.clone();
251            let r2 = c2.intracellular;
252
253            // The first index indicates from where the term originated while the second index
254            // shows for which cell the value needs to be added.
255            // From cell 1 to 2
256            let (dr11, dr12) = c1.calculate_contact_increment(&r1, &r2, &p1, &p2, &())?;
257            // From cell 2 to 1
258            let (dr22, dr21) = c2.calculate_contact_increment(&r2, &r1, &p2, &p1, &())?;
259
260            // Calculate the combined increments
261            let dr1 = dt * (dr11 + dr21) / 2.0;
262            let dr2 = dt * (dr12 + dr22) / 2.0;
263
264            // Update the intracellular values of c1 and c2
265            c1.set_intracellular(r1 + dr1);
266            c2.set_intracellular(r2 + dr2);
267        }
268        // Test that the resulting concentrations are matching in the end.
269        assert!((c1.get_intracellular() - c2.get_intracellular()).abs() < 1e-6);
270        Ok(())
271    }
272
273    #[test]
274    fn test_euler_extra() -> Result<(), Box<dyn std::error::Error>> {
275        let x0 = 1.0;
276        let mut cell = MyCell {
277            pos: [0.0; 2],
278            intracellular: x0,
279            production: 0.0,
280            degradation: 0.0,
281            exchange_term: 0.0,
282            reaction_range: 0.0,
283            secretion_rate: 0.1,
284        };
285        let mut extracellular = 0.0;
286
287        let dt = 0.002;
288        let exact_solution_cell =
289            |t: f64, x0: f64, degradation: f64| -> f64 { x0 * (-degradation * t).exp() };
290
291        for n_step in 0..10_000 {
292            solver_euler_extra(dt, &mut cell, &mut extracellular)?;
293            let x_exact = exact_solution_cell((n_step + 1) as f64 * dt, x0, cell.secretion_rate);
294            assert!((cell.get_intracellular() - x_exact).abs() < 1e-4);
295        }
296        Ok(())
297    }
298
299    #[test]
300    fn runge_kutta_intracellular() -> Result<(), Box<dyn std::error::Error>> {
301        let x0 = 2.0;
302        let mut cell = MyCell {
303            pos: [0.0; 2],
304            intracellular: x0,
305            production: 1.0,
306            degradation: 0.2,
307            exchange_term: 0.0,
308            reaction_range: 0.0,
309            secretion_rate: 0.0,
310        };
311
312        let analytical_solution = |t: f64, x0: f64, production: f64, degradation: f64| -> f64 {
313            production / degradation
314                * (1.0 - (1.0 - x0 * degradation / production) * (-degradation * t).exp())
315        };
316
317        let dt = 1e-0;
318        for n_step in 0..100 {
319            let intra = cell.get_intracellular();
320            // Do the runge-kutta numerical integration steps
321            let k1 = cell.calculate_intracellular_increment(&(intra))?;
322            let k2 = cell.calculate_intracellular_increment(&(intra + dt / 2.0 * k1))?;
323            let k3 = cell.calculate_intracellular_increment(&(intra + dt / 2.0 * k2))?;
324            let k4 = cell.calculate_intracellular_increment(&(intra + dt * k3))?;
325
326            // Calculate the total increment
327            let dintra = dt / 6.0 * (k1 + 2.0 * k2 + 2.0 * k3 + k4);
328
329            // Update the values
330            cell.set_intracellular(intra + dintra);
331            let exact_result = analytical_solution(
332                dt * (n_step + 1) as f64,
333                x0,
334                cell.production,
335                cell.degradation,
336            );
337            assert!((cell.get_intracellular() - exact_result).abs() < 1e-4);
338        }
339        assert!((cell.get_intracellular() - cell.production / cell.degradation).abs() < 1e-6);
340        Ok(())
341    }
342
343    #[test]
344    fn test_generic_solver_runge_kutta_combined() -> Result<(), Box<dyn std::error::Error>> {
345        let x0 = 10.0;
346        let mut cell = MyCell {
347            pos: [0.0; 2],
348            intracellular: x0,
349            production: 0.0,
350            degradation: 0.0,
351            exchange_term: 0.0,
352            reaction_range: 0.0,
353            secretion_rate: 0.15,
354        };
355        let mut extracellular = 1.0;
356
357        let exact_result =
358            |t: f64, x0: f64, degradation: f64| -> f64 { x0 * (-degradation * t).exp() };
359
360        let dt = 0.1;
361        for n_step in 0..1_000 {
362            let t = (n_step + 1) as f64 * dt;
363            solver_runge_kutta_4th_combined(dt, &mut cell, &mut extracellular)?;
364            let exact_value = exact_result(t, x0, cell.secretion_rate);
365            assert!((exact_value - cell.get_intracellular()).abs() < 1e-6);
366        }
367        assert!(cell.get_intracellular().abs() < 1e-5);
368        Ok(())
369    }
370
371    #[test]
372    fn adams_bashforth_3rd_extracellular() -> Result<(), Box<dyn std::error::Error>> {
373        let x0 = 10.0;
374        let mut cell = MyCell {
375            pos: [0.0; 2],
376            intracellular: x0,
377            production: 0.0,
378            degradation: 0.0,
379            exchange_term: 0.0,
380            reaction_range: 0.0,
381            secretion_rate: 0.1,
382        };
383        let mut extracellular = 0.0;
384
385        let mut dcombined1 = None;
386        let mut dcombined2 = None;
387
388        let exact_solution =
389            |t: f64, x0: f64, degradation: f64| -> f64 { x0 * (-degradation * t).exp() };
390
391        let dt = 0.1;
392        for n_step in 0..1_000 {
393            let intra = cell.get_intracellular();
394
395            // Calculate the total increment depening on how many previous values we have
396            let (dintra, dextra) = cell.calculate_combined_increment(&intra, &extracellular)?;
397            match (dcombined1, dcombined2) {
398                (Some((dintra1, dextra1)), Some((dintra2, dextra2))) => {
399                    let h1 = 23.0 / 12.0;
400                    let h2 = -16.0 / 12.0;
401                    let h3 = 5.0 / 12.0;
402                    cell.set_intracellular(
403                        intra + dt * (h1 * dintra + h2 * dintra1 + h3 * dintra2),
404                    );
405                    extracellular += dt * (h1 * dextra + h2 * dextra1 + h3 * dextra2);
406                }
407                (Some((dintra1, dextra1)), None) => {
408                    let h1 = 3.0 / 2.0;
409                    let h2 = -1.0 / 2.0;
410                    cell.set_intracellular(intra + dt * (h1 * dintra + h2 * dintra1));
411                    extracellular += dt * (h1 * dextra + h2 * dextra1);
412                }
413                // This is the euler method
414                _ => {
415                    cell.set_intracellular(intra + dt * dintra);
416                    extracellular += dt * dextra;
417                }
418            }
419            // Reset the increments
420            dcombined2 = dcombined1;
421            dcombined1 = Some((dintra, dextra));
422            assert!((cell.get_intracellular() + extracellular - x0).abs() < 1e-6);
423
424            // Calculate the exact value and commpare
425            let exact_value = exact_solution((n_step + 1) as f64 * dt, x0, cell.secretion_rate);
426            println!("{} {}", cell.get_intracellular(), exact_value);
427            assert!((cell.get_intracellular() - exact_value).abs() < 1e-3);
428        }
429        Ok(())
430    }
431}
432
433/// ```
434/// use cellular_raza_concepts::{CellAgent, Intracellular, Reactions, CalcError};
435/// struct MyReactions;
436/// impl Intracellular<i16> for MyReactions {
437///     fn get_intracellular(&self) -> i16 {
438///         42
439///     }
440///     fn set_intracellular(&mut self, _intracellular: i16) {}
441/// }
442/// impl Reactions<i16> for MyReactions {
443///     fn calculate_intracellular_increment(&self, intracellular: &i16) -> Result<i16,
444///     CalcError> {
445///         Ok(-1)
446///     }
447/// }
448/// #[derive(CellAgent)]
449/// struct MyCell {
450///     #[Reactions]
451///     reactions: MyReactions,
452/// }
453/// let mycell = MyCell {
454///     reactions: MyReactions,
455/// };
456/// assert_eq!(mycell.get_intracellular(), 42);
457/// assert_eq!(mycell.calculate_intracellular_increment(&7).unwrap(), -1);
458/// ```
459#[allow(unused)]
460fn derive_reactions() {}
461
462/// ```
463/// use cellular_raza_concepts::{CellAgent, Intracellular, Reactions, CalcError};
464/// struct MyIntracellular(String);
465/// impl Intracellular<String> for MyIntracellular {
466///     fn get_intracellular(&self) -> String {
467///         format!("{}", self.0)
468///     }
469///     fn set_intracellular(&mut self, intracellular: String) {
470///         self.0 = intracellular;
471///     }
472/// }
473/// #[derive(CellAgent)]
474/// struct MyAgent {
475///     #[Intracellular]
476///     intracellular: MyIntracellular,
477/// }
478/// let mut myagent = MyAgent {
479///     intracellular: MyIntracellular("42".to_owned()),
480/// };
481/// assert_eq!(myagent.get_intracellular(), format!("42"));
482/// myagent.set_intracellular(format!("this wind is nice"));
483/// assert_eq!(myagent.get_intracellular(), "this wind is nice".to_owned());
484/// ```
485#[allow(unused)]
486fn derive_intracellular() {}
487
488/// ```
489/// use cellular_raza_concepts::{CellAgent, Intracellular, Reactions, CalcError};
490/// struct MyReactions;
491/// impl Reactions<i16> for MyReactions {
492///     fn calculate_intracellular_increment(&self, intracellular: &i16) -> Result<i16,
493///     CalcError> {
494///         Ok(-1)
495///     }
496/// }
497/// impl Intracellular<i16> for MyReactions {
498///     fn get_intracellular(&self) -> i16 {42}
499///     fn set_intracellular(&mut self, _intracellular: i16) {}
500/// }
501/// #[derive(CellAgent)]
502/// struct MyCell {
503///     #[ReactionsRaw]
504///     reactions: MyReactions,
505/// }
506/// impl Intracellular<i16> for MyCell {
507///     fn get_intracellular(&self) -> i16 {12}
508///     fn set_intracellular(&mut self, _: i16) {}
509/// }
510/// let mycell = MyCell {
511///     reactions: MyReactions,
512/// };
513/// assert_eq!(mycell.get_intracellular(), 12);
514/// assert_eq!(mycell.calculate_intracellular_increment(&7).unwrap(), -1);
515/// ```
516#[allow(unused)]
517fn derive_reactions_raw() {}
518
519/// ```
520/// use cellular_raza_concepts::{CellAgent, Intracellular, ReactionsContact, CalcError};
521/// struct MyReactions;
522/// impl Intracellular<i16> for MyReactions {
523///     fn get_intracellular(&self) -> i16 {
524///         42
525///     }
526///     fn set_intracellular(&mut self, _intracellular: i16) {}
527/// }
528/// impl ReactionsContact<f32, (f64, f64), f32, (usize, String)> for MyReactions {
529///     fn get_contact_information(&self) -> (usize, String) {
530///         (1, "This is nice".to_owned())
531///     }
532///
533///     fn calculate_contact_increment(
534///         &self,
535///         own_intracellular: &f32,
536///         ext_intracellular: &f32,
537///         own_pos: &(f64, f64),
538///         ext_pos: &(f64, f64),
539///         rinf: &(usize, String),
540///     ) -> Result<(f32, f32), CalcError> {
541///         Ok((1.2, 3.1))
542///     }
543/// }
544/// #[derive(CellAgent)]
545/// struct MyCell {
546///     #[ReactionsContact]
547///     reactions: MyReactions,
548/// }
549/// let mycell = MyCell {
550///     reactions: MyReactions,
551/// };
552/// assert_eq!(mycell.get_contact_information(), (1, "This is nice".to_owned()));
553/// assert_eq!(mycell.calculate_contact_increment(
554///     &0.0,
555///     &0.0,
556///     &(0.0, 0.0),
557///     &(1.0, 1.0),
558///     &(33, "jo".to_owned())
559/// ).unwrap(), (1.2, 3.1));
560/// ```
561#[allow(unused)]
562fn derive_reactions_contact() {}
563
564/// ```
565/// use cellular_raza_concepts::*;
566/// #[derive(Clone, Debug, PartialEq)]
567/// struct MyIntracellular(&'static str);
568/// #[derive(Clone, Debug, PartialEq)]
569/// struct MyExtracellular {
570///     forty_two: (),
571/// }
572/// struct MyReactionsExtra {
573///     intracellular: MyIntracellular,
574/// }
575/// /* impl Intracellular<MyIntracellular> for MyReactionsExtra {
576///     fn get_intracellular(&self) -> MyIntracellular {
577///         self.intracellular.clone()
578///     }
579///
580///     fn set_intracellular(&mut self, intracellular: MyIntracellular) {
581///         self.intracellular = intracellular;
582///     }
583/// }*/
584/// impl ReactionsExtra<MyIntracellular, MyExtracellular> for MyReactionsExtra {
585///     fn calculate_combined_increment(
586///         &self,
587///         intracellular: &MyIntracellular,
588///         extracellular: &MyExtracellular,
589///     ) -> Result<(MyIntracellular, MyExtracellular), CalcError> {
590///         Ok((intracellular.clone(), extracellular.clone()))
591///     }
592/// }
593/// #[derive(CellAgent)]
594/// struct MyCell {
595///     #[ReactionsExtra]
596///     reactions_extra: MyReactionsExtra,
597/// }
598/// let mycell = MyCell {
599///     reactions_extra: MyReactionsExtra {
600///         intracellular: MyIntracellular("nice"),
601///     }
602/// };
603/// let (incr_intra, incr_extra) = mycell.calculate_combined_increment(
604///     &MyIntracellular("not so nice"),
605///     &MyExtracellular {
606///         forty_two: (),
607///     }
608/// ).unwrap();
609/// assert_eq!(incr_intra, MyIntracellular("not so nice"));
610/// assert_eq!(incr_extra, MyExtracellular { forty_two: (), });
611/// ```
612#[allow(unused)]
613fn derive_reactions_extra() {}
614
615/* mod test_particle_sim {
616    use super::*;
617
618    struct Particle([f32; 3]);
619    struct ParticleCollection(Vec<Particle>);
620
621    struct Cell {
622        intracellular: ParticleCollection,
623    }
624}*/