DOLFINx 0.12.0.0
DOLFINx C++
Loading...
Searching...
No Matches
Form.h
1// Copyright (C) 2019-2025 Garth N. Wells, Chris Richardson, Joseph P. Dean and
2// Jørgen S. Dokken
3//
4// This file is part of DOLFINx (https://www.fenicsproject.org)
5//
6// SPDX-License-Identifier: LGPL-3.0-or-later
7
8#pragma once
9
10#include "FunctionSpace.h"
11#include "traits.h"
12#include <algorithm>
13#include <basix/mdspan.hpp>
14#include <cassert>
15#include <concepts>
16#include <cstdint>
17#include <dolfinx/common/types.h>
18#include <dolfinx/mesh/EntityMap.h>
19#include <dolfinx/mesh/Mesh.h>
20#include <dolfinx/mesh/cell_types.h>
21#include <format>
22#include <functional>
23#include <map>
24#include <memory>
25#include <ranges>
26#include <set>
27#include <span>
28#include <stdexcept>
29#include <tuple>
30#include <utility>
31#include <variant>
32#include <vector>
33
34namespace dolfinx::fem
35{
36template <dolfinx::scalar T>
37class Constant;
38template <dolfinx::scalar T, std::floating_point U>
39class Function;
40
42enum class IntegralType : std::int8_t
43{
44 cell = 0,
47 vertex = 3,
48 ridge = 4
49};
50
57constexpr int integral_entity_dim(IntegralType type, int tdim)
58{
59 switch (type)
60 {
63 return tdim - 1;
65 return tdim - 2;
67 return 0;
69 return tdim;
70 }
71
72 throw std::invalid_argument("Unknown integral type.");
73}
74
75namespace impl
76{
90inline md::mdspan<const std::uint8_t, md::dextents<std::size_t, 2>>
91entity_permutations(mesh::Topology& topology, IntegralType type,
92 mesh::CellType cell_type)
93{
94 if (type == IntegralType::cell)
95 return {};
96
97 const int tdim = topology.dim();
98 const int dim = integral_entity_dim(type, tdim);
99
100 topology.create_entity_permutations(dim);
101 const std::vector<std::uint8_t>& p = topology.get_entity_permutations(dim);
102 const int num_entities_per_cell = mesh::cell_num_entities(cell_type, dim);
103 return md::mdspan(p.data(), p.size() / num_entities_per_cell,
104 num_entities_per_cell);
105}
106} // namespace impl
107
111template <dolfinx::scalar T, std::floating_point U = scalar_value_t<T>>
113{
119 template <typename K, typename V, typename W>
120 requires std::is_convertible_v<
121 std::remove_cvref_t<K>,
122 std::function<void(T*, const T*, const T*, const U*,
123 const int*, const uint8_t*, void*)>>
124 and std::is_convertible_v<std::remove_cvref_t<V>,
125 std::vector<std::int32_t>>
126 and std::is_convertible_v<std::remove_cvref_t<W>,
127 std::vector<int>>
129 : kernel(std::forward<K>(kernel)), entities(std::forward<V>(entities)),
130 coeffs(std::forward<W>(coeffs))
131 {
132 }
133
135 std::function<void(T*, const T*, const T*, const U*, const int*,
136 const uint8_t*, void*)>
138
141 std::vector<std::int32_t> entities;
142
145 std::vector<int> coeffs;
146};
147
175template <dolfinx::scalar T, std::floating_point U = dolfinx::scalar_value_t<T>>
176class Form
177{
178public:
180 using scalar_type = T;
181
183 using geometry_type = U;
184
210 template <typename X>
211 requires std::is_convertible_v<
212 std::remove_cvref_t<X>,
213 std::map<std::tuple<IntegralType, int, int>,
216 const std::vector<std::shared_ptr<const FunctionSpace<geometry_type>>>& V,
217 X&& integrals, std::shared_ptr<const mesh::Mesh<geometry_type>> mesh,
218 const std::vector<
219 std::shared_ptr<const Function<scalar_type, geometry_type>>>&
221 const std::vector<std::shared_ptr<const Constant<scalar_type>>>&
222 constants,
224 const std::vector<std::reference_wrapper<const mesh::EntityMap>>&
225 entity_maps)
226 : _function_spaces(V), _integrals(std::forward<X>(integrals)),
227 _mesh(mesh), _coefficients(coefficients), _constants(constants),
228 _needs_facet_permutations(needs_facet_permutations)
229 {
230 if (!_mesh)
231 throw std::invalid_argument("Form Mesh is null.");
232
233 // `_mesh` is fixed for the remainder of construction, so its
234 // topology and dimension are fetched once and reused below rather
235 // than being re-fetched for every integral/coefficient.
236 const mesh::Topology& topology = *_mesh->topology();
237 const int tdim = topology.dim();
238
239 // A helper function to find the correct entity map for a given mesh
240 auto get_entity_map
241 = [mesh, &entity_maps](auto& mesh0) -> const mesh::EntityMap&
242 {
243 auto it = std::ranges::find_if(
244 entity_maps,
245 [mesh, mesh0](const mesh::EntityMap& em)
246 {
247 return ((em.topology() == mesh0->topology()
248 and em.sub_topology() == mesh->topology()))
249 or ((em.sub_topology() == mesh0->topology()
250 and em.topology() == mesh->topology()));
251 });
252
253 if (it == entity_maps.end())
254 {
255 throw std::invalid_argument(
256 "Incompatible mesh. argument entity_maps must be provided.");
257 }
258 return *it;
259 };
260
261 // A helper function to compute the (cell, local_entity) pairs in the
262 // argument/coefficient domain from the (cell, local_entity) pairs in
263 // `this->mesh()`.
264 auto compute_entity_domains
265 = [](const auto& int_ents_mesh, int codim, const auto& c_to_e,
266 const auto& emap, bool inverse)
267 {
268 // TODO: This function would be much neater using
269 // `std::views::stride(2)` from C++ 23
270
271 // Get a list of entities to map to the argument/coefficient
272 // domain
273 std::vector<std::int32_t> entities;
274 entities.reserve(int_ents_mesh.size() / 2);
275 if (codim == 0)
276 {
277 // In the codim 0 case, we need to map from cells in
278 // `this->mesh()` to cells in the argument/coefficient mesh, so
279 // here we extract the cells.
280 for (std::size_t i = 0; i < int_ents_mesh.size(); i += 2)
281 entities.push_back(int_ents_mesh[i]);
282 }
283 else
284 {
285 // Otherwise the integration entities are sub-entities of the
286 // cells of `this->mesh()` and are themselves cells of the
287 // argument/coefficient mesh, so here we extract the entity
288 // index using the cell-to-entity connectivity.
289 for (std::size_t i = 0; i < int_ents_mesh.size(); i += 2)
290 {
291 entities.push_back(
292 c_to_e->links(int_ents_mesh[i])[int_ents_mesh[i + 1]]);
293 }
294 }
295
296 // Map from entity indices in `this->mesh()` to the corresponding
297 // cell indices in the argument/coefficient mesh
298 std::vector<std::int32_t> cells_mesh0
299 = emap.sub_topology_to_topology(entities, inverse);
300
301 // Create a list of (cell, local_entity_index) pairs in the
302 // argument/coefficient domain. Only the cell column is meaningful.
303 // For codim > 0 the entity is itself the cell, so it has
304 // no local index. The colummn is never used later on, but written
305 // like this for consistency for packing/assembly.
306 std::vector<std::int32_t> e = int_ents_mesh;
307 for (std::size_t i = 0; i < cells_mesh0.size(); ++i)
308 e[2 * i] = cells_mesh0[i];
309
310 return e;
311 };
312
313 // Map the integration entities of one integral to the
314 // argument/coefficient domain, checking first that the mapping is
315 // expressible: `compute_entity_domains` maps an integration entity
316 // to a *cell* of that mesh, so unless the meshes have equal
317 // dimension the integral's entity dimension must be that mesh's.
318 auto map_entities = [tdim, &topology, &compute_entity_domains](
319 IntegralType type, const auto& entities,
320 const mesh::Mesh<geometry_type>& mesh0,
321 const mesh::EntityMap& emap,
322 bool inverse) -> std::vector<std::int32_t>
323 {
324 if (type == IntegralType::cell)
325 return emap.sub_topology_to_topology(entities, inverse);
326
327 if (type == IntegralType::vertex)
328 {
329 throw std::invalid_argument(
330 "Vertex integrals are not supported for a form with an argument "
331 "or coefficient on another mesh. Supported types are cell, "
332 "exterior facet, interior facet and ridge.");
333 }
334
335 const int dim0 = mesh0.topology()->dim();
336 const int codim = tdim - dim0;
337 const int edim = integral_entity_dim(type, tdim);
338 assert(codim >= 0);
339 if (codim > 0 and edim != dim0)
340 {
341 throw std::invalid_argument(std::format(
342 "Cannot map integration entities of dimension {} to cells of a "
343 "mesh of dimension {}. An argument or coefficient on another mesh "
344 "must live on the entities being integrated over.",
345 edim, dim0));
346 }
347
348 std::shared_ptr<const graph::AdjacencyList<std::int32_t>> c_to_e
349 = topology.connectivity(tdim, edim);
350 assert(codim == 0 or c_to_e);
351 return compute_entity_domains(entities, codim, c_to_e, emap, inverse);
352 };
353
354 _edata.reserve(_function_spaces.size());
355 for (auto& space : _function_spaces)
356 {
357 // Working map: [integral type, integral_idx, kernel_idx]->entities
358 std::map<std::tuple<IntegralType, int, int>,
359 std::variant<std::vector<std::int32_t>,
360 std::span<const std::int32_t>>>
361 vdata;
362
363 if (auto mesh0 = space->mesh(); mesh0 == _mesh)
364 {
365 for (auto& [key, integral] : _integrals)
366 vdata.insert({key, std::span(integral.entities)});
367 }
368 else
369 {
370 // Find correct entity map
371 const mesh::EntityMap& emap = get_entity_map(mesh0);
372
373 // Determine direction of the map. We need to map from
374 // `this->mesh()` to `mesh0`, so if `emap->sub_topology()` isn't
375 // the source topology, we need the inverse map
376 bool inverse = emap.sub_topology() == mesh0->topology();
377 for (auto& [key, itg] : _integrals)
378 {
379 auto [type, idx, kernel_idx] = key;
380 std::vector<std::int32_t> e;
381 assert(mesh0);
382 e = map_entities(type, itg.entities, *mesh0, emap, inverse);
383
384 vdata.insert({key, std::move(e)});
385 }
386 }
387
388 _edata.push_back(std::move(vdata));
389 }
390
391 for (auto& [key, integral] : _integrals)
392 {
393 auto [type, idx, kernel_idx] = key;
394 for (int c : integral.coeffs)
395 {
396 if (auto mesh0 = coefficients.at(c)->function_space()->mesh();
397 mesh0 == _mesh)
398 {
399 _cdata.insert({{type, idx, c}, std::span(integral.entities)});
400 }
401 else
402 {
403 // Find correct entity map and determine direction of the map
404 const mesh::EntityMap& emap = get_entity_map(mesh0);
405 bool inverse = emap.sub_topology() == mesh0->topology();
406
407 std::vector<std::int32_t> e;
408 assert(mesh0);
409 e = map_entities(type, integral.entities, *mesh0, emap, inverse);
410 _cdata.insert({{type, idx, c}, std::move(e)});
411 }
412 }
413 }
414 }
415
416 // Copy constructor (deleted). _edata and _cdata cache std::spans
417 // aliasing the entity vectors owned by _integrals; a shallow copy
418 // would leave the copy's spans pointing into the original's data.
419 Form(const Form& form) = delete;
420
425#ifdef _MSC_VER
431 Form(Form&& form) noexcept = default;
432#else
433 Form(Form&& form) = default;
434#endif
435
437 ~Form() = default;
438
439 // Copy assignment (deleted). Same aliasing reason as the copy
440 // constructor.
441 Form& operator=(const Form& form) = delete;
442
448 Form& operator=(Form&& form) = default;
449
455 int rank() const { return _function_spaces.size(); }
456
459 std::shared_ptr<const mesh::Mesh<geometry_type>> mesh() const
460 {
461 return _mesh;
462 }
463
466 const std::vector<std::shared_ptr<const FunctionSpace<geometry_type>>>&
468 {
469 return _function_spaces;
470 }
471
480 std::function<void(scalar_type*, const scalar_type*, const scalar_type*,
481 const geometry_type*, const int*, const uint8_t*, void*)>
482 kernel(IntegralType type, int idx, int kernel_idx) const
483 {
484 auto it = _integrals.find({type, idx, kernel_idx});
485 if (it == _integrals.end())
486 throw std::out_of_range("Requested integral kernel not found.");
487 return it->second.kernel;
488 }
489
492 std::set<IntegralType> integral_types() const
493 {
494 std::set<IntegralType> types;
495 for (auto& [key, integral] : _integrals)
496 types.insert(std::get<0>(key));
497 return types;
498 }
499
513 std::vector<int> active_coeffs(IntegralType type, int idx) const
514 {
515 auto it = std::ranges::find_if(_integrals,
516 [type, idx](auto& x)
517 {
518 auto [t, idx_, kernel_idx] = x.first;
519 return t == type and idx_ == idx;
520 });
521 if (it == _integrals.end())
522 throw std::out_of_range("Could not find active coefficient list.");
523 return it->second.coeffs;
524 }
525
542 int num_integrals(IntegralType type, int kernel_idx) const
543 {
544 return std::ranges::count_if(_integrals,
545 [type, kernel_idx](auto& x)
546 {
547 auto [t, id, k_idx] = x.first;
548 return t == type and k_idx == kernel_idx;
549 });
550 }
551
586 std::span<const std::int32_t> domain(IntegralType type, int idx,
587 int kernel_idx) const
588 {
589 auto it = _integrals.find({type, idx, kernel_idx});
590 if (it == _integrals.end())
591 throw std::out_of_range("Requested domain not found.");
592 return it->second.entities;
593 }
594
630 std::span<const std::int32_t> domain_arg(IntegralType type, int rank, int idx,
631 int kernel_idx) const
632 {
633 auto it = _edata.at(rank).find({type, idx, kernel_idx});
634 if (it == _edata.at(rank).end())
635 throw std::out_of_range("Requested domain for argument not found.");
636
637 return std::visit([](const auto& v) -> std::span<const std::int32_t>
638 { return v; }, it->second);
639 }
640
655 std::span<const std::int32_t> domain_coeff(IntegralType type, int idx,
656 int c) const
657 {
658 auto it = _cdata.find({type, idx, c});
659 if (it == _cdata.end())
660 throw std::out_of_range("No domain for requested integral.");
661 return std::visit([](const auto& v) -> std::span<const std::int32_t>
662 { return v; }, it->second);
663 }
664
667 const std::vector<
668 std::shared_ptr<const Function<scalar_type, geometry_type>>>&
670 {
671 return _coefficients;
672 }
673
677 bool needs_facet_permutations() const { return _needs_facet_permutations; }
678
685 std::vector<int> coefficient_offsets() const
686 {
687 std::vector<int> n{0};
688 n.reserve(_coefficients.size() + 1);
689 for (auto& c : _coefficients)
690 {
691 if (!c)
692 throw std::runtime_error("Not all form coefficients have been set.");
693 n.push_back(n.back() + c->function_space()->element()->space_dimension());
694 }
695 return n;
696 }
697
700 const std::vector<std::shared_ptr<const Constant<scalar_type>>>&
701 constants() const
702 {
703 return _constants;
704 }
705
706private:
707 // Function spaces (one for each argument)
708 std::vector<std::shared_ptr<const FunctionSpace<geometry_type>>>
709 _function_spaces;
710
711 // Integrals (integral type, idx, kernel_idx)
712 std::map<std::tuple<IntegralType, int, int>,
714 _integrals;
715
716 // The mesh
717 std::shared_ptr<const mesh::Mesh<geometry_type>> _mesh;
718
719 // Form coefficients
720 std::vector<std::shared_ptr<const Function<scalar_type, geometry_type>>>
721 _coefficients;
722
723 // Constants associated with the Form
724 std::vector<std::shared_ptr<const Constant<scalar_type>>> _constants;
725
726 // True if permutation data needs to be passed into these integrals
727 bool _needs_facet_permutations;
728
729 // Mapped domain index data for argument functions.
730 //
731 // Consider:
732 //
733 // entities = this->domain(type, idx, kernel_idx);
734 // entities0 = _edata[0][{type, idx, kernel_idx}];
735 //
736 // Then `entities[i]` is a mesh entity index (e.g., cell index) in
737 // `_mesh`, and `entities0[i]` is the index of the same entity but in
738 // the mesh associated with the argument 0 (test function) space.
739 std::vector<std::map<
740 std::tuple<IntegralType, int, int>,
741 std::variant<std::vector<std::int32_t>, std::span<const std::int32_t>>>>
742 _edata;
743
744 // Mapped domain index data for coefficient functions.
745 //
746 // Consider:
747 //
748 // entities = this->domain(type, idx, kernel_idx);
749 // entities0 = _cdata[{type, idx, c}];
750 //
751 // where `c` is the coefficient index.
752 //
753 // Then `entities[i]` is a mesh entity index (e.g., cell index) in
754 // `_mesh`, and `entities0[i]` is the index of the same entity but in
755 // the mesh associated with the coefficient Function.
756 std::map<
757 std::tuple<IntegralType, int, int>,
758 std::variant<std::vector<std::int32_t>, std::span<const std::int32_t>>>
759 _cdata;
760};
761} // namespace dolfinx::fem
Constant (in space) value which can be attached to a Form.
Definition Constant.h:22
A representation of finite element variational forms.
Definition Form.h:177
U geometry_type
Geometry type.
Definition Form.h:183
Form & operator=(Form &&form)=default
int num_integrals(IntegralType type, int kernel_idx) const
Get number of integrals (kernels) for a given integral type and kernel index.
Definition Form.h:542
const std::vector< std::shared_ptr< const Function< scalar_type, geometry_type > > > & coefficients() const
Access coefficients.
Definition Form.h:669
~Form()=default
Destructor.
bool needs_facet_permutations() const
Get bool indicating whether permutation data needs to be passed into these integrals.
Definition Form.h:677
Form(Form &&form)=default
int rank() const
Rank of the form.
Definition Form.h:455
Form(const std::vector< std::shared_ptr< const FunctionSpace< geometry_type > > > &V, X &&integrals, std::shared_ptr< const mesh::Mesh< geometry_type > > mesh, const std::vector< std::shared_ptr< const Function< scalar_type, geometry_type > > > &coefficients, const std::vector< std::shared_ptr< const Constant< scalar_type > > > &constants, bool needs_facet_permutations, const std::vector< std::reference_wrapper< const mesh::EntityMap > > &entity_maps)
Create a finite element form.
Definition Form.h:215
const std::vector< std::shared_ptr< const Constant< scalar_type > > > & constants() const
Access constants.
Definition Form.h:701
std::vector< int > coefficient_offsets() const
Offset for each coefficient expansion array on a cell.
Definition Form.h:685
std::function< void(scalar_type *, const scalar_type *, const scalar_type *, const geometry_type *, const int *, const uint8_t *, void *)> kernel(IntegralType type, int idx, int kernel_idx) const
Get the kernel function for an integral.
Definition Form.h:482
std::span< const std::int32_t > domain_coeff(IntegralType type, int idx, int c) const
Coefficient function mesh integration entity indices.
Definition Form.h:655
std::shared_ptr< const mesh::Mesh< geometry_type > > mesh() const
Common mesh for the form (the 'integration domain').
Definition Form.h:459
std::vector< int > active_coeffs(IntegralType type, int idx) const
Indices of coefficients that are active for a given integral (kernel).
Definition Form.h:513
std::set< IntegralType > integral_types() const
Get types of integrals in the form.
Definition Form.h:492
const std::vector< std::shared_ptr< const FunctionSpace< geometry_type > > > & function_spaces() const
Function spaces for all arguments.
Definition Form.h:467
std::span< const std::int32_t > domain_arg(IntegralType type, int rank, int idx, int kernel_idx) const
Argument function mesh integration entity indices.
Definition Form.h:630
std::span< const std::int32_t > domain(IntegralType type, int idx, int kernel_idx) const
Mesh entity indices to integrate over for a given integral (kernel).
Definition Form.h:586
T scalar_type
Scalar type.
Definition Form.h:180
This class represents a finite element function space defined by a mesh, a finite element,...
Definition FunctionSpace.h:35
Definition Function.h:44
A bidirectional map relating entities in one topology to another.
Definition EntityMap.h:27
std::shared_ptr< const Topology > sub_topology() const
Get the sub-topology.
Definition EntityMap.cpp:20
std::shared_ptr< const Topology > topology() const
Get the (parent) topology.
Definition EntityMap.cpp:15
A Mesh consists of a set of connected and numbered mesh topological entities, and geometry data.
Definition Mesh.h:25
Topology stores the topology of a mesh, consisting of mesh entities and connectivity (incidence relat...
Definition Topology.h:49
void create_entity_permutations(int dim, int num_threads=1)
Compute entity permutations and reflections.
Definition Topology.cpp:1098
std::shared_ptr< const graph::AdjacencyList< std::int32_t > > connectivity(std::array< int, 2 > d0, std::array< int, 2 > d1) const
Get the connectivity from entities of topological dimension d0 to dimension d1.
Definition Topology.cpp:939
int dim() const noexcept
Topological dimension of the mesh.
Definition Topology.cpp:878
const std::vector< std::uint8_t > & get_entity_permutations(int dim) const
Get the numbers that encode the permutation to apply to each cell-local entity of a given dimension.
Definition Topology.cpp:973
Finite element method functionality.
Definition assemble_expression_impl.h:22
@ inverse
Inverse.
Definition FiniteElement.h:33
IntegralType
Type of integral.
Definition Form.h:43
@ vertex
Vertex.
Definition Form.h:47
@ interior_facet
Interior facet.
Definition Form.h:46
@ ridge
Ridge.
Definition Form.h:48
@ cell
Cell.
Definition Form.h:44
@ exterior_facet
Exterior facet.
Definition Form.h:45
constexpr int integral_entity_dim(IntegralType type, int tdim)
Topological dimension of the mesh entities an integral of the given type is over.
Definition Form.h:57
CellType
Cell type identifier.
Definition cell_types.h:24
int cell_num_entities(CellType type, int dim)
Number of entities of dimension.
Definition cell_types.cpp:97
Represents integral data, containing the kernel, and a list of entities to integrate over and the ind...
Definition Form.h:113
std::function< void(T *, const T *, const T *, const U *, const int *, const uint8_t *, void *)> kernel
The integration kernel.
Definition Form.h:137
integral_data(K &&kernel, V &&entities, W &&coeffs)
Create a structure to hold integral data.
Definition Form.h:128
std::vector< int > coeffs
Indices of coefficients (from the form) that are in this integral.
Definition Form.h:145
std::vector< std::int32_t > entities
The entities to integrate over for this integral. These are the entities in 'full' mesh.
Definition Form.h:141