Skip to content

Commit 794e374

Browse files
jhalejorgensdclaude
authored
Create new mesh with interpolation of existing mesh geometry (#4205)
* Add gdim. * Expose gdim through to Python. * Add two tests * Added check on UFL coordinate element - check on array nonsense. * ruff * Change exception message * Move gdim before reorder_fn, fix a few other rough edges. * Restore original path for performance. * Small tweaks. * Add some asserts * Change Mesh.RecombineAll option from 2 to 1 * clang-format * Direct translation, not tested, no Python interface. * Claude generated tests, need careful checking. * Add tests * Tidy comment * Tidy up docstrings * Simplify * Simplify * Add tests from Jorgen * Tests pass * clang-format * Add copyright notice regarding io4dolfinx * Docstrings * Move implementation to header * Revert "Move implementation to header" This reverts commit 395b2c3. * Move to FEM. * Split out to utils.py file * Move test to fem * clang-format * Move interpolate_geometry implementation to fem/utils.h header All required includes (basix, FunctionSpace, DofMap, etc.) are already present transitively in fem/utils.h, so the only addition needed is <numeric>. This is consistent with other template functions defined inline in fem/utils.h. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Try again - cannot get clang-format to do anything locally * Update test_interpolate_geometry.py * Fixup * Tidy up geometry mapping * Tweak * clang-format * Convergence check * Add back test which does degree lowering check * Final tidyup * Drop out a radius, adds little * Remove memory - not used as far as I can see. --------- Co-authored-by: Jørgen Schartum Dokken <dokken92@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 60c4730 commit 794e374

5 files changed

Lines changed: 468 additions & 171 deletions

File tree

cpp/dolfinx/fem/utils.h

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include <format>
3232
#include <functional>
3333
#include <memory>
34+
#include <numeric>
3435
#include <optional>
3536
#include <ranges>
3637
#include <set>
@@ -1060,4 +1061,74 @@ Expression<T, U> create_expression(
10601061
return create_expression(e, coeff_map, const_map, argument_space);
10611062
}
10621063

1064+
/// @brief Take an existing mesh and create a new mesh with its geometry
1065+
/// interpolated into a new coordinate element.
1066+
///
1067+
/// The topology is shared between `mesh` and the returned mesh.
1068+
///
1069+
/// Useful for creating a higher-order mesh from a lower-order one for
1070+
/// computation, or vice-versa, for IO.
1071+
///
1072+
/// @param[in] mesh Input mesh.
1073+
/// @param[in] new_cmap Coordinate element for the new geometry.
1074+
/// @param[in] reorder_fn Optional graph reordering function applied to
1075+
/// the new geometry dofmap.
1076+
/// @return A new mesh sharing the topology of `mesh` and with a
1077+
/// geometry described by `new_cmap`.
1078+
template <std::floating_point T>
1079+
mesh::Mesh<T> interpolate_geometry(
1080+
std::shared_ptr<mesh::Mesh<T>> mesh, const CoordinateElement<T>& new_cmap,
1081+
const std::function<std::vector<int>(
1082+
const graph::AdjacencyList<std::int32_t>&)>& reorder_fn = nullptr)
1083+
{
1084+
assert(mesh);
1085+
const CoordinateElement<T>& old_cmap = mesh->geometry().cmap();
1086+
if (new_cmap.cell_shape() != old_cmap.cell_shape())
1087+
{
1088+
throw std::runtime_error(
1089+
"Cell shape of new coordinate element must match input mesh.");
1090+
}
1091+
1092+
const int gdim = mesh->geometry().dim();
1093+
1094+
// Build a vector-valued Lagrange FiniteElement from the coordinate element.
1095+
basix::FiniteElement<T> b_element = basix::create_element<T>(
1096+
basix::element::family::P,
1097+
mesh::cell_type_to_basix_type(new_cmap.cell_shape()), new_cmap.degree(),
1098+
new_cmap.variant(), basix::element::dpc_variant::unset, false);
1099+
auto element = std::make_shared<const FiniteElement<T>>(
1100+
b_element, std::vector<std::size_t>{static_cast<std::size_t>(gdim)});
1101+
1102+
FunctionSpace<T> V = create_functionspace(mesh, element, reorder_fn);
1103+
1104+
// Tabulate physical coordinates of the new geometry dofs.
1105+
std::vector<T> x_new = V.tabulate_dof_coordinates(false);
1106+
1107+
// Pull the geometry dofmap and index map from V.
1108+
std::shared_ptr<const DofMap> dm = V.dofmap();
1109+
assert(dm);
1110+
std::shared_ptr<const common::IndexMap> new_imap = dm->index_map;
1111+
assert(new_imap);
1112+
1113+
auto map_view = dm->map();
1114+
std::vector<std::int32_t> dofmap_flat(
1115+
map_view.data_handle(), map_view.data_handle() + map_view.size());
1116+
1117+
// Build input_global_indices as the local-to-global of the new geometry
1118+
// dofs.
1119+
const std::int32_t num_nodes
1120+
= new_imap->size_local() + new_imap->num_ghosts();
1121+
std::vector<std::int32_t> local(num_nodes);
1122+
std::iota(local.begin(), local.end(), 0);
1123+
std::vector<std::int64_t> igi(num_nodes);
1124+
new_imap->local_to_global(local, igi);
1125+
1126+
mesh::Geometry<T> geometry(
1127+
new_imap, std::vector<std::vector<std::int32_t>>{std::move(dofmap_flat)},
1128+
std::vector<CoordinateElement<T>>{new_cmap}, std::move(x_new), gdim,
1129+
std::move(igi));
1130+
1131+
return mesh::Mesh<T>(mesh->comm(), mesh->topology(), std::move(geometry));
1132+
}
1133+
10631134
} // namespace dolfinx::fem

python/dolfinx/fem/__init__.py

Lines changed: 13 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,8 @@
1010
dependencies and must be explicitly imported.
1111
"""
1212

13-
import typing
14-
15-
import numpy as np
16-
import numpy.typing as npt
17-
1813
from dolfinx.cpp.fem import _IntegralType as IntegralType
19-
from dolfinx.cpp.fem import build_sparsity_pattern as _build_sparsity_pattern
20-
from dolfinx.cpp.fem import compute_integration_domains as _compute_integration_domains
21-
from dolfinx.cpp.fem import create_interpolation_data as _create_interpolation_data
22-
from dolfinx.cpp.fem import create_sparsity_pattern as _create_sparsity_pattern
23-
from dolfinx.cpp.fem import discrete_curl as _discrete_curl
24-
from dolfinx.cpp.fem import discrete_gradient as _discrete_gradient
25-
from dolfinx.cpp.fem import interpolation_matrix as _interpolation_matrix
2614
from dolfinx.cpp.fem import transpose_dofmap
27-
from dolfinx.cpp.la import SparsityPattern
2815
from dolfinx.fem.assemble import (
2916
apply_lifting,
3017
assemble_matrix,
@@ -62,164 +49,16 @@
6249
FunctionSpace,
6350
functionspace,
6451
)
65-
from dolfinx.geometry import PointOwnershipData as _PointOwnershipData
66-
from dolfinx.la import MatrixCSR as _MatrixCSR
67-
68-
if typing.TYPE_CHECKING:
69-
import dolfinx.mesh
70-
71-
72-
def create_sparsity_pattern(a: Form):
73-
"""Create a sparsity pattern from a bilinear form.
74-
75-
Args:
76-
a: Bilinear form to build a sparsity pattern for.
77-
78-
Returns:
79-
Sparsity pattern for the form ``a``.
80-
81-
Note:
82-
The pattern is not finalised, i.e. the caller is responsible for
83-
calling ``assemble`` on the sparsity pattern.
84-
"""
85-
return _create_sparsity_pattern(a._cpp_object)
86-
87-
88-
def build_sparsity_pattern(pattern: SparsityPattern, a: Form):
89-
"""Build a sparsity pattern from a bilinear form.
90-
91-
Args:
92-
pattern: The sparsity pattern to add to
93-
a: Bilinear form to build a sparsity pattern for.
94-
95-
Returns:
96-
Sparsity pattern for the form ``a``.
97-
98-
Note:
99-
The pattern is not finalised, i.e. the caller is responsible for
100-
calling ``assemble`` on the sparsity pattern.
101-
"""
102-
return _build_sparsity_pattern(pattern, a._cpp_object)
103-
104-
105-
def create_interpolation_data(
106-
V_to: FunctionSpace,
107-
V_from: FunctionSpace,
108-
cells: npt.NDArray[np.int32],
109-
padding: float = 1e-14,
110-
) -> _PointOwnershipData:
111-
"""Generate data for interpolating functions on different meshes.
112-
113-
Args:
114-
V_to: Function space to interpolate into.
115-
V_from: Function space to interpolate from.
116-
cells: Indices of the cells associated with `V_to` on which to
117-
interpolate into.
118-
padding: Absolute padding of bounding boxes of all entities on
119-
mesh_to.
120-
121-
Returns:
122-
Data needed to interpolation functions defined on function
123-
spaces on the meshes.
124-
"""
125-
return _PointOwnershipData(
126-
_create_interpolation_data(
127-
V_to.mesh._cpp_object.geometry,
128-
V_to.element._cpp_object,
129-
V_from.mesh._cpp_object,
130-
cells,
131-
padding,
132-
)
133-
)
134-
135-
136-
def discrete_curl(V0: FunctionSpace, V1: FunctionSpace) -> _MatrixCSR:
137-
"""Assemble a discrete curl operator.
138-
139-
The discrete curl operator interpolates the curl of H(curl) finite
140-
element function into a H(div) space.
141-
142-
Args:
143-
V0: H1(curl) space to interpolate the curl from.
144-
V1: H(div) space to interpolate into.
145-
146-
Returns:
147-
Discrete curl operator.
148-
"""
149-
return _MatrixCSR(_discrete_curl(V0._cpp_object, V1._cpp_object))
150-
151-
152-
def discrete_gradient(space0: FunctionSpace, space1: FunctionSpace) -> _MatrixCSR:
153-
"""Assemble a discrete gradient operator.
154-
155-
The discrete gradient operator interpolates the gradient of a H1
156-
finite element function into a H(curl) space. It is assumed that the
157-
H1 space uses an identity map and the H(curl) space uses a covariant
158-
Piola map.
159-
160-
Args:
161-
space0: H1 space to interpolate the gradient from.
162-
space1: H(curl) space to interpolate into.
163-
164-
Returns:
165-
Discrete gradient operator.
166-
"""
167-
return _MatrixCSR(_discrete_gradient(space0._cpp_object, space1._cpp_object))
168-
169-
170-
def interpolation_matrix(space0: FunctionSpace, space1: FunctionSpace) -> _MatrixCSR:
171-
"""Create interpolation matrix between spaces on the same mesh.
172-
173-
Args:
174-
space0: space to interpolate from.
175-
space1: space to interpolate into.
176-
177-
Returns:
178-
Interpolation matrix.
179-
180-
Note:
181-
The returned matrix is not finalised, i.e. ghost values are not
182-
accumulated.
183-
"""
184-
return _MatrixCSR(_interpolation_matrix(space0._cpp_object, space1._cpp_object))
185-
186-
187-
def compute_integration_domains(
188-
integral_type: IntegralType, topology: "dolfinx.mesh.Topology", entities: np.ndarray
189-
):
190-
"""Determine compute integration entities.
191-
192-
This function returns a list ``[(id, entities)]``. For cell
193-
integrals ``entities`` are the cell indices. For exterior facet
194-
integrals, ``entities`` is a list of ``(cell_index,
195-
local_facet_index)`` pairs. For interior facet integrals,
196-
``entities`` is a list of ``(cell_index0, local_facet_index0,
197-
cell_index1, local_facet_index1)``. ``id`` refers to the subdomain
198-
id used in the definition of the integration measures of the
199-
variational form.
200-
201-
Note:
202-
Owned mesh entities only are returned. Ghost entities are not
203-
included.
204-
205-
Note:
206-
For facet integrals, the topology facet-to-cell and
207-
cell-to-facet connectivity must be computed before calling this
208-
function.
209-
210-
Args:
211-
integral_type: Integral type.
212-
topology: Mesh topology.
213-
entities: List of mesh entities. For
214-
``integral_type==IntegralType.cell``, ``entities`` should be
215-
cell indices. For other ``IntegralType``s, ``entities``
216-
should be facet indices.
217-
218-
Returns:
219-
List of integration entities.
220-
"""
221-
return _compute_integration_domains(integral_type, topology._cpp_object, entities)
222-
52+
from dolfinx.fem.utils import (
53+
build_sparsity_pattern,
54+
compute_integration_domains,
55+
create_interpolation_data,
56+
create_sparsity_pattern,
57+
discrete_curl,
58+
discrete_gradient,
59+
interpolate_geometry,
60+
interpolation_matrix,
61+
)
22362

22463
__all__ = [
22564
"Constant",
@@ -238,6 +77,7 @@ def compute_integration_domains(
23877
"assemble_scalar",
23978
"assemble_vector",
24079
"bcs_by_block",
80+
"build_sparsity_pattern",
24181
"compile_form",
24282
"compute_integration_domains",
24383
"coordinate_element",
@@ -255,6 +95,8 @@ def compute_integration_domains(
25595
"form",
25696
"form_cpp_class",
25797
"functionspace",
98+
"interpolate_geometry",
99+
"interpolation_matrix",
258100
"locate_dofs_geometrical",
259101
"locate_dofs_topological",
260102
"mixed_topology_form",

0 commit comments

Comments
 (0)