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
339#[derive(Domain)]
341pub struct CartesianCuboidRods<F, const D: usize> {
342 #[DomainRngSeed]
344 pub domain: CartesianCuboid<F, D>,
345 pub gel_pressure: F,
348 pub surface_friction: F,
350 pub surface_friction_distance: F,
352}
353
354impl<C, F, const D: usize> SortCells<C> for CartesianCuboidRods<F, D>
355where
356 C: Position<Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>>,
357 F: 'static
358 + nalgebra::Field
359 + Clone
360 + core::fmt::Debug
361 + num::FromPrimitive
362 + num::ToPrimitive
363 + num::Float
364 + Copy,
365{
366 type VoxelIndex = [usize; D];
367
368 fn get_voxel_index_of(&self, cell: &C) -> Result<Self::VoxelIndex, BoundaryError> {
369 let pos = cell.pos().row_sum().transpose() / F::from_usize(cell.pos().nrows()).unwrap();
370 let index = self.domain.get_voxel_index_of_raw(&pos)?;
371 Ok(index)
372 }
373}
374
375impl<F, const D: usize> DomainCreateSubDomains<CartesianSubDomainRods<F, D>>
376 for CartesianCuboidRods<F, D>
377where
378 F: 'static + num::Float + core::fmt::Debug + num::FromPrimitive,
379{
380 type SubDomainIndex = usize;
381 type VoxelIndex = [usize; D];
382
383 fn create_subdomains(
384 &self,
385 n_subdomains: std::num::NonZeroUsize,
386 ) -> Result<
387 impl IntoIterator<
388 Item = (
389 Self::SubDomainIndex,
390 CartesianSubDomainRods<F, D>,
391 Vec<Self::VoxelIndex>,
392 ),
393 >,
394 DecomposeError,
395 > {
396 let subdomains = self.domain.create_subdomains(n_subdomains)?;
397 Ok(subdomains
398 .into_iter()
399 .map(move |(subdomain_index, subdomain, voxels)| {
400 (
401 subdomain_index,
402 CartesianSubDomainRods::<F, D> {
403 subdomain,
404 gel_pressure: self.gel_pressure,
405 surface_friction: self.surface_friction,
406 surface_friction_distance: self.surface_friction,
407 },
408 voxels,
409 )
410 }))
411 }
412}
413
414#[derive(Clone, SubDomain, Serialize, Deserialize)]
416#[serde(bound = "
417F: 'static
418 + PartialEq
419 + Clone
420 + core::fmt::Debug
421 + Serialize
422 + for<'a> Deserialize<'a>,
423[usize; D]: Serialize + for<'a> Deserialize<'a>,
424")]
425pub struct CartesianSubDomainRods<F, const D: usize> {
426 #[Base]
428 pub subdomain: CartesianSubDomain<F, D>,
429 pub gel_pressure: F,
431 pub surface_friction: F,
433 pub surface_friction_distance: F,
435}
436
437impl<F>
438 SubDomainForce<
439 Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
440 Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
441 Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
442 F,
443 > for CartesianSubDomainRods<F, 3>
444where
445 F: nalgebra::RealField + num::Float,
446{
447 fn calculate_custom_force(
448 &self,
449 pos: &Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
450 vel: &Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
451 _: &F,
452 ) -> Result<
453 Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
454 cellular_raza_concepts::CalcError,
455 > {
456 use core::ops::AddAssign;
457 let mut force = nalgebra::MatrixXx3::from_fn(pos.nrows(), |_, m| {
458 if m == 2 {
459 -self.gel_pressure
460 } else {
461 F::zero()
462 }
463 });
464 for (i, (p, v)) in pos.row_iter().zip(vel.row_iter()).enumerate() {
465 let d1 = (p.transpose() - self.subdomain.domain_min)
466 .map(|x| <F as num::Float>::abs(x) <= self.surface_friction_distance);
467 let d2 = (p.transpose() - self.subdomain.domain_max)
468 .map(|x| <F as num::Float>::abs(x) <= self.surface_friction_distance);
469 let q = v.norm();
470 if q != F::zero() && d1.zip_map(&d2, |x, y| x || y).into_iter().any(|x| *x) {
471 let dir = v / q;
472 force
473 .row_mut(i)
474 .add_assign(-dir * self.gel_pressure * self.surface_friction);
475 }
476 }
477 Ok(force)
478 }
479}
480
481impl<F, const D: usize>
482 SubDomainMechanics<
483 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
484 Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
485 > for CartesianSubDomainRods<F, D>
486where
487 F: nalgebra::RealField + num::Float,
488{
489 fn apply_boundary(
490 &self,
491 pos: &mut Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
492 vel: &mut Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
493 ) -> Result<(), BoundaryError> {
494 let two = F::one() + F::one();
499 pos.row_iter_mut()
500 .zip(vel.row_iter_mut())
501 .for_each(|(mut p, mut v)| {
502 for i in 0..p.ncols() {
504 if p[i] < self.subdomain.get_domain_min()[i] {
506 p[i] = self.subdomain.get_domain_min()[i] * two - p[i];
507 v[i] = <F as num::Float>::abs(v[i]);
508 }
509
510 if p[i] > self.subdomain.get_domain_max()[i] {
512 p[i] = self.subdomain.get_domain_max()[i] * two - p[i];
513 v[i] = -<F as num::Float>::abs(v[i]);
514 }
515 }
516 });
517
518 for j in 0..pos.nrows() {
520 let p = pos.row(j);
521 for i in 0..pos.ncols() {
522 if p[i] < self.subdomain.get_domain_min()[i]
523 || p[i] > self.subdomain.get_domain_max()[i]
524 {
525 return Err(BoundaryError(format!(
526 "Particle is out of domain at pos {:?}",
527 pos
528 )));
529 }
530 }
531 }
532 Ok(())
533 }
534}
535
536impl<C, F, const D: usize> SortCells<C> for CartesianSubDomainRods<F, D>
537where
538 C: Position<Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>>,
539 F: 'static
540 + nalgebra::Field
541 + Clone
542 + core::fmt::Debug
543 + num::FromPrimitive
544 + num::ToPrimitive
545 + num::Float
546 + Copy,
547{
548 type VoxelIndex = [usize; D];
549
550 fn get_voxel_index_of(&self, cell: &C) -> Result<Self::VoxelIndex, BoundaryError> {
551 let pos = cell.pos().row_sum().transpose() / F::from_usize(cell.pos().nrows()).unwrap();
552 let index = self.subdomain.get_index_of(pos)?;
553 Ok(index)
554 }
555}
556
557impl<F, const D: usize> RodMechanics<F, D> {
558 pub fn divide(
587 &mut self,
588 radius: F,
589 ) -> Result<RodMechanics<F, D>, cellular_raza_concepts::DivisionError>
590 where
591 F: num::Float + nalgebra::RealField + FromPrimitive + std::iter::Sum,
592 {
593 use itertools::Itertools;
594 let pos = self.pos();
595 let c1 = self;
596 let mut c2 = c1.clone();
597
598 let n_rows = c1.pos.nrows();
599 let two = F::one() + F::one();
602 let one_half = F::one() / two;
603 let div_factor = one_half - radius / (F::from_usize(n_rows).unwrap() * c1.spring_length);
604
605 let new_spring_length = div_factor * c1.spring_length;
607 c1.spring_length = new_spring_length;
608 c2.spring_length = new_spring_length;
609
610 c1.pos.set_row(0, &pos.row(0));
612 c2.pos
613 .set_row(c2.pos.nrows() - 1, &pos.row(c2.pos.nrows() - 1));
614
615 let segments: Vec<_> = pos
616 .row_iter()
617 .tuple_windows::<(_, _)>()
618 .map(|(x, y)| (x - y).norm())
619 .collect();
620 let segment_length = (segments.iter().copied().sum::<F>() - two * radius)
621 / F::from_usize(c2.pos.nrows() - 1).unwrap()
622 / two;
623
624 for n_vertex in 0..c2.pos.nrows() {
625 let k = (0..segments.len())
628 .filter(|n| {
629 segments.iter().copied().take(*n).sum::<F>()
630 <= F::from_usize(n_vertex).unwrap() * segment_length
631 })
632 .max()
633 .ok_or(DivisionError(
634 "Could not calculate new segment lengths".to_string(),
635 ))?;
636 let q = (F::from_usize(n_vertex).unwrap() * segment_length
637 - segments.iter().copied().take(k).sum::<F>())
638 / segments[k];
639 c1.pos.set_row(
640 n_vertex,
641 &(pos.row(k) * (F::one() - q) + pos.row(k + 1) * q),
642 );
643
644 let m = (0..c2.pos.nrows())
645 .filter(|n| {
646 segments.iter().rev().copied().take(*n).sum::<F>()
647 <= F::from_usize(n_vertex).unwrap() * segment_length
648 })
649 .max()
650 .ok_or(DivisionError(
651 "Could not calculate new segment lengths".to_string(),
652 ))?;
653 let p = (F::from_usize(n_vertex).unwrap() * segment_length
654 - segments.iter().rev().copied().take(m).sum::<F>())
655 / segments[c2.pos.nrows() - m - 2];
656 c2.pos.set_row(
657 c2.pos.nrows() - n_vertex - 1,
658 &(pos.row(c2.pos.nrows() - m - 1) * (F::one() - p)
659 + pos.row(c2.pos.nrows() - m - 2) * p),
660 );
661 }
662 Ok(c2)
663 }
664}