From 0091b66e4bfe296903d34d05cf5049aa6deffa50 Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 6 Jul 2026 13:22:45 +0200 Subject: [PATCH 001/221] Next version for next will be 5.3.0 --- CMakeLists.txt | 2 +- tools/pylib/_boutpp_build/backend.py | 16 ++++++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3caf4d8caf..ac9b32a127 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,7 +31,7 @@ endif() # Set the version here, strip any extra tags to use in `project` # We try to use git to get a full description, inspired by setuptools_scm set(_bout_previous_version "5.2.0") -set(_bout_next_version "5.2.1") +set(_bout_next_version "5.3.0") execute_process( COMMAND "git" describe --tags --match=v${_bout_previous_version} COMMAND sed -e s/${_bout_previous_version}-/${_bout_next_version}.dev/ -e diff --git a/tools/pylib/_boutpp_build/backend.py b/tools/pylib/_boutpp_build/backend.py index 922ef1c48c..824e7552ad 100755 --- a/tools/pylib/_boutpp_build/backend.py +++ b/tools/pylib/_boutpp_build/backend.py @@ -12,7 +12,7 @@ try: import packaging.tags # packaging -except: +except ImportError: packaging = None @@ -36,7 +36,7 @@ def getversion(): return version.lstrip("v") _bout_previous_version = "v5.2.0" - _bout_next_version = "v5.2.1" + _bout_next_version = "v5.3.0" try: try: @@ -161,7 +161,6 @@ def build_wheel(wheel_directory, config_settings=None, metadata_directory=None): ) run(f"cmake --build _wheel_build/ -j {os.cpu_count()}") run("cmake --install _wheel_build/") - distinfo = f"_wheel_install" prepare_metadata_for_build_wheel("_wheel_install", record=True) # Do not add --symlink as python's does not extract that as symlinks @@ -220,8 +219,8 @@ def build_sdist(sdist_directory, config_settings=None): ) with open("LICENSE") as src: pre = "License: " - for l in src: - f.write(f"{pre}{l}") + for line in src: + f.write(f"{pre}{line}") pre = " " f.write("Description-Content-Type: text/markdown\n\n") with open("README.md") as src: @@ -344,11 +343,8 @@ def help(): """ table = [] for k, v in todos.items(): - try: - doc = v.__doc__.strip() - doc = " : " + doc - except: - doc = "" + doc = v.__doc__.strip() + doc = " : " + doc table.append((k, doc)) maxkey = max([len(k) for k, _ in table]) fmt = f" %-{maxkey}s%s" From 06d7ff52389746f9596a617a1bcda88f8287c05a Mon Sep 17 00:00:00 2001 From: malamast Date: Tue, 7 Jul 2026 16:39:07 -0700 Subject: [PATCH 002/221] boundary_standard: Added a legacy implementation of 1st order Dirichlet and Neumann BCs. - This is what we used to do in Bout++ version 3. - Higher order extrapolation can be unstable sometimes so I added back the 1st order BC. You can use thm in the input file by setting e.g. dirichlet_o1. - Implementation for staggered grids is not done. --- include/bout/boundary_standard.hxx | 48 ++++++ src/mesh/boundary_factory.cxx | 11 +- src/mesh/boundary_standard.cxx | 261 +++++++++++++++++++++++++++++ 3 files changed, 316 insertions(+), 4 deletions(-) diff --git a/include/bout/boundary_standard.hxx b/include/bout/boundary_standard.hxx index b1116e159f..3cb4b23803 100644 --- a/include/bout/boundary_standard.hxx +++ b/include/bout/boundary_standard.hxx @@ -33,6 +33,30 @@ private: BoutReal val; }; +/// Dirichlet (set to zero) boundary condition +class BoundaryDirichlet_O1 : public BoundaryOp { +public: + BoundaryDirichlet_O1() : gen(nullptr) {} + BoundaryDirichlet_O1(BoundaryRegion* region, std::shared_ptr g) + : BoundaryOp(region), gen(std::move(g)) {} + + using BoundaryOp::clone; + BoundaryOp* clone(BoundaryRegion* region, const std::list& args) override; + + using BoundaryOp::apply; + void apply(Field2D& f) override; + void apply(Field2D& f, BoutReal t) override; + void apply(Field3D& f) override; + void apply(Field3D& f, BoutReal t) override; + + using BoundaryOp::apply_ddt; + void apply_ddt(Field2D& f) override; + void apply_ddt(Field3D& f) override; + +private: + std::shared_ptr gen; // Generator +}; + /// Dirichlet (set to zero) boundary condition class BoundaryDirichlet : public BoundaryOp { public: @@ -163,6 +187,30 @@ public: void apply(Field3D& f) override; }; +/// Neumann (zero-gradient) boundary condition, using 1st order on boundary +class BoundaryNeumann_O1 : public BoundaryOp { +public: + BoundaryNeumann_O1() : gen(nullptr) {} + BoundaryNeumann_O1(BoundaryRegion* region, std::shared_ptr g) + : BoundaryOp(region), gen(std::move(g)) {} + + using BoundaryOp::clone; + BoundaryOp* clone(BoundaryRegion* region, const std::list& args) override; + + using BoundaryOp::apply; + void apply(Field2D& f) override; + void apply(Field2D& f, BoutReal t) override; + void apply(Field3D& f) override; + void apply(Field3D& f, BoutReal t) override; + + using BoundaryOp::apply_ddt; + void apply_ddt(Field2D& f) override; + void apply_ddt(Field3D& f) override; + +private: + std::shared_ptr gen; +}; + /// Neumann boundary condition set half way between guard cell and grid cell at 2nd order accuracy class BoundaryNeumann_2ndOrder : public BoundaryOp { public: diff --git a/src/mesh/boundary_factory.cxx b/src/mesh/boundary_factory.cxx index 988fcfce9b..db1189900c 100644 --- a/src/mesh/boundary_factory.cxx +++ b/src/mesh/boundary_factory.cxx @@ -20,15 +20,18 @@ using std::string; BoundaryFactory* BoundaryFactory::instance = nullptr; BoundaryFactory::BoundaryFactory() { - add(new BoundaryDirichlet(), "dirichlet"); + add(new BoundaryDirichlet(), "dirichlet"); // Default + add(new BoundaryDirichlet_O1(), "dirichlet_o1"); // Old implementation in v3 add(new BoundaryDirichlet(), "dirichlet_o2"); // Synonym for "dirichlet" add(new BoundaryDirichlet_O3(), "dirichlet_o3"); add(new BoundaryDirichlet_O4(), "dirichlet_o4"); add(new BoundaryDirichlet_4thOrder(), "dirichlet_4thorder"); - add(new BoundaryNeumann(), "neumann"); - add(new BoundaryNeumann(), "neumann_O2"); // Synonym for "neumann" + + add(new BoundaryNeumann(), "neumann"); // Default + add(new BoundaryNeumann_O1(), "neumann_o1"); // Old implementation in v3 + add(new BoundaryNeumann(), "neumann_o2"); // Synonym for "neumann" add(new BoundaryNeumann_4thOrder(), "neumann_4thorder"); - add(new BoundaryNeumann_O4(), "neumann_O4"); + add(new BoundaryNeumann_O4(), "neumann_o4"); add(new BoundaryNeumannPar(), "neumannpar"); add(new BoundaryNeumann_NonOrthogonal(), "neumann_nonorthogonal"); add(new BoundaryRobin(), "robin"); diff --git a/src/mesh/boundary_standard.cxx b/src/mesh/boundary_standard.cxx index 141cab0a43..ba0eea658e 100644 --- a/src/mesh/boundary_standard.cxx +++ b/src/mesh/boundary_standard.cxx @@ -115,6 +115,135 @@ void verifyNumPoints(BoundaryRegion* region, int ptsRequired) { void verifyNumPoints(BoundaryRegion*, int) {} #endif + +/////////////////////////////////////////////////////////////// + +BoundaryOp* BoundaryDirichlet_O1::clone(BoundaryRegion* region, + const std::list& args) { + verifyNumPoints(region, 1); + + std::shared_ptr newgen; + if (!args.empty()) { + // First argument should be an expression + newgen = FieldFactory::get()->parse(args.front()); + } + return new BoundaryDirichlet_O1(region, newgen); +} + +void BoundaryDirichlet_O1::apply(Field2D& f) { BoundaryDirichlet_O1::apply(f, 0.); } + +void BoundaryDirichlet_O1::apply(Field2D& f, BoutReal t) { + // Set (at 1st order) the value at the grid cell to the guard cells. + + Mesh* mesh = bndry->localmesh; + ASSERT1(mesh == f.getMesh()); + bndry->first(); + + // Decide which generator to use + std::shared_ptr fg = gen; + if (!fg) { + fg = f.getBndryGenerator(bndry->location); + } + + BoutReal val = 0.0; + + // Check for staggered grids + + CELL_LOC loc = f.getLocation(); + if (mesh->StaggerGrids) { + // Staggered + throw BoutException("dirichlet_o1 BC is not implementated for staggered grids."); + + } else { + // Non-staggered, standard case + for (; !bndry->isDone(); bndry->next1d()) { + + if (fg) { + val = fg->generate(Context(bndry, loc, t, mesh)); + } + f(bndry->x, bndry->y) = val; + + // Need to set second guard cell, as may be used for interpolation or upwinding derivatives + // This is not very efficient. Both boundary cells can be treated in one loop. + for (int i = 1; i < bndry->width; i++) { + int xi = bndry->x + i * bndry->bx; + int yi = bndry->y + i * bndry->by; + f(xi, yi) = val; + } + + } + } +} + +void BoundaryDirichlet_O1::apply(Field3D& f) { BoundaryDirichlet_O1::apply(f, 0.); } + +void BoundaryDirichlet_O1::apply(Field3D& f, BoutReal t) { + // Set (at 1st order) the value at the grid cell to the guard cells. + + Mesh* mesh = bndry->localmesh; + ASSERT1(mesh == f.getMesh()); + bndry->first(); + + // Decide which generator to use + std::shared_ptr fg = gen; + if (!fg) { + fg = f.getBndryGenerator(bndry->location); + } + + BoutReal val = 0.0; + + // Check for staggered grids + + CELL_LOC loc = f.getLocation(); + if (mesh->StaggerGrids) { + // Staggered. + throw BoutException("dirichlet_o1 BC is not implementated for staggered grids."); + + } else { + // Standard (non-staggered) case + for (; !bndry->isDone(); bndry->next1d()) { + for (int zk = 0; zk < mesh->LocalNz; zk++) { + if (fg) { + val = fg->generate(Context(bndry, zk, loc, t, mesh)); + } + f(bndry->x, bndry->y, zk) = val; + } + + // This is not very efficient. Both boundary cells can be treated in one loop. + for (int i = 1; i < bndry->width; i++) { + // Set any other guard cells using the values on the cells + int xi = bndry->x + i * bndry->bx; + int yi = bndry->y + i * bndry->by; + for (int zk = 0; zk < mesh->LocalNz; zk++) { + if (fg) { + val = fg->generate(Context(bndry, zk, loc, t, mesh)); + } + f(xi, yi, zk) = val; + } + } + } + } +} + +void BoundaryDirichlet_O1::apply_ddt(Field2D& f) { + Field2D* dt = f.timeDeriv(); + for (bndry->first(); !bndry->isDone(); bndry->next()) { + (*dt)(bndry->x, bndry->y) = 0.; // Set time derivative to zero + } +} + +void BoundaryDirichlet_O1::apply_ddt(Field3D& f) { + Mesh* mesh = bndry->localmesh; + ASSERT1(mesh == f.getMesh()); + Field3D* dt = f.timeDeriv(); + + for (bndry->first(); !bndry->isDone(); bndry->next()) { + for (int z = 0; z < mesh->LocalNz; z++) { + (*dt)(bndry->x, bndry->y, z) = 0.; // Set time derivative to zero + } + } +} + /////////////////////////////////////////////////////////////// BoundaryOp* BoundaryDirichlet::clone(BoundaryRegion* region, @@ -1714,6 +1843,138 @@ void BoundaryNeumann_NonOrthogonal::apply(Field3D& f) { /////////////////////////////////////////////////////////////// + BoundaryOp* BoundaryNeumann_O1::clone(BoundaryRegion * region, + const std::list& args) { + verifyNumPoints(region, 1); + std::shared_ptr newgen = nullptr; + if (!args.empty()) { + // First argument should be an expression + newgen = FieldFactory::get()->parse(args.front()); + } + return new BoundaryNeumann_O1(region, newgen); + } + + void BoundaryNeumann_O1::apply(Field2D & f) { BoundaryNeumann_O1::apply(f, 0.); } + + void BoundaryNeumann_O1::apply(Field2D & f, BoutReal t) { + // Set (at 1st order) the gradient/value at the grid cell to the guard cells. + + +#if not(BOUT_USE_METRIC_3D) + Mesh* mesh = bndry->localmesh; + ASSERT1(mesh == f.getMesh()); + Coordinates* metric = f.getCoordinates(); + + bndry->first(); + + // Decide which generator to use + std::shared_ptr fg = gen; + if (!fg) { + fg = f.getBndryGenerator(bndry->location); + } + + BoutReal val = 0.0; + + // Check for staggered grids + + CELL_LOC loc = f.getLocation(); + if (mesh->StaggerGrids) { + // Staggered. + throw BoutException("neumann_o1 BC is not implementated for staggered grids."); + + } else { + // Non-staggered, standard case + + for (bndry->first(); !bndry->isDone(); bndry->next1d()) { + BoutReal delta = bndry->bx * metric->dx(bndry->x, bndry->y) + + bndry->by * metric->dy(bndry->x, bndry->y); + + if (fg) { + val = fg->generate(Context(bndry, loc, t, mesh)); + } + + f(bndry->x, bndry->y) = + f(bndry->x - bndry->bx, bndry->y - bndry->by) + delta * val; + if (bndry->width == 2) { + f(bndry->x + bndry->bx, bndry->y + bndry->by) = f(bndry->x, bndry->y) + delta * val; + } + } + } +#else + throw BoutException("Applying boundary condition 'neumann' to Field2D " + "not compatible with 3D metrics in all cases."); +#endif + } + + void BoundaryNeumann_O1::apply(Field3D & f) { BoundaryNeumann_O1::apply(f, 0.); } + + void BoundaryNeumann_O1::apply(Field3D & f, BoutReal t) { + Mesh* mesh = bndry->localmesh; + ASSERT1(mesh == f.getMesh()); + Coordinates* metric = f.getCoordinates(); + + bndry->first(); + + // Decide which generator to use + std::shared_ptr fg = gen; + if (!fg) { + fg = f.getBndryGenerator(bndry->location); + } + + BoutReal val = 0.0; + + // Check for staggered grids + + CELL_LOC loc = f.getLocation(); + if (mesh->StaggerGrids) { + // Staggered. + throw BoutException("neumann_o1 BC is not implementated for staggered grids."); + + } else { + for (; !bndry->isDone(); bndry->next1d()) { +#if BOUT_USE_METRIC_3D + for (int zk = 0; zk < mesh->LocalNz; zk++) { + BoutReal delta = bndry->bx * metric->dx(bndry->x, bndry->y, zk) + + bndry->by * metric->dy(bndry->x, bndry->y, zk); +#else + BoutReal delta = bndry->bx * metric->dx(bndry->x, bndry->y) + + bndry->by * metric->dy(bndry->x, bndry->y); + for (int zk = 0; zk < mesh->LocalNz; zk++) { +#endif + if (fg) { + val = fg->generate(Context(bndry, zk, loc, t, mesh)); + } + f(bndry->x, bndry->y, zk) = + f(bndry->x - bndry->bx, bndry->y - bndry->by, zk) + delta * val; + if (bndry->width == 2) { + f(bndry->x + bndry->bx, bndry->y + bndry->by, zk) = + f(bndry->x, bndry->y, zk) + delta * val; + } + } + } + } + } + + void BoundaryNeumann_O1::apply_ddt(Field2D & f) { + Field2D* dt = f.timeDeriv(); + for (bndry->first(); !bndry->isDone(); bndry->next()) { + (*dt)(bndry->x, bndry->y) = 0.; // Set time derivative to zero + } + } + + void BoundaryNeumann_O1::apply_ddt(Field3D & f) { + Mesh* mesh = bndry->localmesh; + ASSERT1(mesh == f.getMesh()); + Field3D* dt = f.timeDeriv(); + for (bndry->first(); !bndry->isDone(); bndry->next()) { + for (int z = 0; z < mesh->LocalNz; z++) { + (*dt)(bndry->x, bndry->y, z) = 0.; // Set time derivative to zero + } + } + } + + /////////////////////////////////////////////////////////////// + BoundaryOp* BoundaryNeumann::clone(BoundaryRegion * region, const std::list& args) { verifyNumPoints(region, 1); From a9a5f418f8d75243c87e922c4e4923feb3c833e5 Mon Sep 17 00:00:00 2001 From: malamast Date: Tue, 7 Jul 2026 16:59:42 -0700 Subject: [PATCH 003/221] boundary_standard: changed LocalNz to zend to make it compatible with the rest of the BC code. -This was a later change to the BCs --- src/mesh/boundary_standard.cxx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/mesh/boundary_standard.cxx b/src/mesh/boundary_standard.cxx index ba0eea658e..f12d079d01 100644 --- a/src/mesh/boundary_standard.cxx +++ b/src/mesh/boundary_standard.cxx @@ -202,7 +202,7 @@ void BoundaryDirichlet_O1::apply(Field3D& f, BoutReal t) { } else { // Standard (non-staggered) case for (; !bndry->isDone(); bndry->next1d()) { - for (int zk = 0; zk < mesh->LocalNz; zk++) { + for (int zk = mesh->zstart; zk <= mesh->zend; zk++) { if (fg) { val = fg->generate(Context(bndry, zk, loc, t, mesh)); } @@ -214,7 +214,7 @@ void BoundaryDirichlet_O1::apply(Field3D& f, BoutReal t) { // Set any other guard cells using the values on the cells int xi = bndry->x + i * bndry->bx; int yi = bndry->y + i * bndry->by; - for (int zk = 0; zk < mesh->LocalNz; zk++) { + for (int zk = mesh->zstart; zk <= mesh->zend; zk++) { if (fg) { val = fg->generate(Context(bndry, zk, loc, t, mesh)); } @@ -238,7 +238,7 @@ void BoundaryDirichlet_O1::apply_ddt(Field3D& f) { Field3D* dt = f.timeDeriv(); for (bndry->first(); !bndry->isDone(); bndry->next()) { - for (int z = 0; z < mesh->LocalNz; z++) { + for (int z = mesh->zstart; z <= mesh->zend; z++) { (*dt)(bndry->x, bndry->y, z) = 0.; // Set time derivative to zero } } @@ -1933,13 +1933,13 @@ void BoundaryNeumann_NonOrthogonal::apply(Field3D& f) { } else { for (; !bndry->isDone(); bndry->next1d()) { #if BOUT_USE_METRIC_3D - for (int zk = 0; zk < mesh->LocalNz; zk++) { + for (int zk = mesh->zstart; zk <= mesh->zend; zk++) { BoutReal delta = bndry->bx * metric->dx(bndry->x, bndry->y, zk) + bndry->by * metric->dy(bndry->x, bndry->y, zk); #else BoutReal delta = bndry->bx * metric->dx(bndry->x, bndry->y) + bndry->by * metric->dy(bndry->x, bndry->y); - for (int zk = 0; zk < mesh->LocalNz; zk++) { + for (int zk = mesh->zstart; zk <= mesh->zend; zk++) { #endif if (fg) { val = fg->generate(Context(bndry, zk, loc, t, mesh)); @@ -1967,7 +1967,7 @@ void BoundaryNeumann_NonOrthogonal::apply(Field3D& f) { ASSERT1(mesh == f.getMesh()); Field3D* dt = f.timeDeriv(); for (bndry->first(); !bndry->isDone(); bndry->next()) { - for (int z = 0; z < mesh->LocalNz; z++) { + for (int z = mesh->zstart; z <= mesh->zend; z++) { (*dt)(bndry->x, bndry->y, z) = 0.; // Set time derivative to zero } } From a96044748c6464b5fc297460476a306c7719905d Mon Sep 17 00:00:00 2001 From: malamast Date: Tue, 7 Jul 2026 17:23:00 -0700 Subject: [PATCH 004/221] Apply clang-format --- src/mesh/boundary_factory.cxx | 6 +++--- src/mesh/boundary_standard.cxx | 32 +++++++++++++++----------------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/mesh/boundary_factory.cxx b/src/mesh/boundary_factory.cxx index db1189900c..58e37ed53f 100644 --- a/src/mesh/boundary_factory.cxx +++ b/src/mesh/boundary_factory.cxx @@ -20,16 +20,16 @@ using std::string; BoundaryFactory* BoundaryFactory::instance = nullptr; BoundaryFactory::BoundaryFactory() { - add(new BoundaryDirichlet(), "dirichlet"); // Default + add(new BoundaryDirichlet(), "dirichlet"); // Default add(new BoundaryDirichlet_O1(), "dirichlet_o1"); // Old implementation in v3 add(new BoundaryDirichlet(), "dirichlet_o2"); // Synonym for "dirichlet" add(new BoundaryDirichlet_O3(), "dirichlet_o3"); add(new BoundaryDirichlet_O4(), "dirichlet_o4"); add(new BoundaryDirichlet_4thOrder(), "dirichlet_4thorder"); - add(new BoundaryNeumann(), "neumann"); // Default + add(new BoundaryNeumann(), "neumann"); // Default add(new BoundaryNeumann_O1(), "neumann_o1"); // Old implementation in v3 - add(new BoundaryNeumann(), "neumann_o2"); // Synonym for "neumann" + add(new BoundaryNeumann(), "neumann_o2"); // Synonym for "neumann" add(new BoundaryNeumann_4thOrder(), "neumann_4thorder"); add(new BoundaryNeumann_O4(), "neumann_o4"); add(new BoundaryNeumannPar(), "neumannpar"); diff --git a/src/mesh/boundary_standard.cxx b/src/mesh/boundary_standard.cxx index f12d079d01..8917011a93 100644 --- a/src/mesh/boundary_standard.cxx +++ b/src/mesh/boundary_standard.cxx @@ -115,11 +115,10 @@ void verifyNumPoints(BoundaryRegion* region, int ptsRequired) { void verifyNumPoints(BoundaryRegion*, int) {} #endif - /////////////////////////////////////////////////////////////// BoundaryOp* BoundaryDirichlet_O1::clone(BoundaryRegion* region, - const std::list& args) { + const std::list& args) { verifyNumPoints(region, 1); std::shared_ptr newgen; @@ -168,9 +167,8 @@ void BoundaryDirichlet_O1::apply(Field2D& f, BoutReal t) { for (int i = 1; i < bndry->width; i++) { int xi = bndry->x + i * bndry->bx; int yi = bndry->y + i * bndry->by; - f(xi, yi) = val; - } - + f(xi, yi) = val; + } } } } @@ -1844,7 +1842,7 @@ void BoundaryNeumann_NonOrthogonal::apply(Field3D& f) { /////////////////////////////////////////////////////////////// BoundaryOp* BoundaryNeumann_O1::clone(BoundaryRegion * region, - const std::list& args) { + const std::list& args) { verifyNumPoints(region, 1); std::shared_ptr newgen = nullptr; if (!args.empty()) { @@ -1857,8 +1855,7 @@ void BoundaryNeumann_NonOrthogonal::apply(Field3D& f) { void BoundaryNeumann_O1::apply(Field2D & f) { BoundaryNeumann_O1::apply(f, 0.); } void BoundaryNeumann_O1::apply(Field2D & f, BoutReal t) { - // Set (at 1st order) the gradient/value at the grid cell to the guard cells. - + // Set (at 1st order) the gradient/value at the grid cell to the guard cells. #if not(BOUT_USE_METRIC_3D) Mesh* mesh = bndry->localmesh; @@ -1879,7 +1876,7 @@ void BoundaryNeumann_NonOrthogonal::apply(Field3D& f) { CELL_LOC loc = f.getLocation(); if (mesh->StaggerGrids) { - // Staggered. + // Staggered. throw BoutException("neumann_o1 BC is not implementated for staggered grids."); } else { @@ -1896,13 +1893,14 @@ void BoundaryNeumann_NonOrthogonal::apply(Field3D& f) { f(bndry->x, bndry->y) = f(bndry->x - bndry->bx, bndry->y - bndry->by) + delta * val; if (bndry->width == 2) { - f(bndry->x + bndry->bx, bndry->y + bndry->by) = f(bndry->x, bndry->y) + delta * val; + f(bndry->x + bndry->bx, bndry->y + bndry->by) = + f(bndry->x, bndry->y) + delta * val; } } } #else - throw BoutException("Applying boundary condition 'neumann' to Field2D " - "not compatible with 3D metrics in all cases."); + throw BoutException("Applying boundary condition 'neumann' to Field2D " + "not compatible with 3D metrics in all cases."); #endif } @@ -1927,7 +1925,7 @@ void BoundaryNeumann_NonOrthogonal::apply(Field3D& f) { CELL_LOC loc = f.getLocation(); if (mesh->StaggerGrids) { - // Staggered. + // Staggered. throw BoutException("neumann_o1 BC is not implementated for staggered grids."); } else { @@ -1937,9 +1935,9 @@ void BoundaryNeumann_NonOrthogonal::apply(Field3D& f) { BoutReal delta = bndry->bx * metric->dx(bndry->x, bndry->y, zk) + bndry->by * metric->dy(bndry->x, bndry->y, zk); #else - BoutReal delta = bndry->bx * metric->dx(bndry->x, bndry->y) - + bndry->by * metric->dy(bndry->x, bndry->y); - for (int zk = mesh->zstart; zk <= mesh->zend; zk++) { + BoutReal delta = bndry->bx * metric->dx(bndry->x, bndry->y) + + bndry->by * metric->dy(bndry->x, bndry->y); + for (int zk = mesh->zstart; zk <= mesh->zend; zk++) { #endif if (fg) { val = fg->generate(Context(bndry, zk, loc, t, mesh)); @@ -1948,7 +1946,7 @@ void BoundaryNeumann_NonOrthogonal::apply(Field3D& f) { f(bndry->x - bndry->bx, bndry->y - bndry->by, zk) + delta * val; if (bndry->width == 2) { f(bndry->x + bndry->bx, bndry->y + bndry->by, zk) = - f(bndry->x, bndry->y, zk) + delta * val; + f(bndry->x, bndry->y, zk) + delta * val; } } } From 370aa694a359156cc27d2aa171abf1fca6a982a2 Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 8 Jul 2026 11:09:05 +0200 Subject: [PATCH 005/221] Next version for next will be 6.0.0 --- CMakeLists.txt | 2 +- tools/pylib/_boutpp_build/backend.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ac9b32a127..a397c3e979 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,7 +31,7 @@ endif() # Set the version here, strip any extra tags to use in `project` # We try to use git to get a full description, inspired by setuptools_scm set(_bout_previous_version "5.2.0") -set(_bout_next_version "5.3.0") +set(_bout_next_version "6.0.0") execute_process( COMMAND "git" describe --tags --match=v${_bout_previous_version} COMMAND sed -e s/${_bout_previous_version}-/${_bout_next_version}.dev/ -e diff --git a/tools/pylib/_boutpp_build/backend.py b/tools/pylib/_boutpp_build/backend.py index 824e7552ad..7037c6b07e 100755 --- a/tools/pylib/_boutpp_build/backend.py +++ b/tools/pylib/_boutpp_build/backend.py @@ -36,7 +36,7 @@ def getversion(): return version.lstrip("v") _bout_previous_version = "v5.2.0" - _bout_next_version = "v5.3.0" + _bout_next_version = "v6.0.0" try: try: From 427aa210ce30fe54b39d8ce830b30d378b0d6314 Mon Sep 17 00:00:00 2001 From: David Bold Date: Thu, 9 Jul 2026 14:25:24 +0200 Subject: [PATCH 006/221] Remove \\ at end of comments This was partially broken by clang-format. It also make the compiler unhappy, that warns about that. --- src/invert/laplace/impls/naulin/naulin_laplace.hxx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/invert/laplace/impls/naulin/naulin_laplace.hxx b/src/invert/laplace/impls/naulin/naulin_laplace.hxx index edcc562c99..a94d855034 100644 --- a/src/invert/laplace/impls/naulin/naulin_laplace.hxx +++ b/src/invert/laplace/impls/naulin/naulin_laplace.hxx @@ -5,7 +5,7 @@ /// \f{eqnarray} /// \nabla^2(\phi[i+1]) /// + 1/DC(C_1 D)\nabla_\perp(DC(C_2))\nabla_\perp(\phi[i+1]) -/// + DC(A/D)\phi[i+1] \\ +/// + DC(A/D)\phi[i+1] /// = rhs(\phi[i]) /// + 1/DC(C_1 D)\nabla_\perp(DC(C_2))\nabla_\perp(\phi[i]) /// + DC(A/D)\phi[i] @@ -28,16 +28,16 @@ /// In an orthogonal system, we have that: /// /// \f{eqnarray}{ -/// \Omega^D &=& \nabla\cdot(n\nabla_\perp \phi)\ \ -/// &=& n \nabla_\perp^2 \phi + \nabla n\cdot\nabla_\perp \phi\\ -/// &=& n \Omega + \nabla n\cdot\nabla_\perp \phi\\ +/// \Omega^D &=& \nabla\cdot(n\nabla_\perp \phi) +/// &=& n \nabla_\perp^2 \phi + \nabla n\cdot\nabla_\perp \phi +/// &=& n \Omega + \nabla n\cdot\nabla_\perp \phi /// &=& n \Omega + \nabla_\perp n\cdot\nabla_\perp \phi /// \f} /// /// Rearranging gives /// /// \f{eqnarray}{ -/// \Omega &=& \frac{\Omega^D}{n} - \nabla_\perp \ln(n)\cdot\nabla_\perp \phi\ \ +/// \Omega &=& \frac{\Omega^D}{n} - \nabla_\perp \ln(n)\cdot\nabla_\perp \phi /// \nabla_\perp^2 \phi /// &=& \frac{\Omega^D}{n} - \nabla_\perp \ln(n)\cdot\nabla_\perp \phi /// \f} From 9df3238af83907d72d9a87278f566a3e517e9989 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Mon, 11 May 2026 13:53:01 +0100 Subject: [PATCH 007/221] Major `Coordinates` refactor Refactor `Coordinates` with a more encapsulated interface. Derived quantities are now automatically updated when the metric components are changed. The most impactful change is the conversion of the metric components and grid sizes to `const&` methods: ```diff - coords.dx + coords.dx() ``` Among other things, this change allows us to defer computation of many of the derived quantities (such as Christoffel symbols) and remove the `Coordinates::geometry` method. --- CHANGELOG.md | 16 + CMakeLists.txt | 26 +- examples/6field-simple/elm_6f.cxx | 10 +- .../IMEX/drift-wave-constraint/test-drift.cxx | 6 +- examples/conducting-wall-mode/cwm.cxx | 6 +- .../elm-pb-outerloop/elm_pb_outerloop.cxx | 10 +- examples/elm-pb/elm_pb.cxx | 10 +- examples/fci-wave/fci-wave.cxx | 2 +- examples/gyro-gem/gem.cxx | 10 +- examples/laplace-petsc3d/test-laplace3d.cxx | 14 +- include/bout/christoffel_symbols.hxx | 63 + include/bout/coordinates.hxx | 326 ++++- include/bout/field.hxx | 3 + include/bout/field3d.hxx | 7 +- include/bout/fv_ops_impl.hxx | 78 +- include/bout/g_values.hxx | 28 + include/bout/mesh.hxx | 20 +- include/bout/metric_tensor.hxx | 104 ++ include/bout/parallel_boundary_op.hxx | 6 +- include/bout/paralleltransform.hxx | 4 - include/bout/tokamak_coordinates.hxx | 17 +- src/field/vecops.cxx | 128 +- src/field/vector2d.cxx | 70 +- src/field/vector3d.cxx | 94 +- .../laplace/impls/hypre3d/hypre3d_laplace.cxx | 90 +- .../laplace/impls/naulin/naulin_laplace.cxx | 4 +- .../laplace/impls/petsc/petsc_laplace.cxx | 51 +- .../laplace/impls/petsc3damg/petsc3damg.cxx | 82 +- src/invert/laplace/invert_laplace.cxx | 2 +- .../laplacexy/impls/hypre/laplacexy-hypre.cxx | 44 +- .../laplacexy/impls/petsc/laplacexy-petsc.cxx | 36 +- .../impls/petsc2/laplacexy-petsc2.cxx | 44 +- .../impls/cyclic/laplacexz-cyclic.cxx | 2 +- src/invert/parderiv/impls/cyclic/cyclic.cxx | 14 +- .../pardiv/impls/cyclic/pardiv_cyclic.cxx | 6 +- src/mesh/boundary_standard.cxx | 10 +- src/mesh/christoffel_symbols.cxx | 116 ++ src/mesh/coordinates.cxx | 1243 +++++++---------- src/mesh/coordinates_accessor.cxx | 24 +- src/mesh/difops.cxx | 82 +- src/mesh/fv_ops.cxx | 36 +- src/mesh/g_values.cxx | 34 + src/mesh/mesh.cxx | 48 +- src/mesh/metric_tensor.cxx | 154 ++ src/mesh/parallel/fci.cxx | 94 +- src/mesh/parallel/fci.hxx | 2 - src/mesh/petsc_operators.cxx | 4 +- src/mesh/tokamak_coordinates.cxx | 55 +- src/physics/smoothing.cxx | 18 +- src/sys/derivs.cxx | 106 +- tests/MMS/GBS/gbs.cxx | 27 +- tests/MMS/advection/advection.cxx | 4 +- tests/MMS/diffusion/diffusion.cxx | 20 +- tests/MMS/diffusion2/diffusion.cxx | 21 +- tests/MMS/elm-pb/elm_pb.cxx | 8 +- tests/MMS/fieldalign/fieldalign.cxx | 14 +- tests/MMS/hw/hw.cxx | 4 +- tests/MMS/laplace/laplace.cxx | 4 +- tests/MMS/spatial/diffusion/diffusion.cxx | 21 +- tests/MMS/tokamak/tokamak.cxx | 4 +- tests/MMS/wave-1d/wave.cxx | 25 +- .../test-drift-instability/2fluid.cxx | 4 +- .../test-interchange-instability/2fluid.cxx | 4 +- .../test-laplacexy-short/test-laplacexy.cxx | 6 +- .../test-laplacexy/test-laplacexy.cxx | 4 +- .../test-laplacexz/test-laplacexz.cxx | 31 +- .../test_multigrid_laplace.cxx | 6 +- .../test_naulin_laplace.cxx | 5 +- .../test_petsc_operators.cxx | 1 - tests/integrated/test-snb/test_snb.cxx | 16 +- tests/unit/fake_mesh_fixture.hxx | 62 +- tests/unit/fake_parallel_mesh.hxx | 1 - tests/unit/field/test_field_factory.cxx | 2 - tests/unit/field/test_vector2d.cxx | 1 - tests/unit/field/test_vector3d.cxx | 1 - .../unit/include/bout/test_petsc_indexer.cxx | 1 - .../invert/laplace/test_laplace_cyclic.cxx | 7 +- .../invert/laplace/test_laplace_hypre3d.cxx | 11 +- .../laplace/test_laplace_petsc3damg.cxx | 11 +- tests/unit/mesh/data/test_gridfromoptions.cxx | 120 +- .../unit/mesh/parallel/test_shiftedmetric.cxx | 1 - tests/unit/mesh/test_coordinates.cxx | 451 ++++-- tests/unit/mesh/test_coordinates_accessor.cxx | 185 +-- tests/unit/mesh/test_interpolation.cxx | 1 - tools/pylib/_boutpp_build/boutcpp.pxd.jinja | 55 +- tools/pylib/_boutpp_build/boutpp.pyx.jinja | 2 +- tools/pylib/_boutpp_build/helper.cxx.jinja | 7 +- 87 files changed, 2664 insertions(+), 1943 deletions(-) create mode 100644 include/bout/christoffel_symbols.hxx create mode 100644 include/bout/g_values.hxx create mode 100644 include/bout/metric_tensor.hxx create mode 100644 src/mesh/christoffel_symbols.cxx create mode 100644 src/mesh/g_values.cxx create mode 100644 src/mesh/metric_tensor.cxx diff --git a/CHANGELOG.md b/CHANGELOG.md index 41e3e2c47b..a0cbfd5915 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## [v6.0.0](https://github.com/boutproject/BOUT-dev/tree/v6.0.0 + +[Full Changelog](https://github.com/boutproject/BOUT-dev/compare/v5.2.0...) + +### Breaking changes + +- `Coordinates` has been refactored to with a more encapsulated interface. This + allows us to ensure that derived quantities are correctly updated when the + metric components are changed, as well as defer creation of the Christoffel + symbols, and other derivatives of the metric components. This change will + require most physics models to be updated. You can do this with the + `bout-upgrader` from `boutdata`: + ```console + $ bout-upgrader v6 v6_upgrader file/to/update.cxx + ```` + ## [v5.2.0](https://github.com/boutproject/BOUT-dev/tree/v5.2.0 [Full Changelog](https://github.com/boutproject/BOUT-dev/compare/v5.1.1...v5.2.0) diff --git a/CMakeLists.txt b/CMakeLists.txt index e6e424b55c..689c668264 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -116,10 +116,11 @@ set(BOUT_SOURCES ./include/bout/bout.hxx ./include/bout/bout_enum_class.hxx ./include/bout/bout_types.hxx - ./include/bout/build_config.hxx ./include/bout/boutcomm.hxx ./include/bout/boutexception.hxx + ./include/bout/build_config.hxx ./include/bout/caliper_wrapper.hxx + ./include/bout/christoffel_symbols.hxx ./include/bout/constants.hxx ./include/bout/coordinates.hxx ./include/bout/coordinates_accessor.hxx @@ -136,18 +137,21 @@ set(BOUT_SOURCES ./include/bout/field_accessor.hxx ./include/bout/field_data.hxx ./include/bout/field_factory.hxx - ./include/bout/fieldops.hxx ./include/bout/fieldgroup.hxx + ./include/bout/fieldops.hxx ./include/bout/fieldperp.hxx ./include/bout/fv_ops.hxx ./include/bout/fv_ops_impl.hxx + ./include/bout/g_values.hxx ./include/bout/generic_factory.hxx + ./include/bout/git_metadata.hxx ./include/bout/globalfield.hxx ./include/bout/globalindexer.hxx ./include/bout/globals.hxx ./include/bout/griddata.hxx ./include/bout/gyro_average.hxx ./include/bout/hypre_interface.hxx + ./include/bout/hyprelib.hxx ./include/bout/index_derivs.hxx ./include/bout/index_derivs_interface.hxx ./include/bout/initialprofiles.hxx @@ -164,6 +168,7 @@ set(BOUT_SOURCES ./include/bout/macro_for_each.hxx ./include/bout/mask.hxx ./include/bout/mesh.hxx + ./include/bout/metric_tensor.hxx ./include/bout/monitor.hxx ./include/bout/mpi_wrapper.hxx ./include/bout/msg_stack.hxx @@ -216,8 +221,8 @@ set(BOUT_SOURCES ./include/bout/vector2d.hxx ./include/bout/vector3d.hxx ./include/bout/where.hxx - ./src/bout++.cxx ./src/bout++-time.hxx + ./src/bout++.cxx ./src/field/field.cxx ./src/field/field2d.cxx ./src/field/field3d.cxx @@ -240,6 +245,8 @@ set(BOUT_SOURCES ./src/invert/laplace/common_transform.hxx ./src/invert/laplace/impls/cyclic/cyclic_laplace.cxx ./src/invert/laplace/impls/cyclic/cyclic_laplace.hxx + ./src/invert/laplace/impls/hypre3d/hypre3d_laplace.cxx + ./src/invert/laplace/impls/hypre3d/hypre3d_laplace.hxx ./src/invert/laplace/impls/iterative_parallel_tri/iterative_parallel_tri.cxx ./src/invert/laplace/impls/iterative_parallel_tri/iterative_parallel_tri.hxx ./src/invert/laplace/impls/multigrid/multigrid_alg.cxx @@ -262,8 +269,6 @@ set(BOUT_SOURCES ./src/invert/laplace/impls/serial_tri/serial_tri.hxx ./src/invert/laplace/impls/spt/spt.cxx ./src/invert/laplace/impls/spt/spt.hxx - ./src/invert/laplace/impls/hypre3d/hypre3d_laplace.cxx - ./src/invert/laplace/impls/hypre3d/hypre3d_laplace.hxx ./src/invert/laplace/invert_laplace.cxx ./src/invert/laplacexy/impls/hypre/laplacexy-hypre.cxx ./src/invert/laplacexy/impls/hypre/laplacexy-hypre.hxx @@ -286,23 +291,26 @@ set(BOUT_SOURCES ./src/mesh/boundary_factory.cxx ./src/mesh/boundary_region.cxx ./src/mesh/boundary_standard.cxx + ./src/mesh/christoffel_symbols.cxx ./src/mesh/coordinates.cxx ./src/mesh/coordinates_accessor.cxx ./src/mesh/data/gridfromfile.cxx ./src/mesh/data/gridfromoptions.cxx ./src/mesh/difops.cxx ./src/mesh/fv_ops.cxx + ./src/mesh/g_values.cxx ./src/mesh/impls/bout/boutmesh.cxx ./src/mesh/impls/bout/boutmesh.hxx ./src/mesh/index_derivs.cxx - ./src/mesh/interpolation_xz.cxx ./src/mesh/interpolation/bilinear_xz.cxx ./src/mesh/interpolation/hermite_spline_xz.cxx ./src/mesh/interpolation/hermite_spline_z.cxx ./src/mesh/interpolation/interpolation_z.cxx ./src/mesh/interpolation/lagrange_4pt_xz.cxx + ./src/mesh/interpolation_xz.cxx ./src/mesh/invert3x3.hxx ./src/mesh/mesh.cxx + ./src/mesh/metric_tensor.cxx ./src/mesh/parallel/fci.cxx ./src/mesh/parallel/fci.hxx ./src/mesh/parallel/fci_comm.cxx @@ -369,18 +377,17 @@ set(BOUT_SOURCES ./src/sys/derivs.cxx ./src/sys/expressionparser.cxx ./src/sys/generator_context.cxx - ./include/bout/hyprelib.hxx ./src/sys/hyprelib.cxx ./src/sys/msg_stack.cxx ./src/sys/options.cxx ./src/sys/options/optionparser.hxx + ./src/sys/options/options_adios.cxx + ./src/sys/options/options_adios.hxx ./src/sys/options/options_ini.cxx ./src/sys/options/options_ini.hxx ./src/sys/options/options_io.cxx ./src/sys/options/options_netcdf.cxx ./src/sys/options/options_netcdf.hxx - ./src/sys/options/options_adios.cxx - ./src/sys/options/options_adios.hxx ./src/sys/optionsreader.cxx ./src/sys/output.cxx ./src/sys/output_bout_types.cxx @@ -390,7 +397,6 @@ set(BOUT_SOURCES ./src/sys/timer.cxx ./src/sys/type_name.cxx ./src/sys/utils.cxx - ./include/bout/git_metadata.hxx ${CMAKE_CURRENT_BINARY_DIR}/include/bout/revision.hxx ${CMAKE_CURRENT_BINARY_DIR}/include/bout/version.hxx ) diff --git a/examples/6field-simple/elm_6f.cxx b/examples/6field-simple/elm_6f.cxx index 7208d89ddf..61b98c2836 100644 --- a/examples/6field-simple/elm_6f.cxx +++ b/examples/6field-simple/elm_6f.cxx @@ -356,7 +356,7 @@ class Elm_6f : public PhysicsModel { result.allocate(); for (auto i : result) { result[i] = - (fp[i.yp()] - fm[i.ym()]) / (2. * coord->dy[i] * sqrt(coord->g_22[i])); + (fp[i.yp()] - fm[i.ym()]) / (2. * coord->dy()[i] * sqrt(coord->g_22()[i])); } } else { result = Grad_par(f, loc); @@ -696,7 +696,7 @@ class Elm_6f : public PhysicsModel { if (mesh->IncIntShear) { // BOUT-06 style, using d/dx = d/dpsi + I * d/dz - mesh->getCoordinates()->IntShiftTorsion = I; + mesh->getCoordinates()->setIntShiftTorsion(I); } else { // Dimits style, using local coordinate system if (include_curvature) { @@ -1399,11 +1399,11 @@ class Elm_6f : public PhysicsModel { if (hyperviscos > 0.0) { // Calculate coefficient. - hyper_mu_x = hyperviscos * coord->g_11 * SQ(coord->dx) - * abs(coord->g11 * D2DX2(U)) / (abs(U) + 1e-3); + hyper_mu_x = hyperviscos * coord->g_11() * SQ(coord->dx()) + * abs(coord->g11() * D2DX2(U)) / (abs(U) + 1e-3); hyper_mu_x.applyBoundary("dirichlet"); // Set to zero on all boundaries - ddt(U) += hyper_mu_x * coord->g11 * D2DX2(U); + ddt(U) += hyper_mu_x * coord->g11() * D2DX2(U); if (first_run) { // Print out maximum values of viscosity used on this processor diff --git a/examples/IMEX/drift-wave-constraint/test-drift.cxx b/examples/IMEX/drift-wave-constraint/test-drift.cxx index e3bf88acfc..ddeb5ef9d4 100644 --- a/examples/IMEX/drift-wave-constraint/test-drift.cxx +++ b/examples/IMEX/drift-wave-constraint/test-drift.cxx @@ -61,10 +61,10 @@ class DriftWave : public PhysicsModel { // This code results in ddt(Ne) depending on y+2, y-2 // which are not (currently) included in the coloring // The result is that IMEX-BDF2 with coloring doesn't converge - + Ve = ( Grad_par(phi) - Grad_par(Ne) ) / nu; mesh->communicate(Ve); - + ddt(Ne) = -Div_par(Ve); ddt(Vort) = -Div_par(Ve); */ @@ -82,7 +82,7 @@ class DriftWave : public PhysicsModel { // ddt(phi) = Delp2(phi) - Vort; // This version uses central differencing for Delp2 - ddt(phi) = (coord->g11 * D2DX2(phi) + coord->g33 * D2DZ2(phi)) - Vort; + ddt(phi) = (coord->g11() * D2DX2(phi) + coord->g33() * D2DZ2(phi)) - Vort; return 0; } diff --git a/examples/conducting-wall-mode/cwm.cxx b/examples/conducting-wall-mode/cwm.cxx index b302b5dfb3..1eda76c493 100644 --- a/examples/conducting-wall-mode/cwm.cxx +++ b/examples/conducting-wall-mode/cwm.cxx @@ -319,7 +319,7 @@ class CWM : public PhysicsModel { result = VDDX(DDZ(p), f); } else { // Use full expression with all terms - result = b0xGrad_dot_Grad(p, f) / coord->Bxy; + result = b0xGrad_dot_Grad(p, f) / coord->Bxy(); } return result; } @@ -331,7 +331,7 @@ class CWM : public PhysicsModel { result = VDDZ(-DDX(p), f); } else { // Use full expression with all terms - result = b0xGrad_dot_Grad(p, f) / coord->Bxy; + result = b0xGrad_dot_Grad(p, f) / coord->Bxy(); } return result; } @@ -343,7 +343,7 @@ class CWM : public PhysicsModel { result = VDDX(DDZ(p), f) + VDDZ(-DDX(p), f); } else { // Use full expression with all terms - result = b0xGrad_dot_Grad(p, f) / coord->Bxy; + result = b0xGrad_dot_Grad(p, f) / coord->Bxy(); } return result; } diff --git a/examples/elm-pb-outerloop/elm_pb_outerloop.cxx b/examples/elm-pb-outerloop/elm_pb_outerloop.cxx index dbad416281..54c3faf3e0 100644 --- a/examples/elm-pb-outerloop/elm_pb_outerloop.cxx +++ b/examples/elm-pb-outerloop/elm_pb_outerloop.cxx @@ -819,7 +819,7 @@ class ELMpb : public PhysicsModel { if (mesh->IncIntShear) { // BOUT-06 style, using d/dx = d/dpsi + I * d/dz - mesh->getCoordinates()->IntShiftTorsion = I; + mesh->getCoordinates()->setIntShiftTorsion(I); } else { // Dimits style, using local coordinate system if (include_curvature) { @@ -1749,11 +1749,11 @@ class ELMpb : public PhysicsModel { if (hyperviscos > 0.0) { // Calculate coefficient. - hyper_mu_x = hyperviscos * metric->g_11 * SQ(metric->dx) - * abs(metric->g11 * D2DX2(U)) / (abs(U) + 1e-3); + hyper_mu_x = hyperviscos * metric->g_11() * SQ(metric->dx()) + * abs(metric->g11() * D2DX2(U)) / (abs(U) + 1e-3); hyper_mu_x.applyBoundary("dirichlet"); // Set to zero on all boundaries - ddt(U) += hyper_mu_x * metric->g11 * D2DX2(U); + ddt(U) += hyper_mu_x * metric->g11() * D2DX2(U); if (first_run) { // Print out maximum values of viscosity used on this processor output.write(" Hyper-viscosity values:\n"); @@ -1865,7 +1865,7 @@ class ELMpb : public PhysicsModel { BoutReal pnorm = P0(0, 0); ddt(P) += heating_P * source_expx2(P0, 2. * hp_width, 0.5 * hp_length) * (Tbar / pnorm); // heat source - ddt(P) += (100. * source_tanhx(P0, hp_width, hp_length) + 0.01) * metric->g11 + ddt(P) += (100. * source_tanhx(P0, hp_width, hp_length) + 0.01) * metric->g11() * D2DX2(P) * (Tbar / Lbar / Lbar); // radial diffusion } diff --git a/examples/elm-pb/elm_pb.cxx b/examples/elm-pb/elm_pb.cxx index 62cc970869..e4bd1bbee3 100644 --- a/examples/elm-pb/elm_pb.cxx +++ b/examples/elm-pb/elm_pb.cxx @@ -775,7 +775,7 @@ class ELMpb : public PhysicsModel { if (mesh->IncIntShear) { // BOUT-06 style, using d/dx = d/dpsi + I * d/dz - mesh->getCoordinates()->IntShiftTorsion = I; + mesh->getCoordinates()->setIntShiftTorsion(I); } else { // Dimits style, using local coordinate system if (include_curvature) { @@ -1772,11 +1772,11 @@ class ELMpb : public PhysicsModel { if (hyperviscos > 0.0) { // Calculate coefficient. - hyper_mu_x = hyperviscos * metric->g_11 * SQ(metric->dx) - * abs(metric->g11 * D2DX2(U)) / (abs(U) + 1e-3); + hyper_mu_x = hyperviscos * metric->g_11() * SQ(metric->dx()) + * abs(metric->g11() * D2DX2(U)) / (abs(U) + 1e-3); hyper_mu_x.applyBoundary("dirichlet"); // Set to zero on all boundaries - ddt(U) += hyper_mu_x * metric->g11 * D2DX2(U); + ddt(U) += hyper_mu_x * metric->g11() * D2DX2(U); if (first_run) { // Print out maximum values of viscosity used on this processor output.write(" Hyper-viscosity values:\n"); @@ -1914,7 +1914,7 @@ class ELMpb : public PhysicsModel { BoutReal pnorm = P0(0, 0); ddt(P) += heating_P * source_expx2(P0, 2. * hp_width, 0.5 * hp_length) * (Tbar / pnorm); // heat source - ddt(P) += (100. * source_tanhx(P0, hp_width, hp_length) + 0.01) * metric->g11 + ddt(P) += (100. * source_tanhx(P0, hp_width, hp_length) + 0.01) * metric->g11() * D2DX2(P) * (Tbar / Lbar / Lbar); // radial diffusion } diff --git a/examples/fci-wave/fci-wave.cxx b/examples/fci-wave/fci-wave.cxx index 9f8bb18b8f..0ea61f3e8d 100644 --- a/examples/fci-wave/fci-wave.cxx +++ b/examples/fci-wave/fci-wave.cxx @@ -45,7 +45,7 @@ class FCIwave : public PhysicsModel { for (auto i : result.getRegion(RGN_NOBNDRY)) { result[i] = Bxyz[i] * (f_B.yup()[i.yp()] - f_B.ydown()[i.ym()]) - / (2. * coord->dy[i] * sqrt(coord->g_22[i])); + / (2. * coord->dy()[i] * sqrt(coord->g_22()[i])); if (!finite(result[i])) { output.write("[{:d},{:d},{:d}]: {:e}, {:e} -> {:e}\n", i.x(), i.y(), i.z(), diff --git a/examples/gyro-gem/gem.cxx b/examples/gyro-gem/gem.cxx index f4347ca2b3..4e5e8538c3 100644 --- a/examples/gyro-gem/gem.cxx +++ b/examples/gyro-gem/gem.cxx @@ -363,7 +363,7 @@ class GEM : public PhysicsModel { if (curv_logB) { Grad_par_logB = Grad_par(logB); } else { - Grad_par_logB = Grad_par(log(coord->Bxy)); + Grad_par_logB = Grad_par(log(coord->Bxy())); } } else { Grad_par_logB = 0.; @@ -1116,7 +1116,7 @@ class GEM : public PhysicsModel { if (curv_logB) { return -bracket(2. * logB, f, BRACKET_ARAKAWA); } - return -bracket(2. * log(coord->Bxy), f, BRACKET_ARAKAWA); + return -bracket(2. * log(coord->Bxy()), f, BRACKET_ARAKAWA); } //////////////////////////////////////////////////////////////////////// @@ -1131,7 +1131,7 @@ class GEM : public PhysicsModel { delp2.applyBoundary("neumann"); mesh->communicate(delp2); - return nu_perp * Delp2(delp2 * SQ(SQ(1. / coord->Bxy))) + return nu_perp * Delp2(delp2 * SQ(SQ(1. / coord->Bxy()))) - nu_par * Grad2_par2(f) // NB: This should be changed for variable B ; } @@ -1147,8 +1147,8 @@ class GEM : public PhysicsModel { } const Field3D Div_parP(const Field3D& f, CELL_LOC loc = CELL_DEFAULT) { - return interp_to(coord->Bxy, loc) - * Grad_parP(f / interp_to(coord->Bxy, f.getLocation()), loc); + return interp_to(coord->Bxy(), loc) + * Grad_parP(f / interp_to(coord->Bxy(), f.getLocation()), loc); } }; diff --git a/examples/laplace-petsc3d/test-laplace3d.cxx b/examples/laplace-petsc3d/test-laplace3d.cxx index 46bfce7859..9f21a69be1 100644 --- a/examples/laplace-petsc3d/test-laplace3d.cxx +++ b/examples/laplace-petsc3d/test-laplace3d.cxx @@ -36,13 +36,13 @@ Field3D this_Laplace_perp(const Field3D& f) { // dfdy not divided by dy yet auto dfdy = bout::derivatives::index::DDY(f, CELL_DEFAULT, "DEFAULT", "RGN_NOY"); - return coords->G1 * DDX(f) - + (coords->G2 - DDY(coords->J / coords->g_22) / coords->J) * DDY(f) - + coords->G3 * DDZ(f) + coords->g11 * D2DX2(f) - + (coords->g22 - 1. / coords->g_22) * D2DY2(f) + coords->g33 * D2DZ2(f) + return coords->G1() * DDX(f) + + (coords->G2() - DDY(coords->J() / coords->g_22()) / coords->J()) * DDY(f) + + coords->G3() * DDZ(f) + coords->g11() * D2DX2(f) + + (coords->g22() - 1. / coords->g_22()) * D2DY2(f) + coords->g33() * D2DZ2(f) + 2. - * (coords->g12 * DDX(dfdy) / coords->dy + coords->g13 * D2DXDZ(f) - + coords->g23 * D2DYDZ(f)); + * (coords->g12() * DDX(dfdy) / coords->dy() + coords->g13() * D2DXDZ(f) + + coords->g23() * D2DYDZ(f)); } int main(int argc, char** argv) { @@ -136,7 +136,7 @@ int main(int argc, char** argv) { /////////////////////////////////////////////////////////////////////////////////////// // Calculate error /////////////////////////////////////////////////////////////////////////////////////// - auto& g_22 = mesh->getCoordinates()->g_22; + auto& g_22 = mesh->getCoordinates()->g_22(); Field3D rhs_check = D * this_Laplace_perp(f) + (Grad(f) * Grad(C2) - DDY(C2) * DDY(f) / g_22) / C1 + A * f; // The usual way to do this would be diff --git a/include/bout/christoffel_symbols.hxx b/include/bout/christoffel_symbols.hxx new file mode 100644 index 0000000000..db88b8688e --- /dev/null +++ b/include/bout/christoffel_symbols.hxx @@ -0,0 +1,63 @@ +#ifndef BOUT_CHRISTOFFELSYMBOLS_HXX +#define BOUT_CHRISTOFFELSYMBOLS_HXX + +#include + +class Coordinates; + +class ChristoffelSymbols { + +public: + explicit ChristoffelSymbols(const Coordinates& coordinates); + + const bout::FieldMetric& G1_11() const { return G1_11_m; } + const bout::FieldMetric& G1_22() const { return G1_22_m; } + const bout::FieldMetric& G1_33() const { return G1_33_m; } + const bout::FieldMetric& G1_12() const { return G1_12_m; } + const bout::FieldMetric& G1_13() const { return G1_13_m; } + const bout::FieldMetric& G1_23() const { return G1_23_m; } + + const bout::FieldMetric& G2_11() const { return G2_11_m; } + const bout::FieldMetric& G2_22() const { return G2_22_m; } + const bout::FieldMetric& G2_33() const { return G2_33_m; } + const bout::FieldMetric& G2_12() const { return G2_12_m; } + const bout::FieldMetric& G2_13() const { return G2_13_m; } + const bout::FieldMetric& G2_23() const { return G2_23_m; } + + const bout::FieldMetric& G3_11() const { return G3_11_m; } + const bout::FieldMetric& G3_22() const { return G3_22_m; } + const bout::FieldMetric& G3_33() const { return G3_33_m; } + const bout::FieldMetric& G3_12() const { return G3_12_m; } + const bout::FieldMetric& G3_13() const { return G3_13_m; } + const bout::FieldMetric& G3_23() const { return G3_23_m; } + + // Transforms the ChristoffelSymbols by applying the given function to every element + template + void map(F function) { + G1_11_m = function(G1_11_m); + G1_22_m = function(G1_22_m); + G1_33_m = function(G1_33_m); + G1_12_m = function(G1_12_m); + G1_13_m = function(G1_13_m); + G1_23_m = function(G1_23_m); + G2_11_m = function(G2_11_m); + G2_22_m = function(G2_22_m); + G2_33_m = function(G2_33_m); + G2_12_m = function(G2_12_m); + G2_13_m = function(G2_13_m); + G2_23_m = function(G2_23_m); + G3_11_m = function(G3_11_m); + G3_22_m = function(G3_22_m); + G3_33_m = function(G3_33_m); + G3_12_m = function(G3_12_m); + G3_13_m = function(G3_13_m); + G3_23_m = function(G3_23_m); + } + +private: + bout::FieldMetric G1_11_m, G1_22_m, G1_33_m, G1_12_m, G1_13_m, G1_23_m; + bout::FieldMetric G2_11_m, G2_22_m, G2_33_m, G2_12_m, G2_13_m, G2_23_m; + bout::FieldMetric G3_11_m, G3_22_m, G3_33_m, G3_12_m, G3_13_m, G3_23_m; +}; + +#endif //BOUT_CHRISTOFFELSYMBOLS_HXX diff --git a/include/bout/coordinates.hxx b/include/bout/coordinates.hxx index 2c33701762..2c73828060 100644 --- a/include/bout/coordinates.hxx +++ b/include/bout/coordinates.hxx @@ -30,30 +30,29 @@ #include "bout/field_data.hxx" #include #include +#include #include #include +#include +#include #include #include #include #include #include +#include +#include class Mesh; class YBoundary; /*! * Represents a coordinate system, and associated operators - * - * This is a container for a collection of metric tensor components */ class Coordinates { public: -#if BOUT_USE_METRIC_3D - using FieldMetric = Field3D; -#else - using FieldMetric = Field2D; -#endif + using FieldMetric = bout::FieldMetric; /// Standard constructor from input Coordinates(Mesh* mesh, Options* options = nullptr); @@ -64,8 +63,8 @@ public: /// force_interpolate_from_centre argument to true to always interpolate /// (useful if CELL_CENTRE Coordinates have been changed, so reading from file /// would not be correct). - Coordinates(Mesh* mesh, Options* options, const CELL_LOC loc, - const Coordinates* coords_in, bool force_interpolate_from_centre = false); + Coordinates(Mesh* mesh, Options* options, CELL_LOC loc, const Coordinates* coords_in, + bool force_interpolate_from_centre = false); /// A constructor useful for testing purposes. To use it, inherit /// from Coordinates. If \p calculate_geometry is true (default), @@ -76,32 +75,81 @@ public: FieldMetric g_22, FieldMetric g_33, FieldMetric g_12, FieldMetric g_13, FieldMetric g_23, FieldMetric ShiftTorsion, FieldMetric IntShiftTorsion); - Coordinates& operator=(Coordinates&&) = default; - - ~Coordinates() = default; - /// Add variables to \p output_options, for post-processing void outputVars(Options& output_options); - FieldMetric dx, dy, dz; ///< Mesh spacing in x, y and z + ///< Mesh spacing in x, y and z + const FieldMetric& dx() const { return dx_; } + const FieldMetric& dy() const { return dy_; } + const FieldMetric& dz() const { return dz_; } + + const BoutReal& dx(int x, int y, int z) const { return dx_(x, y, z); } + const BoutReal& dy(int x, int y, int z) const { return dy_(x, y, z); } + const BoutReal& dz(int x, int y, int z) const { return dz_(x, y, z); } + +#if BOUT_USE_METRIC_3D + const BoutReal* dx(int x, int y) const { return dx_(x, y); } + const BoutReal* dy(int x, int y) const { return dy_(x, y); } + const BoutReal* dz(int x, int y) const { return dz_(x, y); } +#else + const BoutReal& dx(int x, int y) const { return dx_(x, y); } + const BoutReal& dy(int x, int y) const { return dy_(x, y); } + const BoutReal& dz(int x, int y) const { return dz_(x, y); } +#endif + + const BoutReal& IntShiftTorsion(int x, int y, int z) const { + return IntShiftTorsion_(x, y, z); + } + +#if not(BOUT_USE_METRIC_3D) + const BoutReal& IntShiftTorsion(int x, int y) const { return IntShiftTorsion_(x, y); } +#endif + + const BoutReal& J(int x, int y, int z) const { return J()(x, y, z); } + +#if not(BOUT_USE_METRIC_3D) + const BoutReal& J(int x, int y) const { return J()(x, y); } +#endif + + void setDx(FieldMetric dx, bool communicate = true); + void setDy(FieldMetric dy, bool communicate = true); + void setDz(FieldMetric dz, bool communicate = true); + + void setD1_dx(FieldMetric d1_dx) { d1_dx_ = std::move(d1_dx); } + void setD1_dy(FieldMetric d1_dy) { d1_dy_ = std::move(d1_dy); } + void setD1_dz(FieldMetric d1_dz) { d1_dz_ = std::move(d1_dz); } /// Length of the Z domain. Used for FFTs const Field2D& zlength() const; - /// True if corrections for non-uniform mesh spacing should be included in operators - bool non_uniform; - /// 2nd-order correction for non-uniform meshes d/di(1/dx), d/di(1/dy) and d/di(1/dz) - FieldMetric d1_dx, d1_dy, d1_dz; + const BoutReal& zlength(int x, int y) const { return zlength()(x, y); } - FieldMetric J; ///< Coordinate system Jacobian, so volume of cell is J*dx*dy*dz + /// True if corrections for non-uniform mesh spacing should be included in operators + bool non_uniform() const { return non_uniform_; } + void setNon_uniform(bool non_uniform) { non_uniform_ = non_uniform; } - FieldMetric Bxy; ///< Magnitude of B = nabla z times nabla x + /// 2nd-order correction for non-uniform meshes d/di(1/dx), d/di(1/dy) and d/di(1/dz) + const FieldMetric& d1_dx() const { return d1_dx_; } + const FieldMetric& d1_dy() const { return d1_dy_; } + const FieldMetric& d1_dz() const { return d1_dz_; } - /// Contravariant metric tensor (g^{ij}) - FieldMetric g11, g22, g33, g12, g13, g23; +#if BOUT_USE_METRIC_3D + const BoutReal& d1_dx(int x, int y, int z) const { return d1_dx_(x, y, z); } + const BoutReal& d1_dy(int x, int y, int z) const { return d1_dy_(x, y, z); } + const BoutReal& d1_dz(int x, int y, int z) const { return d1_dz_(x, y, z); } +#else + const BoutReal& d1_dx(int x, int y) const { return d1_dx_(x, y); } + const BoutReal& d1_dy(int x, int y) const { return d1_dy_(x, y); } + const BoutReal& d1_dz(int x, int y) const { return d1_dz_(x, y); } +#endif /// Covariant metric tensor - FieldMetric g_11, g_22, g_33, g_12, g_13, g_23; + const FieldMetric& g_11() const { return covariantMetricTensor.g11(); } + const FieldMetric& g_22() const { return covariantMetricTensor.g22(); } + const FieldMetric& g_33() const { return covariantMetricTensor.g33(); } + const FieldMetric& g_12() const { return covariantMetricTensor.g12(); } + const FieldMetric& g_13() const { return covariantMetricTensor.g13(); } + const FieldMetric& g_23() const { return covariantMetricTensor.g23(); } /// get g_22 at the cell faces; const FieldMetric& g_22_ylow() const; @@ -235,26 +283,116 @@ private: void _compute_cell_volume() const; public: - /// Christoffel symbol of the second kind (connection coefficients) - FieldMetric G1_11, G1_22, G1_33, G1_12, G1_13, G1_23; - FieldMetric G2_11, G2_22, G2_33, G2_12, G2_13, G2_23; - FieldMetric G3_11, G3_22, G3_33, G3_12, G3_13, G3_23; + /// Contravariant metric tensor (g^{ij}) + const FieldMetric& g11() const { return contravariantMetricTensor.g11(); } + const FieldMetric& g22() const { return contravariantMetricTensor.g22(); } + const FieldMetric& g33() const { return contravariantMetricTensor.g33(); } + const FieldMetric& g12() const { return contravariantMetricTensor.g12(); } + const FieldMetric& g13() const { return contravariantMetricTensor.g13(); } + const FieldMetric& g23() const { return contravariantMetricTensor.g23(); } + + /// Covariant metric tensor + const BoutReal& g_11(int x, int y, int z) const { + return covariantMetricTensor.g11(x, y, z); + } + const BoutReal& g_22(int x, int y, int z) const { + return covariantMetricTensor.g22(x, y, z); + } + const BoutReal& g_33(int x, int y, int z) const { + return covariantMetricTensor.g33(x, y, z); + } + const BoutReal& g_12(int x, int y, int z) const { + return covariantMetricTensor.g12(x, y, z); + } + const BoutReal& g_13(int x, int y, int z) const { + return covariantMetricTensor.g13(x, y, z); + } + const BoutReal& g_23(int x, int y, int z) const { + return covariantMetricTensor.g23(x, y, z); + } + +#if not(BOUT_USE_METRIC_3D) + const BoutReal& g_11(int x, int y) const { return covariantMetricTensor.g11(x, y); } + const BoutReal& g_22(int x, int y) const { return covariantMetricTensor.g22(x, y); } + const BoutReal& g_33(int x, int y) const { return covariantMetricTensor.g33(x, y); } + const BoutReal& g_12(int x, int y) const { return covariantMetricTensor.g12(x, y); } + const BoutReal& g_13(int x, int y) const { return covariantMetricTensor.g13(x, y); } + const BoutReal& g_23(int x, int y) const { return covariantMetricTensor.g23(x, y); } +#endif + + /// Contravariant metric tensor (g^{ij}) + const BoutReal& g11(int x, int y, int z) const { + return contravariantMetricTensor.g11(x, y, z); + } + const BoutReal& g22(int x, int y, int z) const { + return contravariantMetricTensor.g22(x, y, z); + } + const BoutReal& g33(int x, int y, int z) const { + return contravariantMetricTensor.g33(x, y, z); + } + const BoutReal& g12(int x, int y, int z) const { + return contravariantMetricTensor.g12(x, y, z); + } + const BoutReal& g13(int x, int y, int z) const { + return contravariantMetricTensor.g13(x, y, z); + } + const BoutReal& g23(int x, int y, int z) const { + return contravariantMetricTensor.g23(x, y, z); + } + +#if not(BOUT_USE_METRIC_3D) + const BoutReal& g11(int x, int y) const { return contravariantMetricTensor.g11(x, y); } + const BoutReal& g22(int x, int y) const { return contravariantMetricTensor.g22(x, y); } + const BoutReal& g33(int x, int y) const { return contravariantMetricTensor.g33(x, y); } + const BoutReal& g12(int x, int y) const { return contravariantMetricTensor.g12(x, y); } + const BoutReal& g13(int x, int y) const { return contravariantMetricTensor.g13(x, y); } + const BoutReal& g23(int x, int y) const { return contravariantMetricTensor.g23(x, y); } +#endif + + const ContravariantMetricTensor& getContravariantMetricTensor() const { + return contravariantMetricTensor; + } - FieldMetric G1, G2, G3; + const CovariantMetricTensor& getCovariantMetricTensor() const { + return covariantMetricTensor; + } + + void setContravariantMetricTensor(const ContravariantMetricTensor& metric_tensor, + const std::string& region = "RGN_ALL", + bool recalculate_staggered = true, + bool force_interpolate_from_centre = false); + + void setCovariantMetricTensor(const CovariantMetricTensor& metric_tensor, + const std::string& region = "RGN_ALL", + bool recalculate_staggered = true, + bool force_interpolate_from_centre = false); + + void setMetricTensor(const ContravariantMetricTensor& contravariant_metric_tensor, + const CovariantMetricTensor& covariant_metric_tensor); + + void communicateMetricTensor(); + + void communicateDz(); + + ///< Coordinate system Jacobian, so volume of cell is J*dx*dy*dz + const FieldMetric& J() const; + + ///< Magnitude of B = nabla z times nabla x + const FieldMetric& Bxy() const { return Bxy_; } + + void setJ(const FieldMetric& J, bool communicate = true); + + void setBxy(FieldMetric Bxy, bool communicate = true); /// d pitch angle / dx. Needed for vector differentials (Curl) - FieldMetric ShiftTorsion; + const FieldMetric& ShiftTorsion() const { return ShiftTorsion_; } - FieldMetric IntShiftTorsion; ///< Integrated shear (I in BOUT notation) + ///< Integrated shear (I in BOUT notation) + const FieldMetric& IntShiftTorsion() const { return IntShiftTorsion_; } - /// Calculate differential geometry quantities from the metric tensor - int geometry(bool recalculate_staggered = true, - bool force_interpolate_from_centre = false); - /// Invert contravatiant metric to get covariant components - int calcCovariant(const std::string& region = "RGN_ALL"); - /// Invert covariant metric to get contravariant components - int calcContravariant(const std::string& region = "RGN_ALL"); - int jacobian(); ///< Calculate J and Bxy + void setIntShiftTorsion(FieldMetric IntShiftTorsion) { + IntShiftTorsion_ = std::move(IntShiftTorsion); + } /////////////////////////////////////////////////////////// // Parallel transforms @@ -279,7 +417,7 @@ public: FieldMetric DDX(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); + const std::string& region = "RGN_NOBNDRY") const; FieldMetric DDY(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT", @@ -287,11 +425,11 @@ public: FieldMetric DDZ(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); + const std::string& region = "RGN_NOBNDRY") const; Field3D DDX(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); + const std::string& region = "RGN_NOBNDRY") const; Field3D DDY(const Field3DParallel& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT", @@ -299,7 +437,7 @@ public: Field3D DDZ(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); + const std::string& region = "RGN_NOBNDRY") const; /// Gradient along magnetic field b.Grad(f) FieldMetric Grad_par(const Field2D& var, CELL_LOC outloc = CELL_DEFAULT, @@ -352,7 +490,55 @@ public: // Full perpendicular Laplacian, in form of inverse of Laplacian operator in LaplaceXY // solver - Field2D Laplace_perpXY(const Field2D& A, const Field2D& f); + Field2D Laplace_perpXY(const Field2D& A, const Field2D& f) const; + + /// Christoffel symbol of the second kind (connection coefficients) + const FieldMetric& G1_11() const { return christoffel_symbols().G1_11(); } + const FieldMetric& G1_22() const { return christoffel_symbols().G1_22(); } + const FieldMetric& G1_33() const { return christoffel_symbols().G1_33(); } + const FieldMetric& G1_12() const { return christoffel_symbols().G1_12(); } + const FieldMetric& G1_13() const { return christoffel_symbols().G1_13(); } + const FieldMetric& G1_23() const { return christoffel_symbols().G1_23(); } + const FieldMetric& G2_11() const { return christoffel_symbols().G2_11(); } + const FieldMetric& G2_22() const { return christoffel_symbols().G2_22(); } + const FieldMetric& G2_33() const { return christoffel_symbols().G2_33(); } + const FieldMetric& G2_12() const { return christoffel_symbols().G2_12(); } + const FieldMetric& G2_13() const { return christoffel_symbols().G2_13(); } + const FieldMetric& G2_23() const { return christoffel_symbols().G2_23(); } + const FieldMetric& G3_11() const { return christoffel_symbols().G3_11(); } + const FieldMetric& G3_22() const { return christoffel_symbols().G3_22(); } + const FieldMetric& G3_33() const { return christoffel_symbols().G3_33(); } + const FieldMetric& G3_12() const { return christoffel_symbols().G3_12(); } + const FieldMetric& G3_13() const { return christoffel_symbols().G3_13(); } + const FieldMetric& G3_23() const { return christoffel_symbols().G3_23(); } + + const FieldMetric& G1() const { return g_values().G1(); } + const FieldMetric& G2() const { return g_values().G2(); } + const FieldMetric& G3() const { return g_values().G3(); } + + const BoutReal& G1(int x, int y, int z) const { return G1()(x, y, z); } + const BoutReal& G2(int x, int y, int z) const { return G2()(x, y, z); } + const BoutReal& G3(int x, int y, int z) const { return G3()(x, y, z); } + +#if not(BOUT_USE_METRIC_3D) + const BoutReal& G1(int x, int y) const { return G1()(x, y); } + const BoutReal& G2(int x, int y) const { return G2()(x, y); } + const BoutReal& G3(int x, int y) const { return G3()(x, y); } +#endif + + const FieldMetric& Grad2_par2_DDY_invSg(CELL_LOC outloc, + const std::string& method) const; + + const FieldMetric& invSg() const; + + const ChristoffelSymbols& christoffel_symbols() const; + + GValues& g_values() const; + + void recalculateAndReset(bool recalculate_staggered, + bool force_interpolate_from_centre); + + FieldMetric recalculateJacobian() const; friend std::shared_ptr getYBoundary(Coordinates* coords, YBndryType type); @@ -360,36 +546,78 @@ private: std::shared_ptr makeYBoundary(YBndryType type) const; int nz; // Size of mesh in Z. This is mesh->ngz-1 Mesh* localmesh; - Options* localoptions; + Options* localoptions{nullptr}; CELL_LOC location; + /// True if corrections for non-uniform mesh spacing should be included in operators + bool non_uniform_{}; + + FieldMetric dx_, dy_, dz_; ///< Mesh spacing in x, y and z + + /// 2nd-order correction for non-uniform meshes d/di(1/dx), d/di(1/dy) and d/di(1/dz) + FieldMetric d1_dx_, d1_dy_, d1_dz_; + + /// d pitch angle / dx. Needed for vector differentials (Curl) + FieldMetric ShiftTorsion_; + + ///< Integrated shear (I in BOUT notation) + FieldMetric IntShiftTorsion_; + /// Handles calculation of yup and ydown std::unique_ptr transform{nullptr}; /// Cache variable for `zlength`. Invalidated when - /// `Coordinates::geometry` is called + /// `Coordinates::recalculateAndReset` is called mutable std::unique_ptr zlength_cache{nullptr}; /// Cache variable for Grad2_par2 mutable std::map> Grad2_par2_DDY_invSgCache; mutable std::unique_ptr invSgCache{nullptr}; + ContravariantMetricTensor contravariantMetricTensor; + CovariantMetricTensor covariantMetricTensor; + + /// Christoffel symbol of the second kind (connection coefficients) + mutable std::unique_ptr christoffel_symbols_cache{nullptr}; + + /// `g_values` needs renaming, when we know what the name should be + mutable std::unique_ptr g_values_cache{nullptr}; + + mutable std::unique_ptr jacobian_cache{nullptr}; + + FieldMetric Bxy_; ///< Magnitude of B = nabla z times nabla x + /// Set the parallel (y) transform from the options file. /// Used in the constructor to create the transform object. void setParallelTransform(Options* options); - const FieldMetric& invSg() const; - const FieldMetric& Grad2_par2_DDY_invSg(CELL_LOC outloc, - const std::string& method) const; - // check that covariant tensors are positive (if expected) and finite (always) void checkCovariant(); // check that contravariant tensors are positive (if expected) and finite (always) void checkContravariant(); mutable std::array, 3> ybndrys; + + FieldMetric recalculateBxy() const; + + /// Non-uniform meshes. Need to use DDX, DDY + void correctionForNonUniformMeshes(bool force_interpolate_from_centre); + + void interpolateFromCoordinates(Options* options, const Coordinates* coords_in); + /// Read quantities with given suffix from `Mesh` void readFromMesh(Options* options, const std::string& suffix); + + /// Read parallel slices of metric components from `Mesh` + void readParallelMetricComponents(); + +protected: + /// For testing purposes only; inherit and make this public + void splitBxyParallelSlices(); }; +namespace bout { +std::string parallelSliceFieldName(std::string_view field, int offset); +} + #endif // BOUT_COORDINATES_H diff --git a/include/bout/field.hxx b/include/bout/field.hxx index b39a82eb0b..96c17a1193 100644 --- a/include/bout/field.hxx +++ b/include/bout/field.hxx @@ -134,12 +134,15 @@ public: virtual void setRegion([[maybe_unused]] std::optional regionID) {} virtual void setRegion([[maybe_unused]] const std::string& region_name) {} virtual void resetRegion() {} + virtual void resetRegionParallel([[maybe_unused]] bool force) {}; virtual std::optional getRegionID() const { return {}; } virtual bool hasParallelSlices() const { return true; } virtual void calcParallelSlices() {} virtual void splitParallelSlices() {} virtual void clearParallelSlices() {} virtual size_t numberParallelSlices() const { return 0; } + virtual bool areCalcParallelSlicesAllowed() const { return false; } + virtual void disallowCalcParallelSlices() {} private: /// Labels for the type of coordinate system this field is defined over diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index 905e736999..d60ba4b281 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -355,7 +355,7 @@ public: const Region& getValidRegionWithDefault(const std::string& region_name) const; void setRegion(const std::string& region_name) override; void resetRegion() override { regionID.reset(); }; - void resetRegionParallel(bool force = false); + void resetRegionParallel(bool force = false) override; void setRegion(size_t id) override { regionID = id; }; void setRegion(std::optional id) override { regionID = id; }; std::optional getRegionID() const override { return regionID; }; @@ -601,6 +601,8 @@ public: friend class Vector2D; void calcParallelSlices() override; + bool areCalcParallelSlicesAllowed() const override { return _allowCalcParallelSlices; }; + void disallowCalcParallelSlices() override { _allowCalcParallelSlices = false; }; void applyBoundary(bool init = false) override; void applyBoundary(BoutReal t); @@ -633,9 +635,6 @@ public: std::weak_ptr getTracking() { return tracking; }; - bool areCalcParallelSlicesAllowed() const { return _allowCalcParallelSlices; }; - void disallowCalcParallelSlices() { _allowCalcParallelSlices = false; }; - inline Field3DParallel asField3DParallel(); inline Field3DParallel asField3DParallel() const; diff --git a/include/bout/fv_ops_impl.hxx b/include/bout/fv_ops_impl.hxx index 1b7a36f874..c86a376bc0 100644 --- a/include/bout/fv_ops_impl.hxx +++ b/include/bout/fv_ops_impl.hxx @@ -491,10 +491,10 @@ Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_flux) { BOUT_FOR(i, result.getRegion("RGN_NOBNDRY")) { // Calculate velocities - const BoutReal vU = 0.25 * (vz[i.zp()] + vz[i]) * (coord->J[i.zp()] + coord->J[i]); - const BoutReal vD = 0.25 * (vz[i.zm()] + vz[i]) * (coord->J[i.zm()] + coord->J[i]); - const BoutReal vL = 0.25 * (vx[i.xm()] + vx[i]) * (coord->J[i.xm()] + coord->J[i]); - const BoutReal vR = 0.25 * (vx[i.xp()] + vx[i]) * (coord->J[i.xp()] + coord->J[i]); + const BoutReal vU = 0.25 * (vz[i.zp()] + vz[i]) * (coord->J()[i.zp()] + coord->J()[i]); + const BoutReal vD = 0.25 * (vz[i.zm()] + vz[i]) * (coord->J()[i.zm()] + coord->J()[i]); + const BoutReal vL = 0.25 * (vx[i.xm()] + vx[i]) * (coord->J()[i.xm()] + coord->J()[i]); + const BoutReal vR = 0.25 * (vx[i.xp()] + vx[i]) * (coord->J()[i.xp()] + coord->J()[i]); // X direction Stencil1D s; @@ -517,16 +517,16 @@ Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_flux) { // Flux in from boundary flux = vR * 0.5 * (n[i.xp()] + n[i]); } - result[i] += flux / (coord->dx[i] * coord->J[i]); - result[i.xp()] -= flux / (coord->dx[i.xp()] * coord->J[i.xp()]); + result[i] += flux / (coord->dx()[i] * coord->J()[i]); + result[i.xp()] -= flux / (coord->dx()[i.xp()] * coord->J()[i.xp()]); } } else { // Not at a boundary if (vR > 0.0) { // Flux out into next cell const BoutReal flux = vR * s.R; - result[i] += flux / (coord->dx[i] * coord->J[i]); - result[i.xp()] -= flux / (coord->dx[i.xp()] * coord->J[i.xp()]); + result[i] += flux / (coord->dx()[i] * coord->J()[i]); + result[i.xp()] -= flux / (coord->dx()[i.xp()] * coord->J()[i.xp()]); } } @@ -544,15 +544,15 @@ Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_flux) { // Flux in from boundary flux = vL * 0.5 * (n[i.xm()] + n[i]); } - result[i] -= flux / (coord->dx[i] * coord->J[i]); - result[i.xm()] += flux / (coord->dx[i.xm()] * coord->J[i.xm()]); + result[i] -= flux / (coord->dx()[i] * coord->J()[i]); + result[i.xm()] += flux / (coord->dx()[i.xm()] * coord->J()[i.xm()]); } } else { // Not at a boundary if (vL < 0.0) { const BoutReal flux = vL * s.L; - result[i] -= flux / (coord->dx[i] * coord->J[i]); - result[i.xm()] += flux / (coord->dx[i.xm()] * coord->J[i.xm()]); + result[i] -= flux / (coord->dx()[i] * coord->J()[i]); + result[i.xm()] += flux / (coord->dx()[i.xm()] * coord->J()[i.xm()]); } } @@ -568,13 +568,13 @@ Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_flux) { if (vU > 0.0) { const BoutReal flux = vU * s.R; - result[i] += flux / (coord->J[i] * coord->dz[i]); - result[i.zp()] -= flux / (coord->J[i.zp()] * coord->dz[i.zp()]); + result[i] += flux / (coord->J()[i] * coord->dz()[i]); + result[i.zp()] -= flux / (coord->J()[i.zp()] * coord->dz()[i.zp()]); } if (vD < 0.0) { const BoutReal flux = vD * s.L; - result[i] -= flux / (coord->J[i] * coord->dz[i]); - result[i.zm()] += flux / (coord->J[i.zm()] * coord->dz[i.zm()]); + result[i] -= flux / (coord->J()[i] * coord->dz()[i]); + result[i.zm()] += flux / (coord->J()[i.zm()] * coord->dz()[i.zm()]); } } @@ -592,15 +592,15 @@ Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_flux) { BOUT_FOR(i, result.getRegion("RGN_NOBNDRY")) { // Y velocities on y boundaries - const BoutReal vU = 0.25 * (vy[i] + vy[i.yp()]) * (coord->J[i] + coord->J[i.yp()]); - const BoutReal vD = 0.25 * (vy[i] + vy[i.ym()]) * (coord->J[i] + coord->J[i.ym()]); + const BoutReal vU = 0.25 * (vy[i] + vy[i.yp()]) * (coord->J()[i] + coord->J()[i.yp()]); + const BoutReal vD = 0.25 * (vy[i] + vy[i.ym()]) * (coord->J()[i] + coord->J()[i.ym()]); // n (advected quantity) on y boundaries // Note: Use unshifted n_in variable const BoutReal nU = 0.5 * (n[i] + n[i.yp()]); const BoutReal nD = 0.5 * (n[i] + n[i.ym()]); - yresult[i] = (nU * vU - nD * vD) / (coord->J[i] * coord->dy[i]); + yresult[i] = (nU * vU - nD * vD) / (coord->J()[i] * coord->dy()[i]); } return result + fromFieldAligned(yresult, "RGN_NOBNDRY"); } @@ -823,9 +823,9 @@ Field3D Div_par_fvv(const Field3D& f_in, const Field3D& v_in, ASSERT1(f_in.hasParallelSlices()); ASSERT1(v_in.hasParallelSlices()); - const auto& B = coord->Bxy; - const auto& B_up = coord->Bxy.yup(); - const auto& B_down = coord->Bxy.ydown(); + const auto& B = coord->Bxy(); + const auto& B_up = coord->Bxy().yup(); + const auto& B_down = coord->Bxy().ydown(); const auto& f_up = f_in.yup(); const auto& f_down = f_in.ydown(); @@ -833,8 +833,8 @@ Field3D Div_par_fvv(const Field3D& f_in, const Field3D& v_in, const auto& v_up = v_in.yup(); const auto& v_down = v_in.ydown(); - const auto& g_22 = coord->g_22; - const auto& dy = coord->dy; + const auto& g_22 = coord->g_22(); + const auto& dy = coord->dy(); Field3D result{emptyFrom(f_in)}; BOUT_FOR(i, f_in.getRegion("RGN_NOBNDRY")) { @@ -1013,9 +1013,9 @@ Field3D Div_par_fvv_heating(const Field3D& f_in, const Field3D& v_in, ASSERT1(f_in.hasParallelSlices()); ASSERT1(v_in.hasParallelSlices()); - const auto B = coord->Bxy; - const auto B_up = coord->Bxy.yup(); - const auto B_down = coord->Bxy.ydown(); + const auto B = coord->Bxy(); + const auto B_up = coord->Bxy().yup(); + const auto B_down = coord->Bxy().ydown(); const auto& f_up = f_in.yup(); const auto& f_down = f_in.ydown(); @@ -1023,8 +1023,8 @@ Field3D Div_par_fvv_heating(const Field3D& f_in, const Field3D& v_in, const auto& v_up = v_in.yup(); const auto& v_down = v_in.ydown(); - const auto g_22 = coord->g_22; - const auto dy = coord->dy; + const auto g_22 = coord->g_22(); + const auto dy = coord->dy(); Field3D result{emptyFrom(f_in)}; flow_ylow = zeroFrom(f_in); @@ -1336,10 +1336,10 @@ Field3D Div_a_Grad_perp_limit(const Field3D& a, const Field3D& g, const Field3D& if (fci) { // 3D Metric, need yup/ydown fields. // Requires previous communication of metrics. - if (!coord->g23.hasParallelSlices() || !coord->g_23.hasParallelSlices() - || !coord->dy.hasParallelSlices() || !coord->dz.hasParallelSlices() - || !coord->Bxy.hasParallelSlices() || !coord->J.hasParallelSlices()) { - throw BoutException("metrics have no yup/down: Maybe communicate in init?"); + if (!coord->g23().hasParallelSlices() || !coord->g_23().hasParallelSlices() + || !coord->dy().hasParallelSlices() || !coord->dz().hasParallelSlices() + || !coord->Bxy().hasParallelSlices() || !coord->J().hasParallelSlices()) { + throw BoutException("metrics have no yup/down!"); } } #endif @@ -1357,12 +1357,12 @@ Field3D Div_a_Grad_perp_limit(const Field3D& a, const Field3D& g, const Field3D& #else constexpr bool metric_fci = false; #endif - const auto g23 = makeslices(metric_fci, coord->g23); - const auto g_23 = makeslices(metric_fci, coord->g_23); - const auto J = makeslices(metric_fci, coord->J); - const auto dy = makeslices(metric_fci, coord->dy); - const auto dz = makeslices(metric_fci, coord->dz); - const auto Bxy = makeslices(metric_fci, coord->Bxy); + const auto g23 = makeslices(metric_fci, coord->g23()); + const auto g_23 = makeslices(metric_fci, coord->g_23()); + const auto J = makeslices(metric_fci, coord->J()); + const auto dy = makeslices(metric_fci, coord->dy()); + const auto dz = makeslices(metric_fci, coord->dz()); + const auto Bxy = makeslices(metric_fci, coord->Bxy()); // Result of the Y and Z fluxes Field3D yzresult(0.0, mesh); diff --git a/include/bout/g_values.hxx b/include/bout/g_values.hxx new file mode 100644 index 0000000000..cbbd6d24ff --- /dev/null +++ b/include/bout/g_values.hxx @@ -0,0 +1,28 @@ +#ifndef BOUT_GVALUES_HXX +#define BOUT_GVALUES_HXX + +#include + +class Coordinates; + +/// `GValues` needs renaming, when we know what the name should be +class GValues { +public: + explicit GValues(const Coordinates& coordinates); + + const bout::FieldMetric& G1() const { return G1_m; } + const bout::FieldMetric& G2() const { return G2_m; } + const bout::FieldMetric& G3() const { return G3_m; } + + template + void map(F function) { + G1_m = function(G1_m); + G2_m = function(G2_m); + G3_m = function(G3_m); + } + +private: + bout::FieldMetric G1_m, G2_m, G3_m; +}; + +#endif //BOUT_GVALUES_HXX diff --git a/include/bout/mesh.hxx b/include/bout/mesh.hxx index 84f9ec36c6..833d23c419 100644 --- a/include/bout/mesh.hxx +++ b/include/bout/mesh.hxx @@ -624,25 +624,7 @@ public: return getCoordinatesSmart(location).get(); }; - std::shared_ptr - getCoordinatesSmart(const CELL_LOC location = CELL_CENTRE) { - ASSERT1(location != CELL_DEFAULT); - ASSERT1(location != CELL_VSHIFT); - - auto found = coords_map.find(location); - if (found != coords_map.end()) { - // True branch most common, returns immediately - return found->second; - } - - // No coordinate system set. Create default - // Note that this can't be allocated here due to incomplete type - // (circular dependency between Mesh and Coordinates) - auto inserted = coords_map.emplace(location, nullptr); - inserted.first->second = createDefaultCoordinates(location); - inserted.first->second->geometry(false); - return inserted.first->second; - } + std::shared_ptr getCoordinatesSmart(const CELL_LOC location = CELL_CENTRE); std::shared_ptr getCoordinatesConst(const CELL_LOC location = CELL_CENTRE) const { diff --git a/include/bout/metric_tensor.hxx b/include/bout/metric_tensor.hxx new file mode 100644 index 0000000000..bef5bc3079 --- /dev/null +++ b/include/bout/metric_tensor.hxx @@ -0,0 +1,104 @@ +#ifndef BOUT_METRIC_TENSOR_HXX +#define BOUT_METRIC_TENSOR_HXX + +#include +#include +#include +#include + +#include +#include + +namespace bout { +#if BOUT_USE_METRIC_3D +using FieldMetric = Field3D; +#else +using FieldMetric = Field2D; +#endif +} // namespace bout + +class Coordinates; + +class MetricTensor { +public: + friend class Coordinates; + +#if BOUT_USE_METRIC_3D + using Metric2DSlice = const BoutReal*; +#else + using Metric2DSlice = const BoutReal&; +#endif + using FieldMetric = bout::FieldMetric; + + MetricTensor(const MetricTensor&) = default; + MetricTensor(MetricTensor&&) = default; + MetricTensor& operator=(const MetricTensor&) = default; + MetricTensor& operator=(MetricTensor&&) = default; + MetricTensor(FieldMetric g11, FieldMetric g22, FieldMetric g33, FieldMetric g12, + FieldMetric g13, FieldMetric g23); + + MetricTensor(BoutReal g11, BoutReal g22, BoutReal g33, BoutReal g12, BoutReal g13, + BoutReal g23, Mesh* mesh); + virtual ~MetricTensor() = default; + + /// Check that tensors are positive (if expected) and finite (always) + void check(int ystart); + + const FieldMetric& g11() const { return g11_m; } + const FieldMetric& g22() const { return g22_m; } + const FieldMetric& g33() const { return g33_m; } + const FieldMetric& g12() const { return g12_m; } + const FieldMetric& g13() const { return g13_m; } + const FieldMetric& g23() const { return g23_m; } + + const BoutReal& g11(int x, int y, int z) const { return g11_m(x, y, z); } + const BoutReal& g22(int x, int y, int z) const { return g22_m(x, y, z); } + const BoutReal& g33(int x, int y, int z) const { return g33_m(x, y, z); } + const BoutReal& g12(int x, int y, int z) const { return g12_m(x, y, z); } + const BoutReal& g13(int x, int y, int z) const { return g13_m(x, y, z); } + const BoutReal& g23(int x, int y, int z) const { return g23_m(x, y, z); } + + Metric2DSlice g11(int x, int y) const { return g11_m(x, y); } + Metric2DSlice g22(int x, int y) const { return g22_m(x, y); } + Metric2DSlice g33(int x, int y) const { return g33_m(x, y); } + Metric2DSlice g12(int x, int y) const { return g12_m(x, y); } + Metric2DSlice g13(int x, int y) const { return g13_m(x, y); } + Metric2DSlice g23(int x, int y) const { return g23_m(x, y); } + + /// Transforms the MetricTensor by applying the given function to every component + template + void map(F function) { + g11_m = function(g11_m); + g22_m = function(g22_m); + g33_m = function(g33_m); + g12_m = function(g12_m); + g13_m = function(g13_m); + g23_m = function(g23_m); + } + + void communicate(); + +private: + FieldMetric g11_m, g22_m, g33_m, g12_m, g13_m, g23_m; +}; + +class CovariantMetricTensor; +class ContravariantMetricTensor; + +class CovariantMetricTensor : public MetricTensor { +public: + using MetricTensor::MetricTensor; + + auto inverse(const std::string& region = "RGN_ALL", bool communicate = true) + -> ContravariantMetricTensor; +}; + +class ContravariantMetricTensor : public MetricTensor { +public: + using MetricTensor::MetricTensor; + + auto inverse(const std::string& region = "RGN_ALL", bool communicate = true) + -> CovariantMetricTensor; +}; + +#endif //BOUT_METRIC_TENSOR_HXX diff --git a/include/bout/parallel_boundary_op.hxx b/include/bout/parallel_boundary_op.hxx index 5139cd5090..d98aa7d4e8 100644 --- a/include/bout/parallel_boundary_op.hxx +++ b/include/bout/parallel_boundary_op.hxx @@ -211,7 +211,7 @@ public: void apply(Field3D& f, BoutReal t) override { if (bndry != nullptr) { f.ynext(bndry->dir()).allocate(); // Ensure unique before modifying - auto dy = f.getCoordinates()->dy; + auto dy = f.getCoordinates()->dy(); for (auto pnt : *bndry) { BoutReal value = getValue(pnt, t); if (isNeumann) { @@ -222,7 +222,7 @@ public: } if (bndryX != nullptr) { f.allocate(); - auto dy = f.getCoordinates()->dx; + auto dy = f.getCoordinates()->dx(); for (auto pnt : *bndryX) { BoutReal value = getValue(pnt, t); if (isNeumann) { @@ -233,7 +233,7 @@ public: } if (bndryY != nullptr) { f.allocate(); - auto dy = f.getCoordinates()->dy; + auto dy = f.getCoordinates()->dy(); for (auto pnt : *bndryY) { BoutReal value = getValue(pnt, t); if (isNeumann) { diff --git a/include/bout/paralleltransform.hxx b/include/bout/paralleltransform.hxx index d9ed397bb5..6952069251 100644 --- a/include/bout/paralleltransform.hxx +++ b/include/bout/paralleltransform.hxx @@ -91,10 +91,6 @@ public: /// require a twist-shift at branch cuts on closed field lines? virtual bool requiresTwistShift(bool twist_shift_enabled, YDirectionType ytype) = 0; - /// Can be implemented to load parallel metrics - /// Needed by FCI - virtual void loadParallelMetrics([[maybe_unused]] Coordinates* coords) {} - protected: /// This method should be called in the constructor to check that if the grid /// has a 'parallel_transform' variable, it has the correct value diff --git a/include/bout/tokamak_coordinates.hxx b/include/bout/tokamak_coordinates.hxx index f056033bb9..295d0bf9d1 100644 --- a/include/bout/tokamak_coordinates.hxx +++ b/include/bout/tokamak_coordinates.hxx @@ -4,6 +4,7 @@ #include #include #include +#include class Mesh; @@ -14,21 +15,21 @@ namespace bout { /// `I_unnormalised` are normalised. struct TokamakCoordinates { /// Major radius - Field2D Rxy; + FieldMetric Rxy; /// Vertical height - Field2D Zxy; + FieldMetric Zxy; /// Poloidal magnetic field - Field2D Bpxy; + FieldMetric Bpxy; /// Toroidal magnetic field - Field2D Btxy; + FieldMetric Btxy; /// Total magnetic field - Field2D Bxy; + FieldMetric Bxy; /// Poloidal arc length - Field2D hthe; + FieldMetric hthe; /// Integrated shear (normalised) - Coordinates::FieldMetric I; + FieldMetric I; /// Unnormalised integrated shear - Coordinates::FieldMetric I_unnormalised; + FieldMetric I_unnormalised; }; /// Read, normalise, calculate, and set the metric components for a BOUT++ diff --git a/src/field/vecops.cxx b/src/field/vecops.cxx index 672e8f6c09..1bb3d991f4 100644 --- a/src/field/vecops.cxx +++ b/src/field/vecops.cxx @@ -104,10 +104,10 @@ Vector3D Grad_perp(const Field3D& f, CELL_LOC outloc, const std::string& method) Vector3D result(f.getMesh()); result.x = DDX(f, outloc, method) - - metric->g_12 * DDY(f, outloc, method) / SQ(metric->J * metric->Bxy); + - metric->g_12() * DDY(f, outloc, method) / SQ(metric->J() * metric->Bxy()); result.y = 0.0; result.z = DDZ(f, outloc, method) - - metric->g_23 * DDY(f, outloc, method) / SQ(metric->J * metric->Bxy); + - metric->g_23() * DDY(f, outloc, method) / SQ(metric->J() * metric->Bxy()); result.setLocation(result.x.getLocation()); @@ -126,9 +126,9 @@ Vector2D Grad_perp(const Field2D& f, CELL_LOC outloc, const std::string& method) Vector2D result(f.getMesh()); result.x = DDX(f, outloc, method) - - metric->g_12 * DDY(f, outloc, method) / SQ(metric->J * metric->Bxy); + - metric->g_12() * DDY(f, outloc, method) / SQ(metric->J() * metric->Bxy()); result.y = 0.0; - result.z = -metric->g_23 * DDY(f, outloc, method) / SQ(metric->J * metric->Bxy); + result.z = -metric->g_23() * DDY(f, outloc, method) / SQ(metric->J() * metric->Bxy()); result.setLocation(result.x.getLocation()); @@ -159,10 +159,10 @@ Coordinates::FieldMetric Div(const Vector2D& v, CELL_LOC outloc, Vector2D vcn = v; vcn.toContravariant(); - Coordinates::FieldMetric result = DDX(metric->J * vcn.x, outloc, method); - result += DDY(Coordinates::FieldMetric{metric->J * vcn.y}, outloc, method); - result += DDZ(Coordinates::FieldMetric{metric->J * vcn.z}, outloc, method); - result /= metric->J; + Coordinates::FieldMetric result = DDX(metric->J() * vcn.x, outloc, method); + result += DDY(Coordinates::FieldMetric{metric->J() * vcn.y}, outloc, method); + result += DDZ(Coordinates::FieldMetric{metric->J() * vcn.z}, outloc, method); + result /= metric->J(); return result; } @@ -185,7 +185,7 @@ Field3D Div(const Vector3D& v, CELL_LOC outloc, const std::string& method) { Vector3D vcn = v; vcn.toContravariant(); - Field3D vcnJy = vcn.y.getCoordinates()->J * vcn.y; + Field3D vcnJy = vcn.y.getCoordinates()->J() * vcn.y; if (v.y.hasParallelSlices()) { // If v.y has parallel slices then we are using ShiftedMetric (with // mesh:calcParallelSlices_on_communicate=true) or FCI, so we should calculate @@ -194,9 +194,9 @@ Field3D Div(const Vector3D& v, CELL_LOC outloc, const std::string& method) { } auto result = DDY(vcnJy, outloc, method); - result += DDX(Field3D{vcn.x.getCoordinates()->J * vcn.x}, outloc, method); - result += DDZ(Field3D{vcn.z.getCoordinates()->J * vcn.z}, outloc, method); - result /= metric->J; + result += DDX(Field3D{vcn.x.getCoordinates()->J() * vcn.x}, outloc, method); + result += DDZ(Field3D{vcn.z.getCoordinates()->J() * vcn.z}, outloc, method); + result /= metric->J(); return result; } @@ -224,12 +224,12 @@ Coordinates::FieldMetric Div(const Vector2D& v, const Field2D& f, CELL_LOC outlo vcn.toContravariant(); Coordinates::FieldMetric result = FDDX( - Coordinates::FieldMetric{vcn.x.getCoordinates()->J * vcn.x}, f, outloc, method); - result += FDDY(Coordinates::FieldMetric{vcn.y.getCoordinates()->J * vcn.y}, f, outloc, + Coordinates::FieldMetric{vcn.x.getCoordinates()->J() * vcn.x}, f, outloc, method); + result += FDDY(Coordinates::FieldMetric{vcn.y.getCoordinates()->J() * vcn.y}, f, outloc, method); - result += FDDZ(Coordinates::FieldMetric{vcn.z.getCoordinates()->J * vcn.z}, f, outloc, + result += FDDZ(Coordinates::FieldMetric{vcn.z.getCoordinates()->J() * vcn.z}, f, outloc, method); - result /= metric->J; + result /= metric->J(); return result; } @@ -250,10 +250,10 @@ Field3D Div(const Vector3D& v, const Field3D& f, CELL_LOC outloc, Vector3D vcn = v; vcn.toContravariant(); - Field3D result = FDDX(Field3D{vcn.x.getCoordinates()->J * vcn.x}, f, outloc, method); - result += FDDY(Field3D{vcn.y.getCoordinates()->J * vcn.y}, f, outloc, method); - result += FDDZ(Field3D{vcn.z.getCoordinates()->J * vcn.z}, f, outloc, method); - result /= metric->J; + Field3D result = FDDX(Field3D{vcn.x.getCoordinates()->J() * vcn.x}, f, outloc, method); + result += FDDY(Field3D{vcn.y.getCoordinates()->J() * vcn.y}, f, outloc, method); + result += FDDZ(Field3D{vcn.z.getCoordinates()->J() * vcn.z}, f, outloc, method); + result /= metric->J(); return result; } @@ -274,12 +274,12 @@ Vector2D Curl(const Vector2D& v) { // get components (curl(v))^j Vector2D result(localmesh); - result.x = (DDY(vco.z) - DDZ(vco.y)) / metric->J; - result.y = (DDZ(vco.x) - DDX(vco.z)) / metric->J; - result.z = (DDX(vco.y) - DDY(vco.x)) / metric->J; + result.x = (DDY(vco.z) - DDZ(vco.y)) / metric->J(); + result.y = (DDZ(vco.x) - DDX(vco.z)) / metric->J(); + result.z = (DDX(vco.y) - DDY(vco.x)) / metric->J(); /// Coordinate torsion - result.z -= metric->ShiftTorsion * vco.z / metric->J; + result.z -= metric->ShiftTorsion() * vco.z / metric->J(); result.setLocation(v.getLocation()); @@ -302,12 +302,12 @@ Vector3D Curl(const Vector3D& v) { // get components (curl(v))^j Vector3D result(localmesh); - result.x = (DDY(vco.z) - DDZ(vco.y)) / metric->J; - result.y = (DDZ(vco.x) - DDX(vco.z)) / metric->J; - result.z = (DDX(vco.y) - DDY(vco.x)) / metric->J; + result.x = (DDY(vco.z) - DDZ(vco.y)) / metric->J(); + result.y = (DDZ(vco.x) - DDX(vco.z)) / metric->J(); + result.z = (DDX(vco.y) - DDY(vco.x)) / metric->J(); // Coordinate torsion - result.z -= metric->ShiftTorsion * vco.z / metric->J; + result.z -= metric->ShiftTorsion() * vco.z / metric->J(); result.setLocation(v.getLocation()); @@ -387,80 +387,80 @@ R V_dot_Grad(const T& v, const F& a) { result.x = VDDX(vcn.x, a.x) + VDDY(vcn.y, a.x) + VDDZ(vcn.z, a.x); BOUT_FOR(i, result.x.getRegion("RGN_ALL")) { result.x[i] -= vcn.x[i] - * (metric->G1_11[i] * a.x[i] + metric->G2_11[i] * a.y[i] - + metric->G3_11[i] * a.z[i]); + * (metric->G1_11()[i] * a.x[i] + metric->G2_11()[i] * a.y[i] + + metric->G3_11()[i] * a.z[i]); result.x[i] -= vcn.y[i] - * (metric->G1_12[i] * a.x[i] + metric->G2_12[i] * a.y[i] - + metric->G3_12[i] * a.z[i]); + * (metric->G1_12()[i] * a.x[i] + metric->G2_12()[i] * a.y[i] + + metric->G3_12()[i] * a.z[i]); result.x[i] -= vcn.z[i] - * (metric->G1_13[i] * a.x[i] + metric->G2_13[i] * a.y[i] - + metric->G3_13[i] * a.z[i]); + * (metric->G1_13()[i] * a.x[i] + metric->G2_13()[i] * a.y[i] + + metric->G3_13()[i] * a.z[i]); } result.y = VDDX(vcn.x, a.y) + VDDY(vcn.y, a.y) + VDDZ(vcn.z, a.y); BOUT_FOR(i, result.y.getRegion("RGN_ALL")) { result.y[i] -= vcn.x[i] - * (metric->G1_12[i] * a.x[i] + metric->G2_12[i] * a.y[i] - + metric->G3_12[i] * a.z[i]); + * (metric->G1_12()[i] * a.x[i] + metric->G2_12()[i] * a.y[i] + + metric->G3_12()[i] * a.z[i]); result.y[i] -= vcn.y[i] - * (metric->G1_22[i] * a.x[i] + metric->G2_22[i] * a.y[i] - + metric->G3_22[i] * a.z[i]); + * (metric->G1_22()[i] * a.x[i] + metric->G2_22()[i] * a.y[i] + + metric->G3_22()[i] * a.z[i]); result.y[i] -= vcn.z[i] - * (metric->G1_23[i] * a.x[i] + metric->G2_23[i] * a.y[i] - + metric->G3_23[i] * a.z[i]); + * (metric->G1_23()[i] * a.x[i] + metric->G2_23()[i] * a.y[i] + + metric->G3_23()[i] * a.z[i]); } result.z = VDDX(vcn.x, a.z) + VDDY(vcn.y, a.z) + VDDZ(vcn.z, a.z); BOUT_FOR(i, result.z.getRegion("RGN_ALL")) { result.z[i] -= vcn.x[i] - * (metric->G1_13[i] * a.x[i] + metric->G2_13[i] * a.y[i] - + metric->G3_13[i] * a.z[i]); + * (metric->G1_13()[i] * a.x[i] + metric->G2_13()[i] * a.y[i] + + metric->G3_13()[i] * a.z[i]); result.z[i] -= vcn.y[i] - * (metric->G1_23[i] * a.x[i] + metric->G2_23[i] * a.y[i] - + metric->G3_23[i] * a.z[i]); + * (metric->G1_23()[i] * a.x[i] + metric->G2_23()[i] * a.y[i] + + metric->G3_23()[i] * a.z[i]); result.z[i] -= vcn.z[i] - * (metric->G1_33[i] * a.x[i] + metric->G2_33[i] * a.y[i] - + metric->G3_33[i] * a.z[i]); + * (metric->G1_33()[i] * a.x[i] + metric->G2_33()[i] * a.y[i] + + metric->G3_33()[i] * a.z[i]); } result.covariant = true; } else { result.x = VDDX(vcn.x, a.x) + VDDY(vcn.y, a.x) + VDDZ(vcn.z, a.x); BOUT_FOR(i, result.x.getRegion("RGN_ALL")) { result.x[i] += vcn.x[i] - * (metric->G1_11[i] * a.x[i] + metric->G1_12[i] * a.y[i] - + metric->G1_13[i] * a.z[i]); + * (metric->G1_11()[i] * a.x[i] + metric->G1_12()[i] * a.y[i] + + metric->G1_13()[i] * a.z[i]); result.x[i] += vcn.y[i] - * (metric->G1_12[i] * a.x[i] + metric->G1_22[i] * a.y[i] - + metric->G1_23[i] * a.z[i]); + * (metric->G1_12()[i] * a.x[i] + metric->G1_22()[i] * a.y[i] + + metric->G1_23()[i] * a.z[i]); result.x[i] += vcn.z[i] - * (metric->G1_13[i] * a.x[i] + metric->G1_23[i] * a.y[i] - + metric->G1_33[i] * a.z[i]); + * (metric->G1_13()[i] * a.x[i] + metric->G1_23()[i] * a.y[i] + + metric->G1_33()[i] * a.z[i]); } result.y = VDDX(vcn.x, a.y) + VDDY(vcn.y, a.y) + VDDZ(vcn.z, a.y); BOUT_FOR(i, result.y.getRegion("RGN_ALL")) { result.y[i] += vcn.x[i] - * (metric->G2_11[i] * a.x[i] + metric->G2_12[i] * a.y[i] - + metric->G2_13[i] * a.z[i]); + * (metric->G2_11()[i] * a.x[i] + metric->G2_12()[i] * a.y[i] + + metric->G2_13()[i] * a.z[i]); result.y[i] += vcn.y[i] - * (metric->G2_12[i] * a.x[i] + metric->G2_22[i] * a.y[i] - + metric->G2_23[i] * a.z[i]); + * (metric->G2_12()[i] * a.x[i] + metric->G2_22()[i] * a.y[i] + + metric->G2_23()[i] * a.z[i]); result.y[i] += vcn.z[i] - * (metric->G2_13[i] * a.x[i] + metric->G2_23[i] * a.y[i] - + metric->G2_33[i] * a.z[i]); + * (metric->G2_13()[i] * a.x[i] + metric->G2_23()[i] * a.y[i] + + metric->G2_33()[i] * a.z[i]); } result.z = VDDX(vcn.x, a.z) + VDDY(vcn.y, a.z) + VDDZ(vcn.z, a.z); BOUT_FOR(i, result.z.getRegion("RGN_ALL")) { result.z[i] += vcn.x[i] - * (metric->G3_11[i] * a.x[i] + metric->G3_12[i] * a.y[i] - + metric->G3_13[i] * a.z[i]); + * (metric->G3_11()[i] * a.x[i] + metric->G3_12()[i] * a.y[i] + + metric->G3_13()[i] * a.z[i]); result.z[i] += vcn.y[i] - * (metric->G3_12[i] * a.x[i] + metric->G3_22[i] * a.y[i] - + metric->G3_23[i] * a.z[i]); + * (metric->G3_12()[i] * a.x[i] + metric->G3_22()[i] * a.y[i] + + metric->G3_23()[i] * a.z[i]); result.z[i] += vcn.z[i] - * (metric->G3_13[i] * a.x[i] + metric->G3_23[i] * a.y[i] - + metric->G3_33[i] * a.z[i]); + * (metric->G3_13()[i] * a.x[i] + metric->G3_23()[i] * a.y[i] + + metric->G3_33()[i] * a.z[i]); } result.covariant = false; diff --git a/src/field/vector2d.cxx b/src/field/vector2d.cxx index ab2d993549..bf20b130f5 100644 --- a/src/field/vector2d.cxx +++ b/src/field/vector2d.cxx @@ -2,7 +2,7 @@ * Class for 2D vectors. Built on the Field2D class, * all operators relating to vectors are here (none in Field classes) * - * As with Field2D, Vector2D are constant in z (toroidal angle) + * As with Field2D, Vector2D are constant in z (toroidal angle) * * B.Dudson, October 2007 * @@ -10,7 +10,7 @@ * Copyright 2010 B.D.Dudson, S.Farley, M.V.Umansky, X.Q.Xu * * Contact: Ben Dudson, bd512@york.ac.uk - * + * * This file is part of BOUT++. * * BOUT++ is free software: you can redistribute it and/or modify @@ -86,12 +86,12 @@ void Vector2D::toCovariant() { // multiply by g_{ij} BOUT_FOR(i, x.getRegion("RGN_ALL")) { - x[i] = metric_x->g_11[i] * x[i] + metric_x->g_12[i] * y_at_x[i] - + metric_x->g_13[i] * z_at_x[i]; - y[i] = metric_y->g_22[i] * y[i] + metric_y->g_12[i] * x_at_y[i] - + metric_y->g_23[i] * z_at_y[i]; - z[i] = metric_z->g_33[i] * z[i] + metric_z->g_13[i] * x_at_z[i] - + metric_z->g_23[i] * y_at_z[i]; + x[i] = metric_x->g_11()[i] * x[i] + metric_x->g_12()[i] * y_at_x[i] + + metric_x->g_13()[i] * z_at_x[i]; + y[i] = metric_y->g_22()[i] * y[i] + metric_y->g_12()[i] * x_at_y[i] + + metric_y->g_23()[i] * z_at_y[i]; + z[i] = metric_z->g_33()[i] * z[i] + metric_z->g_13()[i] * x_at_z[i] + + metric_z->g_23()[i] * y_at_z[i]; }; } else { const auto metric = localmesh->getCoordinates(location); @@ -100,9 +100,12 @@ void Vector2D::toCovariant() { Coordinates::FieldMetric gx{emptyFrom(x)}, gy{emptyFrom(y)}, gz{emptyFrom(z)}; BOUT_FOR(i, x.getRegion("RGN_ALL")) { - gx[i] = metric->g_11[i] * x[i] + metric->g_12[i] * y[i] + metric->g_13[i] * z[i]; - gy[i] = metric->g_22[i] * y[i] + metric->g_12[i] * x[i] + metric->g_23[i] * z[i]; - gz[i] = metric->g_33[i] * z[i] + metric->g_13[i] * x[i] + metric->g_23[i] * y[i]; + gx[i] = metric->g_11()[i] * x[i] + metric->g_12()[i] * y[i] + + metric->g_13()[i] * z[i]; + gy[i] = metric->g_22()[i] * y[i] + metric->g_12()[i] * x[i] + + metric->g_23()[i] * z[i]; + gz[i] = metric->g_33()[i] * z[i] + metric->g_13()[i] * x[i] + + metric->g_23()[i] * y[i]; }; x = gx; @@ -140,12 +143,12 @@ void Vector2D::toContravariant() { // multiply by g_{ij} BOUT_FOR(i, x.getRegion("RGN_ALL")) { - x[i] = metric_x->g11[i] * x[i] + metric_x->g12[i] * y_at_x[i] - + metric_x->g13[i] * z_at_x[i]; - y[i] = metric_y->g22[i] * y[i] + metric_y->g12[i] * x_at_y[i] - + metric_y->g23[i] * z_at_y[i]; - z[i] = metric_z->g33[i] * z[i] + metric_z->g13[i] * x_at_z[i] - + metric_z->g23[i] * y_at_z[i]; + x[i] = metric_x->g11()[i] * x[i] + metric_x->g12()[i] * y_at_x[i] + + metric_x->g13()[i] * z_at_x[i]; + y[i] = metric_y->g22()[i] * y[i] + metric_y->g12()[i] * x_at_y[i] + + metric_y->g23()[i] * z_at_y[i]; + z[i] = metric_z->g33()[i] * z[i] + metric_z->g13()[i] * x_at_z[i] + + metric_z->g23()[i] * y_at_z[i]; }; } else { @@ -155,9 +158,12 @@ void Vector2D::toContravariant() { Coordinates::FieldMetric gx{emptyFrom(x)}, gy{emptyFrom(y)}, gz{emptyFrom(z)}; BOUT_FOR(i, x.getRegion("RGN_ALL")) { - gx[i] = metric->g11[i] * x[i] + metric->g12[i] * y[i] + metric->g13[i] * z[i]; - gy[i] = metric->g22[i] * y[i] + metric->g12[i] * x[i] + metric->g23[i] * z[i]; - gz[i] = metric->g33[i] * z[i] + metric->g13[i] * x[i] + metric->g23[i] * y[i]; + gx[i] = + metric->g11()[i] * x[i] + metric->g12()[i] * y[i] + metric->g13()[i] * z[i]; + gy[i] = + metric->g22()[i] * y[i] + metric->g12()[i] * x[i] + metric->g23()[i] * z[i]; + gz[i] = + metric->g33()[i] * z[i] + metric->g13()[i] * x[i] + metric->g23()[i] * y[i]; }; x = gx; @@ -197,7 +203,7 @@ Vector2D* Vector2D::timeDeriv() { } /*************************************************************** - * OPERATORS + * OPERATORS ***************************************************************/ /////////////////// ASSIGNMENT //////////////////// @@ -303,7 +309,7 @@ Vector2D& Vector2D::operator/=(const Field2D& rhs) { } /*************************************************************** - * BINARY OPERATORS + * BINARY OPERATORS ***************************************************************/ ////////////////// ADDITION ////////////////////// @@ -390,18 +396,18 @@ const Coordinates::FieldMetric Vector2D::operator*(const Vector2D& rhs) const { if (covariant) { // Both covariant - result = - x * rhs.x * metric->g11 + y * rhs.y * metric->g22 + z * rhs.z * metric->g33; - result += (x * rhs.y + y * rhs.x) * metric->g12 - + (x * rhs.z + z * rhs.x) * metric->g13 - + (y * rhs.z + z * rhs.y) * metric->g23; + result = x * rhs.x * metric->g11() + y * rhs.y * metric->g22() + + z * rhs.z * metric->g33(); + result += (x * rhs.y + y * rhs.x) * metric->g12() + + (x * rhs.z + z * rhs.x) * metric->g13() + + (y * rhs.z + z * rhs.y) * metric->g23(); } else { // Both contravariant - result = - x * rhs.x * metric->g_11 + y * rhs.y * metric->g_22 + z * rhs.z * metric->g_33; - result += (x * rhs.y + y * rhs.x) * metric->g_12 - + (x * rhs.z + z * rhs.x) * metric->g_13 - + (y * rhs.z + z * rhs.y) * metric->g_23; + result = x * rhs.x * metric->g_11() + y * rhs.y * metric->g_22() + + z * rhs.z * metric->g_33(); + result += (x * rhs.y + y * rhs.x) * metric->g_12() + + (x * rhs.z + z * rhs.x) * metric->g_13() + + (y * rhs.z + z * rhs.y) * metric->g_23(); } } diff --git a/src/field/vector3d.cxx b/src/field/vector3d.cxx index 7fb4a88918..7c5a5e3d67 100644 --- a/src/field/vector3d.cxx +++ b/src/field/vector3d.cxx @@ -10,7 +10,7 @@ * Copyright 2010 B.D.Dudson, S.Farley, M.V.Umansky, X.Q.Xu * * Contact: Ben Dudson, bd512@york.ac.uk - * + * * This file is part of BOUT++. * * BOUT++ is free software: you can redistribute it and/or modify @@ -87,12 +87,12 @@ void Vector3D::toCovariant() { // multiply by g_{ij} BOUT_FOR(i, localmesh->getRegion3D("RGN_ALL")) { - x[i] = metric_x->g_11[i] * x[i] + metric_x->g_12[i] * y_at_x[i] - + metric_x->g_13[i] * z_at_x[i]; - y[i] = metric_y->g_22[i] * y[i] + metric_y->g_12[i] * x_at_y[i] - + metric_y->g_23[i] * z_at_y[i]; - z[i] = metric_z->g_33[i] * z[i] + metric_z->g_13[i] * x_at_z[i] - + metric_z->g_23[i] * y_at_z[i]; + x[i] = metric_x->g_11()[i] * x[i] + metric_x->g_12()[i] * y_at_x[i] + + metric_x->g_13()[i] * z_at_x[i]; + y[i] = metric_y->g_22()[i] * y[i] + metric_y->g_12()[i] * x_at_y[i] + + metric_y->g_23()[i] * z_at_y[i]; + z[i] = metric_z->g_33()[i] * z[i] + metric_z->g_13()[i] * x_at_z[i] + + metric_z->g_23()[i] * y_at_z[i]; }; } else { const auto metric = localmesh->getCoordinates(location); @@ -101,9 +101,12 @@ void Vector3D::toCovariant() { Field3D gx{emptyFrom(x)}, gy{emptyFrom(y)}, gz{emptyFrom(z)}; BOUT_FOR(i, localmesh->getRegion3D("RGN_ALL")) { - gx[i] = metric->g_11[i] * x[i] + metric->g_12[i] * y[i] + metric->g_13[i] * z[i]; - gy[i] = metric->g_22[i] * y[i] + metric->g_12[i] * x[i] + metric->g_23[i] * z[i]; - gz[i] = metric->g_33[i] * z[i] + metric->g_13[i] * x[i] + metric->g_23[i] * y[i]; + gx[i] = metric->g_11()[i] * x[i] + metric->g_12()[i] * y[i] + + metric->g_13()[i] * z[i]; + gy[i] = metric->g_22()[i] * y[i] + metric->g_12()[i] * x[i] + + metric->g_23()[i] * z[i]; + gz[i] = metric->g_33()[i] * z[i] + metric->g_13()[i] * x[i] + + metric->g_23()[i] * y[i]; }; x = gx; @@ -141,12 +144,12 @@ void Vector3D::toContravariant() { // multiply by g_{ij} BOUT_FOR(i, localmesh->getRegion3D("RGN_ALL")) { - x[i] = metric_x->g11[i] * x[i] + metric_x->g12[i] * y_at_x[i] - + metric_x->g13[i] * z_at_x[i]; - y[i] = metric_y->g22[i] * y[i] + metric_y->g12[i] * x_at_y[i] - + metric_y->g23[i] * z_at_y[i]; - z[i] = metric_z->g33[i] * z[i] + metric_z->g13[i] * x_at_z[i] - + metric_z->g23[i] * y_at_z[i]; + x[i] = metric_x->g11()[i] * x[i] + metric_x->g12()[i] * y_at_x[i] + + metric_x->g13()[i] * z_at_x[i]; + y[i] = metric_y->g22()[i] * y[i] + metric_y->g12()[i] * x_at_y[i] + + metric_y->g23()[i] * z_at_y[i]; + z[i] = metric_z->g33()[i] * z[i] + metric_z->g13()[i] * x_at_z[i] + + metric_z->g23()[i] * y_at_z[i]; }; } else { @@ -156,9 +159,12 @@ void Vector3D::toContravariant() { Field3D gx{emptyFrom(x)}, gy{emptyFrom(y)}, gz{emptyFrom(z)}; BOUT_FOR(i, localmesh->getRegion3D("RGN_ALL")) { - gx[i] = metric->g11[i] * x[i] + metric->g12[i] * y[i] + metric->g13[i] * z[i]; - gy[i] = metric->g22[i] * y[i] + metric->g12[i] * x[i] + metric->g23[i] * z[i]; - gz[i] = metric->g33[i] * z[i] + metric->g13[i] * x[i] + metric->g23[i] * y[i]; + gx[i] = + metric->g11()[i] * x[i] + metric->g12()[i] * y[i] + metric->g13()[i] * z[i]; + gy[i] = + metric->g22()[i] * y[i] + metric->g12()[i] * x[i] + metric->g23()[i] * z[i]; + gz[i] = + metric->g33()[i] * z[i] + metric->g13()[i] * x[i] + metric->g23()[i] * y[i]; }; x = gx; @@ -199,7 +205,7 @@ Vector3D* Vector3D::timeDeriv() { } /*************************************************************** - * OPERATORS + * OPERATORS ***************************************************************/ /////////////////// ASSIGNMENT //////////////////// @@ -379,9 +385,9 @@ Vector3D& Vector3D::operator/=(const Field3D& rhs) { Coordinates* metric = localmesh->getCoordinates(lhs.getLocation()); \ \ /* calculate contravariant components of cross-product */ \ - result.x = (lco.y * rco.z - lco.z * rco.y) / metric->J; \ - result.y = (lco.z * rco.x - lco.x * rco.z) / metric->J; \ - result.z = (lco.x * rco.y - lco.y * rco.x) / metric->J; \ + result.x = (lco.y * rco.z - lco.z * rco.y) / metric->J(); \ + result.y = (lco.z * rco.x - lco.x * rco.z) / metric->J(); \ + result.z = (lco.x * rco.y - lco.y * rco.x) / metric->J(); \ result.covariant = false; \ \ return result; \ @@ -393,7 +399,7 @@ CROSS(Vector3D, Vector2D, Vector3D) CROSS(Vector2D, Vector2D, Vector2D) /*************************************************************** - * BINARY OPERATORS + * BINARY OPERATORS ***************************************************************/ ////////////////// ADDITION ////////////////////// @@ -482,18 +488,18 @@ const Field3D Vector3D::operator*(const Vector3D& rhs) const { if (covariant) { // Both covariant - result = - x * rhs.x * metric->g11 + y * rhs.y * metric->g22 + z * rhs.z * metric->g33; - result += (x * rhs.y + y * rhs.x) * metric->g12 - + (x * rhs.z + z * rhs.x) * metric->g13 - + (y * rhs.z + z * rhs.y) * metric->g23; + result = x * rhs.x * metric->g11() + y * rhs.y * metric->g22() + + z * rhs.z * metric->g33(); + result += (x * rhs.y + y * rhs.x) * metric->g12() + + (x * rhs.z + z * rhs.x) * metric->g13() + + (y * rhs.z + z * rhs.y) * metric->g23(); } else { // Both contravariant - result = - x * rhs.x * metric->g_11 + y * rhs.y * metric->g_22 + z * rhs.z * metric->g_33; - result += (x * rhs.y + y * rhs.x) * metric->g_12 - + (x * rhs.z + z * rhs.x) * metric->g_13 - + (y * rhs.z + z * rhs.y) * metric->g_23; + result = x * rhs.x * metric->g_11() + y * rhs.y * metric->g_22() + + z * rhs.z * metric->g_33(); + result += (x * rhs.y + y * rhs.x) * metric->g_12() + + (x * rhs.z + z * rhs.x) * metric->g_13() + + (y * rhs.z + z * rhs.y) * metric->g_23(); } } @@ -514,18 +520,18 @@ const Field3D Vector3D::operator*(const Vector2D& rhs) const { Coordinates* metric = x.getCoordinates(location); if (covariant) { // Both covariant - result = - x * rhs.x * metric->g11 + y * rhs.y * metric->g22 + z * rhs.z * metric->g33; - result += (x * rhs.y + y * rhs.x) * metric->g12 - + (x * rhs.z + z * rhs.x) * metric->g13 - + (y * rhs.z + z * rhs.y) * metric->g23; + result = x * rhs.x * metric->g11() + y * rhs.y * metric->g22() + + z * rhs.z * metric->g33(); + result += (x * rhs.y + y * rhs.x) * metric->g12() + + (x * rhs.z + z * rhs.x) * metric->g13() + + (y * rhs.z + z * rhs.y) * metric->g23(); } else { // Both contravariant - result = - x * rhs.x * metric->g_11 + y * rhs.y * metric->g_22 + z * rhs.z * metric->g_33; - result += (x * rhs.y + y * rhs.x) * metric->g_12 - + (x * rhs.z + z * rhs.x) * metric->g_13 - + (y * rhs.z + z * rhs.y) * metric->g_23; + result = x * rhs.x * metric->g_11() + y * rhs.y * metric->g_22() + + z * rhs.z * metric->g_33(); + result += (x * rhs.y + y * rhs.x) * metric->g_12() + + (x * rhs.z + z * rhs.x) * metric->g_13() + + (y * rhs.z + z * rhs.y) * metric->g_23(); } } diff --git a/src/invert/laplace/impls/hypre3d/hypre3d_laplace.cxx b/src/invert/laplace/impls/hypre3d/hypre3d_laplace.cxx index c50be1db85..6a321b0adf 100644 --- a/src/invert/laplace/impls/hypre3d/hypre3d_laplace.cxx +++ b/src/invert/laplace/impls/hypre3d/hypre3d_laplace.cxx @@ -106,8 +106,8 @@ LaplaceHypre3d::LaplaceHypre3d(Options* opt, const CELL_LOC loc, Mesh* mesh_in, BOUT_FOR_SERIAL(i, indexer->getRegionInnerX()) { if (isInnerBoundaryFlagSet(INVERT_AC_GRAD)) { // Neumann on inner X boundary - operator3D(i, i) = -1. / coords->dx[i] / sqrt(coords->g_11[i]); - operator3D(i, i.xp()) = 1. / coords->dx[i] / sqrt(coords->g_11[i]); + operator3D(i, i) = -1. / coords->dx()[i] / sqrt(coords->g_11()[i]); + operator3D(i, i.xp()) = 1. / coords->dx()[i] / sqrt(coords->g_11()[i]); } else { // Dirichlet on inner X boundary operator3D(i, i) = 0.5; @@ -118,8 +118,8 @@ LaplaceHypre3d::LaplaceHypre3d(Options* opt, const CELL_LOC loc, Mesh* mesh_in, BOUT_FOR_SERIAL(i, indexer->getRegionOuterX()) { if (isOuterBoundaryFlagSet(INVERT_AC_GRAD)) { // Neumann on outer X boundary - operator3D(i, i) = 1. / coords->dx[i] / sqrt(coords->g_11[i]); - operator3D(i, i.xm()) = -1. / coords->dx[i] / sqrt(coords->g_11[i]); + operator3D(i, i) = 1. / coords->dx()[i] / sqrt(coords->g_11()[i]); + operator3D(i, i.xm()) = -1. / coords->dx()[i] / sqrt(coords->g_11()[i]); } else { // Dirichlet on outer X boundary operator3D(i, i) = 0.5; @@ -130,8 +130,8 @@ LaplaceHypre3d::LaplaceHypre3d(Options* opt, const CELL_LOC loc, Mesh* mesh_in, BOUT_FOR_SERIAL(i, indexer->getRegionLowerY()) { if ((lower_boundary_flags & INVERT_AC_GRAD) != 0) { // Neumann on lower Y boundary - operator3D(i, i) = -1. / coords->dy[i] / sqrt(coords->g_22[i]); - operator3D(i, i.yp()) = 1. / coords->dy[i] / sqrt(coords->g_22[i]); + operator3D(i, i) = -1. / coords->dy()[i] / sqrt(coords->g_22()[i]); + operator3D(i, i.yp()) = 1. / coords->dy()[i] / sqrt(coords->g_22()[i]); } else { // Dirichlet on lower Y boundary operator3D(i, i) = 0.5; @@ -142,8 +142,8 @@ LaplaceHypre3d::LaplaceHypre3d(Options* opt, const CELL_LOC loc, Mesh* mesh_in, BOUT_FOR_SERIAL(i, indexer->getRegionUpperY()) { if ((upper_boundary_flags & INVERT_AC_GRAD) != 0) { // Neumann on upper Y boundary - operator3D(i, i) = 1. / coords->dy[i] / sqrt(coords->g_22[i]); - operator3D(i, i.ym()) = -1. / coords->dy[i] / sqrt(coords->g_22[i]); + operator3D(i, i) = 1. / coords->dy()[i] / sqrt(coords->g_22()[i]); + operator3D(i, i.ym()) = -1. / coords->dy()[i] / sqrt(coords->g_22()[i]); } else { // Dirichlet on upper Y boundary operator3D(i, i) = 0.5; @@ -276,7 +276,7 @@ void LaplaceHypre3d::updateMatrix3D() { const Field3D dc_dx = issetC ? DDX(C2) : Field3D(); const Field3D dc_dy = issetC ? DDY(C2) : Field3D(); const Field3D dc_dz = issetC ? DDZ(C2) : Field3D(); - const auto dJ_dy = DDY(coords->J / coords->g_22); + const auto dJ_dy = DDY(coords->J() / coords->g_22()); // Set up the matrix for the internal points on the grid. // Boundary conditions were set in the constructor. @@ -285,18 +285,18 @@ void LaplaceHypre3d::updateMatrix3D() { // avoid confusing it with the x-index. // Calculate coefficients for the terms in the differential operator - BoutReal C_df_dx = coords->G1[l]; - BoutReal C_df_dz = coords->G3[l]; + BoutReal C_df_dx = coords->G1()[l]; + BoutReal C_df_dz = coords->G3()[l]; if (issetD) { C_df_dx *= D[l]; C_df_dz *= D[l]; } if (issetC) { - C_df_dx += (coords->g11[l] * dc_dx[l] + coords->g12[l] * dc_dy[l] - + coords->g13[l] * dc_dz[l]) + C_df_dx += (coords->g11()[l] * dc_dx[l] + coords->g12()[l] * dc_dy[l] + + coords->g13()[l] * dc_dz[l]) / C1[l]; - C_df_dz += (coords->g13[l] * dc_dx[l] + coords->g23[l] * dc_dy[l] - + coords->g33[l] * dc_dz[l]) + C_df_dz += (coords->g13()[l] * dc_dx[l] + coords->g23()[l] * dc_dy[l] + + coords->g33()[l] * dc_dz[l]) / C1[l]; } if (issetE) { @@ -304,32 +304,32 @@ void LaplaceHypre3d::updateMatrix3D() { C_df_dz += Ez[l]; } - BoutReal C_d2f_dx2 = coords->g11[l]; - BoutReal C_d2f_dy2 = (coords->g22[l] - 1.0 / coords->g_22[l]); - BoutReal C_d2f_dz2 = coords->g33[l]; + BoutReal C_d2f_dx2 = coords->g11()[l]; + BoutReal C_d2f_dy2 = (coords->g22()[l] - 1.0 / coords->g_22()[l]); + BoutReal C_d2f_dz2 = coords->g33()[l]; if (issetD) { C_d2f_dx2 *= D[l]; C_d2f_dy2 *= D[l]; C_d2f_dz2 *= D[l]; } - BoutReal C_d2f_dxdz = 2 * coords->g13[l]; + BoutReal C_d2f_dxdz = 2 * coords->g13()[l]; if (issetD) { C_d2f_dxdz *= D[l]; } // Adjust the coefficients to include finite-difference factors if (nonuniform) { - C_df_dx += C_d2f_dx2 * coords->d1_dx[l]; + C_df_dx += C_d2f_dx2 * coords->d1_dx()[l]; } - C_df_dx /= 2 * coords->dx[l]; - C_df_dz /= 2 * coords->dz[l]; + C_df_dx /= 2 * coords->dx()[l]; + C_df_dz /= 2 * coords->dz()[l]; - C_d2f_dx2 /= SQ(coords->dx[l]); - C_d2f_dy2 /= SQ(coords->dy[l]); - C_d2f_dz2 /= SQ(coords->dz[l]); + C_d2f_dx2 /= SQ(coords->dx()[l]); + C_d2f_dy2 /= SQ(coords->dy()[l]); + C_d2f_dz2 /= SQ(coords->dz()[l]); - C_d2f_dxdz /= 4 * coords->dx[l] * coords->dz[l]; + C_d2f_dxdz /= 4 * coords->dx()[l] * coords->dz()[l]; operator3D(l, l) = -2 * (C_d2f_dx2 + C_d2f_dy2 + C_d2f_dz2) + A[l]; operator3D(l, l.xp()) = C_df_dx + C_d2f_dx2; @@ -360,24 +360,24 @@ void LaplaceHypre3d::updateMatrix3D() { // Must add these (rather than assign) so that elements used in // interpolation don't overwrite each other. BOUT_FOR_SERIAL(l, indexer->getRegionNobndry()) { - BoutReal C_df_dy = (coords->G2[l] - dJ_dy[l] / coords->J[l]); + BoutReal C_df_dy = (coords->G2()[l] - dJ_dy[l] / coords->J()[l]); if (issetD) { C_df_dy *= D[l]; } if (issetC) { - C_df_dy += - (coords->g12[l] * dc_dx[l] + (coords->g22[l] - 1. / coords->g_22[l]) * dc_dy[l] - + coords->g23[l] * dc_dz[l]) - / C1[l]; + C_df_dy += (coords->g12()[l] * dc_dx[l] + + (coords->g22()[l] - 1. / coords->g_22()[l]) * dc_dy[l] + + coords->g23()[l] * dc_dz[l]) + / C1[l]; } - BoutReal C_d2f_dy2 = (coords->g22[l] - 1.0 / coords->g_22[l]); + BoutReal C_d2f_dy2 = (coords->g22()[l] - 1.0 / coords->g_22()[l]); if (issetD) { C_d2f_dy2 *= D[l]; } - BoutReal C_d2f_dxdy = 2 * coords->g12[l]; - BoutReal C_d2f_dydz = 2 * coords->g23[l]; + BoutReal C_d2f_dxdy = 2 * coords->g12()[l]; + BoutReal C_d2f_dydz = 2 * coords->g23()[l]; if (issetD) { C_d2f_dxdy *= D[l]; C_d2f_dydz *= D[l]; @@ -385,14 +385,14 @@ void LaplaceHypre3d::updateMatrix3D() { // Adjust the coefficients to include finite-difference factors if (nonuniform) { - C_df_dy += C_d2f_dy2 * coords->d1_dy[l]; + C_df_dy += C_d2f_dy2 * coords->d1_dy()[l]; } - C_df_dy /= 2 * coords->dy[l]; - C_d2f_dy2 /= SQ(coords->dy[l]); - C_d2f_dxdy /= 4 * coords->dx[l]; // NOTE: This value is not completed here. It needs - // to be divide by dx(i +/- 1, j, k) when using to - // set a matrix element - C_d2f_dydz /= 4 * coords->dy[l] * coords->dz[l]; + C_df_dy /= 2 * coords->dy()[l]; + C_d2f_dy2 /= SQ(coords->dy()[l]); + C_d2f_dxdy /= 4 * coords->dx()[l]; // NOTE: This value is not completed here. It needs + // to be divide by dx(i +/- 1, j, k) when using to + // set a matrix element + C_d2f_dydz /= 4 * coords->dy()[l] * coords->dz()[l]; // The values stored in the y-boundary are already interpolated // up/down, so we don't want the matrix to do any such @@ -402,10 +402,10 @@ void LaplaceHypre3d::updateMatrix3D() { operator3D.yup(yup)(l, l.yp()) += C_df_dy + C_d2f_dy2; operator3D.ydown(ydown)(l, l.ym()) += -C_df_dy + C_d2f_dy2; - operator3D.yup(yup)(l, l.xp().yp()) += C_d2f_dxdy / coords->dy[l.xp()]; - operator3D.ydown(ydown)(l, l.xp().ym()) += -C_d2f_dxdy / coords->dy[l.xp()]; - operator3D.yup(yup)(l, l.xm().yp()) += -C_d2f_dxdy / coords->dy[l.xm()]; - operator3D.ydown(ydown)(l, l.xm().ym()) += C_d2f_dxdy / coords->dy[l.xm()]; + operator3D.yup(yup)(l, l.xp().yp()) += C_d2f_dxdy / coords->dy()[l.xp()]; + operator3D.ydown(ydown)(l, l.xp().ym()) += -C_d2f_dxdy / coords->dy()[l.xp()]; + operator3D.yup(yup)(l, l.xm().yp()) += -C_d2f_dxdy / coords->dy()[l.xm()]; + operator3D.ydown(ydown)(l, l.xm().ym()) += C_d2f_dxdy / coords->dy()[l.xm()]; operator3D.yup(yup)(l, l.yp().zp()) += C_d2f_dydz; operator3D.yup(yup)(l, l.yp().zm()) += -C_d2f_dydz; operator3D.ydown(ydown)(l, l.ym().zp()) += -C_d2f_dydz; diff --git a/src/invert/laplace/impls/naulin/naulin_laplace.cxx b/src/invert/laplace/impls/naulin/naulin_laplace.cxx index 3faba2f0ea..c5c2a19915 100644 --- a/src/invert/laplace/impls/naulin/naulin_laplace.cxx +++ b/src/invert/laplace/impls/naulin/naulin_laplace.cxx @@ -189,8 +189,8 @@ Field3D LaplaceNaulin::solve(const Field3D& rhs, const Field3D& x0) { Field3D ddx_x = DDX(x_in, location, "C2"); Field3D ddz_x = DDZ(x_in, location, "FFT"); return rhsOverD - - (coords->g11 * coef_x_AC * ddx_x + coords->g33 * coef_z * ddz_x - + coords->g13 * (coef_x_AC * ddz_x + coef_z * ddx_x)) + - (coords->g11() * coef_x_AC * ddx_x + coords->g33() * coef_z * ddz_x + + coords->g13() * (coef_x_AC * ddz_x + coef_z * ddx_x)) - AOverD_AC * x_in; }; diff --git a/src/invert/laplace/impls/petsc/petsc_laplace.cxx b/src/invert/laplace/impls/petsc/petsc_laplace.cxx index 89ba25405b..880471f7b3 100644 --- a/src/invert/laplace/impls/petsc/petsc_laplace.cxx +++ b/src/invert/laplace/impls/petsc/petsc_laplace.cxx @@ -403,16 +403,16 @@ FieldPerp LaplacePetsc::solve(const FieldPerp& b, const FieldPerp& x0) { LaplacePetsc::CoeffsA LaplacePetsc::Coeffs(Ind3D i) { const auto x = i.x(); - BoutReal coef1 = coords->g11[i]; // X 2nd derivative coefficient - BoutReal coef2 = coords->g33[i]; // Z 2nd derivative coefficient - BoutReal coef3 = 2. * coords->g13[i]; // X-Z mixed derivative coefficient + BoutReal coef1 = coords->g11()[i]; // X 2nd derivative coefficient + BoutReal coef2 = coords->g33()[i]; // Z 2nd derivative coefficient + BoutReal coef3 = 2. * coords->g13()[i]; // X-Z mixed derivative coefficient BoutReal coef4 = 0.0; BoutReal coef5 = 0.0; // If global flag all_terms are set (true by default) if (all_terms) { - coef4 = coords->G1[i]; // X 1st derivative - coef5 = coords->G3[i]; // Z 1st derivative + coef4 = coords->G1()[i]; // X 1st derivative + coef5 = coords->G3()[i]; // Z 1st derivative ASSERT3(std::isfinite(coef4)); ASSERT3(std::isfinite(coef5)); @@ -421,14 +421,15 @@ LaplacePetsc::CoeffsA LaplacePetsc::Coeffs(Ind3D i) { if (nonuniform) { // non-uniform mesh correction if ((x != 0) && (x != (localmesh->LocalNx - 1))) { - coef4 -= 0.5 * ((coords->dx[i.xp()] - coords->dx[i.xm()]) / SQ(coords->dx[i])) + coef4 -= 0.5 * ((coords->dx()[i.xp()] - coords->dx()[i.xm()]) / SQ(coords->dx()[i])) * coef1; // BOUT-06 term } } if (localmesh->IncIntShear) { // d2dz2 term - coef2 += coords->g11[i] * coords->IntShiftTorsion[i] * coords->IntShiftTorsion[i]; + coef2 += + coords->g11()[i] * coords->IntShiftTorsion()[i] * coords->IntShiftTorsion()[i]; // Mixed derivative coef3 = 0.0; // This cancels out } @@ -450,19 +451,19 @@ LaplacePetsc::CoeffsA LaplacePetsc::Coeffs(Ind3D i) { if (fourth_order) { // Fourth order discretization of C in x ddx_C = (-C2[i.xpp()] + (8. * C2[i.xp()]) - (8. * C2[i.xm()]) + C2[i.xmm()]) - / (12. * coords->dx[i] * (C1[i])); + / (12. * coords->dx()[i] * (C1[i])); // Fourth order discretization of C in z ddz_C = (-C2[i.zpp()] + (8. * C2[i.zp()]) - (8. * C2[i.zm()]) + C2[i.zmm()]) - / (12. * coords->dz[i] * (C1[i])); + / (12. * coords->dz()[i] * (C1[i])); } else { // Second order discretization of C in x - ddx_C = (C2[i.xp()] - C2[i.xm()]) / (2. * coords->dx[i] * (C1[i])); + ddx_C = (C2[i.xp()] - C2[i.xm()]) / (2. * coords->dx()[i] * (C1[i])); // Second order discretization of C in z - ddz_C = (C2[i.zp()] - C2[i.zm()]) / (2. * coords->dz[i] * (C1[i])); + ddz_C = (C2[i.zp()] - C2[i.zm()]) / (2. * coords->dz()[i] * (C1[i])); } - coef4 += (coords->g11[i] * ddx_C) + (coords->g13[i] * ddz_C); - coef5 += (coords->g13[i] * ddx_C) + (coords->g33[i] * ddz_C); + coef4 += (coords->g11()[i] * ddx_C) + (coords->g13()[i] * ddz_C); + coef5 += (coords->g13()[i] * ddx_C) + (coords->g33()[i] * ddz_C); } } @@ -487,8 +488,8 @@ void LaplacePetsc::setSecondOrderMatrix(int y, bool inner_X_neumann, bool outer_X_neumann) { // Set the boundaries if (inner_X_neumann) { - const auto dx = sliceXZ(coords->dx, y); - const auto g11 = sliceXZ(coords->g11, y); + const auto dx = sliceXZ(coords->dx(), y); + const auto g11 = sliceXZ(coords->g11(), y); BOUT_FOR_SERIAL(i, indexer->getRegionInnerX()) { const auto factor = 1. / dx[i] / std::sqrt(g11[i]); @@ -502,8 +503,8 @@ void LaplacePetsc::setSecondOrderMatrix(int y, bool inner_X_neumann, } } if (outer_X_neumann) { - const auto dx = sliceXZ(coords->dx, y); - const auto g11 = sliceXZ(coords->g11, y); + const auto dx = sliceXZ(coords->dx(), y); + const auto g11 = sliceXZ(coords->g11(), y); BOUT_FOR_SERIAL(i, indexer->getRegionOuterX()) { const auto factor = 1. / dx[i] / std::sqrt(g11[i]); @@ -535,9 +536,9 @@ void LaplacePetsc::setSecondOrderMatrix(int y, bool inner_X_neumann, ASSERT3(std::isfinite(A4)); ASSERT3(std::isfinite(A5)); - const BoutReal dx = coords->dx[i]; + const BoutReal dx = coords->dx()[i]; const BoutReal dx2 = SQ(dx); - const BoutReal dz = coords->dz[i]; + const BoutReal dz = coords->dz()[i]; const BoutReal dz2 = SQ(dz); const BoutReal dxdz = dx * dz; operator2D(l, l) = A0 - (2.0 * ((A1 / dx2) + (A2 / dz2))); @@ -557,8 +558,8 @@ void LaplacePetsc::setFourthOrderMatrix(int y, bool inner_X_neumann, // Set boundaries if (inner_X_neumann) { - const auto dx = sliceXZ(coords->dx, y); - const auto g11 = sliceXZ(coords->g11, y); + const auto dx = sliceXZ(coords->dx(), y); + const auto g11 = sliceXZ(coords->g11(), y); BOUT_FOR_SERIAL(i, indexer->getRegionInnerX()) { const auto factor = 1. / dx[i] / std::sqrt(g11[i]); @@ -579,8 +580,8 @@ void LaplacePetsc::setFourthOrderMatrix(int y, bool inner_X_neumann, } if (outer_X_neumann) { - const auto dx = sliceXZ(coords->dx, y); - const auto g11 = sliceXZ(coords->g11, y); + const auto dx = sliceXZ(coords->dx(), y); + const auto g11 = sliceXZ(coords->g11(), y); BOUT_FOR_SERIAL(i, indexer->getRegionOuterX()) { const auto factor = 1. / dx[i] / std::sqrt(g11[i]); @@ -618,9 +619,9 @@ void LaplacePetsc::setFourthOrderMatrix(int y, bool inner_X_neumann, ASSERT3(std::isfinite(A4)); ASSERT3(std::isfinite(A5)); - const BoutReal dx = coords->dx[i]; + const BoutReal dx = coords->dx()[i]; const BoutReal dx2 = SQ(dx); - const BoutReal dz = coords->dz[i]; + const BoutReal dz = coords->dz()[i]; const BoutReal dz2 = SQ(dz); const BoutReal dxdz = dx * dz; diff --git a/src/invert/laplace/impls/petsc3damg/petsc3damg.cxx b/src/invert/laplace/impls/petsc3damg/petsc3damg.cxx index 9966ad654d..fa73016db0 100644 --- a/src/invert/laplace/impls/petsc3damg/petsc3damg.cxx +++ b/src/invert/laplace/impls/petsc3damg/petsc3damg.cxx @@ -122,7 +122,7 @@ LaplacePetsc3dAmg::LaplacePetsc3dAmg(Options* opt, const CELL_LOC loc, Mesh* mes const bool inner_X_neumann = isInnerBoundaryFlagSet(INVERT_AC_GRAD); if (inner_X_neumann) { // This is a BinaryExpr that is only evaluated when needed - const auto inner_X_BC = -1. / coords->dx / sqrt(coords->g_11); + const auto inner_X_BC = -1. / coords->dx() / sqrt(coords->g_11()); BOUT_FOR_SERIAL(i, indexer->getRegionInnerX()) { const BoutReal bc = inner_X_BC[i]; operator3D(i, i) = bc; @@ -137,7 +137,7 @@ LaplacePetsc3dAmg::LaplacePetsc3dAmg(Options* opt, const CELL_LOC loc, Mesh* mes const bool outer_X_neumann = isOuterBoundaryFlagSet(INVERT_AC_GRAD); if (outer_X_neumann) { - const auto outer_X_BC = 1. / coords->dx / sqrt(coords->g_11); + const auto outer_X_BC = 1. / coords->dx() / sqrt(coords->g_11()); BOUT_FOR_SERIAL(i, indexer->getRegionOuterX()) { const BoutReal bc = outer_X_BC[i]; operator3D(i, i) = bc; @@ -152,7 +152,7 @@ LaplacePetsc3dAmg::LaplacePetsc3dAmg(Options* opt, const CELL_LOC loc, Mesh* mes const bool lower_Y_neumann = flagSet(lower_boundary_flags, INVERT_AC_GRAD); if (lower_Y_neumann) { - const auto lower_Y_BC = -1. / coords->dy / sqrt(coords->g_22); + const auto lower_Y_BC = -1. / coords->dy() / sqrt(coords->g_22()); BOUT_FOR_SERIAL(i, indexer->getRegionLowerY()) { const BoutReal bc = lower_Y_BC[i]; operator3D(i, i) = bc; @@ -167,7 +167,7 @@ LaplacePetsc3dAmg::LaplacePetsc3dAmg(Options* opt, const CELL_LOC loc, Mesh* mes const bool upper_Y_neumann = flagSet(upper_boundary_flags, INVERT_AC_GRAD); if (upper_Y_neumann) { - const auto upper_Y_BC = 1. / coords->dy / sqrt(coords->g_22); + const auto upper_Y_BC = 1. / coords->dy() / sqrt(coords->g_22()); BOUT_FOR_SERIAL(i, indexer->getRegionUpperY()) { const BoutReal bc = upper_Y_BC[i]; operator3D(i, i) = bc; @@ -299,7 +299,7 @@ void LaplacePetsc3dAmg::updateMatrix3D() { const Field3D dc_dx = issetC ? DDX(C2) : Field3D(); const Field3D dc_dy = issetC ? DDY(C2) : Field3D(); const Field3D dc_dz = issetC ? DDZ(C2) : Field3D(); - const auto dJ_dy = DDY(Coordinates::FieldMetric{coords->J / coords->g_22}); + const auto dJ_dy = DDY(Coordinates::FieldMetric{coords->J() / coords->g_22()}); // Set up the matrix for the internal points on the grid. // Boundary conditions were set in the constructor. @@ -308,18 +308,18 @@ void LaplacePetsc3dAmg::updateMatrix3D() { // avoid confusing it with the x-index. // Calculate coefficients for the terms in the differential operator - BoutReal C_df_dx = coords->G1[l]; - BoutReal C_df_dz = coords->G3[l]; + BoutReal C_df_dx = coords->G1()[l]; + BoutReal C_df_dz = coords->G3()[l]; if (issetD) { C_df_dx *= D[l]; C_df_dz *= D[l]; } if (issetC) { - C_df_dx += (coords->g11[l] * dc_dx[l] + coords->g12[l] * dc_dy[l] - + coords->g13[l] * dc_dz[l]) + C_df_dx += (coords->g11()[l] * dc_dx[l] + coords->g12()[l] * dc_dy[l] + + coords->g13()[l] * dc_dz[l]) / C1[l]; - C_df_dz += (coords->g13[l] * dc_dx[l] + coords->g23[l] * dc_dy[l] - + coords->g33[l] * dc_dz[l]) + C_df_dz += (coords->g13()[l] * dc_dx[l] + coords->g23()[l] * dc_dy[l] + + coords->g33()[l] * dc_dz[l]) / C1[l]; } if (issetE) { @@ -327,32 +327,32 @@ void LaplacePetsc3dAmg::updateMatrix3D() { C_df_dz += Ez[l]; } - BoutReal C_d2f_dx2 = coords->g11[l]; - BoutReal C_d2f_dy2 = (coords->g22[l] - 1.0 / coords->g_22[l]); - BoutReal C_d2f_dz2 = coords->g33[l]; + BoutReal C_d2f_dx2 = coords->g11()[l]; + BoutReal C_d2f_dy2 = (coords->g22()[l] - 1.0 / coords->g_22()[l]); + BoutReal C_d2f_dz2 = coords->g33()[l]; if (issetD) { C_d2f_dx2 *= D[l]; C_d2f_dy2 *= D[l]; C_d2f_dz2 *= D[l]; } - BoutReal C_d2f_dxdz = 2 * coords->g13[l]; + BoutReal C_d2f_dxdz = 2 * coords->g13()[l]; if (issetD) { C_d2f_dxdz *= D[l]; } // Adjust the coefficients to include finite-difference factors if (nonuniform) { - C_df_dx += C_d2f_dx2 * coords->d1_dx[l]; + C_df_dx += C_d2f_dx2 * coords->d1_dx()[l]; } - C_df_dx /= 2 * coords->dx[l]; - C_df_dz /= 2 * coords->dz[l]; + C_df_dx /= 2 * coords->dx()[l]; + C_df_dz /= 2 * coords->dz()[l]; - C_d2f_dx2 /= SQ(coords->dx[l]); - C_d2f_dy2 /= SQ(coords->dy[l]); - C_d2f_dz2 /= SQ(coords->dz[l]); + C_d2f_dx2 /= SQ(coords->dx()[l]); + C_d2f_dy2 /= SQ(coords->dy()[l]); + C_d2f_dz2 /= SQ(coords->dz()[l]); - C_d2f_dxdz /= 4 * coords->dx[l] * coords->dz[l]; + C_d2f_dxdz /= 4 * coords->dx()[l] * coords->dz()[l]; operator3D(l, l) = -2 * (C_d2f_dx2 + C_d2f_dy2 + C_d2f_dz2) + A[l]; operator3D(l, l.xp()) = C_df_dx + C_d2f_dx2; @@ -384,24 +384,24 @@ void LaplacePetsc3dAmg::updateMatrix3D() { // Must add these (rather than assign) so that elements used in // interpolation don't overwrite each other. BOUT_FOR_SERIAL(l, indexer->getRegionNobndry()) { - BoutReal C_df_dy = coords->G2[l] - (dJ_dy[l] / coords->J[l]); + BoutReal C_df_dy = coords->G2()[l] - (dJ_dy[l] / coords->J()[l]); if (issetD) { C_df_dy *= D[l]; } if (issetC) { - C_df_dy += - (coords->g12[l] * dc_dx[l] + (coords->g22[l] - 1. / coords->g_22[l]) * dc_dy[l] - + coords->g23[l] * dc_dz[l]) - / C1[l]; + C_df_dy += (coords->g12()[l] * dc_dx[l] + + (coords->g22()[l] - 1. / coords->g_22()[l]) * dc_dy[l] + + coords->g23()[l] * dc_dz[l]) + / C1[l]; } - BoutReal C_d2f_dy2 = coords->g22[l] - (1.0 / coords->g_22[l]); + BoutReal C_d2f_dy2 = coords->g22()[l] - (1.0 / coords->g_22()[l]); if (issetD) { C_d2f_dy2 *= D[l]; } - BoutReal C_d2f_dxdy = 2 * coords->g12[l]; - BoutReal C_d2f_dydz = 2 * coords->g23[l]; + BoutReal C_d2f_dxdy = 2 * coords->g12()[l]; + BoutReal C_d2f_dydz = 2 * coords->g23()[l]; if (issetD) { C_d2f_dxdy *= D[l]; C_d2f_dydz *= D[l]; @@ -409,15 +409,15 @@ void LaplacePetsc3dAmg::updateMatrix3D() { // Adjust the coefficients to include finite-difference factors if (nonuniform) { - C_df_dy += C_d2f_dy2 * coords->d1_dy[l]; + C_df_dy += C_d2f_dy2 * coords->d1_dy()[l]; } - C_df_dy /= 2 * coords->dy[l]; - C_d2f_dy2 /= SQ(coords->dy[l]); + C_df_dy /= 2 * coords->dy()[l]; + C_d2f_dy2 /= SQ(coords->dy()[l]); C_d2f_dxdy /= - 4 * coords->dx[l]; // NOTE: This value is not completed here. It needs to - // be divide by dx(i +/- 1, j, k) when using to set a - // matrix element - C_d2f_dydz /= 4 * coords->dy[l] * coords->dz[l]; + 4 * coords->dx()[l]; // NOTE: This value is not completed here. It needs to + // be divide by dx(i +/- 1, j, k) when using to set a + // matrix element + C_d2f_dydz /= 4 * coords->dy()[l] * coords->dz()[l]; // The values stored in the y-boundary are already interpolated // up/down, so we don't want the matrix to do any such @@ -427,10 +427,10 @@ void LaplacePetsc3dAmg::updateMatrix3D() { operator3D.yup(yup)(l, l.yp()) += C_df_dy + C_d2f_dy2; operator3D.ydown(ydown)(l, l.ym()) += -C_df_dy + C_d2f_dy2; - operator3D.yup(yup)(l, l.xp().yp()) += C_d2f_dxdy / coords->dy[l.xp()]; - operator3D.ydown(ydown)(l, l.xp().ym()) += -C_d2f_dxdy / coords->dy[l.xp()]; - operator3D.yup(yup)(l, l.xm().yp()) += -C_d2f_dxdy / coords->dy[l.xm()]; - operator3D.ydown(ydown)(l, l.xm().ym()) += C_d2f_dxdy / coords->dy[l.xm()]; + operator3D.yup(yup)(l, l.xp().yp()) += C_d2f_dxdy / coords->dy()[l.xp()]; + operator3D.ydown(ydown)(l, l.xp().ym()) += -C_d2f_dxdy / coords->dy()[l.xp()]; + operator3D.yup(yup)(l, l.xm().yp()) += -C_d2f_dxdy / coords->dy()[l.xm()]; + operator3D.ydown(ydown)(l, l.xm().ym()) += C_d2f_dxdy / coords->dy()[l.xm()]; operator3D.yup(yup)(l, l.yp().zp()) += C_d2f_dydz; operator3D.yup(yup)(l, l.yp().zm()) += -C_d2f_dydz; operator3D.ydown(ydown)(l, l.ym().zp()) += -C_d2f_dydz; diff --git a/src/invert/laplace/invert_laplace.cxx b/src/invert/laplace/invert_laplace.cxx index 9af6fd20f9..0ec5f471da 100644 --- a/src/invert/laplace/invert_laplace.cxx +++ b/src/invert/laplace/invert_laplace.cxx @@ -104,7 +104,7 @@ Laplacian::Laplacian(Options* options, const CELL_LOC loc, Mesh* mesh_in, nonuniform = (*options)["nonuniform"] .doc("Use non-uniform grid corrections? Default is the mesh setting.") - .withDefault(coords->non_uniform); + .withDefault(coords->non_uniform()); all_terms = (*options)["all_terms"].doc("Include first derivative terms?").withDefault(true); diff --git a/src/invert/laplacexy/impls/hypre/laplacexy-hypre.cxx b/src/invert/laplacexy/impls/hypre/laplacexy-hypre.cxx index 61632a332d..fbba16673c 100644 --- a/src/invert/laplacexy/impls/hypre/laplacexy-hypre.cxx +++ b/src/invert/laplacexy/impls/hypre/laplacexy-hypre.cxx @@ -113,20 +113,20 @@ void LaplaceXY2Hypre::setCoefs(const Field2D& A, const Field2D& B) { // XX component // Metrics on x+1/2 boundary - BoutReal J = 0.5 * (coords->J[index] + coords->J[ind_xp]); - BoutReal g11 = 0.5 * (coords->g11[index] + coords->g11[ind_xp]); - BoutReal dx = 0.5 * (coords->dx[index] + coords->dx[ind_xp]); + BoutReal J = 0.5 * (coords->J()[index] + coords->J()[ind_xp]); + BoutReal g11 = 0.5 * (coords->g11()[index] + coords->g11()[ind_xp]); + BoutReal dx = 0.5 * (coords->dx()[index] + coords->dx()[ind_xp]); BoutReal Acoef = 0.5 * (A[index] + A[ind_xp]); - BoutReal xp = Acoef * J * g11 / (coords->J[index] * dx * coords->dx[index]); + BoutReal xp = Acoef * J * g11 / (coords->J()[index] * dx * coords->dx()[index]); // Metrics on x-1/2 boundary - J = 0.5 * (coords->J[index] + coords->J[ind_xm]); - g11 = 0.5 * (coords->g11[index] + coords->g11[ind_xm]); - dx = 0.5 * (coords->dx[index] + coords->dx[ind_xm]); + J = 0.5 * (coords->J()[index] + coords->J()[ind_xm]); + g11 = 0.5 * (coords->g11()[index] + coords->g11()[ind_xm]); + dx = 0.5 * (coords->dx()[index] + coords->dx()[ind_xm]); Acoef = 0.5 * (A[index] + A[ind_xm]); - BoutReal xm = Acoef * J * g11 / (coords->J[index] * dx * coords->dx[index]); + BoutReal xm = Acoef * J * g11 / (coords->J()[index] * dx * coords->dx()[index]); BoutReal c = B[index] - xp - xm; // Central coefficient @@ -139,27 +139,27 @@ void LaplaceXY2Hypre::setCoefs(const Field2D& A, const Field2D& B) { // YY component // Metrics at y+1/2 - J = 0.5 * (coords->J[index] + coords->J[ind_yp]); - BoutReal g_22 = 0.5 * (coords->g_22[index] + coords->g_22[ind_yp]); - BoutReal g23 = 0.5 * (coords->g23[index] + coords->g23[ind_yp]); - BoutReal g_23 = 0.5 * (coords->g_23[index] + coords->g_23[ind_yp]); - BoutReal dy = 0.5 * (coords->dy[index] + coords->dy[ind_yp]); + J = 0.5 * (coords->J()[index] + coords->J()[ind_yp]); + BoutReal g_22 = 0.5 * (coords->g_22()[index] + coords->g_22()[ind_yp]); + BoutReal g23 = 0.5 * (coords->g23()[index] + coords->g23()[ind_yp]); + BoutReal g_23 = 0.5 * (coords->g_23()[index] + coords->g_23()[ind_yp]); + BoutReal dy = 0.5 * (coords->dy()[index] + coords->dy()[ind_yp]); Acoef = 0.5 * (A[ind_yp] + A[index]); - BoutReal yp = - -Acoef * J * g23 * g_23 / (g_22 * coords->J[index] * dy * coords->dy[index]); + BoutReal yp = -Acoef * J * g23 * g_23 + / (g_22 * coords->J()[index] * dy * coords->dy()[index]); c -= yp; // Metrics at y-1/2 - J = 0.5 * (coords->J[index] + coords->J[ind_ym]); - g_22 = 0.5 * (coords->g_22[index] + coords->g_22[ind_ym]); - g23 = 0.5 * (coords->g23[index] + coords->g23[ind_ym]); - g_23 = 0.5 * (coords->g_23[index] + coords->g_23[ind_ym]); - dy = 0.5 * (coords->dy[index] + coords->dy[ind_ym]); + J = 0.5 * (coords->J()[index] + coords->J()[ind_ym]); + g_22 = 0.5 * (coords->g_22()[index] + coords->g_22()[ind_ym]); + g23 = 0.5 * (coords->g23()[index] + coords->g23()[ind_ym]); + g_23 = 0.5 * (coords->g_23()[index] + coords->g_23()[ind_ym]); + dy = 0.5 * (coords->dy()[index] + coords->dy()[ind_ym]); Acoef = 0.5 * (A[ind_ym] + A[index]); - BoutReal ym = - -Acoef * J * g23 * g_23 / (g_22 * coords->J[index] * dy * coords->dy[index]); + BoutReal ym = -Acoef * J * g23 * g_23 + / (g_22 * coords->J()[index] * dy * coords->dy()[index]); c -= ym; M(index, ind_yp) = yp; M(index, ind_ym) = ym; diff --git a/src/invert/laplacexy/impls/petsc/laplacexy-petsc.cxx b/src/invert/laplacexy/impls/petsc/laplacexy-petsc.cxx index 04abecfe05..881a0b7fc0 100644 --- a/src/invert/laplacexy/impls/petsc/laplacexy-petsc.cxx +++ b/src/invert/laplacexy/impls/petsc/laplacexy-petsc.cxx @@ -906,13 +906,13 @@ void LaplaceXYpetsc::setMatrixElementsFiniteVolume(const Field2D& A, const Field // (1/J) d/dx ( J * g11 d/dx ) + (1/J) d/dy ( J * g22 d/dy ) auto coords = localmesh->getCoordinates(location); - const Field2D J_DC = DC(coords->J); - const Field2D g11_DC = DC(coords->g11); - const Field2D dx_DC = DC(coords->dx); - const Field2D dy_DC = DC(coords->dy); - const Field2D g_22_DC = DC(coords->g_22); - const Field2D g_23_DC = DC(coords->g_23); - const Field2D g23_DC = DC(coords->g23); + const Field2D J_DC = DC(coords->J()); + const Field2D g11_DC = DC(coords->g11()); + const Field2D dx_DC = DC(coords->dx()); + const Field2D dy_DC = DC(coords->dy()); + const Field2D g_22_DC = DC(coords->g_22()); + const Field2D g_23_DC = DC(coords->g_23()); + const Field2D g23_DC = DC(coords->g23()); for (int x = localmesh->xstart; x <= localmesh->xend; x++) { for (int y = localmesh->ystart; y <= localmesh->yend; y++) { @@ -1017,17 +1017,17 @@ void LaplaceXYpetsc::setMatrixElementsFiniteDifference(const Field2D& A, // + B*f auto coords = localmesh->getCoordinates(location); - const Field2D G1_2D = DC(coords->G1); - const Field2D G2_2D = DC(coords->G2); - const Field2D J_2D = DC(coords->J); - const Field2D g11_2D = DC(coords->g11); - const Field2D g_22_2D = DC(coords->g_22); - const Field2D g22_2D = DC(coords->g22); - const Field2D g12_2D = DC(coords->g12); - const Field2D d1_dx_2D = DC(coords->d1_dx); - const Field2D d1_dy_2D = DC(coords->d1_dy); - const Field2D dx_2D = DC(coords->dx); - const Field2D dy_2D = DC(coords->dy); + const Field2D G1_2D = DC(coords->G1()); + const Field2D G2_2D = DC(coords->G2()); + const Field2D J_2D = DC(coords->J()); + const Field2D g11_2D = DC(coords->g11()); + const Field2D g_22_2D = DC(coords->g_22()); + const Field2D g22_2D = DC(coords->g22()); + const Field2D g12_2D = DC(coords->g12()); + const Field2D d1_dx_2D = DC(coords->d1_dx()); + const Field2D d1_dy_2D = DC(coords->d1_dy()); + const Field2D dx_2D = DC(coords->dx()); + const Field2D dy_2D = DC(coords->dy()); const Field2D coef_dfdy = G2_2D - DC(DDY(J_2D / g_22_2D) / J_2D); diff --git a/src/invert/laplacexy/impls/petsc2/laplacexy-petsc2.cxx b/src/invert/laplacexy/impls/petsc2/laplacexy-petsc2.cxx index 01019466a8..bdfc3bb5ff 100644 --- a/src/invert/laplacexy/impls/petsc2/laplacexy-petsc2.cxx +++ b/src/invert/laplacexy/impls/petsc2/laplacexy-petsc2.cxx @@ -157,20 +157,20 @@ void LaplaceXYpetsc2::setCoefs(const Field2D& A, const Field2D& B) { // XX component // Metrics on x+1/2 boundary - BoutReal J = 0.5 * (coords->J[index] + coords->J[ind_xp]); - BoutReal g11 = 0.5 * (coords->g11[index] + coords->g11[ind_xp]); - BoutReal dx = 0.5 * (coords->dx[index] + coords->dx[ind_xp]); + BoutReal J = 0.5 * (coords->J()[index] + coords->J()[ind_xp]); + BoutReal g11 = 0.5 * (coords->g11()[index] + coords->g11()[ind_xp]); + BoutReal dx = 0.5 * (coords->dx()[index] + coords->dx()[ind_xp]); BoutReal Acoef = 0.5 * (A[index] + A[ind_xp]); - BoutReal xp = Acoef * J * g11 / (coords->J[index] * dx * coords->dx[index]); + BoutReal xp = Acoef * J * g11 / (coords->J()[index] * dx * coords->dx()[index]); // Metrics on x-1/2 boundary - J = 0.5 * (coords->J[index] + coords->J[ind_xm]); - g11 = 0.5 * (coords->g11[index] + coords->g11[ind_xm]); - dx = 0.5 * (coords->dx[index] + coords->dx[ind_xm]); + J = 0.5 * (coords->J()[index] + coords->J()[ind_xm]); + g11 = 0.5 * (coords->g11()[index] + coords->g11()[ind_xm]); + dx = 0.5 * (coords->dx()[index] + coords->dx()[ind_xm]); Acoef = 0.5 * (A[index] + A[ind_xm]); - BoutReal xm = Acoef * J * g11 / (coords->J[index] * dx * coords->dx[index]); + BoutReal xm = Acoef * J * g11 / (coords->J()[index] * dx * coords->dx()[index]); BoutReal c = B[index] - xp - xm; // Central coefficient @@ -183,28 +183,28 @@ void LaplaceXYpetsc2::setCoefs(const Field2D& A, const Field2D& B) { // YY component // Metrics at y+1/2 - J = 0.5 * (coords->J[index] + coords->J[ind_yp]); - BoutReal g_22 = 0.5 * (coords->g_22[index] + coords->g_22[ind_yp]); - BoutReal g23 = 0.5 * (coords->g23[index] + coords->g23[ind_yp]); - BoutReal g_23 = 0.5 * (coords->g_23[index] + coords->g_23[ind_yp]); - BoutReal dy = 0.5 * (coords->dy[index] + coords->dy[ind_yp]); + J = 0.5 * (coords->J()[index] + coords->J()[ind_yp]); + BoutReal g_22 = 0.5 * (coords->g_22()[index] + coords->g_22()[ind_yp]); + BoutReal g23 = 0.5 * (coords->g23()[index] + coords->g23()[ind_yp]); + BoutReal g_23 = 0.5 * (coords->g_23()[index] + coords->g_23()[ind_yp]); + BoutReal dy = 0.5 * (coords->dy()[index] + coords->dy()[ind_yp]); Acoef = 0.5 * (A[ind_yp] + A[index]); - BoutReal yp = - -Acoef * J * g23 * g_23 / (g_22 * coords->J[index] * dy * coords->dy[index]); + BoutReal yp = -Acoef * J * g23 * g_23 + / (g_22 * coords->J()[index] * dy * coords->dy()[index]); c -= yp; matrix(index, ind_yp) = yp; // Metrics at y-1/2 - J = 0.5 * (coords->J[index] + coords->J[ind_ym]); - g_22 = 0.5 * (coords->g_22[index] + coords->g_22[ind_ym]); - g23 = 0.5 * (coords->g23[index] + coords->g23[ind_ym]); - g_23 = 0.5 * (coords->g_23[index] + coords->g_23[ind_ym]); - dy = 0.5 * (coords->dy[index] + coords->dy[ind_ym]); + J = 0.5 * (coords->J()[index] + coords->J()[ind_ym]); + g_22 = 0.5 * (coords->g_22()[index] + coords->g_22()[ind_ym]); + g23 = 0.5 * (coords->g23()[index] + coords->g23()[ind_ym]); + g_23 = 0.5 * (coords->g_23()[index] + coords->g_23()[ind_ym]); + dy = 0.5 * (coords->dy()[index] + coords->dy()[ind_ym]); Acoef = 0.5 * (A[ind_ym] + A[index]); - BoutReal ym = - -Acoef * J * g23 * g_23 / (g_22 * coords->J[index] * dy * coords->dy[index]); + BoutReal ym = -Acoef * J * g23 * g_23 + / (g_22 * coords->J()[index] * dy * coords->dy()[index]); c -= ym; matrix(index, ind_ym) = ym; } diff --git a/src/invert/laplacexz/impls/cyclic/laplacexz-cyclic.cxx b/src/invert/laplacexz/impls/cyclic/laplacexz-cyclic.cxx index b3e619df0c..0a7030dec9 100644 --- a/src/invert/laplacexz/impls/cyclic/laplacexz-cyclic.cxx +++ b/src/invert/laplacexz/impls/cyclic/laplacexz-cyclic.cxx @@ -74,7 +74,7 @@ void LaplaceXZcyclic::setCoefs(const Field2D& A2D, const Field2D& B2D) { Coordinates* coord = localmesh->getCoordinates(location); // NOTE: For now the X-Z terms are omitted, so check that they are small - ASSERT2(max(abs(coord->g13)) < 1e-5); + ASSERT2(max(abs(coord->g13())) < 1e-5); int ind = 0; const BoutReal zlength = getUniform(coord->zlength()); diff --git a/src/invert/parderiv/impls/cyclic/cyclic.cxx b/src/invert/parderiv/impls/cyclic/cyclic.cxx index c32c3d4b2d..6f432e3bcc 100644 --- a/src/invert/parderiv/impls/cyclic/cyclic.cxx +++ b/src/invert/parderiv/impls/cyclic/cyclic.cxx @@ -1,14 +1,14 @@ /************************************************************************ * Inversion of parallel derivatives - * - * Inverts a matrix of the form + * + * Inverts a matrix of the form * * A + B * Grad2_par2 + C*D2DYDZ + + D*D2DZ2 + E*DDY - * + * * Parallel algorithm, using Cyclic Reduction * * Author: Ben Dudson, University of York, Oct 2011 - * + * * Known issues: * ------------ * @@ -17,7 +17,7 @@ * Copyright 2010 B.D.Dudson, S.Farley, M.V.Umansky, X.Q.Xu * * Contact: Ben Dudson, bd512@york.ac.uk - * + * * This file is part of BOUT++. * * BOUT++ is free software: you can redistribute it and/or modify @@ -58,7 +58,7 @@ InvertParCR::InvertParCR(Options* opt, CELL_LOC location, Mesh* mesh_in) // Number of k equations to solve for each x location nsys = 1 + (localmesh->LocalNz) / 2; - sg = sqrt(localmesh->getCoordinates(location)->g_22); + sg = sqrt(localmesh->getCoordinates(location)->g_22()); sg = DDY(1. / sg) / sg; } @@ -160,7 +160,7 @@ const Field3D InvertParCR::solve(const Field3D& f) { BoutReal ecoef = E(x, y + local_ystart) + sg(x, y + local_ystart) * B(x, y + local_ystart); // ddy - if (coord->non_uniform) { + if (coord->non_uniform()) { ecoef += bcoef * coord->d1_dy(x, y + local_ystart); } diff --git a/src/invert/pardiv/impls/cyclic/pardiv_cyclic.cxx b/src/invert/pardiv/impls/cyclic/pardiv_cyclic.cxx index aad01c5f2f..b5aee9d0a1 100644 --- a/src/invert/pardiv/impls/cyclic/pardiv_cyclic.cxx +++ b/src/invert/pardiv/impls/cyclic/pardiv_cyclic.cxx @@ -104,9 +104,9 @@ Field3D InvertParDivCR::solve(const Field3D& f) { auto b = Matrix(nsys, size); auto c = Matrix(nsys, size); - const Field2D dy = coord->dy; - const Field2D J = coord->J; - const Field2D g_22 = coord->g_22; + const Field2D dy = coord->dy(); + const Field2D J = coord->J(); + const Field2D g_22 = coord->g_22(); const auto zlength = getUniform(coord->zlength()); // Loop over flux-surfaces diff --git a/src/mesh/boundary_standard.cxx b/src/mesh/boundary_standard.cxx index 141cab0a43..56eec912b5 100644 --- a/src/mesh/boundary_standard.cxx +++ b/src/mesh/boundary_standard.cxx @@ -2661,8 +2661,8 @@ void BoundaryNeumann::apply([[maybe_unused]] Field2D& f, [[maybe_unused]] BoutRe rfft(f(x - 2 * bx, y), mesh->LocalNz, c1.begin()); c1[0] = c0[0] - c1[0]; // Only need gradient - // Solve metric->g11*d2f/dx2 - metric->g33*kz^2f = 0 - // Assume metric->g11, metric->g33 constant -> exponential growth or decay + // Solve metric->g11()*d2f/dx2 - metric->g33()*kz^2f = 0 + // Assume metric->g11(), metric->g33() constant -> exponential growth or decay // Loop in X towards edge of domain do { @@ -2878,8 +2878,8 @@ void BoundaryNeumann::apply([[maybe_unused]] Field2D& f, [[maybe_unused]] BoutRe c1[jz] = la * c2[jz] + lb * c1[jz] + lc * c0[jz]; } } - // Solve metric->g11*d2f/dx2 - metric->g33*kz^2f = 0 - // Assume metric->g11, metric->g33 constant -> exponential growth or decay + // Solve metric->g11()*d2f/dx2 - metric->g33()*kz^2f = 0 + // Assume metric->g11(), metric->g33() constant -> exponential growth or decay BoutReal xpos = 0.0; // Loop in X towards edge of domain do { @@ -2976,7 +2976,7 @@ void BoundaryNeumann::apply([[maybe_unused]] Field2D& f, [[maybe_unused]] BoutRe var.z(jx + 1, jy, jz) = var.z(jx - 3, jy, jz) + 4. * metric->dx(jx, jy) * tmp; } - // d/dx( Jmetric->g11 B_x ) = - d/dx( Jmetric->g12 B_y + Jmetric->g13 B_z) + // d/dx( Jmetric->g11() B_x ) = - d/dx( Jmetric->g12() B_y + Jmetric->g13() B_z) // - d/dy( JB^y ) - d/dz( JB^z ) tmp = diff --git a/src/mesh/christoffel_symbols.cxx b/src/mesh/christoffel_symbols.cxx new file mode 100644 index 0000000000..943f020f36 --- /dev/null +++ b/src/mesh/christoffel_symbols.cxx @@ -0,0 +1,116 @@ +#include "bout/christoffel_symbols.hxx" +#include "bout/coordinates.hxx" +#include "bout/mesh.hxx" +#include "bout/output.hxx" + +ChristoffelSymbols::ChristoffelSymbols(const Coordinates& coordinates) { + // Calculate Christoffel symbol terms (18 independent values) + // Note: This calculation is completely general: metric + // tensor can be 2D or 3D. For 2D, all DDZ terms are zero + + const auto& contravariantMetricTensor = coordinates.getContravariantMetricTensor(); + const auto& covariantMetricTensor = coordinates.getCovariantMetricTensor(); + + const auto& g11 = contravariantMetricTensor.g11(); + const auto& g22 = contravariantMetricTensor.g22(); + const auto& g33 = contravariantMetricTensor.g33(); + const auto& g12 = contravariantMetricTensor.g12(); + const auto& g13 = contravariantMetricTensor.g13(); + const auto& g23 = contravariantMetricTensor.g23(); + + const auto& g_11 = covariantMetricTensor.g11(); + const auto& g_22 = covariantMetricTensor.g22(); + const auto& g_33 = covariantMetricTensor.g33(); + const auto& g_12 = covariantMetricTensor.g12(); + const auto& g_13 = covariantMetricTensor.g13(); + const auto& g_23 = covariantMetricTensor.g23(); + + G1_11_m = 0.5 * g11 * coordinates.DDX(g_11) + + g12 * (coordinates.DDX(g_12) - 0.5 * coordinates.DDY(g_11)) + + g13 * (coordinates.DDX(g_13) - 0.5 * coordinates.DDZ(g_11)); + G1_22_m = g11 * (coordinates.DDY(g_12) - 0.5 * coordinates.DDX(g_22)) + + 0.5 * g12 * coordinates.DDY(g_22) + + g13 * (coordinates.DDY(g_23) - 0.5 * coordinates.DDZ(g_22)); + G1_33_m = g11 * (coordinates.DDZ(g_13) - 0.5 * coordinates.DDX(g_33)) + + g12 * (coordinates.DDZ(g_23) - 0.5 * coordinates.DDY(g_33)) + + 0.5 * g13 * coordinates.DDZ(g_33); + G1_12_m = + 0.5 * g11 * coordinates.DDY(g_11) + 0.5 * g12 * coordinates.DDX(g_22) + + 0.5 * g13 + * (coordinates.DDY(g_13) + coordinates.DDX(g_23) - coordinates.DDZ(g_12)); + G1_13_m = + 0.5 * g11 * coordinates.DDZ(g_11) + + 0.5 * g12 + * (coordinates.DDZ(g_12) + coordinates.DDX(g_23) - coordinates.DDY(g_13)) + + 0.5 * g13 * coordinates.DDX(g_33); + G1_23_m = + 0.5 * g11 * (coordinates.DDZ(g_12) + coordinates.DDY(g_13) - coordinates.DDX(g_23)) + + 0.5 * g12 + * (coordinates.DDZ(g_22) + coordinates.DDY(g_23) - coordinates.DDY(g_23)) + // + 0.5 *g13*(coordinates.DDZ(g_32) + coordinates.DDY(g_33) - coordinates.DDZ(g_23)); + // which equals + + 0.5 * g13 * coordinates.DDY(g_33); + + G2_11_m = 0.5 * g12 * coordinates.DDX(g_11) + + g22 * (coordinates.DDX(g_12) - 0.5 * coordinates.DDY(g_11)) + + g23 * (coordinates.DDX(g_13) - 0.5 * coordinates.DDZ(g_11)); + G2_22_m = g12 * (coordinates.DDY(g_12) - 0.5 * coordinates.DDX(g_22)) + + 0.5 * g22 * coordinates.DDY(g_22) + + g23 * (coordinates.DDY(g23) - 0.5 * coordinates.DDZ(g_22)); + G2_33_m = g12 * (coordinates.DDZ(g_13) - 0.5 * coordinates.DDX(g_33)) + + g22 * (coordinates.DDZ(g_23) - 0.5 * coordinates.DDY(g_33)) + + 0.5 * g23 * coordinates.DDZ(g_33); + G2_12_m = + 0.5 * g12 * coordinates.DDY(g_11) + 0.5 * g22 * coordinates.DDX(g_22) + + 0.5 * g23 + * (coordinates.DDY(g_13) + coordinates.DDX(g_23) - coordinates.DDZ(g_12)); + G2_13_m = + // 0.5 *g21*(coordinates.DDZ(g_11) + coordinates.DDX(covariantMetricTensor.Getg13()) - coordinates.DDX(g_13)) + // which equals + 0.5 * g12 * (coordinates.DDZ(g_11) + coordinates.DDX(g_13) - coordinates.DDX(g_13)) + // + 0.5 *g22*(coordinates.DDZ(covariantMetricTensor.Getg21()) + coordinates.DDX(g_23) - coordinates.DDY(g_13)) + // which equals + + 0.5 * g22 + * (coordinates.DDZ(g_12) + coordinates.DDX(g_23) - coordinates.DDY(g_13)) + // + 0.5 *g23*(coordinates.DDZ(covariantMetricTensor.Getg31()) + coordinates.DDX(g_33) - coordinates.DDZ(g_13)); + // which equals + + 0.5 * g23 * coordinates.DDX(g_33); + G2_23_m = + 0.5 * g12 * (coordinates.DDZ(g_12) + coordinates.DDY(g_13) - coordinates.DDX(g_23)) + + 0.5 * g22 * coordinates.DDZ(g_22) + 0.5 * g23 * coordinates.DDY(g_33); + + G3_11_m = 0.5 * g13 * coordinates.DDX(g_11) + + g23 * (coordinates.DDX(g_12) - 0.5 * coordinates.DDY(g_11)) + + g33 * (coordinates.DDX(g_13) - 0.5 * coordinates.DDZ(g_11)); + G3_22_m = g13 * (coordinates.DDY(g_12) - 0.5 * coordinates.DDX(g_22)) + + 0.5 * g23 * coordinates.DDY(g_22) + + g33 * (coordinates.DDY(g_23) - 0.5 * coordinates.DDZ(g_22)); + G3_33_m = g13 * (coordinates.DDZ(g_13) - 0.5 * coordinates.DDX(g_33)) + + g23 * (coordinates.DDZ(g_23) - 0.5 * coordinates.DDY(g_33)) + + 0.5 * g33 * coordinates.DDZ(g_33); + G3_12_m = + // 0.5 *g31*(coordinates.DDY(g_11) + coordinates.DDX(covariantMetricTensor.Getg12()) - coordinates.DDX(g_12)) + // which equals to + 0.5 * g13 * coordinates.DDY(g_11) + // + 0.5 *g32*(coordinates.DDY(covariantMetricTensor.Getg21()) + coordinates.DDX(g_22) - coordinates.DDY(g_12)) + // which equals to + + 0.5 * g23 * coordinates.DDX(g_22) + //+ 0.5 *g33*(coordinates.DDY(covariantMetricTensor.Getg31()) + coordinates.DDX(covariantMetricTensor.Getg32()) - coordinates.DDZ(g_12)); + // which equals to + + 0.5 * g33 * (coordinates.DDY(g_13)) + coordinates.DDX(g_23) + - coordinates.DDZ(g_12); + G3_13_m = + 0.5 * g13 * coordinates.DDZ(g_11) + + 0.5 * g23 + * (coordinates.DDZ(g_12) + coordinates.DDX(g_23) - coordinates.DDY(g_13)) + + 0.5 * g33 * coordinates.DDX(g_33); + G3_23_m = 0.5 * g13 * (coordinates.DDZ(g_12) + coordinates.DDY(g_13)) + - coordinates.DDX(g_23) + 0.5 * g23 * coordinates.DDZ(g_22) + + 0.5 * g33 * coordinates.DDY(g_33); + + output_progress.write("\tCommunicating connection terms\n"); + + G1_11_m.getMesh()->communicate(G1_11_m, G1_22_m, G1_33_m, G1_12_m, G1_13_m, G1_23_m, + G2_11_m, G2_22_m, G2_33_m, G2_12_m, G2_13_m, G2_23_m, + G3_11_m, G3_22_m, G3_33_m, G3_12_m, G3_13_m, G3_23_m); +} diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index 3543995702..22b43a9f8e 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -4,9 +4,11 @@ * given the contravariant metric tensor terms **************************************************************************/ +#include "bout/christoffel_symbols.hxx" #include "bout/coordinates_accessor.hxx" #include "bout/field3d.hxx" #include "bout/field_data.hxx" +#include "bout/g_values.hxx" #include #include #include @@ -21,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -37,14 +40,13 @@ #include #include #include -#include #include #include +#include #include #include -#include "invert3x3.hxx" #include "parallel/fci.hxx" #include "parallel/shiftedmetricinterp.hxx" @@ -266,45 +268,38 @@ Coordinates::Coordinates(Mesh* mesh, FieldMetric dx, FieldMetric dy, FieldMetric FieldMetric g_33, FieldMetric g_12, FieldMetric g_13, FieldMetric g_23, FieldMetric ShiftTorsion, FieldMetric IntShiftTorsion) - : dx(std::move(dx)), dy(std::move(dy)), dz(dz), J(std::move(J)), Bxy(std::move(Bxy)), - g11(std::move(g11)), g22(std::move(g22)), g33(std::move(g33)), g12(std::move(g12)), - g13(std::move(g13)), g23(std::move(g23)), g_11(std::move(g_11)), - g_22(std::move(g_22)), g_33(std::move(g_33)), g_12(std::move(g_12)), - g_13(std::move(g_13)), g_23(std::move(g_23)), ShiftTorsion(std::move(ShiftTorsion)), - IntShiftTorsion(std::move(IntShiftTorsion)), nz(mesh->LocalNz), localmesh(mesh), - localoptions(nullptr), location(CELL_CENTRE) {} + : nz(mesh->LocalNz), localmesh(mesh), location(CELL_CENTRE), dx_(std::move(dx)), + dy_(std::move(dy)), dz_(std::move(dz)), ShiftTorsion_(std::move(ShiftTorsion)), + IntShiftTorsion_(std::move(IntShiftTorsion)), + transform(bout::utils::make_unique(*localmesh)), + contravariantMetricTensor(std::move(g11), std::move(g22), std::move(g33), + std::move(g12), std::move(g13), std::move(g23)), + covariantMetricTensor(std::move(g_11), std::move(g_22), std::move(g_33), + std::move(g_12), std::move(g_13), std::move(g_23)), + jacobian_cache(std::make_unique(std::move(J))), Bxy_(std::move(Bxy)) {} Coordinates::Coordinates(Mesh* mesh, Options* options) - : dx(1., mesh), dy(1., mesh), dz(1., mesh), d1_dx(mesh), d1_dy(mesh), d1_dz(mesh), - J(1., mesh), Bxy(1., mesh), - // Identity metric tensor - g11(1., mesh), g22(1., mesh), g33(1., mesh), g12(0, mesh), g13(0, mesh), - g23(0, mesh), g_11(1., mesh), g_22(1., mesh), g_33(1., mesh), g_12(0, mesh), - g_13(0, mesh), g_23(0, mesh), G1_11(mesh), G1_22(mesh), G1_33(mesh), G1_12(mesh), - G1_13(mesh), G1_23(mesh), G2_11(mesh), G2_22(mesh), G2_33(mesh), G2_12(mesh), - G2_13(mesh), G2_23(mesh), G3_11(mesh), G3_22(mesh), G3_33(mesh), G3_12(mesh), - G3_13(mesh), G3_23(mesh), G1(mesh), G2(mesh), G3(mesh), ShiftTorsion(mesh), - IntShiftTorsion(mesh), nz(mesh->LocalNz), localmesh(mesh), localoptions(options), - location(CELL_CENTRE) { + : nz(mesh->LocalNz), localmesh(mesh), localoptions(options), location(CELL_CENTRE), + dx_(1., mesh), dy_(1., mesh), dz_(1., mesh), d1_dx_(mesh), d1_dy_(mesh), + d1_dz_(mesh), ShiftTorsion_(0.0, mesh), IntShiftTorsion_(0.0, mesh), + contravariantMetricTensor(1., 1., 1., 0, 0, 0, mesh), + covariantMetricTensor(1., 1., 1., 0, 0, 0, mesh), Bxy_(1., mesh) { readFromMesh(options, ""); - // Allow transform to fix things up - transform->loadParallelMetrics(this); + + if (transform != nullptr and not transform->canToFromFieldAligned()) { + // Read parallel metrics from gridfile for FCI + readParallelMetricComponents(); + } } Coordinates::Coordinates(Mesh* mesh, Options* options, const CELL_LOC loc, const Coordinates* coords_in, bool force_interpolate_from_centre) - : dx(1., mesh), dy(1., mesh), dz(1., mesh), d1_dx(mesh), d1_dy(mesh), d1_dz(mesh), - J(1., mesh), Bxy(1., mesh), - // Identity metric tensor - g11(1., mesh), g22(1., mesh), g33(1., mesh), g12(0, mesh), g13(0, mesh), - g23(0, mesh), g_11(1., mesh), g_22(1., mesh), g_33(1., mesh), g_12(0, mesh), - g_13(0, mesh), g_23(0, mesh), G1_11(mesh), G1_22(mesh), G1_33(mesh), G1_12(mesh), - G1_13(mesh), G1_23(mesh), G2_11(mesh), G2_22(mesh), G2_33(mesh), G2_12(mesh), - G2_13(mesh), G2_23(mesh), G3_11(mesh), G3_22(mesh), G3_33(mesh), G3_12(mesh), - G3_13(mesh), G3_23(mesh), G1(mesh), G2(mesh), G3(mesh), ShiftTorsion(mesh), - IntShiftTorsion(mesh), nz(mesh->LocalNz), localmesh(mesh), localoptions(options), - location(loc) { + : nz(mesh->LocalNz), localmesh(mesh), localoptions(options), location(loc), + dx_(1., mesh), dy_(1., mesh), dz_(1., mesh), d1_dx_(mesh), d1_dy_(mesh), + d1_dz_(mesh), ShiftTorsion_(0.0, mesh), IntShiftTorsion_(0.0, mesh), + contravariantMetricTensor(1., 1., 1., 0, 0, 0, mesh), + covariantMetricTensor(1., 1., 1., 0, 0, 0, mesh), Bxy_(1., mesh) { const std::string suffix = getLocationSuffix(location); @@ -313,9 +308,9 @@ Coordinates::Coordinates(Mesh* mesh, Options* options, const CELL_LOC loc, } else { // Interpolate fields from coords_in - if (isUniform(coords_in->dz)) { - dz = coords_in->dz; - dz.setLocation(location); + if (isUniform(coords_in->dz_)) { + dz_ = coords_in->dz_; + dz_.setLocation(location); } else { throw BoutException( "We are asked to transform dz to get dz before we have a transform, which " @@ -327,52 +322,39 @@ Coordinates::Coordinates(Mesh* mesh, Options* options, const CELL_LOC loc, return interpolateAndExtrapolate(f, location, true, true, false, transform.get()); }; - dx = interpField(coords_in->dx); - dy = interpField(coords_in->dy); + dx_ = interpField(coords_in->dx_); + dy_ = interpField(coords_in->dy_); // not really needed - we have used dz already ... - dz = interpField(coords_in->dz); - - // Diagonal components of metric tensor g^{ij} - g11 = interpField(coords_in->g11); - g22 = interpField(coords_in->g22); - g33 = interpField(coords_in->g33); - - // Off-diagonal elements. - g12 = interpField(coords_in->g12); - g13 = interpField(coords_in->g13); - g23 = interpField(coords_in->g23); - - // 3x3 matrix inversion can exaggerate small interpolation errors, so it is - // more robust to interpolate and extrapolate derived quantities directly, - // rather than deriving from interpolated/extrapolated covariant metric - // components - g_11 = interpField(coords_in->g_11); - g_22 = interpField(coords_in->g_22); - g_33 = interpField(coords_in->g_33); - g_12 = interpField(coords_in->g_12); - g_13 = interpField(coords_in->g_13); - g_23 = interpField(coords_in->g_23); + dz_ = interpField(coords_in->dz_); + + setMetricTensor(coords_in->getContravariantMetricTensor(), + coords_in->getCovariantMetricTensor()); + + contravariantMetricTensor.map(interpField); + covariantMetricTensor.map(interpField); // Check input metrics checkContravariant(); checkCovariant(); - J = interpField(coords_in->J); - Bxy = interpField(coords_in->Bxy); + setJ(interpField(coords_in->J())); + setBxy(interpField(coords_in->Bxy())); - bout::checkFinite(J, "The Jacobian", "RGN_NOCORNERS"); - bout::checkPositive(J, "The Jacobian", "RGN_NOCORNERS"); - bout::checkFinite(Bxy, "Bxy", "RGN_NOCORNERS"); - bout::checkPositive(Bxy, "Bxy", "RGN_NOCORNERS"); + bout::checkFinite(J(), "The Jacobian", "RGN_NOCORNERS"); + bout::checkPositive(J(), "The Jacobian", "RGN_NOCORNERS"); + bout::checkFinite(Bxy(), "Bxy", "RGN_NOCORNERS"); + bout::checkPositive(Bxy(), "Bxy", "RGN_NOCORNERS"); - ShiftTorsion = interpField(coords_in->ShiftTorsion); + ShiftTorsion_ = interpField(coords_in->ShiftTorsion_); if (mesh->IncIntShear) { - IntShiftTorsion = interpField(coords_in->IntShiftTorsion); + IntShiftTorsion_ = interpField(coords_in->IntShiftTorsion_); + } + if (not transform->canToFromFieldAligned()) { + // Read parallel metrics from gridfile for FCI + readParallelMetricComponents(); } } - // Allow transform to fix things up - transform->loadParallelMetrics(this); } void Coordinates::readFromMesh(Options* options, const std::string& suffix) { @@ -425,6 +407,7 @@ void Coordinates::readFromMesh(Options* options, const std::string& suffix) { // Passing `field` in here twice is gross but ok because the second argument // is only used when interpolating, and we're not interpolating here fillGuards_impl(field, location, field, extrapolate_x, extrapolate_y); + return field; }; // Read the field, transform if required, and fill in the guards @@ -448,34 +431,27 @@ void Coordinates::readFromMesh(Options* options, const std::string& suffix) { // We can't use the helper functions here because in 3D we might (always?) // need to transform from field aligned -- which requires dz! So we have to // read it "plain" first... - localmesh->get(dz, "dz" + suffix, default_dz, false, location); + localmesh->get(dz_, "dz" + suffix, default_dz, false, location); } // ...then we can set the transform (required early for differentiation)... setParallelTransform(options); // ...and finally we can transform/interpolate/fill in guards - dz = interpolateAndExtrapolate(dz, location, extrapolate_x, extrapolate_y, false, - transform.get()); + setDz(interpolateAndExtrapolate(dz_, location, extrapolate_x, extrapolate_y, false, + transform.get())); // everything else from this point on can use our helper functions - dx = readAndFillGuards("dx", 1.0); - - if (localmesh->periodicX) { - localmesh->communicate_no_slices(dx); - } + setDx(readAndFillGuards("dx", 1.0), localmesh->periodicX); - dy = readAndFillGuards("dy", 1.0); + setDy(readAndFillGuards("dy", 1.0)); // Diagonal components of metric tensor g^{ij} (default to 1) - g11 = readAndFillGuards("g11", 1.0); - g22 = readAndFillGuards("g22", 1.0); - g33 = readAndFillGuards("g33", 1.0); - - // Off-diagonal elements. Default to 0 - g12 = readAndFillGuards("g12", 0.0); - g13 = readAndFillGuards("g13", 0.0); - g23 = readAndFillGuards("g23", 0.0); + contravariantMetricTensor = ContravariantMetricTensor{ + readAndFillGuards("g11", 1.0), readAndFillGuards("g22", 1.0), + readAndFillGuards("g33", 1.0), readAndFillGuards("g12", 0.0), + readAndFillGuards("g13", 0.0), readAndFillGuards("g23", 0.0), + }; // Check input metrics checkContravariant(); @@ -485,115 +461,161 @@ void Coordinates::readFromMesh(Options* options, const std::string& suffix) { auto source_has_component = [&suffix, this](const std::string& name) { return localmesh->sourceHasVar(name + suffix); }; - // Check if any of the components are present - if (std::any_of(begin(covariant_component_names), end(covariant_component_names), - source_has_component)) { - // Check that all components are present - if (std::all_of(begin(covariant_component_names), end(covariant_component_names), - source_has_component)) { - g_11 = readField("g_11", 1.0); - g_22 = readField("g_22", 1.0); - g_33 = readField("g_33", 1.0); - g_12 = readField("g_12", 0.0); - g_13 = readField("g_13", 0.0); - g_23 = readField("g_23", 0.0); - - output_warn.write("\tWARNING! Covariant components of metric tensor set manually. " - "Contravariant components NOT recalculated\n"); - } else { + // Check if any of the components are present, all of them are present + if (const bool all_present = + std::ranges::all_of(covariant_component_names, source_has_component); + std::ranges::any_of(covariant_component_names, source_has_component) + and all_present) { + covariantMetricTensor = CovariantMetricTensor{ + readAndFillGuards("g_11", 1.0), readAndFillGuards("g_22", 1.0), + readAndFillGuards("g_33", 1.0), readAndFillGuards("g_12", 0.0), + readAndFillGuards("g_13", 0.0), readAndFillGuards("g_23", 0.0), + }; + + output_warn.write("\tWARNING! Covariant components of metric tensor set manually. " + "Contravariant components NOT recalculated\n"); + } else { + if (not all_present) { output_warn.write("Not all covariant components of metric tensor found. " "Calculating all from the contravariant tensor\n"); - /// Calculate contravariant metric components if not found - if (calcCovariant("RGN_NOCORNERS") != 0) { - throw BoutException("Error in calcCovariant call"); - } - } - } else { - /// Calculate contravariant metric components if not found - if (calcCovariant("RGN_NOCORNERS") != 0) { - throw BoutException("Error in calcCovariant call"); } + covariantMetricTensor = contravariantMetricTensor.inverse("RGN_NOCORNERS", false); + + // More robust to extrapolate derived quantities directly, rather than + // deriving from extrapolated covariant metric components + covariantMetricTensor.map(fillGuards); } - // More robust to extrapolate derived quantities directly, rather than - // deriving from extrapolated covariant metric components - fillGuards(g_11); - fillGuards(g_22); - fillGuards(g_33); - fillGuards(g_12); - fillGuards(g_13); - fillGuards(g_23); // Check covariant metrics checkCovariant(); - // Calculate Jacobian and Bxy - jacobian(); - // Attempt to read J from the grid file - auto Jcalc = J; - if (localmesh->get(J, "J" + suffix, 0.0, false) != 0) { + auto Jcalc = J(); + FieldMetric J_temp{localmesh}; + if (localmesh->get(J_temp, "J" + suffix, 0.0, false) != 0) { output_warn.write( "\tWARNING: Jacobian 'J' not found. Calculating from metric tensor\n"); - J = Jcalc; } else { checkStaggeredGet(localmesh, "J", suffix); - J = ensuredUnaligned(J); - fillGuards(J); + *jacobian_cache = ensuredUnaligned(J_temp); + fillGuards(*jacobian_cache); // Compare calculated and loaded values - output_warn.write("\tMaximum difference in J is {:e}\n", max(abs(J - Jcalc))); - - localmesh->communicate_no_slices(J); + output_warn.write("\tMaximum difference in J is {:e}\n", max(abs(J() - Jcalc))); - // Re-evaluate Bxy using new J - Bxy = sqrt(g_22) / J; + localmesh->communicate_no_slices(*jacobian_cache); } // Check jacobian - bout::checkFinite(J, "J", "RGN_NOCORNERS"); - bout::checkPositive(J, "J", "RGN_NOCORNERS"); - if (min(abs(J)) < 1.0e-10) { - throw BoutException("\tERROR: Jacobian becomes very small\n"); + bout::checkFinite(J(), "J" + suffix, "RGN_NOCORNERS"); + bout::checkPositive(J(), "J" + suffix, "RGN_NOCORNERS"); + if (min(abs(J())) < 1.0e-10) { + throw BoutException("\tERROR: Jacobian{:s} becomes very small\n", suffix); } // Attempt to read Bxy from the grid file - auto Bcalc = Bxy; - if (localmesh->get(Bxy, "Bxy" + suffix, 0.0, false) != 0) { + const FieldMetric Bcalc = sqrt(g_22()) / J(); + if (localmesh->get(Bxy_, "Bxy" + suffix, 0.0, false) != 0) { output_warn.write("\tWARNING: Magnitude of B field 'Bxy' not found. Calculating from " "metric tensor\n"); - Bxy = Bcalc; + Bxy_ = interpolateAndExtrapolate(Bcalc, location, extrapolate_x, extrapolate_y, false, + transform.get()); } else { checkStaggeredGet(localmesh, "Bxy", suffix); - Bxy = ensuredUnaligned(Bxy); - fillGuards(Bxy); - output_warn.write("\tMaximum difference in Bxy is {:e}\n", max(abs(Bxy - Bcalc))); + Bxy_ = ensuredUnaligned(Bxy_); + fillGuards(Bxy_); + output_warn.write("\tMaximum difference in Bxy is {:e}\n", max(abs(Bxy_ - Bcalc))); } // Check Bxy - bout::checkFinite(Bxy, "Bxy", "RGN_NOCORNERS"); - bout::checkPositive(Bxy, "Bxy", "RGN_NOCORNERS"); + bout::checkFinite(Bxy(), "Bxy" + suffix, "RGN_NOCORNERS"); + bout::checkPositive(Bxy(), "Bxy" + suffix, "RGN_NOCORNERS"); - if (localmesh->get(ShiftTorsion, "ShiftTorsion" + suffix, 0.0, false) != 0) { - output_warn.write( - "\tWARNING: No Torsion specified for zShift. Derivatives may not be correct\n"); - ShiftTorsion = 0.0; + if (not localmesh->sourceHasVar("ShiftTorsion" + suffix)) { + output_warn.write("\tWARNING: No Torsion specified for zShift. " + "Derivatives may not be correct\n"); + } else { + ShiftTorsion_ = readAndFillGuards("ShiftTorsion", 0.0); } - fillGuards(ShiftTorsion); - - ////////////////////////////////////////////////////// if (localmesh->IncIntShear) { - if (localmesh->get(IntShiftTorsion, "IntShiftTorsion", 0.0, false) != 0) { + if (not localmesh->sourceHasVar("IntShiftTorsion" + suffix)) { output_warn.write("\tWARNING: No Integrated torsion specified\n"); + } else { + IntShiftTorsion_ = readAndFillGuards("IntShiftTorsion", 0.0); + } + } +} + +namespace bout { +// Get a unique name for a field based on the sign/magnitude of the offset +std::string parallelSliceFieldName(std::string_view field, int offset) { + using namespace std::string_view_literals; + const std::string_view direction = (offset > 0) ? "forward"sv : "backward"sv; + // We only have a suffix for parallel slices beyond the first + // This is for backwards compatibility + const std::string slice_suffix = + (std::abs(offset) > 1) ? fmt::format("_{}", std::abs(offset)) : ""; + return fmt::format("{}_{}{}", direction, field, slice_suffix); +}; +} // namespace bout + +namespace { +void load_parallel_metric_component(std::string_view name, bout::FieldMetric& component, + int offset) { + Mesh* mesh = component.getMesh(); + bout::FieldMetric tmp{mesh}; + const auto pname = bout::parallelSliceFieldName(name, offset); + if (mesh->get(tmp, pname, 0.0, false) != 0) { + throw BoutException("Could not read {:s} from grid file!\n" + " Fix it up with `zoidberg-update-parallel-metrics `", + pname); + } + if (!component.hasParallelSlices()) { + component.splitParallelSlices(); + component.disallowCalcParallelSlices(); + component.resetRegionParallel(true); + } + auto& pcom = component.ynext(offset); + pcom.allocate(); + BOUT_FOR(i, component.getRegion("RGN_NOBNDRY")) { pcom[i.yp(offset)] = tmp[i]; } +} +} // namespace + +void Coordinates::readParallelMetricComponents() { + if (not bout::build::use_metric_3d) { + return; + } + output_info.write("\tLoading parallel metrics\n"); + const FieldMetric JB0 = J() * Bxy(); + jacobian_cache->splitParallelSlices(); + jacobian_cache->disallowCalcParallelSlices(); + jacobian_cache->resetRegionParallel(true); + for (int i = 1; i <= localmesh->ystart; ++i) { + auto read_offset = [i](std::string_view name, FieldMetric& component) { + load_parallel_metric_component(name, component, -i); + load_parallel_metric_component(name, component, i); + }; + + read_offset("g11", covariantMetricTensor.g11_m); + read_offset("g22", covariantMetricTensor.g22_m); + read_offset("g33", covariantMetricTensor.g33_m); + read_offset("g13", covariantMetricTensor.g13_m); + read_offset("g_11", contravariantMetricTensor.g11_m); + read_offset("g_22", contravariantMetricTensor.g22_m); + read_offset("g_33", contravariantMetricTensor.g33_m); + read_offset("g_13", contravariantMetricTensor.g13_m); + read_offset("dy", dy_); + read_offset("Bxy", Bxy_); + + jacobian_cache->ynext(i).allocate(); + jacobian_cache->ynext(-i).allocate(); + BOUT_FOR(j, JB0.getRegion("RGN_NOBNDRY")) { + jacobian_cache->ynext(i)[j.yp(i)] = JB0[j] / Bxy_.ynext(i)[j.yp(i)]; + jacobian_cache->ynext(-i)[j.yp(-i)] = JB0[j] / Bxy_.ynext(-i)[j.yp(-i)]; } - fillGuards(IntShiftTorsion); - } else { - // IntShiftTorsion will not be used, but set to zero to avoid uninitialized field - IntShiftTorsion = 0.; } - // Allow transform to fix things up - transform->loadParallelMetrics(this); } void Coordinates::outputVars(Options& output_options) { @@ -601,317 +623,96 @@ void Coordinates::outputVars(Options& output_options) { const std::string loc_string = (location == CELL_CENTRE) ? "" : "_" + toString(location); - output_options["dx" + loc_string].force(dx, "Coordinates"); - output_options["dy" + loc_string].force(dy, "Coordinates"); - output_options["dz" + loc_string].force(dz, "Coordinates"); + output_options["dx" + loc_string].force(dx(), "Coordinates"); + output_options["dy" + loc_string].force(dy(), "Coordinates"); + output_options["dz" + loc_string].force(dz(), "Coordinates"); - output_options["g11" + loc_string].force(g11, "Coordinates"); - output_options["g22" + loc_string].force(g22, "Coordinates"); - output_options["g33" + loc_string].force(g33, "Coordinates"); - output_options["g12" + loc_string].force(g12, "Coordinates"); - output_options["g13" + loc_string].force(g13, "Coordinates"); - output_options["g23" + loc_string].force(g23, "Coordinates"); + output_options["g11" + loc_string].force(g11(), "Coordinates"); + output_options["g22" + loc_string].force(g22(), "Coordinates"); + output_options["g33" + loc_string].force(g33(), "Coordinates"); + output_options["g12" + loc_string].force(g12(), "Coordinates"); + output_options["g13" + loc_string].force(g13(), "Coordinates"); + output_options["g23" + loc_string].force(g23(), "Coordinates"); - output_options["g_11" + loc_string].force(g_11, "Coordinates"); - output_options["g_22" + loc_string].force(g_22, "Coordinates"); - output_options["g_33" + loc_string].force(g_33, "Coordinates"); - output_options["g_12" + loc_string].force(g_12, "Coordinates"); - output_options["g_13" + loc_string].force(g_13, "Coordinates"); - output_options["g_23" + loc_string].force(g_23, "Coordinates"); + output_options["g_11" + loc_string].force(g_11(), "Coordinates"); + output_options["g_22" + loc_string].force(g_22(), "Coordinates"); + output_options["g_33" + loc_string].force(g_33(), "Coordinates"); + output_options["g_12" + loc_string].force(g_12(), "Coordinates"); + output_options["g_13" + loc_string].force(g_13(), "Coordinates"); + output_options["g_23" + loc_string].force(g_23(), "Coordinates"); - output_options["J" + loc_string].force(J, "Coordinates"); - output_options["Bxy" + loc_string].force(Bxy, "Coordinates"); + output_options["J" + loc_string].force(J(), "Coordinates"); + output_options["Bxy" + loc_string].force(Bxy(), "Coordinates"); - output_options["G1" + loc_string].force(G1, "Coordinates"); - output_options["G2" + loc_string].force(G2, "Coordinates"); - output_options["G3" + loc_string].force(G3, "Coordinates"); + output_options["G1" + loc_string].force(G1(), "Coordinates"); + output_options["G2" + loc_string].force(G2(), "Coordinates"); + output_options["G3" + loc_string].force(G3(), "Coordinates"); getParallelTransform().outputVars(output_options); } const Field2D& Coordinates::zlength() const { - BOUT_OMP_SAFE(critical) + BOUT_OMP(critical) if (not zlength_cache) { zlength_cache = std::make_unique(0., localmesh); #if BOUT_USE_METRIC_3D - BOUT_FOR_SERIAL(i, dz.getRegion("RGN_ALL")) { (*zlength_cache)[i] += dz[i]; } + BOUT_FOR_SERIAL(i, dz().getRegion("RGN_ALL")) { (*zlength_cache)[i] += dz()[i]; } #else - (*zlength_cache) = dz * nz; + (*zlength_cache) = dz_ * nz; #endif } return *zlength_cache; } -int Coordinates::geometry(bool recalculate_staggered, - bool force_interpolate_from_centre) { - - localmesh->communicate_no_slices(dx, dy, dz, g11, g22, g33, g12, g13, g23, g_11, g_22, - g_33, g_12, g_13, g_23, J, Bxy); - - output_progress.write("Calculating differential geometry terms\n"); - +void Coordinates::setDx(FieldMetric dx, const bool communicate) { if (min(abs(dx)) < 1e-8) { throw BoutException("dx magnitude less than 1e-8"); } + dx_ = std::move(dx); + if (communicate) { + localmesh->communicate_no_slices(dx_); + } +} +void Coordinates::setDy(FieldMetric dy, const bool communicate) { if (min(abs(dy)) < 1e-8) { throw BoutException("dy magnitude less than 1e-8"); } + dy_ = std::move(dy); + if (communicate) { + localmesh->communicate_no_slices(dy_); + } +} +void Coordinates::setDz(FieldMetric dz, const bool communicate) { if (min(abs(dz)) < 1e-8) { throw BoutException("dz magnitude less than 1e-8"); } + dz_ = std::move(dz); + if (communicate) { + localmesh->communicate_no_slices(dz_); + } +} + +void Coordinates::recalculateAndReset(bool recalculate_staggered, + bool force_interpolate_from_centre) { // Check input metrics checkContravariant(); checkCovariant(); - // Calculate Christoffel symbol terms (18 independent values) - // Note: This calculation is completely general: metric - // tensor can be 2D or 3D. For 2D, all DDZ terms are zero - - if (!g11.isFci()) { - G1_11 = 0.5 * g11 * DDX(g_11) + g12 * (DDX(g_12) - 0.5 * DDY(g_11)) - + g13 * (DDX(g_13) - 0.5 * DDZ(g_11)); - G1_22 = g11 * (DDY(g_12) - 0.5 * DDX(g_22)) + 0.5 * g12 * DDY(g_22) - + g13 * (DDY(g_23) - 0.5 * DDZ(g_22)); - G1_33 = g11 * (DDZ(g_13) - 0.5 * DDX(g_33)) + g12 * (DDZ(g_23) - 0.5 * DDY(g_33)) - + 0.5 * g13 * DDZ(g_33); - G1_12 = 0.5 * g11 * DDY(g_11) + 0.5 * g12 * DDX(g_22) - + 0.5 * g13 * (DDY(g_13) + DDX(g_23) - DDZ(g_12)); - G1_13 = 0.5 * g11 * DDZ(g_11) + 0.5 * g12 * (DDZ(g_12) + DDX(g_23) - DDY(g_13)) - + 0.5 * g13 * DDX(g_33); - G1_23 = 0.5 * g11 * (DDZ(g_12) + DDY(g_13) - DDX(g_23)) - + 0.5 * g12 * (DDZ(g_22) + DDY(g_23) - DDY(g_23)) - // + 0.5 *g13*(DDZ(g_32) + DDY(g_33) - DDZ(g_23)); - // which equals - + 0.5 * g13 * DDY(g_33); - - G2_11 = 0.5 * g12 * DDX(g_11) + g22 * (DDX(g_12) - 0.5 * DDY(g_11)) - + g23 * (DDX(g_13) - 0.5 * DDZ(g_11)); - G2_22 = g12 * (DDY(g_12) - 0.5 * DDX(g_22)) + 0.5 * g22 * DDY(g_22) - + g23 * (DDY(g23) - 0.5 * DDZ(g_22)); - G2_33 = g12 * (DDZ(g_13) - 0.5 * DDX(g_33)) + g22 * (DDZ(g_23) - 0.5 * DDY(g_33)) - + 0.5 * g23 * DDZ(g_33); - G2_12 = 0.5 * g12 * DDY(g_11) + 0.5 * g22 * DDX(g_22) - + 0.5 * g23 * (DDY(g_13) + DDX(g_23) - DDZ(g_12)); - G2_13 = - // 0.5 *g21*(DDZ(g_11) + DDX(g_13) - DDX(g_13)) - // which equals - 0.5 * g12 * (DDZ(g_11) + DDX(g_13) - DDX(g_13)) - // + 0.5 *g22*(DDZ(g_21) + DDX(g_23) - DDY(g_13)) - // which equals - + 0.5 * g22 * (DDZ(g_12) + DDX(g_23) - DDY(g_13)) - // + 0.5 *g23*(DDZ(g_31) + DDX(g_33) - DDZ(g_13)); - // which equals - + 0.5 * g23 * DDX(g_33); - G2_23 = 0.5 * g12 * (DDZ(g_12) + DDY(g_13) - DDX(g_23)) + 0.5 * g22 * DDZ(g_22) - + 0.5 * g23 * DDY(g_33); - - G3_11 = 0.5 * g13 * DDX(g_11) + g23 * (DDX(g_12) - 0.5 * DDY(g_11)) - + g33 * (DDX(g_13) - 0.5 * DDZ(g_11)); - G3_22 = g13 * (DDY(g_12) - 0.5 * DDX(g_22)) + 0.5 * g23 * DDY(g_22) - + g33 * (DDY(g_23) - 0.5 * DDZ(g_22)); - G3_33 = g13 * (DDZ(g_13) - 0.5 * DDX(g_33)) + g23 * (DDZ(g_23) - 0.5 * DDY(g_33)) - + 0.5 * g33 * DDZ(g_33); - G3_12 = - // 0.5 *g31*(DDY(g_11) + DDX(g_12) - DDX(g_12)) - // which equals to - 0.5 * g13 * DDY(g_11) - // + 0.5 *g32*(DDY(g_21) + DDX(g_22) - DDY(g_12)) - // which equals to - + 0.5 * g23 * DDX(g_22) - //+ 0.5 *g33*(DDY(g_31) + DDX(g_32) - DDZ(g_12)); - // which equals to - + 0.5 * g33 * (DDY(g_13) + DDX(g_23) - DDZ(g_12)); - G3_13 = 0.5 * g13 * DDZ(g_11) + 0.5 * g23 * (DDZ(g_12) + DDX(g_23) - DDY(g_13)) - + 0.5 * g33 * DDX(g_33); - G3_23 = 0.5 * g13 * (DDZ(g_12) + DDY(g_13) - DDX(g_23)) + 0.5 * g23 * DDZ(g_22) - + 0.5 * g33 * DDY(g_33); - - G1 = (DDX(J * g11) + DDY(J.asField3DParallel() * g12) + DDZ(J * g13)) / J; - G2 = (DDX(J * g12) + DDY(J.asField3DParallel() * g22) + DDZ(J * g23)) / J; - G3 = (DDX(J * g13) + DDY(J.asField3DParallel() * g23) + DDZ(J * g33)) / J; - - // Communicate christoffel symbol terms - output_progress.write("\tCommunicating connection terms\n"); - - localmesh->communicate_no_slices(G1_11, G1_22, G1_33, G1_12, G1_13, G1_23, G2_11, - G2_22, G2_33, G2_12, G2_13, G2_23, G3_11, G3_22, - G3_33, G3_12, G3_13, G3_23, G1, G2, G3); - - // Set boundary guard cells of Christoffel symbol terms - // Ideally, when location is staggered, we would set the upper/outer boundary point - // correctly rather than by extrapolating here: e.g. if location==CELL_YLOW and we are - // at the upper y-boundary the x- and z-derivatives at yend+1 at the boundary can be - // calculated because the guard cells are available, while the y-derivative could be - // calculated from the CELL_CENTRE metric components (which have guard cells available - // past the boundary location). This would avoid the problem that the y-boundary on the - // CELL_YLOW grid is at a 'guard cell' location (yend+1). - // However, the above would require lots of special handling, so just extrapolate for - // now. - G1_11 = interpolateAndExtrapolate(G1_11, location, true, true, true, transform.get()); - G1_22 = interpolateAndExtrapolate(G1_22, location, true, true, true, transform.get()); - G1_33 = interpolateAndExtrapolate(G1_33, location, true, true, true, transform.get()); - G1_12 = interpolateAndExtrapolate(G1_12, location, true, true, true, transform.get()); - G1_13 = interpolateAndExtrapolate(G1_13, location, true, true, true, transform.get()); - G1_23 = interpolateAndExtrapolate(G1_23, location, true, true, true, transform.get()); - - G2_11 = interpolateAndExtrapolate(G2_11, location, true, true, true, transform.get()); - G2_22 = interpolateAndExtrapolate(G2_22, location, true, true, true, transform.get()); - G2_33 = interpolateAndExtrapolate(G2_33, location, true, true, true, transform.get()); - G2_12 = interpolateAndExtrapolate(G2_12, location, true, true, true, transform.get()); - G2_13 = interpolateAndExtrapolate(G2_13, location, true, true, true, transform.get()); - G2_23 = interpolateAndExtrapolate(G2_23, location, true, true, true, transform.get()); - - G3_11 = interpolateAndExtrapolate(G3_11, location, true, true, true, transform.get()); - G3_22 = interpolateAndExtrapolate(G3_22, location, true, true, true, transform.get()); - G3_33 = interpolateAndExtrapolate(G3_33, location, true, true, true, transform.get()); - G3_12 = interpolateAndExtrapolate(G3_12, location, true, true, true, transform.get()); - G3_13 = interpolateAndExtrapolate(G3_13, location, true, true, true, transform.get()); - G3_23 = interpolateAndExtrapolate(G3_23, location, true, true, true, transform.get()); - - G1 = interpolateAndExtrapolate(G1, location, true, true, true, transform.get()); - G2 = interpolateAndExtrapolate(G2, location, true, true, true, transform.get()); - G3 = interpolateAndExtrapolate(G3, location, true, true, true, transform.get()); - } else { - G1_11 = G1_22 = G1_33 = G1_12 = G1_13 = G1_23 = - - G2_11 = G2_22 = G2_33 = G2_12 = G2_13 = G2_23 = - - G3_11 = G3_22 = G3_33 = G3_12 = G3_13 = G3_23 = - - G1 = G2 = G3 = BoutNaN; - } - - ////////////////////////////////////////////////////// - /// Non-uniform meshes. Need to use DDX, DDY - - OPTION(Options::getRoot(), non_uniform, true); - - Coordinates::FieldMetric d2x(localmesh), d2y(localmesh), - d2z(localmesh); // d^2 x / d i^2 - - // Read correction for non-uniform meshes - std::string suffix = getLocationSuffix(location); - if (location == CELL_CENTRE - or (!force_interpolate_from_centre and localmesh->sourceHasVar("dx" + suffix))) { - bool extrapolate_x = not localmesh->sourceHasXBoundaryGuards(); - bool extrapolate_y = not localmesh->sourceHasYBoundaryGuards(); - - if (localmesh->get(d2x, "d2x" + suffix, 0.0, false, location)) { - output_warn.write( - "\tWARNING: differencing quantity 'd2x' not found. Calculating from dx\n"); - d1_dx = bout::derivatives::index::DDX(FieldMetric{1. / dx}); // d/di(1/dx) - - localmesh->communicate_no_slices(d1_dx); - d1_dx = - interpolateAndExtrapolate(d1_dx, location, true, true, true, transform.get()); - } else { - d2x.setLocation(location); - // set boundary cells if necessary - d2x = interpolateAndExtrapolate(d2x, location, extrapolate_x, extrapolate_y, false, - transform.get()); - - d1_dx = -d2x / (dx * dx); - } + christoffel_symbols_cache.reset(); + g_values_cache.reset(); - if (localmesh->get(d2y, "d2y" + suffix, 0.0, false, location)) { - output_warn.write( - "\tWARNING: differencing quantity 'd2y' not found. Calculating from dy\n"); - d1_dy = DDY(1. / dy.asField3DParallel()); // d/di(1/dy) - - localmesh->communicate_no_slices(d1_dy); - d1_dy = - interpolateAndExtrapolate(d1_dy, location, true, true, true, transform.get()); - } else { - d2y.setLocation(location); - // set boundary cells if necessary - d2y = interpolateAndExtrapolate(d2y, location, extrapolate_x, extrapolate_y, false, - transform.get()); - - d1_dy = -d2y / (dy * dy); - } - -#if BOUT_USE_METRIC_3D - if (localmesh->get(d2z, "d2z" + suffix, 0.0, false)) { - output_warn.write( - "\tWARNING: differencing quantity 'd2z' not found. Calculating from dz\n"); - d1_dz = bout::derivatives::index::DDZ(FieldMetric{1. / dz}); - localmesh->communicate_no_slices(d1_dz); - d1_dz = - interpolateAndExtrapolate(d1_dz, location, true, true, true, transform.get()); - } else { - d2z.setLocation(location); - // set boundary cells if necessary - d2z = interpolateAndExtrapolate(d2z, location, extrapolate_x, extrapolate_y, false, - transform.get()); - - d1_dz = -d2z / (dz * dz); - } -#else - d1_dz = 0; -#endif - } else { - if (localmesh->get(d2x, "d2x", 0.0, false)) { - output_warn.write( - "\tWARNING: differencing quantity 'd2x' not found. Calculating from dx\n"); - d1_dx = bout::derivatives::index::DDX(FieldMetric{1. / dx}); // d/di(1/dx) - - localmesh->communicate_no_slices(d1_dx); - d1_dx = - interpolateAndExtrapolate(d1_dx, location, true, true, true, transform.get()); - } else { - // Shift d2x to our location - d2x = interpolateAndExtrapolate(d2x, location, true, true, false, transform.get()); - - d1_dx = -d2x / (dx * dx); - } - - if (localmesh->get(d2y, "d2y", 0.0, false)) { - output_warn.write( - "\tWARNING: differencing quantity 'd2y' not found. Calculating from dy\n"); - d1_dy = DDY(FieldMetric{1. / dy}); // d/di(1/dy) - - localmesh->communicate_no_slices(d1_dy); - d1_dy = - interpolateAndExtrapolate(d1_dy, location, true, true, true, transform.get()); - } else { - // Shift d2y to our location - d2y = interpolateAndExtrapolate(d2y, location, true, true, false, transform.get()); - - d1_dy = -d2y / (dy * dy); - } - -#if BOUT_USE_METRIC_3D - if (localmesh->get(d2z, "d2z", 0.0, false)) { - output_warn.write( - "\tWARNING: differencing quantity 'd2z' not found. Calculating from dz\n"); - d1_dz = bout::derivatives::index::DDZ(FieldMetric{1. / dz}); - - localmesh->communicate_no_slices(d1_dz); - d1_dz = - interpolateAndExtrapolate(d1_dz, location, true, true, true, transform.get()); - } else { - // Shift d2z to our location - d2z = interpolateAndExtrapolate(d2z, location, true, true, false, transform.get()); - - d1_dz = -d2z / (dz * dz); - } -#else - d1_dz = 0; -#endif - } - localmesh->communicate_no_slices(d1_dx, d1_dy, d1_dz); + correctionForNonUniformMeshes(force_interpolate_from_centre); if (location == CELL_CENTRE && recalculate_staggered) { // Re-calculate interpolated Coordinates at staggered locations localmesh->recalculateStaggeredCoordinates(); } - // Invalidate and recalculate cached variables and any accessor zlength_cache.reset(); Grad2_par2_DDY_invSgCache.clear(); invSgCache.reset(); @@ -926,156 +727,102 @@ int Coordinates::geometry(bool recalculate_staggered, _cell_area_zlow.reset(); _cell_area_zhigh.reset(); _cell_volume.reset(); - - return 0; } -int Coordinates::calcCovariant(const std::string& region) { - - // Make sure metric elements are allocated - g_11.allocate(); - g_22.allocate(); - g_33.allocate(); - g_12.allocate(); - g_13.allocate(); - g_23.allocate(); - - g_11.setLocation(location); - g_22.setLocation(location); - g_33.setLocation(location); - g_12.setLocation(location); - g_13.setLocation(location); - g_23.setLocation(location); - - // Perform inversion of g^{ij} to get g_{ij} - // NOTE: Currently this bit assumes that metric terms are Field2D objects - - auto a = Matrix(3, 3); - - BOUT_FOR_SERIAL(i, g11.getRegion(region)) { - a(0, 0) = g11[i]; - a(1, 1) = g22[i]; - a(2, 2) = g33[i]; - - a(0, 1) = a(1, 0) = g12[i]; - a(1, 2) = a(2, 1) = g23[i]; - a(0, 2) = a(2, 0) = g13[i]; - - if (const auto det = bout::invert3x3(a); det.has_value()) { - output_error.write("\tERROR: metric tensor is singular at {}, determinant: {:e}\n", - i, det.value()); - return 1; - } - - g_11[i] = a(0, 0); - g_22[i] = a(1, 1); - g_33[i] = a(2, 2); +void Coordinates::correctionForNonUniformMeshes(bool force_interpolate_from_centre) { + OPTION(Options::getRoot(), non_uniform_, true); - g_12[i] = a(0, 1); - g_13[i] = a(0, 2); - g_23[i] = a(1, 2); - } + FieldMetric d2x(localmesh); + FieldMetric d2y(localmesh); - BoutReal maxerr; - maxerr = BOUTMAX(max(abs((g_11 * g11 + g_12 * g12 + g_13 * g13) - 1)), - max(abs((g_12 * g12 + g_22 * g22 + g_23 * g23) - 1)), - max(abs((g_13 * g13 + g_23 * g23 + g_33 * g33) - 1))); - - output_info.write("\tLocal maximum error in diagonal inversion is {:e}\n", maxerr); - - maxerr = BOUTMAX(max(abs(g_11 * g12 + g_12 * g22 + g_13 * g23)), - max(abs(g_11 * g13 + g_12 * g23 + g_13 * g33)), - max(abs(g_12 * g13 + g_22 * g23 + g_23 * g33))); + // Read correction for non-uniform meshes + const std::string suffix = getLocationSuffix(location); - output_info.write("\tLocal maximum error in off-diagonal inversion is {:e}\n", maxerr); + auto extrapolate_x = true; + auto extrapolate_y = true; + if (location == CELL_CENTRE + or (!force_interpolate_from_centre and localmesh->sourceHasVar("dx" + suffix))) { + extrapolate_x = not localmesh->sourceHasXBoundaryGuards(); + extrapolate_y = not localmesh->sourceHasYBoundaryGuards(); + } - return 0; -} + if (localmesh->get(d2x, "d2x" + suffix, 0.0, false, location) != 0) { + output_warn.write("\tWARNING: differencing quantity 'd2x' not found. " + "Calculating from dx\n"); + d1_dx_ = bout::derivatives::index::DDX(FieldMetric{1. / dx()}); // d/di(1/dx) -int Coordinates::calcContravariant(const std::string& region) { + localmesh->communicate_no_slices(d1_dx_); + d1_dx_ = + interpolateAndExtrapolate(d1_dx_, location, true, true, true, transform.get()); + } else { + d2x.setLocation(location); + // set boundary cells if necessary + d2x = interpolateAndExtrapolate(d2x, location, extrapolate_x, extrapolate_y, false, + transform.get()); - // Make sure metric elements are allocated - g11.allocate(); - g22.allocate(); - g33.allocate(); - g12.allocate(); - g13.allocate(); - g23.allocate(); + d1_dx_ = -d2x / (dx() * dx()); + } - // Perform inversion of g_{ij} to get g^{ij} - // NOTE: Currently this bit assumes that metric terms are Field2D objects + if (localmesh->get(d2y, "d2y" + suffix, 0.0, false, location) != 0) { + output_warn.write("\tWARNING: differencing quantity 'd2y' not found. " + "Calculating from dy\n"); + d1_dy_ = DDY(1. / dy().asField3DParallel()); // d/di(1/dy) - auto a = Matrix(3, 3); + localmesh->communicate_no_slices(d1_dy_); + d1_dy_ = + interpolateAndExtrapolate(d1_dy_, location, true, true, true, transform.get()); + } else { + d2y.setLocation(location); + // set boundary cells if necessary + d2y = interpolateAndExtrapolate(d2y, location, extrapolate_x, extrapolate_y, false, + transform.get()); - BOUT_FOR_SERIAL(i, g_11.getRegion(region)) { - a(0, 0) = g_11[i]; - a(1, 1) = g_22[i]; - a(2, 2) = g_33[i]; + d1_dy_ = -d2y / (dy() * dy()); + } - a(0, 1) = a(1, 0) = g_12[i]; - a(1, 2) = a(2, 1) = g_23[i]; - a(0, 2) = a(2, 0) = g_13[i]; + if (bout::build::use_metric_3d) { + FieldMetric d2z(localmesh); // d^2 x / d i^2 + if (localmesh->get(d2z, "d2z" + suffix, 0.0, false, location) != 0) { + output_warn.write("\tWARNING: differencing quantity 'd2z' not found. " + "Calculating from dz\n"); + d1_dz_ = bout::derivatives::index::DDZ(FieldMetric{1. / dz()}); + localmesh->communicate(d1_dz_); + d1_dz_ = + interpolateAndExtrapolate(d1_dz_, location, true, true, true, transform.get()); + } else { + d2z.setLocation(location); + // set boundary cells if necessary + d2z = interpolateAndExtrapolate(d2z, location, extrapolate_x, extrapolate_y, false, + transform.get()); - if (const auto det = bout::invert3x3(a); det.has_value()) { - output_error.write("\tERROR: metric tensor is singular at {}, determinant: {:e}\n", - i, det.value()); - return 1; + d1_dz_ = -d2z / (dz() * dz()); } - - g11[i] = a(0, 0); - g22[i] = a(1, 1); - g33[i] = a(2, 2); - - g12[i] = a(0, 1); - g13[i] = a(0, 2); - g23[i] = a(1, 2); + } else { + d1_dz_ = 0; } - BoutReal maxerr; - maxerr = BOUTMAX(max(abs((g_11 * g11 + g_12 * g12 + g_13 * g13) - 1)), - max(abs((g_12 * g12 + g_22 * g22 + g_23 * g23) - 1)), - max(abs((g_13 * g13 + g_23 * g23 + g_33 * g33) - 1))); - - output_info.write("\tMaximum error in diagonal inversion is {:e}\n", maxerr); - - maxerr = BOUTMAX(max(abs(g_11 * g12 + g_12 * g22 + g_13 * g23)), - max(abs(g_11 * g13 + g_12 * g23 + g_13 * g33)), - max(abs(g_12 * g13 + g_22 * g23 + g_23 * g33))); - - output_info.write("\tMaximum error in off-diagonal inversion is {:e}\n", maxerr); - return 0; + localmesh->communicate(d1_dx_, d1_dy_, d1_dz_); } -int Coordinates::jacobian() { - +Coordinates::FieldMetric Coordinates::recalculateJacobian() const { // calculate Jacobian using g^-1 = det[g^ij], J = sqrt(g) + const FieldMetric g_matrix = g11() * g22() * g33() + 2.0 * g12() * g13() * g23() + - g11() * g23() * g23() - g22() * g13() * g13() + - g33() * g12() * g12(); - const bool extrapolate_x = not localmesh->sourceHasXBoundaryGuards(); - const bool extrapolate_y = not localmesh->sourceHasYBoundaryGuards(); + bout::checkPositive(g_matrix, "The determinant of g^ij", "RGN_NOBNDRY"); - const FieldMetric g = g11 * g22 * g33 + 2.0 * g12 * g13 * g23 - g11 * g23 * g23 - - g22 * g13 * g13 - g33 * g12 * g12; - - // Check that g is positive - bout::checkPositive(g, "The determinant of g^ij", "RGN_NOBNDRY"); - - J = 1. / sqrt(g); - // More robust to extrapolate derived quantities directly, rather than - // deriving from extrapolated covariant metric components - J = interpolateAndExtrapolate(J, location, extrapolate_x, extrapolate_y, false, - transform.get()); - - Bxy = sqrt(g_22) / J; - Bxy = interpolateAndExtrapolate(Bxy, location, extrapolate_x, extrapolate_y, false, - transform.get()); + return 1. / sqrt(g_matrix); +} - return 0; +Coordinates::FieldMetric Coordinates::recalculateBxy() const { + return sqrt(g_22()) / J(); } namespace { // Utility function for fixing up guard cells of zShift void fixZShiftGuards(Field2D& zShift) { - auto localmesh = zShift.getMesh(); + auto* localmesh = zShift.getMesh(); // extrapolate into boundary guard cells if necessary zShift = interpolateAndExtrapolate(zShift, zShift.getLocation(), @@ -1174,12 +921,12 @@ void Coordinates::setParallelTransform(Options* options) { // Flux Coordinate Independent method const bool fci_zperiodic = (*ptoptions)["z_periodic"].withDefault(true); - transform = - bout::utils::make_unique(*localmesh, dy, fci_zperiodic, ptoptions); - + transform = bout::utils::make_unique(*localmesh, dy(), fci_zperiodic, + ptoptions); } else { - throw BoutException(_("Unrecognised paralleltransform option.\n" - "Valid choices are 'identity', 'shifted', 'fci'")); + throw BoutException(_f("Unrecognised paralleltransform option '{}'.\n" + "Valid choices are 'identity', 'shifted', 'fci'"), + ptstr); } } @@ -1190,19 +937,19 @@ void Coordinates::setParallelTransform(Options* options) { Coordinates::FieldMetric Coordinates::DDX(const Field2D& f, CELL_LOC loc, const std::string& method, - const std::string& region) { + const std::string& region) const { ASSERT1(location == loc || loc == CELL_DEFAULT); - return bout::derivatives::index::DDX(f, loc, method, region) / dx; + return bout::derivatives::index::DDX(f, loc, method, region) / dx(); } Field3D Coordinates::DDX(const Field3D& f, CELL_LOC outloc, const std::string& method, - const std::string& region) { + const std::string& region) const { auto result = bout::derivatives::index::DDX(f, outloc, method, region); - result /= dx; + result /= dx(); if (f.getMesh()->IncIntShear) { // Using BOUT-06 style shifting - result += IntShiftTorsion * DDZ(f, outloc, method, region); + result += IntShiftTorsion() * DDZ(f, outloc, method, region); } return result; @@ -1212,17 +959,17 @@ Coordinates::FieldMetric Coordinates::DDY(const Field2D& f, CELL_LOC loc, const std::string& method, const std::string& region) const { ASSERT1(location == loc || loc == CELL_DEFAULT); - return bout::derivatives::index::DDY(f, loc, method, region) / dy; + return bout::derivatives::index::DDY(f, loc, method, region) / dy(); } Field3D Coordinates::DDY(const Field3DParallel& f, CELL_LOC outloc, const std::string& method, const std::string& region) const { - return bout::derivatives::index::DDY(f, outloc, method, region) / dy; + return bout::derivatives::index::DDY(f, outloc, method, region) / dy(); }; Coordinates::FieldMetric Coordinates::DDZ(const Field2D& f, CELL_LOC loc, const std::string& UNUSED(method), - const std::string& UNUSED(region)) { + const std::string& UNUSED(region)) const { ASSERT1(location == loc || loc == CELL_DEFAULT); ASSERT1(f.getMesh() == localmesh); if (loc == CELL_DEFAULT) { @@ -1231,8 +978,8 @@ Coordinates::FieldMetric Coordinates::DDZ(const Field2D& f, CELL_LOC loc, return zeroFrom(f).setLocation(loc); } Field3D Coordinates::DDZ(const Field3D& f, CELL_LOC outloc, const std::string& method, - const std::string& region) { - return bout::derivatives::index::DDZ(f, outloc, method, region) / dz; + const std::string& region) const { + return bout::derivatives::index::DDZ(f, outloc, method, region) / dz(); }; ///////////////////////////////////////////////////////// @@ -1241,7 +988,6 @@ Field3D Coordinates::DDZ(const Field3D& f, CELL_LOC outloc, const std::string& m Coordinates::FieldMetric Coordinates::Grad_par(const Field2D& var, [[maybe_unused]] CELL_LOC outloc, const std::string& UNUSED(method)) { - ASSERT1(location == outloc || (outloc == CELL_DEFAULT && location == var.getLocation())); @@ -1285,9 +1031,9 @@ Coordinates::FieldMetric Coordinates::Div_par(const Field2D& f, CELL_LOC outloc, // Need Bxy at location of f, which might be different from location of this // Coordinates object - auto Bxy_floc = f.getCoordinates()->Bxy; + auto Bxy_floc = f.getCoordinates()->Bxy(); - return Bxy * Grad_par(FieldMetric{f / Bxy_floc}, outloc, method); + return Bxy_ * Grad_par(FieldMetric{f / Bxy_floc}, outloc, method); } Field3D Coordinates::Div_par(const Field3DParallel& f, CELL_LOC outloc, @@ -1297,9 +1043,9 @@ Field3D Coordinates::Div_par(const Field3DParallel& f, CELL_LOC outloc, // Need Bxy at location of f, which might be different from location of this // Coordinates object - const auto& Bxy_floc = f.getCoordinates()->Bxy; + const auto& Bxy_floc = f.getCoordinates()->Bxy(); - return Bxy * Grad_par(f / Bxy_floc, outloc, method); + return Bxy() * Grad_par(f / Bxy_floc, outloc, method); } ///////////////////////////////////////////////////////// @@ -1312,7 +1058,7 @@ Coordinates::FieldMetric Coordinates::Grad2_par2(const Field2D& f, CELL_LOC outl ASSERT1(location == outloc || (outloc == CELL_DEFAULT && location == f.getLocation())); auto result = Grad2_par2_DDY_invSg(outloc, method) * DDY(f, outloc, method) - + D2DY2(f, outloc, method) / g_22; + + D2DY2(f, outloc, method) / g_22(); return result; } @@ -1327,7 +1073,7 @@ Field3D Coordinates::Grad2_par2(const Field3DParallel& f, CELL_LOC outloc, Field3D result = ::DDY(f, outloc, method); - Field3D r2 = D2DY2(f, outloc, method) / g_22; + Field3D r2 = D2DY2(f, outloc, method) / g_22(); result = Grad2_par2_DDY_invSg(outloc, method) * result + r2; @@ -1346,9 +1092,7 @@ Coordinates::FieldMetric Coordinates::Delp2(const Field2D& f, CELL_LOC outloc, ASSERT1(location == outloc || outloc == CELL_DEFAULT); - auto result = G1 * DDX(f, outloc) + g11 * D2DX2(f, outloc); - - return result; + return G1() * DDX(f, outloc) + g11() * D2DX2(f, outloc); } Field3D Coordinates::Delp2(const Field3D& f, CELL_LOC outloc, bool useFFT) { @@ -1364,7 +1108,7 @@ Field3D Coordinates::Delp2(const Field3D& f, CELL_LOC outloc, bool useFFT) { // copy mesh, location, etc return f * 0; } - ASSERT2(localmesh->xstart > 0); // Need at least one guard cell + ASSERT2(localmesh->xstart > 0); // Need at least one guard cell; Field3D result{emptyFrom(f).setLocation(outloc)}; @@ -1407,9 +1151,10 @@ Field3D Coordinates::Delp2(const Field3D& f, CELL_LOC outloc, bool useFFT) { } } } else { - result = G1 * ::DDX(f, outloc) + G3 * ::DDZ(f, outloc) + g11 * ::D2DX2(f, outloc) - + g33 * ::D2DZ2(f, outloc) + 2 * g13 * ::D2DXDZ(f, outloc); - }; + result = G1() * ::DDX(f, outloc) + G3() * ::DDZ(f, outloc) + + g11() * ::D2DX2(f, outloc) + g33() * ::D2DZ2(f, outloc) + + 2 * g13() * ::D2DXDZ(f, outloc); + } ASSERT2(result.getLocation() == outloc); @@ -1433,7 +1178,7 @@ FieldPerp Coordinates::Delp2(const FieldPerp& f, CELL_LOC outloc, bool useFFT) { FieldPerp result{emptyFrom(f).setLocation(outloc)}; - int jy = f.getIndex(); + const int jy = f.getIndex(); result.setIndex(jy); if (useFFT and localmesh->getNZPE() == 1) { @@ -1480,14 +1225,14 @@ FieldPerp Coordinates::Delp2(const FieldPerp& f, CELL_LOC outloc, bool useFFT) { Coordinates::FieldMetric Coordinates::Laplace_par(const Field2D& f, CELL_LOC outloc) { ASSERT1(location == outloc || outloc == CELL_DEFAULT); - return D2DY2(f, outloc) / g_22 - + DDY(FieldMetric{J / g_22}, outloc) * DDY(f, outloc) / J; + return D2DY2(f, outloc) / g_22() + + DDY(FieldMetric{J() / g_22()}, outloc) * DDY(f, outloc) / J(); } Field3D Coordinates::Laplace_par(const Field3DParallel& f, CELL_LOC outloc) { ASSERT1(location == outloc || outloc == CELL_DEFAULT); - return D2DY2(f, outloc) / g_22 - + DDY(J.asField3DParallel() / g_22, outloc) * ::DDY(f, outloc) / J; + return D2DY2(f, outloc) / g_22() + + DDY(J().asField3DParallel() / g_22(), outloc) * ::DDY(f, outloc) / J(); } // Full Laplacian operator on scalar field @@ -1498,13 +1243,11 @@ Coordinates::FieldMetric Coordinates::Laplace(const Field2D& f, CELL_LOC outloc, ASSERT1(location == outloc || outloc == CELL_DEFAULT); - auto result = G1 * DDX(f, outloc) + G2 * DDY(f, outloc) + g11 * D2DX2(f, outloc) - + g22 * D2DY2(f, outloc) - + 2.0 * g12 - * D2DXDY(f, outloc, "DEFAULT", "RGN_NOBNDRY", - dfdy_boundary_conditions, dfdy_dy_region); - - return result; + return G1() * DDX(f, outloc) + G2() * DDY(f, outloc) + g11() * D2DX2(f, outloc) + + g22() * D2DY2(f, outloc) + + 2.0 * g12() + * ::D2DXDY(f, outloc, "DEFAULT", "RGN_NOBNDRY", dfdy_boundary_conditions, + dfdy_dy_region); } Field3D Coordinates::Laplace(const Field3DParallel& f, CELL_LOC outloc, @@ -1513,23 +1256,20 @@ Field3D Coordinates::Laplace(const Field3DParallel& f, CELL_LOC outloc, ASSERT1(location == outloc || outloc == CELL_DEFAULT); - Field3D result = G1 * ::DDX(f, outloc) + G2 * ::DDY(f, outloc) + G3 * ::DDZ(f, outloc) - + g11 * D2DX2(f, outloc) + g22 * D2DY2(f, outloc) - + g33 * D2DZ2(f, outloc) - + 2.0 - * (g12 - * D2DXDY(f, outloc, "DEFAULT", "RGN_NOBNDRY", - dfdy_boundary_conditions, dfdy_dy_region) - + g13 * D2DXDZ(f, outloc) + g23 * D2DYDZ(f, outloc)); - - return result; + return G1() * ::DDX(f, outloc) + G2() * ::DDY(f, outloc) + G3() * ::DDZ(f, outloc) + + g11() * ::D2DX2(f, outloc) + g22() * ::D2DY2(f, outloc) + + g33() * ::D2DZ2(f, outloc) + + 2.0 + * (g12() + * D2DXDY(f, outloc, "DEFAULT", "RGN_NOBNDRY", + dfdy_boundary_conditions, dfdy_dy_region) + + g13() * ::D2DXDZ(f, outloc) + g23() * ::D2DYDZ(f, outloc)); } // Full perpendicular Laplacian, in form of inverse of Laplacian operator in LaplaceXY // solver Field2D Coordinates::Laplace_perpXY([[maybe_unused]] const Field2D& A, - [[maybe_unused]] const Field2D& f) { - + [[maybe_unused]] const Field2D& f) const { #if not(BOUT_USE_METRIC_3D) Field2D result; result.allocate(); @@ -1539,45 +1279,45 @@ Field2D Coordinates::Laplace_perpXY([[maybe_unused]] const Field2D& A, // outer x boundary const auto outer_x_avg = [&i](const auto& f) { return 0.5 * (f[i] + f[i.xp()]); }; const BoutReal outer_x_A = outer_x_avg(A); - const BoutReal outer_x_J = outer_x_avg(J); - const BoutReal outer_x_g11 = outer_x_avg(g11); - const BoutReal outer_x_dx = outer_x_avg(dx); + const BoutReal outer_x_J = outer_x_avg(J()); + const BoutReal outer_x_g11 = outer_x_avg(g11()); + const BoutReal outer_x_dx = outer_x_avg(dx()); const BoutReal outer_x_value = - outer_x_A * outer_x_J * outer_x_g11 / (J[i] * outer_x_dx * dx[i]); + outer_x_A * outer_x_J * outer_x_g11 / (J()[i] * outer_x_dx * dx()[i]); result[i] += outer_x_value * (f[i.xp()] - f[i]); // inner x boundary const auto inner_x_avg = [&i](const auto& f) { return 0.5 * (f[i] + f[i.xm()]); }; const BoutReal inner_x_A = inner_x_avg(A); - const BoutReal inner_x_J = inner_x_avg(J); - const BoutReal inner_x_g11 = inner_x_avg(g11); - const BoutReal inner_x_dx = inner_x_avg(dx); + const BoutReal inner_x_J = inner_x_avg(J()); + const BoutReal inner_x_g11 = inner_x_avg(g11()); + const BoutReal inner_x_dx = inner_x_avg(dx()); const BoutReal inner_x_value = - inner_x_A * inner_x_J * inner_x_g11 / (J[i] * inner_x_dx * dx[i]); + inner_x_A * inner_x_J * inner_x_g11 / (J()[i] * inner_x_dx * dx()[i]); result[i] += inner_x_value * (f[i.xm()] - f[i]); // upper y boundary const auto upper_y_avg = [&i](const auto& f) { return 0.5 * (f[i] + f[i.yp()]); }; const BoutReal upper_y_A = upper_y_avg(A); - const BoutReal upper_y_J = upper_y_avg(J); - const BoutReal upper_y_g_22 = upper_y_avg(g_22); - const BoutReal upper_y_g23 = upper_y_avg(g23); - const BoutReal upper_y_g_23 = upper_y_avg(g_23); - const BoutReal upper_y_dy = upper_y_avg(dy); + const BoutReal upper_y_J = upper_y_avg(J()); + const BoutReal upper_y_g_22 = upper_y_avg(g_22()); + const BoutReal upper_y_g23 = upper_y_avg(g23()); + const BoutReal upper_y_g_23 = upper_y_avg(g_23()); + const BoutReal upper_y_dy = upper_y_avg(dy()); const BoutReal upper_y_value = -upper_y_A * upper_y_J * upper_y_g23 * upper_y_g_23 - / (upper_y_g_22 * J[i] * upper_y_dy * dy[i]); + / (upper_y_g_22 * J()[i] * upper_y_dy * dy()[i]); result[i] += upper_y_value * (f[i.yp()] - f[i]); // lower y boundary const auto lower_y_avg = [&i](const auto& f) { return 0.5 * (f[i] + f[i.ym()]); }; const BoutReal lower_y_A = lower_y_avg(A); - const BoutReal lower_y_J = lower_y_avg(J); - const BoutReal lower_y_g_22 = lower_y_avg(g_22); - const BoutReal lower_y_g23 = lower_y_avg(g23); - const BoutReal lower_y_g_23 = lower_y_avg(g_23); - const BoutReal lower_y_dy = lower_y_avg(dy); + const BoutReal lower_y_J = lower_y_avg(J()); + const BoutReal lower_y_g_22 = lower_y_avg(g_22()); + const BoutReal lower_y_g23 = lower_y_avg(g23()); + const BoutReal lower_y_g_23 = lower_y_avg(g_23()); + const BoutReal lower_y_dy = lower_y_avg(dy()); const BoutReal lower_y_value = -lower_y_A * lower_y_J * lower_y_g23 * lower_y_g_23 - / (lower_y_g_22 * J[i] * lower_y_dy * dy[i]); + / (lower_y_g_22 * J()[i] * lower_y_dy * dy()[i]); result[i] += lower_y_value * (f[i.ym()] - f[i]); } @@ -1587,10 +1327,43 @@ Field2D Coordinates::Laplace_perpXY([[maybe_unused]] const Field2D& A, #endif } +const ChristoffelSymbols& Coordinates::christoffel_symbols() const { + if (christoffel_symbols_cache == nullptr) { + christoffel_symbols_cache = std::make_unique(*this); + // Set boundary guard cells of Christoffel symbol terms + // Ideally, when location is staggered, we would set the upper/outer boundary point + // correctly rather than by extrapolating here: e.g. if location==CELL_YLOW and we are + // at the upper y-boundary the x- and z-derivatives at yend+1 at the boundary can be + // calculated because the guard cells are available, while the y-derivative could be + // calculated from the CELL_CENTRE metric components (which have guard cells available + // past the boundary location). This would avoid the problem that the y-boundary on the + // CELL_YLOW grid is at a 'guard cell' location (yend+1). + // However, the above would require lots of special handling, so just extrapolate for + // now. + + christoffel_symbols_cache->map([this](const FieldMetric& component) { + return interpolateAndExtrapolate(component, location, true, true, false, + transform.get()); + }); + } + return *christoffel_symbols_cache; +} + +GValues& Coordinates::g_values() const { + if (g_values_cache == nullptr) { + g_values_cache = std::make_unique(*this); + g_values_cache->map([this](const FieldMetric& component) { + return interpolateAndExtrapolate(component, location, true, true, true, + transform.get()); + }); + } + return *g_values_cache; +} + const Coordinates::FieldMetric& Coordinates::invSg() const { if (invSgCache == nullptr) { auto ptr = std::make_unique(); - (*ptr) = 1.0 / sqrt(g_22); + (*ptr) = 1.0 / sqrt(g_22()); invSgCache = std::move(ptr); } return *invSgCache; @@ -1598,6 +1371,7 @@ const Coordinates::FieldMetric& Coordinates::invSg() const { const Coordinates::FieldMetric& Coordinates::Grad2_par2_DDY_invSg(CELL_LOC outloc, const std::string& method) const { + if (auto search = Grad2_par2_DDY_invSgCache.find(method); search != Grad2_par2_DDY_invSgCache.end()) { return *search->second; @@ -1615,107 +1389,27 @@ Coordinates::Grad2_par2_DDY_invSg(CELL_LOC outloc, const std::string& method) co return *Grad2_par2_DDY_invSgCache[method]; } -void Coordinates::checkCovariant() { - // Diagonal metric components should be finite - bout::checkFinite(g_11, "g_11", "RGN_NOCORNERS"); - bout::checkFinite(g_22, "g_22", "RGN_NOCORNERS"); - bout::checkFinite(g_33, "g_33", "RGN_NOCORNERS"); - if (g_11.hasParallelSlices() && &g_11.ynext(1) != &g_11) { - for (int dy = 1; dy <= localmesh->ystart; ++dy) { - for (const auto sign : {1, -1}) { - bout::checkFinite(g_11.ynext(sign * dy), "g_11.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - bout::checkFinite(g_22.ynext(sign * dy), "g_22.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - bout::checkFinite(g_33.ynext(sign * dy), "g_33.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - } - } - } - // Diagonal metric components should be positive - bout::checkPositive(g_11, "g_11", "RGN_NOCORNERS"); - bout::checkPositive(g_22, "g_22", "RGN_NOCORNERS"); - bout::checkPositive(g_33, "g_33", "RGN_NOCORNERS"); - if (g_11.hasParallelSlices() && &g_11.ynext(1) != &g_11) { - for (int dy = 1; dy <= localmesh->ystart; ++dy) { - for (const auto sign : {1, -1}) { - bout::checkPositive(g_11.ynext(sign * dy), "g_11.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - bout::checkPositive(g_22.ynext(sign * dy), "g_22.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - bout::checkPositive(g_33.ynext(sign * dy), "g_33.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - } - } - } +void Coordinates::checkCovariant() { covariantMetricTensor.check(localmesh->ystart); } - // Off-diagonal metric components should be finite - bout::checkFinite(g_12, "g_12", "RGN_NOCORNERS"); - bout::checkFinite(g_13, "g_13", "RGN_NOCORNERS"); - bout::checkFinite(g_23, "g_23", "RGN_NOCORNERS"); - if (g_23.hasParallelSlices() && &g_23.ynext(1) != &g_23) { - for (int dy = 1; dy <= localmesh->ystart; ++dy) { - for (const auto sign : {1, -1}) { - bout::checkFinite(g_12.ynext(sign * dy), "g_12.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - bout::checkFinite(g_13.ynext(sign * dy), "g_13.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - bout::checkFinite(g_23.ynext(sign * dy), "g_23.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - } - } - } +void Coordinates::checkContravariant() { + contravariantMetricTensor.check(localmesh->ystart); } -void Coordinates::checkContravariant() { - // Diagonal metric components should be finite - bout::checkFinite(g11, "g11", "RGN_NOCORNERS"); - bout::checkFinite(g22, "g22", "RGN_NOCORNERS"); - bout::checkFinite(g33, "g33", "RGN_NOCORNERS"); - if (g11.hasParallelSlices() && &g11.ynext(1) != &g11) { - for (int dy = 1; dy <= localmesh->ystart; ++dy) { - for (const auto sign : {1, -1}) { - bout::checkFinite(g11.ynext(sign * dy), "g11.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - bout::checkFinite(g22.ynext(sign * dy), "g22.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - bout::checkFinite(g33.ynext(sign * dy), "g33.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - } - } - } - // Diagonal metric components should be positive - bout::checkPositive(g11, "g11", "RGN_NOCORNERS"); - bout::checkPositive(g22, "g22", "RGN_NOCORNERS"); - bout::checkPositive(g33, "g33", "RGN_NOCORNERS"); - if (g11.hasParallelSlices() && &g11.ynext(1) != &g11) { - for (int dy = 1; dy <= localmesh->ystart; ++dy) { - for (const auto sign : {1, -1}) { - bout::checkPositive(g11.ynext(sign * dy), "g11.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - bout::checkPositive(g22.ynext(sign * dy), "g22.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - bout::checkPositive(g33.ynext(sign * dy), "g33.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - } - } +const Coordinates::FieldMetric& Coordinates::J() const { + if (jacobian_cache == nullptr) { + jacobian_cache = std::make_unique(recalculateJacobian()); } + return *jacobian_cache; +} - // Off-diagonal metric components should be finite - bout::checkFinite(g12, "g12", "RGN_NOCORNERS"); - bout::checkFinite(g13, "g13", "RGN_NOCORNERS"); - bout::checkFinite(g23, "g23", "RGN_NOCORNERS"); - if (g23.hasParallelSlices() && &g23.ynext(1) != &g23) { - for (int dy = 1; dy <= localmesh->ystart; ++dy) { - for (const auto sign : {1, -1}) { - bout::checkFinite(g12.ynext(sign * dy), "g12.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - bout::checkFinite(g13.ynext(sign * dy), "g13.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - bout::checkFinite(g23.ynext(sign * dy), "g23.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - } - } +void Coordinates::setJ(const FieldMetric& J, const bool communicate) { + bout::checkFinite(J, "J", "RGN_NOCORNERS"); + bout::checkPositive(J, "J", "RGN_NOCORNERS"); + + //TODO: Calculate J and check value is close + jacobian_cache = std::make_unique(J); + if (communicate) { + localmesh->communicate_no_slices(*jacobian_cache); } } @@ -1726,18 +1420,16 @@ const Coordinates::FieldMetric& Coordinates::g_22_ylow() const { BOUT_OMP_SAFE(critical) { if (!_g_22_ylow.has_value()) { - _g_22_ylow.emplace(emptyFrom(g_22)); - //_g_22_ylow->setLocation(CELL_YLOW); - auto* mesh = Bxy.getMesh(); - if (Bxy.isFci()) { - if (mesh->get(_g_22_ylow.value(), "g_22_cell_ylow", 0.0, false) != 0) { + _g_22_ylow.emplace(emptyFrom(g_22())); + if (Bxy().isFci()) { + if (localmesh->get(_g_22_ylow.value(), "g_22_cell_ylow", 0.0, false) != 0) { throw BoutException("The grid file does not contain `g_22_cell_ylow`."); } } else { - ASSERT0(mesh->ystart > 0); - BOUT_FOR(i, g_22.getRegion("RGN_NOY")) { + ASSERT0(localmesh->ystart > 0); + BOUT_FOR(i, g_22().getRegion("RGN_NOY")) { _g_22_ylow.value()[i] = - SQ(0.5 * (std::sqrt(g_22[i]) + std::sqrt(g_22[i.ym()]))); + SQ(0.5 * (std::sqrt(g_22()[i]) + std::sqrt(g_22()[i.ym()]))); } } } @@ -1752,17 +1444,16 @@ const Coordinates::FieldMetric& Coordinates::g_22_yhigh() const { BOUT_OMP_SAFE(critical) { if (!_g_22_yhigh.has_value()) { - _g_22_yhigh.emplace(emptyFrom(g_22)); - auto* mesh = Bxy.getMesh(); - if (Bxy.isFci()) { - if (mesh->get(_g_22_yhigh.value(), "g_22_cell_yhigh", 0.0, false) != 0) { + _g_22_yhigh.emplace(emptyFrom(g_22())); + if (Bxy().isFci()) { + if (localmesh->get(_g_22_yhigh.value(), "g_22_cell_yhigh", 0.0, false) != 0) { throw BoutException("The grid file does not contain `g_22_cell_yhigh`."); } } else { - ASSERT0(mesh->ystart > 0); - BOUT_FOR(i, g_22.getRegion("RGN_NOY")) { + ASSERT0(localmesh->ystart > 0); + BOUT_FOR(i, g_22().getRegion("RGN_NOY")) { _g_22_yhigh.value()[i] = - SQ(0.5 * (std::sqrt(g_22[i]) + std::sqrt(g_22[i.yp()]))); + SQ(0.5 * (std::sqrt(g_22()[i]) + std::sqrt(g_22()[i.yp()]))); } } } @@ -1774,13 +1465,12 @@ void Coordinates::_compute_cell_area_x() const { BOUT_OMP_SAFE(critical) { if (!_cell_area_xlow.has_value()) { - const FieldMetric area_centre = J / sqrt(g_11) * dy * dz; + const FieldMetric area_centre = J() / sqrt(g_11()) * dy_ * dz_; _cell_area_xlow.emplace(emptyFrom(area_centre)); _cell_area_xhigh.emplace(emptyFrom(area_centre)); // We cannot setLocation, as that would trigger the computation of staggered // metrics. - auto* mesh = Bxy.getMesh(); - ASSERT0(mesh->xstart > 0); + ASSERT0(localmesh->xstart > 0); BOUT_FOR(i, area_centre.getRegion("RGN_NOX")) { (*_cell_area_xlow)[i] = 0.5 * (area_centre[i] + area_centre[i.xm()]); (*_cell_area_xhigh)[i] = 0.5 * (area_centre[i] + area_centre[i.xp()]); @@ -1793,48 +1483,47 @@ void Coordinates::_compute_cell_area_y() const { BOUT_OMP_SAFE(critical) { if (!_cell_area_ylow.has_value()) { - auto* mesh = Bxy.getMesh(); - if (g_11.isFci()) { - const FieldMetric jxz_centre = J / sqrt(g_22); + if (g_11().isFci()) { + const FieldMetric jxz_centre = J() / sqrt(g_22()); auto jxz_ylow = emptyFrom(jxz_centre); auto jxz_yhigh = emptyFrom(jxz_centre); auto By_c = emptyFrom(jxz_centre); auto By_h = emptyFrom(jxz_yhigh); auto By_l = emptyFrom(jxz_ylow); - if (mesh->get(By_c, "By", 0.0, false, CELL_CENTRE) != 0) { + if (localmesh->get(By_c, "By", 0.0, false, CELL_CENTRE) != 0) { throw BoutException("The grid file does not contain `By`."); } - if (mesh->get(By_l, "By_cell_ylow", 0.0, false) != 0) { + if (localmesh->get(By_l, "By_cell_ylow", 0.0, false) != 0) { throw BoutException("The grid file does not contain `By_cell_ylow`."); } - if (mesh->get(By_h, "By_cell_yhigh", 0.0, false) != 0) { + if (localmesh->get(By_h, "By_cell_yhigh", 0.0, false) != 0) { throw BoutException("The grid file does not contain `By_cell_yhigh`."); } BOUT_FOR(i, By_c.getRegion("RGN_NOY")) { jxz_ylow[i] = By_c[i] / By_l[i] * jxz_centre[i]; jxz_yhigh[i] = By_c[i] / By_h[i] * jxz_centre[i]; } - ASSERT3(isUniform(dx, true, "RGN_ALL")); - ASSERT2(isUniform(dx, false, "RGN_ALL")); - ASSERT3(isUniform(dz, true, "RGN_ALL")); - ASSERT2(isUniform(dz, false, "RGN_ALL")); - _cell_area_ylow.emplace(jxz_ylow * dx * dz); - _cell_area_yhigh.emplace(jxz_yhigh * dx * dz); + ASSERT3(isUniform(dx_, true, "RGN_ALL")); + ASSERT2(isUniform(dx_, false, "RGN_ALL")); + ASSERT3(isUniform(dz_, true, "RGN_ALL")); + ASSERT2(isUniform(dz_, false, "RGN_ALL")); + _cell_area_ylow.emplace(jxz_ylow * dx_ * dz_); + _cell_area_yhigh.emplace(jxz_yhigh * dx_ * dz_); } else { // Field aligned - const FieldMetric area_centre = J / sqrt(g_22) * dx * dz; + const FieldMetric area_centre = J() / sqrt(g_22()) * dx_ * dz_; _cell_area_ylow.emplace(emptyFrom(area_centre)); _cell_area_yhigh.emplace(emptyFrom(area_centre)); // We cannot setLocation, as that would trigger the computation of staggered // metrics. - BOUT_FOR(i, mesh->getRegion("RGN_ALL")) { + BOUT_FOR(i, localmesh->getRegion("RGN_ALL")) { if (i.y() > 0) { (*_cell_area_ylow)[i] = 0.5 * (area_centre[i] + area_centre[i.ym()]); } else { (*_cell_area_ylow)[i] = BoutNaN; } - if (i.y() < mesh->LocalNy - 1) { + if (i.y() < localmesh->LocalNy - 1) { (*_cell_area_yhigh)[i] = 0.5 * (area_centre[i] + area_centre[i.yp()]); } else { (*_cell_area_yhigh)[i] = BoutNaN; @@ -1849,7 +1538,7 @@ void Coordinates::_compute_cell_area_z() const { BOUT_OMP_SAFE(critical) { if (!_cell_area_zlow.has_value()) { - const FieldMetric area_centre = J / sqrt(g_33) * dx * dy; + const FieldMetric area_centre = J() / sqrt(g_33()) * dx_ * dy_; _cell_area_zlow.emplace(emptyFrom(area_centre)); _cell_area_zhigh.emplace(emptyFrom(area_centre)); // We cannot setLocation, as that would trigger the computation of staggered @@ -1866,7 +1555,7 @@ void Coordinates::_compute_cell_volume() const { BOUT_OMP_SAFE(critical) { if (!_cell_volume.has_value()) { - _cell_volume.emplace(J * dx * dy * dz); + _cell_volume.emplace(*jacobian_cache * dx_ * dy_ * dz_); } } } @@ -1874,3 +1563,47 @@ void Coordinates::_compute_cell_volume() const { std::shared_ptr Coordinates::makeYBoundary(YBndryType type) const { return std::make_shared(type, localoptions, *localmesh); } + +void Coordinates::setBxy(FieldMetric Bxy, const bool communicate) { + //TODO: Calculate Bxy and check value is close + Bxy_ = std::move(Bxy); + if (communicate) { + localmesh->communicate_no_slices(Bxy_); + } +} + +void Coordinates::setContravariantMetricTensor( + const ContravariantMetricTensor& metric_tensor, const std::string& region, + bool recalculate_staggered, bool force_interpolate_from_centre) { + contravariantMetricTensor = metric_tensor; + covariantMetricTensor = contravariantMetricTensor.inverse(region); + recalculateAndReset(recalculate_staggered, force_interpolate_from_centre); +} + +void Coordinates::setCovariantMetricTensor(const CovariantMetricTensor& metric_tensor, + const std::string& region, + bool recalculate_staggered, + bool force_interpolate_from_centre) { + covariantMetricTensor = metric_tensor; + contravariantMetricTensor = covariantMetricTensor.inverse(region); + recalculateAndReset(recalculate_staggered, force_interpolate_from_centre); +} + +void Coordinates::setMetricTensor( + const ContravariantMetricTensor& contravariant_metric_tensor, + const CovariantMetricTensor& covariant_metric_tensor) { + contravariantMetricTensor = contravariant_metric_tensor; + covariantMetricTensor = covariant_metric_tensor; +} + +void Coordinates::communicateMetricTensor() { + contravariantMetricTensor.communicate(); + covariantMetricTensor.communicate(); +} + +void Coordinates::communicateDz() { localmesh->communicate(dz_); } + +void Coordinates::splitBxyParallelSlices() { + Bxy_.splitParallelSlices(); + Bxy_.yup() = Bxy_.ydown() = Bxy_; +} diff --git a/src/mesh/coordinates_accessor.cxx b/src/mesh/coordinates_accessor.cxx index efc27e9715..0f11906c21 100644 --- a/src/mesh/coordinates_accessor.cxx +++ b/src/mesh/coordinates_accessor.cxx @@ -18,7 +18,7 @@ CoordinatesAccessor::CoordinatesAccessor(const Coordinates* coords) { ASSERT0(coords != nullptr); // Size of the mesh in Z. Used to convert 3D -> 2D index - Mesh* mesh = coords->dx.getMesh(); + Mesh* mesh = coords->dx().getMesh(); mesh_nz = mesh->LocalNz; auto search = coords_store.find(coords); @@ -41,9 +41,11 @@ CoordinatesAccessor::CoordinatesAccessor(const Coordinates* coords) { // Copy data from Coordinates variable into data array // Uses the symbol to look up the corresponding Offset -#define COPY_STRIPE1(symbol) \ - if (coords->symbol.isAllocated()) \ - data[stripe_size * ind.ind + static_cast(Offset::symbol)] = coords->symbol[ind]; +#define COPY_STRIPE1(symbol) \ + if (coords->symbol().isAllocated()) { \ + data[stripe_size * ind.ind + static_cast(Offset::symbol)] = \ + coords->symbol()[ind]; \ + } // Implement copy for each argument #define COPY_STRIPE(...) \ @@ -53,19 +55,19 @@ CoordinatesAccessor::CoordinatesAccessor(const Coordinates* coords) { // Iterate over all points in the field // Note this could be 2D or 3D, depending on FieldMetric type - for (const auto& ind : coords->dx.getRegion("RGN_ALL")) { + for (const auto& ind : coords->dx().getRegion("RGN_ALL")) { COPY_STRIPE(dx, dy, dz); COPY_STRIPE(d1_dx, d1_dy, d1_dz); COPY_STRIPE(J); - if (coords->Bxy.isAllocated()) { - data[stripe_size * ind.ind + static_cast(Offset::B)] = coords->Bxy[ind]; - if (coords->Bxy.yup().isAllocated()) + if (coords->Bxy().isAllocated()) { + data[stripe_size * ind.ind + static_cast(Offset::B)] = coords->Bxy()[ind]; + if (coords->Bxy().yup().isAllocated()) data[stripe_size * ind.ind + static_cast(Offset::Byup)] = - coords->Bxy.yup()[ind]; - if (coords->Bxy.ydown().isAllocated()) + coords->Bxy().yup()[ind]; + if (coords->Bxy().ydown().isAllocated()) data[stripe_size * ind.ind + static_cast(Offset::Bydown)] = - coords->Bxy.ydown()[ind]; + coords->Bxy().ydown()[ind]; } COPY_STRIPE(G1, G3); diff --git a/src/mesh/difops.cxx b/src/mesh/difops.cxx index 8ecd9d64ff..1b9a993e1a 100644 --- a/src/mesh/difops.cxx +++ b/src/mesh/difops.cxx @@ -288,11 +288,11 @@ Field3D Div_par_flux(const Field3D& v, const Field3D& f, CELL_LOC outloc, const std::string& method) { Coordinates* metric = f.getCoordinates(outloc); - auto Bxy_floc = f.getCoordinates()->Bxy; + auto Bxy_floc = f.getCoordinates()->Bxy(); if (!f.hasParallelSlices()) { Field3D f_B = f / Bxy_floc; - return metric->Bxy * FDDY(v, f_B, outloc, method) / sqrt(metric->g_22); + return metric->Bxy() * FDDY(v, f_B, outloc, method) / sqrt(metric->g_22()); } // Need to modify yup and ydown fields @@ -301,7 +301,7 @@ Field3D Div_par_flux(const Field3D& v, const Field3D& f, CELL_LOC outloc, f_B.splitParallelSlices(); f_B.yup() = f.yup() / Bxy_floc; f_B.ydown() = f.ydown() / Bxy_floc; - return metric->Bxy * FDDY(v, f_B, outloc, method) / sqrt(metric->g_22); + return metric->Bxy() * FDDY(v, f_B, outloc, method) / sqrt(metric->g_22()); } Field3D Div_par_flux(const Field3D& v, const Field3D& f, const std::string& method, @@ -405,24 +405,24 @@ Field3D Div_par_K_Grad_par_mod(const Field3D& Kin, const Field3D& fin, Field3D& // Upper cell edge const BoutReal c_up = 0.5 * (Kin[i] + K_up[iyp]); // K at the upper boundary const BoutReal J_up = - 0.5 * (coord->J[i] + coord->J.yup()[iyp]); // Jacobian at boundary - const BoutReal g_22_up = 0.5 * (coord->g_22[i] + coord->g_22.yup()[iyp]); + 0.5 * (coord->J()[i] + coord->J().yup()[iyp]); // Jacobian at boundary + const BoutReal g_22_up = 0.5 * (coord->g_22()[i] + coord->g_22().yup()[iyp]); const BoutReal gradient_up = - 2. * (f_up[iyp] - fin[i]) / (coord->dy[i] + coord->dy.yup()[iyp]); + 2. * (f_up[iyp] - fin[i]) / (coord->dy()[i] + coord->dy().yup()[iyp]); const BoutReal flux_up = c_up * J_up * gradient_up / g_22_up; // Lower cell edge const BoutReal c_down = 0.5 * (Kin[i] + K_down[iym]); // K at the lower boundary const BoutReal J_down = - 0.5 * (coord->J[i] + coord->J.ydown()[iym]); // Jacobian at boundary - const BoutReal g_22_down = 0.5 * (coord->g_22[i] + coord->g_22.ydown()[iym]); + 0.5 * (coord->J()[i] + coord->J().ydown()[iym]); // Jacobian at boundary + const BoutReal g_22_down = 0.5 * (coord->g_22()[i] + coord->g_22().ydown()[iym]); const BoutReal gradient_down = - 2. * (fin[i] - f_down[iym]) / (coord->dy[i] + coord->dy.ydown()[iym]); + 2. * (fin[i] - f_down[iym]) / (coord->dy()[i] + coord->dy().ydown()[iym]); const BoutReal flux_down = c_down * J_down * gradient_down / g_22_down; - result[i] = (flux_up - flux_down) / (coord->dy[i] * coord->J[i]); + result[i] = (flux_up - flux_down) / (coord->dy()[i] * coord->J()[i]); } return result; @@ -446,26 +446,26 @@ Field3D Div_par_K_Grad_par_mod(const Field3D& Kin, const Field3D& fin, Field3D& if (bndry_flux || is_periodic_y || !mesh->lastY(ix) || (iy != mesh->yend)) { const BoutReal c = 0.5 * (K[i] + K[iyp]); // K at the upper boundary - const BoutReal J = 0.5 * (coord->J[i] + coord->J[iyp]); // Jacobian at boundary - const BoutReal g_22 = 0.5 * (coord->g_22[i] + coord->g_22[iyp]); - const BoutReal gradient = 2. * (f[iyp] - f[i]) / (coord->dy[i] + coord->dy[iyp]); + const BoutReal J = 0.5 * (coord->J()[i] + coord->J()[iyp]); // Jacobian at boundary + const BoutReal g_22 = 0.5 * (coord->g_22()[i] + coord->g_22()[iyp]); + const BoutReal gradient = 2. * (f[iyp] - f[i]) / (coord->dy()[i] + coord->dy()[iyp]); const BoutReal flux = c * J * gradient / g_22; - result[i] += flux / (coord->dy[i] * coord->J[i]); + result[i] += flux / (coord->dy()[i] * coord->J()[i]); } // Calculate flux at lower surface if (bndry_flux || is_periodic_y || !mesh->firstY(ix) || (iy != mesh->ystart)) { const BoutReal c = 0.5 * (K[i] + K[iym]); // K at the lower boundary - const BoutReal J = 0.5 * (coord->J[i] + coord->J[iym]); // Jacobian at boundary - const BoutReal g_22 = 0.5 * (coord->g_22[i] + coord->g_22[iym]); - const BoutReal gradient = 2. * (f[i] - f[iym]) / (coord->dy[i] + coord->dy[iym]); + const BoutReal J = 0.5 * (coord->J()[i] + coord->J()[iym]); // Jacobian at boundary + const BoutReal g_22 = 0.5 * (coord->g_22()[i] + coord->g_22()[iym]); + const BoutReal gradient = 2. * (f[i] - f[iym]) / (coord->dy()[i] + coord->dy()[iym]); const BoutReal flux = c * J * gradient / g_22; - result[i] -= flux / (coord->dy[i] * coord->J[i]); - flow_ylow[i] = -flux * coord->dx[i] * coord->dz[i]; + result[i] -= flux / (coord->dy()[i] * coord->J()[i]); + flow_ylow[i] = -flux * coord->dx()[i] * coord->dz()[i]; } } @@ -580,12 +580,12 @@ Coordinates::FieldMetric b0xGrad_dot_Grad(const Field2D& phi, const Field2D& A, Coordinates::FieldMetric dpdy = DDY(phi, outloc); // Calculate advection velocity - Coordinates::FieldMetric vx = -metric->g_23 * dpdy; - Coordinates::FieldMetric vy = metric->g_23 * dpdx; + Coordinates::FieldMetric vx = -metric->g_23() * dpdy; + Coordinates::FieldMetric vy = metric->g_23() * dpdx; // Upwind A using these velocities Coordinates::FieldMetric result = VDDX(vx, A, outloc) + VDDY(vy, A, outloc); - result /= metric->J * sqrt(metric->g_22); + result /= metric->J() * sqrt(metric->g_22()); ASSERT1(result.getLocation() == outloc); @@ -612,20 +612,20 @@ Field3D b0xGrad_dot_Grad(const Field2D& phi, const Field3D& A, CELL_LOC outloc) Coordinates::FieldMetric dpdy = DDY(phi, outloc); // Calculate advection velocity - Coordinates::FieldMetric vx = -metric->g_23 * dpdy; - Coordinates::FieldMetric vy = metric->g_23 * dpdx; - Coordinates::FieldMetric vz = metric->g_12 * dpdy - metric->g_22 * dpdx; + Coordinates::FieldMetric vx = -metric->g_23() * dpdy; + Coordinates::FieldMetric vy = metric->g_23() * dpdx; + Coordinates::FieldMetric vz = metric->g_12() * dpdy - metric->g_22() * dpdx; if (mesh->IncIntShear) { // BOUT-06 style differencing - vz += metric->IntShiftTorsion * vx; + vz += metric->IntShiftTorsion() * vx; } // Upwind A using these velocities Field3D result = VDDX(vx, A, outloc) + VDDY(vy, A, outloc) + VDDZ(vz, A, outloc); - result /= (metric->J * sqrt(metric->g_22)); + result /= (metric->J() * sqrt(metric->g_22())); #if BOUT_USE_TRACK result.name = "b0xGrad_dot_Grad(" + phi.name + "," + A.name + ")"; @@ -652,14 +652,14 @@ Field3D b0xGrad_dot_Grad(const Field3D& p, const Field2D& A, CELL_LOC outloc) { Field3D dpdz = DDZ(p, outloc); // Calculate advection velocity - Field3D vx = metric->g_22 * dpdz - metric->g_23 * dpdy; - Field3D vy = metric->g_23 * dpdx - metric->g_12 * dpdz; + Field3D vx = metric->g_22() * dpdz - metric->g_23() * dpdy; + Field3D vy = metric->g_23() * dpdx - metric->g_12() * dpdz; // Upwind A using these velocities Field3D result = VDDX(vx, A, outloc) + VDDY(vy, A, outloc); - result /= (metric->J * sqrt(metric->g_22)); + result /= (metric->J() * sqrt(metric->g_22())); #if BOUT_USE_TRACK result.name = "b0xGrad_dot_Grad(" + p.name + "," + A.name + ")"; @@ -688,18 +688,18 @@ Field3D b0xGrad_dot_Grad(const Field3D& phi, const Field3D& A, CELL_LOC outloc) Field3D dpdz = DDZ(phi, outloc); // Calculate advection velocity - Field3D vx = metric->g_22 * dpdz - metric->g_23 * dpdy; - Field3D vy = metric->g_23 * dpdx - metric->g_12 * dpdz; - Field3D vz = metric->g_12 * dpdy - metric->g_22 * dpdx; + Field3D vx = metric->g_22() * dpdz - metric->g_23() * dpdy; + Field3D vy = metric->g_23() * dpdx - metric->g_12() * dpdz; + Field3D vz = metric->g_12() * dpdy - metric->g_22() * dpdx; if (mesh->IncIntShear) { // BOUT-06 style differencing - vz += metric->IntShiftTorsion * vx; + vz += metric->IntShiftTorsion() * vx; } Field3D result = VDDX(vx, A, outloc) + VDDY(vy, A, outloc) + VDDZ(vz, A, outloc); - result /= (metric->J * sqrt(metric->g_22)); + result /= (metric->J() * sqrt(metric->g_22())); #if BOUT_USE_TRACK result.name = "b0xGrad_dot_Grad(" + phi.name + "," + A.name + ")"; @@ -733,7 +733,7 @@ Coordinates::FieldMetric bracket(const Field2D& f, const Field2D& g, result.setLocation(outloc); } else { // Use full expression with all terms - result = b0xGrad_dot_Grad(f, g, outloc) / f.getCoordinates(outloc)->Bxy; + result = b0xGrad_dot_Grad(f, g, outloc) / f.getCoordinates(outloc)->Bxy(); } return result; } @@ -808,7 +808,7 @@ Field3D bracket(const Field3D& f, const Field2D& g, BRACKET_METHOD method, BOUT_FOR(j2D, result.getRegion2D("RGN_NOBNDRY")) { // Get constants for this iteration - const BoutReal spacingFactor = 1.0 / (12 * metric->dz[j2D] * metric->dx[j2D]); + const BoutReal spacingFactor = 1.0 / (12 * metric->dz()[j2D] * metric->dx()[j2D]); const int jy = j2D.y(), jx = j2D.x(); const int xm = jx - 1, xp = jx + 1; @@ -880,7 +880,7 @@ Field3D bracket(const Field3D& f, const Field2D& g, BRACKET_METHOD method, } default: { // Use full expression with all terms - result = b0xGrad_dot_Grad(f, g, outloc) / metric->Bxy; + result = b0xGrad_dot_Grad(f, g, outloc) / metric->Bxy(); } } return result; @@ -915,7 +915,7 @@ Field3D bracket(const Field2D& f, const Field3D& g, BRACKET_METHOD method, default: { // Use full expression with all terms Coordinates* metric = f.getCoordinates(outloc); - result = b0xGrad_dot_Grad(f, g, outloc) / metric->Bxy; + result = b0xGrad_dot_Grad(f, g, outloc) / metric->Bxy(); } } @@ -1058,7 +1058,7 @@ Field3D bracket(const Field3D& f, const Field3D& g, BRACKET_METHOD method, BOUT_FOR(j2D, result.getRegion2D("RGN_NOBNDRY")) { #if not(BOUT_USE_METRIC_3D) - const BoutReal spacingFactor = 1.0 / (12 * metric->dz[j2D] * metric->dx[j2D]); + const BoutReal spacingFactor = 1.0 / (12 * metric->dz()[j2D] * metric->dx()[j2D]); #endif const int jy = j2D.y(), jx = j2D.x(); const int xm = jx - 1, xp = jx + 1; @@ -1156,7 +1156,7 @@ Field3D bracket(const Field3D& f, const Field3D& g, BRACKET_METHOD method, } default: { // Use full expression with all terms - result = b0xGrad_dot_Grad(f, g, outloc) / metric->Bxy; + result = b0xGrad_dot_Grad(f, g, outloc) / metric->Bxy(); } } diff --git a/src/mesh/fv_ops.cxx b/src/mesh/fv_ops.cxx index 660fc51abc..e8958e128d 100644 --- a/src/mesh/fv_ops.cxx +++ b/src/mesh/fv_ops.cxx @@ -71,9 +71,9 @@ Field3D Div_a_Grad_perp(const Field3D& a, const Field3D& f) { // 3D Metric, need yup/ydown fields. // Requires previous communication of metrics // -- should insert communication here? - if (!coord->g23.hasParallelSlices() || !coord->g_23.hasParallelSlices() - || !coord->dy.hasParallelSlices() || !coord->dz.hasParallelSlices() - || !coord->Bxy.hasParallelSlices() || !coord->J.hasParallelSlices()) { + if (!coord->g23().hasParallelSlices() || !coord->g_23().hasParallelSlices() + || !coord->dy().hasParallelSlices() || !coord->dz().hasParallelSlices() + || !coord->Bxy().hasParallelSlices() || !coord->J().hasParallelSlices()) { throw BoutException("metrics have no yup/down!"); } } @@ -89,12 +89,12 @@ Field3D Div_a_Grad_perp(const Field3D& a, const Field3D& f) { // Only in 3D case with FCI do the metrics have parallel slices const bool metric_fci = fci and bout::build::use_metric_3d; - const auto g23 = makeslices(metric_fci, coord->g23); - const auto g_23 = makeslices(metric_fci, coord->g_23); - const auto J = makeslices(metric_fci, coord->J); - const auto dy = makeslices(metric_fci, coord->dy); - const auto dz = makeslices(metric_fci, coord->dz); - const auto Bxy = makeslices(metric_fci, coord->Bxy); + const auto g23 = makeslices(metric_fci, coord->g23()); + const auto g_23 = makeslices(metric_fci, coord->g_23()); + const auto J = makeslices(metric_fci, coord->J()); + const auto dy = makeslices(metric_fci, coord->dy()); + const auto dz = makeslices(metric_fci, coord->dz()); + const auto Bxy = makeslices(metric_fci, coord->Bxy()); // Result of the Y and Z fluxes Field3D yzresult(0.0, mesh); @@ -158,7 +158,7 @@ Field3D Div_a_Grad_perp(const Field3D& a, const Field3D& f) { const BoutReal fout = 0.25 * (a_slice.c[i] + a_slice.c[ikp]) - * (J.c[i] * coord->g33[i] + J.c[ikp] * coord->g33[ikp]) + * (J.c[i] * coord->g33()[i] + J.c[ikp] * coord->g33()[ikp]) * ( // df/dz (f_slice.c[ikp] - f_slice.c[i]) / dz.c[i] // - g_yz * df/dy / SQ(J*B) @@ -215,30 +215,30 @@ Field3D Div_par_K_Grad_par(const Field3D& Kin, const Field3D& fin, bool bndry_fl || (i.y() != mesh->yend)) { const BoutReal c = 0.5 * (K[i] + Kup[iyp]); // K at the upper boundary - const BoutReal J = 0.5 * (coord->J[i] + coord->J[iyp]); // Jacobian at boundary - const BoutReal g_22 = 0.5 * (coord->g_22[i] + coord->g_22[iyp]); + const BoutReal J = 0.5 * (coord->J()[i] + coord->J()[iyp]); // Jacobian at boundary + const BoutReal g_22 = 0.5 * (coord->g_22()[i] + coord->g_22()[iyp]); - const BoutReal gradient = 2. * (fup[iyp] - f[i]) / (coord->dy[i] + coord->dy[iyp]); + const BoutReal gradient = 2. * (fup[iyp] - f[i]) / (coord->dy()[i] + coord->dy()[iyp]); const BoutReal flux = c * J * gradient / g_22; - result[i] += flux / (coord->dy[i] * coord->J[i]); + result[i] += flux / (coord->dy()[i] * coord->J()[i]); } // Calculate flux at lower surface if (bndry_flux || mesh->periodicY(i.x()) || !mesh->firstY(i.x()) || (i.y() != mesh->ystart)) { const BoutReal c = 0.5 * (K[i] + Kdown[iym]); // K at the lower boundary - const BoutReal J = 0.5 * (coord->J[i] + coord->J[iym]); // Jacobian at boundary + const BoutReal J = 0.5 * (coord->J()[i] + coord->J()[iym]); // Jacobian at boundary - const BoutReal g_22 = 0.5 * (coord->g_22[i] + coord->g_22[iym]); + const BoutReal g_22 = 0.5 * (coord->g_22()[i] + coord->g_22()[iym]); const BoutReal gradient = - 2. * (f[i] - fdown[iym]) / (coord->dy[i] + coord->dy[iym]); + 2. * (f[i] - fdown[iym]) / (coord->dy()[i] + coord->dy()[iym]); const BoutReal flux = c * J * gradient / g_22; - result[i] -= flux / (coord->dy[i] * coord->J[i]); + result[i] -= flux / (coord->dy()[i] * coord->J()[i]); } } diff --git a/src/mesh/g_values.cxx b/src/mesh/g_values.cxx new file mode 100644 index 0000000000..31a794fe22 --- /dev/null +++ b/src/mesh/g_values.cxx @@ -0,0 +1,34 @@ +#include "bout/g_values.hxx" +#include "bout/coordinates.hxx" +#include "bout/mesh.hxx" +#include "bout/metric_tensor.hxx" + +GValues::GValues(const Coordinates& coordinates) { + + const auto& contravariantMetricTensor = coordinates.getContravariantMetricTensor(); + const auto& J = coordinates.J(); + + const auto& g11 = contravariantMetricTensor.g11(); + const auto& g22 = contravariantMetricTensor.g22(); + const auto& g33 = contravariantMetricTensor.g33(); + const auto& g12 = contravariantMetricTensor.g12(); + const auto& g13 = contravariantMetricTensor.g13(); + const auto& g23 = contravariantMetricTensor.g23(); + + auto* mesh = J.getMesh(); + + bout::FieldMetric Jg12 = J * g12; + mesh->communicate(Jg12); + G1_m = + (coordinates.DDX(J * g11) + coordinates.DDY(Jg12) + coordinates.DDZ(J * g13)) / J; + bout::FieldMetric Jg22 = J * g22; + mesh->communicate(Jg22); + G2_m = + (coordinates.DDX(J * g12) + coordinates.DDY(Jg22) + coordinates.DDZ(J * g23)) / J; + bout::FieldMetric Jg23 = J * g23; + mesh->communicate(Jg23); + G3_m = + (coordinates.DDX(J * g13) + coordinates.DDY(Jg23) + coordinates.DDZ(J * g33)) / J; + + mesh->communicate(G1_m, G2_m, G3_m); +} diff --git a/src/mesh/mesh.cxx b/src/mesh/mesh.cxx index 33368e6d6a..00bd0b5bc4 100644 --- a/src/mesh/mesh.cxx +++ b/src/mesh/mesh.cxx @@ -1,3 +1,4 @@ +#include "bout/assert.hxx" #include #include #include @@ -554,12 +555,39 @@ Mesh::createDefaultCoordinates(const CELL_LOC location, if (location == CELL_CENTRE || location == CELL_DEFAULT) { // Initialize coordinates from input return std::make_shared(this, options); - } else { - // Interpolate coordinates from CELL_CENTRE version - return std::make_shared(this, options, location, - getCoordinates(CELL_CENTRE), - force_interpolate_from_centre); } + // Interpolate coordinates from CELL_CENTRE version + return std::make_shared(this, options, location, + getCoordinates(CELL_CENTRE), + force_interpolate_from_centre); +} + +std::shared_ptr Mesh::getCoordinatesSmart(CELL_LOC location) { + ASSERT1(location != CELL_DEFAULT); + ASSERT1(location != CELL_VSHIFT); + + auto found = coords_map.find(location); + if (found != coords_map.end()) { + // True branch most common, returns immediately + return found->second; + } + + // No coordinate system set. Create default + // Note that this can't be allocated here due to incomplete type + // (circular dependency between Mesh and Coordinates) + auto inserted = coords_map.emplace(location, nullptr); + auto force_interpolate_from_centre = false; + inserted.first->second = + createDefaultCoordinates(location, force_interpolate_from_centre); + + auto recalculate_staggered = false; + inserted.first->second->recalculateAndReset(recalculate_staggered, + force_interpolate_from_centre); + + inserted.first->second->communicateMetricTensor(); + inserted.first->second->communicateDz(); + + return inserted.first->second; } const Region<>& Mesh::getRegion3D(const std::string& region_name) const { @@ -772,8 +800,14 @@ void Mesh::recalculateStaggeredCoordinates() { continue; } - *coords_map[location] = std::move(*createDefaultCoordinates(location, true)); - coords_map[location]->geometry(false, true); + auto force_interpolate_from_centre = true; + Coordinates& new_coordinates = + *createDefaultCoordinates(location, force_interpolate_from_centre); + *coords_map[location] = std::move(new_coordinates); + + auto recalculate_staggered = false; + new_coordinates.recalculateAndReset(recalculate_staggered, + force_interpolate_from_centre); } } diff --git a/src/mesh/metric_tensor.cxx b/src/mesh/metric_tensor.cxx new file mode 100644 index 0000000000..5444f538d3 --- /dev/null +++ b/src/mesh/metric_tensor.cxx @@ -0,0 +1,154 @@ +#include "bout/metric_tensor.hxx" +#include "invert3x3.hxx" +#include "bout/bout_types.hxx" +#include "bout/boutexception.hxx" +#include "bout/field2d.hxx" +#include "bout/mesh.hxx" +#include "bout/output.hxx" +#include "bout/region.hxx" +#include "bout/utils.hxx" + +#include + +#include +#include +#include + +MetricTensor::MetricTensor(FieldMetric g11, FieldMetric g22, FieldMetric g33, + FieldMetric g12, FieldMetric g13, FieldMetric g23) + : g11_m(std::move(g11)), g22_m(std::move(g22)), g33_m(std::move(g33)), + g12_m(std::move(g12)), g13_m(std::move(g13)), g23_m(std::move(g23)) {} + +MetricTensor::MetricTensor(const BoutReal g11, const BoutReal g22, const BoutReal g33, + const BoutReal g12, const BoutReal g13, const BoutReal g23, + Mesh* mesh) + : g11_m(g11, mesh), g22_m(g22, mesh), g33_m(g33, mesh), g12_m(g12, mesh), + g13_m(g13, mesh), g23_m(g23, mesh) {} + +void MetricTensor::check(int ystart) { + const bool non_identity_parallel_transform = + g11_m.hasParallelSlices() && &g11_m.ynext(1) != &g11_m; + + // Diagonal metric components should be finite + bout::checkFinite(g11_m, "g11", "RGN_NOCORNERS"); + bout::checkFinite(g22_m, "g22", "RGN_NOCORNERS"); + bout::checkFinite(g33_m, "g33", "RGN_NOCORNERS"); + if (non_identity_parallel_transform) { + for (int dy = 1; dy <= ystart; ++dy) { + for (const auto sign : {1, -1}) { + const auto region = fmt::format("RGN_YPAR_{:+d}", sign * dy); + bout::checkFinite(g11_m.ynext(sign * dy), "g11.ynext", region); + bout::checkFinite(g22_m.ynext(sign * dy), "g22.ynext", region); + bout::checkFinite(g33_m.ynext(sign * dy), "g33.ynext", region); + } + } + } + + // Diagonal metric components should be positive + bout::checkPositive(g11_m, "g11", "RGN_NOCORNERS"); + bout::checkPositive(g22_m, "g22", "RGN_NOCORNERS"); + bout::checkPositive(g33_m, "g33", "RGN_NOCORNERS"); + if (non_identity_parallel_transform) { + for (int dy = 1; dy <= ystart; ++dy) { + for (const auto sign : {1, -1}) { + const auto region = fmt::format("RGN_YPAR_{:+d}", sign * dy); + bout::checkPositive(g11_m.ynext(sign * dy), "g11.ynext", region); + bout::checkPositive(g22_m.ynext(sign * dy), "g22.ynext", region); + bout::checkPositive(g33_m.ynext(sign * dy), "g33.ynext", region); + } + } + } + + // Off-diagonal metric components should be finite + bout::checkFinite(g12_m, "g12", "RGN_NOCORNERS"); + bout::checkFinite(g13_m, "g13", "RGN_NOCORNERS"); + bout::checkFinite(g23_m, "g23", "RGN_NOCORNERS"); + // Check off-diagonal separately, might not have them even if we have parallel + // slices for the diagonal components + if (g23_m.hasParallelSlices() && &g23_m.ynext(1) != &g23_m) { + for (int dy = 1; dy <= ystart; ++dy) { + for (const auto sign : {1, -1}) { + const auto region = fmt::format("RGN_YPAR_{:+d}", sign * dy); + bout::checkFinite(g12_m.ynext(sign * dy), "g12.ynext", region); + bout::checkFinite(g13_m.ynext(sign * dy), "g13.ynext", region); + bout::checkFinite(g23_m.ynext(sign * dy), "g23.ynext", region); + } + } + } +} + +namespace { +template +auto inverse_impl(const MetricTensor& metric, const std::string& region) + -> InverseMetric { + // Perform inversion of g{ij} to get g^{ij}, or vice versa + auto matrix = Matrix(3, 3); + + bout::FieldMetric g_11 = emptyFrom(metric.g11()); + bout::FieldMetric g_22 = emptyFrom(metric.g22()); + bout::FieldMetric g_33 = emptyFrom(metric.g33()); + bout::FieldMetric g_12 = emptyFrom(metric.g12()); + bout::FieldMetric g_13 = emptyFrom(metric.g13()); + bout::FieldMetric g_23 = emptyFrom(metric.g23()); + + BOUT_FOR_SERIAL(i, metric.g11().getRegion(region)) { + matrix(0, 0) = metric.g11()[i]; + matrix(1, 1) = metric.g22()[i]; + matrix(2, 2) = metric.g33()[i]; + + matrix(0, 1) = matrix(1, 0) = metric.g12()[i]; + matrix(1, 2) = matrix(2, 1) = metric.g23()[i]; + matrix(0, 2) = matrix(2, 0) = metric.g13()[i]; + + if (const auto det = bout::invert3x3(matrix); det.has_value()) { + throw BoutException("ERROR: metric tensor is singular at ({}, {}), determinant: {}", + i.x(), i.y(), det.value()); + } + + g_11[i] = matrix(0, 0); + g_22[i] = matrix(1, 1); + g_33[i] = matrix(2, 2); + g_12[i] = matrix(0, 1); + g_13[i] = matrix(0, 2); + g_23[i] = matrix(1, 2); + } + + const BoutReal diagonal_maxerr = + BOUTMAX(max(abs((g_11 * g_11 + g_12 * g_12 + g_13 * g_13) - 1)), + max(abs((g_12 * g_12 + g_22 * g_22 + g_23 * g_23) - 1)), + max(abs((g_13 * g_13 + g_23 * g_23 + g_33 * g_33) - 1))); + + output_info.write("\tMaximum error in diagonal inversion is {:e}\n", diagonal_maxerr); + + const BoutReal off_diagonal_maxerr = + BOUTMAX(max(abs(g_11 * g_12 + g_12 * g_22 + g_13 * g_23)), + max(abs(g_11 * g_13 + g_12 * g_23 + g_13 * g_33)), + max(abs(g_12 * g_13 + g_22 * g_23 + g_23 * g_33))); + + output_info.write("\tMaximum error in off-diagonal inversion is {:e}\n", + off_diagonal_maxerr); + return InverseMetric(g_11, g_22, g_33, g_12, g_13, g_23); +} +} // namespace + +auto CovariantMetricTensor::inverse(const std::string& region, bool communicate) + -> ContravariantMetricTensor { + auto result = inverse_impl(*this, region); + if (communicate) { + result.communicate(); + } + return result; +} + +auto ContravariantMetricTensor::inverse(const std::string& region, bool communicate) + -> CovariantMetricTensor { + auto result = inverse_impl(*this, region); + if (communicate) { + result.communicate(); + } + return result; +} + +void MetricTensor::communicate() { + g11_m.getMesh()->communicate_no_slices(g11_m, g22_m, g33_m, g12_m, g13_m, g23_m); +} diff --git a/src/mesh/parallel/fci.cxx b/src/mesh/parallel/fci.cxx index aebfeb654c..72861eb68f 100644 --- a/src/mesh/parallel/fci.cxx +++ b/src/mesh/parallel/fci.cxx @@ -42,10 +42,11 @@ #include "bout/boundary_region_iter.hxx" #include "bout/bout_types.hxx" #include "bout/boutexception.hxx" -#include "bout/build_defines.hxx" +#include "bout/coordinates.hxx" #include "bout/field2d.hxx" #include "bout/field3d.hxx" #include "bout/field_data.hxx" +#include "bout/interpolation_xz.hxx" #include "bout/mesh.hxx" #include "bout/msg_stack.hxx" #include "bout/options.hxx" @@ -62,61 +63,9 @@ #include #include #include -#include -using namespace std::string_view_literals; using bout::boundary::BoundaryRegionFCI; -namespace { -// Get a unique name for a field based on the sign/magnitude of the offset -std::string parallel_slice_field_name(const std::string& field, int offset) { - const std::string direction = (offset > 0) ? "forward" : "backward"; - // We only have a suffix for parallel slices beyond the first - // This is for backwards compatibility - const std::string slice_suffix = - (std::abs(offset) > 1) ? "_" + std::to_string(std::abs(offset)) : ""; - return direction + "_" + field + slice_suffix; -}; - -#if BOUT_USE_METRIC_3D -void load_parallel_metric_component(std::string name, Field3D& component, int offset) { - Mesh* mesh = component.getMesh(); - Field3D tmp{mesh}; - const auto pname = parallel_slice_field_name(name, offset); - if (mesh->get(tmp, pname, 0.0, false) != 0) { - throw BoutException("Could not read {:s} from grid file!\n" - " Fix it up with `zoidberg-update-parallel-metrics `", - pname); - } - if (!component.hasParallelSlices()) { - component.splitParallelSlices(); - component.disallowCalcParallelSlices(); - component.resetRegionParallel(true); - } - auto& pcom = component.ynext(offset); - pcom.allocate(); - BOUT_FOR(i, component.getRegion("RGN_NOBNDRY")) { pcom[i.yp(offset)] = tmp[i]; } -} - -void load_parallel_metric_components(Coordinates* coords, int offset) { -#define LOAD_PAR(var) load_parallel_metric_component(#var, coords->var, offset) - LOAD_PAR(g11); - LOAD_PAR(g22); - LOAD_PAR(g33); - LOAD_PAR(g13); - LOAD_PAR(g_11); - LOAD_PAR(g_22); - LOAD_PAR(g_33); - LOAD_PAR(g_13); - LOAD_PAR(dy); - LOAD_PAR(Bxy); - -#undef LOAD_PAR -} -#endif - -} // namespace - FCIMap::FCIMap(Mesh& mesh, [[maybe_unused]] const Coordinates::FieldMetric& dy, Options& options, int offset, const std::shared_ptr& inner_boundary, @@ -155,29 +104,31 @@ FCIMap::FCIMap(Mesh& mesh, [[maybe_unused]] const Coordinates::FieldMetric& dy, map_mesh->get(R, "R", 0.0, false); map_mesh->get(Z, "Z", 0.0, false); + using bout::parallelSliceFieldName; + // If we can't read in any of these fields, things will silently not // work, so best throw - if (map_mesh->get(xt_prime, parallel_slice_field_name("xt_prime", offset), 0.0, false) + if (map_mesh->get(xt_prime, parallelSliceFieldName("xt_prime", offset), 0.0, false) != 0) { throw BoutException("Could not read {:s} from grid file!\n" " Either add it to the grid file, or reduce MYG", - parallel_slice_field_name("xt_prime", offset)); + parallelSliceFieldName("xt_prime", offset)); } - if (map_mesh->get(zt_prime, parallel_slice_field_name("zt_prime", offset), 0.0, false) + if (map_mesh->get(zt_prime, parallelSliceFieldName("zt_prime", offset), 0.0, false) != 0) { throw BoutException("Could not read {:s} from grid file!\n" " Either add it to the grid file, or reduce MYG", - parallel_slice_field_name("zt_prime", offset)); + parallelSliceFieldName("zt_prime", offset)); } - if (map_mesh->get(R_prime, parallel_slice_field_name("R", offset), 0.0, false) != 0) { + if (map_mesh->get(R_prime, parallelSliceFieldName("R", offset), 0.0, false) != 0) { throw BoutException("Could not read {:s} from grid file!\n" " Either add it to the grid file, or reduce MYG", - parallel_slice_field_name("R", offset)); + parallelSliceFieldName("R", offset)); } - if (map_mesh->get(Z_prime, parallel_slice_field_name("Z", offset), 0.0, false) != 0) { + if (map_mesh->get(Z_prime, parallelSliceFieldName("Z", offset), 0.0, false) != 0) { throw BoutException("Could not read {:s} from grid file!\n" " Either add it to the grid file, or reduce MYG", - parallel_slice_field_name("Z", offset)); + parallelSliceFieldName("Z", offset)); } // Cell corners @@ -467,24 +418,3 @@ void FCITransform::outputVars(Options& output_options) { output_options["R"].force(R, "FCI"); output_options["Z"].force(Z, "FCI"); } - -void FCITransform::loadParallelMetrics([[maybe_unused]] Coordinates* coords) { -#if BOUT_USE_METRIC_3D - output_info.write("\tLoading parallel metrics\n"); - const Coordinates::FieldMetric JB0 = coords->J * coords->Bxy; - coords->J.splitParallelSlices(); - coords->J.disallowCalcParallelSlices(); - coords->J.resetRegionParallel(true); - for (int i = 1; i <= mesh.ystart; ++i) { - load_parallel_metric_components(coords, -i); - load_parallel_metric_components(coords, i); - - coords->J.ynext(i).allocate(); - coords->J.ynext(-i).allocate(); - BOUT_FOR(j, JB0.getRegion("RGN_NOBNDRY")) { - coords->J.ynext(i)[j.yp(i)] = JB0[j] / coords->Bxy.ynext(i)[j.yp(i)]; - coords->J.ynext(-i)[j.yp(-i)] = JB0[j] / coords->Bxy.ynext(-i)[j.yp(-i)]; - } - } -#endif -} diff --git a/src/mesh/parallel/fci.hxx b/src/mesh/parallel/fci.hxx index a90e3e98dd..f5d0642675 100644 --- a/src/mesh/parallel/fci.hxx +++ b/src/mesh/parallel/fci.hxx @@ -126,8 +126,6 @@ public: return false; } - void loadParallelMetrics(Coordinates* coords) override; - protected: void checkInputGrid() override; diff --git a/src/mesh/petsc_operators.cxx b/src/mesh/petsc_operators.cxx index 573044a0a1..6b4de3fe22 100644 --- a/src/mesh/petsc_operators.cxx +++ b/src/mesh/petsc_operators.cxx @@ -333,14 +333,14 @@ PetscOperators::Parallel PetscOperators::getParallel() const { auto* coords = mesh->getCoordinates(); // Parallel spacing in cell space - Field3D dl = Coordinates::FieldMetric{coords->dy * sqrt(coords->g_22)}; + Field3D dl = Coordinates::FieldMetric{coords->dy() * sqrt(coords->g_22())}; dl.splitParallelSlices(); dl.yup() = 0.0; dl.ydown() = 0.0; dl.applyParallelBoundary("parallel_neumann_o1"); // Cell volume - Field3D dV = Coordinates::FieldMetric{coords->J * coords->dx * coords->dy * coords->dz}; + Field3D dV = Coordinates::FieldMetric{coords->J() * coords->dx() * coords->dy() * coords->dz()}; dV.splitParallelSlices(); dV.yup() = 0.0; dV.ydown() = 0.0; diff --git a/src/mesh/tokamak_coordinates.cxx b/src/mesh/tokamak_coordinates.cxx index af8b6335bf..06e38155f0 100644 --- a/src/mesh/tokamak_coordinates.cxx +++ b/src/mesh/tokamak_coordinates.cxx @@ -2,37 +2,38 @@ #include #include #include +#include #include #include namespace bout { TokamakCoordinates set_tokamak_coordinates(Mesh& mesh, BoutReal Lbar, BoutReal Bbar, bool no_shear, BoutReal shear_factor) { - Field2D Rxy; + FieldMetric Rxy; mesh.get(Rxy, "Rxy"); // [m] Rxy /= Lbar; - Field2D Zxy; + FieldMetric Zxy; mesh.get(Zxy, "Zxy"); // [m] Zxy /= Lbar; - Field2D Bpxy; + FieldMetric Bpxy; mesh.get(Bpxy, "Bpxy"); // [T] Bpxy /= Bbar; - Field2D Btxy; + FieldMetric Btxy; mesh.get(Btxy, "Btxy"); // [T] Btxy /= Bbar; - Field2D Bxy; + FieldMetric Bxy; mesh.get(Bxy, "Bxy"); // [T] Bxy /= Bbar; - Field2D hthe; + FieldMetric hthe; mesh.get(hthe, "hthe"); // [m / radian] hthe /= Lbar; - Coordinates::FieldMetric I; + bout::FieldMetric I; if (no_shear) { I = 0.0; } else { @@ -41,36 +42,36 @@ TokamakCoordinates set_tokamak_coordinates(Mesh& mesh, BoutReal Lbar, BoutReal B const auto I_unnormalised = I; I *= Lbar * Lbar * Bbar * shear_factor; - Coordinates::FieldMetric dx; + bout::FieldMetric dx; if (mesh.get(dx, "dpsi") != 0) { - dx = mesh.getCoordinates()->dx; + dx = mesh.getCoordinates()->dx(); } dx /= Lbar * Lbar * Bbar; const BoutReal sign_of_bp = min(Bpxy, true) < 0.0 ? -1.0 : 1.0; - auto* coords = mesh.getCoordinates(); + auto* coord = mesh.getCoordinates(); - coords->Bxy = Bxy; - coords->dx = dx; + const FieldMetric g11 = SQ(Rxy * Bpxy); + const FieldMetric g22 = 1.0 / SQ(hthe); + const FieldMetric g33 = SQ(I) * g11 + SQ(Bxy) / g11; + const FieldMetric g12 = 0.0; + const FieldMetric g13 = -I * g11; + const FieldMetric g23 = -sign_of_bp * Btxy / (hthe * Bpxy * Rxy); - coords->g11 = SQ(Rxy * Bpxy); - coords->g22 = 1.0 / SQ(hthe); - coords->g33 = SQ(I) * coords->g11 + SQ(Bxy) / coords->g11; - coords->g12 = 0.0; - coords->g13 = -I * coords->g11; - coords->g23 = -sign_of_bp * Btxy / (hthe * Bpxy * Rxy); + const FieldMetric g_11 = 1.0 / g11 + SQ(I * Rxy); + const FieldMetric g_22 = SQ(Bxy * hthe / Bpxy); + const FieldMetric g_33 = Rxy * Rxy; + const FieldMetric g_12 = sign_of_bp * Btxy * hthe * I * Rxy / Bpxy; + const FieldMetric g_13 = I * Rxy * Rxy; + const FieldMetric g_23 = sign_of_bp * Btxy * hthe * Rxy / Bpxy; - coords->J = hthe / Bpxy; + coord->setMetricTensor(ContravariantMetricTensor(g11, g22, g33, g12, g13, g23), + CovariantMetricTensor(g_11, g_22, g_33, g_12, g_13, g_23)); - coords->g_11 = 1.0 / coords->g11 + SQ(I * Rxy); - coords->g_22 = SQ(Bxy * hthe / Bpxy); - coords->g_33 = Rxy * Rxy; - coords->g_12 = sign_of_bp * Btxy * hthe * I * Rxy / Bpxy; - coords->g_13 = I * Rxy * Rxy; - coords->g_23 = sign_of_bp * Btxy * hthe * Rxy / Bpxy; - - coords->geometry(); + coord->setJ(FieldMetric{hthe / Bpxy}); + coord->setBxy(Bxy); + coord->setDx(dx); return {Rxy, Zxy, Bpxy, Btxy, Bxy, hthe, I, I_unnormalised}; } diff --git a/src/physics/smoothing.cxx b/src/physics/smoothing.cxx index 1b437b4352..b643a016c8 100644 --- a/src/physics/smoothing.cxx +++ b/src/physics/smoothing.cxx @@ -4,15 +4,15 @@ * * 2014-10-29 Ben Dudson * * Moving averaging routines here from Mesh - * + * * 2010-05-17 Ben Dudson * * Added nonlinear filter - * + * ************************************************************** * Copyright 2010 B.D.Dudson, S.Farley, M.V.Umansky, X.Q.Xu * * Contact: Ben Dudson, bd512@york.ac.uk - * + * * This file is part of BOUT++. * * BOUT++ is free software: you can redistribute it and/or modify @@ -107,7 +107,7 @@ const Field3D smooth_y(const Field3D& f) { Issues ====== - + Assumes every processor has the same domain shape Will only work if X communicator is constant in Y @@ -162,14 +162,14 @@ const Field2D averageX(const Field2D& f) { ====== Creates static arrays - + Not thread safe - + Assumes every processor has the same domain shape - + Will only work if X communicator is constant in Y so no processor/branch cuts in X - + */ const Field3D averageX(const Field3D& f) { Mesh* mesh = f.getMesh(); @@ -349,7 +349,7 @@ BoutReal Vol_Integral([[maybe_unused]] const Field2D& var) { BoutReal Int_Glb; Coordinates* metric = var.getCoordinates(); - auto result = metric->J * var * metric->dx * metric->dy; + auto result = metric->J() * var * metric->dx() * metric->dy(); Int_Glb = Average_XY(result); Int_Glb *= static_cast( diff --git a/src/sys/derivs.cxx b/src/sys/derivs.cxx index e449dbcd30..db44b388f1 100644 --- a/src/sys/derivs.cxx +++ b/src/sys/derivs.cxx @@ -76,7 +76,7 @@ Coordinates::FieldMetric DDX(const Field2D& f, CELL_LOC outloc, const std::strin Field3D DDY(const Field3DParallel& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::DDY(f, outloc, method, region) - / f.getCoordinates(outloc)->dy; + / f.getCoordinates(outloc)->dy(); } Coordinates::FieldMetric DDY(const Field2D& f, CELL_LOC outloc, const std::string& method, @@ -89,7 +89,7 @@ Coordinates::FieldMetric DDY(const Field2D& f, CELL_LOC outloc, const std::strin Field3D DDZ(const Field3D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::DDZ(f, outloc, method, region) - / f.getCoordinates(outloc)->dz; + / f.getCoordinates(outloc)->dz(); } Coordinates::FieldMetric DDZ(const Field2D& f, CELL_LOC UNUSED(outloc), @@ -107,21 +107,21 @@ Vector3D DDZ(const Vector3D& v, CELL_LOC outloc, const std::string& method, if (v.covariant) { // From equation (2.6.32) in D'Haeseleer - result.x = DDZ(v.x, outloc, method, region) - v.x * metric->G1_13 - - v.y * metric->G2_13 - v.z * metric->G3_13; - result.y = DDZ(v.y, outloc, method, region) - v.x * metric->G1_23 - - v.y * metric->G2_23 - v.z * metric->G3_23; - result.z = DDZ(v.z, outloc, method, region) - v.x * metric->G1_33 - - v.y * metric->G2_33 - v.z * metric->G3_33; + result.x = DDZ(v.x, outloc, method, region) - v.x * metric->G1_13() + - v.y * metric->G2_13() - v.z * metric->G3_13(); + result.y = DDZ(v.y, outloc, method, region) - v.x * metric->G1_23() + - v.y * metric->G2_23() - v.z * metric->G3_23(); + result.z = DDZ(v.z, outloc, method, region) - v.x * metric->G1_33() + - v.y * metric->G2_33() - v.z * metric->G3_33(); result.covariant = true; } else { // From equation (2.6.31) in D'Haeseleer - result.x = DDZ(v.x, outloc, method, region) + v.x * metric->G1_13 - + v.y * metric->G1_23 + v.z * metric->G1_33; - result.y = DDZ(v.y, outloc, method, region) + v.x * metric->G2_13 - + v.y * metric->G2_23 + v.z * metric->G2_33; - result.z = DDZ(v.z, outloc, method, region) + v.x * metric->G3_13 - + v.y * metric->G3_23 + v.z * metric->G3_33; + result.x = DDZ(v.x, outloc, method, region) + v.x * metric->G1_13() + + v.y * metric->G1_23() + v.z * metric->G1_33(); + result.y = DDZ(v.y, outloc, method, region) + v.x * metric->G2_13() + + v.y * metric->G2_23() + v.z * metric->G2_33(); + result.z = DDZ(v.z, outloc, method, region) + v.x * metric->G3_13() + + v.y * metric->G3_23() + v.z * metric->G3_33(); result.covariant = false; } @@ -158,12 +158,13 @@ Field3D D2DX2(const Field3D& f, CELL_LOC outloc, const std::string& method, const Coordinates* coords = f.getCoordinates(outloc); Field3D result = - bout::derivatives::index::D2DX2(f, outloc, method, region) / SQ(coords->dx); + bout::derivatives::index::D2DX2(f, outloc, method, region) / SQ(coords->dx()); - if (coords->non_uniform) { + if (coords->non_uniform()) { // Correction for non-uniform f.getMesh() - result += coords->d1_dx * bout::derivatives::index::DDX(f, outloc, "DEFAULT", region) - / coords->dx; + result += coords->d1_dx() + * bout::derivatives::index::DDX(f, outloc, "DEFAULT", region) + / coords->dx(); } ASSERT2(((outloc == CELL_DEFAULT) && (result.getLocation() == f.getLocation())) @@ -177,12 +178,13 @@ Coordinates::FieldMetric D2DX2(const Field2D& f, CELL_LOC outloc, const Coordinates* coords = f.getCoordinates(outloc); Coordinates::FieldMetric result = - bout::derivatives::index::D2DX2(f, outloc, method, region) / SQ(coords->dx); + bout::derivatives::index::D2DX2(f, outloc, method, region) / SQ(coords->dx()); - if (coords->non_uniform) { + if (coords->non_uniform()) { // Correction for non-uniform f.getMesh() - result += coords->d1_dx * bout::derivatives::index::DDX(f, outloc, "DEFAULT", region) - / coords->dx; + result += coords->d1_dx() + * bout::derivatives::index::DDX(f, outloc, "DEFAULT", region) + / coords->dx(); } return result; @@ -195,12 +197,13 @@ Field3D D2DY2(const Field3D& f, CELL_LOC outloc, const std::string& method, const Coordinates* coords = f.getCoordinates(outloc); Field3D result = - bout::derivatives::index::D2DY2(f, outloc, method, region) / SQ(coords->dy); + bout::derivatives::index::D2DY2(f, outloc, method, region) / SQ(coords->dy()); - if (coords->non_uniform) { + if (coords->non_uniform()) { // Correction for non-uniform f.getMesh() - result += coords->d1_dy * bout::derivatives::index::DDY(f, outloc, "DEFAULT", region) - / coords->dy; + result += coords->d1_dy() + * bout::derivatives::index::DDY(f, outloc, "DEFAULT", region) + / coords->dy(); } ASSERT2(((outloc == CELL_DEFAULT) && (result.getLocation() == f.getLocation())) @@ -214,11 +217,12 @@ Coordinates::FieldMetric D2DY2(const Field2D& f, CELL_LOC outloc, const Coordinates* coords = f.getCoordinates(outloc); Coordinates::FieldMetric result = - bout::derivatives::index::D2DY2(f, outloc, method, region) / SQ(coords->dy); - if (coords->non_uniform) { + bout::derivatives::index::D2DY2(f, outloc, method, region) / SQ(coords->dy()); + if (coords->non_uniform()) { // Correction for non-uniform f.getMesh() - result += coords->d1_dy * bout::derivatives::index::DDY(f, outloc, "DEFAULT", region) - / coords->dy; + result += coords->d1_dy() + * bout::derivatives::index::DDY(f, outloc, "DEFAULT", region) + / coords->dy(); } return result; @@ -229,13 +233,13 @@ Coordinates::FieldMetric D2DY2(const Field2D& f, CELL_LOC outloc, Field3D D2DZ2(const Field3D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::D2DZ2(f, outloc, method, region) - / SQ(f.getCoordinates(outloc)->dz); + / SQ(f.getCoordinates(outloc)->dz()); } Coordinates::FieldMetric D2DZ2(const Field2D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::D2DZ2(f, outloc, method, region) - / SQ(f.getCoordinates(outloc)->dz); + / SQ(f.getCoordinates(outloc)->dz()); } /******************************************************************************* @@ -245,37 +249,37 @@ Coordinates::FieldMetric D2DZ2(const Field2D& f, CELL_LOC outloc, Field3D D4DX4(const Field3D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::D4DX4(f, outloc, method, region) - / SQ(SQ(f.getCoordinates(outloc)->dx)); + / SQ(SQ(f.getCoordinates(outloc)->dx())); } Coordinates::FieldMetric D4DX4(const Field2D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::D4DX4(f, outloc, method, region) - / SQ(SQ(f.getCoordinates(outloc)->dx)); + / SQ(SQ(f.getCoordinates(outloc)->dx())); } Field3D D4DY4(const Field3D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::D4DY4(f, outloc, method, region) - / SQ(SQ(f.getCoordinates(outloc)->dy)); + / SQ(SQ(f.getCoordinates(outloc)->dy())); } Coordinates::FieldMetric D4DY4(const Field2D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::D4DY4(f, outloc, method, region) - / SQ(SQ(f.getCoordinates(outloc)->dy)); + / SQ(SQ(f.getCoordinates(outloc)->dy())); } Field3D D4DZ4(const Field3D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::D4DZ4(f, outloc, method, region) - / SQ(SQ(f.getCoordinates(outloc)->dz)); + / SQ(SQ(f.getCoordinates(outloc)->dz())); } Coordinates::FieldMetric D4DZ4(const Field2D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::D4DZ4(f, outloc, method, region) - / SQ(SQ(f.getCoordinates(outloc)->dz)); + / SQ(SQ(f.getCoordinates(outloc)->dz())); } /******************************************************************************* @@ -393,14 +397,14 @@ Field3D D2DYDZ(const Field3D& f, CELL_LOC outloc, Coordinates::FieldMetric VDDX(const Field2D& v, const Field2D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::VDDX(v, f, outloc, method, region) - / f.getCoordinates(outloc)->dx; + / f.getCoordinates(outloc)->dx(); } /// General version for 2 or 3-D objects Field3D VDDX(const Field3D& v, const Field3D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::VDDX(v, f, outloc, method, region) - / f.getCoordinates(outloc)->dx; + / f.getCoordinates(outloc)->dx(); } ////////////// Y DERIVATIVE ///////////////// @@ -409,14 +413,14 @@ Field3D VDDX(const Field3D& v, const Field3D& f, CELL_LOC outloc, Coordinates::FieldMetric VDDY(const Field2D& v, const Field2D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::VDDY(v, f, outloc, method, region) - / f.getCoordinates(outloc)->dy; + / f.getCoordinates(outloc)->dy(); } // general case Field3D VDDY(const Field3D& v, const Field3DParallel& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::VDDY(v, f, outloc, method, region) - / f.getCoordinates(outloc)->dy; + / f.getCoordinates(outloc)->dy(); } ////////////// Z DERIVATIVE ///////////////// @@ -425,7 +429,7 @@ Field3D VDDY(const Field3D& v, const Field3DParallel& f, CELL_LOC outloc, Coordinates::FieldMetric VDDZ(const Field2D& v, const Field2D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::VDDZ(v, f, outloc, method, region) - / f.getCoordinates(outloc)->dz; + / f.getCoordinates(outloc)->dz(); } // Note that this is zero because no compression is included @@ -435,7 +439,7 @@ Coordinates::FieldMetric VDDZ([[maybe_unused]] const Field3D& v, const Field2D& #if BOUT_USE_METRIC_3D Field3D tmp{f}; return bout::derivatives::index::VDDZ(v, tmp, outloc, method, region) - / f.getCoordinates(outloc)->dz; + / f.getCoordinates(outloc)->dz(); #else if (outloc == CELL_DEFAULT) { outloc = f.getLocation(); @@ -448,7 +452,7 @@ Coordinates::FieldMetric VDDZ([[maybe_unused]] const Field3D& v, const Field2D& Field3D VDDZ(const Field3D& v, const Field3D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::VDDZ(v, f, outloc, method, region) - / f.getCoordinates(outloc)->dz; + / f.getCoordinates(outloc)->dz(); } /******************************************************************************* @@ -457,13 +461,13 @@ Field3D VDDZ(const Field3D& v, const Field3D& f, CELL_LOC outloc, Coordinates::FieldMetric FDDX(const Field2D& v, const Field2D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::FDDX(v, f, outloc, method, region) - / f.getCoordinates(outloc)->dx; + / f.getCoordinates(outloc)->dx(); } Field3D FDDX(const Field3D& v, const Field3D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::FDDX(v, f, outloc, method, region) - / f.getCoordinates(outloc)->dx; + / f.getCoordinates(outloc)->dx(); } ///////////////////////////////////////////////////////////////////////// @@ -471,13 +475,13 @@ Field3D FDDX(const Field3D& v, const Field3D& f, CELL_LOC outloc, Coordinates::FieldMetric FDDY(const Field2D& v, const Field2D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::FDDY(v, f, outloc, method, region) - / f.getCoordinates(outloc)->dy; + / f.getCoordinates(outloc)->dy(); } Field3D FDDY(const Field3D& v, const Field3DParallel& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::FDDY(v, f, outloc, method, region) - / f.getCoordinates(outloc)->dy; + / f.getCoordinates(outloc)->dy(); } ///////////////////////////////////////////////////////////////////////// @@ -485,11 +489,11 @@ Field3D FDDY(const Field3D& v, const Field3DParallel& f, CELL_LOC outloc, Coordinates::FieldMetric FDDZ(const Field2D& v, const Field2D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::FDDZ(v, f, outloc, method, region) - / f.getCoordinates(outloc)->dz; + / f.getCoordinates(outloc)->dz(); } Field3D FDDZ(const Field3D& v, const Field3D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::FDDZ(v, f, outloc, method, region) - / f.getCoordinates(outloc)->dz; + / f.getCoordinates(outloc)->dz(); } diff --git a/tests/MMS/GBS/gbs.cxx b/tests/MMS/GBS/gbs.cxx index 6d7fbcafd2..ab23efbd6c 100644 --- a/tests/MMS/GBS/gbs.cxx +++ b/tests/MMS/GBS/gbs.cxx @@ -281,7 +281,7 @@ int GBS::init(bool restarting) { break; } case 3: { // logB, taken from mesh - logB = log(coords->Bxy); + logB = log(coords->Bxy()); break; } default: @@ -292,14 +292,14 @@ int GBS::init(bool restarting) { phiSolver = Laplacian::create(opt->getSection("phiSolver")); aparSolver = Laplacian::create(opt->getSection("aparSolver")); - dx4 = SQ(SQ(coords->dx)); - dy4 = SQ(SQ(coords->dy)); - dz4 = SQ(SQ(coords->dz)); + dx4 = SQ(SQ(coords->dx())); + dy4 = SQ(SQ(coords->dy())); + dz4 = SQ(SQ(coords->dz())); SAVE_REPEAT(Ve); output.write("dx = {:e}, dy = {:e}, dz = {:e}\n", coords->dx(2, 2), coords->dy(2, 2), - coords->dz); + coords->dz()); output.write("g11 = {:e}, g22 = {:e}, g33 = {:e}\n", coords->g11(2, 2), coords->g22(2, 2), coords->g33(2, 2)); output.write("g12 = {:e}, g23 = {:e}\n", coords->g12(2, 2), coords->g23(2, 2)); @@ -369,7 +369,7 @@ int GBS::rhs(BoutReal t) { Gi = 0.0; if (ionvis) { Field3D tau_i = Omega_ci * tau_i0 * pow(Ti, 1.5) / Ne; - Gi = -(0.96 * Ti * Ne * tau_i) * (2. * Grad_par(Vi) + C(phi) / coords->Bxy); + Gi = -(0.96 * Ti * Ne * tau_i) * (2. * Grad_par(Vi) + C(phi) / coords->Bxy()); mesh->communicate(Gi); Gi.applyBoundary("neumann"); } else { @@ -382,7 +382,8 @@ int GBS::rhs(BoutReal t) { Ge = 0.0; if (elecvis) { Ge = -(0.73 * Te * Ne * tau_e) - * (2. * Grad_par(Ve) + (5. * C(Te) + 5. * Te * C(logNe) + C(phi)) / coords->Bxy); + * (2. * Grad_par(Ve) + + (5. * C(Te) + 5. * Te * C(logNe) + C(phi)) / coords->Bxy()); mesh->communicate(Ge); Ge.applyBoundary("neumann"); } else { @@ -396,8 +397,8 @@ int GBS::rhs(BoutReal t) { if (evolve_Ne) { // Density - ddt(Ne) = -vE_Grad(Ne, phi) // ExB term - + (2. / coords->Bxy) * (C(Pe) - Ne * C(phi)) // Perpendicular compression + ddt(Ne) = -vE_Grad(Ne, phi) // ExB term + + (2. / coords->Bxy()) * (C(Pe) - Ne * C(phi)) // Perpendicular compression + D(Ne, Dn) + H(Ne, Hn); if (parallel) { @@ -414,7 +415,7 @@ int GBS::rhs(BoutReal t) { if (evolve_Te) { // Electron temperature ddt(Te) = -vE_Grad(Te, phi) - + (4. / 3.) * (Te / coords->Bxy) + + (4. / 3.) * (Te / coords->Bxy()) * ((7. / 2.) * C(Te) + (Te / Ne) * C(Ne) - C(phi)) + D(Te, Dte) + H(Te, Hte); @@ -438,14 +439,14 @@ int GBS::rhs(BoutReal t) { if (evolve_Vort) { // Vorticity ddt(Vort) = -vE_Grad(Vort, phi) // ExB term - + 2. * coords->Bxy * C(Pe) / Ne + coords->Bxy * C(Gi) / (3. * Ne) + + 2. * coords->Bxy() * C(Pe) / Ne + coords->Bxy() * C(Gi) / (3. * Ne) + D(Vort, Dvort) + H(Vort, Hvort); if (parallel) { Field3D delV = Vi - Ve; mesh->communicate(delV); ddt(Vort) -= Vpar_Grad_par(Vi, Vort); // Parallel advection - ddt(Vort) += SQ(coords->Bxy) * (Grad_par(delV) + (Vi - Ve) * Grad_par(logNe)); + ddt(Vort) += SQ(coords->Bxy()) * (Grad_par(delV) + (Vi - Ve) * Grad_par(logNe)); } } @@ -481,7 +482,7 @@ const Field3D GBS::C(const Field3D& f) { // Curvature operator mesh->communicate(g); return bxcv * Grad(g); } - return coords->Bxy * bracket(logB, f, BRACKET_ARAKAWA); + return coords->Bxy() * bracket(logB, f, BRACKET_ARAKAWA); } const Field3D GBS::D(const Field3D& f, BoutReal d) { // Diffusion operator diff --git a/tests/MMS/advection/advection.cxx b/tests/MMS/advection/advection.cxx index 1201fbc3ac..6b8510ec83 100644 --- a/tests/MMS/advection/advection.cxx +++ b/tests/MMS/advection/advection.cxx @@ -18,8 +18,8 @@ class AdvectMMS : public PhysicsModel { Coordinates* coords = mesh->getCoordinates(); - dx_sq_sq = SQ(SQ(coords->dx)); - dz_sq_sq = SQ(SQ(coords->dz)); + dx_sq_sq = SQ(SQ(coords->dx())); + dz_sq_sq = SQ(SQ(coords->dz())); return 0; } diff --git a/tests/MMS/diffusion/diffusion.cxx b/tests/MMS/diffusion/diffusion.cxx index bd969d4d86..3353767e35 100644 --- a/tests/MMS/diffusion/diffusion.cxx +++ b/tests/MMS/diffusion/diffusion.cxx @@ -32,8 +32,8 @@ int Diffusion::init(bool UNUSED(restarting)) { /*this assumes equidistant grid*/ int nguard = mesh->xstart; - coord->dx = Lx / (mesh->GlobalNx - 2 * nguard); - coord->dy = Ly / (mesh->GlobalNy - 2 * nguard); + coord->setDx(Lx / (mesh->GlobalNx - 2 * nguard)); + coord->setDy(Ly / (mesh->GlobalNy - 2 * nguard)); SAVE_ONCE2(Lx, Ly); @@ -43,20 +43,8 @@ int Diffusion::init(bool UNUSED(restarting)) { SAVE_ONCE(mu_N); //set mesh - coord->g11 = 1.0; - coord->g22 = 1.0; - coord->g33 = 1.0; - coord->g12 = 0.0; - coord->g13 = 0.0; - coord->g23 = 0.0; - - coord->g_11 = 1.0; - coord->g_22 = 1.0; - coord->g_33 = 1.0; - coord->g_12 = 0.0; - coord->g_13 = 0.0; - coord->g_23 = 0.0; - coord->geometry(); + coord->setMetricTensor(ContravariantMetricTensor(1.0, 1.0, 1.0, 0.0, 0.0, 0.0), + CovariantMetricTensor(1.0, 1.0, 1.0, 0.0, 0.0, 0.0)); // Tell BOUT++ to solve N SOLVE_FOR(N); diff --git a/tests/MMS/diffusion2/diffusion.cxx b/tests/MMS/diffusion2/diffusion.cxx index bd2cd72a08..50313fcd2a 100644 --- a/tests/MMS/diffusion2/diffusion.cxx +++ b/tests/MMS/diffusion2/diffusion.cxx @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -21,9 +22,9 @@ class Diffusion : public PhysicsModel { meshoptions->get("Ly", Ly, 1.0); /*this assumes equidistant grid*/ - coords->dx = Lx / (mesh->GlobalNx - 2 * mesh->xstart); + coords->setDx(Lx / (mesh->GlobalNx - 2 * mesh->xstart)); - coords->dy = Ly / (mesh->GlobalNy - 2 * mesh->ystart); + coords->setDy(Ly / (mesh->GlobalNy - 2 * mesh->ystart)); output.write("SIZES: {:d}, {:d}, {:e}\n", mesh->GlobalNy, (mesh->GlobalNy - 2 * mesh->ystart), coords->dy(0, 0, 0)); @@ -38,20 +39,8 @@ class Diffusion : public PhysicsModel { SAVE_ONCE3(Dx, Dy, Dz); // set mesh - coords->g11 = 1.0; - coords->g22 = 1.0; - coords->g33 = 1.0; - coords->g12 = 0.0; - coords->g13 = 0.0; - coords->g23 = 0.0; - - coords->g_11 = 1.0; - coords->g_22 = 1.0; - coords->g_33 = 1.0; - coords->g_12 = 0.0; - coords->g_13 = 0.0; - coords->g_23 = 0.0; - coords->geometry(); + coords->setMetricTensor(ContravariantMetricTensor(1.0, 1.0, 1.0, 0.0, 0.0, 0.0), + CovariantMetricTensor(1.0, 1.0, 1.0, 0.0, 0.0, 0.0)); // Tell BOUT++ to solve N SOLVE_FOR(N); diff --git a/tests/MMS/elm-pb/elm_pb.cxx b/tests/MMS/elm-pb/elm_pb.cxx index 8fb2a58cbf..f678f89aac 100644 --- a/tests/MMS/elm-pb/elm_pb.cxx +++ b/tests/MMS/elm-pb/elm_pb.cxx @@ -292,7 +292,7 @@ class ELMpb : public PhysicsModel { const auto& hthe = tokamak_coords.hthe; const auto& I = tokamak_coords.I_unnormalised; - B0 = tokamak_coords.Bxy; + B0 = tokamak_coords.Bxy(); if (include_curvature) { // Load curvature term @@ -315,7 +315,7 @@ class ELMpb : public PhysicsModel { if (ShiftXderivs) { if (mesh->IncIntShear) { // BOUT-06 style, using d/dx = d/dpsi + I * d/dz - mesh->getCoordinates()->IntShiftTorsion = I; + mesh->getCoordinates()->setIntShiftTorsion(I); } else { // Dimits style, using local coordinate system if (include_curvature) { @@ -596,7 +596,7 @@ class ELMpb : public PhysicsModel { ddt(U) += viscos_perp * Delp2(U); // Perpendicular viscosity } - ddt(U) -= 10 * (SQ(SQ(coords->dx)) * D4DX4(U) + SQ(SQ(coords->dz)) * D4DZ4(U)); + ddt(U) -= 10 * (SQ(SQ(coords->dx())) * D4DX4(U) + SQ(SQ(coords->dz())) * D4DZ4(U)); //////////////////////////////////////////////////// // Pressure equation @@ -616,7 +616,7 @@ class ELMpb : public PhysicsModel { ddt(P) += diffusion_par * Grad2_par2(P); // Parallel diffusion } - ddt(P) -= 10 * (SQ(SQ(coords->dx)) * D4DX4(P) + SQ(SQ(coords->dz)) * D4DZ4(P)); + ddt(P) -= 10 * (SQ(SQ(coords->dx())) * D4DX4(P) + SQ(SQ(coords->dz())) * D4DZ4(P)); //////////////////////////////////////////////////// // Compressional effects diff --git a/tests/MMS/fieldalign/fieldalign.cxx b/tests/MMS/fieldalign/fieldalign.cxx index 75481dee9a..16e9c50bc8 100644 --- a/tests/MMS/fieldalign/fieldalign.cxx +++ b/tests/MMS/fieldalign/fieldalign.cxx @@ -20,14 +20,16 @@ class FieldAlign : public PhysicsModel { // df/dt = df/dtheta + df/dphi ddt(f) = - vx / G * (metric->g11 * DDX(f) + metric->g12 * DDY(f) + metric->g13 * DDZ(f)) - + vy / G * (metric->g12 * DDX(f) + metric->g22 * DDY(f) + metric->g23 * DDZ(f)) + vx / G + * (metric->g11() * DDX(f) + metric->g12() * DDY(f) + metric->g13() * DDZ(f)) + + vy / G + * (metric->g12() * DDX(f) + metric->g22() * DDY(f) + metric->g23() * DDZ(f)) + // Upwinding with second-order central differencing vz / G - * (metric->g13 * DDX(f) + metric->g23 * DDY(f) - + metric->g33 * DDZ(f)); // (unstable without additional dissipation) - -SQ(SQ(metric->dx)) * D4DX4(f) /*- SQ(SQ(metric->dy))*D4DY4(f)*/ - - SQ(SQ(metric->dz)) * D4DZ4(f); // Numerical dissipation terms + * (metric->g13() * DDX(f) + metric->g23() * DDY(f) + + metric->g33() * DDZ(f)); // (unstable without additional dissipation) + -SQ(SQ(metric->dx())) * D4DX4(f) /*- SQ(SQ(metric->dy()))*D4DY4(f)*/ + - SQ(SQ(metric->dz())) * D4DZ4(f); // Numerical dissipation terms return 0; } diff --git a/tests/MMS/hw/hw.cxx b/tests/MMS/hw/hw.cxx index c5dd25773f..c51e17ab14 100644 --- a/tests/MMS/hw/hw.cxx +++ b/tests/MMS/hw/hw.cxx @@ -39,8 +39,8 @@ class Hw : public PhysicsModel { /*this assumes equidistant grid*/ int nguard = mesh->xstart; - mesh->getCoordinates()->dx = Lx / (mesh->GlobalNx - 2 * nguard); - mesh->getCoordinates()->dz = TWOPI * Lx / (mesh->LocalNz); + mesh->getCoordinates()->setDx(Lx / (mesh->GlobalNx - 2 * nguard)); + mesh->getCoordinates()->setDz(TWOPI * Lx / (mesh->LocalNz)); ///// SOLVE_FOR2(n, vort); diff --git a/tests/MMS/laplace/laplace.cxx b/tests/MMS/laplace/laplace.cxx index 214c022cd5..5bb87231e8 100644 --- a/tests/MMS/laplace/laplace.cxx +++ b/tests/MMS/laplace/laplace.cxx @@ -26,8 +26,8 @@ int main(int argc, char** argv) { meshoptions->get("Lx", Lx, 1.0); /*this assumes equidistant grid*/ - mesh->getCoordinates()->dx = Lx / (mesh->GlobalNx - 2 * mesh->xstart); - mesh->getCoordinates()->dz = TWOPI * Lx / (mesh->GlobalNz - 2 * mesh->zstart); + mesh->getCoordinates()->setDx(Lx / (mesh->GlobalNx - 2 * mesh->xstart)); + mesh->getCoordinates()->setDz(TWOPI * Lx / (mesh->GlobalNz - 2 * mesh->zstart)); ///// // Create a Laplacian inversion solver diff --git a/tests/MMS/spatial/diffusion/diffusion.cxx b/tests/MMS/spatial/diffusion/diffusion.cxx index 45e516751a..0050cd2bcf 100644 --- a/tests/MMS/spatial/diffusion/diffusion.cxx +++ b/tests/MMS/spatial/diffusion/diffusion.cxx @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -22,9 +23,9 @@ class Diffusion : public PhysicsModel { meshoptions->get("Ly", Ly, 1.0); /*this assumes equidistant grid*/ - coords->dx = Lx / (mesh->GlobalNx - 2 * mesh->xstart); + coords->setDx(Lx / (mesh->GlobalNx - 2 * mesh->xstart)); - coords->dy = Ly / (mesh->GlobalNy - 2 * mesh->ystart); + coords->setDy(Ly / (mesh->GlobalNy - 2 * mesh->ystart)); output.write("SIZES: {:d}, {:d}, {:e}\n", mesh->GlobalNy, (mesh->GlobalNy - 2 * mesh->ystart), coords->dy(0, 0, 0)); @@ -39,20 +40,8 @@ class Diffusion : public PhysicsModel { SAVE_ONCE3(Dx, Dy, Dz); // set mesh - coords->g11 = 1.0; - coords->g22 = 1.0; - coords->g33 = 1.0; - coords->g12 = 0.0; - coords->g13 = 0.0; - coords->g23 = 0.0; - - coords->g_11 = 1.0; - coords->g_22 = 1.0; - coords->g_33 = 1.0; - coords->g_12 = 0.0; - coords->g_13 = 0.0; - coords->g_23 = 0.0; - coords->geometry(); + coords->setMetricTensor(ContravariantMetricTensor(1.0, 1.0, 1.0, 0.0, 0.0, 0.0), + CovariantMetricTensor(1.0, 1.0, 1.0, 0.0, 0.0, 0.0)); // Tell BOUT++ to solve N SOLVE_FOR(N); diff --git a/tests/MMS/tokamak/tokamak.cxx b/tests/MMS/tokamak/tokamak.cxx index 8b6baab950..fa89c9eb36 100644 --- a/tests/MMS/tokamak/tokamak.cxx +++ b/tests/MMS/tokamak/tokamak.cxx @@ -34,8 +34,8 @@ class TokamakMMS : public PhysicsModel { // Test bracket advection operator ddt(advect) = -1e-3 * bracket(drive, advect, BRACKET_ARAKAWA) - 10. - * (SQ(SQ(mesh->getCoordinates()->dx)) * D4DX4(advect) - + SQ(SQ(mesh->getCoordinates()->dz)) * D4DZ4(advect)); + * (SQ(SQ(mesh->getCoordinates()->dx())) * D4DX4(advect) + + SQ(SQ(mesh->getCoordinates()->dz())) * D4DZ4(advect)); // Test perpendicular diffusion operator ddt(delp2) = 1e-5 * Delp2(delp2); diff --git a/tests/MMS/wave-1d/wave.cxx b/tests/MMS/wave-1d/wave.cxx index 4f53d098c6..7c2506f3d8 100644 --- a/tests/MMS/wave-1d/wave.cxx +++ b/tests/MMS/wave-1d/wave.cxx @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -26,26 +27,14 @@ class Wave1D : public PhysicsModel { // this assumes equidistant grid int nguard = mesh->xstart; - coord->dx = Lx / (mesh->GlobalNx - 2 * nguard); - coord->dy = Ly / (mesh->GlobalNy - 2 * nguard); + coord->setDx(Lx / (mesh->GlobalNx - 2 * nguard)); + coord->setDy(Ly / (mesh->GlobalNy - 2 * nguard)); SAVE_ONCE(Lx, Ly); //set mesh - coord->g11 = 1.0; - coord->g22 = 1.0; - coord->g33 = 1.0; - coord->g12 = 0.0; - coord->g13 = 0.0; - coord->g23 = 0.0; - - coord->g_11 = 1.0; - coord->g_22 = 1.0; - coord->g_33 = 1.0; - coord->g_12 = 0.0; - coord->g_13 = 0.0; - coord->g_23 = 0.0; - coord->geometry(); + coord->setMetricTensor(ContravariantMetricTensor(1.0, 1.0, 1.0, 0.0, 0.0, 0.0), + CovariantMetricTensor(1.0, 1.0, 1.0, 0.0, 0.0, 0.0)); g.setLocation(CELL_XLOW); // g staggered to the left of f @@ -63,8 +52,8 @@ class Wave1D : public PhysicsModel { g.applyBoundary(t); // Central differencing - ddt(f) = DDX(g, CELL_CENTRE); // + 20*SQ(coord->dx)*D2DX2(f); - ddt(g) = DDX(f, CELL_XLOW); // + 20*SQ(coord->dx)*D2DX2(g); + ddt(f) = DDX(g, CELL_CENTRE); // + 20*SQ(coord->dx())*D2DX2(f); + ddt(g) = DDX(f, CELL_XLOW); // + 20*SQ(coord->dx())*D2DX2(g); return 0; } diff --git a/tests/integrated/test-drift-instability/2fluid.cxx b/tests/integrated/test-drift-instability/2fluid.cxx index dca5a69e3b..59f3b22342 100644 --- a/tests/integrated/test-drift-instability/2fluid.cxx +++ b/tests/integrated/test-drift-instability/2fluid.cxx @@ -14,7 +14,7 @@ #include // just define a macro for V_E dot Grad -#define vE_Grad(f, p) (b0xGrad_dot_Grad(p, f) / coord->Bxy) +#define vE_Grad(f, p) (b0xGrad_dot_Grad(p, f) / coord->Bxy()) class TwoFluid : public PhysicsModel { // 2D initial profiles @@ -368,7 +368,7 @@ class TwoFluid : public PhysicsModel { if (evolve_rho) { auto divPar_jpar_ylow = Div_par(jpar); mesh->communicate(divPar_jpar_ylow); - ddt(rho) += SQ(coord->Bxy) * interp_to(divPar_jpar_ylow, CELL_CENTRE); + ddt(rho) += SQ(coord->Bxy()) * interp_to(divPar_jpar_ylow, CELL_CENTRE); } // AJPAR diff --git a/tests/integrated/test-interchange-instability/2fluid.cxx b/tests/integrated/test-interchange-instability/2fluid.cxx index db0fef2fbf..3909093007 100644 --- a/tests/integrated/test-interchange-instability/2fluid.cxx +++ b/tests/integrated/test-interchange-instability/2fluid.cxx @@ -142,10 +142,10 @@ class Interchange : public PhysicsModel { Field3D pei = (Te0 + Ti0) * Ni; // DENSITY EQUATION - ddt(Ni) = -b0xGrad_dot_Grad(phi, Ni0) / coord->Bxy; + ddt(Ni) = -b0xGrad_dot_Grad(phi, Ni0) / coord->Bxy(); // VORTICITY - ddt(rho) = 2.0 * coord->Bxy * b0xcv * Grad(pei); + ddt(rho) = 2.0 * coord->Bxy() * b0xcv * Grad(pei); return (0); } diff --git a/tests/integrated/test-laplacexy-short/test-laplacexy.cxx b/tests/integrated/test-laplacexy-short/test-laplacexy.cxx index 7c284290b3..1ae5ded0da 100644 --- a/tests/integrated/test-laplacexy-short/test-laplacexy.cxx +++ b/tests/integrated/test-laplacexy-short/test-laplacexy.cxx @@ -74,8 +74,8 @@ int main(int argc, char** argv) { if (include_y_derivs) { rhs = a * DC(Laplace_perp(f)) + DC(Grad_perp(a) * Grad_perp(f)) + b * f; } else { - rhs = - a * DC(Delp2(f, CELL_DEFAULT, false)) + DC(coords->g11 * DDX(a) * DDX(f)) + b * f; + rhs = a * DC(Delp2(f, CELL_DEFAULT, false)) + DC(coords->g11() * DDX(a) * DDX(f)) + + b * f; } laplacexy->setCoefs(a, b); @@ -92,7 +92,7 @@ int main(int argc, char** argv) { rhs_check = a * DC(Laplace_perp(sol)) + DC(Grad_perp(a) * Grad_perp(sol)) + b * sol; } else { rhs_check = a * DC(Delp2(sol, CELL_DEFAULT, false)) - + DC(coords->g11 * DDX(a) * DDX(sol)) + b * sol; + + DC(coords->g11() * DDX(a) * DDX(sol)) + b * sol; } Options dump; diff --git a/tests/integrated/test-laplacexy/test-laplacexy.cxx b/tests/integrated/test-laplacexy/test-laplacexy.cxx index 543274d37d..3e215def58 100644 --- a/tests/integrated/test-laplacexy/test-laplacexy.cxx +++ b/tests/integrated/test-laplacexy/test-laplacexy.cxx @@ -69,7 +69,7 @@ int main(int argc, char** argv) { if (include_y_derivs) { rhs = a * Laplace_perp(f) + Grad_perp(a) * Grad_perp(f) + b * f; } else { - rhs = a * Delp2(f, CELL_DEFAULT, false) + coords->g11 * DDX(a) * DDX(f) + b * f; + rhs = a * Delp2(f, CELL_DEFAULT, false) + coords->g11() * DDX(a) * DDX(f) + b * f; } auto laplacexy = LaplaceXY::create(); @@ -89,7 +89,7 @@ int main(int argc, char** argv) { a * Laplace_perp(solution) + Grad_perp(a) * Grad_perp(solution) + b * solution; } else { rhs_check = a * Delp2(solution, CELL_DEFAULT, false) - + coords->g11 * DDX(a) * DDX(solution) + b * solution; + + coords->g11() * DDX(a) * DDX(solution) + b * solution; } Options dump; diff --git a/tests/integrated/test-laplacexz/test-laplacexz.cxx b/tests/integrated/test-laplacexz/test-laplacexz.cxx index 6e43d2f3f7..81688eb135 100644 --- a/tests/integrated/test-laplacexz/test-laplacexz.cxx +++ b/tests/integrated/test-laplacexz/test-laplacexz.cxx @@ -9,30 +9,39 @@ * -mat_superlu_dist_statprint */ #include - #include +#include +#include #include +#include #include +#include +#include +#include int main(int argc, char** argv) { BoutInitialise(argc, argv); auto inv = LaplaceXZ::create(bout::globals::mesh); - auto coord = bout::globals::mesh->getCoordinates(); - coord->g13 = 1.8; // test off-diagonal components with nonzero value + auto* coord = bout::globals::mesh->getCoordinates(); + // test off-diagonal components with nonzero value + coord->setContravariantMetricTensor({1.0, 1.0, 1.0, 0.1, 0.8, 0.1}); // create some input field - Field3D f = FieldFactory::get()->create3D("f", Options::getRoot(), bout::globals::mesh); + const Field3D f = + FieldFactory::get()->create3D("f", Options::getRoot(), bout::globals::mesh); // Calculate the Laplacian with non-zero g13 - Field3D g = coord->g11 * D2DX2(f) + coord->g13 * D2DXDZ(f) + coord->g33 * D2DZ2(f); + const Field3D g = + coord->g11() * D2DX2(f) + coord->g13() * D2DXDZ(f) + coord->g33() * D2DZ2(f); inv->setCoefs(Field2D(1.0), Field2D(0.0)); - Field3D f2 = inv->solve(g, 0.0); // Invert the Laplacian. + const Field3D f2 = inv->solve(g, 0.0); // Invert the Laplacian. - coord->g13 = 0.0; // reset to 0.0 for original laplacexz test + // reset to 0.0 for original laplacexz test + coord->setContravariantMetricTensor({1.0, 1.0, 1.0, 0.0, 0.0, 0.0}); // Now the normal test. output.write("Setting coefficients\n"); @@ -41,17 +50,17 @@ int main(int argc, char** argv) { output.write("First solve\n"); - Field3D rhs = + const Field3D rhs = FieldFactory::get()->create3D("rhs", Options::getRoot(), bout::globals::mesh); - Field3D x = inv->solve(rhs, 0.0); + const Field3D x = inv->solve(rhs, 0.0); output.write("Second solve\n"); inv->setCoefs(Field3D(2.0), Field3D(0.1)); - Field3D rhs2 = + const Field3D rhs2 = FieldFactory::get()->create3D("rhs", Options::getRoot(), bout::globals::mesh); - Field3D x2 = inv->solve(rhs2, 0.0); + const Field3D x2 = inv->solve(rhs2, 0.0); Options dump; diff --git a/tests/integrated/test-multigrid_laplace/test_multigrid_laplace.cxx b/tests/integrated/test-multigrid_laplace/test_multigrid_laplace.cxx index a3e128bfdc..b9f80a6ae0 100644 --- a/tests/integrated/test-multigrid_laplace/test_multigrid_laplace.cxx +++ b/tests/integrated/test-multigrid_laplace/test_multigrid_laplace.cxx @@ -298,9 +298,9 @@ int main(int argc, char** argv) { Field3D this_Grad_perp_dot_Grad_perp(const Field3D& f, const Field3D& g) { auto* mesh = f.getMesh(); - Field3D result = mesh->getCoordinates()->g11 * ::DDX(f) * ::DDX(g) - + mesh->getCoordinates()->g33 * ::DDZ(f) * ::DDZ(g) - + mesh->getCoordinates()->g13 * (DDX(f) * DDZ(g) + DDZ(f) * DDX(g)); + Field3D result = mesh->getCoordinates()->g11() * ::DDX(f) * ::DDX(g) + + mesh->getCoordinates()->g33() * ::DDZ(f) * ::DDZ(g) + + mesh->getCoordinates()->g13() * (DDX(f) * DDZ(g) + DDZ(f) * DDX(g)); return result; } diff --git a/tests/integrated/test-naulin-laplace/test_naulin_laplace.cxx b/tests/integrated/test-naulin-laplace/test_naulin_laplace.cxx index e9af4e5b36..a4af049d14 100644 --- a/tests/integrated/test-naulin-laplace/test_naulin_laplace.cxx +++ b/tests/integrated/test-naulin-laplace/test_naulin_laplace.cxx @@ -304,8 +304,9 @@ int main(int argc, char** argv) { Field3D this_Grad_perp_dot_Grad_perp(const Field3D& f, const Field3D& g) { const auto* coords = f.getCoordinates(); - Field3D result = coords->g11 * ::DDX(f) * ::DDX(g) + coords->g33 * ::DDZ(f) * ::DDZ(g) - + coords->g13 * (DDX(f) * DDZ(g) + DDZ(f) * DDX(g)); + Field3D result = coords->g11() * ::DDX(f) * ::DDX(g) + + coords->g33() * ::DDZ(f) * ::DDZ(g) + + coords->g13() * (DDX(f) * DDZ(g) + DDZ(f) * DDX(g)); return result; } diff --git a/tests/integrated/test-petsc-operators/test_petsc_operators.cxx b/tests/integrated/test-petsc-operators/test_petsc_operators.cxx index 6c70c65f39..2d41d0ae0a 100644 --- a/tests/integrated/test-petsc-operators/test_petsc_operators.cxx +++ b/tests/integrated/test-petsc-operators/test_petsc_operators.cxx @@ -50,7 +50,6 @@ int main(int argc, char** argv) { dump["div_par_op"] = parallel.Div_par(f); auto* coords = bout::globals::mesh->getCoordinates(); - coords->Bxy.applyParallelBoundary("parallel_neumann_o1"); dump["div_par_yud"] = Div_par(f); dump["div_par_grad_par_op"] = parallel.Div_par_Grad_par(f_neumann); diff --git a/tests/integrated/test-snb/test_snb.cxx b/tests/integrated/test-snb/test_snb.cxx index 1b96bfc8b1..68a2884a1e 100644 --- a/tests/integrated/test-snb/test_snb.cxx +++ b/tests/integrated/test-snb/test_snb.cxx @@ -1,6 +1,8 @@ #include #include #include +#include +#include #include #include #include @@ -207,14 +209,18 @@ int main(int argc, char** argv) { // Change the mesh spacing and cell volume (Jdy) Coordinates* coord = Te.getCoordinates(); + auto dy_copy = coord->dy(); + auto J_copy = coord->J(); for (int x = mesh->xstart; x <= mesh->xend; x++) { for (int y = mesh->ystart; y <= mesh->yend; y++) { - double yn = (double(y) + 0.5) / double(mesh->yend + 1); + const double y_n = (double(y) + 0.5) / double(mesh->yend + 1); - coord->dy(x, y) = 1. - 0.9 * yn; - coord->J(x, y) = (1. + yn * yn); + dy_copy(x, y) = 1. - 0.9 * y_n; + J_copy(x, y) = 1. + y_n * y_n; } } + coord->setDy(dy_copy); + coord->setJ(J_copy); HeatFluxSNB snb; @@ -228,8 +234,8 @@ int main(int argc, char** argv) { // Check that fluxes are not equal EXPECT_FALSE(IsFieldClose(Div_q, Div_q_SH, "RGN_NOBNDRY")); - const Field2D dy = coord->dy; - const Field2D J = coord->J; + const Field2D dy = coord->dy(); + const Field2D J = coord->J(); // Integrate Div(q) over domain BoutReal q_sh = 0.0; diff --git a/tests/unit/fake_mesh_fixture.hxx b/tests/unit/fake_mesh_fixture.hxx index 2758dbe416..da1b65cb87 100644 --- a/tests/unit/fake_mesh_fixture.hxx +++ b/tests/unit/fake_mesh_fixture.hxx @@ -3,11 +3,14 @@ #include #include +#include "bout/build_config.hxx" +#include "bout/metric_tensor.hxx" #include #include #include #include #include +#include #include #include #include @@ -47,22 +50,22 @@ public: Field2D{1.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}); - - // Set some auxilliary variables - // Usually set in geometry() - // Note: For testing these are set to non-zero values - test_coords->G1 = test_coords->G2 = test_coords->G3 = 0.1; + mesh_m.setCoordinates(test_coords); // Set nonuniform corrections - test_coords->non_uniform = true; - test_coords->d1_dx = test_coords->d1_dy = 0.2; - test_coords->d1_dz = 0.0; -#if BOUT_USE_METRIC_3D - test_coords->Bxy.splitParallelSlices(); - test_coords->Bxy.yup() = test_coords->Bxy.ydown() = test_coords->Bxy; -#endif - - // No call to Coordinates::geometry() needed here + test_coords->setNon_uniform(true); + test_coords->setD1_dx(0.2); + test_coords->setD1_dy(0.2); + test_coords->setD1_dz(0.0); + + if (bout::build::use_metric_3d) { + bout::FieldMetric mutable_Bxy = test_coords->Bxy(); + mutable_Bxy.splitParallelSlices(); + mutable_Bxy.yup() = test_coords->Bxy(); + mutable_Bxy.ydown() = test_coords->Bxy(); + test_coords->setBxy(mutable_Bxy); + } + mesh_m.setCoordinates(test_coords); mesh_m.setGridDataSource(new FakeGridDataSource()); // May need a ParallelTransform to create fields, because create3D calls @@ -89,22 +92,25 @@ public: Field2D{1.0, &mesh_staggered_m}, Field2D{0.0, &mesh_staggered_m}, Field2D{0.0, &mesh_staggered_m}, Field2D{0.0, &mesh_staggered_m}, Field2D{0.0, &mesh_staggered_m}, Field2D{0.0, &mesh_staggered_m}); - - // Set some auxilliary variables - test_coords_staggered->G1 = test_coords_staggered->G2 = test_coords_staggered->G3 = - 0.1; + mesh_staggered_m.setCoordinates(test_coords_staggered); // Set nonuniform corrections - test_coords_staggered->non_uniform = true; - test_coords_staggered->d1_dx = test_coords_staggered->d1_dy = 0.2; - test_coords_staggered->d1_dz = 0.0; -#if BOUT_USE_METRIC_3D - test_coords_staggered->Bxy.splitParallelSlices(); - test_coords_staggered->Bxy.yup() = test_coords_staggered->Bxy.ydown() = - test_coords_staggered->Bxy; -#endif - - // No call to Coordinates::geometry() needed here + test_coords_staggered->setNon_uniform(true); + test_coords_staggered->setD1_dx(0.2); + test_coords_staggered->setD1_dy(0.2); + test_coords_staggered->setD1_dz(0.0); + + if (bout::build::use_metric_3d) { + bout::FieldMetric mutable_Bxy = test_coords_staggered->Bxy(); + mutable_Bxy.splitParallelSlices(); + test_coords_staggered->setBxy(mutable_Bxy); + + mutable_Bxy = test_coords_staggered->Bxy(); + mutable_Bxy.yup() = test_coords_staggered->Bxy(); + mutable_Bxy.ydown() = test_coords_staggered->Bxy(); + test_coords_staggered->setBxy(mutable_Bxy); + } + test_coords_staggered->setParallelTransform( bout::utils::make_unique(mesh_staggered_m)); diff --git a/tests/unit/fake_parallel_mesh.hxx b/tests/unit/fake_parallel_mesh.hxx index f679963064..bc2892db53 100644 --- a/tests/unit/fake_parallel_mesh.hxx +++ b/tests/unit/fake_parallel_mesh.hxx @@ -265,7 +265,6 @@ std::vector createFakeProcessors(int nx, int ny, int nz, int n 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 static_cast(&meshes[j + i * nype])->setCoordinates(test_coords); test_coords->setParallelTransform( bout::utils::make_unique(*bout::globals::mesh)); diff --git a/tests/unit/field/test_field_factory.cxx b/tests/unit/field/test_field_factory.cxx index 9db9fcef10..38ca612521 100644 --- a/tests/unit/field/test_field_factory.cxx +++ b/tests/unit/field/test_field_factory.cxx @@ -565,8 +565,6 @@ TYPED_TEST(FieldFactoryCreationTest, CreateOnMesh) { 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)); diff --git a/tests/unit/field/test_vector2d.cxx b/tests/unit/field/test_vector2d.cxx index d99d2ed179..354b6b85ad 100644 --- a/tests/unit/field/test_vector2d.cxx +++ b/tests/unit/field/test_vector2d.cxx @@ -42,7 +42,6 @@ class Vector2DTest : public ::testing::Test { Field2D{1.0}, Field2D{2.0}, Field2D{3.0}, Field2D{4.0}, Field2D{5.0}, Field2D{6.0}, Field2D{1.0}, Field2D{2.0}, Field2D{3.0}, Field2D{4.0}, Field2D{5.0}, Field2D{6.0}, Field2D{0.0}, Field2D{0.0})); - // No call to Coordinates::geometry() needed here delete mesh_staggered; mesh_staggered = new FakeMesh(nx, ny, nz); diff --git a/tests/unit/field/test_vector3d.cxx b/tests/unit/field/test_vector3d.cxx index 5b197d5cdf..68977e2228 100644 --- a/tests/unit/field/test_vector3d.cxx +++ b/tests/unit/field/test_vector3d.cxx @@ -41,7 +41,6 @@ class Vector3DTest : public ::testing::Test { Field2D{1.0}, Field2D{2.0}, Field2D{3.0}, Field2D{4.0}, Field2D{5.0}, Field2D{6.0}, Field2D{1.0}, Field2D{2.0}, Field2D{3.0}, Field2D{4.0}, Field2D{5.0}, Field2D{6.0}, Field2D{0.0}, Field2D{0.0})); - // No call to Coordinates::geometry() needed here delete mesh_staggered; mesh_staggered = new FakeMesh(nx, ny, nz); diff --git a/tests/unit/include/bout/test_petsc_indexer.cxx b/tests/unit/include/bout/test_petsc_indexer.cxx index 0e9f597eae..9b1d0a15f8 100644 --- a/tests/unit/include/bout/test_petsc_indexer.cxx +++ b/tests/unit/include/bout/test_petsc_indexer.cxx @@ -45,7 +45,6 @@ class IndexerTest : public FakeMeshFixture { 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 mesh2.setCoordinates(test_coords); // May need a ParallelTransform to create fields, because create3D calls // fromFieldAligned diff --git a/tests/unit/invert/laplace/test_laplace_cyclic.cxx b/tests/unit/invert/laplace/test_laplace_cyclic.cxx index 586dd80adf..c01488a320 100644 --- a/tests/unit/invert/laplace/test_laplace_cyclic.cxx +++ b/tests/unit/invert/laplace/test_laplace_cyclic.cxx @@ -34,7 +34,7 @@ class CyclicForwardOperator { Field3D operator()(Field3D& f) { Field3D result = d * Delp2(f) - + (coords->g11 * DDX(f) + coords->g13 * DDZ(f)) * DDX(c2) / c1 + + (coords->g11() * DDX(f) + coords->g13() * DDZ(f)) * DDX(c2) / c1 + a * f + ex * DDX(f) + ez * DDZ(f); applyBoundaries(result, f); return result; @@ -47,7 +47,7 @@ class CyclicForwardOperator { void applyBoundaries(Field3D& newF, const Field3D& f) const { BOUT_FOR(i, f.getMesh()->getRegion3D("RGN_INNER_X")) { if (inner_x_neumann) { - newF[i] = (f[i.xp()] - f[i]) / coords->dx[i] / sqrt(coords->g_11[i]); + newF[i] = (f[i.xp()] - f[i]) / coords->dx()[i] / sqrt(coords->g_11()[i]); } else { newF[i] = 0.5 * (f[i] + f[i.xp()]); } @@ -55,7 +55,7 @@ class CyclicForwardOperator { BOUT_FOR(i, f.getMesh()->getRegion3D("RGN_OUTER_X")) { if (outer_x_neumann) { - newF[i] = (f[i] - f[i.xm()]) / coords->dx[i] / sqrt(coords->g_11[i]); + newF[i] = (f[i] - f[i.xm()]) / coords->dx()[i] / sqrt(coords->g_11()[i]); } else { newF[i] = 0.5 * (f[i.xm()] + f[i]); } @@ -79,7 +79,6 @@ class CyclicTest : public FakeMeshFixture, static_cast(bout::globals::mesh) ->setGridDataSource(new GridFromOptions(Options::getRoot())); - bout::globals::mesh->getCoordinates()->geometry(); f3.allocate(); coef2.allocate(); coef3.allocate(); diff --git a/tests/unit/invert/laplace/test_laplace_hypre3d.cxx b/tests/unit/invert/laplace/test_laplace_hypre3d.cxx index a721b96833..3a9c332480 100644 --- a/tests/unit/invert/laplace/test_laplace_hypre3d.cxx +++ b/tests/unit/invert/laplace/test_laplace_hypre3d.cxx @@ -40,7 +40,7 @@ class ForwardOperator { const Field3D operator()(Field3D& f) { Field3D result = d * Laplace_perp(f, CELL_DEFAULT, "free", "RGN_NOY") - + (Grad(f) * Grad(c2) - DDY(c2) * DDY(f) / coords->g_22) / c1 + a * f + + (Grad(f) * Grad(c2) - DDY(c2) * DDY(f) / coords->g_22()) / c1 + a * f + ex * DDX(f) + ez * DDZ(f); applyBoundaries(result, f); return result; @@ -56,7 +56,7 @@ class ForwardOperator { void applyBoundaries(Field3D& newF, Field3D& f) { BOUT_FOR(i, f.getMesh()->getRegion3D("RGN_INNER_X")) { if (inner_x_neumann) { - newF[i] = (f[i.xp()] - f[i]) / coords->dx[i] / sqrt(coords->g_11[i]); + newF[i] = (f[i.xp()] - f[i]) / coords->dx()[i] / sqrt(coords->g_11()[i]); } else { newF[i] = 0.5 * (f[i] + f[i.xp()]); } @@ -64,7 +64,7 @@ class ForwardOperator { BOUT_FOR(i, f.getMesh()->getRegion3D("RGN_OUTER_X")) { if (outer_x_neumann) { - newF[i] = (f[i] - f[i.xm()]) / coords->dx[i] / sqrt(coords->g_11[i]); + newF[i] = (f[i] - f[i.xm()]) / coords->dx()[i] / sqrt(coords->g_11()[i]); } else { newF[i] = 0.5 * (f[i.xm()] + f[i]); } @@ -72,7 +72,7 @@ class ForwardOperator { BOUT_FOR(i, f.getMesh()->getRegion3D("RGN_LOWER_Y")) { if (lower_y_neumann) { - newF[i] = (f[i.yp()] - f[i]) / coords->dx[i] / sqrt(coords->g_11[i]); + newF[i] = (f[i.yp()] - f[i]) / coords->dx()[i] / sqrt(coords->g_11()[i]); } else { newF[i] = 0.5 * (f[i] + f[i.yp()]); } @@ -80,7 +80,7 @@ class ForwardOperator { BOUT_FOR(i, f.getMesh()->getRegion3D("RGN_UPPER_Y")) { if (upper_y_neumann) { - newF[i] = (f[i] - f[i.ym()]) / coords->dx[i] / sqrt(coords->g_11[i]); + newF[i] = (f[i] - f[i.ym()]) / coords->dx()[i] / sqrt(coords->g_11()[i]); } else { newF[i] = 0.5 * (f[i.ym()] + f[i]); } @@ -98,7 +98,6 @@ class LaplaceHypre3dTest int nx = mesh->GlobalNx, ny = mesh->GlobalNy, nz = mesh->GlobalNz; static_cast(bout::globals::mesh) ->setGridDataSource(new GridFromOptions(Options::getRoot())); - bout::globals::mesh->getCoordinates()->geometry(); f3.allocate(); coef2.allocate(); coef3.allocate(); diff --git a/tests/unit/invert/laplace/test_laplace_petsc3damg.cxx b/tests/unit/invert/laplace/test_laplace_petsc3damg.cxx index 846cb9107f..2b856871bc 100644 --- a/tests/unit/invert/laplace/test_laplace_petsc3damg.cxx +++ b/tests/unit/invert/laplace/test_laplace_petsc3damg.cxx @@ -40,7 +40,7 @@ class ForwardOperator { const Field3D operator()(Field3D& f) { Field3D result = d * Laplace_perp(f, CELL_DEFAULT, "free", "RGN_NOY") - + (Grad(f) * Grad(c2) - DDY(c2) * DDY(f) / coords->g_22) / c1 + a * f + + (Grad(f) * Grad(c2) - DDY(c2) * DDY(f) / coords->g_22()) / c1 + a * f + ex * DDX(f) + ez * DDZ(f); applyBoundaries(result, f); return result; @@ -56,7 +56,7 @@ class ForwardOperator { void applyBoundaries(Field3D& newF, Field3D& f) { BOUT_FOR(i, f.getMesh()->getRegion3D("RGN_INNER_X")) { if (inner_x_neumann) { - newF[i] = (f[i.xp()] - f[i]) / coords->dx[i] / sqrt(coords->g_11[i]); + newF[i] = (f[i.xp()] - f[i]) / coords->dx()[i] / sqrt(coords->g_11()[i]); } else { newF[i] = 0.5 * (f[i] + f[i.xp()]); } @@ -64,7 +64,7 @@ class ForwardOperator { BOUT_FOR(i, f.getMesh()->getRegion3D("RGN_OUTER_X")) { if (outer_x_neumann) { - newF[i] = (f[i] - f[i.xm()]) / coords->dx[i] / sqrt(coords->g_11[i]); + newF[i] = (f[i] - f[i.xm()]) / coords->dx()[i] / sqrt(coords->g_11()[i]); } else { newF[i] = 0.5 * (f[i.xm()] + f[i]); } @@ -72,7 +72,7 @@ class ForwardOperator { BOUT_FOR(i, f.getMesh()->getRegion3D("RGN_LOWER_Y")) { if (lower_y_neumann) { - newF[i] = (f[i.yp()] - f[i]) / coords->dx[i] / sqrt(coords->g_11[i]); + newF[i] = (f[i.yp()] - f[i]) / coords->dx()[i] / sqrt(coords->g_11()[i]); } else { newF[i] = 0.5 * (f[i] + f[i.yp()]); } @@ -80,7 +80,7 @@ class ForwardOperator { BOUT_FOR(i, f.getMesh()->getRegion3D("RGN_UPPER_Y")) { if (upper_y_neumann) { - newF[i] = (f[i] - f[i.ym()]) / coords->dx[i] / sqrt(coords->g_11[i]); + newF[i] = (f[i] - f[i.ym()]) / coords->dx()[i] / sqrt(coords->g_11()[i]); } else { newF[i] = 0.5 * (f[i.ym()] + f[i]); } @@ -99,7 +99,6 @@ class Petsc3dAmgTest int nx = mesh->GlobalNx, ny = mesh->GlobalNy, nz = mesh->GlobalNz; static_cast(bout::globals::mesh) ->setGridDataSource(new GridFromOptions(Options::getRoot())); - bout::globals::mesh->getCoordinates()->geometry(); f3.allocate(); coef2.allocate(); coef3.allocate(); diff --git a/tests/unit/mesh/data/test_gridfromoptions.cxx b/tests/unit/mesh/data/test_gridfromoptions.cxx index 41dd40fcc3..9b1fb9410b 100644 --- a/tests/unit/mesh/data/test_gridfromoptions.cxx +++ b/tests/unit/mesh/data/test_gridfromoptions.cxx @@ -360,12 +360,12 @@ TEST_F(GridFromOptionsTest, CoordinatesCentre) { mesh_from_options.communicate(expected_2d); - EXPECT_TRUE(IsFieldEqual(coords->g11, expected_metric + 5.)); - EXPECT_TRUE(IsFieldEqual(coords->g22, expected_metric + 4.)); - EXPECT_TRUE(IsFieldEqual(coords->g33, expected_metric + 3.)); - EXPECT_TRUE(IsFieldEqual(coords->g12, expected_metric + 2.)); - EXPECT_TRUE(IsFieldEqual(coords->g13, expected_metric + 1.)); - EXPECT_TRUE(IsFieldEqual(coords->g23, expected_metric)); + EXPECT_TRUE(IsFieldEqual(coords->g11(), expected_metric + 5.)); + EXPECT_TRUE(IsFieldEqual(coords->g22(), expected_metric + 4.)); + EXPECT_TRUE(IsFieldEqual(coords->g33(), expected_metric + 3.)); + EXPECT_TRUE(IsFieldEqual(coords->g12(), expected_metric + 2.)); + EXPECT_TRUE(IsFieldEqual(coords->g13(), expected_metric + 1.)); + EXPECT_TRUE(IsFieldEqual(coords->g23(), expected_metric)); } #if not(BOUT_USE_METRIC_3D) @@ -374,12 +374,12 @@ TEST_F(GridFromOptionsTest, CoordinatesZlow) { mesh_from_options.communicate(expected_2d); - EXPECT_TRUE(IsFieldEqual(coords->g11, expected_metric + 5.)); - EXPECT_TRUE(IsFieldEqual(coords->g22, expected_metric + 4.)); - EXPECT_TRUE(IsFieldEqual(coords->g33, expected_metric + 3.)); - EXPECT_TRUE(IsFieldEqual(coords->g12, expected_metric + 2.)); - EXPECT_TRUE(IsFieldEqual(coords->g13, expected_metric + 1.)); - EXPECT_TRUE(IsFieldEqual(coords->g23, expected_metric)); + EXPECT_TRUE(IsFieldEqual(coords->g11(), expected_metric + 5.)); + EXPECT_TRUE(IsFieldEqual(coords->g22(), expected_metric + 4.)); + EXPECT_TRUE(IsFieldEqual(coords->g33(), expected_metric + 3.)); + EXPECT_TRUE(IsFieldEqual(coords->g12(), expected_metric + 2.)); + EXPECT_TRUE(IsFieldEqual(coords->g13(), expected_metric + 1.)); + EXPECT_TRUE(IsFieldEqual(coords->g23(), expected_metric)); } #else // Maybe replace by MMS test, because we need a periodic function in z. @@ -403,16 +403,16 @@ TEST_F(GridFromOptionsTest, CoordinatesXlowInterp) { mesh_from_options.communicate(expected_xlow); EXPECT_TRUE( - IsFieldEqual(coords->g11, expected_xlow + 5., "RGN_NOBNDRY", this_tolerance)); + IsFieldEqual(coords->g11(), expected_xlow + 5., "RGN_NOBNDRY", this_tolerance)); EXPECT_TRUE( - IsFieldEqual(coords->g22, expected_xlow + 4., "RGN_NOBNDRY", this_tolerance)); + IsFieldEqual(coords->g22(), expected_xlow + 4., "RGN_NOBNDRY", this_tolerance)); EXPECT_TRUE( - IsFieldEqual(coords->g33, expected_xlow + 3., "RGN_NOBNDRY", this_tolerance)); + IsFieldEqual(coords->g33(), expected_xlow + 3., "RGN_NOBNDRY", this_tolerance)); EXPECT_TRUE( - IsFieldEqual(coords->g12, expected_xlow + 2., "RGN_NOBNDRY", this_tolerance)); + IsFieldEqual(coords->g12(), expected_xlow + 2., "RGN_NOBNDRY", this_tolerance)); EXPECT_TRUE( - IsFieldEqual(coords->g13, expected_xlow + 1., "RGN_NOBNDRY", this_tolerance)); - EXPECT_TRUE(IsFieldEqual(coords->g23, expected_xlow, "RGN_NOBNDRY", this_tolerance)); + IsFieldEqual(coords->g13(), expected_xlow + 1., "RGN_NOBNDRY", this_tolerance)); + EXPECT_TRUE(IsFieldEqual(coords->g23(), expected_xlow, "RGN_NOBNDRY", this_tolerance)); } TEST_F(GridFromOptionsTest, CoordinatesXlowRead) { @@ -443,18 +443,18 @@ TEST_F(GridFromOptionsTest, CoordinatesXlowRead) { mesh_from_options.communicate(expected_xlow); - EXPECT_TRUE(IsFieldEqual(coords->g11, expected_xlow + 5.)); - EXPECT_TRUE(coords->g11.getLocation() == CELL_XLOW); - EXPECT_TRUE(IsFieldEqual(coords->g22, expected_xlow + 4.)); - EXPECT_TRUE(coords->g22.getLocation() == CELL_XLOW); - EXPECT_TRUE(IsFieldEqual(coords->g33, expected_xlow + 3.)); - EXPECT_TRUE(coords->g33.getLocation() == CELL_XLOW); - EXPECT_TRUE(IsFieldEqual(coords->g12, expected_xlow + 2.)); - EXPECT_TRUE(coords->g12.getLocation() == CELL_XLOW); - EXPECT_TRUE(IsFieldEqual(coords->g13, expected_xlow + 1.)); - EXPECT_TRUE(coords->g13.getLocation() == CELL_XLOW); - EXPECT_TRUE(IsFieldEqual(coords->g23, expected_xlow)); - EXPECT_TRUE(coords->g23.getLocation() == CELL_XLOW); + EXPECT_TRUE(IsFieldEqual(coords->g11(), expected_xlow + 5.)); + EXPECT_TRUE(coords->g11().getLocation() == CELL_XLOW); + EXPECT_TRUE(IsFieldEqual(coords->g22(), expected_xlow + 4.)); + EXPECT_TRUE(coords->g22().getLocation() == CELL_XLOW); + EXPECT_TRUE(IsFieldEqual(coords->g33(), expected_xlow + 3.)); + EXPECT_TRUE(coords->g33().getLocation() == CELL_XLOW); + EXPECT_TRUE(IsFieldEqual(coords->g12(), expected_xlow + 2.)); + EXPECT_TRUE(coords->g12().getLocation() == CELL_XLOW); + EXPECT_TRUE(IsFieldEqual(coords->g13(), expected_xlow + 1.)); + EXPECT_TRUE(coords->g13().getLocation() == CELL_XLOW); + EXPECT_TRUE(IsFieldEqual(coords->g23(), expected_xlow)); + EXPECT_TRUE(coords->g23().getLocation() == CELL_XLOW); } TEST_F(GridFromOptionsTest, CoordinatesYlowInterp) { @@ -476,22 +476,22 @@ TEST_F(GridFromOptionsTest, CoordinatesYlowInterp) { mesh_from_options.communicate(expected_ylow); EXPECT_TRUE( - IsFieldEqual(coords->g11, expected_ylow + 5., "RGN_NOBNDRY", this_tolerance)); - EXPECT_TRUE(coords->g11.getLocation() == CELL_YLOW); + IsFieldEqual(coords->g11(), expected_ylow + 5., "RGN_NOBNDRY", this_tolerance)); + EXPECT_TRUE(coords->g11().getLocation() == CELL_YLOW); EXPECT_TRUE( - IsFieldEqual(coords->g22, expected_ylow + 4., "RGN_NOBNDRY", this_tolerance)); - EXPECT_TRUE(coords->g22.getLocation() == CELL_YLOW); + IsFieldEqual(coords->g22(), expected_ylow + 4., "RGN_NOBNDRY", this_tolerance)); + EXPECT_TRUE(coords->g22().getLocation() == CELL_YLOW); EXPECT_TRUE( - IsFieldEqual(coords->g33, expected_ylow + 3., "RGN_NOBNDRY", this_tolerance)); - EXPECT_TRUE(coords->g33.getLocation() == CELL_YLOW); + IsFieldEqual(coords->g33(), expected_ylow + 3., "RGN_NOBNDRY", this_tolerance)); + EXPECT_TRUE(coords->g33().getLocation() == CELL_YLOW); EXPECT_TRUE( - IsFieldEqual(coords->g12, expected_ylow + 2., "RGN_NOBNDRY", this_tolerance)); - EXPECT_TRUE(coords->g12.getLocation() == CELL_YLOW); + IsFieldEqual(coords->g12(), expected_ylow + 2., "RGN_NOBNDRY", this_tolerance)); + EXPECT_TRUE(coords->g12().getLocation() == CELL_YLOW); EXPECT_TRUE( - IsFieldEqual(coords->g13, expected_ylow + 1., "RGN_NOBNDRY", this_tolerance)); - EXPECT_TRUE(coords->g13.getLocation() == CELL_YLOW); - EXPECT_TRUE(IsFieldEqual(coords->g23, expected_ylow, "RGN_NOBNDRY", this_tolerance)); - EXPECT_TRUE(coords->g23.getLocation() == CELL_YLOW); + IsFieldEqual(coords->g13(), expected_ylow + 1., "RGN_NOBNDRY", this_tolerance)); + EXPECT_TRUE(coords->g13().getLocation() == CELL_YLOW); + EXPECT_TRUE(IsFieldEqual(coords->g23(), expected_ylow, "RGN_NOBNDRY", this_tolerance)); + EXPECT_TRUE(coords->g23().getLocation() == CELL_YLOW); #endif } @@ -525,18 +525,18 @@ TEST_F(GridFromOptionsTest, CoordinatesYlowRead) { mesh_from_options.communicate(expected_ylow); - EXPECT_TRUE(IsFieldEqual(coords->g11, expected_ylow + 5., "RGN_ALL", this_tolerance)); - EXPECT_TRUE(coords->g11.getLocation() == CELL_YLOW); - EXPECT_TRUE(IsFieldEqual(coords->g22, expected_ylow + 4., "RGN_ALL", this_tolerance)); - EXPECT_TRUE(coords->g22.getLocation() == CELL_YLOW); - EXPECT_TRUE(IsFieldEqual(coords->g33, expected_ylow + 3., "RGN_ALL", this_tolerance)); - EXPECT_TRUE(coords->g33.getLocation() == CELL_YLOW); - EXPECT_TRUE(IsFieldEqual(coords->g12, expected_ylow + 2., "RGN_ALL", this_tolerance)); - EXPECT_TRUE(coords->g12.getLocation() == CELL_YLOW); - EXPECT_TRUE(IsFieldEqual(coords->g13, expected_ylow + 1., "RGN_ALL", this_tolerance)); - EXPECT_TRUE(coords->g13.getLocation() == CELL_YLOW); - EXPECT_TRUE(IsFieldEqual(coords->g23, expected_ylow, "RGN_ALL", this_tolerance)); - EXPECT_TRUE(coords->g23.getLocation() == CELL_YLOW); + EXPECT_TRUE(IsFieldEqual(coords->g11(), expected_ylow + 5., "RGN_ALL", this_tolerance)); + EXPECT_TRUE(coords->g11().getLocation() == CELL_YLOW); + EXPECT_TRUE(IsFieldEqual(coords->g22(), expected_ylow + 4., "RGN_ALL", this_tolerance)); + EXPECT_TRUE(coords->g22().getLocation() == CELL_YLOW); + EXPECT_TRUE(IsFieldEqual(coords->g33(), expected_ylow + 3., "RGN_ALL", this_tolerance)); + EXPECT_TRUE(coords->g33().getLocation() == CELL_YLOW); + EXPECT_TRUE(IsFieldEqual(coords->g12(), expected_ylow + 2., "RGN_ALL", this_tolerance)); + EXPECT_TRUE(coords->g12().getLocation() == CELL_YLOW); + EXPECT_TRUE(IsFieldEqual(coords->g13(), expected_ylow + 1., "RGN_ALL", this_tolerance)); + EXPECT_TRUE(coords->g13().getLocation() == CELL_YLOW); + EXPECT_TRUE(IsFieldEqual(coords->g23(), expected_ylow, "RGN_ALL", this_tolerance)); + EXPECT_TRUE(coords->g23().getLocation() == CELL_YLOW); #endif } @@ -547,11 +547,11 @@ TEST_F(GridFromOptionsTest, CoordinatesZlowRead) { auto coords = mesh_from_options.getCoordinates(CELL_ZLOW); - EXPECT_TRUE(IsFieldEqual(coords->g11, expected_2d + 5.)); - EXPECT_TRUE(IsFieldEqual(coords->g22, expected_2d + 4.)); - EXPECT_TRUE(IsFieldEqual(coords->g33, expected_2d + 3.)); - EXPECT_TRUE(IsFieldEqual(coords->g12, expected_2d + 2.)); - EXPECT_TRUE(IsFieldEqual(coords->g13, expected_2d + 1.)); - EXPECT_TRUE(IsFieldEqual(coords->g23, expected_2d)); + EXPECT_TRUE(IsFieldEqual(coords->g11(), expected_2d + 5.)); + EXPECT_TRUE(IsFieldEqual(coords->g22(), expected_2d + 4.)); + EXPECT_TRUE(IsFieldEqual(coords->g33(), expected_2d + 3.)); + EXPECT_TRUE(IsFieldEqual(coords->g12(), expected_2d + 2.)); + EXPECT_TRUE(IsFieldEqual(coords->g13(), expected_2d + 1.)); + EXPECT_TRUE(IsFieldEqual(coords->g23(), expected_2d)); #endif } diff --git a/tests/unit/mesh/parallel/test_shiftedmetric.cxx b/tests/unit/mesh/parallel/test_shiftedmetric.cxx index 579e6fdd7b..30839bf393 100644 --- a/tests/unit/mesh/parallel/test_shiftedmetric.cxx +++ b/tests/unit/mesh/parallel/test_shiftedmetric.cxx @@ -44,7 +44,6 @@ class ShiftedMetricTest : public ::testing::Test { 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 auto coords = mesh->getCoordinates(); coords->setParallelTransform(bout::utils::make_unique( diff --git a/tests/unit/mesh/test_coordinates.cxx b/tests/unit/mesh/test_coordinates.cxx index 8e1aa2e13f..532ccff6db 100644 --- a/tests/unit/mesh/test_coordinates.cxx +++ b/tests/unit/mesh/test_coordinates.cxx @@ -1,4 +1,5 @@ #include "gtest/gtest.h" +#include #include "bout/build_defines.hxx" #include "bout/constants.hxx" @@ -6,6 +7,7 @@ #include "bout/mesh.hxx" #include "bout/output.hxx" +#include "fake_mesh.hxx" #include "fake_mesh_fixture.hxx" #include "test_extras.hxx" @@ -42,7 +44,6 @@ TEST_F(CoordinatesTest, ZLength) { FieldMetric{0.0}, // g_23 FieldMetric{0.0}, // ShiftTorsion FieldMetric{0.0}}; // IntShiftTorsion - // No call to Coordinates::geometry() needed here EXPECT_TRUE(IsFieldEqual(coords.zlength(), 7.0)); } @@ -75,7 +76,6 @@ TEST_F(CoordinatesTest, ZLength3D) { FieldMetric{0.0}, // g_23 FieldMetric{0.0}, // ShiftTorsion FieldMetric{0.0}}; // IntShiftTorsion - // No call to Coordinates::geometry() needed here EXPECT_TRUE(IsFieldEqual(coords.zlength(), expected)); } @@ -102,12 +102,11 @@ TEST_F(CoordinatesTest, Jacobian) { FieldMetric{0.0}, // g_23 FieldMetric{0.0}, // ShiftTorsion FieldMetric{0.0}}; // IntShiftTorsion - // No call to Coordinates::geometry() needed here - EXPECT_NO_THROW(coords.jacobian()); + EXPECT_NO_THROW(coords.recalculateJacobian()); - EXPECT_TRUE(IsFieldEqual(coords.J, 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.Bxy, 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.J(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.Bxy(), 1.0)); } /// To do generalise these tests @@ -133,16 +132,16 @@ TEST_F(CoordinatesTest, CalcContravariant) { FieldMetric{0.0}, // g_23 FieldMetric{0.0}, // ShiftTorsion FieldMetric{0.0}}; // IntShiftTorsion - // No call to Coordinates::geometry() needed here - coords.calcCovariant(); + coords.setContravariantMetricTensor( + ContravariantMetricTensor(1.0, 1.0, 1.0, 0.0, 0.0, 0.0)); - EXPECT_TRUE(IsFieldEqual(coords.g_11, 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.g_22, 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.g_33, 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.g_12, 0.0)); - EXPECT_TRUE(IsFieldEqual(coords.g_13, 0.0)); - EXPECT_TRUE(IsFieldEqual(coords.g_23, 0.0)); + EXPECT_TRUE(IsFieldEqual(coords.g_11(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.g_22(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.g_33(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.g_12(), 0.0)); + EXPECT_TRUE(IsFieldEqual(coords.g_13(), 0.0)); + EXPECT_TRUE(IsFieldEqual(coords.g_23(), 0.0)); } TEST_F(CoordinatesTest, CalcCovariant) { @@ -166,35 +165,34 @@ TEST_F(CoordinatesTest, CalcCovariant) { FieldMetric{0.0}, // g_23 FieldMetric{0.0}, // ShiftTorsion FieldMetric{0.0}}; // IntShiftTorsion - // No call to Coordinates::geometry() needed here - coords.calcContravariant(); + coords.setCovariantMetricTensor(CovariantMetricTensor(1.0, 1.0, 1.0, 0.0, 0.0, 0.0)); - EXPECT_TRUE(IsFieldEqual(coords.g11, 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.g22, 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.g33, 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.g12, 0.0)); - EXPECT_TRUE(IsFieldEqual(coords.g13, 0.0)); - EXPECT_TRUE(IsFieldEqual(coords.g23, 0.0)); + EXPECT_TRUE(IsFieldEqual(coords.g11(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.g22(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.g33(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.g12(), 0.0)); + EXPECT_TRUE(IsFieldEqual(coords.g13(), 0.0)); + EXPECT_TRUE(IsFieldEqual(coords.g23(), 0.0)); } // #endif TEST_F(CoordinatesTest, DefaultConstructor) { Coordinates coords(mesh); - EXPECT_TRUE(IsFieldEqual(coords.dx, 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.dy, 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.dz, default_dz)); + EXPECT_TRUE(IsFieldEqual(coords.dx(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.dy(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.dz(), default_dz)); - EXPECT_TRUE(IsFieldEqual(coords.g11, 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.g22, 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.g33, 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.g12, 0.0)); - EXPECT_TRUE(IsFieldEqual(coords.g13, 0.0)); - EXPECT_TRUE(IsFieldEqual(coords.g23, 0.0)); + EXPECT_TRUE(IsFieldEqual(coords.g11(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.g22(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.g33(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.g12(), 0.0)); + EXPECT_TRUE(IsFieldEqual(coords.g13(), 0.0)); + EXPECT_TRUE(IsFieldEqual(coords.g23(), 0.0)); - EXPECT_TRUE(IsFieldEqual(coords.J, 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.Bxy, 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.J(), 1.0, "RGN_NOCORNERS")); + EXPECT_TRUE(IsFieldEqual(coords.Bxy(), 1.0, "RGN_NOCORNERS")); } TEST_F(CoordinatesTest, ConstructWithMeshSpacing) { @@ -204,58 +202,58 @@ TEST_F(CoordinatesTest, ConstructWithMeshSpacing) { Coordinates coords(mesh); - EXPECT_TRUE(IsFieldEqual(coords.dx, 2.0)); - EXPECT_TRUE(IsFieldEqual(coords.dy, 3.2)); - EXPECT_TRUE(IsFieldEqual(coords.dz, 42.)); + EXPECT_TRUE(IsFieldEqual(coords.dx(), 2.0)); + EXPECT_TRUE(IsFieldEqual(coords.dy(), 3.2)); + EXPECT_TRUE(IsFieldEqual(coords.dz(), 42.)); - EXPECT_TRUE(IsFieldEqual(coords.g11, 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.g22, 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.g33, 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.g12, 0.0)); - EXPECT_TRUE(IsFieldEqual(coords.g13, 0.0)); - EXPECT_TRUE(IsFieldEqual(coords.g23, 0.0)); + EXPECT_TRUE(IsFieldEqual(coords.g11(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.g22(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.g33(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.g12(), 0.0)); + EXPECT_TRUE(IsFieldEqual(coords.g13(), 0.0)); + EXPECT_TRUE(IsFieldEqual(coords.g23(), 0.0)); - EXPECT_TRUE(IsFieldEqual(coords.J, 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.Bxy, 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.J(), 1.0, "RGN_NOCORNERS")); + EXPECT_TRUE(IsFieldEqual(coords.Bxy(), 1.0, "RGN_NOCORNERS")); } TEST_F(CoordinatesTest, SmallMeshSpacing) { static_cast(bout::globals::mesh) ->setGridDataSource(new FakeGridDataSource({{"dx", 1e-9}})); - Coordinates coords(mesh); - EXPECT_THROW(coords.geometry(), BoutException); + WithQuietOutput quiet_info{output_info}; + WithQuietOutput quiet_warn{output_warn}; + EXPECT_THROW(Coordinates{mesh}, BoutException); } TEST_F(CoordinatesTest, ConstructWithDiagonalContravariantMetric) { - static_cast(bout::globals::mesh) - ->setGridDataSource( - new FakeGridDataSource({{"g11", 2.0}, {"g22", 3.2}, {"g33", 42}})); + dynamic_cast(mesh)->setGridDataSource( + new FakeGridDataSource({{"g11", 2.0}, {"g22", 3.2}, {"g33", 42}})); - Coordinates coords(mesh); + const Coordinates coords(mesh); // Didn't specify grid spacing, so default to 1 - EXPECT_TRUE(IsFieldEqual(coords.dx, 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.dy, 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.dz, default_dz)); + EXPECT_TRUE(IsFieldEqual(coords.dx(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.dy(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.dz(), default_dz)); // Diagonal contravariant metric - EXPECT_TRUE(IsFieldEqual(coords.g11, 2.0)); - EXPECT_TRUE(IsFieldEqual(coords.g22, 3.2)); - EXPECT_TRUE(IsFieldEqual(coords.g33, 42)); - EXPECT_TRUE(IsFieldEqual(coords.g12, 0.0)); - EXPECT_TRUE(IsFieldEqual(coords.g13, 0.0)); - EXPECT_TRUE(IsFieldEqual(coords.g23, 0.0)); + EXPECT_TRUE(IsFieldEqual(coords.g11(), 2.0)); + EXPECT_TRUE(IsFieldEqual(coords.g22(), 3.2)); + EXPECT_TRUE(IsFieldEqual(coords.g33(), 42)); + EXPECT_TRUE(IsFieldEqual(coords.g12(), 0.0)); + EXPECT_TRUE(IsFieldEqual(coords.g13(), 0.0)); + EXPECT_TRUE(IsFieldEqual(coords.g23(), 0.0)); // Covariant metric should be inverse // Note: Not calculated in corners - EXPECT_TRUE(IsFieldEqual(coords.g_11, 1. / 2.0, "RGN_NOCORNERS")); - EXPECT_TRUE(IsFieldEqual(coords.g_22, 1. / 3.2, "RGN_NOCORNERS")); - EXPECT_TRUE(IsFieldEqual(coords.g_33, 1. / 42, "RGN_NOCORNERS")); + EXPECT_TRUE(IsFieldEqual(coords.g_11(), 1. / 2.0, "RGN_NOCORNERS")); + EXPECT_TRUE(IsFieldEqual(coords.g_22(), 1. / 3.2, "RGN_NOCORNERS")); + EXPECT_TRUE(IsFieldEqual(coords.g_33(), 1. / 42, "RGN_NOCORNERS")); - EXPECT_TRUE(IsFieldEqual(coords.J, 1. / sqrt(2.0 * 3.2 * 42), "RGN_NOCORNERS")); - EXPECT_TRUE(IsFieldEqual(coords.Bxy, sqrt(2.0 * 42), "RGN_NOCORNERS", 1e-10)); + EXPECT_TRUE(IsFieldEqual(coords.J(), 1. / sqrt(2.0 * 3.2 * 42), "RGN_NOCORNERS")); + EXPECT_TRUE(IsFieldEqual(coords.Bxy(), sqrt(2.0 * 42), "RGN_NOCORNERS", 1e-10)); } TEST_F(CoordinatesTest, NegativeJacobian) { @@ -294,21 +292,19 @@ TEST_F(CoordinatesTest, CellAreas) { FieldMetric{0.0}, // g_23 FieldMetric{0.0}, // ShiftTorsion FieldMetric{0.0}}; // IntShiftTorsion - // No call to Coordinates::geometry() needed here - - EXPECT_TRUE(IsFieldEqual(coords.dx, 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.dy, 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.dz, 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.dx(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.dy(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.dz(), 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.g_11, 4.0)); - EXPECT_TRUE(IsFieldEqual(coords.g_22, 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.g_33, 9.0)); - EXPECT_TRUE(IsFieldEqual(coords.g_12, 0.0)); - EXPECT_TRUE(IsFieldEqual(coords.g_13, 0.0)); - EXPECT_TRUE(IsFieldEqual(coords.g_23, 0.0)); + EXPECT_TRUE(IsFieldEqual(coords.g_11(), 4.0)); + EXPECT_TRUE(IsFieldEqual(coords.g_22(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.g_33(), 9.0)); + EXPECT_TRUE(IsFieldEqual(coords.g_12(), 0.0)); + EXPECT_TRUE(IsFieldEqual(coords.g_13(), 0.0)); + EXPECT_TRUE(IsFieldEqual(coords.g_23(), 0.0)); - EXPECT_TRUE(IsFieldEqual(coords.J, 6.0)); - EXPECT_TRUE(IsFieldEqual(coords.Bxy, 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.J(), 6.0)); + EXPECT_TRUE(IsFieldEqual(coords.Bxy(), 1.0)); EXPECT_TRUE(IsFieldEqual(coords.cell_area_xlow(), 3.0, "RGN_NOX")); EXPECT_TRUE(IsFieldEqual(coords.cell_area_xhigh(), 3.0, "RGN_NOX")); @@ -343,19 +339,19 @@ TEST_F(CoordinatesTest, CellAreasUpdate) { FieldMetric{0.0}}; // IntShiftTorsion // No call to Coordinates::geometry() needed here - EXPECT_TRUE(IsFieldEqual(coords.dx, 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.dy, 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.dz, 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.dx(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.dy(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.dz(), 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.g_11, 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.g_22, 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.g_33, 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.g_12, 0.0)); - EXPECT_TRUE(IsFieldEqual(coords.g_13, 0.0)); - EXPECT_TRUE(IsFieldEqual(coords.g_23, 0.0)); + EXPECT_TRUE(IsFieldEqual(coords.g_11(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.g_22(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.g_33(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.g_12(), 0.0)); + EXPECT_TRUE(IsFieldEqual(coords.g_13(), 0.0)); + EXPECT_TRUE(IsFieldEqual(coords.g_23(), 0.0)); - EXPECT_TRUE(IsFieldEqual(coords.J, 1.0)); - EXPECT_TRUE(IsFieldEqual(coords.Bxy, 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.J(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.Bxy(), 1.0)); coords.cell_area_xlow() *= 2; coords.cell_area_xhigh() *= 3; @@ -401,3 +397,282 @@ TEST_F(CoordinatesTest, CellAreaZComputedAfterCellVolume) { EXPECT_TRUE(IsFieldEqual(coords.cell_area_zlow(), 2.0, "RGN_NOZ")); EXPECT_TRUE(IsFieldEqual(coords.cell_area_zhigh(), 2.0, "RGN_NOZ")); } + +TEST_F(CoordinatesTest, GetContravariantMetricTensor) { + Coordinates coords{mesh, + FieldMetric{1.0}, // dx + FieldMetric{1.0}, // dy + FieldMetric{1.0}, // dz + FieldMetric{0.0}, // J + FieldMetric{0.0}, // Bxy + FieldMetric{1.2}, // g11 + FieldMetric{2.3}, // g22 + FieldMetric{3.4}, // g33 + FieldMetric{4.5}, // g12 + FieldMetric{5.6}, // g13 + FieldMetric{6.7}, // g23 + FieldMetric{1.0}, // g_11 + FieldMetric{1.0}, // g_22 + FieldMetric{1.0}, // g_23 + FieldMetric{0.0}, // g_12 + FieldMetric{0.0}, // g_13 + FieldMetric{0.0}, // g_23 + FieldMetric{0.0}, // ShiftTorsion + FieldMetric{0.0}}; // IntShiftTorsion + + EXPECT_TRUE(IsFieldEqual(coords.g11(), 1.2)); + EXPECT_TRUE(IsFieldEqual(coords.g22(), 2.3)); + EXPECT_TRUE(IsFieldEqual(coords.g33(), 3.4)); + EXPECT_TRUE(IsFieldEqual(coords.g12(), 4.5)); + EXPECT_TRUE(IsFieldEqual(coords.g13(), 5.6)); + EXPECT_TRUE(IsFieldEqual(coords.g23(), 6.7)); +} + +TEST_F(CoordinatesTest, SetContravariantMetricTensor) { + // Set initial values for the metric tensor in the Coordinates constructor + Coordinates coords{mesh, + FieldMetric{1.0}, // dx + FieldMetric{1.0}, // dy + FieldMetric{1.0}, // dz + FieldMetric{0.0}, // J + FieldMetric{0.0}, // Bxy + FieldMetric{0.0}, // g11 + FieldMetric{0.0}, // g22 + FieldMetric{0.0}, // g33 + FieldMetric{0.0}, // g12 + FieldMetric{0.0}, // g13 + FieldMetric{0.0}, // g23 + FieldMetric{1.0}, // g_11 + FieldMetric{1.0}, // g_22 + FieldMetric{1.0}, // g_23 + FieldMetric{0.0}, // g_12 + FieldMetric{0.0}, // g_13 + FieldMetric{0.0}, // g_23 + FieldMetric{0.0}, // ShiftTorsion + FieldMetric{0.0}}; // IntShiftTorsion + + // Modify with setter + auto updated_metric_tensor = ContravariantMetricTensor(1.0, 2.0, 0.4, 1.0, 0.0, 0.2); + coords.setContravariantMetricTensor(updated_metric_tensor); + + // Get values with getter and check they have been modified as expected + EXPECT_TRUE(IsFieldEqual(coords.g11(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.g22(), 2.0)); + EXPECT_TRUE(IsFieldEqual(coords.g33(), 0.4)); + EXPECT_TRUE(IsFieldEqual(coords.g12(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.g13(), 0.0)); + EXPECT_TRUE(IsFieldEqual(coords.g23(), 0.2)); +} + +TEST_F(CoordinatesTest, CheckCovariantCalculatedFromContravariant) { + + // Set initial values for the metric tensor in the Coordinates constructor + Coordinates coords{mesh, + FieldMetric{1.0}, // dx + FieldMetric{1.0}, // dy + FieldMetric{1.0}, // dz + FieldMetric{0.0}, // J + FieldMetric{0.0}, // Bxy + FieldMetric{0.0}, // g11 + FieldMetric{0.0}, // g22 + FieldMetric{0.0}, // g33 + FieldMetric{0.0}, // g12 + FieldMetric{0.0}, // g13 + FieldMetric{0.0}, // g23 + FieldMetric{1.0}, // g_11 + FieldMetric{1.0}, // g_22 + FieldMetric{1.0}, // g_23 + FieldMetric{0.0}, // g_12 + FieldMetric{0.0}, // g_13 + FieldMetric{0.0}, // g_23 + FieldMetric{0.0}, // ShiftTorsion + FieldMetric{0.0}}; // IntShiftTorsion + + // Modify contravariant components + constexpr double g11 = 1.0; + constexpr double g22 = 1.0; + constexpr double g33 = 1.0; + const double g12 = sqrt(3.0) / 4.0; // (std::sqrt only constexpr since C++26) + constexpr double g13 = 0.5; + const double g23 = sqrt(3.0) / 4.0; // (std::sqrt only constexpr since C++26) + auto updated_metric_tensor = ContravariantMetricTensor(g11, g22, g33, g12, g13, g23); + coords.setContravariantMetricTensor(updated_metric_tensor); + + // Check that the covariant components have been calculated corrected + constexpr double expected_g_11 = 13.0 / 9.0; + constexpr double expected_g_22 = 4.0 / 3.0; + constexpr double expected_g_33 = 13.0 / 9.0; + const double expected_g_12 = -2.0 * sqrt(3.0) / 9.0; + constexpr double expected_g_13 = -5.0 / 9.0; + const double expected_g_23 = -2.0 * sqrt(3.0) / 9.0; + + EXPECT_TRUE(IsFieldEqual(coords.g_11(), expected_g_11)); + EXPECT_TRUE(IsFieldEqual(coords.g_22(), expected_g_22)); + EXPECT_TRUE(IsFieldEqual(coords.g_33(), expected_g_33)); + EXPECT_TRUE(IsFieldEqual(coords.g_12(), expected_g_12)); + EXPECT_TRUE(IsFieldEqual(coords.g_13(), expected_g_13)); + EXPECT_TRUE(IsFieldEqual(coords.g_23(), expected_g_23)); +} + +TEST_F(CoordinatesTest, CheckContravariantCalculatedFromCovariant) { + + // Set initial values for the metric tensor in the Coordinates constructor + Coordinates coords{mesh, + FieldMetric{1.0}, // dx + FieldMetric{1.0}, // dy + FieldMetric{1.0}, // dz + FieldMetric{0.0}, // J + FieldMetric{0.0}, // Bxy + FieldMetric{0.0}, // g11 + FieldMetric{0.0}, // g22 + FieldMetric{0.0}, // g33 + FieldMetric{0.0}, // g12 + FieldMetric{0.0}, // g13 + FieldMetric{0.0}, // g23 + FieldMetric{1.0}, // g_11 + FieldMetric{1.0}, // g_22 + FieldMetric{1.0}, // g_23 + FieldMetric{0.0}, // g_12 + FieldMetric{0.0}, // g_13 + FieldMetric{0.0}, // g_23 + FieldMetric{0.0}, // ShiftTorsion + FieldMetric{0.0}}; // IntShiftTorsion + + // Modify covariant components + constexpr double g_11 = 1.0; + constexpr double g_22 = 1.0; + constexpr double g_33 = 1.0; + const double g_12 = sqrt(3.0) / 4.0; // (std::sqrt only constexpr since C++26) + constexpr double g_13 = 0.5; + const double g_23 = sqrt(3.0) / 4.0; // (std::sqrt only constexpr since C++26) + auto updated_metric_tensor = CovariantMetricTensor(g_11, g_22, g_33, g_12, g_13, g_23); + coords.setCovariantMetricTensor(updated_metric_tensor); + + // Check that the contravariant components have been calculated corrected + constexpr double expected_g11 = 13.0 / 9.0; + constexpr double expected_g22 = 4.0 / 3.0; + constexpr double expected_g33 = 13.0 / 9.0; + const double expected_g12 = -2.0 * sqrt(3.0) / 9.0; + constexpr double expected_g13 = -5.0 / 9.0; + const double expected_g23 = -2.0 * sqrt(3.0) / 9.0; + + EXPECT_TRUE(IsFieldEqual(coords.g11(), expected_g11)); + EXPECT_TRUE(IsFieldEqual(coords.g22(), expected_g22)); + EXPECT_TRUE(IsFieldEqual(coords.g33(), expected_g33)); + EXPECT_TRUE(IsFieldEqual(coords.g12(), expected_g12)); + EXPECT_TRUE(IsFieldEqual(coords.g13(), expected_g13)); + EXPECT_TRUE(IsFieldEqual(coords.g23(), expected_g23)); +} + +TEST_F(CoordinatesTest, GetCovariantMetricTensor) { + Coordinates coords{mesh, + FieldMetric{1.0}, // dx + FieldMetric{1.0}, // dy + FieldMetric{1.0}, // dz + FieldMetric{0.0}, // J + FieldMetric{0.0}, // Bxy + FieldMetric{1.2}, // g11 + FieldMetric{2.3}, // g22 + FieldMetric{3.4}, // g33 + FieldMetric{4.5}, // g12 + FieldMetric{5.6}, // g13 + FieldMetric{6.7}, // g23 + FieldMetric{9.7}, // g_11 + FieldMetric{7.5}, // g_22 + FieldMetric{4.7}, // g_23 + FieldMetric{3.9}, // g_12 + FieldMetric{1.7}, // g_13 + FieldMetric{5.3}, // g_23 + FieldMetric{0.0}, // ShiftTorsion + FieldMetric{0.0}}; // IntShiftTorsion + + EXPECT_TRUE(IsFieldEqual(coords.g_11(), 9.7)); + EXPECT_TRUE(IsFieldEqual(coords.g_22(), 7.5)); + EXPECT_TRUE(IsFieldEqual(coords.g_33(), 4.7)); + EXPECT_TRUE(IsFieldEqual(coords.g_12(), 3.9)); + EXPECT_TRUE(IsFieldEqual(coords.g_13(), 1.7)); + EXPECT_TRUE(IsFieldEqual(coords.g_23(), 5.3)); +} + +TEST_F(CoordinatesTest, SetCovariantMetricTensor) { + { + // Set initial values for the metric tensor in the Coordinates constructor + Coordinates coords{mesh, + FieldMetric{1.0}, // dx + FieldMetric{1.0}, // dy + FieldMetric{1.0}, // dz + FieldMetric{0.0}, // J + FieldMetric{0.0}, // Bxy + FieldMetric{0.0}, // g11 + FieldMetric{0.0}, // g22 + FieldMetric{0.0}, // g33 + FieldMetric{0.0}, // g12 + FieldMetric{0.0}, // g13 + FieldMetric{0.0}, // g23 + FieldMetric{1.0}, // g_11 + FieldMetric{1.0}, // g_22 + FieldMetric{1.0}, // g_23 + FieldMetric{0.0}, // g_12 + FieldMetric{0.0}, // g_13 + FieldMetric{0.0}, // g_23 + FieldMetric{0.0}, // ShiftTorsion + FieldMetric{0.0}}; // IntShiftTorsion + + // Modify with setter + auto updated_metric_tensor = CovariantMetricTensor(1.0, 2.0, 0.4, 1.0, 0.0, 0.2); + coords.setCovariantMetricTensor(updated_metric_tensor); + + // Get values with getter and check they have been modified as expected + EXPECT_TRUE(IsFieldEqual(coords.g_11(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.g_22(), 2.0)); + EXPECT_TRUE(IsFieldEqual(coords.g_33(), 0.4)); + EXPECT_TRUE(IsFieldEqual(coords.g_12(), 1.0)); + EXPECT_TRUE(IsFieldEqual(coords.g_13(), 0.0)); + EXPECT_TRUE(IsFieldEqual(coords.g_23(), 0.2)); + } +} + +TEST_F(CoordinatesTest, IndexedAccessors) { + + int x = mesh->xstart; + int y = mesh->ystart; +#if BOUT_USE_METRIC_3D + int z = mesh->LocalNz; +#endif + + output_info.disable(); + output_warn.disable(); + Coordinates coords(mesh); + output_warn.enable(); + output_info.enable(); + + const auto& dx = coords.dx(); + const auto& dy = coords.dy(); +#if BOUT_USE_METRIC_3D + const auto& dz = coords.dz(); +#endif + +#if not(BOUT_USE_METRIC_3D) + const BoutReal expected_dx = dx(x, y); + const BoutReal expected_dy = dy(x, y); +#else + const BoutReal expected_dx = dx(x, y, z); + const BoutReal expected_dy = dy(x, y, z); + const BoutReal expected_dz = dz(x, y, z); +#endif + +#if not(BOUT_USE_METRIC_3D) + const FieldMetric& actual_dx = coords.dx(x, y); + const FieldMetric& actual_dy = coords.dy(x, y); +#else + const Field3D& actual_dx = coords.dx(x, y, z); + const Field3D& actual_dy = coords.dy(x, y, z); + const Field3D& actual_dz = coords.dz(x, y, z); +#endif + + EXPECT_EQ(actual_dx, expected_dx); + EXPECT_EQ(actual_dy, expected_dy); +#if BOUT_USE_METRIC_3D + EXPECT_EQ(actual_dz, expected_dz); +#endif +} diff --git a/tests/unit/mesh/test_coordinates_accessor.cxx b/tests/unit/mesh/test_coordinates_accessor.cxx index 16985981bb..47fde99d17 100644 --- a/tests/unit/mesh/test_coordinates_accessor.cxx +++ b/tests/unit/mesh/test_coordinates_accessor.cxx @@ -1,6 +1,8 @@ #include "gtest/gtest.h" +#include "bout/build_config.hxx" #include "bout/build_defines.hxx" +#include "bout/coordinates.hxx" #include "bout/coordinates_accessor.hxx" #include "fake_mesh_fixture.hxx" @@ -52,41 +54,48 @@ TEST_F(CoordinatesAccessorTest, ClearOneReused) { EXPECT_EQ(CoordinatesAccessor::clear(), 1); } +namespace { +struct TestCoordinates : public Coordinates { + using Coordinates::Coordinates; + using Coordinates::splitBxyParallelSlices; +}; +} // namespace + TEST_F(CoordinatesAccessorTest, ClearBoth) { CoordinatesAccessor::clear(); // Should clear any left over // Make a new Coordinates object to access - Coordinates coords{mesh, - FieldMetric{1.0}, // dx - FieldMetric{1.0}, // dy - FieldMetric{1.0}, // dz - FieldMetric{1.0}, // J - FieldMetric{1.0}, // Bxy - FieldMetric{1.0}, // g11 - FieldMetric{1.0}, // g22 - FieldMetric{1.0}, // g33 - FieldMetric{0.0}, // g12 - FieldMetric{0.0}, // g13 - FieldMetric{0.0}, // g23 - FieldMetric{1.0}, // g_11 - FieldMetric{1.0}, // g_22 - FieldMetric{1.0}, // g_23 - FieldMetric{0.0}, // g_12 - FieldMetric{0.0}, // g_13 - FieldMetric{0.0}, // g_23 - FieldMetric{0.0}, // ShiftTorsion - FieldMetric{0.0}}; // IntShiftTorsion + TestCoordinates coords{mesh, + FieldMetric{1.0}, // dx + FieldMetric{1.0}, // dy + FieldMetric{1.0}, // dz + FieldMetric{1.0}, // J + FieldMetric{1.0}, // Bxy + FieldMetric{1.0}, // g11 + FieldMetric{1.0}, // g22 + FieldMetric{1.0}, // g33 + FieldMetric{0.0}, // g12 + FieldMetric{0.0}, // g13 + FieldMetric{0.0}, // g23 + FieldMetric{1.0}, // g_11 + FieldMetric{1.0}, // g_22 + FieldMetric{1.0}, // g_23 + FieldMetric{0.0}, // g_12 + FieldMetric{0.0}, // g_13 + FieldMetric{0.0}, // g_23 + FieldMetric{0.0}, // ShiftTorsion + FieldMetric{0.0}}; // IntShiftTorsion // Need to set geometry information - coords.G1 = coords.G2 = coords.G3 = 0.2; - coords.non_uniform = true; - coords.d1_dx = coords.d1_dy = coords.d1_dz = 0.1; -#if BOUT_USE_METRIC_3D - coords.Bxy.splitParallelSlices(); - coords.Bxy.yup() = coords.Bxy.ydown() = coords.Bxy; -#endif + coords.setNon_uniform(true); + coords.setD1_dx(0.1); + coords.setD1_dy(0.1); + coords.setD1_dz(0.1); + if (bout::build::use_metric_3d) { + coords.splitBxyParallelSlices(); + } - CoordinatesAccessor acc(mesh->getCoordinates()); - CoordinatesAccessor acc2(&coords); // Different from previous Coordinates + const CoordinatesAccessor acc(mesh->getCoordinates()); + const CoordinatesAccessor acc2(&coords); // Different from previous Coordinates // Clear both EXPECT_EQ(CoordinatesAccessor::clear(), 2); @@ -96,37 +105,37 @@ TEST_F(CoordinatesAccessorTest, ClearOneTwo) { CoordinatesAccessor::clear(); // Should clear any left over // Make a new Coordinates object to access - Coordinates coords{mesh, - FieldMetric{1.0}, // dx - FieldMetric{1.0}, // dy - FieldMetric{1.0}, // dz - FieldMetric{1.0}, // J - FieldMetric{1.0}, // Bxy - FieldMetric{1.0}, // g11 - FieldMetric{1.0}, // g22 - FieldMetric{1.0}, // g33 - FieldMetric{0.0}, // g12 - FieldMetric{0.0}, // g13 - FieldMetric{0.0}, // g23 - FieldMetric{1.0}, // g_11 - FieldMetric{1.0}, // g_22 - FieldMetric{1.0}, // g_23 - FieldMetric{0.0}, // g_12 - FieldMetric{0.0}, // g_13 - FieldMetric{0.0}, // g_23 - FieldMetric{0.0}, // ShiftTorsion - FieldMetric{0.0}}; // IntShiftTorsion + TestCoordinates coords{mesh, + FieldMetric{1.0}, // dx + FieldMetric{1.0}, // dy + FieldMetric{1.0}, // dz + FieldMetric{1.0}, // J + FieldMetric{1.0}, // Bxy + FieldMetric{1.0}, // g11 + FieldMetric{1.0}, // g22 + FieldMetric{1.0}, // g33 + FieldMetric{0.0}, // g12 + FieldMetric{0.0}, // g13 + FieldMetric{0.0}, // g23 + FieldMetric{1.0}, // g_11 + FieldMetric{1.0}, // g_22 + FieldMetric{1.0}, // g_23 + FieldMetric{0.0}, // g_12 + FieldMetric{0.0}, // g_13 + FieldMetric{0.0}, // g_23 + FieldMetric{0.0}, // ShiftTorsion + FieldMetric{0.0}}; // IntShiftTorsion // Need to set geometry information - coords.G1 = coords.G2 = coords.G3 = 0.2; - coords.non_uniform = true; - coords.d1_dx = coords.d1_dy = coords.d1_dz = 0.1; -#if BOUT_USE_METRIC_3D - coords.Bxy.splitParallelSlices(); - coords.Bxy.yup() = coords.Bxy.ydown() = coords.Bxy; -#endif + coords.setNon_uniform(true); + coords.setD1_dx(0.1); + coords.setD1_dy(0.1); + coords.setD1_dz(0.1); + if (bout::build::use_metric_3d) { + coords.splitBxyParallelSlices(); + } - CoordinatesAccessor acc(mesh->getCoordinates()); - CoordinatesAccessor acc2(&coords); // Different from previous Coordinates + const CoordinatesAccessor acc(mesh->getCoordinates()); + const CoordinatesAccessor acc2(&coords); // Different from previous Coordinates // clear first one EXPECT_EQ(CoordinatesAccessor::clear(mesh->getCoordinates()), 1); @@ -138,37 +147,37 @@ TEST_F(CoordinatesAccessorTest, ClearTwoOneNone) { CoordinatesAccessor::clear(); // Should clear any left over // Make a new Coordinates object to access - Coordinates coords{mesh, - FieldMetric{1.0}, // dx - FieldMetric{1.0}, // dy - FieldMetric{1.0}, // dz - FieldMetric{1.0}, // J - FieldMetric{1.0}, // Bxy - FieldMetric{1.0}, // g11 - FieldMetric{1.0}, // g22 - FieldMetric{1.0}, // g33 - FieldMetric{0.0}, // g12 - FieldMetric{0.0}, // g13 - FieldMetric{0.0}, // g23 - FieldMetric{1.0}, // g_11 - FieldMetric{1.0}, // g_22 - FieldMetric{1.0}, // g_23 - FieldMetric{0.0}, // g_12 - FieldMetric{0.0}, // g_13 - FieldMetric{0.0}, // g_23 - FieldMetric{0.0}, // ShiftTorsion - FieldMetric{0.0}}; // IntShiftTorsion + TestCoordinates coords{mesh, + FieldMetric{1.0}, // dx + FieldMetric{1.0}, // dy + FieldMetric{1.0}, // dz + FieldMetric{1.0}, // J + FieldMetric{1.0}, // Bxy + FieldMetric{1.0}, // g11 + FieldMetric{1.0}, // g22 + FieldMetric{1.0}, // g33 + FieldMetric{0.0}, // g12 + FieldMetric{0.0}, // g13 + FieldMetric{0.0}, // g23 + FieldMetric{1.0}, // g_11 + FieldMetric{1.0}, // g_22 + FieldMetric{1.0}, // g_23 + FieldMetric{0.0}, // g_12 + FieldMetric{0.0}, // g_13 + FieldMetric{0.0}, // g_23 + FieldMetric{0.0}, // ShiftTorsion + FieldMetric{0.0}}; // IntShiftTorsion // Need to set geometry information - coords.G1 = coords.G2 = coords.G3 = 0.2; - coords.non_uniform = true; - coords.d1_dx = coords.d1_dy = coords.d1_dz = 0.1; -#if BOUT_USE_METRIC_3D - coords.Bxy.splitParallelSlices(); - coords.Bxy.yup() = coords.Bxy.ydown() = coords.Bxy; -#endif - - CoordinatesAccessor acc(mesh->getCoordinates()); - CoordinatesAccessor acc2(&coords); // Different from previous Coordinates + coords.setNon_uniform(true); + coords.setD1_dx(0.1); + coords.setD1_dy(0.1); + coords.setD1_dz(0.1); + if (bout::build::use_metric_3d) { + coords.splitBxyParallelSlices(); + } + + const CoordinatesAccessor acc(mesh->getCoordinates()); + const CoordinatesAccessor acc2(&coords); // Different from previous Coordinates // clear second one EXPECT_EQ(CoordinatesAccessor::clear(&coords), 1); diff --git a/tests/unit/mesh/test_interpolation.cxx b/tests/unit/mesh/test_interpolation.cxx index d14d54964e..55a385133e 100644 --- a/tests/unit/mesh/test_interpolation.cxx +++ b/tests/unit/mesh/test_interpolation.cxx @@ -70,7 +70,6 @@ class Field3DInterpToTest : public ::testing::Test { Field2D{0.0, mesh}, Field2D{0.0, mesh}, Field2D{0.0, mesh}, Field2D{0.0, mesh}), location); - // No call to Coordinates::geometry() needed here mesh->getCoordinates(location)->setParallelTransform( bout::utils::make_unique(*mesh)); } diff --git a/tools/pylib/_boutpp_build/boutcpp.pxd.jinja b/tools/pylib/_boutpp_build/boutcpp.pxd.jinja index 9de826384b..acb749f9df 100644 --- a/tools/pylib/_boutpp_build/boutcpp.pxd.jinja +++ b/tools/pylib/_boutpp_build/boutcpp.pxd.jinja @@ -68,21 +68,46 @@ cdef extern from "bout/mesh.hxx": cdef extern from "bout/coordinates.hxx": cppclass Coordinates: Coordinates() - {{ metric_field }} dx, dy, dz - {{ metric_field }} J - {{ metric_field }} Bxy - {{ metric_field }} g11, g22, g33, g12, g13, g23 - {{ metric_field }} g_11, g_22, g_33, g_12, g_13, g_23 - {{ metric_field }} G1_11, G1_22, G1_33, G1_12, G1_13, G1_23 - {{ metric_field }} G2_11, G2_22, G2_33, G2_12, G2_13, G2_23 - {{ metric_field }} G3_11, G3_22, G3_33, G3_12, G3_13, G3_23 - {{ metric_field }} G1, G2, G3 - {{ metric_field }} ShiftTorsion - {{ metric_field }} IntShiftTorsion - int geometry() except +raise_bout_py_error - int calcCovariant() except +raise_bout_py_error - int calcContravariant() except +raise_bout_py_error - int jacobian() except +raise_bout_py_error + {{ metric_field }} dx() + {{ metric_field }} dy() + {{ metric_field }} dz() + {{ metric_field }} J() + {{ metric_field }} Bxy() + {{ metric_field }} g11() + {{ metric_field }} g22() + {{ metric_field }} g33() + {{ metric_field }} g12() + {{ metric_field }} g13() + {{ metric_field }} g23() + {{ metric_field }} g_11() + {{ metric_field }} g_22() + {{ metric_field }} g_33() + {{ metric_field }} g_12() + {{ metric_field }} g_13() + {{ metric_field }} g_23() + {{ metric_field }} G1_11() + {{ metric_field }} G1_22() + {{ metric_field }} G1_33() + {{ metric_field }} G1_12() + {{ metric_field }} G1_13() + {{ metric_field }} G1_23() + {{ metric_field }} G2_11() + {{ metric_field }} G2_22() + {{ metric_field }} G2_33() + {{ metric_field }} G2_12() + {{ metric_field }} G2_13() + {{ metric_field }} G2_23() + {{ metric_field }} G3_11() + {{ metric_field }} G3_22() + {{ metric_field }} G3_33() + {{ metric_field }} G3_12() + {{ metric_field }} G3_13() + {{ metric_field }} G3_23() + {{ metric_field }} G1() + {{ metric_field }} G2() + {{ metric_field }} G3() + {{ metric_field }} ShiftTorsion() + {{ metric_field }} IntShiftTorsion() cdef extern from "bout/fieldgroup.hxx": cppclass FieldGroup: diff --git a/tools/pylib/_boutpp_build/boutpp.pyx.jinja b/tools/pylib/_boutpp_build/boutpp.pyx.jinja index 587aa7d6a5..f72f6ee95a 100644 --- a/tools/pylib/_boutpp_build/boutpp.pyx.jinja +++ b/tools/pylib/_boutpp_build/boutpp.pyx.jinja @@ -876,7 +876,7 @@ cdef public {{ metric_field }} IntShiftTorsion def _setmembers(self): {% for f in "dx", "dy", "dz", "J", "Bxy", "g11", "g22", "g33", "g12", "g13", "g23", "g_11", "g_22", "g_33", "g_12", "g_13", "g_23", "G1_11", "G1_22", "G1_33", "G1_12", "G1_13", "G1_23", "G2_11", "G2_22", "G2_33", "G2_12", "G2_13", "G2_23", "G3_11", "G3_22", "G3_33", "G3_12", "G3_13", "G3_23", "G1", "G2", "G3", "ShiftTorsion", "IntShiftTorsion" %} - self.{{f}} = {{ metric_field.fdd }}FromPtr(&self.cobj.{{f}}) + self.{{f}} = {{ metric_field.fdd }}FromObj(self.cobj.{{f}}()) {% endfor %} {{ class("Laplacian", comment=""" diff --git a/tools/pylib/_boutpp_build/helper.cxx.jinja b/tools/pylib/_boutpp_build/helper.cxx.jinja index fdaa944f3e..a611bc813d 100644 --- a/tools/pylib/_boutpp_build/helper.cxx.jinja +++ b/tools/pylib/_boutpp_build/helper.cxx.jinja @@ -170,8 +170,7 @@ Mesh * c_get_global_mesh(){ void c_mesh_normalise(Mesh * msh, double norm){ //printf("%g\n",norm); auto coord = msh->getCoordinates(); - coord->dx /= norm; - coord->dy /= norm; - coord->dz /= norm; - coord->geometry(); + coord->setDx(coord->dx() / norm); + coord->setDy(coord->dy() / norm); + coord->setDz(coord->dz() / norm); } From c761c016f4ec686618da5fb9de24e1302d1427a3 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Wed, 8 Jul 2026 17:58:10 +0100 Subject: [PATCH 008/221] Move derivative operators out of `Coordinates` --- include/bout/coordinates.hxx | 81 ------- include/bout/derivs.hxx | 129 +++++----- include/bout/difops.hxx | 76 +++--- src/mesh/christoffel_symbols.cxx | 120 ++++------ src/mesh/coordinates.cxx | 397 ------------------------------- src/mesh/difops.cxx | 367 +++++++++++++++++++++------- src/mesh/g_values.cxx | 10 +- src/sys/derivs.cxx | 113 +++++---- 8 files changed, 492 insertions(+), 801 deletions(-) diff --git a/include/bout/coordinates.hxx b/include/bout/coordinates.hxx index 2c73828060..d2019ed31f 100644 --- a/include/bout/coordinates.hxx +++ b/include/bout/coordinates.hxx @@ -411,87 +411,6 @@ public: return *transform; } - /////////////////////////////////////////////////////////// - // Operators - /////////////////////////////////////////////////////////// - - FieldMetric DDX(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY") const; - - FieldMetric DDY(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY") const; - - FieldMetric DDZ(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY") const; - - Field3D DDX(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY") const; - - Field3D DDY(const Field3DParallel& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY") const; - - Field3D DDZ(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY") const; - - /// Gradient along magnetic field b.Grad(f) - FieldMetric Grad_par(const Field2D& var, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT"); - - Field3D Grad_par(const Field3DParallel& var, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT"); - - /// Advection along magnetic field V*b.Grad(f) - FieldMetric Vpar_Grad_par(const Field2D& v, const Field2D& f, - CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT"); - - Field3D Vpar_Grad_par(const Field3D& v, const Field3DParallel& f, - CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT"); - - /// Divergence along magnetic field Div(b*f) = B.Grad(f/B) - FieldMetric Div_par(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT"); - - Field3D Div_par(const Field3DParallel& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT"); - - // Second derivative along magnetic field - FieldMetric Grad2_par2(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT"); - - Field3D Grad2_par2(const Field3DParallel& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT"); - // Perpendicular Laplacian operator, using only X-Z derivatives - // NOTE: This might be better bundled with the Laplacian inversion code - // since it makes use of the same coefficients and FFT routines - FieldMetric Delp2(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, bool useFFT = true); - Field3D Delp2(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, bool useFFT = true); - FieldPerp Delp2(const FieldPerp& f, CELL_LOC outloc = CELL_DEFAULT, bool useFFT = true); - - // Full parallel Laplacian operator on scalar field - // Laplace_par(f) = Div( b (b dot Grad(f)) ) - FieldMetric Laplace_par(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT); - Field3D Laplace_par(const Field3DParallel& f, CELL_LOC outloc = CELL_DEFAULT); - - // Full Laplacian operator on scalar field - FieldMetric Laplace(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& dfdy_boundary_conditions = "free_o3", - const std::string& dfdy_dy_region = ""); - Field3D Laplace(const Field3DParallel& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& dfdy_boundary_conditions = "free_o3", - const std::string& dfdy_dy_region = ""); - - // Full perpendicular Laplacian, in form of inverse of Laplacian operator in LaplaceXY - // solver - Field2D Laplace_perpXY(const Field2D& A, const Field2D& f) const; - /// Christoffel symbol of the second kind (connection coefficients) const FieldMetric& G1_11() const { return christoffel_symbols().G1_11(); } const FieldMetric& G1_22() const { return christoffel_symbols().G1_22(); } diff --git a/include/bout/derivs.hxx b/include/bout/derivs.hxx index 14bc5c2824..be9f2f4111 100644 --- a/include/bout/derivs.hxx +++ b/include/bout/derivs.hxx @@ -29,12 +29,14 @@ #ifndef BOUT_DERIVS_H #define BOUT_DERIVS_H +#include "bout/bout_types.hxx" #include "bout/field2d.hxx" #include "bout/field3d.hxx" +#include "bout/metric_tensor.hxx" #include "bout/vector2d.hxx" #include "bout/vector3d.hxx" -#include "bout/bout_types.hxx" +#include #include ////////// FIRST DERIVATIVES ////////// @@ -67,9 +69,9 @@ Field3D DDX(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric DDX(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric DDX(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// Calculate first partial derivative in Y /// @@ -106,9 +108,9 @@ DDY(const E& expr, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = " /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric DDY(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric DDY(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// Calculate first partial derivative in Z /// @@ -138,9 +140,9 @@ Field3D DDZ(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric DDZ(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric DDZ(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// Calculate first partial derivative in Z /// @@ -204,9 +206,9 @@ Field3D D2DX2(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric D2DX2(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric D2DX2(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// Calculate second partial derivative in Y /// @@ -236,9 +238,9 @@ Field3D D2DY2(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric D2DY2(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric D2DY2(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// Calculate second partial derivative in Z /// @@ -268,9 +270,9 @@ Field3D D2DZ2(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric D2DZ2(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric D2DZ2(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); ////////// FOURTH DERIVATIVES ////////// @@ -302,9 +304,9 @@ Field3D D4DX4(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric D4DX4(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric D4DX4(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// Calculate forth partial derivative in Y /// @@ -334,9 +336,9 @@ Field3D D4DY4(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric D4DY4(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric D4DY4(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// Calculate forth partial derivative in Z /// @@ -366,9 +368,9 @@ Field3D D4DZ4(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric D4DZ4(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric D4DZ4(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// For terms of form v * grad(f) /// @@ -400,10 +402,9 @@ Field3D VDDX(const Field3D& v, const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric VDDX(const Field2D& v, const Field2D& f, - CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric VDDX(const Field2D& v, const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// For terms of form v * grad(f) /// @@ -435,10 +436,9 @@ Field3D VDDY(const Field3D& v, const Field3DParallel& f, CELL_LOC outloc = CELL_ /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric VDDY(const Field2D& v, const Field2D& f, - CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric VDDY(const Field2D& v, const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// For terms of form v * grad(f) /// @@ -470,10 +470,9 @@ Field3D VDDZ(const Field3D& v, const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric VDDZ(const Field2D& v, const Field2D& f, - CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric VDDZ(const Field2D& v, const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// For terms of form v * grad(f) /// @@ -488,10 +487,9 @@ Coordinates::FieldMetric VDDZ(const Field2D& v, const Field2D& f, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric VDDZ(const Field3D& v, const Field2D& f, - CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric VDDZ(const Field3D& v, const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// for terms of form div(v * f) /// @@ -523,10 +521,9 @@ Field3D FDDX(const Field3D& v, const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric FDDX(const Field2D& v, const Field2D& f, - CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric FDDX(const Field2D& v, const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// for terms of form div(v * f) /// @@ -558,10 +555,9 @@ Field3D FDDY(const Field3D& v, const Field3DParallel& f, CELL_LOC outloc = CELL_ /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric FDDY(const Field2D& v, const Field2D& f, - CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric FDDY(const Field2D& v, const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// for terms of form div(v * f) /// @@ -593,10 +589,9 @@ Field3D FDDZ(const Field3D& v, const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric FDDZ(const Field2D& v, const Field2D& f, - CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric FDDZ(const Field2D& v, const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// Calculate mixed partial derivative in x and y /// @@ -639,11 +634,11 @@ Field3D D2DXDY(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// (default) then the same as the region for the calculation as a /// whole. If dfdy_region < region in size then this will cause /// errors. -Coordinates::FieldMetric D2DXDY(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY", - const std::string& dfdy_boundary_condition = "free_o3", - const std::string& dfdy_region = ""); +bout::FieldMetric D2DXDY(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY", + const std::string& dfdy_boundary_condition = "free_o3", + const std::string& dfdy_region = ""); /// Calculate mixed partial derivative in x and z /// @@ -673,9 +668,9 @@ Field3D D2DXDZ(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric D2DXDZ(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric D2DXDZ(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// Calculate mixed partial derivative in y and z /// @@ -705,8 +700,8 @@ Field3D D2DYDZ(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric D2DYDZ(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric D2DYDZ(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); #endif // BOUT_DERIVS_H diff --git a/include/bout/difops.hxx b/include/bout/difops.hxx index 28a4da9f51..fcde8a9b7c 100644 --- a/include/bout/difops.hxx +++ b/include/bout/difops.hxx @@ -36,11 +36,12 @@ #ifndef BOUT_DIFOPS_H #define BOUT_DIFOPS_H +#include "bout/bout_types.hxx" #include "bout/field2d.hxx" #include "bout/field3d.hxx" +#include "bout/metric_tensor.hxx" -#include "bout/bout_types.hxx" -#include "bout/coordinates.hxx" +#include class Solver; @@ -53,10 +54,10 @@ class Solver; * enabled) * @param[in] method The method to use. The default is set in the options. */ -Coordinates::FieldMetric Grad_par(const Field2D& var, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT"); -inline Coordinates::FieldMetric Grad_par(const Field2D& var, CELL_LOC outloc, - DIFF_METHOD method) { +bout::FieldMetric Grad_par(const Field2D& var, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT"); +inline bout::FieldMetric Grad_par(const Field2D& var, CELL_LOC outloc, + DIFF_METHOD method) { return Grad_par(var, outloc, toString(method)); } @@ -89,12 +90,12 @@ Field3D Grad_parP(const Field3D& apar, const Field3D& f); * @param[in] method The numerical method to use. The default is set in the options * */ -Coordinates::FieldMetric Vpar_Grad_par(const Field2D& v, const Field2D& f, - CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT"); +bout::FieldMetric Vpar_Grad_par(const Field2D& v, const Field2D& f, + CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT"); -inline Coordinates::FieldMetric Vpar_Grad_par(const Field2D& v, const Field2D& f, - CELL_LOC outloc, DIFF_METHOD method) { +inline bout::FieldMetric Vpar_Grad_par(const Field2D& v, const Field2D& f, + CELL_LOC outloc, DIFF_METHOD method) { return Vpar_Grad_par(v, f, outloc, toString(method)); } @@ -118,11 +119,10 @@ inline Field3D Vpar_Grad_par(const Field3D& v, const Field3D& f, CELL_LOC outloc * @param[in] method The numerical method to use * */ -Coordinates::FieldMetric Div_par(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT"); +bout::FieldMetric Div_par(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT"); -inline Coordinates::FieldMetric Div_par(const Field2D& f, CELL_LOC outloc, - DIFF_METHOD method) { +inline bout::FieldMetric Div_par(const Field2D& f, CELL_LOC outloc, DIFF_METHOD method) { return Div_par(f, outloc, toString(method)); } @@ -160,10 +160,10 @@ inline Field3D Div_par_flux(const Field3D& v, const Field3D& f, CELL_LOC outloc, * @param[in] f The field to be differentiated * @param[in] outloc The cell location of the result */ -Coordinates::FieldMetric Grad2_par2(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT"); -inline Coordinates::FieldMetric Grad2_par2(const Field2D& f, CELL_LOC outloc, - DIFF_METHOD method) { +bout::FieldMetric Grad2_par2(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT"); +inline bout::FieldMetric Grad2_par2(const Field2D& f, CELL_LOC outloc, + DIFF_METHOD method) { return Grad2_par2(f, outloc, toString(method)); } @@ -183,11 +183,11 @@ inline Field3D Grad2_par2(const Field3D& f, CELL_LOC outloc, DIFF_METHOD method) * @param[in] kY The diffusion coefficient * @param[in] f The field whose gradient drives a flux */ -Coordinates::FieldMetric Div_par_K_Grad_par(BoutReal kY, const Field2D& f, - CELL_LOC outloc = CELL_DEFAULT); +bout::FieldMetric Div_par_K_Grad_par(BoutReal kY, const Field2D& f, + CELL_LOC outloc = CELL_DEFAULT); Field3D Div_par_K_Grad_par(BoutReal kY, const Field3D& f, CELL_LOC outloc = CELL_DEFAULT); -Coordinates::FieldMetric Div_par_K_Grad_par(const Field2D& kY, const Field2D& f, - CELL_LOC outloc = CELL_DEFAULT); +bout::FieldMetric Div_par_K_Grad_par(const Field2D& kY, const Field2D& f, + CELL_LOC outloc = CELL_DEFAULT); Field3D Div_par_K_Grad_par(const Field2D& kY, const Field3D& f, CELL_LOC outloc = CELL_DEFAULT); Field3D Div_par_K_Grad_par(const Field3D& kY, const Field2D& f, @@ -209,8 +209,8 @@ Field3D Div_par_K_Grad_par_mod(const Field3D& k, const Field3D& f, Field3D& flow * * For the full perpendicular Laplacian, use Laplace_perp */ -Coordinates::FieldMetric Delp2(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - bool useFFT = true); +bout::FieldMetric Delp2(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + bool useFFT = true); Field3D Delp2(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, bool useFFT = true); FieldPerp Delp2(const FieldPerp& f, CELL_LOC outloc = CELL_DEFAULT, bool useFFT = true); @@ -219,10 +219,9 @@ FieldPerp Delp2(const FieldPerp& f, CELL_LOC outloc = CELL_DEFAULT, bool useFFT * * */ -Coordinates::FieldMetric -Laplace_perp(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& dfdy_boundary_condition = "free_o3", - const std::string& dfdy_region = ""); +bout::FieldMetric Laplace_perp(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& dfdy_boundary_condition = "free_o3", + const std::string& dfdy_region = ""); Field3D Laplace_perp(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& dfdy_boundary_condition = "free_o3", const std::string& dfdy_region = ""); @@ -231,15 +230,15 @@ Field3D Laplace_perp(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, * Parallel Laplacian operator * */ -Coordinates::FieldMetric Laplace_par(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT); +bout::FieldMetric Laplace_par(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT); Field3D Laplace_par(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT); /*! * Full Laplacian operator (par + perp) */ -Coordinates::FieldMetric Laplace(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& dfdy_boundary_condition = "free_o3", - const std::string& dfdy_region = ""); +bout::FieldMetric Laplace(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& dfdy_boundary_condition = "free_o3", + const std::string& dfdy_region = ""); Field3D Laplace(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& dfdy_boundary_condition = "free_o3", const std::string& dfdy_region = ""); @@ -253,8 +252,8 @@ Field2D Laplace_perpXY(const Field2D& A, const Field2D& f); * Terms of form b0 x Grad(phi) dot Grad(A) * */ -Coordinates::FieldMetric b0xGrad_dot_Grad(const Field2D& phi, const Field2D& A, - CELL_LOC outloc = CELL_DEFAULT); +bout::FieldMetric b0xGrad_dot_Grad(const Field2D& phi, const Field2D& A, + CELL_LOC outloc = CELL_DEFAULT); /*! * Terms of form @@ -304,10 +303,9 @@ constexpr BRACKET_METHOD BRACKET_CTU = BRACKET_METHOD::ctu; * @param[in] solver Pointer to the time integration solver * */ -Coordinates::FieldMetric bracket(const Field2D& f, const Field2D& g, - BRACKET_METHOD method = BRACKET_STD, - CELL_LOC outloc = CELL_DEFAULT, - Solver* solver = nullptr); +bout::FieldMetric bracket(const Field2D& f, const Field2D& g, + BRACKET_METHOD method = BRACKET_STD, + CELL_LOC outloc = CELL_DEFAULT, Solver* solver = nullptr); Field3D bracket(const Field2D& f, const Field3D& g, BRACKET_METHOD method = BRACKET_STD, CELL_LOC outloc = CELL_DEFAULT, Solver* solver = nullptr); Field3D bracket(const Field3D& f, const Field2D& g, BRACKET_METHOD method = BRACKET_STD, diff --git a/src/mesh/christoffel_symbols.cxx b/src/mesh/christoffel_symbols.cxx index 943f020f36..c90d7c6d58 100644 --- a/src/mesh/christoffel_symbols.cxx +++ b/src/mesh/christoffel_symbols.cxx @@ -1,5 +1,6 @@ #include "bout/christoffel_symbols.hxx" #include "bout/coordinates.hxx" +#include "bout/derivs.hxx" #include "bout/mesh.hxx" #include "bout/output.hxx" @@ -25,88 +26,63 @@ ChristoffelSymbols::ChristoffelSymbols(const Coordinates& coordinates) { const auto& g_13 = covariantMetricTensor.g13(); const auto& g_23 = covariantMetricTensor.g23(); - G1_11_m = 0.5 * g11 * coordinates.DDX(g_11) - + g12 * (coordinates.DDX(g_12) - 0.5 * coordinates.DDY(g_11)) - + g13 * (coordinates.DDX(g_13) - 0.5 * coordinates.DDZ(g_11)); - G1_22_m = g11 * (coordinates.DDY(g_12) - 0.5 * coordinates.DDX(g_22)) - + 0.5 * g12 * coordinates.DDY(g_22) - + g13 * (coordinates.DDY(g_23) - 0.5 * coordinates.DDZ(g_22)); - G1_33_m = g11 * (coordinates.DDZ(g_13) - 0.5 * coordinates.DDX(g_33)) - + g12 * (coordinates.DDZ(g_23) - 0.5 * coordinates.DDY(g_33)) - + 0.5 * g13 * coordinates.DDZ(g_33); - G1_12_m = - 0.5 * g11 * coordinates.DDY(g_11) + 0.5 * g12 * coordinates.DDX(g_22) - + 0.5 * g13 - * (coordinates.DDY(g_13) + coordinates.DDX(g_23) - coordinates.DDZ(g_12)); - G1_13_m = - 0.5 * g11 * coordinates.DDZ(g_11) - + 0.5 * g12 - * (coordinates.DDZ(g_12) + coordinates.DDX(g_23) - coordinates.DDY(g_13)) - + 0.5 * g13 * coordinates.DDX(g_33); - G1_23_m = - 0.5 * g11 * (coordinates.DDZ(g_12) + coordinates.DDY(g_13) - coordinates.DDX(g_23)) - + 0.5 * g12 - * (coordinates.DDZ(g_22) + coordinates.DDY(g_23) - coordinates.DDY(g_23)) - // + 0.5 *g13*(coordinates.DDZ(g_32) + coordinates.DDY(g_33) - coordinates.DDZ(g_23)); - // which equals - + 0.5 * g13 * coordinates.DDY(g_33); + G1_11_m = 0.5 * g11 * DDX(g_11) + g12 * (DDX(g_12) - 0.5 * DDY(g_11)) + + g13 * (DDX(g_13) - 0.5 * DDZ(g_11)); + G1_22_m = g11 * (DDY(g_12) - 0.5 * DDX(g_22)) + 0.5 * g12 * DDY(g_22) + + g13 * (DDY(g_23) - 0.5 * DDZ(g_22)); + G1_33_m = g11 * (DDZ(g_13) - 0.5 * DDX(g_33)) + g12 * (DDZ(g_23) - 0.5 * DDY(g_33)) + + 0.5 * g13 * DDZ(g_33); + G1_12_m = 0.5 * g11 * DDY(g_11) + 0.5 * g12 * DDX(g_22) + + 0.5 * g13 * (DDY(g_13) + DDX(g_23) - DDZ(g_12)); + G1_13_m = 0.5 * g11 * DDZ(g_11) + 0.5 * g12 * (DDZ(g_12) + DDX(g_23) - DDY(g_13)) + + 0.5 * g13 * DDX(g_33); + G1_23_m = 0.5 * g11 * (DDZ(g_12) + DDY(g_13) - DDX(g_23)) + + 0.5 * g12 * (DDZ(g_22) + DDY(g_23) - DDY(g_23)) + // + 0.5 *g13*(DDZ(g_32) + DDY(g_33) - DDZ(g_23)); + // which equals + + 0.5 * g13 * DDY(g_33); - G2_11_m = 0.5 * g12 * coordinates.DDX(g_11) - + g22 * (coordinates.DDX(g_12) - 0.5 * coordinates.DDY(g_11)) - + g23 * (coordinates.DDX(g_13) - 0.5 * coordinates.DDZ(g_11)); - G2_22_m = g12 * (coordinates.DDY(g_12) - 0.5 * coordinates.DDX(g_22)) - + 0.5 * g22 * coordinates.DDY(g_22) - + g23 * (coordinates.DDY(g23) - 0.5 * coordinates.DDZ(g_22)); - G2_33_m = g12 * (coordinates.DDZ(g_13) - 0.5 * coordinates.DDX(g_33)) - + g22 * (coordinates.DDZ(g_23) - 0.5 * coordinates.DDY(g_33)) - + 0.5 * g23 * coordinates.DDZ(g_33); - G2_12_m = - 0.5 * g12 * coordinates.DDY(g_11) + 0.5 * g22 * coordinates.DDX(g_22) - + 0.5 * g23 - * (coordinates.DDY(g_13) + coordinates.DDX(g_23) - coordinates.DDZ(g_12)); + G2_11_m = 0.5 * g12 * DDX(g_11) + g22 * (DDX(g_12) - 0.5 * DDY(g_11)) + + g23 * (DDX(g_13) - 0.5 * DDZ(g_11)); + G2_22_m = g12 * (DDY(g_12) - 0.5 * DDX(g_22)) + 0.5 * g22 * DDY(g_22) + + g23 * (DDY(g23) - 0.5 * DDZ(g_22)); + G2_33_m = g12 * (DDZ(g_13) - 0.5 * DDX(g_33)) + g22 * (DDZ(g_23) - 0.5 * DDY(g_33)) + + 0.5 * g23 * DDZ(g_33); + G2_12_m = 0.5 * g12 * DDY(g_11) + 0.5 * g22 * DDX(g_22) + + 0.5 * g23 * (DDY(g_13) + DDX(g_23) - DDZ(g_12)); G2_13_m = - // 0.5 *g21*(coordinates.DDZ(g_11) + coordinates.DDX(covariantMetricTensor.Getg13()) - coordinates.DDX(g_13)) + // 0.5 *g21*(DDZ(g_11) + DDX(covariantMetricTensor.Getg13()) - DDX(g_13)) // which equals - 0.5 * g12 * (coordinates.DDZ(g_11) + coordinates.DDX(g_13) - coordinates.DDX(g_13)) - // + 0.5 *g22*(coordinates.DDZ(covariantMetricTensor.Getg21()) + coordinates.DDX(g_23) - coordinates.DDY(g_13)) + 0.5 * g12 * (DDZ(g_11) + DDX(g_13) - DDX(g_13)) + // + 0.5 *g22*(DDZ(covariantMetricTensor.Getg21()) + DDX(g_23) - DDY(g_13)) // which equals - + 0.5 * g22 - * (coordinates.DDZ(g_12) + coordinates.DDX(g_23) - coordinates.DDY(g_13)) - // + 0.5 *g23*(coordinates.DDZ(covariantMetricTensor.Getg31()) + coordinates.DDX(g_33) - coordinates.DDZ(g_13)); + + 0.5 * g22 * (DDZ(g_12) + DDX(g_23) - DDY(g_13)) + // + 0.5 *g23*(DDZ(covariantMetricTensor.Getg31()) + DDX(g_33) - DDZ(g_13)); // which equals - + 0.5 * g23 * coordinates.DDX(g_33); - G2_23_m = - 0.5 * g12 * (coordinates.DDZ(g_12) + coordinates.DDY(g_13) - coordinates.DDX(g_23)) - + 0.5 * g22 * coordinates.DDZ(g_22) + 0.5 * g23 * coordinates.DDY(g_33); + + 0.5 * g23 * DDX(g_33); + G2_23_m = 0.5 * g12 * (DDZ(g_12) + DDY(g_13) - DDX(g_23)) + 0.5 * g22 * DDZ(g_22) + + 0.5 * g23 * DDY(g_33); - G3_11_m = 0.5 * g13 * coordinates.DDX(g_11) - + g23 * (coordinates.DDX(g_12) - 0.5 * coordinates.DDY(g_11)) - + g33 * (coordinates.DDX(g_13) - 0.5 * coordinates.DDZ(g_11)); - G3_22_m = g13 * (coordinates.DDY(g_12) - 0.5 * coordinates.DDX(g_22)) - + 0.5 * g23 * coordinates.DDY(g_22) - + g33 * (coordinates.DDY(g_23) - 0.5 * coordinates.DDZ(g_22)); - G3_33_m = g13 * (coordinates.DDZ(g_13) - 0.5 * coordinates.DDX(g_33)) - + g23 * (coordinates.DDZ(g_23) - 0.5 * coordinates.DDY(g_33)) - + 0.5 * g33 * coordinates.DDZ(g_33); + G3_11_m = 0.5 * g13 * DDX(g_11) + g23 * (DDX(g_12) - 0.5 * DDY(g_11)) + + g33 * (DDX(g_13) - 0.5 * DDZ(g_11)); + G3_22_m = g13 * (DDY(g_12) - 0.5 * DDX(g_22)) + 0.5 * g23 * DDY(g_22) + + g33 * (DDY(g_23) - 0.5 * DDZ(g_22)); + G3_33_m = g13 * (DDZ(g_13) - 0.5 * DDX(g_33)) + g23 * (DDZ(g_23) - 0.5 * DDY(g_33)) + + 0.5 * g33 * DDZ(g_33); G3_12_m = - // 0.5 *g31*(coordinates.DDY(g_11) + coordinates.DDX(covariantMetricTensor.Getg12()) - coordinates.DDX(g_12)) + // 0.5 *g31*(DDY(g_11) + DDX(covariantMetricTensor.Getg12()) - DDX(g_12)) // which equals to - 0.5 * g13 * coordinates.DDY(g_11) - // + 0.5 *g32*(coordinates.DDY(covariantMetricTensor.Getg21()) + coordinates.DDX(g_22) - coordinates.DDY(g_12)) + 0.5 * g13 * DDY(g_11) + // + 0.5 *g32*(DDY(covariantMetricTensor.Getg21()) + DDX(g_22) - DDY(g_12)) // which equals to - + 0.5 * g23 * coordinates.DDX(g_22) - //+ 0.5 *g33*(coordinates.DDY(covariantMetricTensor.Getg31()) + coordinates.DDX(covariantMetricTensor.Getg32()) - coordinates.DDZ(g_12)); + + 0.5 * g23 * DDX(g_22) + //+ 0.5 *g33*(DDY(covariantMetricTensor.Getg31()) + DDX(covariantMetricTensor.Getg32()) - DDZ(g_12)); // which equals to - + 0.5 * g33 * (coordinates.DDY(g_13)) + coordinates.DDX(g_23) - - coordinates.DDZ(g_12); - G3_13_m = - 0.5 * g13 * coordinates.DDZ(g_11) - + 0.5 * g23 - * (coordinates.DDZ(g_12) + coordinates.DDX(g_23) - coordinates.DDY(g_13)) - + 0.5 * g33 * coordinates.DDX(g_33); - G3_23_m = 0.5 * g13 * (coordinates.DDZ(g_12) + coordinates.DDY(g_13)) - - coordinates.DDX(g_23) + 0.5 * g23 * coordinates.DDZ(g_22) - + 0.5 * g33 * coordinates.DDY(g_33); + + 0.5 * g33 * (DDY(g_13)) + DDX(g_23) - DDZ(g_12); + G3_13_m = 0.5 * g13 * DDZ(g_11) + 0.5 * g23 * (DDZ(g_12) + DDX(g_23) - DDY(g_13)) + + 0.5 * g33 * DDX(g_33); + G3_23_m = 0.5 * g13 * (DDZ(g_12) + DDY(g_13)) - DDX(g_23) + 0.5 * g23 * DDZ(g_22) + + 0.5 * g33 * DDY(g_33); output_progress.write("\tCommunicating connection terms\n"); diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index 22b43a9f8e..b07b9b1995 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -930,403 +930,6 @@ void Coordinates::setParallelTransform(Options* options) { } } -/******************************************************************************* - * Operators - * - *******************************************************************************/ - -Coordinates::FieldMetric Coordinates::DDX(const Field2D& f, CELL_LOC loc, - const std::string& method, - const std::string& region) const { - ASSERT1(location == loc || loc == CELL_DEFAULT); - return bout::derivatives::index::DDX(f, loc, method, region) / dx(); -} -Field3D Coordinates::DDX(const Field3D& f, CELL_LOC outloc, const std::string& method, - const std::string& region) const { - - auto result = bout::derivatives::index::DDX(f, outloc, method, region); - result /= dx(); - - if (f.getMesh()->IncIntShear) { - // Using BOUT-06 style shifting - result += IntShiftTorsion() * DDZ(f, outloc, method, region); - } - - return result; -}; - -Coordinates::FieldMetric Coordinates::DDY(const Field2D& f, CELL_LOC loc, - const std::string& method, - const std::string& region) const { - ASSERT1(location == loc || loc == CELL_DEFAULT); - return bout::derivatives::index::DDY(f, loc, method, region) / dy(); -} - -Field3D Coordinates::DDY(const Field3DParallel& f, CELL_LOC outloc, - const std::string& method, const std::string& region) const { - return bout::derivatives::index::DDY(f, outloc, method, region) / dy(); -}; - -Coordinates::FieldMetric Coordinates::DDZ(const Field2D& f, CELL_LOC loc, - const std::string& UNUSED(method), - const std::string& UNUSED(region)) const { - ASSERT1(location == loc || loc == CELL_DEFAULT); - ASSERT1(f.getMesh() == localmesh); - if (loc == CELL_DEFAULT) { - loc = f.getLocation(); - } - return zeroFrom(f).setLocation(loc); -} -Field3D Coordinates::DDZ(const Field3D& f, CELL_LOC outloc, const std::string& method, - const std::string& region) const { - return bout::derivatives::index::DDZ(f, outloc, method, region) / dz(); -}; - -///////////////////////////////////////////////////////// -// Parallel gradient - -Coordinates::FieldMetric Coordinates::Grad_par(const Field2D& var, - [[maybe_unused]] CELL_LOC outloc, - const std::string& UNUSED(method)) { - ASSERT1(location == outloc - || (outloc == CELL_DEFAULT && location == var.getLocation())); - - return DDY(var) * invSg(); -} - -Field3D Coordinates::Grad_par(const Field3DParallel& var, CELL_LOC outloc, - const std::string& method) { - - ASSERT1(location == outloc || outloc == CELL_DEFAULT); - - return ::DDY(var, outloc, method) * invSg(); -} - -///////////////////////////////////////////////////////// -// Vpar_Grad_par -// vparallel times the parallel derivative along unperturbed B-field - -Coordinates::FieldMetric Coordinates::Vpar_Grad_par(const Field2D& v, const Field2D& f, - [[maybe_unused]] CELL_LOC outloc, - const std::string& UNUSED(method)) { - ASSERT1(location == outloc || (outloc == CELL_DEFAULT && location == f.getLocation())); - - return VDDY(v, f) * invSg(); -} - -Field3D Coordinates::Vpar_Grad_par(const Field3D& v, const Field3DParallel& f, - CELL_LOC outloc, const std::string& method) { - ASSERT1(location == outloc || outloc == CELL_DEFAULT); - - return VDDY(v, f, outloc, method) * invSg(); -} - -///////////////////////////////////////////////////////// -// Parallel divergence - -Coordinates::FieldMetric Coordinates::Div_par(const Field2D& f, CELL_LOC outloc, - const std::string& method) { - - ASSERT1(location == outloc || outloc == CELL_DEFAULT); - - // Need Bxy at location of f, which might be different from location of this - // Coordinates object - auto Bxy_floc = f.getCoordinates()->Bxy(); - - return Bxy_ * Grad_par(FieldMetric{f / Bxy_floc}, outloc, method); -} - -Field3D Coordinates::Div_par(const Field3DParallel& f, CELL_LOC outloc, - const std::string& method) { - - ASSERT1(location == outloc || outloc == CELL_DEFAULT); - - // Need Bxy at location of f, which might be different from location of this - // Coordinates object - const auto& Bxy_floc = f.getCoordinates()->Bxy(); - - return Bxy() * Grad_par(f / Bxy_floc, outloc, method); -} - -///////////////////////////////////////////////////////// -// second parallel derivative (b dot Grad)(b dot Grad) -// Note: For parallel Laplacian use Laplace_par - -Coordinates::FieldMetric Coordinates::Grad2_par2(const Field2D& f, CELL_LOC outloc, - const std::string& method) { - - ASSERT1(location == outloc || (outloc == CELL_DEFAULT && location == f.getLocation())); - - auto result = Grad2_par2_DDY_invSg(outloc, method) * DDY(f, outloc, method) - + D2DY2(f, outloc, method) / g_22(); - - return result; -} - -Field3D Coordinates::Grad2_par2(const Field3DParallel& f, CELL_LOC outloc, - const std::string& method) { - - if (outloc == CELL_DEFAULT) { - outloc = f.getLocation(); - } - ASSERT1(location == outloc); - - Field3D result = ::DDY(f, outloc, method); - - Field3D r2 = D2DY2(f, outloc, method) / g_22(); - - result = Grad2_par2_DDY_invSg(outloc, method) * result + r2; - - ASSERT2(result.getLocation() == outloc); - - return result; -} - -///////////////////////////////////////////////////////// -// perpendicular Laplacian operator - -#include // Delp2 uses same coefficients as inversion code - -Coordinates::FieldMetric Coordinates::Delp2(const Field2D& f, CELL_LOC outloc, - bool UNUSED(useFFT)) { - - ASSERT1(location == outloc || outloc == CELL_DEFAULT); - - return G1() * DDX(f, outloc) + g11() * D2DX2(f, outloc); -} - -Field3D Coordinates::Delp2(const Field3D& f, CELL_LOC outloc, bool useFFT) { - - if (outloc == CELL_DEFAULT) { - outloc = f.getLocation(); - } - - ASSERT1(location == outloc); - ASSERT1(f.getLocation() == outloc); - - if (localmesh->GlobalNx == 1 && localmesh->GlobalNz == 1) { - // copy mesh, location, etc - return f * 0; - } - ASSERT2(localmesh->xstart > 0); // Need at least one guard cell; - - Field3D result{emptyFrom(f).setLocation(outloc)}; - - if (useFFT and not bout::build::use_metric_3d and localmesh->getNZPE() == 1) { - int ncz = localmesh->LocalNz; - - // Allocate memory - auto ft = Matrix(localmesh->LocalNx, ncz / 2 + 1); - auto delft = Matrix(localmesh->LocalNx, ncz / 2 + 1); - - // Loop over y indices - // Note: should not include y-guard or y-boundary points here as that would - // use values from corner cells in dx, which may not be initialised. - for (int jy = localmesh->ystart; jy <= localmesh->yend; jy++) { - - // Take forward FFT - - for (int jx = 0; jx < localmesh->LocalNx; jx++) { - rfft(&f(jx, jy, 0), ncz, &ft(jx, 0)); - } - - // Loop over kz - for (int jz = 0; jz <= ncz / 2; jz++) { - - // No smoothing in the x direction - for (int jx = localmesh->xstart; jx <= localmesh->xend; jx++) { - // Perform x derivative - - dcomplex a, b, c; - laplace_tridag_coefs(jx, jy, jz, a, b, c, nullptr, nullptr, outloc); - - delft(jx, jz) = a * ft(jx - 1, jz) + b * ft(jx, jz) + c * ft(jx + 1, jz); - } - } - - // Reverse FFT - for (int jx = localmesh->xstart; jx <= localmesh->xend; jx++) { - - irfft(&delft(jx, 0), ncz, &result(jx, jy, 0)); - } - } - } else { - result = G1() * ::DDX(f, outloc) + G3() * ::DDZ(f, outloc) - + g11() * ::D2DX2(f, outloc) + g33() * ::D2DZ2(f, outloc) - + 2 * g13() * ::D2DXDZ(f, outloc); - } - - ASSERT2(result.getLocation() == outloc); - - return result; -} - -FieldPerp Coordinates::Delp2(const FieldPerp& f, CELL_LOC outloc, bool useFFT) { - - if (outloc == CELL_DEFAULT) { - outloc = f.getLocation(); - } - - ASSERT1(location == outloc); - ASSERT1(f.getLocation() == outloc); - - if (localmesh->GlobalNx == 1 && localmesh->GlobalNz == 1) { - // copy mesh, location, etc - return f * 0; - } - ASSERT2(localmesh->xstart > 0); // Need at least one guard cell - - FieldPerp result{emptyFrom(f).setLocation(outloc)}; - - const int jy = f.getIndex(); - result.setIndex(jy); - - if (useFFT and localmesh->getNZPE() == 1) { - int ncz = localmesh->LocalNz; - - // Allocate memory - auto ft = Matrix(localmesh->LocalNx, ncz / 2 + 1); - auto delft = Matrix(localmesh->LocalNx, ncz / 2 + 1); - - // Take forward FFT - for (int jx = 0; jx < localmesh->LocalNx; jx++) { - rfft(&f(jx, 0), ncz, &ft(jx, 0)); - } - - // Loop over kz - for (int jz = 0; jz <= ncz / 2; jz++) { - - // No smoothing in the x direction - for (int jx = localmesh->xstart; jx <= localmesh->xend; jx++) { - // Perform x derivative - - dcomplex a, b, c; - laplace_tridag_coefs(jx, jy, jz, a, b, c); - - delft(jx, jz) = a * ft(jx - 1, jz) + b * ft(jx, jz) + c * ft(jx + 1, jz); - } - } - - // Reverse FFT - for (int jx = localmesh->xstart; jx <= localmesh->xend; jx++) { - irfft(&delft(jx, 0), ncz, &result(jx, 0)); - } - - } else { - throw BoutException("Non-fourier Delp2 not currently implented for FieldPerp."); - // Would be the following but don't have standard derivative operators for FieldPerps - // yet - // result = G1 * ::DDX(f, outloc) + G3 * ::DDZ(f, outloc) + g11 * ::D2DX2(f, outloc) - // + g33 * ::D2DZ2(f, outloc) + 2 * g13 * ::D2DXDZ(f, outloc); - }; - - return result; -} - -Coordinates::FieldMetric Coordinates::Laplace_par(const Field2D& f, CELL_LOC outloc) { - ASSERT1(location == outloc || outloc == CELL_DEFAULT); - return D2DY2(f, outloc) / g_22() - + DDY(FieldMetric{J() / g_22()}, outloc) * DDY(f, outloc) / J(); -} - -Field3D Coordinates::Laplace_par(const Field3DParallel& f, CELL_LOC outloc) { - ASSERT1(location == outloc || outloc == CELL_DEFAULT); - return D2DY2(f, outloc) / g_22() - + DDY(J().asField3DParallel() / g_22(), outloc) * ::DDY(f, outloc) / J(); -} - -// Full Laplacian operator on scalar field - -Coordinates::FieldMetric Coordinates::Laplace(const Field2D& f, CELL_LOC outloc, - const std::string& dfdy_boundary_conditions, - const std::string& dfdy_dy_region) { - - ASSERT1(location == outloc || outloc == CELL_DEFAULT); - - return G1() * DDX(f, outloc) + G2() * DDY(f, outloc) + g11() * D2DX2(f, outloc) - + g22() * D2DY2(f, outloc) - + 2.0 * g12() - * ::D2DXDY(f, outloc, "DEFAULT", "RGN_NOBNDRY", dfdy_boundary_conditions, - dfdy_dy_region); -} - -Field3D Coordinates::Laplace(const Field3DParallel& f, CELL_LOC outloc, - const std::string& dfdy_boundary_conditions, - const std::string& dfdy_dy_region) { - - ASSERT1(location == outloc || outloc == CELL_DEFAULT); - - return G1() * ::DDX(f, outloc) + G2() * ::DDY(f, outloc) + G3() * ::DDZ(f, outloc) - + g11() * ::D2DX2(f, outloc) + g22() * ::D2DY2(f, outloc) - + g33() * ::D2DZ2(f, outloc) - + 2.0 - * (g12() - * D2DXDY(f, outloc, "DEFAULT", "RGN_NOBNDRY", - dfdy_boundary_conditions, dfdy_dy_region) - + g13() * ::D2DXDZ(f, outloc) + g23() * ::D2DYDZ(f, outloc)); -} - -// Full perpendicular Laplacian, in form of inverse of Laplacian operator in LaplaceXY -// solver -Field2D Coordinates::Laplace_perpXY([[maybe_unused]] const Field2D& A, - [[maybe_unused]] const Field2D& f) const { -#if not(BOUT_USE_METRIC_3D) - Field2D result; - result.allocate(); - for (auto i : result.getRegion(RGN_NOBNDRY)) { - result[i] = 0.; - - // outer x boundary - const auto outer_x_avg = [&i](const auto& f) { return 0.5 * (f[i] + f[i.xp()]); }; - const BoutReal outer_x_A = outer_x_avg(A); - const BoutReal outer_x_J = outer_x_avg(J()); - const BoutReal outer_x_g11 = outer_x_avg(g11()); - const BoutReal outer_x_dx = outer_x_avg(dx()); - const BoutReal outer_x_value = - outer_x_A * outer_x_J * outer_x_g11 / (J()[i] * outer_x_dx * dx()[i]); - result[i] += outer_x_value * (f[i.xp()] - f[i]); - - // inner x boundary - const auto inner_x_avg = [&i](const auto& f) { return 0.5 * (f[i] + f[i.xm()]); }; - const BoutReal inner_x_A = inner_x_avg(A); - const BoutReal inner_x_J = inner_x_avg(J()); - const BoutReal inner_x_g11 = inner_x_avg(g11()); - const BoutReal inner_x_dx = inner_x_avg(dx()); - const BoutReal inner_x_value = - inner_x_A * inner_x_J * inner_x_g11 / (J()[i] * inner_x_dx * dx()[i]); - result[i] += inner_x_value * (f[i.xm()] - f[i]); - - // upper y boundary - const auto upper_y_avg = [&i](const auto& f) { return 0.5 * (f[i] + f[i.yp()]); }; - const BoutReal upper_y_A = upper_y_avg(A); - const BoutReal upper_y_J = upper_y_avg(J()); - const BoutReal upper_y_g_22 = upper_y_avg(g_22()); - const BoutReal upper_y_g23 = upper_y_avg(g23()); - const BoutReal upper_y_g_23 = upper_y_avg(g_23()); - const BoutReal upper_y_dy = upper_y_avg(dy()); - const BoutReal upper_y_value = -upper_y_A * upper_y_J * upper_y_g23 * upper_y_g_23 - / (upper_y_g_22 * J()[i] * upper_y_dy * dy()[i]); - result[i] += upper_y_value * (f[i.yp()] - f[i]); - - // lower y boundary - const auto lower_y_avg = [&i](const auto& f) { return 0.5 * (f[i] + f[i.ym()]); }; - const BoutReal lower_y_A = lower_y_avg(A); - const BoutReal lower_y_J = lower_y_avg(J()); - const BoutReal lower_y_g_22 = lower_y_avg(g_22()); - const BoutReal lower_y_g23 = lower_y_avg(g23()); - const BoutReal lower_y_g_23 = lower_y_avg(g_23()); - const BoutReal lower_y_dy = lower_y_avg(dy()); - const BoutReal lower_y_value = -lower_y_A * lower_y_J * lower_y_g23 * lower_y_g_23 - / (lower_y_g_22 * J()[i] * lower_y_dy * dy()[i]); - result[i] += lower_y_value * (f[i.ym()] - f[i]); - } - - return result; -#else - throw BoutException("Coordinates::Laplace_perpXY for 3D metric not implemented"); -#endif -} - const ChristoffelSymbols& Coordinates::christoffel_symbols() const { if (christoffel_symbols_cache == nullptr) { christoffel_symbols_cache = std::make_unique(*this); diff --git a/src/mesh/difops.cxx b/src/mesh/difops.cxx index 1b9a993e1a..7e2c313b53 100644 --- a/src/mesh/difops.cxx +++ b/src/mesh/difops.cxx @@ -23,8 +23,10 @@ * **************************************************************************/ +#include "bout/build_config.hxx" #include "bout/build_defines.hxx" - +#include "bout/dcomplex.hxx" +#include "bout/metric_tensor.hxx" #include #include #include @@ -35,40 +37,30 @@ #include #include #include +#include +#include // Delp2 uses same coefficients as inversion code #include #include #include +#include #include #include -#include // Delp2 uses same coefficients as inversion code - -#include -#include - #include +#include /******************************************************************************* * Grad_par * The parallel derivative along unperturbed B-field *******************************************************************************/ -Coordinates::FieldMetric Grad_par(const Field2D& var, CELL_LOC outloc, - const std::string& method) { - return var.getCoordinates(outloc)->Grad_par(var, outloc, method); -} - -Coordinates::FieldMetric Grad_par(const Field2D& var, const std::string& method, - CELL_LOC outloc) { - return var.getCoordinates(outloc)->Grad_par(var, outloc, method); +bout::FieldMetric Grad_par(const Field2D& var, CELL_LOC outloc, + const std::string& method) { + return DDY(var, outloc, method) * var.getCoordinates(outloc)->invSg(); } Field3D Grad_par(const Field3D& var, CELL_LOC outloc, const std::string& method) { - return var.getCoordinates(outloc)->Grad_par(var, outloc, method); -} - -Field3D Grad_par(const Field3D& var, const std::string& method, CELL_LOC outloc) { - return var.getCoordinates(outloc)->Grad_par(var, outloc, method); + return DDY(var, outloc, method) * var.getCoordinates(outloc)->invSg(); } /******************************************************************************* @@ -198,46 +190,34 @@ Field3D Grad_parP(const Field3D& apar, const Field3D& f) { * vparallel times the parallel derivative along unperturbed B-field *******************************************************************************/ -Coordinates::FieldMetric Vpar_Grad_par(const Field2D& v, const Field2D& f, - CELL_LOC outloc, const std::string& method) { - return f.getCoordinates(outloc)->Vpar_Grad_par(v, f, outloc, method); -} - -Coordinates::FieldMetric Vpar_Grad_par(const Field2D& v, const Field2D& f, - const std::string& method, CELL_LOC outloc) { - return f.getCoordinates(outloc)->Vpar_Grad_par(v, f, outloc, method); +bout::FieldMetric Vpar_Grad_par(const Field2D& v, const Field2D& f, CELL_LOC outloc, + const std::string& method) { + return VDDY(v, f, outloc, method) * f.getCoordinates(outloc)->invSg(); } Field3D Vpar_Grad_par(const Field3D& v, const Field3D& f, CELL_LOC outloc, const std::string& method) { - return f.getCoordinates(outloc)->Vpar_Grad_par(v, f, outloc, method); -} - -Field3D Vpar_Grad_par(const Field3D& v, const Field3D& f, const std::string& method, - CELL_LOC outloc) { - return f.getCoordinates(outloc)->Vpar_Grad_par(v, f, outloc, method); + return VDDY(v, f, outloc, method) * f.getCoordinates(outloc)->invSg(); } /******************************************************************************* * Div_par * parallel divergence operator B \partial_{||} (F/B) *******************************************************************************/ -Coordinates::FieldMetric Div_par(const Field2D& f, CELL_LOC outloc, - const std::string& method) { - return f.getCoordinates(outloc)->Div_par(f, outloc, method); -} +bout::FieldMetric Div_par(const Field2D& f, CELL_LOC outloc, const std::string& method) { + const auto& Bxy_outloc = f.getCoordinates(outloc)->Bxy(); + // Need Bxy at location of f, which might be different from outloc + const auto& Bxy_floc = f.getCoordinates()->Bxy(); -Coordinates::FieldMetric Div_par(const Field2D& f, const std::string& method, - CELL_LOC outloc) { - return f.getCoordinates(outloc)->Div_par(f, outloc, method); + return Bxy_outloc * Grad_par(bout::FieldMetric{f / Bxy_floc}, outloc, method); } Field3D Div_par(const Field3D& f, CELL_LOC outloc, const std::string& method) { - return f.getCoordinates(outloc)->Div_par(f, outloc, method); -} + const auto& Bxy_outloc = f.getCoordinates(outloc)->Bxy(); + // Need Bxy at location of f, which might be different from outloc + const auto& Bxy_floc = f.getCoordinates()->Bxy(); -Field3D Div_par(const Field3D& f, const std::string& method, CELL_LOC outloc) { - return f.getCoordinates(outloc)->Div_par(f, outloc, method); + return Bxy_outloc * Grad_par(Field3D{f / Bxy_floc}, outloc, method); } Field3D Div_par(const Field3D& f, const Field3D& v) { @@ -318,13 +298,23 @@ Field3D Div_par_flux(const Field3D& v, const Field3D& f, const std::string& meth * Note: For parallel Laplacian use LaplacePar *******************************************************************************/ -Coordinates::FieldMetric Grad2_par2(const Field2D& f, CELL_LOC outloc, - const std::string& method) { - return f.getCoordinates(outloc)->Grad2_par2(f, outloc, method); +bout::FieldMetric Grad2_par2(const Field2D& f, CELL_LOC outloc, + const std::string& method) { + const auto& coords = *f.getCoordinates(outloc); + + return coords.Grad2_par2_DDY_invSg(outloc, method) * DDY(f, outloc, method) + + D2DY2(f, outloc, method) / coords.g_22(); } Field3D Grad2_par2(const Field3D& f, CELL_LOC outloc, const std::string& method) { - return f.getCoordinates(outloc)->Grad2_par2(f, outloc, method); + if (outloc == CELL_DEFAULT) { + outloc = f.getLocation(); + } + + const auto& coords = *f.getCoordinates(outloc); + + return coords.Grad2_par2_DDY_invSg(outloc, method) * DDY(f, outloc, method) + + D2DY2(f, outloc, method) / coords.g_22(); } /******************************************************************************* @@ -332,8 +322,7 @@ Field3D Grad2_par2(const Field3D& f, CELL_LOC outloc, const std::string& method) * Parallel divergence of diffusive flux, K*Grad_par *******************************************************************************/ -Coordinates::FieldMetric Div_par_K_Grad_par(BoutReal kY, const Field2D& f, - CELL_LOC outloc) { +bout::FieldMetric Div_par_K_Grad_par(BoutReal kY, const Field2D& f, CELL_LOC outloc) { return kY * Grad2_par2(f, outloc); } @@ -341,8 +330,8 @@ Field3D Div_par_K_Grad_par(BoutReal kY, const Field3D& f, CELL_LOC outloc) { return kY * Grad2_par2(f, outloc); } -Coordinates::FieldMetric Div_par_K_Grad_par(const Field2D& kY, const Field2D& f, - CELL_LOC outloc) { +bout::FieldMetric Div_par_K_Grad_par(const Field2D& kY, const Field2D& f, + CELL_LOC outloc) { if (outloc == CELL_DEFAULT) { outloc = f.getLocation(); } @@ -445,10 +434,11 @@ Field3D Div_par_K_Grad_par_mod(const Field3D& Kin, const Field3D& fin, Field3D& const bool is_periodic_y = mesh->periodicY(ix); if (bndry_flux || is_periodic_y || !mesh->lastY(ix) || (iy != mesh->yend)) { - const BoutReal c = 0.5 * (K[i] + K[iyp]); // K at the upper boundary + const BoutReal c = 0.5 * (K[i] + K[iyp]); // K at the upper boundary const BoutReal J = 0.5 * (coord->J()[i] + coord->J()[iyp]); // Jacobian at boundary const BoutReal g_22 = 0.5 * (coord->g_22()[i] + coord->g_22()[iyp]); - const BoutReal gradient = 2. * (f[iyp] - f[i]) / (coord->dy()[i] + coord->dy()[iyp]); + const BoutReal gradient = + 2. * (f[iyp] - f[i]) / (coord->dy()[i] + coord->dy()[iyp]); const BoutReal flux = c * J * gradient / g_22; @@ -457,10 +447,11 @@ Field3D Div_par_K_Grad_par_mod(const Field3D& Kin, const Field3D& fin, Field3D& // Calculate flux at lower surface if (bndry_flux || is_periodic_y || !mesh->firstY(ix) || (iy != mesh->ystart)) { - const BoutReal c = 0.5 * (K[i] + K[iym]); // K at the lower boundary + const BoutReal c = 0.5 * (K[i] + K[iym]); // K at the lower boundary const BoutReal J = 0.5 * (coord->J()[i] + coord->J()[iym]); // Jacobian at boundary const BoutReal g_22 = 0.5 * (coord->g_22()[i] + coord->g_22()[iym]); - const BoutReal gradient = 2. * (f[i] - f[iym]) / (coord->dy()[i] + coord->dy()[iym]); + const BoutReal gradient = + 2. * (f[i] - f[iym]) / (coord->dy()[i] + coord->dy()[iym]); const BoutReal flux = c * J * gradient / g_22; @@ -481,16 +472,136 @@ Field3D Div_par_K_Grad_par_mod(const Field3D& Kin, const Field3D& fin, Field3D& * perpendicular Laplacian operator *******************************************************************************/ -Coordinates::FieldMetric Delp2(const Field2D& f, CELL_LOC outloc, bool useFFT) { - return f.getCoordinates(outloc)->Delp2(f, outloc, useFFT); +bout::FieldMetric Delp2(const Field2D& f, CELL_LOC outloc, [[maybe_unused]] bool useFFT) { + const auto& coords = *f.getCoordinates(outloc); + return coords.G1() * DDX(f, outloc) + coords.g11() * D2DX2(f, outloc); } Field3D Delp2(const Field3D& f, CELL_LOC outloc, bool useFFT) { - return f.getCoordinates(outloc)->Delp2(f, outloc, useFFT); + if (outloc == CELL_DEFAULT) { + outloc = f.getLocation(); + } + + ASSERT1(f.getLocation() == outloc); + const auto* mesh = f.getMesh(); + + if (mesh->GlobalNx == 1 && mesh->GlobalNz == 1) { + // copy mesh, location, etc + return f * 0; + } + ASSERT2(mesh->xstart > 0); // Need at least one guard cell; + + Field3D result{emptyFrom(f).setLocation(outloc)}; + + if (useFFT and not bout::build::use_metric_3d and mesh->getNZPE() == 1) { + const int ncz = mesh->LocalNz; + + // Allocate memory + auto ft = Matrix(mesh->LocalNx, (ncz / 2) + 1); + auto delft = Matrix(mesh->LocalNx, ncz / 2 + 1); + + // Loop over y indices + // Note: should not include y-guard or y-boundary points here as that would + // use values from corner cells in dx, which may not be initialised. + for (int jy = mesh->ystart; jy <= mesh->yend; jy++) { + + // Take forward FFT + + for (int jx = 0; jx < mesh->LocalNx; jx++) { + rfft(&f(jx, jy, 0), ncz, &ft(jx, 0)); + } + + // Loop over kz + for (int jz = 0; jz <= ncz / 2; jz++) { + + // No smoothing in the x direction + for (int jx = mesh->xstart; jx <= mesh->xend; jx++) { + // Perform x derivative + + dcomplex a, b, c; + laplace_tridag_coefs(jx, jy, jz, a, b, c, nullptr, nullptr, outloc); + + delft(jx, jz) = a * ft(jx - 1, jz) + b * ft(jx, jz) + c * ft(jx + 1, jz); + } + } + + // Reverse FFT + for (int jx = mesh->xstart; jx <= mesh->xend; jx++) { + + irfft(&delft(jx, 0), ncz, &result(jx, jy, 0)); + } + } + } else { + const auto& coords = *f.getCoordinates(outloc); + result = coords.G1() * DDX(f, outloc) + coords.G3() * DDZ(f, outloc) + + coords.g11() * D2DX2(f, outloc) + coords.g33() * D2DZ2(f, outloc) + + 2 * coords.g13() * D2DXDZ(f, outloc); + } + + ASSERT2(result.getLocation() == outloc); + + return result; } FieldPerp Delp2(const FieldPerp& f, CELL_LOC outloc, bool useFFT) { - return f.getCoordinates(outloc)->Delp2(f, outloc, useFFT); + if (outloc == CELL_DEFAULT) { + outloc = f.getLocation(); + } + + ASSERT1(f.getLocation() == outloc); + const auto* mesh = f.getMesh(); + + if (mesh->GlobalNx == 1 && mesh->GlobalNz == 1) { + // copy mesh, location, etc + return f * 0; + } + ASSERT2(mesh->xstart > 0); // Need at least one guard cell + + FieldPerp result{emptyFrom(f).setLocation(outloc)}; + + const int jy = f.getIndex(); + result.setIndex(jy); + + if (useFFT and mesh->getNZPE() == 1) { + int ncz = mesh->LocalNz; + + // Allocate memory + auto ft = Matrix(mesh->LocalNx, ncz / 2 + 1); + auto delft = Matrix(mesh->LocalNx, ncz / 2 + 1); + + // Take forward FFT + for (int jx = 0; jx < mesh->LocalNx; jx++) { + rfft(&f(jx, 0), ncz, &ft(jx, 0)); + } + + // Loop over kz + for (int jz = 0; jz <= ncz / 2; jz++) { + + // No smoothing in the x direction + for (int jx = mesh->xstart; jx <= mesh->xend; jx++) { + // Perform x derivative + + dcomplex a, b, c; + laplace_tridag_coefs(jx, jy, jz, a, b, c); + + delft(jx, jz) = a * ft(jx - 1, jz) + b * ft(jx, jz) + c * ft(jx + 1, jz); + } + } + + // Reverse FFT + for (int jx = mesh->xstart; jx <= mesh->xend; jx++) { + irfft(&delft(jx, 0), ncz, &result(jx, 0)); + } + + } else { + throw BoutException("Non-fourier Delp2 not currently implented for FieldPerp."); + // Would be the following but don't have standard derivative operators for FieldPerps + // yet + // result = G1 * ::DDX(f, outloc) + G3 * ::DDZ(f, outloc) + g11 * ::D2DX2(f, outloc) + // + g33 * ::D2DZ2(f, outloc) + 2 * g13 * ::D2DXDZ(f, outloc); + }; + + return result; } /******************************************************************************* @@ -500,9 +611,9 @@ FieldPerp Delp2(const FieldPerp& f, CELL_LOC outloc, bool useFFT) { * Laplace_perp = Laplace - Laplace_par *******************************************************************************/ -Coordinates::FieldMetric Laplace_perp(const Field2D& f, CELL_LOC outloc, - const std::string& dfdy_boundary_condition, - const std::string& dfdy_region) { +bout::FieldMetric Laplace_perp(const Field2D& f, CELL_LOC outloc, + const std::string& dfdy_boundary_condition, + const std::string& dfdy_region) { return Laplace(f, outloc, dfdy_boundary_condition, dfdy_region) - Laplace_par(f, outloc); } @@ -522,12 +633,18 @@ Field3D Laplace_perp(const Field3D& f, CELL_LOC outloc, * *******************************************************************************/ -Coordinates::FieldMetric Laplace_par(const Field2D& f, CELL_LOC outloc) { - return f.getCoordinates(outloc)->Laplace_par(f, outloc); +bout::FieldMetric Laplace_par(const Field2D& f, CELL_LOC outloc) { + const auto& coords = *f.getCoordinates(outloc); + return D2DY2(f, outloc) / coords.g_22() + + DDY(bout::FieldMetric{coords.J() / coords.g_22()}, outloc) * DDY(f, outloc) + / coords.J(); } Field3D Laplace_par(const Field3D& f, CELL_LOC outloc) { - return f.getCoordinates(outloc)->Laplace_par(f, outloc); + const auto& coords = *f.getCoordinates(outloc); + return D2DY2(f, outloc) / coords.g_22() + + DDY(coords.J().asField3DParallel() / coords.g_22(), outloc) * DDY(f, outloc) + / coords.J(); } /******************************************************************************* @@ -535,18 +652,31 @@ Field3D Laplace_par(const Field3D& f, CELL_LOC outloc) { * Full Laplacian operator on scalar field *******************************************************************************/ -Coordinates::FieldMetric Laplace(const Field2D& f, CELL_LOC outloc, - const std::string& dfdy_boundary_condition, - const std::string& dfdy_region) { - return f.getCoordinates(outloc)->Laplace(f, outloc, dfdy_boundary_condition, - dfdy_region); +bout::FieldMetric Laplace(const Field2D& f, CELL_LOC outloc, + const std::string& dfdy_boundary_condition, + const std::string& dfdy_region) { + const auto& coords = *f.getCoordinates(outloc); + + return coords.G1() * DDX(f, outloc) + coords.G2() * DDY(f, outloc) + + coords.g11() * D2DX2(f, outloc) + coords.g22() * D2DY2(f, outloc) + + 2.0 * coords.g12() + * D2DXDY(f, outloc, "DEFAULT", "RGN_NOBNDRY", dfdy_boundary_condition, + dfdy_region); } Field3D Laplace(const Field3D& f, CELL_LOC outloc, const std::string& dfdy_boundary_condition, const std::string& dfdy_region) { - return f.getCoordinates(outloc)->Laplace(f, outloc, dfdy_boundary_condition, - dfdy_region); + const auto& coords = *f.getCoordinates(outloc); + + return coords.G1() * DDX(f, outloc) + coords.G2() * DDY(f, outloc) + + coords.G3() * DDZ(f, outloc) + coords.g11() * D2DX2(f, outloc) + + coords.g22() * D2DY2(f, outloc) + coords.g33() * D2DZ2(f, outloc) + + 2.0 + * (coords.g12() + * D2DXDY(f, outloc, "DEFAULT", "RGN_NOBNDRY", + dfdy_boundary_condition, dfdy_region) + + coords.g13() * D2DXDZ(f, outloc) + coords.g23() * D2DYDZ(f, outloc)); } /******************************************************************************* @@ -555,7 +685,65 @@ Field3D Laplace(const Field3D& f, CELL_LOC outloc, *******************************************************************************/ Field2D Laplace_perpXY(const Field2D& A, const Field2D& f) { - return f.getCoordinates()->Laplace_perpXY(A, f); +#if BOUT_USE_METRIC_3D + throw BoutException("Coordinates::Laplace_perpXY for 3D metric not implemented"); +#else + const auto& coords = *f.getCoordinates(); + + Field2D result; + result.allocate(); + for (auto i : result.getRegion(RGN_NOBNDRY)) { + result[i] = 0.; + + // outer x boundary + const auto outer_x_avg = [&i](const auto& f) { return 0.5 * (f[i] + f[i.xp()]); }; + const BoutReal outer_x_A = outer_x_avg(A); + const BoutReal outer_x_J = outer_x_avg(coords.J()); + const BoutReal outer_x_g11 = outer_x_avg(coords.g11()); + const BoutReal outer_x_dx = outer_x_avg(coords.dx()); + const BoutReal outer_x_value = outer_x_A * outer_x_J * outer_x_g11 + / (coords.J()[i] * outer_x_dx * coords.dx()[i]); + result[i] += outer_x_value * (f[i.xp()] - f[i]); + + // inner x boundary + const auto inner_x_avg = [&i](const auto& f) { return 0.5 * (f[i] + f[i.xm()]); }; + const BoutReal inner_x_A = inner_x_avg(A); + const BoutReal inner_x_J = inner_x_avg(coords.J()); + const BoutReal inner_x_g11 = inner_x_avg(coords.g11()); + const BoutReal inner_x_dx = inner_x_avg(coords.dx()); + const BoutReal inner_x_value = inner_x_A * inner_x_J * inner_x_g11 + / (coords.J()[i] * inner_x_dx * coords.dx()[i]); + result[i] += inner_x_value * (f[i.xm()] - f[i]); + + // upper y boundary + const auto upper_y_avg = [&i](const auto& f) { return 0.5 * (f[i] + f[i.yp()]); }; + const BoutReal upper_y_A = upper_y_avg(A); + const BoutReal upper_y_J = upper_y_avg(coords.J()); + const BoutReal upper_y_g_22 = upper_y_avg(coords.g_22()); + const BoutReal upper_y_g23 = upper_y_avg(coords.g23()); + const BoutReal upper_y_g_23 = upper_y_avg(coords.g_23()); + const BoutReal upper_y_dy = upper_y_avg(coords.dy()); + const BoutReal upper_y_value = + -upper_y_A * upper_y_J * upper_y_g23 * upper_y_g_23 + / (upper_y_g_22 * coords.J()[i] * upper_y_dy * coords.dy()[i]); + result[i] += upper_y_value * (f[i.yp()] - f[i]); + + // lower y boundary + const auto lower_y_avg = [&i](const auto& f) { return 0.5 * (f[i] + f[i.ym()]); }; + const BoutReal lower_y_A = lower_y_avg(A); + const BoutReal lower_y_J = lower_y_avg(coords.J()); + const BoutReal lower_y_g_22 = lower_y_avg(coords.g_22()); + const BoutReal lower_y_g23 = lower_y_avg(coords.g23()); + const BoutReal lower_y_g_23 = lower_y_avg(coords.g_23()); + const BoutReal lower_y_dy = lower_y_avg(coords.dy()); + const BoutReal lower_y_value = + -lower_y_A * lower_y_J * lower_y_g23 * lower_y_g_23 + / (lower_y_g_22 * coords.J()[i] * lower_y_dy * coords.dy()[i]); + result[i] += lower_y_value * (f[i.ym()] - f[i]); + } + + return result; +#endif } /******************************************************************************* @@ -564,8 +752,8 @@ Field2D Laplace_perpXY(const Field2D& A, const Field2D& f) { * Used for ExB terms and perturbed B field using A_|| *******************************************************************************/ -Coordinates::FieldMetric b0xGrad_dot_Grad(const Field2D& phi, const Field2D& A, - CELL_LOC outloc) { +bout::FieldMetric b0xGrad_dot_Grad(const Field2D& phi, const Field2D& A, + CELL_LOC outloc) { if (outloc == CELL_DEFAULT) { outloc = A.getLocation(); @@ -576,15 +764,15 @@ Coordinates::FieldMetric b0xGrad_dot_Grad(const Field2D& phi, const Field2D& A, Coordinates* metric = phi.getCoordinates(outloc); // Calculate phi derivatives - Coordinates::FieldMetric dpdx = DDX(phi, outloc); - Coordinates::FieldMetric dpdy = DDY(phi, outloc); + bout::FieldMetric dpdx = DDX(phi, outloc); + bout::FieldMetric dpdy = DDY(phi, outloc); // Calculate advection velocity - Coordinates::FieldMetric vx = -metric->g_23() * dpdy; - Coordinates::FieldMetric vy = metric->g_23() * dpdx; + bout::FieldMetric vx = -metric->g_23() * dpdy; + bout::FieldMetric vy = metric->g_23() * dpdx; // Upwind A using these velocities - Coordinates::FieldMetric result = VDDX(vx, A, outloc) + VDDY(vy, A, outloc); + bout::FieldMetric result = VDDX(vx, A, outloc) + VDDY(vy, A, outloc); result /= metric->J() * sqrt(metric->g_22()); ASSERT1(result.getLocation() == outloc); @@ -608,13 +796,13 @@ Field3D b0xGrad_dot_Grad(const Field2D& phi, const Field3D& A, CELL_LOC outloc) Coordinates* metric = phi.getCoordinates(outloc); // Calculate phi derivatives - Coordinates::FieldMetric dpdx = DDX(phi, outloc); - Coordinates::FieldMetric dpdy = DDY(phi, outloc); + bout::FieldMetric dpdx = DDX(phi, outloc); + bout::FieldMetric dpdy = DDY(phi, outloc); // Calculate advection velocity - Coordinates::FieldMetric vx = -metric->g_23() * dpdy; - Coordinates::FieldMetric vy = metric->g_23() * dpdx; - Coordinates::FieldMetric vz = metric->g_12() * dpdy - metric->g_22() * dpdx; + bout::FieldMetric vx = -metric->g_23() * dpdy; + bout::FieldMetric vy = metric->g_23() * dpdx; + bout::FieldMetric vz = metric->g_12() * dpdy - metric->g_22() * dpdx; if (mesh->IncIntShear) { // BOUT-06 style differencing @@ -715,9 +903,8 @@ Field3D b0xGrad_dot_Grad(const Field3D& phi, const Field3D& A, CELL_LOC outloc) * Terms of form b0 x Grad(f) dot Grad(g) / B = [f, g] *******************************************************************************/ -Coordinates::FieldMetric bracket(const Field2D& f, const Field2D& g, - BRACKET_METHOD method, CELL_LOC outloc, - Solver* UNUSED(solver)) { +bout::FieldMetric bracket(const Field2D& f, const Field2D& g, BRACKET_METHOD method, + CELL_LOC outloc, Solver* UNUSED(solver)) { ASSERT1_FIELDS_COMPATIBLE(f, g); if (outloc == CELL_DEFAULT) { @@ -725,7 +912,7 @@ Coordinates::FieldMetric bracket(const Field2D& f, const Field2D& g, } ASSERT1(outloc == g.getLocation()); - Coordinates::FieldMetric result{emptyFrom(f)}; + bout::FieldMetric result{emptyFrom(f)}; if ((method == BRACKET_SIMPLE) || (method == BRACKET_ARAKAWA)) { // Use a subset of terms for comparison to BOUT-06 diff --git a/src/mesh/g_values.cxx b/src/mesh/g_values.cxx index 31a794fe22..644f58d52b 100644 --- a/src/mesh/g_values.cxx +++ b/src/mesh/g_values.cxx @@ -1,5 +1,6 @@ #include "bout/g_values.hxx" #include "bout/coordinates.hxx" +#include "bout/derivs.hxx" #include "bout/mesh.hxx" #include "bout/metric_tensor.hxx" @@ -19,16 +20,13 @@ GValues::GValues(const Coordinates& coordinates) { bout::FieldMetric Jg12 = J * g12; mesh->communicate(Jg12); - G1_m = - (coordinates.DDX(J * g11) + coordinates.DDY(Jg12) + coordinates.DDZ(J * g13)) / J; + G1_m = (DDX(J * g11) + DDY(Jg12) + DDZ(J * g13)) / J; bout::FieldMetric Jg22 = J * g22; mesh->communicate(Jg22); - G2_m = - (coordinates.DDX(J * g12) + coordinates.DDY(Jg22) + coordinates.DDZ(J * g23)) / J; + G2_m = (DDX(J * g12) + DDY(Jg22) + DDZ(J * g23)) / J; bout::FieldMetric Jg23 = J * g23; mesh->communicate(Jg23); - G3_m = - (coordinates.DDX(J * g13) + coordinates.DDY(Jg23) + coordinates.DDZ(J * g33)) / J; + G3_m = (DDX(J * g13) + DDY(Jg23) + DDZ(J * g33)) / J; mesh->communicate(G1_m, G2_m, G3_m); } diff --git a/src/sys/derivs.cxx b/src/sys/derivs.cxx index db44b388f1..444135a88d 100644 --- a/src/sys/derivs.cxx +++ b/src/sys/derivs.cxx @@ -48,7 +48,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -63,12 +65,23 @@ Field3D DDX(const Field3D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { - return f.getCoordinates(outloc)->DDX(f, outloc, method, region); + const auto& coords = *f.getCoordinates(); + + Field3D result = bout::derivatives::index::DDX(f, outloc, method, region) / coords.dx(); + + if (f.getMesh()->IncIntShear) { + // Using BOUT-06 style shifting + result += coords.IntShiftTorsion() * DDZ(f, outloc, method, region); + } + + return result; } -Coordinates::FieldMetric DDX(const Field2D& f, CELL_LOC outloc, const std::string& method, - const std::string& region) { - return f.getCoordinates(outloc)->DDX(f, outloc, method, region); +bout::FieldMetric DDX(const Field2D& f, CELL_LOC outloc, const std::string& method, + const std::string& region) { + ASSERT1(f.getLocation() == outloc || outloc == CELL_DEFAULT); + return bout::derivatives::index::DDX(f, outloc, method, region) + / f.getCoordinates()->dx(); } ////////////// Y DERIVATIVE ///////////////// @@ -79,9 +92,11 @@ Field3D DDY(const Field3DParallel& f, CELL_LOC outloc, const std::string& method / f.getCoordinates(outloc)->dy(); } -Coordinates::FieldMetric DDY(const Field2D& f, CELL_LOC outloc, const std::string& method, - const std::string& region) { - return f.getCoordinates(outloc)->DDY(f, outloc, method, region); +bout::FieldMetric DDY(const Field2D& f, CELL_LOC outloc, const std::string& method, + const std::string& region) { + ASSERT1(f.getLocation() == outloc || outloc == CELL_DEFAULT); + return bout::derivatives::index::DDY(f, outloc, method, region) + / f.getCoordinates()->dy(); } ////////////// Z DERIVATIVE ///////////////// @@ -92,9 +107,9 @@ Field3D DDZ(const Field3D& f, CELL_LOC outloc, const std::string& method, / f.getCoordinates(outloc)->dz(); } -Coordinates::FieldMetric DDZ(const Field2D& f, CELL_LOC UNUSED(outloc), - const std::string& UNUSED(method), - const std::string& UNUSED(region)) { +bout::FieldMetric DDZ(const Field2D& f, CELL_LOC UNUSED(outloc), + const std::string& UNUSED(method), + const std::string& UNUSED(region)) { auto tmp = Field2D(0., f.getMesh()); tmp.setLocation(f.getLocation()); return tmp; @@ -173,11 +188,11 @@ Field3D D2DX2(const Field3D& f, CELL_LOC outloc, const std::string& method, return result; } -Coordinates::FieldMetric D2DX2(const Field2D& f, CELL_LOC outloc, - const std::string& method, const std::string& region) { +bout::FieldMetric D2DX2(const Field2D& f, CELL_LOC outloc, const std::string& method, + const std::string& region) { const Coordinates* coords = f.getCoordinates(outloc); - Coordinates::FieldMetric result = + bout::FieldMetric result = bout::derivatives::index::D2DX2(f, outloc, method, region) / SQ(coords->dx()); if (coords->non_uniform()) { @@ -212,11 +227,11 @@ Field3D D2DY2(const Field3D& f, CELL_LOC outloc, const std::string& method, return result; } -Coordinates::FieldMetric D2DY2(const Field2D& f, CELL_LOC outloc, - const std::string& method, const std::string& region) { +bout::FieldMetric D2DY2(const Field2D& f, CELL_LOC outloc, const std::string& method, + const std::string& region) { const Coordinates* coords = f.getCoordinates(outloc); - Coordinates::FieldMetric result = + bout::FieldMetric result = bout::derivatives::index::D2DY2(f, outloc, method, region) / SQ(coords->dy()); if (coords->non_uniform()) { // Correction for non-uniform f.getMesh() @@ -236,8 +251,8 @@ Field3D D2DZ2(const Field3D& f, CELL_LOC outloc, const std::string& method, / SQ(f.getCoordinates(outloc)->dz()); } -Coordinates::FieldMetric D2DZ2(const Field2D& f, CELL_LOC outloc, - const std::string& method, const std::string& region) { +bout::FieldMetric D2DZ2(const Field2D& f, CELL_LOC outloc, const std::string& method, + const std::string& region) { return bout::derivatives::index::D2DZ2(f, outloc, method, region) / SQ(f.getCoordinates(outloc)->dz()); } @@ -252,8 +267,8 @@ Field3D D4DX4(const Field3D& f, CELL_LOC outloc, const std::string& method, / SQ(SQ(f.getCoordinates(outloc)->dx())); } -Coordinates::FieldMetric D4DX4(const Field2D& f, CELL_LOC outloc, - const std::string& method, const std::string& region) { +bout::FieldMetric D4DX4(const Field2D& f, CELL_LOC outloc, const std::string& method, + const std::string& region) { return bout::derivatives::index::D4DX4(f, outloc, method, region) / SQ(SQ(f.getCoordinates(outloc)->dx())); } @@ -264,8 +279,8 @@ Field3D D4DY4(const Field3D& f, CELL_LOC outloc, const std::string& method, / SQ(SQ(f.getCoordinates(outloc)->dy())); } -Coordinates::FieldMetric D4DY4(const Field2D& f, CELL_LOC outloc, - const std::string& method, const std::string& region) { +bout::FieldMetric D4DY4(const Field2D& f, CELL_LOC outloc, const std::string& method, + const std::string& region) { return bout::derivatives::index::D4DY4(f, outloc, method, region) / SQ(SQ(f.getCoordinates(outloc)->dy())); } @@ -276,8 +291,8 @@ Field3D D4DZ4(const Field3D& f, CELL_LOC outloc, const std::string& method, / SQ(SQ(f.getCoordinates(outloc)->dz())); } -Coordinates::FieldMetric D4DZ4(const Field2D& f, CELL_LOC outloc, - const std::string& method, const std::string& region) { +bout::FieldMetric D4DZ4(const Field2D& f, CELL_LOC outloc, const std::string& method, + const std::string& region) { return bout::derivatives::index::D4DZ4(f, outloc, method, region) / SQ(SQ(f.getCoordinates(outloc)->dz())); } @@ -293,10 +308,10 @@ Coordinates::FieldMetric D4DZ4(const Field2D& f, CELL_LOC outloc, * * ** Communicates and applies boundary in X. */ -Coordinates::FieldMetric D2DXDY(const Field2D& f, CELL_LOC outloc, - const std::string& method, const std::string& region, - const std::string& dfdy_boundary_condition, - const std::string& dfdy_region) { +bout::FieldMetric D2DXDY(const Field2D& f, CELL_LOC outloc, const std::string& method, + const std::string& region, + const std::string& dfdy_boundary_condition, + const std::string& dfdy_region) { const std::string dy_region = dfdy_region.empty() ? region : dfdy_region; // If staggering in x, take y-derivative at f's location. @@ -337,9 +352,9 @@ Field3D D2DXDY(const Field3D& f, CELL_LOC outloc, const std::string& method, return DDX(dfdy, outloc, method, region); } -Coordinates::FieldMetric D2DXDZ(const Field2D& f, CELL_LOC outloc, - [[maybe_unused]] const std::string& method, - [[maybe_unused]] const std::string& region) { +bout::FieldMetric D2DXDZ(const Field2D& f, CELL_LOC outloc, + [[maybe_unused]] const std::string& method, + [[maybe_unused]] const std::string& region) { #if BOUT_USE_METRIC_3D Field3D tmp{f}; return D2DXDZ(tmp, outloc, method, region); @@ -362,9 +377,9 @@ Field3D D2DXDZ(const Field3D& f, CELL_LOC outloc, const std::string& method, return DDZ(DDX(f, x_location, method, region), outloc, method, region); } -Coordinates::FieldMetric D2DYDZ(const Field2D& f, CELL_LOC outloc, - [[maybe_unused]] const std::string& method, - [[maybe_unused]] const std::string& region) { +bout::FieldMetric D2DYDZ(const Field2D& f, CELL_LOC outloc, + [[maybe_unused]] const std::string& method, + [[maybe_unused]] const std::string& region) { #if BOUT_USE_METRIC_3D Field3D tmp{f}; return D2DYDZ(tmp, outloc, method, region); @@ -394,8 +409,8 @@ Field3D D2DYDZ(const Field3D& f, CELL_LOC outloc, ////////////// X DERIVATIVE ///////////////// /// Special case where both arguments are 2D. -Coordinates::FieldMetric VDDX(const Field2D& v, const Field2D& f, CELL_LOC outloc, - const std::string& method, const std::string& region) { +bout::FieldMetric VDDX(const Field2D& v, const Field2D& f, CELL_LOC outloc, + const std::string& method, const std::string& region) { return bout::derivatives::index::VDDX(v, f, outloc, method, region) / f.getCoordinates(outloc)->dx(); } @@ -410,8 +425,8 @@ Field3D VDDX(const Field3D& v, const Field3D& f, CELL_LOC outloc, ////////////// Y DERIVATIVE ///////////////// // special case where both are 2D -Coordinates::FieldMetric VDDY(const Field2D& v, const Field2D& f, CELL_LOC outloc, - const std::string& method, const std::string& region) { +bout::FieldMetric VDDY(const Field2D& v, const Field2D& f, CELL_LOC outloc, + const std::string& method, const std::string& region) { return bout::derivatives::index::VDDY(v, f, outloc, method, region) / f.getCoordinates(outloc)->dy(); } @@ -426,16 +441,16 @@ Field3D VDDY(const Field3D& v, const Field3DParallel& f, CELL_LOC outloc, ////////////// Z DERIVATIVE ///////////////// // special case where both are 2D -Coordinates::FieldMetric VDDZ(const Field2D& v, const Field2D& f, CELL_LOC outloc, - const std::string& method, const std::string& region) { +bout::FieldMetric VDDZ(const Field2D& v, const Field2D& f, CELL_LOC outloc, + const std::string& method, const std::string& region) { return bout::derivatives::index::VDDZ(v, f, outloc, method, region) / f.getCoordinates(outloc)->dz(); } // Note that this is zero because no compression is included -Coordinates::FieldMetric VDDZ([[maybe_unused]] const Field3D& v, const Field2D& f, - CELL_LOC outloc, [[maybe_unused]] const std::string& method, - [[maybe_unused]] const std::string& region) { +bout::FieldMetric VDDZ([[maybe_unused]] const Field3D& v, const Field2D& f, + CELL_LOC outloc, [[maybe_unused]] const std::string& method, + [[maybe_unused]] const std::string& region) { #if BOUT_USE_METRIC_3D Field3D tmp{f}; return bout::derivatives::index::VDDZ(v, tmp, outloc, method, region) @@ -458,8 +473,8 @@ Field3D VDDZ(const Field3D& v, const Field3D& f, CELL_LOC outloc, /******************************************************************************* * Flux conserving schemes *******************************************************************************/ -Coordinates::FieldMetric FDDX(const Field2D& v, const Field2D& f, CELL_LOC outloc, - const std::string& method, const std::string& region) { +bout::FieldMetric FDDX(const Field2D& v, const Field2D& f, CELL_LOC outloc, + const std::string& method, const std::string& region) { return bout::derivatives::index::FDDX(v, f, outloc, method, region) / f.getCoordinates(outloc)->dx(); } @@ -472,8 +487,8 @@ Field3D FDDX(const Field3D& v, const Field3D& f, CELL_LOC outloc, ///////////////////////////////////////////////////////////////////////// -Coordinates::FieldMetric FDDY(const Field2D& v, const Field2D& f, CELL_LOC outloc, - const std::string& method, const std::string& region) { +bout::FieldMetric FDDY(const Field2D& v, const Field2D& f, CELL_LOC outloc, + const std::string& method, const std::string& region) { return bout::derivatives::index::FDDY(v, f, outloc, method, region) / f.getCoordinates(outloc)->dy(); } @@ -486,8 +501,8 @@ Field3D FDDY(const Field3D& v, const Field3DParallel& f, CELL_LOC outloc, ///////////////////////////////////////////////////////////////////////// -Coordinates::FieldMetric FDDZ(const Field2D& v, const Field2D& f, CELL_LOC outloc, - const std::string& method, const std::string& region) { +bout::FieldMetric FDDZ(const Field2D& v, const Field2D& f, CELL_LOC outloc, + const std::string& method, const std::string& region) { return bout::derivatives::index::FDDZ(v, f, outloc, method, region) / f.getCoordinates(outloc)->dz(); } From adec652faf1b16a2bb21988d8878656b882458b3 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Wed, 8 Jul 2026 18:04:38 +0100 Subject: [PATCH 009/221] Clang tidy+format --- include/bout/derivs.hxx | 4 +- include/bout/fv_ops_impl.hxx | 18 +- src/mesh/difops.cxx | 286 ++++++++++-------- src/mesh/fv_ops.cxx | 7 +- src/mesh/petsc_operators.cxx | 3 +- src/sys/derivs.cxx | 3 - .../invert/laplace/test_laplace_hypre3d.cxx | 4 +- .../laplace/test_laplace_petsc3damg.cxx | 4 +- 8 files changed, 176 insertions(+), 153 deletions(-) diff --git a/include/bout/derivs.hxx b/include/bout/derivs.hxx index be9f2f4111..8ee3b872a1 100644 --- a/include/bout/derivs.hxx +++ b/include/bout/derivs.hxx @@ -156,7 +156,7 @@ bout::FieldMetric DDZ(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Vector3D DDZ(const Vector3D& f, CELL_LOC outloc = CELL_DEFAULT, +Vector3D DDZ(const Vector3D& v, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT", const std::string& region = "RGN_NOBNDRY"); @@ -172,7 +172,7 @@ Vector3D DDZ(const Vector3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Vector2D DDZ(const Vector2D& f, CELL_LOC outloc = CELL_DEFAULT, +Vector2D DDZ(const Vector2D& v, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT", const std::string& region = "RGN_NOBNDRY"); diff --git a/include/bout/fv_ops_impl.hxx b/include/bout/fv_ops_impl.hxx index c86a376bc0..a4e8f908bf 100644 --- a/include/bout/fv_ops_impl.hxx +++ b/include/bout/fv_ops_impl.hxx @@ -491,10 +491,14 @@ Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_flux) { BOUT_FOR(i, result.getRegion("RGN_NOBNDRY")) { // Calculate velocities - const BoutReal vU = 0.25 * (vz[i.zp()] + vz[i]) * (coord->J()[i.zp()] + coord->J()[i]); - const BoutReal vD = 0.25 * (vz[i.zm()] + vz[i]) * (coord->J()[i.zm()] + coord->J()[i]); - const BoutReal vL = 0.25 * (vx[i.xm()] + vx[i]) * (coord->J()[i.xm()] + coord->J()[i]); - const BoutReal vR = 0.25 * (vx[i.xp()] + vx[i]) * (coord->J()[i.xp()] + coord->J()[i]); + const BoutReal vU = + 0.25 * (vz[i.zp()] + vz[i]) * (coord->J()[i.zp()] + coord->J()[i]); + const BoutReal vD = + 0.25 * (vz[i.zm()] + vz[i]) * (coord->J()[i.zm()] + coord->J()[i]); + const BoutReal vL = + 0.25 * (vx[i.xm()] + vx[i]) * (coord->J()[i.xm()] + coord->J()[i]); + const BoutReal vR = + 0.25 * (vx[i.xp()] + vx[i]) * (coord->J()[i.xp()] + coord->J()[i]); // X direction Stencil1D s; @@ -592,8 +596,10 @@ Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_flux) { BOUT_FOR(i, result.getRegion("RGN_NOBNDRY")) { // Y velocities on y boundaries - const BoutReal vU = 0.25 * (vy[i] + vy[i.yp()]) * (coord->J()[i] + coord->J()[i.yp()]); - const BoutReal vD = 0.25 * (vy[i] + vy[i.ym()]) * (coord->J()[i] + coord->J()[i.ym()]); + const BoutReal vU = + 0.25 * (vy[i] + vy[i.yp()]) * (coord->J()[i] + coord->J()[i.yp()]); + const BoutReal vD = + 0.25 * (vy[i] + vy[i.ym()]) * (coord->J()[i] + coord->J()[i.ym()]); // n (advected quantity) on y boundaries // Note: Use unshifted n_in variable diff --git a/src/mesh/difops.cxx b/src/mesh/difops.cxx index 7e2c313b53..9a22efa67f 100644 --- a/src/mesh/difops.cxx +++ b/src/mesh/difops.cxx @@ -44,7 +44,6 @@ #include #include #include -#include #include #include @@ -78,13 +77,13 @@ Field3D Grad_parP(const Field3D& apar, const Field3D& f) { ASSERT1_FIELDS_COMPATIBLE(apar, f); ASSERT1(f.hasParallelSlices()); - Mesh* mesh = apar.getMesh(); + const Mesh* mesh = apar.getMesh(); Field3D result{emptyFrom(f)}; - int ncz = mesh->LocalNz; + const int ncz = mesh->LocalNz; - Coordinates* metric = apar.getCoordinates(); + const Coordinates* metric = apar.getCoordinates(); Field3D gys{emptyFrom(f)}; @@ -93,8 +92,8 @@ Field3D Grad_parP(const Field3D& apar, const Field3D& f) { for (int y = 1; y <= mesh->LocalNy - 2; y++) { for (int z = 0; z < ncz; z++) { gys(x, y, z) = (f.yup()(x, y + 1, z) - f.ydown()(x, y - 1, z)) - / (0.5 * metric->dy(x, y + 1, z) + metric->dy(x, y, z) - + 0.5 * metric->dy(x, y - 1, z)); + / ((0.5 * metric->dy(x, y + 1, z)) + metric->dy(x, y, z) + + (0.5 * metric->dy(x, y - 1, z))); } } } @@ -102,19 +101,19 @@ Field3D Grad_parP(const Field3D& apar, const Field3D& f) { for (int x = 1; x <= mesh->LocalNx - 2; x++) { for (int y = mesh->ystart; y <= mesh->yend; y++) { for (int z = 0; z < ncz; z++) { - BoutReal by = 1. / sqrt(metric->g_22(x, y, z)); + const BoutReal by = 1. / sqrt(metric->g_22(x, y, z)); // Z indices zm and zp - int zm = (z - 1 + ncz) % ncz; - int zp = (z + 1) % ncz; + const int zm = (z - 1 + ncz) % ncz; + const int zp = (z + 1) % ncz; // bx = -DDZ(apar) - BoutReal bx = (apar(x, y, zm) - apar(x, y, zp)) - / (0.5 * metric->dz(x, y, zm) + metric->dz(x, y, z) - + 0.5 * metric->dz(x, y, zp)); + const BoutReal bx = (apar(x, y, zm) - apar(x, y, zp)) + / ((0.5 * metric->dz(x, y, zm)) + metric->dz(x, y, z) + + (0.5 * metric->dz(x, y, zp))); // bz = DDX(f) - BoutReal bz = (apar(x + 1, y, z) - apar(x - 1, y, z)) - / (0.5 * metric->dx(x - 1, y, z) + metric->dx(x, y, z) - + 0.5 * metric->dx(x + 1, y, z)); + const BoutReal bz = (apar(x + 1, y, z) - apar(x - 1, y, z)) + / ((0.5 * metric->dx(x - 1, y, z)) + metric->dx(x, y, z) + + (0.5 * metric->dx(x + 1, y, z))); // Now calculate (bx*d/dx + by*d/dy + bz*d/dz) f @@ -227,29 +226,29 @@ Field3D Div_par(const Field3D& f, const Field3D& v) { // Parallel divergence, using velocities at cell boundaries // Note: Not guaranteed to be flux conservative - Mesh* mesh = f.getMesh(); + const Mesh* mesh = f.getMesh(); Field3D result{emptyFrom(f)}; - Coordinates* coord = f.getCoordinates(); + const Coordinates* coord = f.getCoordinates(); for (int i = mesh->xstart; i <= mesh->xend; i++) { for (int j = mesh->ystart; j <= mesh->yend; j++) { for (int k = mesh->zstart; k <= mesh->zend; k++) { // Value of f and v at left cell face - BoutReal fL = 0.5 * (f(i, j, k) + f.ydown()(i, j - 1, k)); - BoutReal vL = 0.5 * (v(i, j, k) + v.ydown()(i, j - 1, k)); + const BoutReal fL = 0.5 * (f(i, j, k) + f.ydown()(i, j - 1, k)); + const BoutReal vL = 0.5 * (v(i, j, k) + v.ydown()(i, j - 1, k)); - BoutReal fR = 0.5 * (f(i, j, k) + f.yup()(i, j + 1, k)); - BoutReal vR = 0.5 * (v(i, j, k) + v.yup()(i, j + 1, k)); + const BoutReal fR = 0.5 * (f(i, j, k) + f.yup()(i, j + 1, k)); + const BoutReal vR = 0.5 * (v(i, j, k) + v.yup()(i, j + 1, k)); // Calculate flux at right boundary (y+1/2) - BoutReal fluxRight = + const BoutReal fluxRight = fR * vR * (coord->J(i, j, k) + coord->J(i, j + 1, k)) / (sqrt(coord->g_22(i, j, k)) + sqrt(coord->g_22(i, j + 1, k))); // Calculate at left boundary (y-1/2) - BoutReal fluxLeft = + const BoutReal fluxLeft = fL * vL * (coord->J(i, j, k) + coord->J(i, j - 1, k)) / (sqrt(coord->g_22(i, j, k)) + sqrt(coord->g_22(i, j - 1, k))); @@ -266,12 +265,12 @@ Field3D Div_par(const Field3D& f, const Field3D& v) { Field3D Div_par_flux(const Field3D& v, const Field3D& f, CELL_LOC outloc, const std::string& method) { - Coordinates* metric = f.getCoordinates(outloc); + const Coordinates* metric = f.getCoordinates(outloc); auto Bxy_floc = f.getCoordinates()->Bxy(); if (!f.hasParallelSlices()) { - Field3D f_B = f / Bxy_floc; + const Field3D f_B = f / Bxy_floc; return metric->Bxy() * FDDY(v, f_B, outloc, method) / sqrt(metric->g_22()); } @@ -284,8 +283,8 @@ Field3D Div_par_flux(const Field3D& v, const Field3D& f, CELL_LOC outloc, return metric->Bxy() * FDDY(v, f_B, outloc, method) / sqrt(metric->g_22()); } -Field3D Div_par_flux(const Field3D& v, const Field3D& f, const std::string& method, - CELL_LOC outloc) { +static Field3D Div_par_flux(const Field3D& v, const Field3D& f, const std::string& method, + CELL_LOC outloc) { return Div_par_flux(v, f, outloc, method); } @@ -498,7 +497,7 @@ Field3D Delp2(const Field3D& f, CELL_LOC outloc, bool useFFT) { // Allocate memory auto ft = Matrix(mesh->LocalNx, (ncz / 2) + 1); - auto delft = Matrix(mesh->LocalNx, ncz / 2 + 1); + auto delft = Matrix(mesh->LocalNx, (ncz / 2) + 1); // Loop over y indices // Note: should not include y-guard or y-boundary points here as that would @@ -518,7 +517,9 @@ Field3D Delp2(const Field3D& f, CELL_LOC outloc, bool useFFT) { for (int jx = mesh->xstart; jx <= mesh->xend; jx++) { // Perform x derivative - dcomplex a, b, c; + dcomplex a; + dcomplex b; + dcomplex c; laplace_tridag_coefs(jx, jy, jz, a, b, c, nullptr, nullptr, outloc); delft(jx, jz) = a * ft(jx - 1, jz) + b * ft(jx, jz) + c * ft(jx + 1, jz); @@ -563,11 +564,11 @@ FieldPerp Delp2(const FieldPerp& f, CELL_LOC outloc, bool useFFT) { result.setIndex(jy); if (useFFT and mesh->getNZPE() == 1) { - int ncz = mesh->LocalNz; + const int ncz = mesh->LocalNz; // Allocate memory - auto ft = Matrix(mesh->LocalNx, ncz / 2 + 1); - auto delft = Matrix(mesh->LocalNx, ncz / 2 + 1); + auto ft = Matrix(mesh->LocalNx, (ncz / 2) + 1); + auto delft = Matrix(mesh->LocalNx, (ncz / 2) + 1); // Take forward FFT for (int jx = 0; jx < mesh->LocalNx; jx++) { @@ -581,7 +582,9 @@ FieldPerp Delp2(const FieldPerp& f, CELL_LOC outloc, bool useFFT) { for (int jx = mesh->xstart; jx <= mesh->xend; jx++) { // Perform x derivative - dcomplex a, b, c; + dcomplex a; + dcomplex b; + dcomplex c; laplace_tridag_coefs(jx, jy, jz, a, b, c); delft(jx, jz) = a * ft(jx - 1, jz) + b * ft(jx, jz) + c * ft(jx + 1, jz); @@ -761,15 +764,15 @@ bout::FieldMetric b0xGrad_dot_Grad(const Field2D& phi, const Field2D& A, ASSERT1(phi.getMesh() == A.getMesh()); - Coordinates* metric = phi.getCoordinates(outloc); + const Coordinates* metric = phi.getCoordinates(outloc); // Calculate phi derivatives - bout::FieldMetric dpdx = DDX(phi, outloc); - bout::FieldMetric dpdy = DDY(phi, outloc); + const bout::FieldMetric dpdx = DDX(phi, outloc); + const bout::FieldMetric dpdy = DDY(phi, outloc); // Calculate advection velocity - bout::FieldMetric vx = -metric->g_23() * dpdy; - bout::FieldMetric vy = metric->g_23() * dpdx; + const bout::FieldMetric vx = -metric->g_23() * dpdy; + const bout::FieldMetric vy = metric->g_23() * dpdx; // Upwind A using these velocities bout::FieldMetric result = VDDX(vx, A, outloc) + VDDY(vy, A, outloc); @@ -791,17 +794,17 @@ Field3D b0xGrad_dot_Grad(const Field2D& phi, const Field3D& A, CELL_LOC outloc) ASSERT1(phi.getMesh() == A.getMesh()); - Mesh* mesh = phi.getMesh(); + const Mesh* mesh = phi.getMesh(); - Coordinates* metric = phi.getCoordinates(outloc); + const Coordinates* metric = phi.getCoordinates(outloc); // Calculate phi derivatives - bout::FieldMetric dpdx = DDX(phi, outloc); - bout::FieldMetric dpdy = DDY(phi, outloc); + const bout::FieldMetric dpdx = DDX(phi, outloc); + const bout::FieldMetric dpdy = DDY(phi, outloc); // Calculate advection velocity - bout::FieldMetric vx = -metric->g_23() * dpdy; - bout::FieldMetric vy = metric->g_23() * dpdx; + const bout::FieldMetric vx = -metric->g_23() * dpdy; + const bout::FieldMetric vy = metric->g_23() * dpdx; bout::FieldMetric vz = metric->g_12() * dpdy - metric->g_22() * dpdx; if (mesh->IncIntShear) { @@ -832,16 +835,16 @@ Field3D b0xGrad_dot_Grad(const Field3D& p, const Field2D& A, CELL_LOC outloc) { ASSERT1(p.getMesh() == A.getMesh()); - Coordinates* metric = p.getCoordinates(outloc); + const Coordinates* metric = p.getCoordinates(outloc); // Calculate phi derivatives - Field3D dpdx = DDX(p, outloc); - Field3D dpdy = DDY(p, outloc); - Field3D dpdz = DDZ(p, outloc); + const Field3D dpdx = DDX(p, outloc); + const Field3D dpdy = DDY(p, outloc); + const Field3D dpdz = DDZ(p, outloc); // Calculate advection velocity - Field3D vx = metric->g_22() * dpdz - metric->g_23() * dpdy; - Field3D vy = metric->g_23() * dpdx - metric->g_12() * dpdz; + const Field3D vx = metric->g_22() * dpdz - metric->g_23() * dpdy; + const Field3D vy = metric->g_23() * dpdx - metric->g_12() * dpdz; // Upwind A using these velocities @@ -866,18 +869,18 @@ Field3D b0xGrad_dot_Grad(const Field3D& phi, const Field3D& A, CELL_LOC outloc) ASSERT1(phi.getMesh() == A.getMesh()); - Mesh* mesh = phi.getMesh(); + const Mesh* mesh = phi.getMesh(); - Coordinates* metric = phi.getCoordinates(outloc); + const Coordinates* metric = phi.getCoordinates(outloc); // Calculate phi derivatives - Field3D dpdx = DDX(phi, outloc); - Field3D dpdy = DDY(phi, outloc); - Field3D dpdz = DDZ(phi, outloc); + const Field3D dpdx = DDX(phi, outloc); + const Field3D dpdy = DDY(phi, outloc); + const Field3D dpdz = DDZ(phi, outloc); // Calculate advection velocity - Field3D vx = metric->g_22() * dpdz - metric->g_23() * dpdy; - Field3D vy = metric->g_23() * dpdx - metric->g_12() * dpdz; + const Field3D vx = metric->g_22() * dpdz - metric->g_23() * dpdy; + const Field3D vy = metric->g_23() * dpdx - metric->g_12() * dpdz; Field3D vz = metric->g_12() * dpdy - metric->g_22() * dpdx; if (mesh->IncIntShear) { @@ -934,18 +937,18 @@ Field3D bracket(const Field3D& f, const Field2D& g, BRACKET_METHOD method, } ASSERT1(outloc == g.getLocation()); - [[maybe_unused]] Mesh* mesh = f.getMesh(); + [[maybe_unused]] const Mesh* mesh = f.getMesh(); Field3D result{emptyFrom(f).setLocation(outloc)}; - Coordinates* metric = f.getCoordinates(outloc); + const Coordinates* metric = f.getCoordinates(outloc); switch (method) { case BRACKET_CTU: { // First order Corner Transport Upwind method // P.Collela JCP 87, 171-200 (1990) - if (!solver) { + if (solver == nullptr) { throw BoutException("CTU method requires access to the solver"); } @@ -955,13 +958,14 @@ Field3D bracket(const Field3D& f, const Field2D& g, BRACKET_METHOD method, for (int x = mesh->xstart; x <= mesh->xend; x++) { for (int y = mesh->ystart; y <= mesh->yend; y++) { for (int z = 0; z < ncz; z++) { - int zm = (z - 1 + ncz) % ncz; - int zp = (z + 1) % ncz; + const int zm = (z - 1 + ncz) % ncz; + const int zp = (z + 1) % ncz; - BoutReal gp, gm; + BoutReal gp; + BoutReal gm; // Vx = DDZ(f) - BoutReal vx = (f(x, y, zp) - f(x, y, zm)) / (2. * metric->dz(x, y, z)); + const BoutReal vx = (f(x, y, zp) - f(x, y, zm)) / (2. * metric->dz(x, y, z)); // Set stability condition solver->setMaxTimestep(metric->dx(x, y, z) / (fabs(vx) + 1e-16)); @@ -996,14 +1000,20 @@ Field3D bracket(const Field3D& f, const Field2D& g, BRACKET_METHOD method, BOUT_FOR(j2D, result.getRegion2D("RGN_NOBNDRY")) { // Get constants for this iteration const BoutReal spacingFactor = 1.0 / (12 * metric->dz()[j2D] * metric->dx()[j2D]); - const int jy = j2D.y(), jx = j2D.x(); - const int xm = jx - 1, xp = jx + 1; + const int jy = j2D.y(); + const int jx = j2D.x(); + const int xm = jx - 1; + const int xp = jx + 1; // Extract relevant Field2D values - const BoutReal gxm = g(xm, jy), gc = g(jx, jy), gxp = g(xp, jy); + const BoutReal gxm = g(xm, jy); + const BoutReal gc = g(jx, jy); + const BoutReal gxp = g(xp, jy); // Index Field3D as 2D to get start of z data block - const auto fxm = f(xm, jy), fc = f(jx, jy), fxp = f(xp, jy); + const auto fxm = f(xm, jy); + const auto fc = f(jx, jy); + const auto fxp = f(xp, jy); // Here we split the loop over z into three parts; the first value, the middle block // and the last value @@ -1018,8 +1028,8 @@ Field3D bracket(const Field3D& f, const Field2D& g, BRACKET_METHOD method, const BoutReal Jpp = 2 * (fc[jzp] - fc[jzm]) * (gxp - gxm); // J+x - const BoutReal Jpx = gxp * (fxp[jzp] - fxp[jzm]) - gxm * (fxm[jzp] - fxm[jzm]) - + gc * (fxp[jzm] - fxp[jzp] - fxm[jzm] + fxm[jzp]); + const BoutReal Jpx = (gxp * (fxp[jzp] - fxp[jzm])) - (gxm * (fxm[jzp] - fxm[jzm])) + + (gc * (fxp[jzm] - fxp[jzp] - fxm[jzm] + fxm[jzp])); result(jx, jy, 0) = (Jpp + Jpx) * spacingFactor; } @@ -1033,8 +1043,8 @@ Field3D bracket(const Field3D& f, const Field2D& g, BRACKET_METHOD method, const BoutReal Jpp = 2 * (fc[jzp] - fc[jzm]) * (gxp - gxm); // J+x - const BoutReal Jpx = gxp * (fxp[jzp] - fxp[jzm]) - gxm * (fxm[jzp] - fxm[jzm]) - + gc * (fxp[jzm] - fxp[jzp] - fxm[jzm] + fxm[jzp]); + const BoutReal Jpx = (gxp * (fxp[jzp] - fxp[jzm])) - (gxm * (fxm[jzp] - fxm[jzm])) + + (gc * (fxp[jzm] - fxp[jzp] - fxm[jzm] + fxm[jzp])); result(jx, jy, jz) = (Jpp + Jpx) * spacingFactor; } @@ -1048,8 +1058,8 @@ Field3D bracket(const Field3D& f, const Field2D& g, BRACKET_METHOD method, const BoutReal Jpp = 2 * (fc[jzp] - fc[jzm]) * (gxp - gxm); // J+x - const BoutReal Jpx = gxp * (fxp[jzp] - fxp[jzm]) - gxm * (fxm[jzp] - fxm[jzm]) - + gc * (fxp[jzm] - fxp[jzp] - fxm[jzm] + fxm[jzp]); + const BoutReal Jpx = (gxp * (fxp[jzp] - fxp[jzm])) - (gxm * (fxm[jzp] - fxm[jzm])) + + (gc * (fxp[jzm] - fxp[jzp] - fxm[jzm] + fxm[jzp])); result(jx, jy, ncz - 1) = (Jpp + Jpx) * spacingFactor; } @@ -1101,7 +1111,7 @@ Field3D bracket(const Field2D& f, const Field3D& g, BRACKET_METHOD method, } default: { // Use full expression with all terms - Coordinates* metric = f.getCoordinates(outloc); + const Coordinates* metric = f.getCoordinates(outloc); result = b0xGrad_dot_Grad(f, g, outloc) / metric->Bxy(); } } @@ -1121,7 +1131,7 @@ Field3D bracket(const Field3D& f, const Field3D& g, BRACKET_METHOD method, Field3D result{emptyFrom(f).setLocation(outloc)}; - Coordinates* metric = f.getCoordinates(outloc); + const Coordinates* metric = f.getCoordinates(outloc); if (mesh->GlobalNx == 1 || mesh->GlobalNz == 1) { result = 0; @@ -1134,32 +1144,33 @@ Field3D bracket(const Field3D& f, const Field3D& g, BRACKET_METHOD method, // First order Corner Transport Upwind method // P.Collela JCP 87, 171-200 (1990) #if not(BOUT_USE_METRIC_3D) - if (!solver) { + if (solver == nullptr) { throw BoutException("CTU method requires access to the solver"); } // Get current timestep - BoutReal dt = solver->getCurrentTimestep(); + const BoutReal dt = solver->getCurrentTimestep(); - FieldPerp vx(mesh), vz(mesh); + FieldPerp vx(mesh); + FieldPerp vz(mesh); vx.allocate(); vx.setLocation(outloc); vz.allocate(); vz.setLocation(outloc); - int ncz = mesh->LocalNz; + const int ncz = mesh->LocalNz; for (int y = mesh->ystart; y <= mesh->yend; y++) { for (int x = 1; x <= mesh->LocalNx - 2; x++) { for (int z = mesh->zstart; z <= mesh->zend; z++) { - int zm = (z - 1 + ncz) % ncz; - int zp = (z + 1) % ncz; + const int zm = (z - 1 + ncz) % ncz; + const int zp = (z + 1) % ncz; // Vx = DDZ(f) vx(x, z) = (f(x, y, zp) - f(x, y, zm)) / (2. * metric->dz(x, y, z)); // Vz = -DDX(f) vz(x, z) = (f(x - 1, y, z) - f(x + 1, y, z)) - / (0.5 * metric->dx(x - 1, y) + metric->dx(x, y) - + 0.5 * metric->dx(x + 1, y)); + / ((0.5 * metric->dx(x - 1, y)) + metric->dx(x, y) + + (0.5 * metric->dx(x + 1, y))); // Set stability condition solver->setMaxTimestep(fabs(metric->dx(x, y)) / (fabs(vx(x, z)) + 1e-16)); @@ -1171,33 +1182,34 @@ Field3D bracket(const Field3D& f, const Field3D& g, BRACKET_METHOD method, for (int x = mesh->xstart; x <= mesh->xend; x++) { for (int z = 0; z < ncz; z++) { - int zm = (z - 1 + ncz) % ncz; - int zp = (z + 1) % ncz; + const int zm = (z - 1 + ncz) % ncz; + const int zp = (z + 1) % ncz; - BoutReal gp, gm; + BoutReal gp; + BoutReal gm; // X differencing if (vx(x, z) > 0.0) { gp = g(x, y, z) - + (0.5 * dt / metric->dz(x, y)) - * ((vz(x, z) > 0) ? vz(x, z) * (g(x, y, zm) - g(x, y, z)) - : vz(x, z) * (g(x, y, z) - g(x, y, zp))); + + ((0.5 * dt / metric->dz(x, y)) + * ((vz(x, z) > 0) ? vz(x, z) * (g(x, y, zm) - g(x, y, z)) + : vz(x, z) * (g(x, y, z) - g(x, y, zp)))); gm = g(x - 1, y, z) - + (0.5 * dt / metric->dz(x, y)) - * ((vz(x, z) > 0) ? vz(x, z) * (g(x - 1, y, zm) - g(x - 1, y, z)) - : vz(x, z) * (g(x - 1, y, z) - g(x - 1, y, zp))); + + ((0.5 * dt / metric->dz(x, y)) + * ((vz(x, z) > 0) ? vz(x, z) * (g(x - 1, y, zm) - g(x - 1, y, z)) + : vz(x, z) * (g(x - 1, y, z) - g(x - 1, y, zp)))); } else { gp = g(x + 1, y, z) - + (0.5 * dt / metric->dz(x, y)) - * ((vz(x, z) > 0) ? vz(x, z) * (g(x + 1, y, zm) - g(x + 1, y, z)) - : vz[x][z] * (g(x + 1, y, z) - g(x + 1, y, zp))); + + ((0.5 * dt / metric->dz(x, y)) + * ((vz(x, z) > 0) ? vz(x, z) * (g(x + 1, y, zm) - g(x + 1, y, z)) + : vz[x][z] * (g(x + 1, y, z) - g(x + 1, y, zp)))); gm = g(x, y, z) - + (0.5 * dt / metric->dz(x, y)) - * ((vz(x, z) > 0) ? vz(x, z) * (g(x, y, zm) - g(x, y, z)) - : vz(x, z) * (g(x, y, z) - g(x, y, zp))); + + ((0.5 * dt / metric->dz(x, y)) + * ((vz(x, z) > 0) ? vz(x, z) * (g(x, y, zm) - g(x, y, z)) + : vz(x, z) * (g(x, y, z) - g(x, y, zp)))); } result(x, y, z) = vx(x, z) * (gp - gm) / metric->dx(x, y); @@ -1205,24 +1217,24 @@ Field3D bracket(const Field3D& f, const Field3D& g, BRACKET_METHOD method, // Z differencing if (vz(x, z) > 0.0) { gp = g(x, y, z) - + (0.5 * dt / metric->dx(x, y)) - * ((vx[x][z] > 0) ? vx[x][z] * (g(x - 1, y, z) - g(x, y, z)) - : vx[x][z] * (g(x, y, z) - g(x + 1, y, z))); + + ((0.5 * dt / metric->dx(x, y)) + * ((vx[x][z] > 0) ? vx[x][z] * (g(x - 1, y, z) - g(x, y, z)) + : vx[x][z] * (g(x, y, z) - g(x + 1, y, z)))); gm = g(x, y, zm) - + (0.5 * dt / metric->dx(x, y)) - * ((vx(x, z) > 0) ? vx(x, z) * (g(x - 1, y, zm) - g(x, y, zm)) - : vx(x, z) * (g(x, y, zm) - g(x + 1, y, zm))); + + ((0.5 * dt / metric->dx(x, y)) + * ((vx(x, z) > 0) ? vx(x, z) * (g(x - 1, y, zm) - g(x, y, zm)) + : vx(x, z) * (g(x, y, zm) - g(x + 1, y, zm)))); } else { gp = g(x, y, zp) - + (0.5 * dt / metric->dx(x, y)) - * ((vx(x, z) > 0) ? vx(x, z) * (g(x - 1, y, zp) - g(x, y, zp)) - : vx(x, z) * (g(x, y, zp) - g(x + 1, y, zp))); + + ((0.5 * dt / metric->dx(x, y)) + * ((vx(x, z) > 0) ? vx(x, z) * (g(x - 1, y, zp) - g(x, y, zp)) + : vx(x, z) * (g(x, y, zp) - g(x + 1, y, zp)))); gm = g(x, y, z) - + (0.5 * dt / metric->dx(x, y)) - * ((vx(x, z) > 0) ? vx(x, z) * (g(x - 1, y, z) - g(x, y, z)) - : vx(x, z) * (g(x, y, z) - g(x + 1, y, z))); + + ((0.5 * dt / metric->dx(x, y)) + * ((vx(x, z) > 0) ? vx(x, z) * (g(x - 1, y, z) - g(x, y, z)) + : vx(x, z) * (g(x, y, z) - g(x + 1, y, z)))); } result(x, y, z) += vz(x, z) * (gp - gm) / metric->dz(x, y); @@ -1247,11 +1259,17 @@ Field3D bracket(const Field3D& f, const Field3D& g, BRACKET_METHOD method, #if not(BOUT_USE_METRIC_3D) const BoutReal spacingFactor = 1.0 / (12 * metric->dz()[j2D] * metric->dx()[j2D]); #endif - const int jy = j2D.y(), jx = j2D.x(); - const int xm = jx - 1, xp = jx + 1; - - const auto Fxm = f_temp(xm, jy), Fx = f_temp(jx, jy), Fxp = f_temp(xp, jy); - const auto Gxm = g_temp(xm, jy), Gx = g_temp(jx, jy), Gxp = g_temp(xp, jy); + const int jy = j2D.y(); + const int jx = j2D.x(); + const int xm = jx - 1; + const int xp = jx + 1; + + const auto Fxm = f_temp(xm, jy); + const auto Fx = f_temp(jx, jy); + const auto Fxp = f_temp(xp, jy); + const auto Gxm = g_temp(xm, jy); + const auto Gx = g_temp(jx, jy); + const auto Gxp = g_temp(xp, jy); // Here we split the loop over z into three parts; the first value, the middle block // and the last value @@ -1267,18 +1285,18 @@ Field3D bracket(const Field3D& f, const Field3D& g, BRACKET_METHOD method, #endif // J++ = DDZ(f)*DDX(g) - DDX(f)*DDZ(g) - const BoutReal Jpp = ((Fx[jzp] - Fx[jzm]) * (Gxp[jz] - Gxm[jz]) - - (Fxp[jz] - Fxm[jz]) * (Gx[jzp] - Gx[jzm])); + const BoutReal Jpp = (((Fx[jzp] - Fx[jzm]) * (Gxp[jz] - Gxm[jz])) + - ((Fxp[jz] - Fxm[jz]) * (Gx[jzp] - Gx[jzm]))); // J+x const BoutReal Jpx = - (Gxp[jz] * (Fxp[jzp] - Fxp[jzm]) - Gxm[jz] * (Fxm[jzp] - Fxm[jzm]) - - Gx[jzp] * (Fxp[jzp] - Fxm[jzp]) + Gx[jzm] * (Fxp[jzm] - Fxm[jzm])); + ((Gxp[jz] * (Fxp[jzp] - Fxp[jzm])) - (Gxm[jz] * (Fxm[jzp] - Fxm[jzm])) + - (Gx[jzp] * (Fxp[jzp] - Fxm[jzp])) + (Gx[jzm] * (Fxp[jzm] - Fxm[jzm]))); // Jx+ const BoutReal Jxp = - (Gxp[jzp] * (Fx[jzp] - Fxp[jz]) - Gxm[jzm] * (Fxm[jz] - Fx[jzm]) - - Gxm[jzp] * (Fx[jzp] - Fxm[jz]) + Gxp[jzm] * (Fxp[jz] - Fx[jzm])); + ((Gxp[jzp] * (Fx[jzp] - Fxp[jz])) - (Gxm[jzm] * (Fxm[jz] - Fx[jzm])) + - (Gxm[jzp] * (Fx[jzp] - Fxm[jz])) + (Gxp[jzm] * (Fxp[jz] - Fx[jzm]))); result(jx, jy, jz) = (Jpp + Jpx + Jxp) * spacingFactor; } @@ -1292,18 +1310,18 @@ Field3D bracket(const Field3D& f, const Field3D& g, BRACKET_METHOD method, const int jzm = jz - 1; // J++ = DDZ(f)*DDX(g) - DDX(f)*DDZ(g) - const BoutReal Jpp = ((Fx[jzp] - Fx[jzm]) * (Gxp[jz] - Gxm[jz]) - - (Fxp[jz] - Fxm[jz]) * (Gx[jzp] - Gx[jzm])); + const BoutReal Jpp = (((Fx[jzp] - Fx[jzm]) * (Gxp[jz] - Gxm[jz])) + - ((Fxp[jz] - Fxm[jz]) * (Gx[jzp] - Gx[jzm]))); // J+x const BoutReal Jpx = - (Gxp[jz] * (Fxp[jzp] - Fxp[jzm]) - Gxm[jz] * (Fxm[jzp] - Fxm[jzm]) - - Gx[jzp] * (Fxp[jzp] - Fxm[jzp]) + Gx[jzm] * (Fxp[jzm] - Fxm[jzm])); + ((Gxp[jz] * (Fxp[jzp] - Fxp[jzm])) - (Gxm[jz] * (Fxm[jzp] - Fxm[jzm])) + - (Gx[jzp] * (Fxp[jzp] - Fxm[jzp])) + (Gx[jzm] * (Fxp[jzm] - Fxm[jzm]))); // Jx+ const BoutReal Jxp = - (Gxp[jzp] * (Fx[jzp] - Fxp[jz]) - Gxm[jzm] * (Fxm[jz] - Fx[jzm]) - - Gxm[jzp] * (Fx[jzp] - Fxm[jz]) + Gxp[jzm] * (Fxp[jz] - Fx[jzm])); + ((Gxp[jzp] * (Fx[jzp] - Fxp[jz])) - (Gxm[jzm] * (Fxm[jz] - Fx[jzm])) + - (Gxm[jzp] * (Fx[jzp] - Fxm[jz])) + (Gxp[jzm] * (Fxp[jz] - Fx[jzm]))); result(jx, jy, jz) = (Jpp + Jpx + Jxp) * spacingFactor; } @@ -1318,18 +1336,18 @@ Field3D bracket(const Field3D& f, const Field3D& g, BRACKET_METHOD method, #endif // J++ = DDZ(f)*DDX(g) - DDX(f)*DDZ(g) - const BoutReal Jpp = ((Fx[jzp] - Fx[jzm]) * (Gxp[jz] - Gxm[jz]) - - (Fxp[jz] - Fxm[jz]) * (Gx[jzp] - Gx[jzm])); + const BoutReal Jpp = (((Fx[jzp] - Fx[jzm]) * (Gxp[jz] - Gxm[jz])) + - ((Fxp[jz] - Fxm[jz]) * (Gx[jzp] - Gx[jzm]))); // J+x const BoutReal Jpx = - (Gxp[jz] * (Fxp[jzp] - Fxp[jzm]) - Gxm[jz] * (Fxm[jzp] - Fxm[jzm]) - - Gx[jzp] * (Fxp[jzp] - Fxm[jzp]) + Gx[jzm] * (Fxp[jzm] - Fxm[jzm])); + ((Gxp[jz] * (Fxp[jzp] - Fxp[jzm])) - (Gxm[jz] * (Fxm[jzp] - Fxm[jzm])) + - (Gx[jzp] * (Fxp[jzp] - Fxm[jzp])) + (Gx[jzm] * (Fxp[jzm] - Fxm[jzm]))); // Jx+ const BoutReal Jxp = - (Gxp[jzp] * (Fx[jzp] - Fxp[jz]) - Gxm[jzm] * (Fxm[jz] - Fx[jzm]) - - Gxm[jzp] * (Fx[jzp] - Fxm[jz]) + Gxp[jzm] * (Fxp[jz] - Fx[jzm])); + ((Gxp[jzp] * (Fx[jzp] - Fxp[jz])) - (Gxm[jzm] * (Fxm[jz] - Fx[jzm])) + - (Gxm[jzp] * (Fx[jzp] - Fxm[jz])) + (Gxp[jzm] * (Fxp[jz] - Fx[jzm]))); result(jx, jy, jz) = (Jpp + Jpx + Jxp) * spacingFactor; } diff --git a/src/mesh/fv_ops.cxx b/src/mesh/fv_ops.cxx index e8958e128d..a400028256 100644 --- a/src/mesh/fv_ops.cxx +++ b/src/mesh/fv_ops.cxx @@ -214,11 +214,12 @@ Field3D Div_par_K_Grad_par(const Field3D& Kin, const Field3D& fin, bool bndry_fl if (bndry_flux || mesh->periodicY(i.x()) || !mesh->lastY(i.x()) || (i.y() != mesh->yend)) { - const BoutReal c = 0.5 * (K[i] + Kup[iyp]); // K at the upper boundary + const BoutReal c = 0.5 * (K[i] + Kup[iyp]); // K at the upper boundary const BoutReal J = 0.5 * (coord->J()[i] + coord->J()[iyp]); // Jacobian at boundary const BoutReal g_22 = 0.5 * (coord->g_22()[i] + coord->g_22()[iyp]); - const BoutReal gradient = 2. * (fup[iyp] - f[i]) / (coord->dy()[i] + coord->dy()[iyp]); + const BoutReal gradient = + 2. * (fup[iyp] - f[i]) / (coord->dy()[i] + coord->dy()[iyp]); const BoutReal flux = c * J * gradient / g_22; @@ -228,7 +229,7 @@ Field3D Div_par_K_Grad_par(const Field3D& Kin, const Field3D& fin, bool bndry_fl // Calculate flux at lower surface if (bndry_flux || mesh->periodicY(i.x()) || !mesh->firstY(i.x()) || (i.y() != mesh->ystart)) { - const BoutReal c = 0.5 * (K[i] + Kdown[iym]); // K at the lower boundary + const BoutReal c = 0.5 * (K[i] + Kdown[iym]); // K at the lower boundary const BoutReal J = 0.5 * (coord->J()[i] + coord->J()[iym]); // Jacobian at boundary const BoutReal g_22 = 0.5 * (coord->g_22()[i] + coord->g_22()[iym]); diff --git a/src/mesh/petsc_operators.cxx b/src/mesh/petsc_operators.cxx index 6b4de3fe22..42a35fbf5d 100644 --- a/src/mesh/petsc_operators.cxx +++ b/src/mesh/petsc_operators.cxx @@ -340,7 +340,8 @@ PetscOperators::Parallel PetscOperators::getParallel() const { dl.applyParallelBoundary("parallel_neumann_o1"); // Cell volume - Field3D dV = Coordinates::FieldMetric{coords->J() * coords->dx() * coords->dy() * coords->dz()}; + Field3D dV = + Coordinates::FieldMetric{coords->J() * coords->dx() * coords->dy() * coords->dz()}; dV.splitParallelSlices(); dV.yup() = 0.0; dV.ydown() = 0.0; diff --git a/src/sys/derivs.cxx b/src/sys/derivs.cxx index 444135a88d..4cc18d1ae9 100644 --- a/src/sys/derivs.cxx +++ b/src/sys/derivs.cxx @@ -41,7 +41,6 @@ #include #include #include -#include #include #include #include @@ -51,9 +50,7 @@ #include #include #include -#include #include -#include #include diff --git a/tests/unit/invert/laplace/test_laplace_hypre3d.cxx b/tests/unit/invert/laplace/test_laplace_hypre3d.cxx index 3a9c332480..e0febdbb22 100644 --- a/tests/unit/invert/laplace/test_laplace_hypre3d.cxx +++ b/tests/unit/invert/laplace/test_laplace_hypre3d.cxx @@ -40,8 +40,8 @@ class ForwardOperator { const Field3D operator()(Field3D& f) { Field3D result = d * Laplace_perp(f, CELL_DEFAULT, "free", "RGN_NOY") - + (Grad(f) * Grad(c2) - DDY(c2) * DDY(f) / coords->g_22()) / c1 + a * f - + ex * DDX(f) + ez * DDZ(f); + + (Grad(f) * Grad(c2) - DDY(c2) * DDY(f) / coords->g_22()) / c1 + + a * f + ex * DDX(f) + ez * DDZ(f); applyBoundaries(result, f); return result; } diff --git a/tests/unit/invert/laplace/test_laplace_petsc3damg.cxx b/tests/unit/invert/laplace/test_laplace_petsc3damg.cxx index 2b856871bc..dcb5e74b31 100644 --- a/tests/unit/invert/laplace/test_laplace_petsc3damg.cxx +++ b/tests/unit/invert/laplace/test_laplace_petsc3damg.cxx @@ -40,8 +40,8 @@ class ForwardOperator { const Field3D operator()(Field3D& f) { Field3D result = d * Laplace_perp(f, CELL_DEFAULT, "free", "RGN_NOY") - + (Grad(f) * Grad(c2) - DDY(c2) * DDY(f) / coords->g_22()) / c1 + a * f - + ex * DDX(f) + ez * DDZ(f); + + (Grad(f) * Grad(c2) - DDY(c2) * DDY(f) / coords->g_22()) / c1 + + a * f + ex * DDX(f) + ez * DDZ(f); applyBoundaries(result, f); return result; } From 448fadb1719e601363efab9d50a69e818623e742 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 8 Jul 2026 16:45:49 -0700 Subject: [PATCH 010/221] Coordinates::readParallelMetricComponents swap co/contra-variant Covariant metrics have `_`. The new code is consistent with e.g. metric reads around line 450. --- src/mesh/coordinates.cxx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index b07b9b1995..6359dbe13e 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -598,14 +598,14 @@ void Coordinates::readParallelMetricComponents() { load_parallel_metric_component(name, component, i); }; - read_offset("g11", covariantMetricTensor.g11_m); - read_offset("g22", covariantMetricTensor.g22_m); - read_offset("g33", covariantMetricTensor.g33_m); - read_offset("g13", covariantMetricTensor.g13_m); - read_offset("g_11", contravariantMetricTensor.g11_m); - read_offset("g_22", contravariantMetricTensor.g22_m); - read_offset("g_33", contravariantMetricTensor.g33_m); - read_offset("g_13", contravariantMetricTensor.g13_m); + read_offset("g_11", covariantMetricTensor.g11_m); + read_offset("g_22", covariantMetricTensor.g22_m); + read_offset("g_33", covariantMetricTensor.g33_m); + read_offset("g_13", covariantMetricTensor.g13_m); + read_offset("g11", contravariantMetricTensor.g11_m); + read_offset("g22", contravariantMetricTensor.g22_m); + read_offset("g33", contravariantMetricTensor.g33_m); + read_offset("g13", contravariantMetricTensor.g13_m); read_offset("dy", dy_); read_offset("Bxy", Bxy_); From b7b7f125b30ad79c06e7923ec05dab06a6d96dfe Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 8 Jul 2026 17:20:23 -0700 Subject: [PATCH 011/221] Mesh::recalculateStaggeredCoordinates move after reset `new_coordinates` should be moved into `coords_map` after it has been recalculated, not before. --- src/mesh/mesh.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/mesh.cxx b/src/mesh/mesh.cxx index 00bd0b5bc4..c8367f3541 100644 --- a/src/mesh/mesh.cxx +++ b/src/mesh/mesh.cxx @@ -803,11 +803,11 @@ void Mesh::recalculateStaggeredCoordinates() { auto force_interpolate_from_centre = true; Coordinates& new_coordinates = *createDefaultCoordinates(location, force_interpolate_from_centre); - *coords_map[location] = std::move(new_coordinates); auto recalculate_staggered = false; new_coordinates.recalculateAndReset(recalculate_staggered, force_interpolate_from_centre); + *coords_map[location] = std::move(new_coordinates); } } From 3580dc581cca7426a747b0996ff07ec381b85080 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 8 Jul 2026 17:23:28 -0700 Subject: [PATCH 012/221] Coordinates: Invalidate caches when cell spacing set `CoordinatesAccessor` and non-uniform geometry quantities depend on cell spacing. Reset cache so they are updated when needed. Bxy is not calculated on demand, so is calculated in `set*MetricTensor` methods. --- include/bout/coordinates.hxx | 4 +++ src/mesh/coordinates.cxx | 63 +++++++++++++++++++++++++++++------- 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/include/bout/coordinates.hxx b/include/bout/coordinates.hxx index d2019ed31f..f5d528b689 100644 --- a/include/bout/coordinates.hxx +++ b/include/bout/coordinates.hxx @@ -514,6 +514,10 @@ private: void checkCovariant(); // check that contravariant tensors are positive (if expected) and finite (always) void checkContravariant(); + void invalidateMetricCaches(); + void invalidateJacobianCaches(); + void invalidateCellGeometryCaches(); + void invalidateAccessorCache(); mutable std::array, 3> ybndrys; diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index 6359dbe13e..05cc515c9a 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -671,6 +671,8 @@ void Coordinates::setDx(FieldMetric dx, const bool communicate) { throw BoutException("dx magnitude less than 1e-8"); } dx_ = std::move(dx); + invalidateCellGeometryCaches(); + invalidateAccessorCache(); if (communicate) { localmesh->communicate_no_slices(dx_); } @@ -681,6 +683,8 @@ void Coordinates::setDy(FieldMetric dy, const bool communicate) { throw BoutException("dy magnitude less than 1e-8"); } dy_ = std::move(dy); + invalidateCellGeometryCaches(); + invalidateAccessorCache(); if (communicate) { localmesh->communicate_no_slices(dy_); } @@ -691,6 +695,9 @@ void Coordinates::setDz(FieldMetric dz, const bool communicate) { throw BoutException("dz magnitude less than 1e-8"); } dz_ = std::move(dz); + zlength_cache.reset(); + invalidateCellGeometryCaches(); + invalidateAccessorCache(); if (communicate) { localmesh->communicate_no_slices(dz_); } @@ -716,17 +723,8 @@ void Coordinates::recalculateAndReset(bool recalculate_staggered, zlength_cache.reset(); Grad2_par2_DDY_invSgCache.clear(); invSgCache.reset(); - CoordinatesAccessor::clear(this); - - _g_22_ylow.reset(); - _g_22_yhigh.reset(); - _cell_area_xlow.reset(); - _cell_area_xhigh.reset(); - _cell_area_ylow.reset(); - _cell_area_yhigh.reset(); - _cell_area_zlow.reset(); - _cell_area_zhigh.reset(); - _cell_volume.reset(); + invalidateCellGeometryCaches(); + invalidateAccessorCache(); } void Coordinates::correctionForNonUniformMeshes(bool force_interpolate_from_centre) { @@ -998,6 +996,36 @@ void Coordinates::checkContravariant() { contravariantMetricTensor.check(localmesh->ystart); } +void Coordinates::invalidateCellGeometryCaches() { + _g_22_ylow.reset(); + _g_22_yhigh.reset(); + _cell_area_xlow.reset(); + _cell_area_xhigh.reset(); + _cell_area_ylow.reset(); + _cell_area_yhigh.reset(); + _cell_area_zlow.reset(); + _cell_area_zhigh.reset(); + _cell_volume.reset(); +} + +void Coordinates::invalidateAccessorCache() { CoordinatesAccessor::clear(this); } + +void Coordinates::invalidateJacobianCaches() { + g_values_cache.reset(); + invalidateCellGeometryCaches(); + invalidateAccessorCache(); +} + +void Coordinates::invalidateMetricCaches() { + christoffel_symbols_cache.reset(); + g_values_cache.reset(); + Grad2_par2_DDY_invSgCache.clear(); + invSgCache.reset(); + jacobian_cache.reset(); + invalidateCellGeometryCaches(); + invalidateAccessorCache(); +} + const Coordinates::FieldMetric& Coordinates::J() const { if (jacobian_cache == nullptr) { jacobian_cache = std::make_unique(recalculateJacobian()); @@ -1010,6 +1038,7 @@ void Coordinates::setJ(const FieldMetric& J, const bool communicate) { bout::checkPositive(J, "J", "RGN_NOCORNERS"); //TODO: Calculate J and check value is close + invalidateJacobianCaches(); jacobian_cache = std::make_unique(J); if (communicate) { localmesh->communicate_no_slices(*jacobian_cache); @@ -1170,6 +1199,9 @@ std::shared_ptr Coordinates::makeYBoundary(YBndryType type) const { void Coordinates::setBxy(FieldMetric Bxy, const bool communicate) { //TODO: Calculate Bxy and check value is close Bxy_ = std::move(Bxy); + _g_22_ylow.reset(); + _g_22_yhigh.reset(); + invalidateAccessorCache(); if (communicate) { localmesh->communicate_no_slices(Bxy_); } @@ -1180,6 +1212,9 @@ void Coordinates::setContravariantMetricTensor( bool recalculate_staggered, bool force_interpolate_from_centre) { contravariantMetricTensor = metric_tensor; covariantMetricTensor = contravariantMetricTensor.inverse(region); + invalidateMetricCaches(); + setJ(recalculateJacobian()); + setBxy(recalculateBxy()); recalculateAndReset(recalculate_staggered, force_interpolate_from_centre); } @@ -1189,6 +1224,9 @@ void Coordinates::setCovariantMetricTensor(const CovariantMetricTensor& metric_t bool force_interpolate_from_centre) { covariantMetricTensor = metric_tensor; contravariantMetricTensor = covariantMetricTensor.inverse(region); + invalidateMetricCaches(); + setJ(recalculateJacobian()); + setBxy(recalculateBxy()); recalculateAndReset(recalculate_staggered, force_interpolate_from_centre); } @@ -1197,6 +1235,9 @@ void Coordinates::setMetricTensor( const CovariantMetricTensor& covariant_metric_tensor) { contravariantMetricTensor = contravariant_metric_tensor; covariantMetricTensor = covariant_metric_tensor; + invalidateMetricCaches(); + setJ(recalculateJacobian()); + setBxy(recalculateBxy()); } void Coordinates::communicateMetricTensor() { From 74147965700c58243e6532384baf038a8909e228 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 8 Jul 2026 17:26:03 -0700 Subject: [PATCH 013/221] test_coordinates: Fix IndexedAccessors z index should be in range. Indexed accessors return BoutReal, not reference to field. --- tests/unit/mesh/test_coordinates.cxx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/unit/mesh/test_coordinates.cxx b/tests/unit/mesh/test_coordinates.cxx index 532ccff6db..05f77d0c98 100644 --- a/tests/unit/mesh/test_coordinates.cxx +++ b/tests/unit/mesh/test_coordinates.cxx @@ -637,7 +637,7 @@ TEST_F(CoordinatesTest, IndexedAccessors) { int x = mesh->xstart; int y = mesh->ystart; #if BOUT_USE_METRIC_3D - int z = mesh->LocalNz; + int z = mesh->zstart; #endif output_info.disable(); @@ -662,12 +662,12 @@ TEST_F(CoordinatesTest, IndexedAccessors) { #endif #if not(BOUT_USE_METRIC_3D) - const FieldMetric& actual_dx = coords.dx(x, y); - const FieldMetric& actual_dy = coords.dy(x, y); + const BoutReal actual_dx = coords.dx(x, y); + const BoutReal actual_dy = coords.dy(x, y); #else - const Field3D& actual_dx = coords.dx(x, y, z); - const Field3D& actual_dy = coords.dy(x, y, z); - const Field3D& actual_dz = coords.dz(x, y, z); + const BoutReal actual_dx = coords.dx(x, y, z); + const BoutReal actual_dy = coords.dy(x, y, z); + const BoutReal actual_dz = coords.dz(x, y, z); #endif EXPECT_EQ(actual_dx, expected_dx); From 740f40f3eed0302988c51aa1fe4c3a38f56f1919 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Thu, 9 Jul 2026 14:27:04 +0100 Subject: [PATCH 014/221] Fix setting location of derivatives --- src/sys/derivs.cxx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sys/derivs.cxx b/src/sys/derivs.cxx index 4cc18d1ae9..b41b83900a 100644 --- a/src/sys/derivs.cxx +++ b/src/sys/derivs.cxx @@ -62,7 +62,7 @@ Field3D DDX(const Field3D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { - const auto& coords = *f.getCoordinates(); + const auto& coords = *f.getCoordinates(outloc); Field3D result = bout::derivatives::index::DDX(f, outloc, method, region) / coords.dx(); @@ -78,7 +78,7 @@ bout::FieldMetric DDX(const Field2D& f, CELL_LOC outloc, const std::string& meth const std::string& region) { ASSERT1(f.getLocation() == outloc || outloc == CELL_DEFAULT); return bout::derivatives::index::DDX(f, outloc, method, region) - / f.getCoordinates()->dx(); + / f.getCoordinates(outloc)->dx(); } ////////////// Y DERIVATIVE ///////////////// @@ -93,7 +93,7 @@ bout::FieldMetric DDY(const Field2D& f, CELL_LOC outloc, const std::string& meth const std::string& region) { ASSERT1(f.getLocation() == outloc || outloc == CELL_DEFAULT); return bout::derivatives::index::DDY(f, outloc, method, region) - / f.getCoordinates()->dy(); + / f.getCoordinates(outloc)->dy(); } ////////////// Z DERIVATIVE ///////////////// From 69950d18d9ca1638c733d76c5e3e5d1987e8dc35 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Thu, 9 Jul 2026 14:44:17 +0100 Subject: [PATCH 015/221] Fix broken examples --- examples/6field-simple/elm_6f.cxx | 40 +++++++++++++++++++-------- examples/conducting-wall-mode/cwm.cxx | 14 +++++++++- 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/examples/6field-simple/elm_6f.cxx b/examples/6field-simple/elm_6f.cxx index 61b98c2836..1ffa925580 100644 --- a/examples/6field-simple/elm_6f.cxx +++ b/examples/6field-simple/elm_6f.cxx @@ -6,19 +6,32 @@ * T. Xia *******************************************************************************/ +#include "bout/assert.hxx" #include "bout/bout.hxx" +#include "bout/bout_types.hxx" +#include "bout/build_defines.hxx" #include "bout/constants.hxx" +#include "bout/coordinates.hxx" #include "bout/derivs.hxx" +#include "bout/difops.hxx" +#include "bout/field2d.hxx" +#include "bout/field3d.hxx" +#include "bout/fieldgroup.hxx" #include "bout/initialprofiles.hxx" #include "bout/interpolation_xz.hxx" #include "bout/invert_laplace.hxx" -#include "bout/invert_parderiv.hxx" #include "bout/msg_stack.hxx" +#include "bout/output.hxx" #include "bout/physicsmodel.hxx" +#include "bout/smoothing.hxx" #include "bout/sourcex.hxx" #include "bout/tokamak_coordinates.hxx" +#include "bout/unused.hxx" +#include "bout/vecops.hxx" +#include "bout/where.hxx" #include +#include constexpr BoutReal eV_K = 11605.0; // 1eV = 11605K @@ -929,7 +942,7 @@ class Elm_6f : public PhysicsModel { output.write("\tlog Lambda: {:e}\n", LnLambda); nu_e = 2.91e-6 * LnLambda * ((N0)*Nbar * density / 1.e6) - * pow(Te0 * Tebar, -1.5); // nu_e in 1/S. + * pow(Field2D{Te0 * Tebar}, -1.5); // nu_e in 1/S. output.write("\telectron collision rate: {:e} -> {:e} [1/s]\n", min(nu_e), max(nu_e)); // nu_e.applyBoundary(); // mesh->communicate(nu_e); @@ -941,7 +954,8 @@ class Elm_6f : public PhysicsModel { // xqx addition, begin // Use Spitzer thermal conductivities nu_i = 4.80e-8 * (Zi * Zi * Zi * Zi / sqrt(AA)) * LnLambda - * ((N0)*Nbar * density / 1.e6) * pow(Ti0 * Tibar, -1.5); // nu_i in 1/S. + * ((N0)*Nbar * density / 1.e6) + * pow(Field2D{Ti0 * Tibar}, -1.5); // nu_i in 1/S. // output.write("\tCoulomb Logarithm: {:e} \n", max(LnLambda)); output.write("\tion collision rate: {:e} -> {:e} [1/s]\n", min(nu_i), max(nu_i)); @@ -1006,8 +1020,9 @@ class Elm_6f : public PhysicsModel { // Use Spitzer resistivity output.write("\n\tSpizter parameters"); // output.write("\tTemperature: {:e} -> {:e} [eV]\n", min(Te), max(Te)); - eta_spitzer = 0.51 * 1.03e-4 * Zi * LnLambda - * pow(Te0 * Tebar, -1.5); // eta in Ohm-m. NOTE: ln(Lambda) = 20 + eta_spitzer = + 0.51 * 1.03e-4 * Zi * LnLambda + * pow(Field2D{Te0 * Tebar}, -1.5); // eta in Ohm-m. NOTE: ln(Lambda) = 20 output.write("\tSpitzer resistivity: {:e} -> {:e} [Ohm m]\n", min(eta_spitzer), max(eta_spitzer)); eta_spitzer /= SI::mu0 * Va * Lbar; @@ -1114,7 +1129,7 @@ class Elm_6f : public PhysicsModel { // Only if not restarting: Check initial perturbation // Set U to zero where P0 < vacuum_pressure - U = where(P0 - vacuum_pressure, U, 0.0); + U = where(Field2D{P0 - vacuum_pressure}, U, 0.0); // Field2D lap_temp = 0.0; Field2D logn0 = laplace_alpha * N0; @@ -1241,24 +1256,25 @@ class Elm_6f : public PhysicsModel { // Update resistivity if (spitzer_resist) { // Use Spitzer formula - eta_spitzer = 0.51 * 1.03e-4 * Zi * LnLambda - * pow(Te_tmp * Tebar, -1.5); // eta in Ohm-m. ln(Lambda) = 20 + eta_spitzer = + 0.51 * 1.03e-4 * Zi * LnLambda + * pow(Field3D{Te_tmp * Tebar}, -1.5); // eta in Ohm-m. ln(Lambda) = 20 eta_spitzer /= SI::mu0 * Va * Lbar; } else { eta = core_resist + (vac_resist - core_resist) * vac_mask; } nu_e = 2.91e-6 * LnLambda * (N_tmp * Nbar * density / 1.e6) - * pow(Te_tmp * Tebar, -1.5); // nu_e in 1/S. + * pow(Field3D{Te_tmp * Tebar}, -1.5); // nu_e in 1/S. if (diffusion_par > 0.0) { // Use Spitzer thermal conductivities nu_i = 4.80e-8 * (Zi * Zi * Zi * Zi / sqrt(AA)) * LnLambda * (N_tmp * Nbar * density / 1.e6) - * pow(Ti_tmp * Tibar, -1.5); // nu_i in 1/S. - vth_i = 9.79e3 * sqrt(Ti_tmp * Tibar / AA); // vth_i in m/S. - vth_e = 4.19e5 * sqrt(Te_tmp * Tebar); // vth_e in m/S. + * pow(Field3D{Ti_tmp * Tibar}, -1.5); // nu_i in 1/S. + vth_i = 9.79e3 * sqrt(Ti_tmp * Tibar / AA); // vth_i in m/S. + vth_e = 4.19e5 * sqrt(Te_tmp * Tebar); // vth_e in m/S. } if (diffusion_par > 0.0) { diff --git a/examples/conducting-wall-mode/cwm.cxx b/examples/conducting-wall-mode/cwm.cxx index 1eda76c493..c3cd40630c 100644 --- a/examples/conducting-wall-mode/cwm.cxx +++ b/examples/conducting-wall-mode/cwm.cxx @@ -5,14 +5,26 @@ * Model version in the code created by M. Umansky and J. Myra. *******************************************************************************/ +#include +#include #include +#include +#include +#include +#include #include #include #include +#include #include +#include #include +#include #include +#include +#include + class CWM : public PhysicsModel { private: // 2D initial profiles @@ -328,7 +340,7 @@ class CWM : public PhysicsModel { Field3D result; if (bout_exb) { // Use a subset of terms for comparison to BOUT-06 - result = VDDZ(-DDX(p), f); + result = VDDZ(Field2D{-DDX(p)}, f); } else { // Use full expression with all terms result = b0xGrad_dot_Grad(p, f) / coord->Bxy(); From 7da06afb392e6fc19ebe614b37d483326cee5fd4 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Thu, 9 Jul 2026 14:44:34 +0100 Subject: [PATCH 016/221] Fix some clang-tidy warnings --- examples/fci-wave/fci-wave.cxx | 3 +- examples/laplace-petsc3d/test-laplace3d.cxx | 2 +- include/bout/coordinates.hxx | 2 +- include/bout/mesh.hxx | 2 +- include/bout/metric_tensor.hxx | 1 - src/field/vector2d.cxx | 93 +++++++++++-------- src/field/vector3d.cxx | 88 ++++++++++-------- .../laplace/impls/petsc3damg/petsc3damg.cxx | 9 +- .../impls/petsc2/laplacexy-petsc2.cxx | 12 +-- .../impls/cyclic/laplacexz-cyclic.cxx | 4 +- src/mesh/coordinates.cxx | 2 +- src/mesh/coordinates_accessor.cxx | 10 +- src/mesh/difops.cxx | 5 - tests/MMS/GBS/gbs.cxx | 1 + tests/MMS/advection/advection.cxx | 1 + tests/MMS/fieldalign/fieldalign.cxx | 1 + tests/MMS/tokamak/tokamak.cxx | 1 + tests/MMS/wave-1d/wave.cxx | 1 + .../test-drift-instability/2fluid.cxx | 1 + tests/integrated/test-snb/test_snb.cxx | 4 +- 20 files changed, 134 insertions(+), 109 deletions(-) diff --git a/examples/fci-wave/fci-wave.cxx b/examples/fci-wave/fci-wave.cxx index 0ea61f3e8d..fd3adbe2ab 100644 --- a/examples/fci-wave/fci-wave.cxx +++ b/examples/fci-wave/fci-wave.cxx @@ -1,7 +1,8 @@ - #include "bout/parallel_boundary_region.hxx" #include "bout/physicsmodel.hxx" +#include + class FCIwave : public PhysicsModel { private: Field3D n, nv; //< Evolving density, momentum diff --git a/examples/laplace-petsc3d/test-laplace3d.cxx b/examples/laplace-petsc3d/test-laplace3d.cxx index 9f21a69be1..5e40b42495 100644 --- a/examples/laplace-petsc3d/test-laplace3d.cxx +++ b/examples/laplace-petsc3d/test-laplace3d.cxx @@ -136,7 +136,7 @@ int main(int argc, char** argv) { /////////////////////////////////////////////////////////////////////////////////////// // Calculate error /////////////////////////////////////////////////////////////////////////////////////// - auto& g_22 = mesh->getCoordinates()->g_22(); + const auto& g_22 = mesh->getCoordinates()->g_22(); Field3D rhs_check = D * this_Laplace_perp(f) + (Grad(f) * Grad(C2) - DDY(C2) * DDY(f) / g_22) / C1 + A * f; // The usual way to do this would be diff --git a/include/bout/coordinates.hxx b/include/bout/coordinates.hxx index f5d528b689..f01e3883e0 100644 --- a/include/bout/coordinates.hxx +++ b/include/bout/coordinates.hxx @@ -517,7 +517,7 @@ private: void invalidateMetricCaches(); void invalidateJacobianCaches(); void invalidateCellGeometryCaches(); - void invalidateAccessorCache(); + void invalidateAccessorCache() const; mutable std::array, 3> ybndrys; diff --git a/include/bout/mesh.hxx b/include/bout/mesh.hxx index 833d23c419..ed1dff68b7 100644 --- a/include/bout/mesh.hxx +++ b/include/bout/mesh.hxx @@ -624,7 +624,7 @@ public: return getCoordinatesSmart(location).get(); }; - std::shared_ptr getCoordinatesSmart(const CELL_LOC location = CELL_CENTRE); + std::shared_ptr getCoordinatesSmart(CELL_LOC location = CELL_CENTRE); std::shared_ptr getCoordinatesConst(const CELL_LOC location = CELL_CENTRE) const { diff --git a/include/bout/metric_tensor.hxx b/include/bout/metric_tensor.hxx index bef5bc3079..9e510f2bb9 100644 --- a/include/bout/metric_tensor.hxx +++ b/include/bout/metric_tensor.hxx @@ -7,7 +7,6 @@ #include #include -#include namespace bout { #if BOUT_USE_METRIC_3D diff --git a/src/field/vector2d.cxx b/src/field/vector2d.cxx index bf20b130f5..d6182117ff 100644 --- a/src/field/vector2d.cxx +++ b/src/field/vector2d.cxx @@ -28,16 +28,26 @@ * **************************************************************************/ -#include - +#include #include +#include #include +#include +#include +#include +#include #include +#include +#include #include #include +#include +#include +#include + Vector2D::Vector2D(const Vector2D& f) - : FieldData(f), x(f.x), y(f.y), z(f.z), covariant(f.covariant), deriv(nullptr), + : FieldData(f), x(f.x), y(f.y), z(f.z), covariant(f.covariant), location(f.getLocation()) {} Vector2D::Vector2D(Mesh* localmesh, bool covariant, CELL_LOC location) @@ -67,10 +77,9 @@ void Vector2D::toCovariant() { Mesh* localmesh = getMesh(); if (location == CELL_VSHIFT) { - Coordinates *metric_x, *metric_y, *metric_z; - metric_x = localmesh->getCoordinates(CELL_XLOW); - metric_y = localmesh->getCoordinates(CELL_YLOW); - metric_z = localmesh->getCoordinates(CELL_ZLOW); + const auto* metric_x = localmesh->getCoordinates(CELL_XLOW); + const auto* metric_y = localmesh->getCoordinates(CELL_YLOW); + const auto* metric_z = localmesh->getCoordinates(CELL_ZLOW); // Fields at different locations so we need to interpolate // Note : Could reduce peak memory requirement here by just @@ -86,26 +95,28 @@ void Vector2D::toCovariant() { // multiply by g_{ij} BOUT_FOR(i, x.getRegion("RGN_ALL")) { - x[i] = metric_x->g_11()[i] * x[i] + metric_x->g_12()[i] * y_at_x[i] - + metric_x->g_13()[i] * z_at_x[i]; - y[i] = metric_y->g_22()[i] * y[i] + metric_y->g_12()[i] * x_at_y[i] - + metric_y->g_23()[i] * z_at_y[i]; - z[i] = metric_z->g_33()[i] * z[i] + metric_z->g_13()[i] * x_at_z[i] - + metric_z->g_23()[i] * y_at_z[i]; + x[i] = (metric_x->g_11()[i] * x[i]) + (metric_x->g_12()[i] * y_at_x[i]) + + (metric_x->g_13()[i] * z_at_x[i]); + y[i] = (metric_y->g_22()[i] * y[i]) + (metric_y->g_12()[i] * x_at_y[i]) + + (metric_y->g_23()[i] * z_at_y[i]); + z[i] = (metric_z->g_33()[i] * z[i]) + (metric_z->g_13()[i] * x_at_z[i]) + + (metric_z->g_23()[i] * y_at_z[i]); }; } else { - const auto metric = localmesh->getCoordinates(location); + auto* const metric = localmesh->getCoordinates(location); // Need to use temporary arrays to store result - Coordinates::FieldMetric gx{emptyFrom(x)}, gy{emptyFrom(y)}, gz{emptyFrom(z)}; + bout::FieldMetric gx{emptyFrom(x)}; + bout::FieldMetric gy{emptyFrom(y)}; + bout::FieldMetric gz{emptyFrom(z)}; BOUT_FOR(i, x.getRegion("RGN_ALL")) { - gx[i] = metric->g_11()[i] * x[i] + metric->g_12()[i] * y[i] - + metric->g_13()[i] * z[i]; - gy[i] = metric->g_22()[i] * y[i] + metric->g_12()[i] * x[i] - + metric->g_23()[i] * z[i]; - gz[i] = metric->g_33()[i] * z[i] + metric->g_13()[i] * x[i] - + metric->g_23()[i] * y[i]; + gx[i] = (metric->g_11()[i] * x[i]) + (metric->g_12()[i] * y[i]) + + (metric->g_13()[i] * z[i]); + gy[i] = (metric->g_22()[i] * y[i]) + (metric->g_12()[i] * x[i]) + + (metric->g_23()[i] * z[i]); + gz[i] = (metric->g_33()[i] * z[i]) + (metric->g_13()[i] * x[i]) + + (metric->g_23()[i] * y[i]); }; x = gx; @@ -123,11 +134,9 @@ void Vector2D::toContravariant() { Mesh* localmesh = getMesh(); if (location == CELL_VSHIFT) { - Coordinates *metric_x, *metric_y, *metric_z; - - metric_x = localmesh->getCoordinates(CELL_XLOW); - metric_y = localmesh->getCoordinates(CELL_YLOW); - metric_z = localmesh->getCoordinates(CELL_ZLOW); + const auto* metric_x = localmesh->getCoordinates(CELL_XLOW); + const auto* metric_y = localmesh->getCoordinates(CELL_YLOW); + const auto* metric_z = localmesh->getCoordinates(CELL_ZLOW); // Fields at different locations so we need to interpolate // Note : Could reduce peak memory requirement here by just @@ -143,27 +152,29 @@ void Vector2D::toContravariant() { // multiply by g_{ij} BOUT_FOR(i, x.getRegion("RGN_ALL")) { - x[i] = metric_x->g11()[i] * x[i] + metric_x->g12()[i] * y_at_x[i] - + metric_x->g13()[i] * z_at_x[i]; - y[i] = metric_y->g22()[i] * y[i] + metric_y->g12()[i] * x_at_y[i] - + metric_y->g23()[i] * z_at_y[i]; - z[i] = metric_z->g33()[i] * z[i] + metric_z->g13()[i] * x_at_z[i] - + metric_z->g23()[i] * y_at_z[i]; + x[i] = (metric_x->g11()[i] * x[i]) + (metric_x->g12()[i] * y_at_x[i]) + + (metric_x->g13()[i] * z_at_x[i]); + y[i] = (metric_y->g22()[i] * y[i]) + (metric_y->g12()[i] * x_at_y[i]) + + (metric_y->g23()[i] * z_at_y[i]); + z[i] = (metric_z->g33()[i] * z[i]) + (metric_z->g13()[i] * x_at_z[i]) + + (metric_z->g23()[i] * y_at_z[i]); }; } else { - const auto metric = localmesh->getCoordinates(location); + auto* const metric = localmesh->getCoordinates(location); // Need to use temporary arrays to store result - Coordinates::FieldMetric gx{emptyFrom(x)}, gy{emptyFrom(y)}, gz{emptyFrom(z)}; + bout::FieldMetric gx{emptyFrom(x)}; + bout::FieldMetric gy{emptyFrom(y)}; + bout::FieldMetric gz{emptyFrom(z)}; BOUT_FOR(i, x.getRegion("RGN_ALL")) { - gx[i] = - metric->g11()[i] * x[i] + metric->g12()[i] * y[i] + metric->g13()[i] * z[i]; - gy[i] = - metric->g22()[i] * y[i] + metric->g12()[i] * x[i] + metric->g23()[i] * z[i]; - gz[i] = - metric->g33()[i] * z[i] + metric->g13()[i] * x[i] + metric->g23()[i] * y[i]; + gx[i] = (metric->g11()[i] * x[i]) + (metric->g12()[i] * y[i]) + + (metric->g13()[i] * z[i]); + gy[i] = (metric->g22()[i] * y[i]) + (metric->g12()[i] * x[i]) + + (metric->g23()[i] * z[i]); + gz[i] = (metric->g33()[i] * z[i]) + (metric->g13()[i] * x[i]) + + (metric->g23()[i] * y[i]); }; x = gx; @@ -392,7 +403,7 @@ const Coordinates::FieldMetric Vector2D::operator*(const Vector2D& rhs) const { result = x * rhs.x + y * rhs.y + z * rhs.z; } else { // Both are covariant or contravariant - Coordinates* metric = localmesh->getCoordinates(location); + const Coordinates* metric = localmesh->getCoordinates(location); if (covariant) { // Both covariant diff --git a/src/field/vector3d.cxx b/src/field/vector3d.cxx index 7c5a5e3d67..fc61ab2a24 100644 --- a/src/field/vector3d.cxx +++ b/src/field/vector3d.cxx @@ -28,17 +28,24 @@ * **************************************************************************/ -#include - #include #include +#include #include +#include +#include +#include +#include #include +#include #include #include +#include +#include + Vector3D::Vector3D(const Vector3D& f) - : FieldData(f), x(f.x), y(f.y), z(f.z), covariant(f.covariant), deriv(nullptr), + : FieldData(f), x(f.x), y(f.y), z(f.z), covariant(f.covariant), location(f.getLocation()) {} Vector3D::Vector3D(Mesh* localmesh, bool covariant, CELL_LOC location) @@ -68,10 +75,9 @@ void Vector3D::toCovariant() { Mesh* localmesh = getMesh(); if (location == CELL_VSHIFT) { - Coordinates *metric_x, *metric_y, *metric_z; - metric_x = localmesh->getCoordinates(CELL_XLOW); - metric_y = localmesh->getCoordinates(CELL_YLOW); - metric_z = localmesh->getCoordinates(CELL_ZLOW); + const auto* metric_x = localmesh->getCoordinates(CELL_XLOW); + const auto* metric_y = localmesh->getCoordinates(CELL_YLOW); + const auto* metric_z = localmesh->getCoordinates(CELL_ZLOW); // Fields at different locations so we need to interpolate // Note : Could reduce peak memory requirement here by just @@ -87,26 +93,28 @@ void Vector3D::toCovariant() { // multiply by g_{ij} BOUT_FOR(i, localmesh->getRegion3D("RGN_ALL")) { - x[i] = metric_x->g_11()[i] * x[i] + metric_x->g_12()[i] * y_at_x[i] - + metric_x->g_13()[i] * z_at_x[i]; - y[i] = metric_y->g_22()[i] * y[i] + metric_y->g_12()[i] * x_at_y[i] - + metric_y->g_23()[i] * z_at_y[i]; - z[i] = metric_z->g_33()[i] * z[i] + metric_z->g_13()[i] * x_at_z[i] - + metric_z->g_23()[i] * y_at_z[i]; + x[i] = (metric_x->g_11()[i] * x[i]) + (metric_x->g_12()[i] * y_at_x[i]) + + (metric_x->g_13()[i] * z_at_x[i]); + y[i] = (metric_y->g_22()[i] * y[i]) + (metric_y->g_12()[i] * x_at_y[i]) + + (metric_y->g_23()[i] * z_at_y[i]); + z[i] = (metric_z->g_33()[i] * z[i]) + (metric_z->g_13()[i] * x_at_z[i]) + + (metric_z->g_23()[i] * y_at_z[i]); }; } else { - const auto metric = localmesh->getCoordinates(location); + auto* const metric = localmesh->getCoordinates(location); // Need to use temporary arrays to store result - Field3D gx{emptyFrom(x)}, gy{emptyFrom(y)}, gz{emptyFrom(z)}; + Field3D gx{emptyFrom(x)}; + Field3D gy{emptyFrom(y)}; + Field3D gz{emptyFrom(z)}; BOUT_FOR(i, localmesh->getRegion3D("RGN_ALL")) { - gx[i] = metric->g_11()[i] * x[i] + metric->g_12()[i] * y[i] - + metric->g_13()[i] * z[i]; - gy[i] = metric->g_22()[i] * y[i] + metric->g_12()[i] * x[i] - + metric->g_23()[i] * z[i]; - gz[i] = metric->g_33()[i] * z[i] + metric->g_13()[i] * x[i] - + metric->g_23()[i] * y[i]; + gx[i] = (metric->g_11()[i] * x[i]) + (metric->g_12()[i] * y[i]) + + (metric->g_13()[i] * z[i]); + gy[i] = (metric->g_22()[i] * y[i]) + (metric->g_12()[i] * x[i]) + + (metric->g_23()[i] * z[i]); + gz[i] = (metric->g_33()[i] * z[i]) + (metric->g_13()[i] * x[i]) + + (metric->g_23()[i] * y[i]); }; x = gx; @@ -124,11 +132,9 @@ void Vector3D::toContravariant() { Mesh* localmesh = getMesh(); if (location == CELL_VSHIFT) { - Coordinates *metric_x, *metric_y, *metric_z; - - metric_x = localmesh->getCoordinates(CELL_XLOW); - metric_y = localmesh->getCoordinates(CELL_YLOW); - metric_z = localmesh->getCoordinates(CELL_ZLOW); + const auto* metric_x = localmesh->getCoordinates(CELL_XLOW); + const auto* metric_y = localmesh->getCoordinates(CELL_YLOW); + const auto* metric_z = localmesh->getCoordinates(CELL_ZLOW); // Fields at different locations so we need to interpolate // Note : Could reduce peak memory requirement here by just @@ -144,27 +150,29 @@ void Vector3D::toContravariant() { // multiply by g_{ij} BOUT_FOR(i, localmesh->getRegion3D("RGN_ALL")) { - x[i] = metric_x->g11()[i] * x[i] + metric_x->g12()[i] * y_at_x[i] - + metric_x->g13()[i] * z_at_x[i]; - y[i] = metric_y->g22()[i] * y[i] + metric_y->g12()[i] * x_at_y[i] - + metric_y->g23()[i] * z_at_y[i]; - z[i] = metric_z->g33()[i] * z[i] + metric_z->g13()[i] * x_at_z[i] - + metric_z->g23()[i] * y_at_z[i]; + x[i] = (metric_x->g11()[i] * x[i]) + (metric_x->g12()[i] * y_at_x[i]) + + (metric_x->g13()[i] * z_at_x[i]); + y[i] = (metric_y->g22()[i] * y[i]) + (metric_y->g12()[i] * x_at_y[i]) + + (metric_y->g23()[i] * z_at_y[i]); + z[i] = (metric_z->g33()[i] * z[i]) + (metric_z->g13()[i] * x_at_z[i]) + + (metric_z->g23()[i] * y_at_z[i]); }; } else { - const auto metric = localmesh->getCoordinates(location); + auto* const metric = localmesh->getCoordinates(location); // Need to use temporary arrays to store result - Field3D gx{emptyFrom(x)}, gy{emptyFrom(y)}, gz{emptyFrom(z)}; + Field3D gx{emptyFrom(x)}; + Field3D gy{emptyFrom(y)}; + Field3D gz{emptyFrom(z)}; BOUT_FOR(i, localmesh->getRegion3D("RGN_ALL")) { - gx[i] = - metric->g11()[i] * x[i] + metric->g12()[i] * y[i] + metric->g13()[i] * z[i]; - gy[i] = - metric->g22()[i] * y[i] + metric->g12()[i] * x[i] + metric->g23()[i] * z[i]; - gz[i] = - metric->g33()[i] * z[i] + metric->g13()[i] * x[i] + metric->g23()[i] * y[i]; + gx[i] = (metric->g11()[i] * x[i]) + (metric->g12()[i] * y[i]) + + (metric->g13()[i] * z[i]); + gy[i] = (metric->g22()[i] * y[i]) + (metric->g12()[i] * x[i]) + + (metric->g23()[i] * z[i]); + gz[i] = (metric->g33()[i] * z[i]) + (metric->g13()[i] * x[i]) + + (metric->g23()[i] * y[i]); }; x = gx; diff --git a/src/invert/laplace/impls/petsc3damg/petsc3damg.cxx b/src/invert/laplace/impls/petsc3damg/petsc3damg.cxx index fa73016db0..d254ea4a68 100644 --- a/src/invert/laplace/impls/petsc3damg/petsc3damg.cxx +++ b/src/invert/laplace/impls/petsc3damg/petsc3damg.cxx @@ -24,14 +24,15 @@ * along with BOUT++. If not, see . * **************************************************************************/ -#include "bout/bout_types.hxx" -#include "bout/build_defines.hxx" + +#include #if BOUT_HAS_PETSC #include "petsc3damg.hxx" #include +#include #include #include #include @@ -40,6 +41,8 @@ #include #include +#include + using bout::utils::flagSet; #ifdef PETSC_HAVE_HYPRE @@ -328,7 +331,7 @@ void LaplacePetsc3dAmg::updateMatrix3D() { } BoutReal C_d2f_dx2 = coords->g11()[l]; - BoutReal C_d2f_dy2 = (coords->g22()[l] - 1.0 / coords->g_22()[l]); + BoutReal C_d2f_dy2 = (coords->g22()[l] - (1.0 / coords->g_22()[l])); BoutReal C_d2f_dz2 = coords->g33()[l]; if (issetD) { C_d2f_dx2 *= D[l]; diff --git a/src/invert/laplacexy/impls/petsc2/laplacexy-petsc2.cxx b/src/invert/laplacexy/impls/petsc2/laplacexy-petsc2.cxx index bdfc3bb5ff..916dc0c6af 100644 --- a/src/invert/laplacexy/impls/petsc2/laplacexy-petsc2.cxx +++ b/src/invert/laplacexy/impls/petsc2/laplacexy-petsc2.cxx @@ -162,7 +162,7 @@ void LaplaceXYpetsc2::setCoefs(const Field2D& A, const Field2D& B) { BoutReal dx = 0.5 * (coords->dx()[index] + coords->dx()[ind_xp]); BoutReal Acoef = 0.5 * (A[index] + A[ind_xp]); - BoutReal xp = Acoef * J * g11 / (coords->J()[index] * dx * coords->dx()[index]); + const BoutReal xp = Acoef * J * g11 / (coords->J()[index] * dx * coords->dx()[index]); // Metrics on x-1/2 boundary J = 0.5 * (coords->J()[index] + coords->J()[ind_xm]); @@ -170,7 +170,7 @@ void LaplaceXYpetsc2::setCoefs(const Field2D& A, const Field2D& B) { dx = 0.5 * (coords->dx()[index] + coords->dx()[ind_xm]); Acoef = 0.5 * (A[index] + A[ind_xm]); - BoutReal xm = Acoef * J * g11 / (coords->J()[index] * dx * coords->dx()[index]); + const BoutReal xm = Acoef * J * g11 / (coords->J()[index] * dx * coords->dx()[index]); BoutReal c = B[index] - xp - xm; // Central coefficient @@ -190,8 +190,8 @@ void LaplaceXYpetsc2::setCoefs(const Field2D& A, const Field2D& B) { BoutReal dy = 0.5 * (coords->dy()[index] + coords->dy()[ind_yp]); Acoef = 0.5 * (A[ind_yp] + A[index]); - BoutReal yp = -Acoef * J * g23 * g_23 - / (g_22 * coords->J()[index] * dy * coords->dy()[index]); + const BoutReal yp = -Acoef * J * g23 * g_23 + / (g_22 * coords->J()[index] * dy * coords->dy()[index]); c -= yp; matrix(index, ind_yp) = yp; @@ -203,8 +203,8 @@ void LaplaceXYpetsc2::setCoefs(const Field2D& A, const Field2D& B) { dy = 0.5 * (coords->dy()[index] + coords->dy()[ind_ym]); Acoef = 0.5 * (A[ind_ym] + A[index]); - BoutReal ym = -Acoef * J * g23 * g_23 - / (g_22 * coords->J()[index] * dy * coords->dy()[index]); + const BoutReal ym = -Acoef * J * g23 * g_23 + / (g_22 * coords->J()[index] * dy * coords->dy()[index]); c -= ym; matrix(index, ind_ym) = ym; } diff --git a/src/invert/laplacexz/impls/cyclic/laplacexz-cyclic.cxx b/src/invert/laplacexz/impls/cyclic/laplacexz-cyclic.cxx index 0a7030dec9..2048bb587f 100644 --- a/src/invert/laplacexz/impls/cyclic/laplacexz-cyclic.cxx +++ b/src/invert/laplacexz/impls/cyclic/laplacexz-cyclic.cxx @@ -5,11 +5,11 @@ #include #include +#include +#include #include #include -#include - LaplaceXZcyclic::LaplaceXZcyclic(Mesh* m, Options* options, const CELL_LOC loc) : LaplaceXZ(m, options, loc) { // Note: `m` may be nullptr, but localmesh is set in LaplaceXZ base constructor diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index 05cc515c9a..4ae9fcf1b4 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -1008,7 +1008,7 @@ void Coordinates::invalidateCellGeometryCaches() { _cell_volume.reset(); } -void Coordinates::invalidateAccessorCache() { CoordinatesAccessor::clear(this); } +void Coordinates::invalidateAccessorCache() const { CoordinatesAccessor::clear(this); } void Coordinates::invalidateJacobianCaches() { g_values_cache.reset(); diff --git a/src/mesh/coordinates_accessor.cxx b/src/mesh/coordinates_accessor.cxx index 0f11906c21..2c9eaf6de9 100644 --- a/src/mesh/coordinates_accessor.cxx +++ b/src/mesh/coordinates_accessor.cxx @@ -18,7 +18,7 @@ CoordinatesAccessor::CoordinatesAccessor(const Coordinates* coords) { ASSERT0(coords != nullptr); // Size of the mesh in Z. Used to convert 3D -> 2D index - Mesh* mesh = coords->dx().getMesh(); + const Mesh* mesh = coords->dx().getMesh(); mesh_nz = mesh->LocalNz; auto search = coords_store.find(coords); @@ -61,13 +61,15 @@ CoordinatesAccessor::CoordinatesAccessor(const Coordinates* coords) { COPY_STRIPE(J); if (coords->Bxy().isAllocated()) { - data[stripe_size * ind.ind + static_cast(Offset::B)] = coords->Bxy()[ind]; - if (coords->Bxy().yup().isAllocated()) + data[(stripe_size * ind.ind) + static_cast(Offset::B)] = coords->Bxy()[ind]; + if (coords->Bxy().yup().isAllocated()) { data[stripe_size * ind.ind + static_cast(Offset::Byup)] = coords->Bxy().yup()[ind]; - if (coords->Bxy().ydown().isAllocated()) + } + if (coords->Bxy().ydown().isAllocated()) { data[stripe_size * ind.ind + static_cast(Offset::Bydown)] = coords->Bxy().ydown()[ind]; + } } COPY_STRIPE(G1, G3); diff --git a/src/mesh/difops.cxx b/src/mesh/difops.cxx index 9a22efa67f..96793e2d7d 100644 --- a/src/mesh/difops.cxx +++ b/src/mesh/difops.cxx @@ -283,11 +283,6 @@ Field3D Div_par_flux(const Field3D& v, const Field3D& f, CELL_LOC outloc, return metric->Bxy() * FDDY(v, f_B, outloc, method) / sqrt(metric->g_22()); } -static Field3D Div_par_flux(const Field3D& v, const Field3D& f, const std::string& method, - CELL_LOC outloc) { - return Div_par_flux(v, f, outloc, method); -} - /******************************************************************************* * Grad2_par2 * second parallel derivative diff --git a/tests/MMS/GBS/gbs.cxx b/tests/MMS/GBS/gbs.cxx index ab23efbd6c..e2c42ead65 100644 --- a/tests/MMS/GBS/gbs.cxx +++ b/tests/MMS/GBS/gbs.cxx @@ -16,6 +16,7 @@ #include #include +#include #include #include #include diff --git a/tests/MMS/advection/advection.cxx b/tests/MMS/advection/advection.cxx index 6b8510ec83..2480fdb8c6 100644 --- a/tests/MMS/advection/advection.cxx +++ b/tests/MMS/advection/advection.cxx @@ -4,6 +4,7 @@ */ #include +#include #include #include diff --git a/tests/MMS/fieldalign/fieldalign.cxx b/tests/MMS/fieldalign/fieldalign.cxx index 16e9c50bc8..c5574f26b0 100644 --- a/tests/MMS/fieldalign/fieldalign.cxx +++ b/tests/MMS/fieldalign/fieldalign.cxx @@ -1,4 +1,5 @@ #include +#include #include class FieldAlign : public PhysicsModel { diff --git a/tests/MMS/tokamak/tokamak.cxx b/tests/MMS/tokamak/tokamak.cxx index fa89c9eb36..9648ddf7f5 100644 --- a/tests/MMS/tokamak/tokamak.cxx +++ b/tests/MMS/tokamak/tokamak.cxx @@ -8,6 +8,7 @@ #include #include +#include #include #include #include diff --git a/tests/MMS/wave-1d/wave.cxx b/tests/MMS/wave-1d/wave.cxx index 7c2506f3d8..b5cc343297 100644 --- a/tests/MMS/wave-1d/wave.cxx +++ b/tests/MMS/wave-1d/wave.cxx @@ -1,5 +1,6 @@ #include #include +#include #include #include #include diff --git a/tests/integrated/test-drift-instability/2fluid.cxx b/tests/integrated/test-drift-instability/2fluid.cxx index 59f3b22342..7af5834e8c 100644 --- a/tests/integrated/test-drift-instability/2fluid.cxx +++ b/tests/integrated/test-drift-instability/2fluid.cxx @@ -5,6 +5,7 @@ #include #include +#include #include #include #include diff --git a/tests/integrated/test-snb/test_snb.cxx b/tests/integrated/test-snb/test_snb.cxx index 68a2884a1e..40dfd4880d 100644 --- a/tests/integrated/test-snb/test_snb.cxx +++ b/tests/integrated/test-snb/test_snb.cxx @@ -215,8 +215,8 @@ int main(int argc, char** argv) { for (int y = mesh->ystart; y <= mesh->yend; y++) { const double y_n = (double(y) + 0.5) / double(mesh->yend + 1); - dy_copy(x, y) = 1. - 0.9 * y_n; - J_copy(x, y) = 1. + y_n * y_n; + dy_copy(x, y) = 1. - (0.9 * y_n); + J_copy(x, y) = 1. + (y_n * y_n); } } coord->setDy(dy_copy); From d546becb906d49d5fd50488c55c87fc25aff8bf9 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Thu, 9 Jul 2026 11:57:33 -0700 Subject: [PATCH 017/221] Div_par: Use Field3DParallel Convert input to Field3DParallel before dividing by Bxy, to ensure that yup/ydown fields are retained. --- src/mesh/difops.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/difops.cxx b/src/mesh/difops.cxx index 96793e2d7d..433316a793 100644 --- a/src/mesh/difops.cxx +++ b/src/mesh/difops.cxx @@ -216,7 +216,7 @@ Field3D Div_par(const Field3D& f, CELL_LOC outloc, const std::string& method) { // Need Bxy at location of f, which might be different from outloc const auto& Bxy_floc = f.getCoordinates()->Bxy(); - return Bxy_outloc * Grad_par(Field3D{f / Bxy_floc}, outloc, method); + return Bxy_outloc * Grad_par(Field3DParallel{f} / Bxy_floc, outloc, method); } Field3D Div_par(const Field3D& f, const Field3D& v) { From 0870329749ea1d59a211d1dae7a868df67fecd56 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Thu, 9 Jul 2026 13:25:34 -0700 Subject: [PATCH 018/221] Coordinates: Use communicate_no_slices Should now be consistent with `next` branch. Avoids calculation of parallel slices for derived metric quantities. --- src/mesh/coordinates.cxx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index 4ae9fcf1b4..01dbbb1ce1 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -496,6 +496,7 @@ void Coordinates::readFromMesh(Options* options, const std::string& suffix) { if (localmesh->get(J_temp, "J" + suffix, 0.0, false) != 0) { output_warn.write( "\tWARNING: Jacobian 'J' not found. Calculating from metric tensor\n"); + localmesh->communicate_no_slices(*jacobian_cache); } else { checkStaggeredGet(localmesh, "J", suffix); *jacobian_cache = ensuredUnaligned(J_temp); @@ -521,6 +522,7 @@ void Coordinates::readFromMesh(Options* options, const std::string& suffix) { "metric tensor\n"); Bxy_ = interpolateAndExtrapolate(Bcalc, location, extrapolate_x, extrapolate_y, false, transform.get()); + localmesh->communicate_no_slices(Bxy_); } else { checkStaggeredGet(localmesh, "Bxy", suffix); Bxy_ = ensuredUnaligned(Bxy_); @@ -784,7 +786,7 @@ void Coordinates::correctionForNonUniformMeshes(bool force_interpolate_from_cent output_warn.write("\tWARNING: differencing quantity 'd2z' not found. " "Calculating from dz\n"); d1_dz_ = bout::derivatives::index::DDZ(FieldMetric{1. / dz()}); - localmesh->communicate(d1_dz_); + localmesh->communicate_no_slices(d1_dz_); d1_dz_ = interpolateAndExtrapolate(d1_dz_, location, true, true, true, transform.get()); } else { @@ -799,7 +801,7 @@ void Coordinates::correctionForNonUniformMeshes(bool force_interpolate_from_cent d1_dz_ = 0; } - localmesh->communicate(d1_dx_, d1_dy_, d1_dz_); + localmesh->communicate_no_slices(d1_dx_, d1_dy_, d1_dz_); } Coordinates::FieldMetric Coordinates::recalculateJacobian() const { From 312bfc35e04be1407215200b7729124305a34861 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Thu, 9 Jul 2026 13:26:33 -0700 Subject: [PATCH 019/221] Coordinates: Getters use BOUT_OMP_SAFE Makes lazy getters thread-safe by adding named critical sections. Named so that nested sections (code in critical section calling a function that also contains a critical section) doesn't lead to deadlock. --- src/mesh/coordinates.cxx | 106 +++++++++++++++++++++++---------------- 1 file changed, 63 insertions(+), 43 deletions(-) diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index 01dbbb1ce1..80329536c7 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -931,65 +931,82 @@ void Coordinates::setParallelTransform(Options* options) { } const ChristoffelSymbols& Coordinates::christoffel_symbols() const { - if (christoffel_symbols_cache == nullptr) { - christoffel_symbols_cache = std::make_unique(*this); - // Set boundary guard cells of Christoffel symbol terms - // Ideally, when location is staggered, we would set the upper/outer boundary point - // correctly rather than by extrapolating here: e.g. if location==CELL_YLOW and we are - // at the upper y-boundary the x- and z-derivatives at yend+1 at the boundary can be - // calculated because the guard cells are available, while the y-derivative could be - // calculated from the CELL_CENTRE metric components (which have guard cells available - // past the boundary location). This would avoid the problem that the y-boundary on the - // CELL_YLOW grid is at a 'guard cell' location (yend+1). - // However, the above would require lots of special handling, so just extrapolate for - // now. - - christoffel_symbols_cache->map([this](const FieldMetric& component) { - return interpolateAndExtrapolate(component, location, true, true, false, - transform.get()); - }); + BOUT_OMP_SAFE(critical(christoffel_symbols_cache)) + { + if (christoffel_symbols_cache == nullptr) { + christoffel_symbols_cache = std::make_unique(*this); + // Set boundary guard cells of Christoffel symbol terms + // Ideally, when location is staggered, we would set the upper/outer boundary point + // correctly rather than by extrapolating here: e.g. if location==CELL_YLOW and we are + // at the upper y-boundary the x- and z-derivatives at yend+1 at the boundary can be + // calculated because the guard cells are available, while the y-derivative could be + // calculated from the CELL_CENTRE metric components (which have guard cells available + // past the boundary location). This would avoid the problem that the y-boundary on the + // CELL_YLOW grid is at a 'guard cell' location (yend+1). + // However, the above would require lots of special handling, so just extrapolate for + // now. + + christoffel_symbols_cache->map([this](const FieldMetric& component) { + return interpolateAndExtrapolate(component, location, true, true, false, + transform.get()); + }); + } } return *christoffel_symbols_cache; } GValues& Coordinates::g_values() const { - if (g_values_cache == nullptr) { - g_values_cache = std::make_unique(*this); - g_values_cache->map([this](const FieldMetric& component) { - return interpolateAndExtrapolate(component, location, true, true, true, - transform.get()); - }); + BOUT_OMP_SAFE(critical(g_values_cache)) + { + if (g_values_cache == nullptr) { + g_values_cache = std::make_unique(*this); + g_values_cache->map([this](const FieldMetric& component) { + return interpolateAndExtrapolate(component, location, true, true, true, + transform.get()); + }); + } } return *g_values_cache; } const Coordinates::FieldMetric& Coordinates::invSg() const { - if (invSgCache == nullptr) { - auto ptr = std::make_unique(); - (*ptr) = 1.0 / sqrt(g_22()); - invSgCache = std::move(ptr); + BOUT_OMP_SAFE(critical(invSg_cache)) + { + if (invSgCache == nullptr) { + auto ptr = std::make_unique(); + (*ptr) = 1.0 / sqrt(g_22()); + invSgCache = std::move(ptr); + } } return *invSgCache; } const Coordinates::FieldMetric& Coordinates::Grad2_par2_DDY_invSg(CELL_LOC outloc, const std::string& method) const { + const FieldMetric* result{nullptr}; + BOUT_OMP_SAFE(critical(Grad2_par2_DDY_invSg_cache)) + { + if (auto search = Grad2_par2_DDY_invSgCache.find(method); + search != Grad2_par2_DDY_invSgCache.end()) { + result = search->second.get(); + } else { + if (invSgCache == nullptr) { + auto ptr = std::make_unique(); + (*ptr) = 1.0 / sqrt(g_22()); + invSgCache = std::move(ptr); + } - if (auto search = Grad2_par2_DDY_invSgCache.find(method); - search != Grad2_par2_DDY_invSgCache.end()) { - return *search->second; - } - invSg(); - - // Communicate to get parallel slices - localmesh->communicate(*invSgCache); - invSgCache->applyParallelBoundary("parallel_neumann_o2"); + // Communicate to get parallel slices + localmesh->communicate(*invSgCache); + invSgCache->applyParallelBoundary("parallel_neumann_o2"); - // cache - auto ptr = std::make_unique(); - *ptr = DDY(*invSgCache, outloc, method) * invSg(); - Grad2_par2_DDY_invSgCache[method] = std::move(ptr); - return *Grad2_par2_DDY_invSgCache[method]; + auto ptr = std::make_unique(); + *ptr = DDY(*invSgCache, outloc, method) * (*invSgCache); + result = ptr.get(); + Grad2_par2_DDY_invSgCache[method] = std::move(ptr); + } + } + return *result; } void Coordinates::checkCovariant() { covariantMetricTensor.check(localmesh->ystart); } @@ -1029,8 +1046,11 @@ void Coordinates::invalidateMetricCaches() { } const Coordinates::FieldMetric& Coordinates::J() const { - if (jacobian_cache == nullptr) { - jacobian_cache = std::make_unique(recalculateJacobian()); + BOUT_OMP_SAFE(critical(jacobian_cache)) + { + if (jacobian_cache == nullptr) { + jacobian_cache = std::make_unique(recalculateJacobian()); + } } return *jacobian_cache; } From 23ddf98e76471e9b28be2be94ae4367c57d039c6 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Thu, 9 Jul 2026 13:48:40 -0700 Subject: [PATCH 020/221] Delete unneededed expr.hxx This was an experiment in expression templates, that has now been replaced by the BinaryExpr machinery. --- CMakeLists.txt | 1 - examples/performance/arithmetic/.gitignore | 1 - .../performance/arithmetic/arithmetic.cxx | 106 --------- examples/performance/arithmetic/data/BOUT.inp | 5 - examples/performance/arithmetic/run.sh | 5 - .../performance/arithmetic_3d2d/.gitignore | 1 - .../arithmetic_3d2d/arithmetic_3d2d.cxx | 116 ---------- .../performance/arithmetic_3d2d/data/BOUT.inp | 5 - examples/performance/arithmetic_3d2d/run.sh | 5 - include/bout/expr.hxx | 208 ------------------ 10 files changed, 453 deletions(-) delete mode 100644 examples/performance/arithmetic/.gitignore delete mode 100644 examples/performance/arithmetic/arithmetic.cxx delete mode 100644 examples/performance/arithmetic/data/BOUT.inp delete mode 100755 examples/performance/arithmetic/run.sh delete mode 100644 examples/performance/arithmetic_3d2d/.gitignore delete mode 100644 examples/performance/arithmetic_3d2d/arithmetic_3d2d.cxx delete mode 100644 examples/performance/arithmetic_3d2d/data/BOUT.inp delete mode 100644 examples/performance/arithmetic_3d2d/run.sh delete mode 100644 include/bout/expr.hxx diff --git a/CMakeLists.txt b/CMakeLists.txt index e6e424b55c..23919726cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,7 +128,6 @@ set(BOUT_SOURCES ./include/bout/deriv_store.hxx ./include/bout/derivs.hxx ./include/bout/difops.hxx - ./include/bout/expr.hxx ./include/bout/fft.hxx ./include/bout/field.hxx ./include/bout/field2d.hxx diff --git a/examples/performance/arithmetic/.gitignore b/examples/performance/arithmetic/.gitignore deleted file mode 100644 index 077be4cbd0..0000000000 --- a/examples/performance/arithmetic/.gitignore +++ /dev/null @@ -1 +0,0 @@ -arithmetic \ No newline at end of file diff --git a/examples/performance/arithmetic/arithmetic.cxx b/examples/performance/arithmetic/arithmetic.cxx deleted file mode 100644 index fc2357978a..0000000000 --- a/examples/performance/arithmetic/arithmetic.cxx +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Timing of arithmetic operations - * - */ - -#include - -#include - -#include - -using SteadyClock = std::chrono::time_point; -using Duration = std::chrono::duration; -using namespace std::chrono; - -#define TIMEIT(elapsed, ...) \ - { \ - SteadyClock start = steady_clock::now(); \ - { __VA_ARGS__; } \ - Duration diff = steady_clock::now() - start; \ - diff *= 1000 * 1000; \ - elapsed.min = diff > elapsed.min ? elapsed.min : diff; \ - elapsed.max = diff < elapsed.max ? elapsed.max : diff; \ - elapsed.count++; \ - elapsed.avg = elapsed.avg * (1 - 1. / elapsed.count) + diff / elapsed.count; \ - } - -struct Durations { - Duration max; - Duration min; - Duration avg; - int count; -}; - -class Arithmetic : public PhysicsModel { -protected: - int init(bool) { - - Field3D a = 1.0; - Field3D b = 2.0; - Field3D c = 3.0; - a.setRegion("RGN_ALL"); - b.setRegion("RGN_NOBNDRY"); - - Field3D result1, result2, result3, result4; - - // Using Field methods (classic operator overloading) - - result1 = 2. * a + b * c; -#define dur_init {Duration::min(), Duration::max(), Duration::zero(), 0} - Durations elapsed1 = dur_init, elapsed2 = dur_init, elapsed3 = dur_init, - elapsed4 = dur_init; - - for (int ik = 0; ik < 1e3; ++ik) { - TIMEIT(elapsed1, result1 = 2. * a + b * c;); - - // Using C loops - result2.allocate(); - BoutReal* rd = &result2(0, 0, 0); - BoutReal* ad = &a(0, 0, 0); - BoutReal* bd = &b(0, 0, 0); - BoutReal* cd = &c(0, 0, 0); - TIMEIT( - elapsed2, - for (int i = 0, iend = (mesh->LocalNx * mesh->LocalNy * mesh->LocalNz) - 1; - i != iend; i++) { - *rd = 2. * (*ad) + (*bd) * (*cd); - rd++; - ad++; - bd++; - cd++; - }); - - // Template expressions - TIMEIT(elapsed3, result3 = eval3D(add(mul(2, a), mul(b, c)));); - - // Range iterator - result4.allocate(); - TIMEIT(elapsed4, for (auto i : result4) result4[i] = 2. * a[i] + b[i] * c[i];); - } - - output.enable(); - output << "TIMING | minimum | mean | maximum\n" - << "----------- | ---------- | ---------- | ----------\n"; - //#define PRINT(str,elapsed) output << str << elapsed.min.count()<< - //elapsed.avg.count()<< elapsed.max.count() << endl; -#define PRINT(str, elapsed) \ - output.write("{:s} | {:7.3f} us | {:7.3f} us | {:7.3f} us\n", str, \ - elapsed.min.count(), elapsed.avg.count(), elapsed.max.count()) - PRINT("Fields: ", elapsed1); - PRINT("C loop: ", elapsed2); - PRINT("Templates: ", elapsed3); - PRINT("Range For: ", elapsed4); - output.disable(); - SOLVE_FOR(n); - return 0; - } - - int rhs(BoutReal) { - ddt(n) = 0; - return 0; - } - Field3D n; -}; - -BOUTMAIN(Arithmetic); diff --git a/examples/performance/arithmetic/data/BOUT.inp b/examples/performance/arithmetic/data/BOUT.inp deleted file mode 100644 index 0deb623c4b..0000000000 --- a/examples/performance/arithmetic/data/BOUT.inp +++ /dev/null @@ -1,5 +0,0 @@ -MZ = 1024 - -[mesh] -nx = 50 -ny = 2 diff --git a/examples/performance/arithmetic/run.sh b/examples/performance/arithmetic/run.sh deleted file mode 100755 index 3a1cc844a6..0000000000 --- a/examples/performance/arithmetic/run.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -make || exit - -./arithmetic -q -q -q diff --git a/examples/performance/arithmetic_3d2d/.gitignore b/examples/performance/arithmetic_3d2d/.gitignore deleted file mode 100644 index 14968af063..0000000000 --- a/examples/performance/arithmetic_3d2d/.gitignore +++ /dev/null @@ -1 +0,0 @@ -arithmetic_3d2d \ No newline at end of file diff --git a/examples/performance/arithmetic_3d2d/arithmetic_3d2d.cxx b/examples/performance/arithmetic_3d2d/arithmetic_3d2d.cxx deleted file mode 100644 index 83167a5b42..0000000000 --- a/examples/performance/arithmetic_3d2d/arithmetic_3d2d.cxx +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Timing of arithmetic operations (Field3D/Field2D mixed) - * - */ - -#include - -#include - -#include -#include -#include - -using SteadyClock = std::chrono::time_point; -using Duration = std::chrono::duration; -using namespace std::chrono; - -#define TIMEIT(NAME, ...) \ - { \ - SteadyClock start = steady_clock::now(); \ - __VA_ARGS__ \ - Duration diff = steady_clock::now() - start; \ - auto elapsed = elapsedMap[NAME]; \ - elapsed.min = std::min(diff, elapsed.min); \ - elapsed.max = std::max(diff, elapsed.max); \ - elapsed.count++; \ - elapsed.avg = elapsed.avg * (1 - 1. / elapsed.count) + diff / elapsed.count; \ - elapsedMap[NAME] = elapsed; \ - } - -struct Durations { - Duration max; - Duration min; - Duration avg; - int count; - Durations() - : max(Duration::min()), min(Duration::max()), avg(Duration::zero()), count(0){}; -}; - -class Arithmetic : public PhysicsModel { -protected: - std::map elapsedMap; - - int init(bool) { - Field3D a = 1.0; - Field3D b = 2.0; - Field2D c = 3.0; - - Field3D result1, result2, result3, result4; - - // Using Field methods (classic operator overloading) - result1 = 2. * a + b * c; - - for (int ik = 0; ik < 1e2; ++ik) { - result1.allocate(); - TIMEIT("Fields", result1 = 2. * a + b * c;); - - // Using C loops - result2.allocate(); - BoutReal* rd = &result2(0, 0, 0); - BoutReal* ad = &a(0, 0, 0); - BoutReal* bd = &b(0, 0, 0); - BoutReal* cd = &c(0, 0, 0); - TIMEIT( - "C loop", - for (int i = 0, iend = (mesh->LocalNx * mesh->LocalNy) - 1; i != iend; i++) { - for (int j = 0, jend = mesh->LocalNz - 1; j != jend; j++) { - *rd = 2. * (*ad) + (*bd) * (*cd); - rd++; - ad++; - bd++; - } - cd++; - }); - - // Template expressions - result3.allocate(); - TIMEIT("Templates", result3 = eval3D(add(mul(2, a), mul(b, c)));); - - // Range iterator - result4.allocate(); - TIMEIT("Range For", for (auto i : result4) result4[i] = 2. * a[i] + b[i] * c[i];); - } - - output.enable(); - constexpr int width = 15; - output << std::setw(width) << "TIMING"; - output << std::setw(width) << "min"; - output << std::setw(width) << "avg"; - output << std::setw(width) << "max"; - output << "\n======"; - for (int i = 0; i < 4 * width; ++i) { - output << "="; - }; - output << "\n"; - - for (const auto& approach : elapsedMap) { - output << std::setw(width) << approach.first; - output << std::setw(width) << approach.second.min.count(); - output << std::setw(width) << approach.second.avg.count(); - output << std::setw(width) << approach.second.max.count(); - output << "\n"; - } - output.disable(); - SOLVE_FOR(n); - return 0; - } - - int rhs(BoutReal) { - ddt(n) = 0; - return 0; - } - Field3D n; -}; - -BOUTMAIN(Arithmetic); diff --git a/examples/performance/arithmetic_3d2d/data/BOUT.inp b/examples/performance/arithmetic_3d2d/data/BOUT.inp deleted file mode 100644 index 0deb623c4b..0000000000 --- a/examples/performance/arithmetic_3d2d/data/BOUT.inp +++ /dev/null @@ -1,5 +0,0 @@ -MZ = 1024 - -[mesh] -nx = 50 -ny = 2 diff --git a/examples/performance/arithmetic_3d2d/run.sh b/examples/performance/arithmetic_3d2d/run.sh deleted file mode 100644 index ee36808c21..0000000000 --- a/examples/performance/arithmetic_3d2d/run.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -make || exit - -./arithmetic_3d2d -q -q -q diff --git a/include/bout/expr.hxx b/include/bout/expr.hxx deleted file mode 100644 index 267af202ed..0000000000 --- a/include/bout/expr.hxx +++ /dev/null @@ -1,208 +0,0 @@ -/************************************************************************** - * - * Operators, and support for template expressions - * - * Originally based on article by Klaus Kreft & Angelika Langer - * http://www.angelikalanger.com/Articles/Cuj/ExpressionTemplates/ExpressionTemplates.htm - * - * Parts adapted from Blitz++ library - * - **************************************************************************/ - -#ifndef BOUT_EXPR_H -#define BOUT_EXPR_H - -#warning expr.hxx is deprecated. Do not use! - -#include -#include -#include - -/// Literal class to capture BoutReal values in expressions -class Literal { -public: - /// Type of this expression - using type = Literal; - - Literal(BoutReal v) : val(v) {} - ~Literal() {} - BoutReal operator()(int x, int y, int z) const { return val; } - -private: - const BoutReal val; -}; - -class Field3DExpr { -public: - using type = Field3D; - - Field3DExpr(const Field3D& f) : data(&f(0, 0, 0)) {} - const BoutReal& operator()(int x, int y, int z) const { - return data[(x * bout::globals::mesh->LocalNy + y) * bout::globals::mesh->LocalNz - + z]; - } - -private: - const BoutReal* data; -}; - -class Field2DExpr { -public: - using type = Field2D; - - Field2DExpr(const Field2D& f) : data(&f(0, 0)) {} - const BoutReal& operator()(int x, int y, int z) const { - return data[x * bout::globals::mesh->LocalNy + y]; - } - -private: - const BoutReal* data; -}; - -/// Expression traits, to convert doubles etc. to Literal - -template -struct exprTraits { - using expr_type = ExprT; -}; - -template <> -struct exprTraits { - using expr_type = Literal; -}; - -template <> -struct exprTraits { - using expr_type = Literal; -}; - -template <> -struct exprTraits { - using expr_type = Literal; -}; - -/////////////////////////////////////////////// -// asExpr: convert objects to expressions - -template -struct asExpr { - using type = T; - static const T& getExpr(const T& x) { return x; } -}; - -template <> -struct asExpr { - using type = Literal; - static const Literal getExpr(const int& x) { return Literal(x); } -}; - -template <> -struct asExpr { - using type = Literal; - static const Literal getExpr(const double& x) { return Literal(x); } -}; - -template <> -struct asExpr { - using type = Literal; - static const Literal getExpr(const float& x) { return Literal(x); } -}; - -template <> -struct asExpr { - using type = Field3DExpr; - static const Field3DExpr getExpr(const Field3D& x) { return Field3DExpr(x); } -}; - -///////////////////////////////////////////////////////////// -// Type promotion. Work out the type of a calculation, -// based on the type of the arguments - -template // If in doubt, convert to Field3D -struct PromoteType { - using type = Field3D; -}; - -///////////////////////////////////////////////////////////// -// Binary expressions - -template -class BinaryExpr { -public: - BinaryExpr(const ExprT1& e1, const ExprT2& e2) : _expr1(e1), _expr2(e2) {} - - // Work out the type of the inputs - using ltype = typename exprTraits::expr_type; - using rtype = typename exprTraits::expr_type; - - /// Type of the resulting expression - using type = typename PromoteType::type; - - BoutReal operator()(int x, int y, int z) const { - return BinOp::apply((_expr1)(x, y, z), (_expr2)(x, y, z)); - } - -private: - ltype const _expr1; - rtype const _expr2; -}; - -template -struct BinaryResult { - using arg1 = typename asExpr::type; - using arg2 = typename asExpr::type; - using type = BinaryExpr; -}; - -/// Binary operator classes - -#define DEFINE_BINARY_OP(name, op) \ - struct name { \ - template \ - static inline T apply(T a, T b) { \ - return a op b; \ - } \ - }; - -DEFINE_BINARY_OP(Add, +) -DEFINE_BINARY_OP(Subtract, -) -DEFINE_BINARY_OP(Multiply, *) -DEFINE_BINARY_OP(Divide, /) - -struct Power { - template - static inline T apply(T a, T b) { - return pow(a, b); - } -}; - -/// Define functions add, mul which use operator structs -#define DEFINE_OVERLOAD_FUNC(name, func) \ - template \ - typename BinaryResult::type func(const ExprT1& e1, \ - const ExprT2& e2) { \ - using type = typename BinaryResult::type; \ - return type(asExpr::getExpr(e1), asExpr::getExpr(e2)); \ - } - -/// Addition of two Expressions -DEFINE_OVERLOAD_FUNC(Add, add); -/// Multiplication of two Expressions -DEFINE_OVERLOAD_FUNC(Multiply, mul); - -/// A function to evaluate expressions -template -const Field3D eval3D(Expr e) { - Field3D result; - result.allocate(); - for (int i = 0; i < bout::globals::mesh->LocalNx; i++) { - for (int j = 0; j < bout::globals::mesh->LocalNy; j++) { - for (int k = 0; k < bout::globals::mesh->LocalNz; k++) { - result(i, j, k) = e(i, j, k); - } - } - } - return result; -} - -#endif // BOUT_EXPR_H From 49774de6ffb6701bd26e8ef67434e7cb25435b1a Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Thu, 9 Jul 2026 20:47:10 -0700 Subject: [PATCH 021/221] Field3DParallel BinaryExpr templates Views handle yup/ydown slices, evaluating lazy expressions in parallel slices if assigned to a Field3DParallel. --- include/bout/bout_types.hxx | 4 + include/bout/field2d.hxx | 4 + include/bout/field3d.hxx | 147 ++++++++++++++++++++++------ include/bout/fieldops.hxx | 76 ++++++++++++++- include/bout/fieldperp.hxx | 4 + tests/unit/field/test_field3d.cxx | 155 ++++++++++++++++++++++++++++++ 6 files changed, 362 insertions(+), 28 deletions(-) diff --git a/include/bout/bout_types.hxx b/include/bout/bout_types.hxx index 7747b937b3..dee2ea22e7 100644 --- a/include/bout/bout_types.hxx +++ b/include/bout/bout_types.hxx @@ -149,6 +149,10 @@ struct Constant { T v; View(T v) : v(v) {} BOUT_HOST_DEVICE T operator()(int) const { return v; } + BOUT_HOST_DEVICE bool hasParallelSlices() const { return false; } + BOUT_HOST_DEVICE int numberParallelSlices() const { return 0; } + BOUT_HOST_DEVICE View yup(int = 0) const { return *this; } + BOUT_HOST_DEVICE View ydown(int = 0) const { return *this; } }; operator View() const { return {val}; } }; diff --git a/include/bout/field2d.hxx b/include/bout/field2d.hxx index 540680bb73..ce3009f4ad 100644 --- a/include/bout/field2d.hxx +++ b/include/bout/field2d.hxx @@ -345,6 +345,10 @@ public: this->div = div; return *this; } + BOUT_HOST_DEVICE BOUT_FORCEINLINE bool hasParallelSlices() const { return false; } + BOUT_HOST_DEVICE BOUT_FORCEINLINE int numberParallelSlices() const { return 0; } + BOUT_HOST_DEVICE BOUT_FORCEINLINE View yup(int = 0) const { return *this; } + BOUT_HOST_DEVICE BOUT_FORCEINLINE View ydown(int = 0) const { return *this; } }; operator View() { return View{&data[0]}; } operator View() const { return View{const_cast(&data[0])}; } diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index 905e736999..02e582c20a 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -465,6 +465,10 @@ public: struct View { BoutReal* data; + const Field3D* yup_fields{nullptr}; + const Field3D* ydown_fields{nullptr}; + int num_parallel_slices{0}; + BOUT_HOST_DEVICE BOUT_FORCEINLINE BoutReal operator()(int idx) const { return data[idx]; } @@ -478,9 +482,34 @@ public: "Field3D::View does not support setScale()"); return *this; } + BOUT_HOST_DEVICE BOUT_FORCEINLINE bool hasParallelSlices() const { + return num_parallel_slices > 0; + } + BOUT_HOST_DEVICE BOUT_FORCEINLINE int numberParallelSlices() const { + return num_parallel_slices; + } + BOUT_FORCEINLINE View yup(int slice = 0) const { + ASSERT2(slice < num_parallel_slices); + ASSERT2(yup_fields[slice].isAllocated()); + return static_cast(yup_fields[slice]); + } + BOUT_FORCEINLINE View ydown(int slice = 0) const { + ASSERT2(slice < num_parallel_slices); + ASSERT2(ydown_fields[slice].isAllocated()); + return static_cast(ydown_fields[slice]); + } }; - operator View() { return View{&data[0]}; } - operator View() const { return View{const_cast(&data[0])}; } + operator View() { + return View{&data[0], hasParallelSlices() ? yup_fields.data() : nullptr, + hasParallelSlices() ? ydown_fields.data() : nullptr, + static_cast(numberParallelSlices())}; + } + operator View() const { + return View{const_cast(&data[0]), + hasParallelSlices() ? yup_fields.data() : nullptr, + hasParallelSlices() ? ydown_fields.data() : nullptr, + static_cast(numberParallelSlices())}; + } //operator View() const { return View{&data[0]}; } ///////////////////////////////////////////////////////// @@ -856,31 +885,6 @@ if_else(bool condition, const L& lhs, const R& rhs) { rhs.getMesh()->getRegion("RGN_ALL")}; } -Field3DParallel operator+(const Field3D& lhs, const Field3DParallel& rhs); -Field3DParallel operator-(const Field3D& lhs, const Field3DParallel& rhs); -Field3DParallel operator*(const Field3D& lhs, const Field3DParallel& rhs); -Field3DParallel operator/(const Field3D& lhs, const Field3DParallel& rhs); - -Field3DParallel operator+(const Field3DParallel& lhs, const Field3D& rhs); -Field3DParallel operator-(const Field3DParallel& lhs, const Field3D& rhs); -Field3DParallel operator*(const Field3DParallel& lhs, const Field3D& rhs); -Field3DParallel operator/(const Field3DParallel& lhs, const Field3D& rhs); - -Field3DParallel operator+(const Field3DParallel& lhs, const Field3DParallel& rhs); -Field3DParallel operator-(const Field3DParallel& lhs, const Field3DParallel& rhs); -Field3DParallel operator*(const Field3DParallel& lhs, const Field3DParallel& rhs); -Field3DParallel operator/(const Field3DParallel& lhs, const Field3DParallel& rhs); - -Field3DParallel operator+(BoutReal lhs, const Field3DParallel& rhs); -Field3DParallel operator-(BoutReal lhs, const Field3DParallel& rhs); -Field3DParallel operator*(BoutReal lhs, const Field3DParallel& rhs); -Field3DParallel operator/(BoutReal lhs, const Field3DParallel& rhs); - -Field3DParallel operator+(const Field3DParallel& lhs, BoutReal rhs); -Field3DParallel operator-(const Field3DParallel& lhs, BoutReal rhs); -Field3DParallel operator*(const Field3DParallel& lhs, BoutReal rhs); -Field3DParallel operator/(const Field3DParallel& lhs, BoutReal rhs); - /*! * Unary minus. Returns the negative of given field, * iterates over whole domain including guard/boundary cells. @@ -1024,6 +1028,13 @@ public: explicit Field3DParallel(Types... args) : Field3D(std::move(args)...) { ensureFieldAligned(); } + template || is_expr_field3d_v>> + Field3DParallel(const BinaryExpr& expr) + : Field3DParallel(expr.getMesh(), expr.getLocation(), expr.getDirections(), + expr.getRegionID()) { + *this = expr; + } Field3DParallel(const Field3D& f) : Field3D(f) { ensureFieldAligned(); } Field3DParallel(const Field3D& f, bool isRef) : Field3D(f), isRef(isRef) { ensureFieldAligned(); @@ -1052,6 +1063,53 @@ public: Field3D& asField3D() { return *this; } const Field3D& asField3D() const { return *this; } + struct View { + Field3D::View base; + const Field3D* yup_fields{nullptr}; + const Field3D* ydown_fields{nullptr}; + int num_parallel_slices{0}; + + BOUT_HOST_DEVICE BOUT_FORCEINLINE BoutReal operator()(int idx) const { + return base(idx); + } + BOUT_HOST_DEVICE BOUT_FORCEINLINE BoutReal& operator[](int idx) const { + return base[idx]; + } + + template + View& setScale(Mul /*unused*/, Div /*unused*/) { + static_assert(!std::is_same_v, + "Field3DParallel::View does not support setScale()"); + return *this; + } + + BOUT_FORCEINLINE bool hasParallelSlices() const { return num_parallel_slices > 0; } + BOUT_FORCEINLINE int numberParallelSlices() const { return num_parallel_slices; } + BOUT_FORCEINLINE View yup(int slice = 0) const { + ASSERT2(slice < num_parallel_slices); + ASSERT2(yup_fields[slice].isAllocated()); + return View{static_cast(yup_fields[slice]), nullptr, nullptr, 0}; + } + BOUT_FORCEINLINE View ydown(int slice = 0) const { + ASSERT2(slice < num_parallel_slices); + ASSERT2(ydown_fields[slice].isAllocated()); + return View{static_cast(ydown_fields[slice]), nullptr, nullptr, 0}; + } + }; + + operator View() { + return View{static_cast(*this), + hasParallelSlices() ? yup_fields.data() : nullptr, + hasParallelSlices() ? ydown_fields.data() : nullptr, + static_cast(numberParallelSlices())}; + } + operator View() const { + return View{static_cast(*this), + hasParallelSlices() ? yup_fields.data() : nullptr, + hasParallelSlices() ? ydown_fields.data() : nullptr, + static_cast(numberParallelSlices())}; + } + Field3DParallel& operator*=(const Field3D&); Field3DParallel& operator/=(const Field3D&); Field3DParallel& operator+=(const Field3D&); @@ -1074,6 +1132,41 @@ public: ensureFieldAligned(); return *this; } + template + std::enable_if_t || is_expr_field3d_v, Field3DParallel&> + operator=(const BinaryExpr& expr) { + if (getMesh() != expr.getMesh()) { + clearParallelSlices(); + fieldmesh = expr.getMesh(); + data = Array{}; + } + if (isFci()) { + if (!hasParallelSlices()) { + splitParallelSlices(); + } + } else if (hasParallelSlices()) { + clearParallelSlices(); + } + + setRegion(expr.getRegionID()); + setLocation(expr.getLocation()); + setDirections(expr.getDirections()); + allocate(); + expr.evaluate(static_cast(*this).data); + + if (isFci()) { + ASSERT2(expr.hasParallelSlices()); + ASSERT2(expr.numberParallelSlices() == static_cast(numberParallelSlices())); + for (int i = 0; i < expr.numberParallelSlices(); ++i) { + yup(i).allocate(); + ydown(i).allocate(); + expr.yup(i).evaluate(static_cast(yup(i)).data); + expr.ydown(i).evaluate(static_cast(ydown(i)).data); + } + } + + return *this; + } Field3DParallel& operator=(BoutReal); Field3DParallel& allocate(); diff --git a/include/bout/fieldops.hxx b/include/bout/fieldops.hxx index f36061ceaa..a09734ad21 100644 --- a/include/bout/fieldops.hxx +++ b/include/bout/fieldops.hxx @@ -381,6 +381,43 @@ struct BinaryExpr { } BOUT_HOST_DEVICE BOUT_FORCEINLINE int regionIdx(int idx) const { return indices[idx]; } + bool hasParallelSlices() const { + if constexpr (is_expr_constant_v && is_expr_constant_v) { + return false; + } else if constexpr (is_expr_constant_v) { + return rhs.hasParallelSlices(); + } else if constexpr (is_expr_constant_v) { + return lhs.hasParallelSlices(); + } else { + return lhs.hasParallelSlices() && rhs.hasParallelSlices(); + } + } + int numberParallelSlices() const { + if (!hasParallelSlices()) { + return 0; + } + if constexpr (is_expr_constant_v && is_expr_constant_v) { + return 0; + } else if constexpr (is_expr_constant_v) { + return rhs.numberParallelSlices(); + } else if constexpr (is_expr_constant_v) { + return lhs.numberParallelSlices(); + } else { + ASSERT2(lhs.numberParallelSlices() == rhs.numberParallelSlices()); + return lhs.numberParallelSlices(); + } + } + auto yup(int slice = 0) const { + return BinaryExpr{lhs.yup(slice), rhs.yup(slice), f, + mesh, location, directions, + regionID, indices, yindex}; + } + auto ydown(int slice = 0) const { + return BinaryExpr{ + lhs.ydown(slice), rhs.ydown(slice), f, mesh, location, + directions, regionID, indices, yindex}; + } + //operator ResT() { return ResT{*this}; } struct View { typename L::View lhs; @@ -396,13 +433,50 @@ struct BinaryExpr { this->div = div; return *this; } + BOUT_HOST_DEVICE BOUT_FORCEINLINE bool hasParallelSlices() const { + if constexpr (is_expr_constant_v && is_expr_constant_v) { + return false; + } else if constexpr (is_expr_constant_v) { + return rhs.hasParallelSlices(); + } else if constexpr (is_expr_constant_v) { + return lhs.hasParallelSlices(); + } else { + return lhs.hasParallelSlices() && rhs.hasParallelSlices(); + } + } + BOUT_HOST_DEVICE BOUT_FORCEINLINE int numberParallelSlices() const { + if (!hasParallelSlices()) { + return 0; + } + if constexpr (is_expr_constant_v && is_expr_constant_v) { + return 0; + } else if constexpr (is_expr_constant_v) { + return rhs.numberParallelSlices(); + } else if constexpr (is_expr_constant_v) { + return lhs.numberParallelSlices(); + } else { + ASSERT2(lhs.numberParallelSlices() == rhs.numberParallelSlices()); + return lhs.numberParallelSlices(); + } + } + BOUT_HOST_DEVICE BOUT_FORCEINLINE auto yup(int slice = 0) const { + auto result = *this; + result.lhs = lhs.yup(slice); + result.rhs = rhs.yup(slice); + return result; + } + BOUT_HOST_DEVICE BOUT_FORCEINLINE auto ydown(int slice = 0) const { + auto result = *this; + result.lhs = lhs.ydown(slice); + result.rhs = rhs.ydown(slice); + return result; + } BOUT_HOST_DEVICE BOUT_FORCEINLINE int size() const { return num_indices; } BOUT_HOST_DEVICE BOUT_FORCEINLINE int regionIdx(int idx) const { return indices[idx]; } BOUT_HOST_DEVICE BOUT_FORCEINLINE BoutReal operator()(int idx) const { return f((idx * mul) / div, lhs, rhs); // single‐pass fusion - //return f(lhs((idx * mul) / div), rhs((idx * mul) / div)); // single‐pass fusion } }; diff --git a/include/bout/fieldperp.hxx b/include/bout/fieldperp.hxx index 36a116e1b5..83bb3b79b0 100644 --- a/include/bout/fieldperp.hxx +++ b/include/bout/fieldperp.hxx @@ -352,6 +352,10 @@ public: this->div = div; return *this; } + BOUT_HOST_DEVICE BOUT_FORCEINLINE bool hasParallelSlices() const { return false; } + BOUT_HOST_DEVICE BOUT_FORCEINLINE int numberParallelSlices() const { return 0; } + BOUT_HOST_DEVICE BOUT_FORCEINLINE View yup(int = 0) const { return *this; } + BOUT_HOST_DEVICE BOUT_FORCEINLINE View ydown(int = 0) const { return *this; } }; operator View() { return View{&data[0]}; } operator View() const { return View{const_cast(&data[0])}; } diff --git a/tests/unit/field/test_field3d.cxx b/tests/unit/field/test_field3d.cxx index 905b182018..f0b672f324 100644 --- a/tests/unit/field/test_field3d.cxx +++ b/tests/unit/field/test_field3d.cxx @@ -12,6 +12,7 @@ #include "bout/field3d.hxx" #include "bout/mesh.hxx" #include "bout/output.hxx" +#include "bout/paralleltransform.hxx" #include "bout/unused.hxx" #include "bout/utils.hxx" @@ -27,6 +28,34 @@ using namespace bout::globals; // Reuse the "standard" fixture for FakeMesh using Field3DTest = FakeMeshFixture; +class MockFciParallelTransform : public ParallelTransform { +public: + explicit MockFciParallelTransform(Mesh& mesh_in) : ParallelTransform(mesh_in) {} + + void calcParallelSlices(Field3D& f) override { + if (!f.hasParallelSlices()) { + f.splitParallelSlices(); + } + for (size_t i = 0; i < f.numberParallelSlices(); ++i) { + f.yup(i) = f; + f.ydown(i) = f; + } + } + + Field3D toFieldAligned(const Field3D& f, const std::string&) override { return f; } + FieldPerp toFieldAligned(const FieldPerp& f, const std::string&) override { return f; } + Field3D fromFieldAligned(const Field3D& f, const std::string&) override { return f; } + FieldPerp fromFieldAligned(const FieldPerp& f, const std::string&) override { + return f; + } + + bool canToFromFieldAligned() const override { return false; } + bool requiresTwistShift(bool, YDirectionType) override { return false; } + +protected: + void checkInputGrid() override {} +}; + TEST_F(Field3DTest, Is3D) { Field3D field; @@ -1979,6 +2008,132 @@ TEST_F(Field3DTest, SQField3DParallelPreservesParallelSlices) { EXPECT_TRUE(IsFieldEqual(squared.ydown(), 16.0)); } +TEST_F(Field3DTest, Field3DParallelArithmeticReturnsLazyExpr) { + Field3DParallel parallel; + Field3D field; + + parallel = 2.0; + parallel.splitParallelSlices(); + parallel.yup() = 3.0; + parallel.ydown() = 4.0; + field = 5.0; + + const auto expr = parallel + field; + + EXPECT_TRUE( + (std::is_same_v, + BinaryExpr>)); + + Field3DParallel result{expr}; + + EXPECT_FALSE(result.hasParallelSlices()); + EXPECT_TRUE(IsFieldEqual(result, 7.0)); +} + +TEST_F(Field3DTest, Field3DParallelAssignmentFromLazyExprDiscardsSlicesWhenNotFci) { + Field3DParallel parallel; + Field3DParallel result; + + parallel = 2.0; + parallel.splitParallelSlices(); + parallel.yup() = 3.0; + parallel.ydown() = 4.0; + + const auto expr = 10.0 - parallel; + + EXPECT_TRUE((std::is_same_v< + std::decay_t, + BinaryExpr, Field3DParallel, bout::op::Sub>>)); + + result = expr; + + EXPECT_FALSE(result.hasParallelSlices()); + EXPECT_TRUE(IsFieldEqual(result, 8.0)); +} + +#if CHECK >= 2 +TEST_F(Field3DTest, Field3DParallelAssignmentFromLazyExprRequiresField3DSlicesInFci) { + mesh->getCoordinates()->setParallelTransform( + bout::utils::make_unique(*mesh)); + + Field3DParallel parallel; + Field3D field; + + parallel = 2.0; + parallel.yup() = 3.0; + parallel.ydown() = 4.0; + field = 5.0; + + EXPECT_THROW(Field3DParallel result{parallel + field}, BoutException); +} +#endif + +TEST_F(Field3DTest, Field3DParallelAssignmentFromLazyExprUsesField3DSlicesInFci) { + mesh->getCoordinates()->setParallelTransform( + bout::utils::make_unique(*mesh)); + + Field3DParallel parallel; + Field3D field; + + parallel = 2.0; + parallel.yup() = 3.0; + parallel.ydown() = 4.0; + + field = 5.0; + field.splitParallelSlices(); + field.yup() = 7.0; + field.ydown() = 11.0; + + Field3DParallel result{parallel + field}; + + EXPECT_TRUE(result.hasParallelSlices()); + EXPECT_TRUE(IsFieldEqual(result, 7.0)); + EXPECT_TRUE(IsFieldEqual(result.yup(), 10.0)); + EXPECT_TRUE(IsFieldEqual(result.ydown(), 15.0)); +} + +TEST_F(Field3DTest, Field3DParallelAssignmentFromField3DExprUsesSlicesInFci) { + mesh->getCoordinates()->setParallelTransform( + bout::utils::make_unique(*mesh)); + + Field3D lhs; + Field3D rhs; + + lhs = 2.0; + lhs.splitParallelSlices(); + lhs.yup() = 3.0; + lhs.ydown() = 4.0; + + rhs = 5.0; + rhs.splitParallelSlices(); + rhs.yup() = 7.0; + rhs.ydown() = 11.0; + + Field3DParallel result{lhs * rhs}; + + EXPECT_TRUE(result.hasParallelSlices()); + EXPECT_TRUE(IsFieldEqual(result, 10.0)); + EXPECT_TRUE(IsFieldEqual(result.yup(), 21.0)); + EXPECT_TRUE(IsFieldEqual(result.ydown(), 44.0)); +} + +TEST_F(Field3DTest, Field3DParallelAssignmentFromScalarExprUsesSlicesInFci) { + mesh->getCoordinates()->setParallelTransform( + bout::utils::make_unique(*mesh)); + + Field3DParallel parallel; + parallel = 2.0; + parallel.yup() = 3.0; + parallel.ydown() = 4.0; + + Field3DParallel result{parallel + 1.0}; + + EXPECT_TRUE(result.hasParallelSlices()); + EXPECT_TRUE(IsFieldEqual(result, 3.0)); + EXPECT_TRUE(IsFieldEqual(result.yup(), 4.0)); + EXPECT_TRUE(IsFieldEqual(result.ydown(), 5.0)); +} + TEST_F(Field3DTest, Abs) { Field3D field; From 470abe40140ca1e1a65634676fabfc6e8fdd27f6 Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 8 Jul 2026 14:21:53 +0200 Subject: [PATCH 022/221] Add unit test to ensure Field3DParallel works for products with FCI --- tests/unit/field/test_field3d.cxx | 936 +++++++++++++++--------------- 1 file changed, 484 insertions(+), 452 deletions(-) diff --git a/tests/unit/field/test_field3d.cxx b/tests/unit/field/test_field3d.cxx index f0b672f324..89286a8f80 100644 --- a/tests/unit/field/test_field3d.cxx +++ b/tests/unit/field/test_field3d.cxx @@ -27,6 +27,7 @@ using namespace bout::globals; // Reuse the "standard" fixture for FakeMesh using Field3DTest = FakeMeshFixture; +using Field3DTestFCI = FakeMeshFixture_tmpl<3, 5, 7, true>; class MockFciParallelTransform : public ParallelTransform { public: @@ -2132,618 +2133,649 @@ TEST_F(Field3DTest, Field3DParallelAssignmentFromScalarExprUsesSlicesInFci) { EXPECT_TRUE(IsFieldEqual(result, 3.0)); EXPECT_TRUE(IsFieldEqual(result.yup(), 4.0)); EXPECT_TRUE(IsFieldEqual(result.ydown(), 5.0)); -} -TEST_F(Field3DTest, Abs) { - Field3D field; + TEST_F(Field3DTestFCI, MulField3DParallelPreservesParallelSlices) { + Field3D field; + EXPECT_TRUE(field.isFci()); + + field = 2.0; + field.splitParallelSlices(); + field.yup() = 3.0; + field.ydown() = 4.0; + field.resetRegionParallel(); + + Field3D rhs; + EXPECT_TRUE(rhs.isFci()); + rhs = 3.0; + rhs.splitParallelSlices(); + rhs.yup() = 4.0; + rhs.ydown() = 5.0; + rhs.resetRegionParallel(); + + const Field3D prod = field * rhs; + + EXPECT_FALSE(prod.hasParallelSlices()); + EXPECT_TRUE(IsFieldEqual(prod, 6.0)); + + const Field3DParallel prodpar = field.asField3DParallel() * rhs; + EXPECT_TRUE((std::is_same_v, Field3DParallel>)); + EXPECT_TRUE(prodpar.hasParallelSlices()); + EXPECT_TRUE(IsFieldEqual(prodpar, 6.0)); + EXPECT_TRUE(IsFieldEqual(prodpar.yup(), 12.0, "RGN_YPAR_+1")); + EXPECT_TRUE(IsFieldEqual(prodpar.ydown(), 20.0, "RGN_YPAR_-1")); + } - field = -31.0; - EXPECT_TRUE(IsFieldEqual(abs(field), 31.0)); -} + TEST_F(Field3DTest, Abs) { + Field3D field; -TEST_F(Field3DTest, AbsExpressionUsesAbsOp) { - Field3D field; + field = -31.0; + EXPECT_TRUE(IsFieldEqual(abs(field), 31.0)); + } - field = -2.0; - const auto expr = field + 1.0; + TEST_F(Field3DTest, AbsExpressionUsesAbsOp) { + Field3D field; - EXPECT_TRUE((std::is_same_v, - BinaryExpr, - std::decay_t, bout::op::abs>>)); - EXPECT_TRUE(IsFieldEqual(abs(expr), 1.0)); - EXPECT_TRUE(IsFieldEqual(abs(expr, "RGN_ALL"), 1.0)); -} + field = -2.0; + const auto expr = field + 1.0; -TEST_F(Field3DTest, RegionLimitedExpressionConstructsField3D) { - Field3D field; + EXPECT_TRUE( + (std::is_same_v, + BinaryExpr, + std::decay_t, bout::op::abs>>)); + EXPECT_TRUE(IsFieldEqual(abs(expr), 1.0)); + EXPECT_TRUE(IsFieldEqual(abs(expr, "RGN_ALL"), 1.0)); + } - field = -31.0; + TEST_F(Field3DTest, RegionLimitedExpressionConstructsField3D) { + Field3D field; - Field3D result = abs(field, "RGN_NOBNDRY"); + field = -31.0; - EXPECT_TRUE(IsFieldEqual(result, 31.0, "RGN_NOBNDRY")); -} + Field3D result = abs(field, "RGN_NOBNDRY"); -TEST_F(Field3DTest, Exp) { - Field3D field; + EXPECT_TRUE(IsFieldEqual(result, 31.0, "RGN_NOBNDRY")); + } - field = 2.5; - const BoutReal expected = 12.182493960703473; - EXPECT_TRUE(IsFieldEqual(exp(field), expected)); -} + TEST_F(Field3DTest, Exp) { + Field3D field; -TEST_F(Field3DTest, Log) { - Field3D field; + field = 2.5; + const BoutReal expected = 12.182493960703473; + EXPECT_TRUE(IsFieldEqual(exp(field), expected)); + } - field = 12.182493960703473; - const BoutReal expected = 2.5; - EXPECT_TRUE(IsFieldEqual(log(field), expected)); -} + TEST_F(Field3DTest, Log) { + Field3D field; -TEST_F(Field3DTest, LogExp) { - Field3D field; + field = 12.182493960703473; + const BoutReal expected = 2.5; + EXPECT_TRUE(IsFieldEqual(log(field), expected)); + } - field = 2.5; - const BoutReal expected = 2.5; - EXPECT_TRUE(IsFieldEqual(log(exp(field)), expected)); -} + TEST_F(Field3DTest, LogExp) { + Field3D field; -TEST_F(Field3DTest, Sin) { - Field3D field; + field = 2.5; + const BoutReal expected = 2.5; + EXPECT_TRUE(IsFieldEqual(log(exp(field)), expected)); + } - field = PI / 2.0; - EXPECT_TRUE(IsFieldEqual(sin(field), 1.0)); + TEST_F(Field3DTest, Sin) { + Field3D field; - field = PI; - EXPECT_TRUE(IsFieldEqual(sin(field), 0.0)); -} + field = PI / 2.0; + EXPECT_TRUE(IsFieldEqual(sin(field), 1.0)); -TEST_F(Field3DTest, Cos) { - Field3D field; + field = PI; + EXPECT_TRUE(IsFieldEqual(sin(field), 0.0)); + } - field = PI / 2.0; - EXPECT_TRUE(IsFieldEqual(cos(field), 0.0)); + TEST_F(Field3DTest, Cos) { + Field3D field; - field = PI; - EXPECT_TRUE(IsFieldEqual(cos(field), -1.0)); -} + field = PI / 2.0; + EXPECT_TRUE(IsFieldEqual(cos(field), 0.0)); -TEST_F(Field3DTest, Tan) { - Field3D field; + field = PI; + EXPECT_TRUE(IsFieldEqual(cos(field), -1.0)); + } - field = PI / 4.0; - EXPECT_TRUE(IsFieldEqual(tan(field), 1.0)); + TEST_F(Field3DTest, Tan) { + Field3D field; - field = PI; - EXPECT_TRUE(IsFieldEqual(tan(field), 0.0)); -} + field = PI / 4.0; + EXPECT_TRUE(IsFieldEqual(tan(field), 1.0)); -TEST_F(Field3DTest, Sinh) { - Field3D field; + field = PI; + EXPECT_TRUE(IsFieldEqual(tan(field), 0.0)); + } - field = 1.0; - const BoutReal expected = 1.1752011936438014; - EXPECT_TRUE(IsFieldEqual(sinh(field), expected)); + TEST_F(Field3DTest, Sinh) { + Field3D field; - field = -1.0; - EXPECT_TRUE(IsFieldEqual(sinh(field), -expected)); -} + field = 1.0; + const BoutReal expected = 1.1752011936438014; + EXPECT_TRUE(IsFieldEqual(sinh(field), expected)); -TEST_F(Field3DTest, Cosh) { - Field3D field; + field = -1.0; + EXPECT_TRUE(IsFieldEqual(sinh(field), -expected)); + } - field = 1.0; - const BoutReal expected = 1.5430806348152437; - EXPECT_TRUE(IsFieldEqual(cosh(field), expected)); + TEST_F(Field3DTest, Cosh) { + Field3D field; - field = -1.0; - EXPECT_TRUE(IsFieldEqual(cosh(field), expected)); -} + field = 1.0; + const BoutReal expected = 1.5430806348152437; + EXPECT_TRUE(IsFieldEqual(cosh(field), expected)); -TEST_F(Field3DTest, Tanh) { - Field3D field; + field = -1.0; + EXPECT_TRUE(IsFieldEqual(cosh(field), expected)); + } - field = 1.0; - const BoutReal expected = 0.761594155955764; - EXPECT_TRUE(IsFieldEqual(tanh(field), expected)); + TEST_F(Field3DTest, Tanh) { + Field3D field; - field = -1.0; - EXPECT_TRUE(IsFieldEqual(tanh(field), -expected)); -} + field = 1.0; + const BoutReal expected = 0.761594155955764; + EXPECT_TRUE(IsFieldEqual(tanh(field), expected)); -TEST_F(Field3DTest, Floor) { - Field3D field; + field = -1.0; + EXPECT_TRUE(IsFieldEqual(tanh(field), -expected)); + } - field = 50.0; - field(1, 1, 1) = 49.9; - field(2, 3, 4) = -20; + TEST_F(Field3DTest, Floor) { + Field3D field; - const BoutReal floor_value = 50.0; + field = 50.0; + field(1, 1, 1) = 49.9; + field(2, 3, 4) = -20; - EXPECT_TRUE(IsFieldEqual(floor(field, floor_value), floor_value)); -} + const BoutReal floor_value = 50.0; -TEST_F(Field3DTest, Min) { - Field3D field; + EXPECT_TRUE(IsFieldEqual(floor(field, floor_value), floor_value)); + } - field = 50.0; - field(0, 0, 0) = -99.0; - field(1, 1, 1) = 60.0; - field(1, 2, 2) = 40.0; - field(2, 4, 3) = 99.0; + TEST_F(Field3DTest, Min) { + Field3D field; - // min doesn't include guard cells - const BoutReal min_value = 40.0; + field = 50.0; + field(0, 0, 0) = -99.0; + field(1, 1, 1) = 60.0; + field(1, 2, 2) = 40.0; + field(2, 4, 3) = 99.0; - EXPECT_EQ(min(field, false), min_value); - EXPECT_EQ(min(field, false, "RGN_ALL"), -99.0); - EXPECT_EQ(min(field, true, "RGN_ALL"), -99.0); -} + // min doesn't include guard cells + const BoutReal min_value = 40.0; -TEST_F(Field3DTest, MinBinaryExpr) { - Field3D field; + EXPECT_EQ(min(field, false), min_value); + EXPECT_EQ(min(field, false, "RGN_ALL"), -99.0); + EXPECT_EQ(min(field, true, "RGN_ALL"), -99.0); + } - field = 50.0; - field(0, 0, 0) = -99.0; - field(1, 1, 1) = 60.0; - field(1, 2, 2) = 40.0; - field(2, 4, 3) = 99.0; + TEST_F(Field3DTest, MinBinaryExpr) { + Field3D field; - const auto expr = field / 2.0 - 5.0; + field = 50.0; + field(0, 0, 0) = -99.0; + field(1, 1, 1) = 60.0; + field(1, 2, 2) = 40.0; + field(2, 4, 3) = 99.0; - EXPECT_EQ(min(expr, false), 15.0); - EXPECT_EQ(min(expr, false, "RGN_ALL"), -54.5); -} + const auto expr = field / 2.0 - 5.0; -TEST_F(Field3DTest, Max) { - Field3D field; + EXPECT_EQ(min(expr, false), 15.0); + EXPECT_EQ(min(expr, false, "RGN_ALL"), -54.5); + } - field = 50.0; - field(0, 0, 0) = -99.0; - field(1, 1, 1) = 40.0; - field(1, 2, 2) = 60.0; - field(2, 4, 3) = 99.0; + TEST_F(Field3DTest, Max) { + Field3D field; - // max doesn't include guard cells - const BoutReal max_value = 60.0; + field = 50.0; + field(0, 0, 0) = -99.0; + field(1, 1, 1) = 40.0; + field(1, 2, 2) = 60.0; + field(2, 4, 3) = 99.0; - EXPECT_EQ(max(field, false), max_value); - EXPECT_EQ(max(field, false, "RGN_ALL"), 99.0); - EXPECT_EQ(max(field, true, "RGN_ALL"), 99.0); -} + // max doesn't include guard cells + const BoutReal max_value = 60.0; -TEST_F(Field3DTest, MaxBinaryExpr) { - Field3D field; + EXPECT_EQ(max(field, false), max_value); + EXPECT_EQ(max(field, false, "RGN_ALL"), 99.0); + EXPECT_EQ(max(field, true, "RGN_ALL"), 99.0); + } - field = 50.0; - field(0, 0, 0) = -99.0; - field(1, 1, 1) = 40.0; - field(1, 2, 2) = 60.0; - field(2, 4, 3) = 99.0; + TEST_F(Field3DTest, MaxBinaryExpr) { + Field3D field; - const auto expr = field / 2.0 - 5.0; + field = 50.0; + field(0, 0, 0) = -99.0; + field(1, 1, 1) = 40.0; + field(1, 2, 2) = 60.0; + field(2, 4, 3) = 99.0; - EXPECT_EQ(max(expr, false), 25.0); - EXPECT_EQ(max(expr, false, "RGN_ALL"), 44.5); -} + const auto expr = field / 2.0 - 5.0; -TEST_F(Field3DTest, Mean) { - Field3D field; + EXPECT_EQ(max(expr, false), 25.0); + EXPECT_EQ(max(expr, false, "RGN_ALL"), 44.5); + } - field = 50.0; - field(0, 0, 0) = 1.0; - field(1, 1, 1) = 40.0; - field(1, 2, 2) = 60.0; - field(2, 4, 3) = 109.0; + TEST_F(Field3DTest, Mean) { + Field3D field; - // mean doesn't include guard cells by default - const int npoints_all = nx * ny * nz; - const BoutReal mean_value_nobndry = 50.0; - const BoutReal mean_value_all = 50.0 + 10.0 / npoints_all; + field = 50.0; + field(0, 0, 0) = 1.0; + field(1, 1, 1) = 40.0; + field(1, 2, 2) = 60.0; + field(2, 4, 3) = 109.0; - EXPECT_EQ(mean(field, false), mean_value_nobndry); - EXPECT_EQ(mean(field, false, "RGN_ALL"), mean_value_all); - EXPECT_EQ(mean(field, true, "RGN_ALL"), mean_value_all); -} + // mean doesn't include guard cells by default + const int npoints_all = nx * ny * nz; + const BoutReal mean_value_nobndry = 50.0; + const BoutReal mean_value_all = 50.0 + 10.0 / npoints_all; -TEST_F(Field3DTest, MeanBinaryExpr) { - Field3D field; - - field = 50.0; - field(0, 0, 0) = 1.0; - field(1, 1, 1) = 40.0; - field(1, 2, 2) = 60.0; - field(2, 4, 3) = 109.0; + EXPECT_EQ(mean(field, false), mean_value_nobndry); + EXPECT_EQ(mean(field, false, "RGN_ALL"), mean_value_all); + EXPECT_EQ(mean(field, true, "RGN_ALL"), mean_value_all); + } - const int npoints_all = nx * ny * nz; - const BoutReal mean_value_nobndry = 103.0; - const BoutReal mean_value_all = 103.0 + 20.0 / npoints_all; - const auto expr = field * 2.0 + 3.0; + TEST_F(Field3DTest, MeanBinaryExpr) { + Field3D field; - EXPECT_EQ(mean(expr, false), mean_value_nobndry); - EXPECT_EQ(mean(expr, false, "RGN_ALL"), mean_value_all); -} + field = 50.0; + field(0, 0, 0) = 1.0; + field(1, 1, 1) = 40.0; + field(1, 2, 2) = 60.0; + field(2, 4, 3) = 109.0; -TEST_F(Field3DTest, DC) { - Field3D field; + const int npoints_all = nx * ny * nz; + const BoutReal mean_value_nobndry = 103.0; + const BoutReal mean_value_all = 103.0 + 20.0 / npoints_all; + const auto expr = field * 2.0 + 3.0; - field = 1.0; - for (const auto& i : field) { - field[i] = i.z(); + EXPECT_EQ(mean(expr, false), mean_value_nobndry); + EXPECT_EQ(mean(expr, false, "RGN_ALL"), mean_value_all); } - EXPECT_TRUE(IsFieldEqual(DC(field), 3.0)); -} + TEST_F(Field3DTest, DC) { + Field3D field; -TEST_F(Field3DTest, Swap) { - WithQuietOutput quiet{output_info}; + field = 1.0; + for (const auto& i : field) { + field[i] = i.z(); + } - // First field - Field3D first(1., mesh_staggered); + EXPECT_TRUE(IsFieldEqual(DC(field), 3.0)); + } - first.setLocation(CELL_XLOW); + TEST_F(Field3DTest, Swap) { + WithQuietOutput quiet{output_info}; - first.splitParallelSlices(); - first.yup() = 1.5; - first.ydown() = 0.5; + // First field + Field3D first(1., mesh_staggered); - ddt(first) = 1.1; + first.setLocation(CELL_XLOW); - // Mesh for second field - constexpr int second_nx = Field3DTest::nx + 2; - constexpr int second_ny = Field3DTest::ny + 2; - constexpr int second_nz = Field3DTest::nz + 2; + first.splitParallelSlices(); + first.yup() = 1.5; + first.ydown() = 0.5; - FakeMesh second_mesh{second_nx, second_ny, second_nz}; - second_mesh.setCoordinates(nullptr); - second_mesh.StaggerGrids = false; - second_mesh.createDefaultRegions(); + ddt(first) = 1.1; - // Second field - Field3D second(2., &second_mesh); + // Mesh for second field + constexpr int second_nx = Field3DTest::nx + 2; + constexpr int second_ny = Field3DTest::ny + 2; + constexpr int second_nz = Field3DTest::nz + 2; - second.splitParallelSlices(); - second.yup() = 2.2; - second.ydown() = 1.2; + FakeMesh second_mesh{second_nx, second_ny, second_nz}; + second_mesh.setCoordinates(nullptr); + second_mesh.StaggerGrids = false; + second_mesh.createDefaultRegions(); - ddt(second) = 2.4; + // Second field + Field3D second(2., &second_mesh); - // Basic sanity check - EXPECT_TRUE(IsFieldEqual(first, 1.0)); - EXPECT_TRUE(IsFieldEqual(second, 2.0)); + second.splitParallelSlices(); + second.yup() = 2.2; + second.ydown() = 1.2; - // swap is marked noexcept, so absolutely should not throw! - ASSERT_NO_THROW(swap(first, second)); + ddt(second) = 2.4; - // Values - EXPECT_TRUE(IsFieldEqual(first, 2.0)); - EXPECT_TRUE(IsFieldEqual(second, 1.0)); + // Basic sanity check + EXPECT_TRUE(IsFieldEqual(first, 1.0)); + EXPECT_TRUE(IsFieldEqual(second, 2.0)); - EXPECT_TRUE(IsFieldEqual(first.yup(), 2.2)); - EXPECT_TRUE(IsFieldEqual(first.ydown(), 1.2)); + // swap is marked noexcept, so absolutely should not throw! + ASSERT_NO_THROW(swap(first, second)); - EXPECT_TRUE(IsFieldEqual(second.yup(), 1.5)); - EXPECT_TRUE(IsFieldEqual(second.ydown(), 0.5)); + // Values + EXPECT_TRUE(IsFieldEqual(first, 2.0)); + EXPECT_TRUE(IsFieldEqual(second, 1.0)); - EXPECT_TRUE(IsFieldEqual(ddt(first), 2.4)); - EXPECT_TRUE(IsFieldEqual(ddt(second), 1.1)); + EXPECT_TRUE(IsFieldEqual(first.yup(), 2.2)); + EXPECT_TRUE(IsFieldEqual(first.ydown(), 1.2)); - // Mesh properties - EXPECT_EQ(first.getMesh(), &second_mesh); - EXPECT_EQ(second.getMesh(), mesh_staggered); + EXPECT_TRUE(IsFieldEqual(second.yup(), 1.5)); + EXPECT_TRUE(IsFieldEqual(second.ydown(), 0.5)); - EXPECT_EQ(first.getNx(), second_nx); - EXPECT_EQ(first.getNy(), second_ny); - EXPECT_EQ(first.getNz(), second_nz); + EXPECT_TRUE(IsFieldEqual(ddt(first), 2.4)); + EXPECT_TRUE(IsFieldEqual(ddt(second), 1.1)); - EXPECT_EQ(second.getNx(), Field3DTest::nx); - EXPECT_EQ(second.getNy(), Field3DTest::ny); - EXPECT_EQ(second.getNz(), Field3DTest::nz); + // Mesh properties + EXPECT_EQ(first.getMesh(), &second_mesh); + EXPECT_EQ(second.getMesh(), mesh_staggered); - EXPECT_EQ(first.getLocation(), CELL_CENTRE); - EXPECT_EQ(second.getLocation(), CELL_XLOW); + EXPECT_EQ(first.getNx(), second_nx); + EXPECT_EQ(first.getNy(), second_ny); + EXPECT_EQ(first.getNz(), second_nz); - // We don't check the boundaries, but the data is protected and - // there are no inquiry functions -} + EXPECT_EQ(second.getNx(), Field3DTest::nx); + EXPECT_EQ(second.getNy(), Field3DTest::ny); + EXPECT_EQ(second.getNz(), Field3DTest::nz); -TEST_F(Field3DTest, MoveCtor) { - // First field - Field3D first(1., mesh_staggered); + EXPECT_EQ(first.getLocation(), CELL_CENTRE); + EXPECT_EQ(second.getLocation(), CELL_XLOW); - first.setLocation(CELL_XLOW); + // We don't check the boundaries, but the data is protected and + // there are no inquiry functions + } - first.splitParallelSlices(); - first.yup() = 1.5; - first.ydown() = 0.5; + TEST_F(Field3DTest, MoveCtor) { + // First field + Field3D first(1., mesh_staggered); - ddt(first) = 1.1; + first.setLocation(CELL_XLOW); - // Second field - Field3D second{std::move(first)}; + first.splitParallelSlices(); + first.yup() = 1.5; + first.ydown() = 0.5; - // Values - EXPECT_TRUE(IsFieldEqual(second, 1.0)); + ddt(first) = 1.1; - EXPECT_TRUE(IsFieldEqual(second.yup(), 1.5)); - EXPECT_TRUE(IsFieldEqual(second.ydown(), 0.5)); + // Second field + Field3D second{std::move(first)}; - EXPECT_TRUE(IsFieldEqual(ddt(second), 1.1)); + // Values + EXPECT_TRUE(IsFieldEqual(second, 1.0)); - // Mesh properties - EXPECT_EQ(second.getMesh(), mesh_staggered); + EXPECT_TRUE(IsFieldEqual(second.yup(), 1.5)); + EXPECT_TRUE(IsFieldEqual(second.ydown(), 0.5)); - EXPECT_EQ(second.getNx(), Field3DTest::nx); - EXPECT_EQ(second.getNy(), Field3DTest::ny); - EXPECT_EQ(second.getNz(), Field3DTest::nz); + EXPECT_TRUE(IsFieldEqual(ddt(second), 1.1)); - EXPECT_EQ(second.getLocation(), CELL_XLOW); + // Mesh properties + EXPECT_EQ(second.getMesh(), mesh_staggered); - // We don't check the boundaries, but the data is protected and - // there are no inquiry functions -} + EXPECT_EQ(second.getNx(), Field3DTest::nx); + EXPECT_EQ(second.getNy(), Field3DTest::ny); + EXPECT_EQ(second.getNz(), Field3DTest::nz); -TEST_F(Field3DTest, FillField) { - Field3D f{mesh}; + EXPECT_EQ(second.getLocation(), CELL_XLOW); - fillField(f, {{{1., 1., 1., 1., 1., 1., 1.}, - {1., 1., 1., 1., 1., 1., 1.}, - {1., 1., 1., 1., 1., 1., 1.}, - {1., 1., 1., 1., 1., 1., 1.}, - {1., 1., 1., 1., 1., 1., 1.}}, + // We don't check the boundaries, but the data is protected and + // there are no inquiry functions + } - {{1., 1., 1., 1., 1., 1., 1.}, - {1., 1., 1., 1., 1., 1., 1.}, - {1., 1., 1., 1., 1., 1., 1.}, - {1., 1., 1., 1., 1., 1., 1.}, - {1., 1., 1., 1., 1., 1., 1.}}, + TEST_F(Field3DTest, FillField) { + Field3D f{mesh}; + + fillField(f, {{{1., 1., 1., 1., 1., 1., 1.}, + {1., 1., 1., 1., 1., 1., 1.}, + {1., 1., 1., 1., 1., 1., 1.}, + {1., 1., 1., 1., 1., 1., 1.}, + {1., 1., 1., 1., 1., 1., 1.}}, + + {{1., 1., 1., 1., 1., 1., 1.}, + {1., 1., 1., 1., 1., 1., 1.}, + {1., 1., 1., 1., 1., 1., 1.}, + {1., 1., 1., 1., 1., 1., 1.}, + {1., 1., 1., 1., 1., 1., 1.}}, + + {{1., 1., 1., 1., 1., 1., 1.}, + {1., 1., 1., 1., 1., 1., 1.}, + {1., 1., 1., 1., 1., 1., 1.}, + {1., 1., 1., 1., 1., 1., 1.}, + {1., 1., 1., 1., 1., 1., 1.}}}); + + EXPECT_TRUE(IsFieldEqual(f, 1.)); + + fillField(f, {{{0., 1., 2., 3., 4., 5., 6.}, + {0., 1., 2., 3., 4., 5., 6.}, + {0., 1., 2., 3., 4., 5., 6.}, + {0., 1., 2., 3., 4., 5., 6.}, + {0., 1., 2., 3., 4., 5., 6.}}, + + {{0., 1., 2., 3., 4., 5., 6.}, + {0., 1., 2., 3., 4., 5., 6.}, + {0., 1., 2., 3., 4., 5., 6.}, + {0., 1., 2., 3., 4., 5., 6.}, + {0., 1., 2., 3., 4., 5., 6.}}, + + {{0., 1., 2., 3., 4., 5., 6.}, + {0., 1., 2., 3., 4., 5., 6.}, + {0., 1., 2., 3., 4., 5., 6.}, + {0., 1., 2., 3., 4., 5., 6.}, + {0., 1., 2., 3., 4., 5., 6.}}}); + + Field3D g{mesh}; + g.allocate(); + BOUT_FOR_SERIAL(i, g.getRegion("RGN_ALL")) { g[i] = i.z(); } + + EXPECT_TRUE(IsFieldEqual(f, g)); + } - {{1., 1., 1., 1., 1., 1., 1.}, - {1., 1., 1., 1., 1., 1., 1.}, - {1., 1., 1., 1., 1., 1., 1.}, - {1., 1., 1., 1., 1., 1., 1.}, - {1., 1., 1., 1., 1., 1., 1.}}}); +#if BOUT_HAS_FFTW + namespace bout { + namespace testing { - EXPECT_TRUE(IsFieldEqual(f, 1.)); + // Amplitudes for the nth wavenumber + constexpr int k0{1}; + constexpr int k1{2}; + constexpr int k2{3}; - fillField(f, {{{0., 1., 2., 3., 4., 5., 6.}, - {0., 1., 2., 3., 4., 5., 6.}, - {0., 1., 2., 3., 4., 5., 6.}, - {0., 1., 2., 3., 4., 5., 6.}, - {0., 1., 2., 3., 4., 5., 6.}}, + const BoutReal box_size{TWOPI / Field3DTest::nz}; - {{0., 1., 2., 3., 4., 5., 6.}, - {0., 1., 2., 3., 4., 5., 6.}, - {0., 1., 2., 3., 4., 5., 6.}, - {0., 1., 2., 3., 4., 5., 6.}, - {0., 1., 2., 3., 4., 5., 6.}}, + // Helper function for the filter and lowpass tests + BoutReal zWaves(Field3D::ind_type& i) { + return 1.0 + std::sin(k0 * i.z() * box_size) + std::cos(k1 * i.z() * box_size) + + std::sin(k2 * i.z() * box_size); + } + } // namespace testing + } // namespace bout - {{0., 1., 2., 3., 4., 5., 6.}, - {0., 1., 2., 3., 4., 5., 6.}, - {0., 1., 2., 3., 4., 5., 6.}, - {0., 1., 2., 3., 4., 5., 6.}, - {0., 1., 2., 3., 4., 5., 6.}}}); + TEST_F(Field3DTest, Filter) { - Field3D g{mesh}; - g.allocate(); - BOUT_FOR_SERIAL(i, g.getRegion("RGN_ALL")) { g[i] = i.z(); } + using namespace bout::testing; - EXPECT_TRUE(IsFieldEqual(f, g)); -} + auto input = makeField(zWaves, bout::globals::mesh); -#if BOUT_HAS_FFTW -namespace bout { -namespace testing { + auto expected = makeField( + [&](Field3D::ind_type& i) { return std::cos(k1 * i.z() * box_size); }, + bout::globals::mesh); -// Amplitudes for the nth wavenumber -constexpr int k0{1}; -constexpr int k1{2}; -constexpr int k2{3}; + auto output = filter(input, 2); -const BoutReal box_size{TWOPI / Field3DTest::nz}; + EXPECT_TRUE(IsFieldEqual(output, expected)); + } -// Helper function for the filter and lowpass tests -BoutReal zWaves(Field3D::ind_type& i) { - return 1.0 + std::sin(k0 * i.z() * box_size) + std::cos(k1 * i.z() * box_size) - + std::sin(k2 * i.z() * box_size); -} -} // namespace testing -} // namespace bout + TEST_F(Field3DTest, LowPassOneArg) { -TEST_F(Field3DTest, Filter) { + using namespace bout::testing; - using namespace bout::testing; + auto input = makeField(zWaves, bout::globals::mesh); - auto input = makeField(zWaves, bout::globals::mesh); + auto expected = makeField( + [&](Field3D::ind_type& i) { + return 1.0 + std::sin(k0 * i.z() * box_size) + std::cos(k1 * i.z() * box_size); + }, + bout::globals::mesh); - auto expected = makeField( - [&](Field3D::ind_type& i) { return std::cos(k1 * i.z() * box_size); }, - bout::globals::mesh); + auto output = lowPass(input, 2); - auto output = filter(input, 2); + EXPECT_TRUE(IsFieldEqual(output, expected)); + } - EXPECT_TRUE(IsFieldEqual(output, expected)); -} + TEST_F(Field3DTest, LowPassOneArgNothing) { -TEST_F(Field3DTest, LowPassOneArg) { + using namespace bout::testing; - using namespace bout::testing; + auto input = makeField(zWaves, bout::globals::mesh); - auto input = makeField(zWaves, bout::globals::mesh); + auto output = lowPass(input, 20); - auto expected = makeField( - [&](Field3D::ind_type& i) { - return 1.0 + std::sin(k0 * i.z() * box_size) + std::cos(k1 * i.z() * box_size); - }, - bout::globals::mesh); + EXPECT_TRUE(IsFieldEqual(output, input)); + } - auto output = lowPass(input, 2); + TEST_F(Field3DTest, LowPassTwoArg) { - EXPECT_TRUE(IsFieldEqual(output, expected)); -} + using namespace bout::testing; -TEST_F(Field3DTest, LowPassOneArgNothing) { + auto input = makeField(zWaves, bout::globals::mesh); - using namespace bout::testing; + auto expected = makeField( + [&](Field3D::ind_type& i) { + return std::sin(k0 * i.z() * box_size) + std::cos(k1 * i.z() * box_size); + }, + bout::globals::mesh); - auto input = makeField(zWaves, bout::globals::mesh); + auto output = lowPass(input, 2, false); - auto output = lowPass(input, 20); + EXPECT_TRUE(IsFieldEqual(output, expected)); - EXPECT_TRUE(IsFieldEqual(output, input)); -} + // Check passing int still works + auto output2 = lowPass(input, 2, 0); -TEST_F(Field3DTest, LowPassTwoArg) { + EXPECT_TRUE(IsFieldEqual(output2, expected)); - using namespace bout::testing; + // Calling lowPass with an int that is not 0 or 1 is an error + EXPECT_THROW(lowPass(input, 2, -1), BoutException); + EXPECT_THROW(lowPass(input, 2, 2), BoutException); + } - auto input = makeField(zWaves, bout::globals::mesh); + TEST_F(Field3DTest, LowPassTwoArgKeepZonal) { - auto expected = makeField( - [&](Field3D::ind_type& i) { - return std::sin(k0 * i.z() * box_size) + std::cos(k1 * i.z() * box_size); - }, - bout::globals::mesh); + using namespace bout::testing; - auto output = lowPass(input, 2, false); + auto input = makeField(zWaves, bout::globals::mesh); - EXPECT_TRUE(IsFieldEqual(output, expected)); + auto expected = makeField( + [&](Field3D::ind_type& i) { + return 1.0 + std::sin(k0 * i.z() * box_size) + std::cos(k1 * i.z() * box_size); + }, + bout::globals::mesh); - // Check passing int still works - auto output2 = lowPass(input, 2, 0); + auto output = lowPass(input, 2, true); - EXPECT_TRUE(IsFieldEqual(output2, expected)); + EXPECT_TRUE(IsFieldEqual(output, expected)); - // Calling lowPass with an int that is not 0 or 1 is an error - EXPECT_THROW(lowPass(input, 2, -1), BoutException); - EXPECT_THROW(lowPass(input, 2, 2), BoutException); -} + // Check passing int still works + auto output2 = lowPass(input, 2, 1); -TEST_F(Field3DTest, LowPassTwoArgKeepZonal) { + EXPECT_TRUE(IsFieldEqual(output2, expected)); + } - using namespace bout::testing; + TEST_F(Field3DTest, LowPassTwoArgNothing) { - auto input = makeField(zWaves, bout::globals::mesh); + using namespace bout::testing; - auto expected = makeField( - [&](Field3D::ind_type& i) { - return 1.0 + std::sin(k0 * i.z() * box_size) + std::cos(k1 * i.z() * box_size); - }, - bout::globals::mesh); + auto input = makeField(zWaves, bout::globals::mesh); - auto output = lowPass(input, 2, true); + auto output = lowPass(input, 20, true); - EXPECT_TRUE(IsFieldEqual(output, expected)); + EXPECT_TRUE(IsFieldEqual(output, input)); + } +#endif - // Check passing int still works - auto output2 = lowPass(input, 2, 1); + TEST_F(Field3DTest, OperatorEqualsField3D) { + Field3D field; - EXPECT_TRUE(IsFieldEqual(output2, expected)); -} + // Create field with non-default arguments so we can check they get copied + // to 'field'. + // Note that Average z-direction type is not really allowed for Field3D, but + // we don't check anywhere at the moment. + Field3D field2{ + mesh_staggered, CELL_XLOW, {YDirectionType::Aligned, ZDirectionType::Average}}; -TEST_F(Field3DTest, LowPassTwoArgNothing) { + field = field2; - using namespace bout::testing; + EXPECT_TRUE(areFieldsCompatible(field, field2)); + EXPECT_EQ(field.getMesh(), field2.getMesh()); + EXPECT_EQ(field.getLocation(), field2.getLocation()); + EXPECT_EQ(field.getDirectionY(), field2.getDirectionY()); + EXPECT_EQ(field.getDirectionZ(), field2.getDirectionZ()); + } - auto input = makeField(zWaves, bout::globals::mesh); + TEST_F(Field3DTest, OperatorEqualsBinaryExprCopiesMetadata) { + Field3D source{ + mesh_staggered, CELL_XLOW, {YDirectionType::Aligned, ZDirectionType::Average}}; + source = 9.; - auto output = lowPass(input, 20, true); + Field3D target(mesh_staggered); + target = 0.; + target.splitParallelSlices(); - EXPECT_TRUE(IsFieldEqual(output, input)); -} -#endif + target = sqrt(source); -TEST_F(Field3DTest, OperatorEqualsField3D) { - Field3D field; + EXPECT_EQ(target.getMesh(), source.getMesh()); + EXPECT_EQ(target.getLocation(), source.getLocation()); + EXPECT_EQ(target.getDirectionY(), source.getDirectionY()); + EXPECT_EQ(target.getDirectionZ(), source.getDirectionZ()); + EXPECT_FALSE(target.hasParallelSlices()); + EXPECT_TRUE(IsFieldEqual(target, 3.)); + } - // Create field with non-default arguments so we can check they get copied - // to 'field'. - // Note that Average z-direction type is not really allowed for Field3D, but - // we don't check anywhere at the moment. - Field3D field2{ - mesh_staggered, CELL_XLOW, {YDirectionType::Aligned, ZDirectionType::Average}}; + TEST_F(Field3DTest, EmptyFrom) { + // Create field with non-default arguments so we can check they get copied + // to 'field2'. + // Note that Average z-direction type is not really allowed for Field3D, but + // we don't check anywhere at the moment. + Field3D field{ + mesh_staggered, CELL_XLOW, {YDirectionType::Aligned, ZDirectionType::Average}}; + field = 5.; + + Field3D field2{emptyFrom(field)}; + EXPECT_EQ(field2.getMesh(), mesh_staggered); + EXPECT_EQ(field2.getLocation(), CELL_XLOW); + EXPECT_EQ(field2.getDirectionY(), YDirectionType::Aligned); + EXPECT_EQ(field2.getDirectionZ(), ZDirectionType::Average); + EXPECT_TRUE(field2.isAllocated()); + } - field = field2; + TEST_F(Field3DTest, ZeroFrom) { + // Create field with non-default arguments so we can check they get copied + // to 'field2'. + // Note that Average z-direction type is not really allowed for Field3D, but + // we don't check anywhere at the moment. + Field3D field{ + mesh_staggered, CELL_XLOW, {YDirectionType::Aligned, ZDirectionType::Average}}; + field = 5.; + + Field3D field2{zeroFrom(field)}; + EXPECT_EQ(field2.getMesh(), mesh_staggered); + EXPECT_EQ(field2.getLocation(), CELL_XLOW); + EXPECT_EQ(field2.getDirectionY(), YDirectionType::Aligned); + EXPECT_EQ(field2.getDirectionZ(), ZDirectionType::Average); + EXPECT_TRUE(field2.isAllocated()); + EXPECT_TRUE(IsFieldEqual(field2, 0.)); + } - EXPECT_TRUE(areFieldsCompatible(field, field2)); - EXPECT_EQ(field.getMesh(), field2.getMesh()); - EXPECT_EQ(field.getLocation(), field2.getLocation()); - EXPECT_EQ(field.getDirectionY(), field2.getDirectionY()); - EXPECT_EQ(field.getDirectionZ(), field2.getDirectionZ()); -} - -TEST_F(Field3DTest, OperatorEqualsBinaryExprCopiesMetadata) { - Field3D source{ - mesh_staggered, CELL_XLOW, {YDirectionType::Aligned, ZDirectionType::Average}}; - source = 9.; - - Field3D target(mesh_staggered); - target = 0.; - target.splitParallelSlices(); - - target = sqrt(source); - - EXPECT_EQ(target.getMesh(), source.getMesh()); - EXPECT_EQ(target.getLocation(), source.getLocation()); - EXPECT_EQ(target.getDirectionY(), source.getDirectionY()); - EXPECT_EQ(target.getDirectionZ(), source.getDirectionZ()); - EXPECT_FALSE(target.hasParallelSlices()); - EXPECT_TRUE(IsFieldEqual(target, 3.)); -} - -TEST_F(Field3DTest, EmptyFrom) { - // Create field with non-default arguments so we can check they get copied - // to 'field2'. - // Note that Average z-direction type is not really allowed for Field3D, but - // we don't check anywhere at the moment. - Field3D field{ - mesh_staggered, CELL_XLOW, {YDirectionType::Aligned, ZDirectionType::Average}}; - field = 5.; - - Field3D field2{emptyFrom(field)}; - EXPECT_EQ(field2.getMesh(), mesh_staggered); - EXPECT_EQ(field2.getLocation(), CELL_XLOW); - EXPECT_EQ(field2.getDirectionY(), YDirectionType::Aligned); - EXPECT_EQ(field2.getDirectionZ(), ZDirectionType::Average); - EXPECT_TRUE(field2.isAllocated()); -} - -TEST_F(Field3DTest, ZeroFrom) { - // Create field with non-default arguments so we can check they get copied - // to 'field2'. - // Note that Average z-direction type is not really allowed for Field3D, but - // we don't check anywhere at the moment. - Field3D field{ - mesh_staggered, CELL_XLOW, {YDirectionType::Aligned, ZDirectionType::Average}}; - field = 5.; - - Field3D field2{zeroFrom(field)}; - EXPECT_EQ(field2.getMesh(), mesh_staggered); - EXPECT_EQ(field2.getLocation(), CELL_XLOW); - EXPECT_EQ(field2.getDirectionY(), YDirectionType::Aligned); - EXPECT_EQ(field2.getDirectionZ(), ZDirectionType::Average); - EXPECT_TRUE(field2.isAllocated()); - EXPECT_TRUE(IsFieldEqual(field2, 0.)); -} - -TEST_F(Field3DTest, Field3DParallel) { - Field3DParallel field(1.0); - field = 1.0; + TEST_F(Field3DTest, Field3DParallel) { + Field3DParallel field(1.0); + field = 1.0; - Field3D field2 = field; + Field3D field2 = field; - auto& field3 = field.asField3D(); + auto& field3 = field.asField3D(); - field *= 2; + field *= 2; - EXPECT_TRUE(IsFieldEqual(field, 2.0)); - EXPECT_TRUE(IsFieldEqual(field2, 1.0)); - EXPECT_TRUE(IsFieldEqual(field3, 2.0)); + EXPECT_TRUE(IsFieldEqual(field, 2.0)); + EXPECT_TRUE(IsFieldEqual(field2, 1.0)); + EXPECT_TRUE(IsFieldEqual(field3, 2.0)); - field3.asField3DParallel() *= 3; + field3.asField3DParallel() *= 3; - EXPECT_TRUE(IsFieldEqual(field3, 6.0)); -} + EXPECT_TRUE(IsFieldEqual(field3, 6.0)); + } // Restore compiler warnings #pragma GCC diagnostic pop From c23f2d532b8093beab2fc37868ae420f1b7ac124 Mon Sep 17 00:00:00 2001 From: David Bold Date: Thu, 9 Jul 2026 11:44:46 +0200 Subject: [PATCH 023/221] Allow FCI fixture --- tests/unit/fake_mesh_fixture.hxx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/unit/fake_mesh_fixture.hxx b/tests/unit/fake_mesh_fixture.hxx index 2758dbe416..3c261a6121 100644 --- a/tests/unit/fake_mesh_fixture.hxx +++ b/tests/unit/fake_mesh_fixture.hxx @@ -31,7 +31,7 @@ /// Use this template class directly to use different sized grid: /// /// using MyTest = FakeMeshFixture_tmpl<7, 9, 11>; -template +template class FakeMeshFixture_tmpl : public ::testing::Test { public: FakeMeshFixture_tmpl() @@ -113,6 +113,11 @@ public: mesh_staggered_m.setCoordinates(test_coords_staggered, CELL_XLOW); mesh_staggered_m.setCoordinates(test_coords_staggered, CELL_YLOW); mesh_staggered_m.setCoordinates(test_coords_staggered, CELL_ZLOW); + + if constexpr (FCI) { + mesh_m.getCoordinates()->setParallelTransform( + bout::utils::make_unique(mesh_m, false)); + } } FakeMeshFixture_tmpl(const FakeMeshFixture_tmpl&) = delete; From 356fd0c3e48c6a5b905e9565576bea11378cfed8 Mon Sep 17 00:00:00 2001 From: David Bold Date: Thu, 9 Jul 2026 11:44:33 +0200 Subject: [PATCH 024/221] Move MockParallelTransform To make it reusable --- tests/unit/fake_mesh.hxx | 51 +++++++++++++++++++++++++ tests/unit/field/test_field_factory.cxx | 51 ------------------------- 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/tests/unit/fake_mesh.hxx b/tests/unit/fake_mesh.hxx index f957652a62..6559e554f2 100644 --- a/tests/unit/fake_mesh.hxx +++ b/tests/unit/fake_mesh.hxx @@ -366,3 +366,54 @@ public: private: Options values; ///< Store values to be returned by get() }; + +// A mock ParallelTransform to test transform_from_field_aligned +// property of FieldFactory. For now, the transform just returns the +// negative of the input. Ideally, this will get moved to GoogleMock +// when we start using it. +// +// Can turn off the ability to do the transform. Should still be valid +class MockParallelTransform : public ParallelTransform { +public: + MockParallelTransform(Mesh& mesh, bool allow_transform_) + : ParallelTransform(mesh), allow_transform(allow_transform_) {} + ~MockParallelTransform() = default; + + void calcParallelSlices(Field3D&) override {} + + bool canToFromFieldAligned() const override { return allow_transform; } + + bool requiresTwistShift(bool, YDirectionType) override { return false; } + + void checkInputGrid() override {} + + Field3D fromFieldAligned(const Field3D& f, const std::string&) override { + if (f.getDirectionY() != YDirectionType::Aligned) { + throw BoutException("Unaligned field passed to fromFieldAligned"); + } + return -f; + } + + FieldPerp fromFieldAligned(const FieldPerp& f, const std::string&) override { + if (f.getDirectionY() != YDirectionType::Aligned) { + throw BoutException("Unaligned field passed to fromFieldAligned"); + } + return -f; + } + + Field3D toFieldAligned(const Field3D& f, const std::string&) override { + if (f.getDirectionY() != YDirectionType::Standard) { + throw BoutException("Aligned field passed to toFieldAligned"); + } + return -f; + } + FieldPerp toFieldAligned(const FieldPerp& f, const std::string&) override { + if (f.getDirectionY() != YDirectionType::Standard) { + throw BoutException("Aligned field passed to toFieldAligned"); + } + return -f; + } + +private: + const bool allow_transform; +}; diff --git a/tests/unit/field/test_field_factory.cxx b/tests/unit/field/test_field_factory.cxx index 9db9fcef10..8cb08ed230 100644 --- a/tests/unit/field/test_field_factory.cxx +++ b/tests/unit/field/test_field_factory.cxx @@ -832,57 +832,6 @@ TEST_F(FieldFactoryTest, FuzzyFind) { EXPECT_EQ(CAPS_matches.size(), 1); } -// A mock ParallelTransform to test transform_from_field_aligned -// property of FieldFactory. For now, the transform just returns the -// negative of the input. Ideally, this will get moved to GoogleMock -// when we start using it. -// -// Can turn off the ability to do the transform. Should still be valid -class MockParallelTransform : public ParallelTransform { -public: - MockParallelTransform(Mesh& mesh, bool allow_transform_) - : ParallelTransform(mesh), allow_transform(allow_transform_) {} - ~MockParallelTransform() = default; - - void calcParallelSlices(Field3D&) override {} - - bool canToFromFieldAligned() const override { return allow_transform; } - - bool requiresTwistShift(bool, YDirectionType) override { return false; } - - void checkInputGrid() override {} - - Field3D fromFieldAligned(const Field3D& f, const std::string&) override { - if (f.getDirectionY() != YDirectionType::Aligned) { - throw BoutException("Unaligned field passed to fromFieldAligned"); - } - return -f; - } - - FieldPerp fromFieldAligned(const FieldPerp& f, const std::string&) override { - if (f.getDirectionY() != YDirectionType::Aligned) { - throw BoutException("Unaligned field passed to fromFieldAligned"); - } - return -f; - } - - Field3D toFieldAligned(const Field3D& f, const std::string&) override { - if (f.getDirectionY() != YDirectionType::Standard) { - throw BoutException("Aligned field passed to toFieldAligned"); - } - return -f; - } - FieldPerp toFieldAligned(const FieldPerp& f, const std::string&) override { - if (f.getDirectionY() != YDirectionType::Standard) { - throw BoutException("Aligned field passed to toFieldAligned"); - } - return -f; - } - -private: - const bool allow_transform; -}; - class FieldFactoryCreateAndTransformTest : public FakeMeshFixture { public: WithQuietOutput quiet_info{output_info}; From 8f66c008d013104f684a2c4bf7dd9f404e5e73a1 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Thu, 9 Jul 2026 22:12:40 -0700 Subject: [PATCH 025/221] test_field3d: Tidying, define FakeMeshFixtureFCI Implement some clang-tidy suggestions, use FakeMeshFixtureFCI for tests with FCI parallel transform. --- tests/unit/fake_mesh_fixture.hxx | 1 + tests/unit/field/test_field3d.cxx | 1091 ++++++++++++++--------------- 2 files changed, 528 insertions(+), 564 deletions(-) diff --git a/tests/unit/fake_mesh_fixture.hxx b/tests/unit/fake_mesh_fixture.hxx index 3c261a6121..92d8ee2a13 100644 --- a/tests/unit/fake_mesh_fixture.hxx +++ b/tests/unit/fake_mesh_fixture.hxx @@ -153,3 +153,4 @@ public: }; using FakeMeshFixture = FakeMeshFixture_tmpl<3, 5, 7>; +using FakeMeshFixtureFCI = FakeMeshFixture_tmpl<3, 5, 7, true>; diff --git a/tests/unit/field/test_field3d.cxx b/tests/unit/field/test_field3d.cxx index 89286a8f80..173991a31d 100644 --- a/tests/unit/field/test_field3d.cxx +++ b/tests/unit/field/test_field3d.cxx @@ -7,14 +7,16 @@ #include "gtest/gtest.h" #include "test_extras.hxx" +#include "bout/bout_types.hxx" #include "bout/boutexception.hxx" #include "bout/constants.hxx" +#include "bout/field2d.hxx" #include "bout/field3d.hxx" #include "bout/mesh.hxx" #include "bout/output.hxx" #include "bout/paralleltransform.hxx" +#include "bout/region.hxx" #include "bout/unused.hxx" -#include "bout/utils.hxx" #include #include @@ -27,35 +29,7 @@ using namespace bout::globals; // Reuse the "standard" fixture for FakeMesh using Field3DTest = FakeMeshFixture; -using Field3DTestFCI = FakeMeshFixture_tmpl<3, 5, 7, true>; - -class MockFciParallelTransform : public ParallelTransform { -public: - explicit MockFciParallelTransform(Mesh& mesh_in) : ParallelTransform(mesh_in) {} - - void calcParallelSlices(Field3D& f) override { - if (!f.hasParallelSlices()) { - f.splitParallelSlices(); - } - for (size_t i = 0; i < f.numberParallelSlices(); ++i) { - f.yup(i) = f; - f.ydown(i) = f; - } - } - - Field3D toFieldAligned(const Field3D& f, const std::string&) override { return f; } - FieldPerp toFieldAligned(const FieldPerp& f, const std::string&) override { return f; } - Field3D fromFieldAligned(const Field3D& f, const std::string&) override { return f; } - FieldPerp fromFieldAligned(const FieldPerp& f, const std::string&) override { - return f; - } - - bool canToFromFieldAligned() const override { return false; } - bool requiresTwistShift(bool, YDirectionType) override { return false; } - -protected: - void checkInputGrid() override {} -}; +using Field3DTestFCI = FakeMeshFixtureFCI; TEST_F(Field3DTest, Is3D) { Field3D field; @@ -104,14 +78,14 @@ TEST_F(Field3DTest, GetGridSizes) { } TEST_F(Field3DTest, CreateOnGivenMesh) { - int test_nx = Field3DTest::nx + 2; - int test_ny = Field3DTest::ny + 2; - int test_nz = Field3DTest::nz + 2; + const int test_nx = Field3DTest::nx + 2; + const int test_ny = Field3DTest::ny + 2; + const int test_nz = Field3DTest::nz + 2; FakeMesh fieldmesh{test_nx, test_ny, test_nz}; fieldmesh.setCoordinates(nullptr); - Field3D field{&fieldmesh}; + const Field3D field{&fieldmesh}; EXPECT_EQ(field.getNx(), test_nx); EXPECT_EQ(field.getNy(), test_ny); @@ -121,17 +95,17 @@ TEST_F(Field3DTest, CreateOnGivenMesh) { TEST_F(Field3DTest, CopyCheckFieldmesh) { WithQuietOutput quiet{output_info}; - int test_nx = Field3DTest::nx + 2; - int test_ny = Field3DTest::ny + 2; - int test_nz = Field3DTest::nz + 2; + const int test_nx = Field3DTest::nx + 2; + const int test_ny = Field3DTest::ny + 2; + const int test_nz = Field3DTest::nz + 2; FakeMesh fieldmesh{test_nx, test_ny, test_nz}; fieldmesh.setCoordinates(nullptr); fieldmesh.createDefaultRegions(); - Field3D field{0.0, &fieldmesh}; + const Field3D field{0.0, &fieldmesh}; - Field3D field2{field}; + const Field3D field2{field}; EXPECT_EQ(field2.getNx(), test_nx); EXPECT_EQ(field2.getNy(), test_ny); @@ -196,10 +170,10 @@ TEST_F(Field3DTest, CreateCopyOnNullMesh) { TEST_F(Field3DTest, TimeDeriv) { Field3D field; - auto deriv = field.timeDeriv(); + auto* deriv = field.timeDeriv(); EXPECT_NE(&field, deriv); - auto deriv2 = field.timeDeriv(); + auto* deriv2 = field.timeDeriv(); EXPECT_EQ(deriv, deriv2); EXPECT_EQ(&(ddt(field)), deriv); @@ -323,9 +297,9 @@ TEST_F(Field3DTest, ConstYnext) { const Field3D& field2 = field; - auto& yup = field2.ynext(1); + const auto& yup = field2.ynext(1); EXPECT_NE(&field2, &yup); - auto& ydown = field2.ynext(-1); + const auto& ydown = field2.ynext(-1); EXPECT_NE(&field2, &ydown); EXPECT_NE(&yup, &ydown); @@ -335,9 +309,9 @@ TEST_F(Field3DTest, ConstYnext) { } TEST_F(Field3DTest, GetGlobalMesh) { - Field3D field; + const Field3D field; - auto localmesh = field.getMesh(); + auto* localmesh = field.getMesh(); EXPECT_EQ(localmesh, mesh); } @@ -346,9 +320,9 @@ TEST_F(Field3DTest, GetLocalMesh) { FakeMesh myMesh{nx + 1, ny + 2, nz + 3}; myMesh.setCoordinates(nullptr); - Field3D field(&myMesh); + const Field3D field(&myMesh); - auto localmesh = field.getMesh(); + auto* localmesh = field.getMesh(); EXPECT_EQ(localmesh, &myMesh); } @@ -463,8 +437,9 @@ TEST_F(Field3DTest, IterateOverRegionInd3D_RGN_ALL) { // We use a set in case for some reason the iterator doesn't visit // each point in the order we expect - std::set> test_indices{{0, 0, 0}, {0, 0, 1}, {0, 1, 0}, {1, 0, 0}, - {0, 1, 1}, {1, 0, 1}, {1, 1, 0}, {1, 1, 1}}; + const std::set> test_indices{{0, 0, 0}, {0, 0, 1}, {0, 1, 0}, + {1, 0, 0}, {0, 1, 1}, {1, 0, 1}, + {1, 1, 0}, {1, 1, 1}}; const int num_sentinels = test_indices.size(); // Assign sentinel value to watch out for to our chosen points @@ -802,7 +777,7 @@ TEST_F(Field3DTest, IterateOverRGN_ZGUARDS) { test_indices.insert({1, 1, 1}); // This is the set of indices actually inside the region we want - std::set> region_indices; + const std::set> region_indices; const int num_sentinels = region_indices.size(); @@ -913,7 +888,7 @@ TEST_F(Field3DTest, IterateOver2DRGN_NOBNDRY) { EXPECT_EQ(i.z(), 0); } - EXPECT_EQ(sum, nx * ny - 2 * nx - 2 * (ny - 2)); + EXPECT_EQ(sum, (nx * ny) - (2 * nx) - (2 * (ny - 2))); } TEST_F(Field3DTest, IterateOver2DRGN_NOX) { @@ -931,7 +906,7 @@ TEST_F(Field3DTest, IterateOver2DRGN_NOX) { EXPECT_EQ(i.z(), 0); } - EXPECT_EQ(sum, nx * ny - 2 * ny); + EXPECT_EQ(sum, (nx * ny) - (2 * ny)); } TEST_F(Field3DTest, IterateOver2DRGN_NOY) { @@ -949,7 +924,7 @@ TEST_F(Field3DTest, IterateOver2DRGN_NOY) { EXPECT_EQ(i.z(), 0); } - EXPECT_EQ(sum, nx * ny - 2 * nx); + EXPECT_EQ(sum, (nx * ny) - (2 * nx)); } TEST_F(Field3DTest, Indexing) { @@ -981,7 +956,7 @@ TEST_F(Field3DTest, IndexingInd3D) { } } - Ind3D ind{(2 * ny + 2) * nz + 2}; + const Ind3D ind{(((2 * ny) + 2) * nz) + 2}; EXPECT_DOUBLE_EQ(field[ind], 6); } @@ -1001,7 +976,7 @@ TEST_F(Field3DTest, ConstIndexingInd3D) { const Field3D field2{field1}; - Ind3D ind{(2 * ny + 2) * nz + 2}; + const Ind3D ind{(((2 * ny) + 2) * nz) + 2}; EXPECT_DOUBLE_EQ(field2[ind], 6); } @@ -1012,7 +987,7 @@ TEST_F(Field3DTest, IndexingInd2D) { int ix = 1, iy = 2, iz = 3; field(ix, iy, iz) = sentinel; - Ind2D ind{iy + ny * ix, ny, 1}; + const Ind2D ind{iy + (ny * ix), ny, 1}; EXPECT_DOUBLE_EQ(field(ind, iz), sentinel); field(ind, iz) = -sentinel; EXPECT_DOUBLE_EQ(field(ix, iy, iz), -sentinel); @@ -1024,7 +999,7 @@ TEST_F(Field3DTest, ConstIndexingInd2D) { int ix = 1, iy = 2, iz = 3; field(ix, iy, iz) = sentinel; - Ind2D ind{iy + ny * ix, ny, 1}; + const Ind2D ind{iy + (ny * ix), ny, 1}; const Field3D field2{field}; EXPECT_DOUBLE_EQ(field2(ind, iz), sentinel); @@ -1036,7 +1011,7 @@ TEST_F(Field3DTest, IndexingIndPerp) { int ix = 1, iy = 2, iz = 3; field(ix, iy, iz) = sentinel; - IndPerp ind{iz + nz * ix, 1, nz}; + const IndPerp ind{iz + (nz * ix), 1, nz}; EXPECT_DOUBLE_EQ(field(ind, iy), sentinel); field(ind, iy) = -sentinel; EXPECT_DOUBLE_EQ(field(ix, iy, iz), -sentinel); @@ -1057,7 +1032,7 @@ TEST_F(Field3DTest, IndexingToZPointer) { for (int i = 0; i < nx; ++i) { for (int j = 0; j < ny; ++j) { - auto tmp = field(i, j); + auto* tmp = field(i, j); for (int k = 0; k < nz; ++k) { EXPECT_EQ(tmp[k], i + j + k); tmp[k] = -1.0; @@ -1086,7 +1061,7 @@ TEST_F(Field3DTest, ConstIndexingToZPointer) { for (int i = 0; i < nx; ++i) { for (int j = 0; j < ny; ++j) { - auto tmp = field(i, j); + const auto* tmp = field(i, j); for (int k = 0; k < nz; ++k) { EXPECT_EQ(tmp[k], 1.0); field2(i, j, k) = tmp[k]; @@ -1259,15 +1234,15 @@ TEST_F(Field3DTest, CreateFromBoutReal) { } TEST_F(Field3DTest, CreateFromField3D) { - Field3D field(99.0); - Field3D result(field); + const Field3D field(99.0); + const Field3D result(field); EXPECT_TRUE(IsFieldEqual(result, 99.0)); } TEST_F(Field3DTest, CreateFromField2D) { - Field2D field(99.0); - Field3D result(field); + const Field2D field(99.0); + const Field3D result(field); EXPECT_TRUE(IsFieldEqual(result, 99.0)); } @@ -1289,7 +1264,7 @@ TEST_F(Field3DTest, AssignFromInvalid) { TEST_F(Field3DTest, AssignFromField2D) { Field3D field; - Field2D field2(2.0); + const Field2D field2(2.0); field = field2; @@ -2053,10 +2028,7 @@ TEST_F(Field3DTest, Field3DParallelAssignmentFromLazyExprDiscardsSlicesWhenNotFc } #if CHECK >= 2 -TEST_F(Field3DTest, Field3DParallelAssignmentFromLazyExprRequiresField3DSlicesInFci) { - mesh->getCoordinates()->setParallelTransform( - bout::utils::make_unique(*mesh)); - +TEST_F(Field3DTestFCI, Field3DParallelAssignmentFromLazyExprRequiresField3DSlicesInFci) { Field3DParallel parallel; Field3D field; @@ -2069,10 +2041,7 @@ TEST_F(Field3DTest, Field3DParallelAssignmentFromLazyExprRequiresField3DSlicesIn } #endif -TEST_F(Field3DTest, Field3DParallelAssignmentFromLazyExprUsesField3DSlicesInFci) { - mesh->getCoordinates()->setParallelTransform( - bout::utils::make_unique(*mesh)); - +TEST_F(Field3DTestFCI, Field3DParallelAssignmentFromLazyExprUsesField3DSlicesInFci) { Field3DParallel parallel; Field3D field; @@ -2093,10 +2062,7 @@ TEST_F(Field3DTest, Field3DParallelAssignmentFromLazyExprUsesField3DSlicesInFci) EXPECT_TRUE(IsFieldEqual(result.ydown(), 15.0)); } -TEST_F(Field3DTest, Field3DParallelAssignmentFromField3DExprUsesSlicesInFci) { - mesh->getCoordinates()->setParallelTransform( - bout::utils::make_unique(*mesh)); - +TEST_F(Field3DTestFCI, Field3DParallelAssignmentFromField3DExprUsesSlicesInFci) { Field3D lhs; Field3D rhs; @@ -2118,10 +2084,7 @@ TEST_F(Field3DTest, Field3DParallelAssignmentFromField3DExprUsesSlicesInFci) { EXPECT_TRUE(IsFieldEqual(result.ydown(), 44.0)); } -TEST_F(Field3DTest, Field3DParallelAssignmentFromScalarExprUsesSlicesInFci) { - mesh->getCoordinates()->setParallelTransform( - bout::utils::make_unique(*mesh)); - +TEST_F(Field3DTestFCI, Field3DParallelAssignmentFromScalarExprUsesSlicesInFci) { Field3DParallel parallel; parallel = 2.0; parallel.yup() = 3.0; @@ -2133,649 +2096,649 @@ TEST_F(Field3DTest, Field3DParallelAssignmentFromScalarExprUsesSlicesInFci) { EXPECT_TRUE(IsFieldEqual(result, 3.0)); EXPECT_TRUE(IsFieldEqual(result.yup(), 4.0)); EXPECT_TRUE(IsFieldEqual(result.ydown(), 5.0)); +} - TEST_F(Field3DTestFCI, MulField3DParallelPreservesParallelSlices) { - Field3D field; - EXPECT_TRUE(field.isFci()); - - field = 2.0; - field.splitParallelSlices(); - field.yup() = 3.0; - field.ydown() = 4.0; - field.resetRegionParallel(); - - Field3D rhs; - EXPECT_TRUE(rhs.isFci()); - rhs = 3.0; - rhs.splitParallelSlices(); - rhs.yup() = 4.0; - rhs.ydown() = 5.0; - rhs.resetRegionParallel(); - - const Field3D prod = field * rhs; - - EXPECT_FALSE(prod.hasParallelSlices()); - EXPECT_TRUE(IsFieldEqual(prod, 6.0)); - - const Field3DParallel prodpar = field.asField3DParallel() * rhs; - EXPECT_TRUE((std::is_same_v, Field3DParallel>)); - EXPECT_TRUE(prodpar.hasParallelSlices()); - EXPECT_TRUE(IsFieldEqual(prodpar, 6.0)); - EXPECT_TRUE(IsFieldEqual(prodpar.yup(), 12.0, "RGN_YPAR_+1")); - EXPECT_TRUE(IsFieldEqual(prodpar.ydown(), 20.0, "RGN_YPAR_-1")); - } +TEST_F(Field3DTestFCI, MulField3DParallelPreservesParallelSlices) { + Field3D field; + EXPECT_TRUE(field.isFci()); - TEST_F(Field3DTest, Abs) { - Field3D field; + field = 2.0; + field.splitParallelSlices(); + field.yup() = 3.0; + field.ydown() = 4.0; + field.resetRegionParallel(); - field = -31.0; - EXPECT_TRUE(IsFieldEqual(abs(field), 31.0)); - } + Field3D rhs; + EXPECT_TRUE(rhs.isFci()); + rhs = 3.0; + rhs.splitParallelSlices(); + rhs.yup() = 4.0; + rhs.ydown() = 5.0; + rhs.resetRegionParallel(); - TEST_F(Field3DTest, AbsExpressionUsesAbsOp) { - Field3D field; + const Field3D prod = field * rhs; - field = -2.0; - const auto expr = field + 1.0; + EXPECT_FALSE(prod.hasParallelSlices()); + EXPECT_TRUE(IsFieldEqual(prod, 6.0)); - EXPECT_TRUE( - (std::is_same_v, - BinaryExpr, - std::decay_t, bout::op::abs>>)); - EXPECT_TRUE(IsFieldEqual(abs(expr), 1.0)); - EXPECT_TRUE(IsFieldEqual(abs(expr, "RGN_ALL"), 1.0)); - } + const Field3DParallel prodpar = field.asField3DParallel() * rhs; + EXPECT_TRUE((std::is_same_v, Field3DParallel>)); + EXPECT_TRUE(prodpar.hasParallelSlices()); + EXPECT_TRUE(IsFieldEqual(prodpar, 6.0)); + EXPECT_TRUE(IsFieldEqual(prodpar.yup(), 12.0, "RGN_YPAR_+1")); + EXPECT_TRUE(IsFieldEqual(prodpar.ydown(), 20.0, "RGN_YPAR_-1")); +} - TEST_F(Field3DTest, RegionLimitedExpressionConstructsField3D) { - Field3D field; +TEST_F(Field3DTest, Abs) { + Field3D field; - field = -31.0; + field = -31.0; + EXPECT_TRUE(IsFieldEqual(abs(field), 31.0)); +} - Field3D result = abs(field, "RGN_NOBNDRY"); +TEST_F(Field3DTest, AbsExpressionUsesAbsOp) { + Field3D field; - EXPECT_TRUE(IsFieldEqual(result, 31.0, "RGN_NOBNDRY")); - } + field = -2.0; + const auto expr = field + 1.0; - TEST_F(Field3DTest, Exp) { - Field3D field; + EXPECT_TRUE((std::is_same_v, + BinaryExpr, + std::decay_t, bout::op::abs>>)); + EXPECT_TRUE(IsFieldEqual(abs(expr), 1.0)); + EXPECT_TRUE(IsFieldEqual(abs(expr, "RGN_ALL"), 1.0)); +} - field = 2.5; - const BoutReal expected = 12.182493960703473; - EXPECT_TRUE(IsFieldEqual(exp(field), expected)); - } +TEST_F(Field3DTest, RegionLimitedExpressionConstructsField3D) { + Field3D field; - TEST_F(Field3DTest, Log) { - Field3D field; + field = -31.0; - field = 12.182493960703473; - const BoutReal expected = 2.5; - EXPECT_TRUE(IsFieldEqual(log(field), expected)); - } + Field3D result = abs(field, "RGN_NOBNDRY"); - TEST_F(Field3DTest, LogExp) { - Field3D field; + EXPECT_TRUE(IsFieldEqual(result, 31.0, "RGN_NOBNDRY")); +} - field = 2.5; - const BoutReal expected = 2.5; - EXPECT_TRUE(IsFieldEqual(log(exp(field)), expected)); - } +TEST_F(Field3DTest, Exp) { + Field3D field; - TEST_F(Field3DTest, Sin) { - Field3D field; + field = 2.5; + const BoutReal expected = 12.182493960703473; + EXPECT_TRUE(IsFieldEqual(exp(field), expected)); +} - field = PI / 2.0; - EXPECT_TRUE(IsFieldEqual(sin(field), 1.0)); +TEST_F(Field3DTest, Log) { + Field3D field; - field = PI; - EXPECT_TRUE(IsFieldEqual(sin(field), 0.0)); - } + field = 12.182493960703473; + const BoutReal expected = 2.5; + EXPECT_TRUE(IsFieldEqual(log(field), expected)); +} - TEST_F(Field3DTest, Cos) { - Field3D field; +TEST_F(Field3DTest, LogExp) { + Field3D field; - field = PI / 2.0; - EXPECT_TRUE(IsFieldEqual(cos(field), 0.0)); + field = 2.5; + const BoutReal expected = 2.5; + EXPECT_TRUE(IsFieldEqual(log(exp(field)), expected)); +} - field = PI; - EXPECT_TRUE(IsFieldEqual(cos(field), -1.0)); - } +TEST_F(Field3DTest, Sin) { + Field3D field; - TEST_F(Field3DTest, Tan) { - Field3D field; + field = PI / 2.0; + EXPECT_TRUE(IsFieldEqual(sin(field), 1.0)); - field = PI / 4.0; - EXPECT_TRUE(IsFieldEqual(tan(field), 1.0)); + field = PI; + EXPECT_TRUE(IsFieldEqual(sin(field), 0.0)); +} - field = PI; - EXPECT_TRUE(IsFieldEqual(tan(field), 0.0)); - } +TEST_F(Field3DTest, Cos) { + Field3D field; - TEST_F(Field3DTest, Sinh) { - Field3D field; + field = PI / 2.0; + EXPECT_TRUE(IsFieldEqual(cos(field), 0.0)); - field = 1.0; - const BoutReal expected = 1.1752011936438014; - EXPECT_TRUE(IsFieldEqual(sinh(field), expected)); + field = PI; + EXPECT_TRUE(IsFieldEqual(cos(field), -1.0)); +} - field = -1.0; - EXPECT_TRUE(IsFieldEqual(sinh(field), -expected)); - } +TEST_F(Field3DTest, Tan) { + Field3D field; - TEST_F(Field3DTest, Cosh) { - Field3D field; + field = PI / 4.0; + EXPECT_TRUE(IsFieldEqual(tan(field), 1.0)); - field = 1.0; - const BoutReal expected = 1.5430806348152437; - EXPECT_TRUE(IsFieldEqual(cosh(field), expected)); + field = PI; + EXPECT_TRUE(IsFieldEqual(tan(field), 0.0)); +} - field = -1.0; - EXPECT_TRUE(IsFieldEqual(cosh(field), expected)); - } +TEST_F(Field3DTest, Sinh) { + Field3D field; - TEST_F(Field3DTest, Tanh) { - Field3D field; + field = 1.0; + const BoutReal expected = 1.1752011936438014; + EXPECT_TRUE(IsFieldEqual(sinh(field), expected)); - field = 1.0; - const BoutReal expected = 0.761594155955764; - EXPECT_TRUE(IsFieldEqual(tanh(field), expected)); + field = -1.0; + EXPECT_TRUE(IsFieldEqual(sinh(field), -expected)); +} - field = -1.0; - EXPECT_TRUE(IsFieldEqual(tanh(field), -expected)); - } +TEST_F(Field3DTest, Cosh) { + Field3D field; - TEST_F(Field3DTest, Floor) { - Field3D field; + field = 1.0; + const BoutReal expected = 1.5430806348152437; + EXPECT_TRUE(IsFieldEqual(cosh(field), expected)); - field = 50.0; - field(1, 1, 1) = 49.9; - field(2, 3, 4) = -20; + field = -1.0; + EXPECT_TRUE(IsFieldEqual(cosh(field), expected)); +} - const BoutReal floor_value = 50.0; +TEST_F(Field3DTest, Tanh) { + Field3D field; - EXPECT_TRUE(IsFieldEqual(floor(field, floor_value), floor_value)); - } + field = 1.0; + const BoutReal expected = 0.761594155955764; + EXPECT_TRUE(IsFieldEqual(tanh(field), expected)); - TEST_F(Field3DTest, Min) { - Field3D field; + field = -1.0; + EXPECT_TRUE(IsFieldEqual(tanh(field), -expected)); +} - field = 50.0; - field(0, 0, 0) = -99.0; - field(1, 1, 1) = 60.0; - field(1, 2, 2) = 40.0; - field(2, 4, 3) = 99.0; +TEST_F(Field3DTest, Floor) { + Field3D field; - // min doesn't include guard cells - const BoutReal min_value = 40.0; + field = 50.0; + field(1, 1, 1) = 49.9; + field(2, 3, 4) = -20; - EXPECT_EQ(min(field, false), min_value); - EXPECT_EQ(min(field, false, "RGN_ALL"), -99.0); - EXPECT_EQ(min(field, true, "RGN_ALL"), -99.0); - } + const BoutReal floor_value = 50.0; - TEST_F(Field3DTest, MinBinaryExpr) { - Field3D field; + EXPECT_TRUE(IsFieldEqual(floor(field, floor_value), floor_value)); +} - field = 50.0; - field(0, 0, 0) = -99.0; - field(1, 1, 1) = 60.0; - field(1, 2, 2) = 40.0; - field(2, 4, 3) = 99.0; +TEST_F(Field3DTest, Min) { + Field3D field; - const auto expr = field / 2.0 - 5.0; + field = 50.0; + field(0, 0, 0) = -99.0; + field(1, 1, 1) = 60.0; + field(1, 2, 2) = 40.0; + field(2, 4, 3) = 99.0; - EXPECT_EQ(min(expr, false), 15.0); - EXPECT_EQ(min(expr, false, "RGN_ALL"), -54.5); - } + // min doesn't include guard cells + const BoutReal min_value = 40.0; - TEST_F(Field3DTest, Max) { - Field3D field; + EXPECT_EQ(min(field, false), min_value); + EXPECT_EQ(min(field, false, "RGN_ALL"), -99.0); + EXPECT_EQ(min(field, true, "RGN_ALL"), -99.0); +} - field = 50.0; - field(0, 0, 0) = -99.0; - field(1, 1, 1) = 40.0; - field(1, 2, 2) = 60.0; - field(2, 4, 3) = 99.0; +TEST_F(Field3DTest, MinBinaryExpr) { + Field3D field; - // max doesn't include guard cells - const BoutReal max_value = 60.0; + field = 50.0; + field(0, 0, 0) = -99.0; + field(1, 1, 1) = 60.0; + field(1, 2, 2) = 40.0; + field(2, 4, 3) = 99.0; - EXPECT_EQ(max(field, false), max_value); - EXPECT_EQ(max(field, false, "RGN_ALL"), 99.0); - EXPECT_EQ(max(field, true, "RGN_ALL"), 99.0); - } + const auto expr = field / 2.0 - 5.0; - TEST_F(Field3DTest, MaxBinaryExpr) { - Field3D field; + EXPECT_EQ(min(expr, false), 15.0); + EXPECT_EQ(min(expr, false, "RGN_ALL"), -54.5); +} - field = 50.0; - field(0, 0, 0) = -99.0; - field(1, 1, 1) = 40.0; - field(1, 2, 2) = 60.0; - field(2, 4, 3) = 99.0; +TEST_F(Field3DTest, Max) { + Field3D field; - const auto expr = field / 2.0 - 5.0; + field = 50.0; + field(0, 0, 0) = -99.0; + field(1, 1, 1) = 40.0; + field(1, 2, 2) = 60.0; + field(2, 4, 3) = 99.0; - EXPECT_EQ(max(expr, false), 25.0); - EXPECT_EQ(max(expr, false, "RGN_ALL"), 44.5); - } + // max doesn't include guard cells + const BoutReal max_value = 60.0; - TEST_F(Field3DTest, Mean) { - Field3D field; + EXPECT_EQ(max(field, false), max_value); + EXPECT_EQ(max(field, false, "RGN_ALL"), 99.0); + EXPECT_EQ(max(field, true, "RGN_ALL"), 99.0); +} - field = 50.0; - field(0, 0, 0) = 1.0; - field(1, 1, 1) = 40.0; - field(1, 2, 2) = 60.0; - field(2, 4, 3) = 109.0; +TEST_F(Field3DTest, MaxBinaryExpr) { + Field3D field; - // mean doesn't include guard cells by default - const int npoints_all = nx * ny * nz; - const BoutReal mean_value_nobndry = 50.0; - const BoutReal mean_value_all = 50.0 + 10.0 / npoints_all; + field = 50.0; + field(0, 0, 0) = -99.0; + field(1, 1, 1) = 40.0; + field(1, 2, 2) = 60.0; + field(2, 4, 3) = 99.0; - EXPECT_EQ(mean(field, false), mean_value_nobndry); - EXPECT_EQ(mean(field, false, "RGN_ALL"), mean_value_all); - EXPECT_EQ(mean(field, true, "RGN_ALL"), mean_value_all); - } + const auto expr = field / 2.0 - 5.0; - TEST_F(Field3DTest, MeanBinaryExpr) { - Field3D field; + EXPECT_EQ(max(expr, false), 25.0); + EXPECT_EQ(max(expr, false, "RGN_ALL"), 44.5); +} - field = 50.0; - field(0, 0, 0) = 1.0; - field(1, 1, 1) = 40.0; - field(1, 2, 2) = 60.0; - field(2, 4, 3) = 109.0; +TEST_F(Field3DTest, Mean) { + Field3D field; - const int npoints_all = nx * ny * nz; - const BoutReal mean_value_nobndry = 103.0; - const BoutReal mean_value_all = 103.0 + 20.0 / npoints_all; - const auto expr = field * 2.0 + 3.0; + field = 50.0; + field(0, 0, 0) = 1.0; + field(1, 1, 1) = 40.0; + field(1, 2, 2) = 60.0; + field(2, 4, 3) = 109.0; - EXPECT_EQ(mean(expr, false), mean_value_nobndry); - EXPECT_EQ(mean(expr, false, "RGN_ALL"), mean_value_all); - } + // mean doesn't include guard cells by default + const int npoints_all = nx * ny * nz; + const BoutReal mean_value_nobndry = 50.0; + const BoutReal mean_value_all = 50.0 + 10.0 / npoints_all; - TEST_F(Field3DTest, DC) { - Field3D field; + EXPECT_EQ(mean(field, false), mean_value_nobndry); + EXPECT_EQ(mean(field, false, "RGN_ALL"), mean_value_all); + EXPECT_EQ(mean(field, true, "RGN_ALL"), mean_value_all); +} - field = 1.0; - for (const auto& i : field) { - field[i] = i.z(); - } +TEST_F(Field3DTest, MeanBinaryExpr) { + Field3D field; + + field = 50.0; + field(0, 0, 0) = 1.0; + field(1, 1, 1) = 40.0; + field(1, 2, 2) = 60.0; + field(2, 4, 3) = 109.0; + + const int npoints_all = nx * ny * nz; + const BoutReal mean_value_nobndry = 103.0; + const BoutReal mean_value_all = 103.0 + 20.0 / npoints_all; + const auto expr = field * 2.0 + 3.0; + + EXPECT_EQ(mean(expr, false), mean_value_nobndry); + EXPECT_EQ(mean(expr, false, "RGN_ALL"), mean_value_all); +} + +TEST_F(Field3DTest, DC) { + Field3D field; - EXPECT_TRUE(IsFieldEqual(DC(field), 3.0)); + field = 1.0; + for (const auto& i : field) { + field[i] = i.z(); } - TEST_F(Field3DTest, Swap) { - WithQuietOutput quiet{output_info}; + EXPECT_TRUE(IsFieldEqual(DC(field), 3.0)); +} - // First field - Field3D first(1., mesh_staggered); +TEST_F(Field3DTest, Swap) { + WithQuietOutput quiet{output_info}; - first.setLocation(CELL_XLOW); + // First field + Field3D first(1., mesh_staggered); - first.splitParallelSlices(); - first.yup() = 1.5; - first.ydown() = 0.5; + first.setLocation(CELL_XLOW); - ddt(first) = 1.1; + first.splitParallelSlices(); + first.yup() = 1.5; + first.ydown() = 0.5; - // Mesh for second field - constexpr int second_nx = Field3DTest::nx + 2; - constexpr int second_ny = Field3DTest::ny + 2; - constexpr int second_nz = Field3DTest::nz + 2; + ddt(first) = 1.1; - FakeMesh second_mesh{second_nx, second_ny, second_nz}; - second_mesh.setCoordinates(nullptr); - second_mesh.StaggerGrids = false; - second_mesh.createDefaultRegions(); + // Mesh for second field + constexpr int second_nx = Field3DTest::nx + 2; + constexpr int second_ny = Field3DTest::ny + 2; + constexpr int second_nz = Field3DTest::nz + 2; - // Second field - Field3D second(2., &second_mesh); + FakeMesh second_mesh{second_nx, second_ny, second_nz}; + second_mesh.setCoordinates(nullptr); + second_mesh.StaggerGrids = false; + second_mesh.createDefaultRegions(); - second.splitParallelSlices(); - second.yup() = 2.2; - second.ydown() = 1.2; + // Second field + Field3D second(2., &second_mesh); - ddt(second) = 2.4; + second.splitParallelSlices(); + second.yup() = 2.2; + second.ydown() = 1.2; - // Basic sanity check - EXPECT_TRUE(IsFieldEqual(first, 1.0)); - EXPECT_TRUE(IsFieldEqual(second, 2.0)); + ddt(second) = 2.4; - // swap is marked noexcept, so absolutely should not throw! - ASSERT_NO_THROW(swap(first, second)); + // Basic sanity check + EXPECT_TRUE(IsFieldEqual(first, 1.0)); + EXPECT_TRUE(IsFieldEqual(second, 2.0)); - // Values - EXPECT_TRUE(IsFieldEqual(first, 2.0)); - EXPECT_TRUE(IsFieldEqual(second, 1.0)); + // swap is marked noexcept, so absolutely should not throw! + ASSERT_NO_THROW(swap(first, second)); - EXPECT_TRUE(IsFieldEqual(first.yup(), 2.2)); - EXPECT_TRUE(IsFieldEqual(first.ydown(), 1.2)); + // Values + EXPECT_TRUE(IsFieldEqual(first, 2.0)); + EXPECT_TRUE(IsFieldEqual(second, 1.0)); - EXPECT_TRUE(IsFieldEqual(second.yup(), 1.5)); - EXPECT_TRUE(IsFieldEqual(second.ydown(), 0.5)); + EXPECT_TRUE(IsFieldEqual(first.yup(), 2.2)); + EXPECT_TRUE(IsFieldEqual(first.ydown(), 1.2)); - EXPECT_TRUE(IsFieldEqual(ddt(first), 2.4)); - EXPECT_TRUE(IsFieldEqual(ddt(second), 1.1)); + EXPECT_TRUE(IsFieldEqual(second.yup(), 1.5)); + EXPECT_TRUE(IsFieldEqual(second.ydown(), 0.5)); - // Mesh properties - EXPECT_EQ(first.getMesh(), &second_mesh); - EXPECT_EQ(second.getMesh(), mesh_staggered); + EXPECT_TRUE(IsFieldEqual(ddt(first), 2.4)); + EXPECT_TRUE(IsFieldEqual(ddt(second), 1.1)); - EXPECT_EQ(first.getNx(), second_nx); - EXPECT_EQ(first.getNy(), second_ny); - EXPECT_EQ(first.getNz(), second_nz); + // Mesh properties + EXPECT_EQ(first.getMesh(), &second_mesh); + EXPECT_EQ(second.getMesh(), mesh_staggered); - EXPECT_EQ(second.getNx(), Field3DTest::nx); - EXPECT_EQ(second.getNy(), Field3DTest::ny); - EXPECT_EQ(second.getNz(), Field3DTest::nz); + EXPECT_EQ(first.getNx(), second_nx); + EXPECT_EQ(first.getNy(), second_ny); + EXPECT_EQ(first.getNz(), second_nz); - EXPECT_EQ(first.getLocation(), CELL_CENTRE); - EXPECT_EQ(second.getLocation(), CELL_XLOW); + EXPECT_EQ(second.getNx(), Field3DTest::nx); + EXPECT_EQ(second.getNy(), Field3DTest::ny); + EXPECT_EQ(second.getNz(), Field3DTest::nz); - // We don't check the boundaries, but the data is protected and - // there are no inquiry functions - } + EXPECT_EQ(first.getLocation(), CELL_CENTRE); + EXPECT_EQ(second.getLocation(), CELL_XLOW); - TEST_F(Field3DTest, MoveCtor) { - // First field - Field3D first(1., mesh_staggered); + // We don't check the boundaries, but the data is protected and + // there are no inquiry functions +} - first.setLocation(CELL_XLOW); +TEST_F(Field3DTest, MoveCtor) { + // First field + Field3D first(1., mesh_staggered); - first.splitParallelSlices(); - first.yup() = 1.5; - first.ydown() = 0.5; + first.setLocation(CELL_XLOW); - ddt(first) = 1.1; + first.splitParallelSlices(); + first.yup() = 1.5; + first.ydown() = 0.5; - // Second field - Field3D second{std::move(first)}; + ddt(first) = 1.1; - // Values - EXPECT_TRUE(IsFieldEqual(second, 1.0)); + // Second field + Field3D second{std::move(first)}; - EXPECT_TRUE(IsFieldEqual(second.yup(), 1.5)); - EXPECT_TRUE(IsFieldEqual(second.ydown(), 0.5)); + // Values + EXPECT_TRUE(IsFieldEqual(second, 1.0)); - EXPECT_TRUE(IsFieldEqual(ddt(second), 1.1)); + EXPECT_TRUE(IsFieldEqual(second.yup(), 1.5)); + EXPECT_TRUE(IsFieldEqual(second.ydown(), 0.5)); - // Mesh properties - EXPECT_EQ(second.getMesh(), mesh_staggered); + EXPECT_TRUE(IsFieldEqual(ddt(second), 1.1)); - EXPECT_EQ(second.getNx(), Field3DTest::nx); - EXPECT_EQ(second.getNy(), Field3DTest::ny); - EXPECT_EQ(second.getNz(), Field3DTest::nz); + // Mesh properties + EXPECT_EQ(second.getMesh(), mesh_staggered); - EXPECT_EQ(second.getLocation(), CELL_XLOW); + EXPECT_EQ(second.getNx(), Field3DTest::nx); + EXPECT_EQ(second.getNy(), Field3DTest::ny); + EXPECT_EQ(second.getNz(), Field3DTest::nz); - // We don't check the boundaries, but the data is protected and - // there are no inquiry functions - } + EXPECT_EQ(second.getLocation(), CELL_XLOW); - TEST_F(Field3DTest, FillField) { - Field3D f{mesh}; - - fillField(f, {{{1., 1., 1., 1., 1., 1., 1.}, - {1., 1., 1., 1., 1., 1., 1.}, - {1., 1., 1., 1., 1., 1., 1.}, - {1., 1., 1., 1., 1., 1., 1.}, - {1., 1., 1., 1., 1., 1., 1.}}, - - {{1., 1., 1., 1., 1., 1., 1.}, - {1., 1., 1., 1., 1., 1., 1.}, - {1., 1., 1., 1., 1., 1., 1.}, - {1., 1., 1., 1., 1., 1., 1.}, - {1., 1., 1., 1., 1., 1., 1.}}, - - {{1., 1., 1., 1., 1., 1., 1.}, - {1., 1., 1., 1., 1., 1., 1.}, - {1., 1., 1., 1., 1., 1., 1.}, - {1., 1., 1., 1., 1., 1., 1.}, - {1., 1., 1., 1., 1., 1., 1.}}}); - - EXPECT_TRUE(IsFieldEqual(f, 1.)); - - fillField(f, {{{0., 1., 2., 3., 4., 5., 6.}, - {0., 1., 2., 3., 4., 5., 6.}, - {0., 1., 2., 3., 4., 5., 6.}, - {0., 1., 2., 3., 4., 5., 6.}, - {0., 1., 2., 3., 4., 5., 6.}}, - - {{0., 1., 2., 3., 4., 5., 6.}, - {0., 1., 2., 3., 4., 5., 6.}, - {0., 1., 2., 3., 4., 5., 6.}, - {0., 1., 2., 3., 4., 5., 6.}, - {0., 1., 2., 3., 4., 5., 6.}}, - - {{0., 1., 2., 3., 4., 5., 6.}, - {0., 1., 2., 3., 4., 5., 6.}, - {0., 1., 2., 3., 4., 5., 6.}, - {0., 1., 2., 3., 4., 5., 6.}, - {0., 1., 2., 3., 4., 5., 6.}}}); - - Field3D g{mesh}; - g.allocate(); - BOUT_FOR_SERIAL(i, g.getRegion("RGN_ALL")) { g[i] = i.z(); } - - EXPECT_TRUE(IsFieldEqual(f, g)); - } + // We don't check the boundaries, but the data is protected and + // there are no inquiry functions +} -#if BOUT_HAS_FFTW - namespace bout { - namespace testing { +TEST_F(Field3DTest, FillField) { + Field3D f{mesh}; - // Amplitudes for the nth wavenumber - constexpr int k0{1}; - constexpr int k1{2}; - constexpr int k2{3}; + fillField(f, {{{1., 1., 1., 1., 1., 1., 1.}, + {1., 1., 1., 1., 1., 1., 1.}, + {1., 1., 1., 1., 1., 1., 1.}, + {1., 1., 1., 1., 1., 1., 1.}, + {1., 1., 1., 1., 1., 1., 1.}}, - const BoutReal box_size{TWOPI / Field3DTest::nz}; + {{1., 1., 1., 1., 1., 1., 1.}, + {1., 1., 1., 1., 1., 1., 1.}, + {1., 1., 1., 1., 1., 1., 1.}, + {1., 1., 1., 1., 1., 1., 1.}, + {1., 1., 1., 1., 1., 1., 1.}}, - // Helper function for the filter and lowpass tests - BoutReal zWaves(Field3D::ind_type& i) { - return 1.0 + std::sin(k0 * i.z() * box_size) + std::cos(k1 * i.z() * box_size) - + std::sin(k2 * i.z() * box_size); - } - } // namespace testing - } // namespace bout + {{1., 1., 1., 1., 1., 1., 1.}, + {1., 1., 1., 1., 1., 1., 1.}, + {1., 1., 1., 1., 1., 1., 1.}, + {1., 1., 1., 1., 1., 1., 1.}, + {1., 1., 1., 1., 1., 1., 1.}}}); - TEST_F(Field3DTest, Filter) { + EXPECT_TRUE(IsFieldEqual(f, 1.)); - using namespace bout::testing; + fillField(f, {{{0., 1., 2., 3., 4., 5., 6.}, + {0., 1., 2., 3., 4., 5., 6.}, + {0., 1., 2., 3., 4., 5., 6.}, + {0., 1., 2., 3., 4., 5., 6.}, + {0., 1., 2., 3., 4., 5., 6.}}, - auto input = makeField(zWaves, bout::globals::mesh); + {{0., 1., 2., 3., 4., 5., 6.}, + {0., 1., 2., 3., 4., 5., 6.}, + {0., 1., 2., 3., 4., 5., 6.}, + {0., 1., 2., 3., 4., 5., 6.}, + {0., 1., 2., 3., 4., 5., 6.}}, - auto expected = makeField( - [&](Field3D::ind_type& i) { return std::cos(k1 * i.z() * box_size); }, - bout::globals::mesh); + {{0., 1., 2., 3., 4., 5., 6.}, + {0., 1., 2., 3., 4., 5., 6.}, + {0., 1., 2., 3., 4., 5., 6.}, + {0., 1., 2., 3., 4., 5., 6.}, + {0., 1., 2., 3., 4., 5., 6.}}}); - auto output = filter(input, 2); + Field3D g{mesh}; + g.allocate(); + BOUT_FOR_SERIAL(i, g.getRegion("RGN_ALL")) { g[i] = i.z(); } - EXPECT_TRUE(IsFieldEqual(output, expected)); - } + EXPECT_TRUE(IsFieldEqual(f, g)); +} - TEST_F(Field3DTest, LowPassOneArg) { +#if BOUT_HAS_FFTW +namespace bout { +namespace testing { - using namespace bout::testing; +// Amplitudes for the nth wavenumber +constexpr int k0{1}; +constexpr int k1{2}; +constexpr int k2{3}; - auto input = makeField(zWaves, bout::globals::mesh); +const BoutReal box_size{TWOPI / Field3DTest::nz}; - auto expected = makeField( - [&](Field3D::ind_type& i) { - return 1.0 + std::sin(k0 * i.z() * box_size) + std::cos(k1 * i.z() * box_size); - }, - bout::globals::mesh); +// Helper function for the filter and lowpass tests +BoutReal zWaves(Field3D::ind_type& i) { + return 1.0 + std::sin(k0 * i.z() * box_size) + std::cos(k1 * i.z() * box_size) + + std::sin(k2 * i.z() * box_size); +} +} // namespace testing +} // namespace bout - auto output = lowPass(input, 2); +TEST_F(Field3DTest, Filter) { - EXPECT_TRUE(IsFieldEqual(output, expected)); - } + using namespace bout::testing; - TEST_F(Field3DTest, LowPassOneArgNothing) { + auto input = makeField(zWaves, bout::globals::mesh); - using namespace bout::testing; + auto expected = makeField( + [&](Field3D::ind_type& i) { return std::cos(k1 * i.z() * box_size); }, + bout::globals::mesh); - auto input = makeField(zWaves, bout::globals::mesh); + auto output = filter(input, 2); - auto output = lowPass(input, 20); + EXPECT_TRUE(IsFieldEqual(output, expected)); +} - EXPECT_TRUE(IsFieldEqual(output, input)); - } +TEST_F(Field3DTest, LowPassOneArg) { - TEST_F(Field3DTest, LowPassTwoArg) { + using namespace bout::testing; - using namespace bout::testing; + auto input = makeField(zWaves, bout::globals::mesh); - auto input = makeField(zWaves, bout::globals::mesh); + auto expected = makeField( + [&](Field3D::ind_type& i) { + return 1.0 + std::sin(k0 * i.z() * box_size) + std::cos(k1 * i.z() * box_size); + }, + bout::globals::mesh); - auto expected = makeField( - [&](Field3D::ind_type& i) { - return std::sin(k0 * i.z() * box_size) + std::cos(k1 * i.z() * box_size); - }, - bout::globals::mesh); + auto output = lowPass(input, 2); - auto output = lowPass(input, 2, false); + EXPECT_TRUE(IsFieldEqual(output, expected)); +} - EXPECT_TRUE(IsFieldEqual(output, expected)); +TEST_F(Field3DTest, LowPassOneArgNothing) { - // Check passing int still works - auto output2 = lowPass(input, 2, 0); + using namespace bout::testing; - EXPECT_TRUE(IsFieldEqual(output2, expected)); + auto input = makeField(zWaves, bout::globals::mesh); - // Calling lowPass with an int that is not 0 or 1 is an error - EXPECT_THROW(lowPass(input, 2, -1), BoutException); - EXPECT_THROW(lowPass(input, 2, 2), BoutException); - } + auto output = lowPass(input, 20); - TEST_F(Field3DTest, LowPassTwoArgKeepZonal) { + EXPECT_TRUE(IsFieldEqual(output, input)); +} - using namespace bout::testing; +TEST_F(Field3DTest, LowPassTwoArg) { - auto input = makeField(zWaves, bout::globals::mesh); + using namespace bout::testing; - auto expected = makeField( - [&](Field3D::ind_type& i) { - return 1.0 + std::sin(k0 * i.z() * box_size) + std::cos(k1 * i.z() * box_size); - }, - bout::globals::mesh); + auto input = makeField(zWaves, bout::globals::mesh); - auto output = lowPass(input, 2, true); + auto expected = makeField( + [&](Field3D::ind_type& i) { + return std::sin(k0 * i.z() * box_size) + std::cos(k1 * i.z() * box_size); + }, + bout::globals::mesh); - EXPECT_TRUE(IsFieldEqual(output, expected)); + auto output = lowPass(input, 2, false); - // Check passing int still works - auto output2 = lowPass(input, 2, 1); + EXPECT_TRUE(IsFieldEqual(output, expected)); - EXPECT_TRUE(IsFieldEqual(output2, expected)); - } + // Check passing int still works + auto output2 = lowPass(input, 2, 0); - TEST_F(Field3DTest, LowPassTwoArgNothing) { + EXPECT_TRUE(IsFieldEqual(output2, expected)); - using namespace bout::testing; + // Calling lowPass with an int that is not 0 or 1 is an error + EXPECT_THROW(lowPass(input, 2, -1), BoutException); + EXPECT_THROW(lowPass(input, 2, 2), BoutException); +} - auto input = makeField(zWaves, bout::globals::mesh); +TEST_F(Field3DTest, LowPassTwoArgKeepZonal) { - auto output = lowPass(input, 20, true); + using namespace bout::testing; - EXPECT_TRUE(IsFieldEqual(output, input)); - } -#endif + auto input = makeField(zWaves, bout::globals::mesh); - TEST_F(Field3DTest, OperatorEqualsField3D) { - Field3D field; + auto expected = makeField( + [&](Field3D::ind_type& i) { + return 1.0 + std::sin(k0 * i.z() * box_size) + std::cos(k1 * i.z() * box_size); + }, + bout::globals::mesh); - // Create field with non-default arguments so we can check they get copied - // to 'field'. - // Note that Average z-direction type is not really allowed for Field3D, but - // we don't check anywhere at the moment. - Field3D field2{ - mesh_staggered, CELL_XLOW, {YDirectionType::Aligned, ZDirectionType::Average}}; + auto output = lowPass(input, 2, true); - field = field2; + EXPECT_TRUE(IsFieldEqual(output, expected)); - EXPECT_TRUE(areFieldsCompatible(field, field2)); - EXPECT_EQ(field.getMesh(), field2.getMesh()); - EXPECT_EQ(field.getLocation(), field2.getLocation()); - EXPECT_EQ(field.getDirectionY(), field2.getDirectionY()); - EXPECT_EQ(field.getDirectionZ(), field2.getDirectionZ()); - } + // Check passing int still works + auto output2 = lowPass(input, 2, 1); - TEST_F(Field3DTest, OperatorEqualsBinaryExprCopiesMetadata) { - Field3D source{ - mesh_staggered, CELL_XLOW, {YDirectionType::Aligned, ZDirectionType::Average}}; - source = 9.; + EXPECT_TRUE(IsFieldEqual(output2, expected)); +} - Field3D target(mesh_staggered); - target = 0.; - target.splitParallelSlices(); +TEST_F(Field3DTest, LowPassTwoArgNothing) { - target = sqrt(source); + using namespace bout::testing; - EXPECT_EQ(target.getMesh(), source.getMesh()); - EXPECT_EQ(target.getLocation(), source.getLocation()); - EXPECT_EQ(target.getDirectionY(), source.getDirectionY()); - EXPECT_EQ(target.getDirectionZ(), source.getDirectionZ()); - EXPECT_FALSE(target.hasParallelSlices()); - EXPECT_TRUE(IsFieldEqual(target, 3.)); - } + auto input = makeField(zWaves, bout::globals::mesh); - TEST_F(Field3DTest, EmptyFrom) { - // Create field with non-default arguments so we can check they get copied - // to 'field2'. - // Note that Average z-direction type is not really allowed for Field3D, but - // we don't check anywhere at the moment. - Field3D field{ - mesh_staggered, CELL_XLOW, {YDirectionType::Aligned, ZDirectionType::Average}}; - field = 5.; - - Field3D field2{emptyFrom(field)}; - EXPECT_EQ(field2.getMesh(), mesh_staggered); - EXPECT_EQ(field2.getLocation(), CELL_XLOW); - EXPECT_EQ(field2.getDirectionY(), YDirectionType::Aligned); - EXPECT_EQ(field2.getDirectionZ(), ZDirectionType::Average); - EXPECT_TRUE(field2.isAllocated()); - } + auto output = lowPass(input, 20, true); - TEST_F(Field3DTest, ZeroFrom) { - // Create field with non-default arguments so we can check they get copied - // to 'field2'. - // Note that Average z-direction type is not really allowed for Field3D, but - // we don't check anywhere at the moment. - Field3D field{ - mesh_staggered, CELL_XLOW, {YDirectionType::Aligned, ZDirectionType::Average}}; - field = 5.; - - Field3D field2{zeroFrom(field)}; - EXPECT_EQ(field2.getMesh(), mesh_staggered); - EXPECT_EQ(field2.getLocation(), CELL_XLOW); - EXPECT_EQ(field2.getDirectionY(), YDirectionType::Aligned); - EXPECT_EQ(field2.getDirectionZ(), ZDirectionType::Average); - EXPECT_TRUE(field2.isAllocated()); - EXPECT_TRUE(IsFieldEqual(field2, 0.)); - } + EXPECT_TRUE(IsFieldEqual(output, input)); +} +#endif - TEST_F(Field3DTest, Field3DParallel) { - Field3DParallel field(1.0); - field = 1.0; +TEST_F(Field3DTest, OperatorEqualsField3D) { + Field3D field; - Field3D field2 = field; + // Create field with non-default arguments so we can check they get copied + // to 'field'. + // Note that Average z-direction type is not really allowed for Field3D, but + // we don't check anywhere at the moment. + Field3D field2{ + mesh_staggered, CELL_XLOW, {YDirectionType::Aligned, ZDirectionType::Average}}; - auto& field3 = field.asField3D(); + field = field2; - field *= 2; + EXPECT_TRUE(areFieldsCompatible(field, field2)); + EXPECT_EQ(field.getMesh(), field2.getMesh()); + EXPECT_EQ(field.getLocation(), field2.getLocation()); + EXPECT_EQ(field.getDirectionY(), field2.getDirectionY()); + EXPECT_EQ(field.getDirectionZ(), field2.getDirectionZ()); +} + +TEST_F(Field3DTest, OperatorEqualsBinaryExprCopiesMetadata) { + Field3D source{ + mesh_staggered, CELL_XLOW, {YDirectionType::Aligned, ZDirectionType::Average}}; + source = 9.; + + Field3D target(mesh_staggered); + target = 0.; + target.splitParallelSlices(); + + target = sqrt(source); + + EXPECT_EQ(target.getMesh(), source.getMesh()); + EXPECT_EQ(target.getLocation(), source.getLocation()); + EXPECT_EQ(target.getDirectionY(), source.getDirectionY()); + EXPECT_EQ(target.getDirectionZ(), source.getDirectionZ()); + EXPECT_FALSE(target.hasParallelSlices()); + EXPECT_TRUE(IsFieldEqual(target, 3.)); +} + +TEST_F(Field3DTest, EmptyFrom) { + // Create field with non-default arguments so we can check they get copied + // to 'field2'. + // Note that Average z-direction type is not really allowed for Field3D, but + // we don't check anywhere at the moment. + Field3D field{ + mesh_staggered, CELL_XLOW, {YDirectionType::Aligned, ZDirectionType::Average}}; + field = 5.; + + Field3D field2{emptyFrom(field)}; + EXPECT_EQ(field2.getMesh(), mesh_staggered); + EXPECT_EQ(field2.getLocation(), CELL_XLOW); + EXPECT_EQ(field2.getDirectionY(), YDirectionType::Aligned); + EXPECT_EQ(field2.getDirectionZ(), ZDirectionType::Average); + EXPECT_TRUE(field2.isAllocated()); +} + +TEST_F(Field3DTest, ZeroFrom) { + // Create field with non-default arguments so we can check they get copied + // to 'field2'. + // Note that Average z-direction type is not really allowed for Field3D, but + // we don't check anywhere at the moment. + Field3D field{ + mesh_staggered, CELL_XLOW, {YDirectionType::Aligned, ZDirectionType::Average}}; + field = 5.; + + Field3D field2{zeroFrom(field)}; + EXPECT_EQ(field2.getMesh(), mesh_staggered); + EXPECT_EQ(field2.getLocation(), CELL_XLOW); + EXPECT_EQ(field2.getDirectionY(), YDirectionType::Aligned); + EXPECT_EQ(field2.getDirectionZ(), ZDirectionType::Average); + EXPECT_TRUE(field2.isAllocated()); + EXPECT_TRUE(IsFieldEqual(field2, 0.)); +} + +TEST_F(Field3DTest, Field3DParallel) { + Field3DParallel field(1.0); + field = 1.0; - EXPECT_TRUE(IsFieldEqual(field, 2.0)); - EXPECT_TRUE(IsFieldEqual(field2, 1.0)); - EXPECT_TRUE(IsFieldEqual(field3, 2.0)); + Field3D field2 = field; - field3.asField3DParallel() *= 3; + auto& field3 = field.asField3D(); - EXPECT_TRUE(IsFieldEqual(field3, 6.0)); - } + field *= 2; + + EXPECT_TRUE(IsFieldEqual(field, 2.0)); + EXPECT_TRUE(IsFieldEqual(field2, 1.0)); + EXPECT_TRUE(IsFieldEqual(field3, 2.0)); + + field3.asField3DParallel() *= 3; + + EXPECT_TRUE(IsFieldEqual(field3, 6.0)); +} // Restore compiler warnings #pragma GCC diagnostic pop From ceaaf980e6079f0370924c3ea91769b45df3c97d Mon Sep 17 00:00:00 2001 From: David Bold Date: Thu, 9 Jul 2026 11:54:06 +0200 Subject: [PATCH 026/221] Functions of Field3DParallel preserve slices More unit tests --- include/bout/field.hxx | 119 ++++++++-------- include/bout/fieldops.hxx | 2 + src/field/field3d.cxx | 5 + tests/unit/field/test_field3d.cxx | 222 +++++++++++++++++++++++++++++- 4 files changed, 289 insertions(+), 59 deletions(-) diff --git a/include/bout/field.hxx b/include/bout/field.hxx index b39a82eb0b..b20e8915e9 100644 --- a/include/bout/field.hxx +++ b/include/bout/field.hxx @@ -608,6 +608,20 @@ class Field3DParallel; class FieldPerp; namespace bout::detail { +template +using UnaryFieldResult_t = + std::conditional_t, ::Field3DParallel>, ::Field3D, + std::decay_t>; + +template +std::optional getUnaryRegionID(const Mesh* mesh, const std::string& region_name) { + if constexpr (std::is_same_v, ::Field3D>) { + return bout::detail::getField3DRegionID(mesh, region_name); + } else { + return std::nullopt; + } +} + template std::optional getPerpYIndex(const T& value) { if constexpr (std::is_same_v, ::FieldPerp>) { @@ -641,35 +655,23 @@ std::optional getPerpYIndex(const BinaryExpr& expr) { }; \ template > \ inline auto name(const T& f, const std::string& rgn = "RGN_ALL") { \ - if constexpr (std::is_same_v) { \ - /* Check if the input is allocated */ \ - checkData(f); \ - /* Define and allocate the output result */ \ - T result{emptyFrom(f)}; \ - BOUT_FOR(d, result.getRegion(rgn)) { result[d] = func(f[d]); } \ - for (int i = 0; i < f.numberParallelSlices(); ++i) { \ - result.yup(i) = func(f.yup(i)); \ - result.ydown(i) = func(f.ydown(i)); \ - } \ - result.name = std::string(#name "(") + f.name + std::string(")"); \ - checkData(result); \ - return result; \ - } else { \ - return BinaryExpr{static_cast(f), \ - static_cast(f), \ - bout::op::name{}, \ - f.getMesh(), \ - f.getLocation(), \ - f.getDirections(), \ - std::nullopt, \ - f.getRegion(rgn), \ - bout::detail::getPerpYIndex(f)}; \ - } \ + using ResT = bout::detail::UnaryFieldResult_t; \ + return BinaryExpr{ \ + static_cast(f), \ + static_cast(f), \ + bout::op::name{}, \ + f.getMesh(), \ + f.getLocation(), \ + f.getDirections(), \ + bout::detail::getUnaryRegionID(f.getMesh(), rgn), \ + f.getMesh()->template getRegion(rgn), \ + bout::detail::getPerpYIndex(f)}; \ } \ template \ inline auto name(const BinaryExpr& f) { \ - return BinaryExpr, BinaryExpr, \ - bout::op::name>{ \ + using UnaryResT = bout::detail::UnaryFieldResult_t; \ + return BinaryExpr, \ + BinaryExpr, bout::op::name>{ \ static_cast::View>(f), \ static_cast::View>(f), \ bout::op::name{}, \ @@ -682,7 +684,18 @@ std::optional getPerpYIndex(const BinaryExpr& expr) { } \ template \ inline auto name(const BinaryExpr& f, const std::string& rgn) { \ - return name(ResT{f}, rgn); \ + using UnaryResT = bout::detail::UnaryFieldResult_t; \ + return BinaryExpr, \ + BinaryExpr, bout::op::name>{ \ + static_cast::View>(f), \ + static_cast::View>(f), \ + bout::op::name{}, \ + f.getMesh(), \ + f.getLocation(), \ + f.getDirections(), \ + bout::detail::getUnaryRegionID(f.getMesh(), rgn), \ + f.getMesh()->template getRegion(rgn), \ + bout::detail::getPerpYIndex(f)}; \ } #endif @@ -698,36 +711,23 @@ struct Square { template > inline auto SQ(const T& f, const std::string& rgn = "RGN_ALL") { - if constexpr (std::is_same_v) { - checkData(f); - T result{emptyFrom(f)}; - if (f.hasParallelSlices() and !result.hasParallelSlices()) { - result.splitParallelSlices(); - } - BOUT_FOR(d, result.getRegion(rgn)) { result[d] = ::SQ(f[d]); } - for (size_t i = 0; i < f.numberParallelSlices(); ++i) { - result.yup(i) = SQ(f.yup(i), rgn); - result.ydown(i) = SQ(f.ydown(i), rgn); - } - result.name = std::string("SQ(") + f.name + std::string(")"); - checkData(result); - return result; - } else { - return BinaryExpr{static_cast(f), - static_cast(f), - bout::op::Square{}, - f.getMesh(), - f.getLocation(), - f.getDirections(), - std::nullopt, - f.getRegion(rgn), - bout::detail::getPerpYIndex(f)}; - } + using ResT = bout::detail::UnaryFieldResult_t; + return BinaryExpr{ + static_cast(f), + static_cast(f), + bout::op::Square{}, + f.getMesh(), + f.getLocation(), + f.getDirections(), + bout::detail::getUnaryRegionID(f.getMesh(), rgn), + f.getMesh()->template getRegion(rgn), + bout::detail::getPerpYIndex(f)}; } template inline auto SQ(const BinaryExpr& f) { - return BinaryExpr, BinaryExpr, + using UnaryResT = bout::detail::UnaryFieldResult_t; + return BinaryExpr, BinaryExpr, bout::op::Square>{ static_cast::View>(f), static_cast::View>(f), @@ -742,7 +742,18 @@ inline auto SQ(const BinaryExpr& f) { template inline auto SQ(const BinaryExpr& f, const std::string& rgn) { - return SQ(ResT{f}, rgn); + using UnaryResT = bout::detail::UnaryFieldResult_t; + return BinaryExpr, BinaryExpr, + bout::op::Square>{ + static_cast::View>(f), + static_cast::View>(f), + bout::op::Square{}, + f.getMesh(), + f.getLocation(), + f.getDirections(), + bout::detail::getUnaryRegionID(f.getMesh(), rgn), + f.getMesh()->template getRegion(rgn), + bout::detail::getPerpYIndex(f)}; } /// Square root of \p f over region \p rgn diff --git a/include/bout/fieldops.hxx b/include/bout/fieldops.hxx index a09734ad21..52d2c82a13 100644 --- a/include/bout/fieldops.hxx +++ b/include/bout/fieldops.hxx @@ -12,6 +12,7 @@ #include #include #include +#include #include #if BOUT_HAS_CUDA @@ -29,6 +30,7 @@ namespace bout::detail { // It is used because Mesh is an incomplete type so methods cannot be called // in the template functions in this header file. const Region& getField3DRegion(const Mesh* mesh, std::optional regionID); +size_t getField3DRegionID(const Mesh* mesh, const std::string& region_name); } // namespace bout::detail template diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index 8700096e24..5da438dcdf 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -936,6 +936,11 @@ const Region& getField3DRegion(const Mesh* mesh, std::optional re return mesh->getRegion("RGN_ALL"); } +size_t getField3DRegionID(const Mesh* mesh, const std::string& region_name) { + ASSERT1(mesh != nullptr); + return mesh->getRegionID(region_name); +} + } // namespace bout::detail void swap(Field3D& first, Field3D& second) noexcept { diff --git a/tests/unit/field/test_field3d.cxx b/tests/unit/field/test_field3d.cxx index 173991a31d..38f51a8164 100644 --- a/tests/unit/field/test_field3d.cxx +++ b/tests/unit/field/test_field3d.cxx @@ -1977,11 +1977,16 @@ TEST_F(Field3DTest, SQField3DParallelPreservesParallelSlices) { const auto squared = SQ(field); - EXPECT_TRUE((std::is_same_v, Field3DParallel>)); - EXPECT_TRUE(squared.hasParallelSlices()); - EXPECT_TRUE(IsFieldEqual(squared, 4.0)); - EXPECT_TRUE(IsFieldEqual(squared.yup(), 9.0)); - EXPECT_TRUE(IsFieldEqual(squared.ydown(), 16.0)); + EXPECT_TRUE((std::is_same_v< + std::decay_t, + BinaryExpr>)); + + Field3DParallel result{squared}; + + EXPECT_FALSE(result.hasParallelSlices()); + EXPECT_TRUE(IsFieldEqual(result, 4.0)); + EXPECT_TRUE(IsFieldEqual(result.yup(), 9.0)); + EXPECT_TRUE(IsFieldEqual(result.ydown(), 16.0)); } TEST_F(Field3DTest, Field3DParallelArithmeticReturnsLazyExpr) { @@ -2098,6 +2103,127 @@ TEST_F(Field3DTestFCI, Field3DParallelAssignmentFromScalarExprUsesSlicesInFci) { EXPECT_TRUE(IsFieldEqual(result.ydown(), 5.0)); } +TEST_F(Field3DTestFCI, SqrtField3DParallelPreservesParallelSlices) { + Field3DParallel field; + + field = 4.0; + field.splitParallelSlices(); + field.yup() = 9.0; + field.ydown() = 16.0; + field.resetRegionParallel(); + + { + const auto expr = sqrt(field); + + EXPECT_TRUE((std::is_same_v< + std::decay_t, + BinaryExpr>)); + + const Field3DParallel res{expr}; + + EXPECT_TRUE(res.hasParallelSlices()); + EXPECT_TRUE(IsFieldEqual(res, 2.0)); + EXPECT_TRUE(IsFieldEqual(res.yup(), 3.0)); + EXPECT_TRUE(IsFieldEqual(res.ydown(), 4.0)); + } + { + const Field3D res = sqrt(field.asField3D()); + + EXPECT_FALSE(res.hasParallelSlices()); + } +} + +TEST_F(Field3DTestFCI, SqrtField3DParallelExprPreservesParallelSlices) { + Field3DParallel lhs; + Field3D rhs; + + lhs = 3.0; + lhs.yup() = 8.0; + lhs.ydown() = 15.0; + + rhs = 1.0; + rhs.splitParallelSlices(); + rhs.yup() = 1.0; + rhs.ydown() = 1.0; + + Field3DParallel result{sqrt(lhs + rhs)}; + + EXPECT_TRUE(result.hasParallelSlices()); + EXPECT_TRUE(IsFieldEqual(result, 2.0)); + EXPECT_TRUE(IsFieldEqual(result.yup(), 3.0)); + EXPECT_TRUE(IsFieldEqual(result.ydown(), 4.0)); +} + +TEST_F(Field3DTestFCI, SqrtField3DParallelRegionExprPreservesParallelSlices) { + Field3DParallel lhs; + Field3D rhs; + + lhs = 3.0; + lhs.yup() = 8.0; + lhs.ydown() = 15.0; + + rhs = 1.0; + rhs.splitParallelSlices(); + rhs.yup() = 1.0; + rhs.ydown() = 1.0; + + const auto base = lhs + rhs; + const auto expr = sqrt(base, "RGN_ALL"); + + EXPECT_TRUE((std::is_same_v, + BinaryExpr, + std::decay_t, bout::op::sqrt>>)); + + Field3DParallel result{expr}; + + EXPECT_TRUE(result.hasParallelSlices()); + EXPECT_TRUE(IsFieldEqual(result, 2.0)); + EXPECT_TRUE(IsFieldEqual(result.yup(), 3.0)); + EXPECT_TRUE(IsFieldEqual(result.ydown(), 4.0)); +} + +TEST_F(Field3DTestFCI, SQField3DParallelPreservesParallelSlices) { + Field3DParallel field; + + field = 2.0; + field.yup() = 3.0; + field.ydown() = 4.0; + + const auto expr = SQ(field); + + EXPECT_TRUE((std::is_same_v< + std::decay_t, + BinaryExpr>)); + + Field3DParallel result{expr}; + + EXPECT_TRUE(result.hasParallelSlices()); + EXPECT_TRUE(IsFieldEqual(result, 4.0)); + EXPECT_TRUE(IsFieldEqual(result.yup(), 9.0)); + EXPECT_TRUE(IsFieldEqual(result.ydown(), 16.0)); +} + +TEST_F(Field3DTestFCI, SQField3DParallelExprPreservesParallelSlices) { + Field3DParallel lhs; + Field3D rhs; + + lhs = 2.0; + lhs.yup() = 3.0; + lhs.ydown() = 4.0; + + rhs = 1.0; + rhs.splitParallelSlices(); + rhs.yup() = 2.0; + rhs.ydown() = 3.0; + + Field3DParallel result{SQ(lhs + rhs)}; + + EXPECT_TRUE(result.hasParallelSlices()); + EXPECT_TRUE(IsFieldEqual(result, 9.0)); + EXPECT_TRUE(IsFieldEqual(result.yup(), 25.0)); + EXPECT_TRUE(IsFieldEqual(result.ydown(), 49.0)); +} + TEST_F(Field3DTestFCI, MulField3DParallelPreservesParallelSlices) { Field3D field; EXPECT_TRUE(field.isFci()); @@ -2129,6 +2255,92 @@ TEST_F(Field3DTestFCI, MulField3DParallelPreservesParallelSlices) { EXPECT_TRUE(IsFieldEqual(prodpar.ydown(), 20.0, "RGN_YPAR_-1")); } +TEST_F(Field3DTestFCI, DivField3DParallelPreservesParallelSlices) { + Field3D field; + + field = 2.0; + field.splitParallelSlices(); + field.yup() = 3.0; + field.ydown() = 4.0; + field.resetRegionParallel(); + + Field3DParallel rhs{1.0}; + EXPECT_TRUE(IsFieldEqual(rhs.ydown(), 1, "RGN_YPAR_-1")); + rhs *= 3.0; + EXPECT_TRUE(IsFieldEqual(rhs.ydown(), 3, "RGN_YPAR_-1")); + rhs.yup() *= 4; + EXPECT_TRUE(IsFieldEqual(rhs.ydown(), 3, "RGN_YPAR_-1")); + rhs.ydown() *= 20. / 3; + EXPECT_TRUE(IsFieldEqual(rhs.ydown(), 20, "RGN_YPAR_-1")); + + const Field3D prod = field / rhs.asField3D(); + + EXPECT_FALSE(prod.hasParallelSlices()); + EXPECT_TRUE(IsFieldEqual(prod, 2. / 3)); + + const Field3DParallel prodpar = field / rhs; + EXPECT_TRUE(prodpar.hasParallelSlices()); + EXPECT_TRUE(IsFieldEqual(prodpar, 2. / 3)); + EXPECT_TRUE(IsFieldEqual(prodpar.yup(), .25, "RGN_YPAR_+1")); + EXPECT_TRUE(IsFieldEqual(prodpar.ydown(), 4. / 20, "RGN_YPAR_-1")); +} + +TEST_F(Field3DTestFCI, AddField3DParallelPreservesParallelSlices) { + Field3D field; + field = 2.0; + field.splitParallelSlices(); + field.yup() = 3.0; + field.ydown() = 4.0; + field.resetRegionParallel(); + + Field3D rhs; + rhs = 3.0; + rhs.splitParallelSlices(); + rhs.yup() = 4.0; + rhs.ydown() = 5.0; + rhs.resetRegionParallel(); + + const Field3D res = field + rhs; + + EXPECT_FALSE(res.hasParallelSlices()); + EXPECT_TRUE(IsFieldEqual(res, 5.0)); + + const Field3DParallel respar = field.asField3DParallel() + rhs; + EXPECT_TRUE((std::is_same_v, Field3DParallel>)); + EXPECT_TRUE(respar.hasParallelSlices()); + EXPECT_TRUE(IsFieldEqual(respar, 5.0)); + EXPECT_TRUE(IsFieldEqual(respar.yup(), 7.0, "RGN_YPAR_+1")); + EXPECT_TRUE(IsFieldEqual(respar.ydown(), 9.0, "RGN_YPAR_-1")); +} + +TEST_F(Field3DTestFCI, SubField3DParallelPreservesParallelSlices) { + Field3D field; + field = 2.0; + field.splitParallelSlices(); + field.yup() = 3.0; + field.ydown() = 4.0; + field.resetRegionParallel(); + + Field3D rhs; + rhs = 3.0; + rhs.splitParallelSlices(); + rhs.yup() = 5.0; + rhs.ydown() = 7.0; + rhs.resetRegionParallel(); + + const Field3D res = field - rhs; + + EXPECT_FALSE(res.hasParallelSlices()); + EXPECT_TRUE(IsFieldEqual(res, -1.0)); + + const Field3DParallel respar = field.asField3DParallel() - rhs; + EXPECT_TRUE((std::is_same_v, Field3DParallel>)); + EXPECT_TRUE(respar.hasParallelSlices()); + EXPECT_TRUE(IsFieldEqual(respar, -1.0)); + EXPECT_TRUE(IsFieldEqual(respar.yup(), -2.0, "RGN_YPAR_+1")); + EXPECT_TRUE(IsFieldEqual(respar.ydown(), -3.0, "RGN_YPAR_-1")); +} + TEST_F(Field3DTest, Abs) { Field3D field; From 16a66f0b596923ba81d54e214ba0250e1c084c68 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Wed, 8 Jul 2026 13:52:19 +0100 Subject: [PATCH 027/221] Clang-Tidy: Use 'contains' to check for membership --- include/bout/options.hxx | 6 +++--- src/sys/options.cxx | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/bout/options.hxx b/include/bout/options.hxx index 87503b0a22..b68364c60c 100644 --- a/include/bout/options.hxx +++ b/include/bout/options.hxx @@ -355,7 +355,7 @@ public: /// Return true if this value has attribute \p key bool hasAttribute(const std::string& key) const { - return attributes.find(key) != attributes.end(); + return attributes.contains(key); } /// Set attributes, overwriting any already set @@ -579,7 +579,7 @@ public: value_used = true; // Note this is mutable output_info << "\tOption " << full_name << " = " << val; - if (attributes.count("source")) { + if (attributes.contains("source")) { // Specify the source of the setting output_info << " (" << bout::utils::variantToString(attributes.at("source")) << ")"; } @@ -907,7 +907,7 @@ private: // If already set, and not time evolving then check for changing values // If a variable has a "time_dimension" attribute then it is assumed // that updates to the value is ok and don't need to be forced. - if (isSet() && (attributes.find("time_dimension") == attributes.end())) { + if (isSet() && (!attributes.contains("time_dimension"))) { // Check if current value the same as new value if (!bout::utils::variantEqualTo(value, val)) { if (force or !bout::utils::variantEqualTo(attributes["source"], source)) { diff --git a/src/sys/options.cxx b/src/sys/options.cxx index 85cfa7a49a..dbbe372298 100644 --- a/src/sys/options.cxx +++ b/src/sys/options.cxx @@ -1053,9 +1053,9 @@ bout::details::OptionsFormatterBase::format(const Options& options, fmt::format_to(ctx.out(), " = {}", as_str); } - const bool has_doc = options.attributes.count("doc") != 0U; - const bool has_source = options.attributes.count("source") != 0U; - const bool has_type = options.attributes.count("type") != 0U; + const bool has_doc = options.attributes.contains("doc"); + const bool has_source = options.attributes.contains("source"); + const bool has_type = options.attributes.contains("type"); std::vector comments; From 252067479a736291497f4be95221ac69f645b671 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Wed, 8 Jul 2026 14:58:32 +0100 Subject: [PATCH 028/221] Refactor map::find expression: lookup and retrieval in a single step with C++17 initializer syntax --- src/field/field_data.cxx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/field/field_data.cxx b/src/field/field_data.cxx index 8f6a808482..b54f95ba64 100644 --- a/src/field/field_data.cxx +++ b/src/field/field_data.cxx @@ -189,12 +189,11 @@ void FieldData::addBndryGenerator(FieldGeneratorPtr gen, BndryLoc location) { } FieldGeneratorPtr FieldData::getBndryGenerator(BndryLoc location) { - auto it = bndry_generator.find(location); - if (it == bndry_generator.end()) { - return nullptr; + if (const auto it = bndry_generator.find(location); it != bndry_generator.end()) { + return it->second; } - return it->second; + return nullptr; } Mesh* FieldData::getMesh() const { From 28542c95c77314ba4ceca1e839a84f41dc700d4e Mon Sep 17 00:00:00 2001 From: tomc271 Date: Wed, 8 Jul 2026 15:17:19 +0100 Subject: [PATCH 029/221] Lookup and retrieval in a single step with C++17 initializer syntax --- src/field/field_factory.cxx | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/field/field_factory.cxx b/src/field/field_factory.cxx index 1f5984ba48..220597a215 100644 --- a/src/field/field_factory.cxx +++ b/src/field/field_factory.cxx @@ -301,8 +301,7 @@ Field3D FieldFactory::create3D(FieldGeneratorPtr gen, Mesh* localmesh, CELL_LOC }; if (transform_from_field_aligned) { - auto coords = result.getCoordinates(); - if (coords == nullptr) { + if (auto coords = result.getCoordinates(); coords == nullptr) { // Should not lead to issues. If called from the coordinates // constructor, then this is expected, and the result will be // transformed. Otherwise, if the field is used untransformed, @@ -352,8 +351,7 @@ FieldPerp FieldFactory::createPerp(FieldGeneratorPtr gen, Mesh* localmesh, CELL_ }; if (transform_from_field_aligned) { - auto coords = result.getCoordinates(); - if (coords == nullptr) { + if (auto coords = result.getCoordinates(); coords == nullptr) { // Should not lead to issues. If called from the coordinates // constructor, then this is expected, and the result will be // transformed. Otherwise, if the field is used untransformed, @@ -376,8 +374,7 @@ const Options* FieldFactory::findOption(const Options* opt, const std::string& n const Options* result = opt; // Check if name contains a section separator ':' - size_t pos = name.find(':'); - if (pos == std::string::npos) { + if (auto pos = name.find(':'); pos == std::string::npos) { // No separator. Try this section, and then go through parents while (!result->isSet(name)) { @@ -517,8 +514,7 @@ FieldGeneratorPtr FieldFactory::parse(const std::string& input, key = opt->str() + key; // Include options context in key } - auto it = cache.find(key); - if (it != cache.end()) { + if (auto it = cache.find(key); it != cache.end()) { return it->second; } From a56a9960117e0bc1d06cdcee748e100446611b04 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Wed, 8 Jul 2026 13:27:25 +0100 Subject: [PATCH 030/221] Move variable 'search' to if-init-statement --- src/mesh/coordinates_accessor.cxx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/mesh/coordinates_accessor.cxx b/src/mesh/coordinates_accessor.cxx index efc27e9715..6967087a0e 100644 --- a/src/mesh/coordinates_accessor.cxx +++ b/src/mesh/coordinates_accessor.cxx @@ -21,8 +21,7 @@ CoordinatesAccessor::CoordinatesAccessor(const Coordinates* coords) { Mesh* mesh = coords->dx.getMesh(); mesh_nz = mesh->LocalNz; - auto search = coords_store.find(coords); - if (search != coords_store.end()) { + if (const auto search = coords_store.find(coords); search != coords_store.end()) { // Found, so get the pointer to the data data = search->second.begin(); return; From b2f52cde587b5322e38213285ccefa88e9ea150e Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 10 Jul 2026 08:24:28 -0700 Subject: [PATCH 031/221] Field3DParallel unit test If the parallel transform is not FCI then parallel slices are not calculated on assignment to Field3DParallel. --- tests/unit/field/test_field3d.cxx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/unit/field/test_field3d.cxx b/tests/unit/field/test_field3d.cxx index 38f51a8164..eee92c806e 100644 --- a/tests/unit/field/test_field3d.cxx +++ b/tests/unit/field/test_field3d.cxx @@ -1967,7 +1967,7 @@ TEST_F(Field3DTest, SQExpressionUsesSquareOp) { EXPECT_TRUE(IsFieldEqual(SQ(expr), 9.0)); } -TEST_F(Field3DTest, SQField3DParallelPreservesParallelSlices) { +TEST_F(Field3DTest, SQField3DParallelNoFCIDropsParallelSlices) { Field3DParallel field; field = 2.0; @@ -1983,10 +1983,9 @@ TEST_F(Field3DTest, SQField3DParallelPreservesParallelSlices) { Field3DParallel result{squared}; + // Not FCI so parallel slices are not calculated EXPECT_FALSE(result.hasParallelSlices()); EXPECT_TRUE(IsFieldEqual(result, 4.0)); - EXPECT_TRUE(IsFieldEqual(result.yup(), 9.0)); - EXPECT_TRUE(IsFieldEqual(result.ydown(), 16.0)); } TEST_F(Field3DTest, Field3DParallelArithmeticReturnsLazyExpr) { From 858c5c1961a8970c1b3a4e8443ddc693b1d82084 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 10 Jul 2026 09:59:49 -0700 Subject: [PATCH 032/221] Lazy pow function on fields --- include/bout/field.hxx | 232 ++++++++++++++++++++++++++---- include/bout/field3d.hxx | 1 - include/bout/fieldperp.hxx | 5 + include/bout/fv_ops.hxx | 14 +- src/field/field3d.cxx | 16 --- src/field/fieldperp.cxx | 35 +++++ tests/unit/field/test_field2d.cxx | 13 ++ tests/unit/field/test_field3d.cxx | 74 ++++++++++ 8 files changed, 340 insertions(+), 50 deletions(-) diff --git a/include/bout/field.hxx b/include/bout/field.hxx index b20e8915e9..05bccf250c 100644 --- a/include/bout/field.hxx +++ b/include/bout/field.hxx @@ -539,53 +539,233 @@ inline BoutReal mean(const BinaryExpr& f, bool allpe = false, return bout::reduce::Mean::finalize(state); } +namespace bout::op { +struct Pow { + template + BOUT_HOST_DEVICE BOUT_FORCEINLINE BoutReal operator()(int idx, const LView& L, + const RView& R) const { + return ::pow(L(idx), R(idx)); + } + BOUT_HOST_DEVICE BOUT_FORCEINLINE BoutReal operator()(BoutReal a, BoutReal b) const { + return ::pow(a, b); + } +}; +}; // namespace bout::op + +namespace bout::detail { +template +std::optional getPerpYIndex(const T& value); + +template +std::optional getPerpYIndex(const BinaryExpr& expr); + +template +std::optional getPowRegionID(const Mesh* mesh, const std::string& region_name) { + if constexpr (std::is_same_v) { + return bout::detail::getField3DRegionID(mesh, region_name); + } else { + return std::nullopt; + } +} + +template +auto makePowExpr(const LView& lhs_view, const RView& rhs_view, Mesh* mesh, + CELL_LOC location, DirectionTypes directions, + std::optional regionID, const Region& region, + std::optional yindex = std::nullopt) { + return BinaryExpr{lhs_view, rhs_view, bout::op::Pow{}, + mesh, location, directions, + regionID, region, yindex}; +} +} // namespace bout::detail + /// Exponent: pow(lhs, lhs) is \p lhs raised to the power of \p rhs /// /// This loops over the entire domain, including guard/boundary cells by /// default (can be changed using the \p rgn argument) /// If CHECK >= 3 then the result will be checked for non-finite numbers -template > -T pow(const T& lhs, const T& rhs, const std::string& rgn = "RGN_ALL") { +template +std::enable_if_t && is_expr_field2d_v, + BinaryExpr> +pow(const L& lhs, const R& rhs) { + ASSERT1_EXPR_COMPATIBLE(lhs, rhs); + return bout::detail::makePowExpr( + static_cast(lhs), static_cast(rhs), + lhs.getMesh(), lhs.getLocation(), lhs.getDirections(), std::nullopt, + lhs.getMesh()->getRegion2D("RGN_ALL")); +} - ASSERT1(areFieldsCompatible(lhs, rhs)); +template +std::enable_if_t && is_expr_field2d_v, + BinaryExpr> +pow(const L& lhs, const R& rhs, const std::string& rgn) { + ASSERT1_EXPR_COMPATIBLE(lhs, rhs); + return bout::detail::makePowExpr( + static_cast(lhs), static_cast(rhs), + lhs.getMesh(), lhs.getLocation(), lhs.getDirections(), std::nullopt, + lhs.getMesh()->getRegion2D(rgn)); +} + +template +std::enable_if_t && is_expr_field3d_v, + BinaryExpr> +pow(const L& lhs, const R& rhs) { + ASSERT1_EXPR_COMPATIBLE(lhs, rhs); + auto regionID = lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID()); + return bout::detail::makePowExpr( + static_cast(lhs), static_cast(rhs), + lhs.getMesh(), lhs.getLocation(), lhs.getDirections(), regionID, + (regionID.has_value() ? lhs.getMesh()->getRegion(regionID.value()) + : lhs.getMesh()->getRegion("RGN_ALL")), + bout::detail::getPerpYIndex(lhs)); +} - T result{emptyFrom(lhs)}; +template +std::enable_if_t && is_expr_field3d_v, + BinaryExpr> +pow(const L& lhs, const R& rhs, const std::string& rgn) { + ASSERT1_EXPR_COMPATIBLE(lhs, rhs); + return bout::detail::makePowExpr( + static_cast(lhs), static_cast(rhs), + lhs.getMesh(), lhs.getLocation(), lhs.getDirections(), + bout::detail::getPowRegionID(lhs.getMesh(), rgn), + lhs.getMesh()->getRegion(rgn), bout::detail::getPerpYIndex(lhs)); +} - BOUT_FOR(i, result.getRegion(rgn)) { result[i] = ::pow(lhs[i], rhs[i]); } +template +std::enable_if_t && is_expr_field2d_v, + BinaryExpr> +pow(const L& lhs, const R& rhs) { + ASSERT1_EXPR_COMPATIBLE(lhs, rhs); + int mesh_nz = lhs.getMesh()->LocalNz; + return bout::detail::makePowExpr( + static_cast(lhs), + static_cast(rhs).setScale(1, mesh_nz), lhs.getMesh(), + lhs.getLocation(), lhs.getDirections(), lhs.getRegionID(), + lhs.getMesh()->getRegion("RGN_ALL"), bout::detail::getPerpYIndex(lhs)); +} - checkData(result); - return result; +template +std::enable_if_t && is_expr_field2d_v, + BinaryExpr> +pow(const L& lhs, const R& rhs, const std::string& rgn) { + ASSERT1_EXPR_COMPATIBLE(lhs, rhs); + int mesh_nz = lhs.getMesh()->LocalNz; + return bout::detail::makePowExpr( + static_cast(lhs), + static_cast(rhs).setScale(1, mesh_nz), lhs.getMesh(), + lhs.getLocation(), lhs.getDirections(), + bout::detail::getPowRegionID(lhs.getMesh(), rgn), + lhs.getMesh()->getRegion(rgn), bout::detail::getPerpYIndex(lhs)); } -template > -T pow(const T& lhs, BoutReal rhs, const std::string& rgn = "RGN_ALL") { +template +std::enable_if_t && is_expr_field3d_v, + BinaryExpr> +pow(const L& lhs, const R& rhs) { + ASSERT1_EXPR_COMPATIBLE(lhs, rhs); + int mesh_nz = rhs.getMesh()->LocalNz; + return bout::detail::makePowExpr( + static_cast(lhs).setScale(1, mesh_nz), + static_cast(rhs), rhs.getMesh(), rhs.getLocation(), + rhs.getDirections(), rhs.getRegionID(), rhs.getMesh()->getRegion("RGN_ALL"), + bout::detail::getPerpYIndex(rhs)); +} - // Check if the inputs are allocated - checkData(lhs); - checkData(rhs); +template +std::enable_if_t && is_expr_field3d_v, + BinaryExpr> +pow(const L& lhs, const R& rhs, const std::string& rgn) { + ASSERT1_EXPR_COMPATIBLE(lhs, rhs); + int mesh_nz = rhs.getMesh()->LocalNz; + return bout::detail::makePowExpr( + static_cast(lhs).setScale(1, mesh_nz), + static_cast(rhs), rhs.getMesh(), rhs.getLocation(), + rhs.getDirections(), bout::detail::getPowRegionID(rhs.getMesh(), rgn), + rhs.getMesh()->getRegion(rgn), bout::detail::getPerpYIndex(rhs)); +} - T result{emptyFrom(lhs)}; +template +std::enable_if_t && is_expr_constant_v, + BinaryExpr, bout::op::Pow>> +pow(const L& lhs, R rhs) { + return bout::detail::makePowExpr>( + static_cast(lhs), static_cast::View>(rhs), + lhs.getMesh(), lhs.getLocation(), lhs.getDirections(), std::nullopt, + lhs.getMesh()->getRegion2D("RGN_ALL")); +} - BOUT_FOR(i, result.getRegion(rgn)) { result[i] = ::pow(lhs[i], rhs); } +template +std::enable_if_t && is_expr_constant_v, + BinaryExpr, bout::op::Pow>> +pow(const L& lhs, R rhs, const std::string& rgn) { + return bout::detail::makePowExpr>( + static_cast(lhs), static_cast::View>(rhs), + lhs.getMesh(), lhs.getLocation(), lhs.getDirections(), std::nullopt, + lhs.getMesh()->getRegion2D(rgn)); +} - checkData(result); - return result; +template +std::enable_if_t && is_expr_field2d_v, + BinaryExpr, R, bout::op::Pow>> +pow(L lhs, const R& rhs) { + return bout::detail::makePowExpr, R>( + static_cast::View>(lhs), static_cast(rhs), + rhs.getMesh(), rhs.getLocation(), rhs.getDirections(), std::nullopt, + rhs.getMesh()->getRegion2D("RGN_ALL")); } -template > -T pow(BoutReal lhs, const T& rhs, const std::string& rgn = "RGN_ALL") { +template +std::enable_if_t && is_expr_field2d_v, + BinaryExpr, R, bout::op::Pow>> +pow(L lhs, const R& rhs, const std::string& rgn) { + return bout::detail::makePowExpr, R>( + static_cast::View>(lhs), static_cast(rhs), + rhs.getMesh(), rhs.getLocation(), rhs.getDirections(), std::nullopt, + rhs.getMesh()->getRegion2D(rgn)); +} - // Check if the inputs are allocated - checkData(lhs); - checkData(rhs); +template +std::enable_if_t && is_expr_constant_v, + BinaryExpr, bout::op::Pow>> +pow(const L& lhs, R rhs) { + return bout::detail::makePowExpr>( + static_cast(lhs), static_cast::View>(rhs), + lhs.getMesh(), lhs.getLocation(), lhs.getDirections(), lhs.getRegionID(), + lhs.getMesh()->getRegion("RGN_ALL"), bout::detail::getPerpYIndex(lhs)); +} - // Define and allocate the output result - T result{emptyFrom(rhs)}; +template +std::enable_if_t && is_expr_constant_v, + BinaryExpr, bout::op::Pow>> +pow(const L& lhs, R rhs, const std::string& rgn) { + return bout::detail::makePowExpr>( + static_cast(lhs), static_cast::View>(rhs), + lhs.getMesh(), lhs.getLocation(), lhs.getDirections(), + bout::detail::getPowRegionID(lhs.getMesh(), rgn), + lhs.getMesh()->getRegion(rgn), bout::detail::getPerpYIndex(lhs)); +} - BOUT_FOR(i, result.getRegion(rgn)) { result[i] = ::pow(lhs, rhs[i]); } +template +std::enable_if_t && is_expr_field3d_v, + BinaryExpr, R, bout::op::Pow>> +pow(L lhs, const R& rhs) { + return bout::detail::makePowExpr, R>( + static_cast::View>(lhs), static_cast(rhs), + rhs.getMesh(), rhs.getLocation(), rhs.getDirections(), rhs.getRegionID(), + rhs.getMesh()->getRegion("RGN_ALL"), bout::detail::getPerpYIndex(rhs)); +} - checkData(result); - return result; +template +std::enable_if_t && is_expr_field3d_v, + BinaryExpr, R, bout::op::Pow>> +pow(L lhs, const R& rhs, const std::string& rgn) { + return bout::detail::makePowExpr, R>( + static_cast::View>(lhs), static_cast(rhs), + rhs.getMesh(), rhs.getLocation(), rhs.getDirections(), + bout::detail::getPowRegionID(rhs.getMesh(), rgn), + rhs.getMesh()->getRegion(rgn), bout::detail::getPerpYIndex(rhs)); } /*! diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index 02e582c20a..63e20c349e 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -911,7 +911,6 @@ inline auto operator-(const Field3D& f) { /// This loops over the entire domain, including guard/boundary cells by /// default (can be changed using the \p rgn argument). /// If CHECK >= 3 then the result will be checked for non-finite numbers -Field3D pow(const Field3D& lhs, const Field2D& rhs, const std::string& rgn = "RGN_ALL"); FieldPerp pow(const Field3D& lhs, const FieldPerp& rhs, const std::string& rgn = "RGN_ALL"); diff --git a/include/bout/fieldperp.hxx b/include/bout/fieldperp.hxx index 83bb3b79b0..834c82be4d 100644 --- a/include/bout/fieldperp.hxx +++ b/include/bout/fieldperp.hxx @@ -409,6 +409,11 @@ FieldPerp operator/(const FieldPerp& lhs, const Field2D& rhs); FieldPerp operator/(const FieldPerp& lhs, BoutReal rhs); FieldPerp operator/(BoutReal lhs, const FieldPerp& rhs); +FieldPerp pow(const FieldPerp& lhs, const FieldPerp& rhs, + const std::string& rgn = "RGN_ALL"); +FieldPerp pow(const FieldPerp& lhs, BoutReal rhs, const std::string& rgn = "RGN_ALL"); +FieldPerp pow(BoutReal lhs, const FieldPerp& rhs, const std::string& rgn = "RGN_ALL"); + /*! * Unary minus. Returns the negative of given field, * iterates over whole domain including guard/boundary cells. diff --git a/include/bout/fv_ops.hxx b/include/bout/fv_ops.hxx index 306ff3301f..67db42cf0b 100644 --- a/include/bout/fv_ops.hxx +++ b/include/bout/fv_ops.hxx @@ -79,13 +79,13 @@ Field3D D4DY4_Index(const Field3D& f, bool bndry_flux = true); // Forward declarations of flux limiters // If you want to use your own flux limiter, you need to // #include to instantiate the templates. -class Upwind; -class Fromm; -class MinMod; -class MC; -class Superbee; -class VanAlbada; -class WENO3; +struct Upwind; +struct Fromm; +struct MinMod; +struct MC; +struct Superbee; +struct VanAlbada; +struct WENO3; /*! * Communicate fluxes between processors diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index 5da438dcdf..95ea1de05a 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -686,22 +686,6 @@ void Field3D::swapData(Field3D& other) { std::swap(data, other.data); } //////////////// NON-MEMBER FUNCTIONS ////////////////// -Field3D pow(const Field3D& lhs, const Field2D& rhs, const std::string& rgn) { - - // Check if the inputs are allocated - checkData(lhs); - checkData(rhs); - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - // Define and allocate the output result - Field3D result{emptyFrom(lhs)}; - - BOUT_FOR(i, result.getRegion(rgn)) { result[i] = ::pow(lhs[i], rhs[i]); } - - checkData(result); - return result; -} - FieldPerp pow(const Field3D& lhs, const FieldPerp& rhs, const std::string& rgn) { checkData(lhs); diff --git a/src/field/fieldperp.cxx b/src/field/fieldperp.cxx index b7b2d9d731..bca551c29d 100644 --- a/src/field/fieldperp.cxx +++ b/src/field/fieldperp.cxx @@ -153,6 +153,41 @@ FieldPerp fromFieldAligned(const FieldPerp& f, const std::string& region) { ///////////////////////////////////////////////// // functions +FieldPerp pow(const FieldPerp& lhs, const FieldPerp& rhs, const std::string& rgn) { + checkData(lhs); + checkData(rhs); + ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); + + FieldPerp result{emptyFrom(lhs)}; + + BOUT_FOR(i, result.getRegion(rgn)) { result[i] = ::pow(lhs[i], rhs[i]); } + + checkData(result); + return result; +} + +FieldPerp pow(const FieldPerp& lhs, BoutReal rhs, const std::string& rgn) { + checkData(lhs); + + FieldPerp result{emptyFrom(lhs)}; + + BOUT_FOR(i, result.getRegion(rgn)) { result[i] = ::pow(lhs[i], rhs); } + + checkData(result); + return result; +} + +FieldPerp pow(BoutReal lhs, const FieldPerp& rhs, const std::string& rgn) { + checkData(rhs); + + FieldPerp result{emptyFrom(rhs)}; + + BOUT_FOR(i, result.getRegion(rgn)) { result[i] = ::pow(lhs, rhs[i]); } + + checkData(result); + return result; +} + const FieldPerp sliceXZ(const Field3D& f, int y) { // Source field should be valid checkData(f); diff --git a/tests/unit/field/test_field2d.cxx b/tests/unit/field/test_field2d.cxx index a91acd4a40..1420c79bc2 100644 --- a/tests/unit/field/test_field2d.cxx +++ b/tests/unit/field/test_field2d.cxx @@ -1168,6 +1168,19 @@ TEST_F(Field2DTest, PowField2DField2D) { EXPECT_TRUE(IsFieldEqual(c, 64.0)); } +TEST_F(Field2DTest, PowExpressionUsesPowOp) { + Field2D field; + + field = 2.0; + const auto expr = field + 1.0; + + EXPECT_TRUE((std::is_same_v, + BinaryExpr, + Constant, bout::op::Pow>>)); + EXPECT_TRUE(IsFieldEqual(pow(expr, 2.0), 9.0)); + EXPECT_TRUE(IsFieldEqual(pow(expr, 2.0, "RGN_ALL"), 9.0)); +} + TEST_F(Field2DTest, Sqrt) { Field2D field; diff --git a/tests/unit/field/test_field3d.cxx b/tests/unit/field/test_field3d.cxx index eee92c806e..4ddc7dde5c 100644 --- a/tests/unit/field/test_field3d.cxx +++ b/tests/unit/field/test_field3d.cxx @@ -1947,6 +1947,37 @@ TEST_F(Field3DTest, PowField3DField3D) { EXPECT_TRUE(IsFieldEqual(c, 64.0)); } +TEST_F(Field3DTest, PowExpressionUsesPowOp) { + Field3D field; + + field = 2.0; + const auto expr = field + 1.0; + + EXPECT_TRUE((std::is_same_v, + BinaryExpr, + Constant, bout::op::Pow>>)); + EXPECT_TRUE(IsFieldEqual(pow(expr, 2.0), 9.0)); +} + +TEST_F(Field3DTest, PowMixedField2DField3DExpressionUsesPowOp) { + Field2D lhs; + Field3D rhs; + + lhs = 2.0; + rhs = 3.0; + + const auto lhs_expr = lhs + 1.0; + const auto rhs_expr = rhs + 1.0; + const auto expr = pow(lhs_expr, rhs_expr); + + EXPECT_TRUE( + (std::is_same_v, + BinaryExpr, + std::decay_t, bout::op::Pow>>)); + EXPECT_TRUE(IsFieldEqual(expr, 81.0)); + EXPECT_TRUE(IsFieldEqual(pow(lhs_expr, rhs_expr, "RGN_ALL"), 81.0)); +} + TEST_F(Field3DTest, Sqrt) { Field3D field; @@ -2132,6 +2163,49 @@ TEST_F(Field3DTestFCI, SqrtField3DParallelPreservesParallelSlices) { } } +TEST_F(Field3DTestFCI, PowField3DParallelExprPreservesParallelSlices) { + Field3DParallel field; + + field = 2.0; + field.yup() = 3.0; + field.ydown() = 4.0; + + const auto expr = pow(field + 1.0, 2.0); + + EXPECT_TRUE((std::is_same_v, + BinaryExpr, + Constant, bout::op::Pow>>)); + + Field3DParallel result{expr}; + + EXPECT_TRUE(result.hasParallelSlices()); + EXPECT_TRUE(IsFieldEqual(result, 9.0)); + EXPECT_TRUE(IsFieldEqual(result.yup(), 16.0)); + EXPECT_TRUE(IsFieldEqual(result.ydown(), 25.0)); +} + +TEST_F(Field3DTestFCI, PowField3DParallelRegionExprPreservesParallelSlices) { + Field3DParallel field; + + field = 2.0; + field.yup() = 3.0; + field.ydown() = 4.0; + + const auto base = field + 1.0; + const auto expr = pow(base, 2.0, "RGN_ALL"); + + EXPECT_TRUE((std::is_same_v, + BinaryExpr, + Constant, bout::op::Pow>>)); + + Field3DParallel result{expr}; + + EXPECT_TRUE(result.hasParallelSlices()); + EXPECT_TRUE(IsFieldEqual(result, 9.0)); + EXPECT_TRUE(IsFieldEqual(result.yup(), 16.0)); + EXPECT_TRUE(IsFieldEqual(result.ydown(), 25.0)); +} + TEST_F(Field3DTestFCI, SqrtField3DParallelExprPreservesParallelSlices) { Field3DParallel lhs; Field3D rhs; From c808fa7bf87d5f2be63d790058efa4ae1939e05b Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 10 Jul 2026 10:26:40 -0700 Subject: [PATCH 033/221] floor: Return BinaryExpr Makes the `floor` function lazy, so it can be inlined into template expressions. --- include/bout/field.hxx | 99 +++++++++++++++++------------ tests/unit/field/test_field2d.cxx | 13 ++++ tests/unit/field/test_field3d.cxx | 55 ++++++++++++++++ tests/unit/field/test_fieldperp.cxx | 14 ++++ 4 files changed, 142 insertions(+), 39 deletions(-) diff --git a/include/bout/field.hxx b/include/bout/field.hxx index 05bccf250c..67ce45dc4b 100644 --- a/include/bout/field.hxx +++ b/include/bout/field.hxx @@ -33,10 +33,12 @@ class Field; #include #include #include +#include #include "bout/bout_types.hxx" #include "bout/boutcomm.hxx" #include "bout/boutexception.hxx" +#include "bout/build_config.hxx" #include "bout/field_data.hxx" #include "bout/region.hxx" #include "bout/traits.hxx" @@ -887,6 +889,20 @@ struct Square { return ::SQ(value); } }; + +struct Floor { + template + BOUT_HOST_DEVICE BOUT_FORCEINLINE BoutReal operator()(int idx, const LView& L, + const RView& R) const { + const BoutReal value = L(idx); + const BoutReal floor_value = R(idx); + return value < floor_value ? floor_value : value; + } + BOUT_HOST_DEVICE BOUT_FORCEINLINE BoutReal operator()(BoutReal value, + BoutReal floor_value) const { + return value < floor_value ? floor_value : value; + } +}; }; // namespace bout::op template > @@ -1065,46 +1081,51 @@ class Field3DParallel; /// @param[in] f The floor value /// @param[in] rgn The region to calculate the result over template > -inline T floor(const T& var, BoutReal f, const std::string& rgn = "RGN_ALL") { - checkData(var); - T result = copy(var); +inline auto floor(const T& var, BoutReal f, const std::string& rgn = "RGN_ALL") { + using ResT = bout::detail::UnaryFieldResult_t; + return BinaryExpr, bout::op::Floor>{ + static_cast(var), + static_cast::View>(f), + bout::op::Floor{}, + var.getMesh(), + var.getLocation(), + var.getDirections(), + bout::detail::getUnaryRegionID(var.getMesh(), rgn), + var.getMesh()->template getRegion(rgn), + bout::detail::getPerpYIndex(var)}; +} - BOUT_FOR(d, var.getRegion(rgn)) { - if (result[d] < f) { - result[d] = f; - } - } - if constexpr (std::is_same_v) { - if (var.hasParallelSlices()) { - for (size_t i = 0; i < result.numberParallelSlices(); ++i) { - if (result.yup(i).isAllocated()) { - BOUT_FOR(d, result.yup(i).getRegion(rgn)) { - if (result.yup(i)[d] < f) { - result.yup(i)[d] = f; - } - } - } else { - if (result.isFci()) { - throw BoutException("Expected parallel slice to be allocated"); - } - } - if (result.ydown(i).isAllocated()) { - BOUT_FOR(d, result.ydown(i).getRegion(rgn)) { - if (result.ydown(i)[d] < f) { - result.ydown(i)[d] = f; - } - } - } else { - if (result.isFci()) { - throw BoutException("Expected parallel slice to be allocated"); - } - } - } - } - } else { - result.clearParallelSlices(); - } - return result; +template +inline auto floor(const BinaryExpr& var, BoutReal f) { + using UnaryResT = bout::detail::UnaryFieldResult_t; + return BinaryExpr, Constant, + bout::op::Floor>{ + static_cast::View>(var), + static_cast::View>(f), + bout::op::Floor{}, + var.getMesh(), + var.getLocation(), + var.getDirections(), + var.getRegionID(), + var.indices, + bout::detail::getPerpYIndex(var)}; +} + +template +inline auto floor(const BinaryExpr& var, BoutReal f, + const std::string& rgn) { + using UnaryResT = bout::detail::UnaryFieldResult_t; + return BinaryExpr, Constant, + bout::op::Floor>{ + static_cast::View>(var), + static_cast::View>(f), + bout::op::Floor{}, + var.getMesh(), + var.getLocation(), + var.getDirections(), + bout::detail::getUnaryRegionID(var.getMesh(), rgn), + var.getMesh()->template getRegion(rgn), + bout::detail::getPerpYIndex(var)}; } #undef FIELD_FUNC diff --git a/tests/unit/field/test_field2d.cxx b/tests/unit/field/test_field2d.cxx index 1420c79bc2..91721714ec 100644 --- a/tests/unit/field/test_field2d.cxx +++ b/tests/unit/field/test_field2d.cxx @@ -1330,6 +1330,19 @@ TEST_F(Field2DTest, Floor) { EXPECT_TRUE(IsFieldEqual(floor(field, floor_value), floor_value)); } +TEST_F(Field2DTest, FloorExpressionUsesFloorOp) { + Field2D field; + + field = 2.0; + const auto expr = field + 1.0; + + EXPECT_TRUE((std::is_same_v, + BinaryExpr, + Constant, bout::op::Floor>>)); + EXPECT_TRUE(IsFieldEqual(floor(expr, 5.0), 5.0)); + EXPECT_TRUE(IsFieldEqual(floor(expr, 5.0, "RGN_ALL"), 5.0)); +} + TEST_F(Field2DTest, Min) { Field2D field; diff --git a/tests/unit/field/test_field3d.cxx b/tests/unit/field/test_field3d.cxx index 4ddc7dde5c..6a6ecb9aea 100644 --- a/tests/unit/field/test_field3d.cxx +++ b/tests/unit/field/test_field3d.cxx @@ -2543,6 +2543,61 @@ TEST_F(Field3DTest, Floor) { EXPECT_TRUE(IsFieldEqual(floor(field, floor_value), floor_value)); } +TEST_F(Field3DTest, FloorExpressionUsesFloorOp) { + Field3D field; + + field = 2.0; + const auto expr = field + 1.0; + + EXPECT_TRUE((std::is_same_v, + BinaryExpr, + Constant, bout::op::Floor>>)); + EXPECT_TRUE(IsFieldEqual(floor(expr, 5.0), 5.0)); +} + +TEST_F(Field3DTestFCI, FloorField3DParallelExprPreservesParallelSlices) { + Field3DParallel field; + + field = 2.0; + field.yup() = 3.0; + field.ydown() = 4.0; + + const auto expr = floor(field + 1.0, 4.5); + + EXPECT_TRUE((std::is_same_v, + BinaryExpr, + Constant, bout::op::Floor>>)); + + Field3DParallel result{expr}; + + EXPECT_TRUE(result.hasParallelSlices()); + EXPECT_TRUE(IsFieldEqual(result, 4.5)); + EXPECT_TRUE(IsFieldEqual(result.yup(), 4.5)); + EXPECT_TRUE(IsFieldEqual(result.ydown(), 5.0)); +} + +TEST_F(Field3DTestFCI, FloorField3DParallelRegionExprPreservesParallelSlices) { + Field3DParallel field; + + field = 2.0; + field.yup() = 3.0; + field.ydown() = 4.0; + + const auto base = field + 1.0; + const auto expr = floor(base, 4.5, "RGN_ALL"); + + EXPECT_TRUE((std::is_same_v, + BinaryExpr, + Constant, bout::op::Floor>>)); + + Field3DParallel result{expr}; + + EXPECT_TRUE(result.hasParallelSlices()); + EXPECT_TRUE(IsFieldEqual(result, 4.5)); + EXPECT_TRUE(IsFieldEqual(result.yup(), 4.5)); + EXPECT_TRUE(IsFieldEqual(result.ydown(), 5.0)); +} + TEST_F(Field3DTest, Min) { Field3D field; diff --git a/tests/unit/field/test_fieldperp.cxx b/tests/unit/field/test_fieldperp.cxx index 46f07d589f..754316e077 100644 --- a/tests/unit/field/test_fieldperp.cxx +++ b/tests/unit/field/test_fieldperp.cxx @@ -1713,6 +1713,20 @@ TEST_F(FieldPerpTest, Floor) { EXPECT_TRUE(IsFieldEqual(floor(field, floor_value), floor_value)); } +TEST_F(FieldPerpTest, FloorExpressionUsesFloorOp) { + FieldPerp field; + field.setIndex(0); + + field = 2.0; + const auto expr = field + 1.0; + + EXPECT_TRUE((std::is_same_v, + BinaryExpr, + Constant, bout::op::Floor>>)); + EXPECT_TRUE(IsFieldEqual(floor(expr, 5.0), 5.0)); + EXPECT_TRUE(IsFieldEqual(floor(expr, 5.0, "RGN_ALL"), 5.0)); +} + TEST_F(FieldPerpTest, Min) { FieldPerp field; field.setIndex(0); From b9d13fd154a1f5ef7fcf522d39212a4a3e48cdce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Jul 2026 19:32:48 +0000 Subject: [PATCH 034/221] Bump astral-sh/setup-uv from 5 to 7 Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 5 to 7. - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/v5...v7) --- updated-dependencies: - dependency-name: astral-sh/setup-uv dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 06294e6091..7ad1f6e70b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -158,7 +158,7 @@ jobs: submodules: true - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v7 with: enable-cache: true From c7493e49edb7d6300e2cd0d13fac677b3b4e93bc Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 10 Jul 2026 13:07:07 -0700 Subject: [PATCH 035/221] generated_fieldops: Remove eager functions Arithmetic operators now use the lazy `BinaryExpr` expressions. --- include/bout/field3d.hxx | 10 - src/field/gen_fieldops.jinja | 6 +- src/field/gen_fieldops.py | 18 + src/field/generated_fieldops.cxx | 1336 ------------------------------ tests/unit/fake_mesh.hxx | 3 +- 5 files changed, 24 insertions(+), 1349 deletions(-) diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index 63e20c349e..ade329bb6c 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -1211,16 +1211,6 @@ struct is_expr_field3d> : std::integral_constant>::value || is_expr_field3d_v>> {}; -Field3D operator+(const Field2D& lhs, const Field3DParallel& rhs); -Field3D operator-(const Field2D& lhs, const Field3DParallel& rhs); -Field3D operator*(const Field2D& lhs, const Field3DParallel& rhs); -Field3D operator/(const Field2D& lhs, const Field3DParallel& rhs); - -Field3D operator+(const Field3DParallel& lhs, const Field2D& rhs); -Field3D operator-(const Field3DParallel& lhs, const Field2D& rhs); -Field3D operator*(const Field3DParallel& lhs, const Field2D& rhs); -Field3D operator/(const Field3DParallel& lhs, const Field2D& rhs); - inline Field3DParallel filledFrom(const Field3DParallel& f, const std::function& func) { diff --git a/src/field/gen_fieldops.jinja b/src/field/gen_fieldops.jinja index 913acadf7b..89f46469bd 100644 --- a/src/field/gen_fieldops.jinja +++ b/src/field/gen_fieldops.jinja @@ -1,6 +1,7 @@ {% set use_parallel_arg = lhs.field_type == "Field3DParallel" or rhs.field_type == "Field3DParallel" %} {% set use_raja_path = region_loop == "BOUT_FOR_RAJA" and not use_parallel_arg %} +{% if emit_free_binary %} // Provide the C++ wrapper for {{operator_name}} of {{lhs}} and {{rhs}} {{out}} operator{{operator}}(const {{lhs.passByReference}}, const {{rhs.passByReference}}) { {% if lhs != "BoutReal" and rhs != "BoutReal" %} @@ -140,8 +141,9 @@ checkData({{out.name}}); return {{out.name}}; } +{% endif %} -{% if out.field_type == lhs.field_type and lhs == "Field3D" %} +{% if emit_update_inplace %} // Provide the C++ operator to update {{lhs}} by {{operator_name}} with {{rhs}} {{lhs}} &{{lhs}}::update_{{operator_name}}_inplace(const {{rhs.passByReference}}) { // only if data is unique we update the field @@ -234,7 +236,7 @@ {% endif %} -{% if out.field_type == lhs.field_type %} +{% if emit_member_op_equals %} // Provide the C++ operator to update {{lhs}} by {{operator_name}} with {{rhs}} {{lhs}} &{{lhs}}::operator{{operator}}=(const {{rhs.passByReference}}) { // only if data is unique we update the field diff --git a/src/field/gen_fieldops.py b/src/field/gen_fieldops.py index 6610286af5..e77530ff97 100755 --- a/src/field/gen_fieldops.py +++ b/src/field/gen_fieldops.py @@ -254,6 +254,21 @@ def returnType(f1, f2): return copy(field3D) +def emit_free_binary_wrapper(out): + """Return True if this operator still needs an eager non-member wrapper.""" + return out.field_type == "FieldPerp" + + +def emit_update_inplace(lhs, out): + """Return True if this operator needs a Field3D update_*_inplace definition.""" + return out.field_type == lhs.field_type and lhs.field_type == "Field3D" + + +def emit_member_operator_equals(lhs, out): + """Return True if this operator needs an in-place operator definition.""" + return out.field_type == lhs.field_type + + if __name__ == "__main__": parser = argparse.ArgumentParser( description="Generate code for the Field arithmetic operators" @@ -365,6 +380,9 @@ def returnType(f1, f2): "out": out, "lhs": lhs, "rhs": rhs, + "emit_free_binary": emit_free_binary_wrapper(out), + "emit_update_inplace": emit_update_inplace(lhs, out), + "emit_member_op_equals": emit_member_operator_equals(lhs, out), # "region_loop": region_loop, "region_name": region_name, diff --git a/src/field/generated_fieldops.cxx b/src/field/generated_fieldops.cxx index d47b2a7a89..1e9ade4ba4 100644 --- a/src/field/generated_fieldops.cxx +++ b/src/field/generated_fieldops.cxx @@ -9,23 +9,6 @@ #include #include -// Provide the C++ wrapper for multiplication of Field3D and Field3D -Field3D operator*(const Field3D& lhs, const Field3D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] * rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3D by multiplication with Field3D Field3D& Field3D::update_multiplication_inplace(const Field3D& rhs) { // only if data is unique we update the field @@ -78,23 +61,6 @@ Field3D& Field3D::operator*=(const Field3D& rhs) { return *this; } -// Provide the C++ wrapper for division of Field3D and Field3D -Field3D operator/(const Field3D& lhs, const Field3D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] / rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3D by division with Field3D Field3D& Field3D::update_division_inplace(const Field3D& rhs) { // only if data is unique we update the field @@ -147,23 +113,6 @@ Field3D& Field3D::operator/=(const Field3D& rhs) { return *this; } -// Provide the C++ wrapper for addition of Field3D and Field3D -Field3D operator+(const Field3D& lhs, const Field3D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] + rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3D by addition with Field3D Field3D& Field3D::update_addition_inplace(const Field3D& rhs) { // only if data is unique we update the field @@ -216,23 +165,6 @@ Field3D& Field3D::operator+=(const Field3D& rhs) { return *this; } -// Provide the C++ wrapper for subtraction of Field3D and Field3D -Field3D operator-(const Field3D& lhs, const Field3D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] - rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3D by subtraction with Field3D Field3D& Field3D::update_subtraction_inplace(const Field3D& rhs) { // only if data is unique we update the field @@ -285,28 +217,6 @@ Field3D& Field3D::operator-=(const Field3D& rhs) { return *this; } -// Provide the C++ wrapper for multiplication of Field3D and Field2D -Field3D operator*(const Field3D& lhs, const Field2D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getRegionID()); - - Mesh* localmesh = lhs.getMesh(); - - BOUT_FOR_SERIAL(index, rhs.getRegion("RGN_ALL")) { - const auto base_ind = localmesh->ind2Dto3D(index); - for (int jz = 0; jz < localmesh->LocalNz; ++jz) { - result[base_ind + jz] = lhs[base_ind + jz] * rhs[index]; - } - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3D by multiplication with Field2D Field3D& Field3D::update_multiplication_inplace(const Field2D& rhs) { // only if data is unique we update the field @@ -365,29 +275,6 @@ Field3D& Field3D::operator*=(const Field2D& rhs) { return *this; } -// Provide the C++ wrapper for division of Field3D and Field2D -Field3D operator/(const Field3D& lhs, const Field2D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getRegionID()); - - Mesh* localmesh = lhs.getMesh(); - - BOUT_FOR_SERIAL(index, rhs.getRegion("RGN_ALL")) { - const auto base_ind = localmesh->ind2Dto3D(index); - const auto tmp = 1.0 / rhs[index]; - for (int jz = 0; jz < localmesh->LocalNz; ++jz) { - result[base_ind + jz] = lhs[base_ind + jz] * tmp; - } - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3D by division with Field2D Field3D& Field3D::update_division_inplace(const Field2D& rhs) { // only if data is unique we update the field @@ -448,28 +335,6 @@ Field3D& Field3D::operator/=(const Field2D& rhs) { return *this; } -// Provide the C++ wrapper for addition of Field3D and Field2D -Field3D operator+(const Field3D& lhs, const Field2D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getRegionID()); - - Mesh* localmesh = lhs.getMesh(); - - BOUT_FOR_SERIAL(index, rhs.getRegion("RGN_ALL")) { - const auto base_ind = localmesh->ind2Dto3D(index); - for (int jz = 0; jz < localmesh->LocalNz; ++jz) { - result[base_ind + jz] = lhs[base_ind + jz] + rhs[index]; - } - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3D by addition with Field2D Field3D& Field3D::update_addition_inplace(const Field2D& rhs) { // only if data is unique we update the field @@ -528,28 +393,6 @@ Field3D& Field3D::operator+=(const Field2D& rhs) { return *this; } -// Provide the C++ wrapper for subtraction of Field3D and Field2D -Field3D operator-(const Field3D& lhs, const Field2D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getRegionID()); - - Mesh* localmesh = lhs.getMesh(); - - BOUT_FOR_SERIAL(index, rhs.getRegion("RGN_ALL")) { - const auto base_ind = localmesh->ind2Dto3D(index); - for (int jz = 0; jz < localmesh->LocalNz; ++jz) { - result[base_ind + jz] = lhs[base_ind + jz] - rhs[index]; - } - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3D by subtraction with Field2D Field3D& Field3D::update_subtraction_inplace(const Field2D& rhs) { // only if data is unique we update the field @@ -684,22 +527,6 @@ FieldPerp operator-(const Field3D& lhs, const FieldPerp& rhs) { return result; } -// Provide the C++ wrapper for multiplication of Field3D and BoutReal -Field3D operator*(const Field3D& lhs, const BoutReal rhs) { - - Field3D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getRegionID()); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] * rhs; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3D by multiplication with BoutReal Field3D& Field3D::update_multiplication_inplace(const BoutReal rhs) { // only if data is unique we update the field @@ -746,23 +573,6 @@ Field3D& Field3D::operator*=(const BoutReal rhs) { return *this; } -// Provide the C++ wrapper for division of Field3D and BoutReal -Field3D operator/(const Field3D& lhs, const BoutReal rhs) { - - Field3D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getRegionID()); - - const auto tmp = 1.0 / rhs; - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] * tmp; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3D by division with BoutReal Field3D& Field3D::update_division_inplace(const BoutReal rhs) { // only if data is unique we update the field @@ -811,22 +621,6 @@ Field3D& Field3D::operator/=(const BoutReal rhs) { return *this; } -// Provide the C++ wrapper for addition of Field3D and BoutReal -Field3D operator+(const Field3D& lhs, const BoutReal rhs) { - - Field3D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getRegionID()); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] + rhs; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3D by addition with BoutReal Field3D& Field3D::update_addition_inplace(const BoutReal rhs) { // only if data is unique we update the field @@ -873,22 +667,6 @@ Field3D& Field3D::operator+=(const BoutReal rhs) { return *this; } -// Provide the C++ wrapper for subtraction of Field3D and BoutReal -Field3D operator-(const Field3D& lhs, const BoutReal rhs) { - - Field3D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getRegionID()); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] - rhs; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3D by subtraction with BoutReal Field3D& Field3D::update_subtraction_inplace(const BoutReal rhs) { // only if data is unique we update the field @@ -935,109 +713,6 @@ Field3D& Field3D::operator-=(const BoutReal rhs) { return *this; } -// Provide the C++ wrapper for multiplication of Field2D and Field3D -Field3D operator*(const Field2D& lhs, const Field3D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3D result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(rhs.getRegionID()); - - Mesh* localmesh = lhs.getMesh(); - - BOUT_FOR_SERIAL(index, lhs.getRegion("RGN_ALL")) { - const auto base_ind = localmesh->ind2Dto3D(index); - for (int jz = 0; jz < localmesh->LocalNz; ++jz) { - result[base_ind + jz] = lhs[index] * rhs[base_ind + jz]; - } - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for division of Field2D and Field3D -Field3D operator/(const Field2D& lhs, const Field3D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3D result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(rhs.getRegionID()); - - Mesh* localmesh = lhs.getMesh(); - - BOUT_FOR_SERIAL(index, lhs.getRegion("RGN_ALL")) { - const auto base_ind = localmesh->ind2Dto3D(index); - for (int jz = 0; jz < localmesh->LocalNz; ++jz) { - result[base_ind + jz] = lhs[index] / rhs[base_ind + jz]; - } - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for addition of Field2D and Field3D -Field3D operator+(const Field2D& lhs, const Field3D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3D result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(rhs.getRegionID()); - - Mesh* localmesh = lhs.getMesh(); - - BOUT_FOR_SERIAL(index, lhs.getRegion("RGN_ALL")) { - const auto base_ind = localmesh->ind2Dto3D(index); - for (int jz = 0; jz < localmesh->LocalNz; ++jz) { - result[base_ind + jz] = lhs[index] + rhs[base_ind + jz]; - } - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for subtraction of Field2D and Field3D -Field3D operator-(const Field2D& lhs, const Field3D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3D result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(rhs.getRegionID()); - - Mesh* localmesh = lhs.getMesh(); - - BOUT_FOR_SERIAL(index, lhs.getRegion("RGN_ALL")) { - const auto base_ind = localmesh->ind2Dto3D(index); - for (int jz = 0; jz < localmesh->LocalNz; ++jz) { - result[base_ind + jz] = lhs[index] - rhs[base_ind + jz]; - } - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for multiplication of Field2D and Field2D -Field2D operator*(const Field2D& lhs, const Field2D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field2D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] * rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field2D by multiplication with Field2D Field2D& Field2D::operator*=(const Field2D& rhs) { // only if data is unique we update the field @@ -1058,21 +733,6 @@ Field2D& Field2D::operator*=(const Field2D& rhs) { return *this; } -// Provide the C++ wrapper for division of Field2D and Field2D -Field2D operator/(const Field2D& lhs, const Field2D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field2D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] / rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field2D by division with Field2D Field2D& Field2D::operator/=(const Field2D& rhs) { // only if data is unique we update the field @@ -1093,21 +753,6 @@ Field2D& Field2D::operator/=(const Field2D& rhs) { return *this; } -// Provide the C++ wrapper for addition of Field2D and Field2D -Field2D operator+(const Field2D& lhs, const Field2D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field2D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] + rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field2D by addition with Field2D Field2D& Field2D::operator+=(const Field2D& rhs) { // only if data is unique we update the field @@ -1128,21 +773,6 @@ Field2D& Field2D::operator+=(const Field2D& rhs) { return *this; } -// Provide the C++ wrapper for subtraction of Field2D and Field2D -Field2D operator-(const Field2D& lhs, const Field2D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field2D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] - rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field2D by subtraction with Field2D Field2D& Field2D::operator-=(const Field2D& rhs) { // only if data is unique we update the field @@ -1239,20 +869,6 @@ FieldPerp operator-(const Field2D& lhs, const FieldPerp& rhs) { return result; } -// Provide the C++ wrapper for multiplication of Field2D and BoutReal -Field2D operator*(const Field2D& lhs, const BoutReal rhs) { - - Field2D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] * rhs; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field2D by multiplication with BoutReal Field2D& Field2D::operator*=(const BoutReal rhs) { // only if data is unique we update the field @@ -1272,21 +888,6 @@ Field2D& Field2D::operator*=(const BoutReal rhs) { return *this; } -// Provide the C++ wrapper for division of Field2D and BoutReal -Field2D operator/(const Field2D& lhs, const BoutReal rhs) { - - Field2D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - const auto tmp = 1.0 / rhs; - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] * tmp; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field2D by division with BoutReal Field2D& Field2D::operator/=(const BoutReal rhs) { // only if data is unique we update the field @@ -1307,20 +908,6 @@ Field2D& Field2D::operator/=(const BoutReal rhs) { return *this; } -// Provide the C++ wrapper for addition of Field2D and BoutReal -Field2D operator+(const Field2D& lhs, const BoutReal rhs) { - - Field2D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] + rhs; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field2D by addition with BoutReal Field2D& Field2D::operator+=(const BoutReal rhs) { // only if data is unique we update the field @@ -1340,20 +927,6 @@ Field2D& Field2D::operator+=(const BoutReal rhs) { return *this; } -// Provide the C++ wrapper for subtraction of Field2D and BoutReal -Field2D operator-(const Field2D& lhs, const BoutReal rhs) { - - Field2D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] - rhs; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field2D by subtraction with BoutReal Field2D& Field2D::operator-=(const BoutReal rhs) { // only if data is unique we update the field @@ -2006,126 +1579,6 @@ FieldPerp& FieldPerp::operator-=(const BoutReal rhs) { return *this; } -// Provide the C++ wrapper for multiplication of BoutReal and Field3D -Field3D operator*(const BoutReal lhs, const Field3D& rhs) { - - Field3D result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(rhs.getRegionID()); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs * rhs[index]; - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for division of BoutReal and Field3D -Field3D operator/(const BoutReal lhs, const Field3D& rhs) { - - Field3D result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(rhs.getRegionID()); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs / rhs[index]; - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for addition of BoutReal and Field3D -Field3D operator+(const BoutReal lhs, const Field3D& rhs) { - - Field3D result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(rhs.getRegionID()); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs + rhs[index]; - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for subtraction of BoutReal and Field3D -Field3D operator-(const BoutReal lhs, const Field3D& rhs) { - - Field3D result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(rhs.getRegionID()); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs - rhs[index]; - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for multiplication of BoutReal and Field2D -Field2D operator*(const BoutReal lhs, const Field2D& rhs) { - - Field2D result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs * rhs[index]; - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for division of BoutReal and Field2D -Field2D operator/(const BoutReal lhs, const Field2D& rhs) { - - Field2D result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs / rhs[index]; - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for addition of BoutReal and Field2D -Field2D operator+(const BoutReal lhs, const Field2D& rhs) { - - Field2D result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs + rhs[index]; - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for subtraction of BoutReal and Field2D -Field2D operator-(const BoutReal lhs, const Field2D& rhs) { - - Field2D result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs - rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ wrapper for multiplication of BoutReal and FieldPerp FieldPerp operator*(const BoutReal lhs, const FieldPerp& rhs) { @@ -2182,216 +1635,6 @@ FieldPerp operator-(const BoutReal lhs, const FieldPerp& rhs) { return result; } -// Provide the C++ wrapper for multiplication of Field3D and Field3DParallel -Field3DParallel operator*(const Field3D& lhs, const Field3DParallel& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3DParallel result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) * rhs.yup(i); - result.ydown(i) = lhs.ydown(i) * rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] * rhs[index]; - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for division of Field3D and Field3DParallel -Field3DParallel operator/(const Field3D& lhs, const Field3DParallel& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3DParallel result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) / rhs.yup(i); - result.ydown(i) = lhs.ydown(i) / rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] / rhs[index]; - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for addition of Field3D and Field3DParallel -Field3DParallel operator+(const Field3D& lhs, const Field3DParallel& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3DParallel result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) + rhs.yup(i); - result.ydown(i) = lhs.ydown(i) + rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] + rhs[index]; - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for subtraction of Field3D and Field3DParallel -Field3DParallel operator-(const Field3D& lhs, const Field3DParallel& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3DParallel result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) - rhs.yup(i); - result.ydown(i) = lhs.ydown(i) - rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] - rhs[index]; - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for multiplication of Field3DParallel and Field3D -Field3DParallel operator*(const Field3DParallel& lhs, const Field3D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3DParallel result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) * rhs.yup(i); - result.ydown(i) = lhs.ydown(i) * rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] * rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3DParallel by multiplication with Field3D Field3DParallel& Field3DParallel::operator*=(const Field3D& rhs) { // only if data is unique we update the field @@ -2431,48 +1674,6 @@ Field3DParallel& Field3DParallel::operator*=(const Field3D& rhs) { return *this; } -// Provide the C++ wrapper for division of Field3DParallel and Field3D -Field3DParallel operator/(const Field3DParallel& lhs, const Field3D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3DParallel result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) / rhs.yup(i); - result.ydown(i) = lhs.ydown(i) / rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] / rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3DParallel by division with Field3D Field3DParallel& Field3DParallel::operator/=(const Field3D& rhs) { // only if data is unique we update the field @@ -2512,48 +1713,6 @@ Field3DParallel& Field3DParallel::operator/=(const Field3D& rhs) { return *this; } -// Provide the C++ wrapper for addition of Field3DParallel and Field3D -Field3DParallel operator+(const Field3DParallel& lhs, const Field3D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3DParallel result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) + rhs.yup(i); - result.ydown(i) = lhs.ydown(i) + rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] + rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3DParallel by addition with Field3D Field3DParallel& Field3DParallel::operator+=(const Field3D& rhs) { // only if data is unique we update the field @@ -2593,48 +1752,6 @@ Field3DParallel& Field3DParallel::operator+=(const Field3D& rhs) { return *this; } -// Provide the C++ wrapper for subtraction of Field3DParallel and Field3D -Field3DParallel operator-(const Field3DParallel& lhs, const Field3D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3DParallel result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) - rhs.yup(i); - result.ydown(i) = lhs.ydown(i) - rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] - rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3DParallel by subtraction with Field3D Field3DParallel& Field3DParallel::operator-=(const Field3D& rhs) { // only if data is unique we update the field @@ -2674,48 +1791,6 @@ Field3DParallel& Field3DParallel::operator-=(const Field3D& rhs) { return *this; } -// Provide the C++ wrapper for multiplication of Field3DParallel and Field3DParallel -Field3DParallel operator*(const Field3DParallel& lhs, const Field3DParallel& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3DParallel result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) * rhs.yup(i); - result.ydown(i) = lhs.ydown(i) * rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] * rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3DParallel by multiplication with Field3DParallel Field3DParallel& Field3DParallel::operator*=(const Field3DParallel& rhs) { // only if data is unique we update the field @@ -2755,48 +1830,6 @@ Field3DParallel& Field3DParallel::operator*=(const Field3DParallel& rhs) { return *this; } -// Provide the C++ wrapper for division of Field3DParallel and Field3DParallel -Field3DParallel operator/(const Field3DParallel& lhs, const Field3DParallel& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3DParallel result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) / rhs.yup(i); - result.ydown(i) = lhs.ydown(i) / rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] / rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3DParallel by division with Field3DParallel Field3DParallel& Field3DParallel::operator/=(const Field3DParallel& rhs) { // only if data is unique we update the field @@ -2836,48 +1869,6 @@ Field3DParallel& Field3DParallel::operator/=(const Field3DParallel& rhs) { return *this; } -// Provide the C++ wrapper for addition of Field3DParallel and Field3DParallel -Field3DParallel operator+(const Field3DParallel& lhs, const Field3DParallel& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3DParallel result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) + rhs.yup(i); - result.ydown(i) = lhs.ydown(i) + rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] + rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3DParallel by addition with Field3DParallel Field3DParallel& Field3DParallel::operator+=(const Field3DParallel& rhs) { // only if data is unique we update the field @@ -2917,48 +1908,6 @@ Field3DParallel& Field3DParallel::operator+=(const Field3DParallel& rhs) { return *this; } -// Provide the C++ wrapper for subtraction of Field3DParallel and Field3DParallel -Field3DParallel operator-(const Field3DParallel& lhs, const Field3DParallel& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3DParallel result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) - rhs.yup(i); - result.ydown(i) = lhs.ydown(i) - rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] - rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3DParallel by subtraction with Field3DParallel Field3DParallel& Field3DParallel::operator-=(const Field3DParallel& rhs) { // only if data is unique we update the field @@ -2998,42 +1947,6 @@ Field3DParallel& Field3DParallel::operator-=(const Field3DParallel& rhs) { return *this; } -// Provide the C++ wrapper for multiplication of Field3DParallel and BoutReal -Field3DParallel operator*(const Field3DParallel& lhs, const BoutReal rhs) { - - Field3DParallel result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getRegionID()); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) * rhs; - result.ydown(i) = lhs.ydown(i) * rhs; - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] * rhs; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3DParallel by multiplication with BoutReal Field3DParallel& Field3DParallel::operator*=(const BoutReal rhs) { // only if data is unique we update the field @@ -3070,43 +1983,6 @@ Field3DParallel& Field3DParallel::operator*=(const BoutReal rhs) { return *this; } -// Provide the C++ wrapper for division of Field3DParallel and BoutReal -Field3DParallel operator/(const Field3DParallel& lhs, const BoutReal rhs) { - - Field3DParallel result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getRegionID()); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) / rhs; - result.ydown(i) = lhs.ydown(i) / rhs; - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - const auto tmp = 1.0 / rhs; - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] * tmp; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3DParallel by division with BoutReal Field3DParallel& Field3DParallel::operator/=(const BoutReal rhs) { // only if data is unique we update the field @@ -3143,42 +2019,6 @@ Field3DParallel& Field3DParallel::operator/=(const BoutReal rhs) { return *this; } -// Provide the C++ wrapper for addition of Field3DParallel and BoutReal -Field3DParallel operator+(const Field3DParallel& lhs, const BoutReal rhs) { - - Field3DParallel result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getRegionID()); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) + rhs; - result.ydown(i) = lhs.ydown(i) + rhs; - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] + rhs; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3DParallel by addition with BoutReal Field3DParallel& Field3DParallel::operator+=(const BoutReal rhs) { // only if data is unique we update the field @@ -3215,42 +2055,6 @@ Field3DParallel& Field3DParallel::operator+=(const BoutReal rhs) { return *this; } -// Provide the C++ wrapper for subtraction of Field3DParallel and BoutReal -Field3DParallel operator-(const Field3DParallel& lhs, const BoutReal rhs) { - - Field3DParallel result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getRegionID()); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) - rhs; - result.ydown(i) = lhs.ydown(i) - rhs; - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] - rhs; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3DParallel by subtraction with BoutReal Field3DParallel& Field3DParallel::operator-=(const BoutReal rhs) { // only if data is unique we update the field @@ -3286,143 +2090,3 @@ Field3DParallel& Field3DParallel::operator-=(const BoutReal rhs) { } return *this; } - -// Provide the C++ wrapper for multiplication of BoutReal and Field3DParallel -Field3DParallel operator*(const BoutReal lhs, const Field3DParallel& rhs) { - - Field3DParallel result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(rhs.getRegionID()); - if (result.isFci()) { - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs * rhs.yup(i); - result.ydown(i) = lhs * rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs * rhs[index]; - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for division of BoutReal and Field3DParallel -Field3DParallel operator/(const BoutReal lhs, const Field3DParallel& rhs) { - - Field3DParallel result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(rhs.getRegionID()); - if (result.isFci()) { - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs / rhs.yup(i); - result.ydown(i) = lhs / rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs / rhs[index]; - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for addition of BoutReal and Field3DParallel -Field3DParallel operator+(const BoutReal lhs, const Field3DParallel& rhs) { - - Field3DParallel result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(rhs.getRegionID()); - if (result.isFci()) { - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs + rhs.yup(i); - result.ydown(i) = lhs + rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs + rhs[index]; - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for subtraction of BoutReal and Field3DParallel -Field3DParallel operator-(const BoutReal lhs, const Field3DParallel& rhs) { - - Field3DParallel result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(rhs.getRegionID()); - if (result.isFci()) { - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs - rhs.yup(i); - result.ydown(i) = lhs - rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs - rhs[index]; - } - checkData(result); - return result; -} diff --git a/tests/unit/fake_mesh.hxx b/tests/unit/fake_mesh.hxx index 6559e554f2..216d04e18a 100644 --- a/tests/unit/fake_mesh.hxx +++ b/tests/unit/fake_mesh.hxx @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -90,7 +91,7 @@ public: void setCoordinates(std::shared_ptr coords, CELL_LOC location = CELL_CENTRE) { - coords_map[location] = coords; + coords_map[location] = std::move(coords); } void setGridDataSource(GridDataSource* source_in) { source = source_in; } From d41d93c85d37993a5109815c66d714728a9c4f95 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 10 Jul 2026 16:30:57 -0700 Subject: [PATCH 036/221] Field3DParallel::View simplify Remove unnecessary data members that dublicate the Field3D::View base. --- include/bout/field3d.hxx | 46 ++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index ade329bb6c..5562c58b49 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -488,11 +488,13 @@ public: BOUT_HOST_DEVICE BOUT_FORCEINLINE int numberParallelSlices() const { return num_parallel_slices; } + /// Not a DEVICE function because it dereferences a Field3D pointer BOUT_FORCEINLINE View yup(int slice = 0) const { ASSERT2(slice < num_parallel_slices); ASSERT2(yup_fields[slice].isAllocated()); return static_cast(yup_fields[slice]); } + /// Not a DEVICE function because it dereferences a Field3D pointer BOUT_FORCEINLINE View ydown(int slice = 0) const { ASSERT2(slice < num_parallel_slices); ASSERT2(ydown_fields[slice].isAllocated()); @@ -500,14 +502,11 @@ public: } }; operator View() { - return View{&data[0], hasParallelSlices() ? yup_fields.data() : nullptr, - hasParallelSlices() ? ydown_fields.data() : nullptr, + return View{&data[0], yup_fields.data(), ydown_fields.data(), static_cast(numberParallelSlices())}; } operator View() const { - return View{const_cast(&data[0]), - hasParallelSlices() ? yup_fields.data() : nullptr, - hasParallelSlices() ? ydown_fields.data() : nullptr, + return View{const_cast(&data[0]), yup_fields.data(), ydown_fields.data(), static_cast(numberParallelSlices())}; } //operator View() const { return View{&data[0]}; } @@ -1064,9 +1063,6 @@ public: struct View { Field3D::View base; - const Field3D* yup_fields{nullptr}; - const Field3D* ydown_fields{nullptr}; - int num_parallel_slices{0}; BOUT_HOST_DEVICE BOUT_FORCEINLINE BoutReal operator()(int idx) const { return base(idx); @@ -1082,32 +1078,26 @@ public: return *this; } - BOUT_FORCEINLINE bool hasParallelSlices() const { return num_parallel_slices > 0; } - BOUT_FORCEINLINE int numberParallelSlices() const { return num_parallel_slices; } + BOUT_FORCEINLINE bool hasParallelSlices() const { return base.hasParallelSlices(); } + BOUT_FORCEINLINE int numberParallelSlices() const { + return base.numberParallelSlices(); + } + /// Not a DEVICE function because it dereferences a Field3D pointer BOUT_FORCEINLINE View yup(int slice = 0) const { - ASSERT2(slice < num_parallel_slices); - ASSERT2(yup_fields[slice].isAllocated()); - return View{static_cast(yup_fields[slice]), nullptr, nullptr, 0}; + ASSERT2(slice < base.num_parallel_slices); + ASSERT2(base.yup_fields[slice].isAllocated()); + return View{static_cast(base.yup_fields[slice])}; } + /// Not a DEVICE function because it dereferences a Field3D pointer BOUT_FORCEINLINE View ydown(int slice = 0) const { - ASSERT2(slice < num_parallel_slices); - ASSERT2(ydown_fields[slice].isAllocated()); - return View{static_cast(ydown_fields[slice]), nullptr, nullptr, 0}; + ASSERT2(slice < base.num_parallel_slices); + ASSERT2(base.ydown_fields[slice].isAllocated()); + return View{static_cast(base.ydown_fields[slice])}; } }; - operator View() { - return View{static_cast(*this), - hasParallelSlices() ? yup_fields.data() : nullptr, - hasParallelSlices() ? ydown_fields.data() : nullptr, - static_cast(numberParallelSlices())}; - } - operator View() const { - return View{static_cast(*this), - hasParallelSlices() ? yup_fields.data() : nullptr, - hasParallelSlices() ? ydown_fields.data() : nullptr, - static_cast(numberParallelSlices())}; - } + operator View() { return View{static_cast(*this)}; } + operator View() const { return View{static_cast(*this)}; } Field3DParallel& operator*=(const Field3D&); Field3DParallel& operator/=(const Field3D&); From 3c6565df8e66cfeb7c70ea1ccaad90001c7da664 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 10 Jul 2026 16:49:40 -0700 Subject: [PATCH 037/221] manual: Updates for Field3DParallel expressions --- manual/sphinx/developer_docs/data_types.rst | 33 ++++++++++------- .../sphinx/user_docs/algebraic_operators.rst | 17 +++++++-- manual/sphinx/user_docs/field_expressions.rst | 35 +++++++++++++++---- 3 files changed, 64 insertions(+), 21 deletions(-) diff --git a/manual/sphinx/developer_docs/data_types.rst b/manual/sphinx/developer_docs/data_types.rst index 7feb3945aa..ba4faa6e42 100644 --- a/manual/sphinx/developer_docs/data_types.rst +++ b/manual/sphinx/developer_docs/data_types.rst @@ -515,37 +515,44 @@ central type is ``BinaryExpr``, which stores: result - a cached list of linear region indices describing where the expression is valid +- for 3D expressions, any parallel-slice structure needed to evaluate + matching ``yup`` and ``ydown`` expressions -`Field2D`, `Field3D`, and `FieldPerp` act as expression leaves by -providing lightweight ``View`` types. Those views are the device- and -backend-friendly objects used by the expression evaluator. +`Field2D`, `Field3D`, `Field3DParallel`, and `FieldPerp` act as +expression leaves by providing lightweight ``View`` types. Constants are +also wrapped in small view objects so they can participate in the same +expression machinery. These views are the device- and backend-friendly +objects used by the expression evaluator. Materialization happens when a field is constructed or assigned from an expression, when an expression is stored in `Options`, or when a scalar reduction such as ``min`` or ``mean`` is requested. The same mechanism is also used to propagate metadata such as mesh, staggered location, -directions, and `FieldPerp` y-index. +directions, `FieldPerp` y-index, and for `Field3DParallel` the +forward/backward parallel slices on FCI meshes. The unary algebraic helpers in ``include/bout/field.hxx`` build on the same mechanism. Functions such as ``sqrt``, ``abs``, ``SQ``, -``if_else``, ``if_else_zero``, ``min``, ``max``, and ``mean`` can all -operate directly on lazy expressions. +``pow``, ``floor``, ``if_else``, ``if_else_zero``, ``min``, ``max``, +and ``mean`` can all operate directly on lazy expressions. Generated eager operators ~~~~~~~~~~~~~~~~~~~~~~~~~ -The eager arithmetic operators and in-place update paths are still -generated automatically using the `Jinja`_ templating system. The main -files are: +The remaining eager arithmetic operators and in-place update paths are +still generated automatically using the `Jinja`_ templating system. The +main files are: - ``src/field/gen_fieldops.jinja`` - ``src/field/gen_fieldops.py`` - ``src/field/generated_fieldops.cxx`` -The generated code handles the broad matrix of combinations between -`BoutReal`, `Field2D`, `Field3D`, `Field3DParallel`, and `FieldPerp`, -including several mixed-rank and in-place cases where hand-maintaining -all overloads would be error-prone. +The generated code still handles combinations between `BoutReal`, +`Field2D`, `Field3D`, `Field3DParallel`, and `FieldPerp`, especially the +eager `FieldPerp` wrappers and in-place update operators where +hand-maintaining all overloads would be error-prone. More free +non-member arithmetic now lives in the header-defined lazy-expression +path, so the generator covers a smaller subset than it used to. The generated loops now also depend on the configured execution backend. At configure time, the generator is told whether to emit RAJA-based, diff --git a/manual/sphinx/user_docs/algebraic_operators.rst b/manual/sphinx/user_docs/algebraic_operators.rst index b8c40d4dc5..6955ab5580 100644 --- a/manual/sphinx/user_docs/algebraic_operators.rst +++ b/manual/sphinx/user_docs/algebraic_operators.rst @@ -30,7 +30,7 @@ Common operators +------------------------------------------+------------------------------------------------------+ | ``mean(f, allpe=true, region)`` | Mean (optionally over all processes) | +------------------------------------------+------------------------------------------------------+ - | ``pow(lhs, rhs, region)`` | :math:`\mathtt{lhs}^\mathtt{rhs}` | + | ``pow(lhs, rhs[, region])`` | :math:`\mathtt{lhs}^\mathtt{rhs}` | +------------------------------------------+------------------------------------------------------+ | ``SQ(f, region)`` | Square of ``f`` | +------------------------------------------+------------------------------------------------------+ @@ -54,7 +54,8 @@ Common operators +------------------------------------------+------------------------------------------------------+ | ``tanh(f, region)`` | :math:`\tanh(f)` | +------------------------------------------+------------------------------------------------------+ - | ``floor(f, region)`` | Returns a field with the floor of `f` at each point | + | ``floor(f, floor_value[, region])`` | Returns a field where values below ``floor_value`` | + | | are replaced by ``floor_value`` | +------------------------------------------+------------------------------------------------------+ | ``filter(f, n, region)`` | Calculate the amplitude of the Fourier mode in the | | | z-direction with mode number `n` | @@ -84,12 +85,18 @@ Common operators These operators can usually be combined directly in expressions:: Field3D rhs = sqrt(SQ(n) + SQ(T)); + Field3D profile = pow(n + n0, 1.5); Field3D masked = if_else(use_drive, source * profile, sink * profile); BoutReal max_error = max(abs(lhs - rhs), true); Reductions such as ``min``, ``max``, and ``mean`` can operate directly on an expression, so an intermediate field is often unnecessary. +``pow`` also participates in lazy field expressions, including mixed +`Field2D`/`Field3D` cases where the `Field2D` operand is broadcast in +``z``. `FieldPerp` also has ``pow`` overloads for `FieldPerp` with +`FieldPerp` or a scalar. + Region arguments ---------------- @@ -108,6 +115,12 @@ When a region-limited expression is materialized into a field, only the selected region is guaranteed to contain valid values. This is the same performance-oriented convention used by other field operators. +`Field3DParallel` follows the same rule for the main field data. On FCI +meshes, materializing a lazy expression into `Field3DParallel` also +preserves the forward and backward parallel slices when those slices are +available on the expression operands. Materializing the same expression +into plain `Field3D` does not preserve those slices. + Further reading --------------- diff --git a/manual/sphinx/user_docs/field_expressions.rst b/manual/sphinx/user_docs/field_expressions.rst index 62a50988d6..7c82d48884 100644 --- a/manual/sphinx/user_docs/field_expressions.rst +++ b/manual/sphinx/user_docs/field_expressions.rst @@ -5,9 +5,9 @@ Field Expressions BOUT++ field algebra now supports *lazy expressions* for many common operations. Instead of creating a temporary field for every ``+``, -``-``, ``*``, ``/``, ``sqrt`` or ``abs``, BOUT++ can keep the expression -symbolic and evaluate it only when a concrete field or scalar result is -needed. +``-``, ``*``, ``/``, ``pow``, ``sqrt`` or ``abs``, BOUT++ can keep the +expression symbolic and evaluate it only when a concrete field or scalar +result is needed. This keeps ordinary model code readable while reducing temporary allocations and extra loops over the mesh. It is especially helpful for @@ -21,9 +21,9 @@ The following operations can form lazy expressions over `Field2D`, sense: - Arithmetic operators: ``+``, ``-``, ``*``, ``/`` +- Binary algebraic helpers such as ``pow`` and ``floor`` - Unary algebraic operators such as ``sqrt``, ``abs``, ``exp``, ``log``, - ``sin``, ``cos``, ``tan``, ``sinh``, ``cosh``, ``tanh``, ``floor``, - and ``SQ`` + ``sin``, ``cos``, ``tan``, ``sinh``, ``cosh``, ``tanh``, and ``SQ`` - Simple conditionals with ``if_else`` and ``if_else_zero`` - Reductions such as ``min``, ``max``, and ``mean`` @@ -32,7 +32,7 @@ For example:: Field3D n, T; Field3D result; - result = sqrt(SQ(n) + SQ(T)); + result = sqrt(SQ(n) + pow(T + 1.0, 2.0)); The right-hand side can stay lazy until the assignment to ``result``. @@ -93,6 +93,8 @@ Several mixed-type combinations are supported directly: - `Field2D` with `Field3D`: the 2D quantity is broadcast in ``z`` - `FieldPerp` with matching perpendicular data: the operation uses the `FieldPerp` y-index +- `pow` follows the same mixed-rank pattern, so either operand can be + `Field2D` or `Field3D` and the result is a `Field3D` - expressions involving metric components may return `Coordinates::FieldMetric`, which is `Field2D` or `Field3D` depending on how BOUT++ was built @@ -121,6 +123,27 @@ expression or zero:: This is particularly convenient when optional source terms are enabled or disabled by compile-time or run-time logic. +`Field3DParallel` and FCI +------------------------- + +Lazy expressions can also carry parallel-slice information. This matters +when working with `Field3DParallel` on FCI meshes: + +- materializing into `Field3DParallel` preserves ``yup`` and ``ydown`` + slices +- materializing the same expression into plain `Field3D` keeps only the + main field values +- on FCI meshes, operands contributing to a `Field3DParallel` + expression must have compatible parallel slices available + +For example:: + + Field3DParallel f_par, g_par; + Field3DParallel result = sqrt(f_par + g_par); + +On a non-FCI mesh, assigning a lazy expression to `Field3DParallel` +still evaluates the main field, but no parallel slices are retained. + Reductions on expressions ------------------------- From d8e5419794a30546b73a3842d54610d45f12f9ee Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 17 Jul 2026 17:11:14 -0700 Subject: [PATCH 038/221] Options: ConvertContainer for Array/Matrix/Tensor NVCC doesn't accept `Array>` as `C` so treats ConvertContainer as partially specialized. This explicitly writes convertors for Array, Matrix and Tensor containers. --- src/sys/options.cxx | 87 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 13 deletions(-) diff --git a/src/sys/options.cxx b/src/sys/options.cxx index 85cfa7a49a..5f1f2fe91b 100644 --- a/src/sys/options.cxx +++ b/src/sys/options.cxx @@ -749,13 +749,11 @@ namespace { template struct ConvertContainer; -/// Visitor to convert an int, BoutReal or Array/Matrix/Tensor to the -/// appropriate container. Templated on both the container class C -/// and scalar type Scalar. -template