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