xref: /libCEED/examples/rust/ex1-volume/src/main.rs (revision bf55b007047d3d1ea1227af6b83a1b17e380b530)
1 // Copyright (c) 2017-2025, 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 1
9 //
10 // This example illustrates a simple usage of libCEED to compute the volume of a
11 // 3D body using matrix-free application of a mass operator.  Arbitrary mesh and
12 // solution orders in 1D, 2D and 3D are supported from the same code.
13 //
14 // The example has no dependencies, and is designed to be self-contained. For
15 // additional examples that use external discretization libraries (MFEM, PETSc,
16 // etc.) see the subdirectories in libceed/examples.
17 //
18 // All libCEED objects use a Ceed device object constructed based on a command
19 // line argument (-ceed).
20 
21 use clap::Parser;
22 use libceed::{
23     BasisOpt, Ceed, ElemRestrictionOpt, QFunctionInputs, QFunctionOpt, QFunctionOutputs, VectorOpt,
24 };
25 mod opt;
26 mod transform;
27 
28 // ----------------------------------------------------------------------------
29 // Example 1
30 // ----------------------------------------------------------------------------
31 fn main() -> libceed::Result<()> {
32     let options = opt::Opt::parse();
33     example_1(options)
34 }
35 
36 fn example_1(options: opt::Opt) -> libceed::Result<()> {
37     // Process command line arguments
38     let opt::Opt {
39         ceed_spec,
40         dim,
41         mesh_degree,
42         solution_degree,
43         num_qpts,
44         problem_size_requested,
45         test,
46         quiet,
47         gallery,
48     } = options;
49     assert!((1..=3).contains(&dim));
50     assert!(mesh_degree >= 1);
51     assert!(solution_degree >= 1);
52     assert!(num_qpts >= 1);
53     let ncomp_x = dim;
54     let problem_size: i64 = if problem_size_requested < 0 {
55         if test {
56             8 * 16
57         } else {
58             256 * 1024
59         }
60     } else {
61         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 = ceed.basis_tensor_H1_Lagrange(
84         dim,
85         ncomp_x,
86         mesh_degree + 1,
87         num_qpts,
88         libceed::QuadMode::Gauss,
89     )?;
90     let basis_solution = ceed.basis_tensor_H1_Lagrange(
91         dim,
92         1,
93         solution_degree + 1,
94         num_qpts,
95         libceed::QuadMode::Gauss,
96     )?;
97 
98     // Determine mesh size from approximate problem size
99     let num_xyz = mesh::cartesian_mesh_size(dim, solution_degree, problem_size);
100     if !quiet {
101         print!("\nMesh size                   : nx = {}", num_xyz[0]);
102         if dim > 1 {
103             print!(", ny = {}", num_xyz[1]);
104         }
105         if dim > 2 {
106             print!(", nz = {}", num_xyz[2]);
107         }
108         println!();
109     }
110 
111     // Build ElemRestriction objects describing the mesh and solution discrete
112     // representations
113     let (rstr_mesh, _) =
114         mesh::build_cartesian_restriction(&ceed, dim, num_xyz, mesh_degree, ncomp_x, num_qpts)?;
115     let (rstr_solution, rstr_qdata) =
116         mesh::build_cartesian_restriction(&ceed, dim, num_xyz, solution_degree, 1, num_qpts)?;
117     let mesh_size = rstr_mesh.lvector_size();
118     let solution_size = rstr_solution.lvector_size();
119     if !quiet {
120         println!("Number of mesh nodes        : {}", mesh_size / dim);
121         println!("Number of solution nodes    : {}", solution_size);
122     }
123 
124     // Create a Vector with the mesh coordinates
125     let mut mesh_coords = mesh::cartesian_mesh_coords(&ceed, dim, num_xyz, mesh_degree, mesh_size)?;
126 
127     // Apply a transformation to the mesh coordinates
128     let exact_volume = transform::transform_mesh_coordinates(dim, mesh_size, &mut mesh_coords)?;
129 
130     // QFunction that builds the quadrature data for the mass operator
131     // -- QFunction from user closure
132     let build_mass = move |[jacobian, weights, ..]: QFunctionInputs,
133                            [qdata, ..]: QFunctionOutputs| {
134         // Build quadrature data
135         match dim {
136             1 => qdata
137                 .iter_mut()
138                 .zip(jacobian.iter().zip(weights.iter()))
139                 .for_each(|(qdata, (j, weight))| *qdata = j * weight),
140             2 => {
141                 let q = qdata.len();
142                 qdata.iter_mut().zip(weights.iter()).enumerate().for_each(
143                     |(i, (qdata, weight))| {
144                         *qdata = (jacobian[i + q * 0] * jacobian[i + q * 3]
145                             - jacobian[i + q * 1] * jacobian[i + q * 2])
146                             * weight
147                     },
148                 );
149             }
150             3 => {
151                 let q = qdata.len();
152                 qdata.iter_mut().zip(weights.iter()).enumerate().for_each(
153                     |(i, (qdata, weight))| {
154                         *qdata = (jacobian[i + q * 0]
155                             * (jacobian[i + q * 4] * jacobian[i + q * 8]
156                                 - jacobian[i + q * 5] * jacobian[i + q * 7])
157                             - jacobian[i + q * 1]
158                                 * (jacobian[i + q * 3] * jacobian[i + q * 8]
159                                     - jacobian[i + q * 5] * jacobian[i + q * 6])
160                             + jacobian[i + q * 2]
161                                 * (jacobian[i + q * 3] * jacobian[i + q * 7]
162                                     - jacobian[i + q * 4] * jacobian[i + q * 6]))
163                             * weight
164                     },
165                 );
166             }
167             _ => unreachable!(),
168         };
169 
170         // Return clean error code
171         0
172     };
173     let qf_build_closure = ceed
174         .q_function_interior(1, Box::new(build_mass))?
175         .input("dx", ncomp_x * dim, libceed::EvalMode::Grad)?
176         .input("weights", 1, libceed::EvalMode::Weight)?
177         .output("qdata", 1, libceed::EvalMode::None)?;
178     // -- QFunction from gallery
179     let qf_build_named = {
180         let name = format!("Mass{}DBuild", dim);
181         ceed.q_function_interior_by_name(&name)?
182     };
183     // -- QFunction for use with Operator
184     let qf_build = if gallery {
185         QFunctionOpt::SomeQFunctionByName(&qf_build_named)
186     } else {
187         QFunctionOpt::SomeQFunction(&qf_build_closure)
188     };
189 
190     // Operator that build the quadrature data for the mass operator
191     let op_build = ceed
192         .operator(qf_build, QFunctionOpt::None, QFunctionOpt::None)?
193         .name("build qdata")?
194         .field("dx", &rstr_mesh, &basis_mesh, VectorOpt::Active)?
195         .field(
196             "weights",
197             ElemRestrictionOpt::None,
198             &basis_mesh,
199             VectorOpt::None,
200         )?
201         .field("qdata", &rstr_qdata, BasisOpt::None, VectorOpt::Active)?
202         .check()?;
203 
204     // Compute the quadrature data for the mass operator
205     let elem_qpts = num_qpts.pow(dim as u32);
206     let num_elem: usize = num_xyz.iter().take(dim).product();
207     let mut qdata = ceed.vector(num_elem * elem_qpts)?;
208     op_build.apply(&mesh_coords, &mut qdata)?;
209 
210     // QFunction that applies the mass operator
211     // -- QFunction from user closure
212     let apply_mass = |[u, qdata, ..]: QFunctionInputs, [v, ..]: QFunctionOutputs| {
213         // Apply mass operator
214         v.iter_mut()
215             .zip(u.iter().zip(qdata.iter()))
216             .for_each(|(v, (u, w))| *v = u * w);
217         // Return clean error code
218         0
219     };
220     let qf_mass_closure = ceed
221         .q_function_interior(1, Box::new(apply_mass))?
222         .input("u", 1, libceed::EvalMode::Interp)?
223         .input("qdata", 1, libceed::EvalMode::None)?
224         .output("v", 1, libceed::EvalMode::Interp)?;
225     // -- QFunction from gallery
226     let qf_mass_named = ceed.q_function_interior_by_name("MassApply")?;
227     // -- QFunction for use with Operator
228     let qf_mass = if gallery {
229         QFunctionOpt::SomeQFunctionByName(&qf_mass_named)
230     } else {
231         QFunctionOpt::SomeQFunction(&qf_mass_closure)
232     };
233 
234     // Mass Operator
235     let op_mass = ceed
236         .operator(qf_mass, QFunctionOpt::None, QFunctionOpt::None)?
237         .name("mass")?
238         .field("u", &rstr_solution, &basis_solution, VectorOpt::Active)?
239         .field("qdata", &rstr_qdata, BasisOpt::None, &qdata)?
240         .field("v", &rstr_solution, &basis_solution, VectorOpt::Active)?
241         .check()?;
242 
243     // Solution vectors
244     let u = ceed.vector_from_slice(&vec![1.0; solution_size])?;
245     let mut v = ceed.vector(solution_size)?;
246 
247     // Apply the mass operator
248     op_mass.apply(&u, &mut v)?;
249 
250     // Compute the mesh volume
251     let volume: libceed::Scalar = v.view()?.iter().sum();
252 
253     // Output results
254     if !quiet {
255         println!("Exact mesh volume           : {:.12}", exact_volume);
256         println!("Computed mesh volume        : {:.12}", volume);
257         println!(
258             "Volume error                : {:.12e}",
259             volume - exact_volume
260         );
261     }
262     let tolerance = match dim {
263         1 => 500.0 * libceed::EPSILON,
264         _ => 1E-5,
265     };
266     let error = (volume - exact_volume).abs();
267     if error > tolerance {
268         println!("Volume error too large: {:.12e}", error);
269         return Err(libceed::Error {
270             message: format!(
271                 "Volume error too large - expected: {:.12e}, actual: {:.12e}",
272                 tolerance, error
273             ),
274         });
275     }
276     Ok(())
277 }
278 
279 // ----------------------------------------------------------------------------
280 // Tests
281 // ----------------------------------------------------------------------------
282 #[cfg(test)]
283 mod tests {
284     use super::*;
285 
286     #[test]
287     fn example_1_1d() {
288         let options = opt::Opt {
289             ceed_spec: "/cpu/self/ref/serial".to_string(),
290             dim: 1,
291             mesh_degree: 4,
292             solution_degree: 4,
293             num_qpts: 6,
294             problem_size_requested: -1,
295             test: true,
296             quiet: true,
297             gallery: false,
298         };
299         assert!(example_1(options).is_ok());
300     }
301 
302     #[test]
303     fn example_1_2d() {
304         let options = opt::Opt {
305             ceed_spec: "/cpu/self/ref/serial".to_string(),
306             dim: 2,
307             mesh_degree: 4,
308             solution_degree: 4,
309             num_qpts: 6,
310             problem_size_requested: -1,
311             test: true,
312             quiet: true,
313             gallery: false,
314         };
315         assert!(example_1(options).is_ok());
316     }
317 
318     #[test]
319     fn example_1_3d() {
320         let options = opt::Opt {
321             ceed_spec: "/cpu/self/ref/serial".to_string(),
322             dim: 3,
323             mesh_degree: 4,
324             solution_degree: 4,
325             num_qpts: 6,
326             problem_size_requested: -1,
327             test: true,
328             quiet: false,
329             gallery: false,
330         };
331         assert!(example_1(options).is_ok());
332     }
333 
334     #[test]
335     fn example_1_1d_gallery() {
336         let options = opt::Opt {
337             ceed_spec: "/cpu/self/ref/serial".to_string(),
338             dim: 1,
339             mesh_degree: 4,
340             solution_degree: 4,
341             num_qpts: 6,
342             problem_size_requested: -1,
343             test: true,
344             quiet: true,
345             gallery: true,
346         };
347         assert!(example_1(options).is_ok());
348     }
349 
350     #[test]
351     fn example_1_2d_gallery() {
352         let options = opt::Opt {
353             ceed_spec: "/cpu/self/ref/serial".to_string(),
354             dim: 2,
355             mesh_degree: 4,
356             solution_degree: 4,
357             num_qpts: 6,
358             problem_size_requested: -1,
359             test: true,
360             quiet: true,
361             gallery: true,
362         };
363         assert!(example_1(options).is_ok());
364     }
365 
366     #[test]
367     fn example_1_3d_gallery() {
368         let options = opt::Opt {
369             ceed_spec: "/cpu/self/ref/serial".to_string(),
370             dim: 3,
371             mesh_degree: 4,
372             solution_degree: 4,
373             num_qpts: 6,
374             problem_size_requested: -1,
375             test: true,
376             quiet: true,
377             gallery: true,
378         };
379         assert!(example_1(options).is_ok());
380     }
381 }
382 
383 // ----------------------------------------------------------------------------
384