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