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