1use cellular_raza_concepts::{CalcError, Mechanics, RngError};
2
3use itertools::Itertools;
4use nalgebra::{SMatrix, SVector};
5
6use serde::{Deserialize, Serialize};
7
8#[cfg(feature = "pyo3")]
9use pyo3::prelude::*;
10
11#[cfg(feature = "approx")]
12use approx::RelativeEq;
13
14macro_rules! implement_newton_damped_mechanics(
15 (
16 $struct_name:ident,
17 $d:literal
18 ) => {
19 implement_newton_damped_mechanics!($struct_name, $d, f64);
20 };
21 (
22 $struct_name:ident,
23 $d:literal,
24 $float_type:ty
25 ) => {
26 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
51 #[cfg_attr(feature = "pyo3", pyclass)]
52 #[cfg_attr(feature = "approx", derive(RelativeEq))]
53 #[cfg_attr(feature = "approx", approx(epsilon_type = $float_type))]
54 pub struct $struct_name {
55 #[cfg_attr(feature = "approx", approx(into_iter))]
57 pub pos: SVector<$float_type, $d>,
58 #[cfg_attr(feature = "approx", approx(into_iter))]
60 pub vel: SVector<$float_type, $d>,
61 pub damping_constant: $float_type,
63 pub mass: $float_type,
65 }
66
67 impl $struct_name {
68 #[doc = "Create a new "]
69 #[doc = stringify!($struct_name)]
70 pub fn new(
72 pos: [$float_type; $d],
73 vel: [$float_type; $d],
74 damping_constant: $float_type,
75 mass: $float_type,
76 ) -> Self {
77 Self {
78 pos: pos.into(),
79 vel: vel.into(),
80 damping_constant,
81 mass,
82 }
83 }
84
85 }
86
87 #[cfg(feature = "pyo3")]
88 #[pymethods]
89 #[cfg_attr(docsrs, doc(cfg(feature = "pyo3")))]
90 impl $struct_name {
91 #[new]
92 fn _new(
93 pos: [$float_type; $d],
94 vel: [$float_type; $d],
95 damping_constant: $float_type,
96 mass: $float_type,
97 ) -> Self {
98 Self::new(pos, vel, damping_constant, mass)
99 }
100
101
102 #[getter]
104 pub fn get_pos(&self) -> [$float_type; $d] {
105 self.pos.into()
106 }
107
108 #[getter]
110 pub fn get_vel(&self) -> [$float_type; $d] {
111 self.vel.into()
112 }
113
114 #[getter]
116 pub fn get_damping_constant(&self) -> $float_type {
117 self.damping_constant
118 }
119
120 #[getter]
122 pub fn get_mass(&self) -> $float_type {
123 self.mass
124 }
125
126 #[setter]
128 pub fn set_pos(&mut self, pos: [$float_type; $d]) {
129 self.pos = pos.into();
130 }
131
132 #[setter]
134 pub fn set_vel(&mut self, vel: [$float_type; $d]) {
135 self.vel = vel.into();
136 }
137
138 #[setter]
140 pub fn set_damping_constant(&mut self, damping_constant: $float_type) {
141 self.damping_constant = damping_constant;
142 }
143
144 #[setter]
146 pub fn set_mass(&mut self, mass: $float_type) {
147 self.mass = mass;
148 }
149 }
150
151 impl Mechanics<
152 SVector<$float_type, $d>,
153 SVector<$float_type, $d>,
154 SVector<$float_type, $d>,
155 $float_type
156 > for $struct_name
157 {
158 fn get_random_contribution(
159 &self,
160 _: &mut rand_chacha::ChaCha8Rng,
161 _dt: $float_type,
162 ) -> Result<(SVector<$float_type, $d>, SVector<$float_type, $d>), RngError> {
163 Ok((num::Zero::zero(), num::Zero::zero()))
164 }
165
166 fn calculate_increment(
167 &self,
168 force: SVector<$float_type, $d>,
169 ) -> Result<(SVector<$float_type, $d>, SVector<$float_type, $d>), CalcError> {
170 let dx = self.vel;
171 let dv = force / self.mass - self.damping_constant * self.vel;
172 Ok((dx, dv))
173 }
174 }
175
176 impl cellular_raza_concepts::Position<SVector<$float_type, $d>> for $struct_name {
177 fn pos(&self) -> SVector<$float_type, $d> {
178 self.pos
179 }
180
181 fn set_pos(&mut self, pos: &SVector<$float_type, $d>) {
182 self.pos = *pos;
183 }
184 }
185
186 impl cellular_raza_concepts::Velocity<SVector<$float_type, $d>> for $struct_name {
187 fn velocity(&self) -> SVector<$float_type, $d> {
188 self.vel
189 }
190
191 fn set_velocity(&mut self, velocity: &SVector<$float_type, $d>) {
192 self.vel = *velocity;
193 }
194 }
195 }
196);
197
198implement_newton_damped_mechanics!(NewtonDamped1D, 1);
199implement_newton_damped_mechanics!(NewtonDamped2D, 2);
200implement_newton_damped_mechanics!(NewtonDamped3D, 3);
201
202implement_newton_damped_mechanics!(NewtonDamped1DF32, 1, f32);
203implement_newton_damped_mechanics!(NewtonDamped2DF32, 2, f32);
204implement_newton_damped_mechanics!(NewtonDamped3DF32, 3, f32);
205
206pub fn wiener_process<F, const D: usize>(
212 rng: &mut rand_chacha::ChaCha8Rng,
213 dt: F,
214) -> Result<SVector<F, D>, RngError>
215where
216 F: core::ops::DivAssign + nalgebra::Scalar + num::Float,
217 rand_distr::StandardNormal: rand_distr::Distribution<F>,
218{
219 let std_dev = dt.sqrt();
220 let distr = match rand_distr::Normal::new(F::zero(), std_dev) {
221 Ok(e) => Ok(e),
222 Err(e) => Err(RngError(format!("{e}"))),
223 }?;
224 let random_dir = SVector::<F, D>::from_distribution(&distr, rng);
225 Ok(random_dir / dt)
226}
227
228macro_rules! implement_brownian_mechanics(
229 ($struct_name:ident, $d:literal, $float_type:ty) => {
230 #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
250 #[cfg_attr(feature = "pyo3", pyclass)]
251 #[cfg_attr(feature = "approx", derive(RelativeEq))]
252 #[cfg_attr(feature = "approx", approx(epsilon_type = $float_type))]
253 pub struct $struct_name {
254 #[cfg_attr(feature = "approx", approx(into_iter))]
256 pub pos: SVector<$float_type, $d>,
257 pub diffusion_constant: $float_type,
259 pub kb_temperature: $float_type,
261 }
262
263 impl $struct_name {
264 #[doc = concat!("[", stringify!($struct_name), "]")]
266 pub fn new(
267 pos: [$float_type; $d],
268 diffusion_constant: $float_type,
269 kb_temperature: $float_type,
270 ) -> Self {
271 Self {
272 pos: pos.into(),
273 diffusion_constant,
274 kb_temperature,
275 }
276 }
277 }
278
279 #[cfg(feature = "pyo3")]
280 #[pymethods]
281 #[cfg_attr(docsrs, doc(cfg(feature = "pyo3")))]
282 impl $struct_name {
283 #[new]
284 fn _new(
285 pos: [$float_type; $d],
286 diffusion_constant: $float_type,
287 kb_temperature: $float_type,
288 ) -> Self {
289 Self::new(pos, diffusion_constant, kb_temperature)
290 }
291
292
293 #[setter]
295 pub fn set_pos(&mut self, pos: [$float_type; $d]) {
296 self.pos = pos.into();
297 }
298
299 #[setter]
301 pub fn set_diffusion_constant(&mut self, diffusion_constant: $float_type) {
302 self.diffusion_constant = diffusion_constant;
303 }
304
305 #[setter]
307 pub fn set_kb_temperature(&mut self, kb_temperature: $float_type) {
308 self.kb_temperature = kb_temperature;
309 }
310
311 #[getter]
313 pub fn get_pos(&self) -> [$float_type; $d] {
314 self.pos.into()
315 }
316
317 #[getter]
319 pub fn get_diffusion_constant(&self) -> $float_type {
320 self.diffusion_constant
321 }
322
323 #[getter]
325 pub fn get_kb_temperature(&self) -> $float_type {
326 self.kb_temperature
327 }
328 }
329
330 impl Mechanics<
331 SVector<$float_type, $d>,
332 SVector<$float_type, $d>,
333 SVector<$float_type, $d>,
334 $float_type
335 > for $struct_name {
336 fn get_random_contribution(
337 &self,
338 rng: &mut rand_chacha::ChaCha8Rng,
339 dt: $float_type,
340 ) -> Result<(SVector<$float_type, $d>, SVector<$float_type, $d>), RngError> {
341 let dpos = (2.0 as $float_type * self.diffusion_constant).sqrt()
342 * wiener_process(
343 rng,
344 dt
345 )?;
346 let dvel = SVector::<$float_type, $d>::zeros();
347 Ok((dpos, dvel))
348 }
349
350 fn calculate_increment(
351 &self,
352 force: SVector<$float_type, $d>,
353 ) -> Result<(SVector<$float_type, $d>, SVector<$float_type, $d>), CalcError> {
354 use num::Zero;
355 let dx = self.diffusion_constant / self.kb_temperature * force;
356 Ok((dx, SVector::<$float_type, $d>::zero()))
357 }
358 }
359
360 impl cellular_raza_concepts::Position<SVector<$float_type, $d>> for $struct_name {
361 fn pos(&self) -> SVector<$float_type, $d> {
362 self.pos
363 }
364
365 fn set_pos(&mut self, pos: &SVector<$float_type, $d>) {
366 self.pos = *pos;
367 }
368
369 }
370
371 impl cellular_raza_concepts::Velocity<SVector<$float_type, $d>> for $struct_name {
372 fn velocity(&self) -> SVector<$float_type, $d> {
373 use num::Zero;
374 SVector::<$float_type, $d>::zero()
375 }
376
377 fn set_velocity(&mut self, _velocity: &SVector<$float_type, $d>) {}
378 }
379 }
380);
381
382implement_brownian_mechanics!(Brownian1D, 1, f64);
383implement_brownian_mechanics!(Brownian2D, 2, f64);
384implement_brownian_mechanics!(Brownian3D, 3, f64);
385implement_brownian_mechanics!(Brownian1DF32, 1, f32);
386implement_brownian_mechanics!(Brownian2DF32, 2, f32);
387implement_brownian_mechanics!(Brownian3DF32, 3, f32);
388
389macro_rules! define_langevin_nd(
390 ($struct_name:ident, $d:literal, $float_type:ident) => {
391 #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
410 #[cfg_attr(feature = "pyo3", pyclass)]
411 #[cfg_attr(feature = "approx", derive(RelativeEq))]
412 #[cfg_attr(feature = "approx", approx(epsilon_type = $float_type))]
413 pub struct $struct_name {
414 #[cfg_attr(feature = "approx", approx(into_iter))]
416 pub pos: SVector<$float_type, $d>,
417 #[cfg_attr(feature = "approx", approx(into_iter))]
419 pub vel: SVector<$float_type, $d>,
420 pub mass: $float_type,
422 pub damping: $float_type,
424 pub kb_temperature: $float_type,
426 }
427
428 impl Mechanics<
429 SVector<$float_type, $d>,
430 SVector<$float_type, $d>,
431 SVector<$float_type, $d>,
432 $float_type
433 > for $struct_name {
434 fn get_random_contribution(
435 &self,
436 rng: &mut rand_chacha::ChaCha8Rng,
437 dt: $float_type,
438 ) -> Result<(SVector<$float_type, $d>, SVector<$float_type, $d>), RngError> {
439 let dvel = (
440 2.0 as $float_type
441 * self.damping
442 * self.kb_temperature
443 / self.mass
444 ).sqrt() * wiener_process(
445 rng,
446 dt
447 )?;
448 let dpos = SVector::<$float_type, $d>::zeros();
449 Ok((dpos, dvel))
450 }
451
452 fn calculate_increment(
453 &self,
454 force: SVector<$float_type, $d>,
455 ) -> Result<(SVector<$float_type, $d>, SVector<$float_type, $d>), CalcError> {
456 let dx = self.vel;
457 let dv1 =
458 1.0 as $float_type / self.mass * force;
459 let dv2 =
460 - self.damping * self.vel;
461 let dv = dv1 + dv2;
462 Ok((dx, dv))
463 }
464 }
465
466 impl cellular_raza_concepts::Position<SVector<$float_type, $d>> for $struct_name {
467 fn pos(&self) -> SVector<$float_type, $d> {
468 self.pos
469 }
470
471 fn set_pos(&mut self, pos: &SVector<$float_type, $d>) {
472 self.pos = *pos;
473 }
474 }
475
476 impl cellular_raza_concepts::Velocity<SVector<$float_type, $d>> for $struct_name {
477 fn velocity(&self) -> SVector<$float_type, $d> {
478 self.vel
479 }
480
481 fn set_velocity(&mut self, velocity: &SVector<$float_type, $d>) {
482 self.vel = *velocity;
483 }
484 }
485
486 #[cfg(feature = "pyo3")]
487 #[pymethods]
488 #[cfg_attr(docsrs, doc(cfg(feature = "pyo3")))]
489 impl $struct_name {
490 #[doc = stringify!($struct_name)]
492 #[new]
495 fn _new(
496 pos: [$float_type; $d],
497 vel: [$float_type; $d],
498 mass: $float_type,
499 damping: $float_type,
500 kb_temperature: $float_type,
501 ) -> Self {
502 Self {
503 pos: pos.into(),
504 vel: vel.into(),
505 mass,
506 damping,
507 kb_temperature,
508 }
509 }
510
511 #[getter(pos)]
512 pub fn get_position(&self) -> [$float_type; $d] {
514 self.pos.into()
515 }
516
517 #[setter(pos)]
518 pub fn set_position(&mut self, pos: [$float_type; $d]) {
520 self.pos = pos.into();
521 }
522
523 #[getter(damping)]
524 pub fn get_damping(&self) -> $float_type {
526 self.damping
527 }
528
529 #[setter(damping)]
530 pub fn set_damping(&mut self, damping: $float_type) {
532 self.damping = damping;
533 }
534
535 #[getter(mass)]
536 pub fn get_mass(&self) -> $float_type {
538 self.mass
539 }
540
541 #[setter(mass)]
542 pub fn set_mass(&mut self, mass: $float_type) {
544 self.mass = mass;
545 }
546
547 #[getter(kb_temperature)]
548 pub fn get_kb_temperature(&self) -> $float_type {
550 self.kb_temperature
551 }
552
553 #[setter(kb_temperature)]
554 pub fn set_kb_temperature(&mut self, kb_temperature: $float_type) {
556 self.kb_temperature = kb_temperature;
557 }
558
559 fn __repr__(&self) -> String {
560 format!("{self:#?}")
561 }
562 }
563 }
564);
565
566define_langevin_nd!(Langevin1D, 1, f64);
567define_langevin_nd!(Langevin2D, 2, f64);
568define_langevin_nd!(Langevin3D, 3, f64);
569define_langevin_nd!(Langevin1DF32, 1, f32);
570define_langevin_nd!(Langevin2DF32, 2, f32);
571define_langevin_nd!(Langevin3DF32, 3, f32);
572
573#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
582pub struct VertexMechanics2D<const D: usize> {
583 points: nalgebra::SMatrix<f64, D, 2>,
584 velocity: nalgebra::SMatrix<f64, D, 2>,
585 pub cell_boundary_lengths: nalgebra::SVector<f64, D>,
587 pub spring_tensions: nalgebra::SVector<f64, D>,
589 pub cell_area: f64,
591 pub central_pressure: f64,
593 pub damping_constant: f64,
595 pub diffusion_constant: f64,
597}
598
599impl<const D: usize> VertexMechanics2D<D> {
600 pub fn new(
623 middle: SVector<f64, 2>,
624 cell_area: f64,
625 rotation_angle: f64,
626 spring_tensions: f64,
627 central_pressure: f64,
628 damping_constant: f64,
629 diffusion_constant: f64,
630 randomize: Option<(f64, rand_chacha::ChaCha8Rng)>,
631 ) -> Self {
632 use rand::Rng;
633 let r = match randomize {
635 Some((rand, _)) => rand.clamp(0.0, 1.0),
636 _ => 0.0,
637 };
638 let rng = || -> f64 {
639 match randomize {
640 Some((_, mut rng)) => rng.random_range(0.0..1.0),
641 None => 0.0,
642 }
643 };
644 let rotation_angle = (1.0 - r * rng.clone()()) * rotation_angle;
646 let angle_fraction = std::f64::consts::PI / D as f64;
648 let radius = (cell_area / D as f64 / angle_fraction.sin() / angle_fraction.cos()).sqrt();
650 let points = nalgebra::SMatrix::<f64, D, 2>::from_row_iterator((0..D).flat_map(|n| {
652 let angle =
653 rotation_angle + 2.0 * angle_fraction * n as f64 * (1.0 - r * rng.clone()());
654 let radius_modified = radius * (1.0 + 0.5 * r * (1.0 - rng.clone()()));
655 [
656 middle.x + radius_modified * angle.cos(),
657 middle.y + radius_modified * angle.sin(),
658 ]
659 .into_iter()
660 }));
661 let cell_boundary_lengths = SVector::<f64, D>::from_iterator(
663 points
664 .row_iter()
665 .circular_tuple_windows()
666 .map(|(p1, p2)| (p1 - p2).norm()),
667 );
668 VertexMechanics2D {
669 points,
670 velocity: nalgebra::SMatrix::<f64, D, 2>::zeros(),
671 cell_boundary_lengths,
672 spring_tensions: SVector::<f64, D>::from_element(spring_tensions),
673 cell_area,
674 central_pressure,
675 damping_constant,
676 diffusion_constant,
677 }
678 }
679
680 pub fn calculate_boundary_length(cell_area: f64) -> f64 {
690 (4.0 * cell_area * (std::f64::consts::PI / D as f64).tan() * D as f64).sqrt()
691 }
692
693 pub fn calculate_cell_area(boundary_length: f64) -> f64 {
697 D as f64 * boundary_length.powf(2.0) / (4.0 * (std::f64::consts::PI / D as f64).tan())
698 }
699
700 pub fn get_current_cell_area(&self) -> f64 {
702 0.5_f64
703 * self
704 .points
705 .row_iter()
706 .circular_tuple_windows()
707 .map(|(p1, p2)| p1.transpose().perp(&p2.transpose()))
708 .sum::<f64>()
709 }
710
711 pub fn calculate_current_boundary_length(&self) -> f64 {
713 self.points
714 .row_iter()
715 .tuple_windows::<(_, _)>()
716 .map(|(p1, p2)| (p2 - p1).norm())
717 .sum::<f64>()
718 }
719
720 pub fn get_cell_area(&self) -> f64 {
722 self.cell_area
723 }
724
725 pub fn set_cell_area_and_boundary_length(&mut self, cell_area: f64) {
728 match self.cell_area {
730 0.0 => {
731 let new_interaction_parameters = Self::new(
732 self.points
733 .row_iter()
734 .map(|v| v.transpose())
735 .sum::<nalgebra::Vector2<f64>>(),
736 cell_area,
737 0.0,
738 self.spring_tensions.sum() / self.spring_tensions.len() as f64,
739 self.central_pressure,
740 self.damping_constant,
741 self.diffusion_constant,
742 None,
743 );
744 *self = new_interaction_parameters;
745 }
746 _ => {
747 let relative_length_difference = (cell_area.abs() / self.cell_area.abs()).sqrt();
748 self.cell_boundary_lengths
750 .iter_mut()
751 .for_each(|length| *length *= relative_length_difference);
752 self.cell_area = cell_area;
753 }
754 };
755 }
756
757 pub fn set_cell_area(&mut self, cell_area: f64) {
759 self.cell_area = cell_area;
760 }
761}
762
763impl VertexMechanics2D<6> {
764 pub fn fill_rectangle_flat_top(
780 cell_area: f64,
781 spring_tensions: f64,
782 central_pressure: f64,
783 damping_constant: f64,
784 diffusion_constant: f64,
785 rectangle: [SVector<f64, 2>; 2],
786 ) -> Vec<Self> {
787 let side_x = rectangle[1].x - rectangle[0].x;
789 let side_y = rectangle[1].y - rectangle[0].y;
790 if cell_area > side_x * side_y {
791 return Vec::new();
792 }
793 let segment_length = Self::calculate_boundary_length(cell_area) / 6.0;
794 let radius_outer = Self::outer_radius_from_cell_area(cell_area);
795 let radius_inner = Self::inner_radius_from_cell_area(cell_area);
796
797 let n_max_x = (side_x - 2.0 * radius_outer).div_euclid(3.0 / 2.0 * radius_outer) as usize;
799 let n_max_y = side_y.div_euclid(2.0 * radius_inner);
800 let total_width_x = 2.0 * radius_outer + (n_max_x - 1) as f64 * 3.0 / 2.0 * radius_outer;
801 let total_width_y = n_max_y * 2.0 * radius_inner;
802
803 let pad_x = (side_x - total_width_x) / 2.0;
804 let pad_y = (side_y - total_width_y) / 2.0;
805 let padding = nalgebra::RowVector2::from([pad_x, pad_y]);
806
807 let mut generated_models = vec![];
808 for n_x in 0..n_max_x {
809 for n_y in 0..n_max_y as usize - n_x % 2 {
810 let middle = rectangle[0].transpose()
811 + padding
812 + nalgebra::RowVector2::from([
813 (1.0 + 3.0 / 2.0 * n_x as f64) * radius_outer,
814 (1 + 2 * n_y + n_x % 2) as f64 * radius_inner,
815 ]);
816 let mut pos = nalgebra::SMatrix::<f64, 6, 2>::zeros();
817 for i in 0..6 {
818 let phi = 2.0 * std::f64::consts::PI * i as f64 / 6.0;
819 pos.set_row(
820 i,
821 &(middle
822 + radius_outer * nalgebra::RowVector2::from([phi.cos(), phi.sin()])),
823 );
824 }
825 generated_models.push(Self {
826 points: pos,
827 velocity: SMatrix::zeros(),
828 cell_boundary_lengths: SVector::from_element(segment_length),
829 spring_tensions: SVector::from_element(spring_tensions),
830 cell_area,
831 central_pressure,
832 damping_constant,
833 diffusion_constant,
834 });
835 }
836 }
837 generated_models
838 }
839}
840
841impl VertexMechanics2D<4> {
842 pub fn fill_rectangle(
844 cell_area: f64,
845 spring_tensions: f64,
846 central_pressure: f64,
847 damping_constant: f64,
848 diffusion_constant: f64,
849 rectangle: [SVector<f64, 2>; 2],
850 ) -> Vec<Self> {
851 let cell_side_length: f64 = cell_area.sqrt();
852 let cell_side_length_padded: f64 = cell_side_length * 1.04;
853
854 let number_of_cells_x: u64 =
855 ((rectangle[1].x - rectangle[0].x) / cell_side_length_padded).floor() as u64;
856 let number_of_cells_y: u64 =
857 ((rectangle[1].y - rectangle[0].y) / cell_side_length_padded).floor() as u64;
858
859 let start_x: f64 = rectangle[0].x
860 + 0.5
861 * ((rectangle[1].x - rectangle[0].x)
862 - number_of_cells_x as f64 * cell_side_length_padded);
863 let start_y: f64 = rectangle[0].y
864 + 0.5
865 * ((rectangle[1].y - rectangle[0].y)
866 - number_of_cells_y as f64 * cell_side_length_padded);
867
868 use itertools::iproduct;
869 iproduct!(0..number_of_cells_x, 0..number_of_cells_y)
870 .map(|(i, j)| {
871 let corner = (
872 start_x + (i as f64) * cell_side_length_padded,
873 start_y + (j as f64) * cell_side_length_padded,
874 );
875
876 let points = nalgebra::SMatrix::<f64, 4, 2>::from_row_iterator([
877 corner.0,
878 corner.1,
879 corner.0 + cell_side_length,
880 corner.1,
881 corner.0 + cell_side_length,
882 corner.1 + cell_side_length,
883 corner.0,
884 corner.1 + cell_side_length,
885 ]);
886 let cell_boundary_lengths = nalgebra::SVector::<f64, 4>::from_iterator(
887 (0..2 * 4).map(|_| cell_side_length),
888 );
889
890 VertexMechanics2D {
891 points,
892 velocity: nalgebra::SMatrix::<f64, 4, 2>::zeros(),
893 cell_boundary_lengths,
894 spring_tensions: nalgebra::SVector::<f64, 4>::from_element(spring_tensions),
895 cell_area,
896 central_pressure,
897 damping_constant,
898 diffusion_constant,
899 }
900 })
901 .collect::<Vec<_>>()
902 }
903}
904
905impl<const D: usize> VertexMechanics2D<D> {
906 pub fn outer_radius_from_cell_area(cell_area: f64) -> f64 {
908 let boundary_length = Self::calculate_boundary_length(cell_area);
911 Self::outer_radius_from_boundary_length(boundary_length)
912 }
913
914 pub fn outer_radius_from_boundary_length(boundary_length: f64) -> f64 {
916 let segment_length = boundary_length / D as f64;
917 segment_length / (std::f64::consts::PI / D as f64).sin() / 2.0
918 }
919
920 pub fn inner_radius_from_cell_area(cell_area: f64) -> f64 {
922 let boundary_length = Self::calculate_boundary_length(cell_area);
923 Self::inner_radius_from_boundary_length(boundary_length)
924 }
925
926 pub fn inner_radius_from_boundary_length(boundary_length: f64) -> f64 {
928 let segment_length = boundary_length / D as f64;
929 segment_length / (std::f64::consts::PI / D as f64).tan() / 2.0
930 }
931}
932
933impl<const D: usize>
934 Mechanics<
935 nalgebra::SMatrix<f64, D, 2>,
936 nalgebra::SMatrix<f64, D, 2>,
937 nalgebra::SMatrix<f64, D, 2>,
938 > for VertexMechanics2D<D>
939{
940 fn calculate_increment(
941 &self,
942 force: nalgebra::SMatrix<f64, D, 2>,
943 ) -> Result<(nalgebra::SMatrix<f64, D, 2>, nalgebra::SMatrix<f64, D, 2>), CalcError> {
944 let middle = self.points.row_sum() / self.points.shape().0 as f64;
946 let current_ara: f64 = 0.5_f64
947 * self
948 .points
949 .row_iter()
950 .circular_tuple_windows()
951 .map(|(p1, p2)| p1.transpose().perp(&p2.transpose()))
952 .sum::<f64>();
953
954 let mut internal_force = self.points * 0.0;
955 for (index, (point_1, point_2, point_3)) in self
956 .points
957 .row_iter()
958 .circular_tuple_windows::<(_, _, _)>()
959 .enumerate()
960 {
961 let tension_12 = self.spring_tensions[index];
962 let tension_23 = self.spring_tensions[(index + 1) % self.spring_tensions.len()];
963 let mut force_2 = internal_force.row_mut((index + 1) % self.points.shape().0);
964
965 let p_21 = point_2 - point_1;
967 let p_23 = point_2 - point_3;
968 let force1 =
969 p_21.normalize() * (self.cell_boundary_lengths[index] - p_21.norm()) * tension_12;
970 let force2 = p_23.normalize()
971 * (self.cell_boundary_lengths[(index + 1) % self.cell_boundary_lengths.len()]
972 - p_23.norm())
973 * tension_23;
974
975 let middle_to_point_2 = point_2 - middle;
977 let force3 = middle_to_point_2.normalize()
978 * (self.cell_area - current_ara)
979 * self.central_pressure;
980
981 force_2 += force1 + force2 + force3;
983 }
984 let dx = self.velocity;
985 let dv = force + internal_force - self.damping_constant * self.velocity;
986 Ok((dx, dv))
987 }
988
989 fn get_random_contribution(
990 &self,
991 rng: &mut rand_chacha::ChaCha8Rng,
992 dt: f64,
993 ) -> Result<(nalgebra::SMatrix<f64, D, 2>, nalgebra::SMatrix<f64, D, 2>), RngError> {
994 let mut dvel = nalgebra::SMatrix::<f64, D, 2>::zeros();
995 let dpos = nalgebra::SMatrix::<f64, D, 2>::zeros();
996 if dt != 0.0 {
997 let random_vector: SVector<f64, 2> = wiener_process(rng, dt)?;
998 dvel.row_iter_mut().for_each(|mut r| {
999 r *= 0.0;
1000 r += random_vector.transpose();
1001 });
1002 Ok((dpos, self.diffusion_constant * dvel))
1003 } else {
1004 Ok((nalgebra::SMatrix::<f64, D, 2>::zeros(), dvel))
1005 }
1006 }
1007}
1008
1009impl<const D: usize> cellular_raza_concepts::Position<nalgebra::SMatrix<f64, D, 2>>
1010 for VertexMechanics2D<D>
1011{
1012 fn pos(&self) -> nalgebra::SMatrix<f64, D, 2> {
1013 self.points
1014 }
1015
1016 fn set_pos(&mut self, pos: &nalgebra::SMatrix<f64, D, 2>) {
1017 self.points = *pos;
1018 }
1019}
1020
1021impl<const D: usize> cellular_raza_concepts::Velocity<nalgebra::SMatrix<f64, D, 2>>
1022 for VertexMechanics2D<D>
1023{
1024 fn velocity(&self) -> nalgebra::SMatrix<f64, D, 2> {
1025 self.velocity
1026 }
1027
1028 fn set_velocity(&mut self, velocity: &nalgebra::SMatrix<f64, D, 2>) {
1029 self.velocity = *velocity;
1030 }
1031}
1032
1033#[cfg(test)]
1034mod test_vertex_mechanics_6n {
1035 #[test]
1036 fn test_fill_too_small() {
1037 use crate::VertexMechanics2D;
1038 use nalgebra::Vector2;
1039 let models = VertexMechanics2D::<6>::fill_rectangle_flat_top(
1040 200.0,
1041 0.0,
1042 0.0,
1043 0.0,
1044 0.0,
1045 [Vector2::from([1.0, 1.0]), Vector2::from([2.0, 2.0])],
1046 );
1047 assert_eq!(models.len(), 0);
1048 }
1049
1050 #[test]
1051 fn test_fill_multiple() {
1052 use crate::VertexMechanics2D;
1053 use cellular_raza_concepts::Position;
1054 use nalgebra::Vector2;
1055 let models = VertexMechanics2D::<6>::fill_rectangle_flat_top(
1056 36.0,
1057 0.0,
1058 0.0,
1059 0.0,
1060 0.0,
1061 [Vector2::from([0.0; 2]), Vector2::from([100.0; 2])],
1062 );
1063 use itertools::Itertools;
1064 for (m1, m2) in models.into_iter().circular_tuple_windows() {
1065 if m1.pos().row_mean().transpose().x == m2.pos().row_mean().transpose().x {
1066 let max = m1
1067 .pos()
1068 .row_iter()
1069 .map(|row| row.transpose().y)
1070 .max_by(|x0, x1| x0.partial_cmp(x1).unwrap())
1071 .unwrap();
1072 let min = m2
1073 .pos()
1074 .row_iter()
1075 .map(|row| row.transpose().y)
1076 .min_by(|x0, x1| x0.partial_cmp(x1).unwrap())
1077 .unwrap();
1078 assert!((max - min).abs() < 1e-7);
1079 }
1080 }
1081 }
1082}