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
84template <dolfinx::scalar T, std::floating_point U = scalar_value_t<T>>
86 const std::vector<std::reference_wrapper<const ufcx_form>>& ufcx_forms,
87 const std::vector<std::shared_ptr<const FunctionSpace<U>>>& spaces,
88 const std::vector<std::shared_ptr<const Function<T, U>>>& coefficients,
89 const std::vector<std::shared_ptr<const Constant<T>>>& constants,
90 const std::map<
92 std::vector<std::pair<std::int32_t, std::span<const std::int32_t>>>>&
93 subdomains,
94 const std::vector<std::reference_wrapper<const mesh::EntityMap>>&
95 entity_maps,
96 std::shared_ptr<const mesh::Mesh<U>> mesh = nullptr)
97{
98 for (std::size_t c = 0; c < coefficients.size(); ++c)
99 {
100 if (!coefficients[c])
101 {
102 throw std::invalid_argument(
103 std::format("Cannot create form. Coefficient {} is null.", c));
104 }
105 }
106
107 for (std::size_t c = 0; c < constants.size(); ++c)
108 {
109 if (!constants[c])
110 {
111 throw std::invalid_argument(
112 std::format("Cannot create form. Constant {} is null.", c));
113 }
114 }
115
116 for (const ufcx_form& ufcx_form : ufcx_forms)
117 {
118 if (ufcx_form.rank != (int)spaces.size())
119 throw std::invalid_argument("Wrong number of argument spaces for Form.");
120 if (ufcx_form.num_coefficients != (int)coefficients.size())
121 {
122 throw std::invalid_argument("Mismatch between number of expected and "
123 "provided Form coefficients.");
124 }
125
126 // Check Constants for rank and size consistency
127 if (ufcx_form.num_constants != (int)constants.size())
128 {
129 throw std::invalid_argument(std::format(
130 "Mismatch between number of expected and "
131 "provided Form Constants. Expected {} constants, but got {}.",
132 ufcx_form.num_constants, constants.size()));
133 }
134 for (std::size_t c = 0; c < constants.size(); ++c)
135 {
136 if (ufcx_form.constant_ranks[c] != (int)constants[c]->shape.size())
137 {
138 throw std::invalid_argument(std::format(
139 "Mismatch between expected and actual rank of "
140 "Form Constant. Rank of Constant {} should be {}, but got rank {}.",
141 c, ufcx_form.constant_ranks[c], constants[c]->shape.size()));
142 }
143 if (!std::equal(constants[c]->shape.begin(), constants[c]->shape.end(),
144 ufcx_form.constant_shapes[c]))
145 {
146 throw std::invalid_argument(
147 std::format("Mismatch between expected and actual shape of Form "
148 "Constant for Constant {}.",
149 c));
150 }
151 }
152 }
153
154 // Check argument and coefficient function spaces
155 for (std::size_t form_idx = 0; form_idx < ufcx_forms.size(); ++form_idx)
156 {
157 const ufcx_form& ufcx_form = ufcx_forms[form_idx];
158 for (std::size_t i = 0; i < spaces.size(); ++i)
159 {
160 assert(spaces[i]->elements(form_idx));
161 if (std::uint64_t element_hash = ufcx_form.finite_element_hashes[i];
162 element_hash != 0
163 and element_hash
164 != spaces[i]->elements(form_idx)->basix_element().hash())
165 {
166 throw std::invalid_argument(
167 "Cannot create form. Elements are different to "
168 "those used to compile the form.");
169 }
170 }
171
172 for (std::size_t c = 0; c < coefficients.size(); ++c)
173 {
174 std::uint64_t element_hash
175 = ufcx_form.finite_element_hashes[ufcx_form.rank + c];
176 if (element_hash != 0
177 and element_hash
178 != coefficients[c]
179 ->function_space()
180 ->elements(form_idx)
181 ->basix_element()
182 .hash())
183 {
184 throw std::invalid_argument(std::format(
185 "Cannot create form. Coefficient {} element differs from the "
186 "element used to compile the form.",
187 c));
188 }
189 }
190 }
191
192 // Extract mesh from FunctionSpace, and check they are the same
193 if (!mesh and !spaces.empty())
194 mesh = spaces.front()->mesh();
195 if (!mesh)
196 throw std::invalid_argument("No mesh could be associated with the Form.");
197
198 auto topology = mesh->topology();
199 assert(topology);
200 const int tdim = topology->dim();
201
202 // NOTE: This assumes all forms in mixed-topology meshes have the same
203 // integral offsets. Since the UFL forms for each type of cell should be
204 // the same, I think this assumption is OK.
205 const int* integral_offsets = ufcx_forms[0].get().form_integral_offsets;
206 std::array<int, 5> num_integrals_type;
207 for (std::size_t i = 0; i < num_integrals_type.size(); ++i)
208 num_integrals_type[i] = integral_offsets[i + 1] - integral_offsets[i];
209
210 // Create vertices, if required
211 if (num_integrals_type[vertex] > 0)
212 {
213 mesh->topology_mutable()->create_connectivity(0, tdim);
214 mesh->topology_mutable()->create_connectivity(tdim, 0);
215 }
216
217 // Create facets, if required
218 // NOTE: exterior_facet and interior_facet is declared in ufcx.h
219 if (num_integrals_type[exterior_facet] > 0
220 or num_integrals_type[interior_facet] > 0)
221 {
222 mesh->topology_mutable()->create_entities(tdim - 1);
223 mesh->topology_mutable()->create_connectivity(tdim - 1, tdim);
224 mesh->topology_mutable()->create_connectivity(tdim, tdim - 1);
225 }
226
227 // Create ridges, if required
228 if (num_integrals_type[ridge] > 0)
229 {
230 mesh->topology_mutable()->create_entities(tdim - 2);
231 mesh->topology_mutable()->create_connectivity(tdim - 2, tdim);
232 mesh->topology_mutable()->create_connectivity(tdim, tdim - 2);
233 }
234
235 // Get list of integral IDs, and load tabulate tensor into memory for
236 // each
237 std::map<std::tuple<IntegralType, int, int>, integral_data<T, U>> integrals;
238
239 auto check_geometry_hash
240 = [&geo = mesh->geometry()](const ufcx_integral& integral,
241 std::size_t cell_idx)
242 {
243 if (integral.coordinate_element_hash != geo.cmaps().at(cell_idx).hash())
244 {
245 throw std::invalid_argument(std::format(
246 "Generated integral geometry element does not match mesh geometry: "
247 "{}, {}",
248 integral.coordinate_element_hash, geo.cmaps().at(cell_idx).hash()));
249 }
250 };
251
252 // Attach cell kernels
253 bool needs_facet_permutations = false;
254 {
255 std::vector<std::int32_t> default_cells;
256 std::span<const int> ids(ufcx_forms[0].get().form_integral_ids
257 + integral_offsets[cell],
258 num_integrals_type[cell]);
259 auto sd = subdomains.find(IntegralType::cell);
260 for (std::size_t form_idx = 0; form_idx < ufcx_forms.size(); ++form_idx)
261 {
262 const ufcx_form& ufcx_form = ufcx_forms[form_idx];
263 for (int i = 0; i < num_integrals_type[cell]; ++i)
264 {
265 const int id = ids[i];
266 ufcx_integral* integral
267 = ufcx_form.form_integrals[integral_offsets[cell] + i];
268 assert(integral);
269 check_geometry_hash(*integral, form_idx);
270
271 // Build list of active coefficients
272 std::vector<int> active_coeffs;
273 for (int j = 0; j < ufcx_form.num_coefficients; ++j)
274 {
275 if (integral->enabled_coefficients[j])
276 active_coeffs.push_back(j);
277 }
278
279 impl::kernel_t<T, U> k = impl::extract_kernel<T, U>(integral);
280 if (!k)
281 {
282 throw std::invalid_argument(
283 "UFCx kernel function is NULL. Check requested types.");
284 }
285
286 // Build list of entities to assemble over
287 if (id == -1)
288 {
289 // Default kernel, operates on all (owned) cells
290 assert(topology->index_maps(tdim).at(form_idx));
291 default_cells.resize(
292 topology->index_maps(tdim).at(form_idx)->size_local(), 0);
293 std::iota(default_cells.begin(), default_cells.end(), 0);
294 integrals.insert({{IntegralType::cell, i, form_idx},
295 {k, default_cells, active_coeffs}});
296 }
297 else if (sd != subdomains.end())
298 {
299 // NOTE: This requires that pairs are sorted
300 auto it = std::ranges::lower_bound(sd->second, id, std::less<>{},
301 [](auto& a) { return a.first; });
302 if (it != sd->second.end() and it->first == id)
303 {
304 integrals.insert({{IntegralType::cell, i, form_idx},
305 {k,
306 std::vector<std::int32_t>(it->second.begin(),
307 it->second.end()),
308 active_coeffs}});
309 }
310 }
311
312 if (integral->needs_facet_permutations)
313 needs_facet_permutations = true;
314 }
315 }
316 }
317
318 // Attach interior facet kernels
319 {
320 std::vector<std::int32_t> default_facets_int;
321 std::span<const int> ids(ufcx_forms[0].get().form_integral_ids
322 + integral_offsets[interior_facet],
323 num_integrals_type[interior_facet]);
324 auto sd = subdomains.find(IntegralType::interior_facet);
325 for (std::size_t form_idx = 0; form_idx < ufcx_forms.size(); ++form_idx)
326 {
327 const ufcx_form& ufcx_form = ufcx_forms[form_idx];
328
329 // Create indicator for interprocess facets
330 std::vector<std::int8_t> interprocess_marker;
331 if (num_integrals_type[interior_facet] > 0)
332 {
333 assert(topology->index_map(tdim - 1));
334 const std::vector<std::int32_t>& interprocess_facets
335 = topology->interprocess_facets();
336 std::int32_t num_facets = topology->index_map(tdim - 1)->size_local()
337 + topology->index_map(tdim - 1)->num_ghosts();
338 interprocess_marker.resize(num_facets, 0);
339 std::ranges::for_each(interprocess_facets,
340 [&interprocess_marker](auto f)
341 { interprocess_marker[f] = 1; });
342 }
343
344 for (int i = 0; i < num_integrals_type[interior_facet]; ++i)
345 {
346 const int id = ids[i];
347 ufcx_integral* integral
348 = ufcx_form.form_integrals[integral_offsets[interior_facet] + i];
349 assert(integral);
350 check_geometry_hash(*integral, form_idx);
351
352 std::vector<int> active_coeffs;
353 for (int j = 0; j < ufcx_form.num_coefficients; ++j)
354 {
355 if (integral->enabled_coefficients[j])
356 active_coeffs.push_back(j);
357 }
358
359 impl::kernel_t<T, U> k = impl::extract_kernel<T, U>(integral);
360 assert(k);
361
362 // Build list of entities to assembler over
363 auto f_to_c = topology->connectivity(tdim - 1, tdim);
364 assert(f_to_c);
365 auto c_to_f = topology->connectivity(tdim, tdim - 1);
366 assert(c_to_f);
367 if (id == -1)
368 {
369 // Default kernel, operates on all (owned) interior facets
370 assert(topology->index_map(tdim - 1));
371 std::int32_t num_facets = topology->index_map(tdim - 1)->size_local();
372 default_facets_int.reserve(4 * num_facets);
373 for (std::int32_t f = 0; f < num_facets; ++f)
374 {
375 if (f_to_c->num_links(f) == 2)
376 {
377 std::array<std::int32_t, 4> pairs
378 = impl::get_cell_facet_pairs<2>(f, f_to_c->links(f), *c_to_f);
379 default_facets_int.insert(default_facets_int.end(), pairs.begin(),
380 pairs.end());
381 }
382 else if (interprocess_marker[f])
383 {
384 throw std::runtime_error(
385 "Cannot compute interior facet integral over interprocess "
386 "facet. Please use ghost mode shared facet when creating the "
387 "mesh");
388 }
389 }
390 integrals.insert({{IntegralType::interior_facet, i, form_idx},
391 {k, default_facets_int, active_coeffs}});
392 }
393 else if (sd != subdomains.end())
394 {
395 auto it = std::ranges::lower_bound(sd->second, id, std::less{},
396 [](auto& a) { return a.first; });
397 if (it != sd->second.end() and it->first == id)
398 {
399 integrals.insert({{IntegralType::interior_facet, i, form_idx},
400 {k,
401 std::vector<std::int32_t>(it->second.begin(),
402 it->second.end()),
403 active_coeffs}});
404 }
405 }
406
407 if (integral->needs_facet_permutations)
408 needs_facet_permutations = true;
409 }
410 }
411 }
412
413 // Attach exterior entity integrals
414 {
417 {
418 const std::size_t dim = integral_entity_dim(itg_type, tdim);
419
420 const std::function<std::vector<std::int32_t>(const mesh::Topology&,
422 get_default_integration_entities
423 = [dim](const mesh::Topology& topology, IntegralType itype)
424 {
425 if (itype == IntegralType::exterior_facet)
426 {
427 // Integrate over all owned exterior facets
428 return mesh::exterior_facet_indices(topology);
429 }
430 else
431 {
432 // Integrate over all owned entities
433 std::int32_t num_entities = topology.index_map(dim)->size_local();
434 std::vector<std::int32_t> entities(num_entities);
435 std::iota(entities.begin(), entities.end(), 0);
436 return entities;
437 }
438 };
439
440 std::vector<std::int32_t> default_entities_ext;
441
442 std::span<const int> ids(ufcx_forms[0].get().form_integral_ids
443 + integral_offsets[(std::int8_t)itg_type],
444 num_integrals_type[(std::int8_t)itg_type]);
445 auto sd = subdomains.find(itg_type);
446 for (std::size_t form_idx = 0; form_idx < ufcx_forms.size(); ++form_idx)
447 {
448 const ufcx_form& ufcx_form = ufcx_forms[form_idx];
449 for (int i = 0; i < num_integrals_type[(std::int8_t)itg_type]; ++i)
450 {
451 const int id = ids[i];
452 ufcx_integral* integral
453 = ufcx_form.form_integrals[integral_offsets[(std::int8_t)itg_type]
454 + i];
455 assert(integral);
456 check_geometry_hash(*integral, form_idx);
457
458 std::vector<int> active_coeffs;
459 for (int j = 0; j < ufcx_form.num_coefficients; ++j)
460 {
461 if (integral->enabled_coefficients[j])
462 active_coeffs.push_back(j);
463 }
464
465 impl::kernel_t<T, U> k = impl::extract_kernel<T, U>(integral);
466
467 // Build list of entities to assembler over
468 auto e_to_c = topology->connectivity(dim, tdim);
469 assert(e_to_c);
470 auto c_to_e = topology->connectivity(tdim, dim);
471 assert(c_to_e);
472 if (id == -1)
473 {
474 std::vector default_entities
475 = get_default_integration_entities(*topology, itg_type);
476 // Default kernel
477 default_entities_ext.reserve(2 * default_entities.size());
478 for (std::int32_t e : default_entities)
479 {
480 // There will only be one pair for an exterior facet integral
481 std::array<std::int32_t, 2> pair = impl::get_cell_entity_pairs<1>(
482 e, e_to_c->links(e), *c_to_e);
483 default_entities_ext.insert(default_entities_ext.end(),
484 pair.begin(), pair.end());
485 }
486 integrals.insert({{itg_type, i, form_idx},
487 {k, default_entities_ext, active_coeffs}});
488 }
489 else if (sd != subdomains.end())
490 {
491 // NOTE: This requires that pairs are sorted
492 auto it = std::ranges::lower_bound(sd->second, id, std::less<>{},
493 [](auto& a) { return a.first; });
494 if (it != sd->second.end() and it->first == id)
495 {
496 integrals.insert({{itg_type, i, form_idx},
497 {k,
498 std::vector<std::int32_t>(it->second.begin(),
499 it->second.end()),
500 active_coeffs}});
501 }
502 }
503
504 if (integral->needs_facet_permutations)
505 needs_facet_permutations = true;
506 }
507 }
508 }
509 }
510
511 return Form<T, U>(spaces, std::move(integrals), mesh, coefficients, constants,
512 needs_facet_permutations, entity_maps);
513}
514
529template <dolfinx::scalar T, std::floating_point U = scalar_value_t<T>>
531 const ufcx_form& ufcx_form,
532 const std::vector<std::shared_ptr<const FunctionSpace<U>>>& spaces,
533 const std::map<std::string, std::shared_ptr<const Function<T, U>>>&
534 coefficients,
535 const std::map<std::string, std::shared_ptr<const Constant<T>>>& constants,
536 const std::map<
538 std::vector<std::pair<std::int32_t, std::span<const std::int32_t>>>>&
539 subdomains,
540 const std::vector<std::reference_wrapper<const mesh::EntityMap>>&
541 entity_maps,
542 std::shared_ptr<const mesh::Mesh<U>> mesh = nullptr)
543{
544 // Place coefficients in appropriate order
545 std::vector<std::shared_ptr<const Function<T, U>>> coeff_map;
546 for (const std::string& name : get_coefficient_names(ufcx_form))
547 {
548 if (auto it = coefficients.find(name); it != coefficients.end())
549 coeff_map.push_back(it->second);
550 else
551 {
552 throw std::out_of_range(
553 std::format("Form coefficient \"{}\" not provided.", name));
554 }
555 }
556
557 // Place constants in appropriate order
558 std::vector<std::shared_ptr<const Constant<T>>> const_map;
559 for (const std::string& name : get_constant_names(ufcx_form))
560 {
561 if (auto it = constants.find(name); it != constants.end())
562 const_map.push_back(it->second);
563 else
564 {
565 throw std::out_of_range(
566 std::format("Form constant \"{}\" not provided.", name));
567 }
568 }
569
570 return create_form_factory({ufcx_form}, spaces, coeff_map, const_map,
571 subdomains, entity_maps, mesh);
572}
573
592template <dolfinx::scalar T, std::floating_point U = scalar_value_t<T>>
594 ufcx_form* (*fptr)(),
595 const std::vector<std::shared_ptr<const FunctionSpace<U>>>& spaces,
596 const std::map<std::string, std::shared_ptr<const Function<T, U>>>&
597 coefficients,
598 const std::map<std::string, std::shared_ptr<const Constant<T>>>& constants,
599 const std::map<
601 std::vector<std::pair<std::int32_t, std::span<const std::int32_t>>>>&
602 subdomains,
603 const std::vector<std::reference_wrapper<const mesh::EntityMap>>&
604 entity_maps,
605 std::shared_ptr<const mesh::Mesh<U>> mesh = nullptr)
606{
607 ufcx_form* form = fptr();
608 Form<T, U> L = create_form<T, U>(*form, spaces, coefficients, constants,
609 subdomains, entity_maps, mesh);
610 std::free(form);
611 return L;
612}
613} // 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:36
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:937
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:958
Functions for computing integration domains.
Functions supporting mesh operations.
Finite element method functionality.
Definition assemble_expression_impl.h:28
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:85
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:530
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:304
Represents integral data, containing the kernel, and a list of entities to integrate over and the ind...
Definition Form.h:113