From aa5ea585bc3ba6281204eb802df539a66984317a Mon Sep 17 00:00:00 2001 From: tomc271 Date: Thu, 24 Sep 2026 14:46:14 +0100 Subject: [PATCH 01/44] Add `format_as` methods delegating to existing `toString` methods so that fmt library will handle them automatically. --- include/bout/bout_types.hxx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/bout/bout_types.hxx b/include/bout/bout_types.hxx index 1d8b459c0c..3e8876430d 100644 --- a/include/bout/bout_types.hxx +++ b/include/bout/bout_types.hxx @@ -51,6 +51,7 @@ constexpr CELL_LOC CELL_VSHIFT = CELL_LOC::vshift; std::string toString(CELL_LOC location); CELL_LOC CELL_LOCFromString(const std::string& location_string); +inline std::string format_as(CELL_LOC loc) { return toString(loc); } /// Differential methods. Both central and upwind enum class DIFF_METHOD { deflt, u1, u2, c2, w2, w3, c4, u3, fft, split, s2 }; @@ -67,6 +68,7 @@ constexpr DIFF_METHOD DIFF_SPLIT = DIFF_METHOD::split; constexpr DIFF_METHOD DIFF_S2 = DIFF_METHOD::s2; std::string toString(DIFF_METHOD location); +inline std::string format_as(DIFF_METHOD method) { return toString(method); } /// Specify grid region for looping enum class REGION { all, nobndry, nox, noy, noz }; @@ -77,6 +79,7 @@ constexpr REGION RGN_NOY = REGION::noy; constexpr REGION RGN_NOZ = REGION::noz; std::string toString(REGION region); +inline std::string format_as(REGION region) { return toString(region); } /// To identify particular directions (in index space): /// - X, Y, Z are the coordinate directions @@ -88,6 +91,7 @@ std::string toString(REGION region); enum class DIRECTION { X, Y, Z, YAligned, YOrthogonal }; std::string toString(DIRECTION direction); +inline std::string format_as(DIRECTION dir) { return toString(dir); } /// Identify kind of a field's y-direction /// - Standard is the default for the Mesh/Coordinates/ParallelTransform @@ -97,6 +101,7 @@ enum class YDirectionType { Standard, Aligned }; std::string toString(YDirectionType d); YDirectionType YDirectionTypeFromString(const std::string& y_direction_string); +inline std::string format_as(YDirectionType d) { return toString(d); } /// Identify kind of a field's z-direction /// - Standard is the default @@ -107,6 +112,7 @@ enum class ZDirectionType { Standard, Average }; std::string toString(ZDirectionType d); ZDirectionType ZDirectionTypeFromString(const std::string& z_direction_string); +inline std::string format_as(ZDirectionType d) { return toString(d); } /// Container for direction types struct DirectionTypes { @@ -124,11 +130,13 @@ void swap(const DirectionTypes& first, const DirectionTypes& second); enum class STAGGER { None, C2L, L2C }; std::string toString(STAGGER stagger); +inline std::string format_as(STAGGER stagger) { return toString(stagger); } /// To identify types of derivative method combinations enum class DERIV { Standard, StandardSecond, StandardFourth, Upwind, Flux }; std::string toString(DERIV deriv); +inline std::string format_as(DERIV deriv) { return toString(deriv); } // A small struct that can be used to wrap a specific enum value, giving // it a unique type that can be passed as a valid type to templates and From 2f827598d4183296937281673a0e8ee1c3184e1e Mon Sep 17 00:00:00 2001 From: tomc271 Date: Thu, 24 Sep 2026 14:55:58 +0100 Subject: [PATCH 02/44] Handle 's' format specification in fmt::formatter> (by falling back to the default 'c' representation). --- include/bout/output_bout_types.hxx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/include/bout/output_bout_types.hxx b/include/bout/output_bout_types.hxx index 77ea8ebf2d..392d0de54f 100644 --- a/include/bout/output_bout_types.hxx +++ b/include/bout/output_bout_types.hxx @@ -26,12 +26,15 @@ struct fmt::formatter> { // Presentation format: 'c' - components, 'i' - index. char presentation = 'c'; - // Parses format specifications of the form ['c' | 'i']. + // Parses format specifications of the form ['c' | 'i' | 's']. constexpr auto parse(format_parse_context& ctx) { const auto* it = ctx.begin(); const auto* end = ctx.end(); - if (it != end && (*it == 'c' || *it == 'i')) { - presentation = *it++; + + if (it != end && (*it == 'c' || *it == 'i' || *it == 's')) { + // If 's' is provided, fall back to the default 'c' representation + presentation = (*it == 's') ? 'c' : *it; + ++it; } // Check if reached the end of the range: From f5ec5e0280648d1289a1d4f3581f2143b9a0dfef Mon Sep 17 00:00:00 2001 From: tomc271 Date: Thu, 24 Sep 2026 14:58:57 +0100 Subject: [PATCH 03/44] No longer need to call `toString()` on CELL_LOC types as fmt will handle it (via the new `format_as` wrappers). --- include/bout/field.hxx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/include/bout/field.hxx b/include/bout/field.hxx index fc24020ff6..76eb2c2e87 100644 --- a/include/bout/field.hxx +++ b/include/bout/field.hxx @@ -164,8 +164,8 @@ inline bool areFieldsCompatible(const Field& field1, const Field& field2) { if ((field1).getLocation() != (field2).getLocation()) { \ throw BoutException("Error in {:s}:{:d}\nFields at different position:" \ "`{:s}` at {:s}, `{:s}` at {:s}", \ - __FILE__, __LINE__, #field1, toString((field1).getLocation()), \ - #field2, toString((field2).getLocation())); \ + __FILE__, __LINE__, #field1, (field1).getLocation(), \ + #field2, (field2).getLocation()); \ } \ if ((field1).getCoordinates() != (field2).getCoordinates()) { \ throw BoutException("Error in {:s}:{:d}\nFields have different coordinates:" \ @@ -192,8 +192,8 @@ inline bool areFieldsCompatible(const Field& field1, const Field& field2) { if ((expr1).getLocation() != (expr2).getLocation()) { \ throw BoutException("Error in {:s}:{:d}\nFields at different position:" \ "`{:s}` at {:s}, `{:s}` at {:s}", \ - __FILE__, __LINE__, #expr1, toString((expr1).getLocation()), \ - #expr2, toString((expr2).getLocation())); \ + __FILE__, __LINE__, #expr1, (expr1).getLocation(), \ + #expr2, (expr2).getLocation()); \ } \ if ((expr1).getMesh() != (expr2).getMesh()) { \ throw BoutException("Error in {:s}:{:d}\nFields are on different Meshes:" \ @@ -300,7 +300,7 @@ inline void checkFinite(const T& f, const std::string& name = "field", BOUT_FOR_SERIAL(i, f.getRegion(rgn)) { if (!std::isfinite(f[i])) { - throw BoutException("{:s} is not finite at {:s}", name, toString(i)); + throw BoutException("{:s} is not finite at {:s}", name, i); } } } @@ -324,8 +324,7 @@ inline void checkPositive(const T& f, const std::string& name = "field", BOUT_FOR_SERIAL(i, f.getRegion(rgn)) { if (f[i] <= 0.) { throw BoutException("{:s} ({:s} {:s}) is {:e} (not positive) at {:s}", name, - toString(f.getLocation()), toString(f.getDirections()), f[i], - toString(i)); + f.getLocation(), toString(f.getDirections()), f[i], i); } } } From 163fda26ded57762db0cf17d834e84d9159e38a6 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Thu, 24 Sep 2026 15:10:22 +0100 Subject: [PATCH 04/44] Move fmt::formatter> to same file as SpecificInd is defined (ODR) Prevents fmt compilation issues. --- include/bout/output_bout_types.hxx | 44 ------------------------------ include/bout/region.hxx | 39 ++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 44 deletions(-) diff --git a/include/bout/output_bout_types.hxx b/include/bout/output_bout_types.hxx index 392d0de54f..b9ff56dd33 100644 --- a/include/bout/output_bout_types.hxx +++ b/include/bout/output_bout_types.hxx @@ -21,50 +21,6 @@ #include #include -template -struct fmt::formatter> { - // Presentation format: 'c' - components, 'i' - index. - char presentation = 'c'; - - // Parses format specifications of the form ['c' | 'i' | 's']. - constexpr auto parse(format_parse_context& ctx) { - const auto* it = ctx.begin(); - const auto* end = ctx.end(); - - if (it != end && (*it == 'c' || *it == 'i' || *it == 's')) { - // If 's' is provided, fall back to the default 'c' representation - presentation = (*it == 's') ? 'c' : *it; - ++it; - } - - // Check if reached the end of the range: - if (it != end && *it != '}') { - throw format_error("invalid format"); - } - - // Return an iterator past the end of the parsed range: - return it; - } - - // Formats the point p using the parsed format specification (presentation) - // stored in this formatter. - template - auto format(const SpecificInd& ind, FormatContext& ctx) const { - // ctx.out() is an output iterator to write to. - if (presentation == 'c') { - switch (N) { - case IND_TYPE::IND_2D: - return format_to(ctx.out(), "({}, {})", ind.x(), ind.y()); - case IND_TYPE::IND_3D: - return format_to(ctx.out(), "({}, {}, {})", ind.x(), ind.y(), ind.z()); - case IND_TYPE::IND_PERP: - return format_to(ctx.out(), "({}, {})", ind.x(), ind.z()); - } - } - return format_to(ctx.out(), "({})", ind.ind); - } -}; - namespace bout { namespace details { template diff --git a/include/bout/region.hxx b/include/bout/region.hxx index 802b3758ba..ad5daa793c 100644 --- a/include/bout/region.hxx +++ b/include/bout/region.hxx @@ -56,6 +56,7 @@ #include "bout/build_config.hxx" #include "bout/build_defines.hxx" #include "bout/openmpwrap.hxx" // IWYU pragma: keep +#include class BoutMask; @@ -377,6 +378,44 @@ inline std::string toString(const IndPerp& i) { return "(" + std::to_string(i.x()) + ", " + std::to_string(i.z()) + ")"; } +// Custom fmt formatter for SpecificInd +template +struct fmt::formatter> { + char presentation = 'c'; + + // Parses format specifications: ['c' | 'i' | 's'] or empty {} + constexpr auto parse(format_parse_context& ctx) { + auto it = ctx.begin(); + auto end = ctx.end(); + + if (it != end && (*it == 'c' || *it == 'i' || *it == 's')) { + presentation = (*it == 's') ? 'c' : *it; + ++it; + } + + if (it != end && *it != '}') { + throw format_error("invalid format"); + } + + return it; + } + + template + auto format(const SpecificInd& ind, FormatContext& ctx) const { + if (presentation == 'i') { + return fmt::format_to(ctx.out(), "({})", ind.ind); + } + + if constexpr (N == IND_TYPE::IND_2D) { + return fmt::format_to(ctx.out(), "({}, {})", ind.x(), ind.y()); + } else if constexpr (N == IND_TYPE::IND_PERP) { + return fmt::format_to(ctx.out(), "({}, {})", ind.x(), ind.z()); + } else { + return fmt::format_to(ctx.out(), "({}, {}, {})", ind.x(), ind.y(), ind.z()); + } + } +}; + /// Structure to hold various derived "statistics" from a particular region struct RegionStats { int numBlocks = 0; ///< How many blocks From 670310af7c04b41f7ae2d1b651439ed2979da941 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Thu, 24 Sep 2026 18:27:10 +0100 Subject: [PATCH 05/44] Refactor `fmt::formatter>::format()` method to match the original code flow. --- include/bout/region.hxx | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/include/bout/region.hxx b/include/bout/region.hxx index ad5daa793c..a4776cd3fe 100644 --- a/include/bout/region.hxx +++ b/include/bout/region.hxx @@ -402,17 +402,16 @@ struct fmt::formatter> { template auto format(const SpecificInd& ind, FormatContext& ctx) const { - if (presentation == 'i') { - return fmt::format_to(ctx.out(), "({})", ind.ind); - } - - if constexpr (N == IND_TYPE::IND_2D) { - return fmt::format_to(ctx.out(), "({}, {})", ind.x(), ind.y()); - } else if constexpr (N == IND_TYPE::IND_PERP) { - return fmt::format_to(ctx.out(), "({}, {})", ind.x(), ind.z()); - } else { - return fmt::format_to(ctx.out(), "({}, {}, {})", ind.x(), ind.y(), ind.z()); + if (presentation == 'c') { + if constexpr (N == IND_TYPE::IND_2D) { + return fmt::format_to(ctx.out(), "({}, {})", ind.x(), ind.y()); + } else if constexpr (N == IND_TYPE::IND_3D) { + return fmt::format_to(ctx.out(), "({}, {}, {})", ind.x(), ind.y(), ind.z()); + } else if constexpr (N == IND_TYPE::IND_PERP) { + return fmt::format_to(ctx.out(), "({}, {})", ind.x(), ind.z()); + } } + return fmt::format_to(ctx.out(), "({})", ind.ind); } }; From 9b1ce11a030290befc561cf36c46bacf247929a2 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Thu, 24 Sep 2026 18:27:33 +0100 Subject: [PATCH 06/44] Prefer const --- include/bout/region.hxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/bout/region.hxx b/include/bout/region.hxx index a4776cd3fe..ee0efce1b6 100644 --- a/include/bout/region.hxx +++ b/include/bout/region.hxx @@ -385,8 +385,8 @@ struct fmt::formatter> { // Parses format specifications: ['c' | 'i' | 's'] or empty {} constexpr auto parse(format_parse_context& ctx) { - auto it = ctx.begin(); - auto end = ctx.end(); + const auto* it = ctx.begin(); + const auto* end = ctx.end(); if (it != end && (*it == 'c' || *it == 'i' || *it == 's')) { presentation = (*it == 's') ? 'c' : *it; From 4f5987c45995fdcee3125d85ce4b52a14a570726 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Thu, 24 Sep 2026 18:30:54 +0100 Subject: [PATCH 07/44] Directly include fmt/base.h header (Clang-Tidy) --- include/bout/region.hxx | 1 + 1 file changed, 1 insertion(+) diff --git a/include/bout/region.hxx b/include/bout/region.hxx index ee0efce1b6..b91128450d 100644 --- a/include/bout/region.hxx +++ b/include/bout/region.hxx @@ -56,6 +56,7 @@ #include "bout/build_config.hxx" #include "bout/build_defines.hxx" #include "bout/openmpwrap.hxx" // IWYU pragma: keep +#include "fmt/base.h" #include class BoutMask; From d7b458eff58c050e8998f04a6f00f050a531978b Mon Sep 17 00:00:00 2001 From: tomc271 Date: Thu, 24 Sep 2026 18:32:07 +0100 Subject: [PATCH 08/44] Redundant 'typename' keyword (Clang-Tidy) --- include/bout/region.hxx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/include/bout/region.hxx b/include/bout/region.hxx index b91128450d..4874e393b4 100644 --- a/include/bout/region.hxx +++ b/include/bout/region.hxx @@ -523,9 +523,9 @@ public: using value_type = T; using reference = value_type&; using const_reference = const value_type&; - using size_type = typename RegionIndices::size_type; - using iterator = typename RegionIndices::iterator; - using const_iterator = typename RegionIndices::const_iterator; + using size_type = RegionIndices::size_type; + using iterator = RegionIndices::iterator; + using const_iterator = RegionIndices::const_iterator; // NOTE:: // Probably want to require a mesh in constructor, both to know nx/ny/nz @@ -599,12 +599,12 @@ public: /// /// Note that if the indices are altered using these iterators, the /// blocks may become out of sync and will need to manually updated - typename RegionIndices::iterator begin() { return std::begin(indices); }; - typename RegionIndices::const_iterator begin() const { return std::begin(indices); }; - typename RegionIndices::const_iterator cbegin() const { return indices.cbegin(); }; - typename RegionIndices::iterator end() { return std::end(indices); }; - typename RegionIndices::const_iterator end() const { return std::end(indices); }; - typename RegionIndices::const_iterator cend() const { return indices.cend(); }; + RegionIndices::iterator begin() { return std::begin(indices); }; + RegionIndices::const_iterator begin() const { return std::begin(indices); }; + RegionIndices::const_iterator cbegin() const { return indices.cbegin(); }; + RegionIndices::iterator end() { return std::end(indices); }; + RegionIndices::const_iterator end() const { return std::end(indices); }; + RegionIndices::const_iterator cend() const { return indices.cend(); }; const ContiguousBlocks& getBlocks() const { return blocks; }; const RegionIndices& getIndices() const { return indices; }; From 5a6048b3650e3333b114d71881a392190c3f7663 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Thu, 24 Sep 2026 18:33:20 +0100 Subject: [PATCH 09/44] Clang-Tidy: add parentheses to explicitly specify the order of operations ('*' has higher precedence than '+'). --- include/bout/region.hxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/bout/region.hxx b/include/bout/region.hxx index 4874e393b4..d274697df0 100644 --- a/include/bout/region.hxx +++ b/include/bout/region.hxx @@ -793,7 +793,7 @@ public: for (unsigned int i = 0; i < newInd.size(); i++) { const int index = newInd[i].ind; const int whichBlock = index / period; - newInd[i].ind = ((index + shift) % period) + period * whichBlock; + newInd[i].ind = ((index + shift) % period) + (period * whichBlock); }; setIndices(newInd); @@ -891,7 +891,7 @@ private: int ind = -1; while (!done) { ind++; - region[ind].ind = (x * ny + y) * nz + z; + region[ind].ind = ((x * ny + y) * nz) + z; if (x == xend && y == yend && z == zend) { done = true; } From 753fd1f9f5d595893d8385cca0cb75cce4b35fc0 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Thu, 24 Sep 2026 18:34:08 +0100 Subject: [PATCH 10/44] Clang-Tidy: Redundant parentheses around expression --- include/bout/region.hxx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/bout/region.hxx b/include/bout/region.hxx index d274697df0..b0f86300a6 100644 --- a/include/bout/region.hxx +++ b/include/bout/region.hxx @@ -312,7 +312,7 @@ struct SpecificInd { SpecificInd zm(int dz = 1) const { dz = dz <= nz ? dz : dz % nz; //Fix in case dz > nz, if not force it to be in range ASSERT3(dz >= 0); - return {(ind) % nz < dz ? ind + nz - dz : ind - dz, ny, nz}; + return {ind % nz < dz ? ind + nz - dz : ind - dz, ny, nz}; } /// Automatically select zm or zp depending on sign SpecificInd zpm(int dz) const { return dz > 0 ? zp(dz) : zm(-dz); } @@ -823,12 +823,12 @@ public: auto minMaxSize = std::minmax_element(std::begin(blockSizes), std::end(blockSizes)); // Note have to derefence to get actual value - result.minBlockSize = *(minMaxSize.first); + result.minBlockSize = *minMaxSize.first; result.numMinBlocks = static_cast( std::count(std::begin(blockSizes), std::end(blockSizes), result.minBlockSize)); // Note have to derefence to get actual value - result.maxBlockSize = *(minMaxSize.second); + result.maxBlockSize = *minMaxSize.second; result.numMaxBlocks = static_cast( std::count(std::begin(blockSizes), std::end(blockSizes), result.maxBlockSize)); @@ -928,7 +928,7 @@ private: if (index >= npoints) { break; } - if ((indices[index].ind - indices[index - 1].ind) == 1) { + if (indices[index].ind - indices[index - 1].ind == 1) { count++; } else { // Reached the end of this block so break break; From 05cc34fc619c4a5177826bf0fef0981fc766de94 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Thu, 24 Sep 2026 18:36:02 +0100 Subject: [PATCH 11/44] Clang-Tidy: add parentheses to explicitly specify the order of operations ('*' has higher precedence than '+'). --- include/bout/region.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/bout/region.hxx b/include/bout/region.hxx index b0f86300a6..f8294df744 100644 --- a/include/bout/region.hxx +++ b/include/bout/region.hxx @@ -891,7 +891,7 @@ private: int ind = -1; while (!done) { ind++; - region[ind].ind = ((x * ny + y) * nz) + z; + region[ind].ind = (((x * ny) + y) * nz) + z; if (x == xend && y == yend && z == zend) { done = true; } From 40ad17976ab1eae9906e36aad96a73e337e33b25 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Thu, 24 Sep 2026 18:36:11 +0100 Subject: [PATCH 12/44] Clang-Tidy: Function 'createRegionIndices' has inline specifier but is implicitly inlined --- include/bout/region.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/bout/region.hxx b/include/bout/region.hxx index f8294df744..fdd1a3f848 100644 --- a/include/bout/region.hxx +++ b/include/bout/region.hxx @@ -863,7 +863,7 @@ private: /// Helper function to create a RegionIndices, given the start and end /// points in x, y, z, and the total y, z lengths - inline RegionIndices createRegionIndices(int xstart, int xend, int ystart, int yend, + RegionIndices createRegionIndices(int xstart, int xend, int ystart, int yend, int zstart, int zend, int ny, int nz) { if ((xend + 1 <= xstart) || (yend + 1 <= ystart) || (zend + 1 <= zstart)) { From f15068e940e4558c99bcaf4f70e440119edcfe3e Mon Sep 17 00:00:00 2001 From: tomc271 Date: Thu, 24 Sep 2026 20:53:29 +0100 Subject: [PATCH 13/44] Add custom formatter for DirectionTypes so no need to call `toString()` explicitly. --- include/bout/field.hxx | 19 +++++++++---------- include/bout/output_bout_types.hxx | 27 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/include/bout/field.hxx b/include/bout/field.hxx index 76eb2c2e87..c19fc03365 100644 --- a/include/bout/field.hxx +++ b/include/bout/field.hxx @@ -42,7 +42,6 @@ class Field; #include "bout/field_data.hxx" #include "bout/region.hxx" #include "bout/traits.hxx" -#include "bout/utils.hxx" #include #include @@ -164,8 +163,8 @@ inline bool areFieldsCompatible(const Field& field1, const Field& field2) { if ((field1).getLocation() != (field2).getLocation()) { \ throw BoutException("Error in {:s}:{:d}\nFields at different position:" \ "`{:s}` at {:s}, `{:s}` at {:s}", \ - __FILE__, __LINE__, #field1, (field1).getLocation(), \ - #field2, (field2).getLocation()); \ + __FILE__, __LINE__, #field1, (field1).getLocation(), \ + #field2, (field2).getLocation()); \ } \ if ((field1).getCoordinates() != (field2).getCoordinates()) { \ throw BoutException("Error in {:s}:{:d}\nFields have different coordinates:" \ @@ -184,16 +183,16 @@ inline bool areFieldsCompatible(const Field& field1, const Field& field2) { if (!areDirectionsCompatible((field1).getDirections(), (field2).getDirections())) { \ throw BoutException("Error in {:s}:{:d}\nFields at different directions:" \ "`{:s}` at {:s}, `{:s}` at {:s}", \ - __FILE__, __LINE__, #field1, toString((field1).getDirections()), \ - #field2, toString((field2).getDirections())); \ + __FILE__, __LINE__, #field1, (field1).getDirections(), \ + #field2, (field2).getDirections()); \ } #define ASSERT1_EXPR_COMPATIBLE(expr1, expr2) \ if ((expr1).getLocation() != (expr2).getLocation()) { \ throw BoutException("Error in {:s}:{:d}\nFields at different position:" \ "`{:s}` at {:s}, `{:s}` at {:s}", \ - __FILE__, __LINE__, #expr1, (expr1).getLocation(), \ - #expr2, (expr2).getLocation()); \ + __FILE__, __LINE__, #expr1, (expr1).getLocation(), \ + #expr2, (expr2).getLocation()); \ } \ if ((expr1).getMesh() != (expr2).getMesh()) { \ throw BoutException("Error in {:s}:{:d}\nFields are on different Meshes:" \ @@ -205,8 +204,8 @@ inline bool areFieldsCompatible(const Field& field1, const Field& field2) { if (!areDirectionsCompatible((expr1).getDirections(), (expr2).getDirections())) { \ throw BoutException("Error in {:s}:{:d}\nFields at different directions:" \ "`{:s}` at {:s}, `{:s}` at {:s}", \ - __FILE__, __LINE__, #expr1, toString((expr1).getDirections()), \ - #expr2, toString((expr2).getDirections())); \ + __FILE__, __LINE__, #expr1, (expr1).getDirections(), \ + #expr2, (expr2).getDirections()); \ } #else @@ -324,7 +323,7 @@ inline void checkPositive(const T& f, const std::string& name = "field", BOUT_FOR_SERIAL(i, f.getRegion(rgn)) { if (f[i] <= 0.) { throw BoutException("{:s} ({:s} {:s}) is {:e} (not positive) at {:s}", name, - f.getLocation(), toString(f.getDirections()), f[i], i); + f.getLocation(), f.getDirections(), f[i], i); } } } diff --git a/include/bout/output_bout_types.hxx b/include/bout/output_bout_types.hxx index b9ff56dd33..d45122b61b 100644 --- a/include/bout/output_bout_types.hxx +++ b/include/bout/output_bout_types.hxx @@ -10,6 +10,7 @@ #include "bout/output.hxx" // IWYU pragma: keep #include "bout/region.hxx" #include "bout/traits.hxx" +#include "bout/utils.hxx" #include #include @@ -241,4 +242,30 @@ public: } }; +/// Formatter for Fields +template <> +struct fmt::formatter { + // Parses format specifications: accepts 's' or empty {} + constexpr auto parse(format_parse_context& ctx) { + auto it = ctx.begin(); + auto end = ctx.end(); + + if (it != end && *it == 's') { + ++it; + } + + if (it != end && *it != '}') { + throw format_error("invalid format"); + } + + return it; + } + + template + auto format(const DirectionTypes& dir, FormatContext& ctx) const { + // Uses existing toString(dir) function from utils.hxx + return fmt::format_to(ctx.out(), "{}", toString(dir)); + } +}; + #endif // OUTPUT_BOUT_TYPES_H From 8055c187f4cb48136867f885fa8c62fb69c0fce6 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Thu, 24 Sep 2026 21:20:17 +0100 Subject: [PATCH 14/44] Restore #include utils.hxx to field.hxx (as it's needed for other functions, such as ::SQ). --- include/bout/field.hxx | 1 + 1 file changed, 1 insertion(+) diff --git a/include/bout/field.hxx b/include/bout/field.hxx index c19fc03365..4b49d411c0 100644 --- a/include/bout/field.hxx +++ b/include/bout/field.hxx @@ -42,6 +42,7 @@ class Field; #include "bout/field_data.hxx" #include "bout/region.hxx" #include "bout/traits.hxx" +#include "bout/utils.hxx" #include #include From 9a72d7f13ce925dec0ea1ce3c5328fbf67b7d5db Mon Sep 17 00:00:00 2001 From: tomc271 Date: Thu, 24 Sep 2026 21:25:38 +0100 Subject: [PATCH 15/44] Move `std::string toString(const DirectionTypes& dir)` from utils.hxx to bout_types.cxx and add `toString` and `format_as()` to bout_types.hxx for the `const DirectionTypes&` type --- include/bout/bout_types.hxx | 5 +++++ include/bout/output_bout_types.hxx | 26 -------------------------- include/bout/utils.hxx | 4 ---- src/sys/bout_types.cxx | 4 ++++ 4 files changed, 9 insertions(+), 30 deletions(-) diff --git a/include/bout/bout_types.hxx b/include/bout/bout_types.hxx index 3e8876430d..66b7fe7284 100644 --- a/include/bout/bout_types.hxx +++ b/include/bout/bout_types.hxx @@ -28,6 +28,8 @@ #include #include +struct DirectionTypes; + /// Size of real numbers using BoutReal = double; @@ -93,6 +95,9 @@ enum class DIRECTION { X, Y, Z, YAligned, YOrthogonal }; std::string toString(DIRECTION direction); inline std::string format_as(DIRECTION dir) { return toString(dir); } +std::string toString(const DirectionTypes& dir); +inline std::string format_as(const DirectionTypes& dir) { return toString(dir); } + /// Identify kind of a field's y-direction /// - Standard is the default for the Mesh/Coordinates/ParallelTransform /// - Aligned indicates that the field has been transformed to field-aligned diff --git a/include/bout/output_bout_types.hxx b/include/bout/output_bout_types.hxx index d45122b61b..d8f483690d 100644 --- a/include/bout/output_bout_types.hxx +++ b/include/bout/output_bout_types.hxx @@ -242,30 +242,4 @@ public: } }; -/// Formatter for Fields -template <> -struct fmt::formatter { - // Parses format specifications: accepts 's' or empty {} - constexpr auto parse(format_parse_context& ctx) { - auto it = ctx.begin(); - auto end = ctx.end(); - - if (it != end && *it == 's') { - ++it; - } - - if (it != end && *it != '}') { - throw format_error("invalid format"); - } - - return it; - } - - template - auto format(const DirectionTypes& dir, FormatContext& ctx) const { - // Uses existing toString(dir) function from utils.hxx - return fmt::format_to(ctx.out(), "{}", toString(dir)); - } -}; - #endif // OUTPUT_BOUT_TYPES_H diff --git a/include/bout/utils.hxx b/include/bout/utils.hxx index 0679324723..420ded10d6 100644 --- a/include/bout/utils.hxx +++ b/include/bout/utils.hxx @@ -558,10 +558,6 @@ inline std::string toString(const bool& val) { return "false"; } -inline std::string toString(const DirectionTypes& dir) { - return "{" + toString(dir.y) + ", " + toString(dir.z) + "}"; -} - /// Convert a time stamp to a string /// This uses std::localtime and std::put_time std::string toString(const time_t& time); diff --git a/src/sys/bout_types.cxx b/src/sys/bout_types.cxx index b27fab7ae7..cbcf19e996 100644 --- a/src/sys/bout_types.cxx +++ b/src/sys/bout_types.cxx @@ -73,6 +73,10 @@ std::string toString(DIRECTION direction) { return safeAt(DIRECTIONtoString, direction); } +std::string toString(const DirectionTypes& dir) { + return "{" + toString(dir.y) + ", " + toString(dir.z) + "}"; +} + void swap(DirectionTypes& first, DirectionTypes& second) { DirectionTypes temp = first; first = second; From 762b61821aa027a5b59f80cd479793f27b947839 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Fri, 25 Sep 2026 11:47:18 +0100 Subject: [PATCH 16/44] Add `format_as()` wrapper for BoundaryParType to call its `toString()` method. --- include/bout/mesh.hxx | 2 ++ src/mesh/impls/bout/boutmesh.cxx | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/include/bout/mesh.hxx b/include/bout/mesh.hxx index dd7ccbfc0b..eeab15e228 100644 --- a/include/bout/mesh.hxx +++ b/include/bout/mesh.hxx @@ -88,6 +88,8 @@ public: BOUT_ENUM_CLASS(BoundaryParType, all, xin, xout, fwd, bwd, xin_fwd, xout_fwd, xin_bwd, xout_bwd, SIZE); +inline auto format_as(BoundaryParType type) { return toString(type); } + template using RegisterMesh = MeshFactory::RegisterInFactory; diff --git a/src/mesh/impls/bout/boutmesh.cxx b/src/mesh/impls/bout/boutmesh.cxx index e1cb27cd73..80752a9f4a 100644 --- a/src/mesh/impls/bout/boutmesh.cxx +++ b/src/mesh/impls/bout/boutmesh.cxx @@ -3276,7 +3276,7 @@ void BoutMesh::addBoundaryPar(std::shared_ptr bndry, par_boundary[static_cast(BoundaryParType::bwd)].push_back(bndry); break; default: - throw BoutException("Unexpected type of boundary {}", toString(type)); + throw BoutException("Unexpected type of boundary {}", type); } par_boundary[static_cast(type)].push_back(bndry); par_boundary[static_cast(BoundaryParType::all)].push_back(bndry); From 2fab33936440dc4524cc29c748cb51fe2da6bf7f Mon Sep 17 00:00:00 2001 From: tomc271 Date: Sat, 26 Sep 2026 20:13:38 +0100 Subject: [PATCH 17/44] Remove call to `toString()` in BoutException arguments Not required as handled by fmt::formatter for SpecificInd. --- include/bout/operatorstencil.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/bout/operatorstencil.hxx b/include/bout/operatorstencil.hxx index 1135a79fc7..e58ecea15c 100644 --- a/include/bout/operatorstencil.hxx +++ b/include/bout/operatorstencil.hxx @@ -146,7 +146,7 @@ public: std::find_if(std::begin(stencils), std::end(stencils), [&i](const auto& stencil) -> bool { return stencil.test(i); }); if (result == std::end(stencils)) { - throw BoutException("No stencil was specified for element " + toString(i)); + throw BoutException("No stencil was specified for element {}", i); } return result->part; } From aca326bdb3aa9b037b7fa1e054cb474c92aae2da Mon Sep 17 00:00:00 2001 From: tomc271 Date: Sat, 26 Sep 2026 20:03:47 +0100 Subject: [PATCH 18/44] Redundant 'typename' keyword --- include/bout/operatorstencil.hxx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/bout/operatorstencil.hxx b/include/bout/operatorstencil.hxx index e58ecea15c..39346906ea 100644 --- a/include/bout/operatorstencil.hxx +++ b/include/bout/operatorstencil.hxx @@ -179,10 +179,10 @@ public: } /// Iterators for the underlying vector data type - using iterator = typename std::vector::iterator; - using const_iterator = typename std::vector::const_iterator; - using reverse_iterator = typename std::vector::reverse_iterator; - using const_reverse_iterator = typename std::vector::const_reverse_iterator; + using iterator = std::vector::iterator; + using const_iterator = std::vector::const_iterator; + using reverse_iterator = std::vector::reverse_iterator; + using const_reverse_iterator = std::vector::const_reverse_iterator; iterator begin() { return std::begin(stencils); } const_iterator begin() const { return std::begin(stencils); } From 8e2de4fb2941b43d5334c0d08b500c9b44945399 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Sat, 26 Sep 2026 20:04:15 +0100 Subject: [PATCH 19/44] Clang-Tidy: No header providing "BoutException" is directly included --- include/bout/operatorstencil.hxx | 1 + 1 file changed, 1 insertion(+) diff --git a/include/bout/operatorstencil.hxx b/include/bout/operatorstencil.hxx index 39346906ea..6ea9f72fe6 100644 --- a/include/bout/operatorstencil.hxx +++ b/include/bout/operatorstencil.hxx @@ -36,6 +36,7 @@ #include #include +#include "bout/boutexception.hxx" #include #include From 05383205f403f5c71df9b677a471db7424c8b46d Mon Sep 17 00:00:00 2001 From: tomc271 Date: Sat, 26 Sep 2026 20:05:05 +0100 Subject: [PATCH 20/44] Clang-Tidy: No header providing "std::set" is directly included Clang-Tidy: No header providing "UNUSED" is directly included --- include/bout/operatorstencil.hxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/bout/operatorstencil.hxx b/include/bout/operatorstencil.hxx index 6ea9f72fe6..3427b2c9e0 100644 --- a/include/bout/operatorstencil.hxx +++ b/include/bout/operatorstencil.hxx @@ -33,10 +33,12 @@ #include #include #include +#include #include #include #include "bout/boutexception.hxx" +#include "bout/unused.hxx" #include #include From c5bd56425198ec2a03937ce55cc9041960032798 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Sat, 26 Sep 2026 20:05:43 +0100 Subject: [PATCH 21/44] Remove redundant `const` qualifier --- include/bout/operatorstencil.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/bout/operatorstencil.hxx b/include/bout/operatorstencil.hxx index 3427b2c9e0..1cc2cabfd8 100644 --- a/include/bout/operatorstencil.hxx +++ b/include/bout/operatorstencil.hxx @@ -166,7 +166,7 @@ public: /// Returns a list of indices for which the stencils contain the /// argument - const std::vector getIndicesWithStencilIncluding(const T& i) const { + std::vector getIndicesWithStencilIncluding(const T& i) const { std::vector indices; int count = 0; for (const auto& item : stencils) { From ce45224be493d7a56a721de2ba96fdf85df096fa Mon Sep 17 00:00:00 2001 From: tomc271 Date: Sat, 26 Sep 2026 20:07:19 +0100 Subject: [PATCH 22/44] Clang-Tidy: Pointer parameter 'localmesh' can be pointer to const --- include/bout/operatorstencil.hxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/bout/operatorstencil.hxx b/include/bout/operatorstencil.hxx index 1cc2cabfd8..5a02f46953 100644 --- a/include/bout/operatorstencil.hxx +++ b/include/bout/operatorstencil.hxx @@ -222,7 +222,7 @@ private: /// depend on all of their neighbours to a depth of one, including /// corners. template -OperatorStencil squareStencil(Mesh* localmesh) { +OperatorStencil squareStencil(const Mesh* localmesh) { OperatorStencil stencil; IndexOffset zero; std::set> offsets = { @@ -270,7 +270,7 @@ OperatorStencil squareStencil(Mesh* localmesh) { /// depend on all of their neighbours to a depth of one, excluding /// corners. template -OperatorStencil starStencil(Mesh* localmesh) { +OperatorStencil starStencil(const Mesh* localmesh) { OperatorStencil stencil; IndexOffset zero; std::set> offsets = { From b3719a6753e1464fa05ecefdb755c381df18997f Mon Sep 17 00:00:00 2001 From: tomc271 Date: Sat, 26 Sep 2026 20:10:55 +0100 Subject: [PATCH 23/44] Parameter can be made const --- include/bout/operatorstencil.hxx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/include/bout/operatorstencil.hxx b/include/bout/operatorstencil.hxx index 5a02f46953..5bedb94dbd 100644 --- a/include/bout/operatorstencil.hxx +++ b/include/bout/operatorstencil.hxx @@ -51,12 +51,12 @@ struct IndexOffset { "IndexOffset only works with SpecificInd types"); int dx = 0, dy = 0, dz = 0; - IndexOffset xp(int delta_x = 1) const { return {dx + delta_x, dy, dz}; } - IndexOffset xm(int delta_x = 1) const { return xp(-delta_x); } - IndexOffset yp(int delta_y = 1) const { return {dx, dy + delta_y, dz}; } - IndexOffset ym(int delta_y = 1) const { return yp(-delta_y); } - IndexOffset zp(int delta_z = 1) const { return {dx, dy, dz + delta_z}; } - IndexOffset zm(int delta_z = 1) const { return zp(-delta_z); } + IndexOffset xp(const int delta_x = 1) const { return {dx + delta_x, dy, dz}; } + IndexOffset xm(const int delta_x = 1) const { return xp(-delta_x); } + IndexOffset yp(const int delta_y = 1) const { return {dx, dy + delta_y, dz}; } + IndexOffset ym(const int delta_y = 1) const { return yp(-delta_y); } + IndexOffset zp(const int delta_z = 1) const { return {dx, dy, dz + delta_z}; } + IndexOffset zm(const int delta_z = 1) const { return zp(-delta_z); } IndexOffset& operator+=(const IndexOffset& n) { dx += n.dx; @@ -155,7 +155,7 @@ public: } /// Get the number of elements in the ith stencil-part - int getStencilSize(int i) const { return getStencilPart(i).size(); } + int getStencilSize(const int i) const { return getStencilPart(i).size(); } /// Get the number of elements in the stencil part to be used at /// this index. From 75a6a28769a7b4efc832885115f16762dffad60f Mon Sep 17 00:00:00 2001 From: tomc271 Date: Mon, 28 Sep 2026 13:18:29 +0100 Subject: [PATCH 24/44] Apply clang-format --- include/bout/field.hxx | 90 ++++++++++++++++++++--------------------- include/bout/region.hxx | 4 +- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/include/bout/field.hxx b/include/bout/field.hxx index 4b49d411c0..2b3e3ed89c 100644 --- a/include/bout/field.hxx +++ b/include/bout/field.hxx @@ -160,53 +160,53 @@ inline bool areFieldsCompatible(const Field& field1, const Field& field2) { } #if CHECKLEVEL >= 1 -#define ASSERT1_FIELDS_COMPATIBLE(field1, field2) \ - if ((field1).getLocation() != (field2).getLocation()) { \ - throw BoutException("Error in {:s}:{:d}\nFields at different position:" \ - "`{:s}` at {:s}, `{:s}` at {:s}", \ - __FILE__, __LINE__, #field1, (field1).getLocation(), \ - #field2, (field2).getLocation()); \ - } \ - if ((field1).getCoordinates() != (field2).getCoordinates()) { \ - throw BoutException("Error in {:s}:{:d}\nFields have different coordinates:" \ - "`{:s}` at {:p}, `{:s}` at {:p}", \ - __FILE__, __LINE__, #field1, \ - static_cast((field1).getCoordinates()), #field2, \ - static_cast((field2).getCoordinates())); \ - } \ - if ((field1).getMesh() != (field2).getMesh()) { \ - throw BoutException("Error in {:s}:{:d}\nFields are on different Meshes:" \ - "`{:s}` at {:p}, `{:s}` at {:p}", \ - __FILE__, __LINE__, #field1, \ - static_cast((field1).getMesh()), #field2, \ - static_cast((field2).getMesh())); \ - } \ - if (!areDirectionsCompatible((field1).getDirections(), (field2).getDirections())) { \ - throw BoutException("Error in {:s}:{:d}\nFields at different directions:" \ - "`{:s}` at {:s}, `{:s}` at {:s}", \ - __FILE__, __LINE__, #field1, (field1).getDirections(), \ - #field2, (field2).getDirections()); \ +#define ASSERT1_FIELDS_COMPATIBLE(field1, field2) \ + if ((field1).getLocation() != (field2).getLocation()) { \ + throw BoutException("Error in {:s}:{:d}\nFields at different position:" \ + "`{:s}` at {:s}, `{:s}` at {:s}", \ + __FILE__, __LINE__, #field1, (field1).getLocation(), #field2, \ + (field2).getLocation()); \ + } \ + if ((field1).getCoordinates() != (field2).getCoordinates()) { \ + throw BoutException("Error in {:s}:{:d}\nFields have different coordinates:" \ + "`{:s}` at {:p}, `{:s}` at {:p}", \ + __FILE__, __LINE__, #field1, \ + static_cast((field1).getCoordinates()), #field2, \ + static_cast((field2).getCoordinates())); \ + } \ + if ((field1).getMesh() != (field2).getMesh()) { \ + throw BoutException("Error in {:s}:{:d}\nFields are on different Meshes:" \ + "`{:s}` at {:p}, `{:s}` at {:p}", \ + __FILE__, __LINE__, #field1, \ + static_cast((field1).getMesh()), #field2, \ + static_cast((field2).getMesh())); \ + } \ + if (!areDirectionsCompatible((field1).getDirections(), (field2).getDirections())) { \ + throw BoutException("Error in {:s}:{:d}\nFields at different directions:" \ + "`{:s}` at {:s}, `{:s}` at {:s}", \ + __FILE__, __LINE__, #field1, (field1).getDirections(), #field2, \ + (field2).getDirections()); \ } -#define ASSERT1_EXPR_COMPATIBLE(expr1, expr2) \ - if ((expr1).getLocation() != (expr2).getLocation()) { \ - throw BoutException("Error in {:s}:{:d}\nFields at different position:" \ - "`{:s}` at {:s}, `{:s}` at {:s}", \ - __FILE__, __LINE__, #expr1, (expr1).getLocation(), \ - #expr2, (expr2).getLocation()); \ - } \ - if ((expr1).getMesh() != (expr2).getMesh()) { \ - throw BoutException("Error in {:s}:{:d}\nFields are on different Meshes:" \ - "`{:s}` at {:p}, `{:s}` at {:p}", \ - __FILE__, __LINE__, #expr1, \ - static_cast((expr1).getMesh()), #expr2, \ - static_cast((expr2).getMesh())); \ - } \ - if (!areDirectionsCompatible((expr1).getDirections(), (expr2).getDirections())) { \ - throw BoutException("Error in {:s}:{:d}\nFields at different directions:" \ - "`{:s}` at {:s}, `{:s}` at {:s}", \ - __FILE__, __LINE__, #expr1, (expr1).getDirections(), \ - #expr2, (expr2).getDirections()); \ +#define ASSERT1_EXPR_COMPATIBLE(expr1, expr2) \ + if ((expr1).getLocation() != (expr2).getLocation()) { \ + throw BoutException("Error in {:s}:{:d}\nFields at different position:" \ + "`{:s}` at {:s}, `{:s}` at {:s}", \ + __FILE__, __LINE__, #expr1, (expr1).getLocation(), #expr2, \ + (expr2).getLocation()); \ + } \ + if ((expr1).getMesh() != (expr2).getMesh()) { \ + throw BoutException("Error in {:s}:{:d}\nFields are on different Meshes:" \ + "`{:s}` at {:p}, `{:s}` at {:p}", \ + __FILE__, __LINE__, #expr1, \ + static_cast((expr1).getMesh()), #expr2, \ + static_cast((expr2).getMesh())); \ + } \ + if (!areDirectionsCompatible((expr1).getDirections(), (expr2).getDirections())) { \ + throw BoutException("Error in {:s}:{:d}\nFields at different directions:" \ + "`{:s}` at {:s}, `{:s}` at {:s}", \ + __FILE__, __LINE__, #expr1, (expr1).getDirections(), #expr2, \ + (expr2).getDirections()); \ } #else diff --git a/include/bout/region.hxx b/include/bout/region.hxx index fdd1a3f848..7c8ffe86fa 100644 --- a/include/bout/region.hxx +++ b/include/bout/region.hxx @@ -49,6 +49,7 @@ #include #include +#include "fmt/base.h" #include "bout/array.hxx" #include "bout/assert.hxx" #include "bout/bout_types.hxx" @@ -56,7 +57,6 @@ #include "bout/build_config.hxx" #include "bout/build_defines.hxx" #include "bout/openmpwrap.hxx" // IWYU pragma: keep -#include "fmt/base.h" #include class BoutMask; @@ -864,7 +864,7 @@ private: /// Helper function to create a RegionIndices, given the start and end /// points in x, y, z, and the total y, z lengths RegionIndices createRegionIndices(int xstart, int xend, int ystart, int yend, - int zstart, int zend, int ny, int nz) { + int zstart, int zend, int ny, int nz) { if ((xend + 1 <= xstart) || (yend + 1 <= ystart) || (zend + 1 <= zstart)) { // Empty region From badede6b9fc048af0d94c4566654e447ba6cdbd0 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Mon, 28 Sep 2026 15:10:49 +0100 Subject: [PATCH 25/44] Remove more to `toString()` calls in BoutException arguments Not required as handled by fmt::formatter for SpecificInd. --- include/bout/deriv_store.hxx | 22 +++++++++++----------- include/bout/interpolation.hxx | 2 +- src/mesh/data/gridfromfile.cxx | 2 +- src/solver/solver.cxx | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/include/bout/deriv_store.hxx b/include/bout/deriv_store.hxx index 7c08802cae..36412ec531 100644 --- a/include/bout/deriv_store.hxx +++ b/include/bout/deriv_store.hxx @@ -135,7 +135,7 @@ struct DerivativeStore { if (standard.count(key) != 0) { throw BoutException("Trying to override standard derivative : " "direction {:s}, stagger {:s}, key {:s}", - toString(direction), toString(stagger), methodName); + direction, stagger, methodName); } standard[key] = func; break; @@ -143,7 +143,7 @@ struct DerivativeStore { if (standardSecond.count(key) != 0) { throw BoutException("Trying to override standardSecond derivative : " "direction {:s}, stagger {:s}, key {:s}", - toString(direction), toString(stagger), methodName); + direction, stagger, methodName); } standardSecond[key] = func; break; @@ -151,14 +151,14 @@ struct DerivativeStore { if (standardFourth.count(key) != 0) { throw BoutException("Trying to override standardFourth derivative : " "direction {:s}, stagger {:s}, key {:s}", - toString(direction), toString(stagger), methodName); + direction, stagger, methodName); } standardFourth[key] = func; break; default: throw BoutException("Invalid function signature in registerDerivative : Function " "signature 'standard' but derivative type {:s} passed", - toString(derivType)); + derivType); }; // Register this method name in lookup of known methods @@ -177,7 +177,7 @@ struct DerivativeStore { if (upwind.count(key) != 0) { throw BoutException("Trying to override upwind derivative : " "direction {:s}, stagger {:s}, key {:s}", - toString(direction), toString(stagger), methodName); + direction, stagger, methodName); } upwind[key] = func; break; @@ -185,14 +185,14 @@ struct DerivativeStore { if (flux.count(key) != 0) { throw BoutException("Trying to override flux derivative : " "direction {:s}, stagger {:s}, key {:s}", - toString(direction), toString(stagger), methodName); + direction, stagger, methodName); } flux[key] = func; break; default: throw BoutException("Invalid function signature in registerDerivative : Function " "signature 'upwind/flux' but derivative type {:s} passed", - toString(derivType)); + derivType); }; // Register this method name in lookup of known methods @@ -239,7 +239,7 @@ struct DerivativeStore { } else { throw BoutException("getStandardDerivative only works for derivType in {{Standard, " "StandardSecond, StandardFourth}} but receieved {:s}", - toString(derivType)); + derivType); }; const auto resultOfFind = theMap->find(key); @@ -249,7 +249,7 @@ struct DerivativeStore { throw BoutException("Couldn't find requested method {:s} in map for standard " "derivative of type {:s}.", - getMethodName(realName, direction, stagger), toString(derivType)); + getMethodName(realName, direction, stagger), derivType); }; standardFunc getStandard2ndDerivative(std::string name, DIRECTION direction, @@ -282,7 +282,7 @@ struct DerivativeStore { throw BoutException( "getFlowDerivative only works for derivType in {{Upwind, Flux}} " "but received {:s}", - toString(derivType)); + derivType); }; const auto resultOfFind = theMap->find(key); @@ -292,7 +292,7 @@ struct DerivativeStore { throw BoutException( "Couldn't find requested method {:s} in map for standard flow of type {:s}.", - getMethodName(realName, direction, stagger), toString(derivType)); + getMethodName(realName, direction, stagger), derivType); } upwindFunc getUpwindDerivative(std::string name, DIRECTION direction, diff --git a/include/bout/interpolation.hxx b/include/bout/interpolation.hxx index 40ff603712..bf9b1de466 100644 --- a/include/bout/interpolation.hxx +++ b/include/bout/interpolation.hxx @@ -195,7 +195,7 @@ interp_to(const T& var, CELL_LOC loc, const std::string& region = "RGN_ALL") { // This should never happen throw BoutException("Unsupported direction of interpolation\n" " - don't know how to interpolate to {:s}", - toString(loc)); + loc); } }; diff --git a/src/mesh/data/gridfromfile.cxx b/src/mesh/data/gridfromfile.cxx index 0fe165a04c..5fbc1d3d5d 100644 --- a/src/mesh/data/gridfromfile.cxx +++ b/src/mesh/data/gridfromfile.cxx @@ -279,7 +279,7 @@ bool GridFile::getField(Mesh* m, T& var, const std::string& name, BoutReal def, if (location != CELL_DEFAULT and var.getLocation() != location) { throw BoutException("Incorrect location of field {:s} in grid file, expected {:s}, " "got {:s}.", - name, toString(location), toString(var.getLocation())); + name, location, var.getLocation()); } if (var.isAllocated()) { diff --git a/src/solver/solver.cxx b/src/solver/solver.cxx index 29b50cd915..fb057c0544 100644 --- a/src/solver/solver.cxx +++ b/src/solver/solver.cxx @@ -1191,8 +1191,8 @@ void Solver::save_derivs(BoutReal* dudata) { if (f.var->getLocation() != (f.F_var)->getLocation()) { throw BoutException(_f("Time derivative at wrong location - Field is at {:s}, " "derivative is at {:s} for field '{:s}'\n"), - toString(f.var->getLocation()), - toString(f.F_var->getLocation()), f.name); + f.var->getLocation(), + f.F_var->getLocation(), f.name); } } From c9db23ad04e07834f67daa0009d4f0ee7add94bb Mon Sep 17 00:00:00 2001 From: tomc271 Date: Tue, 29 Sep 2026 09:34:20 +0100 Subject: [PATCH 26/44] Apply clang-format --- src/solver/solver.cxx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/solver/solver.cxx b/src/solver/solver.cxx index fb057c0544..df0013dd3b 100644 --- a/src/solver/solver.cxx +++ b/src/solver/solver.cxx @@ -1191,8 +1191,7 @@ void Solver::save_derivs(BoutReal* dudata) { if (f.var->getLocation() != (f.F_var)->getLocation()) { throw BoutException(_f("Time derivative at wrong location - Field is at {:s}, " "derivative is at {:s} for field '{:s}'\n"), - f.var->getLocation(), - f.F_var->getLocation(), f.name); + f.var->getLocation(), f.F_var->getLocation(), f.name); } } From 0e90ad552b2aeabcc77c3494e855d635407cbf49 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Tue, 29 Sep 2026 10:27:25 +0100 Subject: [PATCH 27/44] Add `format_as()` wrapper for ConductionMethod type --- include/bout/difops.hxx | 6 ++++++ src/mesh/difops.cxx | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/include/bout/difops.hxx b/include/bout/difops.hxx index 0b3320c1f1..b36df15c7f 100644 --- a/include/bout/difops.hxx +++ b/include/bout/difops.hxx @@ -219,6 +219,12 @@ Field3D Div_par_K_Grad_par(const Field3DParallel& kY, const Field3DParallel& f, /// different results when coefficients or cell sizes vary strongly. BOUT_ENUM_CLASS_NS(bout, ConductionMethod, Original, ProductJK, Harmonic); +namespace bout { +inline std::string format_as(const ConductionMethod& method) { + return toString(method); +} +} + /// Version with energy flow diagnostic /// For FCI fields, `flow_ylow` is currently returned as zero. Field3D diff --git a/src/mesh/difops.cxx b/src/mesh/difops.cxx index 1b4323cb92..e707ec41f3 100644 --- a/src/mesh/difops.cxx +++ b/src/mesh/difops.cxx @@ -544,7 +544,7 @@ Field3D Div_par_K_Grad_par_mod(const Field3DParallel& Kin, const Field3DParallel } throw BoutException( "Unknown method `{}` - choose from `Original`, `ProductJK` or `Harmonic`.", - toString(method)); + method); } /******************************************************************************* * Delp2 From 48363a0d492e776c1df9d4a7b158176be4331315 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Tue, 29 Sep 2026 10:40:19 +0100 Subject: [PATCH 28/44] Make `format_as()` parameters const (ref) --- include/bout/bout_types.hxx | 16 ++++++++-------- include/bout/mesh.hxx | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/include/bout/bout_types.hxx b/include/bout/bout_types.hxx index 66b7fe7284..6a9df66c00 100644 --- a/include/bout/bout_types.hxx +++ b/include/bout/bout_types.hxx @@ -53,7 +53,7 @@ constexpr CELL_LOC CELL_VSHIFT = CELL_LOC::vshift; std::string toString(CELL_LOC location); CELL_LOC CELL_LOCFromString(const std::string& location_string); -inline std::string format_as(CELL_LOC loc) { return toString(loc); } +inline std::string format_as(const CELL_LOC& loc) { return toString(loc); } /// Differential methods. Both central and upwind enum class DIFF_METHOD { deflt, u1, u2, c2, w2, w3, c4, u3, fft, split, s2 }; @@ -70,7 +70,7 @@ constexpr DIFF_METHOD DIFF_SPLIT = DIFF_METHOD::split; constexpr DIFF_METHOD DIFF_S2 = DIFF_METHOD::s2; std::string toString(DIFF_METHOD location); -inline std::string format_as(DIFF_METHOD method) { return toString(method); } +inline std::string format_as(const DIFF_METHOD& method) { return toString(method); } /// Specify grid region for looping enum class REGION { all, nobndry, nox, noy, noz }; @@ -81,7 +81,7 @@ constexpr REGION RGN_NOY = REGION::noy; constexpr REGION RGN_NOZ = REGION::noz; std::string toString(REGION region); -inline std::string format_as(REGION region) { return toString(region); } +inline std::string format_as(const REGION& region) { return toString(region); } /// To identify particular directions (in index space): /// - X, Y, Z are the coordinate directions @@ -93,7 +93,7 @@ inline std::string format_as(REGION region) { return toString(region); } enum class DIRECTION { X, Y, Z, YAligned, YOrthogonal }; std::string toString(DIRECTION direction); -inline std::string format_as(DIRECTION dir) { return toString(dir); } +inline std::string format_as(const DIRECTION& dir) { return toString(dir); } std::string toString(const DirectionTypes& dir); inline std::string format_as(const DirectionTypes& dir) { return toString(dir); } @@ -106,7 +106,7 @@ enum class YDirectionType { Standard, Aligned }; std::string toString(YDirectionType d); YDirectionType YDirectionTypeFromString(const std::string& y_direction_string); -inline std::string format_as(YDirectionType d) { return toString(d); } +inline std::string format_as(const YDirectionType& d) { return toString(d); } /// Identify kind of a field's z-direction /// - Standard is the default @@ -117,7 +117,7 @@ enum class ZDirectionType { Standard, Average }; std::string toString(ZDirectionType d); ZDirectionType ZDirectionTypeFromString(const std::string& z_direction_string); -inline std::string format_as(ZDirectionType d) { return toString(d); } +inline std::string format_as(const ZDirectionType& d) { return toString(d); } /// Container for direction types struct DirectionTypes { @@ -135,13 +135,13 @@ void swap(const DirectionTypes& first, const DirectionTypes& second); enum class STAGGER { None, C2L, L2C }; std::string toString(STAGGER stagger); -inline std::string format_as(STAGGER stagger) { return toString(stagger); } +inline std::string format_as(const STAGGER& stagger) { return toString(stagger); } /// To identify types of derivative method combinations enum class DERIV { Standard, StandardSecond, StandardFourth, Upwind, Flux }; std::string toString(DERIV deriv); -inline std::string format_as(DERIV deriv) { return toString(deriv); } +inline std::string format_as(const DERIV& deriv) { return toString(deriv); } // A small struct that can be used to wrap a specific enum value, giving // it a unique type that can be passed as a valid type to templates and diff --git a/include/bout/mesh.hxx b/include/bout/mesh.hxx index eeab15e228..3d630b5ce3 100644 --- a/include/bout/mesh.hxx +++ b/include/bout/mesh.hxx @@ -88,7 +88,7 @@ public: BOUT_ENUM_CLASS(BoundaryParType, all, xin, xout, fwd, bwd, xin_fwd, xout_fwd, xin_bwd, xout_bwd, SIZE); -inline auto format_as(BoundaryParType type) { return toString(type); } +inline auto format_as(const BoundaryParType& type) { return toString(type); } template using RegisterMesh = MeshFactory::RegisterInFactory; From 7b77b110df37f587161705c1a6548650769f0382 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Tue, 29 Sep 2026 11:41:44 +0100 Subject: [PATCH 29/44] Apply clang-format --- include/bout/difops.hxx | 6 ++---- src/mesh/difops.cxx | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/include/bout/difops.hxx b/include/bout/difops.hxx index b36df15c7f..e20aef2459 100644 --- a/include/bout/difops.hxx +++ b/include/bout/difops.hxx @@ -220,10 +220,8 @@ Field3D Div_par_K_Grad_par(const Field3DParallel& kY, const Field3DParallel& f, BOUT_ENUM_CLASS_NS(bout, ConductionMethod, Original, ProductJK, Harmonic); namespace bout { -inline std::string format_as(const ConductionMethod& method) { - return toString(method); -} -} +inline std::string format_as(const ConductionMethod& method) { return toString(method); } +} // namespace bout /// Version with energy flow diagnostic /// For FCI fields, `flow_ylow` is currently returned as zero. diff --git a/src/mesh/difops.cxx b/src/mesh/difops.cxx index e707ec41f3..30047647ec 100644 --- a/src/mesh/difops.cxx +++ b/src/mesh/difops.cxx @@ -543,8 +543,7 @@ Field3D Div_par_K_Grad_par_mod(const Field3DParallel& Kin, const Field3DParallel return Div_par_K_Grad_par_mod_impl(Kin, fin, flow_ylow, bndry_flux); } throw BoutException( - "Unknown method `{}` - choose from `Original`, `ProductJK` or `Harmonic`.", - method); + "Unknown method `{}` - choose from `Original`, `ProductJK` or `Harmonic`.", method); } /******************************************************************************* * Delp2 From d9b4d5156320f2284c4c5160fcb83273518d9cb4 Mon Sep 17 00:00:00 2001 From: Tom C <58003025+tomc271@users.noreply.github.com> Date: Tue, 29 Sep 2026 13:42:26 +0100 Subject: [PATCH 30/44] Remove unused #include "bout/utils.hxx" Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- include/bout/output_bout_types.hxx | 1 - 1 file changed, 1 deletion(-) diff --git a/include/bout/output_bout_types.hxx b/include/bout/output_bout_types.hxx index d8f483690d..66b7656880 100644 --- a/include/bout/output_bout_types.hxx +++ b/include/bout/output_bout_types.hxx @@ -11,7 +11,6 @@ #include "bout/region.hxx" #include "bout/traits.hxx" #include "bout/utils.hxx" - #include #include #include From 8046c2f4fc8e6aa8f94a1faa1daa436d31cdbc13 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Tue, 29 Sep 2026 14:42:59 +0100 Subject: [PATCH 31/44] Restore comments in `fmt::formatter>` which were accidentally deleted when moving. --- include/bout/region.hxx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/bout/region.hxx b/include/bout/region.hxx index 7c8ffe86fa..723da9ed85 100644 --- a/include/bout/region.hxx +++ b/include/bout/region.hxx @@ -382,6 +382,7 @@ inline std::string toString(const IndPerp& i) { // Custom fmt formatter for SpecificInd template struct fmt::formatter> { + // Presentation format: 'c' - components, 'i' - index, 's' - string. char presentation = 'c'; // Parses format specifications: ['c' | 'i' | 's'] or empty {} @@ -394,15 +395,20 @@ struct fmt::formatter> { ++it; } + // Check if reached the end of the range: if (it != end && *it != '}') { throw format_error("invalid format"); } + // Return an iterator past the end of the parsed range: return it; } + // Formats the point p using the parsed format specification (presentation) + // stored in this formatter. template auto format(const SpecificInd& ind, FormatContext& ctx) const { + // ctx.out() is an output iterator to write to. if (presentation == 'c') { if constexpr (N == IND_TYPE::IND_2D) { return fmt::format_to(ctx.out(), "({}, {})", ind.x(), ind.y()); From 2e41f707aba8f4913ade1fad935009156a913583 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Tue, 29 Sep 2026 14:47:13 +0100 Subject: [PATCH 32/44] Move `toString()` and `format_as()` methods for `DirectionTypes` below its definition so forward declaration is not needed. --- include/bout/bout_types.hxx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/include/bout/bout_types.hxx b/include/bout/bout_types.hxx index 6a9df66c00..41ca37c99d 100644 --- a/include/bout/bout_types.hxx +++ b/include/bout/bout_types.hxx @@ -28,8 +28,6 @@ #include #include -struct DirectionTypes; - /// Size of real numbers using BoutReal = double; @@ -95,9 +93,6 @@ enum class DIRECTION { X, Y, Z, YAligned, YOrthogonal }; std::string toString(DIRECTION direction); inline std::string format_as(const DIRECTION& dir) { return toString(dir); } -std::string toString(const DirectionTypes& dir); -inline std::string format_as(const DirectionTypes& dir) { return toString(dir); } - /// Identify kind of a field's y-direction /// - Standard is the default for the Mesh/Coordinates/ParallelTransform /// - Aligned indicates that the field has been transformed to field-aligned @@ -125,6 +120,9 @@ struct DirectionTypes { ZDirectionType z; }; +std::string toString(const DirectionTypes& dir); +inline std::string format_as(const DirectionTypes& dir) { return toString(dir); } + /// Check whether direction types are compatible, so two fields with attributes /// d1 and d2 respectively can be added, subtracted, etc. bool areDirectionsCompatible(const DirectionTypes& d1, const DirectionTypes& d2); From f0239f6a7489c4b714400280ddabfda7485d904e Mon Sep 17 00:00:00 2001 From: tomc271 Date: Tue, 29 Sep 2026 16:39:24 +0100 Subject: [PATCH 33/44] Revert `s` option for presentation format in SpecificInd fmt formatter --- include/bout/field.hxx | 4 ++-- include/bout/region.hxx | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/include/bout/field.hxx b/include/bout/field.hxx index 2b3e3ed89c..9d67616c07 100644 --- a/include/bout/field.hxx +++ b/include/bout/field.hxx @@ -300,7 +300,7 @@ inline void checkFinite(const T& f, const std::string& name = "field", BOUT_FOR_SERIAL(i, f.getRegion(rgn)) { if (!std::isfinite(f[i])) { - throw BoutException("{:s} is not finite at {:s}", name, i); + throw BoutException("{:s} is not finite at {}", name, i); } } } @@ -323,7 +323,7 @@ inline void checkPositive(const T& f, const std::string& name = "field", BOUT_FOR_SERIAL(i, f.getRegion(rgn)) { if (f[i] <= 0.) { - throw BoutException("{:s} ({:s} {:s}) is {:e} (not positive) at {:s}", name, + throw BoutException("{:s} ({:s} {:s}) is {:e} (not positive) at {}", name, f.getLocation(), f.getDirections(), f[i], i); } } diff --git a/include/bout/region.hxx b/include/bout/region.hxx index 723da9ed85..66f3e2797a 100644 --- a/include/bout/region.hxx +++ b/include/bout/region.hxx @@ -382,16 +382,15 @@ inline std::string toString(const IndPerp& i) { // Custom fmt formatter for SpecificInd template struct fmt::formatter> { - // Presentation format: 'c' - components, 'i' - index, 's' - string. + // Presentation format: 'c' - components, 'i' - index. char presentation = 'c'; - // Parses format specifications: ['c' | 'i' | 's'] or empty {} + // Parses format specifications of the form ['c' | 'i']. constexpr auto parse(format_parse_context& ctx) { const auto* it = ctx.begin(); const auto* end = ctx.end(); - - if (it != end && (*it == 'c' || *it == 'i' || *it == 's')) { - presentation = (*it == 's') ? 'c' : *it; + if (it != end && (*it == 'c' || *it == 'i')) { + presentation = *it; ++it; } @@ -410,15 +409,16 @@ struct fmt::formatter> { auto format(const SpecificInd& ind, FormatContext& ctx) const { // ctx.out() is an output iterator to write to. if (presentation == 'c') { - if constexpr (N == IND_TYPE::IND_2D) { - return fmt::format_to(ctx.out(), "({}, {})", ind.x(), ind.y()); - } else if constexpr (N == IND_TYPE::IND_3D) { - return fmt::format_to(ctx.out(), "({}, {}, {})", ind.x(), ind.y(), ind.z()); - } else if constexpr (N == IND_TYPE::IND_PERP) { - return fmt::format_to(ctx.out(), "({}, {})", ind.x(), ind.z()); + switch (N) { + case IND_TYPE::IND_2D: + return format_to(ctx.out(), "({}, {})", ind.x(), ind.y()); + case IND_TYPE::IND_3D: + return format_to(ctx.out(), "({}, {}, {})", ind.x(), ind.y(), ind.z()); + case IND_TYPE::IND_PERP: + return format_to(ctx.out(), "({}, {})", ind.x(), ind.z()); } } - return fmt::format_to(ctx.out(), "({})", ind.ind); + return format_to(ctx.out(), "({})", ind.ind); } }; From d5d10279124a6614eba53e9ad8cd558ec858adb2 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Tue, 29 Sep 2026 16:43:17 +0100 Subject: [PATCH 34/44] Remove unused #include "bout/utils.hxx" --- include/bout/output_bout_types.hxx | 1 - 1 file changed, 1 deletion(-) diff --git a/include/bout/output_bout_types.hxx b/include/bout/output_bout_types.hxx index 66b7656880..9f34d01f79 100644 --- a/include/bout/output_bout_types.hxx +++ b/include/bout/output_bout_types.hxx @@ -10,7 +10,6 @@ #include "bout/output.hxx" // IWYU pragma: keep #include "bout/region.hxx" #include "bout/traits.hxx" -#include "bout/utils.hxx" #include #include #include From 8e5b27d15eb4f2af586175d68cd98c5650648957 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Tue, 29 Sep 2026 17:11:32 +0100 Subject: [PATCH 35/44] Move definitions and specializations of `checkFinite()` and `checkPositive()` to field.cxx --- include/bout/field.hxx | 31 ++++--------------------- src/field/field.cxx | 51 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 27 deletions(-) diff --git a/include/bout/field.hxx b/include/bout/field.hxx index 9d67616c07..bdc29e615b 100644 --- a/include/bout/field.hxx +++ b/include/bout/field.hxx @@ -291,19 +291,8 @@ namespace bout { /// used during initialization, where we always want to check inputs, even for optimized /// builds. template -inline void checkFinite(const T& f, const std::string& name = "field", - const std::string& rgn = "RGN_ALL") { - - if (!f.isAllocated()) { - throw BoutException("{:s} is not allocated", name); - } - - BOUT_FOR_SERIAL(i, f.getRegion(rgn)) { - if (!std::isfinite(f[i])) { - throw BoutException("{:s} is not finite at {}", name, i); - } - } -} +void checkFinite(const T& f, const std::string& name = "field", + const std::string& rgn = "RGN_ALL"); /// Check if all values of a field \p var are positive. Loops over all points including /// the boundaries by default (can be changed using the \p rgn argument) @@ -314,20 +303,8 @@ inline void checkFinite(const T& f, const std::string& name = "field", /// be used during initialization, where we always want to check inputs, even for /// optimized builds. template -inline void checkPositive(const T& f, const std::string& name = "field", - const std::string& rgn = "RGN_ALL") { - - if (!f.isAllocated()) { - throw BoutException("{:s} is not allocated", name); - } - - BOUT_FOR_SERIAL(i, f.getRegion(rgn)) { - if (f[i] <= 0.) { - throw BoutException("{:s} ({:s} {:s}) is {:e} (not positive) at {}", name, - f.getLocation(), f.getDirections(), f[i], i); - } - } -} +void checkPositive(const T& f, const std::string& name = "field", + const std::string& rgn = "RGN_ALL"); } // namespace bout //////////////// NON-MEMBER FUNCTIONS ////////////////// diff --git a/src/field/field.cxx b/src/field/field.cxx index 8c7e8bbfee..bcb4942982 100644 --- a/src/field/field.cxx +++ b/src/field/field.cxx @@ -51,3 +51,54 @@ bool Field::isFci() const { } return not coords->getParallelTransform().canToFromFieldAligned(); } + +namespace bout { + +template +void checkFinite(const T& f, const std::string& name, const std::string& rgn) { + + if (!f.isAllocated()) { + throw BoutException("{:s} is not allocated", name); + } + + BOUT_FOR_SERIAL(i, f.getRegion(rgn)) { + if (!std::isfinite(f[i])) { + throw BoutException("{:s} is not finite at {}", name, i); + } + } +} + +template +void checkPositive(const T& f, const std::string& name, const std::string& rgn) { + + if (!f.isAllocated()) { + throw BoutException("{:s} is not allocated", name); + } + + BOUT_FOR_SERIAL(i, f.getRegion(rgn)) { + if (f[i] <= 0.) { + throw BoutException("{:s} ({:s} {:s}) is {:e} (not positive) at {}", name, + f.getLocation(), f.getDirections(), f[i], i); + } + } +} + +template void checkFinite(const Field2D&, const std::string&, + const std::string&); + +template void checkFinite(const Field3D&, const std::string&, + const std::string&); + +template void checkFinite(const FieldPerp&, const std::string&, + const std::string&); + +template void checkPositive(const Field2D&, const std::string&, + const std::string&); + +template void checkPositive(const Field3D&, const std::string&, + const std::string&); + +template void checkPositive(const FieldPerp&, const std::string&, + const std::string&); + +} // namespace bout From ad83aa6ed5e66a81b9f25d0448104ebe7d660e84 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Wed, 30 Sep 2026 11:01:20 +0100 Subject: [PATCH 36/44] Directly include headers (Clang-Tidy suggestion) --- src/field/field.cxx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/field/field.cxx b/src/field/field.cxx index bcb4942982..61229ef1d8 100644 --- a/src/field/field.cxx +++ b/src/field/field.cxx @@ -28,9 +28,13 @@ #include #include #include +#include +#include #include +#include #include #include +#include Field::Field(Mesh* localmesh, CELL_LOC location_in, DirectionTypes directions_in) : FieldData(localmesh, location_in), directions(directions_in) {} From 641e54b13179a7455c236bc2b38aa2a4a221dd0d Mon Sep 17 00:00:00 2001 From: tomc271 Date: Wed, 30 Sep 2026 11:06:40 +0100 Subject: [PATCH 37/44] Revert "Move fmt::formatter> to same file as SpecificInd is defined (ODR)" This reverts commit 163fda26 --- include/bout/output_bout_types.hxx | 43 ++++++++++++++++ include/bout/region.hxx | 79 +++++++----------------------- src/field/field.cxx | 1 + 3 files changed, 61 insertions(+), 62 deletions(-) diff --git a/include/bout/output_bout_types.hxx b/include/bout/output_bout_types.hxx index 9f34d01f79..dab84eb359 100644 --- a/include/bout/output_bout_types.hxx +++ b/include/bout/output_bout_types.hxx @@ -240,4 +240,47 @@ public: } }; +// Custom fmt formatter for SpecificInd +template +struct fmt::formatter> { + // Presentation format: 'c' - components, 'i' - index. + char presentation = 'c'; + + // Parses format specifications of the form ['c' | 'i']. + constexpr auto parse(format_parse_context& ctx) { + const auto* it = ctx.begin(); + const auto* end = ctx.end(); + if (it != end && (*it == 'c' || *it == 'i')) { + presentation = *it; + ++it; + } + + // Check if reached the end of the range: + if (it != end && *it != '}') { + throw format_error("invalid format"); + } + + // Return an iterator past the end of the parsed range: + return it; + } + + // Formats the point p using the parsed format specification (presentation) + // stored in this formatter. + template + auto format(const SpecificInd& ind, FormatContext& ctx) const { + // ctx.out() is an output iterator to write to. + if (presentation == 'c') { + switch (N) { + case IND_TYPE::IND_2D: + return format_to(ctx.out(), "({}, {})", ind.x(), ind.y()); + case IND_TYPE::IND_3D: + return format_to(ctx.out(), "({}, {}, {})", ind.x(), ind.y(), ind.z()); + case IND_TYPE::IND_PERP: + return format_to(ctx.out(), "({}, {})", ind.x(), ind.z()); + } + } + return format_to(ctx.out(), "({})", ind.ind); + } +}; + #endif // OUTPUT_BOUT_TYPES_H diff --git a/include/bout/region.hxx b/include/bout/region.hxx index 66f3e2797a..802b3758ba 100644 --- a/include/bout/region.hxx +++ b/include/bout/region.hxx @@ -49,7 +49,6 @@ #include #include -#include "fmt/base.h" #include "bout/array.hxx" #include "bout/assert.hxx" #include "bout/bout_types.hxx" @@ -57,7 +56,6 @@ #include "bout/build_config.hxx" #include "bout/build_defines.hxx" #include "bout/openmpwrap.hxx" // IWYU pragma: keep -#include class BoutMask; @@ -312,7 +310,7 @@ struct SpecificInd { SpecificInd zm(int dz = 1) const { dz = dz <= nz ? dz : dz % nz; //Fix in case dz > nz, if not force it to be in range ASSERT3(dz >= 0); - return {ind % nz < dz ? ind + nz - dz : ind - dz, ny, nz}; + return {(ind) % nz < dz ? ind + nz - dz : ind - dz, ny, nz}; } /// Automatically select zm or zp depending on sign SpecificInd zpm(int dz) const { return dz > 0 ? zp(dz) : zm(-dz); } @@ -379,49 +377,6 @@ inline std::string toString(const IndPerp& i) { return "(" + std::to_string(i.x()) + ", " + std::to_string(i.z()) + ")"; } -// Custom fmt formatter for SpecificInd -template -struct fmt::formatter> { - // Presentation format: 'c' - components, 'i' - index. - char presentation = 'c'; - - // Parses format specifications of the form ['c' | 'i']. - constexpr auto parse(format_parse_context& ctx) { - const auto* it = ctx.begin(); - const auto* end = ctx.end(); - if (it != end && (*it == 'c' || *it == 'i')) { - presentation = *it; - ++it; - } - - // Check if reached the end of the range: - if (it != end && *it != '}') { - throw format_error("invalid format"); - } - - // Return an iterator past the end of the parsed range: - return it; - } - - // Formats the point p using the parsed format specification (presentation) - // stored in this formatter. - template - auto format(const SpecificInd& ind, FormatContext& ctx) const { - // ctx.out() is an output iterator to write to. - if (presentation == 'c') { - switch (N) { - case IND_TYPE::IND_2D: - return format_to(ctx.out(), "({}, {})", ind.x(), ind.y()); - case IND_TYPE::IND_3D: - return format_to(ctx.out(), "({}, {}, {})", ind.x(), ind.y(), ind.z()); - case IND_TYPE::IND_PERP: - return format_to(ctx.out(), "({}, {})", ind.x(), ind.z()); - } - } - return format_to(ctx.out(), "({})", ind.ind); - } -}; - /// Structure to hold various derived "statistics" from a particular region struct RegionStats { int numBlocks = 0; ///< How many blocks @@ -529,9 +484,9 @@ public: using value_type = T; using reference = value_type&; using const_reference = const value_type&; - using size_type = RegionIndices::size_type; - using iterator = RegionIndices::iterator; - using const_iterator = RegionIndices::const_iterator; + using size_type = typename RegionIndices::size_type; + using iterator = typename RegionIndices::iterator; + using const_iterator = typename RegionIndices::const_iterator; // NOTE:: // Probably want to require a mesh in constructor, both to know nx/ny/nz @@ -605,12 +560,12 @@ public: /// /// Note that if the indices are altered using these iterators, the /// blocks may become out of sync and will need to manually updated - RegionIndices::iterator begin() { return std::begin(indices); }; - RegionIndices::const_iterator begin() const { return std::begin(indices); }; - RegionIndices::const_iterator cbegin() const { return indices.cbegin(); }; - RegionIndices::iterator end() { return std::end(indices); }; - RegionIndices::const_iterator end() const { return std::end(indices); }; - RegionIndices::const_iterator cend() const { return indices.cend(); }; + typename RegionIndices::iterator begin() { return std::begin(indices); }; + typename RegionIndices::const_iterator begin() const { return std::begin(indices); }; + typename RegionIndices::const_iterator cbegin() const { return indices.cbegin(); }; + typename RegionIndices::iterator end() { return std::end(indices); }; + typename RegionIndices::const_iterator end() const { return std::end(indices); }; + typename RegionIndices::const_iterator cend() const { return indices.cend(); }; const ContiguousBlocks& getBlocks() const { return blocks; }; const RegionIndices& getIndices() const { return indices; }; @@ -799,7 +754,7 @@ public: for (unsigned int i = 0; i < newInd.size(); i++) { const int index = newInd[i].ind; const int whichBlock = index / period; - newInd[i].ind = ((index + shift) % period) + (period * whichBlock); + newInd[i].ind = ((index + shift) % period) + period * whichBlock; }; setIndices(newInd); @@ -829,12 +784,12 @@ public: auto minMaxSize = std::minmax_element(std::begin(blockSizes), std::end(blockSizes)); // Note have to derefence to get actual value - result.minBlockSize = *minMaxSize.first; + result.minBlockSize = *(minMaxSize.first); result.numMinBlocks = static_cast( std::count(std::begin(blockSizes), std::end(blockSizes), result.minBlockSize)); // Note have to derefence to get actual value - result.maxBlockSize = *minMaxSize.second; + result.maxBlockSize = *(minMaxSize.second); result.numMaxBlocks = static_cast( std::count(std::begin(blockSizes), std::end(blockSizes), result.maxBlockSize)); @@ -869,8 +824,8 @@ private: /// Helper function to create a RegionIndices, given the start and end /// points in x, y, z, and the total y, z lengths - RegionIndices createRegionIndices(int xstart, int xend, int ystart, int yend, - int zstart, int zend, int ny, int nz) { + inline RegionIndices createRegionIndices(int xstart, int xend, int ystart, int yend, + int zstart, int zend, int ny, int nz) { if ((xend + 1 <= xstart) || (yend + 1 <= ystart) || (zend + 1 <= zstart)) { // Empty region @@ -897,7 +852,7 @@ private: int ind = -1; while (!done) { ind++; - region[ind].ind = (((x * ny) + y) * nz) + z; + region[ind].ind = (x * ny + y) * nz + z; if (x == xend && y == yend && z == zend) { done = true; } @@ -934,7 +889,7 @@ private: if (index >= npoints) { break; } - if (indices[index].ind - indices[index - 1].ind == 1) { + if ((indices[index].ind - indices[index - 1].ind) == 1) { count++; } else { // Reached the end of this block so break break; diff --git a/src/field/field.cxx b/src/field/field.cxx index 61229ef1d8..b13d5a3ad4 100644 --- a/src/field/field.cxx +++ b/src/field/field.cxx @@ -25,6 +25,7 @@ #include #include +#include #include #include #include From ff8a3378a8bc36be95086a12080d01d715229cab Mon Sep 17 00:00:00 2001 From: tomc271 Date: Wed, 30 Sep 2026 11:17:37 +0100 Subject: [PATCH 38/44] Remove unused #include directives --- src/field/field.cxx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/field/field.cxx b/src/field/field.cxx index b13d5a3ad4..84585afce7 100644 --- a/src/field/field.cxx +++ b/src/field/field.cxx @@ -25,16 +25,13 @@ #include #include -#include #include #include -#include #include #include -#include +#include +#include #include -#include -#include #include Field::Field(Mesh* localmesh, CELL_LOC location_in, DirectionTypes directions_in) From 4952b23faf82a580ef190233dea19cbfe0f6e421 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Wed, 30 Sep 2026 11:18:43 +0100 Subject: [PATCH 39/44] Apply clang-format --- include/bout/field.hxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/bout/field.hxx b/include/bout/field.hxx index bdc29e615b..7e97f71d29 100644 --- a/include/bout/field.hxx +++ b/include/bout/field.hxx @@ -292,7 +292,7 @@ namespace bout { /// builds. template void checkFinite(const T& f, const std::string& name = "field", - const std::string& rgn = "RGN_ALL"); + const std::string& rgn = "RGN_ALL"); /// Check if all values of a field \p var are positive. Loops over all points including /// the boundaries by default (can be changed using the \p rgn argument) @@ -304,7 +304,7 @@ void checkFinite(const T& f, const std::string& name = "field", /// optimized builds. template void checkPositive(const T& f, const std::string& name = "field", - const std::string& rgn = "RGN_ALL"); + const std::string& rgn = "RGN_ALL"); } // namespace bout //////////////// NON-MEMBER FUNCTIONS ////////////////// From 5c7d76878a17b23541f5ffb5a1e317313d671d54 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Wed, 30 Sep 2026 16:56:41 +0100 Subject: [PATCH 40/44] Don't include region.hxx in field.cxx --- src/field/field.cxx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/field/field.cxx b/src/field/field.cxx index 84585afce7..7db3fd65fd 100644 --- a/src/field/field.cxx +++ b/src/field/field.cxx @@ -31,7 +31,6 @@ #include #include #include -#include #include Field::Field(Mesh* localmesh, CELL_LOC location_in, DirectionTypes directions_in) From dd6b000792916ddc28d411acefef8c0fc5bfaa7e Mon Sep 17 00:00:00 2001 From: tomc271 Date: Wed, 30 Sep 2026 16:58:29 +0100 Subject: [PATCH 41/44] Include output_bout_types.hxx in operatorstencil.hxx now that fmt::formatter> is there --- include/bout/operatorstencil.hxx | 1 + 1 file changed, 1 insertion(+) diff --git a/include/bout/operatorstencil.hxx b/include/bout/operatorstencil.hxx index 5bedb94dbd..3727f04cd8 100644 --- a/include/bout/operatorstencil.hxx +++ b/include/bout/operatorstencil.hxx @@ -37,6 +37,7 @@ #include #include +#include #include "bout/boutexception.hxx" #include "bout/unused.hxx" #include From 6033b790978dc5e7be09c4a316c2f42ccf707260 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Wed, 30 Sep 2026 16:59:31 +0100 Subject: [PATCH 42/44] Reset to original function order --- include/bout/output_bout_types.hxx | 85 +++++++++++++++--------------- 1 file changed, 42 insertions(+), 43 deletions(-) diff --git a/include/bout/output_bout_types.hxx b/include/bout/output_bout_types.hxx index dab84eb359..77ea8ebf2d 100644 --- a/include/bout/output_bout_types.hxx +++ b/include/bout/output_bout_types.hxx @@ -10,6 +10,7 @@ #include "bout/output.hxx" // IWYU pragma: keep #include "bout/region.hxx" #include "bout/traits.hxx" + #include #include #include @@ -20,6 +21,47 @@ #include #include +template +struct fmt::formatter> { + // Presentation format: 'c' - components, 'i' - index. + char presentation = 'c'; + + // Parses format specifications of the form ['c' | 'i']. + constexpr auto parse(format_parse_context& ctx) { + const auto* it = ctx.begin(); + const auto* end = ctx.end(); + if (it != end && (*it == 'c' || *it == 'i')) { + presentation = *it++; + } + + // Check if reached the end of the range: + if (it != end && *it != '}') { + throw format_error("invalid format"); + } + + // Return an iterator past the end of the parsed range: + return it; + } + + // Formats the point p using the parsed format specification (presentation) + // stored in this formatter. + template + auto format(const SpecificInd& ind, FormatContext& ctx) const { + // ctx.out() is an output iterator to write to. + if (presentation == 'c') { + switch (N) { + case IND_TYPE::IND_2D: + return format_to(ctx.out(), "({}, {})", ind.x(), ind.y()); + case IND_TYPE::IND_3D: + return format_to(ctx.out(), "({}, {}, {})", ind.x(), ind.y(), ind.z()); + case IND_TYPE::IND_PERP: + return format_to(ctx.out(), "({}, {})", ind.x(), ind.z()); + } + } + return format_to(ctx.out(), "({})", ind.ind); + } +}; + namespace bout { namespace details { template @@ -240,47 +282,4 @@ public: } }; -// Custom fmt formatter for SpecificInd -template -struct fmt::formatter> { - // Presentation format: 'c' - components, 'i' - index. - char presentation = 'c'; - - // Parses format specifications of the form ['c' | 'i']. - constexpr auto parse(format_parse_context& ctx) { - const auto* it = ctx.begin(); - const auto* end = ctx.end(); - if (it != end && (*it == 'c' || *it == 'i')) { - presentation = *it; - ++it; - } - - // Check if reached the end of the range: - if (it != end && *it != '}') { - throw format_error("invalid format"); - } - - // Return an iterator past the end of the parsed range: - return it; - } - - // Formats the point p using the parsed format specification (presentation) - // stored in this formatter. - template - auto format(const SpecificInd& ind, FormatContext& ctx) const { - // ctx.out() is an output iterator to write to. - if (presentation == 'c') { - switch (N) { - case IND_TYPE::IND_2D: - return format_to(ctx.out(), "({}, {})", ind.x(), ind.y()); - case IND_TYPE::IND_3D: - return format_to(ctx.out(), "({}, {}, {})", ind.x(), ind.y(), ind.z()); - case IND_TYPE::IND_PERP: - return format_to(ctx.out(), "({}, {})", ind.x(), ind.z()); - } - } - return format_to(ctx.out(), "({})", ind.ind); - } -}; - #endif // OUTPUT_BOUT_TYPES_H From c29857c8122576096131e886a2dbccd14087cd7b Mon Sep 17 00:00:00 2001 From: tomc271 Date: Tue, 6 Oct 2026 12:28:17 +0100 Subject: [PATCH 43/44] No need to call `toString()` on a string --- src/mesh/parallel/shiftedmetric.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/parallel/shiftedmetric.cxx b/src/mesh/parallel/shiftedmetric.cxx index 17b62257f4..c98333edc3 100644 --- a/src/mesh/parallel/shiftedmetric.cxx +++ b/src/mesh/parallel/shiftedmetric.cxx @@ -169,7 +169,7 @@ Field3D ShiftedMetric::shiftZ(const Field3D& f, const Tensor& phs, Field3D result{emptyFrom(f).setDirectionY(y_direction_out)}; - BOUT_FOR(i, mesh.getRegion2D(toString(region))) { + BOUT_FOR(i, mesh.getRegion2D(region)) { shiftZ(&f(i, 0), &phs(i.x(), i.y(), 0), &result(i, 0)); } From d279cf5e340e3f7856f86e3cfeb52a01660d78b6 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Mon, 12 Oct 2026 12:09:46 +0100 Subject: [PATCH 44/44] Apply clang-format --- include/bout/operatorstencil.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/bout/operatorstencil.hxx b/include/bout/operatorstencil.hxx index 3727f04cd8..4ea7579465 100644 --- a/include/bout/operatorstencil.hxx +++ b/include/bout/operatorstencil.hxx @@ -37,10 +37,10 @@ #include #include -#include #include "bout/boutexception.hxx" #include "bout/unused.hxx" #include +#include #include /// A representation of offsets for indices, which can be added and