DOLFINx 0.12.0.0
DOLFINx C++
Loading...
Searching...
No Matches
form_factory.h
Go to the documentation of this file.
1// Copyright (C) 2013-2026 Johan Hake, Jan Blechta, Garth N. Wells and Paul T.
2// Kühner
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 "Constant.h"
11#include "Form.h"
12#include "Function.h"
13#include "FunctionSpace.h"
14#include "integration_domains.h"
15#include "kernel.h"
16#include <algorithm>
17#include <array>
18#include <cassert>
19#include <concepts>
20#include <cstddef>
21#include <cstdint>
22#include <cstdlib>
23#include <dolfinx/common/types.h>
24#include <dolfinx/mesh/EntityMap.h>
25#include <dolfinx/mesh/Mesh.h>
26#include <dolfinx/mesh/Topology.h>
27#include <dolfinx/mesh/utils.h>
28#include <format>
29#include <functional>
30#include <map>
31#include <memory>
32#include <numeric>
33#include <ranges>
34#include <span>
35#include <stdexcept>
36#include <string>
37#include <tuple>
38#include <ufcx.h>
39#include <utility>
40#include <vector>
41
44
45namespace dolfinx::fem
46{
50std::vector<std::string> get_coefficient_names(const ufcx_form& ufcx_form);
51
55std::vector<std::string> get_constant_names(const ufcx_form& ufcx_form);
56
75template <dolfinx::scalar T, std::floating_point U = scalar_value_t<T>>
77 const std::vector<std::reference_wrapper<const ufcx_form>>& ufcx_forms,
78 const std::vector<std::shared_ptr<const FunctionSpace<U>>>& spaces,
79 const std::vector<std::shared_ptr<const Function<T, U>>>& coefficients,
80 const std::vector<std::shared_ptr<const Constant<T>>>& constants,
81 const std::map<
83 std::vector<std::pair<std::int32_t, std::span<const std::int32_t>>>>&
84 subdomains,
85 const std::vector<std::reference_wrapper<const mesh::EntityMap>>&
86 entity_maps,
87 std::shared_ptr<const mesh::Mesh<U>> mesh = nullptr)
88{
89 for (const ufcx_form& ufcx_form : ufcx_forms)
90 {
91 if (ufcx_form.rank != (int)spaces.size())
92 throw std::invalid_argument("Wrong number of argument spaces for Form.");
93 if (ufcx_form.num_coefficients != (int)coefficients.size())
94 {
95 throw std::invalid_argument("Mismatch between number of expected and "
96 "provided Form coefficients.");
97 }
98
99 // Check Constants for rank and size consistency
100 if (ufcx_form.num_constants != (int)constants.size())
101 {
102 throw std::invalid_argument(std::format(
103 "Mismatch between number of expected and "
104 "provided Form Constants. Expected {} constants, but got {}.",
105 ufcx_form.num_constants, constants.size()));
106 }
107 for (std::size_t c = 0; c < constants.size(); ++c)
108 {
109 if (ufcx_form.constant_ranks[c] != (int)constants[c]->shape.size())
110 {
111 throw std::invalid_argument(std::format(
112 "Mismatch between expected and actual rank of "
113 "Form Constant. Rank of Constant {} should be {}, but got rank {}.",
114 c, ufcx_form.constant_ranks[c], constants[c]->shape.size()));
115 }
116 if (!std::equal(constants[c]->shape.begin(), constants[c]->shape.end(),
117 ufcx_form.constant_shapes[c]))
118 {
119 throw std::invalid_argument(
120 std::format("Mismatch between expected and actual shape of Form "
121 "Constant for Constant {}.",
122 c));
123 }
124 }
125 }
126
127 // Check argument function spaces
128 for (std::size_t form_idx = 0; form_idx < ufcx_forms.size(); ++form_idx)
129 {
130 for (std::size_t i = 0; i < spaces.size(); ++i)
131 {
132 assert(spaces[i]->elements(form_idx));
133 if (auto element_hash
134 = ufcx_forms[form_idx].get().finite_element_hashes[i];
135 element_hash != 0
136 and element_hash
137 != spaces[i]->elements(form_idx)->basix_element().hash())
138 {
139 throw std::invalid_argument(
140 "Cannot create form. Elements are different to "
141 "those used to compile the form.");
142 }
143 }
144 }
145
146 // Extract mesh from FunctionSpace, and check they are the same
147 if (!mesh and !spaces.empty())
148 mesh = spaces.front()->mesh();
149 if (!mesh)
150 throw std::invalid_argument("No mesh could be associated with the Form.");
151
152 auto topology = mesh->topology();
153 assert(topology);
154 const int tdim = topology->dim();
155
156 // NOTE: This assumes all forms in mixed-topology meshes have the same
157 // integral offsets. Since the UFL forms for each type of cell should be
158 // the same, I think this assumption is OK.
159 const int* integral_offsets = ufcx_forms[0].get().form_integral_offsets;
160 std::array<int, 5> num_integrals_type;
161 for (std::size_t i = 0; i < num_integrals_type.size(); ++i)
162 num_integrals_type[i] = integral_offsets[i + 1] - integral_offsets[i];
163
164 // Create vertices, if required
165 if (num_integrals_type[vertex] > 0)
166 {
167 mesh->topology_mutable()->create_connectivity(0, tdim);
168 mesh->topology_mutable()->create_connectivity(tdim, 0);
169 }
170
171 // Create facets, if required
172 // NOTE: exterior_facet and interior_facet is declared in ufcx.h
173 if (num_integrals_type[exterior_facet] > 0
174 or num_integrals_type[interior_facet] > 0)
175 {
176 mesh->topology_mutable()->create_entities(tdim - 1);
177 mesh->topology_mutable()->create_connectivity(tdim - 1, tdim);
178 mesh->topology_mutable()->create_connectivity(tdim, tdim - 1);
179 }
180
181 // Create ridges, if required
182 if (num_integrals_type[ridge] > 0)
183 {
184 mesh->topology_mutable()->create_entities(tdim - 2);
185 mesh->topology_mutable()->create_connectivity(tdim - 2, tdim);
186 mesh->topology_mutable()->create_connectivity(tdim, tdim - 2);
187 }
188
189 // Get list of integral IDs, and load tabulate tensor into memory for
190 // each
191 std::map<std::tuple<IntegralType, int, int>, integral_data<T, U>> integrals;
192
193 auto check_geometry_hash
194 = [&geo = mesh->geometry()](const ufcx_integral& integral,
195 std::size_t cell_idx)
196 {
197 if (integral.coordinate_element_hash != geo.cmaps().at(cell_idx).hash())
198 {
199 throw std::runtime_error(std::format(
200 "Generated integral geometry element does not match mesh geometry: "
201 "{}, {}",
202 integral.coordinate_element_hash, geo.cmaps().at(cell_idx).hash()));
203 }
204 };
205
206 // Attach cell kernels
207 bool needs_facet_permutations = false;
208 {
209 std::vector<std::int32_t> default_cells;
210 std::span<const int> ids(ufcx_forms[0].get().form_integral_ids
211 + integral_offsets[cell],
212 num_integrals_type[cell]);
213 auto sd = subdomains.find(IntegralType::cell);
214 for (std::size_t form_idx = 0; form_idx < ufcx_forms.size(); ++form_idx)
215 {
216 const ufcx_form& ufcx_form = ufcx_forms[form_idx];
217 for (int i = 0; i < num_integrals_type[cell]; ++i)
218 {
219 const int id = ids[i];
220 ufcx_integral* integral
221 = ufcx_form.form_integrals[integral_offsets[cell] + i];
222 assert(integral);
223 check_geometry_hash(*integral, form_idx);
224
225 // Build list of active coefficients
226 std::vector<int> active_coeffs;
227 for (int j = 0; j < ufcx_form.num_coefficients; ++j)
228 {
229 if (integral->enabled_coefficients[j])
230 active_coeffs.push_back(j);
231 }
232
233 impl::kernel_t<T, U> k = impl::extract_kernel<T, U>(integral);
234 if (!k)
235 {
236 throw std::invalid_argument(
237 "UFCx kernel function is NULL. Check requested types.");
238 }
239
240 // Build list of entities to assemble over
241 if (id == -1)
242 {
243 // Default kernel, operates on all (owned) cells
244 assert(topology->index_maps(tdim).at(form_idx));
245 default_cells.resize(
246 topology->index_maps(tdim).at(form_idx)->size_local(), 0);
247 std::iota(default_cells.begin(), default_cells.end(), 0);
248 integrals.insert({{IntegralType::cell, i, form_idx},
249 {k, default_cells, active_coeffs}});
250 }
251 else if (sd != subdomains.end())
252 {
253 // NOTE: This requires that pairs are sorted
254 auto it = std::ranges::lower_bound(sd->second, id, std::less<>{},
255 [](auto& a) { return a.first; });
256 if (it != sd->second.end() and it->first == id)
257 {
258 integrals.insert({{IntegralType::cell, i, form_idx},
259 {k,
260 std::vector<std::int32_t>(it->second.begin(),
261 it->second.end()),
262 active_coeffs}});
263 }
264 }
265
266 if (integral->needs_facet_permutations)
267 needs_facet_permutations = true;
268 }
269 }
270 }
271
272 // Attach interior facet kernels
273 {
274 std::vector<std::int32_t> default_facets_int;
275 std::span<const int> ids(ufcx_forms[0].get().form_integral_ids
276 + integral_offsets[interior_facet],
277 num_integrals_type[interior_facet]);
278 auto sd = subdomains.find(IntegralType::interior_facet);
279 for (std::size_t form_idx = 0; form_idx < ufcx_forms.size(); ++form_idx)
280 {
281 const ufcx_form& ufcx_form = ufcx_forms[form_idx];
282
283 // Create indicator for interprocess facets
284 std::vector<std::int8_t> interprocess_marker;
285 if (num_integrals_type[interior_facet] > 0)
286 {
287 assert(topology->index_map(tdim - 1));
288 const std::vector<std::int32_t>& interprocess_facets
289 = topology->interprocess_facets();
290 std::int32_t num_facets = topology->index_map(tdim - 1)->size_local()
291 + topology->index_map(tdim - 1)->num_ghosts();
292 interprocess_marker.resize(num_facets, 0);
293 std::ranges::for_each(interprocess_facets,
294 [&interprocess_marker](auto f)
295 { interprocess_marker[f] = 1; });
296 }
297
298 for (int i = 0; i < num_integrals_type[interior_facet]; ++i)
299 {
300 const int id = ids[i];
301 ufcx_integral* integral
302 = ufcx_form.form_integrals[integral_offsets[interior_facet] + i];
303 assert(integral);
304 check_geometry_hash(*integral, form_idx);
305
306 std::vector<int> active_coeffs;
307 for (int j = 0; j < ufcx_form.num_coefficients; ++j)
308 {
309 if (integral->enabled_coefficients[j])
310 active_coeffs.push_back(j);
311 }
312
313 impl::kernel_t<T, U> k = impl::extract_kernel<T, U>(integral);
314 assert(k);
315
316 // Build list of entities to assembler over
317 auto f_to_c = topology->connectivity(tdim - 1, tdim);
318 assert(f_to_c);
319 auto c_to_f = topology->connectivity(tdim, tdim - 1);
320 assert(c_to_f);
321 if (id == -1)
322 {
323 // Default kernel, operates on all (owned) interior facets
324 assert(topology->index_map(tdim - 1));
325 std::int32_t num_facets = topology->index_map(tdim - 1)->size_local();
326 default_facets_int.reserve(4 * num_facets);
327 for (std::int32_t f = 0; f < num_facets; ++f)
328 {
329 if (f_to_c->num_links(f) == 2)
330 {
331 std::array<std::int32_t, 4> pairs
332 = impl::get_cell_facet_pairs<2>(f, f_to_c->links(f), *c_to_f);
333 default_facets_int.insert(default_facets_int.end(), pairs.begin(),
334 pairs.end());
335 }
336 else if (interprocess_marker[f])
337 {
338 throw std::runtime_error(
339 "Cannot compute interior facet integral over interprocess "
340 "facet. Please use ghost mode shared facet when creating the "
341 "mesh");
342 }
343 }
344 integrals.insert({{IntegralType::interior_facet, i, form_idx},
345 {k, default_facets_int, active_coeffs}});
346 }
347 else if (sd != subdomains.end())
348 {
349 auto it = std::ranges::lower_bound(sd->second, id, std::less{},
350 [](auto& a) { return a.first; });
351 if (it != sd->second.end() and it->first == id)
352 {
353 integrals.insert({{IntegralType::interior_facet, i, form_idx},
354 {k,
355 std::vector<std::int32_t>(it->second.begin(),
356 it->second.end()),
357 active_coeffs}});
358 }
359 }
360
361 if (integral->needs_facet_permutations)
362 needs_facet_permutations = true;
363 }
364 }
365 }
366
367 // Attach exterior entity integrals
368 {
371 {
372 const std::size_t dim = integral_entity_dim(itg_type, tdim);
373
374 const std::function<std::vector<std::int32_t>(const mesh::Topology&,
376 get_default_integration_entities
377 = [dim](const mesh::Topology& topology, IntegralType itype)
378 {
379 if (itype == IntegralType::exterior_facet)
380 {
381 // Integrate over all owned exterior facets
382 return mesh::exterior_facet_indices(topology);
383 }
384 else
385 {
386 // Integrate over all owned entities
387 std::int32_t num_entities = topology.index_map(dim)->size_local();
388 std::vector<std::int32_t> entities(num_entities);
389 std::iota(entities.begin(), entities.end(), 0);
390 return entities;
391 }
392 };
393
394 std::vector<std::int32_t> default_entities_ext;
395
396 std::span<const int> ids(ufcx_forms[0].get().form_integral_ids
397 + integral_offsets[(std::int8_t)itg_type],
398 num_integrals_type[(std::int8_t)itg_type]);
399 auto sd = subdomains.find(itg_type);
400 for (std::size_t form_idx = 0; form_idx < ufcx_forms.size(); ++form_idx)
401 {
402 const ufcx_form& ufcx_form = ufcx_forms[form_idx];
403 for (int i = 0; i < num_integrals_type[(std::int8_t)itg_type]; ++i)
404 {
405 const int id = ids[i];
406 ufcx_integral* integral
407 = ufcx_form.form_integrals[integral_offsets[(std::int8_t)itg_type]
408 + i];
409 assert(integral);
410 check_geometry_hash(*integral, form_idx);
411
412 std::vector<int> active_coeffs;
413 for (int j = 0; j < ufcx_form.num_coefficients; ++j)
414 {
415 if (integral->enabled_coefficients[j])
416 active_coeffs.push_back(j);
417 }
418
419 impl::kernel_t<T, U> k = impl::extract_kernel<T, U>(integral);
420
421 // Build list of entities to assembler over
422 auto e_to_c = topology->connectivity(dim, tdim);
423 assert(e_to_c);
424 auto c_to_e = topology->connectivity(tdim, dim);
425 assert(c_to_e);
426 if (id == -1)
427 {
428 std::vector default_entities
429 = get_default_integration_entities(*topology, itg_type);
430 // Default kernel
431 default_entities_ext.reserve(2 * default_entities.size());
432 for (std::int32_t e : default_entities)
433 {
434 // There will only be one pair for an exterior facet integral
435 std::array<std::int32_t, 2> pair = impl::get_cell_entity_pairs<1>(
436 e, e_to_c->links(e), *c_to_e);
437 default_entities_ext.insert(default_entities_ext.end(),
438 pair.begin(), pair.end());
439 }
440 integrals.insert({{itg_type, i, form_idx},
441 {k, default_entities_ext, active_coeffs}});
442 }
443 else if (sd != subdomains.end())
444 {
445 // NOTE: This requires that pairs are sorted
446 auto it = std::ranges::lower_bound(sd->second, id, std::less<>{},
447 [](auto& a) { return a.first; });
448 if (it != sd->second.end() and it->first == id)
449 {
450 integrals.insert({{itg_type, i, form_idx},
451 {k,
452 std::vector<std::int32_t>(it->second.begin(),
453 it->second.end()),
454 active_coeffs}});
455 }
456 }
457
458 if (integral->needs_facet_permutations)
459 needs_facet_permutations = true;
460 }
461 }
462 }
463 }
464
465 return Form<T, U>(spaces, std::move(integrals), mesh, coefficients, constants,
466 needs_facet_permutations, entity_maps);
467}
468
483template <dolfinx::scalar T, std::floating_point U = scalar_value_t<T>>
485 const ufcx_form& ufcx_form,
486 const std::vector<std::shared_ptr<const FunctionSpace<U>>>& spaces,
487 const std::map<std::string, std::shared_ptr<const Function<T, U>>>&
488 coefficients,
489 const std::map<std::string, std::shared_ptr<const Constant<T>>>& constants,
490 const std::map<
492 std::vector<std::pair<std::int32_t, std::span<const std::int32_t>>>>&
493 subdomains,
494 const std::vector<std::reference_wrapper<const mesh::EntityMap>>&
495 entity_maps,
496 std::shared_ptr<const mesh::Mesh<U>> mesh = nullptr)
497{
498 // Place coefficients in appropriate order
499 std::vector<std::shared_ptr<const Function<T, U>>> coeff_map;
500 for (const std::string& name : get_coefficient_names(ufcx_form))
501 {
502 if (auto it = coefficients.find(name); it != coefficients.end())
503 coeff_map.push_back(it->second);
504 else
505 {
506 throw std::runtime_error(
507 std::format("Form coefficient \"{}\" not provided.", name));
508 }
509 }
510
511 // Place constants in appropriate order
512 std::vector<std::shared_ptr<const Constant<T>>> const_map;
513 for (const std::string& name : get_constant_names(ufcx_form))
514 {
515 if (auto it = constants.find(name); it != constants.end())
516 const_map.push_back(it->second);
517 else
518 throw std::runtime_error(
519 std::format("Form constant \"{}\" not provided.", name));
520 }
521
522 return create_form_factory({ufcx_form}, spaces, coeff_map, const_map,
523 subdomains, entity_maps, mesh);
524}
525
544template <dolfinx::scalar T, std::floating_point U = scalar_value_t<T>>
546 ufcx_form* (*fptr)(),
547 const std::vector<std::shared_ptr<const FunctionSpace<U>>>& spaces,
548 const std::map<std::string, std::shared_ptr<const Function<T, U>>>&
549 coefficients,
550 const std::map<std::string, std::shared_ptr<const Constant<T>>>& constants,
551 const std::map<
553 std::vector<std::pair<std::int32_t, std::span<const std::int32_t>>>>&
554 subdomains,
555 const std::vector<std::reference_wrapper<const mesh::EntityMap>>&
556 entity_maps,
557 std::shared_ptr<const mesh::Mesh<U>> mesh = nullptr)
558{
559 ufcx_form* form = fptr();
560 Form<T, U> L = create_form<T, U>(*form, spaces, coefficients, constants,
561 subdomains, entity_maps, mesh);
562 std::free(form);
563 return L;
564}
565} // 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
This class represents a finite element function space defined by a mesh, a finite element,...
Definition FunctionSpace.h:35
Definition Function.h:44
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
std::shared_ptr< const common::IndexMap > index_map(int dim) const
Get the IndexMap that describes the parallel distribution of the mesh entities.
Definition Topology.cpp:918
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
Functions for computing integration domains.
Functions supporting mesh operations.
Finite element method functionality.
Definition assemble_expression_impl.h:22
std::vector< std::string > get_constant_names(const ufcx_form &ufcx_form)
Get the name of each constant in a UFC form.
Definition utils.cpp:148
Form< T, U > create_form_factory(const std::vector< std::reference_wrapper< const ufcx_form > > &ufcx_forms, const std::vector< std::shared_ptr< const FunctionSpace< U > > > &spaces, const std::vector< std::shared_ptr< const Function< T, U > > > &coefficients, const std::vector< std::shared_ptr< const Constant< T > > > &constants, const std::map< IntegralType, std::vector< std::pair< std::int32_t, std::span< const std::int32_t > > > > &subdomains, const std::vector< std::reference_wrapper< const mesh::EntityMap > > &entity_maps, std::shared_ptr< const mesh::Mesh< U > > mesh=nullptr)
Create a Form from UFCx input with coefficients and constants passed in the required order.
Definition form_factory.h:76
std::vector< std::string > get_coefficient_names(const ufcx_form &ufcx_form)
Definition utils.cpp:141
Form< T, U > create_form(const ufcx_form &ufcx_form, const std::vector< std::shared_ptr< const FunctionSpace< U > > > &spaces, const std::map< std::string, std::shared_ptr< const Function< T, U > > > &coefficients, const std::map< std::string, std::shared_ptr< const Constant< T > > > &constants, const std::map< IntegralType, std::vector< std::pair< std::int32_t, std::span< const std::int32_t > > > > &subdomains, const std::vector< std::reference_wrapper< const mesh::EntityMap > > &entity_maps, std::shared_ptr< const mesh::Mesh< U > > mesh=nullptr)
Create a Form from UFC input with coefficients and constants resolved by name.
Definition form_factory.h:484
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
Mesh data structures and algorithms on meshes.
Definition DofMap.h:32
std::vector< std::int32_t > exterior_facet_indices(const Topology &topology, int facet_type_idx)
Compute the indices of all exterior facets that are owned by the caller.
Definition utils.cpp:302
Represents integral data, containing the kernel, and a list of entities to integrate over and the ind...
Definition Form.h:113