DOLFINx 0.12.0.0
DOLFINx C++
Loading...
Searching...
No Matches
assemble_vector_impl.h
1// Copyright (C) 2018-2026 Garth N. Wells and Paul T. Kühner
2//
3// This file is part of DOLFINx (https://www.fenicsproject.org)
4//
5// SPDX-License-Identifier: LGPL-3.0-or-later
6
7#pragma once
8
9#include "Constant.h"
10#include "DirichletBC.h"
11#include "DofMap.h"
12#include "Form.h"
13#include "assemble_matrix_impl.h"
14#include "traits.h"
15#include <algorithm>
16#include <array>
17#include <basix/mdspan.hpp>
18#include <concepts>
19#include <cstdint>
20#include <dolfinx/common/IndexMap.h>
21#include <dolfinx/mesh/Geometry.h>
22#include <dolfinx/mesh/Mesh.h>
23#include <dolfinx/mesh/Topology.h>
24#include <functional>
25#include <memory>
26#include <optional>
27#include <span>
28#include <stdexcept>
29#include <type_traits>
30#include <vector>
31
32namespace dolfinx::fem
33{
34template <dolfinx::scalar T, std::floating_point U>
35class DirichletBC;
36}
37
38namespace dolfinx::fem::impl
39{
41using mdspan2_t = md::mdspan<const std::int32_t, md::dextents<std::size_t, 2>>;
43
70template <typename V, MDSpan2Int32 XD, std::floating_point U,
71 dolfinx::scalar T = typename std::remove_cvref_t<V>::value_type>
72 requires AssemblyVector<V, T>
73void assemble_cells_vector(
74 V&& b, GeometryPack<XD, U> geometry, const IndexList auto& cells,
75 const FormArgumentCells<T> auto& arg0, const FEkernel<T, U> auto& kernel,
76 std::span<const T> constants,
77 md::mdspan<const T, md::dextents<std::size_t, 2>> coeffs,
78 ScratchBuffer<T> auto be_b, ScratchBuffer<U> auto cdofs_b)
79{
80 if (std::ranges::empty(cells))
81 return;
82
83 // By value: the sizes below fold only if not read through a
84 // reference (see fem::DofMapPack). mdspan and span are two-word
85 // copies.
86 const auto dmap = arg0.dofmap.map;
87 const auto bs = arg0.dofmap.bs;
88 // By reference: a generated range (e.g. iota) does not convert to a
89 // span, and a caller's std::vector must not be copied.
90 const auto& cells0 = arg0.dofmap.entities;
91 const auto& P0 = arg0.transform;
92 std::span<const std::uint32_t> cell_info0 = arg0.cell_info;
93
94 const auto ndofs_x = geometry.dofmap.extent(1);
95 const auto ndofs = dmap.extent(1);
96 assert(be_b.size() == static_cast<std::size_t>(bs) * ndofs);
97 assert(cdofs_b.size() == 3 * static_cast<std::size_t>(ndofs_x));
98
99 const std::int32_t* dmap_ptr = dmap.data_handle();
100 const T* coeffs_data = coeffs.data_handle();
101 const auto cstride = coeffs.extent(1);
102
103 // P0 does not change across cells in this call, so whether it is a
104 // set (non-null) transform is loop-invariant.
105 const bool p0_set = is_transform_set(P0);
106
107 const std::size_t num_cells = std::ranges::size(cells);
108 assert(std::ranges::size(cells0) == num_cells);
109
110 // The integration-domain and argument cell lists are usually the same
111 // span, making the second lookup redundant. Loop-invariant, so the
112 // branch predicts perfectly. Both concepts require a sized range.
113 bool same_cells = false;
114 if constexpr (std::ranges::contiguous_range<decltype(cells)>
115 and std::ranges::contiguous_range<decltype(cells0)>)
116 {
117 same_cells = std::ranges::data(cells0) == std::ranges::data(cells)
118 and std::ranges::size(cells0) == num_cells;
119 }
120
121 // Iterate over active cells
122 for (std::size_t index = 0; index < num_cells; ++index)
123 {
124 // Integration domain cell and test function cell
125 const std::int32_t c = cells[index];
126 const std::int32_t c0 = same_cells ? c : cells0[index];
127
128 gather_cell_coordinates(geometry, c, cdofs_b.data());
129
130 // Tabulate vector for cell
131 std::ranges::fill(be_b, T(0));
132 kernel(be_b.data(), coeffs_data + index * cstride, constants.data(),
133 cdofs_b.data(), nullptr, nullptr, nullptr);
134 if (p0_set)
135 P0(std::span<T>(be_b), cell_info0, c0, 1);
136
137 // Scatter cell vector to 'global' vector array
138 const std::int32_t* dofs
139 = dmap_ptr + static_cast<std::ptrdiff_t>(c0) * ndofs;
140 for (std::size_t i = 0; i < ndofs; ++i)
141 {
142 const std::int32_t dof = bs * dofs[i];
143 for (int k = 0; k < bs; ++k)
144 b[dof + k] += be_b[bs * i + k];
145 }
146 }
147}
148
185template <typename V, MDSpan2Int32 XD, std::floating_point U,
186 dolfinx::scalar T = typename std::remove_cvref_t<V>::value_type>
187 requires AssemblyVector<V, T>
188void assemble_entities_vector(
189 V&& b, GeometryPack<XD, U> geometry,
190 md::mdspan<const std::int32_t,
191 std::extents<std::size_t, md::dynamic_extent, 2>>
192 entities,
193 const FormArgumentEntities<T> auto& arg0, const FEkernel<T, U> auto& kernel,
194 std::span<const T> constants,
195 md::mdspan<const T, md::dextents<std::size_t, 2>> coeffs,
196 md::mdspan<const std::uint8_t, md::dextents<std::size_t, 2>> perms,
197 ScratchBuffer<T> auto be_b, ScratchBuffer<U> auto cdofs_b)
198{
199 if (entities.empty())
200 return;
201
202 // By value: the sizes below fold only if not read through a
203 // reference (see fem::DofMapPack). mdspan and span are two-word
204 // copies.
205 const auto dmap = arg0.dofmap.map;
206 const auto bs = arg0.dofmap.bs;
207 const auto entities0 = arg0.dofmap.entities;
208 const auto& P0 = arg0.transform;
209 std::span<const std::uint32_t> cell_info0 = arg0.cell_info;
210
211 const auto num_dofs = dmap.extent(1);
212 const auto num_x_dofs_cell = geometry.dofmap.extent(1);
213 assert(cdofs_b.size() == 3 * static_cast<std::size_t>(num_x_dofs_cell));
214 assert(be_b.size() == static_cast<std::size_t>(bs) * num_dofs);
215 assert(entities0.size() == entities.size());
216
217 const std::int32_t* dmap_ptr = dmap.data_handle();
218
219 const bool p0_set = is_transform_set(P0);
220
221 const T* coeffs_data = coeffs.data_handle();
222 const std::size_t cstride = coeffs.extent(1);
223
224 for (std::size_t f = 0; f < entities.extent(0); ++f)
225 {
226 // Cell in the integration domain, local facet index relative to the
227 // integration domain cell, and cell in the test function mesh
228 std::int32_t cell = entities(f, 0);
229 std::int32_t local_entity = entities(f, 1);
230 std::int32_t cell0 = entities0(f, 0);
231
232 gather_cell_coordinates(geometry, cell, cdofs_b.data());
233
234 // Permutations
235 std::uint8_t perm = perms.empty() ? 0 : perms(cell, local_entity);
236
237 // Tabulate element vector
238 std::ranges::fill(be_b, T(0));
239 kernel(be_b.data(), coeffs_data + f * cstride, constants.data(),
240 cdofs_b.data(), &local_entity, &perm, nullptr);
241 if (p0_set)
242 P0(std::span<T>(be_b), cell_info0, cell0, 1);
243
244 // Add to global vector
245 const std::int32_t* dofs
246 = dmap_ptr + static_cast<std::ptrdiff_t>(cell0) * num_dofs;
247 for (std::size_t i = 0; i < num_dofs; ++i)
248 for (int k = 0; k < bs; ++k)
249 b[bs * dofs[i] + k] += be_b[bs * i + k];
250 }
251}
252
282template <typename V, MDSpan2Int32 XD, std::floating_point U,
283 dolfinx::scalar T = typename std::remove_cvref_t<V>::value_type>
284 requires AssemblyVector<V, T>
285void assemble_interior_facets_vector(
286 V&& b, GeometryPack<XD, U> geometry,
287 md::mdspan<const std::int32_t,
288 std::extents<std::size_t, md::dynamic_extent, 2, 2>>
289 facets,
290 const FormArgumentFacets<T> auto& arg0, const FEkernel<T, U> auto& kernel,
291 std::span<const T> constants,
292 md::mdspan<const T, md::extents<std::size_t, md::dynamic_extent, 2,
293 md::dynamic_extent>>
294 coeffs,
295 md::mdspan<const std::uint8_t, md::dextents<std::size_t, 2>> perms,
296 ScratchBuffer<T> auto be_b, ScratchBuffer<U> auto cdofs_b)
297{
298 if (facets.empty())
299 return;
300
301 // By value: the sizes below fold only if not read through a
302 // reference (see fem::DofMapPack). mdspan and span are two-word
303 // copies.
304 const auto dmap = arg0.dofmap.map;
305 const auto bs = arg0.dofmap.bs;
306 const auto facets0 = arg0.dofmap.entities;
307 const auto& P0 = arg0.transform;
308 std::span<const std::uint32_t> cell_info0 = arg0.cell_info;
309
310 const auto num_x_dofs_cell = geometry.dofmap.extent(1);
311 const auto dmap_size = dmap.extent(1);
312 assert(cdofs_b.size() == 2 * static_cast<std::size_t>(num_x_dofs_cell) * 3);
313 assert(be_b.size() == static_cast<std::size_t>(bs) * 2 * dmap_size);
314 assert(facets0.size() == facets.size());
315 U* cdofs0 = cdofs_b.data();
316 U* cdofs1 = cdofs_b.data() + num_x_dofs_cell * 3;
317
318 const T* coeffs_data = coeffs.data_handle();
319 const auto cstride = 2 * coeffs.extent(2);
320
321 const bool p0_set = is_transform_set(P0);
322
323 for (std::size_t f = 0; f < facets.extent(0); ++f)
324 {
325 // Cells in integration domain and test function domain meshes
326 std::array<std::int32_t, 2> cells{facets(f, 0, 0), facets(f, 1, 0)};
327 std::array<std::int32_t, 2> cells0{facets0(f, 0, 0), facets0(f, 1, 0)};
328
329 // Local facet indices
330 std::array<std::int32_t, 2> local_facet{facets(f, 0, 1), facets(f, 1, 1)};
331
332 gather_cell_coordinates(geometry, cells[0], cdofs0);
333 gather_cell_coordinates(geometry, cells[1], cdofs1);
334
335 // Get dofmaps for cells. When integrating over interfaces between
336 // two domains, the test function might only be defined on one side,
337 // so we check which cells exist in the test function domain.
338 std::span dmap0 = cells0[0] >= 0 ? std::span(&dmap(cells0[0], 0), dmap_size)
339 : std::span<const std::int32_t>();
340 std::span dmap1 = cells0[1] >= 0 ? std::span(&dmap(cells0[1], 0), dmap_size)
341 : std::span<const std::int32_t>();
342
343 // Tabulate element vector
344 std::ranges::fill(be_b, T(0));
345 std::array perm = perms.empty()
346 ? std::array<std::uint8_t, 2>{0, 0}
347 : std::array{perms(cells[0], local_facet[0]),
348 perms(cells[1], local_facet[1])};
349 kernel(be_b.data(), coeffs_data + f * cstride, constants.data(),
350 cdofs_b.data(), local_facet.data(), perm.data(), nullptr);
351
352 if (p0_set and cells0[0] >= 0)
353 P0(std::span<T>(be_b), cell_info0, cells0[0], 1);
354 if (p0_set and cells0[1] >= 0)
355 {
356 std::span sub_be(be_b.data() + bs * dmap_size, bs * dmap_size);
357 P0(sub_be, cell_info0, cells0[1], 1);
358 }
359
360 // Add element vector to global vector
361 for (std::size_t i = 0; i < dmap0.size(); ++i)
362 {
363 std::int32_t dof = bs * dmap0[i];
364 std::int32_t offset = bs * i;
365 for (int k = 0; k < bs; ++k)
366 b[dof + k] += be_b[offset + k];
367 }
368 for (std::size_t i = 0; i < dmap1.size(); ++i)
369 {
370 std::int32_t dof = bs * dmap1[i];
371 std::int32_t offset = bs * (i + dmap_size);
372 for (int k = 0; k < bs; ++k)
373 b[dof + k] += be_b[offset + k];
374 }
375 }
376}
377
398template <dolfinx::scalar T, std::floating_point U, typename V>
399 requires AssemblyVector<V, T>
400void lift_bc(V&& b, const Form<T, U>& a, auto bs0, auto bs1,
401 std::span<const T> constants,
402 const std::map<std::pair<IntegralType, int>,
403 std::pair<std::span<const T>, int>>& coefficients,
404 std::span<const T> bc_values1,
405 std::span<const std::int8_t> bc_markers1, std::span<const T> x0,
406 T alpha)
407{
408 // Deduce runtime block sizes as fallback when compile-time sizes
409 // not given. The block size of the dofmap and indexmap is the same
410 // on all sub-topologies.
411 assert(bs0 == a.function_spaces()[0]->dofmaps().front()->bs());
412 assert(bs1 == a.function_spaces()[1]->dofmaps().front()->bs());
413
414 auto lifting_fn = [bs0, bs1, alpha, &b, &bc_values1, &bc_markers1,
415 &x0](auto rows, auto cols, auto Ae)
416 {
417 const std::size_t nc = cols.size() * bs1;
418 for (std::size_t i = 0; i < cols.size(); ++i)
419 {
420 for (int k = 0; k < bs1; ++k)
421 {
422 const std::int32_t ii = cols[i] * bs1 + k;
423 if (bc_markers1[ii])
424 {
425 const T x_bc = bc_values1[ii];
426 const T _x0 = x0.empty() ? 0 : x0[ii];
427 for (std::size_t j = 0; j < rows.size(); ++j)
428 {
429 for (int m = 0; m < bs0; ++m)
430 {
431 const std::int32_t jj = rows[j] * bs0 + m;
432 b[jj] -= Ae[(j * bs0 + m) * nc + (i * bs1 + k)] * alpha
433 * (x_bc - _x0);
434 }
435 }
436 }
437 }
438 }
439 };
440
441 // Use dolfinx::fem::impl::assemble_matrix assembler to work on the
442 // vector b. With LiftingMode=true, the kernel is only called on cells
443 // that have BC-constrained DOFs in the column space.
444 std::shared_ptr<const mesh::Mesh<U>> mesh = a.mesh();
445 assert(mesh);
446 std::span x = mesh->geometry().x();
447 md::mdspan<const U, md::extents<std::size_t, md::dynamic_extent, 3>> _x(
448 x.data(), x.size() / 3, 3);
449 impl::assemble_matrix<true>(lifting_fn, a, _x, constants, coefficients, {},
450 bc_markers1);
451}
452
461template <typename V, std::floating_point U,
462 dolfinx::scalar T = typename std::remove_cvref_t<V>::value_type>
463 requires AssemblyVector<V, T>
464void assemble_vector(
465 V&& b, const Form<T, U>& L,
466 md::mdspan<const U, md::extents<std::size_t, md::dynamic_extent, 3>> x,
467 std::span<const T> constants,
468 const std::map<std::pair<IntegralType, int>,
469 std::pair<std::span<const T>, int>>& coefficients)
470{
471 // Integration domain mesh
472 std::shared_ptr<const mesh::Mesh<U>> mesh = L.mesh();
473 assert(mesh);
474
475 // Test function mesh
476 auto mesh0 = L.function_spaces().at(0)->mesh();
477 assert(mesh0);
478
479 const int num_cell_types = mesh->topology()->cell_types().size();
480 for (int cell_type_idx = 0; cell_type_idx < num_cell_types; ++cell_type_idx)
481 {
482 // Geometry dofmap and data
483 mdspan2_t x_dofmap = mesh->geometry().dofmaps().at(cell_type_idx);
484 GeometryPack geometry{x_dofmap, x};
485
486 // Get dofmap data
487 assert(L.function_spaces().at(0));
488 auto element = L.function_spaces().at(0)->elements(cell_type_idx);
489 assert(element);
490 std::shared_ptr<const fem::DofMap> dofmap
491 = L.function_spaces().at(0)->dofmaps().at(cell_type_idx);
492 assert(dofmap);
493 auto dofs = dofmap->map();
494 const int bs = dofmap->bs();
495
496 // Buffers reused across all integral kernels for this cell type,
497 // sized for the worst case (interior facets, which touch two
498 // cells). The kernels require an exactly-sized buffer, so the
499 // one-cell integrals get the leading half.
500 std::vector<T> be_buffer(2 * bs * dofs.extent(1));
501 std::vector<U> cdofs_buffer(2 * 3 * x_dofmap.extent(1));
502 std::span be_b(be_buffer);
503 std::span cdofs_b(cdofs_buffer);
504 std::span be_b1 = be_b.first(bs * dofs.extent(1));
505 std::span cdofs_b1 = cdofs_b.first(3 * x_dofmap.extent(1));
506
507 const fem::DofTransformKernel<T> auto& P0
508 = element->template dof_transformation_fn<T>(doftransform::standard);
509
510 std::span<const std::uint32_t> cell_info0;
511 if (element->needs_dof_transformations())
512 {
513 mesh0->topology_mutable()->create_cell_permutations();
514 cell_info0 = std::span(mesh0->topology()->get_cell_permutation_info());
515 }
516
517 for (int i = 0; i < L.num_integrals(IntegralType::cell, 0); ++i)
518 {
519 auto fn = L.kernel(IntegralType::cell, i, cell_type_idx);
520 assert(fn);
521 std::span cells = L.domain(IntegralType::cell, i, cell_type_idx);
522 std::span cells0 = L.domain_arg(IntegralType::cell, 0, i, cell_type_idx);
523 auto& [coeffs, cstride] = coefficients.at({IntegralType::cell, i});
524 assert(cells.size() * cstride == coeffs.size());
525 impl::dispatch_bs(
526 bs,
527 [&b, &geometry, &cells, &dofs, &cells0, &P0, &cell_info0, &fn,
528 &constants, &coeffs, cstride, &be_b1, &cdofs_b1](auto bs)
529 {
530 impl::assemble_cells_vector(
531 b, geometry, cells,
532 FormArgument{DofMapPack{dofs, bs, cells0}, P0, cell_info0}, fn,
533 constants, md::mdspan(coeffs.data(), cells.size(), cstride),
534 be_b1, cdofs_b1);
535 });
536 }
537
538 md::mdspan<const std::uint8_t, md::dextents<std::size_t, 2>> facet_perms;
539 if (L.needs_facet_permutations())
540 {
541 facet_perms = impl::entity_permutations(
542 *mesh->topology_mutable(), IntegralType::interior_facet,
543 mesh->topology()->cell_types()[0]);
544 }
545
546 using mdspanx2_t
547 = md::mdspan<const std::int32_t,
548 md::extents<std::size_t, md::dynamic_extent, 2>>;
549 using mdspanx22_t
550 = md::mdspan<const std::int32_t,
551 md::extents<std::size_t, md::dynamic_extent, 2, 2>>;
552 using mdspanx2x_t
553 = md::mdspan<const T, md::extents<std::size_t, md::dynamic_extent, 2,
554 md::dynamic_extent>>;
555
556 for (int i = 0; i < L.num_integrals(IntegralType::interior_facet, 0); ++i)
557 {
558 auto fn = L.kernel(IntegralType::interior_facet, i, 0);
559 assert(fn);
560 auto& [coeffs, cstride]
561 = coefficients.at({IntegralType::interior_facet, i});
562 std::span facets = L.domain(IntegralType::interior_facet, i, 0);
563 std::span facets1 = L.domain_arg(IntegralType::interior_facet, 0, i, 0);
564 assert((facets.size() / 4) * 2 * cstride == coeffs.size());
565
566 mdspanx22_t facets_mdspan(facets.data(), facets.size() / 4, 2, 2);
567 mdspanx22_t facets1_mdspan(facets1.data(), facets1.size() / 4, 2, 2);
568 impl::dispatch_bs(
569 bs,
570 [&b, &geometry, &facets_mdspan, &dofs, &facets1_mdspan, &P0,
571 &cell_info0, &fn, &constants, &coeffs, &facets, cstride,
572 &facet_perms, &be_b, &cdofs_b](auto bs)
573 {
574 impl::assemble_interior_facets_vector(
575 b, geometry, facets_mdspan,
576 FormArgument{DofMapPack{dofs, bs, facets1_mdspan}, P0,
577 cell_info0},
578 fn, constants,
579 mdspanx2x_t(coeffs.data(), facets.size() / 4, 2, cstride),
580 facet_perms, be_b, cdofs_b);
581 });
582 }
583
584 for (auto itg_type : {fem::IntegralType::exterior_facet,
586 {
587 const int num_itg = L.num_integrals(itg_type, 0);
588 if (num_itg == 0)
589 continue;
590
591 // Each integral type is over entities of a different
592 // codimension, so only the permutations this form actually
593 // integrates over are computed.
594 md::mdspan<const std::uint8_t, md::dextents<std::size_t, 2>> perms;
595 if (L.needs_facet_permutations())
596 {
597 perms = impl::entity_permutations(*mesh->topology_mutable(), itg_type,
598 mesh->topology()->cell_types()[0]);
599 }
600
601 for (int i = 0; i < num_itg; ++i)
602 {
603 auto fn = L.kernel(itg_type, i, 0);
604 assert(fn);
605 auto& [coeffs, cstride] = coefficients.at({itg_type, i});
606 std::span e = L.domain(itg_type, i, 0);
607 mdspanx2_t entities(e.data(), e.size() / 2, 2);
608 std::span e1 = L.domain_arg(itg_type, 0, i, 0);
609 mdspanx2_t entities1(e1.data(), e1.size() / 2, 2);
610 assert((entities.size() / 2) * cstride == coeffs.size());
611 impl::dispatch_bs(
612 bs,
613 [&b, &geometry, &entities, &dofs, &entities1, &P0, &cell_info0, &fn,
614 &constants, &coeffs, cstride, &perms, &be_b1, &cdofs_b1](auto bs)
615 {
616 impl::assemble_entities_vector(
617 b, geometry, entities,
618 FormArgument{DofMapPack{dofs, bs, entities1}, P0, cell_info0},
619 fn, constants,
620 md::mdspan(coeffs.data(), entities.extent(0), cstride), perms,
621 be_b1, cdofs_b1);
622 });
623 }
624 }
625 }
626}
627
634template <typename V, std::floating_point U,
635 dolfinx::scalar T = typename std::remove_cvref_t<V>::value_type>
636 requires AssemblyVector<V, T>
637void assemble_vector(
638 V&& b, const Form<T, U>& L, std::span<const T> constants,
639 const std::map<std::pair<IntegralType, int>,
640 std::pair<std::span<const T>, int>>& coefficients)
641{
642 using mdspanx3_t
643 = md::mdspan<const U, md::extents<std::size_t, md::dynamic_extent, 3>>;
644
645 std::shared_ptr<const mesh::Mesh<U>> mesh = L.mesh();
646 assert(mesh);
647 auto x = mesh->geometry().x();
648 impl::assemble_vector(b, L, mdspanx3_t(x.data(), x.size() / 3, 3), constants,
649 coefficients);
650}
651} // namespace dolfinx::fem::impl
Degree-of-freedom map representations and tools.
Definition DirichletBC.h:259
void cells(la::SparsityPattern &pattern, const std::pair< R0, R1 > &cells, std::array< std::reference_wrapper< const DofMap >, 2 > dofmaps)
Iterate over cells and insert entries into sparsity pattern.
Definition sparsitybuild.h:37
Finite element method functionality.
Definition assemble_expression_impl.h:22
@ standard
Standard.
Definition FiniteElement.h:31
@ 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 bool is_transform_set(const F &fn)
Whether a DofTransformKernel fn should be invoked.
Definition traits.h:34