oxiphysics_gpu/
gpu_fem_assembly.rs1#[derive(Debug, Clone)]
21pub struct GpuFemMesh {
22 pub node_coords: Vec<f64>,
24 pub elements: Vec<usize>,
26 pub youngs_modulus: Vec<f64>,
28 pub area: Vec<f64>,
30 pub k_global: Vec<f64>,
32 pub displacements: Vec<f64>,
34 pub ext_forces: Vec<f64>,
36 pub residual: Vec<f64>,
38 pub dirichlet_flags: Vec<bool>,
40}
41
42impl GpuFemMesh {
43 pub fn new(node_coords: Vec<f64>, elements: Vec<usize>) -> Self {
48 let n_nodes = node_coords.len();
49 let n_elems = elements.len() / 2;
50 Self {
51 node_coords,
52 elements,
53 youngs_modulus: vec![1.0; n_elems],
54 area: vec![1.0; n_elems],
55 k_global: vec![0.0; n_nodes * n_nodes],
56 displacements: vec![0.0; n_nodes],
57 ext_forces: vec![0.0; n_nodes],
58 residual: vec![0.0; n_nodes],
59 dirichlet_flags: vec![false; n_nodes],
60 }
61 }
62
63 pub fn n_dofs(&self) -> usize {
65 self.node_coords.len()
66 }
67
68 pub fn n_elements(&self) -> usize {
70 self.elements.len() / 2
71 }
72}
73
74pub fn gpu_element_stiffness(mesh: &GpuFemMesh, e: usize) -> [f64; 4] {
82 let na = mesh.elements[e * 2];
83 let nb = mesh.elements[e * 2 + 1];
84 let xa = mesh.node_coords[na];
85 let xb = mesh.node_coords[nb];
86 let length = (xb - xa).abs();
87 if length < 1e-15 {
88 return [0.0; 4];
89 }
90 let ke = mesh.youngs_modulus[e] * mesh.area[e] / length;
91 [ke, -ke, -ke, ke]
92}
93
94pub fn gpu_assemble_global(mesh: &mut GpuFemMesh) {
98 let n_dofs = mesh.n_dofs();
99 mesh.k_global = vec![0.0; n_dofs * n_dofs];
100 let n_elem = mesh.n_elements();
101 for e in 0..n_elem {
102 let ke = gpu_element_stiffness(mesh, e);
103 let na = mesh.elements[e * 2];
104 let nb = mesh.elements[e * 2 + 1];
105 mesh.k_global[na * n_dofs + na] += ke[0];
107 mesh.k_global[na * n_dofs + nb] += ke[1];
108 mesh.k_global[nb * n_dofs + na] += ke[2];
109 mesh.k_global[nb * n_dofs + nb] += ke[3];
110 }
111}
112
113pub fn gpu_apply_dirichlet(mesh: &mut GpuFemMesh) {
120 let n = mesh.n_dofs();
121 for i in 0..n {
122 if mesh.dirichlet_flags[i] {
123 for j in 0..n {
125 mesh.k_global[i * n + j] = 0.0;
126 }
127 for j in 0..n {
129 mesh.k_global[j * n + i] = 0.0;
130 }
131 mesh.k_global[i * n + i] = 1.0;
133 mesh.ext_forces[i] = 0.0;
135 }
136 }
137}
138
139pub fn gpu_residual(mesh: &mut GpuFemMesh) {
143 let n = mesh.n_dofs();
144 for i in 0..n {
145 let mut ku_i = 0.0f64;
146 for j in 0..n {
147 ku_i += mesh.k_global[i * n + j] * mesh.displacements[j];
148 }
149 mesh.residual[i] = mesh.ext_forces[i] - ku_i;
150 }
151}
152
153pub fn gpu_dot_product(a: &[f64], b: &[f64]) -> f64 {
157 a.iter().zip(b.iter()).map(|(ai, bi)| ai * bi).sum()
158}
159
160pub fn gpu_all_element_stiffness(mesh: &GpuFemMesh) -> Vec<[f64; 4]> {
165 (0..mesh.n_elements())
166 .map(|e| gpu_element_stiffness(mesh, e))
167 .collect()
168}
169
170#[cfg(test)]
173mod tests {
174 use super::*;
175
176 fn make_bar_mesh() -> GpuFemMesh {
178 let coords = vec![0.0, 1.0, 2.0];
179 let elems = vec![0, 1, 1, 2];
180 GpuFemMesh::new(coords, elems)
181 }
182
183 #[test]
184 fn test_new_mesh_n_dofs() {
185 let m = make_bar_mesh();
186 assert_eq!(m.n_dofs(), 3);
187 }
188
189 #[test]
190 fn test_new_mesh_n_elements() {
191 let m = make_bar_mesh();
192 assert_eq!(m.n_elements(), 2);
193 }
194
195 #[test]
196 fn test_new_mesh_default_youngs() {
197 let m = make_bar_mesh();
198 assert!((m.youngs_modulus[0] - 1.0).abs() < 1e-12);
199 }
200
201 #[test]
202 fn test_element_stiffness_unit_bar() {
203 let m = make_bar_mesh();
205 let ke = gpu_element_stiffness(&m, 0);
206 assert!((ke[0] - 1.0).abs() < 1e-12);
207 assert!((ke[1] + 1.0).abs() < 1e-12);
208 assert!((ke[2] + 1.0).abs() < 1e-12);
209 assert!((ke[3] - 1.0).abs() < 1e-12);
210 }
211
212 #[test]
213 fn test_element_stiffness_scaled() {
214 let mut m = make_bar_mesh();
216 m.youngs_modulus[0] = 2.0;
217 m.area[0] = 3.0;
218 let ke = gpu_element_stiffness(&m, 0);
219 assert!((ke[0] - 6.0).abs() < 1e-12);
220 }
221
222 #[test]
223 fn test_element_stiffness_zero_length() {
224 let coords = vec![0.0, 0.0];
225 let elems = vec![0, 1];
226 let m = GpuFemMesh::new(coords, elems);
227 let ke = gpu_element_stiffness(&m, 0);
228 assert_eq!(ke, [0.0; 4]);
229 }
230
231 #[test]
232 fn test_assemble_global_dimensions() {
233 let mut m = make_bar_mesh();
234 gpu_assemble_global(&mut m);
235 assert_eq!(m.k_global.len(), 9); }
237
238 #[test]
239 fn test_assemble_global_diagonal_positive() {
240 let mut m = make_bar_mesh();
241 gpu_assemble_global(&mut m);
242 let n = m.n_dofs();
243 for i in 0..n {
244 assert!(m.k_global[i * n + i] >= 0.0);
245 }
246 }
247
248 #[test]
249 fn test_assemble_global_symmetric() {
250 let mut m = make_bar_mesh();
251 gpu_assemble_global(&mut m);
252 let n = m.n_dofs();
253 for i in 0..n {
254 for j in 0..n {
255 assert!(
256 (m.k_global[i * n + j] - m.k_global[j * n + i]).abs() < 1e-12,
257 "K[{i},{j}] != K[{j},{i}]"
258 );
259 }
260 }
261 }
262
263 #[test]
264 fn test_assemble_global_row_sum_zero() {
265 let mut m = make_bar_mesh();
267 gpu_assemble_global(&mut m);
268 let n = m.n_dofs();
269 for i in 0..n {
270 let row_sum: f64 = (0..n).map(|j| m.k_global[i * n + j]).sum();
271 assert!(row_sum.abs() < 1e-10, "row {i} sum = {row_sum}");
272 }
273 }
274
275 #[test]
276 fn test_apply_dirichlet_zeroes_row() {
277 let mut m = make_bar_mesh();
278 gpu_assemble_global(&mut m);
279 m.dirichlet_flags[0] = true;
280 gpu_apply_dirichlet(&mut m);
281 let n = m.n_dofs();
282 for j in 1..n {
284 assert!((m.k_global[j]).abs() < 1e-12);
285 }
286 assert!((m.k_global[0]).abs() - 1.0 < 1e-12);
288 }
289
290 #[test]
291 fn test_apply_dirichlet_zeroes_column() {
292 let mut m = make_bar_mesh();
293 gpu_assemble_global(&mut m);
294 m.dirichlet_flags[0] = true;
295 gpu_apply_dirichlet(&mut m);
296 let n = m.n_dofs();
297 for i in 1..n {
298 assert!((m.k_global[i * n]).abs() < 1e-12);
299 }
300 }
301
302 #[test]
303 fn test_apply_dirichlet_zeroes_rhs() {
304 let mut m = make_bar_mesh();
305 gpu_assemble_global(&mut m);
306 m.ext_forces[0] = 99.0;
307 m.dirichlet_flags[0] = true;
308 gpu_apply_dirichlet(&mut m);
309 assert!((m.ext_forces[0]).abs() < 1e-12);
310 }
311
312 #[test]
313 fn test_residual_zero_displacement() {
314 let mut m = make_bar_mesh();
315 gpu_assemble_global(&mut m);
316 m.ext_forces[2] = 1.0;
317 gpu_residual(&mut m);
319 assert!((m.residual[2] - 1.0).abs() < 1e-12);
320 }
321
322 #[test]
323 fn test_residual_equilibrium() {
324 let mut m = make_bar_mesh();
326 gpu_assemble_global(&mut m);
327 m.dirichlet_flags[0] = true;
328 gpu_apply_dirichlet(&mut m);
329 m.ext_forces[2] = 1.0;
330 m.displacements = vec![0.0, 1.0, 2.0];
335 gpu_residual(&mut m);
336 for &r in &m.residual {
338 assert!(r.is_finite());
339 }
340 }
341
342 #[test]
343 fn test_gpu_dot_product_basic() {
344 let a = [1.0, 2.0, 3.0];
345 let b = [4.0, 5.0, 6.0];
346 assert!((gpu_dot_product(&a, &b) - 32.0).abs() < 1e-12);
347 }
348
349 #[test]
350 fn test_gpu_dot_product_empty() {
351 assert!((gpu_dot_product(&[], &[])).abs() < 1e-12);
352 }
353
354 #[test]
355 fn test_gpu_dot_product_unit_vectors() {
356 let a = [1.0, 0.0, 0.0];
357 let b = [0.0, 1.0, 0.0];
358 assert!((gpu_dot_product(&a, &b)).abs() < 1e-12);
359 }
360
361 #[test]
362 fn test_gpu_all_element_stiffness_count() {
363 let m = make_bar_mesh();
364 let all_ke = gpu_all_element_stiffness(&m);
365 assert_eq!(all_ke.len(), m.n_elements());
366 }
367
368 #[test]
369 fn test_gpu_all_element_stiffness_values() {
370 let m = make_bar_mesh();
371 let all_ke = gpu_all_element_stiffness(&m);
372 assert_eq!(all_ke[0], all_ke[1]);
374 }
375
376 #[test]
377 fn test_fem_mesh_clone() {
378 let m = make_bar_mesh();
379 let m2 = m.clone();
380 assert_eq!(m2.n_dofs(), 3);
381 }
382
383 #[test]
384 fn test_fem_mesh_debug() {
385 let m = make_bar_mesh();
386 let s = format!("{m:?}");
387 assert!(s.contains("GpuFemMesh"));
388 }
389
390 #[test]
391 fn test_assemble_then_apply_dirichlet_both_ends() {
392 let mut m = make_bar_mesh();
393 gpu_assemble_global(&mut m);
394 m.dirichlet_flags[0] = true;
395 m.dirichlet_flags[2] = true;
396 gpu_apply_dirichlet(&mut m);
397 let n = m.n_dofs();
398 assert!((m.k_global[0] - 1.0).abs() < 1e-12);
400 assert!((m.k_global[2 * n + 2] - 1.0).abs() < 1e-12);
401 }
402
403 #[test]
404 fn test_residual_updates_all_entries() {
405 let mut m = make_bar_mesh();
406 gpu_assemble_global(&mut m);
407 m.ext_forces = vec![1.0, 0.0, -1.0];
408 gpu_residual(&mut m);
409 assert_eq!(m.residual.len(), m.n_dofs());
410 }
411}