From 78538d0dbc1fbc6226614065376a7fb3e3c09390 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 3 Oct 2025 09:39:42 -0700 Subject: [PATCH 1/7] Add a periodicY function to FieldFactory Provides a variable "periodicY" that is 1 in the core and 0 outside. --- src/field/field_factory.cxx | 7 +++++-- src/field/fieldgenerators.hxx | 36 ++++++++++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/field/field_factory.cxx b/src/field/field_factory.cxx index f65f2e7f55..c49106cc1d 100644 --- a/src/field/field_factory.cxx +++ b/src/field/field_factory.cxx @@ -1,7 +1,7 @@ /************************************************************************** - * Copyright 2010 B.D.Dudson, S.Farley, M.V.Umansky, X.Q.Xu + * Copyright 2010-2025 BOUT++ contributors * - * Contact: Ben Dudson, bd512@york.ac.uk + * Contact: Ben Dudson, dudson2@llnl.gov * * This file is part of BOUT++. * @@ -176,6 +176,9 @@ FieldFactory::FieldFactory(Mesh* localmesh, Options* opt) // Where switch function addGenerator("where", std::make_shared(nullptr, nullptr, nullptr)); + + // Periodic in the Y direction? + addGenerator("periodicY", std::make_shared(nullptr)); } Field2D FieldFactory::create2D(const std::string& value, const Options* opt, diff --git a/src/field/fieldgenerators.hxx b/src/field/fieldgenerators.hxx index 2485b4b82d..133d1d1e8a 100644 --- a/src/field/fieldgenerators.hxx +++ b/src/field/fieldgenerators.hxx @@ -1,4 +1,4 @@ -/*! +/*!A * \file fieldgenerators.hxx * * These classes are used by FieldFactory @@ -307,7 +307,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; @@ -322,7 +322,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) { @@ -352,4 +352,34 @@ private: FieldGeneratorPtr test, gt0, lt0; }; +/// Function that evaluates to 1 when Y is periodic (i.e. in the core), 0 otherwise +class FieldPeriodicY : public FieldGenerator { +public: + FieldPeriodicY(Mesh* mesh) : mesh(mesh) { + // Note: Assumes symmetricGlobalX + local_inner_boundary = + 0.5 * (mesh->GlobalX(mesh->xstart - 1) + mesh->GlobalX(mesh->xstart)); + local_outer_boundary = + 0.5 * (mesh->GlobalX(mesh->xend + 1) + mesh->GlobalX(mesh->xend)); + } + FieldGeneratorPtr clone(const std::list UNUSED(args)) override { + return std::make_shared(ctx.getMesh()); + } + BoutReal generate(const bout::generator::Context& ctx) override { + int local_index = mesh->xstart + + int(((ctx.x() - local_inner_boundary) + / (local_outer_boundary - local_inner_boundary)) + * (mesh->xend - mesh->xstart + 1)); + if (mesh->periodicY(local_index)) { + return 1.0; + } + return 0.0; + } + +private: + Mesh* mesh; + BoutReal local_inner_boundary; + BoutReal local_outer_boundary; +}; + #endif // BOUT_FIELDGENERATORS_H From 06c4599dae6d0201d5ce7f6f2641b9b0c4db39da Mon Sep 17 00:00:00 2001 From: bendudson <219233+bendudson@users.noreply.github.com> Date: Fri, 3 Oct 2025 16:41:58 +0000 Subject: [PATCH 2/7] Apply clang-format changes --- src/field/fieldgenerators.hxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/field/fieldgenerators.hxx b/src/field/fieldgenerators.hxx index 133d1d1e8a..ea3682053a 100644 --- a/src/field/fieldgenerators.hxx +++ b/src/field/fieldgenerators.hxx @@ -307,7 +307,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; @@ -322,7 +322,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) { From a8512395bade559a6b7ae10ff3ad06bab4cacb66 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 3 Oct 2025 09:55:39 -0700 Subject: [PATCH 3/7] Fixes for periodicY field generator --- src/field/field_factory.cxx | 2 +- src/field/fieldgenerators.hxx | 31 +++++++++++++------------------ 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/field/field_factory.cxx b/src/field/field_factory.cxx index c49106cc1d..08a7f14fa3 100644 --- a/src/field/field_factory.cxx +++ b/src/field/field_factory.cxx @@ -178,7 +178,7 @@ FieldFactory::FieldFactory(Mesh* localmesh, Options* opt) addGenerator("where", std::make_shared(nullptr, nullptr, nullptr)); // Periodic in the Y direction? - addGenerator("periodicY", std::make_shared(nullptr)); + addGenerator("periodicY", std::make_shared()); } Field2D FieldFactory::create2D(const std::string& value, const Options* opt, diff --git a/src/field/fieldgenerators.hxx b/src/field/fieldgenerators.hxx index ea3682053a..050e335448 100644 --- a/src/field/fieldgenerators.hxx +++ b/src/field/fieldgenerators.hxx @@ -1,4 +1,4 @@ -/*!A +/*! * \file fieldgenerators.hxx * * These classes are used by FieldFactory @@ -353,33 +353,28 @@ private: }; /// Function that evaluates to 1 when Y is periodic (i.e. in the core), 0 otherwise +/// Note: Assumes symmetricGlobalX class FieldPeriodicY : public FieldGenerator { public: - FieldPeriodicY(Mesh* mesh) : mesh(mesh) { - // Note: Assumes symmetricGlobalX - local_inner_boundary = - 0.5 * (mesh->GlobalX(mesh->xstart - 1) + mesh->GlobalX(mesh->xstart)); - local_outer_boundary = - 0.5 * (mesh->GlobalX(mesh->xend + 1) + mesh->GlobalX(mesh->xend)); - } + FieldPeriodicY() = default; FieldGeneratorPtr clone(const std::list UNUSED(args)) override { - return std::make_shared(ctx.getMesh()); + return std::make_shared(); } BoutReal generate(const bout::generator::Context& ctx) override { - int local_index = mesh->xstart - + int(((ctx.x() - local_inner_boundary) - / (local_outer_boundary - local_inner_boundary)) - * (mesh->xend - mesh->xstart + 1)); + const Mesh* mesh = ctx.getMesh(); + const BoutReal local_inner_boundary = + 0.5 * (mesh->GlobalX(mesh->xstart - 1) + mesh->GlobalX(mesh->xstart)); + const BoutReal local_outer_boundary = + 0.5 * (mesh->GlobalX(mesh->xend + 1) + mesh->GlobalX(mesh->xend)); + const int local_index = mesh->xstart + + int(((ctx.x() - local_inner_boundary) + / (local_outer_boundary - local_inner_boundary)) + * (mesh->xend - mesh->xstart + 1)); if (mesh->periodicY(local_index)) { return 1.0; } return 0.0; } - -private: - Mesh* mesh; - BoutReal local_inner_boundary; - BoutReal local_outer_boundary; }; #endif // BOUT_FIELDGENERATORS_H From f84f7116a3cfde8f16f805a86c6440aedf2b7701 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Mon, 29 Dec 2025 10:27:39 -0800 Subject: [PATCH 4/7] Rename is_periodic_y Calls mesh->periodicY internally --- src/field/field_factory.cxx | 2 +- src/field/fieldgenerators.cxx | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/field/field_factory.cxx b/src/field/field_factory.cxx index 08a7f14fa3..b93a39eeda 100644 --- a/src/field/field_factory.cxx +++ b/src/field/field_factory.cxx @@ -178,7 +178,7 @@ FieldFactory::FieldFactory(Mesh* localmesh, Options* opt) addGenerator("where", std::make_shared(nullptr, nullptr, nullptr)); // Periodic in the Y direction? - addGenerator("periodicY", std::make_shared()); + addGenerator("is_periodic_y", std::make_shared()); } Field2D FieldFactory::create2D(const std::string& value, const Options* opt, diff --git a/src/field/fieldgenerators.cxx b/src/field/fieldgenerators.cxx index 1f21f5c262..7e728664f8 100644 --- a/src/field/fieldgenerators.cxx +++ b/src/field/fieldgenerators.cxx @@ -1,6 +1,8 @@ #include "fieldgenerators.hxx" +#include + #include #include From 7b30663b262012f600be382735246d3afb74b487 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Mon, 29 Dec 2025 14:29:12 -0800 Subject: [PATCH 5/7] Unit test is_periodic_y Extends FakeMesh to mock periodicY methods. --- tests/unit/fake_mesh.hxx | 8 +++- tests/unit/field/test_field_factory.cxx | 54 +++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/tests/unit/fake_mesh.hxx b/tests/unit/fake_mesh.hxx index e6f78f8767..99c8d04546 100644 --- a/tests/unit/fake_mesh.hxx +++ b/tests/unit/fake_mesh.hxx @@ -120,8 +120,12 @@ public: } MPI_Comm getXcomm(int UNUSED(jy)) const override { return BoutComm::get(); } 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; } + + // Periodic Y + int ix_separatrix {1000000}; // separatrix index + + bool periodicY(int jx) const override { return jx < ix_separatrix; } + bool periodicY(int jx, BoutReal& UNUSED(ts)) const override { return jx < ix_separatrix; } int numberOfYBoundaries() const override { return 1; } 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..2c47ae90a6 100644 --- a/tests/unit/field/test_field_factory.cxx +++ b/tests/unit/field/test_field_factory.cxx @@ -969,3 +969,57 @@ TEST_F(FieldFactoryCreateAndTransformTest, Create3DCantTransform) { EXPECT_TRUE(IsFieldEqual(output, expected)); } + +TYPED_TEST(FieldFactoryCreationTest, CreatePeriodicY) { + auto output = this->create("is_periodic_y"); + + auto expected = makeField( + [](typename TypeParam::ind_type& index) -> BoutReal { return mesh->periodicY(index.x()); }, mesh); + + EXPECT_TRUE(IsFieldEqual(output, expected)); +} + +TYPED_TEST(FieldFactoryCreationTest, CreatePeriodicYoutsideCore) { + FakeMesh localmesh{5, 1, 1}; + localmesh.createDefaultRegions(); + localmesh.setCoordinates(std::make_shared( + &localmesh, Field2D{1.0}, Field2D{1.0}, BoutReal{1.0}, Field2D{1.0}, Field2D{0.0}, + Field2D{1.0}, Field2D{1.0}, Field2D{1.0}, Field2D{0.0}, Field2D{0.0}, Field2D{0.0}, + Field2D{1.0}, Field2D{1.0}, Field2D{1.0}, Field2D{0.0}, Field2D{0.0}, Field2D{0.0}, + Field2D{0.0}, Field2D{0.0})); + // No call to Coordinates::geometry() needed here + + localmesh.getCoordinates()->setParallelTransform( + bout::utils::make_unique(localmesh)); + + localmesh.ix_separatrix = 0; // All points outside core + + auto output = this->create("is_periodic_y", nullptr, &localmesh); + + auto expected = makeField( + [](typename TypeParam::ind_type& index) -> BoutReal { return 0.0; }, &localmesh); + + EXPECT_TRUE(IsFieldEqual(output, expected)); +} + +TYPED_TEST(FieldFactoryCreationTest, CreatePeriodicYacrossSeparatrix) { + FakeMesh localmesh{5, 1, 1}; + localmesh.createDefaultRegions(); + localmesh.ix_separatrix = 2; // All points in core + localmesh.setCoordinates(std::make_shared( + &localmesh, Field2D{1.0}, Field2D{1.0}, BoutReal{1.0}, Field2D{1.0}, Field2D{0.0}, + Field2D{1.0}, Field2D{1.0}, Field2D{1.0}, Field2D{0.0}, Field2D{0.0}, Field2D{0.0}, + Field2D{1.0}, Field2D{1.0}, Field2D{1.0}, Field2D{0.0}, Field2D{0.0}, Field2D{0.0}, + Field2D{0.0}, Field2D{0.0})); + // No call to Coordinates::geometry() needed here + + localmesh.getCoordinates()->setParallelTransform( + bout::utils::make_unique(localmesh)); + + auto output = this->create("is_periodic_y", nullptr, &localmesh); + + auto expected = makeField( + [&](typename TypeParam::ind_type& index) -> BoutReal { return index.x() < localmesh.ix_separatrix; }, &localmesh); + + EXPECT_TRUE(IsFieldEqual(output, expected)); +} From 1f264d0914e29a352238e7d413b1d3dcacbb9693 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Mon, 29 Dec 2025 14:29:57 -0800 Subject: [PATCH 6/7] Add is_periodic_y to manual page Include in table of values available to expressions. --- manual/sphinx/user_docs/variable_init.rst | 24 ++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/manual/sphinx/user_docs/variable_init.rst b/manual/sphinx/user_docs/variable_init.rst index 4ac13e0ead..1baaba5e84 100644 --- a/manual/sphinx/user_docs/variable_init.rst +++ b/manual/sphinx/user_docs/variable_init.rst @@ -81,17 +81,19 @@ following values are also already defined: .. _tab-initexprvals: .. table:: Initialisation expression values - +--------+------------------------------------------------------------------------------------+ - | Name | Description | - +========+====================================================================================+ - | x | :math:`x` position between :math:`0` and :math:`1` | - +--------+------------------------------------------------------------------------------------+ - | y | :math:`y` angle-like position, definition depends on topology of grid | - +--------+------------------------------------------------------------------------------------+ - | z | :math:`z` position between :math:`0` and :math:`2\pi` (excluding the last point) | - +--------+------------------------------------------------------------------------------------+ - | pi π | :math:`3.1415\ldots` | - +--------+------------------------------------------------------------------------------------+ + +----------------+------------------------------------------------------------------------------------+ + | Name | Description | + +================+====================================================================================+ + | x | :math:`x` position between :math:`0` and :math:`1` | + +----------------+------------------------------------------------------------------------------------+ + | y | :math:`y` angle-like position, definition depends on topology of grid | + +----------------+------------------------------------------------------------------------------------+ + | z | :math:`z` position between :math:`0` and :math:`2\pi` (excluding the last point) | + +----------------+------------------------------------------------------------------------------------+ + | pi π | :math:`3.1415\ldots` | + +----------------+------------------------------------------------------------------------------------+ + | is_periodic_y | :math:`1` in core region where Y is periodic. :math:`0` otherwise | + +----------------+------------------------------------------------------------------------------------+ By default, :math:`x` is defined as ``(i+0.5) / (nx - 2*MXG)``, where ``MXG`` From 67bddf3e9c76440141f5d5c32a5d57c96215145e Mon Sep 17 00:00:00 2001 From: bendudson <219233+bendudson@users.noreply.github.com> Date: Mon, 29 Dec 2025 22:31:09 +0000 Subject: [PATCH 7/7] Apply clang-format changes --- include/bout/invertable_operator.hxx | 4 ++-- include/bout/output.hxx | 14 +++++++------- src/solver/impls/arkode/arkode.cxx | 3 +-- tests/unit/fake_mesh.hxx | 6 ++++-- tests/unit/field/test_field_factory.cxx | 12 +++++++++--- 5 files changed, 23 insertions(+), 16 deletions(-) diff --git a/include/bout/invertable_operator.hxx b/include/bout/invertable_operator.hxx index fe139986be..99f7d9b675 100644 --- a/include/bout/invertable_operator.hxx +++ b/include/bout/invertable_operator.hxx @@ -575,7 +575,7 @@ public: }; #endif // PETSC -}; // namespace inversion -}; // namespace bout +}; // namespace inversion +}; // namespace bout #endif // HEADER GUARD diff --git a/include/bout/output.hxx b/include/bout/output.hxx index 2862899067..8f50f9029b 100644 --- a/include/bout/output.hxx +++ b/include/bout/output.hxx @@ -136,14 +136,14 @@ private: class DummyOutput : public Output { public: template - void write([[maybe_unused]] const S& format, [[maybe_unused]] const Args&... args){}; + void write([[maybe_unused]] const S& format, [[maybe_unused]] const Args&... args) {}; template - void print([[maybe_unused]] const S& format, [[maybe_unused]] const Args&... args){}; - void write([[maybe_unused]] const std::string& message) override{}; - void print([[maybe_unused]] const std::string& message) override{}; - void enable() override{}; - void disable() override{}; - void enable([[maybe_unused]] bool enable){}; + void print([[maybe_unused]] const S& format, [[maybe_unused]] const Args&... args) {}; + void write([[maybe_unused]] const std::string& message) override {}; + void print([[maybe_unused]] const std::string& message) override {}; + void enable() override {}; + void disable() override {}; + void enable([[maybe_unused]] bool enable) {}; bool isEnabled() override { return false; } }; diff --git a/src/solver/impls/arkode/arkode.cxx b/src/solver/impls/arkode/arkode.cxx index 6f20ce11a6..e5c70ae9ba 100644 --- a/src/solver/impls/arkode/arkode.cxx +++ b/src/solver/impls/arkode/arkode.cxx @@ -417,8 +417,7 @@ int ArkodeSolver::init() { if (hasPreconditioner()) { output.write("\tUsing user-supplied preconditioner\n"); - if (ARKodeSetPreconditioner(arkode_mem, nullptr, arkode_pre) - != ARKLS_SUCCESS) { + if (ARKodeSetPreconditioner(arkode_mem, nullptr, arkode_pre) != ARKLS_SUCCESS) { throw BoutException("ARKodeSetPreconditioner failed\n"); } } else { diff --git a/tests/unit/fake_mesh.hxx b/tests/unit/fake_mesh.hxx index 99c8d04546..e8ba4f3e93 100644 --- a/tests/unit/fake_mesh.hxx +++ b/tests/unit/fake_mesh.hxx @@ -122,10 +122,12 @@ public: MPI_Comm getYcomm(int UNUSED(jx)) const override { return BoutComm::get(); } // Periodic Y - int ix_separatrix {1000000}; // separatrix index + int ix_separatrix{1000000}; // separatrix index bool periodicY(int jx) const override { return jx < ix_separatrix; } - bool periodicY(int jx, BoutReal& UNUSED(ts)) const override { return jx < ix_separatrix; } + bool periodicY(int jx, BoutReal& UNUSED(ts)) const override { + return jx < ix_separatrix; + } int numberOfYBoundaries() const override { return 1; } 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 2c47ae90a6..26dc1e2990 100644 --- a/tests/unit/field/test_field_factory.cxx +++ b/tests/unit/field/test_field_factory.cxx @@ -974,7 +974,10 @@ TYPED_TEST(FieldFactoryCreationTest, CreatePeriodicY) { auto output = this->create("is_periodic_y"); auto expected = makeField( - [](typename TypeParam::ind_type& index) -> BoutReal { return mesh->periodicY(index.x()); }, mesh); + [](typename TypeParam::ind_type& index) -> BoutReal { + return mesh->periodicY(index.x()); + }, + mesh); EXPECT_TRUE(IsFieldEqual(output, expected)); } @@ -997,7 +1000,7 @@ TYPED_TEST(FieldFactoryCreationTest, CreatePeriodicYoutsideCore) { auto output = this->create("is_periodic_y", nullptr, &localmesh); auto expected = makeField( - [](typename TypeParam::ind_type& index) -> BoutReal { return 0.0; }, &localmesh); + [](typename TypeParam::ind_type& index) -> BoutReal { return 0.0; }, &localmesh); EXPECT_TRUE(IsFieldEqual(output, expected)); } @@ -1019,7 +1022,10 @@ TYPED_TEST(FieldFactoryCreationTest, CreatePeriodicYacrossSeparatrix) { auto output = this->create("is_periodic_y", nullptr, &localmesh); auto expected = makeField( - [&](typename TypeParam::ind_type& index) -> BoutReal { return index.x() < localmesh.ix_separatrix; }, &localmesh); + [&](typename TypeParam::ind_type& index) -> BoutReal { + return index.x() < localmesh.ix_separatrix; + }, + &localmesh); EXPECT_TRUE(IsFieldEqual(output, expected)); }