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,
99 pub spring_tension: F,
101 pub rigidity: F,
103 pub spring_length: F,
105 pub damping: F,
107}
108
109#[cfg(feature = "approx")]
110impl<F, const D: usize> AbsDiffEq for RodMechanics<F, D>
111where
112 F: AbsDiffEq + nalgebra::Scalar,
113 F::Epsilon: Clone,
114{
115 type Epsilon = F::Epsilon;
116
117 fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
118 let RodMechanics {
119 pos,
120 vel,
121 diffusion_constant,
122 spring_tension,
123 rigidity,
124 spring_length,
125 damping,
126 } = &self;
127 pos.iter()
128 .zip(other.pos.iter())
129 .all(|(x, y)| x.abs_diff_eq(y, epsilon.clone()))
130 && vel
131 .iter()
132 .zip(other.vel.iter())
133 .all(|(x, y)| x.abs_diff_eq(y, epsilon.clone()))
134 && diffusion_constant.abs_diff_eq(&other.diffusion_constant, epsilon.clone())
135 && spring_tension.abs_diff_eq(&other.spring_tension, epsilon.clone())
136 && rigidity.abs_diff_eq(&other.rigidity, epsilon.clone())
137 && spring_length.abs_diff_eq(&other.spring_length, epsilon.clone())
138 && damping.abs_diff_eq(&other.damping, epsilon)
139 }
140
141 fn default_epsilon() -> Self::Epsilon {
142 F::default_epsilon()
143 }
144}
145
146impl<F, const D: usize>
147 Mechanics<
148 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
149 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
150 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
151 F,
152 > for RodMechanics<F, D>
153where
154 F: nalgebra::RealField + Clone + num::Float,
155 rand_distr::StandardNormal: rand_distr::Distribution<F>,
156{
157 fn calculate_increment(
158 &self,
159 force: Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
160 ) -> Result<
161 (
162 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
163 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
164 ),
165 CalcError,
166 > {
167 use core::ops::AddAssign;
168 let one_half = F::one() / (F::one() + F::one());
169 let two = F::one() + F::one();
170
171 let mut total_force = force;
173
174 let n_rows = self.pos.nrows();
176 let dist_internal = self.pos.rows(0, n_rows - 1) - self.pos.rows(1, n_rows - 1);
177 dist_internal.row_iter().enumerate().for_each(|(i, dist)| {
178 if !dist.norm().is_zero() {
179 let dir = dist.normalize();
180 let force_internal =
181 -dir * self.spring_tension * (dist.norm() - self.spring_length);
182 total_force.row_mut(i).add_assign(force_internal * one_half);
183 total_force
184 .row_mut(i + 1)
185 .add_assign(-force_internal * one_half);
186 }
187 });
188
189 use itertools::Itertools;
191 dist_internal
192 .row_iter()
193 .tuple_windows::<(_, _)>()
194 .enumerate()
195 .for_each(|(i, (conn1, conn2))| {
196 let angle = conn1.angle(&conn2);
197 let force_d = conn1 - conn2;
198 let force_dir = if !force_d.norm().is_zero() {
199 force_d.normalize()
200 } else {
201 force_d
202 };
203 let force =
204 force_dir * two * self.rigidity * <F as num::Float>::tan(one_half * angle);
205 total_force.row_mut(i).add_assign(-force * one_half);
206 total_force.row_mut(i + 1).add_assign(force);
207 total_force.row_mut(i + 2).add_assign(-force * one_half);
208 });
209
210 total_force -= &self.vel * self.damping;
212 Ok((self.vel.clone(), total_force))
213 }
214
215 fn get_random_contribution(
216 &self,
217 rng: &mut rand_chacha::ChaCha8Rng,
218 dt: F,
219 ) -> Result<
220 (
221 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
222 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
223 ),
224 RngError,
225 > {
226 let distr = match rand_distr::Normal::new(F::zero(), <F as num::Float>::sqrt(dt)) {
227 Ok(e) => Ok(e),
228 Err(e) => Err(cellular_raza_concepts::RngError(format!("{e}"))),
229 }?;
230 let dpos = nalgebra::Matrix::<F, Dyn, Const<D>, _>::from_distribution(
231 self.pos.nrows(),
232 &distr,
233 rng,
234 ) * <F as num::Float>::powi(F::one() + F::one(), -2)
235 * self.diffusion_constant
236 / dt;
237 let dvel = nalgebra::Matrix::<F, Dyn, Const<D>, _>::zeros(self.pos.nrows());
238
239 Ok((dpos, dvel))
240 }
241}
242
243impl<F: Clone, const D: usize> Position<Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>>
244 for RodMechanics<F, D>
245{
246 fn pos(&self) -> Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>> {
247 self.pos.clone()
248 }
249
250 fn set_pos(&mut self, position: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>) {
251 self.pos = position.clone();
252 }
253}
254
255impl<F: Clone, const D: usize> Velocity<Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>>
256 for RodMechanics<F, D>
257{
258 fn velocity(&self) -> Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>> {
259 self.vel.clone()
260 }
261
262 fn set_velocity(&mut self, velocity: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>) {
263 self.vel = velocity.clone();
264 }
265}
266
267#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
269#[cfg_attr(feature = "approx", derive(AbsDiffEq))]
270pub struct RodInteraction<I>(pub I);
271
272impl<I, Inf> InteractionInformation<Inf> for RodInteraction<I>
273where
274 I: InteractionInformation<Inf>,
275{
276 fn get_interaction_information(&self) -> Inf {
277 self.0.get_interaction_information()
278 }
279}
280
281impl<I, F, Inf, const D: usize>
282 Interaction<
283 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
284 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
285 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
286 Inf,
287 > for RodInteraction<I>
288where
289 I: Interaction<nalgebra::SVector<F, D>, nalgebra::SVector<F, D>, nalgebra::SVector<F, D>, Inf>,
290 F: 'static + nalgebra::RealField + Copy + core::fmt::Debug + num::Zero,
291{
292 fn calculate_force_between(
293 &self,
294 own_pos: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
295 own_vel: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
296 ext_pos: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
297 ext_vel: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
298 ext_inf: &Inf,
299 ) -> Result<
300 (
301 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
302 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
303 ),
304 CalcError,
305 > {
306 use core::ops::AddAssign;
307 use itertools::Itertools;
308 let mut force_own = nalgebra::Matrix::<F, Dyn, Const<D>, _>::zeros(own_vel.nrows());
309 let mut force_ext = nalgebra::Matrix::<F, Dyn, Const<D>, _>::zeros(own_vel.nrows());
310 for (i, p1) in own_pos.row_iter().enumerate() {
311 for (j, (p2_n0, p2_n1)) in ext_pos.row_iter().tuple_windows::<(_, _)>().enumerate() {
312 let (_, nearest_point, rel_length) = crate::nearest_point_from_point_to_line(
314 &p1.transpose(),
315 &(p2_n0.transpose(), p2_n1.transpose()),
316 );
317
318 let (f_own, f_ext) = self.0.calculate_force_between(
319 &p1.transpose(),
320 &own_vel.row(i).transpose(),
321 &nearest_point,
322 &ext_vel.row(j).transpose(),
323 ext_inf,
324 )?;
325
326 force_own.row_mut(i).add_assign(f_own.transpose());
327 force_ext
328 .row_mut(j)
329 .add_assign(f_ext.transpose() * (F::one() - rel_length));
330 force_ext
331 .row_mut((j + 1) % own_pos.nrows())
332 .add_assign(f_ext.transpose() * rel_length);
333 }
334 }
335 Ok((force_own, force_ext))
336 }
337
338 fn is_neighbor(
339 &self,
340 own_pos: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
341 ext_pos: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
342 ext_inf: &Inf,
343 ) -> Result<bool, CalcError> {
344 for p in own_pos.row_iter() {
345 for q in ext_pos.row_iter() {
346 if self
347 .0
348 .is_neighbor(&p.transpose(), &q.transpose(), ext_inf)?
349 {
350 return Ok(true);
351 }
352 }
353 }
354 Ok(false)
355 }
356
357 fn react_to_neighbors(&mut self, neighbors: usize) -> Result<(), CalcError> {
358 self.0.react_to_neighbors(neighbors)
359 }
360}
361
362#[derive(Domain)]
364pub struct CartesianCuboidRods<F, const D: usize> {
365 #[DomainRngSeed]
367 pub domain: CartesianCuboid<F, D>,
368 pub gel_pressure: F,
371 pub surface_friction: F,
373 pub surface_friction_distance: F,
375}
376
377impl<C, F, const D: usize> SortCells<C> for CartesianCuboidRods<F, D>
378where
379 C: Position<Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>>,
380 F: 'static
381 + nalgebra::Field
382 + Clone
383 + core::fmt::Debug
384 + num::FromPrimitive
385 + num::ToPrimitive
386 + num::Float
387 + Copy,
388{
389 type VoxelIndex = [usize; D];
390
391 fn get_voxel_index_of(&self, cell: &C) -> Result<Self::VoxelIndex, BoundaryError> {
392 let pos = cell.pos().row_sum().transpose() / F::from_usize(cell.pos().nrows()).unwrap();
393 let index = self.domain.get_voxel_index_of_raw(&pos)?;
394 Ok(index)
395 }
396}
397
398impl<F, const D: usize> DomainCreateSubDomains<CartesianSubDomainRods<F, D>>
399 for CartesianCuboidRods<F, D>
400where
401 F: 'static + num::Float + core::fmt::Debug + num::FromPrimitive,
402{
403 type SubDomainIndex = usize;
404 type VoxelIndex = [usize; D];
405
406 fn create_subdomains(
407 &self,
408 n_subdomains: std::num::NonZeroUsize,
409 ) -> Result<
410 impl IntoIterator<
411 Item = (
412 Self::SubDomainIndex,
413 CartesianSubDomainRods<F, D>,
414 Vec<Self::VoxelIndex>,
415 ),
416 >,
417 DecomposeError,
418 > {
419 let subdomains = self.domain.create_subdomains(n_subdomains)?;
420 Ok(subdomains
421 .into_iter()
422 .map(move |(subdomain_index, subdomain, voxels)| {
423 (
424 subdomain_index,
425 CartesianSubDomainRods::<F, D> {
426 subdomain,
427 gel_pressure: self.gel_pressure,
428 surface_friction: self.surface_friction,
429 surface_friction_distance: self.surface_friction,
430 },
431 voxels,
432 )
433 }))
434 }
435}
436
437#[derive(Clone, SubDomain, Serialize, Deserialize)]
439#[serde(bound = "
440F: 'static
441 + PartialEq
442 + Clone
443 + core::fmt::Debug
444 + Serialize
445 + for<'a> Deserialize<'a>,
446[usize; D]: Serialize + for<'a> Deserialize<'a>,
447")]
448pub struct CartesianSubDomainRods<F, const D: usize> {
449 #[Base]
451 pub subdomain: CartesianSubDomain<F, D>,
452 pub gel_pressure: F,
454 pub surface_friction: F,
456 pub surface_friction_distance: F,
458}
459
460impl<F>
461 SubDomainForce<
462 Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
463 Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
464 Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
465 F,
466 > for CartesianSubDomainRods<F, 3>
467where
468 F: nalgebra::RealField + num::Float,
469{
470 fn calculate_custom_force(
471 &self,
472 pos: &Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
473 vel: &Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
474 _: &F,
475 ) -> Result<
476 Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
477 cellular_raza_concepts::CalcError,
478 > {
479 use core::ops::AddAssign;
480 let mut force = nalgebra::MatrixXx3::from_fn(pos.nrows(), |_, m| {
481 if m == 2 {
482 -self.gel_pressure
483 } else {
484 F::zero()
485 }
486 });
487 for (i, (p, v)) in pos.row_iter().zip(vel.row_iter()).enumerate() {
488 let d1 = (p.transpose() - self.subdomain.domain_min)
489 .map(|x| <F as num::Float>::abs(x) <= self.surface_friction_distance);
490 let d2 = (p.transpose() - self.subdomain.domain_max)
491 .map(|x| <F as num::Float>::abs(x) <= self.surface_friction_distance);
492 let q = v.norm();
493 if q != F::zero() && d1.zip_map(&d2, |x, y| x || y).into_iter().any(|x| *x) {
494 let dir = v / q;
495 force
496 .row_mut(i)
497 .add_assign(-dir * self.gel_pressure * self.surface_friction);
498 }
499 }
500 Ok(force)
501 }
502}
503
504impl<F, const D: usize>
505 SubDomainMechanics<
506 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
507 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
508 > for CartesianSubDomainRods<F, D>
509where
510 F: nalgebra::RealField + num::Float,
511{
512 fn apply_boundary(
513 &self,
514 pos: &mut Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
515 vel: &mut Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
516 ) -> Result<(), BoundaryError> {
517 let two = F::one() + F::one();
522 pos.row_iter_mut()
523 .zip(vel.row_iter_mut())
524 .for_each(|(mut p, mut v)| {
525 for i in 0..p.ncols() {
527 if p[i] < self.subdomain.get_domain_min()[i] {
529 p[i] = self.subdomain.get_domain_min()[i] * two - p[i];
530 v[i] = <F as num::Float>::abs(v[i]);
531 }
532
533 if p[i] > self.subdomain.get_domain_max()[i] {
535 p[i] = self.subdomain.get_domain_max()[i] * two - p[i];
536 v[i] = -<F as num::Float>::abs(v[i]);
537 }
538 }
539 });
540
541 for j in 0..pos.nrows() {
543 let p = pos.row(j);
544 for i in 0..pos.ncols() {
545 if p[i] < self.subdomain.get_domain_min()[i]
546 || p[i] > self.subdomain.get_domain_max()[i]
547 {
548 return Err(BoundaryError(format!(
549 "Particle is out of domain at pos {:?}",
550 pos
551 )));
552 }
553 }
554 }
555 Ok(())
556 }
557}
558
559impl<C, F, const D: usize> SortCells<C> for CartesianSubDomainRods<F, D>
560where
561 C: Position<Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>>,
562 F: 'static
563 + nalgebra::Field
564 + Clone
565 + core::fmt::Debug
566 + num::FromPrimitive
567 + num::ToPrimitive
568 + num::Float
569 + Copy,
570{
571 type VoxelIndex = [usize; D];
572
573 fn get_voxel_index_of(&self, cell: &C) -> Result<Self::VoxelIndex, BoundaryError> {
574 let pos = cell.pos().row_sum().transpose() / F::from_usize(cell.pos().nrows()).unwrap();
575 let index = self.subdomain.get_index_of(pos)?;
576 Ok(index)
577 }
578}
579
580impl<F, const D: usize> RodMechanics<F, D> {
581 pub fn divide(
610 &mut self,
611 radius: F,
612 ) -> Result<RodMechanics<F, D>, cellular_raza_concepts::DivisionError>
613 where
614 F: num::Float + nalgebra::RealField + FromPrimitive + std::iter::Sum,
615 {
616 use itertools::Itertools;
617 let pos = self.pos();
618 let c1 = self;
619 let mut c2 = c1.clone();
620
621 let n_rows = c1.pos.nrows();
622 let two = F::one() + F::one();
625 let one_half = F::one() / two;
626 let div_factor = one_half - radius / (F::from_usize(n_rows).unwrap() * c1.spring_length);
627
628 let new_spring_length = div_factor * c1.spring_length;
630 c1.spring_length = new_spring_length;
631 c2.spring_length = new_spring_length;
632
633 c1.pos.set_row(0, &pos.row(0));
635 c2.pos
636 .set_row(c2.pos.nrows() - 1, &pos.row(c2.pos.nrows() - 1));
637
638 let segments: Vec<_> = pos
639 .row_iter()
640 .tuple_windows::<(_, _)>()
641 .map(|(x, y)| (x - y).norm())
642 .collect();
643 let segment_length = (segments.iter().copied().sum::<F>() - two * radius)
644 / F::from_usize(c2.pos.nrows() - 1).unwrap()
645 / two;
646
647 for n_vertex in 0..c2.pos.nrows() {
648 let k = (0..segments.len())
651 .filter(|n| {
652 segments.iter().copied().take(*n).sum::<F>()
653 <= F::from_usize(n_vertex).unwrap() * segment_length
654 })
655 .max()
656 .ok_or(DivisionError(
657 "Could not calculate new segment lengths".to_string(),
658 ))?;
659 let q = (F::from_usize(n_vertex).unwrap() * segment_length
660 - segments.iter().copied().take(k).sum::<F>())
661 / segments[k];
662 c1.pos.set_row(
663 n_vertex,
664 &(pos.row(k) * (F::one() - q) + pos.row(k + 1) * q),
665 );
666
667 let m = (0..c2.pos.nrows())
668 .filter(|n| {
669 segments.iter().rev().copied().take(*n).sum::<F>()
670 <= F::from_usize(n_vertex).unwrap() * segment_length
671 })
672 .max()
673 .ok_or(DivisionError(
674 "Could not calculate new segment lengths".to_string(),
675 ))?;
676 let p = (F::from_usize(n_vertex).unwrap() * segment_length
677 - segments.iter().rev().copied().take(m).sum::<F>())
678 / segments[c2.pos.nrows() - m - 2];
679 c2.pos.set_row(
680 c2.pos.nrows() - n_vertex - 1,
681 &(pos.row(c2.pos.nrows() - m - 1) * (F::one() - p)
682 + pos.row(c2.pos.nrows() - m - 2) * p),
683 );
684 }
685 Ok(c2)
686 }
687}