diff --git a/.docker/fedora/Dockerfile b/.docker/fedora/Dockerfile index da4d2d9fac..7d5c04418d 100644 --- a/.docker/fedora/Dockerfile +++ b/.docker/fedora/Dockerfile @@ -26,12 +26,13 @@ ENV HOME=/home/boutuser WORKDIR /home/boutuser/BOUT-dev RUN cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr/local/ \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DBOUT_GENERATE_FIELDOPS=OFF \ -DBOUT_USE_PETSC=ON -DPETSc_ROOT=/usr/local \ -DBOUT_ENABLE_PYTHON=ON \ -DBOUT_USE_SUNDIALS=ON -DSUNDIALS_ROOT=/usr/lib64/$MPI/ -DSUNDIALS_INCLUDE_DIR=/usr/include/$MPI-x86_64/sundials/ \ $CMAKE_OPTIONS || (cat /home/boutuser/BOUT-dev/build/CMakeFiles/CMake{Output,Error}.log ; exit 1); \ - make -C build -j 2; \ + make -C build -j 2 VERBOSE=1; \ sudo make -C build install; \ rm -rf build diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 010100588c..3b0c5739fd 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -3,8 +3,12 @@ d8f14fdddb5ca0fbb32d8e2bf5ac2960d6ac5ce6 ed2117e6d6826a98b6988e2f18c0c34e408563b6 # CMake formatting 17ac13c28aa3b34a0e46dbe87bb3874f6b25e706 +a26a7961b1bc21d389259efe16f0608590bdf0cd # Added by the bot 4b010b7634aee1045743be80c268d4644522cd29 +67d8ac88a9f1ebc33f3db76f39abea1e7db11d8a a71cad2dd6ace5741a754e2ca7daacd4bb094e0e 2c2402ed59c91164eaff46dee0f79386b7347e9e 05b7c571544c3bcb153fce67d12b9ac48947fc2d +14050c2ffecec93dd5db56290884d62959062024 +48ac5ba58bb6f70e7d16cba7ea6d8ffe9154e6de diff --git a/.github/workflows/clang-tidy-review.yml b/.github/workflows/clang-tidy-review.yml index 087c910987..360a162456 100644 --- a/.github/workflows/clang-tidy-review.yml +++ b/.github/workflows/clang-tidy-review.yml @@ -21,7 +21,7 @@ jobs: submodules: true - name: Run clang-tidy - uses: ZedThree/clang-tidy-review@v0.23.0 + uses: ZedThree/clang-tidy-review@v0.23.1 id: review with: build_dir: build @@ -46,4 +46,4 @@ jobs: -DBOUT_UPDATE_GIT_SUBMODULE=OFF - name: Upload clang-tidy fixes - uses: ZedThree/clang-tidy-review/upload@v0.23.0 + uses: ZedThree/clang-tidy-review/upload@v0.23.1 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index c028d17042..74988762f3 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -6,7 +6,7 @@ on: - master - next # Add your branch here if you want containers for it - - fix3121 + - f3dwy - docker-ci env: @@ -26,11 +26,11 @@ jobs: matrix: mpi: [mpich] metric3d: - - name: "With OpenMP" + - name: "With 3D Metrics" cmake: ON - base_prefix: "openmp-" + base_prefix: "" tag_prefix: "3d-" - - name: "Without OpenMP" + - name: "Without 3D Metrics" cmake: OFF base_prefix: "" tag_prefix: "" @@ -75,7 +75,7 @@ jobs: build-args: | BASE=${{ matrix.mpi }}-${{ matrix.metric3d.base_prefix }}${{ matrix.config.base_postfix }}-main MPI=${{ matrix.mpi }} - CMAKE_OPTIONS=${{ matrix.config.options }} -DBOUT_ENABLE_METRIC_3D=${{ matrix.metric3d.cmake }} -DBOUT_ENABLE_OPENMP=${{ matrix.metric3d.cmake }} + CMAKE_OPTIONS=${{ matrix.config.options }} -DBOUT_ENABLE_METRIC_3D=${{ matrix.metric3d.cmake }} COMMIT=${{ github.sha }} URL=${{ github.server_url }}/${{ github.repository }} context: .docker/fedora/ diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f44ebb87ec..8852ca5282 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -181,6 +181,7 @@ jobs: - name: Install pip packages run: | python -m pip install --upgrade pip setuptools + python -m pip install git+https://github.com/boutproject/zoidberg@better-metric python -m pip install -r requirements.txt - name: Cache Zenodo test data diff --git a/CMakeLists.txt b/CMakeLists.txt index c05bad29db..a080217832 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -294,11 +294,12 @@ set(BOUT_SOURCES ./src/mesh/interpolation/hermite_spline_z.cxx ./src/mesh/interpolation/interpolation_z.cxx ./src/mesh/interpolation/lagrange_4pt_xz.cxx - ./src/mesh/interpolation/monotonic_hermite_spline_xz.cxx ./src/mesh/invert3x3.hxx ./src/mesh/mesh.cxx ./src/mesh/parallel/fci.cxx ./src/mesh/parallel/fci.hxx + ./src/mesh/parallel/fci_comm.cxx + ./src/mesh/parallel/fci_comm.hxx ./src/mesh/parallel/identity.cxx ./src/mesh/parallel/shiftedmetric.cxx ./src/mesh/parallel/shiftedmetricinterp.cxx @@ -402,6 +403,7 @@ if(zoidberg_FOUND EQUAL 0) else() set(zoidberg_FOUND OFF) endif() +message(STATUS "Found Zoidberg for FCI tests: ${zoidberg_FOUND}") option( BOUT_GENERATE_FIELDOPS @@ -657,6 +659,9 @@ else() endif() set(BOUT_USE_METRIC_3D ${BOUT_ENABLE_METRIC_3D}) +option(BOUT_ENABLE_FCI_AUTOMAGIC "Enable (slow?) automatic features for FCI" ON) +set(BOUT_USE_FCI_AUTOMAGIC ${BOUT_ENABLE_FCI_AUTOMAGIC}) + include(CheckCXXSourceCompiles) check_cxx_source_compiles( "int main() { const char* name = __PRETTY_FUNCTION__; }" HAS_PRETTY_FUNCTION diff --git a/bin/bout-build-deps.sh b/bin/bout-build-deps.sh index d96d500dc9..6ae7ac1895 100755 --- a/bin/bout-build-deps.sh +++ b/bin/bout-build-deps.sh @@ -10,7 +10,7 @@ NCVER=${NCVER:-4.7.4} NCCXXVER=${NCCXXVER:-4.3.1} FFTWVER=${FFTWVER:-3.3.9} SUNVER=${SUNVER:-5.7.0} -PETSCVER=${PETSCVER:-3.15.0} +PETSCVER=${PETSCVER:-3.21.4} HDF5FLAGS=${HDF5FLAGS:-} @@ -147,7 +147,7 @@ petsc() { test -z $PETSC_DIR || error "\$PETSC_DIR is set ($PETSC_DIR) - please unset" test -z $PETSC_ARCH || error "\$PETSC_ARCH is set ($PETSC_ARCH) - please unset" cd $BUILD - wget -c https://ftp.mcs.anl.gov/pub/petsc/release-snapshots/petsc-$PETSCVER.tar.gz || : + wget -c https://web.cels.anl.gov/projects/petsc/download/release-snapshots/petsc-$PETSCVER.tar.gz || : tar -xf petsc-$PETSCVER.tar.gz cd petsc-$PETSCVER unset PETSC_DIR diff --git a/bout++Config.cmake.in b/bout++Config.cmake.in index a8cf041901..5cc68aefde 100644 --- a/bout++Config.cmake.in +++ b/bout++Config.cmake.in @@ -8,7 +8,7 @@ set(BOUT_USE_TRACK @BOUT_USE_TRACK@) set(BOUT_USE_SIGFPE @BOUT_USE_SIGFPE@) set(BOUT_USE_OPENMP @BOUT_USE_OPENMP@) set(BOUT_HAS_CUDA @BOUT_HAS_CUDA@) -set(BOUT_HAS_OUTPUT_DEBUG @BOUT_HAS_OUTPUT_DEBUG@) +set(BOUT_USE_OUTPUT_DEBUG @BOUT_USE_OUTPUT_DEBUG@) set(BOUT_CHECK_LEVEL @BOUT_CHECK_LEVEL@) set(BOUT_USE_METRIC_3D @BOUT_USE_METRIC_3D@) @@ -182,3 +182,30 @@ if (BOUT_HAS_ADIOS2) endif() include("${CMAKE_CURRENT_LIST_DIR}/bout++Targets.cmake") + +add_library(bout++ INTERFACE IMPORTED) +set_target_properties(bout++ + PROPERTIES BOUT_USE_SIGNAL @BOUT_USE_SIGNAL@ + BOUT_USE_COLOR @BOUT_USE_COLOR@ + BOUT_USE_TRACK @BOUT_USE_TRACK@ + BOUT_USE_SIGFPE @BOUT_USE_SIGFPE@ + BOUT_USE_OPENMP @BOUT_USE_OPENMP@ + BOUT_HAS_CUDA @BOUT_HAS_CUDA@ + BOUT_USE_OUTPUT_DEBUG @BOUT_USE_OUTPUT_DEBUG@ + BOUT_CHECK_LEVEL @BOUT_CHECK_LEVEL@ + BOUT_USE_METRIC_3D @BOUT_USE_METRIC_3D@ + BOUT_HAS_PVODE @BOUT_HAS_PVODE@ + BOUT_HAS_NETCDF @BOUT_HAS_NETCDF@ + BOUT_HAS_ADIOS2 @BOUT_HAS_ADIOS2@ + BOUT_HAS_FFTW @BOUT_HAS_FFTW@ + BOUT_HAS_LAPACK @BOUT_HAS_LAPACK@ + BOUT_HAS_PETSC @BOUT_HAS_PETSC@ + BOUT_HAS_SLEPC @BOUT_HAS_SLEPC@ + BOUT_HAS_SCOREP @BOUT_HAS_SCOREP@ + BOUT_USE_UUID_SYSTEM_GENERATOR @BOUT_USE_UUID_SYSTEM_GENERATOR@ + BOUT_HAS_SUNDIALS @BOUT_HAS_SUNDIALS@ + BOUT_HAS_HYPRE @BOUT_HAS_HYPRE@ + BOUT_HAS_GETTEXT @BOUT_HAS_GETTEXT@ + BOUT_HAS_UMPIRE @BOUT_HAS_UMPIRE@ + BOUT_HAS_RAJA @BOUT_HAS_RAJA@ +) diff --git a/cmake/SetupBOUTThirdParty.cmake b/cmake/SetupBOUTThirdParty.cmake index 850809e856..9e6bdc6d2a 100644 --- a/cmake/SetupBOUTThirdParty.cmake +++ b/cmake/SetupBOUTThirdParty.cmake @@ -362,7 +362,7 @@ if(BOUT_USE_SUNDIALS) CACHE BOOL "" FORCE ) set(ENABLE_OPENMP - OFF + ${BOUT_USE_OPENMP} CACHE BOOL "" FORCE ) if(BUILD_SHARED_LIBS) diff --git a/cmake_build_defines.hxx.in b/cmake_build_defines.hxx.in index 9c73679f29..0d4f5af509 100644 --- a/cmake_build_defines.hxx.in +++ b/cmake_build_defines.hxx.in @@ -34,6 +34,7 @@ #cmakedefine BOUT_METRIC_TYPE @BOUT_METRIC_TYPE@ #cmakedefine01 BOUT_USE_METRIC_3D #cmakedefine01 BOUT_USE_MSGSTACK +#cmakedefine01 BOUT_USE_FCI_AUTOMAGIC // CMake build does not support legacy interface #define BOUT_HAS_LEGACY_NETCDF 0 diff --git a/examples/fci-wave/div-integrate/BOUT.inp b/examples/fci-wave/div-integrate/BOUT.inp index d9ae651515..1c056269a0 100644 --- a/examples/fci-wave/div-integrate/BOUT.inp +++ b/examples/fci-wave/div-integrate/BOUT.inp @@ -38,7 +38,7 @@ bndry_par_ydown = parallel_neumann_o2 [v] -bndry_par_all = parallel_dirichlet +bndry_par_all = parallel_dirichlet_o2 [solver] output_step = 0.1 diff --git a/examples/fci-wave/div/BOUT.inp b/examples/fci-wave/div/BOUT.inp index bffd35149d..be5169fb33 100644 --- a/examples/fci-wave/div/BOUT.inp +++ b/examples/fci-wave/div/BOUT.inp @@ -38,7 +38,7 @@ bndry_par_ydown = parallel_neumann_o2 [v] -bndry_par_all = parallel_dirichlet +bndry_par_all = parallel_dirichlet_o2 [solver] output_step = 0.1 diff --git a/examples/fci-wave/logn/BOUT.inp b/examples/fci-wave/logn/BOUT.inp index 842ba63ff5..ddbbe2f257 100644 --- a/examples/fci-wave/logn/BOUT.inp +++ b/examples/fci-wave/logn/BOUT.inp @@ -38,7 +38,7 @@ bndry_par_ydown = parallel_neumann_o2 [nv] -bndry_par_all = parallel_dirichlet +bndry_par_all = parallel_dirichlet_o2 [solver] output_step = 0.1 diff --git a/externalpackages/PVODE/include/pvode/band.h b/externalpackages/PVODE/include/pvode/band.h index 1c2d21f7ef..4f54397c92 100644 --- a/externalpackages/PVODE/include/pvode/band.h +++ b/externalpackages/PVODE/include/pvode/band.h @@ -138,7 +138,7 @@ typedef struct bandmat_type { * * ******************************************************************/ -#define PVODE_BAND_ELEM(A,i,j) ((A->data)[j][i-j+(A->smu)]) +#define PVODE_BAND_ELEM(A, i, j) (((A)->data)[j][(i) - (j) + ((A)->smu)]) /****************************************************************** * * @@ -153,7 +153,7 @@ typedef struct bandmat_type { * * ******************************************************************/ -#define PVODE_BAND_COL(A,j) (((A->data)[j])+(A->smu)) +#define PVODE_BAND_COL(A, j) ((((A)->data)[j]) + ((A)->smu)) /****************************************************************** * * diff --git a/externalpackages/PVODE/precon/band.h b/externalpackages/PVODE/precon/band.h index 0817e3cdc0..727c1dc942 100644 --- a/externalpackages/PVODE/precon/band.h +++ b/externalpackages/PVODE/precon/band.h @@ -153,7 +153,7 @@ typedef struct bandmat_type { * * ******************************************************************/ -#define BAND_COL(A, j) (((A->data)[j]) + (A->smu)) +#define PVODE_BAND_COL(A, j) ((((A)->data)[j]) + ((A)->smu)) /****************************************************************** * * diff --git a/include/bout/boundary_iterator.hxx b/include/bout/boundary_iterator.hxx new file mode 100644 index 0000000000..dbc29bf201 --- /dev/null +++ b/include/bout/boundary_iterator.hxx @@ -0,0 +1,218 @@ +#pragma once + +#include "bout/assert.hxx" +#include "bout/bout_types.hxx" +#include "bout/build_defines.hxx" +#include "bout/field2d.hxx" +#include "bout/field3d.hxx" +#include "bout/mesh.hxx" +#include "bout/parallel_boundary_region.hxx" +#include "bout/region.hxx" +#include "bout/sys/parallel_stencils.hxx" +#include "bout/sys/range.hxx" + +#include +#include + +class BoundaryRegionIter { +public: + BoundaryRegionIter(int x, int y, int bx, int by, Mesh* mesh) + : dir(bx + by), x(x), y(y), bx(bx), by(by), localmesh(mesh) { + ASSERT3(bx * by == 0); + } + bool operator!=(const BoundaryRegionIter& rhs) const { return ind() != rhs.ind(); } + + Ind3D ind() const { return xyz2ind(x, y, z); } + BoundaryRegionIter& operator++() { + ASSERT3(z < nz()); + z++; + if (z == nz()) { + z = 0; + _next(); + } + return *this; + } + virtual void _next() = 0; + BoundaryRegionIter& operator*() { return *this; } + + void dirichlet_o2(Field3D& f, BoutReal value) const { + ynext(f) = parallel_stencil::dirichlet_o2(1, f[ind()], 0.5, value); + } + + BoutReal extrapolate_grad_o2(const Field3D& f) const { return f[ind()] - yprev(f); } + + BoutReal extrapolate_sheath_o2(const Field3D& f) const { + return (f[ind()] * 3 - yprev(f)) * 0.5; + } + + BoutReal extrapolate_next_o2(const Field3D& f) const { + return (2 * f[ind()]) - yprev(f); + } + + BoutReal + extrapolate_next_o2(const std::function& f) const { + return (2 * f(0, ind())) - f(0, ind().yp(-by).xp(-bx)); + } + + BoutReal interpolate_sheath_o2(const Field3D& f) const { + return (f[ind()] + ynext(f)) * 0.5; + } + + BoutReal + interpolate_sheath_o2(const std::function& f) const { + return (f(0, ind()) + f(0, ind().yp(-by).xp(-bx))) * 0.5; + } + + BoutReal + extrapolate_sheath_o2(const std::function& f) const { + return 0.5 * (3 * f(0, ind()) - f(0, ind().yp(-by).xp(-bx))); + } + + BoutReal extrapolate_sheath_free(const Field3D& f, SheathLimitMode mode) const { + const BoutReal fac = + bout::parallel_boundary_region::limitFreeScale(yprev(f), ythis(f), mode); + const BoutReal val = ythis(f); + const BoutReal next = mode == SheathLimitMode::linear_free ? val + fac : val * fac; + return 0.5 * (val + next); + } + + void set_free(Field3D& f, SheathLimitMode mode) const { + const BoutReal fac = + bout::parallel_boundary_region::limitFreeScale(yprev(f), ythis(f), mode); + BoutReal val = ythis(f); + if (mode == SheathLimitMode::linear_free) { + for (int i = 1; i <= localmesh->ystart; ++i) { + val += fac; + f[ind().yp(by * i).xp(bx * i)] = val; + } + } else { + for (int i = 1; i <= localmesh->ystart; ++i) { + val *= fac; + f[ind().yp(by * i).xp(bx * i)] = val; + } + } + } + + void limitFree(Field3D& f) const { + const BoutReal fac = + bout::parallel_boundary_region::limitFreeScale(yprev(f), ythis(f)); + BoutReal val = ythis(f); + for (int i = 1; i <= localmesh->ystart; ++i) { + val *= fac; + f[ind().yp(by * i).xp(bx * i)] = val; + } + } + + bool is_lower() const { + ASSERT2(bx == 0); + return by == -1; + } + + void neumann_o1(Field3D& f, BoutReal grad) const { + BoutReal val = ythis(f); + for (int i = 1; i <= localmesh->ystart; ++i) { + val += grad; + f[ind().yp(by * i).xp(bx * i)] = val; + } + } + + void neumann_o2(Field3D& f, BoutReal grad) const { + BoutReal val = yprev(f) + grad; + for (int i = 1; i <= localmesh->ystart; ++i) { + val += grad; + f[ind().yp(by * i).xp(bx * i)] = val; + } + } + + void limit_at_least(Field3D& f, BoutReal value) const { + ynext(f) = std::max(ynext(f), value); + } + + BoutReal& ynext(Field3D& f) const { return f[ind().yp(by).xp(bx)]; } + const BoutReal& ynext(const Field3D& f) const { return f[ind().yp(by).xp(bx)]; } + BoutReal& yprev(Field3D& f) const { return f[ind().yp(-by).xp(-bx)]; } + const BoutReal& yprev(const Field3D& f) const { return f[ind().yp(-by).xp(-bx)]; } + BoutReal& ythis(Field3D& f) const { return f[ind()]; } + const BoutReal& ythis(const Field3D& f) const { return f[ind()]; } + + void setYPrevIfValid(Field3D& f, BoutReal val) const { yprev(f) = val; } + void setAll(Field3D& f, const BoutReal val) const { + for (int i = -localmesh->ystart; i <= localmesh->ystart; ++i) { + f[ind().yp(by * i).xp(bx * i)] = val; + } + } + + static int abs_offset() { return 1; } + +#if BOUT_USE_METRIC_3D == 0 + BoutReal& ynext(Field2D& f) const { return f[ind().yp(by).xp(bx)]; } + const BoutReal& ynext(const Field2D& f) const { return f[ind().yp(by).xp(bx)]; } + BoutReal& yprev(Field2D& f) const { return f[ind().yp(-by).xp(-bx)]; } + const BoutReal& yprev(const Field2D& f) const { return f[ind().yp(-by).xp(-bx)]; } +#endif + + const int dir; + virtual ~BoundaryRegionIter() = default; + +protected: + int z{0}; + int x; + int y; + const int bx; + const int by; + +private: + Mesh* localmesh; + int nx() const { return localmesh->LocalNx; } + int ny() const { return localmesh->LocalNy; } + int nz() const { return localmesh->LocalNz; } + + Ind3D xyz2ind(int x, int y, int z) const { + return Ind3D{((x * ny() + y) * nz()) + z, ny(), nz()}; + } +}; + +class BoundaryRegionIterY : public BoundaryRegionIter { +public: + BoundaryRegionIterY(const RangeIterator& r, int y, int dir, bool is_end, Mesh* mesh) + : BoundaryRegionIter(r.ind, y, 0, dir, mesh), r(r), is_end(is_end) {} + + bool operator!=(const BoundaryRegionIterY& rhs) { + ASSERT2(y == rhs.y); + if (is_end) { + if (rhs.is_end) { + return false; + } + return !rhs.r.isDone(); + } + if (rhs.is_end) { + return !r.isDone(); + } + return x != rhs.x; + } + + void _next() override { + ++r; + x = r.ind; + } + +private: + RangeIterator r; + bool is_end; +}; + +class NewBoundaryRegionY { +public: + NewBoundaryRegionY(Mesh* mesh, bool lower, const RangeIterator& r) + : mesh(mesh), lower(lower), r(r) {} + BoundaryRegionIterY begin(bool begin = true) { + return BoundaryRegionIterY(r, lower ? mesh->ystart : mesh->yend, lower ? -1 : +1, + !begin, mesh); + } + BoundaryRegionIterY end() { return begin(false); } + +private: + Mesh* mesh; + bool lower; + RangeIterator r; +}; diff --git a/include/bout/boundary_region.hxx b/include/bout/boundary_region.hxx index 58de12045e..acee4d3c7f 100644 --- a/include/bout/boundary_region.hxx +++ b/include/bout/boundary_region.hxx @@ -4,6 +4,7 @@ class BoundaryRegion; #ifndef BOUT_BNDRY_REGION_H #define BOUT_BNDRY_REGION_H +#include "bout/mesh.hxx" #include #include diff --git a/include/bout/coordinates.hxx b/include/bout/coordinates.hxx index e7ead42ee5..f65a72621d 100644 --- a/include/bout/coordinates.hxx +++ b/include/bout/coordinates.hxx @@ -38,6 +38,7 @@ #include #include #include +#include class Mesh; @@ -102,6 +103,25 @@ public: /// Covariant metric tensor FieldMetric g_11, g_22, g_33, g_12, g_13, g_23; + /// get g_22 at the cell faces; + const FieldMetric& g_22_ylow() const; + const FieldMetric& g_22_yhigh() const; + FieldMetric& g_22_ylow(); + FieldMetric& g_22_yhigh(); + /// get Jxz at the cell faces or cell centre + const FieldMetric& Jxz_ylow() const; + const FieldMetric& Jxz_yhigh() const; + const FieldMetric& Jxz() const; + FieldMetric& Jxz_ylow(); + FieldMetric& Jxz_yhigh(); + FieldMetric& Jxz(); + +private: + mutable std::optional _g_22_ylow, _g_22_yhigh; + mutable std::optional _jxz_ylow, _jxz_yhigh, _jxz_centre; + void _compute_Jxz_cell_faces() 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; @@ -160,7 +180,7 @@ public: const std::string& method = "DEFAULT", const std::string& region = "RGN_NOBNDRY"); - Field3D DDY(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, + Field3D DDY(const Field3DParallel& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT", const std::string& region = "RGN_NOBNDRY") const; @@ -172,7 +192,7 @@ public: FieldMetric Grad_par(const Field2D& var, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT"); - Field3D Grad_par(const Field3D& var, CELL_LOC outloc = CELL_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) @@ -180,7 +200,7 @@ public: CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT"); - Field3D Vpar_Grad_par(const Field3D& v, const Field3D& f, + Field3D Vpar_Grad_par(const Field3D& v, const Field3DParallel& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT"); @@ -188,14 +208,14 @@ public: FieldMetric Div_par(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT"); - Field3D Div_par(const Field3D& f, CELL_LOC outloc = CELL_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 Field3D& f, CELL_LOC outloc = CELL_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 @@ -207,13 +227,13 @@ public: // 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 Field3D& 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 Field3D& f, CELL_LOC outloc = CELL_DEFAULT, + Field3D Laplace(const Field3DParallel& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& dfdy_boundary_conditions = "free_o3", const std::string& dfdy_dy_region = ""); @@ -236,12 +256,14 @@ private: /// Cache variable for Grad2_par2 mutable std::map> Grad2_par2_DDY_invSgCache; mutable std::unique_ptr invSgCache{nullptr}; + mutable std::optional JgCache; /// 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& Jg() const; const FieldMetric& Grad2_par2_DDY_invSg(CELL_LOC outloc, const std::string& method) const; diff --git a/include/bout/deriv_store.hxx b/include/bout/deriv_store.hxx index b2b7928c2e..7c08802cae 100644 --- a/include/bout/deriv_store.hxx +++ b/include/bout/deriv_store.hxx @@ -33,8 +33,10 @@ #include #include #include +#include #include +#include "bout/field3d.hxx" #include #include @@ -522,4 +524,13 @@ private: } }; +template +auto& getStore() { + if constexpr (std::is_same::value) { + return DerivativeStore::getInstance(); + } else { + return DerivativeStore::getInstance(); + } +} + #endif diff --git a/include/bout/derivs.hxx b/include/bout/derivs.hxx index 1c360bb9cd..a8d9279378 100644 --- a/include/bout/derivs.hxx +++ b/include/bout/derivs.hxx @@ -82,7 +82,7 @@ Coordinates::FieldMetric DDX(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 -Field3D DDY(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, +Field3D DDY(const Field3DParallel& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT", const std::string& region = "RGN_NOBNDRY"); @@ -410,7 +410,7 @@ Coordinates::FieldMetric VDDX(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 -Field3D VDDY(const Field3D& v, const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, +Field3D VDDY(const Field3D& v, const Field3DParallel& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT", const std::string& region = "RGN_NOBNDRY"); @@ -533,7 +533,7 @@ Coordinates::FieldMetric FDDX(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 -Field3D FDDY(const Field3D& v, const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, +Field3D FDDY(const Field3D& v, const Field3DParallel& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT", const std::string& region = "RGN_NOBNDRY"); diff --git a/include/bout/difops.hxx b/include/bout/difops.hxx index 8b502002f2..2cd99f8d33 100644 --- a/include/bout/difops.hxx +++ b/include/bout/difops.hxx @@ -40,7 +40,9 @@ #include "bout/field3d.hxx" #include "bout/bout_types.hxx" -#include "bout/solver.hxx" +#include "bout/coordinates.hxx" + +class Solver; /*! * Parallel derivative (central differencing) in Y @@ -193,6 +195,10 @@ Field3D Div_par_K_Grad_par(const Field3D& kY, const Field2D& f, Field3D Div_par_K_Grad_par(const Field3D& kY, const Field3D& f, CELL_LOC outloc = CELL_DEFAULT); +/// Version with energy flow diagnostic +Field3D Div_par_K_Grad_par_mod(const Field3D& k, const Field3D& f, Field3D& flow_ylow, + bool bndry_flux = true); + /*! * Perpendicular Laplacian operator * diff --git a/include/bout/field.hxx b/include/bout/field.hxx index 4e41aa3632..c33b279335 100644 --- a/include/bout/field.hxx +++ b/include/bout/field.hxx @@ -24,12 +24,16 @@ * */ +#include "bout/build_defines.hxx" +#include "bout/unused.hxx" +#include class Field; #ifndef FIELD_H #define FIELD_H #include +#include #include #include "bout/bout_types.hxx" @@ -125,6 +129,12 @@ public: swap(first.directions, second.directions); } + virtual void setRegion(size_t UNUSED(regionID)) {} + virtual void setRegion(std::optional UNUSED(regionID)) {} + virtual void setRegion(const std::string& UNUSED(region_name)) {} + virtual void resetRegion() {} + virtual std::optional getRegionID() const { return {}; } + private: /// Labels for the type of coordinate system this field is defined over DirectionTypes directions{YDirectionType::Standard, ZDirectionType::Standard}; @@ -176,7 +186,8 @@ inline bool areFieldsCompatible(const Field& field1, const Field& field2) { template inline T emptyFrom(const T& f) { static_assert(bout::utils::is_Field_v, "emptyFrom only works on Fields"); - return T(f.getMesh(), f.getLocation(), {f.getDirectionY(), f.getDirectionZ()}) + return T(f.getMesh(), f.getLocation(), + DirectionTypes{f.getDirectionY(), f.getDirectionZ()}, f.getRegionID()) .allocate(); } @@ -281,6 +292,7 @@ inline void checkPositive(const T& f, const std::string& name = "field", template inline T toFieldAligned(const T& f, const std::string& region = "RGN_ALL") { static_assert(bout::utils::is_Field_v, "toFieldAligned only works on Fields"); + ASSERT3(f.getCoordinates() != nullptr); return f.getCoordinates()->getParallelTransform().toFieldAligned(f, region); } @@ -288,6 +300,7 @@ inline T toFieldAligned(const T& f, const std::string& region = "RGN_ALL") { template inline T fromFieldAligned(const T& f, const std::string& region = "RGN_ALL") { static_assert(bout::utils::is_Field_v, "fromFieldAligned only works on Fields"); + ASSERT3(f.getCoordinates() != nullptr); return f.getCoordinates()->getParallelTransform().fromFieldAligned(f, region); } @@ -508,20 +521,27 @@ T pow(BoutReal lhs, const T& rhs, const std::string& rgn = "RGN_ALL") { * result for non-finite numbers * */ +class Field3DParallel; #ifdef FIELD_FUNC #error This macro has already been defined #else -#define FIELD_FUNC(name, func) \ - template > \ - inline T name(const T& f, const std::string& rgn = "RGN_ALL") { \ - \ - /* 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]); } \ - checkData(result); \ - return result; \ +#define FIELD_FUNC(_name, func) \ + template > \ + inline T _name(const T& f, const std::string& rgn = "RGN_ALL") { \ + /* Check if the input is allocated */ \ + checkData(f); \ + /* Define and allocate the output result */ \ + T result{emptyFrom(f)}; \ + BOUT_FOR(d, result.getRegion(rgn)) { result[d] = func(f[d]); } \ + if constexpr (std::is_base_of_v) { \ + for (size_t 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; \ } #endif @@ -645,6 +665,8 @@ T copy(const T& f) { return result; } +class Field3DParallel; + /// Apply a floor value \p f to a field \p var. Any value lower than /// the floor is set to the floor. /// @@ -661,10 +683,54 @@ inline T floor(const T& var, BoutReal f, const std::string& rgn = "RGN_ALL") { 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; } #undef FIELD_FUNC +template , class... Types> +inline void setName(T& f, const std::string& name, Types... args) { +#if BOUT_USE_TRACK + f.name = fmt::format(name, args...); +#endif +} + +template , class... Types> +inline T setName(T&& f, const std::string& name, Types... args) { +#if BOUT_USE_TRACK + f.name = fmt::format(name, args...); +#endif + return f; +} + #endif /* FIELD_H */ diff --git a/include/bout/field2d.hxx b/include/bout/field2d.hxx index 92658f1bbf..e9af55cb2c 100644 --- a/include/bout/field2d.hxx +++ b/include/bout/field2d.hxx @@ -24,6 +24,8 @@ * along with BOUT++. If not, see . * */ +#include +#include class Field2D; #pragma once @@ -66,7 +68,8 @@ public: */ Field2D(Mesh* localmesh = nullptr, CELL_LOC location_in = CELL_CENTRE, DirectionTypes directions_in = {YDirectionType::Standard, - ZDirectionType::Average}); + ZDirectionType::Average}, + std::optional region = {}); /*! * Copy constructor. After this both fields @@ -133,8 +136,12 @@ public: return *this; } - /// Check if this field has yup and ydown fields + /// Dummy functions to increase portability bool hasParallelSlices() const { return true; } + void calcParallelSlices() const {} + void splitParallelSlices() const {} + void clearParallelSlices() const {} + int numberParallelSlices() const { return 0; } Field2D& yup(std::vector::size_type UNUSED(index) = 0) { return *this; } const Field2D& yup(std::vector::size_type UNUSED(index) = 0) const { @@ -274,7 +281,10 @@ public: friend void swap(Field2D& first, Field2D& second) noexcept; - int size() const override { return nx * ny; }; + int size() const override { return nx * ny; } + + Field2D& asField3DParallel() { return *this; } + const Field2D& asField3DParallel() const { return *this; } private: /// Internal data array. Handles allocation/freeing of memory @@ -288,6 +298,10 @@ private: }; // Non-member overloaded operators +FieldPerp operator+(const Field2D& lhs, const FieldPerp& rhs); +FieldPerp operator-(const Field2D& lhs, const FieldPerp& rhs); +FieldPerp operator*(const Field2D& lhs, const FieldPerp& rhs); +FieldPerp operator/(const Field2D& lhs, const FieldPerp& rhs); Field2D operator+(const Field2D& lhs, const Field2D& rhs); Field2D operator-(const Field2D& lhs, const Field2D& rhs); diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index a75e38df36..8b12c0c0df 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -20,6 +20,10 @@ * **************************************************************************/ +#include "bout/field_data.hxx" +#include "bout/traits.hxx" +#include +#include class Field3D; #pragma once @@ -34,10 +38,13 @@ class Field3D; #include "bout/fieldperp.hxx" #include "bout/region.hxx" +#include #include +#include #include class Mesh; +class Field3DParallel; /// Class for 3D X-Y-Z scalar fields /*! @@ -166,7 +173,8 @@ public: */ Field3D(Mesh* localmesh = nullptr, CELL_LOC location_in = CELL_CENTRE, DirectionTypes directions_in = {YDirectionType::Standard, - ZDirectionType::Standard}); + ZDirectionType::Standard}, + std::optional regionID = {}); /*! * Copy constructor @@ -236,6 +244,8 @@ public: */ void splitParallelSlices(); + void splitParallelSlicesAndAllocate(); + /*! * Clear the parallel slices, yup and ydown */ @@ -257,6 +267,13 @@ public: #endif } + /// get number of parallel slices + size_t numberParallelSlices() const { + // Do checks + hasParallelSlices(); + return yup_fields.size(); + } + /// Check if this field has yup and ydown fields /// Return reference to yup field Field3D& yup(std::vector::size_type index = 0) { @@ -291,6 +308,17 @@ public: /// cuts on closed field lines? bool requiresTwistShift(bool twist_shift_enabled); + /// Enable a special tracking mode for debugging + /// Save all changes that, are done to the field, to tracking + Field3D& enableTracking(const std::string& name, Options& tracking); + + /// Disable tracking + Field3D& disableTracking() { + tracking = nullptr; + tracking_state = 0; + return *this; + } + ///////////////////////////////////////////////////////// // Data access @@ -312,11 +340,12 @@ public: const Region& getRegion(const std::string& region_name) const; /// Use region provided by the default, and if none is set, use the provided one const Region& getValidRegionWithDefault(const std::string& region_name) const; - void setRegion(const std::string& region_name); - void resetRegion() { regionID.reset(); }; - void setRegion(size_t id) { regionID = id; }; - void setRegion(std::optional id) { regionID = id; }; - std::optional getRegionID() const { return regionID; }; + void setRegion(const std::string& region_name) override; + void resetRegion() override; + void resetRegionParallel(); + void setRegion(size_t id) override; + void setRegion(std::optional id) override; + std::optional getRegionID() const override { return regionID; }; /// Return a Region reference to use to iterate over the x- and /// y-indices of this field @@ -454,6 +483,19 @@ public: Field3D& operator/=(BoutReal rhs); ///@} + Field3D& update_multiplication_inplace(const Field3D& rhs); + Field3D& update_division_inplace(const Field3D& rhs); + Field3D& update_addition_inplace(const Field3D& rhs); + Field3D& update_subtraction_inplace(const Field3D& rhs); + Field3D& update_multiplication_inplace(const Field2D& rhs); + Field3D& update_division_inplace(const Field2D& rhs); + Field3D& update_addition_inplace(const Field2D& rhs); + Field3D& update_subtraction_inplace(const Field2D& rhs); + Field3D& update_multiplication_inplace(BoutReal rhs); + Field3D& update_division_inplace(BoutReal rhs); + Field3D& update_addition_inplace(BoutReal rhs); + Field3D& update_subtraction_inplace(BoutReal rhs); + // FieldData virtual functions bool is3D() const override { return true; } @@ -467,6 +509,11 @@ public: friend class Vector2D; Field3D& calcParallelSlices(); + void allowParallelSlices([[maybe_unused]] bool allow) { +#if CHECK > 0 + allowCalcParallelSlices = allow; +#endif + } void applyBoundary(bool init = false) override; void applyBoundary(BoutReal t); @@ -479,8 +526,10 @@ public: /// This uses 2nd order central differences to set the value /// on the boundary to the value on the boundary in field \p f3d. /// Note: does not just copy values in boundary region. - void setBoundaryTo(const Field3D& f3d); + void setBoundaryTo(const Field3D& f3d) { setBoundaryTo(f3d, true); } + void setBoundaryTo(const Field3D& f3d, bool copyParallelSlices); + using FieldData::applyParallelBoundary; void applyParallelBoundary() override; void applyParallelBoundary(BoutReal t) override; void applyParallelBoundary(const std::string& condition) override; @@ -488,12 +537,20 @@ public: const std::string& condition) override; void applyParallelBoundary(const std::string& region, const std::string& condition, Field3D* f); + void applyParallelBoundaryWithDefault(const std::string& condition); friend void swap(Field3D& first, Field3D& second) noexcept; int size() const override { return nx * ny * nz; }; -private: + Options* getTracking() { return tracking; }; + + bool allowCalcParallelSlices{true}; + + inline Field3DParallel asField3DParallel(); + inline const Field3DParallel asField3DParallel() const; + +protected: /// Array sizes (from fieldmesh). These are valid only if fieldmesh is not null int nx{-1}, ny{-1}, nz{-1}; @@ -508,6 +565,20 @@ private: /// RegionID over which the field is valid std::optional regionID; + + int tracking_state{0}; + Options* tracking{nullptr}; + std::string selfname; + template + Options* track(const T& change, std::string operation) { + if (tracking != nullptr and tracking_state != 0) { + return doTrack(change, operation); + } + return nullptr; + } + template + Options* doTrack(const T& change, std::string operation); + Options* doTrack(const BoutReal& change, std::string operation); }; // Non-member overloaded operators @@ -538,6 +609,31 @@ Field3D operator-(BoutReal lhs, const Field3D& rhs); Field3D operator*(BoutReal lhs, const Field3D& rhs); Field3D operator/(BoutReal lhs, const Field3D& 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 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. @@ -650,4 +746,148 @@ bool operator==(const Field3D& a, const Field3D& b); /// Output a string describing a Field3D to a stream std::ostream& operator<<(std::ostream& out, const Field3D& value); +inline Field3D copy(const Field3D& f) { + Field3D result{f}; + result.allocate(); + for (size_t i = 0; i < result.numberParallelSlices(); ++i) { + result.yup(i).allocate(); + result.ydown(i).allocate(); + } + return result; +} + +/// Field3DParallel is intended to behave like Field3D, but preserve parallel +/// Fields. +/// Operations on Field3D, like multiplication, exp and floor only work on the +/// "main" field, Field3DParallel will retain the parallel slices. +class Field3DParallel : public Field3D { +public: + template + explicit Field3DParallel(Types... args) : Field3D(std::move(args)...) { + ensureFieldAligned(); + } + Field3DParallel(const Field3D& f) : Field3D(f) { ensureFieldAligned(); } + Field3DParallel(const Field3D& f, bool isRef) : Field3D(f), isRef(isRef) { + ensureFieldAligned(); + } + Field3DParallel(const Field2D& f) : Field3D(f) { ensureFieldAligned(); } + // Explicitly needed, as DirectionTypes is sometimes constructed from a + // brace enclosed list + explicit Field3DParallel(Mesh* localmesh = nullptr, CELL_LOC location_in = CELL_CENTRE, + DirectionTypes directions_in = {YDirectionType::Standard, + ZDirectionType::Standard}, + std::optional regionID = {}) + : Field3D(localmesh, location_in, directions_in, regionID) { + splitParallelSlices(); + ensureFieldAligned(); + } + explicit Field3DParallel(Array data, Mesh* localmesh, + CELL_LOC location = CELL_CENTRE, + DirectionTypes directions_in = {YDirectionType::Standard, + ZDirectionType::Standard}) + : Field3D(std::move(data), localmesh, location, directions_in) { + ensureFieldAligned(); + } + explicit Field3DParallel(BoutReal, Mesh* mesh = nullptr); + Field3D& asField3D() { return *this; } + const Field3D& asField3D() const { return *this; } + + Field3DParallel& operator*=(const Field3D&); + Field3DParallel& operator/=(const Field3D&); + Field3DParallel& operator+=(const Field3D&); + Field3DParallel& operator-=(const Field3D&); + Field3DParallel& operator*=(const Field3DParallel&); + Field3DParallel& operator/=(const Field3DParallel&); + Field3DParallel& operator+=(const Field3DParallel&); + Field3DParallel& operator-=(const Field3DParallel&); + Field3DParallel& operator*=(BoutReal); + Field3DParallel& operator/=(BoutReal); + Field3DParallel& operator+=(BoutReal); + Field3DParallel& operator-=(BoutReal); + Field3DParallel& operator=(const Field3D& rhs) { + Field3D::operator=(rhs); + ensureFieldAligned(); + return *this; + } + Field3DParallel& operator=(Field3D&& rhs) { + Field3D::operator=(std::move(rhs)); + ensureFieldAligned(); + return *this; + } + Field3DParallel& operator=(BoutReal); + Field3DParallel& allocate(); + +private: + void ensureFieldAligned(); + bool isRef{false}; +}; + +Field3DParallel Field3D::asField3DParallel() { + if (isAllocated()) { + allocate(); + for (size_t i = 0; i < numberParallelSlices(); ++i) { + if (yup(i).isAllocated()) { + yup(i).allocate(); + } + if (ydown(i).isAllocated()) { + ydown(i).allocate(); + } + } + } + return Field3DParallel(*this, true); +} +const Field3DParallel Field3D::asField3DParallel() const { + return Field3DParallel(*this); +} + +inline Field3D operator+(const Field2D& lhs, const Field3DParallel& rhs) { + return lhs + rhs.asField3D(); +} +inline Field3D operator-(const Field2D& lhs, const Field3DParallel& rhs) { + return lhs + rhs.asField3D(); +} +inline Field3D operator*(const Field2D& lhs, const Field3DParallel& rhs) { + return lhs + rhs.asField3D(); +} +inline Field3D operator/(const Field2D& lhs, const Field3DParallel& rhs) { + return lhs + rhs.asField3D(); +} + +inline Field3D operator+(const Field3DParallel& lhs, const Field2D& rhs) { + return lhs.asField3D() + rhs; +} +inline Field3D operator-(const Field3DParallel& lhs, const Field2D& rhs) { + return lhs.asField3D() - rhs; +} +inline Field3D operator*(const Field3DParallel& lhs, const Field2D& rhs) { + return lhs.asField3D() * rhs; +} +inline Field3D operator/(const Field3DParallel& lhs, const Field2D& rhs) { + return lhs.asField3D() / rhs; +} + +inline Field3DParallel +filledFrom(const Field3DParallel& f, + const std::function& func) { + auto result{emptyFrom(f)}; + if (f.isFci()) { + BOUT_FOR(i, result.getRegion("RGN_NOY")) { result[i] = func(0, i); } + + for (size_t i = 0; i < result.numberParallelSlices(); ++i) { + result.yup(i).allocate(); + BOUT_FOR(d, result.yup(i).getValidRegionWithDefault("RGN_INVALID")) { + result.yup(i)[d] = func(i + 1, d); + } + result.ydown(i).allocate(); + BOUT_FOR(d, result.ydown(i).getValidRegionWithDefault("RGN_INVALID")) { + result.ydown(i)[d] = func(-i - 1, d); + } + } + } else { + BOUT_FOR(i, result.getRegion("RGN_ALL")) { result[i] = func(0, i); } + } + + return result; +} + #endif /* BOUT_FIELD3D_H */ diff --git a/include/bout/field_data.hxx b/include/bout/field_data.hxx index 185dcabf2d..a32fd792a5 100644 --- a/include/bout/field_data.hxx +++ b/include/bout/field_data.hxx @@ -43,7 +43,7 @@ class BoundaryOpPar; class Coordinates; class Mesh; -#include "bout/boundary_region.hxx" +class BoundaryRegion; class BoundaryRegionPar; enum class BndryLoc; diff --git a/include/bout/fieldperp.hxx b/include/bout/fieldperp.hxx index 6995308dbe..8ee3559d8d 100644 --- a/include/bout/fieldperp.hxx +++ b/include/bout/fieldperp.hxx @@ -23,6 +23,9 @@ * **************************************************************************/ +#include +#include +#include class FieldPerp; #ifndef BOUT_FIELDPERP_H @@ -58,7 +61,8 @@ public: FieldPerp(Mesh* fieldmesh = nullptr, CELL_LOC location_in = CELL_CENTRE, int yindex_in = -1, DirectionTypes directions_in = {YDirectionType::Standard, - ZDirectionType::Standard}); + ZDirectionType::Standard}, + std::optional regionID = {}); /*! * Copy constructor. After this the data @@ -157,6 +161,25 @@ public: return *this; } + /// Dummy functions to increase portability + bool hasParallelSlices() const { return true; } + void calcParallelSlices() const {} + void clearParallelSlices() {} + int numberParallelSlices() { return 0; } + + FieldPerp& yup(std::vector::size_type UNUSED(index) = 0) { return *this; } + const FieldPerp& yup(std::vector::size_type UNUSED(index) = 0) const { + return *this; + } + + FieldPerp& ydown(std::vector::size_type UNUSED(index) = 0) { return *this; } + const FieldPerp& ydown(std::vector::size_type UNUSED(index) = 0) const { + return *this; + } + + FieldPerp& ynext(int UNUSED(dir)) { return *this; } + const FieldPerp& ynext(int UNUSED(dir)) const { return *this; } + /*! * Ensure that data array is allocated and unique */ diff --git a/include/bout/fv_ops.hxx b/include/bout/fv_ops.hxx index 0ec1fbe3ad..e475fa3916 100644 --- a/include/bout/fv_ops.hxx +++ b/include/bout/fv_ops.hxx @@ -5,29 +5,33 @@ #ifndef BOUT_FV_OPS_H #define BOUT_FV_OPS_H +#include "bout/bout_types.hxx" #include "bout/build_defines.hxx" #include "bout/field3d.hxx" #include "bout/globals.hxx" #include "bout/utils.hxx" #include "bout/vector2d.hxx" +#include #include +#if CHECK > 0 +#include +#endif namespace FV { /*! * Div ( a Grad_perp(f) ) -- ∇⊥ ( a ⋅ ∇⊥ f) -- Vorticity */ -Field3D Div_a_Grad_perp(const Field3D& a, const Field3D& x); +Field3D Div_a_Grad_perp(const Field3D& a, const Field3D& f); [[deprecated("Please use Div_a_Grad_perp instead")]] inline Field3D -Div_a_Laplace_perp(const Field3D& a, const Field3D& x) { - return Div_a_Grad_perp(a, x); +Div_a_Laplace_perp(const Field3D& a, const Field3D& f) { + return Div_a_Grad_perp(a, f); } /*! * Divergence of a parallel diffusion Div( k * Grad_par(f) ) */ -const Field3D Div_par_K_Grad_par(const Field3D& k, const Field3D& f, - bool bndry_flux = true); +Field3D Div_par_K_Grad_par(const Field3D& k, const Field3D& f, bool bndry_flux = true); /*! * 4th-order derivative in Y, using derivatives @@ -49,7 +53,7 @@ const Field3D Div_par_K_Grad_par(const Field3D& k, const Field3D& f, * * No fluxes through domain boundaries */ -const Field3D D4DY4(const Field3D& d, const Field3D& f); +Field3D D4DY4(const Field3D& d, const Field3D& f); /*! * 4th-order dissipation term @@ -67,18 +71,24 @@ const Field3D D4DY4(const Field3D& d, const Field3D& f); * f_2 | f_1 | f_0 | * f_b */ -const Field3D D4DY4_Index(const Field3D& f, bool bndry_flux = true); +Field3D D4DY4_Index(const Field3D& f, bool bndry_flux = true); /*! * Stencil used for Finite Volume calculations * which includes cell face values L and R */ struct Stencil1D { - // Cell centre values - BoutReal c, m, p, mm, pp; - - // Left and right cell face values - BoutReal L, R; + /// Cell centre values + BoutReal c; + BoutReal m; + BoutReal p; + BoutReal mm = BoutNaN; + BoutReal pp = BoutNaN; + + /// Left cell face value + BoutReal L = BoutNaN; + /// Right cell face value + BoutReal R = BoutNaN; }; /*! @@ -93,8 +103,8 @@ struct Upwind { */ struct Fromm { void operator()(Stencil1D& n) { - n.L = n.c - 0.25 * (n.p - n.m); - n.R = n.c + 0.25 * (n.p - n.m); + n.L = n.c - (0.25 * (n.p - n.m)); + n.R = n.c + (0.25 * (n.p - n.m)); } }; @@ -110,9 +120,9 @@ struct MinMod { void operator()(Stencil1D& n) { // Choose the gradient within the cell // as the minimum (smoothest) solution - BoutReal slope = _minmod(n.p - n.c, n.c - n.m); - n.L = n.c - 0.5 * slope; - n.R = n.c + 0.5 * slope; + const BoutReal slope = _minmod(n.p - n.c, n.c - n.m); + n.L = n.c - (0.5 * slope); + n.R = n.c + (0.5 * slope); } private: @@ -123,7 +133,7 @@ private: * returns zero, otherwise chooses the value * with the minimum magnitude. */ - BoutReal _minmod(BoutReal a, BoutReal b) { + static BoutReal _minmod(BoutReal a, BoutReal b) { if (a * b <= 0.0) { return 0.0; } @@ -145,17 +155,17 @@ private: */ struct MC { void operator()(Stencil1D& n) { - BoutReal slope = minmod(2. * (n.p - n.c), // 2*right difference - 0.5 * (n.p - n.m), // Central difference - 2. * (n.c - n.m)); // 2*left difference - n.L = n.c - 0.5 * slope; - n.R = n.c + 0.5 * slope; + const BoutReal slope = minmod(2. * (n.p - n.c), // 2*right difference + 0.5 * (n.p - n.m), // Central difference + 2. * (n.c - n.m)); // 2*left difference + n.L = n.c - (0.5 * slope); + n.R = n.c + (0.5 * slope); } private: // Return zero if any signs are different // otherwise return the value with the minimum magnitude - BoutReal minmod(BoutReal a, BoutReal b, BoutReal c) { + static BoutReal minmod(BoutReal a, BoutReal b, BoutReal c) { // if any of the signs are different, return zero gradient if ((a * b <= 0.0) || (a * c <= 0.0)) { return 0.0; @@ -166,6 +176,52 @@ private: } }; +/// Superbee limiter +/// +/// This corresponds to the limiter function +/// φ(r) = max(0, min(2r, 1), min(r,2) +/// +/// The value at cell right (i.e. i + 1/2) is: +/// +/// n.R = n.c - φ(r) (n.c - (n.p + n.c)/2) +/// = n.c + φ(r) (n.p - n.c)/2 +/// +/// Four regimes: +/// a) r < 1/2 -> φ(r) = 2r +/// n.R = n.c + gL +/// b) 1/2 < r < 1 -> φ(r) = 1 +/// n.R = n.c + gR/2 +/// c) 1 < r < 2 -> φ(r) = r +/// n.R = n.c + gL/2 +/// d) 2 < r -> φ(r) = 2 +/// n.R = n.c + gR +/// +/// where the left and right gradients are: +/// gL = n.c - n.m +/// gR = n.p - n.c +/// +struct Superbee { + void operator()(Stencil1D& n) { + const BoutReal gL = n.c - n.m; + const BoutReal gR = n.p - n.c; + + // r = gL / gR + // Limiter is φ(r) + if (gL * gR < 0) { + // Different signs => Zero gradient + n.L = n.R = n.c; + } else { + const BoutReal sign = SIGN(gL); + const BoutReal abs_gL = fabs(gL); + const BoutReal abs_gR = fabs(gR); + const BoutReal half_slope = + sign * BOUTMAX(BOUTMIN(abs_gL, 0.5 * abs_gR), BOUTMIN(abs_gR, 0.5 * abs_gL)); + n.L = n.c - half_slope; + n.R = n.c + half_slope; + } + } +}; + /*! * Communicate fluxes between processors * Takes values in guard cells, and adds them to cells @@ -189,13 +245,17 @@ void communicateFluxes(Field3D& f); /// /// NB: Uses to/from FieldAligned coordinates template -const Field3D Div_par(const Field3D& f_in, const Field3D& v_in, - const Field3D& wave_speed_in, bool fixflux = true) { +Field3D Div_par(const Field3D& f_in, const Field3D& v_in, const Field3D& wave_speed_in, + bool fixflux = true) { + + if (f_in.isFci()) { + return ::Div_par(f_in, v_in); + } ASSERT1_FIELDS_COMPATIBLE(f_in, v_in); ASSERT1_FIELDS_COMPATIBLE(f_in, wave_speed_in); - Mesh* mesh = f_in.getMesh(); + Mesh const* mesh = f_in.getMesh(); CellEdges cellboundary; @@ -215,29 +275,17 @@ const Field3D Div_par(const Field3D& f_in, const Field3D& v_in, Field3D result{zeroFrom(f)}; - // Only need one guard cell, so no need to communicate fluxes - // Instead calculate in guard cells to preserve fluxes - int ys = mesh->ystart - 1; - int ye = mesh->yend + 1; - for (int i = mesh->xstart; i <= mesh->xend; i++) { + const bool is_periodic_y = mesh->periodicY(i); + const bool is_first_y = mesh->firstY(i); + const bool is_last_y = mesh->lastY(i); - if (!mesh->firstY(i) || mesh->periodicY(i)) { - // Calculate in guard cell to get fluxes consistent between processors - ys = mesh->ystart - 1; - } else { - // Don't include the boundary cell. Note that this implies special - // handling of boundaries later - ys = mesh->ystart; - } - - if (!mesh->lastY(i) || mesh->periodicY(i)) { - // Calculate in guard cells - ye = mesh->yend + 1; - } else { - // Not in boundary cells - ye = mesh->yend; - } + // Only need one guard cell, so no need to communicate fluxes Instead + // calculate in guard cells to get fluxes consistent between processors, but + // don't include the boundary cell. Note that this implies special handling + // of boundaries later + const int ys = (!is_first_y || is_periodic_y) ? mesh->ystart - 1 : mesh->ystart; + const int ye = (!is_last_y || is_periodic_y) ? mesh->yend + 1 : mesh->yend; for (int j = ys; j <= ye; j++) { // Pre-calculate factors which multiply fluxes @@ -246,16 +294,16 @@ const Field3D Div_par(const Field3D& f_in, const Field3D& v_in, BoutReal common_factor = (coord->J(i, j) + coord->J(i, j + 1)) / (sqrt(coord->g_22(i, j)) + sqrt(coord->g_22(i, j + 1))); - BoutReal flux_factor_rc = common_factor / (coord->dy(i, j) * coord->J(i, j)); - BoutReal flux_factor_rp = + const BoutReal flux_factor_rc = common_factor / (coord->dy(i, j) * coord->J(i, j)); + const BoutReal flux_factor_rp = common_factor / (coord->dy(i, j + 1) * coord->J(i, j + 1)); // For left cell boundaries common_factor = (coord->J(i, j) + coord->J(i, j - 1)) / (sqrt(coord->g_22(i, j)) + sqrt(coord->g_22(i, j - 1))); - BoutReal flux_factor_lc = common_factor / (coord->dy(i, j) * coord->J(i, j)); - BoutReal flux_factor_lm = + const BoutReal flux_factor_lc = common_factor / (coord->dy(i, j) * coord->J(i, j)); + const BoutReal flux_factor_lm = common_factor / (coord->dy(i, j - 1) * coord->J(i, j - 1)); #endif for (int k = mesh->zstart; k <= mesh->zend; k++) { @@ -298,23 +346,23 @@ const Field3D Div_par(const Field3D& f_in, const Field3D& v_in, // Calculate velocity at right boundary (y+1/2) BoutReal vpar = 0.5 * (v(i, j, k) + v(i, j + 1, k)); - BoutReal flux; + BoutReal flux = NAN; - if (mesh->lastY(i) && (j == mesh->yend) && !mesh->periodicY(i)) { + if (is_last_y && (j == mesh->yend) && !is_periodic_y) { // Last point in domain - BoutReal bndryval = 0.5 * (s.c + s.p); + const BoutReal bndryval = 0.5 * (s.c + s.p); if (fixflux) { // Use mid-point to be consistent with boundary conditions flux = bndryval * vpar; } else { // Add flux due to difference in boundary values - flux = s.R * vpar + wave_speed(i, j, k) * (s.R - bndryval); + flux = (s.R * vpar) + (wave_speed(i, j, k) * (s.R - bndryval)); } } else { // Maximum wave speed in the two cells - BoutReal amax = BOUTMAX(wave_speed(i, j, k), wave_speed(i, j + 1, k)); + const BoutReal amax = BOUTMAX(wave_speed(i, j, k), wave_speed(i, j + 1, k)); if (vpar > amax) { // Supersonic flow out of this cell @@ -336,20 +384,20 @@ const Field3D Div_par(const Field3D& f_in, const Field3D& v_in, vpar = 0.5 * (v(i, j, k) + v(i, j - 1, k)); - if (mesh->firstY(i) && (j == mesh->ystart) && !mesh->periodicY(i)) { + if (is_first_y && (j == mesh->ystart) && !is_periodic_y) { // First point in domain - BoutReal bndryval = 0.5 * (s.c + s.m); + const BoutReal bndryval = 0.5 * (s.c + s.m); if (fixflux) { // Use mid-point to be consistent with boundary conditions flux = bndryval * vpar; } else { // Add flux due to difference in boundary values - flux = s.L * vpar - wave_speed(i, j, k) * (s.L - bndryval); + flux = (s.L * vpar) - (wave_speed(i, j, k) * (s.L - bndryval)); } } else { // Maximum wave speed in the two cells - BoutReal amax = BOUTMAX(wave_speed(i, j, k), wave_speed(i, j - 1, k)); + const BoutReal amax = BOUTMAX(wave_speed(i, j, k), wave_speed(i, j - 1, k)); if (vpar < -amax) { // Supersonic out of this cell @@ -383,11 +431,11 @@ const Field3D Div_par(const Field3D& f_in, const Field3D& v_in, * */ template -const Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_flux) { +Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_flux) { ASSERT1(n_in.getLocation() == v.getLocation()); ASSERT1_FIELDS_COMPATIBLE(n_in, v.x); - Mesh* mesh = n_in.getMesh(); + const Mesh* mesh = n_in.getMesh(); CellEdges cellboundary; @@ -406,10 +454,10 @@ const Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_flux) { BOUT_FOR(i, result.getRegion("RGN_NOBNDRY")) { // Calculate velocities - BoutReal vU = 0.25 * (vz[i.zp()] + vz[i]) * (coord->J[i.zp()] + coord->J[i]); - BoutReal vD = 0.25 * (vz[i.zm()] + vz[i]) * (coord->J[i.zm()] + coord->J[i]); - BoutReal vL = 0.25 * (vx[i.xm()] + vx[i]) * (coord->J[i.xm()] + coord->J[i]); - 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; @@ -424,7 +472,7 @@ const Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_flux) { if ((i.x() == mesh->xend) && (mesh->lastX())) { // At right boundary in X if (bndry_flux) { - BoutReal flux; + BoutReal flux = NAN; if (vR > 0.0) { // Flux to boundary flux = vR * s.R; @@ -439,7 +487,7 @@ const Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_flux) { // Not at a boundary if (vR > 0.0) { // Flux out into next cell - BoutReal flux = vR * s.R; + 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()]); } @@ -451,7 +499,7 @@ const Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_flux) { // At left boundary in X if (bndry_flux) { - BoutReal flux; + BoutReal flux = NAN; if (vL < 0.0) { // Flux to boundary flux = vL * s.L; @@ -465,7 +513,7 @@ const Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_flux) { } else { // Not at a boundary if (vL < 0.0) { - BoutReal flux = vL * s.L; + 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()]); } @@ -482,12 +530,12 @@ const Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_flux) { cellboundary(s); if (vU > 0.0) { - BoutReal flux = vU * s.R; + 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()]); } if (vD < 0.0) { - BoutReal flux = vD * s.L; + 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()]); } @@ -507,13 +555,13 @@ const 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 - BoutReal vU = 0.25 * (vy[i] + vy[i.yp()]) * (coord->J[i] + coord->J[i.yp()]); - 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 - BoutReal nU = 0.5 * (n[i] + n[i.yp()]); - BoutReal nD = 0.5 * (n[i] + n[i.ym()]); + 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]); } @@ -524,5 +572,451 @@ const Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_flux) { * X-Z Finite Volume diffusion operator */ Field3D Div_Perp_Lap(const Field3D& a, const Field3D& f, CELL_LOC outloc = CELL_DEFAULT); + +/// Finite volume parallel divergence +/// +/// NOTE: Modified version, applies limiter to velocity and field +/// Performs better (smaller overshoots) than Div_par +/// +/// Preserves the sum of f*J*dx*dy*dz over the domain +/// +/// @param[in] f_in The field being advected. +/// This will be reconstructed at cell faces +/// using the given CellEdges method +/// @param[in] v_in The advection velocity. +/// This will be interpolated to cell boundaries +/// using linear interpolation +/// @param[in] wave_speed_in Local maximum speed of all waves in the system at each +// point in space +/// @param[in] fixflux Fix the flux at the boundary to be the value at the +/// midpoint (for boundary conditions) +/// +/// @param[out] flow_ylow Flow at the lower Y cell boundary +/// Already includes area factor * flux +template +Field3D Div_par_mod(const Field3D& f_in, const Field3D& v_in, + const Field3D& wave_speed_in, Field3D& flow_ylow, + bool fixflux = true) { + + Coordinates* coord = f_in.getCoordinates(); + ASSERT1_FIELDS_COMPATIBLE(f_in, v_in); + + if (f_in.isFci()) { + // Use mid-point (cell boundary) averages + + ASSERT1(f_in.hasParallelSlices()); + ASSERT1(v_in.hasParallelSlices()); + + const auto& f_up = f_in.yup(); + const auto& f_down = f_in.ydown(); + + const auto& v_up = v_in.yup(); + const auto& v_down = v_in.ydown(); + + Field3D result{emptyFrom(f_in)}; + BOUT_FOR(i, f_in.getRegion("RGN_NOBNDRY")) { + const auto iyp = i.yp(); + const auto iym = i.ym(); + + result[i] = + (0.25 * (f_in[i] + f_up[iyp]) * (v_in[i] + v_up[iyp]) * coord->Jxz_yhigh()[i] + - 0.25 * (f_in[i] + f_down[iym]) * (v_in[i] + v_down[iym]) + * coord->Jxz_ylow()[i]) + / (coord->dy[i] * coord->J[i]); + } + return result; + } + ASSERT1_FIELDS_COMPATIBLE(f_in, wave_speed_in); + + const Mesh* mesh = f_in.getMesh(); + + CellEdges cellboundary; + + ASSERT2(f_in.getDirectionY() == v_in.getDirectionY()); + ASSERT2(f_in.getDirectionY() == wave_speed_in.getDirectionY()); + const bool are_unaligned = + ((f_in.getDirectionY() == YDirectionType::Standard) + and (v_in.getDirectionY() == YDirectionType::Standard) + and (wave_speed_in.getDirectionY() == YDirectionType::Standard)); + + const Field3D f = are_unaligned ? toFieldAligned(f_in, "RGN_NOX") : f_in; + const Field3D v = are_unaligned ? toFieldAligned(v_in, "RGN_NOX") : v_in; + const Field3D wave_speed = + are_unaligned ? toFieldAligned(wave_speed_in, "RGN_NOX") : wave_speed_in; + + Field3D result{zeroFrom(f)}; + flow_ylow = zeroFrom(f); + + for (int i = mesh->xstart; i <= mesh->xend; i++) { + const bool is_periodic_y = mesh->periodicY(i); + const bool is_first_y = mesh->firstY(i); + const bool is_last_y = mesh->lastY(i); + + // Only need one guard cell, so no need to communicate fluxes Instead + // calculate in guard cells to get fluxes consistent between processors, but + // don't include the boundary cell. Note that this implies special handling + // of boundaries later + const int ys = (!is_first_y || is_periodic_y) ? mesh->ystart - 1 : mesh->ystart; + const int ye = (!is_last_y || is_periodic_y) ? mesh->yend + 1 : mesh->yend; + + for (int j = ys; j <= ye; j++) { + // Pre-calculate factors which multiply fluxes +#if not(BOUT_USE_METRIC_3D) + // For right cell boundaries + const BoutReal common_factor_r = + (coord->J(i, j) + coord->J(i, j + 1)) + / (sqrt(coord->g_22(i, j)) + sqrt(coord->g_22(i, j + 1))); + + const BoutReal flux_factor_rc = + common_factor_r / (coord->dy(i, j) * coord->J(i, j)); + const BoutReal flux_factor_rp = + common_factor_r / (coord->dy(i, j + 1) * coord->J(i, j + 1)); + + const BoutReal area_rp = + common_factor_r * coord->dx(i, j + 1) * coord->dz(i, j + 1); + + // For left cell boundaries + const BoutReal common_factor_l = + (coord->J(i, j) + coord->J(i, j - 1)) + / (sqrt(coord->g_22(i, j)) + sqrt(coord->g_22(i, j - 1))); + + const BoutReal flux_factor_lc = + common_factor_l / (coord->dy(i, j) * coord->J(i, j)); + const BoutReal flux_factor_lm = + common_factor_l / (coord->dy(i, j - 1) * coord->J(i, j - 1)); + + const BoutReal area_lc = common_factor_l * coord->dx(i, j) * coord->dz(i, j); +#endif + for (int k = 0; k < mesh->LocalNz; k++) { +#if BOUT_USE_METRIC_3D + // For right cell boundaries + const BoutReal common_factor_r = + (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))); + + const BoutReal flux_factor_rc = + common_factor_r / (coord->dy(i, j, k) * coord->J(i, j, k)); + const BoutReal flux_factor_rp = + common_factor_r / (coord->dy(i, j + 1, k) * coord->J(i, j + 1, k)); + + const BoutReal area_rp = + common_factor_r * coord->dx(i, j + 1, k) * coord->dz(i, j + 1, k); + + // For left cell boundaries + const BoutReal common_factor_l = + (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))); + + const BoutReal flux_factor_lc = + common_factor_l / (coord->dy(i, j, k) * coord->J(i, j, k)); + const BoutReal flux_factor_lm = + common_factor_l / (coord->dy(i, j - 1, k) * coord->J(i, j - 1, k)); + + const BoutReal area_lc = + common_factor_l * coord->dx(i, j, k) * coord->dz(i, j, k); +#endif + + //////////////////////////////////////////// + // Reconstruct f at the cell faces + // This calculates s.R and s.L for the Right and Left + // face values on this cell + + // Reconstruct f at the cell faces + // TODO(peter): We can remove this #ifdef guard after switching to C++20 +#if __cpp_designated_initializers >= 201707L + Stencil1D s{.c = f(i, j, k), .m = f(i, j - 1, k), .p = f(i, j + 1, k)}; +#else + Stencil1D s{f(i, j, k), f(i, j - 1, k), f(i, j + 1, k), BoutNaN, + BoutNaN, BoutNaN, BoutNaN}; +#endif + cellboundary(s); // Calculate s.R and s.L + + //////////////////////////////////////////// + // Reconstruct v at the cell faces + // TODO(peter): We can remove this #ifdef guard after switching to C++20 +#if __cpp_designated_initializers >= 201707L + Stencil1D sv{.c = v(i, j, k), .m = v(i, j - 1, k), .p = v(i, j + 1, k)}; +#else + Stencil1D sv{v(i, j, k), v(i, j - 1, k), v(i, j + 1, k), BoutNaN, + BoutNaN, BoutNaN, BoutNaN}; +#endif + cellboundary(sv); // Calculate sv.R and sv.L + + //////////////////////////////////////////// + // Right boundary + + BoutReal flux = BoutNaN; + + if (is_last_y && (j == mesh->yend) && !is_periodic_y) { + // Last point in domain + + // Calculate velocity at right boundary (y+1/2) + const BoutReal vpar = 0.5 * (v(i, j, k) + v(i, j + 1, k)); + + const BoutReal bndryval = 0.5 * (s.c + s.p); + if (fixflux) { + // Use mid-point to be consistent with boundary conditions + flux = bndryval * vpar; + } else { + // Add flux due to difference in boundary values + flux = (s.R * vpar) + (wave_speed(i, j, k) * (s.R - bndryval)); + } + + } else { + // Maximum wave speed in the two cells + const BoutReal amax = BOUTMAX(wave_speed(i, j, k), wave_speed(i, j + 1, k), + fabs(v(i, j, k)), fabs(v(i, j + 1, k))); + + flux = s.R * 0.5 * (sv.R + amax); + } + + result(i, j, k) += flux * flux_factor_rc; + result(i, j + 1, k) -= flux * flux_factor_rp; + + flow_ylow(i, j + 1, k) += flux * area_rp; + + //////////////////////////////////////////// + // Calculate at left boundary + + if (is_first_y && (j == mesh->ystart) && !is_periodic_y) { + // First point in domain + const BoutReal bndryval = 0.5 * (s.c + s.m); + const BoutReal vpar = 0.5 * (v(i, j, k) + v(i, j - 1, k)); + if (fixflux) { + // Use mid-point to be consistent with boundary conditions + flux = bndryval * vpar; + } else { + // Add flux due to difference in boundary values + flux = (s.L * vpar) - (wave_speed(i, j, k) * (s.L - bndryval)); + } + } else { + + // Maximum wave speed in the two cells + const BoutReal amax = BOUTMAX(wave_speed(i, j, k), wave_speed(i, j - 1, k), + fabs(v(i, j, k)), fabs(v(i, j - 1, k))); + + flux = s.L * 0.5 * (sv.L - amax); + } + + result(i, j, k) -= flux * flux_factor_lc; + result(i, j - 1, k) += flux * flux_factor_lm; + + flow_ylow(i, j, k) += flux * area_lc; + } + } + } + if (are_unaligned) { + flow_ylow = fromFieldAligned(flow_ylow, "RGN_NOBNDRY"); + } + return are_unaligned ? fromFieldAligned(result, "RGN_NOBNDRY") : result; +} + +/// This operator calculates Div_par(f v v) +/// It is used primarily (only?) in the parallel momentum equation. +/// +/// This operator is used rather than Div(f fv) so that the values of +/// f and v are consistent with other advection equations: The product +/// fv is not interpolated to cell boundaries. +template +Field3D Div_par_fvv(const Field3D& f_in, const Field3D& v_in, + const Field3D& wave_speed_in, bool fixflux = true) { + ASSERT1_FIELDS_COMPATIBLE(f_in, v_in); + const Mesh* mesh = f_in.getMesh(); + const Coordinates* coord = f_in.getCoordinates(); + CellEdges cellboundary; + + if (f_in.isFci()) { + // FCI version, using yup/down fields + 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& f_up = f_in.yup(); + const auto& f_down = f_in.ydown(); + + 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; + + Field3D result{emptyFrom(f_in)}; + BOUT_FOR(i, f_in.getRegion("RGN_NOBNDRY")) { + const auto iyp = i.yp(); + const auto iym = i.ym(); + + // Maximum local wave speed + const BoutReal amax = + BOUTMAX(wave_speed_in[i], fabs(v_in[i]), fabs(v_up[iyp]), fabs(v_down[iym])); + + const BoutReal term = (f_up[iyp] * v_up[iyp] * v_up[iyp] / B_up[iyp]) + - (f_down[iym] * v_down[iym] * v_down[iym] / B_down[iym]); + + // Penalty terms. This implementation is very dissipative. + const BoutReal penalty = + (amax * (f_in[i] + f_up[iyp]) * (v_in[i] - v_up[iyp]) / (B[i] + B_up[iyp])) + + (amax * (f_in[i] + f_down[iym]) * (v_in[i] - v_down[iym]) + / (B[i] + B_down[iym])); + + // if (fabs(penalty) > fabs(term) and penalty * v_in[i] > 0) { + // if (term * penalty > 0) { + // penalty = term; + // } else { + // penalty = -term; + // } + // } + + result[i] = B[i] * (term + penalty) / (2 * dy[i] * sqrt(g_22[i])); + +#if CHECK > 0 + if (!std::isfinite(result[i])) { + throw BoutException("Non-finite value in Div_par_fvv at {}\n" + "fup {} vup {} fdown {} vdown {} amax {}\n" + "B {} Bup {} Bdown {} dy {} sqrt(g_22} {}", + i, f_up[i], v_up[i], f_down[i], v_down[i], amax, B[i], + B_up[i], B_down[i], dy[i], sqrt(g_22[i])); + } +#endif + } + return result; + } + + ASSERT1(areFieldsCompatible(f_in, wave_speed_in)); + + /// Ensure that f, v and wave_speed are field aligned + Field3D f = toFieldAligned(f_in, "RGN_NOX"); + Field3D v = toFieldAligned(v_in, "RGN_NOX"); + Field3D wave_speed = toFieldAligned(wave_speed_in, "RGN_NOX"); + + Field3D result{zeroFrom(f)}; + + for (int i = mesh->xstart; i <= mesh->xend; i++) { + const bool is_periodic_y = mesh->periodicY(i); + const bool is_first_y = mesh->firstY(i); + const bool is_last_y = mesh->lastY(i); + + // Only need one guard cell, so no need to communicate fluxes Instead + // calculate in guard cells to get fluxes consistent between processors, but + // don't include the boundary cell. Note that this implies special handling + // of boundaries later + const int ys = (!is_first_y || is_periodic_y) ? mesh->ystart - 1 : mesh->ystart; + const int ye = (!is_last_y || is_periodic_y) ? mesh->yend + 1 : mesh->yend; + + for (int j = ys; j <= ye; j++) { + // Pre-calculate factors which multiply fluxes + + for (int k = 0; k < mesh->LocalNz; k++) { + // For right cell boundaries + const BoutReal common_factor_r = + (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))); + + const BoutReal flux_factor_rc = + common_factor_r / (coord->dy(i, j, k) * coord->J(i, j, k)); + const BoutReal flux_factor_rp = + common_factor_r / (coord->dy(i, j + 1, k) * coord->J(i, j + 1, k)); + + // For left cell boundaries + const BoutReal common_factor_l = + (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))); + + const BoutReal flux_factor_lc = + common_factor_l / (coord->dy(i, j, k) * coord->J(i, j, k)); + const BoutReal flux_factor_lm = + common_factor_l / (coord->dy(i, j - 1, k) * coord->J(i, j - 1, k)); + + //////////////////////////////////////////// + // Reconstruct f at the cell faces + // This calculates s.R and s.L for the Right and Left + // face values on this cell + + // Reconstruct f at the cell faces +#if __cpp_designated_initializers >= 201707L + Stencil1D s{.c = f(i, j, k), .m = f(i, j - 1, k), .p = f(i, j + 1, k)}; +#else + Stencil1D s{f(i, j, k), f(i, j - 1, k), f(i, j + 1, k), BoutNaN, + BoutNaN, BoutNaN, BoutNaN}; +#endif + cellboundary(s); // Calculate s.R and s.L + + //////////////////////////////////////////// + // Reconstruct v at the cell faces + // TODO(peter): We can remove this #ifdef guard after switching to C++20 +#if __cpp_designated_initializers >= 201707L + Stencil1D sv{.c = v(i, j, k), .m = v(i, j - 1, k), .p = v(i, j + 1, k)}; +#else + Stencil1D sv{v(i, j, k), v(i, j - 1, k), v(i, j + 1, k), BoutNaN, + BoutNaN, BoutNaN, BoutNaN}; +#endif + cellboundary(sv); + + //////////////////////////////////////////// + // Right boundary + + // Calculate velocity at right boundary (y+1/2) + const BoutReal v_mid_r = 0.5 * (sv.c + sv.p); + // And mid-point density at right boundary + const BoutReal n_mid_r = 0.5 * (s.c + s.p); + BoutReal flux = NAN; + + if (mesh->lastY(i) && (j == mesh->yend) && !mesh->periodicY(i)) { + // Last point in domain + + if (fixflux) { + // Use mid-point to be consistent with boundary conditions + flux = n_mid_r * v_mid_r * v_mid_r; + } else { + // Add flux due to difference in boundary values + flux = (s.R * sv.R * sv.R) // Use right cell edge values + + (BOUTMAX(wave_speed(i, j, k), fabs(sv.c), fabs(sv.p)) * n_mid_r + * (sv.R - v_mid_r)); // Damp differences in velocity, not flux + } + } else { + // Maximum wave speed in the two cells + const BoutReal amax = BOUTMAX(wave_speed(i, j, k), wave_speed(i, j + 1, k), + fabs(sv.c), fabs(sv.p)); + + flux = s.R * 0.5 * (sv.R + amax) * sv.R; + } + + result(i, j, k) += flux * flux_factor_rc; + result(i, j + 1, k) -= flux * flux_factor_rp; + + //////////////////////////////////////////// + // Calculate at left boundary + + const BoutReal v_mid_l = 0.5 * (sv.c + sv.m); + const BoutReal n_mid_l = 0.5 * (s.c + s.m); + + if (mesh->firstY(i) && (j == mesh->ystart) && !mesh->periodicY(i)) { + // First point in domain + if (fixflux) { + // Use mid-point to be consistent with boundary conditions + flux = n_mid_l * v_mid_l * v_mid_l; + } else { + // Add flux due to difference in boundary values + flux = (s.L * sv.L * sv.L) + - (BOUTMAX(wave_speed(i, j, k), fabs(sv.c), fabs(sv.m)) * n_mid_l + * (sv.L - v_mid_l)); + } + } else { + // Maximum wave speed in the two cells + const BoutReal amax = BOUTMAX(wave_speed(i, j, k), wave_speed(i, j - 1, k), + fabs(sv.c), fabs(sv.m)); + + flux = s.L * 0.5 * (sv.L - amax) * sv.L; + } + + result(i, j, k) -= flux * flux_factor_lc; + result(i, j - 1, k) += flux * flux_factor_lm; + } + } + } + return fromFieldAligned(result, "RGN_NOBNDRY"); +} } // namespace FV #endif // BOUT_FV_OPS_H diff --git a/include/bout/index_derivs.hxx b/include/bout/index_derivs.hxx index ccce9a7f5e..a8d1bbb1d3 100644 --- a/include/bout/index_derivs.hxx +++ b/include/bout/index_derivs.hxx @@ -148,7 +148,7 @@ struct registerMethod { // removed and we can use nGuard directly in the template statement. const int nGuards = method.meta.nGuards; - auto& derivativeRegister = DerivativeStore::getInstance(); + auto& derivativeRegister = getStore(); switch (method.meta.derivType) { case (DERIV::Standard): diff --git a/include/bout/index_derivs_interface.hxx b/include/bout/index_derivs_interface.hxx index 242492c3f8..70731f9055 100644 --- a/include/bout/index_derivs_interface.hxx +++ b/include/bout/index_derivs_interface.hxx @@ -29,6 +29,8 @@ #ifndef __INDEX_DERIVS_INTERFACE_HXX__ #define __INDEX_DERIVS_INTERFACE_HXX__ +#include "bout/boutexception.hxx" +#include "bout/field3d.hxx" #include "bout/traits.hxx" #include #include @@ -148,8 +150,8 @@ T standardDerivative(const T& f, CELL_LOC outloc, const std::string& method, } // Lookup the method - auto derivativeMethod = DerivativeStore::getInstance().getStandardDerivative( - method, direction, stagger, derivType); + auto derivativeMethod = + getStore().getStandardDerivative(method, direction, stagger, derivType); // Create the result field T result{emptyFrom(f).setLocation(outloc)}; @@ -175,6 +177,11 @@ T DDX(const T& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "D return standardDerivative(f, outloc, method, region); } +inline Field3D DDX(const Field3DParallel& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY") { + return DDX(f.asField3D(), outloc, method, region); +} template T D2DX2(const T& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT", @@ -198,8 +205,13 @@ template T DDY(const T& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT", const std::string& region = "RGN_NOBNDRY") { - if (f.hasParallelSlices()) { + if (f.isFci()) { ASSERT1(f.getDirectionY() == YDirectionType::Standard); + if (!f.hasParallelSlices()) { + throw BoutException( + "parallel slices needed for parallel derivatives. Make sure to communicate and " + "apply parallel boundary conditions before calling derivative"); + } return standardDerivative(f, outloc, method, region); } else { @@ -210,6 +222,11 @@ T DDY(const T& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "D return is_unaligned ? fromFieldAligned(result, region) : result; } } +inline Field3D DDY(const Field3DParallel& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY") { + return DDY(f.asField3D(), outloc, method, region); +} template T D2DY2(const T& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT", @@ -252,6 +269,11 @@ T DDZ(const T& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "D return standardDerivative(f, outloc, method, region); } +inline Field3D DDZ(const Field3DParallel& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY") { + return DDZ(f.asField3D(), outloc, method, region); +} template T D2DZ2(const T& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT", @@ -356,6 +378,11 @@ T VDDY(const T& vel, const T& f, CELL_LOC outloc = CELL_DEFAULT, return are_unaligned ? fromFieldAligned(result, region) : result; } } +inline Field3D VDDY(const Field3D& v, const Field3DParallel& f, + CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY") { + return VDDY(v, f.asField3D(), outloc, method, region); +} template T FDDY(const T& vel, const T& f, CELL_LOC outloc = CELL_DEFAULT, @@ -380,6 +407,11 @@ T FDDY(const T& vel, const T& f, CELL_LOC outloc = CELL_DEFAULT, return are_unaligned ? fromFieldAligned(result, region) : result; } } +inline Field3D FDDY(const Field3D& v, const Field3DParallel& f, + CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY") { + return FDDY(v, f.asField3D(), outloc, method, region); +} ////////////// Z DERIVATIVE ///////////////// diff --git a/include/bout/interpolation_xz.hxx b/include/bout/interpolation_xz.hxx index 79e959b3af..f95ef05a87 100644 --- a/include/bout/interpolation_xz.hxx +++ b/include/bout/interpolation_xz.hxx @@ -24,8 +24,14 @@ #ifndef BOUT_INTERP_XZ_H #define BOUT_INTERP_XZ_H +#include "bout/bout_types.hxx" #include "bout/build_defines.hxx" #include "bout/mask.hxx" +#include +#if BOUT_HAS_PETSC +#include +#include +#endif #define USE_NEW_WEIGHTS 1 #if BOUT_HAS_PETSC @@ -37,6 +43,7 @@ #endif class Options; +class GlobalField3DAccess; /// Interpolate a field onto a perturbed set of points const Field3D interpolate(const Field3D& f, const Field3D& delta_x, @@ -134,7 +141,8 @@ public: } }; -class XZHermiteSpline : public XZInterpolation { +template +class XZHermiteSplineBase : public XZInterpolation { protected: /// This is protected rather than private so that it can be /// extended and used by HermiteSplineMonotonic @@ -142,6 +150,9 @@ protected: Tensor> i_corner; // index of bottom-left grid point Tensor k_corner; // z-index of bottom-left grid point + std::unique_ptr gf3daccess; + Tensor> g3dinds; + // Basis functions for cubic Hermite spline interpolation // see http://en.wikipedia.org/wiki/Cubic_Hermite_spline // The h00 and h01 basis functions are applied to the function itself @@ -162,18 +173,24 @@ protected: #if HS_USE_PETSC PetscLib* petsclib; bool isInit{false}; - Mat petscWeights; - Vec rhs, result; + Mat petscWeights{}; + Vec rhs{}, result{}; #endif + /// Factors to allow for some wiggleroom + BoutReal abs_fac_monotonic{1e-2}; + BoutReal rel_fac_monotonic{1e-1}; + public: - XZHermiteSpline(Mesh* mesh = nullptr) : XZHermiteSpline(0, mesh) {} - XZHermiteSpline(int y_offset = 0, Mesh* mesh = nullptr); - XZHermiteSpline(const BoutMask& mask, int y_offset = 0, Mesh* mesh = nullptr) - : XZHermiteSpline(y_offset, mesh) { + XZHermiteSplineBase(Mesh* mesh = nullptr, Options* options = nullptr) + : XZHermiteSplineBase(0, mesh, options) {} + XZHermiteSplineBase(int y_offset = 0, Mesh* mesh = nullptr, Options* options = nullptr); + XZHermiteSplineBase(const BoutMask& mask, int y_offset = 0, Mesh* mesh = nullptr, + Options* options = nullptr) + : XZHermiteSplineBase(y_offset, mesh, options) { setRegion(regionFromMask(mask, localmesh)); } - ~XZHermiteSpline() { + ~XZHermiteSplineBase() { #if HS_USE_PETSC if (isInit) { MatDestroy(&petscWeights); @@ -211,33 +228,8 @@ public: /// but also degrades accuracy near maxima and minima. /// Perhaps should only impose near boundaries, since that is where /// problems most obviously occur. -class XZMonotonicHermiteSpline : public XZHermiteSpline { -public: - XZMonotonicHermiteSpline(Mesh* mesh = nullptr) : XZHermiteSpline(0, mesh) { - if (localmesh->getNXPE() > 1) { - throw BoutException("Do not support MPI splitting in X"); - } - } - XZMonotonicHermiteSpline(int y_offset = 0, Mesh* mesh = nullptr) - : XZHermiteSpline(y_offset, mesh) { - if (localmesh->getNXPE() > 1) { - throw BoutException("Do not support MPI splitting in X"); - } - } - XZMonotonicHermiteSpline(const BoutMask& mask, int y_offset = 0, Mesh* mesh = nullptr) - : XZHermiteSpline(mask, y_offset, mesh) { - if (localmesh->getNXPE() > 1) { - throw BoutException("Do not support MPI splitting in X"); - } - } - - using XZHermiteSpline::interpolate; - /// Interpolate using precalculated weights. - /// This function is called by the other interpolate functions - /// in the base class XZHermiteSpline. - Field3D interpolate(const Field3D& f, - const std::string& region = "RGN_NOBNDRY") const override; -}; +using XZMonotonicHermiteSpline = XZHermiteSplineBase; +using XZHermiteSpline = XZHermiteSplineBase; /// XZLagrange4pt interpolation class /// @@ -308,6 +300,15 @@ public: const std::string& region = "RGN_NOBNDRY") override; }; +class XZMonotonicHermiteSplineLegacy : public XZHermiteSplineBase { +public: + using XZHermiteSplineBase::interpolate; + virtual Field3D interpolate(const Field3D& f, + const std::string& region = "RGN_NOBNDRY") const override; + template + XZMonotonicHermiteSplineLegacy(Ts... args) : XZHermiteSplineBase(args...) {} +}; + class XZInterpolationFactory : public Factory { public: diff --git a/include/bout/invert_laplace.hxx b/include/bout/invert_laplace.hxx index 187056d115..90cff3511e 100644 --- a/include/bout/invert_laplace.hxx +++ b/include/bout/invert_laplace.hxx @@ -138,7 +138,11 @@ public: static constexpr auto type_name = "Laplacian"; static constexpr auto section_name = "laplace"; static constexpr auto option_name = "type"; +#if BOUT_USE_METRIC_3D + static constexpr auto default_type = LAPLACE_PETSC; +#else static constexpr auto default_type = LAPLACE_CYCLIC; +#endif ReturnType create(Options* options = nullptr, CELL_LOC loc = CELL_CENTRE, Mesh* mesh = nullptr, Solver* solver = nullptr) { @@ -255,6 +259,11 @@ public: virtual Field3D solve(const Field3D& b, const Field3D& x0); virtual Field2D solve(const Field2D& b, const Field2D& x0); + /// Some implementations can also implement the forward operator for testing + /// and debugging + virtual FieldPerp forward(const FieldPerp& f); + virtual Field3D forward(const Field3D& f); + /// Coefficients in tridiagonal inversion void tridagCoefs(int jx, int jy, int jz, dcomplex& a, dcomplex& b, dcomplex& c, const Field2D* ccoef = nullptr, const Field2D* d = nullptr, diff --git a/include/bout/mask.hxx b/include/bout/mask.hxx index fc5f814676..e618d9db4d 100644 --- a/include/bout/mask.hxx +++ b/include/bout/mask.hxx @@ -65,9 +65,8 @@ public: inline bool& operator()(int jx, int jy, int jz) { return mask(jx, jy, jz); } inline const bool& operator()(int jx, int jy, int jz) const { return mask(jx, jy, jz); } - - inline bool& operator[](const Ind3D& i) { return mask[i]; } inline const bool& operator[](const Ind3D& i) const { return mask[i]; } + inline bool& operator[](const Ind3D& i) { return mask[i]; } }; inline std::unique_ptr> regionFromMask(const BoutMask& mask, @@ -80,4 +79,13 @@ inline std::unique_ptr> regionFromMask(const BoutMask& mask, } return std::make_unique>(indices); } + +inline BoutMask maskFromRegion(const Region& region, const Mesh* mesh) { + BoutMask mask{mesh, false}; + //(int nx, int ny, int nz, bool value=false) : + + BOUT_FOR(i, region) { mask[i] = true; } + return mask; +} + #endif //BOUT_MASK_H diff --git a/include/bout/msg_stack.hxx b/include/bout/msg_stack.hxx index 1abb26d2c7..0fe22cd0e6 100644 --- a/include/bout/msg_stack.hxx +++ b/include/bout/msg_stack.hxx @@ -80,7 +80,7 @@ public: } void pop() {} - void pop(int [[maybe_unused]] id) {} + void pop([[maybe_unused]] int id) {} void clear() {} void dump() {} diff --git a/include/bout/options.hxx b/include/bout/options.hxx index 1c552cd4f9..a731f73692 100644 --- a/include/bout/options.hxx +++ b/include/bout/options.hxx @@ -1012,6 +1012,9 @@ auto Options::as(const Tensor& similar_to) const -> Tensor; /// Convert \p value to string std::string toString(const Options& value); +/// Save the parallel fields +void saveParallel(Options& opt, const std::string& name, const Field3D& tosave); + /// Output a stringified \p value to a stream /// /// This is templated to avoid implict casting: anything is diff --git a/include/bout/parallel_boundary_op.hxx b/include/bout/parallel_boundary_op.hxx index d8620e892b..9e551ebc17 100644 --- a/include/bout/parallel_boundary_op.hxx +++ b/include/bout/parallel_boundary_op.hxx @@ -49,7 +49,7 @@ protected: enum class ValueType { GEN, FIELD, REAL }; const ValueType value_type{ValueType::REAL}; - BoutReal getValue(const BoundaryRegionPar& bndry, BoutReal t); + BoutReal getValue(const BoundaryRegionParIter& bndry, BoutReal t); }; template @@ -95,12 +95,13 @@ public: auto dy = f.getCoordinates()->dy; - for (bndry->first(); !bndry->isDone(); bndry->next()) { - BoutReal value = getValue(*bndry, t); + for (auto pnt : *bndry) { + //for (bndry->first(); !bndry->isDone(); bndry->next()) { + BoutReal value = getValue(pnt, t); if (isNeumann) { - value *= dy[bndry->ind()]; + value *= dy[pnt.ind()]; } - static_cast(this)->apply_stencil(f, bndry, value); + static_cast(this)->apply_stencil(f, pnt, value); } } }; @@ -111,24 +112,27 @@ public: class BoundaryOpPar_dirichlet_o1 : public BoundaryOpParTemp { public: using BoundaryOpParTemp::BoundaryOpParTemp; - static void apply_stencil(Field3D& f, const BoundaryRegionPar* bndry, BoutReal value) { - bndry->dirichlet_o1(f, value); + static void apply_stencil(Field3D& f, const BoundaryRegionParIter& pnt, + BoutReal value) { + pnt.dirichlet_o1(f, value); } }; class BoundaryOpPar_dirichlet_o2 : public BoundaryOpParTemp { public: using BoundaryOpParTemp::BoundaryOpParTemp; - static void apply_stencil(Field3D& f, const BoundaryRegionPar* bndry, BoutReal value) { - bndry->dirichlet_o2(f, value); + static void apply_stencil(Field3D& f, const BoundaryRegionParIter& pnt, + BoutReal value) { + pnt.dirichlet_o2(f, value); } }; class BoundaryOpPar_dirichlet_o3 : public BoundaryOpParTemp { public: using BoundaryOpParTemp::BoundaryOpParTemp; - static void apply_stencil(Field3D& f, const BoundaryRegionPar* bndry, BoutReal value) { - bndry->dirichlet_o3(f, value); + static void apply_stencil(Field3D& f, const BoundaryRegionParIter& pnt, + BoutReal value) { + pnt.dirichlet_o3(f, value); } }; @@ -136,8 +140,9 @@ class BoundaryOpPar_neumann_o1 : public BoundaryOpParTemp { public: using BoundaryOpParTemp::BoundaryOpParTemp; - static void apply_stencil(Field3D& f, const BoundaryRegionPar* bndry, BoutReal value) { - bndry->neumann_o1(f, value); + static void apply_stencil(Field3D& f, const BoundaryRegionParIter& pnt, + BoutReal value) { + pnt.neumann_o1(f, value); } }; @@ -145,8 +150,9 @@ class BoundaryOpPar_neumann_o2 : public BoundaryOpParTemp { public: using BoundaryOpParTemp::BoundaryOpParTemp; - static void apply_stencil(Field3D& f, const BoundaryRegionPar* bndry, BoutReal value) { - bndry->neumann_o2(f, value); + static void apply_stencil(Field3D& f, const BoundaryRegionParIter& pnt, + BoutReal value) { + pnt.neumann_o2(f, value); } }; @@ -154,8 +160,9 @@ class BoundaryOpPar_neumann_o3 : public BoundaryOpParTemp { public: using BoundaryOpParTemp::BoundaryOpParTemp; - static void apply_stencil(Field3D& f, const BoundaryRegionPar* bndry, BoutReal value) { - bndry->neumann_o3(f, value); + static void apply_stencil(Field3D& f, const BoundaryRegionParIter& pnt, + BoutReal value) { + pnt.neumann_o3(f, value); } }; diff --git a/include/bout/parallel_boundary_region.hxx b/include/bout/parallel_boundary_region.hxx index 4d5278d00f..83d7f1f515 100644 --- a/include/bout/parallel_boundary_region.hxx +++ b/include/bout/parallel_boundary_region.hxx @@ -1,10 +1,22 @@ #ifndef BOUT_PAR_BNDRY_H #define BOUT_PAR_BNDRY_H +#include "bout/assert.hxx" #include "bout/boundary_region.hxx" +#include "bout/bout_enum_class.hxx" #include "bout/bout_types.hxx" +#include "bout/build_defines.hxx" +#include "bout/field2d.hxx" +#include "bout/region.hxx" +#include +#include +#include +#include +#include #include +#include "bout/sys/parallel_stencils.hxx" +#include "bout/utils.hxx" #include #include @@ -14,99 +26,99 @@ * */ -namespace parallel_stencil { -// generated by src/mesh/parallel_boundary_stencil.cxx.py -inline BoutReal pow(BoutReal val, int exp) { - // constexpr int expval = exp; - // static_assert(expval == 2 or expval == 3, "This pow is only for exponent 2 or 3"); - if (exp == 2) { - return val * val; - } - ASSERT3(exp == 3); - return val * val * val; -} -inline BoutReal dirichlet_o1(BoutReal UNUSED(spacing0), BoutReal value0) { - return value0; -} -inline BoutReal dirichlet_o2(BoutReal spacing0, BoutReal value0, BoutReal spacing1, - BoutReal value1) { - return (spacing0 * value1 - spacing1 * value0) / (spacing0 - spacing1); -} -inline BoutReal neumann_o2(BoutReal UNUSED(spacing0), BoutReal value0, BoutReal spacing1, - BoutReal value1) { - return -spacing1 * value0 + value1; -} -inline BoutReal dirichlet_o3(BoutReal spacing0, BoutReal value0, BoutReal spacing1, - BoutReal value1, BoutReal spacing2, BoutReal value2) { - return (pow(spacing0, 2) * spacing1 * value2 - pow(spacing0, 2) * spacing2 * value1 - - spacing0 * pow(spacing1, 2) * value2 + spacing0 * pow(spacing2, 2) * value1 - + pow(spacing1, 2) * spacing2 * value0 - spacing1 * pow(spacing2, 2) * value0) - / ((spacing0 - spacing1) * (spacing0 - spacing2) * (spacing1 - spacing2)); -} -inline BoutReal neumann_o3(BoutReal spacing0, BoutReal value0, BoutReal spacing1, - BoutReal value1, BoutReal spacing2, BoutReal value2) { - return (2 * spacing0 * spacing1 * value2 - 2 * spacing0 * spacing2 * value1 - + pow(spacing1, 2) * spacing2 * value0 - pow(spacing1, 2) * value2 - - spacing1 * pow(spacing2, 2) * value0 + pow(spacing2, 2) * value1) - / ((spacing1 - spacing2) * (2 * spacing0 - spacing1 - spacing2)); -} -} // namespace parallel_stencil +BOUT_ENUM_CLASS(SheathLimitMode, limit_free, exponential_free, linear_free); -class BoundaryRegionPar : public BoundaryRegionBase { +namespace bout { +namespace parallel_boundary_region { - struct RealPoint { - BoutReal s_x; - BoutReal s_y; - BoutReal s_z; - }; - - struct Indices { - // Indices of the boundary point - Ind3D index; - // Intersection with boundary in index space - RealPoint intersection; - // Distance to intersection - BoutReal length; - // Angle between field line and boundary - // BoutReal angle; - // How many points we can go in the opposite direction - signed char valid; - }; - - using IndicesVec = std::vector; - using IndicesIter = IndicesVec::iterator; +struct RealPoint { + BoutReal s_x; + BoutReal s_y; + BoutReal s_z; +}; - /// Vector of points in the boundary - IndicesVec bndry_points; - /// Current position in the boundary points - IndicesIter bndry_position; +struct Indices { + // Indices of the boundary point + Ind3D index; + // Intersection with boundary in index space + RealPoint intersection; + // Distance to intersection + BoutReal length; + // Angle between field line and boundary + // BoutReal angle; + // How many points we can go in the opposite direction + signed char valid; + signed char offset; + unsigned char abs_offset; + Indices(Ind3D index, RealPoint&& intersection, BoutReal length, signed char valid, + signed char offset, unsigned char abs_offset) + : index(index), intersection(intersection), length(length), valid(valid), + offset(offset), abs_offset(abs_offset){}; +}; -public: - BoundaryRegionPar(const std::string& name, int dir, Mesh* passmesh) - : BoundaryRegionBase(name, passmesh), dir(dir) { - ASSERT0(std::abs(dir) == 1); - BoundaryRegionBase::isParallel = true; +using IndicesVec = std::vector; +using IndicesIter = IndicesVec::iterator; +using IndicesIterConst = IndicesVec::const_iterator; + +/// Limited free gradient of log of a quantity +/// This ensures that the guard cell values remain positive +/// while also ensuring that the quantity never increases +/// +/// fm fc | fp +/// ^ boundary +/// +/// exp( 2*log(fc) - log(fm) ) +inline BoutReal limitFreeScale(BoutReal fm, BoutReal fc, SheathLimitMode mode) { + if ((fm < fc) && (mode == SheathLimitMode::limit_free)) { + return fc; // Neumann rather than increasing into boundary } - BoundaryRegionPar(const std::string& name, BndryLoc loc, int dir, Mesh* passmesh) - : BoundaryRegionBase(name, loc, passmesh), dir(dir) { - BoundaryRegionBase::isParallel = true; - ASSERT0(std::abs(dir) == 1); + if (fm < 1e-10) { + return fc; // Low / no density condition } - /// Add a point to the boundary - void add_point(Ind3D ind, BoutReal x, BoutReal y, BoutReal z, BoutReal length, - signed char valid) { - bndry_points.push_back({ind, {x, y, z}, length, valid}); + BoutReal fp = 0; + switch (mode) { + case SheathLimitMode::limit_free: + case SheathLimitMode::exponential_free: + fp = SQ(fc) / fm; // Exponential + break; + case SheathLimitMode::linear_free: + fp = (2.0 * fc) - fm; // Linear + break; } - void add_point(int ix, int iy, int iz, BoutReal x, BoutReal y, BoutReal z, - BoutReal length, signed char valid) { - bndry_points.push_back({xyz2ind(ix, iy, iz, localmesh), {x, y, z}, length, valid}); + +#if CHECKLEVEL >= 2 + if (!std::isfinite(fp)) { + throw BoutException("SheathBoundary limitFree: {}, {} -> {}", fm, fc, fp); } +#endif - // final, so they can be inlined - void first() final { bndry_position = begin(bndry_points); } - void next() final { ++bndry_position; } - bool isDone() final { return (bndry_position == end(bndry_points)); } + return fp; +} + +inline BoutReal limitFreeScale(BoutReal fm, BoutReal fc) { + if (fm < fc) { + return 1; // Neumann rather than increasing into boundary + } + if (fm < 1e-10) { + return 1; // Low / no density condition + } + BoutReal fp = fc / fm; +#if CHECKLEVEL >= 2 + if (!std::isfinite(fp)) { + throw BoutException("SheathBoundaryParallel limitFree: {}, {} -> {}", fm, fc, fp); + } +#endif + return fp; +} + +template +class BoundaryRegionParIterBase { +public: + BoundaryRegionParIterBase(IndicesVec& bndry_points, IndicesIter bndry_position, int dir, + Mesh* localmesh) + : bndry_points(bndry_points), bndry_position(bndry_position), dir(dir), + localmesh(localmesh){}; // getter Ind3D ind() const { return bndry_position->index; } @@ -115,37 +127,99 @@ public: BoutReal s_z() const { return bndry_position->intersection.s_z; } BoutReal length() const { return bndry_position->length; } signed char valid() const { return bndry_position->valid; } + signed char offset() const { return bndry_position->offset; } + unsigned char abs_offset() const { return bndry_position->abs_offset; } // setter - void setValid(signed char val) { bndry_position->valid = val; } + void setValid(signed char valid) { bndry_position->valid = valid; } - bool contains(const BoundaryRegionPar& bndry) const { - return std::binary_search( - begin(bndry_points), end(bndry_points), *bndry.bndry_position, - [](const Indices& i1, const Indices& i2) { return i1.index < i2.index; }); + // extrapolate a given point to the boundary + BoutReal extrapolate_sheath_o1(const Field3D& f) const { return ythis(f); } + BoutReal extrapolate_sheath_o2(const Field3D& f) const { + ASSERT3(valid() >= 0); + if (valid() < 1) { + return extrapolate_sheath_o1(f); + } + return ythis(f) * (1 + length()) - yprev(f) * length(); + } + inline BoutReal + extrapolate_sheath_o1(const std::function& f) const { + return ythis(f); + } + inline BoutReal + extrapolate_sheath_o2(const std::function& f) const { + ASSERT3(valid() >= 0); + if (valid() < 1) { + return extrapolate_sheath_o1(f); + } + return ythis(f) * (1 + length()) - yprev(f) * length(); } - // extrapolate a given point to the boundary - BoutReal extrapolate_o1(const Field3D& f) const { return f[ind()]; } - BoutReal extrapolate_o2(const Field3D& f) const { + inline BoutReal interpolate_sheath_o2(const Field3D& f) const { + return ythis(f) * (1 - length()) + ynext(f) * length(); + } + inline BoutReal + interpolate_sheath_o2(const std::function& f) const { + return ythis(f) * (1 - length()) + ynext(f) * length(); + } + + inline BoutReal extrapolate_next_o1(const Field3D& f) const { return ythis(f); } + inline BoutReal extrapolate_next_o2(const Field3D& f) const { + ASSERT3(valid() >= 0); + if (valid() < 1) { + return extrapolate_next_o1(f); + } + return ythis(f) * 2 - yprev(f); + } + + inline BoutReal + extrapolate_next_o1(const std::function& f) const { + return ythis(f); + } + inline BoutReal + extrapolate_next_o2(const std::function& f) const { ASSERT3(valid() >= 0); if (valid() < 1) { - return extrapolate_o1(f); + return extrapolate_sheath_o1(f); } - return f[ind()] * (1 + length()) - f.ynext(-dir)[ind().yp(-dir)] * length(); + return ythis(f) * 2 - yprev(f); } + // extrapolate the gradient into the boundary + inline BoutReal extrapolate_grad_o1(const Field3D& f) const { return 0; } + inline BoutReal extrapolate_grad_o2(const Field3D& f) const { + ASSERT3(valid() >= 0); + if (valid() < 1) { + return extrapolate_grad_o1(f); + } + return ythis(f) - ynext(f); + } + + BoundaryRegionParIterBase& operator*() { return *this; } + + BoundaryRegionParIterBase& operator++() { + ++bndry_position; + return *this; + } + + bool operator!=(const BoundaryRegionParIterBase& rhs) { + return bndry_position != rhs.bndry_position; + } + +#define ITER() for (int i = 0; i < localmesh->ystart - abs_offset(); ++i) // dirichlet boundary code void dirichlet_o1(Field3D& f, BoutReal value) const { - f.ynext(dir)[ind().yp(dir)] = value; + ITER() { getAt(f, i) = value; } } void dirichlet_o2(Field3D& f, BoutReal value) const { if (length() < small_value) { return dirichlet_o1(f, value); } - ynext(f) = parallel_stencil::dirichlet_o2(1, f[ind()], 1 - length(), value); - // ynext(f) = f[ind()] * (1 + 1/length()) + value / length(); + ITER() { + getAt(f, i) = + parallel_stencil::dirichlet_o2(i + 1, ythis(f), i + 1 - length(), value); + } } void dirichlet_o3(Field3D& f, BoutReal value) const { @@ -154,17 +228,34 @@ public: return dirichlet_o2(f, value); } if (length() < small_value) { - ynext(f) = parallel_stencil::dirichlet_o2(2, yprev(f), 1 - length(), value); + ITER() { + getAt(f, i) = + parallel_stencil::dirichlet_o2(i + 2, yprev(f), i + 1 - length(), value); + } } else { - ynext(f) = - parallel_stencil::dirichlet_o3(2, yprev(f), 1, f[ind()], 1 - length(), value); + ITER() { + getAt(f, i) = parallel_stencil::dirichlet_o3(i + 2, yprev(f), i + 1, ythis(f), + i + 1 - length(), value); + } } } + void limit_at_least(Field3D& f, BoutReal value) const { + ITER() { + if (getAt(f, i) < value) { + getAt(f, i) = value; + } + } + } + + bool is_lower() const { return dir == -1; } + // NB: value needs to be scaled by dy // neumann_o1 is actually o2 if we would use an appropriate one-sided stencil. // But in general we do not, and thus for normal C2 stencils, this is 1st order. - void neumann_o1(Field3D& f, BoutReal value) const { ynext(f) = f[ind()] + value; } + void neumann_o1(Field3D& f, BoutReal value) const { + ITER() { getAt(f, i) = ythis(f) + value * (i + 1); } + } // NB: value needs to be scaled by dy void neumann_o2(Field3D& f, BoutReal value) const { @@ -172,34 +263,231 @@ public: if (valid() < 1) { return neumann_o1(f, value); } - ynext(f) = yprev(f) + 2 * value; + ITER() { getAt(f, i) = yprev(f) + (2 + i) * value; } } // NB: value needs to be scaled by dy void neumann_o3(Field3D& f, BoutReal value) const { ASSERT3(valid() >= 0); if (valid() < 1) { - return neumann_o1(f, value); + return neumann_o2(f, value); + } + ITER() { + getAt(f, i) = parallel_stencil::neumann_o3(i + 1 - length(), value, i + 1, ythis(f), + 2, yprev(f)); } - ynext(f) = - parallel_stencil::neumann_o3(1 - length(), value, 1, f[ind()], 2, yprev(f)); } - const int dir; + // extrapolate into the boundary using only monotonic decreasing values. + // f needs to be positive + void limitFree(Field3D& f) const { + const auto fac = valid() > 0 ? limitFreeScale(yprev(f), ythis(f)) : 1; + auto val = ythis(f); + ITER() { + val *= fac; + getAt(f, i) = val; + } + } + + BoutReal extrapolate_sheath_free(const Field3D& f, SheathLimitMode mode) const { + const auto fac = valid() > 0 ? limitFreeScale(yprev(f), ythis(f), mode) + : (mode == SheathLimitMode::linear_free ? 0 : 1); + auto val = ythis(f); + BoutReal next = mode == SheathLimitMode::linear_free ? val + fac : val * fac; + return val * length() + next * (1 - length()); + } + + void set_free(Field3D& f, SheathLimitMode mode) const { + const auto fac = valid() > 0 ? limitFreeScale(yprev(f), ythis(f), mode) + : (mode == SheathLimitMode::linear_free ? 0 : 1); + auto val = ythis(f); + if (mode == SheathLimitMode::linear_free) { + ITER() { + val += fac; + getAt(f, i) = val; + } + } else { + ITER() { + val *= fac; + getAt(f, i) = val; + } + } + } + + void setAll(Field3D& f, const BoutReal val) const { + for (int i = -localmesh->ystart; i <= localmesh->ystart; ++i) { + f.ynext(i)[ind().yp(i)] = val; + } + } + + template + BoutReal& getAt(Field3D& f, int off) const { + ASSERT3(f.hasParallelSlices()); + if constexpr (check) { + ASSERT3(valid() > -off - 2); + } + auto _off = offset() + off * dir; + return f.ynext(_off)[ind().yp(_off)]; + } + template + const BoutReal& getAt(const Field3D& f, int off) const { + ASSERT3(f.hasParallelSlices()); + if constexpr (check) { + ASSERT3(valid() > -off - 2); + } + auto _off = offset() + off * dir; + return f.ynext(_off)[ind().yp(_off)]; + } + + const BoutReal& ynext(const Field3D& f) const { return getAt(f, 0); } + BoutReal& ynext(Field3D& f) const { return getAt(f, 0); } + const BoutReal& ythis(const Field3D& f) const { return getAt(f, -1); } + BoutReal& ythis(Field3D& f) const { return getAt(f, -1); } + const BoutReal& yprev(const Field3D& f) const { return getAt(f, -2); } + BoutReal& yprev(Field3D& f) const { return getAt(f, -2); } + + template + BoutReal getAt(const std::function& f, + int off) const { + if constexpr (check) { + ASSERT3(valid() > -off - 2); + } + auto _off = offset() + off * dir; + return f(_off, ind().yp(_off)); + } + BoutReal ynext(const std::function& f) const { + return getAt(f, 0); + } + BoutReal ythis(const std::function& f) const { + return getAt(f, -1); + } + BoutReal yprev(const std::function& f) const { + return getAt(f, -2); + } + + void setYPrevIfValid(Field3D& f, BoutReal val) const { + if (valid() > 0) { + yprev(f) = val; + } + } + +#if BOUT_USE_METRIC_3D == 0 + const BoutReal& ynext(const Field2D& f) const { return f.ynext(dir)[ind().yp(dir)]; } + BoutReal& ynext(Field2D& f) const { return f.ynext(dir)[ind().yp(dir)]; } + + const BoutReal& yprev(const Field2D& f) const { + ASSERT3(valid() > 0); + return f.ynext(-dir)[ind().yp(-dir)]; + } + BoutReal& yprev(Field2D& f) const { + ASSERT3(valid() > 0); + return f.ynext(-dir)[ind().yp(-dir)]; + } +#endif private: + const IndicesVec& bndry_points; + IndicesIter bndry_position; + constexpr static BoutReal small_value = 1e-2; - // BoutReal get(const Field3D& f, int off) - const BoutReal& ynext(const Field3D& f) const { return f.ynext(dir)[ind().yp(dir)]; } - BoutReal& ynext(Field3D& f) const { return f.ynext(dir)[ind().yp(dir)]; } - const BoutReal& yprev(const Field3D& f) const { return f.ynext(-dir)[ind().yp(-dir)]; } - BoutReal& yprev(Field3D& f) const { return f.ynext(-dir)[ind().yp(-dir)]; } +public: + const int dir; + Mesh* localmesh; +}; +} // namespace parallel_boundary_region +} // namespace bout +using BoundaryRegionParIter = bout::parallel_boundary_region::BoundaryRegionParIterBase< + bout::parallel_boundary_region::IndicesVec, + bout::parallel_boundary_region::IndicesIter>; +using BoundaryRegionParIterConst = + bout::parallel_boundary_region::BoundaryRegionParIterBase< + const bout::parallel_boundary_region::IndicesVec, + bout::parallel_boundary_region::IndicesIterConst>; + +class BoundaryRegionPar : public BoundaryRegionBase { +public: + BoundaryRegionPar(const std::string& name, int dir, Mesh* passmesh) + : BoundaryRegionBase(name, passmesh), dir(dir) { + ASSERT0(std::abs(dir) == 1); + BoundaryRegionBase::isParallel = true; + } + BoundaryRegionPar(const std::string& name, BndryLoc loc, int dir, Mesh* passmesh) + : BoundaryRegionBase(name, loc, passmesh), dir(dir) { + BoundaryRegionBase::isParallel = true; + ASSERT0(std::abs(dir) == 1); + } + + /// Add a point to the boundary + void add_point(Ind3D ind, BoutReal x, BoutReal y, BoutReal z, BoutReal length, + char valid, signed char offset) { + if (!bndry_points.empty() && bndry_points.back().index > ind) { + is_sorted = false; + } + bndry_points.emplace_back(ind, bout::parallel_boundary_region::RealPoint{x, y, z}, + length, valid, offset, + static_cast(std::abs(offset))); + } + void add_point(int ix, int iy, int iz, BoutReal x, BoutReal y, BoutReal z, + BoutReal length, char valid, signed char offset) { + add_point(xyz2ind(ix, iy, iz, localmesh), x, y, z, length, valid, offset); + } + + // final, so they can be inlined + void first() final { bndry_position = std::begin(bndry_points); } + void next() final { ++bndry_position; } + bool isDone() final { return (bndry_position == std::end(bndry_points)); } + + bool contains(const BoundaryRegionPar& bndry) const { + ASSERT2(is_sorted); + return std::binary_search(std::begin(bndry_points), std::end(bndry_points), + *bndry.bndry_position, + [](const bout::parallel_boundary_region::Indices& i1, + const bout::parallel_boundary_region::Indices& i2) { + return i1.index < i2.index; + }); + } + + bool contains(const int ix, const int iy, const int iz) const { + const auto i2 = xyz2ind(ix, iy, iz, localmesh); + for (auto i1 : bndry_points) { + if (i1.index == i2) { + return true; + } + } + return false; + } + + // setter + void setValid(char val) { bndry_position->valid = val; } + + // BoundaryRegionParIterConst begin() const { + // return BoundaryRegionParIterConst(bndry_points, bndry_points.begin(), dir); + // } + // BoundaryRegionParIterConst end() const { + // return BoundaryRegionParIterConst(bndry_points, bndry_points.begin(), dir); + // } + BoundaryRegionParIter begin() { + return BoundaryRegionParIter(bndry_points, bndry_points.begin(), dir, localmesh); + } + BoundaryRegionParIter end() { + return BoundaryRegionParIter(bndry_points, bndry_points.end(), dir, localmesh); + } + + const int dir; + +private: + /// Vector of points in the boundary + bout::parallel_boundary_region::IndicesVec bndry_points; + /// Current position in the boundary points + bout::parallel_boundary_region::IndicesIter bndry_position; + static Ind3D xyz2ind(int x, int y, int z, Mesh* mesh) { const int ny = mesh->LocalNy; const int nz = mesh->LocalNz; return Ind3D{(x * ny + y) * nz + z, ny, nz}; } + bool is_sorted{true}; }; #endif // BOUT_PAR_BNDRY_H diff --git a/include/bout/paralleltransform.hxx b/include/bout/paralleltransform.hxx index 49dea67743..c03afddefc 100644 --- a/include/bout/paralleltransform.hxx +++ b/include/bout/paralleltransform.hxx @@ -9,6 +9,7 @@ #include "bout/bout_types.hxx" #include "bout/dcomplex.hxx" #include "bout/field3d.hxx" +#include "bout/field_data.hxx" #include "bout/options.hxx" #include "bout/unused.hxx" @@ -90,6 +91,10 @@ public: /// require a twist-shift at branch cuts on closed field lines? virtual bool requiresTwistShift(bool twist_shift_enabled, YDirectionType ytype) = 0; + /// Can be implemented to load parallel metrics + /// Needed by FCI + virtual void loadParallelMetrics(Coordinates* UNUSED(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/petsclib.hxx b/include/bout/petsclib.hxx index 41d7618fd2..a1454b129e 100644 --- a/include/bout/petsclib.hxx +++ b/include/bout/petsclib.hxx @@ -160,6 +160,10 @@ private: #endif // PETSC_VERSION_GE +#if !PETSC_VERSION_GE(3, 19, 0) +#define PETSC_SUCCESS ((PetscErrorCode)0) +#endif + #else // BOUT_HAS_PETSC #include "bout/unused.hxx" diff --git a/include/bout/physicsmodel.hxx b/include/bout/physicsmodel.hxx index e6d34daef9..471faca307 100644 --- a/include/bout/physicsmodel.hxx +++ b/include/bout/physicsmodel.hxx @@ -47,6 +47,8 @@ class PhysicsModel; #include "bout/unused.hxx" #include "bout/utils.hxx" +#include +#include #include #include @@ -270,8 +272,11 @@ protected: virtual int rhs(BoutReal UNUSED(t)) { return 1; } virtual int rhs(BoutReal t, bool UNUSED(linear)) { return rhs(t); } +public: /// Output additional variables other than the evolving variables virtual void outputVars(Options& options); + +protected: /// Add additional variables other than the evolving variables to the restart files virtual void restartVars(Options& options); @@ -434,6 +439,7 @@ private: solver->solve(); \ } catch (const BoutException& e) { \ output.write("Error encountered: {}\n", e.what()); \ + std::this_thread::sleep_for(std::chrono::milliseconds(100)); \ MPI_Abort(BoutComm::get(), 1); \ } \ BoutFinalise(); \ diff --git a/include/bout/region.hxx b/include/bout/region.hxx index bb1cf82bf1..17ded65987 100644 --- a/include/bout/region.hxx +++ b/include/bout/region.hxx @@ -139,7 +139,7 @@ class BoutMask; BOUT_FOR_OMP(index, (region), for schedule(BOUT_OPENMP_SCHEDULE) nowait) // NOLINTEND(cppcoreguidelines-macro-usage,bugprone-macro-parentheses) -enum class IND_TYPE { IND_3D = 0, IND_2D = 1, IND_PERP = 2 }; +enum class IND_TYPE { IND_3D = 0, IND_2D = 1, IND_PERP = 2, IND_GLOBAL_3D = 3 }; /// Indices base class for Fields -- Regions are dereferenced into these /// @@ -386,6 +386,7 @@ inline SpecificInd operator-(SpecificInd lhs, const SpecificInd& rhs) { using Ind3D = SpecificInd; using Ind2D = SpecificInd; using IndPerp = SpecificInd; +using IndG3D = SpecificInd; /// Get string representation of Ind3D inline std::string toString(const Ind3D& i) { diff --git a/include/bout/solver.hxx b/include/bout/solver.hxx index 446acefecb..71bcc3fe4b 100644 --- a/include/bout/solver.hxx +++ b/include/bout/solver.hxx @@ -321,6 +321,8 @@ public: /// @param[in] save_repeat If true, add variables with time dimension virtual void outputVars(Options& output_options, bool save_repeat = true); + void modelOutputVars(Options& output_options); + /// Copy evolving variables out of \p options virtual void readEvolvingVariablesFromOptions(Options& options); diff --git a/include/bout/sys/parallel_stencils.hxx b/include/bout/sys/parallel_stencils.hxx new file mode 100644 index 0000000000..e9a3e6a6e7 --- /dev/null +++ b/include/bout/sys/parallel_stencils.hxx @@ -0,0 +1,39 @@ +#pragma once + +namespace parallel_stencil { +// generated by src/mesh/parallel_boundary_stencil.cxx.py +inline BoutReal pow(BoutReal val, int exp) { + // constexpr int expval = exp; + // static_assert(expval == 2 or expval == 3, "This pow is only for exponent 2 or 3"); + if (exp == 2) { + return val * val; + } + ASSERT3(exp == 3); + return val * val * val; +} +inline BoutReal dirichlet_o1(BoutReal UNUSED(spacing0), BoutReal value0) { + return value0; +} +inline BoutReal dirichlet_o2(BoutReal spacing0, BoutReal value0, BoutReal spacing1, + BoutReal value1) { + return (spacing0 * value1 - spacing1 * value0) / (spacing0 - spacing1); +} +inline BoutReal neumann_o2(BoutReal UNUSED(spacing0), BoutReal value0, BoutReal spacing1, + BoutReal value1) { + return (-spacing1 * value0) + value1; +} +inline BoutReal dirichlet_o3(BoutReal spacing0, BoutReal value0, BoutReal spacing1, + BoutReal value1, BoutReal spacing2, BoutReal value2) { + return (pow(spacing0, 2) * spacing1 * value2 - pow(spacing0, 2) * spacing2 * value1 + - spacing0 * pow(spacing1, 2) * value2 + spacing0 * pow(spacing2, 2) * value1 + + pow(spacing1, 2) * spacing2 * value0 - spacing1 * pow(spacing2, 2) * value0) + / ((spacing0 - spacing1) * (spacing0 - spacing2) * (spacing1 - spacing2)); +} +inline BoutReal neumann_o3(BoutReal spacing0, BoutReal value0, BoutReal spacing1, + BoutReal value1, BoutReal spacing2, BoutReal value2) { + return (2 * spacing0 * spacing1 * value2 - 2 * spacing0 * spacing2 * value1 + + pow(spacing1, 2) * spacing2 * value0 - pow(spacing1, 2) * value2 + - spacing1 * pow(spacing2, 2) * value0 + pow(spacing2, 2) * value1) + / ((spacing1 - spacing2) * (2 * spacing0 - spacing1 - spacing2)); +} +} // namespace parallel_stencil diff --git a/include/bout/utils.hxx b/include/bout/utils.hxx index 04aa0b281d..468c1a3267 100644 --- a/include/bout/utils.hxx +++ b/include/bout/utils.hxx @@ -353,14 +353,14 @@ public: return data[(i1 * n2 + i2) * n3 + i3]; } - const T& operator[](Ind3D i) const { + const T& operator[](const Ind3D i) const { // ny and nz are private :-( // ASSERT2(i.nz == n3); // ASSERT2(i.ny == n2); ASSERT2(0 <= i.ind && i.ind < n1 * n2 * n3); return data[i.ind]; } - T& operator[](Ind3D i) { + T& operator[](const Ind3D i) { // ny and nz are private :-( // ASSERT2(i.nz == n3); // ASSERT2(i.ny == n2); diff --git a/include/bout/yboundary_regions.hxx b/include/bout/yboundary_regions.hxx new file mode 100644 index 0000000000..1c4e467593 --- /dev/null +++ b/include/bout/yboundary_regions.hxx @@ -0,0 +1,85 @@ +#pragma once + +#include "./boundary_iterator.hxx" +#include "bout/assert.hxx" +#include "bout/options" +#include "bout/options.hxx" +#include "bout/parallel_boundary_region.hxx" +#include +#include + +/// This class allows to simplify iterating over y-boundaries. +/// +/// It makes it easier to write code for FieldAligned boundaries, but if a bit +/// care is taken the code also works with FluxCoordinateIndependent code. +/// +/// An example how to replace old code is given here: +/// ../../manual/sphinx/user_docs/boundary_options.rst + +class YBoundary { +public: + template + void iter_regions(const F& f) { + ASSERT1(is_init); + for (auto& region : boundary_regions) { + f(*region); + } + for (auto& region : boundary_regions_par) { + f(*region); + } + } + template + void iter_pnts(const F& f) { + iter_regions([&](auto& region) { + for (auto& pnt : region) { + f(pnt); + } + }); + } + + template + void iter(const F& f) { + return iter_regions(f); + } + + void init(Options& options, Mesh* mesh = nullptr) { + if (mesh == nullptr) { + mesh = bout::globals::mesh; + } + + bool lower_y = options["lower_y"].doc("Boundary on lower y?").withDefault(true); + bool upper_y = options["upper_y"].doc("Boundary on upper y?").withDefault(true); + bool outer_x = options["outer_x"].doc("Boundary on outer x?").withDefault(true); + bool inner_x = + options["inner_x"].doc("Boundary on inner x?").withDefault(false); + + if (mesh->isFci()) { + if (outer_x) { + for (auto& bndry : mesh->getBoundariesPar(BoundaryParType::xout)) { + boundary_regions_par.push_back(bndry); + } + } + if (inner_x) { + for (auto& bndry : mesh->getBoundariesPar(BoundaryParType::xin)) { + boundary_regions_par.push_back(bndry); + } + } + } else { + if (lower_y) { + boundary_regions.push_back( + std::make_shared(mesh, true, mesh->iterateBndryLowerY())); + } + if (upper_y) { + boundary_regions.push_back(std::make_shared( + mesh, false, mesh->iterateBndryUpperY())); + } + } + is_init = true; + } + +private: + std::vector> boundary_regions_par; + std::vector> boundary_regions; + + bool is_init{false}; +}; diff --git a/manual/sphinx/user_docs/boundary_options.rst b/manual/sphinx/user_docs/boundary_options.rst index 8493049516..c990a6d3e6 100644 --- a/manual/sphinx/user_docs/boundary_options.rst +++ b/manual/sphinx/user_docs/boundary_options.rst @@ -147,10 +147,9 @@ shifted``, see :ref:`sec-shifted-metric`), the recommended method is to apply boundary conditions directly to the ``yup`` and ``ydown`` parallel slices. This can be done by setting ``bndry_par_yup`` and ``bndry_par_ydown``, or ``bndry_par_all`` to set both at once. The -possible values are ``parallel_dirichlet_o1``, -``parallel_dirichlet_o2``, ``parallel_dirichlet_o3`` -and ``parallel_neumann_o1``, ``parallel_neumann_o2``, -``parallel_neumann_o3``. The stencils used are the same as for the +possible values are ``parallel_dirichlet_o1``, ``parallel_dirichlet_o2``, +``parallel_dirichlet_o3``, ``parallel_neumann_o1``, ``parallel_neumann_o2`` +and ``parallel_neumann_o3``. The stencils used are the same as for the standard boundary conditions without the ``parallel_`` prefix, but are applied directly to parallel slices. The boundary condition can only be applied after the parallel slices are calculated, which is usually @@ -436,6 +435,130 @@ the upper Y boundary of a 2D variable ``var``:: The `BoundaryRegion` class is defined in ``include/boundary_region.hxx`` +Y-Boundaries +------------ + +The sheath boundaries are often implemented in the physics model. +Previously of they where implemented using a `RangeIterator`:: + + class yboundary_example_legacy { + public: + yboundary_example_legacy(Options* opt, const Field3D& N, const Field3D& V) + : N(N), V(V) { + Options& options = *opt; + lower_y = options["lower_y"].doc("Boundary on lower y?").withDefault(lower_y); + upper_y = options["upper_y"].doc("Boundary on upper y?").withDefault(upper_y); + } + + void rhs() { + BoutReal totalFlux = 0; + if (lower_y) { + for (RangeIterator r = mesh->iterateBndryLowerY(); !r.isDone(); r++) { + for (int jz = 0; jz < mesh->LocalNz; jz++) { + // Calculate flux through surface [normalised m^-2 s^-1], + // should be positive since V < 0.0 + BoutReal flux = + -0.5 * (N(r.ind, mesh->ystart, jz) + N(r.ind, mesh->ystart - 1, jz)) * 0.5 + * (V(r.ind, mesh->ystart, jz) + V(r.ind, mesh->ystart - 1, jz)); + totalFlux += flux; + } + } + } + if (upper_y) { + for (RangeIterator r = mesh->iterateBndryUpperY(); !r.isDone(); r++) { + for (int jz = 0; jz < mesh->LocalNz; jz++) { + // Calculate flux through surface [normalised m^-2 s^-1], + // should be positive since V < 0.0 + BoutReal flux = -0.5 * (N(r.ind, mesh->yend, jz) + N(r.ind, mesh->yend + 1, jz)) + * 0.5 + * (V(r.ind, mesh->yend, jz) + V(r.ind, mesh->yend + 1, jz)); + totalFlux += flux; + } + } + } + } + + private: + bool lower_y{true}; + bool upper_y{true}; + const Field3D& N; + const Field3D& V; + } + + +This can be replaced using the `YBoundary` class, which not only simplifies the +code, but also allows to have the same code working with non-field-aligned +geometries, as flux coordinate independent (FCI) method:: + + #include + + class yboundary_example { + public: + yboundary_example(Options* opt, const Field3D& N, const Field3D& V) : N(N), V(V) { + // Set what kind of yboundaries you want to include + yboundary.init(opt); + } + + void rhs() { + BoutReal totalFlux = 0; + yboundary.iter_pnts([&](auto& pnt) { + BoutReal flux = pnt.interpolate_sheath_o2(N) * pnt.interpolate_sheath_o2(V); + }); + } + + private: + YBoundary ybounday; + const Field3D& N; + const Field3D& V; + }; + + + +There are several member functions of ``pnt``. ``pnt`` is of type +`BoundaryRegionParIterBase` and `BoundaryRegionIter`, and both should provide +the same interface. If they don't that is a bug, as the above code is a +template, that gets instantiated for both types, and thus requires both +classes to provide the same interface, one for FCI-like boundaries and one for +field aligned boundaries. + +Here is a short summary of some members of ``pnt``, where ``f`` is a : + +.. list-table:: Members for boundary operation + :widths: 15 70 + :header-rows: 1 + + * - Function + - Description + * - ``pnt.ythis(f)`` + - Returns the value at the last point in the domain + * - ``pnt.ynext(f)`` + - Returns the value at the first point in the domain + * - ``pnt.yprev(f)`` + - Returns the value at the second to last point in the domain, if it is + valid. NB: this point may not be valid. + * - ``pnt.interpolate_sheath_o2(f)`` + - Returns the value at the boundary, assuming the bounday value has been set + * - ``pnt.extrapolate_sheath_o1(f)`` + - Returns the value at the boundary, extrapolating from the bulk, first order + * - ``pnt.extrapolate_sheath_o2(f)`` + - Returns the value at the boundary, extrapolating from the bulk, second order + * - ``pnt.extrapolate_next_o{1,2}(f)`` + - Extrapolate into the boundary from the bulk, first or second order + * - ``pnt.extrapolate_grad_o{1,2}(f)`` + - Extrapolate the gradient into the boundary, first or second order + * - ``pnt.dirichlet_o{1,2,3}(f, v)`` + - Apply dirichlet boundary conditions with value ``v`` and given order + * - ``pnt.neumann_o{1,2,3}(f, v)`` + - Applies a gradient of ``v / dy`` boundary condition. + * - ``pnt.limitFree(f)`` + - Extrapolate into the boundary using only monotonic decreasing values. + ``f`` needs to be positive. + * - ``pnt.dir`` + - The direction of the boundary. + + + + Boundary regions ---------------- diff --git a/manual/sphinx/user_docs/bout_options.rst b/manual/sphinx/user_docs/bout_options.rst index 926c201c16..4a9c568e89 100644 --- a/manual/sphinx/user_docs/bout_options.rst +++ b/manual/sphinx/user_docs/bout_options.rst @@ -889,7 +889,7 @@ Fields can also be stored and written:: Options fields; fields["f2d"] = Field2D(1.0); fields["f3d"] = Field3D(2.0); - bout::OptionsIO::create("fields.nc").write(fields); + bout::OptionsIO::create("fields.nc")->write(fields); This allows the input settings and evolving variables to be combined into a single tree (see above on joining trees) and written diff --git a/src/field/field2d.cxx b/src/field/field2d.cxx index a046078585..c2d35162b3 100644 --- a/src/field/field2d.cxx +++ b/src/field/field2d.cxx @@ -25,8 +25,10 @@ * **************************************************************************/ +#include "bout/bout_types.hxx" #include "bout/build_defines.hxx" +#include "bout/unused.hxx" #include #include #include @@ -38,8 +40,11 @@ #include #include +#include +#include -Field2D::Field2D(Mesh* localmesh, CELL_LOC location_in, DirectionTypes directions_in) +Field2D::Field2D(Mesh* localmesh, CELL_LOC location_in, DirectionTypes directions_in, + std::optional UNUSED(regionID)) : Field(localmesh, location_in, directions_in) { if (fieldmesh) { diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index b3448238f5..e5d58b9029 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -25,12 +25,18 @@ * **************************************************************************/ +#include "bout/bout_types.hxx" #include "bout/build_defines.hxx" +#include "bout/index_derivs_interface.hxx" #include #include #include +#include +#include +#include +#include #include "bout/parallel_boundary_op.hxx" #include "bout/parallel_boundary_region.hxx" @@ -47,8 +53,9 @@ #include /// Constructor -Field3D::Field3D(Mesh* localmesh, CELL_LOC location_in, DirectionTypes directions_in) - : Field(localmesh, location_in, directions_in) { +Field3D::Field3D(Mesh* localmesh, CELL_LOC location_in, DirectionTypes directions_in, + std::optional regionID) + : Field(localmesh, location_in, directions_in), regionID{regionID} { #if BOUT_USE_TRACK name = ""; #endif @@ -64,7 +71,8 @@ Field3D::Field3D(Mesh* localmesh, CELL_LOC location_in, DirectionTypes direction /// later) Field3D::Field3D(const Field3D& f) : Field(f), data(f.data), yup_fields(f.yup_fields), ydown_fields(f.ydown_fields), - regionID(f.regionID) { + regionID(f.regionID), tracking_state(f.tracking_state), tracking(f.tracking), + selfname(f.selfname) { if (fieldmesh) { nx = fieldmesh->LocalNx; @@ -87,6 +95,22 @@ Field3D::Field3D(const BoutReal val, Mesh* localmesh) : Field3D(localmesh) { *this = val; } +Field3DParallel::Field3DParallel(const BoutReal val, Mesh* localmesh) + : Field3D(localmesh) { + + TRACE("Field3DParallel: Copy constructor from value"); + + *this = val; + if (this->isFci()) { + splitParallelSlices(); + for (size_t i = 0; i < numberParallelSlices(); ++i) { + yup(i) = val; + ydown(i) = val; + } + resetRegionParallel(); + } +} + Field3D::Field3D(Array data_in, Mesh* localmesh, CELL_LOC datalocation, DirectionTypes directions_in) : Field(localmesh, datalocation, directions_in), data(std::move(data_in)) { @@ -140,6 +164,15 @@ void Field3D::splitParallelSlices() { yup_fields.emplace_back(fieldmesh); ydown_fields.emplace_back(fieldmesh); } + resetRegionParallel(); +} +void Field3D::splitParallelSlicesAndAllocate() { + splitParallelSlices(); + allocate(); + for (int i = 0; i < fieldmesh->ystart; ++i) { + yup_fields[i].allocate(); + ydown_fields[i].allocate(); + } } void Field3D::clearParallelSlices() { @@ -235,6 +268,8 @@ Field3D& Field3D::operator=(const Field3D& rhs) { return (*this); // skip this assignment } + track(rhs, "operator="); + // Copy base slice Field::operator=(rhs); @@ -254,6 +289,7 @@ Field3D& Field3D::operator=(const Field3D& rhs) { } Field3D& Field3D::operator=(Field3D&& rhs) { + track(rhs, "operator="); // Move parallel slices or delete existing ones. yup_fields = std::move(rhs.yup_fields); @@ -274,6 +310,7 @@ Field3D& Field3D::operator=(Field3D&& rhs) { } Field3D& Field3D::operator=(const Field2D& rhs) { + track(rhs, "operator="); /// Check that the data is allocated ASSERT1(rhs.isAllocated()); @@ -318,6 +355,7 @@ void Field3D::operator=(const FieldPerp& rhs) { } Field3D& Field3D::operator=(const BoutReal val) { + track(val, "operator="); // Delete existing parallel slices. We don't copy parallel slices, so any // that currently exist will be incorrect. @@ -327,11 +365,37 @@ Field3D& Field3D::operator=(const BoutReal val) { allocate(); BOUT_FOR(i, getRegion("RGN_ALL")) { (*this)[i] = val; } + this->name = "BR"; + + return *this; +} + +Field3DParallel& Field3DParallel::operator=(const BoutReal val) { + TRACE("Field3DParallel = BoutReal"); + track(val, "operator="); + + if (isFci()) { + if (!hasParallelSlices()) { + splitParallelSlices(); + } + for (size_t i = 0; i < numberParallelSlices(); ++i) { + yup(i) = val; + ydown(i) = val; + } + } + resetRegion(); + resetRegionParallel(); + + allocate(); + + BOUT_FOR(i, getRegion("RGN_ALL")) { (*this)[i] = val; } + this->name = "BR"; return *this; } Field3D& Field3D::calcParallelSlices() { + ASSERT2(allowCalcParallelSlices); getCoordinates()->getParallelTransform().calcParallelSlices(*this); return *this; } @@ -430,14 +494,42 @@ void Field3D::applyTDerivBoundary() { } } -void Field3D::setBoundaryTo(const Field3D& f3d) { +void Field3D::setBoundaryTo(const Field3D& f3d, bool copyParallelSlices) { checkData(f3d); allocate(); // Make sure data allocated - /// Loop over boundary regions + if (isFci()) { + ASSERT1(f3d.hasParallelSlices()); + if (copyParallelSlices) { + splitParallelSlices(); + for (int i = 0; i < fieldmesh->ystart; ++i) { + yup(i) = f3d.yup(i); + ydown(i) = f3d.ydown(i); + } + } else { + // Set yup/ydown using midpoint values from f3d + ASSERT1(hasParallelSlices()); + + for (auto& region : fieldmesh->getBoundariesPar()) { + for (const auto& pnt : *region) { + // Interpolate midpoint value in f3d + const BoutReal val = pnt.interpolate_sheath_o2(f3d); + // Set the same boundary value in this field + pnt.dirichlet_o1(*this, val); + } + } + } + } + + // Non-FCI. + // Transform to field-aligned coordinates? + // Loop over boundary regions for (const auto& reg : fieldmesh->getBoundaries()) { + if (isFci() && reg->by != 0) { + continue; + } /// Loop within each region for (reg->first(); !reg->isDone(); reg->next()) { for (int z = 0; z < nz; z++) { @@ -468,6 +560,21 @@ void Field3D::applyParallelBoundary() { } } +void Field3D::applyParallelBoundaryWithDefault(const std::string& condition) { + + checkData(*this); + ASSERT1(hasParallelSlices()); + + // Apply boundary to this field + if (getBoundaryOpPars().empty()) { + applyParallelBoundary(condition); + } else { + for (const auto& bndry : getBoundaryOpPars()) { + bndry->apply(*this); + } + } +} + void Field3D::applyParallelBoundary(BoutReal t) { checkData(*this); @@ -818,6 +925,10 @@ void swap(Field3D& first, Field3D& second) noexcept { swap(first.deriv, second.deriv); swap(first.yup_fields, second.yup_fields); swap(first.ydown_fields, second.ydown_fields); + swap(first.regionID, second.regionID); + swap(first.tracking_state, second.tracking_state); + swap(first.tracking, second.tracking); + swap(first.selfname, second.selfname); } const Region& @@ -831,3 +942,90 @@ Field3D::getValidRegionWithDefault(const std::string& region_name) const { void Field3D::setRegion(const std::string& region_name) { regionID = fieldmesh->getRegionID(region_name); } + +void Field3D::resetRegion() { regionID.reset(); }; +void Field3D::resetRegionParallel() { + if (isFci()) { + for (int i = 0; i < fieldmesh->ystart; ++i) { + yup_fields[i].setRegion(fmt::format("RGN_YPAR_{:+d}", i + 1)); + ydown_fields[i].setRegion(fmt::format("RGN_YPAR_{:+d}", -i - 1)); + } + } +} +void Field3D::setRegion(size_t id) { regionID = id; }; +void Field3D::setRegion(std::optional id) { regionID = id; }; + +Field3D& Field3D::enableTracking(const std::string& name, Options& _tracking) { + tracking = &_tracking; + tracking_state = 1; + selfname = name; + return *this; +} + +template +Options* Field3D::doTrack(const T& change, std::string operation) { + const std::string outname{fmt::format("track_{:s}_{:d}", selfname, tracking_state++)}; + tracking->set(outname, change, "tracking"); + const std::string trace = cpptrace::generate_trace().to_string(); + // Workaround for bug in gcc9.4 +#if BOUT_USE_TRACK + const std::string changename = change.name; +#endif + (*tracking)[outname].setAttributes({ + {"operation", operation}, +#if BOUT_USE_TRACK + {"rhs.name2", changename}, +#endif + {"trace", trace}, + }); + return &(*tracking)[outname]; +} + +template Options* Field3D::track(const Field3DParallel&, std::string); +template Options* Field3D::track(const Field3D&, std::string); +template Options* Field3D::track(const Field2D&, std::string); +template Options* Field3D::track(const FieldPerp&, std::string); + +Options* Field3D::doTrack(const BoutReal& change, std::string operation) { + const std::string outname{fmt::format("track_{:s}_{:d}", selfname, tracking_state++)}; + tracking->set(outname, change, "tracking"); + const std::string trace = cpptrace::generate_trace().to_string(); + (*tracking)[outname].setAttributes({ + {"operation", operation}, + {"rhs.name2", "BR"}, + {"trace", trace}, + }); + return &(*tracking)[outname]; +} + +void Field3DParallel::ensureFieldAligned() { + if (isFci()) { + ASSERT2(hasParallelSlices()); + if (fieldmesh != nullptr) { + for (int i = 0; i < fieldmesh->ystart; ++i) { + ASSERT2(yup_fields[i].getRegionID().has_value()); + ASSERT2(ydown_fields[i].getRegionID().has_value()); + } + } + if (isAllocated()) { + for (int i = 0; i < fieldmesh->ystart; ++i) { + ASSERT2(yup_fields[i].isAllocated()); + ASSERT2(ydown_fields[i].isAllocated()); + } + } + } +} + +Field3DParallel& Field3DParallel::allocate() { + Field3D::allocate(); + if (isFci()) { + ASSERT2(hasParallelSlices()); + if (fieldmesh != nullptr) { + for (int i = 0; i < fieldmesh->ystart; ++i) { + yup_fields[i].allocate(); + ydown_fields[i].allocate(); + } + } + } + return *this; +} diff --git a/src/field/fieldperp.cxx b/src/field/fieldperp.cxx index d74a986473..ed98419fe1 100644 --- a/src/field/fieldperp.cxx +++ b/src/field/fieldperp.cxx @@ -23,10 +23,13 @@ * **************************************************************************/ +#include "bout/unused.hxx" #include #include #include +#include +#include #include #include @@ -34,7 +37,7 @@ #include FieldPerp::FieldPerp(Mesh* localmesh, CELL_LOC location_in, int yindex_in, - DirectionTypes directions) + DirectionTypes directions, std::optional UNUSED(regionID)) : Field(localmesh, location_in, directions), yindex(yindex_in) { if (fieldmesh) { nx = fieldmesh->LocalNx; diff --git a/src/field/gen_fieldops.jinja b/src/field/gen_fieldops.jinja index ecd4e628cc..64cc64c588 100644 --- a/src/field/gen_fieldops.jinja +++ b/src/field/gen_fieldops.jinja @@ -8,15 +8,30 @@ checkData({{lhs.name}}); checkData({{rhs.name}}); - {% if out == "Field3D" %} - {% if lhs == rhs == "Field3D" %} + {% if out.region_type == "3D" %} + {% if lhs.region_type == rhs.region_type == "3D" %} {{out.name}}.setRegion({{lhs.name}}.getMesh()->getCommonRegion({{lhs.name}}.getRegionID(), {{rhs.name}}.getRegionID())); - {% elif lhs == "Field3D" %} + {% elif lhs.region_type == "3D" %} {{out.name}}.setRegion({{lhs.name}}.getRegionID()); - {% elif rhs == "Field3D" %} + {% elif rhs.region_type == "3D" %} {{out.name}}.setRegion({{rhs.name}}.getRegionID()); {% endif %} + {% if out == "Field3DParallel" %} + if ({{out.name}}.isFci()) { + {{ lhs.assertParallelSlices }} + {{ rhs.assertParallelSlices }} + {{out.name}}.splitParallelSlices(); + {% if lhs.region_type == "3D" %} + for (size_t i{0} ; i < {{lhs.name}}.numberParallelSlices() ; ++i) { + {% else %} + for (size_t i{0} ; i < {{rhs.name}}.numberParallelSlices() ; ++i) { + {% endif %} + {{out.name}}.yup(i) = {{lhs.yup}} {{operator}} {{rhs.yup}}; + {{out.name}}.ydown(i) = {{lhs.ydown}} {{operator}} {{rhs.ydown}}; + } + } + {% endif %} {% endif %} {% if (out == "Field3D") and ((lhs == "Field2D") or (rhs =="Field2D")) %} @@ -61,31 +76,121 @@ } {% endif %} +#if BOUT_USE_TRACK + {{out.name}}.name = fmt::format("{:s} {{operator}} {:s}", {{'"BR"' if lhs == "BoutReal" else lhs.name + ".name"}} + , {{'"BR"' if rhs == "BoutReal" else rhs.name + ".name"}}); +#endif checkData({{out.name}}); return {{out.name}}; } +{% if out.field_type == lhs.field_type and lhs == "Field3D" %} +// 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 + // otherwise just call the non-inplace version + {% if lhs != "BoutReal" and rhs != "BoutReal" %} + ASSERT1_FIELDS_COMPATIBLE(*this, rhs); + {% endif %} + + // Delete existing parallel slices. We don't update parallel slices, so any + // that currently exist will be incorrect. + clearParallelSlices(); + checkData(*this); + checkData({{rhs.name}}); + + {% if lhs.region_type == rhs.region_type == "3D" %} + regionID = fieldmesh->getCommonRegion(regionID, {{rhs.name}}.getRegionID()); + {% endif %} + + + {% if (lhs == "Field3D") and (rhs =="Field2D") %} + {{region_loop}}({{index_var}}, {{rhs.name}}.getRegion({{region_name}})) { + const auto {{mixed_base_ind}} = fieldmesh->ind2Dto3D({{index_var}}); + {% if (operator == "/") and (rhs == "Field2D") %} + const auto tmp = 1.0 / {{rhs.mixed_index}}; + for (int {{jz_var}} = 0; {{jz_var}} < fieldmesh->LocalNz; ++{{jz_var}}){ + (*this)[{{mixed_base_ind}} + {{jz_var}}] *= tmp; + {% else %} + for (int {{jz_var}} = 0; {{jz_var}} < fieldmesh->LocalNz; ++{{jz_var}}){ + (*this)[{{mixed_base_ind}} + {{jz_var}}] {{operator}}= {{rhs.index}}; + {% endif %} + } + } + {% elif rhs == "FieldPerp" and (lhs == "Field3D" or lhs == "Field2D")%} + Mesh *localmesh = this->getMesh(); + + {{region_loop}}({{index_var}}, {{rhs.name}}.getRegion({{region_name}})) { + int yind = {{rhs.name}}.getIndex(); + const auto {{mixed_base_ind}} = localmesh->indPerpto3D({{index_var}}, yind); + (*this)[{{base_ind_var}}] {{operator}}= {{rhs.index}}; + } + {% elif (operator == "/") and (lhs == "Field3D" or lhs == "Field2D") and (rhs =="BoutReal") %} + const auto tmp = 1.0 / {{rhs.index}}; + {{region_loop}}({{index_var}}, this->getRegion({{region_name}})) { + (*this)[{{index_var}}] *= tmp; + } + {% else %} + {{region_loop}}({{index_var}}, this->getRegion({{region_name}})) { + (*this)[{{index_var}}] {{operator}}= {{rhs.index}}; + } + {% endif %} + + {% if lhs.region_type == "3D" %} + track(rhs, "operator{{operator}}="); + {% endif %} +#if BOUT_USE_TRACK + name = fmt::format("{:s} {{operator}}= {:s}", this->name, {{'"BR"' if rhs == "BoutReal" else rhs.name + ".name"}}); +#endif + + checkData(*this); + + return *this; +} +{% endif %} + + {% if out.field_type == lhs.field_type %} // 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 // otherwise just call the non-inplace version +{% if lhs == "Field3DParallel" %} + if (data.unique() or isRef) { +{% else %} if (data.unique()) { +{% endif %} {% if lhs != "BoutReal" and rhs != "BoutReal" %} ASSERT1_FIELDS_COMPATIBLE(*this, rhs); {% endif %} - {% if (lhs == "Field3D") %} - // Delete existing parallel slices. We don't copy parallel slices, so any + {% if lhs == "Field3D" %} + // Delete existing parallel slices. We don't update parallel slices, so any // that currently exist will be incorrect. clearParallelSlices(); - + {% endif %} + {% if lhs == "Field3DParallel" and (rhs.region_type == "3D" or rhs == "BoutReal") %} + if (this->isFci()) { + if (isRef) { + for (size_t i{0} ; i < yup_fields.size() ; ++i) { + yup(i).update_{{operator_name}}_inplace({{rhs.name}}{% if rhs == "Field3D" %}.yup(i){% endif %}); + ydown(i).update_{{operator_name}}_inplace({{rhs.name}}{% if rhs == "Field3D" %}.ydown(i){% endif %}); + } + } else { + for (size_t i{0} ; i < yup_fields.size() ; ++i) { + yup(i) {{operator}}= {{rhs.name}}{% if rhs == "Field3D" %}.yup(i){% endif %}; + ydown(i) {{operator}}= {{rhs.name}}{% if rhs == "Field3D" %}.ydown(i){% endif %}; + } + } + } else { + clearParallelSlices(); + } {% endif %} checkData(*this); checkData({{rhs.name}}); - {% if lhs == rhs == "Field3D" %} - regionID = fieldmesh->getCommonRegion(regionID, {{rhs.name}}.regionID); + {% if lhs.region_type == rhs.region_type == "3D" %} + regionID = fieldmesh->getCommonRegion(regionID, {{rhs.name}}.getRegionID()); {% endif %} @@ -129,9 +234,19 @@ } {% endif %} + {% if lhs.region_type == "3D" %} + track(rhs, "operator{{operator}}="); + {% endif %} +#if BOUT_USE_TRACK + name = fmt::format("{:s} {{operator}}= {:s}", this->name, {{'"BR"' if rhs == "BoutReal" else rhs.name + ".name"}}); +#endif + checkData(*this); } else { + {% if lhs.region_type == "3D" %} + track(rhs, "operator{{operator}}="); + {% endif %} (*this) = (*this) {{operator}} {{rhs.name}}; } return *this; diff --git a/src/field/gen_fieldops.py b/src/field/gen_fieldops.py index 3e07d6fec4..92325c5243 100755 --- a/src/field/gen_fieldops.py +++ b/src/field/gen_fieldops.py @@ -53,15 +53,13 @@ def smart_open(filename, mode="r"): # The arthimetic operators -# OrderedDict to (try to) ensure consistency between python 2 & 3 -operators = OrderedDict( - [ - ("*", "multiplication"), - ("/", "division"), - ("+", "addition"), - ("-", "subtraction"), - ] -) +operators = { + "*": "multiplication", + "/": "division", + "+": "addition", + "-": "subtraction", +} + header = """// This file is autogenerated - see gen_fieldops.py #include @@ -70,6 +68,7 @@ def smart_open(filename, mode="r"): #include #include #include +#include """ @@ -103,7 +102,7 @@ def __init__( self.mixed_base_ind_var = mixed_base_ind_var # Note region_type isn't actually used currently but # may be useful in future. - if self.field_type == "Field3D": + if "Field3D" in self.field_type: self.region_type = "3D" elif self.field_type == "Field2D": self.region_type = "2D" @@ -157,6 +156,28 @@ def base_index(self): else: return "{self.name}[{self.mixed_base_ind_var}]".format(self=self) + @property + def yup(self): + """Returns {{name}}.yup(i) if it is a field with parallel slices. + If it is BoutReal just {{name}}""" + if self.field_type == "BoutReal": + return "{self.name}".format(self=self) + return "{self.name}.yup(i)".format(self=self) + + @property + def ydown(self): + """Returns {{name}}.ydown(i) if it is a field with parallel slices. + If it is BoutReal just {{name}}""" + if self.field_type == "BoutReal": + return "{self.name}".format(self=self) + return "{self.name}.ydown(i)".format(self=self) + + @property + def assertParallelSlices(self): + if self.field_type == "BoutReal": + return "" + return f"ASSERT2({self.name}.hasParallelSlices());" + def __eq__(self, other): try: return self.field_type == other.field_type @@ -183,6 +204,8 @@ def returnType(f1, f2): return copy(f1) elif f1 == "FieldPerp" or f2 == "FieldPerp": return copy(fieldPerp) + elif f1 == "Field3DParallel" or f2 == "Field3DParallel": + return copy(field3DPar) else: return copy(field3D) @@ -218,7 +241,6 @@ def returnType(f1, f2): region_loop = "BOUT_FOR" # Declare what fields we currently support: - # Field perp is currently missing field3D = Field( "Field3D", ["x", "y", "z"], @@ -226,6 +248,13 @@ def returnType(f1, f2): jz_var=jz_var, mixed_base_ind_var=mixed_base_ind_var, ) + field3DPar = Field( + "Field3DParallel", + ["x", "y", "z"], + index_var=index_var, + jz_var=jz_var, + mixed_base_ind_var=mixed_base_ind_var, + ) field2D = Field( "Field2D", ["x", "y"], @@ -248,7 +277,8 @@ def returnType(f1, f2): mixed_base_ind_var=mixed_base_ind_var, ) - fields = [field3D, field2D, fieldPerp, boutreal] + fields = (field3D, field2D, fieldPerp, boutreal) + fields2 = (field3D, field3DPar, boutreal) with smart_open(args.filename, "w") as f: f.write(header) @@ -258,10 +288,16 @@ def returnType(f1, f2): template = env.get_template("gen_fieldops.jinja") - for lhs, rhs in itertools.product(fields, fields): - # We don't have to define BoutReal BoutReal operations - if lhs == rhs == "BoutReal": + # We don't have to define BoutReal BoutReal operations + done = [(boutreal, boutreal)] + for lhs, rhs in itertools.chain( + itertools.product(fields, fields), + itertools.product(fields2, fields2), + ): + if (lhs, rhs) in done: continue + done.append((lhs, rhs)) + rhs = copy(rhs) lhs = copy(lhs) diff --git a/src/field/generated_fieldops.cxx b/src/field/generated_fieldops.cxx index 6b778acee3..5c08c70173 100644 --- a/src/field/generated_fieldops.cxx +++ b/src/field/generated_fieldops.cxx @@ -1,10 +1,14 @@ // This file is autogenerated - see gen_fieldops.py +#include "fmt/format.h" +#include "bout/assert.hxx" +#include "bout/build_defines.hxx" #include #include #include #include #include #include +#include // Provide the C++ wrapper for multiplication of Field3D and Field3D Field3D operator*(const Field3D& lhs, const Field3D& rhs) { @@ -20,10 +24,39 @@ Field3D operator*(const Field3D& lhs, const Field3D& rhs) { result[index] = lhs[index] * rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } +// 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 + // otherwise just call the non-inplace version + ASSERT1_FIELDS_COMPATIBLE(*this, rhs); + + // Delete existing parallel slices. We don't update parallel slices, so any + // that currently exist will be incorrect. + clearParallelSlices(); + checkData(*this); + checkData(rhs); + + regionID = fieldmesh->getCommonRegion(regionID, rhs.getRegionID()); + + BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] *= rhs[index]; } + + track(rhs, "operator*="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} *= {:s}", this->name, rhs.name); +#endif + + checkData(*this); + + return *this; +} + // Provide the C++ operator to update Field3D by multiplication with Field3D Field3D& Field3D::operator*=(const Field3D& rhs) { // only if data is unique we update the field @@ -31,20 +64,25 @@ Field3D& Field3D::operator*=(const Field3D& rhs) { if (data.unique()) { ASSERT1_FIELDS_COMPATIBLE(*this, rhs); - // Delete existing parallel slices. We don't copy parallel slices, so any + // Delete existing parallel slices. We don't update parallel slices, so any // that currently exist will be incorrect. clearParallelSlices(); - checkData(*this); checkData(rhs); - regionID = fieldmesh->getCommonRegion(regionID, rhs.regionID); + regionID = fieldmesh->getCommonRegion(regionID, rhs.getRegionID()); BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] *= rhs[index]; } + track(rhs, "operator*="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} *= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { + track(rhs, "operator*="); (*this) = (*this) * rhs; } return *this; @@ -64,10 +102,39 @@ Field3D operator/(const Field3D& lhs, const Field3D& rhs) { result[index] = lhs[index] / rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } +// 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 + // otherwise just call the non-inplace version + ASSERT1_FIELDS_COMPATIBLE(*this, rhs); + + // Delete existing parallel slices. We don't update parallel slices, so any + // that currently exist will be incorrect. + clearParallelSlices(); + checkData(*this); + checkData(rhs); + + regionID = fieldmesh->getCommonRegion(regionID, rhs.getRegionID()); + + BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] /= rhs[index]; } + + track(rhs, "operator/="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} /= {:s}", this->name, rhs.name); +#endif + + checkData(*this); + + return *this; +} + // Provide the C++ operator to update Field3D by division with Field3D Field3D& Field3D::operator/=(const Field3D& rhs) { // only if data is unique we update the field @@ -75,20 +142,25 @@ Field3D& Field3D::operator/=(const Field3D& rhs) { if (data.unique()) { ASSERT1_FIELDS_COMPATIBLE(*this, rhs); - // Delete existing parallel slices. We don't copy parallel slices, so any + // Delete existing parallel slices. We don't update parallel slices, so any // that currently exist will be incorrect. clearParallelSlices(); - checkData(*this); checkData(rhs); - regionID = fieldmesh->getCommonRegion(regionID, rhs.regionID); + regionID = fieldmesh->getCommonRegion(regionID, rhs.getRegionID()); BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] /= rhs[index]; } + track(rhs, "operator/="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} /= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { + track(rhs, "operator/="); (*this) = (*this) / rhs; } return *this; @@ -108,10 +180,39 @@ Field3D operator+(const Field3D& lhs, const Field3D& rhs) { result[index] = lhs[index] + rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } +// 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 + // otherwise just call the non-inplace version + ASSERT1_FIELDS_COMPATIBLE(*this, rhs); + + // Delete existing parallel slices. We don't update parallel slices, so any + // that currently exist will be incorrect. + clearParallelSlices(); + checkData(*this); + checkData(rhs); + + regionID = fieldmesh->getCommonRegion(regionID, rhs.getRegionID()); + + BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] += rhs[index]; } + + track(rhs, "operator+="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} += {:s}", this->name, rhs.name); +#endif + + checkData(*this); + + return *this; +} + // Provide the C++ operator to update Field3D by addition with Field3D Field3D& Field3D::operator+=(const Field3D& rhs) { // only if data is unique we update the field @@ -119,20 +220,25 @@ Field3D& Field3D::operator+=(const Field3D& rhs) { if (data.unique()) { ASSERT1_FIELDS_COMPATIBLE(*this, rhs); - // Delete existing parallel slices. We don't copy parallel slices, so any + // Delete existing parallel slices. We don't update parallel slices, so any // that currently exist will be incorrect. clearParallelSlices(); - checkData(*this); checkData(rhs); - regionID = fieldmesh->getCommonRegion(regionID, rhs.regionID); + regionID = fieldmesh->getCommonRegion(regionID, rhs.getRegionID()); BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] += rhs[index]; } + track(rhs, "operator+="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} += {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { + track(rhs, "operator+="); (*this) = (*this) + rhs; } return *this; @@ -152,10 +258,39 @@ Field3D operator-(const Field3D& lhs, const Field3D& rhs) { result[index] = lhs[index] - rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } +// 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 + // otherwise just call the non-inplace version + ASSERT1_FIELDS_COMPATIBLE(*this, rhs); + + // Delete existing parallel slices. We don't update parallel slices, so any + // that currently exist will be incorrect. + clearParallelSlices(); + checkData(*this); + checkData(rhs); + + regionID = fieldmesh->getCommonRegion(regionID, rhs.getRegionID()); + + BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] -= rhs[index]; } + + track(rhs, "operator-="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} -= {:s}", this->name, rhs.name); +#endif + + checkData(*this); + + return *this; +} + // Provide the C++ operator to update Field3D by subtraction with Field3D Field3D& Field3D::operator-=(const Field3D& rhs) { // only if data is unique we update the field @@ -163,20 +298,25 @@ Field3D& Field3D::operator-=(const Field3D& rhs) { if (data.unique()) { ASSERT1_FIELDS_COMPATIBLE(*this, rhs); - // Delete existing parallel slices. We don't copy parallel slices, so any + // Delete existing parallel slices. We don't update parallel slices, so any // that currently exist will be incorrect. clearParallelSlices(); - checkData(*this); checkData(rhs); - regionID = fieldmesh->getCommonRegion(regionID, rhs.regionID); + regionID = fieldmesh->getCommonRegion(regionID, rhs.getRegionID()); BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] -= rhs[index]; } + track(rhs, "operator-="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} -= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { + track(rhs, "operator-="); (*this) = (*this) - rhs; } return *this; @@ -201,10 +341,42 @@ Field3D operator*(const Field3D& lhs, const Field2D& rhs) { } } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } +// 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 + // otherwise just call the non-inplace version + ASSERT1_FIELDS_COMPATIBLE(*this, rhs); + + // Delete existing parallel slices. We don't update parallel slices, so any + // that currently exist will be incorrect. + clearParallelSlices(); + checkData(*this); + checkData(rhs); + + BOUT_FOR(index, rhs.getRegion("RGN_ALL")) { + const auto base_ind = fieldmesh->ind2Dto3D(index); + for (int jz = 0; jz < fieldmesh->LocalNz; ++jz) { + (*this)[base_ind + jz] *= rhs[index]; + } + } + + track(rhs, "operator*="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} *= {:s}", this->name, rhs.name); +#endif + + checkData(*this); + + return *this; +} + // Provide the C++ operator to update Field3D by multiplication with Field2D Field3D& Field3D::operator*=(const Field2D& rhs) { // only if data is unique we update the field @@ -212,10 +384,9 @@ Field3D& Field3D::operator*=(const Field2D& rhs) { if (data.unique()) { ASSERT1_FIELDS_COMPATIBLE(*this, rhs); - // Delete existing parallel slices. We don't copy parallel slices, so any + // Delete existing parallel slices. We don't update parallel slices, so any // that currently exist will be incorrect. clearParallelSlices(); - checkData(*this); checkData(rhs); @@ -226,9 +397,15 @@ Field3D& Field3D::operator*=(const Field2D& rhs) { } } + track(rhs, "operator*="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} *= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { + track(rhs, "operator*="); (*this) = (*this) * rhs; } return *this; @@ -254,10 +431,43 @@ Field3D operator/(const Field3D& lhs, const Field2D& rhs) { } } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } +// 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 + // otherwise just call the non-inplace version + ASSERT1_FIELDS_COMPATIBLE(*this, rhs); + + // Delete existing parallel slices. We don't update parallel slices, so any + // that currently exist will be incorrect. + clearParallelSlices(); + checkData(*this); + checkData(rhs); + + BOUT_FOR(index, rhs.getRegion("RGN_ALL")) { + const auto base_ind = fieldmesh->ind2Dto3D(index); + const auto tmp = 1.0 / rhs[index]; + for (int jz = 0; jz < fieldmesh->LocalNz; ++jz) { + (*this)[base_ind + jz] *= tmp; + } + } + + track(rhs, "operator/="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} /= {:s}", this->name, rhs.name); +#endif + + checkData(*this); + + return *this; +} + // Provide the C++ operator to update Field3D by division with Field2D Field3D& Field3D::operator/=(const Field2D& rhs) { // only if data is unique we update the field @@ -265,10 +475,9 @@ Field3D& Field3D::operator/=(const Field2D& rhs) { if (data.unique()) { ASSERT1_FIELDS_COMPATIBLE(*this, rhs); - // Delete existing parallel slices. We don't copy parallel slices, so any + // Delete existing parallel slices. We don't update parallel slices, so any // that currently exist will be incorrect. clearParallelSlices(); - checkData(*this); checkData(rhs); @@ -280,9 +489,15 @@ Field3D& Field3D::operator/=(const Field2D& rhs) { } } + track(rhs, "operator/="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} /= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { + track(rhs, "operator/="); (*this) = (*this) / rhs; } return *this; @@ -307,10 +522,42 @@ Field3D operator+(const Field3D& lhs, const Field2D& rhs) { } } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } +// 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 + // otherwise just call the non-inplace version + ASSERT1_FIELDS_COMPATIBLE(*this, rhs); + + // Delete existing parallel slices. We don't update parallel slices, so any + // that currently exist will be incorrect. + clearParallelSlices(); + checkData(*this); + checkData(rhs); + + BOUT_FOR(index, rhs.getRegion("RGN_ALL")) { + const auto base_ind = fieldmesh->ind2Dto3D(index); + for (int jz = 0; jz < fieldmesh->LocalNz; ++jz) { + (*this)[base_ind + jz] += rhs[index]; + } + } + + track(rhs, "operator+="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} += {:s}", this->name, rhs.name); +#endif + + checkData(*this); + + return *this; +} + // Provide the C++ operator to update Field3D by addition with Field2D Field3D& Field3D::operator+=(const Field2D& rhs) { // only if data is unique we update the field @@ -318,10 +565,9 @@ Field3D& Field3D::operator+=(const Field2D& rhs) { if (data.unique()) { ASSERT1_FIELDS_COMPATIBLE(*this, rhs); - // Delete existing parallel slices. We don't copy parallel slices, so any + // Delete existing parallel slices. We don't update parallel slices, so any // that currently exist will be incorrect. clearParallelSlices(); - checkData(*this); checkData(rhs); @@ -332,9 +578,15 @@ Field3D& Field3D::operator+=(const Field2D& rhs) { } } + track(rhs, "operator+="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} += {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { + track(rhs, "operator+="); (*this) = (*this) + rhs; } return *this; @@ -359,10 +611,42 @@ Field3D operator-(const Field3D& lhs, const Field2D& rhs) { } } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } +// 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 + // otherwise just call the non-inplace version + ASSERT1_FIELDS_COMPATIBLE(*this, rhs); + + // Delete existing parallel slices. We don't update parallel slices, so any + // that currently exist will be incorrect. + clearParallelSlices(); + checkData(*this); + checkData(rhs); + + BOUT_FOR(index, rhs.getRegion("RGN_ALL")) { + const auto base_ind = fieldmesh->ind2Dto3D(index); + for (int jz = 0; jz < fieldmesh->LocalNz; ++jz) { + (*this)[base_ind + jz] -= rhs[index]; + } + } + + track(rhs, "operator-="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} -= {:s}", this->name, rhs.name); +#endif + + checkData(*this); + + return *this; +} + // Provide the C++ operator to update Field3D by subtraction with Field2D Field3D& Field3D::operator-=(const Field2D& rhs) { // only if data is unique we update the field @@ -370,10 +654,9 @@ Field3D& Field3D::operator-=(const Field2D& rhs) { if (data.unique()) { ASSERT1_FIELDS_COMPATIBLE(*this, rhs); - // Delete existing parallel slices. We don't copy parallel slices, so any + // Delete existing parallel slices. We don't update parallel slices, so any // that currently exist will be incorrect. clearParallelSlices(); - checkData(*this); checkData(rhs); @@ -384,9 +667,15 @@ Field3D& Field3D::operator-=(const Field2D& rhs) { } } + track(rhs, "operator-="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} -= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { + track(rhs, "operator-="); (*this) = (*this) - rhs; } return *this; @@ -408,6 +697,9 @@ FieldPerp operator*(const Field3D& lhs, const FieldPerp& rhs) { result[index] = lhs[base_ind] * rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -428,6 +720,9 @@ FieldPerp operator/(const Field3D& lhs, const FieldPerp& rhs) { result[index] = lhs[base_ind] / rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -448,6 +743,9 @@ FieldPerp operator+(const Field3D& lhs, const FieldPerp& rhs) { result[index] = lhs[base_ind] + rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -468,6 +766,9 @@ FieldPerp operator-(const Field3D& lhs, const FieldPerp& rhs) { result[index] = lhs[base_ind] - rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -485,28 +786,59 @@ Field3D operator*(const Field3D& lhs, const BoutReal rhs) { result[index] = lhs[index] * rhs; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", lhs.name, "BR"); +#endif checkData(result); return result; } +// 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 + // otherwise just call the non-inplace version + + // Delete existing parallel slices. We don't update parallel slices, so any + // that currently exist will be incorrect. + clearParallelSlices(); + checkData(*this); + checkData(rhs); + + BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] *= rhs; } + + track(rhs, "operator*="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} *= {:s}", this->name, "BR"); +#endif + + checkData(*this); + + return *this; +} + // Provide the C++ operator to update Field3D by multiplication with BoutReal Field3D& Field3D::operator*=(const BoutReal rhs) { // only if data is unique we update the field // otherwise just call the non-inplace version if (data.unique()) { - // Delete existing parallel slices. We don't copy parallel slices, so any + // Delete existing parallel slices. We don't update parallel slices, so any // that currently exist will be incorrect. clearParallelSlices(); - checkData(*this); checkData(rhs); BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] *= rhs; } + track(rhs, "operator*="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} *= {:s}", this->name, "BR"); +#endif + checkData(*this); } else { + track(rhs, "operator*="); (*this) = (*this) * rhs; } return *this; @@ -526,29 +858,61 @@ Field3D operator/(const Field3D& lhs, const BoutReal rhs) { result[index] = lhs[index] * tmp; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", lhs.name, "BR"); +#endif checkData(result); return result; } +// 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 + // otherwise just call the non-inplace version + + // Delete existing parallel slices. We don't update parallel slices, so any + // that currently exist will be incorrect. + clearParallelSlices(); + checkData(*this); + checkData(rhs); + + const auto tmp = 1.0 / rhs; + BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] *= tmp; } + + track(rhs, "operator/="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} /= {:s}", this->name, "BR"); +#endif + + checkData(*this); + + return *this; +} + // Provide the C++ operator to update Field3D by division with BoutReal Field3D& Field3D::operator/=(const BoutReal rhs) { // only if data is unique we update the field // otherwise just call the non-inplace version if (data.unique()) { - // Delete existing parallel slices. We don't copy parallel slices, so any + // Delete existing parallel slices. We don't update parallel slices, so any // that currently exist will be incorrect. clearParallelSlices(); - checkData(*this); checkData(rhs); const auto tmp = 1.0 / rhs; BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] *= tmp; } + track(rhs, "operator/="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} /= {:s}", this->name, "BR"); +#endif + checkData(*this); } else { + track(rhs, "operator/="); (*this) = (*this) / rhs; } return *this; @@ -567,28 +931,59 @@ Field3D operator+(const Field3D& lhs, const BoutReal rhs) { result[index] = lhs[index] + rhs; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", lhs.name, "BR"); +#endif checkData(result); return result; } +// 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 + // otherwise just call the non-inplace version + + // Delete existing parallel slices. We don't update parallel slices, so any + // that currently exist will be incorrect. + clearParallelSlices(); + checkData(*this); + checkData(rhs); + + BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] += rhs; } + + track(rhs, "operator+="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} += {:s}", this->name, "BR"); +#endif + + checkData(*this); + + return *this; +} + // Provide the C++ operator to update Field3D by addition with BoutReal Field3D& Field3D::operator+=(const BoutReal rhs) { // only if data is unique we update the field // otherwise just call the non-inplace version if (data.unique()) { - // Delete existing parallel slices. We don't copy parallel slices, so any + // Delete existing parallel slices. We don't update parallel slices, so any // that currently exist will be incorrect. clearParallelSlices(); - checkData(*this); checkData(rhs); BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] += rhs; } + track(rhs, "operator+="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} += {:s}", this->name, "BR"); +#endif + checkData(*this); } else { + track(rhs, "operator+="); (*this) = (*this) + rhs; } return *this; @@ -607,28 +1002,59 @@ Field3D operator-(const Field3D& lhs, const BoutReal rhs) { result[index] = lhs[index] - rhs; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", lhs.name, "BR"); +#endif checkData(result); return result; } +// 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 + // otherwise just call the non-inplace version + + // Delete existing parallel slices. We don't update parallel slices, so any + // that currently exist will be incorrect. + clearParallelSlices(); + checkData(*this); + checkData(rhs); + + BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] -= rhs; } + + track(rhs, "operator-="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} -= {:s}", this->name, "BR"); +#endif + + checkData(*this); + + return *this; +} + // Provide the C++ operator to update Field3D by subtraction with BoutReal Field3D& Field3D::operator-=(const BoutReal rhs) { // only if data is unique we update the field // otherwise just call the non-inplace version if (data.unique()) { - // Delete existing parallel slices. We don't copy parallel slices, so any + // Delete existing parallel slices. We don't update parallel slices, so any // that currently exist will be incorrect. clearParallelSlices(); - checkData(*this); checkData(rhs); BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] -= rhs; } + track(rhs, "operator-="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} -= {:s}", this->name, "BR"); +#endif + checkData(*this); } else { + track(rhs, "operator-="); (*this) = (*this) - rhs; } return *this; @@ -653,6 +1079,9 @@ Field3D operator*(const Field2D& lhs, const Field3D& rhs) { } } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -676,6 +1105,9 @@ Field3D operator/(const Field2D& lhs, const Field3D& rhs) { } } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -699,6 +1131,9 @@ Field3D operator+(const Field2D& lhs, const Field3D& rhs) { } } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -722,6 +1157,9 @@ Field3D operator-(const Field2D& lhs, const Field3D& rhs) { } } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -738,6 +1176,9 @@ Field2D operator*(const Field2D& lhs, const Field2D& rhs) { result[index] = lhs[index] * rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -754,6 +1195,10 @@ Field2D& Field2D::operator*=(const Field2D& rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] *= rhs[index]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} *= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -774,6 +1219,9 @@ Field2D operator/(const Field2D& lhs, const Field2D& rhs) { result[index] = lhs[index] / rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -790,6 +1238,10 @@ Field2D& Field2D::operator/=(const Field2D& rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] /= rhs[index]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} /= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -810,6 +1262,9 @@ Field2D operator+(const Field2D& lhs, const Field2D& rhs) { result[index] = lhs[index] + rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -826,6 +1281,10 @@ Field2D& Field2D::operator+=(const Field2D& rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] += rhs[index]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} += {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -846,6 +1305,9 @@ Field2D operator-(const Field2D& lhs, const Field2D& rhs) { result[index] = lhs[index] - rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -862,6 +1324,10 @@ Field2D& Field2D::operator-=(const Field2D& rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] -= rhs[index]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} -= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -886,6 +1352,9 @@ FieldPerp operator*(const Field2D& lhs, const FieldPerp& rhs) { result[index] = lhs[base_ind] * rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -906,6 +1375,9 @@ FieldPerp operator/(const Field2D& lhs, const FieldPerp& rhs) { result[index] = lhs[base_ind] / rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -926,6 +1398,9 @@ FieldPerp operator+(const Field2D& lhs, const FieldPerp& rhs) { result[index] = lhs[base_ind] + rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -946,6 +1421,9 @@ FieldPerp operator-(const Field2D& lhs, const FieldPerp& rhs) { result[index] = lhs[base_ind] - rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -961,6 +1439,9 @@ Field2D operator*(const Field2D& lhs, const BoutReal rhs) { result[index] = lhs[index] * rhs; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", lhs.name, "BR"); +#endif checkData(result); return result; } @@ -976,6 +1457,10 @@ Field2D& Field2D::operator*=(const BoutReal rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] *= rhs; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} *= {:s}", this->name, "BR"); +#endif + checkData(*this); } else { @@ -996,6 +1481,9 @@ Field2D operator/(const Field2D& lhs, const BoutReal rhs) { result[index] = lhs[index] * tmp; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", lhs.name, "BR"); +#endif checkData(result); return result; } @@ -1012,6 +1500,10 @@ Field2D& Field2D::operator/=(const BoutReal rhs) { const auto tmp = 1.0 / rhs; BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] *= tmp; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} /= {:s}", this->name, "BR"); +#endif + checkData(*this); } else { @@ -1031,6 +1523,9 @@ Field2D operator+(const Field2D& lhs, const BoutReal rhs) { result[index] = lhs[index] + rhs; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", lhs.name, "BR"); +#endif checkData(result); return result; } @@ -1046,6 +1541,10 @@ Field2D& Field2D::operator+=(const BoutReal rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] += rhs; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} += {:s}", this->name, "BR"); +#endif + checkData(*this); } else { @@ -1065,6 +1564,9 @@ Field2D operator-(const Field2D& lhs, const BoutReal rhs) { result[index] = lhs[index] - rhs; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", lhs.name, "BR"); +#endif checkData(result); return result; } @@ -1080,6 +1582,10 @@ Field2D& Field2D::operator-=(const BoutReal rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] -= rhs; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} -= {:s}", this->name, "BR"); +#endif + checkData(*this); } else { @@ -1104,6 +1610,9 @@ FieldPerp operator*(const FieldPerp& lhs, const Field3D& rhs) { result[index] = lhs[index] * rhs[base_ind]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -1126,6 +1635,10 @@ FieldPerp& FieldPerp::operator*=(const Field3D& rhs) { (*this)[index] *= rhs[base_ind]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} *= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -1150,6 +1663,9 @@ FieldPerp operator/(const FieldPerp& lhs, const Field3D& rhs) { result[index] = lhs[index] / rhs[base_ind]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -1172,6 +1688,10 @@ FieldPerp& FieldPerp::operator/=(const Field3D& rhs) { (*this)[index] /= rhs[base_ind]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} /= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -1196,6 +1716,9 @@ FieldPerp operator+(const FieldPerp& lhs, const Field3D& rhs) { result[index] = lhs[index] + rhs[base_ind]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -1218,6 +1741,10 @@ FieldPerp& FieldPerp::operator+=(const Field3D& rhs) { (*this)[index] += rhs[base_ind]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} += {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -1242,6 +1769,9 @@ FieldPerp operator-(const FieldPerp& lhs, const Field3D& rhs) { result[index] = lhs[index] - rhs[base_ind]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -1264,6 +1794,10 @@ FieldPerp& FieldPerp::operator-=(const Field3D& rhs) { (*this)[index] -= rhs[base_ind]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} -= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -1288,6 +1822,9 @@ FieldPerp operator*(const FieldPerp& lhs, const Field2D& rhs) { result[index] = lhs[index] * rhs[base_ind]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -1310,6 +1847,10 @@ FieldPerp& FieldPerp::operator*=(const Field2D& rhs) { (*this)[index] *= rhs[base_ind]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} *= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -1334,6 +1875,9 @@ FieldPerp operator/(const FieldPerp& lhs, const Field2D& rhs) { result[index] = lhs[index] / rhs[base_ind]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -1356,6 +1900,10 @@ FieldPerp& FieldPerp::operator/=(const Field2D& rhs) { (*this)[index] /= rhs[base_ind]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} /= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -1380,6 +1928,9 @@ FieldPerp operator+(const FieldPerp& lhs, const Field2D& rhs) { result[index] = lhs[index] + rhs[base_ind]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -1402,6 +1953,10 @@ FieldPerp& FieldPerp::operator+=(const Field2D& rhs) { (*this)[index] += rhs[base_ind]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} += {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -1426,6 +1981,9 @@ FieldPerp operator-(const FieldPerp& lhs, const Field2D& rhs) { result[index] = lhs[index] - rhs[base_ind]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -1448,6 +2006,10 @@ FieldPerp& FieldPerp::operator-=(const Field2D& rhs) { (*this)[index] -= rhs[base_ind]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} -= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -1468,6 +2030,9 @@ FieldPerp operator*(const FieldPerp& lhs, const FieldPerp& rhs) { result[index] = lhs[index] * rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -1484,6 +2049,10 @@ FieldPerp& FieldPerp::operator*=(const FieldPerp& rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] *= rhs[index]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} *= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -1504,6 +2073,9 @@ FieldPerp operator/(const FieldPerp& lhs, const FieldPerp& rhs) { result[index] = lhs[index] / rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -1520,6 +2092,10 @@ FieldPerp& FieldPerp::operator/=(const FieldPerp& rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] /= rhs[index]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} /= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -1540,6 +2116,9 @@ FieldPerp operator+(const FieldPerp& lhs, const FieldPerp& rhs) { result[index] = lhs[index] + rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -1556,6 +2135,10 @@ FieldPerp& FieldPerp::operator+=(const FieldPerp& rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] += rhs[index]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} += {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -1576,6 +2159,9 @@ FieldPerp operator-(const FieldPerp& lhs, const FieldPerp& rhs) { result[index] = lhs[index] - rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", lhs.name, rhs.name); +#endif checkData(result); return result; } @@ -1592,6 +2178,10 @@ FieldPerp& FieldPerp::operator-=(const FieldPerp& rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] -= rhs[index]; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} -= {:s}", this->name, rhs.name); +#endif + checkData(*this); } else { @@ -1611,6 +2201,9 @@ FieldPerp operator*(const FieldPerp& lhs, const BoutReal rhs) { result[index] = lhs[index] * rhs; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", lhs.name, "BR"); +#endif checkData(result); return result; } @@ -1626,6 +2219,10 @@ FieldPerp& FieldPerp::operator*=(const BoutReal rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] *= rhs; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} *= {:s}", this->name, "BR"); +#endif + checkData(*this); } else { @@ -1646,6 +2243,9 @@ FieldPerp operator/(const FieldPerp& lhs, const BoutReal rhs) { result[index] = lhs[index] * tmp; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", lhs.name, "BR"); +#endif checkData(result); return result; } @@ -1661,6 +2261,10 @@ FieldPerp& FieldPerp::operator/=(const BoutReal rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] /= rhs; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} /= {:s}", this->name, "BR"); +#endif + checkData(*this); } else { @@ -1680,6 +2284,9 @@ FieldPerp operator+(const FieldPerp& lhs, const BoutReal rhs) { result[index] = lhs[index] + rhs; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", lhs.name, "BR"); +#endif checkData(result); return result; } @@ -1695,6 +2302,10 @@ FieldPerp& FieldPerp::operator+=(const BoutReal rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] += rhs; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} += {:s}", this->name, "BR"); +#endif + checkData(*this); } else { @@ -1714,6 +2325,9 @@ FieldPerp operator-(const FieldPerp& lhs, const BoutReal rhs) { result[index] = lhs[index] - rhs; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", lhs.name, "BR"); +#endif checkData(result); return result; } @@ -1729,6 +2343,10 @@ FieldPerp& FieldPerp::operator-=(const BoutReal rhs) { BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] -= rhs; } +#if BOUT_USE_TRACK + name = fmt::format("{:s} -= {:s}", this->name, "BR"); +#endif + checkData(*this); } else { @@ -1750,6 +2368,9 @@ Field3D operator*(const BoutReal lhs, const Field3D& rhs) { result[index] = lhs * rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", "BR", rhs.name); +#endif checkData(result); return result; } @@ -1767,6 +2388,9 @@ Field3D operator/(const BoutReal lhs, const Field3D& rhs) { result[index] = lhs / rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", "BR", rhs.name); +#endif checkData(result); return result; } @@ -1784,6 +2408,9 @@ Field3D operator+(const BoutReal lhs, const Field3D& rhs) { result[index] = lhs + rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", "BR", rhs.name); +#endif checkData(result); return result; } @@ -1801,6 +2428,9 @@ Field3D operator-(const BoutReal lhs, const Field3D& rhs) { result[index] = lhs - rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", "BR", rhs.name); +#endif checkData(result); return result; } @@ -1816,6 +2446,9 @@ Field2D operator*(const BoutReal lhs, const Field2D& rhs) { result[index] = lhs * rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", "BR", rhs.name); +#endif checkData(result); return result; } @@ -1831,6 +2464,9 @@ Field2D operator/(const BoutReal lhs, const Field2D& rhs) { result[index] = lhs / rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", "BR", rhs.name); +#endif checkData(result); return result; } @@ -1846,6 +2482,9 @@ Field2D operator+(const BoutReal lhs, const Field2D& rhs) { result[index] = lhs + rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", "BR", rhs.name); +#endif checkData(result); return result; } @@ -1861,6 +2500,9 @@ Field2D operator-(const BoutReal lhs, const Field2D& rhs) { result[index] = lhs - rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", "BR", rhs.name); +#endif checkData(result); return result; } @@ -1876,6 +2518,9 @@ FieldPerp operator*(const BoutReal lhs, const FieldPerp& rhs) { result[index] = lhs * rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", "BR", rhs.name); +#endif checkData(result); return result; } @@ -1891,6 +2536,9 @@ FieldPerp operator/(const BoutReal lhs, const FieldPerp& rhs) { result[index] = lhs / rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", "BR", rhs.name); +#endif checkData(result); return result; } @@ -1906,6 +2554,9 @@ FieldPerp operator+(const BoutReal lhs, const FieldPerp& rhs) { result[index] = lhs + rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", "BR", rhs.name); +#endif checkData(result); return result; } @@ -1921,6 +2572,1106 @@ FieldPerp operator-(const BoutReal lhs, const FieldPerp& rhs) { result[index] = lhs - rhs[index]; } +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", "BR", rhs.name); +#endif + checkData(result); + return result; +} + +// 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()); + ASSERT2(rhs.hasParallelSlices()); + result.splitParallelSlices(); + for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { + result.yup(i) = lhs.yup(i) * rhs.yup(i); + result.ydown(i) = lhs.ydown(i) * rhs.ydown(i); + } + } + + BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { + result[index] = lhs[index] * rhs[index]; + } + +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", lhs.name, rhs.name); +#endif + checkData(result); + return result; +} + +// 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()); + ASSERT2(rhs.hasParallelSlices()); + result.splitParallelSlices(); + for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { + result.yup(i) = lhs.yup(i) / rhs.yup(i); + result.ydown(i) = lhs.ydown(i) / rhs.ydown(i); + } + } + + BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { + result[index] = lhs[index] / rhs[index]; + } + +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", lhs.name, rhs.name); +#endif + checkData(result); + return result; +} + +// 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()); + ASSERT2(rhs.hasParallelSlices()); + result.splitParallelSlices(); + for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { + result.yup(i) = lhs.yup(i) + rhs.yup(i); + result.ydown(i) = lhs.ydown(i) + rhs.ydown(i); + } + } + + BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { + result[index] = lhs[index] + rhs[index]; + } + +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", lhs.name, rhs.name); +#endif + checkData(result); + return result; +} + +// 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()); + ASSERT2(rhs.hasParallelSlices()); + result.splitParallelSlices(); + for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { + result.yup(i) = lhs.yup(i) - rhs.yup(i); + result.ydown(i) = lhs.ydown(i) - rhs.ydown(i); + } + } + + BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { + result[index] = lhs[index] - rhs[index]; + } + +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", lhs.name, rhs.name); +#endif + checkData(result); + return result; +} + +// 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()); + ASSERT2(rhs.hasParallelSlices()); + result.splitParallelSlices(); + for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { + result.yup(i) = lhs.yup(i) * rhs.yup(i); + result.ydown(i) = lhs.ydown(i) * rhs.ydown(i); + } + } + + BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { + result[index] = lhs[index] * rhs[index]; + } + +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", lhs.name, rhs.name); +#endif + checkData(result); + return result; +} + +// 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 + // otherwise just call the non-inplace version + if (data.unique() or isRef) { + ASSERT1_FIELDS_COMPATIBLE(*this, rhs); + + if (this->isFci()) { + if (isRef) { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i).update_multiplication_inplace(rhs.yup(i)); + ydown(i).update_multiplication_inplace(rhs.ydown(i)); + } + } else { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i) *= rhs.yup(i); + ydown(i) *= rhs.ydown(i); + } + } + } else { + clearParallelSlices(); + } + checkData(*this); + checkData(rhs); + + regionID = fieldmesh->getCommonRegion(regionID, rhs.getRegionID()); + + BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] *= rhs[index]; } + + track(rhs, "operator*="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} *= {:s}", this->name, rhs.name); +#endif + + checkData(*this); + + } else { + track(rhs, "operator*="); + (*this) = (*this) * rhs; + } + return *this; +} + +// 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()); + ASSERT2(rhs.hasParallelSlices()); + result.splitParallelSlices(); + for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { + result.yup(i) = lhs.yup(i) / rhs.yup(i); + result.ydown(i) = lhs.ydown(i) / rhs.ydown(i); + } + } + + BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { + result[index] = lhs[index] / rhs[index]; + } + +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", lhs.name, rhs.name); +#endif + checkData(result); + return result; +} + +// 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 + // otherwise just call the non-inplace version + if (data.unique() or isRef) { + ASSERT1_FIELDS_COMPATIBLE(*this, rhs); + + if (this->isFci()) { + if (isRef) { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i).update_division_inplace(rhs.yup(i)); + ydown(i).update_division_inplace(rhs.ydown(i)); + } + } else { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i) /= rhs.yup(i); + ydown(i) /= rhs.ydown(i); + } + } + } else { + clearParallelSlices(); + } + checkData(*this); + checkData(rhs); + + regionID = fieldmesh->getCommonRegion(regionID, rhs.getRegionID()); + + BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] /= rhs[index]; } + + track(rhs, "operator/="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} /= {:s}", this->name, rhs.name); +#endif + + checkData(*this); + + } else { + track(rhs, "operator/="); + (*this) = (*this) / rhs; + } + return *this; +} + +// 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()); + ASSERT2(rhs.hasParallelSlices()); + result.splitParallelSlices(); + for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { + result.yup(i) = lhs.yup(i) + rhs.yup(i); + result.ydown(i) = lhs.ydown(i) + rhs.ydown(i); + } + } + + BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { + result[index] = lhs[index] + rhs[index]; + } + +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", lhs.name, rhs.name); +#endif + checkData(result); + return result; +} + +// 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 + // otherwise just call the non-inplace version + if (data.unique() or isRef) { + ASSERT1_FIELDS_COMPATIBLE(*this, rhs); + + if (this->isFci()) { + if (isRef) { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i).update_addition_inplace(rhs.yup(i)); + ydown(i).update_addition_inplace(rhs.ydown(i)); + } + } else { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i) += rhs.yup(i); + ydown(i) += rhs.ydown(i); + } + } + } else { + clearParallelSlices(); + } + checkData(*this); + checkData(rhs); + + regionID = fieldmesh->getCommonRegion(regionID, rhs.getRegionID()); + + BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] += rhs[index]; } + + track(rhs, "operator+="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} += {:s}", this->name, rhs.name); +#endif + + checkData(*this); + + } else { + track(rhs, "operator+="); + (*this) = (*this) + rhs; + } + return *this; +} + +// 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()); + ASSERT2(rhs.hasParallelSlices()); + result.splitParallelSlices(); + for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { + result.yup(i) = lhs.yup(i) - rhs.yup(i); + result.ydown(i) = lhs.ydown(i) - rhs.ydown(i); + } + } + + BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { + result[index] = lhs[index] - rhs[index]; + } + +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", lhs.name, rhs.name); +#endif + checkData(result); + return result; +} + +// 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 + // otherwise just call the non-inplace version + if (data.unique() or isRef) { + ASSERT1_FIELDS_COMPATIBLE(*this, rhs); + + if (this->isFci()) { + if (isRef) { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i).update_subtraction_inplace(rhs.yup(i)); + ydown(i).update_subtraction_inplace(rhs.ydown(i)); + } + } else { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i) -= rhs.yup(i); + ydown(i) -= rhs.ydown(i); + } + } + } else { + clearParallelSlices(); + } + checkData(*this); + checkData(rhs); + + regionID = fieldmesh->getCommonRegion(regionID, rhs.getRegionID()); + + BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] -= rhs[index]; } + + track(rhs, "operator-="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} -= {:s}", this->name, rhs.name); +#endif + + checkData(*this); + + } else { + track(rhs, "operator-="); + (*this) = (*this) - rhs; + } + return *this; +} + +// 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()); + ASSERT2(rhs.hasParallelSlices()); + result.splitParallelSlices(); + for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { + result.yup(i) = lhs.yup(i) * rhs.yup(i); + result.ydown(i) = lhs.ydown(i) * rhs.ydown(i); + } + } + + BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { + result[index] = lhs[index] * rhs[index]; + } + +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", lhs.name, rhs.name); +#endif + checkData(result); + return result; +} + +// 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 + // otherwise just call the non-inplace version + if (data.unique() or isRef) { + ASSERT1_FIELDS_COMPATIBLE(*this, rhs); + + if (this->isFci()) { + if (isRef) { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i).update_multiplication_inplace(rhs); + ydown(i).update_multiplication_inplace(rhs); + } + } else { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i) *= rhs; + ydown(i) *= rhs; + } + } + } else { + clearParallelSlices(); + } + checkData(*this); + checkData(rhs); + + regionID = fieldmesh->getCommonRegion(regionID, rhs.getRegionID()); + + BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] *= rhs[index]; } + + track(rhs, "operator*="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} *= {:s}", this->name, rhs.name); +#endif + + checkData(*this); + + } else { + track(rhs, "operator*="); + (*this) = (*this) * rhs; + } + return *this; +} + +// 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()); + ASSERT2(rhs.hasParallelSlices()); + result.splitParallelSlices(); + for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { + result.yup(i) = lhs.yup(i) / rhs.yup(i); + result.ydown(i) = lhs.ydown(i) / rhs.ydown(i); + } + } + + BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { + result[index] = lhs[index] / rhs[index]; + } + +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", lhs.name, rhs.name); +#endif + checkData(result); + return result; +} + +// 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 + // otherwise just call the non-inplace version + if (data.unique() or isRef) { + ASSERT1_FIELDS_COMPATIBLE(*this, rhs); + + if (this->isFci()) { + if (isRef) { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i).update_division_inplace(rhs); + ydown(i).update_division_inplace(rhs); + } + } else { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i) /= rhs; + ydown(i) /= rhs; + } + } + } else { + clearParallelSlices(); + } + checkData(*this); + checkData(rhs); + + regionID = fieldmesh->getCommonRegion(regionID, rhs.getRegionID()); + + BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] /= rhs[index]; } + + track(rhs, "operator/="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} /= {:s}", this->name, rhs.name); +#endif + + checkData(*this); + + } else { + track(rhs, "operator/="); + (*this) = (*this) / rhs; + } + return *this; +} + +// 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()); + ASSERT2(rhs.hasParallelSlices()); + result.splitParallelSlices(); + for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { + result.yup(i) = lhs.yup(i) + rhs.yup(i); + result.ydown(i) = lhs.ydown(i) + rhs.ydown(i); + } + } + + BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { + result[index] = lhs[index] + rhs[index]; + } + +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", lhs.name, rhs.name); +#endif + checkData(result); + return result; +} + +// 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 + // otherwise just call the non-inplace version + if (data.unique() or isRef) { + ASSERT1_FIELDS_COMPATIBLE(*this, rhs); + + if (this->isFci()) { + if (isRef) { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i).update_addition_inplace(rhs); + ydown(i).update_addition_inplace(rhs); + } + } else { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i) += rhs; + ydown(i) += rhs; + } + } + } else { + clearParallelSlices(); + } + checkData(*this); + checkData(rhs); + + regionID = fieldmesh->getCommonRegion(regionID, rhs.getRegionID()); + + BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] += rhs[index]; } + + track(rhs, "operator+="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} += {:s}", this->name, rhs.name); +#endif + + checkData(*this); + + } else { + track(rhs, "operator+="); + (*this) = (*this) + rhs; + } + return *this; +} + +// 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()); + ASSERT2(rhs.hasParallelSlices()); + result.splitParallelSlices(); + for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { + result.yup(i) = lhs.yup(i) - rhs.yup(i); + result.ydown(i) = lhs.ydown(i) - rhs.ydown(i); + } + } + + BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { + result[index] = lhs[index] - rhs[index]; + } + +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", lhs.name, rhs.name); +#endif + checkData(result); + return result; +} + +// 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 + // otherwise just call the non-inplace version + if (data.unique() or isRef) { + ASSERT1_FIELDS_COMPATIBLE(*this, rhs); + + if (this->isFci()) { + if (isRef) { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i).update_subtraction_inplace(rhs); + ydown(i).update_subtraction_inplace(rhs); + } + } else { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i) -= rhs; + ydown(i) -= rhs; + } + } + } else { + clearParallelSlices(); + } + checkData(*this); + checkData(rhs); + + regionID = fieldmesh->getCommonRegion(regionID, rhs.getRegionID()); + + BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] -= rhs[index]; } + + track(rhs, "operator-="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} -= {:s}", this->name, rhs.name); +#endif + + checkData(*this); + + } else { + track(rhs, "operator-="); + (*this) = (*this) - rhs; + } + return *this; +} + +// 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()); + + 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; + } + } + + BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { + result[index] = lhs[index] * rhs; + } + +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", lhs.name, "BR"); +#endif + 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 + // otherwise just call the non-inplace version + if (data.unique() or isRef) { + + if (this->isFci()) { + if (isRef) { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i).update_multiplication_inplace(rhs); + ydown(i).update_multiplication_inplace(rhs); + } + } else { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i) *= rhs; + ydown(i) *= rhs; + } + } + } else { + clearParallelSlices(); + } + checkData(*this); + checkData(rhs); + + BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] *= rhs; } + + track(rhs, "operator*="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} *= {:s}", this->name, "BR"); +#endif + + checkData(*this); + + } else { + track(rhs, "operator*="); + (*this) = (*this) * rhs; + } + return *this; +} + +// 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()); + + 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; + } + } + + const auto tmp = 1.0 / rhs; + BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { + result[index] = lhs[index] * tmp; + } + +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", lhs.name, "BR"); +#endif + 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 + // otherwise just call the non-inplace version + if (data.unique() or isRef) { + + if (this->isFci()) { + if (isRef) { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i).update_division_inplace(rhs); + ydown(i).update_division_inplace(rhs); + } + } else { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i) /= rhs; + ydown(i) /= rhs; + } + } + } else { + clearParallelSlices(); + } + checkData(*this); + checkData(rhs); + + BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] /= rhs; } + + track(rhs, "operator/="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} /= {:s}", this->name, "BR"); +#endif + + checkData(*this); + + } else { + track(rhs, "operator/="); + (*this) = (*this) / rhs; + } + return *this; +} + +// 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()); + + 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; + } + } + + BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { + result[index] = lhs[index] + rhs; + } + +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", lhs.name, "BR"); +#endif + 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 + // otherwise just call the non-inplace version + if (data.unique() or isRef) { + + if (this->isFci()) { + if (isRef) { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i).update_addition_inplace(rhs); + ydown(i).update_addition_inplace(rhs); + } + } else { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i) += rhs; + ydown(i) += rhs; + } + } + } else { + clearParallelSlices(); + } + checkData(*this); + checkData(rhs); + + BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] += rhs; } + + track(rhs, "operator+="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} += {:s}", this->name, "BR"); +#endif + + checkData(*this); + + } else { + track(rhs, "operator+="); + (*this) = (*this) + rhs; + } + return *this; +} + +// 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()); + + 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; + } + } + + BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { + result[index] = lhs[index] - rhs; + } + +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", lhs.name, "BR"); +#endif + 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 + // otherwise just call the non-inplace version + if (data.unique() or isRef) { + + if (this->isFci()) { + if (isRef) { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i).update_subtraction_inplace(rhs); + ydown(i).update_subtraction_inplace(rhs); + } + } else { + for (size_t i{0}; i < yup_fields.size(); ++i) { + yup(i) -= rhs; + ydown(i) -= rhs; + } + } + } else { + clearParallelSlices(); + } + checkData(*this); + checkData(rhs); + + BOUT_FOR(index, this->getRegion("RGN_ALL")) { (*this)[index] -= rhs; } + + track(rhs, "operator-="); +#if BOUT_USE_TRACK + name = fmt::format("{:s} -= {:s}", this->name, "BR"); +#endif + + checkData(*this); + + } else { + track(rhs, "operator-="); + (*this) = (*this) - rhs; + } + return *this; +} + +// 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()); + 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); + } + } + + BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { + result[index] = lhs * rhs[index]; + } + +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} * {:s}", "BR", rhs.name); +#endif + 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()); + 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); + } + } + + BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { + result[index] = lhs / rhs[index]; + } + +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} / {:s}", "BR", rhs.name); +#endif + 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()); + 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); + } + } + + BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { + result[index] = lhs + rhs[index]; + } + +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} + {:s}", "BR", rhs.name); +#endif + 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()); + 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); + } + } + + BOUT_FOR(index, result.getValidRegionWithDefault("RGN_ALL")) { + result[index] = lhs - rhs[index]; + } + +#if BOUT_USE_TRACK + result.name = fmt::format("{:s} - {:s}", "BR", rhs.name); +#endif checkData(result); return result; } diff --git a/src/invert/laplace/impls/naulin/naulin_laplace.cxx b/src/invert/laplace/impls/naulin/naulin_laplace.cxx index f61dc93908..b131e048f0 100644 --- a/src/invert/laplace/impls/naulin/naulin_laplace.cxx +++ b/src/invert/laplace/impls/naulin/naulin_laplace.cxx @@ -143,6 +143,7 @@ #include #include #include +#include #include #include @@ -203,7 +204,7 @@ LaplaceNaulin::LaplaceNaulin(Options* opt, const CELL_LOC loc, Mesh* mesh_in, ASSERT0(underrelax_recovery >= 1.); delp2solver = create(opt->getSection("delp2solver"), location, localmesh); std::string delp2type; - opt->getSection("delp2solver")->get("type", delp2type, "cyclic"); + opt->getSection("delp2solver")->get("type", delp2type, LaplaceFactory::default_type); // Check delp2solver is using an FFT scheme, otherwise it will not exactly // invert Delp2 and we will not converge ASSERT0(delp2type == "cyclic" || delp2type == "spt" || delp2type == "tri"); diff --git a/src/invert/laplace/impls/petsc/petsc_laplace.cxx b/src/invert/laplace/impls/petsc/petsc_laplace.cxx index c6a6712f4d..4a4c37854d 100644 --- a/src/invert/laplace/impls/petsc/petsc_laplace.cxx +++ b/src/invert/laplace/impls/petsc/petsc_laplace.cxx @@ -63,8 +63,9 @@ static PetscErrorCode laplacePCapply(PC pc, Vec x, Vec y) { LaplacePetsc::LaplacePetsc(Options* opt, const CELL_LOC loc, Mesh* mesh_in, Solver* UNUSED(solver)) - : Laplacian(opt, loc, mesh_in), A(0.0), C1(1.0), C2(1.0), D(1.0), Ex(0.0), Ez(0.0), - issetD(false), issetC(false), issetE(false), + : Laplacian(opt, loc, mesh_in), A(0.0, mesh_in), C1(1.0, mesh_in), C2(1.0, mesh_in), + D(1.0, mesh_in), Ex(0.0, mesh_in), Ez(0.0, mesh_in), issetD(false), issetC(false), + issetE(false), sol(mesh_in), lib(opt == nullptr ? &(Options::root()["laplace"]) : opt) { A.setLocation(location); C1.setLocation(location); @@ -335,7 +336,8 @@ FieldPerp LaplacePetsc::solve(const FieldPerp& b) { return solve(b, b); } * * \returns sol The solution x of the problem Ax=b. */ -FieldPerp LaplacePetsc::solve(const FieldPerp& b, const FieldPerp& x0) { +FieldPerp LaplacePetsc::solve(const FieldPerp& b, const FieldPerp& x0, + const bool forward) { ASSERT1(localmesh == b.getMesh() && localmesh == x0.getMesh()); ASSERT1(b.getLocation() == location); @@ -345,7 +347,7 @@ FieldPerp LaplacePetsc::solve(const FieldPerp& b, const FieldPerp& x0) { checkFlags(); #endif - int y = b.getIndex(); // Get the Y index + const int y = b.getIndex(); // Get the Y index sol.setIndex(y); // Initialize the solution field. sol = 0.; @@ -470,11 +472,11 @@ FieldPerp LaplacePetsc::solve(const FieldPerp& b, const FieldPerp& x0) { // Set the matrix coefficients Coeffs(x, y, z, A1, A2, A3, A4, A5); - BoutReal dx = coords->dx(x, y, z); - BoutReal dx2 = SQ(dx); - BoutReal dz = coords->dz(x, y, z); - BoutReal dz2 = SQ(dz); - BoutReal dxdz = dx * dz; + const BoutReal dx = coords->dx(x, y, z); + const BoutReal dx2 = SQ(dx); + const BoutReal dz = coords->dz(x, y, z); + const BoutReal dz2 = SQ(dz); + const BoutReal dxdz = dx * dz; ASSERT3(std::isfinite(A1)); ASSERT3(std::isfinite(A2)); @@ -630,6 +632,7 @@ FieldPerp LaplacePetsc::solve(const FieldPerp& b, const FieldPerp& x0) { // Set Components of Trial Solution Vector val = x0[x][z]; VecSetValues(xs, 1, &i, &val, INSERT_VALUES); + ASSERT3(i == getIndex(x, z)); i++; } } @@ -713,7 +716,7 @@ FieldPerp LaplacePetsc::solve(const FieldPerp& b, const FieldPerp& x0) { // INSERT_VALUES replaces existing entries with new values val = x0[x][z]; VecSetValues(xs, 1, &i, &val, INSERT_VALUES); - + ASSERT3(i == getIndex(x, z)); i++; // Increment row in Petsc matrix } } @@ -738,71 +741,98 @@ FieldPerp LaplacePetsc::solve(const FieldPerp& b, const FieldPerp& x0) { VecAssemblyBegin(xs); VecAssemblyEnd(xs); - // Configure Linear Solver + if (not forward) { + // Configure Linear Solver #if PETSC_VERSION_GE(3, 5, 0) - KSPSetOperators(ksp, MatA, MatA); + KSPSetOperators(ksp, MatA, MatA); #else - KSPSetOperators(ksp, MatA, MatA, DIFFERENT_NONZERO_PATTERN); + KSPSetOperators(ksp, MatA, MatA, DIFFERENT_NONZERO_PATTERN); #endif - PC pc; // The preconditioner option - - if (direct) { // If a direct solver has been chosen - // Get the preconditioner - KSPGetPC(ksp, &pc); - // Set the preconditioner - PCSetType(pc, PCLU); - // Set the solver type + PC pc = nullptr; // The preconditioner option + + if (direct) { // If a direct solver has been chosen + // Get the preconditioner + KSPGetPC(ksp, &pc); + // Set the preconditioner + PCSetType(pc, PCLU); + // Set the solver type #if PETSC_VERSION_GE(3, 9, 0) - PCFactorSetMatSolverType(pc, "mumps"); + PCFactorSetMatSolverType(pc, "mumps"); #else - PCFactorSetMatSolverPackage(pc, "mumps"); + PCFactorSetMatSolverPackage(pc, "mumps"); #endif - } else { // If a iterative solver has been chosen - KSPSetType(ksp, ksptype.c_str()); // Set the type of the solver + } else { // If a iterative solver has been chosen + KSPSetType(ksp, ksptype.c_str()); // Set the type of the solver - if (ksptype == KSPRICHARDSON) { - KSPRichardsonSetScale(ksp, richardson_damping_factor); - } + if (ksptype == KSPRICHARDSON) { + KSPRichardsonSetScale(ksp, richardson_damping_factor); + } #ifdef KSPCHEBYSHEV - else if (ksptype == KSPCHEBYSHEV) { - KSPChebyshevSetEigenvalues(ksp, chebyshev_max, chebyshev_min); - } + else if (ksptype == KSPCHEBYSHEV) { + KSPChebyshevSetEigenvalues(ksp, chebyshev_max, chebyshev_min); + } #endif - else if (ksptype == KSPGMRES) { - KSPGMRESSetRestart(ksp, gmres_max_steps); - } + else if (ksptype == KSPGMRES) { + KSPGMRESSetRestart(ksp, gmres_max_steps); + } - // Set the relative and absolute tolerances - KSPSetTolerances(ksp, rtol, atol, dtol, maxits); + // Set the relative and absolute tolerances + KSPSetTolerances(ksp, rtol, atol, dtol, maxits); - // If the initial guess is not set to zero - if (!isGlobalFlagSet(INVERT_START_NEW)) { - KSPSetInitialGuessNonzero(ksp, static_cast(true)); - } + // If the initial guess is not set to zero + if (!isGlobalFlagSet(INVERT_START_NEW)) { + KSPSetInitialGuessNonzero(ksp, static_cast(true)); + } - // Get the preconditioner - KSPGetPC(ksp, &pc); + // Get the preconditioner + KSPGetPC(ksp, &pc); - // Set the type of the preconditioner - PCSetType(pc, pctype.c_str()); + // Set the type of the preconditioner + PCSetType(pc, pctype.c_str()); - // If pctype = user in BOUT.inp, it will be translated to PCSHELL upon - // construction of the object - if (pctype == PCSHELL) { - // User-supplied preconditioner function - PCShellSetApply(pc, laplacePCapply); - PCShellSetContext(pc, this); - if (rightprec) { - KSPSetPCSide(ksp, PC_RIGHT); // Right preconditioning - } else { - KSPSetPCSide(ksp, PC_LEFT); // Left preconditioning + // If pctype = user in BOUT.inp, it will be translated to PCSHELL upon + // construction of the object + if (pctype == PCSHELL) { + // User-supplied preconditioner function + PCShellSetApply(pc, laplacePCapply); + PCShellSetContext(pc, this); + if (rightprec) { + KSPSetPCSide(ksp, PC_RIGHT); // Right preconditioning + } else { + KSPSetPCSide(ksp, PC_LEFT); // Left preconditioning + } + //ierr = PCShellSetApply(pc,laplacePCapply);CHKERRQ(ierr); + //ierr = PCShellSetContext(pc,this);CHKERRQ(ierr); + //ierr = KSPSetPCSide(ksp, PC_RIGHT);CHKERRQ(ierr); } - //ierr = PCShellSetApply(pc,laplacePCapply);CHKERRQ(ierr); - //ierr = PCShellSetContext(pc,this);CHKERRQ(ierr); - //ierr = KSPSetPCSide(ksp, PC_RIGHT);CHKERRQ(ierr); + + lib.setOptionsFromInputFile(ksp); } + //timer.reset(); - lib.setOptionsFromInputFile(ksp); + // Call the actual solver + { + const Timer timer("petscsolve"); + KSPSolve(ksp, bs, xs); // Call the solver to solve the system + } + + KSPConvergedReason reason; + KSPGetConvergedReason(ksp, &reason); + if (reason + == -3) { // Too many iterations, might be fixed by taking smaller timestep + throw BoutIterationFail("petsc_laplace: too many iterations"); + } + if (reason <= 0) { + throw BoutException( + "petsc_laplace: inversion failed to converge. KSPConvergedReason: {} ({})", + KSPConvergedReasons[reason], static_cast(reason)); + } + } else { + //timer.reset(); + const PetscErrorCode err = MatMult(MatA, bs, xs); + if (err != PETSC_SUCCESS) { + throw BoutException("MatMult failed with {:d}", static_cast(err)); + } } } @@ -869,24 +899,7 @@ FieldPerp LaplacePetsc::solve(const FieldPerp& b, const FieldPerp& x0) { return sol; } -/*! - * Sets the elements of the matrix A, which is used to solve the problem Ax=b. - * - * \param[in] - * i - * The row of the PETSc matrix - * \param[in] x Local x index of the mesh - * \param[in] z Local z index of the mesh - * \param[in] xshift The shift in rows from the index x - * \param[in] zshift The shift in columns from the index z - * \param[in] ele Value of the element - * \param[in] MatA The matrix A used in the inversion - * - * \param[out] MatA The matrix A used in the inversion - */ -void LaplacePetsc::Element(int i, int x, int z, int xshift, int zshift, PetscScalar ele, - Mat& MatA) { - +int LaplacePetsc::getIndex(const int x, const int z) { // Need to convert LOCAL x to GLOBAL x in order to correctly calculate // PETSC Matrix Index. int xoffset = Istart / meshz; @@ -895,22 +908,43 @@ void LaplacePetsc::Element(int i, int x, int z, int xshift, int zshift, PetscSca } // Calculate the row to be set - int row_new = x + xshift; // should never be out of range. + int row_new = x; // should never be out of range. if (!localmesh->firstX()) { row_new += (xoffset - localmesh->xstart); } // Calculate the column to be set - int col_new = z + zshift; + int col_new = z; if (col_new < 0) { col_new += meshz; } else if (col_new > meshz - 1) { col_new -= meshz; } + ASSERT3(0 <= col_new and col_new < meshz); // Convert to global indices - int index = (row_new * meshz) + col_new; + return (row_new * meshz) + col_new; +} + +/*! + * Sets the elements of the matrix A, which is used to solve the problem Ax=b. + * + * \param[in] + * i + * The row of the PETSc matrix + * \param[in] x Local x index of the mesh + * \param[in] z Local z index of the mesh + * \param[in] xshift The shift in rows from the index x + * \param[in] zshift The shift in columns from the index z + * \param[in] ele Value of the element + * \param[in] MatA The matrix A used in the inversion + * + * \param[out] MatA The matrix A used in the inversion + */ +void LaplacePetsc::Element(const int i, const int x, const int z, const int xshift, + const int zshift, const PetscScalar ele, Mat& MatA) { + const int index = getIndex(x + xshift, z + zshift); #if CHECK > 2 if (!std::isfinite(ele)) { throw BoutException("Non-finite element at x={:d}, z={:d}, row={:d}, col={:d}\n", x, diff --git a/src/invert/laplace/impls/petsc/petsc_laplace.hxx b/src/invert/laplace/impls/petsc/petsc_laplace.hxx index 1d56abd00b..5a73030c8c 100644 --- a/src/invert/laplace/impls/petsc/petsc_laplace.hxx +++ b/src/invert/laplace/impls/petsc/petsc_laplace.hxx @@ -194,7 +194,13 @@ public: using Laplacian::solve; FieldPerp solve(const FieldPerp& b) override; - FieldPerp solve(const FieldPerp& b, const FieldPerp& x0) override; + FieldPerp solve(const FieldPerp& b, const FieldPerp& x0) override { + return solve(b, x0, false); + } + FieldPerp solve(const FieldPerp& b, const FieldPerp& x0, bool forward); + + using Laplacian::forward; + FieldPerp forward(const FieldPerp& b) override { return solve(b, b, true); } int precon(Vec x, Vec y); ///< Preconditioner function @@ -202,7 +208,7 @@ private: void Element(int i, int x, int z, int xshift, int zshift, PetscScalar ele, Mat& MatA); void Coeffs(int x, int y, int z, BoutReal& A1, BoutReal& A2, BoutReal& A3, BoutReal& A4, BoutReal& A5); - + int getIndex(int x, int z); /* Ex and Ez * Additional 1st derivative terms to allow for solution field to be * components of a vector diff --git a/src/invert/laplace/invert_laplace.cxx b/src/invert/laplace/invert_laplace.cxx index e96248ef7e..7b4a7e2501 100644 --- a/src/invert/laplace/invert_laplace.cxx +++ b/src/invert/laplace/invert_laplace.cxx @@ -31,6 +31,7 @@ * */ +#include "bout/index_derivs_interface.hxx" #include #include #include @@ -224,10 +225,10 @@ Field3D Laplacian::solve(const Field3D& b, const Field3D& x0) { // Setting the start and end range of the y-slices int ys = localmesh->ystart, ye = localmesh->yend; - if (localmesh->hasBndryLowerY() && include_yguards) { + if (include_yguards && localmesh->hasBndryLowerY()) { ys = 0; // Mesh contains a lower boundary } - if (localmesh->hasBndryUpperY() && include_yguards) { + if (include_yguards && localmesh->hasBndryUpperY()) { ye = localmesh->LocalNy - 1; // Contains upper boundary } @@ -254,6 +255,37 @@ Field2D Laplacian::solve(const Field2D& b, const Field2D& x0) { return DC(f); } +Field3D Laplacian::forward(const Field3D& b) { + TRACE("Laplacian::solve(Field3D, Field3D)"); + + ASSERT1(b.getLocation() == location); + ASSERT1(localmesh == b.getMesh()); + + // Setting the start and end range of the y-slices + int ys = localmesh->ystart; + int ye = localmesh->yend; + if (include_yguards && localmesh->hasBndryLowerY()) { + ys = 0; // Mesh contains a lower boundary + } + if (include_yguards && localmesh->hasBndryUpperY()) { + ye = localmesh->LocalNy - 1; // Contains upper boundary + } + + Field3D x{emptyFrom(b)}; + + for (int jy = ys; jy <= ye; jy++) { + // 1. Slice b and x (i.e. take a X-Z plane out of the field) + // 2. Send them to the solver of the implementation (determined during creation) + x = forward(sliceXZ(b, jy)); + } + + return x; +} + +FieldPerp Laplacian::forward([[maybe_unused]] const FieldPerp& b) { + throw BoutException("Not implemented for this inversion"); +} + /********************************************************************************** * MATRIX ELEMENTS **********************************************************************************/ diff --git a/src/mesh/boundary_factory.cxx b/src/mesh/boundary_factory.cxx index d112a216ad..fd5bbdc70d 100644 --- a/src/mesh/boundary_factory.cxx +++ b/src/mesh/boundary_factory.cxx @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -228,46 +229,82 @@ BoundaryOpBase* BoundaryFactory::createFromOptions(const string& varname, string prefix("bndry_"); - string side; + std::array sides; + sides[0] = region->label; switch (region->location) { case BNDRY_XIN: { - side = "xin"; + sides[1] = "xin"; break; } case BNDRY_XOUT: { - side = "xout"; + sides[1] = "xout"; break; } case BNDRY_YDOWN: { - side = "ydown"; + sides[1] = "ydown"; break; } case BNDRY_YUP: { - side = "yup"; + sides[1] = "yup"; break; } case BNDRY_PAR_FWD_XIN: { - side = "par_yup_xin"; + sides[1] = "par_yup_xin"; break; } case BNDRY_PAR_FWD_XOUT: { - side = "par_yup_xout"; + sides[1] = "par_yup_xout"; break; } case BNDRY_PAR_BKWD_XIN: { - side = "par_ydown_xin"; + sides[1] = "par_ydown_xin"; break; } case BNDRY_PAR_BKWD_XOUT: { - side = "par_ydown_xout"; + sides[1] = "par_ydown_xout"; break; } default: { - side = "all"; + sides[1] = "all"; break; } } + switch (region->location) { + case BNDRY_PAR_FWD_XIN: + case BNDRY_PAR_BKWD_XIN: { + sides[2] = "par_xin"; + break; + } + case BNDRY_PAR_BKWD_XOUT: + case BNDRY_PAR_FWD_XOUT: { + sides[2] = "par_xout"; + break; + } + default: { + sides[2] = "all"; + break; + } + } + switch (region->location) { + case BNDRY_PAR_FWD_XIN: + case BNDRY_PAR_FWD_XOUT: { + sides[3] = "par_yup"; + break; + } + case BNDRY_PAR_BKWD_XIN: + case BNDRY_PAR_BKWD_XOUT: { + sides[3] = "par_ydown"; + break; + } + default: { + sides[3] = "all"; + break; + } + } + + sides[4] = region->isParallel ? "par_all" : "all"; + // Get options Options* options = Options::getRoot(); @@ -275,27 +312,10 @@ BoundaryOpBase* BoundaryFactory::createFromOptions(const string& varname, Options* varOpts = options->getSection(varname); string set; - /// First try looking for (var, region) - if (varOpts->isSet(prefix + region->label)) { - varOpts->get(prefix + region->label, set, ""); - return create(set, region); - } - - /// Then (var, side) - if (varOpts->isSet(prefix + side)) { - varOpts->get(prefix + side, set, ""); - return create(set, region); - } - - /// Then (var, all) - if (region->isParallel) { - if (varOpts->isSet(prefix + "par_all")) { - varOpts->get(prefix + "par_all", set, ""); - return create(set, region); - } - } else { - if (varOpts->isSet(prefix + "all")) { - varOpts->get(prefix + "all", set, ""); + /// First try looking for (var, ...) + for (const auto& side : sides) { + if (varOpts->isSet(prefix + side)) { + varOpts->get(prefix + side, set, ""); return create(set, region); } } @@ -303,16 +323,13 @@ BoundaryOpBase* BoundaryFactory::createFromOptions(const string& varname, // Get the "all" options varOpts = options->getSection("all"); - /// Then (all, region) - if (varOpts->isSet(prefix + region->label)) { - varOpts->get(prefix + region->label, set, ""); - return create(set, region); - } - - /// Then (all, side) - if (varOpts->isSet(prefix + side)) { - varOpts->get(prefix + side, set, ""); - return create(set, region); + /// First try looking for (all, ...) + for (const auto& side : sides) { + if (varOpts->isSet(prefix + side)) { + varOpts->get(prefix + side, set, + region->isParallel ? "parallel_dirichlet_o2" : "dirichlet"); + return create(set, region); + } } /// Then (all, all) diff --git a/src/mesh/boundary_standard.cxx b/src/mesh/boundary_standard.cxx index 39a1ea3641..36ddb57f26 100644 --- a/src/mesh/boundary_standard.cxx +++ b/src/mesh/boundary_standard.cxx @@ -1727,7 +1727,7 @@ void BoundaryNeumann_NonOrthogonal::apply(Field3D& f) { void BoundaryNeumann::apply(Field2D & f) { BoundaryNeumann::apply(f, 0.); } - void BoundaryNeumann::apply(Field2D & f, BoutReal t) { + void BoundaryNeumann::apply([[maybe_unused]] Field2D & f, [[maybe_unused]] BoutReal t) { // Set (at 2nd order / 3rd order) the value at the mid-point between // the guard cell and the grid cell to be val // N.B. First guard cells (closest to the grid) is 2nd order, while @@ -2165,7 +2165,7 @@ void BoundaryNeumann_NonOrthogonal::apply(Field3D& f) { } else { throw BoutException("Unrecognized location"); } - } else { + } else { // loc == CELL_CENTRE for (; !bndry->isDone(); bndry->next1d()) { #if BOUT_USE_METRIC_3D for (int zk = mesh->zstart; zk <= mesh->zend; zk++) { diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index 91bbddfd56..090551354b 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -4,6 +4,8 @@ * given the contravariant metric tensor terms **************************************************************************/ +#include "bout/field3d.hxx" +#include "bout/region.hxx" #include #include #include @@ -18,6 +20,7 @@ #include #include +#include #include "invert3x3.hxx" #include "parallel/fci.hxx" @@ -591,6 +594,9 @@ Coordinates::Coordinates(Mesh* mesh, Options* options) // IntShiftTorsion will not be used, but set to zero to avoid uninitialized field IntShiftTorsion = 0.; } + + // Allow transform to fix things up + transform->loadParallelMetrics(this); } Coordinates::Coordinates(Mesh* mesh, Options* options, const CELL_LOC loc, @@ -879,6 +885,8 @@ Coordinates::Coordinates(Mesh* mesh, Options* options, const CELL_LOC loc, true, true, false, transform.get()); } } + // Allow transform to fix things up + transform->loadParallelMetrics(this); } void Coordinates::outputVars(Options& output_options) { @@ -931,6 +939,13 @@ const Field2D& Coordinates::zlength() const { int Coordinates::geometry(bool recalculate_staggered, bool force_interpolate_from_centre) { + { + std::vector fields{dx, dy, dz, g11, g22, g33, g12, g13, + g23, g_11, g_22, g_33, g_12, g_13, g_23, J}; + for (auto& f : fields) { + f.allowParallelSlices(false); + } + } 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); @@ -1015,15 +1030,13 @@ int Coordinates::geometry(bool recalculate_staggered, G3_23 = 0.5 * g13 * (DDZ(g_12) + DDY(g_13) - DDX(g_23)) + 0.5 * g23 * DDZ(g_22) + 0.5 * g33 * DDY(g_33); - auto tmp = J * g12; - localmesh->communicate_no_slices(tmp); - G1 = (DDX(J * g11) + DDY(tmp) + DDZ(J * g13)) / J; - tmp = J * g22; - localmesh->communicate_no_slices(tmp); - G2 = (DDX(J * g12) + DDY(tmp) + DDZ(J * g23)) / J; - tmp = J * g23; - localmesh->communicate_no_slices(tmp); - G3 = (DDX(J * g13) + DDY(tmp) + DDZ(J * g33)) / J; + if (J.isFci()) { + G1 = G2 = G3 = BoutNaN; + } else { + G1 = (DDX(J * g11) + DDY(J * g12) + DDZ(J * g13)) / J; + G2 = (DDX(J * g12) + DDY(J * g22) + DDZ(J * g23)) / J; + G3 = (DDX(J * g13) + DDY(J * g23) + DDZ(J * g33)) / J; + } // Communicate christoffel symbol terms output_progress.write("\tCommunicating connection terms\n"); @@ -1102,7 +1115,7 @@ int Coordinates::geometry(bool recalculate_staggered, 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); // d/di(1/dy) + d1_dy = DDY(1. / dy.asField3DParallel()); // d/di(1/dy) localmesh->communicate_no_slices(d1_dy); d1_dy = @@ -1259,6 +1272,31 @@ int Coordinates::calcCovariant(const std::string& region) { output_info.write("\tLocal maximum error in off-diagonal inversion is {:e}\n", maxerr); + if (Bxy.isFci()) { + BoutReal maxError = 0; + Options* options = Options::getRoot(); + auto BJg = Bxy.asField3DParallel() * J / sqrt(g_22.asField3DParallel()); + auto* mesh = localmesh; + for (int p = -mesh->ystart; p <= mesh->ystart; p++) { + if (p == 0) { + continue; + } + BOUT_FOR(i, BJg.getRegion("RGN_NOBNDRY")) { + auto local = BJg[i] / BJg.ynext(p)[i.yp(p)]; + maxError = std::max(std::abs(local - 1), maxError); + } + } + const BoutReal allowedError = (*options)["allowedFluxError"].withDefault(1e-6); + if (maxError < allowedError / 100) { + output_info.write("\tInfo: The maximum flux conservation error is {:e}", maxError); + } else if (maxError < allowedError) { + output_warn.write("\tWarning: The maximum flux conservation error is {:e}", + maxError); + } else { + throw BoutException("Error: The maximum flux conservation error is {:e}", maxError); + } + } + return 0; } @@ -1485,16 +1523,8 @@ Coordinates::FieldMetric Coordinates::DDY(const Field2D& f, CELL_LOC loc, return bout::derivatives::index::DDY(f, loc, method, region) / dy; } -Field3D Coordinates::DDY(const Field3D& f, CELL_LOC outloc, const std::string& method, - const std::string& region) const { -#if BOUT_USE_METRIC_3D - if (!f.hasParallelSlices() and !transform->canToFromFieldAligned()) { - Field3D f_parallel = f; - transform->calcParallelSlices(f_parallel); - f_parallel.applyParallelBoundary("parallel_neumann_o2"); - return bout::derivatives::index::DDY(f_parallel, outloc, method, region); - } -#endif +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; }; @@ -1526,12 +1556,12 @@ Coordinates::FieldMetric Coordinates::Grad_par(const Field2D& var, return DDY(var) * invSg(); } -Field3D Coordinates::Grad_par(const Field3D& var, CELL_LOC outloc, +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(); + return setName(::DDY(var, outloc, method) * invSg(), "Grad_par({:s})", var.name); } ///////////////////////////////////////////////////////// @@ -1546,8 +1576,8 @@ Coordinates::FieldMetric Coordinates::Vpar_Grad_par(const Field2D& v, const Fiel return VDDY(v, f) * invSg(); } -Field3D Coordinates::Vpar_Grad_par(const Field3D& v, const Field3D& f, CELL_LOC outloc, - const std::string& method) { +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(); @@ -1568,14 +1598,14 @@ Coordinates::FieldMetric Coordinates::Div_par(const Field2D& f, CELL_LOC outloc, return Bxy * Grad_par(f / Bxy_floc, outloc, method); } -Field3D Coordinates::Div_par(const Field3D& f, CELL_LOC outloc, +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 - auto Bxy_floc = f.getCoordinates()->Bxy; + const auto& Bxy_floc = f.getCoordinates()->Bxy; if (!f.hasParallelSlices()) { // No yup/ydown fields. The Grad_par operator will @@ -1583,14 +1613,8 @@ Field3D Coordinates::Div_par(const Field3D& f, CELL_LOC outloc, return Bxy * Grad_par(f / Bxy_floc, outloc, method); } - // Need to modify yup and ydown fields - Field3D f_B = f / Bxy_floc; - f_B.splitParallelSlices(); - for (int i = 0; i < f.getMesh()->ystart; ++i) { - f_B.yup(i) = f.yup(i) / Bxy_floc.yup(i); - f_B.ydown(i) = f.ydown(i) / Bxy_floc.ydown(i); - } - return Bxy * Grad_par(f_B, outloc, method); + ASSERT3(f.getCoordinates() == this); + return setName(Jg() * Grad_par(f / Jg(), outloc, method), "Div_par({:s})", f.name); } ///////////////////////////////////////////////////////// @@ -1608,7 +1632,7 @@ Coordinates::FieldMetric Coordinates::Grad2_par2(const Field2D& f, CELL_LOC outl return result; } -Field3D Coordinates::Grad2_par2(const Field3D& f, CELL_LOC outloc, +Field3D Coordinates::Grad2_par2(const Field3DParallel& f, CELL_LOC outloc, const std::string& method) { if (outloc == CELL_DEFAULT) { @@ -1774,9 +1798,10 @@ Coordinates::FieldMetric Coordinates::Laplace_par(const Field2D& f, CELL_LOC out return D2DY2(f, outloc) / g_22 + DDY(J / g_22, outloc) * DDY(f, outloc) / J; } -Field3D Coordinates::Laplace_par(const Field3D& f, CELL_LOC outloc) { +Field3D Coordinates::Laplace_par(const Field3DParallel& f, CELL_LOC outloc) { ASSERT1(location == outloc || outloc == CELL_DEFAULT); - return D2DY2(f, outloc) / g_22 + DDY(J / 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 @@ -1796,7 +1821,7 @@ Coordinates::FieldMetric Coordinates::Laplace(const Field2D& f, CELL_LOC outloc, return result; } -Field3D Coordinates::Laplace(const Field3D& f, CELL_LOC outloc, +Field3D Coordinates::Laplace(const Field3DParallel& f, CELL_LOC outloc, const std::string& dfdy_boundary_conditions, const std::string& dfdy_dy_region) { @@ -1878,13 +1903,33 @@ Field2D Coordinates::Laplace_perpXY([[maybe_unused]] const Field2D& A, const Coordinates::FieldMetric& Coordinates::invSg() const { if (invSgCache == nullptr) { - auto ptr = std::make_unique(); + auto ptr = std::make_unique(); (*ptr) = 1.0 / sqrt(g_22); invSgCache = std::move(ptr); } return *invSgCache; } +const Coordinates::FieldMetric& Coordinates::Jg() const { + if (not JgCache.has_value()) { + const auto* coords = this; // + // Need to modify yup and ydown fields + auto Jg = coords->Jxz(); +#if BOUT_USE_METRIC_3D + Jg.splitParallelSlicesAndAllocate(); + auto B = coords->Bxy; + for (size_t j = 0; j < B.numberParallelSlices(); ++j) { + BOUT_FOR(i, B.getRegion("RGN_NOBNDRY")) { + Jg.yup(j)[i.yp(j + 1)] = Jg[i] * B.yup(j)[i.yp(j + 1)] / B[i]; + Jg.ydown(j)[i.ym(j + 1)] = Jg[i] * B.ydown(j)[i.ym(j + 1)] / B[i]; + } + } +#endif + JgCache = Jg; + } + return *JgCache; +} + 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); @@ -1898,7 +1943,7 @@ Coordinates::Grad2_par2_DDY_invSg(CELL_LOC outloc, const std::string& method) co invSgCache->applyParallelBoundary("parallel_neumann_o2"); // cache - auto ptr = std::make_unique(); + 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]; @@ -2007,3 +2052,127 @@ void Coordinates::checkContravariant() { } } } + +const Coordinates::FieldMetric& Coordinates::g_22_ylow() const { + if (_g_22_ylow.has_value()) { + return *_g_22_ylow; + } + _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) { //, CELL_YLOW)) { + 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")) { + _g_22_ylow.value()[i] = SQ(0.5 * (sqrt(g_22[i]) + sqrt(g_22[i.ym()]))); + } + } + return g_22_ylow(); +} + +const Coordinates::FieldMetric& Coordinates::g_22_yhigh() const { + if (_g_22_yhigh.has_value()) { + return *_g_22_yhigh; + } + _g_22_yhigh.emplace(emptyFrom(g_22)); + //_g_22_yhigh->setLocation(CELL_YHIGH); + auto* mesh = Bxy.getMesh(); + if (Bxy.isFci()) { + if (mesh->get(_g_22_yhigh.value(), "g_22_cell_yhigh", 0.0, + false) + != 0) { //, CELL_YHIGH)) { + 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")) { + _g_22_yhigh.value()[i] = SQ(0.5 * (sqrt(g_22[i]) + sqrt(g_22[i.yp()]))); + } + } + return g_22_yhigh(); +} + +Coordinates::FieldMetric& Coordinates::g_22_yhigh() { + return const_cast( + const_cast(this)->g_22_yhigh()); +} + +Coordinates::FieldMetric& Coordinates::g_22_ylow() { + return const_cast( + const_cast(this)->g_22_ylow()); +} + +const Coordinates::FieldMetric& Coordinates::Jxz_ylow() const { + if (!_jxz_ylow.has_value()) { + _compute_Jxz_cell_faces(); + } + return *_jxz_ylow; +} +const Coordinates::FieldMetric& Coordinates::Jxz_yhigh() const { + if (!_jxz_yhigh.has_value()) { + _compute_Jxz_cell_faces(); + } + return *_jxz_yhigh; +} +const Coordinates::FieldMetric& Coordinates::Jxz() const { + if (!_jxz_centre.has_value()) { + _compute_Jxz_cell_faces(); + } + return *_jxz_centre; +} + +Coordinates::FieldMetric& Coordinates::Jxz_ylow() { + if (!_jxz_ylow.has_value()) { + _compute_Jxz_cell_faces(); + } + return *_jxz_ylow; +} +Coordinates::FieldMetric& Coordinates::Jxz_yhigh() { + if (!_jxz_yhigh.has_value()) { + _compute_Jxz_cell_faces(); + } + return *_jxz_yhigh; +} +Coordinates::FieldMetric& Coordinates::Jxz() { + if (!_jxz_centre.has_value()) { + _compute_Jxz_cell_faces(); + } + return *_jxz_centre; +} + +void Coordinates::_compute_Jxz_cell_faces() const { + _jxz_centre.emplace(sqrt(g_11 * g_33 - SQ(g_13))); + _jxz_ylow.emplace(emptyFrom(_jxz_centre.value())); + //_jxz_ylow->setLocation(CELL_YLOW); + _jxz_yhigh.emplace(emptyFrom(_jxz_centre.value())); + //_jxz_yhigh->setLocation(CELL_YHIGH); + auto* mesh = _jxz_centre->getMesh(); + if (Bxy.isFci()) { + Coordinates::FieldMetric By_c; + Coordinates::FieldMetric By_h; + Coordinates::FieldMetric By_l; + if (mesh->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) { //, CELL_YLOW)) { + throw BoutException("The grid file does not contain `By_cell_ylow`."); + } + if (mesh->get(By_h, "By_cell_yhigh", 0.0, false) != 0) { //, CELL_YHIGH)) { + 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]; + } + } else { + ASSERT0(mesh->ystart > 0); + BOUT_FOR(i, _jxz_centre->getRegion("RGN_NOY")) { + (*_jxz_ylow)[i] = 0.5 * ((*_jxz_centre)[i] + (*_jxz_centre)[i.ym()]); + (*_jxz_yhigh)[i] = 0.5 * ((*_jxz_centre)[i] + (*_jxz_centre)[i.yp()]); + } + } +} diff --git a/src/mesh/difops.cxx b/src/mesh/difops.cxx index 09433b0685..bf7877907b 100644 --- a/src/mesh/difops.cxx +++ b/src/mesh/difops.cxx @@ -25,6 +25,8 @@ #include "bout/build_defines.hxx" +#include "bout/index_derivs_interface.hxx" +#include "bout/region.hxx" #include #include #include @@ -233,7 +235,21 @@ Field3D Div_par(const Field3D& f, const std::string& method, CELL_LOC outloc) { return f.getCoordinates(outloc)->Div_par(f, outloc, method); } -Field3D Div_par(const Field3D& f, const Field3D& v) { +Field3D Div_par(const Field3D& f_in, const Field3D& v_in) { +#if BOUT_USE_FCI_AUTOMAGIC + auto f{f_in}; + auto v{v_in}; + if (!f.hasParallelSlices()) { + f.calcParallelSlices(); + } + if (!v.hasParallelSlices()) { + v.calcParallelSlices(); + } +#else + const auto& f{f_in}; + const auto& v{v_in}; +#endif + ASSERT1_FIELDS_COMPATIBLE(f, v); ASSERT1(f.hasParallelSlices()); ASSERT1(v.hasParallelSlices()); @@ -366,6 +382,116 @@ Field3D Div_par_K_Grad_par(const Field3D& kY, const Field3D& f, CELL_LOC outloc) + Div_par(kY, outloc) * Grad_par(f, outloc); } +Field3D Div_par_K_Grad_par_mod(const Field3D& Kin, const Field3D& fin, Field3D& flow_ylow, + bool bndry_flux) { + TRACE("FV::Div_par_K_Grad_par_mod"); + + ASSERT2(Kin.getLocation() == fin.getLocation()); + + const Mesh* mesh = Kin.getMesh(); + const Coordinates* coord = fin.getCoordinates(); + + if (Kin.isFci()) { + ASSERT1(Kin.hasParallelSlices()); + ASSERT1(fin.hasParallelSlices()); + // Using parallel slices. + // Note: Y slices may use different coordinate systems + // -> Only B, dy and g_22 can be used in yup/ydown + // Others (e.g J) may not be averaged between y planes. + + const auto& K_up = Kin.yup(); + const auto& K_down = Kin.ydown(); + + const auto& f_up = fin.yup(); + const auto& f_down = fin.ydown(); + + Field3D result{zeroFrom(fin)}; + flow_ylow = zeroFrom(fin); + +#if BOUT_USE_OPENMP + // ensure they are loaded, before they are used + coord->Jxz_yhigh(); + coord->Jxz_ylow(); + coord->g_22_yhigh(); + coord->g_22_ylow(); +#endif + + BOUT_FOR(i, result.getRegion("RGN_NOBNDRY")) { + const auto iyp = i.yp(); + const auto iym = i.ym(); + + // Upper cell edge + const BoutReal c_up = 0.5 * (Kin[i] + K_up[iyp]); // K at the upper boundary + + const BoutReal Jxz_up = coord->Jxz_yhigh()[i] / sqrt(coord->g_22_yhigh()[i]); + + const BoutReal gradient_up = + 2. * (f_up[iyp] - fin[i]) / (coord->dy[i] + coord->dy.yup()[iyp]); + + const BoutReal flux_up = c_up * Jxz_up * gradient_up; + + // Lower cell edge + const BoutReal c_down = 0.5 * (Kin[i] + K_down[iym]); // K at the lower boundary + const BoutReal Jxz_down = coord->Jxz_ylow()[i] / sqrt(coord->g_22_ylow()[i]); + const BoutReal gradient_down = + 2. * (fin[i] - f_down[iym]) / (coord->dy[i] + coord->dy.ydown()[iym]); + + const BoutReal flux_down = c_down * Jxz_down * gradient_down; + + result[i] = (flux_up - flux_down) / (coord->dy[i] * coord->J[i]); + } + + return result; + } + + // Calculate in field-aligned coordinates + const auto& K = toFieldAligned(Kin, "RGN_NOX"); + const auto& f = toFieldAligned(fin, "RGN_NOX"); + + Field3D result{zeroFrom(f)}; + flow_ylow = zeroFrom(f); + + BOUT_FOR(i, result.getRegion("RGN_NOBNDRY")) { + // Calculate flux at upper surface + const auto ix = i.x(); + const auto iy = i.y(); + const auto iyp = i.yp(); + const auto iym = i.ym(); + + 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 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]); + } + + // 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 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]; + } + } + + // Shifted to field aligned coordinates, so need to shift back + result = fromFieldAligned(result, "RGN_NOBNDRY"); + flow_ylow = fromFieldAligned(flow_ylow); + + return result; +} + /******************************************************************************* * Delp2 * perpendicular Laplacian operator diff --git a/src/mesh/fv_ops.cxx b/src/mesh/fv_ops.cxx index fab8beb794..be6fa0f9bc 100644 --- a/src/mesh/fv_ops.cxx +++ b/src/mesh/fv_ops.cxx @@ -1,3 +1,6 @@ +#include "bout/bout_types.hxx" +#include "bout/boutexception.hxx" +#include "bout/difops.hxx" #include #include #include @@ -33,28 +36,19 @@ Field3D Div_a_Grad_perp(const Field3D& a, const Field3D& f) { // Flux in x - int xs = mesh->xstart - 1; - int xe = mesh->xend; - - /* - if(mesh->firstX()) - xs += 1; - */ - /* - if(mesh->lastX()) - xe -= 1; - */ + const int xs = mesh->xstart - 1; + const int xe = mesh->xend; for (int i = xs; i <= xe; i++) { for (int j = mesh->ystart; j <= mesh->yend; j++) { for (int k = mesh->zstart; k <= mesh->zend; k++) { // Calculate flux from i to i+1 - BoutReal fout = 0.5 * (a(i, j, k) + a(i + 1, j, k)) - * (coord->J(i, j, k) * coord->g11(i, j, k) - + coord->J(i + 1, j, k) * coord->g11(i + 1, j, k)) - * (f(i + 1, j, k) - f(i, j, k)) - / (coord->dx(i, j, k) + coord->dx(i + 1, j, k)); + const BoutReal fout = 0.5 * (a(i, j, k) + a(i + 1, j, k)) + * (coord->J(i, j, k) * coord->g11(i, j, k) + + coord->J(i + 1, j, k) * coord->g11(i + 1, j, k)) + * (f(i + 1, j, k) - f(i, j, k)) + / (coord->dx(i, j, k) + coord->dx(i + 1, j, k)); result(i, j, k) += fout / (coord->dx(i, j, k) * coord->J(i, j, k)); result(i + 1, j, k) -= fout / (coord->dx(i + 1, j, k) * coord->J(i + 1, j, k)); @@ -62,17 +56,8 @@ Field3D Div_a_Grad_perp(const Field3D& a, const Field3D& f) { } } - const bool fci = f.hasParallelSlices() && a.hasParallelSlices(); - - if (bout::build::use_metric_3d and fci) { - // 3D Metric, need yup/ydown fields. - // Requires previous communication of metrics - // -- should insert communication here? - if (!coord->g23.hasParallelSlices() || !coord->g_23.hasParallelSlices() - || !coord->dy.hasParallelSlices() || !coord->dz.hasParallelSlices() - || !coord->Bxy.hasParallelSlices() || !coord->J.hasParallelSlices()) { - throw BoutException("metrics have no yup/down: Maybe communicate in init?"); - } + if (a.isFci()) { + throw BoutException("FCI does not work with FV methods in y direction"); } // Y and Z fluxes require Y derivatives @@ -81,11 +66,11 @@ Field3D Div_a_Grad_perp(const Field3D& a, const Field3D& f) { // Values on this y slice (centre). // This is needed because toFieldAligned may modify the field - const auto f_slice = makeslices(fci, f); - const auto a_slice = makeslices(fci, a); + const auto f_slice = makeslices(false, f); + const auto a_slice = makeslices(false, a); // Only in 3D case with FCI do the metrics have parallel slices - const bool metric_fci = fci and bout::build::use_metric_3d; + const bool metric_fci = false; const auto g23 = makeslices(metric_fci, coord->g23); const auto g_23 = makeslices(metric_fci, coord->g_23); const auto J = makeslices(metric_fci, coord->J); @@ -95,9 +80,7 @@ Field3D Div_a_Grad_perp(const Field3D& a, const Field3D& f) { // Result of the Y and Z fluxes Field3D yzresult(0.0, mesh); - if (!fci) { - yzresult.setDirectionY(YDirectionType::Aligned); - } + yzresult.setDirectionY(YDirectionType::Aligned); // Y flux @@ -168,24 +151,22 @@ Field3D Div_a_Grad_perp(const Field3D& a, const Field3D& f) { } } - // Check if we need to transform back - if (fci) { - result += yzresult; - } else { - result += fromFieldAligned(yzresult); - } + result += fromFieldAligned(yzresult); return result; } -const Field3D Div_par_K_Grad_par(const Field3D& Kin, const Field3D& fin, - bool bndry_flux) { +Field3D Div_par_K_Grad_par(const Field3D& Kin, const Field3D& fin, bool bndry_flux) { + + if (Kin.isFci()) { + return ::Div_par_K_Grad_par(Kin, fin); + } ASSERT2(Kin.getLocation() == fin.getLocation()); - Mesh* mesh = Kin.getMesh(); + const Mesh* mesh = Kin.getMesh(); - bool use_parallel_slices = (Kin.hasParallelSlices() && fin.hasParallelSlices()); + const bool use_parallel_slices = (Kin.hasParallelSlices() && fin.hasParallelSlices()); const auto& K = use_parallel_slices ? Kin : toFieldAligned(Kin, "RGN_NOX"); const auto& f = use_parallel_slices ? fin : toFieldAligned(fin, "RGN_NOX"); @@ -209,13 +190,13 @@ const Field3D Div_par_K_Grad_par(const Field3D& Kin, const Field3D& fin, if (bndry_flux || mesh->periodicY(i.x()) || !mesh->lastY(i.x()) || (i.y() != mesh->yend)) { - BoutReal c = 0.5 * (K[i] + Kup[iyp]); // K at the upper boundary - BoutReal J = 0.5 * (coord->J[i] + coord->J[iyp]); // Jacobian at boundary - BoutReal g_22 = 0.5 * (coord->g_22[i] + coord->g_22[iyp]); + 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]); - 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]); - BoutReal flux = c * J * gradient / g_22; + const BoutReal flux = c * J * gradient / g_22; result[i] += flux / (coord->dy[i] * coord->J[i]); } @@ -223,14 +204,15 @@ const Field3D Div_par_K_Grad_par(const Field3D& Kin, const Field3D& fin, // Calculate flux at lower surface if (bndry_flux || mesh->periodicY(i.x()) || !mesh->firstY(i.x()) || (i.y() != mesh->ystart)) { - BoutReal c = 0.5 * (K[i] + Kdown[iym]); // K at the lower boundary - BoutReal J = 0.5 * (coord->J[i] + coord->J[iym]); // Jacobian at 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 - 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]); - BoutReal gradient = 2. * (f[i] - fdown[iym]) / (coord->dy[i] + coord->dy[iym]); + const BoutReal gradient = + 2. * (f[i] - fdown[iym]) / (coord->dy[i] + coord->dy[iym]); - BoutReal flux = c * J * gradient / g_22; + const BoutReal flux = c * J * gradient / g_22; result[i] -= flux / (coord->dy[i] * coord->J[i]); } @@ -244,10 +226,10 @@ const Field3D Div_par_K_Grad_par(const Field3D& Kin, const Field3D& fin, return result; } -const Field3D D4DY4(const Field3D& d_in, const Field3D& f_in) { +Field3D D4DY4(const Field3D& d_in, const Field3D& f_in) { ASSERT1_FIELDS_COMPATIBLE(d_in, f_in); - Mesh* mesh = d_in.getMesh(); + const Mesh* mesh = d_in.getMesh(); Coordinates* coord = f_in.getCoordinates(); @@ -263,9 +245,9 @@ const Field3D D4DY4(const Field3D& d_in, const Field3D& f_in) { for (int i = mesh->xstart; i <= mesh->xend; i++) { // Check for boundaries - bool yperiodic = mesh->periodicY(i); - bool has_upper_boundary = !yperiodic && mesh->lastY(i); - bool has_lower_boundary = !yperiodic && mesh->firstY(i); + const bool yperiodic = mesh->periodicY(i); + const bool has_upper_boundary = !yperiodic && mesh->lastY(i); + const bool has_lower_boundary = !yperiodic && mesh->firstY(i); // Always calculate fluxes at upper Y cell boundary const int ystart = @@ -281,15 +263,15 @@ const Field3D D4DY4(const Field3D& d_in, const Field3D& f_in) { for (int j = ystart; j <= yend; j++) { for (int k = mesh->zstart; k <= mesh->zend; k++) { - BoutReal dy3 = SQ(coord->dy(i, j, k)) * coord->dy(i, j, k); + const BoutReal dy3 = SQ(coord->dy(i, j, k)) * coord->dy(i, j, k); // 3rd derivative at upper boundary - BoutReal d3fdy3 = + const BoutReal d3fdy3 = (f(i, j + 2, k) - 3. * f(i, j + 1, k) + 3. * f(i, j, k) - f(i, j - 1, k)) / dy3; - BoutReal flux = 0.5 * (d(i, j, k) + d(i, j + 1, k)) - * (coord->J(i, j, k) + coord->J(i, j + 1, k)) * d3fdy3; + const BoutReal flux = 0.5 * (d(i, j, k) + d(i, j + 1, k)) + * (coord->J(i, j, k) + coord->J(i, j + 1, k)) * d3fdy3; result(i, j, k) += flux / (coord->J(i, j, k) * coord->dy(i, j, k)); result(i, j + 1, k) -= flux / (coord->J(i, j + 1, k) * coord->dy(i, j + 1, k)); @@ -301,8 +283,8 @@ const Field3D D4DY4(const Field3D& d_in, const Field3D& f_in) { return are_unaligned ? fromFieldAligned(result, "RGN_NOBNDRY") : result; } -const Field3D D4DY4_Index(const Field3D& f_in, bool bndry_flux) { - Mesh* mesh = f_in.getMesh(); +Field3D D4DY4_Index(const Field3D& f_in, bool bndry_flux) { + const Mesh* mesh = f_in.getMesh(); // Convert to field aligned coordinates const bool is_unaligned = (f_in.getDirectionY() == YDirectionType::Standard); @@ -313,10 +295,10 @@ const Field3D D4DY4_Index(const Field3D& f_in, bool bndry_flux) { Coordinates* coord = f_in.getCoordinates(); for (int i = mesh->xstart; i <= mesh->xend; i++) { - bool yperiodic = mesh->periodicY(i); + const bool yperiodic = mesh->periodicY(i); - bool has_upper_boundary = !yperiodic && mesh->lastY(i); - bool has_lower_boundary = !yperiodic && mesh->firstY(i); + const bool has_upper_boundary = !yperiodic && mesh->lastY(i); + const bool has_lower_boundary = !yperiodic && mesh->firstY(i); for (int j = mesh->ystart; j <= mesh->yend; j++) { @@ -341,8 +323,8 @@ const Field3D D4DY4_Index(const Field3D& f_in, bool bndry_flux) { // Not on domain boundary // 3rd derivative at right cell boundary - const BoutReal d3fdx3 = - (f(i, j + 2, k) - 3. * f(i, j + 1, k) + 3. * f(i, j, k) - f(i, j - 1, k)); + const BoutReal d3fdx3 = (f(i, j + 2, k) - (3. * f(i, j + 1, k)) + + (3. * f(i, j, k)) - f(i, j - 1, k)); result(i, j, k) += d3fdx3 * factor_rc; result(i, j + 1, k) -= d3fdx3 * factor_rp; @@ -363,10 +345,10 @@ const Field3D D4DY4_Index(const Field3D& f_in, bool bndry_flux) { common_factor / (coord->J(i, j + 1, k) * coord->dy(i, j + 1, k)); const BoutReal d3fdx3 = - -((16. / 5) * 0.5 * (f(i, j + 1, k) + f(i, j, k)) // Boundary value f_b - - 6. * f(i, j, k) // f_0 - + 4. * f(i, j - 1, k) // f_1 - - (6. / 5) * f(i, j - 2, k) // f_2 + -(((16. / 5) * 0.5 * (f(i, j + 1, k) + f(i, j, k))) // Boundary value f_b + - (6. * f(i, j, k)) // f_0 + + (4. * f(i, j - 1, k)) // f_1 + - ((6. / 5) * f(i, j - 2, k)) // f_2 ); result(i, j, k) += d3fdx3 * factor_rc; @@ -392,8 +374,8 @@ const Field3D D4DY4_Index(const Field3D& f_in, bool bndry_flux) { common_factor / (coord->J(i, j - 1, k) * coord->dy(i, j - 1, k)); // Not on a domain boundary - const BoutReal d3fdx3 = - (f(i, j + 1, k) - 3. * f(i, j, k) + 3. * f(i, j - 1, k) - f(i, j - 2, k)); + const BoutReal d3fdx3 = (f(i, j + 1, k) - (3. * f(i, j, k)) + + (3. * f(i, j - 1, k)) - f(i, j - 2, k)); result(i, j, k) -= d3fdx3 * factor_lc; result(i, j - 1, k) += d3fdx3 * factor_lm; @@ -410,10 +392,10 @@ const Field3D D4DY4_Index(const Field3D& f_in, bool bndry_flux) { const BoutReal factor_lm = common_factor / (coord->J(i, j - 1, k) * coord->dy(i, j - 1, k)); const BoutReal d3fdx3 = - -(-(16. / 5) * 0.5 * (f(i, j - 1, k) + f(i, j, k)) // Boundary value f_b - + 6. * f(i, j, k) // f_0 - - 4. * f(i, j + 1, k) // f_1 - + (6. / 5) * f(i, j + 2, k) // f_2 + -((-(16. / 5) * 0.5 * (f(i, j - 1, k) + f(i, j, k))) // Boundary value f_b + + (6. * f(i, j, k)) // f_0 + - (4. * f(i, j + 1, k)) // f_1 + + ((6. / 5) * f(i, j + 2, k)) // f_2 ); result(i, j, k) -= d3fdx3 * factor_lc; @@ -436,8 +418,9 @@ void communicateFluxes(Field3D& f) { throw BoutException("communicateFluxes: Sorry!"); } - int size = mesh->LocalNy * mesh->LocalNz; - comm_handle xin, xout; + const int size = mesh->LocalNy * mesh->LocalNz; + comm_handle xin = nullptr; + comm_handle xout = nullptr; // Cache results to silence spurious compiler warning about xin, // xout possibly being uninitialised when used const bool not_first = mesh->periodicX || !mesh->firstX(); @@ -496,45 +479,45 @@ Field3D Div_Perp_Lap(const Field3D& a, const Field3D& f, CELL_LOC outloc) { // o --- gD --- o // Coordinates* coords = a.getCoordinates(outloc); - Mesh* mesh = f.getMesh(); + const Mesh* mesh = f.getMesh(); for (int i = mesh->xstart; i <= mesh->xend; i++) { for (int j = mesh->ystart; j <= mesh->yend; j++) { for (int k = 0; k < mesh->LocalNz; k++) { // wrap k-index around as Z is (currently) periodic. - int kp = (k + 1) % (mesh->LocalNz); - int km = (k - 1 + mesh->LocalNz) % (mesh->LocalNz); + const int kp = (k + 1) % (mesh->LocalNz); + const int km = (k - 1 + mesh->LocalNz) % (mesh->LocalNz); // Calculate gradients on cell faces -- assumes constant grid spacing - BoutReal gR = - (coords->g11(i, j, k) + coords->g11(i + 1, j, k)) - * (f(i + 1, j, k) - f(i, j, k)) - / (coords->dx(i + 1, j, k) + coords->dx(i, j, k)) - + 0.5 * (coords->g13(i, j, k) + coords->g13(i + 1, j, k)) - * (f(i + 1, j, kp) - f(i + 1, j, km) + f(i, j, kp) - f(i, j, km)) - / (4. * coords->dz(i, j, k)); - - BoutReal gL = - (coords->g11(i - 1, j, k) + coords->g11(i, j, k)) - * (f(i, j, k) - f(i - 1, j, k)) - / (coords->dx(i - 1, j, k) + coords->dx(i, j, k)) - + 0.5 * (coords->g13(i - 1, j, k) + coords->g13(i, j, k)) - * (f(i - 1, j, kp) - f(i - 1, j, km) + f(i, j, kp) - f(i, j, km)) - / (4 * coords->dz(i, j, k)); - - BoutReal gD = - coords->g13(i, j, k) - * (f(i + 1, j, km) - f(i - 1, j, km) + f(i + 1, j, k) - f(i - 1, j, k)) - / (4. * coords->dx(i, j, k)) - + coords->g33(i, j, k) * (f(i, j, k) - f(i, j, km)) / coords->dz(i, j, k); - - BoutReal gU = - coords->g13(i, j, k) - * (f(i + 1, j, kp) - f(i - 1, j, kp) + f(i + 1, j, k) - f(i - 1, j, k)) - / (4. * coords->dx(i, j, k)) - + coords->g33(i, j, k) * (f(i, j, kp) - f(i, j, k)) / coords->dz(i, j, k); + const BoutReal gR = + ((coords->g11(i, j, k) + coords->g11(i + 1, j, k)) + * (f(i + 1, j, k) - f(i, j, k)) + / (coords->dx(i + 1, j, k) + coords->dx(i, j, k))) + + (0.5 * (coords->g13(i, j, k) + coords->g13(i + 1, j, k)) + * (f(i + 1, j, kp) - f(i + 1, j, km) + f(i, j, kp) - f(i, j, km)) + / (4. * coords->dz(i, j, k))); + + const BoutReal gL = + ((coords->g11(i - 1, j, k) + coords->g11(i, j, k)) + * (f(i, j, k) - f(i - 1, j, k)) + / (coords->dx(i - 1, j, k) + coords->dx(i, j, k))) + + (0.5 * (coords->g13(i - 1, j, k) + coords->g13(i, j, k)) + * (f(i - 1, j, kp) - f(i - 1, j, km) + f(i, j, kp) - f(i, j, km)) + / (4 * coords->dz(i, j, k))); + + const BoutReal gD = + (coords->g13(i, j, k) + * (f(i + 1, j, km) - f(i - 1, j, km) + f(i + 1, j, k) - f(i - 1, j, k)) + / (4. * coords->dx(i, j, k))) + + (coords->g33(i, j, k) * (f(i, j, k) - f(i, j, km)) / coords->dz(i, j, k)); + + const BoutReal gU = + (coords->g13(i, j, k) + * (f(i + 1, j, kp) - f(i - 1, j, kp) + f(i + 1, j, k) - f(i - 1, j, k)) + / (4. * coords->dx(i, j, k))) + + (coords->g33(i, j, k) * (f(i, j, kp) - f(i, j, k)) / coords->dz(i, j, k)); // Flow right BoutReal flux = gR * 0.25 * (coords->J(i + 1, j, k) + coords->J(i, j, k)) diff --git a/src/mesh/impls/bout/boutmesh.cxx b/src/mesh/impls/bout/boutmesh.cxx index 4aaa760c04..c8a914c7cf 100644 --- a/src/mesh/impls/bout/boutmesh.cxx +++ b/src/mesh/impls/bout/boutmesh.cxx @@ -485,8 +485,20 @@ int BoutMesh::load() { } ASSERT0(MXG >= 0); - if (Mesh::get(MYG, "MYG") != 0) { - MYG = options["MYG"].doc("Number of guard cells on each side in Y").withDefault(2); + const bool meshHasMyg = Mesh::get(MYG, "MYG") == 0; + int meshMyg = 0; + if (!meshHasMyg) { + MYG = 2; + } else { + meshMyg = MYG; + } + if (options.isSet("MYG") or (!meshHasMyg)) { + MYG = options["MYG"].doc("Number of guard cells on each side in Y").withDefault(MYG); + } + if (meshHasMyg && MYG != meshMyg) { + output_warn.write(_("Options changed the number of y-guard cells. Grid has {} but " + "option specified {}! Continuing with {}"), + meshMyg, MYG, MYG); } ASSERT0(MYG >= 0); @@ -609,23 +621,28 @@ int BoutMesh::load() { // Add boundary regions addBoundaryRegions(); - // Set cached values - { - int mybndry = static_cast(!(iterateBndryLowerY().isDone())); - int allbndry = 0; - mpi->MPI_Allreduce(&mybndry, &allbndry, 1, MPI_INT, MPI_BOR, getXcomm(yend)); - has_boundary_lower_y = static_cast(allbndry); - } - { - int mybndry = static_cast(!(iterateBndryUpperY().isDone())); - int allbndry = 0; - mpi->MPI_Allreduce(&mybndry, &allbndry, 1, MPI_INT, MPI_BOR, getXcomm(ystart)); - has_boundary_upper_y = static_cast(allbndry); - } - // Initialize default coordinates getCoordinates(); + // Set cached values + if (isFci()) { + has_boundary_lower_y = false; + has_boundary_upper_y = false; + } else { + { + int mybndry = static_cast(!(iterateBndryLowerY().isDone())); + int allbndry = 0; + mpi->MPI_Allreduce(&mybndry, &allbndry, 1, MPI_INT, MPI_BOR, getXcomm(yend)); + has_boundary_lower_y = static_cast(allbndry); + } + { + int mybndry = static_cast(!(iterateBndryUpperY().isDone())); + int allbndry = 0; + mpi->MPI_Allreduce(&mybndry, &allbndry, 1, MPI_INT, MPI_BOR, getXcomm(ystart)); + has_boundary_upper_y = static_cast(allbndry); + } + } + output_info.write(_("\tdone\n")); return 0; @@ -2378,8 +2395,8 @@ int BoutMesh::pack_data(const std::vector& var_list, int xge, int xl ASSERT2(var3d_ref.isAllocated()); for (int jx = xge; jx != xlt; jx++) { for (int jy = yge; jy < ylt; jy++) { - for (int jz = 0; jz < LocalNz; jz++, len++) { - buffer[len] = var3d_ref(jx, jy, jz); + for (int jz = 0; jz < LocalNz; jz++) { + buffer[len++] = var3d_ref(jx, jy, jz); } } } @@ -2390,8 +2407,8 @@ int BoutMesh::pack_data(const std::vector& var_list, int xge, int xl auto& var2d_ref = *var2d_ref_ptr; ASSERT2(var2d_ref.isAllocated()); for (int jx = xge; jx != xlt; jx++) { - for (int jy = yge; jy < ylt; jy++, len++) { - buffer[len] = var2d_ref(jx, jy); + for (int jy = yge; jy < ylt; jy++) { + buffer[len++] = var2d_ref(jx, jy); } } } @@ -2412,8 +2429,8 @@ int BoutMesh::unpack_data(const std::vector& var_list, int xge, int auto& var3d_ref = *dynamic_cast(var); for (int jx = xge; jx != xlt; jx++) { for (int jy = yge; jy < ylt; jy++) { - for (int jz = 0; jz < LocalNz; jz++, len++) { - var3d_ref(jx, jy, jz) = buffer[len]; + for (int jz = 0; jz < LocalNz; jz++) { + var3d_ref(jx, jy, jz) = buffer[len++]; } } } @@ -2421,8 +2438,8 @@ int BoutMesh::unpack_data(const std::vector& var_list, int xge, int // 2D variable auto& var2d_ref = *dynamic_cast(var); for (int jx = xge; jx != xlt; jx++) { - for (int jy = yge; jy < ylt; jy++, len++) { - var2d_ref(jx, jy) = buffer[len]; + for (int jy = yge; jy < ylt; jy++) { + var2d_ref(jx, jy) = buffer[len++]; } } } @@ -2894,6 +2911,9 @@ void BoutMesh::addBoundaryRegions() { } RangeIterator BoutMesh::iterateBndryLowerInnerY() const { + if (this->isFci()) { + throw BoutException("FCI should never use this iterator"); + } int xs = 0; int xe = LocalNx - 1; @@ -2929,6 +2949,9 @@ RangeIterator BoutMesh::iterateBndryLowerInnerY() const { } RangeIterator BoutMesh::iterateBndryLowerOuterY() const { + if (this->isFci()) { + throw BoutException("FCI should never use this iterator"); + } int xs = 0; int xe = LocalNx - 1; @@ -2963,6 +2986,10 @@ RangeIterator BoutMesh::iterateBndryLowerOuterY() const { } RangeIterator BoutMesh::iterateBndryLowerY() const { + if (this->isFci()) { + throw BoutException("FCI should never use this iterator"); + } + int xs = 0; int xe = LocalNx - 1; if ((DDATA_INDEST >= 0) && (DDATA_XSPLIT > xstart)) { @@ -2992,6 +3019,10 @@ RangeIterator BoutMesh::iterateBndryLowerY() const { } RangeIterator BoutMesh::iterateBndryUpperInnerY() const { + if (this->isFci()) { + throw BoutException("FCI should never use this iterator"); + } + int xs = 0; int xe = LocalNx - 1; @@ -3026,6 +3057,10 @@ RangeIterator BoutMesh::iterateBndryUpperInnerY() const { } RangeIterator BoutMesh::iterateBndryUpperOuterY() const { + if (this->isFci()) { + throw BoutException("FCI should never use this iterator"); + } + int xs = 0; int xe = LocalNx - 1; @@ -3060,6 +3095,10 @@ RangeIterator BoutMesh::iterateBndryUpperOuterY() const { } RangeIterator BoutMesh::iterateBndryUpperY() const { + if (this->isFci()) { + throw BoutException("FCI should never use this iterator"); + } + int xs = 0; int xe = LocalNx - 1; if ((UDATA_INDEST >= 0) && (UDATA_XSPLIT > xstart)) { diff --git a/src/mesh/interpolation/bilinear_xz.cxx b/src/mesh/interpolation/bilinear_xz.cxx index 1ed18a7303..32a8aa3395 100644 --- a/src/mesh/interpolation/bilinear_xz.cxx +++ b/src/mesh/interpolation/bilinear_xz.cxx @@ -20,6 +20,7 @@ * **************************************************************************/ +#include "bout/boutexception.hxx" #include "bout/globals.hxx" #include "bout/interpolation_xz.hxx" #include "bout/mesh.hxx" diff --git a/src/mesh/interpolation/hermite_spline_xz.cxx b/src/mesh/interpolation/hermite_spline_xz.cxx index 5020a5b9a3..df4d3edffa 100644 --- a/src/mesh/interpolation/hermite_spline_xz.cxx +++ b/src/mesh/interpolation/hermite_spline_xz.cxx @@ -21,11 +21,17 @@ **************************************************************************/ #include "../impls/bout/boutmesh.hxx" +#include "../parallel/fci_comm.hxx" #include "bout/bout.hxx" #include "bout/globals.hxx" #include "bout/index_derivs_interface.hxx" #include "bout/interpolation_xz.hxx" +#include "bout/mask.hxx" +#include "bout/utils.hxx" +#include +#include +#include #include class IndConverter { @@ -35,7 +41,7 @@ class IndConverter { xstart(mesh->xstart), ystart(mesh->ystart), zstart(0), lnx(mesh->LocalNx - 2 * xstart), lny(mesh->LocalNy - 2 * ystart), lnz(mesh->LocalNz - 2 * zstart) {} - // ix and iy are global indices + // ix and iz are global indices // iy is local int fromMeshToGlobal(int ix, int iy, int iz) { const int xstart = mesh->xstart; @@ -102,11 +108,25 @@ class IndConverter { } }; -XZHermiteSpline::XZHermiteSpline(int y_offset, Mesh* meshin) +template +XZHermiteSplineBase::XZHermiteSplineBase(int y_offset, Mesh* meshin, + Options* options) : XZInterpolation(y_offset, meshin), h00_x(localmesh), h01_x(localmesh), h10_x(localmesh), h11_x(localmesh), h00_z(localmesh), h01_z(localmesh), h10_z(localmesh), h11_z(localmesh) { + if constexpr (monotonic) { + if (options == nullptr) { + options = &Options::root()["mesh:paralleltransform:xzinterpolation"]; + } + abs_fac_monotonic = (*options)["atol"] + .doc("Absolute tolerance for clipping overshoot") + .withDefault(abs_fac_monotonic); + rel_fac_monotonic = (*options)["rtol"] + .doc("Relative tolerance for clipping overshoot") + .withDefault(rel_fac_monotonic); + } + // Index arrays contain guard cells in order to get subscripts right i_corner.reallocate(localmesh->LocalNx, localmesh->LocalNy, localmesh->LocalNz); k_corner.reallocate(localmesh->LocalNx, localmesh->LocalNy, localmesh->LocalNz); @@ -139,6 +159,10 @@ XZHermiteSpline::XZHermiteSpline(int y_offset, Mesh* meshin) MatCreateAIJ(BoutComm::get(), m, m, M, M, 16, nullptr, 16, nullptr, &petscWeights); #endif #endif + if constexpr (monotonic) { + gf3daccess = std::make_unique(localmesh); + g3dinds.reallocate(localmesh->LocalNx, localmesh->LocalNy, localmesh->LocalNz); + } #ifndef HS_USE_PETSC if (localmesh->getNXPE() > 1) { throw BoutException("Require PETSc for MPI splitting in X"); @@ -146,16 +170,20 @@ XZHermiteSpline::XZHermiteSpline(int y_offset, Mesh* meshin) #endif } -void XZHermiteSpline::calcWeights(const Field3D& delta_x, const Field3D& delta_z, - const std::string& region) { +template +void XZHermiteSplineBase::calcWeights( + const Field3D& delta_x, const Field3D& delta_z, + [[maybe_unused]] const std::string& region) { const int ny = localmesh->LocalNy; const int nz = localmesh->LocalNz; - const int xend = (localmesh->xend - localmesh->xstart + 1) * localmesh->getNXPE() + const int xend = ((localmesh->xend - localmesh->xstart + 1) * localmesh->getNXPE()) + localmesh->xstart - 1; #ifdef HS_USE_PETSC IndConverter conv{localmesh}; #endif + [[maybe_unused]] const int y_global_offset = + localmesh->getYProcIndex() * (localmesh->yend - localmesh->ystart + 1); BOUT_FOR(i, getRegion(region)) { const int x = i.x(); const int y = i.y(); @@ -171,7 +199,19 @@ void XZHermiteSpline::calcWeights(const Field3D& delta_x, const Field3D& delta_z BoutReal t_x = delta_x(x, y, z) - static_cast(i_corn); BoutReal t_z = delta_z(x, y, z) - static_cast(k_corner(x, y, z)); - // NOTE: A (small) hack to avoid one-sided differences + // NOTE: A (small) hack to avoid one-sided differences. We need at + // least 2 interior points due to an awkwardness with the + // boundaries. The splines need derivatives in x, but we don't + // know the value in the boundaries, so _any_ interpolation in the + // last interior cell can't be done. Instead, we fudge the + // interpolation in the last cell to be at the extreme right-hand + // edge of the previous cell (that is, exactly on the last + // interior point). However, this doesn't work with only one + // interior point, because we have to do something similar to the + // _first_ cell, and these two fudges cancel out and we end up + // indexing into the boundary anyway. + // TODO(peter): Can we remove this if we apply (dirichlet?) BCs to + // the X derivatives? Note that we need at least _2_ if (i_corn >= xend) { i_corn = xend - 1; t_x = 1.0; @@ -288,6 +328,18 @@ void XZHermiteSpline::calcWeights(const Field3D& delta_x, const Field3D& delta_z } #endif #endif + if constexpr (monotonic) { + const auto gind = + gf3daccess->xyzg(i_corn, y + y_offset + y_global_offset, k_corner(x, y, z)); + gf3daccess->get(gind); + gf3daccess->get(gind.xp(1)); + gf3daccess->get(gind.zp(1)); + gf3daccess->get(gind.xp(1).zp(1)); + g3dinds[i] = {gind.ind, gind.xp(1).ind, gind.zp(1).ind, gind.xp(1).zp(1).ind}; + } + } + if constexpr (monotonic) { + gf3daccess->setup(); } #ifdef HS_USE_PETSC MatAssemblyBegin(petscWeights, MAT_FINAL_ASSEMBLY); @@ -299,8 +351,11 @@ void XZHermiteSpline::calcWeights(const Field3D& delta_x, const Field3D& delta_z #endif } -void XZHermiteSpline::calcWeights(const Field3D& delta_x, const Field3D& delta_z, - const BoutMask& mask, const std::string& region) { +template +void XZHermiteSplineBase::calcWeights(const Field3D& delta_x, + const Field3D& delta_z, + const BoutMask& mask, + const std::string& region) { setMask(mask); calcWeights(delta_x, delta_z, region); } @@ -321,8 +376,14 @@ void XZHermiteSpline::calcWeights(const Field3D& delta_x, const Field3D& delta_z * (i, j+1, k+1) h01_z + h10_z / 2 * (i, j+1, k+2) h11_z / 2 */ +template std::vector -XZHermiteSpline::getWeightsForYApproximation(int i, int j, int k, int yoffset) { +XZHermiteSplineBase::getWeightsForYApproximation(int i, int j, int k, + int yoffset) { + if (localmesh->getNXPE() > 1) { + throw BoutException("It is likely that the function calling this is not handling the " + "result correctly."); + } const int nz = localmesh->LocalNz; const int k_mod = k_corner(i, j, k); const int k_mod_m1 = (k_mod > 0) ? (k_mod - 1) : (nz - 1); @@ -335,16 +396,24 @@ XZHermiteSpline::getWeightsForYApproximation(int i, int j, int k, int yoffset) { {i, j + yoffset, k_mod_p2, 0.5 * h11_z(i, j, k)}}; } -Field3D XZHermiteSpline::interpolate(const Field3D& f, const std::string& region) const { +template +Field3D XZHermiteSplineBase::interpolate( + const Field3D& f, [[maybe_unused]] const std::string& region) const { + + const auto region2 = + y_offset == 0 ? "RGN_NOY" : fmt::format("RGN_YPAR_{:+d}", y_offset); ASSERT1(f.getMesh() == localmesh); Field3D f_interp{emptyFrom(f)}; - const auto region2 = - y_offset == 0 ? "RGN_NOY" : fmt::format("RGN_YPAR_{:+d}", y_offset); + std::unique_ptr gf; + if constexpr (monotonic) { + gf = gf3daccess->communicate_asPtr(f); + } -#if USE_NEW_WEIGHTS -#ifdef HS_USE_PETSC + // TODO(peter): Should we apply dirichlet BCs to derivatives? + +#if USE_NEW_WEIGHTS and defined(HS_USE_PETSC) BoutReal* ptr; const BoutReal* cptr; VecGetArray(rhs, &ptr); @@ -354,10 +423,10 @@ Field3D XZHermiteSpline::interpolate(const Field3D& f, const std::string& region VecGetArrayRead(result, &cptr); BOUT_FOR(i, f.getRegion(region2)) { f_interp[i] = cptr[int(i)]; - ASSERT2(std::isfinite(cptr[int(i)])); - } - VecRestoreArrayRead(result, &cptr); -#else + if constexpr (monotonic) { + const auto iyp = i; + const auto i = iyp.ym(y_offset); +#elif USE_NEW_WEIGHTS // No Petsc BOUT_FOR(i, getRegion(region)) { auto ic = i_corner[i]; auto iyp = i.yp(y_offset); @@ -369,9 +438,8 @@ Field3D XZHermiteSpline::interpolate(const Field3D& f, const std::string& region f_interp[iyp] += newWeights[w * 4 + 2][i] * f[ic.zp().xp(w - 1)]; f_interp[iyp] += newWeights[w * 4 + 3][i] * f[ic.zp(2).xp(w - 1)]; } - } -#endif -#else + if constexpr (monotonic) { +#else // Legacy interpolation // Derivatives are used for tension and need to be on dimensionless // coordinates @@ -410,6 +478,25 @@ Field3D XZHermiteSpline::interpolate(const Field3D& f, const std::string& region f_interp[iyp] = +f_z * h00_z[i] + f_zp1 * h01_z[i] + fz_z * h10_z[i] + fz_zp1 * h11_z[i]; + if constexpr (monotonic) { +#endif + const auto corners = {(*gf)[IndG3D(g3dinds[i][0])], (*gf)[IndG3D(g3dinds[i][1])], + (*gf)[IndG3D(g3dinds[i][2])], (*gf)[IndG3D(g3dinds[i][3])]}; + const auto minmax = std::minmax(corners); + + const auto diff = + ((minmax.second - minmax.first) * rel_fac_monotonic) + abs_fac_monotonic; + f_interp[iyp] = std::max(f_interp[iyp], minmax.first - diff); + f_interp[iyp] = std::min(f_interp[iyp], minmax.second + diff); + } +#if USE_NEW_WEIGHTS and defined(HS_USE_PETSC) + ASSERT2(std::isfinite(cptr[int(i)])); + } + VecRestoreArrayRead(result, &cptr); +#elif USE_NEW_WEIGHTS + ASSERT2(std::isfinite(f_interp[iyp])); + } +#else ASSERT2(std::isfinite(f_interp[iyp]) || i.x() < localmesh->xstart || i.x() > localmesh->xend); } @@ -419,15 +506,109 @@ Field3D XZHermiteSpline::interpolate(const Field3D& f, const std::string& region return f_interp; } -Field3D XZHermiteSpline::interpolate(const Field3D& f, const Field3D& delta_x, - const Field3D& delta_z, const std::string& region) { +template +Field3D XZHermiteSplineBase::interpolate(const Field3D& f, + const Field3D& delta_x, + const Field3D& delta_z, + const std::string& region) { calcWeights(delta_x, delta_z, region); return interpolate(f, region); } -Field3D XZHermiteSpline::interpolate(const Field3D& f, const Field3D& delta_x, - const Field3D& delta_z, const BoutMask& mask, - const std::string& region) { +template +Field3D +XZHermiteSplineBase::interpolate(const Field3D& f, const Field3D& delta_x, + const Field3D& delta_z, const BoutMask& mask, + const std::string& region) { calcWeights(delta_x, delta_z, mask, region); return interpolate(f, region); } + +// ensure they are instantiated +template class XZHermiteSplineBase; +template class XZHermiteSplineBase; + +Field3D XZMonotonicHermiteSplineLegacy::interpolate(const Field3D& f, + const std::string& region) const { + ASSERT1(f.getMesh() == localmesh); + Field3D f_interp(f.getMesh()); + f_interp.allocate(); + + // Derivatives are used for tension and need to be on dimensionless + // coordinates + Field3D fx = bout::derivatives::index::DDX(f, CELL_DEFAULT, "DEFAULT"); + localmesh->communicateXZ(fx); + // communicate in y, but do not calculate parallel slices + { + auto* h = localmesh->sendY(fx); + localmesh->wait(h); + } + Field3D fz = bout::derivatives::index::DDZ(f, CELL_DEFAULT, "DEFAULT", "RGN_ALL"); + localmesh->communicateXZ(fz); + // communicate in y, but do not calculate parallel slices + { + auto* h = localmesh->sendY(fz); + localmesh->wait(h); + } + Field3D fxz = bout::derivatives::index::DDX(fz, CELL_DEFAULT, "DEFAULT"); + localmesh->communicateXZ(fxz); + // communicate in y, but do not calculate parallel slices + { + auto* h = localmesh->sendY(fxz); + localmesh->wait(h); + } + + const auto curregion{getRegion(region)}; + BOUT_FOR(i, curregion) { + const auto iyp = i.yp(y_offset); + + const auto ic = i_corner[i]; + const auto iczp = ic.zp(); + const auto icxp = ic.xp(); + const auto icxpzp = iczp.xp(); + + // Interpolate f in X at Z + const BoutReal f_z = (f[ic] * h00_x[i]) + (f[icxp] * h01_x[i]) + (fx[ic] * h10_x[i]) + + (fx[icxp] * h11_x[i]); + + // Interpolate f in X at Z+1 + const BoutReal f_zp1 = (f[iczp] * h00_x[i]) + (f[icxpzp] * h01_x[i]) + + (fx[iczp] * h10_x[i]) + (fx[icxpzp] * h11_x[i]); + + // Interpolate fz in X at Z + const BoutReal fz_z = (fz[ic] * h00_x[i]) + (fz[icxp] * h01_x[i]) + + (fxz[ic] * h10_x[i]) + (fxz[icxp] * h11_x[i]); + + // Interpolate fz in X at Z+1 + const BoutReal fz_zp1 = (fz[iczp] * h00_x[i]) + (fz[icxpzp] * h01_x[i]) + + (fxz[iczp] * h10_x[i]) + (fxz[icxpzp] * h11_x[i]); + + // Interpolate in Z + BoutReal result = + (+f_z * h00_z[i]) + (f_zp1 * h01_z[i]) + (fz_z * h10_z[i]) + (fz_zp1 * h11_z[i]); + + ASSERT2(std::isfinite(result) || i.x() < localmesh->xstart + || i.x() > localmesh->xend); + + // Monotonicity + // Force the interpolated result to be in the range of the + // neighbouring cell values. This prevents unphysical overshoots, + // but also degrades accuracy near maxima and minima. + // Perhaps should only impose near boundaries, since that is where + // problems most obviously occur. + const BoutReal localmax = BOUTMAX(f[ic], f[icxp], f[iczp], f[icxpzp]); + + const BoutReal localmin = BOUTMIN(f[ic], f[icxp], f[iczp], f[icxpzp]); + + ASSERT2(std::isfinite(localmax) || i.x() < localmesh->xstart + || i.x() > localmesh->xend); + ASSERT2(std::isfinite(localmin) || i.x() < localmesh->xstart + || i.x() > localmesh->xend); + + result = std::min(result, localmax); + result = std::max(result, localmin); + + f_interp[iyp] = result; + } + return f_interp; +} diff --git a/src/mesh/interpolation/lagrange_4pt_xz.cxx b/src/mesh/interpolation/lagrange_4pt_xz.cxx index e16b2699d1..17284bfac0 100644 --- a/src/mesh/interpolation/lagrange_4pt_xz.cxx +++ b/src/mesh/interpolation/lagrange_4pt_xz.cxx @@ -20,10 +20,12 @@ * **************************************************************************/ +#include "bout/boutexception.hxx" #include "bout/globals.hxx" #include "bout/interpolation_xz.hxx" #include "bout/mesh.hxx" +#include #include XZLagrange4pt::XZLagrange4pt(int y_offset, Mesh* mesh) @@ -133,7 +135,10 @@ Field3D XZLagrange4pt::interpolate(const Field3D& f, const std::string& region) // Then in X f_interp(x, y_next, z) = lagrange_4pt(xvals, t_x(x, y, z)); + ASSERT2(std::isfinite(f_interp(x, y_next, z))); } + const auto region2 = y_offset != 0 ? fmt::format("RGN_YPAR_{:+d}", y_offset) : region; + f_interp.setRegion(region2); return f_interp; } diff --git a/src/mesh/interpolation/monotonic_hermite_spline_xz.cxx b/src/mesh/interpolation/monotonic_hermite_spline_xz.cxx deleted file mode 100644 index f23bfd499e..0000000000 --- a/src/mesh/interpolation/monotonic_hermite_spline_xz.cxx +++ /dev/null @@ -1,101 +0,0 @@ -/************************************************************************** - * Copyright 2018 B.D.Dudson, P. Hill - * - * Contact: Ben Dudson, bd512@york.ac.uk - * - * This file is part of BOUT++. - * - * BOUT++ is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * BOUT++ is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with BOUT++. If not, see . - * - **************************************************************************/ - -#include "bout/globals.hxx" -#include "bout/index_derivs_interface.hxx" -#include "bout/interpolation_xz.hxx" -#include "bout/mesh.hxx" - -#include - -Field3D XZMonotonicHermiteSpline::interpolate(const Field3D& f, - const std::string& region) const { - ASSERT1(f.getMesh() == localmesh); - Field3D f_interp(f.getMesh()); - f_interp.allocate(); - - // Derivatives are used for tension and need to be on dimensionless - // coordinates - Field3D fx = bout::derivatives::index::DDX(f, CELL_DEFAULT, "DEFAULT"); - Field3D fz = bout::derivatives::index::DDZ(f, CELL_DEFAULT, "DEFAULT", "RGN_ALL"); - localmesh->communicate_no_slices(fx, fz); - Field3D fxz = bout::derivatives::index::DDX(fz, CELL_DEFAULT, "DEFAULT"); - localmesh->communicate_no_slices(fxz); - - const auto curregion{getRegion(region)}; - BOUT_FOR(i, curregion) { - const auto iyp = i.yp(y_offset); - - const auto ic = i_corner[i]; - const auto iczp = ic.zp(); - const auto icxp = ic.xp(); - const auto icxpzp = iczp.xp(); - - // Interpolate f in X at Z - const BoutReal f_z = - f[ic] * h00_x[i] + f[icxp] * h01_x[i] + fx[ic] * h10_x[i] + fx[icxp] * h11_x[i]; - - // Interpolate f in X at Z+1 - const BoutReal f_zp1 = f[iczp] * h00_x[i] + f[icxpzp] * h01_x[i] + fx[iczp] * h10_x[i] - + fx[icxpzp] * h11_x[i]; - - // Interpolate fz in X at Z - const BoutReal fz_z = fz[ic] * h00_x[i] + fz[icxp] * h01_x[i] + fxz[ic] * h10_x[i] - + fxz[icxp] * h11_x[i]; - - // Interpolate fz in X at Z+1 - const BoutReal fz_zp1 = fz[iczp] * h00_x[i] + fz[icxpzp] * h01_x[i] - + fxz[iczp] * h10_x[i] + fxz[icxpzp] * h11_x[i]; - - // Interpolate in Z - BoutReal result = - +f_z * h00_z[i] + f_zp1 * h01_z[i] + fz_z * h10_z[i] + fz_zp1 * h11_z[i]; - - ASSERT2(std::isfinite(result) || i.x() < localmesh->xstart - || i.x() > localmesh->xend); - - // Monotonicity - // Force the interpolated result to be in the range of the - // neighbouring cell values. This prevents unphysical overshoots, - // but also degrades accuracy near maxima and minima. - // Perhaps should only impose near boundaries, since that is where - // problems most obviously occur. - const BoutReal localmax = BOUTMAX(f[ic], f[icxp], f[iczp], f[icxpzp]); - - const BoutReal localmin = BOUTMIN(f[ic], f[icxp], f[iczp], f[icxpzp]); - - ASSERT2(std::isfinite(localmax) || i.x() < localmesh->xstart - || i.x() > localmesh->xend); - ASSERT2(std::isfinite(localmin) || i.x() < localmesh->xstart - || i.x() > localmesh->xend); - - if (result > localmax) { - result = localmax; - } - if (result < localmin) { - result = localmin; - } - - f_interp[iyp] = result; - } - return f_interp; -} diff --git a/src/mesh/interpolation_xz.cxx b/src/mesh/interpolation_xz.cxx index a58554dc82..3a79f04b9f 100644 --- a/src/mesh/interpolation_xz.cxx +++ b/src/mesh/interpolation_xz.cxx @@ -23,6 +23,7 @@ * **************************************************************************/ +#include "parallel/fci_comm.hxx" #include #include #include @@ -86,6 +87,8 @@ namespace { RegisterXZInterpolation registerinterphermitespline{"hermitespline"}; RegisterXZInterpolation registerinterpmonotonichermitespline{ "monotonichermitespline"}; +RegisterXZInterpolation + registerinterpmonotonichermitesplinelegacy{"monotonichermitesplinelegacy"}; RegisterXZInterpolation registerinterplagrange4pt{"lagrange4pt"}; RegisterXZInterpolation registerinterpbilinear{"bilinear"}; } // namespace diff --git a/src/mesh/mesh.cxx b/src/mesh/mesh.cxx index 8964a6b797..0fffdc0d0e 100644 --- a/src/mesh/mesh.cxx +++ b/src/mesh/mesh.cxx @@ -10,6 +10,7 @@ #include +#include "fmt/format.h" #include "impls/bout/boutmesh.hxx" MeshFactory::ReturnType MeshFactory::create(Options* options, @@ -654,6 +655,12 @@ void Mesh::createDefaultRegions() { + getRegion3D("RGN_YGUARDS") + getRegion3D("RGN_ZGUARDS")) .unique()); + for (int offset_ = -ystart; offset_ <= ystart; ++offset_) { + const auto region = fmt::format("RGN_YPAR_{:+d}", offset_); + addRegion3D(region, Region(xstart, xend, ystart + offset_, yend + offset_, 0, + LocalNz - 1, LocalNy, LocalNz)); + } + //2D regions addRegion2D("RGN_ALL", Region(0, LocalNx - 1, 0, LocalNy - 1, 0, 0, LocalNy, 1, maxregionblocksize)); diff --git a/src/mesh/parallel/fci.cxx b/src/mesh/parallel/fci.cxx index 08e56584e1..0867810676 100644 --- a/src/mesh/parallel/fci.cxx +++ b/src/mesh/parallel/fci.cxx @@ -37,82 +37,224 @@ **************************************************************************/ #include "fci.hxx" + +#include "bout/assert.hxx" +#include "bout/bout_types.hxx" +#include "bout/boutexception.hxx" +#include "bout/build_defines.hxx" +#include "bout/field2d.hxx" +#include "bout/field3d.hxx" +#include "bout/field_data.hxx" +#include "bout/mesh.hxx" +#include "bout/msg_stack.hxx" +#include "bout/options.hxx" #include "bout/parallel_boundary_op.hxx" #include "bout/parallel_boundary_region.hxx" -#include -#include -#include -#include -#include +#include "bout/paralleltransform.hxx" +#include "bout/region.hxx" + +#include +#include +#include +#include +#include +#include #include +#include + +namespace { +using namespace std::literals; +// Get a unique name for a field based on the sign/magnitude of the offset +std::string parallel_slice_field_name(std::string field, int offset) { + const auto direction = (offset > 0) ? "forward"sv : "backward"sv; + // We only have a suffix for parallel slices beyond the first + // This is for backwards compatibility + if (std::abs(offset) == 1) { + return fmt::format("{}_{}", direction, field); + } + return fmt::format("{}_{}_{}", direction, field, std::abs(offset)); +}; + +#if BOUT_USE_METRIC_3D +void set_parallel_metric_component(std::string name, Field3D& component, int offset, + Field3D& data) { + if (!component.hasParallelSlices()) { + component.splitParallelSlices(); + component.allowCalcParallelSlices = false; + } + auto& pcom = component.ynext(offset); + pcom.allocate(); + pcom.setRegion(fmt::format("RGN_YPAR_{:+d}", offset)); + pcom.name = std::move(name); + BOUT_FOR(i, component.getRegion("RGN_NOBNDRY")) { pcom[i.yp(offset)] = data[i]; } +} + +bool load_parallel_metric_component(const std::string& name, Field3D& component, + int offset, bool doZero) { + Mesh* mesh = component.getMesh(); + Field3D tmp{mesh}; + const bool doload = mesh->sourceHasVar(name); + bool isValid{false}; + if (doload) { + const auto pname = parallel_slice_field_name(name, offset); + isValid = mesh->get(tmp, pname, 0.0, false) == 0; + } + if (not isValid) { + auto lmin = min(component, true); + auto lmax = max(component, true); + if (lmin != lmax) { + if (doZero) { + lmin = lmax = 0.0; + } else { + throw BoutException("{:s} is neither in the grid file, nor constant!\n" + " Cannot determine value for parallel slices.\n" + " Regenerate the grid with a recent zoidberg!", + name); + } + } else { + isValid = true; + } + tmp = lmin; + } + set_parallel_metric_component(name, component, offset, tmp); + return isValid; +} +#endif + +void load_parallel_metric_components([[maybe_unused]] Coordinates* coords, + [[maybe_unused]] int offset) { +#if BOUT_USE_METRIC_3D +#define LOAD_PAR(var, doZero) \ + load_parallel_metric_component(#var, coords->var, offset, doZero) + LOAD_PAR(g11, false); + LOAD_PAR(g22, false); + LOAD_PAR(g33, false); + LOAD_PAR(g12, false); + LOAD_PAR(g13, false); + LOAD_PAR(g23, false); + + LOAD_PAR(g_11, false); + LOAD_PAR(g_22, false); + LOAD_PAR(g_33, false); + LOAD_PAR(g_12, false); + LOAD_PAR(g_13, false); + LOAD_PAR(g_23, false); + + LOAD_PAR(dy, false); + LOAD_PAR(Bxy, false); + + if (not LOAD_PAR(J, true)) { + auto g = + coords->g11.ynext(offset) * coords->g22.ynext(offset) * coords->g33.ynext(offset) + + 2.0 * coords->g12.ynext(offset) * coords->g13.ynext(offset) + * coords->g23.ynext(offset) + - coords->g11.ynext(offset) * coords->g23.ynext(offset) + * coords->g23.ynext(offset) + - coords->g22.ynext(offset) * coords->g13.ynext(offset) + * coords->g13.ynext(offset) + - coords->g33.ynext(offset) * coords->g12.ynext(offset) + * coords->g12.ynext(offset); + + const auto rgn = fmt::format("RGN_YPAR_{:+d}", offset); + // Check that g is positive + bout::checkPositive(g, "The determinant of g^ij", rgn); + auto J = 1. / sqrt(g); + auto& pcom = coords->J.ynext(offset); + BOUT_FOR(i, J.getRegion(rgn)) { pcom[i] = J[i]; } + } +#undef LOAD_PAR + + // fixup for optimizing flux conservation + BOUT_FOR(i, coords->J.getRegion("RGN_NOBNDRY")) { + const auto is = i.yp(offset); + const BoutReal fac = + coords->Bxy[i] * sqrt(coords->g_11[i] * coords->g_33[i] - SQ(coords->g_13[i])) + / (coords->Bxy.ynext(offset)[is] + * sqrt(coords->g_11.ynext(offset)[is] * coords->g_33.ynext(offset)[is] + - SQ(coords->g_13.ynext(offset)[is]))); + coords->g_11.ynext(offset)[is] *= fac; + coords->g_33.ynext(offset)[is] *= fac; + coords->g_13.ynext(offset)[is] *= fac; + coords->g11.ynext(offset)[is] /= fac; + coords->g33.ynext(offset)[is] /= fac; + coords->g13.ynext(offset)[is] /= fac; + coords->J.ynext(offset)[is] *= fac; + } -FCIMap::FCIMap(Mesh& mesh, const Coordinates::FieldMetric& UNUSED(dy), Options& options, - int offset_, const std::shared_ptr& inner_boundary, +#endif +} + +template +int sgn(T val) { + return (T(0) < val) - (val < T(0)); +} + +} // namespace + +using namespace std::string_view_literals; + +FCIMap::FCIMap(Mesh& mesh, [[maybe_unused]] const Coordinates::FieldMetric& dy, + Options& options, int offset, + const std::shared_ptr& inner_boundary, const std::shared_ptr& outer_boundary, bool zperiodic) - : map_mesh(mesh), offset(offset_), - region_no_boundary(map_mesh.getRegion("RGN_NOBNDRY")), + : map_mesh(&mesh), offset_(offset), + region_no_boundary(map_mesh->getRegion("RGN_NOBNDRY")), corner_boundary_mask(map_mesh) { - TRACE("Creating FCIMAP for direction {:d}", offset); + TRACE("Creating FCIMAP for direction {:d}", offset_); - if (offset == 0) { + if (offset_ == 0) { throw BoutException( "FCIMap called with offset = 0; You probably didn't mean to do that"); } auto& interpolation_options = options["xzinterpolation"]; - interp = - XZInterpolationFactory::getInstance().create(&interpolation_options, &map_mesh); - interp->setYOffset(offset); + interp = XZInterpolationFactory::getInstance().create(&interpolation_options, map_mesh); + interp->setYOffset(offset_); interp_corner = - XZInterpolationFactory::getInstance().create(&interpolation_options, &map_mesh); - interp_corner->setYOffset(offset); + XZInterpolationFactory::getInstance().create(&interpolation_options, map_mesh); + interp_corner->setYOffset(offset_); // Index-space coordinates of forward/backward points - Field3D xt_prime{&map_mesh}, zt_prime{&map_mesh}; + Field3D xt_prime{map_mesh}; + Field3D zt_prime{map_mesh}; // Real-space coordinates of grid points - Field3D R{&map_mesh}, Z{&map_mesh}; + Field3D R{map_mesh}; + Field3D Z{map_mesh}; // Real-space coordinates of forward/backward points - Field3D R_prime{&map_mesh}, Z_prime{&map_mesh}; + Field3D R_prime{map_mesh}; + Field3D Z_prime{map_mesh}; - map_mesh.get(R, "R", 0.0, false); - map_mesh.get(Z, "Z", 0.0, false); - - // Get a unique name for a field based on the sign/magnitude of the offset - const auto parallel_slice_field_name = [&](std::string field) -> std::string { - const std::string direction = (offset > 0) ? "forward" : "backward"; - // We only have a suffix for parallel slices beyond the first - // This is for backwards compatibility - const std::string slice_suffix = - (std::abs(offset) > 1) ? "_" + std::to_string(std::abs(offset)) : ""; - return direction + "_" + field + slice_suffix; - }; + map_mesh->get(R, "R", 0.0, false); + map_mesh->get(Z, "Z", 0.0, false); // If we can't read in any of these fields, things will silently not // work, so best throw - if (map_mesh.get(xt_prime, parallel_slice_field_name("xt_prime"), 0.0, false) != 0) { + if (map_mesh->get(xt_prime, parallel_slice_field_name("xt_prime", offset_), 0.0, false) + != 0) { throw BoutException("Could not read {:s} from grid file!\n" " Either add it to the grid file, or reduce MYG", - parallel_slice_field_name("xt_prime")); + parallel_slice_field_name("xt_prime", offset_)); } - if (map_mesh.get(zt_prime, parallel_slice_field_name("zt_prime"), 0.0, false) != 0) { + if (map_mesh->get(zt_prime, parallel_slice_field_name("zt_prime", offset_), 0.0, false) + != 0) { throw BoutException("Could not read {:s} from grid file!\n" " Either add it to the grid file, or reduce MYG", - parallel_slice_field_name("zt_prime")); + parallel_slice_field_name("zt_prime", offset_)); } - if (map_mesh.get(R_prime, parallel_slice_field_name("R"), 0.0, false) != 0) { + if (map_mesh->get(R_prime, parallel_slice_field_name("R", offset_), 0.0, false) != 0) { throw BoutException("Could not read {:s} from grid file!\n" " Either add it to the grid file, or reduce MYG", - parallel_slice_field_name("R")); + parallel_slice_field_name("R", offset_)); } - if (map_mesh.get(Z_prime, parallel_slice_field_name("Z"), 0.0, false) != 0) { + if (map_mesh->get(Z_prime, parallel_slice_field_name("Z", offset_), 0.0, false) != 0) { throw BoutException("Could not read {:s} from grid file!\n" " Either add it to the grid file, or reduce MYG", - parallel_slice_field_name("Z")); + parallel_slice_field_name("Z", offset_)); } // Cell corners @@ -157,25 +299,28 @@ FCIMap::FCIMap(Mesh& mesh, const Coordinates::FieldMetric& UNUSED(dy), Options& interp->calcWeights(xt_prime, zt_prime); } - const int ncz = map_mesh.LocalNz; + const int ncz = map_mesh->LocalNz; BoutMask to_remove(map_mesh); - const int xend = - map_mesh.xstart + (map_mesh.xend - map_mesh.xstart + 1) * map_mesh.getNXPE() - 1; + const int xend = map_mesh->xstart + + ((map_mesh->xend - map_mesh->xstart + 1) * map_mesh->getNXPE()) - 1; + // Default to the maximum number of points + const int defValid{map_mesh->ystart - 1 + std::abs(offset)}; // Serial loop because call to BoundaryRegionPar::addPoint // (probably?) can't be done in parallel BOUT_FOR_SERIAL(i, xt_prime.getRegion("RGN_NOBNDRY")) { // z is periodic, so make sure the z-index wraps around if (zperiodic) { - zt_prime[i] = zt_prime[i] - - ncz * (static_cast(zt_prime[i] / static_cast(ncz))); + zt_prime[i] = + zt_prime[i] + - (ncz * (static_cast(zt_prime[i] / static_cast(ncz)))); if (zt_prime[i] < 0.0) { zt_prime[i] += ncz; } } - if ((xt_prime[i] >= map_mesh.xstart) and (xt_prime[i] <= xend)) { + if ((xt_prime[i] >= map_mesh->xstart) and (xt_prime[i] <= xend)) { // Not a boundary continue; } @@ -215,7 +360,7 @@ FCIMap::FCIMap(Mesh& mesh, const Coordinates::FieldMetric& UNUSED(dy), Options& const BoutReal dR_dz = 0.5 * (R[i_zp] - R[i_zm]); const BoutReal dZ_dz = 0.5 * (Z[i_zp] - Z[i_zm]); - const BoutReal det = dR_dx * dZ_dz - dR_dz * dZ_dx; // Determinant of 2x2 matrix + const BoutReal det = (dR_dx * dZ_dz) - (dR_dz * dZ_dx); // Determinant of 2x2 matrix const BoutReal dR = R_prime[i] - R[i]; const BoutReal dZ = Z_prime[i] - Z[i]; @@ -228,32 +373,24 @@ FCIMap::FCIMap(Mesh& mesh, const Coordinates::FieldMetric& UNUSED(dy), Options& // outer boundary. However, if any of the surrounding points are negative, // that also means inner. So to differentiate between inner and outer we // need at least 2 points in the domain. - ASSERT2(map_mesh.xend - map_mesh.xstart >= 2); - auto boundary = (xt_prime[i] < map_mesh.xstart) ? inner_boundary : outer_boundary; - boundary->add_point(x, y, z, x + dx, y + 0.5 * offset, - z + dz, // Intersection point in local index space - 0.5, // Distance to intersection - 1 // Default to that there is a point in the other direction - ); + ASSERT2(map_mesh->xend - map_mesh->xstart >= 2); + auto boundary = (xt_prime[i] < map_mesh->xstart) ? inner_boundary : outer_boundary; + if (!boundary->contains(x, y, z)) { + boundary->add_point(x, y, z, x + dx, y + offset - (sgn(offset) * 0.5), + z + dz, // Intersection point in local index space + std::abs(offset_) - 0.5, // Distance to intersection + defValid, offset_); + } } region_no_boundary = region_no_boundary.mask(to_remove); interp->setRegion(region_no_boundary); - - const auto region = fmt::format("RGN_YPAR_{:+d}", offset); - if (not map_mesh.hasRegion3D(region)) { - // The valid region for this slice - map_mesh.addRegion3D( - region, Region(map_mesh.xstart, map_mesh.xend, map_mesh.ystart + offset, - map_mesh.yend + offset, 0, map_mesh.LocalNz - 1, - map_mesh.LocalNy, map_mesh.LocalNz)); - } } Field3D FCIMap::integrate(Field3D& f) const { ASSERT1(f.getDirectionY() == YDirectionType::Standard); - ASSERT1(&map_mesh == f.getMesh()); + ASSERT1(map_mesh == f.getMesh()); // Cell centre values Field3D centre = interp->interpolate(f); @@ -268,7 +405,7 @@ Field3D FCIMap::integrate(Field3D& f) const { #endif BOUT_FOR(i, region_no_boundary) { - const auto inext = i.yp(offset); + const auto inext = i.yp(offset_); const BoutReal f_c = centre[inext]; const auto izm = i.zm(); const int x = i.x(); @@ -277,7 +414,7 @@ Field3D FCIMap::integrate(Field3D& f) const { const int zm = izm.z(); if (corner_boundary_mask(x, y, z) || corner_boundary_mask(x - 1, y, z) || corner_boundary_mask(x, y, zm) || corner_boundary_mask(x - 1, y, zm) - || (x == map_mesh.xstart)) { + || (x == map_mesh->xstart)) { // One of the corners leaves the domain. // Use the cell centre value, since boundary conditions are not // currently applied to corners. @@ -298,23 +435,78 @@ Field3D FCIMap::integrate(Field3D& f) const { return result; } +FCITransform::FCITransform(Mesh& mesh, const Coordinates::FieldMetric& dy, bool zperiodic, + Options* opt) + : ParallelTransform(mesh, opt), R{&mesh}, Z{&mesh} { + + // check the coordinate system used for the grid data source + FCITransform::checkInputGrid(); + + // Real-space coordinates of grid cells + mesh.get(R, "R", 0.0, false); + mesh.get(Z, "Z", 0.0, false); + + auto forward_boundary_xin = + std::make_shared("FCI_forward", BNDRY_PAR_FWD_XIN, +1, &mesh); + auto backward_boundary_xin = + std::make_shared("FCI_backward", BNDRY_PAR_BKWD_XIN, -1, &mesh); + auto forward_boundary_xout = + std::make_shared("FCI_forward", BNDRY_PAR_FWD_XOUT, +1, &mesh); + auto backward_boundary_xout = + std::make_shared("FCI_backward", BNDRY_PAR_BKWD_XOUT, -1, &mesh); + + // Add the boundary region to the mesh's vector of parallel boundaries + mesh.addBoundaryPar(forward_boundary_xin, BoundaryParType::xin_fwd); + mesh.addBoundaryPar(backward_boundary_xin, BoundaryParType::xin_bwd); + mesh.addBoundaryPar(forward_boundary_xout, BoundaryParType::xout_fwd); + mesh.addBoundaryPar(backward_boundary_xout, BoundaryParType::xout_bwd); + + field_line_maps.reserve(static_cast(mesh.ystart) * 2); + for (int offset = 1; offset < mesh.ystart + 1; ++offset) { + field_line_maps.emplace_back(mesh, dy, options, offset, forward_boundary_xin, + forward_boundary_xout, zperiodic); + field_line_maps.emplace_back(mesh, dy, options, -offset, backward_boundary_xin, + backward_boundary_xout, zperiodic); + } + const std::array bndries = {forward_boundary_xin, forward_boundary_xout, + backward_boundary_xin, backward_boundary_xout}; + for (const auto& bndry : bndries) { + for (const auto& bndry2 : bndries) { + if (bndry->dir == bndry2->dir) { + continue; + } + for (auto pnt : *bndry) { + for (auto pnt2 : *bndry2) { + if (pnt.ind() == pnt2.ind()) { + pnt.setValid( + static_cast(std::abs((pnt2.offset() - pnt.offset())) - 2)); + } + } + } + } + } +} + void FCITransform::checkInputGrid() { std::string parallel_transform; if (mesh.isDataSourceGridFile() - && !mesh.get(parallel_transform, "parallel_transform")) { + && (mesh.get(parallel_transform, "parallel_transform") == 0)) { if (parallel_transform != "fci") { throw BoutException( "Incorrect parallel transform type '" + parallel_transform + "' used " "to generate metric components for FCITransform. Should be 'fci'."); } - } // else: parallel_transform variable not found in grid input, indicates older input - // file or grid from options so must rely on the user having ensured the type is - // correct + } + // else: parallel_transform variable not found in grid input, indicates older input + // file or grid from options so must rely on the user having ensured the type is + // correct } void FCITransform::calcParallelSlices(Field3D& f) { + ASSERT1(f.allowCalcParallelSlices); + ASSERT1(f.getDirectionY() == YDirectionType::Standard); // Only have forward_map/backward_map for CELL_CENTRE, so can only deal with // CELL_CENTRE inputs @@ -325,8 +517,8 @@ void FCITransform::calcParallelSlices(Field3D& f) { // Interpolate f onto yup and ydown fields for (const auto& map : field_line_maps) { - f.ynext(map.offset) = map.interpolate(f); - f.ynext(map.offset).setRegion(fmt::format("RGN_YPAR_{:+d}", map.offset)); + f.ynext(map.offset()) = map.interpolate(f); + f.ynext(map.offset()).setRegion(fmt::format("RGN_YPAR_{:+d}", map.offset())); } } @@ -342,7 +534,14 @@ void FCITransform::integrateParallelSlices(Field3D& f) { // Integrate f onto yup and ydown fields for (const auto& map : field_line_maps) { - f.ynext(map.offset) = map.integrate(f); + f.ynext(map.offset()) = map.integrate(f); + } +} + +void FCITransform::loadParallelMetrics(Coordinates* coords) { + for (int i = 1; i <= mesh.ystart; ++i) { + load_parallel_metric_components(coords, -i); + load_parallel_metric_components(coords, i); } } diff --git a/src/mesh/parallel/fci.hxx b/src/mesh/parallel/fci.hxx index 1a02f558e1..71ac35a192 100644 --- a/src/mesh/parallel/fci.hxx +++ b/src/mesh/parallel/fci.hxx @@ -26,6 +26,11 @@ #ifndef BOUT_FCITRANSFORM_H #define BOUT_FCITRANSFORM_H +#include "bout/assert.hxx" +#include "bout/bout_types.hxx" +#include "bout/boutexception.hxx" +#include "bout/coordinates.hxx" +#include "bout/region.hxx" #include #include #include @@ -33,25 +38,26 @@ #include #include +#include #include +class BoundaryRegionPar; +class FieldPerp; +class Field2D; +class Field3D; +class Options; + /// Field line map - contains the coefficients for interpolation class FCIMap { /// Interpolation objects std::unique_ptr interp; // Cell centre std::unique_ptr interp_corner; // Cell corner at (x+1, z+1) -public: - FCIMap() = delete; - FCIMap(Mesh& mesh, const Coordinates::FieldMetric& dy, Options& options, int offset, - const std::shared_ptr& inner_boundary, - const std::shared_ptr& outer_boundary, bool zperiodic); - // The mesh this map was created on - Mesh& map_mesh; + Mesh* map_mesh; /// Direction of map - const int offset; + int offset_; /// region containing all points where the field line has not left the /// domain @@ -59,8 +65,17 @@ public: /// If any of the integration area has left the domain BoutMask corner_boundary_mask; +public: + FCIMap() = delete; + FCIMap(Mesh& mesh, const Coordinates::FieldMetric& dy, Options& options, int offset, + const std::shared_ptr& inner_boundary, + const std::shared_ptr& outer_boundary, bool zperiodic); + + /// Direction of map + int offset() const { return offset_; } + Field3D interpolate(Field3D& f) const { - ASSERT1(&map_mesh == f.getMesh()); + ASSERT1(map_mesh == f.getMesh()); return interp->interpolate(f); } @@ -72,55 +87,7 @@ class FCITransform : public ParallelTransform { public: FCITransform() = delete; FCITransform(Mesh& mesh, const Coordinates::FieldMetric& dy, bool zperiodic = true, - Options* opt = nullptr) - : ParallelTransform(mesh, opt), R{&mesh}, Z{&mesh} { - - // check the coordinate system used for the grid data source - FCITransform::checkInputGrid(); - - // Real-space coordinates of grid cells - mesh.get(R, "R", 0.0, false); - mesh.get(Z, "Z", 0.0, false); - - auto forward_boundary_xin = - std::make_shared("FCI_forward", BNDRY_PAR_FWD_XIN, +1, &mesh); - auto backward_boundary_xin = std::make_shared( - "FCI_backward", BNDRY_PAR_BKWD_XIN, -1, &mesh); - auto forward_boundary_xout = - std::make_shared("FCI_forward", BNDRY_PAR_FWD_XOUT, +1, &mesh); - auto backward_boundary_xout = std::make_shared( - "FCI_backward", BNDRY_PAR_BKWD_XOUT, -1, &mesh); - - // Add the boundary region to the mesh's vector of parallel boundaries - mesh.addBoundaryPar(forward_boundary_xin, BoundaryParType::xin_fwd); - mesh.addBoundaryPar(backward_boundary_xin, BoundaryParType::xin_bwd); - mesh.addBoundaryPar(forward_boundary_xout, BoundaryParType::xout_fwd); - mesh.addBoundaryPar(backward_boundary_xout, BoundaryParType::xout_bwd); - - field_line_maps.reserve(mesh.ystart * 2); - for (int offset = 1; offset < mesh.ystart + 1; ++offset) { - field_line_maps.emplace_back(mesh, dy, options, offset, forward_boundary_xin, - forward_boundary_xout, zperiodic); - field_line_maps.emplace_back(mesh, dy, options, -offset, backward_boundary_xin, - backward_boundary_xout, zperiodic); - } - ASSERT0(mesh.ystart == 1); - std::shared_ptr bndries[]{ - forward_boundary_xin, forward_boundary_xout, backward_boundary_xin, - backward_boundary_xout}; - for (auto& bndry : bndries) { - for (const auto& bndry2 : bndries) { - if (bndry->dir == bndry2->dir) { - continue; - } - for (bndry->first(); !bndry->isDone(); bndry->next()) { - if (bndry2->contains(*bndry)) { - bndry->setValid(0); - } - } - } - } - } + Options* opt = nullptr); void calcParallelSlices(Field3D& f) override; @@ -158,6 +125,8 @@ public: return false; } + void loadParallelMetrics(Coordinates* coords) override; + protected: void checkInputGrid() override; diff --git a/src/mesh/parallel/fci_comm.cxx b/src/mesh/parallel/fci_comm.cxx new file mode 100644 index 0000000000..95bfa6958e --- /dev/null +++ b/src/mesh/parallel/fci_comm.cxx @@ -0,0 +1,37 @@ +/************************************************************************** + * Communication for Flux-coordinate Independent interpolation + * + ************************************************************************** + * Copyright 2025 BOUT++ contributors + * + * Contact: Ben Dudson, dudson2@llnl.gov + * + * This file is part of BOUT++. + * + * BOUT++ is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * BOUT++ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with BOUT++. If not, see . + * + **************************************************************************/ + +#include "fci_comm.hxx" +#include "bout/assert.hxx" +#include "bout/bout_types.hxx" +#include "bout/region.hxx" + +#include + +const BoutReal& GlobalField3DAccessInstance::operator[](IndG3D ind) const { + auto it = gfa.mapping.find(ind.ind); + ASSERT2(it != gfa.mapping.end()); + return data[it->second]; +} diff --git a/src/mesh/parallel/fci_comm.hxx b/src/mesh/parallel/fci_comm.hxx new file mode 100644 index 0000000000..057a2cccbf --- /dev/null +++ b/src/mesh/parallel/fci_comm.hxx @@ -0,0 +1,310 @@ +/************************************************************************** + * Communication for Flux-coordinate Independent interpolation + * + ************************************************************************** + * Copyright 2025 BOUT++ contributors + * + * Contact: Ben Dudson, dudson2@llnl.gov + * + * This file is part of BOUT++. + * + * BOUT++ is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * BOUT++ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with BOUT++. If not, see . + * + **************************************************************************/ + +#pragma once + +#include "bout/assert.hxx" +#include "bout/bout_types.hxx" +#include "bout/boutcomm.hxx" +#include "bout/field3d.hxx" +#include "bout/mesh.hxx" +#include "bout/region.hxx" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +class GlobalField3DAccess; + +namespace fci_comm { +struct ProcLocal { + int proc; + int ind; +}; +struct globalToLocal1D { + const int mg; + const int npe; + const int localwith; + const int local; + const int global; + const int globalwith; + const bool periodic; + globalToLocal1D(int mg, int npe, int localwith, bool periodic) + : mg(mg), npe(npe), localwith(localwith), local(localwith - (2 * mg)), + global(local * npe), globalwith(global + (2 * mg)), periodic(periodic){}; + ProcLocal convert(int id) const { + if (periodic) { + while (id < mg) { + id += global; + } + while (id >= global + mg) { + id -= global; + } + } + int const idwo = id - mg; + int proc = idwo / local; + if (not periodic) { + if (proc >= npe) { + proc = npe - 1; + } + } + int const loc = id - (local * proc); +#if CHECK > 1 + if ((loc < 0 or loc > localwith or proc < 0 or proc >= npe) + or (periodic and (loc < mg or loc >= local + mg))) { + printf("globalToLocal1D failure: %d %d, %d %d, %d %d %s\n", id, idwo, globalwith, + npe, proc, loc, periodic ? "periodic" : "non-periodic"); + ASSERT0(false); + } +#endif + return {proc, loc}; + } +}; +template +struct XYZ2Ind { + const int nx; + const int ny; + const int nz; + ind convert(const int x, const int y, const int z) const { + return {z + ((y + x * ny) * nz), ny, nz}; + } + ind operator()(const int x, const int y, const int z) const { return convert(x, y, z); } + XYZ2Ind(const int nx, const int ny, const int nz) : nx(nx), ny(ny), nz(nz) {} +}; +} // namespace fci_comm + +class GlobalField3DAccessInstance { +public: + const BoutReal& operator[](IndG3D ind) const; + + GlobalField3DAccessInstance(const GlobalField3DAccess* gfa, + std::vector&& data) + : gfa(*gfa), data(std::move(data)){}; + +private: + const GlobalField3DAccess& gfa; + const std::vector data; +}; + +class GlobalField3DAccess { +public: + friend class GlobalField3DAccessInstance; + GlobalField3DAccess(Mesh* mesh) + : mesh(mesh), g2lx(mesh->xstart, mesh->getNXPE(), mesh->LocalNx, false), + g2ly(mesh->ystart, mesh->getNYPE(), mesh->LocalNy, true), + g2lz(mesh->zstart, 1, mesh->LocalNz, true), + xyzl(g2lx.localwith, g2ly.localwith, g2lz.localwith), + xyzg(g2lx.globalwith, g2ly.globalwith, g2lz.globalwith), comm(BoutComm::get()) { +#ifdef _OPENMP + o_ids.resize(omp_get_max_threads()); +#endif + }; + void get(IndG3D ind) { + ASSERT2(is_setup == false); +#ifdef _OPENMP + ASSERT2(o_ids.size() > static_cast(omp_get_thread_num())); + o_ids[omp_get_thread_num()].emplace(ind.ind); +#else + ids.emplace(ind.ind); +#endif + } + + void operator[](IndG3D ind) { get(ind); } + void setup() { + ASSERT2(is_setup == false); +#ifdef _OPENMP + for (auto& o_id : o_ids) { + ids.merge(o_id); + } + o_ids.clear(); +#endif + toGet.resize(static_cast(g2lx.npe * g2ly.npe * g2lz.npe)); + for (const auto id : ids) { + const IndG3D gind{id, g2ly.globalwith, g2lz.globalwith}; + const auto pix = g2lx.convert(gind.x()); + const auto piy = g2ly.convert(gind.y()); + const auto piz = g2lz.convert(gind.z()); + ASSERT3(piz.proc == 0); + toGet[(piy.proc * g2lx.npe) + pix.proc].push_back( + xyzl.convert(pix.ind, piy.ind, piz.ind).ind); + } + for (auto& v : toGet) { + std::sort(v.begin(), v.end()); + } + commCommLists(); + { + int offset = 0; + for (const auto& get : toGet) { + getOffsets.push_back(offset); + offset += get.size(); + } + getOffsets.push_back(offset); + } + for (const auto id : ids) { + const IndG3D gind{id, g2ly.globalwith, g2lz.globalwith}; + const auto pix = g2lx.convert(gind.x()); + const auto piy = g2ly.convert(gind.y()); + const auto piz = g2lz.convert(gind.z()); + ASSERT3(piz.proc == 0); + const auto proc = (piy.proc * g2lx.npe) + pix.proc; + const auto& vec = toGet[proc]; + const auto tofind = xyzl.convert(pix.ind, piy.ind, piz.ind).ind; + auto it = std::lower_bound(vec.begin(), vec.end(), tofind); + ASSERT3(it != vec.end()); + ASSERT3(*it == tofind); + mapping[id] = std::distance(vec.begin(), it) + getOffsets[proc]; + } + is_setup = true; + } + GlobalField3DAccessInstance communicate(const Field3D& f) { + return {this, communicate_data(f)}; + } + std::unique_ptr communicate_asPtr(const Field3D& f) { + return std::make_unique(this, communicate_data(f)); + } + +private: + void commCommLists() { + toSend.resize(toGet.size()); + std::vector toGetSizes(toGet.size(), -1); + std::vector toSendSizes(toSend.size(), -1); +#if CHECK > 3 + { + int thisproc; + MPI_Comm_rank(comm, &thisproc); + ASSERT0(thisproc == mesh->getYProcIndex() * g2lx.npe + mesh->getXProcIndex()); + } +#endif + std::vector reqs(toSend.size()); + for (size_t proc = 0; proc < toGet.size(); ++proc) { + auto ret = MPI_Irecv(static_cast(&toSendSizes[proc]), 1, MPI_INT, proc, 666, + comm, &reqs[proc]); + ASSERT0(ret == MPI_SUCCESS); + } + for (size_t proc = 0; proc < toGet.size(); ++proc) { + toGetSizes[proc] = toGet[proc].size(); + auto ret = + MPI_Send(static_cast(&toGetSizes[proc]), 1, MPI_INT, proc, 666, comm); + ASSERT0(ret == MPI_SUCCESS); + } + std::vector reqs2(toSend.size()); + int cnt = 0; + for ([[maybe_unused]] auto* dummy : reqs) { + int ind{0}; + auto ret = MPI_Waitany(reqs.size(), reqs.data(), &ind, MPI_STATUS_IGNORE); + ASSERT0(ret == MPI_SUCCESS); + ASSERT3(ind != MPI_UNDEFINED); + ASSERT2(static_cast(ind) < toSend.size()); + ASSERT3(toSendSizes[ind] >= 0); + if (toSendSizes[ind] == 0) { + continue; + } + sendBufferSize += toSendSizes[ind]; + toSend[ind].resize(toSendSizes[ind], -1); + + ret = MPI_Irecv(static_cast(toSend[ind].data()), toSend[ind].size(), MPI_INT, + ind, 666 * 666, comm, reqs2.data() + cnt++); + ASSERT0(ret == MPI_SUCCESS); + } + for (size_t proc = 0; proc < toGet.size(); ++proc) { + if (!toGet[proc].empty()) { + const auto ret = MPI_Send(static_cast(toGet[proc].data()), + toGet[proc].size(), MPI_INT, proc, 666 * 666, comm); + ASSERT0(ret == MPI_SUCCESS); + } + } + for (int c = 0; c < cnt; c++) { + int ind{0}; + const auto ret = MPI_Waitany(cnt, reqs2.data(), &ind, MPI_STATUS_IGNORE); + ASSERT0(ret == MPI_SUCCESS); + ASSERT3(ind != MPI_UNDEFINED); + } + } + Mesh* mesh; +#ifdef _OPENMP + std::vector> o_ids; +#endif + std::set ids; + std::map mapping; + bool is_setup{false}; + const fci_comm::globalToLocal1D g2lx; + const fci_comm::globalToLocal1D g2ly; + const fci_comm::globalToLocal1D g2lz; + +public: + const fci_comm::XYZ2Ind xyzl; + const fci_comm::XYZ2Ind xyzg; + +private: + std::vector> toGet; + std::vector> toSend; + std::vector getOffsets; + int sendBufferSize{0}; + MPI_Comm comm; + std::vector communicate_data(const Field3D& f) { + ASSERT2(is_setup); + ASSERT2(f.getMesh() == mesh); + std::vector data(getOffsets.back()); + std::vector sendBuffer(sendBufferSize); + std::vector reqs(toSend.size()); + int cnt1 = 0; + for (size_t proc = 0; proc < toGet.size(); ++proc) { + if (toGet[proc].empty()) { + continue; + } + auto ret = + MPI_Irecv(static_cast(data.data() + getOffsets[proc]), + toGet[proc].size(), MPI_DOUBLE, proc, 666, comm, reqs.data() + cnt1); + ASSERT0(ret == MPI_SUCCESS); + cnt1++; + } + int cnt = 0; + for (size_t proc = 0; proc < toGet.size(); ++proc) { + if (toSend[proc].empty()) { + continue; + } + const void* start = static_cast(sendBuffer.data() + cnt); + for (auto i : toSend[proc]) { + sendBuffer[cnt++] = f[Ind3D(i)]; + } + auto ret = MPI_Send(start, toSend[proc].size(), MPI_DOUBLE, proc, 666, comm); + ASSERT0(ret == MPI_SUCCESS); + } + for (int j = 0; j < cnt1; ++j) { + int ind{0}; + auto ret = MPI_Waitany(cnt1, reqs.data(), &ind, MPI_STATUS_IGNORE); + ASSERT0(ret == MPI_SUCCESS); + ASSERT3(ind != MPI_UNDEFINED); + ASSERT3(ind >= 0); + ASSERT3(ind < cnt1); + } + return data; + } +}; diff --git a/src/mesh/parallel/shiftedmetricinterp.cxx b/src/mesh/parallel/shiftedmetricinterp.cxx index c71618ab19..22a8f0748a 100644 --- a/src/mesh/parallel/shiftedmetricinterp.cxx +++ b/src/mesh/parallel/shiftedmetricinterp.cxx @@ -146,7 +146,7 @@ ShiftedMetricInterp::ShiftedMetricInterp(Mesh& mesh, CELL_LOC location_in, 0.25 * (1 // dy/2 + dy(it.ind, mesh.yend + 1) / dy(it.ind, mesh.yend)), // length - yvalid); + yvalid, 1); } } auto backward_boundary_xin = std::make_shared( @@ -162,7 +162,7 @@ ShiftedMetricInterp::ShiftedMetricInterp(Mesh& mesh, CELL_LOC location_in, 0.25 * (1 // dy/2 + dy(it.ind, mesh.ystart - 1) / dy(it.ind, mesh.ystart)), - yvalid); + yvalid, -1); } } // Create regions for parallel boundary conditions @@ -179,7 +179,7 @@ ShiftedMetricInterp::ShiftedMetricInterp(Mesh& mesh, CELL_LOC location_in, 0.25 * (1 // dy/2 + dy(it.ind, mesh.yend + 1) / dy(it.ind, mesh.yend)), - yvalid); + yvalid, 1); } } auto backward_boundary_xout = std::make_shared( @@ -195,7 +195,7 @@ ShiftedMetricInterp::ShiftedMetricInterp(Mesh& mesh, CELL_LOC location_in, 0.25 * (dy(it.ind, mesh.ystart - 1) / dy(it.ind, mesh.ystart) // dy/2 + 1), - yvalid); + yvalid, -1); } } diff --git a/src/mesh/parallel_boundary_op.cxx b/src/mesh/parallel_boundary_op.cxx index ebd9852791..644331221e 100644 --- a/src/mesh/parallel_boundary_op.cxx +++ b/src/mesh/parallel_boundary_op.cxx @@ -1,21 +1,20 @@ #include "bout/parallel_boundary_op.hxx" +#include "bout/bout_types.hxx" #include "bout/constants.hxx" #include "bout/field_factory.hxx" #include "bout/globals.hxx" #include "bout/mesh.hxx" #include "bout/output.hxx" +#include "bout/parallel_boundary_region.hxx" -BoutReal BoundaryOpPar::getValue(const BoundaryRegionPar& bndry, BoutReal t) { - BoutReal value; - +BoutReal BoundaryOpPar::getValue(const BoundaryRegionParIter& bndry, BoutReal t) { switch (value_type) { case ValueType::GEN: return gen_values->generate(bout::generator::Context( bndry.s_x(), bndry.s_y(), bndry.s_z(), CELL_CENTRE, bndry.localmesh, t)); case ValueType::FIELD: // FIXME: Interpolate to s_x, s_y, s_z... - value = (*field_values)[bndry.ind()]; - return value; + return (*field_values)[bndry.ind()]; case ValueType::REAL: return real_value; default: diff --git a/src/physics/smoothing.cxx b/src/physics/smoothing.cxx index 309fff1542..1b437b4352 100644 --- a/src/physics/smoothing.cxx +++ b/src/physics/smoothing.cxx @@ -340,7 +340,7 @@ BoutReal Average_XY(const Field2D& var) { return Vol_Glb; } -BoutReal Vol_Integral(const Field2D& var) { +BoutReal Vol_Integral([[maybe_unused]] const Field2D& var) { #if BOUT_USE_METRIC_3D throw BoutException("Vol_Intregral currently incompatible with 3D metrics"); diff --git a/src/solver/impls/euler/euler.cxx b/src/solver/impls/euler/euler.cxx index a3911e78d6..0789591e9c 100644 --- a/src/solver/impls/euler/euler.cxx +++ b/src/solver/impls/euler/euler.cxx @@ -1,10 +1,16 @@ #include "euler.hxx" +#include "fmt/format.h" +#include "bout/bout.hxx" +#include "bout/field2d.hxx" #include #include #include #include +#include +#include +#include EulerSolver::EulerSolver(Options* options) : Solver(options), mxstep((*options)["mxstep"] @@ -15,7 +21,10 @@ EulerSolver::EulerSolver(Options* options) .withDefault(2.)), timestep((*options)["timestep"] .doc("Internal timestep (defaults to output timestep)") - .withDefault(getOutputTimestep())) {} + .withDefault(getOutputTimestep())), + dump_at_time((*options)["dump_at_time"] + .doc("Dump debug info about the simulation") + .withDefault(-1)) {} void EulerSolver::setMaxTimestep(BoutReal dt) { if (dt >= cfl_factor * timestep) { @@ -134,7 +143,47 @@ void EulerSolver::take_step(BoutReal curtime, BoutReal dt, Array& star Array& result) { load_vars(std::begin(start)); + const bool dump_now = + (dump_at_time >= 0 && std::abs(dump_at_time - curtime) < dt) || dump_at_time < -3; + std::unique_ptr debug_ptr; + if (dump_now) { + debug_ptr = std::make_unique(); + Options& debug = *debug_ptr; + for (auto& f : f3d) { + f.F_var->enableTracking(fmt::format("ddt_{:s}", f.name), debug); + setName(*f.var, f.name); + debug[fmt::format("pre_{:s}", f.name)] = *f.var; + f.var->allocate(); + } + } + run_rhs(curtime); + if (dump_now) { + Options& debug = *debug_ptr; + Mesh* mesh{nullptr}; + for (auto& f : f3d) { + saveParallel(debug, f.name, *f.var); + mesh = f.var->getMesh(); + } + + if (mesh != nullptr) { + mesh->outputVars(debug); + debug["BOUT_VERSION"].force(bout::version::as_double); + } + + const std::string outnumber = + dump_at_time < -3 ? fmt::format(".{}", debug_counter++) : ""; + const std::string outname = + fmt::format("{}/BOUT.debug{}.{}.nc", + Options::root()["datadir"].withDefault("data"), + outnumber, BoutComm::rank()); + + bout::OptionsIO::create(outname)->write(debug); + MPI_Barrier(BoutComm::get()); + for (auto& f : f3d) { + f.F_var->disableTracking(); + } + } save_derivs(std::begin(result)); BOUT_OMP_PERF(parallel for) diff --git a/src/solver/impls/euler/euler.hxx b/src/solver/impls/euler/euler.hxx index 0ee81a3d33..fc9b7f53bb 100644 --- a/src/solver/impls/euler/euler.hxx +++ b/src/solver/impls/euler/euler.hxx @@ -64,6 +64,9 @@ private: /// Take a single step to calculate f1 void take_step(BoutReal curtime, BoutReal dt, Array& start, Array& result); + + BoutReal dump_at_time{-1.}; + int debug_counter{0}; }; #endif // BOUT_KARNIADAKIS_SOLVER_H diff --git a/src/solver/impls/pvode/pvode.cxx b/src/solver/impls/pvode/pvode.cxx index cb6e81d80a..97e6ca7dc2 100644 --- a/src/solver/impls/pvode/pvode.cxx +++ b/src/solver/impls/pvode/pvode.cxx @@ -24,6 +24,11 @@ **************************************************************************/ #include "bout/build_defines.hxx" +#include "bout/field2d.hxx" +#include "bout/field3d.hxx" +#include +#include +#include #include "pvode.hxx" @@ -35,6 +40,7 @@ #include #include #include +#include #include "bout/unused.hxx" @@ -42,12 +48,40 @@ #include // contains the enum for types of preconditioning #include // band preconditioner function prototypes +#include + using namespace pvode; void solver_f(integer N, BoutReal t, N_Vector u, N_Vector udot, void* f_data); void solver_gloc(integer N, BoutReal t, BoutReal* u, BoutReal* udot, void* f_data); void solver_cfn(integer N, BoutReal t, N_Vector u, void* f_data); +namespace { +// local only +void pvode_load_data_f3d(const std::vector& evolve_bndrys, + std::vector& ffs, const BoutReal* udata) { + int p = 0; + const Mesh* mesh = ffs[0].getMesh(); + const int nz = mesh->LocalNz; + for (const auto& bndry : {true, false}) { + for (const auto& i2d : mesh->getRegion2D(bndry ? "RGN_BNDRY" : "RGN_NOBNDRY")) { + for (int jz = 0; jz < nz; jz++) { + // Loop over 3D variables + std::vector::const_iterator evolve_bndry = evolve_bndrys.begin(); + for (std::vector::iterator ff = ffs.begin(); ff != ffs.end(); ++ff) { + if (bndry && !*evolve_bndry) { + continue; + } + (*ff)[mesh->ind2Dto3D(i2d, jz)] = udata[p]; + p++; + } + ++evolve_bndry; + } + } + } +} +} // namespace + const BoutReal ZERO = 0.0; long int iopt[OPT_SIZE]; @@ -184,10 +218,47 @@ int PvodeSolver::init() { for (i = 0; i < OPT_SIZE; i++) { ropt[i] = ZERO; } + /* iopt[MXSTEP] : maximum number of internal steps to be taken by * + * the solver in its attempt to reach tout. * + * Optional input. (Default = 500). */ iopt[MXSTEP] = pvode_mxstep; - cvode_mem = CVodeMalloc(neq, solver_f, simtime, u, BDF, NEWTON, SS, &reltol, &abstol, - this, nullptr, optIn, iopt, ropt, machEnv); + { + /* ropt[H0] : initial step size. Optional input. */ + + /* ropt[HMAX] : maximum absolute value of step size allowed. * + * Optional input. (Default is infinity). */ + const BoutReal hmax( + (*options)["max_timestep"].doc("Maximum internal timestep").withDefault(-1.)); + if (hmax > 0) { + ropt[HMAX] = hmax; + } + /* ropt[HMIN] : minimum absolute value of step size allowed. * + * Optional input. (Default is 0.0). */ + const BoutReal hmin( + (*options)["min_timestep"].doc("Minimum internal timestep").withDefault(-1.)); + if (hmin > 0) { + ropt[HMIN] = hmin; + } + /* iopt[MAXORD] : maximum lmm order to be used by the solver. * + * Optional input. (Default = 12 for ADAMS, 5 for * + * BDF). */ + const int maxOrder((*options)["max_order"].doc("Maximum order").withDefault(-1)); + if (maxOrder > 0) { + iopt[MAXORD] = maxOrder; + } + } + const bool use_adam((*options)["adams_moulton"] + .doc("Use Adams Moulton solver instead of BDF") + .withDefault(false)); + + debug_on_failure = + (*options)["debug_on_failure"] + .doc("Run an aditional rhs if the solver fails with extra tracking") + .withDefault(false); + + cvode_mem = CVodeMalloc(neq, solver_f, simtime, u, use_adam ? ADAMS : BDF, NEWTON, SS, + &reltol, &abstol, this, nullptr, optIn, iopt, ropt, machEnv); if (cvode_mem == nullptr) { throw BoutException("\tError: CVodeMalloc failed.\n"); @@ -291,6 +362,58 @@ BoutReal PvodeSolver::run(BoutReal tout) { // Check return flag if (flag != SUCCESS) { output_error.write("ERROR CVODE step failed, flag = {:d}\n", flag); + if (debug_on_failure) { + const CVodeMemRec* cv_mem = (CVodeMem)cvode_mem; + if (f2d.empty() and v2d.empty() and v3d.empty()) { + Options debug{}; + using namespace std::string_literals; + Mesh* mesh{}; + for (const auto& prefix : {"pre_"s, "residuum_"s}) { + std::vector list_of_fields{}; + std::vector evolve_bndrys{}; + for (const auto& f : f3d) { + mesh = f.var->getMesh(); + Field3D to_load{0., mesh}; + to_load.allocate(); + to_load.setLocation(f.location); + debug[fmt::format("{:s}{:s}", prefix, f.name)] = to_load; + list_of_fields.push_back(to_load); + evolve_bndrys.push_back(f.evolve_bndry); + } + pvode_load_data_f3d(evolve_bndrys, list_of_fields, + prefix == "pre_"s ? udata : N_VDATA(cv_mem->cv_acor)); + } + + for (auto& f : f3d) { + f.F_var->enableTracking(fmt::format("ddt_{:s}", f.name), debug); + setName(*f.var, f.name); + } + run_rhs(simtime); + modelOutputVars(debug); + + for (auto& f : f3d) { + debug[f.name] = *f.var; + if (f.var->hasParallelSlices()) { + saveParallel(debug, f.name, *f.var); + } + } + + if (mesh != nullptr) { + mesh->outputVars(debug); + debug["BOUT_VERSION"].force(bout::version::as_double); + } + + const std::string outname = + fmt::format("{}/BOUT.debug.{}.nc", + Options::root()["datadir"].withDefault("data"), + BoutComm::rank()); + + bout::OptionsIO::create(outname)->write(debug); + MPI_Barrier(BoutComm::get()); + } else { + output_warn.write("debug_on_failure is currently only supported for Field3Ds"); + } + } return (-1.0); } diff --git a/src/solver/impls/pvode/pvode.hxx b/src/solver/impls/pvode/pvode.hxx index 6425fc1868..cf68444b6c 100644 --- a/src/solver/impls/pvode/pvode.hxx +++ b/src/solver/impls/pvode/pvode.hxx @@ -75,10 +75,15 @@ private: pvode::machEnvType machEnv{nullptr}; void* cvode_mem{nullptr}; - BoutReal abstol, reltol; // addresses passed in init must be preserved + BoutReal abstol, reltol; + // addresses passed in init must be preserved pvode::PVBBDData pdata{nullptr}; - bool pvode_initialised = false; + /// is pvode already initialised? + bool pvode_initialised{false}; + + /// Add debugging data if solver fails + bool debug_on_failure{false}; }; #endif // BOUT_PVODE_SOLVER_H diff --git a/src/solver/solver.cxx b/src/solver/solver.cxx index ac3d632419..4c6d44e3ec 100644 --- a/src/solver/solver.cxx +++ b/src/solver/solver.cxx @@ -676,6 +676,10 @@ int Solver::init() { return 0; } +void Solver::modelOutputVars(Options& output_options) { + model->outputVars(output_options); +} + void Solver::outputVars(Options& output_options, bool save_repeat) { Timer time("io"); output_options["tt"].force(simtime, "Solver"); diff --git a/src/sys/derivs.cxx b/src/sys/derivs.cxx index aab75b8f19..2b606b9e5d 100644 --- a/src/sys/derivs.cxx +++ b/src/sys/derivs.cxx @@ -66,7 +66,7 @@ Coordinates::FieldMetric DDX(const Field2D& f, CELL_LOC outloc, const std::strin ////////////// Y DERIVATIVE ///////////////// -Field3D DDY(const Field3D& f, CELL_LOC outloc, const std::string& method, +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; @@ -406,7 +406,7 @@ Coordinates::FieldMetric VDDY(const Field2D& v, const Field2D& f, CELL_LOC outlo } // general case -Field3D VDDY(const Field3D& v, const Field3D& f, CELL_LOC outloc, +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; @@ -467,7 +467,7 @@ Coordinates::FieldMetric FDDY(const Field2D& v, const Field2D& f, CELL_LOC outlo / f.getCoordinates(outloc)->dy; } -Field3D FDDY(const Field3D& v, const Field3D& f, CELL_LOC outloc, +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; diff --git a/src/sys/options.cxx b/src/sys/options.cxx index bb4c920b90..79ad08ce5e 100644 --- a/src/sys/options.cxx +++ b/src/sys/options.cxx @@ -1,6 +1,7 @@ #include "bout/options.hxx" #include "bout/array.hxx" +#include "bout/assert.hxx" #include "bout/bout_types.hxx" #include "bout/boutexception.hxx" #include "bout/field2d.hxx" @@ -16,6 +17,7 @@ #include "bout/unused.hxx" #include "bout/utils.hxx" +#include #include #include #include @@ -354,6 +356,30 @@ Options& Options::assign<>(Tensor val, std::string source) { return *this; } +void saveParallel(Options& opt, const std::string& name, const Field3D& tosave) { + ASSERT0(tosave.isAllocated()); + opt[name] = tosave; + for (size_t i0 = 1; i0 <= tosave.numberParallelSlices(); ++i0) { + for (int i : {i0, -i0}) { + Field3D tmp; + tmp.allocate(); + const auto& fpar = tosave.ynext(i); + if (fpar.isAllocated()) { + for (auto j : tmp.getRegion("RGN_NOY")) { + tmp[j] = fpar[j.yp(i)]; + } + opt[fmt::format("{}_y{:+d}", name, i)] = tmp; + } else { + if (tosave.isFci()) { // likely an error + throw BoutException( + "Tried to save parallel fields - but parallel field {i} is not allocated", + i); + } + } + } + } +} + namespace { /// Use FieldFactory to evaluate expression double parseExpression(const Options::ValueType& value, const Options* options, diff --git a/tests/MMS/CMakeLists.txt b/tests/MMS/CMakeLists.txt index a6667bcfa5..510385d54c 100644 --- a/tests/MMS/CMakeLists.txt +++ b/tests/MMS/CMakeLists.txt @@ -8,6 +8,7 @@ add_subdirectory(spatial/d2dx2) add_subdirectory(spatial/d2dz2) add_subdirectory(spatial/diffusion) add_subdirectory(spatial/fci) +add_subdirectory(spatial/finite-volume) add_subdirectory(time) add_subdirectory(time-petsc) add_subdirectory(wave-1d) diff --git a/tests/MMS/spatial/fci/CMakeLists.txt b/tests/MMS/spatial/fci/CMakeLists.txt index dcdb183fdd..48721a8199 100644 --- a/tests/MMS/spatial/fci/CMakeLists.txt +++ b/tests/MMS/spatial/fci/CMakeLists.txt @@ -3,5 +3,6 @@ bout_add_mms_test( SOURCES fci_mms.cxx USE_RUNTEST USE_DATA_BOUT_INP REQUIRES zoidberg_FOUND + REQUIRES BOUT_USE_METRIC_3D PROCESSORS 2 ) diff --git a/tests/MMS/spatial/fci/data/BOUT.inp b/tests/MMS/spatial/fci/data/BOUT.inp index 5f2001a906..76ac3035c9 100644 --- a/tests/MMS/spatial/fci/data/BOUT.inp +++ b/tests/MMS/spatial/fci/data/BOUT.inp @@ -1,15 +1,20 @@ - input_field = sin(y - 2*z) + sin(y - z) - -solution = (6.28318530717959*(0.01*x + 0.045)*(-2*cos(y - 2*z) - cos(y - z)) + 0.628318530717959*cos(y - 2*z) + 0.628318530717959*cos(y - z))/sqrt((0.01*x + 0.045)^2 + 1.0) - -MXG = 1 -MYG = 1 -NXPE = 1 +grad_par_solution = (6.28318530717959*(0.01*x + 0.045)*(-2*cos(y - 2*z) - cos(y - z)) + 0.628318530717959*cos(y - 2*z) + 0.628318530717959*cos(y - z))/sqrt((0.01*x + 0.045)^2 + 1.0) +grad2_par2_solution = (6.28318530717959*(0.01*x + 0.045)*(6.28318530717959*(0.01*x + 0.045)*(-4*sin(y - 2*z) - sin(y - z)) + 1.25663706143592*sin(y - 2*z) + 0.628318530717959*sin(y - z))/sqrt((0.01*x + 0.045)^2 + 1.0) + 0.628318530717959*(6.28318530717959*(0.01*x + 0.045)*(2*sin(y - 2*z) + sin(y - z)) - 0.628318530717959*sin(y - 2*z) - 0.628318530717959*sin(y - z))/sqrt((0.01*x + 0.045)^2 + 1.0))/sqrt((0.01*x + 0.045)^2 + 1.0) +div_par_solution = (0.01*x + 0.045)*(-12.5663706143592*cos(y - 2*z) - 6.28318530717959*cos(y - z) + 0.628318530717959*(cos(y - 2*z) + cos(y - z))/(0.01*x + 0.045))/sqrt((0.01*x + 0.045)^2 + 1.0) +div_par_K_grad_par_solution = (0.01*x + 0.045)*(6.28318530717959*sin(y - z) - 0.628318530717959*sin(y - z)/(0.01*x + 0.045))*(6.28318530717959*(0.01*x + 0.045)*(-2*cos(y - 2*z) - cos(y - z)) + 0.628318530717959*cos(y - 2*z) + 0.628318530717959*cos(y - z))/((0.01*x + 0.045)^2 + 1.0) + (6.28318530717959*(0.01*x + 0.045)*(6.28318530717959*(0.01*x + 0.045)*(-4*sin(y - 2*z) - sin(y - z)) + 1.25663706143592*sin(y - 2*z) + 0.628318530717959*sin(y - z))/sqrt((0.01*x + 0.045)^2 + 1.0) + 0.628318530717959*(6.28318530717959*(0.01*x + 0.045)*(2*sin(y - 2*z) + sin(y - z)) - 0.628318530717959*sin(y - 2*z) - 0.628318530717959*sin(y - z))/sqrt((0.01*x + 0.045)^2 + 1.0))*cos(y - z)/sqrt((0.01*x + 0.045)^2 + 1.0) +K = cos(y - z) +laplace_par_solution = (0.01*x + 0.045)*(6.28318530717959*(6.28318530717959*(0.01*x + 0.045)*(-4*sin(y - 2*z) - sin(y - z)) + 1.25663706143592*sin(y - 2*z) + 0.628318530717959*sin(y - z))/sqrt((0.01*x + 0.045)^2 + 1.0) + 0.628318530717959*(6.28318530717959*(0.01*x + 0.045)*(2*sin(y - 2*z) + sin(y - z)) - 0.628318530717959*sin(y - 2*z) - 0.628318530717959*sin(y - z))/((0.01*x + 0.045)*sqrt((0.01*x + 0.045)^2 + 1.0)))/sqrt((0.01*x + 0.045)^2 + 1.0) +FV_div_par_mod_solution = (0.01*x + 0.045)*(6.28318530717959*(0.01*x + 0.045)*((sin(y - 2*z) + sin(y - z))*sin(y - z)/(0.01*x + 0.045) + (-2*cos(y - 2*z) - cos(y - z))*cos(y - z)/(0.01*x + 0.045)) - 0.628318530717959*(sin(y - 2*z) + sin(y - z))*sin(y - z)/(0.01*x + 0.045) + 0.628318530717959*(cos(y - 2*z) + cos(y - z))*cos(y - z)/(0.01*x + 0.045))/sqrt((0.01*x + 0.045)^2 + 1.0) +FV_div_par_fvv_solution = (0.01*x + 0.045)*(6.28318530717959*(0.01*x + 0.045)*(2*(sin(y - 2*z) + sin(y - z))*sin(y - z)*cos(y - z)/(0.01*x + 0.045) + (-2*cos(y - 2*z) - cos(y - z))*cos(y - z)^2/(0.01*x + 0.045)) - 1.25663706143592*(sin(y - 2*z) + sin(y - z))*sin(y - z)*cos(y - z)/(0.01*x + 0.045) + 0.628318530717959*(cos(y - 2*z) + cos(y - z))*cos(y - z)^2/(0.01*x + 0.045))/sqrt((0.01*x + 0.045)^2 + 1.0) +div_par_K_grad_par_mod_solution = (0.01*x + 0.045)*(6.28318530717959*sin(y - z) - 0.628318530717959*sin(y - z)/(0.01*x + 0.045))*(6.28318530717959*(0.01*x + 0.045)*(-2*cos(y - 2*z) - cos(y - z)) + 0.628318530717959*cos(y - 2*z) + 0.628318530717959*cos(y - z))/((0.01*x + 0.045)^2 + 1.0) + (6.28318530717959*(0.01*x + 0.045)*(6.28318530717959*(0.01*x + 0.045)*(-4*sin(y - 2*z) - sin(y - z)) + 1.25663706143592*sin(y - 2*z) + 0.628318530717959*sin(y - z))/sqrt((0.01*x + 0.045)^2 + 1.0) + 0.628318530717959*(6.28318530717959*(0.01*x + 0.045)*(2*sin(y - 2*z) + sin(y - z)) - 0.628318530717959*sin(y - 2*z) - 0.628318530717959*sin(y - z))/sqrt((0.01*x + 0.045)^2 + 1.0))*cos(y - z)/sqrt((0.01*x + 0.045)^2 + 1.0) [mesh] symmetricglobalx = true file = fci.grid.nc +MXG = 1 +MYG = 1 +NXPE = 1 [mesh:ddy] first = C2 diff --git a/tests/MMS/spatial/fci/fci_mms.cxx b/tests/MMS/spatial/fci/fci_mms.cxx index 18405a7f88..26f40217b8 100644 --- a/tests/MMS/spatial/fci/fci_mms.cxx +++ b/tests/MMS/spatial/fci/fci_mms.cxx @@ -1,6 +1,42 @@ #include "bout/bout.hxx" -#include "bout/derivs.hxx" +#include "bout/difops.hxx" +#include "bout/field.hxx" +#include "bout/field2d.hxx" +#include "bout/field3d.hxx" #include "bout/field_factory.hxx" +#include "bout/fv_ops.hxx" +#include "bout/globals.hxx" +#include "bout/options.hxx" +#include "bout/options_io.hxx" +#include "bout/utils.hxx" + +#include + +#include +#include + +namespace { +auto fci_op_test(const std::string& name, Options& dump, const Field3D& input, + const Field3D& result) { + auto* mesh = input.getMesh(); + const Field3D solution{FieldFactory::get()->create3D(fmt::format("{}_solution", name), + Options::getRoot(), mesh)}; + const Field3D error{result - solution}; + + dump[fmt::format("{}_l_2", name)] = sqrt(mean(SQ(error), true, "RGN_NOBNDRY")); + dump[fmt::format("{}_l_inf", name)] = max(abs(error), true, "RGN_NOBNDRY"); + + dump[fmt::format("{}_result", name)] = result; + dump[fmt::format("{}_error", name)] = error; + dump[fmt::format("{}_input", name)] = input; + dump[fmt::format("{}_solution", name)] = solution; + + for (int slice = 1; slice < mesh->ystart; ++slice) { + dump[fmt::format("{}_input.ynext(-{})", name, slice)] = input.ynext(-slice); + dump[fmt::format("{}_input.ynext({})", name, slice)] = input.ynext(slice); + } +} +} // namespace int main(int argc, char** argv) { BoutInitialise(argc, argv); @@ -8,30 +44,29 @@ int main(int argc, char** argv) { using bout::globals::mesh; Field3D input{FieldFactory::get()->create3D("input_field", Options::getRoot(), mesh)}; - Field3D solution{FieldFactory::get()->create3D("solution", Options::getRoot(), mesh)}; + Field3D K{FieldFactory::get()->create3D("K", Options::getRoot(), mesh)}; - // Communicate to calculate parallel transform - mesh->communicate(input); - - Field3D result{Grad_par(input)}; - Field3D error{result - solution}; + // Communicate to calculate parallel transform. + mesh->communicate(input, K); Options dump; // Add mesh geometry variables mesh->outputVars(dump); - dump["l_2"] = sqrt(mean(SQ(error), true, "RGN_NOBNDRY")); - dump["l_inf"] = max(abs(error), true, "RGN_NOBNDRY"); + // Dummy variable for *_mod overloads + Field3D flow_ylow; - dump["result"] = result; - dump["error"] = error; - dump["input"] = input; - dump["solution"] = solution; + fci_op_test("grad_par", dump, input, Grad_par(input)); + fci_op_test("grad2_par2", dump, input, Grad2_par2(input)); + fci_op_test("div_par", dump, input, Div_par(input)); + fci_op_test("div_par_K_grad_par", dump, input, Div_par_K_Grad_par(K, input)); + fci_op_test("div_par_K_grad_par_mod", dump, input, + Div_par_K_Grad_par_mod(K, input, flow_ylow)); + fci_op_test("laplace_par", dump, input, Laplace_par(input)); - for (int slice = 1; slice < mesh->ystart; ++slice) { - dump[fmt::format("input.ynext(-{})", slice)] = input.ynext(-slice); - dump[fmt::format("input.ynext({})", slice)] = input.ynext(slice); - } + // Finite volume methods + fci_op_test("FV_div_par_mod", dump, input, FV::Div_par_mod(input, K, K, flow_ylow)); + fci_op_test("FV_div_par_fvv", dump, input, FV::Div_par_fvv(input, K, K)); bout::writeDefaultOutputFile(dump); diff --git a/tests/MMS/spatial/fci/mms.py b/tests/MMS/spatial/fci/mms.py index 1e71135c90..801a8d3f26 100755 --- a/tests/MMS/spatial/fci/mms.py +++ b/tests/MMS/spatial/fci/mms.py @@ -3,13 +3,19 @@ # Generate manufactured solution and sources for FCI test # -from boutdata.mms import * +from math import pi +import warnings -from sympy import sin, cos, sqrt +from boututils.boutwarnings import AlwaysWarning +from boutdata.data import BoutOptionsFile +from boutdata.mms import diff, exprToStr, x, y, z +from sympy import sin, cos, sqrt, Expr -from math import pi +warnings.simplefilter("ignore", AlwaysWarning) f = sin(y - z) + sin(y - 2 * z) +K = cos(z - y) + Lx = 0.1 Ly = 10.0 @@ -23,12 +29,45 @@ B = sqrt(Bpx**2 + Bt**2) -def FCI_ddy(f): +def FCI_grad_par(f: Expr) -> Expr: return (Bt * diff(f, y) * 2.0 * pi / Ly + Bpx * diff(f, z) * 2.0 * pi / Lz) / B +def FCI_grad2_par2(f: Expr) -> Expr: + return FCI_grad_par(FCI_grad_par(f)) + + +def FCI_div_par(f: Expr) -> Expr: + return Bpx * FCI_grad_par(f / Bpx) + + +def FCI_div_par_K_grad_par(f: Expr, K: Expr) -> Expr: + return (K * FCI_grad2_par2(f)) + (FCI_div_par(K) * FCI_grad_par(f)) + + +def FCI_Laplace_par(f: Expr) -> Expr: + return FCI_div_par(FCI_grad_par(f)) + + ############################################ # Equations solved -print("input = " + exprToStr(f)) -print("solution = " + exprToStr(FCI_ddy(f))) +options = BoutOptionsFile("data/BOUT.inp") + +for name, expr in ( + ("input_field", f), + ("K", K), + ("grad_par_solution", FCI_grad_par(f)), + ("grad2_par2_solution", FCI_grad2_par2(f)), + ("div_par_solution", FCI_div_par(f)), + ("div_par_K_grad_par_solution", FCI_div_par_K_grad_par(f, K)), + ("div_par_K_grad_par_mod_solution", FCI_div_par_K_grad_par(f, K)), + ("laplace_par_solution", FCI_Laplace_par(f)), + ("FV_div_par_mod_solution", FCI_div_par(f * K)), + ("FV_div_par_fvv_solution", FCI_div_par(f * K * K)), +): + expr_str = exprToStr(expr) + print(f"{name} = {expr_str}") + options[name] = expr_str + +options.write("data/BOUT.inp", overwrite=True) diff --git a/tests/MMS/spatial/fci/runtest b/tests/MMS/spatial/fci/runtest index 7a9d6e655e..7dab379e5a 100755 --- a/tests/MMS/spatial/fci/runtest +++ b/tests/MMS/spatial/fci/runtest @@ -6,209 +6,267 @@ # Cores: 2 # requires: zoidberg -from boututils.run_wrapper import build_and_log, launch_safe -from boutdata.collect import collect +import argparse +import json +import pathlib +import sys +from time import time + import boutconfig as conf +import zoidberg as zb +from boutdata.collect import collect +from boututils.run_wrapper import build_and_log, launch_safe +from numpy import array, linspace, log, polyfit + +# Global parameters +DIRECTORY = "data" +NPROC = 2 +MTHREAD = 2 +OPERATORS = ( + "grad_par", + "grad2_par2", + "div_par", + "div_par_K_grad_par", + "div_par_K_grad_par_mod", + "laplace_par", + "FV_div_par_mod", + "FV_div_par_fvv", +) + +# div_par_fvv is also tested in ../finite-volume, where 1.5th order is achieved +operator_order = { + "FV_div_par_fvv": 1, +} +# Note that we need at least _2_ interior points for hermite spline +# interpolation due to an awkwardness with the boundaries +NX = 4 +# Resolution in y and z +NLIST = [8, 16, 32, 64] +dx = 1.0 / array(NLIST) -from numpy import array, log, polyfit, linspace, arange -import pickle +def quiet_collect(name: str) -> float: + # Index to return a plain (numpy) float rather than `BoutArray` + return collect( + name, + tind=[1, 1], + info=False, + path=DIRECTORY, + xguards=False, + yguards=False, + )[()] + -from sys import stdout +def assert_convergence(error, dx, name, expected) -> bool: + fit = polyfit(log(dx), log(error), 1) + order = fit[0] + print(f"{name} convergence order = {order:f} (fit)", end="") -import zoidberg as zb + order = log(error[-2] / error[-1]) / log(dx[-2] / dx[-1]) + print(f", {order:f} (small spacing)", end="") -nx = 4 # Not changed for these tests + # Should be close to the expected order + success = order > expected * 0.95 + print(f"\t............ {'PASS' if success else 'FAIL'}") + + return success + + +def make_grids(): + for nz in NLIST: + make_grid(nz) + + +def make_grid(nz: int): + # Define the magnetic field using new poloidal gridding method + # Note that the Bz and Bzprime parameters here must be the same as in mms.py + field = zb.field.Slab(Bz=0.05, Bzprime=0.1) + # Create rectangular poloidal grids + poloidal_grid = zb.poloidal_grid.RectangularPoloidalGrid(NX, nz, 0.1, 1.0, MXG=1) + # Set the ylength and y locations + ylength = 10.0 -# Resolution in y and z -nlist = [8, 16, 32, 64, 128] - -# Number of parallel slices (in each direction) -nslices = [1] - -directory = "data" - -nproc = 2 -mthread = 2 - - -success = True - -error_2 = {} -error_inf = {} -method_orders = {} - -# Run with periodic Y? -yperiodic = True - -failures = [] - -build_and_log("FCI MMS test") - -for nslice in nslices: - for method in [ - "hermitespline", - "lagrange4pt", - "bilinear", - # "monotonichermitespline", - ]: - error_2[nslice] = [] - error_inf[nslice] = [] - - # Which central difference scheme to use and its expected order - order = nslice * 2 - method_orders[nslice] = {"name": "C{}".format(order), "order": order} - - for n in nlist: - # Define the magnetic field using new poloidal gridding method - # Note that the Bz and Bzprime parameters here must be the same as in mms.py - field = zb.field.Slab(Bz=0.05, Bzprime=0.1) - # Create rectangular poloidal grids - poloidal_grid = zb.poloidal_grid.RectangularPoloidalGrid( - nx, n, 0.1, 1.0, MXG=1 - ) - # Set the ylength and y locations - ylength = 10.0 - - if yperiodic: - ycoords = linspace(0.0, ylength, n, endpoint=False) - else: - # Doesn't include the end points - ycoords = (arange(n) + 0.5) * ylength / float(n) - - # Create the grid - grid = zb.grid.Grid(poloidal_grid, ycoords, ylength, yperiodic=yperiodic) - # Make and write maps - maps = zb.make_maps(grid, field, nslice=nslice, quiet=True, MXG=1) - zb.write_maps( - grid, - field, - maps, - new_names=False, - metric2d=conf.isMetric2D(), - quiet=True, - ) - - args = " MZ={} MYG={} mesh:paralleltransform:y_periodic={} mesh:ddy:first={} NXPE={}".format( - n, - nslice, - yperiodic, - method_orders[nslice]["name"], - 2 if conf.has["petsc"] and method == "hermitespline" else 1, - ) - args += f" mesh:paralleltransform:xzinterpolation:type={method}" - - # Command to run - cmd = "./fci_mms " + args - - print("Running command: " + cmd) - - # Launch using MPI - s, out = launch_safe(cmd, nproc=nproc, mthread=mthread, pipe=True) - - # Save output to log file - with open("run.log." + str(n), "w") as f: - f.write(out) - - if s: - print("Run failed!\nOutput was:\n") - print(out) - exit(s) - - # Collect data - l_2 = collect( - "l_2", - tind=[1, 1], - info=False, - path=directory, - xguards=False, - yguards=False, - ) - l_inf = collect( - "l_inf", - tind=[1, 1], - info=False, - path=directory, - xguards=False, - yguards=False, - ) - - error_2[nslice].append(l_2) - error_inf[nslice].append(l_inf) - - print("Errors : l-2 {:f} l-inf {:f}".format(l_2, l_inf)) - - dx = 1.0 / array(nlist) - - # Calculate convergence order - fit = polyfit(log(dx), log(error_2[nslice]), 1) - order = fit[0] - stdout.write("Convergence order = {:f} (fit)".format(order)) - - order = log(error_2[nslice][-2] / error_2[nslice][-1]) / log(dx[-2] / dx[-1]) - stdout.write(", {:f} (small spacing)".format(order)) - - # Should be close to the expected order - if order > method_orders[nslice]["order"] * 0.95: - print("............ PASS\n") - else: - print("............ FAIL\n") - success = False - failures.append(method_orders[nslice]["name"]) - - -with open("fci_mms.pkl", "wb") as output: - pickle.dump(nlist, output) - for nslice in nslices: - pickle.dump(error_2[nslice], output) - pickle.dump(error_inf[nslice], output) - -# Do we want to show the plot as well as save it to file. -showPlot = True - -if False: + ycoords = linspace(0.0, ylength, nz, endpoint=False) + + # Create the grid + grid = zb.grid.Grid(poloidal_grid, ycoords, ylength, yperiodic=True) + maps = zb.make_maps(grid, field, nslice=1, quiet=True, MXG=1) + zb.write_maps( + grid, + field, + maps, + new_names=False, + metric2d=conf.isMetric2D(), + quiet=True, + gridfile=f"mesh.{nz}.fci.nc", + ) + + +def run_fci_operators(nslice: int, nz: int, name: str) -> dict[str, float]: + + # Command to run + args = f"MYG={nslice} {name} mesh:file=mesh.{nz}.fci.nc" + cmd = f"./fci_mms {args}" + print(f"Running command: {cmd}", end="") + + # Launch using MPI + start = time() + status, out = launch_safe(cmd, nproc=NPROC, mthread=MTHREAD, pipe=True) + print(f" ... done in {time() - start:.3}s") + + # Save output to log file + pathlib.Path(f"run.log.{nz}").write_text(out) + + if status: + print(f"Run failed!\nOutput was:\n{out}") + sys.exit(status) + + return { + operator: { + "l_2": quiet_collect(f"{operator}_l_2"), + "l_inf": quiet_collect(f"{operator}_l_inf"), + } + for operator in OPERATORS + } + + +def transpose( + errors: list[dict[str, dict[str, float]]], +) -> dict[str, dict[str, list[float]]]: + """Turn a list of {operator: error} into a dict of {operator: [errors]}""" + + kinds = ("l_2", "l_inf") + result = {operator: {kind: [] for kind in kinds} for operator in OPERATORS} + for error in errors: + for k, v in error.items(): + for kind in kinds: + result[k][kind].append(v[kind]) + return result + + +def check_fci_operators(name: str, case: dict) -> bool: + failures = [] + + nslice = case["nslice"] + order = case["order"] + args = case["args"] + + all_errors = [] + + for n in NLIST: + errors = run_fci_operators(nslice, n, args) + all_errors.append(errors) + + for operator in OPERATORS: + l_2 = errors[operator]["l_2"] + l_inf = errors[operator]["l_inf"] + + print(f"{operator} errors: l-2 {l_2:f} l-inf {l_inf:f}") + + final_errors = transpose(all_errors) + for operator in OPERATORS: + test_name = f"{operator} {name}" + success = assert_convergence( + final_errors[operator]["l_2"], + dx, + test_name, + operator_order.get(operator, order), + ) + if not success: + failures.append(test_name) + + return final_errors, failures + + +def make_plots(cases: dict[str, dict]): try: - # Plot using matplotlib if available import matplotlib.pyplot as plt + except ImportError: + print("No matplotlib") + return - fig, ax = plt.subplots(1, 1) - - for nslice in nslices: - ax.plot( - dx, - error_2[nslice], - "-", - label="{} $l_2$".format(method_orders[nslice]["name"]), - ) - ax.plot( - dx, - error_inf[nslice], - "--", - label="{} $l_\\inf$".format(method_orders[nslice]["name"]), - ) + num_operators = len(OPERATORS) + fig, axes = plt.subplots(1, num_operators, figsize=(num_operators * 4, 4)) + + for ax, operator in zip(axes, OPERATORS): + for name, case in cases.items(): + ax.loglog(dx, case[operator]["l_2"], "-", label=f"{name} $l_2$") + ax.loglog(dx, case[operator]["l_inf"], "--", label=f"{name} $l_\\inf$") ax.legend(loc="upper left") ax.grid() - ax.set_yscale("log") - ax.set_xscale("log") - ax.set_title("error scaling") + ax.set_title(f"Error scaling for {operator}") ax.set_xlabel(r"Mesh spacing $\delta x$") ax.set_ylabel("Error norm") - plt.savefig("fci_mms.pdf") - - print("Plot saved to fci_mms.pdf") - - if showPlot: - plt.show() - plt.close() - except ImportError: - print("No matplotlib") - -if success: - print("All tests passed") - exit(0) -else: - print("Some tests failed:") - for failure in failures: - print("\t" + failure) - exit(1) + fig.tight_layout() + fig.savefig("fci_mms.pdf") + print("Plot saved to fci_mms.pdf") + + if args.show_plots: + plt.show() + plt.close() + + +if __name__ == "__main__": + build_and_log("FCI MMS test") + + parser = argparse.ArgumentParser("Error scaling test for FCI operators") + parser.add_argument( + "--make-plots", action="store_true", help="Create plots of error scaling" + ) + parser.add_argument( + "--show-plots", + action="store_true", + help="Stop and show plots, implies --make-plots", + ) + parser.add_argument( + "--dump-errors", + type=str, + help="Output file to dump errors as JSON", + default="fci_operator_errors.json", + ) + + args = parser.parse_args() + + success = True + failures = [] + cases = { + "nslice=1 hermitespline": { + "nslice": 1, + "order": 2, + "args": "mesh:ddy:first=C2 mesh:paralleltransform:xzinterpolation:type=hermitespline", + }, + "nslice=1 lagrange4pt": { + "nslice": 1, + "order": 2, + "args": "mesh:ddy:first=C2 mesh:paralleltransform:xzinterpolation:type=lagrange4pt", + }, + "nslice=1 monotonichermitespline": { + "nslice": 1, + "order": 2, + "args": "mesh:ddy:first=C2 mesh:paralleltransform:xzinterpolation:type=monotonichermitespline", + }, + } + + make_grids() + + for name, case in cases.items(): + error2, failures_ = check_fci_operators(name, case) + case.update(error2) + failures.extend(failures_) + success &= len(failures) == 0 + + if args.dump_errors: + pathlib.Path(args.dump_errors).write_text(json.dumps(cases)) + + if args.make_plots or args.show_plots: + make_plots(cases) + + if success: + print("\nAll tests passed") + else: + print("\nSome tests failed:") + for failure in failures: + print(f"\t{failure}") + + sys.exit(0 if success else 1) diff --git a/tests/MMS/spatial/finite-volume/CMakeLists.txt b/tests/MMS/spatial/finite-volume/CMakeLists.txt new file mode 100644 index 0000000000..7180eb7d98 --- /dev/null +++ b/tests/MMS/spatial/finite-volume/CMakeLists.txt @@ -0,0 +1,6 @@ +bout_add_mms_test( + MMS-spatial-finite-volume + SOURCES fv_mms.cxx + USE_RUNTEST USE_DATA_BOUT_INP + PROCESSORS 2 +) diff --git a/tests/MMS/spatial/finite-volume/data/BOUT.inp b/tests/MMS/spatial/finite-volume/data/BOUT.inp new file mode 100644 index 0000000000..029011e437 --- /dev/null +++ b/tests/MMS/spatial/finite-volume/data/BOUT.inp @@ -0,0 +1,23 @@ +input_field = 0.1*sin(2.0*y) + 1 +FV_Div_par_mod_solution = -0.188495559215388*sin(3.0*y) +FV_Div_par_fvv_solution = -0.376991118430775*(0.1*cos(3.0*y) + 1)*sin(3.0*y)/(0.1*sin(2.0*y) + 1) - 0.125663706143592*(0.1*cos(3.0*y) + 1)^2*cos(2.0*y)/(0.1*sin(2.0*y) + 1)^2 +FV_Div_par_solution = -0.188495559215388*sin(3.0*y) +FV_Div_par_K_Grad_par_solution = 0.125663706143592*(-0.188495559215388*sin(3.0*y)/(0.1*sin(2.0*y) + 1) - 0.125663706143592*(0.1*cos(3.0*y) + 1)*cos(2.0*y)/(0.1*sin(2.0*y) + 1)^2)*cos(2.0*y) - 0.15791367041743*(0.1*cos(3.0*y) + 1)*sin(2.0*y)/(0.1*sin(2.0*y) + 1) +FV_Div_par_K_Grad_par_mod_solution = 0.125663706143592*(-0.188495559215388*sin(3.0*y)/(0.1*sin(2.0*y) + 1) - 0.125663706143592*(0.1*cos(3.0*y) + 1)*cos(2.0*y)/(0.1*sin(2.0*y) + 1)^2)*cos(2.0*y) - 0.15791367041743*(0.1*cos(3.0*y) + 1)*sin(2.0*y)/(0.1*sin(2.0*y) + 1) +v = (0.1*cos(3.0*y) + 1)/(0.1*sin(2.0*y) + 1) + +[mesh] +MXG = 0 + +nx = 1 +ny = 128 +nz = 1 + +Ly = 10 + +dy = Ly / ny +J = 1 # Identity metric + +[mesh:ddy] +first = C2 +second = C2 diff --git a/tests/MMS/spatial/finite-volume/fv_mms.cxx b/tests/MMS/spatial/finite-volume/fv_mms.cxx new file mode 100644 index 0000000000..bb999bcc66 --- /dev/null +++ b/tests/MMS/spatial/finite-volume/fv_mms.cxx @@ -0,0 +1,102 @@ +#include "bout/bout.hxx" +#include "bout/build_config.hxx" +#include "bout/difops.hxx" +#include "bout/field.hxx" +#include "bout/field3d.hxx" +#include "bout/field_factory.hxx" +#include "bout/fv_ops.hxx" +#include "bout/globals.hxx" +#include "bout/options.hxx" +#include "bout/options_io.hxx" +#include "bout/utils.hxx" + +#include + +#include +#include + +namespace { +auto fv_op_test(const std::string& name, Options& dump, const Field3D& input, + const Field3D& result, std::string suffix = "") { + auto* mesh = input.getMesh(); + const Field3D solution{FieldFactory::get()->create3D(fmt::format("{}_solution", name), + Options::getRoot(), mesh)}; + const Field3D error{result - solution}; + + dump[fmt::format("{}{}_l_2", name, suffix)] = + sqrt(mean(SQ(error), true, "RGN_NOBNDRY")); + dump[fmt::format("{}{}_l_inf", name, suffix)] = max(abs(error), true, "RGN_NOBNDRY"); + + dump[fmt::format("{}{}_result", name, suffix)] = result; + dump[fmt::format("{}{}_error", name, suffix)] = error; + dump[fmt::format("{}{}_input", name, suffix)] = input; + dump[fmt::format("{}{}_solution", name, suffix)] = solution; +} +} // namespace + +int main(int argc, char** argv) { + BoutInitialise(argc, argv); + + using bout::globals::mesh; + + Field3D input{FieldFactory::get()->create3D("input_field", Options::getRoot(), mesh)}; + Field3D v{FieldFactory::get()->create3D("v", Options::getRoot(), mesh)}; + + // Communicate to calculate parallel transform. + if constexpr (bout::build::use_metric_3d) { + // Div_par operators require B parallel slices: + // Coordinates::geometry doesn't ensure this (yet) + auto& Bxy = mesh->getCoordinates()->Bxy; + auto& J = mesh->getCoordinates()->J; + auto& dy = mesh->getCoordinates()->dy; + auto& g_22 = mesh->getCoordinates()->g_22; + mesh->communicate(Bxy, J, dy, g_22); + } + mesh->communicate(input, v); + + Options dump; + // Add mesh geometry variables + mesh->outputVars(dump); + dump["v"] = v; + + // Dummy variable for *_mod overloads + Field3D flow_ylow; + + fv_op_test("FV_Div_par", dump, input, FV::Div_par(input, v, v), "_MC"); + fv_op_test("FV_Div_par_mod", dump, input, + FV::Div_par_mod(input, v, v, flow_ylow), "_MC"); + fv_op_test("FV_Div_par_fvv", dump, input, FV::Div_par_fvv(input, v, v), "_MC"); + + fv_op_test("FV_Div_par", dump, input, FV::Div_par(input, v, v), "_Upwind"); + fv_op_test("FV_Div_par_mod", dump, input, + FV::Div_par_mod(input, v, v, flow_ylow), "_Upwind"); + fv_op_test("FV_Div_par_fvv", dump, input, FV::Div_par_fvv(input, v, v), + "_Upwind"); + + fv_op_test("FV_Div_par", dump, input, FV::Div_par(input, v, v), "_Fromm"); + fv_op_test("FV_Div_par_mod", dump, input, + FV::Div_par_mod(input, v, v, flow_ylow), "_Fromm"); + fv_op_test("FV_Div_par_fvv", dump, input, FV::Div_par_fvv(input, v, v), + "_Fromm"); + + fv_op_test("FV_Div_par", dump, input, FV::Div_par(input, v, v), "_MinMod"); + fv_op_test("FV_Div_par_mod", dump, input, + FV::Div_par_mod(input, v, v, flow_ylow), "_MinMod"); + fv_op_test("FV_Div_par_fvv", dump, input, FV::Div_par_fvv(input, v, v), + "_MinMod"); + + fv_op_test("FV_Div_par", dump, input, FV::Div_par(input, v, v), + "_Superbee"); + fv_op_test("FV_Div_par_mod", dump, input, + FV::Div_par_mod(input, v, v, flow_ylow), "_Superbee"); + fv_op_test("FV_Div_par_fvv", dump, input, FV::Div_par_fvv(input, v, v), + "_Superbee"); + + fv_op_test("FV_Div_par_K_Grad_par", dump, input, FV::Div_par_K_Grad_par(v, input)); + fv_op_test("FV_Div_par_K_Grad_par_mod", dump, input, + Div_par_K_Grad_par_mod(v, input, flow_ylow)); + + bout::writeDefaultOutputFile(dump); + + BoutFinalise(); +} diff --git a/tests/MMS/spatial/finite-volume/makefile b/tests/MMS/spatial/finite-volume/makefile new file mode 100644 index 0000000000..88ba6c77e7 --- /dev/null +++ b/tests/MMS/spatial/finite-volume/makefile @@ -0,0 +1,6 @@ + +BOUT_TOP = ../../../.. + +SOURCEC = fci_mms.cxx + +include $(BOUT_TOP)/make.config diff --git a/tests/MMS/spatial/finite-volume/mms.py b/tests/MMS/spatial/finite-volume/mms.py new file mode 100755 index 0000000000..a95ecc1328 --- /dev/null +++ b/tests/MMS/spatial/finite-volume/mms.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +# +# Generate manufactured solution and sources for FCI test +# + +from math import pi +import warnings + +from boututils.boutwarnings import AlwaysWarning +from boutdata.data import BoutOptionsFile +from boutdata.mms import exprToStr, y, Grad_par, Div_par, Metric +from sympy import sin, cos, Expr + +warnings.simplefilter("ignore", AlwaysWarning) + +# Length of the y domain +Ly = 10.0 + +# Identity +metric = Metric() + +# Define solution in terms of input x,y,z +f = 1 + 0.1 * sin(2 * y) +fv = 1 + 0.1 * cos(3 * y) + + +# Turn solution into real x and z coordinates +replace = [(y, metric.y * 2 * pi / Ly)] + +f = f.subs(replace) +fv = fv.subs(replace) +v = fv / f + +# Substitute back to get input y coordinates +replace = [(metric.y, y * Ly / (2 * pi))] + + +def Grad2_par2(f: Expr) -> Expr: + return Grad_par(Grad_par(f)) + + +def Div_par_K_Grad_par(f: Expr, K: Expr) -> Expr: + return (K * Grad2_par2(f)) + (Div_par(K) * Grad_par(f)) + + +############################################ +# Equations solved + +options = BoutOptionsFile("data/BOUT.inp") + +for name, expr in ( + ("input_field", f), + ("v", v), + ("FV_Div_par_solution", Div_par(f * v)), + ("FV_Div_par_K_Grad_par_solution", Div_par_K_Grad_par(f, v)), + ("FV_Div_par_K_Grad_par_mod_solution", Div_par_K_Grad_par(f, v)), + ("FV_Div_par_mod_solution", Div_par(f * v)), + ("FV_Div_par_fvv_solution", Div_par(f * v * v)), +): + expr_str = exprToStr(expr.subs(replace)) + print(f"{name} = {expr_str}") + options[name] = expr_str + +options.write("data/BOUT.inp", overwrite=True) diff --git a/tests/MMS/spatial/finite-volume/runtest b/tests/MMS/spatial/finite-volume/runtest new file mode 100755 index 0000000000..bcd4672545 --- /dev/null +++ b/tests/MMS/spatial/finite-volume/runtest @@ -0,0 +1,201 @@ +#!/usr/bin/env python3 +# +# Python script to run and analyse MMS test +# + +import argparse +import json +import pathlib +import sys +from time import time + +from boutdata.collect import collect +from boututils.run_wrapper import build_and_log, launch_safe +from numpy import array, log, polyfit + +# Global parameters +DIRECTORY = "data" +NPROC = 2 +MTHREAD = 2 +OPERATORS = { + # Slope-limiters necessarily reduce the accuracy in places + "FV_Div_par_MC": 1.5, + "FV_Div_par_mod_MC": 1.5, + "FV_Div_par_fvv_MC": 1.5, + "FV_Div_par_Upwind": 1, + "FV_Div_par_mod_Upwind": 1, + "FV_Div_par_fvv_Upwind": 1, + "FV_Div_par_Fromm": 1.5, + "FV_Div_par_mod_Fromm": 1.5, + "FV_Div_par_fvv_Fromm": 1.5, + "FV_Div_par_MinMod": 1.5, + "FV_Div_par_mod_MinMod": 1.5, + "FV_Div_par_fvv_MinMod": 1.5, + "FV_Div_par_Superbee": 1.5, + "FV_Div_par_mod_Superbee": 1.5, + "FV_Div_par_fvv_Superbee": 1.5, + "FV_Div_par_K_Grad_par": 2, + "FV_Div_par_K_Grad_par_mod": 2, +} +# Resolution in y and z +NLIST = [8, 16, 32, 64] +dx = 1.0 / array(NLIST) + + +def quiet_collect(name: str) -> float: + # Index to return a plain (numpy) float rather than `BoutArray` + return collect( + name, + tind=[1, 1], + info=False, + path=DIRECTORY, + xguards=False, + yguards=False, + )[()] + + +def assert_convergence(error, dx, name, expected) -> bool: + fit = polyfit(log(dx), log(error), 1) + order = fit[0] + print(f"{name} convergence order = {order:f} (fit)", end="") + + order = log(error[-2] / error[-1]) / log(dx[-2] / dx[-1]) + print(f", {order:f} (small spacing)", end="") + + # Should be close to the expected order + success = order > expected * 0.95 + print(f"\t............ {'PASS' if success else 'FAIL'}") + + return success + + +def run_fv_operators(nz: int) -> dict[str, float]: + # Command to run + cmd = f"./fv_mms MZ={nz} mesh:ny={nz}" + print(f"Running command: {cmd}", end="") + + # Launch using MPI + start = time() + status, out = launch_safe(cmd, nproc=NPROC, mthread=MTHREAD, pipe=True) + print(f" ... done in {time() - start:.3}s") + + # Save output to log file + pathlib.Path(f"run.log.{nz}").write_text(out) + + if status: + print(f"Run failed!\nOutput was:\n{out}") + sys.exit(status) + + return { + operator: { + "l_2": quiet_collect(f"{operator}_l_2"), + "l_inf": quiet_collect(f"{operator}_l_inf"), + } + for operator in OPERATORS + } + + +def transpose( + errors: list[dict[str, dict[str, float]]], +) -> dict[str, dict[str, list[float]]]: + """Turn a list of {operator: error} into a dict of {operator: [errors]}""" + + kinds = ("l_2", "l_inf") + result = {operator: {kind: [] for kind in kinds} for operator in OPERATORS} + for error in errors: + for k, v in error.items(): + for kind in kinds: + result[k][kind].append(v[kind]) + return result + + +def test_fv_operators() -> bool: + failures = [] + + all_errors = [] + + for n in NLIST: + errors = run_fv_operators(n) + all_errors.append(errors) + + for operator in OPERATORS: + l_2 = errors[operator]["l_2"] + l_inf = errors[operator]["l_inf"] + + print(f"{operator} errors: l-2 {l_2:f} l-inf {l_inf:f}") + + final_errors = transpose(all_errors) + for operator, order in OPERATORS.items(): + success = assert_convergence(final_errors[operator]["l_2"], dx, operator, order) + if not success: + failures.append(operator) + + return final_errors, failures + + +def make_plots(cases: dict[str, dict]): + try: + import matplotlib.pyplot as plt + except ImportError: + print("No matplotlib") + return + + num_operators = len(OPERATORS) + fig, axes = plt.subplots(1, num_operators, figsize=(num_operators * 4, 4)) + + for ax, operator in zip(axes, OPERATORS): + ax.loglog(dx, cases[operator]["l_2"], "-", label="$l_2$") + ax.loglog(dx, cases[operator]["l_inf"], "--", label="$l_\\inf$") + ax.legend(loc="upper left") + ax.grid() + ax.set_title(f"Error scaling for {operator}") + ax.set_xlabel(r"Mesh spacing $\delta x$") + ax.set_ylabel("Error norm") + + fig.tight_layout() + fig.savefig("fv_mms.pdf") + print("Plot saved to fv_mms.pdf") + + if args.show_plots: + plt.show() + plt.close() + + +if __name__ == "__main__": + build_and_log("Finite volume MMS test") + + parser = argparse.ArgumentParser("Error scaling test for finite volume operators") + parser.add_argument( + "--make-plots", action="store_true", help="Create plots of error scaling" + ) + parser.add_argument( + "--show-plots", + action="store_true", + help="Stop and show plots, implies --make-plots", + ) + parser.add_argument( + "--dump-errors", + type=str, + help="Output file to dump errors as JSON", + default="fv_operator_errors.json", + ) + + args = parser.parse_args() + + error2, failures = test_fv_operators() + success = len(failures) == 0 + + if args.dump_errors: + pathlib.Path(args.dump_errors).write_text(json.dumps(error2)) + + if args.make_plots or args.show_plots: + make_plots(error2) + + if success: + print("\nAll tests passed") + else: + print("\nSome tests failed:") + for failure in failures: + print(f"\t{failure}") + + sys.exit(0 if success else 1) diff --git a/tests/integrated/test-boutpp/collect/input/BOUT.inp b/tests/integrated/test-boutpp/collect/input/BOUT.inp index d5ca4c4d71..519faa0403 100644 --- a/tests/integrated/test-boutpp/collect/input/BOUT.inp +++ b/tests/integrated/test-boutpp/collect/input/BOUT.inp @@ -1,7 +1,3 @@ - -MXG = 2 -MYG = 2 - [mesh] staggergrids = true n = 1 diff --git a/tests/integrated/test-boutpp/mms-ddz/data/BOUT.inp b/tests/integrated/test-boutpp/mms-ddz/data/BOUT.inp index d5ca4c4d71..519faa0403 100644 --- a/tests/integrated/test-boutpp/mms-ddz/data/BOUT.inp +++ b/tests/integrated/test-boutpp/mms-ddz/data/BOUT.inp @@ -1,7 +1,3 @@ - -MXG = 2 -MYG = 2 - [mesh] staggergrids = true n = 1 diff --git a/tests/integrated/test-fci-boundary/get_par_bndry.cxx b/tests/integrated/test-fci-boundary/get_par_bndry.cxx index ac0f5de2a6..0cbc8b02e9 100644 --- a/tests/integrated/test-fci-boundary/get_par_bndry.cxx +++ b/tests/integrated/test-fci-boundary/get_par_bndry.cxx @@ -1,31 +1,37 @@ #include "bout/bout.hxx" -#include "bout/derivs.hxx" +#include "bout/field3d.hxx" #include "bout/field_factory.hxx" +#include "bout/globals.hxx" +#include "bout/options.hxx" +#include "bout/options_io.hxx" +#include "bout/output.hxx" #include "bout/parallel_boundary_region.hxx" +#include +#include int main(int argc, char** argv) { BoutInitialise(argc, argv); using bout::globals::mesh; - std::vector fields; - fields.resize(static_cast(BoundaryParType::SIZE)); + std::vector fields(static_cast(BoundaryParType::SIZE), Field3D{0.0}); + Options dump; for (int i = 0; i < fields.size(); i++) { - fields[i] = Field3D{0.0}; + fields[i].allocate(); + const auto boundary = static_cast(i); + const auto boundary_name = toString(boundary); mesh->communicate(fields[i]); - for (const auto& bndry_par : - mesh->getBoundariesPar(static_cast(i))) { - output.write("{:s} region\n", toString(static_cast(i))); - for (bndry_par->first(); !bndry_par->isDone(); bndry_par->next()) { - fields[i][bndry_par->ind()] += 1; - output.write("{:s} increment\n", toString(static_cast(i))); + for (const auto& bndry_par : mesh->getBoundariesPar(boundary)) { + output.write("{:s} region\n", boundary_name); + for (const auto& pnt : *bndry_par) { + fields[i][pnt.ind()] += 1; + output.write("{:s} increment\n", boundary_name); } } - output.write("{:s} done\n", toString(static_cast(i))); + output.write("{:s} done\n", boundary_name); - dump[fmt::format("field_{:s}", toString(static_cast(i)))] = - fields[i]; + dump[fmt::format("field_{:s}", boundary_name)] = fields[i]; } bout::writeDefaultOutputFile(dump); diff --git a/tests/integrated/test-fci-boundary/runtest b/tests/integrated/test-fci-boundary/runtest index 1b1460da53..30d58cfddb 100755 --- a/tests/integrated/test-fci-boundary/runtest +++ b/tests/integrated/test-fci-boundary/runtest @@ -1,29 +1,14 @@ #!/usr/bin/env python3 # # Python script to run and analyse MMS test -# -# Cores: 2 -# only working with cmake -# requires: False from boututils.run_wrapper import launch_safe from boututils.datafile import DataFile -from boutdata.collect import collect as _collect +from boutdata.collect import collect import numpy as np - -def collect(var): - return _collect( - var, - info=False, - path=directory, - xguards=False, - yguards=False, - ) - - -nprocs = [1] # , 2, 4] +nprocs = [1] mthread = 2 directory = "data" @@ -43,11 +28,6 @@ regions = { } regions = {k: v.astype(int) for k, v in regions.items()} -# for x in "xout", "xin": -# regions[x] = np.logical_or(regions[f"{x}_fwd"], regions[f"{x}_bwd"]) -# for x in "fwd", "bwd": -# regions[x] = np.logical_or(regions[f"xin_{x}"], regions[f"xout_{x}"]) -# regions["all"] = np.logical_or(regions["xin"], regions["xout"]) for x in "xout", "xin": regions[x] = regions[f"{x}_fwd"] + regions[f"{x}_bwd"] for x in "fwd", "bwd": @@ -56,15 +36,18 @@ regions["all"] = regions["xin"] + regions["xout"] for nproc in nprocs: cmd = "./get_par_bndry" - - # Launch using MPI _, out = launch_safe(cmd, nproc=nproc, mthread=mthread, pipe=True) for k, v in regions.items(): - # Collect data - data = collect(f"field_{k}") + data = collect( + f"field_{k}", + info=False, + path=directory, + xguards=False, + yguards=False, + ) assert np.allclose(data, v), ( - k + " does not match", + f"{k} does not match", np.sum(data), np.sum(v), np.max(data), diff --git a/tests/integrated/test-fci-mpi/CMakeLists.txt b/tests/integrated/test-fci-mpi/CMakeLists.txt index 0abbbce099..ba126d7e76 100644 --- a/tests/integrated/test-fci-mpi/CMakeLists.txt +++ b/tests/integrated/test-fci-mpi/CMakeLists.txt @@ -4,7 +4,7 @@ bout_add_mms_test( USE_RUNTEST USE_DATA_BOUT_INP PROCESSORS 6 DOWNLOAD - https://zenodo.org/records/7614499/files/W7X-conf4-36x8x128.fci.nc?download=1 + https://zenodo.org/records/17571607/files/W7X-conf0-36x8x128.fci.nc?download=1 DOWNLOAD_NAME grid.fci.nc REQUIRES BOUT_HAS_PETSC ) diff --git a/tests/integrated/test-fci-mpi/fci_mpi.cxx b/tests/integrated/test-fci-mpi/fci_mpi.cxx index 94520dd4a6..94e8e878ef 100644 --- a/tests/integrated/test-fci-mpi/fci_mpi.cxx +++ b/tests/integrated/test-fci-mpi/fci_mpi.cxx @@ -1,38 +1,41 @@ +#include "fmt/format.h" #include "bout/bout.hxx" -#include "bout/derivs.hxx" +#include "bout/field3d.hxx" #include "bout/field_factory.hxx" +#include "bout/globals.hxx" +#include "bout/options.hxx" +#include "bout/options_io.hxx" +#include "bout/region.hxx" + +namespace { +auto fci_mpi_test(int num, Options& dump) { + using bout::globals::mesh; + Field3D input{FieldFactory::get()->create3D(fmt::format("input_{:d}:function", num), + Options::getRoot(), mesh)}; + mesh->communicate(input); + input.applyParallelBoundary("parallel_neumann_o2"); + + for (int slice = -mesh->ystart; slice <= mesh->ystart; ++slice) { + if (slice == 0) { + continue; + } + Field3D tmp{0.}; + BOUT_FOR(i, tmp.getRegion("RGN_NOBNDRY")) { + tmp[i] = input.ynext(slice)[i.yp(slice)]; + } + dump[fmt::format("output_{:d}_{:+d}", num, slice)] = tmp; + } +} +} // namespace int main(int argc, char** argv) { BoutInitialise(argc, argv); - { - using bout::globals::mesh; - Options* options = Options::getRoot(); - int i = 0; - const std::string default_str{"not_set"}; - Options dump; - while (true) { - std::string temp_str; - options->get(fmt::format("input_{:d}:function", i), temp_str, default_str); - if (temp_str == default_str) { - break; - } - Field3D input{FieldFactory::get()->create3D(fmt::format("input_{:d}:function", i), - Options::getRoot(), mesh)}; - // options->get(fmt::format("input_{:d}:boundary_perp", i), temp_str, s"free_o3"); - mesh->communicate(input); - input.applyParallelBoundary("parallel_neumann_o2"); - for (int slice = -mesh->ystart; slice <= mesh->ystart; ++slice) { - if (slice != 0) { - Field3D tmp{0.}; - BOUT_FOR(i, tmp.getRegion("RGN_NOBNDRY")) { - tmp[i] = input.ynext(slice)[i.yp(slice)]; - } - dump[fmt::format("output_{:d}_{:+d}", i, slice)] = tmp; - } - } - ++i; - } - bout::writeDefaultOutputFile(dump); + Options dump; + + for (auto num : {0, 1, 2, 3}) { + fci_mpi_test(num, dump); } + + bout::writeDefaultOutputFile(dump); BoutFinalise(); } diff --git a/tests/integrated/test-fci-mpi/runtest b/tests/integrated/test-fci-mpi/runtest index 6676f8f7a5..c18ab0391d 100755 --- a/tests/integrated/test-fci-mpi/runtest +++ b/tests/integrated/test-fci-mpi/runtest @@ -1,57 +1,82 @@ #!/usr/bin/env python3 # # Python script to run and analyse MMS test -# - -# Cores: 8 -# requires: metric_3d -from boututils.run_wrapper import build_and_log, launch_safe, shell_safe +from boututils.run_wrapper import build_and_log, launch_safe from boutdata.collect import collect -import boutconfig as conf import itertools +import sys -import numpy as np +import numpy.testing as npt # Resolution in x and y -nlist = [1, 2, 4] +NLIST = [1, 2, 4] +MAXCORES = 8 +NSLICES = [1] -maxcores = 8 +build_and_log("FCI MMS test") -nslices = [1] +COLLECT_KW = dict(info=False, xguards=False, yguards=False, path="data") -success = True -build_and_log("FCI MMS test") +def run_case(nxpe: int, nype: int, mthread: int): + cmd = f"./fci_mpi NXPE={nxpe} NYPE={nype}" + print(f"Running command: {cmd}") + + _, out = launch_safe(cmd, nproc=nxpe * nype, mthread=mthread, pipe=True) + + # Save output to log file + with open(f"run.log.{nxpe}.{nype}.{nslice}.log", "w") as f: + f.write(out) + + +def test_case(nxpe: int, nype: int, mthread: int, ref: dict) -> bool: + run_case(nxpe, nype, mthread) + + failures = [] + + for name, val in ref.items(): + try: + npt.assert_allclose(val, collect(name, **COLLECT_KW)) + except AssertionError as e: + failures.append((nxpe, nype, name, e)) -for nslice in nslices: - for NXPE, NYPE in itertools.product(nlist, nlist): - if NXPE * NYPE > maxcores: + return failures + + +failures = [] + +for nslice in NSLICES: + # reference data! + run_case(1, 1, MAXCORES) + + ref = {} + for i in range(4): + for yp in range(1, nslice + 1): + for y in [-yp, yp]: + name = f"output_{i}_{y:+d}" + ref[name] = collect(name, **COLLECT_KW) + + for nxpe, nype in itertools.product(NLIST, NLIST): + if (nxpe, nype) == (1, 1): + # reference case, done above continue - args = f"NXPE={NXPE} NYPE={NYPE}" - # Command to run - cmd = f"./fci_mpi {args}" - - print(f"Running command: {cmd}") - - mthread = maxcores // (NXPE * NYPE) - # Launch using MPI - _, out = launch_safe(cmd, nproc=NXPE * NYPE, mthread=mthread, pipe=True) - - # Save output to log file - with open(f"run.log.{NXPE}.{NYPE}.{nslice}.log", "w") as f: - f.write(out) - - collect_kw = dict(info=False, xguards=False, yguards=False, path="data") - if NXPE == NYPE == 1: - # reference data! - ref = {} - for i in range(4): - for yp in range(1, nslice + 1): - for y in [-yp, yp]: - name = f"output_{i}_{y:+d}" - ref[name] = collect(name, **collect_kw) - else: - for name, val in ref.items(): - assert np.allclose(val, collect(name, **collect_kw)) + if nxpe * nype > MAXCORES: + continue + + mthread = MAXCORES // (nxpe * nype) + failures_ = test_case(nxpe, nype, mthread, ref) + failures.extend(failures_) + + +success = len(failures) == 0 +if success: + print("\nAll tests passed") +else: + print("\nSome tests failed:") + for nxpe, nype, name, error in failures: + print("----------") + print(f"case {nxpe=} {nype=} {name=}\n{error}") + +sys.exit(0 if success else 1) diff --git a/tests/integrated/test-laplace-hypre3d/runtest b/tests/integrated/test-laplace-hypre3d/runtest index b50c5993b7..12ae54d9b7 100755 --- a/tests/integrated/test-laplace-hypre3d/runtest +++ b/tests/integrated/test-laplace-hypre3d/runtest @@ -13,7 +13,7 @@ test_directories = [ ("data_circular_core-sol", 1), ] -tolerance = 1.0e-6 +tolerance = 1.0e-4 build_and_log("Laplace 3D with Hypre") diff --git a/tests/integrated/test-laplace-petsc3d/runtest b/tests/integrated/test-laplace-petsc3d/runtest index 7b0d55f357..44ae3340fb 100755 --- a/tests/integrated/test-laplace-petsc3d/runtest +++ b/tests/integrated/test-laplace-petsc3d/runtest @@ -13,7 +13,7 @@ test_directories = [ ("data_circular_core-sol", 1), ] -tolerance = 1.0e-6 +tolerance = 1.0e-4 build_and_log("Laplace 3D with PETSc") diff --git a/tests/integrated/test-petsc_laplace/data/BOUT.inp b/tests/integrated/test-petsc_laplace/data/BOUT.inp index e7c285b54c..3fb3f25b63 100644 --- a/tests/integrated/test-petsc_laplace/data/BOUT.inp +++ b/tests/integrated/test-petsc_laplace/data/BOUT.inp @@ -26,20 +26,9 @@ nonuniform = true rtol = 1e-08 atol = 1e-06 include_yguards = false -maxits = 1000 +maxits = 100000 -gmres_max_steps = 300 - -pctype = shell # Supply a second solver as a preconditioner -rightprec = true # Right precondition - -[petsc2nd:precon] # Options for the preconditioning solver -# Leave default type (tri or spt) -all_terms = true -nonuniform = true -filter = 0.0 # Must not filter -inner_boundary_flags = 32 # Identity in boundary -outer_boundary_flags = 32 # Identity in boundary +gmres_max_steps = 3000 ############################################# @@ -50,32 +39,7 @@ nonuniform = true rtol = 1e-08 atol = 1e-06 include_yguards = false -maxits = 1000 +maxits = 100000 fourth_order = true -gmres_max_steps = 30 - -pctype = shell -rightprec = true - -[petsc4th:precon] -all_terms = true -nonuniform = true -filter = 0.0 -inner_boundary_flags = 32 # Identity in boundary -outer_boundary_flags = 32 # Identity in boundary - -############################################# - -[SPT] -#type=spt -all_terms = true -nonuniform = true -#flags=15 -include_yguards = false - -#maxits=10000 - -[laplace] -all_terms = true -nonuniform = true +gmres_max_steps = 300 diff --git a/tests/integrated/test-petsc_laplace/runtest b/tests/integrated/test-petsc_laplace/runtest index 83e1006338..3522063c1a 100755 --- a/tests/integrated/test-petsc_laplace/runtest +++ b/tests/integrated/test-petsc_laplace/runtest @@ -9,20 +9,14 @@ # cores: 4 # Variables to compare -from __future__ import print_function -from builtins import str - vars = [ ("max_error1", 2.0e-4), ("max_error2", 2.0e-4), ("max_error3", 2.0e-4), - ("max_error4", 1.0e-5), + ("max_error4", 2.0e-4), ("max_error5", 2.0e-4), - ("max_error6", 2.0e-5), - ("max_error7", 2.0e-4), - ("max_error8", 2.0e-5), + ("max_error6", 2.0e-4), ] -# tol = 1e-4 # Absolute (?) tolerance from boututils.run_wrapper import build_and_log, shell, launch_safe from boutdata.collect import collect @@ -58,7 +52,7 @@ for nproc in [1, 2, 4]: print("Convergence error") success = False elif error > tol: - print("Fail, maximum error is = " + str(error)) + print(f"Fail, maximum error is = {error}") success = False else: print("Pass") diff --git a/tests/integrated/test-petsc_laplace/test_petsc_laplace.cxx b/tests/integrated/test-petsc_laplace/test_petsc_laplace.cxx index c42c55d8d6..8e7c0f7da8 100644 --- a/tests/integrated/test-petsc_laplace/test_petsc_laplace.cxx +++ b/tests/integrated/test-petsc_laplace/test_petsc_laplace.cxx @@ -34,6 +34,7 @@ #include "bout/options.hxx" #include "bout/options_io.hxx" #include "bout/output.hxx" +#include "bout/sys/timer.hxx" #include "bout/traits.hxx" #include "fmt/core.h" @@ -66,14 +67,10 @@ void check_laplace(int test_num, std::string_view test_name, Laplacian& invert, Field3D abs_error; BoutReal max_error = -1; - try { - sol = invert.solve(sliceXZ(bcoef, ystart)); - error = (field - sol) / field; - abs_error = field - sol; - max_error = max_error_at_ystart(abs(abs_error)); - } catch (BoutException& err) { - output.write("BoutException occured in invert->solve(b1): {}\n", err.what()); - } + sol = invert.solve(sliceXZ(bcoef, ystart)); + error = (field - sol) / field; + abs_error = field - sol; + max_error = max_error_at_ystart(abs(abs_error)); output.write("\nTest {}: {}\n", test_num, test_name); output.write("Magnitude of maximum absolute error is {}\n", max_error); @@ -147,7 +144,7 @@ int main(int argc, char** argv) { INVERT_AC_GRAD, a_1, c_1, d_1, b_1, f_1, mesh->ystart, dump); //////////////////////////////////////////////////////////////////////////////////////// - // Test 3+4: Gaussian x-profiles, z-independent coefficients and compare with SPT method + // Test 3: Gaussian x-profiles, z-independent coefficients const Field2D a_3 = DC(a_1); const Field2D c_3 = DC(c_1); @@ -158,15 +155,8 @@ int main(int argc, char** argv) { INVERT_AC_GRAD, INVERT_AC_GRAD, a_3, c_3, d_3, b_3, f_1, mesh->ystart, dump); - Options* SPT_options = Options::getRoot()->getSection("SPT"); - auto invert_SPT = Laplacian::create(SPT_options); - - check_laplace(++test_num, "with coefficients constant in z, default solver", - *invert_SPT, INVERT_AC_GRAD, INVERT_AC_GRAD | INVERT_DC_GRAD, a_3, c_3, - d_3, b_3, f_1, mesh->ystart, dump); - ////////////////////////////////////////////// - // Test 5: Cosine x-profiles, 2nd order Krylov + // Test 4: Cosine x-profiles, 2nd order Krylov Field3D f_5 = generate_f5(*mesh); Field3D a_5 = generate_a5(*mesh); Field3D c_5 = generate_c5(*mesh); @@ -181,14 +171,14 @@ int main(int argc, char** argv) { dump); ////////////////////////////////////////////// - // Test 6: Cosine x-profiles, 4th order Krylov + // Test 5: Cosine x-profiles, 4th order Krylov check_laplace(++test_num, "different profiles, PETSc 4th order", *invert_4th, INVERT_AC_GRAD, INVERT_AC_GRAD, a_5, c_5, d_5, b_5, f_5, mesh->ystart, dump); ////////////////////////////////////////////////////////////////////////////////////// - // Test 7+8: Cosine x-profiles, z-independent coefficients and compare with SPT method + // Test 6: Cosine x-profiles, z-independent coefficients const Field2D a_7 = DC(a_5); const Field2D c_7 = DC(c_5); @@ -200,17 +190,15 @@ int main(int argc, char** argv) { *invert, INVERT_AC_GRAD, INVERT_AC_GRAD, a_7, c_7, d_7, b_7, f_5, mesh->ystart, dump); - check_laplace(++test_num, - "different profiles, with coefficients constant in z, default solver", - *invert_SPT, INVERT_AC_GRAD, INVERT_AC_GRAD | INVERT_DC_GRAD, a_7, c_7, - d_7, b_7, f_5, mesh->ystart, dump); - // Write and close the output file bout::writeDefaultOutputFile(dump); MPI_Barrier(BoutComm::get()); // Wait for all processors to write data } + output.write("Used {}s for setup and {}s for solving\n", + Timer::getTotalTime("petscsetup"), Timer::getTotalTime("petscsolve")); + bout::checkForUnusedOptions(); BoutFinalise(); diff --git a/tests/unit/field/test_field3d.cxx b/tests/unit/field/test_field3d.cxx index d80affc3f7..7672ec7dae 100644 --- a/tests/unit/field/test_field3d.cxx +++ b/tests/unit/field/test_field3d.cxx @@ -2450,5 +2450,25 @@ TEST_F(Field3DTest, ZeroFrom) { EXPECT_TRUE(field2.isAllocated()); EXPECT_TRUE(IsFieldEqual(field2, 0.)); } + +TEST_F(Field3DTest, Field3DParallel) { + Field3DParallel field(1.0); + field = 1.0; + + Field3D field2 = field; + + auto& field3 = field.asField3D(); + + 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 diff --git a/tests/unit/include/bout/test_single_index_ops.cxx b/tests/unit/include/bout/test_single_index_ops.cxx index a809ae3683..deb2c032f1 100644 --- a/tests/unit/include/bout/test_single_index_ops.cxx +++ b/tests/unit/include/bout/test_single_index_ops.cxx @@ -277,6 +277,9 @@ TEST_F(SingleIndexOpsTest, Div_par) { // Need parallel derivatives of input input.calcParallelSlices(); + // and of coordinates + input.getMesh()->getCoordinates()->J.calcParallelSlices(); + input.getMesh()->getCoordinates()->g_22.calcParallelSlices(); // Differentiate whole field Field3D difops = Div_par(input); diff --git a/tests/unit/include/test_derivs.cxx b/tests/unit/include/test_derivs.cxx index 7f6aee07c1..9749279581 100644 --- a/tests/unit/include/test_derivs.cxx +++ b/tests/unit/include/test_derivs.cxx @@ -334,6 +334,7 @@ TEST_P(FirstDerivativesInterfaceTest, Sanity) { result = bout::derivatives::index::DDX(input); break; case DIRECTION::Y: + input.setDirectionY(YDirectionType::Aligned); result = bout::derivatives::index::DDY(input); break; case DIRECTION::Z: diff --git a/tests/unit/sys/test_options.cxx b/tests/unit/sys/test_options.cxx index b74cfdcb9f..ce793052d8 100644 --- a/tests/unit/sys/test_options.cxx +++ b/tests/unit/sys/test_options.cxx @@ -1099,7 +1099,8 @@ value6 = 12 } TEST_F(OptionsTest, InvalidFormat) { - EXPECT_THROW([[maybe_unused]] auto none = fmt::format("{:nope}", Options{}), fmt::format_error); + EXPECT_THROW([[maybe_unused]] auto none = fmt::format("{:nope}", Options{}), + fmt::format_error); } TEST_F(OptionsTest, FormatValue) { diff --git a/tools/pylib/_boutpp_build/boutcpp.pxd.jinja b/tools/pylib/_boutpp_build/boutcpp.pxd.jinja index 8f838b864c..aa39e9843b 100644 --- a/tools/pylib/_boutpp_build/boutcpp.pxd.jinja +++ b/tools/pylib/_boutpp_build/boutcpp.pxd.jinja @@ -16,12 +16,12 @@ cdef extern from "bout/{{ field.header }}.hxx": cppclass {{ field.field_type }}: {{ field.field_type }}(Mesh * mesh); {{ field.field_type }}(const {{ field.field_type }} &) - double & operator()(int, int, int) + double & operator()(int, int, int) except +raise_bout_py_error int getNx() int getNy() int getNz() bool isAllocated() - void setLocation(benum.CELL_LOC) + void setLocation(benum.CELL_LOC) except +raise_bout_py_error benum.CELL_LOC getLocation() Mesh* getMesh() {% for boundaryMethod in field.boundaries %} @@ -30,12 +30,12 @@ cdef extern from "bout/{{ field.header }}.hxx": void {{ boundaryMethod }}(double t) {% endfor %} {% for fun in "sqrt", "exp", "log", "sin", "cos", "abs" %} - {{ field.field_type }} {{ fun }}({{ field.field_type }}) + {{ field.field_type }} {{ fun }}({{ field.field_type }}) except +raise_bout_py_error {% endfor %} double max({{ field.field_type }}) double min({{ field.field_type }}) - {{ field.field_type }} pow({{ field.field_type }},double) - {{ field.field_type }} & ddt({{ field.field_type }}) + {{ field.field_type }} pow({{ field.field_type }}, double) except +raise_bout_py_error + {{ field.field_type }} & ddt({{ field.field_type }}) except +raise_bout_py_error {% endfor %} {% for vec in vecs %} cdef extern from "bout/{{ vec.header }}.hxx": @@ -51,9 +51,9 @@ cdef extern from "bout/mesh.hxx": cppclass Mesh: Mesh() @staticmethod - Mesh * create(Options * option) - void load() - void communicate(FieldGroup&) + Mesh * create(Options * option) except +raise_bout_py_error + void load() except +raise_bout_py_error + void communicate(FieldGroup&) except +raise_bout_py_error int getNXPE() int getNYPE() int getXProcIndex() @@ -62,7 +62,8 @@ cdef extern from "bout/mesh.hxx": int ystart int LocalNx int LocalNy - Coordinates * getCoordinates() + Coordinates * getCoordinates() except +raise_bout_py_error + int get(Field3D, const string) except +raise_bout_py_error cdef extern from "bout/coordinates.hxx": cppclass Coordinates: @@ -78,10 +79,10 @@ cdef extern from "bout/coordinates.hxx": {{ metric_field }} G1, G2, G3 {{ metric_field }} ShiftTorsion {{ metric_field }} IntShiftTorsion - int geometry() - int calcCovariant() - int calcContravariant() - int jacobian() + int geometry() except +raise_bout_py_error + int calcCovariant() except +raise_bout_py_error + int calcContravariant() except +raise_bout_py_error + int jacobian() except +raise_bout_py_error cdef extern from "bout/fieldgroup.hxx": cppclass FieldGroup: @@ -90,10 +91,9 @@ cdef extern from "bout/fieldgroup.hxx": cdef extern from "bout/invert_laplace.hxx": cppclass Laplacian: @staticmethod - unique_ptr[Laplacian] create() - @staticmethod - unique_ptr[Laplacian] create(Options *) - Field3D solve(Field3D,Field3D) + unique_ptr[Laplacian] create(Options*, benum.CELL_LOC, Mesh*, Solver*) except +raise_bout_py_error + Field3D solve(Field3D, Field3D) except +raise_bout_py_error + Field3D forward(Field3D) void setCoefA(Field3D) void setCoefC(Field3D) void setCoefC1(Field3D) @@ -103,13 +103,21 @@ cdef extern from "bout/invert_laplace.hxx": void setCoefEy(Field3D) void setCoefEz(Field3D) +cdef extern from "bout/invert/laplacexz.hxx": + cppclass LaplaceXZ: + LaplaceXZ(Mesh*, Options*, benum.CELL_LOC) + void setCoefs(const Field3D& A, const Field3D& B) except +raise_bout_py_error + Field3D solve(const Field3D& b, const Field3D& x0) except +raise_bout_py_error + @staticmethod + unique_ptr[LaplaceXZ] create(Mesh* m, Options* opt, benum.CELL_LOC loc) + cdef extern from "bout/difops.hxx": - Field3D Div_par(Field3D, benum.CELL_LOC, string) - Field3D Grad_par(Field3D, benum.CELL_LOC, string) - Field3D Laplace(Field3D) - Field3D Vpar_Grad_par(Field3D, Field3D, benum.CELL_LOC, string) - Field3D bracket(Field3D,Field3D, benum.BRACKET_METHOD, benum.CELL_LOC) - Field3D Delp2(Field3D) + Field3D Div_par(Field3D, benum.CELL_LOC, string) except +raise_bout_py_error + Field3D Grad_par(Field3D, benum.CELL_LOC, string) except +raise_bout_py_error + Field3D Laplace(Field3D) except +raise_bout_py_error + Field3D Vpar_Grad_par(Field3D, Field3D, benum.CELL_LOC, string) except +raise_bout_py_error + Field3D bracket(Field3D,Field3D, benum.BRACKET_METHOD, benum.CELL_LOC) except +raise_bout_py_error + Field3D Delp2(Field3D) except +raise_bout_py_error cdef extern from "bout/derivs.hxx": {% for d in "XYZ" %} @@ -131,16 +139,16 @@ cdef extern from "bout/interpolation.hxx": cdef extern from "bout/field_factory.hxx": cppclass FieldFactory: - FieldFactory(Mesh*,Options*) - Field3D create3D(string bla, Options * o, Mesh * m,benum.CELL_LOC loc, double t) + FieldFactory(Mesh*,Options*) except +raise_bout_py_error + Field3D create3D(string bla, Options * o, Mesh * m,benum.CELL_LOC loc, double t) except +raise_bout_py_error cdef extern from "bout/solver.hxx": cppclass Solver: @staticmethod - Solver * create() - void setModel(PhysicsModel *) - void add(Field3D, char * name) - void solve() + Solver * create() except +raise_bout_py_error + void setModel(PhysicsModel *) except +raise_bout_py_error + void add(Field3D, char * name) except +raise_bout_py_error + void solve() except +raise_bout_py_error cdef extern from "bout/physicsmodel.hxx": cppclass PhysicsModel: @@ -166,8 +174,8 @@ cdef extern from "bout/output.hxx": ConditionalOutput output_info cdef extern from "bout/vecops.hxx": - Vector3D Grad(const Field3D& f, benum.CELL_LOC, string) - Vector3D Grad_perp(const Field3D& f, benum.CELL_LOC, string) + Vector3D Grad(const Field3D& f, benum.CELL_LOC, string) except +raise_bout_py_error + Vector3D Grad_perp(const Field3D& f, benum.CELL_LOC, string) except +raise_bout_py_error cdef extern from "bout/vector3d.hxx": - Vector3D cross(Vector3D, Vector3D) + Vector3D cross(Vector3D, Vector3D) except +raise_bout_py_error diff --git a/tools/pylib/_boutpp_build/boutpp.pyx.jinja b/tools/pylib/_boutpp_build/boutpp.pyx.jinja index d6f0601f1c..ff224cd70f 100644 --- a/tools/pylib/_boutpp_build/boutpp.pyx.jinja +++ b/tools/pylib/_boutpp_build/boutpp.pyx.jinja @@ -304,6 +304,7 @@ cdef {{ field.field_type }} {{ field.fdd }}FromPtr(c.{{ field.field_type }}* i): dims_in = self._checkDims(dims, data.shape) cdef np.ndarray[double, mode="c", ndim={{ field.ndims }}] data_ = np.ascontiguousarray(data) c_set_all(self.cobj,&data_[{{ zeros }}]) + return self def get(self): """ @@ -780,13 +781,13 @@ cdef options norm : float The length with which to rescale """ - if self.isNormalised>0: - t=norm - norm=norm/self.isNormalised - self.isNormalised=t - c_mesh_normalise(self.cobj,norm) + if self.isNormalised > 0: + t = norm + norm = norm/self.isNormalised + self.isNormalised = t + c_mesh_normalise(self.cobj, norm) - def communicate(self,*args): + def communicate(self, *args): """ Communicate (MPI) the boundaries of the Field3Ds with neighbours @@ -811,6 +812,20 @@ cdef options self._coords = coordsFromPtr(self.cobj.getCoordinates()) return self._coords + def get(self, name, default=None): + """ + Load a variable from the grid source + + If no default is given, and the variable is not found, an error is raised. + """ + cdef c.string cstr = name.encode() + cdef Field3D defaultfield = default if isinstance(default, Field3D) else Field3D.fromMesh(self) + if deref(self.cobj).get(deref(defaultfield.cobj), cstr): + if default is None: + raise ValueError(f"No default value for {name} given and not set for this mesh") + return default + return defaultfield + cdef Mesh meshFromPtr(c.Mesh * obj): mesh = Mesh() @@ -853,7 +868,7 @@ Compute the Laplacian inversion of objects. Equation solved is: d\\nabla^2_\\perp x + (1/c1)\\nabla_perp c2\\cdot\\nabla_\\perp x + ex\\nabla_x x + ez\\nabla_z x + a x = b """, uniquePtr=True) }} - def __init__(self, section=None): + def __init__(self, section=None, loc="CELL_CENTRE", mesh=None): """ Initialiase a Laplacian solver @@ -863,10 +878,20 @@ Equation solved is: d\\nabla^2_\\perp x + (1/c1)\\nabla_perp c2\\cdot\\nabla_\\p The section from the Option tree to take the options from """ checkInit() + cdef c.Options* copt = NULL if section: - self.cobj = c.Laplacian.create((section).cobj) - else: - self.cobj = c.Laplacian.create(NULL) + if isinstance(section, str): + section = Options.root(section) + copt = (section).cobj + cdef benum.CELL_LOC cloc = benum.resolve_cell_loc(loc) + cdef c.Mesh* cmesh = NULL + if mesh: + cmesh = (mesh).cobj + # Solver is not exposed yet + # cdef c.Solver* csolver = NULL + # if solver: + # csolver = (solver).cobj + self.cobj = c.Laplacian.create(copt, cloc, cmesh, NULL) self.isSelfOwned = True def solve(self, Field3D x, Field3D guess): @@ -888,19 +913,38 @@ Equation solved is: d\\nabla^2_\\perp x + (1/c1)\\nabla_perp c2\\cdot\\nabla_\\p """ return f3dFromObj(deref(self.cobj).solve(x.cobj[0],guess.cobj[0])) - def setCoefs(self, **kwargs): + def forward(self, Field3D x): + """ + Calculate the Laplacian + + Parameters + ---------- + x : Field3D + Field to take the derivative + + + Returns + ------- + Field3D + the inversion of x, where guess is a guess to start with + """ + return f3dFromObj(deref(self.cobj).forward(x.cobj[0])) + + +{% set coeffs="A C C1 C2 D Ex Ez".split() %} + def setCoefs(self, *{% for coeff in coeffs %}, {{coeff}}=None{% endfor %}): """ Set the coefficients for the Laplacian solver. The coefficients A, C, C1, C2, D, Ex and Ez can be passed as keyword arguments """ {% set coeffs="A C C1 C2 D Ex Ez".split() %} {% for coeff in coeffs %} - if "{{ coeff }}" in kwargs: - self.setCoef{{ coeff}}(kwargs["{{ coeff }}"]) + if {{ coeff }} is not None: + self.setCoef{{ coeff}}({{ coeff }}) {% endfor %} {% for coeff in coeffs %} - def setCoef{{ coeff }}(self,Field3D {{ coeff }}): + def setCoef{{ coeff }}(self, Field3D {{ coeff }}): """ Set the "{{ coeff }}" coefficient of the Laplacian solver @@ -913,6 +957,64 @@ Equation solved is: d\\nabla^2_\\perp x + (1/c1)\\nabla_perp c2\\cdot\\nabla_\\p {% endfor %} +{{ class("LaplaceXZ", comment=""" +LaplaceXZ inversion solver + +Compute the Laplacian inversion of objects. + +Equation solved is: \\nabla\\cdot\\left( A \\nabla_\\perp f \\right) + Bf = b +""", uniquePtr=True) }} + + def __init__(self, section=None, loc="CELL_CENTRE", mesh=None): + """ + Initialiase a Laplacian solver + + Parameters + ---------- + section : Options, optional + The section from the Option tree to take the options from + """ + checkInit() + cdef c.Options* copt = NULL + if section: + if isinstance(section, str): + section = Options.root(section) + copt = (section).cobj + cdef benum.CELL_LOC cloc = benum.resolve_cell_loc(loc) + cdef c.Mesh* cmesh = NULL + if mesh: + cmesh = (mesh).cobj + self.cobj = c.LaplaceXZ.create(cmesh, copt, cloc) + self.isSelfOwned = True + + def solve(self, Field3D x, Field3D guess): + """ + Calculate the Laplacian inversion + + Parameters + ---------- + x : Field3D + Field to be inverted + guess : Field3D + initial guess for the inversion + + + Returns + ------- + Field3D + the inversion of x, where guess is a guess to start with + """ + return f3dFromObj(deref(self.cobj).solve(x.cobj[0],guess.cobj[0])) + + def setCoefs(self, *, Field3D A, Field3D B): + """ + Set the coefficients for the Laplacian solver. + The coefficients A and B have both to be passed. + A and B have to be Field3D. + """ + deref(self.cobj).setCoefs(A.cobj[0], B.cobj[0]) + + {{ class("FieldFactory", defaultSO=False) }} cdef void callback(void * parameter, void * method) with gil: @@ -1454,8 +1556,8 @@ def create3D(string, Mesh msh=None,outloc="CELL_DEFAULT",time=0): cdef benum.CELL_LOC outloc_=benum.resolve_cell_loc(outloc) if msh is None: msh=Mesh.getGlobal() - cdef FieldFactory fact=msh.getFactory() - cdef c.string str_=string.encode() + cdef FieldFactory fact = msh.getFactory() + cdef c.string str_ = string.encode() return f3dFromObj( (fact).cobj.create3D(str_,0,0 ,outloc_,time))