1use crate::{CartesianCuboid, CartesianSubDomain};
2use cellular_raza_concepts::*;
3
4#[cfg(feature = "approx")]
5use approx::AbsDiffEq;
6use num::FromPrimitive;
7use serde::{Deserialize, Serialize};
8
9use nalgebra::{Const, Dyn, Matrix, VecStorage};
10
11#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
74#[serde(bound = "
75F: 'static
76 + PartialEq
77 + Clone
78 + core::fmt::Debug
79 + Serialize
80 + for<'a> Deserialize<'a>,
81")]
82pub struct RodMechanics<F, const D: usize> {
83 pub pos: Matrix<
85 F,
86 nalgebra::Dyn,
87 nalgebra::Const<D>,
88 nalgebra::VecStorage<F, nalgebra::Dyn, nalgebra::Const<D>>,
89 >,
90 pub vel: Matrix<
92 F,
93 nalgebra::Dyn,
94 nalgebra::Const<D>,
95 nalgebra::VecStorage<F, nalgebra::Dyn, nalgebra::Const<D>>,
96 >,
97 pub diffusion_constant: F,
101 pub spring_tension: F,
103 pub rigidity: F,
105 pub spring_length: F,
107 pub damping: F,
109}
110
111#[cfg(feature = "approx")]
112impl<F, const D: usize> AbsDiffEq for RodMechanics<F, D>
113where
114 F: AbsDiffEq + nalgebra::Scalar,
115 F::Epsilon: Clone,
116{
117 type Epsilon = F::Epsilon;
118
119 fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
120 let RodMechanics {
121 pos,
122 vel,
123 diffusion_constant,
124 spring_tension,
125 rigidity,
126 spring_length,
127 damping,
128 } = &self;
129 pos.iter()
130 .zip(other.pos.iter())
131 .all(|(x, y)| x.abs_diff_eq(y, epsilon.clone()))
132 && vel
133 .iter()
134 .zip(other.vel.iter())
135 .all(|(x, y)| x.abs_diff_eq(y, epsilon.clone()))
136 && diffusion_constant.abs_diff_eq(&other.diffusion_constant, epsilon.clone())
137 && spring_tension.abs_diff_eq(&other.spring_tension, epsilon.clone())
138 && rigidity.abs_diff_eq(&other.rigidity, epsilon.clone())
139 && spring_length.abs_diff_eq(&other.spring_length, epsilon.clone())
140 && damping.abs_diff_eq(&other.damping, epsilon)
141 }
142
143 fn default_epsilon() -> Self::Epsilon {
144 F::default_epsilon()
145 }
146}
147
148impl<F, const D: usize>
149 Mechanics<
150 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
151 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
152 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
153 F,
154 > for RodMechanics<F, D>
155where
156 F: nalgebra::RealField + Clone + num::Float,
157 rand_distr::StandardNormal: rand_distr::Distribution<F>,
158{
159 fn calculate_increment(
160 &self,
161 force: Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
162 ) -> Result<
163 (
164 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
165 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
166 ),
167 CalcError,
168 > {
169 use core::ops::AddAssign;
170 let one_half = F::one() / (F::one() + F::one());
171 let two = F::one() + F::one();
172
173 let mut total_force = force;
175
176 let n_rows = self.pos.nrows();
178 let dist_internal = self.pos.rows(0, n_rows - 1) - self.pos.rows(1, n_rows - 1);
179 dist_internal.row_iter().enumerate().for_each(|(i, dist)| {
180 if !dist.norm().is_zero() {
181 let dir = dist.normalize();
182 let force_internal =
183 -dir * self.spring_tension * (dist.norm() - self.spring_length);
184 total_force.row_mut(i).add_assign(force_internal * one_half);
185 total_force
186 .row_mut(i + 1)
187 .add_assign(-force_internal * one_half);
188 }
189 });
190
191 use itertools::Itertools;
193 dist_internal
194 .row_iter()
195 .tuple_windows::<(_, _)>()
196 .enumerate()
197 .for_each(|(i, (conn1, conn2))| {
198 let angle = conn1.angle(&conn2);
199 let force_d = conn1 - conn2;
200 let force_dir = if !force_d.norm().is_zero() {
201 force_d.normalize()
202 } else {
203 force_d
204 };
205 let force =
206 force_dir * two * self.rigidity * <F as num::Float>::tan(one_half * angle);
207 total_force.row_mut(i).add_assign(-force * one_half);
208 total_force.row_mut(i + 1).add_assign(force);
209 total_force.row_mut(i + 2).add_assign(-force * one_half);
210 });
211
212 total_force -= &self.vel * self.damping;
214 Ok((self.vel.clone(), total_force))
215 }
216
217 fn get_random_contribution(
218 &self,
219 rng: &mut rand_chacha::ChaCha8Rng,
220 dt: F,
221 ) -> Result<
222 (
223 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
224 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
225 ),
226 RngError,
227 > {
228 let distr = match rand_distr::Normal::new(F::zero(), <F as num::Float>::sqrt(dt)) {
229 Ok(e) => Ok(e),
230 Err(e) => Err(cellular_raza_concepts::RngError(format!("{e}"))),
231 }?;
232 let dpos = nalgebra::Matrix::<F, Dyn, Const<D>, _>::from_distribution(
233 self.pos.nrows(),
234 &distr,
235 rng,
236 ) * <F as num::Float>::powi(F::one() + F::one(), -2)
237 * self.diffusion_constant
238 / dt;
239 let dvel = nalgebra::Matrix::<F, Dyn, Const<D>, _>::zeros(self.pos.nrows());
240
241 Ok((dpos, dvel))
242 }
243}
244
245impl<F: Clone, const D: usize> Position<Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>>
246 for RodMechanics<F, D>
247{
248 fn pos(&self) -> Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>> {
249 self.pos.clone()
250 }
251
252 fn set_pos(&mut self, position: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>) {
253 self.pos = position.clone();
254 }
255}
256
257impl<F: Clone, const D: usize> Velocity<Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>>
258 for RodMechanics<F, D>
259{
260 fn velocity(&self) -> Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>> {
261 self.vel.clone()
262 }
263
264 fn set_velocity(&mut self, velocity: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>) {
265 self.vel = velocity.clone();
266 }
267}
268
269#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
271#[cfg_attr(feature = "approx", derive(AbsDiffEq))]
272pub struct RodInteraction<I>(pub I);
273
274impl<I, Inf> InteractionInformation<Inf> for RodInteraction<I>
275where
276 I: InteractionInformation<Inf>,
277{
278 fn get_interaction_information(&self) -> Inf {
279 self.0.get_interaction_information()
280 }
281}
282
283impl<I, F, Inf, const D: usize>
284 Interaction<
285 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
286 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
287 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
288 Inf,
289 > for RodInteraction<I>
290where
291 I: Interaction<nalgebra::SVector<F, D>, nalgebra::SVector<F, D>, nalgebra::SVector<F, D>, Inf>,
292 F: 'static + nalgebra::RealField + Copy + core::fmt::Debug + num::Zero,
293{
294 fn calculate_force_between(
295 &self,
296 own_pos: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
297 own_vel: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
298 ext_pos: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
299 ext_vel: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
300 ext_inf: &Inf,
301 ) -> Result<
302 (
303 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
304 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
305 ),
306 CalcError,
307 > {
308 use core::ops::AddAssign;
309 use itertools::Itertools;
310 let mut force_own = nalgebra::Matrix::<F, Dyn, Const<D>, _>::zeros(own_vel.nrows());
311 let mut force_ext = nalgebra::Matrix::<F, Dyn, Const<D>, _>::zeros(own_vel.nrows());
312 for (i, p1) in own_pos.row_iter().enumerate() {
313 for (j, (p2_n0, p2_n1)) in ext_pos.row_iter().tuple_windows::<(_, _)>().enumerate() {
314 let (_, nearest_point, rel_length) = crate::nearest_point_from_point_to_line(
316 &p1.transpose(),
317 &(p2_n0.transpose(), p2_n1.transpose()),
318 );
319
320 let (f_own, f_ext) = self.0.calculate_force_between(
321 &p1.transpose(),
322 &own_vel.row(i).transpose(),
323 &nearest_point,
324 &ext_vel.row(j).transpose(),
325 ext_inf,
326 )?;
327
328 force_own.row_mut(i).add_assign(f_own.transpose());
329 force_ext
330 .row_mut(j)
331 .add_assign(f_ext.transpose() * (F::one() - rel_length));
332 force_ext
333 .row_mut((j + 1) % own_pos.nrows())
334 .add_assign(f_ext.transpose() * rel_length);
335 }
336 }
337 Ok((force_own, force_ext))
338 }
339
340 fn is_neighbor(
341 &self,
342 own_pos: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
343 ext_pos: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
344 ext_inf: &Inf,
345 ) -> Result<bool, CalcError> {
346 for p in own_pos.row_iter() {
347 for q in ext_pos.row_iter() {
348 if self
349 .0
350 .is_neighbor(&p.transpose(), &q.transpose(), ext_inf)?
351 {
352 return Ok(true);
353 }
354 }
355 }
356 Ok(false)
357 }
358
359 fn react_to_neighbors(&mut self, neighbors: usize) -> Result<(), CalcError> {
360 self.0.react_to_neighbors(neighbors)
361 }
362}
363
364#[derive(Domain)]
431pub struct CartesianCuboidRods<F, const D: usize> {
432 #[DomainRngSeed]
434 pub domain: CartesianCuboid<F, D>,
435 pub gel_pressure: F,
438 pub surface_friction: F,
440 pub surface_friction_distance: F,
442}
443
444impl<C, F, const D: usize> SortCells<C> for CartesianCuboidRods<F, D>
445where
446 C: Position<Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>>,
447 F: 'static
448 + nalgebra::Field
449 + Clone
450 + core::fmt::Debug
451 + num::FromPrimitive
452 + num::ToPrimitive
453 + num::Float
454 + Copy,
455{
456 type VoxelIndex = [usize; D];
457
458 fn get_voxel_index_of(&self, cell: &C) -> Result<Self::VoxelIndex, BoundaryError> {
459 let pos = cell.pos().row_sum().transpose() / F::from_usize(cell.pos().nrows()).unwrap();
460 let index = self.domain.get_voxel_index_of_raw(&pos)?;
461 Ok(index)
462 }
463}
464
465impl<F, const D: usize> DomainCreateSubDomains<CartesianSubDomainRods<F, D>>
466 for CartesianCuboidRods<F, D>
467where
468 F: 'static + num::Float + core::fmt::Debug + num::FromPrimitive,
469{
470 type SubDomainIndex = usize;
471 type VoxelIndex = [usize; D];
472
473 fn create_subdomains(
474 &self,
475 n_subdomains: std::num::NonZeroUsize,
476 ) -> Result<
477 impl IntoIterator<
478 Item = (
479 Self::SubDomainIndex,
480 CartesianSubDomainRods<F, D>,
481 Vec<Self::VoxelIndex>,
482 ),
483 >,
484 DecomposeError,
485 > {
486 let subdomains = self.domain.create_subdomains(n_subdomains)?;
487 Ok(subdomains
488 .into_iter()
489 .map(move |(subdomain_index, subdomain, voxels)| {
490 (
491 subdomain_index,
492 CartesianSubDomainRods::<F, D> {
493 subdomain,
494 gel_pressure: self.gel_pressure,
495 surface_friction: self.surface_friction,
496 surface_friction_distance: self.surface_friction,
497 },
498 voxels,
499 )
500 }))
501 }
502}
503
504#[derive(Clone, SubDomain, Serialize, Deserialize)]
506#[serde(bound = "
507F: 'static
508 + PartialEq
509 + Clone
510 + core::fmt::Debug
511 + Serialize
512 + for<'a> Deserialize<'a>,
513[usize; D]: Serialize + for<'a> Deserialize<'a>,
514")]
515pub struct CartesianSubDomainRods<F, const D: usize> {
516 #[Base]
518 pub subdomain: CartesianSubDomain<F, D>,
519 pub gel_pressure: F,
521 pub surface_friction: F,
523 pub surface_friction_distance: F,
525}
526
527impl<F>
528 SubDomainForce<
529 Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
530 Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
531 Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
532 F,
533 > for CartesianSubDomainRods<F, 3>
534where
535 F: nalgebra::RealField + num::Float,
536{
537 fn calculate_custom_force(
538 &self,
539 pos: &Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
540 vel: &Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
541 _: &F,
542 ) -> Result<
543 Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
544 cellular_raza_concepts::CalcError,
545 > {
546 use core::ops::AddAssign;
547 let mut force = nalgebra::MatrixXx3::from_fn(pos.nrows(), |_, m| {
548 if m == 2 {
549 -self.gel_pressure
550 } else {
551 F::zero()
552 }
553 });
554 for (i, (p, v)) in pos.row_iter().zip(vel.row_iter()).enumerate() {
555 let d1 = (p.transpose() - self.subdomain.domain_min)
556 .map(|x| <F as num::Float>::abs(x) <= self.surface_friction_distance);
557 let d2 = (p.transpose() - self.subdomain.domain_max)
558 .map(|x| <F as num::Float>::abs(x) <= self.surface_friction_distance);
559 let q = v.norm();
560 if q != F::zero() && d1.zip_map(&d2, |x, y| x || y).into_iter().any(|x| *x) {
561 let dir = v / q;
562 force
563 .row_mut(i)
564 .add_assign(-dir * self.gel_pressure * self.surface_friction);
565 }
566 }
567 Ok(force)
568 }
569}
570
571impl<F, const D: usize>
572 SubDomainMechanics<
573 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
574 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
575 > for CartesianSubDomainRods<F, D>
576where
577 F: nalgebra::RealField + num::Float,
578{
579 fn apply_boundary(
580 &self,
581 pos: &mut Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
582 vel: &mut Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
583 ) -> Result<(), BoundaryError> {
584 let two = F::one() + F::one();
589 pos.row_iter_mut()
590 .zip(vel.row_iter_mut())
591 .for_each(|(mut p, mut v)| {
592 for i in 0..p.ncols() {
594 if p[i] < self.subdomain.get_domain_min()[i] {
596 p[i] = self.subdomain.get_domain_min()[i] * two - p[i];
597 v[i] = <F as num::Float>::abs(v[i]);
598 }
599
600 if p[i] > self.subdomain.get_domain_max()[i] {
602 p[i] = self.subdomain.get_domain_max()[i] * two - p[i];
603 v[i] = -<F as num::Float>::abs(v[i]);
604 }
605 }
606 });
607
608 for j in 0..pos.nrows() {
610 let p = pos.row(j);
611 for i in 0..pos.ncols() {
612 if p[i] < self.subdomain.get_domain_min()[i]
613 || p[i] > self.subdomain.get_domain_max()[i]
614 {
615 return Err(BoundaryError(format!(
616 "Particle is out of domain at pos {:?}",
617 pos
618 )));
619 }
620 }
621 }
622 Ok(())
623 }
624}
625
626impl<C, F, const D: usize> SortCells<C> for CartesianSubDomainRods<F, D>
627where
628 C: Position<Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>>,
629 F: 'static
630 + nalgebra::Field
631 + Clone
632 + core::fmt::Debug
633 + num::FromPrimitive
634 + num::ToPrimitive
635 + num::Float
636 + Copy,
637{
638 type VoxelIndex = [usize; D];
639
640 fn get_voxel_index_of(&self, cell: &C) -> Result<Self::VoxelIndex, BoundaryError> {
641 let pos = cell.pos().row_sum().transpose() / F::from_usize(cell.pos().nrows()).unwrap();
642 let index = self.subdomain.get_index_of(pos)?;
643 Ok(index)
644 }
645}
646
647impl<F, const D: usize> RodMechanics<F, D> {
648 pub fn divide(
677 &mut self,
678 radius: F,
679 ) -> Result<RodMechanics<F, D>, cellular_raza_concepts::DivisionError>
680 where
681 F: num::Float + nalgebra::RealField + FromPrimitive + std::iter::Sum,
682 {
683 use itertools::Itertools;
684 let pos = self.pos();
685 let c1 = self;
686 let mut c2 = c1.clone();
687
688 let n_rows = c1.pos.nrows();
689 let two = F::one() + F::one();
692 let one_half = F::one() / two;
693 let div_factor = one_half - radius / (F::from_usize(n_rows).unwrap() * c1.spring_length);
694
695 let new_spring_length = div_factor * c1.spring_length;
697 c1.spring_length = new_spring_length;
698 c2.spring_length = new_spring_length;
699
700 c1.pos.set_row(0, &pos.row(0));
702 c2.pos
703 .set_row(c2.pos.nrows() - 1, &pos.row(c2.pos.nrows() - 1));
704
705 let segments: Vec<_> = pos
706 .row_iter()
707 .tuple_windows::<(_, _)>()
708 .map(|(x, y)| (x - y).norm())
709 .collect();
710 let segment_length = (segments.iter().copied().sum::<F>() - two * radius)
711 / F::from_usize(c2.pos.nrows() - 1).unwrap()
712 / two;
713
714 for n_vertex in 0..c2.pos.nrows() {
715 let k = (0..segments.len())
718 .filter(|n| {
719 segments.iter().copied().take(*n).sum::<F>()
720 <= F::from_usize(n_vertex).unwrap() * segment_length
721 })
722 .max()
723 .ok_or(DivisionError(
724 "Could not calculate new segment lengths".to_string(),
725 ))?;
726 let q = (F::from_usize(n_vertex).unwrap() * segment_length
727 - segments.iter().copied().take(k).sum::<F>())
728 / segments[k];
729 c1.pos.set_row(
730 n_vertex,
731 &(pos.row(k) * (F::one() - q) + pos.row(k + 1) * q),
732 );
733
734 let m = (0..c2.pos.nrows())
735 .filter(|n| {
736 segments.iter().rev().copied().take(*n).sum::<F>()
737 <= F::from_usize(n_vertex).unwrap() * segment_length
738 })
739 .max()
740 .ok_or(DivisionError(
741 "Could not calculate new segment lengths".to_string(),
742 ))?;
743 let p = (F::from_usize(n_vertex).unwrap() * segment_length
744 - segments.iter().rev().copied().take(m).sum::<F>())
745 / segments[c2.pos.nrows() - m - 2];
746 c2.pos.set_row(
747 c2.pos.nrows() - n_vertex - 1,
748 &(pos.row(c2.pos.nrows() - m - 1) * (F::one() - p)
749 + pos.row(c2.pos.nrows() - m - 2) * p),
750 );
751 }
752 Ok(c2)
753 }
754}