Skip to content

Petsc matrix operators for FCI#3330

Open
bendudson wants to merge 50 commits into
nextfrom
petsc-operators
Open

Petsc matrix operators for FCI#3330
bendudson wants to merge 50 commits into
nextfrom
petsc-operators

Conversation

@bendudson

Copy link
Copy Markdown
Contributor

Reads matrix operators from the mesh file in CSR format, creating PETSc matrices that apply to Field3Ds. Mainly intended to implement FCI parallel operators as an alternative to yup/down communication.

This enables:

  1. Arbitrary parallelisation : PETSc handles the communication pattern, so there is no restriction on the field-line trajectories and processor boundaries. The mapping between mesh global indices and petsc global indices is handled automatically.
  2. Exact conservation of fluxes: Using the Support Operator Method to derive the divergence operator from the transpose of the gradient operator (as in e.g. GRILLIX) ensures that the numerical integral of the divergence of a flux cancels to numerical precision.
  3. Better preconditioning: The LU/MUMPS/STRUMPACK preconditioning used in the snes solver needs to know which cells are connected, so that PETSc can perform coloring and calculate the system Jacobian efficiently. Knowing the forward and backward matrices will enable this to be done.

There are a couple of downsides:

  1. Linearity: The operators are fixed matrices so are linear. Monotonic splines, for example, require nonlinearity.
  2. More communications: Each matrix-vector operation will perform communications. Perhaps there is a way to batch operations into some kind of composite matrix?

Read operators from the mesh in CSR format. PETSc handles
communication pattern for MPI decomposition.
Handles the mapping between mesh indices and PETSc global indices.
Simple wrapper functions around the Options functionality,
so that Array types can be read from the mesh file.
Reads row, column and weights from Mesh, constructs Petsc Mat
objects. Working on composition and operation on Field3D objects.
Map from Field3D, including yup/ydown boundaries, to PETSc Vec,
perform matrix-vector product, and map back to a Field3D.
Docstrings to explain PetscMapping, PetscOperator and PetscOperators
classes.

Extend `BOUT_DO_PETSC` macro to print error message, file and line
number.
Alternative to initializer_list constructor that avoids
confusion with e.g. `Array{2}` : Is it an array of size 2,
or an array of size 1 containing value 2?

`std::vector` uses `vector(2)` means size 2; `vector{2}`
means value 2.

With this commit, both `Array{2}` and `Array(2)` mean size 2;
`Array<T>::fromValues({2})` means size 1, value 2.  It's more verbose
but less prone to typos.
Some care needed with yup/down and yp/ym indices.
`diagonal` creates diagonal matrices from Field3D.

`transpose` creates a new matrix that is a transpose
of a PetscOperator. This is useful for converting
gradients into divergences.
Allows input operators to contain entries in boundary rows.
This is important so that the gradient and divergence operators
depend on boundary cell values.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

Comment thread include/bout/parallel_boundary_region.hxx
Comment thread include/bout/petsc_operators.hxx Outdated
/// The local index is the offset into the packed local PETSc vector.
template <typename Function>
void map_local_yup(Function func) const {
PetscInt row = evolving_region.size() + xin_region.size() + xout_region.size();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: narrowing conversion from 'unsigned int' to signed type 'PetscInt' (aka 'int') is implementation-defined [bugprone-narrowing-conversions]

    PetscInt row = evolving_region.size() + xin_region.size() + xout_region.size();
                   ^

/// The local index is the offset into the packed local PETSc vector.
template <typename Function>
void map_local_ydown(Function func) const {
PetscInt row = evolving_region.size() + xin_region.size() + xout_region.size()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: narrowing conversion from 'unsigned int' to signed type 'PetscInt' (aka 'int') is implementation-defined [bugprone-narrowing-conversions]

    PetscInt row = evolving_region.size() + xin_region.size() + xout_region.size()
                   ^

/// - applied to a `Field3D`;
/// - composed with other operators; and
/// - added or subtracted.
class PetscOperator {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: class 'PetscOperator' defines a destructor but does not define a copy constructor, a copy assignment operator, a move constructor or a move assignment operator [cppcoreguidelines-special-member-functions]

class PetscOperator {
      ^

Comment thread src/mesh/petsc_operators.cxx Outdated
Comment thread src/mesh/petsc_operators.cxx Outdated
Comment thread src/mesh/petsc_operators.cxx Outdated
int mesh_total_cells;
if (mesh->get(mesh_total_cells, "total_cells") == 0) {
// Check total number of cells
if (mesh_total_cells != mapping->size()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: comparison of integers of different signs: 'int' and 'unsigned int' [clang-diagnostic-sign-compare]

    if (mesh_total_cells != mapping->size()) {
                         ^

IMEXBDF2* s;
ierr = PCShellGetContext(pc, reinterpret_cast<void**>(&s));
CHKERRQ(ierr);
PetscCall(PCShellGetContext(pc, reinterpret_cast<void**>(&s)));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: do not use reinterpret_cast [cppcoreguidelines-pro-type-reinterpret-cast]

  PetscCall(PCShellGetContext(pc, reinterpret_cast<void**>(&s)));
                                  ^

IMEXBDF2* s;
ierr = PCShellGetContext(pc, reinterpret_cast<void**>(&s));
CHKERRQ(ierr);
PetscCall(PCShellGetContext(pc, reinterpret_cast<void**>(&s)));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: multilevel pointer conversion from 'void **' to 'void *', please use explicit cast [bugprone-multi-level-implicit-pointer-conversion]

  PetscCall(PCShellGetContext(pc, reinterpret_cast<void**>(&s)));
                                  ^

Not available unless configured with PETSc.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

Comment thread src/mesh/petsc_operators.cxx Outdated
return Region<Ind3D>(xout_indices);
}

PetscMapping::PetscMapping(const Field3D& cell_number, const Field3D& forward_cell_number,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: constructor does not initialize these fields: row_start, row_end, mat_mesh_to_petsc, mat_petsc_to_mesh [cppcoreguidelines-pro-type-member-init]

include/bout/petsc_operators.hxx:236:

-   PetscInt row_start, row_end;
+   PetscInt row_start{}, row_end{};

include/bout/petsc_operators.hxx:239:

-   Mat mat_mesh_to_petsc;
+   Mat mat_mesh_to_petsc{};

include/bout/petsc_operators.hxx:242:

-   Mat mat_petsc_to_mesh;
+   Mat mat_petsc_to_mesh{};

Comment thread src/mesh/petsc_operators.cxx Outdated
MatDestroy(&this->mat_petsc_to_mesh);
}

PetscOperator::PetscOperator(PetscMappingPtr mapping, Array<int> rows, Array<int> cols,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: constructor does not initialize these fields: mat_operator [cppcoreguidelines-pro-type-member-init]

include/bout/petsc_operators.hxx:335:

-   Mat mat_operator;
+   Mat mat_operator{};

Comment thread src/mesh/petsc_operators.cxx Outdated
BOUT_DO_PETSC(MatDestroy(&mat));
}

void PetscOperator::copyToVec(PetscMappingPtr mapping, const Field3D& f, Vec vec) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: the parameter 'mapping' is copied for each invocation but only used as a const reference; consider making it a const reference [performance-unnecessary-value-param]

Suggested change
void PetscOperator::copyToVec(PetscMappingPtr mapping, const Field3D& f, Vec vec) {
void PetscOperator::copyToVec(const PetscMappingPtr& mapping, const Field3D& f, Vec vec) {

include/bout/petsc_operators.hxx:345:

-   static void copyToVec(PetscMappingPtr mapping, const Field3D& f, Vec vec);
+   static void copyToVec(const PetscMappingPtr& mapping, const Field3D& f, Vec vec);

Comment thread src/mesh/petsc_operators.cxx Outdated
return PetscOperator(this->mapping, mat);
}

Field3D PetscOperators::meshGetField3D(Mesh* mesh, const std::string& name) const {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: method 'meshGetField3D' can be made static [readability-convert-member-functions-to-static]

Suggested change
Field3D PetscOperators::meshGetField3D(Mesh* mesh, const std::string& name) const {
Field3D PetscOperators::meshGetField3D(Mesh* mesh, const std::string& name) {

include/bout/petsc_operators.hxx:392:

-   Field3D meshGetField3D(Mesh* mesh, const std::string& name) const;
+   static Field3D meshGetField3D(Mesh* mesh, const std::string& name) ;

Check number of cells against global mapping size, not local size.
Handle non-standard CSR inputs:
- Negative indices for missing rows
- Missing final entry in rows array

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

Comment thread include/bout/petsc_operators.hxx Outdated
Comment thread include/bout/petsc_operators.hxx Outdated
Comment thread src/mesh/petsc_operators.cxx Outdated
Comment thread src/mesh/petsc_operators.cxx Outdated
Comment thread src/mesh/petsc_operators.cxx Outdated
Comment thread src/mesh/petsc_operators.cxx Outdated
Comment thread src/mesh/petsc_operators.cxx Outdated
Comment thread src/mesh/petsc_operators.cxx Outdated
Comment thread src/mesh/petsc_operators.cxx Outdated
Comment thread src/mesh/petsc_operators.cxx Outdated
Changes PetscMapping::size() to PetscMapping::localSize()
Defined in `bout/petsc_interface.hxx`, `bout::petsc::UniqueVec
and `UniqueMat` are `std::unique_ptr` wrappers around PETSc
Vec and Mat pointers, with custom deleters.

PetscOperator copy constructor and assignment deleted, move
assignment set to default. This enables some optimizations
to operate in-place on temporaries.
@ZedThree

Copy link
Copy Markdown
Member

This looks amazing, thanks @bendudson!

I've not finished going through this yet, but how much is this a replacement for GlobalIndexer and/or PetscMatrix/PetscVector? Or does it work together with them? It looks like this can only do Field3D, whereas GlobalIndexer can do the other fields too?

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

struct VecDeleter {
void operator()(Vec* vec_ptr) const {
VecDestroy(vec_ptr);
delete vec_ptr;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: deleting a pointer through a type that is not marked 'gsl::owner<>'; consider using a smart pointer instead [cppcoreguidelines-owning-memory]

    delete vec_ptr;
    ^
Additional context

include/bout/petsc_interface.hxx:67: variable declared here

  void operator()(Vec* vec_ptr) const {
                  ^

struct MatDeleter {
void operator()(Mat* mat_ptr) const {
MatDestroy(mat_ptr);
delete mat_ptr;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: deleting a pointer through a type that is not marked 'gsl::owner<>'; consider using a smart pointer instead [cppcoreguidelines-owning-memory]

    delete mat_ptr;
    ^
Additional context

include/bout/petsc_interface.hxx:80: variable declared here

  void operator()(Mat* mat_ptr) const {
                  ^

Comment thread src/mesh/petsc_operators.cxx Outdated
Comment thread src/mesh/petsc_operators.cxx Outdated
Comment thread src/mesh/petsc_operators.cxx Outdated
Comment thread src/mesh/petsc_operators.cxx Outdated
Comment thread src/mesh/petsc_operators.cxx Outdated
Shows how to use the PetscOperators, and compares against the
yup/ydown operators. Includes a script to generate the mesh,
which currently requires a branch of Zoidberg to run.

The code to calculate Grad_par, Div_par and Div_par_Grad_par from
forward and backward operators is now in
`PetscOperators::getParallel()`. That calculates and returns a
collection of `Parallel` operators.

This example could be turned into a test:
- `Grad_par` should be close between the two methods
- Volume integrals of `Div_par` and `Div_par_Grad_par` results should be
  near zero (flux conserved).
@bendudson

Copy link
Copy Markdown
Contributor Author

Thanks for going through this @ZedThree ! Sorry there's quite a lot, though most is self-contained. The implementation is independent of GlobalIndexer, PetscVector and PetscMatrix. Perhaps there is a way to reuse that machinery, but the way these operators are created is quite specific: The mesh generator assigns global indices to cells, including y boundary cells, and PetscMapping is calculating the mapping between the mesh global indices and PETSc's global indices that depend on the number of processors / domain decomposition.

I've added an example of how these operators are used (https://github.com/boutproject/BOUT-dev/pull/3330/changes#diff-d24773f587abce8257e893cfcfec54002aafaf875fb9cf93f54076b5c60ec7abR16). There is some amount of setup needed to calculate differential operators from the forward / backward maps, that I've put into a helper function that returns a struct of operators (https://github.com/boutproject/BOUT-dev/pull/3330/changes#diff-62e947100b5e999ca6d6d57a6f1281449adc7e2db9e8b7fc5e9273fea887ad88R302). Not sure this is the right API; suggestions welcome!

@ZedThree

Copy link
Copy Markdown
Member

The mesh generator assigns global indices to cells, including y boundary cells, and PetscMapping is calculating the mapping between the mesh global indices and PETSc's global indices that depend on the number of processors / domain decomposition.

I had thought that's essentially what GlobalIndexer was doing, so possibly I am misunderstanding either that or this! Maybe we can discuss at the next dev meeting?

@bendudson

bendudson commented Mar 18, 2026

Copy link
Copy Markdown
Contributor Author

The mesh generator assigns global indices to cells, including y boundary cells, and PetscMapping is calculating the mapping between the mesh global indices and PETSc's global indices that depend on the number of processors / domain decomposition.

I had thought that's essentially what GlobalIndexer was doing, so possibly I am misunderstanding either that or this! Maybe we can discuss at the next dev meeting?

The GlobalIndexer assigns a global index to each cell but doesn't map this to a mesh global index that is independent of the domain decompositions: Changing the number of processors will change the global indices. The key part of PetscMapping is the consistency of cell numbering in the mesh file, row and column indices in the CSR matrices for the operators.

@ZedThree

Copy link
Copy Markdown
Member

Ahhh, I see! Thanks!

This is a function rather than a PetscOperator because it
requires at least two matrix-vector products. This
implementation requires six Mat-Vec products that could probably be
optimized.
Trace multiple field lines per cell, to construct smoother maps.
A complication is cells that partially intersect a boundary.
Mesh not available in repo, only generation script.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

Comment thread include/bout/petsc_operators.hxx
@bendudson bendudson changed the title WIP: Petsc matrix operators for FCI Petsc matrix operators for FCI May 13, 2026
Since correctness depends on a combination of mesh generation
and BOUT++ implementation, this integrated test installs
Zoidberg in a virtual environment to generate a mesh.

Uses pytest for forward compatibility

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

Comment thread tests/integrated/test-petsc-operators/test_petsc_operators.cxx Outdated
Comment thread tests/integrated/test-petsc-operators/test_petsc_operators.cxx Outdated
bendudson and others added 10 commits June 1, 2026 14:51
Test passes locally, fails in CI.

One error occurs when allocating:
```
  	Option total_cells = 4828 (grid.fci.nc)
  	Option backward_cell_number = Tensor<BoutReal> (grid.fci.nc)
  	Option  backward_cell_number = Tensor<BoutReal> (grid.fci.nc)
  	Option forward_cell_number = Tensor<BoutReal> (grid.fci.nc)
  	Option  forward_cell_number = Tensor<BoutReal> (grid.fci.nc)
  	Option cell_number = Tensor<BoutReal> (grid.fci.nc)
  	Option  cell_number = Tensor<BoutReal> (grid.fci.nc)

  STDERR:
  terminate called after throwing an instance of 'std::bad_array_new_length'
    what():  std::bad_array_new_length
  [runnervm3jyl0:12838] *** Process received signal ***
  [runnervm3jyl0:12838] Signal: Aborted (6)
  [runnervm3jyl0:12838] Signal code:  (-6)
  malloc(): invalid size (unsorted)
```

A second error is due to missing `netCDF4` library
```
 FAILED test_petsc_operators.py::test_petsc_operators - ImportError:
 DataFile: No supported NetCDF modules available -- requires netCDF4
```

but netCDF4 seems to be installed:
```
Successfully installed boutdata-0.4.0 certifi-2026.5.20 cftime-1.6.5 contourpy-1.3.3 cycler-0.12.1 fonttools-4.63.0 fortranformat-2.0.3 freeqdsk-0.5.2 kiwisolver-1.5.0 matplotlib-3.10.9 mpmath-1.3.0 natsort-8.4.0 netCDF4-1.7.4 numpy-2.4.6 packaging-26.2 pillow-12.2.0 pyparsing-3.3.2 python-dateutil-2.9.0.post0 scipy-1.17.1 shapely-2.1.2 six-1.17.0 sympy-1.14.0 zoidberg-0.3.1.dev3+gfc5e428
```
Attempting to resolve netCDF4 issue
Trying to get stack trace on allocation failure.
Enclose PetscOperators in a scope that ends before BoutFinalise
is called, so that the last PetscLib destructor is called before
MPI_Finalize.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

Comment thread tests/integrated/test-petsc-operators/test_petsc_operators.cxx
Comment thread tests/integrated/test-petsc-operators/test_petsc_operators.cxx
@dschwoerer

Copy link
Copy Markdown
Contributor

I noticed we run 56 test in the "static, optimized case", including the test-petsc-operator, while fedora runs 52 + 8 + 1 - and the test-petsc-operator is not among them. It does get however build, just not run.

The Fedora CI run has 3 additional tests disabled, according to the configure stage:

-- Not building test test-laplace-hypre3d, requirement not met: BOUT_HAS_HYPRE
-- Not building test test-laplacexy2-hypre, requirement not met: BOUT_HAS_HYPRE
-- Not building test test-slepc-solver, requirement not met: BOUT_HAS_SLEPC

So on the non-fedora CI should have 64 tests run - so 8 tests are missing.
Taking into account that test-petsc-operator is missing on fedora, that makes at least 9 missing tests.

Any idea why some tests are not run @ZedThree ?

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

Ind3D _ind() const { return region->bndry_points[pos].index; }
signed char _boundary_width() const {
return region->localmesh->ystart - region->bndry_points[pos].abs_offset;
return region->localmesh->ystart - region->bndry_points[pos].abs_offset + 1;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: narrowing conversion from 'int' to signed type 'signed char' is implementation-defined [bugprone-narrowing-conversions]

{
             ^

Comment thread tests/integrated/test-petsc-operators/test_petsc_operators.cxx

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

Comment thread src/mesh/petsc_operators.cxx
@dschwoerer dschwoerer force-pushed the petsc-operators branch 2 times, most recently from 562e38c to 8019a94 Compare June 18, 2026 07:23

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

Comment thread include/bout/petsc_operators.hxx Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants