Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor LA into native and PETSc part and add assigners #3659

Merged
merged 51 commits into from
Mar 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
f697b85
Take directly from other branch.
jhale Feb 27, 2025
cd19cf0
Make implementations private.
jhale Feb 27, 2025
4815e19
Prototype dispatcher.
jhale Feb 27, 2025
c4d838e
Remove trailing comma
jhale Feb 27, 2025
887412b
Looser typing
jhale Feb 27, 2025
096980b
Polishing.
jhale Feb 27, 2025
a5abd65
Sketch function for assigning arrays of arrays to PETSc vectors
garth-wells Feb 28, 2025
9ec900a
Use update in tests
garth-wells Mar 1, 2025
e9f26eb
Parallel fix
garth-wells Mar 1, 2025
18c3b71
Remove ghost update
garth-wells Mar 1, 2025
159e40f
Merge branch 'main' into jhale/vec-func-copy-routines
garth-wells Mar 1, 2025
89f07fc
Remove ghost update that is called.
garth-wells Mar 1, 2025
f550e3a
Add reverse assign direction
garth-wells Mar 1, 2025
48afa7b
Consistent use of assign
garth-wells Mar 2, 2025
dc25bae
More simplifications
garth-wells Mar 2, 2025
5b9e352
Add assign from Function to Vec
garth-wells Mar 2, 2025
e7e5143
More simplification
garth-wells Mar 2, 2025
0f8b934
Work on docs
garth-wells Mar 2, 2025
e73bbf4
Doc fixes
garth-wells Mar 2, 2025
4c47db5
Merge branch 'main' into jhale/vec-func-copy-routines
garth-wells Mar 2, 2025
125890c
Merge branch 'main' into jhale/vec-func-copy-routines
garth-wells Mar 2, 2025
7464fe1
Merge branch 'main' into jhale/vec-func-copy-routines
garth-wells Mar 4, 2025
f6c0811
Update python/dolfinx/fem/petsc.py
jhale Mar 5, 2025
bf1bc3f
Update python/dolfinx/fem/petsc.py
jhale Mar 5, 2025
fb874e3
Update python/dolfinx/fem/petsc.py
jhale Mar 5, 2025
170670e
Update python/dolfinx/fem/petsc.py
jhale Mar 5, 2025
e03f232
Fix.
jhale Mar 5, 2025
3e91222
Tidy up typing.
jhale Mar 5, 2025
e78e7f6
Make la into a directory-based module - no API changes.
jhale Mar 5, 2025
f1ce783
Fix some tests.
jhale Mar 5, 2025
eeed728
Fix.
jhale Mar 5, 2025
8fe5730
Fix.
jhale Mar 5, 2025
383baab
Remove duplicate code
jhale Mar 5, 2025
831eb52
Add back workflow dispatch for Windows.
jhale Mar 5, 2025
7a673ba
Fix.
jhale Mar 5, 2025
8f6f956
Update __init__.py
jhale Mar 5, 2025
a9bd4fa
Don't understand docstring issue.
jhale Mar 5, 2025
c3c3b33
Merge branch 'jhale/refactor-la-petsc' of github.com:FEniCS/dolfinx i…
jhale Mar 5, 2025
b5004d4
Update petsc.py
jhale Mar 5, 2025
2e5fd20
Fix docstring ala Garth.
jhale Mar 5, 2025
4b140ec
Partial renaming.
jhale Mar 5, 2025
3a5349e
Refactor.
jhale Mar 5, 2025
77a3722
Fix up.
jhale Mar 5, 2025
ead511a
ruff
jhale Mar 5, 2025
8314922
Improve typing
jhale Mar 5, 2025
56289d2
Fix typo
jhale Mar 6, 2025
bb53b08
Guard typechecked definitions, remove noqa, ruff
jhale Mar 6, 2025
f237254
Tidy up typing.
jhale Mar 6, 2025
d6e7dec
Fix bug picked up by pyright - looks like a fluke that it works?
jhale Mar 6, 2025
acf57cf
Update python/dolfinx/fem/petsc.py
jhale Mar 6, 2025
4ae1801
Remove now obvious assign comments
jhale Mar 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ on:
# merge_group:
# branches:
# - main
# workflow_dispatch:
workflow_dispatch:

jobs:

Expand Down
8 changes: 3 additions & 5 deletions cpp/dolfinx/la/petsc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -215,12 +215,10 @@ void la::petsc::scatter_local_vectors(
int offset_ghost = offset_owned; // Ghost DoFs start after owned
for (std::size_t i = 0; i < maps.size(); ++i)
{
const std::int32_t size_owned
= maps[i].first.get().size_local() * maps[i].second;
const std::int32_t size_ghost
= maps[i].first.get().num_ghosts() * maps[i].second;

std::int32_t size_owned = maps[i].first.get().size_local() * maps[i].second;
std::copy_n(x_b[i].begin(), size_owned, std::next(_x.begin(), offset));

std::int32_t size_ghost = maps[i].first.get().num_ghosts() * maps[i].second;
std::copy_n(std::next(x_b[i].begin(), size_owned), size_ghost,
std::next(_x.begin(), offset_ghost));

Expand Down
5 changes: 3 additions & 2 deletions python/demo/demo_mixed-poisson.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,9 @@
import dolfinx.fem.petsc
import ufl
from basix.ufl import element
from dolfinx import fem, la, mesh
from dolfinx import fem, mesh
from dolfinx.fem.petsc import discrete_gradient, interpolation_matrix
from dolfinx.la.petsc import create_vector_wrap
from dolfinx.mesh import CellType, create_unit_square

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

# +
x = PETSc.Vec().createNest([la.create_petsc_vector_wrap(sigma.x), la.create_petsc_vector_wrap(u.x)])
x = PETSc.Vec().createNest([create_vector_wrap(sigma.x), create_vector_wrap(u.x)])
ksp.solve(b, x)
reason = ksp.getConvergedReason()
assert reason > 0, f"Krylov solver has not converged {reason}."
Expand Down
3 changes: 2 additions & 1 deletion python/demo/demo_stokes.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
)
from dolfinx.fem.petsc import assemble_matrix_block, assemble_vector_block
from dolfinx.io import XDMFFile
from dolfinx.la.petsc import create_vector_wrap
from dolfinx.mesh import CellType, create_rectangle, locate_entities_boundary

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

# Save solution to file in XDMF format for visualization, e.g. with
Expand Down
79 changes: 69 additions & 10 deletions python/dolfinx/fem/petsc.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (C) 2018-2022 Garth N. Wells and Jørgen S. Dokken
# Copyright (C) 2018-2025 Garth N. Wells and Jørgen S. Dokken
#
# This file is part of DOLFINx (https://www.fenicsproject.org)
#
Expand All @@ -16,6 +16,7 @@
import contextlib
import functools
import typing
from collections.abc import Sequence

from petsc4py import PETSc

Expand All @@ -27,8 +28,8 @@
import numpy as np

import dolfinx.cpp as _cpp
import dolfinx.la.petsc
import ufl
from dolfinx import la
from dolfinx.cpp.fem import pack_coefficients as _pack_coefficients
from dolfinx.cpp.fem import pack_constants as _pack_constants
from dolfinx.cpp.fem.petsc import discrete_curl as _discrete_curl
Expand All @@ -42,7 +43,6 @@
from dolfinx.fem.forms import form as _create_form
from dolfinx.fem.function import Function as _Function
from dolfinx.fem.function import FunctionSpace as _FunctionSpace
from dolfinx.la import create_petsc_vector

__all__ = [
"LinearProblem",
Expand All @@ -55,6 +55,7 @@
"assemble_vector",
"assemble_vector_block",
"assemble_vector_nest",
"assign",
"create_matrix",
"create_matrix_block",
"create_matrix_nest",
Expand Down Expand Up @@ -118,7 +119,7 @@ def create_vector(L: Form) -> PETSc.Vec:
A PETSc vector with a layout that is compatible with ``L``.
"""
dofmap = L.function_spaces[0].dofmaps(0)
return create_petsc_vector(dofmap.index_map, dofmap.index_map_bs)
return dolfinx.la.petsc.create_vector(dofmap.index_map, dofmap.index_map_bs)


def create_vector_block(L: list[Form]) -> PETSc.Vec:
Expand Down Expand Up @@ -261,7 +262,7 @@ def assemble_vector(L: typing.Any, constants=None, coeffs=None) -> PETSc.Vec:
Returns:
An assembled vector.
"""
b = create_petsc_vector(
b = dolfinx.la.petsc.create_vector(
L.function_spaces[0].dofmaps(0).index_map, L.function_spaces[0].dofmaps(0).index_map_bs
)
with b.localForm() as b_local:
Expand Down Expand Up @@ -881,7 +882,7 @@ def __init__(
else:
self.u = u

self._x = la.create_petsc_vector_wrap(self.u.x)
self._x = dolfinx.la.petsc.create_vector_wrap(self.u.x)
self.bcs = bcs

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

@property
def L(self) -> Form:
"""The compiled linear form"""
"""The compiled linear form."""
return self._L

@property
def a(self) -> Form:
"""The compiled bilinear form"""
"""The compiled bilinear form."""
return self._a

@property
Expand Down Expand Up @@ -1015,12 +1016,12 @@ def __init__(

@property
def L(self) -> Form:
"""Compiled linear form (the residual form)"""
"""The compiled linear form (the residual form)."""
return self._L

@property
def a(self) -> Form:
"""Compiled bilinear form (the Jacobian form)"""
"""The compiled bilinear form (the Jacobian form)."""
return self._a

def form(self, x: PETSc.Vec) -> None:
Expand Down Expand Up @@ -1118,3 +1119,61 @@ def interpolation_matrix(space0: _FunctionSpace, space1: _FunctionSpace) -> PETS
Interpolation matrix.
"""
return _interpolation_matrix(space0._cpp_object, space1._cpp_object)


@functools.singledispatch
def assign(u: typing.Union[_Function, Sequence[_Function]], x: PETSc.Vec):
"""Assign :class:`Function` degrees-of-freedom to a vector.

Assigns degree-of-freedom values in values of ``u``, which is possibly a
Sequence of ``Functions``s, to ``x``. When ``u`` is a Sequence of
``Function``s, degrees-of-freedom for the ``Function``s in ``u`` are
'stacked' and assigned to ``x``. See :func:`assign` for documentation on
how stacked assignment is handled.

Args:
u: ``Function`` (s) to assign degree-of-freedom value from.
x: Vector to assign degree-of-freedom values in ``u`` to.
"""
if x.getType() == PETSc.Vec.Type().NEST:
dolfinx.la.petsc.assign([v.x.array for v in u], x)
else:
if isinstance(u, Sequence):
data0, data1 = [], []
for v in u:
bs = v.function_space.dofmap.bs
n = v.function_space.dofmap.index_map.size_local
data0.append(v.x.array[: bs * n])
data1.append(v.x.array[bs * n :])
dolfinx.la.petsc.assign(data0 + data1, x)
else:
dolfinx.la.petsc.assign(u.x.array, x)


@assign.register(PETSc.Vec)
def _(x: PETSc.Vec, u: typing.Union[_Function, Sequence[_Function]]):
"""Assign vector entries to :class:`Function` degrees-of-freedom.

Assigns values in ``x`` to the degrees-of-freedom of ``u``, which is
possibly a Sequence of ``Function``s. When ``u`` is a Sequence of
``Function``s, values in ``x`` are assigned block-wise to the
``Function``s. See :func:`assign` for documentation on how blocked
assignment is handled.

Args:
x: Vector with values to assign values from.
u: ``Function`` (s) to assign degree-of-freedom values to.
"""
if x.getType() == PETSc.Vec.Type().NEST:
dolfinx.la.petsc.assign(x, [v.x.array for v in u])
else:
if isinstance(u, Sequence):
data0, data1 = [], []
for v in u:
bs = v.function_space.dofmap.bs
n = v.function_space.dofmap.index_map.size_local
data0.append(v.x.array[: bs * n])
data1.append(v.x.array[bs * n :])
dolfinx.la.petsc.assign(x, data0 + data1)
else:
dolfinx.la.petsc.assign(x, u.x.array)
61 changes: 9 additions & 52 deletions python/dolfinx/la.py → python/dolfinx/la/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (C) 2017-2021 Garth N. Wells
# Copyright (C) 2017-2025 Garth N. Wells, Jack S. Hale
#
# This file is part of DOLFINx (https://www.fenicsproject.org)
#
Expand All @@ -10,6 +10,7 @@
import numpy as np
import numpy.typing as npt

import dolfinx
from dolfinx import cpp as _cpp
from dolfinx.cpp.common import IndexMap
from dolfinx.cpp.la import BlockMode, InsertMode, Norm
Expand All @@ -19,9 +20,9 @@
"MatrixCSR",
"Norm",
"Vector",
"create_petsc_vector",
"is_orthonormal",
"matrix_csr",
"norm",
"orthonormalize",
"vector",
]
Expand Down Expand Up @@ -93,8 +94,12 @@ def petsc_vec(self):
When the object is destroyed it will destroy the underlying petsc4py
vector automatically.
"""
assert dolfinx.has_petsc4py

from dolfinx.la.petsc import create_vector_wrap

if self._petsc_x is None:
self._petsc_x = create_petsc_vector_wrap(self)
self._petsc_x = create_vector_wrap(self)
return self._petsc_x

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


def create_petsc_vector_wrap(x: Vector):
"""Wrap a distributed DOLFINx vector as a PETSc vector.

Note:
Due to subtle issues in the interaction between petsc4py memory management
and the Python garbage collector, it is recommended that the method ``PETSc.Vec.destroy()``
is called on the returned object once the object is no longer required. Note that
``PETSc.Vec.destroy()`` is collective over the object's MPI communicator.

Args:
x: The vector to wrap as a PETSc vector.

Returns:
A PETSc vector that shares data with ``x``.
"""
from petsc4py import PETSc

map = x.index_map
ghosts = map.ghosts.astype(PETSc.IntType) # type: ignore
bs = x.block_size
size = (map.size_local * bs, map.size_global * bs)
return PETSc.Vec().createGhostWithArray(ghosts, x.array, size=size, bsize=bs, comm=map.comm) # type: ignore


def create_petsc_vector(map, bs: int):
"""Create a distributed PETSc vector.

Note:
Due to subtle issues in the interaction between petsc4py memory management
and the Python garbage collector, it is recommended that the method ``PETSc.Vec.destroy()``
is called on the returned object once the object is no longer required. Note that
``PETSc.Vec.destroy()`` is collective over the object's MPI communicator.

Args:
map: Index map that describes the size and parallel layout of
the vector to create.
bs: Block size of the vector.

Returns:
PETSc Vec object.
"""
from petsc4py import PETSc

ghosts = map.ghosts.astype(PETSc.IntType) # type: ignore
size = (map.size_local * bs, map.size_global * bs)
return PETSc.Vec().createGhost(ghosts, size=size, bsize=bs, comm=map.comm) # type: ignore


def orthonormalize(basis: list[Vector]):
"""Orthogonalise set of PETSc vectors in-place."""
"""Orthogonalise set of vectors in-place."""
_cpp.la.orthonormalize([x._cpp_object for x in basis])


Expand Down
Loading
Loading