1use 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]);
169 let p1 = nodes.row(connectivity[1]);
170 let p2 = nodes.row(connectivity[2]);
171 let p3 = nodes.row(connectivity[3]);
172
173 let v1 = array![p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]];
175 let v2 = array![p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]];
176 let cross1 = cross_product(&v1, &v2);
177 let area1 = 0.5 * cross1.dot(&cross1).sqrt();
178
179 let v3 = array![p3[0] - p0[0], p3[1] - p0[1], p3[2] - p0[2]];
181 let cross2 = cross_product(&v2, &v3);
182 let area2 = 0.5 * cross2.dot(&cross2).sqrt();
183
184 area1 + area2
185 }
186 }
187}
188
189pub fn compute_element_normal(
191 nodes: &Array2<f64>,
192 connectivity: &[usize],
193 element_type: ElementType,
194) -> Array1<f64> {
195 match element_type {
196 ElementType::Tri3 => {
197 let p0 = nodes.row(connectivity[0]);
198 let p1 = nodes.row(connectivity[1]);
199 let p2 = nodes.row(connectivity[2]);
200
201 let v1 = array![p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]];
202 let v2 = array![p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]];
203
204 let cross = cross_product(&v1, &v2);
205 let (normal, _) = normalize(&cross);
206 normal
207 }
208 ElementType::Quad4 => {
209 let p0 = nodes.row(connectivity[0]);
211 let p2 = nodes.row(connectivity[2]);
212 let p1 = nodes.row(connectivity[1]);
213 let p3 = nodes.row(connectivity[3]);
214
215 let d1 = array![p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]];
216 let d2 = array![p3[0] - p1[0], p3[1] - p1[1], p3[2] - p1[2]];
217
218 let cross = cross_product(&d1, &d2);
219 let (normal, _) = normalize(&cross);
220 normal
221 }
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228 use crate::core::types::{BoundaryCondition, ElementProperty};
229 use num_complex::Complex64;
230
231 fn make_test_triangle() -> (Element, Array2<f64>) {
232 let nodes =
233 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])
234 .unwrap();
235
236 let element = Element {
237 connectivity: vec![0, 1, 2],
238 element_type: ElementType::Tri3,
239 property: ElementProperty::Surface,
240 normal: array![0.0, 0.0, 1.0],
241 node_normals: Array2::zeros((3, 3)),
242 center: array![1.0 / 3.0, 1.0 / 3.0, 0.0],
243 area: 0.5,
244 boundary_condition: BoundaryCondition::Velocity(vec![Complex64::new(0.0, 0.0)]),
245 group: 0,
246 dof_addresses: vec![0],
247 };
248
249 (element, nodes)
250 }
251
252 #[test]
253 fn test_triangle_shape_functions() {
254 let (element, _nodes) = make_test_triangle();
255
256 let n = element.shape_functions(0.0, 0.0);
258 assert!((n[0] - 1.0).abs() < 1e-10);
259 assert!(n[1].abs() < 1e-10);
260 assert!(n[2].abs() < 1e-10);
261
262 let n = element.shape_functions(1.0, 0.0);
264 assert!(n[0].abs() < 1e-10);
265 assert!((n[1] - 1.0).abs() < 1e-10);
266 assert!(n[2].abs() < 1e-10);
267
268 let n = element.shape_functions(1.0 / 3.0, 1.0 / 3.0);
270 assert!((n[0] - 1.0 / 3.0).abs() < 1e-10);
271 assert!((n[1] - 1.0 / 3.0).abs() < 1e-10);
272 assert!((n[2] - 1.0 / 3.0).abs() < 1e-10);
273 }
274
275 #[test]
276 fn test_triangle_local_to_global() {
277 let (element, nodes) = make_test_triangle();
278
279 let p = element.local_to_global(0.0, 0.0, &nodes);
281 assert!((p[0] - 0.0).abs() < 1e-10);
282 assert!((p[1] - 0.0).abs() < 1e-10);
283
284 let p = element.local_to_global(1.0, 0.0, &nodes);
286 assert!((p[0] - 1.0).abs() < 1e-10);
287 assert!((p[1] - 0.0).abs() < 1e-10);
288
289 let p = element.local_to_global(0.0, 1.0, &nodes);
291 assert!((p[0] - 0.0).abs() < 1e-10);
292 assert!((p[1] - 1.0).abs() < 1e-10);
293 }
294
295 #[test]
296 fn test_triangle_normal() {
297 let (element, nodes) = make_test_triangle();
298
299 let (normal, jacobian) = element.normal_and_jacobian(0.5, 0.25, &nodes);
300
301 assert!((normal[0]).abs() < 1e-10);
303 assert!((normal[1]).abs() < 1e-10);
304 assert!((normal[2] - 1.0).abs() < 1e-10);
305
306 assert!((jacobian - 1.0).abs() < 1e-10);
308 }
309
310 #[test]
311 fn test_element_area() {
312 let nodes =
313 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])
314 .unwrap();
315
316 let area = compute_element_area(&nodes, &[0, 1, 2], ElementType::Tri3);
317 assert!((area - 0.5).abs() < 1e-10);
318 }
319}