xref: /libCEED/examples/rust/ex1-volume/src/main.rs (revision a697ff736c4bbf0dcf3b0c0690ba5a6b92dd6bdf)
1 // Copyright (c) 2017-2021, Lawrence Livermore National Security, LLC.
2 // Produced at the Lawrence Livermore National Laboratory. LLNL-CODE-734707.
3 // All Rights reserved. See files LICENSE and NOTICE for details.
4 //
5 // This file is part of CEED, a collection of benchmarks, miniapps, software
6 // libraries and APIs for efficient high-order finite element and spectral
7 // element discretizations for exascale applications. For more information and
8 // source code availability see http://github.com/ceed.
9 //
10 // The CEED research is supported by the Exascale Computing Project 17-SC-20-SC,
11 // a collaborative effort of two U.S. Department of Energy organizations (Office
12 // of Science and the National Nuclear Security Administration) responsible for
13 // the planning and preparation of a capable exascale ecosystem, including
14 // software, applications, hardware, advanced system engineering and early
15 // testbed platforms, in support of the nation's exascale computing imperative.
16 
17 //                             libCEED Example 1
18 //
19 // This example illustrates a simple usage of libCEED to compute the volume of a
20 // 3D body using matrix-free application of a mass operator.  Arbitrary mesh and
21 // solution orders in 1D, 2D and 3D are supported from the same code.
22 //
23 // The example has no dependencies, and is designed to be self-contained. For
24 // additional examples that use external discretization libraries (MFEM, PETSc,
25 // etc.) see the subdirectories in libceed/examples.
26 //
27 // All libCEED objects use a Ceed device object constructed based on a command
28 // line argument (-ceed).
29 
30 use libceed::{prelude::*, Ceed};
31 use mesh;
32 use structopt::StructOpt;
33 
34 mod opt;
35 mod transform;
36 
37 // ----------------------------------------------------------------------------
38 // Example 1
39 // ----------------------------------------------------------------------------
40 #[cfg(not(tarpaulin_include))]
41 fn main() -> Result<(), libceed::CeedError> {
42     let options = opt::Opt::from_args();
43     example_1(options)
44 }
45 
46 fn example_1(options: opt::Opt) -> Result<(), libceed::CeedError> {
47     // Process command line arguments
48     let opt::Opt {
49         ceed_spec,
50         dim,
51         mesh_degree,
52         solution_degree,
53         num_qpts,
54         problem_size_requested,
55         test,
56         quiet,
57         gallery,
58     } = options;
59     assert!(dim >= 1 && dim <= 3);
60     assert!(mesh_degree >= 1);
61     assert!(solution_degree >= 1);
62     assert!(num_qpts >= 1);
63     let ncomp_x = dim;
64     let problem_size: i64;
65     if problem_size_requested < 0 {
66         problem_size = if test { 8 * 16 } else { 256 * 1024 };
67     } else {
68         problem_size = problem_size_requested;
69     }
70 
71     // Summary output
72     if !quiet {
73         println!("Selected options: [command line option] : <current value>");
74         println!("    Ceed specification [-c] : {}", ceed_spec);
75         println!("    Mesh dimension     [-d] : {}", dim);
76         println!("    Mesh degree        [-m] : {}", mesh_degree);
77         println!("    Solution degree    [-p] : {}", solution_degree);
78         println!("    Num. 1D quadr. pts [-q] : {}", num_qpts);
79         println!("    Approx. # unknowns [-s] : {}", problem_size);
80         println!(
81             "    QFunction source   [-g] : {}",
82             if gallery { "gallery" } else { "user closure" }
83         );
84     }
85 
86     // Initalize ceed context
87     let ceed = Ceed::init(&ceed_spec);
88 
89     // Mesh and solution bases
90     let basis_mesh = ceed
91         .basis_tensor_H1_Lagrange(dim, ncomp_x, mesh_degree + 1, num_qpts, QuadMode::Gauss)
92         .unwrap();
93     let basis_solution = ceed
94         .basis_tensor_H1_Lagrange(dim, 1, solution_degree + 1, num_qpts, QuadMode::Gauss)
95         .unwrap();
96 
97     // Determine mesh size from approximate problem size
98     let num_xyz = mesh::cartesian_mesh_size(dim, solution_degree, problem_size);
99     if !quiet {
100         print!("\nMesh size                   : nx = {}", num_xyz[0]);
101         if dim > 1 {
102             print!(", ny = {}", num_xyz[1]);
103         }
104         if dim > 2 {
105             print!(", nz = {}", num_xyz[2]);
106         }
107         print!("\n");
108     }
109 
110     // Build ElemRestriction objects describing the mesh and solution discrete
111     // representations
112     let (restr_mesh, _) =
113         mesh::build_cartesian_restriction(&ceed, dim, num_xyz, mesh_degree, ncomp_x, num_qpts)?;
114     let (restr_solution, restr_qdata) =
115         mesh::build_cartesian_restriction(&ceed, dim, num_xyz, solution_degree, 1, num_qpts)?;
116     let mesh_size = restr_mesh.lvector_size();
117     let solution_size = restr_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_volume = transform::transform_mesh_coordinates(dim, mesh_size, &mut mesh_coords);
128 
129     // QFunction that builds the quadrature data for the mass operator
130     // -- QFunction from user closure
131     let build_mass = 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 = j * weight),
139             2 => {
140                 let q = qdata.len();
141                 qdata.iter_mut().zip(weights.iter()).enumerate().for_each(
142                     |(i, (qdata, weight))| {
143                         *qdata = (jacobian[i + q * 0] * jacobian[i + q * 3]
144                             - jacobian[i + q * 1] * jacobian[i + q * 2])
145                             * weight
146                     },
147                 );
148             }
149             3 => {
150                 let q = qdata.len();
151                 qdata.iter_mut().zip(weights.iter()).enumerate().for_each(
152                     |(i, (qdata, weight))| {
153                         *qdata = (jacobian[i + q * 0]
154                             * (jacobian[i + q * 4] * jacobian[i + q * 8]
155                                 - jacobian[i + q * 5] * jacobian[i + q * 7])
156                             - jacobian[i + q * 1]
157                                 * (jacobian[i + q * 3] * jacobian[i + q * 8]
158                                     - jacobian[i + q * 5] * jacobian[i + q * 6])
159                             + jacobian[i + q * 2]
160                                 * (jacobian[i + q * 3] * jacobian[i + q * 7]
161                                     - jacobian[i + q * 4] * jacobian[i + q * 6]))
162                             * weight
163                     },
164                 );
165             }
166             _ => unreachable!(),
167         };
168 
169         // Return clean error code
170         0
171     };
172     let qf_build_closure = ceed
173         .q_function_interior(1, Box::new(build_mass))?
174         .input("dx", ncomp_x * dim, EvalMode::Grad)?
175         .input("weights", 1, EvalMode::Weight)?
176         .output("qdata", 1, EvalMode::None)?;
177     // -- QFunction from gallery
178     let qf_build_named = {
179         let name = format!("Mass{}DBuild", dim);
180         ceed.q_function_interior_by_name(&name)?
181     };
182     // -- QFunction for use with Operator
183     let qf_build = if gallery {
184         QFunctionOpt::SomeQFunctionByName(&qf_build_named)
185     } else {
186         QFunctionOpt::SomeQFunction(&qf_build_closure)
187     };
188 
189     // Operator that build the quadrature data for the mass operator
190     let op_build = ceed
191         .operator(qf_build, QFunctionOpt::None, QFunctionOpt::None)?
192         .field("dx", &restr_mesh, &basis_mesh, VectorOpt::Active)?
193         .field(
194             "weights",
195             ElemRestrictionOpt::None,
196             &basis_mesh,
197             VectorOpt::None,
198         )?
199         .field(
200             "qdata",
201             &restr_qdata,
202             BasisOpt::Collocated,
203             VectorOpt::Active,
204         )?;
205 
206     // Compute the quadrature data for the mass operator
207     let elem_qpts = num_qpts.pow(dim as u32);
208     let num_elem: usize = num_xyz.iter().take(dim).product();
209     let mut qdata = ceed.vector(num_elem * elem_qpts)?;
210     op_build.apply(&mesh_coords, &mut qdata)?;
211 
212     // QFunction that applies the mass operator
213     // -- QFunction from user closure
214     let apply_mass = |[u, qdata, ..]: QFunctionInputs, [v, ..]: QFunctionOutputs| {
215         // Apply mass operator
216         v.iter_mut()
217             .zip(u.iter().zip(qdata.iter()))
218             .for_each(|(v, (u, w))| *v = u * w);
219         // Return clean error code
220         0
221     };
222     let qf_mass_closure = ceed
223         .q_function_interior(1, Box::new(apply_mass))?
224         .input("u", 1, EvalMode::Interp)?
225         .input("qdata", 1, EvalMode::None)?
226         .output("v", 1, EvalMode::Interp)?;
227     // -- QFunction from gallery
228     let qf_mass_named = ceed.q_function_interior_by_name("MassApply")?;
229     // -- QFunction for use with Operator
230     let qf_mass = if gallery {
231         QFunctionOpt::SomeQFunctionByName(&qf_mass_named)
232     } else {
233         QFunctionOpt::SomeQFunction(&qf_mass_closure)
234     };
235 
236     // Mass Operator
237     let op_mass = ceed
238         .operator(qf_mass, QFunctionOpt::None, QFunctionOpt::None)?
239         .field("u", &restr_solution, &basis_solution, VectorOpt::Active)?
240         .field("qdata", &restr_qdata, BasisOpt::Collocated, &qdata)?
241         .field("v", &restr_solution, &basis_solution, VectorOpt::Active)?;
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: f64 = 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 => 1E-14,
264         2 => 1E-7,
265         _ => 1E-5,
266     };
267     let error = (volume - exact_volume).abs();
268     if error > tolerance {
269         println!("Volume error too large: {:.12e}", error);
270         return Err(libceed::CeedError {
271             message: format!(
272                 "Volume error too large - expected: {:.12e}, actual: {:.12e}",
273                 tolerance, error
274             ),
275         });
276     }
277     Ok(())
278 }
279 
280 // ----------------------------------------------------------------------------
281 // Tests
282 // ----------------------------------------------------------------------------
283 #[cfg(test)]
284 mod tests {
285     use super::*;
286 
287     #[test]
288     fn example_1_1d() {
289         let options = opt::Opt {
290             ceed_spec: "/cpu/self/ref/serial".to_string(),
291             dim: 1,
292             mesh_degree: 4,
293             solution_degree: 4,
294             num_qpts: 6,
295             problem_size_requested: -1,
296             test: true,
297             quiet: true,
298             gallery: false,
299         };
300         assert!(example_1(options).is_ok());
301     }
302 
303     #[test]
304     fn example_1_2d() {
305         let options = opt::Opt {
306             ceed_spec: "/cpu/self/ref/serial".to_string(),
307             dim: 2,
308             mesh_degree: 4,
309             solution_degree: 4,
310             num_qpts: 6,
311             problem_size_requested: -1,
312             test: true,
313             quiet: true,
314             gallery: false,
315         };
316         assert!(example_1(options).is_ok());
317     }
318 
319     #[test]
320     fn example_1_3d() {
321         let options = opt::Opt {
322             ceed_spec: "/cpu/self/ref/serial".to_string(),
323             dim: 3,
324             mesh_degree: 4,
325             solution_degree: 4,
326             num_qpts: 6,
327             problem_size_requested: -1,
328             test: true,
329             quiet: false,
330             gallery: false,
331         };
332         assert!(example_1(options).is_ok());
333     }
334 
335     #[test]
336     fn example_1_1d_gallery() {
337         let options = opt::Opt {
338             ceed_spec: "/cpu/self/ref/serial".to_string(),
339             dim: 1,
340             mesh_degree: 4,
341             solution_degree: 4,
342             num_qpts: 6,
343             problem_size_requested: -1,
344             test: true,
345             quiet: true,
346             gallery: true,
347         };
348         assert!(example_1(options).is_ok());
349     }
350 
351     #[test]
352     fn example_1_2d_gallery() {
353         let options = opt::Opt {
354             ceed_spec: "/cpu/self/ref/serial".to_string(),
355             dim: 2,
356             mesh_degree: 4,
357             solution_degree: 4,
358             num_qpts: 6,
359             problem_size_requested: -1,
360             test: true,
361             quiet: true,
362             gallery: true,
363         };
364         assert!(example_1(options).is_ok());
365     }
366 
367     #[test]
368     fn example_1_3d_gallery() {
369         let options = opt::Opt {
370             ceed_spec: "/cpu/self/ref/serial".to_string(),
371             dim: 3,
372             mesh_degree: 4,
373             solution_degree: 4,
374             num_qpts: 6,
375             problem_size_requested: -1,
376             test: true,
377             quiet: true,
378             gallery: true,
379         };
380         assert!(example_1(options).is_ok());
381     }
382 }
383 
384 // ----------------------------------------------------------------------------
385