From 1fb921e54c29c711954fa8fa20f572e7cff7e42b Mon Sep 17 00:00:00 2001 From: David Bold Date: Thu, 2 Feb 2023 12:42:27 +0100 Subject: [PATCH 001/242] Add some asserts for non parallelised XZ interpolation --- src/mesh/interpolation/bilinear_xz.cxx | 4 ++++ src/mesh/interpolation/hermite_spline_xz.cxx | 4 ++-- src/mesh/interpolation/lagrange_4pt_xz.cxx | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/mesh/interpolation/bilinear_xz.cxx b/src/mesh/interpolation/bilinear_xz.cxx index 8445764a8f..4facdac34c 100644 --- a/src/mesh/interpolation/bilinear_xz.cxx +++ b/src/mesh/interpolation/bilinear_xz.cxx @@ -31,6 +31,10 @@ XZBilinear::XZBilinear(int y_offset, Mesh* mesh) : XZInterpolation(y_offset, mesh), w0(localmesh), w1(localmesh), w2(localmesh), w3(localmesh) { + if (localmesh->getNXPE() > 1) { + throw BoutException("Do not support MPI splitting in X"); + } + // Index arrays contain guard cells in order to get subscripts right i_corner.reallocate(localmesh->LocalNx, localmesh->LocalNy, localmesh->LocalNz); k_corner.reallocate(localmesh->LocalNx, localmesh->LocalNy, localmesh->LocalNz); diff --git a/src/mesh/interpolation/hermite_spline_xz.cxx b/src/mesh/interpolation/hermite_spline_xz.cxx index c0040d096e..165d387d66 100644 --- a/src/mesh/interpolation/hermite_spline_xz.cxx +++ b/src/mesh/interpolation/hermite_spline_xz.cxx @@ -101,8 +101,8 @@ class IndConverter { } }; -XZHermiteSpline::XZHermiteSpline(int y_offset, Mesh* mesh) - : XZInterpolation(y_offset, mesh), h00_x(localmesh), h01_x(localmesh), +XZHermiteSpline::XZHermiteSpline(int y_offset, Mesh* meshin) + : XZInterpolation(y_offset, meshin), h00_x(localmesh), h01_x(localmesh), h10_x(localmesh), h11_x(localmesh), h00_z(localmesh), h01_z(localmesh), h10_z(localmesh), h11_z(localmesh) { diff --git a/src/mesh/interpolation/lagrange_4pt_xz.cxx b/src/mesh/interpolation/lagrange_4pt_xz.cxx index 92c14ecfd5..8fa201ba72 100644 --- a/src/mesh/interpolation/lagrange_4pt_xz.cxx +++ b/src/mesh/interpolation/lagrange_4pt_xz.cxx @@ -29,6 +29,10 @@ XZLagrange4pt::XZLagrange4pt(int y_offset, Mesh* mesh) : XZInterpolation(y_offset, mesh), t_x(localmesh), t_z(localmesh) { + if (localmesh->getNXPE() > 1) { + throw BoutException("Do not support MPI splitting in X"); + } + // Index arrays contain guard cells in order to get subscripts right i_corner.reallocate(localmesh->LocalNx, localmesh->LocalNy, localmesh->LocalNz); k_corner.reallocate(localmesh->LocalNx, localmesh->LocalNy, localmesh->LocalNz); From 4a79fb49ee0d9a78fa91bf96e25b13396600e9e6 Mon Sep 17 00:00:00 2001 From: David Bold Date: Thu, 23 Feb 2023 13:52:03 +0100 Subject: [PATCH 002/242] enable openmp for sundials if it is enabled for BOUT++ --- cmake/SetupBOUTThirdParty.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/SetupBOUTThirdParty.cmake b/cmake/SetupBOUTThirdParty.cmake index 53adbec92d..f783487556 100644 --- a/cmake/SetupBOUTThirdParty.cmake +++ b/cmake/SetupBOUTThirdParty.cmake @@ -288,7 +288,7 @@ if (BOUT_USE_SUNDIALS) set(EXAMPLES_ENABLE_C OFF CACHE BOOL "" FORCE) set(EXAMPLES_INSTALL OFF CACHE BOOL "" FORCE) set(ENABLE_MPI ${BOUT_USE_MPI} CACHE BOOL "" FORCE) - set(ENABLE_OPENMP OFF CACHE BOOL "" FORCE) + set(ENABLE_OPENMP ${BOUT_USE_OPENMP} CACHE BOOL "" FORCE) if (BUILD_SHARED_LIBS) set(BUILD_STATIC_LIBS OFF CACHE BOOL "" FORCE) else() From b5bd5f0258856860ba6a5c9f4cfe8fdf59eb9d52 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 3 Nov 2023 14:02:42 +0100 Subject: [PATCH 003/242] Add required interfaces --- include/bout/coordinates.hxx | 4 ++++ include/bout/field3d.hxx | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/include/bout/coordinates.hxx b/include/bout/coordinates.hxx index 49feffa0a7..c0a13aafab 100644 --- a/include/bout/coordinates.hxx +++ b/include/bout/coordinates.hxx @@ -133,6 +133,10 @@ public: transform = std::move(pt); } + bool hasParallelTransform() const{ + return transform != nullptr; + } + /// Return the parallel transform ParallelTransform& getParallelTransform() { ASSERT1(transform != nullptr); diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index ba8c8e879e..964e3f096c 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -261,6 +261,13 @@ public: #endif } + /// get number of parallel slices + size_t numberParallelSlices() const { + // Do checks + hasParallelSlices(); + return yup_fields.size(); + } + /// Check if this field has yup and ydown fields /// Return reference to yup field Field3D& yup(std::vector::size_type index = 0) { From 681971830276a899c433597c40a12c084cb7438d Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 19 Mar 2024 15:41:00 +0100 Subject: [PATCH 004/242] Add maskFromRegion --- include/bout/mask.hxx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/bout/mask.hxx b/include/bout/mask.hxx index 4250d21105..624f3d7513 100644 --- a/include/bout/mask.hxx +++ b/include/bout/mask.hxx @@ -67,6 +67,7 @@ public: inline bool& operator()(int jx, int jy, int jz) { return mask(jx, jy, jz); } inline const bool& operator()(int jx, int jy, int jz) const { return mask(jx, jy, jz); } inline const bool& operator[](const Ind3D& i) const { return mask[i]; } + inline bool& operator[](const Ind3D& i) { return mask[i]; } }; inline std::unique_ptr> regionFromMask(const BoutMask& mask, @@ -79,4 +80,13 @@ inline std::unique_ptr> regionFromMask(const BoutMask& mask, } return std::make_unique>(indices); } + +inline BoutMask maskFromRegion(const Region& region, const Mesh* mesh) { + BoutMask mask{mesh, false}; + //(int nx, int ny, int nz, bool value=false) : + + BOUT_FOR(i, region) { mask[i] = true; } + return mask; +} + #endif //BOUT_MASK_H From fee27c95cbf1ac53eb2e7612293e90716548f761 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 3 Nov 2023 11:51:03 +0100 Subject: [PATCH 005/242] Dump field before and after rhs evaluation for debugging --- src/solver/impls/pvode/pvode.cxx | 43 ++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/solver/impls/pvode/pvode.cxx b/src/solver/impls/pvode/pvode.cxx index b2cfd233a9..8a51a9cb19 100644 --- a/src/solver/impls/pvode/pvode.cxx +++ b/src/solver/impls/pvode/pvode.cxx @@ -293,6 +293,49 @@ BoutReal PvodeSolver::run(BoutReal tout) { // Check return flag if (flag != SUCCESS) { output_error.write("ERROR CVODE step failed, flag = {:d}\n", flag); + CVodeMemRec* cv_mem = (CVodeMem)cvode_mem; + if (f2d.empty() and v2d.empty() and v3d.empty()) { + Options debug{}; + using namespace std::string_literals; + Mesh* mesh{}; + for (const auto& prefix : {"pre_"s, "residuum_"s}) { + std::vector ffs{}; + std::vector evolve_bndrys{}; + for (const auto& f : f3d) { + Field3D ff{0.}; + ff.allocate(); + ff.setLocation(f.location); + mesh = ff.getMesh(); + debug[fmt::format("{:s}{:s}", prefix, f.name)] = ff; + ffs.push_back(ff); + evolve_bndrys.push_back(f.evolve_bndry); + } + pvode_load_data_f3d(evolve_bndrys, ffs, + prefix == "pre_"s ? udata : N_VDATA(cv_mem->cv_acor)); + } + + for (auto& f : f3d) { + f.F_var->enableTracking(fmt::format("ddt_{:s}", f.name), debug); + setName(f.var, f.name); + } + run_rhs(simtime); + + for (auto& f : f3d) { + debug[f.name] = *f.var; + } + + if (mesh) { + mesh->outputVars(debug); + debug["BOUT_VERSION"].force(bout::version::as_double); + } + + std::string outname = fmt::format( + "{}/BOUT.debug.{}.nc", + Options::root()["datadir"].withDefault("data"), BoutComm::rank()); + + bout::OptionsNetCDF(outname).write(debug); + MPI_Barrier(BoutComm::get()); + } return (-1.0); } From 96da2e9f88e313f4dd388b2c83d701046aedf96c Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 3 Nov 2023 11:49:32 +0100 Subject: [PATCH 006/242] Add setName function --- include/bout/field.hxx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/bout/field.hxx b/include/bout/field.hxx index c0693ec0fb..0867560c3b 100644 --- a/include/bout/field.hxx +++ b/include/bout/field.hxx @@ -683,4 +683,12 @@ inline T floor(const T& var, BoutReal f, const std::string& rgn = "RGN_ALL") { #undef FIELD_FUNC +template , class... Types> +inline T setName(T&& f, const std::string& name, Types... args) { +#if BOUT_USE_TRACK + f.name = fmt::format(name, args...); +#endif + return f; +} + #endif /* FIELD_H */ From e56981ceeb0409d7d48d81e1225e7332c0346519 Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 19 Jun 2023 09:33:00 +0200 Subject: [PATCH 007/242] Set div_par and grad_par names --- src/mesh/coordinates.cxx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index 01f0fe46ca..3948c75b94 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -1542,7 +1542,11 @@ Field3D Coordinates::Grad_par(const Field3D& var, CELL_LOC outloc, TRACE("Coordinates::Grad_par( Field3D )"); ASSERT1(location == outloc || outloc == CELL_DEFAULT); - return ::DDY(var, outloc, method) * invSg(); + if (invSg == nullptr) { + invSg = std::make_unique(); + (*invSg) = 1.0 / sqrt(g_22); + } + return setName(::DDY(var, outloc, method) * invSg(), "Grad_par({:s})", var.name); } ///////////////////////////////////////////////////////// @@ -1601,7 +1605,7 @@ Field3D Coordinates::Div_par(const Field3D& f, CELL_LOC outloc, f_B.yup(i) = f.yup(i) / Bxy_floc.yup(i); f_B.ydown(i) = f.ydown(i) / Bxy_floc.ydown(i); } - return Bxy * Grad_par(f_B, outloc, method); + return setName(Bxy * Grad_par(f_B, outloc, method), "Div_par({:s})", f.name); } ///////////////////////////////////////////////////////// From 6b2c132e3db61fa4f453e43a6988e8534f6c0a43 Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 19 Jun 2023 09:32:25 +0200 Subject: [PATCH 008/242] Dump debug file if PVODE fails Use the new track feature (better name required) to dump the different components of the ddt() as well as the residuum for the evolved fields. --- src/solver/impls/pvode/pvode.cxx | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/solver/impls/pvode/pvode.cxx b/src/solver/impls/pvode/pvode.cxx index 8a51a9cb19..7283b7d0eb 100644 --- a/src/solver/impls/pvode/pvode.cxx +++ b/src/solver/impls/pvode/pvode.cxx @@ -42,12 +42,39 @@ #include // contains the enum for types of preconditioning #include // band preconditioner function prototypes +#include + using namespace pvode; void solver_f(integer N, BoutReal t, N_Vector u, N_Vector udot, void* f_data); void solver_gloc(integer N, BoutReal t, BoutReal* u, BoutReal* udot, void* f_data); void solver_cfn(integer N, BoutReal t, N_Vector u, void* f_data); +namespace { +// local only +void pvode_load_data_f3d(const std::vector& evolve_bndrys, + std::vector& ffs, BoutReal* udata) { + int p = 0; + Mesh* mesh = ffs[0].getMesh(); + const int nz = mesh->LocalNz; + for (const auto& bndry : {true, false}) { + for (const auto& i2d : mesh->getRegion2D(bndry ? "RGN_BNDRY" : "RGN_NOBNDRY")) { + for (int jz = 0; jz < nz; jz++) { + // Loop over 3D variables + std::vector::const_iterator evolve_bndry = evolve_bndrys.begin(); + for (std::vector::iterator ff = ffs.begin(); ff != ffs.end(); ++ff) { + if (bndry && !*evolve_bndry) + continue; + (*ff)[mesh->ind2Dto3D(i2d, jz)] = udata[p]; + p++; + } + ++evolve_bndry; + } + } + } +} +} // namespace + const BoutReal ZERO = 0.0; long int iopt[OPT_SIZE]; From df2d66189f4d2e87cc024521ad287936b9c0ba85 Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 19 Jun 2023 09:27:19 +0200 Subject: [PATCH 009/242] Add tracking to Field3D This keeps track of all the changes done to the field and stores them to a OptionsObject. --- include/bout/field3d.hxx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index ba8c8e879e..8992575be6 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -514,6 +514,13 @@ private: /// RegionID over which the field is valid std::optional regionID; + + int tracking_state{0}; + Options* tracking{nullptr}; + std::string selfname{""}; + template + Options* track(const T& change, std::string op); + Options* track(const BoutReal& change, std::string op); }; // Non-member overloaded operators From 263f9fedaad854832bfdbf6a5ac9322eb492c71e Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 19 Jun 2023 09:27:19 +0200 Subject: [PATCH 010/242] Add tracking to Field3D This keeps track of all the changes done to the field and stores them to a OptionsObject. --- include/bout/field3d.hxx | 4 + src/field/field3d.cxx | 44 ++++ src/field/gen_fieldops.jinja | 14 ++ src/field/generated_fieldops.cxx | 348 +++++++++++++++++++++++++++++++ 4 files changed, 410 insertions(+) diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index 8992575be6..bf7a9cc180 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -295,6 +295,10 @@ public: /// cuts on closed field lines? bool requiresTwistShift(bool twist_shift_enabled); + /// Enable a special tracking mode for debugging + /// Save all changes that, are done to the field, to tracking + Field3D& enableTracking(const std::string& name, Options& tracking); + ///////////////////////////////////////////////////////// // Data access diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index 011353f34a..f0f088b656 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -243,6 +243,7 @@ Field3D& Field3D::operator=(const Field3D& rhs) { } TRACE("Field3D: Assignment from Field3D"); + track(rhs, "operator="); // Copy base slice Field::operator=(rhs); @@ -263,6 +264,7 @@ Field3D& Field3D::operator=(const Field3D& rhs) { Field3D& Field3D::operator=(Field3D&& rhs) { TRACE("Field3D: Assignment from Field3D"); + track(rhs, "operator="); // Move parallel slices or delete existing ones. yup_fields = std::move(rhs.yup_fields); @@ -283,6 +285,7 @@ Field3D& Field3D::operator=(Field3D&& rhs) { Field3D& Field3D::operator=(const Field2D& rhs) { TRACE("Field3D = Field2D"); + track(rhs, "operator="); /// Check that the data is allocated ASSERT1(rhs.isAllocated()); @@ -327,6 +330,7 @@ void Field3D::operator=(const FieldPerp& rhs) { Field3D& Field3D::operator=(const BoutReal val) { TRACE("Field3D = BoutReal"); + track(val, "operator="); // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. @@ -831,3 +835,43 @@ Field3D::getValidRegionWithDefault(const std::string& region_name) const { void Field3D::setRegion(const std::string& region_name) { regionID = fieldmesh->getRegionID(region_name); } + +Field3D& Field3D::enableTracking(const std::string& name, Options& _tracking) { + tracking = &_tracking; + tracking_state = 1; + selfname = name; + return *this; +} + +template +Options* Field3D::track(const T& change, std::string op) { + if (tracking and tracking_state) { + const std::string outname{fmt::format("track_{:s}_{:d}", selfname, tracking_state++)}; + tracking->set(outname, change, "tracking"); + (*tracking)[outname].setAttributes({ + {"operation", op}, +#if BOUT_USE_TRACK + {"rhs.name", change.name}, +#endif + }); + return &(*tracking)[outname]; + } + return nullptr; +} + +template Options* Field3D::track(const Field3D&, std::string); +template Options* Field3D::track(const Field2D&, std::string); +template Options* Field3D::track(const FieldPerp&, std::string); + +Options* Field3D::track(const BoutReal& change, std::string op) { + if (tracking and tracking_state) { + const std::string outname{fmt::format("track_{:s}_{:d}", selfname, tracking_state++)}; + tracking->set(outname, change, "tracking"); + (*tracking)[outname].setAttributes({ + {"operation", op}, + {"rhs.name", "BR"}, + }); + return &(*tracking)[outname]; + } + return nullptr; +} diff --git a/src/field/gen_fieldops.jinja b/src/field/gen_fieldops.jinja index ecd4e628cc..58b1ae28ba 100644 --- a/src/field/gen_fieldops.jinja +++ b/src/field/gen_fieldops.jinja @@ -61,6 +61,10 @@ } {% endif %} +#if BOUT_USE_TRACK + {{out.name}}.name = fmt::format("{:s} {{operator}} {:s}", {{'"BR"' if lhs == "BoutReal" else lhs.name + ".name"}} + , {{'"BR"' if rhs == "BoutReal" else rhs.name + ".name"}}); +#endif checkData({{out.name}}); return {{out.name}}; } @@ -129,9 +133,19 @@ } {% endif %} + {% if lhs == "Field3D" %} + track(rhs, "operator{{operator}}="); + {% endif %} +#if BOUT_USE_TRACK + name = fmt::format("{:s} {{operator}}= {:s}", this->name, {{'"BR"' if rhs == "BoutReal" else rhs.name + ".name"}}); +#endif + checkData(*this); } else { + {% if lhs == "Field3D" %} + track(rhs, "operator{{operator}}="); + {% endif %} (*this) = (*this) {{operator}} {{rhs.name}}; } return *this; diff --git a/src/field/generated_fieldops.cxx b/src/field/generated_fieldops.cxx index 6b778acee3..3495d87dbc 100644 --- a/src/field/generated_fieldops.cxx +++ b/src/field/generated_fieldops.cxx @@ -20,6 +20,9 @@ Field3D operator*(const Field3D& lhs, const Field3D& rhs) { result[index] = lhs[index] * rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -42,9 +45,15 @@ Field3D& Field3D::operator*=(const Field3D& rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] *= rhs[index]; } + track(rhs, "operator*="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} *= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { + track(rhs, "operator*="); (*this) = (*this) * rhs; } return *this; @@ -64,6 +73,9 @@ Field3D operator/(const Field3D& lhs, const Field3D& rhs) { result[index] = lhs[index] / rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -86,9 +98,15 @@ Field3D& Field3D::operator/=(const Field3D& rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] /= rhs[index]; } + track(rhs, "operator/="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} /= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { + track(rhs, "operator/="); (*this) = (*this) / rhs; } return *this; @@ -108,6 +126,9 @@ Field3D operator+(const Field3D& lhs, const Field3D& rhs) { result[index] = lhs[index] + rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -130,9 +151,15 @@ Field3D& Field3D::operator+=(const Field3D& rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] += rhs[index]; } + track(rhs, "operator+="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} += {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { + track(rhs, "operator+="); (*this) = (*this) + rhs; } return *this; @@ -152,6 +179,9 @@ Field3D operator-(const Field3D& lhs, const Field3D& rhs) { result[index] = lhs[index] - rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -174,9 +204,15 @@ Field3D& Field3D::operator-=(const Field3D& rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] -= rhs[index]; } + track(rhs, "operator-="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} -= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { + track(rhs, "operator-="); (*this) = (*this) - rhs; } return *this; @@ -201,6 +237,9 @@ Field3D operator*(const Field3D& lhs, const Field2D& rhs) { } } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -226,9 +265,15 @@ Field3D& Field3D::operator*=(const Field2D& rhs) { } } + track(rhs, "operator*="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} *= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { + track(rhs, "operator*="); (*this) = (*this) * rhs; } return *this; @@ -254,6 +299,9 @@ Field3D operator/(const Field3D& lhs, const Field2D& rhs) { } } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -280,9 +328,15 @@ Field3D& Field3D::operator/=(const Field2D& rhs) { } } + track(rhs, "operator/="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} /= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { + track(rhs, "operator/="); (*this) = (*this) / rhs; } return *this; @@ -307,6 +361,9 @@ Field3D operator+(const Field3D& lhs, const Field2D& rhs) { } } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -332,9 +389,15 @@ Field3D& Field3D::operator+=(const Field2D& rhs) { } } + track(rhs, "operator+="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} += {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { + track(rhs, "operator+="); (*this) = (*this) + rhs; } return *this; @@ -359,6 +422,9 @@ Field3D operator-(const Field3D& lhs, const Field2D& rhs) { } } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -384,9 +450,15 @@ Field3D& Field3D::operator-=(const Field2D& rhs) { } } + track(rhs, "operator-="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} -= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { + track(rhs, "operator-="); (*this) = (*this) - rhs; } return *this; @@ -408,6 +480,9 @@ FieldPerp operator*(const Field3D& lhs, const FieldPerp& rhs) { result[index] = lhs[base_ind] * rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -428,6 +503,9 @@ FieldPerp operator/(const Field3D& lhs, const FieldPerp& rhs) { result[index] = lhs[base_ind] / rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -448,6 +526,9 @@ FieldPerp operator+(const Field3D& lhs, const FieldPerp& rhs) { result[index] = lhs[base_ind] + rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -468,6 +549,9 @@ FieldPerp operator-(const Field3D& lhs, const FieldPerp& rhs) { result[index] = lhs[base_ind] - rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -485,6 +569,9 @@ Field3D operator*(const Field3D& lhs, const BoutReal rhs) { result[index] = lhs[index] * rhs; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", lhs.name, "BR"); +#endif checkData(result); return result; } @@ -504,9 +591,15 @@ Field3D& Field3D::operator*=(const BoutReal rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] *= rhs; } + track(rhs, "operator*="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} *= {:s}", this->name, "BR"); +#endif + checkData(*this); } else { + track(rhs, "operator*="); (*this) = (*this) * rhs; } return *this; @@ -526,6 +619,9 @@ Field3D operator/(const Field3D& lhs, const BoutReal rhs) { result[index] = lhs[index] * tmp; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", lhs.name, "BR"); +#endif checkData(result); return result; } @@ -546,9 +642,15 @@ Field3D& Field3D::operator/=(const BoutReal rhs) { const auto tmp = 1.0 / rhs; BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] *= tmp; } + track(rhs, "operator/="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} /= {:s}", this->name, "BR"); +#endif + checkData(*this); } else { + track(rhs, "operator/="); (*this) = (*this) / rhs; } return *this; @@ -567,6 +669,9 @@ Field3D operator+(const Field3D& lhs, const BoutReal rhs) { result[index] = lhs[index] + rhs; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", lhs.name, "BR"); +#endif checkData(result); return result; } @@ -586,9 +691,15 @@ Field3D& Field3D::operator+=(const BoutReal rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] += rhs; } + track(rhs, "operator+="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} += {:s}", this->name, "BR"); +#endif + checkData(*this); } else { + track(rhs, "operator+="); (*this) = (*this) + rhs; } return *this; @@ -607,6 +718,9 @@ Field3D operator-(const Field3D& lhs, const BoutReal rhs) { result[index] = lhs[index] - rhs; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", lhs.name, "BR"); +#endif checkData(result); return result; } @@ -626,9 +740,15 @@ Field3D& Field3D::operator-=(const BoutReal rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] -= rhs; } + track(rhs, "operator-="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} -= {:s}", this->name, "BR"); +#endif + checkData(*this); } else { + track(rhs, "operator-="); (*this) = (*this) - rhs; } return *this; @@ -653,6 +773,9 @@ Field3D operator*(const Field2D& lhs, const Field3D& rhs) { } } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -676,6 +799,9 @@ Field3D operator/(const Field2D& lhs, const Field3D& rhs) { } } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -699,6 +825,9 @@ Field3D operator+(const Field2D& lhs, const Field3D& rhs) { } } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -722,6 +851,9 @@ Field3D operator-(const Field2D& lhs, const Field3D& rhs) { } } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -738,6 +870,9 @@ Field2D operator*(const Field2D& lhs, const Field2D& rhs) { result[index] = lhs[index] * rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -754,6 +889,10 @@ Field2D& Field2D::operator*=(const Field2D& rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] *= rhs[index]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} *= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -774,6 +913,9 @@ Field2D operator/(const Field2D& lhs, const Field2D& rhs) { result[index] = lhs[index] / rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -790,6 +932,10 @@ Field2D& Field2D::operator/=(const Field2D& rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] /= rhs[index]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} /= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -810,6 +956,9 @@ Field2D operator+(const Field2D& lhs, const Field2D& rhs) { result[index] = lhs[index] + rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -826,6 +975,10 @@ Field2D& Field2D::operator+=(const Field2D& rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] += rhs[index]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} += {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -846,6 +999,9 @@ Field2D operator-(const Field2D& lhs, const Field2D& rhs) { result[index] = lhs[index] - rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -862,6 +1018,10 @@ Field2D& Field2D::operator-=(const Field2D& rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] -= rhs[index]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} -= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -886,6 +1046,9 @@ FieldPerp operator*(const Field2D& lhs, const FieldPerp& rhs) { result[index] = lhs[base_ind] * rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -906,6 +1069,9 @@ FieldPerp operator/(const Field2D& lhs, const FieldPerp& rhs) { result[index] = lhs[base_ind] / rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -926,6 +1092,9 @@ FieldPerp operator+(const Field2D& lhs, const FieldPerp& rhs) { result[index] = lhs[base_ind] + rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -946,6 +1115,9 @@ FieldPerp operator-(const Field2D& lhs, const FieldPerp& rhs) { result[index] = lhs[base_ind] - rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -961,6 +1133,9 @@ Field2D operator*(const Field2D& lhs, const BoutReal rhs) { result[index] = lhs[index] * rhs; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", lhs.name, "BR"); +#endif checkData(result); return result; } @@ -976,6 +1151,10 @@ Field2D& Field2D::operator*=(const BoutReal rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] *= rhs; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} *= {:s}", this->name, "BR"); +#endif + checkData(*this); } else { @@ -996,6 +1175,9 @@ Field2D operator/(const Field2D& lhs, const BoutReal rhs) { result[index] = lhs[index] * tmp; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", lhs.name, "BR"); +#endif checkData(result); return result; } @@ -1012,6 +1194,10 @@ Field2D& Field2D::operator/=(const BoutReal rhs) { const auto tmp = 1.0 / rhs; BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] *= tmp; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} /= {:s}", this->name, "BR"); +#endif + checkData(*this); } else { @@ -1031,6 +1217,9 @@ Field2D operator+(const Field2D& lhs, const BoutReal rhs) { result[index] = lhs[index] + rhs; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", lhs.name, "BR"); +#endif checkData(result); return result; } @@ -1046,6 +1235,10 @@ Field2D& Field2D::operator+=(const BoutReal rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] += rhs; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} += {:s}", this->name, "BR"); +#endif + checkData(*this); } else { @@ -1065,6 +1258,9 @@ Field2D operator-(const Field2D& lhs, const BoutReal rhs) { result[index] = lhs[index] - rhs; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", lhs.name, "BR"); +#endif checkData(result); return result; } @@ -1080,6 +1276,10 @@ Field2D& Field2D::operator-=(const BoutReal rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] -= rhs; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} -= {:s}", this->name, "BR"); +#endif + checkData(*this); } else { @@ -1104,6 +1304,9 @@ FieldPerp operator*(const FieldPerp& lhs, const Field3D& rhs) { result[index] = lhs[index] * rhs[base_ind]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -1126,6 +1329,10 @@ FieldPerp& FieldPerp::operator*=(const Field3D& rhs) { (*this)[index] *= rhs[base_ind]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} *= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -1150,6 +1357,9 @@ FieldPerp operator/(const FieldPerp& lhs, const Field3D& rhs) { result[index] = lhs[index] / rhs[base_ind]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -1172,6 +1382,10 @@ FieldPerp& FieldPerp::operator/=(const Field3D& rhs) { (*this)[index] /= rhs[base_ind]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} /= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -1196,6 +1410,9 @@ FieldPerp operator+(const FieldPerp& lhs, const Field3D& rhs) { result[index] = lhs[index] + rhs[base_ind]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -1218,6 +1435,10 @@ FieldPerp& FieldPerp::operator+=(const Field3D& rhs) { (*this)[index] += rhs[base_ind]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} += {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -1242,6 +1463,9 @@ FieldPerp operator-(const FieldPerp& lhs, const Field3D& rhs) { result[index] = lhs[index] - rhs[base_ind]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -1264,6 +1488,10 @@ FieldPerp& FieldPerp::operator-=(const Field3D& rhs) { (*this)[index] -= rhs[base_ind]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} -= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -1288,6 +1516,9 @@ FieldPerp operator*(const FieldPerp& lhs, const Field2D& rhs) { result[index] = lhs[index] * rhs[base_ind]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -1310,6 +1541,10 @@ FieldPerp& FieldPerp::operator*=(const Field2D& rhs) { (*this)[index] *= rhs[base_ind]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} *= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -1334,6 +1569,9 @@ FieldPerp operator/(const FieldPerp& lhs, const Field2D& rhs) { result[index] = lhs[index] / rhs[base_ind]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -1356,6 +1594,10 @@ FieldPerp& FieldPerp::operator/=(const Field2D& rhs) { (*this)[index] /= rhs[base_ind]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} /= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -1380,6 +1622,9 @@ FieldPerp operator+(const FieldPerp& lhs, const Field2D& rhs) { result[index] = lhs[index] + rhs[base_ind]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -1402,6 +1647,10 @@ FieldPerp& FieldPerp::operator+=(const Field2D& rhs) { (*this)[index] += rhs[base_ind]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} += {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -1426,6 +1675,9 @@ FieldPerp operator-(const FieldPerp& lhs, const Field2D& rhs) { result[index] = lhs[index] - rhs[base_ind]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -1448,6 +1700,10 @@ FieldPerp& FieldPerp::operator-=(const Field2D& rhs) { (*this)[index] -= rhs[base_ind]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} -= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -1468,6 +1724,9 @@ FieldPerp operator*(const FieldPerp& lhs, const FieldPerp& rhs) { result[index] = lhs[index] * rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -1484,6 +1743,10 @@ FieldPerp& FieldPerp::operator*=(const FieldPerp& rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] *= rhs[index]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} *= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -1504,6 +1767,9 @@ FieldPerp operator/(const FieldPerp& lhs, const FieldPerp& rhs) { result[index] = lhs[index] / rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -1520,6 +1786,10 @@ FieldPerp& FieldPerp::operator/=(const FieldPerp& rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] /= rhs[index]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} /= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -1540,6 +1810,9 @@ FieldPerp operator+(const FieldPerp& lhs, const FieldPerp& rhs) { result[index] = lhs[index] + rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -1556,6 +1829,10 @@ FieldPerp& FieldPerp::operator+=(const FieldPerp& rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] += rhs[index]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} += {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -1576,6 +1853,9 @@ FieldPerp operator-(const FieldPerp& lhs, const FieldPerp& rhs) { result[index] = lhs[index] - rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -1592,6 +1872,10 @@ FieldPerp& FieldPerp::operator-=(const FieldPerp& rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] -= rhs[index]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} -= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -1611,6 +1895,9 @@ FieldPerp operator*(const FieldPerp& lhs, const BoutReal rhs) { result[index] = lhs[index] * rhs; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", lhs.name, "BR"); +#endif checkData(result); return result; } @@ -1626,6 +1913,10 @@ FieldPerp& FieldPerp::operator*=(const BoutReal rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] *= rhs; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} *= {:s}", this->name, "BR"); +#endif + checkData(*this); } else { @@ -1646,6 +1937,9 @@ FieldPerp operator/(const FieldPerp& lhs, const BoutReal rhs) { result[index] = lhs[index] * tmp; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", lhs.name, "BR"); +#endif checkData(result); return result; } @@ -1661,6 +1955,10 @@ FieldPerp& FieldPerp::operator/=(const BoutReal rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] /= rhs; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} /= {:s}", this->name, "BR"); +#endif + checkData(*this); } else { @@ -1680,6 +1978,9 @@ FieldPerp operator+(const FieldPerp& lhs, const BoutReal rhs) { result[index] = lhs[index] + rhs; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", lhs.name, "BR"); +#endif checkData(result); return result; } @@ -1695,6 +1996,10 @@ FieldPerp& FieldPerp::operator+=(const BoutReal rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] += rhs; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} += {:s}", this->name, "BR"); +#endif + checkData(*this); } else { @@ -1714,6 +2019,9 @@ FieldPerp operator-(const FieldPerp& lhs, const BoutReal rhs) { result[index] = lhs[index] - rhs; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", lhs.name, "BR"); +#endif checkData(result); return result; } @@ -1729,6 +2037,10 @@ FieldPerp& FieldPerp::operator-=(const BoutReal rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] -= rhs; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} -= {:s}", this->name, "BR"); +#endif + checkData(*this); } else { @@ -1750,6 +2062,9 @@ Field3D operator*(const BoutReal lhs, const Field3D& rhs) { result[index] = lhs * rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", "BR", rhs.name); +#endif checkData(result); return result; } @@ -1767,6 +2082,9 @@ Field3D operator/(const BoutReal lhs, const Field3D& rhs) { result[index] = lhs / rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", "BR", rhs.name); +#endif checkData(result); return result; } @@ -1784,6 +2102,9 @@ Field3D operator+(const BoutReal lhs, const Field3D& rhs) { result[index] = lhs + rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", "BR", rhs.name); +#endif checkData(result); return result; } @@ -1801,6 +2122,9 @@ Field3D operator-(const BoutReal lhs, const Field3D& rhs) { result[index] = lhs - rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", "BR", rhs.name); +#endif checkData(result); return result; } @@ -1816,6 +2140,9 @@ Field2D operator*(const BoutReal lhs, const Field2D& rhs) { result[index] = lhs * rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", "BR", rhs.name); +#endif checkData(result); return result; } @@ -1831,6 +2158,9 @@ Field2D operator/(const BoutReal lhs, const Field2D& rhs) { result[index] = lhs / rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", "BR", rhs.name); +#endif checkData(result); return result; } @@ -1846,6 +2176,9 @@ Field2D operator+(const BoutReal lhs, const Field2D& rhs) { result[index] = lhs + rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", "BR", rhs.name); +#endif checkData(result); return result; } @@ -1861,6 +2194,9 @@ Field2D operator-(const BoutReal lhs, const Field2D& rhs) { result[index] = lhs - rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", "BR", rhs.name); +#endif checkData(result); return result; } @@ -1876,6 +2212,9 @@ FieldPerp operator*(const BoutReal lhs, const FieldPerp& rhs) { result[index] = lhs * rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", "BR", rhs.name); +#endif checkData(result); return result; } @@ -1891,6 +2230,9 @@ FieldPerp operator/(const BoutReal lhs, const FieldPerp& rhs) { result[index] = lhs / rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", "BR", rhs.name); +#endif checkData(result); return result; } @@ -1906,6 +2248,9 @@ FieldPerp operator+(const BoutReal lhs, const FieldPerp& rhs) { result[index] = lhs + rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", "BR", rhs.name); +#endif checkData(result); return result; } @@ -1921,6 +2266,9 @@ FieldPerp operator-(const BoutReal lhs, const FieldPerp& rhs) { result[index] = lhs - rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", "BR", rhs.name); +#endif checkData(result); return result; } From bac4ca92a31b60c40e87dd47b2ef764f571a58be Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 25 Apr 2023 09:16:38 +0200 Subject: [PATCH 011/242] cvode: Add option to use Adams Moulton solver instead of BDF --- src/solver/impls/pvode/pvode.cxx | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/solver/impls/pvode/pvode.cxx b/src/solver/impls/pvode/pvode.cxx index 7283b7d0eb..ae5cd783a8 100644 --- a/src/solver/impls/pvode/pvode.cxx +++ b/src/solver/impls/pvode/pvode.cxx @@ -214,7 +214,34 @@ int PvodeSolver::init() { } iopt[MXSTEP] = pvode_mxstep; - cvode_mem = CVodeMalloc(neq, solver_f, simtime, u, BDF, NEWTON, SS, &reltol, &abstol, + { + /* ropt[H0] : initial step size. Optional input. */ + + /* ropt[HMAX] : maximum absolute value of step size allowed. * + * Optional input. (Default is infinity). */ + const BoutReal hmax( + (*options)["max_timestep"].doc("Maximum internal timestep").withDefault(-1.)); + if (hmax > 0) { + ropt[HMAX] = hmax; + } + /* ropt[HMIN] : minimum absolute value of step size allowed. * + * Optional input. (Default is 0.0). */ + const BoutReal hmin( + (*options)["min_timestep"].doc("Minimum internal timestep").withDefault(-1.)); + if (hmin > 0) { + ropt[HMIN] = hmin; + } + /* iopt[MAXORD] : maximum lmm order to be used by the solver. * + * Optional input. (Default = 12 for ADAMS, 5 for * + * BDF). */ + const int maxOrder((*options)["max_order"].doc("Maximum order").withDefault(-1)); + if (maxOrder > 0) { + iopt[MAXORD] = maxOrder; + } + } + const bool use_adam((*options)["adams_moulton"].doc("Use Adams Moulton solver instead of BDF").withDefault(false)); + + cvode_mem = CVodeMalloc(neq, solver_f, simtime, u, use_adam ? ADAMS : BDF, NEWTON, SS, &reltol, &abstol, this, nullptr, optIn, iopt, ropt, machEnv); if (cvode_mem == nullptr) { From 708bdcb2ff0a5c346edb43f7e609949b7c7afbd9 Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 29 Mar 2023 12:59:22 +0200 Subject: [PATCH 012/242] Expose more pvode option to user --- src/solver/impls/pvode/pvode.cxx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/solver/impls/pvode/pvode.cxx b/src/solver/impls/pvode/pvode.cxx index ae5cd783a8..ac6e981b50 100644 --- a/src/solver/impls/pvode/pvode.cxx +++ b/src/solver/impls/pvode/pvode.cxx @@ -212,6 +212,9 @@ int PvodeSolver::init() { for (i = 0; i < OPT_SIZE; i++) { ropt[i] = ZERO; } + /* iopt[MXSTEP] : maximum number of internal steps to be taken by * + * the solver in its attempt to reach tout. * + * Optional input. (Default = 500). */ iopt[MXSTEP] = pvode_mxstep; { From d88b454aa2b2924a26a180440f956911c05a0abc Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 19 Mar 2024 16:04:48 +0100 Subject: [PATCH 013/242] Fix bad cherry-pick --- src/mesh/coordinates.cxx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index 3948c75b94..32774d6229 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -1542,10 +1542,6 @@ Field3D Coordinates::Grad_par(const Field3D& var, CELL_LOC outloc, TRACE("Coordinates::Grad_par( Field3D )"); ASSERT1(location == outloc || outloc == CELL_DEFAULT); - if (invSg == nullptr) { - invSg = std::make_unique(); - (*invSg) = 1.0 / sqrt(g_22); - } return setName(::DDY(var, outloc, method) * invSg(), "Grad_par({:s})", var.name); } From 023bc41730de39040a50ae245363945d2447d63b Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 19 Mar 2024 16:35:43 +0100 Subject: [PATCH 014/242] Update to new API --- include/bout/field.hxx | 7 +++++++ src/solver/impls/pvode/pvode.cxx | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/include/bout/field.hxx b/include/bout/field.hxx index 0867560c3b..04035f5b76 100644 --- a/include/bout/field.hxx +++ b/include/bout/field.hxx @@ -683,6 +683,13 @@ inline T floor(const T& var, BoutReal f, const std::string& rgn = "RGN_ALL") { #undef FIELD_FUNC +template , class... Types> +inline void setName(T& f, const std::string& name, Types... args) { +#if BOUT_USE_TRACK + f.name = fmt::format(name, args...); +#endif +} + template , class... Types> inline T setName(T&& f, const std::string& name, Types... args) { #if BOUT_USE_TRACK diff --git a/src/solver/impls/pvode/pvode.cxx b/src/solver/impls/pvode/pvode.cxx index ac6e981b50..762fba32d1 100644 --- a/src/solver/impls/pvode/pvode.cxx +++ b/src/solver/impls/pvode/pvode.cxx @@ -373,7 +373,7 @@ BoutReal PvodeSolver::run(BoutReal tout) { for (auto& f : f3d) { f.F_var->enableTracking(fmt::format("ddt_{:s}", f.name), debug); - setName(f.var, f.name); + setName(*f.var, f.name); } run_rhs(simtime); @@ -390,7 +390,7 @@ BoutReal PvodeSolver::run(BoutReal tout) { "{}/BOUT.debug.{}.nc", Options::root()["datadir"].withDefault("data"), BoutComm::rank()); - bout::OptionsNetCDF(outname).write(debug); + bout::OptionsIO::create(outname)->write(debug); MPI_Barrier(BoutComm::get()); } return (-1.0); From affc995c4ba482c79677c890cc44ccb47d45b648 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 19 Mar 2024 16:35:53 +0100 Subject: [PATCH 015/242] Fix documentation --- manual/sphinx/user_docs/bout_options.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/sphinx/user_docs/bout_options.rst b/manual/sphinx/user_docs/bout_options.rst index 85a8a17d59..330a0dad7e 100644 --- a/manual/sphinx/user_docs/bout_options.rst +++ b/manual/sphinx/user_docs/bout_options.rst @@ -889,7 +889,7 @@ Fields can also be stored and written:: Options fields; fields["f2d"] = Field2D(1.0); fields["f3d"] = Field3D(2.0); - bout::OptionsIO::create("fields.nc").write(fields); + bout::OptionsIO::create("fields.nc")->write(fields); This allows the input settings and evolving variables to be combined into a single tree (see above on joining trees) and written From 71f5b6adb6a8ad7b8941ba783773897906d870d2 Mon Sep 17 00:00:00 2001 From: dschwoerer Date: Tue, 19 Mar 2024 15:50:33 +0000 Subject: [PATCH 016/242] Apply clang-format changes --- src/solver/impls/pvode/pvode.cxx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/solver/impls/pvode/pvode.cxx b/src/solver/impls/pvode/pvode.cxx index 762fba32d1..a12f330964 100644 --- a/src/solver/impls/pvode/pvode.cxx +++ b/src/solver/impls/pvode/pvode.cxx @@ -242,10 +242,12 @@ int PvodeSolver::init() { iopt[MAXORD] = maxOrder; } } - const bool use_adam((*options)["adams_moulton"].doc("Use Adams Moulton solver instead of BDF").withDefault(false)); + const bool use_adam((*options)["adams_moulton"] + .doc("Use Adams Moulton solver instead of BDF") + .withDefault(false)); - cvode_mem = CVodeMalloc(neq, solver_f, simtime, u, use_adam ? ADAMS : BDF, NEWTON, SS, &reltol, &abstol, - this, nullptr, optIn, iopt, ropt, machEnv); + cvode_mem = CVodeMalloc(neq, solver_f, simtime, u, use_adam ? ADAMS : BDF, NEWTON, SS, + &reltol, &abstol, this, nullptr, optIn, iopt, ropt, machEnv); if (cvode_mem == nullptr) { throw BoutException("\tError: CVodeMalloc failed.\n"); @@ -373,12 +375,12 @@ BoutReal PvodeSolver::run(BoutReal tout) { for (auto& f : f3d) { f.F_var->enableTracking(fmt::format("ddt_{:s}", f.name), debug); - setName(*f.var, f.name); + setName(*f.var, f.name); } run_rhs(simtime); for (auto& f : f3d) { - debug[f.name] = *f.var; + debug[f.name] = *f.var; } if (mesh) { From 31fd46153fad6977524394179d0b83ac51f26b9e Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 20 Mar 2024 09:59:13 +0100 Subject: [PATCH 017/242] Apply recomendations from code-review --- include/bout/field3d.hxx | 6 +++--- src/field/field3d.cxx | 10 +++++----- src/solver/impls/pvode/pvode.cxx | 5 +++-- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index bf7a9cc180..cfde9e5328 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -521,10 +521,10 @@ private: int tracking_state{0}; Options* tracking{nullptr}; - std::string selfname{""}; + std::string selfname; template - Options* track(const T& change, std::string op); - Options* track(const BoutReal& change, std::string op); + Options* track(const T& change, std::string operation); + Options* track(const BoutReal& change, std::string operation); }; // Non-member overloaded operators diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index f0f088b656..2196d6eea4 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -844,12 +844,12 @@ Field3D& Field3D::enableTracking(const std::string& name, Options& _tracking) { } template -Options* Field3D::track(const T& change, std::string op) { - if (tracking and tracking_state) { +Options* Field3D::track(const T& change, std::string operation) { + if (tracking != nullptr and tracking_state != 0) { const std::string outname{fmt::format("track_{:s}_{:d}", selfname, tracking_state++)}; tracking->set(outname, change, "tracking"); (*tracking)[outname].setAttributes({ - {"operation", op}, + {"operation", operation}, #if BOUT_USE_TRACK {"rhs.name", change.name}, #endif @@ -863,12 +863,12 @@ template Options* Field3D::track(const Field3D&, std::string); template Options* Field3D::track(const Field2D&, std::string); template Options* Field3D::track(const FieldPerp&, std::string); -Options* Field3D::track(const BoutReal& change, std::string op) { +Options* Field3D::track(const BoutReal& change, std::string operation) { if (tracking and tracking_state) { const std::string outname{fmt::format("track_{:s}_{:d}", selfname, tracking_state++)}; tracking->set(outname, change, "tracking"); (*tracking)[outname].setAttributes({ - {"operation", op}, + {"operation", operation}, {"rhs.name", "BR"}, }); return &(*tracking)[outname]; diff --git a/src/solver/impls/pvode/pvode.cxx b/src/solver/impls/pvode/pvode.cxx index a12f330964..f3a96b03af 100644 --- a/src/solver/impls/pvode/pvode.cxx +++ b/src/solver/impls/pvode/pvode.cxx @@ -53,7 +53,7 @@ void solver_cfn(integer N, BoutReal t, N_Vector u, void* f_data); namespace { // local only void pvode_load_data_f3d(const std::vector& evolve_bndrys, - std::vector& ffs, BoutReal* udata) { + std::vector& ffs, const BoutReal* udata) { int p = 0; Mesh* mesh = ffs[0].getMesh(); const int nz = mesh->LocalNz; @@ -63,8 +63,9 @@ void pvode_load_data_f3d(const std::vector& evolve_bndrys, // Loop over 3D variables std::vector::const_iterator evolve_bndry = evolve_bndrys.begin(); for (std::vector::iterator ff = ffs.begin(); ff != ffs.end(); ++ff) { - if (bndry && !*evolve_bndry) + if (bndry && !*evolve_bndry) { continue; + } (*ff)[mesh->ind2Dto3D(i2d, jz)] = udata[p]; p++; } From 17e46cfc1c0a835fea474ac68f64a9addbf4379f Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 20 Mar 2024 10:02:28 +0100 Subject: [PATCH 018/242] Use more meaningful names --- src/solver/impls/pvode/pvode.cxx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/solver/impls/pvode/pvode.cxx b/src/solver/impls/pvode/pvode.cxx index f3a96b03af..c389a3c0d1 100644 --- a/src/solver/impls/pvode/pvode.cxx +++ b/src/solver/impls/pvode/pvode.cxx @@ -359,18 +359,18 @@ BoutReal PvodeSolver::run(BoutReal tout) { using namespace std::string_literals; Mesh* mesh{}; for (const auto& prefix : {"pre_"s, "residuum_"s}) { - std::vector ffs{}; + std::vector list_of_fields{}; std::vector evolve_bndrys{}; for (const auto& f : f3d) { - Field3D ff{0.}; - ff.allocate(); - ff.setLocation(f.location); - mesh = ff.getMesh(); - debug[fmt::format("{:s}{:s}", prefix, f.name)] = ff; - ffs.push_back(ff); + mesh = f.var->getMesh(); + Field3D to_load{0., mesh}; + to_load.allocate(); + to_load.setLocation(f.location); + debug[fmt::format("{:s}{:s}", prefix, f.name)] = to_load; + list_of_fields.push_back(to_load); evolve_bndrys.push_back(f.evolve_bndry); } - pvode_load_data_f3d(evolve_bndrys, ffs, + pvode_load_data_f3d(evolve_bndrys, list_of_fields, prefix == "pre_"s ? udata : N_VDATA(cv_mem->cv_acor)); } From 9c0ae16ed905588b50f3e4fe634dcedf47de22b5 Mon Sep 17 00:00:00 2001 From: dschwoerer Date: Wed, 20 Mar 2024 09:03:10 +0000 Subject: [PATCH 019/242] Apply clang-format changes --- src/solver/impls/pvode/pvode.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solver/impls/pvode/pvode.cxx b/src/solver/impls/pvode/pvode.cxx index c389a3c0d1..fe231e1086 100644 --- a/src/solver/impls/pvode/pvode.cxx +++ b/src/solver/impls/pvode/pvode.cxx @@ -65,7 +65,7 @@ void pvode_load_data_f3d(const std::vector& evolve_bndrys, for (std::vector::iterator ff = ffs.begin(); ff != ffs.end(); ++ff) { if (bndry && !*evolve_bndry) { continue; - } + } (*ff)[mesh->ind2Dto3D(i2d, jz)] = udata[p]; p++; } From 4a17b4982df9788fc26407db70f14d0ce16098e3 Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 20 Mar 2024 10:04:52 +0100 Subject: [PATCH 020/242] Apply suggestions from code review --- src/solver/impls/pvode/pvode.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/solver/impls/pvode/pvode.cxx b/src/solver/impls/pvode/pvode.cxx index fe231e1086..db28f64d86 100644 --- a/src/solver/impls/pvode/pvode.cxx +++ b/src/solver/impls/pvode/pvode.cxx @@ -384,12 +384,12 @@ BoutReal PvodeSolver::run(BoutReal tout) { debug[f.name] = *f.var; } - if (mesh) { + if (mesh != nullptr) { mesh->outputVars(debug); debug["BOUT_VERSION"].force(bout::version::as_double); } - std::string outname = fmt::format( + const std::string outname = fmt::format( "{}/BOUT.debug.{}.nc", Options::root()["datadir"].withDefault("data"), BoutComm::rank()); From 4bbd9ba699185b6491f1e408825daa2a00884ef3 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 3 Nov 2023 14:05:15 +0100 Subject: [PATCH 021/242] Add option to automatically compute parallel fields --- CMakeLists.txt | 3 + cmake_build_defines.hxx.in | 1 + src/field/gen_fieldops.jinja | 21 +++- src/field/generated_fieldops.cxx | 180 ++++++++++++++++++++++++++++--- 4 files changed, 192 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c1c82ea4e3..ac4be59575 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -591,6 +591,9 @@ else() endif() set(BOUT_USE_METRIC_3D ${BOUT_ENABLE_METRIC_3D}) +option(BOUT_ENABLE_FCI_AUTOMAGIC "Enable (slow?) automatic features for FCI" ON) +set(BOUT_USE_FCI_AUTOMAGIC ${BOUT_ENABLE_FCI_AUTOMAGIC}) + include(CheckCXXSourceCompiles) check_cxx_source_compiles("int main() { const char* name = __PRETTY_FUNCTION__; }" HAS_PRETTY_FUNCTION) diff --git a/cmake_build_defines.hxx.in b/cmake_build_defines.hxx.in index ed6e8685f6..d3a4ea0334 100644 --- a/cmake_build_defines.hxx.in +++ b/cmake_build_defines.hxx.in @@ -35,6 +35,7 @@ #cmakedefine BOUT_METRIC_TYPE @BOUT_METRIC_TYPE@ #cmakedefine01 BOUT_USE_METRIC_3D #cmakedefine01 BOUT_USE_MSGSTACK +#cmakedefine01 BOUT_USE_FCI_AUTOMAGIC // CMake build does not support legacy interface #define BOUT_HAS_LEGACY_NETCDF 0 diff --git a/src/field/gen_fieldops.jinja b/src/field/gen_fieldops.jinja index ecd4e628cc..6360cba783 100644 --- a/src/field/gen_fieldops.jinja +++ b/src/field/gen_fieldops.jinja @@ -12,6 +12,15 @@ {% if lhs == rhs == "Field3D" %} {{out.name}}.setRegion({{lhs.name}}.getMesh()->getCommonRegion({{lhs.name}}.getRegionID(), {{rhs.name}}.getRegionID())); +#if BOUT_USE_FCI_AUTOMAGIC + if (isFci({{lhs.name}}) and {{lhs.name}}.hasParallelSlices() and {{rhs.name}}.hasParallelSlices()) { + {{out.name}}.splitParallelSlices(); + for (size_t i{0} ; i < {{lhs.name}}.numberParallelSlices() ; ++i) { + {{out.name}}.yup(i) = {{lhs.name}}.yup(i) {{operator}} {{rhs.name}}.yup(i); + {{out.name}}.ydown(i) = {{lhs.name}}.ydown(i) {{operator}} {{rhs.name}}.ydown(i); + } + } +#endif {% elif lhs == "Field3D" %} {{out.name}}.setRegion({{lhs.name}}.getRegionID()); {% elif rhs == "Field3D" %} @@ -78,7 +87,17 @@ {% if (lhs == "Field3D") %} // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. - clearParallelSlices(); +#if BOUT_USE_FCI_AUTOMAGIC + if (isFci(*this) and this->hasParallelSlices() {% if rhs == "Field3D" %} and {{rhs.name}}.hasParallelSlices() {% endif %}) { + for (size_t i{0} ; i < yup_fields.size() ; ++i) { + yup(i) {{operator}}= {{rhs.name}}{% if rhs == "Field3D" %}.yup(i){% endif %}; + ydown(i) {{operator}}= {{rhs.name}}{% if rhs == "Field3D" %}.ydown(i){% endif %}; + } + } else +#endif + { + clearParallelSlices(); + } {% endif %} checkData(*this); diff --git a/src/field/generated_fieldops.cxx b/src/field/generated_fieldops.cxx index 6b778acee3..72379b313c 100644 --- a/src/field/generated_fieldops.cxx +++ b/src/field/generated_fieldops.cxx @@ -15,6 +15,15 @@ Field3D operator*(const Field3D& lhs, const Field3D& rhs) { checkData(rhs); result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); +#if BOUT_USE_FCI_AUTOMAGIC + if (isFci(lhs) and lhs.hasParallelSlices() and rhs.hasParallelSlices()) { + result.splitParallelSlices(); + for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { + result.yup(i) = lhs.yup(i) * rhs.yup(i); + result.ydown(i) = lhs.ydown(i) * rhs.ydown(i); + } + } +#endif BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { result[index] = lhs[index] * rhs[index]; @@ -33,7 +42,17 @@ Field3D& Field3D::operator*=(const Field3D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. - clearParallelSlices(); +#if BOUT_USE_FCI_AUTOMAGIC + if (isFci(*this) and this->hasParallelSlices() and rhs.hasParallelSlices()) { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i) *= rhs.yup(i); + ydown(i) *= rhs.ydown(i); + } + } else +#endif + { + clearParallelSlices(); + } checkData(*this); checkData(rhs); @@ -59,6 +78,15 @@ Field3D operator/(const Field3D& lhs, const Field3D& rhs) { checkData(rhs); result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); +#if BOUT_USE_FCI_AUTOMAGIC + if (isFci(lhs) and lhs.hasParallelSlices() and rhs.hasParallelSlices()) { + result.splitParallelSlices(); + for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { + result.yup(i) = lhs.yup(i) / rhs.yup(i); + result.ydown(i) = lhs.ydown(i) / rhs.ydown(i); + } + } +#endif BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { result[index] = lhs[index] / rhs[index]; @@ -77,7 +105,17 @@ Field3D& Field3D::operator/=(const Field3D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. - clearParallelSlices(); +#if BOUT_USE_FCI_AUTOMAGIC + if (isFci(*this) and this->hasParallelSlices() and rhs.hasParallelSlices()) { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i) /= rhs.yup(i); + ydown(i) /= rhs.ydown(i); + } + } else +#endif + { + clearParallelSlices(); + } checkData(*this); checkData(rhs); @@ -103,6 +141,15 @@ Field3D operator+(const Field3D& lhs, const Field3D& rhs) { checkData(rhs); result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); +#if BOUT_USE_FCI_AUTOMAGIC + if (isFci(lhs) and lhs.hasParallelSlices() and rhs.hasParallelSlices()) { + result.splitParallelSlices(); + for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { + result.yup(i) = lhs.yup(i) + rhs.yup(i); + result.ydown(i) = lhs.ydown(i) + rhs.ydown(i); + } + } +#endif BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { result[index] = lhs[index] + rhs[index]; @@ -121,7 +168,17 @@ Field3D& Field3D::operator+=(const Field3D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. - clearParallelSlices(); +#if BOUT_USE_FCI_AUTOMAGIC + if (isFci(*this) and this->hasParallelSlices() and rhs.hasParallelSlices()) { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i) += rhs.yup(i); + ydown(i) += rhs.ydown(i); + } + } else +#endif + { + clearParallelSlices(); + } checkData(*this); checkData(rhs); @@ -147,6 +204,15 @@ Field3D operator-(const Field3D& lhs, const Field3D& rhs) { checkData(rhs); result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); +#if BOUT_USE_FCI_AUTOMAGIC + if (isFci(lhs) and lhs.hasParallelSlices() and rhs.hasParallelSlices()) { + result.splitParallelSlices(); + for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { + result.yup(i) = lhs.yup(i) - rhs.yup(i); + result.ydown(i) = lhs.ydown(i) - rhs.ydown(i); + } + } +#endif BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { result[index] = lhs[index] - rhs[index]; @@ -165,7 +231,17 @@ Field3D& Field3D::operator-=(const Field3D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. - clearParallelSlices(); +#if BOUT_USE_FCI_AUTOMAGIC + if (isFci(*this) and this->hasParallelSlices() and rhs.hasParallelSlices()) { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i) -= rhs.yup(i); + ydown(i) -= rhs.ydown(i); + } + } else +#endif + { + clearParallelSlices(); + } checkData(*this); checkData(rhs); @@ -214,7 +290,17 @@ Field3D& Field3D::operator*=(const Field2D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. - clearParallelSlices(); +#if BOUT_USE_FCI_AUTOMAGIC + if (isFci(*this) and this->hasParallelSlices()) { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i) *= rhs; + ydown(i) *= rhs; + } + } else +#endif + { + clearParallelSlices(); + } checkData(*this); checkData(rhs); @@ -267,7 +353,17 @@ Field3D& Field3D::operator/=(const Field2D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. - clearParallelSlices(); +#if BOUT_USE_FCI_AUTOMAGIC + if (isFci(*this) and this->hasParallelSlices()) { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i) /= rhs; + ydown(i) /= rhs; + } + } else +#endif + { + clearParallelSlices(); + } checkData(*this); checkData(rhs); @@ -320,7 +416,17 @@ Field3D& Field3D::operator+=(const Field2D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. - clearParallelSlices(); +#if BOUT_USE_FCI_AUTOMAGIC + if (isFci(*this) and this->hasParallelSlices()) { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i) += rhs; + ydown(i) += rhs; + } + } else +#endif + { + clearParallelSlices(); + } checkData(*this); checkData(rhs); @@ -372,7 +478,17 @@ Field3D& Field3D::operator-=(const Field2D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. - clearParallelSlices(); +#if BOUT_USE_FCI_AUTOMAGIC + if (isFci(*this) and this->hasParallelSlices()) { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i) -= rhs; + ydown(i) -= rhs; + } + } else +#endif + { + clearParallelSlices(); + } checkData(*this); checkData(rhs); @@ -497,7 +613,17 @@ Field3D& Field3D::operator*=(const BoutReal rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. - clearParallelSlices(); +#if BOUT_USE_FCI_AUTOMAGIC + if (isFci(*this) and this->hasParallelSlices()) { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i) *= rhs; + ydown(i) *= rhs; + } + } else +#endif + { + clearParallelSlices(); + } checkData(*this); checkData(rhs); @@ -538,7 +664,17 @@ Field3D& Field3D::operator/=(const BoutReal rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. - clearParallelSlices(); +#if BOUT_USE_FCI_AUTOMAGIC + if (isFci(*this) and this->hasParallelSlices()) { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i) /= rhs; + ydown(i) /= rhs; + } + } else +#endif + { + clearParallelSlices(); + } checkData(*this); checkData(rhs); @@ -579,7 +715,17 @@ Field3D& Field3D::operator+=(const BoutReal rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. - clearParallelSlices(); +#if BOUT_USE_FCI_AUTOMAGIC + if (isFci(*this) and this->hasParallelSlices()) { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i) += rhs; + ydown(i) += rhs; + } + } else +#endif + { + clearParallelSlices(); + } checkData(*this); checkData(rhs); @@ -619,7 +765,17 @@ Field3D& Field3D::operator-=(const BoutReal rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. - clearParallelSlices(); +#if BOUT_USE_FCI_AUTOMAGIC + if (isFci(*this) and this->hasParallelSlices()) { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i) -= rhs; + ydown(i) -= rhs; + } + } else +#endif + { + clearParallelSlices(); + } checkData(*this); checkData(rhs); From 4fac0185e8015cfa60ebf6b5e36ec3d3606be24a Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 6 Nov 2023 15:20:55 +0100 Subject: [PATCH 022/242] Explicitly set parallel boundary order --- examples/fci-wave-logn/boundary/BOUT.inp | 4 ++-- examples/fci-wave-logn/div-integrate/BOUT.inp | 2 +- examples/fci-wave-logn/expanded/BOUT.inp | 2 +- examples/fci-wave/div-integrate/BOUT.inp | 2 +- examples/fci-wave/div/BOUT.inp | 2 +- examples/fci-wave/logn/BOUT.inp | 2 +- manual/sphinx/user_docs/boundary_options.rst | 11 ++++++----- src/mesh/boundary_factory.cxx | 2 +- 8 files changed, 14 insertions(+), 13 deletions(-) diff --git a/examples/fci-wave-logn/boundary/BOUT.inp b/examples/fci-wave-logn/boundary/BOUT.inp index 11e57ec47d..a33fd07136 100644 --- a/examples/fci-wave-logn/boundary/BOUT.inp +++ b/examples/fci-wave-logn/boundary/BOUT.inp @@ -40,5 +40,5 @@ bndry_par_ydown = parallel_neumann [v] -bndry_par_yup = parallel_dirichlet(+1.0) -bndry_par_ydown = parallel_dirichlet(-1.0) +bndry_par_yup = parallel_dirichlet_o2(+1.0) +bndry_par_ydown = parallel_dirichlet_o2(-1.0) diff --git a/examples/fci-wave-logn/div-integrate/BOUT.inp b/examples/fci-wave-logn/div-integrate/BOUT.inp index a37bf3e2a5..22d2c00aa2 100644 --- a/examples/fci-wave-logn/div-integrate/BOUT.inp +++ b/examples/fci-wave-logn/div-integrate/BOUT.inp @@ -40,4 +40,4 @@ bndry_par_ydown = parallel_neumann [v] -bndry_par_all = parallel_dirichlet +bndry_par_all = parallel_dirichlet_o2 diff --git a/examples/fci-wave-logn/expanded/BOUT.inp b/examples/fci-wave-logn/expanded/BOUT.inp index 3a2935c6e8..347299ca12 100644 --- a/examples/fci-wave-logn/expanded/BOUT.inp +++ b/examples/fci-wave-logn/expanded/BOUT.inp @@ -40,4 +40,4 @@ bndry_par_ydown = parallel_neumann [v] -bndry_par_all = parallel_dirichlet +bndry_par_all = parallel_dirichlet_o2 diff --git a/examples/fci-wave/div-integrate/BOUT.inp b/examples/fci-wave/div-integrate/BOUT.inp index eb41d5f228..68bc1093c1 100644 --- a/examples/fci-wave/div-integrate/BOUT.inp +++ b/examples/fci-wave/div-integrate/BOUT.inp @@ -41,4 +41,4 @@ bndry_par_ydown = parallel_neumann [v] -bndry_par_all = parallel_dirichlet +bndry_par_all = parallel_dirichlet_o2 diff --git a/examples/fci-wave/div/BOUT.inp b/examples/fci-wave/div/BOUT.inp index 70b60757eb..b954dd94a9 100644 --- a/examples/fci-wave/div/BOUT.inp +++ b/examples/fci-wave/div/BOUT.inp @@ -41,4 +41,4 @@ bndry_par_ydown = parallel_neumann [v] -bndry_par_all = parallel_dirichlet +bndry_par_all = parallel_dirichlet_o2 diff --git a/examples/fci-wave/logn/BOUT.inp b/examples/fci-wave/logn/BOUT.inp index f97d8cc891..c2cfd46465 100644 --- a/examples/fci-wave/logn/BOUT.inp +++ b/examples/fci-wave/logn/BOUT.inp @@ -41,4 +41,4 @@ bndry_par_ydown = parallel_neumann [nv] -bndry_par_all = parallel_dirichlet +bndry_par_all = parallel_dirichlet_o2 diff --git a/manual/sphinx/user_docs/boundary_options.rst b/manual/sphinx/user_docs/boundary_options.rst index 57c6658891..826f873dc1 100644 --- a/manual/sphinx/user_docs/boundary_options.rst +++ b/manual/sphinx/user_docs/boundary_options.rst @@ -147,8 +147,9 @@ shifted``, see :ref:`sec-shifted-metric`), the recommended method is to apply boundary conditions directly to the ``yup`` and ``ydown`` parallel slices. This can be done by setting ``bndry_par_yup`` and ``bndry_par_ydown``, or ``bndry_par_all`` to set both at once. The -possible values are ``parallel_dirichlet``, ``parallel_dirichlet_O3`` -and ``parallel_neumann``. The stencils used are the same as for the +possible values are ``parallel_dirichlet_o1``, ``parallel_dirichlet_o2``, +``parallel_dirichlet_o3``, ``parallel_neumann_o1``, ``parallel_neumann_o2`` +and ``parallel_neumann_o3``. The stencils used are the same as for the standard boundary conditions without the ``parallel_`` prefix, but are applied directly to parallel slices. The boundary condition can only be applied after the parallel slices are calculated, which is usually @@ -168,7 +169,7 @@ For example, for an evolving variable ``f``, put a section in the [f] bndry_xin = dirichlet bndry_xout = dirichlet - bndry_par_all = parallel_neumann + bndry_par_all = parallel_neumann_o2 bndry_ydown = none bndry_yup = none @@ -278,7 +279,7 @@ cells of the base variable. For example, for an evolving variable [f] bndry_xin = dirichlet bndry_xout = dirichlet - bndry_par_all = parallel_dirichlet + bndry_par_all = parallel_dirichlet_o2 bndry_ydown = none bndry_yup = none @@ -289,7 +290,7 @@ communication, while the perpendicular ones before: f.applyBoundary(); mesh->communicate(f); - f.applyParallelBoundary("parallel_neumann"); + f.applyParallelBoundary("parallel_neumann_o2"); Note that during grid generation care has to be taken to ensure that there are no "short" connection lengths. Otherwise it can happen that for a point on a diff --git a/src/mesh/boundary_factory.cxx b/src/mesh/boundary_factory.cxx index 5f5978f132..35c8d845b9 100644 --- a/src/mesh/boundary_factory.cxx +++ b/src/mesh/boundary_factory.cxx @@ -314,7 +314,7 @@ BoundaryOpBase* BoundaryFactory::createFromOptions(const string& varname, /// Then (all, all) if (region->isParallel) { // Different default for parallel boundary regions - varOpts->get(prefix + "par_all", set, "parallel_dirichlet"); + varOpts->get(prefix + "par_all", set, "parallel_dirichlet_o2"); } else { varOpts->get(prefix + "all", set, "dirichlet"); } From 29a195f490a4323d2d93e0e5e2388ba2b2799d39 Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 6 Nov 2023 16:30:06 +0100 Subject: [PATCH 023/242] Make Field2d and Field3D more similar Useful for templates --- include/bout/field2d.hxx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/bout/field2d.hxx b/include/bout/field2d.hxx index 10b801ef8d..ee43a005d3 100644 --- a/include/bout/field2d.hxx +++ b/include/bout/field2d.hxx @@ -135,8 +135,9 @@ public: return *this; } - /// Check if this field has yup and ydown fields + /// Dummy functions to increase portability bool hasParallelSlices() const { return true; } + void calcParallelSlices() const {} Field2D& yup(std::vector::size_type UNUSED(index) = 0) { return *this; } const Field2D& yup(std::vector::size_type UNUSED(index) = 0) const { From 4ee86309b5c56b33020b83a5b11064c0b66d463b Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 6 Nov 2023 16:30:35 +0100 Subject: [PATCH 024/242] Do more things automagically --- src/field/field3d.cxx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index 011353f34a..bccd676ace 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -89,6 +89,15 @@ Field3D::Field3D(const BoutReal val, Mesh* localmesh) : Field3D(localmesh) { TRACE("Field3D: Copy constructor from value"); *this = val; +#if BOUT_USE_FCI_AUTOMAGIC + if (isFci(*this)) { + splitParallelSlices(); + for (size_t i=0; i data_in, Mesh* localmesh, CELL_LOC datalocation, @@ -341,6 +350,11 @@ Field3D& Field3D::operator=(const BoutReal val) { Field3D& Field3D::calcParallelSlices() { getCoordinates()->getParallelTransform().calcParallelSlices(*this); +#if BOUT_USE_FCI_AUTOMAGIC + if (isFci(*this)) { + this->applyParallelBoundary("parallel_neumann_o2"); + } +#endif return *this; } From 2e216be56995286e75df52f8b0f81e96b5ec9dd0 Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 6 Nov 2023 16:30:59 +0100 Subject: [PATCH 025/242] Allow DDY without parallel slices --- include/bout/index_derivs_interface.hxx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/include/bout/index_derivs_interface.hxx b/include/bout/index_derivs_interface.hxx index 8f7e41a68e..9e9288b564 100644 --- a/include/bout/index_derivs_interface.hxx +++ b/include/bout/index_derivs_interface.hxx @@ -200,9 +200,17 @@ template T DDY(const T& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT", const std::string& region = "RGN_NOBNDRY") { AUTO_TRACE(); - if (f.hasParallelSlices()) { + if (isFci(f)) { ASSERT1(f.getDirectionY() == YDirectionType::Standard); - return standardDerivative(f, outloc, + T f_tmp = f; + if (!f.hasParallelSlices()){ +#if BOUT_USE_FCI_AUTOMAGIC + f_tmp.calcParallelSlices(); +#else + raise BoutException("parallel slices needed for parallel derivatives. Make sure to communicate and apply parallel boundary conditions before calling derivative"); +#endif + } + return standardDerivative(f_tmp, outloc, method, region); } else { const bool is_unaligned = (f.getDirectionY() == YDirectionType::Standard); From faa1046809ebf8682ed65b2e243345a7323471d6 Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 18 Mar 2024 17:28:36 +0100 Subject: [PATCH 026/242] Add more fci-auto-magic --- include/bout/field.hxx | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/include/bout/field.hxx b/include/bout/field.hxx index c0693ec0fb..9b95e00437 100644 --- a/include/bout/field.hxx +++ b/include/bout/field.hxx @@ -677,7 +677,25 @@ inline T floor(const T& var, BoutReal f, const std::string& rgn = "RGN_ALL") { result[d] = f; } } - +#if BOUT_USE_FCI_AUTOMAGIC + if (isFci(var)) { + for (size_t i=0; i < result.numberParallelSlices(); ++i) { + BOUT_FOR(d, result.yup(i).getRegion(rgn)) { + if (result.yup(i)[d] < f) { + result.yup(i)[d] = f; + } + } + BOUT_FOR(d, result.ydown(i).getRegion(rgn)) { + if (result.ydown(i)[d] < f) { + result.ydown(i)[d] = f; + } + } + } + } else +#endif + { + result.clearParallelSlices(); + } return result; } From 414247b1c40e8ad7c7b5983f3c7d4ec56aa6444d Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 18 Mar 2024 17:29:04 +0100 Subject: [PATCH 027/242] Add copy function --- include/bout/field3d.hxx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index 964e3f096c..c29ecbfeca 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -663,4 +663,14 @@ bool operator==(const Field3D& a, const Field3D& b); /// Output a string describing a Field3D to a stream std::ostream& operator<<(std::ostream& out, const Field3D& value); +inline Field3D copy(const Field3D& f) { + Field3D result{f}; + result.allocate(); + for (size_t i = 0; i < result.numberParallelSlices(); ++i) { + result.yup(i).allocate(); + result.ydown(i).allocate(); + } + return result; +} + #endif /* BOUT_FIELD3D_H */ From 6ba17ed64e9207e00e1d292bb3cbca58fc817fdd Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 18 Mar 2024 17:29:18 +0100 Subject: [PATCH 028/242] Inherit applyParallelBoundary functions --- include/bout/field3d.hxx | 1 + 1 file changed, 1 insertion(+) diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index c29ecbfeca..7b8d0861ef 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -494,6 +494,7 @@ public: /// Note: does not just copy values in boundary region. void setBoundaryTo(const Field3D& f3d); + using FieldData::applyParallelBoundary; void applyParallelBoundary() override; void applyParallelBoundary(BoutReal t) override; void applyParallelBoundary(const std::string& condition) override; From b814c9bdb2a9df2f925f61f6fb8d2e892d056f3f Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 20 Mar 2024 14:21:28 +0100 Subject: [PATCH 029/242] update calls to isFci --- include/bout/index_derivs_interface.hxx | 2 +- src/field/field3d.cxx | 4 ++-- src/field/gen_fieldops.jinja | 4 ++-- src/field/generated_fieldops.cxx | 32 ++++++++++++------------- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/include/bout/index_derivs_interface.hxx b/include/bout/index_derivs_interface.hxx index 9e9288b564..86dd4c9287 100644 --- a/include/bout/index_derivs_interface.hxx +++ b/include/bout/index_derivs_interface.hxx @@ -200,7 +200,7 @@ template T DDY(const T& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT", const std::string& region = "RGN_NOBNDRY") { AUTO_TRACE(); - if (isFci(f)) { + if (f.isFci()) { ASSERT1(f.getDirectionY() == YDirectionType::Standard); T f_tmp = f; if (!f.hasParallelSlices()){ diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index bccd676ace..3430be008f 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -90,7 +90,7 @@ Field3D::Field3D(const BoutReal val, Mesh* localmesh) : Field3D(localmesh) { *this = val; #if BOUT_USE_FCI_AUTOMAGIC - if (isFci(*this)) { + if (this->isFci()) { splitParallelSlices(); for (size_t i=0; igetParallelTransform().calcParallelSlices(*this); #if BOUT_USE_FCI_AUTOMAGIC - if (isFci(*this)) { + if (this->isFci()) { this->applyParallelBoundary("parallel_neumann_o2"); } #endif diff --git a/src/field/gen_fieldops.jinja b/src/field/gen_fieldops.jinja index 6360cba783..dede7d120f 100644 --- a/src/field/gen_fieldops.jinja +++ b/src/field/gen_fieldops.jinja @@ -13,7 +13,7 @@ {{out.name}}.setRegion({{lhs.name}}.getMesh()->getCommonRegion({{lhs.name}}.getRegionID(), {{rhs.name}}.getRegionID())); #if BOUT_USE_FCI_AUTOMAGIC - if (isFci({{lhs.name}}) and {{lhs.name}}.hasParallelSlices() and {{rhs.name}}.hasParallelSlices()) { + if ({{lhs.name}}.isFci() and {{lhs.name}}.hasParallelSlices() and {{rhs.name}}.hasParallelSlices()) { {{out.name}}.splitParallelSlices(); for (size_t i{0} ; i < {{lhs.name}}.numberParallelSlices() ; ++i) { {{out.name}}.yup(i) = {{lhs.name}}.yup(i) {{operator}} {{rhs.name}}.yup(i); @@ -88,7 +88,7 @@ // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. #if BOUT_USE_FCI_AUTOMAGIC - if (isFci(*this) and this->hasParallelSlices() {% if rhs == "Field3D" %} and {{rhs.name}}.hasParallelSlices() {% endif %}) { + if (this->isFci() and this->hasParallelSlices() {% if rhs == "Field3D" %} and {{rhs.name}}.hasParallelSlices() {% endif %}) { for (size_t i{0} ; i < yup_fields.size() ; ++i) { yup(i) {{operator}}= {{rhs.name}}{% if rhs == "Field3D" %}.yup(i){% endif %}; ydown(i) {{operator}}= {{rhs.name}}{% if rhs == "Field3D" %}.ydown(i){% endif %}; diff --git a/src/field/generated_fieldops.cxx b/src/field/generated_fieldops.cxx index 72379b313c..74b319e314 100644 --- a/src/field/generated_fieldops.cxx +++ b/src/field/generated_fieldops.cxx @@ -16,7 +16,7 @@ Field3D operator*(const Field3D& lhs, const Field3D& rhs) { result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); #if BOUT_USE_FCI_AUTOMAGIC - if (isFci(lhs) and lhs.hasParallelSlices() and rhs.hasParallelSlices()) { + if (lhs.isFci() and lhs.hasParallelSlices() and rhs.hasParallelSlices()) { result.splitParallelSlices(); for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { result.yup(i) = lhs.yup(i) * rhs.yup(i); @@ -43,7 +43,7 @@ Field3D& Field3D::operator*=(const Field3D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. #if BOUT_USE_FCI_AUTOMAGIC - if (isFci(*this) and this->hasParallelSlices() and rhs.hasParallelSlices()) { + if (this->isFci() and this->hasParallelSlices() and rhs.hasParallelSlices()) { for (size_t i{0}; i < yup_fields.size(); ++i) { yup(i) *= rhs.yup(i); ydown(i) *= rhs.ydown(i); @@ -79,7 +79,7 @@ Field3D operator/(const Field3D& lhs, const Field3D& rhs) { result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); #if BOUT_USE_FCI_AUTOMAGIC - if (isFci(lhs) and lhs.hasParallelSlices() and rhs.hasParallelSlices()) { + if (lhs.isFci() and lhs.hasParallelSlices() and rhs.hasParallelSlices()) { result.splitParallelSlices(); for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { result.yup(i) = lhs.yup(i) / rhs.yup(i); @@ -106,7 +106,7 @@ Field3D& Field3D::operator/=(const Field3D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. #if BOUT_USE_FCI_AUTOMAGIC - if (isFci(*this) and this->hasParallelSlices() and rhs.hasParallelSlices()) { + if (this->isFci() and this->hasParallelSlices() and rhs.hasParallelSlices()) { for (size_t i{0}; i < yup_fields.size(); ++i) { yup(i) /= rhs.yup(i); ydown(i) /= rhs.ydown(i); @@ -142,7 +142,7 @@ Field3D operator+(const Field3D& lhs, const Field3D& rhs) { result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); #if BOUT_USE_FCI_AUTOMAGIC - if (isFci(lhs) and lhs.hasParallelSlices() and rhs.hasParallelSlices()) { + if (lhs.isFci() and lhs.hasParallelSlices() and rhs.hasParallelSlices()) { result.splitParallelSlices(); for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { result.yup(i) = lhs.yup(i) + rhs.yup(i); @@ -169,7 +169,7 @@ Field3D& Field3D::operator+=(const Field3D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. #if BOUT_USE_FCI_AUTOMAGIC - if (isFci(*this) and this->hasParallelSlices() and rhs.hasParallelSlices()) { + if (this->isFci() and this->hasParallelSlices() and rhs.hasParallelSlices()) { for (size_t i{0}; i < yup_fields.size(); ++i) { yup(i) += rhs.yup(i); ydown(i) += rhs.ydown(i); @@ -205,7 +205,7 @@ Field3D operator-(const Field3D& lhs, const Field3D& rhs) { result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); #if BOUT_USE_FCI_AUTOMAGIC - if (isFci(lhs) and lhs.hasParallelSlices() and rhs.hasParallelSlices()) { + if (lhs.isFci() and lhs.hasParallelSlices() and rhs.hasParallelSlices()) { result.splitParallelSlices(); for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { result.yup(i) = lhs.yup(i) - rhs.yup(i); @@ -232,7 +232,7 @@ Field3D& Field3D::operator-=(const Field3D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. #if BOUT_USE_FCI_AUTOMAGIC - if (isFci(*this) and this->hasParallelSlices() and rhs.hasParallelSlices()) { + if (this->isFci() and this->hasParallelSlices() and rhs.hasParallelSlices()) { for (size_t i{0}; i < yup_fields.size(); ++i) { yup(i) -= rhs.yup(i); ydown(i) -= rhs.ydown(i); @@ -291,7 +291,7 @@ Field3D& Field3D::operator*=(const Field2D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. #if BOUT_USE_FCI_AUTOMAGIC - if (isFci(*this) and this->hasParallelSlices()) { + if (this->isFci() and this->hasParallelSlices()) { for (size_t i{0}; i < yup_fields.size(); ++i) { yup(i) *= rhs; ydown(i) *= rhs; @@ -354,7 +354,7 @@ Field3D& Field3D::operator/=(const Field2D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. #if BOUT_USE_FCI_AUTOMAGIC - if (isFci(*this) and this->hasParallelSlices()) { + if (this->isFci() and this->hasParallelSlices()) { for (size_t i{0}; i < yup_fields.size(); ++i) { yup(i) /= rhs; ydown(i) /= rhs; @@ -417,7 +417,7 @@ Field3D& Field3D::operator+=(const Field2D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. #if BOUT_USE_FCI_AUTOMAGIC - if (isFci(*this) and this->hasParallelSlices()) { + if (this->isFci() and this->hasParallelSlices()) { for (size_t i{0}; i < yup_fields.size(); ++i) { yup(i) += rhs; ydown(i) += rhs; @@ -479,7 +479,7 @@ Field3D& Field3D::operator-=(const Field2D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. #if BOUT_USE_FCI_AUTOMAGIC - if (isFci(*this) and this->hasParallelSlices()) { + if (this->isFci() and this->hasParallelSlices()) { for (size_t i{0}; i < yup_fields.size(); ++i) { yup(i) -= rhs; ydown(i) -= rhs; @@ -614,7 +614,7 @@ Field3D& Field3D::operator*=(const BoutReal rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. #if BOUT_USE_FCI_AUTOMAGIC - if (isFci(*this) and this->hasParallelSlices()) { + if (this->isFci() and this->hasParallelSlices()) { for (size_t i{0}; i < yup_fields.size(); ++i) { yup(i) *= rhs; ydown(i) *= rhs; @@ -665,7 +665,7 @@ Field3D& Field3D::operator/=(const BoutReal rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. #if BOUT_USE_FCI_AUTOMAGIC - if (isFci(*this) and this->hasParallelSlices()) { + if (this->isFci() and this->hasParallelSlices()) { for (size_t i{0}; i < yup_fields.size(); ++i) { yup(i) /= rhs; ydown(i) /= rhs; @@ -716,7 +716,7 @@ Field3D& Field3D::operator+=(const BoutReal rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. #if BOUT_USE_FCI_AUTOMAGIC - if (isFci(*this) and this->hasParallelSlices()) { + if (this->isFci() and this->hasParallelSlices()) { for (size_t i{0}; i < yup_fields.size(); ++i) { yup(i) += rhs; ydown(i) += rhs; @@ -766,7 +766,7 @@ Field3D& Field3D::operator-=(const BoutReal rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. #if BOUT_USE_FCI_AUTOMAGIC - if (isFci(*this) and this->hasParallelSlices()) { + if (this->isFci() and this->hasParallelSlices()) { for (size_t i{0}; i < yup_fields.size(); ++i) { yup(i) -= rhs; ydown(i) -= rhs; From 4a6fdba0b8fd02dd67b38f16fd8d716a18a1fb85 Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 20 Mar 2024 14:56:28 +0100 Subject: [PATCH 030/242] Fix remaining usage of free isFci function --- include/bout/field.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/bout/field.hxx b/include/bout/field.hxx index b4524b5347..1ed5ab2a5f 100644 --- a/include/bout/field.hxx +++ b/include/bout/field.hxx @@ -680,7 +680,7 @@ inline T floor(const T& var, BoutReal f, const std::string& rgn = "RGN_ALL") { } } #if BOUT_USE_FCI_AUTOMAGIC - if (isFci(var)) { + if (var.isFci()) { for (size_t i=0; i < result.numberParallelSlices(); ++i) { BOUT_FOR(d, result.yup(i).getRegion(rgn)) { if (result.yup(i)[d] < f) { From 2f7c3c0664c954c016b949ea8c199f6f35ac289f Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 20 Mar 2024 16:02:22 +0100 Subject: [PATCH 031/242] Workaround for gcc 9.4 gcc 9.4 is unable to correctly parse the construction for the function argument, if name.change is used directly. First making a copy seems to work around that issue. --- src/field/field3d.cxx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index 2196d6eea4..e0d4dda01d 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -848,10 +848,14 @@ Options* Field3D::track(const T& change, std::string operation) { if (tracking != nullptr and tracking_state != 0) { const std::string outname{fmt::format("track_{:s}_{:d}", selfname, tracking_state++)}; tracking->set(outname, change, "tracking"); + // Workaround for bug in gcc9.4 +#if BOUT_USE_TRACK + const std::string changename = change.name; +#endif (*tracking)[outname].setAttributes({ {"operation", operation}, #if BOUT_USE_TRACK - {"rhs.name", change.name}, + {"rhs.name", changename}, #endif }); return &(*tracking)[outname]; From 413e54f0ba4cc360420c1e15566b43fa7e015535 Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 20 Mar 2024 16:48:28 +0100 Subject: [PATCH 032/242] Fixup porting to shared_ptr --- src/mesh/parallel/fci.hxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mesh/parallel/fci.hxx b/src/mesh/parallel/fci.hxx index c78080d9e9..751a177c0e 100644 --- a/src/mesh/parallel/fci.hxx +++ b/src/mesh/parallel/fci.hxx @@ -101,8 +101,8 @@ public: backward_boundary_xout, zperiodic); } ASSERT0(mesh.ystart == 1); - BoundaryRegionPar* bndries[]{forward_boundary_xin, forward_boundary_xout, - backward_boundary_xin, backward_boundary_xout}; + std::shared_ptr bndries[]{forward_boundary_xin, forward_boundary_xout, + backward_boundary_xin, backward_boundary_xout}; for (auto bndry : bndries) { for (auto bndry2 : bndries) { if (bndry->dir == bndry2->dir) { From d5d7c6a5e783f5da6b4f9fc11489b7616f13c6e3 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 26 Mar 2024 11:21:37 +0100 Subject: [PATCH 033/242] Update include to moved location --- src/mesh/impls/bout/boutmesh.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mesh/impls/bout/boutmesh.cxx b/src/mesh/impls/bout/boutmesh.cxx index 684c8afc40..59f936d265 100644 --- a/src/mesh/impls/bout/boutmesh.cxx +++ b/src/mesh/impls/bout/boutmesh.cxx @@ -49,8 +49,8 @@ #include #include -#include "boundary_region.hxx" -#include "parallel_boundary_region.hxx" +#include "bout/boundary_region.hxx" +#include "bout/parallel_boundary_region.hxx" #include #include From 652be61a9c5fedb228ce75468abe5bdbad62ba97 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 26 Mar 2024 11:44:19 +0100 Subject: [PATCH 034/242] Move iteration mostly to iterator This allows to use range-based-for loop on the iterator --- include/bout/parallel_boundary_op.hxx | 41 ++-- include/bout/parallel_boundary_region.hxx | 249 ++++++++++++++++------ src/mesh/parallel_boundary_op.cxx | 7 +- 3 files changed, 208 insertions(+), 89 deletions(-) diff --git a/include/bout/parallel_boundary_op.hxx b/include/bout/parallel_boundary_op.hxx index d8620e892b..9e551ebc17 100644 --- a/include/bout/parallel_boundary_op.hxx +++ b/include/bout/parallel_boundary_op.hxx @@ -49,7 +49,7 @@ protected: enum class ValueType { GEN, FIELD, REAL }; const ValueType value_type{ValueType::REAL}; - BoutReal getValue(const BoundaryRegionPar& bndry, BoutReal t); + BoutReal getValue(const BoundaryRegionParIter& bndry, BoutReal t); }; template @@ -95,12 +95,13 @@ public: auto dy = f.getCoordinates()->dy; - for (bndry->first(); !bndry->isDone(); bndry->next()) { - BoutReal value = getValue(*bndry, t); + for (auto pnt : *bndry) { + //for (bndry->first(); !bndry->isDone(); bndry->next()) { + BoutReal value = getValue(pnt, t); if (isNeumann) { - value *= dy[bndry->ind()]; + value *= dy[pnt.ind()]; } - static_cast(this)->apply_stencil(f, bndry, value); + static_cast(this)->apply_stencil(f, pnt, value); } } }; @@ -111,24 +112,27 @@ public: class BoundaryOpPar_dirichlet_o1 : public BoundaryOpParTemp { public: using BoundaryOpParTemp::BoundaryOpParTemp; - static void apply_stencil(Field3D& f, const BoundaryRegionPar* bndry, BoutReal value) { - bndry->dirichlet_o1(f, value); + static void apply_stencil(Field3D& f, const BoundaryRegionParIter& pnt, + BoutReal value) { + pnt.dirichlet_o1(f, value); } }; class BoundaryOpPar_dirichlet_o2 : public BoundaryOpParTemp { public: using BoundaryOpParTemp::BoundaryOpParTemp; - static void apply_stencil(Field3D& f, const BoundaryRegionPar* bndry, BoutReal value) { - bndry->dirichlet_o2(f, value); + static void apply_stencil(Field3D& f, const BoundaryRegionParIter& pnt, + BoutReal value) { + pnt.dirichlet_o2(f, value); } }; class BoundaryOpPar_dirichlet_o3 : public BoundaryOpParTemp { public: using BoundaryOpParTemp::BoundaryOpParTemp; - static void apply_stencil(Field3D& f, const BoundaryRegionPar* bndry, BoutReal value) { - bndry->dirichlet_o3(f, value); + static void apply_stencil(Field3D& f, const BoundaryRegionParIter& pnt, + BoutReal value) { + pnt.dirichlet_o3(f, value); } }; @@ -136,8 +140,9 @@ class BoundaryOpPar_neumann_o1 : public BoundaryOpParTemp { public: using BoundaryOpParTemp::BoundaryOpParTemp; - static void apply_stencil(Field3D& f, const BoundaryRegionPar* bndry, BoutReal value) { - bndry->neumann_o1(f, value); + static void apply_stencil(Field3D& f, const BoundaryRegionParIter& pnt, + BoutReal value) { + pnt.neumann_o1(f, value); } }; @@ -145,8 +150,9 @@ class BoundaryOpPar_neumann_o2 : public BoundaryOpParTemp { public: using BoundaryOpParTemp::BoundaryOpParTemp; - static void apply_stencil(Field3D& f, const BoundaryRegionPar* bndry, BoutReal value) { - bndry->neumann_o2(f, value); + static void apply_stencil(Field3D& f, const BoundaryRegionParIter& pnt, + BoutReal value) { + pnt.neumann_o2(f, value); } }; @@ -154,8 +160,9 @@ class BoundaryOpPar_neumann_o3 : public BoundaryOpParTemp { public: using BoundaryOpParTemp::BoundaryOpParTemp; - static void apply_stencil(Field3D& f, const BoundaryRegionPar* bndry, BoutReal value) { - bndry->neumann_o3(f, value); + static void apply_stencil(Field3D& f, const BoundaryRegionParIter& pnt, + BoutReal value) { + pnt.neumann_o3(f, value); } }; diff --git a/include/bout/parallel_boundary_region.hxx b/include/bout/parallel_boundary_region.hxx index 308b5ac5d7..7831d1af82 100644 --- a/include/bout/parallel_boundary_region.hxx +++ b/include/bout/parallel_boundary_region.hxx @@ -3,6 +3,7 @@ #include "bout/boundary_region.hxx" #include "bout/bout_types.hxx" +#include #include #include @@ -52,61 +53,41 @@ inline BoutReal neumann_o3(BoutReal spacing0, BoutReal value0, BoutReal spacing1 } } // namespace parallel_stencil -class BoundaryRegionPar : public BoundaryRegionBase { +namespace bout { +namespace parallel_boundary_region { - struct RealPoint { - BoutReal s_x; - BoutReal s_y; - BoutReal s_z; - }; - - struct Indices { - // Indices of the boundary point - Ind3D index; - // Intersection with boundary in index space - RealPoint intersection; - // Distance to intersection - BoutReal length; - // Angle between field line and boundary - // BoutReal angle; - // How many points we can go in the opposite direction - signed char valid; - }; - - using IndicesVec = std::vector; - using IndicesIter = IndicesVec::iterator; +struct RealPoint { + BoutReal s_x; + BoutReal s_y; + BoutReal s_z; +}; - /// Vector of points in the boundary - IndicesVec bndry_points; - /// Current position in the boundary points - IndicesIter bndry_position; +struct Indices { + // Indices of the boundary point + Ind3D index; + // Intersection with boundary in index space + RealPoint intersection; + // Distance to intersection + BoutReal length; + // Angle between field line and boundary + // BoutReal angle; + // How many points we can go in the opposite direction + signed char valid; +}; -public: - BoundaryRegionPar(const std::string& name, int dir, Mesh* passmesh) - : BoundaryRegionBase(name, passmesh), dir(dir) { - ASSERT0(std::abs(dir) == 1); - BoundaryRegionBase::isParallel = true; - } - BoundaryRegionPar(const std::string& name, BndryLoc loc, int dir, Mesh* passmesh) - : BoundaryRegionBase(name, loc, passmesh), dir(dir) { - BoundaryRegionBase::isParallel = true; - ASSERT0(std::abs(dir) == 1); - } +using IndicesVec = std::vector; +using IndicesIter = IndicesVec::iterator; +using IndicesIterConst = IndicesVec::const_iterator; - /// Add a point to the boundary - void add_point(Ind3D ind, BoutReal x, BoutReal y, BoutReal z, BoutReal length, - char valid) { - bndry_points.push_back({ind, {x, y, z}, length, valid}); - } - void add_point(int ix, int iy, int iz, BoutReal x, BoutReal y, BoutReal z, - BoutReal length, char valid) { - bndry_points.push_back({xyz2ind(ix, iy, iz, localmesh), {x, y, z}, length, valid}); - } +//} - // final, so they can be inlined - void first() final { bndry_position = begin(bndry_points); } - void next() final { ++bndry_position; } - bool isDone() final { return (bndry_position == end(bndry_points)); } +template +class BoundaryRegionParIterBase { +public: + BoundaryRegionParIterBase(IndicesVec& bndry_points, IndicesIter bndry_position, int dir, + Mesh* localmesh) + : bndry_points(bndry_points), bndry_position(bndry_position), dir(dir), + localmesh(localmesh){}; // getter Ind3D ind() const { return bndry_position->index; } @@ -116,23 +97,73 @@ public: BoutReal length() const { return bndry_position->length; } char valid() const { return bndry_position->valid; } - // setter - void setValid(char val) { bndry_position->valid = val; } + // extrapolate a given point to the boundary + BoutReal extrapolate_sheath_o1(const Field3D& f) const { return f[ind()]; } + BoutReal extrapolate_sheath_o2(const Field3D& f) const { + ASSERT3(valid() >= 0); + if (valid() < 1) { + return extrapolate_sheath_o1(f); + } + return f[ind()] * (1 + length()) - f.ynext(-dir)[ind().yp(-dir)] * length(); + } + inline BoutReal + extrapolate_sheath_o1(const std::function& f) const { + return f(0, ind()); + } + inline BoutReal + extrapolate_sheath_o2(const std::function& f) const { + ASSERT3(valid() >= 0); + if (valid() < 1) { + return extrapolate_sheath_o1(f); + } + return f(0, ind()) * (1 + length()) - f(-dir, ind().yp(-dir)) * length(); + } - bool contains(const BoundaryRegionPar& bndry) const { - return std::binary_search( - begin(bndry_points), end(bndry_points), *bndry.bndry_position, - [](const Indices& i1, const Indices& i2) { return i1.index < i2.index; }); + inline BoutReal interpolate_sheath(const Field3D& f) const { + return f[ind()] * (1 - length()) + ynext(f) * length(); } - // extrapolate a given point to the boundary - BoutReal extrapolate_o1(const Field3D& f) const { return f[ind()]; } - BoutReal extrapolate_o2(const Field3D& f) const { + inline BoutReal extrapolate_next_o1(const Field3D& f) const { return f[ind()]; } + inline BoutReal extrapolate_next_o2(const Field3D& f) const { ASSERT3(valid() >= 0); if (valid() < 1) { - return extrapolate_o1(f); + return extrapolate_next_o1(f); } - return f[ind()] * (1 + length()) - f.ynext(-dir)[ind().yp(-dir)] * length(); + return f[ind()] * 2 - f.ynext(-dir)[ind().yp(-dir)]; + } + + inline BoutReal + extrapolate_next_o1(const std::function& f) const { + return f(0, ind()); + } + inline BoutReal + extrapolate_next_o2(const std::function& f) const { + ASSERT3(valid() >= 0); + if (valid() < 1) { + return extrapolate_sheath_o1(f); + } + return f(0, ind()) * 2 - f(-dir, ind().yp(-dir)); + } + + // extrapolate the gradient into the boundary + inline BoutReal extrapolate_grad_o1(const Field3D& f) const { return 0; } + inline BoutReal extrapolate_grad_o2(const Field3D& f) const { + ASSERT3(valid() >= 0); + if (valid() < 1) { + return extrapolate_grad_o1(f); + } + return f[ind()] - f.ynext(-dir)[ind().yp(-dir)]; + } + + BoundaryRegionParIterBase& operator*() { return *this; } + + BoundaryRegionParIterBase& operator++() { + ++bndry_position; + return *this; + } + + bool operator!=(const BoundaryRegionParIterBase& rhs) { + return bndry_position != rhs.bndry_position; } // dirichlet boundary code @@ -185,16 +216,100 @@ public: parallel_stencil::neumann_o3(1 - length(), value, 1, f[ind()], 2, yprev(f)); } - const int dir; + // BoutReal get(const Field3D& f, int off) + const BoutReal& ynext(const Field3D& f) const { return f.ynext(dir)[ind().yp(dir)]; } + BoutReal& ynext(Field3D& f) const { return f.ynext(dir)[ind().yp(dir)]; } + + const BoutReal& yprev(const Field3D& f) const { + ASSERT3(valid() > 0); + return f.ynext(-dir)[ind().yp(-dir)]; + } + BoutReal& yprev(Field3D& f) const { + ASSERT3(valid() > 0); + return f.ynext(-dir)[ind().yp(-dir)]; + } private: + const IndicesVec& bndry_points; + IndicesIter bndry_position; + constexpr static BoutReal small_value = 1e-2; - // BoutReal get(const Field3D& f, int off) - const BoutReal& ynext(const Field3D& f) const { return f.ynext(dir)[ind().yp(dir)]; } - BoutReal& ynext(Field3D& f) const { return f.ynext(dir)[ind().yp(dir)]; } - const BoutReal& yprev(const Field3D& f) const { return f.ynext(-dir)[ind().yp(-dir)]; } - BoutReal& yprev(Field3D& f) const { return f.ynext(-dir)[ind().yp(-dir)]; } +public: + const int dir; + Mesh* localmesh; +}; +} // namespace parallel_boundary_region +} // namespace bout +using BoundaryRegionParIter = bout::parallel_boundary_region::BoundaryRegionParIterBase< + bout::parallel_boundary_region::IndicesVec, + bout::parallel_boundary_region::IndicesIter>; +using BoundaryRegionParIterConst = + bout::parallel_boundary_region::BoundaryRegionParIterBase< + const bout::parallel_boundary_region::IndicesVec, + bout::parallel_boundary_region::IndicesIterConst>; + +class BoundaryRegionPar : public BoundaryRegionBase { +public: + BoundaryRegionPar(const std::string& name, int dir, Mesh* passmesh) + : BoundaryRegionBase(name, passmesh), dir(dir) { + ASSERT0(std::abs(dir) == 1); + BoundaryRegionBase::isParallel = true; + } + BoundaryRegionPar(const std::string& name, BndryLoc loc, int dir, Mesh* passmesh) + : BoundaryRegionBase(name, loc, passmesh), dir(dir) { + BoundaryRegionBase::isParallel = true; + ASSERT0(std::abs(dir) == 1); + } + + /// Add a point to the boundary + void add_point(Ind3D ind, BoutReal x, BoutReal y, BoutReal z, BoutReal length, + char valid) { + bndry_points.push_back({ind, {x, y, z}, length, valid}); + } + void add_point(int ix, int iy, int iz, BoutReal x, BoutReal y, BoutReal z, + BoutReal length, char valid) { + bndry_points.push_back({xyz2ind(ix, iy, iz, localmesh), {x, y, z}, length, valid}); + } + + // final, so they can be inlined + void first() final { bndry_position = std::begin(bndry_points); } + void next() final { ++bndry_position; } + bool isDone() final { return (bndry_position == std::end(bndry_points)); } + + bool contains(const BoundaryRegionPar& bndry) const { + return std::binary_search(std::begin(bndry_points), std::end(bndry_points), + *bndry.bndry_position, + [](const bout::parallel_boundary_region::Indices& i1, + const bout::parallel_boundary_region::Indices& i2) { + return i1.index < i2.index; + }); + } + + // setter + void setValid(char val) { bndry_position->valid = val; } + + // BoundaryRegionParIterConst begin() const { + // return BoundaryRegionParIterConst(bndry_points, bndry_points.begin(), dir); + // } + // BoundaryRegionParIterConst end() const { + // return BoundaryRegionParIterConst(bndry_points, bndry_points.begin(), dir); + // } + BoundaryRegionParIter begin() { + return BoundaryRegionParIter(bndry_points, bndry_points.begin(), dir, localmesh); + } + BoundaryRegionParIter end() { + return BoundaryRegionParIter(bndry_points, bndry_points.end(), dir, localmesh); + } + + const int dir; + +private: + /// Vector of points in the boundary + bout::parallel_boundary_region::IndicesVec bndry_points; + /// Current position in the boundary points + bout::parallel_boundary_region::IndicesIter bndry_position; + static Ind3D xyz2ind(int x, int y, int z, Mesh* mesh) { const int ny = mesh->LocalNy; const int nz = mesh->LocalNz; diff --git a/src/mesh/parallel_boundary_op.cxx b/src/mesh/parallel_boundary_op.cxx index ebd9852791..df164ce43f 100644 --- a/src/mesh/parallel_boundary_op.cxx +++ b/src/mesh/parallel_boundary_op.cxx @@ -5,17 +5,14 @@ #include "bout/mesh.hxx" #include "bout/output.hxx" -BoutReal BoundaryOpPar::getValue(const BoundaryRegionPar& bndry, BoutReal t) { - BoutReal value; - +BoutReal BoundaryOpPar::getValue(const BoundaryRegionParIter& bndry, BoutReal t) { switch (value_type) { case ValueType::GEN: return gen_values->generate(bout::generator::Context( bndry.s_x(), bndry.s_y(), bndry.s_z(), CELL_CENTRE, bndry.localmesh, t)); case ValueType::FIELD: // FIXME: Interpolate to s_x, s_y, s_z... - value = (*field_values)[bndry.ind()]; - return value; + return (*field_values)[bndry.ind()]; case ValueType::REAL: return real_value; default: From 7d48dbd339311e068de5afab6e112356b18cd156 Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 27 Mar 2024 13:29:58 +0100 Subject: [PATCH 035/242] Move stencils to separte header Makes it easier to reuse for other code --- include/bout/parallel_boundary_region.hxx | 39 +---------------------- include/bout/sys/parallel_stencils.hxx | 39 +++++++++++++++++++++++ 2 files changed, 40 insertions(+), 38 deletions(-) create mode 100644 include/bout/sys/parallel_stencils.hxx diff --git a/include/bout/parallel_boundary_region.hxx b/include/bout/parallel_boundary_region.hxx index 7831d1af82..07150a55b3 100644 --- a/include/bout/parallel_boundary_region.hxx +++ b/include/bout/parallel_boundary_region.hxx @@ -6,6 +6,7 @@ #include #include +#include "bout/sys/parallel_stencils.hxx" #include #include @@ -15,44 +16,6 @@ * */ -namespace parallel_stencil { -// generated by src/mesh/parallel_boundary_stencil.cxx.py -inline BoutReal pow(BoutReal val, int exp) { - // constexpr int expval = exp; - // static_assert(expval == 2 or expval == 3, "This pow is only for exponent 2 or 3"); - if (exp == 2) { - return val * val; - } - ASSERT3(exp == 3); - return val * val * val; -} -inline BoutReal dirichlet_o1(BoutReal UNUSED(spacing0), BoutReal value0) { - return value0; -} -inline BoutReal dirichlet_o2(BoutReal spacing0, BoutReal value0, BoutReal spacing1, - BoutReal value1) { - return (spacing0 * value1 - spacing1 * value0) / (spacing0 - spacing1); -} -inline BoutReal neumann_o2(BoutReal UNUSED(spacing0), BoutReal value0, BoutReal spacing1, - BoutReal value1) { - return -spacing1 * value0 + value1; -} -inline BoutReal dirichlet_o3(BoutReal spacing0, BoutReal value0, BoutReal spacing1, - BoutReal value1, BoutReal spacing2, BoutReal value2) { - return (pow(spacing0, 2) * spacing1 * value2 - pow(spacing0, 2) * spacing2 * value1 - - spacing0 * pow(spacing1, 2) * value2 + spacing0 * pow(spacing2, 2) * value1 - + pow(spacing1, 2) * spacing2 * value0 - spacing1 * pow(spacing2, 2) * value0) - / ((spacing0 - spacing1) * (spacing0 - spacing2) * (spacing1 - spacing2)); -} -inline BoutReal neumann_o3(BoutReal spacing0, BoutReal value0, BoutReal spacing1, - BoutReal value1, BoutReal spacing2, BoutReal value2) { - return (2 * spacing0 * spacing1 * value2 - 2 * spacing0 * spacing2 * value1 - + pow(spacing1, 2) * spacing2 * value0 - pow(spacing1, 2) * value2 - - spacing1 * pow(spacing2, 2) * value0 + pow(spacing2, 2) * value1) - / ((spacing1 - spacing2) * (2 * spacing0 - spacing1 - spacing2)); -} -} // namespace parallel_stencil - namespace bout { namespace parallel_boundary_region { diff --git a/include/bout/sys/parallel_stencils.hxx b/include/bout/sys/parallel_stencils.hxx new file mode 100644 index 0000000000..34a51c5285 --- /dev/null +++ b/include/bout/sys/parallel_stencils.hxx @@ -0,0 +1,39 @@ +#pragma once + +namespace parallel_stencil { +// generated by src/mesh/parallel_boundary_stencil.cxx.py +inline BoutReal pow(BoutReal val, int exp) { + // constexpr int expval = exp; + // static_assert(expval == 2 or expval == 3, "This pow is only for exponent 2 or 3"); + if (exp == 2) { + return val * val; + } + ASSERT3(exp == 3); + return val * val * val; +} +inline BoutReal dirichlet_o1(BoutReal UNUSED(spacing0), BoutReal value0) { + return value0; +} +inline BoutReal dirichlet_o2(BoutReal spacing0, BoutReal value0, BoutReal spacing1, + BoutReal value1) { + return (spacing0 * value1 - spacing1 * value0) / (spacing0 - spacing1); +} +inline BoutReal neumann_o2(BoutReal UNUSED(spacing0), BoutReal value0, BoutReal spacing1, + BoutReal value1) { + return -spacing1 * value0 + value1; +} +inline BoutReal dirichlet_o3(BoutReal spacing0, BoutReal value0, BoutReal spacing1, + BoutReal value1, BoutReal spacing2, BoutReal value2) { + return (pow(spacing0, 2) * spacing1 * value2 - pow(spacing0, 2) * spacing2 * value1 + - spacing0 * pow(spacing1, 2) * value2 + spacing0 * pow(spacing2, 2) * value1 + + pow(spacing1, 2) * spacing2 * value0 - spacing1 * pow(spacing2, 2) * value0) + / ((spacing0 - spacing1) * (spacing0 - spacing2) * (spacing1 - spacing2)); +} +inline BoutReal neumann_o3(BoutReal spacing0, BoutReal value0, BoutReal spacing1, + BoutReal value1, BoutReal spacing2, BoutReal value2) { + return (2 * spacing0 * spacing1 * value2 - 2 * spacing0 * spacing2 * value1 + + pow(spacing1, 2) * spacing2 * value0 - pow(spacing1, 2) * value2 + - spacing1 * pow(spacing2, 2) * value0 + pow(spacing2, 2) * value1) + / ((spacing1 - spacing2) * (2 * spacing0 - spacing1 - spacing2)); +} +} // namespace parallel_stencil From 59cd39dd915ba4e8cb028c31a1b8ae4dfe3f427b Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 27 Mar 2024 13:30:54 +0100 Subject: [PATCH 036/242] Add more dummy functions to field2d Allows to write code for Field3D, that also works for Field2D --- include/bout/field2d.hxx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/bout/field2d.hxx b/include/bout/field2d.hxx index ee43a005d3..cd036c04ff 100644 --- a/include/bout/field2d.hxx +++ b/include/bout/field2d.hxx @@ -138,6 +138,8 @@ public: /// Dummy functions to increase portability bool hasParallelSlices() const { return true; } void calcParallelSlices() const {} + void clearParallelSlices() {} + int numberParallelSlices() { return 0; } Field2D& yup(std::vector::size_type UNUSED(index) = 0) { return *this; } const Field2D& yup(std::vector::size_type UNUSED(index) = 0) const { @@ -281,7 +283,7 @@ public: friend void swap(Field2D& first, Field2D& second) noexcept; - int size() const override { return nx * ny; }; + int size() const override { return nx * ny; } private: /// Internal data array. Handles allocation/freeing of memory From 127fc9a756379c1033331bacb525ec73ec356e3a Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 27 Mar 2024 13:40:07 +0100 Subject: [PATCH 037/242] Add basic boundary region iterator Mimicks the parallel case, to write FCI independent code --- include/bout/boundary_region.hxx | 93 ++++++++++++++++++++++++++++++++ src/mesh/boundary_region.cxx | 3 ++ 2 files changed, 96 insertions(+) diff --git a/include/bout/boundary_region.hxx b/include/bout/boundary_region.hxx index 58de12045e..6e7a939d3c 100644 --- a/include/bout/boundary_region.hxx +++ b/include/bout/boundary_region.hxx @@ -4,6 +4,9 @@ class BoundaryRegion; #ifndef BOUT_BNDRY_REGION_H #define BOUT_BNDRY_REGION_H +#include "bout/mesh.hxx" +#include "bout/region.hxx" +#include "bout/sys/parallel_stencils.hxx" #include #include @@ -62,6 +65,7 @@ public: isDone() = 0; ///< Returns true if outside domain. Can use this with nested nextX, nextY }; +class BoundaryRegionIter; /// Describes a region of the boundary, and a means of iterating over it class BoundaryRegion : public BoundaryRegionBase { public: @@ -80,6 +84,95 @@ public: virtual void next1d() = 0; ///< Loop over the innermost elements virtual void nextX() = 0; ///< Just loop over X virtual void nextY() = 0; ///< Just loop over Y + + BoundaryRegionIter begin(); + BoundaryRegionIter end(); +}; + +class BoundaryRegionIter { +public: + BoundaryRegionIter(BoundaryRegion* rgn, bool is_end) + : rgn(rgn), is_end(is_end), dir(rgn->bx + rgn->by) { + //static_assert(std::is_base_of, "BoundaryRegionIter only works on BoundaryRegion"); + + // Ensure only one is non-zero + ASSERT3(rgn->bx * rgn->by == 0); + if (!is_end) { + rgn->first(); + } + } + bool operator!=(const BoundaryRegionIter& rhs) { + if (is_end) { + if (rhs.is_end || rhs.rgn->isDone()) { + return false; + } else { + return true; + } + } + if (rhs.is_end) { + return !rgn->isDone(); + } + return ind() != rhs.ind(); + } + + Ind3D ind() const { return xyz2ind(rgn->x - rgn->bx, rgn->y - rgn->by, z); } + BoundaryRegionIter& operator++() { + ASSERT3(z < nz()); + z++; + if (z == nz()) { + z = 0; + rgn->next(); + } + return *this; + } + BoundaryRegionIter& operator*() { return *this; } + + void dirichlet_o2(Field3D& f, BoutReal value) const { + ynext(f) = parallel_stencil::dirichlet_o2(1, f[ind()], 0.5, value); + } + + BoutReal extrapolate_grad_o2(const Field3D& f) const { return f[ind()] - yprev(f); } + + BoutReal extrapolate_sheath_o2(const Field3D& f) const { + return (f[ind()] * 3 - yprev(f)) * 0.5; + } + + BoutReal extrapolate_next_o2(const Field3D& f) const { return 2 * f[ind()] - yprev(f); } + + BoutReal + extrapolate_next_o2(const std::function& f) const { + return 2 * f(0, ind()) - f(0, ind().yp(-rgn->by).xp(-rgn->bx)); + } + + BoutReal interpolate_sheath(const Field3D& f) const { + return (f[ind()] + ynext(f)) * 0.5; + } + + BoutReal& ynext(Field3D& f) const { return f[ind().yp(rgn->by).xp(rgn->bx)]; } + const BoutReal& ynext(const Field3D& f) const { + return f[ind().yp(rgn->by).xp(rgn->bx)]; + } + BoutReal& yprev(Field3D& f) const { return f[ind().yp(-rgn->by).xp(-rgn->bx)]; } + const BoutReal& yprev(const Field3D& f) const { + return f[ind().yp(-rgn->by).xp(-rgn->bx)]; + } + +private: + BoundaryRegion* rgn; + const bool is_end; + int z{0}; + +public: + const int dir; + +private: + int nx() const { return rgn->localmesh->LocalNx; } + int ny() const { return rgn->localmesh->LocalNy; } + int nz() const { return rgn->localmesh->LocalNz; } + + Ind3D xyz2ind(int x, int y, int z) const { + return Ind3D{(x * ny() + y) * nz() + z, ny(), nz()}; + } }; class BoundaryRegionXIn : public BoundaryRegion { diff --git a/src/mesh/boundary_region.cxx b/src/mesh/boundary_region.cxx index 700ef8a91f..ef4aa13a66 100644 --- a/src/mesh/boundary_region.cxx +++ b/src/mesh/boundary_region.cxx @@ -202,3 +202,6 @@ void BoundaryRegionYUp::nextY() { bool BoundaryRegionYUp::isDone() { return (x > xe) || (y >= localmesh->LocalNy); // Return true if gone out of the boundary } + +BoundaryRegionIter BoundaryRegion::begin() { return BoundaryRegionIter(this, false); } +BoundaryRegionIter BoundaryRegion::end() { return BoundaryRegionIter(this, true); } From 9b68bf2820a80c316b8391e9780533831d900cad Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 9 Apr 2024 14:40:17 +0200 Subject: [PATCH 038/242] Provide a new boundary iterator based on RangeIterator The boundary region does not match what is done for the parallel case, thus porting it to a iterator does not work. --- include/bout/boundary_iterator.hxx | 117 +++++++++++++++++++++++++++++ include/bout/boundary_region.hxx | 90 ---------------------- 2 files changed, 117 insertions(+), 90 deletions(-) create mode 100644 include/bout/boundary_iterator.hxx diff --git a/include/bout/boundary_iterator.hxx b/include/bout/boundary_iterator.hxx new file mode 100644 index 0000000000..93f02c004d --- /dev/null +++ b/include/bout/boundary_iterator.hxx @@ -0,0 +1,117 @@ +#pragma once + +#include "bout/mesh.hxx" +#include "bout/sys/parallel_stencils.hxx" +#include "bout/sys/range.hxx" + +class BoundaryRegionIter { +public: + BoundaryRegionIter(int x, int y, int bx, int by, Mesh* mesh) + : dir(bx + by), x(x), y(y), bx(bx), by(by), localmesh(mesh) { + ASSERT3(bx * by == 0); + } + bool operator!=(const BoundaryRegionIter& rhs) { return ind() != rhs.ind(); } + + Ind3D ind() const { return xyz2ind(x, y, z); } + BoundaryRegionIter& operator++() { + ASSERT3(z < nz()); + z++; + if (z == nz()) { + z = 0; + _next(); + } + return *this; + } + virtual void _next() = 0; + BoundaryRegionIter& operator*() { return *this; } + + void dirichlet_o2(Field3D& f, BoutReal value) const { + ynext(f) = parallel_stencil::dirichlet_o2(1, f[ind()], 0.5, value); + } + + BoutReal extrapolate_grad_o2(const Field3D& f) const { return f[ind()] - yprev(f); } + + BoutReal extrapolate_sheath_o2(const Field3D& f) const { + return (f[ind()] * 3 - yprev(f)) * 0.5; + } + + BoutReal extrapolate_next_o2(const Field3D& f) const { return 2 * f[ind()] - yprev(f); } + + BoutReal + extrapolate_next_o2(const std::function& f) const { + return 2 * f(0, ind()) - f(0, ind().yp(-by).xp(-bx)); + } + + BoutReal interpolate_sheath(const Field3D& f) const { + return (f[ind()] + ynext(f)) * 0.5; + } + + BoutReal& ynext(Field3D& f) const { return f[ind().yp(by).xp(bx)]; } + const BoutReal& ynext(const Field3D& f) const { return f[ind().yp(by).xp(bx)]; } + BoutReal& yprev(Field3D& f) const { return f[ind().yp(-by).xp(-bx)]; } + const BoutReal& yprev(const Field3D& f) const { return f[ind().yp(-by).xp(-bx)]; } + + const int dir; + +protected: + int z{0}; + int x; + int y; + const int bx; + const int by; + +private: + Mesh* localmesh; + int nx() const { return localmesh->LocalNx; } + int ny() const { return localmesh->LocalNy; } + int nz() const { return localmesh->LocalNz; } + + Ind3D xyz2ind(int x, int y, int z) const { + return Ind3D{(x * ny() + y) * nz() + z, ny(), nz()}; + } +}; + +class BoundaryRegionIterY : public BoundaryRegionIter { +public: + BoundaryRegionIterY(RangeIterator r, int y, int dir, bool is_end, Mesh* mesh) + : BoundaryRegionIter(r.ind, y, 0, dir, mesh), r(r), is_end(is_end) {} + + bool operator!=(const BoundaryRegionIterY& rhs) { + ASSERT2(y == rhs.y); + if (is_end) { + if (rhs.is_end) { + return false; + } + return !rhs.r.isDone(); + } + if (rhs.is_end) { + return !r.isDone(); + } + return x != rhs.x; + } + + virtual void _next() override { + ++r; + x = r.ind; + } + +private: + RangeIterator r; + bool is_end; +}; + +class NewBoundaryRegionY { +public: + NewBoundaryRegionY(Mesh* mesh, bool lower, RangeIterator r) + : mesh(mesh), lower(lower), r(std::move(r)) {} + BoundaryRegionIterY begin(bool begin = true) { + return BoundaryRegionIterY(r, lower ? mesh->ystart : mesh->yend, lower ? -1 : +1, + !begin, mesh); + } + BoundaryRegionIterY end() { return begin(false); } + +private: + Mesh* mesh; + bool lower; + RangeIterator r; +}; diff --git a/include/bout/boundary_region.hxx b/include/bout/boundary_region.hxx index 6e7a939d3c..22956d1d4a 100644 --- a/include/bout/boundary_region.hxx +++ b/include/bout/boundary_region.hxx @@ -65,7 +65,6 @@ public: isDone() = 0; ///< Returns true if outside domain. Can use this with nested nextX, nextY }; -class BoundaryRegionIter; /// Describes a region of the boundary, and a means of iterating over it class BoundaryRegion : public BoundaryRegionBase { public: @@ -84,95 +83,6 @@ public: virtual void next1d() = 0; ///< Loop over the innermost elements virtual void nextX() = 0; ///< Just loop over X virtual void nextY() = 0; ///< Just loop over Y - - BoundaryRegionIter begin(); - BoundaryRegionIter end(); -}; - -class BoundaryRegionIter { -public: - BoundaryRegionIter(BoundaryRegion* rgn, bool is_end) - : rgn(rgn), is_end(is_end), dir(rgn->bx + rgn->by) { - //static_assert(std::is_base_of, "BoundaryRegionIter only works on BoundaryRegion"); - - // Ensure only one is non-zero - ASSERT3(rgn->bx * rgn->by == 0); - if (!is_end) { - rgn->first(); - } - } - bool operator!=(const BoundaryRegionIter& rhs) { - if (is_end) { - if (rhs.is_end || rhs.rgn->isDone()) { - return false; - } else { - return true; - } - } - if (rhs.is_end) { - return !rgn->isDone(); - } - return ind() != rhs.ind(); - } - - Ind3D ind() const { return xyz2ind(rgn->x - rgn->bx, rgn->y - rgn->by, z); } - BoundaryRegionIter& operator++() { - ASSERT3(z < nz()); - z++; - if (z == nz()) { - z = 0; - rgn->next(); - } - return *this; - } - BoundaryRegionIter& operator*() { return *this; } - - void dirichlet_o2(Field3D& f, BoutReal value) const { - ynext(f) = parallel_stencil::dirichlet_o2(1, f[ind()], 0.5, value); - } - - BoutReal extrapolate_grad_o2(const Field3D& f) const { return f[ind()] - yprev(f); } - - BoutReal extrapolate_sheath_o2(const Field3D& f) const { - return (f[ind()] * 3 - yprev(f)) * 0.5; - } - - BoutReal extrapolate_next_o2(const Field3D& f) const { return 2 * f[ind()] - yprev(f); } - - BoutReal - extrapolate_next_o2(const std::function& f) const { - return 2 * f(0, ind()) - f(0, ind().yp(-rgn->by).xp(-rgn->bx)); - } - - BoutReal interpolate_sheath(const Field3D& f) const { - return (f[ind()] + ynext(f)) * 0.5; - } - - BoutReal& ynext(Field3D& f) const { return f[ind().yp(rgn->by).xp(rgn->bx)]; } - const BoutReal& ynext(const Field3D& f) const { - return f[ind().yp(rgn->by).xp(rgn->bx)]; - } - BoutReal& yprev(Field3D& f) const { return f[ind().yp(-rgn->by).xp(-rgn->bx)]; } - const BoutReal& yprev(const Field3D& f) const { - return f[ind().yp(-rgn->by).xp(-rgn->bx)]; - } - -private: - BoundaryRegion* rgn; - const bool is_end; - int z{0}; - -public: - const int dir; - -private: - int nx() const { return rgn->localmesh->LocalNx; } - int ny() const { return rgn->localmesh->LocalNy; } - int nz() const { return rgn->localmesh->LocalNz; } - - Ind3D xyz2ind(int x, int y, int z) const { - return Ind3D{(x * ny() + y) * nz() + z, ny(), nz()}; - } }; class BoundaryRegionXIn : public BoundaryRegion { From d611758ae6b02a93e51eeeaebe025b628a23c9b3 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 9 Apr 2024 15:15:30 +0200 Subject: [PATCH 039/242] Add option to debug on failure --- src/solver/impls/pvode/pvode.cxx | 80 ++++++++++++++++++-------------- src/solver/impls/pvode/pvode.hxx | 9 +++- 2 files changed, 52 insertions(+), 37 deletions(-) diff --git a/src/solver/impls/pvode/pvode.cxx b/src/solver/impls/pvode/pvode.cxx index db28f64d86..9dce5d357f 100644 --- a/src/solver/impls/pvode/pvode.cxx +++ b/src/solver/impls/pvode/pvode.cxx @@ -247,6 +247,11 @@ int PvodeSolver::init() { .doc("Use Adams Moulton solver instead of BDF") .withDefault(false)); + debug_on_failure = + (*options)["debug_on_failure"] + .doc("Run an aditional rhs if the solver fails with extra tracking") + .withDefault(false); + cvode_mem = CVodeMalloc(neq, solver_f, simtime, u, use_adam ? ADAMS : BDF, NEWTON, SS, &reltol, &abstol, this, nullptr, optIn, iopt, ropt, machEnv); @@ -354,47 +359,52 @@ BoutReal PvodeSolver::run(BoutReal tout) { if (flag != SUCCESS) { output_error.write("ERROR CVODE step failed, flag = {:d}\n", flag); CVodeMemRec* cv_mem = (CVodeMem)cvode_mem; - if (f2d.empty() and v2d.empty() and v3d.empty()) { - Options debug{}; - using namespace std::string_literals; - Mesh* mesh{}; - for (const auto& prefix : {"pre_"s, "residuum_"s}) { - std::vector list_of_fields{}; - std::vector evolve_bndrys{}; - for (const auto& f : f3d) { - mesh = f.var->getMesh(); - Field3D to_load{0., mesh}; - to_load.allocate(); - to_load.setLocation(f.location); - debug[fmt::format("{:s}{:s}", prefix, f.name)] = to_load; - list_of_fields.push_back(to_load); - evolve_bndrys.push_back(f.evolve_bndry); + if (debug_on_failure) { + if (f2d.empty() and v2d.empty() and v3d.empty()) { + Options debug{}; + using namespace std::string_literals; + Mesh* mesh{}; + for (const auto& prefix : {"pre_"s, "residuum_"s}) { + std::vector list_of_fields{}; + std::vector evolve_bndrys{}; + for (const auto& f : f3d) { + mesh = f.var->getMesh(); + Field3D to_load{0., mesh}; + to_load.allocate(); + to_load.setLocation(f.location); + debug[fmt::format("{:s}{:s}", prefix, f.name)] = to_load; + list_of_fields.push_back(to_load); + evolve_bndrys.push_back(f.evolve_bndry); + } + pvode_load_data_f3d(evolve_bndrys, list_of_fields, + prefix == "pre_"s ? udata : N_VDATA(cv_mem->cv_acor)); } - pvode_load_data_f3d(evolve_bndrys, list_of_fields, - prefix == "pre_"s ? udata : N_VDATA(cv_mem->cv_acor)); - } - for (auto& f : f3d) { - f.F_var->enableTracking(fmt::format("ddt_{:s}", f.name), debug); - setName(*f.var, f.name); - } - run_rhs(simtime); + for (auto& f : f3d) { + f.F_var->enableTracking(fmt::format("ddt_{:s}", f.name), debug); + setName(*f.var, f.name); + } + run_rhs(simtime); - for (auto& f : f3d) { - debug[f.name] = *f.var; - } + for (auto& f : f3d) { + debug[f.name] = *f.var; + } - if (mesh != nullptr) { - mesh->outputVars(debug); - debug["BOUT_VERSION"].force(bout::version::as_double); - } + if (mesh != nullptr) { + mesh->outputVars(debug); + debug["BOUT_VERSION"].force(bout::version::as_double); + } - const std::string outname = fmt::format( - "{}/BOUT.debug.{}.nc", - Options::root()["datadir"].withDefault("data"), BoutComm::rank()); + const std::string outname = + fmt::format("{}/BOUT.debug.{}.nc", + Options::root()["datadir"].withDefault("data"), + BoutComm::rank()); - bout::OptionsIO::create(outname)->write(debug); - MPI_Barrier(BoutComm::get()); + bout::OptionsIO::create(outname)->write(debug); + MPI_Barrier(BoutComm::get()); + } else { + output_warn.write("debug_on_failure is currently only supported for Field3Ds"); + } } return (-1.0); } diff --git a/src/solver/impls/pvode/pvode.hxx b/src/solver/impls/pvode/pvode.hxx index d29135d02e..3b385af99d 100644 --- a/src/solver/impls/pvode/pvode.hxx +++ b/src/solver/impls/pvode/pvode.hxx @@ -75,10 +75,15 @@ private: pvode::machEnvType machEnv{nullptr}; void* cvode_mem{nullptr}; - BoutReal abstol, reltol; // addresses passed in init must be preserved + BoutReal abstol, reltol; + // addresses passed in init must be preserved pvode::PVBBDData pdata{nullptr}; - bool pvode_initialised = false; + /// is pvode already initialised? + bool pvode_initialised{false}; + + /// Add debugging data if solver fails + bool debug_on_failure{false}; }; #endif // BOUT_PVODE_SOLVER_H From db96b7ecc3df8613a80203d59a421bf4186f25d1 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 9 Apr 2024 15:56:25 +0200 Subject: [PATCH 040/242] Add option to euler solver to dump debug info --- src/solver/impls/euler/euler.cxx | 36 +++++++++++++++++++++++++++++++- src/solver/impls/euler/euler.hxx | 2 ++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/solver/impls/euler/euler.cxx b/src/solver/impls/euler/euler.cxx index 3976f4402c..45ba5ccdbf 100644 --- a/src/solver/impls/euler/euler.cxx +++ b/src/solver/impls/euler/euler.cxx @@ -20,7 +20,10 @@ EulerSolver::EulerSolver(Options* options) .withDefault(2.)), timestep((*options)["timestep"] .doc("Internal timestep (defaults to output timestep)") - .withDefault(getOutputTimestep())) {} + .withDefault(getOutputTimestep())), + dump_at_time((*options)["dump_at_time"] + .doc("Dump debug info about the simulation") + .withDefault(-1)) {} void EulerSolver::setMaxTimestep(BoutReal dt) { if (dt >= cfl_factor * timestep) { @@ -141,7 +144,38 @@ void EulerSolver::take_step(BoutReal curtime, BoutReal dt, Array& star Array& result) { load_vars(std::begin(start)); + const bool dump_now = dump_at_time > 0 && std::abs(dump_at_time - curtime) < dt; + std::unique_ptr debug_ptr; + if (dump_now) { + debug_ptr = std::make_unique(); + Options& debug = *debug_ptr; + for (auto& f : f3d) { + f.F_var->enableTracking(fmt::format("ddt_{:s}", f.name), debug); + setName(*f.var, f.name); + } + } + run_rhs(curtime); + if (dump_now) { + Options& debug = *debug_ptr; + Mesh* mesh; + for (auto& f : f3d) { + debug[f.name] = *f.var; + mesh = f.var->getMesh(); + } + + if (mesh != nullptr) { + mesh->outputVars(debug); + debug["BOUT_VERSION"].force(bout::version::as_double); + } + + const std::string outname = fmt::format( + "{}/BOUT.debug.{}.nc", + Options::root()["datadir"].withDefault("data"), BoutComm::rank()); + + bout::OptionsIO::create(outname)->write(debug); + MPI_Barrier(BoutComm::get()); + } save_derivs(std::begin(result)); BOUT_OMP_PERF(parallel for) diff --git a/src/solver/impls/euler/euler.hxx b/src/solver/impls/euler/euler.hxx index 0ee81a3d33..4b6dc60a62 100644 --- a/src/solver/impls/euler/euler.hxx +++ b/src/solver/impls/euler/euler.hxx @@ -64,6 +64,8 @@ private: /// Take a single step to calculate f1 void take_step(BoutReal curtime, BoutReal dt, Array& start, Array& result); + + BoutReal dump_at_time{-1.}; }; #endif // BOUT_KARNIADAKIS_SOLVER_H From 6f3bbf5ca9672c729aeb70d5fd02e2b49f857141 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 2 Jul 2024 14:37:46 +0200 Subject: [PATCH 041/242] Fall back to non fv div_par for fci --- include/bout/fv_ops.hxx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/bout/fv_ops.hxx b/include/bout/fv_ops.hxx index 94007a57a2..97558ddcfb 100644 --- a/include/bout/fv_ops.hxx +++ b/include/bout/fv_ops.hxx @@ -11,6 +11,7 @@ #include "bout/utils.hxx" #include +#include namespace FV { /*! @@ -192,6 +193,12 @@ template const Field3D Div_par(const Field3D& f_in, const Field3D& v_in, const Field3D& wave_speed_in, bool fixflux = true) { +#if BOUT_USE_FCI_AUTOMAGIC + if (f_in.isFci()) { + return ::Div_par(f_in, v_in); + } +#endif + ASSERT1_FIELDS_COMPATIBLE(f_in, v_in); ASSERT1_FIELDS_COMPATIBLE(f_in, wave_speed_in); From a73080d8d55914973a03a189f85154af7578bbfa Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 2 Jul 2024 14:38:47 +0200 Subject: [PATCH 042/242] Add isFci also to mesh --- include/bout/mesh.hxx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/include/bout/mesh.hxx b/include/bout/mesh.hxx index c80716fc12..a3a36ad933 100644 --- a/include/bout/mesh.hxx +++ b/include/bout/mesh.hxx @@ -828,6 +828,17 @@ public: ASSERT1(RegionID.has_value()); return region3D[RegionID.value()]; } + bool isFci() const { + const auto coords = this->getCoordinatesConst(); + if (coords == nullptr) { + return false; + } + if (not coords->hasParallelTransform()) { + return false; + } + return not coords->getParallelTransform().canToFromFieldAligned(); + } + private: /// Allocates default Coordinates objects From d8cf334cec39a07e045ad63b0a27050011f259a5 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 2 Jul 2024 14:41:42 +0200 Subject: [PATCH 043/242] Only check hasBndry*Y if they would be included hasBndryUpperY / hasBndryLowerY does not work for FCI and thus the request does not make sense / can be configured to throw. Thus it should not be checked if it is not needed. --- src/invert/laplace/invert_laplace.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/invert/laplace/invert_laplace.cxx b/src/invert/laplace/invert_laplace.cxx index 505b04cc4f..963a8763d2 100644 --- a/src/invert/laplace/invert_laplace.cxx +++ b/src/invert/laplace/invert_laplace.cxx @@ -226,10 +226,10 @@ Field3D Laplacian::solve(const Field3D& b, const Field3D& x0) { // Setting the start and end range of the y-slices int ys = localmesh->ystart, ye = localmesh->yend; - if (localmesh->hasBndryLowerY() && include_yguards) { + if (include_yguards && localmesh->hasBndryLowerY()) { ys = 0; // Mesh contains a lower boundary } - if (localmesh->hasBndryUpperY() && include_yguards) { + if (include_yguards && localmesh->hasBndryUpperY()) { ye = localmesh->LocalNy - 1; // Contains upper boundary } From 98da34a442f3411406f17ab576261f4fe3383102 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 2 Jul 2024 15:38:01 +0200 Subject: [PATCH 044/242] Add option to disable tracking --- include/bout/field3d.hxx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index b8ea64a738..99359a4d4f 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -306,6 +306,13 @@ public: /// Save all changes that, are done to the field, to tracking Field3D& enableTracking(const std::string& name, Options& tracking); + /// Disable tracking + Field3D& disableTracking() { + tracking = nullptr; + tracking_state = 0; + return *this; + } + ///////////////////////////////////////////////////////// // Data access From e25884e16d5475ad855771f3c9db2f3370f6f425 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 2 Jul 2024 15:39:11 +0200 Subject: [PATCH 045/242] DEBUG: add debug statements for regionID --- include/bout/field3d.hxx | 6 +++--- src/field/field3d.cxx | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index 99359a4d4f..2a37d9e0c8 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -335,9 +335,9 @@ public: /// Use region provided by the default, and if none is set, use the provided one const Region& getValidRegionWithDefault(const std::string& region_name) const; void setRegion(const std::string& region_name); - void resetRegion() { regionID.reset(); }; - void setRegion(size_t id) { regionID = id; }; - void setRegion(std::optional id) { regionID = id; }; + void resetRegion(); + void setRegion(size_t id); + void setRegion(std::optional id); std::optional getRegionID() const { return regionID; }; /// Return a Region reference to use to iterate over the x- and diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index cfbc4ba2fa..7c39c5aff1 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -47,6 +47,26 @@ #include #include + +#include "bout/output.hxx" +#include + +namespace fmt { +template +struct formatter> : fmt::formatter { + + template + auto format(const std::optional& opt, FormatContext& ctx) { + if (opt) { + fmt::formatter::format(opt.value(), ctx); + return ctx.out(); + } + return fmt::format_to(ctx.out(), "NO VALUE"); + } +}; +} // namespace fmt + + /// Constructor Field3D::Field3D(Mesh* localmesh, CELL_LOC location_in, DirectionTypes directions_in) : Field(localmesh, location_in, directions_in) { @@ -850,7 +870,22 @@ Field3D::getValidRegionWithDefault(const std::string& region_name) const { void Field3D::setRegion(const std::string& region_name) { regionID = fieldmesh->getRegionID(region_name); -} + output.write("{:p}: set {} {}\n", static_cast(this), regionID, region_name); +} + +void Field3D::resetRegion() { + regionID.reset(); + output.write("{:p}: reset\n", static_cast(this)); +}; +void Field3D::setRegion(size_t id) { + regionID = id; + //output.write("{:p}: set {:d}\n", static_cast(this), regionID); + output.write("{:p}: set {}\n", static_cast(this), regionID); +}; +void Field3D::setRegion(std::optional id) { + regionID = id; + output.write("{:p}: set {}\n", static_cast(this), regionID); +}; Field3D& Field3D::enableTracking(const std::string& name, Options& _tracking) { tracking = &_tracking; From 6b6ee52b74853750b012d643411070bcd4c6c63c Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 5 Jul 2024 11:38:16 +0200 Subject: [PATCH 046/242] Fix: preserve regionID --- src/field/field3d.cxx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index 7c39c5aff1..af21f7d784 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -84,7 +84,8 @@ Field3D::Field3D(Mesh* localmesh, CELL_LOC location_in, DirectionTypes direction /// Doesn't copy any data, just create a new reference to the same data (copy on change /// later) Field3D::Field3D(const Field3D& f) - : Field(f), data(f.data), yup_fields(f.yup_fields), ydown_fields(f.ydown_fields) { + : Field(f), data(f.data), yup_fields(f.yup_fields), ydown_fields(f.ydown_fields), + regionID(f.regionID) { TRACE("Field3D(Field3D&)"); @@ -282,6 +283,7 @@ Field3D& Field3D::operator=(const Field3D& rhs) { // Copy parallel slices or delete existing ones. yup_fields = rhs.yup_fields; ydown_fields = rhs.ydown_fields; + regionID = rhs.regionID; // Copy the data and data sizes nx = rhs.nx; @@ -305,6 +307,7 @@ Field3D& Field3D::operator=(Field3D&& rhs) { nx = rhs.nx; ny = rhs.ny; nz = rhs.nz; + regionID = rhs.regionID; data = std::move(rhs.data); @@ -324,6 +327,7 @@ Field3D& Field3D::operator=(const Field2D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. clearParallelSlices(); + resetRegion(); setLocation(rhs.getLocation()); @@ -351,6 +355,7 @@ void Field3D::operator=(const FieldPerp& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. clearParallelSlices(); + resetRegion(); /// Make sure there's a unique array to copy data into allocate(); @@ -366,6 +371,7 @@ Field3D& Field3D::operator=(const BoutReal val) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. clearParallelSlices(); + resetRegion(); allocate(); From 03c98daf5a0b41cd4f68d005711c34be4612811b Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 5 Jul 2024 11:41:02 +0200 Subject: [PATCH 047/242] Fix OOB read/write in boutmesh Previously fortran indexing was used. This resulted in reads and writes one past the last index. Valgrind has complained about this ever since I started, but I only noticed now that it was a genuine bug and not something MPI related. --- src/mesh/impls/bout/boutmesh.cxx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/mesh/impls/bout/boutmesh.cxx b/src/mesh/impls/bout/boutmesh.cxx index 59f936d265..da00fbf11e 100644 --- a/src/mesh/impls/bout/boutmesh.cxx +++ b/src/mesh/impls/bout/boutmesh.cxx @@ -2303,8 +2303,8 @@ int BoutMesh::pack_data(const std::vector& var_list, int xge, int xl ASSERT2(var3d_ref.isAllocated()); for (int jx = xge; jx != xlt; jx++) { for (int jy = yge; jy < ylt; jy++) { - for (int jz = 0; jz < LocalNz; jz++, len++) { - buffer[len] = var3d_ref(jx, jy, jz); + for (int jz = 0; jz < LocalNz; jz++) { + buffer[len++] = var3d_ref(jx, jy, jz); } } } @@ -2313,8 +2313,8 @@ int BoutMesh::pack_data(const std::vector& var_list, int xge, int xl auto& var2d_ref = *dynamic_cast(var); ASSERT2(var2d_ref.isAllocated()); for (int jx = xge; jx != xlt; jx++) { - for (int jy = yge; jy < ylt; jy++, len++) { - buffer[len] = var2d_ref(jx, jy); + for (int jy = yge; jy < ylt; jy++) { + buffer[len++] = var2d_ref(jx, jy); } } } @@ -2335,8 +2335,8 @@ int BoutMesh::unpack_data(const std::vector& var_list, int xge, int auto& var3d_ref = *dynamic_cast(var); for (int jx = xge; jx != xlt; jx++) { for (int jy = yge; jy < ylt; jy++) { - for (int jz = 0; jz < LocalNz; jz++, len++) { - var3d_ref(jx, jy, jz) = buffer[len]; + for (int jz = 0; jz < LocalNz; jz++) { + var3d_ref(jx, jy, jz) = buffer[len++]; } } } @@ -2344,8 +2344,8 @@ int BoutMesh::unpack_data(const std::vector& var_list, int xge, int // 2D variable auto& var2d_ref = *dynamic_cast(var); for (int jx = xge; jx != xlt; jx++) { - for (int jy = yge; jy < ylt; jy++, len++) { - var2d_ref(jx, jy) = buffer[len]; + for (int jy = yge; jy < ylt; jy++) { + var2d_ref(jx, jy) = buffer[len++]; } } } From 8bf48400778856d94a7f127c15248a97a2c2f053 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 5 Jul 2024 11:41:52 +0200 Subject: [PATCH 048/242] Ensure pointer is checked before dereferencing dynamic_cast can return a nullptr, thus we should check. Otherwise gcc raises a warning. --- src/mesh/impls/bout/boutmesh.cxx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/mesh/impls/bout/boutmesh.cxx b/src/mesh/impls/bout/boutmesh.cxx index da00fbf11e..83e12f88b8 100644 --- a/src/mesh/impls/bout/boutmesh.cxx +++ b/src/mesh/impls/bout/boutmesh.cxx @@ -2299,7 +2299,9 @@ int BoutMesh::pack_data(const std::vector& var_list, int xge, int xl for (const auto& var : var_list) { if (var->is3D()) { // 3D variable - auto& var3d_ref = *dynamic_cast(var); + auto var3d_ref_ptr = dynamic_cast(var); + ASSERT0(var3d_ref_ptr != nullptr); + auto& var3d_ref = *var3d_ref_ptr; ASSERT2(var3d_ref.isAllocated()); for (int jx = xge; jx != xlt; jx++) { for (int jy = yge; jy < ylt; jy++) { @@ -2310,7 +2312,9 @@ int BoutMesh::pack_data(const std::vector& var_list, int xge, int xl } } else { // 2D variable - auto& var2d_ref = *dynamic_cast(var); + auto var2d_ref_ptr = dynamic_cast(var); + ASSERT0(var2d_ref_ptr != nullptr); + auto& var2d_ref = *var2d_ref_ptr; ASSERT2(var2d_ref.isAllocated()); for (int jx = xge; jx != xlt; jx++) { for (int jy = yge; jy < ylt; jy++) { From e39774a575714f40b5c98c88c65c3211f118d993 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 5 Jul 2024 11:42:18 +0200 Subject: [PATCH 049/242] ensure we dont mix non-fci BCs with fci --- src/mesh/impls/bout/boutmesh.cxx | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/mesh/impls/bout/boutmesh.cxx b/src/mesh/impls/bout/boutmesh.cxx index 83e12f88b8..dcd5c8bc85 100644 --- a/src/mesh/impls/bout/boutmesh.cxx +++ b/src/mesh/impls/bout/boutmesh.cxx @@ -2821,6 +2821,9 @@ void BoutMesh::addBoundaryRegions() { } RangeIterator BoutMesh::iterateBndryLowerInnerY() const { + if (this->isFci()) { + throw BoutException("FCI should never use this iterator"); + } int xs = 0; int xe = LocalNx - 1; @@ -2856,6 +2859,9 @@ RangeIterator BoutMesh::iterateBndryLowerInnerY() const { } RangeIterator BoutMesh::iterateBndryLowerOuterY() const { + if (this->isFci()) { + throw BoutException("FCI should never use this iterator"); + } int xs = 0; int xe = LocalNx - 1; @@ -2890,6 +2896,10 @@ RangeIterator BoutMesh::iterateBndryLowerOuterY() const { } RangeIterator BoutMesh::iterateBndryLowerY() const { + if (this->isFci()) { + throw BoutException("FCI should never use this iterator"); + } + int xs = 0; int xe = LocalNx - 1; if ((DDATA_INDEST >= 0) && (DDATA_XSPLIT > xstart)) { @@ -2919,6 +2929,10 @@ RangeIterator BoutMesh::iterateBndryLowerY() const { } RangeIterator BoutMesh::iterateBndryUpperInnerY() const { + if (this->isFci()) { + throw BoutException("FCI should never use this iterator"); + } + int xs = 0; int xe = LocalNx - 1; @@ -2953,6 +2967,10 @@ RangeIterator BoutMesh::iterateBndryUpperInnerY() const { } RangeIterator BoutMesh::iterateBndryUpperOuterY() const { + if (this->isFci()) { + throw BoutException("FCI should never use this iterator"); + } + int xs = 0; int xe = LocalNx - 1; @@ -2987,6 +3005,10 @@ RangeIterator BoutMesh::iterateBndryUpperOuterY() const { } RangeIterator BoutMesh::iterateBndryUpperY() const { + if (this->isFci()) { + throw BoutException("FCI should never use this iterator"); + } + int xs = 0; int xe = LocalNx - 1; if ((UDATA_INDEST >= 0) && (UDATA_XSPLIT > xstart)) { From 22f493cc0a2a96c79f6e942cc0be63150c8dcb88 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 9 Aug 2024 10:29:19 +0200 Subject: [PATCH 050/242] Fix exception message --- include/bout/field3d.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index 2a37d9e0c8..3b98294160 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -250,7 +250,7 @@ public: #if CHECK > 2 if (yup_fields.size() != ydown_fields.size()) { throw BoutException( - "Field3D::splitParallelSlices: forward/backward parallel slices not in sync.\n" + "Field3D::hasParallelSlices: forward/backward parallel slices not in sync.\n" " This is an internal library error"); } #endif From 6c2c82c6cb10180706540f1b129d7f149b38c858 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 9 Aug 2024 10:30:32 +0200 Subject: [PATCH 051/242] Expose tracking Useful for physics module to record additional data if the solver is failing --- include/bout/field3d.hxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index 3b98294160..a246fd15ab 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -518,6 +518,8 @@ public: int size() const override { return nx * ny * nz; }; + Options* getTracking() { return tracking; }; + private: /// Array sizes (from fieldmesh). These are valid only if fieldmesh is not null int nx{-1}, ny{-1}, nz{-1}; From c6c259bd9ce2c06813a2e817b3aa40dc82f1061d Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 9 Aug 2024 10:32:17 +0200 Subject: [PATCH 052/242] Add simple interface to store parallel fields Just dumping the parallel slices does in general not work, as then collect discards that, especially if NYPE==ny --- include/bout/options.hxx | 3 +++ src/sys/options.cxx | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/include/bout/options.hxx b/include/bout/options.hxx index 839c847289..e1f5ae68fa 100644 --- a/include/bout/options.hxx +++ b/include/bout/options.hxx @@ -946,6 +946,9 @@ Tensor Options::as>(const Tensor& similar_t /// Convert \p value to string std::string toString(const Options& value); +/// Save the parallel fields +void saveParallel(Options& opt, const std::string name, const Field3D& tosave); + /// Output a stringified \p value to a stream /// /// This is templated to avoid implict casting: anything is diff --git a/src/sys/options.cxx b/src/sys/options.cxx index 49a81cfa88..71339b6089 100644 --- a/src/sys/options.cxx +++ b/src/sys/options.cxx @@ -306,6 +306,22 @@ void Options::assign<>(Tensor val, std::string source) { _set_no_check(std::move(val), std::move(source)); } +void saveParallel(Options& opt, const std::string name, const Field3D& tosave){ + ASSERT2(tosave.hasParallelSlices()); + opt[name] = tosave; + for (size_t i0=1 ; i0 <= tosave.numberParallelSlices(); ++i0) { + for (int i: {i0, -i0} ) { + Field3D tmp; + tmp.allocate(); + const auto& fpar = tosave.ynext(i); + for (auto j: fpar.getValidRegionWithDefault("RGN_NO_BOUNDARY")){ + tmp[j.yp(-i)] = fpar[j]; + } + opt[fmt::format("{}_y{:+d}", name, i)] = tmp; + } + } +} + namespace { /// Use FieldFactory to evaluate expression double parseExpression(const Options::ValueType& value, const Options* options, From de99accd5e9785cd370106b759a939208721f06d Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 9 Aug 2024 10:47:16 +0200 Subject: [PATCH 053/242] Add const version for getCoordinates --- include/bout/mesh.hxx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/include/bout/mesh.hxx b/include/bout/mesh.hxx index a3a36ad933..ccc979987b 100644 --- a/include/bout/mesh.hxx +++ b/include/bout/mesh.hxx @@ -636,6 +636,19 @@ public: return inserted.first->second; } + std::shared_ptr + getCoordinatesConst(const CELL_LOC location = CELL_CENTRE) const { + ASSERT1(location != CELL_DEFAULT); + ASSERT1(location != CELL_VSHIFT); + + auto found = coords_map.find(location); + if (found != coords_map.end()) { + // True branch most common, returns immediately + return found->second; + } + throw BoutException("Coordinates not yet set. Use non-const version!"); + } + /// Returns the non-CELL_CENTRE location /// allowed as a staggered location CELL_LOC getAllowedStaggerLoc(DIRECTION direction) const { From c35d7736d320dc97d4ed9473a45a4d6300159a11 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 9 Aug 2024 13:45:23 +0200 Subject: [PATCH 054/242] rename to include order --- include/bout/parallel_boundary_region.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/bout/parallel_boundary_region.hxx b/include/bout/parallel_boundary_region.hxx index 07150a55b3..f8fe3d8ee1 100644 --- a/include/bout/parallel_boundary_region.hxx +++ b/include/bout/parallel_boundary_region.hxx @@ -82,7 +82,7 @@ public: return f(0, ind()) * (1 + length()) - f(-dir, ind().yp(-dir)) * length(); } - inline BoutReal interpolate_sheath(const Field3D& f) const { + inline BoutReal interpolate_sheath_o1(const Field3D& f) const { return f[ind()] * (1 - length()) + ynext(f) * length(); } From 22bbd27acb64ea71b073a7d0c096105296918b13 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 9 Aug 2024 13:46:06 +0200 Subject: [PATCH 055/242] Set using value rather then using self Using self is cheaper, but then the parallel slices have parallel fields them self, which causes issues --- src/field/field3d.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index af21f7d784..329ff6898a 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -116,8 +116,8 @@ Field3D::Field3D(const BoutReal val, Mesh* localmesh) : Field3D(localmesh) { if (this->isFci()) { splitParallelSlices(); for (size_t i=0; i Date: Fri, 9 Aug 2024 13:46:32 +0200 Subject: [PATCH 056/242] Also set parallel fields for operator= --- src/field/field3d.cxx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index 329ff6898a..7e30bc8bb8 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -370,7 +370,16 @@ Field3D& Field3D::operator=(const BoutReal val) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. +#if BOUT_USE_FCI_AUTOMAGIC + if (isFci() && hasParallelSlices()) { + for (size_t i=0; i Date: Fri, 9 Aug 2024 13:46:44 +0200 Subject: [PATCH 057/242] Set parallel region by default --- src/field/field3d.cxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index 7e30bc8bb8..386d24f376 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -176,7 +176,9 @@ void Field3D::splitParallelSlices() { // Note the fields constructed here will be fully overwritten by the // ParallelTransform, so we don't need a full constructor yup_fields.emplace_back(fieldmesh); + yup_fields[i].setRegion(fmt::format("RGN_YPAR_{:+d}", i + 1)); ydown_fields.emplace_back(fieldmesh); + yup_fields[i].setRegion(fmt::format("RGN_YPAR_{:+d}", -i - 1)); } } From 61a2521a0363e3a0f3505856c5b18a5f5cd90d35 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 9 Aug 2024 13:49:21 +0200 Subject: [PATCH 058/242] Set name in operator= --- src/field/field3d.cxx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index 386d24f376..0720778e84 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -387,6 +387,7 @@ Field3D& Field3D::operator=(const BoutReal val) { allocate(); BOUT_FOR(i, getRegion("RGN_ALL")) { (*this)[i] = val; } + this->name = "BR"; return *this; } From 27db217cf5566dd9afed7266fc15f13ba1a45bdc Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 9 Aug 2024 13:49:59 +0200 Subject: [PATCH 059/242] preserve parallel fields more often --- src/field/gen_fieldops.jinja | 24 ++++++ src/field/generated_fieldops.cxx | 136 +++++++++++++++++++------------ 2 files changed, 108 insertions(+), 52 deletions(-) diff --git a/src/field/gen_fieldops.jinja b/src/field/gen_fieldops.jinja index d268790255..88e877c197 100644 --- a/src/field/gen_fieldops.jinja +++ b/src/field/gen_fieldops.jinja @@ -23,8 +23,30 @@ #endif {% elif lhs == "Field3D" %} {{out.name}}.setRegion({{lhs.name}}.getRegionID()); + {% if rhs == "BoutReal" %} +#if BOUT_USE_FCI_AUTOMAGIC + if ({{lhs.name}}.isFci() and {{lhs.name}}.hasParallelSlices()) { + {{out.name}}.splitParallelSlices(); + for (size_t i{0} ; i < {{lhs.name}}.numberParallelSlices() ; ++i) { + {{out.name}}.yup(i) = {{lhs.name}}.yup(i) {{operator}} {{rhs.name}}; + {{out.name}}.ydown(i) = {{lhs.name}}.ydown(i) {{operator}} {{rhs.name}}; + } + } +#endif + {% endif %} {% elif rhs == "Field3D" %} {{out.name}}.setRegion({{rhs.name}}.getRegionID()); + {% if lhs == "BoutReal" %} +#if BOUT_USE_FCI_AUTOMAGIC + if ({{rhs.name}}.isFci() and {{rhs.name}}.hasParallelSlices()) { + {{out.name}}.splitParallelSlices(); + for (size_t i{0} ; i < {{rhs.name}}.numberParallelSlices() ; ++i) { + {{out.name}}.yup(i) = {{lhs.name}} {{operator}} {{rhs.name}}.yup(i); + {{out.name}}.ydown(i) = {{lhs.name}} {{operator}} {{rhs.name}}.ydown(i); + } + } +#endif + {% endif %} {% endif %} {% endif %} @@ -91,6 +113,7 @@ {% if (lhs == "Field3D") %} // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. + {% if (rhs == "Field3D" or rhs == "BoutReal") %} #if BOUT_USE_FCI_AUTOMAGIC if (this->isFci() and this->hasParallelSlices() {% if rhs == "Field3D" %} and {{rhs.name}}.hasParallelSlices() {% endif %}) { for (size_t i{0} ; i < yup_fields.size() ; ++i) { @@ -99,6 +122,7 @@ } } else #endif + {% endif %} { clearParallelSlices(); } diff --git a/src/field/generated_fieldops.cxx b/src/field/generated_fieldops.cxx index 18e857ba92..75d2ede82d 100644 --- a/src/field/generated_fieldops.cxx +++ b/src/field/generated_fieldops.cxx @@ -44,7 +44,7 @@ Field3D& Field3D::operator*=(const Field3D& rhs) { ASSERT1_FIELDS_COMPATIBLE(*this, rhs); // Delete existing parallel slices. We don't copy parallel slices, so any - // that currently exist will be incorrect. +// that currently exist will be incorrect. #if BOUT_USE_FCI_AUTOMAGIC if (this->isFci() and this->hasParallelSlices() and rhs.hasParallelSlices()) { for (size_t i{0}; i < yup_fields.size(); ++i) { @@ -116,7 +116,7 @@ Field3D& Field3D::operator/=(const Field3D& rhs) { ASSERT1_FIELDS_COMPATIBLE(*this, rhs); // Delete existing parallel slices. We don't copy parallel slices, so any - // that currently exist will be incorrect. +// that currently exist will be incorrect. #if BOUT_USE_FCI_AUTOMAGIC if (this->isFci() and this->hasParallelSlices() and rhs.hasParallelSlices()) { for (size_t i{0}; i < yup_fields.size(); ++i) { @@ -188,7 +188,7 @@ Field3D& Field3D::operator+=(const Field3D& rhs) { ASSERT1_FIELDS_COMPATIBLE(*this, rhs); // Delete existing parallel slices. We don't copy parallel slices, so any - // that currently exist will be incorrect. +// that currently exist will be incorrect. #if BOUT_USE_FCI_AUTOMAGIC if (this->isFci() and this->hasParallelSlices() and rhs.hasParallelSlices()) { for (size_t i{0}; i < yup_fields.size(); ++i) { @@ -260,7 +260,7 @@ Field3D& Field3D::operator-=(const Field3D& rhs) { ASSERT1_FIELDS_COMPATIBLE(*this, rhs); // Delete existing parallel slices. We don't copy parallel slices, so any - // that currently exist will be incorrect. +// that currently exist will be incorrect. #if BOUT_USE_FCI_AUTOMAGIC if (this->isFci() and this->hasParallelSlices() and rhs.hasParallelSlices()) { for (size_t i{0}; i < yup_fields.size(); ++i) { @@ -329,17 +329,7 @@ Field3D& Field3D::operator*=(const Field2D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. -#if BOUT_USE_FCI_AUTOMAGIC - if (this->isFci() and this->hasParallelSlices()) { - for (size_t i{0}; i < yup_fields.size(); ++i) { - yup(i) *= rhs; - ydown(i) *= rhs; - } - } else -#endif - { - clearParallelSlices(); - } + { clearParallelSlices(); } checkData(*this); checkData(rhs); @@ -401,17 +391,7 @@ Field3D& Field3D::operator/=(const Field2D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. -#if BOUT_USE_FCI_AUTOMAGIC - if (this->isFci() and this->hasParallelSlices()) { - for (size_t i{0}; i < yup_fields.size(); ++i) { - yup(i) /= rhs; - ydown(i) /= rhs; - } - } else -#endif - { - clearParallelSlices(); - } + { clearParallelSlices(); } checkData(*this); checkData(rhs); @@ -473,17 +453,7 @@ Field3D& Field3D::operator+=(const Field2D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. -#if BOUT_USE_FCI_AUTOMAGIC - if (this->isFci() and this->hasParallelSlices()) { - for (size_t i{0}; i < yup_fields.size(); ++i) { - yup(i) += rhs; - ydown(i) += rhs; - } - } else -#endif - { - clearParallelSlices(); - } + { clearParallelSlices(); } checkData(*this); checkData(rhs); @@ -544,17 +514,7 @@ Field3D& Field3D::operator-=(const Field2D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. -#if BOUT_USE_FCI_AUTOMAGIC - if (this->isFci() and this->hasParallelSlices()) { - for (size_t i{0}; i < yup_fields.size(); ++i) { - yup(i) -= rhs; - ydown(i) -= rhs; - } - } else -#endif - { - clearParallelSlices(); - } + { clearParallelSlices(); } checkData(*this); checkData(rhs); @@ -680,6 +640,15 @@ Field3D operator*(const Field3D& lhs, const BoutReal rhs) { checkData(rhs); result.setRegion(lhs.getRegionID()); +#if BOUT_USE_FCI_AUTOMAGIC + if (lhs.isFci() and lhs.hasParallelSlices()) { + result.splitParallelSlices(); + for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { + result.yup(i) = lhs.yup(i) * rhs; + result.ydown(i) = lhs.ydown(i) * rhs; + } + } +#endif BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { result[index] = lhs[index] * rhs; @@ -699,7 +668,7 @@ Field3D& Field3D::operator*=(const BoutReal rhs) { if (data.unique()) { // Delete existing parallel slices. We don't copy parallel slices, so any - // that currently exist will be incorrect. +// that currently exist will be incorrect. #if BOUT_USE_FCI_AUTOMAGIC if (this->isFci() and this->hasParallelSlices()) { for (size_t i{0}; i < yup_fields.size(); ++i) { @@ -739,6 +708,15 @@ Field3D operator/(const Field3D& lhs, const BoutReal rhs) { checkData(rhs); result.setRegion(lhs.getRegionID()); +#if BOUT_USE_FCI_AUTOMAGIC + if (lhs.isFci() and lhs.hasParallelSlices()) { + result.splitParallelSlices(); + for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { + result.yup(i) = lhs.yup(i) / rhs; + result.ydown(i) = lhs.ydown(i) / rhs; + } + } +#endif const auto tmp = 1.0 / rhs; BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { @@ -759,7 +737,7 @@ Field3D& Field3D::operator/=(const BoutReal rhs) { if (data.unique()) { // Delete existing parallel slices. We don't copy parallel slices, so any - // that currently exist will be incorrect. +// that currently exist will be incorrect. #if BOUT_USE_FCI_AUTOMAGIC if (this->isFci() and this->hasParallelSlices()) { for (size_t i{0}; i < yup_fields.size(); ++i) { @@ -800,6 +778,15 @@ Field3D operator+(const Field3D& lhs, const BoutReal rhs) { checkData(rhs); result.setRegion(lhs.getRegionID()); +#if BOUT_USE_FCI_AUTOMAGIC + if (lhs.isFci() and lhs.hasParallelSlices()) { + result.splitParallelSlices(); + for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { + result.yup(i) = lhs.yup(i) + rhs; + result.ydown(i) = lhs.ydown(i) + rhs; + } + } +#endif BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { result[index] = lhs[index] + rhs; @@ -819,7 +806,7 @@ Field3D& Field3D::operator+=(const BoutReal rhs) { if (data.unique()) { // Delete existing parallel slices. We don't copy parallel slices, so any - // that currently exist will be incorrect. +// that currently exist will be incorrect. #if BOUT_USE_FCI_AUTOMAGIC if (this->isFci() and this->hasParallelSlices()) { for (size_t i{0}; i < yup_fields.size(); ++i) { @@ -859,6 +846,15 @@ Field3D operator-(const Field3D& lhs, const BoutReal rhs) { checkData(rhs); result.setRegion(lhs.getRegionID()); +#if BOUT_USE_FCI_AUTOMAGIC + if (lhs.isFci() and lhs.hasParallelSlices()) { + result.splitParallelSlices(); + for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { + result.yup(i) = lhs.yup(i) - rhs; + result.ydown(i) = lhs.ydown(i) - rhs; + } + } +#endif BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { result[index] = lhs[index] - rhs; @@ -878,7 +874,7 @@ Field3D& Field3D::operator-=(const BoutReal rhs) { if (data.unique()) { // Delete existing parallel slices. We don't copy parallel slices, so any - // that currently exist will be incorrect. +// that currently exist will be incorrect. #if BOUT_USE_FCI_AUTOMAGIC if (this->isFci() and this->hasParallelSlices()) { for (size_t i{0}; i < yup_fields.size(); ++i) { @@ -2213,6 +2209,15 @@ Field3D operator*(const BoutReal lhs, const Field3D& rhs) { checkData(rhs); result.setRegion(rhs.getRegionID()); +#if BOUT_USE_FCI_AUTOMAGIC + if (rhs.isFci() and rhs.hasParallelSlices()) { + result.splitParallelSlices(); + for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { + result.yup(i) = lhs * rhs.yup(i); + result.ydown(i) = lhs * rhs.ydown(i); + } + } +#endif BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { result[index] = lhs * rhs[index]; @@ -2233,6 +2238,15 @@ Field3D operator/(const BoutReal lhs, const Field3D& rhs) { checkData(rhs); result.setRegion(rhs.getRegionID()); +#if BOUT_USE_FCI_AUTOMAGIC + if (rhs.isFci() and rhs.hasParallelSlices()) { + result.splitParallelSlices(); + for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { + result.yup(i) = lhs / rhs.yup(i); + result.ydown(i) = lhs / rhs.ydown(i); + } + } +#endif BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { result[index] = lhs / rhs[index]; @@ -2253,6 +2267,15 @@ Field3D operator+(const BoutReal lhs, const Field3D& rhs) { checkData(rhs); result.setRegion(rhs.getRegionID()); +#if BOUT_USE_FCI_AUTOMAGIC + if (rhs.isFci() and rhs.hasParallelSlices()) { + result.splitParallelSlices(); + for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { + result.yup(i) = lhs + rhs.yup(i); + result.ydown(i) = lhs + rhs.ydown(i); + } + } +#endif BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { result[index] = lhs + rhs[index]; @@ -2273,6 +2296,15 @@ Field3D operator-(const BoutReal lhs, const Field3D& rhs) { checkData(rhs); result.setRegion(rhs.getRegionID()); +#if BOUT_USE_FCI_AUTOMAGIC + if (rhs.isFci() and rhs.hasParallelSlices()) { + result.splitParallelSlices(); + for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { + result.yup(i) = lhs - rhs.yup(i); + result.ydown(i) = lhs - rhs.ydown(i); + } + } +#endif BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { result[index] = lhs - rhs[index]; From 6b9e86e5ad5beec7c778f6a4b840e9988651039c Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 9 Aug 2024 13:50:34 +0200 Subject: [PATCH 060/242] Fix: remove broken code BoundaryRegionIter has been delted --- src/mesh/boundary_region.cxx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/mesh/boundary_region.cxx b/src/mesh/boundary_region.cxx index ef4aa13a66..700ef8a91f 100644 --- a/src/mesh/boundary_region.cxx +++ b/src/mesh/boundary_region.cxx @@ -202,6 +202,3 @@ void BoundaryRegionYUp::nextY() { bool BoundaryRegionYUp::isDone() { return (x > xe) || (y >= localmesh->LocalNy); // Return true if gone out of the boundary } - -BoundaryRegionIter BoundaryRegion::begin() { return BoundaryRegionIter(this, false); } -BoundaryRegionIter BoundaryRegion::end() { return BoundaryRegionIter(this, true); } From d3b0d65bad721793790c8dcd37f18fe4ae5f8cef Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 9 Aug 2024 13:50:41 +0200 Subject: [PATCH 061/242] Add comment --- src/mesh/boundary_standard.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/boundary_standard.cxx b/src/mesh/boundary_standard.cxx index 80c2053f39..c8b3269198 100644 --- a/src/mesh/boundary_standard.cxx +++ b/src/mesh/boundary_standard.cxx @@ -2164,7 +2164,7 @@ void BoundaryNeumann_NonOrthogonal::apply(Field3D& f) { } else { throw BoutException("Unrecognized location"); } - } else { + } else { // loc == CELL_CENTRE for (; !bndry->isDone(); bndry->next1d()) { #if BOUT_USE_METRIC_3D for (int zk = 0; zk < mesh->LocalNz; zk++) { From 63a1f74655a164671e0210d486362a9be3e811f3 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 9 Aug 2024 13:51:22 +0200 Subject: [PATCH 062/242] Communicate automatically in Div_par with automagic --- src/mesh/difops.cxx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/mesh/difops.cxx b/src/mesh/difops.cxx index 2e25dfeedb..5270895e52 100644 --- a/src/mesh/difops.cxx +++ b/src/mesh/difops.cxx @@ -234,7 +234,21 @@ Field3D Div_par(const Field3D& f, const std::string& method, CELL_LOC outloc) { return f.getCoordinates(outloc)->Div_par(f, outloc, method); } -Field3D Div_par(const Field3D& f, const Field3D& v) { +Field3D Div_par(const Field3D& f_in, const Field3D& v_in) { +#if BOUT_USE_FCI_AUTOMAGIC + auto f{f_in}; + auto v{v_in}; + if (!f.hasParallelSlices()) { + f.calcParallelSlices(); + } + if (!v.hasParallelSlices()) { + v.calcParallelSlices(); + } +#else + const auto& f{f_in}; + const auto& v{v_in}; +#endif + ASSERT1_FIELDS_COMPATIBLE(f, v); ASSERT1(f.hasParallelSlices()); ASSERT1(v.hasParallelSlices()); From 8e773f7f7e5a5b516550d47c8313f11b13c435fa Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 9 Aug 2024 11:48:56 +0200 Subject: [PATCH 063/242] Add option to disallow calculating parallel fields Calculating parallel fields for metrics terms does not make sense. Using such parallel fields is very, very likely a bug. --- include/bout/field3d.hxx | 11 +++++++++++ src/field/field3d.cxx | 3 +++ src/mesh/coordinates.cxx | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index a246fd15ab..4eccedd7e3 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -272,23 +272,27 @@ public: /// Return reference to yup field Field3D& yup(std::vector::size_type index = 0) { ASSERT2(index < yup_fields.size()); + ASSERT2(allow_parallel_slices); return yup_fields[index]; } /// Return const reference to yup field const Field3D& yup(std::vector::size_type index = 0) const { ASSERT2(index < yup_fields.size()); + ASSERT2(allow_parallel_slices); return yup_fields[index]; } /// Return reference to ydown field Field3D& ydown(std::vector::size_type index = 0) { ASSERT2(index < ydown_fields.size()); + ASSERT2(allow_parallel_slices); return ydown_fields[index]; } /// Return const reference to ydown field const Field3D& ydown(std::vector::size_type index = 0) const { ASSERT2(index < ydown_fields.size()); + ASSERT2(allow_parallel_slices); return ydown_fields[index]; } @@ -491,6 +495,11 @@ public: friend class Vector2D; Field3D& calcParallelSlices(); + void allowParallelSlices([[maybe_unused]] bool allow){ +#if CHECK > 0 + allow_parallel_slices = allow; +#endif + } void applyBoundary(bool init = false) override; void applyBoundary(BoutReal t); @@ -542,6 +551,8 @@ private: template Options* track(const T& change, std::string operation); Options* track(const BoutReal& change, std::string operation); + bool allow_parallel_slices{true}; + }; // Non-member overloaded operators diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index 0720778e84..345e1c227d 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -167,6 +167,7 @@ BOUT_HOST_DEVICE Field3D* Field3D::timeDeriv() { void Field3D::splitParallelSlices() { TRACE("Field3D::splitParallelSlices"); + ASSERT2(allow_parallel_slices); if (hasParallelSlices()) { return; @@ -195,6 +196,7 @@ void Field3D::clearParallelSlices() { const Field3D& Field3D::ynext(int dir) const { #if CHECK > 0 + ASSERT2(allow_parallel_slices); // Asked for more than yguards if (std::abs(dir) > fieldmesh->ystart) { throw BoutException( @@ -393,6 +395,7 @@ Field3D& Field3D::operator=(const BoutReal val) { } Field3D& Field3D::calcParallelSlices() { + ASSERT2(allow_parallel_slices); getCoordinates()->getParallelTransform().calcParallelSlices(*this); #if BOUT_USE_FCI_AUTOMAGIC if (this->isFci()) { diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index e4b75d0032..cbf50afcfe 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -942,6 +942,13 @@ const Field2D& Coordinates::zlength() const { int Coordinates::geometry(bool recalculate_staggered, bool force_interpolate_from_centre) { TRACE("Coordinates::geometry"); + { + std::vector fields{dx, dy, dz, g11, g22, g33, g12, g13, g23, g_11, g_22, g_33, g_12, g_13, + g_23, J}; + for (auto& f: fields) { + f.allowParallelSlices(false); + } + } communicate(dx, dy, dz, g11, g22, g33, g12, g13, g23, g_11, g_22, g_33, g_12, g_13, g_23, J, Bxy); From 84853532ff7078379c798d205b995a917492ebbf Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 9 Aug 2024 11:50:33 +0200 Subject: [PATCH 064/242] Disable metric components that require y-derivatives for fci --- src/mesh/coordinates.cxx | 234 ++++++++++++++++++++------------------- 1 file changed, 122 insertions(+), 112 deletions(-) diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index cbf50afcfe..b12ca24ee8 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -970,119 +970,129 @@ int Coordinates::geometry(bool recalculate_staggered, checkContravariant(); checkCovariant(); - // Calculate Christoffel symbol terms (18 independent values) - // Note: This calculation is completely general: metric - // tensor can be 2D or 3D. For 2D, all DDZ terms are zero - - G1_11 = 0.5 * g11 * DDX(g_11) + g12 * (DDX(g_12) - 0.5 * DDY(g_11)) - + g13 * (DDX(g_13) - 0.5 * DDZ(g_11)); - G1_22 = g11 * (DDY(g_12) - 0.5 * DDX(g_22)) + 0.5 * g12 * DDY(g_22) - + g13 * (DDY(g_23) - 0.5 * DDZ(g_22)); - G1_33 = g11 * (DDZ(g_13) - 0.5 * DDX(g_33)) + g12 * (DDZ(g_23) - 0.5 * DDY(g_33)) - + 0.5 * g13 * DDZ(g_33); - G1_12 = 0.5 * g11 * DDY(g_11) + 0.5 * g12 * DDX(g_22) - + 0.5 * g13 * (DDY(g_13) + DDX(g_23) - DDZ(g_12)); - G1_13 = 0.5 * g11 * DDZ(g_11) + 0.5 * g12 * (DDZ(g_12) + DDX(g_23) - DDY(g_13)) - + 0.5 * g13 * DDX(g_33); - G1_23 = 0.5 * g11 * (DDZ(g_12) + DDY(g_13) - DDX(g_23)) - + 0.5 * g12 * (DDZ(g_22) + DDY(g_23) - DDY(g_23)) - // + 0.5 *g13*(DDZ(g_32) + DDY(g_33) - DDZ(g_23)); - // which equals - + 0.5 * g13 * DDY(g_33); - - G2_11 = 0.5 * g12 * DDX(g_11) + g22 * (DDX(g_12) - 0.5 * DDY(g_11)) - + g23 * (DDX(g_13) - 0.5 * DDZ(g_11)); - G2_22 = g12 * (DDY(g_12) - 0.5 * DDX(g_22)) + 0.5 * g22 * DDY(g_22) - + g23 * (DDY(g23) - 0.5 * DDZ(g_22)); - G2_33 = g12 * (DDZ(g_13) - 0.5 * DDX(g_33)) + g22 * (DDZ(g_23) - 0.5 * DDY(g_33)) - + 0.5 * g23 * DDZ(g_33); - G2_12 = 0.5 * g12 * DDY(g_11) + 0.5 * g22 * DDX(g_22) - + 0.5 * g23 * (DDY(g_13) + DDX(g_23) - DDZ(g_12)); - G2_13 = - // 0.5 *g21*(DDZ(g_11) + DDX(g_13) - DDX(g_13)) - // which equals - 0.5 * g12 * (DDZ(g_11) + DDX(g_13) - DDX(g_13)) - // + 0.5 *g22*(DDZ(g_21) + DDX(g_23) - DDY(g_13)) - // which equals - + 0.5 * g22 * (DDZ(g_12) + DDX(g_23) - DDY(g_13)) - // + 0.5 *g23*(DDZ(g_31) + DDX(g_33) - DDZ(g_13)); - // which equals - + 0.5 * g23 * DDX(g_33); - G2_23 = 0.5 * g12 * (DDZ(g_12) + DDY(g_13) - DDX(g_23)) + 0.5 * g22 * DDZ(g_22) - + 0.5 * g23 * DDY(g_33); - - G3_11 = 0.5 * g13 * DDX(g_11) + g23 * (DDX(g_12) - 0.5 * DDY(g_11)) - + g33 * (DDX(g_13) - 0.5 * DDZ(g_11)); - G3_22 = g13 * (DDY(g_12) - 0.5 * DDX(g_22)) + 0.5 * g23 * DDY(g_22) - + g33 * (DDY(g_23) - 0.5 * DDZ(g_22)); - G3_33 = g13 * (DDZ(g_13) - 0.5 * DDX(g_33)) + g23 * (DDZ(g_23) - 0.5 * DDY(g_33)) - + 0.5 * g33 * DDZ(g_33); - G3_12 = - // 0.5 *g31*(DDY(g_11) + DDX(g_12) - DDX(g_12)) - // which equals to - 0.5 * g13 * DDY(g_11) - // + 0.5 *g32*(DDY(g_21) + DDX(g_22) - DDY(g_12)) - // which equals to - + 0.5 * g23 * DDX(g_22) - //+ 0.5 *g33*(DDY(g_31) + DDX(g_32) - DDZ(g_12)); - // which equals to - + 0.5 * g33 * (DDY(g_13) + DDX(g_23) - DDZ(g_12)); - G3_13 = 0.5 * g13 * DDZ(g_11) + 0.5 * g23 * (DDZ(g_12) + DDX(g_23) - DDY(g_13)) - + 0.5 * g33 * DDX(g_33); - G3_23 = 0.5 * g13 * (DDZ(g_12) + DDY(g_13) - DDX(g_23)) + 0.5 * g23 * DDZ(g_22) - + 0.5 * g33 * DDY(g_33); - - auto tmp = J * g12; - communicate(tmp); - G1 = (DDX(J * g11) + DDY(tmp) + DDZ(J * g13)) / J; - tmp = J * g22; - communicate(tmp); - G2 = (DDX(J * g12) + DDY(tmp) + DDZ(J * g23)) / J; - tmp = J * g23; - communicate(tmp); - G3 = (DDX(J * g13) + DDY(tmp) + DDZ(J * g33)) / J; - - // Communicate christoffel symbol terms - output_progress.write("\tCommunicating connection terms\n"); - - communicate(G1_11, G1_22, G1_33, G1_12, G1_13, G1_23, G2_11, G2_22, G2_33, G2_12, G2_13, - G2_23, G3_11, G3_22, G3_33, G3_12, G3_13, G3_23, G1, G2, G3); - - // Set boundary guard cells of Christoffel symbol terms - // Ideally, when location is staggered, we would set the upper/outer boundary point - // correctly rather than by extrapolating here: e.g. if location==CELL_YLOW and we are - // at the upper y-boundary the x- and z-derivatives at yend+1 at the boundary can be - // calculated because the guard cells are available, while the y-derivative could be - // calculated from the CELL_CENTRE metric components (which have guard cells available - // past the boundary location). This would avoid the problem that the y-boundary on the - // CELL_YLOW grid is at a 'guard cell' location (yend+1). - // However, the above would require lots of special handling, so just extrapolate for - // now. - G1_11 = interpolateAndExtrapolate(G1_11, location, true, true, true, transform.get()); - G1_22 = interpolateAndExtrapolate(G1_22, location, true, true, true, transform.get()); - G1_33 = interpolateAndExtrapolate(G1_33, location, true, true, true, transform.get()); - G1_12 = interpolateAndExtrapolate(G1_12, location, true, true, true, transform.get()); - G1_13 = interpolateAndExtrapolate(G1_13, location, true, true, true, transform.get()); - G1_23 = interpolateAndExtrapolate(G1_23, location, true, true, true, transform.get()); - - G2_11 = interpolateAndExtrapolate(G2_11, location, true, true, true, transform.get()); - G2_22 = interpolateAndExtrapolate(G2_22, location, true, true, true, transform.get()); - G2_33 = interpolateAndExtrapolate(G2_33, location, true, true, true, transform.get()); - G2_12 = interpolateAndExtrapolate(G2_12, location, true, true, true, transform.get()); - G2_13 = interpolateAndExtrapolate(G2_13, location, true, true, true, transform.get()); - G2_23 = interpolateAndExtrapolate(G2_23, location, true, true, true, transform.get()); - - G3_11 = interpolateAndExtrapolate(G3_11, location, true, true, true, transform.get()); - G3_22 = interpolateAndExtrapolate(G3_22, location, true, true, true, transform.get()); - G3_33 = interpolateAndExtrapolate(G3_33, location, true, true, true, transform.get()); - G3_12 = interpolateAndExtrapolate(G3_12, location, true, true, true, transform.get()); - G3_13 = interpolateAndExtrapolate(G3_13, location, true, true, true, transform.get()); - G3_23 = interpolateAndExtrapolate(G3_23, location, true, true, true, transform.get()); - - G1 = interpolateAndExtrapolate(G1, location, true, true, true, transform.get()); - G2 = interpolateAndExtrapolate(G2, location, true, true, true, transform.get()); - G3 = interpolateAndExtrapolate(G3, location, true, true, true, transform.get()); + if (g_11.isFci()) { + // for FCI the y derivatives of metric components is meaningless. + G1_11 = G1_22 = G1_33 = G1_12 = G1_13 = G1_23 = + G2_11 = G2_22 = G2_33 = G2_12 = G2_13 = G2_23 = + + G3_11 = G3_22 = G3_33 = G3_12 = G3_13 = G3_23 = + + G1 = G2 = G3 = BoutNaN; + } else { + // Calculate Christoffel symbol terms (18 independent values) + // Note: This calculation is completely general: metric + // tensor can be 2D or 3D. For 2D, all DDZ terms are zero + + G1_11 = 0.5 * g11 * DDX(g_11) + g12 * (DDX(g_12) - 0.5 * DDY(g_11)) + + g13 * (DDX(g_13) - 0.5 * DDZ(g_11)); + G1_22 = g11 * (DDY(g_12) - 0.5 * DDX(g_22)) + 0.5 * g12 * DDY(g_22) + + g13 * (DDY(g_23) - 0.5 * DDZ(g_22)); + G1_33 = g11 * (DDZ(g_13) - 0.5 * DDX(g_33)) + g12 * (DDZ(g_23) - 0.5 * DDY(g_33)) + + 0.5 * g13 * DDZ(g_33); + G1_12 = 0.5 * g11 * DDY(g_11) + 0.5 * g12 * DDX(g_22) + + 0.5 * g13 * (DDY(g_13) + DDX(g_23) - DDZ(g_12)); + G1_13 = 0.5 * g11 * DDZ(g_11) + 0.5 * g12 * (DDZ(g_12) + DDX(g_23) - DDY(g_13)) + + 0.5 * g13 * DDX(g_33); + G1_23 = 0.5 * g11 * (DDZ(g_12) + DDY(g_13) - DDX(g_23)) + + 0.5 * g12 * (DDZ(g_22) + DDY(g_23) - DDY(g_23)) + // + 0.5 *g13*(DDZ(g_32) + DDY(g_33) - DDZ(g_23)); + // which equals + + 0.5 * g13 * DDY(g_33); + + G2_11 = 0.5 * g12 * DDX(g_11) + g22 * (DDX(g_12) - 0.5 * DDY(g_11)) + + g23 * (DDX(g_13) - 0.5 * DDZ(g_11)); + G2_22 = g12 * (DDY(g_12) - 0.5 * DDX(g_22)) + 0.5 * g22 * DDY(g_22) + + g23 * (DDY(g23) - 0.5 * DDZ(g_22)); + G2_33 = g12 * (DDZ(g_13) - 0.5 * DDX(g_33)) + g22 * (DDZ(g_23) - 0.5 * DDY(g_33)) + + 0.5 * g23 * DDZ(g_33); + G2_12 = 0.5 * g12 * DDY(g_11) + 0.5 * g22 * DDX(g_22) + + 0.5 * g23 * (DDY(g_13) + DDX(g_23) - DDZ(g_12)); + G2_13 = + // 0.5 *g21*(DDZ(g_11) + DDX(g_13) - DDX(g_13)) + // which equals + 0.5 * g12 * (DDZ(g_11) + DDX(g_13) - DDX(g_13)) + // + 0.5 *g22*(DDZ(g_21) + DDX(g_23) - DDY(g_13)) + // which equals + + 0.5 * g22 * (DDZ(g_12) + DDX(g_23) - DDY(g_13)) + // + 0.5 *g23*(DDZ(g_31) + DDX(g_33) - DDZ(g_13)); + // which equals + + 0.5 * g23 * DDX(g_33); + G2_23 = 0.5 * g12 * (DDZ(g_12) + DDY(g_13) - DDX(g_23)) + 0.5 * g22 * DDZ(g_22) + + 0.5 * g23 * DDY(g_33); + + G3_11 = 0.5 * g13 * DDX(g_11) + g23 * (DDX(g_12) - 0.5 * DDY(g_11)) + + g33 * (DDX(g_13) - 0.5 * DDZ(g_11)); + G3_22 = g13 * (DDY(g_12) - 0.5 * DDX(g_22)) + 0.5 * g23 * DDY(g_22) + + g33 * (DDY(g_23) - 0.5 * DDZ(g_22)); + G3_33 = g13 * (DDZ(g_13) - 0.5 * DDX(g_33)) + g23 * (DDZ(g_23) - 0.5 * DDY(g_33)) + + 0.5 * g33 * DDZ(g_33); + G3_12 = + // 0.5 *g31*(DDY(g_11) + DDX(g_12) - DDX(g_12)) + // which equals to + 0.5 * g13 * DDY(g_11) + // + 0.5 *g32*(DDY(g_21) + DDX(g_22) - DDY(g_12)) + // which equals to + + 0.5 * g23 * DDX(g_22) + //+ 0.5 *g33*(DDY(g_31) + DDX(g_32) - DDZ(g_12)); + // which equals to + + 0.5 * g33 * (DDY(g_13) + DDX(g_23) - DDZ(g_12)); + G3_13 = 0.5 * g13 * DDZ(g_11) + 0.5 * g23 * (DDZ(g_12) + DDX(g_23) - DDY(g_13)) + + 0.5 * g33 * DDX(g_33); + G3_23 = 0.5 * g13 * (DDZ(g_12) + DDY(g_13) - DDX(g_23)) + 0.5 * g23 * DDZ(g_22) + + 0.5 * g33 * DDY(g_33); + + auto tmp = J * g12; + communicate(tmp); + G1 = (DDX(J * g11) + DDY(tmp) + DDZ(J * g13)) / J; + tmp = J * g22; + communicate(tmp); + G2 = (DDX(J * g12) + DDY(tmp) + DDZ(J * g23)) / J; + tmp = J * g23; + communicate(tmp); + G3 = (DDX(J * g13) + DDY(tmp) + DDZ(J * g33)) / J; + + // Communicate christoffel symbol terms + output_progress.write("\tCommunicating connection terms\n"); + + communicate(G1_11, G1_22, G1_33, G1_12, G1_13, G1_23, G2_11, G2_22, G2_33, G2_12, + G2_13, G2_23, G3_11, G3_22, G3_33, G3_12, G3_13, G3_23, G1, G2, G3); + + // Set boundary guard cells of Christoffel symbol terms + // Ideally, when location is staggered, we would set the upper/outer boundary point + // correctly rather than by extrapolating here: e.g. if location==CELL_YLOW and we are + // at the upper y-boundary the x- and z-derivatives at yend+1 at the boundary can be + // calculated because the guard cells are available, while the y-derivative could be + // calculated from the CELL_CENTRE metric components (which have guard cells available + // past the boundary location). This would avoid the problem that the y-boundary on the + // CELL_YLOW grid is at a 'guard cell' location (yend+1). + // However, the above would require lots of special handling, so just extrapolate for + // now. + G1_11 = interpolateAndExtrapolate(G1_11, location, true, true, true, transform.get()); + G1_22 = interpolateAndExtrapolate(G1_22, location, true, true, true, transform.get()); + G1_33 = interpolateAndExtrapolate(G1_33, location, true, true, true, transform.get()); + G1_12 = interpolateAndExtrapolate(G1_12, location, true, true, true, transform.get()); + G1_13 = interpolateAndExtrapolate(G1_13, location, true, true, true, transform.get()); + G1_23 = interpolateAndExtrapolate(G1_23, location, true, true, true, transform.get()); + + G2_11 = interpolateAndExtrapolate(G2_11, location, true, true, true, transform.get()); + G2_22 = interpolateAndExtrapolate(G2_22, location, true, true, true, transform.get()); + G2_33 = interpolateAndExtrapolate(G2_33, location, true, true, true, transform.get()); + G2_12 = interpolateAndExtrapolate(G2_12, location, true, true, true, transform.get()); + G2_13 = interpolateAndExtrapolate(G2_13, location, true, true, true, transform.get()); + G2_23 = interpolateAndExtrapolate(G2_23, location, true, true, true, transform.get()); + + G3_11 = interpolateAndExtrapolate(G3_11, location, true, true, true, transform.get()); + G3_22 = interpolateAndExtrapolate(G3_22, location, true, true, true, transform.get()); + G3_33 = interpolateAndExtrapolate(G3_33, location, true, true, true, transform.get()); + G3_12 = interpolateAndExtrapolate(G3_12, location, true, true, true, transform.get()); + G3_13 = interpolateAndExtrapolate(G3_13, location, true, true, true, transform.get()); + G3_23 = interpolateAndExtrapolate(G3_23, location, true, true, true, transform.get()); + + G1 = interpolateAndExtrapolate(G1, location, true, true, true, transform.get()); + G2 = interpolateAndExtrapolate(G2, location, true, true, true, transform.get()); + G3 = interpolateAndExtrapolate(G3, location, true, true, true, transform.get()); + } ////////////////////////////////////////////////////// /// Non-uniform meshes. Need to use DDX, DDY From 484a4731693f01f2d43c7a5fb8ddc660197cc267 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 9 Aug 2024 13:51:57 +0200 Subject: [PATCH 065/242] Communicate Bxy if needed --- src/mesh/coordinates.cxx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index b12ca24ee8..af04cb0781 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -1611,6 +1611,12 @@ Field3D Coordinates::Div_par(const Field3D& f, CELL_LOC outloc, return Bxy * Grad_par(f / Bxy_floc, outloc, method); } +#if BOUT_USE_FCI_AUTOMAGIC + if (!Bxy_floc.hasParallelSlices()) { + localmesh->communicate(Bxy_floc); + Bxy_floc.applyParallelBoundary("parallel_neumann_o2"); + } +#endif // Need to modify yup and ydown fields Field3D f_B = f / Bxy_floc; f_B.splitParallelSlices(); From a45f681351028a77f4e9185e0f1274f5c3253e3b Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 9 Aug 2024 11:53:26 +0200 Subject: [PATCH 066/242] Clarify which div_par has been used --- src/mesh/coordinates.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index af04cb0781..f728189d82 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -1624,7 +1624,7 @@ Field3D Coordinates::Div_par(const Field3D& f, CELL_LOC outloc, f_B.yup(i) = f.yup(i) / Bxy_floc.yup(i); f_B.ydown(i) = f.ydown(i) / Bxy_floc.ydown(i); } - return setName(Bxy * Grad_par(f_B, outloc, method), "Div_par({:s})", f.name); + return setName(Bxy * Grad_par(f_B, outloc, method), "C:Div_par({:s})", f.name); } ///////////////////////////////////////////////////////// From 1a265153ff1fa895b7f1447a1a0e0c06a32cef72 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 9 Aug 2024 11:56:16 +0200 Subject: [PATCH 067/242] Avoid using FV in y direction with FCI --- src/mesh/fv_ops.cxx | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/mesh/fv_ops.cxx b/src/mesh/fv_ops.cxx index 0a5d5f9624..ddf2715a71 100644 --- a/src/mesh/fv_ops.cxx +++ b/src/mesh/fv_ops.cxx @@ -63,17 +63,8 @@ Field3D Div_a_Grad_perp(const Field3D& a, const Field3D& f) { } } - const bool fci = f.hasParallelSlices() && a.hasParallelSlices(); - - if (bout::build::use_metric_3d and fci) { - // 3D Metric, need yup/ydown fields. - // Requires previous communication of metrics - // -- should insert communication here? - if (!coord->g23.hasParallelSlices() || !coord->g_23.hasParallelSlices() - || !coord->dy.hasParallelSlices() || !coord->dz.hasParallelSlices() - || !coord->Bxy.hasParallelSlices() || !coord->J.hasParallelSlices()) { - throw BoutException("metrics have no yup/down: Maybe communicate in init?"); - } + if (a.isFci()) + throw BoutException("FCI does not work with FV methods in y direction"); } // Y and Z fluxes require Y derivatives @@ -183,6 +174,10 @@ const Field3D Div_par_K_Grad_par(const Field3D& Kin, const Field3D& fin, bool bndry_flux) { TRACE("FV::Div_par_K_Grad_par"); + if (Kin.isFci()) { + return ::Div_par_K_Grad_par(Kin, fin); + } + ASSERT2(Kin.getLocation() == fin.getLocation()); Mesh* mesh = Kin.getMesh(); From 167ba2e3f5e008801675598fd8ce5d45178a0546 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 9 Aug 2024 13:01:43 +0200 Subject: [PATCH 068/242] fixup fv_ops --- src/mesh/fv_ops.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/fv_ops.cxx b/src/mesh/fv_ops.cxx index ddf2715a71..02e059b571 100644 --- a/src/mesh/fv_ops.cxx +++ b/src/mesh/fv_ops.cxx @@ -77,7 +77,7 @@ Field3D Div_a_Grad_perp(const Field3D& a, const Field3D& f) { const auto a_slice = makeslices(fci, a); // Only in 3D case with FCI do the metrics have parallel slices - const bool metric_fci = fci and bout::build::use_metric_3d; + const bool metric_fci = a.isFci() and bout::build::use_metric_3d; const auto g23 = makeslices(metric_fci, coord->g23); const auto g_23 = makeslices(metric_fci, coord->g_23); const auto J = makeslices(metric_fci, coord->J); From df4299838f61375c62bf25f6db0401fc41f9a09d Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 9 Aug 2024 13:16:38 +0200 Subject: [PATCH 069/242] fixup again --- src/mesh/fv_ops.cxx | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/mesh/fv_ops.cxx b/src/mesh/fv_ops.cxx index 02e059b571..1646e07e1d 100644 --- a/src/mesh/fv_ops.cxx +++ b/src/mesh/fv_ops.cxx @@ -63,7 +63,7 @@ Field3D Div_a_Grad_perp(const Field3D& a, const Field3D& f) { } } - if (a.isFci()) + if (a.isFci()) { throw BoutException("FCI does not work with FV methods in y direction"); } @@ -73,11 +73,11 @@ Field3D Div_a_Grad_perp(const Field3D& a, const Field3D& f) { // Values on this y slice (centre). // This is needed because toFieldAligned may modify the field - const auto f_slice = makeslices(fci, f); - const auto a_slice = makeslices(fci, a); + const auto f_slice = makeslices(false, f); + const auto a_slice = makeslices(false, a); // Only in 3D case with FCI do the metrics have parallel slices - const bool metric_fci = a.isFci() and bout::build::use_metric_3d; + const bool metric_fci = false; const auto g23 = makeslices(metric_fci, coord->g23); const auto g_23 = makeslices(metric_fci, coord->g_23); const auto J = makeslices(metric_fci, coord->J); @@ -87,9 +87,7 @@ Field3D Div_a_Grad_perp(const Field3D& a, const Field3D& f) { // Result of the Y and Z fluxes Field3D yzresult(0.0, mesh); - if (!fci) { - yzresult.setDirectionY(YDirectionType::Aligned); - } + yzresult.setDirectionY(YDirectionType::Aligned); // Y flux @@ -160,12 +158,7 @@ Field3D Div_a_Grad_perp(const Field3D& a, const Field3D& f) { } } - // Check if we need to transform back - if (fci) { - result += yzresult; - } else { - result += fromFieldAligned(yzresult); - } + result += fromFieldAligned(yzresult); return result; } From b53d27d817ea1ce509e0d30cfc7c2838d7cbfc18 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 9 Aug 2024 11:56:57 +0200 Subject: [PATCH 070/242] Always set region for hermite_spline_xz --- src/mesh/interpolation/hermite_spline_xz.cxx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/mesh/interpolation/hermite_spline_xz.cxx b/src/mesh/interpolation/hermite_spline_xz.cxx index 165d387d66..69df6d8906 100644 --- a/src/mesh/interpolation/hermite_spline_xz.cxx +++ b/src/mesh/interpolation/hermite_spline_xz.cxx @@ -346,6 +346,8 @@ Field3D XZHermiteSpline::interpolate(const Field3D& f, const std::string& region ASSERT1(f.getMesh() == localmesh); Field3D f_interp{emptyFrom(f)}; + const auto region2 = fmt::format("RGN_YPAR_{:+d}", y_offset); + #if USE_NEW_WEIGHTS #ifdef HS_USE_PETSC BoutReal* ptr; @@ -355,7 +357,6 @@ Field3D XZHermiteSpline::interpolate(const Field3D& f, const std::string& region VecRestoreArray(rhs, &ptr); MatMult(petscWeights, rhs, result); VecGetArrayRead(result, &cptr); - const auto region2 = y_offset == 0 ? region : fmt::format("RGN_YPAR_{:+d}", y_offset); BOUT_FOR(i, f.getRegion(region2)) { f_interp[i] = cptr[int(i)]; ASSERT2(std::isfinite(cptr[int(i)])); @@ -375,11 +376,10 @@ Field3D XZHermiteSpline::interpolate(const Field3D& f, const std::string& region } } #endif - return f_interp; #else // Derivatives are used for tension and need to be on dimensionless // coordinates - const auto region2 = fmt::format("RGN_YPAR_{:+d}", y_offset); + // f has been communcated, and thus we can assume that the x-boundaries are // also valid in the y-boundary. Thus the differentiated field needs no // extra comms. @@ -418,8 +418,10 @@ Field3D XZHermiteSpline::interpolate(const Field3D& f, const std::string& region ASSERT2(std::isfinite(f_interp[iyp]) || i.x() < localmesh->xstart || i.x() > localmesh->xend); } - return f_interp; #endif + f_interp.setRegion(region2); + ASSERT2(f_interp.getRegionID()); + return f_interp; } Field3D XZHermiteSpline::interpolate(const Field3D& f, const Field3D& delta_x, From 43669c88020dd11eedd9b10b1ed300d091f42621 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 9 Aug 2024 11:59:36 +0200 Subject: [PATCH 071/242] Remove debugging code --- src/field/field3d.cxx | 29 ++--------------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index 345e1c227d..cdcc2af261 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -47,26 +47,6 @@ #include #include - -#include "bout/output.hxx" -#include - -namespace fmt { -template -struct formatter> : fmt::formatter { - - template - auto format(const std::optional& opt, FormatContext& ctx) { - if (opt) { - fmt::formatter::format(opt.value(), ctx); - return ctx.out(); - } - return fmt::format_to(ctx.out(), "NO VALUE"); - } -}; -} // namespace fmt - - /// Constructor Field3D::Field3D(Mesh* localmesh, CELL_LOC location_in, DirectionTypes directions_in) : Field(localmesh, location_in, directions_in) { @@ -372,8 +352,6 @@ Field3D& Field3D::operator=(const BoutReal val) { TRACE("Field3D = BoutReal"); track(val, "operator="); - // Delete existing parallel slices. We don't copy parallel slices, so any - // that currently exist will be incorrect. #if BOUT_USE_FCI_AUTOMAGIC if (isFci() && hasParallelSlices()) { for (size_t i=0; igetRegionID(region_name); - output.write("{:p}: set {} {}\n", static_cast(this), regionID, region_name); } void Field3D::resetRegion() { regionID.reset(); - output.write("{:p}: reset\n", static_cast(this)); }; void Field3D::setRegion(size_t id) { regionID = id; - //output.write("{:p}: set {:d}\n", static_cast(this), regionID); - output.write("{:p}: set {}\n", static_cast(this), regionID); }; void Field3D::setRegion(std::optional id) { regionID = id; - output.write("{:p}: set {}\n", static_cast(this), regionID); }; Field3D& Field3D::enableTracking(const std::string& name, Options& _tracking) { From b600cdaa9b57768a7e0c94f265902830450cbdf4 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 9 Aug 2024 12:00:21 +0200 Subject: [PATCH 072/242] Allow dumping at 0 --- src/solver/impls/euler/euler.cxx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/solver/impls/euler/euler.cxx b/src/solver/impls/euler/euler.cxx index 45ba5ccdbf..f43f1e4c29 100644 --- a/src/solver/impls/euler/euler.cxx +++ b/src/solver/impls/euler/euler.cxx @@ -144,7 +144,8 @@ void EulerSolver::take_step(BoutReal curtime, BoutReal dt, Array& star Array& result) { load_vars(std::begin(start)); - const bool dump_now = dump_at_time > 0 && std::abs(dump_at_time - curtime) < dt; + const bool dump_now = + (dump_at_time >= 0 && std::abs(dump_at_time - curtime) < dt) || dump_at_time < -3; std::unique_ptr debug_ptr; if (dump_now) { debug_ptr = std::make_unique(); From 1ce3f2af05e9b7b91bce2dc3b1a8c7b002cc84b4 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 9 Aug 2024 12:00:41 +0200 Subject: [PATCH 073/242] Dump variables before rhs() --- src/solver/impls/euler/euler.cxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/solver/impls/euler/euler.cxx b/src/solver/impls/euler/euler.cxx index f43f1e4c29..4d5ed08cc2 100644 --- a/src/solver/impls/euler/euler.cxx +++ b/src/solver/impls/euler/euler.cxx @@ -153,6 +153,8 @@ void EulerSolver::take_step(BoutReal curtime, BoutReal dt, Array& star for (auto& f : f3d) { f.F_var->enableTracking(fmt::format("ddt_{:s}", f.name), debug); setName(*f.var, f.name); + debug[fmt::format("pre_{:s}", f.name)] = *f.var; + f.var->allocate(); } } From 6bc5010e62f0d5e34f9203d8c888655200b7fbd0 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 9 Aug 2024 12:01:05 +0200 Subject: [PATCH 074/242] Ensure mesh is either valid of nullptr --- src/solver/impls/euler/euler.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solver/impls/euler/euler.cxx b/src/solver/impls/euler/euler.cxx index 4d5ed08cc2..788aae70ed 100644 --- a/src/solver/impls/euler/euler.cxx +++ b/src/solver/impls/euler/euler.cxx @@ -161,7 +161,7 @@ void EulerSolver::take_step(BoutReal curtime, BoutReal dt, Array& star run_rhs(curtime); if (dump_now) { Options& debug = *debug_ptr; - Mesh* mesh; + Mesh* mesh{nullptr}; for (auto& f : f3d) { debug[f.name] = *f.var; mesh = f.var->getMesh(); From 3831c37e6e02e81b62480702efff82fdb4493116 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 9 Aug 2024 12:01:16 +0200 Subject: [PATCH 075/242] Also dump parallel fields --- src/solver/impls/euler/euler.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solver/impls/euler/euler.cxx b/src/solver/impls/euler/euler.cxx index 788aae70ed..31100c5e20 100644 --- a/src/solver/impls/euler/euler.cxx +++ b/src/solver/impls/euler/euler.cxx @@ -163,7 +163,7 @@ void EulerSolver::take_step(BoutReal curtime, BoutReal dt, Array& star Options& debug = *debug_ptr; Mesh* mesh{nullptr}; for (auto& f : f3d) { - debug[f.name] = *f.var; + saveParallel(debug, f.name, *f.var); mesh = f.var->getMesh(); } From 35a2c4f3c7258d4d92472769e49fe13b046b367d Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 9 Aug 2024 12:01:56 +0200 Subject: [PATCH 076/242] Allow dumping several times --- src/solver/impls/euler/euler.cxx | 9 ++++++--- src/solver/impls/euler/euler.hxx | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/solver/impls/euler/euler.cxx b/src/solver/impls/euler/euler.cxx index 31100c5e20..dd091ae808 100644 --- a/src/solver/impls/euler/euler.cxx +++ b/src/solver/impls/euler/euler.cxx @@ -172,9 +172,12 @@ void EulerSolver::take_step(BoutReal curtime, BoutReal dt, Array& star debug["BOUT_VERSION"].force(bout::version::as_double); } - const std::string outname = fmt::format( - "{}/BOUT.debug.{}.nc", - Options::root()["datadir"].withDefault("data"), BoutComm::rank()); + const std::string outnumber = + dump_at_time < -3 ? fmt::format(".{}", debug_counter++) : ""; + const std::string outname = + fmt::format("{}/BOUT.debug{}.{}.nc", + Options::root()["datadir"].withDefault("data"), + outnumber, BoutComm::rank()); bout::OptionsIO::create(outname)->write(debug); MPI_Barrier(BoutComm::get()); diff --git a/src/solver/impls/euler/euler.hxx b/src/solver/impls/euler/euler.hxx index 4b6dc60a62..fc9b7f53bb 100644 --- a/src/solver/impls/euler/euler.hxx +++ b/src/solver/impls/euler/euler.hxx @@ -66,6 +66,7 @@ private: Array& result); BoutReal dump_at_time{-1.}; + int debug_counter{0}; }; #endif // BOUT_KARNIADAKIS_SOLVER_H From 3e5bf8cf55830e57713de6f2cb967afedfc128c9 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 9 Aug 2024 12:02:26 +0200 Subject: [PATCH 077/242] Stop debugging after dump has been written --- src/solver/impls/euler/euler.cxx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/solver/impls/euler/euler.cxx b/src/solver/impls/euler/euler.cxx index dd091ae808..5477b5760b 100644 --- a/src/solver/impls/euler/euler.cxx +++ b/src/solver/impls/euler/euler.cxx @@ -181,6 +181,9 @@ void EulerSolver::take_step(BoutReal curtime, BoutReal dt, Array& star bout::OptionsIO::create(outname)->write(debug); MPI_Barrier(BoutComm::get()); + for (auto& f : f3d) { + f.F_var->disableTracking(); + } } save_derivs(std::begin(result)); From a78350fa323b57dd78aecbfeb997623758133802 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 9 Aug 2024 12:02:43 +0200 Subject: [PATCH 078/242] Dump also parallel fields by default --- src/solver/impls/pvode/pvode.cxx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/solver/impls/pvode/pvode.cxx b/src/solver/impls/pvode/pvode.cxx index 9dce5d357f..66344f7cde 100644 --- a/src/solver/impls/pvode/pvode.cxx +++ b/src/solver/impls/pvode/pvode.cxx @@ -358,8 +358,8 @@ BoutReal PvodeSolver::run(BoutReal tout) { // Check return flag if (flag != SUCCESS) { output_error.write("ERROR CVODE step failed, flag = {:d}\n", flag); - CVodeMemRec* cv_mem = (CVodeMem)cvode_mem; if (debug_on_failure) { + CVodeMemRec* cv_mem = (CVodeMem)cvode_mem; if (f2d.empty() and v2d.empty() and v3d.empty()) { Options debug{}; using namespace std::string_literals; @@ -388,6 +388,9 @@ BoutReal PvodeSolver::run(BoutReal tout) { for (auto& f : f3d) { debug[f.name] = *f.var; + if (f.var->hasParallelSlices()) { + saveParallel(debug, f.name, *f.var); + } } if (mesh != nullptr) { From f9438800f45a3948244687d9ed3b9f9ec9427d80 Mon Sep 17 00:00:00 2001 From: David Bold Date: Thu, 26 Sep 2024 09:03:35 +0200 Subject: [PATCH 079/242] Set name for Field functions --- include/bout/field.hxx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/bout/field.hxx b/include/bout/field.hxx index dd32c42a63..0898b716c9 100644 --- a/include/bout/field.hxx +++ b/include/bout/field.hxx @@ -528,15 +528,16 @@ T pow(BoutReal lhs, const T& rhs, const std::string& rgn = "RGN_ALL") { #ifdef FIELD_FUNC #error This macro has already been defined #else -#define FIELD_FUNC(name, func) \ +#define FIELD_FUNC(_name, func) \ template > \ - inline T name(const T& f, const std::string& rgn = "RGN_ALL") { \ + inline T _name(const T& f, const std::string& rgn = "RGN_ALL") { \ AUTO_TRACE(); \ /* Check if the input is allocated */ \ checkData(f); \ /* Define and allocate the output result */ \ T result{emptyFrom(f)}; \ BOUT_FOR(d, result.getRegion(rgn)) { result[d] = func(f[d]); } \ + result.name = std::string(#_name "(") + f.name + std::string(")"); \ checkData(result); \ return result; \ } From a128be584c9a72a27367efa03a4ae63a2d70f30e Mon Sep 17 00:00:00 2001 From: David Bold Date: Thu, 26 Sep 2024 09:05:04 +0200 Subject: [PATCH 080/242] Fix code path without FCI automagic --- include/bout/index_derivs_interface.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/bout/index_derivs_interface.hxx b/include/bout/index_derivs_interface.hxx index 86dd4c9287..2c2c21d6cf 100644 --- a/include/bout/index_derivs_interface.hxx +++ b/include/bout/index_derivs_interface.hxx @@ -207,7 +207,7 @@ T DDY(const T& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "D #if BOUT_USE_FCI_AUTOMAGIC f_tmp.calcParallelSlices(); #else - raise BoutException("parallel slices needed for parallel derivatives. Make sure to communicate and apply parallel boundary conditions before calling derivative"); + throw BoutException("parallel slices needed for parallel derivatives. Make sure to communicate and apply parallel boundary conditions before calling derivative"); #endif } return standardDerivative(f_tmp, outloc, From 653d8360eddddd3526407e399350487db5117fcf Mon Sep 17 00:00:00 2001 From: David Bold Date: Thu, 26 Sep 2024 09:05:41 +0200 Subject: [PATCH 081/242] Only set region of parallel fields for FCI --- src/field/field3d.cxx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index cdcc2af261..f45cfdcb61 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -157,9 +157,11 @@ void Field3D::splitParallelSlices() { // Note the fields constructed here will be fully overwritten by the // ParallelTransform, so we don't need a full constructor yup_fields.emplace_back(fieldmesh); - yup_fields[i].setRegion(fmt::format("RGN_YPAR_{:+d}", i + 1)); ydown_fields.emplace_back(fieldmesh); - yup_fields[i].setRegion(fmt::format("RGN_YPAR_{:+d}", -i - 1)); + if (isFci()) { + yup_fields[i].setRegion(fmt::format("RGN_YPAR_{:+d}", i + 1)); + yup_fields[i].setRegion(fmt::format("RGN_YPAR_{:+d}", -i - 1)); + } } } From 34c3f8f6bfd6cf1c50e967ea2d65fc6505f48242 Mon Sep 17 00:00:00 2001 From: David Bold Date: Thu, 26 Sep 2024 09:08:21 +0200 Subject: [PATCH 082/242] Ensure field to be saved is allocated Storing parallel slices needs them to be to exist. If some field is stored that is not allocated, that will throw an error if the field is stored, but at that point it is going to be difficult to figure out where it came from. --- src/sys/options.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sys/options.cxx b/src/sys/options.cxx index 71339b6089..c1f031be24 100644 --- a/src/sys/options.cxx +++ b/src/sys/options.cxx @@ -307,7 +307,7 @@ void Options::assign<>(Tensor val, std::string source) { } void saveParallel(Options& opt, const std::string name, const Field3D& tosave){ - ASSERT2(tosave.hasParallelSlices()); + ASSERT0(tosave.isAllocated()); opt[name] = tosave; for (size_t i0=1 ; i0 <= tosave.numberParallelSlices(); ++i0) { for (int i: {i0, -i0} ) { From b37ef0ebd160f0b90308583a6145d61cd875f8d3 Mon Sep 17 00:00:00 2001 From: David Bold Date: Thu, 26 Sep 2024 10:17:51 +0200 Subject: [PATCH 083/242] Output model vars to debug file --- include/bout/physicsmodel.hxx | 2 ++ include/bout/solver.hxx | 2 ++ src/solver/impls/pvode/pvode.cxx | 1 + src/solver/solver.cxx | 4 ++++ 4 files changed, 9 insertions(+) diff --git a/include/bout/physicsmodel.hxx b/include/bout/physicsmodel.hxx index 9fa25d8b0f..fa113670ba 100644 --- a/include/bout/physicsmodel.hxx +++ b/include/bout/physicsmodel.hxx @@ -270,8 +270,10 @@ protected: virtual int rhs(BoutReal UNUSED(t)) { return 1; } virtual int rhs(BoutReal t, bool UNUSED(linear)) { return rhs(t); } +public: /// Output additional variables other than the evolving variables virtual void outputVars(Options& options); +protected: /// Add additional variables other than the evolving variables to the restart files virtual void restartVars(Options& options); diff --git a/include/bout/solver.hxx b/include/bout/solver.hxx index 896ce62965..ea34feb2d3 100644 --- a/include/bout/solver.hxx +++ b/include/bout/solver.hxx @@ -321,6 +321,8 @@ public: /// @param[in] save_repeat If true, add variables with time dimension virtual void outputVars(Options& output_options, bool save_repeat = true); + void modelOutputVars(Options& output_options); + /// Copy evolving variables out of \p options virtual void readEvolvingVariablesFromOptions(Options& options); diff --git a/src/solver/impls/pvode/pvode.cxx b/src/solver/impls/pvode/pvode.cxx index 66344f7cde..5c41dbf93b 100644 --- a/src/solver/impls/pvode/pvode.cxx +++ b/src/solver/impls/pvode/pvode.cxx @@ -385,6 +385,7 @@ BoutReal PvodeSolver::run(BoutReal tout) { setName(*f.var, f.name); } run_rhs(simtime); + modelOutputVars(debug); for (auto& f : f3d) { debug[f.name] = *f.var; diff --git a/src/solver/solver.cxx b/src/solver/solver.cxx index 1b7ec1fd74..7c0b6247dc 100644 --- a/src/solver/solver.cxx +++ b/src/solver/solver.cxx @@ -673,6 +673,10 @@ int Solver::init() { return 0; } +void Solver::modelOutputVars(Options& output_options) { + model->outputVars(output_options); +} + void Solver::outputVars(Options& output_options, bool save_repeat) { Timer time("io"); output_options["tt"].force(simtime, "Solver"); From 761bfc41b60ce0edd72a972baf6ddda8e0bdfb43 Mon Sep 17 00:00:00 2001 From: David Bold Date: Thu, 26 Sep 2024 10:18:27 +0200 Subject: [PATCH 084/242] Update PETSc download url --- bin/bout-build-deps.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/bout-build-deps.sh b/bin/bout-build-deps.sh index 19e3b2a0d3..6e53ecc0e8 100755 --- a/bin/bout-build-deps.sh +++ b/bin/bout-build-deps.sh @@ -10,7 +10,7 @@ NCVER=${NCVER:-4.7.4} NCCXXVER=${NCCXXVER:-4.3.1} FFTWVER=${FFTWVER:-3.3.9} SUNVER=${SUNVER:-5.7.0} -PETSCVER=${PETSCVER:-3.15.0} +PETSCVER=${PETSCVER:-3.21.4} HDF5FLAGS=${HDF5FLAGS:-} @@ -147,7 +147,7 @@ petsc() { test -z $PETSC_DIR || error "\$PETSC_DIR is set ($PETSC_DIR) - please unset" test -z $PETSC_ARCH || error "\$PETSC_ARCH is set ($PETSC_ARCH) - please unset" cd $BUILD - wget -c https://ftp.mcs.anl.gov/pub/petsc/release-snapshots/petsc-$PETSCVER.tar.gz || : + wget -c https://web.cels.anl.gov/projects/petsc/download/release-snapshots/petsc-$PETSCVER.tar.gz || : tar -xf petsc-$PETSCVER.tar.gz cd petsc-$PETSCVER unset PETSC_DIR From 805b4c1269c9d47bed6945d10966119d8e8c315c Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 27 Sep 2024 11:05:33 +0200 Subject: [PATCH 085/242] Add dummy functions for FieldPerp --- include/bout/fieldperp.hxx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/include/bout/fieldperp.hxx b/include/bout/fieldperp.hxx index 6995308dbe..ad069f0d01 100644 --- a/include/bout/fieldperp.hxx +++ b/include/bout/fieldperp.hxx @@ -157,6 +157,25 @@ public: return *this; } + /// Dummy functions to increase portability + bool hasParallelSlices() const { return true; } + void calcParallelSlices() const {} + void clearParallelSlices() {} + int numberParallelSlices() { return 0; } + + FieldPerp& yup(std::vector::size_type UNUSED(index) = 0) { return *this; } + const FieldPerp& yup(std::vector::size_type UNUSED(index) = 0) const { + return *this; + } + + FieldPerp& ydown(std::vector::size_type UNUSED(index) = 0) { return *this; } + const FieldPerp& ydown(std::vector::size_type UNUSED(index) = 0) const { + return *this; + } + + FieldPerp& ynext(int UNUSED(dir)) { return *this; } + const FieldPerp& ynext(int UNUSED(dir)) const { return *this; } + /*! * Ensure that data array is allocated and unique */ From d2200ccc7dc32127ecb47fcf0f6866a301865ce2 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 27 Sep 2024 11:05:58 +0200 Subject: [PATCH 086/242] Allow XZHermiteSpline also without y-offset --- src/mesh/interpolation/hermite_spline_xz.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/interpolation/hermite_spline_xz.cxx b/src/mesh/interpolation/hermite_spline_xz.cxx index 69df6d8906..650e4022e7 100644 --- a/src/mesh/interpolation/hermite_spline_xz.cxx +++ b/src/mesh/interpolation/hermite_spline_xz.cxx @@ -346,7 +346,7 @@ Field3D XZHermiteSpline::interpolate(const Field3D& f, const std::string& region ASSERT1(f.getMesh() == localmesh); Field3D f_interp{emptyFrom(f)}; - const auto region2 = fmt::format("RGN_YPAR_{:+d}", y_offset); + const auto region2 = y_offset != 0 ? fmt::format("RGN_YPAR_{:+d}", y_offset) : region; #if USE_NEW_WEIGHTS #ifdef HS_USE_PETSC From 433df79d2a96335f975db125122fc9315803579e Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 27 Sep 2024 11:06:33 +0200 Subject: [PATCH 087/242] Convert test to new iterator scheeme --- tests/integrated/test-fci-boundary/get_par_bndry.cxx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/integrated/test-fci-boundary/get_par_bndry.cxx b/tests/integrated/test-fci-boundary/get_par_bndry.cxx index ac0f5de2a6..4079b55574 100644 --- a/tests/integrated/test-fci-boundary/get_par_bndry.cxx +++ b/tests/integrated/test-fci-boundary/get_par_bndry.cxx @@ -14,11 +14,11 @@ int main(int argc, char** argv) { for (int i = 0; i < fields.size(); i++) { fields[i] = Field3D{0.0}; mesh->communicate(fields[i]); - for (const auto& bndry_par : + for (auto& bndry_par : mesh->getBoundariesPar(static_cast(i))) { output.write("{:s} region\n", toString(static_cast(i))); - for (bndry_par->first(); !bndry_par->isDone(); bndry_par->next()) { - fields[i][bndry_par->ind()] += 1; + for (const auto& pnt: *bndry_par) { + fields[i][pnt.ind()] += 1; output.write("{:s} increment\n", toString(static_cast(i))); } } From 8112e9881a844dd34a2894f2eda0e3807b2d0387 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 27 Sep 2024 15:54:33 +0200 Subject: [PATCH 088/242] Fix segfault in unit test The coordinate is not set, thus transforming to field aligned fails. --- tests/unit/include/test_derivs.cxx | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/include/test_derivs.cxx b/tests/unit/include/test_derivs.cxx index a6b8e43ef0..783af4f446 100644 --- a/tests/unit/include/test_derivs.cxx +++ b/tests/unit/include/test_derivs.cxx @@ -332,6 +332,7 @@ TEST_P(FirstDerivativesInterfaceTest, Sanity) { result = bout::derivatives::index::DDX(input); break; case DIRECTION::Y: + input.setDirectionY(YDirectionType::Aligned); result = bout::derivatives::index::DDY(input); break; case DIRECTION::Z: From 5c80fbd1a3331e0bce81b59fce89aaa949c4f5ca Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 27 Sep 2024 15:55:15 +0200 Subject: [PATCH 089/242] Ensure we do not segfault if coords is not set --- include/bout/field.hxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/bout/field.hxx b/include/bout/field.hxx index 0898b716c9..c2340f3d34 100644 --- a/include/bout/field.hxx +++ b/include/bout/field.hxx @@ -292,6 +292,7 @@ inline void checkPositive(const T& f, const std::string& name = "field", template inline T toFieldAligned(const T& f, const std::string& region = "RGN_ALL") { static_assert(bout::utils::is_Field_v, "toFieldAligned only works on Fields"); + ASSERT3(f.getCoordinates() != nullptr); return f.getCoordinates()->getParallelTransform().toFieldAligned(f, region); } @@ -299,6 +300,7 @@ inline T toFieldAligned(const T& f, const std::string& region = "RGN_ALL") { template inline T fromFieldAligned(const T& f, const std::string& region = "RGN_ALL") { static_assert(bout::utils::is_Field_v, "fromFieldAligned only works on Fields"); + ASSERT3(f.getCoordinates() != nullptr); return f.getCoordinates()->getParallelTransform().fromFieldAligned(f, region); } From 7b9b7e4bf43836a996018ebb1cdae5f26291b311 Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 30 Sep 2024 09:14:15 +0200 Subject: [PATCH 090/242] fix boundary condition --- tests/integrated/test-fci-mpi/fci_mpi.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integrated/test-fci-mpi/fci_mpi.cxx b/tests/integrated/test-fci-mpi/fci_mpi.cxx index f4c26adc96..94520dd4a6 100644 --- a/tests/integrated/test-fci-mpi/fci_mpi.cxx +++ b/tests/integrated/test-fci-mpi/fci_mpi.cxx @@ -20,7 +20,7 @@ int main(int argc, char** argv) { Options::getRoot(), mesh)}; // options->get(fmt::format("input_{:d}:boundary_perp", i), temp_str, s"free_o3"); mesh->communicate(input); - input.applyParallelBoundary("parallel_neumann"); + input.applyParallelBoundary("parallel_neumann_o2"); for (int slice = -mesh->ystart; slice <= mesh->ystart; ++slice) { if (slice != 0) { Field3D tmp{0.}; From d91607f66cdd7a6a476e641e0c86fbce14521f40 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 1 Oct 2024 15:27:21 +0200 Subject: [PATCH 091/242] Add Field2D version for 2D metrics --- include/bout/parallel_boundary_region.hxx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/include/bout/parallel_boundary_region.hxx b/include/bout/parallel_boundary_region.hxx index f8fe3d8ee1..837aeba392 100644 --- a/include/bout/parallel_boundary_region.hxx +++ b/include/bout/parallel_boundary_region.hxx @@ -192,6 +192,20 @@ public: return f.ynext(-dir)[ind().yp(-dir)]; } +#if BOUT_USE_METRIC_3D == 0 + const BoutReal& ynext(const Field2D& f) const { return f.ynext(dir)[ind().yp(dir)]; } + BoutReal& ynext(Field2D& f) const { return f.ynext(dir)[ind().yp(dir)]; } + + const BoutReal& yprev(const Field2D& f) const { + ASSERT3(valid() > 0); + return f.ynext(-dir)[ind().yp(-dir)]; + } + BoutReal& yprev(Field2D& f) const { + ASSERT3(valid() > 0); + return f.ynext(-dir)[ind().yp(-dir)]; + } +#endif + private: const IndicesVec& bndry_points; IndicesIter bndry_position; From e95636ebc54eadef8a8fbb4ece82a9ee9b973b7c Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 1 Oct 2024 15:52:40 +0200 Subject: [PATCH 092/242] Add setYPrevIfValid --- include/bout/parallel_boundary_region.hxx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/bout/parallel_boundary_region.hxx b/include/bout/parallel_boundary_region.hxx index 837aeba392..622843c858 100644 --- a/include/bout/parallel_boundary_region.hxx +++ b/include/bout/parallel_boundary_region.hxx @@ -191,6 +191,11 @@ public: ASSERT3(valid() > 0); return f.ynext(-dir)[ind().yp(-dir)]; } + void setYPrevIfValid(Field3D& f, BoutReal val) const { + if (valid() > 0) { + yprev(f) = val; + } + } #if BOUT_USE_METRIC_3D == 0 const BoutReal& ynext(const Field2D& f) const { return f.ynext(dir)[ind().yp(dir)]; } From 63531f0b81368bb95b63a59e18a84cf61ed8be1c Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 1 Oct 2024 16:15:55 +0200 Subject: [PATCH 093/242] Fix default region name --- src/sys/options.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sys/options.cxx b/src/sys/options.cxx index 080fb180ba..ce238bd09b 100644 --- a/src/sys/options.cxx +++ b/src/sys/options.cxx @@ -345,7 +345,7 @@ void saveParallel(Options& opt, const std::string name, const Field3D& tosave){ Field3D tmp; tmp.allocate(); const auto& fpar = tosave.ynext(i); - for (auto j: fpar.getValidRegionWithDefault("RGN_NO_BOUNDARY")){ + for (auto j: fpar.getValidRegionWithDefault("RGN_NOBNDRY")){ tmp[j.yp(-i)] = fpar[j]; } opt[fmt::format("{}_y{:+d}", name, i)] = tmp; From 2b1d9fb86dc2860bb831e26f579dafb7696c6004 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 11 Oct 2024 09:52:03 +0200 Subject: [PATCH 094/242] Rename to allowCalcParallelSlices It is not that the parallel slices may not be set - but rather that they must not be calculated by interpolation. --- include/bout/field3d.hxx | 12 ++++++------ src/field/field3d.cxx | 6 +++--- src/mesh/parallel/fci.cxx | 2 ++ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index 4eccedd7e3..70ae53178e 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -272,27 +272,27 @@ public: /// Return reference to yup field Field3D& yup(std::vector::size_type index = 0) { ASSERT2(index < yup_fields.size()); - ASSERT2(allow_parallel_slices); + ASSERT2(allowCalcParallelSlices); return yup_fields[index]; } /// Return const reference to yup field const Field3D& yup(std::vector::size_type index = 0) const { ASSERT2(index < yup_fields.size()); - ASSERT2(allow_parallel_slices); + ASSERT2(allowCalcParallelSlices); return yup_fields[index]; } /// Return reference to ydown field Field3D& ydown(std::vector::size_type index = 0) { ASSERT2(index < ydown_fields.size()); - ASSERT2(allow_parallel_slices); + ASSERT2(allowCalcParallelSlices); return ydown_fields[index]; } /// Return const reference to ydown field const Field3D& ydown(std::vector::size_type index = 0) const { ASSERT2(index < ydown_fields.size()); - ASSERT2(allow_parallel_slices); + ASSERT2(allowCalcParallelSlices); return ydown_fields[index]; } @@ -497,7 +497,7 @@ public: Field3D& calcParallelSlices(); void allowParallelSlices([[maybe_unused]] bool allow){ #if CHECK > 0 - allow_parallel_slices = allow; + allowCalcParallelSlices = allow; #endif } @@ -551,7 +551,7 @@ private: template Options* track(const T& change, std::string operation); Options* track(const BoutReal& change, std::string operation); - bool allow_parallel_slices{true}; + bool allowCalcParallelSlices{true}; }; diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index f45cfdcb61..c1704c9d36 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -147,7 +147,7 @@ BOUT_HOST_DEVICE Field3D* Field3D::timeDeriv() { void Field3D::splitParallelSlices() { TRACE("Field3D::splitParallelSlices"); - ASSERT2(allow_parallel_slices); + ASSERT2(allowCalcParallelSlices); if (hasParallelSlices()) { return; @@ -178,7 +178,7 @@ void Field3D::clearParallelSlices() { const Field3D& Field3D::ynext(int dir) const { #if CHECK > 0 - ASSERT2(allow_parallel_slices); + ASSERT2(allowCalcParallelSlices); // Asked for more than yguards if (std::abs(dir) > fieldmesh->ystart) { throw BoutException( @@ -377,7 +377,7 @@ Field3D& Field3D::operator=(const BoutReal val) { } Field3D& Field3D::calcParallelSlices() { - ASSERT2(allow_parallel_slices); + ASSERT2(allowCalcParallelSlices); getCoordinates()->getParallelTransform().calcParallelSlices(*this); #if BOUT_USE_FCI_AUTOMAGIC if (this->isFci()) { diff --git a/src/mesh/parallel/fci.cxx b/src/mesh/parallel/fci.cxx index 3363d331e1..2989bc2702 100644 --- a/src/mesh/parallel/fci.cxx +++ b/src/mesh/parallel/fci.cxx @@ -317,6 +317,8 @@ void FCITransform::checkInputGrid() { void FCITransform::calcParallelSlices(Field3D& f) { TRACE("FCITransform::calcParallelSlices"); + ASSERT1(f.allowCalcParallelSlices); + ASSERT1(f.getDirectionY() == YDirectionType::Standard); // Only have forward_map/backward_map for CELL_CENTRE, so can only deal with // CELL_CENTRE inputs From 749bddbbd4280920098651518f58bc4bf9b3bbd5 Mon Sep 17 00:00:00 2001 From: David Bold Date: Thu, 10 Oct 2024 12:45:35 +0200 Subject: [PATCH 095/242] Add code to load parallel metric slices --- include/bout/paralleltransform.hxx | 4 ++ src/mesh/coordinates.cxx | 5 ++ src/mesh/parallel/fci.cxx | 79 +++++++++++++++++++++++------- src/mesh/parallel/fci.hxx | 1 + 4 files changed, 72 insertions(+), 17 deletions(-) diff --git a/include/bout/paralleltransform.hxx b/include/bout/paralleltransform.hxx index 0aafa04303..c8050eec23 100644 --- a/include/bout/paralleltransform.hxx +++ b/include/bout/paralleltransform.hxx @@ -89,6 +89,10 @@ public: /// require a twist-shift at branch cuts on closed field lines? virtual bool requiresTwistShift(bool twist_shift_enabled, YDirectionType ytype) = 0; + /// Can be implemented to load parallel metrics + /// Needed by FCI + virtual void loadParallelMetrics(MAYBE_UNUSED(Coordinates* coords)) {} + protected: /// This method should be called in the constructor to check that if the grid /// has a 'parallel_transform' variable, it has the correct value diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index f728189d82..96dec02e52 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -601,6 +601,9 @@ Coordinates::Coordinates(Mesh* mesh, Options* options) // IntShiftTorsion will not be used, but set to zero to avoid uninitialized field IntShiftTorsion = 0.; } + + // Allow transform to fix things up + transform->loadParallelMetrics(this); } Coordinates::Coordinates(Mesh* mesh, Options* options, const CELL_LOC loc, @@ -889,6 +892,8 @@ Coordinates::Coordinates(Mesh* mesh, Options* options, const CELL_LOC loc, true, true, false, transform.get()); } } + // Allow transform to fix things up + transform->loadParallelMetrics(this); } void Coordinates::outputVars(Options& output_options) { diff --git a/src/mesh/parallel/fci.cxx b/src/mesh/parallel/fci.cxx index 2989bc2702..a71d19cfa8 100644 --- a/src/mesh/parallel/fci.cxx +++ b/src/mesh/parallel/fci.cxx @@ -47,6 +47,52 @@ #include +namespace { +// Get a unique name for a field based on the sign/magnitude of the offset +std::string parallel_slice_field_name(std::string field, int offset) { + const std::string direction = (offset > 0) ? "forward" : "backward"; + // We only have a suffix for parallel slices beyond the first + // This is for backwards compatibility + const std::string slice_suffix = + (std::abs(offset) > 1) ? "_" + std::to_string(std::abs(offset)) : ""; + return direction + "_" + field + slice_suffix; +}; + +void load_parallel_metric_component(std::string name, Field3D& component, int offset) { + Mesh* mesh = component.getMesh(); + Field3D tmp{mesh}; + const auto pname = parallel_slice_field_name(name, offset); + if (mesh->get(tmp, pname, 0.0, false) != 0) { + throw BoutException("Could not read {:s} from grid file!\n" + " Fix it up with `zoidberg-update-parallel-metrics `", pname); + } + if (!component.hasParallelSlices()){ + component.splitParallelSlices(); + component.allowCalcParallelSlices = false; + } + auto& pcom = component.ynext(offset); + pcom.allocate(); + BOUT_FOR(i, component.getRegion("RGN_NOBNDRY")) { + pcom[i.yp(offset)] = tmp[i]; + } +} + +void load_parallel_metric_components(Coordinates* coords, int offset){ +#define LOAD_PAR(var) load_parallel_metric_component(#var, coords->var, offset) + LOAD_PAR(g11); + LOAD_PAR(g22); + LOAD_PAR(g33); + LOAD_PAR(g13); + LOAD_PAR(g_11); + LOAD_PAR(g_22); + LOAD_PAR(g_33); + LOAD_PAR(g_13); + LOAD_PAR(J); +#undef LOAD_PAR +} + +} // namespace + FCIMap::FCIMap(Mesh& mesh, const Coordinates::FieldMetric& UNUSED(dy), Options& options, int offset_, const std::shared_ptr& inner_boundary, const std::shared_ptr& outer_boundary, bool zperiodic) @@ -82,38 +128,30 @@ FCIMap::FCIMap(Mesh& mesh, const Coordinates::FieldMetric& UNUSED(dy), Options& map_mesh.get(R, "R", 0.0, false); map_mesh.get(Z, "Z", 0.0, false); - // Get a unique name for a field based on the sign/magnitude of the offset - const auto parallel_slice_field_name = [&](std::string field) -> std::string { - const std::string direction = (offset > 0) ? "forward" : "backward"; - // We only have a suffix for parallel slices beyond the first - // This is for backwards compatibility - const std::string slice_suffix = - (std::abs(offset) > 1) ? "_" + std::to_string(std::abs(offset)) : ""; - return direction + "_" + field + slice_suffix; - }; // If we can't read in any of these fields, things will silently not // work, so best throw - if (map_mesh.get(xt_prime, parallel_slice_field_name("xt_prime"), 0.0, false) != 0) { + if (map_mesh.get(xt_prime, parallel_slice_field_name("xt_prime", offset), 0.0, false) != 0) { throw BoutException("Could not read {:s} from grid file!\n" " Either add it to the grid file, or reduce MYG", - parallel_slice_field_name("xt_prime")); + parallel_slice_field_name("xt_prime", offset)); } - if (map_mesh.get(zt_prime, parallel_slice_field_name("zt_prime"), 0.0, false) != 0) { + if (map_mesh.get(zt_prime, parallel_slice_field_name("zt_prime", offset), 0.0, false) != 0) { throw BoutException("Could not read {:s} from grid file!\n" " Either add it to the grid file, or reduce MYG", - parallel_slice_field_name("zt_prime")); + parallel_slice_field_name("zt_prime", offset)); } - if (map_mesh.get(R_prime, parallel_slice_field_name("R"), 0.0, false) != 0) { + if (map_mesh.get(R_prime, parallel_slice_field_name("R", offset), 0.0, false) != 0) { throw BoutException("Could not read {:s} from grid file!\n" " Either add it to the grid file, or reduce MYG", - parallel_slice_field_name("R")); + parallel_slice_field_name("R", offset)); } - if (map_mesh.get(Z_prime, parallel_slice_field_name("Z"), 0.0, false) != 0) { + if (map_mesh.get(Z_prime, parallel_slice_field_name("Z", offset), 0.0, false) != 0) { throw BoutException("Could not read {:s} from grid file!\n" " Either add it to the grid file, or reduce MYG", - parallel_slice_field_name("Z")); + parallel_slice_field_name("Z", offset)); } + // Cell corners Field3D xt_prime_corner{emptyFrom(xt_prime)}; @@ -350,3 +388,10 @@ void FCITransform::integrateParallelSlices(Field3D& f) { f.ynext(map.offset) = map.integrate(f); } } + +void FCITransform::loadParallelMetrics(Coordinates* coords) { + for (int i=1; i<= mesh.ystart; ++i) { + load_parallel_metric_components(coords, -i); + load_parallel_metric_components(coords, i); + } +} diff --git a/src/mesh/parallel/fci.hxx b/src/mesh/parallel/fci.hxx index 3ec3321a6a..7085a71535 100644 --- a/src/mesh/parallel/fci.hxx +++ b/src/mesh/parallel/fci.hxx @@ -150,6 +150,7 @@ public: return false; } + void loadParallelMetrics(Coordinates* coords) override; protected: void checkInputGrid() override; From a00625f3613eafc16690391e0c54e3683c2074df Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 16 Oct 2024 10:27:20 +0200 Subject: [PATCH 096/242] add setRegion / getRegionID to all fields --- include/bout/field.hxx | 7 +++++++ include/bout/field3d.hxx | 14 +++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/include/bout/field.hxx b/include/bout/field.hxx index c2340f3d34..e37b504744 100644 --- a/include/bout/field.hxx +++ b/include/bout/field.hxx @@ -34,6 +34,7 @@ class Field; #include #include #include +#include #include #include "bout/field_data.hxx" @@ -134,6 +135,12 @@ public: swap(first.directions, second.directions); } + virtual void setRegion(size_t UNUSED(regionID)) {} + virtual void setRegion(std::optional UNUSED(regionID)) {} + virtual void setRegion(const std::string& UNUSED(region_name)) {} + virtual void resetRegion() {} + virtual std::optional getRegionID() const { return {}; } + private: /// Labels for the type of coordinate system this field is defined over DirectionTypes directions{YDirectionType::Standard, ZDirectionType::Standard}; diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index 70ae53178e..d400fc101d 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -338,11 +338,11 @@ public: const Region& getRegion(const std::string& region_name) const; /// Use region provided by the default, and if none is set, use the provided one const Region& getValidRegionWithDefault(const std::string& region_name) const; - void setRegion(const std::string& region_name); - void resetRegion(); - void setRegion(size_t id); - void setRegion(std::optional id); - std::optional getRegionID() const { return regionID; }; + void setRegion(const std::string& region_name) override; + void resetRegion() override; + void setRegion(size_t id) override; + void setRegion(std::optional id) override; + std::optional getRegionID() const override { return regionID; }; /// Return a Region reference to use to iterate over the x- and /// y-indices of this field @@ -529,6 +529,8 @@ public: Options* getTracking() { return tracking; }; + bool allowCalcParallelSlices{true}; + private: /// Array sizes (from fieldmesh). These are valid only if fieldmesh is not null int nx{-1}, ny{-1}, nz{-1}; @@ -551,8 +553,6 @@ private: template Options* track(const T& change, std::string operation); Options* track(const BoutReal& change, std::string operation); - bool allowCalcParallelSlices{true}; - }; // Non-member overloaded operators From 1b4128fd463ea2face831065de9740088fde648b Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 16 Oct 2024 10:46:13 +0200 Subject: [PATCH 097/242] Prefer UNUSED over MAYBE_UNUSED MAYBE_UNUSED seems to no be defined --- include/bout/paralleltransform.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/bout/paralleltransform.hxx b/include/bout/paralleltransform.hxx index c8050eec23..63c75228fc 100644 --- a/include/bout/paralleltransform.hxx +++ b/include/bout/paralleltransform.hxx @@ -91,7 +91,7 @@ public: /// Can be implemented to load parallel metrics /// Needed by FCI - virtual void loadParallelMetrics(MAYBE_UNUSED(Coordinates* coords)) {} + virtual void loadParallelMetrics(Coordinates* UNUSED(coords)) {} protected: /// This method should be called in the constructor to check that if the grid From 4c50c6eac96e57c8e1676633eb4c4b25de93b292 Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 16 Oct 2024 10:46:47 +0200 Subject: [PATCH 098/242] Preserve regionID in emptyFrom --- include/bout/field.hxx | 3 ++- include/bout/field2d.hxx | 3 ++- include/bout/field3d.hxx | 3 ++- include/bout/fieldperp.hxx | 3 ++- src/field/field2d.cxx | 3 ++- src/field/field3d.cxx | 5 +++-- src/field/fieldperp.cxx | 2 +- 7 files changed, 14 insertions(+), 8 deletions(-) diff --git a/include/bout/field.hxx b/include/bout/field.hxx index e37b504744..51c30d78ad 100644 --- a/include/bout/field.hxx +++ b/include/bout/field.hxx @@ -192,7 +192,8 @@ inline bool areFieldsCompatible(const Field& field1, const Field& field2) { template inline T emptyFrom(const T& f) { static_assert(bout::utils::is_Field_v, "emptyFrom only works on Fields"); - return T(f.getMesh(), f.getLocation(), {f.getDirectionY(), f.getDirectionZ()}) + return T(f.getMesh(), f.getLocation(), {f.getDirectionY(), f.getDirectionZ()}, + f.getRegionID()) .allocate(); } diff --git a/include/bout/field2d.hxx b/include/bout/field2d.hxx index cd036c04ff..97f04a3b83 100644 --- a/include/bout/field2d.hxx +++ b/include/bout/field2d.hxx @@ -68,7 +68,8 @@ public: */ Field2D(Mesh* localmesh = nullptr, CELL_LOC location_in = CELL_CENTRE, DirectionTypes directions_in = {YDirectionType::Standard, - ZDirectionType::Average}); + ZDirectionType::Average}, + std::optional region = {}); /*! * Copy constructor. After this both fields diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index d400fc101d..d03f489f62 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -170,7 +170,8 @@ public: */ Field3D(Mesh* localmesh = nullptr, CELL_LOC location_in = CELL_CENTRE, DirectionTypes directions_in = {YDirectionType::Standard, - ZDirectionType::Standard}); + ZDirectionType::Standard}, + std::optional regionID = {}); /*! * Copy constructor diff --git a/include/bout/fieldperp.hxx b/include/bout/fieldperp.hxx index ad069f0d01..b50eef1991 100644 --- a/include/bout/fieldperp.hxx +++ b/include/bout/fieldperp.hxx @@ -58,7 +58,8 @@ public: FieldPerp(Mesh* fieldmesh = nullptr, CELL_LOC location_in = CELL_CENTRE, int yindex_in = -1, DirectionTypes directions_in = {YDirectionType::Standard, - ZDirectionType::Standard}); + ZDirectionType::Standard}, + std::optional regionID = {}); /*! * Copy constructor. After this the data diff --git a/src/field/field2d.cxx b/src/field/field2d.cxx index 6a6740669b..00a2777125 100644 --- a/src/field/field2d.cxx +++ b/src/field/field2d.cxx @@ -48,7 +48,8 @@ #include -Field2D::Field2D(Mesh* localmesh, CELL_LOC location_in, DirectionTypes directions_in) +Field2D::Field2D(Mesh* localmesh, CELL_LOC location_in, DirectionTypes directions_in, + std::optional UNUSED(regionID)) : Field(localmesh, location_in, directions_in) { if (fieldmesh) { diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index c1704c9d36..334b1e9ebd 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -48,8 +48,9 @@ #include /// Constructor -Field3D::Field3D(Mesh* localmesh, CELL_LOC location_in, DirectionTypes directions_in) - : Field(localmesh, location_in, directions_in) { +Field3D::Field3D(Mesh* localmesh, CELL_LOC location_in, DirectionTypes directions_in, + std::optional regionID) + : Field(localmesh, location_in, directions_in), regionID{regionID} { #if BOUT_USE_TRACK name = ""; #endif diff --git a/src/field/fieldperp.cxx b/src/field/fieldperp.cxx index 22e8aa994e..4012647454 100644 --- a/src/field/fieldperp.cxx +++ b/src/field/fieldperp.cxx @@ -35,7 +35,7 @@ #include FieldPerp::FieldPerp(Mesh* localmesh, CELL_LOC location_in, int yindex_in, - DirectionTypes directions) + DirectionTypes directions, std::optional UNUSED(regionID)) : Field(localmesh, location_in, directions), yindex(yindex_in) { if (fieldmesh) { nx = fieldmesh->LocalNx; From f88a35f52154b999125eec7bc6e32277d910f410 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 11 Oct 2024 17:04:17 +0200 Subject: [PATCH 099/242] set region in loaded parallel fields --- src/mesh/parallel/fci.cxx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mesh/parallel/fci.cxx b/src/mesh/parallel/fci.cxx index a71d19cfa8..82300f73b9 100644 --- a/src/mesh/parallel/fci.cxx +++ b/src/mesh/parallel/fci.cxx @@ -72,6 +72,7 @@ void load_parallel_metric_component(std::string name, Field3D& component, int of } auto& pcom = component.ynext(offset); pcom.allocate(); + pcom.setRegion(fmt::format("RGN_YPAR_{:+d}", offset)); BOUT_FOR(i, component.getRegion("RGN_NOBNDRY")) { pcom[i.yp(offset)] = tmp[i]; } From 638438483a53d170377646c3b5ffc2aaf01961f7 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 15 Oct 2024 16:21:41 +0200 Subject: [PATCH 100/242] Only load parallel J if J is loadable --- src/mesh/parallel/fci.cxx | 78 +++++++++++++++++++++++++++++++-------- 1 file changed, 63 insertions(+), 15 deletions(-) diff --git a/src/mesh/parallel/fci.cxx b/src/mesh/parallel/fci.cxx index 82300f73b9..2532785540 100644 --- a/src/mesh/parallel/fci.cxx +++ b/src/mesh/parallel/fci.cxx @@ -58,13 +58,35 @@ std::string parallel_slice_field_name(std::string field, int offset) { return direction + "_" + field + slice_suffix; }; -void load_parallel_metric_component(std::string name, Field3D& component, int offset) { +bool load_parallel_metric_component(std::string name, Field3D& component, int offset, + bool doZero) { Mesh* mesh = component.getMesh(); Field3D tmp{mesh}; - const auto pname = parallel_slice_field_name(name, offset); - if (mesh->get(tmp, pname, 0.0, false) != 0) { - throw BoutException("Could not read {:s} from grid file!\n" - " Fix it up with `zoidberg-update-parallel-metrics `", pname); + bool doload = mesh->sourceHasVar(name); + bool isValid{false}; + if (doload) { + const auto pname = parallel_slice_field_name(name, offset); + isValid = mesh->get(tmp, pname, 0.0, false) == 0; + if (not isValid) { + throw BoutException("Could not read {:s} from grid file!\n" + " Fix it up with `zoidberg-update-parallel-metrics `", + pname); + } + } else { + auto lmin = min(component, true); + auto lmax = max(component, true); + if (lmin != lmax) { + if (doZero) { + lmin = lmax = 0.0; + } else { + throw BoutException("{:s} not in grid file but not constant!\n" + " Cannot determine value for parallel slices", + name); + } + } else { + isValid = true; + } + tmp = lmin; } if (!component.hasParallelSlices()){ component.splitParallelSlices(); @@ -76,19 +98,45 @@ void load_parallel_metric_component(std::string name, Field3D& component, int of BOUT_FOR(i, component.getRegion("RGN_NOBNDRY")) { pcom[i.yp(offset)] = tmp[i]; } + return isValid; } void load_parallel_metric_components(Coordinates* coords, int offset){ -#define LOAD_PAR(var) load_parallel_metric_component(#var, coords->var, offset) - LOAD_PAR(g11); - LOAD_PAR(g22); - LOAD_PAR(g33); - LOAD_PAR(g13); - LOAD_PAR(g_11); - LOAD_PAR(g_22); - LOAD_PAR(g_33); - LOAD_PAR(g_13); - LOAD_PAR(J); +#define LOAD_PAR(var, doZero) \ + load_parallel_metric_component(#var, coords->var, offset, doZero) + LOAD_PAR(g11, false); + LOAD_PAR(g22, false); + LOAD_PAR(g33, false); + LOAD_PAR(g12, false); + LOAD_PAR(g13, false); + LOAD_PAR(g23, false); + + LOAD_PAR(g_11, false); + LOAD_PAR(g_22, false); + LOAD_PAR(g_33, false); + LOAD_PAR(g_12, false); + LOAD_PAR(g_13, false); + LOAD_PAR(g_23, false); + + if (not LOAD_PAR(J, true)) { + auto g = + coords->g11.ynext(offset) * coords->g22.ynext(offset) * coords->g33.ynext(offset) + + 2.0 * coords->g12.ynext(offset) * coords->g13.ynext(offset) + * coords->g23.ynext(offset) + - coords->g11.ynext(offset) * coords->g23.ynext(offset) + * coords->g23.ynext(offset) + - coords->g22.ynext(offset) * coords->g13.ynext(offset) + * coords->g13.ynext(offset) + - coords->g33.ynext(offset) * coords->g12.ynext(offset) + * coords->g12.ynext(offset); + + const auto rgn = fmt::format("RGN_YPAR_{:+d}", offset); + // Check that g is positive + bout::checkPositive(g, "The determinant of g^ij", rgn); + auto J = 1. / sqrt(g); + auto& pcom = coords->J.ynext(offset); + BOUT_FOR(i, J.getRegion(rgn)) { pcom[i] = J[i]; } + } #undef LOAD_PAR } From 1ab7fb0e925a43aa2e2ffc77a074a76f926a5bc2 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 11 Oct 2024 15:31:47 +0200 Subject: [PATCH 101/242] Fix Div_par --- src/mesh/coordinates.cxx | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index 96dec02e52..41c2a0bc21 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -1616,20 +1616,15 @@ Field3D Coordinates::Div_par(const Field3D& f, CELL_LOC outloc, return Bxy * Grad_par(f / Bxy_floc, outloc, method); } -#if BOUT_USE_FCI_AUTOMAGIC - if (!Bxy_floc.hasParallelSlices()) { - localmesh->communicate(Bxy_floc); - Bxy_floc.applyParallelBoundary("parallel_neumann_o2"); - } -#endif + auto coords = f.getCoordinates(); // Need to modify yup and ydown fields - Field3D f_B = f / Bxy_floc; + Field3D f_B = f / coords->J * sqrt(coords->g_22); f_B.splitParallelSlices(); for (int i = 0; i < f.getMesh()->ystart; ++i) { - f_B.yup(i) = f.yup(i) / Bxy_floc.yup(i); - f_B.ydown(i) = f.ydown(i) / Bxy_floc.ydown(i); + f_B.yup(i) = f.yup(i) / coords->J.yup(i) * sqrt(coords->g_22.yup(i)); + f_B.ydown(i) = f.ydown(i) / coords->J.ydown(i) * sqrt(coords->g_22.ydown(i)); } - return setName(Bxy * Grad_par(f_B, outloc, method), "C:Div_par({:s})", f.name); + return setName(coords->J / sqrt(coords->g_22) * Grad_par(f_B, outloc, method), "Div_par({:s})", f.name); } ///////////////////////////////////////////////////////// From 5f7a7992cb8a9968b93c5e2f1c1e5dc6e92aa55e Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 18 Oct 2024 15:39:45 +0200 Subject: [PATCH 102/242] Only check for allowCalcParallelSlices if we are about to calculate Previously this flag was used to prevent the usage of parallel slices, now it only prevents calculation. --- include/bout/field3d.hxx | 4 ---- src/field/field3d.cxx | 2 -- 2 files changed, 6 deletions(-) diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index d03f489f62..ddbc628050 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -273,27 +273,23 @@ public: /// Return reference to yup field Field3D& yup(std::vector::size_type index = 0) { ASSERT2(index < yup_fields.size()); - ASSERT2(allowCalcParallelSlices); return yup_fields[index]; } /// Return const reference to yup field const Field3D& yup(std::vector::size_type index = 0) const { ASSERT2(index < yup_fields.size()); - ASSERT2(allowCalcParallelSlices); return yup_fields[index]; } /// Return reference to ydown field Field3D& ydown(std::vector::size_type index = 0) { ASSERT2(index < ydown_fields.size()); - ASSERT2(allowCalcParallelSlices); return ydown_fields[index]; } /// Return const reference to ydown field const Field3D& ydown(std::vector::size_type index = 0) const { ASSERT2(index < ydown_fields.size()); - ASSERT2(allowCalcParallelSlices); return ydown_fields[index]; } diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index 334b1e9ebd..cc6e3509fc 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -148,7 +148,6 @@ BOUT_HOST_DEVICE Field3D* Field3D::timeDeriv() { void Field3D::splitParallelSlices() { TRACE("Field3D::splitParallelSlices"); - ASSERT2(allowCalcParallelSlices); if (hasParallelSlices()) { return; @@ -179,7 +178,6 @@ void Field3D::clearParallelSlices() { const Field3D& Field3D::ynext(int dir) const { #if CHECK > 0 - ASSERT2(allowCalcParallelSlices); // Asked for more than yguards if (std::abs(dir) > fieldmesh->ystart) { throw BoutException( From f7919e0ee1bf7b14cd34a348b5bad4dd22302530 Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 6 Nov 2024 14:52:41 +0100 Subject: [PATCH 103/242] Fix bad merge --- include/bout/mask.hxx | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/bout/mask.hxx b/include/bout/mask.hxx index 386bcbf127..624f3d7513 100644 --- a/include/bout/mask.hxx +++ b/include/bout/mask.hxx @@ -66,8 +66,6 @@ public: inline bool& operator()(int jx, int jy, int jz) { return mask(jx, jy, jz); } inline const bool& operator()(int jx, int jy, int jz) const { return mask(jx, jy, jz); } - - inline bool& operator[](const Ind3D& i) { return mask[i]; } inline const bool& operator[](const Ind3D& i) const { return mask[i]; } inline bool& operator[](const Ind3D& i) { return mask[i]; } }; From c9124f605c2ede89b6e010ae11da7d5cef6fa1dc Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 6 Nov 2024 14:54:00 +0100 Subject: [PATCH 104/242] Fix error message --- src/mesh/parallel/fci.cxx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mesh/parallel/fci.cxx b/src/mesh/parallel/fci.cxx index 2532785540..29e6e14739 100644 --- a/src/mesh/parallel/fci.cxx +++ b/src/mesh/parallel/fci.cxx @@ -69,7 +69,7 @@ bool load_parallel_metric_component(std::string name, Field3D& component, int of isValid = mesh->get(tmp, pname, 0.0, false) == 0; if (not isValid) { throw BoutException("Could not read {:s} from grid file!\n" - " Fix it up with `zoidberg-update-parallel-metrics `", + "Regenerate the grid with a recent zoidberg!", pname); } } else { @@ -80,7 +80,8 @@ bool load_parallel_metric_component(std::string name, Field3D& component, int of lmin = lmax = 0.0; } else { throw BoutException("{:s} not in grid file but not constant!\n" - " Cannot determine value for parallel slices", + " Cannot determine value for parallel slices.\n" + " Regenerate the grid with a recent zoidberg!", name); } } else { From adf3e51663815f35b6f5c8d7de6280f22a30d97a Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 6 Nov 2024 15:24:49 +0100 Subject: [PATCH 105/242] Set parallel slices only for 3D metrics --- src/mesh/parallel/fci.cxx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/mesh/parallel/fci.cxx b/src/mesh/parallel/fci.cxx index 29e6e14739..eca119b9c5 100644 --- a/src/mesh/parallel/fci.cxx +++ b/src/mesh/parallel/fci.cxx @@ -58,6 +58,7 @@ std::string parallel_slice_field_name(std::string field, int offset) { return direction + "_" + field + slice_suffix; }; +#if BOUT_USE_METRIC3D bool load_parallel_metric_component(std::string name, Field3D& component, int offset, bool doZero) { Mesh* mesh = component.getMesh(); @@ -101,8 +102,10 @@ bool load_parallel_metric_component(std::string name, Field3D& component, int of } return isValid; } +#endif -void load_parallel_metric_components(Coordinates* coords, int offset){ +void load_parallel_metric_components([[maybe_unused]] Coordinates* coords, [[maybe_unused]] int offset){ +#if BOUT_USE_METRIC3D #define LOAD_PAR(var, doZero) \ load_parallel_metric_component(#var, coords->var, offset, doZero) LOAD_PAR(g11, false); @@ -139,6 +142,7 @@ void load_parallel_metric_components(Coordinates* coords, int offset){ BOUT_FOR(i, J.getRegion(rgn)) { pcom[i] = J[i]; } } #undef LOAD_PAR +#endif } } // namespace From 0619ffefa326a20452faf938ca1db8b7329d0035 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 8 Nov 2024 15:24:20 +0100 Subject: [PATCH 106/242] Fix #if guard --- src/mesh/parallel/fci.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mesh/parallel/fci.cxx b/src/mesh/parallel/fci.cxx index eca119b9c5..758b26a377 100644 --- a/src/mesh/parallel/fci.cxx +++ b/src/mesh/parallel/fci.cxx @@ -58,7 +58,7 @@ std::string parallel_slice_field_name(std::string field, int offset) { return direction + "_" + field + slice_suffix; }; -#if BOUT_USE_METRIC3D +#if BOUT_USE_METRIC_3D bool load_parallel_metric_component(std::string name, Field3D& component, int offset, bool doZero) { Mesh* mesh = component.getMesh(); @@ -105,7 +105,7 @@ bool load_parallel_metric_component(std::string name, Field3D& component, int of #endif void load_parallel_metric_components([[maybe_unused]] Coordinates* coords, [[maybe_unused]] int offset){ -#if BOUT_USE_METRIC3D +#if BOUT_USE_METRIC_3D #define LOAD_PAR(var, doZero) \ load_parallel_metric_component(#var, coords->var, offset, doZero) LOAD_PAR(g11, false); From ac212ef834bed90ae000600d696ed7622a9a5e63 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 8 Nov 2024 15:50:07 +0100 Subject: [PATCH 107/242] Fix bad merge --- include/bout/field.hxx | 1 + src/solver/impls/euler/euler.cxx | 1 + src/solver/impls/pvode/pvode.cxx | 1 + 3 files changed, 3 insertions(+) diff --git a/include/bout/field.hxx b/include/bout/field.hxx index 863163ce60..d56322070e 100644 --- a/include/bout/field.hxx +++ b/include/bout/field.hxx @@ -31,6 +31,7 @@ class Field; #include #include +#include #include #include "bout/bout_types.hxx" diff --git a/src/solver/impls/euler/euler.cxx b/src/solver/impls/euler/euler.cxx index 5477b5760b..709ac5ba9b 100644 --- a/src/solver/impls/euler/euler.cxx +++ b/src/solver/impls/euler/euler.cxx @@ -6,6 +6,7 @@ #include #include #include +#include #include diff --git a/src/solver/impls/pvode/pvode.cxx b/src/solver/impls/pvode/pvode.cxx index 7524d21238..65d44d6e49 100644 --- a/src/solver/impls/pvode/pvode.cxx +++ b/src/solver/impls/pvode/pvode.cxx @@ -35,6 +35,7 @@ #include #include #include +#include #include "bout/unused.hxx" From 70726f3dfff66b5908ea1e4ccd8ca129919afc2c Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Thu, 21 Nov 2024 15:14:22 -0800 Subject: [PATCH 108/242] Fix Field3D::setBoundaryTo for FCI methods Without this fix, boundary conditions set on yup/down fields are not applied when a boundary is copied from one field to another. --- src/field/field3d.cxx | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index cc6e3509fc..eec87b4f33 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -491,7 +491,25 @@ void Field3D::setBoundaryTo(const Field3D& f3d) { allocate(); // Make sure data allocated - /// Loop over boundary regions + if (isFci()) { + // Set yup/ydown using midpoint values from f3d + ASSERT1(f3d.hasParallelSlices()); + ASSERT1(hasParallelSlices()); + + for (auto& region : fieldmesh->getBoundariesPar()) { + for (const auto& pnt : *region) { + // Interpolate midpoint value in f3d + const BoutReal val = pnt.interpolate_sheath_o1(f3d); + // Set the same boundary value in this field + pnt.dirichlet_o2(*this, val); + } + } + return; + } + + // Non-FCI. + // Transform to field-aligned coordinates? + // Loop over boundary regions for (const auto& reg : fieldmesh->getBoundaries()) { /// Loop within each region for (reg->first(); !reg->isDone(); reg->next()) { From c64d43934ceac9ce05752fd34323146703bde7ea Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 25 Nov 2024 16:44:29 +0100 Subject: [PATCH 109/242] Copy BCs in x-direction also for FCI --- src/field/field3d.cxx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index eec87b4f33..8772f4aed3 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -504,13 +504,15 @@ void Field3D::setBoundaryTo(const Field3D& f3d) { pnt.dirichlet_o2(*this, val); } } - return; } // Non-FCI. // Transform to field-aligned coordinates? // Loop over boundary regions for (const auto& reg : fieldmesh->getBoundaries()) { + if (isFci() && reg->by != 0) { + continue; + } /// Loop within each region for (reg->first(); !reg->isDone(); reg->next()) { for (int z = 0; z < nz; z++) { From 2d64a0d7a9f4a46b235e7007f07b85ee71d385aa Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 25 Nov 2024 16:48:06 +0100 Subject: [PATCH 110/242] Use consistently first order interpolation --- src/field/field3d.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index 8772f4aed3..2f97e8d02b 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -501,7 +501,7 @@ void Field3D::setBoundaryTo(const Field3D& f3d) { // Interpolate midpoint value in f3d const BoutReal val = pnt.interpolate_sheath_o1(f3d); // Set the same boundary value in this field - pnt.dirichlet_o2(*this, val); + pnt.dirichlet_o1(*this, val); } } } From d59517ef5f0e957c39132f4951c9b449d89594ce Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 25 Nov 2024 16:50:34 +0100 Subject: [PATCH 111/242] Disable broken test-laplace-petsc3d by default --- tests/integrated/test-laplace-petsc3d/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integrated/test-laplace-petsc3d/CMakeLists.txt b/tests/integrated/test-laplace-petsc3d/CMakeLists.txt index 93bf4f7efa..d0d5bd5958 100644 --- a/tests/integrated/test-laplace-petsc3d/CMakeLists.txt +++ b/tests/integrated/test-laplace-petsc3d/CMakeLists.txt @@ -6,5 +6,5 @@ bout_add_integrated_test(test-laplace-petsc3d data_slab_core/BOUT.inp data_slab_sol/BOUT.inp USE_RUNTEST - REQUIRES BOUT_HAS_PETSC + REQUIRES BOUT_HAS_PETSC BOUT_ENABLE_ALL_TESTS ) From a65b1d8c483cb6d80b71a027bc0971239ae85dea Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 25 Nov 2024 17:49:54 +0100 Subject: [PATCH 112/242] Fix unit test for FCI --- tests/unit/include/bout/test_single_index_ops.cxx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/unit/include/bout/test_single_index_ops.cxx b/tests/unit/include/bout/test_single_index_ops.cxx index 4359d1d282..8ce9f77a19 100644 --- a/tests/unit/include/bout/test_single_index_ops.cxx +++ b/tests/unit/include/bout/test_single_index_ops.cxx @@ -276,6 +276,9 @@ TEST_F(SingleIndexOpsTest, Div_par) { // Need parallel derivatives of input input.calcParallelSlices(); + // and of coordinates + input.getMesh()->getCoordinates()->J.calcParallelSlices(); + input.getMesh()->getCoordinates()->g_22.calcParallelSlices(); // Differentiate whole field Field3D difops = Div_par(input); From b71e978385ac02b61bbc5960d1777c3f483b5384 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 26 Nov 2024 09:35:20 +0100 Subject: [PATCH 113/242] Update to new grid with parallel metrics --- tests/integrated/test-fci-mpi/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integrated/test-fci-mpi/CMakeLists.txt b/tests/integrated/test-fci-mpi/CMakeLists.txt index 0dd38487a3..783b30bfd4 100644 --- a/tests/integrated/test-fci-mpi/CMakeLists.txt +++ b/tests/integrated/test-fci-mpi/CMakeLists.txt @@ -3,7 +3,7 @@ bout_add_mms_test(test-fci-mpi USE_RUNTEST USE_DATA_BOUT_INP PROCESSORS 6 - DOWNLOAD https://zenodo.org/record/7614499/files/W7X-conf4-36x8x128.fci.nc?download=1 + DOWNLOAD https://zenodo.org/records/14221309/files/W7X-conf0-36x8x128.fci.nc?download=1 DOWNLOAD_NAME grid.fci.nc REQUIRES BOUT_HAS_PETSC ) From 93b6c485add37f435abbf5330108f6970de2c0b4 Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 25 Nov 2024 12:43:42 +0100 Subject: [PATCH 114/242] Avoid define conflict with sundials --- externalpackages/PVODE/include/pvode/band.h | 46 ++++++++++----------- externalpackages/PVODE/precon/band.h | 46 ++++++++++----------- externalpackages/PVODE/precon/pvbbdpre.cpp | 4 +- 3 files changed, 48 insertions(+), 48 deletions(-) diff --git a/externalpackages/PVODE/include/pvode/band.h b/externalpackages/PVODE/include/pvode/band.h index 1fd04a2057..d8eb2d92e9 100644 --- a/externalpackages/PVODE/include/pvode/band.h +++ b/externalpackages/PVODE/include/pvode/band.h @@ -107,13 +107,13 @@ namespace pvode { * references and without knowing too much about the underlying * * element storage. The only storage assumption needed is that * * elements are stored columnwise and that a pointer into the jth * - * column of elements can be obtained via the BAND_COL macro. The * - * BAND_COL_ELEM macro selects an element from a column which has * - * already been isolated via BAND_COL. BAND_COL_ELEM allows the * + * column of elements can be obtained via the PVODE_BAND_COL macro. The * + * PVODE_BAND_COL_ELEM macro selects an element from a column which has * + * already been isolated via PVODE_BAND_COL. PVODE_BAND_COL_ELEM allows the * * user to avoid the translation from the matrix location (i,j) * - * to the index in the array returned by BAND_COL at which the * - * (i,j)th element is stored. See the documentation for BAND_COL * - * and BAND_COL_ELEM for usage details. Users should use these * + * to the index in the array returned by PVODE_BAND_COL at which the * + * (i,j)th element is stored. See the documentation for PVODE_BAND_COL * + * and PVODE_BAND_COL_ELEM for usage details. Users should use these * * macros whenever possible. * * * ******************************************************************/ @@ -131,49 +131,49 @@ typedef struct bandmat_type { /****************************************************************** * * - * Macro : BAND_ELEM * - * Usage : BAND_ELEM(A,i,j) = a_ij; OR * - * a_ij = BAND_ELEM(A,i,j); * + * Macro : PVODE_BAND_ELEM * + * Usage : PVODE_BAND_ELEM(A,i,j) = a_ij; OR * + * a_ij = PVODE_BAND_ELEM(A,i,j); * *----------------------------------------------------------------* - * BAND_ELEM(A,i,j) references the (i,j)th element of the * + * PVODE_BAND_ELEM(A,i,j) references the (i,j)th element of the * * N by N band matrix A, where 0 <= i,j <= N-1. The location * * (i,j) should further satisfy j-(A->mu) <= i <= j+(A->ml). * * * ******************************************************************/ -#define BAND_ELEM(A,i,j) ((A->data)[j][i-j+(A->smu)]) +#define PVODE_BAND_ELEM(A,i,j) ((A->data)[j][i-j+(A->smu)]) /****************************************************************** * * - * Macro : BAND_COL * - * Usage : col_j = BAND_COL(A,j); * + * Macro : PVODE_BAND_COL * + * Usage : col_j = PVODE_BAND_COL(A,j); * *----------------------------------------------------------------* - * BAND_COL(A,j) references the diagonal element of the jth * + * PVODE_BAND_COL(A,j) references the diagonal element of the jth * * column of the N by N band matrix A, 0 <= j <= N-1. The type of * - * the expression BAND_COL(A,j) is real *. The pointer returned * - * by the call BAND_COL(A,j) can be treated as an array which is * + * the expression PVODE_BAND_COL(A,j) is real *. The pointer returned * + * by the call PVODE_BAND_COL(A,j) can be treated as an array which is * * indexed from -(A->mu) to (A->ml). * * * ******************************************************************/ -#define BAND_COL(A,j) (((A->data)[j])+(A->smu)) +#define PVODE_BAND_COL(A,j) (((A->data)[j])+(A->smu)) /****************************************************************** * * - * Macro : BAND_COL_ELEM * - * Usage : col_j = BAND_COL(A,j); * - * BAND_COL_ELEM(col_j,i,j) = a_ij; OR * - * a_ij = BAND_COL_ELEM(col_j,i,j); * + * Macro : PVODE_BAND_COL_ELEM * + * Usage : col_j = PVODE_BAND_COL(A,j); * + * PVODE_BAND_COL_ELEM(col_j,i,j) = a_ij; OR * + * a_ij = PVODE_BAND_COL_ELEM(col_j,i,j); * *----------------------------------------------------------------* * This macro references the (i,j)th entry of the band matrix A * - * when used in conjunction with BAND_COL as shown above. The * + * when used in conjunction with PVODE_BAND_COL as shown above. The * * index (i,j) should satisfy j-(A->mu) <= i <= j+(A->ml). * * * ******************************************************************/ -#define BAND_COL_ELEM(col_j,i,j) (col_j[i-j]) +#define PVODE_BAND_COL_ELEM(col_j,i,j) (col_j[i-j]) /* Functions that use the BandMat representation for a band matrix */ diff --git a/externalpackages/PVODE/precon/band.h b/externalpackages/PVODE/precon/band.h index 1fd04a2057..d8eb2d92e9 100644 --- a/externalpackages/PVODE/precon/band.h +++ b/externalpackages/PVODE/precon/band.h @@ -107,13 +107,13 @@ namespace pvode { * references and without knowing too much about the underlying * * element storage. The only storage assumption needed is that * * elements are stored columnwise and that a pointer into the jth * - * column of elements can be obtained via the BAND_COL macro. The * - * BAND_COL_ELEM macro selects an element from a column which has * - * already been isolated via BAND_COL. BAND_COL_ELEM allows the * + * column of elements can be obtained via the PVODE_BAND_COL macro. The * + * PVODE_BAND_COL_ELEM macro selects an element from a column which has * + * already been isolated via PVODE_BAND_COL. PVODE_BAND_COL_ELEM allows the * * user to avoid the translation from the matrix location (i,j) * - * to the index in the array returned by BAND_COL at which the * - * (i,j)th element is stored. See the documentation for BAND_COL * - * and BAND_COL_ELEM for usage details. Users should use these * + * to the index in the array returned by PVODE_BAND_COL at which the * + * (i,j)th element is stored. See the documentation for PVODE_BAND_COL * + * and PVODE_BAND_COL_ELEM for usage details. Users should use these * * macros whenever possible. * * * ******************************************************************/ @@ -131,49 +131,49 @@ typedef struct bandmat_type { /****************************************************************** * * - * Macro : BAND_ELEM * - * Usage : BAND_ELEM(A,i,j) = a_ij; OR * - * a_ij = BAND_ELEM(A,i,j); * + * Macro : PVODE_BAND_ELEM * + * Usage : PVODE_BAND_ELEM(A,i,j) = a_ij; OR * + * a_ij = PVODE_BAND_ELEM(A,i,j); * *----------------------------------------------------------------* - * BAND_ELEM(A,i,j) references the (i,j)th element of the * + * PVODE_BAND_ELEM(A,i,j) references the (i,j)th element of the * * N by N band matrix A, where 0 <= i,j <= N-1. The location * * (i,j) should further satisfy j-(A->mu) <= i <= j+(A->ml). * * * ******************************************************************/ -#define BAND_ELEM(A,i,j) ((A->data)[j][i-j+(A->smu)]) +#define PVODE_BAND_ELEM(A,i,j) ((A->data)[j][i-j+(A->smu)]) /****************************************************************** * * - * Macro : BAND_COL * - * Usage : col_j = BAND_COL(A,j); * + * Macro : PVODE_BAND_COL * + * Usage : col_j = PVODE_BAND_COL(A,j); * *----------------------------------------------------------------* - * BAND_COL(A,j) references the diagonal element of the jth * + * PVODE_BAND_COL(A,j) references the diagonal element of the jth * * column of the N by N band matrix A, 0 <= j <= N-1. The type of * - * the expression BAND_COL(A,j) is real *. The pointer returned * - * by the call BAND_COL(A,j) can be treated as an array which is * + * the expression PVODE_BAND_COL(A,j) is real *. The pointer returned * + * by the call PVODE_BAND_COL(A,j) can be treated as an array which is * * indexed from -(A->mu) to (A->ml). * * * ******************************************************************/ -#define BAND_COL(A,j) (((A->data)[j])+(A->smu)) +#define PVODE_BAND_COL(A,j) (((A->data)[j])+(A->smu)) /****************************************************************** * * - * Macro : BAND_COL_ELEM * - * Usage : col_j = BAND_COL(A,j); * - * BAND_COL_ELEM(col_j,i,j) = a_ij; OR * - * a_ij = BAND_COL_ELEM(col_j,i,j); * + * Macro : PVODE_BAND_COL_ELEM * + * Usage : col_j = PVODE_BAND_COL(A,j); * + * PVODE_BAND_COL_ELEM(col_j,i,j) = a_ij; OR * + * a_ij = PVODE_BAND_COL_ELEM(col_j,i,j); * *----------------------------------------------------------------* * This macro references the (i,j)th entry of the band matrix A * - * when used in conjunction with BAND_COL as shown above. The * + * when used in conjunction with PVODE_BAND_COL as shown above. The * * index (i,j) should satisfy j-(A->mu) <= i <= j+(A->ml). * * * ******************************************************************/ -#define BAND_COL_ELEM(col_j,i,j) (col_j[i-j]) +#define PVODE_BAND_COL_ELEM(col_j,i,j) (col_j[i-j]) /* Functions that use the BandMat representation for a band matrix */ diff --git a/externalpackages/PVODE/precon/pvbbdpre.cpp b/externalpackages/PVODE/precon/pvbbdpre.cpp index 3a1181dcf1..b5e35b8e35 100644 --- a/externalpackages/PVODE/precon/pvbbdpre.cpp +++ b/externalpackages/PVODE/precon/pvbbdpre.cpp @@ -364,13 +364,13 @@ static void PVBBDDQJac(integer Nlocal, integer mudq, integer mldq, /* Restore ytemp, then form and load difference quotients */ for (j=group-1; j < Nlocal; j+=width) { ytemp_data[j] = y_data[j]; - col_j = BAND_COL(J,j); + col_j = PVODE_BAND_COL(J,j); inc = MAX(rely*ABS(y_data[j]), minInc/ewt_data[j]); inc_inv = ONE/inc; i1 = MAX(0, j-mukeep); i2 = MIN(j+mlkeep, Nlocal-1); for (i=i1; i <= i2; i++) - BAND_COL_ELEM(col_j,i,j) = + PVODE_BAND_COL_ELEM(col_j,i,j) = inc_inv * (gtemp_data[i] - gy_data[i]); } } From 095c980f74d3d916ca6fbbfab6ed178a95c09a10 Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 4 Dec 2024 11:37:22 +0100 Subject: [PATCH 115/242] Allow setter to be chained --- tools/pylib/_boutpp_build/boutpp.pyx.jinja | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/pylib/_boutpp_build/boutpp.pyx.jinja b/tools/pylib/_boutpp_build/boutpp.pyx.jinja index 9aedbb291a..1972a4e530 100644 --- a/tools/pylib/_boutpp_build/boutpp.pyx.jinja +++ b/tools/pylib/_boutpp_build/boutpp.pyx.jinja @@ -268,6 +268,7 @@ cdef class {{ field.field_type }}: dims_in = self._checkDims(dims, data.shape) cdef np.ndarray[double, mode="c", ndim={{ field.ndims }}] data_ = np.ascontiguousarray(data) c_set_all(self.cobj,&data_[{{ zeros }}]) + return self def get(self): """ From 6c674fed1173dbf96423f1ae27e3bb499961360d Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 4 Dec 2024 11:39:13 +0100 Subject: [PATCH 116/242] Expose more arguments of Laplacian --- tools/pylib/_boutpp_build/boutcpp.pxd.jinja | 4 +--- tools/pylib/_boutpp_build/boutpp.pyx.jinja | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/tools/pylib/_boutpp_build/boutcpp.pxd.jinja b/tools/pylib/_boutpp_build/boutcpp.pxd.jinja index 8f838b864c..4324fe0c03 100644 --- a/tools/pylib/_boutpp_build/boutcpp.pxd.jinja +++ b/tools/pylib/_boutpp_build/boutcpp.pxd.jinja @@ -90,9 +90,7 @@ cdef extern from "bout/fieldgroup.hxx": cdef extern from "bout/invert_laplace.hxx": cppclass Laplacian: @staticmethod - unique_ptr[Laplacian] create() - @staticmethod - unique_ptr[Laplacian] create(Options *) + unique_ptr[Laplacian] create(Options*, benum.CELL_LOC, Mesh*, Solver*) Field3D solve(Field3D,Field3D) void setCoefA(Field3D) void setCoefC(Field3D) diff --git a/tools/pylib/_boutpp_build/boutpp.pyx.jinja b/tools/pylib/_boutpp_build/boutpp.pyx.jinja index 1972a4e530..00ff75eb09 100644 --- a/tools/pylib/_boutpp_build/boutpp.pyx.jinja +++ b/tools/pylib/_boutpp_build/boutpp.pyx.jinja @@ -868,7 +868,7 @@ cdef class Laplacian: """ cdef unique_ptr[c.Laplacian] cobj cdef c.bool isSelfOwned - def __init__(self,section=None): + def __init__(self, section=None, loc="CELL_CENTRE", mesh=None): """ Initialiase a Laplacian solver @@ -878,11 +878,22 @@ cdef class Laplacian: The section from the Option tree to take the options from """ checkInit() + cdef c.Options* copt = NULL if section: - self.cobj = c.Laplacian.create((section).cobj) - else: - self.cobj = c.Laplacian.create(NULL) + if isinstance(section, str): + section = Options.root(section) + copt = (section).cobj + cdef benum.CELL_LOC cloc = benum.resolve_cell_loc(loc) + cdef c.Mesh* cmesh = NULL + if mesh: + cmesh = (mesh).cobj + # Solver is not exposed yet + # cdef c.Solver* csolver = NULL + # if solver: + # csolver = (solver).cobj + self.cobj = c.Laplacian.create(copt, cloc, cmesh, NULL) self.isSelfOwned = True + def solve(self,Field3D x, Field3D guess): """ Calculate the Laplacian inversion From 6f7eff8322d2a62e85bb6e8d513484b5ad28da8b Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 4 Dec 2024 11:40:38 +0100 Subject: [PATCH 117/242] Expose `Mesh::get` for Field3D --- tools/pylib/_boutpp_build/boutcpp.pxd.jinja | 1 + tools/pylib/_boutpp_build/boutpp.pyx.jinja | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/tools/pylib/_boutpp_build/boutcpp.pxd.jinja b/tools/pylib/_boutpp_build/boutcpp.pxd.jinja index 4324fe0c03..659ad8ff6d 100644 --- a/tools/pylib/_boutpp_build/boutcpp.pxd.jinja +++ b/tools/pylib/_boutpp_build/boutcpp.pxd.jinja @@ -63,6 +63,7 @@ cdef extern from "bout/mesh.hxx": int LocalNx int LocalNy Coordinates * getCoordinates() + int get(Field3D, const string) cdef extern from "bout/coordinates.hxx": cppclass Coordinates: diff --git a/tools/pylib/_boutpp_build/boutpp.pyx.jinja b/tools/pylib/_boutpp_build/boutpp.pyx.jinja index 00ff75eb09..a5a1609454 100644 --- a/tools/pylib/_boutpp_build/boutpp.pyx.jinja +++ b/tools/pylib/_boutpp_build/boutpp.pyx.jinja @@ -742,6 +742,17 @@ cdef class Mesh: msh.isSelfOwned = False return msh + def get(self, name): + """ + Read a variable from the input source + + Currently only supports reading a Field3D + """ + checkInit() + cdef Field3D f3d = Field3D.fromMesh(self) + self.cobj.get(f3d.cobj[0], name.encode()) + return f3d + def __dealloc__(self): self._boutpp_dealloc() From 6934acbf2e62b9d8c08165cd2ebadad9f02cf42a Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 4 Dec 2024 11:42:18 +0100 Subject: [PATCH 118/242] Avoid using kwargs, to avoid hiding typos --- tools/pylib/_boutpp_build/boutpp.pyx.jinja | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tools/pylib/_boutpp_build/boutpp.pyx.jinja b/tools/pylib/_boutpp_build/boutpp.pyx.jinja index a5a1609454..57bf6dcece 100644 --- a/tools/pylib/_boutpp_build/boutpp.pyx.jinja +++ b/tools/pylib/_boutpp_build/boutpp.pyx.jinja @@ -924,19 +924,20 @@ cdef class Laplacian: """ return f3dFromObj(deref(self.cobj).solve(x.cobj[0],guess.cobj[0])) - def setCoefs(self, **kwargs): +{% set coeffs="A C C1 C2 D Ex Ez".split() %} + def setCoefs(self, *{% for coeff in coeffs %}, {{coeff}}=None{% endfor %}): """ Set the coefficients for the Laplacian solver. The coefficients A, C, C1, C2, D, Ex and Ez can be passed as keyword arguments """ {% set coeffs="A C C1 C2 D Ex Ez".split() %} {% for coeff in coeffs %} - if "{{ coeff }}" in kwargs: - self.setCoef{{ coeff}}(kwargs["{{ coeff }}"]) + if {{ coeff }} is not None: + self.setCoef{{ coeff}}({{ coeff }}) {% endfor %} {% for coeff in coeffs %} - def setCoef{{ coeff }}(self,Field3D {{ coeff }}): + def setCoef{{ coeff }}(self, Field3D {{ coeff }}): """ Set the "{{ coeff }}" coefficient of the Laplacian solver From 50e01aec4df6e9e3f501b2d6ef4efb3d8b0edd81 Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 4 Dec 2024 11:42:41 +0100 Subject: [PATCH 119/242] Fix deallocation of Laplacian --- tools/pylib/_boutpp_build/boutpp.pyx.jinja | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/pylib/_boutpp_build/boutpp.pyx.jinja b/tools/pylib/_boutpp_build/boutpp.pyx.jinja index 57bf6dcece..7b07cd8296 100644 --- a/tools/pylib/_boutpp_build/boutpp.pyx.jinja +++ b/tools/pylib/_boutpp_build/boutpp.pyx.jinja @@ -949,6 +949,13 @@ cdef class Laplacian: deref(self.cobj).setCoef{{ coeff }}({{ coeff }}.cobj[0]) {% endfor %} + def __dealloc__(self): + self._boutpp_dealloc() + + def _boutpp_dealloc(self): + if self.cobj and self.isSelfOwned: + self.cobj.release() + cdef class FieldFactory: cdef c.FieldFactory * cobj def __init__(self): From 9f3fb54860e585082dbbab52126000c642007d20 Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 4 Dec 2024 11:43:25 +0100 Subject: [PATCH 120/242] Set mesh for Fields in Laplacian --- src/invert/laplace/impls/petsc/petsc_laplace.cxx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/invert/laplace/impls/petsc/petsc_laplace.cxx b/src/invert/laplace/impls/petsc/petsc_laplace.cxx index f06f4c7de6..40efdb4655 100644 --- a/src/invert/laplace/impls/petsc/petsc_laplace.cxx +++ b/src/invert/laplace/impls/petsc/petsc_laplace.cxx @@ -63,8 +63,9 @@ static PetscErrorCode laplacePCapply(PC pc, Vec x, Vec y) { LaplacePetsc::LaplacePetsc(Options* opt, const CELL_LOC loc, Mesh* mesh_in, Solver* UNUSED(solver)) - : Laplacian(opt, loc, mesh_in), A(0.0), C1(1.0), C2(1.0), D(1.0), Ex(0.0), Ez(0.0), - issetD(false), issetC(false), issetE(false), + : Laplacian(opt, loc, mesh_in), A(0.0, mesh_in), C1(1.0, mesh_in), C2(1.0, mesh_in), + D(1.0, mesh_in), Ex(0.0, mesh_in), Ez(0.0, mesh_in), issetD(false), issetC(false), + issetE(false), sol(mesh_in), lib(opt == nullptr ? &(Options::root()["laplace"]) : opt) { A.setLocation(location); C1.setLocation(location); From 1f93c7356be3aa5170e3b76263e13de8e2db633b Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 4 Dec 2024 11:43:51 +0100 Subject: [PATCH 121/242] Fix some unused variable warning for 3D metrics --- src/mesh/boundary_standard.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mesh/boundary_standard.cxx b/src/mesh/boundary_standard.cxx index c8b3269198..367f6b7d54 100644 --- a/src/mesh/boundary_standard.cxx +++ b/src/mesh/boundary_standard.cxx @@ -1593,7 +1593,7 @@ BoundaryOp* BoundaryNeumann_NonOrthogonal::clone(BoundaryRegion* region, return new BoundaryNeumann_NonOrthogonal(region); } -void BoundaryNeumann_NonOrthogonal::apply(Field2D& f) { +void BoundaryNeumann_NonOrthogonal::apply(Field2D& [[maybe_unused]] f) { #if not(BOUT_USE_METRIC_3D) Mesh* mesh = bndry->localmesh; ASSERT1(mesh == f.getMesh()); @@ -1728,7 +1728,7 @@ void BoundaryNeumann_NonOrthogonal::apply(Field3D& f) { void BoundaryNeumann::apply(Field2D & f) { BoundaryNeumann::apply(f, 0.); } - void BoundaryNeumann::apply(Field2D & f, BoutReal t) { + void BoundaryNeumann::apply(Field2D& [[maybe_unused]] f, BoutReal t) { // Set (at 2nd order / 3rd order) the value at the mid-point between // the guard cell and the grid cell to be val // N.B. First guard cells (closest to the grid) is 2nd order, while From ec5fe922a7c3f8c130f5bb8738609d71d2c8968a Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Wed, 30 Oct 2024 13:58:31 +0000 Subject: [PATCH 122/242] Move `invert3x3` out of general purpose `utils.hxx` header Only used in `Coordinates`, so make private implementation detail --- CMakeLists.txt | 1 + include/bout/utils.hxx | 53 ------------------ src/mesh/coordinates.cxx | 1 + src/mesh/invert3x3.hxx | 81 +++++++++++++++++++++++++++ tests/unit/CMakeLists.txt | 1 + tests/unit/mesh/test_invert3x3.cxx | 89 ++++++++++++++++++++++++++++++ tests/unit/sys/test_utils.cxx | 85 ---------------------------- 7 files changed, 173 insertions(+), 138 deletions(-) create mode 100644 src/mesh/invert3x3.hxx create mode 100644 tests/unit/mesh/test_invert3x3.cxx diff --git a/CMakeLists.txt b/CMakeLists.txt index 257308d578..7df044c867 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -270,6 +270,7 @@ set(BOUT_SOURCES ./src/mesh/interpolation/interpolation_z.cxx ./src/mesh/interpolation/lagrange_4pt_xz.cxx ./src/mesh/interpolation/monotonic_hermite_spline_xz.cxx + ./src/mesh/invert3x3.hxx ./src/mesh/mesh.cxx ./src/mesh/parallel/fci.cxx ./src/mesh/parallel/fci.hxx diff --git a/include/bout/utils.hxx b/include/bout/utils.hxx index f4a41c1a20..c25a8f0ec8 100644 --- a/include/bout/utils.hxx +++ b/include/bout/utils.hxx @@ -410,59 +410,6 @@ bool operator==(const Tensor& lhs, const Tensor& rhs) { return std::equal(lhs.begin(), lhs.end(), rhs.begin()); } -/************************************************************************** - * Matrix routines - **************************************************************************/ -/// Explicit inversion of a 3x3 matrix \p a -/// -/// The input \p small determines how small the determinant must be for -/// us to throw due to the matrix being singular (ill conditioned); -/// If small is less than zero then instead of throwing we return 1. -/// This is ugly but can be used to support some use cases. -template -int invert3x3(Matrix& a, BoutReal small = 1.0e-15) { - TRACE("invert3x3"); - - // Calculate the first co-factors - T A = a(1, 1) * a(2, 2) - a(1, 2) * a(2, 1); - T B = a(1, 2) * a(2, 0) - a(1, 0) * a(2, 2); - T C = a(1, 0) * a(2, 1) - a(1, 1) * a(2, 0); - - // Calculate the determinant - T det = a(0, 0) * A + a(0, 1) * B + a(0, 2) * C; - - if (std::abs(det) < std::abs(small)) { - if (small >= 0) { - throw BoutException("Determinant of matrix < {:e} --> Poorly conditioned", small); - } else { - return 1; - } - } - - // Calculate the rest of the co-factors - T D = a(0, 2) * a(2, 1) - a(0, 1) * a(2, 2); - T E = a(0, 0) * a(2, 2) - a(0, 2) * a(2, 0); - T F = a(0, 1) * a(2, 0) - a(0, 0) * a(2, 1); - T G = a(0, 1) * a(1, 2) - a(0, 2) * a(1, 1); - T H = a(0, 2) * a(1, 0) - a(0, 0) * a(1, 2); - T I = a(0, 0) * a(1, 1) - a(0, 1) * a(1, 0); - - // Now construct the output, overwrites input - T detinv = 1.0 / det; - - a(0, 0) = A * detinv; - a(0, 1) = D * detinv; - a(0, 2) = G * detinv; - a(1, 0) = B * detinv; - a(1, 1) = E * detinv; - a(1, 2) = H * detinv; - a(2, 0) = C * detinv; - a(2, 1) = F * detinv; - a(2, 2) = I * detinv; - - return 0; -} - /*! * Get Random number between 0 and 1 */ diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index 41c2a0bc21..eff5672ce6 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -20,6 +20,7 @@ #include "parallel/fci.hxx" #include "parallel/shiftedmetricinterp.hxx" +#include "invert3x3.hxx" // use anonymous namespace so this utility function is not available outside this file namespace { diff --git a/src/mesh/invert3x3.hxx b/src/mesh/invert3x3.hxx new file mode 100644 index 0000000000..dce208338d --- /dev/null +++ b/src/mesh/invert3x3.hxx @@ -0,0 +1,81 @@ +/*!************************************************************************* + * \file invert3x3.hxx + * + * A mix of short utilities for memory management, strings, and some + * simple but common calculations + * + ************************************************************************** + * Copyright 2010-2024 B.D.Dudson, BOUT++ Team + * + * Contact: Ben Dudson, dudson2@llnl.gov + * + * This file is part of BOUT++. + * + * BOUT++ is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * BOUT++ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with BOUT++. If not, see . + * + **************************************************************************/ + +#pragma once + +#include + +/// Explicit inversion of a 3x3 matrix \p a +/// +/// The input \p small determines how small the determinant must be for +/// us to throw due to the matrix being singular (ill conditioned); +/// If small is less than zero then instead of throwing we return 1. +/// This is ugly but can be used to support some use cases. +template +int invert3x3(Matrix& a, BoutReal small = 1.0e-15) { + TRACE("invert3x3"); + + // Calculate the first co-factors + T A = a(1, 1) * a(2, 2) - a(1, 2) * a(2, 1); + T B = a(1, 2) * a(2, 0) - a(1, 0) * a(2, 2); + T C = a(1, 0) * a(2, 1) - a(1, 1) * a(2, 0); + + // Calculate the determinant + T det = a(0, 0) * A + a(0, 1) * B + a(0, 2) * C; + + if (std::abs(det) < std::abs(small)) { + if (small >= 0) { + throw BoutException("Determinant of matrix < {:e} --> Poorly conditioned", small); + } else { + return 1; + } + } + + // Calculate the rest of the co-factors + T D = a(0, 2) * a(2, 1) - a(0, 1) * a(2, 2); + T E = a(0, 0) * a(2, 2) - a(0, 2) * a(2, 0); + T F = a(0, 1) * a(2, 0) - a(0, 0) * a(2, 1); + T G = a(0, 1) * a(1, 2) - a(0, 2) * a(1, 1); + T H = a(0, 2) * a(1, 0) - a(0, 0) * a(1, 2); + T I = a(0, 0) * a(1, 1) - a(0, 1) * a(1, 0); + + // Now construct the output, overwrites input + T detinv = 1.0 / det; + + a(0, 0) = A * detinv; + a(0, 1) = D * detinv; + a(0, 2) = G * detinv; + a(1, 0) = B * detinv; + a(1, 1) = E * detinv; + a(1, 2) = H * detinv; + a(2, 0) = C * detinv; + a(2, 1) = F * detinv; + a(2, 2) = I * detinv; + + return 0; +} diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 44f1fe5b22..47253c508f 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -69,6 +69,7 @@ set(serial_tests_source ./mesh/test_coordinates.cxx ./mesh/test_coordinates_accessor.cxx ./mesh/test_interpolation.cxx + ./mesh/test_invert3x3.cxx ./mesh/test_mesh.cxx ./mesh/test_paralleltransform.cxx ./solver/test_fakesolver.cxx diff --git a/tests/unit/mesh/test_invert3x3.cxx b/tests/unit/mesh/test_invert3x3.cxx new file mode 100644 index 0000000000..02beeec644 --- /dev/null +++ b/tests/unit/mesh/test_invert3x3.cxx @@ -0,0 +1,89 @@ +#include "../../src/mesh/invert3x3.hxx" + +#include "gtest/gtest.h" + +TEST(Invert3x3Test, Identity) { + Matrix input(3, 3); + input = 0; + for (int i = 0; i < 3; i++) { + input(i, i) = 1.0; + } + auto expected = input; + invert3x3(input); + + for (int j = 0; j < 3; j++) { + for (int i = 0; i < 3; i++) { + EXPECT_EQ(input(i, j), expected(i, j)); + } + } +} + +TEST(Invert3x3Test, InvertTwice) { + std::vector rawDataMat = {0.05567105, 0.92458227, 0.19954631, + 0.28581972, 0.54009039, 0.13234403, + 0.8841194, 0.161224, 0.74853209}; + std::vector rawDataInv = {-2.48021781, 4.27410022, -0.09449605, + 0.6278449, 0.87275842, -0.32168092, + 2.79424897, -5.23628123, 1.51684677}; + + Matrix input(3, 3); + Matrix expected(3, 3); + + int counter = 0; + for (int j = 0; j < 3; j++) { + for (int i = 0; i < 3; i++) { + input(i, j) = rawDataMat[counter]; + expected(i, j) = rawDataInv[counter]; + counter++; + } + } + + // Invert twice to check if we get back to where we started + invert3x3(input); + + for (int j = 0; j < 3; j++) { + for (int i = 0; i < 3; i++) { + // Note we only check to single tolerance here + EXPECT_FLOAT_EQ(input(i, j), expected(i, j)); + } + } +} + +TEST(Invert3x3Test, Singular) { + Matrix input(3, 3); + input = 0; + EXPECT_THROW(invert3x3(input), BoutException); +} + +TEST(Invert3x3Test, BadCondition) { + Matrix input(3, 3); + + // Default small + input = 0.; + input(0, 0) = 1.0e-16; + input(1, 1) = 1.0; + input(2, 2) = 1.0; + EXPECT_THROW(invert3x3(input), BoutException); + + // Default small -- not quite bad enough condition + input = 0.; + input(0, 0) = 1.0e-12; + input(1, 1) = 1.0; + input(2, 2) = 1.0; + EXPECT_NO_THROW(invert3x3(input)); + + // Non-default small + input = 0.; + input(0, 0) = 1.0e-12; + input(1, 1) = 1.0; + input(2, 2) = 1.0; + EXPECT_THROW(invert3x3(input, 1.0e-10), BoutException); + + // Non-default small + input = 0.; + input(0, 0) = 1.0e-12; + input(1, 1) = 1.0; + input(2, 2) = 1.0; + EXPECT_NO_THROW(invert3x3(input, -1.0e-10)); +} + diff --git a/tests/unit/sys/test_utils.cxx b/tests/unit/sys/test_utils.cxx index 747257bafc..6d84813c48 100644 --- a/tests/unit/sys/test_utils.cxx +++ b/tests/unit/sys/test_utils.cxx @@ -386,91 +386,6 @@ TEST(TensorTest, ConstGetData) { std::all_of(std::begin(tensor), std::end(tensor), [](int a) { return a == 3; })); } -TEST(Invert3x3Test, Identity) { - Matrix input(3, 3); - input = 0; - for (int i = 0; i < 3; i++) { - input(i, i) = 1.0; - } - auto expected = input; - invert3x3(input); - - for (int j = 0; j < 3; j++) { - for (int i = 0; i < 3; i++) { - EXPECT_EQ(input(i, j), expected(i, j)); - } - } -} - -TEST(Invert3x3Test, InvertTwice) { - std::vector rawDataMat = {0.05567105, 0.92458227, 0.19954631, - 0.28581972, 0.54009039, 0.13234403, - 0.8841194, 0.161224, 0.74853209}; - std::vector rawDataInv = {-2.48021781, 4.27410022, -0.09449605, - 0.6278449, 0.87275842, -0.32168092, - 2.79424897, -5.23628123, 1.51684677}; - - Matrix input(3, 3); - Matrix expected(3, 3); - - int counter = 0; - for (int j = 0; j < 3; j++) { - for (int i = 0; i < 3; i++) { - input(i, j) = rawDataMat[counter]; - expected(i, j) = rawDataInv[counter]; - counter++; - } - } - - // Invert twice to check if we get back to where we started - invert3x3(input); - - for (int j = 0; j < 3; j++) { - for (int i = 0; i < 3; i++) { - // Note we only check to single tolerance here - EXPECT_FLOAT_EQ(input(i, j), expected(i, j)); - } - } -} - -TEST(Invert3x3Test, Singular) { - Matrix input(3, 3); - input = 0; - EXPECT_THROW(invert3x3(input), BoutException); -} - -TEST(Invert3x3Test, BadCondition) { - Matrix input(3, 3); - - // Default small - input = 0.; - input(0, 0) = 1.0e-16; - input(1, 1) = 1.0; - input(2, 2) = 1.0; - EXPECT_THROW(invert3x3(input), BoutException); - - // Default small -- not quite bad enough condition - input = 0.; - input(0, 0) = 1.0e-12; - input(1, 1) = 1.0; - input(2, 2) = 1.0; - EXPECT_NO_THROW(invert3x3(input)); - - // Non-default small - input = 0.; - input(0, 0) = 1.0e-12; - input(1, 1) = 1.0; - input(2, 2) = 1.0; - EXPECT_THROW(invert3x3(input, 1.0e-10), BoutException); - - // Non-default small - input = 0.; - input(0, 0) = 1.0e-12; - input(1, 1) = 1.0; - input(2, 2) = 1.0; - EXPECT_NO_THROW(invert3x3(input, -1.0e-10)); -} - TEST(NumberUtilitiesTest, SquareInt) { EXPECT_EQ(4, SQ(2)); EXPECT_EQ(4, SQ(-2)); From bd2f36d1e1ca4ee8beacf47b721394c42c322918 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Wed, 30 Oct 2024 14:20:14 +0000 Subject: [PATCH 123/242] Return `bool` instead of `int` from `invert3x3` --- src/mesh/coordinates.cxx | 4 ++-- src/mesh/invert3x3.hxx | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index eff5672ce6..1907c311a7 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -1264,7 +1264,7 @@ int Coordinates::calcCovariant(const std::string& region) { a(1, 2) = a(2, 1) = g23[i]; a(0, 2) = a(2, 0) = g13[i]; - if (invert3x3(a)) { + if (!invert3x3(a)) { output_error.write("\tERROR: metric tensor is singular at ({:d}, {:d})\n", i.x(), i.y()); return 1; @@ -1320,7 +1320,7 @@ int Coordinates::calcContravariant(const std::string& region) { a(1, 2) = a(2, 1) = g_23[i]; a(0, 2) = a(2, 0) = g_13[i]; - if (invert3x3(a)) { + if (!invert3x3(a)) { output_error.write("\tERROR: metric tensor is singular at ({:d}, {:d})\n", i.x(), i.y()); return 1; diff --git a/src/mesh/invert3x3.hxx b/src/mesh/invert3x3.hxx index dce208338d..84278e2e43 100644 --- a/src/mesh/invert3x3.hxx +++ b/src/mesh/invert3x3.hxx @@ -34,10 +34,10 @@ /// /// The input \p small determines how small the determinant must be for /// us to throw due to the matrix being singular (ill conditioned); -/// If small is less than zero then instead of throwing we return 1. +/// If small is less than zero then instead of throwing we return false. /// This is ugly but can be used to support some use cases. template -int invert3x3(Matrix& a, BoutReal small = 1.0e-15) { +bool invert3x3(Matrix& a, T small = 1.0e-15) { TRACE("invert3x3"); // Calculate the first co-factors @@ -51,9 +51,8 @@ int invert3x3(Matrix& a, BoutReal small = 1.0e-15) { if (std::abs(det) < std::abs(small)) { if (small >= 0) { throw BoutException("Determinant of matrix < {:e} --> Poorly conditioned", small); - } else { - return 1; } + return false; } // Calculate the rest of the co-factors @@ -77,5 +76,5 @@ int invert3x3(Matrix& a, BoutReal small = 1.0e-15) { a(2, 1) = F * detinv; a(2, 2) = I * detinv; - return 0; + return true; } From 3ceef07fd1edef4e6d7f282637433e5dfd8b81a7 Mon Sep 17 00:00:00 2001 From: ZedThree Date: Wed, 30 Oct 2024 14:21:46 +0000 Subject: [PATCH 124/242] Apply clang-format changes --- src/mesh/coordinates.cxx | 2 +- tests/unit/mesh/test_invert3x3.cxx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index 1907c311a7..93d748a61c 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -18,9 +18,9 @@ #include +#include "invert3x3.hxx" #include "parallel/fci.hxx" #include "parallel/shiftedmetricinterp.hxx" -#include "invert3x3.hxx" // use anonymous namespace so this utility function is not available outside this file namespace { diff --git a/tests/unit/mesh/test_invert3x3.cxx b/tests/unit/mesh/test_invert3x3.cxx index 02beeec644..77b08354cc 100644 --- a/tests/unit/mesh/test_invert3x3.cxx +++ b/tests/unit/mesh/test_invert3x3.cxx @@ -86,4 +86,3 @@ TEST(Invert3x3Test, BadCondition) { input(2, 2) = 1.0; EXPECT_NO_THROW(invert3x3(input, -1.0e-10)); } - From c42dc24910ef7a4131af48f1e94398c0de1aaab5 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Thu, 7 Nov 2024 16:15:28 +0000 Subject: [PATCH 125/242] Return `std::optional` from `invert3x3` Allows throwing more specific error in coordinates --- src/mesh/coordinates.cxx | 14 +++++----- src/mesh/invert3x3.hxx | 43 ++++++++++++++---------------- tests/unit/mesh/test_invert3x3.cxx | 28 +++++-------------- 3 files changed, 35 insertions(+), 50 deletions(-) diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index 93d748a61c..2a94d55d5e 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -1264,9 +1264,10 @@ int Coordinates::calcCovariant(const std::string& region) { a(1, 2) = a(2, 1) = g23[i]; a(0, 2) = a(2, 0) = g13[i]; - if (!invert3x3(a)) { - output_error.write("\tERROR: metric tensor is singular at ({:d}, {:d})\n", i.x(), - i.y()); + if (const auto det = bout::invert3x3(a); det.has_value()) { + output_error.write( + "\tERROR: metric tensor is singular at ({:d}, {:d}), determinant: {:d}\n", + i.x(), i.y(), det.value()); return 1; } @@ -1320,9 +1321,10 @@ int Coordinates::calcContravariant(const std::string& region) { a(1, 2) = a(2, 1) = g_23[i]; a(0, 2) = a(2, 0) = g_13[i]; - if (!invert3x3(a)) { - output_error.write("\tERROR: metric tensor is singular at ({:d}, {:d})\n", i.x(), - i.y()); + if (const auto det = bout::invert3x3(a); det.has_value()) { + output_error.write( + "\tERROR: metric tensor is singular at ({:d}, {:d}), determinant: {:d}\n", + i.x(), i.y(), det.value()); return 1; } diff --git a/src/mesh/invert3x3.hxx b/src/mesh/invert3x3.hxx index 84278e2e43..9e635d8150 100644 --- a/src/mesh/invert3x3.hxx +++ b/src/mesh/invert3x3.hxx @@ -29,42 +29,38 @@ #pragma once #include +#include /// Explicit inversion of a 3x3 matrix \p a /// -/// The input \p small determines how small the determinant must be for -/// us to throw due to the matrix being singular (ill conditioned); -/// If small is less than zero then instead of throwing we return false. -/// This is ugly but can be used to support some use cases. -template -bool invert3x3(Matrix& a, T small = 1.0e-15) { +/// If the matrix is singular (ill conditioned), the determinant is +/// return. Otherwise, an empty `std::optional` is return +namespace bout { +inline std::optional invert3x3(Matrix& a) { TRACE("invert3x3"); // Calculate the first co-factors - T A = a(1, 1) * a(2, 2) - a(1, 2) * a(2, 1); - T B = a(1, 2) * a(2, 0) - a(1, 0) * a(2, 2); - T C = a(1, 0) * a(2, 1) - a(1, 1) * a(2, 0); + BoutReal A = a(1, 1) * a(2, 2) - a(1, 2) * a(2, 1); + BoutReal B = a(1, 2) * a(2, 0) - a(1, 0) * a(2, 2); + BoutReal C = a(1, 0) * a(2, 1) - a(1, 1) * a(2, 0); // Calculate the determinant - T det = a(0, 0) * A + a(0, 1) * B + a(0, 2) * C; - + const BoutReal det = a(0, 0) * A + a(0, 1) * B + a(0, 2) * C; + constexpr BoutReal small = 1.0e-15; if (std::abs(det) < std::abs(small)) { - if (small >= 0) { - throw BoutException("Determinant of matrix < {:e} --> Poorly conditioned", small); - } - return false; + return std::optional{det}; } // Calculate the rest of the co-factors - T D = a(0, 2) * a(2, 1) - a(0, 1) * a(2, 2); - T E = a(0, 0) * a(2, 2) - a(0, 2) * a(2, 0); - T F = a(0, 1) * a(2, 0) - a(0, 0) * a(2, 1); - T G = a(0, 1) * a(1, 2) - a(0, 2) * a(1, 1); - T H = a(0, 2) * a(1, 0) - a(0, 0) * a(1, 2); - T I = a(0, 0) * a(1, 1) - a(0, 1) * a(1, 0); + BoutReal D = a(0, 2) * a(2, 1) - a(0, 1) * a(2, 2); + BoutReal E = a(0, 0) * a(2, 2) - a(0, 2) * a(2, 0); + BoutReal F = a(0, 1) * a(2, 0) - a(0, 0) * a(2, 1); + BoutReal G = a(0, 1) * a(1, 2) - a(0, 2) * a(1, 1); + BoutReal H = a(0, 2) * a(1, 0) - a(0, 0) * a(1, 2); + BoutReal I = a(0, 0) * a(1, 1) - a(0, 1) * a(1, 0); // Now construct the output, overwrites input - T detinv = 1.0 / det; + BoutReal detinv = 1.0 / det; a(0, 0) = A * detinv; a(0, 1) = D * detinv; @@ -76,5 +72,6 @@ bool invert3x3(Matrix& a, T small = 1.0e-15) { a(2, 1) = F * detinv; a(2, 2) = I * detinv; - return true; + return std::nullopt; } +} // namespace bout diff --git a/tests/unit/mesh/test_invert3x3.cxx b/tests/unit/mesh/test_invert3x3.cxx index 77b08354cc..3bc4ae69d8 100644 --- a/tests/unit/mesh/test_invert3x3.cxx +++ b/tests/unit/mesh/test_invert3x3.cxx @@ -9,7 +9,7 @@ TEST(Invert3x3Test, Identity) { input(i, i) = 1.0; } auto expected = input; - invert3x3(input); + bout::invert3x3(input); for (int j = 0; j < 3; j++) { for (int i = 0; i < 3; i++) { @@ -39,7 +39,7 @@ TEST(Invert3x3Test, InvertTwice) { } // Invert twice to check if we get back to where we started - invert3x3(input); + bout::invert3x3(input); for (int j = 0; j < 3; j++) { for (int i = 0; i < 3; i++) { @@ -52,37 +52,23 @@ TEST(Invert3x3Test, InvertTwice) { TEST(Invert3x3Test, Singular) { Matrix input(3, 3); input = 0; - EXPECT_THROW(invert3x3(input), BoutException); + auto result = bout::invert3x3(input); + EXPECT_TRUE(result.has_value()); } TEST(Invert3x3Test, BadCondition) { Matrix input(3, 3); - // Default small input = 0.; input(0, 0) = 1.0e-16; input(1, 1) = 1.0; input(2, 2) = 1.0; - EXPECT_THROW(invert3x3(input), BoutException); + EXPECT_TRUE(bout::invert3x3(input).has_value()); - // Default small -- not quite bad enough condition + // not quite bad enough condition input = 0.; input(0, 0) = 1.0e-12; input(1, 1) = 1.0; input(2, 2) = 1.0; - EXPECT_NO_THROW(invert3x3(input)); - - // Non-default small - input = 0.; - input(0, 0) = 1.0e-12; - input(1, 1) = 1.0; - input(2, 2) = 1.0; - EXPECT_THROW(invert3x3(input, 1.0e-10), BoutException); - - // Non-default small - input = 0.; - input(0, 0) = 1.0e-12; - input(1, 1) = 1.0; - input(2, 2) = 1.0; - EXPECT_NO_THROW(invert3x3(input, -1.0e-10)); + EXPECT_FALSE(bout::invert3x3(input).has_value()); } From 36a06f32e8bbc544acaefd09b174952fa3d0ca2b Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 26 Nov 2024 11:10:28 +0100 Subject: [PATCH 126/242] simplify return statement --- src/mesh/invert3x3.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/invert3x3.hxx b/src/mesh/invert3x3.hxx index 9e635d8150..9c6b614168 100644 --- a/src/mesh/invert3x3.hxx +++ b/src/mesh/invert3x3.hxx @@ -48,7 +48,7 @@ inline std::optional invert3x3(Matrix& a) { const BoutReal det = a(0, 0) * A + a(0, 1) * B + a(0, 2) * C; constexpr BoutReal small = 1.0e-15; if (std::abs(det) < std::abs(small)) { - return std::optional{det}; + return det; } // Calculate the rest of the co-factors From b4dd92faa294e586c4624060380649a9ab461350 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 26 Nov 2024 11:11:26 +0100 Subject: [PATCH 127/242] Use formatter for SpecificInd This works for 2D and 3D fields (and is also shorter code) --- src/mesh/coordinates.cxx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index 2a94d55d5e..aba401818d 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -1266,8 +1266,8 @@ int Coordinates::calcCovariant(const std::string& region) { if (const auto det = bout::invert3x3(a); det.has_value()) { output_error.write( - "\tERROR: metric tensor is singular at ({:d}, {:d}), determinant: {:d}\n", - i.x(), i.y(), det.value()); + "\tERROR: metric tensor is singular at {}, determinant: {:d}\n", + i, det.value()); return 1; } @@ -1323,8 +1323,8 @@ int Coordinates::calcContravariant(const std::string& region) { if (const auto det = bout::invert3x3(a); det.has_value()) { output_error.write( - "\tERROR: metric tensor is singular at ({:d}, {:d}), determinant: {:d}\n", - i.x(), i.y(), det.value()); + "\tERROR: metric tensor is singular at {}, determinant: {:d}\n", + i, det.value()); return 1; } From a7e783a497e4b41356bc6c2026c46a469a667e91 Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 4 Dec 2024 12:10:03 +0100 Subject: [PATCH 128/242] Apply clang-format changes --- externalpackages/PVODE/include/pvode/band.h | 12 +++--------- externalpackages/PVODE/precon/band.h | 12 +++--------- src/mesh/coordinates.cxx | 10 ++++------ 3 files changed, 10 insertions(+), 24 deletions(-) diff --git a/externalpackages/PVODE/include/pvode/band.h b/externalpackages/PVODE/include/pvode/band.h index d8eb2d92e9..49a98b63d6 100644 --- a/externalpackages/PVODE/include/pvode/band.h +++ b/externalpackages/PVODE/include/pvode/band.h @@ -57,7 +57,6 @@ namespace pvode { - /****************************************************************** * * * Type: BandMat * @@ -118,7 +117,6 @@ namespace pvode { * * ******************************************************************/ - typedef struct bandmat_type { integer size; integer mu, ml, smu; @@ -128,7 +126,6 @@ typedef struct bandmat_type { /* BandMat accessor macros */ - /****************************************************************** * * * Macro : PVODE_BAND_ELEM * @@ -141,8 +138,7 @@ typedef struct bandmat_type { * * ******************************************************************/ -#define PVODE_BAND_ELEM(A,i,j) ((A->data)[j][i-j+(A->smu)]) - +#define PVODE_BAND_ELEM(A, i, j) ((A->data)[j][i - j + (A->smu)]) /****************************************************************** * * @@ -157,8 +153,7 @@ typedef struct bandmat_type { * * ******************************************************************/ -#define PVODE_BAND_COL(A,j) (((A->data)[j])+(A->smu)) - +#define PVODE_BAND_COL(A, j) (((A->data)[j]) + (A->smu)) /****************************************************************** * * @@ -173,8 +168,7 @@ typedef struct bandmat_type { * * ******************************************************************/ -#define PVODE_BAND_COL_ELEM(col_j,i,j) (col_j[i-j]) - +#define PVODE_BAND_COL_ELEM(col_j, i, j) (col_j[i - j]) /* Functions that use the BandMat representation for a band matrix */ diff --git a/externalpackages/PVODE/precon/band.h b/externalpackages/PVODE/precon/band.h index d8eb2d92e9..49a98b63d6 100644 --- a/externalpackages/PVODE/precon/band.h +++ b/externalpackages/PVODE/precon/band.h @@ -57,7 +57,6 @@ namespace pvode { - /****************************************************************** * * * Type: BandMat * @@ -118,7 +117,6 @@ namespace pvode { * * ******************************************************************/ - typedef struct bandmat_type { integer size; integer mu, ml, smu; @@ -128,7 +126,6 @@ typedef struct bandmat_type { /* BandMat accessor macros */ - /****************************************************************** * * * Macro : PVODE_BAND_ELEM * @@ -141,8 +138,7 @@ typedef struct bandmat_type { * * ******************************************************************/ -#define PVODE_BAND_ELEM(A,i,j) ((A->data)[j][i-j+(A->smu)]) - +#define PVODE_BAND_ELEM(A, i, j) ((A->data)[j][i - j + (A->smu)]) /****************************************************************** * * @@ -157,8 +153,7 @@ typedef struct bandmat_type { * * ******************************************************************/ -#define PVODE_BAND_COL(A,j) (((A->data)[j])+(A->smu)) - +#define PVODE_BAND_COL(A, j) (((A->data)[j]) + (A->smu)) /****************************************************************** * * @@ -173,8 +168,7 @@ typedef struct bandmat_type { * * ******************************************************************/ -#define PVODE_BAND_COL_ELEM(col_j,i,j) (col_j[i-j]) - +#define PVODE_BAND_COL_ELEM(col_j, i, j) (col_j[i - j]) /* Functions that use the BandMat representation for a band matrix */ diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index aba401818d..102d8ba7b5 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -1265,9 +1265,8 @@ int Coordinates::calcCovariant(const std::string& region) { a(0, 2) = a(2, 0) = g13[i]; if (const auto det = bout::invert3x3(a); det.has_value()) { - output_error.write( - "\tERROR: metric tensor is singular at {}, determinant: {:d}\n", - i, det.value()); + output_error.write("\tERROR: metric tensor is singular at {}, determinant: {:d}\n", + i, det.value()); return 1; } @@ -1322,9 +1321,8 @@ int Coordinates::calcContravariant(const std::string& region) { a(0, 2) = a(2, 0) = g_13[i]; if (const auto det = bout::invert3x3(a); det.has_value()) { - output_error.write( - "\tERROR: metric tensor is singular at {}, determinant: {:d}\n", - i, det.value()); + output_error.write("\tERROR: metric tensor is singular at {}, determinant: {:d}\n", + i, det.value()); return 1; } From 33a72a5ea9af1fbc2085861385b4f6e9c2a2e887 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 26 Nov 2024 13:23:10 +0100 Subject: [PATCH 129/242] Add missing header to format SpecificInd --- src/mesh/coordinates.cxx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index 102d8ba7b5..4db84601af 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -15,6 +15,7 @@ #include #include #include +#include #include From 4f2da4dedbd159c0e4c549325d0b3c998c9f24c0 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 26 Nov 2024 14:10:43 +0100 Subject: [PATCH 130/242] Prefere const --- src/mesh/invert3x3.hxx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/mesh/invert3x3.hxx b/src/mesh/invert3x3.hxx index 9c6b614168..c011f55bf7 100644 --- a/src/mesh/invert3x3.hxx +++ b/src/mesh/invert3x3.hxx @@ -40,9 +40,9 @@ inline std::optional invert3x3(Matrix& a) { TRACE("invert3x3"); // Calculate the first co-factors - BoutReal A = a(1, 1) * a(2, 2) - a(1, 2) * a(2, 1); - BoutReal B = a(1, 2) * a(2, 0) - a(1, 0) * a(2, 2); - BoutReal C = a(1, 0) * a(2, 1) - a(1, 1) * a(2, 0); + const BoutReal A = a(1, 1) * a(2, 2) - a(1, 2) * a(2, 1); + const BoutReal B = a(1, 2) * a(2, 0) - a(1, 0) * a(2, 2); + const BoutReal C = a(1, 0) * a(2, 1) - a(1, 1) * a(2, 0); // Calculate the determinant const BoutReal det = a(0, 0) * A + a(0, 1) * B + a(0, 2) * C; @@ -52,15 +52,15 @@ inline std::optional invert3x3(Matrix& a) { } // Calculate the rest of the co-factors - BoutReal D = a(0, 2) * a(2, 1) - a(0, 1) * a(2, 2); - BoutReal E = a(0, 0) * a(2, 2) - a(0, 2) * a(2, 0); - BoutReal F = a(0, 1) * a(2, 0) - a(0, 0) * a(2, 1); - BoutReal G = a(0, 1) * a(1, 2) - a(0, 2) * a(1, 1); - BoutReal H = a(0, 2) * a(1, 0) - a(0, 0) * a(1, 2); - BoutReal I = a(0, 0) * a(1, 1) - a(0, 1) * a(1, 0); + const BoutReal D = a(0, 2) * a(2, 1) - a(0, 1) * a(2, 2); + const BoutReal E = a(0, 0) * a(2, 2) - a(0, 2) * a(2, 0); + const BoutReal F = a(0, 1) * a(2, 0) - a(0, 0) * a(2, 1); + const BoutReal G = a(0, 1) * a(1, 2) - a(0, 2) * a(1, 1); + const BoutReal H = a(0, 2) * a(1, 0) - a(0, 0) * a(1, 2); + const BoutReal I = a(0, 0) * a(1, 1) - a(0, 1) * a(1, 0); // Now construct the output, overwrites input - BoutReal detinv = 1.0 / det; + const BoutReal detinv = 1.0 / det; a(0, 0) = A * detinv; a(0, 1) = D * detinv; From a1f4b46aa771546ba49bf1592582c67e3057d998 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 26 Nov 2024 10:00:17 +0100 Subject: [PATCH 131/242] Use PEP 625 compatible archive name --- tools/pylib/_boutpp_build/backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/pylib/_boutpp_build/backend.py b/tools/pylib/_boutpp_build/backend.py index e89f37bb42..254fa7c2fd 100644 --- a/tools/pylib/_boutpp_build/backend.py +++ b/tools/pylib/_boutpp_build/backend.py @@ -198,7 +198,7 @@ def build_sdist(sdist_directory, config_settings=None): if k == "nightly": useLocalVersion = False pkgname = "boutpp-nightly" - prefix = f"{pkgname}-{getversion()}" + prefix = f"{pkgname.replace('-', '_')}-{getversion()}" fname = f"{prefix}.tar" run(f"git archive HEAD --prefix {prefix}/ -o {sdist_directory}/{fname}") _, tmp = tempfile.mkstemp(suffix=".tar") From b4bd5b89a078dcbcad2bbfa7ced681fb03bdc7c1 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 26 Nov 2024 09:37:59 +0100 Subject: [PATCH 132/242] CI: Increase check level for debug run --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f1fc19aeac..e493ca88ea 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -79,7 +79,7 @@ jobs: - name: "Debug, shared" os: ubuntu-latest - cmake_options: "-DCHECK=3 + cmake_options: "-DCHECK=4 -DCMAKE_BUILD_TYPE=Debug -DBOUT_ENABLE_SIGNAL=ON -DBOUT_ENABLE_TRACK=ON From 58638563986cd73acc0793b1c07d81e26ae80b53 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 26 Nov 2024 09:46:29 +0100 Subject: [PATCH 133/242] Fix unit test for CHECK=4 --- tests/unit/include/bout/test_stencil.cxx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/unit/include/bout/test_stencil.cxx b/tests/unit/include/bout/test_stencil.cxx index 033a865154..4a919614b4 100644 --- a/tests/unit/include/bout/test_stencil.cxx +++ b/tests/unit/include/bout/test_stencil.cxx @@ -12,10 +12,12 @@ class IndexOffsetStructTests : public ::testing::Test { public: IndexOffsetStructTests() { zero = T(0, std::is_same_v ? 1 : 5, std::is_same_v ? 1 : 7); + finite = T(239, std::is_same_v ? 1 : 5, std::is_same_v ? 1 : 12); } IndexOffset noOffset; T zero; + T finite; }; template @@ -144,15 +146,15 @@ TYPED_TEST(IndexOffsetStructTests, AddToIndex) { TYPED_TEST(IndexOffsetStructTests, SubtractFromIndex) { IndexOffset offset1 = {1, 0, 0}, offset2 = {0, 2, 0}, offset3 = {0, 0, 11}, offset4 = {2, 3, -2}; - EXPECT_EQ(this->zero - offset1, this->zero.xm()); + EXPECT_EQ(this->finite - offset1, this->finite.xm()); if constexpr (!std::is_same_v) { - EXPECT_EQ(this->zero - offset2, this->zero.ym(2)); + EXPECT_EQ(this->finite - offset2, this->finite.ym(2)); } if constexpr (!std::is_same_v) { - EXPECT_EQ(this->zero - offset3, this->zero.zm(11)); + EXPECT_EQ(this->finite - offset3, this->finite.zm(11)); } if constexpr (std::is_same_v) { - EXPECT_EQ(this->zero - offset4, this->zero.zp(2).xm(2).ym(3)); + EXPECT_EQ(this->finite - offset4, this->finite.zp(2).xm(2).ym(3)); } } From 57f0553d261215b75c9e2ed0f792dbb38ab83b98 Mon Sep 17 00:00:00 2001 From: dschwoerer Date: Tue, 26 Nov 2024 09:27:33 +0000 Subject: [PATCH 134/242] Apply clang-format changes --- tests/unit/include/bout/test_stencil.cxx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/include/bout/test_stencil.cxx b/tests/unit/include/bout/test_stencil.cxx index 4a919614b4..2d76a9a7f1 100644 --- a/tests/unit/include/bout/test_stencil.cxx +++ b/tests/unit/include/bout/test_stencil.cxx @@ -12,7 +12,8 @@ class IndexOffsetStructTests : public ::testing::Test { public: IndexOffsetStructTests() { zero = T(0, std::is_same_v ? 1 : 5, std::is_same_v ? 1 : 7); - finite = T(239, std::is_same_v ? 1 : 5, std::is_same_v ? 1 : 12); + finite = + T(239, std::is_same_v ? 1 : 5, std::is_same_v ? 1 : 12); } IndexOffset noOffset; From cc9d5cc33f71c0badce09514649c8bcb10ebbf5e Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 29 Nov 2024 14:18:40 +0100 Subject: [PATCH 135/242] Avoid using the wrong grid by accident I sometimes have `[mesh:file]` set in the input file, and specify `grid` on the command line, only to be confused why the wrong grid was picked. --- src/mesh/mesh.cxx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/mesh/mesh.cxx b/src/mesh/mesh.cxx index 6d7a5de512..6eb435a663 100644 --- a/src/mesh/mesh.cxx +++ b/src/mesh/mesh.cxx @@ -29,8 +29,16 @@ MeshFactory::ReturnType MeshFactory::create(const std::string& type, Options* op if (options->isSet("file") or Options::root().isSet("grid")) { // Specified mesh file - const auto grid_name = - (*options)["file"].withDefault(Options::root()["grid"].withDefault("")); + const auto grid_name1 = Options::root()["grid"].withDefault(""); + const auto grid_name = (*options)["file"].withDefault(grid_name1); + if (options->isSet("file") and Options::root().isSet("grid")) { + if (grid_name1 != grid_name) { + throw BoutException( + "Mismatch in grid names - specified `{:s}` in grid and `{:s} in " + "mesh:file!\nPlease specify only one name or ensure they are the same!", + grid_name1, grid_name); + } + } output << "\nGetting grid data from file " << grid_name << "\n"; // Create a grid file, using specified format if given From aef421523f760c15125688359fa29dc6587c87c9 Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 16 Dec 2024 15:14:06 +0100 Subject: [PATCH 136/242] Add some checks to petsc_laplace --- .../laplace/impls/petsc/petsc_laplace.cxx | 62 ++++++++++--------- .../laplace/impls/petsc/petsc_laplace.hxx | 2 +- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/src/invert/laplace/impls/petsc/petsc_laplace.cxx b/src/invert/laplace/impls/petsc/petsc_laplace.cxx index 40efdb4655..9a09b7edad 100644 --- a/src/invert/laplace/impls/petsc/petsc_laplace.cxx +++ b/src/invert/laplace/impls/petsc/petsc_laplace.cxx @@ -347,7 +347,7 @@ FieldPerp LaplacePetsc::solve(const FieldPerp& b, const FieldPerp& x0) { checkFlags(); #endif - int y = b.getIndex(); // Get the Y index + const int y = b.getIndex(); // Get the Y index sol.setIndex(y); // Initialize the solution field. sol = 0.; @@ -455,6 +455,7 @@ FieldPerp LaplacePetsc::solve(const FieldPerp& b, const FieldPerp& x0) { val = x0[x][z]; VecSetValues(xs, 1, &i, &val, INSERT_VALUES); + ASSERT3(i == getIndex(x, z)); i++; // Increment row in Petsc matrix } } @@ -472,11 +473,11 @@ FieldPerp LaplacePetsc::solve(const FieldPerp& b, const FieldPerp& x0) { // Set the matrix coefficients Coeffs(x, y, z, A1, A2, A3, A4, A5); - BoutReal dx = coords->dx(x, y, z); - BoutReal dx2 = SQ(dx); - BoutReal dz = coords->dz(x, y, z); - BoutReal dz2 = SQ(dz); - BoutReal dxdz = dx * dz; + const BoutReal dx = coords->dx(x, y, z); + const BoutReal dx2 = SQ(dx); + const BoutReal dz = coords->dz(x, y, z); + const BoutReal dz2 = SQ(dz); + const BoutReal dxdz = dx * dz; ASSERT3(finite(A1)); ASSERT3(finite(A2)); @@ -632,6 +633,7 @@ FieldPerp LaplacePetsc::solve(const FieldPerp& b, const FieldPerp& x0) { // Set Components of Trial Solution Vector val = x0[x][z]; VecSetValues(xs, 1, &i, &val, INSERT_VALUES); + ASSERT3(i == getIndex(x, z)); i++; } } @@ -715,7 +717,7 @@ FieldPerp LaplacePetsc::solve(const FieldPerp& b, const FieldPerp& x0) { // INSERT_VALUES replaces existing entries with new values val = x0[x][z]; VecSetValues(xs, 1, &i, &val, INSERT_VALUES); - + ASSERT3(i == getIndex(x, z)); i++; // Increment row in Petsc matrix } } @@ -871,24 +873,7 @@ FieldPerp LaplacePetsc::solve(const FieldPerp& b, const FieldPerp& x0) { return sol; } -/*! - * Sets the elements of the matrix A, which is used to solve the problem Ax=b. - * - * \param[in] - * i - * The row of the PETSc matrix - * \param[in] x Local x index of the mesh - * \param[in] z Local z index of the mesh - * \param[in] xshift The shift in rows from the index x - * \param[in] zshift The shift in columns from the index z - * \param[in] ele Value of the element - * \param[in] MatA The matrix A used in the inversion - * - * \param[out] MatA The matrix A used in the inversion - */ -void LaplacePetsc::Element(int i, int x, int z, int xshift, int zshift, PetscScalar ele, - Mat& MatA) { - +int LaplacePetsc::getIndex(const int x, const int z) { // Need to convert LOCAL x to GLOBAL x in order to correctly calculate // PETSC Matrix Index. int xoffset = Istart / meshz; @@ -897,22 +882,43 @@ void LaplacePetsc::Element(int i, int x, int z, int xshift, int zshift, PetscSca } // Calculate the row to be set - int row_new = x + xshift; // should never be out of range. + int row_new = x; // should never be out of range. if (!localmesh->firstX()) { row_new += (xoffset - localmesh->xstart); } // Calculate the column to be set - int col_new = z + zshift; + int col_new = z; if (col_new < 0) { col_new += meshz; } else if (col_new > meshz - 1) { col_new -= meshz; } + ASSERT3(0 <= col_new and col_new < meshz); // Convert to global indices - int index = (row_new * meshz) + col_new; + return (row_new * meshz) + col_new; +} + +/*! + * Sets the elements of the matrix A, which is used to solve the problem Ax=b. + * + * \param[in] + * i + * The row of the PETSc matrix + * \param[in] x Local x index of the mesh + * \param[in] z Local z index of the mesh + * \param[in] xshift The shift in rows from the index x + * \param[in] zshift The shift in columns from the index z + * \param[in] ele Value of the element + * \param[in] MatA The matrix A used in the inversion + * + * \param[out] MatA The matrix A used in the inversion + */ +void LaplacePetsc::Element(const int i, const int x, const int z, const int xshift, + const int zshift, const PetscScalar ele, Mat& MatA) { + const int index = getIndex(x + xshift, z + zshift); #if CHECK > 2 if (!finite(ele)) { throw BoutException("Non-finite element at x={:d}, z={:d}, row={:d}, col={:d}\n", x, diff --git a/src/invert/laplace/impls/petsc/petsc_laplace.hxx b/src/invert/laplace/impls/petsc/petsc_laplace.hxx index 1d56abd00b..3a616b4b09 100644 --- a/src/invert/laplace/impls/petsc/petsc_laplace.hxx +++ b/src/invert/laplace/impls/petsc/petsc_laplace.hxx @@ -202,7 +202,7 @@ private: void Element(int i, int x, int z, int xshift, int zshift, PetscScalar ele, Mat& MatA); void Coeffs(int x, int y, int z, BoutReal& A1, BoutReal& A2, BoutReal& A3, BoutReal& A4, BoutReal& A5); - + int getIndex(int x, int z); /* Ex and Ez * Additional 1st derivative terms to allow for solution field to be * components of a vector From bbc8e080828eefd4bff503b931091e7db6258f72 Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 16 Dec 2024 15:17:30 +0100 Subject: [PATCH 137/242] Add forward method to Laplacian inversion Mostly for debugging and testing purposes. Allows to implement a forward operator for the inversion. Here only the forward operator for the PETSc based inversion is implemented. --- include/bout/invert_laplace.hxx | 5 ++++ .../laplace/impls/petsc/petsc_laplace.cxx | 24 ++++++++++----- .../laplace/impls/petsc/petsc_laplace.hxx | 8 ++++- src/invert/laplace/invert_laplace.cxx | 30 +++++++++++++++++++ tools/pylib/_boutpp_build/boutcpp.pxd.jinja | 3 +- tools/pylib/_boutpp_build/boutpp.pyx.jinja | 20 ++++++++++++- 6 files changed, 80 insertions(+), 10 deletions(-) diff --git a/include/bout/invert_laplace.hxx b/include/bout/invert_laplace.hxx index 187056d115..ee0c4493a7 100644 --- a/include/bout/invert_laplace.hxx +++ b/include/bout/invert_laplace.hxx @@ -255,6 +255,11 @@ public: virtual Field3D solve(const Field3D& b, const Field3D& x0); virtual Field2D solve(const Field2D& b, const Field2D& x0); + /// Some implementations can also implement the forward operator for testing + /// and debugging + virtual FieldPerp forward(const FieldPerp& f); + virtual Field3D forward(const Field3D& f); + /// Coefficients in tridiagonal inversion void tridagCoefs(int jx, int jy, int jz, dcomplex& a, dcomplex& b, dcomplex& c, const Field2D* ccoef = nullptr, const Field2D* d = nullptr, diff --git a/src/invert/laplace/impls/petsc/petsc_laplace.cxx b/src/invert/laplace/impls/petsc/petsc_laplace.cxx index 9a09b7edad..d0d68bee52 100644 --- a/src/invert/laplace/impls/petsc/petsc_laplace.cxx +++ b/src/invert/laplace/impls/petsc/petsc_laplace.cxx @@ -336,7 +336,8 @@ FieldPerp LaplacePetsc::solve(const FieldPerp& b) { return solve(b, b); } * * \returns sol The solution x of the problem Ax=b. */ -FieldPerp LaplacePetsc::solve(const FieldPerp& b, const FieldPerp& x0) { +FieldPerp LaplacePetsc::solve(const FieldPerp& b, const FieldPerp& x0, + const bool forward) { TRACE("LaplacePetsc::solve"); ASSERT1(localmesh == b.getMesh() && localmesh == x0.getMesh()); @@ -355,12 +356,12 @@ FieldPerp LaplacePetsc::solve(const FieldPerp& b, const FieldPerp& x0) { MatGetOwnershipRange(MatA, &Istart, &Iend); int i = Istart; // The row in the PETSc matrix - { - Timer timer("petscsetup"); - // if ((fourth_order) && !(lastflag&INVERT_4TH_ORDER)) throw BoutException("Should not change INVERT_4TH_ORDER flag in LaplacePetsc: 2nd order and 4th order require different pre-allocation to optimize PETSc solver"); + auto timer = std::make_unique("petscsetup"); + + // if ((fourth_order) && !(lastflag&INVERT_4TH_ORDER)) throw BoutException("Should not change INVERT_4TH_ORDER flag in LaplacePetsc: 2nd order and 4th order require different pre-allocation to optimize PETSc solver"); - /* Set Matrix Elements + /* Set Matrix Elements * * Loop over locally owned rows of matrix A * i labels NODE POINT from @@ -742,6 +743,7 @@ FieldPerp LaplacePetsc::solve(const FieldPerp& b, const FieldPerp& x0) { VecAssemblyBegin(xs); VecAssemblyEnd(xs); + if (not forward) { // Configure Linear Solver #if PETSC_VERSION_GE(3, 5, 0) KSPSetOperators(ksp, MatA, MatA); @@ -808,7 +810,8 @@ FieldPerp LaplacePetsc::solve(const FieldPerp& b, const FieldPerp& x0) { lib.setOptionsFromInputFile(ksp); } - } + timer.reset(); + // Call the actual solver { @@ -826,8 +829,15 @@ FieldPerp LaplacePetsc::solve(const FieldPerp& b, const FieldPerp& x0) { "petsc_laplace: inversion failed to converge. KSPConvergedReason: {} ({})", KSPConvergedReasons[reason], static_cast(reason)); } + } else { + timer.reset(); + PetscErrorCode err = MatMult(MatA, bs, xs); + if (err != PETSC_SUCCESS) { + throw BoutException("MatMult failed with {:d}", static_cast(err)); + } + } - // Add data to FieldPerp Object + // Add data to FieldPerp Object i = Istart; // Set the inner boundary values if (localmesh->firstX()) { diff --git a/src/invert/laplace/impls/petsc/petsc_laplace.hxx b/src/invert/laplace/impls/petsc/petsc_laplace.hxx index 3a616b4b09..5a73030c8c 100644 --- a/src/invert/laplace/impls/petsc/petsc_laplace.hxx +++ b/src/invert/laplace/impls/petsc/petsc_laplace.hxx @@ -194,7 +194,13 @@ public: using Laplacian::solve; FieldPerp solve(const FieldPerp& b) override; - FieldPerp solve(const FieldPerp& b, const FieldPerp& x0) override; + FieldPerp solve(const FieldPerp& b, const FieldPerp& x0) override { + return solve(b, x0, false); + } + FieldPerp solve(const FieldPerp& b, const FieldPerp& x0, bool forward); + + using Laplacian::forward; + FieldPerp forward(const FieldPerp& b) override { return solve(b, b, true); } int precon(Vec x, Vec y); ///< Preconditioner function diff --git a/src/invert/laplace/invert_laplace.cxx b/src/invert/laplace/invert_laplace.cxx index bd839256c3..897e7e45a9 100644 --- a/src/invert/laplace/invert_laplace.cxx +++ b/src/invert/laplace/invert_laplace.cxx @@ -256,6 +256,36 @@ Field2D Laplacian::solve(const Field2D& b, const Field2D& x0) { return DC(f); } +Field3D Laplacian::forward(const Field3D& b) { + TRACE("Laplacian::solve(Field3D, Field3D)"); + + ASSERT1(b.getLocation() == location); + ASSERT1(localmesh == b.getMesh()); + + // Setting the start and end range of the y-slices + int ys = localmesh->ystart, ye = localmesh->yend; + if (include_yguards && localmesh->hasBndryLowerY()) { + ys = 0; // Mesh contains a lower boundary + } + if (include_yguards && localmesh->hasBndryUpperY()) { + ye = localmesh->LocalNy - 1; // Contains upper boundary + } + + Field3D x{emptyFrom(b)}; + + for (int jy = ys; jy <= ye; jy++) { + // 1. Slice b and x (i.e. take a X-Z plane out of the field) + // 2. Send them to the solver of the implementation (determined during creation) + x = forward(sliceXZ(b, jy)); + } + + return x; +} + +FieldPerp Laplacian::forward([[maybe_unused]] const FieldPerp& b) { + throw BoutException("Not implemented for this inversion"); +} + /********************************************************************************** * MATRIX ELEMENTS **********************************************************************************/ diff --git a/tools/pylib/_boutpp_build/boutcpp.pxd.jinja b/tools/pylib/_boutpp_build/boutcpp.pxd.jinja index 659ad8ff6d..71ca09cb46 100644 --- a/tools/pylib/_boutpp_build/boutcpp.pxd.jinja +++ b/tools/pylib/_boutpp_build/boutcpp.pxd.jinja @@ -92,7 +92,8 @@ cdef extern from "bout/invert_laplace.hxx": cppclass Laplacian: @staticmethod unique_ptr[Laplacian] create(Options*, benum.CELL_LOC, Mesh*, Solver*) - Field3D solve(Field3D,Field3D) + Field3D solve(Field3D, Field3D) + Field3D forward(Field3D) void setCoefA(Field3D) void setCoefC(Field3D) void setCoefC1(Field3D) diff --git a/tools/pylib/_boutpp_build/boutpp.pyx.jinja b/tools/pylib/_boutpp_build/boutpp.pyx.jinja index 7b07cd8296..39aa327cb9 100644 --- a/tools/pylib/_boutpp_build/boutpp.pyx.jinja +++ b/tools/pylib/_boutpp_build/boutpp.pyx.jinja @@ -905,7 +905,7 @@ cdef class Laplacian: self.cobj = c.Laplacian.create(copt, cloc, cmesh, NULL) self.isSelfOwned = True - def solve(self,Field3D x, Field3D guess): + def solve(self, Field3D x, Field3D guess): """ Calculate the Laplacian inversion @@ -924,6 +924,24 @@ cdef class Laplacian: """ return f3dFromObj(deref(self.cobj).solve(x.cobj[0],guess.cobj[0])) + def forward(self, Field3D x): + """ + Calculate the Laplacian + + Parameters + ---------- + x : Field3D + Field to take the derivative + + + Returns + ------- + Field3D + the inversion of x, where guess is a guess to start with + """ + return f3dFromObj(deref(self.cobj).forward(x.cobj[0])) + + {% set coeffs="A C C1 C2 D Ex Ez".split() %} def setCoefs(self, *{% for coeff in coeffs %}, {{coeff}}=None{% endfor %}): """ From 552c2fd9c8c908d61e5a248e32dbdf1cf9941605 Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 13 Jan 2025 09:54:15 +0100 Subject: [PATCH 138/242] Add function to check wehther point is in the boundary --- include/bout/parallel_boundary_region.hxx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/include/bout/parallel_boundary_region.hxx b/include/bout/parallel_boundary_region.hxx index d6bfc7556e..144d6c55ab 100644 --- a/include/bout/parallel_boundary_region.hxx +++ b/include/bout/parallel_boundary_region.hxx @@ -268,6 +268,15 @@ public: }); } + bool contains(const int ix, const int iy, const int iz) const { + const auto i2 = xyz2ind(ix, iy, iz, localmesh); + for (auto i1 : bndry_points) { + if (i1.index == i2) { + return true; + } + } + return false; + } // setter void setValid(char val) { bndry_position->valid = val; } From 00233cf1e658c2570a23f8e89f39a685d8af1b17 Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 13 Jan 2025 09:53:15 +0100 Subject: [PATCH 139/242] Add offset to parallel boundary region This allows to extend the boundary code to place the boundary further away from the boundary. --- include/bout/parallel_boundary_region.hxx | 18 ++++++++++++++---- src/mesh/parallel/fci.cxx | 20 ++++++++++++++------ src/mesh/parallel/shiftedmetricinterp.cxx | 8 ++++---- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/include/bout/parallel_boundary_region.hxx b/include/bout/parallel_boundary_region.hxx index 144d6c55ab..d6234117bf 100644 --- a/include/bout/parallel_boundary_region.hxx +++ b/include/bout/parallel_boundary_region.hxx @@ -36,6 +36,8 @@ struct Indices { // BoutReal angle; // How many points we can go in the opposite direction signed char valid; + signed char offset; + unsigned char abs_offset; }; using IndicesVec = std::vector; @@ -59,6 +61,9 @@ public: BoutReal s_z() const { return bndry_position->intersection.s_z; } BoutReal length() const { return bndry_position->length; } signed char valid() const { return bndry_position->valid; } + signed char offset() const { return bndry_position->offset; } + unsigned char abs_offset() const { return bndry_position->abs_offset; } + // extrapolate a given point to the boundary BoutReal extrapolate_sheath_o1(const Field3D& f) const { return f[ind()]; } @@ -246,12 +251,17 @@ public: /// Add a point to the boundary void add_point(Ind3D ind, BoutReal x, BoutReal y, BoutReal z, BoutReal length, - char valid) { - bndry_points.push_back({ind, {x, y, z}, length, valid}); + char valid, signed char offset) { + bndry_points.push_back({ind, + {x, y, z}, + length, + valid, + offset, + static_cast(std::abs(offset))}); } void add_point(int ix, int iy, int iz, BoutReal x, BoutReal y, BoutReal z, - BoutReal length, char valid) { - bndry_points.push_back({xyz2ind(ix, iy, iz, localmesh), {x, y, z}, length, valid}); + BoutReal length, char valid, signed char offset) { + add_point(xyz2ind(ix, iy, iz, localmesh), x, y, z, length, valid, offset); } // final, so they can be inlined diff --git a/src/mesh/parallel/fci.cxx b/src/mesh/parallel/fci.cxx index 758b26a377..7629cbe9c4 100644 --- a/src/mesh/parallel/fci.cxx +++ b/src/mesh/parallel/fci.cxx @@ -144,7 +144,12 @@ void load_parallel_metric_components([[maybe_unused]] Coordinates* coords, [[may #undef LOAD_PAR #endif } - + +template +int sgn(T val) { + return (T(0) < val) - (val < T(0)); +} + } // namespace FCIMap::FCIMap(Mesh& mesh, const Coordinates::FieldMetric& UNUSED(dy), Options& options, @@ -254,6 +259,8 @@ FCIMap::FCIMap(Mesh& mesh, const Coordinates::FieldMetric& UNUSED(dy), Options& BoutMask to_remove(map_mesh); const int xend = map_mesh.xstart + (map_mesh.xend - map_mesh.xstart + 1) * map_mesh.getNXPE() - 1; + // Default to the maximum number of points + const int defValid{map_mesh.ystart - 1 + std::abs(offset)}; // Serial loop because call to BoundaryRegionPar::addPoint // (probably?) can't be done in parallel BOUT_FOR_SERIAL(i, xt_prime.getRegion("RGN_NOBNDRY")) { @@ -322,11 +329,12 @@ FCIMap::FCIMap(Mesh& mesh, const Coordinates::FieldMetric& UNUSED(dy), Options& // need at least 2 points in the domain. ASSERT2(map_mesh.xend - map_mesh.xstart >= 2); auto boundary = (xt_prime[i] < map_mesh.xstart) ? inner_boundary : outer_boundary; - boundary->add_point(x, y, z, x + dx, y + 0.5 * offset, - z + dz, // Intersection point in local index space - 0.5, // Distance to intersection - 1 // Default to that there is a point in the other direction - ); + if (!boundary->contains(x, y, z)) { + boundary->add_point(x, y, z, x + dx, y + offset - sgn(offset) * 0.5, + z + dz, // Intersection point in local index space + std::abs(offset) - 0.5, // Distance to intersection + defValid, offset); + } } region_no_boundary = region_no_boundary.mask(to_remove); diff --git a/src/mesh/parallel/shiftedmetricinterp.cxx b/src/mesh/parallel/shiftedmetricinterp.cxx index ce27843267..dfb397c626 100644 --- a/src/mesh/parallel/shiftedmetricinterp.cxx +++ b/src/mesh/parallel/shiftedmetricinterp.cxx @@ -135,7 +135,7 @@ ShiftedMetricInterp::ShiftedMetricInterp(Mesh& mesh, CELL_LOC location_in, 0.25 * (1 // dy/2 + dy(it.ind, mesh.yend + 1) / dy(it.ind, mesh.yend)), // length - yvalid); + yvalid, 1); } } auto backward_boundary_xin = std::make_shared( @@ -151,7 +151,7 @@ ShiftedMetricInterp::ShiftedMetricInterp(Mesh& mesh, CELL_LOC location_in, 0.25 * (1 // dy/2 + dy(it.ind, mesh.ystart - 1) / dy(it.ind, mesh.ystart)), - yvalid); + yvalid, -1); } } // Create regions for parallel boundary conditions @@ -168,7 +168,7 @@ ShiftedMetricInterp::ShiftedMetricInterp(Mesh& mesh, CELL_LOC location_in, 0.25 * (1 // dy/2 + dy(it.ind, mesh.yend + 1) / dy(it.ind, mesh.yend)), - yvalid); + yvalid, 1); } } auto backward_boundary_xout = std::make_shared( @@ -184,7 +184,7 @@ ShiftedMetricInterp::ShiftedMetricInterp(Mesh& mesh, CELL_LOC location_in, 0.25 * (dy(it.ind, mesh.ystart - 1) / dy(it.ind, mesh.ystart) // dy/2 + 1), - yvalid); + yvalid, -1); } } From 136fa8a0b8f8b6c784ac2d12c4e119b5153aff86 Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 13 Jan 2025 09:55:59 +0100 Subject: [PATCH 140/242] Add setValid to BoundaryRegionParIterBase --- include/bout/parallel_boundary_region.hxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/bout/parallel_boundary_region.hxx b/include/bout/parallel_boundary_region.hxx index d6234117bf..8d8cabe295 100644 --- a/include/bout/parallel_boundary_region.hxx +++ b/include/bout/parallel_boundary_region.hxx @@ -64,6 +64,8 @@ public: signed char offset() const { return bndry_position->offset; } unsigned char abs_offset() const { return bndry_position->abs_offset; } + // setter + void setValid(signed char valid) { bndry_position->valid = valid; } // extrapolate a given point to the boundary BoutReal extrapolate_sheath_o1(const Field3D& f) const { return f[ind()]; } From 0e089cd40c18a6ab365f85fdaa72a07501f9564f Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 13 Jan 2025 09:57:32 +0100 Subject: [PATCH 141/242] Reimplement ynext for the boundary region This is more general and takes the offset() into account, and thus works for cases where the boundary is between the first and second guard cell --- include/bout/parallel_boundary_region.hxx | 49 ++++++++++++++++++----- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/include/bout/parallel_boundary_region.hxx b/include/bout/parallel_boundary_region.hxx index 8d8cabe295..5aa5403104 100644 --- a/include/bout/parallel_boundary_region.hxx +++ b/include/bout/parallel_boundary_region.hxx @@ -186,18 +186,49 @@ public: parallel_stencil::neumann_o3(1 - length(), value, 1, f[ind()], 2, yprev(f)); } - // BoutReal get(const Field3D& f, int off) - const BoutReal& ynext(const Field3D& f) const { return f.ynext(dir)[ind().yp(dir)]; } - BoutReal& ynext(Field3D& f) const { return f.ynext(dir)[ind().yp(dir)]; } + template + BoutReal& getAt(Field3D& f, int off) const { + if constexpr (check) { + ASSERT3(valid() > -off - 2); + } + auto _off = offset() + off * dir; + return f.ynext(_off)[ind().yp(_off)]; + } + template + const BoutReal& getAt(const Field3D& f, int off) const { + if constexpr (check) { + ASSERT3(valid() > -off - 2); + } + auto _off = offset() + off * dir; + return f.ynext(_off)[ind().yp(_off)]; + } - const BoutReal& yprev(const Field3D& f) const { - ASSERT3(valid() > 0); - return f.ynext(-dir)[ind().yp(-dir)]; + const BoutReal& ynext(const Field3D& f) const { return getAt(f, 0); } + BoutReal& ynext(Field3D& f) const { return getAt(f, 0); } + const BoutReal& ythis(const Field3D& f) const { return getAt(f, -1); } + BoutReal& ythis(Field3D& f) const { return getAt(f, -1); } + const BoutReal& yprev(const Field3D& f) const { return getAt(f, -2); } + BoutReal& yprev(Field3D& f) const { return getAt(f, -2); } + + template + BoutReal getAt(const std::function& f, + int off) const { + if constexpr (check) { + ASSERT3(valid() > -off - 2); + } + auto _off = offset() + off * dir; + return f(_off, ind().yp(_off)); } - BoutReal& yprev(Field3D& f) const { - ASSERT3(valid() > 0); - return f.ynext(-dir)[ind().yp(-dir)]; + BoutReal ynext(const std::function& f) const { + return getAt(f, 0); } + BoutReal ythis(const std::function& f) const { + return getAt(f, -1); + } + BoutReal yprev(const std::function& f) const { + return getAt(f, -2); + } + void setYPrevIfValid(Field3D& f, BoutReal val) const { if (valid() > 0) { yprev(f) = val; From 7e1067a7188ef4163f915a670ff9ea2988154195 Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 13 Jan 2025 09:58:41 +0100 Subject: [PATCH 142/242] Fix extrapolaton / interpolation --- include/bout/parallel_boundary_region.hxx | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/include/bout/parallel_boundary_region.hxx b/include/bout/parallel_boundary_region.hxx index 5aa5403104..41b06c693c 100644 --- a/include/bout/parallel_boundary_region.hxx +++ b/include/bout/parallel_boundary_region.hxx @@ -68,17 +68,17 @@ public: void setValid(signed char valid) { bndry_position->valid = valid; } // extrapolate a given point to the boundary - BoutReal extrapolate_sheath_o1(const Field3D& f) const { return f[ind()]; } + BoutReal extrapolate_sheath_o1(const Field3D& f) const { return ythis(f); } BoutReal extrapolate_sheath_o2(const Field3D& f) const { ASSERT3(valid() >= 0); if (valid() < 1) { return extrapolate_sheath_o1(f); } - return f[ind()] * (1 + length()) - f.ynext(-dir)[ind().yp(-dir)] * length(); + return ythis(f) * (1 + length()) - yprev(f) * length(); } inline BoutReal extrapolate_sheath_o1(const std::function& f) const { - return f(0, ind()); + return ythis(f); } inline BoutReal extrapolate_sheath_o2(const std::function& f) const { @@ -86,25 +86,25 @@ public: if (valid() < 1) { return extrapolate_sheath_o1(f); } - return f(0, ind()) * (1 + length()) - f(-dir, ind().yp(-dir)) * length(); + return ythis(f) * (1 + length()) - yprev(f) * length(); } inline BoutReal interpolate_sheath_o1(const Field3D& f) const { - return f[ind()] * (1 - length()) + ynext(f) * length(); + return ythis(f) * (1 - length()) + ynext(f) * length(); } - inline BoutReal extrapolate_next_o1(const Field3D& f) const { return f[ind()]; } + inline BoutReal extrapolate_next_o1(const Field3D& f) const { return ythis(f); } inline BoutReal extrapolate_next_o2(const Field3D& f) const { ASSERT3(valid() >= 0); if (valid() < 1) { return extrapolate_next_o1(f); } - return f[ind()] * 2 - f.ynext(-dir)[ind().yp(-dir)]; + return ythis(f) * 2 - yprev(f); } inline BoutReal extrapolate_next_o1(const std::function& f) const { - return f(0, ind()); + return ythis(f); } inline BoutReal extrapolate_next_o2(const std::function& f) const { @@ -112,7 +112,7 @@ public: if (valid() < 1) { return extrapolate_sheath_o1(f); } - return f(0, ind()) * 2 - f(-dir, ind().yp(-dir)); + return ythis(f) * 2 - yprev(f); } // extrapolate the gradient into the boundary @@ -122,7 +122,7 @@ public: if (valid() < 1) { return extrapolate_grad_o1(f); } - return f[ind()] - f.ynext(-dir)[ind().yp(-dir)]; + return ythis(f) - ynext(f); } BoundaryRegionParIterBase& operator*() { return *this; } @@ -320,6 +320,7 @@ public: } return false; } + // setter void setValid(char val) { bndry_position->valid = val; } From d416b5d939df6d2416153879769b591dc3f530de Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 13 Jan 2025 11:12:38 +0100 Subject: [PATCH 143/242] Fix parallel boundary to interpolate into the boundary Previously only the first boundary point was set --- include/bout/parallel_boundary_region.hxx | 32 ++++++++++++++++------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/include/bout/parallel_boundary_region.hxx b/include/bout/parallel_boundary_region.hxx index 41b06c693c..c637b9e908 100644 --- a/include/bout/parallel_boundary_region.hxx +++ b/include/bout/parallel_boundary_region.hxx @@ -136,17 +136,20 @@ public: return bndry_position != rhs.bndry_position; } +#define ITER() for (int i = 0; i < localmesh->ystart - abs_offset(); ++i) // dirichlet boundary code void dirichlet_o1(Field3D& f, BoutReal value) const { - f.ynext(dir)[ind().yp(dir)] = value; + ITER() { getAt(f, i) = value; } } void dirichlet_o2(Field3D& f, BoutReal value) const { if (length() < small_value) { return dirichlet_o1(f, value); } - ynext(f) = parallel_stencil::dirichlet_o2(1, f[ind()], 1 - length(), value); - // ynext(f) = f[ind()] * (1 + 1/length()) + value / length(); + ITER() { + getAt(f, i) = + parallel_stencil::dirichlet_o2(i + 1, ythis(f), i + 1 - length(), value); + } } void dirichlet_o3(Field3D& f, BoutReal value) const { @@ -155,17 +158,24 @@ public: return dirichlet_o2(f, value); } if (length() < small_value) { - ynext(f) = parallel_stencil::dirichlet_o2(2, yprev(f), 1 - length(), value); + ITER() { + getAt(f, i) = + parallel_stencil::dirichlet_o2(i + 2, yprev(f), i + 1 - length(), value); + } } else { - ynext(f) = - parallel_stencil::dirichlet_o3(2, yprev(f), 1, f[ind()], 1 - length(), value); + ITER() { + getAt(f, i) = parallel_stencil::dirichlet_o3(i + 2, yprev(f), i + 1, ythis(f), + i + 1 - length(), value); + } } } // NB: value needs to be scaled by dy // neumann_o1 is actually o2 if we would use an appropriate one-sided stencil. // But in general we do not, and thus for normal C2 stencils, this is 1st order. - void neumann_o1(Field3D& f, BoutReal value) const { ynext(f) = f[ind()] + value; } + void neumann_o1(Field3D& f, BoutReal value) const { + ITER() { getAt(f, i) = ythis(f) + value; } + } // NB: value needs to be scaled by dy void neumann_o2(Field3D& f, BoutReal value) const { @@ -173,7 +183,7 @@ public: if (valid() < 1) { return neumann_o1(f, value); } - ynext(f) = yprev(f) + 2 * value; + ITER() { getAt(f, i) = yprev(f) + 2 * value; } } // NB: value needs to be scaled by dy @@ -182,8 +192,10 @@ public: if (valid() < 1) { return neumann_o1(f, value); } - ynext(f) = - parallel_stencil::neumann_o3(1 - length(), value, 1, f[ind()], 2, yprev(f)); + ITER() { + getAt(f, i) = parallel_stencil::neumann_o3(i + 1 - length(), value, i + 1, ythis(f), + 2, yprev(f)); + } } template From 6cca6a41c055be1ab988db3ebd65d8662b683047 Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 13 Jan 2025 11:13:14 +0100 Subject: [PATCH 144/242] Ensure data is sorted --- include/bout/parallel_boundary_region.hxx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/bout/parallel_boundary_region.hxx b/include/bout/parallel_boundary_region.hxx index c637b9e908..7e4e340c8a 100644 --- a/include/bout/parallel_boundary_region.hxx +++ b/include/bout/parallel_boundary_region.hxx @@ -297,6 +297,9 @@ public: /// Add a point to the boundary void add_point(Ind3D ind, BoutReal x, BoutReal y, BoutReal z, BoutReal length, char valid, signed char offset) { + if (!bndry_points.empty() && bndry_points.back().index > ind) { + is_sorted = false; + } bndry_points.push_back({ind, {x, y, z}, length, @@ -315,6 +318,7 @@ public: bool isDone() final { return (bndry_position == std::end(bndry_points)); } bool contains(const BoundaryRegionPar& bndry) const { + ASSERT2(is_sorted); return std::binary_search(std::begin(bndry_points), std::end(bndry_points), *bndry.bndry_position, [](const bout::parallel_boundary_region::Indices& i1, @@ -362,6 +366,7 @@ private: const int nz = mesh->LocalNz; return Ind3D{(x * ny + y) * nz + z, ny, nz}; } + bool is_sorted{true}; }; #endif // BOUT_PAR_BNDRY_H From 62b62bf44189357959ba5dd338a5bee75edffb6b Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 13 Jan 2025 11:13:26 +0100 Subject: [PATCH 145/242] fix typo --- src/mesh/parallel/fci.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/parallel/fci.cxx b/src/mesh/parallel/fci.cxx index 7629cbe9c4..07a7a6490b 100644 --- a/src/mesh/parallel/fci.cxx +++ b/src/mesh/parallel/fci.cxx @@ -159,7 +159,7 @@ FCIMap::FCIMap(Mesh& mesh, const Coordinates::FieldMetric& UNUSED(dy), Options& region_no_boundary(map_mesh.getRegion("RGN_NOBNDRY")), corner_boundary_mask(map_mesh) { - TRACE("Creating FCIMAP for direction {:d}", offset); + TRACE("Creating FCIMap for direction {:d}", offset); if (offset == 0) { throw BoutException( From 628a6ce705cedddda561bb26b89bbb8f66534701 Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 13 Jan 2025 11:13:59 +0100 Subject: [PATCH 146/242] Calculate valid for the general case --- src/mesh/parallel/fci.hxx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/mesh/parallel/fci.hxx b/src/mesh/parallel/fci.hxx index 7085a71535..f993812a43 100644 --- a/src/mesh/parallel/fci.hxx +++ b/src/mesh/parallel/fci.hxx @@ -100,7 +100,6 @@ public: field_line_maps.emplace_back(mesh, dy, options, -offset, backward_boundary_xin, backward_boundary_xout, zperiodic); } - ASSERT0(mesh.ystart == 1); std::shared_ptr bndries[]{ forward_boundary_xin, forward_boundary_xout, backward_boundary_xin, backward_boundary_xout}; @@ -109,9 +108,13 @@ public: if (bndry->dir == bndry2->dir) { continue; } - for (bndry->first(); !bndry->isDone(); bndry->next()) { - if (bndry2->contains(*bndry)) { - bndry->setValid(0); + for (auto pnt : *bndry) { + for (auto pnt2 : *bndry2) { +#warning this could likely be done faster + if (pnt.ind() == pnt2.ind()) { + pnt.setValid( + static_cast(std::abs((pnt2.offset() - pnt.offset())) - 2)); + } } } } From e5c9fc125e7cbc34fb1358342bf08fdea6e97338 Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 15 Jan 2025 13:20:10 +0100 Subject: [PATCH 147/242] Add communication routine for FCI operation 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. ``` --- CMakeLists.txt | 2 + include/bout/region.hxx | 3 +- src/mesh/parallel/fci_comm.cxx | 34 +++++ src/mesh/parallel/fci_comm.hxx | 241 +++++++++++++++++++++++++++++++++ 4 files changed, 279 insertions(+), 1 deletion(-) create mode 100644 src/mesh/parallel/fci_comm.cxx create mode 100644 src/mesh/parallel/fci_comm.hxx diff --git a/CMakeLists.txt b/CMakeLists.txt index 7df044c867..781fc65672 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -274,6 +274,8 @@ set(BOUT_SOURCES ./src/mesh/mesh.cxx ./src/mesh/parallel/fci.cxx ./src/mesh/parallel/fci.hxx + ./src/mesh/parallel/fci_comm.cxx + ./src/mesh/parallel/fci_comm.hxx ./src/mesh/parallel/identity.cxx ./src/mesh/parallel/shiftedmetric.cxx ./src/mesh/parallel/shiftedmetricinterp.cxx diff --git a/include/bout/region.hxx b/include/bout/region.hxx index bb1cf82bf1..f441b3edd7 100644 --- a/include/bout/region.hxx +++ b/include/bout/region.hxx @@ -139,7 +139,7 @@ class BoutMask; BOUT_FOR_OMP(index, (region), for schedule(BOUT_OPENMP_SCHEDULE) nowait) // NOLINTEND(cppcoreguidelines-macro-usage,bugprone-macro-parentheses) -enum class IND_TYPE { IND_3D = 0, IND_2D = 1, IND_PERP = 2 }; +enum class IND_TYPE { IND_3D = 0, IND_2D = 1, IND_PERP = 2, IND_GLOBAL_3D }; /// Indices base class for Fields -- Regions are dereferenced into these /// @@ -386,6 +386,7 @@ inline SpecificInd operator-(SpecificInd lhs, const SpecificInd& rhs) { using Ind3D = SpecificInd; using Ind2D = SpecificInd; using IndPerp = SpecificInd; +using IndG3D = SpecificInd; /// Get string representation of Ind3D inline std::string toString(const Ind3D& i) { diff --git a/src/mesh/parallel/fci_comm.cxx b/src/mesh/parallel/fci_comm.cxx new file mode 100644 index 0000000000..c0d51d1eb9 --- /dev/null +++ b/src/mesh/parallel/fci_comm.cxx @@ -0,0 +1,34 @@ +/************************************************************************** + * Communication for Flux-coordinate Independent interpolation + * + ************************************************************************** + * Copyright 2025 BOUT++ contributors + * + * Contact: Ben Dudson, dudson2@llnl.gov + * + * This file is part of BOUT++. + * + * BOUT++ is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * BOUT++ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with BOUT++. If not, see . + * + **************************************************************************/ + +#include "fci_comm.hxx" + +#include + +const BoutReal& GlobalField3DAccessInstance::operator[](IndG3D ind) const { + auto it = gfa.mapping.find(ind.ind); + ASSERT2(it != gfa.mapping.end()); + return data[it->second]; +} diff --git a/src/mesh/parallel/fci_comm.hxx b/src/mesh/parallel/fci_comm.hxx new file mode 100644 index 0000000000..aa3b5ecfb5 --- /dev/null +++ b/src/mesh/parallel/fci_comm.hxx @@ -0,0 +1,241 @@ +/************************************************************************** + * Communication for Flux-coordinate Independent interpolation + * + ************************************************************************** + * Copyright 2025 BOUT++ contributors + * + * Contact: Ben Dudson, dudson2@llnl.gov + * + * This file is part of BOUT++. + * + * BOUT++ is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * BOUT++ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with BOUT++. If not, see . + * + **************************************************************************/ + +#pragma once + +#include "bout/assert.hxx" +#include "bout/bout_types.hxx" +#include "bout/boutcomm.hxx" +#include "bout/field3d.hxx" +#include "bout/mesh.hxx" +#include "bout/region.hxx" +#include +#include +#include +#include +#include +#include +#include +class GlobalField3DAccess; + +namespace fci_comm { +struct ProcLocal { + int proc; + int ind; +}; +struct globalToLocal1D { + const int mg; + const int npe; + const int localwith; + const int local; + const int global; + const int globalwith; + globalToLocal1D(int mg, int npe, int localwith) + : mg(mg), npe(npe), localwith(localwith), local(localwith - 2 * mg), + global(local * npe), globalwith(global + 2 * mg) {}; + ProcLocal convert(int id) const { + int idwo = id - mg; + int proc = idwo / local; + if (proc >= npe) { + proc = npe - 1; + } + ASSERT2(proc >= 0); + int loc = id - local * proc; + ASSERT2(0 <= loc); + ASSERT2(loc < (local + 2 * mg)); + return {proc, loc}; + } +}; +template +struct XYZ2Ind { + const int nx; + const int ny; + const int nz; + ind convert(const int x, const int y, const int z) const { + return {z + (y + x * ny) * nz, ny, nz}; + } + ind operator()(const int x, const int y, const int z) const { return convert(x, y, z); } + XYZ2Ind(const int nx, const int ny, const int nz) : nx(nx), ny(ny), nz(nz) {} +}; +} // namespace fci_comm + +class GlobalField3DAccessInstance { +public: + const BoutReal& operator[](IndG3D ind) const; + + GlobalField3DAccessInstance(const GlobalField3DAccess* gfa, + const std::vector&& data) + : gfa(*gfa), data(std::move(data)) {}; + +private: + const GlobalField3DAccess& gfa; + const std::vector data; +}; + +class GlobalField3DAccess { +public: + friend class GlobalField3DAccessInstance; + GlobalField3DAccess(Mesh* mesh) + : mesh(mesh), g2lx(mesh->xstart, mesh->getNXPE(), mesh->LocalNx), + g2ly(mesh->ystart, mesh->getNYPE(), mesh->LocalNy), + g2lz(mesh->zstart, 1, mesh->LocalNz), + xyzl(g2lx.localwith, g2ly.localwith, g2lz.localwith), + xyzg(g2lx.globalwith, g2ly.globalwith, g2lz.globalwith), comm(BoutComm::get()) {}; + void get(IndG3D ind) { ids.emplace(ind.ind); } + void operator[](IndG3D ind) { return get(ind); } + void setup() { + ASSERT2(is_setup == false); + toGet.resize(g2lx.npe * g2ly.npe * g2lz.npe); + for (const auto id : ids) { + IndG3D gind{id, g2ly.globalwith, g2lz.globalwith}; + const auto pix = g2lx.convert(gind.x()); + const auto piy = g2ly.convert(gind.y()); + const auto piz = g2lz.convert(gind.z()); + ASSERT3(piz.proc == 0); + toGet[piy.proc * g2lx.npe + pix.proc].push_back( + xyzl.convert(pix.ind, piy.ind, piz.ind).ind); + } + for (auto v : toGet) { + std::sort(v.begin(), v.end()); + } + commCommLists(); + { + int offset = 0; + for (auto get : toGet) { + offsets.push_back(offset); + offset += get.size(); + } + offsets.push_back(offset); + } + std::map mapping; + for (const auto id : ids) { + IndG3D gind{id, g2ly.globalwith, g2lz.globalwith}; + const auto pix = g2lx.convert(gind.x()); + const auto piy = g2ly.convert(gind.y()); + const auto piz = g2lz.convert(gind.z()); + ASSERT3(piz.proc == 0); + const auto proc = piy.proc * g2lx.npe + pix.proc; + const auto& vec = toGet[proc]; + auto it = + std::find(vec.begin(), vec.end(), xyzl.convert(pix.ind, piy.ind, piz.ind).ind); + ASSERT3(it != vec.end()); + mapping[id] = it - vec.begin() + offsets[proc]; + } + is_setup = true; + } + GlobalField3DAccessInstance communicate(const Field3D& f) { + return {this, communicate_data(f)}; + } + std::unique_ptr communicate_asPtr(const Field3D& f) { + return std::make_unique(this, communicate_data(f)); + } + +private: + void commCommLists() { + toSend.resize(toGet.size()); + std::vector toGetSizes(toGet.size()); + std::vector toSendSizes(toSend.size()); + //const int thisproc = mesh->getYProcIndex() * g2lx.npe + mesh->getXProcIndex(); + std::vector reqs(toSend.size()); + for (size_t proc = 0; proc < toGet.size(); ++proc) { + auto ret = MPI_Irecv(static_cast(&toSendSizes[proc]), 1, MPI_INT, proc, + 666 + proc, comm, &reqs[proc]); + ASSERT0(ret == MPI_SUCCESS); + } + for (size_t proc = 0; proc < toGet.size(); ++proc) { + toGetSizes[proc] = toGet[proc].size(); + sendBufferSize += toGetSizes[proc]; + auto ret = MPI_Send(static_cast(&toGetSizes[proc]), 1, MPI_INT, proc, + 666 + proc, comm); + ASSERT0(ret == MPI_SUCCESS); + } + for ([[maybe_unused]] auto dummy : reqs) { + int ind{0}; + auto ret = MPI_Waitany(reqs.size(), &reqs[0], &ind, MPI_STATUS_IGNORE); + ASSERT0(ret == MPI_SUCCESS); + ASSERT3(ind != MPI_UNDEFINED); + toSend[ind].resize(toSendSizes[ind]); + ret = MPI_Irecv(static_cast(&toSend[ind]), toSend[ind].size(), MPI_INT, ind, + 666 * 666 + ind, comm, &reqs[ind]); + ASSERT0(ret == MPI_SUCCESS); + } + for (size_t proc = 0; proc < toGet.size(); ++proc) { + const auto ret = MPI_Send(static_cast(&toGet[proc]), toGet[proc].size(), + MPI_INT, proc, 666 * 666 + proc, comm); + ASSERT0(ret == MPI_SUCCESS); + } + for ([[maybe_unused]] auto dummy : reqs) { + int ind{0}; + const auto ret = MPI_Waitany(reqs.size(), &reqs[0], &ind, MPI_STATUS_IGNORE); + ASSERT0(ret == MPI_SUCCESS); + ASSERT3(ind != MPI_UNDEFINED); + } + } + Mesh* mesh; + std::set ids; + std::map mapping; + bool is_setup{false}; + const fci_comm::globalToLocal1D g2lx; + const fci_comm::globalToLocal1D g2ly; + const fci_comm::globalToLocal1D g2lz; + +public: + const fci_comm::XYZ2Ind xyzl; + const fci_comm::XYZ2Ind xyzg; + +private: + std::vector> toGet; + std::vector> toSend; + std::vector offsets; + int sendBufferSize{0}; + MPI_Comm comm; + std::vector communicate_data(const Field3D& f) { + ASSERT2(f.getMesh() == mesh); + std::vector data(offsets.back()); + std::vector sendBuffer(sendBufferSize); + std::vector reqs(toSend.size()); + for (size_t proc = 0; proc < toGet.size(); ++proc) { + auto ret = MPI_Irecv(static_cast(&data[proc]), toGet[proc].size(), + MPI_DOUBLE, proc, 666 + proc, comm, &reqs[proc]); + ASSERT0(ret == MPI_SUCCESS); + } + int cnt = 0; + for (size_t proc = 0; proc < toGet.size(); ++proc) { + void* start = static_cast(&sendBuffer[cnt]); + for (auto i : toSend[proc]) { + sendBuffer[cnt++] = f[Ind3D(i)]; + } + auto ret = MPI_Send(start, toSend[proc].size(), MPI_DOUBLE, proc, 666 + proc, comm); + ASSERT0(ret == MPI_SUCCESS); + } + for ([[maybe_unused]] auto dummy : reqs) { + int ind{0}; + auto ret = MPI_Waitany(reqs.size(), &reqs[0], &ind, MPI_STATUS_IGNORE); + ASSERT0(ret == MPI_SUCCESS); + ASSERT3(ind != MPI_UNDEFINED); + } + return data; + } +}; From 40dac599408ce1b2f2699e48960e83ed4f97d7bc Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 15 Jan 2025 13:26:37 +0100 Subject: [PATCH 148/242] Unify XZMonotonicHermiteSpline and XZMonotonicHermiteSpline If they are two instances of the same template, this allows to have an if in the inner loop that can be optimised out. --- CMakeLists.txt | 1 - include/bout/interpolation_xz.hxx | 46 ++----- src/mesh/interpolation/hermite_spline_xz.cxx | 74 +++++++++-- .../monotonic_hermite_spline_xz.cxx | 117 ------------------ src/mesh/interpolation_xz.cxx | 1 + 5 files changed, 76 insertions(+), 163 deletions(-) delete mode 100644 src/mesh/interpolation/monotonic_hermite_spline_xz.cxx diff --git a/CMakeLists.txt b/CMakeLists.txt index 781fc65672..9e57495885 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -269,7 +269,6 @@ set(BOUT_SOURCES ./src/mesh/interpolation/hermite_spline_z.cxx ./src/mesh/interpolation/interpolation_z.cxx ./src/mesh/interpolation/lagrange_4pt_xz.cxx - ./src/mesh/interpolation/monotonic_hermite_spline_xz.cxx ./src/mesh/invert3x3.hxx ./src/mesh/mesh.cxx ./src/mesh/parallel/fci.cxx diff --git a/include/bout/interpolation_xz.hxx b/include/bout/interpolation_xz.hxx index def8a60a3e..261b4c1515 100644 --- a/include/bout/interpolation_xz.hxx +++ b/include/bout/interpolation_xz.hxx @@ -36,6 +36,7 @@ #endif class Options; +class GlobalField3DAccess; /// Interpolate a field onto a perturbed set of points const Field3D interpolate(const Field3D& f, const Field3D& delta_x, @@ -133,7 +134,8 @@ public: } }; -class XZHermiteSpline : public XZInterpolation { +template +class XZHermiteSplineBase : public XZInterpolation { protected: /// This is protected rather than private so that it can be /// extended and used by HermiteSplineMonotonic @@ -141,6 +143,9 @@ protected: Tensor> i_corner; // index of bottom-left grid point Tensor k_corner; // z-index of bottom-left grid point + std::unique_ptr gf3daccess; + Tensor> g3dinds; + // Basis functions for cubic Hermite spline interpolation // see http://en.wikipedia.org/wiki/Cubic_Hermite_spline // The h00 and h01 basis functions are applied to the function itself @@ -166,13 +171,13 @@ protected: #endif public: - XZHermiteSpline(Mesh* mesh = nullptr) : XZHermiteSpline(0, mesh) {} - XZHermiteSpline(int y_offset = 0, Mesh* mesh = nullptr); - XZHermiteSpline(const BoutMask& mask, int y_offset = 0, Mesh* mesh = nullptr) - : XZHermiteSpline(y_offset, mesh) { + XZHermiteSplineBase(Mesh* mesh = nullptr) : XZHermiteSplineBase(0, mesh) {} + XZHermiteSplineBase(int y_offset = 0, Mesh* mesh = nullptr); + XZHermiteSplineBase(const BoutMask& mask, int y_offset = 0, Mesh* mesh = nullptr) + : XZHermiteSplineBase(y_offset, mesh) { setRegion(regionFromMask(mask, localmesh)); } - ~XZHermiteSpline() { + ~XZHermiteSplineBase() { #if HS_USE_PETSC if (isInit) { MatDestroy(&petscWeights); @@ -210,33 +215,8 @@ public: /// but also degrades accuracy near maxima and minima. /// Perhaps should only impose near boundaries, since that is where /// problems most obviously occur. -class XZMonotonicHermiteSpline : public XZHermiteSpline { -public: - XZMonotonicHermiteSpline(Mesh* mesh = nullptr) : XZHermiteSpline(0, mesh) { - if (localmesh->getNXPE() > 1) { - throw BoutException("Do not support MPI splitting in X"); - } - } - XZMonotonicHermiteSpline(int y_offset = 0, Mesh* mesh = nullptr) - : XZHermiteSpline(y_offset, mesh) { - if (localmesh->getNXPE() > 1) { - throw BoutException("Do not support MPI splitting in X"); - } - } - XZMonotonicHermiteSpline(const BoutMask& mask, int y_offset = 0, Mesh* mesh = nullptr) - : XZHermiteSpline(mask, y_offset, mesh) { - if (localmesh->getNXPE() > 1) { - throw BoutException("Do not support MPI splitting in X"); - } - } - - using XZHermiteSpline::interpolate; - /// Interpolate using precalculated weights. - /// This function is called by the other interpolate functions - /// in the base class XZHermiteSpline. - Field3D interpolate(const Field3D& f, - const std::string& region = "RGN_NOBNDRY") const override; -}; +using XZMonotonicHermiteSpline = XZHermiteSplineBase; +using XZHermiteSpline = XZHermiteSplineBase; class XZLagrange4pt : public XZInterpolation { Tensor i_corner; // x-index of bottom-left grid point diff --git a/src/mesh/interpolation/hermite_spline_xz.cxx b/src/mesh/interpolation/hermite_spline_xz.cxx index 650e4022e7..85786d5381 100644 --- a/src/mesh/interpolation/hermite_spline_xz.cxx +++ b/src/mesh/interpolation/hermite_spline_xz.cxx @@ -21,6 +21,7 @@ **************************************************************************/ #include "../impls/bout/boutmesh.hxx" +#include "../parallel/fci_comm.hxx" #include "bout/globals.hxx" #include "bout/index_derivs_interface.hxx" #include "bout/interpolation_xz.hxx" @@ -101,7 +102,8 @@ class IndConverter { } }; -XZHermiteSpline::XZHermiteSpline(int y_offset, Mesh* meshin) +template +XZHermiteSplineBase::XZHermiteSplineBase(int y_offset, Mesh* meshin) : XZInterpolation(y_offset, meshin), h00_x(localmesh), h01_x(localmesh), h10_x(localmesh), h11_x(localmesh), h00_z(localmesh), h01_z(localmesh), h10_z(localmesh), h11_z(localmesh) { @@ -145,6 +147,10 @@ XZHermiteSpline::XZHermiteSpline(int y_offset, Mesh* meshin) MatCreateAIJ(MPI_COMM_WORLD, m, m, M, M, 16, nullptr, 16, nullptr, &petscWeights); #endif #endif + if constexpr (monotonic) { + gf3daccess = std::make_unique(localmesh); + g3dinds.reallocate(localmesh->LocalNx, localmesh->LocalNy, localmesh->LocalNz); + } #ifndef HS_USE_PETSC if (localmesh->getNXPE() > 1) { throw BoutException("Require PETSc for MPI splitting in X"); @@ -152,8 +158,10 @@ XZHermiteSpline::XZHermiteSpline(int y_offset, Mesh* meshin) #endif } -void XZHermiteSpline::calcWeights(const Field3D& delta_x, const Field3D& delta_z, - const std::string& region) { +template +void XZHermiteSplineBase::calcWeights(const Field3D& delta_x, + const Field3D& delta_z, + const std::string& region) { const int ny = localmesh->LocalNy; const int nz = localmesh->LocalNz; @@ -294,6 +302,14 @@ void XZHermiteSpline::calcWeights(const Field3D& delta_x, const Field3D& delta_z } #endif #endif + if constexpr (monotonic) { + const auto gind = gf3daccess->xyzg(i_corn, y + y_offset, k_corner(x, y, z)); + gf3daccess->get(gind); + gf3daccess->get(gind.xp(1)); + gf3daccess->get(gind.zp(1)); + gf3daccess->get(gind.xp(1).zp(1)); + g3dinds[i] = {gind.ind, gind.xp(1).ind, gind.zp(1).ind, gind.xp(1).zp(1).ind}; + } } #ifdef HS_USE_PETSC MatAssemblyBegin(petscWeights, MAT_FINAL_ASSEMBLY); @@ -305,8 +321,11 @@ void XZHermiteSpline::calcWeights(const Field3D& delta_x, const Field3D& delta_z #endif } -void XZHermiteSpline::calcWeights(const Field3D& delta_x, const Field3D& delta_z, - const BoutMask& mask, const std::string& region) { +template +void XZHermiteSplineBase::calcWeights(const Field3D& delta_x, + const Field3D& delta_z, + const BoutMask& mask, + const std::string& region) { setMask(mask); calcWeights(delta_x, delta_z, region); } @@ -327,8 +346,10 @@ void XZHermiteSpline::calcWeights(const Field3D& delta_x, const Field3D& delta_z * (i, j+1, k+1) h01_z + h10_z / 2 * (i, j+1, k+2) h11_z / 2 */ +template std::vector -XZHermiteSpline::getWeightsForYApproximation(int i, int j, int k, int yoffset) { +XZHermiteSplineBase::getWeightsForYApproximation(int i, int j, int k, + int yoffset) { const int nz = localmesh->LocalNz; const int k_mod = k_corner(i, j, k); const int k_mod_m1 = (k_mod > 0) ? (k_mod - 1) : (nz - 1); @@ -341,7 +362,9 @@ XZHermiteSpline::getWeightsForYApproximation(int i, int j, int k, int yoffset) { {i, j + yoffset, k_mod_p2, 0.5 * h11_z(i, j, k)}}; } -Field3D XZHermiteSpline::interpolate(const Field3D& f, const std::string& region) const { +template +Field3D XZHermiteSplineBase::interpolate(const Field3D& f, + const std::string& region) const { ASSERT1(f.getMesh() == localmesh); Field3D f_interp{emptyFrom(f)}; @@ -387,6 +410,11 @@ Field3D XZHermiteSpline::interpolate(const Field3D& f, const std::string& region Field3D fz = bout::derivatives::index::DDZ(f, CELL_DEFAULT, "DEFAULT", region2); Field3D fxz = bout::derivatives::index::DDZ(fx, CELL_DEFAULT, "DEFAULT", region2); + std::unique_ptr g3d; + if constexpr (monotonic) { + gf = gf3daccess.communicate_asPtr(f); + } + BOUT_FOR(i, getRegion(region)) { const auto iyp = i.yp(y_offset); @@ -415,6 +443,19 @@ Field3D XZHermiteSpline::interpolate(const Field3D& f, const std::string& region f_interp[iyp] = +f_z * h00_z[i] + f_zp1 * h01_z[i] + fz_z * h10_z[i] + fz_zp1 * h11_z[i]; + if constexpr (monotonic) { + const auto corners = {gf[g3dinds[i][0]], gf[g3dinds[i][1]], gf[g3dinds[i][2]], + gf[g3dinds[i][3]]}; + const auto minmax = std::minmax(corners); + if (f_interp[iyp] < minmax.first) { + f_interp[iyp] = minmax.first; + } else { + if (f_interp[iyp] > minmax.second) { + f_interp[iyp] = minmax.second; + } + } + } + ASSERT2(std::isfinite(f_interp[iyp]) || i.x() < localmesh->xstart || i.x() > localmesh->xend); } @@ -424,15 +465,24 @@ Field3D XZHermiteSpline::interpolate(const Field3D& f, const std::string& region return f_interp; } -Field3D XZHermiteSpline::interpolate(const Field3D& f, const Field3D& delta_x, - const Field3D& delta_z, const std::string& region) { +template +Field3D XZHermiteSplineBase::interpolate(const Field3D& f, + const Field3D& delta_x, + const Field3D& delta_z, + const std::string& region) { calcWeights(delta_x, delta_z, region); return interpolate(f, region); } -Field3D XZHermiteSpline::interpolate(const Field3D& f, const Field3D& delta_x, - const Field3D& delta_z, const BoutMask& mask, - const std::string& region) { +template +Field3D +XZHermiteSplineBase::interpolate(const Field3D& f, const Field3D& delta_x, + const Field3D& delta_z, const BoutMask& mask, + const std::string& region) { calcWeights(delta_x, delta_z, mask, region); return interpolate(f, region); } + +// ensure they are instantiated +template class XZHermiteSplineBase; +template class XZHermiteSplineBase; diff --git a/src/mesh/interpolation/monotonic_hermite_spline_xz.cxx b/src/mesh/interpolation/monotonic_hermite_spline_xz.cxx deleted file mode 100644 index 4b84bcd265..0000000000 --- a/src/mesh/interpolation/monotonic_hermite_spline_xz.cxx +++ /dev/null @@ -1,117 +0,0 @@ -/************************************************************************** - * Copyright 2018 B.D.Dudson, P. Hill - * - * Contact: Ben Dudson, bd512@york.ac.uk - * - * This file is part of BOUT++. - * - * BOUT++ is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * BOUT++ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with BOUT++. If not, see . - * - **************************************************************************/ - -#include "bout/globals.hxx" -#include "bout/index_derivs_interface.hxx" -#include "bout/interpolation_xz.hxx" -#include "bout/mesh.hxx" - -#include - -Field3D XZMonotonicHermiteSpline::interpolate(const Field3D& f, - const std::string& region) const { - ASSERT1(f.getMesh() == localmesh); - Field3D f_interp(f.getMesh()); - f_interp.allocate(); - - // Derivatives are used for tension and need to be on dimensionless - // coordinates - Field3D fx = bout::derivatives::index::DDX(f, CELL_DEFAULT, "DEFAULT"); - localmesh->communicateXZ(fx); - // communicate in y, but do not calculate parallel slices - { - auto h = localmesh->sendY(fx); - localmesh->wait(h); - } - Field3D fz = bout::derivatives::index::DDZ(f, CELL_DEFAULT, "DEFAULT", "RGN_ALL"); - localmesh->communicateXZ(fz); - // communicate in y, but do not calculate parallel slices - { - auto h = localmesh->sendY(fz); - localmesh->wait(h); - } - Field3D fxz = bout::derivatives::index::DDX(fz, CELL_DEFAULT, "DEFAULT"); - localmesh->communicateXZ(fxz); - // communicate in y, but do not calculate parallel slices - { - auto h = localmesh->sendY(fxz); - localmesh->wait(h); - } - - const auto curregion{getRegion(region)}; - BOUT_FOR(i, curregion) { - const auto iyp = i.yp(y_offset); - - const auto ic = i_corner[i]; - const auto iczp = ic.zp(); - const auto icxp = ic.xp(); - const auto icxpzp = iczp.xp(); - - // Interpolate f in X at Z - const BoutReal f_z = - f[ic] * h00_x[i] + f[icxp] * h01_x[i] + fx[ic] * h10_x[i] + fx[icxp] * h11_x[i]; - - // Interpolate f in X at Z+1 - const BoutReal f_zp1 = f[iczp] * h00_x[i] + f[icxpzp] * h01_x[i] + fx[iczp] * h10_x[i] - + fx[icxpzp] * h11_x[i]; - - // Interpolate fz in X at Z - const BoutReal fz_z = fz[ic] * h00_x[i] + fz[icxp] * h01_x[i] + fxz[ic] * h10_x[i] - + fxz[icxp] * h11_x[i]; - - // Interpolate fz in X at Z+1 - const BoutReal fz_zp1 = fz[iczp] * h00_x[i] + fz[icxpzp] * h01_x[i] - + fxz[iczp] * h10_x[i] + fxz[icxpzp] * h11_x[i]; - - // Interpolate in Z - BoutReal result = - +f_z * h00_z[i] + f_zp1 * h01_z[i] + fz_z * h10_z[i] + fz_zp1 * h11_z[i]; - - ASSERT2(std::isfinite(result) || i.x() < localmesh->xstart - || i.x() > localmesh->xend); - - // Monotonicity - // Force the interpolated result to be in the range of the - // neighbouring cell values. This prevents unphysical overshoots, - // but also degrades accuracy near maxima and minima. - // Perhaps should only impose near boundaries, since that is where - // problems most obviously occur. - const BoutReal localmax = BOUTMAX(f[ic], f[icxp], f[iczp], f[icxpzp]); - - const BoutReal localmin = BOUTMIN(f[ic], f[icxp], f[iczp], f[icxpzp]); - - ASSERT2(std::isfinite(localmax) || i.x() < localmesh->xstart - || i.x() > localmesh->xend); - ASSERT2(std::isfinite(localmin) || i.x() < localmesh->xstart - || i.x() > localmesh->xend); - - if (result > localmax) { - result = localmax; - } - if (result < localmin) { - result = localmin; - } - - f_interp[iyp] = result; - } - return f_interp; -} diff --git a/src/mesh/interpolation_xz.cxx b/src/mesh/interpolation_xz.cxx index f7f0b457f2..0bc25111ab 100644 --- a/src/mesh/interpolation_xz.cxx +++ b/src/mesh/interpolation_xz.cxx @@ -23,6 +23,7 @@ * **************************************************************************/ +#include "parallel/fci_comm.hxx" #include #include #include From 9a98a8be4defbf918189c8f8986c62d3898514bd Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 15 Jan 2025 13:26:59 +0100 Subject: [PATCH 149/242] Use x-splitting for monotonichermitespline test --- tests/MMS/spatial/fci/runtest | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/MMS/spatial/fci/runtest b/tests/MMS/spatial/fci/runtest index b51c311a50..f817db0b73 100755 --- a/tests/MMS/spatial/fci/runtest +++ b/tests/MMS/spatial/fci/runtest @@ -53,7 +53,7 @@ for nslice in nslices: "hermitespline", "lagrange4pt", "bilinear", - # "monotonichermitespline", + "monotonichermitespline", ]: error_2[nslice] = [] error_inf[nslice] = [] @@ -97,7 +97,7 @@ for nslice in nslices: nslice, yperiodic, method_orders[nslice]["name"], - 2 if conf.has["petsc"] and method == "hermitespline" else 1, + 2 if conf.has["petsc"] and "hermitespline" in method else 1, ) args += f" mesh:paralleltransform:xzinterpolation:type={method}" From cb068ea5ac27a54e95e9be7ad8a27b3829a58ca3 Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 15 Jan 2025 13:45:20 +0100 Subject: [PATCH 150/242] Fix position of maybe_unused for old gcc --- src/mesh/boundary_standard.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mesh/boundary_standard.cxx b/src/mesh/boundary_standard.cxx index 367f6b7d54..8c6c0d19fc 100644 --- a/src/mesh/boundary_standard.cxx +++ b/src/mesh/boundary_standard.cxx @@ -1593,7 +1593,7 @@ BoundaryOp* BoundaryNeumann_NonOrthogonal::clone(BoundaryRegion* region, return new BoundaryNeumann_NonOrthogonal(region); } -void BoundaryNeumann_NonOrthogonal::apply(Field2D& [[maybe_unused]] f) { +void BoundaryNeumann_NonOrthogonal::apply([[maybe_unused]] Field2D& f) { #if not(BOUT_USE_METRIC_3D) Mesh* mesh = bndry->localmesh; ASSERT1(mesh == f.getMesh()); @@ -1728,7 +1728,7 @@ void BoundaryNeumann_NonOrthogonal::apply(Field3D& f) { void BoundaryNeumann::apply(Field2D & f) { BoundaryNeumann::apply(f, 0.); } - void BoundaryNeumann::apply(Field2D& [[maybe_unused]] f, BoutReal t) { + void BoundaryNeumann::apply([[maybe_unused]] Field2D& f, BoutReal t) { // Set (at 2nd order / 3rd order) the value at the mid-point between // the guard cell and the grid cell to be val // N.B. First guard cells (closest to the grid) is 2nd order, while From d7d7c91338816f4ef6fd5ffce38e811b48a09887 Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 15 Jan 2025 13:45:48 +0100 Subject: [PATCH 151/242] Ensure PETSC_SUCCESS is defined --- src/invert/laplace/impls/petsc/petsc_laplace.cxx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/invert/laplace/impls/petsc/petsc_laplace.cxx b/src/invert/laplace/impls/petsc/petsc_laplace.cxx index d0d68bee52..c2b62dc570 100644 --- a/src/invert/laplace/impls/petsc/petsc_laplace.cxx +++ b/src/invert/laplace/impls/petsc/petsc_laplace.cxx @@ -29,6 +29,7 @@ #if BOUT_HAS_PETSC #include "petsc_laplace.hxx" +#include "petscsys.h" #include #include From a05201bebe94884dfae9b916b63b0446cd40889b Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 15 Jan 2025 14:06:44 +0100 Subject: [PATCH 152/242] Revert "Ensure PETSC_SUCCESS is defined" This reverts commit d7d7c91338816f4ef6fd5ffce38e811b48a09887. --- src/invert/laplace/impls/petsc/petsc_laplace.cxx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/invert/laplace/impls/petsc/petsc_laplace.cxx b/src/invert/laplace/impls/petsc/petsc_laplace.cxx index c2b62dc570..d0d68bee52 100644 --- a/src/invert/laplace/impls/petsc/petsc_laplace.cxx +++ b/src/invert/laplace/impls/petsc/petsc_laplace.cxx @@ -29,7 +29,6 @@ #if BOUT_HAS_PETSC #include "petsc_laplace.hxx" -#include "petscsys.h" #include #include From 7d73c5c771e1fac9c50b4babcc73ea565147fec8 Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 15 Jan 2025 14:07:06 +0100 Subject: [PATCH 153/242] Define PETSC_SUCCESS for old petsc versions --- include/bout/petsclib.hxx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/bout/petsclib.hxx b/include/bout/petsclib.hxx index 2008671286..aa6f874f11 100644 --- a/include/bout/petsclib.hxx +++ b/include/bout/petsclib.hxx @@ -156,6 +156,10 @@ private: #endif // PETSC_VERSION_GE +#if ! PETSC_VERSION_GE(3, 19, 0) +#define PETSC_SUCCESS ((PetscErrorCode)0) +#endif + #else // BOUT_HAS_PETSC #include "bout/unused.hxx" From ebf2d07d071b4a1a2b99c78f14cb0e7022593c96 Mon Sep 17 00:00:00 2001 From: David Bold Date: Thu, 16 Jan 2025 09:07:51 +0100 Subject: [PATCH 154/242] Set region for lagrange4pt --- src/mesh/interpolation/lagrange_4pt_xz.cxx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mesh/interpolation/lagrange_4pt_xz.cxx b/src/mesh/interpolation/lagrange_4pt_xz.cxx index 8fa201ba72..16368299a0 100644 --- a/src/mesh/interpolation/lagrange_4pt_xz.cxx +++ b/src/mesh/interpolation/lagrange_4pt_xz.cxx @@ -132,7 +132,11 @@ Field3D XZLagrange4pt::interpolate(const Field3D& f, const std::string& region) // Then in X f_interp(x, y_next, z) = lagrange_4pt(xvals, t_x(x, y, z)); + ASSERT2(std::isfinite(f_interp(x, y_next, z))); + } + const auto region2 = y_offset != 0 ? fmt::format("RGN_YPAR_{:+d}", y_offset) : region; + f_interp.setRegion(region2); return f_interp; } From ae1fcfe067c4a499ec957107c2c470b53088c93a Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 20 Jan 2025 16:28:15 +0100 Subject: [PATCH 155/242] Fix split parallel slices --- src/field/field3d.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index 2f97e8d02b..33980e22df 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -160,7 +160,7 @@ void Field3D::splitParallelSlices() { ydown_fields.emplace_back(fieldmesh); if (isFci()) { yup_fields[i].setRegion(fmt::format("RGN_YPAR_{:+d}", i + 1)); - yup_fields[i].setRegion(fmt::format("RGN_YPAR_{:+d}", -i - 1)); + ydown_fields[i].setRegion(fmt::format("RGN_YPAR_{:+d}", -i - 1)); } } } From 8b7d97d01cabd5b198ff1b784d4833652c86c30b Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 20 Jan 2025 16:28:31 +0100 Subject: [PATCH 156/242] Add option to split + allocate --- include/bout/field3d.hxx | 2 ++ src/field/field3d.cxx | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index eaae59a7f9..bc87c6ba3f 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -237,6 +237,8 @@ public: */ void splitParallelSlices(); + void splitParallelSlicesAndAllocate(); + /*! * Clear the parallel slices, yup and ydown */ diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index 33980e22df..1d323ade29 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -164,6 +164,14 @@ void Field3D::splitParallelSlices() { } } } +void Field3D::splitParallelSlicesAndAllocate() { + splitParallelSlices(); + allocate(); + for (int i = 0; i < fieldmesh->ystart; ++i) { + yup_fields[i].allocate(); + ydown_fields[i].allocate(); + } +} void Field3D::clearParallelSlices() { TRACE("Field3D::clearParallelSlices"); From ad56d8e77ea5c8924a9e3bd0f18fdbdbede76f7e Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 20 Jan 2025 16:29:05 +0100 Subject: [PATCH 157/242] fix parallel neumann BC --- include/bout/parallel_boundary_region.hxx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/bout/parallel_boundary_region.hxx b/include/bout/parallel_boundary_region.hxx index 7e4e340c8a..2cbb0977e7 100644 --- a/include/bout/parallel_boundary_region.hxx +++ b/include/bout/parallel_boundary_region.hxx @@ -174,7 +174,7 @@ public: // neumann_o1 is actually o2 if we would use an appropriate one-sided stencil. // But in general we do not, and thus for normal C2 stencils, this is 1st order. void neumann_o1(Field3D& f, BoutReal value) const { - ITER() { getAt(f, i) = ythis(f) + value; } + ITER() { getAt(f, i) = ythis(f) + value * (i + 1); } } // NB: value needs to be scaled by dy @@ -183,14 +183,14 @@ public: if (valid() < 1) { return neumann_o1(f, value); } - ITER() { getAt(f, i) = yprev(f) + 2 * value; } + ITER() { getAt(f, i) = yprev(f) + (2 + i) * value; } } // NB: value needs to be scaled by dy void neumann_o3(Field3D& f, BoutReal value) const { ASSERT3(valid() >= 0); if (valid() < 1) { - return neumann_o1(f, value); + return neumann_o2(f, value); } ITER() { getAt(f, i) = parallel_stencil::neumann_o3(i + 1 - length(), value, i + 1, ythis(f), From 2a4834138371eca80ba6f4442c19da18388e138d Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 20 Jan 2025 16:30:25 +0100 Subject: [PATCH 158/242] set name for parallel component We have the info, so might as well store it --- src/mesh/parallel/fci.cxx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mesh/parallel/fci.cxx b/src/mesh/parallel/fci.cxx index 07a7a6490b..cf6b8cb986 100644 --- a/src/mesh/parallel/fci.cxx +++ b/src/mesh/parallel/fci.cxx @@ -97,6 +97,7 @@ bool load_parallel_metric_component(std::string name, Field3D& component, int of auto& pcom = component.ynext(offset); pcom.allocate(); pcom.setRegion(fmt::format("RGN_YPAR_{:+d}", offset)); + pcom.name = name; BOUT_FOR(i, component.getRegion("RGN_NOBNDRY")) { pcom[i.yp(offset)] = tmp[i]; } From f73813be1fb6cda77d42a98e4c7540be2f281fb4 Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 20 Jan 2025 16:30:54 +0100 Subject: [PATCH 159/242] Add limitFree Taken from hermes-3, adopted for higher order --- include/bout/parallel_boundary_region.hxx | 27 ++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/include/bout/parallel_boundary_region.hxx b/include/bout/parallel_boundary_region.hxx index 2cbb0977e7..5c0df00be6 100644 --- a/include/bout/parallel_boundary_region.hxx +++ b/include/bout/parallel_boundary_region.hxx @@ -44,7 +44,21 @@ using IndicesVec = std::vector; using IndicesIter = IndicesVec::iterator; using IndicesIterConst = IndicesVec::const_iterator; -//} +inline BoutReal limitFreeScale(BoutReal fm, BoutReal fc) { + if (fm < fc) { + return 1; // Neumann rather than increasing into boundary + } + if (fm < 1e-10) { + return 1; // Low / no density condition + } + BoutReal fp = fc / fm; +#if CHECKLEVEL >= 2 + if (!std::isfinite(fp)) { + throw BoutException("SheathBoundaryParallel limitFree: {}, {} -> {}", fm, fc, fp); + } +#endif + return fp; +} template class BoundaryRegionParIterBase { @@ -198,6 +212,17 @@ public: } } + // extrapolate into the boundary using only monotonic decreasing values. + // f needs to be positive + void limitFree(Field3D& f) const { + const auto fac = valid() > 0 ? limitFreeScale(yprev(f), ythis(f)) : 1; + auto val = ythis(f); + ITER() { + val *= fac; + getAt(f, i) = val; + } + } + template BoutReal& getAt(Field3D& f, int off) const { if constexpr (check) { From ff7525db692dee68d90f90ac5a93f1eeba630336 Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 20 Jan 2025 16:31:18 +0100 Subject: [PATCH 160/242] add setAll to parallel BC --- include/bout/parallel_boundary_region.hxx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/bout/parallel_boundary_region.hxx b/include/bout/parallel_boundary_region.hxx index 5c0df00be6..f808296edb 100644 --- a/include/bout/parallel_boundary_region.hxx +++ b/include/bout/parallel_boundary_region.hxx @@ -223,6 +223,12 @@ public: } } + void setAll(Field3D& f, const BoutReal val) const { + for (int i = -localmesh->ystart; i <= localmesh->ystart; ++i) { + f.ynext(i)[ind().yp(i)] = val; + } + } + template BoutReal& getAt(Field3D& f, int off) const { if constexpr (check) { From 1c44f98bd6925f9a3393e3cb73fbc194f27826bc Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 21 Jan 2025 10:43:58 +0100 Subject: [PATCH 161/242] Add monotonic check also to other code branches --- src/mesh/interpolation/hermite_spline_xz.cxx | 27 +++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/mesh/interpolation/hermite_spline_xz.cxx b/src/mesh/interpolation/hermite_spline_xz.cxx index 85786d5381..70c311c08f 100644 --- a/src/mesh/interpolation/hermite_spline_xz.cxx +++ b/src/mesh/interpolation/hermite_spline_xz.cxx @@ -35,7 +35,7 @@ class IndConverter { xstart(mesh->xstart), ystart(mesh->ystart), zstart(0), lnx(mesh->LocalNx - 2 * xstart), lny(mesh->LocalNy - 2 * ystart), lnz(mesh->LocalNz - 2 * zstart) {} - // ix and iy are global indices + // ix and iz are global indices // iy is local int fromMeshToGlobal(int ix, int iy, int iz) { const int xstart = mesh->xstart; @@ -382,6 +382,19 @@ Field3D XZHermiteSplineBase::interpolate(const Field3D& f, VecGetArrayRead(result, &cptr); BOUT_FOR(i, f.getRegion(region2)) { f_interp[i] = cptr[int(i)]; + if constexpr (monotonic) { + const auto corners = {gf[g3dinds[i][0]], gf[g3dinds[i][1]], gf[g3dinds[i][2]], + gf[g3dinds[i][3]]}; + const auto minmax = std::minmax(corners); + if (f_interp[iyp] < minmax.first) { + f_interp[iyp] = minmax.first; + } else { + if (f_interp[iyp] > minmax.second) { + f_interp[iyp] = minmax.second; + } + } + } + ASSERT2(std::isfinite(cptr[int(i)])); } VecRestoreArrayRead(result, &cptr); @@ -397,6 +410,18 @@ Field3D XZHermiteSplineBase::interpolate(const Field3D& f, f_interp[iyp] += newWeights[w * 4 + 2][i] * f[ic.zp().xp(w - 1)]; f_interp[iyp] += newWeights[w * 4 + 3][i] * f[ic.zp(2).xp(w - 1)]; } + if constexpr (monotonic) { + const auto corners = {gf[g3dinds[i][0]], gf[g3dinds[i][1]], gf[g3dinds[i][2]], + gf[g3dinds[i][3]]}; + const auto minmax = std::minmax(corners); + if (f_interp[iyp] < minmax.first) { + f_interp[iyp] = minmax.first; + } else { + if (f_interp[iyp] > minmax.second) { + f_interp[iyp] = minmax.second; + } + } + } } #endif #else From 42c095874b30dad76aec6dab9777a1980b62583f Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 21 Jan 2025 13:31:14 +0100 Subject: [PATCH 162/242] use lower_bound instead of find lower_bound takes into account the data is sorted --- src/mesh/parallel/fci_comm.hxx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/mesh/parallel/fci_comm.hxx b/src/mesh/parallel/fci_comm.hxx index aa3b5ecfb5..a847700548 100644 --- a/src/mesh/parallel/fci_comm.hxx +++ b/src/mesh/parallel/fci_comm.hxx @@ -138,10 +138,11 @@ public: ASSERT3(piz.proc == 0); const auto proc = piy.proc * g2lx.npe + pix.proc; const auto& vec = toGet[proc]; - auto it = - std::find(vec.begin(), vec.end(), xyzl.convert(pix.ind, piy.ind, piz.ind).ind); + const auto tofind = xyzl.convert(pix.ind, piy.ind, piz.ind).ind; + auto it = std::lower_bound(vec.begin(), vec.end(), tofind); ASSERT3(it != vec.end()); - mapping[id] = it - vec.begin() + offsets[proc]; + ASSERT3(*it == tofind); + mapping[id] = std::distance(vec.begin(), it) + offsets[proc]; } is_setup = true; } From 21d19850f8529b8cf61caf87e99f3f155a294b08 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 21 Jan 2025 13:31:27 +0100 Subject: [PATCH 163/242] Do not shadow mapping --- src/mesh/parallel/fci_comm.hxx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/mesh/parallel/fci_comm.hxx b/src/mesh/parallel/fci_comm.hxx index a847700548..5250fb99d3 100644 --- a/src/mesh/parallel/fci_comm.hxx +++ b/src/mesh/parallel/fci_comm.hxx @@ -129,7 +129,6 @@ public: } offsets.push_back(offset); } - std::map mapping; for (const auto id : ids) { IndG3D gind{id, g2ly.globalwith, g2lz.globalwith}; const auto pix = g2lx.convert(gind.x()); From 4f39c1dd336035a44bc8edc928448ba4f45d57a6 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 21 Jan 2025 13:32:01 +0100 Subject: [PATCH 164/242] Ensure setup has been called --- src/mesh/parallel/fci_comm.hxx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mesh/parallel/fci_comm.hxx b/src/mesh/parallel/fci_comm.hxx index 5250fb99d3..6c83d58f63 100644 --- a/src/mesh/parallel/fci_comm.hxx +++ b/src/mesh/parallel/fci_comm.hxx @@ -212,6 +212,7 @@ private: int sendBufferSize{0}; MPI_Comm comm; std::vector communicate_data(const Field3D& f) { + ASSERT2(is_setup); ASSERT2(f.getMesh() == mesh); std::vector data(offsets.back()); std::vector sendBuffer(sendBufferSize); From cabdc4cdaf713aed543f170c5afda9aac9463fc1 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 21 Jan 2025 13:32:12 +0100 Subject: [PATCH 165/242] Use pointer to data --- src/mesh/parallel/fci_comm.hxx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/mesh/parallel/fci_comm.hxx b/src/mesh/parallel/fci_comm.hxx index 6c83d58f63..d44eea76ef 100644 --- a/src/mesh/parallel/fci_comm.hxx +++ b/src/mesh/parallel/fci_comm.hxx @@ -176,13 +176,14 @@ private: auto ret = MPI_Waitany(reqs.size(), &reqs[0], &ind, MPI_STATUS_IGNORE); ASSERT0(ret == MPI_SUCCESS); ASSERT3(ind != MPI_UNDEFINED); + ASSERT2(static_cast(ind) < toSend.size()); toSend[ind].resize(toSendSizes[ind]); - ret = MPI_Irecv(static_cast(&toSend[ind]), toSend[ind].size(), MPI_INT, ind, - 666 * 666 + ind, comm, &reqs[ind]); + ret = MPI_Irecv(static_cast(&toSend[ind][0]), toSend[ind].size(), MPI_INT, + ind, 666 * 666 + ind, comm, &reqs[ind]); ASSERT0(ret == MPI_SUCCESS); } for (size_t proc = 0; proc < toGet.size(); ++proc) { - const auto ret = MPI_Send(static_cast(&toGet[proc]), toGet[proc].size(), + const auto ret = MPI_Send(static_cast(&toGet[proc][0]), toGet[proc].size(), MPI_INT, proc, 666 * 666 + proc, comm); ASSERT0(ret == MPI_SUCCESS); } From aa7938dff460874b49be12663368279f23870802 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 21 Jan 2025 13:32:36 +0100 Subject: [PATCH 166/242] Call setup before communicator is used --- src/mesh/interpolation/hermite_spline_xz.cxx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mesh/interpolation/hermite_spline_xz.cxx b/src/mesh/interpolation/hermite_spline_xz.cxx index 70c311c08f..6932e5d7e4 100644 --- a/src/mesh/interpolation/hermite_spline_xz.cxx +++ b/src/mesh/interpolation/hermite_spline_xz.cxx @@ -311,6 +311,9 @@ void XZHermiteSplineBase::calcWeights(const Field3D& delta_x, g3dinds[i] = {gind.ind, gind.xp(1).ind, gind.zp(1).ind, gind.xp(1).zp(1).ind}; } } + if constexpr (monotonic) { + gf3daccess->setup(); + } #ifdef HS_USE_PETSC MatAssemblyBegin(petscWeights, MAT_FINAL_ASSEMBLY); MatAssemblyEnd(petscWeights, MAT_FINAL_ASSEMBLY); From d1dee59a984a2b0e452713579f0444255bf885d0 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 21 Jan 2025 13:34:36 +0100 Subject: [PATCH 167/242] Deduplicate code Ensures we all ways check for monotonicity --- src/mesh/interpolation/hermite_spline_xz.cxx | 61 +++++++------------- 1 file changed, 21 insertions(+), 40 deletions(-) diff --git a/src/mesh/interpolation/hermite_spline_xz.cxx b/src/mesh/interpolation/hermite_spline_xz.cxx index 6932e5d7e4..b8b30e68d8 100644 --- a/src/mesh/interpolation/hermite_spline_xz.cxx +++ b/src/mesh/interpolation/hermite_spline_xz.cxx @@ -374,8 +374,12 @@ Field3D XZHermiteSplineBase::interpolate(const Field3D& f, const auto region2 = y_offset != 0 ? fmt::format("RGN_YPAR_{:+d}", y_offset) : region; -#if USE_NEW_WEIGHTS -#ifdef HS_USE_PETSC + std::unique_ptr gf; + if constexpr (monotonic) { + gf = gf3daccess->communicate_asPtr(f); + } + +#if USE_NEW_WEIGHTS and defined(HS_USE_PETSC) BoutReal* ptr; const BoutReal* cptr; VecGetArray(rhs, &ptr); @@ -386,22 +390,9 @@ Field3D XZHermiteSplineBase::interpolate(const Field3D& f, BOUT_FOR(i, f.getRegion(region2)) { f_interp[i] = cptr[int(i)]; if constexpr (monotonic) { - const auto corners = {gf[g3dinds[i][0]], gf[g3dinds[i][1]], gf[g3dinds[i][2]], - gf[g3dinds[i][3]]}; - const auto minmax = std::minmax(corners); - if (f_interp[iyp] < minmax.first) { - f_interp[iyp] = minmax.first; - } else { - if (f_interp[iyp] > minmax.second) { - f_interp[iyp] = minmax.second; - } - } - } - - ASSERT2(std::isfinite(cptr[int(i)])); - } - VecRestoreArrayRead(result, &cptr); -#else + const auto iyp = i; + const auto i = iyp.ym(y_offset); +#elif USE_NEW_WEIGHTS // No Petsc BOUT_FOR(i, getRegion(region)) { auto ic = i_corner[i]; auto iyp = i.yp(y_offset); @@ -414,20 +405,7 @@ Field3D XZHermiteSplineBase::interpolate(const Field3D& f, f_interp[iyp] += newWeights[w * 4 + 3][i] * f[ic.zp(2).xp(w - 1)]; } if constexpr (monotonic) { - const auto corners = {gf[g3dinds[i][0]], gf[g3dinds[i][1]], gf[g3dinds[i][2]], - gf[g3dinds[i][3]]}; - const auto minmax = std::minmax(corners); - if (f_interp[iyp] < minmax.first) { - f_interp[iyp] = minmax.first; - } else { - if (f_interp[iyp] > minmax.second) { - f_interp[iyp] = minmax.second; - } - } - } - } -#endif -#else +#else // Legacy interpolation // Derivatives are used for tension and need to be on dimensionless // coordinates @@ -438,11 +416,6 @@ Field3D XZHermiteSplineBase::interpolate(const Field3D& f, Field3D fz = bout::derivatives::index::DDZ(f, CELL_DEFAULT, "DEFAULT", region2); Field3D fxz = bout::derivatives::index::DDZ(fx, CELL_DEFAULT, "DEFAULT", region2); - std::unique_ptr g3d; - if constexpr (monotonic) { - gf = gf3daccess.communicate_asPtr(f); - } - BOUT_FOR(i, getRegion(region)) { const auto iyp = i.yp(y_offset); @@ -472,8 +445,9 @@ Field3D XZHermiteSplineBase::interpolate(const Field3D& f, +f_z * h00_z[i] + f_zp1 * h01_z[i] + fz_z * h10_z[i] + fz_zp1 * h11_z[i]; if constexpr (monotonic) { - const auto corners = {gf[g3dinds[i][0]], gf[g3dinds[i][1]], gf[g3dinds[i][2]], - gf[g3dinds[i][3]]}; +#endif + const auto corners = {(*gf)[IndG3D(g3dinds[i][0])], (*gf)[IndG3D(g3dinds[i][1])], + (*gf)[IndG3D(g3dinds[i][2])], (*gf)[IndG3D(g3dinds[i][3])]}; const auto minmax = std::minmax(corners); if (f_interp[iyp] < minmax.first) { f_interp[iyp] = minmax.first; @@ -483,7 +457,14 @@ Field3D XZHermiteSplineBase::interpolate(const Field3D& f, } } } - +#if USE_NEW_WEIGHTS and defined(HS_USE_PETSC) + ASSERT2(std::isfinite(cptr[int(i)])); + } + VecRestoreArrayRead(result, &cptr); +#elif USE_NEW_WEIGHTS + ASSERT2(std::isfinite(f_interp[iyp])); + } +#else ASSERT2(std::isfinite(f_interp[iyp]) || i.x() < localmesh->xstart || i.x() > localmesh->xend); } From 58183272043740f43b6079d781ee9e27370f7a35 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 21 Jan 2025 14:30:05 +0100 Subject: [PATCH 168/242] Fix tags for comm Tags were different for sender and receiver --- src/mesh/parallel/fci_comm.hxx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/mesh/parallel/fci_comm.hxx b/src/mesh/parallel/fci_comm.hxx index d44eea76ef..c0227f4773 100644 --- a/src/mesh/parallel/fci_comm.hxx +++ b/src/mesh/parallel/fci_comm.hxx @@ -161,14 +161,14 @@ private: std::vector reqs(toSend.size()); for (size_t proc = 0; proc < toGet.size(); ++proc) { auto ret = MPI_Irecv(static_cast(&toSendSizes[proc]), 1, MPI_INT, proc, - 666 + proc, comm, &reqs[proc]); + 666, comm, &reqs[proc]); ASSERT0(ret == MPI_SUCCESS); } for (size_t proc = 0; proc < toGet.size(); ++proc) { toGetSizes[proc] = toGet[proc].size(); sendBufferSize += toGetSizes[proc]; auto ret = MPI_Send(static_cast(&toGetSizes[proc]), 1, MPI_INT, proc, - 666 + proc, comm); + 666, comm); ASSERT0(ret == MPI_SUCCESS); } for ([[maybe_unused]] auto dummy : reqs) { @@ -179,12 +179,12 @@ private: ASSERT2(static_cast(ind) < toSend.size()); toSend[ind].resize(toSendSizes[ind]); ret = MPI_Irecv(static_cast(&toSend[ind][0]), toSend[ind].size(), MPI_INT, - ind, 666 * 666 + ind, comm, &reqs[ind]); + ind, 666 * 666, comm, &reqs[ind]); ASSERT0(ret == MPI_SUCCESS); } for (size_t proc = 0; proc < toGet.size(); ++proc) { const auto ret = MPI_Send(static_cast(&toGet[proc][0]), toGet[proc].size(), - MPI_INT, proc, 666 * 666 + proc, comm); + MPI_INT, proc, 666 * 666, comm); ASSERT0(ret == MPI_SUCCESS); } for ([[maybe_unused]] auto dummy : reqs) { @@ -220,7 +220,7 @@ private: std::vector reqs(toSend.size()); for (size_t proc = 0; proc < toGet.size(); ++proc) { auto ret = MPI_Irecv(static_cast(&data[proc]), toGet[proc].size(), - MPI_DOUBLE, proc, 666 + proc, comm, &reqs[proc]); + MPI_DOUBLE, proc, 666, comm, &reqs[proc]); ASSERT0(ret == MPI_SUCCESS); } int cnt = 0; @@ -229,7 +229,7 @@ private: for (auto i : toSend[proc]) { sendBuffer[cnt++] = f[Ind3D(i)]; } - auto ret = MPI_Send(start, toSend[proc].size(), MPI_DOUBLE, proc, 666 + proc, comm); + auto ret = MPI_Send(start, toSend[proc].size(), MPI_DOUBLE, proc, 666, comm); ASSERT0(ret == MPI_SUCCESS); } for ([[maybe_unused]] auto dummy : reqs) { From aa2d8b6ea7f1c22a0cb0f96a32fb67b61a4d446c Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 21 Jan 2025 14:39:43 +0100 Subject: [PATCH 169/242] Use pointer instead of std::vector --- src/mesh/parallel/fci_comm.hxx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mesh/parallel/fci_comm.hxx b/src/mesh/parallel/fci_comm.hxx index c0227f4773..257639bb32 100644 --- a/src/mesh/parallel/fci_comm.hxx +++ b/src/mesh/parallel/fci_comm.hxx @@ -216,7 +216,8 @@ private: ASSERT2(is_setup); ASSERT2(f.getMesh() == mesh); std::vector data(offsets.back()); - std::vector sendBuffer(sendBufferSize); + //std::vector sendBuffer(sendBufferSize); + BoutReal* sendBuffer = new BoutReal[sendBufferSize]; std::vector reqs(toSend.size()); for (size_t proc = 0; proc < toGet.size(); ++proc) { auto ret = MPI_Irecv(static_cast(&data[proc]), toGet[proc].size(), @@ -238,6 +239,7 @@ private: ASSERT0(ret == MPI_SUCCESS); ASSERT3(ind != MPI_UNDEFINED); } + delete[] sendBuffer; return data; } }; From 007fed0936d0abe3884c16d1b4273074a2b11efc Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 21 Jan 2025 14:43:12 +0100 Subject: [PATCH 170/242] Do not reuse requests if the array is still in use Otherwise mpi might wait for the wrong request. --- src/mesh/parallel/fci_comm.hxx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mesh/parallel/fci_comm.hxx b/src/mesh/parallel/fci_comm.hxx index 257639bb32..ec34d622a6 100644 --- a/src/mesh/parallel/fci_comm.hxx +++ b/src/mesh/parallel/fci_comm.hxx @@ -171,6 +171,7 @@ private: 666, comm); ASSERT0(ret == MPI_SUCCESS); } + std::vector reqs2(toSend.size()); for ([[maybe_unused]] auto dummy : reqs) { int ind{0}; auto ret = MPI_Waitany(reqs.size(), &reqs[0], &ind, MPI_STATUS_IGNORE); @@ -179,7 +180,7 @@ private: ASSERT2(static_cast(ind) < toSend.size()); toSend[ind].resize(toSendSizes[ind]); ret = MPI_Irecv(static_cast(&toSend[ind][0]), toSend[ind].size(), MPI_INT, - ind, 666 * 666, comm, &reqs[ind]); + ind, 666 * 666, comm, &reqs2[ind]); ASSERT0(ret == MPI_SUCCESS); } for (size_t proc = 0; proc < toGet.size(); ++proc) { @@ -189,7 +190,7 @@ private: } for ([[maybe_unused]] auto dummy : reqs) { int ind{0}; - const auto ret = MPI_Waitany(reqs.size(), &reqs[0], &ind, MPI_STATUS_IGNORE); + const auto ret = MPI_Waitany(reqs.size(), &reqs2[0], &ind, MPI_STATUS_IGNORE); ASSERT0(ret == MPI_SUCCESS); ASSERT3(ind != MPI_UNDEFINED); } From c40110b31ceb2cd81c11dbb389853f70ee7c491c Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 22 Jan 2025 10:27:11 +0100 Subject: [PATCH 171/242] rename offset to getOffsets to avoid confusion whether the offsets are for sending or receiving --- src/mesh/parallel/fci_comm.hxx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/mesh/parallel/fci_comm.hxx b/src/mesh/parallel/fci_comm.hxx index ec34d622a6..f079385dc1 100644 --- a/src/mesh/parallel/fci_comm.hxx +++ b/src/mesh/parallel/fci_comm.hxx @@ -124,10 +124,10 @@ public: { int offset = 0; for (auto get : toGet) { - offsets.push_back(offset); + getOffsets.push_back(offset); offset += get.size(); } - offsets.push_back(offset); + getOffsets.push_back(offset); } for (const auto id : ids) { IndG3D gind{id, g2ly.globalwith, g2lz.globalwith}; @@ -141,7 +141,7 @@ public: auto it = std::lower_bound(vec.begin(), vec.end(), tofind); ASSERT3(it != vec.end()); ASSERT3(*it == tofind); - mapping[id] = std::distance(vec.begin(), it) + offsets[proc]; + mapping[id] = std::distance(vec.begin(), it) + getOffsets[proc]; } is_setup = true; } @@ -155,9 +155,8 @@ public: private: void commCommLists() { toSend.resize(toGet.size()); - std::vector toGetSizes(toGet.size()); - std::vector toSendSizes(toSend.size()); - //const int thisproc = mesh->getYProcIndex() * g2lx.npe + mesh->getXProcIndex(); + std::vector toGetSizes(toGet.size(), -1); + std::vector toSendSizes(toSend.size(), -1); std::vector reqs(toSend.size()); for (size_t proc = 0; proc < toGet.size(); ++proc) { auto ret = MPI_Irecv(static_cast(&toSendSizes[proc]), 1, MPI_INT, proc, @@ -210,15 +209,15 @@ public: private: std::vector> toGet; std::vector> toSend; - std::vector offsets; + std::vector getOffsets; int sendBufferSize{0}; MPI_Comm comm; std::vector communicate_data(const Field3D& f) { ASSERT2(is_setup); ASSERT2(f.getMesh() == mesh); - std::vector data(offsets.back()); //std::vector sendBuffer(sendBufferSize); BoutReal* sendBuffer = new BoutReal[sendBufferSize]; + std::vector data(getOffsets.back()); std::vector reqs(toSend.size()); for (size_t proc = 0; proc < toGet.size(); ++proc) { auto ret = MPI_Irecv(static_cast(&data[proc]), toGet[proc].size(), From 2a10ccd6395adaf2d3e482167ad17cc946138f41 Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 22 Jan 2025 10:28:04 +0100 Subject: [PATCH 172/242] Fix: mixup of sending / receiving size --- src/mesh/parallel/fci_comm.hxx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/mesh/parallel/fci_comm.hxx b/src/mesh/parallel/fci_comm.hxx index f079385dc1..0926f5c415 100644 --- a/src/mesh/parallel/fci_comm.hxx +++ b/src/mesh/parallel/fci_comm.hxx @@ -165,7 +165,6 @@ private: } for (size_t proc = 0; proc < toGet.size(); ++proc) { toGetSizes[proc] = toGet[proc].size(); - sendBufferSize += toGetSizes[proc]; auto ret = MPI_Send(static_cast(&toGetSizes[proc]), 1, MPI_INT, proc, 666, comm); ASSERT0(ret == MPI_SUCCESS); @@ -177,6 +176,8 @@ private: ASSERT0(ret == MPI_SUCCESS); ASSERT3(ind != MPI_UNDEFINED); ASSERT2(static_cast(ind) < toSend.size()); + ASSERT3(toSendSizes[ind] >= 0); + sendBufferSize += toSendSizes[ind]; toSend[ind].resize(toSendSizes[ind]); ret = MPI_Irecv(static_cast(&toSend[ind][0]), toSend[ind].size(), MPI_INT, ind, 666 * 666, comm, &reqs2[ind]); @@ -215,9 +216,8 @@ private: std::vector communicate_data(const Field3D& f) { ASSERT2(is_setup); ASSERT2(f.getMesh() == mesh); - //std::vector sendBuffer(sendBufferSize); - BoutReal* sendBuffer = new BoutReal[sendBufferSize]; std::vector data(getOffsets.back()); + std::vector sendBuffer(sendBufferSize); std::vector reqs(toSend.size()); for (size_t proc = 0; proc < toGet.size(); ++proc) { auto ret = MPI_Irecv(static_cast(&data[proc]), toGet[proc].size(), @@ -239,7 +239,6 @@ private: ASSERT0(ret == MPI_SUCCESS); ASSERT3(ind != MPI_UNDEFINED); } - delete[] sendBuffer; return data; } }; From 296cc15747eb8c7c9737f24af9e481b3cd45ca03 Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 22 Jan 2025 10:28:43 +0100 Subject: [PATCH 173/242] Fix receive data offset --- src/mesh/parallel/fci_comm.hxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mesh/parallel/fci_comm.hxx b/src/mesh/parallel/fci_comm.hxx index 0926f5c415..8db9d8ef14 100644 --- a/src/mesh/parallel/fci_comm.hxx +++ b/src/mesh/parallel/fci_comm.hxx @@ -220,8 +220,8 @@ private: std::vector sendBuffer(sendBufferSize); std::vector reqs(toSend.size()); for (size_t proc = 0; proc < toGet.size(); ++proc) { - auto ret = MPI_Irecv(static_cast(&data[proc]), toGet[proc].size(), - MPI_DOUBLE, proc, 666, comm, &reqs[proc]); + auto ret = MPI_Irecv(static_cast(&data[getOffsets[proc]]), + toGet[proc].size(), MPI_DOUBLE, proc, 666, comm, &reqs[proc]); ASSERT0(ret == MPI_SUCCESS); } int cnt = 0; From 31d7702dd65fffff38ab957bc0d687b23c480fb9 Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 22 Jan 2025 10:30:47 +0100 Subject: [PATCH 174/242] Add check to ensure the proc layout is as expected --- src/mesh/parallel/fci_comm.hxx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/mesh/parallel/fci_comm.hxx b/src/mesh/parallel/fci_comm.hxx index 8db9d8ef14..30f7df76ee 100644 --- a/src/mesh/parallel/fci_comm.hxx +++ b/src/mesh/parallel/fci_comm.hxx @@ -157,6 +157,13 @@ private: toSend.resize(toGet.size()); std::vector toGetSizes(toGet.size(), -1); std::vector toSendSizes(toSend.size(), -1); +#if CHECK > 3 + { + int thisproc; + MPI_Comm_rank(comm, thisproc); + assert(thisproc == mesh->getYProcIndex() * g2lx.npe + mesh->getXProcIndex()); + } +#endif std::vector reqs(toSend.size()); for (size_t proc = 0; proc < toGet.size(); ++proc) { auto ret = MPI_Irecv(static_cast(&toSendSizes[proc]), 1, MPI_INT, proc, From 71dd37c6138aa97267227b63bec2d76531d68f1f Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 22 Jan 2025 10:31:54 +0100 Subject: [PATCH 175/242] clang-format --- src/mesh/parallel/fci_comm.hxx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mesh/parallel/fci_comm.hxx b/src/mesh/parallel/fci_comm.hxx index 30f7df76ee..bd4b955f12 100644 --- a/src/mesh/parallel/fci_comm.hxx +++ b/src/mesh/parallel/fci_comm.hxx @@ -166,14 +166,14 @@ private: #endif std::vector reqs(toSend.size()); for (size_t proc = 0; proc < toGet.size(); ++proc) { - auto ret = MPI_Irecv(static_cast(&toSendSizes[proc]), 1, MPI_INT, proc, - 666, comm, &reqs[proc]); + auto ret = MPI_Irecv(static_cast(&toSendSizes[proc]), 1, MPI_INT, proc, 666, + comm, &reqs[proc]); ASSERT0(ret == MPI_SUCCESS); } for (size_t proc = 0; proc < toGet.size(); ++proc) { toGetSizes[proc] = toGet[proc].size(); - auto ret = MPI_Send(static_cast(&toGetSizes[proc]), 1, MPI_INT, proc, - 666, comm); + auto ret = + MPI_Send(static_cast(&toGetSizes[proc]), 1, MPI_INT, proc, 666, comm); ASSERT0(ret == MPI_SUCCESS); } std::vector reqs2(toSend.size()); From 90a7f4ffcaf7ddbd3f107bab0381f86d8886b50e Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 22 Jan 2025 10:40:35 +0100 Subject: [PATCH 176/242] Use BOUT++ assert --- src/mesh/parallel/fci_comm.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/parallel/fci_comm.hxx b/src/mesh/parallel/fci_comm.hxx index bd4b955f12..39348d2e10 100644 --- a/src/mesh/parallel/fci_comm.hxx +++ b/src/mesh/parallel/fci_comm.hxx @@ -161,7 +161,7 @@ private: { int thisproc; MPI_Comm_rank(comm, thisproc); - assert(thisproc == mesh->getYProcIndex() * g2lx.npe + mesh->getXProcIndex()); + ASSERT0(thisproc == mesh->getYProcIndex() * g2lx.npe + mesh->getXProcIndex()); } #endif std::vector reqs(toSend.size()); From db77ef3ce78280dbe1f1f3ff98452f6b1038f9de Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 22 Jan 2025 10:41:15 +0100 Subject: [PATCH 177/242] Fix check --- src/mesh/parallel/fci_comm.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/parallel/fci_comm.hxx b/src/mesh/parallel/fci_comm.hxx index 39348d2e10..ec4aeaba49 100644 --- a/src/mesh/parallel/fci_comm.hxx +++ b/src/mesh/parallel/fci_comm.hxx @@ -160,7 +160,7 @@ private: #if CHECK > 3 { int thisproc; - MPI_Comm_rank(comm, thisproc); + MPI_Comm_rank(comm, &thisproc); ASSERT0(thisproc == mesh->getYProcIndex() * g2lx.npe + mesh->getXProcIndex()); } #endif From 147a874cd6ce6d8896a4f31b75cbfa6f75c1475b Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 22 Jan 2025 13:22:19 +0100 Subject: [PATCH 178/242] Expect lower convergence for monotonic correction --- tests/MMS/spatial/fci/runtest | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/MMS/spatial/fci/runtest b/tests/MMS/spatial/fci/runtest index 6248c75afb..a2785a24ae 100755 --- a/tests/MMS/spatial/fci/runtest +++ b/tests/MMS/spatial/fci/runtest @@ -159,7 +159,9 @@ for nslice in nslices: stdout.write(", {:f} (small spacing)".format(order)) # Should be close to the expected order - if order > method_orders[nslice]["order"] * 0.95: + if order > method_orders[nslice]["order"] * ( + 0.6 if "monot" in method else 0.95 + ): print("............ PASS\n") else: print("............ FAIL\n") From 276007c0c5c892a5a877410ac131f4e679945832 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 31 Jan 2025 10:07:22 +0100 Subject: [PATCH 179/242] Move yboundary iterator from hermes to BOUT++ This allows to write code for FCI and non-FCI using templates. --- include/bout/boundary_iterator.hxx | 51 ++++++++++++++++++++++- include/bout/yboundary_regions.hxx | 66 ++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 include/bout/yboundary_regions.hxx diff --git a/include/bout/boundary_iterator.hxx b/include/bout/boundary_iterator.hxx index 93f02c004d..601f6a8a28 100644 --- a/include/bout/boundary_iterator.hxx +++ b/include/bout/boundary_iterator.hxx @@ -1,6 +1,7 @@ #pragma once #include "bout/mesh.hxx" +#include "bout/parallel_boundary_region.hxx" #include "bout/sys/parallel_stencils.hxx" #include "bout/sys/range.hxx" @@ -42,14 +43,62 @@ public: return 2 * f(0, ind()) - f(0, ind().yp(-by).xp(-bx)); } - BoutReal interpolate_sheath(const Field3D& f) const { + BoutReal interpolate_sheath_o1(const Field3D& f) const { return (f[ind()] + ynext(f)) * 0.5; } + BoutReal + extrapolate_sheath_o2(const std::function& f) const { + return 0.5 * (3 * f(0, ind()) - f(0, ind().yp(-by).xp(-bx))); + } + + void limitFree(Field3D& f) const { + const BoutReal fac = + bout::parallel_boundary_region::limitFreeScale(yprev(f), ythis(f)); + BoutReal val = ythis(f); + for (int i = 1; i <= localmesh->ystart; ++i) { + val *= fac; + f[ind().yp(by * i).xp(bx * i)] = val; + } + } + + void neumann_o1(Field3D& f, BoutReal grad) const { + BoutReal val = ythis(f); + for (int i = 1; i <= localmesh->ystart; ++i) { + val += grad; + f[ind().yp(by * i).xp(bx * i)] = val; + } + } + + void neumann_o2(Field3D& f, BoutReal grad) const { + BoutReal val = yprev(f) + grad; + for (int i = 1; i <= localmesh->ystart; ++i) { + val += grad; + f[ind().yp(by * i).xp(bx * i)] = val; + } + } BoutReal& ynext(Field3D& f) const { return f[ind().yp(by).xp(bx)]; } const BoutReal& ynext(const Field3D& f) const { return f[ind().yp(by).xp(bx)]; } BoutReal& yprev(Field3D& f) const { return f[ind().yp(-by).xp(-bx)]; } const BoutReal& yprev(const Field3D& f) const { return f[ind().yp(-by).xp(-bx)]; } + BoutReal& ythis(Field3D& f) const { return f[ind()]; } + const BoutReal& ythis(const Field3D& f) const { return f[ind()]; } + + void setYPrevIfValid(Field3D& f, BoutReal val) const { yprev(f) = val; } + void setAll(Field3D& f, const BoutReal val) const { + for (int i = -localmesh->ystart; i <= localmesh->ystart; ++i) { + f[ind().yp(by * i).xp(bx * i)] = val; + } + } + + int abs_offset() const { return 1; } + +#if BOUT_USE_METRIC_3D == 0 + BoutReal& ynext(Field2D& f) const { return f[ind().yp(by).xp(bx)]; } + const BoutReal& ynext(const Field2D& f) const { return f[ind().yp(by).xp(bx)]; } + BoutReal& yprev(Field2D& f) const { return f[ind().yp(-by).xp(-bx)]; } + const BoutReal& yprev(const Field2D& f) const { return f[ind().yp(-by).xp(-bx)]; } +#endif const int dir; diff --git a/include/bout/yboundary_regions.hxx b/include/bout/yboundary_regions.hxx new file mode 100644 index 0000000000..e0e93e17f9 --- /dev/null +++ b/include/bout/yboundary_regions.hxx @@ -0,0 +1,66 @@ +#pragma once + +#include "./boundary_iterator.hxx" +#include "bout/parallel_boundary_region.hxx" + +class YBoundary { +public: + template + void iter_regions(const T& f) { + ASSERT1(is_init); + for (auto& region : boundary_regions) { + f(*region); + } + for (auto& region : boundary_regions_par) { + f(*region); + } + } + + template + void iter(const F& f) { + return iter_regions(f); + } + + void init(Options& options, Mesh* mesh = nullptr) { + if (mesh == nullptr) { + mesh = bout::globals::mesh; + } + + bool lower_y = options["lower_y"].doc("Boundary on lower y?").withDefault(true); + bool upper_y = options["upper_y"].doc("Boundary on upper y?").withDefault(true); + bool outer_x = options["outer_x"].doc("Boundary on outer x?").withDefault(true); + bool inner_x = + options["inner_x"].doc("Boundary on inner x?").withDefault(false); + + if (mesh->isFci()) { + if (outer_x) { + for (auto& bndry : mesh->getBoundariesPar(BoundaryParType::xout)) { + boundary_regions_par.push_back(bndry); + } + } + if (inner_x) { + for (auto& bndry : mesh->getBoundariesPar(BoundaryParType::xin)) { + boundary_regions_par.push_back(bndry); + } + } + } else { + if (lower_y) { + boundary_regions.push_back( + std::make_shared(mesh, true, mesh->iterateBndryLowerY())); + } + if (upper_y) { + boundary_regions.push_back(std::make_shared( + mesh, false, mesh->iterateBndryUpperY())); + } + } + is_init = true; + } + +private: + std::vector> boundary_regions_par; + std::vector> boundary_regions; + + bool is_init{false}; +}; + +extern YBoundary yboundary; From 731db609b7b5abd1770badeae7b456b255ec2312 Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 22 Jan 2025 17:17:04 +0100 Subject: [PATCH 180/242] Add delay on error --- include/bout/physicsmodel.hxx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/bout/physicsmodel.hxx b/include/bout/physicsmodel.hxx index fa113670ba..7588b86f79 100644 --- a/include/bout/physicsmodel.hxx +++ b/include/bout/physicsmodel.hxx @@ -47,6 +47,8 @@ class PhysicsModel; #include "bout/unused.hxx" #include "bout/utils.hxx" +#include +#include #include #include @@ -437,6 +439,7 @@ private: } catch (const BoutException& e) { \ output << "Error encountered: " << e.what(); \ output << e.getBacktrace() << endl; \ + std::this_thread::sleep_for(std::chrono::milliseconds(100)); \ MPI_Abort(BoutComm::get(), 1); \ } \ BoutFinalise(); \ From dc94e06465fedf311495ac4a512a0c0467bed46b Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 22 Jan 2025 16:53:58 +0100 Subject: [PATCH 181/242] Add legacy monotonic hermite spline implementation again --- include/bout/interpolation_xz.hxx | 14 +++ src/mesh/interpolation/hermite_spline_xz.cxx | 89 ++++++++++++++++++++ src/mesh/interpolation_xz.cxx | 2 + tests/MMS/spatial/fci/runtest | 7 +- 4 files changed, 111 insertions(+), 1 deletion(-) diff --git a/include/bout/interpolation_xz.hxx b/include/bout/interpolation_xz.hxx index 261b4c1515..fd4a4fcd50 100644 --- a/include/bout/interpolation_xz.hxx +++ b/include/bout/interpolation_xz.hxx @@ -134,6 +134,7 @@ public: } }; + template class XZHermiteSplineBase : public XZInterpolation { protected: @@ -281,6 +282,19 @@ public: const std::string& region = "RGN_NOBNDRY") override; }; + +class XZMonotonicHermiteSplineLegacy: public XZHermiteSplineBase { +public: + using XZHermiteSplineBase::interpolate; + virtual Field3D interpolate(const Field3D& f, + const std::string& region = "RGN_NOBNDRY") const override; + template + XZMonotonicHermiteSplineLegacy(Ts... args) : + XZHermiteSplineBase(args...) + {} +}; + + class XZInterpolationFactory : public Factory { public: diff --git a/src/mesh/interpolation/hermite_spline_xz.cxx b/src/mesh/interpolation/hermite_spline_xz.cxx index b8b30e68d8..c0952bae5e 100644 --- a/src/mesh/interpolation/hermite_spline_xz.cxx +++ b/src/mesh/interpolation/hermite_spline_xz.cxx @@ -495,3 +495,92 @@ XZHermiteSplineBase::interpolate(const Field3D& f, const Field3D& del // ensure they are instantiated template class XZHermiteSplineBase; template class XZHermiteSplineBase; + +Field3D XZMonotonicHermiteSplineLegacy::interpolate(const Field3D& f, + const std::string& region) const { + ASSERT1(f.getMesh() == localmesh); + Field3D f_interp(f.getMesh()); + f_interp.allocate(); + + // Derivatives are used for tension and need to be on dimensionless + // coordinates + Field3D fx = bout::derivatives::index::DDX(f, CELL_DEFAULT, "DEFAULT"); + localmesh->communicateXZ(fx); + // communicate in y, but do not calculate parallel slices + { + auto h = localmesh->sendY(fx); + localmesh->wait(h); + } + Field3D fz = bout::derivatives::index::DDZ(f, CELL_DEFAULT, "DEFAULT", "RGN_ALL"); + localmesh->communicateXZ(fz); + // communicate in y, but do not calculate parallel slices + { + auto h = localmesh->sendY(fz); + localmesh->wait(h); + } + Field3D fxz = bout::derivatives::index::DDX(fz, CELL_DEFAULT, "DEFAULT"); + localmesh->communicateXZ(fxz); + // communicate in y, but do not calculate parallel slices + { + auto h = localmesh->sendY(fxz); + localmesh->wait(h); + } + + const auto curregion{getRegion(region)}; + BOUT_FOR(i, curregion) { + const auto iyp = i.yp(y_offset); + + const auto ic = i_corner[i]; + const auto iczp = ic.zp(); + const auto icxp = ic.xp(); + const auto icxpzp = iczp.xp(); + + // Interpolate f in X at Z + const BoutReal f_z = + f[ic] * h00_x[i] + f[icxp] * h01_x[i] + fx[ic] * h10_x[i] + fx[icxp] * h11_x[i]; + + // Interpolate f in X at Z+1 + const BoutReal f_zp1 = f[iczp] * h00_x[i] + f[icxpzp] * h01_x[i] + fx[iczp] * h10_x[i] + + fx[icxpzp] * h11_x[i]; + + // Interpolate fz in X at Z + const BoutReal fz_z = fz[ic] * h00_x[i] + fz[icxp] * h01_x[i] + fxz[ic] * h10_x[i] + + fxz[icxp] * h11_x[i]; + + // Interpolate fz in X at Z+1 + const BoutReal fz_zp1 = fz[iczp] * h00_x[i] + fz[icxpzp] * h01_x[i] + + fxz[iczp] * h10_x[i] + fxz[icxpzp] * h11_x[i]; + + // Interpolate in Z + BoutReal result = + +f_z * h00_z[i] + f_zp1 * h01_z[i] + fz_z * h10_z[i] + fz_zp1 * h11_z[i]; + + ASSERT2(std::isfinite(result) || i.x() < localmesh->xstart + || i.x() > localmesh->xend); + + // Monotonicity + // Force the interpolated result to be in the range of the + // neighbouring cell values. This prevents unphysical overshoots, + // but also degrades accuracy near maxima and minima. + // Perhaps should only impose near boundaries, since that is where + // problems most obviously occur. + const BoutReal localmax = BOUTMAX(f[ic], f[icxp], f[iczp], f[icxpzp]); + + const BoutReal localmin = BOUTMIN(f[ic], f[icxp], f[iczp], f[icxpzp]); + + ASSERT2(std::isfinite(localmax) || i.x() < localmesh->xstart + || i.x() > localmesh->xend); + ASSERT2(std::isfinite(localmin) || i.x() < localmesh->xstart + || i.x() > localmesh->xend); + + if (result > localmax) { + result = localmax; + } + if (result < localmin) { + result = localmin; + } + + f_interp[iyp] = result; + } + return f_interp; +} diff --git a/src/mesh/interpolation_xz.cxx b/src/mesh/interpolation_xz.cxx index 0bc25111ab..bf22ba995d 100644 --- a/src/mesh/interpolation_xz.cxx +++ b/src/mesh/interpolation_xz.cxx @@ -91,6 +91,8 @@ namespace { RegisterXZInterpolation registerinterphermitespline{"hermitespline"}; RegisterXZInterpolation registerinterpmonotonichermitespline{ "monotonichermitespline"}; +RegisterXZInterpolation registerinterpmonotonichermitesplinelegacy{ + "monotonichermitesplinelegacy"}; RegisterXZInterpolation registerinterplagrange4pt{"lagrange4pt"}; RegisterXZInterpolation registerinterpbilinear{"bilinear"}; } // namespace diff --git a/tests/MMS/spatial/fci/runtest b/tests/MMS/spatial/fci/runtest index a2785a24ae..c8bb3f914b 100755 --- a/tests/MMS/spatial/fci/runtest +++ b/tests/MMS/spatial/fci/runtest @@ -51,6 +51,7 @@ for nslice in nslices: "lagrange4pt", "bilinear", "monotonichermitespline", + "monotonichermitesplinelegacy", ]: error_2[nslice] = [] error_inf[nslice] = [] @@ -104,7 +105,11 @@ for nslice in nslices: nslice, yperiodic, method_orders[nslice]["name"], - 2 if conf.has["petsc"] and "hermitespline" in method else 1, + ( + 1 + if "legacy" in method + else 2 if conf.has["petsc"] and "hermitespline" in method else 1 + ), ) args += f" mesh:paralleltransform:xzinterpolation:type={method}" From 18231442a0fc5a6bc7d6dca739f5ceab23618d17 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 28 Jan 2025 09:49:41 +0100 Subject: [PATCH 182/242] Take periodicity into account --- src/mesh/parallel/fci_comm.hxx | 36 ++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/mesh/parallel/fci_comm.hxx b/src/mesh/parallel/fci_comm.hxx index ec4aeaba49..a93b55244a 100644 --- a/src/mesh/parallel/fci_comm.hxx +++ b/src/mesh/parallel/fci_comm.hxx @@ -52,19 +52,35 @@ struct globalToLocal1D { const int local; const int global; const int globalwith; - globalToLocal1D(int mg, int npe, int localwith) + const bool periodic; + globalToLocal1D(int mg, int npe, int localwith, bool periodic) : mg(mg), npe(npe), localwith(localwith), local(localwith - 2 * mg), - global(local * npe), globalwith(global + 2 * mg) {}; + global(local * npe), globalwith(global + 2 * mg), periodic(periodic) {}; ProcLocal convert(int id) const { + if (periodic) { + while (id < mg) { + id += global; + } + while (id >= global + mg) { + id -= global; + } + } int idwo = id - mg; int proc = idwo / local; - if (proc >= npe) { - proc = npe - 1; + if (not periodic) { + if (proc >= npe) { + proc = npe - 1; + } } - ASSERT2(proc >= 0); int loc = id - local * proc; - ASSERT2(0 <= loc); - ASSERT2(loc < (local + 2 * mg)); +#if CHECK > 1 + if ((loc < 0 or loc > localwith or proc < 0 or proc > npe) + or (periodic and (loc < mg or loc >= local + mg))) { + printf("globalToLocal1D failure: %d %d, %d %d, %d %d %s\n", id, idwo, globalwith, + npe, proc, loc, periodic ? "periodic" : "non-periodic"); + ASSERT0(false); + } +#endif return {proc, loc}; } }; @@ -98,9 +114,9 @@ class GlobalField3DAccess { public: friend class GlobalField3DAccessInstance; GlobalField3DAccess(Mesh* mesh) - : mesh(mesh), g2lx(mesh->xstart, mesh->getNXPE(), mesh->LocalNx), - g2ly(mesh->ystart, mesh->getNYPE(), mesh->LocalNy), - g2lz(mesh->zstart, 1, mesh->LocalNz), + : mesh(mesh), g2lx(mesh->xstart, mesh->getNXPE(), mesh->LocalNx, false), + g2ly(mesh->ystart, mesh->getNYPE(), mesh->LocalNy, true), + g2lz(mesh->zstart, 1, mesh->LocalNz, true), xyzl(g2lx.localwith, g2ly.localwith, g2lz.localwith), xyzg(g2lx.globalwith, g2ly.globalwith, g2lz.globalwith), comm(BoutComm::get()) {}; void get(IndG3D ind) { ids.emplace(ind.ind); } From 1e96b3e8bfa7b86a1939d1150e4c350ccb689859 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 28 Jan 2025 09:49:56 +0100 Subject: [PATCH 183/242] Sort a reference, not a copy --- src/mesh/parallel/fci_comm.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/parallel/fci_comm.hxx b/src/mesh/parallel/fci_comm.hxx index a93b55244a..9f296848b1 100644 --- a/src/mesh/parallel/fci_comm.hxx +++ b/src/mesh/parallel/fci_comm.hxx @@ -133,7 +133,7 @@ public: toGet[piy.proc * g2lx.npe + pix.proc].push_back( xyzl.convert(pix.ind, piy.ind, piz.ind).ind); } - for (auto v : toGet) { + for (auto& v : toGet) { std::sort(v.begin(), v.end()); } commCommLists(); From ad8f403f9c427e2497b8e08c23fe3c26f33793a2 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 28 Jan 2025 09:52:06 +0100 Subject: [PATCH 184/242] Only communicate non-empty vectors --- src/mesh/parallel/fci_comm.hxx | 44 ++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/src/mesh/parallel/fci_comm.hxx b/src/mesh/parallel/fci_comm.hxx index 9f296848b1..1ca67f0ecc 100644 --- a/src/mesh/parallel/fci_comm.hxx +++ b/src/mesh/parallel/fci_comm.hxx @@ -193,6 +193,7 @@ private: ASSERT0(ret == MPI_SUCCESS); } std::vector reqs2(toSend.size()); + int cnt = 0; for ([[maybe_unused]] auto dummy : reqs) { int ind{0}; auto ret = MPI_Waitany(reqs.size(), &reqs[0], &ind, MPI_STATUS_IGNORE); @@ -200,20 +201,26 @@ private: ASSERT3(ind != MPI_UNDEFINED); ASSERT2(static_cast(ind) < toSend.size()); ASSERT3(toSendSizes[ind] >= 0); + if (toSendSizes[ind] == 0) { + continue; + } sendBufferSize += toSendSizes[ind]; - toSend[ind].resize(toSendSizes[ind]); - ret = MPI_Irecv(static_cast(&toSend[ind][0]), toSend[ind].size(), MPI_INT, - ind, 666 * 666, comm, &reqs2[ind]); + toSend[ind].resize(toSendSizes[ind], -1); + + ret = MPI_Irecv(static_cast(toSend[ind].data()), toSend[ind].size(), MPI_INT, + ind, 666 * 666, comm, reqs2.data() + cnt++); ASSERT0(ret == MPI_SUCCESS); } for (size_t proc = 0; proc < toGet.size(); ++proc) { - const auto ret = MPI_Send(static_cast(&toGet[proc][0]), toGet[proc].size(), - MPI_INT, proc, 666 * 666, comm); - ASSERT0(ret == MPI_SUCCESS); + if (toGet.size() != 0) { + const auto ret = MPI_Send(static_cast(toGet[proc].data()), + toGet[proc].size(), MPI_INT, proc, 666 * 666, comm); + ASSERT0(ret == MPI_SUCCESS); + } } - for ([[maybe_unused]] auto dummy : reqs) { + for (int c = 0; c < cnt; c++) { int ind{0}; - const auto ret = MPI_Waitany(reqs.size(), &reqs2[0], &ind, MPI_STATUS_IGNORE); + const auto ret = MPI_Waitany(cnt, reqs2.data(), &ind, MPI_STATUS_IGNORE); ASSERT0(ret == MPI_SUCCESS); ASSERT3(ind != MPI_UNDEFINED); } @@ -242,25 +249,36 @@ private: std::vector data(getOffsets.back()); std::vector sendBuffer(sendBufferSize); std::vector reqs(toSend.size()); + int cnt1 = 0; for (size_t proc = 0; proc < toGet.size(); ++proc) { - auto ret = MPI_Irecv(static_cast(&data[getOffsets[proc]]), - toGet[proc].size(), MPI_DOUBLE, proc, 666, comm, &reqs[proc]); + if (toGet[proc].size() == 0) { + continue; + } + auto ret = + MPI_Irecv(static_cast(data.data() + getOffsets[proc]), + toGet[proc].size(), MPI_DOUBLE, proc, 666, comm, reqs.data() + cnt1); ASSERT0(ret == MPI_SUCCESS); + cnt1++; } int cnt = 0; for (size_t proc = 0; proc < toGet.size(); ++proc) { - void* start = static_cast(&sendBuffer[cnt]); + if (toSend[proc].size() == 0) { + continue; + } + const void* start = static_cast(sendBuffer.data() + cnt); for (auto i : toSend[proc]) { sendBuffer[cnt++] = f[Ind3D(i)]; } auto ret = MPI_Send(start, toSend[proc].size(), MPI_DOUBLE, proc, 666, comm); ASSERT0(ret == MPI_SUCCESS); } - for ([[maybe_unused]] auto dummy : reqs) { + for (int j = 0; j < cnt1; ++j) { int ind{0}; - auto ret = MPI_Waitany(reqs.size(), &reqs[0], &ind, MPI_STATUS_IGNORE); + auto ret = MPI_Waitany(cnt1, reqs.data(), &ind, MPI_STATUS_IGNORE); ASSERT0(ret == MPI_SUCCESS); ASSERT3(ind != MPI_UNDEFINED); + ASSERT3(ind >= 0); + ASSERT3(ind < cnt1); } return data; } From 5adf893cb4f205147984151fe10c89fc6bb3419a Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 31 Jan 2025 11:45:08 +0100 Subject: [PATCH 185/242] Make check stricter --- src/mesh/parallel/fci_comm.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/parallel/fci_comm.hxx b/src/mesh/parallel/fci_comm.hxx index 1ca67f0ecc..df08a02dda 100644 --- a/src/mesh/parallel/fci_comm.hxx +++ b/src/mesh/parallel/fci_comm.hxx @@ -74,7 +74,7 @@ struct globalToLocal1D { } int loc = id - local * proc; #if CHECK > 1 - if ((loc < 0 or loc > localwith or proc < 0 or proc > npe) + if ((loc < 0 or loc > localwith or proc < 0 or proc >= npe) or (periodic and (loc < mg or loc >= local + mg))) { printf("globalToLocal1D failure: %d %d, %d %d, %d %d %s\n", id, idwo, globalwith, npe, proc, loc, periodic ? "periodic" : "non-periodic"); From cd383e24b13b8925bee0c68b5775d38d4d47068a Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 31 Jan 2025 11:49:11 +0100 Subject: [PATCH 186/242] Minor improvements to mms test --- tests/MMS/spatial/fci/data/BOUT.inp | 1 + tests/MMS/spatial/fci/mms.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/MMS/spatial/fci/data/BOUT.inp b/tests/MMS/spatial/fci/data/BOUT.inp index b4825c6207..f3e5046c54 100644 --- a/tests/MMS/spatial/fci/data/BOUT.inp +++ b/tests/MMS/spatial/fci/data/BOUT.inp @@ -1,5 +1,6 @@ grid = fci.grid.nc +# generated by ../mms.py input_field = sin(y - 2*z) + sin(y - z) solution = (6.28318530717959*(0.01*x + 0.045)*(-2*cos(y - 2*z) - cos(y - z)) + 0.628318530717959*cos(y - 2*z) + 0.628318530717959*cos(y - z))/sqrt((0.01*x + 0.045)^2 + 1.0) diff --git a/tests/MMS/spatial/fci/mms.py b/tests/MMS/spatial/fci/mms.py index 1e71135c90..994b3f9761 100755 --- a/tests/MMS/spatial/fci/mms.py +++ b/tests/MMS/spatial/fci/mms.py @@ -30,5 +30,5 @@ def FCI_ddy(f): ############################################ # Equations solved -print("input = " + exprToStr(f)) +print("input_field = " + exprToStr(f)) print("solution = " + exprToStr(FCI_ddy(f))) From 2d72bab00647ce0e3dc0290916282d9d3f51257b Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 31 Jan 2025 11:49:47 +0100 Subject: [PATCH 187/242] Fix: include global offset in monotonic spline --- src/mesh/interpolation/hermite_spline_xz.cxx | 5 ++++- tests/integrated/test_suite | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/mesh/interpolation/hermite_spline_xz.cxx b/src/mesh/interpolation/hermite_spline_xz.cxx index c0952bae5e..1990bddb21 100644 --- a/src/mesh/interpolation/hermite_spline_xz.cxx +++ b/src/mesh/interpolation/hermite_spline_xz.cxx @@ -170,6 +170,8 @@ void XZHermiteSplineBase::calcWeights(const Field3D& delta_x, #ifdef HS_USE_PETSC IndConverter conv{localmesh}; #endif + [[maybe_unused]] const int y_global_offset = + localmesh->getYProcIndex() * (localmesh->yend - localmesh->ystart + 1); BOUT_FOR(i, getRegion(region)) { const int x = i.x(); const int y = i.y(); @@ -303,7 +305,8 @@ void XZHermiteSplineBase::calcWeights(const Field3D& delta_x, #endif #endif if constexpr (monotonic) { - const auto gind = gf3daccess->xyzg(i_corn, y + y_offset, k_corner(x, y, z)); + const auto gind = + gf3daccess->xyzg(i_corn, y + y_offset + y_global_offset, k_corner(x, y, z)); gf3daccess->get(gind); gf3daccess->get(gind.xp(1)); gf3daccess->get(gind.zp(1)); diff --git a/tests/integrated/test_suite b/tests/integrated/test_suite index 307a8d84b3..77ad7882c4 100755 --- a/tests/integrated/test_suite +++ b/tests/integrated/test_suite @@ -188,7 +188,7 @@ class Test(threading.Thread): self.output += "\n(It is likely that a timeout occured)" else: # ❌ Failed - print("\u274C", end="") # No newline + print("\u274c", end="") # No newline print(" %7.3f s" % (time.time() - self.local.start_time), flush=True) def _cost(self): From b3841fb21eb589426fc594b071ef20caaab92797 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 31 Jan 2025 14:51:11 +0100 Subject: [PATCH 188/242] make fci_comm openmp thread safe 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. --- src/mesh/parallel/fci_comm.hxx | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/mesh/parallel/fci_comm.hxx b/src/mesh/parallel/fci_comm.hxx index df08a02dda..40c1fe9f72 100644 --- a/src/mesh/parallel/fci_comm.hxx +++ b/src/mesh/parallel/fci_comm.hxx @@ -118,11 +118,30 @@ public: g2ly(mesh->ystart, mesh->getNYPE(), mesh->LocalNy, true), g2lz(mesh->zstart, 1, mesh->LocalNz, true), xyzl(g2lx.localwith, g2ly.localwith, g2lz.localwith), - xyzg(g2lx.globalwith, g2ly.globalwith, g2lz.globalwith), comm(BoutComm::get()) {}; - void get(IndG3D ind) { ids.emplace(ind.ind); } + xyzg(g2lx.globalwith, g2ly.globalwith, g2lz.globalwith), comm(BoutComm::get()) { +#ifdef _OPENMP + o_ids.resize(omp_get_max_threads()); +#endif + }; + void get(IndG3D ind) { + ASSERT2(is_setup == false); +#ifdef _OPENMP + ASSERT2(o_ids.size() > static_cast(omp_get_thread_num())); + o_ids[omp_get_thread_num()].emplace(ind.ind); +#else + ids.emplace(ind.ind); +#endif + } + void operator[](IndG3D ind) { return get(ind); } void setup() { ASSERT2(is_setup == false); +#ifdef _OPENMP + for (auto& o_id : o_ids) { + ids.merge(o_id); + } + o_ids.clear(); +#endif toGet.resize(g2lx.npe * g2ly.npe * g2lz.npe); for (const auto id : ids) { IndG3D gind{id, g2ly.globalwith, g2lz.globalwith}; @@ -226,6 +245,9 @@ private: } } Mesh* mesh; +#ifdef _OPENMP + std::vector> o_ids; +#endif std::set ids; std::map mapping; bool is_setup{false}; From 63e8cb9e793b4047ca709066e1afa651dd86c5a4 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 7 Feb 2025 13:50:10 +0100 Subject: [PATCH 189/242] Fix communication for fci We want to skip sending if there is no data for this process ... --- src/mesh/parallel/fci_comm.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/parallel/fci_comm.hxx b/src/mesh/parallel/fci_comm.hxx index 40c1fe9f72..27dd111765 100644 --- a/src/mesh/parallel/fci_comm.hxx +++ b/src/mesh/parallel/fci_comm.hxx @@ -231,7 +231,7 @@ private: ASSERT0(ret == MPI_SUCCESS); } for (size_t proc = 0; proc < toGet.size(); ++proc) { - if (toGet.size() != 0) { + if (toGet[proc].size() != 0) { const auto ret = MPI_Send(static_cast(toGet[proc].data()), toGet[proc].size(), MPI_INT, proc, 666 * 666, comm); ASSERT0(ret == MPI_SUCCESS); From 371c928fa0b57238044b6ef53bab1e5314e4a4af Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 7 Feb 2025 15:58:07 +0100 Subject: [PATCH 190/242] Add check to reduce risk of bugs 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. --- src/mesh/interpolation/hermite_spline_xz.cxx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mesh/interpolation/hermite_spline_xz.cxx b/src/mesh/interpolation/hermite_spline_xz.cxx index 1990bddb21..f850704b22 100644 --- a/src/mesh/interpolation/hermite_spline_xz.cxx +++ b/src/mesh/interpolation/hermite_spline_xz.cxx @@ -356,6 +356,9 @@ template std::vector XZHermiteSplineBase::getWeightsForYApproximation(int i, int j, int k, int yoffset) { + if (localmesh->getNXPE() > 1){ + throw BoutException("It is likely that the function calling this is not handling the result correctly."); + } const int nz = localmesh->LocalNz; const int k_mod = k_corner(i, j, k); const int k_mod_m1 = (k_mod > 0) ? (k_mod - 1) : (nz - 1); From f2939c66a4e0935ea6cbf303c4dcc45ba2a03235 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 7 Feb 2025 16:45:29 +0100 Subject: [PATCH 191/242] Handle C++ exception for more functions --- tools/pylib/_boutpp_build/boutcpp.pxd.jinja | 62 ++++++++++----------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/tools/pylib/_boutpp_build/boutcpp.pxd.jinja b/tools/pylib/_boutpp_build/boutcpp.pxd.jinja index 71ca09cb46..9a6435a018 100644 --- a/tools/pylib/_boutpp_build/boutcpp.pxd.jinja +++ b/tools/pylib/_boutpp_build/boutcpp.pxd.jinja @@ -16,12 +16,12 @@ cdef extern from "bout/{{ field.header }}.hxx": cppclass {{ field.field_type }}: {{ field.field_type }}(Mesh * mesh); {{ field.field_type }}(const {{ field.field_type }} &) - double & operator()(int, int, int) + double & operator()(int, int, int) except +raise_bout_py_error int getNx() int getNy() int getNz() bool isAllocated() - void setLocation(benum.CELL_LOC) + void setLocation(benum.CELL_LOC) except +raise_bout_py_error benum.CELL_LOC getLocation() Mesh* getMesh() {% for boundaryMethod in field.boundaries %} @@ -30,12 +30,12 @@ cdef extern from "bout/{{ field.header }}.hxx": void {{ boundaryMethod }}(double t) {% endfor %} {% for fun in "sqrt", "exp", "log", "sin", "cos", "abs" %} - {{ field.field_type }} {{ fun }}({{ field.field_type }}) + {{ field.field_type }} {{ fun }}({{ field.field_type }}) except +raise_bout_py_error {% endfor %} double max({{ field.field_type }}) double min({{ field.field_type }}) - {{ field.field_type }} pow({{ field.field_type }},double) - {{ field.field_type }} & ddt({{ field.field_type }}) + {{ field.field_type }} pow({{ field.field_type }}, double) except +raise_bout_py_error + {{ field.field_type }} & ddt({{ field.field_type }}) except +raise_bout_py_error {% endfor %} {% for vec in vecs %} cdef extern from "bout/{{ vec.header }}.hxx": @@ -51,9 +51,9 @@ cdef extern from "bout/mesh.hxx": cppclass Mesh: Mesh() @staticmethod - Mesh * create(Options * option) - void load() - void communicate(FieldGroup&) + Mesh * create(Options * option) except +raise_bout_py_error + void load() except +raise_bout_py_error + void communicate(FieldGroup&) except +raise_bout_py_error int getNXPE() int getNYPE() int getXProcIndex() @@ -62,8 +62,8 @@ cdef extern from "bout/mesh.hxx": int ystart int LocalNx int LocalNy - Coordinates * getCoordinates() - int get(Field3D, const string) + Coordinates * getCoordinates() except +raise_bout_py_error + int get(Field3D, const string) except +raise_bout_py_error cdef extern from "bout/coordinates.hxx": cppclass Coordinates: @@ -79,10 +79,10 @@ cdef extern from "bout/coordinates.hxx": {{ metric_field }} G1, G2, G3 {{ metric_field }} ShiftTorsion {{ metric_field }} IntShiftTorsion - int geometry() - int calcCovariant() - int calcContravariant() - int jacobian() + int geometry() except +raise_bout_py_error + int calcCovariant() except +raise_bout_py_error + int calcContravariant() except +raise_bout_py_error + int jacobian() except +raise_bout_py_error cdef extern from "bout/fieldgroup.hxx": cppclass FieldGroup: @@ -91,8 +91,8 @@ cdef extern from "bout/fieldgroup.hxx": cdef extern from "bout/invert_laplace.hxx": cppclass Laplacian: @staticmethod - unique_ptr[Laplacian] create(Options*, benum.CELL_LOC, Mesh*, Solver*) - Field3D solve(Field3D, Field3D) + unique_ptr[Laplacian] create(Options*, benum.CELL_LOC, Mesh*, Solver*) except +raise_bout_py_error + Field3D solve(Field3D, Field3D) except +raise_bout_py_error Field3D forward(Field3D) void setCoefA(Field3D) void setCoefC(Field3D) @@ -104,12 +104,12 @@ cdef extern from "bout/invert_laplace.hxx": void setCoefEz(Field3D) cdef extern from "bout/difops.hxx": - Field3D Div_par(Field3D, benum.CELL_LOC, string) - Field3D Grad_par(Field3D, benum.CELL_LOC, string) - Field3D Laplace(Field3D) - Field3D Vpar_Grad_par(Field3D, Field3D, benum.CELL_LOC, string) - Field3D bracket(Field3D,Field3D, benum.BRACKET_METHOD, benum.CELL_LOC) - Field3D Delp2(Field3D) + Field3D Div_par(Field3D, benum.CELL_LOC, string) except +raise_bout_py_error + Field3D Grad_par(Field3D, benum.CELL_LOC, string) except +raise_bout_py_error + Field3D Laplace(Field3D) except +raise_bout_py_error + Field3D Vpar_Grad_par(Field3D, Field3D, benum.CELL_LOC, string) except +raise_bout_py_error + Field3D bracket(Field3D,Field3D, benum.BRACKET_METHOD, benum.CELL_LOC) except +raise_bout_py_error + Field3D Delp2(Field3D) except +raise_bout_py_error cdef extern from "bout/derivs.hxx": {% for d in "XYZ" %} @@ -131,16 +131,16 @@ cdef extern from "bout/interpolation.hxx": cdef extern from "bout/field_factory.hxx": cppclass FieldFactory: - FieldFactory(Mesh*,Options*) - Field3D create3D(string bla, Options * o, Mesh * m,benum.CELL_LOC loc, double t) + FieldFactory(Mesh*,Options*) except +raise_bout_py_error + Field3D create3D(string bla, Options * o, Mesh * m,benum.CELL_LOC loc, double t) except +raise_bout_py_error cdef extern from "bout/solver.hxx": cppclass Solver: @staticmethod - Solver * create() - void setModel(PhysicsModel *) - void add(Field3D, char * name) - void solve() + Solver * create() except +raise_bout_py_error + void setModel(PhysicsModel *) except +raise_bout_py_error + void add(Field3D, char * name) except +raise_bout_py_error + void solve() except +raise_bout_py_error cdef extern from "bout/physicsmodel.hxx": cppclass PhysicsModel: @@ -166,8 +166,8 @@ cdef extern from "bout/output.hxx": ConditionalOutput output_info cdef extern from "bout/vecops.hxx": - Vector3D Grad(const Field3D& f, benum.CELL_LOC, string) - Vector3D Grad_perp(const Field3D& f, benum.CELL_LOC, string) + Vector3D Grad(const Field3D& f, benum.CELL_LOC, string) except +raise_bout_py_error + Vector3D Grad_perp(const Field3D& f, benum.CELL_LOC, string) except +raise_bout_py_error cdef extern from "bout/vector3d.hxx": - Vector3D cross(Vector3D, Vector3D) + Vector3D cross(Vector3D, Vector3D) except +raise_bout_py_error From ad09499e640f1c3bcfcac98021fe7ed0786fea49 Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 10 Feb 2025 10:20:13 +0100 Subject: [PATCH 192/242] Expose LaplaceXZ --- tools/pylib/_boutpp_build/boutcpp.pxd.jinja | 8 +++ tools/pylib/_boutpp_build/boutpp.pyx.jinja | 58 +++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/tools/pylib/_boutpp_build/boutcpp.pxd.jinja b/tools/pylib/_boutpp_build/boutcpp.pxd.jinja index 9a6435a018..aa39e9843b 100644 --- a/tools/pylib/_boutpp_build/boutcpp.pxd.jinja +++ b/tools/pylib/_boutpp_build/boutcpp.pxd.jinja @@ -103,6 +103,14 @@ cdef extern from "bout/invert_laplace.hxx": void setCoefEy(Field3D) void setCoefEz(Field3D) +cdef extern from "bout/invert/laplacexz.hxx": + cppclass LaplaceXZ: + LaplaceXZ(Mesh*, Options*, benum.CELL_LOC) + void setCoefs(const Field3D& A, const Field3D& B) except +raise_bout_py_error + Field3D solve(const Field3D& b, const Field3D& x0) except +raise_bout_py_error + @staticmethod + unique_ptr[LaplaceXZ] create(Mesh* m, Options* opt, benum.CELL_LOC loc) + cdef extern from "bout/difops.hxx": Field3D Div_par(Field3D, benum.CELL_LOC, string) except +raise_bout_py_error Field3D Grad_par(Field3D, benum.CELL_LOC, string) except +raise_bout_py_error diff --git a/tools/pylib/_boutpp_build/boutpp.pyx.jinja b/tools/pylib/_boutpp_build/boutpp.pyx.jinja index 0712dbc499..c64d7aba80 100644 --- a/tools/pylib/_boutpp_build/boutpp.pyx.jinja +++ b/tools/pylib/_boutpp_build/boutpp.pyx.jinja @@ -943,6 +943,64 @@ Equation solved is: d\\nabla^2_\\perp x + (1/c1)\\nabla_perp c2\\cdot\\nabla_\\p {% endfor %} +{{ class("LaplaceXZ", comment=""" +LaplaceXZ inversion solver + +Compute the Laplacian inversion of objects. + +Equation solved is: \\nabla\\cdot\\left( A \\nabla_\\perp f \\right) + Bf = b +""", uniquePtr=True) }} + + def __init__(self, section=None, loc="CELL_CENTRE", mesh=None): + """ + Initialiase a Laplacian solver + + Parameters + ---------- + section : Options, optional + The section from the Option tree to take the options from + """ + checkInit() + cdef c.Options* copt = NULL + if section: + if isinstance(section, str): + section = Options.root(section) + copt = (section).cobj + cdef benum.CELL_LOC cloc = benum.resolve_cell_loc(loc) + cdef c.Mesh* cmesh = NULL + if mesh: + cmesh = (mesh).cobj + self.cobj = c.LaplaceXZ.create(cmesh, copt, cloc) + self.isSelfOwned = True + + def solve(self, Field3D x, Field3D guess): + """ + Calculate the Laplacian inversion + + Parameters + ---------- + x : Field3D + Field to be inverted + guess : Field3D + initial guess for the inversion + + + Returns + ------- + Field3D + the inversion of x, where guess is a guess to start with + """ + return f3dFromObj(deref(self.cobj).solve(x.cobj[0],guess.cobj[0])) + + def setCoefs(self, *, Field3D A, Field3D B): + """ + Set the coefficients for the Laplacian solver. + The coefficients A and B have both to be passed. + A and B have to be Field3D. + """ + deref(self.cobj).setCoefs(A.cobj[0], B.cobj[0]) + + {{ class("FieldFactory", defaultSO=False) }} cdef void callback(void * parameter, void * method) with gil: From def8a06e3d8bcb095b75a1fd91abb3f39200a5a5 Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 10 Feb 2025 10:20:37 +0100 Subject: [PATCH 193/242] Formatting fixes --- tools/pylib/_boutpp_build/boutpp.pyx.jinja | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tools/pylib/_boutpp_build/boutpp.pyx.jinja b/tools/pylib/_boutpp_build/boutpp.pyx.jinja index c64d7aba80..819ffcd489 100644 --- a/tools/pylib/_boutpp_build/boutpp.pyx.jinja +++ b/tools/pylib/_boutpp_build/boutpp.pyx.jinja @@ -781,13 +781,13 @@ cdef options norm : float The length with which to rescale """ - if self.isNormalised>0: - t=norm - norm=norm/self.isNormalised - self.isNormalised=t - c_mesh_normalise(self.cobj,norm) + if self.isNormalised > 0: + t = norm + norm = norm/self.isNormalised + self.isNormalised = t + c_mesh_normalise(self.cobj, norm) - def communicate(self,*args): + def communicate(self, *args): """ Communicate (MPI) the boundaries of the Field3Ds with neighbours @@ -1542,8 +1542,8 @@ def create3D(string, Mesh msh=None,outloc="CELL_DEFAULT",time=0): cdef benum.CELL_LOC outloc_=benum.resolve_cell_loc(outloc) if msh is None: msh=Mesh.getGlobal() - cdef FieldFactory fact=msh.getFactory() - cdef c.string str_=string.encode() + cdef FieldFactory fact = msh.getFactory() + cdef c.string str_ = string.encode() return f3dFromObj( (fact).cobj.create3D(str_,0,0 ,outloc_,time)) From f1534f2db7b93a15a6cc49b91427808a0804afe4 Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 10 Feb 2025 10:20:57 +0100 Subject: [PATCH 194/242] Expose Mesh::get for Field3D --- tools/pylib/_boutpp_build/boutpp.pyx.jinja | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tools/pylib/_boutpp_build/boutpp.pyx.jinja b/tools/pylib/_boutpp_build/boutpp.pyx.jinja index 819ffcd489..ff224cd70f 100644 --- a/tools/pylib/_boutpp_build/boutpp.pyx.jinja +++ b/tools/pylib/_boutpp_build/boutpp.pyx.jinja @@ -812,6 +812,20 @@ cdef options self._coords = coordsFromPtr(self.cobj.getCoordinates()) return self._coords + def get(self, name, default=None): + """ + Load a variable from the grid source + + If no default is given, and the variable is not found, an error is raised. + """ + cdef c.string cstr = name.encode() + cdef Field3D defaultfield = default if isinstance(default, Field3D) else Field3D.fromMesh(self) + if deref(self.cobj).get(deref(defaultfield.cobj), cstr): + if default is None: + raise ValueError(f"No default value for {name} given and not set for this mesh") + return default + return defaultfield + cdef Mesh meshFromPtr(c.Mesh * obj): mesh = Mesh() From 0bcc047d64e87ca9446fe1127b761a58215201f5 Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 10 Feb 2025 13:50:12 +0100 Subject: [PATCH 195/242] remove non-petsc inversions This allows to also run the tests with 3D metrics. It also tightens the tollerances, as this is a regression test. Also removing the preconditioner is needed. --- .../test-petsc_laplace/CMakeLists.txt | 1 - .../test-petsc_laplace/data/BOUT.inp | 44 ++----------------- tests/integrated/test-petsc_laplace/runtest | 16 +++---- .../test-petsc_laplace/test_petsc_laplace.cxx | 32 ++++---------- 4 files changed, 17 insertions(+), 76 deletions(-) diff --git a/tests/integrated/test-petsc_laplace/CMakeLists.txt b/tests/integrated/test-petsc_laplace/CMakeLists.txt index 9492b9f34f..15286ecfda 100644 --- a/tests/integrated/test-petsc_laplace/CMakeLists.txt +++ b/tests/integrated/test-petsc_laplace/CMakeLists.txt @@ -1,7 +1,6 @@ bout_add_integrated_test(test-petsc-laplace SOURCES test_petsc_laplace.cxx REQUIRES BOUT_HAS_PETSC - CONFLICTS BOUT_USE_METRIC_3D # default preconditioner uses 'cyclic' Laplace solver which is not available with 3d metrics USE_RUNTEST USE_DATA_BOUT_INP PROCESSORS 4 diff --git a/tests/integrated/test-petsc_laplace/data/BOUT.inp b/tests/integrated/test-petsc_laplace/data/BOUT.inp index e7c285b54c..3fb3f25b63 100644 --- a/tests/integrated/test-petsc_laplace/data/BOUT.inp +++ b/tests/integrated/test-petsc_laplace/data/BOUT.inp @@ -26,20 +26,9 @@ nonuniform = true rtol = 1e-08 atol = 1e-06 include_yguards = false -maxits = 1000 +maxits = 100000 -gmres_max_steps = 300 - -pctype = shell # Supply a second solver as a preconditioner -rightprec = true # Right precondition - -[petsc2nd:precon] # Options for the preconditioning solver -# Leave default type (tri or spt) -all_terms = true -nonuniform = true -filter = 0.0 # Must not filter -inner_boundary_flags = 32 # Identity in boundary -outer_boundary_flags = 32 # Identity in boundary +gmres_max_steps = 3000 ############################################# @@ -50,32 +39,7 @@ nonuniform = true rtol = 1e-08 atol = 1e-06 include_yguards = false -maxits = 1000 +maxits = 100000 fourth_order = true -gmres_max_steps = 30 - -pctype = shell -rightprec = true - -[petsc4th:precon] -all_terms = true -nonuniform = true -filter = 0.0 -inner_boundary_flags = 32 # Identity in boundary -outer_boundary_flags = 32 # Identity in boundary - -############################################# - -[SPT] -#type=spt -all_terms = true -nonuniform = true -#flags=15 -include_yguards = false - -#maxits=10000 - -[laplace] -all_terms = true -nonuniform = true +gmres_max_steps = 300 diff --git a/tests/integrated/test-petsc_laplace/runtest b/tests/integrated/test-petsc_laplace/runtest index ac248c4ce7..87c3991d00 100755 --- a/tests/integrated/test-petsc_laplace/runtest +++ b/tests/integrated/test-petsc_laplace/runtest @@ -9,20 +9,14 @@ # cores: 4 # Variables to compare -from __future__ import print_function -from builtins import str - vars = [ ("max_error1", 2.0e-4), - ("max_error2", 2.0e-4), + ("max_error2", 2.0e-8), ("max_error3", 2.0e-4), - ("max_error4", 1.0e-5), - ("max_error5", 2.0e-4), - ("max_error6", 2.0e-5), - ("max_error7", 2.0e-4), - ("max_error8", 2.0e-5), + ("max_error4", 2.0e-4), + ("max_error5", 4.0e-6), + ("max_error6", 2.0e-4), ] -# tol = 1e-4 # Absolute (?) tolerance from boututils.run_wrapper import build_and_log, shell, launch_safe from boutdata.collect import collect @@ -59,7 +53,7 @@ for nproc in [1, 2, 4]: print("Convergence error") success = False elif error > tol: - print("Fail, maximum error is = " + str(error)) + print(f"Fail, maximum error is = {error}") success = False else: print("Pass") diff --git a/tests/integrated/test-petsc_laplace/test_petsc_laplace.cxx b/tests/integrated/test-petsc_laplace/test_petsc_laplace.cxx index 1e3cdde310..8ca8383244 100644 --- a/tests/integrated/test-petsc_laplace/test_petsc_laplace.cxx +++ b/tests/integrated/test-petsc_laplace/test_petsc_laplace.cxx @@ -66,14 +66,10 @@ void check_laplace(int test_num, std::string_view test_name, Laplacian& invert, Field3D abs_error; BoutReal max_error = -1; - try { - sol = invert.solve(sliceXZ(bcoef, ystart)); - error = (field - sol) / field; - abs_error = field - sol; - max_error = max_error_at_ystart(abs(abs_error)); - } catch (BoutException& err) { - output.write("BoutException occured in invert->solve(b1): {}\n", err.what()); - } + sol = invert.solve(sliceXZ(bcoef, ystart)); + error = (field - sol) / field; + abs_error = field - sol; + max_error = max_error_at_ystart(abs(abs_error)); output.write("\nTest {}: {}\n", test_num, test_name); output.write("Magnitude of maximum absolute error is {}\n", max_error); @@ -147,7 +143,7 @@ int main(int argc, char** argv) { INVERT_AC_GRAD, a_1, c_1, d_1, b_1, f_1, mesh->ystart, dump); //////////////////////////////////////////////////////////////////////////////////////// - // Test 3+4: Gaussian x-profiles, z-independent coefficients and compare with SPT method + // Test 3: Gaussian x-profiles, z-independent coefficients const Field2D a_3 = DC(a_1); const Field2D c_3 = DC(c_1); @@ -158,15 +154,8 @@ int main(int argc, char** argv) { INVERT_AC_GRAD, INVERT_AC_GRAD, a_3, c_3, d_3, b_3, f_1, mesh->ystart, dump); - Options* SPT_options = Options::getRoot()->getSection("SPT"); - auto invert_SPT = Laplacian::create(SPT_options); - - check_laplace(++test_num, "with coefficients constant in z, default solver", - *invert_SPT, INVERT_AC_GRAD, INVERT_AC_GRAD | INVERT_DC_GRAD, a_3, c_3, - d_3, b_3, f_1, mesh->ystart, dump); - ////////////////////////////////////////////// - // Test 5: Cosine x-profiles, 2nd order Krylov + // Test 4: Cosine x-profiles, 2nd order Krylov Field3D f_5 = generate_f5(*mesh); Field3D a_5 = generate_a5(*mesh); Field3D c_5 = generate_c5(*mesh); @@ -181,14 +170,14 @@ int main(int argc, char** argv) { dump); ////////////////////////////////////////////// - // Test 6: Cosine x-profiles, 4th order Krylov + // Test 5: Cosine x-profiles, 4th order Krylov check_laplace(++test_num, "different profiles, PETSc 4th order", *invert_4th, INVERT_AC_GRAD, INVERT_AC_GRAD, a_5, c_5, d_5, b_5, f_5, mesh->ystart, dump); ////////////////////////////////////////////////////////////////////////////////////// - // Test 7+8: Cosine x-profiles, z-independent coefficients and compare with SPT method + // Test 6: Cosine x-profiles, z-independent coefficients const Field2D a_7 = DC(a_5); const Field2D c_7 = DC(c_5); @@ -200,11 +189,6 @@ int main(int argc, char** argv) { *invert, INVERT_AC_GRAD, INVERT_AC_GRAD, a_7, c_7, d_7, b_7, f_5, mesh->ystart, dump); - check_laplace(++test_num, - "different profiles, with coefficients constant in z, default solver", - *invert_SPT, INVERT_AC_GRAD, INVERT_AC_GRAD | INVERT_DC_GRAD, a_7, c_7, - d_7, b_7, f_5, mesh->ystart, dump); - // Write and close the output file bout::writeDefaultOutputFile(dump); From f6106a255f36c6f11e47ab8a53993e140d5e8660 Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 24 Feb 2025 13:38:40 +0100 Subject: [PATCH 196/242] Allow to overwrite MYG with options. --- src/mesh/impls/bout/boutmesh.cxx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/mesh/impls/bout/boutmesh.cxx b/src/mesh/impls/bout/boutmesh.cxx index 909754d507..5c61e23554 100644 --- a/src/mesh/impls/bout/boutmesh.cxx +++ b/src/mesh/impls/bout/boutmesh.cxx @@ -493,8 +493,18 @@ int BoutMesh::load() { } ASSERT0(MXG >= 0); - if (Mesh::get(MYG, "MYG") != 0) { - MYG = options["MYG"].doc("Number of guard cells on each side in Y").withDefault(2); + bool meshHasMyg = Mesh::get(MYG, "MYG") == 0; + int meshMyg; + if (!meshHasMyg) { + MYG = 2; + } else { + meshMyg = MYG; + } + MYG = options["MYG"].doc("Number of guard cells on each side in Y").withDefault(MYG); + if (meshHasMyg && MYG != meshMyg) { + output_warn.write(_("Options changed the number of y-guard cells. Grid has {} but " + "option specified {}! Continuing with {}"), + meshMyg, MYG, MYG); } ASSERT0(MYG >= 0); From 8665a6752d57f6107cf1fe701d03764b319221d3 Mon Sep 17 00:00:00 2001 From: David Bold Date: Thu, 27 Feb 2025 09:44:36 +0100 Subject: [PATCH 197/242] Revert "Disable metric components that require y-derivatives for fci" This reverts commit 84853532ff7078379c798d205b995a917492ebbf. The parallel metric components are loaded, thus we can take meaningful derivatives in y-direction. --- src/mesh/coordinates.cxx | 234 +++++++++++++++++++-------------------- 1 file changed, 112 insertions(+), 122 deletions(-) diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index 4db84601af..53ec4e9d16 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -977,129 +977,119 @@ int Coordinates::geometry(bool recalculate_staggered, checkContravariant(); checkCovariant(); - if (g_11.isFci()) { - // for FCI the y derivatives of metric components is meaningless. - G1_11 = G1_22 = G1_33 = G1_12 = G1_13 = G1_23 = + // Calculate Christoffel symbol terms (18 independent values) + // Note: This calculation is completely general: metric + // tensor can be 2D or 3D. For 2D, all DDZ terms are zero + + G1_11 = 0.5 * g11 * DDX(g_11) + g12 * (DDX(g_12) - 0.5 * DDY(g_11)) + + g13 * (DDX(g_13) - 0.5 * DDZ(g_11)); + G1_22 = g11 * (DDY(g_12) - 0.5 * DDX(g_22)) + 0.5 * g12 * DDY(g_22) + + g13 * (DDY(g_23) - 0.5 * DDZ(g_22)); + G1_33 = g11 * (DDZ(g_13) - 0.5 * DDX(g_33)) + g12 * (DDZ(g_23) - 0.5 * DDY(g_33)) + + 0.5 * g13 * DDZ(g_33); + G1_12 = 0.5 * g11 * DDY(g_11) + 0.5 * g12 * DDX(g_22) + + 0.5 * g13 * (DDY(g_13) + DDX(g_23) - DDZ(g_12)); + G1_13 = 0.5 * g11 * DDZ(g_11) + 0.5 * g12 * (DDZ(g_12) + DDX(g_23) - DDY(g_13)) + + 0.5 * g13 * DDX(g_33); + G1_23 = 0.5 * g11 * (DDZ(g_12) + DDY(g_13) - DDX(g_23)) + + 0.5 * g12 * (DDZ(g_22) + DDY(g_23) - DDY(g_23)) + // + 0.5 *g13*(DDZ(g_32) + DDY(g_33) - DDZ(g_23)); + // which equals + + 0.5 * g13 * DDY(g_33); + + G2_11 = 0.5 * g12 * DDX(g_11) + g22 * (DDX(g_12) - 0.5 * DDY(g_11)) + + g23 * (DDX(g_13) - 0.5 * DDZ(g_11)); + G2_22 = g12 * (DDY(g_12) - 0.5 * DDX(g_22)) + 0.5 * g22 * DDY(g_22) + + g23 * (DDY(g23) - 0.5 * DDZ(g_22)); + G2_33 = g12 * (DDZ(g_13) - 0.5 * DDX(g_33)) + g22 * (DDZ(g_23) - 0.5 * DDY(g_33)) + + 0.5 * g23 * DDZ(g_33); + G2_12 = 0.5 * g12 * DDY(g_11) + 0.5 * g22 * DDX(g_22) + + 0.5 * g23 * (DDY(g_13) + DDX(g_23) - DDZ(g_12)); + G2_13 = + // 0.5 *g21*(DDZ(g_11) + DDX(g_13) - DDX(g_13)) + // which equals + 0.5 * g12 * (DDZ(g_11) + DDX(g_13) - DDX(g_13)) + // + 0.5 *g22*(DDZ(g_21) + DDX(g_23) - DDY(g_13)) + // which equals + + 0.5 * g22 * (DDZ(g_12) + DDX(g_23) - DDY(g_13)) + // + 0.5 *g23*(DDZ(g_31) + DDX(g_33) - DDZ(g_13)); + // which equals + + 0.5 * g23 * DDX(g_33); + G2_23 = 0.5 * g12 * (DDZ(g_12) + DDY(g_13) - DDX(g_23)) + 0.5 * g22 * DDZ(g_22) + + 0.5 * g23 * DDY(g_33); + + G3_11 = 0.5 * g13 * DDX(g_11) + g23 * (DDX(g_12) - 0.5 * DDY(g_11)) + + g33 * (DDX(g_13) - 0.5 * DDZ(g_11)); + G3_22 = g13 * (DDY(g_12) - 0.5 * DDX(g_22)) + 0.5 * g23 * DDY(g_22) + + g33 * (DDY(g_23) - 0.5 * DDZ(g_22)); + G3_33 = g13 * (DDZ(g_13) - 0.5 * DDX(g_33)) + g23 * (DDZ(g_23) - 0.5 * DDY(g_33)) + + 0.5 * g33 * DDZ(g_33); + G3_12 = + // 0.5 *g31*(DDY(g_11) + DDX(g_12) - DDX(g_12)) + // which equals to + 0.5 * g13 * DDY(g_11) + // + 0.5 *g32*(DDY(g_21) + DDX(g_22) - DDY(g_12)) + // which equals to + + 0.5 * g23 * DDX(g_22) + //+ 0.5 *g33*(DDY(g_31) + DDX(g_32) - DDZ(g_12)); + // which equals to + + 0.5 * g33 * (DDY(g_13) + DDX(g_23) - DDZ(g_12)); + G3_13 = 0.5 * g13 * DDZ(g_11) + 0.5 * g23 * (DDZ(g_12) + DDX(g_23) - DDY(g_13)) + + 0.5 * g33 * DDX(g_33); + G3_23 = 0.5 * g13 * (DDZ(g_12) + DDY(g_13) - DDX(g_23)) + 0.5 * g23 * DDZ(g_22) + + 0.5 * g33 * DDY(g_33); + + auto tmp = J * g12; + communicate(tmp); + G1 = (DDX(J * g11) + DDY(tmp) + DDZ(J * g13)) / J; + tmp = J * g22; + communicate(tmp); + G2 = (DDX(J * g12) + DDY(tmp) + DDZ(J * g23)) / J; + tmp = J * g23; + communicate(tmp); + G3 = (DDX(J * g13) + DDY(tmp) + DDZ(J * g33)) / J; + + // Communicate christoffel symbol terms + output_progress.write("\tCommunicating connection terms\n"); + + communicate(G1_11, G1_22, G1_33, G1_12, G1_13, G1_23, G2_11, G2_22, G2_33, G2_12, G2_13, + G2_23, G3_11, G3_22, G3_33, G3_12, G3_13, G3_23, G1, G2, G3); + + // Set boundary guard cells of Christoffel symbol terms + // Ideally, when location is staggered, we would set the upper/outer boundary point + // correctly rather than by extrapolating here: e.g. if location==CELL_YLOW and we are + // at the upper y-boundary the x- and z-derivatives at yend+1 at the boundary can be + // calculated because the guard cells are available, while the y-derivative could be + // calculated from the CELL_CENTRE metric components (which have guard cells available + // past the boundary location). This would avoid the problem that the y-boundary on the + // CELL_YLOW grid is at a 'guard cell' location (yend+1). + // However, the above would require lots of special handling, so just extrapolate for + // now. + G1_11 = interpolateAndExtrapolate(G1_11, location, true, true, true, transform.get()); + G1_22 = interpolateAndExtrapolate(G1_22, location, true, true, true, transform.get()); + G1_33 = interpolateAndExtrapolate(G1_33, location, true, true, true, transform.get()); + G1_12 = interpolateAndExtrapolate(G1_12, location, true, true, true, transform.get()); + G1_13 = interpolateAndExtrapolate(G1_13, location, true, true, true, transform.get()); + G1_23 = interpolateAndExtrapolate(G1_23, location, true, true, true, transform.get()); + + G2_11 = interpolateAndExtrapolate(G2_11, location, true, true, true, transform.get()); + G2_22 = interpolateAndExtrapolate(G2_22, location, true, true, true, transform.get()); + G2_33 = interpolateAndExtrapolate(G2_33, location, true, true, true, transform.get()); + G2_12 = interpolateAndExtrapolate(G2_12, location, true, true, true, transform.get()); + G2_13 = interpolateAndExtrapolate(G2_13, location, true, true, true, transform.get()); + G2_23 = interpolateAndExtrapolate(G2_23, location, true, true, true, transform.get()); + + G3_11 = interpolateAndExtrapolate(G3_11, location, true, true, true, transform.get()); + G3_22 = interpolateAndExtrapolate(G3_22, location, true, true, true, transform.get()); + G3_33 = interpolateAndExtrapolate(G3_33, location, true, true, true, transform.get()); + G3_12 = interpolateAndExtrapolate(G3_12, location, true, true, true, transform.get()); + G3_13 = interpolateAndExtrapolate(G3_13, location, true, true, true, transform.get()); + G3_23 = interpolateAndExtrapolate(G3_23, location, true, true, true, transform.get()); + + G1 = interpolateAndExtrapolate(G1, location, true, true, true, transform.get()); + G2 = interpolateAndExtrapolate(G2, location, true, true, true, transform.get()); + G3 = interpolateAndExtrapolate(G3, location, true, true, true, transform.get()); - G2_11 = G2_22 = G2_33 = G2_12 = G2_13 = G2_23 = - - G3_11 = G3_22 = G3_33 = G3_12 = G3_13 = G3_23 = - - G1 = G2 = G3 = BoutNaN; - } else { - // Calculate Christoffel symbol terms (18 independent values) - // Note: This calculation is completely general: metric - // tensor can be 2D or 3D. For 2D, all DDZ terms are zero - - G1_11 = 0.5 * g11 * DDX(g_11) + g12 * (DDX(g_12) - 0.5 * DDY(g_11)) - + g13 * (DDX(g_13) - 0.5 * DDZ(g_11)); - G1_22 = g11 * (DDY(g_12) - 0.5 * DDX(g_22)) + 0.5 * g12 * DDY(g_22) - + g13 * (DDY(g_23) - 0.5 * DDZ(g_22)); - G1_33 = g11 * (DDZ(g_13) - 0.5 * DDX(g_33)) + g12 * (DDZ(g_23) - 0.5 * DDY(g_33)) - + 0.5 * g13 * DDZ(g_33); - G1_12 = 0.5 * g11 * DDY(g_11) + 0.5 * g12 * DDX(g_22) - + 0.5 * g13 * (DDY(g_13) + DDX(g_23) - DDZ(g_12)); - G1_13 = 0.5 * g11 * DDZ(g_11) + 0.5 * g12 * (DDZ(g_12) + DDX(g_23) - DDY(g_13)) - + 0.5 * g13 * DDX(g_33); - G1_23 = 0.5 * g11 * (DDZ(g_12) + DDY(g_13) - DDX(g_23)) - + 0.5 * g12 * (DDZ(g_22) + DDY(g_23) - DDY(g_23)) - // + 0.5 *g13*(DDZ(g_32) + DDY(g_33) - DDZ(g_23)); - // which equals - + 0.5 * g13 * DDY(g_33); - - G2_11 = 0.5 * g12 * DDX(g_11) + g22 * (DDX(g_12) - 0.5 * DDY(g_11)) - + g23 * (DDX(g_13) - 0.5 * DDZ(g_11)); - G2_22 = g12 * (DDY(g_12) - 0.5 * DDX(g_22)) + 0.5 * g22 * DDY(g_22) - + g23 * (DDY(g23) - 0.5 * DDZ(g_22)); - G2_33 = g12 * (DDZ(g_13) - 0.5 * DDX(g_33)) + g22 * (DDZ(g_23) - 0.5 * DDY(g_33)) - + 0.5 * g23 * DDZ(g_33); - G2_12 = 0.5 * g12 * DDY(g_11) + 0.5 * g22 * DDX(g_22) - + 0.5 * g23 * (DDY(g_13) + DDX(g_23) - DDZ(g_12)); - G2_13 = - // 0.5 *g21*(DDZ(g_11) + DDX(g_13) - DDX(g_13)) - // which equals - 0.5 * g12 * (DDZ(g_11) + DDX(g_13) - DDX(g_13)) - // + 0.5 *g22*(DDZ(g_21) + DDX(g_23) - DDY(g_13)) - // which equals - + 0.5 * g22 * (DDZ(g_12) + DDX(g_23) - DDY(g_13)) - // + 0.5 *g23*(DDZ(g_31) + DDX(g_33) - DDZ(g_13)); - // which equals - + 0.5 * g23 * DDX(g_33); - G2_23 = 0.5 * g12 * (DDZ(g_12) + DDY(g_13) - DDX(g_23)) + 0.5 * g22 * DDZ(g_22) - + 0.5 * g23 * DDY(g_33); - - G3_11 = 0.5 * g13 * DDX(g_11) + g23 * (DDX(g_12) - 0.5 * DDY(g_11)) - + g33 * (DDX(g_13) - 0.5 * DDZ(g_11)); - G3_22 = g13 * (DDY(g_12) - 0.5 * DDX(g_22)) + 0.5 * g23 * DDY(g_22) - + g33 * (DDY(g_23) - 0.5 * DDZ(g_22)); - G3_33 = g13 * (DDZ(g_13) - 0.5 * DDX(g_33)) + g23 * (DDZ(g_23) - 0.5 * DDY(g_33)) - + 0.5 * g33 * DDZ(g_33); - G3_12 = - // 0.5 *g31*(DDY(g_11) + DDX(g_12) - DDX(g_12)) - // which equals to - 0.5 * g13 * DDY(g_11) - // + 0.5 *g32*(DDY(g_21) + DDX(g_22) - DDY(g_12)) - // which equals to - + 0.5 * g23 * DDX(g_22) - //+ 0.5 *g33*(DDY(g_31) + DDX(g_32) - DDZ(g_12)); - // which equals to - + 0.5 * g33 * (DDY(g_13) + DDX(g_23) - DDZ(g_12)); - G3_13 = 0.5 * g13 * DDZ(g_11) + 0.5 * g23 * (DDZ(g_12) + DDX(g_23) - DDY(g_13)) - + 0.5 * g33 * DDX(g_33); - G3_23 = 0.5 * g13 * (DDZ(g_12) + DDY(g_13) - DDX(g_23)) + 0.5 * g23 * DDZ(g_22) - + 0.5 * g33 * DDY(g_33); - - auto tmp = J * g12; - communicate(tmp); - G1 = (DDX(J * g11) + DDY(tmp) + DDZ(J * g13)) / J; - tmp = J * g22; - communicate(tmp); - G2 = (DDX(J * g12) + DDY(tmp) + DDZ(J * g23)) / J; - tmp = J * g23; - communicate(tmp); - G3 = (DDX(J * g13) + DDY(tmp) + DDZ(J * g33)) / J; - - // Communicate christoffel symbol terms - output_progress.write("\tCommunicating connection terms\n"); - - communicate(G1_11, G1_22, G1_33, G1_12, G1_13, G1_23, G2_11, G2_22, G2_33, G2_12, - G2_13, G2_23, G3_11, G3_22, G3_33, G3_12, G3_13, G3_23, G1, G2, G3); - - // Set boundary guard cells of Christoffel symbol terms - // Ideally, when location is staggered, we would set the upper/outer boundary point - // correctly rather than by extrapolating here: e.g. if location==CELL_YLOW and we are - // at the upper y-boundary the x- and z-derivatives at yend+1 at the boundary can be - // calculated because the guard cells are available, while the y-derivative could be - // calculated from the CELL_CENTRE metric components (which have guard cells available - // past the boundary location). This would avoid the problem that the y-boundary on the - // CELL_YLOW grid is at a 'guard cell' location (yend+1). - // However, the above would require lots of special handling, so just extrapolate for - // now. - G1_11 = interpolateAndExtrapolate(G1_11, location, true, true, true, transform.get()); - G1_22 = interpolateAndExtrapolate(G1_22, location, true, true, true, transform.get()); - G1_33 = interpolateAndExtrapolate(G1_33, location, true, true, true, transform.get()); - G1_12 = interpolateAndExtrapolate(G1_12, location, true, true, true, transform.get()); - G1_13 = interpolateAndExtrapolate(G1_13, location, true, true, true, transform.get()); - G1_23 = interpolateAndExtrapolate(G1_23, location, true, true, true, transform.get()); - - G2_11 = interpolateAndExtrapolate(G2_11, location, true, true, true, transform.get()); - G2_22 = interpolateAndExtrapolate(G2_22, location, true, true, true, transform.get()); - G2_33 = interpolateAndExtrapolate(G2_33, location, true, true, true, transform.get()); - G2_12 = interpolateAndExtrapolate(G2_12, location, true, true, true, transform.get()); - G2_13 = interpolateAndExtrapolate(G2_13, location, true, true, true, transform.get()); - G2_23 = interpolateAndExtrapolate(G2_23, location, true, true, true, transform.get()); - - G3_11 = interpolateAndExtrapolate(G3_11, location, true, true, true, transform.get()); - G3_22 = interpolateAndExtrapolate(G3_22, location, true, true, true, transform.get()); - G3_33 = interpolateAndExtrapolate(G3_33, location, true, true, true, transform.get()); - G3_12 = interpolateAndExtrapolate(G3_12, location, true, true, true, transform.get()); - G3_13 = interpolateAndExtrapolate(G3_13, location, true, true, true, transform.get()); - G3_23 = interpolateAndExtrapolate(G3_23, location, true, true, true, transform.get()); - - G1 = interpolateAndExtrapolate(G1, location, true, true, true, transform.get()); - G2 = interpolateAndExtrapolate(G2, location, true, true, true, transform.get()); - G3 = interpolateAndExtrapolate(G3, location, true, true, true, transform.get()); - } ////////////////////////////////////////////////////// /// Non-uniform meshes. Need to use DDX, DDY From ff711821172a0ac30b1a5559974f84c7028c6359 Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 3 Mar 2025 10:01:55 +0100 Subject: [PATCH 198/242] Add iter_pnts function Directly iterate over the points --- include/bout/yboundary_regions.hxx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/include/bout/yboundary_regions.hxx b/include/bout/yboundary_regions.hxx index e0e93e17f9..c58a7a59b7 100644 --- a/include/bout/yboundary_regions.hxx +++ b/include/bout/yboundary_regions.hxx @@ -5,8 +5,8 @@ class YBoundary { public: - template - void iter_regions(const T& f) { + template + void iter_regions(const F& f) { ASSERT1(is_init); for (auto& region : boundary_regions) { f(*region); @@ -15,6 +15,14 @@ public: f(*region); } } + template + void iter_pnts(const F& f) { + iter_regions([&](auto& region) { + for (auto& pnt : region) { + f(pnt); + } + } + } template void iter(const F& f) { From d3bc5cc6e50e674019bb73e4319d6a81a6bba072 Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 3 Mar 2025 10:02:46 +0100 Subject: [PATCH 199/242] Add example on how to replace RangeIterator with YBoundary --- include/bout/example_yboundary_regions.cxx | 65 ++++++++++++++++++++++ include/bout/yboundary_regions.hxx | 13 +++++ 2 files changed, 78 insertions(+) create mode 100644 include/bout/example_yboundary_regions.cxx diff --git a/include/bout/example_yboundary_regions.cxx b/include/bout/example_yboundary_regions.cxx new file mode 100644 index 0000000000..0e8d9b07dc --- /dev/null +++ b/include/bout/example_yboundary_regions.cxx @@ -0,0 +1,65 @@ +#include + +class yboundary_example_legacy { +public: + yboundary_example_legacy(Options* opt, const Field3D& N, const Field3D& V) + : N(N), V(V) { + Options& options = *opt; + lower_y = options["lower_y"].doc("Boundary on lower y?").withDefault(lower_y); + upper_y = options["upper_y"].doc("Boundary on upper y?").withDefault(upper_y); + } + + void rhs() { + BoutReal totalFlux = 0; + if (lower_y) { + for (RangeIterator r = mesh->iterateBndryLowerY(); !r.isDone(); r++) { + for (int jz = 0; jz < mesh->LocalNz; jz++) { + // Calculate flux through surface [normalised m^-2 s^-1], + // should be positive since V < 0.0 + BoutReal flux = + -0.5 * (N(r.ind, mesh->ystart, jz) + N(r.ind, mesh->ystart - 1, jz)) * 0.5 + * (V(r.ind, mesh->ystart, jz) + V(r.ind, mesh->ystart - 1, jz)); + totalFlux += flux; + } + } + } + if (upper_y) { + for (RangeIterator r = mesh->iterateBndryUpperY(); !r.isDone(); r++) { + for (int jz = 0; jz < mesh->LocalNz; jz++) { + // Calculate flux through surface [normalised m^-2 s^-1], + // should be positive since V < 0.0 + BoutReal flux = -0.5 * (N(r.ind, mesh->yend, jz) + N(r.ind, mesh->yend + 1, jz)) + * 0.5 + * (V(r.ind, mesh->yend, jz) + V(r.ind, mesh->yend + 1, jz)); + totalFlux += flux; + } + } + } + } + +private: + bool lower_y{true}; + bool upper_y{true}; + const Field3D& N; + const Field3D& V; +} + +class yboundary_example { +public: + yboundary_example(Options* opt, const Field3D& N, const Field3D& V) : N(N), V(V) { + // Set what kind of yboundaries you want to include + yboundary.init(opt); + } + + void rhs() { + BoutReal totalFlux = 0; + yboundary.iter_pnts([&](auto& pnt) { + BoutReal flux = pnt.interpolate_sheath_o1(N) * pnt.interpolate_sheath_o1(V); + }); + } + +private: + YBoundary ybounday; + const Field3D& N; + const Field3D& V; +}; diff --git a/include/bout/yboundary_regions.hxx b/include/bout/yboundary_regions.hxx index c58a7a59b7..67fdebc823 100644 --- a/include/bout/yboundary_regions.hxx +++ b/include/bout/yboundary_regions.hxx @@ -2,6 +2,19 @@ #include "./boundary_iterator.hxx" #include "bout/parallel_boundary_region.hxx" +/*! + * This class allows to simplify iterating over y-boundaries. + * + * It makes it easier to write code for FieldAligned boundaries, but if a bit + * care is taken the code also works with FluxCoordinateIndependent code. + * + * An example how to replace old code is given here: + * + * \example example_yboundary_regions.hxx + * This is an example how to use the YBoundary class to replace RangeIterator + * boundaries. + */ + class YBoundary { public: From 83375317790c7a4d7a3299ea1a2bafb0080eb2ba Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 3 Mar 2025 11:25:50 +0100 Subject: [PATCH 200/242] Move documentation to sphinx --- include/bout/example_yboundary_regions.cxx | 65 ---------------- include/bout/yboundary_regions.hxx | 21 +++--- manual/sphinx/user_docs/boundary_options.rst | 79 ++++++++++++++++++++ 3 files changed, 88 insertions(+), 77 deletions(-) delete mode 100644 include/bout/example_yboundary_regions.cxx diff --git a/include/bout/example_yboundary_regions.cxx b/include/bout/example_yboundary_regions.cxx deleted file mode 100644 index 0e8d9b07dc..0000000000 --- a/include/bout/example_yboundary_regions.cxx +++ /dev/null @@ -1,65 +0,0 @@ -#include - -class yboundary_example_legacy { -public: - yboundary_example_legacy(Options* opt, const Field3D& N, const Field3D& V) - : N(N), V(V) { - Options& options = *opt; - lower_y = options["lower_y"].doc("Boundary on lower y?").withDefault(lower_y); - upper_y = options["upper_y"].doc("Boundary on upper y?").withDefault(upper_y); - } - - void rhs() { - BoutReal totalFlux = 0; - if (lower_y) { - for (RangeIterator r = mesh->iterateBndryLowerY(); !r.isDone(); r++) { - for (int jz = 0; jz < mesh->LocalNz; jz++) { - // Calculate flux through surface [normalised m^-2 s^-1], - // should be positive since V < 0.0 - BoutReal flux = - -0.5 * (N(r.ind, mesh->ystart, jz) + N(r.ind, mesh->ystart - 1, jz)) * 0.5 - * (V(r.ind, mesh->ystart, jz) + V(r.ind, mesh->ystart - 1, jz)); - totalFlux += flux; - } - } - } - if (upper_y) { - for (RangeIterator r = mesh->iterateBndryUpperY(); !r.isDone(); r++) { - for (int jz = 0; jz < mesh->LocalNz; jz++) { - // Calculate flux through surface [normalised m^-2 s^-1], - // should be positive since V < 0.0 - BoutReal flux = -0.5 * (N(r.ind, mesh->yend, jz) + N(r.ind, mesh->yend + 1, jz)) - * 0.5 - * (V(r.ind, mesh->yend, jz) + V(r.ind, mesh->yend + 1, jz)); - totalFlux += flux; - } - } - } - } - -private: - bool lower_y{true}; - bool upper_y{true}; - const Field3D& N; - const Field3D& V; -} - -class yboundary_example { -public: - yboundary_example(Options* opt, const Field3D& N, const Field3D& V) : N(N), V(V) { - // Set what kind of yboundaries you want to include - yboundary.init(opt); - } - - void rhs() { - BoutReal totalFlux = 0; - yboundary.iter_pnts([&](auto& pnt) { - BoutReal flux = pnt.interpolate_sheath_o1(N) * pnt.interpolate_sheath_o1(V); - }); - } - -private: - YBoundary ybounday; - const Field3D& N; - const Field3D& V; -}; diff --git a/include/bout/yboundary_regions.hxx b/include/bout/yboundary_regions.hxx index 67fdebc823..f9ee0ff21c 100644 --- a/include/bout/yboundary_regions.hxx +++ b/include/bout/yboundary_regions.hxx @@ -2,18 +2,15 @@ #include "./boundary_iterator.hxx" #include "bout/parallel_boundary_region.hxx" -/*! - * This class allows to simplify iterating over y-boundaries. - * - * It makes it easier to write code for FieldAligned boundaries, but if a bit - * care is taken the code also works with FluxCoordinateIndependent code. - * - * An example how to replace old code is given here: - * - * \example example_yboundary_regions.hxx - * This is an example how to use the YBoundary class to replace RangeIterator - * boundaries. - */ + +/// This class allows to simplify iterating over y-boundaries. +/// +/// It makes it easier to write code for FieldAligned boundaries, but if a bit +/// care is taken the code also works with FluxCoordinateIndependent code. +/// +/// An example how to replace old code is given here: +/// ../../manual/sphinx/user_docs/boundary_options.rst + class YBoundary { diff --git a/manual/sphinx/user_docs/boundary_options.rst b/manual/sphinx/user_docs/boundary_options.rst index 826f873dc1..d3cea5edb6 100644 --- a/manual/sphinx/user_docs/boundary_options.rst +++ b/manual/sphinx/user_docs/boundary_options.rst @@ -435,6 +435,85 @@ the upper Y boundary of a 2D variable ``var``:: The `BoundaryRegion` class is defined in ``include/boundary_region.hxx`` +Y-Boundaries +------------ + +The sheath boundaries are often implemented in the physics model. +Previously of they where implemented using a `RangeIterator`:: + + class yboundary_example_legacy { + public: + yboundary_example_legacy(Options* opt, const Field3D& N, const Field3D& V) + : N(N), V(V) { + Options& options = *opt; + lower_y = options["lower_y"].doc("Boundary on lower y?").withDefault(lower_y); + upper_y = options["upper_y"].doc("Boundary on upper y?").withDefault(upper_y); + } + + void rhs() { + BoutReal totalFlux = 0; + if (lower_y) { + for (RangeIterator r = mesh->iterateBndryLowerY(); !r.isDone(); r++) { + for (int jz = 0; jz < mesh->LocalNz; jz++) { + // Calculate flux through surface [normalised m^-2 s^-1], + // should be positive since V < 0.0 + BoutReal flux = + -0.5 * (N(r.ind, mesh->ystart, jz) + N(r.ind, mesh->ystart - 1, jz)) * 0.5 + * (V(r.ind, mesh->ystart, jz) + V(r.ind, mesh->ystart - 1, jz)); + totalFlux += flux; + } + } + } + if (upper_y) { + for (RangeIterator r = mesh->iterateBndryUpperY(); !r.isDone(); r++) { + for (int jz = 0; jz < mesh->LocalNz; jz++) { + // Calculate flux through surface [normalised m^-2 s^-1], + // should be positive since V < 0.0 + BoutReal flux = -0.5 * (N(r.ind, mesh->yend, jz) + N(r.ind, mesh->yend + 1, jz)) + * 0.5 + * (V(r.ind, mesh->yend, jz) + V(r.ind, mesh->yend + 1, jz)); + totalFlux += flux; + } + } + } + } + + private: + bool lower_y{true}; + bool upper_y{true}; + const Field3D& N; + const Field3D& V; + } + + +This can be replaced using the `YBoundary` class, which not only simplifies the +code, but also allows to have the same code working with non-field-aligned +geometries, as flux coordinate independent (FCI) method:: + + #include + + class yboundary_example { + public: + yboundary_example(Options* opt, const Field3D& N, const Field3D& V) : N(N), V(V) { + // Set what kind of yboundaries you want to include + yboundary.init(opt); + } + + void rhs() { + BoutReal totalFlux = 0; + yboundary.iter_pnts([&](auto& pnt) { + BoutReal flux = pnt.interpolate_sheath_o1(N) * pnt.interpolate_sheath_o1(V); + }); + } + + private: + YBoundary ybounday; + const Field3D& N; + const Field3D& V; + }; + + + Boundary regions ---------------- From fcc3af65263ee8e227868fb9d10b9466aab8333a Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 3 Mar 2025 13:40:58 +0100 Subject: [PATCH 201/242] Only read `MYG` if it set or mesh:MYG is not set This avoids errors in the MMS tests --- src/mesh/impls/bout/boutmesh.cxx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mesh/impls/bout/boutmesh.cxx b/src/mesh/impls/bout/boutmesh.cxx index 5c61e23554..d81bd10698 100644 --- a/src/mesh/impls/bout/boutmesh.cxx +++ b/src/mesh/impls/bout/boutmesh.cxx @@ -500,7 +500,9 @@ int BoutMesh::load() { } else { meshMyg = MYG; } - MYG = options["MYG"].doc("Number of guard cells on each side in Y").withDefault(MYG); + if (options.isSet("MYG") or (!meshHasMyg)) { + MYG = options["MYG"].doc("Number of guard cells on each side in Y").withDefault(MYG); + } if (meshHasMyg && MYG != meshMyg) { output_warn.write(_("Options changed the number of y-guard cells. Grid has {} but " "option specified {}! Continuing with {}"), From f925c94f3d6cc70975ca4a602fcac2d88928bf07 Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 3 Mar 2025 14:02:54 +0100 Subject: [PATCH 202/242] Do not set MYG/MXG if it is not needed --- tests/integrated/test-boutpp/mms-ddz/data/BOUT.inp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/integrated/test-boutpp/mms-ddz/data/BOUT.inp b/tests/integrated/test-boutpp/mms-ddz/data/BOUT.inp index d5ca4c4d71..519faa0403 100644 --- a/tests/integrated/test-boutpp/mms-ddz/data/BOUT.inp +++ b/tests/integrated/test-boutpp/mms-ddz/data/BOUT.inp @@ -1,7 +1,3 @@ - -MXG = 2 -MYG = 2 - [mesh] staggergrids = true n = 1 From 51e7b58991b10ff033813980b547f9af03d84675 Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 3 Mar 2025 14:03:41 +0100 Subject: [PATCH 203/242] Do not set MYG/MXG if it is not needed --- tests/integrated/test-boutpp/collect/input/BOUT.inp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/integrated/test-boutpp/collect/input/BOUT.inp b/tests/integrated/test-boutpp/collect/input/BOUT.inp index d5ca4c4d71..519faa0403 100644 --- a/tests/integrated/test-boutpp/collect/input/BOUT.inp +++ b/tests/integrated/test-boutpp/collect/input/BOUT.inp @@ -1,7 +1,3 @@ - -MXG = 2 -MYG = 2 - [mesh] staggergrids = true n = 1 From 1c8fb4737a52805a128a002ac382ab0fa825380c Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 4 Mar 2025 15:51:40 +0100 Subject: [PATCH 204/242] Fix iter_pnts --- include/bout/yboundary_regions.hxx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/include/bout/yboundary_regions.hxx b/include/bout/yboundary_regions.hxx index f9ee0ff21c..1d434e2420 100644 --- a/include/bout/yboundary_regions.hxx +++ b/include/bout/yboundary_regions.hxx @@ -11,8 +11,6 @@ /// An example how to replace old code is given here: /// ../../manual/sphinx/user_docs/boundary_options.rst - - class YBoundary { public: template @@ -29,9 +27,9 @@ public: void iter_pnts(const F& f) { iter_regions([&](auto& region) { for (auto& pnt : region) { - f(pnt); + f(pnt); } - } + }); } template @@ -80,5 +78,3 @@ private: bool is_init{false}; }; - -extern YBoundary yboundary; From bfaf9867b9d9c94155f25b606e65638dec575432 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 4 Mar 2025 15:52:00 +0100 Subject: [PATCH 205/242] Add more documentation on YBoundary --- manual/sphinx/user_docs/boundary_options.rst | 45 ++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/manual/sphinx/user_docs/boundary_options.rst b/manual/sphinx/user_docs/boundary_options.rst index d3cea5edb6..e2d8f28b3f 100644 --- a/manual/sphinx/user_docs/boundary_options.rst +++ b/manual/sphinx/user_docs/boundary_options.rst @@ -514,6 +514,51 @@ geometries, as flux coordinate independent (FCI) method:: +There are several member functions of ``pnt``. ``pnt`` is of type +`BoundaryRegionParIterBase` and `BoundaryRegionIter`, and both should provide +the same interface. If they don't that is a bug, as the above code is a +template, that gets instantiated for both types, and thus requires both +classes to provide the same interface, one for FCI-like boundaries and one for +field aligned boundaries. + +Here is a short summary of some members of ``pnt``, where ``f`` is a : + +.. list-table:: Members for boundary operation + :widths: 15 70 + :header-rows: 1 + + * - Function + - Description + * - ``pnt.ythis(f)`` + - Returns the value at the last point in the domain + * - ``pnt.ynext(f)`` + - Returns the value at the first point in the domain + * - ``pnt.yprev(f)`` + - Returns the value at the second to last point in the domain, if it is + valid. NB: this point may not be valid. + * - ``pnt.interpolate_sheath_o1(f)`` + - Returns the value at the boundary, assuming the bounday value has been set + * - ``pnt.extrapolate_sheath_o1(f)`` + - Returns the value at the boundary, extrapolating from the bulk, first order + * - ``pnt.extrapolate_sheath_o2(f)`` + - Returns the value at the boundary, extrapolating from the bulk, second order + * - ``pnt.extrapolate_next_o{1,2}(f)`` + - Extrapolate into the boundary from the bulk, first or second order + * - ``pnt.extrapolate_grad_o{1,2}(f)`` + - Extrapolate the gradient into the boundary, first or second order + * - ``pnt.dirichlet_o{1,2,3}(f, v)`` + - Apply dirichlet boundary conditions with value ``v`` and given order + * - ``pnt.neumann_o{1,2,3}(f, v)`` + - Applies a gradient of ``v / dy`` boundary condition. + * - ``pnt.limitFree(f)`` + - Extrapolate into the boundary using only monotonic decreasing values. + ``f`` needs to be positive. + * - ``pnt.dir`` + - The direction of the boundary. + + + + Boundary regions ---------------- From f4acdb0bcbb3599d8e99d3cf4bee29b94bc1512e Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 7 Mar 2025 10:39:28 +0100 Subject: [PATCH 206/242] Ensure the field has parallel slices --- include/bout/parallel_boundary_region.hxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/bout/parallel_boundary_region.hxx b/include/bout/parallel_boundary_region.hxx index f808296edb..ad2bcd0331 100644 --- a/include/bout/parallel_boundary_region.hxx +++ b/include/bout/parallel_boundary_region.hxx @@ -231,6 +231,7 @@ public: template BoutReal& getAt(Field3D& f, int off) const { + ASSERT4(f.hasParallelSlices()); if constexpr (check) { ASSERT3(valid() > -off - 2); } @@ -239,6 +240,7 @@ public: } template const BoutReal& getAt(const Field3D& f, int off) const { + ASSERT4(f.hasParallelSlices()); if constexpr (check) { ASSERT3(valid() > -off - 2); } From 23f599230b36044a969e9658b68bf4f7f42d3a32 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 7 Mar 2025 11:30:36 +0100 Subject: [PATCH 207/242] Lower check level --- include/bout/parallel_boundary_region.hxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/bout/parallel_boundary_region.hxx b/include/bout/parallel_boundary_region.hxx index ad2bcd0331..849bb0ffe3 100644 --- a/include/bout/parallel_boundary_region.hxx +++ b/include/bout/parallel_boundary_region.hxx @@ -231,7 +231,7 @@ public: template BoutReal& getAt(Field3D& f, int off) const { - ASSERT4(f.hasParallelSlices()); + ASSERT3(f.hasParallelSlices()); if constexpr (check) { ASSERT3(valid() > -off - 2); } @@ -240,7 +240,7 @@ public: } template const BoutReal& getAt(const Field3D& f, int off) const { - ASSERT4(f.hasParallelSlices()); + ASSERT3(f.hasParallelSlices()); if constexpr (check) { ASSERT3(valid() > -off - 2); } From d05e7336a839c10b1687a0517ddb90cbf2786a1c Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 11 Mar 2025 15:02:12 +0100 Subject: [PATCH 208/242] Loosen tolereances again This partially reverts 0bcc047d64e87ca9446fe1127b761a58215201f5 It seems the achieved accuracy depends on some factors that are not well controlled. --- tests/integrated/test-petsc_laplace/runtest | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integrated/test-petsc_laplace/runtest b/tests/integrated/test-petsc_laplace/runtest index 87c3991d00..befb87c04e 100755 --- a/tests/integrated/test-petsc_laplace/runtest +++ b/tests/integrated/test-petsc_laplace/runtest @@ -11,10 +11,10 @@ # Variables to compare vars = [ ("max_error1", 2.0e-4), - ("max_error2", 2.0e-8), + ("max_error2", 2.0e-4), ("max_error3", 2.0e-4), ("max_error4", 2.0e-4), - ("max_error5", 4.0e-6), + ("max_error5", 2.0e-4), ("max_error6", 2.0e-4), ] From 35206d44c900eaae284e507110d79d5b6a531aea Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 11 Mar 2025 16:14:49 +0100 Subject: [PATCH 209/242] Remove broken code Likely this is not needed. --- src/mesh/coordinates.cxx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index 53ec4e9d16..d650c9e9ec 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -1512,14 +1512,6 @@ Coordinates::FieldMetric Coordinates::DDY(const Field2D& f, CELL_LOC loc, Field3D Coordinates::DDY(const Field3D& f, CELL_LOC outloc, const std::string& method, const std::string& region) const { -#if BOUT_USE_METRIC_3D - if (!f.hasParallelSlices() and !transform->canToFromFieldAligned()) { - Field3D f_parallel = f; - transform->calcParallelSlices(f_parallel); - f_parallel.applyParallelBoundary("parallel_neumann_o2"); - return bout::derivatives::index::DDY(f_parallel, outloc, method, region); - } -#endif return bout::derivatives::index::DDY(f, outloc, method, region) / dy; }; From df490b9278b601ce1ed47a0bef6f100e5a39f26e Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 12 Mar 2025 09:16:05 +0100 Subject: [PATCH 210/242] CI: Avoid issues with special characters --- .github/workflows/clang-format.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index a6508a2dcd..d99b370810 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -22,7 +22,7 @@ jobs: - name: Run clang-format id: format - run: git clang-format origin/${{ github.base_ref }} || : + run: 'git clang-format origin/${{ github.base_ref }} || :' - name: Commit to the PR branch uses: stefanzweifel/git-auto-commit-action@v5 From eef32f937436cffccabdbf733936a1be6f0d7e08 Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 12 Mar 2025 09:50:40 +0100 Subject: [PATCH 211/242] CI: run git-clang-format until there are no more changes That might format more code at once, but should avoid a CI loop. --- .github/workflows/clang-format.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index d99b370810..5e25154300 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -22,7 +22,11 @@ jobs: - name: Run clang-format id: format - run: 'git clang-format origin/${{ github.base_ref }} || :' + run: + while ! git clang-format origin/${{ github.base_ref }} + do + true + done - name: Commit to the PR branch uses: stefanzweifel/git-auto-commit-action@v5 From 9fd76bfe5c526cfa3d9984f6e761c235403d1723 Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 12 Mar 2025 11:06:31 +0100 Subject: [PATCH 212/242] CI: use one line --- .github/workflows/clang-format.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 5e25154300..3b8cf6ee50 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -23,10 +23,7 @@ jobs: - name: Run clang-format id: format run: - while ! git clang-format origin/${{ github.base_ref }} - do - true - done + while ! git clang-format origin/${{ github.base_ref }} ; do true ; done - name: Commit to the PR branch uses: stefanzweifel/git-auto-commit-action@v5 From ee9dc990700cac267bd4555ac499fe68d57dd8bc Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 12 Mar 2025 11:16:07 +0100 Subject: [PATCH 213/242] CI: stage before we run git-clang-format again --- .github/workflows/clang-format.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 3b8cf6ee50..49dfb31e25 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -23,7 +23,7 @@ jobs: - name: Run clang-format id: format run: - while ! git clang-format origin/${{ github.base_ref }} ; do true ; done + while ! git clang-format origin/${{ github.base_ref }} ; do git add . ; done - name: Commit to the PR branch uses: stefanzweifel/git-auto-commit-action@v5 From be5e285d1a29de658cf684d1a9229762cd24093e Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 14 Mar 2025 10:28:46 +0100 Subject: [PATCH 214/242] fix bad merge --- src/mesh/parallel/fci.cxx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mesh/parallel/fci.cxx b/src/mesh/parallel/fci.cxx index d8b0e95296..cdcaec3dd2 100644 --- a/src/mesh/parallel/fci.cxx +++ b/src/mesh/parallel/fci.cxx @@ -457,6 +457,7 @@ void FCITransform::loadParallelMetrics(Coordinates* coords) { load_parallel_metric_components(coords, -i); load_parallel_metric_components(coords, i); } +} void FCITransform::outputVars(Options& output_options) { // Real-space coordinates of grid points From 092c578d0cf3c4446d4531049936a0d279de108b Mon Sep 17 00:00:00 2001 From: dschwoerer <5637662+dschwoerer@users.noreply.github.com> Date: Fri, 14 Mar 2025 09:31:46 +0000 Subject: [PATCH 215/242] Apply clang-format changes --- include/bout/field.hxx | 38 ++-- include/bout/field3d.hxx | 2 +- include/bout/fv_ops.hxx | 2 +- include/bout/index_derivs_interface.hxx | 6 +- include/bout/interpolation_xz.hxx | 11 +- include/bout/mesh.hxx | 1 - include/bout/petsclib.hxx | 2 +- include/bout/physicsmodel.hxx | 1 + src/field/field3d.cxx | 20 +- .../laplace/impls/petsc/petsc_laplace.cxx | 189 +++++++++--------- src/mesh/boundary_standard.cxx | 2 +- src/mesh/coordinates.cxx | 9 +- src/mesh/fv_ops.cxx | 2 +- src/mesh/impls/bout/boutmesh.cxx | 4 +- src/mesh/interpolation/hermite_spline_xz.cxx | 7 +- src/mesh/interpolation/lagrange_4pt_xz.cxx | 1 - src/mesh/interpolation_xz.cxx | 4 +- src/mesh/parallel/fci.cxx | 21 +- src/mesh/parallel/fci.hxx | 1 + src/mesh/parallel/fci_comm.hxx | 4 +- src/solver/impls/pvode/pvode.cxx | 6 +- src/sys/options.cxx | 10 +- .../test-fci-boundary/get_par_bndry.cxx | 5 +- 23 files changed, 169 insertions(+), 179 deletions(-) diff --git a/include/bout/field.hxx b/include/bout/field.hxx index d56322070e..27835ecbd7 100644 --- a/include/bout/field.hxx +++ b/include/bout/field.hxx @@ -531,18 +531,18 @@ T pow(BoutReal lhs, const T& rhs, const std::string& rgn = "RGN_ALL") { #ifdef FIELD_FUNC #error This macro has already been defined #else -#define FIELD_FUNC(_name, func) \ - template > \ - inline T _name(const T& f, const std::string& rgn = "RGN_ALL") { \ - AUTO_TRACE(); \ - /* Check if the input is allocated */ \ - checkData(f); \ - /* Define and allocate the output result */ \ - T result{emptyFrom(f)}; \ - BOUT_FOR(d, result.getRegion(rgn)) { result[d] = func(f[d]); } \ - result.name = std::string(#_name "(") + f.name + std::string(")"); \ - checkData(result); \ - return result; \ +#define FIELD_FUNC(_name, func) \ + template > \ + inline T _name(const T& f, const std::string& rgn = "RGN_ALL") { \ + AUTO_TRACE(); \ + /* Check if the input is allocated */ \ + checkData(f); \ + /* Define and allocate the output result */ \ + T result{emptyFrom(f)}; \ + BOUT_FOR(d, result.getRegion(rgn)) { result[d] = func(f[d]); } \ + result.name = std::string(#_name "(") + f.name + std::string(")"); \ + checkData(result); \ + return result; \ } #endif @@ -685,16 +685,16 @@ inline T floor(const T& var, BoutReal f, const std::string& rgn = "RGN_ALL") { } #if BOUT_USE_FCI_AUTOMAGIC if (var.isFci()) { - for (size_t i=0; i < result.numberParallelSlices(); ++i) { + for (size_t i = 0; i < result.numberParallelSlices(); ++i) { BOUT_FOR(d, result.yup(i).getRegion(rgn)) { - if (result.yup(i)[d] < f) { - result.yup(i)[d] = f; - } + if (result.yup(i)[d] < f) { + result.yup(i)[d] = f; + } } BOUT_FOR(d, result.ydown(i).getRegion(rgn)) { - if (result.ydown(i)[d] < f) { - result.ydown(i)[d] = f; - } + if (result.ydown(i)[d] < f) { + result.ydown(i)[d] = f; + } } } } else diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index 2d4c2e243d..763c334cc1 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -488,7 +488,7 @@ public: friend class Vector2D; Field3D& calcParallelSlices(); - void allowParallelSlices([[maybe_unused]] bool allow){ + void allowParallelSlices([[maybe_unused]] bool allow) { #if CHECK > 0 allowCalcParallelSlices = allow; #endif diff --git a/include/bout/fv_ops.hxx b/include/bout/fv_ops.hxx index 97558ddcfb..8a9baaf3e7 100644 --- a/include/bout/fv_ops.hxx +++ b/include/bout/fv_ops.hxx @@ -10,8 +10,8 @@ #include "bout/vector2d.hxx" #include "bout/utils.hxx" -#include #include +#include namespace FV { /*! diff --git a/include/bout/index_derivs_interface.hxx b/include/bout/index_derivs_interface.hxx index 2c2c21d6cf..bc9a687b34 100644 --- a/include/bout/index_derivs_interface.hxx +++ b/include/bout/index_derivs_interface.hxx @@ -203,11 +203,13 @@ T DDY(const T& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "D if (f.isFci()) { ASSERT1(f.getDirectionY() == YDirectionType::Standard); T f_tmp = f; - if (!f.hasParallelSlices()){ + if (!f.hasParallelSlices()) { #if BOUT_USE_FCI_AUTOMAGIC f_tmp.calcParallelSlices(); #else - throw BoutException("parallel slices needed for parallel derivatives. Make sure to communicate and apply parallel boundary conditions before calling derivative"); + throw BoutException( + "parallel slices needed for parallel derivatives. Make sure to communicate and " + "apply parallel boundary conditions before calling derivative"); #endif } return standardDerivative(f_tmp, outloc, diff --git a/include/bout/interpolation_xz.hxx b/include/bout/interpolation_xz.hxx index fd4a4fcd50..9a7e788e67 100644 --- a/include/bout/interpolation_xz.hxx +++ b/include/bout/interpolation_xz.hxx @@ -134,7 +134,6 @@ public: } }; - template class XZHermiteSplineBase : public XZInterpolation { protected: @@ -282,19 +281,15 @@ public: const std::string& region = "RGN_NOBNDRY") override; }; - -class XZMonotonicHermiteSplineLegacy: public XZHermiteSplineBase { +class XZMonotonicHermiteSplineLegacy : public XZHermiteSplineBase { public: using XZHermiteSplineBase::interpolate; virtual Field3D interpolate(const Field3D& f, const std::string& region = "RGN_NOBNDRY") const override; - template - XZMonotonicHermiteSplineLegacy(Ts... args) : - XZHermiteSplineBase(args...) - {} + template + XZMonotonicHermiteSplineLegacy(Ts... args) : XZHermiteSplineBase(args...) {} }; - class XZInterpolationFactory : public Factory { public: diff --git a/include/bout/mesh.hxx b/include/bout/mesh.hxx index 563d792bf8..af1caad89b 100644 --- a/include/bout/mesh.hxx +++ b/include/bout/mesh.hxx @@ -842,7 +842,6 @@ public: return not coords->getParallelTransform().canToFromFieldAligned(); } - private: /// Allocates default Coordinates objects /// By default attempts to read staggered Coordinates from grid data source, diff --git a/include/bout/petsclib.hxx b/include/bout/petsclib.hxx index aa6f874f11..83e57184aa 100644 --- a/include/bout/petsclib.hxx +++ b/include/bout/petsclib.hxx @@ -156,7 +156,7 @@ private: #endif // PETSC_VERSION_GE -#if ! PETSC_VERSION_GE(3, 19, 0) +#if !PETSC_VERSION_GE(3, 19, 0) #define PETSC_SUCCESS ((PetscErrorCode)0) #endif diff --git a/include/bout/physicsmodel.hxx b/include/bout/physicsmodel.hxx index 7588b86f79..ff53bc6845 100644 --- a/include/bout/physicsmodel.hxx +++ b/include/bout/physicsmodel.hxx @@ -275,6 +275,7 @@ protected: public: /// Output additional variables other than the evolving variables virtual void outputVars(Options& options); + protected: /// Add additional variables other than the evolving variables to the restart files virtual void restartVars(Options& options); diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index 5980274e4e..85077a73ff 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -96,7 +96,7 @@ Field3D::Field3D(const BoutReal val, Mesh* localmesh) : Field3D(localmesh) { #if BOUT_USE_FCI_AUTOMAGIC if (this->isFci()) { splitParallelSlices(); - for (size_t i=0; igetRegionID(region_name); } -void Field3D::resetRegion() { - regionID.reset(); -}; -void Field3D::setRegion(size_t id) { - regionID = id; -}; -void Field3D::setRegion(std::optional id) { - regionID = id; -}; +void Field3D::resetRegion() { regionID.reset(); }; +void Field3D::setRegion(size_t id) { regionID = id; }; +void Field3D::setRegion(std::optional id) { regionID = id; }; Field3D& Field3D::enableTracking(const std::string& name, Options& _tracking) { tracking = &_tracking; @@ -929,9 +923,9 @@ Options* Field3D::track(const T& change, std::string operation) { const std::string changename = change.name; #endif (*tracking)[outname].setAttributes({ - {"operation", operation}, + {"operation", operation}, #if BOUT_USE_TRACK - {"rhs.name", changename}, + {"rhs.name", changename}, #endif }); return &(*tracking)[outname]; diff --git a/src/invert/laplace/impls/petsc/petsc_laplace.cxx b/src/invert/laplace/impls/petsc/petsc_laplace.cxx index 19978939f6..bcfe6264d7 100644 --- a/src/invert/laplace/impls/petsc/petsc_laplace.cxx +++ b/src/invert/laplace/impls/petsc/petsc_laplace.cxx @@ -375,92 +375,92 @@ FieldPerp LaplacePetsc::solve(const FieldPerp& b, const FieldPerp& x0, * In other word the indexing is done in a row-major order, but starting at * bottom left rather than top left */ - // X=0 to localmesh->xstart-1 defines the boundary region of the domain. - // Set the values for the inner boundary region - if (localmesh->firstX()) { - for (int x = 0; x < localmesh->xstart; x++) { - for (int z = 0; z < localmesh->LocalNz; z++) { - PetscScalar val; // Value of element to be set in the matrix - // If Neumann Boundary Conditions are set. - if (isInnerBoundaryFlagSet(INVERT_AC_GRAD)) { - // Set values corresponding to nodes adjacent in x - if (fourth_order) { - // Fourth Order Accuracy on Boundary - Element(i, x, z, 0, 0, - -25.0 / (12.0 * coords->dx(x, y, z)) / sqrt(coords->g_11(x, y, z)), - MatA); - Element(i, x, z, 1, 0, - 4.0 / coords->dx(x, y, z) / sqrt(coords->g_11(x, y, z)), MatA); - Element(i, x, z, 2, 0, - -3.0 / coords->dx(x, y, z) / sqrt(coords->g_11(x, y, z)), MatA); - Element(i, x, z, 3, 0, - 4.0 / (3.0 * coords->dx(x, y, z)) / sqrt(coords->g_11(x, y, z)), - MatA); - Element(i, x, z, 4, 0, - -1.0 / (4.0 * coords->dx(x, y, z)) / sqrt(coords->g_11(x, y, z)), - MatA); - } else { - // Second Order Accuracy on Boundary - // Element(i,x,z, 0, 0, -3.0 / (2.0*coords->dx(x,y)), MatA ); - // Element(i,x,z, 1, 0, 2.0 / coords->dx(x,y), MatA ); - // Element(i,x,z, 2, 0, -1.0 / (2.0*coords->dx(x,y)), MatA ); - // Element(i,x,z, 3, 0, 0.0, MatA ); // Reset these elements to 0 - // in case 4th order flag was used previously: not allowed now - // Element(i,x,z, 4, 0, 0.0, MatA ); - // Second Order Accuracy on Boundary, set half-way between grid points - Element(i, x, z, 0, 0, - -1.0 / coords->dx(x, y, z) / sqrt(coords->g_11(x, y, z)), MatA); - Element(i, x, z, 1, 0, - 1.0 / coords->dx(x, y, z) / sqrt(coords->g_11(x, y, z)), MatA); - Element(i, x, z, 2, 0, 0.0, MatA); - // Element(i,x,z, 3, 0, 0.0, MatA ); // Reset - // these elements to 0 in case 4th order flag was - // used previously: not allowed now - // Element(i,x,z, 4, 0, 0.0, MatA ); - } + // X=0 to localmesh->xstart-1 defines the boundary region of the domain. + // Set the values for the inner boundary region + if (localmesh->firstX()) { + for (int x = 0; x < localmesh->xstart; x++) { + for (int z = 0; z < localmesh->LocalNz; z++) { + PetscScalar val; // Value of element to be set in the matrix + // If Neumann Boundary Conditions are set. + if (isInnerBoundaryFlagSet(INVERT_AC_GRAD)) { + // Set values corresponding to nodes adjacent in x + if (fourth_order) { + // Fourth Order Accuracy on Boundary + Element(i, x, z, 0, 0, + -25.0 / (12.0 * coords->dx(x, y, z)) / sqrt(coords->g_11(x, y, z)), + MatA); + Element(i, x, z, 1, 0, + 4.0 / coords->dx(x, y, z) / sqrt(coords->g_11(x, y, z)), MatA); + Element(i, x, z, 2, 0, + -3.0 / coords->dx(x, y, z) / sqrt(coords->g_11(x, y, z)), MatA); + Element(i, x, z, 3, 0, + 4.0 / (3.0 * coords->dx(x, y, z)) / sqrt(coords->g_11(x, y, z)), + MatA); + Element(i, x, z, 4, 0, + -1.0 / (4.0 * coords->dx(x, y, z)) / sqrt(coords->g_11(x, y, z)), + MatA); } else { - if (fourth_order) { - // Set Diagonal Values to 1 - Element(i, x, z, 0, 0, 1., MatA); - - // Set off diagonal elements to zero - Element(i, x, z, 1, 0, 0.0, MatA); - Element(i, x, z, 2, 0, 0.0, MatA); - Element(i, x, z, 3, 0, 0.0, MatA); - Element(i, x, z, 4, 0, 0.0, MatA); - } else { - Element(i, x, z, 0, 0, 0.5, MatA); - Element(i, x, z, 1, 0, 0.5, MatA); - Element(i, x, z, 2, 0, 0., MatA); - } + // Second Order Accuracy on Boundary + // Element(i,x,z, 0, 0, -3.0 / (2.0*coords->dx(x,y)), MatA ); + // Element(i,x,z, 1, 0, 2.0 / coords->dx(x,y), MatA ); + // Element(i,x,z, 2, 0, -1.0 / (2.0*coords->dx(x,y)), MatA ); + // Element(i,x,z, 3, 0, 0.0, MatA ); // Reset these elements to 0 + // in case 4th order flag was used previously: not allowed now + // Element(i,x,z, 4, 0, 0.0, MatA ); + // Second Order Accuracy on Boundary, set half-way between grid points + Element(i, x, z, 0, 0, + -1.0 / coords->dx(x, y, z) / sqrt(coords->g_11(x, y, z)), MatA); + Element(i, x, z, 1, 0, + 1.0 / coords->dx(x, y, z) / sqrt(coords->g_11(x, y, z)), MatA); + Element(i, x, z, 2, 0, 0.0, MatA); + // Element(i,x,z, 3, 0, 0.0, MatA ); // Reset + // these elements to 0 in case 4th order flag was + // used previously: not allowed now + // Element(i,x,z, 4, 0, 0.0, MatA ); } - - val = 0; // Initialize val - - // Set Components of RHS - // If the inner boundary value should be set by b or x0 - if (isInnerBoundaryFlagSet(INVERT_RHS)) { - val = b[x][z]; - } else if (isInnerBoundaryFlagSet(INVERT_SET)) { - val = x0[x][z]; + } else { + if (fourth_order) { + // Set Diagonal Values to 1 + Element(i, x, z, 0, 0, 1., MatA); + + // Set off diagonal elements to zero + Element(i, x, z, 1, 0, 0.0, MatA); + Element(i, x, z, 2, 0, 0.0, MatA); + Element(i, x, z, 3, 0, 0.0, MatA); + Element(i, x, z, 4, 0, 0.0, MatA); + } else { + Element(i, x, z, 0, 0, 0.5, MatA); + Element(i, x, z, 1, 0, 0.5, MatA); + Element(i, x, z, 2, 0, 0., MatA); } + } - // Set components of the RHS (the PETSc vector bs) - // 1 element is being set in row i to val - // INSERT_VALUES replaces existing entries with new values - VecSetValues(bs, 1, &i, &val, INSERT_VALUES); + val = 0; // Initialize val - // Set components of the and trial solution (the PETSc vector xs) - // 1 element is being set in row i to val - // INSERT_VALUES replaces existing entries with new values + // Set Components of RHS + // If the inner boundary value should be set by b or x0 + if (isInnerBoundaryFlagSet(INVERT_RHS)) { + val = b[x][z]; + } else if (isInnerBoundaryFlagSet(INVERT_SET)) { val = x0[x][z]; - VecSetValues(xs, 1, &i, &val, INSERT_VALUES); - - ASSERT3(i == getIndex(x, z)); - i++; // Increment row in Petsc matrix } + + // Set components of the RHS (the PETSc vector bs) + // 1 element is being set in row i to val + // INSERT_VALUES replaces existing entries with new values + VecSetValues(bs, 1, &i, &val, INSERT_VALUES); + + // Set components of the and trial solution (the PETSc vector xs) + // 1 element is being set in row i to val + // INSERT_VALUES replaces existing entries with new values + val = x0[x][z]; + VecSetValues(xs, 1, &i, &val, INSERT_VALUES); + + ASSERT3(i == getIndex(x, z)); + i++; // Increment row in Petsc matrix } } + } // Set the values for the main domain for (int x = localmesh->xstart; x <= localmesh->xend; x++) { @@ -744,7 +744,7 @@ FieldPerp LaplacePetsc::solve(const FieldPerp& b, const FieldPerp& x0, VecAssemblyEnd(xs); if (not forward) { - // Configure Linear Solver + // Configure Linear Solver #if PETSC_VERSION_GE(3, 5, 0) KSPSetOperators(ksp, MatA, MatA); #else @@ -811,13 +811,12 @@ FieldPerp LaplacePetsc::solve(const FieldPerp& b, const FieldPerp& x0, lib.setOptionsFromInputFile(ksp); } timer.reset(); - - // Call the actual solver - { - Timer timer("petscsolve"); - KSPSolve(ksp, bs, xs); // Call the solver to solve the system - } + // Call the actual solver + { + Timer timer("petscsolve"); + KSPSolve(ksp, bs, xs); // Call the solver to solve the system + } KSPConvergedReason reason; KSPGetConvergedReason(ksp, &reason); @@ -833,23 +832,23 @@ FieldPerp LaplacePetsc::solve(const FieldPerp& b, const FieldPerp& x0, timer.reset(); PetscErrorCode err = MatMult(MatA, bs, xs); if (err != PETSC_SUCCESS) { - throw BoutException("MatMult failed with {:d}", static_cast(err)); + throw BoutException("MatMult failed with {:d}", static_cast(err)); } } // Add data to FieldPerp Object - i = Istart; - // Set the inner boundary values - if (localmesh->firstX()) { - for (int x = 0; x < localmesh->xstart; x++) { - for (int z = 0; z < localmesh->LocalNz; z++) { - PetscScalar val = 0; - VecGetValues(xs, 1, &i, &val); - sol[x][z] = val; - i++; // Increment row in Petsc matrix + i = Istart; + // Set the inner boundary values + if (localmesh->firstX()) { + for (int x = 0; x < localmesh->xstart; x++) { + for (int z = 0; z < localmesh->LocalNz; z++) { + PetscScalar val = 0; + VecGetValues(xs, 1, &i, &val); + sol[x][z] = val; + i++; // Increment row in Petsc matrix + } } } - } // Set the main domain values for (int x = localmesh->xstart; x <= localmesh->xend; x++) { diff --git a/src/mesh/boundary_standard.cxx b/src/mesh/boundary_standard.cxx index dd7d353a48..fc313689be 100644 --- a/src/mesh/boundary_standard.cxx +++ b/src/mesh/boundary_standard.cxx @@ -1728,7 +1728,7 @@ void BoundaryNeumann_NonOrthogonal::apply(Field3D& f) { void BoundaryNeumann::apply(Field2D & f) { BoundaryNeumann::apply(f, 0.); } - void BoundaryNeumann::apply([[maybe_unused]] Field2D& f, BoutReal t) { + void BoundaryNeumann::apply([[maybe_unused]] Field2D & f, BoutReal t) { // Set (at 2nd order / 3rd order) the value at the mid-point between // the guard cell and the grid cell to be val // N.B. First guard cells (closest to the grid) is 2nd order, while diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index d650c9e9ec..d013634644 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -950,9 +950,9 @@ int Coordinates::geometry(bool recalculate_staggered, bool force_interpolate_from_centre) { TRACE("Coordinates::geometry"); { - std::vector fields{dx, dy, dz, g11, g22, g33, g12, g13, g23, g_11, g_22, g_33, g_12, g_13, - g_23, J}; - for (auto& f: fields) { + std::vector fields{dx, dy, dz, g11, g22, g33, g12, g13, + g23, g_11, g_22, g_33, g_12, g_13, g_23, J}; + for (auto& f : fields) { f.allowParallelSlices(false); } } @@ -1608,7 +1608,8 @@ Field3D Coordinates::Div_par(const Field3D& f, CELL_LOC outloc, f_B.yup(i) = f.yup(i) / coords->J.yup(i) * sqrt(coords->g_22.yup(i)); f_B.ydown(i) = f.ydown(i) / coords->J.ydown(i) * sqrt(coords->g_22.ydown(i)); } - return setName(coords->J / sqrt(coords->g_22) * Grad_par(f_B, outloc, method), "Div_par({:s})", f.name); + return setName(coords->J / sqrt(coords->g_22) * Grad_par(f_B, outloc, method), + "Div_par({:s})", f.name); } ///////////////////////////////////////////////////////// diff --git a/src/mesh/fv_ops.cxx b/src/mesh/fv_ops.cxx index bccae9e362..acfa6aa3ed 100644 --- a/src/mesh/fv_ops.cxx +++ b/src/mesh/fv_ops.cxx @@ -170,7 +170,7 @@ const Field3D Div_par_K_Grad_par(const Field3D& Kin, const Field3D& fin, if (Kin.isFci()) { return ::Div_par_K_Grad_par(Kin, fin); } - + ASSERT2(Kin.getLocation() == fin.getLocation()); Mesh* mesh = Kin.getMesh(); diff --git a/src/mesh/impls/bout/boutmesh.cxx b/src/mesh/impls/bout/boutmesh.cxx index c7c6523244..d9d029e7ca 100644 --- a/src/mesh/impls/bout/boutmesh.cxx +++ b/src/mesh/impls/bout/boutmesh.cxx @@ -496,8 +496,8 @@ int BoutMesh::load() { } if (meshHasMyg && MYG != meshMyg) { output_warn.write(_("Options changed the number of y-guard cells. Grid has {} but " - "option specified {}! Continuing with {}"), - meshMyg, MYG, MYG); + "option specified {}! Continuing with {}"), + meshMyg, MYG, MYG); } ASSERT0(MYG >= 0); diff --git a/src/mesh/interpolation/hermite_spline_xz.cxx b/src/mesh/interpolation/hermite_spline_xz.cxx index f850704b22..03a062df42 100644 --- a/src/mesh/interpolation/hermite_spline_xz.cxx +++ b/src/mesh/interpolation/hermite_spline_xz.cxx @@ -356,8 +356,9 @@ template std::vector XZHermiteSplineBase::getWeightsForYApproximation(int i, int j, int k, int yoffset) { - if (localmesh->getNXPE() > 1){ - throw BoutException("It is likely that the function calling this is not handling the result correctly."); + if (localmesh->getNXPE() > 1) { + throw BoutException("It is likely that the function calling this is not handling the " + "result correctly."); } const int nz = localmesh->LocalNz; const int k_mod = k_corner(i, j, k); @@ -503,7 +504,7 @@ template class XZHermiteSplineBase; template class XZHermiteSplineBase; Field3D XZMonotonicHermiteSplineLegacy::interpolate(const Field3D& f, - const std::string& region) const { + const std::string& region) const { ASSERT1(f.getMesh() == localmesh); Field3D f_interp(f.getMesh()); f_interp.allocate(); diff --git a/src/mesh/interpolation/lagrange_4pt_xz.cxx b/src/mesh/interpolation/lagrange_4pt_xz.cxx index 16368299a0..1a1e484c07 100644 --- a/src/mesh/interpolation/lagrange_4pt_xz.cxx +++ b/src/mesh/interpolation/lagrange_4pt_xz.cxx @@ -133,7 +133,6 @@ Field3D XZLagrange4pt::interpolate(const Field3D& f, const std::string& region) // Then in X f_interp(x, y_next, z) = lagrange_4pt(xvals, t_x(x, y, z)); ASSERT2(std::isfinite(f_interp(x, y_next, z))); - } const auto region2 = y_offset != 0 ? fmt::format("RGN_YPAR_{:+d}", y_offset) : region; f_interp.setRegion(region2); diff --git a/src/mesh/interpolation_xz.cxx b/src/mesh/interpolation_xz.cxx index bf22ba995d..5ee20c1a06 100644 --- a/src/mesh/interpolation_xz.cxx +++ b/src/mesh/interpolation_xz.cxx @@ -91,8 +91,8 @@ namespace { RegisterXZInterpolation registerinterphermitespline{"hermitespline"}; RegisterXZInterpolation registerinterpmonotonichermitespline{ "monotonichermitespline"}; -RegisterXZInterpolation registerinterpmonotonichermitesplinelegacy{ - "monotonichermitesplinelegacy"}; +RegisterXZInterpolation + registerinterpmonotonichermitesplinelegacy{"monotonichermitesplinelegacy"}; RegisterXZInterpolation registerinterplagrange4pt{"lagrange4pt"}; RegisterXZInterpolation registerinterpbilinear{"bilinear"}; } // namespace diff --git a/src/mesh/parallel/fci.cxx b/src/mesh/parallel/fci.cxx index cdcaec3dd2..580897f47a 100644 --- a/src/mesh/parallel/fci.cxx +++ b/src/mesh/parallel/fci.cxx @@ -54,7 +54,7 @@ std::string parallel_slice_field_name(std::string field, int offset) { // We only have a suffix for parallel slices beyond the first // This is for backwards compatibility const std::string slice_suffix = - (std::abs(offset) > 1) ? "_" + std::to_string(std::abs(offset)) : ""; + (std::abs(offset) > 1) ? "_" + std::to_string(std::abs(offset)) : ""; return direction + "_" + field + slice_suffix; }; @@ -90,7 +90,7 @@ bool load_parallel_metric_component(std::string name, Field3D& component, int of } tmp = lmin; } - if (!component.hasParallelSlices()){ + if (!component.hasParallelSlices()) { component.splitParallelSlices(); component.allowCalcParallelSlices = false; } @@ -98,14 +98,13 @@ bool load_parallel_metric_component(std::string name, Field3D& component, int of pcom.allocate(); pcom.setRegion(fmt::format("RGN_YPAR_{:+d}", offset)); pcom.name = name; - BOUT_FOR(i, component.getRegion("RGN_NOBNDRY")) { - pcom[i.yp(offset)] = tmp[i]; - } + BOUT_FOR(i, component.getRegion("RGN_NOBNDRY")) { pcom[i.yp(offset)] = tmp[i]; } return isValid; } #endif -void load_parallel_metric_components([[maybe_unused]] Coordinates* coords, [[maybe_unused]] int offset){ +void load_parallel_metric_components([[maybe_unused]] Coordinates* coords, + [[maybe_unused]] int offset) { #if BOUT_USE_METRIC_3D #define LOAD_PAR(var, doZero) \ load_parallel_metric_component(#var, coords->var, offset, doZero) @@ -188,15 +187,16 @@ FCIMap::FCIMap(Mesh& mesh, const Coordinates::FieldMetric& UNUSED(dy), Options& map_mesh.get(R, "R", 0.0, false); map_mesh.get(Z, "Z", 0.0, false); - // If we can't read in any of these fields, things will silently not // work, so best throw - if (map_mesh.get(xt_prime, parallel_slice_field_name("xt_prime", offset), 0.0, false) != 0) { + if (map_mesh.get(xt_prime, parallel_slice_field_name("xt_prime", offset), 0.0, false) + != 0) { throw BoutException("Could not read {:s} from grid file!\n" " Either add it to the grid file, or reduce MYG", parallel_slice_field_name("xt_prime", offset)); } - if (map_mesh.get(zt_prime, parallel_slice_field_name("zt_prime", offset), 0.0, false) != 0) { + if (map_mesh.get(zt_prime, parallel_slice_field_name("zt_prime", offset), 0.0, false) + != 0) { throw BoutException("Could not read {:s} from grid file!\n" " Either add it to the grid file, or reduce MYG", parallel_slice_field_name("zt_prime", offset)); @@ -211,7 +211,6 @@ FCIMap::FCIMap(Mesh& mesh, const Coordinates::FieldMetric& UNUSED(dy), Options& " Either add it to the grid file, or reduce MYG", parallel_slice_field_name("Z", offset)); } - // Cell corners Field3D xt_prime_corner{emptyFrom(xt_prime)}; @@ -453,7 +452,7 @@ void FCITransform::integrateParallelSlices(Field3D& f) { } void FCITransform::loadParallelMetrics(Coordinates* coords) { - for (int i=1; i<= mesh.ystart; ++i) { + for (int i = 1; i <= mesh.ystart; ++i) { load_parallel_metric_components(coords, -i); load_parallel_metric_components(coords, i); } diff --git a/src/mesh/parallel/fci.hxx b/src/mesh/parallel/fci.hxx index f7acd276a4..513a1d86c3 100644 --- a/src/mesh/parallel/fci.hxx +++ b/src/mesh/parallel/fci.hxx @@ -162,6 +162,7 @@ public: } void loadParallelMetrics(Coordinates* coords) override; + protected: void checkInputGrid() override; diff --git a/src/mesh/parallel/fci_comm.hxx b/src/mesh/parallel/fci_comm.hxx index 27dd111765..3514e4ba17 100644 --- a/src/mesh/parallel/fci_comm.hxx +++ b/src/mesh/parallel/fci_comm.hxx @@ -55,7 +55,7 @@ struct globalToLocal1D { const bool periodic; globalToLocal1D(int mg, int npe, int localwith, bool periodic) : mg(mg), npe(npe), localwith(localwith), local(localwith - 2 * mg), - global(local * npe), globalwith(global + 2 * mg), periodic(periodic) {}; + global(local * npe), globalwith(global + 2 * mg), periodic(periodic){}; ProcLocal convert(int id) const { if (periodic) { while (id < mg) { @@ -103,7 +103,7 @@ public: GlobalField3DAccessInstance(const GlobalField3DAccess* gfa, const std::vector&& data) - : gfa(*gfa), data(std::move(data)) {}; + : gfa(*gfa), data(std::move(data)){}; private: const GlobalField3DAccess& gfa; diff --git a/src/solver/impls/pvode/pvode.cxx b/src/solver/impls/pvode/pvode.cxx index 65d44d6e49..a4af3117ad 100644 --- a/src/solver/impls/pvode/pvode.cxx +++ b/src/solver/impls/pvode/pvode.cxx @@ -390,9 +390,9 @@ BoutReal PvodeSolver::run(BoutReal tout) { for (auto& f : f3d) { debug[f.name] = *f.var; - if (f.var->hasParallelSlices()) { - saveParallel(debug, f.name, *f.var); - } + if (f.var->hasParallelSlices()) { + saveParallel(debug, f.name, *f.var); + } } if (mesh != nullptr) { diff --git a/src/sys/options.cxx b/src/sys/options.cxx index e13f7931ee..ee2326df29 100644 --- a/src/sys/options.cxx +++ b/src/sys/options.cxx @@ -343,16 +343,16 @@ Options& Options::assign<>(Tensor val, std::string source) { return *this; } -void saveParallel(Options& opt, const std::string name, const Field3D& tosave){ +void saveParallel(Options& opt, const std::string name, const Field3D& tosave) { ASSERT0(tosave.isAllocated()); opt[name] = tosave; - for (size_t i0=1 ; i0 <= tosave.numberParallelSlices(); ++i0) { - for (int i: {i0, -i0} ) { + for (size_t i0 = 1; i0 <= tosave.numberParallelSlices(); ++i0) { + for (int i : {i0, -i0}) { Field3D tmp; tmp.allocate(); const auto& fpar = tosave.ynext(i); - for (auto j: fpar.getValidRegionWithDefault("RGN_NOBNDRY")){ - tmp[j.yp(-i)] = fpar[j]; + for (auto j : fpar.getValidRegionWithDefault("RGN_NOBNDRY")) { + tmp[j.yp(-i)] = fpar[j]; } opt[fmt::format("{}_y{:+d}", name, i)] = tmp; } diff --git a/tests/integrated/test-fci-boundary/get_par_bndry.cxx b/tests/integrated/test-fci-boundary/get_par_bndry.cxx index 4079b55574..6c5c38eaf6 100644 --- a/tests/integrated/test-fci-boundary/get_par_bndry.cxx +++ b/tests/integrated/test-fci-boundary/get_par_bndry.cxx @@ -14,10 +14,9 @@ int main(int argc, char** argv) { for (int i = 0; i < fields.size(); i++) { fields[i] = Field3D{0.0}; mesh->communicate(fields[i]); - for (auto& bndry_par : - mesh->getBoundariesPar(static_cast(i))) { + for (auto& bndry_par : mesh->getBoundariesPar(static_cast(i))) { output.write("{:s} region\n", toString(static_cast(i))); - for (const auto& pnt: *bndry_par) { + for (const auto& pnt : *bndry_par) { fields[i][pnt.ind()] += 1; output.write("{:s} increment\n", toString(static_cast(i))); } From 2fb1b34e50e7b418448840543240b94ff58d8283 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 14 Mar 2025 13:51:06 +0100 Subject: [PATCH 216/242] Add option to copy parallel slices at setBoundaryTo --- include/bout/field3d.hxx | 3 ++- src/field/field3d.cxx | 28 ++++++++++++++++++---------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index 763c334cc1..0643efbe0a 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -505,7 +505,8 @@ public: /// This uses 2nd order central differences to set the value /// on the boundary to the value on the boundary in field \p f3d. /// Note: does not just copy values in boundary region. - void setBoundaryTo(const Field3D& f3d); + void setBoundaryTo(const Field3D& f3d) { setBoundaryTo(f3d, true); } + void setBoundaryTo(const Field3D& f3d, bool copyParallelSlices); using FieldData::applyParallelBoundary; void applyParallelBoundary() override; diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index 85077a73ff..74a6f0853c 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -492,7 +492,7 @@ void Field3D::applyTDerivBoundary() { } } -void Field3D::setBoundaryTo(const Field3D& f3d) { +void Field3D::setBoundaryTo(const Field3D& f3d, bool copyParallelSlices) { TRACE("Field3D::setBoundary(const Field3D&)"); checkData(f3d); @@ -500,16 +500,24 @@ void Field3D::setBoundaryTo(const Field3D& f3d) { allocate(); // Make sure data allocated if (isFci()) { - // Set yup/ydown using midpoint values from f3d ASSERT1(f3d.hasParallelSlices()); - ASSERT1(hasParallelSlices()); - - for (auto& region : fieldmesh->getBoundariesPar()) { - for (const auto& pnt : *region) { - // Interpolate midpoint value in f3d - const BoutReal val = pnt.interpolate_sheath_o1(f3d); - // Set the same boundary value in this field - pnt.dirichlet_o1(*this, val); + if (copyParallelSlices) { + splitParallelSlices(); + for (int i = 0; i < fieldmesh->ystart; ++i) { + yup(i) = f3d.yup(i); + ydown(i) = f3d.ydown(i); + } + } else { + // Set yup/ydown using midpoint values from f3d + ASSERT1(hasParallelSlices()); + + for (auto& region : fieldmesh->getBoundariesPar()) { + for (const auto& pnt : *region) { + // Interpolate midpoint value in f3d + const BoutReal val = pnt.interpolate_sheath_o1(f3d); + // Set the same boundary value in this field + pnt.dirichlet_o1(*this, val); + } } } } From 7a1d0619a0c6d0a52535303f179217e31bcc73b1 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 14 Mar 2025 13:51:41 +0100 Subject: [PATCH 217/242] Print timings for petsc solver in test --- tests/integrated/test-petsc_laplace/test_petsc_laplace.cxx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/integrated/test-petsc_laplace/test_petsc_laplace.cxx b/tests/integrated/test-petsc_laplace/test_petsc_laplace.cxx index 8ca8383244..87124514d3 100644 --- a/tests/integrated/test-petsc_laplace/test_petsc_laplace.cxx +++ b/tests/integrated/test-petsc_laplace/test_petsc_laplace.cxx @@ -34,6 +34,7 @@ #include "bout/options.hxx" #include "bout/options_io.hxx" #include "bout/output.hxx" +#include "bout/sys/timer.hxx" #include "bout/traits.hxx" #include "fmt/core.h" @@ -195,6 +196,9 @@ int main(int argc, char** argv) { MPI_Barrier(BoutComm::get()); // Wait for all processors to write data } + output.write("Used {}s for setup and {}s for solving\n", + Timer::getTotalTime("petscsetup"), Timer::getTotalTime("petscsolve")); + bout::checkForUnusedOptions(); BoutFinalise(); From a6be0ab53ab94ea6bff6b40cfc772f7f16997dfe Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 14 Mar 2025 15:38:24 +0100 Subject: [PATCH 218/242] Add Field2D::splitParallelSlices() for writing FCI aware code --- include/bout/field2d.hxx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/bout/field2d.hxx b/include/bout/field2d.hxx index 3be7427fa7..5eab330e8e 100644 --- a/include/bout/field2d.hxx +++ b/include/bout/field2d.hxx @@ -137,8 +137,9 @@ public: /// Dummy functions to increase portability bool hasParallelSlices() const { return true; } void calcParallelSlices() const {} - void clearParallelSlices() {} - int numberParallelSlices() { return 0; } + void splitParallelSlices() const {} + void clearParallelSlices() const {} + int numberParallelSlices() const { return 0; } Field2D& yup(std::vector::size_type UNUSED(index) = 0) { return *this; } const Field2D& yup(std::vector::size_type UNUSED(index) = 0) const { From d09d8f8ad023532d81a775ac2885e8c47bd5338e Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sat, 15 Mar 2025 15:10:07 -0700 Subject: [PATCH 219/242] difops: Replace Div_par(Field3D, Field3D) When using FCI yup/down fields, each poloidal plane uses a different coordinate system. Quantities like J therefore can't be averaged between planes. Magnetic field strength is a scalar that can be interpolated, and is used here to calculate the divergence of a parallel flow. --- src/mesh/difops.cxx | 69 ++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 38 deletions(-) diff --git a/src/mesh/difops.cxx b/src/mesh/difops.cxx index 639a5a1612..5d9f579515 100644 --- a/src/mesh/difops.cxx +++ b/src/mesh/difops.cxx @@ -234,57 +234,50 @@ Field3D Div_par(const Field3D& f, const std::string& method, CELL_LOC outloc) { return f.getCoordinates(outloc)->Div_par(f, outloc, method); } -Field3D Div_par(const Field3D& f_in, const Field3D& v_in) { -#if BOUT_USE_FCI_AUTOMAGIC - auto f{f_in}; - auto v{v_in}; +Field3D Div_par(const Field3D& f, const Field3D& v) { + AUTO_TRACE(); + ASSERT1_FIELDS_COMPATIBLE(f, v); + + // Either both have parallel slices or neither if (!f.hasParallelSlices()) { - f.calcParallelSlices(); - } - if (!v.hasParallelSlices()) { - v.calcParallelSlices(); + // No parallel slices + ASSERT1(!v.hasParallelSlices()); + + return Div_par(f * v); } -#else - const auto& f{f_in}; - const auto& v{v_in}; -#endif - ASSERT1_FIELDS_COMPATIBLE(f, v); + // Using parallel slices ASSERT1(f.hasParallelSlices()); ASSERT1(v.hasParallelSlices()); - // Parallel divergence, using velocities at cell boundaries - // Note: Not guaranteed to be flux conservative - Mesh* mesh = f.getMesh(); - - Field3D result{emptyFrom(f)}; - Coordinates* coord = f.getCoordinates(); - for (int i = mesh->xstart; i <= mesh->xend; i++) { - for (int j = mesh->ystart; j <= mesh->yend; j++) { - for (int k = mesh->zstart; k <= mesh->zend; k++) { - // Value of f and v at left cell face - BoutReal fL = 0.5 * (f(i, j, k) + f.ydown()(i, j - 1, k)); - BoutReal vL = 0.5 * (v(i, j, k) + v.ydown()(i, j - 1, k)); + auto B = coord->Bxy; + auto B_up = coord->Bxy.yup(); + auto B_down = coord->Bxy.ydown(); - BoutReal fR = 0.5 * (f(i, j, k) + f.yup()(i, j + 1, k)); - BoutReal vR = 0.5 * (v(i, j, k) + v.yup()(i, j + 1, k)); + auto f_up = f.yup(); + auto f_down = f.ydown(); - // Calculate flux at right boundary (y+1/2) - BoutReal fluxRight = - fR * vR * (coord->J(i, j, k) + coord->J(i, j + 1, k)) - / (sqrt(coord->g_22(i, j, k)) + sqrt(coord->g_22(i, j + 1, k))); + auto v_up = v.yup(); + auto v_down = v.ydown(); - // Calculate at left boundary (y-1/2) - BoutReal fluxLeft = - fL * vL * (coord->J(i, j, k) + coord->J(i, j - 1, k)) - / (sqrt(coord->g_22(i, j, k)) + sqrt(coord->g_22(i, j - 1, k))); + auto g_22 = coord->g_22; + auto dy = coord->dy; - result(i, j, k) = - (fluxRight - fluxLeft) / (coord->dy(i, j, k) * coord->J(i, j, k)); - } + Field3D result{emptyFrom(f)}; + BOUT_FOR(i, f.getRegion("RGN_NOBNDRY")) { + result[i] = B[i] * ((f_up[i] * v_up[i] / B_up[i]) + - (f_down[i] * v_down[i] / B_down[i])) + / (dy[i] * sqrt(g_22[i])); + +#if CHECK > 0 + if(!std::isfinite(result[i])) { + output.write("{} {} {} {}\n", f_up[i], v_up[i], f_down[i], v_down[i]); + output.write("{} {} {} {} {}\n", B[i], B_up[i], B_down[i], dy[i], sqrt(g_22[i])); + throw BoutException("Non-finite value in Div_"); } +#endif } return result; From 81ee64eae6298526927bf8ccd02a68c38fb01104 Mon Sep 17 00:00:00 2001 From: bendudson <219233+bendudson@users.noreply.github.com> Date: Sat, 15 Mar 2025 22:16:55 +0000 Subject: [PATCH 220/242] Apply clang-format changes --- src/mesh/difops.cxx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mesh/difops.cxx b/src/mesh/difops.cxx index 5d9f579515..f722b85afb 100644 --- a/src/mesh/difops.cxx +++ b/src/mesh/difops.cxx @@ -267,12 +267,12 @@ Field3D Div_par(const Field3D& f, const Field3D& v) { Field3D result{emptyFrom(f)}; BOUT_FOR(i, f.getRegion("RGN_NOBNDRY")) { - result[i] = B[i] * ((f_up[i] * v_up[i] / B_up[i]) - - (f_down[i] * v_down[i] / B_down[i])) - / (dy[i] * sqrt(g_22[i])); + result[i] = B[i] + * ((f_up[i] * v_up[i] / B_up[i]) - (f_down[i] * v_down[i] / B_down[i])) + / (dy[i] * sqrt(g_22[i])); #if CHECK > 0 - if(!std::isfinite(result[i])) { + if (!std::isfinite(result[i])) { output.write("{} {} {} {}\n", f_up[i], v_up[i], f_down[i], v_down[i]); output.write("{} {} {} {} {}\n", B[i], B_up[i], B_down[i], dy[i], sqrt(g_22[i])); throw BoutException("Non-finite value in Div_"); From 3a7743758c7dedca760530601254cc128f640829 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sat, 15 Mar 2025 15:21:57 -0700 Subject: [PATCH 221/242] Coordinates: Fix Div_par(f) for FCI Use Bxy rather than J on neighboring slices: B can be compared between slices, but J cannot (different coordinate system). --- src/mesh/coordinates.cxx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index d013634644..f39c6c9133 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -1600,15 +1600,14 @@ Field3D Coordinates::Div_par(const Field3D& f, CELL_LOC outloc, return Bxy * Grad_par(f / Bxy_floc, outloc, method); } - auto coords = f.getCoordinates(); // Need to modify yup and ydown fields - Field3D f_B = f / coords->J * sqrt(coords->g_22); + Field3D f_B = f / Bxy_floc; f_B.splitParallelSlices(); for (int i = 0; i < f.getMesh()->ystart; ++i) { - f_B.yup(i) = f.yup(i) / coords->J.yup(i) * sqrt(coords->g_22.yup(i)); - f_B.ydown(i) = f.ydown(i) / coords->J.ydown(i) * sqrt(coords->g_22.ydown(i)); + f_B.yup(i) = f.yup(i) / Bxy_floc.yup(i); + f_B.ydown(i) = f.ydown(i) / Bxy_floc.ydown(i); } - return setName(coords->J / sqrt(coords->g_22) * Grad_par(f_B, outloc, method), + return setName(Bxy * Grad_par(f_B, outloc, method), "Div_par({:s})", f.name); } From 1d785595610ec8c5a2ce2a804c02d2e93b93c00f Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sat, 15 Mar 2025 15:25:42 -0700 Subject: [PATCH 222/242] FCItransform: Only load some parallel metrics Each poloidal slice has a different coordinate system, so metrics can't be directly compared or averaged. Only Bxy and the parallel connection length (dy, g_22) make sense to calculate. --- src/mesh/parallel/fci.cxx | 54 ++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/src/mesh/parallel/fci.cxx b/src/mesh/parallel/fci.cxx index 580897f47a..f7ba0b9aed 100644 --- a/src/mesh/parallel/fci.cxx +++ b/src/mesh/parallel/fci.cxx @@ -108,39 +108,29 @@ void load_parallel_metric_components([[maybe_unused]] Coordinates* coords, #if BOUT_USE_METRIC_3D #define LOAD_PAR(var, doZero) \ load_parallel_metric_component(#var, coords->var, offset, doZero) - LOAD_PAR(g11, false); - LOAD_PAR(g22, false); - LOAD_PAR(g33, false); - LOAD_PAR(g12, false); - LOAD_PAR(g13, false); - LOAD_PAR(g23, false); - - LOAD_PAR(g_11, false); + + // Only some components can be compared between planes + // because each poloidal X-Z plane has a separate coordinate system + LOAD_PAR(dy, false); LOAD_PAR(g_22, false); - LOAD_PAR(g_33, false); - LOAD_PAR(g_12, false); - LOAD_PAR(g_13, false); - LOAD_PAR(g_23, false); - - if (not LOAD_PAR(J, true)) { - auto g = - coords->g11.ynext(offset) * coords->g22.ynext(offset) * coords->g33.ynext(offset) - + 2.0 * coords->g12.ynext(offset) * coords->g13.ynext(offset) - * coords->g23.ynext(offset) - - coords->g11.ynext(offset) * coords->g23.ynext(offset) - * coords->g23.ynext(offset) - - coords->g22.ynext(offset) * coords->g13.ynext(offset) - * coords->g13.ynext(offset) - - coords->g33.ynext(offset) * coords->g12.ynext(offset) - * coords->g12.ynext(offset); - - const auto rgn = fmt::format("RGN_YPAR_{:+d}", offset); - // Check that g is positive - bout::checkPositive(g, "The determinant of g^ij", rgn); - auto J = 1. / sqrt(g); - auto& pcom = coords->J.ynext(offset); - BOUT_FOR(i, J.getRegion(rgn)) { pcom[i] = J[i]; } - } + // LOAD_PAR(Bxy, false); + + // Other components can't have parallel slices + coords->g11.allowParallelSlices(false); + coords->g22.allowParallelSlices(false); + coords->g33.allowParallelSlices(false); + coords->g12.allowParallelSlices(false); + coords->g13.allowParallelSlices(false); + coords->g23.allowParallelSlices(false); + + coords->g_11.allowParallelSlices(false); + coords->g_33.allowParallelSlices(false); + coords->g_12.allowParallelSlices(false); + coords->g_13.allowParallelSlices(false); + coords->g_23.allowParallelSlices(false); + + coords->J.allowParallelSlices(false); + #undef LOAD_PAR #endif } From e5834c18960fa5bcf46201e342793933b3894547 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sat, 15 Mar 2025 15:44:23 -0700 Subject: [PATCH 223/242] DDY: Remove FCI_AUTOMAGIC calcParallelSlices Don't calculate parallel slices in a derivative: The result is unlikely to be correct. --- include/bout/index_derivs_interface.hxx | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/include/bout/index_derivs_interface.hxx b/include/bout/index_derivs_interface.hxx index bc9a687b34..fb1e7ef020 100644 --- a/include/bout/index_derivs_interface.hxx +++ b/include/bout/index_derivs_interface.hxx @@ -4,10 +4,9 @@ * Definition of main derivative kernels * ************************************************************************** - * Copyright 2018 - * D.Dickinson + * Copyright 2018 - 2025 BOUT++ contributors * - * Contact: Ben Dudson, bd512@york.ac.uk + * Contact: Ben Dudson, dudson2@llnl.gov * * This file is part of BOUT++. * @@ -204,13 +203,9 @@ T DDY(const T& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "D ASSERT1(f.getDirectionY() == YDirectionType::Standard); T f_tmp = f; if (!f.hasParallelSlices()) { -#if BOUT_USE_FCI_AUTOMAGIC - f_tmp.calcParallelSlices(); -#else throw BoutException( "parallel slices needed for parallel derivatives. Make sure to communicate and " "apply parallel boundary conditions before calling derivative"); -#endif } return standardDerivative(f_tmp, outloc, method, region); From e058507c293ace2e1ae5c6d1392276369101471d Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sat, 15 Mar 2025 16:14:37 -0700 Subject: [PATCH 224/242] Field: Extend operations to calculate in parallel slices If the argument to `sqrt` has parallel slices, then the slices will be operated on and the result will have parallel slices. To avoid unnecessary work, discard slices before calling. --- include/bout/field.hxx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/include/bout/field.hxx b/include/bout/field.hxx index 27835ecbd7..ad4fa51b93 100644 --- a/include/bout/field.hxx +++ b/include/bout/field.hxx @@ -527,6 +527,10 @@ T pow(BoutReal lhs, const T& rhs, const std::string& rgn = "RGN_ALL") { * and uses checkData() to, if CHECK >= 3, check * result for non-finite numbers * + * If the input field has parallel slices, then those will also be + * operated on. To avoid this, use `withoutParallelSlices()` to + * discard slices before calling. + * */ #ifdef FIELD_FUNC #error This macro has already been defined @@ -540,6 +544,20 @@ T pow(BoutReal lhs, const T& rhs, const std::string& rgn = "RGN_ALL") { /* Define and allocate the output result */ \ T result{emptyFrom(f)}; \ BOUT_FOR(d, result.getRegion(rgn)) { result[d] = func(f[d]); } \ + if (f.hasParallelSlices()) { \ + /* Operate on parallel slices */ \ + result.splitParallelSlicesAndAllocate(); \ + for (size_t i{0}; i != f.numberParallelSlices(); ++i) { \ + BOUT_FOR(d, result.getRegion(rgn)) { \ + result.yup(i)[d] = func(f.yup(i)[d]); \ + } \ + checkData(result.yup(i)); \ + BOUT_FOR(d, result.getRegion(rgn)) { \ + result.ydown(i)[d] = func(f.ydown(i)[d]); \ + } \ + checkData(result.ydown(i)); \ + } \ + } \ result.name = std::string(#_name "(") + f.name + std::string(")"); \ checkData(result); \ return result; \ From a46a4f7b0332d70d2382618eff0b6b55fdfd9a05 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sat, 15 Mar 2025 16:36:38 -0700 Subject: [PATCH 225/242] Field: Add withoutParallelSlices() method Returns a shallow copy without parallel slices. Enables user to avoid performing calculations on slices if not needed. --- include/bout/field2d.hxx | 6 ++++-- include/bout/field3d.hxx | 14 +++++++++++++- include/bout/fieldperp.hxx | 9 ++++++--- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/include/bout/field2d.hxx b/include/bout/field2d.hxx index 5eab330e8e..eaf5e21d72 100644 --- a/include/bout/field2d.hxx +++ b/include/bout/field2d.hxx @@ -134,12 +134,14 @@ public: return *this; } - /// Dummy functions to increase portability + /// Dummy functions to replicate Field3D interface bool hasParallelSlices() const { return true; } void calcParallelSlices() const {} void splitParallelSlices() const {} + void splitParallelSlicesAndAllocate() const {} void clearParallelSlices() const {} - int numberParallelSlices() const { return 0; } + Field2D withoutParallelSlices() const { return *this; } + size_t numberParallelSlices() const { return 0; } Field2D& yup(std::vector::size_type UNUSED(index) = 0) { return *this; } const Field2D& yup(std::vector::size_type UNUSED(index) = 0) const { diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index 0643efbe0a..96e0722700 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -237,6 +237,9 @@ public: */ void splitParallelSlices(); + /// Separate fields for yup and ydown, with memory allocated. Note: + /// After this the parallel slices will be allocated and unique, but + /// may contain uninitialised values. void splitParallelSlicesAndAllocate(); /*! @@ -244,6 +247,14 @@ public: */ void clearParallelSlices(); + /// Returns a shallow copy without parallel slices + Field3D withoutParallelSlices() const { + Field3D result{getMesh(), getLocation(), + getDirections(), getRegionID()}; + result.data = data; + return result; + } + /// Check if this field has yup and ydown fields bool hasParallelSlices() const { #if CHECK > 2 @@ -488,10 +499,11 @@ public: friend class Vector2D; Field3D& calcParallelSlices(); - void allowParallelSlices([[maybe_unused]] bool allow) { + Field3D& allowParallelSlices([[maybe_unused]] bool allow) { #if CHECK > 0 allowCalcParallelSlices = allow; #endif + return *this; } void applyBoundary(bool init = false) override; diff --git a/include/bout/fieldperp.hxx b/include/bout/fieldperp.hxx index b50eef1991..376d333d1c 100644 --- a/include/bout/fieldperp.hxx +++ b/include/bout/fieldperp.hxx @@ -158,11 +158,14 @@ public: return *this; } - /// Dummy functions to increase portability + /// Dummy functions to match Field3D interface bool hasParallelSlices() const { return true; } + void splitParallelSlices() const {} + void splitParallelSlicesAndAllocate() const {} void calcParallelSlices() const {} - void clearParallelSlices() {} - int numberParallelSlices() { return 0; } + void clearParallelSlices() const {} + FieldPerp withoutParallelSlices() const { return *this; } + size_t numberParallelSlices() const { return 0; } FieldPerp& yup(std::vector::size_type UNUSED(index) = 0) { return *this; } const FieldPerp& yup(std::vector::size_type UNUSED(index) = 0) const { From cedeb2b945aa8fd3eb89b6e76928f9769f407987 Mon Sep 17 00:00:00 2001 From: bendudson <219233+bendudson@users.noreply.github.com> Date: Sat, 15 Mar 2025 23:41:24 +0000 Subject: [PATCH 226/242] Apply clang-format changes --- include/bout/field.hxx | 48 ++++++++++++++++++---------------------- include/bout/field3d.hxx | 3 +-- src/mesh/coordinates.cxx | 3 +-- 3 files changed, 24 insertions(+), 30 deletions(-) diff --git a/include/bout/field.hxx b/include/bout/field.hxx index ad4fa51b93..017fc39e3b 100644 --- a/include/bout/field.hxx +++ b/include/bout/field.hxx @@ -535,32 +535,28 @@ T pow(BoutReal lhs, const T& rhs, const std::string& rgn = "RGN_ALL") { #ifdef FIELD_FUNC #error This macro has already been defined #else -#define FIELD_FUNC(_name, func) \ - template > \ - inline T _name(const T& f, const std::string& rgn = "RGN_ALL") { \ - AUTO_TRACE(); \ - /* Check if the input is allocated */ \ - checkData(f); \ - /* Define and allocate the output result */ \ - T result{emptyFrom(f)}; \ - BOUT_FOR(d, result.getRegion(rgn)) { result[d] = func(f[d]); } \ - if (f.hasParallelSlices()) { \ - /* Operate on parallel slices */ \ - result.splitParallelSlicesAndAllocate(); \ - for (size_t i{0}; i != f.numberParallelSlices(); ++i) { \ - BOUT_FOR(d, result.getRegion(rgn)) { \ - result.yup(i)[d] = func(f.yup(i)[d]); \ - } \ - checkData(result.yup(i)); \ - BOUT_FOR(d, result.getRegion(rgn)) { \ - result.ydown(i)[d] = func(f.ydown(i)[d]); \ - } \ - checkData(result.ydown(i)); \ - } \ - } \ - result.name = std::string(#_name "(") + f.name + std::string(")"); \ - checkData(result); \ - return result; \ +#define FIELD_FUNC(_name, func) \ + template > \ + inline T _name(const T& f, const std::string& rgn = "RGN_ALL") { \ + AUTO_TRACE(); \ + /* Check if the input is allocated */ \ + checkData(f); \ + /* Define and allocate the output result */ \ + T result{emptyFrom(f)}; \ + BOUT_FOR(d, result.getRegion(rgn)) { result[d] = func(f[d]); } \ + if (f.hasParallelSlices()) { \ + /* Operate on parallel slices */ \ + result.splitParallelSlicesAndAllocate(); \ + for (size_t i{0}; i != f.numberParallelSlices(); ++i) { \ + BOUT_FOR(d, result.getRegion(rgn)) { result.yup(i)[d] = func(f.yup(i)[d]); } \ + checkData(result.yup(i)); \ + BOUT_FOR(d, result.getRegion(rgn)) { result.ydown(i)[d] = func(f.ydown(i)[d]); } \ + checkData(result.ydown(i)); \ + } \ + } \ + result.name = std::string(#_name "(") + f.name + std::string(")"); \ + checkData(result); \ + return result; \ } #endif diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index 96e0722700..c162c12237 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -249,8 +249,7 @@ public: /// Returns a shallow copy without parallel slices Field3D withoutParallelSlices() const { - Field3D result{getMesh(), getLocation(), - getDirections(), getRegionID()}; + Field3D result{getMesh(), getLocation(), getDirections(), getRegionID()}; result.data = data; return result; } diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index f39c6c9133..e2348d5775 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -1607,8 +1607,7 @@ Field3D Coordinates::Div_par(const Field3D& f, CELL_LOC outloc, f_B.yup(i) = f.yup(i) / Bxy_floc.yup(i); f_B.ydown(i) = f.ydown(i) / Bxy_floc.ydown(i); } - return setName(Bxy * Grad_par(f_B, outloc, method), - "Div_par({:s})", f.name); + return setName(Bxy * Grad_par(f_B, outloc, method), "Div_par({:s})", f.name); } ///////////////////////////////////////////////////////// From 0b976ec8131ed60321a32eb783061f6843de2dc2 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sat, 15 Mar 2025 22:09:21 -0700 Subject: [PATCH 227/242] field ops: Remove FCI_AUTOMAGIC flag Always perform calculations in the yup/down slices if present in BOTH arguments. i.e. automagic always on for arithmetic operators. To avoid unnecessary work, use `withoutParallelSlices` to pass arguments without parallel slices. --- src/field/gen_fieldops.jinja | 16 +--- src/field/generated_fieldops.cxx | 136 +++++++++++-------------------- 2 files changed, 52 insertions(+), 100 deletions(-) diff --git a/src/field/gen_fieldops.jinja b/src/field/gen_fieldops.jinja index 88e877c197..9d1c3ee68d 100644 --- a/src/field/gen_fieldops.jinja +++ b/src/field/gen_fieldops.jinja @@ -12,40 +12,34 @@ {% if lhs == rhs == "Field3D" %} {{out.name}}.setRegion({{lhs.name}}.getMesh()->getCommonRegion({{lhs.name}}.getRegionID(), {{rhs.name}}.getRegionID())); -#if BOUT_USE_FCI_AUTOMAGIC - if ({{lhs.name}}.isFci() and {{lhs.name}}.hasParallelSlices() and {{rhs.name}}.hasParallelSlices()) { + if ({{lhs.name}}.hasParallelSlices() and {{rhs.name}}.hasParallelSlices()) { {{out.name}}.splitParallelSlices(); for (size_t i{0} ; i < {{lhs.name}}.numberParallelSlices() ; ++i) { {{out.name}}.yup(i) = {{lhs.name}}.yup(i) {{operator}} {{rhs.name}}.yup(i); {{out.name}}.ydown(i) = {{lhs.name}}.ydown(i) {{operator}} {{rhs.name}}.ydown(i); } } -#endif {% elif lhs == "Field3D" %} {{out.name}}.setRegion({{lhs.name}}.getRegionID()); {% if rhs == "BoutReal" %} -#if BOUT_USE_FCI_AUTOMAGIC - if ({{lhs.name}}.isFci() and {{lhs.name}}.hasParallelSlices()) { + if ({{lhs.name}}.hasParallelSlices()) { {{out.name}}.splitParallelSlices(); for (size_t i{0} ; i < {{lhs.name}}.numberParallelSlices() ; ++i) { {{out.name}}.yup(i) = {{lhs.name}}.yup(i) {{operator}} {{rhs.name}}; {{out.name}}.ydown(i) = {{lhs.name}}.ydown(i) {{operator}} {{rhs.name}}; } } -#endif {% endif %} {% elif rhs == "Field3D" %} {{out.name}}.setRegion({{rhs.name}}.getRegionID()); {% if lhs == "BoutReal" %} -#if BOUT_USE_FCI_AUTOMAGIC - if ({{rhs.name}}.isFci() and {{rhs.name}}.hasParallelSlices()) { + if ({{rhs.name}}.hasParallelSlices()) { {{out.name}}.splitParallelSlices(); for (size_t i{0} ; i < {{rhs.name}}.numberParallelSlices() ; ++i) { {{out.name}}.yup(i) = {{lhs.name}} {{operator}} {{rhs.name}}.yup(i); {{out.name}}.ydown(i) = {{lhs.name}} {{operator}} {{rhs.name}}.ydown(i); } } -#endif {% endif %} {% endif %} {% endif %} @@ -114,14 +108,12 @@ // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. {% if (rhs == "Field3D" or rhs == "BoutReal") %} -#if BOUT_USE_FCI_AUTOMAGIC - if (this->isFci() and this->hasParallelSlices() {% if rhs == "Field3D" %} and {{rhs.name}}.hasParallelSlices() {% endif %}) { + if (this->hasParallelSlices() {% if rhs == "Field3D" %} and {{rhs.name}}.hasParallelSlices() {% endif %}) { for (size_t i{0} ; i < yup_fields.size() ; ++i) { yup(i) {{operator}}= {{rhs.name}}{% if rhs == "Field3D" %}.yup(i){% endif %}; ydown(i) {{operator}}= {{rhs.name}}{% if rhs == "Field3D" %}.ydown(i){% endif %}; } } else -#endif {% endif %} { clearParallelSlices(); diff --git a/src/field/generated_fieldops.cxx b/src/field/generated_fieldops.cxx index 75d2ede82d..dd2a7add9a 100644 --- a/src/field/generated_fieldops.cxx +++ b/src/field/generated_fieldops.cxx @@ -15,15 +15,13 @@ Field3D operator*(const Field3D& lhs, const Field3D& rhs) { checkData(rhs); result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); -#if BOUT_USE_FCI_AUTOMAGIC - if (lhs.isFci() and lhs.hasParallelSlices() and rhs.hasParallelSlices()) { + if (lhs.hasParallelSlices() and rhs.hasParallelSlices()) { result.splitParallelSlices(); for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { result.yup(i) = lhs.yup(i) * rhs.yup(i); result.ydown(i) = lhs.ydown(i) * rhs.ydown(i); } } -#endif BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { result[index] = lhs[index] * rhs[index]; @@ -44,16 +42,13 @@ Field3D& Field3D::operator*=(const Field3D& rhs) { ASSERT1_FIELDS_COMPATIBLE(*this, rhs); // Delete existing parallel slices. We don't copy parallel slices, so any -// that currently exist will be incorrect. -#if BOUT_USE_FCI_AUTOMAGIC - if (this->isFci() and this->hasParallelSlices() and rhs.hasParallelSlices()) { + // that currently exist will be incorrect. + if (this->hasParallelSlices() and rhs.hasParallelSlices()) { for (size_t i{0}; i < yup_fields.size(); ++i) { yup(i) *= rhs.yup(i); ydown(i) *= rhs.ydown(i); } - } else -#endif - { + } else { clearParallelSlices(); } @@ -87,15 +82,13 @@ Field3D operator/(const Field3D& lhs, const Field3D& rhs) { checkData(rhs); result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); -#if BOUT_USE_FCI_AUTOMAGIC - if (lhs.isFci() and lhs.hasParallelSlices() and rhs.hasParallelSlices()) { + if (lhs.hasParallelSlices() and rhs.hasParallelSlices()) { result.splitParallelSlices(); for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { result.yup(i) = lhs.yup(i) / rhs.yup(i); result.ydown(i) = lhs.ydown(i) / rhs.ydown(i); } } -#endif BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { result[index] = lhs[index] / rhs[index]; @@ -116,16 +109,13 @@ Field3D& Field3D::operator/=(const Field3D& rhs) { ASSERT1_FIELDS_COMPATIBLE(*this, rhs); // Delete existing parallel slices. We don't copy parallel slices, so any -// that currently exist will be incorrect. -#if BOUT_USE_FCI_AUTOMAGIC - if (this->isFci() and this->hasParallelSlices() and rhs.hasParallelSlices()) { + // that currently exist will be incorrect. + if (this->hasParallelSlices() and rhs.hasParallelSlices()) { for (size_t i{0}; i < yup_fields.size(); ++i) { yup(i) /= rhs.yup(i); ydown(i) /= rhs.ydown(i); } - } else -#endif - { + } else { clearParallelSlices(); } @@ -159,15 +149,13 @@ Field3D operator+(const Field3D& lhs, const Field3D& rhs) { checkData(rhs); result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); -#if BOUT_USE_FCI_AUTOMAGIC - if (lhs.isFci() and lhs.hasParallelSlices() and rhs.hasParallelSlices()) { + if (lhs.hasParallelSlices() and rhs.hasParallelSlices()) { result.splitParallelSlices(); for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { result.yup(i) = lhs.yup(i) + rhs.yup(i); result.ydown(i) = lhs.ydown(i) + rhs.ydown(i); } } -#endif BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { result[index] = lhs[index] + rhs[index]; @@ -188,16 +176,13 @@ Field3D& Field3D::operator+=(const Field3D& rhs) { ASSERT1_FIELDS_COMPATIBLE(*this, rhs); // Delete existing parallel slices. We don't copy parallel slices, so any -// that currently exist will be incorrect. -#if BOUT_USE_FCI_AUTOMAGIC - if (this->isFci() and this->hasParallelSlices() and rhs.hasParallelSlices()) { + // that currently exist will be incorrect. + if (this->hasParallelSlices() and rhs.hasParallelSlices()) { for (size_t i{0}; i < yup_fields.size(); ++i) { yup(i) += rhs.yup(i); ydown(i) += rhs.ydown(i); } - } else -#endif - { + } else { clearParallelSlices(); } @@ -231,15 +216,13 @@ Field3D operator-(const Field3D& lhs, const Field3D& rhs) { checkData(rhs); result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); -#if BOUT_USE_FCI_AUTOMAGIC - if (lhs.isFci() and lhs.hasParallelSlices() and rhs.hasParallelSlices()) { + if (lhs.hasParallelSlices() and rhs.hasParallelSlices()) { result.splitParallelSlices(); for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { result.yup(i) = lhs.yup(i) - rhs.yup(i); result.ydown(i) = lhs.ydown(i) - rhs.ydown(i); } } -#endif BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { result[index] = lhs[index] - rhs[index]; @@ -260,16 +243,13 @@ Field3D& Field3D::operator-=(const Field3D& rhs) { ASSERT1_FIELDS_COMPATIBLE(*this, rhs); // Delete existing parallel slices. We don't copy parallel slices, so any -// that currently exist will be incorrect. -#if BOUT_USE_FCI_AUTOMAGIC - if (this->isFci() and this->hasParallelSlices() and rhs.hasParallelSlices()) { + // that currently exist will be incorrect. + if (this->hasParallelSlices() and rhs.hasParallelSlices()) { for (size_t i{0}; i < yup_fields.size(); ++i) { yup(i) -= rhs.yup(i); ydown(i) -= rhs.ydown(i); } - } else -#endif - { + } else { clearParallelSlices(); } @@ -329,7 +309,9 @@ Field3D& Field3D::operator*=(const Field2D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. - { clearParallelSlices(); } + { + clearParallelSlices(); + } checkData(*this); checkData(rhs); @@ -391,7 +373,9 @@ Field3D& Field3D::operator/=(const Field2D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. - { clearParallelSlices(); } + { + clearParallelSlices(); + } checkData(*this); checkData(rhs); @@ -453,7 +437,9 @@ Field3D& Field3D::operator+=(const Field2D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. - { clearParallelSlices(); } + { + clearParallelSlices(); + } checkData(*this); checkData(rhs); @@ -514,7 +500,9 @@ Field3D& Field3D::operator-=(const Field2D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. - { clearParallelSlices(); } + { + clearParallelSlices(); + } checkData(*this); checkData(rhs); @@ -640,15 +628,13 @@ Field3D operator*(const Field3D& lhs, const BoutReal rhs) { checkData(rhs); result.setRegion(lhs.getRegionID()); -#if BOUT_USE_FCI_AUTOMAGIC - if (lhs.isFci() and lhs.hasParallelSlices()) { + if (lhs.hasParallelSlices()) { result.splitParallelSlices(); for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { result.yup(i) = lhs.yup(i) * rhs; result.ydown(i) = lhs.ydown(i) * rhs; } } -#endif BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { result[index] = lhs[index] * rhs; @@ -668,16 +654,13 @@ Field3D& Field3D::operator*=(const BoutReal rhs) { if (data.unique()) { // Delete existing parallel slices. We don't copy parallel slices, so any -// that currently exist will be incorrect. -#if BOUT_USE_FCI_AUTOMAGIC - if (this->isFci() and this->hasParallelSlices()) { + // that currently exist will be incorrect. + if (this->hasParallelSlices()) { for (size_t i{0}; i < yup_fields.size(); ++i) { yup(i) *= rhs; ydown(i) *= rhs; } - } else -#endif - { + } else { clearParallelSlices(); } @@ -708,15 +691,13 @@ Field3D operator/(const Field3D& lhs, const BoutReal rhs) { checkData(rhs); result.setRegion(lhs.getRegionID()); -#if BOUT_USE_FCI_AUTOMAGIC - if (lhs.isFci() and lhs.hasParallelSlices()) { + if (lhs.hasParallelSlices()) { result.splitParallelSlices(); for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { result.yup(i) = lhs.yup(i) / rhs; result.ydown(i) = lhs.ydown(i) / rhs; } } -#endif const auto tmp = 1.0 / rhs; BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { @@ -737,16 +718,13 @@ Field3D& Field3D::operator/=(const BoutReal rhs) { if (data.unique()) { // Delete existing parallel slices. We don't copy parallel slices, so any -// that currently exist will be incorrect. -#if BOUT_USE_FCI_AUTOMAGIC - if (this->isFci() and this->hasParallelSlices()) { + // that currently exist will be incorrect. + if (this->hasParallelSlices()) { for (size_t i{0}; i < yup_fields.size(); ++i) { yup(i) /= rhs; ydown(i) /= rhs; } - } else -#endif - { + } else { clearParallelSlices(); } @@ -778,15 +756,13 @@ Field3D operator+(const Field3D& lhs, const BoutReal rhs) { checkData(rhs); result.setRegion(lhs.getRegionID()); -#if BOUT_USE_FCI_AUTOMAGIC - if (lhs.isFci() and lhs.hasParallelSlices()) { + if (lhs.hasParallelSlices()) { result.splitParallelSlices(); for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { result.yup(i) = lhs.yup(i) + rhs; result.ydown(i) = lhs.ydown(i) + rhs; } } -#endif BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { result[index] = lhs[index] + rhs; @@ -806,16 +782,13 @@ Field3D& Field3D::operator+=(const BoutReal rhs) { if (data.unique()) { // Delete existing parallel slices. We don't copy parallel slices, so any -// that currently exist will be incorrect. -#if BOUT_USE_FCI_AUTOMAGIC - if (this->isFci() and this->hasParallelSlices()) { + // that currently exist will be incorrect. + if (this->hasParallelSlices()) { for (size_t i{0}; i < yup_fields.size(); ++i) { yup(i) += rhs; ydown(i) += rhs; } - } else -#endif - { + } else { clearParallelSlices(); } @@ -846,15 +819,13 @@ Field3D operator-(const Field3D& lhs, const BoutReal rhs) { checkData(rhs); result.setRegion(lhs.getRegionID()); -#if BOUT_USE_FCI_AUTOMAGIC - if (lhs.isFci() and lhs.hasParallelSlices()) { + if (lhs.hasParallelSlices()) { result.splitParallelSlices(); for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { result.yup(i) = lhs.yup(i) - rhs; result.ydown(i) = lhs.ydown(i) - rhs; } } -#endif BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { result[index] = lhs[index] - rhs; @@ -874,16 +845,13 @@ Field3D& Field3D::operator-=(const BoutReal rhs) { if (data.unique()) { // Delete existing parallel slices. We don't copy parallel slices, so any -// that currently exist will be incorrect. -#if BOUT_USE_FCI_AUTOMAGIC - if (this->isFci() and this->hasParallelSlices()) { + // that currently exist will be incorrect. + if (this->hasParallelSlices()) { for (size_t i{0}; i < yup_fields.size(); ++i) { yup(i) -= rhs; ydown(i) -= rhs; } - } else -#endif - { + } else { clearParallelSlices(); } @@ -2209,15 +2177,13 @@ Field3D operator*(const BoutReal lhs, const Field3D& rhs) { checkData(rhs); result.setRegion(rhs.getRegionID()); -#if BOUT_USE_FCI_AUTOMAGIC - if (rhs.isFci() and rhs.hasParallelSlices()) { + if (rhs.hasParallelSlices()) { result.splitParallelSlices(); for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { result.yup(i) = lhs * rhs.yup(i); result.ydown(i) = lhs * rhs.ydown(i); } } -#endif BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { result[index] = lhs * rhs[index]; @@ -2238,15 +2204,13 @@ Field3D operator/(const BoutReal lhs, const Field3D& rhs) { checkData(rhs); result.setRegion(rhs.getRegionID()); -#if BOUT_USE_FCI_AUTOMAGIC - if (rhs.isFci() and rhs.hasParallelSlices()) { + if (rhs.hasParallelSlices()) { result.splitParallelSlices(); for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { result.yup(i) = lhs / rhs.yup(i); result.ydown(i) = lhs / rhs.ydown(i); } } -#endif BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { result[index] = lhs / rhs[index]; @@ -2267,15 +2231,13 @@ Field3D operator+(const BoutReal lhs, const Field3D& rhs) { checkData(rhs); result.setRegion(rhs.getRegionID()); -#if BOUT_USE_FCI_AUTOMAGIC - if (rhs.isFci() and rhs.hasParallelSlices()) { + if (rhs.hasParallelSlices()) { result.splitParallelSlices(); for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { result.yup(i) = lhs + rhs.yup(i); result.ydown(i) = lhs + rhs.ydown(i); } } -#endif BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { result[index] = lhs + rhs[index]; @@ -2296,15 +2258,13 @@ Field3D operator-(const BoutReal lhs, const Field3D& rhs) { checkData(rhs); result.setRegion(rhs.getRegionID()); -#if BOUT_USE_FCI_AUTOMAGIC - if (rhs.isFci() and rhs.hasParallelSlices()) { + if (rhs.hasParallelSlices()) { result.splitParallelSlices(); for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { result.yup(i) = lhs - rhs.yup(i); result.ydown(i) = lhs - rhs.ydown(i); } } -#endif BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { result[index] = lhs - rhs[index]; From fd93d6901123911f20050d10dd8e43462004d5d5 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Mon, 17 Mar 2025 21:12:06 -0700 Subject: [PATCH 228/242] FCI parallel: Revert deletion of coordinate slices Added comment to explain why it's ok to use the yup/down slices of the metric components if they are read from the grid file. --- src/mesh/parallel/fci.cxx | 60 +++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/src/mesh/parallel/fci.cxx b/src/mesh/parallel/fci.cxx index f7ba0b9aed..17a6aaf6fd 100644 --- a/src/mesh/parallel/fci.cxx +++ b/src/mesh/parallel/fci.cxx @@ -109,28 +109,45 @@ void load_parallel_metric_components([[maybe_unused]] Coordinates* coords, #define LOAD_PAR(var, doZero) \ load_parallel_metric_component(#var, coords->var, offset, doZero) - // Only some components can be compared between planes - // because each poloidal X-Z plane has a separate coordinate system - LOAD_PAR(dy, false); + // Parallel slices of metric components must NOT be calculated by + // interpolation. The X and Z coordinates are defined independently on + // on each poloidal plane. Instead, these yup/ydown fields are calculated + // by mapping coordinate points, and calculating metrics on the mapped points. + LOAD_PAR(g11, false); + LOAD_PAR(g22, false); + LOAD_PAR(g33, false); + LOAD_PAR(g12, false); + LOAD_PAR(g13, false); + LOAD_PAR(g23, false); + + // LOAD_PAR(Bxy, false); // Not yet written to mesh file + + LOAD_PAR(g_11, false); LOAD_PAR(g_22, false); - // LOAD_PAR(Bxy, false); - - // Other components can't have parallel slices - coords->g11.allowParallelSlices(false); - coords->g22.allowParallelSlices(false); - coords->g33.allowParallelSlices(false); - coords->g12.allowParallelSlices(false); - coords->g13.allowParallelSlices(false); - coords->g23.allowParallelSlices(false); - - coords->g_11.allowParallelSlices(false); - coords->g_33.allowParallelSlices(false); - coords->g_12.allowParallelSlices(false); - coords->g_13.allowParallelSlices(false); - coords->g_23.allowParallelSlices(false); - - coords->J.allowParallelSlices(false); - + LOAD_PAR(g_33, false); + LOAD_PAR(g_12, false); + LOAD_PAR(g_13, false); + LOAD_PAR(g_23, false); + + if (not LOAD_PAR(J, true)) { + auto g = + coords->g11.ynext(offset) * coords->g22.ynext(offset) * coords->g33.ynext(offset) + + 2.0 * coords->g12.ynext(offset) * coords->g13.ynext(offset) + * coords->g23.ynext(offset) + - coords->g11.ynext(offset) * coords->g23.ynext(offset) + * coords->g23.ynext(offset) + - coords->g22.ynext(offset) * coords->g13.ynext(offset) + * coords->g13.ynext(offset) + - coords->g33.ynext(offset) * coords->g12.ynext(offset) + * coords->g12.ynext(offset); + + const auto rgn = fmt::format("RGN_YPAR_{:+d}", offset); + // Check that g is positive + bout::checkPositive(g, "The determinant of g^ij", rgn); + auto J = 1. / sqrt(g); + auto& pcom = coords->J.ynext(offset); + BOUT_FOR(i, J.getRegion(rgn)) { pcom[i] = J[i]; } + } #undef LOAD_PAR #endif } @@ -420,6 +437,7 @@ void FCITransform::calcParallelSlices(Field3D& f) { // Interpolate f onto yup and ydown fields for (const auto& map : field_line_maps) { f.ynext(map.offset) = map.interpolate(f); + //f.ynext(map.offset) = map.integrate(f); f.ynext(map.offset).setRegion(fmt::format("RGN_YPAR_{:+d}", map.offset)); } } From 192d10528aa59d8f05253e8e0a60362613c9eaec Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 19 Mar 2025 20:01:58 -0700 Subject: [PATCH 229/242] parallel_boundary_region: Fix ITER macro Iterated for `i < localmesh->ystart - abs_offset()` so when ystart is 1 and the boundary offset is 1, no boundary is applied. --- include/bout/parallel_boundary_region.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/bout/parallel_boundary_region.hxx b/include/bout/parallel_boundary_region.hxx index 849bb0ffe3..453f7099e4 100644 --- a/include/bout/parallel_boundary_region.hxx +++ b/include/bout/parallel_boundary_region.hxx @@ -150,7 +150,7 @@ public: return bndry_position != rhs.bndry_position; } -#define ITER() for (int i = 0; i < localmesh->ystart - abs_offset(); ++i) +#define ITER() for (int i = 0; i <= localmesh->ystart - abs_offset(); ++i) // dirichlet boundary code void dirichlet_o1(Field3D& f, BoutReal value) const { ITER() { getAt(f, i) = value; } From 8d79e504320704600804c58e484968f6bbf4a733 Mon Sep 17 00:00:00 2001 From: bendudson <219233+bendudson@users.noreply.github.com> Date: Thu, 20 Mar 2025 03:16:41 +0000 Subject: [PATCH 230/242] Apply clang-format changes --- src/field/generated_fieldops.cxx | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/field/generated_fieldops.cxx b/src/field/generated_fieldops.cxx index dd2a7add9a..1664668e5f 100644 --- a/src/field/generated_fieldops.cxx +++ b/src/field/generated_fieldops.cxx @@ -309,9 +309,7 @@ Field3D& Field3D::operator*=(const Field2D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. - { - clearParallelSlices(); - } + { clearParallelSlices(); } checkData(*this); checkData(rhs); @@ -373,9 +371,7 @@ Field3D& Field3D::operator/=(const Field2D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. - { - clearParallelSlices(); - } + { clearParallelSlices(); } checkData(*this); checkData(rhs); @@ -437,9 +433,7 @@ Field3D& Field3D::operator+=(const Field2D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. - { - clearParallelSlices(); - } + { clearParallelSlices(); } checkData(*this); checkData(rhs); @@ -500,9 +494,7 @@ Field3D& Field3D::operator-=(const Field2D& rhs) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. - { - clearParallelSlices(); - } + { clearParallelSlices(); } checkData(*this); checkData(rhs); From e23b104c24170fd1f20f1525adb81430deaae601 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 21 Mar 2025 22:02:44 -0700 Subject: [PATCH 231/242] Fixes for FCI: Load dy, improve errors, use J in Div_par Reverting some changes back to David's version. --- src/mesh/coordinates.cxx | 16 +++++++++------- src/mesh/difops.cxx | 22 +++++++++++++++------- src/mesh/parallel/fci.cxx | 2 ++ 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index e2348d5775..bcbd141890 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -1601,13 +1601,15 @@ Field3D Coordinates::Div_par(const Field3D& f, CELL_LOC outloc, } // Need to modify yup and ydown fields - Field3D f_B = f / Bxy_floc; - f_B.splitParallelSlices(); - for (int i = 0; i < f.getMesh()->ystart; ++i) { - f_B.yup(i) = f.yup(i) / Bxy_floc.yup(i); - f_B.ydown(i) = f.ydown(i) / Bxy_floc.ydown(i); - } - return setName(Bxy * Grad_par(f_B, outloc, method), "Div_par({:s})", f.name); + //Field3D f_B = f / Bxy_floc; + //return setName(Bxy * Grad_par(f_B, outloc, method), + // "Div_par({:s})", f.name); + + auto coords = f.getCoordinates(); + // Note: Arithmetic operators iterate over yup/ydown slices + Field3D f_B = f * coords->J / sqrt(coords->g_22); + return setName(sqrt(coords->g_22) * Grad_par(f_B, outloc, method) / coords->J, + "Div_par({:s})", f.name); } ///////////////////////////////////////////////////////// diff --git a/src/mesh/difops.cxx b/src/mesh/difops.cxx index f722b85afb..541fdb8986 100644 --- a/src/mesh/difops.cxx +++ b/src/mesh/difops.cxx @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -267,15 +268,22 @@ Field3D Div_par(const Field3D& f, const Field3D& v) { Field3D result{emptyFrom(f)}; BOUT_FOR(i, f.getRegion("RGN_NOBNDRY")) { - result[i] = B[i] - * ((f_up[i] * v_up[i] / B_up[i]) - (f_down[i] * v_down[i] / B_down[i])) - / (dy[i] * sqrt(g_22[i])); + result[i] = B[i] * ((f_up[i] * v_up[i] / B_up[i]) + - (f_down[i] * v_down[i] / B_down[i])) + / (2 * dy[i] * sqrt(g_22[i])); -#if CHECK > 0 +#if CHECK > 2 if (!std::isfinite(result[i])) { - output.write("{} {} {} {}\n", f_up[i], v_up[i], f_down[i], v_down[i]); - output.write("{} {} {} {} {}\n", B[i], B_up[i], B_down[i], dy[i], sqrt(g_22[i])); - throw BoutException("Non-finite value in Div_"); + throw BoutException("Non-finite value in Div_par(f, v) at {}\n" + " f down {} up {}\n" + " v down {} up {}\n" + " B down {} central {} up {}\n" + " dy {} sqrt(g_22) {}\n", + i, + f_down[i], f_up[i], + v_down[i], v_up[i], + B_down[i], B[i], B_up[i], + dy[i], sqrt(g_22[i])); } #endif } diff --git a/src/mesh/parallel/fci.cxx b/src/mesh/parallel/fci.cxx index 17a6aaf6fd..eeccd0e7b1 100644 --- a/src/mesh/parallel/fci.cxx +++ b/src/mesh/parallel/fci.cxx @@ -113,6 +113,8 @@ void load_parallel_metric_components([[maybe_unused]] Coordinates* coords, // interpolation. The X and Z coordinates are defined independently on // on each poloidal plane. Instead, these yup/ydown fields are calculated // by mapping coordinate points, and calculating metrics on the mapped points. + LOAD_PAR(dy, false); + LOAD_PAR(g11, false); LOAD_PAR(g22, false); LOAD_PAR(g33, false); From f169b8c7de895fd2f2e0c4e78bdc6aa72afd0a5d Mon Sep 17 00:00:00 2001 From: bendudson <219233+bendudson@users.noreply.github.com> Date: Sat, 22 Mar 2025 05:06:27 +0000 Subject: [PATCH 232/242] Apply clang-format changes --- src/mesh/difops.cxx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/mesh/difops.cxx b/src/mesh/difops.cxx index 541fdb8986..1537650b0e 100644 --- a/src/mesh/difops.cxx +++ b/src/mesh/difops.cxx @@ -268,9 +268,9 @@ Field3D Div_par(const Field3D& f, const Field3D& v) { Field3D result{emptyFrom(f)}; BOUT_FOR(i, f.getRegion("RGN_NOBNDRY")) { - result[i] = B[i] * ((f_up[i] * v_up[i] / B_up[i]) - - (f_down[i] * v_down[i] / B_down[i])) - / (2 * dy[i] * sqrt(g_22[i])); + result[i] = B[i] + * ((f_up[i] * v_up[i] / B_up[i]) - (f_down[i] * v_down[i] / B_down[i])) + / (2 * dy[i] * sqrt(g_22[i])); #if CHECK > 2 if (!std::isfinite(result[i])) { @@ -279,11 +279,8 @@ Field3D Div_par(const Field3D& f, const Field3D& v) { " v down {} up {}\n" " B down {} central {} up {}\n" " dy {} sqrt(g_22) {}\n", - i, - f_down[i], f_up[i], - v_down[i], v_up[i], - B_down[i], B[i], B_up[i], - dy[i], sqrt(g_22[i])); + i, f_down[i], f_up[i], v_down[i], v_up[i], B_down[i], B[i], + B_up[i], dy[i], sqrt(g_22[i])); } #endif } From ce70d8684b351240fd72bb4a36d5cb7da577dbc1 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 26 Mar 2025 10:51:00 -0700 Subject: [PATCH 233/242] Remove FCI_AUTOMAGIC compile option Prefer to have consistent behavior so that code works and can be optimized without breaking. The field.withoutParallelSlices() method can be used to strip slices before calling an operation, if slices are not needed. --- CMakeLists.txt | 3 --- include/bout/field.hxx | 10 ++++------ include/bout/fv_ops.hxx | 5 +---- src/field/field3d.cxx | 17 ++++++++--------- 4 files changed, 13 insertions(+), 22 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e57495885..73a40ccd66 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -593,9 +593,6 @@ else() endif() set(BOUT_USE_METRIC_3D ${BOUT_ENABLE_METRIC_3D}) -option(BOUT_ENABLE_FCI_AUTOMAGIC "Enable (slow?) automatic features for FCI" ON) -set(BOUT_USE_FCI_AUTOMAGIC ${BOUT_ENABLE_FCI_AUTOMAGIC}) - include(CheckCXXSourceCompiles) check_cxx_source_compiles("int main() { const char* name = __PRETTY_FUNCTION__; }" HAS_PRETTY_FUNCTION) diff --git a/include/bout/field.hxx b/include/bout/field.hxx index 017fc39e3b..81130fcad5 100644 --- a/include/bout/field.hxx +++ b/include/bout/field.hxx @@ -684,6 +684,8 @@ T copy(const T& f) { /// Apply a floor value \p f to a field \p var. Any value lower than /// the floor is set to the floor. /// +/// If the field has parallel slices then they will also be floored. +/// /// @param[in] var Variable to apply floor to /// @param[in] f The floor value /// @param[in] rgn The region to calculate the result over @@ -697,8 +699,8 @@ inline T floor(const T& var, BoutReal f, const std::string& rgn = "RGN_ALL") { result[d] = f; } } -#if BOUT_USE_FCI_AUTOMAGIC - if (var.isFci()) { + + if (var.hasParallelSlices()) { for (size_t i = 0; i < result.numberParallelSlices(); ++i) { BOUT_FOR(d, result.yup(i).getRegion(rgn)) { if (result.yup(i)[d] < f) { @@ -711,10 +713,6 @@ inline T floor(const T& var, BoutReal f, const std::string& rgn = "RGN_ALL") { } } } - } else -#endif - { - result.clearParallelSlices(); } return result; } diff --git a/include/bout/fv_ops.hxx b/include/bout/fv_ops.hxx index 8a9baaf3e7..454818d8b5 100644 --- a/include/bout/fv_ops.hxx +++ b/include/bout/fv_ops.hxx @@ -188,16 +188,13 @@ void communicateFluxes(Field3D& f); /// @param[in] fixflux Fix the flux at the boundary to be the value at the /// midpoint (for boundary conditions) /// -/// NB: Uses to/from FieldAligned coordinates +/// Handles FCI by calling ::Div_par(f_in, v_in) template const Field3D Div_par(const Field3D& f_in, const Field3D& v_in, const Field3D& wave_speed_in, bool fixflux = true) { - -#if BOUT_USE_FCI_AUTOMAGIC if (f_in.isFci()) { return ::Div_par(f_in, v_in); } -#endif ASSERT1_FIELDS_COMPATIBLE(f_in, v_in); ASSERT1_FIELDS_COMPATIBLE(f_in, wave_speed_in); diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index 74a6f0853c..72cff777ae 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -93,7 +93,7 @@ Field3D::Field3D(const BoutReal val, Mesh* localmesh) : Field3D(localmesh) { TRACE("Field3D: Copy constructor from value"); *this = val; -#if BOUT_USE_FCI_AUTOMAGIC + if (this->isFci()) { splitParallelSlices(); for (size_t i = 0; i < numberParallelSlices(); ++i) { @@ -101,7 +101,6 @@ Field3D::Field3D(const BoutReal val, Mesh* localmesh) : Field3D(localmesh) { ydown(i) = val; } } -#endif } Field3D::Field3D(Array data_in, Mesh* localmesh, CELL_LOC datalocation, @@ -361,14 +360,12 @@ Field3D& Field3D::operator=(const BoutReal val) { TRACE("Field3D = BoutReal"); track(val, "operator="); -#if BOUT_USE_FCI_AUTOMAGIC if (isFci() && hasParallelSlices()) { for (size_t i = 0; i < numberParallelSlices(); ++i) { yup(i) = val; ydown(i) = val; } } -#else // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. clearParallelSlices(); @@ -386,11 +383,13 @@ Field3D& Field3D::operator=(const BoutReal val) { Field3D& Field3D::calcParallelSlices() { ASSERT2(allowCalcParallelSlices); getCoordinates()->getParallelTransform().calcParallelSlices(*this); -#if BOUT_USE_FCI_AUTOMAGIC - if (this->isFci()) { - this->applyParallelBoundary("parallel_neumann_o2"); - } -#endif + + // This seems like it could work but give wrong results. + // Better to insert NaNs into boundary cells + // if (this->isFci()) { + // this->applyParallelBoundary("parallel_neumann_o2"); + // } + return *this; } From 328880814c81c85edc625b4b3f8ac35b6d018104 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 26 Mar 2025 11:37:08 -0700 Subject: [PATCH 234/242] Remove stray #endif --- src/field/field3d.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index 72cff777ae..7878bb46e8 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -369,7 +369,7 @@ Field3D& Field3D::operator=(const BoutReal val) { // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. clearParallelSlices(); -#endif + resetRegion(); allocate(); From c840122139c609a7cbdf0e1909fd476fc58fc48a Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 26 Mar 2025 21:20:46 -0700 Subject: [PATCH 235/242] Field3D: Fix operator=(BoutReal) If the field has parallel slices, assign the value also to the slices. --- src/field/field3d.cxx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index 7878bb46e8..70569027c3 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -360,15 +360,12 @@ Field3D& Field3D::operator=(const BoutReal val) { TRACE("Field3D = BoutReal"); track(val, "operator="); - if (isFci() && hasParallelSlices()) { + if (hasParallelSlices()) { for (size_t i = 0; i < numberParallelSlices(); ++i) { yup(i) = val; ydown(i) = val; } } - // Delete existing parallel slices. We don't copy parallel slices, so any - // that currently exist will be incorrect. - clearParallelSlices(); resetRegion(); From 568ad422c094c3a918d69a281b7462c3f4ddeb1d Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 17 Nov 2025 12:54:30 +0100 Subject: [PATCH 236/242] applyParallelBoundary can be a no-op for non-fci --- src/field/field3d.cxx | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index 70569027c3..a124141634 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -544,7 +544,12 @@ void Field3D::applyParallelBoundary() { TRACE("Field3D::applyParallelBoundary()"); checkData(*this); - ASSERT1(hasParallelSlices()); + if (isFci()) { + ASSERT1(hasParallelSlices()); + } + if (!hasParallelSlices()) { + return; + } // Apply boundary to this field for (const auto& bndry : getBoundaryOpPars()) { @@ -557,7 +562,12 @@ void Field3D::applyParallelBoundary(BoutReal t) { TRACE("Field3D::applyParallelBoundary(t)"); checkData(*this); - ASSERT1(hasParallelSlices()); + if (isFci()) { + ASSERT1(hasParallelSlices()); + } + if (!hasParallelSlices()) { + return; + } // Apply boundary to this field for (const auto& bndry : getBoundaryOpPars()) { @@ -570,7 +580,12 @@ void Field3D::applyParallelBoundary(const std::string& condition) { TRACE("Field3D::applyParallelBoundary(condition)"); checkData(*this); - ASSERT1(hasParallelSlices()); + if (isFci()) { + ASSERT1(hasParallelSlices()); + } + if (!hasParallelSlices()) { + return; + } /// Get the boundary factory (singleton) BoundaryFactory* bfact = BoundaryFactory::getInstance(); @@ -589,7 +604,12 @@ void Field3D::applyParallelBoundary(const std::string& region, TRACE("Field3D::applyParallelBoundary(region, condition)"); checkData(*this); - ASSERT1(hasParallelSlices()); + if (isFci()) { + ASSERT1(hasParallelSlices()); + } + if (!hasParallelSlices()) { + return; + } /// Get the boundary factory (singleton) BoundaryFactory* bfact = BoundaryFactory::getInstance(); @@ -611,7 +631,12 @@ void Field3D::applyParallelBoundary(const std::string& region, TRACE("Field3D::applyParallelBoundary(region, condition, f)"); checkData(*this); - ASSERT1(hasParallelSlices()); + if (isFci()) { + ASSERT1(hasParallelSlices()); + } + if (!hasParallelSlices()) { + return; + } /// Get the boundary factory (singleton) BoundaryFactory* bfact = BoundaryFactory::getInstance(); From 736343a30945c4a3fd08bbcf375df5aec1f9dbe6 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Mon, 1 Dec 2025 15:00:20 +0000 Subject: [PATCH 237/242] Remove unused `Context` constructor --- include/bout/sys/generator_context.hxx | 3 --- src/sys/generator_context.cxx | 3 --- 2 files changed, 6 deletions(-) diff --git a/include/bout/sys/generator_context.hxx b/include/bout/sys/generator_context.hxx index bbe60fab65..528b96113d 100644 --- a/include/bout/sys/generator_context.hxx +++ b/include/bout/sys/generator_context.hxx @@ -27,9 +27,6 @@ public: /// Context(int ix, int iy, int iz, CELL_LOC loc, Mesh* msh, BoutReal t); - /// Specify the values directly - Context(BoutReal x, BoutReal y, BoutReal z, Mesh* msh, BoutReal t); - /// If constructed without parameters, contains no values (null). /// Requesting x,y,z or t throws an exception Context() = default; diff --git a/src/sys/generator_context.cxx b/src/sys/generator_context.cxx index 25274e8107..236de7f95b 100644 --- a/src/sys/generator_context.cxx +++ b/src/sys/generator_context.cxx @@ -45,8 +45,5 @@ Context::Context(const BoundaryRegion* bndry, int iz, CELL_LOC loc, BoutReal t, parameters["t"] = t; } -Context::Context(BoutReal x, BoutReal y, BoutReal z, Mesh* msh, BoutReal t) - : localmesh(msh), parameters{{"x", x}, {"y", y}, {"z", z}, {"t", t}} {} - } // namespace generator } // namespace bout From 01a214d2a598f78af60ecf70130f7d54fd82d1b0 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Tue, 2 Dec 2025 12:59:00 +0000 Subject: [PATCH 238/242] Add cell indices to `generator::Context` --- include/bout/sys/generator_context.hxx | 10 +++++++++- src/sys/generator_context.cxx | 21 +++++++++------------ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/include/bout/sys/generator_context.hxx b/include/bout/sys/generator_context.hxx index 528b96113d..a5dd9d236a 100644 --- a/include/bout/sys/generator_context.hxx +++ b/include/bout/sys/generator_context.hxx @@ -24,7 +24,6 @@ public: : Context(i.x(), i.y(), 0, (loc == CELL_ZLOW) ? CELL_CENTRE : loc, msh, t) {} /// Specify a cell index, together with the cell location, mesh and time - /// Context(int ix, int iy, int iz, CELL_LOC loc, Mesh* msh, BoutReal t); /// If constructed without parameters, contains no values (null). @@ -41,6 +40,11 @@ public: BoutReal z() const { return get("z"); } BoutReal t() const { return get("t"); } + /// Cell indices + int ix() const { return ix_; } + int jy() const { return jy_; } + int kz() const { return kz_;} + /// Set the value of a parameter with given name Context& set(const std::string& name, BoutReal value) { parameters[name] = value; @@ -73,6 +77,10 @@ public: } private: + int ix_{0}; + int jy_{0}; + int kz_{0}; + Mesh* localmesh{nullptr}; ///< The mesh on which the position is defined /// Contains user-set values which can be set and retrieved diff --git a/src/sys/generator_context.cxx b/src/sys/generator_context.cxx index 236de7f95b..c34f266b76 100644 --- a/src/sys/generator_context.cxx +++ b/src/sys/generator_context.cxx @@ -7,7 +7,7 @@ namespace bout { namespace generator { Context::Context(int ix, int iy, int iz, CELL_LOC loc, Mesh* msh, BoutReal t) - : localmesh(msh) { + : ix_(ix), jy_(iy), kz_(iz), localmesh(msh) { parameters["x"] = (loc == CELL_XLOW) ? 0.5 * (msh->GlobalX(ix) + msh->GlobalX(ix - 1)) : msh->GlobalX(ix); @@ -23,20 +23,17 @@ Context::Context(int ix, int iy, int iz, CELL_LOC loc, Mesh* msh, BoutReal t) } Context::Context(const BoundaryRegion* bndry, int iz, CELL_LOC loc, BoutReal t, Mesh* msh) - : localmesh(msh) { - - // Add one to X index if boundary is in -x direction, so that XLOW is on the boundary - int ix = (bndry->bx < 0) ? bndry->x + 1 : bndry->x; + : // Add one to X index if boundary is in -x direction, so that XLOW is on the boundary + ix_((bndry->bx < 0) ? bndry->x + 1 : bndry->x), + jy_((bndry->by < 0) ? bndry->y + 1 : bndry->y), kz_(iz), localmesh(msh) { parameters["x"] = ((loc == CELL_XLOW) || (bndry->bx != 0)) - ? 0.5 * (msh->GlobalX(ix) + msh->GlobalX(ix - 1)) - : msh->GlobalX(ix); - - int iy = (bndry->by < 0) ? bndry->y + 1 : bndry->y; + ? 0.5 * (msh->GlobalX(ix_) + msh->GlobalX(ix_ - 1)) + : msh->GlobalX(ix_); - parameters["y"] = ((loc == CELL_YLOW) || bndry->by) - ? PI * (msh->GlobalY(iy) + msh->GlobalY(iy - 1)) - : TWOPI * msh->GlobalY(iy); + parameters["y"] = ((loc == CELL_YLOW) || (bndry->by != 0)) + ? PI * (msh->GlobalY(jy_) + msh->GlobalY(jy_ - 1)) + : TWOPI * msh->GlobalY(jy_); parameters["z"] = (loc == CELL_ZLOW) ? TWOPI * (iz - 0.5) / static_cast(msh->LocalNz) From 462e0867bdcbce6d42996ed2c00a5875c92ad83b Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Tue, 2 Dec 2025 13:28:48 +0000 Subject: [PATCH 239/242] Add ability to use variables from grid files in input expressions --- src/field/field_factory.cxx | 48 ++++++++++++++++++++++--- src/field/fieldgenerators.hxx | 28 +++++++++++++++ tests/unit/fake_mesh.hxx | 2 +- tests/unit/field/test_field_factory.cxx | 37 +++++++++++++++++++ 4 files changed, 109 insertions(+), 6 deletions(-) diff --git a/src/field/field_factory.cxx b/src/field/field_factory.cxx index f65f2e7f55..e1bd202e0e 100644 --- a/src/field/field_factory.cxx +++ b/src/field/field_factory.cxx @@ -19,20 +19,24 @@ * along with BOUT++. If not, see . * **************************************************************************/ -#include #include -#include - +#include #include +#include #include #include -#include "bout/constants.hxx" - #include "fieldgenerators.hxx" +#include +#include +#include +#include +#include +#include + using bout::generator::Context; /// Helper function to create a FieldValue generator from a BoutReal @@ -80,6 +84,16 @@ class FieldIndirect : public FieldGenerator { FieldGeneratorPtr target; }; + +// Split a string on commas and trim whitespace from the results +auto trimsplit(const std::string& str) -> std::vector { + auto split = strsplit(str, ','); + std::vector result{}; + result.reserve(split.size()); + std::transform(split.begin(), split.end(), std::back_inserter(result), + [](const std::string& element) { return trim(element); }); + return result; +} } // namespace ////////////////////////////////////////////////////////// @@ -122,6 +136,30 @@ FieldFactory::FieldFactory(Mesh* localmesh, Options* opt) nonconst_options["input"]["max_recursion_depth"].as()); } + const auto field_variables = + nonconst_options["input"]["field_variables"] + .doc("Variables to read from the grid file and make available in expressions. " + "Comma-separated list of variable names") + .withDefault(""); + + if (not field_variables.empty()) { + if (not localmesh->isDataSourceGridFile()) { + throw BoutException("A grid file ('mesh:file') is required for `field_variables`"); + } + + for (const auto& name : trimsplit(field_variables)) { + if (not localmesh->sourceHasVar(name)) { + const auto filename = Options::root()["mesh"]["file"].as(); + throw BoutException( + "Grid file '{}' missing `{}` specified in `input:field_variables`", filename, + name); + } + Field3D var; + localmesh->get(var, name); + addGenerator(name, std::make_shared(var, name)); + } + } + // Useful values addGenerator("pi", std::make_shared(PI)); addGenerator("π", std::make_shared(PI)); diff --git a/src/field/fieldgenerators.hxx b/src/field/fieldgenerators.hxx index 2485b4b82d..1869e2ae6e 100644 --- a/src/field/fieldgenerators.hxx +++ b/src/field/fieldgenerators.hxx @@ -8,10 +8,14 @@ #define BOUT_FIELDGENERATORS_H #include +#include #include +#include #include #include +#include +#include ////////////////////////////////////////////////////////// // Generators from values @@ -352,4 +356,28 @@ private: FieldGeneratorPtr test, gt0, lt0; }; +/// A `Field3D` that can be used in expressions +class Field3DVariable : public FieldGenerator { +public: + Field3DVariable(Field3D var, std::string name) + : variable(std::move(var)), name(std::move(name)) {} + + double generate(const bout::generator::Context& ctx) override { + return variable(ctx.ix(), ctx.jy(), ctx.kz()); + } + + FieldGeneratorPtr clone(const std::list args) override { + if (args.size() != 0) { + throw ParseException("Variable '{}' takes no arguments but got {:d}", args.size()); + } + return std::make_shared(variable, name); + } + + std::string str() const override { return name; } + +private: + Field3D variable; + std::string name; +}; + #endif // BOUT_FIELDGENERATORS_H diff --git a/tests/unit/fake_mesh.hxx b/tests/unit/fake_mesh.hxx index e6f78f8767..208ad79453 100644 --- a/tests/unit/fake_mesh.hxx +++ b/tests/unit/fake_mesh.hxx @@ -122,7 +122,7 @@ public: MPI_Comm getYcomm(int UNUSED(jx)) const override { return BoutComm::get(); } bool periodicY(int UNUSED(jx)) const override { return true; } bool periodicY(int UNUSED(jx), BoutReal& UNUSED(ts)) const override { return true; } - int numberOfYBoundaries() const override { return 1; } + int numberOfYBoundaries() const override { return 0; } std::pair hasBranchCutLower(int UNUSED(jx)) const override { return std::make_pair(false, 0.); } diff --git a/tests/unit/field/test_field_factory.cxx b/tests/unit/field/test_field_factory.cxx index 964788b25a..93b2a39ce3 100644 --- a/tests/unit/field/test_field_factory.cxx +++ b/tests/unit/field/test_field_factory.cxx @@ -1,17 +1,21 @@ #include "gtest/gtest.h" +#include "fake_mesh.hxx" #include "test_extras.hxx" #include "bout/boutexception.hxx" #include "bout/constants.hxx" #include "bout/field2d.hxx" #include "bout/field3d.hxx" #include "bout/field_factory.hxx" +#include "bout/globals.hxx" #include "bout/mesh.hxx" +#include "bout/options_io.hxx" #include "bout/output.hxx" #include "bout/paralleltransform.hxx" #include "bout/traits.hxx" #include "fake_mesh_fixture.hxx" +#include "test_tmpfiles.hxx" // The unit tests use the global mesh using namespace bout::globals; @@ -969,3 +973,36 @@ TEST_F(FieldFactoryCreateAndTransformTest, Create3DCantTransform) { EXPECT_TRUE(IsFieldEqual(output, expected)); } + +struct FieldFactoryFieldVariableTest : public FakeMeshFixture { + WithQuietOutput quiet{output_info}; +}; + +TEST_F(FieldFactoryFieldVariableTest, CreateField3D) { + bout::testing::TempFile filename; + + { + // Write some fields to a grid file + FieldFactory factory{mesh}; + const auto rho = factory.create3D("sqrt(x^2 + y^2)"); + const auto theta = factory.create3D("atan(y, x)"); + Options grid{{"rho", rho}, + {"theta", theta}, + {"nx", mesh->LocalNx}, + {"ny", mesh->LocalNy}, + {"nz", mesh->LocalNz}}; + bout::OptionsIO::create(filename)->write(grid); + } + + { + Options options{{"mesh", {{"file", filename.string()}}}, + {"input", {{"field_variables", "rho, theta"}}}}; + + dynamic_cast(mesh)->setGridDataSource(new GridFile{filename}); + auto factory = FieldFactory{mesh, &options}; + + const auto output = factory.create3D("rho * cos(theta)"); + const auto x = factory.create3D("x"); + EXPECT_TRUE(IsFieldEqual(output, x)); + } +} From 68580cfd59f36541cfaff6eebfbb66910c904972 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Tue, 2 Dec 2025 13:29:18 +0000 Subject: [PATCH 240/242] tests: Only remove temp files if test passes --- tests/unit/test_tmpfiles.hxx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/unit/test_tmpfiles.hxx b/tests/unit/test_tmpfiles.hxx index 35d7579df2..db7387bb45 100644 --- a/tests/unit/test_tmpfiles.hxx +++ b/tests/unit/test_tmpfiles.hxx @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -30,7 +31,11 @@ public: TempFile& operator=(const TempFile&) = delete; TempFile& operator=(TempFile&&) = delete; - ~TempFile() { std::filesystem::remove(filename); } + ~TempFile() { + if (std::uncaught_exceptions() <= 0) { + std::filesystem::remove_all(filename); + } + } // Enable conversions to std::string / const char* operator std::string() const { return filename.string(); } From b026f701880c1f28c3dc11f717257b9e21301386 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Tue, 2 Dec 2025 16:06:24 +0000 Subject: [PATCH 241/242] Enable reading `Field2D`s as well as `Field3D`s for expressions --- manual/sphinx/user_docs/input_grids.rst | 12 +++++ src/field/field_factory.cxx | 61 +++++++++++++++---------- src/field/fieldgenerators.hxx | 15 +++--- tests/unit/field/test_field_factory.cxx | 60 +++++++++++++++++++++++- 4 files changed, 116 insertions(+), 32 deletions(-) diff --git a/manual/sphinx/user_docs/input_grids.rst b/manual/sphinx/user_docs/input_grids.rst index 3d6be2cf77..6aa4e0bcf8 100644 --- a/manual/sphinx/user_docs/input_grids.rst +++ b/manual/sphinx/user_docs/input_grids.rst @@ -155,6 +155,18 @@ The only quantities which are required are the sizes of the grid. If these are the only quantities specified, then the coordinates revert to Cartesian. +You can read additional quantities from the grid and make them available in +expressions in the input file using ``input:read_Field3Ds`` and +``input:read_Field2Ds``: + +.. code-block:: cfg + + [input] + read_Field2Ds = rho, theta + + [mesh] + B = (1 / rho) * cos(theta) + This section describes how to generate inputs for tokamak equilibria. If you’re not interested in tokamaks then you can skip to the next section. diff --git a/src/field/field_factory.cxx b/src/field/field_factory.cxx index e1bd202e0e..825d7f3f73 100644 --- a/src/field/field_factory.cxx +++ b/src/field/field_factory.cxx @@ -24,8 +24,12 @@ #include #include +#include +#include +#include #include #include +#include #include #include "fieldgenerators.hxx" @@ -94,6 +98,35 @@ auto trimsplit(const std::string& str) -> std::vector { [](const std::string& element) { return trim(element); }); return result; } + +// Read variables from the grid file and make them available in expressions +template +auto read_grid_variables(FieldFactory& factory, Mesh& mesh, Options& options, + const std::string& option_name) { + const auto field_variables = + options["input"][option_name] + .doc("Variables to read from the grid file and make available in expressions. " + "Comma-separated list of variable names") + .withDefault(""); + + if (not field_variables.empty()) { + if (not mesh.isDataSourceGridFile()) { + throw BoutException("A grid file ('mesh:file') is required for `input:{}`", + option_name); + } + + for (const auto& name : trimsplit(field_variables)) { + if (not mesh.sourceHasVar(name)) { + const auto filename = Options::root()["mesh"]["file"].as(); + throw BoutException("Grid file '{}' missing `{}` specified in `input:{}`", + filename, name, option_name); + } + T var; + mesh.get(var, name); + factory.addGenerator(name, std::make_shared>(var, name)); + } + } +} } // namespace ////////////////////////////////////////////////////////// @@ -136,30 +169,6 @@ FieldFactory::FieldFactory(Mesh* localmesh, Options* opt) nonconst_options["input"]["max_recursion_depth"].as()); } - const auto field_variables = - nonconst_options["input"]["field_variables"] - .doc("Variables to read from the grid file and make available in expressions. " - "Comma-separated list of variable names") - .withDefault(""); - - if (not field_variables.empty()) { - if (not localmesh->isDataSourceGridFile()) { - throw BoutException("A grid file ('mesh:file') is required for `field_variables`"); - } - - for (const auto& name : trimsplit(field_variables)) { - if (not localmesh->sourceHasVar(name)) { - const auto filename = Options::root()["mesh"]["file"].as(); - throw BoutException( - "Grid file '{}' missing `{}` specified in `input:field_variables`", filename, - name); - } - Field3D var; - localmesh->get(var, name); - addGenerator(name, std::make_shared(var, name)); - } - } - // Useful values addGenerator("pi", std::make_shared(PI)); addGenerator("π", std::make_shared(PI)); @@ -214,6 +223,10 @@ FieldFactory::FieldFactory(Mesh* localmesh, Options* opt) // Where switch function addGenerator("where", std::make_shared(nullptr, nullptr, nullptr)); + + // Variables from the grid file + read_grid_variables(*this, *localmesh, nonconst_options, "read_Field3Ds"); + read_grid_variables(*this, *localmesh, nonconst_options, "read_Field2Ds"); } Field2D FieldFactory::create2D(const std::string& value, const Options* opt, diff --git a/src/field/fieldgenerators.hxx b/src/field/fieldgenerators.hxx index 1869e2ae6e..7315ed3eba 100644 --- a/src/field/fieldgenerators.hxx +++ b/src/field/fieldgenerators.hxx @@ -8,9 +8,9 @@ #define BOUT_FIELDGENERATORS_H #include -#include #include #include +#include #include #include @@ -311,7 +311,7 @@ public: // Constructor FieldTanhHat(FieldGeneratorPtr xin, FieldGeneratorPtr widthin, FieldGeneratorPtr centerin, FieldGeneratorPtr steepnessin) - : X(xin), width(widthin), center(centerin), steepness(steepnessin){}; + : X(xin), width(widthin), center(centerin), steepness(steepnessin) {}; // Clone containing the list of arguments FieldGeneratorPtr clone(const std::list args) override; BoutReal generate(const bout::generator::Context& pos) override; @@ -326,7 +326,7 @@ private: class FieldWhere : public FieldGenerator { public: FieldWhere(FieldGeneratorPtr test, FieldGeneratorPtr gt0, FieldGeneratorPtr lt0) - : test(test), gt0(gt0), lt0(lt0){}; + : test(test), gt0(gt0), lt0(lt0) {}; FieldGeneratorPtr clone(const std::list args) override { if (args.size() != 3) { @@ -357,9 +357,10 @@ private: }; /// A `Field3D` that can be used in expressions -class Field3DVariable : public FieldGenerator { +template > +class GridVariable : public FieldGenerator { public: - Field3DVariable(Field3D var, std::string name) + GridVariable(T var, std::string name) : variable(std::move(var)), name(std::move(name)) {} double generate(const bout::generator::Context& ctx) override { @@ -370,13 +371,13 @@ public: if (args.size() != 0) { throw ParseException("Variable '{}' takes no arguments but got {:d}", args.size()); } - return std::make_shared(variable, name); + return std::make_shared>(variable, name); } std::string str() const override { return name; } private: - Field3D variable; + T variable; std::string name; }; diff --git a/tests/unit/field/test_field_factory.cxx b/tests/unit/field/test_field_factory.cxx index 93b2a39ce3..c8c5f7cb3e 100644 --- a/tests/unit/field/test_field_factory.cxx +++ b/tests/unit/field/test_field_factory.cxx @@ -996,7 +996,7 @@ TEST_F(FieldFactoryFieldVariableTest, CreateField3D) { { Options options{{"mesh", {{"file", filename.string()}}}, - {"input", {{"field_variables", "rho, theta"}}}}; + {"input", {{"read_Field3Ds", "rho, theta"}}}}; dynamic_cast(mesh)->setGridDataSource(new GridFile{filename}); auto factory = FieldFactory{mesh, &options}; @@ -1006,3 +1006,61 @@ TEST_F(FieldFactoryFieldVariableTest, CreateField3D) { EXPECT_TRUE(IsFieldEqual(output, x)); } } + +TEST_F(FieldFactoryFieldVariableTest, CreateField2D) { + bout::testing::TempFile filename; + + { + // Write some fields to a grid file + FieldFactory factory{mesh}; + const auto rho = factory.create2D("sqrt(x^2 + y^2)"); + const auto theta = factory.create2D("atan(y, x)"); + Options grid{{"rho", rho}, + {"theta", theta}, + {"nx", mesh->LocalNx}, + {"ny", mesh->LocalNy}, + {"nz", mesh->LocalNz}}; + bout::OptionsIO::create(filename)->write(grid); + } + + { + Options options{{"mesh", {{"file", filename.string()}}}, + {"input", {{"read_Field2Ds", "rho, theta"}}}}; + + dynamic_cast(mesh)->setGridDataSource(new GridFile{filename}); + auto factory = FieldFactory{mesh, &options}; + + const auto output = factory.create2D("rho * cos(theta)"); + const auto x = factory.create2D("x"); + EXPECT_TRUE(IsFieldEqual(output, x)); + } +} + +TEST_F(FieldFactoryFieldVariableTest, NoMeshFile) { + Options options{{"input", {{"read_Field2Ds", "rho, theta"}}}}; + + EXPECT_THROW((FieldFactory(mesh, &options)), BoutException); +} + +TEST_F(FieldFactoryFieldVariableTest, MissingVariable) { + bout::testing::TempFile filename; + + { + // Write some fields to a grid file + FieldFactory factory{mesh}; + const auto rho = factory.create3D("sqrt(x^2 + y^2)"); + Options grid{{"rho", rho}, + {"nx", mesh->LocalNx}, + {"ny", mesh->LocalNy}, + {"nz", mesh->LocalNz}}; + bout::OptionsIO::create(filename)->write(grid); + } + + { + Options options{{"mesh", {{"file", filename.string()}}}, + {"input", {{"read_Field3Ds", "rho, theta"}}}}; + + dynamic_cast(mesh)->setGridDataSource(new GridFile{filename}); + EXPECT_THROW((FieldFactory{mesh, &options}), BoutException); + } +} From b139ae4d195f601261967f55d65afa176546c024 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Tue, 2 Dec 2025 16:49:00 +0000 Subject: [PATCH 242/242] Refactor reading grid variables for input expressions --- manual/sphinx/user_docs/input_grids.rst | 13 +++-- src/field/field_factory.cxx | 72 +++++++++++++------------ tests/unit/field/test_field_factory.cxx | 42 ++++++++++++--- 3 files changed, 81 insertions(+), 46 deletions(-) diff --git a/manual/sphinx/user_docs/input_grids.rst b/manual/sphinx/user_docs/input_grids.rst index 6aa4e0bcf8..983346f14e 100644 --- a/manual/sphinx/user_docs/input_grids.rst +++ b/manual/sphinx/user_docs/input_grids.rst @@ -156,16 +156,19 @@ these are the only quantities specified, then the coordinates revert to Cartesian. You can read additional quantities from the grid and make them available in -expressions in the input file using ``input:read_Field3Ds`` and -``input:read_Field2Ds``: +expressions in the input file by listing them in the ``input:grid_variables`` +section, with the key being the name in the grid file (``mesh:file``) and the +value being the type (one of ``field3d``, ``field2d``, ``boutreal``): .. code-block:: cfg - [input] - read_Field2Ds = rho, theta + [input:grid_variables] + rho = field2d + theta = field2d + scale = boutreal [mesh] - B = (1 / rho) * cos(theta) + B = (scale / rho) * cos(theta) This section describes how to generate inputs for tokamak equilibria. If you’re not interested in tokamaks then you can skip to the next section. diff --git a/src/field/field_factory.cxx b/src/field/field_factory.cxx index 825d7f3f73..243ee3f20c 100644 --- a/src/field/field_factory.cxx +++ b/src/field/field_factory.cxx @@ -31,15 +31,15 @@ #include #include #include +#include +#include +#include #include "fieldgenerators.hxx" -#include #include -#include #include #include -#include using bout::generator::Context; @@ -53,6 +53,8 @@ FieldGeneratorPtr generator(BoutReal* ptr) { return std::make_shared(ptr); } +BOUT_ENUM_CLASS(GridVariableFunction, field3d, field2d, boutreal); + namespace { /// Provides a placeholder whose target can be changed after creation. /// This enables recursive FieldGenerator expressions to be generated @@ -89,41 +91,44 @@ class FieldIndirect : public FieldGenerator { FieldGeneratorPtr target; }; -// Split a string on commas and trim whitespace from the results -auto trimsplit(const std::string& str) -> std::vector { - auto split = strsplit(str, ','); - std::vector result{}; - result.reserve(split.size()); - std::transform(split.begin(), split.end(), std::back_inserter(result), - [](const std::string& element) { return trim(element); }); - return result; -} - // Read variables from the grid file and make them available in expressions template -auto read_grid_variables(FieldFactory& factory, Mesh& mesh, Options& options, - const std::string& option_name) { - const auto field_variables = - options["input"][option_name] - .doc("Variables to read from the grid file and make available in expressions. " - "Comma-separated list of variable names") - .withDefault(""); - - if (not field_variables.empty()) { +auto add_grid_variable(FieldFactory& factory, Mesh& mesh, const std::string& name) { + T var; + mesh.get(var, name); + factory.addGenerator(name, std::make_shared>(var, name)); +} + +auto read_grid_variables(FieldFactory& factory, Mesh& mesh, Options& options) { + auto& field_variables = options["input"]["grid_variables"].doc( + "Variables to read from the grid file and make available in expressions"); + + for (const auto& [name, value] : field_variables) { if (not mesh.isDataSourceGridFile()) { - throw BoutException("A grid file ('mesh:file') is required for `input:{}`", - option_name); + throw BoutException( + "A grid file ('mesh:file') is required for `input:grid_variables`"); } - for (const auto& name : trimsplit(field_variables)) { - if (not mesh.sourceHasVar(name)) { - const auto filename = Options::root()["mesh"]["file"].as(); - throw BoutException("Grid file '{}' missing `{}` specified in `input:{}`", - filename, name, option_name); - } - T var; + if (not mesh.sourceHasVar(name)) { + const auto filename = Options::root()["mesh"]["file"].as(); + throw BoutException( + "Grid file '{}' missing `{}` specified in `input:grid_variables`", filename, + name); + } + + const auto func = value.as(); + switch (func) { + case GridVariableFunction::field3d: + add_grid_variable(factory, mesh, name); + break; + case GridVariableFunction::field2d: + add_grid_variable(factory, mesh, name); + break; + case GridVariableFunction::boutreal: + BoutReal var{}; mesh.get(var, name); - factory.addGenerator(name, std::make_shared>(var, name)); + factory.addGenerator(name, std::make_shared(var)); + break; } } } @@ -225,8 +230,7 @@ FieldFactory::FieldFactory(Mesh* localmesh, Options* opt) addGenerator("where", std::make_shared(nullptr, nullptr, nullptr)); // Variables from the grid file - read_grid_variables(*this, *localmesh, nonconst_options, "read_Field3Ds"); - read_grid_variables(*this, *localmesh, nonconst_options, "read_Field2Ds"); + read_grid_variables(*this, *localmesh, nonconst_options); } Field2D FieldFactory::create2D(const std::string& value, const Options* opt, diff --git a/tests/unit/field/test_field_factory.cxx b/tests/unit/field/test_field_factory.cxx index c8c5f7cb3e..dd5d730545 100644 --- a/tests/unit/field/test_field_factory.cxx +++ b/tests/unit/field/test_field_factory.cxx @@ -995,8 +995,9 @@ TEST_F(FieldFactoryFieldVariableTest, CreateField3D) { } { - Options options{{"mesh", {{"file", filename.string()}}}, - {"input", {{"read_Field3Ds", "rho, theta"}}}}; + Options options{ + {"mesh", {{"file", filename.string()}}}, + {"input", {{"grid_variables", {{"rho", "field3d"}, {"theta", "field3d"}}}}}}; dynamic_cast(mesh)->setGridDataSource(new GridFile{filename}); auto factory = FieldFactory{mesh, &options}; @@ -1024,8 +1025,9 @@ TEST_F(FieldFactoryFieldVariableTest, CreateField2D) { } { - Options options{{"mesh", {{"file", filename.string()}}}, - {"input", {{"read_Field2Ds", "rho, theta"}}}}; + Options options{ + {"mesh", {{"file", filename.string()}}}, + {"input", {{"grid_variables", {{"rho", "field2d"}, {"theta", "field2d"}}}}}}; dynamic_cast(mesh)->setGridDataSource(new GridFile{filename}); auto factory = FieldFactory{mesh, &options}; @@ -1036,8 +1038,33 @@ TEST_F(FieldFactoryFieldVariableTest, CreateField2D) { } } +TEST_F(FieldFactoryFieldVariableTest, ReadBoutReal) { + bout::testing::TempFile filename; + + { + Options grid{{"rho", 4}, + {"theta", 5}, + {"nx", mesh->LocalNx}, + {"ny", mesh->LocalNy}, + {"nz", mesh->LocalNz}}; + bout::OptionsIO::create(filename)->write(grid); + } + + { + Options options{ + {"mesh", {{"file", filename.string()}}}, + {"input", {{"grid_variables", {{"rho", "boutreal"}, {"theta", "boutreal"}}}}}}; + + dynamic_cast(mesh)->setGridDataSource(new GridFile{filename}); + auto factory = FieldFactory{mesh, &options}; + + const auto output = factory.create3D("rho * theta"); + EXPECT_TRUE(IsFieldEqual(output, 4 * 5)); + } +} + TEST_F(FieldFactoryFieldVariableTest, NoMeshFile) { - Options options{{"input", {{"read_Field2Ds", "rho, theta"}}}}; + Options options{{"input", {{"grid_variables", {{"rho", "field3d"}}}}}}; EXPECT_THROW((FieldFactory(mesh, &options)), BoutException); } @@ -1057,8 +1084,9 @@ TEST_F(FieldFactoryFieldVariableTest, MissingVariable) { } { - Options options{{"mesh", {{"file", filename.string()}}}, - {"input", {{"read_Field3Ds", "rho, theta"}}}}; + Options options{ + {"mesh", {{"file", filename.string()}}}, + {"input", {{"grid_variables", {{"rho", "field3d"}, {"theta", "field3d"}}}}}}; dynamic_cast(mesh)->setGridDataSource(new GridFile{filename}); EXPECT_THROW((FieldFactory{mesh, &options}), BoutException);