Skip to content

Commit 7e9c3e2

Browse files
committed
Allow for vector creation from list of function spaces
1 parent c452638 commit 7e9c3e2

File tree

3 files changed

+94
-52
lines changed

3 files changed

+94
-52
lines changed

python/dolfinx/fem/petsc.py

Lines changed: 6 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (C) 2018-2025 Garth N. Wells and Jørgen S. Dokken
1+
# Copyright (C) 2018-2025 Garth N. Wells, Jørgen S. Dokken and Paul T. Kühner
22
#
33
# This file is part of DOLFINx (https://www.fenicsproject.org)
44
#
@@ -23,7 +23,6 @@
2323

2424
import contextlib
2525
import functools
26-
import itertools
2726
import typing
2827
from collections.abc import Iterable, Sequence
2928

@@ -48,7 +47,7 @@
4847
from dolfinx.fem.assemble import apply_lifting as _apply_lifting
4948
from dolfinx.fem.bcs import DirichletBC
5049
from dolfinx.fem.bcs import bcs_by_block as _bcs_by_block
51-
from dolfinx.fem.forms import Form
50+
from dolfinx.fem.forms import Form, extract_function_spaces
5251
from dolfinx.fem.forms import extract_function_spaces as _extract_spaces
5352
from dolfinx.fem.forms import form as _create_form
5453
from dolfinx.fem.function import Function as _Function
@@ -152,38 +151,10 @@ def create_vector(
152151
A PETSc vector with a layout that is compatible with ``L``. The
153152
vector is not initialised to zero.
154153
"""
155-
try:
156-
dofmap = L.function_spaces[0].dofmaps(0) # Single form case
157-
return dolfinx.la.petsc.create_vector(dofmap.index_map, dofmap.index_map_bs)
158-
except AttributeError:
159-
maps = [
160-
(
161-
form.function_spaces[0].dofmaps(0).index_map,
162-
form.function_spaces[0].dofmaps(0).index_map_bs,
163-
)
164-
for form in L
165-
]
166-
if kind == PETSc.Vec.Type.NEST:
167-
return _cpp.fem.petsc.create_vector_nest(maps)
168-
elif kind == PETSc.Vec.Type.MPI:
169-
off_owned = tuple(
170-
itertools.accumulate(maps, lambda off, m: off + m[0].size_local * m[1], initial=0)
171-
)
172-
off_ghost = tuple(
173-
itertools.accumulate(
174-
maps, lambda off, m: off + m[0].num_ghosts * m[1], initial=off_owned[-1]
175-
)
176-
)
154+
if isinstance(L, Form):
155+
L = [L]
177156

178-
b = _cpp.fem.petsc.create_vector_block(maps)
179-
b.setAttr("_blocks", (off_owned, off_ghost))
180-
return b
181-
else:
182-
raise NotImplementedError(
183-
"Vector type must be specified for blocked/nested assembly."
184-
f"Vector type '{kind}' not supported."
185-
"Did you mean 'nest' or 'mpi'?"
186-
)
157+
return dolfinx.la.petsc.create_vector(extract_function_spaces(L), kind)
187158

188159

189160
# -- Matrix instantiation ----------------------------------------------------
@@ -299,6 +270,7 @@ def assemble_vector(
299270
An assembled vector.
300271
"""
301272
b = create_vector(L, kind=kind)
273+
302274
if kind == PETSc.Vec.Type.NEST:
303275
for b_sub in b.getNestSubVecs():
304276
with b_sub.localForm() as b_local:

python/dolfinx/la/petsc.py

Lines changed: 87 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"""
1515

1616
import functools
17+
import itertools
1718
import typing
1819

1920
from petsc4py import PETSc
@@ -22,29 +23,14 @@
2223
import numpy.typing as npt
2324

2425
import dolfinx
25-
from dolfinx.la import IndexMap, Vector
26+
from dolfinx.fem.function import FunctionSpace
27+
from dolfinx.la import Vector
2628

2729
assert dolfinx.has_petsc4py
2830

2931
__all__ = ["assign", "create_vector", "create_vector_wrap"]
3032

3133

32-
def create_vector(index_map: IndexMap, bs: int) -> PETSc.Vec: # type: ignore[name-defined]
33-
"""Create a distributed PETSc vector.
34-
35-
Args:
36-
index_map: Index map that describes the size and parallel layout of
37-
the vector to create.
38-
bs: Block size of the vector.
39-
40-
Returns:
41-
PETSc Vec object.
42-
"""
43-
ghosts = index_map.ghosts.astype(PETSc.IntType) # type: ignore[attr-defined]
44-
size = (index_map.size_local * bs, index_map.size_global * bs)
45-
return PETSc.Vec().createGhost(ghosts, size=size, bsize=bs, comm=index_map.comm) # type: ignore
46-
47-
4834
def create_vector_wrap(x: Vector) -> PETSc.Vec: # type: ignore[name-defined]
4935
"""Wrap a distributed DOLFINx vector as a PETSc vector.
5036
@@ -63,6 +49,90 @@ def create_vector_wrap(x: Vector) -> PETSc.Vec: # type: ignore[name-defined]
6349
)
6450

6551

52+
def create_vector(
53+
V: typing.Union[list[FunctionSpace]],
54+
kind: typing.Optional[str] = None,
55+
) -> PETSc.Vec:
56+
"""Create a PETSc vector that is compatible with a Functionspace
57+
or a list of Functionspace(s).
58+
59+
Three cases are supported:
60+
61+
1. For a single functionspace ``V``, if ``kind`` is ``None`` or is
62+
``PETSc.Vec.Type.MPI``, a ghosted PETSc vector which is
63+
compatible with ``V`` is created.
64+
65+
2. If ``V=[V_0, ..., V_n]`` is a sequence of function spaces and ``kind`` is ``None``
66+
or is ``PETSc.Vec.Type.MPI``, a ghosted PETSc vector which is
67+
compatible with ``V`` is created. The created vector ``b`` is
68+
initialized such that on each MPI process ``b = [b_0, b_1, ...,
69+
b_n, b_0g, b_1g, ..., b_ng]``, where ``b_i`` are the entries
70+
associated with the 'owned' degrees-of-freedom for ``V[i]`` and
71+
``b_ig`` are the 'unowned' (ghost) entries for ``V[i]``.
72+
73+
For this case, the returned vector has an attribute ``_blocks``
74+
that holds the local offsets into ``b`` for the (i) owned and
75+
(ii) ghost entries for each ``V[i]``. It can be accessed by
76+
``b.getAttr("_blocks")``. The offsets can be used to get views
77+
into ``b`` for blocks, e.g.::
78+
79+
>>> offsets0, offsets1, = b.getAttr("_blocks")
80+
>>> offsets0
81+
(0, 12, 28)
82+
>>> offsets1
83+
(28, 32, 35)
84+
>>> b0_owned = b.array[offsets0[0]:offsets0[1]]
85+
>>> b0_ghost = b.array[offsets1[0]:offsets1[1]]
86+
>>> b1_owned = b.array[offsets0[1]:offsets0[2]]
87+
>>> b1_ghost = b.array[offsets1[1]:offsets1[2]]
88+
89+
3. If `V=[V_0, ..., V_n]`` is a sequence of function space and ``kind`` is
90+
``PETSc.Vec.Type.NEST``, a PETSc nested vector (a 'nest' of
91+
ghosted PETSc vectors) which is compatible with ``V`` is created.
92+
93+
Args:
94+
V: Functionspace or a sequence of functionspaces.
95+
kind: PETSc vector type (``VecType``) to create.
96+
97+
Returns:
98+
A PETSc vector with a layout that is compatible with ````. The
99+
vector is not initialised to zero.
100+
"""
101+
if len(V) == 1:
102+
# Single space case
103+
index_map = V[0].dofmap.index_map
104+
bs = V[0].dofmap.index_map_bs
105+
ghosts = index_map.ghosts.astype(PETSc.IntType) # type: ignore[attr-defined]
106+
size = (index_map.size_local * bs, index_map.size_global * bs)
107+
return PETSc.Vec().createGhost(ghosts, size=size, bsize=bs, comm=index_map.comm) # type: ignore
108+
109+
maps = [(_V.dofmaps(0).index_map, _V.dofmaps(0).index_map_bs) for _V in V]
110+
111+
if kind is None or kind == PETSc.Vec.Type.MPI:
112+
off_owned = tuple(
113+
itertools.accumulate(maps, lambda off, m: off + m[0].size_local * m[1], initial=0)
114+
)
115+
off_ghost = tuple(
116+
itertools.accumulate(
117+
maps, lambda off, m: off + m[0].num_ghosts * m[1], initial=off_owned[-1]
118+
)
119+
)
120+
121+
b = dolfinx.cpp.fem.petsc.create_vector_block(maps)
122+
b.setAttr("_blocks", (off_owned, off_ghost))
123+
return b
124+
125+
elif kind == PETSc.Vec.Type.NEST:
126+
return dolfinx.cpp.fem.petsc.create_vector_nest(maps)
127+
128+
else:
129+
raise NotImplementedError(
130+
"Vector type must be specified for blocked/nested assembly."
131+
f"Vector type '{kind}' not supported."
132+
"Did you mean 'nest' or 'mpi'?"
133+
)
134+
135+
66136
@functools.singledispatch
67137
def assign(x0: typing.Union[npt.NDArray[np.inexact], list[npt.NDArray[np.inexact]]], x1: PETSc.Vec): # type: ignore
68138
"""Assign ``x0`` values to a PETSc vector ``x1``.

python/test/unit/nls/test_newton.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ def test_nonlinear_pde_snes(self):
220220
problem = NonlinearPDE_SNESProblem(F, u, bc)
221221

222222
u.x.array[:] = 0.9
223-
b = create_vector(V.dofmap.index_map, V.dofmap.index_map_bs)
223+
b = create_vector([V])
224224
J = create_matrix(problem.a)
225225

226226
# Create Newton solver and solve

0 commit comments

Comments
 (0)