1use cellular_raza_concepts::*;
3
4use itertools::Itertools;
6use nalgebra::SVector;
7
8use serde::{Deserialize, Serialize};
9
10pub(super) fn get_decomp_res(n_voxel: usize, n_regions: usize) -> Option<(usize, usize, usize)> {
20 let mut average_len: i64 = (n_voxel as f64 / n_regions as f64).ceil() as i64;
33
34 let residue = |n: i64, m: i64, avg: i64| n_voxel as i64 - avg * n - (avg - 1) * m;
35
36 let mut n = n_regions as i64;
37 let mut m = 0;
38
39 for _ in 0..n_regions {
40 match residue(n, m, average_len) {
41 0 => {
42 return Some((n as usize, m as usize, average_len as usize));
43 }
44 1..=i64::MAX => {
45 if n == n_regions as i64 {
46 average_len += 1;
48 n = n_regions as i64;
49 m = 0;
50 }
51 }
52 i64::MIN..0 => {
53 n -= 1;
54 m += 1;
55 }
56 }
57 }
58 None
59}
60
61#[derive(Clone, Debug)]
65pub struct CartesianCuboid<F, const D: usize> {
66 min: SVector<F, D>,
67 max: SVector<F, D>,
68 dx: SVector<F, D>,
69 n_voxels: SVector<usize, D>,
70 pub rng_seed: u64,
72}
73
74impl<F, const D: usize> CartesianCuboid<F, D>
75where
76 F: Clone,
77{
78 pub fn get_min(&self) -> SVector<F, D> {
80 self.min.clone()
81 }
82
83 pub fn get_max(&self) -> SVector<F, D> {
85 self.max.clone()
86 }
87
88 pub fn get_dx(&self) -> SVector<F, D> {
90 self.dx.clone()
91 }
92
93 pub fn get_n_voxels(&self) -> SVector<usize, D> {
95 self.n_voxels
96 }
97}
98
99impl<C, Ci, F, const D: usize> Domain<C, CartesianSubDomain<F, D>, Ci> for CartesianCuboid<F, D>
100where
101 C: Position<nalgebra::SVector<F, D>>,
102 F: 'static
103 + num::Float
104 + Copy
105 + core::fmt::Debug
106 + num::FromPrimitive
107 + num::ToPrimitive
108 + core::ops::SubAssign
109 + core::ops::Div<Output = F>
110 + core::ops::DivAssign,
111 Ci: IntoIterator<Item = C>,
112{
113 type SubDomainIndex = usize;
114 type VoxelIndex = [usize; D];
115
116 fn decompose(
117 self,
118 n_subdomains: core::num::NonZeroUsize,
119 cells: Ci,
120 ) -> Result<DecomposedDomain<Self::SubDomainIndex, CartesianSubDomain<F, D>, C>, DecomposeError>
121 {
122 #[derive(Clone, Domain)]
123 struct MyIntermdiatedomain<F, const D: usize>
124 where
125 F: 'static
126 + num::Float
127 + Copy
128 + core::fmt::Debug
129 + num::FromPrimitive
130 + num::ToPrimitive
131 + core::ops::SubAssign
132 + core::ops::Div<Output = F>
133 + core::ops::DivAssign,
134 {
135 #[DomainRngSeed]
136 #[DomainCreateSubDomains]
137 #[SortCells]
138 domain: CartesianCuboid<F, D>,
139 }
140 let my_intermediate_domain = MyIntermdiatedomain { domain: self };
141 my_intermediate_domain.decompose(n_subdomains, cells)
142 }
143}
144
145impl<F, const D: usize> CartesianCuboid<F, D>
146where
147 F: 'static + num::Float + Copy + core::fmt::Debug + num::FromPrimitive + num::ToPrimitive,
148{
149 fn check_min_max(min: &[F; D], max: &[F; D]) -> Result<(), BoundaryError>
150 where
151 F: core::fmt::Debug,
152 {
153 for i in 0..D {
154 if min[i] >= max[i] {
155 return Err(BoundaryError(format!(
156 "Min {:?} must be smaller than Max {:?} for domain boundaries!",
157 min, max
158 )));
159 }
160 }
161 Ok(())
162 }
163
164 pub fn from_boundaries_and_interaction_range(
184 min: impl Into<[F; D]>,
185 max: impl Into<[F; D]>,
186 interaction_range: F,
187 ) -> Result<Self, BoundaryError> {
188 let min: [F; D] = min.into();
190 let max: [F; D] = max.into();
191
192 Self::check_min_max(&min, &max)?;
194
195 let mut n_voxels = [0; D];
197 let mut dx = [F::zero(); D];
198 for i in 0..D {
199 let n = ((max[i] - min[i]) / interaction_range).floor();
200 let m = n.to_usize().ok_or(BoundaryError(
202 cellular_raza_concepts::format_error_message!(
203 format!(
204 "Cannot convert float {:?} of type {} to usize",
205 n,
206 std::any::type_name::<F>()
207 ),
208 "conversion error during domain setup"
209 ),
210 ))?;
211 n_voxels[i] = m.max(1);
212 dx[i] = (max[i] - min[i]) / n;
213 }
214
215 Ok(Self {
216 min: min.into(),
217 max: max.into(),
218 dx: dx.into(),
219 n_voxels: n_voxels.into(),
220 rng_seed: 0,
221 })
222 }
223
224 pub fn from_boundaries_and_n_voxels(
227 min: impl Into<[F; D]>,
228 max: impl Into<[F; D]>,
229 n_voxels: impl Into<[usize; D]>,
230 ) -> Result<Self, BoundaryError> {
231 let min: [F; D] = min.into();
232 let max: [F; D] = max.into();
233 let n_voxels: [usize; D] = n_voxels.into();
234 Self::check_min_max(&min, &max)?;
235 let mut dx: SVector<F, D> = [F::zero(); D].into();
236 for i in 0..D {
237 let n = F::from_usize(n_voxels[i]).ok_or(BoundaryError(
238 cellular_raza_concepts::format_error_message!(
239 "conversion error during domain setup",
240 format!(
241 "Cannot convert usize {} to float of type {}",
242 n_voxels[i],
243 std::any::type_name::<F>()
244 )
245 ),
246 ))?;
247 dx[i] = (max[i] - min[i]) / n;
248 }
249 Ok(Self {
250 min: min.into(),
251 max: max.into(),
252 dx,
253 n_voxels: n_voxels.into(),
254 rng_seed: 0,
255 })
256 }
257}
258
259impl<F, const D: usize> CartesianCuboid<F, D> {
260 fn get_all_voxel_indices(&self) -> impl IntoIterator<Item = [usize; D]> {
261 use itertools::*;
262 (0..D)
263 .map(|i| 0..self.n_voxels[i])
264 .multi_cartesian_product()
265 .map(|x| {
266 let mut index = [0; D];
267 index.copy_from_slice(&x);
268 index
269 })
270 }
271
272 fn get_n_indices(&self) -> usize {
274 let mut res = 1;
275 for i in 0..D {
276 res *= self.n_voxels[i];
277 }
278 res
279 }
280}
281
282mod test_domain_setup {
283 #[test]
284 fn from_boundaries_and_interaction_range() {
285 use crate::CartesianCuboid;
286 let min = [0.0; 2];
287 let max = [2.0; 2];
288 let interaction_range = 1.0;
289 let _ = CartesianCuboid::from_boundaries_and_interaction_range(min, max, interaction_range)
290 .unwrap();
291 }
293
294 #[test]
295 fn from_boundaries_and_n_voxels() {
296 use crate::CartesianCuboid;
297 let min = [-100.0f32; 55];
298 let max = [43000.0f32; 55];
299 let n_voxels = [22; 55];
300 let _ = CartesianCuboid::from_boundaries_and_n_voxels(min, max, n_voxels).unwrap();
301 }
303}
304
305impl<F, const D: usize> CartesianCuboid<F, D>
306where
307 F: 'static
308 + num::Float
309 + Copy
310 + core::fmt::Debug
311 + num::FromPrimitive
312 + num::ToPrimitive
313 + core::ops::SubAssign
314 + core::ops::Div<Output = F>
315 + core::ops::DivAssign,
316{
317 pub fn get_voxel_index_of_raw(&self, pos: &SVector<F, D>) -> Result<[usize; D], BoundaryError> {
321 Self::check_min_max(&self.min.into(), &(*pos).into())?;
322 let n_vox = (pos - self.min).component_div(&self.dx);
323 let mut res = [0usize; D];
324 for i in 0..D {
325 res[i] = n_vox[i].to_usize().ok_or(BoundaryError(
326 cellular_raza_concepts::format_error_message!(
327 "conversion error during domain setup",
328 format!(
329 "Cannot convert float {:?} of type {} to usize",
330 n_vox[i],
331 std::any::type_name::<F>()
332 )
333 ),
334 ))?;
335 }
336 Ok(res)
337 }
338}
339
340impl<C, F, const D: usize> SortCells<C> for CartesianCuboid<F, D>
341where
342 F: 'static
343 + num::Float
344 + Copy
345 + core::fmt::Debug
346 + num::FromPrimitive
347 + num::ToPrimitive
348 + core::ops::SubAssign
349 + core::ops::Div<Output = F>
350 + core::ops::DivAssign,
351 C: Position<SVector<F, D>>,
352{
353 type VoxelIndex = [usize; D];
354
355 fn get_voxel_index_of(&self, cell: &C) -> Result<Self::VoxelIndex, BoundaryError> {
356 let pos = cell.pos();
357 self.get_voxel_index_of_raw(&pos)
358 }
359}
360
361impl<C, F, const D: usize> SortCells<C> for CartesianSubDomain<F, D>
362where
363 C: Position<nalgebra::SVector<F, D>>,
364 F: 'static + num::Float + core::fmt::Debug + core::ops::SubAssign + core::ops::DivAssign,
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();
370 self.get_index_of(pos)
371 }
372}
373
374impl<F, const D: usize> DomainRngSeed for CartesianCuboid<F, D> {
375 fn get_rng_seed(&self) -> u64 {
376 self.rng_seed
377 }
378}
379
380#[test]
381fn generate_subdomains() {
382 use DomainCreateSubDomains;
383 let min = [0.0; 3];
384 let max = [100.0; 3];
385 let interaction_range = 20.0;
386 let domain =
387 CartesianCuboid::from_boundaries_and_interaction_range(min, max, interaction_range)
388 .unwrap();
389 let sub_domains = domain
390 .create_subdomains(4.try_into().unwrap())
391 .unwrap()
392 .into_iter()
393 .collect::<Vec<_>>();
394 assert_eq!(sub_domains.len(), 4);
395 assert_eq!(
396 sub_domains
397 .iter()
398 .map(|(_, _, voxels)| voxels.len())
399 .sum::<usize>(),
400 5usize.pow(3)
401 );
402}
403
404#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
406#[serde(bound = "
407F: 'static
408 + PartialEq
409 + Clone
410 + core::fmt::Debug
411 + Serialize
412 + for<'a> Deserialize<'a>,
413[usize; D]: Serialize + for<'a> Deserialize<'a>,
414")]
415pub struct CartesianSubDomain<F, const D: usize> {
416 min: SVector<F, D>,
417 max: SVector<F, D>,
418 dx: SVector<F, D>,
419 voxels: Vec<[usize; D]>,
420 pub(crate) domain_min: SVector<F, D>,
421 pub(crate) domain_max: SVector<F, D>,
422 domain_n_voxels: SVector<usize, D>,
423}
424
425impl<F, const D: usize> CartesianSubDomain<F, D>
426where
427 F: Clone,
428{
429 pub fn get_min(&self) -> SVector<F, D> {
432 self.min.clone()
433 }
434
435 pub fn get_max(&self) -> SVector<F, D> {
438 self.max.clone()
439 }
440
441 pub fn get_dx(&self) -> SVector<F, D> {
443 self.dx.clone()
444 }
445
446 pub fn get_voxels(&self) -> Vec<[usize; D]> {
448 self.voxels.clone()
449 }
450
451 pub fn get_domain_min(&self) -> SVector<F, D> {
453 self.domain_min.clone()
454 }
455
456 pub fn get_domain_max(&self) -> SVector<F, D> {
458 self.domain_max.clone()
459 }
460
461 pub fn get_domain_n_voxels(&self) -> SVector<usize, D> {
463 self.domain_n_voxels
464 }
465}
466
467impl<F, const D: usize> CartesianSubDomain<F, D> {
468 pub fn get_index_of<P>(&self, pos: P) -> Result<[usize; D], BoundaryError>
470 where
471 [F; D]: From<P>,
472 F: 'static + num::Float + core::fmt::Debug + core::ops::SubAssign + core::ops::DivAssign,
473 {
474 let pos: [F; D] = pos.into();
475 let mut res = [0usize; D];
476 for i in 0..D {
477 let n_vox = (pos[i] - self.domain_min[i]) / self.dx[i];
478 res[i] = n_vox.to_usize().ok_or(BoundaryError(
479 cellular_raza_concepts::format_error_message!(
480 "conversion error during domain setup",
481 format!(
482 "Cannot convert float {:?} of type {} to usize",
483 n_vox,
484 std::any::type_name::<F>()
485 )
486 ),
487 ))?;
488 }
489 Ok(res)
490 }
491}
492
493impl<F, const D: usize> DomainCreateSubDomains<CartesianSubDomain<F, D>> for CartesianCuboid<F, D>
494where
495 F: 'static + num::Float + core::fmt::Debug + num::FromPrimitive,
496{
497 type SubDomainIndex = usize;
498 type VoxelIndex = [usize; D];
499
500 fn create_subdomains(
501 &self,
502 n_subdomains: core::num::NonZeroUsize,
503 ) -> Result<
504 impl IntoIterator<
505 Item = (
506 Self::SubDomainIndex,
507 CartesianSubDomain<F, D>,
508 Vec<Self::VoxelIndex>,
509 ),
510 >,
511 DecomposeError,
512 > {
513 let indices = self.get_all_voxel_indices();
514 let n_indices = self.get_n_indices();
515
516 let (n, _m, average_len) = get_decomp_res(n_indices, n_subdomains.into()).ok_or(
517 DecomposeError::Generic("Could not find a suiting decomposition".to_owned()),
518 )?;
519
520 let switcher = n * average_len;
523 let indices_grouped = indices.into_iter().enumerate().chunk_by(|(i, _)| {
524 use num::Integer;
525 if *i < switcher {
526 i.div_rem(&average_len).0
527 } else {
528 (i - switcher).div_rem(&(average_len - 1).max(1)).0 + n
529 }
530 });
531 let mut res = Vec::new();
532 for (n_subdomain, indices) in indices_grouped.into_iter() {
533 let mut min_vox = [usize::MAX; D];
534 let mut max_vox = [0; D];
535 let voxels = indices
536 .into_iter()
537 .map(|(_, index)| {
538 for i in 0..D {
539 min_vox[i] = min_vox[i].min(index[i]);
540 max_vox[i] = max_vox[i].max(index[i]);
541 }
542 index
543 })
544 .collect::<Vec<_>>();
545 let mut min = [F::zero(); D];
546 let mut max = [F::zero(); D];
547 for i in 0..D {
548 let n_vox_min = F::from_usize(min_vox[i]).ok_or(DecomposeError::Generic(
549 cellular_raza_concepts::format_error_message!(
550 "conversion error during domain setup",
551 format!(
552 "Cannot convert float {:?} of type {} to usize",
553 min_vox[i],
554 std::any::type_name::<F>()
555 )
556 ),
557 ))?;
558 let n_vox_max = F::from_usize(max_vox[i]).ok_or(DecomposeError::Generic(
559 cellular_raza_concepts::format_error_message!(
560 "conversion error during domain setup",
561 format!(
562 "Cannot convert float {:?} of type {} to usize",
563 max_vox[i],
564 std::any::type_name::<F>()
565 )
566 ),
567 ))?;
568 min[i] = self.min[i] + n_vox_min * self.dx[i];
569 max[i] = self.min[i] + (n_vox_max + F::one()) * self.dx[i];
570 }
571 let subdomain = CartesianSubDomain {
572 min: min.into(),
573 max: max.into(),
574 dx: self.dx,
575 voxels: voxels.clone(),
576 domain_min: self.min,
577 domain_max: self.max,
578 domain_n_voxels: self.n_voxels,
579 };
580 res.push((n_subdomain, subdomain, voxels));
581 }
582 Ok(res)
583 }
584}
585
586impl<Coord, F, const D: usize> SubDomainMechanics<Coord, Coord> for CartesianSubDomain<F, D>
587where
588 Coord: Clone,
589 [F; D]: From<Coord>,
590 Coord: From<[F; D]>,
591 Coord: std::fmt::Debug,
592 F: num::Float,
593{
594 fn apply_boundary(&self, pos: &mut Coord, vel: &mut Coord) -> Result<(), BoundaryError> {
595 let mut velocity: [F; D] = vel.clone().into();
596 let mut position: [F; D] = pos.clone().into();
597
598 let two = F::one() + F::one();
600
601 for i in 0..D {
603 if position[i] < self.domain_min[i] {
605 position[i] = two * self.domain_min[i] - position[i];
606 velocity[i] = velocity[i].abs();
607 }
608 if position[i] > self.domain_max[i] {
610 position[i] = two * self.domain_max[i] - position[i];
611 velocity[i] = -velocity[i].abs();
612 }
613 }
614
615 for (p, (dmin, dmax)) in position
616 .iter()
617 .zip(self.domain_min.iter().zip(self.domain_max.iter()))
618 {
619 if p < dmin || p > dmax {
620 return Err(BoundaryError(format!(
621 "Particle is out of domain at position {:?}",
622 pos
623 )));
624 }
625 }
626
627 *pos = position.into();
629 *vel = velocity.into();
630 Ok(())
631 }
632}
633
634impl<F, const D: usize> SubDomain for CartesianSubDomain<F, D> {
635 type VoxelIndex = [usize; D];
636
637 fn get_all_indices(&self) -> Vec<Self::VoxelIndex> {
638 self.voxels.clone()
639 }
640
641 fn get_neighbor_voxel_indices(&self, voxel_index: &Self::VoxelIndex) -> Vec<Self::VoxelIndex> {
642 let mut bounds = [[0; 2]; D];
644 for i in 0..D {
645 bounds[i] = [
646 (voxel_index[i] as i64 - 1).max(0) as usize,
647 (voxel_index[i] + 2).min(self.domain_n_voxels[i]),
648 ];
649 }
650
651 (0..D)
653 .map(|i| bounds[i][0]..bounds[i][1])
654 .multi_cartesian_product()
655 .map(|ind_v| {
656 let mut res = [0; D];
657 <[usize]>::copy_from_slice(&mut res, &ind_v);
658 res
659 })
660 .filter(|ind| ind != voxel_index)
661 .collect()
662 }
663}
664
665#[cfg(test)]
666mod test {
667 use super::get_decomp_res;
668 use rayon::prelude::*;
669
670 #[test]
671 fn test_get_demomp_res() {
672 #[cfg(debug_assertions)]
673 let max = 500;
674 #[cfg(not(debug_assertions))]
675 let max = 5_000;
676
677 (1..max)
678 .into_par_iter()
679 .map(|n_voxel| {
680 #[cfg(debug_assertions)]
681 let max_regions = 100;
682 #[cfg(not(debug_assertions))]
683 let max_regions = 1_000;
684 for n_regions in 1..max_regions {
685 match get_decomp_res(n_voxel, n_regions) {
686 Some(res) => {
687 let (n, m, average_len) = res;
688 assert_eq!(n + m, n_regions);
689 assert_eq!(n * average_len + m * (average_len - 1), n_voxel);
690 }
691 None => panic!(
692 "No result for inputs n_voxel: {} n_regions: {}",
693 n_voxel, n_regions
694 ),
695 }
696 }
697 })
698 .collect::<Vec<()>>();
699 }
700}