Skip to content

Improvements for hermitespline interpolation#3298

Merged
bendudson merged 67 commits into
nextfrom
hermitespline
Mar 11, 2026
Merged

Improvements for hermitespline interpolation#3298
bendudson merged 67 commits into
nextfrom
hermitespline

Conversation

@dschwoerer
Copy link
Copy Markdown
Contributor

Includes X-splitting also for monotonichermitespline and more.

For FCI we need to be able to access "random" data from the adjacent
slices. If they are split in x-direction, this requires some tricky
communication pattern.

It can be used like this:
```
// Create object
GlobalField3DAccess fci_comm(thismesh);
// let it know what data points will be required:
// where IndG3D is an index in the global field, which would be the
// normal Ind3D if there would be only one proc.
fci_comm.get(IndG3D(i, ny, nz));
// If all index have been added, the communication pattern will be
// established. This has to be called by all processors in parallel
fci_comm.setup()
// Once the data for a given field is needed, it needs to be
// communicated:
GlobalField3DAccessInstance global_data = fci_comm.communicate(f3d);
// and can be accessed like this
BoutReal data = global_data[IndG3D(i, ny, nz)];
// ny and nz in the IndG3D are always optional.
```
If they are two instances of the same template, this allows to have an
if in the inner loop that can be optimised out.
lower_bound takes into account the data is sorted
Ensures we all ways check for monotonicity
Tags were different for sender and receiver
Otherwise mpi might wait for the wrong request.
to avoid confusion whether the offsets are for sending or receiving
Using a local set for each thread ensures we do not need a mutex for
adding data, at the cost of having to merge the different sets later.
We want to skip sending if there is no data for this process ...
The result needs to be well understood, as the indices are a mix of
local and global indices, as we need to access non-local data.
@dschwoerer
Copy link
Copy Markdown
Contributor Author

Looks ok, but needs a lot more explanatory comments and docstrings. I can guess what a lot of things do, but I'd rather read a high-level overview first.

I have added some docs.

Lots of things need renaming too -- again, I can guess, but I'd really rather not. We have to think about new developers, are they going to be able to work out what xyzl or o_ids means?

I improved the naming

The template base class for the monotonic version is a good idea, but I still really don't like squashing together the PETSc, "new weights", and non-PETSc versions. It means three separate builds to test them all, rather than being able to test all of them in a single build. The interleaved preprocessor statements also make it much harder to understand what's going on.

I extended on the templating. Now all versions are templated.

Lastly, it would be good to make this already work for parallelised-Z so we have to do less work when that's ready. There's also already two other mechanisms for local-to-global mapping, GlobalIndexer and GlobalField (which I'm not sure is even used for anything seriously now), it would be good to understand if they could be used here at all.

I added an interface to the mesh, to do the mapping. If you think this should be reproduced here, I would need to know how you intend to do it.

All comments addressed, ready for re-review 👍

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

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

There were too many comments to post at once. Showing the first 25 out of 80. Check the log or trigger a new build to see more.

#endif

namespace {
enum class implementation_type { new_weights, petsc, legacy };
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: enum 'implementation_type' uses a larger base type ('int', size: 4 bytes) than necessary for its value set, consider using 'std::uint8_t' (1 byte) as the base type to reduce its size [performance-enum-size]

enum class implementation_type { new_weights, petsc, legacy };
           ^

PE_XIND = MYPE % NXPE;
PE_ZIND = 0;

ASSERT2(MYPE == getProcIndex(PE_XIND, PE_YIND, PE_ZIND));
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: no header providing "ASSERT2" is directly included [misc-include-cleaner]

  ASSERT2(MYPE == getProcIndex(PE_XIND, PE_YIND, PE_ZIND));
  ^

Comment thread src/mesh/impls/bout/boutmesh.cxx Outdated
}
}

int BoutMesh::getProcIndex(int X, int Y, int Z) { return Y * NXPE + X; }
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: '*' has higher precedence than '+'; add parentheses to explicitly specify the order of operations [readability-math-missing-parentheses]

Suggested change
int BoutMesh::getProcIndex(int X, int Y, int Z) { return Y * NXPE + X; }
int BoutMesh::getProcIndex(int X, int Y, int Z) { return (Y * NXPE) + X; }

Comment thread src/mesh/impls/bout/boutmesh.cxx Outdated
}
}

int BoutMesh::getProcIndex(int X, int Y, int Z) { return Y * NXPE + X; }
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 'getProcIndex' can be made const [readability-make-member-function-const]

Suggested change
int BoutMesh::getProcIndex(int X, int Y, int Z) { return Y * NXPE + X; }
int BoutMesh::getProcIndex(int X, int Y, int Z) const { return Y * NXPE + X; }

Comment thread src/mesh/impls/bout/boutmesh.cxx Outdated
}
}

int BoutMesh::getProcIndex(int X, int Y, int Z) { return Y * NXPE + X; }
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: out-of-line definition of 'getProcIndex' does not match any declaration in 'BoutMesh' [clang-diagnostic-error]

int BoutMesh::getProcIndex(int X, int Y, int Z) { return Y * NXPE + X; }
              ^
Additional context

src/mesh/impls/bout/boutmesh.hxx:18: BoutMesh defined here

class BoutMesh : public Mesh {
      ^

src/mesh/impls/bout/boutmesh.hxx:65: member declaration does not match because it is const qualified

  int getProcIndex(int X, int Y, int Z) const override;
      ^

#endif
if constexpr (monotonic) {
const auto gind =
gf3daccess->xyzg(i_corn, y + y_offset + y_global_offset, k_corner(x, y, z));
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: no member named 'xyzg' in 'GlobalField3DAccess' [clang-diagnostic-error]

          gf3daccess->xyzg(i_corn, y + y_offset + y_global_offset, k_corner(x, y, z));
                      ^

const BoutMask& mask,
const std::string& region) {
setMask(mask);
calcWeights(delta_x, delta_z, region);
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: no viable conversion from 'const std::string' (aka 'const basic_string') to 'const BoutMask' [clang-diagnostic-error]

  calcWeights(delta_x, delta_z, region);
                                ^
Additional context

src/mesh/interpolation/hermite_spline_xz.cxx:535: in instantiation of member function 'XZHermiteSplineBase<true, (anonymous namespace)::implementation_type::new_weights>::calcWeights' requested here

template class XZHermiteSplineBase<true, implementation_type::new_weights>;
               ^

include/bout/mask.hxx:47: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'const std::string' (aka 'const basic_string') to 'const BoutMask &' for 1st argument

class BoutMask {
      ^

include/bout/mask.hxx:47: candidate constructor (the implicit move constructor) not viable: no known conversion from 'const std::string' (aka 'const basic_string') to 'BoutMask &&' for 1st argument

class BoutMask {
      ^

include/bout/mask.hxx:55: explicit constructor is not a candidate

  explicit BoutMask(const Mesh& mesh, bool value = false)
           ^

include/bout/mask.hxx:57: explicit constructor is not a candidate

  explicit BoutMask(const Mesh* mesh = nullptr, bool value = false)
           ^

/usr/include/c++/15/bits/basic_string.h:1035: candidate function

      operator __sv_type() const noexcept
      ^

include/bout/interpolation_xz.hxx:209: passing argument to parameter 'mask' here

  void calcWeights(const Field3D& delta_x, const Field3D& delta_z, const BoutMask& mask,
                                                                                   ^

f_interp[iyp] += newWeights[w * 4 + 3][i] * f[ic.zp(2).xp(w - 1)];

if constexpr (imp_type == implementation_type::petsc) {
BoutReal* 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: variable 'ptr' is not initialized [cppcoreguidelines-init-variables]

Suggested change
BoutReal* ptr;
BoutReal* ptr = nullptr;


if constexpr (imp_type == implementation_type::petsc) {
BoutReal* ptr;
const BoutReal* cptr;
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: variable 'cptr' is not initialized [cppcoreguidelines-init-variables]

Suggested change
const BoutReal* cptr;
const BoutReal* cptr = nullptr;

if constexpr (monotonic) {
const auto iyp = i;
const auto i = iyp.ym(y_offset);
const auto corners = {(*gf)[IndG3D(g3dinds[i][0])], (*gf)[IndG3D(g3dinds[i][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: no header providing "IndG3D" is directly included [misc-include-cleaner]

        const auto corners = {(*gf)[IndG3D(g3dinds[i][0])], (*gf)[IndG3D(g3dinds[i][1])],
                                    ^

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

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

There were too many comments to post at once. Showing the first 25 out of 49. Check the log or trigger a new build to see more.

}
}

int BoutMesh::getProcIndex(int X, int Y, int Z) const { return Y * NXPE + X; }
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: '*' has higher precedence than '+'; add parentheses to explicitly specify the order of operations [readability-math-missing-parentheses]

Suggested change
int BoutMesh::getProcIndex(int X, int Y, int Z) const { return Y * NXPE + X; }
int BoutMesh::getProcIndex(int X, int Y, int Z) const { return (Y * NXPE) + X; }

}
}

int BoutMesh::getProcIndex(int X, int Y, int Z) const { return Y * NXPE + X; }
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: parameter 'Z' is unused [misc-unused-parameters]

Suggested change
int BoutMesh::getProcIndex(int X, int Y, int Z) const { return Y * NXPE + X; }
int BoutMesh::getProcIndex(int X, int Y, int /*Z*/) const { return Y * NXPE + X; }

}
}

int BoutMesh::getProcIndex(int X, int Y, int Z) const { return Y * NXPE + X; }
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: unused parameter 'Z' [clang-diagnostic-unused-parameter]

int BoutMesh::getProcIndex(int X, int Y, int Z) const { return Y * NXPE + X; }
                                             ^

if (localmesh->getNXPE() > 1) {
throw BoutException("Require PETSc for MPI splitting in X");
if constexpr (imp_type == implementation_type::petsc) {
petsclib = new PetscLib(
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: no header providing "PetscLib" is directly included [misc-include-cleaner]

src/mesh/interpolation/hermite_spline_xz.cxx:31:

+ #include "bout/petsclib.hxx"

const BoutMask& mask,
const std::string& region) {
setMask(mask);
calcWeights(delta_x, delta_z, region);
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: no viable conversion from 'const std::string' (aka 'const basic_string') to 'const BoutMask' [clang-diagnostic-error]

  calcWeights(delta_x, delta_z, region);
                                ^
Additional context

src/mesh/interpolation/hermite_spline_xz.cxx:537: in instantiation of member function 'XZHermiteSplineBase<true, (anonymous namespace)::implementation_type::new_weights>::calcWeights' requested here

template class XZHermiteSplineBase<true, implementation_type::new_weights>;
               ^

include/bout/mask.hxx:47: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'const std::string' (aka 'const basic_string') to 'const BoutMask &' for 1st argument

class BoutMask {
      ^

include/bout/mask.hxx:47: candidate constructor (the implicit move constructor) not viable: no known conversion from 'const std::string' (aka 'const basic_string') to 'BoutMask &&' for 1st argument

class BoutMask {
      ^

include/bout/mask.hxx:55: explicit constructor is not a candidate

  explicit BoutMask(const Mesh& mesh, bool value = false)
           ^

include/bout/mask.hxx:57: explicit constructor is not a candidate

  explicit BoutMask(const Mesh* mesh = nullptr, bool value = false)
           ^

/usr/include/c++/15/bits/basic_string.h:1035: candidate function

      operator __sv_type() const noexcept
      ^

include/bout/interpolation_xz.hxx:209: passing argument to parameter 'mask' here

  void calcWeights(const Field3D& delta_x, const Field3D& delta_z, const BoutMask& mask,
                                                                                   ^

Comment thread src/mesh/parallel/fci_comm.cxx Outdated
const auto piy = global2local_y.convert(gind.y());
const auto piz = global2local_z.convert(gind.z());
ASSERT3(piz.proc == 0);
toGet[mesh.getProcIndex(pix.proc, piy.index, piz.index)].push_back(
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: no member named 'index' in 'fci_comm::ProcLocal' [clang-diagnostic-error]

    toGet[mesh.getProcIndex(pix.proc, piy.index, piz.index)].push_back(
                                                     ^

int offset = 0;
for (const auto& get : toGet) {
getOffsets.push_back(offset);
offset += get.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 'size_type' (aka 'unsigned long') to signed type 'int' is implementation-defined [bugprone-narrowing-conversions]

      offset += get.size();
                ^

getOffsets.push_back(offset);
}
for (const auto id : ids) {
const IndG3D gind{id, global2local_y.getGlobalWith(), global2local_z.getGlobalWith()};
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: use of undeclared identifier 'global2local_y' [clang-diagnostic-error]

    const IndG3D gind{id, global2local_y.getGlobalWith(), global2local_z.getGlobalWith()};
                          ^

for (const auto id : ids) {
const IndG3D gind{id, global2local_y.getGlobalWith(), global2local_z.getGlobalWith()};
const auto pix = global2local_x.convert(gind.x());
const auto piy = global2local_y.convert(gind.y());
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: use of undeclared identifier 'global2local_y' [clang-diagnostic-error]

    const auto piy = global2local_y.convert(gind.y());
                     ^

Comment thread src/mesh/parallel/fci_comm.cxx Outdated
const auto piy = global2local_y.convert(gind.y());
const auto piz = global2local_z.convert(gind.z());
ASSERT3(piz.proc == 0);
const auto proc = mesh.getProcIndex(pix.proc, piy.index, piz.index);
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: member reference type 'Mesh *' is a pointer; did you mean to use '->'? [clang-diagnostic-error]

Suggested change
const auto proc = mesh.getProcIndex(pix.proc, piy.index, piz.index);
const auto proc = mesh->getProcIndex(pix.proc, piy.index, piz.index);

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

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

There were too many comments to post at once. Showing the first 25 out of 27. Check the log or trigger a new build to see more.

Comment thread include/bout/interpolation_xz.hxx
Comment thread include/bout/interpolation_xz.hxx
Comment thread include/bout/interpolation_xz.hxx
Comment thread src/mesh/interpolation/hermite_spline_xz.cxx
Comment thread src/mesh/interpolation/hermite_spline_xz.cxx
continue;
}
auto ret = MPI_Irecv(data.data() + getOffsets[proc], toGet[proc].size(), MPI_DOUBLE,
proc, 666, comm, reqs.data() + cnt1);
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 'size_t' (aka 'unsigned long') to signed type 'int' is implementation-defined [bugprone-narrowing-conversions]

                         proc, 666, comm, reqs.data() + cnt1);
                         ^

for (auto i : toSend[proc]) {
sendBuffer[cnt++] = f[Ind3D(i)];
}
auto ret = MPI_Send(start, toSend[proc].size(), MPI_DOUBLE, proc, 666, comm);
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 'size_t' (aka 'unsigned long') to signed type 'int' is implementation-defined [bugprone-narrowing-conversions]

    auto ret = MPI_Send(start, toSend[proc].size(), MPI_DOUBLE, proc, 666, comm);
                                                                ^

for (auto i : toSend[proc]) {
sendBuffer[cnt++] = f[Ind3D(i)];
}
auto ret = MPI_Send(start, toSend[proc].size(), MPI_DOUBLE, proc, 666, comm);
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 'size_type' (aka 'unsigned long') to signed type 'int' is implementation-defined [bugprone-narrowing-conversions]

    auto ret = MPI_Send(start, toSend[proc].size(), MPI_DOUBLE, proc, 666, comm);
                               ^

Comment thread src/mesh/parallel/fci_comm.hxx Outdated
Comment thread src/mesh/parallel/fci_comm.hxx Outdated
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

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

auto it = std::lower_bound(vec.begin(), vec.end(), tofind);
ASSERT3(it != vec.end());
ASSERT3(*it == tofind);
mapping[id] = std::distance(vec.begin(), it) + getOffsets[proc];
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 'typename iterator_traits<__gnu_cxx::__normal_iterator<const int *, std::vector<int, std::allocator>>>::difference_type' (aka 'long') to signed type 'mapped_type' (aka 'int') is implementation-defined [bugprone-narrowing-conversions]

    mapping[id] = std::distance(vec.begin(), it) + getOffsets[proc];
                  ^

auto it = std::lower_bound(vec.begin(), vec.end(), tofind);
ASSERT3(it != vec.end());
ASSERT3(*it == tofind);
mapping[id] = std::distance(vec.begin(), it) + getOffsets[proc];
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: no header providing "std::distance" is directly included [misc-include-cleaner]

src/mesh/parallel/fci_comm.cxx:33:

- #include <mpi.h>
+ #include <iterator>
+ #include <mpi.h>

fci_comm::GlobalToLocal1D global2local_z;

public:
fci_comm::XYZ2Ind<Ind3D> xyzlocal;
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: member variable 'xyzlocal' has public visibility [cppcoreguidelines-non-private-member-variables-in-classes]

  fci_comm::XYZ2Ind<Ind3D> xyzlocal;
                           ^


public:
fci_comm::XYZ2Ind<Ind3D> xyzlocal;
fci_comm::XYZ2Ind<IndG3D> xyzglobal;
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: member variable 'xyzglobal' has public visibility [cppcoreguidelines-non-private-member-variables-in-classes]

  fci_comm::XYZ2Ind<IndG3D> xyzglobal;
                            ^

@bendudson bendudson merged commit 568c70a into next Mar 11, 2026
32 of 33 checks passed
@bendudson bendudson deleted the hermitespline branch March 11, 2026 16:31
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.

3 participants