DOLFINx 0.12.0.0
DOLFINx C++
Loading...
Searching...
No Matches
assemble_matrix_impl.h
1// Copyright (C) 2018-2026 Garth N. Wells
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 "DofMap.h"
10#include "Form.h"
11#include "FunctionSpace.h"
12#include "traits.h"
13#include "utils.h"
14#include <algorithm>
15#include <concepts>
16#include <dolfinx/la/utils.h>
17#include <dolfinx/mesh/Geometry.h>
18#include <dolfinx/mesh/Mesh.h>
19#include <dolfinx/mesh/Topology.h>
20#include <functional>
21#include <iterator>
22#include <span>
23#include <stdexcept>
24#include <tuple>
25#include <vector>
26
27namespace dolfinx::fem::impl
28{
29bool has_bc(auto& dofs, auto& bc, auto bs)
30{
31 for (auto dof : dofs)
32 for (int k = 0; k < bs; ++k)
33 if (bc[bs * dof + k])
34 return true;
35 return false;
36};
37
39using mdspan2_t = md::mdspan<const std::int32_t, md::dextents<std::size_t, 2>>;
40
94template <bool LiftingMode, dolfinx::scalar T, std::floating_point U>
95void assemble_cells_matrix(
96 la::MatSet<T> auto mat_set, MDSpan2Int32 auto x_dofmap,
97 MDSpan2Floating<U> auto x, std::span<const std::int32_t> cells,
98 const DofMapPackCells auto& dofmap0,
99 const fem::DofTransformKernel<T> auto& P0,
100 const DofMapPackCells auto& dofmap1,
101 const fem::DofTransformKernel<T> auto& P1T,
102 std::span<const std::int8_t> bc0, std::span<const std::int8_t> bc1,
103 const FEkernel<T, U> auto& kernel,
104 md::mdspan<const T, md::dextents<std::size_t, 2>> coeffs,
105 std::span<const T> constants, std::span<const std::uint32_t> cell_info0,
106 std::span<const std::uint32_t> cell_info1, std::span<T> Ab,
107 std::span<U> cdofs_b)
108{
109 if (cells.empty())
110 return;
111
112 const auto [dmap0, bs0, cells0] = dofmap0;
113 const auto [dmap1, bs1, cells1] = dofmap1;
114
115 std::size_t num_dofs0 = dmap0.extent(1);
116 std::size_t num_dofs1 = dmap1.extent(1);
117 std::size_t ndim0 = bs0 * num_dofs0;
118 std::size_t ndim1 = bs1 * num_dofs1;
119
120 const U* x_ptr = x.data_handle();
121 const std::int32_t gdim = x.extent(1);
122 const std::int32_t* x_dofmap_ptr = x_dofmap.data_handle();
123 const std::int32_t num_x_dofs_cell = x_dofmap.extent(1);
124
125 assert(Ab.size() >= ndim0 * ndim1);
126 assert(cdofs_b.size() >= 3 * x_dofmap.extent(1));
127 auto Ae = Ab.first(ndim0 * ndim1);
128
129 // P0/P1T do not change across cells in this call, so whether each is a
130 // set (non-null) transform is loop-invariant -- checked once here
131 // rather than on every cell.
132 const bool p0_set = is_transform_set(P0);
133 const bool p1t_set = is_transform_set(P1T);
134
135 const T* coeffs_data = coeffs.data_handle();
136 const std::size_t cstride = coeffs.extent(1);
137
138 // Iterate over active cells
139 assert(cells0.size() == cells.size());
140 assert(cells1.size() == cells.size());
141 for (std::size_t c = 0; c < cells.size(); ++c)
142 {
143 // Cell index in integration domain mesh (c), test function mesh
144 // (c0) and trial function mesh (c1)
145 std::int32_t cell = cells[c];
146 std::int32_t cell0 = cells0[c];
147 std::int32_t cell1 = cells1[c];
148
149 std::span dofs0(dmap0.data_handle() + cell0 * num_dofs0, num_dofs0);
150 std::span dofs1(dmap1.data_handle() + cell1 * num_dofs1, num_dofs1);
151
152 // In "LiftingMode" only execute kernel if there are BCs on column space
153 if constexpr (LiftingMode)
154 {
155 if (!has_bc(dofs1, bc1, bs1))
156 continue;
157 }
158
159 // Get cell coordinates/geometry
160 for (std::int32_t i = 0; i < num_x_dofs_cell; ++i)
161 {
162 const U* _x_ptr = x_ptr + x_dofmap_ptr[cell * num_x_dofs_cell + i] * gdim;
163 std::copy_n(_x_ptr, gdim, cdofs_b.data() + 3 * i);
164 }
165
166 // Tabulate tensor
167 std::ranges::fill(Ae, 0);
168 kernel(Ae.data(), coeffs_data + c * cstride, constants.data(),
169 cdofs_b.data(), nullptr, nullptr, nullptr);
170
171 // Compute A = P_0 \tilde{A} P_1^T (dof transformation)
172 if (p0_set)
173 P0(Ae, cell_info0, cell0, ndim1); // B = P0 \tilde{A}
174 if (p1t_set)
175 P1T(Ae, cell_info1, cell1, ndim0); // A = B P1_T
176
177 // In lifting mode only BC dofs are assembled, while in standard mode these
178 // row/column dofs are zeroed.
179 if constexpr (!LiftingMode)
180 {
181 // Zero rows and columns for BCs
182 if (!bc0.empty())
183 {
184 for (std::size_t i = 0; i < num_dofs0; ++i)
185 {
186 for (int k = 0; k < bs0; ++k)
187 {
188 if (bc0[bs0 * dofs0[i] + k])
189 {
190 // Zero row bs0 * i + k
191 const int row = bs0 * i + k;
192 std::fill_n(std::next(Ae.begin(), ndim1 * row), ndim1, 0);
193 }
194 }
195 }
196 }
197
198 if (!bc1.empty())
199 {
200 for (std::size_t j = 0; j < num_dofs1; ++j)
201 {
202 for (int k = 0; k < bs1; ++k)
203 {
204 if (bc1[bs1 * dofs1[j] + k])
205 {
206 // Zero column bs1 * j + k
207 int col = bs1 * j + k;
208 for (std::size_t row = 0; row < ndim0; ++row)
209 Ae[row * ndim1 + col] = 0;
210 }
211 }
212 }
213 }
214 }
215
216 mat_set(dofs0, dofs1, Ae);
217 }
218}
219
282template <bool LiftingMode, dolfinx::scalar T, std::floating_point U>
283void assemble_entities(
284 la::MatSet<T> auto mat_set, MDSpan2Int32 auto x_dofmap,
285 MDSpan2Floating<U> auto x,
286 md::mdspan<const std::int32_t,
287 std::extents<std::size_t, md::dynamic_extent, 2>>
288 entities,
289 const DofMapPackEntities auto& dofmap0,
290 const fem::DofTransformKernel<T> auto& P0,
291 const DofMapPackEntities auto& dofmap1,
292 const fem::DofTransformKernel<T> auto& P1T,
293 std::span<const std::int8_t> bc0, std::span<const std::int8_t> bc1,
294 const FEkernel<T, U> auto& kernel,
295 md::mdspan<const T, md::dextents<std::size_t, 2>> coeffs,
296 std::span<const T> constants, std::span<const std::uint32_t> cell_info0,
297 std::span<const std::uint32_t> cell_info1,
298 md::mdspan<const std::uint8_t, md::dextents<std::size_t, 2>> perms,
299 std::span<T> Ab, std::span<U> cdofs_b)
300{
301 if (entities.empty())
302 return;
303
304 const auto [dmap0, bs0, entities0] = dofmap0;
305 const auto [dmap1, bs1, entities1] = dofmap1;
306
307 std::size_t num_dofs0 = dmap0.extent(1);
308 std::size_t num_dofs1 = dmap1.extent(1);
309 std::size_t ndim0 = bs0 * num_dofs0;
310 std::size_t ndim1 = bs1 * num_dofs1;
311 assert(entities0.size() == entities.size());
312 assert(entities1.size() == entities.size());
313 assert(Ab.size() >= ndim0 * ndim1);
314 assert(cdofs_b.size() >= 3 * x_dofmap.extent(1));
315 auto Ae = Ab.first(ndim0 * ndim1);
316
317 const U* x_ptr = x.data_handle();
318 const std::int32_t gdim = x.extent(1);
319 const std::int32_t* x_dofmap_ptr = x_dofmap.data_handle();
320 const std::int32_t num_x_dofs_cell = x_dofmap.extent(1);
321
322 // P0/P1T do not change across entities in this call, so whether each is a
323 // set (non-null) transform is loop-invariant -- checked once here rather
324 // than on every entity.
325 const bool p0_set = is_transform_set(P0);
326 const bool p1t_set = is_transform_set(P1T);
327
328 const T* coeffs_data = coeffs.data_handle();
329 const std::size_t cstride = coeffs.extent(1);
330
331 for (std::size_t f = 0; f < entities.extent(0); ++f)
332 {
333 // Cell in the integration domain, local entity index relative to the
334 // integration domain cell, and cells in the test and trial function
335 // meshes
336 std::int32_t cell = entities(f, 0);
337 std::int32_t local_entity = entities(f, 1);
338 std::int32_t cell0 = entities0(f, 0);
339 std::int32_t cell1 = entities1(f, 0);
340
341 std::span dofs0(dmap0.data_handle() + cell0 * num_dofs0, num_dofs0);
342 std::span dofs1(dmap1.data_handle() + cell1 * num_dofs1, num_dofs1);
343
344 // Check for BCs on column space
345 if constexpr (LiftingMode)
346 {
347 if (!has_bc(dofs1, bc1, bs1))
348 continue;
349 }
350
351 // Get cell coordinates/geometry
352 for (std::int32_t i = 0; i < num_x_dofs_cell; ++i)
353 {
354 const U* _x_ptr = x_ptr + x_dofmap_ptr[cell * num_x_dofs_cell + i] * gdim;
355 std::copy_n(_x_ptr, gdim, cdofs_b.data() + 3 * i);
356 }
357
358 // Permutations
359 std::uint8_t perm = perms.empty() ? 0 : perms(cell, local_entity);
360
361 // Tabulate tensor
362 std::ranges::fill(Ae, 0);
363 kernel(Ae.data(), coeffs_data + f * cstride, constants.data(),
364 cdofs_b.data(), &local_entity, &perm, nullptr);
365 if (p0_set)
366 P0(Ae, cell_info0, cell0, ndim1);
367 if (p1t_set)
368 P1T(Ae, cell_info1, cell1, ndim0);
369
370 // Don't clear rows/cols in LiftingMode
371 if constexpr (!LiftingMode)
372 {
373 // Zero rows and columns for BCs
374 if (!bc0.empty())
375 {
376 for (std::size_t i = 0; i < num_dofs0; ++i)
377 {
378 for (int k = 0; k < bs0; ++k)
379 {
380 if (bc0[bs0 * dofs0[i] + k])
381 {
382 // Zero row bs0 * i + k
383 const int row = bs0 * i + k;
384 std::fill_n(std::next(Ae.begin(), ndim1 * row), ndim1, 0);
385 }
386 }
387 }
388 }
389
390 if (!bc1.empty())
391 {
392 for (std::size_t j = 0; j < num_dofs1; ++j)
393 {
394 for (int k = 0; k < bs1; ++k)
395 {
396 if (bc1[bs1 * dofs1[j] + k])
397 {
398 // Zero column bs1 * j + k
399 int col = bs1 * j + k;
400 for (std::size_t row = 0; row < ndim0; ++row)
401 Ae[row * ndim1 + col] = 0;
402 }
403 }
404 }
405 }
406 }
407
408 mat_set(dofs0, dofs1, Ae);
409 }
410}
411
473template <bool LiftingMode, dolfinx::scalar T, std::floating_point U>
474void assemble_interior_facets(
475 la::MatSet<T> auto mat_set, MDSpan2Int32 auto x_dofmap,
476 MDSpan2Floating<U> auto x,
477 md::mdspan<const std::int32_t,
478 std::extents<std::size_t, md::dynamic_extent, 2, 2>>
479 facets,
480 const DofMapPackFacets auto& dofmap0,
481 const fem::DofTransformKernel<T> auto& P0,
482 const DofMapPackFacets auto& dofmap1,
483 const fem::DofTransformKernel<T> auto& P1T,
484 std::span<const std::int8_t> bc0, std::span<const std::int8_t> bc1,
485 const FEkernel<T, U> auto& kernel,
486 md::mdspan<const T, md::extents<std::size_t, md::dynamic_extent, 2,
487 md::dynamic_extent>>
488 coeffs,
489 std::span<const T> constants, std::span<const std::uint32_t> cell_info0,
490 std::span<const std::uint32_t> cell_info1,
491 md::mdspan<const std::uint8_t, md::dextents<std::size_t, 2>> perms,
492 std::span<T> Ab, std::span<U> cdofs_b, std::span<std::int32_t> dofs_b,
493 std::span<T> Ae_block_b)
494{
495 if (facets.empty())
496 return;
497
498 const auto [dmap0, bs0, facets0] = dofmap0;
499 const auto [dmap1, bs1, facets1] = dofmap1;
500
501 // Data structures used in assembly
502 assert(cdofs_b.size() >= 2 * 3 * x_dofmap.extent(1));
503 auto cdofs0 = cdofs_b.first(3 * x_dofmap.extent(1));
504 auto cdofs1 = cdofs_b.last(3 * x_dofmap.extent(1));
505
506 const U* x_ptr = x.data_handle();
507 const std::int32_t gdim = x.extent(1);
508 const std::int32_t* x_dofmap_ptr = x_dofmap.data_handle();
509 const std::int32_t num_x_dofs_cell = x_dofmap.extent(1);
510
511 std::size_t dmap0_size = dmap0.extent(1);
512 std::size_t dmap1_size = dmap1.extent(1);
513 std::size_t num_rows = bs0 * 2 * dmap0_size;
514 std::size_t num_cols = bs1 * 2 * dmap1_size;
515
516 // Dofmap data structures
517 assert(dofs_b.size() >= (2 * dmap0_size) + (2 * dmap1_size));
518 auto dmapjoint0 = dofs_b.first(2 * dmap0_size);
519 auto dmapjoint1 = dofs_b.last(2 * dmap1_size);
520
521 assert(facets0.size() == facets.size());
522 assert(facets1.size() == facets.size());
523 assert(Ab.size() >= num_rows * num_cols);
524 auto Ae = Ab.first(num_rows * num_cols);
525
526 // Buffer used to gather a contiguous (test, trial) block of Ae when
527 // one of the two cells attached to the facet does not exist in the
528 // test/trial function domain (e.g. an interface between two
529 // domains) -- the sparsity pattern only holds entries for blocks
530 // where both cells exist, so such blocks must be inserted
531 // individually rather than as part of the full joint block.
532 assert(Ae_block_b.size() >= dmap0_size * bs0 * dmap1_size * bs1);
533
534 const T* coeffs_data = coeffs.data_handle();
535 const std::size_t cstride = 2 * coeffs.extent(2);
536
537 auto insert_block = [&Ae_block_b, &Ae, &bs0, &bs1, &num_cols,
538 &mat_set](std::span<const std::int32_t> rdofs,
539 std::span<const std::int32_t> cdofs,
540 std::size_t row_offset, std::size_t col_offset)
541 {
542 if (rdofs.empty() or cdofs.empty())
543 return;
544 auto Ae_block = Ae_block_b.first(rdofs.size() * bs0 * cdofs.size() * bs1);
545 for (std::size_t i = 0; i < rdofs.size() * bs0; ++i)
546 {
547 auto row
548 = std::next(Ae.begin(), (row_offset + i) * num_cols + col_offset);
549 std::copy_n(row, cdofs.size() * bs1,
550 std::next(Ae_block.begin(), i * cdofs.size() * bs1));
551 }
552 mat_set(rdofs, cdofs, Ae_block);
553 };
554
555 // P0/P1T do not change across facets in this call, so whether each is a
556 // set (non-null) transform is loop-invariant -- checked once here rather
557 // than on every facet.
558 const bool p0_set = is_transform_set(P0);
559 const bool p1t_set = is_transform_set(P1T);
560
561 for (std::size_t f = 0; f < facets.extent(0); ++f)
562 {
563 // Cells in integration domain, test function domain and trial
564 // function domain
565 std::array cells{facets(f, 0, 0), facets(f, 1, 0)};
566 std::array cells0{facets0(f, 0, 0), facets0(f, 1, 0)};
567 std::array cells1{facets1(f, 0, 0), facets1(f, 1, 0)};
568
569 // Local facets indices
570 std::array local_facet{facets(f, 0, 1), facets(f, 1, 1)};
571
572 // Get cell geometry
573 for (std::int32_t i = 0; i < num_x_dofs_cell; ++i)
574 {
575 const U* _x_ptr0
576 = x_ptr + x_dofmap_ptr[cells[0] * num_x_dofs_cell + i] * gdim;
577 std::copy_n(_x_ptr0, gdim, cdofs0.data() + 3 * i);
578 const U* _x_ptr1
579 = x_ptr + x_dofmap_ptr[cells[1] * num_x_dofs_cell + i] * gdim;
580 std::copy_n(_x_ptr1, gdim, cdofs1.data() + 3 * i);
581 }
582
583 // Get dof maps for cells and pack
584 // When integrating over interfaces between two domains, the test function
585 // might only be defined on one side, so we check which cells exist in the
586 // test function domain
587 std::span<const std::int32_t> dmap0_cell0
588 = cells0[0] >= 0
589 ? std::span(dmap0.data_handle() + cells0[0] * dmap0_size,
590 dmap0_size)
591 : std::span<const std::int32_t>();
592 std::span<const std::int32_t> dmap0_cell1
593 = cells0[1] >= 0
594 ? std::span(dmap0.data_handle() + cells0[1] * dmap0_size,
595 dmap0_size)
596 : std::span<const std::int32_t>();
597
598 std::ranges::copy(dmap0_cell0, dmapjoint0.begin());
599 std::ranges::copy(dmap0_cell1, std::next(dmapjoint0.begin(), dmap0_size));
600
601 // Check which cells exist in the trial function domain
602 std::span<const std::int32_t> dmap1_cell0
603 = cells1[0] >= 0
604 ? std::span(dmap1.data_handle() + cells1[0] * dmap1_size,
605 dmap1_size)
606 : std::span<const std::int32_t>();
607 std::span<const std::int32_t> dmap1_cell1
608 = cells1[1] >= 0
609 ? std::span(dmap1.data_handle() + cells1[1] * dmap1_size,
610 dmap1_size)
611 : std::span<const std::int32_t>();
612
613 std::ranges::copy(dmap1_cell0, dmapjoint1.begin());
614 std::ranges::copy(dmap1_cell1, std::next(dmapjoint1.begin(), dmap1_size));
615
616 // Check for BCs on column space
617 if constexpr (LiftingMode)
618 {
619 if (!has_bc(dmapjoint1, bc1, bs1))
620 continue;
621 }
622
623 // Tabulate tensor
624 std::ranges::fill(Ae, 0);
625 std::array perm = perms.empty()
626 ? std::array<std::uint8_t, 2>{0, 0}
627 : std::array{perms(cells[0], local_facet[0]),
628 perms(cells[1], local_facet[1])};
629 kernel(Ae.data(), coeffs_data + f * cstride, constants.data(),
630 cdofs_b.data(), local_facet.data(), perm.data(), nullptr);
631
632 // Local element layout is a 2x2 block matrix with structure
633 //
634 // cell0cell0 | cell0cell1
635 // cell1cell0 | cell1cell1
636 //
637 // where each block is element tensor of size (dmap0, dmap1).
638
639 // Only apply transformation when cells exist
640 if (p0_set and cells0[0] >= 0)
641 P0(Ae, cell_info0, cells0[0], num_cols);
642 if (p0_set and cells0[1] >= 0)
643 {
644 std::span sub_Ae0(Ae.data() + bs0 * dmap0_size * num_cols,
645 bs0 * dmap0_size * num_cols);
646 P0(sub_Ae0, cell_info0, cells0[1], num_cols);
647 }
648 if (p1t_set and cells1[0] >= 0)
649 P1T(Ae, cell_info1, cells1[0], num_rows);
650
651 if (p1t_set and cells1[1] >= 0)
652 {
653 for (std::size_t row = 0; row < num_rows; ++row)
654 {
655 // DOFs for dmap1 and cell1 are not stored contiguously in the
656 // block matrix, so each row needs a separate span access
657 std::span sub_Ae1(Ae.data() + row * num_cols + bs1 * dmap1_size,
658 bs1 * dmap1_size);
659 P1T(sub_Ae1, cell_info1, cells1[1], 1);
660 }
661 }
662
663 // Clear rows/cols if not in LiftingMode
664 if constexpr (!LiftingMode)
665 {
666 // Zero rows and columns for BCs
667 if (!bc0.empty())
668 {
669 for (std::size_t i = 0; i < dmapjoint0.size(); ++i)
670 {
671 for (int k = 0; k < bs0; ++k)
672 {
673 if (bc0[bs0 * dmapjoint0[i] + k])
674 {
675 // Zero row bs0 * i + k
676 std::fill_n(std::next(Ae.begin(), num_cols * (bs0 * i + k)),
677 num_cols, 0);
678 }
679 }
680 }
681 }
682
683 if (!bc1.empty())
684 {
685 for (std::size_t j = 0; j < dmapjoint1.size(); ++j)
686 {
687 for (int k = 0; k < bs1; ++k)
688 {
689 if (bc1[bs1 * dmapjoint1[j] + k])
690 {
691 // Zero column bs1 * j + k
692 for (std::size_t m = 0; m < num_rows; ++m)
693 Ae[m * num_cols + bs1 * j + k] = 0;
694 }
695 }
696 }
697 }
698 }
699
700 // The common case is that a cell exists on both sides of the
701 // facet for both the test and trial function domains, in which
702 // case the full joint block can be inserted in one go. Otherwise
703 // (e.g. an interface between two domains), only the blocks
704 // corresponding to existing (test, trial) cell pairs are present
705 // in the sparsity pattern, so each must be inserted individually.
706 if (cells0[0] >= 0 and cells0[1] >= 0 and cells1[0] >= 0 and cells1[1] >= 0)
707 mat_set(dmapjoint0, dmapjoint1, Ae);
708 else
709 {
710 insert_block(dmap0_cell0, dmap1_cell0, 0, 0);
711 insert_block(dmap0_cell0, dmap1_cell1, 0, bs1 * dmap1_size);
712 insert_block(dmap0_cell1, dmap1_cell0, bs0 * dmap0_size, 0);
713 insert_block(dmap0_cell1, dmap1_cell1, bs0 * dmap0_size,
714 bs1 * dmap1_size);
715 }
716 }
717}
718
747template <bool LiftingMode, dolfinx::scalar T, std::floating_point U>
748void assemble_matrix(
749 la::MatSet<T> auto mat_set, const Form<T, U>& a,
750 md::mdspan<const U, md::extents<std::size_t, md::dynamic_extent, 3>> x,
751 std::span<const T> constants,
752 const std::map<std::pair<IntegralType, int>,
753 std::pair<std::span<const T>, int>>& coefficients,
754 std::span<const std::int8_t> bc0, std::span<const std::int8_t> bc1)
755{
756 // Integration domain mesh
757 std::shared_ptr<const mesh::Mesh<U>> mesh = a.mesh();
758 assert(mesh);
759
760 // Test function mesh
761 auto mesh0 = a.function_spaces().at(0)->mesh();
762 assert(mesh0);
763
764 // Trial function mesh
765 auto mesh1 = a.function_spaces().at(1)->mesh();
766 assert(mesh1);
767
768 // TODO: Mixed topology with exterior and interior facet integrals.
769 //
770 // NOTE: Can't just loop over cell types for interior facet integrals
771 // because we have a kernel per combination of comparable cell types,
772 // rather than one per cell type. Also, we need the dofmaps for two
773 // different cell types at the same time.
774 const int num_cell_types = mesh->topology()->cell_types().size();
775 for (int cell_type_idx = 0; cell_type_idx < num_cell_types; ++cell_type_idx)
776 {
777 // Geometry dofmap and data
778 mdspan2_t x_dofmap = mesh->geometry().dofmaps().at(cell_type_idx);
779
780 // Get dofmap data
781 std::shared_ptr<const fem::DofMap> dofmap0
782 = a.function_spaces().at(0)->dofmaps().at(cell_type_idx);
783 std::shared_ptr<const fem::DofMap> dofmap1
784 = a.function_spaces().at(1)->dofmaps().at(cell_type_idx);
785 assert(dofmap0);
786 assert(dofmap1);
787 md::mdspan<const std::int32_t, md::dextents<std::size_t, 2>> dofs0
788 = dofmap0->map();
789 const int bs0 = dofmap0->bs();
790 md::mdspan<const std::int32_t, md::dextents<std::size_t, 2>> dofs1
791 = dofmap1->map();
792 const int bs1 = dofmap1->bs();
793
794 // Buffers reused across all integral kernels for this cell type,
795 // sized for the worst case (interior facets, which touch two cells).
796 std::vector<T> Ab((2 * bs0 * dofs0.extent(1))
797 * (2 * bs1 * dofs1.extent(1)));
798 std::vector<U> cdofs_b(2 * 3 * x_dofmap.extent(1));
799 std::size_t dmap0_size = dofmap0->map().extent(1);
800 std::size_t dmap1_size = dofmap1->map().extent(1);
801 std::vector<std::int32_t> dmap_b((2 * dmap0_size) + (2 * dmap1_size));
802 std::vector<T> Ae_block_b(dmap0_size * bs0 * dmap1_size * bs1);
803
804 auto element0 = a.function_spaces().at(0)->elements(cell_type_idx);
805 assert(element0);
806 auto element1 = a.function_spaces().at(1)->elements(cell_type_idx);
807 assert(element1);
808 const fem::DofTransformKernel<T> auto& P0
809 = element0->template dof_transformation_fn<T>(doftransform::standard);
810 const fem::DofTransformKernel<T> auto& P1T
811 = element1->template dof_transformation_right_fn<T>(
813
814 std::span<const std::uint32_t> cell_info0;
815 std::span<const std::uint32_t> cell_info1;
816 if (element0->needs_dof_transformations()
817 or element1->needs_dof_transformations()
818 or a.needs_facet_permutations())
819 {
820 mesh0->topology_mutable()->create_entity_permutations();
821 mesh1->topology_mutable()->create_entity_permutations();
822 cell_info0 = std::span(mesh0->topology()->get_cell_permutation_info());
823 cell_info1 = std::span(mesh1->topology()->get_cell_permutation_info());
824 }
825
826 for (int i = 0; i < a.num_integrals(IntegralType::cell, cell_type_idx); ++i)
827 {
828 auto fn = a.kernel(IntegralType::cell, i, cell_type_idx);
829 assert(fn);
830 std::span cells = a.domain(IntegralType::cell, i, cell_type_idx);
831 std::span cells0 = a.domain_arg(IntegralType::cell, 0, i, cell_type_idx);
832 std::span cells1 = a.domain_arg(IntegralType::cell, 1, i, cell_type_idx);
833 auto& [coeffs, cstride] = coefficients.at({IntegralType::cell, i});
834 assert(cells.size() * cstride == coeffs.size());
835 if (bs0 == 1 and bs1 == 1)
836 {
837 impl::assemble_cells_matrix<LiftingMode>(
838 mat_set, x_dofmap, x, cells,
839 std::tuple{dofs0, std::integral_constant<int, 1>{}, cells0}, P0,
840 std::tuple{dofs1, std::integral_constant<int, 1>{}, cells1}, P1T,
841 bc0, bc1, fn, md::mdspan(coeffs.data(), cells.size(), cstride),
842 constants, cell_info0, cell_info1, std::span(Ab),
843 std::span(cdofs_b));
844 }
845 else if (bs0 == 3 and bs1 == 3)
846 {
847 impl::assemble_cells_matrix<LiftingMode>(
848 mat_set, x_dofmap, x, cells,
849 std::tuple{dofs0, std::integral_constant<int, 3>{}, cells0}, P0,
850 std::tuple{dofs1, std::integral_constant<int, 3>{}, cells1}, P1T,
851 bc0, bc1, fn, md::mdspan(coeffs.data(), cells.size(), cstride),
852 constants, cell_info0, cell_info1, std::span(Ab),
853 std::span(cdofs_b));
854 }
855 else
856 {
857 impl::assemble_cells_matrix<LiftingMode>(
858 mat_set, x_dofmap, x, cells, std::tuple{dofs0, bs0, cells0}, P0,
859 std::tuple{dofs1, bs1, cells1}, P1T, bc0, bc1, fn,
860 md::mdspan(coeffs.data(), cells.size(), cstride), constants,
861 cell_info0, cell_info1, std::span(Ab), std::span(cdofs_b));
862 }
863 }
864
865 md::mdspan<const std::uint8_t, md::dextents<std::size_t, 2>> facet_perms;
866 if (a.needs_facet_permutations())
867 {
868 mesh::CellType cell_type = mesh->topology()->cell_types()[cell_type_idx];
869 int num_facets_per_cell
870 = mesh::cell_num_entities(cell_type, mesh->topology()->dim() - 1);
871 mesh->topology_mutable()->create_entity_permutations();
872 const std::vector<std::uint8_t>& p
873 = mesh->topology()->get_facet_permutations();
874 facet_perms = md::mdspan(p.data(), p.size() / num_facets_per_cell,
875 num_facets_per_cell);
876 }
877
878 for (int i = 0;
879 i < a.num_integrals(IntegralType::interior_facet, cell_type_idx); ++i)
880 {
881 if (num_cell_types > 1)
882 {
883 throw std::invalid_argument("Interior facet integrals with mixed "
884 "topology aren't supported yet");
885 }
886
887 using mdspanx22_t
888 = md::mdspan<const std::int32_t,
889 md::extents<std::size_t, md::dynamic_extent, 2, 2>>;
890 using mdspanx2x_t
891 = md::mdspan<const T, md::extents<std::size_t, md::dynamic_extent, 2,
892 md::dynamic_extent>>;
893
894 auto fn = a.kernel(IntegralType::interior_facet, i, 0);
895 assert(fn);
896 auto& [coeffs, cstride]
897 = coefficients.at({IntegralType::interior_facet, i});
898
899 std::span facets = a.domain(IntegralType::interior_facet, i, 0);
900 std::span facets0 = a.domain_arg(IntegralType::interior_facet, 0, i, 0);
901 std::span facets1 = a.domain_arg(IntegralType::interior_facet, 1, i, 0);
902 assert((facets.size() / 4) * 2 * cstride == coeffs.size());
903 if (bs0 == 1 and bs1 == 1)
904 {
905 impl::assemble_interior_facets<LiftingMode>(
906 mat_set, x_dofmap, x,
907 mdspanx22_t(facets.data(), facets.size() / 4, 2, 2),
908 std::tuple{dofs0, std::integral_constant<int, 1>{},
909 mdspanx22_t(facets0.data(), facets0.size() / 4, 2, 2)},
910 P0,
911 std::tuple{dofs1, std::integral_constant<int, 1>{},
912 mdspanx22_t(facets1.data(), facets1.size() / 4, 2, 2)},
913 P1T, bc0, bc1, fn,
914 mdspanx2x_t(coeffs.data(), facets.size() / 4, 2, cstride),
915 constants, cell_info0, cell_info1, facet_perms, std::span(Ab),
916 std::span(cdofs_b), dmap_b, std::span(Ae_block_b));
917 }
918 else if (bs0 == 3 and bs1 == 3)
919 {
920 impl::assemble_interior_facets<LiftingMode>(
921 mat_set, x_dofmap, x,
922 mdspanx22_t(facets.data(), facets.size() / 4, 2, 2),
923 std::tuple{dofs0, std::integral_constant<int, 3>{},
924 mdspanx22_t(facets0.data(), facets0.size() / 4, 2, 2)},
925 P0,
926 std::tuple{dofs1, std::integral_constant<int, 3>{},
927 mdspanx22_t(facets1.data(), facets1.size() / 4, 2, 2)},
928 P1T, bc0, bc1, fn,
929 mdspanx2x_t(coeffs.data(), facets.size() / 4, 2, cstride),
930 constants, cell_info0, cell_info1, facet_perms, std::span(Ab),
931 std::span(cdofs_b), dmap_b, std::span(Ae_block_b));
932 }
933 else
934 {
935 impl::assemble_interior_facets<LiftingMode>(
936 mat_set, x_dofmap, x,
937 mdspanx22_t(facets.data(), facets.size() / 4, 2, 2),
938 std::tuple{dofs0, bs0,
939 mdspanx22_t(facets0.data(), facets0.size() / 4, 2, 2)},
940 P0,
941 std::tuple{dofs1, bs1,
942 mdspanx22_t(facets1.data(), facets1.size() / 4, 2, 2)},
943 P1T, bc0, bc1, fn,
944 mdspanx2x_t(coeffs.data(), facets.size() / 4, 2, cstride),
945 constants, cell_info0, cell_info1, facet_perms, std::span(Ab),
946 std::span(cdofs_b), dmap_b, std::span(Ae_block_b));
947 }
948 }
949
950 for (auto itg_type : {fem::IntegralType::exterior_facet,
952 {
953 md::mdspan<const std::uint8_t, md::dextents<std::size_t, 2>> perms
955 ? facet_perms
956 : md::mdspan<const std::uint8_t,
957 md::dextents<std::size_t, 2>>{};
958
959 for (int i = 0; i < a.num_integrals(itg_type, cell_type_idx); ++i)
960 {
961 if (num_cell_types > 1)
962 {
963 throw std::invalid_argument("Exterior facet integrals with mixed "
964 "topology aren't supported yet");
965 }
966
967 using mdspanx2_t
968 = md::mdspan<const std::int32_t,
969 md::extents<std::size_t, md::dynamic_extent, 2>>;
970
971 auto fn = a.kernel(itg_type, i, 0);
972 assert(fn);
973 auto& [coeffs, cstride] = coefficients.at({itg_type, i});
974
975 std::span e = a.domain(itg_type, i, 0);
976 mdspanx2_t entities(e.data(), e.size() / 2, 2);
977 std::span e0 = a.domain_arg(itg_type, 0, i, 0);
978 mdspanx2_t entities0(e0.data(), e0.size() / 2, 2);
979 std::span e1 = a.domain_arg(itg_type, 1, i, 0);
980 mdspanx2_t entities1(e1.data(), e1.size() / 2, 2);
981 assert((entities.size() / 2) * cstride == coeffs.size());
982 if (bs0 == 1 and bs1 == 1)
983 {
984 impl::assemble_entities<LiftingMode>(
985 mat_set, x_dofmap, x, entities,
986 std::tuple{dofs0, std::integral_constant<int, 1>{}, entities0},
987 P0,
988 std::tuple{dofs1, std::integral_constant<int, 1>{}, entities1},
989 P1T, bc0, bc1, fn,
990 md::mdspan(coeffs.data(), entities.extent(0), cstride), constants,
991 cell_info0, cell_info1, perms, std::span(Ab), std::span(cdofs_b));
992 }
993 else if (bs0 == 3 and bs1 == 3)
994 {
995 impl::assemble_entities<LiftingMode>(
996 mat_set, x_dofmap, x, entities,
997 std::tuple{dofs0, std::integral_constant<int, 3>{}, entities0},
998 P0,
999 std::tuple{dofs1, std::integral_constant<int, 3>{}, entities1},
1000 P1T, bc0, bc1, fn,
1001 md::mdspan(coeffs.data(), entities.extent(0), cstride), constants,
1002 cell_info0, cell_info1, perms, std::span(Ab), std::span(cdofs_b));
1003 }
1004 else
1005 {
1006 impl::assemble_entities<LiftingMode>(
1007 mat_set, x_dofmap, x, entities, std::tuple{dofs0, bs0, entities0},
1008 P0, std::tuple{dofs1, bs1, entities1}, P1T, bc0, bc1, fn,
1009 md::mdspan(coeffs.data(), entities.extent(0), cstride), constants,
1010 cell_info0, cell_info1, perms, std::span(Ab), std::span(cdofs_b));
1011 }
1012 }
1013 }
1014 }
1015}
1016} // namespace dolfinx::fem::impl
Degree-of-freedom map representations and tools.
Functions supporting finite element method operations.
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
@ transpose
Transpose.
Definition FiniteElement.h:30
@ standard
Standard.
Definition FiniteElement.h:29
@ vertex
Vertex.
Definition Form.h:45
@ interior_facet
Interior facet.
Definition Form.h:44
@ ridge
Ridge.
Definition Form.h:46
@ cell
Cell.
Definition Form.h:42
@ exterior_facet
Exterior facet.
Definition Form.h:43
constexpr bool is_transform_set(const F &fn)
Whether a DofTransformKernel fn should be invoked.
Definition traits.h:33
CellType
Cell type identifier.
Definition cell_types.h:22
int cell_num_entities(CellType type, int dim)
Number of entities of dimension.
Definition cell_types.cpp:92