Skip to content

Template expressions with Field3DParallel#3430

Open
bendudson wants to merge 13 commits into
nextfrom
more-binaryexpr
Open

Template expressions with Field3DParallel#3430
bendudson wants to merge 13 commits into
nextfrom
more-binaryexpr

Conversation

@bendudson

@bendudson bendudson commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Enables expressions with Field3DParallel to be merged using templates.

  • If a BinaryExpr is assigned to a Field3D then no calculations are performed on the parallel slices and the result will not have parallel slices.
  • If a BinaryExpr is assigned to a Field3DParallel then if isFci() the parallel slices will be calculated. If CHECK >= 2 then the assignment will assert that it has slices.
  • scalars "broadcast" across slices.
  • The pow and floor functions are now lazy.
  • Eager arithmetic operators removed from generated_fieldops. In-place operators and FieldPerp operators are still generated (for now).

Unit tests taken from #3428 are passing.

ToDo:

  • Check that the BinaryExpr changes don't affect performance on GPUs, especially CUDA builds. yup() and ydown() members and Field3D pointers have been added.
  • Quantify performance change on CPU (optimized) builds.

bendudson and others added 6 commits July 9, 2026 13:48
This was an experiment in expression templates, that has now
been replaced by the BinaryExpr machinery.
Views handle yup/ydown slices, evaluating lazy expressions in parallel
slices if assigned to a Field3DParallel.
To make it reusable
Implement some clang-tidy suggestions, use FakeMeshFixtureFCI for
tests with FCI parallel transform.

@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

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

BOUT_HOST_DEVICE T operator()(int) const { return v; }
BOUT_HOST_DEVICE bool hasParallelSlices() const { return false; }
BOUT_HOST_DEVICE int numberParallelSlices() const { return 0; }
BOUT_HOST_DEVICE View yup(int = 0) const { return *this; }

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: all parameters should be named in a function [readability-named-parameter]

Suggested change
BOUT_HOST_DEVICE View yup(int = 0) const { return *this; }
BOUT_HOST_DEVICE View yup(int /*unused*/= 0) const { return *this; }

BOUT_HOST_DEVICE bool hasParallelSlices() const { return false; }
BOUT_HOST_DEVICE int numberParallelSlices() const { return 0; }
BOUT_HOST_DEVICE View yup(int = 0) const { return *this; }
BOUT_HOST_DEVICE View ydown(int = 0) const { return *this; }

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: all parameters should be named in a function [readability-named-parameter]

Suggested change
BOUT_HOST_DEVICE View ydown(int = 0) const { return *this; }
BOUT_HOST_DEVICE View ydown(int /*unused*/= 0) const { return *this; }

Comment thread include/bout/field2d.hxx
this->div = div;
return *this;
}
BOUT_HOST_DEVICE BOUT_FORCEINLINE bool hasParallelSlices() const { return false; }

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 'hasParallelSlices' can be made static [readability-convert-member-functions-to-static]

Suggested change
BOUT_HOST_DEVICE BOUT_FORCEINLINE bool hasParallelSlices() const { return false; }
BOUT_HOST_DEVICE static BOUT_FORCEINLINE bool hasParallelSlices() { return false; }

Comment thread include/bout/field2d.hxx
return *this;
}
BOUT_HOST_DEVICE BOUT_FORCEINLINE bool hasParallelSlices() const { return false; }
BOUT_HOST_DEVICE BOUT_FORCEINLINE int numberParallelSlices() const { return 0; }

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 'numberParallelSlices' can be made static [readability-convert-member-functions-to-static]

Suggested change
BOUT_HOST_DEVICE BOUT_FORCEINLINE int numberParallelSlices() const { return 0; }
BOUT_HOST_DEVICE static BOUT_FORCEINLINE int numberParallelSlices() { return 0; }

Comment thread include/bout/field2d.hxx
}
BOUT_HOST_DEVICE BOUT_FORCEINLINE bool hasParallelSlices() const { return false; }
BOUT_HOST_DEVICE BOUT_FORCEINLINE int numberParallelSlices() const { return 0; }
BOUT_HOST_DEVICE BOUT_FORCEINLINE View yup(int = 0) const { return *this; }

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: all parameters should be named in a function [readability-named-parameter]

Suggested change
BOUT_HOST_DEVICE BOUT_FORCEINLINE View yup(int = 0) const { return *this; }
BOUT_HOST_DEVICE BOUT_FORCEINLINE View yup(int /*unused*/= 0) const { return *this; }

Comment thread tests/unit/fake_mesh.hxx
// when we start using it.
//
// Can turn off the ability to do the transform. Should still be valid
class MockParallelTransform : public ParallelTransform {

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 'MockParallelTransform' defines a default 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 MockParallelTransform : public ParallelTransform {
      ^

Comment thread tests/unit/fake_mesh.hxx
// when we start using it.
//
// Can turn off the ability to do the transform. Should still be valid
class MockParallelTransform : public ParallelTransform {

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 "ParallelTransform" is directly included [misc-include-cleaner]

tests/unit/fake_mesh.hxx:2:

- #include <bout/boundary_region.hxx>
+ #include "bout/paralleltransform.hxx"
+ #include <bout/boundary_region.hxx>

Comment thread tests/unit/fake_mesh.hxx
public:
MockParallelTransform(Mesh& mesh, bool allow_transform_)
: ParallelTransform(mesh), allow_transform(allow_transform_) {}
~MockParallelTransform() = default;

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: annotate this function with 'override' or (rarely) 'final' [cppcoreguidelines-explicit-virtual-functions]

Suggested change
~MockParallelTransform() = default;
~MockParallelTransform() override = default;

Comment thread tests/unit/fake_mesh.hxx
: ParallelTransform(mesh), allow_transform(allow_transform_) {}
~MockParallelTransform() = default;

void calcParallelSlices(Field3D&) override {}

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: all parameters should be named in a function [readability-named-parameter]

Suggested change
void calcParallelSlices(Field3D&) override {}
void calcParallelSlices(Field3D& /*f*/) override {}

Comment thread tests/unit/fake_mesh.hxx

bool canToFromFieldAligned() const override { return allow_transform; }

bool requiresTwistShift(bool, YDirectionType) override { return false; }

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: all parameters should be named in a function [readability-named-parameter]

Suggested change
bool requiresTwistShift(bool, YDirectionType) override { return false; }
bool requiresTwistShift(bool /*twist_shift_enabled*/, YDirectionType /*ytype*/) override { return false; }

@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/field.hxx
namespace bout::detail {
template <typename T>
using UnaryFieldResult_t =
std::conditional_t<std::is_same_v<std::decay_t<T>, ::Field3DParallel>, ::Field3D,

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::conditional_t" is directly included [misc-include-cleaner]

include/bout/field.hxx:26:

- class Field;
+ #include <type_traits>
+ class Field;

Comment thread include/bout/field.hxx
namespace bout::detail {
template <typename T>
using UnaryFieldResult_t =
std::conditional_t<std::is_same_v<std::decay_t<T>, ::Field3DParallel>, ::Field3D,

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::decay_t" is directly included [misc-include-cleaner]

=
                                             ^

Comment thread tests/unit/fake_mesh.hxx

void checkInputGrid() override {}

Field3D fromFieldAligned(const Field3D& f, const std::string&) override {

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: all parameters should be named in a function [readability-named-parameter]

Suggested change
Field3D fromFieldAligned(const Field3D& f, const std::string&) override {
Field3D fromFieldAligned(const Field3D& f, const std::string& /*region*/) override {

Comment thread tests/unit/fake_mesh.hxx
return -f;
}

FieldPerp fromFieldAligned(const FieldPerp& f, const std::string&) override {

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: all parameters should be named in a function [readability-named-parameter]

Suggested change
FieldPerp fromFieldAligned(const FieldPerp& f, const std::string&) override {
FieldPerp fromFieldAligned(const FieldPerp& f, const std::string& /*region*/) override {

Comment thread tests/unit/fake_mesh.hxx
return -f;
}

Field3D toFieldAligned(const Field3D& f, const std::string&) override {

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: all parameters should be named in a function [readability-named-parameter]

Suggested change
Field3D toFieldAligned(const Field3D& f, const std::string&) override {
Field3D toFieldAligned(const Field3D& f, const std::string& /*region*/) override {

Comment thread tests/unit/fake_mesh.hxx
}
return -f;
}
FieldPerp toFieldAligned(const FieldPerp& f, const std::string&) override {

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: all parameters should be named in a function [readability-named-parameter]

Suggested change
FieldPerp toFieldAligned(const FieldPerp& f, const std::string&) override {
FieldPerp toFieldAligned(const FieldPerp& f, const std::string& /*region*/) override {

Comment thread tests/unit/fake_mesh.hxx
}

private:
const bool allow_transform;

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 'allow_transform' of type 'const bool' is const qualified [cppcoreguidelines-avoid-const-or-ref-data-members]

  const bool allow_transform;
             ^

If the parallel transform is not FCI then parallel slices are not
calculated on assignment to Field3DParallel.
Makes the `floor` function lazy, so it can be inlined into template
expressions.
@bendudson bendudson requested a review from dschwoerer July 10, 2026 17:58
Arithmetic operators now use the lazy `BinaryExpr` expressions.
Remove unnecessary data members that dublicate the Field3D::View base.

@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/field3d.hxx
static_cast<int>(numberParallelSlices())};
}
operator View() const {
return View{const_cast<BoutReal*>(&data[0]), yup_fields.data(), ydown_fields.data(),

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 const_cast to remove const qualifier [cppcoreguidelines-pro-type-const-cast]

    return View{const_cast<BoutReal*>(&data[0]), yup_fields.data(), ydown_fields.data(),
                ^

Comment thread include/bout/field3d.hxx
/// Not a DEVICE function because it dereferences a Field3D pointer
BOUT_FORCEINLINE View yup(int slice = 0) const {
ASSERT2(slice < base.num_parallel_slices);
ASSERT2(base.yup_fields[slice].isAllocated());

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: pointer arithmetic on polymorphic object of type 'Field3D' can result in undefined behavior if the dynamic type differs from the pointer type [bugprone-pointer-arithmetic-on-polymorphic-object]

      ASSERT2(base.yup_fields[slice].isAllocated());
              ^
Additional context

include/bout/assert.hxx:50: expanded from macro 'ASSERT2'

  if (!(condition)) {                                                                    \
        ^

Comment thread include/bout/field3d.hxx
BOUT_FORCEINLINE View yup(int slice = 0) const {
ASSERT2(slice < base.num_parallel_slices);
ASSERT2(base.yup_fields[slice].isAllocated());
return View{static_cast<Field3D::View>(base.yup_fields[slice])};

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: pointer arithmetic on polymorphic object of type 'Field3D' can result in undefined behavior if the dynamic type differs from the pointer type [bugprone-pointer-arithmetic-on-polymorphic-object]

      return View{static_cast<Field3D::View>(base.yup_fields[slice])};
                                             ^

Comment thread include/bout/field3d.hxx
/// Not a DEVICE function because it dereferences a Field3D pointer
BOUT_FORCEINLINE View ydown(int slice = 0) const {
ASSERT2(slice < base.num_parallel_slices);
ASSERT2(base.ydown_fields[slice].isAllocated());

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: pointer arithmetic on polymorphic object of type 'Field3D' can result in undefined behavior if the dynamic type differs from the pointer type [bugprone-pointer-arithmetic-on-polymorphic-object]

      ASSERT2(base.ydown_fields[slice].isAllocated());
              ^
Additional context

include/bout/assert.hxx:50: expanded from macro 'ASSERT2'

  if (!(condition)) {                                                                    \
        ^

Comment thread include/bout/field3d.hxx
BOUT_FORCEINLINE View ydown(int slice = 0) const {
ASSERT2(slice < base.num_parallel_slices);
ASSERT2(base.ydown_fields[slice].isAllocated());
return View{static_cast<Field3D::View>(base.ydown_fields[slice])};

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: pointer arithmetic on polymorphic object of type 'Field3D' can result in undefined behavior if the dynamic type differs from the pointer type [bugprone-pointer-arithmetic-on-polymorphic-object]

      return View{static_cast<Field3D::View>(base.ydown_fields[slice])};
                                             ^

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.

2 participants