cmtool_core/grid/mod.rs
1// SPDX-License-Identifier: GPL-3.0-or-later
2
3mod collections;
4use collections::*;
5pub use collections::{
6 AxisDescriptor, CylindricalAxis, OrientedAxis, cylindrical_index, index_to_oriented,
7};
8use enum_dispatch::enum_dispatch;
9use std::f64;
10
11#[cfg(feature = "use_vtk")]
12pub(crate) mod vtk;
13
14use crate::coordinates::*;
15use crate::utils::AxisPoints;
16
17// fn get_tangent_plane_at_r(
18// axis: usize,
19// r0: f64,
20// theta: f64,
21// z: f64,
22// extent_u: [f64; 2],
23// extent_v: [f64; 2],
24// ) -> BoundedPlane {
25// let x0 = r0 * theta.cos();
26// let y0 = r0 * theta.sin();
27// let z0 = z;
28
29// let normal = CartesianVec3([x0 / r0, y0 / r0, 0.0]);
30
31// let origin = CartesianCoordinates([x0, y0, z0]);
32
33// BoundedPlane {
34// normal,
35// origin,
36// extent_u,
37// extent_v,
38// axis,
39// }
40// }
41//
42// fn get_tangent_plane_at_r(
43// axis: usize,
44// r0: f64,
45// theta: f64,
46// z: f64,
47// extent_u: [f64; 2], // [theta0, theta1] — will be converted to arc length
48// extent_v: [f64; 2], // [z0, z1] — already metric
49// ) -> BoundedPlane {
50// let origin = CartesianCoordinates([r0 * theta.cos(), r0 * theta.sin(), z]);
51// let normal = CartesianVec3([theta.cos(), theta.sin(), 0.0]);
52
53// let extent_u_metric = [r0 * extent_u[0], r0 * extent_u[1]];
54
55// BoundedPlane {
56// normal,
57// origin,
58// extent_u: extent_u_metric,
59// extent_v, // z is already metric
60// axis,
61// }
62// }
63
64/// Represents the type of mesh geometry.
65#[derive(PartialEq, Clone, Copy)]
66pub enum MeshType {
67 /// A cylindrical mesh type.
68 Cylindrical,
69 /// A rectangular mesh type.
70 Rectangular,
71}
72
73/// Represents the direction of a neighboring cell relative to a given cell in a 3D grid.
74///
75/// This enum is used to indicate the spatial relationship between cells in a grid
76#[derive(Debug, PartialEq, Clone, Copy)]
77pub enum NeighborDirection {
78 /// Indicates that the cells are not neighbors.
79 NotNeighbors = 0,
80 /// Indicates the neighbor is in the negative X direction.
81 XMinus = 1,
82 /// Indicates the neighbor is in the positive X direction.
83 XPlus = 2,
84 /// Indicates the neighbor is in the negative Y direction.
85 YMinus = 3,
86 /// Indicates the neighbor is in the positive Y direction.
87 YPlus = 4,
88 /// Indicates the neighbor is in the negative Z direction.
89 ZMinus = 5,
90 /// Indicates the neighbor is in the positive Z direction.
91 ZPlus = 6,
92}
93
94impl NeighborDirection {
95 /// Checks if the direction is positive.
96 ///
97 /// Returns `true` if the direction is positive (XPlus, YPlus, ZPlus),
98 /// otherwise returns `false`.
99 pub const fn is_positive(self) -> bool {
100 matches!(
101 self,
102 NeighborDirection::XPlus | NeighborDirection::YPlus | NeighborDirection::ZPlus
103 )
104 }
105
106 /// Checks if the direction is negative.
107 ///
108 /// Returns `true` if the direction is negative (XMinus, YMinus, ZMinus),
109 /// otherwise returns `false`.
110 pub const fn is_negative(self) -> bool {
111 matches!(
112 self,
113 NeighborDirection::XMinus | NeighborDirection::YMinus | NeighborDirection::ZMinus
114 )
115 }
116
117 /// Returns an ordered pair of values based on the direction's positivity.
118 ///
119 /// If the direction is positive, returns (a, b). Otherwise, returns (b, a).
120 pub fn ordered_pair<T: Copy>(self, a: T, b: T) -> (T, T) {
121 if self.is_positive() { (a, b) } else { (b, a) }
122 }
123
124 /// Converts the direction into a coordinate index.
125 ///
126 /// Returns `Some(usize)` representing the index of the coordinate (0 for X axis,
127 /// 1 for Y axis, 2 for Z axis), or `None` if the variant is `NotNeighbors`.
128 pub fn to_coord_index(&self) -> Option<usize> {
129 match self {
130 Self::XMinus | Self::XPlus => Some(0),
131 Self::YMinus | Self::YPlus => Some(1),
132 Self::ZMinus | Self::ZPlus => Some(2),
133 Self::NotNeighbors => None,
134 }
135 }
136}
137
138// impl TryFrom<i32> for NeighborDirection {
139// type Error = &'static str;
140
141// fn try_from(value: i32) -> Result<Self, Self::Error> {
142// match value {
143// 0 => Ok(NeighborDirection::NotNeighbors),
144// 1 => Ok(NeighborDirection::XMinus),
145// 2 => Ok(NeighborDirection::XPlus),
146// 3 => Ok(NeighborDirection::YMinus),
147// 4 => Ok(NeighborDirection::YPlus),
148// 5 => Ok(NeighborDirection::ZMinus),
149// 6 => Ok(NeighborDirection::ZPlus),
150// _ => Err("Invalid integer value for NeighborDirection"),
151// }
152// }
153// }
154
155/// Trait for accessing mesh data in a coarsed-mesh model.
156///
157/// Defines methods to access various properties of a coarsed mesh (structured grid),
158pub trait CompartmentMeshAccessor {
159 /// Returns the minimum value along the specified axis.
160 ///
161 /// # Arguments
162 ///
163 /// * `i_axis` - The index of the axis
164 ///
165 /// # Returns
166 ///
167 /// The minimum value along the specified axis.
168 fn min_axis(&self, i_axis: usize) -> f64;
169
170 /// Returns the maximum value along the specified axis.
171 ///
172 /// # Arguments
173 ///
174 /// * `i_axis` - The index of the axis
175 ///
176 /// # Returns
177 ///
178 /// The maximum value along the specified axis.
179 fn max_axis(&self, i_axis: usize) -> f64;
180
181 /// Returns the step size between points along the specified axis.
182 ///
183 /// # Arguments
184 ///
185 /// * `i_axis` - The index of the axis
186 ///
187 /// # Returns
188 ///
189 /// The step size between points along the specified axis.
190 fn mesh_step_axis(&self, i_axis: usize) -> f64;
191
192 /// Returns the number of points along the specified axis.
193 ///
194 /// # Arguments
195 ///
196 /// * `i_axis` - The index of the axis
197 ///
198 /// # Returns
199 ///
200 /// The number of points along the specified axis.
201 fn n_points_axis(&self, i_axis: usize) -> usize;
202
203 /// Returns the total number of cells in the mesh.
204 ///
205 /// # Returns
206 ///
207 /// The total number of cells.
208 fn number_cell(&self) -> usize;
209
210 /// Returns the edge position of a specific cell along the specified axis and point index.
211 ///
212 /// # Arguments
213 ///
214 /// * `i_axis` - The index of the axis.
215 /// * `i_point` - The index of the point along the specified axis.
216 ///
217 /// # Returns
218 ///
219 /// The edge position of the specified cell.
220 fn get_cell_edge(&self, i_axis: usize, i_point: usize) -> f64;
221
222 /// Returns the center position of a specific cell along the specified axis and point index.
223 ///
224 /// # Arguments
225 ///
226 /// * `i_axis` - The index of the axis.
227 /// * `i_point` - The index of the point along the specified axis.
228 ///
229 /// # Returns
230 ///
231 /// The center position of the specified cell.
232 fn get_cell_center(&self, i_axis: usize, i_point: usize) -> f64;
233}
234
235/// Trait for manipulating and querying properties of cells in a compartment mesh.
236pub trait CompartmentMeshManip {
237 /// Determines if two cells are neighbors and returns the direction of neighborhood.
238 ///
239 /// # Arguments
240 ///
241 /// * `cell1_id` - The ID of the first cell.
242 /// * `cell2_id` - The ID of the second cell.
243 ///
244 /// # Returns
245 ///
246 /// A `NeighborDirection` indicating whether and how the cells are neighbors.
247 fn are_cell_neighbor(&self, cell1_id: usize, cell2_id: usize) -> NeighborDirection;
248
249 /// Computes the surface area of a specified cell.
250 ///
251 /// # Arguments
252 ///
253 /// * `cell_id` - The ID of the cell.
254 /// * `axis_project` - Axis index on which the surface is calculated
255 ///
256 /// # Returns
257 ///
258 /// The surface area of the cell as a floating-point number.
259 fn cell_surface(&self, cell_id: usize, i_axis: OrientedAxis) -> f64;
260
261 /// Computes the volume of a specified cell.
262 ///
263 /// # Arguments
264 ///
265 /// * `cell_id` - The ID of the cell.
266 ///
267 /// # Returns
268 ///
269 /// The volume of the cell as a floating-point number.
270 fn cell_volume(&self, cell_id: usize) -> f64;
271
272 /// Finds the cell ID corresponding to specific 3D coordinates.
273 ///
274 /// # Arguments
275 ///
276 /// * `coords` - A reference to a `Coords3` representing the 3D coordinates.
277 ///
278 /// # Returns
279 ///
280 /// An `Option<usize>` containing the cell ID if found, or `None` otherwise.
281 fn cell_from_coordinates(&self, coords: &Coords3) -> Option<usize>;
282
283 /// Checks if a point is inside a specified cell.
284 ///
285 /// # Arguments
286 ///
287 /// * `cell_id` - The ID of the cell to check.
288 /// * `point_coords` - A reference to a `Coords3` representing the point's coordinates.
289 ///
290 /// # Returns
291 ///
292 /// A boolean indicating whether the point is inside the cell.
293 fn is_point_inside(&self, cell_id: usize, point_coords: &Coords3) -> bool;
294
295 /// Retrieves the points index defining a specified cell.
296 ///
297 /// # Arguments
298 ///
299 /// * `cell_1d` - The ID of the cell.
300 ///
301 /// # Returns
302 ///
303 /// An `AxisPoints` object containing the indices of points of the cell.
304 fn cell_points(&self, cell_1d: usize) -> AxisPoints;
305
306 /// Returns the maximum number of interfaces a cell can have in this mesh.
307 ///
308 /// # Returns
309 ///
310 /// The maximum number of interfaces as a `usize`.
311 fn n_maximum_interface(&self) -> usize;
312
313 fn get_interface_plane(&self, cell1_id: usize, cell2_id: usize) -> (BoundedPlane, usize);
314
315 fn cell_from_ax_points(&self, axis_points: &AxisPoints) -> Option<usize>;
316 fn get_boundary(&self) -> Vec<usize>;
317}
318/// A compartment mesh grid.
319///
320/// This trait is automatically implemented for any type that implements both
321/// `CompartmentMeshAccessor` and `CompartmentMeshManip`, and is thread-safe (`Send` + `Sync`).
322/// It provides a unified interface for operations on compartment meshes, ensuring that such types
323/// can be used in concurrent programming contexts safely.
324#[enum_dispatch]
325pub trait CompartmentMesh: Send + Sync + CompartmentMeshAccessor + CompartmentMeshManip {}
326
327/// Automatically implements `CompartmentMesh` for any type `T` that implements both
328/// `CompartmentMeshAccessor` and `CompartmentMeshManip`, and is thread-safe.
329///
330/// This blanket implementation ensures that any type meeting these criteria can be used
331/// wherever a `CompartmentMesh` is required, without explicit implementation.
332impl<T: CompartmentMeshAccessor + CompartmentMeshManip + Send + Sync> CompartmentMesh for T {}
333
334pub struct CylindricalMarker;
335pub struct RectangularMarker;
336
337/// Base Compartment mesh structure for spatial modeling.
338///
339/// This struct provides the base components data for compartmental meshes
340///
341/// # Type Parameters
342///
343/// * `T`: A marker type used to distinguish different mesh configurations
344pub struct BaseCompartmentMesh<T> {
345 /// The axes of the coordinate system for the mesh.
346 ///
347 /// This array contains three `CoordAxis` instances, each representing one of the
348 /// principal axes
349 axes: [CoordAxis; 3],
350
351 /// The total number of cells in the mesh.
352 ///
353 /// This field stores the count of cells within the compartmental mesh.
354 n_cells: usize,
355
356 /// PhantomData marker for generic type `T`.
357 ///
358 /// This field is used to mark the generic type `T` in the struct without actually storing data.
359 _marker: std::marker::PhantomData<T>,
360}
361
362impl<T> BaseCompartmentMesh<T> {
363 fn new(descriptors: [AxisDescriptor; 3]) -> Self {
364 let axes: [CoordAxis; 3] = descriptors.map(CoordAxis::from);
365
366 let n_cells = axes
367 .iter()
368 .map(|ax| ax.descriptor.n_range)
369 .product::<usize>();
370 Self {
371 axes,
372 n_cells,
373 _marker: std::marker::PhantomData,
374 }
375 }
376}
377
378impl<T> CompartmentMeshAccessor for BaseCompartmentMesh<T> {
379 fn min_axis(&self, i_axis: usize) -> f64 {
380 self.axes[i_axis].descriptor.min_range
381 }
382
383 fn max_axis(&self, i_axis: usize) -> f64 {
384 self.axes[i_axis].descriptor.max_range
385 }
386
387 fn mesh_step_axis(&self, i_axis: usize) -> f64 {
388 self.axes[i_axis].descriptor.step
389 }
390
391 fn n_points_axis(&self, i_axis: usize) -> usize {
392 self.axes[i_axis].descriptor.n_range
393 }
394
395 fn number_cell(&self) -> usize {
396 self.n_cells
397 }
398
399 fn get_cell_edge(&self, i_axis: usize, i_point: usize) -> f64 {
400 #[cfg(debug_assertions)]
401 {
402 // Debug mode: safe indexing with bounds check
403 self.axes[i_axis].edges[i_point]
404 }
405
406 #[cfg(not(debug_assertions))]
407 unsafe {
408 *self.axes.get_unchecked(i_axis).edges.get_unchecked(i_point)
409 }
410 }
411
412 fn get_cell_center(&self, i_axis: usize, i_point: usize) -> f64 {
413 self.axes[i_axis].centers[i_point]
414 }
415}
416
417/// Cylindrical Compartment mesh structure for spatial modeling.
418pub type MeshCylindrical = BaseCompartmentMesh<CylindricalMarker>;
419
420/// Cuboid Compartment mesh structure for spatial modeling.
421pub type MeshRectangular = BaseCompartmentMesh<RectangularMarker>;
422
423impl CompartmentMeshManip for MeshCylindrical {
424 fn n_maximum_interface(&self) -> usize {
425 //Trivial numbering of interfaces in a structured grid
426 let nr = self.axes[0].descriptor.n_range;
427 let ntheta = self.axes[1].descriptor.n_range;
428 let nz = self.axes[2].descriptor.n_range;
429
430 let interfaces_r = (nr - 1) * ntheta * nz;
431 let interfaces_theta = nr * (ntheta - 1) * nz;
432 let interfaces_z = nr * ntheta * (nz - 1);
433
434 let wrap = nr * nz; //Wrap-in for connection between theta=-pi and theta=pi
435
436 interfaces_r + interfaces_theta + interfaces_z + wrap
437 }
438
439 fn get_interface_plane(&self, cell1_id: usize, cell2_id: usize) -> (BoundedPlane, usize) {
440 let neighbors = self.are_cell_neighbor(cell1_id, cell2_id);
441 let axis = neighbors
442 .to_coord_index()
443 .expect("RMTOOL(get_interface_plane): Cells must be neighbors to get interface plane");
444
445 let sign = if neighbors.is_negative() { -1.0 } else { 1.0 };
446 let normal_dir = get_normal(axis, sign == -1.);
447
448 let indices_cell = self.cell_points(cell1_id);
449
450 // Cell edges
451 let r0 = self.get_cell_edge(0, indices_cell[0]);
452 let r1 = self.get_cell_edge(0, indices_cell[0] + 1);
453 let theta0 = self.get_cell_edge(1, indices_cell[1]);
454 let theta1 = self.get_cell_edge(1, indices_cell[1] + 1);
455 let z0 = self.get_cell_edge(2, indices_cell[2]);
456 let z1 = self.get_cell_edge(2, indices_cell[2] + 1);
457 let pi = std::f64::consts::PI;
458 let normalize = |a: f64| -> f64 {
459 let mut x = a % (2.0 * pi);
460 if x > pi {
461 x -= 2.0 * pi;
462 }
463 if x < -pi {
464 x += 2.0 * pi;
465 }
466 x
467 };
468
469 // Centers
470 let r_center = 0.5 * (r0 + r1);
471
472 let z_center = 0.5 * (z0 + z1);
473 // let theta_center = 0.5 * (theta0 + theta1);
474 let theta_center = {
475 let mut dtheta = theta1 - theta0;
476 if dtheta > pi {
477 dtheta -= 2.0 * pi;
478 }
479 if dtheta < -pi {
480 dtheta += 2.0 * pi;
481 }
482 normalize(theta0 + 0.5 * dtheta)
483 };
484
485 let (r, theta, z) = match axis {
486 0 => (if sign < 0.0 { r0 } else { r1 }, theta_center, z_center),
487 1 => (r_center, if sign < 0.0 { theta0 } else { theta1 }, z_center),
488 2 => (r_center, theta_center, if sign < 0.0 { z0 } else { z1 }),
489 _ => unreachable!(),
490 };
491
492 let (extent_u, extent_v) = match axis {
493 0 => ([theta0, theta1], [z0, z1]),
494 1 => ([r0, r1], [z0, z1]),
495 2 => ([r0, r1], [theta0, theta1]),
496 _ => unreachable!(),
497 };
498
499 if axis == 0 {
500 let normal_cartesian = CartesianVec3([theta.cos(), theta.sin(), 0.0]);
501 let origin = CartesianCoordinates([r * theta.cos(), r * theta.sin(), z]);
502 let bounded_plane = BoundedPlane {
503 normal: normal_cartesian,
504 origin,
505 extent_u,
506 extent_v,
507 axis,
508 };
509 (bounded_plane, axis)
510 } else {
511 let cyl_normal = CylindricalVec3(normal_dir, theta);
512 let normal_cartesian = cyl_normal.to_cartesian_vec();
513 let origin = CylindricalCoordinates([r, theta, z]).into();
514 let bounded_plane = BoundedPlane {
515 normal: normal_cartesian,
516 origin,
517 extent_u,
518 extent_v,
519 axis,
520 };
521 (bounded_plane, axis)
522 }
523 }
524
525 // fn get_interface_plane(&self, cell1_id: usize, cell2_id: usize) -> (BoundedPlane, usize) {
526 // let neighbors = self.are_cell_neighbor(cell1_id, cell2_id);
527 // let axis = neighbors
528 // .to_coord_index()
529 // .expect("Cells must be neighbors to get interface plane");
530
531 // let sign = if neighbors.is_negative() { -1.0 } else { 1.0 };
532
533 // let indices_cell = self.cell_points(cell1_id);
534
535 // // Cell edges
536 // let r0 = self.get_cell_edge(0, indices_cell[0]);
537 // let r1 = self.get_cell_edge(0, indices_cell[0] + 1);
538 // let theta0 = self.get_cell_edge(1, indices_cell[1]);
539 // let theta1 = self.get_cell_edge(1, indices_cell[1] + 1);
540 // let z0 = self.get_cell_edge(2, indices_cell[2]);
541 // let z1 = self.get_cell_edge(2, indices_cell[2] + 1);
542
543 // // Midpoints
544 // let r_center = 0.5 * (r0 + r1);
545 // let z_center = 0.5 * (z0 + z1);
546 // let theta_center = 0.5 * (theta0 + theta1);
547
548 // // Extents
549 // let (extent_u, extent_v) = match axis {
550 // 0 => ([r0, r1], [z0, z1]), // Plane defined by (r, z)
551 // 1 => ([theta0, theta1], [z0, z1]), // Plane defined by (theta, z)
552 // 2 => ([r0, r1], [theta0, theta1]), // Plane defined by (r, theta)
553 // _ => unreachable!("Axis must be 0, 1, or 2"),
554 // };
555
556 // // Origin and normal
557 // let (origin, normal) = match axis {
558 // 0 => {
559 // // Plane defined by (r, z)
560 // let origin = CartesianCoordinates([r_center, theta_center, z_center]).into();
561 // let normal = CartesianVec3([1.0, 0.0, 0.0]); // Radial direction
562 // (origin, normal)
563 // }
564 // 1 => {
565 // // Plane defined by (theta, z)
566 // let origin = CartesianCoordinates([r_center, theta_center, z_center]).into();
567 // let normal = CartesianVec3([0.0, 1.0, 0.0]); // Angular direction
568 // (origin, normal)
569 // }
570 // 2 => {
571 // // Plane defined by (r, theta)
572 // let origin = CartesianCoordinates([r_center, theta_center, z_center]).into();
573 // let normal = CartesianVec3([0.0, 0.0, 1.0]); // Axial direction
574 // (origin, normal)
575 // }
576 // _ => unreachable!("Axis must be 0, 1, or 2"),
577 // };
578
579 // let bounded_plane = BoundedPlane {
580 // normal,
581 // origin,
582 // extent_u,
583 // extent_v,
584 // axis,
585 // };
586
587 // (bounded_plane, axis)
588 // }
589
590 fn are_cell_neighbor(&self, cell1_id: usize, cell2_id: usize) -> NeighborDirection {
591 let [r1, theta1, z1] = self.cell_points(cell1_id);
592 let [r2, theta2, z2] = self.cell_points(cell2_id);
593
594 let theta_divs = self.axes[1].descriptor.n_range as isize;
595
596 let r1 = r1 as isize;
597 let r2 = r2 as isize;
598 let z1 = z1 as isize;
599 let z2 = z2 as isize;
600 let t1 = theta1 as isize;
601 let t2 = theta2 as isize;
602
603 let dr = r2 - r1;
604 let dz = z2 - z1;
605
606 let delta_theta = (t2 - t1 + theta_divs) % theta_divs;
607
608 //Handle specific case for theta, which is mod pi
609 let dtheta: isize = if delta_theta == 1 {
610 1
611 } else if delta_theta == theta_divs - 1 {
612 -1
613 } else if delta_theta == 0 {
614 0
615 } else {
616 2
617 };
618
619 let distance = dr.abs() + dtheta.abs() + dz.abs();
620
621 if distance != 1 {
622 return NeighborDirection::NotNeighbors;
623 }
624
625 match (dr, dtheta, dz) {
626 (1, 0, 0) => NeighborDirection::XPlus,
627 (-1, 0, 0) => NeighborDirection::XMinus,
628 (0, 1, 0) => NeighborDirection::YPlus,
629 (0, -1, 0) => NeighborDirection::YMinus,
630 (0, 0, 1) => NeighborDirection::ZPlus,
631 (0, 0, -1) => NeighborDirection::ZMinus,
632 _ => NeighborDirection::NotNeighbors,
633 }
634 }
635
636 fn cell_surface(&self, cell_id: usize, i_axis: OrientedAxis) -> f64 {
637 let points_indices = self.cell_points(cell_id);
638
639 let i_axis: CylindricalAxis = oriented_to_cylindrical(i_axis);
640
641 let delta_ijk: Vec<f64> = self
642 .axes
643 .iter()
644 .zip(points_indices.iter())
645 .map(|(ax, cell_p)| ax.edges[cell_p + 1] - ax.edges[*cell_p])
646 .collect();
647
648 //The radial face of a cell sits on its outer edge, which is where get_interface_plane
649 //places the interface as well
650 let r = self.axes[0].edges[points_indices[0] + 1];
651 match i_axis {
652 CylindricalAxis::R => r * delta_ijk[1] * delta_ijk[2], // ds=r*dtheta*dz
653 CylindricalAxis::Theta => delta_ijk[0] * delta_ijk[2], // ds = dr*dz
654 CylindricalAxis::Z => {
655 // ds =r*dr*dtheta
656 // rr here is not radius but (r-R)
657 let rr = self.axes[0].edges[points_indices[0] + 1];
658 let r2 = self.axes[0].edges[points_indices[0]];
659 0.5 * (rr * rr - r2 * r2) * delta_ijk[1]
660 }
661 }
662 }
663
664 fn cell_volume(&self, cell_id: usize) -> f64 {
665 let points_indices = self.cell_points(cell_id);
666 let delta_ijk: Vec<f64> = self
667 .axes
668 .iter()
669 .zip(points_indices.iter())
670 .map(|(ax, cell_p)| ax.edges[cell_p + 1] - ax.edges[*cell_p])
671 .collect();
672
673 let height_axis: usize = CylindricalAxis::Z.into();
674 let surface_ij = self.cell_surface(cell_id, height_axis.into());
675
676 delta_ijk[height_axis] * surface_ij
677 }
678
679 fn cell_from_ax_points(&self, axis_points: &AxisPoints) -> Option<usize> {
680 let mut cell_1d = 0;
681 let mut multiplier = 1;
682
683 for i in (0..self.axes.len()).rev() {
684 let n = self.axes[i].descriptor.n_range;
685 if axis_points[i] >= n {
686 return None;
687 }
688 cell_1d += axis_points[i] * multiplier;
689
690 multiplier *= n;
691 }
692
693 Some(cell_1d)
694 }
695
696 fn cell_from_coordinates(&self, coords: &Coords3) -> Option<usize> {
697 let mut mesh_id = 0;
698 let mut cumulative_product = 1;
699 let CylindricalCoordinates(cylindrical_coords) = CartesianCoordinates(*coords).into();
700
701 // for (i, axe) in self.axes.as_ref().iter().enumerate() {
702 // let current_index = axe.index_from_edge_value(cylindrical_coords[i])?;
703 // mesh_id += current_index * cumulative_product;
704 // cumulative_product *= axe.descriptor.n_range;
705 // }
706
707 for i in (0..self.axes.len()).rev() {
708 let axe = &self.axes[i];
709 let current_index = axe.index_from_edge_value(cylindrical_coords[i])?;
710 mesh_id += current_index * cumulative_product;
711 cumulative_product *= axe.descriptor.n_range;
712 }
713
714 Some(mesh_id)
715 }
716
717 fn is_point_inside(&self, _cell_id: usize, _point_coords: &Coords3) -> bool {
718 todo!()
719 }
720
721 fn cell_points(&self, cell_1d: usize) -> AxisPoints {
722 let mut axis_points = AxisPoints::default();
723 let mut p_coeff_up = cell_1d;
724
725 // for (current_point, current_axis) in axis_points.iter_mut().zip(&self.axes) {
726 // let array_size = current_axis.descriptor.n_range;
727 // *current_point = p_coeff_up % array_size;
728 // p_coeff_up /= array_size;
729 // }
730
731 //Keep same logic as c++ code, would be better to start numbering from top to have forward loop
732 for i in (0..self.axes.len()).rev() {
733 let axe = &self.axes[i];
734 let array_size = axe.descriptor.n_range;
735 axis_points[i] = p_coeff_up % array_size;
736 p_coeff_up /= array_size;
737 }
738
739 axis_points
740 }
741
742 fn get_boundary(&self) -> Vec<usize> {
743 let (n_r, n_theta, n_z) = (
744 self.n_points_axis(0),
745 self.n_points_axis(1),
746 self.n_points_axis(2),
747 );
748
749 //Both z faces plus the outer r shell, which excludes the cells already taken by the faces
750 let expected = 2 * n_r * n_theta + n_theta * (n_z - 2);
751 let mut v = Vec::with_capacity(expected);
752
753 for i in 0..n_r {
754 for j in 0..n_theta {
755 let p = self
756 .cell_from_ax_points(&[i, j, 0])
757 .expect("get_boundary: out of bound ");
758 let p2 = self
759 .cell_from_ax_points(&[i, j, n_z - 1])
760 .expect("get_boundary: out of bound ");
761 v.push(p);
762 v.push(p2);
763 }
764 }
765
766 for k in 1..n_z - 1 {
767 for j in 0..n_theta {
768 let p = self
769 .cell_from_ax_points(&[n_r - 1, j, k])
770 .expect("get_boundary: out of bound ");
771 v.push(p)
772 }
773 }
774
775 if expected != v.len() {
776 panic!("Detected number is not correct {} {}", expected, v.len());
777 }
778
779 v
780 }
781}
782
783pub fn get_mesh(
784 meshtype: MeshType,
785 mut ax_descriptor: [AxisDescriptor; 3],
786) -> Box<dyn CompartmentMesh> {
787 match meshtype {
788 //TODO move this into geometryy module as just assert
789 //Having this logic here, hides specific behaviour to user which may lead to errors
790 MeshType::Cylindrical => {
791 ax_descriptor[0].min_range = 0.;
792 ax_descriptor[1].min_range = -std::f64::consts::PI;
793 ax_descriptor[1].max_range = std::f64::consts::PI;
794 Box::new(MeshCylindrical::new(ax_descriptor))
795 }
796 MeshType::Rectangular => unimplemented!("Manip for Rectangular impl"),
797 }
798}
799
800#[cfg(test)]
801mod tests;