Skip to main content

cmtool_core/model/
interfaces.rs

1// SPDX-License-Identifier: GPL-3.0-or-later
2
3use crate::coordinates::*;
4use crate::grid::NeighborDirection;
5use crate::model::CMGeometry;
6use crate::utils::{compute_intersection_area, is_curved_face, tangent_plane_at};
7
8const REL_TOLERANCE_AREA: f64 = 0.1;
9
10///The interface areas of a face should add up to the surface of the cell, a face with no
11///geometric surface should carry no interface either
12fn is_area_mismatch(total_area: f64, theoretical_area: f64) -> bool {
13    if theoretical_area.abs() < f64::EPSILON {
14        return total_area.abs() > f64::EPSILON;
15    }
16
17    (total_area - theoretical_area).abs() / theoretical_area > REL_TOLERANCE_AREA
18}
19
20#[derive(Default, Clone)]
21pub struct InterfaceInfo {
22    pub source_id: usize,
23    pub target_id: usize,
24}
25
26#[derive(Clone, Copy, Debug)]
27pub struct InterfaceFlow {
28    pub source_flow: f64,
29    pub target_flow: f64,
30}
31
32pub struct AInterfacesInfo {
33    n_facet: Vec<usize>,
34    pub ids: Vec<InterfaceInfo>,
35    pub area: Vec<Vec<f64>>,
36    pub normal_axis: Vec<usize>,
37    pub global_id_from_interface: Vec<Vec<usize>>,
38    pub interface_theta: Vec<f64>,
39    // pub plane_coordinates: Vec<f64>,
40    // pub planes: Vec<BoundedPlane>,
41}
42
43impl AInterfacesInfo {
44    pub fn new(at_interface: Vec<usize>) -> Self {
45        let n_interfaces = at_interface.len();
46
47        Self {
48            n_facet: at_interface,
49            ids: vec![Default::default(); n_interfaces],
50            area: vec![Default::default(); n_interfaces],
51            normal_axis: vec![Default::default(); n_interfaces],
52            global_id_from_interface: vec![Default::default(); n_interfaces],
53            interface_theta: vec![Default::default(); n_interfaces],
54            // plane_coordinates: vec![0.; n_interfaces * 3 * 2], //Extent geometry
55            // planes: Vec::new(),
56        }
57    }
58}
59
60//use std::{iter::Sum, ops::Add};
61//impl Add for InterfaceFlow {
62//type Output = Self;
63//
64//fn add(self, other: Self) -> Self {
65//InterfaceFlow {
66//source_flow: self.source_flow + other.source_flow,
67//target_flow: self.target_flow + other.target_flow,
68//}
69//}
70//}
71
72//impl Sum for InterfaceFlow {
73//fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
74//iter.fold(Self::default(), Add::add)
75//}
76//}
77
78impl Default for InterfaceFlow {
79    fn default() -> Self {
80        InterfaceFlow {
81            source_flow: 0.0,
82            target_flow: 0.0,
83        }
84    }
85}
86
87impl AInterfacesInfo {
88    pub fn n_interfaces(&self) -> usize {
89        self.n_facet.len()
90    }
91
92    pub fn fill(&mut self, geometry: &CMGeometry, interface_count_raw: &[usize]) {
93        let mut planes: Vec<BoundedPlane> = Vec::with_capacity(self.n_interfaces());
94
95        let grid = geometry.get_grid().unwrap();
96        let n_zones = geometry.n_zone();
97        let mut interfaces_id_from_cells = vec![0; n_zones * n_zones];
98        let mut interface_counter = 0;
99
100        for source_id in 0..n_zones {
101            for target_id in 0..n_zones {
102                if interface_count_raw[source_id * n_zones + target_id] == 0 {
103                    continue;
104                }
105
106                let interface_id = interface_counter;
107                interface_counter += 1;
108                self.ids[interface_id] = InterfaceInfo {
109                    source_id,
110                    target_id,
111                };
112                interfaces_id_from_cells[source_id * n_zones + target_id] = interface_id;
113                interfaces_id_from_cells[target_id * n_zones + source_id] = interface_id;
114
115                let (plane, direction_neighbors) = grid.get_interface_plane(source_id, target_id);
116
117                let origin = plane.origin.0;
118                let theta = origin[1].atan2(origin[0]);
119                self.interface_theta[interface_id] = theta;
120                planes.push(plane);
121                self.normal_axis[interface_id] = direction_neighbors;
122            }
123        }
124        self.global_id_from_interface =
125            self.count_interfaces_second_pass(geometry, &interfaces_id_from_cells);
126        self.fill_area(geometry, &planes);
127
128        // for i_interface in 0..self.n_facet.len() {
129        //     let total: f64 = self.area[i_interface].iter().sum();
130        //     if total > 0.0 {
131        //         let source = self.ids[i_interface].source_id;
132        //         let axis = self.normal_axis[i_interface];
133        //         let theoretical = grid.cell_surface(source, index_to_oriented(axis));
134        //         let factor = theoretical / total;
135        //         for a in self.area[i_interface].iter_mut() {
136        //             *a *= factor;
137        //         }
138        //     }
139        // }
140
141        // self.check_areas(geometry);
142    }
143    #[allow(unused)]
144    fn check_areas(&self, geometry: &CMGeometry) {
145        let grid = geometry.get_grid().unwrap();
146        let n_zones = geometry.n_zone();
147        for cell_id in 0..n_zones {
148            for axis_idx in 0..3 {
149                let axis = crate::grid::index_to_oriented(axis_idx);
150                let theoretical_area = grid.cell_surface(cell_id, axis);
151                let total_area: f64 = self
152                    .ids
153                    .iter()
154                    .enumerate()
155                    .filter(|(i, id)| {
156                        (id.source_id == cell_id || id.target_id == cell_id)
157                            && self.normal_axis[*i] == axis_idx
158                    })
159                    .map(|(i, _)| self.area[i].iter().sum::<f64>())
160                    .sum();
161
162                if is_area_mismatch(total_area, theoretical_area) {
163                    println!(
164                        "(areas): area incorrect : axis: {}\r\n -cell_id:{}\r\n -total_area: {}\r\n -theoretical: {}",
165                        axis_idx, cell_id, total_area, theoretical_area
166                    );
167                }
168            }
169        }
170    }
171
172    fn count_interfaces_second_pass(
173        &mut self,
174        geometry: &CMGeometry,
175        interfaces_id_from_cells: &[usize],
176    ) -> Vec<Vec<usize>> {
177        let mut tmp_element_counter = vec![0; self.n_facet.len()];
178        let mut global_id_from_interface: Vec<Vec<usize>> = vec![Vec::new(); self.n_facet.len()];
179        for (element_id, n_element) in global_id_from_interface.iter_mut().zip(self.n_facet.iter())
180        {
181            *element_id = vec![0; *n_element];
182        }
183        let grid = geometry.get_grid().unwrap();
184        let n_zones = geometry.n_zone();
185
186        for (vol_element_global_id, _, interface_cid_k, k_vertex) in geometry.interface_iter() {
187            // if k_vertex >= 1 {
188            for i in 0..k_vertex {
189                let cid_i = geometry
190                    .volume_elements
191                    .get_list_compartment_id(vol_element_global_id, i);
192                let neighbors = grid.are_cell_neighbor(cid_i, interface_cid_k);
193                if neighbors != NeighborDirection::NotNeighbors {
194                    let interface_global_id =
195                        interfaces_id_from_cells[cid_i * n_zones + interface_cid_k];
196                    let k_element = tmp_element_counter[interface_global_id];
197                    tmp_element_counter[interface_global_id] += 1;
198                    global_id_from_interface[interface_global_id][k_element] =
199                        vol_element_global_id;
200                }
201            }
202            // }
203        }
204
205        global_id_from_interface
206    }
207
208    fn fill_area(&mut self, geometry: &CMGeometry, planes: &[BoundedPlane]) {
209        //This is almost the same algorithm as fill for c_info struct (to compute volume of velem)
210        for (i, n) in self.n_facet.iter().enumerate() {
211            self.area[i].resize(*n, 0.);
212        }
213
214        let mut local_vertices: Vec<CartesianCoordinates> = Vec::new();
215
216        for (interface_id, cn_facet) in self.n_facet.iter().enumerate() {
217            let plane = &planes[interface_id];
218            for i_facet in 0..*cn_facet {
219                let volume_element_global_id = self.global_id_from_interface[interface_id][i_facet];
220
221                let (elem_type, n_vertex) = geometry
222                    .volume_elements
223                    .get_element_and_nvertex(volume_element_global_id);
224
225                geometry.fill_vertices(volume_element_global_id, n_vertex, &mut local_vertices);
226
227                //A curved face gives every element the tangent plane of its own position
228                let element_plane = is_curved_face(plane).then(|| {
229                    tangent_plane_at(
230                        plane,
231                        geometry.volume_elements.xyz[volume_element_global_id],
232                    )
233                });
234
235                let area = compute_intersection_area(
236                    &local_vertices,
237                    elem_type,
238                    element_plane.as_ref().unwrap_or(plane),
239                )
240                .expect("Area between element");
241
242                self.area[interface_id][i_facet] = area;
243            }
244        }
245    }
246}
247
248#[cfg(test)]
249mod test {
250    use super::*;
251
252    #[test]
253    fn test_matching_area_is_not_reported() {
254        assert!(!is_area_mismatch(10., 10.));
255        //Within the tolerance
256        assert!(!is_area_mismatch(10.5, 10.));
257    }
258
259    #[test]
260    fn test_mismatching_area_is_reported() {
261        assert!(is_area_mismatch(5., 10.));
262        assert!(is_area_mismatch(0., 10.));
263        assert!(is_area_mismatch(20., 10.));
264    }
265
266    ///A degenerate face divides by zero, which used to hide the mismatch behind a NaN
267    #[test]
268    fn test_area_without_geometric_surface() {
269        assert!(is_area_mismatch(1., 0.));
270        assert!(!is_area_mismatch(0., 0.));
271    }
272}