Skip to content

Commit 4627a17

Browse files
jhalegarth-wellsjorgensdmichalhabera
authored
Refactor LA into native and PETSc part and add assigners (#3659)
* Take directly from other branch. * Make implementations private. * Prototype dispatcher. * Remove trailing comma * Looser typing * Polishing. * Sketch function for assigning arrays of arrays to PETSc vectors Untested. * Use update in tests * Parallel fix * Remove ghost update * Remove ghost update that is called. * Add reverse assign direction * Consistent use of assign * More simplifications * Add assign from Function to Vec * More simplification * Work on docs * Doc fixes * Update python/dolfinx/fem/petsc.py Co-authored-by: Jørgen Schartum Dokken <dokken92@gmail.com> * Update python/dolfinx/fem/petsc.py Co-authored-by: Jørgen Schartum Dokken <dokken92@gmail.com> * Update python/dolfinx/fem/petsc.py Co-authored-by: Jørgen Schartum Dokken <dokken92@gmail.com> * Update python/dolfinx/fem/petsc.py Co-authored-by: Jørgen Schartum Dokken <dokken92@gmail.com> * Fix. * Tidy up typing. A Collection has no ordering - Sequence is correct. Use np.inexact instead of np.floating * Make la into a directory-based module - no API changes. * Fix some tests. * Fix. * Fix. * Remove duplicate code * Add back workflow dispatch for Windows. * Fix. * Update __init__.py * Don't understand docstring issue. * Update petsc.py * Fix docstring ala Garth. * Partial renaming. * Refactor. * Fix up. * ruff * Improve typing * Fix typo * Guard typechecked definitions, remove noqa, ruff * Tidy up typing. * Fix bug picked up by pyright - looks like a fluke that it works? * Update python/dolfinx/fem/petsc.py Co-authored-by: Michal Habera <michal.habera@gmail.com> * Remove now obvious assign comments --------- Co-authored-by: Garth N. Wells <gnw20@cam.ac.uk> Co-authored-by: Jørgen Schartum Dokken <dokken92@gmail.com> Co-authored-by: Michal Habera <michal.habera@gmail.com>
1 parent 43dafba commit 4627a17

File tree

9 files changed

+260
-147
lines changed

9 files changed

+260
-147
lines changed

.github/workflows/windows.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ on:
1212
# merge_group:
1313
# branches:
1414
# - main
15-
# workflow_dispatch:
15+
workflow_dispatch:
1616

1717
jobs:
1818

cpp/dolfinx/la/petsc.cpp

+3-5
Original file line numberDiff line numberDiff line change
@@ -215,12 +215,10 @@ void la::petsc::scatter_local_vectors(
215215
int offset_ghost = offset_owned; // Ghost DoFs start after owned
216216
for (std::size_t i = 0; i < maps.size(); ++i)
217217
{
218-
const std::int32_t size_owned
219-
= maps[i].first.get().size_local() * maps[i].second;
220-
const std::int32_t size_ghost
221-
= maps[i].first.get().num_ghosts() * maps[i].second;
222-
218+
std::int32_t size_owned = maps[i].first.get().size_local() * maps[i].second;
223219
std::copy_n(x_b[i].begin(), size_owned, std::next(_x.begin(), offset));
220+
221+
std::int32_t size_ghost = maps[i].first.get().num_ghosts() * maps[i].second;
224222
std::copy_n(std::next(x_b[i].begin(), size_owned), size_ghost,
225223
std::next(_x.begin(), offset_ghost));
226224

python/demo/demo_mixed-poisson.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,9 @@
112112
import dolfinx.fem.petsc
113113
import ufl
114114
from basix.ufl import element
115-
from dolfinx import fem, la, mesh
115+
from dolfinx import fem, mesh
116116
from dolfinx.fem.petsc import discrete_gradient, interpolation_matrix
117+
from dolfinx.la.petsc import create_vector_wrap
117118
from dolfinx.mesh import CellType, create_unit_square
118119

119120
# Solution scalar (e.g., float32, complex128) and geometry (float32/64)
@@ -369,7 +370,7 @@
369370
# `u` solution (degree-of-freedom) vectors and solve.
370371

371372
# +
372-
x = PETSc.Vec().createNest([la.create_petsc_vector_wrap(sigma.x), la.create_petsc_vector_wrap(u.x)])
373+
x = PETSc.Vec().createNest([create_vector_wrap(sigma.x), create_vector_wrap(u.x)])
373374
ksp.solve(b, x)
374375
reason = ksp.getConvergedReason()
375376
assert reason > 0, f"Krylov solver has not converged {reason}."

python/demo/demo_stokes.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@
115115
)
116116
from dolfinx.fem.petsc import assemble_matrix_block, assemble_vector_block
117117
from dolfinx.io import XDMFFile
118+
from dolfinx.la.petsc import create_vector_wrap
118119
from dolfinx.mesh import CellType, create_rectangle, locate_entities_boundary
119120

120121
# We create a {py:class}`Mesh <dolfinx.mesh.Mesh>`, define functions for
@@ -291,7 +292,7 @@ def nested_iterative_solver():
291292
# space `Q`). The vectors for `u` and `p` are combined to form a
292293
# nested vector and the system is solved.
293294
u, p = Function(V), Function(Q)
294-
x = PETSc.Vec().createNest([la.create_petsc_vector_wrap(u.x), la.create_petsc_vector_wrap(p.x)])
295+
x = PETSc.Vec().createNest([create_vector_wrap(u.x), create_vector_wrap(p.x)])
295296
ksp.solve(b, x)
296297

297298
# Save solution to file in XDMF format for visualization, e.g. with

python/dolfinx/fem/petsc.py

+69-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (C) 2018-2022 Garth N. Wells and Jørgen S. Dokken
1+
# Copyright (C) 2018-2025 Garth N. Wells and Jørgen S. Dokken
22
#
33
# This file is part of DOLFINx (https://www.fenicsproject.org)
44
#
@@ -16,6 +16,7 @@
1616
import contextlib
1717
import functools
1818
import typing
19+
from collections.abc import Sequence
1920

2021
from petsc4py import PETSc
2122

@@ -27,8 +28,8 @@
2728
import numpy as np
2829

2930
import dolfinx.cpp as _cpp
31+
import dolfinx.la.petsc
3032
import ufl
31-
from dolfinx import la
3233
from dolfinx.cpp.fem import pack_coefficients as _pack_coefficients
3334
from dolfinx.cpp.fem import pack_constants as _pack_constants
3435
from dolfinx.cpp.fem.petsc import discrete_curl as _discrete_curl
@@ -42,7 +43,6 @@
4243
from dolfinx.fem.forms import form as _create_form
4344
from dolfinx.fem.function import Function as _Function
4445
from dolfinx.fem.function import FunctionSpace as _FunctionSpace
45-
from dolfinx.la import create_petsc_vector
4646

4747
__all__ = [
4848
"LinearProblem",
@@ -55,6 +55,7 @@
5555
"assemble_vector",
5656
"assemble_vector_block",
5757
"assemble_vector_nest",
58+
"assign",
5859
"create_matrix",
5960
"create_matrix_block",
6061
"create_matrix_nest",
@@ -118,7 +119,7 @@ def create_vector(L: Form) -> PETSc.Vec:
118119
A PETSc vector with a layout that is compatible with ``L``.
119120
"""
120121
dofmap = L.function_spaces[0].dofmaps(0)
121-
return create_petsc_vector(dofmap.index_map, dofmap.index_map_bs)
122+
return dolfinx.la.petsc.create_vector(dofmap.index_map, dofmap.index_map_bs)
122123

123124

124125
def create_vector_block(L: list[Form]) -> PETSc.Vec:
@@ -261,7 +262,7 @@ def assemble_vector(L: typing.Any, constants=None, coeffs=None) -> PETSc.Vec:
261262
Returns:
262263
An assembled vector.
263264
"""
264-
b = create_petsc_vector(
265+
b = dolfinx.la.petsc.create_vector(
265266
L.function_spaces[0].dofmaps(0).index_map, L.function_spaces[0].dofmaps(0).index_map_bs
266267
)
267268
with b.localForm() as b_local:
@@ -881,7 +882,7 @@ def __init__(
881882
else:
882883
self.u = u
883884

884-
self._x = la.create_petsc_vector_wrap(self.u.x)
885+
self._x = dolfinx.la.petsc.create_vector_wrap(self.u.x)
885886
self.bcs = bcs
886887

887888
self._solver = PETSc.KSP().create(self.u.function_space.mesh.comm)
@@ -939,12 +940,12 @@ def solve(self) -> _Function:
939940

940941
@property
941942
def L(self) -> Form:
942-
"""The compiled linear form"""
943+
"""The compiled linear form."""
943944
return self._L
944945

945946
@property
946947
def a(self) -> Form:
947-
"""The compiled bilinear form"""
948+
"""The compiled bilinear form."""
948949
return self._a
949950

950951
@property
@@ -1015,12 +1016,12 @@ def __init__(
10151016

10161017
@property
10171018
def L(self) -> Form:
1018-
"""Compiled linear form (the residual form)"""
1019+
"""The compiled linear form (the residual form)."""
10191020
return self._L
10201021

10211022
@property
10221023
def a(self) -> Form:
1023-
"""Compiled bilinear form (the Jacobian form)"""
1024+
"""The compiled bilinear form (the Jacobian form)."""
10241025
return self._a
10251026

10261027
def form(self, x: PETSc.Vec) -> None:
@@ -1118,3 +1119,61 @@ def interpolation_matrix(space0: _FunctionSpace, space1: _FunctionSpace) -> PETS
11181119
Interpolation matrix.
11191120
"""
11201121
return _interpolation_matrix(space0._cpp_object, space1._cpp_object)
1122+
1123+
1124+
@functools.singledispatch
1125+
def assign(u: typing.Union[_Function, Sequence[_Function]], x: PETSc.Vec):
1126+
"""Assign :class:`Function` degrees-of-freedom to a vector.
1127+
1128+
Assigns degree-of-freedom values in values of ``u``, which is possibly a
1129+
Sequence of ``Functions``s, to ``x``. When ``u`` is a Sequence of
1130+
``Function``s, degrees-of-freedom for the ``Function``s in ``u`` are
1131+
'stacked' and assigned to ``x``. See :func:`assign` for documentation on
1132+
how stacked assignment is handled.
1133+
1134+
Args:
1135+
u: ``Function`` (s) to assign degree-of-freedom value from.
1136+
x: Vector to assign degree-of-freedom values in ``u`` to.
1137+
"""
1138+
if x.getType() == PETSc.Vec.Type().NEST:
1139+
dolfinx.la.petsc.assign([v.x.array for v in u], x)
1140+
else:
1141+
if isinstance(u, Sequence):
1142+
data0, data1 = [], []
1143+
for v in u:
1144+
bs = v.function_space.dofmap.bs
1145+
n = v.function_space.dofmap.index_map.size_local
1146+
data0.append(v.x.array[: bs * n])
1147+
data1.append(v.x.array[bs * n :])
1148+
dolfinx.la.petsc.assign(data0 + data1, x)
1149+
else:
1150+
dolfinx.la.petsc.assign(u.x.array, x)
1151+
1152+
1153+
@assign.register(PETSc.Vec)
1154+
def _(x: PETSc.Vec, u: typing.Union[_Function, Sequence[_Function]]):
1155+
"""Assign vector entries to :class:`Function` degrees-of-freedom.
1156+
1157+
Assigns values in ``x`` to the degrees-of-freedom of ``u``, which is
1158+
possibly a Sequence of ``Function``s. When ``u`` is a Sequence of
1159+
``Function``s, values in ``x`` are assigned block-wise to the
1160+
``Function``s. See :func:`assign` for documentation on how blocked
1161+
assignment is handled.
1162+
1163+
Args:
1164+
x: Vector with values to assign values from.
1165+
u: ``Function`` (s) to assign degree-of-freedom values to.
1166+
"""
1167+
if x.getType() == PETSc.Vec.Type().NEST:
1168+
dolfinx.la.petsc.assign(x, [v.x.array for v in u])
1169+
else:
1170+
if isinstance(u, Sequence):
1171+
data0, data1 = [], []
1172+
for v in u:
1173+
bs = v.function_space.dofmap.bs
1174+
n = v.function_space.dofmap.index_map.size_local
1175+
data0.append(v.x.array[: bs * n])
1176+
data1.append(v.x.array[bs * n :])
1177+
dolfinx.la.petsc.assign(x, data0 + data1)
1178+
else:
1179+
dolfinx.la.petsc.assign(x, u.x.array)

python/dolfinx/la.py python/dolfinx/la/__init__.py

+9-52
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (C) 2017-2021 Garth N. Wells
1+
# Copyright (C) 2017-2025 Garth N. Wells, Jack S. Hale
22
#
33
# This file is part of DOLFINx (https://www.fenicsproject.org)
44
#
@@ -10,6 +10,7 @@
1010
import numpy as np
1111
import numpy.typing as npt
1212

13+
import dolfinx
1314
from dolfinx import cpp as _cpp
1415
from dolfinx.cpp.common import IndexMap
1516
from dolfinx.cpp.la import BlockMode, InsertMode, Norm
@@ -19,9 +20,9 @@
1920
"MatrixCSR",
2021
"Norm",
2122
"Vector",
22-
"create_petsc_vector",
2323
"is_orthonormal",
2424
"matrix_csr",
25+
"norm",
2526
"orthonormalize",
2627
"vector",
2728
]
@@ -93,8 +94,12 @@ def petsc_vec(self):
9394
When the object is destroyed it will destroy the underlying petsc4py
9495
vector automatically.
9596
"""
97+
assert dolfinx.has_petsc4py
98+
99+
from dolfinx.la.petsc import create_vector_wrap
100+
96101
if self._petsc_x is None:
97-
self._petsc_x = create_petsc_vector_wrap(self)
102+
self._petsc_x = create_vector_wrap(self)
98103
return self._petsc_x
99104

100105
def scatter_forward(self) -> None:
@@ -326,56 +331,8 @@ def vector(map, bs=1, dtype: npt.DTypeLike = np.float64) -> Vector:
326331
return Vector(vtype(map, bs))
327332

328333

329-
def create_petsc_vector_wrap(x: Vector):
330-
"""Wrap a distributed DOLFINx vector as a PETSc vector.
331-
332-
Note:
333-
Due to subtle issues in the interaction between petsc4py memory management
334-
and the Python garbage collector, it is recommended that the method ``PETSc.Vec.destroy()``
335-
is called on the returned object once the object is no longer required. Note that
336-
``PETSc.Vec.destroy()`` is collective over the object's MPI communicator.
337-
338-
Args:
339-
x: The vector to wrap as a PETSc vector.
340-
341-
Returns:
342-
A PETSc vector that shares data with ``x``.
343-
"""
344-
from petsc4py import PETSc
345-
346-
map = x.index_map
347-
ghosts = map.ghosts.astype(PETSc.IntType) # type: ignore
348-
bs = x.block_size
349-
size = (map.size_local * bs, map.size_global * bs)
350-
return PETSc.Vec().createGhostWithArray(ghosts, x.array, size=size, bsize=bs, comm=map.comm) # type: ignore
351-
352-
353-
def create_petsc_vector(map, bs: int):
354-
"""Create a distributed PETSc vector.
355-
356-
Note:
357-
Due to subtle issues in the interaction between petsc4py memory management
358-
and the Python garbage collector, it is recommended that the method ``PETSc.Vec.destroy()``
359-
is called on the returned object once the object is no longer required. Note that
360-
``PETSc.Vec.destroy()`` is collective over the object's MPI communicator.
361-
362-
Args:
363-
map: Index map that describes the size and parallel layout of
364-
the vector to create.
365-
bs: Block size of the vector.
366-
367-
Returns:
368-
PETSc Vec object.
369-
"""
370-
from petsc4py import PETSc
371-
372-
ghosts = map.ghosts.astype(PETSc.IntType) # type: ignore
373-
size = (map.size_local * bs, map.size_global * bs)
374-
return PETSc.Vec().createGhost(ghosts, size=size, bsize=bs, comm=map.comm) # type: ignore
375-
376-
377334
def orthonormalize(basis: list[Vector]):
378-
"""Orthogonalise set of PETSc vectors in-place."""
335+
"""Orthogonalise set of vectors in-place."""
379336
_cpp.la.orthonormalize([x._cpp_object for x in basis])
380337

381338

0 commit comments

Comments
 (0)