Skip to content

Commit 9406bc1

Browse files
authored
Merge pull request #36 from scientificcomputing/dokken/vertex_to_dofmap
Add vertex_to_dofmap
2 parents fd36e29 + a5209d2 commit 9406bc1

File tree

5 files changed

+176
-8
lines changed

5 files changed

+176
-8
lines changed

CMakeLists.txt

-2
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ if(Basix_FOUND)
3232
message(STATUS "Found Basix at ${Basix_DIR}")
3333
endif()
3434

35-
36-
3735
find_package(DOLFINX REQUIRED CONFIG)
3836

3937
if(DOLFINX_FOUND)

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
# Scientific Computing Tools for Finite Element Methods
2+
23
This package contains a collection of tools for scientific computing with a focus on finite element methods. The tools are written in Python and are intended to be used in conjunction with the [dolfinx](https://github.com/FEniCS/dolfinx).
34

45
Many users that are transitioning from legacy FEniCS to FEniCSx may find the transition difficult due to the lack of some functionalities in FEniCSx.
56
This package aims to provide some of the functionalities that are missing in FEniCSx.
67
The package is still in its early stages and many functionalities are still missing.
78

89
## Features
10+
911
- Real-space implementation for usage in DOLFINx (>=v0.8.0)
1012
- Save quadrature functions as point clouds
1113
- Save any function that can tabulate dof coordinates as point clouds.
1214
- Point sources for usage in DOLFINx (>=v0.8.0)
1315
- Point sources in vector spaces are only supported on v0.9.0, post [DOLFINx PR 3429](https://github.com/FEniCS/dolfinx/pull/3429).
1416
For older versions, apply one point source in each sub space.
15-
17+
- Maps between degrees of freedom and vertices: `vertex_to_dofmap` and `dof_to_vertex`
1618

1719
## Installation
1820

src/scifem.cpp

+90-4
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,46 @@
1010
#include <dolfinx/mesh/Mesh.h>
1111
#include <memory>
1212
#include <nanobind/nanobind.h>
13+
#include <nanobind/ndarray.h>
1314
#include <nanobind/stl/shared_ptr.h>
1415
#include <nanobind/stl/tuple.h>
1516
#include <nanobind/stl/vector.h>
1617

17-
using namespace dolfinx;
18+
namespace
19+
{
20+
// Copyright (c) 2020-2024 Chris Richardson, Matthew Scroggs and Garth N. Wells
21+
// FEniCS Project (Basix)
22+
// SPDX-License-Identifier: MIT
23+
24+
template <typename V>
25+
auto as_nbarray(V&& x, std::size_t ndim, const std::size_t* shape)
26+
{
27+
using _V = std::decay_t<V>;
28+
_V* ptr = new _V(std::move(x));
29+
return nanobind::ndarray<typename _V::value_type, nanobind::numpy>(
30+
ptr->data(), ndim, shape,
31+
nanobind::capsule(ptr, [](void* p) noexcept { delete (_V*)p; }));
32+
}
33+
34+
template <typename V>
35+
auto as_nbarray(V&& x, const std::initializer_list<std::size_t> shape)
36+
{
37+
return as_nbarray(x, shape.size(), shape.begin());
38+
}
39+
40+
template <typename V>
41+
auto as_nbarray(V&& x)
42+
{
43+
return as_nbarray(std::move(x), {x.size()});
44+
}
45+
46+
template <typename V, std::size_t U>
47+
auto as_nbarrayp(std::pair<V, std::array<std::size_t, U>>&& x)
48+
{
49+
return as_nbarray(std::move(x.first), x.second.size(), x.second.data());
50+
}
51+
52+
} // namespace
1853

1954
namespace scifem
2055
{
@@ -26,7 +61,7 @@ create_real_functionspace(std::shared_ptr<const dolfinx::mesh::Mesh<T>> mesh,
2661

2762
basix::FiniteElement e_v = basix::create_element<T>(
2863
basix::element::family::P,
29-
mesh::cell_type_to_basix_type(mesh->topology()->cell_type()), 0,
64+
dolfinx::mesh::cell_type_to_basix_type(mesh->topology()->cell_type()), 0,
3065
basix::element::lagrange_variant::unset,
3166
basix::element::dpc_variant::unset, true);
3267

@@ -45,8 +80,8 @@ create_real_functionspace(std::shared_ptr<const dolfinx::mesh::Mesh<T>> mesh,
4580
int index_map_bs = value_size;
4681
int bs = value_size;
4782
// Element dof layout
48-
fem::ElementDofLayout dof_layout(value_size, e_v.entity_dofs(),
49-
e_v.entity_closure_dofs(), {}, {});
83+
dolfinx::fem::ElementDofLayout dof_layout(value_size, e_v.entity_dofs(),
84+
e_v.entity_closure_dofs(), {}, {});
5085
std::size_t num_cells_on_process
5186
= mesh->topology()->index_map(mesh->topology()->dim())->size_local()
5287
+ mesh->topology()->index_map(mesh->topology()->dim())->num_ghosts();
@@ -63,6 +98,47 @@ create_real_functionspace(std::shared_ptr<const dolfinx::mesh::Mesh<T>> mesh,
6398

6499
return dolfinx::fem::FunctionSpace<T>(mesh, d_el, real_dofmap, value_shape);
65100
}
101+
102+
std::vector<std::int32_t>
103+
create_vertex_to_dofmap(std::shared_ptr<const dolfinx::mesh::Topology> topology,
104+
std::shared_ptr<const dolfinx::fem::DofMap> dofmap)
105+
{
106+
// Get number of vertices
107+
const std::shared_ptr<const dolfinx::common::IndexMap> v_map
108+
= topology->index_map(0);
109+
std::size_t num_vertices = v_map->size_local() + v_map->num_ghosts();
110+
111+
// Get cell to vertex connectivity
112+
std::shared_ptr<const dolfinx::graph::AdjacencyList<std::int32_t>> c_to_v
113+
= topology->connectivity(topology->dim(), 0);
114+
assert(c_to_v);
115+
116+
// Get vertex dof layout
117+
const dolfinx::fem::ElementDofLayout& layout = dofmap->element_dof_layout();
118+
const std::vector<std::vector<std::vector<int>>>& entity_dofs
119+
= layout.entity_dofs_all();
120+
const std::vector<std::vector<int>> vertex_dofs = entity_dofs.front();
121+
122+
// Get number of cells on process
123+
const std::shared_ptr<const dolfinx::common::IndexMap> c_map
124+
= topology->index_map(topology->dim());
125+
std::size_t num_cells = c_map->size_local() + c_map->num_ghosts();
126+
127+
std::vector<std::int32_t> vertex_to_dofmap(num_vertices);
128+
for (std::size_t i = 0; i < num_cells; i++)
129+
{
130+
auto vertices = c_to_v->links(i);
131+
auto dofs = dofmap->cell_dofs(i);
132+
for (std::size_t j = 0; j < vertices.size(); ++j)
133+
{
134+
const std::vector<int>& vdof = vertex_dofs[j];
135+
assert(vdof.size() == 1);
136+
vertex_to_dofmap[vertices[j]] = dofs[vdof.front()];
137+
}
138+
}
139+
return vertex_to_dofmap;
140+
}
141+
66142
} // namespace scifem
67143

68144
namespace scifem_wrapper
@@ -77,6 +153,16 @@ void declare_real_function_space(nanobind::module_& m, std::string type)
77153
std::vector<std::size_t> value_shape)
78154
{ return scifem::create_real_functionspace<T>(mesh, value_shape); },
79155
"Create a real function space");
156+
m.def(
157+
"vertex_to_dofmap",
158+
[](std::shared_ptr<const dolfinx::mesh::Topology> topology,
159+
std::shared_ptr<const dolfinx::fem::DofMap> dofmap)
160+
{
161+
std::vector<std::int32_t> v_to_d
162+
= scifem::create_vertex_to_dofmap(topology, dofmap);
163+
return as_nbarray(v_to_d);
164+
},
165+
"Create a vertex to dofmap.");
80166
}
81167

82168
} // namespace scifem_wrapper

src/scifem/__init__.py

+47-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
11
import dolfinx
22
import basix
33
import numpy as np
4+
import numpy.typing as npt
45
from . import _scifem # type: ignore
56
from .point_source import PointSource
67
from .assembly import assemble_scalar
78
from . import xdmf
89

10+
__all__ = [
11+
"PointSource",
12+
"assemble_scalar",
13+
"xdmf",
14+
"create_real_functionspace",
15+
"assemble_scalar",
16+
"PointSource",
17+
"xdmf",
18+
"vertex_to_dofmap",
19+
"dof_to_vertexmap",
20+
]
21+
922

1023
def create_real_functionspace(
1124
mesh: dolfinx.mesh.Mesh, value_shape: tuple[int, ...] = ()
@@ -37,4 +50,37 @@ def create_real_functionspace(
3750
return dolfinx.fem.FunctionSpace(mesh, ufl_e, cppV)
3851

3952

40-
__all__ = ["create_real_functionspace", "assemble_scalar", "PointSource", "xdmf"]
53+
def vertex_to_dofmap(V: dolfinx.fem.FunctionSpace) -> npt.NDArray[np.int32]:
54+
"""
55+
Create a map from the vertices (local to the process) to the correspondning degrees
56+
of freedom.
57+
58+
Args:
59+
V: The function space
60+
61+
Returns:
62+
An array mapping local vertex i to local degree of freedom
63+
64+
Note:
65+
If using a blocked space this map is not unrolled for the DofMap block size.
66+
"""
67+
return _scifem.vertex_to_dofmap(V.mesh._cpp_object.topology, V.dofmap._cpp_object)
68+
69+
70+
def dof_to_vertexmap(V: dolfinx.fem.FunctionSpace) -> npt.NDArray[np.int32]:
71+
"""
72+
Create a map from the degrees of freedom to the vertices of the mesh.
73+
As not every degree of freedom is associated with a vertex, every dof that is not
74+
associated with a vertex returns -1
75+
76+
Args:
77+
V: The function space
78+
79+
Returns:
80+
An array mapping local dof i to a local vertex
81+
"""
82+
num_dofs_local = V.dofmap.index_map.size_local + V.dofmap.index_map.num_ghosts
83+
dof_to_vertex_map = np.full(num_dofs_local, -1, dtype=np.int32)
84+
v_to_d = _scifem.vertex_to_dofmap(V.mesh._cpp_object.topology, V.dofmap._cpp_object)
85+
dof_to_vertex_map[v_to_d] = np.arange(len(v_to_d), dtype=np.int32)
86+
return dof_to_vertex_map

tests/test_vertex_to_dofmap.py

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from mpi4py import MPI
2+
import pytest
3+
import dolfinx
4+
from scifem import vertex_to_dofmap, dof_to_vertexmap
5+
import numpy as np
6+
7+
8+
@pytest.mark.parametrize("degree", range(1, 4))
9+
@pytest.mark.parametrize("value_size", [(), (2,), (2, 3)])
10+
def test_vertex_to_dofmap_P(degree, value_size):
11+
mesh = dolfinx.mesh.create_unit_square(MPI.COMM_WORLD, 10, 2)
12+
V = dolfinx.fem.functionspace(mesh, ("Lagrange", degree, value_size))
13+
14+
v_to_d = vertex_to_dofmap(V)
15+
mesh.topology.create_connectivity(0, mesh.topology.dim)
16+
geom_indices = dolfinx.mesh.entities_to_geometry(
17+
mesh, 0, np.arange(len(v_to_d), dtype=np.int32)
18+
)
19+
20+
x_V = V.tabulate_dof_coordinates()
21+
x_g = mesh.geometry.x
22+
tol = 1e3 * np.finfo(x_g.dtype).eps
23+
mesh = V.mesh
24+
25+
np.testing.assert_allclose(x_V[v_to_d], x_g[geom_indices.flatten()], atol=tol)
26+
27+
# Test inverse map
28+
d_to_v = dof_to_vertexmap(V)
29+
np.testing.assert_allclose(d_to_v[v_to_d], np.arange(len(v_to_d)))
30+
31+
# Mark all dofs connected with a vertex
32+
dof_marker = np.full(len(d_to_v), True, dtype=np.bool_)
33+
dof_marker[v_to_d] = False
34+
35+
other_dofs = np.flatnonzero(dof_marker)
36+
np.testing.assert_allclose(d_to_v[other_dofs], -1)

0 commit comments

Comments
 (0)