1use cellular_raza_concepts::{CalcError, Xapy};
2use num::FromPrimitive;
3
4#[cfg(feature = "tracing")]
5use tracing::instrument;
6
7use super::{UpdateReactions, UpdateReactionsContact};
8
9#[cfg_attr(feature = "tracing", instrument(skip_all))]
23pub fn mechanics_euler<C, A, Pos, Vel, For, Float>(
24 cell: &mut C,
25 aux_storage: &mut A,
26 dt: Float,
27 rng: &mut rand_chacha::ChaCha8Rng,
28) -> Result<(), super::SimulationError>
29where
30 A: super::aux_storage::UpdateMechanics<Pos, Vel, For, 0>,
31 C: cellular_raza_concepts::Mechanics<Pos, Vel, For, Float>,
32 C: cellular_raza_concepts::Position<Pos>,
33 C: cellular_raza_concepts::Velocity<Vel>,
34 Pos: Xapy<Float> + Clone,
35 Vel: Xapy<Float> + Clone,
36 Float: num::Float + Copy,
37{
38 let force = aux_storage.get_current_force_and_reset();
39 let velocity = cell.velocity();
40 let position = cell.pos();
41
42 let (dx, dv) = cell.calculate_increment(force)?;
43 let (dx_rand, dv_rand) = cell.get_random_contribution(rng, dt)?;
44
45 aux_storage.set_last_position(dx.clone());
47 aux_storage.set_last_velocity(dv.clone());
48
49 let new_position = euler(position, dx, dt, dx_rand)?;
51 let new_velocity = euler(velocity, dv, dt, dv_rand)?;
52 cell.set_pos(&new_position);
53 cell.set_velocity(&new_velocity);
54 Ok(())
55}
56
57pub(crate) struct MechanicsAdamsBashforthSolver<const N: usize>;
61
62pub(crate) trait AdamsBashforth<const N: usize> {
63 #[allow(unused)]
64 fn update<C, A, Pos, Vel, For, Float>(
65 cell: &mut C,
66 aux_storage: &mut A,
67 dt: Float,
68 rng: &mut rand_chacha::ChaCha8Rng,
69 ) -> Result<(), super::SimulationError>
70 where
71 A: super::aux_storage::UpdateMechanics<Pos, Vel, For, N>,
72 C: cellular_raza_concepts::Mechanics<Pos, Vel, For, Float>,
73 C: cellular_raza_concepts::Position<Pos>,
74 C: cellular_raza_concepts::Velocity<Vel>,
75 Pos: Xapy<Float> + Clone,
76 Vel: Xapy<Float> + Clone,
77 Float: num::Float + FromPrimitive;
78}
79
80impl AdamsBashforth<2> for MechanicsAdamsBashforthSolver<2> {
81 #[allow(unused)]
82 fn update<C, A, Pos, Vel, For, Float>(
83 cell: &mut C,
84 aux_storage: &mut A,
85 dt: Float,
86 rng: &mut rand_chacha::ChaCha8Rng,
87 ) -> Result<(), super::SimulationError>
88 where
89 A: super::aux_storage::UpdateMechanics<Pos, Vel, For, 2>,
90 C: cellular_raza_concepts::Mechanics<Pos, Vel, For, Float>,
91 C: cellular_raza_concepts::Position<Pos>,
92 C: cellular_raza_concepts::Velocity<Vel>,
93 Pos: Xapy<Float> + Clone,
94 Vel: Xapy<Float> + Clone,
95 Float: num::Float + FromPrimitive,
96 {
97 mechanics_adams_bashforth_3(cell, aux_storage, dt, rng)
98 }
99}
100
101impl AdamsBashforth<1> for MechanicsAdamsBashforthSolver<1> {
102 fn update<C, A, Pos, Vel, For, Float>(
103 cell: &mut C,
104 aux_storage: &mut A,
105 dt: Float,
106 rng: &mut rand_chacha::ChaCha8Rng,
107 ) -> Result<(), super::SimulationError>
108 where
109 A: super::aux_storage::UpdateMechanics<Pos, Vel, For, 1>,
110 C: cellular_raza_concepts::Mechanics<Pos, Vel, For, Float>,
111 C: cellular_raza_concepts::Position<Pos>,
112 C: cellular_raza_concepts::Velocity<Vel>,
113 Pos: Xapy<Float> + Clone,
114 Vel: Xapy<Float> + Clone,
115 Float: num::Float + FromPrimitive,
116 {
117 mechanics_adams_bashforth_2(cell, aux_storage, dt, rng)
118 }
119}
120
121impl AdamsBashforth<0> for MechanicsAdamsBashforthSolver<0> {
122 fn update<C, A, Pos, Vel, For, Float>(
123 cell: &mut C,
124 aux_storage: &mut A,
125 dt: Float,
126 rng: &mut rand_chacha::ChaCha8Rng,
127 ) -> Result<(), super::SimulationError>
128 where
129 A: super::aux_storage::UpdateMechanics<Pos, Vel, For, 0>,
130 C: cellular_raza_concepts::Mechanics<Pos, Vel, For, Float>,
131 C: cellular_raza_concepts::Position<Pos>,
132 C: cellular_raza_concepts::Velocity<Vel>,
133 Pos: Xapy<Float> + Clone,
134 Vel: Xapy<Float> + Clone,
135 Float: num::Float + FromPrimitive,
136 {
137 mechanics_euler(cell, aux_storage, dt, rng)
138 }
139}
140
141#[cfg_attr(feature = "tracing", instrument(skip_all))]
156pub fn mechanics_adams_bashforth_3<C, A, Pos, Vel, For, Float>(
157 cell: &mut C,
158 aux_storage: &mut A,
159 dt: Float,
160 rng: &mut rand_chacha::ChaCha8Rng,
161) -> Result<(), super::SimulationError>
162where
163 A: super::aux_storage::UpdateMechanics<Pos, Vel, For, 2>,
164 C: cellular_raza_concepts::Mechanics<Pos, Vel, For, Float>,
165 C: cellular_raza_concepts::Position<Pos>,
166 C: cellular_raza_concepts::Velocity<Vel>,
167 Pos: Xapy<Float> + Clone,
168 Vel: Xapy<Float> + Clone,
169 Float: num::Float + FromPrimitive,
170{
171 let force = aux_storage.get_current_force_and_reset();
172 let velocity = cell.velocity();
173 let position = cell.pos();
174
175 let (dx, dv) = cell.calculate_increment(force)?;
176 let (dx_rand, dv_rand) = cell.get_random_contribution(rng, dt)?;
177
178 aux_storage.set_last_position(dx.clone());
180 aux_storage.set_last_velocity(dv.clone());
181
182 let n_previous_values = aux_storage.n_previous_values();
184 let mut old_pos_increments = aux_storage.previous_positions();
185 let mut old_vel_increments = aux_storage.previous_velocities();
186 let (new_position, new_velocity) = match n_previous_values {
187 2 => (
188 adams_bashforth_3(
189 position,
190 [
191 dx,
192 old_pos_increments.next().unwrap().clone(),
193 old_pos_increments.next().unwrap().clone(),
194 ],
195 dt,
196 dx_rand,
197 )?,
198 adams_bashforth_3(
199 velocity,
200 [
201 dv,
202 old_vel_increments.next().unwrap().clone(),
203 old_vel_increments.next().unwrap().clone(),
204 ],
205 dt,
206 dv_rand,
207 )?,
208 ),
209 1 => (
210 adams_bashforth_2(
211 position,
212 [dx, old_pos_increments.next().unwrap().clone()],
213 dt,
214 dx_rand,
215 )?,
216 adams_bashforth_2(
217 velocity,
218 [dv, old_vel_increments.next().unwrap().clone()],
219 dt,
220 dv_rand,
221 )?,
222 ),
223 _ => (
224 euler(position, dx, dt, dx_rand)?,
225 euler(velocity, dv, dt, dv_rand)?,
226 ),
227 };
228 cell.set_pos(&new_position);
229 cell.set_velocity(&new_velocity);
230 Ok(())
231}
232
233pub fn mechanics_adams_bashforth_2<C, A, Pos, Vel, For, Float>(
248 cell: &mut C,
249 aux_storage: &mut A,
250 dt: Float,
251 rng: &mut rand_chacha::ChaCha8Rng,
252) -> Result<(), super::SimulationError>
253where
254 A: super::aux_storage::UpdateMechanics<Pos, Vel, For, 1>,
255 C: cellular_raza_concepts::Mechanics<Pos, Vel, For, Float>,
256 C: cellular_raza_concepts::Position<Pos>,
257 C: cellular_raza_concepts::Velocity<Vel>,
258 Pos: Xapy<Float> + Clone,
259 Vel: Xapy<Float> + Clone,
260 Float: num::Float + FromPrimitive,
261{
262 let force = aux_storage.get_current_force_and_reset();
263 let velocity = cell.velocity();
264 let position = cell.pos();
265
266 let (dx, dv) = cell.calculate_increment(force)?;
267 let (dx_rand, dv_rand) = cell.get_random_contribution(rng, dt)?;
268
269 aux_storage.set_last_position(dx.clone());
271 aux_storage.set_last_velocity(dv.clone());
272
273 let n_previous_values = aux_storage.n_previous_values();
275 let mut old_pos_increments = aux_storage.previous_positions();
276 let mut old_vel_increments = aux_storage.previous_velocities();
277 let (new_position, new_velocity) = match n_previous_values {
278 1 => (
279 adams_bashforth_2(
280 position,
281 [dx, old_pos_increments.next().unwrap().clone()],
282 dt,
283 dx_rand,
284 )?,
285 adams_bashforth_2(
286 velocity,
287 [dv, old_vel_increments.next().unwrap().clone()],
288 dt,
289 dv_rand,
290 )?,
291 ),
292 _ => (
293 euler(position, dx, dt, dx_rand)?,
294 euler(velocity, dv, dt, dv_rand)?,
295 ),
296 };
297 cell.set_pos(&new_position);
298 cell.set_velocity(&new_velocity);
299 Ok(())
300}
301
302#[inline]
303fn euler<X, F>(x: X, dx: X, dt: F, dx_rand: X) -> Result<X, CalcError>
304where
305 X: Xapy<F>,
306 F: num::Float + Copy,
307{
308 let x_new = dx.xapy(dt, &x).xapy(F::one(), &dx_rand.xa(dt));
309 Ok(x_new)
310}
311
312#[inline]
313fn adams_bashforth_3<X, F>(x: X, dx: [X; 3], dt: F, dx_rand: X) -> Result<X, CalcError>
314where
315 X: Xapy<F>,
316 F: Copy + FromPrimitive + num::Float,
317{
318 let f0 = F::from_isize(23).unwrap() / F::from_isize(12).unwrap();
319 let f1 = -F::from_isize(16).unwrap() / F::from_isize(12).unwrap();
320 let f2 = F::from_isize(5).unwrap() / F::from_isize(12).unwrap();
321
322 let [dx0, dx1, dx2] = dx;
323
324 let x_new = dx0
325 .xapy(f0, &dx1.xapy(f1, &dx2.xa(f2)))
326 .xapy(dt, &x)
327 .xapy(F::one(), &dx_rand.xa(dt));
328 Ok(x_new)
329}
330
331#[inline]
332fn adams_bashforth_2<X, F>(x: X, dx: [X; 2], dt: F, dx_rand: X) -> Result<X, CalcError>
333where
334 X: Xapy<F>,
335 F: Copy + FromPrimitive + num::Float,
336{
337 let f0 = F::from_isize(3).unwrap() / F::from_isize(2).unwrap();
338 let f1 = -F::from_isize(1).unwrap() / F::from_isize(2).unwrap();
339
340 let [dx0, dx1] = dx;
341
342 let x_new = dx0
343 .xapy(f0, &dx1.xa(f1))
344 .xapy(dt, &x)
345 .xapy(F::one(), &dx_rand.xa(dt));
346 Ok(x_new)
347}
348
349pub(crate) struct ReactionsRungeKuttaSolver<const N: usize>;
353
354pub(crate) trait RungeKutta<const N: usize> {
355 #[allow(unused)]
356 fn update<C, A, Ri, Float>(
357 cell: &mut C,
358 aux_storage: &mut A,
359 dt: Float,
360 ) -> Result<(), super::SimulationError>
361 where
362 A: UpdateReactions<Ri>,
363 C: cellular_raza_concepts::Reactions<Ri>,
364 Float: num::Float,
365 Ri: Xapy<Float>;
366}
367
368impl RungeKutta<1> for ReactionsRungeKuttaSolver<1> {
369 #[allow(unused)]
370 #[inline]
371 #[cfg_attr(feature = "tracing", instrument(skip_all))]
372 fn update<C, A, Ri, Float>(
373 cell: &mut C,
374 aux_storage: &mut A,
375 dt: Float,
376 ) -> Result<(), super::SimulationError>
377 where
378 A: UpdateReactions<Ri>,
379 C: cellular_raza_concepts::Reactions<Ri>,
380 Float: num::Float,
381 Ri: Xapy<Float>,
382 {
383 let intra = cell.get_intracellular();
385
386 let dintra = cell.calculate_intracellular_increment(&intra)?;
388
389 aux_storage.incr_conc(dintra);
391 Ok(())
392 }
393}
394
395impl RungeKutta<2> for ReactionsRungeKuttaSolver<2> {
396 #[allow(unused)]
397 #[inline]
398 #[cfg_attr(feature = "tracing", instrument(skip_all))]
399 fn update<C, A, Ri, Float>(
400 cell: &mut C,
401 aux_storage: &mut A,
402 dt: Float,
403 ) -> Result<(), super::SimulationError>
404 where
405 A: UpdateReactions<Ri>,
406 C: cellular_raza_concepts::Reactions<Ri>,
407 Float: num::Float,
408 Ri: Xapy<Float>,
409 {
410 let two = Float::one() + Float::one();
412 let intra = cell.get_intracellular();
413
414 let dintra1 = cell.calculate_intracellular_increment(&intra)?;
416 let dintra = cell.calculate_intracellular_increment(&dintra1.xapy(dt / two, &intra))?;
417
418 aux_storage.incr_conc(dintra);
420 Ok(())
421 }
422}
423
424impl RungeKutta<4> for ReactionsRungeKuttaSolver<4> {
425 #[allow(unused)]
426 #[inline]
427 #[cfg_attr(feature = "tracing", instrument(skip_all))]
428 fn update<C, A, Ri, Float>(
429 cell: &mut C,
430 aux_storage: &mut A,
431 dt: Float,
432 ) -> Result<(), super::SimulationError>
433 where
434 A: UpdateReactions<Ri>,
435 C: cellular_raza_concepts::Reactions<Ri>,
436 Float: num::Float,
437 Ri: Xapy<Float>,
438 {
439 let two = Float::one() + Float::one();
441 let six = two + two + two;
442
443 let intra = cell.get_intracellular();
444
445 let dintra1 = cell.calculate_intracellular_increment(&intra)?;
447 let dintra2 = cell.calculate_intracellular_increment(&dintra1.xapy(dt / two, &intra))?;
448 let dintra3 = cell.calculate_intracellular_increment(&dintra2.xapy(dt / two, &intra))?;
449 let dintra4 = cell.calculate_intracellular_increment(&dintra3.xapy(dt, &intra))?;
450 let dintra = dintra1.xapy(
451 Float::one() / six,
452 &dintra2.xapy(
453 two / six,
454 &dintra3.xapy(two / six, &dintra4.xa(Float::one() / six)),
455 ),
456 );
457
458 aux_storage.incr_conc(dintra);
460 Ok(())
461 }
462}
463
464#[inline]
467#[cfg_attr(feature = "tracing", instrument(skip_all))]
468pub fn reactions_contact_adams_bashforth_3rd<C, A, F, Ri>(
469 _cell: &mut C,
470 aux_storage: &mut A,
471) -> Result<(), CalcError>
472where
473 A: UpdateReactions<Ri>,
474 A: UpdateReactionsContact<Ri, 2>,
475 Ri: Xapy<F> + Clone,
476 F: FromPrimitive + num::Float,
477{
478 let dintra = aux_storage.get_conc();
480 let conc_zero = dintra.xa(F::zero());
481 let dintra_contact = <A as UpdateReactionsContact<Ri, 2>>::get_current_increment(&aux_storage);
482 aux_storage.set_last_increment(dintra_contact.clone());
483 let n_previous_values = aux_storage.n_previous_values();
484 let mut old_intracellular_increments = aux_storage.previous_increments();
485 let dintra_new = match n_previous_values {
486 2 => adams_bashforth_3(
487 dintra,
488 [
489 dintra_contact,
490 old_intracellular_increments.next().unwrap().clone(),
491 old_intracellular_increments.next().unwrap().clone(),
492 ],
493 F::one(),
494 conc_zero,
495 )?,
496 1 => adams_bashforth_2(
497 dintra,
498 [
499 dintra_contact,
500 old_intracellular_increments.next().unwrap().clone(),
501 ],
502 F::one(),
503 conc_zero,
504 )?,
505 _ => euler(dintra, dintra_contact, F::one(), conc_zero)?,
506 };
507 aux_storage.set_conc(dintra_new);
508 Ok(())
509}
510
511#[cfg(test)]
512mod test_solvers_reactions {
513 use rand::SeedableRng;
514
515 use crate::backend::chili::{UpdateReactions, local_reactions_use_increment};
516
517 use super::*;
518
519 #[test]
520 fn exponential_decay_rk4() -> Result<(), super::super::SimulationError> {
521 use cellular_raza_concepts::*;
522 struct PlainCell {
523 intracellular: f64,
524 lambda: f64,
525 }
526 impl Intracellular<f64> for PlainCell {
527 fn get_intracellular(&self) -> f64 {
528 self.intracellular
529 }
530 fn set_intracellular(&mut self, intracellular: f64) {
531 self.intracellular = intracellular;
532 }
533 }
534 impl Reactions<f64> for PlainCell {
535 fn calculate_intracellular_increment(
536 &self,
537 intracellular: &f64,
538 ) -> Result<f64, CalcError> {
539 Ok(-self.lambda * intracellular)
540 }
541 }
542 struct AuxStorage {
543 increment: f64,
544 }
545 impl UpdateReactions<f64> for AuxStorage {
546 fn get_conc(&self) -> f64 {
547 self.increment
548 }
549 fn incr_conc(&mut self, incr: f64) {
550 self.increment += incr;
551 }
552 fn set_conc(&mut self, conc: f64) {
553 self.increment = conc;
554 }
555 }
556 let y0 = 33.0;
557 let lambda = 0.2;
558 let dt = 0.1;
559 let exact_solution = |t: f64| -> f64 { y0 * (-lambda * t).exp() };
560
561 let mut cell = PlainCell {
562 intracellular: y0,
563 lambda,
564 };
565 let mut aux_storage = AuxStorage { increment: 0.0 };
566 let mut results_cr = vec![(0.0, cell.get_intracellular())];
567 let mut t = 0.0;
568 for _ in 0..100 {
569 ReactionsRungeKuttaSolver::<4>::update(&mut cell, &mut aux_storage, dt)?;
570 let mut _rng = rand_chacha::ChaCha8Rng::seed_from_u64(0);
572 local_reactions_use_increment(&mut cell, &mut aux_storage, dt, &mut _rng)?;
573 t += dt;
574 results_cr.push((t, cell.get_intracellular()));
575 }
576 for (t, res) in results_cr {
577 let res_exact = exact_solution(t);
578 assert!((res - res_exact).abs() < 1e-6);
579 }
580 Ok(())
581 }
582}
583
584#[cfg(test)]
585mod test_solvers {
586 use super::*;
587
588 #[test]
589 fn euler_exp_decay() {
590 let y0 = 27.0;
591 let lambda = 0.3;
592 let dt = 0.001;
593 let rhs = |y: f64| -> f64 { -lambda * y };
594 let exact_solution = |t: f64| -> f64 { y0 * (-lambda * t).exp() };
595
596 let lipschitz_constant = lambda;
600 let second_derivative_bound = y0 * lambda.powi(2);
602
603 let expected_global_truncation_error = |t: f64| -> f64 {
604 dt * second_derivative_bound / (2.0 * lipschitz_constant)
605 * ((lipschitz_constant * t).exp() - 1.0)
606 };
607
608 let mut y = y0;
609 let mut t = 0.0;
610 for _ in 0..100 {
611 let dy = rhs(y);
612 y = euler(y, dy, dt, 0.0).unwrap();
613 t += dt;
614 let e = expected_global_truncation_error(t);
615 assert!((y - exact_solution(t)).abs() < e);
616 }
617 }
618
619 #[test]
620 fn adams_bashforth_2_harmonic_oscillator() {
621 #[derive(Clone, Copy, Debug)]
622 struct Vec2(f32, f32);
623 impl<'a> core::ops::Add<&'a Vec2> for Vec2 {
624 type Output = Vec2;
625 fn add(self, rhs: &'a Vec2) -> Self::Output {
626 Vec2(self.0 + rhs.0, self.1 + rhs.1)
627 }
628 }
629 impl core::ops::Add<Vec2> for Vec2 {
630 type Output = Self;
631 fn add(self, rhs: Vec2) -> Self::Output {
632 Vec2(self.0 + rhs.0, self.1 + rhs.1)
633 }
634 }
635 impl<'a> core::ops::Mul<f32> for &'a Vec2 {
636 type Output = Vec2;
637 fn mul(self, rhs: f32) -> Self::Output {
638 Vec2(self.0 * rhs, self.1 * rhs)
639 }
640 }
641 impl core::ops::Neg for Vec2 {
642 type Output = Vec2;
643 fn neg(self) -> Self::Output {
644 Self(-self.0, -self.1)
645 }
646 }
647 impl num::Zero for Vec2 {
648 fn is_zero(&self) -> bool {
649 self.0.is_zero() && self.1.is_zero()
650 }
651 fn set_zero(&mut self) {
652 self.0 = 0.0;
653 self.1 = 0.0;
654 }
655 fn zero() -> Self {
656 Vec2(0., 0.)
657 }
658 }
659
660 let y0 = Vec2(2.8347, 0.0);
662 let omega: f32 = 0.319;
663 let dt: f32 = 0.045;
664
665 let rhs = |y: Vec2| -> Vec2 { Vec2(y.1, -omega.powi(2) * y.0) };
667 let exact_solution =
668 |t: f32| -> Vec2 { Vec2(y0.0 * (omega * t).cos(), -y0.0 * omega * (omega * t).sin()) };
669 let third_derivative_bound = Vec2(y0.0 * omega.powi(3), y0.0 * omega.powi(3));
673 let lipschitz_constant = Vec2(1.0, omega);
674 let local_truncation_error = &third_derivative_bound * (5f32 / 12.0 * dt.powi(2));
675 let global_truncation_error = |t: f32| -> Vec2 {
678 Vec2(
679 ((lipschitz_constant.0 * t).exp() - 1.0) * local_truncation_error.0
680 / dt
681 / lipschitz_constant.0,
682 ((lipschitz_constant.1 * t).exp() - 1.0) * local_truncation_error.1
683 / dt
684 / lipschitz_constant.1,
685 )
686 };
687
688 let mut t = 0.0;
690 let mut y = y0.clone();
691 let mut dy_storage = [
692 rhs(exact_solution(t - dt)),
693 rhs(exact_solution(t - 2.0 * dt)),
694 ];
695
696 for i in 0..100 {
697 let dy = rhs(y);
698 dy_storage[1] = dy_storage[0];
699 dy_storage[0] = dy;
700 y = adams_bashforth_2(y, dy_storage, dt, Vec2(0.0, 0.0)).unwrap();
701 t += dt;
702 let e = global_truncation_error(t);
703 let d1 = (y.0 - exact_solution(t).0).abs();
704 let d2 = (y.1 - exact_solution(t).1).abs();
705 if i > 0 {
706 assert!(d1 < e.0);
707 assert!(d2 < e.1);
708 }
709 }
710 }
711}