xref: /libCEED/examples/rust/ex2-surface/src/main.rs (revision 9fb324b05a2e3bbc004e33421e78d8911447dfe7)
1 // Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and other CEED contributors.
2 // All Rights Reserved. See the top-level LICENSE and NOTICE files for details.
3 //
4 // SPDX-License-Identifier: BSD-2-Clause
5 //
6 // This file is part of CEED:  http://github.com/ceed
7 //
8 //                             libCEED Example 2
9 //
10 // This example illustrates a simple usage of libCEED to compute the surface
11 // area of a 3D body using matrix-free application of a diffusion operator.
12 // Arbitrary mesh and solution degrees in 1D, 2D and 3D are supported from the
13 // same code.
14 //
15 // The example has no dependencies, and is designed to be self-contained. For
16 // additional examples that use external discretization libraries (MFEM, PETSc,
17 // etc.) see the subdirectories in libceed/examples.
18 //
19 // All libCEED objects use a Ceed device object constructed based on a command
20 // line argument (-ceed).
21 
22 use clap::Parser;
23 use libceed::{prelude::*, Ceed};
24 mod opt;
25 mod transform;
26 
27 // ----------------------------------------------------------------------------
28 // Example 2
29 // ----------------------------------------------------------------------------
30 fn main() -> libceed::Result<()> {
31     let options = opt::Opt::parse();
32     example_2(options)
33 }
34 
35 fn example_2(options: opt::Opt) -> libceed::Result<()> {
36     // Process command line arguments
37     let opt::Opt {
38         ceed_spec,
39         dim,
40         mesh_degree,
41         solution_degree,
42         num_qpts,
43         problem_size_requested,
44         test,
45         quiet,
46         gallery,
47     } = options;
48     assert!(dim >= 1 && dim <= 3);
49     assert!(mesh_degree >= 1);
50     assert!(solution_degree >= 1);
51     assert!(num_qpts >= 1);
52     let ncomp_x = dim;
53     let problem_size: i64;
54     if problem_size_requested < 0 {
55         problem_size = if test {
56             16 * 16 * (dim * dim) as i64
57         } else {
58             256 * 1024
59         };
60     } else {
61         problem_size = problem_size_requested;
62     }
63 
64     // Summary output
65     if !quiet {
66         println!("Selected options: [command line option] : <current value>");
67         println!("    Ceed specification [-c] : {}", ceed_spec);
68         println!("    Mesh dimension     [-d] : {}", dim);
69         println!("    Mesh degree        [-m] : {}", mesh_degree);
70         println!("    Solution degree    [-p] : {}", solution_degree);
71         println!("    Num. 1D quadr. pts [-q] : {}", num_qpts);
72         println!("    Approx. # unknowns [-s] : {}", problem_size);
73         println!(
74             "    QFunction source   [-g] : {}",
75             if gallery { "gallery" } else { "user closure" }
76         );
77     }
78 
79     // Initalize ceed context
80     let ceed = Ceed::init(&ceed_spec);
81 
82     // Mesh and solution bases
83     let basis_mesh =
84         ceed.basis_tensor_H1_Lagrange(dim, ncomp_x, mesh_degree + 1, num_qpts, QuadMode::Gauss)?;
85     let basis_solution =
86         ceed.basis_tensor_H1_Lagrange(dim, 1, solution_degree + 1, num_qpts, QuadMode::Gauss)?;
87 
88     // Determine mesh size from approximate problem size
89     let num_xyz = mesh::cartesian_mesh_size(dim, solution_degree, problem_size);
90     if !quiet {
91         print!("\nMesh size                   : nx = {}", num_xyz[0]);
92         if dim > 1 {
93             print!(", ny = {}", num_xyz[1]);
94         }
95         if dim > 2 {
96             print!(", nz = {}", num_xyz[2]);
97         }
98         print!("\n");
99     }
100 
101     // Build ElemRestriction objects describing the mesh and solution discrete
102     // representations
103     let (rstr_mesh, _) =
104         mesh::build_cartesian_restriction(&ceed, dim, num_xyz, mesh_degree, ncomp_x, num_qpts)?;
105     let (_, rstr_qdata) = mesh::build_cartesian_restriction(
106         &ceed,
107         dim,
108         num_xyz,
109         solution_degree,
110         dim * (dim + 1) / 2,
111         num_qpts,
112     )?;
113 
114     let (rstr_solution, _) =
115         mesh::build_cartesian_restriction(&ceed, dim, num_xyz, solution_degree, 1, num_qpts)?;
116     let mesh_size = rstr_mesh.lvector_size();
117     let solution_size = rstr_solution.lvector_size();
118     if !quiet {
119         println!("Number of mesh nodes        : {}", mesh_size / dim);
120         println!("Number of solution nodes    : {}", solution_size);
121     }
122 
123     // Create a Vector with the mesh coordinates
124     let mut mesh_coords = mesh::cartesian_mesh_coords(&ceed, dim, num_xyz, mesh_degree, mesh_size)?;
125 
126     // Apply a transformation to the mesh coordinates
127     let exact_area = transform::transform_mesh_coordinates(dim, &mut mesh_coords)?;
128 
129     // QFunction that builds the quadrature data for the diff operator
130     // -- QFunction from user closure
131     let build_diff = move |[jacobian, weights, ..]: QFunctionInputs,
132                            [qdata, ..]: QFunctionOutputs| {
133         // Build quadrature data
134         match dim {
135             1 => qdata
136                 .iter_mut()
137                 .zip(jacobian.iter().zip(weights.iter()))
138                 .for_each(|(qdata, (j, weight))| *qdata = weight / j),
139             2 => {
140                 let q = qdata.len() / 3;
141                 for i in 0..q {
142                     let j11 = jacobian[i + q * 0];
143                     let j21 = jacobian[i + q * 1];
144                     let j12 = jacobian[i + q * 2];
145                     let j22 = jacobian[i + q * 3];
146                     let qw = weights[i] / (j11 * j22 - j21 * j12);
147                     qdata[i + q * 0] = qw * (j12 * j12 + j22 * j22);
148                     qdata[i + q * 1] = qw * (j11 * j11 + j21 * j21);
149                     qdata[i + q * 2] = -qw * (j11 * j12 + j21 * j22);
150                 }
151             }
152             3 => {
153                 let q = qdata.len() / 6;
154                 for i in 0..q {
155                     let mut a = [0.0; 9];
156                     for j in 0..3 {
157                         for k in 0..3 {
158                             a[k * 3 + j] = jacobian[i + q * ((j + 1) % 3 + 3 * ((k + 1) % 3))]
159                                 * jacobian[i + q * ((j + 2) % 3 + 3 * ((k + 2) % 3))]
160                                 - jacobian[i + q * ((j + 1) % 3 + 3 * ((k + 2) % 3))]
161                                     * jacobian[i + q * ((j + 2) % 3 + 3 * ((k + 1) % 3))];
162                         }
163                     }
164                     let qw = weights[i]
165                         / (jacobian[i + q * 0] * a[0 * 3 + 0]
166                             + jacobian[i + q * 1] * a[0 * 3 + 1]
167                             + jacobian[i + q * 2] * a[0 * 3 + 2]);
168                     qdata[i + q * 0] = qw
169                         * (a[0 * 3 + 0] * a[0 * 3 + 0]
170                             + a[0 * 3 + 1] * a[0 * 3 + 1]
171                             + a[0 * 3 + 2] * a[0 * 3 + 2]);
172                     qdata[i + q * 1] = qw
173                         * (a[1 * 3 + 0] * a[1 * 3 + 0]
174                             + a[1 * 3 + 1] * a[1 * 3 + 1]
175                             + a[1 * 3 + 2] * a[1 * 3 + 2]);
176                     qdata[i + q * 2] = qw
177                         * (a[2 * 3 + 0] * a[2 * 3 + 0]
178                             + a[2 * 3 + 1] * a[2 * 3 + 1]
179                             + a[2 * 3 + 2] * a[2 * 3 + 2]);
180                     qdata[i + q * 3] = qw
181                         * (a[1 * 3 + 0] * a[2 * 3 + 0]
182                             + a[1 * 3 + 1] * a[2 * 3 + 1]
183                             + a[1 * 3 + 2] * a[2 * 3 + 2]);
184                     qdata[i + q * 4] = qw
185                         * (a[0 * 3 + 0] * a[2 * 3 + 0]
186                             + a[0 * 3 + 1] * a[2 * 3 + 1]
187                             + a[0 * 3 + 2] * a[2 * 3 + 2]);
188                     qdata[i + q * 5] = qw
189                         * (a[0 * 3 + 0] * a[1 * 3 + 0]
190                             + a[0 * 3 + 1] * a[1 * 3 + 1]
191                             + a[0 * 3 + 2] * a[1 * 3 + 2]);
192                 }
193             }
194             _ => unreachable!(),
195         };
196 
197         // Return clean error code
198         0
199     };
200     let qf_build_closure = ceed
201         .q_function_interior(1, Box::new(build_diff))?
202         .input("dx", ncomp_x * dim, EvalMode::Grad)?
203         .input("weights", 1, EvalMode::Weight)?
204         .output("qdata", dim * (dim + 1) / 2, EvalMode::None)?;
205     // -- QFunction from gallery
206     let qf_build_named = {
207         let name = format!("Poisson{}DBuild", dim);
208         ceed.q_function_interior_by_name(&name)?
209     };
210     // -- QFunction for use with Operator
211     let qf_build = if gallery {
212         QFunctionOpt::SomeQFunctionByName(&qf_build_named)
213     } else {
214         QFunctionOpt::SomeQFunction(&qf_build_closure)
215     };
216 
217     // Operator that build the quadrature data for the diff operator
218     let op_build = ceed
219         .operator(qf_build, QFunctionOpt::None, QFunctionOpt::None)?
220         .name("build qdata")?
221         .field("dx", &rstr_mesh, &basis_mesh, VectorOpt::Active)?
222         .field(
223             "weights",
224             ElemRestrictionOpt::None,
225             &basis_mesh,
226             VectorOpt::None,
227         )?
228         .field("qdata", &rstr_qdata, BasisOpt::None, VectorOpt::Active)?
229         .check()?;
230 
231     // Compute the quadrature data for the diff operator
232     let elem_qpts = num_qpts.pow(dim as u32);
233     let num_elem: usize = num_xyz.iter().take(dim).product();
234     let mut qdata = ceed.vector(num_elem * elem_qpts * dim * (dim + 1) / 2)?;
235     op_build.apply(&mesh_coords, &mut qdata)?;
236 
237     // QFunction that applies the diff operator
238     // -- QFunction from user closure
239     let apply_diff = move |[ug, qdata, ..]: QFunctionInputs, [vg, ..]: QFunctionOutputs| {
240         // Apply diffusion operator
241         match dim {
242             1 => vg
243                 .iter_mut()
244                 .zip(ug.iter().zip(qdata.iter()))
245                 .for_each(|(vg, (ug, w))| *vg = ug * w),
246             2 => {
247                 let q = qdata.len() / 3;
248                 for i in 0..q {
249                     let du = [ug[i + q * 0], ug[i + q * 1]];
250                     let dxdxdxdx_t = [
251                         [qdata[i + 0 * q], qdata[i + 2 * q]],
252                         [qdata[i + 2 * q], qdata[i + 1 * q]],
253                     ];
254                     for j in 0..2 {
255                         vg[i + j * q] = du[0] * dxdxdxdx_t[0][j] + du[1] * dxdxdxdx_t[1][j];
256                     }
257                 }
258             }
259             3 => {
260                 let q = qdata.len() / 6;
261                 for i in 0..q {
262                     let du = [ug[i + q * 0], ug[i + q * 1], ug[i + q * 2]];
263                     let dxdxdxdx_t = [
264                         [qdata[i + 0 * q], qdata[i + 5 * q], qdata[i + 4 * q]],
265                         [qdata[i + 5 * q], qdata[i + 1 * q], qdata[i + 3 * q]],
266                         [qdata[i + 4 * q], qdata[i + 3 * q], qdata[i + 2 * q]],
267                     ];
268                     for j in 0..3 {
269                         vg[i + j * q] = du[0] * dxdxdxdx_t[0][j]
270                             + du[1] * dxdxdxdx_t[1][j]
271                             + du[2] * dxdxdxdx_t[2][j];
272                     }
273                 }
274             }
275             _ => unreachable!(),
276         };
277 
278         // Return clean error code
279         0
280     };
281     let qf_diff_closure = ceed
282         .q_function_interior(1, Box::new(apply_diff))?
283         .input("du", dim, EvalMode::Grad)?
284         .input("qdata", dim * (dim + 1) / 2, EvalMode::None)?
285         .output("dv", dim, EvalMode::Grad)?;
286     // -- QFunction from gallery
287     let qf_diff_named = {
288         let name = format!("Poisson{}DApply", dim);
289         ceed.q_function_interior_by_name(&name)?
290     };
291     // -- QFunction for use with Operator
292     let qf_diff = if gallery {
293         QFunctionOpt::SomeQFunctionByName(&qf_diff_named)
294     } else {
295         QFunctionOpt::SomeQFunction(&qf_diff_closure)
296     };
297 
298     // Diff Operator
299     let op_diff = ceed
300         .operator(qf_diff, QFunctionOpt::None, QFunctionOpt::None)?
301         .name("Poisson")?
302         .field("du", &rstr_solution, &basis_solution, VectorOpt::Active)?
303         .field("qdata", &rstr_qdata, BasisOpt::None, &qdata)?
304         .field("dv", &rstr_solution, &basis_solution, VectorOpt::Active)?
305         .check()?;
306 
307     // Solution vectors
308     let mut u = ceed.vector(solution_size)?;
309     let mut v = ceed.vector(solution_size)?;
310 
311     // Initialize u with sum of node coordinates
312     let coords = mesh_coords.view()?;
313     u.set_value(0.0)?;
314     for (i, u) in u.view_mut()?.iter_mut().enumerate() {
315         *u = (0..dim).map(|d| coords[i + d * solution_size]).sum();
316     }
317 
318     // Apply the diff operator
319     op_diff.apply(&u, &mut v)?;
320 
321     // Compute the mesh surface area
322     let area: Scalar = v.view()?.iter().map(|v| (*v).abs()).sum();
323 
324     // Output results
325     if !quiet {
326         println!("Exact mesh surface area     : {:.12}", exact_area);
327         println!("Computed mesh surface_area  : {:.12}", area);
328         println!("Surface area error          : {:.12e}", area - exact_area);
329     }
330     let tolerance = match dim {
331         1 => 10000.0 * libceed::EPSILON,
332         _ => 1E-1,
333     };
334     let error = (area - exact_area).abs();
335     if error > tolerance {
336         println!("Volume error too large: {:.12e}", error);
337         return Err(libceed::Error {
338             message: format!(
339                 "Volume error too large - expected: {:.12e}, actual: {:.12e}",
340                 tolerance, error
341             ),
342         });
343     }
344     Ok(())
345 }
346 
347 // ----------------------------------------------------------------------------
348 // Tests
349 // ----------------------------------------------------------------------------
350 #[cfg(test)]
351 mod tests {
352     use super::*;
353 
354     #[test]
355     fn example_2_1d() {
356         let options = opt::Opt {
357             ceed_spec: "/cpu/self/ref/serial".to_string(),
358             dim: 1,
359             mesh_degree: 4,
360             solution_degree: 4,
361             num_qpts: 6,
362             problem_size_requested: -1,
363             test: true,
364             quiet: true,
365             gallery: false,
366         };
367         assert!(example_2(options).is_ok());
368     }
369 
370     #[test]
371     fn example_2_2d() {
372         let options = opt::Opt {
373             ceed_spec: "/cpu/self/ref/serial".to_string(),
374             dim: 2,
375             mesh_degree: 4,
376             solution_degree: 4,
377             num_qpts: 6,
378             problem_size_requested: -1,
379             test: true,
380             quiet: true,
381             gallery: false,
382         };
383         assert!(example_2(options).is_ok());
384     }
385 
386     #[test]
387     fn example_2_3d() {
388         let options = opt::Opt {
389             ceed_spec: "/cpu/self/ref/serial".to_string(),
390             dim: 3,
391             mesh_degree: 4,
392             solution_degree: 4,
393             num_qpts: 6,
394             problem_size_requested: -1,
395             test: true,
396             quiet: false,
397             gallery: false,
398         };
399         assert!(example_2(options).is_ok());
400     }
401 
402     #[test]
403     fn example_2_1d_gallery() {
404         let options = opt::Opt {
405             ceed_spec: "/cpu/self/ref/serial".to_string(),
406             dim: 1,
407             mesh_degree: 4,
408             solution_degree: 4,
409             num_qpts: 6,
410             problem_size_requested: -1,
411             test: true,
412             quiet: true,
413             gallery: true,
414         };
415         assert!(example_2(options).is_ok());
416     }
417 
418     #[test]
419     fn example_2_2d_gallery() {
420         let options = opt::Opt {
421             ceed_spec: "/cpu/self/ref/serial".to_string(),
422             dim: 2,
423             mesh_degree: 4,
424             solution_degree: 4,
425             num_qpts: 6,
426             problem_size_requested: -1,
427             test: true,
428             quiet: true,
429             gallery: true,
430         };
431         assert!(example_2(options).is_ok());
432     }
433 
434     #[test]
435     fn example_2_3d_gallery() {
436         let options = opt::Opt {
437             ceed_spec: "/cpu/self/ref/serial".to_string(),
438             dim: 3,
439             mesh_degree: 4,
440             solution_degree: 4,
441             num_qpts: 6,
442             problem_size_requested: -1,
443             test: true,
444             quiet: true,
445             gallery: true,
446         };
447         assert!(example_2(options).is_ok());
448     }
449 }
450 
451 // ----------------------------------------------------------------------------
452