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