21 #include "serac/numerics/functional/integral.hpp"
22 #include "serac/numerics/functional/dof_numbering.hpp"
23 #include "serac/numerics/functional/differentiate_wrt.hpp"
25 #include "serac/numerics/functional/element_restriction.hpp"
48 template <
typename... T>
51 constexpr uint32_t n =
sizeof...(T);
52 bool matching[] = {std::is_same_v<T, differentiate_wrt_this>...};
53 for (uint32_t i = 0; i < n; i++) {
58 return NO_DIFFERENTIATION;
69 constexpr
operator int() {
return ind; }
75 if (mesh.GetNodes() ==
nullptr) {
78 the provided mesh does not have a nodal gridfunction.
79 If you created an mfem::Mesh manually, make sure that the
80 following member functions are invoked before use
82 > mfem::Mesh::EnsureNodes();
83 > mfem::Mesh::ExchangeFaceNbrData();
85 or else the mfem::Mesh won't be fully initialized
93 int num_elements = mesh.GetNE();
94 for (
int e = 0; e < num_elements; e++) {
95 auto type = mesh.GetElementType(e);
96 if (
type == mfem::Element::POINT ||
type == mfem::Element::WEDGE ||
type == mfem::Element::PYRAMID) {
97 SLIC_ERROR_ROOT(
"Mesh contains unsupported element type");
110 template <
typename function_space>
111 inline std::pair<std::unique_ptr<mfem::ParFiniteElementSpace>, std::unique_ptr<mfem::FiniteElementCollection>>
114 const int dim = mesh->Dimension();
115 std::unique_ptr<mfem::FiniteElementCollection> fec;
116 const auto ordering = mfem::Ordering::byNODES;
118 switch (function_space::family) {
120 fec = std::make_unique<mfem::H1_FECollection>(function_space::order, dim);
123 fec = std::make_unique<mfem::ND_FECollection>(function_space::order, dim);
126 fec = std::make_unique<mfem::RT_FECollection>(function_space::order, dim);
130 fec = std::make_unique<mfem::L2_FECollection>(function_space::order, dim, mfem::BasisType::GaussLobatto);
133 return std::pair<std::unique_ptr<mfem::ParFiniteElementSpace>, std::unique_ptr<mfem::FiniteElementCollection>>(
138 auto fes = std::make_unique<mfem::ParFiniteElementSpace>(mesh, fec.get(), function_space::components, ordering);
140 return std::pair(std::move(fes), std::move(fec));
144 template <
typename T, ExecutionSpace exec = serac::default_execution_space>
189 class Functional<test(trials...), exec> {
190 static constexpr
tuple<trials...> trial_spaces{};
191 static constexpr uint32_t num_trial_spaces =
sizeof...(trials);
192 static constexpr
auto Q =
std::max({test::order, trials::order...}) + 1;
194 static constexpr mfem::Geometry::Type elem_geom[4] = {mfem::Geometry::INVALID, mfem::Geometry::SEGMENT,
195 mfem::Geometry::SQUARE, mfem::Geometry::CUBE};
196 static constexpr mfem::Geometry::Type simplex_geom[4] = {mfem::Geometry::INVALID, mfem::Geometry::SEGMENT,
197 mfem::Geometry::TRIANGLE, mfem::Geometry::TETRAHEDRON};
202 template <u
int32_t i>
203 struct operator_paren_return {
204 using type =
typename std::conditional<
205 i == NO_DIFFERENTIATION,
218 Functional(
const mfem::ParFiniteElementSpace* test_fes,
219 std::array<const mfem::ParFiniteElementSpace*, num_trial_spaces> trial_fes)
220 : update_qdata_(false), test_space_(test_fes), trial_space_(trial_fes)
222 auto mem_type = mfem::Device::GetMemoryType();
224 for (
auto type : {Domain::Type::Elements, Domain::Type::BoundaryElements}) {
225 input_E_[
type].resize(num_trial_spaces);
228 for (uint32_t i = 0; i < num_trial_spaces; i++) {
229 P_trial_[i] = trial_space_[i]->GetProlongationMatrix();
231 input_L_[i].SetSize(P_trial_[i]->Height(), mfem::Device::GetMemoryType());
234 for (
auto type : {Domain::Type::Elements, Domain::Type::BoundaryElements}) {
235 if (
type == Domain::Type::Elements) {
236 G_trial_[
type][i] = BlockElementRestriction(trial_fes[i]);
238 G_trial_[
type][i] = BlockElementRestriction(trial_fes[i], FaceType::BOUNDARY);
244 input_E_[
type][i].Update(G_trial_[
type][i].bOffsets(), mem_type);
248 for (
auto type : {Domain::Type::Elements, Domain::Type::BoundaryElements}) {
249 if (
type == Domain::Type::Elements) {
250 G_test_[
type] = BlockElementRestriction(test_fes);
252 G_test_[
type] = BlockElementRestriction(test_fes, FaceType::BOUNDARY);
255 output_E_[
type].Update(G_test_[
type].bOffsets(), mem_type);
258 P_test_ = test_space_->GetProlongationMatrix();
260 output_L_.SetSize(P_test_->Height(), mem_type);
262 output_T_.SetSize(test_fes->GetTrueVSize(), mem_type);
267 for (uint32_t i = 0; i < num_trial_spaces; i++) {
268 grad_.emplace_back(*
this, i);
283 template <
int dim,
int... args,
typename lambda,
typename qpt_data_type = Nothing>
284 void AddDomainIntegral(Dimension<dim>, DependsOn<args...>, lambda&& integrand, mfem::Mesh& domain,
285 std::shared_ptr<QuadratureData<qpt_data_type>> qdata =
NoQData)
287 if (domain.GetNE() == 0)
return;
289 SLIC_ERROR_ROOT_IF(dim != domain.Dimension(),
"invalid mesh dimension for domain integral");
294 using signature = test(decltype(serac::type<args>(trial_spaces))...);
295 integrals_.push_back(
296 MakeDomainIntegral<signature, Q, dim>(
EntireDomain(domain), integrand, qdata, std::vector<uint32_t>{args...}));
300 template <
int dim,
int... args,
typename lambda,
typename qpt_data_type = Nothing>
301 void AddDomainIntegral(Dimension<dim>, DependsOn<args...>, lambda&& integrand, Domain& domain,
302 std::shared_ptr<QuadratureData<qpt_data_type>> qdata =
NoQData)
304 if (domain.mesh_.GetNE() == 0)
return;
306 SLIC_ERROR_ROOT_IF(dim != domain.mesh_.Dimension(),
"invalid mesh dimension for domain integral");
311 using signature = test(decltype(serac::type<args>(trial_spaces))...);
312 integrals_.push_back(
313 MakeDomainIntegral<signature, Q, dim>(domain, integrand, qdata, std::vector<uint32_t>{args...}));
325 template <
int dim,
int... args,
typename lambda>
326 void AddBoundaryIntegral(Dimension<dim>, DependsOn<args...>, lambda&& integrand, mfem::Mesh& domain)
328 auto num_bdr_elements = domain.GetNBE();
329 if (num_bdr_elements == 0)
return;
333 using signature = test(decltype(serac::type<args>(trial_spaces))...);
334 integrals_.push_back(
335 MakeBoundaryIntegral<signature, Q, dim>(
EntireBoundary(domain), integrand, std::vector<uint32_t>{args...}));
339 template <
int dim,
int... args,
typename lambda>
340 void AddBoundaryIntegral(Dimension<dim>, DependsOn<args...>, lambda&& integrand,
const Domain& domain)
342 auto num_bdr_elements = domain.mesh_.GetNBE();
343 if (num_bdr_elements == 0)
return;
345 SLIC_ERROR_ROOT_IF(dim != domain.dim_,
"invalid domain of integration for boundary integral");
349 using signature = test(decltype(serac::type<args>(trial_spaces))...);
350 integrals_.push_back(MakeBoundaryIntegral<signature, Q, dim>(domain, integrand, std::vector<uint32_t>{args...}));
362 template <
int... args,
typename lambda,
typename qpt_data_type = Nothing>
363 void AddAreaIntegral(DependsOn<args...> which_args, lambda&& integrand, mfem::Mesh& domain,
364 std::shared_ptr<QuadratureData<qpt_data_type>> data =
NoQData)
366 AddDomainIntegral(Dimension<2>{}, which_args, integrand, domain, data);
378 template <
int... args,
typename lambda,
typename qpt_data_type =
Nothing>
379 void AddVolumeIntegral(
DependsOn<args...> which_args, lambda&& integrand, mfem::Mesh& domain,
382 AddDomainIntegral(
Dimension<3>{}, which_args, integrand, domain, data);
386 template <
int... args,
typename lambda>
387 void AddSurfaceIntegral(
DependsOn<args...> which_args, lambda&& integrand, mfem::Mesh& domain)
389 AddBoundaryIntegral(
Dimension<2>{}, which_args, integrand, domain);
403 void ActionOfGradient(
const mfem::Vector& input_T, mfem::Vector& output_T, uint32_t which)
const
405 P_trial_[which]->Mult(input_T, input_L_[which]);
413 for (
auto& integral : integrals_) {
414 auto type = integral.domain_.type_;
416 if (!already_computed[
type]) {
417 G_trial_[
type][which].Gather(input_L_[which], input_E_[
type][which]);
418 already_computed[
type] =
true;
421 integral.GradientMult(input_E_[
type][which], output_E_[
type], which);
424 G_test_[
type].ScatterAdd(output_E_[
type], output_L_);
428 P_test_->MultTranspose(output_L_, output_T);
443 template <uint32_t wrt,
typename... T>
446 const mfem::Vector* input_T[] = {&
static_cast<const mfem::Vector&
>(args)...};
449 for (uint32_t i = 0; i < num_trial_spaces; i++) {
450 P_trial_[i]->Mult(*input_T[i], input_L_[i]);
459 for (
auto& integral : integrals_) {
460 auto type = integral.domain_.type_;
462 for (
auto i : integral.active_trial_spaces_) {
463 if (!already_computed[
type][i]) {
464 G_trial_[
type][i].Gather(input_L_[i], input_E_[
type][i]);
465 already_computed[
type][i] =
true;
469 integral.Mult(t, input_E_[
type], output_E_[
type], wrt, update_qdata_);
472 G_test_[
type].ScatterAdd(output_E_[
type], output_L_);
476 P_test_->MultTranspose(output_L_, output_T_);
478 if constexpr (wrt != NO_DIFFERENTIATION) {
485 return {output_T_, grad_[wrt]};
487 if constexpr (wrt == NO_DIFFERENTIATION) {
498 template <
typename... T>
499 auto operator()(
double t,
const T&... args)
501 constexpr
int num_differentiated_arguments = (std::is_same_v<T, differentiate_wrt_this> + ...);
502 static_assert(num_differentiated_arguments <= 1,
503 "Error: Functional::operator() can only differentiate w.r.t. 1 argument a time");
504 static_assert(
sizeof...(T) == num_trial_spaces,
505 "Error: Functional::operator() must take exactly as many arguments as trial spaces");
509 return (*
this)(DifferentiateWRT<i>{}, t, args...);
520 void updateQdata(
bool update_flag) { update_qdata_ = update_flag; }
531 class Gradient :
public mfem::Operator {
537 Gradient(Functional<test(trials...), exec>& f, uint32_t which = 0)
538 : mfem::Operator(f.test_space_->GetTrueVSize(), f.trial_space_[which]->GetTrueVSize()),
540 lookup_tables(f.G_test_[
Domain::Type::Elements], f.G_trial_[
Domain::Type::Elements][which]),
541 which_argument(which),
542 test_space_(f.test_space_),
543 trial_space_(f.trial_space_[which]),
544 df_(f.test_space_->GetTrueVSize())
553 virtual void Mult(
const mfem::Vector& dx, mfem::Vector& df)
const override
555 form_.ActionOfGradient(dx, df, which_argument);
559 mfem::Vector& operator()(
const mfem::Vector& dx)
561 form_.ActionOfGradient(dx, df_, which_argument);
566 std::unique_ptr<mfem::HypreParMatrix> assemble()
570 constexpr
bool sparse_matrix_frees_graph_ptrs =
false;
574 constexpr
bool sparse_matrix_frees_values_ptr =
true;
576 constexpr
bool col_ind_is_sorted =
true;
578 double* values =
new double[lookup_tables.nnz]{};
580 std::map<mfem::Geometry::Type, ExecArray<double, 3, exec>> element_gradients[
Domain::num_types];
582 for (
auto& integral : form_.integrals_) {
583 auto& K_elem = element_gradients[integral.domain_.type_];
584 auto& test_restrictions = form_.G_test_[integral.domain_.type_].restrictions;
585 auto& trial_restrictions = form_.G_trial_[integral.domain_.type_][which_argument].restrictions;
587 if (K_elem.empty()) {
588 for (
auto& [geom, test_restriction] : test_restrictions) {
589 auto& trial_restriction = trial_restrictions[geom];
591 K_elem[geom] = ExecArray<double, 3, exec>(test_restriction.num_elements,
592 trial_restriction.nodes_per_elem * trial_restriction.components,
593 test_restriction.nodes_per_elem * test_restriction.components);
595 detail::zero_out(K_elem[geom]);
599 integral.ComputeElementGradients(K_elem, which_argument);
602 for (
auto type : {Domain::Type::Elements, Domain::Type::BoundaryElements}) {
603 auto& K_elem = element_gradients[
type];
604 auto& test_restrictions = form_.G_test_[
type].restrictions;
605 auto& trial_restrictions = form_.G_trial_[
type][which_argument].restrictions;
607 if (!K_elem.empty()) {
608 for (
auto [geom, elem_matrices] : K_elem) {
609 std::vector<DoF> test_vdofs(test_restrictions[geom].nodes_per_elem * test_restrictions[geom].components);
610 std::vector<DoF> trial_vdofs(trial_restrictions[geom].nodes_per_elem * trial_restrictions[geom].components);
612 for (axom::IndexType e = 0; e < elem_matrices.shape()[0]; e++) {
613 test_restrictions[geom].GetElementVDofs(e, test_vdofs);
614 trial_restrictions[geom].GetElementVDofs(e, trial_vdofs);
616 for (uint32_t i = 0; i < uint32_t(elem_matrices.shape()[1]); i++) {
617 int col = int(trial_vdofs[i].index());
619 for (uint32_t j = 0; j < uint32_t(elem_matrices.shape()[2]); j++) {
620 int row = int(test_vdofs[j].index());
622 int sign = test_vdofs[j].sign() * trial_vdofs[i].sign();
629 [[maybe_unused]]
auto nz = lookup_tables(row, col);
630 values[lookup_tables(row, col)] += sign * elem_matrices(e, i, j);
639 col_ind_copy_ = lookup_tables.col_ind;
642 mfem::SparseMatrix(lookup_tables.row_ptr.data(), col_ind_copy_.data(), values, form_.output_L_.Size(),
643 form_.input_L_[which_argument].Size(), sparse_matrix_frees_graph_ptrs,
644 sparse_matrix_frees_values_ptr, col_ind_is_sorted);
646 auto* R = form_.test_space_->Dof_TrueDof_Matrix();
649 new mfem::HypreParMatrix(test_space_->GetComm(), test_space_->GlobalVSize(), trial_space_->GlobalVSize(),
650 test_space_->GetDofOffsets(), trial_space_->GetDofOffsets(), &J_local);
652 auto* P = trial_space_->Dof_TrueDof_Matrix();
654 std::unique_ptr<mfem::HypreParMatrix> K(mfem::RAP(R, A, P));
661 friend auto assemble(Gradient& g) {
return g.assemble(); }
665 Functional<test(trials...), exec>& form_;
672 GradientAssemblyLookupTables lookup_tables;
678 std::vector<int> col_ind_copy_;
690 uint32_t which_argument;
693 const mfem::ParFiniteElementSpace* test_space_;
696 const mfem::ParFiniteElementSpace* trial_space_;
703 const mfem::ParFiniteElementSpace* test_space_;
706 std::array<const mfem::ParFiniteElementSpace*, num_trial_spaces> trial_space_;
712 const mfem::Operator* P_trial_[num_trial_spaces];
715 mutable mfem::Vector input_L_[num_trial_spaces];
721 std::vector<Integral> integrals_;
728 mutable mfem::Vector output_L_;
730 const mfem::Operator* P_test_;
733 mutable mfem::Vector output_T_;
736 mutable std::vector<Gradient> grad_;
many of the functions in this file amount to extracting element indices from an mfem::Mesh like
This file contains helper traits and enumerations for classifying finite elements.
a specialization of serac::Functional for quantities of interest
This file contains the all the necessary functions and macros required for logging as well as a helpe...
Accelerator functionality.
Domain EntireBoundary(const mfem::Mesh &mesh)
constructs a domain from all the boundary elements in a mesh
std::shared_ptr< QuadratureData< Nothing > > NoQData
a single instance of a QuadratureData container of Nothings, since they are all interchangeable
SERAC_HOST_DEVICE auto max(dual< gradient_type > a, double b)
Implementation of max for dual numbers.
void check_for_missing_nodal_gridfunc(const mfem::Mesh &mesh)
function for verifying that the mesh has been fully initialized
constexpr uint32_t index_of_differentiation()
given a list of types, this function returns the index that corresponds to the type dual_vector.
tuple(T...) -> tuple< T... >
Class template argument deduction rule for tuples.
constexpr SERAC_HOST_DEVICE auto type(const tuple< T... > &values)
a function intended to be used for extracting the ith type from a tuple.
ExecutionSpace
enum used for signalling whether or not to perform certain calculations on the CPU or GPU
void check_for_unsupported_elements(const mfem::Mesh &mesh)
function for verifying that there are no unsupported element types in the mesh
Domain EntireDomain(const mfem::Mesh &mesh)
constructs a domain from all the elements in a mesh
std::pair< std::unique_ptr< mfem::ParFiniteElementSpace >, std::unique_ptr< mfem::FiniteElementCollection > > generateParFiniteElementSpace(mfem::ParMesh *mesh)
create an mfem::ParFiniteElementSpace from one of serac's tag types: H1, Hcurl, L2
Definitions of quadrature rules for quads and hexes.
Compile-time alias for a dimension.
a class for representing a geometric region that can be used for integration
static constexpr int num_types
the number of entries in the Type enum
Compile-time alias for index of differentiation.
these classes are a little confusing. These two special types represent the similar (but different) c...
A class for storing and access user-defined types at quadrature points.
This is a class that mimics most of std::tuple's interface, except that it is usable in CUDA kernels ...
Implementation of the tensor class used by Functional.