Skip to content
38 changes: 20 additions & 18 deletions include/bout/field.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -630,17 +630,17 @@ std::optional<int> getPerpYIndex(const BinaryExpr<ResT, L, R, Func>& expr) {
#ifdef FIELD_FUNC
#error This macro has already been defined
#else
#define FIELD_FUNC(name, func) \
#define FIELD_FUNC(opname, func) \
namespace bout::op { \
struct name { \
struct opname { \
template <typename LView, typename RView> \
BOUT_HOST_DEVICE BoutReal operator()(int idx, const LView& L, const RView&) const { \
return func(L(idx)); \
} \
}; \
}; \
template <typename T, typename = bout::utils::EnableIfField<T>> \
inline auto name(const T& f, const std::string& rgn = "RGN_ALL") { \
inline auto opname(const T& f, const std::string& rgn = "RGN_ALL") { \
if constexpr (std::is_same_v<T, Field3DParallel>) { \
/* Check if the input is allocated */ \
checkData(f); \
Expand All @@ -651,28 +651,28 @@ std::optional<int> getPerpYIndex(const BinaryExpr<ResT, L, R, Func>& expr) {
result.yup(i) = func(f.yup(i)); \
result.ydown(i) = func(f.ydown(i)); \
} \
result.name = std::string(#name "(") + f.name + std::string(")"); \
result.name = fmt::format(#opname "({})", f.name); \
checkData(result); \
return result; \
} else { \
return BinaryExpr<T, T, T, bout::op::name>{static_cast<typename T::View>(f), \
static_cast<typename T::View>(f), \
bout::op::name{}, \
f.getMesh(), \
f.getLocation(), \
f.getDirections(), \
std::nullopt, \
f.getRegion(rgn), \
bout::detail::getPerpYIndex(f)}; \
return BinaryExpr<T, T, T, bout::op::opname>{static_cast<typename T::View>(f), \
static_cast<typename T::View>(f), \
bout::op::opname{}, \
f.getMesh(), \
f.getLocation(), \
f.getDirections(), \
std::nullopt, \
f.getRegion(rgn), \
bout::detail::getPerpYIndex(f)}; \
} \
} \
template <typename ResT, typename L, typename R, typename Func> \
inline auto name(const BinaryExpr<ResT, L, R, Func>& f) { \
inline auto opname(const BinaryExpr<ResT, L, R, Func>& f) { \
return BinaryExpr<ResT, BinaryExpr<ResT, L, R, Func>, BinaryExpr<ResT, L, R, Func>, \
bout::op::name>{ \
bout::op::opname>{ \
static_cast<typename BinaryExpr<ResT, L, R, Func>::View>(f), \
static_cast<typename BinaryExpr<ResT, L, R, Func>::View>(f), \
bout::op::name{}, \
bout::op::opname{}, \
f.getMesh(), \
f.getLocation(), \
f.getDirections(), \
Expand All @@ -681,8 +681,8 @@ std::optional<int> getPerpYIndex(const BinaryExpr<ResT, L, R, Func>& expr) {
bout::detail::getPerpYIndex(f)}; \
} \
template <typename ResT, typename L, typename R, typename Func> \
inline auto name(const BinaryExpr<ResT, L, R, Func>& f, const std::string& rgn) { \
return name(ResT{f}, rgn); \
inline auto opname(const BinaryExpr<ResT, L, R, Func>& f, const std::string& rgn) { \
return opname(ResT{f}, rgn); \
}
#endif

Expand All @@ -709,7 +709,9 @@ inline auto SQ(const T& f, const std::string& rgn = "RGN_ALL") {
result.yup(i) = SQ(f.yup(i), rgn);
result.ydown(i) = SQ(f.ydown(i), rgn);
}
#if TRACK
result.name = std::string("SQ(") + f.name + std::string(")");
#endif
checkData(result);
return result;
} else {
Expand Down
168 changes: 85 additions & 83 deletions include/bout/field3d.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,88 @@ protected:
}
};

/// Field3DParallel is intended to behave like Field3D, but preserve parallel
/// Fields.
/// Operations on Field3D, like multiplication, exp and floor only work on the
/// "main" field, Field3DParallel will retain the parallel slices.
class Field3DParallel : public Field3D {
public:
template <class... Types>
explicit Field3DParallel(Types... args) : Field3D(std::move(args)...) {
ensureFieldAligned();
}
Field3DParallel(const Field3D& f) : Field3D(f) { ensureFieldAligned(); }
Field3DParallel(const Field3D& f, bool isRef) : Field3D(f), isRef(isRef) {
ensureFieldAligned();
}
Field3DParallel(const Field2D& f) : Field3D(f) { ensureFieldAligned(); }
// Explicitly needed, as DirectionTypes is sometimes constructed from a
// brace enclosed list
explicit Field3DParallel(Mesh* localmesh = nullptr, CELL_LOC location_in = CELL_CENTRE,
DirectionTypes directions_in = {YDirectionType::Standard,
ZDirectionType::Standard},
std::optional<size_t> regionID = {})
: Field3D(localmesh, location_in, directions_in, regionID) {
if (isFci()) {
splitParallelSlices();
}
ensureFieldAligned();
}
explicit Field3DParallel(Array<BoutReal> data, Mesh* localmesh,
CELL_LOC location = CELL_CENTRE,
DirectionTypes directions_in = {YDirectionType::Standard,
ZDirectionType::Standard})
: Field3D(std::move(data), localmesh, location, directions_in) {
ensureFieldAligned();
}
explicit Field3DParallel(BoutReal, Mesh* mesh = nullptr);
Field3D& asField3D() { return *this; }
const Field3D& asField3D() const { return *this; }

Field3DParallel& operator*=(const Field3D&);
Field3DParallel& operator/=(const Field3D&);
Field3DParallel& operator+=(const Field3D&);
Field3DParallel& operator-=(const Field3D&);
Field3DParallel& operator*=(const Field3DParallel&);
Field3DParallel& operator/=(const Field3DParallel&);
Field3DParallel& operator+=(const Field3DParallel&);
Field3DParallel& operator-=(const Field3DParallel&);
Field3DParallel& operator*=(BoutReal);
Field3DParallel& operator/=(BoutReal);
Field3DParallel& operator+=(BoutReal);
Field3DParallel& operator-=(BoutReal);
Field3DParallel& operator=(const Field3D& rhs) {
Field3D::operator=(rhs);
ensureFieldAligned();
return *this;
}
Field3DParallel& operator=(Field3D&& rhs) {
Field3D::operator=(std::move(rhs));
ensureFieldAligned();
return *this;
}
Field3DParallel& operator=(BoutReal);
Field3DParallel& allocate();

private:
void ensureFieldAligned();
bool isRef{false};
};

// We need checkData for templates
#if CHECK > 0
/// Throw an exception if \p f is not allocated or if any
/// elements are non-finite (for CHECK > 2).
/// Loops over all points including the boundaries by
/// default (can be changed using the \p rgn argument
void checkData(const Field3D& f, const std::string& region = "RGN_NOBNDRY");
#else
/// Ignored with disabled CHECK; Throw an exception if \p f is not
/// allocated or if any elements are non-finite (for CHECK > 2)
inline void checkData([[maybe_unused]] const Field3D& f,
[[maybe_unused]] const std::string& region = "RGN_NOBNDRY") {};
#endif

// Non-member overloaded operators

template <typename T>
Expand All @@ -698,9 +780,10 @@ FieldPerp operator/(const Field3D& lhs, const FieldPerp& rhs);
#define FIELD3D_FIELD3D_FIELD3D_OP(OP_SYM, OP_TYPE) \
template <typename L, typename R, \
typename = std::enable_if_t<is_expr_field3d_v<L> && is_expr_field3d_v<R>>> \
BinaryExpr<Field3D, L, R, bout::op::OP_TYPE> operator OP_SYM(const L& lhs, \
const R& rhs) { \
auto operator OP_SYM(const L& lhs, const R& rhs) { \
ASSERT1_EXPR_COMPATIBLE(lhs, rhs); \
static_assert(!std::is_same_v<L, Field3DParallel>); \
static_assert(!std::is_same_v<R, Field3DParallel>); \
Comment on lines 784 to +786

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Concepts might help here

auto regionID = \
lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID()); \
return BinaryExpr<Field3D, L, R, bout::op::OP_TYPE>{ \
Expand Down Expand Up @@ -911,19 +994,6 @@ Field3D pow(const Field3D& lhs, const Field2D& rhs, const std::string& rgn = "RG
FieldPerp pow(const Field3D& lhs, const FieldPerp& rhs,
const std::string& rgn = "RGN_ALL");

#if CHECK > 0
/// Throw an exception if \p f is not allocated or if any
/// elements are non-finite (for CHECK > 2).
/// Loops over all points including the boundaries by
/// default (can be changed using the \p rgn argument
void checkData(const Field3D& f, const std::string& region = "RGN_NOBNDRY");
#else
/// Ignored with disabled CHECK; Throw an exception if \p f is not
/// allocated or if any elements are non-finite (for CHECK > 2)
inline void checkData([[maybe_unused]] const Field3D& f,
[[maybe_unused]] const std::string& region = "RGN_NOBNDRY") {};
#endif

/// Fourier filtering, removes all except one mode
///
/// @param[in] var Variable to apply filter to
Expand Down Expand Up @@ -1014,74 +1084,6 @@ inline Field3D copy(const Field3D& f) {
return result;
}

/// Field3DParallel is intended to behave like Field3D, but preserve parallel
/// Fields.
/// Operations on Field3D, like multiplication, exp and floor only work on the
/// "main" field, Field3DParallel will retain the parallel slices.
class Field3DParallel : public Field3D {
public:
template <class... Types>
explicit Field3DParallel(Types... args) : Field3D(std::move(args)...) {
ensureFieldAligned();
}
Field3DParallel(const Field3D& f) : Field3D(f) { ensureFieldAligned(); }
Field3DParallel(const Field3D& f, bool isRef) : Field3D(f), isRef(isRef) {
ensureFieldAligned();
}
Field3DParallel(const Field2D& f) : Field3D(f) { ensureFieldAligned(); }
// Explicitly needed, as DirectionTypes is sometimes constructed from a
// brace enclosed list
explicit Field3DParallel(Mesh* localmesh = nullptr, CELL_LOC location_in = CELL_CENTRE,
DirectionTypes directions_in = {YDirectionType::Standard,
ZDirectionType::Standard},
std::optional<size_t> regionID = {})
: Field3D(localmesh, location_in, directions_in, regionID) {
if (isFci()) {
splitParallelSlices();
}
ensureFieldAligned();
}
explicit Field3DParallel(Array<BoutReal> data, Mesh* localmesh,
CELL_LOC location = CELL_CENTRE,
DirectionTypes directions_in = {YDirectionType::Standard,
ZDirectionType::Standard})
: Field3D(std::move(data), localmesh, location, directions_in) {
ensureFieldAligned();
}
explicit Field3DParallel(BoutReal, Mesh* mesh = nullptr);
Field3D& asField3D() { return *this; }
const Field3D& asField3D() const { return *this; }

Field3DParallel& operator*=(const Field3D&);
Field3DParallel& operator/=(const Field3D&);
Field3DParallel& operator+=(const Field3D&);
Field3DParallel& operator-=(const Field3D&);
Field3DParallel& operator*=(const Field3DParallel&);
Field3DParallel& operator/=(const Field3DParallel&);
Field3DParallel& operator+=(const Field3DParallel&);
Field3DParallel& operator-=(const Field3DParallel&);
Field3DParallel& operator*=(BoutReal);
Field3DParallel& operator/=(BoutReal);
Field3DParallel& operator+=(BoutReal);
Field3DParallel& operator-=(BoutReal);
Field3DParallel& operator=(const Field3D& rhs) {
Field3D::operator=(rhs);
ensureFieldAligned();
return *this;
}
Field3DParallel& operator=(Field3D&& rhs) {
Field3D::operator=(std::move(rhs));
ensureFieldAligned();
return *this;
}
Field3DParallel& operator=(BoutReal);
Field3DParallel& allocate();

private:
void ensureFieldAligned();
bool isRef{false};
};

Field3DParallel Field3D::asField3DParallel() {
if (isAllocated()) {
allocate();
Expand Down
2 changes: 1 addition & 1 deletion include/bout/fieldops.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ struct is_expr_fieldperp : std::false_type {};
template <>
struct is_expr_fieldperp<FieldPerp> : std::true_type {};

// Helper variable template
template <typename T>
inline constexpr bool is_expr_fieldperp_v = is_expr_fieldperp<std::decay_t<T>>::value;

// Helper variable template
template <typename T>
inline constexpr bool is_expr_field3d_v = is_expr_field3d<std::decay_t<T>>::value;

Expand Down
59 changes: 59 additions & 0 deletions tests/unit/fake_mesh.hxx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include "bout/paralleltransform.hxx"
#include <bout/boundary_region.hxx>
#include <bout/boundary_region_iter.hxx>
#include <bout/bout_types.hxx>
Expand Down Expand Up @@ -366,3 +367,61 @@ public:
private:
Options values; ///< Store values to be returned by get()
};

// A mock ParallelTransform to test transform_from_field_aligned
// property of FieldFactory. For now, the transform just returns the
// negative of the input. Ideally, this will get moved to GoogleMock
// when we start using it.
//
// Can turn off the ability to do the transform. Should still be valid
class MockParallelTransform : public ParallelTransform {
Comment thread
ZedThree marked this conversation as resolved.
Comment thread
dschwoerer marked this conversation as resolved.
public:
MockParallelTransform(Mesh& mesh, bool allow_transform_)
: ParallelTransform(mesh), allow_transform(allow_transform_) {}
MockParallelTransform(const MockParallelTransform&) = delete;
MockParallelTransform& operator=(const MockParallelTransform&) = delete;
MockParallelTransform(MockParallelTransform&& other) = delete;
MockParallelTransform& operator=(MockParallelTransform&& other) = delete;
~MockParallelTransform() override = default;

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

bool canToFromFieldAligned() const override { return allow_transform; }

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

void checkInputGrid() override {}

Field3D fromFieldAligned(const Field3D& f, const std::string& /*region*/) override {
if (f.getDirectionY() != YDirectionType::Aligned) {
throw BoutException("Unaligned field passed to fromFieldAligned");
}
return -f;
}

FieldPerp fromFieldAligned(const FieldPerp& f, const std::string& /*region*/) override {
if (f.getDirectionY() != YDirectionType::Aligned) {
throw BoutException("Unaligned field passed to fromFieldAligned");
}
return -f;
}

Field3D toFieldAligned(const Field3D& f, const std::string& /*region*/) override {
if (f.getDirectionY() != YDirectionType::Standard) {
throw BoutException("Aligned field passed to toFieldAligned");
}
return -f;
}
FieldPerp toFieldAligned(const FieldPerp& f, const std::string& /*region*/) override {
if (f.getDirectionY() != YDirectionType::Standard) {
throw BoutException("Aligned field passed to toFieldAligned");
}
return -f;
}

private:
bool allow_transform;
};
7 changes: 6 additions & 1 deletion tests/unit/fake_mesh_fixture.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
/// Use this template class directly to use different sized grid:
///
/// using MyTest = FakeMeshFixture_tmpl<7, 9, 11>;
template <int NX, int NY, int NZ>
template <int NX, int NY, int NZ, bool FCI = false>
class FakeMeshFixture_tmpl : public ::testing::Test {
public:
FakeMeshFixture_tmpl()
Expand Down Expand Up @@ -113,6 +113,11 @@ public:
mesh_staggered_m.setCoordinates(test_coords_staggered, CELL_XLOW);
mesh_staggered_m.setCoordinates(test_coords_staggered, CELL_YLOW);
mesh_staggered_m.setCoordinates(test_coords_staggered, CELL_ZLOW);

if constexpr (FCI) {
mesh_m.getCoordinates()->setParallelTransform(
bout::utils::make_unique<MockParallelTransform>(mesh_m, false));
}
}

FakeMeshFixture_tmpl(const FakeMeshFixture_tmpl&) = delete;
Expand Down
Loading
Loading