math_audio_bem/core/mesh/
element.rs1use ndarray::{Array1, Array2, array};
6
7use crate::core::types::{Element, ElementType};
8
9impl Element {
11 pub fn shape_functions(&self, s: f64, t: f64) -> Array1<f64> {
20 match self.element_type {
21 ElementType::Tri3 => {
22 array![1.0 - s - t, s, t]
27 }
28 ElementType::Quad4 => {
29 let s1 = 0.25 * (s + 1.0);
31 let s2 = 0.25 * (s - 1.0);
32 let t1 = t + 1.0;
33 let t2 = t - 1.0;
34 array![s1 * t1, -s2 * t1, s2 * t2, -s1 * t2]
35 }
36 }
37 }
38
39 pub fn shape_derivatives(&self, s: f64, t: f64) -> (Array1<f64>, Array1<f64>) {
41 match self.element_type {
42 ElementType::Tri3 => {
43 (array![-1.0, 1.0, 0.0], array![-1.0, 0.0, 1.0])
45 }
46 ElementType::Quad4 => {
47 let dns = array![
49 0.25 * (t + 1.0),
50 -0.25 * (t + 1.0),
51 0.25 * (t - 1.0),
52 -0.25 * (t - 1.0)
53 ];
54 let dnt = array![
56 0.25 * (s + 1.0),
57 0.25 * (1.0 - s),
58 0.25 * (s - 1.0),
59 -0.25 * (s + 1.0)
60 ];
61 (dns, dnt)
62 }
63 }
64 }
65
66 pub fn local_to_global(&self, s: f64, t: f64, nodes: &Array2<f64>) -> Array1<f64> {
68 let n = self.shape_functions(s, t);
69 let mut global = Array1::zeros(3);
70
71 for (i, &node_idx) in self.connectivity.iter().enumerate() {
72 for j in 0..3 {
73 global[j] += n[i] * nodes[[node_idx, j]];
74 }
75 }
76
77 global
78 }
79
80 pub fn normal_and_jacobian(&self, s: f64, t: f64, nodes: &Array2<f64>) -> (Array1<f64>, f64) {
84 let (dns, dnt) = self.shape_derivatives(s, t);
85
86 let mut dxds = Array1::zeros(3);
88 let mut dxdt = Array1::zeros(3);
89
90 for (i, &node_idx) in self.connectivity.iter().enumerate() {
91 for j in 0..3 {
92 dxds[j] += dns[i] * nodes[[node_idx, j]];
93 dxdt[j] += dnt[i] * nodes[[node_idx, j]];
94 }
95 }
96
97 let normal = cross_product(&dxds, &dxdt);
99 let length = normal.dot(&normal).sqrt();
100
101 if length > 1e-15 {
102 (normal / length, length)
103 } else {
104 (Array1::zeros(3), 0.0)
105 }
106 }
107}
108
109pub fn cross_product(a: &Array1<f64>, b: &Array1<f64>) -> Array1<f64> {
111 array![
112 a[1] * b[2] - a[2] * b[1],
113 a[2] * b[0] - a[0] * b[2],
114 a[0] * b[1] - a[1] * b[0]
115 ]
116}
117
118pub fn dot_product(a: &Array1<f64>, b: &Array1<f64>) -> f64 {
120 a.dot(b)
121}
122
123pub fn normalize(v: &Array1<f64>) -> (Array1<f64>, f64) {
125 let len = v.dot(v).sqrt();
126 if len > 1e-15 {
127 (v / len, len)
128 } else {
129 (Array1::zeros(3), 0.0)
130 }
131}
132
133pub fn compute_element_center(nodes: &Array2<f64>, connectivity: &[usize]) -> Array1<f64> {
135 let n = connectivity.len();
136 let mut center = Array1::zeros(3);
137
138 for &node_idx in connectivity {
139 for j in 0..3 {
140 center[j] += nodes[[node_idx, j]];
141 }
142 }
143
144 center / n as f64
145}
146
147pub fn compute_element_area(
149 nodes: &Array2<f64>,
150 connectivity: &[usize],
151 element_type: ElementType,
152) -> f64 {
153 match element_type {
154 ElementType::Tri3 => {
155 let p0 = nodes.row(connectivity[0]);
157 let p1 = nodes.row(connectivity[1]);
158 let p2 = nodes.row(connectivity[2]);
159
160 let v1 = array![p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]];
161 let v2 = array![p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]];
162
163 let cross = cross_product(&v1, &v2);
164 0.5 * cross.dot(&cross).sqrt()
165 }
166 ElementType::Quad4 => {
167 let p0 = nodes.row(connectivity[0]);
170 let p1 = nodes.row(connectivity[1]);
171 let p2 = nodes.row(connectivity[2]);
172 let p3 = nodes.row(connectivity[3]);
173
174 let v1 = array![p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]];
176 let v2 = array![p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]];
177 let cross1 = cross_product(&v1, &v2);
178 let area1 = 0.5 * cross1.dot(&cross1).sqrt();
179
180 let v3 = array![p3[0] - p0[0], p3[1] - p0[1], p3[2] - p0[2]];
182 let cross2 = cross_product(&v2, &v3);
183 let area2 = 0.5 * cross2.dot(&cross2).sqrt();
184
185 let v4 = array![p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]];
187 let v5 = array![p3[0] - p1[0], p3[1] - p1[1], p3[2] - p1[2]];
188 let cross3 = cross_product(&v4, &v5);
189 let area3 = 0.5 * cross3.dot(&cross3).sqrt();
190
191 let v6 = array![p0[0] - p1[0], p0[1] - p1[1], p0[2] - p1[2]];
193 let cross4 = cross_product(&v5, &v6);
194 let area4 = 0.5 * cross4.dot(&cross4).sqrt();
195
196 0.5 * (area1 + area2 + area3 + area4)
198 }
199 }
200}
201
202pub fn compute_element_normal(
204 nodes: &Array2<f64>,
205 connectivity: &[usize],
206 element_type: ElementType,
207) -> Array1<f64> {
208 match element_type {
209 ElementType::Tri3 => {
210 let p0 = nodes.row(connectivity[0]);
211 let p1 = nodes.row(connectivity[1]);
212 let p2 = nodes.row(connectivity[2]);
213
214 let v1 = array![p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]];
215 let v2 = array![p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]];
216
217 let cross = cross_product(&v1, &v2);
218 let (normal, _) = normalize(&cross);
219 normal
220 }
221 ElementType::Quad4 => {
222 let p0 = nodes.row(connectivity[0]);
224 let p2 = nodes.row(connectivity[2]);
225 let p1 = nodes.row(connectivity[1]);
226 let p3 = nodes.row(connectivity[3]);
227
228 let d1 = array![p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]];
229 let d2 = array![p3[0] - p1[0], p3[1] - p1[1], p3[2] - p1[2]];
230
231 let cross = cross_product(&d1, &d2);
232 let (normal, _) = normalize(&cross);
233 normal
234 }
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241 use crate::core::types::{BoundaryCondition, ElementProperty};
242 use num_complex::Complex64;
243
244 fn make_test_triangle() -> (Element, Array2<f64>) {
245 let nodes =
246 Array2::from_shape_vec((3, 3), vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0])
247 .unwrap();
248
249 let element = Element {
250 connectivity: vec![0, 1, 2],
251 element_type: ElementType::Tri3,
252 property: ElementProperty::Surface,
253 normal: array![0.0, 0.0, 1.0],
254 node_normals: Array2::zeros((3, 3)),
255 center: array![1.0 / 3.0, 1.0 / 3.0, 0.0],
256 area: 0.5,
257 boundary_condition: BoundaryCondition::Velocity(vec![Complex64::new(0.0, 0.0)]),
258 group: 0,
259 dof_addresses: vec![0],
260 };
261
262 (element, nodes)
263 }
264
265 #[test]
266 fn test_triangle_shape_functions() {
267 let (element, _nodes) = make_test_triangle();
268
269 let n = element.shape_functions(0.0, 0.0);
271 assert!((n[0] - 1.0).abs() < 1e-10);
272 assert!(n[1].abs() < 1e-10);
273 assert!(n[2].abs() < 1e-10);
274
275 let n = element.shape_functions(1.0, 0.0);
277 assert!(n[0].abs() < 1e-10);
278 assert!((n[1] - 1.0).abs() < 1e-10);
279 assert!(n[2].abs() < 1e-10);
280
281 let n = element.shape_functions(1.0 / 3.0, 1.0 / 3.0);
283 assert!((n[0] - 1.0 / 3.0).abs() < 1e-10);
284 assert!((n[1] - 1.0 / 3.0).abs() < 1e-10);
285 assert!((n[2] - 1.0 / 3.0).abs() < 1e-10);
286 }
287
288 #[test]
289 fn test_triangle_local_to_global() {
290 let (element, nodes) = make_test_triangle();
291
292 let p = element.local_to_global(0.0, 0.0, &nodes);
294 assert!((p[0] - 0.0).abs() < 1e-10);
295 assert!((p[1] - 0.0).abs() < 1e-10);
296
297 let p = element.local_to_global(1.0, 0.0, &nodes);
299 assert!((p[0] - 1.0).abs() < 1e-10);
300 assert!((p[1] - 0.0).abs() < 1e-10);
301
302 let p = element.local_to_global(0.0, 1.0, &nodes);
304 assert!((p[0] - 0.0).abs() < 1e-10);
305 assert!((p[1] - 1.0).abs() < 1e-10);
306 }
307
308 #[test]
309 fn test_triangle_normal() {
310 let (element, nodes) = make_test_triangle();
311
312 let (normal, jacobian) = element.normal_and_jacobian(0.5, 0.25, &nodes);
313
314 assert!((normal[0]).abs() < 1e-10);
316 assert!((normal[1]).abs() < 1e-10);
317 assert!((normal[2] - 1.0).abs() < 1e-10);
318
319 assert!((jacobian - 1.0).abs() < 1e-10);
321 }
322
323 #[test]
324 fn test_element_area() {
325 let nodes =
326 Array2::from_shape_vec((3, 3), vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0])
327 .unwrap();
328
329 let area = compute_element_area(&nodes, &[0, 1, 2], ElementType::Tri3);
330 assert!((area - 0.5).abs() < 1e-10);
331 }
332}