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_, 1,
smith::ordering),
86 trial_space_(trial_fes),
87 mem_type(mfem::Device::GetMemoryType())
91 std::array<Family, num_trial_spaces> trial_families = {trials::family...};
92 std::array<int, num_trial_spaces> trial_orders = {trials::order...};
93 std::array<int, num_trial_spaces> trial_components = {trials::components...};
95 for (uint32_t i = 0; i < num_trial_spaces; i++) {
96 trial_function_spaces_[i] = {trial_families[i], trial_orders[i], trial_components[i]};
98 P_trial_[i] = trial_space_[i]->GetProlongationMatrix();
103 if (trial_function_spaces_[i].family == Family::L2) {
105 input_ldof_values_[i].SetSize(P_trial_[i]->Height(), mem_type);
108 updateFaceNbrData(trial_space_[i], trial_pargrid_functions_[i], input_ldof_values_[i]);
111 input_L_[i].SetSize(input_ldof_values_[i].Size() + trial_pargrid_functions_[i].FaceNbrData().Size(), mem_type);
113 input_L_[i].SetSize(P_trial_[i]->Height(), mem_type);
117 input_E_.push_back({});
118 input_E_buffer_.push_back({});
124 output_L_.SetSize(1, mem_type);
126 output_T_.SetSize(1, mem_type);
131 for (uint32_t i = 0; i < num_trial_spaces; i++) {
132 grad_.emplace_back(*
this, i);
148 template <
int dim,
int... args,
typename lambda,
typename qpt_data_type =
Nothing>
152 if (domain.
mesh_.GetNE() == 0)
return;
154 SLIC_ERROR_ROOT_IF(dim != domain.
mesh_.Dimension(),
"invalid mesh dimension for domain integral");
159 std::vector<uint32_t> arg_vec = {args...};
160 for (uint32_t i : arg_vec) {
164 using signature =
test(decltype(smith::type<args>(trial_spaces))...);
165 integrals_.push_back(
166 MakeDomainIntegral<signature, Q, dim>(domain, integrand, qdata, std::vector<uint32_t>{args...}));
180 template <
int dim,
int... args,
typename lambda>
183 auto num_bdr_elements = domain.
mesh_.GetNBE();
184 if (num_bdr_elements == 0)
return;
186 SLIC_ERROR_ROOT_IF(dim != domain.
dim_,
"invalid domain of integration for boundary integral");
190 std::vector<uint32_t> arg_vec = {args...};
191 for (uint32_t i : arg_vec) {
195 using signature =
test(decltype(smith::type<args>(trial_spaces))...);
196 integrals_.push_back(MakeBoundaryIntegral<signature, Q, dim>(domain, integrand, arg_vec));
210 template <
int dim,
int... args,
typename Integrand>
215 std::vector<uint32_t> arg_vec = {args...};
216 for (uint32_t i : arg_vec) {
220 using signature =
test(decltype(smith::type<args>(trial_spaces))...);
221 integrals_.push_back(MakeInteriorFaceIntegral<signature, Q, dim>(domain, integrand, arg_vec));
234 template <
int... args,
typename lambda,
typename qpt_data_type =
Nothing>
238 AddDomainIntegral(
Dimension<2>{}, which_args, integrand, domain, data);
251 template <
int... args,
typename lambda,
typename qpt_data_type =
Nothing>
255 AddDomainIntegral(
Dimension<3>{}, which_args, integrand, domain, data);
259 template <
int... args,
typename lambda>
262 AddBoundaryIntegral(
Dimension<2>{}, which_args, integrand, domain);
278 if (trial_function_spaces_[which].family == Family::L2) {
279 P_trial_[which]->Mult(input_T, input_ldof_values_[which]);
280 input_L_[which].SetVector(input_ldof_values_[which], 0);
281 updateFaceNbrData(trial_space_[which], trial_pargrid_functions_[which], input_ldof_values_[which]);
283 if (trial_space_[which]->GetVDim() == 1) {
284 input_L_[which].SetVector(trial_pargrid_functions_[which].FaceNbrData(), input_ldof_values_[which].Size());
286 if (trial_space_[which]->GetOrdering() == mfem::Ordering::byVDIM) {
287 int local_size = input_ldof_values_[which].Size();
288 appendFaceNbrData(trial_space_[which], trial_pargrid_functions_[which], local_size, input_L_[which]);
290 SLIC_ERROR_ROOT(
"Unsupported: L2 vector field ordered by nodes");
294 P_trial_[which]->Mult(input_T, input_L_[which]);
299 for (
auto& integral : integrals_) {
300 if (integral.DependsOn(which)) {
301 Domain& dom = integral.domain_;
304 input_E_buffer_[which].SetSize(
int(G_trial.
ESize()));
305 input_E_[which].Update(input_E_buffer_[which], G_trial.
bOffsets());
306 G_trial.
Gather(input_L_[which], input_E_[which]);
309 output_E_.Update(output_E_buffer_, dom.
bOffsets());
311 integral.GradientMult(input_E_[which], output_E_, which);
314 if (dom.
type_ == Domain::Type::InteriorFaces) {
316 output_E_[shared_id] *= 0.5;
321 G_test_.ScatterAdd(output_E_, output_L_);
326 P_test_.MultTranspose(output_L_, output_T_);
341 template <uint32_t wrt,
typename... T>
344 const mfem::Vector* input_T[] = {&
static_cast<const mfem::Vector&
>(args)...};
348 for (uint32_t i = 0; i < num_trial_spaces; i++) {
349 if (trial_function_spaces_[i].family == Family::L2) {
350 P_trial_[i]->Mult(*input_T[i], input_ldof_values_[i]);
351 input_L_[i].SetVector(input_ldof_values_[i], 0);
352 updateFaceNbrData(trial_space_[i], trial_pargrid_functions_[i], input_ldof_values_[i]);
354 if (trial_space_[i]->GetVDim() == 1) {
355 input_L_[i].SetVector(trial_pargrid_functions_[i].FaceNbrData(), input_ldof_values_[i].Size());
357 if (trial_space_[i]->GetOrdering() == mfem::Ordering::byVDIM) {
358 int local_size = input_ldof_values_[i].Size();
359 appendFaceNbrData(trial_space_[i], trial_pargrid_functions_[i], local_size, input_L_[i]);
361 SLIC_ERROR_ROOT(
"Unsupported: L2 vector field ordered by nodes");
365 P_trial_[i]->Mult(*input_T[i], input_L_[i]);
371 for (
auto& integral : integrals_) {
372 Domain& dom = integral.domain_;
374 for (
auto i : integral.active_trial_spaces_) {
376 input_E_buffer_[i].SetSize(
int(G_trial.
ESize()));
377 input_E_[i].Update(input_E_buffer_[i], G_trial.
bOffsets());
378 G_trial.
Gather(input_L_[i], input_E_[i]);
382 output_E_.Update(output_E_buffer_, dom.
bOffsets());
384 const bool update_qdata =
false;
385 integral.Mult(t, input_E_, output_E_, wrt, update_qdata);
388 if (dom.
type_ == Domain::Type::InteriorFaces) {
390 output_E_[shared_id] *= 0.5;
395 G_test_.ScatterAdd(output_E_, output_L_);
399 P_test_.MultTranspose(output_L_, output_T_);
401 if constexpr (wrt != NO_DIFFERENTIATION) {
408 return {output_T_[0], grad_[wrt]};
411 if constexpr (wrt == NO_DIFFERENTIATION) {
422 template <
typename... T>
426 constexpr
int num_differentiated_arguments = (std::is_same_v<T, differentiate_wrt_this> + ... + 0);
427 static_assert(num_differentiated_arguments <= 1,
428 "Error: Functional::operator() can only differentiate w.r.t. 1 argument a time");
429 static_assert(
sizeof...(T) == num_trial_spaces,
430 "Error: Functional::operator() must take exactly as many arguments as trial spaces");
456 Gradient(Functional<
double(trials...)>& f, uint32_t which = 0)
457 : form_(f), which_argument(which), gradient_L_(f.trial_space_[which]->GetVSize())
461 double operator()(
const mfem::Vector& x)
const {
return form_.ActionOfGradient(x, which_argument); }
463 uint64_t max_buffer_size()
465 uint64_t max_entries = 0;
466 for (
auto& integral : form_.integrals_) {
467 if (integral.DependsOn(which_argument)) {
468 Domain& dom = integral.domain_;
469 const auto& G_trial = dom.get_restriction(form_.trial_function_spaces_[which_argument]);
470 for (
const auto& [geom, test_restriction] : G_trial.restrictions) {
471 const auto& trial_restriction = G_trial.restrictions.at(geom);
472 uint64_t entries_per_element = trial_restriction.nodes_per_elem * trial_restriction.components;
473 max_entries =
std::max(max_entries, trial_restriction.num_elements * entries_per_element);
480 std::unique_ptr<mfem::HypreParVector> assemble()
483 std::unique_ptr<mfem::HypreParVector> gradient_T(
484 const_cast<mfem::ParFiniteElementSpace*
>(form_.trial_space_[which_argument])->NewTrueDofVector());
488 std::vector<double> K_elem_buffer(max_buffer_size());
490 std::map<mfem::Geometry::Type, ExecArray<double, 3, exec>> element_gradients[
Domain::num_types];
492 int lcols = form_.trial_space_[which_argument]->GetVSize();
496 for (
auto& integral : form_.integrals_) {
497 Domain& dom = integral.domain_;
500 if (integral.functional_to_integral_index_.count(which_argument) > 0) {
501 uint32_t
id = integral.functional_to_integral_index_.at(which_argument);
502 const auto& G_trial = dom.get_restriction(form_.trial_function_spaces_[which_argument]);
503 for (
const auto& [geom, calculate_element_gradients] : integral.element_gradient_[
id]) {
504 const auto& trial_restriction = G_trial.restrictions.at(geom);
507 CPUArrayView<double, 3> K_e(K_elem_buffer.data(), trial_restriction.num_elements, 1,
508 trial_restriction.nodes_per_elem * trial_restriction.components);
509 detail::zero_out(K_e);
511 calculate_element_gradients(K_e);
513 const std::vector<int>& element_ids = integral.domain_.get(geom);
515 uint32_t cols_per_elem = uint32_t(trial_restriction.nodes_per_elem * trial_restriction.components);
516 std::vector<DoF> trial_vdofs(cols_per_elem);
518 for (uint32_t e = 0; e < element_ids.size(); e++) {
519 trial_restriction.GetElementVDofs(
int(e), trial_vdofs);
521 for (uint32_t i = 0; i < cols_per_elem; i++) {
522 int col = int(trial_vdofs[i].index());
526 gradient_L_[col] += K_e(e, 0, i);
536 form_.P_trial_[which_argument]->MultTranspose(gradient_L_, *gradient_T);
541 friend auto assemble(Gradient& g) {
return g.assemble(); }
547 Functional<double(trials...), exec>& form_;
549 uint32_t which_argument;
551 mfem::Vector gradient_L_;
555 const mfem::L2_FECollection test_fec_;
556 const mfem::ParFiniteElementSpace test_space_;
559 std::array<const mfem::ParFiniteElementSpace*, num_trial_spaces> trial_space_;
561 std::array<FunctionSpace, num_trial_spaces> trial_function_spaces_;
564 mutable std::array<mfem::ParGridFunction, num_trial_spaces> trial_pargrid_functions_;
567 mutable std::array<mfem::Vector, num_trial_spaces> input_ldof_values_;
573 const mfem::Operator* P_trial_[num_trial_spaces];
576 mutable mfem::Vector input_L_[num_trial_spaces];
578 mutable std::vector<mfem::Vector> input_E_buffer_;
579 mutable std::vector<mfem::BlockVector> input_E_;
581 mutable std::vector<Integral> integrals_;
583 mutable mfem::Vector output_E_buffer_;
584 mutable mfem::BlockVector output_E_;
586 QoIElementRestriction G_test_;
589 mutable mfem::Vector output_L_;
591 QoIProlongation P_test_;
594 mutable mfem::Vector output_T_;
597 mutable std::vector<Gradient> grad_;
599 const mfem::MemoryType mem_type;
void AddVolumeIntegral(DependsOn< args... > which_args, const lambda &integrand, Domain &domain, std::shared_ptr< QuadratureData< qpt_data_type >> &data=NoQData)
Adds a volume integral, i.e., over 3D elements in R^3.
void AddDomainIntegral(Dimension< dim >, DependsOn< args... >, const lambda &integrand, Domain &domain, std::shared_ptr< QuadratureData< qpt_data_type >> qdata=NoQData)
Adds a domain integral term to the Functional object.
double ActionOfGradient(const mfem::Vector &input_T, uint32_t which) const
this function computes the directional derivative of the quantity of interest functional
auto operator()(double t, const T &... args)
This is an overloaded member function, provided for convenience. It differs from the above function o...
void AddAreaIntegral(DependsOn< args... > which_args, const lambda &integrand, Domain &domain, std::shared_ptr< QuadratureData< qpt_data_type >> &data=NoQData)
Adds an area integral, i.e., over 2D elements in R^2.
void AddSurfaceIntegral(DependsOn< args... > which_args, const lambda &integrand, Domain &domain)
alias for Functional::AddBoundaryIntegral(Dimension<2>{}, integrand, domain);
Functional(std::array< const mfem::ParFiniteElementSpace *, num_trial_spaces > trial_fes)
Constructs using a mfem::ParFiniteElementSpace object corresponding to the trial space.
void AddInteriorFaceIntegral(Dimension< dim >, DependsOn< args... >, const Integrand &integrand, Domain &domain)
Adds a interior face integral term to the Functional object.
void AddBoundaryIntegral(Dimension< dim >, DependsOn< args... >, const lambda &integrand, Domain &domain)
Adds a boundary integral term to the Functional object.
operator_paren_return< wrt >::type operator()(DifferentiateWRT< wrt >, double t, const T &... args)
this function lets the user evaluate the smith::Functional with the given trial space values
Accelerator functionality.
void appendFaceNbrData(const mfem::ParFiniteElementSpace *trial_space, const mfem::ParGridFunction &trial_pgf, const int LSize, mfem::Vector &input_L)
helper functional to reorder the ordering of FaceNbrData for L2 space to byVDIM and append this vecto...
std::shared_ptr< QuadratureData< Nothing > > NoQData
a single instance of a QuadratureData container of Nothings, since they are all interchangeable
void check_for_unsupported_elements(const mfem::Mesh &mesh)
function for verifying that there are no unsupported element types in the mesh
constexpr SMITH_HOST_DEVICE auto type(const tuple< T... > &values)
a function intended to be used for extracting the ith type from a tuple.
constexpr uint32_t index_of_differentiation()
given a list of types, this function returns the index that corresponds to the type dual_vector.
SMITH_HOST_DEVICE auto max(dual< gradient_type > a, double b)
Implementation of max for dual numbers.
ExecutionSpace
enum used for signalling whether or not to perform certain calculations on the CPU or GPU
void check_for_missing_nodal_gridfunc(const mfem::Mesh &mesh)
function for verifying that the mesh has been fully initialized
void updateFaceNbrData(const mfem::ParFiniteElementSpace *const_trial_space, mfem::ParGridFunction &trial_pgf, mfem::Vector &trial_tdof_vals)
helper function to locally cast away const on FE space so we can update face neighbor data with Excha...
#define SMITH_MARK_FUNCTION
a generalization of mfem::ElementRestriction that works with multiple kinds of element geometries....
uint64_t ESize() const
the size of the "E-vector" associated with this restriction operator
void Gather(const mfem::Vector &L_vector, mfem::BlockVector &E_block_vector) const
"L->E" in mfem parlance, each element gathers the values that belong to it, and stores them in the "E...
mfem::Array< int > bOffsets() const
block offsets used when constructing mfem::HypreParVectors
Compile-time alias for a dimension.
a class for representing a geometric region that can be used for integration
int dim_
the geometric dimension of the domain
std::vector< int > shared_interior_face_ids_
Ids of interior faces that lie on the boundary shared by two processors.
const BlockElementRestriction & get_restriction(FunctionSpace space)
getter for accessing a restriction operator by its function space
void insert_restriction(const fes_t *fes, FunctionSpace space)
create a restriction operator over this domain, using its FunctionSpace as a key
int total_elements() const
returns how many elements of any type belong to this domain
Type type_
whether the elements in this domain are on the boundary or not
static constexpr int num_types
the number of entries in the Type enum
mfem::Array< int > bOffsets() const
returns an array of the prefix sum of element counts belonging to this domain. Primarily intended to ...
const mesh_t & 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 Mult(const mfem::Vector &, mfem::Vector &) const
unimplemented: do not use
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.
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 ...