DOLFINx 0.12.0.0
DOLFINx C++
Loading...
Searching...
No Matches
discreteoperators.h
1// Copyright (C) 2015-2026 Garth N. Wells, Jørgen S. Dokken
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 "ElementDofLayout.h"
11#include "FiniteElement.h"
12#include "FunctionSpace.h"
13#include "sparsitybuild.h"
14#include <algorithm>
15#include <array>
16#include <basix/sobolev-spaces.h>
17#include <concepts>
18#include <cstdint>
19#include <dolfinx/common/IndexMap.h>
20#include <dolfinx/common/math.h>
21#include <dolfinx/la/utils.h>
22#include <dolfinx/mesh/Mesh.h>
23#include <memory>
24#include <span>
25#include <stdexcept>
26#include <utility>
27#include <vector>
28
29namespace dolfinx::fem
30{
31
32namespace impl
33{
36template <std::floating_point T>
37void check_derivative_source(const FiniteElement<T>& element)
38{
39 const basix::sobolev::space space = element.basix_element().sobolev_space();
40 const bool h1
41 = basix::sobolev::space_intersection(space, basix::sobolev::space::H1)
42 == basix::sobolev::space::H1;
43 if (element.map_type() == basix::maps::type::identity and !h1)
44 {
45 throw std::invalid_argument(
46 "Source element must be H1-conforming for the discrete gradient.");
47 }
48 if (element.map_type() == basix::maps::type::covariantPiola and !h1
49 and space != basix::sobolev::space::HCurl)
50 {
51 throw std::invalid_argument(
52 "Source element must be H(curl)-conforming for the discrete curl.");
53 }
54}
55
69template <dolfinx::scalar T>
70void insert_entity_blocks(
71 std::span<const std::pair<std::span<const int>, std::span<const int>>>
72 blocks,
73 std::span<const T> A, std::size_t ncols,
74 std::span<const std::int32_t> cell_rows,
75 std::span<const std::int32_t> cell_cols, std::span<std::int32_t> rows,
76 std::span<std::int32_t> cols, std::span<T> Ae, auto&& mat_set)
77{
78 for (const auto& [rdofs, cdofs] : blocks)
79 {
80 std::ranges::transform(cdofs, cols.begin(),
81 [cell_cols](int d) { return cell_cols[d]; });
82 for (std::size_t i = 0; i < rdofs.size(); ++i)
83 {
84 rows[i] = cell_rows[rdofs[i]];
85 for (std::size_t j = 0; j < cdofs.size(); ++j)
86 Ae[i * cdofs.size() + j] = A[rdofs[i] * ncols + cdofs[j]];
87 }
88 mat_set(rows.first(rdofs.size()), cols.first(cdofs.size()),
89 Ae.first(rdofs.size() * cdofs.size()));
90 }
91}
92} // namespace impl
93
164template <std::floating_point T, dolfinx::scalar U = T>
166 la::MatSet<U> auto&& mat_set)
167{
168 // Get mesh
169 auto mesh = V0.mesh();
170 assert(mesh);
171 assert(V1.mesh());
172 if (mesh != V1.mesh())
173 throw std::invalid_argument("Meshes must be the same.");
174
175 if (mesh->geometry().dim() != 3)
176 throw std::invalid_argument("Geometric must be equal to 3..");
177 if (mesh->geometry().dim() != mesh->topology()->dim())
178 {
179 throw std::invalid_argument(
180 "Geometric and topological dimensions must be equal.");
181 }
182 constexpr int gdim = 3;
183
184 // Get elements
185 std::shared_ptr<const FiniteElement<T>> e0 = V0.element();
186 assert(e0);
187 if (e0->map_type() != basix::maps::type::covariantPiola)
188 {
189 throw std::invalid_argument(
190 "Finite element for parent space must be covariant Piola.");
191 }
192 impl::check_derivative_source(*e0);
193
194 std::shared_ptr<const FiniteElement<T>> e1 = V1.element();
195 assert(e1);
196 if (e1->map_type() != basix::maps::type::contravariantPiola)
197 {
198 throw std::invalid_argument(
199 "Finite element for target space must be contracovariant Piola.");
200 }
201
202 // Get cell orientation information
203 std::span<const std::uint32_t> cell_info;
204 if (e1->needs_dof_transformations() or e0->needs_dof_transformations())
205 {
206 mesh->topology_mutable()->create_cell_permutations();
207 cell_info = std::span(mesh->topology()->get_cell_permutation_info());
208 }
209
210 // Get dofmaps
211 auto dofmap0 = V0.dofmap();
212 assert(dofmap0);
213 auto dofmap1 = V1.dofmap();
214 assert(dofmap1);
215
216 // Get dof transformation operators
217 auto apply_dof_transformation0
218 = e0->template dof_transformation_fn<T>(doftransform::standard, false);
219 auto apply_inverse_dof_transform1 = e1->template dof_transformation_fn<U>(
221
222 // Get sizes of elements
223 const std::size_t space_dim0 = e0->space_dimension();
224 const std::size_t space_dim1 = e1->space_dimension();
225 if (e0->reference_value_size() != 3)
226 throw std::invalid_argument("Value size for parent space should be 3.");
227 if (e1->reference_value_size() != 3)
228 throw std::invalid_argument("Value size for target space should be 3.");
229
230 // Get the V1 reference interpolation points
231 const auto [X, Xshape] = e1->interpolation_points();
232
233 // Evaluate V0 basis function derivatives at reference interpolation
234 // points for V1, (deriv, pt_idx, phi (dof), comp)
235 const auto [Phi0_b, Phi0_shape] = e0->tabulate(X, Xshape, 1);
236 md::mdspan<const T, md::extents<std::size_t, 4, md::dynamic_extent,
237 md::dynamic_extent, 3>>
238 Phi0(Phi0_b.data(), Phi0_shape);
239
240 // Create working arrays, (point, phi (dof), deriv, comp)
241 md::extents<std::size_t, md::dynamic_extent, md::dynamic_extent, 3, 3>
242 dPhi0_ext(Phi0.extent(1), Phi0.extent(2), Phi0.extent(0) - 1,
243 Phi0.extent(3));
244 std::vector<T> dPhi0_b(dPhi0_ext.extent(0) * dPhi0_ext.extent(1)
245 * dPhi0_ext.extent(2) * dPhi0_ext.extent(3));
246 md::mdspan<T, decltype(dPhi0_ext)> dPhi0(dPhi0_b.data(), dPhi0_ext);
247
248 // Get the interpolation operator (matrix) Pi that maps a function
249 // evaluated at the interpolation points to the V1 element degrees of
250 // freedom, i.e. dofs = Pi f_x
251 const auto [Pi1_b, pi_shape] = e1->interpolation_operator();
252 md::mdspan<const T, md::dextents<std::size_t, 2>> Pi_1(Pi1_b.data(),
253 pi_shape);
254
255 // curl data structure, (pt_idx, phi (dof), comp)
256 std::vector<T> curl_b(dPhi0.extent(0) * dPhi0.extent(1) * dPhi0.extent(3));
257 md::mdspan<
258 T, md::extents<std::size_t, md::dynamic_extent, md::dynamic_extent, 3>>
259 curl(curl_b.data(), dPhi0.extent(0), dPhi0.extent(1), dPhi0.extent(3));
260
261 std::vector<U> Ab(space_dim0 * space_dim1);
262
263 // Insert the element matrix one mesh entity at a time, so that the
264 // operator gets its exact sparsity
265 const std::vector<std::pair<std::span<const int>, std::span<const int>>>
267 dofmap1->element_dof_layout(), dofmap0->element_dof_layout());
268 std::vector<U> Ab_e(Ab.size());
269 std::vector<std::int32_t> rows(space_dim1), cols(space_dim0);
270
271 // Iterate over mesh and interpolate on each cell
272 assert(mesh->topology()->index_map(gdim));
273 for (std::int32_t c = 0; c < mesh->topology()->index_map(gdim)->size_local();
274 ++c)
275 {
276 // TODO: re-order loops and/or re-pack Phi0 to allow a simple flat
277 // copy?
278
279 // Copy (d)Phi0 (on reference) and apply DOF transformation
280 // Phi0: (deriv, pt_idx, phi (dof), comp)
281 // dPhi0: (pt_idx, phi (dov), deriv, comp)
282 for (std::size_t p = 0; p < Phi0.extent(1); ++p) // point
283 for (std::size_t phi = 0; phi < Phi0.extent(2); ++phi) // phi_i
284 for (std::size_t d = 0; d < Phi0.extent(3); ++d) // Comp. of phi
285 for (std::size_t dx = 0; dx < dPhi0.extent(2); ++dx) // dx
286 dPhi0(p, phi, dx, d) = Phi0(dx + 1, p, phi, d);
287
288 for (std::size_t p = 0; p < dPhi0.extent(0); ++p) // point
289 {
290 // Size: num_phi * num_derivs * num_components
291 std::size_t size = dPhi0.extent(1) * dPhi0.extent(2) * dPhi0.extent(3);
292 std::size_t offset = p * size; // Offset for point p
293
294 // Shape: (num_phi , (value_size * num_derivs))
295 if (apply_dof_transformation0)
296 {
297 apply_dof_transformation0(std::span(dPhi0.data_handle() + offset, size),
298 cell_info, c,
299 dPhi0.extent(2) * dPhi0.extent(3));
300 }
301 }
302
303 // Compute curl
304 // dPhi0: (pt_idx, phi_idx, deriv, comp)
305 // curl: (pt_idx, phi_idx, comp)
306 for (std::size_t p = 0; p < curl.extent(0); ++p) // point
307 {
308 for (std::size_t i = 0; i < curl.extent(1); ++i) // phi_i
309 {
310 curl(p, i, 0) = dPhi0(p, i, 1, 2) - dPhi0(p, i, 2, 1);
311 curl(p, i, 1) = dPhi0(p, i, 2, 0) - dPhi0(p, i, 0, 2);
312 curl(p, i, 2) = dPhi0(p, i, 0, 1) - dPhi0(p, i, 1, 0);
313 }
314 }
315
316 // Apply interpolation matrix to basis derivative values of V0 at
317 // the interpolation points of V1. Pi_1 does not depend on the
318 // basis function index i, so all space_dim0 applications are
319 // combined into a single loop nest (rather than calling
320 // interpolation_apply once per basis function) so that each row
321 // of Pi_1 is read from memory once instead of space_dim0 times.
322 std::ranges::fill(Ab, U(0));
323 for (std::size_t j = 0; j < space_dim1; ++j) // V1 dof
324 for (std::size_t k = 0; k < curl.extent(2); ++k) // component
325 for (std::size_t p = 0; p < curl.extent(0); ++p) // point
326 {
327 T pi_val = Pi_1(j, k * curl.extent(0) + p);
328 for (std::size_t i = 0; i < space_dim0; ++i) // V0 basis function
329 Ab[space_dim0 * j + i] += static_cast<U>(pi_val * curl(p, i, k));
330 }
331
332 if (apply_inverse_dof_transform1)
333 apply_inverse_dof_transform1(Ab, cell_info, c, space_dim0);
334
335 impl::insert_entity_blocks<U>(blocks, Ab, space_dim0, dofmap1->cell_dofs(c),
336 dofmap0->cell_dofs(c), rows, cols, Ab_e,
337 mat_set);
338 }
339}
340
370template <dolfinx::scalar T, std::floating_point U = dolfinx::scalar_value_t<T>>
372 std::pair<std::reference_wrapper<const FiniteElement<U>>,
373 std::reference_wrapper<const DofMap>>
374 V0,
375 std::pair<std::reference_wrapper<const FiniteElement<U>>,
376 std::reference_wrapper<const DofMap>>
377 V1,
378 auto&& mat_set)
379{
380 auto& e0 = V0.first.get();
381 const DofMap& dofmap0 = V0.second.get();
382 auto& e1 = V1.first.get();
383 const DofMap& dofmap1 = V1.second.get();
384
385 using cmdspan2_t = md::mdspan<const U, md::dextents<std::size_t, 2>>;
386 using cmdspan4_t = md::mdspan<const U, md::dextents<std::size_t, 4>>;
387
388 // Check elements
389 if (e0.map_type() != basix::maps::type::identity)
390 throw std::invalid_argument("Wrong finite element space for V0.");
391 if (e0.block_size() != 1)
392 throw std::invalid_argument("Block size is greater than 1 for V0.");
393 if (e0.reference_value_size() != 1)
394 throw std::invalid_argument("Wrong value size for V0.");
395 impl::check_derivative_source(e0);
396
397 if (e1.map_type() != basix::maps::type::covariantPiola)
398 throw std::invalid_argument("Wrong finite element space for V1.");
399 if (e1.block_size() != 1)
400 throw std::invalid_argument("Block size is greater than 1 for V1.");
401
402 // Get V1 (H(curl)) space interpolation points
403 const auto [X, Xshape] = e1.interpolation_points();
404
405 // Tabulate first derivatives of Lagrange space at V1 interpolation
406 // points
407 const int ndofs0 = e0.space_dimension();
408 const int tdim = topology.dim();
409 std::vector<U> phi0_b((tdim + 1) * Xshape[0] * ndofs0 * 1);
410 cmdspan4_t phi0(phi0_b.data(), tdim + 1, Xshape[0], ndofs0, 1);
411 e0.tabulate(phi0_b, X, Xshape, 1);
412
413 // Reshape Lagrange basis derivatives as a matrix of shape (tdim *
414 // num_points, num_dofs_per_cell)
415 cmdspan2_t dphi_reshaped(
416 phi0_b.data() + phi0.extent(3) * phi0.extent(2) * phi0.extent(1),
417 tdim * phi0.extent(1), phi0.extent(2));
418
419 // Get inverse DOF transform function
420 auto apply_inverse_dof_transform = e1.template dof_transformation_fn<T>(
422
423 // Generate cell permutations
424 topology.create_cell_permutations();
425 const std::vector<std::uint32_t>& cell_info
426 = topology.get_cell_permutation_info();
427
428 // Create element kernel function
429
430 // Build the element interpolation matrix
431 std::vector<T> Ab(e1.space_dimension() * ndofs0);
432 {
433 md::mdspan<T, md::dextents<std::size_t, 2>> A(Ab.data(),
434 e1.space_dimension(), ndofs0);
435 const auto [Pi, shape] = e1.interpolation_operator();
436 cmdspan2_t _Pi(Pi.data(), shape);
437 math::dot(_Pi, dphi_reshaped, A);
438 }
439
440 // Insert the element matrix one mesh entity at a time, so that the
441 // operator gets its exact sparsity
442 const std::vector<std::pair<std::span<const int>, std::span<const int>>>
444 dofmap1.element_dof_layout(), dofmap0.element_dof_layout());
445 auto cell_map = topology.index_map(tdim);
446 assert(cell_map);
447 std::int32_t num_cells = cell_map->size_local();
448 std::vector<T> Ae(Ab.size()), Ab_e(Ab.size());
449 std::vector<std::int32_t> rows(e1.space_dimension()), cols(ndofs0);
450 for (std::int32_t c = 0; c < num_cells; ++c)
451 {
452 std::ranges::copy(Ab, Ae.begin());
453 if (apply_inverse_dof_transform)
454 apply_inverse_dof_transform(Ae, cell_info, c, ndofs0);
455 impl::insert_entity_blocks<T>(blocks, Ae, ndofs0, dofmap1.cell_dofs(c),
456 dofmap0.cell_dofs(c), rows, cols, Ab_e,
457 mat_set);
458 }
459}
460
480template <dolfinx::scalar T, std::floating_point U>
482 const FunctionSpace<U>& V1, auto&& mat_add)
483{
484 // Get mesh
485 auto mesh = V0.mesh();
486 assert(mesh);
487
488 // Mesh dims
489 const int tdim = mesh->topology()->dim();
490 const int gdim = mesh->geometry().dim();
491
492 // Get elements
493 std::shared_ptr<const FiniteElement<U>> e0 = V0.element();
494 assert(e0);
495 std::shared_ptr<const FiniteElement<U>> e1 = V1.element();
496 assert(e1);
497
498 if (!std::ranges::equal(e0->value_shape(), e1->value_shape()))
499 {
500 throw std::invalid_argument(
501 "Interpolation operator: elements have different value dimensions");
502 }
503
504 std::span<const std::uint32_t> cell_info;
505 if (e1->needs_dof_transformations() or e0->needs_dof_transformations())
506 {
507 mesh->topology_mutable()->create_cell_permutations();
508 cell_info = std::span(mesh->topology()->get_cell_permutation_info());
509 }
510
511 // Get dofmaps
512 auto dofmap0 = V0.dofmap();
513 assert(dofmap0);
514 auto dofmap1 = V1.dofmap();
515 assert(dofmap1);
516
517 // Get block sizes and dof transformation operators
518 const int bs0 = e0->block_size();
519 const int bs1 = e1->block_size();
520 auto apply_dof_transformation0
521 = e0->template dof_transformation_fn<U>(doftransform::standard, false);
522 auto apply_inverse_dof_transform1 = e1->template dof_transformation_fn<T>(
524
525 // Get sizes of elements
526 const std::size_t space_dim0 = e0->space_dimension();
527 const std::size_t space_dim1 = e1->space_dimension();
528 const std::size_t dim0 = space_dim0 / bs0;
529 const std::size_t value_size_ref0 = e0->reference_value_size();
530 // basis0 holds one block of V0's basis pushed forward to the physical
531 // cell; basis_values holds all bs0 blocks; mapped_values holds them
532 // pulled back to the reference cell of e1.
533 const std::size_t value_size0 = e0->physical_base_value_size();
534 const std::size_t value_size_phys = e0->value_size();
535 const std::size_t value_size_ref1
536 = e1->reference_value_size() * static_cast<std::size_t>(bs1);
537
538 // Get geometry data
539 const CoordinateElement<U>& cmap = mesh->geometry().cmaps().front();
540 auto x_dofmap = mesh->geometry().dofmaps().front();
541 const std::size_t num_dofs_g = cmap.dim();
542 std::span<const U> x_g = mesh->geometry().x();
543
544 using mdspan2_t = md::mdspan<U, md::dextents<std::size_t, 2>>;
545 using cmdspan2_t = md::mdspan<const U, md::dextents<std::size_t, 2>>;
546 using cmdspan4_t = md::mdspan<const U, md::dextents<std::size_t, 4>>;
547 using mdspan3_t = md::mdspan<U, md::dextents<std::size_t, 3>>;
548
549 // Evaluate coordinate map basis at reference interpolation points
550 const auto [X, Xshape] = e1->interpolation_points();
551 std::array<std::size_t, 4> phi_shape = cmap.tabulate_shape(1, Xshape[0]);
552 std::vector<U> phi_b(
553 std::reduce(phi_shape.begin(), phi_shape.end(), 1, std::multiplies{}));
554 cmdspan4_t phi(phi_b.data(), phi_shape);
555 cmap.tabulate(1, X, Xshape, phi_b);
556
557 // Evaluate V0 basis functions at reference interpolation points for V1
558 std::vector<U> basis_derivatives_reference0_b(Xshape[0] * dim0
559 * value_size_ref0);
560 cmdspan4_t basis_derivatives_reference0(basis_derivatives_reference0_b.data(),
561 1, Xshape[0], dim0, value_size_ref0);
562 e0->tabulate(basis_derivatives_reference0_b, X, Xshape, 0);
563
564 // Clamp values
565 std::ranges::transform(
566 basis_derivatives_reference0_b, basis_derivatives_reference0_b.begin(),
567 [atol = 1e-14](auto x) { return std::abs(x) < atol ? 0.0 : x; });
568
569 // Create working arrays
570 std::vector<U> basis_reference0_b(Xshape[0] * dim0 * value_size_ref0);
571 mdspan3_t basis_reference0(basis_reference0_b.data(), Xshape[0], dim0,
572 value_size_ref0);
573 std::vector<U> J_b(Xshape[0] * gdim * tdim);
574 mdspan3_t J(J_b.data(), Xshape[0], gdim, tdim);
575 std::vector<U> K_b(Xshape[0] * tdim * gdim);
576 mdspan3_t K(K_b.data(), Xshape[0], tdim, gdim);
577 std::vector<U> detJ(Xshape[0]);
578 std::vector<U> det_scratch(2 * tdim * gdim);
579
580 // Get the interpolation operator (matrix) `Pi` that maps a function
581 // evaluated at the interpolation points to the element degrees of
582 // freedom, i.e. dofs = Pi f_x
583 const auto [_Pi_1, pi_shape] = e1->interpolation_operator();
584 cmdspan2_t Pi_1(_Pi_1.data(), pi_shape);
585
586 bool interpolation_ident = e1->interpolation_ident();
587
588 using u_t = md::mdspan<U, md::dextents<std::size_t, 2>>;
589 using U_t = md::mdspan<const U, md::dextents<std::size_t, 2>>;
590 using J_t = md::mdspan<const U, md::dextents<std::size_t, 2>>;
591 using K_t = md::mdspan<const U, md::dextents<std::size_t, 2>>;
592 auto push_forward_fn0
593 = e0->basix_element().template map_fn<u_t, U_t, J_t, K_t>();
594
595 // Basis values of Lagrange space unrolled for block size
596 // (num_quadrature_points, Lagrange dof, value_size)
597 std::vector<U> basis_values_b(Xshape[0] * bs0 * dim0 * value_size_phys);
598 mdspan3_t basis_values(basis_values_b.data(), Xshape[0], bs0 * dim0,
599 value_size_phys);
600 std::vector<U> mapped_values_b(Xshape[0] * bs0 * dim0 * value_size_ref1);
601 mdspan3_t mapped_values(mapped_values_b.data(), Xshape[0], bs0 * dim0,
602 value_size_ref1);
603
604 auto pull_back_fn1
605 = e1->basix_element().template map_fn<u_t, U_t, K_t, J_t>();
606
607 std::vector<U> coord_dofs_b(num_dofs_g * gdim);
608 mdspan2_t coord_dofs(coord_dofs_b.data(), num_dofs_g, gdim);
609 std::vector<U> basis0_b(Xshape[0] * dim0 * value_size0);
610 mdspan3_t basis0(basis0_b.data(), Xshape[0], dim0, value_size0);
611
612 // Buffers
613 std::vector<T> Ab(space_dim0 * space_dim1);
614
615 // Iterate over mesh and interpolate on each cell
616 auto cell_map = mesh->topology()->index_map(tdim);
617 assert(cell_map);
618 std::int32_t num_cells = cell_map->size_local();
619 int row_bs = dofmap1->bs();
620 std::int32_t num_owned_rows
621 = dofmap1->index_map->size_local() * dofmap1->index_map_bs() / row_bs;
622 std::int32_t num_ghosted_rows
623 = dofmap1->index_map->num_ghosts() * dofmap1->index_map_bs() / row_bs;
624 std::vector<std::int8_t> row_added(num_owned_rows + num_ghosted_rows, 0);
625
626 for (std::int32_t c = 0; c < num_cells; ++c)
627 {
628 // Get cell geometry (coordinate dofs)
629 auto x_dofs = md::submdspan(x_dofmap, c, md::full_extent);
630 for (std::size_t i = 0; i < x_dofs.size(); ++i)
631 {
632 for (int j = 0; j < gdim; ++j)
633 coord_dofs(i, j) = x_g[3 * x_dofs[i] + j];
634 }
635
636 // Compute Jacobians and reference points for current cell. For an
637 // affine map the Jacobian (and hence its inverse and determinant)
638 // is constant over the cell, so it is computed once and broadcast
639 // to the remaining interpolation points rather than being
640 // recomputed Xshape[0] times.
641 std::ranges::fill(J_b, 0);
642 if (cmap.is_affine() and Xshape[0] > 0)
643 {
644 auto dphi
645 = md::submdspan(phi, std::pair(1, tdim + 1), 0, md::full_extent, 0);
646 auto _J = md::submdspan(J, 0, md::full_extent, md::full_extent);
647 cmap.compute_jacobian(dphi, coord_dofs, _J);
648 auto _K = md::submdspan(K, 0, md::full_extent, md::full_extent);
649 cmap.compute_jacobian_inverse(_J, _K);
650 detJ[0] = cmap.compute_jacobian_determinant(_J, det_scratch);
651 for (std::size_t p = 1; p < Xshape[0]; ++p)
652 {
653 std::copy_n(J_b.begin(), gdim * tdim, J_b.begin() + p * gdim * tdim);
654 std::copy_n(K_b.begin(), tdim * gdim, K_b.begin() + p * tdim * gdim);
655 detJ[p] = detJ[0];
656 }
657 }
658 else
659 {
660 for (std::size_t p = 0; p < Xshape[0]; ++p)
661 {
662 auto dphi
663 = md::submdspan(phi, std::pair(1, tdim + 1), p, md::full_extent, 0);
664 auto _J = md::submdspan(J, p, md::full_extent, md::full_extent);
665 cmap.compute_jacobian(dphi, coord_dofs, _J);
666 auto _K = md::submdspan(K, p, md::full_extent, md::full_extent);
667 cmap.compute_jacobian_inverse(_J, _K);
668 detJ[p] = cmap.compute_jacobian_determinant(_J, det_scratch);
669 }
670 }
671
672 // Copy evaluated basis on reference (a fresh copy is needed each
673 // cell as DOF transformations below are applied in place), and
674 // push forward to physical element. The source and destination
675 // have identical memory layout (the leading, unit-length
676 // derivative axis of basis_derivatives_reference0 is a no-op for
677 // indexing purposes), so a flat copy is used instead of an
678 // element-by-element mdspan loop.
679 std::ranges::copy(basis_derivatives_reference0_b,
680 basis_reference0_b.begin());
681 if (apply_dof_transformation0)
682 {
683 for (std::size_t p = 0; p < Xshape[0]; ++p)
684 {
685 apply_dof_transformation0(std::span(basis_reference0.data_handle()
686 + p * dim0 * value_size_ref0,
687 dim0 * value_size_ref0),
688 cell_info, c, value_size_ref0);
689 }
690 }
691
692 for (std::size_t p = 0; p < basis0.extent(0); ++p)
693 {
694 auto _u = md::submdspan(basis0, p, md::full_extent, md::full_extent);
695 auto _U = md::submdspan(basis_reference0, p, md::full_extent,
696 md::full_extent);
697 auto _K = md::submdspan(K, p, md::full_extent, md::full_extent);
698 auto _J = md::submdspan(J, p, md::full_extent, md::full_extent);
699 push_forward_fn0(_u, _U, _J, detJ[p], _K);
700 }
701
702 // Unroll basis function for input space for block size
703 for (std::size_t p = 0; p < Xshape[0]; ++p)
704 for (std::size_t i = 0; i < dim0; ++i)
705 for (std::size_t j = 0; j < value_size0; ++j)
706 for (int k = 0; k < bs0; ++k)
707 basis_values(p, i * bs0 + k, j * bs0 + k) = basis0(p, i, j);
708
709 // Pull back the physical values to the reference of output space
710 for (std::size_t p = 0; p < basis_values.extent(0); ++p)
711 {
712 auto _u
713 = md::submdspan(basis_values, p, md::full_extent, md::full_extent);
714 auto _U
715 = md::submdspan(mapped_values, p, md::full_extent, md::full_extent);
716 auto _K = md::submdspan(K, p, md::full_extent, md::full_extent);
717 auto _J = md::submdspan(J, p, md::full_extent, md::full_extent);
718 pull_back_fn1(_U, _u, _K, 1.0 / detJ[p], _J);
719 }
720
721 // Apply interpolation matrix to basis values of V0 at the
722 // interpolation points of V1
723 if (interpolation_ident)
724 {
725 md::mdspan<T, md::dextents<std::size_t, 3>> A(
726 Ab.data(), Xshape[0], value_size_ref1, space_dim0);
727 for (std::size_t i = 0; i < mapped_values.extent(0); ++i)
728 for (std::size_t j = 0; j < mapped_values.extent(1); ++j)
729 for (std::size_t k = 0; k < mapped_values.extent(2); ++k)
730 A(i, k, j) = mapped_values(i, j, k);
731 }
732 else
733 {
734 // Apply interpolation matrix to basis values of V0 at the
735 // interpolation points of V1. Pi_1 does not depend on the basis
736 // function index i, so all space_dim0 applications are combined
737 // into a single loop nest (rather than calling
738 // interpolation_apply once per basis function) so that each row
739 // of Pi_1 is read from memory once instead of space_dim0 times.
740 // This mirrors the two cases handled by interpolation_apply
741 // (interpolate.h): bs1 == 1 (columns of Pi_1 fold the point and
742 // component indices together) and bs1 != 1 (each block/component
743 // is handled by a separate outer loop, columns of Pi_1 index
744 // points only).
745 std::ranges::fill(Ab, T(0));
746 std::size_t num_pts = mapped_values.extent(0);
747 if (bs1 == 1)
748 {
749 for (std::size_t idof = 0; idof < Pi_1.extent(0); ++idof)
750 for (std::size_t k = 0; k < mapped_values.extent(2); ++k)
751 for (std::size_t p = 0; p < num_pts; ++p)
752 {
753 U pi_val = Pi_1(idof, k * num_pts + p);
754 for (std::size_t i = 0; i < space_dim0; ++i) // V0 basis fn
755 {
756 Ab[space_dim0 * idof + i]
757 += static_cast<T>(pi_val * mapped_values(p, i, k));
758 }
759 }
760 }
761 else
762 {
763 for (std::size_t idof = 0; idof < Pi_1.extent(0); ++idof)
764 for (int k = 0; k < bs1; ++k)
765 for (std::size_t p = 0; p < num_pts; ++p)
766 {
767 U pi_val = Pi_1(idof, p);
768 for (std::size_t i = 0; i < space_dim0; ++i) // V0 basis fn
769 {
770 Ab[space_dim0 * (bs1 * idof + k) + i]
771 += static_cast<T>(pi_val * mapped_values(p, i, k));
772 }
773 }
774 }
775 }
776
777 if (apply_inverse_dof_transform1)
778 apply_inverse_dof_transform1(Ab, cell_info, c, space_dim0);
779
780 // Zero out all rows that have already been added on this process
781 // and only add owned rows
782 {
783 md::mdspan<T, md::dextents<std::size_t, 2>> A(Ab.data(), space_dim1,
784 space_dim0);
785 auto row_dofs = dofmap1->cell_dofs(c);
786 for (std::size_t i = 0; i < row_dofs.size(); ++i)
787 {
788 std::int32_t r = row_dofs[i];
789 if (r >= num_owned_rows || row_added[r])
790 {
791 for (std::size_t j = 0; j < space_dim0; ++j)
792 for (int k = 0; k < row_bs; ++k)
793 A(i * row_bs + k, j) = 0.0;
794 }
795 row_added[r] = 1;
796 }
797 }
798 mat_add(dofmap1->cell_dofs(c), dofmap0->cell_dofs(c), Ab);
799 }
800}
801
802} // namespace dolfinx::fem
Degree-of-freedom map representations and tools.
Definition CoordinateElement.h:39
static void compute_jacobian(const U &dphi, const V &cell_geometry, W &&J)
Definition CoordinateElement.h:139
void tabulate(int nd, std::span< const T > X, std::array< std::size_t, 2 > shape, std::span< T > basis) const
Evaluate basis values and derivatives at set of points.
Definition CoordinateElement.cpp:61
static void compute_jacobian_inverse(const U &J, V &&K)
Compute the inverse of the Jacobian.
Definition CoordinateElement.h:148
std::array< std::size_t, 4 > tabulate_shape(std::size_t nd, std::size_t num_points) const
Shape of array to fill when calling tabulate.
Definition CoordinateElement.cpp:54
int dim() const
The dimension of the coordinate element space.
Definition CoordinateElement.cpp:225
bool is_affine() const noexcept
Check if geometry map is affine.
Definition CoordinateElement.h:295
static double compute_jacobian_determinant(const U &J, std::span< typename U::value_type > w)
Compute the determinant of the Jacobian.
Definition CoordinateElement.h:165
Degree-of-freedom map.
Definition DofMap.h:73
std::span< const std::int32_t > cell_dofs(std::int32_t c) const
Local-to-global mapping of dofs on a cell.
Definition DofMap.h:127
const ElementDofLayout & element_dof_layout() const
Layout of dofs on an element.
Definition DofMap.h:157
Model of a finite element.
Definition FiniteElement.h:201
This class represents a finite element function space defined by a mesh, a finite element,...
Definition FunctionSpace.h:36
std::shared_ptr< const DofMap > dofmap() const
The dofmap.
Definition FunctionSpace.h:408
std::shared_ptr< const mesh::Mesh< geometry_type > > mesh() const
The mesh.
Definition FunctionSpace.h:383
std::shared_ptr< const FiniteElement< geometry_type > > element() const
The finite element.
Definition FunctionSpace.h:389
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
const std::vector< std::uint32_t > & get_cell_permutation_info() const
Get the cell permutation information.
Definition Topology.cpp:978
void create_cell_permutations(int num_threads=1)
Compute the packed per-cell permutation info.
Definition Topology.cpp:1229
int dim() const noexcept
Topological dimension of the mesh.
Definition Topology.cpp:897
Matrix accumulate/set concept for functions that can be used in assemblers to accumulate or set value...
Definition utils.h:33
std::vector< std::pair< std::span< const int >, std::span< const int > > > entity_closure_blocks(const ElementDofLayout &layout_rows, const ElementDofLayout &layout_cols)
Element-matrix blocks of an entity-closure stencil.
Definition sparsitybuild.cpp:20
Finite element method functionality.
Definition assemble_expression_impl.h:28
void discrete_curl(const FunctionSpace< T > &V0, const FunctionSpace< T > &V1, la::MatSet< U > auto &&mat_set)
Assemble a discrete curl operator.
Definition discreteoperators.h:165
void discrete_gradient(mesh::Topology &topology, std::pair< std::reference_wrapper< const FiniteElement< U > >, std::reference_wrapper< const DofMap > > V0, std::pair< std::reference_wrapper< const FiniteElement< U > >, std::reference_wrapper< const DofMap > > V1, auto &&mat_set)
Assemble a discrete gradient operator.
Definition discreteoperators.h:371
@ inverse_transpose
Transpose inverse.
Definition FiniteElement.h:36
@ standard
Standard.
Definition FiniteElement.h:33
void interpolation_matrix(const FunctionSpace< U > &V0, const FunctionSpace< U > &V1, auto &&mat_add)
Assemble an interpolation operator matrix.
Definition discreteoperators.h:481
Mesh data structures and algorithms on meshes.
Definition DofMap.h:32