DOLFINx 0.12.0.0
DOLFINx C++
Loading...
Searching...
No Matches
interpolate.h
1// Copyright (C) 2020-2026 Garth N. Wells, Igor A. Baratta, Massimiliano Leoni,
2// Jørgen S.Dokken and Paul T. Kühner
3//
4// This file is part of DOLFINx (https://www.fenicsproject.org)
5//
6// SPDX-License-Identifier: LGPL-3.0-or-later
7
8#pragma once
9
10#include "CoordinateElement.h"
11#include "DofMap.h"
12#include "FiniteElement.h"
13#include "FunctionSpace.h"
14#include <algorithm>
15#include <basix/mdspan.hpp>
16#include <concepts>
17#include <dolfinx/common/IndexMap.h>
18#include <dolfinx/common/log.h>
19#include <dolfinx/common/types.h>
20#include <dolfinx/geometry/utils.h>
21#include <dolfinx/mesh/Mesh.h>
22#include <functional>
23#include <numeric>
24#include <ranges>
25#include <span>
26#include <stdexcept>
27#include <utility>
28#include <vector>
29
30namespace dolfinx::fem
31{
32template <dolfinx::scalar T, std::floating_point U>
33class Function;
34
45template <std::floating_point T>
46std::vector<T> interpolation_coords(const fem::FiniteElement<T>& element,
48 mesh::CellRange auto&& cells)
49{
50 // Find CoordinateElement appropriate to element
51 auto cmap_index = [&geometry](mesh::CellType cell_type)
52 {
53 for (std::size_t i = 0; i < geometry.cmaps().size(); ++i)
54 {
55 if (geometry.cmaps().at(i).cell_shape() == cell_type)
56 return i;
57 }
58 throw std::runtime_error("Cannot find CoordinateElement for FiniteElement");
59 };
60 int index = cmap_index(element.cell_type());
61
62 // Get geometry data and the element coordinate map
63 const std::size_t gdim = geometry.dim();
64 auto x_dofmap = geometry.dofmaps().at(index);
65 std::span<const T> x_g = geometry.x();
66
67 const CoordinateElement<T>& cmap = geometry.cmaps().at(index);
68 const std::size_t num_dofs_g = cmap.dim();
69
70 // Get the interpolation points on the reference cells
71 const auto [X, Xshape] = element.interpolation_points();
72
73 // Evaluate coordinate element basis at reference points
74 std::array<std::size_t, 4> phi_shape = cmap.tabulate_shape(0, Xshape[0]);
75 std::vector<T> phi_b(
76 std::reduce(phi_shape.begin(), phi_shape.end(), 1, std::multiplies{}));
77 md::mdspan<const T, md::extents<std::size_t, 1, md::dynamic_extent,
78 md::dynamic_extent, 1>>
79 phi_full(phi_b.data(), phi_shape);
80 cmap.tabulate(0, X, Xshape, phi_b);
81 auto phi = md::submdspan(phi_full, 0, md::full_extent, md::full_extent, 0);
82
83 // Push reference coordinates (X) forward to the physical coordinates
84 // (x) for each cell
85 std::vector<T> coordinate_dofs(num_dofs_g * gdim, 0);
86 std::vector<T> x(3 * (cells.size() * Xshape[0]), 0);
87 for (auto cell_it = cells.begin(); cell_it != cells.end(); ++cell_it)
88 {
89 // Get geometry data for current cell
90 auto x_dofs = md::submdspan(x_dofmap, *cell_it, md::full_extent);
91 for (std::size_t i = 0; i < x_dofs.size(); ++i)
92 {
93 std::copy_n(std::next(x_g.begin(), 3 * x_dofs[i]), gdim,
94 std::next(coordinate_dofs.begin(), i * gdim));
95 }
96
97 // Push forward coordinates (X -> x)
98 std::size_t offset = std::ranges::distance(cells.begin(), cell_it);
99 for (std::size_t p = 0; p < Xshape[0]; ++p)
100 {
101 for (std::size_t j = 0; j < gdim; ++j)
102 {
103 T acc = 0;
104 for (std::size_t k = 0; k < num_dofs_g; ++k)
105 acc += phi(p, k) * coordinate_dofs[k * gdim + j];
106 x[j * (cells.size() * Xshape[0]) + offset * Xshape[0] + p] = acc;
107 }
108 }
109 }
110
111 return x;
112}
113
138template <dolfinx::scalar T, std::floating_point U>
139void interpolate(Function<T, U>& u, std::span<const T> f,
140 std::array<std::size_t, 2> fshape,
141 mesh::CellRange auto&& cells);
142
143namespace impl
144{
146template <typename T, std::size_t D>
147using mdspan_t = md::mdspan<T, md::dextents<std::size_t, D>>;
148
168template <dolfinx::scalar T>
169void scatter_values(MPI_Comm comm, std::span<const std::int32_t> src_ranks,
170 std::span<const std::int32_t> dest_ranks,
171 mdspan_t<const T, 2> send_values, std::span<T> recv_values)
172{
173 const std::size_t block_size = send_values.extent(1);
174 assert(src_ranks.size() * block_size == send_values.size());
175 assert(recv_values.size() == dest_ranks.size() * block_size);
176
177 // Build unique set of the sorted src_ranks
178 std::vector<std::int32_t> out_ranks(src_ranks.size());
179 out_ranks.assign(src_ranks.begin(), src_ranks.end());
180 out_ranks.erase(std::ranges::unique(out_ranks).begin(), out_ranks.end());
181 out_ranks.reserve(out_ranks.size() + 1);
182
183 // Remove negative entries from dest_ranks
184 std::vector<std::int32_t> in_ranks;
185 in_ranks.reserve(dest_ranks.size());
186 std::copy_if(dest_ranks.begin(), dest_ranks.end(),
187 std::back_inserter(in_ranks),
188 [](auto rank) { return rank >= 0; });
189
190 // Create unique set of sorted in-ranks
191 std::ranges::sort(in_ranks);
192 in_ranks.erase(std::ranges::unique(in_ranks).begin(), in_ranks.end());
193 in_ranks.reserve(in_ranks.size() + 1);
194
195 // Create neighborhood communicator
196 MPI_Comm reverse_comm;
197 MPI_Dist_graph_create_adjacent(
198 comm, in_ranks.size(), in_ranks.data(), MPI_UNWEIGHTED, out_ranks.size(),
199 out_ranks.data(), MPI_UNWEIGHTED, MPI_INFO_NULL, false, &reverse_comm);
200
201 std::vector<std::int32_t> comm_to_output;
202 std::vector<std::int32_t> recv_sizes(in_ranks.size());
203 recv_sizes.reserve(1);
204 std::vector<std::int32_t> recv_offsets(in_ranks.size() + 1, 0);
205 {
206 // Build map from parent to neighborhood communicator ranks
207 std::vector<std::pair<std::int32_t, std::int32_t>> rank_to_neighbor;
208 rank_to_neighbor.reserve(in_ranks.size());
209 for (std::size_t i = 0; i < in_ranks.size(); i++)
210 rank_to_neighbor.push_back({in_ranks[i], i});
211 std::ranges::sort(rank_to_neighbor);
212
213 // Compute receive sizes
214 std::ranges::for_each(
215 dest_ranks,
216 [&rank_to_neighbor, &recv_sizes, block_size](auto rank)
217 {
218 if (rank >= 0)
219 {
220 auto it = std::ranges::lower_bound(rank_to_neighbor, rank,
221 std::ranges::less(),
222 [](auto e) { return e.first; });
223 assert(it != rank_to_neighbor.end() and it->first == rank);
224 recv_sizes[it->second] += block_size;
225 }
226 });
227
228 // Compute receiving offsets
229 std::partial_sum(recv_sizes.begin(), recv_sizes.end(),
230 std::next(recv_offsets.begin(), 1));
231
232 // Compute map from receiving values to position in recv_values
233 comm_to_output.resize(recv_offsets.back() / block_size);
234 std::vector<std::int32_t> recv_counter(recv_sizes.size(), 0);
235 for (std::size_t i = 0; i < dest_ranks.size(); ++i)
236 {
237 if (const std::int32_t rank = dest_ranks[i]; rank >= 0)
238 {
239 auto it = std::ranges::lower_bound(rank_to_neighbor, rank,
240 std::ranges::less(),
241 [](auto e) { return e.first; });
242 assert(it != rank_to_neighbor.end() and it->first == rank);
243 int insert_pos = recv_offsets[it->second] + recv_counter[it->second];
244 comm_to_output[insert_pos / block_size] = i * block_size;
245 recv_counter[it->second] += block_size;
246 }
247 }
248 }
249
250 std::vector<std::int32_t> send_sizes(out_ranks.size());
251 send_sizes.reserve(1);
252 {
253 // Compute map from parent MPI rank to neighbor rank for outgoing
254 // data. `out_ranks` is sorted, so rank_to_neighbor will be sorted
255 // too.
256 std::vector<std::pair<std::int32_t, std::int32_t>> rank_to_neighbor;
257 rank_to_neighbor.reserve(out_ranks.size());
258 for (std::size_t i = 0; i < out_ranks.size(); i++)
259 rank_to_neighbor.push_back({out_ranks[i], i});
260
261 // Compute send sizes. As `src_ranks` is sorted, we can move 'start'
262 // in search forward.
263 auto start = rank_to_neighbor.begin();
264 std::ranges::for_each(
265 src_ranks,
266 [&rank_to_neighbor, &send_sizes, block_size, &start](auto rank)
267 {
268 auto it = std::ranges::lower_bound(start, rank_to_neighbor.end(),
269 rank, std::ranges::less(),
270 [](auto e) { return e.first; });
271 assert(it != rank_to_neighbor.end() and it->first == rank);
272 send_sizes[it->second] += block_size;
273 start = it;
274 });
275 }
276
277 // Compute sending offsets
278 std::vector<std::int32_t> send_offsets(send_sizes.size() + 1, 0);
279 std::partial_sum(send_sizes.begin(), send_sizes.end(),
280 std::next(send_offsets.begin(), 1));
281
282 // Send values to dest ranks
283 std::vector<T> values(recv_offsets.back());
284 values.reserve(1);
285 MPI_Neighbor_alltoallv(send_values.data_handle(), send_sizes.data(),
286 send_offsets.data(), dolfinx::MPI::mpi_t<T>,
287 values.data(), recv_sizes.data(), recv_offsets.data(),
288 dolfinx::MPI::mpi_t<T>, reverse_comm);
289 MPI_Comm_free(&reverse_comm);
290
291 // Insert values received from neighborhood communicator in output
292 // span
293 std::ranges::fill(recv_values, T{0});
294 for (std::size_t i = 0; i < comm_to_output.size(); i++)
295 {
296 auto vals = std::next(recv_values.begin(), comm_to_output[i]);
297 auto vals_from = std::next(values.begin(), i * block_size);
298 std::copy_n(vals_from, block_size, vals);
299 }
300};
301
310template <dolfinx::MDSpanRank2 U, dolfinx::MDSpanRank2 V, dolfinx::scalar T>
311void interpolation_apply(U&& Pi, V&& data, std::span<T> coeffs, int bs)
312{
313 // Geometry (real) scalar type, taken from the interpolation operator Pi
314 // rather than scalar_value_t<T> so it is independent of the value scalar T.
315 using X = typename std::remove_cvref_t<U>::value_type;
316
317 // Compute coefficients = Pi * x (matrix-vector multiply)
318 if (bs == 1)
319 {
320 assert(data.extent(0) * data.extent(1) == Pi.extent(1));
321 for (std::size_t i = 0; i < Pi.extent(0); ++i)
322 {
323 coeffs[i] = 0.0;
324 for (std::size_t k = 0; k < data.extent(1); ++k)
325 for (std::size_t j = 0; j < data.extent(0); ++j)
326 {
327 coeffs[i]
328 += static_cast<X>(Pi(i, k * data.extent(0) + j)) * data(j, k);
329 }
330 }
331 }
332 else
333 {
334 assert(data.extent(0) == Pi.extent(1));
335 assert(static_cast<int>(data.extent(1)) == bs);
336 std::size_t cols = Pi.extent(1);
337 for (int k = 0; k < bs; ++k)
338 {
339 for (std::size_t i = 0; i < Pi.extent(0); ++i)
340 {
341 T acc = 0;
342 for (std::size_t j = 0; j < cols; ++j)
343 acc += static_cast<X>(Pi(i, j)) * data(j, k);
344 coeffs[bs * i + k] = acc;
345 }
346 }
347 }
348}
349
369template <dolfinx::scalar T, std::floating_point U>
370void interpolate_same_map(Function<T, U>& u1, mesh::CellRange auto&& cells1,
371 const Function<T, U>& u0,
372 mesh::CellRange auto&& cells0)
373{
374 auto V0 = u0.function_space();
375 assert(V0);
376 auto V1 = u1.function_space();
377 assert(V1);
378 auto mesh0 = V0->mesh();
379 assert(mesh0);
380
381 auto mesh1 = V1->mesh();
382 assert(mesh1);
383
384 auto element0 = V0->element();
385 assert(element0);
386 auto element1 = V1->element();
387 assert(element1);
388
389 assert(mesh0->topology()->dim());
390 const int tdim = mesh0->topology()->dim();
391 auto map = mesh0->topology()->index_map(tdim);
392 assert(map);
393 std::span<T> u1_array = u1.x()->array();
394 std::span<const T> u0_array = u0.x()->array();
395
396 std::span<const std::uint32_t> cell_info0;
397 std::span<const std::uint32_t> cell_info1;
398 if (element1->needs_dof_transformations()
399 or element0->needs_dof_transformations())
400 {
401 mesh0->topology_mutable()->create_cell_permutations();
402 cell_info0 = std::span(mesh0->topology()->get_cell_permutation_info());
403 mesh1->topology_mutable()->create_cell_permutations();
404 cell_info1 = std::span(mesh1->topology()->get_cell_permutation_info());
405 }
406
407 // Get dofmaps
408 auto dofmap1 = V1->dofmap();
409 auto dofmap0 = V0->dofmap();
410
411 // Get block sizes and dof transformation operators
412 const int bs1 = dofmap1->bs();
413 const int bs0 = dofmap0->bs();
414 auto apply_dof_transformation = element0->template dof_transformation_fn<T>(
416 auto apply_inverse_dof_transform
417 = element1->template dof_transformation_fn<T>(
419
420 // Create working array
421 std::vector<T> local0(element0->space_dimension());
422 std::vector<T> local1(element1->space_dimension());
423
424 // Create interpolation operator
425 auto [i_m, im_shape] = element1->create_interpolation_operator(*element0);
426
427 // Iterate over mesh and interpolate on each cell
428 using X = U; // geometry (real) type, independent of the value scalar T
429 if (cells0.size() != cells1.size())
430 throw std::invalid_argument("Length of cells0 and cells1 must match.");
431 for (auto cell0_it = cells0.begin(), cell1_it = cells1.begin();
432 cell0_it != cells0.end() and cell1_it != cells1.end();
433 ++cell0_it, ++cell1_it)
434 {
435 // Pack and transform cell dofs to reference ordering
436 std::span<const std::int32_t> dofs0 = dofmap0->cell_dofs(*cell0_it);
437 for (std::size_t i = 0; i < dofs0.size(); ++i)
438 for (int k = 0; k < bs0; ++k)
439 local0[bs0 * i + k] = u0_array[bs0 * dofs0[i] + k];
440
441 if (apply_dof_transformation)
442 apply_dof_transformation(local0, cell_info0, *cell0_it, 1);
443
444 // FIXME: Get compile-time ranges from Basix
445 // Apply interpolation operator
446 std::ranges::fill(local1, 0);
447 for (std::size_t i = 0; i < im_shape[0]; ++i)
448 for (std::size_t j = 0; j < im_shape[1]; ++j)
449 local1[i] += static_cast<X>(i_m[im_shape[1] * i + j]) * local0[j];
450
451 if (apply_inverse_dof_transform)
452 apply_inverse_dof_transform(local1, cell_info1, *cell1_it, 1);
453 std::span<const std::int32_t> dofs1 = dofmap1->cell_dofs(*cell1_it);
454 for (std::size_t i = 0; i < dofs1.size(); ++i)
455 for (int k = 0; k < bs1; ++k)
456 u1_array[bs1 * dofs1[i] + k] = local1[bs1 * i + k];
457 }
458}
459
473template <dolfinx::scalar T, std::floating_point U>
474void interpolate_nonmatching_maps(Function<T, U>& u1,
475 mesh::CellRange auto&& cells1,
476 const Function<T, U>& u0,
477 mesh::CellRange auto&& cells0)
478{
479 // Get mesh
480 auto V0 = u0.function_space();
481 assert(V0);
482 auto mesh0 = V0->mesh();
483 assert(mesh0);
484
485 // Mesh dims
486 const int tdim = mesh0->topology()->dim();
487 const int gdim = mesh0->geometry().dim();
488
489 // Get elements
490 auto V1 = u1.function_space();
491 assert(V1);
492 auto mesh1 = V1->mesh();
493 assert(mesh1);
494 auto element0 = V0->element();
495 assert(element0);
496 auto element1 = V1->element();
497 assert(element1);
498
499 std::span<const std::uint32_t> cell_info0;
500 std::span<const std::uint32_t> cell_info1;
501 if (element1->needs_dof_transformations()
502 or element0->needs_dof_transformations())
503 {
504 mesh0->topology_mutable()->create_cell_permutations();
505 cell_info0 = std::span(mesh0->topology()->get_cell_permutation_info());
506 mesh1->topology_mutable()->create_cell_permutations();
507 cell_info1 = std::span(mesh1->topology()->get_cell_permutation_info());
508 }
509
510 // Get symmetry
511 const bool symmetric0 = V0->symmetric();
512 const bool symmetric1 = V1->symmetric();
513
514 // Get dofmaps
515 auto dofmap0 = V0->dofmap();
516 auto dofmap1 = V1->dofmap();
517
518 const auto [X, Xshape] = element1->interpolation_points();
519
520 // Get block sizes and dof transformation operators
521 const int bs0 = element0->block_size();
522 const int bs1 = element1->block_size();
523 auto apply_dof_transformation0 = element0->template dof_transformation_fn<U>(
525 auto apply_inv_dof_transform1 = element1->template dof_transformation_fn<T>(
527
528 // Get sizes of elements
529 const std::size_t dim0 = element0->space_dimension() / bs0;
530 const std::size_t value_size_ref0 = element0->reference_value_size();
531 // basis0 holds values pushed forward to the physical cell, one block
532 // at a time, so it is sized with the physical (base) value size.
533 const std::size_t value_size0 = V0->element()->physical_base_value_size();
534
535 const CoordinateElement<U>& cmap = mesh0->geometry().cmaps().front();
536 auto x_dofmap = mesh0->geometry().dofmaps().front();
537 std::span<const U> x_g = mesh0->geometry().x();
538
539 // (0) is derivative index, (1) is the point index, (2) is the basis
540 // function index and (3) is the basis function component.
541
542 // Evaluate coordinate map basis at reference interpolation points
543 const std::array<std::size_t, 4> phi_shape
544 = cmap.tabulate_shape(1, Xshape[0]);
545 std::vector<U> phi_b(
546 std::reduce(phi_shape.begin(), phi_shape.end(), 1, std::multiplies{}));
547 md::mdspan<const U, md::extents<std::size_t, md::dynamic_extent,
548 md::dynamic_extent, md::dynamic_extent, 1>>
549 phi(phi_b.data(), phi_shape);
550 cmap.tabulate(1, X, Xshape, phi_b);
551
552 // Evaluate v basis functions at reference interpolation points
553 const auto [_basis_derivatives_reference0, b0shape]
554 = element0->tabulate(X, Xshape, 0);
555 md::mdspan<const U, std::extents<std::size_t, 1, md::dynamic_extent,
556 md::dynamic_extent, md::dynamic_extent>>
557 basis_derivatives_reference0(_basis_derivatives_reference0.data(),
558 b0shape);
559
560 // Create working arrays
561 std::vector<T> local1(element1->space_dimension());
562 std::vector<T> coeffs0(element0->space_dimension());
563
564 std::vector<U> basis0_b(Xshape[0] * dim0 * value_size0);
565 md::mdspan<U, std::dextents<std::size_t, 3>> basis0(
566 basis0_b.data(), Xshape[0], dim0, value_size0);
567
568 std::vector<U> basis_reference0_b(Xshape[0] * dim0 * value_size_ref0);
569 md::mdspan<U, std::dextents<std::size_t, 3>> basis_reference0(
570 basis_reference0_b.data(), Xshape[0], dim0, value_size_ref0);
571
572 // Size of (rank-2) tensor for symmetric elements
573 std::size_t matrix_size = 0;
574 if (symmetric0 or symmetric1)
575 {
576 assert(element0->value_shape().size() == 2);
577 assert(!symmetric0 or element0->physical_base_value_size() == 1);
578 assert(!symmetric1 or element1->physical_base_value_size() == 1);
579 matrix_size = element1->value_shape().front();
580 }
581
582 // values0 holds physical values in the block layout of element 1;
583 // mapped_values0 holds them pulled back to the reference
584 // cell of element 1, where each of its bs1 blocks has
585 // reference_value_size components.
586 const std::size_t value_size1
587 = element1->physical_base_value_size() * static_cast<std::size_t>(bs1);
588 const std::size_t value_size_ref1
589 = element1->reference_value_size() * static_cast<std::size_t>(bs1);
590 std::vector<T> values0_b(Xshape[0] * 1 * value_size1);
591 md::mdspan<
592 T, md::extents<std::size_t, md::dynamic_extent, 1, md::dynamic_extent>>
593 values0(values0_b.data(), Xshape[0], 1, value_size1);
594
595 std::vector<T> mapped_values_b(Xshape[0] * 1 * value_size_ref1);
596 md::mdspan<
597 T, md::extents<std::size_t, md::dynamic_extent, 1, md::dynamic_extent>>
598 mapped_values0(mapped_values_b.data(), Xshape[0], 1, value_size_ref1);
599
600 const std::size_t num_dofs_g = cmap.dim();
601 std::vector<U> coord_dofs_b(num_dofs_g * gdim);
602 md::mdspan<U, std::dextents<std::size_t, 2>> coord_dofs(coord_dofs_b.data(),
603 num_dofs_g, gdim);
604
605 std::vector<U> J_b(Xshape[0] * gdim * tdim);
606 md::mdspan<U, std::dextents<std::size_t, 3>> J(J_b.data(), Xshape[0], gdim,
607 tdim);
608 std::vector<U> K_b(Xshape[0] * tdim * gdim);
609 md::mdspan<U, std::dextents<std::size_t, 3>> K(K_b.data(), Xshape[0], tdim,
610 gdim);
611 std::vector<U> detJ(Xshape[0]);
612 std::vector<U> det_scratch(2 * gdim * tdim);
613
614 // Get interpolation operator
615 const auto [_Pi_1, pi_shape] = element1->interpolation_operator();
616 impl::mdspan_t<const U, 2> Pi_1(_Pi_1.data(), pi_shape);
617
618 using u_t = md::mdspan<U, std::dextents<std::size_t, 2>>;
619 using U_t = md::mdspan<const U, std::dextents<std::size_t, 2>>;
620 using J_t = md::mdspan<const U, std::dextents<std::size_t, 2>>;
621 using K_t = md::mdspan<const U, std::dextents<std::size_t, 2>>;
622 auto push_forward_fn0
623 = element0->basix_element().template map_fn<u_t, U_t, J_t, K_t>();
624
625 using v_t = md::mdspan<const T, std::dextents<std::size_t, 2>>;
626 using V_t = decltype(md::submdspan(mapped_values0, 0, md::full_extent,
627 md::full_extent));
628 auto pull_back_fn1
629 = element1->basix_element().template map_fn<V_t, v_t, K_t, J_t>();
630
631 // Iterate over mesh and interpolate on each cell
632 std::span<const T> array0 = u0.x()->array();
633 std::span<T> array1 = u1.x()->array();
634 if (cells0.size() != cells1.size())
635 throw std::invalid_argument("Length of cells0 and cells1 must match.");
636 for (auto cell0_it = cells0.begin(), cell1_it = cells1.begin();
637 cell0_it != cells0.end() and cell1_it != cells1.end();
638 ++cell0_it, ++cell1_it)
639 {
640 // Get cell geometry (coordinate dofs)
641 auto x_dofs = md::submdspan(x_dofmap, *cell0_it, md::full_extent);
642 for (std::size_t i = 0; i < num_dofs_g; ++i)
643 {
644 const int pos = 3 * x_dofs[i];
645 for (int j = 0; j < gdim; ++j)
646 coord_dofs(i, j) = x_g[pos + j];
647 }
648
649 // Compute Jacobians and reference points for current cell
650 std::ranges::fill(J_b, 0);
651 for (std::size_t p = 0; p < Xshape[0]; ++p)
652 {
653 auto dphi
654 = md::submdspan(phi, std::pair(1, tdim + 1), p, md::full_extent, 0);
655 auto _J = md::submdspan(J, p, md::full_extent, md::full_extent);
656 cmap.compute_jacobian(dphi, coord_dofs, _J);
657 auto _K = md::submdspan(K, p, md::full_extent, md::full_extent);
658 cmap.compute_jacobian_inverse(_J, _K);
659 detJ[p] = cmap.compute_jacobian_determinant(_J, det_scratch);
660 }
661
662 // Copy evaluated basis on reference, apply DOF transformations, and
663 // push forward to physical element
664 for (std::size_t k0 = 0; k0 < basis_reference0.extent(0); ++k0)
665 for (std::size_t k1 = 0; k1 < basis_reference0.extent(1); ++k1)
666 for (std::size_t k2 = 0; k2 < basis_reference0.extent(2); ++k2)
667 {
668 basis_reference0(k0, k1, k2)
669 = basis_derivatives_reference0(0, k0, k1, k2);
670 }
671
672 if (apply_dof_transformation0)
673 {
674 for (std::size_t p = 0; p < Xshape[0]; ++p)
675 {
676 apply_dof_transformation0(
677 std::span(basis_reference0_b.data() + p * dim0 * value_size_ref0,
678 dim0 * value_size_ref0),
679 cell_info0, *cell0_it, value_size_ref0);
680 }
681 }
682
683 for (std::size_t i = 0; i < basis0.extent(0); ++i)
684 {
685 auto _u = md::submdspan(basis0, i, md::full_extent, md::full_extent);
686 auto _U = md::submdspan(basis_reference0, i, md::full_extent,
687 md::full_extent);
688 auto _K = md::submdspan(K, i, md::full_extent, md::full_extent);
689 auto _J = md::submdspan(J, i, md::full_extent, md::full_extent);
690 push_forward_fn0(_u, _U, _J, detJ[i], _K);
691 }
692
693 // Copy expansion coefficients for v into local array
694 const int dof_bs0 = dofmap0->bs();
695 std::span<const std::int32_t> dofs0 = dofmap0->cell_dofs(*cell0_it);
696 for (std::size_t i = 0; i < dofs0.size(); ++i)
697 for (int k = 0; k < dof_bs0; ++k)
698 coeffs0[dof_bs0 * i + k] = array0[dof_bs0 * dofs0[i] + k];
699
700 // Evaluate v at the interpolation points (physical space values).
701 if (symmetric0 == symmetric1)
702 {
703 // Same block layout: both or neither symmetric
704 for (std::size_t p = 0; p < Xshape[0]; ++p)
705 {
706 for (int k = 0; k < bs0; ++k)
707 {
708 for (std::size_t j = 0; j < value_size0; ++j)
709 {
710 T acc = 0;
711 for (std::size_t i = 0; i < dim0; ++i)
712 acc += coeffs0[bs0 * i + k] * static_cast<T>(basis0(p, i, j));
713 values0(p, 0, j * bs0 + k) = acc;
714 }
715 }
716 }
717 }
718 else if (symmetric0 and !symmetric1)
719 {
720 // Expand the stored components of v to the full matrix
721 for (std::size_t p = 0; p < Xshape[0]; ++p)
722 {
723 int row = 0;
724 int rowstart = 0;
725 for (int k = 0; k < bs0; ++k)
726 {
727 if (k - rowstart > row)
728 {
729 ++row;
730 rowstart = k;
731 }
732 T acc = 0;
733 for (std::size_t i = 0; i < dim0; ++i)
734 acc += coeffs0[bs0 * i + k] * static_cast<T>(basis0(p, i, 0));
735 values0(p, 0, row * matrix_size + k - rowstart) = acc;
736 values0(p, 0, row + matrix_size * (k - rowstart)) = acc;
737 }
738 }
739 }
740 else // if (!symmetric0 and symmetric1)
741 {
742 // Evaluate only the components of v stored by u1. Physical
743 // component c of v is value component c / bs0 of block c % bs0.
744 for (std::size_t p = 0; p < Xshape[0]; ++p)
745 {
746 int row = 0;
747 int rowstart = 0;
748 for (int k = 0; k < bs1; ++k)
749 {
750 if (k - rowstart > row)
751 {
752 ++row;
753 rowstart = k;
754 }
755 const int c = row * matrix_size + k - rowstart;
756 T acc = 0;
757 for (std::size_t i = 0; i < dim0; ++i)
758 {
759 acc += coeffs0[bs0 * i + c % bs0]
760 * static_cast<T>(basis0(p, i, c / bs0));
761 }
762 values0(p, 0, k) = acc;
763 }
764 }
765 }
766
767 // Pull back the physical values to the u reference
768 for (std::size_t i = 0; i < values0.extent(0); ++i)
769 {
770 auto _u = md::submdspan(values0, i, md::full_extent, md::full_extent);
771 auto _U
772 = md::submdspan(mapped_values0, i, md::full_extent, md::full_extent);
773 auto _K = md::submdspan(K, i, md::full_extent, md::full_extent);
774 auto _J = md::submdspan(J, i, md::full_extent, md::full_extent);
775 pull_back_fn1(_U, _u, _K, 1.0 / detJ[i], _J);
776 }
777
778 auto values
779 = md::submdspan(mapped_values0, md::full_extent, 0, md::full_extent);
780 interpolation_apply(Pi_1, values, std::span(local1), bs1);
781 if (apply_inv_dof_transform1)
782 apply_inv_dof_transform1(local1, cell_info1, *cell1_it, 1);
783
784 // Copy local coefficients to the correct position in u dof array
785 const int dof_bs1 = dofmap1->bs();
786 std::span<const std::int32_t> dofs1 = dofmap1->cell_dofs(*cell1_it);
787 for (std::size_t i = 0; i < dofs1.size(); ++i)
788 for (int k = 0; k < dof_bs1; ++k)
789 array1[dof_bs1 * dofs1[i] + k] = local1[dof_bs1 * i + k];
790 }
791}
792
804template <dolfinx::scalar T, std::floating_point U>
805void point_evaluation(const FiniteElement<U>& element, bool symmetric,
806 const DofMap& dofmap, mesh::CellRange auto&& cells,
807 std::span<const std::uint32_t> cell_info,
808 std::span<const T> f, std::array<std::size_t, 2> fshape,
809 std::span<T> coeffs)
810{
811 // Point evaluation element *and* the geometric map is the identity,
812 // e.g. not Piola mapped
813
814 const int element_bs = element.block_size();
815 const int num_scalar_dofs = element.space_dimension() / element_bs;
816 const int dofmap_bs = dofmap.bs();
817
818 auto apply_inv_transpose_dof_transformation
819 = element.template dof_transformation_fn<T>(
821 std::vector<T> coeffs_b(num_scalar_dofs);
822
823 // Skip the div/mod below when block sizes match (the common case)
824 const bool same_bs = (dofmap_bs == element_bs);
825
826 if (symmetric)
827 {
828 std::size_t matrix_size = 0;
829 while (matrix_size * matrix_size < fshape[0])
830 ++matrix_size;
831 assert(matrix_size * matrix_size == fshape[0]);
832
833 // Loop over cells
834 for (auto cell_it = cells.begin(); cell_it != cells.end(); ++cell_it)
835 {
836 // The entries of a symmetric matrix are numbered (for an
837 // example 4x4 element):
838 // 0 * * *
839 // 1 2 * *
840 // 3 4 5 *
841 // 6 7 8 9
842 // The loop extracts these elements. In this loop, row is the
843 // row of this matrix, and (k - rowstart) is the column
844 std::size_t row = 0;
845 std::size_t rowstart = 0;
846 std::span<const std::int32_t> dofs = dofmap.cell_dofs(*cell_it);
847 std::size_t offset = std::ranges::distance(cells.begin(), cell_it);
848 for (int k = 0; k < element_bs; ++k)
849 {
850 if (k - rowstart > row)
851 {
852 ++row;
853 rowstart = k;
854 }
855
856 // num_scalar_dofs is the number of interpolation points per
857 // cell in this case (interpolation matrix is identity)
858 std::copy_n(
859 std::next(f.begin(), (row * matrix_size + k - rowstart) * fshape[1]
860 + offset * num_scalar_dofs),
861 num_scalar_dofs, coeffs_b.data());
862 if (apply_inv_transpose_dof_transformation)
863 {
864 apply_inv_transpose_dof_transformation(coeffs_b, cell_info, *cell_it,
865 1);
866 }
867 if (same_bs)
868 {
869 for (int i = 0; i < num_scalar_dofs; ++i)
870 coeffs[dofmap_bs * dofs[i] + k] = coeffs_b[i];
871 }
872 else
873 {
874 for (int i = 0; i < num_scalar_dofs; ++i)
875 {
876 std::div_t pos = std::div(i * element_bs + k, dofmap_bs);
877 coeffs[dofmap_bs * dofs[pos.quot] + pos.rem] = coeffs_b[i];
878 }
879 }
880 }
881 }
882 }
883 else
884 {
885 // Loop over cells
886 for (auto cell_it = cells.begin(); cell_it != cells.end(); ++cell_it)
887 {
888 std::size_t offset = std::ranges::distance(cells.begin(), cell_it);
889 std::span<const std::int32_t> dofs = dofmap.cell_dofs(*cell_it);
890 for (int k = 0; k < element_bs; ++k)
891 {
892 // num_scalar_dofs is the number of interpolation points per
893 // cell in this case (interpolation matrix is identity)
894 std::copy_n(
895 std::next(f.begin(), k * fshape[1] + offset * num_scalar_dofs),
896 num_scalar_dofs, coeffs_b.data());
897 if (apply_inv_transpose_dof_transformation)
898 {
899 apply_inv_transpose_dof_transformation(coeffs_b, cell_info, *cell_it,
900 1);
901 }
902 if (same_bs)
903 {
904 for (int i = 0; i < num_scalar_dofs; ++i)
905 coeffs[dofmap_bs * dofs[i] + k] = coeffs_b[i];
906 }
907 else
908 {
909 for (int i = 0; i < num_scalar_dofs; ++i)
910 {
911 std::div_t pos = std::div(i * element_bs + k, dofmap_bs);
912 coeffs[dofmap_bs * dofs[pos.quot] + pos.rem] = coeffs_b[i];
913 }
914 }
915 }
916 }
917 }
918}
919
931template <dolfinx::scalar T, std::floating_point U>
932void identity_mapped_evaluation(const FiniteElement<U>& element, bool symmetric,
933 const DofMap& dofmap,
934 mesh::CellRange auto&& cells,
935 std::span<const std::uint32_t> cell_info,
936 std::span<const T> f,
937 std::array<std::size_t, 2> fshape,
938 std::span<T> coeffs)
939{
940 // Not a point evaluation, but the geometric map is the identity,
941 // e.g. not Piola mapped
942
943 if (symmetric)
944 {
945 throw std::invalid_argument(
946 "Interpolation into this element not supported.");
947 }
948
949 const int element_bs = element.block_size();
950 const int num_scalar_dofs = element.space_dimension() / element_bs;
951 const int dofmap_bs = dofmap.bs();
952
953 // Identity map, so the physical and reference value sizes coincide.
954 const int element_vs = element.reference_value_size();
955 assert(element_vs == element.physical_base_value_size());
956 if (element_vs > 1 and element_bs > 1)
957 throw std::runtime_error("Interpolation into this element not supported.");
958
959 // Get interpolation operator
960 const auto [_Pi, pi_shape] = element.interpolation_operator();
961 md::mdspan<const U, std::dextents<std::size_t, 2>> Pi(_Pi.data(), pi_shape);
962 const std::size_t num_interp_points = Pi.extent(1);
963 assert(static_cast<int>(Pi.extent(0)) == num_scalar_dofs);
964
965 auto apply_inv_transpose_dof_transformation
966 = element.template dof_transformation_fn<T>(
968
969 // Skip the div/mod below when block sizes match (the common case)
970 const bool same_bs = (dofmap_bs == element_bs);
971
972 // Loop over cells
973 std::vector<T> ref_data_b(num_interp_points);
974 md::mdspan<T, md::extents<std::size_t, md::dynamic_extent, 1>> ref_data(
975 ref_data_b.data(), num_interp_points, 1);
976 std::vector<T> coeffs_b(num_scalar_dofs);
977 for (auto cell_it = cells.begin(); cell_it != cells.end(); ++cell_it)
978 {
979 std::size_t offset = std::ranges::distance(cells.begin(), cell_it);
980 std::span<const std::int32_t> dofs = dofmap.cell_dofs(*cell_it);
981 for (int k = 0; k < element_bs; ++k)
982 {
983 for (int i = 0; i < element_vs; ++i)
984 {
985 std::copy_n(
986 std::next(f.begin(), (i + k) * fshape[1]
987 + offset * num_interp_points / element_vs),
988 num_interp_points / element_vs,
989 std::next(ref_data_b.begin(), i * num_interp_points / element_vs));
990 }
991
992 impl::interpolation_apply(Pi, ref_data, std::span(coeffs_b), 1);
993 if (apply_inv_transpose_dof_transformation)
994 {
995 apply_inv_transpose_dof_transformation(coeffs_b, cell_info, *cell_it,
996 1);
997 }
998 if (same_bs)
999 {
1000 for (int i = 0; i < num_scalar_dofs; ++i)
1001 coeffs[dofmap_bs * dofs[i] + k] = coeffs_b[i];
1002 }
1003 else
1004 {
1005 for (int i = 0; i < num_scalar_dofs; ++i)
1006 {
1007 std::div_t pos = std::div(i * element_bs + k, dofmap_bs);
1008 coeffs[dofmap_bs * dofs[pos.quot] + pos.rem] = coeffs_b[i];
1009 }
1010 }
1011 }
1012 }
1013}
1014
1027template <dolfinx::scalar T, std::floating_point U>
1028void piola_mapped_evaluation(const FiniteElement<U>& element, bool symmetric,
1029 const DofMap& dofmap, mesh::CellRange auto&& cells,
1030 std::span<const std::uint32_t> cell_info,
1031 std::span<const T> f,
1032 std::array<std::size_t, 2> fshape,
1033 const mesh::Mesh<U>& mesh, std::span<T> coeffs)
1034{
1035 if (symmetric)
1036 {
1037 throw std::invalid_argument(
1038 "Interpolation into this element not supported.");
1039 }
1040
1041 const int gdim = mesh.geometry().dim();
1042 assert(mesh.topology());
1043 const int tdim = mesh.topology()->dim();
1044
1045 const int element_bs = element.block_size();
1046 const int num_scalar_dofs = element.space_dimension() / element_bs;
1047 // f holds physical values, one block at a time.
1048 const int value_size = element.physical_base_value_size();
1049 const int dofmap_bs = dofmap.bs();
1050
1051 // Skip the div/mod below when block sizes match (the common case)
1052 const bool same_bs = (dofmap_bs == element_bs);
1053
1054 md::mdspan<const T, md::dextents<std::size_t, 2>> _f(f.data(), fshape);
1055
1056 // Get the interpolation points on the reference cells
1057 const auto [X, Xshape] = element.interpolation_points();
1058 if (X.empty())
1059 {
1060 throw std::invalid_argument(
1061 "Interpolation into this space is not yet supported.");
1062 }
1063
1064 if (_f.extent(1) != cells.size() * Xshape[0])
1065 throw std::invalid_argument("Interpolation data has the wrong shape.");
1066
1067 // Get coordinate map
1068 const CoordinateElement<U>& cmap = mesh.geometry().cmaps().front();
1069
1070 // Get geometry data
1071 auto x_dofmap = mesh.geometry().dofmaps().front();
1072 const int num_dofs_g = cmap.dim();
1073 std::span<const U> x_g = mesh.geometry().x();
1074
1075 // Create data structures for Jacobian info
1076 std::vector<U> J_b(Xshape[0] * gdim * tdim);
1077 md::mdspan<U, std::dextents<std::size_t, 3>> J(J_b.data(), Xshape[0], gdim,
1078 tdim);
1079 std::vector<U> K_b(Xshape[0] * tdim * gdim);
1080 md::mdspan<U, std::dextents<std::size_t, 3>> K(K_b.data(), Xshape[0], tdim,
1081 gdim);
1082 std::vector<U> detJ(Xshape[0]);
1083 std::vector<U> det_scratch(2 * gdim * tdim);
1084
1085 std::vector<U> coord_dofs_b(num_dofs_g * gdim);
1086 md::mdspan<U, std::dextents<std::size_t, 2>> coord_dofs(coord_dofs_b.data(),
1087 num_dofs_g, gdim);
1088 const std::size_t value_size_ref = element.reference_value_size();
1089 std::vector<T> ref_data_b(Xshape[0] * 1 * value_size_ref);
1090 md::mdspan<
1091 T, md::extents<std::size_t, md::dynamic_extent, 1, md::dynamic_extent>>
1092 ref_data(ref_data_b.data(), Xshape[0], 1, value_size_ref);
1093
1094 std::vector<T> _vals_b(Xshape[0] * 1 * value_size);
1095 md::mdspan<
1096 T, md::extents<std::size_t, md::dynamic_extent, 1, md::dynamic_extent>>
1097 _vals(_vals_b.data(), Xshape[0], 1, value_size);
1098
1099 // Tabulate 1st derivative of shape functions at interpolation
1100 // coords
1101 std::array<std::size_t, 4> phi_shape = cmap.tabulate_shape(1, Xshape[0]);
1102 std::vector<U> phi_b(
1103 std::reduce(phi_shape.begin(), phi_shape.end(), 1, std::multiplies{}));
1104 md::mdspan<const U, md::extents<std::size_t, md::dynamic_extent,
1105 md::dynamic_extent, md::dynamic_extent, 1>>
1106 phi(phi_b.data(), phi_shape);
1107 cmap.tabulate(1, X, Xshape, phi_b);
1108 auto dphi = md::submdspan(phi, std::pair(1, tdim + 1), md::full_extent,
1109 md::full_extent, 0);
1110
1111 std::function<void(std::span<T>, std::span<const std::uint32_t>, std::int32_t,
1112 int)>
1113 apply_inv_trans_dof_transformation
1114 = element.template dof_transformation_fn<T>(
1116
1117 // Get interpolation operator
1118 const auto [_Pi, pi_shape] = element.interpolation_operator();
1119 md::mdspan<const U, std::dextents<std::size_t, 2>> Pi(_Pi.data(), pi_shape);
1120
1121 using u_t = md::mdspan<const T, md::dextents<std::size_t, 2>>;
1122 using U_t
1123 = decltype(md::submdspan(ref_data, 0, md::full_extent, md::full_extent));
1124 using J_t = md::mdspan<const U, md::dextents<std::size_t, 2>>;
1125 using K_t = md::mdspan<const U, md::dextents<std::size_t, 2>>;
1126 auto pull_back_fn
1127 = element.basix_element().template map_fn<U_t, u_t, J_t, K_t>();
1128
1129 std::vector<T> coeffs_b(num_scalar_dofs);
1130 for (auto cell_it = cells.begin(); cell_it != cells.end(); ++cell_it)
1131 {
1132 auto x_dofs = md::submdspan(x_dofmap, *cell_it, md::full_extent);
1133 for (int i = 0; i < num_dofs_g; ++i)
1134 {
1135 const int pos = 3 * x_dofs[i];
1136 for (int j = 0; j < gdim; ++j)
1137 coord_dofs(i, j) = x_g[pos + j];
1138 }
1139
1140 // Compute J, detJ and K
1141 std::ranges::fill(J_b, 0);
1142 for (std::size_t p = 0; p < Xshape[0]; ++p)
1143 {
1144 auto _dphi = md::submdspan(dphi, md::full_extent, p, md::full_extent);
1145 auto _J = md::submdspan(J, p, md::full_extent, md::full_extent);
1146 cmap.compute_jacobian(_dphi, coord_dofs, _J);
1147 auto _K = md::submdspan(K, p, md::full_extent, md::full_extent);
1148 cmap.compute_jacobian_inverse(_J, _K);
1149 detJ[p] = cmap.compute_jacobian_determinant(_J, det_scratch);
1150 }
1151
1152 const std::size_t offset = std::ranges::distance(cells.begin(), cell_it);
1153 std::span<const std::int32_t> dofs = dofmap.cell_dofs(*cell_it);
1154 for (int k = 0; k < element_bs; ++k)
1155 {
1156 // Extract computed expression values for element block k
1157 for (int m = 0; m < value_size; ++m)
1158 {
1159 for (std::size_t k0 = 0; k0 < Xshape[0]; ++k0)
1160 {
1161 _vals(k0, 0, m)
1162 = f[fshape[1] * (k * value_size + m) + offset * Xshape[0] + k0];
1163 }
1164 }
1165
1166 // Get element degrees of freedom for block
1167 for (std::size_t i = 0; i < Xshape[0]; ++i)
1168 {
1169 auto _u = md::submdspan(_vals, i, md::full_extent, md::full_extent);
1170 auto _U = md::submdspan(ref_data, i, md::full_extent, md::full_extent);
1171 auto _K = md::submdspan(K, i, md::full_extent, md::full_extent);
1172 auto _J = md::submdspan(J, i, md::full_extent, md::full_extent);
1173 pull_back_fn(_U, _u, _K, 1.0 / detJ[i], _J);
1174 }
1175
1176 auto ref = md::submdspan(ref_data, md::full_extent, 0, md::full_extent);
1177 impl::interpolation_apply(Pi, ref, std::span(coeffs_b), element_bs);
1178 if (apply_inv_trans_dof_transformation)
1179 apply_inv_trans_dof_transformation(coeffs_b, cell_info, *cell_it, 1);
1180
1181 // Copy interpolation dofs into coefficient vector
1182 assert(coeffs_b.size() == static_cast<std::size_t>(num_scalar_dofs));
1183 if (same_bs)
1184 {
1185 for (int i = 0; i < num_scalar_dofs; ++i)
1186 coeffs[dofmap_bs * dofs[i] + k] = coeffs_b[i];
1187 }
1188 else
1189 {
1190 for (int i = 0; i < num_scalar_dofs; ++i)
1191 {
1192 std::div_t pos = std::div(i * element_bs + k, dofmap_bs);
1193 coeffs[dofmap_bs * dofs[pos.quot] + pos.rem] = coeffs_b[i];
1194 }
1195 }
1196 }
1197 }
1198}
1199
1200//----------------------------------------------------------------------------
1201} // namespace impl
1202
1228template <std::floating_point T>
1230 const mesh::Geometry<T>& geometry0, const FiniteElement<T>& element0,
1231 const mesh::Mesh<T>& mesh1, mesh::CellRange auto&& cells, T padding,
1232 bool allow_extrapolation = true)
1233{
1234 // Collect all the points at which values are needed to define the
1235 // interpolating function
1236 std::vector<T> coords = interpolation_coords(element0, geometry0, cells);
1237
1238 // Transpose interpolation coords
1239 std::vector<T> x(coords.size());
1240 std::size_t num_points = coords.size() / 3;
1241 for (std::size_t i = 0; i < num_points; ++i)
1242 for (std::size_t j = 0; j < 3; ++j)
1243 x[3 * i + j] = coords[i + j * num_points];
1244
1245 // Determine ownership of each point
1246 return geometry::determine_point_ownership<T>(mesh1, x, padding, std::nullopt,
1247 allow_extrapolation);
1248}
1249
1250template <dolfinx::scalar T, std::floating_point U>
1251void interpolate(Function<T, U>& u, std::span<const T> f,
1252 std::array<std::size_t, 2> fshape,
1253 mesh::CellRange auto&& cells)
1254{
1255 // TODO: Index for mixed-topology, zero for now
1256 const int index = 0;
1257 auto element = u.function_space()->elements(index);
1258 assert(element);
1259 const int element_bs = element->block_size();
1260 if (int num_sub = element->num_sub_elements();
1261 num_sub > 0 and num_sub != element_bs)
1262 {
1263 throw std::invalid_argument("Cannot directly interpolate a mixed space. "
1264 "Interpolate into subspaces.");
1265 }
1266
1267 // Get mesh
1268 assert(u.function_space());
1269 auto mesh = u.function_space()->mesh();
1270 assert(mesh);
1271
1272 if (fshape[0]
1273 != (std::size_t)u.function_space()->elements(index)->value_size()
1274 or f.size() != fshape[0] * fshape[1])
1275 {
1276 throw std::invalid_argument("Interpolation data has the wrong shape/size.");
1277 }
1278
1279 logger().debug("Check for dof transformation");
1280 std::span<const std::uint32_t> cell_info;
1281 if (element->needs_dof_transformations())
1282 {
1283 mesh->topology_mutable()->create_cell_permutations();
1284 cell_info = std::span(mesh->topology()->get_cell_permutation_info());
1285 }
1286
1287 // Get dofmap
1288 logger().debug("Interpolate: get dofmap");
1289 const auto dofmap = u.function_space()->dofmaps().at(index);
1290 assert(dofmap);
1291
1292 // Result will be stored to coeffs
1293 std::span<T> coeffs = u.x()->array();
1294
1295 if (bool symmetric = u.function_space()->symmetric();
1296 element->map_ident() and element->interpolation_ident())
1297 {
1298 // This assumes that any element with an identity interpolation
1299 // matrix is a point evaluation
1300 logger().debug("Interpolate: point evaluation");
1301 impl::point_evaluation(*element, symmetric, *dofmap, cells, cell_info, f,
1302 fshape, coeffs);
1303 }
1304 else if (element->map_ident())
1305 {
1306 logger().debug("Interpolate: identity-mapped evaluation");
1307 impl::identity_mapped_evaluation(*element, symmetric, *dofmap, cells,
1308 cell_info, f, fshape, coeffs);
1309 }
1310 else
1311 {
1312 logger().debug("Interpolate: Piola-mapped evaluation");
1313 impl::piola_mapped_evaluation(*element, symmetric, *dofmap, cells,
1314 cell_info, f, fshape, *mesh, coeffs);
1315 }
1316}
1317
1334template <dolfinx::scalar T, std::floating_point U>
1336 mesh::CellRange auto&& cells, double tol, int maxit,
1337 const geometry::PointOwnershipData<U>& interpolation_data)
1338{
1339 auto mesh1 = u1.function_space()->mesh();
1340 assert(mesh1);
1341 MPI_Comm comm = mesh1->comm();
1342 {
1343 assert(u0.function_space());
1344 auto mesh0 = u0.function_space()->mesh();
1345 assert(mesh0);
1346 int result;
1347 MPI_Comm_compare(comm, mesh0->comm(), &result);
1348 if (result == MPI_UNEQUAL)
1349 {
1350 throw std::invalid_argument("Interpolation on different meshes is only "
1351 "supported on the same communicator.");
1352 }
1353 }
1354
1355 assert(mesh1->topology());
1356 auto cell_map = mesh1->topology()->index_map(mesh1->topology()->dim());
1357 assert(cell_map);
1358 auto element1 = u1.function_space()->element();
1359 assert(element1);
1360 const std::size_t value_size = element1->value_size();
1361
1362 const std::vector<int>& dest_ranks = interpolation_data.src_owner;
1363 const std::vector<int>& src_ranks = interpolation_data.dest_owners;
1364 const std::vector<U>& recv_points = interpolation_data.dest_points;
1365 const std::vector<std::int32_t>& evaluation_cells
1366 = interpolation_data.dest_cells;
1367
1368 // Evaluate the interpolating function where possible
1369 std::vector<T> send_values(recv_points.size() / 3 * value_size);
1370 u0.eval(recv_points, {recv_points.size() / 3, (std::size_t)3},
1371 evaluation_cells, send_values, {recv_points.size() / 3, value_size},
1372 tol, maxit);
1373
1374 // Send values back to owning process
1375 std::vector<T> values_b(dest_ranks.size() * value_size);
1376 md::mdspan<const T, md::dextents<std::size_t, 2>> _send_values(
1377 send_values.data(), src_ranks.size(), value_size);
1378 impl::scatter_values(comm, src_ranks, dest_ranks, _send_values,
1379 std::span(values_b));
1380
1381 // Transpose received data
1382 md::mdspan<const T, md::dextents<std::size_t, 2>> values(
1383 values_b.data(), dest_ranks.size(), value_size);
1384 std::vector<T> valuesT_b(value_size * dest_ranks.size());
1385 md::mdspan<T, md::dextents<std::size_t, 2>> valuesT(
1386 valuesT_b.data(), value_size, dest_ranks.size());
1387 for (std::size_t i = 0; i < values.extent(0); ++i)
1388 for (std::size_t j = 0; j < values.extent(1); ++j)
1389 valuesT(j, i) = values(i, j);
1390
1391 // Call local interpolation operator
1392 fem::interpolate<T>(u1, valuesT_b, {valuesT.extent(0), valuesT.extent(1)},
1393 cells);
1394}
1395
1420template <dolfinx::scalar T, std::floating_point U>
1422 const Function<T, U>& u0, mesh::CellRange auto&& cells0)
1423{
1424 if (cells0.size() != cells1.size())
1425 throw std::invalid_argument("Length of cell lists do not match.");
1426
1427 auto V1 = u1.function_space();
1428 assert(V1);
1429 auto V0 = u0.function_space();
1430 assert(V0);
1431
1432 // Get elements and check value shape
1433 auto e0 = V0->element();
1434 assert(e0);
1435 auto e1 = V1->element();
1436 assert(e1);
1437 if (!std::ranges::equal(e0->value_shape(), e1->value_shape()))
1438 {
1439 throw std::invalid_argument(
1440 "Interpolation: elements have different value dimensions");
1441 }
1442
1443 if (e0->symmetric() != e1->symmetric())
1444 {
1445 // Convert between independent and full tensor components. This must
1446 // precede the same-map branch below, which cannot change the number
1447 // of components.
1448 impl::interpolate_nonmatching_maps(u1, cells1, u0, cells0);
1449 }
1450 else if (V1->mesh() == V0->mesh() and (e1 == e0 or *e1 == *e0))
1451 {
1452 // Same element and same mesh
1453 if (e1->block_size() != e0->block_size())
1454 throw std::invalid_argument("Mismatch in element block size.");
1455
1456 // Get dofmaps
1457 std::shared_ptr<const DofMap> dofmap0 = V0->dofmap();
1458 assert(dofmap0);
1459 std::shared_ptr<const DofMap> dofmap1 = V1->dofmap();
1460 assert(dofmap1);
1461
1462 // Iterate over mesh and interpolate on each cell
1463 const int bs0 = dofmap0->bs();
1464 const int bs1 = dofmap1->bs();
1465 std::span<T> u1_array = u1.x()->array();
1466 std::span<const T> u0_array = u0.x()->array();
1467 assert(cells0.size() == cells1.size());
1468 for (auto cell0_it = cells0.begin(), cell1_it = cells1.begin();
1469 cell0_it != cells0.end() and cell1_it != cells1.end();
1470 ++cell0_it, ++cell1_it)
1471
1472 {
1473 std::span<const std::int32_t> dofs0 = dofmap0->cell_dofs(*cell0_it);
1474 std::span<const std::int32_t> dofs1 = dofmap1->cell_dofs(*cell1_it);
1475 assert(bs0 * dofs0.size() == bs1 * dofs1.size());
1476 for (std::size_t i = 0; i < dofs0.size(); ++i)
1477 {
1478 for (int k = 0; k < bs0; ++k)
1479 {
1480 int index = bs0 * i + k;
1481 std::div_t dv1 = std::div(index, bs1);
1482 u1_array[bs1 * dofs1[dv1.quot] + dv1.rem]
1483 = u0_array[bs0 * dofs0[i] + k];
1484 }
1485 }
1486 }
1487 }
1488 else if (e1->map_type() == e0->map_type())
1489 {
1490 // Different elements, same basis function map type
1491 impl::interpolate_same_map(u1, cells1, u0, cells0);
1492 }
1493 else
1494 {
1495 // Different elements with different maps for basis functions
1496 impl::interpolate_nonmatching_maps(u1, cells1, u0, cells0);
1497 }
1498}
1499
1509template <dolfinx::scalar T, std::floating_point U>
1511 std::ranges::input_range auto&& cells)
1512{
1513 assert(u1.function_space());
1514 assert(u0.function_space());
1515 if (u1.function_space()->mesh() == u0.function_space()->mesh())
1516 interpolate<T, U>(u1, cells, u0, cells);
1517 else
1518 throw std::invalid_argument("Meshes do no match.");
1519}
1520
1530template <dolfinx::scalar T, std::floating_point U>
1532{
1533 assert(u1.function_space());
1534 assert(u0.function_space());
1535 if (auto V1 = u1.function_space(); V1 == u0.function_space())
1536 std::ranges::copy(u0.x()->array(), u1.x()->array().begin());
1537 else
1538 {
1539 auto mesh = V1->mesh();
1540 assert(mesh);
1541 assert(mesh->topology());
1542 auto map = mesh->topology()->index_map(mesh->topology()->dim());
1543 assert(map);
1544 std::int32_t num_cells = map->size_local() + map->num_ghosts();
1545 interpolate<T, U>(u1, u0, std::ranges::views::iota(0, num_cells));
1546 }
1547}
1548} // namespace dolfinx::fem
Degree-of-freedom map representations and tools.
Definition CoordinateElement.h:39
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
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
Model of a finite element.
Definition FiniteElement.h:201
std::pair< std::vector< geometry_type >, std::array< std::size_t, 2 > > interpolation_points() const
Points on the reference cell at which an expression needs to be evaluated in order to interpolate the...
Definition FiniteElement.cpp:550
mesh::CellType cell_type() const noexcept
Cell shape that the element is defined on.
Definition FiniteElement.cpp:346
Definition Function.h:44
std::shared_ptr< const FunctionSpace< geometry_type > > function_space() const
Access the function space.
Definition Function.h:145
void eval(std::span< const geometry_type > x, std::array< std::size_t, 2 > xshape, mesh::CellRange auto &&cells, std::span< value_type > u, std::array< std::size_t, 2 > ushape, double tol, int maxit) const
Evaluate the Function at points.
Definition Function.h:320
std::shared_ptr< const la::Vector< value_type > > x() const
Underlying vector (const version).
Definition Function.h:151
Geometry stores the geometry imposed on a mesh.
Definition Geometry.h:39
A Mesh consists of a set of connected and numbered mesh topological entities, and geometry data.
Definition Mesh.h:25
Requirement on range of cell indices.
Definition Topology.h:32
MPI_Datatype mpi_t
Retrieves the MPI data type associated to the provided type.
Definition MPI.h:326
int rank(MPI_Comm comm)
Return process rank for the communicator.
Definition MPI.cpp:73
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:40
Finite element method functionality.
Definition assemble_expression_impl.h:28
geometry::PointOwnershipData< T > create_interpolation_data(const mesh::Geometry< T > &geometry0, const FiniteElement< T > &element0, const mesh::Mesh< T > &mesh1, mesh::CellRange auto &&cells, T padding, bool allow_extrapolation=true)
Generate data needed to interpolate finite element fem::Function's across different meshes.
Definition interpolate.h:1229
@ transpose
Transpose.
Definition FiniteElement.h:34
@ inverse_transpose
Transpose inverse.
Definition FiniteElement.h:36
@ standard
Standard.
Definition FiniteElement.h:33
std::vector< T > interpolation_coords(const fem::FiniteElement< T > &element, const mesh::Geometry< T > &geometry, mesh::CellRange auto &&cells)
Compute the evaluation points in the physical space at which an expression should be computed to inte...
Definition interpolate.h:46
void interpolate(Function< T, U > &u1, mesh::CellRange auto &&cells1, const Expression< T, U > &e0, mesh::CellRange auto &&cells0)
Interpolate an Expression into a Function over a subset of cells.
Definition expression_evaluate.h:209
Geometry data structures and algorithms.
Definition BoundingBoxTree.h:26
PointOwnershipData< T > determine_point_ownership(const mesh::Mesh< T > &mesh, std::span< const T > points, T padding, std::optional< std::span< const std::int32_t > > cells, bool find_closest_cell=true)
Determine, for a set of points, the owning process of the cell (if any) that contains each point.
Definition utils.h:820
Mesh data structures and algorithms on meshes.
Definition DofMap.h:32
CellType
Cell type identifier.
Definition cell_types.h:24
auto logger() -> spdlog::logger &
Dolfinx's logger.
Definition log.cpp:24
Information on the ownership of points distributed across processes.
Definition utils.h:36
std::vector< T > dest_points
Points that are owned by current process.
Definition utils.h:41
std::vector< std::int32_t > dest_cells
Definition utils.h:43
std::vector< int > dest_owners
Ranks that sent dest_points to current process.
Definition utils.h:40
std::vector< int > src_owner
Definition utils.h:37