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 <algorithm>
14#include <concepts>
15#include <dolfinx/la/utils.h>
16#include <dolfinx/mesh/Geometry.h>
17#include <dolfinx/mesh/Mesh.h>
18#include <dolfinx/mesh/Topology.h>
19#include <functional>
20#include <iterator>
21#include <span>
22#include <stdexcept>
23#include <vector>
24
25namespace dolfinx::fem::impl
26{
27bool has_bc(auto& dofs, auto& bc, auto bs)
28{
29 for (auto dof : dofs)
30 for (int k = 0; k < bs; ++k)
31 if (bc[bs * dof + k])
32 return true;
33 return false;
34};
35
81template <bool LiftingMode, typename AB, MDSpan2Int32 XD, std::floating_point U,
82 dolfinx::scalar T = typename std::remove_cvref_t<AB>::value_type>
83 requires ScratchBuffer<AB, T>
84void assemble_cells_matrix(
85 la::MatSet<T> auto mat_set, GeometryPack<XD, U> geometry,
86 const IndexList auto& cells, const FormArgumentCells<T> auto& arg0,
87 const FormArgumentCells<T> auto& arg1, std::span<const std::int8_t> bc0,
88 std::span<const std::int8_t> bc1, const FEkernel<T, U> auto& kernel,
89 std::span<const T> constants,
90 md::mdspan<const T, md::dextents<std::size_t, 2>> coeffs, AB Ab,
91 ScratchBuffer<U> auto cdofs_b)
92{
93 if (std::ranges::empty(cells))
94 return;
95
96 // By value: the sizes below fold only if not read through a
97 // reference (see fem::DofMapPack). mdspan and span are two-word
98 // copies.
99 const auto& P0 = arg0.transform;
100 const auto& P1T = arg1.transform;
101 std::span<const std::uint32_t> cell_info0 = arg0.cell_info;
102 std::span<const std::uint32_t> cell_info1 = arg1.cell_info;
103 const auto dmap0 = arg0.dofmap.map;
104 const auto bs0 = arg0.dofmap.bs;
105 // By reference: a generated range (e.g. iota) does not convert to a
106 // span, and a caller's std::vector must not be copied.
107 const auto& cells0 = arg0.dofmap.entities;
108 const auto dmap1 = arg1.dofmap.map;
109 const auto bs1 = arg1.dofmap.bs;
110 const auto& cells1 = arg1.dofmap.entities;
111
112 const auto num_dofs0 = dmap0.extent(1);
113 const auto num_dofs1 = dmap1.extent(1);
114 constexpr std::size_t nd0 = static_dofs_per_cell<decltype(dmap0)>;
115 constexpr std::size_t nd1 = static_dofs_per_cell<decltype(dmap1)>;
116 const std::size_t ndim0 = bs0 * num_dofs0;
117 const std::size_t ndim1 = bs1 * num_dofs1;
118 const auto num_x_dofs_cell = geometry.dofmap.extent(1);
119
120 assert(Ab.size() == ndim0 * ndim1);
121 assert(cdofs_b.size() == 3 * static_cast<std::size_t>(num_x_dofs_cell));
122
123 const bool p0_set = is_transform_set(P0);
124 const bool p1t_set = is_transform_set(P1T);
125
126 const T* coeffs_data = coeffs.data_handle();
127 const std::size_t cstride = coeffs.extent(1);
128
129 // Iterate over active cells
130 const std::size_t num_cells = std::ranges::size(cells);
131 assert(std::ranges::size(cells0) == num_cells);
132 assert(std::ranges::size(cells1) == num_cells);
133 for (std::size_t c = 0; c < num_cells; ++c)
134 {
135 // Cell index in integration domain mesh (c), test function mesh
136 // (c0) and trial function mesh (c1)
137 std::int32_t cell = cells[c];
138 std::int32_t cell0 = cells0[c];
139 std::int32_t cell1 = cells1[c];
140
141 std::span<const std::int32_t, nd0> dofs0(
142 dmap0.data_handle() + cell0 * num_dofs0, num_dofs0);
143 std::span<const std::int32_t, nd1> dofs1(
144 dmap1.data_handle() + cell1 * num_dofs1, num_dofs1);
145
146 // In "LiftingMode" only execute kernel if there are BCs on column space
147 if constexpr (LiftingMode)
148 {
149 if (!has_bc(dofs1, bc1, bs1))
150 continue;
151 }
152
153 gather_cell_coordinates(geometry, cell, cdofs_b.data());
154
155 // Tabulate tensor
156 std::ranges::fill(Ab, T(0));
157 kernel(Ab.data(), coeffs_data + c * cstride, constants.data(),
158 cdofs_b.data(), nullptr, nullptr, nullptr);
159
160 // Compute A = P_0 \tilde{A} P_1^T (dof transformation)
161 if (p0_set)
162 P0(Ab, cell_info0, cell0, ndim1); // B = P0 \tilde{A}
163 if (p1t_set)
164 P1T(Ab, cell_info1, cell1, ndim0); // A = B P1_T
165
166 // In lifting mode only BC dofs are assembled, while in standard mode these
167 // row/column dofs are zeroed.
168 if constexpr (!LiftingMode)
169 {
170 // Zero rows and columns for BCs
171 if (!bc0.empty())
172 {
173 for (std::size_t i = 0; i < num_dofs0; ++i)
174 {
175 for (int k = 0; k < bs0; ++k)
176 {
177 if (bc0[bs0 * dofs0[i] + k])
178 {
179 // Zero row bs0 * i + k
180 const int row = bs0 * i + k;
181 std::fill_n(std::next(Ab.begin(), ndim1 * row), ndim1, T(0));
182 }
183 }
184 }
185 }
186
187 if (!bc1.empty())
188 {
189 for (std::size_t j = 0; j < num_dofs1; ++j)
190 {
191 for (int k = 0; k < bs1; ++k)
192 {
193 if (bc1[bs1 * dofs1[j] + k])
194 {
195 // Zero column bs1 * j + k
196 int col = bs1 * j + k;
197 for (std::size_t row = 0; row < ndim0; ++row)
198 Ab[row * ndim1 + col] = 0;
199 }
200 }
201 }
202 }
203 }
204
205 mat_set(dofs0, dofs1, Ab);
206 }
207}
208
264template <bool LiftingMode, typename AB, MDSpan2Int32 XD, std::floating_point U,
265 dolfinx::scalar T = typename std::remove_cvref_t<AB>::value_type>
266 requires ScratchBuffer<AB, T>
267void assemble_entities_matrix(
268 la::MatSet<T> auto mat_set, GeometryPack<XD, U> geometry,
269 md::mdspan<const std::int32_t,
270 std::extents<std::size_t, md::dynamic_extent, 2>>
271 entities,
272 const FormArgumentEntities<T> auto& arg0,
273 const FormArgumentEntities<T> auto& arg1, std::span<const std::int8_t> bc0,
274 std::span<const std::int8_t> bc1, const FEkernel<T, U> auto& kernel,
275 std::span<const T> constants,
276 md::mdspan<const T, md::dextents<std::size_t, 2>> coeffs,
277 md::mdspan<const std::uint8_t, md::dextents<std::size_t, 2>> perms, AB Ab,
278 ScratchBuffer<U> auto cdofs_b)
279{
280 if (entities.empty())
281 return;
282
283 // By value: the sizes below fold only if not read through a
284 // reference (see fem::DofMapPack). mdspan and span are two-word
285 // copies.
286 const auto& P0 = arg0.transform;
287 const auto& P1T = arg1.transform;
288 std::span<const std::uint32_t> cell_info0 = arg0.cell_info;
289 std::span<const std::uint32_t> cell_info1 = arg1.cell_info;
290 const auto dmap0 = arg0.dofmap.map;
291 const auto bs0 = arg0.dofmap.bs;
292 const auto entities0 = arg0.dofmap.entities;
293 const auto dmap1 = arg1.dofmap.map;
294 const auto bs1 = arg1.dofmap.bs;
295 const auto entities1 = arg1.dofmap.entities;
296
297 const auto num_dofs0 = dmap0.extent(1);
298 const auto num_dofs1 = dmap1.extent(1);
299 constexpr std::size_t nd0 = static_dofs_per_cell<decltype(dmap0)>;
300 constexpr std::size_t nd1 = static_dofs_per_cell<decltype(dmap1)>;
301 const std::size_t ndim0 = bs0 * num_dofs0;
302 const std::size_t ndim1 = bs1 * num_dofs1;
303 const auto num_x_dofs_cell = geometry.dofmap.extent(1);
304 assert(entities0.size() == entities.size());
305 assert(entities1.size() == entities.size());
306 assert(Ab.size() == ndim0 * ndim1);
307 assert(cdofs_b.size() == 3 * static_cast<std::size_t>(num_x_dofs_cell));
308
309 // P0/P1T do not change across entities in this call, so whether each is a
310 // set (non-null) transform is loop-invariant -- checked once here rather
311 // than on every entity.
312 const bool p0_set = is_transform_set(P0);
313 const bool p1t_set = is_transform_set(P1T);
314
315 const T* coeffs_data = coeffs.data_handle();
316 const std::size_t cstride = coeffs.extent(1);
317
318 for (std::size_t f = 0; f < entities.extent(0); ++f)
319 {
320 // Cell in the integration domain, local entity index relative to the
321 // integration domain cell, and cells in the test and trial function
322 // meshes
323 std::int32_t cell = entities(f, 0);
324 std::int32_t local_entity = entities(f, 1);
325 std::int32_t cell0 = entities0(f, 0);
326 std::int32_t cell1 = entities1(f, 0);
327
328 std::span<const std::int32_t, nd0> dofs0(
329 dmap0.data_handle() + cell0 * num_dofs0, num_dofs0);
330 std::span<const std::int32_t, nd1> dofs1(
331 dmap1.data_handle() + cell1 * num_dofs1, num_dofs1);
332
333 // Check for BCs on column space
334 if constexpr (LiftingMode)
335 {
336 if (!has_bc(dofs1, bc1, bs1))
337 continue;
338 }
339
340 gather_cell_coordinates(geometry, cell, cdofs_b.data());
341
342 // Permutations
343 std::uint8_t perm = perms.empty() ? 0 : perms(cell, local_entity);
344
345 // Tabulate tensor
346 std::ranges::fill(Ab, T(0));
347 kernel(Ab.data(), coeffs_data + f * cstride, constants.data(),
348 cdofs_b.data(), &local_entity, &perm, nullptr);
349 if (p0_set)
350 P0(Ab, cell_info0, cell0, ndim1);
351 if (p1t_set)
352 P1T(Ab, cell_info1, cell1, ndim0);
353
354 // Don't clear rows/cols in LiftingMode
355 if constexpr (!LiftingMode)
356 {
357 // Zero rows and columns for BCs
358 if (!bc0.empty())
359 {
360 for (std::size_t i = 0; i < num_dofs0; ++i)
361 {
362 for (int k = 0; k < bs0; ++k)
363 {
364 if (bc0[bs0 * dofs0[i] + k])
365 {
366 // Zero row bs0 * i + k
367 const int row = bs0 * i + k;
368 std::fill_n(std::next(Ab.begin(), ndim1 * row), ndim1, T(0));
369 }
370 }
371 }
372 }
373
374 if (!bc1.empty())
375 {
376 for (std::size_t j = 0; j < num_dofs1; ++j)
377 {
378 for (int k = 0; k < bs1; ++k)
379 {
380 if (bc1[bs1 * dofs1[j] + k])
381 {
382 // Zero column bs1 * j + k
383 int col = bs1 * j + k;
384 for (std::size_t row = 0; row < ndim0; ++row)
385 Ab[row * ndim1 + col] = 0;
386 }
387 }
388 }
389 }
390 }
391
392 mat_set(dofs0, dofs1, Ab);
393 }
394}
395
452template <bool LiftingMode, typename AB, MDSpan2Int32 XD, std::floating_point U,
453 dolfinx::scalar T = typename std::remove_cvref_t<AB>::value_type>
454 requires ScratchBuffer<AB, T>
455void assemble_interior_facets_matrix(
456 la::MatSet<T> auto mat_set, GeometryPack<XD, U> geometry,
457 md::mdspan<const std::int32_t,
458 std::extents<std::size_t, md::dynamic_extent, 2, 2>>
459 facets,
460 const FormArgumentFacets<T> auto& arg0,
461 const FormArgumentFacets<T> auto& arg1, std::span<const std::int8_t> bc0,
462 std::span<const std::int8_t> bc1, const FEkernel<T, U> auto& kernel,
463 std::span<const T> constants,
464 md::mdspan<const T, md::extents<std::size_t, md::dynamic_extent, 2,
465 md::dynamic_extent>>
466 coeffs,
467 md::mdspan<const std::uint8_t, md::dextents<std::size_t, 2>> perms, AB Ab,
468 ScratchBuffer<U> auto cdofs_b, ScratchBuffer<std::int32_t> auto dofs_b,
469 ScratchBuffer<T> auto Ae_block_b)
470{
471 if (facets.empty())
472 return;
473
474 // By value: the sizes below fold only if not read through a
475 // reference (see fem::DofMapPack). mdspan and span are two-word
476 // copies.
477 const auto& P0 = arg0.transform;
478 const auto& P1T = arg1.transform;
479 std::span<const std::uint32_t> cell_info0 = arg0.cell_info;
480 std::span<const std::uint32_t> cell_info1 = arg1.cell_info;
481 const auto dmap0 = arg0.dofmap.map;
482 const auto bs0 = arg0.dofmap.bs;
483 const auto facets0 = arg0.dofmap.entities;
484 const auto dmap1 = arg1.dofmap.map;
485 const auto bs1 = arg1.dofmap.bs;
486 const auto facets1 = arg1.dofmap.entities;
487
488 // Data structures used in assembly
489 const auto num_x_dofs_cell = geometry.dofmap.extent(1);
490 assert(cdofs_b.size() == 2 * 3 * static_cast<std::size_t>(num_x_dofs_cell));
491 U* cdofs0 = cdofs_b.data();
492 U* cdofs1 = cdofs_b.data() + 3 * num_x_dofs_cell;
493
494 const auto dmap0_size = dmap0.extent(1);
495 const auto dmap1_size = dmap1.extent(1);
496 // The joint (two-cell) dofmap length is a constant exactly when the
497 // per-cell one is; dynamic_extent propagates through otherwise.
498 constexpr std::size_t nd0
499 = std::remove_cvref_t<decltype(dmap0)>::static_extent(1);
500 constexpr std::size_t nd1
501 = std::remove_cvref_t<decltype(dmap1)>::static_extent(1);
502 constexpr std::size_t njoint0
503 = nd0 == md::dynamic_extent ? std::dynamic_extent : 2 * nd0;
504 constexpr std::size_t njoint1
505 = nd1 == md::dynamic_extent ? std::dynamic_extent : 2 * nd1;
506 std::size_t num_rows = bs0 * 2 * dmap0_size;
507 std::size_t num_cols = bs1 * 2 * dmap1_size;
508
509 // Dofmap data structures
510 assert(dofs_b.size() == (2 * dmap0_size) + (2 * dmap1_size));
511 std::span<std::int32_t> dofs_all(dofs_b);
512 std::span<std::int32_t, njoint0> dmapjoint0(dofs_all.data(), 2 * dmap0_size);
513 std::span<std::int32_t, njoint1> dmapjoint1(dofs_all.data() + 2 * dmap0_size,
514 2 * dmap1_size);
515
516 assert(facets0.size() == facets.size());
517 assert(facets1.size() == facets.size());
518 assert(Ab.size() == num_rows * num_cols);
519
520 // Buffer used to gather a contiguous (test, trial) block of Ab when
521 // one of the two cells attached to the facet does not exist in the
522 // test/trial function domain (e.g. an interface between two
523 // domains) -- the sparsity pattern only holds entries for blocks
524 // where both cells exist, so such blocks must be inserted
525 // individually rather than as part of the full joint block.
526 assert(Ae_block_b.size() == dmap0_size * bs0 * dmap1_size * bs1);
527 std::span<T> Ae_block_all(Ae_block_b);
528
529 const T* coeffs_data = coeffs.data_handle();
530 const std::size_t cstride = 2 * coeffs.extent(2);
531
532 auto insert_block = [&Ae_block_all, &Ab, &bs0, &bs1, &num_cols,
533 &mat_set](std::span<const std::int32_t> rdofs,
534 std::span<const std::int32_t> cdofs,
535 std::size_t row_offset, std::size_t col_offset)
536 {
537 if (rdofs.empty() or cdofs.empty())
538 return;
539 auto Ae_block = Ae_block_all.first(rdofs.size() * bs0 * cdofs.size() * bs1);
540 for (std::size_t i = 0; i < rdofs.size() * bs0; ++i)
541 {
542 auto row
543 = std::next(Ab.begin(), (row_offset + i) * num_cols + col_offset);
544 std::copy_n(row, cdofs.size() * bs1,
545 std::next(Ae_block.begin(), i * cdofs.size() * bs1));
546 }
547 mat_set(rdofs, cdofs, Ae_block);
548 };
549
550 // P0/P1T do not change across facets in this call, so whether each is a
551 // set (non-null) transform is loop-invariant -- checked once here rather
552 // than on every facet.
553 const bool p0_set = is_transform_set(P0);
554 const bool p1t_set = is_transform_set(P1T);
555
556 for (std::size_t f = 0; f < facets.extent(0); ++f)
557 {
558 // Cells in integration domain, test function domain and trial
559 // function domain
560 std::array cells{facets(f, 0, 0), facets(f, 1, 0)};
561 std::array cells0{facets0(f, 0, 0), facets0(f, 1, 0)};
562 std::array cells1{facets1(f, 0, 0), facets1(f, 1, 0)};
563
564 // Local facets indices
565 std::array local_facet{facets(f, 0, 1), facets(f, 1, 1)};
566
567 // Get cell geometry
568 gather_cell_coordinates(geometry, cells[0], cdofs0);
569 gather_cell_coordinates(geometry, cells[1], cdofs1);
570
571 // Get dof maps for cells and pack
572 // When integrating over interfaces between two domains, the test function
573 // might only be defined on one side, so we check which cells exist in the
574 // test function domain
575 std::span<const std::int32_t> dmap0_cell0
576 = cells0[0] >= 0
577 ? std::span(dmap0.data_handle() + cells0[0] * dmap0_size,
578 dmap0_size)
579 : std::span<const std::int32_t>();
580 std::span<const std::int32_t> dmap0_cell1
581 = cells0[1] >= 0
582 ? std::span(dmap0.data_handle() + cells0[1] * dmap0_size,
583 dmap0_size)
584 : std::span<const std::int32_t>();
585
586 std::ranges::copy(dmap0_cell0, dmapjoint0.begin());
587 std::ranges::copy(dmap0_cell1, std::next(dmapjoint0.begin(), dmap0_size));
588
589 // Check which cells exist in the trial function domain
590 std::span<const std::int32_t> dmap1_cell0
591 = cells1[0] >= 0
592 ? std::span(dmap1.data_handle() + cells1[0] * dmap1_size,
593 dmap1_size)
594 : std::span<const std::int32_t>();
595 std::span<const std::int32_t> dmap1_cell1
596 = cells1[1] >= 0
597 ? std::span(dmap1.data_handle() + cells1[1] * dmap1_size,
598 dmap1_size)
599 : std::span<const std::int32_t>();
600
601 // Check for BCs on column space. The per-cell dofmaps are tested
602 // rather than the joint one: a side absent from the trial function
603 // domain leaves its half of the joint buffer holding the previous
604 // facet's dofs, which would spuriously run the kernel.
605 if constexpr (LiftingMode)
606 {
607 if (!has_bc(dmap1_cell0, bc1, bs1) and !has_bc(dmap1_cell1, bc1, bs1))
608 continue;
609 }
610
611 std::ranges::copy(dmap1_cell0, dmapjoint1.begin());
612 std::ranges::copy(dmap1_cell1, std::next(dmapjoint1.begin(), dmap1_size));
613
614 // Tabulate tensor
615 std::ranges::fill(Ab, T(0));
616 std::array perm = perms.empty()
617 ? std::array<std::uint8_t, 2>{0, 0}
618 : std::array{perms(cells[0], local_facet[0]),
619 perms(cells[1], local_facet[1])};
620 kernel(Ab.data(), coeffs_data + f * cstride, constants.data(),
621 cdofs_b.data(), local_facet.data(), perm.data(), nullptr);
622
623 // Local element layout is a 2x2 block matrix with structure
624 //
625 // cell0cell0 | cell0cell1
626 // cell1cell0 | cell1cell1
627 //
628 // where each block is element tensor of size (dmap0, dmap1).
629
630 // Only apply transformation when cells exist
631 if (p0_set and cells0[0] >= 0)
632 P0(Ab, cell_info0, cells0[0], num_cols);
633 if (p0_set and cells0[1] >= 0)
634 {
635 std::span sub_Ae0(Ab.data() + bs0 * dmap0_size * num_cols,
636 bs0 * dmap0_size * num_cols);
637 P0(sub_Ae0, cell_info0, cells0[1], num_cols);
638 }
639 if (p1t_set and cells1[0] >= 0)
640 P1T(Ab, cell_info1, cells1[0], num_rows);
641
642 if (p1t_set and cells1[1] >= 0)
643 {
644 for (std::size_t row = 0; row < num_rows; ++row)
645 {
646 // DOFs for dmap1 and cell1 are not stored contiguously in the
647 // block matrix, so each row needs a separate span access
648 std::span sub_Ae1(Ab.data() + row * num_cols + bs1 * dmap1_size,
649 bs1 * dmap1_size);
650 P1T(sub_Ae1, cell_info1, cells1[1], 1);
651 }
652 }
653
654 // Clear rows/cols if not in LiftingMode
655 if constexpr (!LiftingMode)
656 {
657 // Zero rows and columns for BCs
658 if (!bc0.empty())
659 {
660 for (std::size_t i = 0; i < dmapjoint0.size(); ++i)
661 {
662 for (int k = 0; k < bs0; ++k)
663 {
664 if (bc0[bs0 * dmapjoint0[i] + k])
665 {
666 // Zero row bs0 * i + k
667 std::fill_n(std::next(Ab.begin(), num_cols * (bs0 * i + k)),
668 num_cols, T(0));
669 }
670 }
671 }
672 }
673
674 if (!bc1.empty())
675 {
676 for (std::size_t j = 0; j < dmapjoint1.size(); ++j)
677 {
678 for (int k = 0; k < bs1; ++k)
679 {
680 if (bc1[bs1 * dmapjoint1[j] + k])
681 {
682 // Zero column bs1 * j + k
683 for (std::size_t m = 0; m < num_rows; ++m)
684 Ab[m * num_cols + bs1 * j + k] = 0;
685 }
686 }
687 }
688 }
689 }
690
691 // The common case is that a cell exists on both sides of the
692 // facet for both the test and trial function domains, in which
693 // case the full joint block can be inserted in one go. Otherwise
694 // (e.g. an interface between two domains), only the blocks
695 // corresponding to existing (test, trial) cell pairs are present
696 // in the sparsity pattern, so each must be inserted individually.
697 if (cells0[0] >= 0 and cells0[1] >= 0 and cells1[0] >= 0 and cells1[1] >= 0)
698 mat_set(dmapjoint0, dmapjoint1, Ab);
699 else
700 {
701 insert_block(dmap0_cell0, dmap1_cell0, 0, 0);
702 insert_block(dmap0_cell0, dmap1_cell1, 0, bs1 * dmap1_size);
703 insert_block(dmap0_cell1, dmap1_cell0, bs0 * dmap0_size, 0);
704 insert_block(dmap0_cell1, dmap1_cell1, bs0 * dmap0_size,
705 bs1 * dmap1_size);
706 }
707 }
708}
709
738template <bool LiftingMode, dolfinx::scalar T, std::floating_point U>
739void assemble_matrix(
740 la::MatSet<T> auto mat_set, const Form<T, U>& a,
741 md::mdspan<const U, md::extents<std::size_t, md::dynamic_extent, 3>> x,
742 std::span<const T> constants,
743 const std::map<std::pair<IntegralType, int>,
744 std::pair<std::span<const T>, int>>& coefficients,
745 std::span<const std::int8_t> bc0, std::span<const std::int8_t> bc1)
746{
747 // Integration domain mesh
748 std::shared_ptr<const mesh::Mesh<U>> mesh = a.mesh();
749 assert(mesh);
750
751 // Test function mesh
752 auto mesh0 = a.function_spaces().at(0)->mesh();
753 assert(mesh0);
754
755 // Trial function mesh
756 auto mesh1 = a.function_spaces().at(1)->mesh();
757 assert(mesh1);
758
759 // TODO: Mixed topology with exterior and interior facet integrals.
760 //
761 // NOTE: Can't just loop over cell types for interior facet integrals
762 // because we have a kernel per combination of comparable cell types,
763 // rather than one per cell type. Also, we need the dofmaps for two
764 // different cell types at the same time.
765 const int num_cell_types = mesh->topology()->cell_types().size();
766 for (int cell_type_idx = 0; cell_type_idx < num_cell_types; ++cell_type_idx)
767 {
768 // Geometry dofmap and data
769 mdspan2_t x_dofmap = mesh->geometry().dofmaps().at(cell_type_idx);
770 GeometryPack geometry{x_dofmap, x};
771
772 // Get dofmap data
773 std::shared_ptr<const fem::DofMap> dofmap0
774 = a.function_spaces().at(0)->dofmaps().at(cell_type_idx);
775 std::shared_ptr<const fem::DofMap> dofmap1
776 = a.function_spaces().at(1)->dofmaps().at(cell_type_idx);
777 assert(dofmap0);
778 assert(dofmap1);
779 md::mdspan<const std::int32_t, md::dextents<std::size_t, 2>> dofs0
780 = dofmap0->map();
781 const int bs0 = dofmap0->bs();
782 md::mdspan<const std::int32_t, md::dextents<std::size_t, 2>> dofs1
783 = dofmap1->map();
784 const int bs1 = dofmap1->bs();
785
786 // Buffers reused across all integral kernels for this cell type,
787 // sized for the worst case (interior facets, which touch two
788 // cells). The kernels require an exactly-sized buffer, so the
789 // one-cell integrals get a leading view.
790 std::vector<T> Ab((2 * bs0 * dofs0.extent(1))
791 * (2 * bs1 * dofs1.extent(1)));
792 std::vector<U> cdofs_b(2 * 3 * x_dofmap.extent(1));
793 std::span Ab1 = std::span(Ab).first((bs0 * dofs0.extent(1))
794 * (bs1 * dofs1.extent(1)));
795 std::span cdofs_b1 = std::span(cdofs_b).first(3 * x_dofmap.extent(1));
796 std::size_t dmap0_size = dofmap0->map().extent(1);
797 std::size_t dmap1_size = dofmap1->map().extent(1);
798 std::vector<std::int32_t> dmap_b((2 * dmap0_size) + (2 * dmap1_size));
799 std::vector<T> Ae_block_b(dmap0_size * bs0 * dmap1_size * bs1);
800
801 auto element0 = a.function_spaces().at(0)->elements(cell_type_idx);
802 assert(element0);
803 auto element1 = a.function_spaces().at(1)->elements(cell_type_idx);
804 assert(element1);
805 const fem::DofTransformKernel<T> auto& P0
806 = element0->template dof_transformation_fn<T>(doftransform::standard);
807 const fem::DofTransformKernel<T> auto& P1T
808 = element1->template dof_transformation_right_fn<T>(
810
811 std::span<const std::uint32_t> cell_info0;
812 std::span<const std::uint32_t> cell_info1;
813 if (element0->needs_dof_transformations()
814 or element1->needs_dof_transformations())
815 {
816 mesh0->topology_mutable()->create_cell_permutations();
817 mesh1->topology_mutable()->create_cell_permutations();
818 cell_info0 = std::span(mesh0->topology()->get_cell_permutation_info());
819 cell_info1 = std::span(mesh1->topology()->get_cell_permutation_info());
820 }
821
822 for (int i = 0; i < a.num_integrals(IntegralType::cell, cell_type_idx); ++i)
823 {
824 auto fn = a.kernel(IntegralType::cell, i, cell_type_idx);
825 assert(fn);
826 std::span cells = a.domain(IntegralType::cell, i, cell_type_idx);
827 std::span cells0 = a.domain_arg(IntegralType::cell, 0, i, cell_type_idx);
828 std::span cells1 = a.domain_arg(IntegralType::cell, 1, i, cell_type_idx);
829 auto& [coeffs, cstride] = coefficients.at({IntegralType::cell, i});
830 assert(cells.size() * cstride == coeffs.size());
831 impl::dispatch_bs(
832 bs0, bs1,
833 [&mat_set, &geometry, &cells, &dofs0, &cells0, &P0, &cell_info0,
834 &dofs1, &cells1, &P1T, &cell_info1, &bc0, &bc1, &fn, &coeffs,
835 cstride, &constants, &Ab1, &cdofs_b1](auto bs0, auto bs1)
836 {
837 impl::assemble_cells_matrix<LiftingMode>(
838 mat_set, geometry, cells,
839 FormArgument{DofMapPack{dofs0, bs0, cells0}, P0, cell_info0},
840 FormArgument{DofMapPack{dofs1, bs1, cells1}, P1T, cell_info1},
841 bc0, bc1, fn, constants,
842 md::mdspan(coeffs.data(), cells.size(), cstride), Ab1,
843 cdofs_b1);
844 });
845 }
846
847 md::mdspan<const std::uint8_t, md::dextents<std::size_t, 2>> facet_perms;
848 if (a.needs_facet_permutations())
849 {
850 facet_perms = impl::entity_permutations(
851 *mesh->topology_mutable(), IntegralType::interior_facet,
852 mesh->topology()->cell_types()[cell_type_idx]);
853 }
854
855 for (int i = 0;
856 i < a.num_integrals(IntegralType::interior_facet, cell_type_idx); ++i)
857 {
858 if (num_cell_types > 1)
859 {
860 throw std::invalid_argument("Interior facet integrals with mixed "
861 "topology aren't supported yet");
862 }
863
864 using mdspanx22_t
865 = md::mdspan<const std::int32_t,
866 md::extents<std::size_t, md::dynamic_extent, 2, 2>>;
867 using mdspanx2x_t
868 = md::mdspan<const T, md::extents<std::size_t, md::dynamic_extent, 2,
869 md::dynamic_extent>>;
870
871 auto fn = a.kernel(IntegralType::interior_facet, i, 0);
872 assert(fn);
873 auto& [coeffs, cstride]
874 = coefficients.at({IntegralType::interior_facet, i});
875
876 std::span facets = a.domain(IntegralType::interior_facet, i, 0);
877 std::span facets0 = a.domain_arg(IntegralType::interior_facet, 0, i, 0);
878 std::span facets1 = a.domain_arg(IntegralType::interior_facet, 1, i, 0);
879 assert((facets.size() / 4) * 2 * cstride == coeffs.size());
880 impl::dispatch_bs(
881 bs0, bs1,
882 [&mat_set, &geometry, &facets, &dofs0, &facets0, &P0, &cell_info0,
883 &dofs1, &facets1, &P1T, &cell_info1, &bc0, &bc1, &fn, &coeffs,
884 cstride, &constants, &facet_perms, &Ab, &cdofs_b, &dmap_b,
885 &Ae_block_b](auto bs0, auto bs1)
886 {
887 impl::assemble_interior_facets_matrix<LiftingMode>(
888 mat_set, geometry,
889 mdspanx22_t(facets.data(), facets.size() / 4, 2, 2),
890 FormArgument{DofMapPack{dofs0, bs0,
891 mdspanx22_t(facets0.data(),
892 facets0.size() / 4, 2, 2)},
893 P0, cell_info0},
894 FormArgument{DofMapPack{dofs1, bs1,
895 mdspanx22_t(facets1.data(),
896 facets1.size() / 4, 2, 2)},
897 P1T, cell_info1},
898 bc0, bc1, fn, constants,
899 mdspanx2x_t(coeffs.data(), facets.size() / 4, 2, cstride),
900 facet_perms, std::span(Ab), std::span(cdofs_b),
901 std::span(dmap_b), std::span(Ae_block_b));
902 });
903 }
904
905 for (auto itg_type : {fem::IntegralType::exterior_facet,
907 {
908 const int num_itg = a.num_integrals(itg_type, cell_type_idx);
909 if (num_itg == 0)
910 continue;
911
912 // Each integral type is over entities of a different
913 // codimension, so only the permutations this form actually
914 // integrates over are computed.
915 md::mdspan<const std::uint8_t, md::dextents<std::size_t, 2>> perms;
916 if (a.needs_facet_permutations())
917 {
918 perms = impl::entity_permutations(
919 *mesh->topology_mutable(), itg_type,
920 mesh->topology()->cell_types()[cell_type_idx]);
921 }
922
923 for (int i = 0; i < num_itg; ++i)
924 {
925 if (num_cell_types > 1)
926 {
927 throw std::invalid_argument("Exterior facet integrals with mixed "
928 "topology aren't supported yet");
929 }
930
931 using mdspanx2_t
932 = md::mdspan<const std::int32_t,
933 md::extents<std::size_t, md::dynamic_extent, 2>>;
934
935 auto fn = a.kernel(itg_type, i, 0);
936 assert(fn);
937 auto& [coeffs, cstride] = coefficients.at({itg_type, i});
938
939 std::span e = a.domain(itg_type, i, 0);
940 mdspanx2_t entities(e.data(), e.size() / 2, 2);
941 std::span e0 = a.domain_arg(itg_type, 0, i, 0);
942 mdspanx2_t entities0(e0.data(), e0.size() / 2, 2);
943 std::span e1 = a.domain_arg(itg_type, 1, i, 0);
944 mdspanx2_t entities1(e1.data(), e1.size() / 2, 2);
945 assert((entities.size() / 2) * cstride == coeffs.size());
946 impl::dispatch_bs(
947 bs0, bs1,
948 [&mat_set, &geometry, &entities, &dofs0, &entities0, &P0,
949 &cell_info0, &dofs1, &entities1, &P1T, &cell_info1, &bc0, &bc1,
950 &fn, &coeffs, cstride, &constants, &perms, &Ab1,
951 &cdofs_b1](auto bs0, auto bs1)
952 {
953 impl::assemble_entities_matrix<LiftingMode>(
954 mat_set, geometry, entities,
955 FormArgument{DofMapPack{dofs0, bs0, entities0}, P0,
956 cell_info0},
957 FormArgument{DofMapPack{dofs1, bs1, entities1}, P1T,
958 cell_info1},
959 bc0, bc1, fn, constants,
960 md::mdspan(coeffs.data(), entities.extent(0), cstride), perms,
961 Ab1, cdofs_b1);
962 });
963 }
964 }
965 }
966}
967} // namespace dolfinx::fem::impl
Degree-of-freedom map representations and tools.
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:32
@ 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