Template expressions with Field3DParallel#3430
Conversation
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.
| 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; } |
There was a problem hiding this comment.
warning: all parameters should be named in a function [readability-named-parameter]
| 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; } |
There was a problem hiding this comment.
warning: all parameters should be named in a function [readability-named-parameter]
| BOUT_HOST_DEVICE View ydown(int = 0) const { return *this; } | |
| BOUT_HOST_DEVICE View ydown(int /*unused*/= 0) const { return *this; } |
| this->div = div; | ||
| return *this; | ||
| } | ||
| BOUT_HOST_DEVICE BOUT_FORCEINLINE bool hasParallelSlices() const { return false; } |
There was a problem hiding this comment.
warning: method 'hasParallelSlices' can be made static [readability-convert-member-functions-to-static]
| BOUT_HOST_DEVICE BOUT_FORCEINLINE bool hasParallelSlices() const { return false; } | |
| BOUT_HOST_DEVICE static BOUT_FORCEINLINE bool hasParallelSlices() { return false; } |
| return *this; | ||
| } | ||
| BOUT_HOST_DEVICE BOUT_FORCEINLINE bool hasParallelSlices() const { return false; } | ||
| BOUT_HOST_DEVICE BOUT_FORCEINLINE int numberParallelSlices() const { return 0; } |
There was a problem hiding this comment.
warning: method 'numberParallelSlices' can be made static [readability-convert-member-functions-to-static]
| BOUT_HOST_DEVICE BOUT_FORCEINLINE int numberParallelSlices() const { return 0; } | |
| BOUT_HOST_DEVICE static BOUT_FORCEINLINE int numberParallelSlices() { return 0; } |
| } | ||
| 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; } |
There was a problem hiding this comment.
warning: all parameters should be named in a function [readability-named-parameter]
| 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; } |
| // when we start using it. | ||
| // | ||
| // Can turn off the ability to do the transform. Should still be valid | ||
| class MockParallelTransform : public ParallelTransform { |
There was a problem hiding this comment.
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 {
^| // when we start using it. | ||
| // | ||
| // Can turn off the ability to do the transform. Should still be valid | ||
| class MockParallelTransform : public ParallelTransform { |
There was a problem hiding this comment.
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>| public: | ||
| MockParallelTransform(Mesh& mesh, bool allow_transform_) | ||
| : ParallelTransform(mesh), allow_transform(allow_transform_) {} | ||
| ~MockParallelTransform() = default; |
There was a problem hiding this comment.
warning: annotate this function with 'override' or (rarely) 'final' [cppcoreguidelines-explicit-virtual-functions]
| ~MockParallelTransform() = default; | |
| ~MockParallelTransform() override = default; |
| : ParallelTransform(mesh), allow_transform(allow_transform_) {} | ||
| ~MockParallelTransform() = default; | ||
|
|
||
| void calcParallelSlices(Field3D&) override {} |
There was a problem hiding this comment.
warning: all parameters should be named in a function [readability-named-parameter]
| void calcParallelSlices(Field3D&) override {} | |
| void calcParallelSlices(Field3D& /*f*/) override {} |
|
|
||
| bool canToFromFieldAligned() const override { return allow_transform; } | ||
|
|
||
| bool requiresTwistShift(bool, YDirectionType) override { return false; } |
There was a problem hiding this comment.
warning: all parameters should be named in a function [readability-named-parameter]
| bool requiresTwistShift(bool, YDirectionType) override { return false; } | |
| bool requiresTwistShift(bool /*twist_shift_enabled*/, YDirectionType /*ytype*/) override { return false; } |
More unit tests
| namespace bout::detail { | ||
| template <typename T> | ||
| using UnaryFieldResult_t = | ||
| std::conditional_t<std::is_same_v<std::decay_t<T>, ::Field3DParallel>, ::Field3D, |
There was a problem hiding this comment.
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;| namespace bout::detail { | ||
| template <typename T> | ||
| using UnaryFieldResult_t = | ||
| std::conditional_t<std::is_same_v<std::decay_t<T>, ::Field3DParallel>, ::Field3D, |
There was a problem hiding this comment.
warning: no header providing "std::decay_t" is directly included [misc-include-cleaner]
=
^|
|
||
| void checkInputGrid() override {} | ||
|
|
||
| Field3D fromFieldAligned(const Field3D& f, const std::string&) override { |
There was a problem hiding this comment.
warning: all parameters should be named in a function [readability-named-parameter]
| Field3D fromFieldAligned(const Field3D& f, const std::string&) override { | |
| Field3D fromFieldAligned(const Field3D& f, const std::string& /*region*/) override { |
| return -f; | ||
| } | ||
|
|
||
| FieldPerp fromFieldAligned(const FieldPerp& f, const std::string&) override { |
There was a problem hiding this comment.
warning: all parameters should be named in a function [readability-named-parameter]
| FieldPerp fromFieldAligned(const FieldPerp& f, const std::string&) override { | |
| FieldPerp fromFieldAligned(const FieldPerp& f, const std::string& /*region*/) override { |
| return -f; | ||
| } | ||
|
|
||
| Field3D toFieldAligned(const Field3D& f, const std::string&) override { |
There was a problem hiding this comment.
warning: all parameters should be named in a function [readability-named-parameter]
| Field3D toFieldAligned(const Field3D& f, const std::string&) override { | |
| Field3D toFieldAligned(const Field3D& f, const std::string& /*region*/) override { |
| } | ||
| return -f; | ||
| } | ||
| FieldPerp toFieldAligned(const FieldPerp& f, const std::string&) override { |
There was a problem hiding this comment.
warning: all parameters should be named in a function [readability-named-parameter]
| FieldPerp toFieldAligned(const FieldPerp& f, const std::string&) override { | |
| FieldPerp toFieldAligned(const FieldPerp& f, const std::string& /*region*/) override { |
| } | ||
|
|
||
| private: | ||
| const bool allow_transform; |
There was a problem hiding this comment.
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.
Arithmetic operators now use the lazy `BinaryExpr` expressions.
Remove unnecessary data members that dublicate the Field3D::View base.
| static_cast<int>(numberParallelSlices())}; | ||
| } | ||
| operator View() const { | ||
| return View{const_cast<BoutReal*>(&data[0]), yup_fields.data(), ydown_fields.data(), |
There was a problem hiding this comment.
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(),
^| /// 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()); |
There was a problem hiding this comment.
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)) { \
^| 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])}; |
There was a problem hiding this comment.
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])};
^| /// 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()); |
There was a problem hiding this comment.
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)) { \
^| 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])}; |
There was a problem hiding this comment.
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])};
^
Enables expressions with
Field3DParallelto be merged using templates.BinaryExpris assigned to aField3Dthen no calculations are performed on the parallel slices and the result will not have parallel slices.BinaryExpris assigned to aField3DParallelthen ifisFci()the parallel slices will be calculated. IfCHECK >= 2then the assignment will assert that it has slices.powandfloorfunctions are now lazy.generated_fieldops. In-place operators andFieldPerpoperators are still generated (for now).Unit tests taken from #3428 are passing.
ToDo:
BinaryExprchanges don't affect performance on GPUs, especially CUDA builds.yup()andydown()members and Field3D pointers have been added.