Skip to main content

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;