27 void Mult(
const mfem::Vector&, mfem::Vector&)
const { SLIC_ERROR_ROOT(
"QoIProlongation::Mult() is not defined"); }
30 void MultTranspose(
const mfem::Vector& input, mfem::Vector& output)
const
34 MPI_Allreduce(
const_cast<double*
>(&input[0]), &output[0], 1, MPI_DOUBLE, MPI_SUM,
comm);
52 void ScatterAdd(
const mfem::Vector& input, mfem::Vector& output)
const { output[0] += input.Sum(); }
59 class Functional<double(trials...), exec> {
61 static constexpr
tuple<trials...> trial_spaces{};
62 static constexpr uint32_t num_trial_spaces =
sizeof...(trials);
63 static constexpr
auto Q =
std::max({test::order, trials::order...}) + 1;
69 struct operator_paren_return {
70 using type =
typename std::conditional<
71 i == NO_DIFFERENTIATION,
83 Functional(std::array<const mfem::ParFiniteElementSpace*, num_trial_spaces> trial_fes)
84 : test_fec_(0, trial_fes[0]->GetMesh()->
Dimension()),
85 test_space_(dynamic_cast<mfem::ParMesh*>(trial_fes[0]->GetMesh()), &test_fec_),
86 trial_space_(trial_fes)
88 auto* mesh = trial_fes[0]->GetMesh();
90 auto mem_type = mfem::Device::GetMemoryType();
92 for (
auto type : {Domain::Type::Elements, Domain::Type::BoundaryElements}) {
93 input_E_[
type].resize(num_trial_spaces);
96 for (uint32_t i = 0; i < num_trial_spaces; i++) {
97 P_trial_[i] = trial_space_[i]->GetProlongationMatrix();
99 input_L_[i].SetSize(P_trial_[i]->Height(), mfem::Device::GetMemoryType());
101 for (
auto type : {Domain::Type::Elements, Domain::Type::BoundaryElements}) {
102 if (
type == Domain::Type::Elements) {
111 input_E_[
type][i].Update(G_trial_[
type][i].bOffsets(), mem_type);
115 for (
auto type : {Domain::Type::Elements, Domain::Type::BoundaryElements}) {
116 std::array<uint32_t, mfem::Geometry::NUM_GEOMETRIES> counts{};
117 if (
type == Domain::Type::Elements) {
123 mfem::Array<int> offsets(mfem::Geometry::NUM_GEOMETRIES + 1);
125 for (
int i = 0; i < mfem::Geometry::NUM_GEOMETRIES; i++) {
126 auto g = mfem::Geometry::Type(i);
127 offsets[g + 1] = offsets[g] + int(counts[uint32_t(g)]);
130 output_E_[
type].Update(offsets, mem_type);
136 output_L_.SetSize(1, mem_type);
138 output_T_.SetSize(1, mfem::Device::GetMemoryType());
143 for (uint32_t i = 0; i < num_trial_spaces; i++) {
144 grad_.emplace_back(*
this, i);
159 template <
int dim,
int... args,
typename lambda,
typename qpt_data_type =
Nothing>
163 if (mesh.GetNE() == 0)
return;
165 SLIC_ERROR_ROOT_IF(dim != mesh.Dimension(),
"invalid mesh dimension for domain integral");
170 using signature =
test(decltype(serac::type<args>(trial_spaces))...);
171 integrals_.push_back(
172 MakeDomainIntegral<signature, Q, dim>(
EntireDomain(mesh), integrand, qdata, std::vector<uint32_t>{args...}));
176 template <
int dim,
int... args,
typename lambda,
typename qpt_data_type =
Nothing>
180 if (domain.
mesh_.GetNE() == 0)
return;
182 SLIC_ERROR_ROOT_IF(dim != domain.
mesh_.Dimension(),
"invalid mesh dimension for domain integral");
187 using signature =
test(decltype(serac::type<args>(trial_spaces))...);
188 integrals_.push_back(
189 MakeDomainIntegral<signature, Q, dim>(domain, integrand, qdata, std::vector<uint32_t>{args...}));
203 template <
int dim,
int... args,
typename lambda,
typename qpt_data_type =
void>
206 auto num_bdr_elements = mesh.GetNBE();
207 if (num_bdr_elements == 0)
return;
211 using signature =
test(decltype(serac::type<args>(trial_spaces))...);
212 integrals_.push_back(
213 MakeBoundaryIntegral<signature, Q, dim>(
EntireBoundary(mesh), integrand, std::vector<uint32_t>{args...}));
217 template <
int dim,
int... args,
typename lambda>
220 auto num_bdr_elements = domain.
mesh_.GetNBE();
221 if (num_bdr_elements == 0)
return;
223 SLIC_ERROR_ROOT_IF(dim != domain.
dim_,
"invalid domain of integration for boundary integral");
227 using signature =
test(decltype(serac::type<args>(trial_spaces))...);
228 integrals_.push_back(MakeBoundaryIntegral<signature, Q, dim>(domain, integrand, std::vector<uint32_t>{args...}));
241 template <
int... args,
typename lambda,
typename qpt_data_type =
Nothing>
245 AddDomainIntegral(
Dimension<2>{}, which_args, integrand, domain, data);
258 template <
int... args,
typename lambda,
typename qpt_data_type =
Nothing>
262 AddDomainIntegral(
Dimension<3>{}, which_args, integrand, domain, data);
266 template <
int... args,
typename lambda>
269 AddBoundaryIntegral(
Dimension<2>{}, which_args, integrand, domain);
284 P_trial_[which]->Mult(input_T, input_L_[which]);
292 for (
auto& integral : integrals_) {
293 auto type = integral.domain_.type_;
295 if (!already_computed[
type]) {
296 G_trial_[
type][which].Gather(input_L_[which], input_E_[
type][which]);
297 already_computed[
type] =
true;
300 integral.GradientMult(input_E_[
type][which], output_E_[
type], which);
303 G_test_.ScatterAdd(output_E_[
type], output_L_);
307 P_test_.MultTranspose(output_L_, output_T_);
322 template <uint32_t wrt,
typename... T>
325 const mfem::Vector* input_T[] = {&
static_cast<const mfem::Vector&
>(args)...};
328 for (uint32_t i = 0; i < num_trial_spaces; i++) {
329 P_trial_[i]->Mult(*input_T[i], input_L_[i]);
338 for (
auto& integral : integrals_) {
339 auto type = integral.domain_.type_;
341 for (
auto i : integral.active_trial_spaces_) {
342 if (!already_computed[
type][i]) {
343 G_trial_[
type][i].Gather(input_L_[i], input_E_[
type][i]);
344 already_computed[
type][i] =
true;
348 const bool update_state =
false;
349 integral.Mult(t, input_E_[
type], output_E_[
type], wrt, update_state);
352 G_test_.ScatterAdd(output_E_[
type], output_L_);
356 P_test_.MultTranspose(output_L_, output_T_);
358 if constexpr (wrt != NO_DIFFERENTIATION) {
365 return {output_T_[0], grad_[wrt]};
368 if constexpr (wrt == NO_DIFFERENTIATION) {
379 template <
typename... T>
383 constexpr
int num_differentiated_arguments = (std::is_same_v<T, differentiate_wrt_this> + ... + 0);
384 static_assert(num_differentiated_arguments <= 1,
385 "Error: Functional::operator() can only differentiate w.r.t. 1 argument a time");
386 static_assert(
sizeof...(T) == num_trial_spaces,
387 "Error: Functional::operator() must take exactly as many arguments as trial spaces");
413 Gradient(Functional<
double(trials...)>& f, uint32_t which = 0)
414 : form_(f), which_argument(which), gradient_L_(f.trial_space_[which]->GetVSize())
418 void Mult(
const mfem::Vector& x, mfem::Vector& y)
const { form_.GradientMult(x, y); }
420 double operator()(
const mfem::Vector& x)
const {
return form_.ActionOfGradient(x, which_argument); }
422 std::unique_ptr<mfem::HypreParVector> assemble()
425 std::unique_ptr<mfem::HypreParVector> gradient_T(
426 const_cast<mfem::ParFiniteElementSpace*
>(form_.trial_space_[which_argument])->NewTrueDofVector());
430 std::map<mfem::Geometry::Type, ExecArray<double, 3, exec>> element_gradients[
Domain::num_types];
432 for (
auto& integral : form_.integrals_) {
433 auto& K_elem = element_gradients[integral.domain_.type_];
434 auto& trial_restrictions = form_.G_trial_[integral.domain_.type_][which_argument].restrictions;
436 if (K_elem.empty()) {
437 for (
auto& [geom, trial_restriction] : trial_restrictions) {
438 K_elem[geom] = ExecArray<double, 3, exec>(trial_restriction.num_elements, 1,
439 trial_restriction.nodes_per_elem * trial_restriction.components);
441 detail::zero_out(K_elem[geom]);
445 integral.ComputeElementGradients(K_elem, which_argument);
448 for (
auto type : {Domain::Type::Elements, Domain::Type::BoundaryElements}) {
449 auto& K_elem = element_gradients[
type];
450 auto& trial_restrictions = form_.G_trial_[
type][which_argument].restrictions;
452 if (!K_elem.empty()) {
453 for (
auto [geom, elem_matrices] : K_elem) {
454 std::vector<DoF> trial_vdofs(trial_restrictions[geom].nodes_per_elem * trial_restrictions[geom].components);
456 for (axom::IndexType e = 0; e < elem_matrices.shape()[0]; e++) {
457 trial_restrictions[geom].GetElementVDofs(e, trial_vdofs);
460 for (axom::IndexType i = 0; i < elem_matrices.shape()[1]; i++) {
461 for (axom::IndexType j = 0; j < elem_matrices.shape()[2]; j++) {
462 int sign = trial_vdofs[uint32_t(j)].sign();
463 int col = int(trial_vdofs[uint32_t(j)].index());
464 gradient_L_[col] += sign * elem_matrices(e, i, j);
472 form_.P_trial_[which_argument]->MultTranspose(gradient_L_, *gradient_T);
477 friend auto assemble(Gradient& g) {
return g.assemble(); }
483 Functional<double(trials...), exec>& form_;
485 uint32_t which_argument;
487 mfem::Vector gradient_L_;
491 const mfem::L2_FECollection test_fec_;
492 const mfem::ParFiniteElementSpace test_space_;
495 std::array<const mfem::ParFiniteElementSpace*, num_trial_spaces> trial_space_;
501 const mfem::Operator* P_trial_[num_trial_spaces];
504 mutable mfem::Vector input_L_[num_trial_spaces];
510 std::vector<Integral> integrals_;
514 QoIElementRestriction G_test_;
517 mutable mfem::Vector output_L_;
519 QoIProlongation P_test_;
522 mutable mfem::Vector output_T_;
525 mutable std::vector<Gradient> grad_;
Functional(std::array< const mfem::ParFiniteElementSpace *, num_trial_spaces > trial_fes)
Constructs using a mfem::ParFiniteElementSpace object corresponding to the trial space.
void AddAreaIntegral(DependsOn< args... > which_args, lambda &&integrand, mfem::Mesh &domain, std::shared_ptr< QuadratureData< qpt_data_type >> &data=NoQData)
Adds an area integral, i.e., over 2D elements in R^2.
void AddVolumeIntegral(DependsOn< args... > which_args, lambda &&integrand, mfem::Mesh &domain, std::shared_ptr< QuadratureData< qpt_data_type >> &data=NoQData)
Adds a volume integral, i.e., over 3D elements in R^3.
void AddBoundaryIntegral(Dimension< dim >, DependsOn< args... >, lambda &&integrand, const Domain &domain)
This is an overloaded member function, provided for convenience. It differs from the above function o...
auto operator()(double t, const T &... args)
This is an overloaded member function, provided for convenience. It differs from the above function o...
void AddDomainIntegral(Dimension< dim >, DependsOn< args... >, lambda &&integrand, Domain &domain, std::shared_ptr< QuadratureData< qpt_data_type >> qdata=NoQData)
This is an overloaded member function, provided for convenience. It differs from the above function o...
operator_paren_return< wrt >::type operator()(DifferentiateWRT< wrt >, double t, const T &... args)
this function lets the user evaluate the serac::Functional with the given trial space values
void AddBoundaryIntegral(Dimension< dim >, DependsOn< args... >, lambda &&integrand, mfem::Mesh &mesh)
Adds a boundary integral term to the Functional object.
void AddSurfaceIntegral(DependsOn< args... > which_args, lambda &&integrand, mfem::Mesh &domain)
alias for Functional::AddBoundaryIntegral(Dimension<2>{}, integrand, domain);
double ActionOfGradient(const mfem::Vector &input_T, uint32_t which) const
this function computes the directional derivative of the quantity of interest functional
void AddDomainIntegral(Dimension< dim >, DependsOn< args... >, lambda &&integrand, mfem::Mesh &mesh, std::shared_ptr< QuadratureData< qpt_data_type >> qdata=NoQData)
Adds a domain integral term to the Functional object.
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.
std::array< uint32_t, mfem::Geometry::NUM_GEOMETRIES > boundary_geometry_counts(const mfem::Mesh &mesh)
count the number of boundary elements of each geometry in a mesh
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::array< uint32_t, mfem::Geometry::NUM_GEOMETRIES > geometry_counts(const mfem::Mesh &mesh)
count the number of elements of each geometry in a mesh
a generalization of mfem::ElementRestriction that works with multiple kinds of element geometries....
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
int dim_
the geometric dimension of the domain
const mfem::Mesh & mesh_
the underyling mesh for this domain
these classes are a little confusing. These two special types represent the similar (but different) c...
"Quantity of Interest" elements (i.e. elements with a single shape function, 1)
this class behaves like a Restriction operator, except is specialized for the case of a quantity of i...
void ScatterAdd(const mfem::Vector &input, mfem::Vector &output) const
element-to-global ScatterAdd operation used in FEM assembly, for quantities of interest
this class behaves like a Prolongation operator, except is specialized for the case of a quantity of ...
void MultTranspose(const mfem::Vector &input, mfem::Vector &output) const
set the value of output to the distributed sum over input values from different processors
QoIProlongation(MPI_Comm c)
create a QoIProlongation for a Quantity of Interest
MPI_Comm comm
MPI communicator used to carry out the distributed reduction.
void Mult(const mfem::Vector &, mfem::Vector &) const
unimplemented: do not use
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 ...