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