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