xref: /libCEED/examples/rust/ex1-volume/src/main.rs (revision eb07d68f27a4df7d950ee845c62e88e28e25414a)
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!(dim >= 1 && dim <= 3);
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;
55     if problem_size_requested < 0 {
56         problem_size = if test { 8 * 16 } else { 256 * 1024 };
57     } else {
58         problem_size = problem_size_requested;
59     }
60 
61     // Summary output
62     if !quiet {
63         println!("Selected options: [command line option] : <current value>");
64         println!("    Ceed specification [-c] : {}", ceed_spec);
65         println!("    Mesh dimension     [-d] : {}", dim);
66         println!("    Mesh degree        [-m] : {}", mesh_degree);
67         println!("    Solution degree    [-p] : {}", solution_degree);
68         println!("    Num. 1D quadr. pts [-q] : {}", num_qpts);
69         println!("    Approx. # unknowns [-s] : {}", problem_size);
70         println!(
71             "    QFunction source   [-g] : {}",
72             if gallery { "gallery" } else { "user closure" }
73         );
74     }
75 
76     // Initalize ceed context
77     let ceed = Ceed::init(&ceed_spec);
78 
79     // Mesh and solution bases
80     let basis_mesh = ceed.basis_tensor_H1_Lagrange(
81         dim,
82         ncomp_x,
83         mesh_degree + 1,
84         num_qpts,
85         libceed::QuadMode::Gauss,
86     )?;
87     let basis_solution = ceed.basis_tensor_H1_Lagrange(
88         dim,
89         1,
90         solution_degree + 1,
91         num_qpts,
92         libceed::QuadMode::Gauss,
93     )?;
94 
95     // Determine mesh size from approximate problem size
96     let num_xyz = mesh::cartesian_mesh_size(dim, solution_degree, problem_size);
97     if !quiet {
98         print!("\nMesh size                   : nx = {}", num_xyz[0]);
99         if dim > 1 {
100             print!(", ny = {}", num_xyz[1]);
101         }
102         if dim > 2 {
103             print!(", nz = {}", num_xyz[2]);
104         }
105         print!("\n");
106     }
107 
108     // Build ElemRestriction objects describing the mesh and solution discrete
109     // representations
110     let (rstr_mesh, _) =
111         mesh::build_cartesian_restriction(&ceed, dim, num_xyz, mesh_degree, ncomp_x, num_qpts)?;
112     let (rstr_solution, rstr_qdata) =
113         mesh::build_cartesian_restriction(&ceed, dim, num_xyz, solution_degree, 1, num_qpts)?;
114     let mesh_size = rstr_mesh.lvector_size();
115     let solution_size = rstr_solution.lvector_size();
116     if !quiet {
117         println!("Number of mesh nodes        : {}", mesh_size / dim);
118         println!("Number of solution nodes    : {}", solution_size);
119     }
120 
121     // Create a Vector with the mesh coordinates
122     let mut mesh_coords = mesh::cartesian_mesh_coords(&ceed, dim, num_xyz, mesh_degree, mesh_size)?;
123 
124     // Apply a transformation to the mesh coordinates
125     let exact_volume = transform::transform_mesh_coordinates(dim, mesh_size, &mut mesh_coords)?;
126 
127     // QFunction that builds the quadrature data for the mass operator
128     // -- QFunction from user closure
129     let build_mass = move |[jacobian, weights, ..]: QFunctionInputs,
130                            [qdata, ..]: QFunctionOutputs| {
131         // Build quadrature data
132         match dim {
133             1 => qdata
134                 .iter_mut()
135                 .zip(jacobian.iter().zip(weights.iter()))
136                 .for_each(|(qdata, (j, weight))| *qdata = j * weight),
137             2 => {
138                 let q = qdata.len();
139                 qdata.iter_mut().zip(weights.iter()).enumerate().for_each(
140                     |(i, (qdata, weight))| {
141                         *qdata = (jacobian[i + q * 0] * jacobian[i + q * 3]
142                             - jacobian[i + q * 1] * jacobian[i + q * 2])
143                             * weight
144                     },
145                 );
146             }
147             3 => {
148                 let q = qdata.len();
149                 qdata.iter_mut().zip(weights.iter()).enumerate().for_each(
150                     |(i, (qdata, weight))| {
151                         *qdata = (jacobian[i + q * 0]
152                             * (jacobian[i + q * 4] * jacobian[i + q * 8]
153                                 - jacobian[i + q * 5] * jacobian[i + q * 7])
154                             - jacobian[i + q * 1]
155                                 * (jacobian[i + q * 3] * jacobian[i + q * 8]
156                                     - jacobian[i + q * 5] * jacobian[i + q * 6])
157                             + jacobian[i + q * 2]
158                                 * (jacobian[i + q * 3] * jacobian[i + q * 7]
159                                     - jacobian[i + q * 4] * jacobian[i + q * 6]))
160                             * weight
161                     },
162                 );
163             }
164             _ => unreachable!(),
165         };
166 
167         // Return clean error code
168         0
169     };
170     let qf_build_closure = ceed
171         .q_function_interior(1, Box::new(build_mass))?
172         .input("dx", ncomp_x * dim, libceed::EvalMode::Grad)?
173         .input("weights", 1, libceed::EvalMode::Weight)?
174         .output("qdata", 1, libceed::EvalMode::None)?;
175     // -- QFunction from gallery
176     let qf_build_named = {
177         let name = format!("Mass{}DBuild", dim);
178         ceed.q_function_interior_by_name(&name)?
179     };
180     // -- QFunction for use with Operator
181     let qf_build = if gallery {
182         QFunctionOpt::SomeQFunctionByName(&qf_build_named)
183     } else {
184         QFunctionOpt::SomeQFunction(&qf_build_closure)
185     };
186 
187     // Operator that build the quadrature data for the mass operator
188     let op_build = ceed
189         .operator(qf_build, QFunctionOpt::None, QFunctionOpt::None)?
190         .name("build qdata")?
191         .field("dx", &rstr_mesh, &basis_mesh, VectorOpt::Active)?
192         .field(
193             "weights",
194             ElemRestrictionOpt::None,
195             &basis_mesh,
196             VectorOpt::None,
197         )?
198         .field("qdata", &rstr_qdata, BasisOpt::None, VectorOpt::Active)?
199         .check()?;
200 
201     // Compute the quadrature data for the mass operator
202     let elem_qpts = num_qpts.pow(dim as u32);
203     let num_elem: usize = num_xyz.iter().take(dim).product();
204     let mut qdata = ceed.vector(num_elem * elem_qpts)?;
205     op_build.apply(&mesh_coords, &mut qdata)?;
206 
207     // QFunction that applies the mass operator
208     // -- QFunction from user closure
209     let apply_mass = |[u, qdata, ..]: QFunctionInputs, [v, ..]: QFunctionOutputs| {
210         // Apply mass operator
211         v.iter_mut()
212             .zip(u.iter().zip(qdata.iter()))
213             .for_each(|(v, (u, w))| *v = u * w);
214         // Return clean error code
215         0
216     };
217     let qf_mass_closure = ceed
218         .q_function_interior(1, Box::new(apply_mass))?
219         .input("u", 1, libceed::EvalMode::Interp)?
220         .input("qdata", 1, libceed::EvalMode::None)?
221         .output("v", 1, libceed::EvalMode::Interp)?;
222     // -- QFunction from gallery
223     let qf_mass_named = ceed.q_function_interior_by_name("MassApply")?;
224     // -- QFunction for use with Operator
225     let qf_mass = if gallery {
226         QFunctionOpt::SomeQFunctionByName(&qf_mass_named)
227     } else {
228         QFunctionOpt::SomeQFunction(&qf_mass_closure)
229     };
230 
231     // Mass Operator
232     let op_mass = ceed
233         .operator(qf_mass, QFunctionOpt::None, QFunctionOpt::None)?
234         .name("mass")?
235         .field("u", &rstr_solution, &basis_solution, VectorOpt::Active)?
236         .field("qdata", &rstr_qdata, BasisOpt::None, &qdata)?
237         .field("v", &rstr_solution, &basis_solution, VectorOpt::Active)?
238         .check()?;
239 
240     // Solution vectors
241     let u = ceed.vector_from_slice(&vec![1.0; solution_size])?;
242     let mut v = ceed.vector(solution_size)?;
243 
244     // Apply the mass operator
245     op_mass.apply(&u, &mut v)?;
246 
247     // Compute the mesh volume
248     let volume: libceed::Scalar = v.view()?.iter().sum();
249 
250     // Output results
251     if !quiet {
252         println!("Exact mesh volume           : {:.12}", exact_volume);
253         println!("Computed mesh volume        : {:.12}", volume);
254         println!(
255             "Volume error                : {:.12e}",
256             volume - exact_volume
257         );
258     }
259     let tolerance = match dim {
260         1 => 500.0 * libceed::EPSILON,
261         _ => 1E-5,
262     };
263     let error = (volume - exact_volume).abs();
264     if error > tolerance {
265         println!("Volume error too large: {:.12e}", error);
266         return Err(libceed::Error {
267             message: format!(
268                 "Volume error too large - expected: {:.12e}, actual: {:.12e}",
269                 tolerance, error
270             ),
271         });
272     }
273     Ok(())
274 }
275 
276 // ----------------------------------------------------------------------------
277 // Tests
278 // ----------------------------------------------------------------------------
279 #[cfg(test)]
280 mod tests {
281     use super::*;
282 
283     #[test]
284     fn example_1_1d() {
285         let options = opt::Opt {
286             ceed_spec: "/cpu/self/ref/serial".to_string(),
287             dim: 1,
288             mesh_degree: 4,
289             solution_degree: 4,
290             num_qpts: 6,
291             problem_size_requested: -1,
292             test: true,
293             quiet: true,
294             gallery: false,
295         };
296         assert!(example_1(options).is_ok());
297     }
298 
299     #[test]
300     fn example_1_2d() {
301         let options = opt::Opt {
302             ceed_spec: "/cpu/self/ref/serial".to_string(),
303             dim: 2,
304             mesh_degree: 4,
305             solution_degree: 4,
306             num_qpts: 6,
307             problem_size_requested: -1,
308             test: true,
309             quiet: true,
310             gallery: false,
311         };
312         assert!(example_1(options).is_ok());
313     }
314 
315     #[test]
316     fn example_1_3d() {
317         let options = opt::Opt {
318             ceed_spec: "/cpu/self/ref/serial".to_string(),
319             dim: 3,
320             mesh_degree: 4,
321             solution_degree: 4,
322             num_qpts: 6,
323             problem_size_requested: -1,
324             test: true,
325             quiet: false,
326             gallery: false,
327         };
328         assert!(example_1(options).is_ok());
329     }
330 
331     #[test]
332     fn example_1_1d_gallery() {
333         let options = opt::Opt {
334             ceed_spec: "/cpu/self/ref/serial".to_string(),
335             dim: 1,
336             mesh_degree: 4,
337             solution_degree: 4,
338             num_qpts: 6,
339             problem_size_requested: -1,
340             test: true,
341             quiet: true,
342             gallery: true,
343         };
344         assert!(example_1(options).is_ok());
345     }
346 
347     #[test]
348     fn example_1_2d_gallery() {
349         let options = opt::Opt {
350             ceed_spec: "/cpu/self/ref/serial".to_string(),
351             dim: 2,
352             mesh_degree: 4,
353             solution_degree: 4,
354             num_qpts: 6,
355             problem_size_requested: -1,
356             test: true,
357             quiet: true,
358             gallery: true,
359         };
360         assert!(example_1(options).is_ok());
361     }
362 
363     #[test]
364     fn example_1_3d_gallery() {
365         let options = opt::Opt {
366             ceed_spec: "/cpu/self/ref/serial".to_string(),
367             dim: 3,
368             mesh_degree: 4,
369             solution_degree: 4,
370             num_qpts: 6,
371             problem_size_requested: -1,
372             test: true,
373             quiet: true,
374             gallery: true,
375         };
376         assert!(example_1(options).is_ok());
377     }
378 }
379 
380 // ----------------------------------------------------------------------------
381