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}*/