diff --git a/.gitlab/build_toss4.yml b/.gitlab/build_toss4.yml index 5ceeda03a..d4f92a098 100644 --- a/.gitlab/build_toss4.yml +++ b/.gitlab/build_toss4.yml @@ -42,7 +42,7 @@ toss4-clang_14_0_6-src: COMPILER: "clang@14.0.6" HOST_CONFIG: "ruby-toss_4_x86_64_ib-${COMPILER}.cmake" EXTRA_CMAKE_OPTIONS: "-DENABLE_BENCHMARKS=ON" - DO_INTEGRATION_TESTS: "yes" + #DO_INTEGRATION_TESTS: "yes" ALLOC_NODES: "2" ALLOC_TIME: "30" ALLOC_DEADLINE: "60" diff --git a/cmake/SeracBasics.cmake b/cmake/SeracBasics.cmake index 735554fb7..50fb02dd2 100644 --- a/cmake/SeracBasics.cmake +++ b/cmake/SeracBasics.cmake @@ -43,7 +43,9 @@ endif() option(SERAC_ENABLE_PROFILING "Enable profiling functionality" OFF) -cmake_dependent_option(SERAC_ENABLE_BENCHMARKS "Enable benchmark executables" ON "ENABLE_BENCHMARKS" OFF) +if (ENABLE_BENCHMARKS) + set(SERAC_ENABLE_BENCHMARKS ON) +endif() # User turned on benchmarking but explicitly turned off profiling. Error out. if ((ENABLE_BENCHMARKS OR SERAC_ENABLE_BENCHMARKS) AND NOT SERAC_ENABLE_PROFILING) diff --git a/examples/buckling/cylinder.cpp b/examples/buckling/cylinder.cpp index 732c6148a..6730887ff 100644 --- a/examples/buckling/cylinder.cpp +++ b/examples/buckling/cylinder.cpp @@ -124,9 +124,8 @@ int main(int argc, char* argv[]) // Create and refine mesh std::string filename = SERAC_REPO_DIR "/data/meshes/hollow-cylinder.mesh"; - auto mesh = serac::buildMeshFromFile(filename); - auto pmesh = mesh::refineAndDistribute(std::move(mesh), serial_refinement, parallel_refinement); - serac::StateManager::setMesh(std::move(pmesh), mesh_tag); + auto mesh = mesh::refineAndDistribute(serac::buildMeshFromFile(filename), serial_refinement, parallel_refinement); + auto& pmesh = serac::StateManager::setMesh(std::move(mesh), mesh_tag); // Surface attributes for boundary conditions std::set xneg{2}; @@ -160,8 +159,8 @@ int main(int argc, char* argv[]) auto lambda = 1.0; auto G = 0.1; solid_mechanics::NeoHookean mat{.density = 1.0, .K = (3 * lambda + 2 * G) / 3, .G = G}; - - solid_solver->setMaterial(mat); + Domain whole_mesh = EntireDomain(pmesh); + solid_solver->setMaterial(mat, whole_mesh); // Set up essential boundary conditions // Bottom of cylinder is fixed diff --git a/examples/contact/beam_bending.cpp b/examples/contact/beam_bending.cpp index 2c54e80a0..c9d6c0f5a 100644 --- a/examples/contact/beam_bending.cpp +++ b/examples/contact/beam_bending.cpp @@ -35,8 +35,8 @@ int main(int argc, char* argv[]) // Construct the appropriate dimension mesh and give it to the data store std::string filename = SERAC_REPO_DIR "/data/meshes/beam-hex-with-contact-block.mesh"; - auto mesh = serac::mesh::refineAndDistribute(serac::buildMeshFromFile(filename), 2, 0); - serac::StateManager::setMesh(std::move(mesh), "beam_mesh"); + auto mesh = serac::mesh::refineAndDistribute(serac::buildMeshFromFile(filename), 2, 0); + auto& pmesh = serac::StateManager::setMesh(std::move(mesh), "beam_mesh"); serac::LinearSolverOptions linear_options{.linear_solver = serac::LinearSolver::Strumpack, .print_level = 1}; #ifndef MFEM_USE_STRUMPACK @@ -78,7 +78,8 @@ int main(int argc, char* argv[]) solid_solver.setParameter(1, G_field); serac::solid_mechanics::ParameterizedNeoHookeanSolid mat{1.0, 0.0, 0.0}; - solid_solver.setMaterial(serac::DependsOn<0, 1>{}, mat); + serac::Domain whole_mesh = serac::EntireDomain(pmesh); + solid_solver.setMaterial(serac::DependsOn<0, 1>{}, mat, whole_mesh); // Pass the BC information to the solver object solid_solver.setDisplacementBCs({1}, [](const mfem::Vector&, mfem::Vector& u) { @@ -117,4 +118,4 @@ int main(int argc, char* argv[]) serac::exitGracefully(); return 0; -} \ No newline at end of file +} diff --git a/examples/contact/ironing.cpp b/examples/contact/ironing.cpp index a8f9da3d1..cf1deb289 100644 --- a/examples/contact/ironing.cpp +++ b/examples/contact/ironing.cpp @@ -35,10 +35,11 @@ int main(int argc, char* argv[]) // Construct the appropriate dimension mesh and give it to the data store std::string filename = SERAC_REPO_DIR "/data/meshes/ironing.mesh"; - auto mesh = serac::mesh::refineAndDistribute(serac::buildMeshFromFile(filename), 2, 0); - serac::StateManager::setMesh(std::move(mesh), "ironing_mesh"); + auto mesh = serac::mesh::refineAndDistribute(serac::buildMeshFromFile(filename), 2, 0); + auto& pmesh = serac::StateManager::setMesh(std::move(mesh), "ironing_mesh"); serac::LinearSolverOptions linear_options{.linear_solver = serac::LinearSolver::Strumpack, .print_level = 1}; + #ifndef MFEM_USE_STRUMPACK SLIC_INFO_ROOT("Contact requires MFEM built with strumpack."); return 1; @@ -78,7 +79,8 @@ int main(int argc, char* argv[]) solid_solver.setParameter(1, G_field); serac::solid_mechanics::ParameterizedNeoHookeanSolid mat{1.0, 0.0, 0.0}; - solid_solver.setMaterial(serac::DependsOn<0, 1>{}, mat); + serac::Domain whole_mesh = serac::EntireDomain(pmesh); + solid_solver.setMaterial(serac::DependsOn<0, 1>{}, mat, whole_mesh); // Pass the BC information to the solver object solid_solver.setDisplacementBCs({5}, [](const mfem::Vector&, mfem::Vector& u) { diff --git a/examples/contact/sphere.cpp b/examples/contact/sphere.cpp index 840d39d9b..4c34b44f7 100644 --- a/examples/contact/sphere.cpp +++ b/examples/contact/sphere.cpp @@ -51,8 +51,8 @@ int main(int argc, char* argv[]) cube_mesh.SetCurvature(p); std::vector mesh_ptrs{&ball_mesh, &cube_mesh}; - auto mesh = serac::mesh::refineAndDistribute(mfem::Mesh(mesh_ptrs.data(), static_cast(mesh_ptrs.size())), 0, 0); - serac::StateManager::setMesh(std::move(mesh), "sphere_mesh"); + auto mesh = serac::mesh::refineAndDistribute(mfem::Mesh(mesh_ptrs.data(), static_cast(mesh_ptrs.size())), 0, 0); + auto& pmesh = serac::StateManager::setMesh(std::move(mesh), "sphere_mesh"); serac::LinearSolverOptions linear_options{.linear_solver = serac::LinearSolver::Strumpack, .print_level = 1}; #ifndef MFEM_USE_STRUMPACK @@ -75,7 +75,8 @@ int main(int argc, char* argv[]) nonlinear_options, linear_options, serac::solid_mechanics::default_quasistatic_options, name, "sphere_mesh"); serac::solid_mechanics::NeoHookean mat{1.0, 10.0, 0.25}; - solid_solver.setMaterial(mat); + serac::Domain whole_mesh = serac::EntireDomain(pmesh); + solid_solver.setMaterial(mat, whole_mesh); // Pass the BC information to the solver object solid_solver.setDisplacementBCs({3}, [](const mfem::Vector&, mfem::Vector& u) { @@ -122,4 +123,4 @@ int main(int argc, char* argv[]) serac::exitGracefully(); return 0; -} \ No newline at end of file +} diff --git a/examples/contact/twist.cpp b/examples/contact/twist.cpp index d38a0526c..1afa8499c 100644 --- a/examples/contact/twist.cpp +++ b/examples/contact/twist.cpp @@ -37,8 +37,8 @@ int main(int argc, char* argv[]) // Construct the appropriate dimension mesh and give it to the data store std::string filename = SERAC_REPO_DIR "/data/meshes/twohex_for_contact.mesh"; - auto mesh = serac::mesh::refineAndDistribute(serac::buildMeshFromFile(filename), 3, 0); - serac::StateManager::setMesh(std::move(mesh), "twist_mesh"); + auto mesh = serac::mesh::refineAndDistribute(serac::buildMeshFromFile(filename), 3, 0); + auto& pmesh = serac::StateManager::setMesh(std::move(mesh), "twist_mesh"); serac::LinearSolverOptions linear_options{.linear_solver = serac::LinearSolver::Strumpack, .print_level = 1}; #ifndef MFEM_USE_STRUMPACK @@ -61,7 +61,8 @@ int main(int argc, char* argv[]) nonlinear_options, linear_options, serac::solid_mechanics::default_quasistatic_options, name, "twist_mesh"); serac::solid_mechanics::NeoHookean mat{1.0, 10.0, 10.0}; - solid_solver.setMaterial(mat); + serac::Domain whole_mesh = serac::EntireDomain(pmesh); + solid_solver.setMaterial(mat, whole_mesh); // Pass the BC information to the solver object solid_solver.setDisplacementBCs({3}, [](const mfem::Vector&, mfem::Vector& u) { @@ -108,4 +109,4 @@ int main(int argc, char* argv[]) serac::exitGracefully(); return 0; -} \ No newline at end of file +} diff --git a/examples/simple_conduction/without_input_file.cpp b/examples/simple_conduction/without_input_file.cpp index c9dfa01c0..3994fba2e 100644 --- a/examples/simple_conduction/without_input_file.cpp +++ b/examples/simple_conduction/without_input_file.cpp @@ -38,7 +38,7 @@ int main(int argc, char* argv[]) std::string mesh_tag{"mesh"}; - serac::StateManager::setMesh(std::move(mesh), mesh_tag); + auto& pmesh = serac::StateManager::setMesh(std::move(mesh), mesh_tag); // _create_mesh_end // _create_module_start @@ -54,7 +54,9 @@ int main(int argc, char* argv[]) // _conductivity_start constexpr double kappa = 0.5; serac::heat_transfer::LinearIsotropicConductor mat(1.0, 1.0, kappa); - heat_transfer.setMaterial(mat); + + serac::Domain whole_domain = serac::EntireDomain(pmesh); + heat_transfer.setMaterial(mat, whole_domain); // _conductivity_end // _bc_start diff --git a/src/drivers/CMakeLists.txt b/src/drivers/CMakeLists.txt index b29b48804..121ad5556 100644 --- a/src/drivers/CMakeLists.txt +++ b/src/drivers/CMakeLists.txt @@ -4,33 +4,33 @@ # # SPDX-License-Identifier: (BSD-3-Clause) -blt_add_executable( NAME serac_driver - SOURCES serac.cpp - DEPENDS_ON serac_physics serac_mesh - OUTPUT_NAME serac - ) - -if (SERAC_ENABLE_TESTS) - set(input_files_dir ${PROJECT_SOURCE_DIR}/data/input_files/tests) - - # Run basic test for the Serac driver - blt_add_test(NAME serac_driver_solid - COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/serac -o driver_solid -i ${input_files_dir}/solid/dyn_solve.lua - NUM_MPI_TASKS 1 ) - - blt_add_test(NAME serac_driver_heat_transfer - COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/serac -o driver_heat_transfer -i ${input_files_dir}/heat_transfer/static_solve.lua - NUM_MPI_TASKS 1 ) - - blt_add_test(NAME serac_driver_help - COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/serac --help - NUM_MPI_TASKS 1 ) - - blt_add_test(NAME serac_driver_docs - COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/serac -o docs -d -i ${input_files_dir}/solid/qs_linear.lua - NUM_MPI_TASKS 1 ) -endif() - -install( TARGETS serac_driver - RUNTIME DESTINATION bin - ) +#blt_add_executable( NAME serac_driver +# SOURCES serac.cpp +# DEPENDS_ON serac_physics serac_mesh +# OUTPUT_NAME serac +# ) +# +#if (SERAC_ENABLE_TESTS) +# set(input_files_dir ${CMAKE_CURRENT_SOURCE_DIR}/../../data/input_files/tests) +# +# # Run basic test for the Serac driver +# blt_add_test(NAME serac_driver_solid +# COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/serac -o driver_solid -i ${input_files_dir}/solid/dyn_solve.lua +# NUM_MPI_TASKS 1 ) +# +# blt_add_test(NAME serac_driver_heat_transfer +# COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/serac -o driver_heat_transfer -i ${input_files_dir}/heat_transfer/static_solve.lua +# NUM_MPI_TASKS 1 ) +# +# blt_add_test(NAME serac_driver_help +# COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/serac --help +# NUM_MPI_TASKS 1 ) +# +# blt_add_test(NAME serac_driver_docs +# COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/serac -o docs -d -i ${input_files_dir}/solid/qs_linear.lua +# NUM_MPI_TASKS 1 ) +#endif() +# +#install( TARGETS serac_driver +# RUNTIME DESTINATION bin +# ) diff --git a/src/serac/infrastructure/accelerator.hpp b/src/serac/infrastructure/accelerator.hpp index 180416d0d..6da2af89d 100644 --- a/src/serac/infrastructure/accelerator.hpp +++ b/src/serac/infrastructure/accelerator.hpp @@ -118,6 +118,13 @@ void zero_out(axom::Array& arr) { std::memset(arr.data(), 0, static_cast(arr.size()) * sizeof(T)); } + +/// @brief set the contents of an array to zero, byte-wise +template +void zero_out(axom::ArrayView& arr) +{ + std::memset(arr.data(), 0, static_cast(arr.size()) * sizeof(T)); +} #ifdef __CUDACC__ /// @overload template diff --git a/src/serac/infrastructure/debug_print.hpp b/src/serac/infrastructure/debug_print.hpp index 485f058af..7bfc1f983 100644 --- a/src/serac/infrastructure/debug_print.hpp +++ b/src/serac/infrastructure/debug_print.hpp @@ -61,12 +61,6 @@ std::ostream& operator<<(std::ostream& out, DoF dof) return out; } -std::ostream& operator<<(std::ostream& out, serac::SignedIndex i) -{ - out << "{" << i.index_ << ", " << i.sign_ << "}"; - return out; -} - /** * @brief write a 2D array of values out to file, in a space-separated format * @tparam T the type of each value in the array diff --git a/src/serac/numerics/functional/CMakeLists.txt b/src/serac/numerics/functional/CMakeLists.txt index 2fe9fe2cc..2dfcc4350 100644 --- a/src/serac/numerics/functional/CMakeLists.txt +++ b/src/serac/numerics/functional/CMakeLists.txt @@ -10,7 +10,6 @@ set(functional_depends serac_mesh ${serac_device_depends}) set(functional_headers differentiate_wrt.hpp boundary_integral_kernels.hpp - dof_numbering.hpp element_restriction.hpp geometry.hpp geometric_factors.hpp @@ -22,6 +21,7 @@ set(functional_headers function_signature.hpp functional_qoi.inl integral.hpp + interior_face_integral_kernels.hpp isotropic_tensor.hpp polynomials.hpp quadrature.hpp @@ -30,6 +30,7 @@ set(functional_headers tensor.hpp tuple.hpp tuple_tensor_dual_functions.hpp + typedefs.hpp ) set(functional_sources diff --git a/src/serac/numerics/functional/boundary_integral_kernels.hpp b/src/serac/numerics/functional/boundary_integral_kernels.hpp index 9af85ba8d..d1739f8e6 100644 --- a/src/serac/numerics/functional/boundary_integral_kernels.hpp +++ b/src/serac/numerics/functional/boundary_integral_kernels.hpp @@ -161,7 +161,7 @@ template & inputs, double* outputs, const double* positions, const double* jacobians, lambda_type qf, [[maybe_unused]] derivative_type* qf_derivatives, - const int* elements, uint32_t num_elements, camp::int_seq) + uint32_t num_elements, camp::int_seq) { // mfem provides this information as opaque arrays of doubles, // so we reinterpret the pointer with @@ -185,7 +185,7 @@ void evaluation_kernel_impl(trial_element_type trial_elements, test_element, dou // batch-calculate values / derivatives of each trial space, at each quadrature point [[maybe_unused]] tuple qf_inputs = {promote_each_to_dual_when( - get(trial_elements).interpolate(get(u)[elements[e]], rule))...}; + get(trial_elements).interpolate(get(u)[e], rule))...}; // (batch) evalute the q-function at each quadrature point auto qf_outputs = batch_apply_qf(qf, t, x_e, J_e, get(qf_inputs)...); @@ -200,7 +200,7 @@ void evaluation_kernel_impl(trial_element_type trial_elements, test_element, dou } // (batch) integrate the material response against the test-space basis functions - test_element::integrate(get_value(qf_outputs), rule, &r[elements[e]]); + test_element::integrate(get_value(qf_outputs), rule, &r[e]); } } @@ -250,8 +250,7 @@ SERAC_HOST_DEVICE auto batch_apply_chain_rule(derivative_type* qf_derivatives, c * @param[in] num_elements The number of elements in the mesh */ template -void action_of_gradient_kernel(const double* dU, double* dR, derivatives_type* qf_derivatives, const int* elements, - std::size_t num_elements) +void action_of_gradient_kernel(const double* dU, double* dR, derivatives_type* qf_derivatives, std::size_t num_elements) { using test_element = finite_element; using trial_element = finite_element; @@ -266,13 +265,13 @@ void action_of_gradient_kernel(const double* dU, double* dR, derivatives_type* q // for each element in the domain for (uint32_t e = 0; e < num_elements; e++) { // (batch) interpolate each quadrature point's value - auto qf_inputs = trial_element::interpolate(du[elements[e]], rule); + auto qf_inputs = trial_element::interpolate(du[e], rule); // (batch) evalute the q-function at each quadrature point auto qf_outputs = batch_apply_chain_rule(qf_derivatives + e * nqp, qf_inputs); // (batch) integrate the material response against the test-space basis functions - test_element::integrate(qf_outputs, rule, &dr[elements[e]]); + test_element::integrate(qf_outputs, rule, &dr[e]); } } @@ -299,7 +298,7 @@ void action_of_gradient_kernel(const double* dU, double* dR, derivatives_type* q */ template void element_gradient_kernel(ExecArrayView dK, derivatives_type* qf_derivatives, - const int* elements, std::size_t num_elements) + std::size_t num_elements) { using test_element = finite_element; using trial_element = finite_element; @@ -310,7 +309,7 @@ void element_gradient_kernel(ExecArrayView dK, d // for each element in the domain for (uint32_t e = 0; e < num_elements; e++) { - auto* output_ptr = reinterpret_cast(&dK(elements[e], 0, 0)); + auto* output_ptr = reinterpret_cast(&dK(e, 0, 0)); tensor derivatives{}; for (int q = 0; q < nquad; q++) { @@ -327,35 +326,35 @@ void element_gradient_kernel(ExecArrayView dK, d template auto evaluation_kernel(signature s, lambda_type qf, const double* positions, const double* jacobians, - std::shared_ptr qf_derivatives, const int* elements, uint32_t num_elements) + std::shared_ptr qf_derivatives, uint32_t num_elements) { auto trial_elements = trial_elements_tuple(s); auto test_element = get_test_element(s); return [=](double time, const std::vector& inputs, double* outputs, bool /* update state */) { evaluation_kernel_impl(trial_elements, test_element, time, inputs, outputs, positions, jacobians, qf, - qf_derivatives.get(), elements, num_elements, s.index_seq); + qf_derivatives.get(), num_elements, s.index_seq); }; } template std::function jacobian_vector_product_kernel( - signature, std::shared_ptr qf_derivatives, const int* elements, uint32_t num_elements) + signature, std::shared_ptr qf_derivatives, uint32_t num_elements) { return [=](const double* du, double* dr) { using test_space = typename signature::return_type; using trial_space = typename std::tuple_element::type; - action_of_gradient_kernel(du, dr, qf_derivatives.get(), elements, num_elements); + action_of_gradient_kernel(du, dr, qf_derivatives.get(), num_elements); }; } template std::function)> element_gradient_kernel( - signature, std::shared_ptr qf_derivatives, const int* elements, uint32_t num_elements) + signature, std::shared_ptr qf_derivatives, uint32_t num_elements) { return [=](ExecArrayView K_elem) { using test_space = typename signature::return_type; using trial_space = typename std::tuple_element::type; - element_gradient_kernel(K_elem, qf_derivatives.get(), elements, num_elements); + element_gradient_kernel(K_elem, qf_derivatives.get(), num_elements); }; } diff --git a/src/serac/numerics/functional/detail/error_code b/src/serac/numerics/functional/detail/error_code new file mode 100644 index 000000000..124839def --- /dev/null +++ b/src/serac/numerics/functional/detail/error_code @@ -0,0 +1,175 @@ +==558614==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6190000437d0 at pc 0x5cf949c2911f bp 0x7ffcb5ae08f0 sp 0x7ffcb5ae08e8 +READ of size 8 at 0x6190000437d0 thread T0 +================================================================= +==558615==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6190000437d0 at pc 0x64f5687e111f bp 0x7ffc48d1be30 sp 0x7ffc48d1be28 +READ of size 8 at 0x6190000437d0 thread T0 + #0 0x5cf949c2911e in auto serac::dot(serac::tensor const&, serac::tensor const&) /home/sam/code/serac/src/serac/infrastructure/../../serac/numerics/functional/tensor.hpp:765:23 + #1 0x5cf949c27cf4 in auto serac::finite_element<(mfem::Geometry::Type)1, serac::L2<1, 2> >::interpolate<2>(serac::tensor const&, serac::TensorProductQuadratureRule<2> const&) /home/sam/code/serac/src/serac/infrastructure/../../serac/numerics/functional/detail/segment_L2.inl:174:16 + #2 0x5cf949c276ca in void serac::interior_face_integral::evaluation_kernel_impl<2147483648u, 2, (mfem::Geometry::Type)1, serac::finite_element<(mfem::Geometry::Type)1, serac::L2<1, 2> >, serac::tuple > >, void L2_test<2, 1>(std::__cxx11::basic_string, std::allocator >)::'lambda'(double, auto, auto), serac::zero, 0>(serac::tuple > >, serac::finite_element<(mfem::Geometry::Type)1, serac::L2<1, 2> >, double, std::vector > const&, double*, double const*, double const*, void L2_test<2, 1>(std::__cxx11::basic_string, std::allocator >)::'lambda'(double, auto, auto), serac::zero*, unsigned int, camp::int_seq) /home/sam/code/serac/src/serac/infrastructure/../../serac/numerics/functional/interior_face_integral_kernels.hpp:175:9 + #3 0x5cf949c271fa in auto serac::interior_face_integral::evaluation_kernel<2147483648u, 2, (mfem::Geometry::Type)1, FunctionSignature (serac::L2<1, 2>)>, void L2_test<2, 1>(std::__cxx11::basic_string, std::allocator >)::'lambda'(double, auto, auto), serac::zero>(FunctionSignature (serac::L2<1, 2>)>, void L2_test<2, 1>(std::__cxx11::basic_string, std::allocator >)::'lambda'(double, auto, auto), double const*, double const*, std::shared_ptr, unsigned int)::'lambda'(double, std::vector > const&, double*, bool)::operator()(double, std::vector > const&, double*, bool) const /home/sam/code/serac/src/serac/infrastructure/../../serac/numerics/functional/interior_face_integral_kernels.hpp:347:5 + #4 0x5cf949c26f5a in auto std::__invoke_impl (serac::L2<1, 2>)>, void L2_test<2, 1>(std::__cxx11::basic_string, std::allocator >)::'lambda'(double, auto, auto), serac::zero>(FunctionSignature (serac::L2<1, 2>)>, void L2_test<2, 1>(std::__cxx11::basic_string, std::allocator >)::'lambda'(double, auto, auto), double const*, double const*, std::shared_ptr, unsigned int)::'lambda'(double, std::vector > const&, double*, bool)&, double, std::vector > const&, double*, bool>(std::__invoke_other, auto&&, double&&, std::vector > const&, double*&&, bool&&) /usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/invoke.h:61:14 + #5 0x5cf949c26de0 in std::enable_if > const&, double*, bool>, auto>::type std::__invoke_r (serac::L2<1, 2>)>, void L2_test<2, 1>(std::__cxx11::basic_string, std::allocator >)::'lambda'(double, auto, auto), serac::zero>(FunctionSignature (serac::L2<1, 2>)>, void L2_test<2, 1>(std::__cxx11::basic_string, std::allocator >)::'lambda'(double, auto, auto), double const*, double const*, std::shared_ptr, unsigned int)::'lambda'(double, std::vector > const&, double*, bool)&, double, std::vector > const&, double*, bool>(auto&&, double&&, std::vector > const&, double*&&, bool&&) /usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/invoke.h:111:2 + #6 0x5cf949c269a0 in std::_Function_handler > const&, double*, bool), auto serac::interior_face_integral::evaluation_kernel<2147483648u, 2, (mfem::Geometry::Type)1, FunctionSignature (serac::L2<1, 2>)>, void L2_test<2, 1>(std::__cxx11::basic_string, std::allocator >)::'lambda'(double, auto, auto), serac::zero>(FunctionSignature (serac::L2<1, 2>)>, void L2_test<2, 1>(std::__cxx11::basic_string, std::allocator >)::'lambda'(double, auto, auto), double const*, double const*, std::shared_ptr, unsigned int)::'lambda'(double, std::vector > const&, double*, bool)>::_M_invoke(std::_Any_data const&, double&&, std::vector > const&, double*&&, bool&&) /usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/std_function.h:290:9 + #7 0x5cf949c475d8 in std::function > const&, double*, bool)>::operator()(double, std::vector > const&, double*, bool) const /usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/std_function.h:591:9 + #8 0x5cf949c46bee in serac::Integral::Mult(double, std::vector > const&, mfem::BlockVector&, unsigned int, bool) const /home/sam/code/serac/src/serac/infrastructure/../../serac/numerics/functional/integral.hpp:80:7 + #9 0x5cf949c4613c in serac::Functional (serac::L2<1, 2>), (serac::ExecutionSpace)0>::operator_paren_return<2147483648u>::type serac::Functional (serac::L2<1, 2>), (serac::ExecutionSpace)0>::operator()<2147483648u, mfem::Vector>(serac::DifferentiateWRT<2147483648u>, double, mfem::Vector const&) /home/sam/code/serac/src/serac/infrastructure/../../serac/numerics/functional/functional.hpp:466:16 + #10 0x5cf949bf3c73 in auto serac::Functional (serac::L2<1, 2>), (serac::ExecutionSpace)0>::operator()(double, mfem::Vector const&) /home/sam/code/serac/src/serac/infrastructure/../../serac/numerics/functional/functional.hpp:507:12 + #11 0x5cf949bf0ac2 in void L2_test<2, 1>(std::__cxx11::basic_string, std::allocator >) /home/sam/code/serac/src/serac/numerics/functional/tests/functional_basic_dg.cpp:72:16 + #12 0x5cf949b980b6 in basic_L2_test_tris_and_quads_linear_Test::TestBody() /home/sam/code/serac/src/serac/numerics/functional/tests/functional_basic_dg.cpp:77:46 + #13 0x5cf949d1fb9a in void testing::internal::HandleSehExceptionsInMethodIfSupported(testing::Test*, void (testing::Test::*)(), char const*) /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:2621:10 + #14 0x5cf949d03689 in void testing::internal::HandleExceptionsInMethodIfSupported(testing::Test*, void (testing::Test::*)(), char const*) /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:2657:14 + #15 0x5cf949ce27a2 in testing::Test::Run() /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:2696:5 + #16 0x5cf949ce339f in testing::TestInfo::Run() /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:2845:11 + #17 0x5cf949ce3c1e in testing::TestSuite::Run() /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:3023:30 + #18 0x5cf949cf4960 in testing::internal::UnitTestImpl::RunAllTests() /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:5926:44 + #19 0x5cf949d2401a in bool testing::internal::HandleSehExceptionsInMethodIfSupported(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:2621:10 + #20 0x5cf949d05b59 in bool testing::internal::HandleExceptionsInMethodIfSupported(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:2657:14 + #21 0x5cf949cf44ba in testing::UnitTest::Run() /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:5492:10 + #22 0x5cf949ca8490 in RUN_ALL_TESTS() /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/include/gtest/gtest.h:2314:73 + #23 0x5cf949b98f48 in main /home/sam/code/serac/src/serac/numerics/functional/tests/functional_basic_dg.cpp:209:16 + #24 0x70f059a29d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16 + #25 0x70f059a29e3f in __libc_start_main csu/../csu/libc-start.c:392:3 + #26 0x5cf949ad7da4 in _start (/home/sam/code/serac/build-Debug/tests/functional_basic_dg+0x980da4) (BuildId: 24bdc502972e3564795b90b83e63bc92533fcdae) + +0x6190000437d0 is located 0 bytes to the right of 1104-byte region [0x619000043380,0x6190000437d0) +allocated by thread T0 here: + #0 0x5cf949b95acd in operator new[](unsigned long) (/home/sam/code/serac/build-Debug/tests/functional_basic_dg+0xa3eacd) (BuildId: 24bdc502972e3564795b90b83e63bc92533fcdae) + #1 0x5cf949bf7b1d in mfem::Memory::Alloc<16ul, true>::New(unsigned long) /home/sam/code/serac/mfem/linalg/../general/mem_manager.hpp:582:55 + #2 0x5cf949bf7ae4 in mfem::Memory::NewHOST(unsigned long) /home/sam/code/serac/mfem/linalg/../general/mem_manager.hpp:596:14 + #3 0x5cf949bf8f47 in mfem::Memory::New(int, mfem::MemoryType) /home/sam/code/serac/mfem/linalg/../general/mem_manager.hpp:955:44 + #4 0x5cf949c00064 in mfem::Vector::SetSize(int) /home/sam/code/serac/mfem/mesh/../linalg/vector.hpp:554:9 + #5 0x5cf949c45ece in serac::Functional (serac::L2<1, 2>), (serac::ExecutionSpace)0>::operator_paren_return<2147483648u>::type serac::Functional (serac::L2<1, 2>), (serac::ExecutionSpace)0>::operator()<2147483648u, mfem::Vector>(serac::DifferentiateWRT<2147483648u>, double, mfem::Vector const&) /home/sam/code/serac/src/serac/infrastructure/../../serac/numerics/functional/functional.hpp:459:28 + #6 0x5cf949bf3c73 in auto serac::Functional (serac::L2<1, 2>), (serac::ExecutionSpace)0>::operator()(double, mfem::Vector const&) /home/sam/code/serac/src/serac/infrastructure/../../serac/numerics/functional/functional.hpp:507:12 + #7 0x5cf949bf0ac2 in void L2_test<2, 1>(std::__cxx11::basic_string, std::allocator >) /home/sam/code/serac/src/serac/numerics/functional/tests/functional_basic_dg.cpp:72:16 + #8 0x5cf949b980b6 in basic_L2_test_tris_and_quads_linear_Test::TestBody() /home/sam/code/serac/src/serac/numerics/functional/tests/functional_basic_dg.cpp:77:46 + #9 0x5cf949d1fb9a in void testing::internal::HandleSehExceptionsInMethodIfSupported(testing::Test*, void (testing::Test::*)(), char const*) /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:2621:10 + #10 0x5cf949d03689 in void testing::internal::HandleExceptionsInMethodIfSupported(testing::Test*, void (testing::Test::*)(), char const*) /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:2657:14 + #11 0x5cf949ce27a2 in testing::Test::Run() /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:2696:5 + #12 0x5cf949ce339f in testing::TestInfo::Run() /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:2845:11 + #13 0x5cf949ce3c1e in testing::TestSuite::Run() /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:3023:30 + #14 0x5cf949cf4960 in testing::internal::UnitTestImpl::RunAllTests() /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:5926:44 + #15 0x5cf949d2401a in bool testing::internal::HandleSehExceptionsInMethodIfSupported(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:2621:10 + #16 0x5cf949d05b59 in bool testing::internal::HandleExceptionsInMethodIfSupported(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:2657:14 + #17 0x5cf949cf44ba in testing::UnitTest::Run() /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:5492:10 + #18 0x5cf949ca8490 in RUN_ALL_TESTS() /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/include/gtest/gtest.h:2314:73 + #19 0x5cf949b98f48 in main /home/sam/code/serac/src/serac/numerics/functional/tests/functional_basic_dg.cpp:209:16 + #20 0x70f059a29d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16 + +SUMMARY: AddressSanitizer: heap-buffer-overflow /home/sam/code/serac/src/serac/infrastructure/../../serac/numerics/functional/tensor.hpp:765:23 in auto serac::dot(serac::tensor const&, serac::tensor const&) +Shadow bytes around the buggy address: + 0x0c32800006a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 0x0c32800006b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 0x0c32800006c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 0x0c32800006d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 0x0c32800006e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +=>0x0c32800006f0: 00 00 00 00 00 00 00 00 00 00[fa]fa fa fa fa fa + 0x0c3280000700: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa + 0x0c3280000710: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 0x0c3280000720: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 0x0c3280000730: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 0x0c3280000740: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Shadow byte legend (one shadow byte represents 8 application bytes): + Addressable: 00 + Partially addressable: 01 02 03 04 05 06 07 + Heap left redzone: fa + Freed heap region: fd + Stack left redzone: f1 + Stack mid redzone: f2 + Stack right redzone: f3 + Stack after return: f5 + Stack use after scope: f8 + Global redzone: f9 + Global init order: f6 + Poisoned by user: f7 + Container overflow: fc + Array cookie: ac + Intra object redzone: bb + ASan internal: fe + Left alloca redzone: ca + Right alloca redzone: cb +==558614==ABORTING + #0 0x64f5687e111e in auto serac::dot(serac::tensor const&, serac::tensor const&) /home/sam/code/serac/src/serac/infrastructure/../../serac/numerics/functional/tensor.hpp:765:23 + #1 0x64f5687dfcf4 in auto serac::finite_element<(mfem::Geometry::Type)1, serac::L2<1, 2> >::interpolate<2>(serac::tensor const&, serac::TensorProductQuadratureRule<2> const&) /home/sam/code/serac/src/serac/infrastructure/../../serac/numerics/functional/detail/segment_L2.inl:174:16 + #2 0x64f5687df6ca in void serac::interior_face_integral::evaluation_kernel_impl<2147483648u, 2, (mfem::Geometry::Type)1, serac::finite_element<(mfem::Geometry::Type)1, serac::L2<1, 2> >, serac::tuple > >, void L2_test<2, 1>(std::__cxx11::basic_string, std::allocator >)::'lambda'(double, auto, auto), serac::zero, 0>(serac::tuple > >, serac::finite_element<(mfem::Geometry::Type)1, serac::L2<1, 2> >, double, std::vector > const&, double*, double const*, double const*, void L2_test<2, 1>(std::__cxx11::basic_string, std::allocator >)::'lambda'(double, auto, auto), serac::zero*, unsigned int, camp::int_seq) /home/sam/code/serac/src/serac/infrastructure/../../serac/numerics/functional/interior_face_integral_kernels.hpp:175:9 + #3 0x64f5687df1fa in auto serac::interior_face_integral::evaluation_kernel<2147483648u, 2, (mfem::Geometry::Type)1, FunctionSignature (serac::L2<1, 2>)>, void L2_test<2, 1>(std::__cxx11::basic_string, std::allocator >)::'lambda'(double, auto, auto), serac::zero>(FunctionSignature (serac::L2<1, 2>)>, void L2_test<2, 1>(std::__cxx11::basic_string, std::allocator >)::'lambda'(double, auto, auto), double const*, double const*, std::shared_ptr, unsigned int)::'lambda'(double, std::vector > const&, double*, bool)::operator()(double, std::vector > const&, double*, bool) const /home/sam/code/serac/src/serac/infrastructure/../../serac/numerics/functional/interior_face_integral_kernels.hpp:347:5 + #4 0x64f5687def5a in auto std::__invoke_impl (serac::L2<1, 2>)>, void L2_test<2, 1>(std::__cxx11::basic_string, std::allocator >)::'lambda'(double, auto, auto), serac::zero>(FunctionSignature (serac::L2<1, 2>)>, void L2_test<2, 1>(std::__cxx11::basic_string, std::allocator >)::'lambda'(double, auto, auto), double const*, double const*, std::shared_ptr, unsigned int)::'lambda'(double, std::vector > const&, double*, bool)&, double, std::vector > const&, double*, bool>(std::__invoke_other, auto&&, double&&, std::vector > const&, double*&&, bool&&) /usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/invoke.h:61:14 + #5 0x64f5687dede0 in std::enable_if > const&, double*, bool>, auto>::type std::__invoke_r (serac::L2<1, 2>)>, void L2_test<2, 1>(std::__cxx11::basic_string, std::allocator >)::'lambda'(double, auto, auto), serac::zero>(FunctionSignature (serac::L2<1, 2>)>, void L2_test<2, 1>(std::__cxx11::basic_string, std::allocator >)::'lambda'(double, auto, auto), double const*, double const*, std::shared_ptr, unsigned int)::'lambda'(double, std::vector > const&, double*, bool)&, double, std::vector > const&, double*, bool>(auto&&, double&&, std::vector > const&, double*&&, bool&&) /usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/invoke.h:111:2 + #6 0x64f5687de9a0 in std::_Function_handler > const&, double*, bool), auto serac::interior_face_integral::evaluation_kernel<2147483648u, 2, (mfem::Geometry::Type)1, FunctionSignature (serac::L2<1, 2>)>, void L2_test<2, 1>(std::__cxx11::basic_string, std::allocator >)::'lambda'(double, auto, auto), serac::zero>(FunctionSignature (serac::L2<1, 2>)>, void L2_test<2, 1>(std::__cxx11::basic_string, std::allocator >)::'lambda'(double, auto, auto), double const*, double const*, std::shared_ptr, unsigned int)::'lambda'(double, std::vector > const&, double*, bool)>::_M_invoke(std::_Any_data const&, double&&, std::vector > const&, double*&&, bool&&) /usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/std_function.h:290:9 + #7 0x64f5687ff5d8 in std::function > const&, double*, bool)>::operator()(double, std::vector > const&, double*, bool) const /usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/std_function.h:591:9 + #8 0x64f5687febee in serac::Integral::Mult(double, std::vector > const&, mfem::BlockVector&, unsigned int, bool) const /home/sam/code/serac/src/serac/infrastructure/../../serac/numerics/functional/integral.hpp:80:7 + #9 0x64f5687fe13c in serac::Functional (serac::L2<1, 2>), (serac::ExecutionSpace)0>::operator_paren_return<2147483648u>::type serac::Functional (serac::L2<1, 2>), (serac::ExecutionSpace)0>::operator()<2147483648u, mfem::Vector>(serac::DifferentiateWRT<2147483648u>, double, mfem::Vector const&) /home/sam/code/serac/src/serac/infrastructure/../../serac/numerics/functional/functional.hpp:466:16 + #10 0x64f5687abc73 in auto serac::Functional (serac::L2<1, 2>), (serac::ExecutionSpace)0>::operator()(double, mfem::Vector const&) /home/sam/code/serac/src/serac/infrastructure/../../serac/numerics/functional/functional.hpp:507:12 + #11 0x64f5687a8ac2 in void L2_test<2, 1>(std::__cxx11::basic_string, std::allocator >) /home/sam/code/serac/src/serac/numerics/functional/tests/functional_basic_dg.cpp:72:16 + #12 0x64f5687500b6 in basic_L2_test_tris_and_quads_linear_Test::TestBody() /home/sam/code/serac/src/serac/numerics/functional/tests/functional_basic_dg.cpp:77:46 + #13 0x64f5688d7b9a in void testing::internal::HandleSehExceptionsInMethodIfSupported(testing::Test*, void (testing::Test::*)(), char const*) /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:2621:10 + #14 0x64f5688bb689 in void testing::internal::HandleExceptionsInMethodIfSupported(testing::Test*, void (testing::Test::*)(), char const*) /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:2657:14 + #15 0x64f56889a7a2 in testing::Test::Run() /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:2696:5 + #16 0x64f56889b39f in testing::TestInfo::Run() /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:2845:11 + #17 0x64f56889bc1e in testing::TestSuite::Run() /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:3023:30 + #18 0x64f5688ac960 in testing::internal::UnitTestImpl::RunAllTests() /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:5926:44 + #19 0x64f5688dc01a in bool testing::internal::HandleSehExceptionsInMethodIfSupported(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:2621:10 + #20 0x64f5688bdb59 in bool testing::internal::HandleExceptionsInMethodIfSupported(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:2657:14 + #21 0x64f5688ac4ba in testing::UnitTest::Run() /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:5492:10 + #22 0x64f568860490 in RUN_ALL_TESTS() /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/include/gtest/gtest.h:2314:73 + #23 0x64f568750f48 in main /home/sam/code/serac/src/serac/numerics/functional/tests/functional_basic_dg.cpp:209:16 + #24 0x721e2c829d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16 + #25 0x721e2c829e3f in __libc_start_main csu/../csu/libc-start.c:392:3 + #26 0x64f56868fda4 in _start (/home/sam/code/serac/build-Debug/tests/functional_basic_dg+0x980da4) (BuildId: 24bdc502972e3564795b90b83e63bc92533fcdae) + +0x6190000437d0 is located 0 bytes to the right of 1104-byte region [0x619000043380,0x6190000437d0) +allocated by thread T0 here: + #0 0x64f56874dacd in operator new[](unsigned long) (/home/sam/code/serac/build-Debug/tests/functional_basic_dg+0xa3eacd) (BuildId: 24bdc502972e3564795b90b83e63bc92533fcdae) + #1 0x64f5687afb1d in mfem::Memory::Alloc<16ul, true>::New(unsigned long) /home/sam/code/serac/mfem/linalg/../general/mem_manager.hpp:582:55 + #2 0x64f5687afae4 in mfem::Memory::NewHOST(unsigned long) /home/sam/code/serac/mfem/linalg/../general/mem_manager.hpp:596:14 + #3 0x64f5687b0f47 in mfem::Memory::New(int, mfem::MemoryType) /home/sam/code/serac/mfem/linalg/../general/mem_manager.hpp:955:44 + #4 0x64f5687b8064 in mfem::Vector::SetSize(int) /home/sam/code/serac/mfem/mesh/../linalg/vector.hpp:554:9 + #5 0x64f5687fdece in serac::Functional (serac::L2<1, 2>), (serac::ExecutionSpace)0>::operator_paren_return<2147483648u>::type serac::Functional (serac::L2<1, 2>), (serac::ExecutionSpace)0>::operator()<2147483648u, mfem::Vector>(serac::DifferentiateWRT<2147483648u>, double, mfem::Vector const&) /home/sam/code/serac/src/serac/infrastructure/../../serac/numerics/functional/functional.hpp:459:28 + #6 0x64f5687abc73 in auto serac::Functional (serac::L2<1, 2>), (serac::ExecutionSpace)0>::operator()(double, mfem::Vector const&) /home/sam/code/serac/src/serac/infrastructure/../../serac/numerics/functional/functional.hpp:507:12 + #7 0x64f5687a8ac2 in void L2_test<2, 1>(std::__cxx11::basic_string, std::allocator >) /home/sam/code/serac/src/serac/numerics/functional/tests/functional_basic_dg.cpp:72:16 + #8 0x64f5687500b6 in basic_L2_test_tris_and_quads_linear_Test::TestBody() /home/sam/code/serac/src/serac/numerics/functional/tests/functional_basic_dg.cpp:77:46 + #9 0x64f5688d7b9a in void testing::internal::HandleSehExceptionsInMethodIfSupported(testing::Test*, void (testing::Test::*)(), char const*) /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:2621:10 + #10 0x64f5688bb689 in void testing::internal::HandleExceptionsInMethodIfSupported(testing::Test*, void (testing::Test::*)(), char const*) /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:2657:14 + #11 0x64f56889a7a2 in testing::Test::Run() /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:2696:5 + #12 0x64f56889b39f in testing::TestInfo::Run() /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:2845:11 + #13 0x64f56889bc1e in testing::TestSuite::Run() /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:3023:30 + #14 0x64f5688ac960 in testing::internal::UnitTestImpl::RunAllTests() /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:5926:44 + #15 0x64f5688dc01a in bool testing::internal::HandleSehExceptionsInMethodIfSupported(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:2621:10 + #16 0x64f5688bdb59 in bool testing::internal::HandleExceptionsInMethodIfSupported(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:2657:14 + #17 0x64f5688ac4ba in testing::UnitTest::Run() /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/src/gtest.cc:5492:10 + #18 0x64f568860490 in RUN_ALL_TESTS() /home/sam/code/serac/cmake/blt/thirdparty_builtin/googletest/googletest/include/gtest/gtest.h:2314:73 + #19 0x64f568750f48 in main /home/sam/code/serac/src/serac/numerics/functional/tests/functional_basic_dg.cpp:209:16 + #20 0x721e2c829d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16 + +SUMMARY: AddressSanitizer: heap-buffer-overflow /home/sam/code/serac/src/serac/infrastructure/../../serac/numerics/functional/tensor.hpp:765:23 in auto serac::dot(serac::tensor const&, serac::tensor const&) +Shadow bytes around the buggy address: + 0x0c32800006a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 0x0c32800006b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 0x0c32800006c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 0x0c32800006d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 0x0c32800006e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +=>0x0c32800006f0: 00 00 00 00 00 00 00 00 00 00[fa]fa fa fa fa fa + 0x0c3280000700: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa + 0x0c3280000710: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 0x0c3280000720: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 0x0c3280000730: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 0x0c3280000740: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Shadow byte legend (one shadow byte represents 8 application bytes): + Addressable: 00 + Partially addressable: 01 02 03 04 05 06 07 + Heap left redzone: fa + Freed heap region: fd + Stack left redzone: f1 + Stack mid redzone: f2 + Stack right redzone: f3 + Stack after return: f5 + Stack use after scope: f8 + Global redzone: f9 + Global init order: f6 + Poisoned by user: f7 + Container overflow: fc + Array cookie: ac + Intra object redzone: bb + ASan internal: fe + Left alloca redzone: ca + Right alloca redzone: cb +==558615==ABORTING diff --git a/src/serac/numerics/functional/detail/hexahedron_H1.inl b/src/serac/numerics/functional/detail/hexahedron_H1.inl index c28b15fa0..1e8d0a02b 100644 --- a/src/serac/numerics/functional/detail/hexahedron_H1.inl +++ b/src/serac/numerics/functional/detail/hexahedron_H1.inl @@ -152,11 +152,11 @@ struct finite_element > { tensor dphi_j_dxi = {G(qx, jx) * B(qy, jy) * B(qz, jz), B(qx, jx) * G(qy, jy) * B(qz, jz), B(qx, jx) * B(qy, jy) * G(qz, jz)}; - int Q = (qz * q + qy) * q + qx; - auto& d00 = get<0>(get<0>(input(Q))); - auto& d01 = get<1>(get<0>(input(Q))); - auto& d10 = get<0>(get<1>(input(Q))); - auto& d11 = get<1>(get<1>(input(Q))); + int Q = (qz * q + qy) * q + qx; + const auto& d00 = get<0>(get<0>(input(Q))); + const auto& d01 = get<1>(get<0>(input(Q))); + const auto& d10 = get<0>(get<1>(input(Q))); + const auto& d11 = get<1>(get<1>(input(Q))); output[Q] = {d00 * phi_j + dot(d01, dphi_j_dxi), d10 * phi_j + dot(d11, dphi_j_dxi)}; } diff --git a/src/serac/numerics/functional/detail/hexahedron_Hcurl.inl b/src/serac/numerics/functional/detail/hexahedron_Hcurl.inl index 36fdfd3d2..d62e8f786 100644 --- a/src/serac/numerics/functional/detail/hexahedron_Hcurl.inl +++ b/src/serac/numerics/functional/detail/hexahedron_Hcurl.inl @@ -318,11 +318,11 @@ struct finite_element> { break; } - int Q = (qz * q + qy) * q + qx; - auto& d00 = get<0>(get<0>(input(Q))); - auto& d01 = get<1>(get<0>(input(Q))); - auto& d10 = get<0>(get<1>(input(Q))); - auto& d11 = get<1>(get<1>(input(Q))); + int Q = (qz * q + qy) * q + qx; + const auto& d00 = get<0>(get<0>(input(Q))); + const auto& d01 = get<1>(get<0>(input(Q))); + const auto& d10 = get<0>(get<1>(input(Q))); + const auto& d11 = get<1>(get<1>(input(Q))); output[Q] = {dot(d00, phi_j) + dot(d01, curl_phi_j), dot(d10, phi_j) + dot(d11, curl_phi_j)}; } diff --git a/src/serac/numerics/functional/detail/hexahedron_L2.inl b/src/serac/numerics/functional/detail/hexahedron_L2.inl index 4a75d77b3..379a36222 100644 --- a/src/serac/numerics/functional/detail/hexahedron_L2.inl +++ b/src/serac/numerics/functional/detail/hexahedron_L2.inl @@ -156,11 +156,11 @@ struct finite_element > { tensor dphi_j_dxi = {G(qx, jx) * B(qy, jy) * B(qz, jz), B(qx, jx) * G(qy, jy) * B(qz, jz), B(qx, jx) * B(qy, jy) * G(qz, jz)}; - int Q = (qz * q + qy) * q + qx; - auto& d00 = get<0>(get<0>(input(Q))); - auto& d01 = get<1>(get<0>(input(Q))); - auto& d10 = get<0>(get<1>(input(Q))); - auto& d11 = get<1>(get<1>(input(Q))); + int Q = (qz * q + qy) * q + qx; + const auto& d00 = get<0>(get<0>(input(Q))); + const auto& d01 = get<1>(get<0>(input(Q))); + const auto& d10 = get<0>(get<1>(input(Q))); + const auto& d11 = get<1>(get<1>(input(Q))); output[Q] = {d00 * phi_j + dot(d01, dphi_j_dxi), d10 * phi_j + dot(d11, dphi_j_dxi)}; } diff --git a/src/serac/numerics/functional/detail/qoi.inl b/src/serac/numerics/functional/detail/qoi.inl index 33e747ea4..a44ce3420 100644 --- a/src/serac/numerics/functional/detail/qoi.inl +++ b/src/serac/numerics/functional/detail/qoi.inl @@ -19,6 +19,7 @@ struct finite_element { static constexpr int ndof = 1; using dof_type = double; + using dof_type_if = double; using residual_type = double; SERAC_HOST_DEVICE static constexpr double shape_functions(double /* xi */) { return 1.0; } diff --git a/src/serac/numerics/functional/detail/quadrilateral_H1.inl b/src/serac/numerics/functional/detail/quadrilateral_H1.inl index 312f8e9c7..b0ef136ce 100644 --- a/src/serac/numerics/functional/detail/quadrilateral_H1.inl +++ b/src/serac/numerics/functional/detail/quadrilateral_H1.inl @@ -32,7 +32,8 @@ struct finite_element > { using residual_type = typename std::conditional, tensor >::type; - using dof_type = tensor; + using dof_type = tensor; + using dof_type_if = dof_type; using value_type = typename std::conditional >::type; using derivative_type = @@ -174,11 +175,11 @@ struct finite_element > { double phi_j = B(qx, jx) * B(qy, jy); tensor dphi_j_dxi = {G(qx, jx) * B(qy, jy), B(qx, jx) * G(qy, jy)}; - int Q = qy * q + qx; - auto& d00 = get<0>(get<0>(input(Q))); - auto& d01 = get<1>(get<0>(input(Q))); - auto& d10 = get<0>(get<1>(input(Q))); - auto& d11 = get<1>(get<1>(input(Q))); + int Q = qy * q + qx; + const auto& d00 = get<0>(get<0>(input(Q))); + const auto& d01 = get<1>(get<0>(input(Q))); + const auto& d10 = get<0>(get<1>(input(Q))); + const auto& d11 = get<1>(get<1>(input(Q))); output[Q] = {d00 * phi_j + dot(d01, dphi_j_dxi), d10 * phi_j + dot(d11, dphi_j_dxi)}; } diff --git a/src/serac/numerics/functional/detail/quadrilateral_Hcurl.inl b/src/serac/numerics/functional/detail/quadrilateral_Hcurl.inl index 815b5b72e..168d4fc0a 100644 --- a/src/serac/numerics/functional/detail/quadrilateral_Hcurl.inl +++ b/src/serac/numerics/functional/detail/quadrilateral_Hcurl.inl @@ -43,6 +43,7 @@ struct finite_element > { tensor x; tensor y; }; + using dof_type_if = dof_type; template using cpu_batched_values_type = tensor, q, q>; @@ -271,11 +272,11 @@ struct finite_element > { double curl_phi_j = (dir == 0) * -B1(qx, jx) * G2(qy, jy) + (dir == 1) * B1(qy, jy) * G2(qx, jx); - int Q = qy * q + qx; - auto& d00 = get<0>(get<0>(input(Q))); - auto& d01 = get<1>(get<0>(input(Q))); - auto& d10 = get<0>(get<1>(input(Q))); - auto& d11 = get<1>(get<1>(input(Q))); + int Q = qy * q + qx; + const auto& d00 = get<0>(get<0>(input(Q))); + const auto& d01 = get<1>(get<0>(input(Q))); + const auto& d10 = get<0>(get<1>(input(Q))); + const auto& d11 = get<1>(get<1>(input(Q))); output[Q] = {dot(d00, phi_j) + d01 * curl_phi_j, dot(d10, phi_j) + d11 * curl_phi_j}; } diff --git a/src/serac/numerics/functional/detail/quadrilateral_L2.inl b/src/serac/numerics/functional/detail/quadrilateral_L2.inl index 48ba045f0..30444d0ea 100644 --- a/src/serac/numerics/functional/detail/quadrilateral_L2.inl +++ b/src/serac/numerics/functional/detail/quadrilateral_L2.inl @@ -31,7 +31,8 @@ struct finite_element > { using residual_type = typename std::conditional, tensor >::type; - using dof_type = tensor; + using dof_type = tensor; + using dof_type_if = tensor; using value_type = typename std::conditional >::type; using derivative_type = @@ -173,11 +174,11 @@ struct finite_element > { double phi_j = B(qx, jx) * B(qy, jy); tensor dphi_j_dxi = {G(qx, jx) * B(qy, jy), B(qx, jx) * G(qy, jy)}; - int Q = qy * q + qx; - auto& d00 = get<0>(get<0>(input(Q))); - auto& d01 = get<1>(get<0>(input(Q))); - auto& d10 = get<0>(get<1>(input(Q))); - auto& d11 = get<1>(get<1>(input(Q))); + int Q = qy * q + qx; + const auto& d00 = get<0>(get<0>(input(Q))); + const auto& d01 = get<1>(get<0>(input(Q))); + const auto& d10 = get<0>(get<1>(input(Q))); + const auto& d11 = get<1>(get<1>(input(Q))); output[Q] = {d00 * phi_j + dot(d01, dphi_j_dxi), d10 * phi_j + dot(d11, dphi_j_dxi)}; } @@ -186,6 +187,39 @@ struct finite_element > { return output; } + template + static auto batch_apply_shape_fn_interior_face(int j, tensor input, const TensorProductQuadratureRule&) + { + static constexpr bool apply_weights = false; + static constexpr auto B = calculate_B(); + + using source0_t = decltype(get<0>(get<0>(T{})) + get<1>(get<0>(T{}))); + using source1_t = decltype(get<0>(get<1>(T{})) + get<1>(get<1>(T{}))); + + int jx = j % n; + int jy = (j % ndof) / n; + int s = j / ndof; + + tensor, q * q> output; + + for (int qy = 0; qy < q; qy++) { + for (int qx = 0; qx < q; qx++) { + double phi0_j = B(qx, jx) * B(qy, jy) * (s == 0); + double phi1_j = B(qx, jx) * B(qy, jy) * (s == 1); + + int Q = qy * q + qx; + const auto& d00 = get<0>(get<0>(input(Q))); + const auto& d01 = get<1>(get<0>(input(Q))); + const auto& d10 = get<0>(get<1>(input(Q))); + const auto& d11 = get<1>(get<1>(input(Q))); + + output[Q] = {d00 * phi0_j + d01 * phi1_j, d10 * phi0_j + d11 * phi1_j}; + } + } + + return output; + } + // we want to compute the following: // // X_q(u, v) := (B(u, i) * B(v, j)) * X_e(i, j) @@ -240,6 +274,57 @@ struct finite_element > { return output.one_dimensional; } + // overload for two-sided interior face kernels + template + SERAC_HOST_DEVICE static auto interpolate([[maybe_unused]] const dof_type_if& X, + const TensorProductQuadratureRule&) + { + static constexpr bool apply_weights = false; + static constexpr auto B = calculate_B(); + + tensor, q * q> output; + + tensor value{}; + + // side 0 + for (int i = 0; i < c; i++) { + auto A0 = contract<1, 1>(X(i, 0), B); + value(i) = contract<0, 1>(A0, B); + } + + for (int qy = 0; qy < q; qy++) { + for (int qx = 0; qx < q; qx++) { + if constexpr (c == 1) { + get<0>(output[qy * q + qx]) = value(0, qy, qx); + } else { + for (int i = 0; i < c; i++) { + get<0>(output[qy * q + qx])[i] = value(i, qy, qx); + } + } + } + } + + // side 1 + for (int i = 0; i < c; i++) { + auto A0 = contract<1, 1>(X(i, 1), B); + value(i) = contract<0, 1>(A0, B); + } + + for (int qy = 0; qy < q; qy++) { + for (int qx = 0; qx < q; qx++) { + if constexpr (c == 1) { + get<1>(output[qy * q + qx]) = value(0, qy, qx); + } else { + for (int i = 0; i < c; i++) { + get<1>(output[qy * q + qx])[i] = value(i, qy, qx); + } + } + } + } + + return output; + } + // source can be one of: {zero, double, tensor, tensor} // flux can be one of: {zero, tensor, tensor, tensor, // tensor} @@ -288,6 +373,48 @@ struct finite_element > { } } + template + SERAC_HOST_DEVICE static void integrate(const tensor, q * q>& qf_output, + const TensorProductQuadratureRule&, dof_type_if* element_residual, + [[maybe_unused]] int step = 1) + { + constexpr int ntrial = size(T{}) / c; + static constexpr bool apply_weights = true; + static constexpr auto B = calculate_B(); + + for (int j = 0; j < ntrial; j++) { + for (int i = 0; i < c; i++) { + tensor source; + + // side 0 + { + for (int qy = 0; qy < q; qy++) { + for (int qx = 0; qx < q; qx++) { + int Q = qy * q + qx; + source(qy, qx) = reinterpret_cast(&get<0>(qf_output[Q]))[i * ntrial + j]; + } + } + + auto A0 = contract<1, 0>(source, B); + element_residual[j * step](i, 0) += contract<0, 0>(A0, B); + } + + // side 1 + { + for (int qy = 0; qy < q; qy++) { + for (int qx = 0; qx < q; qx++) { + int Q = qy * q + qx; + source(qy, qx) = reinterpret_cast(&get<1>(qf_output[Q]))[i * ntrial + j]; + } + } + + auto A0 = contract<1, 0>(source, B); + element_residual[j * step](i, 1) += contract<0, 0>(A0, B); + } + } + } + } + #if 0 template diff --git a/src/serac/numerics/functional/detail/segment_H1.inl b/src/serac/numerics/functional/detail/segment_H1.inl index 11fe30bc2..5d6819b69 100644 --- a/src/serac/numerics/functional/detail/segment_H1.inl +++ b/src/serac/numerics/functional/detail/segment_H1.inl @@ -28,7 +28,8 @@ struct finite_element > { static constexpr int VALUE = 0, GRADIENT = 1; static constexpr int SOURCE = 0, FLUX = 1; - using dof_type = tensor; + using dof_type = tensor; + using dof_type_if = dof_type; using value_type = typename std::conditional >::type; using derivative_type = value_type; @@ -109,10 +110,10 @@ struct finite_element > { double phi_j = B(qx, jx); double dphi_j_dxi = G(qx, jx); - auto& d00 = get<0>(get<0>(input(qx))); - auto& d01 = get<1>(get<0>(input(qx))); - auto& d10 = get<0>(get<1>(input(qx))); - auto& d11 = get<1>(get<1>(input(qx))); + const auto& d00 = get<0>(get<0>(input(qx))); + const auto& d01 = get<1>(get<0>(input(qx))); + const auto& d10 = get<0>(get<1>(input(qx))); + const auto& d11 = get<1>(get<1>(input(qx))); output[qx] = {d00 * phi_j + d01 * dphi_j_dxi, d10 * phi_j + d11 * dphi_j_dxi}; } diff --git a/src/serac/numerics/functional/detail/segment_Hcurl.inl b/src/serac/numerics/functional/detail/segment_Hcurl.inl index 98677f684..0f52571bd 100644 --- a/src/serac/numerics/functional/detail/segment_Hcurl.inl +++ b/src/serac/numerics/functional/detail/segment_Hcurl.inl @@ -26,6 +26,9 @@ struct finite_element > { static constexpr int dim = 1; static constexpr int ndof = (p + 1); + using dof_type = tensor; + using dof_type_if = dof_type; + SERAC_HOST_DEVICE static constexpr tensor shape_functions(double xi) { return GaussLegendreInterpolation(xi); diff --git a/src/serac/numerics/functional/detail/segment_L2.inl b/src/serac/numerics/functional/detail/segment_L2.inl index d14603793..165b70fe2 100644 --- a/src/serac/numerics/functional/detail/segment_L2.inl +++ b/src/serac/numerics/functional/detail/segment_L2.inl @@ -28,7 +28,8 @@ struct finite_element > { static constexpr int VALUE = 0, GRADIENT = 1; static constexpr int SOURCE = 0, FLUX = 1; - using dof_type = tensor; + using dof_type = tensor; + using dof_type_if = tensor; using value_type = typename std::conditional >::type; using derivative_type = value_type; @@ -120,6 +121,69 @@ struct finite_element > { return output; } + template + static auto batch_apply_shape_fn_interior_face(int jx, tensor input, const TensorProductQuadratureRule&) + { + static constexpr bool apply_weights = false; + static constexpr auto B = calculate_B(); + + using source0_t = decltype(get<0>(get<0>(T{})) + get<1>(get<0>(T{}))); + using source1_t = decltype(get<0>(get<1>(T{})) + get<1>(get<1>(T{}))); + + tensor, q> output; + + for (int qx = 0; qx < q; qx++) { + int j = jx % ndof; + int s = jx / ndof; + + double phi0_j = B(qx, j) * (s == 0); + double phi1_j = B(qx, j) * (s == 1); + + const auto& d00 = get<0>(get<0>(input(qx))); + const auto& d01 = get<1>(get<0>(input(qx))); + const auto& d10 = get<0>(get<1>(input(qx))); + const auto& d11 = get<1>(get<1>(input(qx))); + + output[qx] = {d00 * phi0_j + d01 * phi1_j, d10 * phi0_j + d11 * phi1_j}; + } + + return output; + } + + template + SERAC_HOST_DEVICE static auto interpolate(const dof_type_if& X, const TensorProductQuadratureRule&) + { + static constexpr bool apply_weights = false; + static constexpr auto BT = transpose(calculate_B()); + + tensor values{}; + + tensor, q> output{}; + + // apply the shape functions + for (int i = 0; i < c; i++) { + values = dot(X[i][0], BT); + for (int qx = 0; qx < q; qx++) { + if constexpr (c == 1) { + get<0>(output[qx]) = values[qx]; + } else { + get<0>(output[qx])[i] = values[qx]; + } + } + + values = dot(X[i][1], BT); + for (int qx = 0; qx < q; qx++) { + if constexpr (c == 1) { + get<1>(output[qx]) = values[qx]; + } else { + get<1>(output[qx])[i] = values[qx]; + } + } + } + + return output; + } + template SERAC_HOST_DEVICE static auto interpolate(const dof_type& X, const TensorProductQuadratureRule&) { @@ -190,5 +254,33 @@ struct finite_element > { } } } + + template + SERAC_HOST_DEVICE static void integrate(const tensor, q>& qf_output, + const TensorProductQuadratureRule&, dof_type_if* element_residual, + [[maybe_unused]] int step = 1) + { + constexpr int ntrial = size(T{}) / c; + + using buffer_type = tensor; + + static constexpr bool apply_weights = true; + static constexpr auto B = calculate_B(); + + for (int j = 0; j < ntrial; j++) { + for (int i = 0; i < c; i++) { + buffer_type source_0; + buffer_type source_1; + + for (int qx = 0; qx < q; qx++) { + source_0(qx) = reinterpret_cast(&get<0>(qf_output[qx]))[i * ntrial + j]; + source_1(qx) = reinterpret_cast(&get<1>(qf_output[qx]))[i * ntrial + j]; + } + + element_residual[j * step](i, 0) += dot(source_0, B); + element_residual[j * step](i, 1) += dot(source_1, B); + } + } + } }; /// @endcond diff --git a/src/serac/numerics/functional/detail/tetrahedron_H1.inl b/src/serac/numerics/functional/detail/tetrahedron_H1.inl index 2a7613cee..74c97b3d6 100644 --- a/src/serac/numerics/functional/detail/tetrahedron_H1.inl +++ b/src/serac/numerics/functional/detail/tetrahedron_H1.inl @@ -349,10 +349,10 @@ struct finite_element > { double phi_j = shape_function(xi[i], j); tensor dphi_j_dxi = shape_function_gradient(xi[i], j); - auto& d00 = get<0>(get<0>(input(i))); - auto& d01 = get<1>(get<0>(input(i))); - auto& d10 = get<0>(get<1>(input(i))); - auto& d11 = get<1>(get<1>(input(i))); + const auto& d00 = get<0>(get<0>(input(i))); + const auto& d01 = get<1>(get<0>(input(i))); + const auto& d10 = get<0>(get<1>(input(i))); + const auto& d11 = get<1>(get<1>(input(i))); output[i] = {d00 * phi_j + dot(d01, dphi_j_dxi), d10 * phi_j + dot(d11, dphi_j_dxi)}; } diff --git a/src/serac/numerics/functional/detail/tetrahedron_L2.inl b/src/serac/numerics/functional/detail/tetrahedron_L2.inl index 25bc8ffd3..1434e77d6 100644 --- a/src/serac/numerics/functional/detail/tetrahedron_L2.inl +++ b/src/serac/numerics/functional/detail/tetrahedron_L2.inl @@ -354,10 +354,10 @@ struct finite_element > { double phi_j = shape_function(xi[i], j); tensor dphi_j_dxi = shape_function_gradient(xi[i], j); - auto& d00 = get<0>(get<0>(input(i))); - auto& d01 = get<1>(get<0>(input(i))); - auto& d10 = get<0>(get<1>(input(i))); - auto& d11 = get<1>(get<1>(input(i))); + const auto& d00 = get<0>(get<0>(input(i))); + const auto& d01 = get<1>(get<0>(input(i))); + const auto& d10 = get<0>(get<1>(input(i))); + const auto& d11 = get<1>(get<1>(input(i))); output[i] = {d00 * phi_j + dot(d01, dphi_j_dxi), d10 * phi_j + dot(d11, dphi_j_dxi)}; } diff --git a/src/serac/numerics/functional/detail/triangle_H1.inl b/src/serac/numerics/functional/detail/triangle_H1.inl index 37c1600fa..0c600bc8d 100644 --- a/src/serac/numerics/functional/detail/triangle_H1.inl +++ b/src/serac/numerics/functional/detail/triangle_H1.inl @@ -35,7 +35,8 @@ struct finite_element > { using residual_type = typename std::conditional, tensor >::type; - using dof_type = tensor; + using dof_type = tensor; + using dof_type_if = dof_type; using value_type = typename std::conditional >::type; using derivative_type = @@ -254,10 +255,10 @@ struct finite_element > { double phi_j = shape_function(xi[i], j); tensor dphi_j_dxi = shape_function_gradient(xi[i], j); - auto& d00 = get<0>(get<0>(input(i))); - auto& d01 = get<1>(get<0>(input(i))); - auto& d10 = get<0>(get<1>(input(i))); - auto& d11 = get<1>(get<1>(input(i))); + const auto& d00 = get<0>(get<0>(input(i))); + const auto& d01 = get<1>(get<0>(input(i))); + const auto& d10 = get<0>(get<1>(input(i))); + const auto& d11 = get<1>(get<1>(input(i))); output[i] = {d00 * phi_j + dot(d01, dphi_j_dxi), d10 * phi_j + dot(d11, dphi_j_dxi)}; } diff --git a/src/serac/numerics/functional/detail/triangle_L2.inl b/src/serac/numerics/functional/detail/triangle_L2.inl index 2030008da..bdbe60404 100644 --- a/src/serac/numerics/functional/detail/triangle_L2.inl +++ b/src/serac/numerics/functional/detail/triangle_L2.inl @@ -33,7 +33,8 @@ struct finite_element > { using residual_type = typename std::conditional, tensor >::type; - using dof_type = tensor; + using dof_type = tensor; + using dof_type_if = tensor; using value_type = typename std::conditional >::type; using derivative_type = @@ -249,7 +250,8 @@ struct finite_element > { } template - static auto batch_apply_shape_fn(int j, tensor input, const TensorProductQuadratureRule&) + static auto batch_apply_shape_fn(int j, const tensor& input, + const TensorProductQuadratureRule&) { using source_t = decltype(get<0>(get<0>(in_t{})) + dot(get<1>(get<0>(in_t{})), tensor{})); using flux_t = decltype(get<0>(get<1>(in_t{})) + dot(get<1>(get<1>(in_t{})), tensor{})); @@ -263,10 +265,10 @@ struct finite_element > { double phi_j = shape_function(xi[i], j); tensor dphi_j_dxi = shape_function_gradient(xi[i], j); - auto& d00 = get<0>(get<0>(input(i))); - auto& d01 = get<1>(get<0>(input(i))); - auto& d10 = get<0>(get<1>(input(i))); - auto& d11 = get<1>(get<1>(input(i))); + const auto& d00 = get<0>(get<0>(input(i))); + const auto& d01 = get<1>(get<0>(input(i))); + const auto& d10 = get<0>(get<1>(input(i))); + const auto& d11 = get<1>(get<1>(input(i))); output[i] = {d00 * phi_j + dot(d01, dphi_j_dxi), d10 * phi_j + dot(d11, dphi_j_dxi)}; } @@ -274,6 +276,36 @@ struct finite_element > { return output; } + template + static auto batch_apply_shape_fn_interior_face(int jx, const tensor& input, + const TensorProductQuadratureRule&) + { + constexpr auto xi = GaussLegendreNodes(); + + using source0_t = decltype(get<0>(get<0>(T{})) + get<1>(get<0>(T{}))); + using source1_t = decltype(get<0>(get<1>(T{})) + get<1>(get<1>(T{}))); + + static constexpr int Q = q * (q + 1) / 2; + tensor, Q> output; + + for (int i = 0; i < Q; i++) { + int j = jx % ndof; + int s = jx / ndof; + + double phi0_j = shape_function(xi[i], j) * (s == 0); + double phi1_j = shape_function(xi[i], j) * (s == 1); + + const auto& d00 = get<0>(get<0>(input(i))); + const auto& d01 = get<1>(get<0>(input(i))); + const auto& d10 = get<0>(get<1>(input(i))); + const auto& d11 = get<1>(get<1>(input(i))); + + output[i] = {d00 * phi0_j + d01 * phi1_j, d10 * phi0_j + d11 * phi1_j}; + } + + return output; + } + template SERAC_HOST_DEVICE static auto interpolate(const tensor& X, const TensorProductQuadratureRule&) { @@ -298,6 +330,33 @@ struct finite_element > { return output.flattened; } + // overload for two-sided interior face kernels + template + SERAC_HOST_DEVICE static auto interpolate(const dof_type_if& X, const TensorProductQuadratureRule&) + { + constexpr auto xi = GaussLegendreNodes(); + static constexpr int num_quadrature_points = q * (q + 1) / 2; + + tensor, num_quadrature_points> output{}; + + // apply the shape functions + for (int i = 0; i < c; i++) { + for (int j = 0; j < num_quadrature_points; j++) { + for (int k = 0; k < ndof; k++) { + if constexpr (c == 1) { + get<0>(output[j]) += X[i][0][k] * shape_function(xi[j], k); + get<1>(output[j]) += X[i][1][k] * shape_function(xi[j], k); + } else { + get<0>(output[j])[i] += X[i][0][k] * shape_function(xi[j], k); + get<1>(output[j])[i] += X[i][1][k] * shape_function(xi[j], k); + } + } + } + } + + return output; + } + template SERAC_HOST_DEVICE static void integrate(const tensor, q*(q + 1) / 2>& qf_output, const TensorProductQuadratureRule&, @@ -341,5 +400,33 @@ struct finite_element > { } } } + + template + SERAC_HOST_DEVICE static void integrate(const tensor, (q * (q + 1)) / 2>& qf_output, + const TensorProductQuadratureRule&, dof_type_if* element_residual, + [[maybe_unused]] int step = 1) + { + constexpr int ntrial = size(T{}) / c; + constexpr int num_quadrature_points = (q * (q + 1)) / 2; + constexpr auto integration_points = GaussLegendreNodes(); + constexpr auto integration_weights = GaussLegendreWeights(); + + for (int j = 0; j < ntrial; j++) { + for (int i = 0; i < c; i++) { + for (int Q = 0; Q < num_quadrature_points; Q++) { + tensor xi = integration_points[Q]; + double wt = integration_weights[Q]; + + double source_0 = reinterpret_cast(&get<0>(qf_output[Q]))[i * ntrial + j]; + double source_1 = reinterpret_cast(&get<1>(qf_output[Q]))[i * ntrial + j]; + + for (int k = 0; k < ndof; k++) { + element_residual[j * step](i, 0, k) += (source_0 * shape_function(xi, k)) * wt; + element_residual[j * step](i, 1, k) += (source_1 * shape_function(xi, k)) * wt; + } + } + } + } + } }; /// @endcond diff --git a/src/serac/numerics/functional/dof_numbering.hpp b/src/serac/numerics/functional/dof_numbering.hpp deleted file mode 100644 index 089e3065c..000000000 --- a/src/serac/numerics/functional/dof_numbering.hpp +++ /dev/null @@ -1,382 +0,0 @@ -// Copyright (c) 2019-2024, Lawrence Livermore National Security, LLC and -// other Serac Project Developers. See the top-level LICENSE file for -// details. -// -// SPDX-License-Identifier: (BSD-3-Clause) -#pragma once - -#include "mfem.hpp" - -#include "serac/infrastructure/accelerator.hpp" - -#include "serac/numerics/functional/integral.hpp" -#include "serac/numerics/functional/element_restriction.hpp" - -namespace serac { - -/** - * @brief a (poorly named) tuple of quantities used to discover the sparsity - * pattern associated with element and boundary element matrices. - * - * It stores information about how the entries of an element "stiffness" matrix - * map to the global stiffness. The operator< definition allows us to sort them - * lexicographically, to facilitate creating the CSR matrix graph. - */ -struct ElemInfo { - uint32_t global_row_; ///< The global row number - uint32_t global_col_; ///< The global column number - uint32_t local_row_; ///< The local row number - uint32_t local_col_; ///< The global column number - uint32_t element_id_; ///< The element ID - int sign_; ///< The orientation of the element - Domain::Type type; ///< Which kind of Integral this entry comes from -}; - -/** - * @brief operator for sorting lexicographically by {global_row, global_col} - * @param x the ElemInfo on the left - * @param y the ElemInfo on the right - */ -inline bool operator<(const ElemInfo& x, const ElemInfo& y) -{ - return (x.global_row_ < y.global_row_) || (x.global_row_ == y.global_row_ && x.global_col_ < y.global_col_); -} - -/** - * @brief operator determining inequality by {global_row, global_col} - * @param x the ElemInfo on the left - * @param y the ElemInfo on the right - */ -inline bool operator!=(const ElemInfo& x, const ElemInfo& y) -{ - return (x.global_row_ != y.global_row_) || (x.global_col_ != y.global_col_); -} - -/** - * @brief this type explicitly stores sign (typically used conveying edge/face orientation) and index values - * - * TODO: investigate implementation via bitfield (should have smaller memory footprint, better readability than mfem's - * {sign, index} int32_t encoding) - */ -struct SignedIndex { - /// the actual index of some quantity - uint32_t index_; - - /// whether or not the value associated with this index is positive or negative - int sign_; - - /// the implicit conversion to int extracts only the index - operator uint32_t() { return index_; } -}; - -/** - * @brief mfem will frequently encode {sign, index} into a single int32_t. - * This function decodes those values. - * - * @param i an integer that mfem has encoded to contain two separate pieces of information - */ -inline SignedIndex decodeSignedIndex(int i) -{ - return SignedIndex{static_cast((i >= 0) ? i : -1 - i), (i >= 0) ? 1 : -1}; -} - -/** - * @brief return whether or not the underlying function space is Hcurl or not - * - * @param fes the finite element space in question - */ -inline bool isHcurl(const mfem::ParFiniteElementSpace& fes) -{ - return (fes.FEColl()->GetContType() == mfem::FiniteElementCollection::TANGENTIAL); -} - -/** - * @brief return whether or not the underlying function space is L2 or not - * - * @param fes the finite element space in question - */ -inline bool isL2(const mfem::ParFiniteElementSpace& fes) -{ - return (fes.FEColl()->GetContType() == mfem::FiniteElementCollection::DISCONTINUOUS); -} - -/** - * @brief attempt to characterize which FiniteElementSpaces - * mfem::FaceRestriction actually works with - * - * @param fes the finite element space in question - */ -inline bool compatibleWithFaceRestriction(const mfem::ParFiniteElementSpace& fes) -{ - return !(isHcurl(fes) && fes.GetMesh()->Dimension() == 2) && !(isHcurl(fes) && fes.GetMesh()->Dimension() == 3) && - !(isL2(fes)) && fes.GetMesh()->GetNBE() > 0; -} - -/** - * @brief this is a (hopefully) temporary measure to work around the fact that mfem's - * support for querying information about boundary elements is inconsistent, or entirely - * unimplemented. If the finite element spaces both work with mfem::FaceRestriction, it will - * return a 3D array sized to store the boundary element gradient matrices, else the 3D array - * will have dimensions 0x0x0 to indicate that it is unused. - * - * @param trial_fes the trial finite element space - * @param test_fes the test finite element space - * - * known issues: getting dofs/ids for boundary elements in 2D w/ Hcurl spaces - * getting dofs/ids for boundary elements in 2D,3D w/ L2 spaces - */ -template -ExecArray allocateMemoryForBdrElementGradients(const mfem::ParFiniteElementSpace& trial_fes, - const mfem::ParFiniteElementSpace& test_fes) -{ - auto* test_BE = test_fes.GetBE(0); - auto* trial_BE = trial_fes.GetBE(0); - return {static_cast(trial_fes.GetNFbyType(mfem::FaceType::Boundary)), - static_cast(test_BE->GetDof() * test_fes.GetVDim()), - static_cast(trial_BE->GetDof() * trial_fes.GetVDim())}; -} - -/// @overload -template -ExecArray allocateMemoryForBdrElementGradients(const mfem::ParFiniteElementSpace& fes) -{ - if (compatibleWithFaceRestriction(fes)) { - auto* BE = fes.GetBE(0); - return {static_cast(fes.GetNFbyType(mfem::FaceType::Boundary)), - static_cast(BE->GetDof() * fes.GetVDim())}; - } else { - return {0, 0}; - } -} - -/** - * @brief this object extracts the dofs for each element in a FiniteElementSpace as a 2D array such that - * element_dofs_(e, i) will be the `i`th dof of element `e`. - * - * Note: due to an internal inconsistency between mfem::FiniteElementSpace and mfem::FaceRestriction, - * we choose to use the Restriction operator as the "source of truth", since we are also using its - * convention for quadrature point numbering. - */ - -struct DofNumbering { - /** - * @param fespace the finite element space to extract dof numbers from - * - * @brief create lookup tables of which degrees of freedom correspond to - * each element and boundary element - */ - DofNumbering(const mfem::ParFiniteElementSpace& fespace) - : element_dofs_(static_cast(fespace.GetNE()), - static_cast(fespace.GetFE(0)->GetDof() * fespace.GetVDim())), - bdr_element_dofs_(allocateMemoryForBdrElementGradients(fespace)) - { - int dim = fespace.GetMesh()->Dimension(); - mfem::Geometry::Type elem_geom[4] = {mfem::Geometry::INVALID, mfem::Geometry::SEGMENT, mfem::Geometry::SQUARE, - mfem::Geometry::CUBE}; - ElementRestriction dofs(&fespace, elem_geom[dim]); - ElementRestriction boundary_dofs(&fespace, elem_geom[dim - 1], FaceType::BOUNDARY); - - { - auto elem_restriction = fespace.GetElementRestriction(mfem::ElementDofOrdering::LEXICOGRAPHIC); - - mfem::Vector iota(elem_restriction->Width()); - mfem::Vector dof_ids(elem_restriction->Height()); - dof_ids = 0.0; - for (int i = 0; i < iota.Size(); i++) { - iota[i] = i + 1; // note: 1-based index - } - - // we're using Mult() to reveal the locations nonzero entries - // in the restriction operator, since that information is not - // made available through its public interface - // - // TODO: investigate refactoring mfem's restriction operators - // to provide this information in more natural way. - elem_restriction->Mult(iota, dof_ids); - const double* dof_ids_h = dof_ids.HostRead(); - - int index = 0; - for (axom::IndexType e = 0; e < element_dofs_.shape()[0]; e++) { - for (axom::IndexType i = 0; i < element_dofs_.shape()[1]; i++) { - uint32_t dof_id = static_cast(fabs(dof_ids_h[index])); // note: 1-based index - int dof_sign = dof_ids[index] > 0 ? +1 : -1; - element_dofs_(e, i) = {dof_id - 1, dof_sign}; // subtract 1 to get back to 0-based index - index++; - } - } - } - - if (bdr_element_dofs_.size() > 0) { - auto face_restriction = fespace.GetFaceRestriction(mfem::ElementDofOrdering::LEXICOGRAPHIC, - mfem::FaceType::Boundary, mfem::L2FaceValues::SingleValued); - - mfem::Vector iota(face_restriction->Width()); - mfem::Vector dof_ids(face_restriction->Height()); - for (int i = 0; i < iota.Size(); i++) { - iota[i] = i + 1; // note: 1-based index - } - - face_restriction->Mult(iota, dof_ids); - const double* dof_ids_h = dof_ids.HostRead(); - - int index = 0; - for (axom::IndexType e = 0; e < bdr_element_dofs_.shape()[0]; e++) { - for (axom::IndexType i = 0; i < bdr_element_dofs_.shape()[1]; i++) { - uint32_t dof_id = static_cast(fabs(dof_ids_h[index])); // note: 1-based index - int dof_sign = dof_ids[index] > 0 ? +1 : -1; - bdr_element_dofs_(e, i) = {dof_id - 1, dof_sign}; // subtract 1 to get back to 0-based index - index++; - } - } - } - } - - /// @brief element_dofs_(e, i) stores the `i`th dof of element `e`. - CPUArray element_dofs_; - - /// @brief bdr_element_dofs_(b, i) stores the `i`th dof of boundary element `b`. - CPUArray bdr_element_dofs_; -}; - -/** - * @brief this object figures out the sparsity pattern associated with a finite element discretization - * of the given test and trial function spaces, and records which nonzero each element "stiffness" - * matrix maps to, to facilitate assembling the element matrices into the global sparse matrix. e.g. - * - * element_nonzero_LUT(e, i, j) says where (in the global sparse matrix) - * to put the (i,j) component of the matrix associated with element element matrix `e` - * - * Note: due to an internal inconsistency between mfem::FiniteElementSpace and mfem::FaceRestriction, - * we choose to use the Restriction operator as the "source of truth", since we are also using its - * convention for quadrature point numbering. - */ -struct GradientAssemblyLookupTables { - /// @brief a type for representing a nonzero entry in a sparse matrix - struct Entry { - uint32_t row; ///< row value for this nonzero Entry - uint32_t column; ///< column value for this nonzero Entry - - /// operator< is used when sorting `Entry`. Lexicographical ordering - bool operator<(const Entry& other) const - { - return (row < other.row) || ((row == other.row) && (column < other.column)); - } - - /// operator== is required for use in `std::unordered_map` - bool operator==(const Entry& other) const { return (row == other.row && column == other.column); } - - /// hash functor required for use in `std::unordered_map` - struct Hasher { - /// @brief a hash function implementation for `Entry` - std::size_t operator()(const Entry& k) const - { - std::size_t seed = std::hash()(k.row); - seed ^= std::hash()(k.column) + 0x9e3779b9 + (seed << 6) + (seed >> 2); - return seed; - } - }; - }; - - /// dummy default ctor to enable deferred initialization - GradientAssemblyLookupTables() : initialized{false} {}; - - /** - * @param block_test_dofs object containing information about dofs for the test space - * @param block_trial_dofs object containing information about dofs for the trial space - * - * @brief create lookup tables describing which degrees of freedom - * correspond to each domain/boundary element - */ - void init(const serac::BlockElementRestriction& block_test_dofs, - const serac::BlockElementRestriction& block_trial_dofs) - { - // we start by having each element and boundary element emit the (i,j) entry that it - // touches in the global "stiffness matrix", and also keep track of some metadata about - // which element and which dof are associated with that particular nonzero entry - for (const auto& [geometry, trial_dofs] : block_trial_dofs.restrictions) { - const auto& test_dofs = block_test_dofs.restrictions.at(geometry); - - std::vector test_vdofs(test_dofs.nodes_per_elem * test_dofs.components); - std::vector trial_vdofs(trial_dofs.nodes_per_elem * trial_dofs.components); - - auto num_elements = static_cast(trial_dofs.num_elements); - for (uint32_t e = 0; e < num_elements; e++) { - for (uint64_t i = 0; i < uint64_t(test_dofs.dof_info.shape()[1]); i++) { - auto test_dof = test_dofs.dof_info(e, i); - - for (uint64_t j = 0; j < uint64_t(trial_dofs.dof_info.shape()[1]); j++) { - auto trial_dof = trial_dofs.dof_info(e, j); - - for (uint64_t k = 0; k < test_dofs.components; k++) { - uint32_t test_global_id = uint32_t(test_dofs.GetVDof(test_dof, k).index()); - for (uint64_t l = 0; l < trial_dofs.components; l++) { - uint32_t trial_global_id = uint32_t(trial_dofs.GetVDof(trial_dof, l).index()); - nz_LUT[{test_global_id, trial_global_id}] = 0; // just store the keys initially - } - } - } - } - } - } - - std::vector entries(nz_LUT.size()); - - uint32_t count = 0; - for (auto [key, value] : nz_LUT) { - entries[count++] = key; - } - - std::sort(entries.begin(), entries.end()); - - nnz = static_cast(nz_LUT.size()); - row_ptr.resize(static_cast(block_test_dofs.LSize() + 1)); - col_ind.resize(nnz); - - row_ptr[0] = 0; - col_ind[0] = int(entries[0].column); - - for (uint32_t i = 1; i < nnz; i++) { - nz_LUT[entries[i]] = i; - col_ind[i] = int(entries[i].column); - - // if the new entry has a different row, then the row_ptr offsets must be set as well - for (uint32_t j = entries[i - 1].row; j < entries[i].row; j++) { - row_ptr[j + 1] = int(i); - } - } - - row_ptr.back() = static_cast(nnz); - - initialized = true; - } - - /** - * @brief return the index (into the nonzero entries) corresponding to entry (i,j) - * @param i the row - * @param j the column - */ - uint32_t operator()(int i, int j) const { return nz_LUT.at({uint32_t(i), uint32_t(j)}); } - - /// @brief how many nonzero entries appear in the sparse matrix - uint32_t nnz; - - /** - * @brief array holding the offsets for a given row of the sparse matrix - * i.e. row r corresponds to the indices [row_ptr[r], row_ptr[r+1]) - */ - std::vector row_ptr; - - /// @brief array holding the column associated with each nonzero entry - std::vector col_ind; - - /** - * @brief `nz_LUT` returns the index of the `col_ind` / `value` CSR arrays - * corresponding to the (i,j) entry - */ - std::unordered_map nz_LUT; - - /// @brief specifies if the table has already been initialized or not - bool initialized; -}; - -} // namespace serac diff --git a/src/serac/numerics/functional/domain.cpp b/src/serac/numerics/functional/domain.cpp index 55dfe9f81..49fbbb64b 100644 --- a/src/serac/numerics/functional/domain.cpp +++ b/src/serac/numerics/functional/domain.cpp @@ -8,7 +8,7 @@ * @file domain.hpp * * @brief many of the functions in this file amount to extracting - * element indices from an mfem::Mesh like + * element indices from an mesh_t like * * | mfem::Geometry | mfem element id | tri id | quad id | * | -------------- | --------------- | ------ | ------- | @@ -52,7 +52,7 @@ std::vector> gather(const mfem::Vector& coordinates, mfem::Arr /////////////////////////////////////////////////////////////////////////////////////// template -static Domain domain_of_edges(const mfem::Mesh& mesh, std::function predicate) +static Domain domain_of_edges(const mesh_t& mesh, std::function predicate) { assert(mesh.SpaceDimension() == d); @@ -91,12 +91,12 @@ static Domain domain_of_edges(const mfem::Mesh& mesh, std::function predicate return output; } -Domain Domain::ofEdges(const mfem::Mesh& mesh, std::function, int)> func) +Domain Domain::ofEdges(const mesh_t& mesh, std::function, int)> func) { return domain_of_edges<2>(mesh, func); } -Domain Domain::ofEdges(const mfem::Mesh& mesh, std::function)> func) +Domain Domain::ofEdges(const mesh_t& mesh, std::function)> func) { return domain_of_edges<3>(mesh, func); } @@ -105,8 +105,7 @@ Domain Domain::ofEdges(const mfem::Mesh& mesh, std::function -static Domain domain_of_faces(const mfem::Mesh& mesh, - std::function>, int)> predicate) +static Domain domain_of_faces(const mesh_t& mesh, std::function>, int)> predicate) { assert(mesh.SpaceDimension() == d); @@ -172,12 +171,12 @@ static Domain domain_of_faces(const mfem::Mesh& return output; } -Domain Domain::ofFaces(const mfem::Mesh& mesh, std::function, int)> func) +Domain Domain::ofFaces(const mesh_t& mesh, std::function, int)> func) { return domain_of_faces(mesh, func); } -Domain Domain::ofFaces(const mfem::Mesh& mesh, std::function, int)> func) +Domain Domain::ofFaces(const mesh_t& mesh, std::function, int)> func) { return domain_of_faces(mesh, func); } @@ -186,8 +185,7 @@ Domain Domain::ofFaces(const mfem::Mesh& mesh, std::function -static Domain domain_of_elems(const mfem::Mesh& mesh, - std::function>, int)> predicate) +static Domain domain_of_elems(const mesh_t& mesh, std::function>, int)> predicate) { assert(mesh.SpaceDimension() == d); @@ -249,12 +247,12 @@ static Domain domain_of_elems(const mfem::Mesh& return output; } -Domain Domain::ofElements(const mfem::Mesh& mesh, std::function, int)> func) +Domain Domain::ofElements(const mesh_t& mesh, std::function, int)> func) { return domain_of_elems<2>(mesh, func); } -Domain Domain::ofElements(const mfem::Mesh& mesh, std::function, int)> func) +Domain Domain::ofElements(const mesh_t& mesh, std::function, int)> func) { return domain_of_elems<3>(mesh, func); } @@ -296,7 +294,7 @@ void Domain::addElements(const std::vector& geom_ids, const std::vector -static Domain domain_of_boundary_elems(const mfem::Mesh& mesh, +static Domain domain_of_boundary_elems(const mesh_t& mesh, std::function>, int)> predicate) { assert(mesh.SpaceDimension() == d); @@ -359,20 +357,17 @@ static Domain domain_of_boundary_elems(const mfem::Mesh& return output; } -Domain Domain::ofBoundaryElements(const mfem::Mesh& mesh, std::function, int)> func) +Domain Domain::ofBoundaryElements(const mesh_t& mesh, std::function, int)> func) { return domain_of_boundary_elems<2>(mesh, func); } -Domain Domain::ofBoundaryElements(const mfem::Mesh& mesh, std::function, int)> func) +Domain Domain::ofBoundaryElements(const mesh_t& mesh, std::function, int)> func) { return domain_of_boundary_elems<3>(mesh, func); } -/////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////// - -mfem::Array Domain::dof_list(mfem::FiniteElementSpace* fes) const +mfem::Array Domain::dof_list(const serac::fes_t* fes) const { std::set dof_ids; mfem::Array elem_dofs; @@ -440,12 +435,22 @@ mfem::Array Domain::dof_list(mfem::FiniteElementSpace* fes) const return uniq_dof_ids; } +void Domain::insert_restriction(const serac::fes_t* fes, FunctionSpace space) +{ + // if we don't already have a BlockElementRestriction for this FunctionSpace, make one + if (restriction_operators.count(space) == 0) { + restriction_operators[space] = BlockElementRestriction(fes, *this); + } +} + +const BlockElementRestriction& Domain::get_restriction(FunctionSpace space) { return restriction_operators.at(space); }; + /////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////// -Domain EntireDomain(const mfem::Mesh& mesh) +Domain EntireDomain(const mesh_t& mesh) { switch (mesh.SpaceDimension()) { case 2: @@ -460,7 +465,7 @@ Domain EntireDomain(const mfem::Mesh& mesh) } } -Domain EntireBoundary(const mfem::Mesh& mesh) +Domain EntireBoundary(const mesh_t& mesh) { switch (mesh.SpaceDimension()) { case 2: @@ -475,61 +480,140 @@ Domain EntireBoundary(const mfem::Mesh& mesh) } } -/// @cond -using c_iter = std::vector::const_iterator; -using b_iter = std::back_insert_iterator>; -using set_op = std::function; +/// @brief constructs a domain from all the interior face elements in a mesh +Domain InteriorFaces(const mesh_t& mesh) +{ + Domain output{mesh, mesh.SpaceDimension() - 1, Domain::Type::InteriorFaces}; -set_op union_op = std::set_union; -set_op intersection_op = std::set_intersection; -set_op difference_op = std::set_difference; + int edge_id = 0; + int tri_id = 0; + int quad_id = 0; + + for (int f = 0; f < mesh.GetNumFaces(); f++) { + // discard faces with the wrong type + if (!mesh.GetFaceInformation(f).IsInterior()) continue; + + auto geom = mesh.GetFaceGeometry(f); + + switch (geom) { + case mfem::Geometry::SEGMENT: + output.edge_ids_.push_back(edge_id++); + output.mfem_edge_ids_.push_back(f); + break; + case mfem::Geometry::TRIANGLE: + output.tri_ids_.push_back(tri_id++); + output.mfem_tri_ids_.push_back(f); + break; + case mfem::Geometry::SQUARE: + output.quad_ids_.push_back(quad_id++); + output.mfem_quad_ids_.push_back(f); + break; + default: + SLIC_ERROR("unsupported element type"); + break; + } + } + + return output; +} + +/////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////// + +/// @cond +using int2 = std::tuple; +enum SET_OPERATION +{ + UNION, + INTERSECTION, + DIFFERENCE +}; /// @endcond +/// @brief combine a pair of arrays of ints into a single array of `int2`, see also: unzip() +void zip(std::vector& ab, const std::vector& a, const std::vector& b) +{ + ab.resize(a.size()); + for (uint32_t i = 0; i < a.size(); i++) { + ab[i] = {a[i], b[i]}; + } +} + +/// @brief split an array of `int2` into a pair of arrays of ints, see also: zip() +void unzip(const std::vector& ab, std::vector& a, std::vector& b) +{ + a.resize(ab.size()); + b.resize(ab.size()); + for (uint32_t i = 0; i < ab.size(); i++) { + auto ab_i = ab[i]; + a[i] = std::get<0>(ab_i); + b[i] = std::get<1>(ab_i); + } +} + /// @brief return a std::vector that is the result of applying (a op b) -std::vector set_operation(set_op op, const std::vector& a, const std::vector& b) +template +std::vector set_operation(SET_OPERATION op, const std::vector& a, const std::vector& b) { - std::vector output; - op(a.begin(), a.end(), b.begin(), b.end(), back_inserter(output)); - return output; + using c_iter = typename std::vector::const_iterator; + using b_iter = std::back_insert_iterator>; + using set_op = std::function; + + set_op combine; + if (op == SET_OPERATION::UNION) { + combine = std::set_union; + } + if (op == SET_OPERATION::INTERSECTION) { + combine = std::set_intersection; + } + if (op == SET_OPERATION::DIFFERENCE) { + combine = std::set_difference; + } + + std::vector combined; + combine(a.begin(), a.end(), b.begin(), b.end(), back_inserter(combined)); + return combined; } /// @brief return a Domain that is the result of applying (a op b) -Domain set_operation(set_op op, const Domain& a, const Domain& b) +Domain set_operation(SET_OPERATION op, const Domain& a, const Domain& b) { assert(&a.mesh_ == &b.mesh_); assert(a.dim_ == b.dim_); - Domain output{a.mesh_, a.dim_}; + Domain combined{a.mesh_, a.dim_}; using Ids = std::vector; auto apply_set_op = [&op](const Ids& x, const Ids& y) { return set_operation(op, x, y); }; - auto fill_output_lists = [apply_set_op, &output](const Ids& a_ids, const Ids& a_mfem_ids, const Ids& b_ids, - const Ids& b_mfem_ids, mfem::Geometry::Type g) { - auto output_ids = apply_set_op(a_ids, b_ids); - auto output_mfem_ids = apply_set_op(a_mfem_ids, b_mfem_ids); - output.addElements(output_ids, output_mfem_ids, g); + auto fill_combined_lists = [apply_set_op, &combined](const Ids& a_ids, const Ids& a_mfem_ids, const Ids& b_ids, + const Ids& b_mfem_ids, mfem::Geometry::Type g) { + auto combined_ids = apply_set_op(a_ids, b_ids); + auto combined_mfem_ids = apply_set_op(a_mfem_ids, b_mfem_ids); + combined.addElements(combined_ids, combined_mfem_ids, g); }; - if (output.dim_ == 1) { - fill_output_lists(a.edge_ids_, a.mfem_edge_ids_, b.edge_ids_, b.mfem_edge_ids_, mfem::Geometry::SEGMENT); + if (combined.dim_ == 1) { + fill_combined_lists(a.edge_ids_, a.mfem_edge_ids_, b.edge_ids_, b.mfem_edge_ids_, mfem::Geometry::SEGMENT); } - if (output.dim_ == 2) { - fill_output_lists(a.tri_ids_, a.mfem_tri_ids_, b.tri_ids_, b.mfem_tri_ids_, mfem::Geometry::TRIANGLE); - fill_output_lists(a.quad_ids_, a.mfem_quad_ids_, b.quad_ids_, b.mfem_quad_ids_, mfem::Geometry::SQUARE); + if (combined.dim_ == 2) { + fill_combined_lists(a.tri_ids_, a.mfem_tri_ids_, b.tri_ids_, b.mfem_tri_ids_, mfem::Geometry::TRIANGLE); + fill_combined_lists(a.quad_ids_, a.mfem_quad_ids_, b.quad_ids_, b.mfem_quad_ids_, mfem::Geometry::SQUARE); } - if (output.dim_ == 3) { - fill_output_lists(a.tet_ids_, a.mfem_tet_ids_, b.tet_ids_, b.mfem_tet_ids_, mfem::Geometry::TETRAHEDRON); - fill_output_lists(a.hex_ids_, a.mfem_hex_ids_, b.hex_ids_, b.mfem_hex_ids_, mfem::Geometry::CUBE); + if (combined.dim_ == 3) { + fill_combined_lists(a.tet_ids_, a.mfem_tet_ids_, b.tet_ids_, b.mfem_tet_ids_, mfem::Geometry::TETRAHEDRON); + fill_combined_lists(a.hex_ids_, a.mfem_hex_ids_, b.hex_ids_, b.mfem_hex_ids_, mfem::Geometry::CUBE); } - return output; + return combined; } -Domain operator|(const Domain& a, const Domain& b) { return set_operation(union_op, a, b); } -Domain operator&(const Domain& a, const Domain& b) { return set_operation(intersection_op, a, b); } -Domain operator-(const Domain& a, const Domain& b) { return set_operation(difference_op, a, b); } +Domain operator|(const Domain& a, const Domain& b) { return set_operation(SET_OPERATION::UNION, a, b); } +Domain operator&(const Domain& a, const Domain& b) { return set_operation(SET_OPERATION::INTERSECTION, a, b); } +Domain operator-(const Domain& a, const Domain& b) { return set_operation(SET_OPERATION::DIFFERENCE, a, b); } } // namespace serac diff --git a/src/serac/numerics/functional/domain.hpp b/src/serac/numerics/functional/domain.hpp index 7ecfdcfae..8e1fb6cea 100644 --- a/src/serac/numerics/functional/domain.hpp +++ b/src/serac/numerics/functional/domain.hpp @@ -7,12 +7,18 @@ #pragma once #include + #include "mfem.hpp" #include "serac/numerics/functional/tensor.hpp" +#include "serac/numerics/functional/finite_element.hpp" +#include "serac/numerics/functional/element_restriction.hpp" +#include "serac/numerics/functional/typedefs.hpp" namespace serac { +struct BlockElementRestriction; + /** * @brief a class for representing a geometric region that can be used for integration * @@ -23,13 +29,14 @@ struct Domain { enum Type { Elements, - BoundaryElements + BoundaryElements, + InteriorFaces }; - static constexpr int num_types = 2; ///< the number of entries in the Type enum + static constexpr int num_types = 3; ///< the number of entries in the Type enum /// @brief the underyling mesh for this domain - const mfem::Mesh& mesh_; + const mesh_t& mesh_; /// @brief the geometric dimension of the domain int dim_; @@ -82,8 +89,17 @@ struct Domain { std::vector mfem_hex_ids_; /// @endcond - /// @brief construct an "empty" domain, to later be populated later with addElement member functions - Domain(const mfem::Mesh& m, int d, Type type = Domain::Type::Elements) : mesh_(m), dim_(d), type_(type) {} + /** + * @brief a collection of restriction operators for the different test/trial spaces appearing in + * integrals evaluated over this Domain. These are stored on the Domain itself to avoid duplicating + * these restriction operators in each Integral over a given Domain. + */ + std::map restriction_operators; + + /** + * @brief empty Domain constructor, with connectivity info to be populated later + */ + Domain(const mesh_t& m, int d, Type type = Domain::Type::Elements) : mesh_(m), dim_(d), type_(type) {} /** * @brief create a domain from some subset of the vertices in an mfem::Mesh @@ -91,10 +107,10 @@ struct Domain { * @param func predicate function for determining which vertices will be * included in this domain. The function's argument is the spatial position of the vertex. */ - static Domain ofVertices(const mfem::Mesh& mesh, std::function func); + static Domain ofVertices(const mesh_t& mesh, std::function func); /// @overload - static Domain ofVertices(const mfem::Mesh& mesh, std::function func); + static Domain ofVertices(const mesh_t& mesh, std::function func); /** * @brief create a domain from some subset of the edges in an mfem::Mesh @@ -103,10 +119,10 @@ struct Domain { * included in this domain. The function's arguments are the list of vertex coordinates and * an attribute index (if appropriate). */ - static Domain ofEdges(const mfem::Mesh& mesh, std::function, int)> func); + static Domain ofEdges(const mesh_t& mesh, std::function, int)> func); /// @overload - static Domain ofEdges(const mfem::Mesh& mesh, std::function)> func); + static Domain ofEdges(const mesh_t& mesh, std::function)> func); /** * @brief create a domain from some subset of the faces in an mfem::Mesh @@ -115,10 +131,10 @@ struct Domain { * included in this domain. The function's arguments are the list of vertex coordinates and * an attribute index (if appropriate). */ - static Domain ofFaces(const mfem::Mesh& mesh, std::function, int)> func); + static Domain ofFaces(const mesh_t& mesh, std::function, int)> func); /// @overload - static Domain ofFaces(const mfem::Mesh& mesh, std::function, int)> func); + static Domain ofFaces(const mesh_t& mesh, std::function, int)> func); /** * @brief create a domain from some subset of the elements (spatial dim == geometry dim) in an mfem::Mesh @@ -127,20 +143,20 @@ struct Domain { * included in this domain. The function's arguments are the list of vertex coordinates and * an attribute index (if appropriate). */ - static Domain ofElements(const mfem::Mesh& mesh, std::function, int)> func); + static Domain ofElements(const mesh_t& mesh, std::function, int)> func); /// @overload - static Domain ofElements(const mfem::Mesh& mesh, std::function, int)> func); + static Domain ofElements(const mesh_t& mesh, std::function, int)> func); /** * @brief create a domain from some subset of the boundary elements (spatial dim == geometry dim + 1) in an mfem::Mesh * @param mesh the entire mesh * @param func predicate function for determining which boundary elements will be included in this domain */ - static Domain ofBoundaryElements(const mfem::Mesh& mesh, std::function, int)> func); + static Domain ofBoundaryElements(const mesh_t& mesh, std::function, int)> func); /// @overload - static Domain ofBoundaryElements(const mfem::Mesh& mesh, std::function, int)> func); + static Domain ofBoundaryElements(const mesh_t& mesh, std::function, int)> func); /// @brief get elements by geometry type const std::vector& get(mfem::Geometry::Type geom) const @@ -154,8 +170,65 @@ struct Domain { exit(1); } + /// @brief get elements by geometry type + const std::vector& get_mfem_ids(mfem::Geometry::Type geom) const + { + if (geom == mfem::Geometry::SEGMENT) return mfem_edge_ids_; + if (geom == mfem::Geometry::TRIANGLE) return mfem_tri_ids_; + if (geom == mfem::Geometry::SQUARE) return mfem_quad_ids_; + if (geom == mfem::Geometry::TETRAHEDRON) return mfem_tet_ids_; + if (geom == mfem::Geometry::CUBE) return mfem_hex_ids_; + + exit(1); + } + + /** + * @brief returns how many elements of any type belong to this domain + */ + int total_elements() const + { + return int(edge_ids_.size() + tri_ids_.size() + quad_ids_.size() + tet_ids_.size() + hex_ids_.size()); + } + + /** + * @brief returns an array of the prefix sum of element counts belonging to this domain. + * Primarily intended to be used in mfem::BlockVector::Update(double * data, mfem::Array bOffsets); + */ + mfem::Array bOffsets() const + { + mfem::Array offsets(mfem::Geometry::NUM_GEOMETRIES + 1); + + int total = 0; + offsets[mfem::Geometry::POINT] = total; + total += 0; // vertices; + offsets[mfem::Geometry::SEGMENT] = total; + total += int(edge_ids_.size()); + offsets[mfem::Geometry::TRIANGLE] = total; + total += int(tri_ids_.size()); + offsets[mfem::Geometry::SQUARE] = total; + total += int(quad_ids_.size()); + offsets[mfem::Geometry::TETRAHEDRON] = total; + total += int(tet_ids_.size()); + offsets[mfem::Geometry::CUBE] = total; + total += int(hex_ids_.size()); + offsets[mfem::Geometry::PRISM] = total; + offsets[mfem::Geometry::PYRAMID] = total; + offsets[mfem::Geometry::NUM_GEOMETRIES] = total; + + return offsets; + } + /// @brief get mfem degree of freedom list for a given FiniteElementSpace - mfem::Array dof_list(mfem::FiniteElementSpace* fes) const; + mfem::Array dof_list(const fes_t* fes) const; + + /** + * @brief create a restriction operator over this domain, using its FunctionSpace as a key + * @note if a restriction for the given key (i.e. FunctionSpace) already exists, this function does nothing + */ + void insert_restriction(const fes_t* fes, FunctionSpace space); + + /// @brief getter for accessing a restriction operator by its function space + const BlockElementRestriction& get_restriction(FunctionSpace space); /// @brief Add an element to the domain /// @@ -174,10 +247,13 @@ struct Domain { }; /// @brief constructs a domain from all the elements in a mesh -Domain EntireDomain(const mfem::Mesh& mesh); +Domain EntireDomain(const mesh_t& mesh); /// @brief constructs a domain from all the boundary elements in a mesh -Domain EntireBoundary(const mfem::Mesh& mesh); +Domain EntireBoundary(const mesh_t& mesh); + +/// @brief constructs a domain from all the interior face elements in a mesh +Domain InteriorFaces(const mesh_t& mesh); /// @brief create a new domain that is the union of `a` and `b` Domain operator|(const Domain& a, const Domain& b); @@ -195,4 +271,34 @@ inline auto by_attr(int value) return [value](std::vector >, int attr) { return attr == value; }; } +/** + * @brief count the number of elements of each geometry in a domain + * @param domain the domain to count + */ +inline std::array geometry_counts(const Domain& domain) +{ + std::array counts{}; + + constexpr std::array geometries = {mfem::Geometry::SEGMENT, mfem::Geometry::TRIANGLE, + mfem::Geometry::SQUARE, mfem::Geometry::TETRAHEDRON, + mfem::Geometry::CUBE}; + for (auto geom : geometries) { + counts[uint32_t(geom)] = uint32_t(domain.get(geom).size()); + } + return counts; +} + +/** + * @brief convenience function for computing the arithmetic mean of some list of vectors + */ +template +inline tensor average(std::vector >& positions) +{ + tensor total{}; + for (auto x : positions) { + total += x; + } + return total / double(positions.size()); +} + } // namespace serac diff --git a/src/serac/numerics/functional/domain_integral_kernels.hpp b/src/serac/numerics/functional/domain_integral_kernels.hpp index c218025fb..74a749c1d 100644 --- a/src/serac/numerics/functional/domain_integral_kernels.hpp +++ b/src/serac/numerics/functional/domain_integral_kernels.hpp @@ -156,8 +156,8 @@ void evaluation_kernel_impl(trial_element_tuple trial_elements, test_element, do const std::vector& inputs, double* outputs, const double* positions, const double* jacobians, lambda_type qf, [[maybe_unused]] axom::ArrayView qf_state, - [[maybe_unused]] derivative_type* qf_derivatives, const int* elements, - uint32_t num_elements, bool update_state, camp::int_seq) + [[maybe_unused]] derivative_type* qf_derivatives, uint32_t num_elements, bool update_state, + camp::int_seq) { // mfem provides this information as opaque arrays of doubles, // so we reinterpret the pointer with @@ -180,7 +180,7 @@ void evaluation_kernel_impl(trial_element_tuple trial_elements, test_element, do //[[maybe_unused]] static constexpr trial_element_tuple trial_element_tuple{}; // batch-calculate values / derivatives of each trial space, at each quadrature point [[maybe_unused]] tuple qf_inputs = {promote_each_to_dual_when( - get(trial_elements).interpolate(get(u)[elements[e]], rule))...}; + get(trial_elements).interpolate(get(u)[e], rule))...}; // use J_e to transform values / derivatives on the parent element // to the to the corresponding values / derivatives on the physical element @@ -213,7 +213,7 @@ void evaluation_kernel_impl(trial_element_tuple trial_elements, test_element, do } // (batch) integrate the material response against the test-space basis functions - test_element::integrate(get_value(qf_outputs), rule, &r[elements[e]]); + test_element::integrate(get_value(qf_outputs), rule, &r[e]); } return; @@ -275,8 +275,7 @@ SERAC_HOST_DEVICE auto batch_apply_chain_rule(derivative_type* qf_derivatives, c */ template -void action_of_gradient_kernel(const double* dU, double* dR, derivatives_type* qf_derivatives, const int* elements, - std::size_t num_elements) +void action_of_gradient_kernel(const double* dU, double* dR, derivatives_type* qf_derivatives, std::size_t num_elements) { using test_element = finite_element; using trial_element = finite_element; @@ -293,13 +292,13 @@ void action_of_gradient_kernel(const double* dU, double* dR, derivatives_type* q // for each element in the domain for (uint32_t e = 0; e < num_elements; e++) { // (batch) interpolate each quadrature point's value - auto qf_inputs = trial_element::interpolate(du[elements[e]], rule); + auto qf_inputs = trial_element::interpolate(du[e], rule); // (batch) evalute the q-function at each quadrature point auto qf_outputs = batch_apply_chain_rule(qf_derivatives + e * num_qpts, qf_inputs); // (batch) integrate the material response against the test-space basis functions - test_element::integrate(qf_outputs, rule, &dr[elements[e]]); + test_element::integrate(qf_outputs, rule, &dr[e]); } } @@ -326,7 +325,7 @@ void action_of_gradient_kernel(const double* dU, double* dR, derivatives_type* q */ template void element_gradient_kernel(ExecArrayView dK, derivatives_type* qf_derivatives, - const int* elements, std::size_t num_elements) + std::size_t num_elements) { // quantities of interest have no flux term, so we pad the derivative // tuple with a "zero" type in the second position to treat it like the standard case @@ -342,7 +341,7 @@ void element_gradient_kernel(ExecArrayView dK, d // for each element in the domain for (uint32_t e = 0; e < num_elements; e++) { - auto* output_ptr = reinterpret_cast(&dK(elements[e], 0, 0)); + auto* output_ptr = reinterpret_cast(&dK(e, 0, 0)); tensor derivatives{}; for (int q = 0; q < nquad; q++) { @@ -364,36 +363,36 @@ template auto evaluation_kernel(signature s, const lambda_type& qf, const double* positions, const double* jacobians, std::shared_ptr> qf_state, - std::shared_ptr qf_derivatives, const int* elements, uint32_t num_elements) + std::shared_ptr qf_derivatives, uint32_t num_elements) { auto trial_elements = trial_elements_tuple(s); auto test_element = get_test_element(s); return [=](double time, const std::vector& inputs, double* outputs, bool update_state) { domain_integral::evaluation_kernel_impl( trial_elements, test_element, time, inputs, outputs, positions, jacobians, qf, (*qf_state)[geom], - qf_derivatives.get(), elements, num_elements, update_state, s.index_seq); + qf_derivatives.get(), num_elements, update_state, s.index_seq); }; } template std::function jacobian_vector_product_kernel( - signature, std::shared_ptr qf_derivatives, const int* elements, uint32_t num_elements) + signature, std::shared_ptr qf_derivatives, uint32_t num_elements) { return [=](const double* du, double* dr) { using test_space = typename signature::return_type; using trial_space = typename std::tuple_element::type; - action_of_gradient_kernel(du, dr, qf_derivatives.get(), elements, num_elements); + action_of_gradient_kernel(du, dr, qf_derivatives.get(), num_elements); }; } template std::function)> element_gradient_kernel( - signature, std::shared_ptr qf_derivatives, const int* elements, uint32_t num_elements) + signature, std::shared_ptr qf_derivatives, uint32_t num_elements) { return [=](ExecArrayView K_elem) { using test_space = typename signature::return_type; using trial_space = typename std::tuple_element::type; - element_gradient_kernel(K_elem, qf_derivatives.get(), elements, num_elements); + element_gradient_kernel(K_elem, qf_derivatives.get(), num_elements); }; } diff --git a/src/serac/numerics/functional/element_restriction.cpp b/src/serac/numerics/functional/element_restriction.cpp index 9068da405..d5e721312 100644 --- a/src/serac/numerics/functional/element_restriction.cpp +++ b/src/serac/numerics/functional/element_restriction.cpp @@ -4,6 +4,9 @@ #include "serac/numerics/functional/geometry.hpp" +// TODO REMOVE AFTER DEBUGGING +#include "serac/infrastructure/mpi_fstream.hpp" + std::vector > lexicographic_permutations(int p) { // p == 0 is admissible for L2 spaces, but lexicographic permutations @@ -213,8 +216,7 @@ std::vector > geom_local_face_dofs(int p) return output; } -axom::Array GetElementRestriction(const mfem::FiniteElementSpace* fes, - mfem::Geometry::Type geom) +axom::Array GetElementRestriction(const serac::fes_t* fes, mfem::Geometry::Type geom) { std::vector elem_dofs{}; mfem::Mesh* mesh = fes->GetMesh(); @@ -276,8 +278,74 @@ axom::Array GetElementRestriction(const mfem::F } } -axom::Array GetFaceDofs(const mfem::FiniteElementSpace* fes, - mfem::Geometry::Type face_geom, FaceType type) +axom::Array GetElementDofs(const serac::fes_t* fes, mfem::Geometry::Type geom, + const std::vector& mfem_elem_ids) + +{ + std::vector elem_dofs{}; + mfem::Mesh* mesh = fes->GetMesh(); + + // note: this assumes that all the elements are the same polynomial order + int p = fes->GetElementOrder(0); + std::vector > lex_perm = lexicographic_permutations(p); + + uint64_t n = 0; + + for (auto elem : mfem_elem_ids) { + // discard elements with the wrong geometry + if (mesh->GetElementGeometry(elem) != geom) { + SLIC_ERROR("encountered incorrect element geometry type"); + } + + mfem::Array dofs; + + [[maybe_unused]] auto* dof_transformation = fes->GetElementDofs(elem, dofs); + + // mfem returns the H1 dofs in "native" order, so we need + // to apply the native-to-lexicographic permutation + if (isH1(*fes)) { + for (int k = 0; k < dofs.Size(); k++) { + elem_dofs.push_back({uint64_t(dofs[lex_perm[uint32_t(geom)][uint32_t(k)]])}); + } + } + + // the dofs mfem returns for Hcurl include information about + // dof orientation, but not for triangle faces on 3D elements. + // So, we need to manually + if (isHcurl(*fes)) { + // TODO + // TODO + // TODO + uint64_t sign = 1; + uint64_t orientation = 0; + for (int k = 0; k < dofs.Size(); k++) { + elem_dofs.push_back({uint64_t(dofs[k]), sign, orientation}); + } + } + + // mfem returns DG dofs in lexicographic order already + // so no permutation is required here + if (isDG(*fes)) { + for (int k = 0; k < dofs.Size(); k++) { + elem_dofs.push_back({uint64_t(dofs[k])}); + } + } + + n++; + } + + if (n == 0) { + return axom::Array{}; + } else { + uint64_t dofs_per_elem = elem_dofs.size() / n; + axom::Array output(n, dofs_per_elem); + std::memcpy(output.data(), elem_dofs.data(), sizeof(DoF) * n * dofs_per_elem); + return output; + } +} + +axom::Array GetFaceDofs(const serac::fes_t* fes, mfem::Geometry::Type face_geom, + FaceType type) { std::vector face_dofs; mfem::Mesh* mesh = fes->GetMesh(); @@ -363,7 +431,11 @@ axom::Array GetFaceDofs(const mfem::FiniteEleme if (isHcurl(*fes)) { for (int k = 0; k < dofs.Size(); k++) { - face_dofs.push_back(uint64_t(dofs[k])); + if (dofs[k] >= 0) { + face_dofs.push_back(DoF{uint64_t(dofs[k]), 0}); + } else { + face_dofs.push_back(DoF{uint64_t(-1 - dofs[k]), 1}); + } } } else { for (int k = 0; k < dofs.Size(); k++) { @@ -387,26 +459,187 @@ axom::Array GetFaceDofs(const mfem::FiniteEleme } } -namespace serac { - -ElementRestriction::ElementRestriction(const mfem::FiniteElementSpace* fes, mfem::Geometry::Type elem_geom) +axom::Array GetFaceDofs(const serac::fes_t* fes, mfem::Geometry::Type face_geom, + const std::vector& mfem_face_ids) { - dof_info = GetElementRestriction(fes, elem_geom); + std::vector face_dofs; + mfem::Mesh* mesh = fes->GetMesh(); + mfem::Table* face_to_elem = mesh->GetFaceToElementTable(); - ordering = fes->GetOrdering(); + // note: this assumes that all the elements are the same polynomial order + int p = fes->GetElementOrder(0); + Array2D face_perm = face_permutations(face_geom, p); + std::vector > local_face_dofs = geom_local_face_dofs(p); + std::vector > lex_perm = lexicographic_permutations(p); - lsize = uint64_t(fes->GetVSize()); - components = uint64_t(fes->GetVDim()); - num_nodes = lsize / components; - num_elements = uint64_t(dof_info.shape()[0]); - nodes_per_elem = uint64_t(dof_info.shape()[1]); - esize = num_elements * nodes_per_elem * components; + // sam: this function contains several comments that relate to an investigation into adopting + // mfem's FaceNbrData pattern (which ended up being too invasive to implement initially). + // I leave some of the commented code here so Julian's recommendations are not lost. + // + // int components_per_node = fes->GetVDim(); + // bool by_vdim = fes->GetOrdering() == mfem::Ordering::byVDIM; + // fes->ExchangeFaceNbrData(); + // int LSize = fes->GetProlongationMatrix()->Height(); + + uint64_t n = 0; + + for (int f : mfem_face_ids) { + mfem::Mesh::FaceInformation info = mesh->GetFaceInformation(f); + + if (mesh->GetFaceGeometry(f) != face_geom) { + SLIC_ERROR("encountered incorrect face geometry type"); + } + + // mfem doesn't provide this connectivity info for DG spaces directly (?), + // so we have to get at it indirectly in several steps: + if (isDG(*fes)) { + // 1. find the element(s) that this face belongs to + mfem::Array elem_ids; + face_to_elem->GetRow(f, elem_ids); + +#if 0 + mpi::out << f << " elem ids: "; + for (auto elem : elem_ids) { + mpi::out << elem << " "; + } + mpi::out << std::endl; + + mfem::Array test_dofs; + fes->GetFaceDofs(f, test_dofs); + + mpi::out << " dofs (sz = " << test_dofs.Size() << "): "; + for (int z = 0; z < test_dofs.Size(); z++) { + mpi::out << test_dofs[z] << " "; + } + mpi::out << std::endl; + mpi::out << std::endl; +#endif + + for (auto elem : elem_ids) { + // 2a. get the list of faces (and their orientations) that belong to that element ... + mfem::Array elem_side_ids, orientations; + if (mesh->Dimension() == 2) { + mesh->GetElementEdges(elem, elem_side_ids, orientations); + + // mfem returns {-1, 1} for edge orientations, + // but {0, 1, ... , n} for face orientations. + // Here, we renumber the edge orientations to + // {0 (no permutation), 1 (reversed)} so the values can be + // consistently used as indices into a permutation table + for (auto& o : orientations) { + o = (o == -1) ? 1 : 0; + } + + } else { + mesh->GetElementFaces(elem, elem_side_ids, orientations); + } + + // 2b. ... and find `i` such that `elem_side_ids[i] == f` + int i; + for (i = 0; i < elem_side_ids.Size(); i++) { + if (elem_side_ids[i] == f) break; + } + + // 3. get the dofs for the entire element + mfem::Array elem_dof_ids; + fes->GetElementDofs(elem, elem_dof_ids); + + mfem::Geometry::Type elem_geom = mesh->GetElementGeometry(elem); + + // mfem uses different conventions for boundary element orientations in 2D and 3D. + // In 2D, mfem's official edge orientations on the boundary will always be a mix of + // CW and CCW, so we have to discard mfem's orientation information in order + // to get a consistent winding. + // + // In 3D, mfem does use a consistently CCW winding for boundary faces (I think). + int orientation = (mesh->Dimension() == 2 && info.IsBoundary()) ? 0 : orientations[i]; + + // 4. extract only the dofs that correspond to side `i` + for (auto k : face_perm(orientation)) { + face_dofs.push_back(uint64_t(elem_dof_ids[local_face_dofs[uint32_t(elem_geom)](i, k)])); + } + +#if 0 + // (5. optional) add remaining dofs that were omitted on shared faces + if (info.IsShared()) { + + mfem::Array shared_elem_vdof_ids; + + // on my processor + // VVVVVVV + // dofs for the face [0 1 3 4 | 5 8 12 13] + // ^^^^^^^^^ + // on the other processor + int other_element_id = info.element[1].index; + fes->GetFaceNbrElementVDofs(other_element_id, shared_elem_vdof_ids); // indices into vector from FaceNbrData + + mpi::out << " this face is also shared so it has additional dofs: "; + int dofs_per_face = shared_elem_vdof_ids.Size() / components_per_node; + int stride = (by_vdim) ? components_per_node : 1; + + // sam: is this right? + // byVDIM == x y z x y z x y z x y z + // byNODES == x x x x y y y y z z z z + for (int k = 0; k < dofs_per_face; k++) { + face_dofs.push_back(uint64_t(fes->VDofToDof(shared_elem_vdof_ids[k * stride]) + LSize)); + mpi::out << face_dofs.back().index() << " "; + } + mpi::out << std::endl; + } +#endif + } + + // H1 and Hcurl spaces are more straight-forward, since + // we can use FiniteElementSpace::GetFaceDofs() directly + } else { + mfem::Array dofs; + + fes->GetFaceDofs(f, dofs); + + if (isHcurl(*fes)) { + for (int k = 0; k < dofs.Size(); k++) { + if (dofs[k] >= 0) { + face_dofs.push_back(DoF{uint64_t(dofs[k]), 0}); + } else { + face_dofs.push_back(DoF{uint64_t(-1 - dofs[k]), 1}); + } + } + } else { + for (int k = 0; k < dofs.Size(); k++) { + face_dofs.push_back(uint64_t(dofs[lex_perm[uint32_t(face_geom)][uint32_t(k)]])); + } + } + } + + n++; + } + + delete face_to_elem; + + if (n == 0) { + return axom::Array{}; + } else { + uint64_t dofs_per_face = face_dofs.size() / n; + axom::Array output(n, dofs_per_face); + std::memcpy(output.data(), face_dofs.data(), sizeof(DoF) * n * dofs_per_face); + return output; + } } -ElementRestriction::ElementRestriction(const mfem::FiniteElementSpace* fes, mfem::Geometry::Type face_geom, - FaceType type) +namespace serac { + +ElementRestriction::ElementRestriction(const fes_t* fes, mfem::Geometry::Type elem_geom, + const std::vector& elem_ids) { - dof_info = GetFaceDofs(fes, face_geom, type); + int sdim = fes->GetMesh()->Dimension(); + int gdim = dimension_of(elem_geom); + + if (gdim == sdim) { + dof_info = GetElementDofs(fes, elem_geom, elem_ids); + } + if (gdim + 1 == sdim) { + dof_info = GetFaceDofs(fes, elem_geom, elem_ids); + } ordering = fes->GetOrdering(); @@ -468,39 +701,40 @@ void ElementRestriction::ScatterAdd(const mfem::Vector& E_vector, mfem::Vector& //////////////////////////////////////////////////////////////////////// -BlockElementRestriction::BlockElementRestriction(const mfem::FiniteElementSpace* fes) +/// create a BlockElementRestriction for the elements in a given domain +BlockElementRestriction::BlockElementRestriction(const fes_t* fes, const Domain& domain) { - int dim = fes->GetMesh()->Dimension(); - - if (dim == 2) { - for (auto geom : {mfem::Geometry::TRIANGLE, mfem::Geometry::SQUARE}) { - restrictions[geom] = ElementRestriction(fes, geom); - } + // TODO: changing the mfem_XXX_ids arrays to mfem_ids[XXX] would simplify this + if (domain.mfem_edge_ids_.size() > 0) { + restrictions[mfem::Geometry::SEGMENT] = ElementRestriction(fes, mfem::Geometry::SEGMENT, domain.mfem_edge_ids_); } - if (dim == 3) { - for (auto geom : {mfem::Geometry::TETRAHEDRON, mfem::Geometry::CUBE}) { - restrictions[geom] = ElementRestriction(fes, geom); - } + if (domain.mfem_tri_ids_.size() > 0) { + restrictions[mfem::Geometry::TRIANGLE] = ElementRestriction(fes, mfem::Geometry::TRIANGLE, domain.mfem_tri_ids_); } -} -BlockElementRestriction::BlockElementRestriction(const mfem::FiniteElementSpace* fes, FaceType type) -{ - int dim = fes->GetMesh()->Dimension(); + if (domain.mfem_quad_ids_.size() > 0) { + restrictions[mfem::Geometry::SQUARE] = ElementRestriction(fes, mfem::Geometry::SQUARE, domain.mfem_quad_ids_); + } - if (dim == 2) { - restrictions[mfem::Geometry::SEGMENT] = ElementRestriction(fes, mfem::Geometry::SEGMENT, type); + if (domain.mfem_tet_ids_.size() > 0) { + restrictions[mfem::Geometry::TETRAHEDRON] = + ElementRestriction(fes, mfem::Geometry::TETRAHEDRON, domain.mfem_tet_ids_); } - if (dim == 3) { - for (auto geom : {mfem::Geometry::TRIANGLE, mfem::Geometry::SQUARE}) { - restrictions[geom] = ElementRestriction(fes, geom, type); - } + if (domain.mfem_hex_ids_.size() > 0) { + restrictions[mfem::Geometry::CUBE] = ElementRestriction(fes, mfem::Geometry::CUBE, domain.mfem_hex_ids_); } } -uint64_t BlockElementRestriction::ESize() const { return (*restrictions.begin()).second.ESize(); } +uint64_t BlockElementRestriction::ESize() const +{ + uint64_t total = 0; + for (auto& [geom, restriction] : restrictions) { + total += restriction.ESize(); + } + return total; +} uint64_t BlockElementRestriction::LSize() const { return (*restrictions.begin()).second.LSize(); } @@ -516,21 +750,20 @@ mfem::Array BlockElementRestriction::bOffsets() const } else { offsets[g + 1] = offsets[g]; } - // std::cout << g << " " << offsets[g+1] << std::endl; } return offsets; }; void BlockElementRestriction::Gather(const mfem::Vector& L_vector, mfem::BlockVector& E_block_vector) const { - for (auto [geom, restriction] : restrictions) { + for (auto& [geom, restriction] : restrictions) { restriction.Gather(L_vector, E_block_vector.GetBlock(geom)); } } void BlockElementRestriction::ScatterAdd(const mfem::BlockVector& E_block_vector, mfem::Vector& L_vector) const { - for (auto [geom, restriction] : restrictions) { + for (auto& [geom, restriction] : restrictions) { restriction.ScatterAdd(E_block_vector.GetBlock(geom), L_vector); } } diff --git a/src/serac/numerics/functional/element_restriction.hpp b/src/serac/numerics/functional/element_restriction.hpp index 7f12dca59..dc2e1c336 100644 --- a/src/serac/numerics/functional/element_restriction.hpp +++ b/src/serac/numerics/functional/element_restriction.hpp @@ -5,6 +5,9 @@ #include "mfem.hpp" #include "axom/core.hpp" #include "geometry.hpp" +#include "domain.hpp" + +#include "serac/numerics/functional/typedefs.hpp" inline bool isH1(const mfem::FiniteElementSpace& fes) { @@ -141,17 +144,16 @@ struct Array2D { namespace serac { +struct Domain; + /// a more complete version of mfem::ElementRestriction that works with {H1, Hcurl, L2} spaces (including on the /// boundary) struct ElementRestriction { /// default ctor leaves this object uninitialized ElementRestriction() {} - /// create an ElementRestriction for all domain-type (geom dim == spatial dim) elements of the specified geometry - ElementRestriction(const mfem::FiniteElementSpace* fes, mfem::Geometry::Type elem_geom); - - /// create an ElementRestriction for all face-type (geom dim == spatial dim) elements of the specified geometry - ElementRestriction(const mfem::FiniteElementSpace* fes, mfem::Geometry::Type face_geom, FaceType type); + /// ctor from a list of elements (e.g. from a serac::Domain) + ElementRestriction(const fes_t* fes, mfem::Geometry::Type elem_geom, const std::vector& domain_elements); /// the size of the "E-vector" associated with this restriction operator uint64_t ESize() const; @@ -210,11 +212,8 @@ struct BlockElementRestriction { /// default ctor leaves this object uninitialized BlockElementRestriction() {} - /// create a BlockElementRestriction for all domain-elements (geom dim == spatial dim) - BlockElementRestriction(const mfem::FiniteElementSpace* fes); - - /// create a BlockElementRestriction for all face-elements (geom dim + 1 == spatial dim) - BlockElementRestriction(const mfem::FiniteElementSpace* fes, FaceType type); + /// create a BlockElementRestriction for the elements in a given domain + BlockElementRestriction(const fes_t* fes, const Domain& domain); /// the size of the "E-vector" associated with this restriction operator uint64_t ESize() const; @@ -243,13 +242,18 @@ struct BlockElementRestriction { * @param fes the finite element space containing the dof information * @param geom the kind of element geometry */ -Array2D GetElementDofs(mfem::FiniteElementSpace* fes, mfem::Geometry::Type geom); +axom::Array GetElementDofs(const serac::fes_t* fes, mfem::Geometry::Type geom); /** - * @brief Get the list of dofs for each face element (of the specified geometry) from the mfem::FiniteElementSpace + * @brief Get the list of dofs for each face element (of the specified geometry) from the fes_t * * @param fes the finite element space containing the dof information * @param geom the kind of element geometry * @param type whether the face is of interior or boundary type */ -Array2D GetFaceDofs(mfem::FiniteElementSpace* fes, mfem::Geometry::Type face_geom, FaceType type); +axom::Array GetFaceDofs(const serac::fes_t* fes, mfem::Geometry::Type face_geom, + FaceType type); + +/// @overload +axom::Array GetFaceDofs(const serac::fes_t* fes, mfem::Geometry::Type face_geom, + const std::vector& mfem_face_ids); diff --git a/src/serac/numerics/functional/finite_element.hpp b/src/serac/numerics/functional/finite_element.hpp index 12b87f684..dcbab1f8e 100644 --- a/src/serac/numerics/functional/finite_element.hpp +++ b/src/serac/numerics/functional/finite_element.hpp @@ -232,6 +232,27 @@ struct QOI { static constexpr Family family = Family::QOI; ///< the family of the basis functions }; +/** + * @brief a small POD class for tracking function space metadata + */ +struct FunctionSpace { + Family family; ///< either H1, Hcurl, L2 + int order; ///< polynomial order + int components; ///< how many values are stored at each node + + /** + * @brief return the data contained in this struct as a tuple + * @note the main point of this conversion is to take advantage of std::tuple's automatic + * lexicographic-ordering comparison operators + */ + std::tuple as_tuple() const { return std::tuple(int(family), order, components); } + + /** + * @brief defines an ordering over FunctionSpaces, to enable use in containers like std::map + */ + bool operator<(FunctionSpace other) const { return this->as_tuple() < other.as_tuple(); } +}; + /** * @brief transform information in the parent space (i.e. values and derivatives w.r.t {xi, eta, zeta}) * into the physical space (i.e. values and derivatives w.r.t. {x, y, z}) diff --git a/src/serac/numerics/functional/functional.hpp b/src/serac/numerics/functional/functional.hpp index 79311b68c..bb9fa8a5f 100644 --- a/src/serac/numerics/functional/functional.hpp +++ b/src/serac/numerics/functional/functional.hpp @@ -20,7 +20,6 @@ #include "serac/numerics/functional/quadrature.hpp" #include "serac/numerics/functional/finite_element.hpp" #include "serac/numerics/functional/integral.hpp" -#include "serac/numerics/functional/dof_numbering.hpp" #include "serac/numerics/functional/differentiate_wrt.hpp" #include "serac/numerics/functional/element_restriction.hpp" @@ -100,6 +99,25 @@ inline void check_for_unsupported_elements(const mfem::Mesh& mesh) } } +/** + * @brief function for verifying that DG spaces aren't used on interior face integrals over meshes that contain "shared" + * faces + * + * sam: I would like to support these "shared" faces, but apparently mfem handles them in a fundamentally different way + * than the "finite element operator decomposition" pattern used by everything else (see: "ExchangeFaceNbrData") + */ +inline void check_interior_face_compatibility(const mfem::Mesh& mesh, const FunctionSpace space) +{ + if (space.family == Family::L2) { + const mfem::ParMesh* pmesh = dynamic_cast(&mesh); + if (pmesh) { + SLIC_ERROR_IF( + pmesh->GetNSharedFaces() > 0, + "interior face integrals involving DG function spaces don't currently support meshes with shared faces"); + } + } +} + /** * @brief create an mfem::ParFiniteElementSpace from one of serac's * tag types: H1, Hcurl, L2 @@ -218,46 +236,33 @@ class Functional { */ Functional(const mfem::ParFiniteElementSpace* test_fes, std::array trial_fes) - : update_qdata_(false), test_space_(test_fes), trial_space_(trial_fes) + : update_qdata_(false), test_space_(test_fes), trial_space_(trial_fes), mem_type(mfem::Device::GetMemoryType()) { SERAC_MARK_FUNCTION; - auto mem_type = mfem::Device::GetMemoryType(); - - for (auto type : {Domain::Type::Elements, Domain::Type::BoundaryElements}) { - input_E_[type].resize(num_trial_spaces); - } - for (uint32_t i = 0; i < num_trial_spaces; i++) { P_trial_[i] = trial_space_[i]->GetProlongationMatrix(); input_L_[i].SetSize(P_trial_[i]->Height(), mfem::Device::GetMemoryType()); - // L->E - for (auto type : {Domain::Type::Elements, Domain::Type::BoundaryElements}) { - if (type == Domain::Type::Elements) { - G_trial_[type][i] = BlockElementRestriction(trial_fes[i]); - } else { - G_trial_[type][i] = BlockElementRestriction(trial_fes[i], FaceType::BOUNDARY); - } - - // note: we have to use "Update" here, as mfem::BlockVector's - // copy assignment ctor (operator=) doesn't let you make changes - // to the block size - input_E_[type][i].Update(G_trial_[type][i].bOffsets(), mem_type); - } + // create the necessary number of empty mfem::Vectors, to be resized later + input_E_.push_back({}); + input_E_buffer_.push_back({}); } - for (auto type : {Domain::Type::Elements, Domain::Type::BoundaryElements}) { - if (type == Domain::Type::Elements) { - G_test_[type] = BlockElementRestriction(test_fes); - } else { - G_test_[type] = BlockElementRestriction(test_fes, FaceType::BOUNDARY); - } + test_function_space_ = {test::family, test::order, test::components}; - output_E_[type].Update(G_test_[type].bOffsets(), mem_type); + std::array trial_families = {trials::family...}; + std::array trial_orders = {trials::order...}; + std::array trial_components = {trials::components...}; + for (uint32_t i = 0; i < num_trial_spaces; i++) { + trial_function_spaces_[i] = {trial_families[i], trial_orders[i], trial_components[i]}; } + // for (auto type : {Domain::Type::Elements, Domain::Type::BoundaryElements, Domain::Type::InteriorFaces}) { + // output_E_[type].Update(G_test_[type].bOffsets(), mem_type); + // } + P_test_ = test_space_->GetProlongationMatrix(); output_L_.SetSize(P_test_->Height(), mem_type); @@ -283,25 +288,8 @@ class Functional { * and @a spatial_dim template parameter * @param[inout] qdata The data for each quadrature point */ - template - void AddDomainIntegral(Dimension, DependsOn, const Integrand& integrand, mfem::Mesh& domain, - std::shared_ptr> qdata = NoQData) - { - if (domain.GetNE() == 0) return; - - SLIC_ERROR_ROOT_IF(dim != domain.Dimension(), "invalid mesh dimension for domain integral"); - - check_for_unsupported_elements(domain); - check_for_missing_nodal_gridfunc(domain); - - using signature = test(decltype(serac::type(trial_spaces))...); - integrals_.push_back( - MakeDomainIntegral(EntireDomain(domain), integrand, qdata, std::vector{args...})); - } - - /// @overload template - void AddDomainIntegral(Dimension, DependsOn, const lambda& integrand, const Domain& domain, + void AddDomainIntegral(Dimension, DependsOn, const lambda& integrand, Domain& domain, std::shared_ptr> qdata = NoQData) { if (domain.mesh_.GetNE() == 0) return; @@ -311,6 +299,12 @@ class Functional { check_for_unsupported_elements(domain.mesh_); check_for_missing_nodal_gridfunc(domain.mesh_); + std::vector arg_vec = {args...}; + for (uint32_t i : arg_vec) { + domain.insert_restriction(trial_space_[i], trial_function_spaces_[i]); + } + domain.insert_restriction(test_space_, test_function_space_); + using signature = test(decltype(serac::type(trial_spaces))...); integrals_.push_back( MakeDomainIntegral(domain, integrand, qdata, std::vector{args...})); @@ -325,22 +319,8 @@ class Functional { * @note The @p Dimension parameters are used to assist in the deduction of the @a geometry_dim * and @a spatial_dim template parameter */ - template - void AddBoundaryIntegral(Dimension, DependsOn, const Integrand& integrand, mfem::Mesh& domain) - { - auto num_bdr_elements = domain.GetNBE(); - if (num_bdr_elements == 0) return; - - check_for_missing_nodal_gridfunc(domain); - - using signature = test(decltype(serac::type(trial_spaces))...); - integrals_.push_back( - MakeBoundaryIntegral(EntireBoundary(domain), integrand, std::vector{args...})); - } - - /// @overload template - void AddBoundaryIntegral(Dimension, DependsOn, const lambda& integrand, const Domain& domain) + void AddBoundaryIntegral(Dimension, DependsOn, const lambda& integrand, Domain& domain) { auto num_bdr_elements = domain.mesh_.GetNBE(); if (num_bdr_elements == 0) return; @@ -349,10 +329,37 @@ class Functional { check_for_missing_nodal_gridfunc(domain.mesh_); + std::vector arg_vec = {args...}; + for (uint32_t i : arg_vec) { + domain.insert_restriction(trial_space_[i], trial_function_spaces_[i]); + } + domain.insert_restriction(test_space_, test_function_space_); + using signature = test(decltype(serac::type(trial_spaces))...); integrals_.push_back(MakeBoundaryIntegral(domain, integrand, std::vector{args...})); } + /** + * @brief TODO + */ + template + void AddInteriorFaceIntegral(Dimension, DependsOn, const Integrand& integrand, Domain& domain) + { + check_for_missing_nodal_gridfunc(domain.mesh_); + + std::vector arg_vec = {args...}; + for (uint32_t i : arg_vec) { + domain.insert_restriction(trial_space_[i], trial_function_spaces_[i]); + check_interior_face_compatibility(domain.mesh_, trial_function_spaces_[i]); + } + domain.insert_restriction(test_space_, test_function_space_); + check_interior_face_compatibility(domain.mesh_, test_function_space_); + + using signature = test(decltype(serac::type(trial_spaces))...); + integrals_.push_back( + MakeInteriorFaceIntegral(domain, integrand, std::vector{args...})); + } + /** * @brief Adds an area integral, i.e., over 2D elements in R^2 space * @tparam lambda the type of the integrand functor: must implement operator() with an appropriate function signature @@ -363,7 +370,7 @@ class Functional { * @param[inout] data The data for each quadrature point */ template - void AddAreaIntegral(DependsOn which_args, const lambda& integrand, mfem::Mesh& domain, + void AddAreaIntegral(DependsOn which_args, const lambda& integrand, Domain& domain, std::shared_ptr> data = NoQData) { AddDomainIntegral(Dimension<2>{}, which_args, integrand, domain, data); @@ -379,7 +386,7 @@ class Functional { * @param[inout] data The data for each quadrature point */ template - void AddVolumeIntegral(DependsOn which_args, const lambda& integrand, mfem::Mesh& domain, + void AddVolumeIntegral(DependsOn which_args, const lambda& integrand, Domain& domain, std::shared_ptr> data = NoQData) { AddDomainIntegral(Dimension<3>{}, which_args, integrand, domain, data); @@ -387,7 +394,7 @@ class Functional { /// @brief alias for Functional::AddBoundaryIntegral(Dimension<2>{}, integrand, domain); template - void AddSurfaceIntegral(DependsOn which_args, const lambda& integrand, mfem::Mesh& domain) + void AddSurfaceIntegral(DependsOn which_args, const lambda& integrand, Domain& domain) { AddBoundaryIntegral(Dimension<2>{}, which_args, integrand, domain); } @@ -409,22 +416,24 @@ class Functional { output_L_ = 0.0; - // this is used to mark when gather operations have been performed, - // to avoid doing them more than once per trial space - bool already_computed[Domain::num_types]{}; // default initializes to `false` - for (auto& integral : integrals_) { - auto type = integral.domain_.type_; + if (integral.DependsOn(which)) { + Domain& dom = integral.domain_; - if (!already_computed[type]) { - G_trial_[type][which].Gather(input_L_[which], input_E_[type][which]); - already_computed[type] = true; - } + const serac::BlockElementRestriction& G_trial = dom.get_restriction(trial_function_spaces_[which]); + input_E_buffer_[which].SetSize(int(G_trial.ESize())); + input_E_[which].Update(input_E_buffer_[which], G_trial.bOffsets()); + G_trial.Gather(input_L_[which], input_E_[which]); - integral.GradientMult(input_E_[type][which], output_E_[type], which); + const serac::BlockElementRestriction& G_test = dom.get_restriction(test_function_space_); + output_E_buffer_.SetSize(int(G_test.ESize())); + output_E_.Update(output_E_buffer_, G_test.bOffsets()); - // scatter-add to compute residuals on the local processor - G_test_[type].ScatterAdd(output_E_[type], output_L_); + integral.GradientMult(input_E_[which], output_E_, which); + + // scatter-add to compute residuals on the local processor + G_test.ScatterAdd(output_E_, output_L_); + } } // scatter-add to compute global residuals @@ -450,29 +459,58 @@ class Functional { // get the values for each local processor for (uint32_t i = 0; i < num_trial_spaces; i++) { +#if 0 + if (trial_function_spaces_[i].family == Family::L2) { + + // make a PGF on the fly + // TODO: don't allocate/deallocate this data every invocation + mfem::ParGridFunction X; + X.MakeRef(trial_space_[i], input_L_[i].GetData()); + + // call exchange face nbr + X.ExchangeFaceNbrData(); + + // copy input_L[i] and facenbrdata [i] into a common array like: + // first part second part + // [ --- L --- | --- FND --- ] + mfem::Vector first_part; + first_part.MakeRef(input_L_[i], 0); + P_trial_[i]->Mult(*input_T[i], first_part); + + mfem::Vector second_part; + second_part.MakeRef(input_L_[i], first_part.Size()); + second_part = X.FaceNbrData(); + + } else { + + P_trial_[i]->Mult(*input_T[i], input_L_[i]); + + } +#else P_trial_[i]->Mult(*input_T[i], input_L_[i]); +#endif } output_L_ = 0.0; - // this is used to mark when operations have been performed, - // to avoid doing them more than once - bool already_computed[Domain::num_types][num_trial_spaces]{}; // default initializes to `false` - for (auto& integral : integrals_) { - auto type = integral.domain_.type_; + Domain& dom = integral.domain_; + + const serac::BlockElementRestriction& G_test = dom.get_restriction(test_function_space_); for (auto i : integral.active_trial_spaces_) { - if (!already_computed[type][i]) { - G_trial_[type][i].Gather(input_L_[i], input_E_[type][i]); - already_computed[type][i] = true; - } + const serac::BlockElementRestriction& G_trial = dom.get_restriction(trial_function_spaces_[i]); + input_E_buffer_[i].SetSize(int(G_trial.ESize())); + input_E_[i].Update(input_E_buffer_[i], G_trial.bOffsets()); + G_trial.Gather(input_L_[i], input_E_[i]); } - integral.Mult(t, input_E_[type], output_E_[type], wrt, update_qdata_); + output_E_buffer_.SetSize(int(G_test.ESize())); + output_E_.Update(output_E_buffer_, G_test.bOffsets()); + integral.Mult(t, input_E_, output_E_, wrt, update_qdata_); // scatter-add to compute residuals on the local processor - G_test_[type].ScatterAdd(output_E_[type], output_L_); + G_test.ScatterAdd(output_E_, output_L_); } // scatter-add to compute global residuals @@ -487,6 +525,7 @@ class Functional { // e.g. auto [value, gradient_wrt_arg1] = my_functional(arg0, differentiate_wrt(arg1)); return {output_T_, grad_[wrt]}; } + if constexpr (wrt == NO_DIFFERENTIATION) { // if the user passes only `mfem::Vector`s then we assume they only want the output value // @@ -565,77 +604,156 @@ class Functional { return df_; } - /// @brief assemble element matrices and form an mfem::HypreParMatrix - std::unique_ptr assemble() + void initialize_sparsity_pattern() { - // the CSR graph (sparsity pattern) is reusable, so we cache - // that and ask mfem to not free that memory in ~SparseMatrix() - constexpr bool sparse_matrix_frees_graph_ptrs = false; - - // the CSR values are NOT reusable, so we pass ownership of - // them to the mfem::SparseMatrix, to be freed in ~SparseMatrix() - constexpr bool sparse_matrix_frees_values_ptr = true; + using row_col = std::tuple; - constexpr bool col_ind_is_sorted = true; + std::set nonzero_entries; - if (!lookup_tables.initialized) { - lookup_tables.init(form_.G_test_[Domain::Type::Elements], - form_.G_trial_[Domain::Type::Elements][which_argument]); - } + for (auto& integral : form_.integrals_) { + if (integral.DependsOn(which_argument)) { + Domain& dom = integral.domain_; + const auto& G_test = dom.get_restriction(form_.test_function_space_); + const auto& G_trial = dom.get_restriction(form_.trial_function_spaces_[which_argument]); + for (const auto& [geom, test_restriction] : G_test.restrictions) { + const auto& trial_restriction = G_trial.restrictions.at(geom); + + // the degrees of freedom associated with the rows/columns of the e^th element stiffness matrix + std::vector test_vdofs(test_restriction.nodes_per_elem * test_restriction.components); + std::vector trial_vdofs(trial_restriction.nodes_per_elem * trial_restriction.components); + + auto num_elements = static_cast(test_restriction.num_elements); + for (uint32_t e = 0; e < num_elements; e++) { + for (uint32_t i = 0; i < test_restriction.nodes_per_elem; i++) { + auto test_dof = test_restriction.dof_info(e, i); + for (uint32_t j = 0; j < test_restriction.components; j++) { + test_vdofs[i * test_restriction.components + j] = int(test_restriction.GetVDof(test_dof, j).index()); + } + } - double* values = new double[lookup_tables.nnz]{}; + for (uint32_t i = 0; i < trial_restriction.nodes_per_elem; i++) { + auto trial_dof = trial_restriction.dof_info(e, i); + for (uint32_t j = 0; j < trial_restriction.components; j++) { + trial_vdofs[i * trial_restriction.components + j] = + int(trial_restriction.GetVDof(trial_dof, j).index()); + } + } - std::map> element_gradients[Domain::num_types]; + for (int row : test_vdofs) { + for (int col : trial_vdofs) { + nonzero_entries.insert({row, col}); + } + } + } + } + } + } - for (auto& integral : form_.integrals_) { - auto& K_elem = element_gradients[integral.domain_.type_]; - auto& test_restrictions = form_.G_test_[integral.domain_.type_].restrictions; - auto& trial_restrictions = form_.G_trial_[integral.domain_.type_][which_argument].restrictions; + uint64_t nnz = nonzero_entries.size(); + int nrows = form_.output_L_.Size(); - if (K_elem.empty()) { - for (auto& [geom, test_restriction] : test_restrictions) { - auto& trial_restriction = trial_restrictions[geom]; + row_ptr.resize(uint32_t(nrows + 1)); + col_ind.resize(nnz); - K_elem[geom] = ExecArray(test_restriction.num_elements, - trial_restriction.nodes_per_elem * trial_restriction.components, - test_restriction.nodes_per_elem * test_restriction.components); + int nz = 0; + int last_row = -1; + for (auto [row, col] : nonzero_entries) { + col_ind[uint32_t(nz)] = col; + for (int i = last_row + 1; i <= row; i++) { + row_ptr[uint32_t(i)] = nz; + } + last_row = row; + nz++; + } + for (int i = last_row + 1; i <= nrows; i++) { + row_ptr[uint32_t(i)] = nz; + } + }; - detail::zero_out(K_elem[geom]); + uint64_t max_buffer_size() + { + uint64_t max_entries = 0; + for (auto& integral : form_.integrals_) { + if (integral.DependsOn(which_argument)) { + Domain& dom = integral.domain_; + const auto& G_test = dom.get_restriction(form_.test_function_space_); + const auto& G_trial = dom.get_restriction(form_.trial_function_spaces_[which_argument]); + for (const auto& [geom, test_restriction] : G_test.restrictions) { + const auto& trial_restriction = G_trial.restrictions.at(geom); + uint64_t nrows_per_element = test_restriction.nodes_per_elem * test_restriction.components; + uint64_t ncols_per_element = trial_restriction.nodes_per_elem * trial_restriction.components; + uint64_t entries_per_element = nrows_per_element * ncols_per_element; + uint64_t entries_needed = test_restriction.num_elements * entries_per_element; + max_entries = std::max(entries_needed, max_entries); } } + } + return max_entries; + } - integral.ComputeElementGradients(K_elem, which_argument); + std::unique_ptr assemble() + { + if (row_ptr.empty()) { + initialize_sparsity_pattern(); } - for (auto type : {Domain::Type::Elements, Domain::Type::BoundaryElements}) { - auto& K_elem = element_gradients[type]; - auto& test_restrictions = form_.G_test_[type].restrictions; - auto& trial_restrictions = form_.G_trial_[type][which_argument].restrictions; + // since we own the storage for row_ptr, col_ind, values, + // we ask mfem to not deallocate those pointers in the SparseMatrix dtor + constexpr bool sparse_matrix_frees_graph_ptrs = false; + constexpr bool sparse_matrix_frees_values_ptr = false; + constexpr bool col_ind_is_sorted = true; - if (!K_elem.empty()) { - for (auto [geom, elem_matrices] : K_elem) { - std::vector test_vdofs(test_restrictions[geom].nodes_per_elem * test_restrictions[geom].components); - std::vector trial_vdofs(trial_restrictions[geom].nodes_per_elem * trial_restrictions[geom].components); + // note: we make a copy of col_ind since mfem::HypreParMatrix + // changes it in the constructor + std::vector col_ind_copy = col_ind; - for (axom::IndexType e = 0; e < elem_matrices.shape()[0]; e++) { - test_restrictions[geom].GetElementVDofs(e, test_vdofs); - trial_restrictions[geom].GetElementVDofs(e, trial_vdofs); + int nnz = row_ptr.back(); + std::vector values(uint32_t(nnz), 0.0); + auto A_local = mfem::SparseMatrix(row_ptr.data(), col_ind_copy.data(), values.data(), form_.output_L_.Size(), + form_.input_L_[which_argument].Size(), sparse_matrix_frees_graph_ptrs, + sparse_matrix_frees_values_ptr, col_ind_is_sorted); - for (uint32_t i = 0; i < uint32_t(elem_matrices.shape()[1]); i++) { - int col = int(trial_vdofs[i].index()); + std::vector K_elem_buffer(max_buffer_size()); - for (uint32_t j = 0; j < uint32_t(elem_matrices.shape()[2]); j++) { - int row = int(test_vdofs[j].index()); + for (auto& integral : form_.integrals_) { + // if this integral's derivative isn't identically zero + if (integral.functional_to_integral_index_.count(which_argument) > 0) { + Domain& dom = integral.domain_; + + uint32_t id = integral.functional_to_integral_index_.at(which_argument); + const auto& G_test = dom.get_restriction(form_.test_function_space_); + const auto& G_trial = dom.get_restriction(form_.trial_function_spaces_[which_argument]); + for (const auto& [geom, calculate_element_matrices_func] : integral.element_gradient_[id]) { + const auto& test_restriction = G_test.restrictions.at(geom); + const auto& trial_restriction = G_trial.restrictions.at(geom); + + // prepare a buffer to hold the element matrices + CPUArrayView K_e(K_elem_buffer.data(), test_restriction.num_elements, + trial_restriction.nodes_per_elem * trial_restriction.components, + test_restriction.nodes_per_elem * test_restriction.components); + detail::zero_out(K_e); + + // perform the actual calculations + calculate_element_matrices_func(K_e); + + const std::vector& element_ids = integral.domain_.get(geom); + + uint32_t rows_per_elem = uint32_t(test_restriction.nodes_per_elem * test_restriction.components); + uint32_t cols_per_elem = uint32_t(trial_restriction.nodes_per_elem * trial_restriction.components); - int sign = test_vdofs[j].sign() * trial_vdofs[i].sign(); + std::vector test_vdofs(rows_per_elem); + std::vector trial_vdofs(cols_per_elem); - // note: col / row appear backwards here, because the element matrix kernel - // is actually transposed, as a result of being row-major storage. - // - // This is kind of confusing, and will be fixed in a future refactor - // of the element gradient kernel implementation - [[maybe_unused]] auto nz = lookup_tables(row, col); - values[lookup_tables(row, col)] += sign * elem_matrices(e, i, j); + for (uint32_t e = 0; e < element_ids.size(); e++) { + test_restriction.GetElementVDofs(int(e), test_vdofs); + trial_restriction.GetElementVDofs(int(e), trial_vdofs); + + for (uint32_t i = 0; i < cols_per_elem; i++) { + int col = int(trial_vdofs[i].index()); + + for (uint32_t j = 0; j < rows_per_elem; j++) { + int row = int(test_vdofs[j].index()); + A_local.SearchRow(row, col) += K_e(e, i, j); } } } @@ -643,27 +761,19 @@ class Functional { } } - // Copy the column indices to an auxilliary array as MFEM can mutate these during HypreParMatrix construction - col_ind_copy_ = lookup_tables.col_ind; - - auto J_local = - mfem::SparseMatrix(lookup_tables.row_ptr.data(), col_ind_copy_.data(), values, form_.output_L_.Size(), - form_.input_L_[which_argument].Size(), sparse_matrix_frees_graph_ptrs, - sparse_matrix_frees_values_ptr, col_ind_is_sorted); - auto* R = form_.test_space_->Dof_TrueDof_Matrix(); - auto* A = + auto* A_hypre = new mfem::HypreParMatrix(test_space_->GetComm(), test_space_->GlobalVSize(), trial_space_->GlobalVSize(), - test_space_->GetDofOffsets(), trial_space_->GetDofOffsets(), &J_local); + test_space_->GetDofOffsets(), trial_space_->GetDofOffsets(), &A_local); auto* P = trial_space_->Dof_TrueDof_Matrix(); - std::unique_ptr K(mfem::RAP(R, A, P)); + std::unique_ptr A(mfem::RAP(R, A_hypre, P)); - delete A; + delete A_hypre; - return K; + return A; }; friend auto assemble(Gradient& g) { return g.assemble(); } @@ -672,18 +782,8 @@ class Functional { /// @brief The "parent" @p Functional to calculate gradients with Functional& form_; - /** - * @brief this object has lookup tables for where to place each - * element and boundary element gradient contribution in the global - * sparse matrix - */ - GradientAssemblyLookupTables lookup_tables; - - /** - * @brief Copy of the column indices for sparse matrix assembly - * @note These are mutated by MFEM during HypreParMatrix construction - */ - std::vector col_ind_copy_; + std::vector row_ptr; + std::vector col_ind; /** * @brief this member variable tells us which argument the associated Functional this gradient @@ -713,6 +813,9 @@ class Functional { /// @brief Manages DOFs for the trial space std::array trial_space_; + std::array trial_function_spaces_; + FunctionSpace test_function_space_; + /** * @brief Operator that converts true (global) DOF values to local (current rank) DOF values * for the test space @@ -722,15 +825,13 @@ class Functional { /// @brief The input set of local DOF values (i.e., on the current rank) mutable mfem::Vector input_L_[num_trial_spaces]; - BlockElementRestriction G_trial_[Domain::num_types][num_trial_spaces]; + mutable std::vector input_E_buffer_; + mutable std::vector input_E_; - mutable std::vector input_E_[Domain::num_types]; + mutable std::vector integrals_; - std::vector integrals_; - - mutable mfem::BlockVector output_E_[Domain::num_types]; - - BlockElementRestriction G_test_[Domain::num_types]; + mutable mfem::Vector output_E_buffer_; + mutable mfem::BlockVector output_E_; /// @brief The output set of local DOF values (i.e., on the current rank) mutable mfem::Vector output_L_; @@ -742,6 +843,8 @@ class Functional { /// @brief The objects representing the gradients w.r.t. each input argument of the Functional mutable std::vector grad_; + + const mfem::MemoryType mem_type; }; } // namespace serac diff --git a/src/serac/numerics/functional/functional_qoi.inl b/src/serac/numerics/functional/functional_qoi.inl index 2c0cea577..bec030cad 100644 --- a/src/serac/numerics/functional/functional_qoi.inl +++ b/src/serac/numerics/functional/functional_qoi.inl @@ -83,51 +83,26 @@ public: Functional(std::array trial_fes) : test_fec_(0, trial_fes[0]->GetMesh()->Dimension()), test_space_(dynamic_cast(trial_fes[0]->GetMesh()), &test_fec_, 1, serac::ordering), - trial_space_(trial_fes) + trial_space_(trial_fes), + mem_type(mfem::Device::GetMemoryType()) { - auto* mesh = trial_fes[0]->GetMesh(); - - auto mem_type = mfem::Device::GetMemoryType(); - - for (auto type : {Domain::Type::Elements, Domain::Type::BoundaryElements}) { - input_E_[type].resize(num_trial_spaces); - } + SERAC_MARK_FUNCTION; for (uint32_t i = 0; i < num_trial_spaces; i++) { P_trial_[i] = trial_space_[i]->GetProlongationMatrix(); input_L_[i].SetSize(P_trial_[i]->Height(), mfem::Device::GetMemoryType()); - for (auto type : {Domain::Type::Elements, Domain::Type::BoundaryElements}) { - if (type == Domain::Type::Elements) { - G_trial_[type][i] = BlockElementRestriction(trial_fes[i]); - } else { - G_trial_[type][i] = BlockElementRestriction(trial_fes[i], FaceType::BOUNDARY); - } - - // note: we have to use "Update" here, as mfem::BlockVector's - // copy assignment ctor (operator=) doesn't let you make changes - // to the block size - input_E_[type][i].Update(G_trial_[type][i].bOffsets(), mem_type); - } + // create the necessary number of empty mfem::Vectors, to be resized later + input_E_.push_back({}); + input_E_buffer_.push_back({}); } - for (auto type : {Domain::Type::Elements, Domain::Type::BoundaryElements}) { - std::array counts{}; - if (type == Domain::Type::Elements) { - counts = geometry_counts(*mesh); - } else { - counts = boundary_geometry_counts(*mesh); - } - - mfem::Array offsets(mfem::Geometry::NUM_GEOMETRIES + 1); - offsets[0] = 0; - for (int i = 0; i < mfem::Geometry::NUM_GEOMETRIES; i++) { - auto g = mfem::Geometry::Type(i); - offsets[g + 1] = offsets[g] + int(counts[uint32_t(g)]); - } - - output_E_[type].Update(offsets, mem_type); + std::array trial_families = {trials::family...}; + std::array trial_orders = {trials::order...}; + std::array trial_components = {trials::components...}; + for (uint32_t i = 0; i < num_trial_spaces; i++) { + trial_function_spaces_[i] = {trial_families[i], trial_orders[i], trial_components[i]}; } G_test_ = QoIElementRestriction(); @@ -151,28 +126,12 @@ public: * @tparam lambda the type of the integrand functor: must implement operator() with an appropriate function signature * @tparam qpt_data_type The type of the data to store for each quadrature point * @param[in] integrand The user-provided quadrature function, see @p Integral - * @param[in] mesh The domain on which to evaluate the integral + * @param[in] domain The domain on which to evaluate the integral * @param[in] qdata The data structure containing per-quadrature-point data * @note The @p Dimension parameters are used to assist in the deduction of the @a geometry_dim * and @a spatial_dim template parameter */ - template - void AddDomainIntegral(Dimension, DependsOn, const lambda& integrand, mfem::Mesh& mesh, - std::shared_ptr> qdata = NoQData) - { - if (mesh.GetNE() == 0) return; - - SLIC_ERROR_ROOT_IF(dim != mesh.Dimension(), "invalid mesh dimension for domain integral"); - - check_for_unsupported_elements(mesh); - check_for_missing_nodal_gridfunc(mesh); - - using signature = test(decltype(serac::type(trial_spaces))...); - integrals_.push_back( - MakeDomainIntegral(EntireDomain(mesh), integrand, qdata, std::vector{args...})); - } - /// @overload template void AddDomainIntegral(Dimension, DependsOn, const lambda& integrand, Domain& domain, std::shared_ptr> qdata = NoQData) @@ -184,6 +143,11 @@ public: check_for_unsupported_elements(domain.mesh_); check_for_missing_nodal_gridfunc(domain.mesh_); + std::vector arg_vec = {args...}; + for (uint32_t i : arg_vec) { + domain.insert_restriction(trial_space_[i], trial_function_spaces_[i]); + } + using signature = test(decltype(serac::type(trial_spaces))...); integrals_.push_back( MakeDomainIntegral(domain, integrand, qdata, std::vector{args...})); @@ -193,39 +157,56 @@ public: * @tparam dim The dimension of the boundary element (1 for line, 2 for quad, etc) * @tparam lambda the type of the integrand functor: must implement operator() with an appropriate function signature * @param[in] integrand The user-provided quadrature function, see @p Integral - * @param[in] mesh The domain on which to evaluate the integral + * @param[in] domain which elements make up the domain of integration * * @brief Adds a boundary integral term to the Functional object * * @note The @p Dimension parameters are used to assist in the deduction of the @a geometry_dim * and @a spatial_dim template parameter */ - template - void AddBoundaryIntegral(Dimension, DependsOn, const lambda& integrand, mfem::Mesh& mesh) + template + void AddBoundaryIntegral(Dimension, DependsOn, const lambda& integrand, Domain& domain) { - auto num_bdr_elements = mesh.GetNBE(); + auto num_bdr_elements = domain.mesh_.GetNBE(); if (num_bdr_elements == 0) return; - check_for_missing_nodal_gridfunc(mesh); + SLIC_ERROR_ROOT_IF(dim != domain.dim_, "invalid domain of integration for boundary integral"); + + check_for_missing_nodal_gridfunc(domain.mesh_); + + std::vector arg_vec = {args...}; + for (uint32_t i : arg_vec) { + domain.insert_restriction(trial_space_[i], trial_function_spaces_[i]); + } using signature = test(decltype(serac::type(trial_spaces))...); - integrals_.push_back( - MakeBoundaryIntegral(EntireBoundary(mesh), integrand, std::vector{args...})); + integrals_.push_back(MakeBoundaryIntegral(domain, integrand, arg_vec)); } - /// @overload - template - void AddBoundaryIntegral(Dimension, DependsOn, const lambda& integrand, const Domain& domain) + /** + * @tparam dim The dimension of the boundary element (1 for line, 2 for quad, etc) + * @tparam lambda the type of the integrand functor: must implement operator() with an appropriate function signature + * @param[in] integrand The user-provided quadrature function, see @p Integral + * @param[in] domain which elements make up the domain of integration + * + * @brief Adds a interior face integral term to the Functional object + * + * @note The @p Dimension parameters are used to assist in the deduction of the @a geometry_dim + * and @a spatial_dim template parameter + */ + template + void AddInteriorFaceIntegral(Dimension, DependsOn, const Integrand& integrand, Domain& domain) { - auto num_bdr_elements = domain.mesh_.GetNBE(); - if (num_bdr_elements == 0) return; - - SLIC_ERROR_ROOT_IF(dim != domain.dim_, "invalid domain of integration for boundary integral"); - check_for_missing_nodal_gridfunc(domain.mesh_); + std::vector arg_vec = {args...}; + for (uint32_t i : arg_vec) { + domain.insert_restriction(trial_space_[i], trial_function_spaces_[i]); + check_interior_face_compatibility(domain.mesh_, trial_function_spaces_[i]); + } + using signature = test(decltype(serac::type(trial_spaces))...); - integrals_.push_back(MakeBoundaryIntegral(domain, integrand, std::vector{args...})); + integrals_.push_back(MakeInteriorFaceIntegral(domain, integrand, arg_vec)); } /** @@ -239,7 +220,7 @@ public: * @brief Adds an area integral, i.e., over 2D elements in R^2 */ template - void AddAreaIntegral(DependsOn which_args, const lambda& integrand, mfem::Mesh& domain, + void AddAreaIntegral(DependsOn which_args, const lambda& integrand, Domain& domain, std::shared_ptr>& data = NoQData) { AddDomainIntegral(Dimension<2>{}, which_args, integrand, domain, data); @@ -256,7 +237,7 @@ public: * @brief Adds a volume integral, i.e., over 3D elements in R^3 */ template - void AddVolumeIntegral(DependsOn which_args, const lambda& integrand, mfem::Mesh& domain, + void AddVolumeIntegral(DependsOn which_args, const lambda& integrand, Domain& domain, std::shared_ptr>& data = NoQData) { AddDomainIntegral(Dimension<3>{}, which_args, integrand, domain, data); @@ -264,7 +245,7 @@ public: /// @brief alias for Functional::AddBoundaryIntegral(Dimension<2>{}, integrand, domain); template - void AddSurfaceIntegral(DependsOn which_args, const lambda& integrand, mfem::Mesh& domain) + void AddSurfaceIntegral(DependsOn which_args, const lambda& integrand, Domain& domain) { AddBoundaryIntegral(Dimension<2>{}, which_args, integrand, domain); } @@ -285,25 +266,26 @@ public: output_L_ = 0.0; - // this is used to mark when gather operations have been performed, - // to avoid doing them more than once per trial space - bool already_computed[Domain::num_types]{}; // default initializes to `false` - for (auto& integral : integrals_) { - auto type = integral.domain_.type_; + if (integral.DependsOn(which)) { + Domain& dom = integral.domain_; - if (!already_computed[type]) { - G_trial_[type][which].Gather(input_L_[which], input_E_[type][which]); - already_computed[type] = true; - } + const serac::BlockElementRestriction& G_trial = dom.get_restriction(trial_function_spaces_[which]); + input_E_buffer_[which].SetSize(int(G_trial.ESize())); + input_E_[which].Update(input_E_buffer_[which], G_trial.bOffsets()); + G_trial.Gather(input_L_[which], input_E_[which]); + + output_E_buffer_.SetSize(dom.total_elements()); + output_E_.Update(output_E_buffer_, dom.bOffsets()); - integral.GradientMult(input_E_[type][which], output_E_[type], which); + integral.GradientMult(input_E_[which], output_E_, which); - // scatter-add to compute residuals on the local processor - G_test_.ScatterAdd(output_E_[type], output_L_); + // scatter-add to compute QoI value for the local processor + G_test_.ScatterAdd(output_E_, output_L_); + } } - // scatter-add to compute global residuals + // compute global QoI value by summing values from different processors P_test_.MultTranspose(output_L_, output_T_); return output_T_[0]; @@ -331,28 +313,27 @@ public: output_L_ = 0.0; - // this is used to mark when operations have been performed, - // to avoid doing them more than once - bool already_computed[Domain::num_types][num_trial_spaces]{}; // default initializes to `false` - for (auto& integral : integrals_) { - auto type = integral.domain_.type_; + Domain& dom = integral.domain_; for (auto i : integral.active_trial_spaces_) { - if (!already_computed[type][i]) { - G_trial_[type][i].Gather(input_L_[i], input_E_[type][i]); - already_computed[type][i] = true; - } + const serac::BlockElementRestriction& G_trial = dom.get_restriction(trial_function_spaces_[i]); + input_E_buffer_[i].SetSize(int(G_trial.ESize())); + input_E_[i].Update(input_E_buffer_[i], G_trial.bOffsets()); + G_trial.Gather(input_L_[i], input_E_[i]); } - const bool update_state = false; - integral.Mult(t, input_E_[type], output_E_[type], wrt, update_state); + output_E_buffer_.SetSize(dom.total_elements()); + output_E_.Update(output_E_buffer_, dom.bOffsets()); + + const bool update_qdata = false; + integral.Mult(t, input_E_, output_E_, wrt, update_qdata); - // scatter-add to compute residuals on the local processor - G_test_.ScatterAdd(output_E_[type], output_L_); + // scatter-add to compute QoI value for the local processor + G_test_.ScatterAdd(output_E_, output_L_); } - // scatter-add to compute global residuals + // compute global QoI value by summing values from different processors P_test_.MultTranspose(output_L_, output_T_); if constexpr (wrt != NO_DIFFERENTIATION) { @@ -419,6 +400,23 @@ private: double operator()(const mfem::Vector& x) const { return form_.ActionOfGradient(x, which_argument); } + uint64_t max_buffer_size() + { + uint64_t max_entries = 0; + for (auto& integral : form_.integrals_) { + if (integral.DependsOn(which_argument)) { + Domain& dom = integral.domain_; + const auto& G_trial = dom.get_restriction(form_.trial_function_spaces_[which_argument]); + for (const auto& [geom, test_restriction] : G_trial.restrictions) { + const auto& trial_restriction = G_trial.restrictions.at(geom); + uint64_t entries_per_element = trial_restriction.nodes_per_elem * trial_restriction.components; + max_entries = std::max(max_entries, trial_restriction.num_elements * entries_per_element); + } + } + } + return max_entries; + } + std::unique_ptr assemble() { // The mfem method ParFiniteElementSpace.NewTrueDofVector should really be marked const @@ -427,48 +425,48 @@ private: gradient_L_ = 0.0; + std::vector K_elem_buffer(max_buffer_size()); + std::map> element_gradients[Domain::num_types]; + //////////////////////////////////////////////////////////////////////////////// + for (auto& integral : form_.integrals_) { - auto& K_elem = element_gradients[integral.domain_.type_]; - auto& trial_restrictions = form_.G_trial_[integral.domain_.type_][which_argument].restrictions; + Domain& dom = integral.domain_; - if (K_elem.empty()) { - for (auto& [geom, trial_restriction] : trial_restrictions) { - K_elem[geom] = ExecArray(trial_restriction.num_elements, 1, - trial_restriction.nodes_per_elem * trial_restriction.components); + // if this integral's derivative isn't identically zero + if (integral.functional_to_integral_index_.count(which_argument) > 0) { + uint32_t id = integral.functional_to_integral_index_.at(which_argument); + const auto& G_trial = dom.get_restriction(form_.trial_function_spaces_[which_argument]); + for (const auto& [geom, calculate_element_gradients] : integral.element_gradient_[id]) { + const auto& trial_restriction = G_trial.restrictions.at(geom); - detail::zero_out(K_elem[geom]); - } - } + // prepare a buffer to hold the element matrices + CPUArrayView K_e(K_elem_buffer.data(), trial_restriction.num_elements, 1, + trial_restriction.nodes_per_elem * trial_restriction.components); + detail::zero_out(K_e); - integral.ComputeElementGradients(K_elem, which_argument); - } + calculate_element_gradients(K_e); - for (auto type : {Domain::Type::Elements, Domain::Type::BoundaryElements}) { - auto& K_elem = element_gradients[type]; - auto& trial_restrictions = form_.G_trial_[type][which_argument].restrictions; + const std::vector& element_ids = integral.domain_.get(geom); - if (!K_elem.empty()) { - for (auto [geom, elem_matrices] : K_elem) { - std::vector trial_vdofs(trial_restrictions[geom].nodes_per_elem * trial_restrictions[geom].components); + uint32_t cols_per_elem = uint32_t(trial_restriction.nodes_per_elem * trial_restriction.components); + std::vector trial_vdofs(cols_per_elem); - for (axom::IndexType e = 0; e < elem_matrices.shape()[0]; e++) { - trial_restrictions[geom].GetElementVDofs(e, trial_vdofs); + for (uint32_t e = 0; e < element_ids.size(); e++) { + trial_restriction.GetElementVDofs(int(e), trial_vdofs); - // note: elem_matrices.shape()[1] is 1 for a QoI - for (axom::IndexType i = 0; i < elem_matrices.shape()[1]; i++) { - for (axom::IndexType j = 0; j < elem_matrices.shape()[2]; j++) { - int sign = trial_vdofs[uint32_t(j)].sign(); - int col = int(trial_vdofs[uint32_t(j)].index()); - gradient_L_[col] += sign * elem_matrices(e, i, j); - } + for (uint32_t i = 0; i < cols_per_elem; i++) { + int col = int(trial_vdofs[i].index()); + gradient_L_[col] += K_e(e, 0, i); } } } } } + //////////////////////////////////////////////////////////////////////////////// + form_.P_trial_[which_argument]->MultTranspose(gradient_L_, *gradient_T); return gradient_T; @@ -494,6 +492,8 @@ private: /// @brief Manages DOFs for the trial space std::array trial_space_; + std::array trial_function_spaces_; + /** * @brief Operator that converts true (global) DOF values to local (current rank) DOF values * for the test space @@ -503,13 +503,13 @@ private: /// @brief The input set of local DOF values (i.e., on the current rank) mutable mfem::Vector input_L_[num_trial_spaces]; - BlockElementRestriction G_trial_[Domain::num_types][num_trial_spaces]; + mutable std::vector input_E_buffer_; + mutable std::vector input_E_; - mutable std::vector input_E_[Domain::num_types]; + mutable std::vector integrals_; - std::vector integrals_; - - mutable mfem::BlockVector output_E_[Domain::num_types]; + mutable mfem::Vector output_E_buffer_; + mutable mfem::BlockVector output_E_; QoIElementRestriction G_test_; @@ -523,6 +523,8 @@ private: /// @brief The objects representing the gradients w.r.t. each input argument of the Functional mutable std::vector grad_; + + const mfem::MemoryType mem_type; }; } // namespace serac diff --git a/src/serac/numerics/functional/geometric_factors.cpp b/src/serac/numerics/functional/geometric_factors.cpp index 7e8897fa5..55c906c7f 100644 --- a/src/serac/numerics/functional/geometric_factors.cpp +++ b/src/serac/numerics/functional/geometric_factors.cpp @@ -36,7 +36,7 @@ void compute_geometric_factors(mfem::Vector& positions_q, mfem::Vector& jacobian // for each element in the domain for (uint32_t e = 0; e < num_elements; e++) { // load the positions for the nodes in this element - auto X_e = X[elements[e]]; + auto X_e = X[e]; // calculate the values and derivatives (w.r.t. xi) of X at each quadrature point auto quadrature_values = element_type::interpolate(X_e, rule); @@ -59,164 +59,154 @@ void compute_geometric_factors(mfem::Vector& positions_q, mfem::Vector& jacobian } } -GeometricFactors::GeometricFactors(const Domain& d, int q, mfem::Geometry::Type g) +GeometricFactors::GeometricFactors(const Domain& domain, int q, mfem::Geometry::Type geom) { - auto* nodes = d.mesh_.GetNodes(); - auto* fes = nodes->FESpace(); + // const mfem::ParGridFunction* nodes = static_cast< const mfem::ParGridFunction * >(domain.mesh_.GetNodes()); + // mfem::ParFiniteElementSpace * pfes = nodes->ParFESpace(); + const mfem::GridFunction* nodes = domain.mesh_.GetNodes(); + const mfem::FiniteElementSpace* fes = nodes->FESpace(); - auto restriction = serac::ElementRestriction(fes, g); - mfem::Vector X_e(int(restriction.ESize())); - restriction.Gather(*nodes, X_e); - - // assumes all elements are the same order - int p = fes->GetElementOrder(0); - - int spatial_dim = d.mesh_.SpaceDimension(); - int geometry_dim = dimension_of(g); - int qpts_per_elem = num_quadrature_points(g, q); + const std::vector& element_ids = domain.get_mfem_ids(geom); - if (g == mfem::Geometry::TRIANGLE) elements = d.tri_ids_; - if (g == mfem::Geometry::SQUARE) elements = d.quad_ids_; - if (g == mfem::Geometry::TETRAHEDRON) elements = d.tet_ids_; - if (g == mfem::Geometry::CUBE) elements = d.hex_ids_; - - num_elements = elements.size(); - - X = mfem::Vector(int(num_elements) * qpts_per_elem * spatial_dim); - J = mfem::Vector(int(num_elements) * qpts_per_elem * spatial_dim * geometry_dim); - -#define DISPATCH_KERNEL(GEOM, P, Q) \ - if (g == mfem::Geometry::GEOM && p == P && q == Q) { \ - compute_geometric_factors >(X, J, X_e, \ - elements); \ - return; \ - } - - DISPATCH_KERNEL(TRIANGLE, 1, 1); - DISPATCH_KERNEL(TRIANGLE, 1, 2); - DISPATCH_KERNEL(TRIANGLE, 1, 3); - DISPATCH_KERNEL(TRIANGLE, 1, 4); - - DISPATCH_KERNEL(SQUARE, 1, 1); - DISPATCH_KERNEL(SQUARE, 1, 2); - DISPATCH_KERNEL(SQUARE, 1, 3); - DISPATCH_KERNEL(SQUARE, 1, 4); - - DISPATCH_KERNEL(SQUARE, 2, 1); - DISPATCH_KERNEL(SQUARE, 2, 2); - DISPATCH_KERNEL(SQUARE, 2, 3); - DISPATCH_KERNEL(SQUARE, 2, 4); - - DISPATCH_KERNEL(SQUARE, 3, 1); - DISPATCH_KERNEL(SQUARE, 3, 2); - DISPATCH_KERNEL(SQUARE, 3, 3); - DISPATCH_KERNEL(SQUARE, 3, 4); - - DISPATCH_KERNEL(TETRAHEDRON, 1, 1); - DISPATCH_KERNEL(TETRAHEDRON, 1, 2); - DISPATCH_KERNEL(TETRAHEDRON, 1, 3); - DISPATCH_KERNEL(TETRAHEDRON, 1, 4); - - DISPATCH_KERNEL(CUBE, 1, 1); - DISPATCH_KERNEL(CUBE, 1, 2); - DISPATCH_KERNEL(CUBE, 1, 3); - DISPATCH_KERNEL(CUBE, 1, 4); - - DISPATCH_KERNEL(CUBE, 2, 1); - DISPATCH_KERNEL(CUBE, 2, 2); - DISPATCH_KERNEL(CUBE, 2, 3); - DISPATCH_KERNEL(CUBE, 2, 4); - - DISPATCH_KERNEL(CUBE, 3, 1); - DISPATCH_KERNEL(CUBE, 3, 2); - DISPATCH_KERNEL(CUBE, 3, 3); - DISPATCH_KERNEL(CUBE, 3, 4); - -#undef DISPATCH_KERNEL - - std::cout << "should never be reached " << std::endl; -} - -GeometricFactors::GeometricFactors(const Domain& d, int q, mfem::Geometry::Type g, FaceType type) -{ - auto* nodes = d.mesh_.GetNodes(); - auto* fes = nodes->FESpace(); - - auto restriction = serac::ElementRestriction(fes, g, type); + auto restriction = serac::ElementRestriction(fes, geom, element_ids); mfem::Vector X_e(int(restriction.ESize())); restriction.Gather(*nodes, X_e); // assumes all elements are the same order int p = fes->GetElementOrder(0); - int spatial_dim = d.mesh_.SpaceDimension(); - int geometry_dim = dimension_of(g); - int qpts_per_elem = num_quadrature_points(g, q); + int spatial_dim = domain.mesh_.SpaceDimension(); + int geometry_dim = dimension_of(geom); + int qpts_per_elem = num_quadrature_points(geom, q); - // NB: we only want the number of elements with the specified - // geometry, which is not the same as mesh->GetNE() in general - elements = d.get(g); - - num_elements = std::size_t(elements.size()); + num_elements = element_ids.size(); X = mfem::Vector(int(num_elements) * qpts_per_elem * spatial_dim); J = mfem::Vector(int(num_elements) * qpts_per_elem * spatial_dim * geometry_dim); -#define DISPATCH_KERNEL(GEOM, P, Q) \ - if (g == mfem::Geometry::GEOM && p == P && q == Q) { \ - compute_geometric_factors >(X, J, X_e, \ - elements); \ - return; \ +#define DISPATCH_KERNEL(GEOM, P, Q, BDR) \ + if (geom == mfem::Geometry::GEOM && p == P && q == Q && (spatial_dim - geometry_dim) == BDR) { \ + compute_geometric_factors >(X, J, X_e, \ + element_ids); \ + return; \ } - DISPATCH_KERNEL(SEGMENT, 1, 1); - DISPATCH_KERNEL(SEGMENT, 1, 2); - DISPATCH_KERNEL(SEGMENT, 1, 3); - DISPATCH_KERNEL(SEGMENT, 1, 4); - - DISPATCH_KERNEL(SEGMENT, 2, 1); - DISPATCH_KERNEL(SEGMENT, 2, 2); - DISPATCH_KERNEL(SEGMENT, 2, 3); - DISPATCH_KERNEL(SEGMENT, 2, 4); - - DISPATCH_KERNEL(SEGMENT, 3, 1); - DISPATCH_KERNEL(SEGMENT, 3, 2); - DISPATCH_KERNEL(SEGMENT, 3, 3); - DISPATCH_KERNEL(SEGMENT, 3, 4); - - DISPATCH_KERNEL(TRIANGLE, 1, 1); - DISPATCH_KERNEL(TRIANGLE, 1, 2); - DISPATCH_KERNEL(TRIANGLE, 1, 3); - DISPATCH_KERNEL(TRIANGLE, 1, 4); - - DISPATCH_KERNEL(TRIANGLE, 2, 1); - DISPATCH_KERNEL(TRIANGLE, 2, 2); - DISPATCH_KERNEL(TRIANGLE, 2, 3); - DISPATCH_KERNEL(TRIANGLE, 2, 4); - - DISPATCH_KERNEL(TRIANGLE, 3, 1); - DISPATCH_KERNEL(TRIANGLE, 3, 2); - DISPATCH_KERNEL(TRIANGLE, 3, 3); - DISPATCH_KERNEL(TRIANGLE, 3, 4); - - DISPATCH_KERNEL(SQUARE, 1, 1); - DISPATCH_KERNEL(SQUARE, 1, 2); - DISPATCH_KERNEL(SQUARE, 1, 3); - DISPATCH_KERNEL(SQUARE, 1, 4); - - DISPATCH_KERNEL(SQUARE, 2, 1); - DISPATCH_KERNEL(SQUARE, 2, 2); - DISPATCH_KERNEL(SQUARE, 2, 3); - DISPATCH_KERNEL(SQUARE, 2, 4); - - DISPATCH_KERNEL(SQUARE, 3, 1); - DISPATCH_KERNEL(SQUARE, 3, 2); - DISPATCH_KERNEL(SQUARE, 3, 3); - DISPATCH_KERNEL(SQUARE, 3, 4); + DISPATCH_KERNEL(SEGMENT, 1, 1, 1); + DISPATCH_KERNEL(SEGMENT, 1, 2, 1); + DISPATCH_KERNEL(SEGMENT, 1, 3, 1); + DISPATCH_KERNEL(SEGMENT, 1, 4, 1); + + DISPATCH_KERNEL(SEGMENT, 2, 1, 1); + DISPATCH_KERNEL(SEGMENT, 2, 2, 1); + DISPATCH_KERNEL(SEGMENT, 2, 3, 1); + DISPATCH_KERNEL(SEGMENT, 2, 4, 1); + + DISPATCH_KERNEL(SEGMENT, 3, 1, 1); + DISPATCH_KERNEL(SEGMENT, 3, 2, 1); + DISPATCH_KERNEL(SEGMENT, 3, 3, 1); + DISPATCH_KERNEL(SEGMENT, 3, 4, 1); + + /////////////////////////////////////// + + DISPATCH_KERNEL(TRIANGLE, 1, 1, 0); + DISPATCH_KERNEL(TRIANGLE, 1, 2, 0); + DISPATCH_KERNEL(TRIANGLE, 1, 3, 0); + DISPATCH_KERNEL(TRIANGLE, 1, 4, 0); + + DISPATCH_KERNEL(TRIANGLE, 2, 1, 0); + DISPATCH_KERNEL(TRIANGLE, 2, 2, 0); + DISPATCH_KERNEL(TRIANGLE, 2, 3, 0); + DISPATCH_KERNEL(TRIANGLE, 2, 4, 0); + + DISPATCH_KERNEL(TRIANGLE, 3, 1, 0); + DISPATCH_KERNEL(TRIANGLE, 3, 2, 0); + DISPATCH_KERNEL(TRIANGLE, 3, 3, 0); + DISPATCH_KERNEL(TRIANGLE, 3, 4, 0); + + DISPATCH_KERNEL(TRIANGLE, 1, 1, 1); + DISPATCH_KERNEL(TRIANGLE, 1, 2, 1); + DISPATCH_KERNEL(TRIANGLE, 1, 3, 1); + DISPATCH_KERNEL(TRIANGLE, 1, 4, 1); + + DISPATCH_KERNEL(TRIANGLE, 2, 1, 1); + DISPATCH_KERNEL(TRIANGLE, 2, 2, 1); + DISPATCH_KERNEL(TRIANGLE, 2, 3, 1); + DISPATCH_KERNEL(TRIANGLE, 2, 4, 1); + + DISPATCH_KERNEL(TRIANGLE, 3, 1, 1); + DISPATCH_KERNEL(TRIANGLE, 3, 2, 1); + DISPATCH_KERNEL(TRIANGLE, 3, 3, 1); + DISPATCH_KERNEL(TRIANGLE, 3, 4, 1); + + /////////////////////////////////////// + + DISPATCH_KERNEL(SQUARE, 1, 1, 0); + DISPATCH_KERNEL(SQUARE, 1, 2, 0); + DISPATCH_KERNEL(SQUARE, 1, 3, 0); + DISPATCH_KERNEL(SQUARE, 1, 4, 0); + + DISPATCH_KERNEL(SQUARE, 2, 1, 0); + DISPATCH_KERNEL(SQUARE, 2, 2, 0); + DISPATCH_KERNEL(SQUARE, 2, 3, 0); + DISPATCH_KERNEL(SQUARE, 2, 4, 0); + + DISPATCH_KERNEL(SQUARE, 3, 1, 0); + DISPATCH_KERNEL(SQUARE, 3, 2, 0); + DISPATCH_KERNEL(SQUARE, 3, 3, 0); + DISPATCH_KERNEL(SQUARE, 3, 4, 0); + + DISPATCH_KERNEL(SQUARE, 1, 1, 1); + DISPATCH_KERNEL(SQUARE, 1, 2, 1); + DISPATCH_KERNEL(SQUARE, 1, 3, 1); + DISPATCH_KERNEL(SQUARE, 1, 4, 1); + + DISPATCH_KERNEL(SQUARE, 2, 1, 1); + DISPATCH_KERNEL(SQUARE, 2, 2, 1); + DISPATCH_KERNEL(SQUARE, 2, 3, 1); + DISPATCH_KERNEL(SQUARE, 2, 4, 1); + + DISPATCH_KERNEL(SQUARE, 3, 1, 1); + DISPATCH_KERNEL(SQUARE, 3, 2, 1); + DISPATCH_KERNEL(SQUARE, 3, 3, 1); + DISPATCH_KERNEL(SQUARE, 3, 4, 1); + + /////////////////////////////////////// + + DISPATCH_KERNEL(TETRAHEDRON, 1, 1, 0); + DISPATCH_KERNEL(TETRAHEDRON, 1, 2, 0); + DISPATCH_KERNEL(TETRAHEDRON, 1, 3, 0); + DISPATCH_KERNEL(TETRAHEDRON, 1, 4, 0); + + DISPATCH_KERNEL(TETRAHEDRON, 2, 1, 0); + DISPATCH_KERNEL(TETRAHEDRON, 2, 2, 0); + DISPATCH_KERNEL(TETRAHEDRON, 2, 3, 0); + DISPATCH_KERNEL(TETRAHEDRON, 2, 4, 0); + + DISPATCH_KERNEL(TETRAHEDRON, 3, 1, 0); + DISPATCH_KERNEL(TETRAHEDRON, 3, 2, 0); + DISPATCH_KERNEL(TETRAHEDRON, 3, 3, 0); + DISPATCH_KERNEL(TETRAHEDRON, 3, 4, 0); + + /////////////////////////////////////// + + DISPATCH_KERNEL(CUBE, 1, 1, 0); + DISPATCH_KERNEL(CUBE, 1, 2, 0); + DISPATCH_KERNEL(CUBE, 1, 3, 0); + DISPATCH_KERNEL(CUBE, 1, 4, 0); + + DISPATCH_KERNEL(CUBE, 2, 1, 0); + DISPATCH_KERNEL(CUBE, 2, 2, 0); + DISPATCH_KERNEL(CUBE, 2, 3, 0); + DISPATCH_KERNEL(CUBE, 2, 4, 0); + + DISPATCH_KERNEL(CUBE, 3, 1, 0); + DISPATCH_KERNEL(CUBE, 3, 2, 0); + DISPATCH_KERNEL(CUBE, 3, 3, 0); + DISPATCH_KERNEL(CUBE, 3, 4, 0); #undef DISPATCH_KERNEL - std::cout << "should never be reached" << std::endl; + std::cout << "should never be reached " << std::endl; } } // namespace serac diff --git a/src/serac/numerics/functional/geometric_factors.hpp b/src/serac/numerics/functional/geometric_factors.hpp index 5d33ab256..9983e6aff 100644 --- a/src/serac/numerics/functional/geometric_factors.hpp +++ b/src/serac/numerics/functional/geometric_factors.hpp @@ -27,17 +27,6 @@ struct GeometricFactors { */ GeometricFactors(const Domain& domain, int q, mfem::Geometry::Type elem_geom); - /** - * @brief calculate positions and jacobians for quadrature points belonging to - * boundary elements with the specified geometry, belonging to the provided mesh. - * - * @param domain the domain of integration - * @param q a parameter controlling the number of quadrature points per element - * @param elem_geom which kind of element geometry to select - * @param type whether or not the faces are on the boundary (supported) or interior (unsupported) - */ - GeometricFactors(const Domain& domain, int q, mfem::Geometry::Type elem_geom, FaceType type); - // descriptions copied from mfem /// Mapped (physical) coordinates of all quadrature points. @@ -57,9 +46,6 @@ struct GeometricFactors { - NE = number of elements in the mesh. */ mfem::Vector J; - /// @brief list of element indices that are part of the associated domain - std::vector elements; - /// the number of elements in the domain std::size_t num_elements; }; diff --git a/src/serac/numerics/functional/geometry.hpp b/src/serac/numerics/functional/geometry.hpp index eb4da0b2a..d56312c22 100644 --- a/src/serac/numerics/functional/geometry.hpp +++ b/src/serac/numerics/functional/geometry.hpp @@ -2,8 +2,6 @@ #include "mfem.hpp" -#include "serac/numerics/functional/domain.hpp" - namespace serac { /** @@ -78,23 +76,6 @@ inline std::array geometry_counts(cons return counts; } -/** - * @brief count the number of elements of each geometry in a domain - * @param domain the domain to count - */ -inline std::array geometry_counts(const Domain& domain) -{ - std::array counts{}; - - constexpr std::array geometries = {mfem::Geometry::SEGMENT, mfem::Geometry::TRIANGLE, - mfem::Geometry::SQUARE, mfem::Geometry::TETRAHEDRON, - mfem::Geometry::CUBE}; - for (auto geom : geometries) { - counts[uint32_t(geom)] = uint32_t(domain.get(geom).size()); - } - return counts; -} - /** * @brief count the number of boundary elements of each geometry in a mesh * @param mesh the mesh to count diff --git a/src/serac/numerics/functional/integral.hpp b/src/serac/numerics/functional/integral.hpp index 337118a7d..9444ba215 100644 --- a/src/serac/numerics/functional/integral.hpp +++ b/src/serac/numerics/functional/integral.hpp @@ -16,6 +16,7 @@ #include "serac/numerics/functional/function_signature.hpp" #include "serac/numerics/functional/domain_integral_kernels.hpp" #include "serac/numerics/functional/boundary_integral_kernels.hpp" +#include "serac/numerics/functional/interior_face_integral_kernels.hpp" #include "serac/numerics/functional/differentiate_wrt.hpp" namespace serac { @@ -23,7 +24,7 @@ namespace serac { /// @brief a class for representing a Integral calculations and their derivatives struct Integral { /// @brief the number of different kinds of integration domains - static constexpr std::size_t num_types = 2; + static constexpr std::size_t num_types = 3; /** * @brief Construct an "empty" Integral object, whose kernels are to be initialized later @@ -83,21 +84,22 @@ struct Integral { /** * @brief evaluate the jacobian(with respect to some trial space)-vector product of this integral * - * @param input_E a block vector (block index corresponds to the element geometry) of a specific trial space element + * @param dinput_E a block vector (block index corresponds to the element geometry) of a specific trial space element * values - * @param output_E a block vector (block index corresponds to the element geometry) of the output values for each + * @param doutput_E a block vector (block index corresponds to the element geometry) of the output values for each * element. * @param differentiation_index a non-negative value indicates directional derivative with respect to the trial space * with that index. */ - void GradientMult(const mfem::BlockVector& input_E, mfem::BlockVector& output_E, uint32_t differentiation_index) const + void GradientMult(const mfem::BlockVector& dinput_E, mfem::BlockVector& doutput_E, + uint32_t differentiation_index) const { - output_E = 0.0; + doutput_E = 0.0; // if this integral actually depends on the specified variable if (functional_to_integral_index_.count(differentiation_index) > 0) { for (auto& [geometry, func] : jvp_[functional_to_integral_index_.at(differentiation_index)]) { - func(input_E.GetBlock(geometry).Read(), output_E.GetBlock(geometry).ReadWrite()); + func(dinput_E.GetBlock(geometry).Read(), doutput_E.GetBlock(geometry).ReadWrite()); } } } @@ -120,6 +122,20 @@ struct Integral { } } + /** + * @brief returns whether or not this integral depends on argument `which` + * + * @param which an argument index + * @return true when if this Integral object was created with a DependsOn<...> statement that includes `i` + */ + bool DependsOn(uint32_t which) const + { + for (uint32_t i : active_trial_spaces_) { + if (which == i) return true; + } + return false; + } + /// @brief information about which elements to integrate over Domain domain_; @@ -192,13 +208,12 @@ void generate_kernels(FunctionSignature s, Integral& integral, const double* positions = gf.X.Read(); const double* jacobians = gf.J.Read(); - const int* elements = &integral.domain_.get(geom)[0]; const uint32_t num_elements = uint32_t(gf.num_elements); const uint32_t qpts_per_element = num_quadrature_points(geom, Q); std::shared_ptr dummy_derivatives; integral.evaluation_[geom] = domain_integral::evaluation_kernel( - s, qf, positions, jacobians, qdata, dummy_derivatives, elements, num_elements); + s, qf, positions, jacobians, qdata, dummy_derivatives, num_elements); constexpr std::size_t num_args = s.num_args; [[maybe_unused]] static constexpr int dim = dimension_of(geom); @@ -211,13 +226,12 @@ void generate_kernels(FunctionSignature s, Integral& integral, using derivative_type = decltype(domain_integral::get_derivative_type(qf, qpt_data_type{})); auto ptr = accelerator::make_shared_array(num_elements * qpts_per_element); - integral.evaluation_with_AD_[index][geom] = domain_integral::evaluation_kernel( - s, qf, positions, jacobians, qdata, ptr, elements, num_elements); + integral.evaluation_with_AD_[index][geom] = + domain_integral::evaluation_kernel(s, qf, positions, jacobians, qdata, ptr, num_elements); - integral.jvp_[index][geom] = - domain_integral::jacobian_vector_product_kernel(s, ptr, elements, num_elements); + integral.jvp_[index][geom] = domain_integral::jacobian_vector_product_kernel(s, ptr, num_elements); integral.element_gradient_[index][geom] = - domain_integral::element_gradient_kernel(s, ptr, elements, num_elements); + domain_integral::element_gradient_kernel(s, ptr, num_elements); }); } @@ -275,7 +289,7 @@ Integral MakeDomainIntegral(const Domain& domain, const lambda_type& qf, template void generate_bdr_kernels(FunctionSignature s, Integral& integral, const lambda_type& qf) { - integral.geometric_factors_[geom] = GeometricFactors(integral.domain_, Q, geom, FaceType::BOUNDARY); + integral.geometric_factors_[geom] = GeometricFactors(integral.domain_, Q, geom); GeometricFactors& gf = integral.geometric_factors_[geom]; if (gf.num_elements == 0) return; @@ -283,11 +297,10 @@ void generate_bdr_kernels(FunctionSignature s, Integral& integr const double* jacobians = gf.J.Read(); const uint32_t num_elements = uint32_t(gf.num_elements); const uint32_t qpts_per_element = num_quadrature_points(geom, Q); - const int* elements = &gf.elements[0]; std::shared_ptr dummy_derivatives; integral.evaluation_[geom] = boundary_integral::evaluation_kernel( - s, qf, positions, jacobians, dummy_derivatives, elements, num_elements); + s, qf, positions, jacobians, dummy_derivatives, num_elements); constexpr std::size_t num_args = s.num_args; [[maybe_unused]] static constexpr int dim = dimension_of(geom); @@ -301,12 +314,12 @@ void generate_bdr_kernels(FunctionSignature s, Integral& integr auto ptr = accelerator::make_shared_array(num_elements * qpts_per_element); integral.evaluation_with_AD_[index][geom] = - boundary_integral::evaluation_kernel(s, qf, positions, jacobians, ptr, elements, num_elements); + boundary_integral::evaluation_kernel(s, qf, positions, jacobians, ptr, num_elements); integral.jvp_[index][geom] = - boundary_integral::jacobian_vector_product_kernel(s, ptr, elements, num_elements); + boundary_integral::jacobian_vector_product_kernel(s, ptr, num_elements); integral.element_gradient_[index][geom] = - boundary_integral::element_gradient_kernel(s, ptr, elements, num_elements); + boundary_integral::element_gradient_kernel(s, ptr, num_elements); }); } @@ -346,4 +359,90 @@ Integral MakeBoundaryIntegral(const Domain& domain, const lambda_type& qf, std:: return integral; } +/** + * @brief function to generate kernels held by an `Integral` object of type "InteriorFaceDomain", with a specific + * element type + * + * @tparam geom the element geometry + * @tparam Q a parameter that controls the number of quadrature points + * @tparam test the kind of test functions used in the integral + * @tparam trials the trial space(s) of the integral's inputs + * @tparam lambda_type a callable object that implements the q-function concept + * @param s an object used to pass around test/trial information + * @param integral the Integral object to initialize + * @param qf the quadrature function + */ +template +void generate_interior_face_kernels(FunctionSignature s, Integral& integral, const lambda_type& qf) +{ + integral.geometric_factors_[geom] = GeometricFactors(integral.domain_, Q, geom); + GeometricFactors& gf = integral.geometric_factors_[geom]; + if (gf.num_elements == 0) return; + + const double* positions = gf.X.Read(); + const double* jacobians = gf.J.Read(); + const uint32_t num_elements = uint32_t(gf.num_elements); + const uint32_t qpts_per_element = num_quadrature_points(geom, Q); + + std::shared_ptr dummy_derivatives; + integral.evaluation_[geom] = interior_face_integral::evaluation_kernel( + s, qf, positions, jacobians, dummy_derivatives, num_elements); + + constexpr std::size_t num_args = s.num_args; + [[maybe_unused]] static constexpr int dim = dimension_of(geom); + for_constexpr([&](auto index) { + // allocate memory for the derivatives of the q-function at each quadrature point + // + // Note: ptrs' lifetime is managed in an unusual way! It is captured by-value in the + // action_of_gradient functor below to augment the reference count, and extend its lifetime to match + // that of the boundaryIntegral that allocated it. + using derivative_type = decltype(interior_face_integral::get_derivative_type(qf)); + auto ptr = accelerator::make_shared_array(num_elements * qpts_per_element); + + integral.evaluation_with_AD_[index][geom] = + interior_face_integral::evaluation_kernel(s, qf, positions, jacobians, ptr, num_elements); + + integral.jvp_[index][geom] = + interior_face_integral::jacobian_vector_product_kernel(s, ptr, num_elements); + integral.element_gradient_[index][geom] = + interior_face_integral::element_gradient_kernel(s, ptr, num_elements); + }); +} + +/** + * @brief function to generate kernels held by an `Integral` object of type "Boundary", for all element types + * + * @tparam s a function signature type containing test/trial space informationa type containing a function signature + * @tparam Q a parameter that controls the number of quadrature points + * @tparam dim the dimension of the domain + * @tparam lambda_type a callable object that implements the q-function concept + * @param domain the domain of integration + * @param qf the quadrature function + * @param argument_indices the indices of trial space arguments used in the Integral + * @return Integral the initialized `Integral` object + * + * @note this function is not meant to be called by users + */ +template +Integral MakeInteriorFaceIntegral(const Domain& domain, const lambda_type& qf, std::vector argument_indices) +{ + FunctionSignature signature; + + SLIC_ERROR_IF(domain.type_ != Domain::Type::InteriorFaces, + "Error: trying to evaluate a boundary integral over a non-boundary domain of integration"); + + Integral integral(domain, argument_indices); + + if constexpr (dim == 1) { + generate_interior_face_kernels(signature, integral, qf); + } + + if constexpr (dim == 2) { + generate_interior_face_kernels(signature, integral, qf); + generate_interior_face_kernels(signature, integral, qf); + } + + return integral; +} + } // namespace serac diff --git a/src/serac/numerics/functional/interior_face_integral_kernels.hpp b/src/serac/numerics/functional/interior_face_integral_kernels.hpp new file mode 100644 index 000000000..83842deee --- /dev/null +++ b/src/serac/numerics/functional/interior_face_integral_kernels.hpp @@ -0,0 +1,375 @@ +// Copyright (c) 2019-2024, Lawrence Livermore National Security, LLC and +// other Serac Project Developers. See the top-level LICENSE file for +// details. +// +// SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + +#include + +#include "serac/serac_config.hpp" +#include "serac/numerics/functional/quadrature_data.hpp" +#include "serac/numerics/functional/differentiate_wrt.hpp" + +namespace serac { + +namespace interior_face_integral { + +/** + * @tparam space the user-specified trial space + * @tparam dimension describes whether the problem is 1D, 2D, or 3D + * + * @brief a struct used to encode what type of arguments will be passed to a domain integral q-function, for the given + * trial space + */ +template +struct QFunctionArgument; + +/// @overload +template +struct QFunctionArgument, Dimension<1>> { + using type = serac::tuple; ///< what will be passed to the q-function +}; + +/// @overload +template +struct QFunctionArgument, Dimension> { + using type = serac::tuple>; ///< what will be passed to the q-function +}; + +/// @overload +template +struct QFunctionArgument, Dimension<1>> { + using type = serac::tuple, tensor>; ///< what will be passed to the q-function +}; + +/// @overload +template +struct QFunctionArgument, Dimension> { + using type = serac::tuple, tensor>; ///< what will be passed to the q-function +}; + +/// @overload +template +struct QFunctionArgument, Dimension> { + using type = serac::tuple; ///< what will be passed to the q-function +}; + +/// @overload +template +struct QFunctionArgument, Dimension> { + using type = serac::tuple, tensor>; ///< what will be passed to the q-function +}; + +/// @overload +SERAC_SUPPRESS_NVCC_HOSTDEVICE_WARNING +template +SERAC_HOST_DEVICE auto apply_qf_helper(const lambda& qf, double t, const tensor& x_q, const T& arg_tuple, + std::integer_sequence) +{ + tensor J_q{}; + return qf(t, serac::tuple{x_q, J_q}, serac::get(arg_tuple)...); +} + +/// @overload +SERAC_SUPPRESS_NVCC_HOSTDEVICE_WARNING +template +SERAC_HOST_DEVICE auto apply_qf_helper(const lambda& qf, double t, const tensor& x_q, const T& arg_tuple, + std::integer_sequence) +{ + constexpr int dim = 3; + tensor J_q{}; + return qf(t, serac::tuple{x_q, J_q}, serac::get(arg_tuple)...); +} + +/// @overload +template +SERAC_HOST_DEVICE auto apply_qf(const lambda& qf, double t, const coords_type& x_q, const serac::tuple& arg_tuple) +{ + return apply_qf_helper(qf, t, x_q, arg_tuple, std::make_integer_sequence(sizeof...(T))>{}); +} + +template +auto get_derivative_type(lambda qf) +{ + using qf_arguments = serac::tuple>::type...>; + return get_gradient(apply_qf(qf, double{}, tensor{}, make_dual_wrt(qf_arguments{}))); +}; + +template +SERAC_HOST_DEVICE auto batch_apply_qf(lambda qf, double t, const tensor& positions, + const tensor& jacobians, const T&... inputs) +{ + constexpr int dim = 2; + using first_arg_t = serac::tuple, tensor>; + using return_type = decltype(qf(double{}, first_arg_t{}, T{}[0]...)); + tensor outputs{}; + for (int i = 0; i < n; i++) { + tensor x_q; + tensor J_q; + for (int j = 0; j < dim; j++) { + x_q[j] = positions(j, i); + J_q[j] = jacobians(0, j, i); + } + double scale = norm(cross(J_q)); + + outputs[i] = qf(t, serac::tuple{x_q, J_q}, inputs[i]...) * scale; + } + return outputs; +} + +template +SERAC_HOST_DEVICE auto batch_apply_qf(lambda qf, double t, const tensor& positions, + const tensor& jacobians, const T&... inputs) +{ + constexpr int dim = 3; + using first_arg_t = serac::tuple, tensor>; + using return_type = decltype(qf(double{}, first_arg_t{}, T{}[0]...)); + tensor outputs{}; + for (int i = 0; i < n; i++) { + tensor x_q; + tensor J_q; + for (int j = 0; j < dim; j++) { + x_q[j] = positions(j, i); + for (int k = 0; k < dim - 1; k++) { + J_q(j, k) = jacobians(k, j, i); + } + } + double scale = norm(cross(J_q)); + + outputs[i] = qf(t, serac::tuple{x_q, J_q}, inputs[i]...) * scale; + } + return outputs; +} + +/// @trial_elements the element type for each trial space +template +void evaluation_kernel_impl(trial_element_type trial_elements, test_element, double t, + const std::vector& inputs, double* outputs, const double* positions, + const double* jacobians, lambda_type qf, [[maybe_unused]] derivative_type* qf_derivatives, + uint32_t num_elements, camp::int_seq) +{ + // mfem provides this information as opaque arrays of doubles, + // so we reinterpret the pointer with + constexpr int dim = dimension_of(geom) + 1; + constexpr int nqp = num_quadrature_points(geom, Q); + auto J = reinterpret_cast*>(jacobians); + auto x = reinterpret_cast*>(positions); + auto r = reinterpret_cast(outputs); + static constexpr TensorProductQuadratureRule rule{}; + + static constexpr int qpts_per_elem = num_quadrature_points(geom, Q); + + [[maybe_unused]] tuple u = { + reinterpret_cast(trial_elements))::dof_type_if*>(inputs[indices])...}; + + // for each element in the domain + for (uint32_t e = 0; e < num_elements; e++) { + // load the jacobians and positions for each quadrature point in this element + auto J_e = J[e]; + auto x_e = x[e]; + + // batch-calculate values / derivatives of each trial space, at each quadrature point + [[maybe_unused]] tuple qf_inputs = {promote_each_to_dual_when( + get(trial_elements).interpolate(get(u)[e], rule))...}; + + // (batch) evalute the q-function at each quadrature point + auto qf_outputs = batch_apply_qf(qf, t, x_e, J_e, get(qf_inputs)...); + + // write out the q-function derivatives after applying the + // physical_to_parent transformation, so that those transformations + // won't need to be applied in the action_of_gradient and element_gradient kernels + if constexpr (differentiation_index != serac::NO_DIFFERENTIATION) { + for (int q = 0; q < leading_dimension(qf_outputs); q++) { + qf_derivatives[e * qpts_per_elem + uint32_t(q)] = get_gradient(qf_outputs[q]); + } + } + + // (batch) integrate the material response against the test-space basis functions + test_element::integrate(get_value(qf_outputs), rule, &r[e]); + } +} + +//clang-format off +template +SERAC_HOST_DEVICE auto chain_rule(const S& dfdx, const T& dx) +{ + if constexpr (is_QOI) { + return serac::chain_rule(serac::get<0>(dfdx), serac::get<0>(dx)) + + serac::chain_rule(serac::get<1>(dfdx), serac::get<1>(dx)); + } + + if constexpr (!is_QOI) { + return serac::tuple{serac::chain_rule(serac::get<0>(serac::get<0>(dfdx)), serac::get<0>(dx)) + + serac::chain_rule(serac::get<1>(serac::get<0>(dfdx)), serac::get<1>(dx)), + serac::chain_rule(serac::get<0>(serac::get<1>(dfdx)), serac::get<0>(dx)) + + serac::chain_rule(serac::get<1>(serac::get<1>(dfdx)), serac::get<1>(dx))}; + } +} +//clang-format on + +template +SERAC_HOST_DEVICE auto batch_apply_chain_rule(derivative_type* qf_derivatives, const tensor& inputs) +{ + using return_type = decltype(chain_rule(derivative_type{}, T{})); + tensor outputs{}; + for (int i = 0; i < n; i++) { + outputs[i] = chain_rule(qf_derivatives[i], inputs[i]); + } + return outputs; +} + +/** + * @brief The base kernel template used to create create custom directional derivative + * kernels associated with finite element calculations + * + * @tparam test The type of the test function space + * @tparam trial The type of the trial function space + * The above spaces can be any combination of {H1, Hcurl, Hdiv (TODO), L2 (TODO)} + * + * Template parameters other than the test and trial spaces are used for customization + optimization + * and are erased through the @p std::function members of @p BoundaryIntegral + * @tparam g The shape of the element (only quadrilateral and hexahedron are supported at present) + * @tparam Q parameter describing number of quadrature points (see num_quadrature_points() function for more details) + * @tparam derivatives_type Type representing the derivative of the q-function w.r.t. its input arguments + * + * @note lambda does not appear as a template argument, as the directional derivative is + * inherently just a linear transformation + * + * @param[in] dU The full set of per-element DOF values (primary input) + * @param[inout] dR The full set of per-element residuals (primary output) + * @param[in] derivatives_ptr The address at which derivatives of the q-function with + * respect to its arguments are stored + * @param[in] J_ The Jacobians of the element transformations at all quadrature points + * @see mfem::GeometricFactors + * @param[in] num_elements The number of elements in the mesh + */ +template +void action_of_gradient_kernel(const double* dU, double* dR, derivatives_type* qf_derivatives, std::size_t num_elements) +{ + using test_element = finite_element; + using trial_element = finite_element; + + // mfem provides this information in 1D arrays, so we reshape it + // into strided multidimensional arrays before using + constexpr bool is_QOI = (test::family == Family::QOI); + constexpr int nqp = num_quadrature_points(geom, Q); + auto du = reinterpret_cast(dU); + auto dr = reinterpret_cast(dR); + static constexpr TensorProductQuadratureRule rule{}; + + // for each element in the domain + for (uint32_t e = 0; e < num_elements; e++) { + // (batch) interpolate each quadrature point's value + auto qf_inputs = trial_element::interpolate(du[e], rule); + + // (batch) evalute the q-function at each quadrature point + auto qf_outputs = batch_apply_chain_rule(qf_derivatives + e * nqp, qf_inputs); + + // (batch) integrate the material response against the test-space basis functions + test_element::integrate(qf_outputs, rule, &dr[e]); + } +} + +/** + * @brief The base kernel template used to compute tangent element entries that can be assembled + * into a tangent matrix + * + * @tparam test The type of the test function space + * @tparam trial The type of the trial function space + * The above spaces can be any combination of {H1, Hcurl, Hdiv (TODO), L2 (TODO), QOI} + * + * Template parameters other than the test and trial spaces are used for customization + optimization + * and are erased through the @p std::function members of @p Integral + * @tparam g The shape of the element (only quadrilateral and hexahedron are supported at present) + * @tparam Q parameter describing number of quadrature points (see num_quadrature_points() function for more details) + * @tparam derivatives_type Type representing the derivative of the q-function w.r.t. its input arguments + * + * + * @param[inout] dk 3-dimensional array storing the element gradient matrices + * @param[in] derivatives_ptr pointer to data describing the derivatives of the q-function with respect to its arguments + * @param[in] J_ The Jacobians of the element transformations at all quadrature points + * @see mfem::GeometricFactors + * @param[in] num_elements The number of elements in the mesh + */ +template +void element_gradient_kernel([[maybe_unused]] ExecArrayView dK, + [[maybe_unused]] derivatives_type* qf_derivatives, + [[maybe_unused]] std::size_t num_elements) +{ + using test_element = finite_element; + using trial_element = finite_element; + + constexpr bool is_QOI = test::family == Family::QOI; + using padded_derivative_type = std::conditional_t, derivatives_type>; + + constexpr int nquad = num_quadrature_points(g, Q); + + static constexpr TensorProductQuadratureRule rule{}; + + // for each element in the domain + for (uint32_t e = 0; e < num_elements; e++) { + auto* output_ptr = reinterpret_cast(&dK(e, 0, 0)); + + tensor derivatives{}; + for (int q = 0; q < nquad; q++) { + if constexpr (is_QOI) { + get<0>(derivatives(q)) = qf_derivatives[e * nquad + uint32_t(q)]; + } else { + derivatives(q) = qf_derivatives[e * nquad + uint32_t(q)]; + } + } + + if constexpr (trial::family == Family::L2) { + for (int J = 0; J < 2 * trial_element::ndof; J++) { + auto source_and_flux = trial_element::batch_apply_shape_fn_interior_face(J, derivatives, rule); + test_element::integrate(source_and_flux, rule, output_ptr + J, 2 * trial_element::ndof); + } + } else { + for (int J = 0; J < trial_element::ndof; J++) { + auto source_and_flux = trial_element::batch_apply_shape_fn(J, derivatives, rule); + test_element::integrate(source_and_flux, rule, output_ptr + J, trial_element::ndof); + } + } + } +} + +template +auto evaluation_kernel(signature s, lambda_type qf, const double* positions, const double* jacobians, + std::shared_ptr qf_derivatives, uint32_t num_elements) +{ + auto trial_elements = trial_elements_tuple(s); + auto test_element = get_test_element(s); + return [=](double time, const std::vector& inputs, double* outputs, bool /* update state */) { + evaluation_kernel_impl(trial_elements, test_element, time, inputs, outputs, positions, jacobians, qf, + qf_derivatives.get(), num_elements, s.index_seq); + }; +} + +template +std::function jacobian_vector_product_kernel( + signature, std::shared_ptr qf_derivatives, uint32_t num_elements) +{ + return [=](const double* du, double* dr) { + using test_space = typename signature::return_type; + using trial_space = typename std::tuple_element::type; + action_of_gradient_kernel(du, dr, qf_derivatives.get(), num_elements); + }; +} + +template +std::function)> element_gradient_kernel( + signature, std::shared_ptr qf_derivatives, uint32_t num_elements) +{ + return [=](ExecArrayView K_elem) { + using test_space = typename signature::return_type; + using trial_space = typename std::tuple_element::type; + element_gradient_kernel(K_elem, qf_derivatives.get(), num_elements); + }; +} + +} // namespace interior_face_integral + +} // namespace serac diff --git a/src/serac/numerics/functional/shape_aware_functional.hpp b/src/serac/numerics/functional/shape_aware_functional.hpp index 2e2cfc172..9de6eb1ae 100644 --- a/src/serac/numerics/functional/shape_aware_functional.hpp +++ b/src/serac/numerics/functional/shape_aware_functional.hpp @@ -443,7 +443,6 @@ class ShapeAwareFunctional { * @tparam dim The dimension of the element (2 for quad, 3 for hex, etc) * @tparam args The type of the trial function input arguments * @tparam lambda The type of the integrand functor: must implement operator() with an appropriate function signature - * @tparam domain_type The type of the integration domain (either serac::Domain or mfem::Mesh) * @tparam qpt_data_type The type of the data to store for each quadrature point * * @param[in] integrand The user-provided quadrature function, see @p Integral @@ -453,8 +452,8 @@ class ShapeAwareFunctional { * @note The @p Dimension parameters are used to assist in the deduction of the @a geometry_dim * and @a spatial_dim template parameter */ - template - void AddDomainIntegral(Dimension, DependsOn, const lambda& integrand, domain_type& domain, + template + void AddDomainIntegral(Dimension, DependsOn, const lambda& integrand, Domain& domain, std::shared_ptr> qdata = NoQData) { if constexpr (std::is_same_v) { @@ -512,8 +511,8 @@ class ShapeAwareFunctional { * @note The @p Dimension parameters are used to assist in the deduction of the @a geometry_dim * and @a spatial_dim template parameter */ - template - void AddBoundaryIntegral(Dimension, DependsOn, const lambda& integrand, domain_type& domain) + template + void AddBoundaryIntegral(Dimension, DependsOn, const lambda& integrand, Domain& domain) { functional_->AddBoundaryIntegral(Dimension{}, DependsOn<0, (args + 1)...>{}, ShapeAwareBoundaryIntegrandWrapper(integrand), domain); diff --git a/src/serac/numerics/functional/tests/CMakeLists.txt b/src/serac/numerics/functional/tests/CMakeLists.txt index 96e3178d1..62bf13048 100644 --- a/src/serac/numerics/functional/tests/CMakeLists.txt +++ b/src/serac/numerics/functional/tests/CMakeLists.txt @@ -18,6 +18,9 @@ blt_add_executable(NAME tensor_unit_tests set(functional_serial_test_sources functional_shape_derivatives.cpp simplex_basis_function_unit_tests.cpp + element_restriction_tests.cpp + dg_restriction_operators.cpp + functional_basic_dg.cpp bug_boundary_qoi.cpp domain_tests.cpp geometric_factors_tests.cpp diff --git a/src/serac/numerics/functional/tests/bug_boundary_qoi.cpp b/src/serac/numerics/functional/tests/bug_boundary_qoi.cpp index 97e6a6159..38f0932e9 100644 --- a/src/serac/numerics/functional/tests/bug_boundary_qoi.cpp +++ b/src/serac/numerics/functional/tests/bug_boundary_qoi.cpp @@ -48,8 +48,10 @@ TEST(BoundaryIntegralQOI, AttrBug) using shapeFES = serac::H1; auto [shape_fes, shape_fec] = serac::generateParFiniteElementSpace(pmesh.get()); + Domain whole_boundary = EntireBoundary(*pmesh); + serac::ShapeAwareFunctional totalSurfArea(shape_fes.get(), {}); - totalSurfArea.AddBoundaryIntegral(serac::Dimension<2 - 1>{}, serac::DependsOn<>{}, IdentityFunctor{}, *pmesh); + totalSurfArea.AddBoundaryIntegral(serac::Dimension<2 - 1>{}, serac::DependsOn<>{}, IdentityFunctor{}, whole_boundary); serac::FiniteElementState shape(*shape_fes); double totalSurfaceArea = totalSurfArea(0.0, shape); diff --git a/src/serac/numerics/functional/tests/check_gradient.hpp b/src/serac/numerics/functional/tests/check_gradient.hpp index fb48070f3..e8fcdc376 100644 --- a/src/serac/numerics/functional/tests/check_gradient.hpp +++ b/src/serac/numerics/functional/tests/check_gradient.hpp @@ -13,6 +13,44 @@ #include "serac/serac_config.hpp" #include "serac/numerics/functional/functional.hpp" +template +void debug_sparse_matrix(serac::Functional& f, double t, const mfem::Vector& U, + [[maybe_unused]] double epsilon = 1.0e-4) +{ + mfem::Vector dU(U.Size()); + dU = 0.0; + + auto [value, dfdU] = f(t, serac::differentiate_wrt(U)); + std::unique_ptr dfdU_matrix = assemble(dfdU); + + std::cout << "{"; + for (int i = 0; i < U.Size(); i++) { + dU[i] = 1; + mfem::Vector df_jvp = dfdU(dU); // matrix-free + + std::cout << "{"; + for (int j = 0; j < df_jvp.Size(); j++) { + std::cout << df_jvp[j]; + if (j != df_jvp.Size() - 1) { + std::cout << ","; + } else { + std::cout << " "; + } + } + std::cout << "}"; + if (i != U.Size() - 1) { + std::cout << ",\n"; + } else { + std::cout << "\n"; + } + + dU[i] = 0; + } + std::cout << "}" << std::endl; + + dfdU_matrix->Print("K.mtx"); +} + template void check_gradient(serac::Functional& f, double t, mfem::Vector& U, double epsilon = 1.0e-4) { @@ -98,7 +136,7 @@ void check_gradient(serac::Functional& f, double t, const mfem::Vector& U, co mfem::Vector dU(U.Size()); dU.Randomize(seed); - mfem::Vector ddU_dt(U.Size()); + mfem::Vector ddU_dt(dU_dt.Size()); ddU_dt.Randomize(seed + 1); { @@ -111,9 +149,12 @@ void check_gradient(serac::Functional& f, double t, const mfem::Vector& U, co mfem::Vector df_jvp2(df_jvp1.Size()); dfdU_matrix->Mult(dU, df_jvp2); // sparse matvec - if (df_jvp1.Norml2() != 0) { + if (df_jvp1.Norml2() < 1.0e-13) { + double absolute_error = df_jvp1.DistanceTo(df_jvp2.GetData()); + EXPECT_NEAR(0., absolute_error, 5.e-14); + } else { double relative_error = df_jvp1.DistanceTo(df_jvp2.GetData()) / df_jvp1.Norml2(); - EXPECT_NEAR(0., relative_error, 5.e-6); + EXPECT_NEAR(0., relative_error, 5.e-14); } // {f(x - 2 * h), f(x - h), f(x), f(x + h), f(x + 2 * h)} @@ -171,8 +212,13 @@ void check_gradient(serac::Functional& f, double t, const mfem::Vector& U, co mfem::Vector df_jvp2(df_jvp1.Size()); df_ddU_dt_matrix->Mult(ddU_dt, df_jvp2); // sparse matvec - double relative_error = df_jvp1.DistanceTo(df_jvp2.GetData()) / df_jvp1.Norml2(); - EXPECT_NEAR(0., relative_error, 5.e-14); + if (df_jvp1.Norml2() < 1.0e-13) { + double absolute_error = df_jvp1.DistanceTo(df_jvp2.GetData()); + EXPECT_NEAR(0., absolute_error, 5.e-14); + } else { + double relative_error = df_jvp1.DistanceTo(df_jvp2.GetData()) / df_jvp1.Norml2(); + EXPECT_NEAR(0., relative_error, 5.e-14); + } // {f(x - 2 * h), f(x - h), f(x), f(x + h), f(x + 2 * h)} mfem::Vector f_values[5]; diff --git a/src/serac/numerics/functional/tests/dg_restriction_operators.cpp b/src/serac/numerics/functional/tests/dg_restriction_operators.cpp new file mode 100644 index 000000000..74cadd1a1 --- /dev/null +++ b/src/serac/numerics/functional/tests/dg_restriction_operators.cpp @@ -0,0 +1,553 @@ +#include + +#include "serac/mesh/mesh_utils_base.hpp" +#include "serac/numerics/functional/domain.hpp" +#include "serac/numerics/functional/element_restriction.hpp" +#include "serac/numerics/functional/functional.hpp" + +using namespace serac; + +std::string mesh_dir = SERAC_REPO_DIR "/data/meshes/"; + +constexpr mfem::Geometry::Type face_type(mfem::Geometry::Type geom) +{ + if (geom == mfem::Geometry::TRIANGLE) return mfem::Geometry::SEGMENT; + if (geom == mfem::Geometry::SQUARE) return mfem::Geometry::SEGMENT; + if (geom == mfem::Geometry::TETRAHEDRON) return mfem::Geometry::TRIANGLE; + if (geom == mfem::Geometry::CUBE) return mfem::Geometry::SQUARE; + return mfem::Geometry::INVALID; +} + +int possible_permutations(mfem::Geometry::Type geom) +{ + if (geom == mfem::Geometry::TRIANGLE) return 3; + if (geom == mfem::Geometry::SQUARE) return 4; + if (geom == mfem::Geometry::TETRAHEDRON) return 12; + if (geom == mfem::Geometry::CUBE) return 24; + return -1; +} + +template +std::array apply_permutation(const int (&arr)[n], const int (&p)[n]) +{ + std::array permuted_arr{}; + for (uint32_t i = 0; i < n; i++) { + permuted_arr[i] = arr[p[i]]; + } + return permuted_arr; +} + +mfem::Mesh generate_permuted_mesh(mfem::Geometry::Type geom, int i) +{ + if (geom == mfem::Geometry::TRIANGLE) { + constexpr int dim = 2; + constexpr int num_elements = 2; + constexpr int num_vertices = 4; + constexpr int num_permutations = 3; + int positive_permutations[num_permutations][3] = {{0, 1, 2}, {1, 2, 0}, {2, 0, 1}}; + + /* + y + ^ + | + 3----------2 + |'. | + | '. | + | '. | + | '. | + | '.| + 0----------1--> x + */ + int elements[num_elements][3] = {{0, 1, 3}, {1, 2, 3}}; + double vertices[num_vertices][dim] = {{0.0, 0.0}, {1.0, 0.0}, {1.0, 1.0}, {0.0, 1.0}}; + + mfem::Mesh output(dim, num_vertices, num_elements); + + for (auto vertex : vertices) { + output.AddVertex(vertex); + } + + // the first element is always fixed + output.AddTri(elements[0]); + + // but the second element is permuted to the specified orientation + auto permuted_element = apply_permutation(elements[1], positive_permutations[i]); + output.AddTri(permuted_element.data()); + + output.FinalizeMesh(); + + return output; + } + + if (geom == mfem::Geometry::SQUARE) { + constexpr int dim = 2; + constexpr int num_elements = 2; + constexpr int num_vertices = 6; + constexpr int num_permutations = 4; + int positive_permutations[num_permutations][4] = {{0, 1, 2, 3}, {1, 2, 3, 0}, {2, 3, 0, 1}, {3, 0, 1, 2}}; + + /* + y + ^ + | + 3----------4----------5 + | | | + | | | + | | | + | | | + 0----------1----------2--> x + */ + int elements[num_elements][4] = {{0, 1, 4, 3}, {1, 2, 5, 4}}; + double vertices[num_vertices][dim] = {{0.0, 0.0}, {1.0, 0.0}, {2.0, 0.0}, {0.0, 1.0}, {1.0, 1.0}, {2.0, 1.0}}; + + mfem::Mesh output(dim, num_vertices, num_elements); + + for (auto vertex : vertices) { + output.AddVertex(vertex); + } + + // the first element is always fixed + output.AddQuad(elements[0]); + + // but the second element is permuted to the specified orientation + auto permuted_element = apply_permutation(elements[1], positive_permutations[i]); + output.AddQuad(permuted_element.data()); + + output.FinalizeMesh(); + + return output; + } + + if (geom == mfem::Geometry::TETRAHEDRON) { + constexpr int dim = 3; + constexpr int num_elements = 2; + constexpr int num_vertices = 5; + constexpr int num_permutations = 12; + int positive_permutations[num_permutations][4] = {{0, 1, 2, 3}, {0, 2, 3, 1}, {0, 3, 1, 2}, {1, 0, 3, 2}, + {1, 2, 0, 3}, {1, 3, 2, 0}, {2, 0, 1, 3}, {2, 1, 3, 0}, + {2, 3, 0, 1}, {3, 0, 2, 1}, {3, 1, 0, 2}, {3, 2, 1, 0}}; + + /* + + .4. + y .*'/ '*. + \ .*' / '*. + 2--.../ '*. + |\ / '---... '*. x + | \ / '''---...'*. .*' + | / :::>1 + z | / \ ...---'''.*' + '*. |/ ...---''' .*' + 3--'''\ .*' + '*. \ .*' + '*.\ .*' + '0' + + */ + int elements[num_elements][4] = {{0, 1, 2, 3}, {1, 2, 3, 4}}; + double vertices[num_vertices][dim] = {{0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {0, 0, 1}, {1, 1, 1}}; + + mfem::Mesh output(dim, num_vertices, num_elements); + + for (auto vertex : vertices) { + output.AddVertex(vertex); + } + + // the first element is always fixed + output.AddTet(elements[0]); + + // but the second element is permuted to the specified orientation + auto permuted_element = apply_permutation(elements[1], positive_permutations[i]); + output.AddTet(permuted_element.data()); + + output.FinalizeMesh(); + + return output; + } + + if (geom == mfem::Geometry::CUBE) { + constexpr int dim = 3; + constexpr int num_elements = 2; + constexpr int num_vertices = 12; + constexpr int num_permutations = 24; + int positive_permutations[num_permutations][8] = { + {0, 1, 2, 3, 4, 5, 6, 7}, {0, 3, 7, 4, 1, 2, 6, 5}, {0, 4, 5, 1, 3, 7, 6, 2}, {1, 0, 4, 5, 2, 3, 7, 6}, + {1, 2, 3, 0, 5, 6, 7, 4}, {1, 5, 6, 2, 0, 4, 7, 3}, {2, 1, 5, 6, 3, 0, 4, 7}, {2, 3, 0, 1, 6, 7, 4, 5}, + {2, 6, 7, 3, 1, 5, 4, 0}, {3, 0, 1, 2, 7, 4, 5, 6}, {3, 2, 6, 7, 0, 1, 5, 4}, {3, 7, 4, 0, 2, 6, 5, 1}, + {4, 0, 3, 7, 5, 1, 2, 6}, {4, 5, 1, 0, 7, 6, 2, 3}, {4, 7, 6, 5, 0, 3, 2, 1}, {5, 1, 0, 4, 6, 2, 3, 7}, + {5, 4, 7, 6, 1, 0, 3, 2}, {5, 6, 2, 1, 4, 7, 3, 0}, {6, 2, 1, 5, 7, 3, 0, 4}, {6, 5, 4, 7, 2, 1, 0, 3}, + {6, 7, 3, 2, 5, 4, 0, 1}, {7, 3, 2, 6, 4, 0, 1, 5}, {7, 4, 0, 3, 6, 5, 1, 2}, {7, 6, 5, 4, 3, 2, 1, 0}}; + + /* + z + ^ + | + 8----------11 + |\ |\ + | \ | \ + | \ | \ + | 9------+---10 + | | | | + 4---+------7 | + |\ | |\ | + | \ | | \ | + | \| | \| + | 5------+---6 + | | | | + 0---+------3---|--> y + \ | \ | + \ | \ | + \| \| + 1----------2 + \ + v + x + */ + double vertices[num_vertices][dim] = {{0, 0, 0}, {1, 0, 0}, {1, 1, 0}, {0, 1, 0}, {0, 0, 1}, {1, 0, 1}, + {1, 1, 1}, {0, 1, 1}, {0, 0, 2}, {1, 0, 2}, {1, 1, 2}, {0, 1, 2}}; + + int elements[num_elements][8] = {{0, 1, 2, 3, 4, 5, 6, 7}, {4, 5, 6, 7, 8, 9, 10, 11}}; + + mfem::Mesh output(dim, num_vertices, num_elements); + + for (auto vertex : vertices) { + output.AddVertex(vertex); + } + + // the first element is always fixed + output.AddHex(elements[0]); + + // but the second element is permuted to the specified orientation + auto permuted_element = apply_permutation(elements[1], positive_permutations[i]); + output.AddHex(permuted_element.data()); + + output.FinalizeMesh(); + + return output; + } + + return {}; +} + +std::ostream& operator<<(std::ostream& out, axom::Array arr) +{ + for (int i = 0; i < arr.shape()[0]; i++) { + for (int j = 0; j < arr.shape()[1]; j++) { + out << arr[i][j].index() << " "; + } + out << std::endl; + } + return out; +} + +template +double scalar_func_mfem(const mfem::Vector& x, double /*t*/) +{ + if constexpr (dim == 2) { + return x[0] + 10 * x[1]; + } else { + return x[0] + 10 * x[1] + 100 * x[2]; + } +} + +template +double scalar_func(const tensor x) +{ + if constexpr (dim == 2) { + return x[0] + 10 * x[1]; + } else { + return x[0] + 10 * x[1] + 100 * x[2]; + } +} + +template +void vector_func_mfem(const mfem::Vector& x, double /*t*/, mfem::Vector& output) +{ + if constexpr (dim == 2) { + output[0] = +x[1]; + output[1] = -x[0]; + } else { + output[0] = +x[1]; + output[1] = -x[0] + x[2]; + output[2] = -x[1]; + } +} + +template +void parametrized_test(int permutation) +{ + constexpr mfem::Geometry::Type face_geom = face_type(geom); + constexpr int dim = dimension_of(geom); + + mfem::Mesh mesh = generate_permuted_mesh(geom, permutation); + + Domain interior_faces = InteriorFaces(mesh); + + // each one of these meshes should have two elements + // and a single "face" that separates them + EXPECT_EQ(mesh.GetNE(), 2); + + std::vector face_ids; + + if (face_geom == mfem::Geometry::SEGMENT) { + EXPECT_EQ(interior_faces.edge_ids_.size(), 1); + EXPECT_EQ(interior_faces.mfem_edge_ids_.size(), 1); + face_ids = interior_faces.mfem_edge_ids_; + } + + if (face_geom == mfem::Geometry::TRIANGLE) { + EXPECT_EQ(interior_faces.tri_ids_.size(), 1); + EXPECT_EQ(interior_faces.mfem_tri_ids_.size(), 1); + face_ids = interior_faces.mfem_tri_ids_; + } + + if (face_geom == mfem::Geometry::SQUARE) { + EXPECT_EQ(interior_faces.quad_ids_.size(), 1); + EXPECT_EQ(interior_faces.mfem_quad_ids_.size(), 1); + face_ids = interior_faces.mfem_quad_ids_; + } + + auto H1_fec = std::make_unique(polynomial_order, dim); + auto Hcurl_fec = std::make_unique(polynomial_order, dim); + auto L2_fec = std::make_unique(polynomial_order, dim, mfem::BasisType::GaussLobatto); + + auto H1_fes = std::make_unique(&mesh, H1_fec.get()); + auto Hcurl_fes = std::make_unique(&mesh, Hcurl_fec.get()); + auto L2_fes = std::make_unique(&mesh, L2_fec.get()); + + mfem::GridFunction H1_gf(H1_fes.get()); + mfem::GridFunction Hcurl_gf(Hcurl_fes.get()); + mfem::GridFunction L2_gf(L2_fes.get()); + + mfem::FunctionCoefficient sfunc(scalar_func_mfem); + mfem::VectorFunctionCoefficient vfunc(dim, vector_func_mfem); + + H1_gf.ProjectCoefficient(sfunc); + Hcurl_gf.ProjectCoefficient(vfunc); + L2_gf.ProjectCoefficient(sfunc); + + auto H1_dofs = GetFaceDofs(H1_fes.get(), face_geom, FaceType::INTERIOR); + auto Hcurl_dofs = GetFaceDofs(Hcurl_fes.get(), face_geom, FaceType::INTERIOR); + + auto L2_dofs = GetFaceDofs(L2_fes.get(), face_geom, face_ids); + + // verify that the dofs for the L2 faces are aligned properly + int dofs_per_side = L2_dofs.shape()[1] / 2; + for (int i = 0; i < dofs_per_side; i++) { + int id1 = int(L2_dofs(0, i).index()); + int id2 = int(L2_dofs(0, i + dofs_per_side).index()); + // std::cout << id1 << " " << id2 << " " << L2_gf[id1] << " " << L2_gf[id2] << std::endl; + EXPECT_NEAR(L2_gf[id1], L2_gf[id2], 5.0e-14); + } + + //////////////////////////////////////////////////////////////////////////////// + + using space = L2; + + auto pmesh = mesh::refineAndDistribute(std::move(mesh), 0); + Domain pinterior_faces = InteriorFaces(*pmesh); + + auto L2_pfes = mfem::ParFiniteElementSpace(pmesh.get(), L2_fec.get()); + + Functional r({&L2_pfes}); + + r.AddInteriorFaceIntegral( + Dimension{}, DependsOn<0>{}, + [](double /*t*/, auto X, auto rho) { + // std::cout << get<0>(X) << std::endl; + double expected = scalar_func(get<0>(X)); + auto [rho1, rho2] = rho; + EXPECT_NEAR(expected, get_value(rho1), 5.0e-14); + EXPECT_NEAR(expected, get_value(rho2), 5.0e-14); + auto difference = rho1 - rho2; + return difference * difference; + }, + pinterior_faces); + + mfem::Vector U(L2_pfes.TrueVSize()); + mfem::ParGridFunction U_pgf(&L2_pfes); + U_pgf.ProjectCoefficient(sfunc); + U_pgf.GetTrueDofs(U); + + double t = 0.0; + double output = r(t, U); + + EXPECT_NEAR(output, 0.0, 5.0e-14); + + // TODO: check that the actual values match their respective functions + // evaluated directly at the nodes (for H1, Hcurl, and L2 gfs) +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +TEST(DomainInterior, TriMesh10) { parametrized_test(0); } +TEST(DomainInterior, TriMesh11) { parametrized_test(1); } +TEST(DomainInterior, TriMesh12) { parametrized_test(2); } + +TEST(DomainInterior, TriMesh20) { parametrized_test(0); } +TEST(DomainInterior, TriMesh21) { parametrized_test(1); } +TEST(DomainInterior, TriMesh22) { parametrized_test(2); } + +TEST(DomainInterior, TriMesh30) { parametrized_test(0); } +TEST(DomainInterior, TriMesh31) { parametrized_test(1); } +TEST(DomainInterior, TriMesh32) { parametrized_test(2); } + +//////////////////////////////////////////////////////////////////////////////// + +TEST(DomainInterior, QuadMesh10) { parametrized_test(0); } +TEST(DomainInterior, QuadMesh11) { parametrized_test(1); } +TEST(DomainInterior, QuadMesh12) { parametrized_test(2); } +TEST(DomainInterior, QuadMesh13) { parametrized_test(3); } + +TEST(DomainInterior, QuadMesh20) { parametrized_test(0); } +TEST(DomainInterior, QuadMesh21) { parametrized_test(1); } +TEST(DomainInterior, QuadMesh22) { parametrized_test(2); } +TEST(DomainInterior, QuadMesh23) { parametrized_test(3); } + +TEST(DomainInterior, QuadMesh30) { parametrized_test(0); } +TEST(DomainInterior, QuadMesh31) { parametrized_test(1); } +TEST(DomainInterior, QuadMesh32) { parametrized_test(2); } +TEST(DomainInterior, QuadMesh33) { parametrized_test(3); } + +//////////////////////////////////////////////////////////////////////////////// + +TEST(DomainInterior, TetMesh100) { parametrized_test(0); } +TEST(DomainInterior, TetMesh101) { parametrized_test(1); } +TEST(DomainInterior, TetMesh102) { parametrized_test(2); } +TEST(DomainInterior, TetMesh103) { parametrized_test(3); } +TEST(DomainInterior, TetMesh104) { parametrized_test(4); } +TEST(DomainInterior, TetMesh105) { parametrized_test(5); } +TEST(DomainInterior, TetMesh106) { parametrized_test(6); } +TEST(DomainInterior, TetMesh107) { parametrized_test(7); } +TEST(DomainInterior, TetMesh108) { parametrized_test(8); } +TEST(DomainInterior, TetMesh109) { parametrized_test(9); } +TEST(DomainInterior, TetMesh110) { parametrized_test(10); } +TEST(DomainInterior, TetMesh111) { parametrized_test(11); } + +TEST(DomainInterior, TetMesh200) { parametrized_test(0); } +TEST(DomainInterior, TetMesh201) { parametrized_test(1); } +TEST(DomainInterior, TetMesh202) { parametrized_test(2); } +TEST(DomainInterior, TetMesh203) { parametrized_test(3); } +TEST(DomainInterior, TetMesh204) { parametrized_test(4); } +TEST(DomainInterior, TetMesh205) { parametrized_test(5); } +TEST(DomainInterior, TetMesh206) { parametrized_test(6); } +TEST(DomainInterior, TetMesh207) { parametrized_test(7); } +TEST(DomainInterior, TetMesh208) { parametrized_test(8); } +TEST(DomainInterior, TetMesh209) { parametrized_test(9); } +TEST(DomainInterior, TetMesh210) { parametrized_test(10); } +TEST(DomainInterior, TetMesh211) { parametrized_test(11); } + +TEST(DomainInterior, TetMesh300) { parametrized_test(0); } +TEST(DomainInterior, TetMesh301) { parametrized_test(1); } +TEST(DomainInterior, TetMesh302) { parametrized_test(2); } +TEST(DomainInterior, TetMesh303) { parametrized_test(3); } +TEST(DomainInterior, TetMesh304) { parametrized_test(4); } +TEST(DomainInterior, TetMesh305) { parametrized_test(5); } +TEST(DomainInterior, TetMesh306) { parametrized_test(6); } +TEST(DomainInterior, TetMesh307) { parametrized_test(7); } +TEST(DomainInterior, TetMesh308) { parametrized_test(8); } +TEST(DomainInterior, TetMesh309) { parametrized_test(9); } +TEST(DomainInterior, TetMesh310) { parametrized_test(10); } +TEST(DomainInterior, TetMesh311) { parametrized_test(11); } + +//////////////////////////////////////////////////////////////////////////////// + +TEST(DomainInterior, HexMesh100) { parametrized_test(0); } +TEST(DomainInterior, HexMesh101) { parametrized_test(1); } +TEST(DomainInterior, HexMesh102) { parametrized_test(2); } +TEST(DomainInterior, HexMesh103) { parametrized_test(3); } +TEST(DomainInterior, HexMesh104) { parametrized_test(4); } +TEST(DomainInterior, HexMesh105) { parametrized_test(5); } +TEST(DomainInterior, HexMesh106) { parametrized_test(6); } +TEST(DomainInterior, HexMesh107) { parametrized_test(7); } +TEST(DomainInterior, HexMesh108) { parametrized_test(8); } +TEST(DomainInterior, HexMesh109) { parametrized_test(9); } +TEST(DomainInterior, HexMesh110) { parametrized_test(10); } +TEST(DomainInterior, HexMesh111) { parametrized_test(11); } +TEST(DomainInterior, HexMesh112) { parametrized_test(12); } +TEST(DomainInterior, HexMesh113) { parametrized_test(13); } +TEST(DomainInterior, HexMesh114) { parametrized_test(14); } +TEST(DomainInterior, HexMesh115) { parametrized_test(15); } +TEST(DomainInterior, HexMesh116) { parametrized_test(16); } +TEST(DomainInterior, HexMesh117) { parametrized_test(17); } +TEST(DomainInterior, HexMesh118) { parametrized_test(18); } +TEST(DomainInterior, HexMesh119) { parametrized_test(19); } +TEST(DomainInterior, HexMesh120) { parametrized_test(20); } +TEST(DomainInterior, HexMesh121) { parametrized_test(21); } +TEST(DomainInterior, HexMesh122) { parametrized_test(22); } +TEST(DomainInterior, HexMesh123) { parametrized_test(23); } + +TEST(DomainInterior, HexMesh200) { parametrized_test(0); } +TEST(DomainInterior, HexMesh201) { parametrized_test(1); } +TEST(DomainInterior, HexMesh202) { parametrized_test(2); } +TEST(DomainInterior, HexMesh203) { parametrized_test(3); } +TEST(DomainInterior, HexMesh204) { parametrized_test(4); } +TEST(DomainInterior, HexMesh205) { parametrized_test(5); } +TEST(DomainInterior, HexMesh206) { parametrized_test(6); } +TEST(DomainInterior, HexMesh207) { parametrized_test(7); } +TEST(DomainInterior, HexMesh208) { parametrized_test(8); } +TEST(DomainInterior, HexMesh209) { parametrized_test(9); } +TEST(DomainInterior, HexMesh210) { parametrized_test(10); } +TEST(DomainInterior, HexMesh211) { parametrized_test(11); } +TEST(DomainInterior, HexMesh212) { parametrized_test(12); } +TEST(DomainInterior, HexMesh213) { parametrized_test(13); } +TEST(DomainInterior, HexMesh214) { parametrized_test(14); } +TEST(DomainInterior, HexMesh215) { parametrized_test(15); } +TEST(DomainInterior, HexMesh216) { parametrized_test(16); } +TEST(DomainInterior, HexMesh217) { parametrized_test(17); } +TEST(DomainInterior, HexMesh218) { parametrized_test(18); } +TEST(DomainInterior, HexMesh219) { parametrized_test(19); } +TEST(DomainInterior, HexMesh220) { parametrized_test(20); } +TEST(DomainInterior, HexMesh221) { parametrized_test(21); } +TEST(DomainInterior, HexMesh222) { parametrized_test(22); } +TEST(DomainInterior, HexMesh223) { parametrized_test(23); } + +TEST(DomainInterior, HexMesh300) { parametrized_test(0); } +TEST(DomainInterior, HexMesh301) { parametrized_test(1); } +TEST(DomainInterior, HexMesh302) { parametrized_test(2); } +TEST(DomainInterior, HexMesh303) { parametrized_test(3); } +TEST(DomainInterior, HexMesh304) { parametrized_test(4); } +TEST(DomainInterior, HexMesh305) { parametrized_test(5); } +TEST(DomainInterior, HexMesh306) { parametrized_test(6); } +TEST(DomainInterior, HexMesh307) { parametrized_test(7); } +TEST(DomainInterior, HexMesh308) { parametrized_test(8); } +TEST(DomainInterior, HexMesh309) { parametrized_test(9); } +TEST(DomainInterior, HexMesh310) { parametrized_test(10); } +TEST(DomainInterior, HexMesh311) { parametrized_test(11); } +TEST(DomainInterior, HexMesh312) { parametrized_test(12); } +TEST(DomainInterior, HexMesh313) { parametrized_test(13); } +TEST(DomainInterior, HexMesh314) { parametrized_test(14); } +TEST(DomainInterior, HexMesh315) { parametrized_test(15); } +TEST(DomainInterior, HexMesh316) { parametrized_test(16); } +TEST(DomainInterior, HexMesh317) { parametrized_test(17); } +TEST(DomainInterior, HexMesh318) { parametrized_test(18); } +TEST(DomainInterior, HexMesh319) { parametrized_test(19); } +TEST(DomainInterior, HexMesh320) { parametrized_test(20); } +TEST(DomainInterior, HexMesh321) { parametrized_test(21); } +TEST(DomainInterior, HexMesh322) { parametrized_test(22); } +TEST(DomainInterior, HexMesh323) { parametrized_test(23); } + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +int main(int argc, char* argv[]) +{ + int num_procs, myid; + + ::testing::InitGoogleTest(&argc, argv); + + MPI_Init(&argc, &argv); + MPI_Comm_size(MPI_COMM_WORLD, &num_procs); + MPI_Comm_rank(MPI_COMM_WORLD, &myid); + + axom::slic::SimpleLogger logger; + + int result = RUN_ALL_TESTS(); + + MPI_Finalize(); + + return result; +} diff --git a/src/serac/numerics/functional/tests/domain_tests.cpp b/src/serac/numerics/functional/tests/domain_tests.cpp index 7893107a9..40cba25aa 100644 --- a/src/serac/numerics/functional/tests/domain_tests.cpp +++ b/src/serac/numerics/functional/tests/domain_tests.cpp @@ -21,16 +21,6 @@ mfem::Mesh import_mesh(std::string meshfile) return mesh; } -template -tensor average(std::vector >& positions) -{ - tensor total{}; - for (auto x : positions) { - total += x; - } - return total / double(positions.size()); -} - TEST(domain, of_edges) { { diff --git a/src/serac/numerics/functional/tests/element_restriction_tests.cpp b/src/serac/numerics/functional/tests/element_restriction_tests.cpp new file mode 100644 index 000000000..ec543cdd2 --- /dev/null +++ b/src/serac/numerics/functional/tests/element_restriction_tests.cpp @@ -0,0 +1,363 @@ +#include + +#include "serac/numerics/functional/domain.hpp" +#include "serac/numerics/functional/element_restriction.hpp" + +using namespace serac; + +std::ostream& operator<<(std::ostream& out, axom::Array arr) +{ + for (int i = 0; i < arr.shape()[0]; i++) { + for (int j = 0; j < arr.shape()[1]; j++) { + out << arr[i][j].index() << " "; + } + out << std::endl; + } + return out; +} + +TEST(patch_test_meshes, triangle_domains) +{ + int p = 2; + int dim = 2; + mfem::Mesh mesh(SERAC_REPO_DIR "/data/meshes/patch2D_tris.mesh"); + + auto H1_fec = std::make_unique(p, dim); + auto H1_fes = std::make_unique(&mesh, H1_fec.get()); + + auto Hcurl_fec = std::make_unique(p, dim); + auto Hcurl_fes = std::make_unique(&mesh, Hcurl_fec.get()); + + auto L2_fec = std::make_unique(p, dim, mfem::BasisType::GaussLobatto); + auto L2_fes = std::make_unique(&mesh, L2_fec.get()); + + Domain whole = EntireDomain(mesh); + EXPECT_EQ(whole.mfem_edge_ids_.size(), 0); + EXPECT_EQ(whole.mfem_tri_ids_.size(), 4); + EXPECT_EQ(whole.mfem_quad_ids_.size(), 0); + EXPECT_EQ(whole.mfem_tet_ids_.size(), 0); + EXPECT_EQ(whole.mfem_hex_ids_.size(), 0); + + { + BlockElementRestriction H1_BER(H1_fes.get(), whole); + EXPECT_EQ(H1_BER.ESize(), 4 * 6); + + BlockElementRestriction Hcurl_BER(Hcurl_fes.get(), whole); + EXPECT_EQ(Hcurl_BER.ESize(), 4 * 8); + + BlockElementRestriction L2_BER(L2_fes.get(), whole); + EXPECT_EQ(L2_BER.ESize(), 4 * 6); + } + + Domain boundary = EntireBoundary(mesh); + EXPECT_EQ(boundary.mfem_edge_ids_.size(), 4); + EXPECT_EQ(boundary.mfem_tri_ids_.size(), 0); + EXPECT_EQ(boundary.mfem_quad_ids_.size(), 0); + EXPECT_EQ(boundary.mfem_tet_ids_.size(), 0); + EXPECT_EQ(boundary.mfem_hex_ids_.size(), 0); + + { + BlockElementRestriction H1_BER(H1_fes.get(), boundary); + EXPECT_EQ(H1_BER.ESize(), 4 * 3); + + BlockElementRestriction Hcurl_BER(Hcurl_fes.get(), boundary); + EXPECT_EQ(Hcurl_BER.ESize(), 4 * 2); + + BlockElementRestriction L2_BER(L2_fes.get(), boundary); + EXPECT_EQ(L2_BER.ESize(), 4 * 3); + } + + Domain interior = InteriorFaces(mesh); + EXPECT_EQ(interior.mfem_edge_ids_.size(), 4); + EXPECT_EQ(interior.mfem_tri_ids_.size(), 0); + EXPECT_EQ(interior.mfem_quad_ids_.size(), 0); + EXPECT_EQ(interior.mfem_tet_ids_.size(), 0); + EXPECT_EQ(interior.mfem_hex_ids_.size(), 0); + + { + BlockElementRestriction H1_BER(H1_fes.get(), interior); + EXPECT_EQ(H1_BER.ESize(), 4 * 3); + + BlockElementRestriction Hcurl_BER(Hcurl_fes.get(), interior); + EXPECT_EQ(Hcurl_BER.ESize(), 4 * 2); + + BlockElementRestriction L2_BER(L2_fes.get(), interior); + EXPECT_EQ(L2_BER.ESize(), 4 * (3 * 2)); + } +} + +TEST(patch_test_meshes, quadrilateral_domains) +{ + int p = 2; + int dim = 2; + mfem::Mesh mesh(SERAC_REPO_DIR "/data/meshes/patch2D_quads.mesh"); + + auto H1_fec = std::make_unique(p, dim); + auto Hcurl_fec = std::make_unique(p, dim); + auto L2_fec = std::make_unique(p, dim, mfem::BasisType::GaussLobatto); + + auto H1_fes = std::make_unique(&mesh, H1_fec.get()); + auto Hcurl_fes = std::make_unique(&mesh, Hcurl_fec.get()); + auto L2_fes = std::make_unique(&mesh, L2_fec.get()); + + Domain whole = EntireDomain(mesh); + EXPECT_EQ(whole.mfem_edge_ids_.size(), 0); + EXPECT_EQ(whole.mfem_tri_ids_.size(), 0); + EXPECT_EQ(whole.mfem_quad_ids_.size(), 5); + EXPECT_EQ(whole.mfem_tet_ids_.size(), 0); + EXPECT_EQ(whole.mfem_hex_ids_.size(), 0); + + { + BlockElementRestriction H1_BER(H1_fes.get(), whole); + EXPECT_EQ(H1_BER.ESize(), 5 * 9); + + BlockElementRestriction Hcurl_BER(Hcurl_fes.get(), whole); + EXPECT_EQ(Hcurl_BER.ESize(), 5 * 12); + + BlockElementRestriction L2_BER(L2_fes.get(), whole); + EXPECT_EQ(L2_BER.ESize(), 5 * 9); + } + + Domain boundary = EntireBoundary(mesh); + EXPECT_EQ(boundary.mfem_edge_ids_.size(), 4); + EXPECT_EQ(boundary.mfem_tri_ids_.size(), 0); + EXPECT_EQ(boundary.mfem_quad_ids_.size(), 0); + EXPECT_EQ(boundary.mfem_tet_ids_.size(), 0); + EXPECT_EQ(boundary.mfem_hex_ids_.size(), 0); + + { + BlockElementRestriction H1_BER(H1_fes.get(), boundary); + EXPECT_EQ(H1_BER.ESize(), 4 * 3); + + BlockElementRestriction Hcurl_BER(Hcurl_fes.get(), boundary); + EXPECT_EQ(Hcurl_BER.ESize(), 4 * 2); + + BlockElementRestriction L2_BER(L2_fes.get(), boundary); + EXPECT_EQ(L2_BER.ESize(), 4 * 3); + } + + Domain interior = InteriorFaces(mesh); + EXPECT_EQ(interior.mfem_edge_ids_.size(), 8); + EXPECT_EQ(interior.mfem_tri_ids_.size(), 0); + EXPECT_EQ(interior.mfem_quad_ids_.size(), 0); + EXPECT_EQ(interior.mfem_tet_ids_.size(), 0); + EXPECT_EQ(interior.mfem_hex_ids_.size(), 0); + + { + BlockElementRestriction H1_BER(H1_fes.get(), interior); + EXPECT_EQ(H1_BER.ESize(), 8 * 3); + + BlockElementRestriction Hcurl_BER(Hcurl_fes.get(), interior); + EXPECT_EQ(Hcurl_BER.ESize(), 8 * 2); + + BlockElementRestriction L2_BER(L2_fes.get(), interior); + EXPECT_EQ(L2_BER.ESize(), 8 * (3 * 2)); + } +} + +TEST(patch_test_meshes, triangle_and_quadrilateral_domains) +{ + int p = 2; + int dim = 2; + mfem::Mesh mesh(SERAC_REPO_DIR "/data/meshes/patch2D_tris_and_quads.mesh"); + + auto H1_fec = std::make_unique(p, dim); + auto Hcurl_fec = std::make_unique(p, dim); + auto L2_fec = std::make_unique(p, dim, mfem::BasisType::GaussLobatto); + + auto H1_fes = std::make_unique(&mesh, H1_fec.get()); + auto Hcurl_fes = std::make_unique(&mesh, Hcurl_fec.get()); + auto L2_fes = std::make_unique(&mesh, L2_fec.get()); + + Domain whole = EntireDomain(mesh); + EXPECT_EQ(whole.mfem_edge_ids_.size(), 0); + EXPECT_EQ(whole.mfem_tri_ids_.size(), 2); + EXPECT_EQ(whole.mfem_quad_ids_.size(), 4); + EXPECT_EQ(whole.mfem_tet_ids_.size(), 0); + EXPECT_EQ(whole.mfem_hex_ids_.size(), 0); + + { + BlockElementRestriction H1_BER(H1_fes.get(), whole); + EXPECT_EQ(H1_BER.ESize(), 2 * 6 + 4 * 9); + + BlockElementRestriction Hcurl_BER(Hcurl_fes.get(), whole); + EXPECT_EQ(Hcurl_BER.ESize(), 2 * 8 + 4 * 12); + + BlockElementRestriction L2_BER(L2_fes.get(), whole); + EXPECT_EQ(L2_BER.ESize(), 2 * 6 + 4 * 9); + } + + Domain boundary = EntireBoundary(mesh); + EXPECT_EQ(boundary.mfem_edge_ids_.size(), 4); + EXPECT_EQ(boundary.mfem_tri_ids_.size(), 0); + EXPECT_EQ(boundary.mfem_quad_ids_.size(), 0); + EXPECT_EQ(boundary.mfem_tet_ids_.size(), 0); + EXPECT_EQ(boundary.mfem_hex_ids_.size(), 0); + + { + BlockElementRestriction H1_BER(H1_fes.get(), boundary); + EXPECT_EQ(H1_BER.ESize(), 4 * 3); + + BlockElementRestriction Hcurl_BER(Hcurl_fes.get(), boundary); + EXPECT_EQ(Hcurl_BER.ESize(), 4 * 2); + + BlockElementRestriction L2_BER(L2_fes.get(), boundary); + EXPECT_EQ(L2_BER.ESize(), 4 * 3); + } + + Domain interior = InteriorFaces(mesh); + EXPECT_EQ(interior.mfem_edge_ids_.size(), 9); + EXPECT_EQ(interior.mfem_tri_ids_.size(), 0); + EXPECT_EQ(interior.mfem_quad_ids_.size(), 0); + EXPECT_EQ(interior.mfem_tet_ids_.size(), 0); + EXPECT_EQ(interior.mfem_hex_ids_.size(), 0); + + { + BlockElementRestriction H1_BER(H1_fes.get(), interior); + EXPECT_EQ(H1_BER.ESize(), 9 * 3); + + BlockElementRestriction Hcurl_BER(Hcurl_fes.get(), interior); + EXPECT_EQ(Hcurl_BER.ESize(), 9 * 2); + + BlockElementRestriction L2_BER(L2_fes.get(), interior); + EXPECT_EQ(L2_BER.ESize(), 9 * (3 * 2)); + } +} + +TEST(patch_test_meshes, tetrahedron_domains) +{ + int p = 2; + int dim = 3; + mfem::Mesh mesh(SERAC_REPO_DIR "/data/meshes/patch3D_tets.mesh"); + + auto H1_fec = std::make_unique(p, dim); + auto Hcurl_fec = std::make_unique(p, dim); + auto L2_fec = std::make_unique(p, dim, mfem::BasisType::GaussLobatto); + + auto H1_fes = std::make_unique(&mesh, H1_fec.get()); + auto Hcurl_fes = std::make_unique(&mesh, Hcurl_fec.get()); + auto L2_fes = std::make_unique(&mesh, L2_fec.get()); + + Domain whole = EntireDomain(mesh); + EXPECT_EQ(whole.mfem_edge_ids_.size(), 0); + EXPECT_EQ(whole.mfem_tri_ids_.size(), 0); + EXPECT_EQ(whole.mfem_quad_ids_.size(), 0); + EXPECT_EQ(whole.mfem_tet_ids_.size(), 12); + EXPECT_EQ(whole.mfem_hex_ids_.size(), 0); + + { + BlockElementRestriction H1_BER(H1_fes.get(), whole); + EXPECT_EQ(H1_BER.ESize(), 12 * 10); + + BlockElementRestriction Hcurl_BER(Hcurl_fes.get(), whole); + EXPECT_EQ(Hcurl_BER.ESize(), 12 * 20); + + BlockElementRestriction L2_BER(L2_fes.get(), whole); + EXPECT_EQ(L2_BER.ESize(), 12 * 10); + } + + Domain boundary = EntireBoundary(mesh); + EXPECT_EQ(boundary.mfem_edge_ids_.size(), 0); + EXPECT_EQ(boundary.mfem_tri_ids_.size(), 12); + EXPECT_EQ(boundary.mfem_quad_ids_.size(), 0); + EXPECT_EQ(boundary.mfem_tet_ids_.size(), 0); + EXPECT_EQ(boundary.mfem_hex_ids_.size(), 0); + + { + BlockElementRestriction H1_BER(H1_fes.get(), boundary); + EXPECT_EQ(H1_BER.ESize(), 12 * 6); + + BlockElementRestriction Hcurl_BER(Hcurl_fes.get(), boundary); + EXPECT_EQ(Hcurl_BER.ESize(), 12 * 8); + + BlockElementRestriction L2_BER(L2_fes.get(), boundary); + EXPECT_EQ(L2_BER.ESize(), 12 * 6); + } + + Domain interior = InteriorFaces(mesh); + EXPECT_EQ(interior.mfem_edge_ids_.size(), 0); + EXPECT_EQ(interior.mfem_tri_ids_.size(), 18); + EXPECT_EQ(interior.mfem_quad_ids_.size(), 0); + EXPECT_EQ(interior.mfem_tet_ids_.size(), 0); + EXPECT_EQ(interior.mfem_hex_ids_.size(), 0); + + { + BlockElementRestriction H1_BER(H1_fes.get(), interior); + EXPECT_EQ(H1_BER.ESize(), 18 * 6); + + BlockElementRestriction Hcurl_BER(Hcurl_fes.get(), interior); + EXPECT_EQ(Hcurl_BER.ESize(), 18 * 8); + + BlockElementRestriction L2_BER(L2_fes.get(), interior); + EXPECT_EQ(L2_BER.ESize(), 18 * (6 * 2)); + } +} + +TEST(patch_test_meshes, hexahedron_domains) +{ + int p = 2; + int dim = 3; + mfem::Mesh mesh(SERAC_REPO_DIR "/data/meshes/patch3D_hexes.mesh"); + + auto H1_fec = std::make_unique(p, dim); + auto Hcurl_fec = std::make_unique(p, dim); + auto L2_fec = std::make_unique(p, dim, mfem::BasisType::GaussLobatto); + + auto H1_fes = std::make_unique(&mesh, H1_fec.get()); + auto Hcurl_fes = std::make_unique(&mesh, Hcurl_fec.get()); + auto L2_fes = std::make_unique(&mesh, L2_fec.get()); + + Domain whole = EntireDomain(mesh); + EXPECT_EQ(whole.mfem_edge_ids_.size(), 0); + EXPECT_EQ(whole.mfem_tri_ids_.size(), 0); + EXPECT_EQ(whole.mfem_quad_ids_.size(), 0); + EXPECT_EQ(whole.mfem_tet_ids_.size(), 0); + EXPECT_EQ(whole.mfem_hex_ids_.size(), 7); + + { + BlockElementRestriction H1_BER(H1_fes.get(), whole); + EXPECT_EQ(H1_BER.ESize(), 7 * 27); + + BlockElementRestriction Hcurl_BER(Hcurl_fes.get(), whole); + EXPECT_EQ(Hcurl_BER.ESize(), 7 * 54); + + BlockElementRestriction L2_BER(L2_fes.get(), whole); + EXPECT_EQ(L2_BER.ESize(), 7 * 27); + } + + Domain boundary = EntireBoundary(mesh); + EXPECT_EQ(boundary.mfem_edge_ids_.size(), 0); + EXPECT_EQ(boundary.mfem_tri_ids_.size(), 0); + EXPECT_EQ(boundary.mfem_quad_ids_.size(), 6); + EXPECT_EQ(boundary.mfem_tet_ids_.size(), 0); + EXPECT_EQ(boundary.mfem_hex_ids_.size(), 0); + + { + BlockElementRestriction H1_BER(H1_fes.get(), boundary); + EXPECT_EQ(H1_BER.ESize(), 6 * 9); + + BlockElementRestriction Hcurl_BER(Hcurl_fes.get(), boundary); + EXPECT_EQ(Hcurl_BER.ESize(), 6 * 12); + + BlockElementRestriction L2_BER(L2_fes.get(), boundary); + EXPECT_EQ(L2_BER.ESize(), 6 * 9); + } + + Domain interior = InteriorFaces(mesh); + EXPECT_EQ(interior.mfem_edge_ids_.size(), 0); + EXPECT_EQ(interior.mfem_tri_ids_.size(), 0); + EXPECT_EQ(interior.mfem_quad_ids_.size(), 18); + EXPECT_EQ(interior.mfem_tet_ids_.size(), 0); + EXPECT_EQ(interior.mfem_hex_ids_.size(), 0); + + { + BlockElementRestriction H1_BER(H1_fes.get(), interior); + EXPECT_EQ(H1_BER.ESize(), 18 * 9); + + BlockElementRestriction Hcurl_BER(Hcurl_fes.get(), interior); + EXPECT_EQ(Hcurl_BER.ESize(), 18 * 12); + + BlockElementRestriction L2_BER(L2_fes.get(), interior); + EXPECT_EQ(L2_BER.ESize(), 18 * (9 * 2)); + } +} diff --git a/src/serac/numerics/functional/tests/functional_basic_dg.cpp b/src/serac/numerics/functional/tests/functional_basic_dg.cpp new file mode 100644 index 000000000..6f5c205ae --- /dev/null +++ b/src/serac/numerics/functional/tests/functional_basic_dg.cpp @@ -0,0 +1,222 @@ +// Copyright (c) 2019-2024, Lawrence Livermore National Security, LLC and +// other Serac Project Developers. See the top-level LICENSE file for +// details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include +#include + +#include "mfem.hpp" + +#include + +#include "axom/slic/core/SimpleLogger.hpp" +#include "serac/infrastructure/input.hpp" +#include "serac/serac_config.hpp" +#include "serac/mesh/mesh_utils_base.hpp" +#include "serac/numerics/stdfunction_operator.hpp" +#include "serac/numerics/functional/functional.hpp" +#include "serac/numerics/functional/tensor.hpp" + +#include "serac/numerics/functional/tests/check_gradient.hpp" + +using namespace serac; +using namespace serac::profiling; + +template +void L2_test(std::string meshfile) +{ + using test_space = L2; + using trial_space = L2; + + // int k = 0; + // while (k == 0); + + auto mesh = mesh::refineAndDistribute(buildMeshFromFile(meshfile), 0); + + auto fec = mfem::L2_FECollection(p, dim, mfem::BasisType::GaussLobatto); + mfem::ParFiniteElementSpace fespace(mesh.get(), &fec, dim, serac::ordering); + + mfem::Vector U(fespace.TrueVSize()); + U.Randomize(); + + // Construct the new functional object using the specified test and trial spaces + Functional residual(&fespace, {&fespace}); + + constexpr int DERIVATIVE = 1; + + Domain interior_faces = InteriorFaces(*mesh); + + residual.AddInteriorFaceIntegral( + Dimension{}, DependsOn<0>{}, + [=](double /*t*/, auto X, auto velocity) { + // compute the surface normal + auto dX_dxi = get(X); + auto n = normalize(cross(dX_dxi)); + + // extract the velocity values from each side of the interface + // note: the orientation convention is such that the normal + // computed as above will point from from side 1->2 + auto [u_1, u_2] = velocity; + + auto a = dot(u_2 - u_1, n); + + auto f_1 = u_1 * a; + auto f_2 = u_2 * a; + return serac::tuple{f_1, f_2}; + }, + interior_faces); + + double t = 0.0; + + auto value = residual(t, U); + // check_gradient(residual, t, U); +} + +TEST(basic, L2_test_tris_and_quads_linear) { L2_test<2, 1>(SERAC_REPO_DIR "/data/meshes/patch2D_tris_and_quads.mesh"); } +TEST(basic, L2_test_tris_and_quads_quadratic) +{ + L2_test<2, 2>(SERAC_REPO_DIR "/data/meshes/patch2D_tris_and_quads.mesh"); +} + +TEST(basic, L2_test_tets_linear) { L2_test<3, 1>(SERAC_REPO_DIR "/data/meshes/patch3D_tets.mesh"); } +TEST(basic, L2_test_tets_quadratic) { L2_test<3, 2>(SERAC_REPO_DIR "/data/meshes/patch3D_tets.mesh"); } + +TEST(basic, L2_test_hexes_linear) { L2_test<3, 1>(SERAC_REPO_DIR "/data/meshes/patch3D_hexes.mesh"); } +TEST(basic, L2_test_hexes_quadratic) { L2_test<3, 2>(SERAC_REPO_DIR "/data/meshes/patch3D_hexes.mesh"); } + +template +void L2_qoi_test(std::string meshfile) +{ + using trial_space = L2; + + auto mesh = mesh::refineAndDistribute(buildMeshFromFile(meshfile), 1); + + auto fec = mfem::L2_FECollection(p, dim, mfem::BasisType::GaussLobatto); + mfem::ParFiniteElementSpace fespace(mesh.get(), &fec, dim, serac::ordering); + + int seed = 0; + mfem::HypreParVector U = *fespace.NewTrueDofVector(); + U.Randomize(seed); + + // Construct the new functional object using the specified test and trial spaces + Functional qoi({&fespace}); + + constexpr int DERIVATIVE = 1; + + Domain interior_faces = InteriorFaces(*mesh); + + qoi.AddInteriorFaceIntegral( + Dimension{}, DependsOn<0>{}, + [=](double /*t*/, auto X, auto velocity) { + // compute the unit surface normal + auto dX_dxi = get(X); + auto n = normalize(cross(dX_dxi)); + + // extract the velocity values from each side of the interface + // note: the orientation convention is such that the normal + // computed as above will point from from side 1->2 + auto [u_1, u_2] = velocity; + + auto a = dot(u_2 - u_1, n); + + auto f_1 = u_1 * a; + auto f_2 = u_2 * a; + return dot(f_1, f_2); + }, + interior_faces); + + double t = 0.0; + check_gradient(qoi, t, U); +} + +TEST(basic, L2_qoi_test_tri_and_quads_linear) +{ + L2_qoi_test<2, 1>(SERAC_REPO_DIR "/data/meshes/patch2D_tris_and_quads.mesh"); +} +TEST(basic, L2_qoi_test_tri_and_quads_quadratic) +{ + L2_qoi_test<2, 2>(SERAC_REPO_DIR "/data/meshes/patch2D_tris_and_quads.mesh"); +} + +TEST(basic, L2_qoi_test_tets_linear) { L2_qoi_test<3, 1>(SERAC_REPO_DIR "/data/meshes/patch3D_tets.mesh"); } +TEST(basic, L2_qoi_test_tets_quadratic) { L2_qoi_test<3, 2>(SERAC_REPO_DIR "/data/meshes/patch3D_tets.mesh"); } + +TEST(basic, L2_qoi_test_hexes_linear) { L2_qoi_test<3, 1>(SERAC_REPO_DIR "/data/meshes/patch3D_hexes.mesh"); } +TEST(basic, L2_qoi_test_hexes_quadratic) { L2_qoi_test<3, 2>(SERAC_REPO_DIR "/data/meshes/patch3D_hexes.mesh"); } + +template +void L2_scalar_valued_test(std::string meshfile) +{ + using test_space = L2

; + using trial_space_0 = L2

; + using trial_space_1 = H1; + + auto mesh = mesh::refineAndDistribute(buildMeshFromFile(meshfile), 1); + + auto L2fec = mfem::L2_FECollection(p, dim, mfem::BasisType::GaussLobatto); + mfem::ParFiniteElementSpace fespace_0(mesh.get(), &L2fec, 1, serac::ordering); + + auto H1fec = mfem::H1_FECollection(p, dim); + mfem::ParFiniteElementSpace fespace_1(mesh.get(), &H1fec, dim, serac::ordering); + + mfem::Vector U0(fespace_0.TrueVSize()); + U0.Randomize(); + + mfem::Vector U1(fespace_1.TrueVSize()); + U1.Randomize(); + + // Construct the new functional object using the specified test and trial spaces + Functional residual(&fespace_0, {&fespace_0, &fespace_1}); + + constexpr int VALUE = 0; + constexpr int DERIVATIVE = 1; + + Domain interior_faces = InteriorFaces(*mesh); + + residual.AddInteriorFaceIntegral( + Dimension{}, DependsOn<0, 1>{}, + [=](double /*t*/, auto X, auto rho, auto u) { + auto n = normalize(cross(get(X))); + + auto [rho0, rho1] = rho; + auto uTn = dot(get(u), n); + auto s = uTn > 0; + + return serac::tuple{uTn * ((s)*rho0 + (1.0 - s) * rho1), uTn * ((1.0 - s) * rho0 + (s)*rho1)}; + }, + interior_faces); + + double t = 0.0; + check_gradient(residual, t, U0, U1); +} + +TEST(basic, L2_mixed_scalar_test_tris_and_quads_linear) +{ + L2_scalar_valued_test<2, 1>(SERAC_REPO_DIR "/data/meshes/patch2D_tris_and_quads.mesh"); +} + +TEST(basic, L2_mixed_scalar_test_tets_and_hexes_linear) +{ + L2_scalar_valued_test<3, 1>(SERAC_REPO_DIR "/data/meshes/patch3D_tets_and_hexes.mesh"); +} + +int main(int argc, char* argv[]) +{ + int num_procs, myid; + + ::testing::InitGoogleTest(&argc, argv); + + MPI_Init(&argc, &argv); + MPI_Comm_size(MPI_COMM_WORLD, &num_procs); + MPI_Comm_rank(MPI_COMM_WORLD, &myid); + + axom::slic::SimpleLogger logger; + + int result = RUN_ALL_TESTS(); + + MPI_Finalize(); + + return result; +} diff --git a/src/serac/numerics/functional/tests/functional_basic_h1_scalar.cpp b/src/serac/numerics/functional/tests/functional_basic_h1_scalar.cpp index 7a4fc6118..92ba9765c 100644 --- a/src/serac/numerics/functional/tests/functional_basic_h1_scalar.cpp +++ b/src/serac/numerics/functional/tests/functional_basic_h1_scalar.cpp @@ -78,12 +78,14 @@ void thermal_test_impl(std::unique_ptr& mesh) // Construct the new functional object using the known test and trial spaces Functional residual(test_fespace.get(), {trial_fespace.get()}); - residual.AddDomainIntegral(Dimension{}, DependsOn<0>{}, TestThermalModelOne{}, *mesh); + Domain dom = EntireDomain(*mesh); + Domain bdr = EntireBoundary(*mesh); - residual.AddBoundaryIntegral(Dimension{}, DependsOn<0>{}, TestThermalModelTwo{}, *mesh); + residual.AddDomainIntegral(Dimension{}, DependsOn<0>{}, TestThermalModelOne{}, dom); - double t = 0.0; + residual.AddBoundaryIntegral(Dimension{}, DependsOn<0>{}, TestThermalModelTwo{}, bdr); + double t = 0.0; check_gradient(residual, t, U); } diff --git a/src/serac/numerics/functional/tests/functional_basic_h1_vector.cpp b/src/serac/numerics/functional/tests/functional_basic_h1_vector.cpp index a69aefec6..d6191112f 100644 --- a/src/serac/numerics/functional/tests/functional_basic_h1_vector.cpp +++ b/src/serac/numerics/functional/tests/functional_basic_h1_vector.cpp @@ -102,9 +102,10 @@ void weird_mixed_test(std::unique_ptr& mesh) // note: this is not really an elasticity problem, it's testing source and flux // terms that have the appropriate shapes to ensure that all the differentiation // code works as intended - residual.AddDomainIntegral(Dimension{}, DependsOn<0>{}, MixedModelOne{}, *mesh); - - residual.AddBoundaryIntegral(Dimension{}, DependsOn<0>{}, MixedModelTwo{}, *mesh); + Domain dom = EntireDomain(*mesh); + Domain bdr = EntireBoundary(*mesh); + residual.AddDomainIntegral(Dimension{}, DependsOn<0>{}, MixedModelOne{}, dom); + residual.AddBoundaryIntegral(Dimension{}, DependsOn<0>{}, MixedModelTwo{}, bdr); double t = 0.0; check_gradient(residual, t, U); @@ -128,8 +129,11 @@ void elasticity_test(std::unique_ptr& mesh) // note: this is not really an elasticity problem, it's testing source and flux // terms that have the appropriate shapes to ensure that all the differentiation // code works as intended - residual.AddDomainIntegral(Dimension{}, DependsOn<0>{}, ElasticityTestModelOne{}, *mesh); - residual.AddBoundaryIntegral(Dimension{}, DependsOn<0>{}, ElasticityTestModelTwo{}, *mesh); + Domain dom = EntireDomain(*mesh); + Domain bdr = EntireBoundary(*mesh); + + residual.AddDomainIntegral(Dimension{}, DependsOn<0>{}, ElasticityTestModelOne{}, dom); + residual.AddBoundaryIntegral(Dimension{}, DependsOn<0>{}, ElasticityTestModelTwo{}, bdr); double t = 0.0; check_gradient(residual, t, U); diff --git a/src/serac/numerics/functional/tests/functional_boundary_test.cpp b/src/serac/numerics/functional/tests/functional_boundary_test.cpp index f34248f08..b177e37c2 100644 --- a/src/serac/numerics/functional/tests/functional_boundary_test.cpp +++ b/src/serac/numerics/functional/tests/functional_boundary_test.cpp @@ -92,6 +92,8 @@ void boundary_test(mfem::ParMesh& mesh, H1

test, H1

trial, Dimension) Functional residual(fespace.get(), {fespace.get()}); + Domain bdr = EntireBoundary(mesh); + residual.AddBoundaryIntegral( Dimension{}, DependsOn<0>{}, [&](double /*t*/, auto position, auto temperature) { @@ -103,7 +105,7 @@ void boundary_test(mfem::ParMesh& mesh, H1

test, H1

trial, Dimension) tensor b{sin(X[0]), X[0] * X[1]}; return X[0] * X[1] + dot(b, n) + rho * u; }, - mesh); + bdr); // mfem::Vector r1 = (*J) * U + (*F); mfem::Vector r1(U.Size()); @@ -171,6 +173,8 @@ void boundary_test(mfem::ParMesh& mesh, L2

test, L2

trial, Dimension) Functional residual(fespace.get(), {fespace.get()}); + Domain bdr = EntireBoundary(mesh); + residual.AddBoundaryIntegral( Dimension{}, DependsOn<0>{}, [&](double /*t*/, auto position, auto temperature) { @@ -181,7 +185,7 @@ void boundary_test(mfem::ParMesh& mesh, L2

test, L2

trial, Dimension) // tensor b{sin(x[0]), x[0] * x[1]}; return X[0] * X[1] + /* dot(b, n) +*/ rho * u; }, - mesh); + bdr); // mfem::Vector r1 = (*J) * U + (*F); mfem::Vector r1(U.Size()); diff --git a/src/serac/numerics/functional/tests/functional_comparison_L2.cpp b/src/serac/numerics/functional/tests/functional_comparison_L2.cpp index 3678fa213..8064491d5 100644 --- a/src/serac/numerics/functional/tests/functional_comparison_L2.cpp +++ b/src/serac/numerics/functional/tests/functional_comparison_L2.cpp @@ -109,7 +109,8 @@ void functional_test(mfem::ParMesh& mesh, L2

test, L2

trial, Dimension residual(fespace.get(), {fespace.get()}); // Add the total domain residual term to the weak form - residual.AddDomainIntegral(Dimension{}, DependsOn<0>{}, test_qfunction{}, mesh); + Domain dom = EntireDomain(mesh); + residual.AddDomainIntegral(Dimension{}, DependsOn<0>{}, test_qfunction{}, dom); // uncomment lines below to verify that compile-time error messages // explain L2 spaces are not currently supported in boundary integrals. @@ -180,8 +181,10 @@ TEST(L2, 2DMixed) auto [H1fespace, H1fec] = serac::generateParFiniteElementSpace(mesh2D.get()); + Domain dom = EntireDomain(*mesh2D); + serac::Functional f(L2fespace.get(), {H1fespace.get()}); - f.AddDomainIntegral(serac::Dimension{}, serac::DependsOn<0>{}, hcurl_qfunction{}, *mesh2D); + f.AddDomainIntegral(serac::Dimension{}, serac::DependsOn<0>{}, hcurl_qfunction{}, dom); } int main(int argc, char* argv[]) diff --git a/src/serac/numerics/functional/tests/functional_comparisons.cpp b/src/serac/numerics/functional/tests/functional_comparisons.cpp index 99f4ae9a2..d4621afa7 100644 --- a/src/serac/numerics/functional/tests/functional_comparisons.cpp +++ b/src/serac/numerics/functional/tests/functional_comparisons.cpp @@ -143,8 +143,10 @@ void functional_test(mfem::ParMesh& mesh, H1

test, H1

trial, Dimension residual(fespace.get(), {fespace.get()}); + Domain dom = EntireDomain(mesh); + // Add the total domain residual term to the functional - residual.AddDomainIntegral(Dimension{}, DependsOn<0>{}, SourceFluxFunctor{}, mesh); + residual.AddDomainIntegral(Dimension{}, DependsOn<0>{}, SourceFluxFunctor{}, dom); // Compute the residual using standard MFEM methods // mfem::Vector r1 = (*J_mfem) * U - (*F); @@ -263,7 +265,9 @@ void functional_test(mfem::ParMesh& mesh, H1 test, H1 trial, Dim Functional residual(fespace.get(), {fespace.get()}); - residual.AddDomainIntegral(Dimension{}, DependsOn<0>{}, StressFunctor{}, mesh); + Domain domain = EntireDomain(mesh); + + residual.AddDomainIntegral(Dimension{}, DependsOn<0>{}, StressFunctor{}, domain); // mfem::Vector r1 = (*J_mfem) * U - (*F); mfem::Vector r1(U.Size()); diff --git a/src/serac/numerics/functional/tests/functional_multiphysics.cpp b/src/serac/numerics/functional/tests/functional_multiphysics.cpp index fc750c0c8..12b04514f 100644 --- a/src/serac/numerics/functional/tests/functional_multiphysics.cpp +++ b/src/serac/numerics/functional/tests/functional_multiphysics.cpp @@ -52,6 +52,9 @@ TEST(FunctionalMultiphysics, NonlinearThermalTest3D) // Construct the new functional object using the known test and trial spaces Functional residual(fespace.get(), {fespace.get(), fespace.get()}); + Domain dom = EntireDomain(*mesh3D); + Domain bdr = EntireBoundary(*mesh3D); + residual.AddVolumeIntegral( DependsOn<0, 1>{}, [=](double /*t*/, auto position, auto temperature, auto dtemperature_dt) { @@ -62,7 +65,7 @@ TEST(FunctionalMultiphysics, NonlinearThermalTest3D) auto flux = kappa * du_dX; return serac::tuple{source, flux}; }, - *mesh3D); + dom); residual.AddSurfaceIntegral( DependsOn<0, 1>{}, @@ -72,7 +75,7 @@ TEST(FunctionalMultiphysics, NonlinearThermalTest3D) auto [du_dt, _1] = dtemperature_dt; return X[0] + X[1] - cos(u) * du_dt; }, - *mesh3D); + bdr); double t = 0.0; mfem::Vector r = residual(t, U, dU_dt); diff --git a/src/serac/numerics/functional/tests/functional_nonlinear.cpp b/src/serac/numerics/functional/tests/functional_nonlinear.cpp index d846df32b..b411056c4 100644 --- a/src/serac/numerics/functional/tests/functional_nonlinear.cpp +++ b/src/serac/numerics/functional/tests/functional_nonlinear.cpp @@ -72,6 +72,9 @@ void functional_test(mfem::ParMesh& mesh, H1

test, H1

trial, Dimension residual(fespace.get(), {fespace.get()}); + Domain dom = EntireDomain(mesh); + Domain bdr = EntireBoundary(mesh); + // Add the total domain residual term to the functional residual.AddDomainIntegral( Dimension{}, DependsOn<0>{}, @@ -82,7 +85,7 @@ void functional_test(mfem::ParMesh& mesh, H1

test, H1

trial, Dimension{}, DependsOn<0>{}, @@ -91,7 +94,7 @@ void functional_test(mfem::ParMesh& mesh, H1

test, H1

trial, Dimension(temperature); return X[0] + X[1] - cos(u); }, - mesh); + bdr); double t = 0.0; check_gradient(residual, t, U); @@ -119,6 +122,9 @@ void functional_test(mfem::ParMesh& mesh, H1 test, H1 trial, Dim // Construct the new functional object using the known test and trial spaces Functional residual(fespace.get(), {fespace.get()}); + Domain dom = EntireDomain(mesh); + Domain bdr = EntireBoundary(mesh); + // Add the total domain residual term to the functional residual.AddDomainIntegral( Dimension{}, DependsOn<0>{}, @@ -129,17 +135,18 @@ void functional_test(mfem::ParMesh& mesh, H1 test, H1 trial, Dim auto flux = b * du_dx; return serac::tuple{source, flux}; }, - mesh); + dom); residual.AddBoundaryIntegral( Dimension{}, DependsOn<0>{}, [=](double /*t*/, auto position, auto displacement) { auto [X, dX_dxi] = position; - auto u = get<0>(displacement); - auto n = normalize(cross(dX_dxi)); + std::cout << X << " " << dX_dxi << std::endl; + auto u = get<0>(displacement); + auto n = normalize(cross(dX_dxi)); return (X[0] + X[1] - cos(u[0])) * n; }, - mesh); + bdr); double t = 0.0; check_gradient(residual, t, U); diff --git a/src/serac/numerics/functional/tests/functional_qoi.cpp b/src/serac/numerics/functional/tests/functional_qoi.cpp index 50808c267..17ad0d5c0 100644 --- a/src/serac/numerics/functional/tests/functional_qoi.cpp +++ b/src/serac/numerics/functional/tests/functional_qoi.cpp @@ -211,10 +211,13 @@ void qoi_test(mfem::ParMesh& mesh, H1

trial, Dimension, WhichTest which) mfem::HypreParVector V = *tmp2; V_gf.GetTrueDofs(V); + Domain domain = EntireDomain(mesh); + Domain boundary = EntireBoundary(mesh); + switch (which) { case WhichTest::Measure: { Functional measure({fespace.get()}); - measure.AddDomainIntegral(Dimension{}, DependsOn<>{}, TrivialIntegrator{}, mesh); + measure.AddDomainIntegral(Dimension{}, DependsOn<>{}, TrivialIntegrator{}, domain); constexpr double expected[] = {1.0, 16.0}; @@ -228,7 +231,7 @@ void qoi_test(mfem::ParMesh& mesh, H1

trial, Dimension, WhichTest which) case WhichTest::Moment: { Functional x_moment({fespace.get()}); - x_moment.AddDomainIntegral(Dimension{}, DependsOn<>{}, ZeroIndexIntegrator{}, mesh); + x_moment.AddDomainIntegral(Dimension{}, DependsOn<>{}, ZeroIndexIntegrator{}, domain); constexpr double expected[] = {0.5, 40.0}; @@ -239,7 +242,7 @@ void qoi_test(mfem::ParMesh& mesh, H1

trial, Dimension, WhichTest which) EXPECT_NEAR(0.0, relative_error, 1.0e-10); Functional x_moment_2({fespace.get()}); - x_moment_2.AddDomainIntegral(Dimension{}, DependsOn<0>{}, GetZeroIntegrator{}, mesh); + x_moment_2.AddDomainIntegral(Dimension{}, DependsOn<0>{}, GetZeroIntegrator{}, domain); relative_error = (x_moment_2(t, V) - expected[dim - 2]) / expected[dim - 2]; EXPECT_NEAR(0.0, relative_error, 1.0e-10); @@ -251,8 +254,8 @@ void qoi_test(mfem::ParMesh& mesh, H1

trial, Dimension, WhichTest which) case WhichTest::SumOfMeasures: { Functional sum_of_measures({fespace.get()}); - sum_of_measures.AddDomainIntegral(Dimension{}, DependsOn<>{}, TrivialIntegrator{}, mesh); - sum_of_measures.AddBoundaryIntegral(Dimension{}, DependsOn<>{}, TrivialIntegrator{}, mesh); + sum_of_measures.AddDomainIntegral(Dimension{}, DependsOn<>{}, TrivialIntegrator{}, domain); + sum_of_measures.AddBoundaryIntegral(Dimension{}, DependsOn<>{}, TrivialIntegrator{}, boundary); constexpr double expected[] = {5.0, 64.0}; @@ -266,8 +269,8 @@ void qoi_test(mfem::ParMesh& mesh, H1

trial, Dimension, WhichTest which) case WhichTest::Nonlinear: { Functional f({fespace.get()}); - f.AddDomainIntegral(Dimension{}, DependsOn<0>{}, SineIntegrator{}, mesh); - f.AddBoundaryIntegral(Dimension{}, DependsOn<0>{}, CosineIntegrator{}, mesh); + f.AddDomainIntegral(Dimension{}, DependsOn<0>{}, SineIntegrator{}, domain); + f.AddBoundaryIntegral(Dimension{}, DependsOn<0>{}, CosineIntegrator{}, boundary); constexpr double expected[] = {4.6640262484879, 192400.1149761554}; @@ -315,9 +318,12 @@ void qoi_test(mfem::ParMesh& mesh, H1 trial1, H1 trial2, Dimension) mfem::HypreParVector U2 = *tmp; U2_gf.GetTrueDofs(U2); + Domain domain = EntireDomain(mesh); + Domain boundary = EntireBoundary(mesh); + Functional f({fespace1.get(), fespace2.get()}); - f.AddDomainIntegral(Dimension{}, DependsOn<0, 1>{}, FourArgSineIntegrator{}, mesh); - f.AddBoundaryIntegral(Dimension{}, DependsOn<0, 1>{}, FourArgCosineIntegrator{}, mesh); + f.AddDomainIntegral(Dimension{}, DependsOn<0, 1>{}, FourArgSineIntegrator{}, domain); + f.AddBoundaryIntegral(Dimension{}, DependsOn<0, 1>{}, FourArgCosineIntegrator{}, boundary); // note: these answers are generated by a Mathematica script that // integrates the qoi for these domains to machine precision @@ -357,8 +363,10 @@ TEST(QoI, DependsOnVectorValuedInput) mfem::HypreParVector U = *tmp; U_gf.GetTrueDofs(U); + Domain whole_mesh = EntireDomain(mesh); + Functional f({fespace.get()}); - f.AddVolumeIntegral(DependsOn<0>{}, GetNormZeroIntegrator{}, mesh); + f.AddVolumeIntegral(DependsOn<0>{}, GetNormZeroIntegrator{}, whole_mesh); double exact_answer = 141.3333333333333; double relative_error = (f(t, U) - exact_answer) / exact_answer; @@ -387,7 +395,9 @@ TEST(QoI, AddAreaIntegral) U_gf.GetTrueDofs(U); Functional measure({fespace.get()}); - measure.AddAreaIntegral(DependsOn<>{}, TrivialIntegrator{}, mesh); + + Domain whole_mesh = EntireDomain(mesh); + measure.AddAreaIntegral(DependsOn<>{}, TrivialIntegrator{}, whole_mesh); double relative_error = (measure(t, U) - measure_mfem(mesh)) / measure(t, U); EXPECT_NEAR(0.0, relative_error, 1.0e-10); @@ -415,7 +425,9 @@ TEST(QoI, AddVolumeIntegral) U_gf.GetTrueDofs(U); Functional measure({fespace.get()}); - measure.AddVolumeIntegral(DependsOn<>{}, TrivialIntegrator{}, mesh); + + Domain whole_mesh = EntireDomain(mesh); + measure.AddVolumeIntegral(DependsOn<>{}, TrivialIntegrator{}, whole_mesh); double relative_error = (measure(t, U) - measure_mfem(mesh)) / measure(t, U); EXPECT_NEAR(0.0, relative_error, 1.0e-10); @@ -445,8 +457,11 @@ TEST(QoI, UsingL2) // this tests a fix for the QoI constructor segfaulting when using L2 spaces Functional f({fespace_0.get(), fespace_1.get()}); - f.AddVolumeIntegral(DependsOn<1>{}, TrivialVariadicIntegrator{}, mesh); - f.AddSurfaceIntegral(DependsOn<0>{}, TrivialVariadicIntegrator{}, mesh); + Domain whole_mesh = EntireDomain(mesh); + Domain whole_boundary = EntireBoundary(mesh); + + f.AddVolumeIntegral(DependsOn<1>{}, TrivialVariadicIntegrator{}, whole_mesh); + f.AddSurfaceIntegral(DependsOn<0>{}, TrivialVariadicIntegrator{}, whole_boundary); check_gradient(f, t, *U0, *U1); } diff --git a/src/serac/numerics/functional/tests/functional_shape_derivatives.cpp b/src/serac/numerics/functional/tests/functional_shape_derivatives.cpp index b79841bc9..28b648086 100644 --- a/src/serac/numerics/functional/tests/functional_shape_derivatives.cpp +++ b/src/serac/numerics/functional/tests/functional_shape_derivatives.cpp @@ -290,9 +290,11 @@ void functional_test_2D(mfem::ParMesh& mesh, double tolerance) // Construct the new functional object using the known test and trial spaces ShapeAwareFunctional residual(fespace2.get(), fespace1.get(), {fespace1.get()}); - residual.AddDomainIntegral(Dimension{}, DependsOn<>{}, TestFunctorOne{}, mesh); + Domain whole_domain = EntireDomain(mesh); + residual.AddDomainIntegral(Dimension{}, DependsOn<>{}, TestFunctorOne{}, whole_domain); - residual.AddBoundaryIntegral(Dimension{}, DependsOn<>{}, TestFunctorTwo{}, mesh); + Domain whole_boundary = EntireBoundary(mesh); + residual.AddBoundaryIntegral(Dimension{}, DependsOn<>{}, TestFunctorTwo{}, whole_boundary); double t = 0.0; auto [r, drdU2] = residual(t, serac::differentiate_wrt(U2), U1); @@ -333,9 +335,11 @@ void functional_test_3D(mfem::ParMesh& mesh, double tolerance) // Construct the new functional object using the known test and trial spaces ShapeAwareFunctional residual(fespace2.get(), fespace1.get(), {fespace1.get()}); - residual.AddDomainIntegral(Dimension{}, DependsOn<>{}, TestFunctorOne{}, mesh); + Domain whole_domain = EntireDomain(mesh); + residual.AddDomainIntegral(Dimension{}, DependsOn<>{}, TestFunctorOne{}, whole_domain); - residual.AddBoundaryIntegral(Dimension{}, DependsOn<>{}, TestFunctorTwo{}, mesh); + Domain whole_boundary = EntireBoundary(mesh); + residual.AddBoundaryIntegral(Dimension{}, DependsOn<>{}, TestFunctorTwo{}, whole_boundary); double t = 0.0; auto [r, drdU2] = residual(t, serac::differentiate_wrt(U2), U1); diff --git a/src/serac/numerics/functional/tests/functional_tet_quality.cpp b/src/serac/numerics/functional/tests/functional_tet_quality.cpp index 36a237377..5b90eb71e 100644 --- a/src/serac/numerics/functional/tests/functional_tet_quality.cpp +++ b/src/serac/numerics/functional/tests/functional_tet_quality.cpp @@ -49,6 +49,8 @@ TEST(QoI, TetrahedronQuality) auto [fes, fec] = generateParFiniteElementSpace(mesh.get()); + Domain whole_domain = EntireDomain(*mesh); + // Define the shape-aware QOI objects serac::ShapeAwareFunctional saf_qoi(fes.get(), {}); @@ -60,7 +62,7 @@ TEST(QoI, TetrahedronQuality) auto [x, dx_dxi] = position; return mu(dot(regular_tet_correction, dx_dxi)); }, - *mesh); + whole_domain); serac::Functional qoi({fes.get()}); @@ -78,7 +80,7 @@ TEST(QoI, TetrahedronQuality) auto dx_dxi = dX_dxi + dot(du_dX, dX_dxi); return mu(dot(regular_tet_correction, dx_dxi)); }, - *mesh); + whole_domain); std::unique_ptr u(fes->NewTrueDofVector()); *u = 0.0; diff --git a/src/serac/numerics/functional/tests/functional_with_domain.cpp b/src/serac/numerics/functional/tests/functional_with_domain.cpp index cdefd63c5..d05079a1b 100644 --- a/src/serac/numerics/functional/tests/functional_with_domain.cpp +++ b/src/serac/numerics/functional/tests/functional_with_domain.cpp @@ -73,16 +73,6 @@ struct TrivialIntegrator { } }; -template -tensor average(std::vector>& positions) -{ - tensor total{}; - for (auto x : positions) { - total += x; - } - return total / double(positions.size()); -} - template void whole_mesh_comparison_test_impl(std::unique_ptr& mesh) { @@ -222,10 +212,11 @@ void partial_mesh_comparison_test_impl(std::unique_ptr& mesh) ////////////// - residual_comparison.AddDomainIntegral(Dimension{}, DependsOn<0>{}, TestThermalIntegratorOne{}, *mesh); + Domain d = EntireDomain(*mesh); + residual_comparison.AddDomainIntegral(Dimension{}, DependsOn<0>{}, TestThermalIntegratorOne{}, d); - residual_comparison.AddBoundaryIntegral(Dimension{}, DependsOn<0>{}, TestThermalIntegratorTwo{}, - *mesh); + Domain bdr = EntireBoundary(*mesh); + residual_comparison.AddBoundaryIntegral(Dimension{}, DependsOn<0>{}, TestThermalIntegratorTwo{}, bdr); auto r1 = residual_comparison(t, U); diff --git a/src/serac/numerics/functional/tests/geometric_factors_tests.cpp b/src/serac/numerics/functional/tests/geometric_factors_tests.cpp index 9df09ed93..02e76b382 100644 --- a/src/serac/numerics/functional/tests/geometric_factors_tests.cpp +++ b/src/serac/numerics/functional/tests/geometric_factors_tests.cpp @@ -21,16 +21,6 @@ mfem::Mesh import_mesh(std::string meshfile) return mesh; } -template -tensor average(std::vector >& positions) -{ - tensor total{}; - for (auto x : positions) { - total += x; - } - return total / double(positions.size()); -} - TEST(geometric_factors, with_2D_domains) { auto mesh = import_mesh("patch2D_tris_and_quads.mesh"); diff --git a/src/serac/numerics/functional/typedefs.hpp b/src/serac/numerics/functional/typedefs.hpp new file mode 100644 index 000000000..7dbe070f9 --- /dev/null +++ b/src/serac/numerics/functional/typedefs.hpp @@ -0,0 +1,14 @@ +#pragma once + +namespace serac { + +// sam: this is a kludge-- it looks like the DG spaces need to use some interface defined ONLY on +// mfem::ParMesh / mfeme::ParFiniteElementSpace, but I'm not ready to pull the trigger on a big +// interface change like that, so these typedefs mark the parts that would need to eventually change + +/// @cond +using mesh_t = mfem::Mesh; +using fes_t = mfem::FiniteElementSpace; +/// @endcond + +} // namespace serac diff --git a/src/serac/numerics/tests/equationsolver.cpp b/src/serac/numerics/tests/equationsolver.cpp index d48ecf68f..ddbe3c393 100644 --- a/src/serac/numerics/tests/equationsolver.cpp +++ b/src/serac/numerics/tests/equationsolver.cpp @@ -58,6 +58,8 @@ TEST_P(EquationSolverSuite, All) x_exact.Randomize(0); + Domain domain = EntireDomain(pmesh); + residual.AddDomainIntegral( Dimension{}, DependsOn<0>{}, [&](double /*t*/, auto, auto scalar) { @@ -66,7 +68,7 @@ TEST_P(EquationSolverSuite, All) auto flux = du_dx; return serac::tuple{source, flux}; }, - pmesh); + domain); StdFunctionOperator residual_opr( fes->TrueVSize(), diff --git a/src/serac/numerics/tests/equationsolver_petsc.cpp b/src/serac/numerics/tests/equationsolver_petsc.cpp index c92fbeded..208efa50a 100644 --- a/src/serac/numerics/tests/equationsolver_petsc.cpp +++ b/src/serac/numerics/tests/equationsolver_petsc.cpp @@ -61,6 +61,8 @@ TEST_P(EquationSolverSuite, All) x_exact.Randomize(0); + Domain domain = EntireDomain(pmesh); + residual.AddDomainIntegral( Dimension{}, DependsOn<0>{}, [&](double /*t*/, auto, auto scalar) { @@ -69,7 +71,7 @@ TEST_P(EquationSolverSuite, All) auto flux = du_dx; return serac::tuple{source, flux}; }, - pmesh); + domain); StdFunctionOperator residual_opr( fes.TrueVSize(), diff --git a/src/serac/physics/benchmarks/physics_benchmark_functional.cpp b/src/serac/physics/benchmarks/physics_benchmark_functional.cpp index 6645074b0..ee67b9cb6 100644 --- a/src/serac/physics/benchmarks/physics_benchmark_functional.cpp +++ b/src/serac/physics/benchmarks/physics_benchmark_functional.cpp @@ -36,6 +36,8 @@ void functional_test(int parallel_refinement) using space = serac::H1; auto [fespace, fec] = serac::generateParFiniteElementSpace(mesh.get()); + serac::Domain whole_domain = serac::EntireDomain(*mesh); + serac::Functional residual(fespace.get(), {fespace.get()}); // Add the total domain residual term to the functional @@ -46,7 +48,7 @@ void functional_test(int parallel_refinement) auto [u, du_dx] = phi; return serac::tuple{u, du_dx}; }, - *mesh); + whole_domain); // Set a random state to evaluate the residual mfem::ParGridFunction u_global(fespace.get()); diff --git a/src/serac/physics/benchmarks/physics_benchmark_solid_nonlinear_solve.cpp b/src/serac/physics/benchmarks/physics_benchmark_solid_nonlinear_solve.cpp index 7c789c094..67c5b7652 100644 --- a/src/serac/physics/benchmarks/physics_benchmark_solid_nonlinear_solve.cpp +++ b/src/serac/physics/benchmarks/physics_benchmark_solid_nonlinear_solve.cpp @@ -242,8 +242,10 @@ void functional_solid_test_euler(NonlinSolve nonlinSolve, Prec prec) serac::solid_mechanics::default_quasistatic_options, "serac_solid", meshTag, std::vector{}); + Domain whole_domain = EntireDomain(*meshPtr); + serac::solid_mechanics::NeoHookean material{density, bulkMod, shearMod}; - seracSolid->setMaterial(serac::DependsOn<>{}, material); + seracSolid->setMaterial(material, whole_domain); serac::Domain backSurface = serac::Domain::ofBoundaryElements(*meshPtr, serac::by_attr(3)); // 4,5 with traction makes a twist @@ -320,8 +322,10 @@ void functional_solid_test_nonlinear_buckle(NonlinSolve nonlinSolve, Prec prec, serac::solid_mechanics::default_quasistatic_options, "serac_solid", meshTag, std::vector{}); + Domain whole_domain = EntireDomain(*meshPtr); + serac::solid_mechanics::NeoHookean material{density, bulkMod, shearMod}; - seracSolid->setMaterial(serac::DependsOn<>{}, material); + seracSolid->setMaterial(material, whole_domain); // fix displacement on side surface seracSolid->setDisplacementBCs({2, 3, 4, 5}, [](const mfem::Vector&, mfem::Vector& u) { u = 0.0; }); diff --git a/src/serac/physics/benchmarks/physics_benchmark_thermal.cpp b/src/serac/physics/benchmarks/physics_benchmark_thermal.cpp index cb31ad519..a8ab5a8f6 100644 --- a/src/serac/physics/benchmarks/physics_benchmark_thermal.cpp +++ b/src/serac/physics/benchmarks/physics_benchmark_thermal.cpp @@ -36,7 +36,8 @@ void functional_test_static() auto mesh = serac::mesh::refineAndDistribute(serac::buildMeshFromFile(filename), serial_refinement, parallel_refinement); - serac::StateManager::setMesh(std::move(mesh), "default_mesh"); + + auto& pmesh = serac::StateManager::setMesh(std::move(mesh), "default_mesh"); // Define a boundary attribute set std::set ess_bdr = {1}; @@ -63,8 +64,10 @@ void functional_test_static() cond = {{{1.5, 0.01, 0.0}, {0.01, 1.0, 0.0}, {0.0, 0.0, 1.0}}}; } + serac::Domain whole_domain = serac::EntireDomain(pmesh); + serac::heat_transfer::LinearConductor mat(1.0, 1.0, cond); - thermal_solver.setMaterial(mat); + thermal_solver.setMaterial(mat, whole_domain); // Define the function for the initial temperature and boundary condition auto one = [](const mfem::Vector&, double) -> double { return 1.0; }; @@ -75,7 +78,7 @@ void functional_test_static() // Define a constant source term serac::heat_transfer::ConstantSource source{1.0}; - thermal_solver.setSource(source); + thermal_solver.setSource(source, whole_domain); // Finalize the data structures thermal_solver.completeSetup(); @@ -108,7 +111,8 @@ void functional_test_dynamic() auto mesh = serac::mesh::refineAndDistribute(serac::buildMeshFromFile(filename), serial_refinement, parallel_refinement); - serac::StateManager::setMesh(std::move(mesh), "default_mesh"); + + auto& pmesh = serac::StateManager::setMesh(std::move(mesh), "default_mesh"); // Define a boundary attribute set std::set ess_bdr = {1}; @@ -118,10 +122,12 @@ void functional_test_dynamic() serac::heat_transfer::default_nonlinear_options, serac::heat_transfer::default_linear_options, serac::heat_transfer::default_timestepping_options, "thermal_functional", "default_mesh"); + serac::Domain whole_domain = serac::EntireDomain(pmesh); + // Define an isotropic conductor material model serac::heat_transfer::LinearIsotropicConductor mat(1.0, 1.0, 1.0); - thermal_solver.setMaterial(mat); + thermal_solver.setMaterial(mat, whole_domain); // Define the function for the initial temperature and boundary condition auto initial_temp = [](const mfem::Vector& x, double) -> double { @@ -137,7 +143,7 @@ void functional_test_dynamic() // Define a constant source term serac::heat_transfer::ConstantSource source{1.0}; - thermal_solver.setSource(source); + thermal_solver.setSource(source, whole_domain); // Finalize the data structures thermal_solver.completeSetup(); diff --git a/src/serac/physics/fit.hpp b/src/serac/physics/fit.hpp index a83c63b48..88815b43c 100644 --- a/src/serac/physics/fit.hpp +++ b/src/serac/physics/fit.hpp @@ -17,29 +17,30 @@ namespace detail { /// @overload template -FiniteElementState fit(std::integer_sequence, func f, mfem::ParMesh& mesh, const T&... solution_fields) +FiniteElementState fit(std::integer_sequence, func f, mfem::ParMesh& pmesh, const T&... solution_fields) { // signature looks like return_type(arg0_type, arg1_type); // so this unpacks the return type using output_space = typename FunctionSignature::return_type; - FiniteElementState fitted_field(mesh, output_space{}); + FiniteElementState fitted_field(pmesh, output_space{}); fitted_field = 0.0; // mass term + Domain whole_domain = EntireDomain(pmesh); serac::Functional phi_phi(&fitted_field.space(), {&fitted_field.space()}); phi_phi.AddDomainIntegral( Dimension{}, DependsOn<0>{}, [](double /*t*/, auto /*x*/, auto u) { return tuple{get<0>(u), zero{}}; }, - mesh); + whole_domain); auto M = get<1>(phi_phi(DifferentiateWRT<0>{}, 0.0 /* t */, fitted_field)); // rhs std::array trial_spaces = {&solution_fields.space()...}; serac::Functional phi_f(&fitted_field.space(), trial_spaces); - phi_f.AddDomainIntegral(Dimension{}, DependsOn{}, f, mesh); + phi_f.AddDomainIntegral(Dimension{}, DependsOn{}, f, whole_domain); mfem::Vector b = phi_f(0.0, solution_fields...); mfem::CGSolver cg(MPI_COMM_WORLD); @@ -57,16 +58,16 @@ FiniteElementState fit(std::integer_sequence, func f, mfem::ParMesh& /** * @brief determine field parameters to approximate the output of a user-provided q-function * @param[in] f the user-provided function to approximate - * @param[in] mesh the region over which to approximate the function f + * @param[in] pmesh the region over which to approximate the function f * @param[in] solution_fields [optional] any auxiliary field quantities needed to evaluate f * * @note: mesh is passed by non-const ref because mfem mutates the mesh when creating ParGridFunctions */ template -FiniteElementState fit(func f, mfem::ParMesh& mesh, const T&... solution_fields) +FiniteElementState fit(func f, mfem::ParMesh& pmesh, const T&... solution_fields) { auto iseq = std::make_integer_sequence{}; - return detail::fit(iseq, f, mesh, solution_fields...); + return detail::fit(iseq, f, pmesh, solution_fields...); } } // namespace serac diff --git a/src/serac/physics/heat_transfer.hpp b/src/serac/physics/heat_transfer.hpp index 0147658bd..a4c1adcb8 100644 --- a/src/serac/physics/heat_transfer.hpp +++ b/src/serac/physics/heat_transfer.hpp @@ -410,6 +410,7 @@ class HeatTransfer, std::integer_sequ * * @tparam MaterialType The thermal material type * @param material A material containing heat capacity and thermal flux evaluation information + * @param domain which elements in the mesh are described by the specified material * * @pre material must be a object that can be called with the following arguments: * 1. `tensor x` the spatial position of the material evaluation call @@ -429,17 +430,17 @@ class HeatTransfer, std::integer_sequ * @note This method must be called prior to completeSetup() */ template - void setMaterial(DependsOn, const MaterialType& material) + void setMaterial(DependsOn, const MaterialType& material, Domain& domain) { residual_->AddDomainIntegral(Dimension{}, DependsOn<0, 1, NUM_STATE_VARS + active_parameters...>{}, - ThermalMaterialIntegrand(material), mesh_); + ThermalMaterialIntegrand(material), domain); } /// @overload template - void setMaterial(const MaterialType& material) + void setMaterial(const MaterialType& material, Domain& domain) { - setMaterial(DependsOn<>{}, material); + setMaterial(DependsOn<>{}, material, domain); } /** @@ -466,8 +467,7 @@ class HeatTransfer, std::integer_sequ * * @tparam SourceType The type of the source function * @param source_function A source function for a prescribed thermal load - * @param optional_domain The domain over which the source is applied. If nothing is supplied the entire domain is - * used. + * @param domain The domain over which the source is applied. * * @pre source_function must be a object that can be called with the following arguments: * 1. `tensor x` the spatial coordinates for the quadrature point @@ -485,11 +485,8 @@ class HeatTransfer, std::integer_sequ * @note This method must be called prior to completeSetup() */ template - void setSource(DependsOn, SourceType source_function, - const std::optional& optional_domain = std::nullopt) + void setSource(DependsOn, SourceType source_function, Domain& domain) { - Domain domain = (optional_domain) ? *optional_domain : EntireDomain(mesh_); - residual_->AddDomainIntegral( Dimension{}, DependsOn<0, 1, active_parameters + NUM_STATE_VARS...>{}, [source_function](double t, auto x, auto temperature, auto /* dtemp_dt */, auto... params) { @@ -506,9 +503,9 @@ class HeatTransfer, std::integer_sequ /// @overload template - void setSource(SourceType source_function, const std::optional& optional_domain = std::nullopt) + void setSource(SourceType source_function, Domain& domain) { - setSource(DependsOn<>{}, source_function, optional_domain); + setSource(DependsOn<>{}, source_function, domain); } /** @@ -516,8 +513,7 @@ class HeatTransfer, std::integer_sequ * * @tparam FluxType The type of the thermal flux object * @param flux_function A function describing the flux applied to a boundary - * @param optional_domain The domain over which the flux is applied. If nothing is supplied the entire boundary is - * used. + * @param domain The domain over which the flux is applied * * @pre FluxType must be a object that can be called with the following arguments: * 1. `tensor x` the spatial coordinates for the quadrature point @@ -535,11 +531,8 @@ class HeatTransfer, std::integer_sequ * @note This method must be called prior to completeSetup() */ template - void setFluxBCs(DependsOn, FluxType flux_function, - const std::optional& optional_domain = std::nullopt) + void setFluxBCs(DependsOn, FluxType flux_function, Domain& domain) { - Domain domain = (optional_domain) ? *optional_domain : EntireBoundary(mesh_); - residual_->AddBoundaryIntegral( Dimension{}, DependsOn<0, 1, active_parameters + NUM_STATE_VARS...>{}, [flux_function](double t, auto X, auto u, auto /* dtemp_dt */, auto... params) { @@ -553,9 +546,9 @@ class HeatTransfer, std::integer_sequ /// @overload template - void setFluxBCs(FluxType flux_function, const std::optional& optional_domain = std::nullopt) + void setFluxBCs(FluxType flux_function, Domain& domain) { - setFluxBCs(DependsOn<>{}, flux_function, optional_domain); + setFluxBCs(DependsOn<>{}, flux_function, domain); } /** @@ -651,28 +644,27 @@ class HeatTransfer, std::integer_sequ * * @tparam active_parameters a list of indices, describing which parameters to pass to the q-function * @param qfunction a callable that returns the normal heat flux on a boundary surface - * @param optional_domain The domain over which the integral is computed + * @param domain The domain over which the integral is computed * * ~~~ {.cpp} * * heat_transfer.addCustomBoundaryIntegral( - * DependsOn<>{}, - * [](double t, auto position, auto temperature, auto temperature_rate) { - * auto [T, dT_dxi] = temperature; - * auto q = 5.0*(T-25.0); - * return q; // define a temperature-proportional heat-flux - * }); + * DependsOn<>{}, + * [](double t, auto position, auto temperature, auto temperature_rate) { + * auto [T, dT_dxi] = temperature; + * auto q = 5.0*(T-25.0); + * return q; // define a temperature-proportional heat-flux + * }, + * domain + * ); * * ~~~ * * @note This method must be called prior to completeSetup() */ template - void addCustomBoundaryIntegral(DependsOn, callable qfunction, - const std::optional& optional_domain = std::nullopt) + void addCustomBoundaryIntegral(DependsOn, callable qfunction, Domain& domain) { - Domain domain = (optional_domain) ? *optional_domain : EntireBoundary(mesh_); - residual_->AddBoundaryIntegral(Dimension{}, DependsOn<0, 1, active_parameters + NUM_STATE_VARS...>{}, qfunction, domain); } diff --git a/src/serac/physics/solid_mechanics.hpp b/src/serac/physics/solid_mechanics.hpp index eec39eb6f..b6b9b7c7a 100644 --- a/src/serac/physics/solid_mechanics.hpp +++ b/src/serac/physics/solid_mechanics.hpp @@ -263,6 +263,7 @@ class SolidMechanics, std::integer_se initializeSolidMechanicsStates(); } +#if 0 /** * @brief Construct a new Nonlinear SolidMechanics Solver object * @@ -352,6 +353,7 @@ class SolidMechanics, std::integer_se } } } +#endif /// @brief Destroy the SolidMechanics Functional object virtual ~SolidMechanics() {} @@ -813,6 +815,7 @@ class SolidMechanics, std::integer_se * @tparam active_parameters a list of indices, describing which parameters to pass to the q-function * @tparam StateType the type that contains the internal variables (if any) for q-function * @param qfunction a callable that returns a tuple of body-force and stress + * @param domain which elements should evaluate the provided qfunction * @param qdata the buffer of material internal variables at each quadrature point * * ~~~ {.cpp} @@ -837,11 +840,11 @@ class SolidMechanics, std::integer_se * @note This method must be called prior to completeSetup() */ template - void addCustomDomainIntegral(DependsOn, callable qfunction, + void addCustomDomainIntegral(DependsOn, callable qfunction, Domain& domain, qdata_type qdata = NoQData) { residual_->AddDomainIntegral(Dimension{}, DependsOn<0, 1, active_parameters + NUM_STATE_VARS...>{}, qfunction, - mesh_, qdata); + domain, qdata); } /** @@ -910,7 +913,7 @@ class SolidMechanics, std::integer_se * @note This method must be called prior to completeSetup() */ template - void setMaterial(DependsOn, const MaterialType& material, const Domain& domain, + void setMaterial(DependsOn, const MaterialType& material, Domain& domain, qdata_type qdata = EmptyQData) { static_assert(std::is_same_v || std::is_same_v, @@ -926,29 +929,14 @@ class SolidMechanics, std::integer_se std::move(material_functor), domain, qdata); } - /// @overload - template - void setMaterial(DependsOn, const MaterialType& material, - qdata_type qdata = EmptyQData) - { - setMaterial(DependsOn{}, material, EntireDomain(mesh_), qdata); - } - /// @overload template - void setMaterial(const MaterialType& material, const Domain& domain, + void setMaterial(const MaterialType& material, Domain& domain, std::shared_ptr> qdata = EmptyQData) { setMaterial(DependsOn<>{}, material, domain, qdata); } - /// @overload - template - void setMaterial(const MaterialType& material, std::shared_ptr> qdata = EmptyQData) - { - setMaterial(DependsOn<>{}, material, EntireDomain(mesh_), qdata); - } - /** * @brief Set the underlying finite element state to a prescribed displacement * @@ -1016,8 +1004,8 @@ class SolidMechanics, std::integer_se * * @tparam BodyForceType The type of the body force load * @param body_force A function describing the body force applied - * @param optional_domain The domain over which the body force is applied. If nothing is supplied the entire domain is - * used. + * @param domain which part of the mesh to apply the body force to + * * @pre body_force must be a object that can be called with the following arguments: * 1. `tensor x` the spatial coordinates for the quadrature point * 2. `double t` the time (note: time will be handled differently in the future) @@ -1031,19 +1019,17 @@ class SolidMechanics, std::integer_se * @note This method must be called prior to completeSetup() */ template - void addBodyForce(DependsOn, BodyForceType body_force, - const std::optional& optional_domain = std::nullopt) + void addBodyForce(DependsOn, BodyForceType body_force, Domain& domain) { - Domain domain = (optional_domain) ? *optional_domain : EntireDomain(mesh_); residual_->AddDomainIntegral(Dimension{}, DependsOn<0, 1, active_parameters + NUM_STATE_VARS...>{}, BodyForceIntegrand(body_force), domain); } /// @overload template - void addBodyForce(BodyForceType body_force, const std::optional& optional_domain = std::nullopt) + void addBodyForce(BodyForceType body_force, Domain& domain) { - addBodyForce(DependsOn<>{}, body_force, optional_domain); + addBodyForce(DependsOn<>{}, body_force, domain); } /** @@ -1051,8 +1037,8 @@ class SolidMechanics, std::integer_se * * @tparam TractionType The type of the traction load * @param traction_function A function describing the traction applied to a boundary - * @param optional_domain The domain over which the traction is applied. If nothing is supplied the entire boundary is - * used. + * @param domain The domain over which the traction is applied. + * * @pre TractionType must be a object that can be called with the following arguments: * 1. `tensor x` the spatial coordinates for the quadrature point * 2. `tensor n` the outward-facing unit normal for the quadrature point @@ -1070,11 +1056,8 @@ class SolidMechanics, std::integer_se * @note This method must be called prior to completeSetup() */ template - void setTraction(DependsOn, TractionType traction_function, - const std::optional& optional_domain = std::nullopt) + void setTraction(DependsOn, TractionType traction_function, Domain& domain) { - Domain domain = (optional_domain) ? *optional_domain : EntireBoundary(mesh_); - residual_->AddBoundaryIntegral( Dimension{}, DependsOn<0, 1, active_parameters + NUM_STATE_VARS...>{}, [traction_function](double t, auto X, auto /* displacement */, auto /* acceleration */, auto... params) { @@ -1087,9 +1070,9 @@ class SolidMechanics, std::integer_se /// @overload template - void setTraction(TractionType traction_function, const std::optional& optional_domain = std::nullopt) + void setTraction(TractionType traction_function, Domain& domain) { - setTraction(DependsOn<>{}, traction_function, optional_domain); + setTraction(DependsOn<>{}, traction_function, domain); } /** @@ -1097,8 +1080,8 @@ class SolidMechanics, std::integer_se * * @tparam PressureType The type of the pressure load * @param pressure_function A function describing the pressure applied to a boundary - * @param optional_domain The domain over which the pressure is applied. If nothing is supplied the entire boundary is - * used. + * @param domain The domain over which the pressure is applied. + * * @pre PressureType must be a object that can be called with the following arguments: * 1. `tensor x` the reference configuration spatial coordinates for the quadrature point * 2. `double t` the time (note: time will be handled differently in the future) @@ -1116,11 +1099,8 @@ class SolidMechanics, std::integer_se * @note This method must be called prior to completeSetup() */ template - void setPressure(DependsOn, PressureType pressure_function, - const std::optional& optional_domain = std::nullopt) + void setPressure(DependsOn, PressureType pressure_function, Domain& domain) { - Domain domain = (optional_domain) ? *optional_domain : EntireBoundary(mesh_); - residual_->AddBoundaryIntegral( Dimension{}, DependsOn<0, 1, active_parameters + NUM_STATE_VARS...>{}, [pressure_function](double t, auto X, auto displacement, auto /* acceleration */, auto... params) { @@ -1148,9 +1128,9 @@ class SolidMechanics, std::integer_se /// @overload template - void setPressure(PressureType pressure_function, const std::optional& optional_domain = std::nullopt) + void setPressure(PressureType pressure_function, Domain& domain) { - setPressure(DependsOn<>{}, pressure_function, optional_domain); + setPressure(DependsOn<>{}, pressure_function, domain); } /// @brief Build the quasi-static operator corresponding to the total Lagrangian formulation diff --git a/src/serac/physics/state/finite_element_vector.hpp b/src/serac/physics/state/finite_element_vector.hpp index a91cf7d41..399b34ce5 100644 --- a/src/serac/physics/state/finite_element_vector.hpp +++ b/src/serac/physics/state/finite_element_vector.hpp @@ -61,10 +61,12 @@ class FiniteElementVector : public mfem::HypreParVector { * * @tparam FunctionSpace what kind of interpolating functions to use * @param mesh The mesh used to construct the finite element state + * @param f metadata describing the kind of function space to define * @param name The name of the new finite element state field */ template - FiniteElementVector(mfem::ParMesh& mesh, FunctionSpace, const std::string& name = "") : mesh_(mesh), name_(name) + FiniteElementVector(mfem::ParMesh& mesh, [[maybe_unused]] FunctionSpace f, const std::string& name = "") + : mesh_(mesh), name_(name) { std::tie(space_, coll_) = serac::generateParFiniteElementSpace(&mesh); diff --git a/src/serac/physics/tests/beam_bending.cpp b/src/serac/physics/tests/beam_bending.cpp index 557bb7a21..459fc7deb 100644 --- a/src/serac/physics/tests/beam_bending.cpp +++ b/src/serac/physics/tests/beam_bending.cpp @@ -40,7 +40,7 @@ TEST(BeamBending, TwoDimensional) std::string mesh_tag{"mesh"}; - serac::StateManager::setMesh(std::move(mesh), mesh_tag); + auto& pmesh = serac::StateManager::setMesh(std::move(mesh), mesh_tag); serac::LinearSolverOptions linear_options{.linear_solver = LinearSolver::GMRES, .preconditioner = Preconditioner::HypreAMG, @@ -69,7 +69,8 @@ TEST(BeamBending, TwoDimensional) double K = 1.91666666666667; double G = 1.0; solid_mechanics::StVenantKirchhoff mat{1.0, K, G}; - solid_solver.setMaterial(mat); + Domain material_block = EntireDomain(pmesh); + solid_solver.setMaterial(mat, material_block); // Define the function for the initial displacement and boundary condition auto bc = [](const mfem::Vector&, mfem::Vector& bc_vec) -> void { bc_vec = 0.0; }; @@ -79,8 +80,10 @@ TEST(BeamBending, TwoDimensional) solid_solver.setDisplacementBCs(ess_bdr, bc); solid_solver.setDisplacement(bc); - solid_solver.setTraction([](const auto& x, const auto& n, const double) { return -0.01 * n * (x[1] > 0.99); }, - EntireBoundary(StateManager::mesh(mesh_tag))); + Domain top_face = Domain::ofBoundaryElements( + pmesh, [](std::vector vertices, int /*attr*/) { return (average(vertices)[1] > 0.99); }); + + solid_solver.setTraction([](auto /*x*/, auto n, auto /*t*/) { return -0.01 * n; }, top_face); // Finalize the data structures solid_solver.completeSetup(); diff --git a/src/serac/physics/tests/contact_beam.cpp b/src/serac/physics/tests/contact_beam.cpp index 5e7015859..daf071884 100644 --- a/src/serac/physics/tests/contact_beam.cpp +++ b/src/serac/physics/tests/contact_beam.cpp @@ -39,8 +39,8 @@ TEST_P(ContactTest, beam) // Construct the appropriate dimension mesh and give it to the data store std::string filename = SERAC_REPO_DIR "/data/meshes/beam-hex-with-contact-block.mesh"; - auto mesh = mesh::refineAndDistribute(buildMeshFromFile(filename), 1, 0); - StateManager::setMesh(std::move(mesh), "beam_mesh"); + auto mesh = mesh::refineAndDistribute(buildMeshFromFile(filename), 1, 0); + auto& pmesh = serac::StateManager::setMesh(std::move(mesh), "beam_mesh"); LinearSolverOptions linear_options{.linear_solver = LinearSolver::Strumpack, .print_level = 1}; #ifndef MFEM_USE_STRUMPACK @@ -71,7 +71,8 @@ TEST_P(ContactTest, beam) double K = 10.0; double G = 0.25; solid_mechanics::NeoHookean mat{1.0, K, G}; - solid_solver.setMaterial(mat); + Domain material_block = EntireDomain(pmesh); + solid_solver.setMaterial(mat, material_block); // Pass the BC information to the solver object solid_solver.setDisplacementBCs({1}, [](const mfem::Vector&, mfem::Vector& u) { diff --git a/src/serac/physics/tests/contact_patch.cpp b/src/serac/physics/tests/contact_patch.cpp index 7417c3f86..6cbdcfba0 100644 --- a/src/serac/physics/tests/contact_patch.cpp +++ b/src/serac/physics/tests/contact_patch.cpp @@ -41,8 +41,8 @@ TEST_P(ContactTest, patch) // Construct the appropriate dimension mesh and give it to the data store std::string filename = SERAC_REPO_DIR "/data/meshes/twohex_for_contact.mesh"; - auto mesh = mesh::refineAndDistribute(buildMeshFromFile(filename), 2, 0); - StateManager::setMesh(std::move(mesh), "patch_mesh"); + auto mesh = mesh::refineAndDistribute(buildMeshFromFile(filename), 2, 0); + auto& pmesh = serac::StateManager::setMesh(std::move(mesh), "patch_mesh"); #ifdef SERAC_USE_PETSC LinearSolverOptions linear_options{ @@ -78,7 +78,8 @@ TEST_P(ContactTest, patch) double K = 10.0; double G = 0.25; solid_mechanics::NeoHookean mat{1.0, K, G}; - solid_solver.setMaterial(mat); + Domain material_block = EntireDomain(pmesh); + solid_solver.setMaterial(mat, material_block); // Define the function for the initial displacement and boundary condition auto zero_disp_bc = [](const mfem::Vector&) { return 0.0; }; diff --git a/src/serac/physics/tests/contact_patch_tied.cpp b/src/serac/physics/tests/contact_patch_tied.cpp index 600cbf439..0eb767cf8 100644 --- a/src/serac/physics/tests/contact_patch_tied.cpp +++ b/src/serac/physics/tests/contact_patch_tied.cpp @@ -41,8 +41,8 @@ TEST_P(ContactPatchTied, patch) // Construct the appropriate dimension mesh and give it to the data store std::string filename = SERAC_REPO_DIR "/data/meshes/twohex_for_contact.mesh"; - auto mesh = mesh::refineAndDistribute(buildMeshFromFile(filename), 3, 0); - StateManager::setMesh(std::move(mesh), "patch_mesh"); + auto mesh = mesh::refineAndDistribute(buildMeshFromFile(filename), 3, 0); + auto& pmesh = serac::StateManager::setMesh(std::move(mesh), "patch_mesh"); // TODO: investigate performance with Petsc // #ifdef SERAC_USE_PETSC @@ -79,7 +79,8 @@ TEST_P(ContactPatchTied, patch) double K = 10.0; double G = 0.25; solid_mechanics::NeoHookean mat{1.0, K, G}; - solid_solver.setMaterial(mat); + Domain material_block = EntireDomain(pmesh); + solid_solver.setMaterial(mat, material_block); // Define the function for the initial displacement and boundary condition auto zero_disp_bc = [](const mfem::Vector&) { return 0.0; }; diff --git a/src/serac/physics/tests/dynamic_solid_adjoint.cpp b/src/serac/physics/tests/dynamic_solid_adjoint.cpp index 567ba2806..685cabeeb 100644 --- a/src/serac/physics/tests/dynamic_solid_adjoint.cpp +++ b/src/serac/physics/tests/dynamic_solid_adjoint.cpp @@ -87,7 +87,8 @@ void applyInitialAndBoundaryConditions(SolidMechanics& solid_solver) } std::unique_ptr> createNonlinearSolidMechanicsSolver( - const NonlinearSolverOptions& nonlinear_opts, const TimesteppingOptions& dyn_opts, const SolidMaterial& mat) + const NonlinearSolverOptions& nonlinear_opts, const TimesteppingOptions& dyn_opts, const SolidMaterial& mat, + Domain& whole_domain) { static int iter = 0; const LinearSolverOptions linear_options = {.linear_solver = LinearSolver::CG, @@ -101,16 +102,18 @@ std::unique_ptr> createNonlinearSolidMechanicsSolver( auto solid = std::make_unique>(nonlinear_opts, linear_options, dyn_opts, physics_prefix + std::to_string(iter++), mesh_tag, std::vector{}, 0, 0.0, checkpoint_to_disk, false); - solid->setMaterial(mat); + solid->setMaterial(mat, whole_domain); solid->setDisplacementBCs( {1}, [](const mfem::Vector&, double t, mfem::Vector& disp) { disp = (1.0 + 10 * t) * boundary_disp; }); - solid->addBodyForce([](auto X, auto t) { - auto Y = X; - Y[0] = 0.1 + 0.1 * X[0] + 0.3 * X[1] - 0.2 * t; - Y[1] = -0.05 - 0.08 * X[0] + 0.15 * X[1] + 0.3 * t; - return 0.4 * X + Y; - }); + solid->addBodyForce( + [](auto X, auto t) { + auto Y = X; + Y[0] = 0.1 + 0.1 * X[0] + 0.3 * X[1] - 0.2 * t; + Y[1] = -0.05 - 0.08 * X[0] + 0.15 * X[1] + 0.3 * t; + return 0.4 * X + Y; + }, + whole_domain); solid->completeSetup(); applyInitialAndBoundaryConditions(*solid); @@ -273,7 +276,8 @@ struct SolidMechanicsSensitivityFixture : public ::testing::Test { TEST_F(SolidMechanicsSensitivityFixture, InitialDisplacementSensitivities) { - auto solid_solver = createNonlinearSolidMechanicsSolver(nonlinear_opts, dyn_opts, mat); + Domain whole_domain = EntireDomain(*mesh); + auto solid_solver = createNonlinearSolidMechanicsSolver(nonlinear_opts, dyn_opts, mat, whole_domain); auto [qoi_base, init_disp_sensitivity, _, __] = computeSolidMechanicsQoiSensitivities(*solid_solver, tsInfo); solid_solver->resetStates(); @@ -290,7 +294,8 @@ TEST_F(SolidMechanicsSensitivityFixture, InitialDisplacementSensitivities) TEST_F(SolidMechanicsSensitivityFixture, InitialVelocitySensitivities) { - auto solid_solver = createNonlinearSolidMechanicsSolver(nonlinear_opts, dyn_opts, mat); + Domain whole_domain = EntireDomain(*mesh); + auto solid_solver = createNonlinearSolidMechanicsSolver(nonlinear_opts, dyn_opts, mat, whole_domain); auto [qoi_base, _, init_velo_sensitivity, __] = computeSolidMechanicsQoiSensitivities(*solid_solver, tsInfo); solid_solver->resetStates(); @@ -306,7 +311,8 @@ TEST_F(SolidMechanicsSensitivityFixture, InitialVelocitySensitivities) TEST_F(SolidMechanicsSensitivityFixture, ShapeSensitivities) { - auto solid_solver = createNonlinearSolidMechanicsSolver(nonlinear_opts, dyn_opts, mat); + Domain whole_domain = EntireDomain(*mesh); + auto solid_solver = createNonlinearSolidMechanicsSolver(nonlinear_opts, dyn_opts, mat, whole_domain); auto [qoi_base, _, __, shape_sensitivity] = computeSolidMechanicsQoiSensitivities(*solid_solver, tsInfo); solid_solver->resetStates(); @@ -322,8 +328,9 @@ TEST_F(SolidMechanicsSensitivityFixture, ShapeSensitivities) TEST_F(SolidMechanicsSensitivityFixture, QuasiStaticShapeSensitivities) { - dyn_opts.timestepper = TimestepMethod::QuasiStatic; - auto solid_solver = createNonlinearSolidMechanicsSolver(nonlinear_opts, dyn_opts, mat); + Domain whole_domain = EntireDomain(*mesh); + dyn_opts.timestepper = TimestepMethod::QuasiStatic; + auto solid_solver = createNonlinearSolidMechanicsSolver(nonlinear_opts, dyn_opts, mat, whole_domain); auto [qoi_base, _, __, shape_sensitivity] = computeSolidMechanicsQoiSensitivities(*solid_solver, tsInfo); solid_solver->resetStates(); @@ -339,7 +346,8 @@ TEST_F(SolidMechanicsSensitivityFixture, QuasiStaticShapeSensitivities) TEST_F(SolidMechanicsSensitivityFixture, WhenShapeSensitivitiesCalledTwice_GetSameObjectiveAndGradient) { - auto solid_solver = createNonlinearSolidMechanicsSolver(nonlinear_opts, dyn_opts, mat); + Domain whole_domain = EntireDomain(*mesh); + auto solid_solver = createNonlinearSolidMechanicsSolver(nonlinear_opts, dyn_opts, mat, whole_domain); auto [qoi1, _, __, shape_sensitivity1] = computeSolidMechanicsQoiSensitivities(*solid_solver, tsInfo); solid_solver->resetStates(); diff --git a/src/serac/physics/tests/dynamic_thermal_adjoint.cpp b/src/serac/physics/tests/dynamic_thermal_adjoint.cpp index 8b5f833ff..463c787b9 100644 --- a/src/serac/physics/tests/dynamic_thermal_adjoint.cpp +++ b/src/serac/physics/tests/dynamic_thermal_adjoint.cpp @@ -52,16 +52,17 @@ static int iter = 0; std::unique_ptr> createNonlinearHeatTransfer( axom::sidre::DataStore& /*data_store*/, const NonlinearSolverOptions& nonlinear_opts, const TimesteppingOptions& dyn_opts, - const heat_transfer::IsotropicConductorWithLinearConductivityVsTemperature& mat) + const heat_transfer::IsotropicConductorWithLinearConductivityVsTemperature& mat, Domain& whole_domain) { // Note that we are testing the non-default checkpoint to disk capability here auto thermal = std::make_unique>(nonlinear_opts, heat_transfer::direct_linear_options, dyn_opts, thermal_prefix + std::to_string(iter++), mesh_tag, std::vector{}, 0, 0.0); - thermal->setMaterial(mat); + thermal->setMaterial(mat, whole_domain); + thermal->setSource([](auto /* X */, auto /* time */, auto /* u */, auto /* du_dx */) { return 1.0; }, whole_domain); + thermal->setTemperature([](const mfem::Vector&, double) { return 0.0; }); thermal->setTemperatureBCs({1}, [](const mfem::Vector&, double) { return 0.0; }); - thermal->setSource([](auto /* X */, auto /* time */, auto /* u */, auto /* du_dx */) { return 1.0; }); thermal->completeSetup(); return thermal; } @@ -70,7 +71,8 @@ using ParametrizedHeatTransferT = HeatTransfer>, std::i std::unique_ptr createParameterizedHeatTransfer( axom::sidre::DataStore& /*data_store*/, const NonlinearSolverOptions& nonlinear_opts, - const TimesteppingOptions& dyn_opts, const heat_transfer::ParameterizedLinearIsotropicConductor& mat) + const TimesteppingOptions& dyn_opts, const heat_transfer::ParameterizedLinearIsotropicConductor& mat, + Domain& whole_domain) { std::vector names{"conductivity"}; @@ -81,11 +83,10 @@ std::unique_ptr createParameterizedHeatTransfer( FiniteElementState user_defined_conductivity(StateManager::mesh(mesh_tag), H1

{}, "user_defined_conductivity"); user_defined_conductivity = 1.1; thermal->setParameter(0, user_defined_conductivity); - thermal->setMaterial(DependsOn<0>{}, mat); + thermal->setMaterial(DependsOn<0>{}, mat, whole_domain); + thermal->setSource([](auto /* X */, auto /* time */, auto /* u */, auto /* du_dx */) { return 1.0; }, whole_domain); thermal->setTemperature([](const mfem::Vector&, double) { return 0.0; }); thermal->setTemperatureBCs({1}, [](const mfem::Vector&, double) { return 0.0; }); - thermal->setSource([](auto /* X */, auto /* time */, auto /* u */, auto /* du_dx */) { return 1.0; }, - EntireDomain(StateManager::mesh(mesh_tag))); thermal->completeSetup(); return thermal; } @@ -93,7 +94,7 @@ std::unique_ptr createParameterizedHeatTransfer( std::unique_ptr createParameterizedNonlinearHeatTransfer( axom::sidre::DataStore& /*data_store*/, const NonlinearSolverOptions& nonlinear_opts, const TimesteppingOptions& dyn_opts, - const heat_transfer::ParameterizedIsotropicConductorWithLinearConductivityVsTemperature& mat) + const heat_transfer::ParameterizedIsotropicConductorWithLinearConductivityVsTemperature& mat, Domain& whole_domain) { std::vector names{"conductivity"}; @@ -104,10 +105,12 @@ std::unique_ptr createParameterizedNonlinearHeatTrans FiniteElementState user_defined_conductivity(StateManager::mesh(mesh_tag), H1

{}, "user_defined_conductivity"); user_defined_conductivity = 1.1; thermal->setParameter(0, user_defined_conductivity); - thermal->setMaterial(DependsOn<0>{}, mat); + + thermal->setMaterial(DependsOn<0>{}, mat, whole_domain); + thermal->setSource([](auto /* X */, auto /* time */, auto /* u */, auto /* du_dx */) { return 1.0; }, whole_domain); + thermal->setTemperature([](const mfem::Vector&, double) { return 0.0; }); thermal->setTemperatureBCs({1}, [](const mfem::Vector&, double) { return 0.0; }); - thermal->setSource([](auto /* X */, auto /* time */, auto /* u */, auto /* du_dx */) { return 1.0; }); thermal->completeSetup(); return thermal; } @@ -261,7 +264,8 @@ struct HeatTransferSensitivityFixture : public ::testing::Test { TEST_F(HeatTransferSensitivityFixture, InitialTemperatureSensitivities) { - auto thermal_solver = createNonlinearHeatTransfer(data_store, nonlinear_opts, dyn_opts, nonlinearMat); + Domain whole_domain = EntireDomain(*mesh); + auto thermal_solver = createNonlinearHeatTransfer(data_store, nonlinear_opts, dyn_opts, nonlinearMat, whole_domain); auto [qoi_base, temperature_sensitivity, _] = computeThermalQoiAndInitialTemperatureAndShapeSensitivity(*thermal_solver, tsInfo); @@ -279,7 +283,8 @@ TEST_F(HeatTransferSensitivityFixture, InitialTemperatureSensitivities) TEST_F(HeatTransferSensitivityFixture, ShapeSensitivities) { - auto thermal_solver = createNonlinearHeatTransfer(data_store, nonlinear_opts, dyn_opts, nonlinearMat); + Domain whole_domain = EntireDomain(*mesh); + auto thermal_solver = createNonlinearHeatTransfer(data_store, nonlinear_opts, dyn_opts, nonlinearMat, whole_domain); auto [qoi_base, _, shape_sensitivity] = computeThermalQoiAndInitialTemperatureAndShapeSensitivity(*thermal_solver, tsInfo); @@ -296,7 +301,9 @@ TEST_F(HeatTransferSensitivityFixture, ShapeSensitivities) TEST_F(HeatTransferSensitivityFixture, ConductivityParameterSensitivities) { - auto thermal_solver = createParameterizedHeatTransfer(data_store, nonlinear_opts, dyn_opts, parameterizedMat); + Domain whole_domain = EntireDomain(*mesh); + auto thermal_solver = + createParameterizedHeatTransfer(data_store, nonlinear_opts, dyn_opts, parameterizedMat, whole_domain); auto [qoi_base, conductivity_sensitivity] = computeThermalConductivitySensitivity(*thermal_solver, tsInfo); thermal_solver->resetStates(); @@ -311,8 +318,9 @@ TEST_F(HeatTransferSensitivityFixture, ConductivityParameterSensitivities) TEST_F(HeatTransferSensitivityFixture, NonlinearConductivityParameterSensitivities) { - auto thermal_solver = - createParameterizedNonlinearHeatTransfer(data_store, nonlinear_opts, dyn_opts, parameterizedNonlinearMat); + Domain whole_domain = EntireDomain(*mesh); + auto thermal_solver = createParameterizedNonlinearHeatTransfer(data_store, nonlinear_opts, dyn_opts, + parameterizedNonlinearMat, whole_domain); auto [qoi_base, conductivity_sensitivity] = computeThermalConductivitySensitivity(*thermal_solver, tsInfo); thermal_solver->resetStates(); diff --git a/src/serac/physics/tests/finite_element_vector_set_over_domain.cpp b/src/serac/physics/tests/finite_element_vector_set_over_domain.cpp index 795219e9a..ca860f000 100644 --- a/src/serac/physics/tests/finite_element_vector_set_over_domain.cpp +++ b/src/serac/physics/tests/finite_element_vector_set_over_domain.cpp @@ -18,16 +18,6 @@ namespace serac { -template -tensor average(std::vector >& positions) -{ - tensor total{}; - for (auto x : positions) { - total += x; - } - return total / double(positions.size()); -} - TEST(FiniteElementVector, SetScalarFieldOver2DDomain) { MPI_Barrier(MPI_COMM_WORLD); diff --git a/src/serac/physics/tests/fit_test.cpp b/src/serac/physics/tests/fit_test.cpp index 3af17185f..adcc7aa67 100644 --- a/src/serac/physics/tests/fit_test.cpp +++ b/src/serac/physics/tests/fit_test.cpp @@ -67,7 +67,9 @@ void stress_extrapolation_test() 50.0 // shear modulus }; - solid_solver.setMaterial(mat); + Domain whole_domain = EntireDomain(pmesh); + + solid_solver.setMaterial(mat, whole_domain); // prescribe small displacement at each hole, pulling the plate apart std::set top_hole = {2}; diff --git a/src/serac/physics/tests/lce_Bertoldi_lattice.cpp b/src/serac/physics/tests/lce_Bertoldi_lattice.cpp index cf1df68ac..1edf2c056 100644 --- a/src/serac/physics/tests/lce_Bertoldi_lattice.cpp +++ b/src/serac/physics/tests/lce_Bertoldi_lattice.cpp @@ -56,7 +56,7 @@ TEST(LiquidCrystalElastomer, Bertoldi) std::string mesh_tag{"mesh"}; - serac::StateManager::setMesh(std::move(mesh), mesh_tag); + auto& pmesh = serac::StateManager::setMesh(std::move(mesh), mesh_tag); // Construct a functional-based solid mechanics solver LinearSolverOptions linear_options = {.linear_solver = LinearSolver::SuperLU}; @@ -116,7 +116,8 @@ TEST(LiquidCrystalElastomer, Bertoldi) // Set material LiquidCrystalElastomerBertoldi lceMat(density, young_modulus, possion_ratio, max_order_param, beta_param); - solid_solver.setMaterial(DependsOn{}, lceMat); + Domain whole_domain = EntireDomain(pmesh); + solid_solver.setMaterial(DependsOn{}, lceMat, whole_domain); // Boundary conditions: // Prescribe zero displacement at the supported end of the beam diff --git a/src/serac/physics/tests/lce_Brighenti_tensile.cpp b/src/serac/physics/tests/lce_Brighenti_tensile.cpp index 1e32beb52..07dbcac8f 100644 --- a/src/serac/physics/tests/lce_Brighenti_tensile.cpp +++ b/src/serac/physics/tests/lce_Brighenti_tensile.cpp @@ -117,8 +117,9 @@ TEST(LiquidCrystalElastomer, Brighenti) transition_temperature, Nb2); LiquidCrystElastomerBrighenti::State initial_state{}; - auto qdata = solid_solver.createQuadratureDataBuffer(initial_state); - solid_solver.setMaterial(DependsOn{}, mat, qdata); + auto qdata = solid_solver.createQuadratureDataBuffer(initial_state); + Domain whole_mesh = EntireDomain(pmesh); + solid_solver.setMaterial(DependsOn{}, mat, whole_mesh, qdata); // prescribe symmetry conditions auto zeroFunc = [](const mfem::Vector /*x*/) { return 0.0; }; @@ -132,11 +133,15 @@ TEST(LiquidCrystalElastomer, Brighenti) double iniLoadVal = 1.0e0; double maxLoadVal = 4 * 1.3e0 / lx / lz; double loadVal = iniLoadVal + 0.0 * maxLoadVal; + + Domain front_face = Domain::ofBoundaryElements( + pmesh, [ly](std::vector vertices, int /* attr */) { return average(vertices)[1] > 0.99 * ly; }); + solid_solver.setTraction( - [&loadVal, ly](auto x, auto /*n*/, auto /*t*/) { - return tensor{0, loadVal * (x[1] > 0.99 * ly), 0}; + [&loadVal](auto /*x*/, auto /*n*/, auto /*t*/) { + return tensor{0, loadVal, 0}; }, - EntireBoundary(pmesh)); + front_face); solid_solver.setDisplacement(ini_displacement); @@ -155,18 +160,13 @@ TEST(LiquidCrystalElastomer, Brighenti) auto [X, dX_dxi] = position; auto [u, du_dxi] = displacement; auto n = normalize(cross(dX_dxi)); - return dot(u, n) * ((X[1] > 0.99 * ly) ? 1.0 : 0.0); + return dot(u, n); }, - pmesh); + front_face); Functional)> area({&solid_solver.displacement().space()}); area.AddSurfaceIntegral( - DependsOn<>{}, - [=](double /*t*/, auto position) { - auto X = get<0>(position); - return (X[1] > 0.99 * ly) ? 1.0 : 0.0; - }, - pmesh); + DependsOn<>{}, [=](double /*t*/, auto /*position*/) { return 1.0; }, front_face); double t = 0.0; double initial_area = area(t, solid_solver.displacement()); diff --git a/src/serac/physics/tests/parameterized_thermal.cpp b/src/serac/physics/tests/parameterized_thermal.cpp index d89174ac3..42f769d74 100644 --- a/src/serac/physics/tests/parameterized_thermal.cpp +++ b/src/serac/physics/tests/parameterized_thermal.cpp @@ -66,9 +66,12 @@ TEST(Thermal, ParameterizedMaterial) thermal_solver.setParameter(0, user_defined_conductivity); + Domain whole_domain = EntireDomain(pmesh); + Domain whole_boundary = EntireBoundary(pmesh); + // Construct a potentially user-defined parameterized material and send it to the thermal module heat_transfer::ParameterizedLinearIsotropicConductor mat; - thermal_solver.setMaterial(DependsOn<0>{}, mat); + thermal_solver.setMaterial(DependsOn<0>{}, mat, whole_domain); // Define the function for the initial temperature and boundary condition auto bdr_temp = [](const mfem::Vector& x, double) -> double { @@ -84,11 +87,11 @@ TEST(Thermal, ParameterizedMaterial) // Define a constant source term heat_transfer::ConstantSource source{-1.0}; - thermal_solver.setSource(source, EntireDomain(pmesh)); + thermal_solver.setSource(source, whole_domain); // Set the flux term to zero for testing code paths heat_transfer::ConstantFlux flux_bc{0.0}; - thermal_solver.setFluxBCs(flux_bc); + thermal_solver.setFluxBCs(flux_bc, whole_boundary); // Finalize the data structures thermal_solver.completeSetup(); diff --git a/src/serac/physics/tests/parameterized_thermomechanics_example.cpp b/src/serac/physics/tests/parameterized_thermomechanics_example.cpp index 19b1d7c62..def0989f4 100644 --- a/src/serac/physics/tests/parameterized_thermomechanics_example.cpp +++ b/src/serac/physics/tests/parameterized_thermomechanics_example.cpp @@ -108,8 +108,8 @@ TEST(Thermomechanics, ParameterizedMaterial) double theta_ref = 0.0; ///< datum temperature for thermal expansion ParameterizedThermoelasticMaterial material{density, E, nu, theta_ref}; - - simulation.setMaterial(DependsOn<0, 1>{}, material); + Domain material_block = EntireDomain(pmesh); + simulation.setMaterial(DependsOn<0, 1>{}, material, material_block); double deltaT = 1.0; FiniteElementState temperature(pmesh, H1

{}, "theta"); @@ -150,8 +150,13 @@ TEST(Thermomechanics, ParameterizedMaterial) simulation.outputStateToDisk("paraview"); - // define quantities of interest + // create Domains to integrate over + Domain top_surface = Domain::ofBoundaryElements(pmesh, [=](std::vector vertices, int /* attr */) { + // select the faces whose "average" z coordinate is close to the top of the mesh + return average(vertices)[2] > 0.99 * height; + }); + // define quantities of interest Functional)> qoi({&simulation.displacement().space()}); qoi.AddSurfaceIntegral( DependsOn<0>{}, @@ -159,9 +164,9 @@ TEST(Thermomechanics, ParameterizedMaterial) auto [X, dX_dxi] = position; auto [u, du_dxi] = displacement; auto n = normalize(cross(dX_dxi)); - return dot(u, n) * ((X[2] > 0.99 * height) ? 1.0 : 0.0); + return dot(u, n); }, - pmesh); + top_surface); double initial_qoi = qoi(time, simulation.displacement()); SLIC_INFO_ROOT(axom::fmt::format("vertical displacement integrated over the top surface: {}", initial_qoi)); @@ -169,12 +174,7 @@ TEST(Thermomechanics, ParameterizedMaterial) Functional)> area({&simulation.displacement().space()}); area.AddSurfaceIntegral( - DependsOn<>{}, - [=](double /*t*/, auto position) { - auto [X, dX_dxi] = position; - return (X[2] > 0.99 * height) ? 1.0 : 0.0; - }, - pmesh); + DependsOn<>{}, [=](double /*t*/, auto /*position*/) { return 1.0; }, top_surface); double top_area = area(time, simulation.displacement()); diff --git a/src/serac/physics/tests/quasistatic_solid_adjoint.cpp b/src/serac/physics/tests/quasistatic_solid_adjoint.cpp index f09c276a1..5bee184c4 100644 --- a/src/serac/physics/tests/quasistatic_solid_adjoint.cpp +++ b/src/serac/physics/tests/quasistatic_solid_adjoint.cpp @@ -157,7 +157,9 @@ TEST(quasistatic, finiteDifference) using materialType = ParameterizedNeoHookeanSolid; materialType material; - seracSolid->setMaterial(::serac::DependsOn<0, 1>{}, material); + + Domain whole_domain = EntireDomain(*meshPtr); + seracSolid->setMaterial(::serac::DependsOn<0, 1>{}, material, whole_domain); seracSolid->setDisplacementBCs( {3}, [](const mfem::Vector&) { return 0.0; }, 0); @@ -195,7 +197,7 @@ TEST(quasistatic, finiteDifference) auto stress = material(state, du_dx, E, v); return stress[2][2] * time; }, - *meshPtr); + whole_domain); int nTimeSteps = 3; double timeStep = 0.8; diff --git a/src/serac/physics/tests/solid.cpp b/src/serac/physics/tests/solid.cpp index c9d7304cf..9962471b1 100644 --- a/src/serac/physics/tests/solid.cpp +++ b/src/serac/physics/tests/solid.cpp @@ -45,7 +45,7 @@ void functional_solid_test_static_J2() std::string mesh_tag{"mesh"}; - serac::StateManager::setMesh(std::move(mesh), mesh_tag); + auto& pmesh = serac::StateManager::setMesh(std::move(mesh), mesh_tag); // _solver_params_start serac::LinearSolverOptions linear_options{.linear_solver = LinearSolver::SuperLU}; @@ -76,7 +76,8 @@ void functional_solid_test_static_J2() auto qdata = solid_solver.createQuadratureDataBuffer(initial_state); - solid_solver.setMaterial(mat, qdata); + Domain whole_domain = EntireDomain(pmesh); + solid_solver.setMaterial(mat, whole_domain, qdata); // prescribe zero displacement at the supported end of the beam, std::set support = {1}; @@ -135,7 +136,7 @@ void functional_solid_spatial_essential_bc() std::string mesh_tag{"mesh"}; - serac::StateManager::setMesh(std::move(mesh), mesh_tag); + auto& pmesh = serac::StateManager::setMesh(std::move(mesh), mesh_tag); // Construct a functional-based solid mechanics solver SolidMechanics solid_solver(solid_mechanics::default_nonlinear_options, @@ -143,7 +144,8 @@ void functional_solid_spatial_essential_bc() solid_mechanics::default_quasistatic_options, "solid_mechanics", mesh_tag); solid_mechanics::LinearIsotropic mat{1.0, 1.0, 1.0}; - solid_solver.setMaterial(mat); + Domain whole_domain = EntireDomain(pmesh); + solid_solver.setMaterial(mat, whole_domain); // Set up auto zero_vector = [](const mfem::Vector&, mfem::Vector& u) { u = 0.0; }; @@ -301,8 +303,11 @@ void functional_parameterized_solid_test(double expected_disp_norm) solid_solver.setParameter(0, user_defined_bulk_modulus); solid_solver.setParameter(1, user_defined_shear_modulus); + Domain whole_domain = EntireDomain(pmesh); + Domain whole_boundary = EntireBoundary(pmesh); + solid_mechanics::ParameterizedLinearIsotropicSolid mat{1.0, 0.0, 0.0}; - solid_solver.setMaterial(DependsOn<0, 1>{}, mat); + solid_solver.setMaterial(DependsOn<0, 1>{}, mat, whole_domain); // Define the function for the initial displacement and boundary condition auto bc = [](const mfem::Vector&, mfem::Vector& bc_vec) -> void { bc_vec = 0.0; }; @@ -331,16 +336,16 @@ void functional_parameterized_solid_test(double expected_disp_norm) } solid_mechanics::ConstantBodyForce force{constant_force}; - solid_solver.addBodyForce(force, EntireDomain(pmesh)); + solid_solver.addBodyForce(force, whole_domain); // add some nonexistent body forces / tractions to check that // these parameterized versions compile and run without error solid_solver.addBodyForce( - DependsOn<0>{}, [](const auto& x, double /*t*/, auto /* bulk */) { return x * 0.0; }, EntireDomain(pmesh)); + DependsOn<0>{}, [](const auto& x, double /*t*/, auto /* bulk */) { return x * 0.0; }, whole_domain); solid_solver.addBodyForce(DependsOn<1>{}, ParameterizedBodyForce{[](const auto& x) { return 0.0 * x; }}, - EntireDomain(pmesh)); + whole_domain); solid_solver.setTraction( - DependsOn<1>{}, [](const auto& x, auto...) { return 0 * x; }, EntireBoundary(pmesh)); + DependsOn<1>{}, [](const auto& x, auto...) { return 0 * x; }, whole_boundary); // Finalize the data structures solid_solver.completeSetup(); diff --git a/src/serac/physics/tests/solid_dynamics_patch.cpp b/src/serac/physics/tests/solid_dynamics_patch.cpp index 30ea7b354..e50d92c21 100644 --- a/src/serac/physics/tests/solid_dynamics_patch.cpp +++ b/src/serac/physics/tests/solid_dynamics_patch.cpp @@ -131,7 +131,8 @@ class AffineSolution { * @param essential_boundaries Boundary attributes on which essential boundary conditions are desired */ template - void applyLoads(const Material& material, SolidMechanics& solid, std::set essential_boundaries) const + void applyLoads(const Material& material, SolidMechanics& solid, std::set essential_boundaries, + Domain& bdr_domain) const { // essential BCs auto ebc_func = [*this](const auto& X, double t, auto& u) { this->operator()(X, t, u); }; @@ -162,7 +163,7 @@ class AffineSolution { auto T = dot(P, n0); return T; }; - solid.setTraction(traction, EntireBoundary(solid.mesh())); + solid.setTraction(traction, bdr_domain); } private: @@ -224,7 +225,8 @@ class ConstantAccelerationSolution { * @param essential_boundaries Boundary attributes on which essential boundary conditions are desired */ template - void applyLoads(const Material& material, SolidMechanics& solid, std::set essential_boundaries) const + void applyLoads(const Material& material, SolidMechanics& solid, std::set essential_boundaries, + Domain& domain) const { // essential BCs auto ebc_func = [*this](const auto& X, double t, auto& u) { this->operator()(X, t, u); }; @@ -234,7 +236,7 @@ class ConstantAccelerationSolution { // body force auto a = make_tensor([*this](int i) { return this->acceleration(i); }); - solid.addBodyForce([&material, a](auto /* X */, auto /* t */) { return material.density * a; }); + solid.addBodyForce([&material, a](auto /* X */, auto /* t */) { return material.density * a; }, domain); } private: @@ -300,7 +302,7 @@ double solution_error(solution_type exact_solution, PatchBoundaryCondition bc) std::string mesh_tag{"mesh"}; - serac::StateManager::setMesh(std::move(mesh), mesh_tag); + auto& pmesh = serac::StateManager::setMesh(std::move(mesh), mesh_tag); // Construct a functional-based solid mechanics solver serac::NonlinearSolverOptions nonlin_opts{.relative_tol = 1.0e-13, .absolute_tol = 1.0e-13}; @@ -310,7 +312,9 @@ double solution_error(solution_type exact_solution, PatchBoundaryCondition bc) "solid_dynamics", mesh_tag); solid_mechanics::NeoHookean mat{.density = 1.0, .K = 1.0, .G = 1.0}; - solid.setMaterial(mat); + Domain whole_domain = EntireDomain(pmesh); + Domain whole_boundary = EntireBoundary(pmesh); + solid.setMaterial(mat, whole_domain); // initial conditions solid.setVelocity([exact_solution](const mfem::Vector& x, mfem::Vector& v) { exact_solution.velocity(x, 0.0, v); }); @@ -318,7 +322,13 @@ double solution_error(solution_type exact_solution, PatchBoundaryCondition bc) solid.setDisplacement([exact_solution](const mfem::Vector& x, mfem::Vector& u) { exact_solution(x, 0.0, u); }); // forcing terms - exact_solution.applyLoads(mat, solid, essentialBoundaryAttributes(bc)); + if constexpr (std::is_same >::value) { + exact_solution.applyLoads(mat, solid, essentialBoundaryAttributes(bc), whole_domain); + } + + if constexpr (std::is_same >::value) { + exact_solution.applyLoads(mat, solid, essentialBoundaryAttributes(bc), whole_boundary); + } // Finalize the data structures solid.completeSetup(); diff --git a/src/serac/physics/tests/solid_finite_diff.cpp b/src/serac/physics/tests/solid_finite_diff.cpp index b700fec3c..5a6ea940c 100644 --- a/src/serac/physics/tests/solid_finite_diff.cpp +++ b/src/serac/physics/tests/solid_finite_diff.cpp @@ -73,7 +73,8 @@ TEST(SolidMechanics, FiniteDifferenceParameter) constexpr int bulk_parameter_index = 0; solid_mechanics::ParameterizedNeoHookeanSolid mat{1.0, 0.0, 0.0}; - solid_solver.setMaterial(DependsOn<0, 1>{}, mat); + Domain whole_mesh = EntireDomain(pmesh); + solid_solver.setMaterial(DependsOn<0, 1>{}, mat, whole_mesh); // Define the function for the initial displacement and boundary condition auto bc = [](const mfem::Vector&, mfem::Vector& bc_vec) -> void { bc_vec = 0.0; }; @@ -93,7 +94,7 @@ TEST(SolidMechanics, FiniteDifferenceParameter) } solid_mechanics::ConstantBodyForce force{constant_force}; - solid_solver.addBodyForce(force, EntireDomain(pmesh)); + solid_solver.addBodyForce(force, whole_mesh); // Finalize the data structures solid_solver.completeSetup(); @@ -221,7 +222,8 @@ void finite_difference_shape_test(LoadingType load) solid_mechanics::default_quasistatic_options, "solid_functional", mesh_tag); solid_mechanics::NeoHookean mat{1.0, 1.0, 1.0}; - solid_solver.setMaterial(mat); + Domain whole_mesh = EntireDomain(pmesh); + solid_solver.setMaterial(mat, whole_mesh); FiniteElementState shape_displacement(pmesh, H1{}); @@ -235,38 +237,20 @@ void finite_difference_shape_test(LoadingType load) solid_solver.setDisplacementBCs(ess_bdr, bc); solid_solver.setDisplacement(bc); - if (load == LoadingType::BodyForce) { - tensor constant_force; + Domain top_face = Domain::ofBoundaryElements(pmesh, [](std::vector vertices, int /*attr*/) { + return average(vertices)[1] > 0.99; // select faces by y-coordinate + }); - constant_force[0] = 0.0; + if (load == LoadingType::BodyForce) { + tensor constant_force{}; constant_force[1] = 1.0e-1; - if (dim == 3) { - constant_force[2] = 0.0; - } - solid_mechanics::ConstantBodyForce force{constant_force}; - solid_solver.addBodyForce(force, EntireDomain(pmesh)); + solid_solver.addBodyForce(force, whole_mesh); } else if (load == LoadingType::Pressure) { - solid_solver.setPressure( - [](auto& X, double) { - if (X[1] > 0.99) { - return 0.1; - } - return 0.0; - }, - EntireBoundary(pmesh)); + solid_solver.setPressure([](auto /*X*/, double /*t*/) { return 0.1; }, top_face); } else if (load == LoadingType::Traction) { - solid_solver.setTraction( - [](auto& X, auto, double) { - auto traction = 0.0 * X; - if (X[1] > 0.99) { - traction[0] = 1.0e-2; - traction[1] = 1.0e-2; - } - return traction; - }, - EntireBoundary(pmesh)); + solid_solver.setTraction([](auto /*X*/, auto /*n*/, double /*t*/) { return vec2{0.01, 0.01}; }, top_face); } // Finalize the data structures diff --git a/src/serac/physics/tests/solid_multi_material.cpp b/src/serac/physics/tests/solid_multi_material.cpp index 9929a280f..b0ab17f41 100644 --- a/src/serac/physics/tests/solid_multi_material.cpp +++ b/src/serac/physics/tests/solid_multi_material.cpp @@ -16,16 +16,6 @@ namespace serac { -template -tensor average(std::vector>& positions) -{ - tensor total{}; - for (auto x : positions) { - total += x; - } - return total / double(positions.size()); -} - TEST(Solid, MultiMaterial) { /* diff --git a/src/serac/physics/tests/solid_periodic.cpp b/src/serac/physics/tests/solid_periodic.cpp index 27e6984e8..fd5ba5e59 100644 --- a/src/serac/physics/tests/solid_periodic.cpp +++ b/src/serac/physics/tests/solid_periodic.cpp @@ -75,8 +75,10 @@ void periodic_test(mfem::Element::Type element_type) solid_solver.setParameter(0, user_defined_bulk_modulus); solid_solver.setParameter(1, user_defined_shear_modulus); + Domain whole_mesh = EntireDomain(pmesh); + solid_mechanics::ParameterizedNeoHookeanSolid mat{1.0, 0.0, 0.0}; - solid_solver.setMaterial(DependsOn<0, 1>{}, mat); + solid_solver.setMaterial(DependsOn<0, 1>{}, mat, whole_mesh); // Boundary conditions: // Prescribe zero displacement at the supported end of the beam @@ -88,17 +90,11 @@ void periodic_test(mfem::Element::Type element_type) auto ini_displacement = [iniDispVal](const mfem::Vector&, mfem::Vector& u) -> void { u = iniDispVal; }; solid_solver.setDisplacement(ini_displacement); - tensor constant_force; - - constant_force[0] = 0.0; + tensor constant_force{}; constant_force[1] = 1.0e-2; - if (dim == 3) { - constant_force[2] = 0.0; - } - solid_mechanics::ConstantBodyForce force{constant_force}; - solid_solver.addBodyForce(force, EntireDomain(pmesh)); + solid_solver.addBodyForce(force, whole_mesh); // Finalize the data structures solid_solver.completeSetup(); diff --git a/src/serac/physics/tests/solid_reaction_adjoint.cpp b/src/serac/physics/tests/solid_reaction_adjoint.cpp index 6b65fea58..bdb187736 100644 --- a/src/serac/physics/tests/solid_reaction_adjoint.cpp +++ b/src/serac/physics/tests/solid_reaction_adjoint.cpp @@ -37,7 +37,7 @@ constexpr double boundary_disp = 0.013; constexpr double shear_modulus_value = 1.0; constexpr double bulk_modulus_value = 1.0; -std::unique_ptr createNonlinearSolidMechanicsSolver(mfem::ParMesh&, +std::unique_ptr createNonlinearSolidMechanicsSolver(mfem::ParMesh& pmesh, const NonlinearSolverOptions& nonlinear_opts, const SolidMaterial& mat) { @@ -57,29 +57,26 @@ std::unique_ptr createNonlinearSolidMechanicsSolver(mfem::Pa solid->setParameter(0, user_defined_bulk_modulus); solid->setParameter(1, user_defined_shear_modulus); - solid->setMaterial(DependsOn<0, 1>{}, mat); + Domain whole_mesh = EntireDomain(pmesh); + + solid->setMaterial(DependsOn<0, 1>{}, mat, whole_mesh); + + solid->addBodyForce( + [](auto X, auto /* t */) { + auto Y = X; + Y[0] = 0.1 + 0.1 * X[0] + 0.3 * X[1]; + Y[1] = -0.05 - 0.2 * X[0] + 0.15 * X[1]; + return 0.1 * X + Y; + }, + whole_mesh); + solid->setDisplacementBCs({1}, [](const mfem::Vector&, mfem::Vector& disp) { disp = boundary_disp; }); - solid->addBodyForce([](auto X, auto /* t */) { - auto Y = X; - Y[0] = 0.1 + 0.1 * X[0] + 0.3 * X[1]; - Y[1] = -0.05 - 0.2 * X[0] + 0.15 * X[1]; - return 0.1 * X + Y; - }); + solid->completeSetup(); return solid; } -template -tensor average(std::vector>& positions) -{ - tensor total{}; - for (auto x : positions) { - total += x; - } - return total / double(positions.size()); -} - FiniteElementState createReactionDirection(const BasePhysics& solid_solver, int direction) { const FiniteElementDual& reactions = solid_solver.dual("reactions"); diff --git a/src/serac/physics/tests/solid_robin_condition.cpp b/src/serac/physics/tests/solid_robin_condition.cpp index 7b90bc252..a5a9a1213 100644 --- a/src/serac/physics/tests/solid_robin_condition.cpp +++ b/src/serac/physics/tests/solid_robin_condition.cpp @@ -41,8 +41,8 @@ void functional_solid_test_robin_condition() std::string mesh_tag{"mesh"}; - auto mesh = mesh::refineAndDistribute(buildMeshFromFile(filename), serial_refinement, parallel_refinement); - serac::StateManager::setMesh(std::move(mesh), mesh_tag); + auto mesh = mesh::refineAndDistribute(buildMeshFromFile(filename), serial_refinement, parallel_refinement); + auto& pmesh = serac::StateManager::setMesh(std::move(mesh), mesh_tag); // _solver_params_start serac::NonlinearSolverOptions nonlinear_options{.nonlin_solver = NonlinearSolver::Newton, @@ -61,7 +61,8 @@ void functional_solid_test_robin_condition() 1.0 // shear modulus }; - solid_solver.setMaterial(mat); + Domain whole_domain = EntireDomain(pmesh); + solid_solver.setMaterial(mat, whole_domain); // prescribe zero displacement in the y- and z-directions // at the supported end of the beam, diff --git a/src/serac/physics/tests/solid_shape.cpp b/src/serac/physics/tests/solid_shape.cpp index 1b90c9aa1..49608d6db 100644 --- a/src/serac/physics/tests/solid_shape.cpp +++ b/src/serac/physics/tests/solid_shape.cpp @@ -120,9 +120,10 @@ void shape_test() solid_solver.setShapeDisplacement(user_defined_shape_displacement); - solid_solver.setMaterial(mat); + Domain whole_mesh = EntireDomain(StateManager::mesh(mesh_tag)); - solid_solver.addBodyForce(force, EntireDomain(StateManager::mesh(mesh_tag))); + solid_solver.setMaterial(mat, whole_mesh); + solid_solver.addBodyForce(force, whole_mesh); // Finalize the data structures solid_solver.completeSetup(); @@ -167,9 +168,10 @@ void shape_test() solid_solver_no_shape.setDisplacementBCs(ess_bdr, bc_pure); solid_solver_no_shape.setDisplacement(bc_pure); - solid_solver_no_shape.setMaterial(mat); + Domain whole_mesh = EntireDomain(StateManager::mesh(new_mesh_tag)); - solid_solver_no_shape.addBodyForce(force, EntireDomain(StateManager::mesh(new_mesh_tag))); + solid_solver_no_shape.setMaterial(mat, whole_mesh); + solid_solver_no_shape.addBodyForce(force, whole_mesh); // Finalize the data structures solid_solver_no_shape.completeSetup(); diff --git a/src/serac/physics/tests/solid_statics_patch.cpp b/src/serac/physics/tests/solid_statics_patch.cpp index 240291891..1198c2a16 100644 --- a/src/serac/physics/tests/solid_statics_patch.cpp +++ b/src/serac/physics/tests/solid_statics_patch.cpp @@ -177,7 +177,7 @@ class ManufacturedSolution { * @param essential_boundaries Boundary attributes on which essential boundary conditions are desired */ template - void applyLoads(const material_type & material, SolidMechanics& sf, std::set essential_boundaries) const + void applyLoads(const material_type & material, SolidMechanics& sf, std::set essential_boundaries, Domain & domain, Domain & boundary) const { // essential BCs auto ebc_func = [*this](const auto& X, auto& u){ this->operator()(X, u); }; @@ -191,7 +191,7 @@ class ManufacturedSolution { return dot(P, n0); }; - sf.setTraction(traction, EntireBoundary(sf.mesh())); + sf.setTraction(traction, boundary); auto bf = [=](auto X, auto) { auto X_val = get_value(X); @@ -206,7 +206,7 @@ class ManufacturedSolution { return divP; }; - sf.addBodyForce(DependsOn<>{}, bf, EntireDomain(sf.mesh())); + sf.addBodyForce(DependsOn<>{}, bf, domain); } @@ -324,9 +324,11 @@ double solution_error(PatchBoundaryCondition bc) SolidMechanics solid(std::move(equation_solver), solid_mechanics::default_quasistatic_options, "solid", mesh_tag); solid_mechanics::NeoHookean mat{.density=1.0, .K=1.0, .G=1.0}; - solid.setMaterial(mat); + Domain domain = EntireDomain(pmesh); + Domain boundary = EntireBoundary(pmesh); + solid.setMaterial(mat, domain); - exact_displacement.applyLoads(mat, solid, essentialBoundaryAttributes(bc)); + exact_displacement.applyLoads(mat, solid, essentialBoundaryAttributes(bc), domain, boundary); // Finalize the data structures solid.completeSetup(); @@ -399,7 +401,9 @@ double pressure_error() SolidMechanics solid(std::move(equation_solver), solid_mechanics::default_quasistatic_options, "solid", mesh_tag); solid_mechanics::NeoHookean mat{.density=1.0, .K=1.0, .G=1.0}; - solid.setMaterial(mat); + Domain material_block = EntireDomain(pmesh); + Domain boundary = EntireBoundary(pmesh); + solid.setMaterial(mat, material_block); typename solid_mechanics::NeoHookean::State state; auto H = make_tensor([](int i, int j) { @@ -416,7 +420,7 @@ double pressure_error() // Set the pressure corresponding to 10% uniaxial strain solid.setPressure([pressure](auto&, double) { return pressure; - }); + }, boundary); // Define the essential boundary conditions corresponding to 10% uniaxial strain everywhere // except the pressure loaded surface diff --git a/src/serac/physics/tests/thermal_dynamics_patch.cpp b/src/serac/physics/tests/thermal_dynamics_patch.cpp index 0dfd6b818..e6bada373 100644 --- a/src/serac/physics/tests/thermal_dynamics_patch.cpp +++ b/src/serac/physics/tests/thermal_dynamics_patch.cpp @@ -99,12 +99,10 @@ double dynamic_solution_error(const ExactSolution& exact_solution, PatchBoundary static_assert(dim == 2 || dim == 3, "Dimension must be 2 or 3 for heat transfer test"); + std::string mesh_tag{"mesh"}; std::string filename = std::string(SERAC_REPO_DIR) + "/data/meshes/patch" + std::to_string(dim) + "D.mesh"; auto mesh = mesh::refineAndDistribute(buildMeshFromFile(filename)); - - std::string mesh_tag{"mesh"}; - - serac::StateManager::setMesh(std::move(mesh), mesh_tag); + auto& pmesh = serac::StateManager::setMesh(std::move(mesh), mesh_tag); // Construct a heat transfer solver NonlinearSolverOptions nonlinear_opts{.relative_tol = 5.0e-13, .absolute_tol = 5.0e-13}; @@ -114,14 +112,17 @@ double dynamic_solution_error(const ExactSolution& exact_solution, PatchBoundary HeatTransfer thermal(nonlinear_opts, heat_transfer::direct_linear_options, dyn_opts, "thermal", mesh_tag); + Domain whole_domain = EntireDomain(pmesh); + Domain whole_boundary = EntireBoundary(pmesh); + heat_transfer::LinearIsotropicConductor mat(1.0, 1.0, 1.0); - thermal.setMaterial(mat); + thermal.setMaterial(mat, whole_domain); // initial conditions thermal.setTemperature([exact_solution](const mfem::Vector& x, double) { return exact_solution(x, 0.0); }); // forcing terms - exact_solution.applyLoads(mat, thermal, essentialBoundaryAttributes(bc)); + exact_solution.applyLoads(mat, thermal, whole_domain, whole_boundary, essentialBoundaryAttributes(bc)); // Finalize the data structures thermal.completeSetup(); @@ -190,7 +191,8 @@ class LinearSolution { * @param essential_boundaries Boundary attributes on which essential boundary conditions are desired */ template - void applyLoads(const Material& material, HeatTransfer& thermal, std::set essential_boundaries) const + void applyLoads(const Material& material, HeatTransfer& thermal, Domain& dom, Domain& bdr, + std::set essential_boundaries) const { // essential BCs auto ebc_func = [*this](const auto& X, double t) { return this->operator()(X, t); }; @@ -205,13 +207,13 @@ class LinearSolution { return dot(flux, n0); }; - thermal.setFluxBCs(flux_function, EntireBoundary(thermal.mesh())); + thermal.setFluxBCs(flux_function, bdr); // volumetric source auto source_function = [temp_rate_grad](auto position, auto /* time */, auto /* u */, auto /* du_dx */) { return dot(get(position), temp_rate_grad); }; - thermal.setSource(source_function, EntireDomain(thermal.mesh())); + thermal.setSource(source_function, dom); } private: diff --git a/src/serac/physics/tests/thermal_finite_diff.cpp b/src/serac/physics/tests/thermal_finite_diff.cpp index 6b69791fe..e967f7074 100644 --- a/src/serac/physics/tests/thermal_finite_diff.cpp +++ b/src/serac/physics/tests/thermal_finite_diff.cpp @@ -65,9 +65,20 @@ TEST(Thermal, FiniteDifference) thermal_solver.setParameter(0, user_defined_conductivity); + Domain whole_domain = EntireDomain(pmesh); + Domain whole_boundary = EntireBoundary(pmesh); + // Construct a potentially user-defined parameterized material and send it to the thermal module heat_transfer::ParameterizedLinearIsotropicConductor mat; - thermal_solver.setMaterial(DependsOn<0>{}, mat); + thermal_solver.setMaterial(DependsOn<0>{}, mat, whole_domain); + + // Define a constant source term + heat_transfer::ConstantSource source{1.0}; + thermal_solver.setSource(source, whole_domain); + + // Set the flux term to zero for testing code paths + heat_transfer::ConstantFlux flux_bc{0.0}; + thermal_solver.setFluxBCs(flux_bc, whole_boundary); // Define the function for the initial temperature and boundary condition auto bdr_temp = [](const mfem::Vector& x, double) -> double { return (x[0] < 0.5 || x[1] < 0.5) ? 1.0 : 0.0; }; @@ -76,14 +87,6 @@ TEST(Thermal, FiniteDifference) thermal_solver.setTemperatureBCs(ess_bdr, bdr_temp); thermal_solver.setTemperature(bdr_temp); - // Define a constant source term - heat_transfer::ConstantSource source{1.0}; - thermal_solver.setSource(source, EntireDomain(pmesh)); - - // Set the flux term to zero for testing code paths - heat_transfer::ConstantFlux flux_bc{0.0}; - thermal_solver.setFluxBCs(flux_bc, EntireBoundary(pmesh)); - // Finalize the data structures thermal_solver.completeSetup(); @@ -190,7 +193,12 @@ TEST(HeatTransfer, FiniteDifferenceShape) heat_transfer::LinearIsotropicConductor mat(1.0, 1.0, 1.0); - thermal_solver.setMaterial(mat); + Domain whole_domain = EntireDomain(pmesh); + + thermal_solver.setMaterial(mat, whole_domain); + + heat_transfer::ConstantSource source{1.0}; + thermal_solver.setSource(source, whole_domain); FiniteElementState shape_displacement(pmesh, H1{}); @@ -204,9 +212,6 @@ TEST(HeatTransfer, FiniteDifferenceShape) thermal_solver.setTemperatureBCs(ess_bdr, one); thermal_solver.setTemperature(one); - heat_transfer::ConstantSource source{1.0}; - thermal_solver.setSource(source, EntireDomain(pmesh)); - // Finalize the data structures thermal_solver.completeSetup(); diff --git a/src/serac/physics/tests/thermal_mechanics.cpp b/src/serac/physics/tests/thermal_mechanics.cpp index b73c7f750..27540ae45 100644 --- a/src/serac/physics/tests/thermal_mechanics.cpp +++ b/src/serac/physics/tests/thermal_mechanics.cpp @@ -40,7 +40,7 @@ void functional_test_static_3D(double expected_norm) std::string mesh_tag{"mesh"}; - serac::StateManager::setMesh(std::move(mesh), mesh_tag); + auto& pmesh = serac::StateManager::setMesh(std::move(mesh), mesh_tag); // Define a boundary attribute set std::set ess_bdr = {1}; @@ -73,7 +73,9 @@ void functional_test_static_3D(double expected_norm) GreenSaintVenantThermoelasticMaterial material{rho, E, nu, c, alpha, theta_ref, k}; GreenSaintVenantThermoelasticMaterial::State initial_state{}; auto qdata = thermal_solid_solver.createQuadratureDataBuffer(initial_state); - thermal_solid_solver.setMaterial(material, qdata); + + Domain whole_domain = EntireDomain(pmesh); + thermal_solid_solver.setMaterial(material, whole_domain, qdata); // Define the function for the initial temperature and boundary condition auto one = [](const mfem::Vector&, double) -> double { return 1.0; }; @@ -122,7 +124,7 @@ void functional_test_shrinking_3D(double expected_norm) std::string mesh_tag{"mesh"}; - serac::StateManager::setMesh(std::move(mesh), mesh_tag); + auto& pmesh = serac::StateManager::setMesh(std::move(mesh), mesh_tag); // Define a boundary attribute set std::set constraint_bdr = {1}; @@ -155,7 +157,9 @@ void functional_test_shrinking_3D(double expected_norm) GreenSaintVenantThermoelasticMaterial material{rho, E, nu, c, alpha, theta_ref, k}; GreenSaintVenantThermoelasticMaterial::State initial_state{}; auto qdata = thermal_solid_solver.createQuadratureDataBuffer(initial_state); - thermal_solid_solver.setMaterial(material, qdata); + + Domain whole_domain = EntireDomain(pmesh); + thermal_solid_solver.setMaterial(material, whole_domain, qdata); // Define the function for the initial temperature double theta_0 = 1.0; diff --git a/src/serac/physics/tests/thermal_nonlinear_solve.cpp b/src/serac/physics/tests/thermal_nonlinear_solve.cpp index 32cc4a1c5..6f2742d7f 100644 --- a/src/serac/physics/tests/thermal_nonlinear_solve.cpp +++ b/src/serac/physics/tests/thermal_nonlinear_solve.cpp @@ -40,8 +40,8 @@ void functional_thermal_test_nonlinear() std::string mesh_tag{"mesh"}; - auto mesh = mesh::refineAndDistribute(buildMeshFromFile(filename), serial_refinement, parallel_refinement); - serac::StateManager::setMesh(std::move(mesh), mesh_tag); + auto mesh = mesh::refineAndDistribute(buildMeshFromFile(filename), serial_refinement, parallel_refinement); + auto& pmesh = serac::StateManager::setMesh(std::move(mesh), mesh_tag); // _solver_params_start serac::NonlinearSolverOptions nonlinear_options{.nonlin_solver = NonlinearSolver::NewtonLineSearch, @@ -60,12 +60,13 @@ void functional_thermal_test_nonlinear() 21.0 // isotropic thermal conductivity }; - thermal_solver.setMaterial(mat); + Domain whole_domain = EntireDomain(pmesh); + Domain whole_boundary = EntireBoundary(pmesh); - // prescribe zero temperature at one end of the beam - std::set support = {1}; - auto zero = [](const mfem::Vector&, double) -> double { return 42.0; }; - thermal_solver.setTemperatureBCs(support, zero); + thermal_solver.setMaterial(mat, whole_domain); + + // set heat source + thermal_solver.setSource([](auto, auto, auto, auto) { return 2.0; }, whole_domain); // clang-format off thermal_solver.addCustomBoundaryIntegral(serac::DependsOn<>{}, [&](auto, auto, auto temperature, auto) { @@ -74,11 +75,13 @@ void functional_thermal_test_nonlinear() using std::pow; auto T = serac::get<0>(temperature); return radiateConstant * (pow(T, 4.0) - pow(T0, 4.0)); - }); + }, whole_boundary); // clang-format on - // set heat source - thermal_solver.setSource([](auto, auto, auto, auto) { return 2.0; }); + // prescribe zero temperature at one end of the beam + std::set support = {1}; + auto zero = [](const mfem::Vector&, double) -> double { return 42.0; }; + thermal_solver.setTemperatureBCs(support, zero); // Finalize the data structures thermal_solver.completeSetup(); diff --git a/src/serac/physics/tests/thermal_robin_condition.cpp b/src/serac/physics/tests/thermal_robin_condition.cpp index 6a2d7b614..6f07ee586 100644 --- a/src/serac/physics/tests/thermal_robin_condition.cpp +++ b/src/serac/physics/tests/thermal_robin_condition.cpp @@ -40,8 +40,8 @@ void functional_thermal_test_robin_condition() std::string mesh_tag{"mesh"}; - auto mesh = mesh::refineAndDistribute(buildMeshFromFile(filename), serial_refinement, parallel_refinement); - serac::StateManager::setMesh(std::move(mesh), mesh_tag); + auto mesh = mesh::refineAndDistribute(buildMeshFromFile(filename), serial_refinement, parallel_refinement); + auto& pmesh = serac::StateManager::setMesh(std::move(mesh), mesh_tag); // _solver_params_start serac::NonlinearSolverOptions nonlinear_options{.nonlin_solver = NonlinearSolver::Newton, @@ -60,24 +60,29 @@ void functional_thermal_test_robin_condition() 1.0 // isotropic thermal conductivity }; - thermal_solver.setMaterial(mat); + Domain whole_domain = EntireDomain(pmesh); + Domain whole_boundary = EntireBoundary(pmesh); - // prescribe zero temperature at one end of the beam - std::set support = {1}; - auto zero = [](const mfem::Vector&, double) -> double { return 0.0; }; - thermal_solver.setTemperatureBCs(support, zero); + thermal_solver.setMaterial(mat, whole_domain); + + // set heat source + thermal_solver.setSource([](auto, auto, auto, auto) { return 2.0; }, whole_domain); // clang-format off thermal_solver.addCustomBoundaryIntegral(DependsOn<>{}, - [](double /* t */, auto /*position*/, auto temperature, auto /*temperature_rate*/) { - auto [T, dT_dxi] = temperature; - auto q = 5.0*(T-25.0); - return q; // define a convective (temperature-proportional) heat flux - }); + [](double /* t */, auto /*position*/, auto temperature, auto /*temperature_rate*/) { + auto [T, dT_dxi] = temperature; + auto q = 5.0*(T-25.0); + return q; // define a convective (temperature-proportional) heat flux + }, + whole_boundary + ); // clang-format on - // set heat source - thermal_solver.setSource([](auto, auto, auto, auto) { return 2.0; }); + // prescribe zero temperature at one end of the beam + std::set support = {1}; + auto zero = [](const mfem::Vector&, double) -> double { return 0.0; }; + thermal_solver.setTemperatureBCs(support, zero); // Finalize the data structures thermal_solver.completeSetup(); diff --git a/src/serac/physics/tests/thermal_shape.cpp b/src/serac/physics/tests/thermal_shape.cpp index bb25ac9e4..afd19b0bb 100644 --- a/src/serac/physics/tests/thermal_shape.cpp +++ b/src/serac/physics/tests/thermal_shape.cpp @@ -94,9 +94,9 @@ TEST(HeatTransfer, MoveShape) shape_displacement.project(shape_coef); thermal_solver.setShapeDisplacement(shape_displacement); - thermal_solver.setMaterial(mat); - - thermal_solver.setSource(source, EntireDomain(StateManager::mesh(mesh_tag))); + Domain whole_domain = EntireDomain(pmesh); + thermal_solver.setMaterial(mat, whole_domain); + thermal_solver.setSource(source, whole_domain); // Finalize the data structures thermal_solver.completeSetup(); @@ -138,9 +138,9 @@ TEST(HeatTransfer, MoveShape) thermal_solver_no_shape.setTemperatureBCs(ess_bdr, zero); thermal_solver_no_shape.setTemperature(zero); - thermal_solver_no_shape.setMaterial(mat); - - thermal_solver_no_shape.setSource(source, EntireDomain(StateManager::mesh(pure_mesh_tag))); + Domain whole_domain = EntireDomain(new_pmesh); + thermal_solver_no_shape.setMaterial(mat, whole_domain); + thermal_solver_no_shape.setSource(source, whole_domain); // Finalize the data structures thermal_solver_no_shape.completeSetup(); diff --git a/src/serac/physics/tests/thermal_statics_patch.cpp b/src/serac/physics/tests/thermal_statics_patch.cpp index 0b0357e99..b802690f2 100644 --- a/src/serac/physics/tests/thermal_statics_patch.cpp +++ b/src/serac/physics/tests/thermal_statics_patch.cpp @@ -68,7 +68,7 @@ class AffineSolution { * @param essential_boundaries Boundary attributes on which essential boundary conditions are desired */ template - void applyLoads(const Material& material, HeatTransfer& physics, std::set essential_boundaries) const + void applyLoads(const Material& material, HeatTransfer& physics, std::set essential_boundaries, Domain & boundary) const { // essential BCs auto ebc_func = [*this](const auto& X, auto){ return this->operator()(X); }; @@ -80,7 +80,7 @@ class AffineSolution { auto flux = serac::get<1>(material(dummy_x, 1.0, temp_grad)); auto surface_flux = [flux](auto, auto n0, auto, auto) { return dot(flux, n0); }; - physics.setFluxBCs(surface_flux); + physics.setFluxBCs(surface_flux, boundary); } private: @@ -167,7 +167,7 @@ double solution_error(const ExactSolution& exact_temperature, PatchBoundaryCondi std::string mesh_tag{"mesh"}; - serac::StateManager::setMesh(std::move(mesh), mesh_tag); + auto & pmesh = serac::StateManager::setMesh(std::move(mesh), mesh_tag); // Construct a heat transfer mechanics solver auto nonlinear_opts = heat_transfer::default_nonlinear_options; @@ -176,9 +176,11 @@ double solution_error(const ExactSolution& exact_temperature, PatchBoundaryCondi HeatTransfer thermal(nonlinear_opts, heat_transfer::direct_linear_options, heat_transfer::default_static_options, "thermal", mesh_tag); heat_transfer::LinearIsotropicConductor mat(1.0,1.0,1.0); - thermal.setMaterial(mat); + Domain whole_domain = EntireDomain(pmesh); + Domain whole_boundary = EntireBoundary(pmesh); + thermal.setMaterial(mat, whole_domain); - exact_temperature.applyLoads(mat, thermal, essentialBoundaryAttributes(bc)); + exact_temperature.applyLoads(mat, thermal, essentialBoundaryAttributes(bc), whole_boundary); // Finalize the data structures thermal.completeSetup(); diff --git a/src/serac/physics/thermomechanics.hpp b/src/serac/physics/thermomechanics.hpp index f0a210c53..688be30af 100644 --- a/src/serac/physics/thermomechanics.hpp +++ b/src/serac/physics/thermomechanics.hpp @@ -352,6 +352,7 @@ class Thermomechanics : public BasePhysics { * @tparam MaterialType The thermomechanical material type * @tparam StateType The type that contains the internal variables for MaterialType * @param material A material that provides a function to evaluate stress, heat flux, density, and heat capacity + * @param domain which elements in the mesh are described by the specified material * @param qdata the buffer of material internal variables at each quadrature point * * @pre material must be a object that can be called with the following arguments: @@ -371,22 +372,24 @@ class Thermomechanics : public BasePhysics { * and thermal flux when operator() is called with the arguments listed above. */ template - void setMaterial(DependsOn, const MaterialType& material, + void setMaterial(DependsOn, const MaterialType& material, Domain& domain, std::shared_ptr> qdata) { // note: these parameter indices are offset by 1 since, internally, this module uses the first parameter // to communicate the temperature and displacement field information to the other physics module // - thermal_.setMaterial(DependsOn<0, active_parameters + 1 ...>{}, ThermalMaterialInterface{material}); + thermal_.setMaterial(DependsOn<0, active_parameters + 1 ...>{}, ThermalMaterialInterface{material}, + domain); solid_.setMaterial(DependsOn<0, active_parameters + 1 ...>{}, MechanicalMaterialInterface{material}, - qdata); + domain, qdata); } /// @overload template - void setMaterial(const MaterialType& material, std::shared_ptr> qdata = EmptyQData) + void setMaterial(const MaterialType& material, Domain& domain, + std::shared_ptr> qdata = EmptyQData) { - setMaterial(DependsOn<>{}, material, qdata); + setMaterial(DependsOn<>{}, material, domain, qdata); } /**