diff --git a/.clang-format b/.clang-format index 51cf39683a..9f7c5d763e 100644 --- a/.clang-format +++ b/.clang-format @@ -112,6 +112,6 @@ StatementMacros: - BOUT_OMP - BOUT_OMP_PERF - BOUT_OMP_SAFE -Standard: c++14 +Standard: c++20 TabWidth: 8 UseTab: Never diff --git a/.github/workflows/auto-formatting.yml b/.github/workflows/auto-formatting.yml index 518cfd3898..7274f34038 100644 --- a/.github/workflows/auto-formatting.yml +++ b/.github/workflows/auto-formatting.yml @@ -16,7 +16,7 @@ jobs: fetch-depth: 0 - name: "Set up Python" - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 - name: Install uv uses: astral-sh/setup-uv@v7 diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index e2023fa5c8..53561934e2 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -22,7 +22,7 @@ jobs: fetch-depth: 0 submodules: true - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 - name: Install dependencies run: python -m pip install --upgrade pip && pip install --upgrade build && @@ -62,7 +62,7 @@ jobs: fetch-depth: 0 submodules: true - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 - name: Install dependencies run: python -m pip install --upgrade pip && pip install --upgrade build && @@ -111,7 +111,7 @@ jobs: fetch-depth: 0 submodules: true - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 - name: Install dependencies run: python -m pip install --upgrade pip && pip install --upgrade build && diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 06294e6091..16218f39be 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -158,11 +158,11 @@ jobs: submodules: true - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v7 with: enable-cache: true - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: '3.x' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 84a440847b..31369b738b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,14 +24,14 @@ repos: # C++ formatting - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v22.1.1 + rev: v22.1.8 hooks: - id: clang-format types_or: [c++, c, cuda] # Python linting and formatting - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.6 + rev: v0.16.2 hooks: # Run the linter. - id: ruff-check diff --git a/CHANGELOG.md b/CHANGELOG.md index 41e3e2c47b..a0cbfd5915 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## [v6.0.0](https://github.com/boutproject/BOUT-dev/tree/v6.0.0 + +[Full Changelog](https://github.com/boutproject/BOUT-dev/compare/v5.2.0...) + +### Breaking changes + +- `Coordinates` has been refactored to with a more encapsulated interface. This + allows us to ensure that derived quantities are correctly updated when the + metric components are changed, as well as defer creation of the Christoffel + symbols, and other derivatives of the metric components. This change will + require most physics models to be updated. You can do this with the + `bout-upgrader` from `boutdata`: + ```console + $ bout-upgrader v6 v6_upgrader file/to/update.cxx + ```` + ## [v5.2.0](https://github.com/boutproject/BOUT-dev/tree/v5.2.0 [Full Changelog](https://github.com/boutproject/BOUT-dev/compare/v5.1.1...v5.2.0) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b180cf0e8..0d74e9bf90 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,7 +31,7 @@ endif() # Set the version here, strip any extra tags to use in `project` # We try to use git to get a full description, inspired by setuptools_scm set(_bout_previous_version "5.2.0") -set(_bout_next_version "5.2.1") +set(_bout_next_version "6.0.0") execute_process( COMMAND "git" describe --tags --match=v${_bout_previous_version} COMMAND sed -e s/${_bout_previous_version}-/${_bout_next_version}.dev/ -e @@ -116,10 +116,11 @@ set(BOUT_SOURCES ./include/bout/bout.hxx ./include/bout/bout_enum_class.hxx ./include/bout/bout_types.hxx - ./include/bout/build_config.hxx ./include/bout/boutcomm.hxx ./include/bout/boutexception.hxx + ./include/bout/build_config.hxx ./include/bout/caliper_wrapper.hxx + ./include/bout/christoffel_symbols.hxx ./include/bout/constants.hxx ./include/bout/coordinates.hxx ./include/bout/coordinates_accessor.hxx @@ -128,7 +129,6 @@ set(BOUT_SOURCES ./include/bout/deriv_store.hxx ./include/bout/derivs.hxx ./include/bout/difops.hxx - ./include/bout/expr.hxx ./include/bout/fft.hxx ./include/bout/field.hxx ./include/bout/field2d.hxx @@ -136,18 +136,21 @@ set(BOUT_SOURCES ./include/bout/field_accessor.hxx ./include/bout/field_data.hxx ./include/bout/field_factory.hxx - ./include/bout/fieldops.hxx ./include/bout/fieldgroup.hxx + ./include/bout/fieldops.hxx ./include/bout/fieldperp.hxx ./include/bout/fv_ops.hxx ./include/bout/fv_ops_impl.hxx + ./include/bout/g_values.hxx ./include/bout/generic_factory.hxx + ./include/bout/git_metadata.hxx ./include/bout/globalfield.hxx ./include/bout/globalindexer.hxx ./include/bout/globals.hxx ./include/bout/griddata.hxx ./include/bout/gyro_average.hxx ./include/bout/hypre_interface.hxx + ./include/bout/hyprelib.hxx ./include/bout/index_derivs.hxx ./include/bout/index_derivs_interface.hxx ./include/bout/initialprofiles.hxx @@ -164,6 +167,7 @@ set(BOUT_SOURCES ./include/bout/macro_for_each.hxx ./include/bout/mask.hxx ./include/bout/mesh.hxx + ./include/bout/metric_tensor.hxx ./include/bout/monitor.hxx ./include/bout/mpi_wrapper.hxx ./include/bout/msg_stack.hxx @@ -176,7 +180,6 @@ set(BOUT_SOURCES ./include/bout/output.hxx ./include/bout/output_bout_types.hxx ./include/bout/parallel_boundary_op.hxx - ./include/bout/parallel_boundary_region.hxx ./include/bout/paralleltransform.hxx ./include/bout/petsc_interface.hxx ./include/bout/petsc_operators.hxx @@ -216,8 +219,9 @@ set(BOUT_SOURCES ./include/bout/vector2d.hxx ./include/bout/vector3d.hxx ./include/bout/where.hxx - ./src/bout++.cxx + ./include/bout/yboundary_regions.hxx ./src/bout++-time.hxx + ./src/bout++.cxx ./src/field/field.cxx ./src/field/field2d.cxx ./src/field/field3d.cxx @@ -240,6 +244,8 @@ set(BOUT_SOURCES ./src/invert/laplace/common_transform.hxx ./src/invert/laplace/impls/cyclic/cyclic_laplace.cxx ./src/invert/laplace/impls/cyclic/cyclic_laplace.hxx + ./src/invert/laplace/impls/hypre3d/hypre3d_laplace.cxx + ./src/invert/laplace/impls/hypre3d/hypre3d_laplace.hxx ./src/invert/laplace/impls/iterative_parallel_tri/iterative_parallel_tri.cxx ./src/invert/laplace/impls/iterative_parallel_tri/iterative_parallel_tri.hxx ./src/invert/laplace/impls/multigrid/multigrid_alg.cxx @@ -262,8 +268,6 @@ set(BOUT_SOURCES ./src/invert/laplace/impls/serial_tri/serial_tri.hxx ./src/invert/laplace/impls/spt/spt.cxx ./src/invert/laplace/impls/spt/spt.hxx - ./src/invert/laplace/impls/hypre3d/hypre3d_laplace.cxx - ./src/invert/laplace/impls/hypre3d/hypre3d_laplace.hxx ./src/invert/laplace/invert_laplace.cxx ./src/invert/laplacexy/impls/hypre/laplacexy-hypre.cxx ./src/invert/laplacexy/impls/hypre/laplacexy-hypre.hxx @@ -286,23 +290,26 @@ set(BOUT_SOURCES ./src/mesh/boundary_factory.cxx ./src/mesh/boundary_region.cxx ./src/mesh/boundary_standard.cxx + ./src/mesh/christoffel_symbols.cxx ./src/mesh/coordinates.cxx ./src/mesh/coordinates_accessor.cxx ./src/mesh/data/gridfromfile.cxx ./src/mesh/data/gridfromoptions.cxx ./src/mesh/difops.cxx ./src/mesh/fv_ops.cxx + ./src/mesh/g_values.cxx ./src/mesh/impls/bout/boutmesh.cxx ./src/mesh/impls/bout/boutmesh.hxx ./src/mesh/index_derivs.cxx - ./src/mesh/interpolation_xz.cxx ./src/mesh/interpolation/bilinear_xz.cxx ./src/mesh/interpolation/hermite_spline_xz.cxx ./src/mesh/interpolation/hermite_spline_z.cxx ./src/mesh/interpolation/interpolation_z.cxx ./src/mesh/interpolation/lagrange_4pt_xz.cxx + ./src/mesh/interpolation_xz.cxx ./src/mesh/invert3x3.hxx ./src/mesh/mesh.cxx + ./src/mesh/metric_tensor.cxx ./src/mesh/parallel/fci.cxx ./src/mesh/parallel/fci.hxx ./src/mesh/parallel/fci_comm.cxx @@ -316,6 +323,7 @@ set(BOUT_SOURCES ./src/mesh/petsc_operators.cxx ./src/mesh/surfaceiter.cxx ./src/mesh/tokamak_coordinates.cxx + ./src/mesh/yboundary_regions.cxx ./src/physics/gyro_average.cxx ./src/physics/physicsmodel.cxx ./src/physics/smoothing.cxx @@ -371,19 +379,18 @@ set(BOUT_SOURCES ./src/sys/derivs.cxx ./src/sys/expressionparser.cxx ./src/sys/generator_context.cxx - ./include/bout/hyprelib.hxx ./src/sys/hyprelib.cxx ./src/sys/hypre_interface.cxx ./src/sys/msg_stack.cxx ./src/sys/options.cxx ./src/sys/options/optionparser.hxx + ./src/sys/options/options_adios.cxx + ./src/sys/options/options_adios.hxx ./src/sys/options/options_ini.cxx ./src/sys/options/options_ini.hxx ./src/sys/options/options_io.cxx ./src/sys/options/options_netcdf.cxx ./src/sys/options/options_netcdf.hxx - ./src/sys/options/options_adios.cxx - ./src/sys/options/options_adios.hxx ./src/sys/optionsreader.cxx ./src/sys/output.cxx ./src/sys/output_bout_types.cxx @@ -393,7 +400,6 @@ set(BOUT_SOURCES ./src/sys/timer.cxx ./src/sys/type_name.cxx ./src/sys/utils.cxx - ./include/bout/git_metadata.hxx ${CMAKE_CURRENT_BINARY_DIR}/include/bout/revision.hxx ${CMAKE_CURRENT_BINARY_DIR}/include/bout/version.hxx ) diff --git a/examples/6field-simple/elm_6f.cxx b/examples/6field-simple/elm_6f.cxx index 7208d89ddf..1ffa925580 100644 --- a/examples/6field-simple/elm_6f.cxx +++ b/examples/6field-simple/elm_6f.cxx @@ -6,19 +6,32 @@ * T. Xia *******************************************************************************/ +#include "bout/assert.hxx" #include "bout/bout.hxx" +#include "bout/bout_types.hxx" +#include "bout/build_defines.hxx" #include "bout/constants.hxx" +#include "bout/coordinates.hxx" #include "bout/derivs.hxx" +#include "bout/difops.hxx" +#include "bout/field2d.hxx" +#include "bout/field3d.hxx" +#include "bout/fieldgroup.hxx" #include "bout/initialprofiles.hxx" #include "bout/interpolation_xz.hxx" #include "bout/invert_laplace.hxx" -#include "bout/invert_parderiv.hxx" #include "bout/msg_stack.hxx" +#include "bout/output.hxx" #include "bout/physicsmodel.hxx" +#include "bout/smoothing.hxx" #include "bout/sourcex.hxx" #include "bout/tokamak_coordinates.hxx" +#include "bout/unused.hxx" +#include "bout/vecops.hxx" +#include "bout/where.hxx" #include +#include constexpr BoutReal eV_K = 11605.0; // 1eV = 11605K @@ -356,7 +369,7 @@ class Elm_6f : public PhysicsModel { result.allocate(); for (auto i : result) { result[i] = - (fp[i.yp()] - fm[i.ym()]) / (2. * coord->dy[i] * sqrt(coord->g_22[i])); + (fp[i.yp()] - fm[i.ym()]) / (2. * coord->dy()[i] * sqrt(coord->g_22()[i])); } } else { result = Grad_par(f, loc); @@ -696,7 +709,7 @@ class Elm_6f : public PhysicsModel { if (mesh->IncIntShear) { // BOUT-06 style, using d/dx = d/dpsi + I * d/dz - mesh->getCoordinates()->IntShiftTorsion = I; + mesh->getCoordinates()->setIntShiftTorsion(I); } else { // Dimits style, using local coordinate system if (include_curvature) { @@ -929,7 +942,7 @@ class Elm_6f : public PhysicsModel { output.write("\tlog Lambda: {:e}\n", LnLambda); nu_e = 2.91e-6 * LnLambda * ((N0)*Nbar * density / 1.e6) - * pow(Te0 * Tebar, -1.5); // nu_e in 1/S. + * pow(Field2D{Te0 * Tebar}, -1.5); // nu_e in 1/S. output.write("\telectron collision rate: {:e} -> {:e} [1/s]\n", min(nu_e), max(nu_e)); // nu_e.applyBoundary(); // mesh->communicate(nu_e); @@ -941,7 +954,8 @@ class Elm_6f : public PhysicsModel { // xqx addition, begin // Use Spitzer thermal conductivities nu_i = 4.80e-8 * (Zi * Zi * Zi * Zi / sqrt(AA)) * LnLambda - * ((N0)*Nbar * density / 1.e6) * pow(Ti0 * Tibar, -1.5); // nu_i in 1/S. + * ((N0)*Nbar * density / 1.e6) + * pow(Field2D{Ti0 * Tibar}, -1.5); // nu_i in 1/S. // output.write("\tCoulomb Logarithm: {:e} \n", max(LnLambda)); output.write("\tion collision rate: {:e} -> {:e} [1/s]\n", min(nu_i), max(nu_i)); @@ -1006,8 +1020,9 @@ class Elm_6f : public PhysicsModel { // Use Spitzer resistivity output.write("\n\tSpizter parameters"); // output.write("\tTemperature: {:e} -> {:e} [eV]\n", min(Te), max(Te)); - eta_spitzer = 0.51 * 1.03e-4 * Zi * LnLambda - * pow(Te0 * Tebar, -1.5); // eta in Ohm-m. NOTE: ln(Lambda) = 20 + eta_spitzer = + 0.51 * 1.03e-4 * Zi * LnLambda + * pow(Field2D{Te0 * Tebar}, -1.5); // eta in Ohm-m. NOTE: ln(Lambda) = 20 output.write("\tSpitzer resistivity: {:e} -> {:e} [Ohm m]\n", min(eta_spitzer), max(eta_spitzer)); eta_spitzer /= SI::mu0 * Va * Lbar; @@ -1114,7 +1129,7 @@ class Elm_6f : public PhysicsModel { // Only if not restarting: Check initial perturbation // Set U to zero where P0 < vacuum_pressure - U = where(P0 - vacuum_pressure, U, 0.0); + U = where(Field2D{P0 - vacuum_pressure}, U, 0.0); // Field2D lap_temp = 0.0; Field2D logn0 = laplace_alpha * N0; @@ -1241,24 +1256,25 @@ class Elm_6f : public PhysicsModel { // Update resistivity if (spitzer_resist) { // Use Spitzer formula - eta_spitzer = 0.51 * 1.03e-4 * Zi * LnLambda - * pow(Te_tmp * Tebar, -1.5); // eta in Ohm-m. ln(Lambda) = 20 + eta_spitzer = + 0.51 * 1.03e-4 * Zi * LnLambda + * pow(Field3D{Te_tmp * Tebar}, -1.5); // eta in Ohm-m. ln(Lambda) = 20 eta_spitzer /= SI::mu0 * Va * Lbar; } else { eta = core_resist + (vac_resist - core_resist) * vac_mask; } nu_e = 2.91e-6 * LnLambda * (N_tmp * Nbar * density / 1.e6) - * pow(Te_tmp * Tebar, -1.5); // nu_e in 1/S. + * pow(Field3D{Te_tmp * Tebar}, -1.5); // nu_e in 1/S. if (diffusion_par > 0.0) { // Use Spitzer thermal conductivities nu_i = 4.80e-8 * (Zi * Zi * Zi * Zi / sqrt(AA)) * LnLambda * (N_tmp * Nbar * density / 1.e6) - * pow(Ti_tmp * Tibar, -1.5); // nu_i in 1/S. - vth_i = 9.79e3 * sqrt(Ti_tmp * Tibar / AA); // vth_i in m/S. - vth_e = 4.19e5 * sqrt(Te_tmp * Tebar); // vth_e in m/S. + * pow(Field3D{Ti_tmp * Tibar}, -1.5); // nu_i in 1/S. + vth_i = 9.79e3 * sqrt(Ti_tmp * Tibar / AA); // vth_i in m/S. + vth_e = 4.19e5 * sqrt(Te_tmp * Tebar); // vth_e in m/S. } if (diffusion_par > 0.0) { @@ -1399,11 +1415,11 @@ class Elm_6f : public PhysicsModel { if (hyperviscos > 0.0) { // Calculate coefficient. - hyper_mu_x = hyperviscos * coord->g_11 * SQ(coord->dx) - * abs(coord->g11 * D2DX2(U)) / (abs(U) + 1e-3); + hyper_mu_x = hyperviscos * coord->g_11() * SQ(coord->dx()) + * abs(coord->g11() * D2DX2(U)) / (abs(U) + 1e-3); hyper_mu_x.applyBoundary("dirichlet"); // Set to zero on all boundaries - ddt(U) += hyper_mu_x * coord->g11 * D2DX2(U); + ddt(U) += hyper_mu_x * coord->g11() * D2DX2(U); if (first_run) { // Print out maximum values of viscosity used on this processor diff --git a/examples/IMEX/drift-wave-constraint/test-drift.cxx b/examples/IMEX/drift-wave-constraint/test-drift.cxx index e3bf88acfc..ddeb5ef9d4 100644 --- a/examples/IMEX/drift-wave-constraint/test-drift.cxx +++ b/examples/IMEX/drift-wave-constraint/test-drift.cxx @@ -61,10 +61,10 @@ class DriftWave : public PhysicsModel { // This code results in ddt(Ne) depending on y+2, y-2 // which are not (currently) included in the coloring // The result is that IMEX-BDF2 with coloring doesn't converge - + Ve = ( Grad_par(phi) - Grad_par(Ne) ) / nu; mesh->communicate(Ve); - + ddt(Ne) = -Div_par(Ve); ddt(Vort) = -Div_par(Ve); */ @@ -82,7 +82,7 @@ class DriftWave : public PhysicsModel { // ddt(phi) = Delp2(phi) - Vort; // This version uses central differencing for Delp2 - ddt(phi) = (coord->g11 * D2DX2(phi) + coord->g33 * D2DZ2(phi)) - Vort; + ddt(phi) = (coord->g11() * D2DX2(phi) + coord->g33() * D2DZ2(phi)) - Vort; return 0; } diff --git a/examples/conducting-wall-mode/cwm.cxx b/examples/conducting-wall-mode/cwm.cxx index b302b5dfb3..c3cd40630c 100644 --- a/examples/conducting-wall-mode/cwm.cxx +++ b/examples/conducting-wall-mode/cwm.cxx @@ -5,14 +5,26 @@ * Model version in the code created by M. Umansky and J. Myra. *******************************************************************************/ +#include +#include #include +#include +#include +#include +#include #include #include #include +#include #include +#include #include +#include #include +#include +#include + class CWM : public PhysicsModel { private: // 2D initial profiles @@ -319,7 +331,7 @@ class CWM : public PhysicsModel { result = VDDX(DDZ(p), f); } else { // Use full expression with all terms - result = b0xGrad_dot_Grad(p, f) / coord->Bxy; + result = b0xGrad_dot_Grad(p, f) / coord->Bxy(); } return result; } @@ -328,10 +340,10 @@ class CWM : public PhysicsModel { Field3D result; if (bout_exb) { // Use a subset of terms for comparison to BOUT-06 - result = VDDZ(-DDX(p), f); + result = VDDZ(Field2D{-DDX(p)}, f); } else { // Use full expression with all terms - result = b0xGrad_dot_Grad(p, f) / coord->Bxy; + result = b0xGrad_dot_Grad(p, f) / coord->Bxy(); } return result; } @@ -343,7 +355,7 @@ class CWM : public PhysicsModel { result = VDDX(DDZ(p), f) + VDDZ(-DDX(p), f); } else { // Use full expression with all terms - result = b0xGrad_dot_Grad(p, f) / coord->Bxy; + result = b0xGrad_dot_Grad(p, f) / coord->Bxy(); } return result; } diff --git a/examples/elm-pb-outerloop/elm_pb_outerloop.cxx b/examples/elm-pb-outerloop/elm_pb_outerloop.cxx index dbad416281..54c3faf3e0 100644 --- a/examples/elm-pb-outerloop/elm_pb_outerloop.cxx +++ b/examples/elm-pb-outerloop/elm_pb_outerloop.cxx @@ -819,7 +819,7 @@ class ELMpb : public PhysicsModel { if (mesh->IncIntShear) { // BOUT-06 style, using d/dx = d/dpsi + I * d/dz - mesh->getCoordinates()->IntShiftTorsion = I; + mesh->getCoordinates()->setIntShiftTorsion(I); } else { // Dimits style, using local coordinate system if (include_curvature) { @@ -1749,11 +1749,11 @@ class ELMpb : public PhysicsModel { if (hyperviscos > 0.0) { // Calculate coefficient. - hyper_mu_x = hyperviscos * metric->g_11 * SQ(metric->dx) - * abs(metric->g11 * D2DX2(U)) / (abs(U) + 1e-3); + hyper_mu_x = hyperviscos * metric->g_11() * SQ(metric->dx()) + * abs(metric->g11() * D2DX2(U)) / (abs(U) + 1e-3); hyper_mu_x.applyBoundary("dirichlet"); // Set to zero on all boundaries - ddt(U) += hyper_mu_x * metric->g11 * D2DX2(U); + ddt(U) += hyper_mu_x * metric->g11() * D2DX2(U); if (first_run) { // Print out maximum values of viscosity used on this processor output.write(" Hyper-viscosity values:\n"); @@ -1865,7 +1865,7 @@ class ELMpb : public PhysicsModel { BoutReal pnorm = P0(0, 0); ddt(P) += heating_P * source_expx2(P0, 2. * hp_width, 0.5 * hp_length) * (Tbar / pnorm); // heat source - ddt(P) += (100. * source_tanhx(P0, hp_width, hp_length) + 0.01) * metric->g11 + ddt(P) += (100. * source_tanhx(P0, hp_width, hp_length) + 0.01) * metric->g11() * D2DX2(P) * (Tbar / Lbar / Lbar); // radial diffusion } diff --git a/examples/elm-pb/elm_pb.cxx b/examples/elm-pb/elm_pb.cxx index 62cc970869..e4bd1bbee3 100644 --- a/examples/elm-pb/elm_pb.cxx +++ b/examples/elm-pb/elm_pb.cxx @@ -775,7 +775,7 @@ class ELMpb : public PhysicsModel { if (mesh->IncIntShear) { // BOUT-06 style, using d/dx = d/dpsi + I * d/dz - mesh->getCoordinates()->IntShiftTorsion = I; + mesh->getCoordinates()->setIntShiftTorsion(I); } else { // Dimits style, using local coordinate system if (include_curvature) { @@ -1772,11 +1772,11 @@ class ELMpb : public PhysicsModel { if (hyperviscos > 0.0) { // Calculate coefficient. - hyper_mu_x = hyperviscos * metric->g_11 * SQ(metric->dx) - * abs(metric->g11 * D2DX2(U)) / (abs(U) + 1e-3); + hyper_mu_x = hyperviscos * metric->g_11() * SQ(metric->dx()) + * abs(metric->g11() * D2DX2(U)) / (abs(U) + 1e-3); hyper_mu_x.applyBoundary("dirichlet"); // Set to zero on all boundaries - ddt(U) += hyper_mu_x * metric->g11 * D2DX2(U); + ddt(U) += hyper_mu_x * metric->g11() * D2DX2(U); if (first_run) { // Print out maximum values of viscosity used on this processor output.write(" Hyper-viscosity values:\n"); @@ -1914,7 +1914,7 @@ class ELMpb : public PhysicsModel { BoutReal pnorm = P0(0, 0); ddt(P) += heating_P * source_expx2(P0, 2. * hp_width, 0.5 * hp_length) * (Tbar / pnorm); // heat source - ddt(P) += (100. * source_tanhx(P0, hp_width, hp_length) + 0.01) * metric->g11 + ddt(P) += (100. * source_tanhx(P0, hp_width, hp_length) + 0.01) * metric->g11() * D2DX2(P) * (Tbar / Lbar / Lbar); // radial diffusion } diff --git a/examples/fci-wave/fci-wave.cxx b/examples/fci-wave/fci-wave.cxx index 9f8bb18b8f..0ded45ebfd 100644 --- a/examples/fci-wave/fci-wave.cxx +++ b/examples/fci-wave/fci-wave.cxx @@ -1,6 +1,15 @@ - -#include "bout/parallel_boundary_region.hxx" -#include "bout/physicsmodel.hxx" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include class FCIwave : public PhysicsModel { private: @@ -19,35 +28,26 @@ class FCIwave : public PhysicsModel { Field3D f_B = f / Bxyz; f_B.splitParallelSlices(); - mesh->getCoordinates()->getParallelTransform().integrateParallelSlices(f_B); + const Coordinates* coord = mesh->getCoordinates(); + coord->getParallelTransform().integrateParallelSlices(f_B); // integrateParallelSlices replaces all yup/down points, so the boundary conditions // now need to be applied. If Bxyz has neumann parallel boundary conditions // then the boundary condition is simpler since f = 0 gives f_B=0 boundary condition. - /// Loop over the mesh boundary regions - for (const auto& reg : mesh->getBoundariesPar()) { - Field3D& f_B_next = f_B.ynext(reg->dir); - const Field3D& f_next = f.ynext(reg->dir); - const Field3D& B_next = Bxyz.ynext(reg->dir); - - for (reg->first(); !reg->isDone(); reg->next()) { - f_B_next(reg->ind().x(), reg->ind().y() + reg->dir, reg->ind().z()) = - f_next(reg->ind().x(), reg->ind().y() + reg->dir, reg->ind().z()) - / B_next(reg->ind().x(), reg->ind().y() + reg->dir, reg->ind().z()); - } - } + const auto ybndry = coord->getYBoundary(); + ybndry.iter([&](const bout::boundary::BoundaryIterator auto& point) { + point.next(f_B) = point.next(f) / point.next(Bxyz); + }); Field3D result; result.allocate(); - Coordinates* coord = mesh->getCoordinates(); - for (auto i : result.getRegion(RGN_NOBNDRY)) { result[i] = Bxyz[i] * (f_B.yup()[i.yp()] - f_B.ydown()[i.ym()]) - / (2. * coord->dy[i] * sqrt(coord->g_22[i])); + / (2. * coord->dy()[i] * sqrt(coord->g_22()[i])); - if (!finite(result[i])) { + if (!std::isfinite(result[i])) { output.write("[{:d},{:d},{:d}]: {:e}, {:e} -> {:e}\n", i.x(), i.y(), i.z(), f_B.yup()[i.yp()], f_B.ydown()[i.ym()], result[i]); } @@ -65,7 +65,7 @@ class FCIwave : public PhysicsModel { auto& options = Options::root()["fciwave"]; div_integrate = options["div_integrate"].withDefault(true); log_density = options["log_density"].withDefault(false); - background = options["background"].withDefault(false); + background = options["background"].withDefault(0.0); log_background = log(background); // Neumann boundaries simplifies parallel derivatives @@ -117,38 +117,16 @@ class FCIwave : public PhysicsModel { // between v, nv and momentum flux momflux.splitParallelSlices(); - for (const auto& reg : mesh->getBoundariesPar()) { + const auto ybndry = mesh->getCoordinates()->getYBoundary(); + ybndry.iter([&](const bout::boundary::BoundaryIterator auto& point) { // Using the values of density and velocity on the boundary - const Field3D& n_next = n.ynext(reg->dir); - const Field3D& v_next = v.ynext(reg->dir); + const BoutReal n_b = 0.5 * point.next(n) + point.current(n); + const BoutReal v_b = 0.5 * point.next(v) + point.current(v); // Set the momentum and momentum flux - Field3D& nv_next = nv.ynext(reg->dir); - Field3D& momflux_next = momflux.ynext(reg->dir); - momflux_next.allocate(); - - for (reg->first(); !reg->isDone(); reg->next()) { - // Density at the boundary - // Note: If evolving density, this should interpolate logn - // but neumann boundaries are used here anyway. - BoutReal n_b = - 0.5 - * (n_next(reg->ind().x(), reg->ind().y() + reg->dir, reg->ind().z()) - + n(reg->ind().x(), reg->ind().y(), reg->ind().z())); - // Velocity at the boundary - BoutReal v_b = - 0.5 - * (v_next(reg->ind().x(), reg->ind().y() + reg->dir, reg->ind().z()) - + v(reg->ind().x(), reg->ind().y(), reg->ind().z())); - - nv_next(reg->ind().x(), reg->ind().y() + reg->dir, reg->ind().z()) = - 2. * n_b * v_b - nv(reg->ind().x(), reg->ind().y(), reg->ind().z()); - - momflux_next(reg->ind().x(), reg->ind().y() + reg->dir, reg->ind().z()) = - 2. * n_b * v_b * v_b - - momflux(reg->ind().x(), reg->ind().y(), reg->ind().z()); - } - } + point.next(nv) = 2. * n_b * v_b - point.current(nv); + point.next(momflux) = 2. * n_b * v_b * v_b - point.current(momflux); + }); // Momentum ddt(nv) = -Div_par_integrate(momflux) - Grad_par(n) + Grad2_par2(nv); diff --git a/examples/gyro-gem/gem.cxx b/examples/gyro-gem/gem.cxx index f4347ca2b3..4e5e8538c3 100644 --- a/examples/gyro-gem/gem.cxx +++ b/examples/gyro-gem/gem.cxx @@ -363,7 +363,7 @@ class GEM : public PhysicsModel { if (curv_logB) { Grad_par_logB = Grad_par(logB); } else { - Grad_par_logB = Grad_par(log(coord->Bxy)); + Grad_par_logB = Grad_par(log(coord->Bxy())); } } else { Grad_par_logB = 0.; @@ -1116,7 +1116,7 @@ class GEM : public PhysicsModel { if (curv_logB) { return -bracket(2. * logB, f, BRACKET_ARAKAWA); } - return -bracket(2. * log(coord->Bxy), f, BRACKET_ARAKAWA); + return -bracket(2. * log(coord->Bxy()), f, BRACKET_ARAKAWA); } //////////////////////////////////////////////////////////////////////// @@ -1131,7 +1131,7 @@ class GEM : public PhysicsModel { delp2.applyBoundary("neumann"); mesh->communicate(delp2); - return nu_perp * Delp2(delp2 * SQ(SQ(1. / coord->Bxy))) + return nu_perp * Delp2(delp2 * SQ(SQ(1. / coord->Bxy()))) - nu_par * Grad2_par2(f) // NB: This should be changed for variable B ; } @@ -1147,8 +1147,8 @@ class GEM : public PhysicsModel { } const Field3D Div_parP(const Field3D& f, CELL_LOC loc = CELL_DEFAULT) { - return interp_to(coord->Bxy, loc) - * Grad_parP(f / interp_to(coord->Bxy, f.getLocation()), loc); + return interp_to(coord->Bxy(), loc) + * Grad_parP(f / interp_to(coord->Bxy(), f.getLocation()), loc); } }; diff --git a/examples/laplace-petsc3d/test-laplace3d.cxx b/examples/laplace-petsc3d/test-laplace3d.cxx index 46bfce7859..5e40b42495 100644 --- a/examples/laplace-petsc3d/test-laplace3d.cxx +++ b/examples/laplace-petsc3d/test-laplace3d.cxx @@ -36,13 +36,13 @@ Field3D this_Laplace_perp(const Field3D& f) { // dfdy not divided by dy yet auto dfdy = bout::derivatives::index::DDY(f, CELL_DEFAULT, "DEFAULT", "RGN_NOY"); - return coords->G1 * DDX(f) - + (coords->G2 - DDY(coords->J / coords->g_22) / coords->J) * DDY(f) - + coords->G3 * DDZ(f) + coords->g11 * D2DX2(f) - + (coords->g22 - 1. / coords->g_22) * D2DY2(f) + coords->g33 * D2DZ2(f) + return coords->G1() * DDX(f) + + (coords->G2() - DDY(coords->J() / coords->g_22()) / coords->J()) * DDY(f) + + coords->G3() * DDZ(f) + coords->g11() * D2DX2(f) + + (coords->g22() - 1. / coords->g_22()) * D2DY2(f) + coords->g33() * D2DZ2(f) + 2. - * (coords->g12 * DDX(dfdy) / coords->dy + coords->g13 * D2DXDZ(f) - + coords->g23 * D2DYDZ(f)); + * (coords->g12() * DDX(dfdy) / coords->dy() + coords->g13() * D2DXDZ(f) + + coords->g23() * D2DYDZ(f)); } int main(int argc, char** argv) { @@ -136,7 +136,7 @@ int main(int argc, char** argv) { /////////////////////////////////////////////////////////////////////////////////////// // Calculate error /////////////////////////////////////////////////////////////////////////////////////// - auto& g_22 = mesh->getCoordinates()->g_22; + const auto& g_22 = mesh->getCoordinates()->g_22(); Field3D rhs_check = D * this_Laplace_perp(f) + (Grad(f) * Grad(C2) - DDY(C2) * DDY(f) / g_22) / C1 + A * f; // The usual way to do this would be diff --git a/examples/performance/arithmetic/.gitignore b/examples/performance/arithmetic/.gitignore deleted file mode 100644 index 077be4cbd0..0000000000 --- a/examples/performance/arithmetic/.gitignore +++ /dev/null @@ -1 +0,0 @@ -arithmetic \ No newline at end of file diff --git a/examples/performance/arithmetic/arithmetic.cxx b/examples/performance/arithmetic/arithmetic.cxx deleted file mode 100644 index fc2357978a..0000000000 --- a/examples/performance/arithmetic/arithmetic.cxx +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Timing of arithmetic operations - * - */ - -#include - -#include - -#include - -using SteadyClock = std::chrono::time_point; -using Duration = std::chrono::duration; -using namespace std::chrono; - -#define TIMEIT(elapsed, ...) \ - { \ - SteadyClock start = steady_clock::now(); \ - { __VA_ARGS__; } \ - Duration diff = steady_clock::now() - start; \ - diff *= 1000 * 1000; \ - elapsed.min = diff > elapsed.min ? elapsed.min : diff; \ - elapsed.max = diff < elapsed.max ? elapsed.max : diff; \ - elapsed.count++; \ - elapsed.avg = elapsed.avg * (1 - 1. / elapsed.count) + diff / elapsed.count; \ - } - -struct Durations { - Duration max; - Duration min; - Duration avg; - int count; -}; - -class Arithmetic : public PhysicsModel { -protected: - int init(bool) { - - Field3D a = 1.0; - Field3D b = 2.0; - Field3D c = 3.0; - a.setRegion("RGN_ALL"); - b.setRegion("RGN_NOBNDRY"); - - Field3D result1, result2, result3, result4; - - // Using Field methods (classic operator overloading) - - result1 = 2. * a + b * c; -#define dur_init {Duration::min(), Duration::max(), Duration::zero(), 0} - Durations elapsed1 = dur_init, elapsed2 = dur_init, elapsed3 = dur_init, - elapsed4 = dur_init; - - for (int ik = 0; ik < 1e3; ++ik) { - TIMEIT(elapsed1, result1 = 2. * a + b * c;); - - // Using C loops - result2.allocate(); - BoutReal* rd = &result2(0, 0, 0); - BoutReal* ad = &a(0, 0, 0); - BoutReal* bd = &b(0, 0, 0); - BoutReal* cd = &c(0, 0, 0); - TIMEIT( - elapsed2, - for (int i = 0, iend = (mesh->LocalNx * mesh->LocalNy * mesh->LocalNz) - 1; - i != iend; i++) { - *rd = 2. * (*ad) + (*bd) * (*cd); - rd++; - ad++; - bd++; - cd++; - }); - - // Template expressions - TIMEIT(elapsed3, result3 = eval3D(add(mul(2, a), mul(b, c)));); - - // Range iterator - result4.allocate(); - TIMEIT(elapsed4, for (auto i : result4) result4[i] = 2. * a[i] + b[i] * c[i];); - } - - output.enable(); - output << "TIMING | minimum | mean | maximum\n" - << "----------- | ---------- | ---------- | ----------\n"; - //#define PRINT(str,elapsed) output << str << elapsed.min.count()<< - //elapsed.avg.count()<< elapsed.max.count() << endl; -#define PRINT(str, elapsed) \ - output.write("{:s} | {:7.3f} us | {:7.3f} us | {:7.3f} us\n", str, \ - elapsed.min.count(), elapsed.avg.count(), elapsed.max.count()) - PRINT("Fields: ", elapsed1); - PRINT("C loop: ", elapsed2); - PRINT("Templates: ", elapsed3); - PRINT("Range For: ", elapsed4); - output.disable(); - SOLVE_FOR(n); - return 0; - } - - int rhs(BoutReal) { - ddt(n) = 0; - return 0; - } - Field3D n; -}; - -BOUTMAIN(Arithmetic); diff --git a/examples/performance/arithmetic/data/BOUT.inp b/examples/performance/arithmetic/data/BOUT.inp deleted file mode 100644 index 0deb623c4b..0000000000 --- a/examples/performance/arithmetic/data/BOUT.inp +++ /dev/null @@ -1,5 +0,0 @@ -MZ = 1024 - -[mesh] -nx = 50 -ny = 2 diff --git a/examples/performance/arithmetic/run.sh b/examples/performance/arithmetic/run.sh deleted file mode 100755 index 3a1cc844a6..0000000000 --- a/examples/performance/arithmetic/run.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -make || exit - -./arithmetic -q -q -q diff --git a/examples/performance/arithmetic_3d2d/.gitignore b/examples/performance/arithmetic_3d2d/.gitignore deleted file mode 100644 index 14968af063..0000000000 --- a/examples/performance/arithmetic_3d2d/.gitignore +++ /dev/null @@ -1 +0,0 @@ -arithmetic_3d2d \ No newline at end of file diff --git a/examples/performance/arithmetic_3d2d/arithmetic_3d2d.cxx b/examples/performance/arithmetic_3d2d/arithmetic_3d2d.cxx deleted file mode 100644 index 83167a5b42..0000000000 --- a/examples/performance/arithmetic_3d2d/arithmetic_3d2d.cxx +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Timing of arithmetic operations (Field3D/Field2D mixed) - * - */ - -#include - -#include - -#include -#include -#include - -using SteadyClock = std::chrono::time_point; -using Duration = std::chrono::duration; -using namespace std::chrono; - -#define TIMEIT(NAME, ...) \ - { \ - SteadyClock start = steady_clock::now(); \ - __VA_ARGS__ \ - Duration diff = steady_clock::now() - start; \ - auto elapsed = elapsedMap[NAME]; \ - elapsed.min = std::min(diff, elapsed.min); \ - elapsed.max = std::max(diff, elapsed.max); \ - elapsed.count++; \ - elapsed.avg = elapsed.avg * (1 - 1. / elapsed.count) + diff / elapsed.count; \ - elapsedMap[NAME] = elapsed; \ - } - -struct Durations { - Duration max; - Duration min; - Duration avg; - int count; - Durations() - : max(Duration::min()), min(Duration::max()), avg(Duration::zero()), count(0){}; -}; - -class Arithmetic : public PhysicsModel { -protected: - std::map elapsedMap; - - int init(bool) { - Field3D a = 1.0; - Field3D b = 2.0; - Field2D c = 3.0; - - Field3D result1, result2, result3, result4; - - // Using Field methods (classic operator overloading) - result1 = 2. * a + b * c; - - for (int ik = 0; ik < 1e2; ++ik) { - result1.allocate(); - TIMEIT("Fields", result1 = 2. * a + b * c;); - - // Using C loops - result2.allocate(); - BoutReal* rd = &result2(0, 0, 0); - BoutReal* ad = &a(0, 0, 0); - BoutReal* bd = &b(0, 0, 0); - BoutReal* cd = &c(0, 0, 0); - TIMEIT( - "C loop", - for (int i = 0, iend = (mesh->LocalNx * mesh->LocalNy) - 1; i != iend; i++) { - for (int j = 0, jend = mesh->LocalNz - 1; j != jend; j++) { - *rd = 2. * (*ad) + (*bd) * (*cd); - rd++; - ad++; - bd++; - } - cd++; - }); - - // Template expressions - result3.allocate(); - TIMEIT("Templates", result3 = eval3D(add(mul(2, a), mul(b, c)));); - - // Range iterator - result4.allocate(); - TIMEIT("Range For", for (auto i : result4) result4[i] = 2. * a[i] + b[i] * c[i];); - } - - output.enable(); - constexpr int width = 15; - output << std::setw(width) << "TIMING"; - output << std::setw(width) << "min"; - output << std::setw(width) << "avg"; - output << std::setw(width) << "max"; - output << "\n======"; - for (int i = 0; i < 4 * width; ++i) { - output << "="; - }; - output << "\n"; - - for (const auto& approach : elapsedMap) { - output << std::setw(width) << approach.first; - output << std::setw(width) << approach.second.min.count(); - output << std::setw(width) << approach.second.avg.count(); - output << std::setw(width) << approach.second.max.count(); - output << "\n"; - } - output.disable(); - SOLVE_FOR(n); - return 0; - } - - int rhs(BoutReal) { - ddt(n) = 0; - return 0; - } - Field3D n; -}; - -BOUTMAIN(Arithmetic); diff --git a/examples/performance/arithmetic_3d2d/data/BOUT.inp b/examples/performance/arithmetic_3d2d/data/BOUT.inp deleted file mode 100644 index 0deb623c4b..0000000000 --- a/examples/performance/arithmetic_3d2d/data/BOUT.inp +++ /dev/null @@ -1,5 +0,0 @@ -MZ = 1024 - -[mesh] -nx = 50 -ny = 2 diff --git a/examples/performance/arithmetic_3d2d/run.sh b/examples/performance/arithmetic_3d2d/run.sh deleted file mode 100644 index ee36808c21..0000000000 --- a/examples/performance/arithmetic_3d2d/run.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -make || exit - -./arithmetic_3d2d -q -q -q diff --git a/externalpackages/googletest b/externalpackages/googletest index 8b53336594..91c99b6ffe 160000 --- a/externalpackages/googletest +++ b/externalpackages/googletest @@ -1 +1 @@ -Subproject commit 8b53336594cc52213c6c2c7a0b29194fa896d039 +Subproject commit 91c99b6ffecb6f37ca11c25a0db84007c263c1f2 diff --git a/include/bout/adios_object.hxx b/include/bout/adios_object.hxx index 63a2d4f85f..ee7e82e45a 100755 --- a/include/bout/adios_object.hxx +++ b/include/bout/adios_object.hxx @@ -16,11 +16,23 @@ #if BOUT_HAS_ADIOS2 +#include "bout/assert.hxx" +#include "bout/boutcomm.hxx" #include "bout/boutexception.hxx" +#include "bout/field2d.hxx" +#include "bout/field3d.hxx" +#include "bout/fieldperp.hxx" +#include "bout/globals.hxx" +#include "bout/mesh.hxx" +#include "bout/utils.hxx" #include #include #include +#include +#include +#include +#include namespace bout { @@ -29,21 +41,17 @@ void ADIOSInit(const std::string configFile, MPI_Comm comm); void ADIOSFinalize(); using ADIOSPtr = std::shared_ptr; -using EnginePtr = std::shared_ptr; -using IOPtr = std::shared_ptr; ADIOSPtr GetADIOSPtr(); -IOPtr GetIOPtr(const std::string IOName); class ADIOSStream { public: adios2::IO io; - adios2::Variable vTime; - adios2::Variable vStep; int adiosStep = 0; /** create or return the ADIOSStream based on the target file name */ - static ADIOSStream& ADIOSGetStream(const std::string& fname, adios2::Mode mode); + static ADIOSStream& ADIOSGetStream(const std::string& fname, adios2::Mode mode, + const std::string& engineType = "BP5"); ~ADIOSStream(); @@ -56,6 +64,91 @@ public: return v; } + template + void Get(const std::string& varname, T& value, adios2::Mode mode = adios2::Mode::Sync) { + auto variable = io.InquireVariable(varname); + ASSERT1(variable); + ASSERT1(variable.ShapeID() == adios2::ShapeID::GlobalValue); + engine().Get(variable, &value, mode); + } + + template + void Get(const std::string& varname, Array& value, + adios2::Mode mode = adios2::Mode::Sync) { + GetArrayLike(varname, value, mode); + } + + template + void Get(const std::string& varname, Matrix& value, + adios2::Mode mode = adios2::Mode::Sync) { + GetArrayLike(varname, value, mode); + } + + template + void Get(const std::string& varname, Tensor& value, + adios2::Mode mode = adios2::Mode::Sync) { + GetArrayLike(varname, value, mode); + } + + void Get(const std::string& varname, Field2D& value, + adios2::Mode mode = adios2::Mode::Sync) { + value.allocate(); + GetField(varname, {"x", "y"}, *value.getMesh(), &value(0, 0), mode); + } + + void Get(const std::string& varname, Field3D& value, + adios2::Mode mode = adios2::Mode::Sync) { + value.allocate(); + GetField(varname, {"x", "y", "z"}, *value.getMesh(), &value(0, 0, 0), mode); + } + + void Get(const std::string& varname, FieldPerp& value, + adios2::Mode mode = adios2::Mode::Sync) { + value.allocate(); + GetField(varname, {"x", "z"}, *value.getMesh(), &value(0, 0), mode); + } + + template + void Put(const std::string& varname, T value, adios2::Mode mode = adios2::Mode::Sync) { + if (BoutComm::rank() != 0) { + return; + } + engine().Put(GetValueVariable(varname), value, mode); + } + + template + void Put(const std::string& varname, const Array& value, + adios2::Mode mode = adios2::Mode::Sync) { + PutArrayLike(varname, value, mode); + } + + template + void Put(const std::string& varname, const Matrix& value, + adios2::Mode mode = adios2::Mode::Sync) { + PutArrayLike(varname, value, mode); + } + + template + void Put(const std::string& varname, const Tensor& value, + adios2::Mode mode = adios2::Mode::Sync) { + PutArrayLike(varname, value, mode); + } + + void Put(const std::string& varname, const Field2D& value, + adios2::Mode mode = adios2::Mode::Sync) { + PutField(varname, {"x", "y"}, *value.getMesh(), &value(0, 0), mode); + } + + void Put(const std::string& varname, const Field3D& value, + adios2::Mode mode = adios2::Mode::Sync) { + PutField(varname, {"x", "y", "z"}, *value.getMesh(), &value(0, 0, 0), mode); + } + + void Put(const std::string& varname, const FieldPerp& value, + adios2::Mode mode = adios2::Mode::Sync) { + PutField(varname, {"x", "z"}, *value.getMesh(), &value(0, 0), mode); + } + template adios2::Variable GetArrayVariable(const std::string& varname, const adios2::Dims& shape, @@ -78,7 +171,7 @@ public: if (not engine_) { engine_ = io.Open(fname, file_mode); if (not engine_) { - throw BoutException("Could not open ADIOS file '{:s}' for writing", fname); + throw BoutException("Could not open ADIOS file '{:s}'", fname); } } return engine_; @@ -101,25 +194,184 @@ public: void finish() { if (engine_) { - engine().EndStep(); + if (isInStep) { + engine().EndStep(); + isInStep = false; + } engine().Close(); + engine_ = adios2::Engine(); } } private: - ADIOSStream(const std::string& fname, adios2::Mode mode) - : fname(fname), file_mode(mode) { - - ADIOSPtr adiosp = GetADIOSPtr(); - std::string ioname = "write_" + fname; - try { - io = adiosp->AtIO(ioname); - } catch (const std::invalid_argument& e) { - io = adiosp->DeclareIO(ioname); - io.SetEngine("BP5"); + ADIOSStream(const std::string& fname, adios2::Mode mode, const std::string& engineType); + + struct FieldSelection { + adios2::Dims shape; + adios2::Dims start; + adios2::Dims count; + adios2::Dims mem_start; + adios2::Dims mem_count; + + auto selection() const { return adios2::Box{start, count}; } + auto memorySelection() const { + return adios2::Box{mem_start, mem_count}; } }; + void GetField(const std::string& varname, const std::vector& dim_names, + const Mesh& mesh, BoutReal* data, adios2::Mode mode) { + auto variable = io.InquireVariable(varname); + ASSERT1(variable); + ASSERT1(variable.ShapeID() == adios2::ShapeID::GlobalArray); + + auto selection = makeFieldSelection(dim_names, mesh); + variable.SetSelection(selection.selection()); + variable.SetMemorySelection(selection.memorySelection()); + engine().Get(variable, data, mode); + } + + void PutField(const std::string& varname, const std::vector& dim_names, + const Mesh& mesh, const BoutReal* data, adios2::Mode mode) { + auto selection = makeFieldSelection(dim_names, mesh); + auto variable = + GetArrayVariable(varname, selection.shape, dim_names, BoutComm::rank()); + variable.SetSelection(selection.selection()); + variable.SetMemorySelection(selection.memorySelection()); + engine().Put(variable, data, mode); + } + + FieldSelection makeFieldSelection(const std::vector& dim_names, + const Mesh& mesh) const { + ASSERT1(!dim_names.empty()); + ASSERT1(dim_names.size() <= 3); + ASSERT1(dim_names[0] == "x"); + + FieldSelection selection; + selection.shape.push_back(static_cast(mesh.GlobalNx)); + selection.start.push_back(static_cast(mesh.MapGlobalX)); + selection.count.push_back(static_cast(mesh.MapCountX)); + selection.mem_start.push_back(static_cast(mesh.MapLocalX)); + selection.mem_count.push_back(static_cast(mesh.LocalNx)); + + if (dim_names.size() > 1) { + if (dim_names[1] == "y") { + selection.shape.push_back(static_cast(mesh.GlobalNy)); + selection.start.push_back(static_cast(mesh.MapGlobalY)); + selection.count.push_back(static_cast(mesh.MapCountY)); + selection.mem_start.push_back(static_cast(mesh.MapLocalY)); + selection.mem_count.push_back(static_cast(mesh.LocalNy)); + } else if (dim_names[1] == "z") { + selection.shape.push_back(static_cast(mesh.GlobalNz)); + selection.start.push_back(static_cast(mesh.MapGlobalZ)); + selection.count.push_back(static_cast(mesh.MapCountZ)); + selection.mem_start.push_back(static_cast(mesh.MapLocalZ)); + selection.mem_count.push_back(static_cast(mesh.LocalNz)); + } else { + ASSERT1(false); + } + } + + if (dim_names.size() > 2) { + ASSERT1(dim_names[1] == "y"); + ASSERT1(dim_names[2] == "z"); + selection.shape.push_back(static_cast(mesh.GlobalNz)); + selection.start.push_back(static_cast(mesh.MapGlobalZ)); + selection.count.push_back(static_cast(mesh.MapCountZ)); + selection.mem_start.push_back(static_cast(mesh.MapLocalZ)); + selection.mem_count.push_back(static_cast(mesh.LocalNz)); + } + + return selection; + } + + template + void GetArrayLike(const std::string& varname, Container& value, adios2::Mode mode) { + using T = typename Container::data_type; + auto variable = io.InquireVariable(varname); + ASSERT1(variable); + ASSERT1(variable.ShapeID() == adios2::ShapeID::GlobalArray); + + const auto shape = variable.Shape(); + auto dims_attr = io.InquireAttribute(varname + "/__xarray_dimensions__"); + auto read_shape = shape; + + if (dims_attr) { + const auto dim_names = dims_attr.Data(); + if (!dim_names.empty() && dim_names[0] == "rank") { + ASSERT1(!shape.empty()); + ASSERT1(static_cast(BoutComm::rank()) < shape[0]); + + adios2::Dims start{static_cast(BoutComm::rank())}; + adios2::Dims count{1}; + for (std::size_t i = 1; i < shape.size(); i++) { + start.push_back(0); + count.push_back(shape[i]); + } + + variable.SetSelection(adios2::Box{start, count}); + variable.SetMemorySelection(adios2::Box{start, count}); + read_shape = adios2::Dims(shape.begin() + 1, shape.end()); + } else if (!dim_names.empty() && dim_names[0] == "x") { + ASSERT1(globals::mesh); + auto selection = makeFieldSelection(dim_names, *globals::mesh); + variable.SetSelection(selection.selection()); + variable.SetMemorySelection(selection.memorySelection()); + read_shape = selection.mem_count; + } + } + + constexpr auto ndims = std::tuple_size_v; + ASSERT1(read_shape.size() == ndims); + + resizeForShape(value, read_shape, std::make_index_sequence{}); + engine().Get(variable, value.begin(), mode); + } + + template + void PutArrayLike(const std::string& varname, const Container& value, + adios2::Mode mode) { + using T = typename Container::data_type; + auto var = GetArrayVariable(varname, makeShape(BoutComm::size(), value), + makeDimNames(value), BoutComm::rank()); + var.SetSelection(adios2::Box{makeStart(value), makeShape(1, value)}); + engine().Put(var, value.begin(), mode); + } + + template + adios2::Dims makeShape(std::size_t first, const Container& value) const { + return std::apply( + [first](auto... sizes) { + return adios2::Dims{first, static_cast(sizes)...}; + }, + value.shape()); + } + + template + adios2::Dims makeStart(const Container& value) const { + constexpr auto ndims = std::tuple_size_v; + adios2::Dims start(ndims + 1, 0); + start[0] = static_cast(BoutComm::rank()); + return start; + } + + template + std::vector makeDimNames(const Container& value) const { + constexpr auto ndims = std::tuple_size_v; + std::vector dim_names{"rank"}; + dim_names.reserve(ndims + 1); + for (std::size_t i = 0; i < ndims; i++) { + dim_names.push_back("dim_" + std::to_string(i)); + } + return dim_names; + } + + template + void resizeForShape(Container& value, const adios2::Dims& shape, + std::index_sequence /*indices*/) { + value.reallocate(static_cast(shape[I])...); + } + std::string fname; adios2::Mode file_mode; adios2::Engine engine_; @@ -128,10 +380,6 @@ private: bool isInStep = false; }; -/** Set user parameters for an IO group */ -void ADIOSSetParameters(const std::string& input, char delimKeyValue, char delimItem, - adios2::IO& io); - } // namespace bout #endif //BOUT_HAS_ADIOS2 diff --git a/include/bout/array.hxx b/include/bout/array.hxx index 0a0034b8fa..9e1437eb9c 100644 --- a/include/bout/array.hxx +++ b/include/bout/array.hxx @@ -37,6 +37,7 @@ #include #endif +#include "bout/build_config.hxx" #include "bout/build_defines.hxx" #if BOUT_HAS_UMPIRE @@ -98,7 +99,7 @@ struct ArrayData { } iterator begin() const { return data; } iterator end() const { return data + len; } - int size() const { return len; } + BOUT_FORCEINLINE int size() const { return len; } /// Copy assignment /// Copy the underlying data from one array to the other diff --git a/include/bout/assert.hxx b/include/bout/assert.hxx index 63bed2407b..9de22f958b 100644 --- a/include/bout/assert.hxx +++ b/include/bout/assert.hxx @@ -19,6 +19,8 @@ #include "bout/boutexception.hxx" +#include // IWYU pragma: export + #ifndef CHECK #define CHECKLEVEL 0 #else @@ -26,41 +28,45 @@ #endif #if CHECKLEVEL >= 0 -#define ASSERT0(condition) \ - if (!(condition)) { \ - throw BoutException("Assertion failed in {:s}, line {:d}: {:s}", __FILE__, __LINE__, \ - #condition); \ +#define ASSERT0(condition) \ + if (!(condition)) { \ + throw BoutException("Assertion failed in {:s}, line {:d}: {:s}", \ + std::string_view(__FILE__), __LINE__, \ + std::string_view(#condition)); \ } #else // CHECKLEVEL >= 0 #define ASSERT0(condition) #endif #if CHECKLEVEL >= 1 -#define ASSERT1(condition) \ - if (!(condition)) { \ - throw BoutException("Assertion failed in {:s}, line {:d}: {:s}", __FILE__, __LINE__, \ - #condition); \ - abort(); \ +#define ASSERT1(condition) \ + if (!(condition)) { \ + throw BoutException("Assertion failed in {:s}, line {:d}: {:s}", \ + std::string_view(__FILE__), __LINE__, \ + std::string_view(#condition)); \ + abort(); \ } #else // CHECKLEVEL >= 1 #define ASSERT1(condition) #endif #if CHECKLEVEL >= 2 -#define ASSERT2(condition) \ - if (!(condition)) { \ - throw BoutException("Assertion failed in {:s}, line {:d}: {:s}", __FILE__, __LINE__, \ - #condition); \ +#define ASSERT2(condition) \ + if (!(condition)) { \ + throw BoutException("Assertion failed in {:s}, line {:d}: {:s}", \ + std::string_view(__FILE__), __LINE__, \ + std::string_view(#condition)); \ } #else // CHECKLEVEL >= 2 #define ASSERT2(condition) #endif #if CHECKLEVEL >= 3 -#define ASSERT3(condition) \ - if (!(condition)) { \ - throw BoutException("Assertion failed in {:s}, line {:d}: {:s}", __FILE__, __LINE__, \ - #condition); \ +#define ASSERT3(condition) \ + if (!(condition)) { \ + throw BoutException("Assertion failed in {:s}, line {:d}: {:s}", \ + std::string_view(__FILE__), __LINE__, \ + std::string_view(#condition)); \ } #else // CHECKLEVEL >= 3 #define ASSERT3(condition) diff --git a/include/bout/boundary_iterator.hxx b/include/bout/boundary_iterator.hxx index b2d2b9fb99..8e5f37182e 100644 --- a/include/bout/boundary_iterator.hxx +++ b/include/bout/boundary_iterator.hxx @@ -1,16 +1,11 @@ #pragma once #include "bout/assert.hxx" -#include "bout/bout_types.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: diff --git a/include/bout/boundary_op.hxx b/include/bout/boundary_op.hxx index e4d2c459bf..dedc56d0e8 100644 --- a/include/bout/boundary_op.hxx +++ b/include/bout/boundary_op.hxx @@ -86,6 +86,8 @@ public: BoundaryModifier() = default; BoundaryModifier(BoundaryOp* operation) : BoundaryOp(operation->bndry), op(operation) {} virtual BoundaryOp* cloneMod(BoundaryOp* op, const std::list& args) = 0; + + using BoundaryOp::clone; BoundaryOp* clone(BoundaryRegion* UNUSED(region), const std::list& UNUSED(args)) override { throw BoutException("This must not be used!"); diff --git a/include/bout/boundary_region.hxx b/include/bout/boundary_region.hxx index 739d06f11f..106d52f21c 100644 --- a/include/bout/boundary_region.hxx +++ b/include/bout/boundary_region.hxx @@ -1,13 +1,12 @@ - -class BoundaryRegion; - #ifndef BOUT_BNDRY_REGION_H #define BOUT_BNDRY_REGION_H #include +#include #include #include +class BoundaryRegion; class Mesh; namespace bout { namespace globals { @@ -42,16 +41,21 @@ constexpr BndryLoc BNDRY_INVALID = BndryLoc::invalid; /// Physical type of y boundary enum class YBndryType : std::int8_t { sheath, not_sheath, all }; +/// Base class for boundary regions class BoundaryRegionBase { public: BoundaryRegionBase() = delete; + BoundaryRegionBase(const BoundaryRegionBase&) = delete; + BoundaryRegionBase(BoundaryRegionBase&&) = delete; + BoundaryRegionBase& operator=(const BoundaryRegionBase&) = delete; + BoundaryRegionBase& operator=(BoundaryRegionBase&&) = delete; BoundaryRegionBase(std::string name, Mesh* passmesh = nullptr) : localmesh(passmesh ? passmesh : bout::globals::mesh), label(std::move(name)) {} BoundaryRegionBase(std::string name, BndryLoc loc, Mesh* passmesh = nullptr) : localmesh(passmesh ? passmesh : bout::globals::mesh), label(std::move(name)), location(loc) {} - virtual ~BoundaryRegionBase(); + virtual ~BoundaryRegionBase() = default; virtual BoundaryRegion* getLegacyPointer(); Mesh* localmesh; ///< Mesh does this boundary region belongs to @@ -70,7 +74,7 @@ public: virtual bool isDone() = 0; ///< Returns true if outside domain. Can use this with nested nextX, nextY - BoundaryRegion* legacy{nullptr}; + std::unique_ptr legacy{nullptr}; }; /// Describes a region of the boundary, and a means of iterating over it diff --git a/include/bout/boundary_region_iter.hxx b/include/bout/boundary_region_iter.hxx index 6b6cd5b6ff..d28f9d21d4 100644 --- a/include/bout/boundary_region_iter.hxx +++ b/include/bout/boundary_region_iter.hxx @@ -1,556 +1,440 @@ #pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include #include +#include #include -#include -#include #include #include #include #include -#include "bout/assert.hxx" -#include "bout/boundary_common.hxx" -#include "bout/bout_types.hxx" -#include "bout/field_data.hxx" -#include "bout/utils.hxx" -#include -#include -#include -#include -#include - namespace bout { namespace boundary { -/// Physical type of y boundary -enum class BndryType : std::int8_t { - sheath, - not_sheath_par, - core, - sol, - sol_perp, - sol_par, - all, - num +/// Helper concept for `BoundaryRegionIterBase::at` function accessor overloads +/// +/// This is a callable that takes two arguments: +/// - `int yoffset`, the parallel slice offset +/// - `Ind3D ind`, the index of the boundary point +/// +/// and returns a `BoutReal` +template +concept function_accessor = + std::regular_invocable + and std::is_same_v, BoutReal>; + +/// Interface for boundary iterators +/// +/// The function passed into `YBoundary::iter`, for example, operates over +/// elements of types derived from `BoundaryRegionIterBase`, but because this +/// uses CRTP they can be a bit difficult to name. Instead use `BoundaryIterator +/// auto` as the parameter type. +template +concept BoundaryIterator = requires(Iter point, Field3D f) { + point.at(f, int{}); + point.next(f); + point.current(f); + point.prev(f); + + point.ind(); + point.length(CELL_LOC{}); + point.valid(); + point.boundary_width(); + point.is_lower(); + point.offset(); + + point.smallValue(); }; -template +/// Common base class for boundary region iterators +/// +/// This uses CRTP: boundary region iterators should inherit from this, +/// templated on themselves, and they must implement all methods in +/// `boundary_iterator`, (that is, those methods that call `impl().` in this +/// class) +template class BoundaryRegionIterBase { BoundaryRegionIterBase() = default; - /// get the index at the last point in domain + + /// Get a reference to the derived/implementation class + const Impl& impl() const { return *static_cast(this); } + public: - Ind3D ind() const { return static_cast(this)->_ind(); } + /// Get the index at the last point in domain + Ind3D ind() const { return impl()._ind(); } /// get the length from the point in the domain to the boundary in index /// space. It is in the range [0, 1] - BoutReal length(CELL_LOC loc) const { - return static_cast(this)->_length(loc); - } + BoutReal length(CELL_LOC loc) const { return impl()._length(loc); } /// Lower bound of how many points are between the first point in the domain /// and the boundary in the other direction. - signed char valid() const { return static_cast(this)->_valid(); } + signed char valid() const { return impl()._valid(); } /// Get the width of the boundary at the current point - int boundary_width() const { return static_cast(this)->_boundary_width(); } + int boundary_width() const { return impl()._boundary_width(); } /// Is this the lower boundary? - bool is_lower() const { return static_cast(this)->_is_lower(); } - - /* - * FIELD3D ACCESSORS - */ - - /// get the value at a given offset `off` of a field `f`. - /// off = -1 is the second point in the boundary - /// off = 0 is the first point in the boundary - /// off = 1 is the last point in the domain - /// off = 2 is the second to last point in the domain - template - BoutReal& getAt(Field3D& f, int off) const { - return static_cast(this)->template _getAt(f, off); - } - /// get the value at a given offset `off` of a field `f`. - template - BoutReal& getAt(const Field3D& f, int off) const { - return static_cast(this)->template _getAt(f, off); - } - - /// Get the first point in the boundary - const BoutReal& next(const Field3D& f) const { - return static_cast(this)->_getAt(f, 0); - } - /// Get the first point in the boundary - BoutReal& next(Field3D& f) const { - return static_cast(this)->_getAt(f, 0); - } - /// Get the last point in the domain - const BoutReal& current(const Field3D& f) const { - return static_cast(this)->_getAt(f, 1); - } - /// Get the last point in the domain - BoutReal& current(Field3D& f) const { - return static_cast(this)->_getAt(f, 1); - } - /// Get the second to last point in the domain - this may not be valid and thus throw - const BoutReal& prev(const Field3D& f) const { - return static_cast(this)->_getAt(f, 2); - } + bool is_lower() const { return impl()._is_lower(); } /// Get the offset from the last point in the domain /// For FA this is always ±1, for FCI this can be up to ±MYG, excluding 0 - int offset() const { return static_cast(this)->_offset(); } - /* - * FIELD2D ACCESSORS - */ - - /// get the value at a given offset `off` of a field `f`. - /// off = -1 is the second point in the boundary - /// off = 0 is the first point in the boundary - /// off = 1 is the last point in the domain - /// off = 2 is the second to last point in the domain - template - BoutReal& getAt(Field2D& f, int off) const { - return static_cast(this)->template _getAt(f, off); - } - /// get the value at a given offset `off` of a field `f`. - template - BoutReal& getAt(const Field2D& f, int off) const { - return static_cast(this)->template _getAt(f, off); + int offset() const { return impl()._offset(); } + + /// Get the value at a given \p offset of a field \p f. + /// + /// `offset = -1` is the second point in the boundary + /// `offset = 0` is the first point in the boundary + /// `offset = 1` is the last point in the domain + /// `offset = 2` is the second to last point in the domain + /// + /// |---|---|---|--> interior points + /// -1 0 ^ 1 2 + /// | + /// boundary + template + requires utils::is_Field_v + const BoutReal& at(const T& f, int offset) const { + return impl().template _at(f, offset); + } + + template + requires utils::is_Field_v + BoutReal& at(T& f, int offset) const { + return impl().template _at(f, offset); } /// Get the first point in the boundary - const BoutReal& next(const Field2D& f) const { - return static_cast(this)->_getAt(f, 0); + template + requires utils::is_Field_v + const BoutReal& next(const T& f) const { + return at(f, 0); } /// Get the first point in the boundary - BoutReal& next(Field2D& f) const { - return static_cast(this)->_getAt(f, 0); + template + requires utils::is_Field_v + BoutReal& next(T& f) const { + return at(f, 0); } /// Get the last point in the domain - const BoutReal& current(const Field2D& f) const { - return static_cast(this)->_getAt(f, 1); + template + requires utils::is_Field_v + const BoutReal& current(const T& f) const { + return at(f, 1); } /// Get the last point in the domain - BoutReal& current(Field2D& f) const { - return static_cast(this)->_getAt(f, 1); + template + requires utils::is_Field_v + BoutReal& current(T& f) const { + return at(f, 1); } /// Get the second to last point in the domain - this may not be valid and thus throw - const BoutReal& prev(const Field2D& f) const { - return static_cast(this)->_getAt(f, 2); + template + requires utils::is_Field_v + const BoutReal& prev(const T& f) const { + return at(f, 2); } /* * FUNCTIONS ACCESSORS */ - /// get the value at a given offset `off` of a field `f`. - /// off = -1 is the second point in the boundary - /// off = 0 is the first point in the boundary - /// off = 1 is the last point in the domain - /// off = 2 is the second to last point in the domain + /// Apply the function \p func at a given \p offset + /// + /// |---|---|---|--> interior points + /// -1 0 ^ 1 2 + /// | + /// boundary template - BoutReal getAt(const std::function& func, - int off) const { - return static_cast(this)->template _getAt(func, off); + BoutReal at(const function_accessor auto& func, int offset) const { + return impl().template _at(func, offset); } /// Get the first point in the boundary - BoutReal next(const std::function& func) const { - return static_cast(this)->_getAt(func, 0); - } + BoutReal next(const function_accessor auto& func) const { return at(func, 0); } /// Get the last point in the domain - BoutReal current(const std::function& func) const { - return static_cast(this)->_getAt(func, 1); - } + BoutReal current(const function_accessor auto& func) const { return at(func, 1); } /// Get the second to last point in the domain - this may not be valid and thus throw - BoutReal prev(const std::function& func) const { - return static_cast(this)->_getAt(func, 2); - } - - /* - * INTERPOLATION and EXTRAPOLATION - */ - - // extrapolate a given field to the boundary - BoutReal extrapolate_boundary_o1(const Field3D& f) const { return current(f); } - // extrapolate a given field to the boundary - BoutReal extrapolate_boundary_o2(const Field3D& f) const { - ASSERT3(valid() >= 0); - if (valid() < 1) { - return extrapolate_boundary_o1(f); - } - return current(f) * (1 + length(f.getLocation())) - prev(f) * length(f.getLocation()); - } - /// Extrapolate a given function to the boundary - BoutReal - extrapolate_bounday_o1(const std::function& func, - [[maybe_unused]] CELL_LOC loc = CELL_CENTRE) const { - return current(func); - } - /// Extrapolate a given function to the boundary - BoutReal - extrapolate_boundary_o2(const std::function& func, - CELL_LOC loc = CELL_CENTRE) const { - ASSERT3(valid() >= 0); - if (valid() < 1) { - return extrapolate_boundary_o1(func); - } - return current(func) * (1 + length(loc)) - prev(func) * length(loc); - } - - /// Interpolate a field to the boundary, using the boundary values - BoutReal interpolate_boundary_o2(const Field3D& f) const { - return current(f) * (1 - length(f.getLocation())) + next(f) * length(f.getLocation()); - } - /// Interpolate a field to the boundary, using the boundary values - BoutReal - interpolate_boundary_o2(const std::function& func, - CELL_LOC loc = CELL_CENTRE) const { - return current(func) * (1 - length(loc)) + next(func) * length(loc); - } - /// Extrapolate to the first boundary value freely - BoutReal extrapolate_next_o1(const Field3D& f) const { return current(f); } - /// Extrapolate to the first boundary value freely - BoutReal extrapolate_next_o2(const Field3D& f) const { - ASSERT3(valid() >= 0); - if (valid() < 1) { - return extrapolate_next_o1(f); - } - return current(f) * 2 - prev(f); - } - - /// Extrapolate to the first boundary value freely - BoutReal - extrapolate_next_o1(const std::function& func) const { - return current(func); - } - /// Extrapolate to the first boundary value freely - BoutReal - extrapolate_next_o2(const std::function& func) const { - ASSERT3(valid() >= 0); - if (valid() < 1) { - return extrapolate_next_o1(func); - } - return current(func) * 2 - prev(func); - } - - /// extrapolate the gradient into the boundary - BoutReal extrapolate_grad_o1([[maybe_unused]] const Field3D& f) const { return 0; } - /// extrapolate the gradient into the boundary - BoutReal extrapolate_grad_o2(const Field3D& f) const { - ASSERT3(valid() >= 0); - if (valid() < 1) { - return extrapolate_grad_o1(f); - } - return current(f) - next(f); - } + BoutReal prev(const function_accessor auto& func) const { return at(func, 2); } - BoutReal extrapolate_boundary_free(const Field3D& f, - BoundaryFreeExtrapolation mode) const { - BoutReal fac = BoutNaN; - if (valid() > 0) { - fac = limitFreeScale(prev(f), current(f), mode); - } else { - fac = mode == BoundaryFreeExtrapolation::linear ? 0 : 1; - } - auto val = current(f); - BoutReal next = mode == BoundaryFreeExtrapolation::linear ? val + fac : val * fac; - return val * length(f.getLocation()) + next * (1 - length(f.getLocation())); + void setSmallValue(BoutReal val) { + ASSERT2(val > 0); + ASSERT2(val < 0.5); + small_value = val; } - /* - * APPLY BOUNDARY CONDITIONS - */ + BoutReal smallValue() const { return small_value; } - /// Apply a dirichlet boundary condition - void dirichlet_o1(Field3D& f, BoutReal value) const { - for (int i = 0; i < boundary_width(); ++i) { - getAt(f, -i) = value; - } - } +private: + BoutReal small_value = 1e-4; + friend Impl; +}; - /// Apply a dirichlet boundary condition - void dirichlet_o2(Field3D& f, BoutReal value) const { - if (length(f.getLocation()) < small_value) { - return dirichlet_o1(f, value); - } - for (int i = 0; i < boundary_width(); ++i) { - getAt(f, -i) = parallel_stencil::dirichlet_o2( - i + 1, current(f), i + 1 - length(f.getLocation()), value); - } - } +/// An FCI-aware boundary region +/// +/// This can't use the legacy iteration methods (`first()`, `next()`, and so on) +class BoundaryRegionFCI : public BoundaryRegionBase { + struct RealPoint { + BoutReal s_x; + BoutReal s_y; + BoutReal s_z; + }; - /// Apply a dirichlet boundary condition - void dirichlet_o3(Field3D& f, BoutReal value) const { - ASSERT3(valid() >= 0); - if (valid() < 1) { - return dirichlet_o2(f, value); - } - if (length(f.getLocation()) < small_value) { - for (int i = 0; i < boundary_width(); ++i) { - getAt(f, -i) = parallel_stencil::dirichlet_o2( - i + 2, prev(f), i + 1 - length(f.getLocation()), value); - } - } else { - for (int i = 0; i < boundary_width(); ++i) { - getAt(f, -i) = parallel_stencil::dirichlet_o3( - i + 2, prev(f), i + 1, current(f), i + 1 - length(f.getLocation()), value); +public: + /// A single point in a `BoundaryRegionFCI` + class Point : public BoundaryRegionIterBase { + private: + /// Index of the first boundary point + Ind3D index; + Mesh* localmesh_m; + /// Intersection with boundary in index space + RealPoint intersection; + /// Distance to intersection + BoutReal length_m; + /// Direction to the boundary. Positive values are in the sense of the + /// increasing coordinate + int dir_m; + /// How many points we can go in the opposite direction + signed char valid_m; + /// How many points we are away from the boundary + signed char offset_m; + unsigned char abs_offset_m; + + public: + Point() = delete; + Point(Ind3D index, RealPoint intersection, BoutReal length, signed char valid, + signed char offset, Mesh* mesh, int dir) + : index(index), localmesh_m(mesh), intersection(intersection), length_m(length), + dir_m(dir), valid_m(valid), offset_m(offset), + abs_offset_m(static_cast(std::abs(offset))) {} + + void setValid(char valid) { valid_m = valid; }; + + BoutReal s_x() const { return intersection.s_x; }; + BoutReal s_y() const { return intersection.s_y; }; + BoutReal s_z() const { return intersection.s_z; }; + + Mesh* localmesh() const { return localmesh_m; }; + int dir() const { return dir_m; } + bool _is_lower() const { return dir_m < 0; } + + template + requires utils::is_Field_v + const BoutReal& _at(const T& f, int off) const { + ASSERT3(f.hasParallelSlices()); + if constexpr (check) { + ASSERT3(_valid() > -off - 2); } + auto _off = _offset() - (off * dir_m); + return f.ynext(_off)[_ind().yp(_off)]; } - } - /// Ensure the value in the boundary is at least `value` - void limit_at_least(Field3D& f, BoutReal value) const { - for (int i = 0; i < boundary_width(); ++i) { - if (getAt(f, -i) < value) { - getAt(f, -i) = value; + template + requires utils::is_Field_v + BoutReal& _at(T& f, int off) const { + ASSERT3(f.hasParallelSlices()); + if constexpr (check) { + ASSERT3(_valid() > -off - 2); } + auto _off = _offset() - (off * dir_m); + return f.ynext(_off)[_ind().yp(_off)]; } - } - - /// Apply neumann boundary condition, where `value` is the gradient in index space - - // neumann_o1 would give second order convergence, given 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 { - for (int i = 0; i < boundary_width(); ++i) { - getAt(f, -i) = current(f) + value * (i + 1); - } - } - /// Apply neumann boundary condition, where `value` is the gradient in index space - void neumann_o2(Field3D& f, BoutReal value) const { - ASSERT3(valid() >= 0); - if (valid() < 1) { - return neumann_o1(f, value); - } - for (int i = 0; i < boundary_width(); ++i) { - getAt(f, -i) = prev(f) + (2 + i) * value; + template + BoutReal _at(const function_accessor auto& f, int off) const { + if constexpr (check) { + ASSERT3(valid() > -off - 2); + } + auto _off = _offset() + (off * dir_m); + return f(_off, _ind().yp(_off)); } - } - /// Apply neumann boundary condition, where `value` is the gradient in index space - void neumann_o3(Field3D& f, BoutReal value) const { - ASSERT3(valid() >= 0); - if (valid() < 1) { - return neumann_o2(f, value); + signed char _offset() const { return offset_m; } + signed char _valid() const { return valid_m; } + Ind3D _ind() const { return index; } + int _boundary_width() const { return localmesh_m->ystart - abs_offset_m + 1; } + BoutReal _length([[maybe_unused]] CELL_LOC loc) const { + ASSERT3(loc == CELL_CENTRE); + return length_m; } - for (int i = 0; i < boundary_width(); ++i) { - getAt(f, -i) = parallel_stencil::neumann_o3(i + 1 - length(f.getLocation()), value, - i + 1, current(f), 2, prev(f)); - } - } - void set_free(Field3D& f, BoundaryFreeExtrapolation mode) const { - BoutReal fac = BoutNaN; - if (valid() > 0) { - fac = limitFreeScale(prev(f), current(f), mode); - } else { - fac = mode == BoundaryFreeExtrapolation::linear ? 0 : 1; + auto operator<=>(const Point& rhs) const { + // Ensure we're looking at the same boundary + ASSERT3(dir_m == rhs.dir_m); + return _ind() <=> rhs._ind(); } - auto val = current(f); - if (mode == BoundaryFreeExtrapolation::linear) { - for (int i = 0; i < boundary_width(); ++i) { - val += fac; - getAt(f, -i) = val; - } - } else { - for (int i = 0; i < boundary_width(); ++i) { - val *= fac; - getAt(f, -i) = val; - } + bool operator==(Point rhs) const { + ASSERT3(dir_m == rhs.dir_m); + return _ind() == rhs._ind(); } - } - void setSmallValue(BoutReal val) { - ASSERT2(val > 0); - ASSERT2(val < 0.5); - small_value = val; - } - -private: - BoutReal small_value = 1e-4; - friend impl; -}; - -namespace { -/// 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, BoundaryFreeExtrapolation mode) { - if ((fm < fc) && (mode == BoundaryFreeExtrapolation::limited)) { - return fc; // Neumann rather than increasing into boundary - } - if (fm < 1e-10) { - return fc; // Low / no density condition - } - - BoutReal fp = 0; - switch (mode) { - case BoundaryFreeExtrapolation::limited: - case BoundaryFreeExtrapolation::exponential: - fp = SQ(fc) / fm; // Exponential - break; - case BoundaryFreeExtrapolation::linear: - fp = (2.0 * fc) - fm; // Linear - break; - } - -#if CHECKLEVEL >= 2 - if (!std::isfinite(fp)) { - throw BoutException("SheathBoundary limitFree {}: {}, {} -> {}", - static_cast(mode), fm, fc, fp); - } -#endif - - return fp; -} -} // namespace + }; -class BoundaryRegionFCI : public BoundaryRegionBase { -public: BoundaryRegionFCI(const std::string& name, const BndryLoc& loc, int dir, Mesh* mesh) : BoundaryRegionBase(name, loc, mesh), _dir(dir), localmesh(mesh) { isParallel = true; }; + /// 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) { + if (!bndry_points.empty() && bndry_points.back()._ind() > 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))); + bndry_points.emplace_back(ind, RealPoint{x, y, z}, length, valid, offset, localmesh, + _dir); } + + /// Add a point to the boundary 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), x, y, z, length, valid, offset); } + + /// Return `true` if the boundary contains a point at the given index bool contains(int ix, int iy, int iz) { - const auto ind = xyz2ind(ix, iy, iz); ensureSorted(); - const auto found = - std::lower_bound(std::begin(bndry_points), std::end(bndry_points), ind); - return found != std::end(bndry_points) and found->index == ind; + return std::ranges::binary_search(bndry_points, xyz2ind(ix, iy, iz), {}, + [](const auto& point) { return point._ind(); }); } + + /// The direction of the boundary int dir() const { return _dir; } + // legacy interface void first() override { throw BoutException("Legacy interface is not suppored"); } void next() override { throw BoutException("Legacy interface is not suppored"); } bool isDone() override { throw BoutException("Legacy interface is not suppored"); } + auto begin() const { return bndry_points.begin(); } + auto end() const { return bndry_points.end(); } + auto begin() { return bndry_points.begin(); } + auto end() { return bndry_points.end(); } + private: - friend class BoundaryRegionIterFCI; + friend class Point; int _dir; // Vector of points in the boundary - bout::parallel_boundary_region::IndicesVec bndry_points; + std::vector bndry_points; Mesh* localmesh; bool is_sorted{true}; void ensureSorted() { if (is_sorted) { return; } - std::sort(std::begin(bndry_points), std::end(bndry_points)); + std::ranges::sort(bndry_points); } Ind3D xyz2ind(int x, int y, int z) const { const int ny = localmesh->LocalNy; const int nz = localmesh->LocalNz; - return Ind3D{((x * ny + y) * nz) + z, ny, nz}; + return Ind3D{(((x * ny) + y) * nz) + z, ny, nz}; } }; -class BoundaryRegionIterFCI : public BoundaryRegionIterBase { -private: - // TODO(dave) make non-const? - const BoundaryRegionFCI* region; - size_t pos{0}; - +/// Boundary region for field-aligned grids +/// +/// FCI grids should use `BoundaryRegionFCI` +/// +/// Template parameter `isXtemp` is `true` for boundaries in X, and `false` for boundaries in Y +template +class BoundaryRegionXY : public BoundaryRegionBase { public: - BoundaryRegionIterFCI() = delete; - BoundaryRegionIterFCI(const BoundaryRegionFCI* reg, bool isstart) - : region(reg), pos(isstart ? 0 : reg->bndry_points.size()) {} - void setValid(char valid) { - const_cast(region)->bndry_points[pos].valid = valid; - }; - BoutReal s_x() const { return region->bndry_points[pos].intersection.s_x; }; - BoutReal s_y() const { return region->bndry_points[pos].intersection.s_y; }; - BoutReal s_z() const { return region->bndry_points[pos].intersection.s_z; }; - Mesh* localmesh() const { return region->localmesh; }; - int dir() const { return region->_dir; } - template - BoutReal& _getAt(Field3D& f, int off) const { - ASSERT3(f.hasParallelSlices()); - if constexpr (check) { - ASSERT3(_valid() > -off - 2); + /// Iterator over a `BoundaryRegionXY` + class Iterator : public BoundaryRegionIterBase { + private: + const BoundaryRegionXY* region; + size_t pos{0}; + + /// Return the current index displaced by \p offset + Ind3D offsetInd(int offset) const { + if constexpr (isXtemp) { + return _ind().xp(offset); + } else { + return _ind().yp(offset); + } } - auto _off = _offset() - (off * region->_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); + + public: + Iterator() = delete; + Iterator(const BoundaryRegionXY* reg, bool isstart) + : region(reg), pos(isstart ? 0 : reg->rgn.size()) {} + int dir() const { return region->_dir; } + Ind3D ind() const { return _ind(); } + + template + requires utils::is_Field_v + const BoutReal& _at(const T& f, int off) const { + if constexpr (check) { + ASSERT3(_valid() > -off - 2); + } + auto _off = (1 - off) * region->_dir; + return f[offsetInd(_off)]; } - auto _off = _offset() - (off * region->_dir); - return f.ynext(_off)[_ind().yp(_off)]; - } - template - BoutReal& _getAt(Field2D& f, int off) const { - ASSERT3(f.hasParallelSlices()); - if constexpr (check) { - ASSERT3(_valid() > -off - 2); + + template + requires utils::is_Field_v + BoutReal& _at(T& f, int off) const { + if constexpr (check) { + ASSERT3(_valid() > -off - 2); + } + auto _off = (1 - off) * region->_dir; + return f[offsetInd(_off)]; } - auto _off = _offset() - (off * region->_dir); - return f.ynext(_off)[_ind().yp(_off)]; - } - template - const BoutReal& _getAt(const Field2D& f, int off) const { - ASSERT3(f.hasParallelSlices()); - if constexpr (check) { - ASSERT3(_valid() > -off - 2); + + template + BoutReal _at(const function_accessor auto& f, int off) const { + if constexpr (check) { + ASSERT3(_valid() > -off - 2); + } + auto _off = (1 - off) * region->_dir; + return f(0, offsetInd(_off)); } - auto _off = _offset() - (off * region->_dir); - return f.ynext(_off)[_ind().yp(_off)]; - } - template - BoutReal _getAt(const std::function& f, - int off) const { - if constexpr (check) { - ASSERT3(valid() > -off - 2); + + signed char _offset() const { return region->_dir; } + signed char _valid() const { return region->valid; } + Ind3D _ind() const { return region->rgn[pos]; } + int _boundary_width() const { + if constexpr (isXtemp) { + return region->localmesh->xstart; + } + return region->localmesh->ystart; } - auto _off = _offset() + (off * region->_dir); - return f(_off, _ind().yp(_off)); - } - signed char _offset() const { return region->bndry_points[pos].offset; } - signed char _valid() const { return region->bndry_points[pos].valid; } - Ind3D _ind() const { return region->bndry_points[pos].index; } - int _boundary_width() const { - return region->localmesh->ystart - region->bndry_points[pos].abs_offset + 1; - } - BoutReal _length([[maybe_unused]] CELL_LOC loc) const { - ASSERT3(loc == CELL_CENTRE); - return region->bndry_points[pos].length; - } - bool operator!=(BoundaryRegionIterFCI lhs) const { - ASSERT3(region == lhs.region); - return pos != lhs.pos; - } - BoundaryRegionIterFCI& operator++() { - ++pos; - return *this; - } - // No-op for compatibility - BoundaryRegionIterFCI& operator*() { return *this; } -}; -template -class BoundaryRegionXY : public BoundaryRegionBase { -public: + BoutReal _length(CELL_LOC loc) const { + // Account for staggered grids + if ((isXtemp and loc == CELL_XLOW) or (not isXtemp and loc == CELL_YLOW)) { + return (dir() == 1) ? 1 : 0; + } + return 0.5; + } + + auto operator<=>(const Iterator& rhs) const { + ASSERT3(region == rhs.region); + return pos <=> rhs.pos; + } + + bool operator==(const Iterator& rhs) const { + ASSERT3(region == rhs.region); + return pos == rhs.pos; + } + + Iterator& operator++() { + ++pos; + return *this; + } + + Iterator& operator*() { return *this; } + }; + BoundaryRegionXY() = delete; - BoundaryRegionXY(const std::string& name, int dir, Mesh* mesh, Region&& rgn) + BoundaryRegionXY(const std::string& name, int dir, Mesh* mesh, const Region& rgn) : BoundaryRegionBase(name, mesh), _dir(dir), valid(isXtemp ? mesh->xstart : mesh->ystart) { BOUT_FOR_SERIAL(i, rgn) { this->rgn.emplace_back(i); } @@ -562,125 +446,25 @@ public: location = dir == 1 ? BNDRY_YUP : BNDRY_YDOWN; } } - int dir() { return _dir; } + int dir() const { return _dir; } // legacy interface void first() override { throw BoutException("Legacy interface is not suppored"); } void next() override { throw BoutException("Legacy interface is not suppored"); } bool isDone() override { throw BoutException("Legacy interface is not suppored"); } + auto begin() const { return Iterator(this, true); } + auto end() const { return Iterator(this, false); } + private: - template - friend class BoundaryRegionIterXY; int _dir; std::vector rgn; signed char valid; }; -template -class BoundaryRegionIterXY : public BoundaryRegionIterBase> { -private: - const BoundaryRegionXY* region; - size_t pos{0}; - -public: - BoundaryRegionIterXY() = delete; - BoundaryRegionIterXY(const BoundaryRegionXY* reg, bool isstart) - : region(reg), pos(isstart ? 0 : reg->rgn.size()) {} - int dir() const { return region->_dir; } - template - BoutReal& _getAt(Field3D& f, int off) const { - if constexpr (check) { - ASSERT3(_valid() > -off - 2); - } - auto _off = (1 - off) * region->_dir; - if constexpr (isX) { - return f[_ind().xp(_off)]; - } else { - return f[_ind().yp(_off)]; - } - } - template - const BoutReal& _getAt(const Field3D& f, int off) const { - if constexpr (check) { - ASSERT3(_valid() > -off - 2); - } - auto _off = (1 - off) * region->_dir; - if constexpr (isX) { - return f[_ind().xp(_off)]; - } else { - return f[_ind().yp(_off)]; - } - } - template - BoutReal& _getAt(Field2D& f, int off) const { - if constexpr (check) { - ASSERT3(_valid() > -off - 2); - } - auto _off = (1 - off) * region->_dir; - if constexpr (isX) { - return f[_ind().xp(_off)]; - } else { - return f[_ind().yp(_off)]; - } - } - template - const BoutReal& _getAt(const Field2D& f, int off) const { - if constexpr (check) { - ASSERT3(_valid() > -off - 2); - } - auto _off = (1 - off) * region->_dir; - if constexpr (isX) { - return f[_ind().xp(_off)]; - } else { - return f[_ind().yp(_off)]; - } - } - template - BoutReal _getAt(const std::function& f, - int off) const { - if constexpr (check) { - ASSERT3(_valid() > -off - 2); - } - auto _off = (1 - off) * region->_dir; - if constexpr (isX) { - return f(0, _ind().xp(_off)); - } else { - return f(0, _ind().yp(_off)); - } - } - signed char _offset() const { return region->_dir; } - signed char _valid() const { return region->valid; } - Ind3D _ind() const { return region->rgn[pos]; } - int _boundary_width() const { - if constexpr (isX) { - return region->localmesh->xstart; - } - return region->localmesh->ystart; - } - BoutReal _length(CELL_LOC loc) const { - if (loc == CELL_XLOW) { - if (dir() == 1) { - return 1; - } - return 0; - } - return 0.5; - } - bool operator!=(BoundaryRegionIterXY lhs) { - ASSERT3(region == lhs.region); - return pos != lhs.pos; - } - BoundaryRegionIterXY& operator++() { - ++pos; - return *this; - } - // No-op for compatibility - BoundaryRegionIterXY& operator*() { return *this; } -}; +/// Alias for boundary regions over X specifically using BoundaryRegionX = BoundaryRegionXY; +/// Alias for boundary regions over Y specifically using BoundaryRegionY = BoundaryRegionXY; -using BoundaryRegionIterX = BoundaryRegionIterXY; -using BoundaryRegionIterY = BoundaryRegionIterXY; inline std::shared_ptr NewBoundaryRegionXIn(const std::string& name, int ymin, int ymax, Mesh* mesh) { @@ -688,7 +472,7 @@ NewBoundaryRegionXIn(const std::string& name, int ymin, int ymax, Mesh* mesh) { name, -1, mesh, Region(mesh->xstart, mesh->xstart, ymin, ymax, mesh->zstart, mesh->zend, mesh->LocalNy, mesh->LocalNz, mesh->maxregionblocksize)); - pointer->legacy = new ::BoundaryRegionXIn(name, ymin, ymax, mesh); + pointer->legacy = std::make_unique<::BoundaryRegionXIn>(name, ymin, ymax, mesh); return pointer; } @@ -698,7 +482,7 @@ NewBoundaryRegionXOut(const std::string& name, int ymin, int ymax, Mesh* mesh) { name, 1, mesh, Region(mesh->xend, mesh->xend, ymin, ymax, mesh->zstart, mesh->zend, mesh->LocalNy, mesh->LocalNz, mesh->maxregionblocksize)); - pointer->legacy = new ::BoundaryRegionXOut(name, ymin, ymax, mesh); + pointer->legacy = std::make_unique<::BoundaryRegionXOut>(name, ymin, ymax, mesh); return pointer; } @@ -708,7 +492,7 @@ NewBoundaryRegionYUp(const std::string& name, int xmin, int xmax, Mesh* mesh) { name, 1, mesh, Region(xmin, xmax, mesh->yend, mesh->yend, mesh->zstart, mesh->zend, mesh->LocalNy, mesh->LocalNz, mesh->maxregionblocksize)); - pointer->legacy = new ::BoundaryRegionYUp(name, xmin, xmax, mesh); + pointer->legacy = std::make_unique<::BoundaryRegionYUp>(name, xmin, xmax, mesh); return pointer; } @@ -718,7 +502,7 @@ NewBoundaryRegionYDown(const std::string& name, int xmin, int xmax, Mesh* mesh) name, -1, mesh, Region(xmin, xmax, mesh->ystart, mesh->ystart, mesh->zstart, mesh->zend, mesh->LocalNy, mesh->LocalNz, mesh->maxregionblocksize)); - pointer->legacy = new ::BoundaryRegionYDown(name, xmin, xmax, mesh); + pointer->legacy = std::make_unique<::BoundaryRegionYDown>(name, xmin, xmax, mesh); return pointer; } @@ -741,34 +525,240 @@ void iter_boundary(const std::shared_ptr& bndrybase, bndrybase->label); } -template ::value>> +template + requires std::derived_from void iter_boundary(const Bndry& bndry, const Func& func) { - static_assert(std::is_base_of::value, - "Bndry must derive from BoundaryRegionY"); for (auto& point : bndry) { func(point); } } -} // namespace boundary -} // namespace bout -inline bout::boundary::BoundaryRegionIterFCI -begin(const bout::boundary::BoundaryRegionFCI& reg) { - return bout::boundary::BoundaryRegionIterFCI(®, true); +/* + * INTERPOLATION and EXTRAPOLATION + */ + +/// Extrapolate a given field to the boundary +template +BoutReal extrapolate_boundary_o1(const Iter& point, const Field3D& f) { + return point.current(f); +} + +/// Extrapolate a given field to the boundary +template +BoutReal extrapolate_boundary_o2(const Iter& point, const Field3D& f) { + ASSERT3(point.valid() >= 0); + if (point.valid() < 1) { + return extrapolate_boundary_o1(point, f); + } + return point.current(f) * (1 + point.length(f.getLocation())) + - point.prev(f) * point.length(f.getLocation()); +} + +/// Extrapolate a given function to the boundary +template +BoutReal extrapolate_bounday_o1(const Iter& point, const function_accessor auto& func, + [[maybe_unused]] CELL_LOC loc = CELL_CENTRE) { + return point.current(func); +} + +/// Extrapolate a given function to the boundary +template +BoutReal extrapolate_boundary_o2(const Iter& point, const function_accessor auto& func, + CELL_LOC loc = CELL_CENTRE) { + ASSERT3(point.valid() >= 0); + if (point.valid() < 1) { + return extrapolate_boundary_o1(point, func); + } + return point.current(func) * (1 + point.length(loc)) + - point.prev(func) * point.length(loc); +} + +/// Interpolate a field to the boundary, using the boundary values +template +BoutReal interpolate_boundary_o2(const Iter& point, const Field3D& f) { + return point.current(f) * (1 - point.length(f.getLocation())) + + point.next(f) * point.length(f.getLocation()); +} +/// Interpolate a field to the boundary, using the boundary values +template +BoutReal interpolate_boundary_o2(const Iter& point, const function_accessor auto& func, + CELL_LOC loc = CELL_CENTRE) { + return point.current(func) * (1 - point.length(loc)) + + point.next(func) * point.length(loc); +} +/// Extrapolate to the first boundary value freely +template +BoutReal extrapolate_next_o1(const Iter& point, const Field3D& f) { + return point.current(f); +} +/// Extrapolate to the first boundary value freely +template +BoutReal extrapolate_next_o2(const Iter& point, const Field3D& f) { + ASSERT3(point.valid() >= 0); + if (point.valid() < 1) { + return extrapolate_next_o1(point, f); + } + return point.current(f) * 2 - point.prev(f); +} + +/// Extrapolate to the first boundary value freely +template +BoutReal extrapolate_next_o1(const Iter& point, const function_accessor auto& func) { + return point.current(func); +} +/// Extrapolate to the first boundary value freely +template +BoutReal extrapolate_next_o2(const Iter& point, const function_accessor auto& func) { + ASSERT3(point.valid() >= 0); + if (point.valid() < 1) { + return extrapolate_next_o1(func); + } + return point.current(func) * 2 - prev(func); +} + +/// extrapolate the gradient into the boundary +template +BoutReal extrapolate_grad_o1([[maybe_unused]] const Iter& point, + [[maybe_unused]] const Field3D& f) { + return 0; +} +/// extrapolate the gradient into the boundary +template +BoutReal extrapolate_grad_o2(const Iter& point, const Field3D& f) { + ASSERT3(point.valid() >= 0); + if (point.valid() < 1) { + return extrapolate_grad_o1(point, f); + } + return point.current(f) - point.next(f); +} + +template +BoutReal extrapolate_boundary_free(const Iter& point, const Field3D& f, + BoundaryFreeExtrapolation mode) { + BoutReal fac = BoutNaN; + if (point.valid() > 0) { + fac = limitFreeScale(point.prev(f), point.current(f), mode); + } else { + fac = mode == BoundaryFreeExtrapolation::linear ? 0 : 1; + } + const auto val = point.current(f); + const BoutReal next = mode == BoundaryFreeExtrapolation::linear ? val + fac : val * fac; + return val * point.length(f.getLocation()) + next * (1 - point.length(f.getLocation())); +} + +/* + * APPLY BOUNDARY CONDITIONS + */ + +/// Apply a dirichlet boundary condition +template +void dirichlet_o1(const Iter& point, Field3D& f, BoutReal value) { + for (int i = 0; i < point.boundary_width(); ++i) { + point.at(f, -i) = value; + } +} + +/// Apply a dirichlet boundary condition +template +void dirichlet_o2(const Iter& point, Field3D& f, BoutReal value) { + if (point.length(f.getLocation()) < point.smallValue()) { + return dirichlet_o1(point, f, value); + } + for (int i = 0; i < point.boundary_width(); ++i) { + point.at(f, -i) = parallel_stencil::dirichlet_o2( + i + 1, point.current(f), i + 1 - point.length(f.getLocation()), value); + } +} + +/// Apply a dirichlet boundary condition +template +void dirichlet_o3(const Iter& point, Field3D& f, BoutReal value) { + ASSERT3(point.valid() >= 0); + if (point.valid() < 1) { + return dirichlet_o2(point, f, value); + } + if (point.length(f.getLocation()) < point.smallValue()) { + for (int i = 0; i < point.boundary_width(); ++i) { + point.at(f, -i) = parallel_stencil::dirichlet_o2( + i + 2, point.prev(f), i + 1 - point.length(f.getLocation()), value); + } + } else { + for (int i = 0; i < point.boundary_width(); ++i) { + point.at(f, -i) = + parallel_stencil::dirichlet_o3(i + 2, point.prev(f), i + 1, point.current(f), + i + 1 - point.length(f.getLocation()), value); + } + } +} + +/// Ensure the value in the boundary is at least `value` +template +void limit_at_least(const Iter& point, Field3D& f, BoutReal value) { + for (int i = 0; i < point.boundary_width(); ++i) { + if (point.at(f, -i) < value) { + point.at(f, -i) = value; + } + } } -inline bout::boundary::BoundaryRegionIterFCI -end(const bout::boundary::BoundaryRegionFCI& reg) { - return bout::boundary::BoundaryRegionIterFCI(®, false); + +/// Apply neumann boundary condition, where `value` is the gradient in index space + +// neumann_o1 would give second order convergence, given an appropriate one-sided stencil. +// But in general we do not, and thus for normal C2 stencils, this is 1st order. +template +void neumann_o1(const Iter& point, Field3D& f, BoutReal value) { + for (int i = 0; i < point.boundary_width(); ++i) { + point.at(f, -i) = point.current(f) + value * (i + 1); + } } -template -inline bout::boundary::BoundaryRegionIterXY -begin(const bout::boundary::BoundaryRegionXY& reg) { - return bout::boundary::BoundaryRegionIterXY(®, true); +/// Apply neumann boundary condition, where `value` is the gradient in index space +template +void neumann_o2(const Iter& point, Field3D& f, BoutReal value) { + ASSERT3(point.valid() >= 0); + if (point.valid() < 1) { + return neumann_o1(point, f, value); + } + for (int i = 0; i < point.boundary_width(); ++i) { + point.at(f, -i) = point.prev(f) + (2 + i) * value; + } } -template -inline bout::boundary::BoundaryRegionIterXY -end(const bout::boundary::BoundaryRegionXY& reg) { - return bout::boundary::BoundaryRegionIterXY(®, false); + +/// Apply neumann boundary condition, where `value` is the gradient in index space +template +void neumann_o3(const Iter& point, Field3D& f, BoutReal value) { + ASSERT3(point.valid() >= 0); + if (point.valid() < 1) { + return neumann_o2(point, f, value); + } + for (int i = 0; i < point.boundary_width(); ++i) { + point.at(f, -i) = + parallel_stencil::neumann_o3(i + 1 - point.length(f.getLocation()), value, i + 1, + point.current(f), 2, point.prev(f)); + } } + +template +void set_free(const Iter& point, Field3D& f, BoundaryFreeExtrapolation mode) { + BoutReal fac = BoutNaN; + if (point.valid() > 0) { + fac = limitFreeScale(point.prev(f), point.current(f), mode); + } else { + fac = mode == BoundaryFreeExtrapolation::linear ? 0 : 1; + } + auto val = point.current(f); + if (mode == BoundaryFreeExtrapolation::linear) { + for (int i = 0; i < point.boundary_width(); ++i) { + val += fac; + point.at(f, -i) = val; + } + } else { + for (int i = 0; i < point.boundary_width(); ++i) { + val *= fac; + point.at(f, -i) = val; + } + } +} + +} // namespace boundary +} // namespace bout diff --git a/include/bout/boundary_standard.hxx b/include/bout/boundary_standard.hxx index 3cb4b23803..b7848dcf64 100644 --- a/include/bout/boundary_standard.hxx +++ b/include/bout/boundary_standard.hxx @@ -5,9 +5,11 @@ #include "bout/boundary_op.hxx" #include "bout/bout_types.hxx" +#include "bout/sys/expressionparser.hxx" #include "bout/unused.hxx" #include +#include #include /// Dirichlet boundary condition set half way between guard cell and grid cell at 2nd order accuracy diff --git a/include/bout/bout_enum_class.hxx b/include/bout/bout_enum_class.hxx index 334fa657da..ce088a4628 100644 --- a/include/bout/bout_enum_class.hxx +++ b/include/bout/bout_enum_class.hxx @@ -67,7 +67,7 @@ /// Create an enum class with toString and FromString functions, and an /// Options::as overload to read the enum #define BOUT_ENUM_CLASS(enumname, ...) \ - enum class enumname : std::int8_t { __VA_ARGS__ }; \ + enum class enumname : std::uint8_t { __VA_ARGS__ }; \ \ inline std::string toString(enumname e) { \ \ @@ -105,4 +105,48 @@ return out << toString(e); \ } +/// Create an enum class with toString and FromString functions, and an +/// Options::as overload to read the enum +#define BOUT_ENUM_CLASS_NS(ns, enumname, ...) \ + namespace ns { \ + enum class enumname : std::uint8_t { __VA_ARGS__ }; \ + } \ + \ + inline std::string toString(ns::enumname e) { \ + \ + const static std::map toString_map = { \ + BOUT_ENUM_CLASS_MAP_ARGS(BOUT_ENUM_CLASS_STR, ns::enumname, __VA_ARGS__)}; \ + auto found = toString_map.find(e); \ + if (found == toString_map.end()) { \ + throw BoutException("Did not find enum {:d}", static_cast(e)); \ + } \ + return found->second; \ + } \ + namespace ns { \ + inline enumname BOUT_MAKE_FROMSTRING_NAME(enumname)(const std::string& s) { \ + const static std::map fromString_map = { \ + BOUT_ENUM_CLASS_MAP_ARGS(BOUT_STR_ENUM_CLASS, enumname, __VA_ARGS__)}; \ + auto found = fromString_map.find(s); \ + if (found == fromString_map.end()) { \ + std::string valid_values{}; \ + for (auto const& entry : fromString_map) { \ + valid_values += std::string(" ") + entry.first; \ + } \ + throw BoutException("Did not find enum {:s}. Valid values: {:s}", s, \ + valid_values); \ + } \ + return found->second; \ + } \ + } \ + \ + template <> \ + inline ns::enumname Options::as(const ns::enumname&) const { \ + return ns::BOUT_MAKE_FROMSTRING_NAME(enumname)(this->as()); \ + } \ + namespace ns { \ + inline std::ostream& operator<<(std::ostream& out, const enumname& e) { \ + return out << toString(e); \ + } \ + } + #endif // BOUT_ENUM_CLASS_H diff --git a/include/bout/bout_types.hxx b/include/bout/bout_types.hxx index 7747b937b3..1d8b459c0c 100644 --- a/include/bout/bout_types.hxx +++ b/include/bout/bout_types.hxx @@ -25,6 +25,7 @@ #include "bout/build_config.hxx" #include +#include #include /// Size of real numbers @@ -149,6 +150,12 @@ struct Constant { T v; View(T v) : v(v) {} BOUT_HOST_DEVICE T operator()(int) const { return v; } + BOUT_HOST_DEVICE bool hasParallelSlices() const { return false; } + BOUT_HOST_DEVICE int numberParallelSlices() const { return 0; } + BOUT_HOST_DEVICE View yup(int = 0) const { return *this; } + BOUT_HOST_DEVICE View ydown(int = 0) const { return *this; } + + std::optional getRegionID() const { return {}; } }; operator View() const { return {val}; } }; diff --git a/include/bout/build_config.hxx b/include/bout/build_config.hxx index aa7f257ca5..0fed934af0 100644 --- a/include/bout/build_config.hxx +++ b/include/bout/build_config.hxx @@ -92,6 +92,15 @@ constexpr auto use_msgstack = static_cast(BOUT_USE_MSGSTACK); #else #define BOUT_ASSUME(condition) ((void)0) #endif + +#if defined(__GNUC__) || defined(__clang__) +#define BOUT_UNREACHABLE() __builtin_unreachable() +#elif defined(_MSC_VER) +#define BOUT_UNREACHABLE() __assume(false) +#else +// Cannot call std::abort in device code +#define BOUT_UNREACHABLE() +#endif // NOLINTEND(cppcoreguidelines-macro-usage) #endif // BOUT_BUILD_OPTIONS_HXX diff --git a/include/bout/christoffel_symbols.hxx b/include/bout/christoffel_symbols.hxx new file mode 100644 index 0000000000..db88b8688e --- /dev/null +++ b/include/bout/christoffel_symbols.hxx @@ -0,0 +1,63 @@ +#ifndef BOUT_CHRISTOFFELSYMBOLS_HXX +#define BOUT_CHRISTOFFELSYMBOLS_HXX + +#include + +class Coordinates; + +class ChristoffelSymbols { + +public: + explicit ChristoffelSymbols(const Coordinates& coordinates); + + const bout::FieldMetric& G1_11() const { return G1_11_m; } + const bout::FieldMetric& G1_22() const { return G1_22_m; } + const bout::FieldMetric& G1_33() const { return G1_33_m; } + const bout::FieldMetric& G1_12() const { return G1_12_m; } + const bout::FieldMetric& G1_13() const { return G1_13_m; } + const bout::FieldMetric& G1_23() const { return G1_23_m; } + + const bout::FieldMetric& G2_11() const { return G2_11_m; } + const bout::FieldMetric& G2_22() const { return G2_22_m; } + const bout::FieldMetric& G2_33() const { return G2_33_m; } + const bout::FieldMetric& G2_12() const { return G2_12_m; } + const bout::FieldMetric& G2_13() const { return G2_13_m; } + const bout::FieldMetric& G2_23() const { return G2_23_m; } + + const bout::FieldMetric& G3_11() const { return G3_11_m; } + const bout::FieldMetric& G3_22() const { return G3_22_m; } + const bout::FieldMetric& G3_33() const { return G3_33_m; } + const bout::FieldMetric& G3_12() const { return G3_12_m; } + const bout::FieldMetric& G3_13() const { return G3_13_m; } + const bout::FieldMetric& G3_23() const { return G3_23_m; } + + // Transforms the ChristoffelSymbols by applying the given function to every element + template + void map(F function) { + G1_11_m = function(G1_11_m); + G1_22_m = function(G1_22_m); + G1_33_m = function(G1_33_m); + G1_12_m = function(G1_12_m); + G1_13_m = function(G1_13_m); + G1_23_m = function(G1_23_m); + G2_11_m = function(G2_11_m); + G2_22_m = function(G2_22_m); + G2_33_m = function(G2_33_m); + G2_12_m = function(G2_12_m); + G2_13_m = function(G2_13_m); + G2_23_m = function(G2_23_m); + G3_11_m = function(G3_11_m); + G3_22_m = function(G3_22_m); + G3_33_m = function(G3_33_m); + G3_12_m = function(G3_12_m); + G3_13_m = function(G3_13_m); + G3_23_m = function(G3_23_m); + } + +private: + bout::FieldMetric G1_11_m, G1_22_m, G1_33_m, G1_12_m, G1_13_m, G1_23_m; + bout::FieldMetric G2_11_m, G2_22_m, G2_33_m, G2_12_m, G2_13_m, G2_23_m; + bout::FieldMetric G3_11_m, G3_22_m, G3_33_m, G3_12_m, G3_13_m, G3_23_m; +}; + +#endif //BOUT_CHRISTOFFELSYMBOLS_HXX diff --git a/include/bout/coordinates.hxx b/include/bout/coordinates.hxx index 2c33701762..a090dcd959 100644 --- a/include/bout/coordinates.hxx +++ b/include/bout/coordinates.hxx @@ -26,34 +26,38 @@ #ifndef BOUT_COORDINATES_H #define BOUT_COORDINATES_H -#include "bout/assert.hxx" #include "bout/field_data.hxx" +#include +#include #include #include +#include #include #include +#include +#include #include #include #include #include #include +#include +#include class Mesh; +struct MetricNormaliser; + +namespace bout::boundary { class YBoundary; +} /*! * Represents a coordinate system, and associated operators - * - * This is a container for a collection of metric tensor components */ class Coordinates { public: -#if BOUT_USE_METRIC_3D - using FieldMetric = Field3D; -#else - using FieldMetric = Field2D; -#endif + using FieldMetric = bout::FieldMetric; /// Standard constructor from input Coordinates(Mesh* mesh, Options* options = nullptr); @@ -64,8 +68,8 @@ public: /// force_interpolate_from_centre argument to true to always interpolate /// (useful if CELL_CENTRE Coordinates have been changed, so reading from file /// would not be correct). - Coordinates(Mesh* mesh, Options* options, const CELL_LOC loc, - const Coordinates* coords_in, bool force_interpolate_from_centre = false); + Coordinates(Mesh* mesh, Options* options, CELL_LOC loc, const Coordinates* coords_in, + bool force_interpolate_from_centre = false); /// A constructor useful for testing purposes. To use it, inherit /// from Coordinates. If \p calculate_geometry is true (default), @@ -76,38 +80,90 @@ public: FieldMetric g_22, FieldMetric g_33, FieldMetric g_12, FieldMetric g_13, FieldMetric g_23, FieldMetric ShiftTorsion, FieldMetric IntShiftTorsion); - Coordinates& operator=(Coordinates&&) = default; - - ~Coordinates() = default; - /// Add variables to \p output_options, for post-processing void outputVars(Options& output_options); - FieldMetric dx, dy, dz; ///< Mesh spacing in x, y and z + ///< Mesh spacing in x, y and z + const FieldMetric& dx() const { return dx_; } + const FieldMetric& dy() const { return dy_; } + const FieldMetric& dz() const { return dz_; } + + const BoutReal& dx(int x, int y, int z) const { return dx_(x, y, z); } + const BoutReal& dy(int x, int y, int z) const { return dy_(x, y, z); } + const BoutReal& dz(int x, int y, int z) const { return dz_(x, y, z); } + +#if BOUT_USE_METRIC_3D + const BoutReal* dx(int x, int y) const { return dx_(x, y); } + const BoutReal* dy(int x, int y) const { return dy_(x, y); } + const BoutReal* dz(int x, int y) const { return dz_(x, y); } +#else + const BoutReal& dx(int x, int y) const { return dx_(x, y); } + const BoutReal& dy(int x, int y) const { return dy_(x, y); } + const BoutReal& dz(int x, int y) const { return dz_(x, y); } +#endif + + const BoutReal& IntShiftTorsion(int x, int y, int z) const { + return IntShiftTorsion_(x, y, z); + } + +#if not(BOUT_USE_METRIC_3D) + const BoutReal& IntShiftTorsion(int x, int y) const { return IntShiftTorsion_(x, y); } +#endif + + const BoutReal& J(int x, int y, int z) const { return J()(x, y, z); } + +#if not(BOUT_USE_METRIC_3D) + const BoutReal& J(int x, int y) const { return J()(x, y); } +#endif + + const BoutReal& Bxy(int x, int y, int z) const { return Bxy()(x, y, z); } +#if not(BOUT_USE_METRIC_3D) + const BoutReal& Bxy(int x, int y) const { return Bxy()(x, y); } +#endif + + void setDx(FieldMetric dx, bool communicate = true); + void setDy(FieldMetric dy, bool communicate = true); + void setDz(FieldMetric dz, bool communicate = true); + + void setD1_dx(FieldMetric d1_dx) { d1_dx_ = std::move(d1_dx); } + void setD1_dy(FieldMetric d1_dy) { d1_dy_ = std::move(d1_dy); } + void setD1_dz(FieldMetric d1_dz) { d1_dz_ = std::move(d1_dz); } /// Length of the Z domain. Used for FFTs const Field2D& zlength() const; - /// True if corrections for non-uniform mesh spacing should be included in operators - bool non_uniform; - /// 2nd-order correction for non-uniform meshes d/di(1/dx), d/di(1/dy) and d/di(1/dz) - FieldMetric d1_dx, d1_dy, d1_dz; + const BoutReal& zlength(int x, int y) const { return zlength()(x, y); } - FieldMetric J; ///< Coordinate system Jacobian, so volume of cell is J*dx*dy*dz + /// True if corrections for non-uniform mesh spacing should be included in operators + bool non_uniform() const { return non_uniform_; } + void setNon_uniform(bool non_uniform) { non_uniform_ = non_uniform; } - FieldMetric Bxy; ///< Magnitude of B = nabla z times nabla x + /// 2nd-order correction for non-uniform meshes d/di(1/dx), d/di(1/dy) and d/di(1/dz) + const FieldMetric& d1_dx() const { return d1_dx_; } + const FieldMetric& d1_dy() const { return d1_dy_; } + const FieldMetric& d1_dz() const { return d1_dz_; } - /// Contravariant metric tensor (g^{ij}) - FieldMetric g11, g22, g33, g12, g13, g23; +#if BOUT_USE_METRIC_3D + const BoutReal& d1_dx(int x, int y, int z) const { return d1_dx_(x, y, z); } + const BoutReal& d1_dy(int x, int y, int z) const { return d1_dy_(x, y, z); } + const BoutReal& d1_dz(int x, int y, int z) const { return d1_dz_(x, y, z); } +#else + const BoutReal& d1_dx(int x, int y) const { return d1_dx_(x, y); } + const BoutReal& d1_dy(int x, int y) const { return d1_dy_(x, y); } + const BoutReal& d1_dz(int x, int y) const { return d1_dz_(x, y); } +#endif /// Covariant metric tensor - FieldMetric g_11, g_22, g_33, g_12, g_13, g_23; + const FieldMetric& g_11() const { return covariantMetricTensor.g11(); } + const FieldMetric& g_22() const { return covariantMetricTensor.g22(); } + const FieldMetric& g_33() const { return covariantMetricTensor.g33(); } + const FieldMetric& g_12() const { return covariantMetricTensor.g12(); } + const FieldMetric& g_13() const { return covariantMetricTensor.g13(); } + const FieldMetric& g_23() const { return covariantMetricTensor.g23(); } /// get g_22 at the cell faces; const FieldMetric& g_22_ylow() const; const FieldMetric& g_22_yhigh() const; - FieldMetric& g_22_ylow(); - FieldMetric& g_22_yhigh(); // Cell Areas const FieldMetric& cell_area_xlow() const { if (_cell_area_xlow.has_value()) { @@ -235,26 +291,122 @@ private: void _compute_cell_volume() const; public: - /// Christoffel symbol of the second kind (connection coefficients) - FieldMetric G1_11, G1_22, G1_33, G1_12, G1_13, G1_23; - FieldMetric G2_11, G2_22, G2_33, G2_12, G2_13, G2_23; - FieldMetric G3_11, G3_22, G3_33, G3_12, G3_13, G3_23; + /// Contravariant metric tensor (g^{ij}) + const FieldMetric& g11() const { return contravariantMetricTensor.g11(); } + const FieldMetric& g22() const { return contravariantMetricTensor.g22(); } + const FieldMetric& g33() const { return contravariantMetricTensor.g33(); } + const FieldMetric& g12() const { return contravariantMetricTensor.g12(); } + const FieldMetric& g13() const { return contravariantMetricTensor.g13(); } + const FieldMetric& g23() const { return contravariantMetricTensor.g23(); } + + /// Covariant metric tensor + const BoutReal& g_11(int x, int y, int z) const { + return covariantMetricTensor.g11(x, y, z); + } + const BoutReal& g_22(int x, int y, int z) const { + return covariantMetricTensor.g22(x, y, z); + } + const BoutReal& g_33(int x, int y, int z) const { + return covariantMetricTensor.g33(x, y, z); + } + const BoutReal& g_12(int x, int y, int z) const { + return covariantMetricTensor.g12(x, y, z); + } + const BoutReal& g_13(int x, int y, int z) const { + return covariantMetricTensor.g13(x, y, z); + } + const BoutReal& g_23(int x, int y, int z) const { + return covariantMetricTensor.g23(x, y, z); + } - FieldMetric G1, G2, G3; +#if not(BOUT_USE_METRIC_3D) + const BoutReal& g_11(int x, int y) const { return covariantMetricTensor.g11(x, y); } + const BoutReal& g_22(int x, int y) const { return covariantMetricTensor.g22(x, y); } + const BoutReal& g_33(int x, int y) const { return covariantMetricTensor.g33(x, y); } + const BoutReal& g_12(int x, int y) const { return covariantMetricTensor.g12(x, y); } + const BoutReal& g_13(int x, int y) const { return covariantMetricTensor.g13(x, y); } + const BoutReal& g_23(int x, int y) const { return covariantMetricTensor.g23(x, y); } +#endif + + /// Contravariant metric tensor (g^{ij}) + const BoutReal& g11(int x, int y, int z) const { + return contravariantMetricTensor.g11(x, y, z); + } + const BoutReal& g22(int x, int y, int z) const { + return contravariantMetricTensor.g22(x, y, z); + } + const BoutReal& g33(int x, int y, int z) const { + return contravariantMetricTensor.g33(x, y, z); + } + const BoutReal& g12(int x, int y, int z) const { + return contravariantMetricTensor.g12(x, y, z); + } + const BoutReal& g13(int x, int y, int z) const { + return contravariantMetricTensor.g13(x, y, z); + } + const BoutReal& g23(int x, int y, int z) const { + return contravariantMetricTensor.g23(x, y, z); + } + +#if not(BOUT_USE_METRIC_3D) + const BoutReal& g11(int x, int y) const { return contravariantMetricTensor.g11(x, y); } + const BoutReal& g22(int x, int y) const { return contravariantMetricTensor.g22(x, y); } + const BoutReal& g33(int x, int y) const { return contravariantMetricTensor.g33(x, y); } + const BoutReal& g12(int x, int y) const { return contravariantMetricTensor.g12(x, y); } + const BoutReal& g13(int x, int y) const { return contravariantMetricTensor.g13(x, y); } + const BoutReal& g23(int x, int y) const { return contravariantMetricTensor.g23(x, y); } +#endif + + const ContravariantMetricTensor& getContravariantMetricTensor() const { + return contravariantMetricTensor; + } + + const CovariantMetricTensor& getCovariantMetricTensor() const { + return covariantMetricTensor; + } + + void setContravariantMetricTensor(const ContravariantMetricTensor& metric_tensor, + const std::string& region = "RGN_ALL", + bool recalculate_staggered = true, + bool force_interpolate_from_centre = false); + + void setCovariantMetricTensor(const CovariantMetricTensor& metric_tensor, + const std::string& region = "RGN_ALL", + bool recalculate_staggered = true, + bool force_interpolate_from_centre = false); + + void setMetricTensor(const ContravariantMetricTensor& contravariant_metric_tensor, + const CovariantMetricTensor& covariant_metric_tensor); + + void setMetricTensorJB(const ContravariantMetricTensor& contravariant_metric_tensor, + const CovariantMetricTensor& covariant_metric_tensor, + const FieldMetric& J, const FieldMetric& Bxy); + + void communicateMetricTensor(); + + void communicateDz(); + + void normaliseMetric(const MetricNormaliser& norm); + + ///< Coordinate system Jacobian, so volume of cell is J*dx*dy*dz + const FieldMetric& J() const; + + ///< Magnitude of B = nabla z times nabla x + const FieldMetric& Bxy() const { return Bxy_; } + + void setJ(const FieldMetric& J, bool communicate = true); + + void setBxy(FieldMetric Bxy, bool communicate = true); /// d pitch angle / dx. Needed for vector differentials (Curl) - FieldMetric ShiftTorsion; + const FieldMetric& ShiftTorsion() const { return ShiftTorsion_; } - FieldMetric IntShiftTorsion; ///< Integrated shear (I in BOUT notation) + ///< Integrated shear (I in BOUT notation) + const FieldMetric& IntShiftTorsion() const { return IntShiftTorsion_; } - /// Calculate differential geometry quantities from the metric tensor - int geometry(bool recalculate_staggered = true, - bool force_interpolate_from_centre = false); - /// Invert contravatiant metric to get covariant components - int calcCovariant(const std::string& region = "RGN_ALL"); - /// Invert covariant metric to get contravariant components - int calcContravariant(const std::string& region = "RGN_ALL"); - int jacobian(); ///< Calculate J and Bxy + void setIntShiftTorsion(FieldMetric IntShiftTorsion) { + IntShiftTorsion_ = std::move(IntShiftTorsion); + } /////////////////////////////////////////////////////////// // Parallel transforms @@ -273,123 +425,156 @@ public: return *transform; } - /////////////////////////////////////////////////////////// - // Operators - /////////////////////////////////////////////////////////// + /// Christoffel symbol of the second kind (connection coefficients) + const FieldMetric& G1_11() const { return christoffel_symbols().G1_11(); } + const FieldMetric& G1_22() const { return christoffel_symbols().G1_22(); } + const FieldMetric& G1_33() const { return christoffel_symbols().G1_33(); } + const FieldMetric& G1_12() const { return christoffel_symbols().G1_12(); } + const FieldMetric& G1_13() const { return christoffel_symbols().G1_13(); } + const FieldMetric& G1_23() const { return christoffel_symbols().G1_23(); } + const FieldMetric& G2_11() const { return christoffel_symbols().G2_11(); } + const FieldMetric& G2_22() const { return christoffel_symbols().G2_22(); } + const FieldMetric& G2_33() const { return christoffel_symbols().G2_33(); } + const FieldMetric& G2_12() const { return christoffel_symbols().G2_12(); } + const FieldMetric& G2_13() const { return christoffel_symbols().G2_13(); } + const FieldMetric& G2_23() const { return christoffel_symbols().G2_23(); } + const FieldMetric& G3_11() const { return christoffel_symbols().G3_11(); } + const FieldMetric& G3_22() const { return christoffel_symbols().G3_22(); } + const FieldMetric& G3_33() const { return christoffel_symbols().G3_33(); } + const FieldMetric& G3_12() const { return christoffel_symbols().G3_12(); } + const FieldMetric& G3_13() const { return christoffel_symbols().G3_13(); } + const FieldMetric& G3_23() const { return christoffel_symbols().G3_23(); } + + const FieldMetric& G1() const { return g_values().G1(); } + const FieldMetric& G2() const { return g_values().G2(); } + const FieldMetric& G3() const { return g_values().G3(); } + + const BoutReal& G1(int x, int y, int z) const { return G1()(x, y, z); } + const BoutReal& G2(int x, int y, int z) const { return G2()(x, y, z); } + const BoutReal& G3(int x, int y, int z) const { return G3()(x, y, z); } + +#if not(BOUT_USE_METRIC_3D) + const BoutReal& G1(int x, int y) const { return G1()(x, y); } + const BoutReal& G2(int x, int y) const { return G2()(x, y); } + const BoutReal& G3(int x, int y) const { return G3()(x, y); } +#endif + + const FieldMetric& Grad2_par2_DDY_invSg(CELL_LOC outloc, + const std::string& method) const; + + const FieldMetric& invSg() const; + + const ChristoffelSymbols& christoffel_symbols() const; + + GValues& g_values() const; + + void recalculateAndReset(bool recalculate_staggered, + bool force_interpolate_from_centre); + + FieldMetric recalculateJacobian() const; - FieldMetric DDX(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); - - FieldMetric DDY(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY") const; - - FieldMetric DDZ(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); - - Field3D DDX(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); - - Field3D DDY(const Field3DParallel& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY") const; - - Field3D DDZ(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); - - /// Gradient along magnetic field b.Grad(f) - FieldMetric Grad_par(const Field2D& var, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT"); - - Field3D Grad_par(const Field3DParallel& var, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT"); - - /// Advection along magnetic field V*b.Grad(f) - FieldMetric Vpar_Grad_par(const Field2D& v, const Field2D& f, - CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT"); - - Field3D Vpar_Grad_par(const Field3D& v, const Field3DParallel& f, - CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT"); - - /// Divergence along magnetic field Div(b*f) = B.Grad(f/B) - FieldMetric Div_par(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT"); - - Field3D Div_par(const Field3DParallel& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT"); - - // Second derivative along magnetic field - FieldMetric Grad2_par2(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT"); - - Field3D Grad2_par2(const Field3DParallel& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT"); - // Perpendicular Laplacian operator, using only X-Z derivatives - // NOTE: This might be better bundled with the Laplacian inversion code - // since it makes use of the same coefficients and FFT routines - FieldMetric Delp2(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, bool useFFT = true); - Field3D Delp2(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, bool useFFT = true); - FieldPerp Delp2(const FieldPerp& f, CELL_LOC outloc = CELL_DEFAULT, bool useFFT = true); - - // Full parallel Laplacian operator on scalar field - // Laplace_par(f) = Div( b (b dot Grad(f)) ) - FieldMetric Laplace_par(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT); - Field3D Laplace_par(const Field3DParallel& f, CELL_LOC outloc = CELL_DEFAULT); - - // Full Laplacian operator on scalar field - FieldMetric Laplace(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& dfdy_boundary_conditions = "free_o3", - const std::string& dfdy_dy_region = ""); - Field3D Laplace(const Field3DParallel& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& dfdy_boundary_conditions = "free_o3", - const std::string& dfdy_dy_region = ""); - - // Full perpendicular Laplacian, in form of inverse of Laplacian operator in LaplaceXY - // solver - Field2D Laplace_perpXY(const Field2D& A, const Field2D& f); - - friend std::shared_ptr getYBoundary(Coordinates* coords, YBndryType type); + const bout::boundary::YBoundary& + getYBoundary(YBndryType type = YBndryType::sheath) const; private: - std::shared_ptr makeYBoundary(YBndryType type) const; int nz; // Size of mesh in Z. This is mesh->ngz-1 Mesh* localmesh; - Options* localoptions; + Options* localoptions{nullptr}; CELL_LOC location; + /// True if corrections for non-uniform mesh spacing should be included in operators + bool non_uniform_{}; + + FieldMetric dx_, dy_, dz_; ///< Mesh spacing in x, y and z + + /// 2nd-order correction for non-uniform meshes d/di(1/dx), d/di(1/dy) and d/di(1/dz) + FieldMetric d1_dx_, d1_dy_, d1_dz_; + + /// d pitch angle / dx. Needed for vector differentials (Curl) + FieldMetric ShiftTorsion_; + + ///< Integrated shear (I in BOUT notation) + FieldMetric IntShiftTorsion_; + /// Handles calculation of yup and ydown std::unique_ptr transform{nullptr}; /// Cache variable for `zlength`. Invalidated when - /// `Coordinates::geometry` is called + /// `Coordinates::recalculateAndReset` is called mutable std::unique_ptr zlength_cache{nullptr}; /// Cache variable for Grad2_par2 mutable std::map> Grad2_par2_DDY_invSgCache; mutable std::unique_ptr invSgCache{nullptr}; + ContravariantMetricTensor contravariantMetricTensor; + CovariantMetricTensor covariantMetricTensor; + + /// Christoffel symbol of the second kind (connection coefficients) + mutable std::unique_ptr christoffel_symbols_cache{nullptr}; + + /// `g_values` needs renaming, when we know what the name should be + mutable std::unique_ptr g_values_cache{nullptr}; + + mutable std::unique_ptr jacobian_cache{nullptr}; + + FieldMetric Bxy_; ///< Magnitude of B = nabla z times nabla x + /// Set the parallel (y) transform from the options file. /// Used in the constructor to create the transform object. void setParallelTransform(Options* options); - const FieldMetric& invSg() const; - const FieldMetric& Grad2_par2_DDY_invSg(CELL_LOC outloc, - const std::string& method) const; - // check that covariant tensors are positive (if expected) and finite (always) void checkCovariant(); // check that contravariant tensors are positive (if expected) and finite (always) void checkContravariant(); + void invalidateMetricCaches(); + void invalidateJacobianCaches(); + void invalidateCellGeometryCaches(); + void invalidateAccessorCache() const; + + mutable std::array, 3> ybndrys; + + FieldMetric recalculateBxy() const; + + /// Non-uniform meshes. Need to use DDX, DDY + void correctionForNonUniformMeshes(bool force_interpolate_from_centre); + + void interpolateFromCoordinates(Options* options, const Coordinates* coords_in); - mutable std::array, 3> ybndrys; /// Read quantities with given suffix from `Mesh` void readFromMesh(Options* options, const std::string& suffix); + + /// Read parallel slices of metric components from `Mesh` + void readParallelMetricComponents(); + +protected: + /// For testing purposes only; inherit and make this public + void splitBxyParallelSlices(); +}; + +namespace bout { +std::string parallelSliceFieldName(std::string_view field, int offset); +} + +/// Represents a way to normalise the coordinate system +/// If a component returns nothing, no normalisation is performed. +/// Coordinate values are divided by the respective component from +/// MetricNormaliser, with the exception of the contravariant metric +/// tensor, which is multiplied by the normalisation factor. +struct MetricNormaliser { + std::optional g = std::nullopt; + std::optional g11 = std::nullopt; + std::optional g22 = std::nullopt; + std::optional g33 = std::nullopt; + std::optional g12 = std::nullopt; + std::optional g13 = std::nullopt; + std::optional g23 = std::nullopt; + std::optional dx = std::nullopt; + std::optional dy = std::nullopt; + std::optional dz = std::nullopt; + std::optional J = std::nullopt; + std::optional Bxy = std::nullopt; }; #endif // BOUT_COORDINATES_H diff --git a/include/bout/derivs.hxx b/include/bout/derivs.hxx index 14bc5c2824..37cb6e1911 100644 --- a/include/bout/derivs.hxx +++ b/include/bout/derivs.hxx @@ -29,12 +29,14 @@ #ifndef BOUT_DERIVS_H #define BOUT_DERIVS_H +#include "bout/bout_types.hxx" #include "bout/field2d.hxx" #include "bout/field3d.hxx" +#include "bout/metric_tensor.hxx" #include "bout/vector2d.hxx" #include "bout/vector3d.hxx" -#include "bout/bout_types.hxx" +#include #include ////////// FIRST DERIVATIVES ////////// @@ -67,9 +69,9 @@ Field3D DDX(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric DDX(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric DDX(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// Calculate first partial derivative in Y /// @@ -91,7 +93,7 @@ template std::enable_if_t && !bout::utils::is_Field3D_v, const Field3D> DDY(const E& expr, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT", const std::string& region = "RGN_NOBNDRY") { - return DDY(Field3D{expr}, outloc, method, region); + return DDY(Field3D{Field3DParallel{expr}}, outloc, method, region); } /// Calculate first partial derivative in Y @@ -106,9 +108,9 @@ DDY(const E& expr, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = " /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric DDY(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric DDY(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// Calculate first partial derivative in Z /// @@ -138,9 +140,9 @@ Field3D DDZ(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric DDZ(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric DDZ(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// Calculate first partial derivative in Z /// @@ -154,7 +156,7 @@ Coordinates::FieldMetric DDZ(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Vector3D DDZ(const Vector3D& f, CELL_LOC outloc = CELL_DEFAULT, +Vector3D DDZ(const Vector3D& v, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT", const std::string& region = "RGN_NOBNDRY"); @@ -170,7 +172,7 @@ Vector3D DDZ(const Vector3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Vector2D DDZ(const Vector2D& f, CELL_LOC outloc = CELL_DEFAULT, +Vector2D DDZ(const Vector2D& v, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT", const std::string& region = "RGN_NOBNDRY"); @@ -204,9 +206,9 @@ Field3D D2DX2(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric D2DX2(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric D2DX2(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// Calculate second partial derivative in Y /// @@ -236,9 +238,9 @@ Field3D D2DY2(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric D2DY2(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric D2DY2(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// Calculate second partial derivative in Z /// @@ -268,9 +270,9 @@ Field3D D2DZ2(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric D2DZ2(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric D2DZ2(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); ////////// FOURTH DERIVATIVES ////////// @@ -302,9 +304,9 @@ Field3D D4DX4(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric D4DX4(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric D4DX4(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// Calculate forth partial derivative in Y /// @@ -334,9 +336,9 @@ Field3D D4DY4(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric D4DY4(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric D4DY4(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// Calculate forth partial derivative in Z /// @@ -366,9 +368,9 @@ Field3D D4DZ4(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric D4DZ4(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric D4DZ4(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// For terms of form v * grad(f) /// @@ -400,10 +402,9 @@ Field3D VDDX(const Field3D& v, const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric VDDX(const Field2D& v, const Field2D& f, - CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric VDDX(const Field2D& v, const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// For terms of form v * grad(f) /// @@ -435,10 +436,9 @@ Field3D VDDY(const Field3D& v, const Field3DParallel& f, CELL_LOC outloc = CELL_ /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric VDDY(const Field2D& v, const Field2D& f, - CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric VDDY(const Field2D& v, const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// For terms of form v * grad(f) /// @@ -470,10 +470,9 @@ Field3D VDDZ(const Field3D& v, const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric VDDZ(const Field2D& v, const Field2D& f, - CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric VDDZ(const Field2D& v, const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// For terms of form v * grad(f) /// @@ -488,10 +487,9 @@ Coordinates::FieldMetric VDDZ(const Field2D& v, const Field2D& f, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric VDDZ(const Field3D& v, const Field2D& f, - CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric VDDZ(const Field3D& v, const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// for terms of form div(v * f) /// @@ -523,10 +521,9 @@ Field3D FDDX(const Field3D& v, const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric FDDX(const Field2D& v, const Field2D& f, - CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric FDDX(const Field2D& v, const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// for terms of form div(v * f) /// @@ -558,10 +555,9 @@ Field3D FDDY(const Field3D& v, const Field3DParallel& f, CELL_LOC outloc = CELL_ /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric FDDY(const Field2D& v, const Field2D& f, - CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric FDDY(const Field2D& v, const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// for terms of form div(v * f) /// @@ -593,10 +589,9 @@ Field3D FDDZ(const Field3D& v, const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric FDDZ(const Field2D& v, const Field2D& f, - CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric FDDZ(const Field2D& v, const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// Calculate mixed partial derivative in x and y /// @@ -639,11 +634,11 @@ Field3D D2DXDY(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// (default) then the same as the region for the calculation as a /// whole. If dfdy_region < region in size then this will cause /// errors. -Coordinates::FieldMetric D2DXDY(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY", - const std::string& dfdy_boundary_condition = "free_o3", - const std::string& dfdy_region = ""); +bout::FieldMetric D2DXDY(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY", + const std::string& dfdy_boundary_condition = "free_o3", + const std::string& dfdy_region = ""); /// Calculate mixed partial derivative in x and z /// @@ -673,9 +668,9 @@ Field3D D2DXDZ(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric D2DXDZ(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric D2DXDZ(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); /// Calculate mixed partial derivative in y and z /// @@ -705,8 +700,8 @@ Field3D D2DYDZ(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, /// If not given, defaults to DIFF_DEFAULT /// @param[in] region What region is expected to be calculated /// If not given, defaults to RGN_NOBNDRY -Coordinates::FieldMetric D2DYDZ(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT", - const std::string& region = "RGN_NOBNDRY"); +bout::FieldMetric D2DYDZ(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT", + const std::string& region = "RGN_NOBNDRY"); #endif // BOUT_DERIVS_H diff --git a/include/bout/difops.hxx b/include/bout/difops.hxx index 28a4da9f51..0b3320c1f1 100644 --- a/include/bout/difops.hxx +++ b/include/bout/difops.hxx @@ -36,11 +36,15 @@ #ifndef BOUT_DIFOPS_H #define BOUT_DIFOPS_H +#include "bout/bout_enum_class.hxx" +#include "bout/bout_types.hxx" +#include "bout/coordinates.hxx" #include "bout/field2d.hxx" #include "bout/field3d.hxx" +#include "bout/metric_tensor.hxx" -#include "bout/bout_types.hxx" -#include "bout/coordinates.hxx" +#include +#include class Solver; @@ -53,14 +57,14 @@ class Solver; * enabled) * @param[in] method The method to use. The default is set in the options. */ -Coordinates::FieldMetric Grad_par(const Field2D& var, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT"); -inline Coordinates::FieldMetric Grad_par(const Field2D& var, CELL_LOC outloc, - DIFF_METHOD method) { +bout::FieldMetric Grad_par(const Field2D& var, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT"); +inline bout::FieldMetric Grad_par(const Field2D& var, CELL_LOC outloc, + DIFF_METHOD method) { return Grad_par(var, outloc, toString(method)); } -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"); /*! @@ -73,7 +77,7 @@ Field3D Grad_par(const Field3D& var, CELL_LOC outloc = CELL_DEFAULT, * Combines the parallel and perpendicular calculation to include * grid-points at the corners. */ -Field3D Grad_parP(const Field3D& apar, const Field3D& f); +Field3D Grad_parP(const Field3D& apar, const Field3DParallel& f); /*! * vpar times parallel derivative along unperturbed B-field (upwinding) @@ -89,20 +93,21 @@ Field3D Grad_parP(const Field3D& apar, const Field3D& f); * @param[in] method The numerical method to use. The default is set in the options * */ -Coordinates::FieldMetric Vpar_Grad_par(const Field2D& v, const Field2D& f, - CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT"); +bout::FieldMetric Vpar_Grad_par(const Field2D& v, const Field2D& f, + CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT"); -inline Coordinates::FieldMetric Vpar_Grad_par(const Field2D& v, const Field2D& f, - CELL_LOC outloc, DIFF_METHOD method) { +inline bout::FieldMetric Vpar_Grad_par(const Field2D& v, const Field2D& f, + CELL_LOC outloc, DIFF_METHOD method) { return Vpar_Grad_par(v, f, outloc, toString(method)); } -Field3D Vpar_Grad_par(const Field3D& v, const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, +Field3D Vpar_Grad_par(const Field3DParallel& v, const Field3DParallel& f, + CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT"); -inline Field3D Vpar_Grad_par(const Field3D& v, const Field3D& f, CELL_LOC outloc, - DIFF_METHOD method) { +inline Field3D Vpar_Grad_par(const Field3DParallel& v, const Field3DParallel& f, + CELL_LOC outloc, DIFF_METHOD method) { return Vpar_Grad_par(v, f, outloc, toString(method)); } @@ -118,18 +123,17 @@ inline Field3D Vpar_Grad_par(const Field3D& v, const Field3D& f, CELL_LOC outloc * @param[in] method The numerical method to use * */ -Coordinates::FieldMetric Div_par(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT"); +bout::FieldMetric Div_par(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT"); -inline Coordinates::FieldMetric Div_par(const Field2D& f, CELL_LOC outloc, - DIFF_METHOD method) { +inline bout::FieldMetric Div_par(const Field2D& f, CELL_LOC outloc, DIFF_METHOD method) { return Div_par(f, outloc, toString(method)); } -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"); -inline Field3D Div_par(const Field3D& f, CELL_LOC outloc, DIFF_METHOD method) { +inline Field3D Div_par(const Field3DParallel& f, CELL_LOC outloc, DIFF_METHOD method) { return Div_par(f, outloc, toString(method)); } @@ -137,15 +141,16 @@ inline Field3D Div_par(const Field3D& f, CELL_LOC outloc, DIFF_METHOD method) { // Both f and v are interpolated onto cell boundaries // using 2nd order central difference, then multiplied together // to get the flux at the boundary. -Field3D Div_par(const Field3D& f, const Field3D& v); +Field3D Div_par(const Field3DParallel& f, const Field3DParallel& v); // Flux methods. Model divergence of flux: df/dt = Div(v * f) // TODO : Should we add Field2D versions? -Field3D Div_par_flux(const Field3D& v, const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, +Field3D Div_par_flux(const Field3DParallel& v, const Field3DParallel& f, + CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT"); -inline Field3D Div_par_flux(const Field3D& v, const Field3D& f, CELL_LOC outloc, - DIFF_METHOD method) { +inline Field3D Div_par_flux(const Field3DParallel& v, const Field3DParallel& f, + CELL_LOC outloc, DIFF_METHOD method) { return Div_par_flux(v, f, outloc, toString(method)); } @@ -160,16 +165,16 @@ inline Field3D Div_par_flux(const Field3D& v, const Field3D& f, CELL_LOC outloc, * @param[in] f The field to be differentiated * @param[in] outloc The cell location of the result */ -Coordinates::FieldMetric Grad2_par2(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& method = "DEFAULT"); -inline Coordinates::FieldMetric Grad2_par2(const Field2D& f, CELL_LOC outloc, - DIFF_METHOD method) { +bout::FieldMetric Grad2_par2(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& method = "DEFAULT"); +inline bout::FieldMetric Grad2_par2(const Field2D& f, CELL_LOC outloc, + DIFF_METHOD method) { return Grad2_par2(f, outloc, toString(method)); } -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"); -inline Field3D Grad2_par2(const Field3D& f, CELL_LOC outloc, DIFF_METHOD method) { +inline Field3D Grad2_par2(const Field3DParallel& f, CELL_LOC outloc, DIFF_METHOD method) { return Grad2_par2(f, outloc, toString(method)); } @@ -183,22 +188,43 @@ inline Field3D Grad2_par2(const Field3D& f, CELL_LOC outloc, DIFF_METHOD method) * @param[in] kY The diffusion coefficient * @param[in] f The field whose gradient drives a flux */ -Coordinates::FieldMetric Div_par_K_Grad_par(BoutReal kY, const Field2D& f, - CELL_LOC outloc = CELL_DEFAULT); -Field3D Div_par_K_Grad_par(BoutReal kY, const Field3D& f, CELL_LOC outloc = CELL_DEFAULT); -Coordinates::FieldMetric Div_par_K_Grad_par(const Field2D& kY, const Field2D& f, - CELL_LOC outloc = CELL_DEFAULT); -Field3D Div_par_K_Grad_par(const Field2D& kY, const Field3D& f, +bout::FieldMetric Div_par_K_Grad_par(BoutReal kY, const Field2D& f, + CELL_LOC outloc = CELL_DEFAULT); +Field3D Div_par_K_Grad_par(BoutReal kY, const Field3DParallel& f, + CELL_LOC outloc = CELL_DEFAULT); +bout::FieldMetric Div_par_K_Grad_par(const Field2D& kY, const Field2D& f, + CELL_LOC outloc = CELL_DEFAULT); +Field3D Div_par_K_Grad_par(const Field2D& kY, const Field3DParallel& f, CELL_LOC outloc = CELL_DEFAULT); -Field3D Div_par_K_Grad_par(const Field3D& kY, const Field2D& f, +Field3D Div_par_K_Grad_par(const Field3DParallel& kY, const Field2D& f, CELL_LOC outloc = CELL_DEFAULT); -Field3D Div_par_K_Grad_par(const Field3D& kY, const Field3D& f, +Field3D Div_par_K_Grad_par(const Field3DParallel& kY, const Field3DParallel& f, CELL_LOC outloc = CELL_DEFAULT); +/// enum class bout::ConductionMethod +/// --------------------------------- +/// +/// Original: +/// Separately averages :math:`K`, :math:`J`, :math:`g_{22}`, and +/// :math:`dy` at the face and then multiplies them together. +/// +/// ProductJK: +/// Uses the same stencil as ``Original`` but averages :math:`J K` +/// together before applying the face gradient. +/// +/// Harmonic: +/// Uses a harmonic average of the half-cell conductances +/// :math:`K J / (g_{22} dy)`. This better matches a series-resistance +/// interpretation of two adjacent half-cells and can give noticeably +/// different results when coefficients or cell sizes vary strongly. +BOUT_ENUM_CLASS_NS(bout, ConductionMethod, Original, ProductJK, Harmonic); + /// Version with energy flow diagnostic /// For FCI fields, `flow_ylow` is currently returned as zero. -Field3D Div_par_K_Grad_par_mod(const Field3D& k, const Field3D& f, Field3D& flow_ylow, - bool bndry_flux = true); +Field3D +Div_par_K_Grad_par_mod(const Field3DParallel& k, const Field3DParallel& f, + Field3D& flow_ylow, bool bndry_flux = true, + bout::ConductionMethod method = bout::ConductionMethod::Original); /*! * Perpendicular Laplacian operator @@ -209,8 +235,8 @@ Field3D Div_par_K_Grad_par_mod(const Field3D& k, const Field3D& f, Field3D& flow * * For the full perpendicular Laplacian, use Laplace_perp */ -Coordinates::FieldMetric Delp2(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - bool useFFT = true); +bout::FieldMetric Delp2(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + bool useFFT = true); Field3D Delp2(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, bool useFFT = true); FieldPerp Delp2(const FieldPerp& f, CELL_LOC outloc = CELL_DEFAULT, bool useFFT = true); @@ -219,10 +245,9 @@ FieldPerp Delp2(const FieldPerp& f, CELL_LOC outloc = CELL_DEFAULT, bool useFFT * * */ -Coordinates::FieldMetric -Laplace_perp(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& dfdy_boundary_condition = "free_o3", - const std::string& dfdy_region = ""); +bout::FieldMetric Laplace_perp(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& dfdy_boundary_condition = "free_o3", + const std::string& dfdy_region = ""); Field3D Laplace_perp(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& dfdy_boundary_condition = "free_o3", const std::string& dfdy_region = ""); @@ -231,16 +256,16 @@ Field3D Laplace_perp(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, * Parallel Laplacian operator * */ -Coordinates::FieldMetric Laplace_par(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT); -Field3D Laplace_par(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT); +bout::FieldMetric Laplace_par(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT); +Field3D Laplace_par(const Field3DParallel& f, CELL_LOC outloc = CELL_DEFAULT); /*! * Full Laplacian operator (par + perp) */ -Coordinates::FieldMetric Laplace(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, - const std::string& dfdy_boundary_condition = "free_o3", - const std::string& dfdy_region = ""); -Field3D Laplace(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, +bout::FieldMetric Laplace(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, + const std::string& dfdy_boundary_condition = "free_o3", + const std::string& dfdy_region = ""); +Field3D Laplace(const Field3DParallel& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& dfdy_boundary_condition = "free_o3", const std::string& dfdy_region = ""); @@ -253,8 +278,8 @@ Field2D Laplace_perpXY(const Field2D& A, const Field2D& f); * Terms of form b0 x Grad(phi) dot Grad(A) * */ -Coordinates::FieldMetric b0xGrad_dot_Grad(const Field2D& phi, const Field2D& A, - CELL_LOC outloc = CELL_DEFAULT); +bout::FieldMetric b0xGrad_dot_Grad(const Field2D& phi, const Field2D& A, + CELL_LOC outloc = CELL_DEFAULT); /*! * Terms of form @@ -267,11 +292,11 @@ Coordinates::FieldMetric b0xGrad_dot_Grad(const Field2D& phi, const Field2D& A, * @param[in] A The field being advected * @param[in] outloc The cell location where the result is defined. By default the same as A. */ -Field3D b0xGrad_dot_Grad(const Field3D& phi, const Field2D& A, +Field3D b0xGrad_dot_Grad(const Field3DParallel& phi, const Field2D& A, CELL_LOC outloc = CELL_DEFAULT); Field3D b0xGrad_dot_Grad(const Field2D& phi, const Field3D& A, CELL_LOC outloc = CELL_DEFAULT); -Field3D b0xGrad_dot_Grad(const Field3D& phi, const Field3D& A, +Field3D b0xGrad_dot_Grad(const Field3DParallel& phi, const Field3D& A, CELL_LOC outloc = CELL_DEFAULT); /*! @@ -304,10 +329,9 @@ constexpr BRACKET_METHOD BRACKET_CTU = BRACKET_METHOD::ctu; * @param[in] solver Pointer to the time integration solver * */ -Coordinates::FieldMetric bracket(const Field2D& f, const Field2D& g, - BRACKET_METHOD method = BRACKET_STD, - CELL_LOC outloc = CELL_DEFAULT, - Solver* solver = nullptr); +bout::FieldMetric bracket(const Field2D& f, const Field2D& g, + BRACKET_METHOD method = BRACKET_STD, + CELL_LOC outloc = CELL_DEFAULT, Solver* solver = nullptr); Field3D bracket(const Field2D& f, const Field3D& g, BRACKET_METHOD method = BRACKET_STD, CELL_LOC outloc = CELL_DEFAULT, Solver* solver = nullptr); Field3D bracket(const Field3D& f, const Field2D& g, BRACKET_METHOD method = BRACKET_STD, diff --git a/include/bout/expr.hxx b/include/bout/expr.hxx deleted file mode 100644 index 267af202ed..0000000000 --- a/include/bout/expr.hxx +++ /dev/null @@ -1,208 +0,0 @@ -/************************************************************************** - * - * Operators, and support for template expressions - * - * Originally based on article by Klaus Kreft & Angelika Langer - * http://www.angelikalanger.com/Articles/Cuj/ExpressionTemplates/ExpressionTemplates.htm - * - * Parts adapted from Blitz++ library - * - **************************************************************************/ - -#ifndef BOUT_EXPR_H -#define BOUT_EXPR_H - -#warning expr.hxx is deprecated. Do not use! - -#include -#include -#include - -/// Literal class to capture BoutReal values in expressions -class Literal { -public: - /// Type of this expression - using type = Literal; - - Literal(BoutReal v) : val(v) {} - ~Literal() {} - BoutReal operator()(int x, int y, int z) const { return val; } - -private: - const BoutReal val; -}; - -class Field3DExpr { -public: - using type = Field3D; - - Field3DExpr(const Field3D& f) : data(&f(0, 0, 0)) {} - const BoutReal& operator()(int x, int y, int z) const { - return data[(x * bout::globals::mesh->LocalNy + y) * bout::globals::mesh->LocalNz - + z]; - } - -private: - const BoutReal* data; -}; - -class Field2DExpr { -public: - using type = Field2D; - - Field2DExpr(const Field2D& f) : data(&f(0, 0)) {} - const BoutReal& operator()(int x, int y, int z) const { - return data[x * bout::globals::mesh->LocalNy + y]; - } - -private: - const BoutReal* data; -}; - -/// Expression traits, to convert doubles etc. to Literal - -template -struct exprTraits { - using expr_type = ExprT; -}; - -template <> -struct exprTraits { - using expr_type = Literal; -}; - -template <> -struct exprTraits { - using expr_type = Literal; -}; - -template <> -struct exprTraits { - using expr_type = Literal; -}; - -/////////////////////////////////////////////// -// asExpr: convert objects to expressions - -template -struct asExpr { - using type = T; - static const T& getExpr(const T& x) { return x; } -}; - -template <> -struct asExpr { - using type = Literal; - static const Literal getExpr(const int& x) { return Literal(x); } -}; - -template <> -struct asExpr { - using type = Literal; - static const Literal getExpr(const double& x) { return Literal(x); } -}; - -template <> -struct asExpr { - using type = Literal; - static const Literal getExpr(const float& x) { return Literal(x); } -}; - -template <> -struct asExpr { - using type = Field3DExpr; - static const Field3DExpr getExpr(const Field3D& x) { return Field3DExpr(x); } -}; - -///////////////////////////////////////////////////////////// -// Type promotion. Work out the type of a calculation, -// based on the type of the arguments - -template // If in doubt, convert to Field3D -struct PromoteType { - using type = Field3D; -}; - -///////////////////////////////////////////////////////////// -// Binary expressions - -template -class BinaryExpr { -public: - BinaryExpr(const ExprT1& e1, const ExprT2& e2) : _expr1(e1), _expr2(e2) {} - - // Work out the type of the inputs - using ltype = typename exprTraits::expr_type; - using rtype = typename exprTraits::expr_type; - - /// Type of the resulting expression - using type = typename PromoteType::type; - - BoutReal operator()(int x, int y, int z) const { - return BinOp::apply((_expr1)(x, y, z), (_expr2)(x, y, z)); - } - -private: - ltype const _expr1; - rtype const _expr2; -}; - -template -struct BinaryResult { - using arg1 = typename asExpr::type; - using arg2 = typename asExpr::type; - using type = BinaryExpr; -}; - -/// Binary operator classes - -#define DEFINE_BINARY_OP(name, op) \ - struct name { \ - template \ - static inline T apply(T a, T b) { \ - return a op b; \ - } \ - }; - -DEFINE_BINARY_OP(Add, +) -DEFINE_BINARY_OP(Subtract, -) -DEFINE_BINARY_OP(Multiply, *) -DEFINE_BINARY_OP(Divide, /) - -struct Power { - template - static inline T apply(T a, T b) { - return pow(a, b); - } -}; - -/// Define functions add, mul which use operator structs -#define DEFINE_OVERLOAD_FUNC(name, func) \ - template \ - typename BinaryResult::type func(const ExprT1& e1, \ - const ExprT2& e2) { \ - using type = typename BinaryResult::type; \ - return type(asExpr::getExpr(e1), asExpr::getExpr(e2)); \ - } - -/// Addition of two Expressions -DEFINE_OVERLOAD_FUNC(Add, add); -/// Multiplication of two Expressions -DEFINE_OVERLOAD_FUNC(Multiply, mul); - -/// A function to evaluate expressions -template -const Field3D eval3D(Expr e) { - Field3D result; - result.allocate(); - for (int i = 0; i < bout::globals::mesh->LocalNx; i++) { - for (int j = 0; j < bout::globals::mesh->LocalNy; j++) { - for (int k = 0; k < bout::globals::mesh->LocalNz; k++) { - result(i, j, k) = e(i, j, k); - } - } - } - return result; -} - -#endif // BOUT_EXPR_H diff --git a/include/bout/field.hxx b/include/bout/field.hxx index b39a82eb0b..8ab6f09f65 100644 --- a/include/bout/field.hxx +++ b/include/bout/field.hxx @@ -33,10 +33,12 @@ class Field; #include #include #include +#include #include "bout/bout_types.hxx" #include "bout/boutcomm.hxx" #include "bout/boutexception.hxx" +#include "bout/build_config.hxx" #include "bout/field_data.hxx" #include "bout/region.hxx" #include "bout/traits.hxx" @@ -134,12 +136,15 @@ public: virtual void setRegion([[maybe_unused]] std::optional regionID) {} virtual void setRegion([[maybe_unused]] const std::string& region_name) {} virtual void resetRegion() {} + virtual void resetRegionParallel([[maybe_unused]] bool force) {}; virtual std::optional getRegionID() const { return {}; } virtual bool hasParallelSlices() const { return true; } virtual void calcParallelSlices() {} virtual void splitParallelSlices() {} virtual void clearParallelSlices() {} virtual size_t numberParallelSlices() const { return 0; } + virtual bool areCalcParallelSlicesAllowed() const { return false; } + virtual void disallowCalcParallelSlices() {} private: /// Labels for the type of coordinate system this field is defined over @@ -539,53 +544,233 @@ inline BoutReal mean(const BinaryExpr& f, bool allpe = false, return bout::reduce::Mean::finalize(state); } +namespace bout::op { +struct Pow { + template + BOUT_HOST_DEVICE BOUT_FORCEINLINE BoutReal operator()(int idx, const LView& L, + const RView& R) const { + return ::pow(L(idx), R(idx)); + } + BOUT_HOST_DEVICE BOUT_FORCEINLINE BoutReal operator()(BoutReal a, BoutReal b) const { + return ::pow(a, b); + } +}; +}; // namespace bout::op + +namespace bout::detail { +template +std::optional getPerpYIndex(const T& value); + +template +std::optional getPerpYIndex(const BinaryExpr& expr); + +template +std::optional getPowRegionID(const Mesh* mesh, const std::string& region_name) { + if constexpr (std::is_same_v) { + return bout::detail::getField3DRegionID(mesh, region_name); + } else { + return std::nullopt; + } +} + +template +auto makePowExpr(const LView& lhs_view, const RView& rhs_view, Mesh* mesh, + CELL_LOC location, DirectionTypes directions, + std::optional regionID, const Region& region, + std::optional yindex = std::nullopt) { + return BinaryExpr{lhs_view, rhs_view, bout::op::Pow{}, + mesh, location, directions, + regionID, region, yindex}; +} +} // namespace bout::detail + /// Exponent: pow(lhs, lhs) is \p lhs raised to the power of \p rhs /// /// This loops over the entire domain, including guard/boundary cells by /// default (can be changed using the \p rgn argument) /// If CHECK >= 3 then the result will be checked for non-finite numbers -template > -T pow(const T& lhs, const T& rhs, const std::string& rgn = "RGN_ALL") { +template +std::enable_if_t && is_expr_field2d_v, + BinaryExpr> +pow(const L& lhs, const R& rhs) { + ASSERT1_EXPR_COMPATIBLE(lhs, rhs); + return bout::detail::makePowExpr( + static_cast(lhs), static_cast(rhs), + lhs.getMesh(), lhs.getLocation(), lhs.getDirections(), std::nullopt, + lhs.getMesh()->getRegion2D("RGN_ALL")); +} + +template +std::enable_if_t && is_expr_field2d_v, + BinaryExpr> +pow(const L& lhs, const R& rhs, const std::string& rgn) { + ASSERT1_EXPR_COMPATIBLE(lhs, rhs); + return bout::detail::makePowExpr( + static_cast(lhs), static_cast(rhs), + lhs.getMesh(), lhs.getLocation(), lhs.getDirections(), std::nullopt, + lhs.getMesh()->getRegion2D(rgn)); +} - ASSERT1(areFieldsCompatible(lhs, rhs)); +template +std::enable_if_t && is_expr_field3d_v, + BinaryExpr> +pow(const L& lhs, const R& rhs) { + ASSERT1_EXPR_COMPATIBLE(lhs, rhs); + auto regionID = lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID()); + return bout::detail::makePowExpr( + static_cast(lhs), static_cast(rhs), + lhs.getMesh(), lhs.getLocation(), lhs.getDirections(), regionID, + (regionID.has_value() ? lhs.getMesh()->getRegion(regionID.value()) + : lhs.getMesh()->getRegion("RGN_ALL")), + bout::detail::getPerpYIndex(lhs)); +} - T result{emptyFrom(lhs)}; +template +std::enable_if_t && is_expr_field3d_v, + BinaryExpr> +pow(const L& lhs, const R& rhs, const std::string& rgn) { + ASSERT1_EXPR_COMPATIBLE(lhs, rhs); + return bout::detail::makePowExpr( + static_cast(lhs), static_cast(rhs), + lhs.getMesh(), lhs.getLocation(), lhs.getDirections(), + bout::detail::getPowRegionID(lhs.getMesh(), rgn), + lhs.getMesh()->getRegion(rgn), bout::detail::getPerpYIndex(lhs)); +} - BOUT_FOR(i, result.getRegion(rgn)) { result[i] = ::pow(lhs[i], rhs[i]); } +template +std::enable_if_t && is_expr_field2d_v, + BinaryExpr> +pow(const L& lhs, const R& rhs) { + ASSERT1_EXPR_COMPATIBLE(lhs, rhs); + int mesh_nz = lhs.getMesh()->LocalNz; + return bout::detail::makePowExpr( + static_cast(lhs), + static_cast(rhs).setScale(1, mesh_nz), lhs.getMesh(), + lhs.getLocation(), lhs.getDirections(), lhs.getRegionID(), + lhs.getMesh()->getRegion("RGN_ALL"), bout::detail::getPerpYIndex(lhs)); +} - checkData(result); - return result; +template +std::enable_if_t && is_expr_field2d_v, + BinaryExpr> +pow(const L& lhs, const R& rhs, const std::string& rgn) { + ASSERT1_EXPR_COMPATIBLE(lhs, rhs); + int mesh_nz = lhs.getMesh()->LocalNz; + return bout::detail::makePowExpr( + static_cast(lhs), + static_cast(rhs).setScale(1, mesh_nz), lhs.getMesh(), + lhs.getLocation(), lhs.getDirections(), + bout::detail::getPowRegionID(lhs.getMesh(), rgn), + lhs.getMesh()->getRegion(rgn), bout::detail::getPerpYIndex(lhs)); } -template > -T pow(const T& lhs, BoutReal rhs, const std::string& rgn = "RGN_ALL") { +template +std::enable_if_t && is_expr_field3d_v, + BinaryExpr> +pow(const L& lhs, const R& rhs) { + ASSERT1_EXPR_COMPATIBLE(lhs, rhs); + int mesh_nz = rhs.getMesh()->LocalNz; + return bout::detail::makePowExpr( + static_cast(lhs).setScale(1, mesh_nz), + static_cast(rhs), rhs.getMesh(), rhs.getLocation(), + rhs.getDirections(), rhs.getRegionID(), rhs.getMesh()->getRegion("RGN_ALL"), + bout::detail::getPerpYIndex(rhs)); +} - // Check if the inputs are allocated - checkData(lhs); - checkData(rhs); +template +std::enable_if_t && is_expr_field3d_v, + BinaryExpr> +pow(const L& lhs, const R& rhs, const std::string& rgn) { + ASSERT1_EXPR_COMPATIBLE(lhs, rhs); + int mesh_nz = rhs.getMesh()->LocalNz; + return bout::detail::makePowExpr( + static_cast(lhs).setScale(1, mesh_nz), + static_cast(rhs), rhs.getMesh(), rhs.getLocation(), + rhs.getDirections(), bout::detail::getPowRegionID(rhs.getMesh(), rgn), + rhs.getMesh()->getRegion(rgn), bout::detail::getPerpYIndex(rhs)); +} - T result{emptyFrom(lhs)}; +template +std::enable_if_t && is_expr_constant_v, + BinaryExpr, bout::op::Pow>> +pow(const L& lhs, R rhs) { + return bout::detail::makePowExpr>( + static_cast(lhs), static_cast::View>(rhs), + lhs.getMesh(), lhs.getLocation(), lhs.getDirections(), std::nullopt, + lhs.getMesh()->getRegion2D("RGN_ALL")); +} - BOUT_FOR(i, result.getRegion(rgn)) { result[i] = ::pow(lhs[i], rhs); } +template +std::enable_if_t && is_expr_constant_v, + BinaryExpr, bout::op::Pow>> +pow(const L& lhs, R rhs, const std::string& rgn) { + return bout::detail::makePowExpr>( + static_cast(lhs), static_cast::View>(rhs), + lhs.getMesh(), lhs.getLocation(), lhs.getDirections(), std::nullopt, + lhs.getMesh()->getRegion2D(rgn)); +} - checkData(result); - return result; +template +std::enable_if_t && is_expr_field2d_v, + BinaryExpr, R, bout::op::Pow>> +pow(L lhs, const R& rhs) { + return bout::detail::makePowExpr, R>( + static_cast::View>(lhs), static_cast(rhs), + rhs.getMesh(), rhs.getLocation(), rhs.getDirections(), std::nullopt, + rhs.getMesh()->getRegion2D("RGN_ALL")); } -template > -T pow(BoutReal lhs, const T& rhs, const std::string& rgn = "RGN_ALL") { +template +std::enable_if_t && is_expr_field2d_v, + BinaryExpr, R, bout::op::Pow>> +pow(L lhs, const R& rhs, const std::string& rgn) { + return bout::detail::makePowExpr, R>( + static_cast::View>(lhs), static_cast(rhs), + rhs.getMesh(), rhs.getLocation(), rhs.getDirections(), std::nullopt, + rhs.getMesh()->getRegion2D(rgn)); +} - // Check if the inputs are allocated - checkData(lhs); - checkData(rhs); +template +std::enable_if_t && is_expr_constant_v, + BinaryExpr, bout::op::Pow>> +pow(const L& lhs, R rhs) { + return bout::detail::makePowExpr>( + static_cast(lhs), static_cast::View>(rhs), + lhs.getMesh(), lhs.getLocation(), lhs.getDirections(), lhs.getRegionID(), + lhs.getMesh()->getRegion("RGN_ALL"), bout::detail::getPerpYIndex(lhs)); +} - // Define and allocate the output result - T result{emptyFrom(rhs)}; +template +std::enable_if_t && is_expr_constant_v, + BinaryExpr, bout::op::Pow>> +pow(const L& lhs, R rhs, const std::string& rgn) { + return bout::detail::makePowExpr>( + static_cast(lhs), static_cast::View>(rhs), + lhs.getMesh(), lhs.getLocation(), lhs.getDirections(), + bout::detail::getPowRegionID(lhs.getMesh(), rgn), + lhs.getMesh()->getRegion(rgn), bout::detail::getPerpYIndex(lhs)); +} - BOUT_FOR(i, result.getRegion(rgn)) { result[i] = ::pow(lhs, rhs[i]); } +template +std::enable_if_t && is_expr_field3d_v, + BinaryExpr, R, bout::op::Pow>> +pow(L lhs, const R& rhs) { + return bout::detail::makePowExpr, R>( + static_cast::View>(lhs), static_cast(rhs), + rhs.getMesh(), rhs.getLocation(), rhs.getDirections(), rhs.getRegionID(), + rhs.getMesh()->getRegion("RGN_ALL"), bout::detail::getPerpYIndex(rhs)); +} - checkData(result); - return result; +template +std::enable_if_t && is_expr_field3d_v, + BinaryExpr, R, bout::op::Pow>> +pow(L lhs, const R& rhs, const std::string& rgn) { + return bout::detail::makePowExpr, R>( + static_cast::View>(lhs), static_cast(rhs), + rhs.getMesh(), rhs.getLocation(), rhs.getDirections(), + bout::detail::getPowRegionID(rhs.getMesh(), rgn), + rhs.getMesh()->getRegion(rgn), bout::detail::getPerpYIndex(rhs)); } /*! @@ -608,6 +793,20 @@ class Field3DParallel; class FieldPerp; namespace bout::detail { +template +using UnaryFieldResult_t = + std::conditional_t, ::Field3DParallel>, ::Field3D, + std::decay_t>; + +template +std::optional getUnaryRegionID(const Mesh* mesh, const std::string& region_name) { + if constexpr (std::is_same_v, ::Field3D>) { + return bout::detail::getField3DRegionID(mesh, region_name); + } else { + return std::nullopt; + } +} + template std::optional getPerpYIndex(const T& value) { if constexpr (std::is_same_v, ::FieldPerp>) { @@ -641,35 +840,23 @@ std::optional getPerpYIndex(const BinaryExpr& expr) { }; \ template > \ inline auto name(const T& f, const std::string& rgn = "RGN_ALL") { \ - if constexpr (std::is_same_v) { \ - /* Check if the input is allocated */ \ - checkData(f); \ - /* Define and allocate the output result */ \ - T result{emptyFrom(f)}; \ - BOUT_FOR(d, result.getRegion(rgn)) { result[d] = func(f[d]); } \ - for (int i = 0; i < f.numberParallelSlices(); ++i) { \ - result.yup(i) = func(f.yup(i)); \ - result.ydown(i) = func(f.ydown(i)); \ - } \ - result.name = std::string(#name "(") + f.name + std::string(")"); \ - checkData(result); \ - return result; \ - } else { \ - return BinaryExpr{static_cast(f), \ - static_cast(f), \ - bout::op::name{}, \ - f.getMesh(), \ - f.getLocation(), \ - f.getDirections(), \ - std::nullopt, \ - f.getRegion(rgn), \ - bout::detail::getPerpYIndex(f)}; \ - } \ + using ResT = bout::detail::UnaryFieldResult_t; \ + return BinaryExpr{ \ + static_cast(f), \ + static_cast(f), \ + bout::op::name{}, \ + f.getMesh(), \ + f.getLocation(), \ + f.getDirections(), \ + bout::detail::getUnaryRegionID(f.getMesh(), rgn), \ + f.getMesh()->template getRegion(rgn), \ + bout::detail::getPerpYIndex(f)}; \ } \ template \ inline auto name(const BinaryExpr& f) { \ - return BinaryExpr, BinaryExpr, \ - bout::op::name>{ \ + using UnaryResT = bout::detail::UnaryFieldResult_t; \ + return BinaryExpr, \ + BinaryExpr, bout::op::name>{ \ static_cast::View>(f), \ static_cast::View>(f), \ bout::op::name{}, \ @@ -682,7 +869,18 @@ std::optional getPerpYIndex(const BinaryExpr& expr) { } \ template \ inline auto name(const BinaryExpr& f, const std::string& rgn) { \ - return name(ResT{f}, rgn); \ + using UnaryResT = bout::detail::UnaryFieldResult_t; \ + return BinaryExpr, \ + BinaryExpr, bout::op::name>{ \ + static_cast::View>(f), \ + static_cast::View>(f), \ + bout::op::name{}, \ + f.getMesh(), \ + f.getLocation(), \ + f.getDirections(), \ + bout::detail::getUnaryRegionID(f.getMesh(), rgn), \ + f.getMesh()->template getRegion(rgn), \ + bout::detail::getPerpYIndex(f)}; \ } #endif @@ -694,40 +892,41 @@ struct Square { return ::SQ(value); } }; + +struct Floor { + template + BOUT_HOST_DEVICE BOUT_FORCEINLINE BoutReal operator()(int idx, const LView& L, + const RView& R) const { + const BoutReal value = L(idx); + const BoutReal floor_value = R(idx); + return value < floor_value ? floor_value : value; + } + BOUT_HOST_DEVICE BOUT_FORCEINLINE BoutReal operator()(BoutReal value, + BoutReal floor_value) const { + return value < floor_value ? floor_value : value; + } +}; }; // namespace bout::op template > inline auto SQ(const T& f, const std::string& rgn = "RGN_ALL") { - if constexpr (std::is_same_v) { - checkData(f); - T result{emptyFrom(f)}; - if (f.hasParallelSlices() and !result.hasParallelSlices()) { - result.splitParallelSlices(); - } - BOUT_FOR(d, result.getRegion(rgn)) { result[d] = ::SQ(f[d]); } - for (size_t i = 0; i < f.numberParallelSlices(); ++i) { - result.yup(i) = SQ(f.yup(i), rgn); - result.ydown(i) = SQ(f.ydown(i), rgn); - } - result.name = std::string("SQ(") + f.name + std::string(")"); - checkData(result); - return result; - } else { - return BinaryExpr{static_cast(f), - static_cast(f), - bout::op::Square{}, - f.getMesh(), - f.getLocation(), - f.getDirections(), - std::nullopt, - f.getRegion(rgn), - bout::detail::getPerpYIndex(f)}; - } + using ResT = bout::detail::UnaryFieldResult_t; + return BinaryExpr{ + static_cast(f), + static_cast(f), + bout::op::Square{}, + f.getMesh(), + f.getLocation(), + f.getDirections(), + bout::detail::getUnaryRegionID(f.getMesh(), rgn), + f.getMesh()->template getRegion(rgn), + bout::detail::getPerpYIndex(f)}; } template inline auto SQ(const BinaryExpr& f) { - return BinaryExpr, BinaryExpr, + using UnaryResT = bout::detail::UnaryFieldResult_t; + return BinaryExpr, BinaryExpr, bout::op::Square>{ static_cast::View>(f), static_cast::View>(f), @@ -742,7 +941,18 @@ inline auto SQ(const BinaryExpr& f) { template inline auto SQ(const BinaryExpr& f, const std::string& rgn) { - return SQ(ResT{f}, rgn); + using UnaryResT = bout::detail::UnaryFieldResult_t; + return BinaryExpr, BinaryExpr, + bout::op::Square>{ + static_cast::View>(f), + static_cast::View>(f), + bout::op::Square{}, + f.getMesh(), + f.getLocation(), + f.getDirections(), + bout::detail::getUnaryRegionID(f.getMesh(), rgn), + f.getMesh()->template getRegion(rgn), + bout::detail::getPerpYIndex(f)}; } /// Square root of \p f over region \p rgn @@ -874,46 +1084,51 @@ class Field3DParallel; /// @param[in] f The floor value /// @param[in] rgn The region to calculate the result over template > -inline T floor(const T& var, BoutReal f, const std::string& rgn = "RGN_ALL") { - checkData(var); - T result = copy(var); +inline auto floor(const T& var, BoutReal f, const std::string& rgn = "RGN_ALL") { + using ResT = bout::detail::UnaryFieldResult_t; + return BinaryExpr, bout::op::Floor>{ + static_cast(var), + static_cast::View>(f), + bout::op::Floor{}, + var.getMesh(), + var.getLocation(), + var.getDirections(), + bout::detail::getUnaryRegionID(var.getMesh(), rgn), + var.getMesh()->template getRegion(rgn), + bout::detail::getPerpYIndex(var)}; +} - BOUT_FOR(d, var.getRegion(rgn)) { - if (result[d] < f) { - result[d] = f; - } - } - if constexpr (std::is_same_v) { - if (var.hasParallelSlices()) { - for (size_t i = 0; i < result.numberParallelSlices(); ++i) { - if (result.yup(i).isAllocated()) { - BOUT_FOR(d, result.yup(i).getRegion(rgn)) { - if (result.yup(i)[d] < f) { - result.yup(i)[d] = f; - } - } - } else { - if (result.isFci()) { - throw BoutException("Expected parallel slice to be allocated"); - } - } - if (result.ydown(i).isAllocated()) { - BOUT_FOR(d, result.ydown(i).getRegion(rgn)) { - if (result.ydown(i)[d] < f) { - result.ydown(i)[d] = f; - } - } - } else { - if (result.isFci()) { - throw BoutException("Expected parallel slice to be allocated"); - } - } - } - } - } else { - result.clearParallelSlices(); - } - return result; +template +inline auto floor(const BinaryExpr& var, BoutReal f) { + using UnaryResT = bout::detail::UnaryFieldResult_t; + return BinaryExpr, Constant, + bout::op::Floor>{ + static_cast::View>(var), + static_cast::View>(f), + bout::op::Floor{}, + var.getMesh(), + var.getLocation(), + var.getDirections(), + var.getRegionID(), + var.indices, + bout::detail::getPerpYIndex(var)}; +} + +template +inline auto floor(const BinaryExpr& var, BoutReal f, + const std::string& rgn) { + using UnaryResT = bout::detail::UnaryFieldResult_t; + return BinaryExpr, Constant, + bout::op::Floor>{ + static_cast::View>(var), + static_cast::View>(f), + bout::op::Floor{}, + var.getMesh(), + var.getLocation(), + var.getDirections(), + bout::detail::getUnaryRegionID(var.getMesh(), rgn), + var.getMesh()->template getRegion(rgn), + bout::detail::getPerpYIndex(var)}; } #undef FIELD_FUNC diff --git a/include/bout/field2d.hxx b/include/bout/field2d.hxx index 540680bb73..13a26faa38 100644 --- a/include/bout/field2d.hxx +++ b/include/bout/field2d.hxx @@ -35,6 +35,8 @@ class Field2D; #include "bout/array.hxx" #include "bout/assert.hxx" #include "bout/bout_types.hxx" +#include "bout/boutexception.hxx" +#include "bout/build_config.hxx" #include "bout/build_defines.hxx" #include "bout/field.hxx" #include "bout/field_data.hxx" @@ -45,6 +47,7 @@ class Field2D; #include #include #include +#include #include "bout/fieldops.hxx" @@ -333,6 +336,7 @@ public: BoutReal* data; int mul = 1; int div = 1; + BOUT_HOST_DEVICE BOUT_FORCEINLINE BoutReal operator()(int idx) const { return data[(idx * mul / div)]; } @@ -345,6 +349,12 @@ public: this->div = div; return *this; } + BOUT_HOST_DEVICE BOUT_FORCEINLINE bool hasParallelSlices() const { return false; } + BOUT_HOST_DEVICE BOUT_FORCEINLINE int numberParallelSlices() const { return 0; } + BOUT_HOST_DEVICE BOUT_FORCEINLINE View yup(int = 0) const { return *this; } + BOUT_HOST_DEVICE BOUT_FORCEINLINE View ydown(int = 0) const { return *this; } + + std::optional getRegionID() const { return {}; } }; operator View() { return View{&data[0]}; } operator View() const { return View{const_cast(&data[0])}; } diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index 905e736999..62486dded9 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -31,6 +31,7 @@ class Field3D; #include "bout/array.hxx" #include "bout/assert.hxx" #include "bout/bout_types.hxx" +#include "bout/boutexception.hxx" #include "bout/build_config.hxx" #include "bout/field.hxx" #include "bout/field2d.hxx" @@ -355,7 +356,7 @@ public: const Region& getValidRegionWithDefault(const std::string& region_name) const; void setRegion(const std::string& region_name) override; void resetRegion() override { regionID.reset(); }; - void resetRegionParallel(bool force = false); + void resetRegionParallel(bool force = false) override; void setRegion(size_t id) override { regionID = id; }; void setRegion(std::optional id) override { regionID = id; }; std::optional getRegionID() const override { return regionID; }; @@ -465,6 +466,11 @@ public: struct View { BoutReal* data; + const Field3D* yup_fields{nullptr}; + const Field3D* ydown_fields{nullptr}; + int num_parallel_slices{0}; + std::optional regionID; + BOUT_HOST_DEVICE BOUT_FORCEINLINE BoutReal operator()(int idx) const { return data[idx]; } @@ -478,9 +484,35 @@ public: "Field3D::View does not support setScale()"); return *this; } + BOUT_HOST_DEVICE BOUT_FORCEINLINE bool hasParallelSlices() const { + return num_parallel_slices > 0; + } + BOUT_HOST_DEVICE BOUT_FORCEINLINE int numberParallelSlices() const { + return num_parallel_slices; + } + /// Not a DEVICE function because it dereferences a Field3D pointer + BOUT_FORCEINLINE View yup(int slice = 0) const { + ASSERT2(slice < num_parallel_slices); + ASSERT2(yup_fields[slice].isAllocated()); + return static_cast(yup_fields[slice]); + } + /// Not a DEVICE function because it dereferences a Field3D pointer + BOUT_FORCEINLINE View ydown(int slice = 0) const { + ASSERT2(slice < num_parallel_slices); + ASSERT2(ydown_fields[slice].isAllocated()); + return static_cast(ydown_fields[slice]); + } + + std::optional getRegionID() const { return regionID; } }; - operator View() { return View{&data[0]}; } - operator View() const { return View{const_cast(&data[0])}; } + operator View() { + return View{&data[0], yup_fields.data(), ydown_fields.data(), + static_cast(numberParallelSlices()), regionID}; + } + operator View() const { + return View{const_cast(&data[0]), yup_fields.data(), ydown_fields.data(), + static_cast(numberParallelSlices()), regionID}; + } //operator View() const { return View{&data[0]}; } ///////////////////////////////////////////////////////// @@ -601,6 +633,8 @@ public: friend class Vector2D; void calcParallelSlices() override; + bool areCalcParallelSlicesAllowed() const override { return _allowCalcParallelSlices; }; + void disallowCalcParallelSlices() override { _allowCalcParallelSlices = false; }; void applyBoundary(bool init = false) override; void applyBoundary(BoutReal t); @@ -633,9 +667,6 @@ public: std::weak_ptr getTracking() { return tracking; }; - bool areCalcParallelSlicesAllowed() const { return _allowCalcParallelSlices; }; - void disallowCalcParallelSlices() { _allowCalcParallelSlices = false; }; - inline Field3DParallel asField3DParallel(); inline Field3DParallel asField3DParallel() const; @@ -856,31 +887,6 @@ if_else(bool condition, const L& lhs, const R& rhs) { rhs.getMesh()->getRegion("RGN_ALL")}; } -Field3DParallel operator+(const Field3D& lhs, const Field3DParallel& rhs); -Field3DParallel operator-(const Field3D& lhs, const Field3DParallel& rhs); -Field3DParallel operator*(const Field3D& lhs, const Field3DParallel& rhs); -Field3DParallel operator/(const Field3D& lhs, const Field3DParallel& rhs); - -Field3DParallel operator+(const Field3DParallel& lhs, const Field3D& rhs); -Field3DParallel operator-(const Field3DParallel& lhs, const Field3D& rhs); -Field3DParallel operator*(const Field3DParallel& lhs, const Field3D& rhs); -Field3DParallel operator/(const Field3DParallel& lhs, const Field3D& rhs); - -Field3DParallel operator+(const Field3DParallel& lhs, const Field3DParallel& rhs); -Field3DParallel operator-(const Field3DParallel& lhs, const Field3DParallel& rhs); -Field3DParallel operator*(const Field3DParallel& lhs, const Field3DParallel& rhs); -Field3DParallel operator/(const Field3DParallel& lhs, const Field3DParallel& rhs); - -Field3DParallel operator+(BoutReal lhs, const Field3DParallel& rhs); -Field3DParallel operator-(BoutReal lhs, const Field3DParallel& rhs); -Field3DParallel operator*(BoutReal lhs, const Field3DParallel& rhs); -Field3DParallel operator/(BoutReal lhs, const Field3DParallel& rhs); - -Field3DParallel operator+(const Field3DParallel& lhs, BoutReal rhs); -Field3DParallel operator-(const Field3DParallel& lhs, BoutReal rhs); -Field3DParallel operator*(const Field3DParallel& lhs, BoutReal rhs); -Field3DParallel operator/(const Field3DParallel& lhs, BoutReal rhs); - /*! * Unary minus. Returns the negative of given field, * iterates over whole domain including guard/boundary cells. @@ -907,7 +913,6 @@ inline auto operator-(const Field3D& f) { /// This loops over the entire domain, including guard/boundary cells by /// default (can be changed using the \p rgn argument). /// If CHECK >= 3 then the result will be checked for non-finite numbers -Field3D pow(const Field3D& lhs, const Field2D& rhs, const std::string& rgn = "RGN_ALL"); FieldPerp pow(const Field3D& lhs, const FieldPerp& rhs, const std::string& rgn = "RGN_ALL"); @@ -1024,6 +1029,13 @@ public: explicit Field3DParallel(Types... args) : Field3D(std::move(args)...) { ensureFieldAligned(); } + template || is_expr_field3d_v>> + Field3DParallel(const BinaryExpr& expr) + : Field3DParallel(expr.getMesh(), expr.getLocation(), expr.getDirections(), + expr.getRegionID()) { + *this = expr; + } Field3DParallel(const Field3D& f) : Field3D(f) { ensureFieldAligned(); } Field3DParallel(const Field3D& f, bool isRef) : Field3D(f), isRef(isRef) { ensureFieldAligned(); @@ -1052,6 +1064,46 @@ public: Field3D& asField3D() { return *this; } const Field3D& asField3D() const { return *this; } + struct View { + Field3D::View base; + + BOUT_HOST_DEVICE BOUT_FORCEINLINE BoutReal operator()(int idx) const { + return base(idx); + } + BOUT_HOST_DEVICE BOUT_FORCEINLINE BoutReal& operator[](int idx) const { + return base[idx]; + } + + template + View& setScale(Mul /*unused*/, Div /*unused*/) { + static_assert(!std::is_same_v, + "Field3DParallel::View does not support setScale()"); + return *this; + } + + BOUT_FORCEINLINE bool hasParallelSlices() const { return base.hasParallelSlices(); } + BOUT_FORCEINLINE int numberParallelSlices() const { + return base.numberParallelSlices(); + } + /// Not a DEVICE function because it dereferences a Field3D pointer + BOUT_FORCEINLINE View yup(int slice = 0) const { + ASSERT2(slice < base.num_parallel_slices); + ASSERT2(base.yup_fields[slice].isAllocated()); + return View{static_cast(base.yup_fields[slice])}; + } + /// Not a DEVICE function because it dereferences a Field3D pointer + BOUT_FORCEINLINE View ydown(int slice = 0) const { + ASSERT2(slice < base.num_parallel_slices); + ASSERT2(base.ydown_fields[slice].isAllocated()); + return View{static_cast(base.ydown_fields[slice])}; + } + + std::optional getRegionID() const { return base.regionID; } + }; + + operator View() { return View{static_cast(*this)}; } + operator View() const { return View{static_cast(*this)}; } + Field3DParallel& operator*=(const Field3D&); Field3DParallel& operator/=(const Field3D&); Field3DParallel& operator+=(const Field3D&); @@ -1074,6 +1126,45 @@ public: ensureFieldAligned(); return *this; } + template + std::enable_if_t || is_expr_field3d_v, Field3DParallel&> + operator=(const BinaryExpr& expr) { + if (getMesh() != expr.getMesh()) { + clearParallelSlices(); + fieldmesh = expr.getMesh(); + data = Array{}; + } + if (isFci()) { + if (!hasParallelSlices()) { + splitParallelSlices(); + } + } else if (hasParallelSlices()) { + clearParallelSlices(); + } + + setRegion(expr.getRegionID()); + setLocation(expr.getLocation()); + setDirections(expr.getDirections()); + allocate(); + expr.evaluate(static_cast(*this).data); + + if (isFci()) { + ASSERT2(expr.hasParallelSlices()); + ASSERT2(expr.numberParallelSlices() == static_cast(numberParallelSlices())); + for (int i = 0; i < expr.numberParallelSlices(); ++i) { + yup(i).allocate(); + ydown(i).allocate(); + auto expr_yup = expr.yup(i); + auto expr_ydown = expr.ydown(i); + yup(i).setRegion(expr_yup.getRegionID()); + ydown(i).setRegion(expr_ydown.getRegionID()); + expr_yup.evaluate(static_cast(yup(i)).data); + expr_ydown.evaluate(static_cast(ydown(i)).data); + } + } + + return *this; + } Field3DParallel& operator=(BoutReal); Field3DParallel& allocate(); @@ -1119,16 +1210,6 @@ struct is_expr_field3d> : std::integral_constant>::value || is_expr_field3d_v>> {}; -Field3D operator+(const Field2D& lhs, const Field3DParallel& rhs); -Field3D operator-(const Field2D& lhs, const Field3DParallel& rhs); -Field3D operator*(const Field2D& lhs, const Field3DParallel& rhs); -Field3D operator/(const Field2D& lhs, const Field3DParallel& rhs); - -Field3D operator+(const Field3DParallel& lhs, const Field2D& rhs); -Field3D operator-(const Field3DParallel& lhs, const Field2D& rhs); -Field3D operator*(const Field3DParallel& lhs, const Field2D& rhs); -Field3D operator/(const Field3DParallel& lhs, const Field2D& rhs); - inline Field3DParallel filledFrom(const Field3DParallel& f, const std::function& func) { diff --git a/include/bout/field_data.hxx b/include/bout/field_data.hxx index 20b6a517f9..c5e60011f1 100644 --- a/include/bout/field_data.hxx +++ b/include/bout/field_data.hxx @@ -30,7 +30,9 @@ class FieldData; #ifndef FIELD_DATA_H #define FIELD_DATA_H +#include "bout/boundary_region.hxx" #include "bout/bout_types.hxx" +#include "bout/sys/expressionparser.hxx" #include "bout/unused.hxx" #include @@ -47,12 +49,8 @@ class BoundaryRegionFCI; class Coordinates; class Mesh; -#include "bout/boundary_region.hxx" -class BoundaryRegionPar; enum class BndryLoc : std::int8_t; -#include "bout/sys/expressionparser.hxx" - /// Base class for both scalar and vector fields, holds common /// information about the grid, coordinates, and boundaries class FieldData { diff --git a/include/bout/fieldops.hxx b/include/bout/fieldops.hxx index f36061ceaa..b12c1d1046 100644 --- a/include/bout/fieldops.hxx +++ b/include/bout/fieldops.hxx @@ -12,6 +12,7 @@ #include #include #include +#include #include #if BOUT_HAS_CUDA @@ -29,6 +30,9 @@ namespace bout::detail { // It is used because Mesh is an incomplete type so methods cannot be called // in the template functions in this header file. const Region& getField3DRegion(const Mesh* mesh, std::optional regionID); +size_t getField3DRegionID(const Mesh* mesh, const std::string& region_name); +std::optional meshGetCommonRegionID(Mesh* mesh, std::optional regionID1, + std::optional regionID2); } // namespace bout::detail template @@ -366,7 +370,7 @@ struct BinaryExpr { BinaryExpr& operator=(const BinaryExpr&) = delete; BinaryExpr& operator=(BinaryExpr&&) = delete; - BOUT_HOST_DEVICE BOUT_FORCEINLINE int size() const { return indices.size(); } + BOUT_FORCEINLINE int size() const { return indices.size(); } BOUT_HOST_DEVICE BOUT_FORCEINLINE BoutReal operator()(int idx) const { return f(idx, lhs, rhs); // single‐pass fusion } @@ -381,13 +385,66 @@ struct BinaryExpr { } BOUT_HOST_DEVICE BOUT_FORCEINLINE int regionIdx(int idx) const { return indices[idx]; } - //operator ResT() { return ResT{*this}; } + bool hasParallelSlices() const { + if constexpr (is_expr_constant_v && is_expr_constant_v) { + return false; + } else if constexpr (is_expr_constant_v) { + return rhs.hasParallelSlices(); + } else if constexpr (is_expr_constant_v) { + return lhs.hasParallelSlices(); + } else { + return lhs.hasParallelSlices() && rhs.hasParallelSlices(); + } + } + int numberParallelSlices() const { + if (!hasParallelSlices()) { + return 0; + } + if constexpr (is_expr_constant_v && is_expr_constant_v) { + return 0; + } else if constexpr (is_expr_constant_v) { + return rhs.numberParallelSlices(); + } else if constexpr (is_expr_constant_v) { + return lhs.numberParallelSlices(); + } else { + ASSERT2(lhs.numberParallelSlices() == rhs.numberParallelSlices()); + return lhs.numberParallelSlices(); + } + } + auto yup(int slice = 0) const { + return BinaryExpr{ + lhs.yup(slice), + rhs.yup(slice), + f, + mesh, + location, + directions, + bout::detail::meshGetCommonRegionID(mesh, lhs.yup(slice).getRegionID(), + rhs.yup(slice).getRegionID()), + indices, + yindex}; + } + auto ydown(int slice = 0) const { + return BinaryExpr{ + lhs.ydown(slice), + rhs.ydown(slice), + f, + mesh, + location, + directions, + bout::detail::meshGetCommonRegionID(mesh, lhs.ydown(slice).getRegionID(), + rhs.ydown(slice).getRegionID()), + indices, + yindex}; + } + struct View { typename L::View lhs; typename R::View rhs; const int* indices; int num_indices; Func f; + std::optional regionID; int mul = 1; int div = 1; @@ -396,18 +453,59 @@ struct BinaryExpr { this->div = div; return *this; } + BOUT_HOST_DEVICE BOUT_FORCEINLINE bool hasParallelSlices() const { + if constexpr (is_expr_constant_v && is_expr_constant_v) { + return false; + } else if constexpr (is_expr_constant_v) { + return rhs.hasParallelSlices(); + } else if constexpr (is_expr_constant_v) { + return lhs.hasParallelSlices(); + } else { + return lhs.hasParallelSlices() && rhs.hasParallelSlices(); + } + } + BOUT_HOST_DEVICE BOUT_FORCEINLINE int numberParallelSlices() const { + if (!hasParallelSlices()) { + return 0; + } + if constexpr (is_expr_constant_v && is_expr_constant_v) { + return 0; + } else if constexpr (is_expr_constant_v) { + return rhs.numberParallelSlices(); + } else if constexpr (is_expr_constant_v) { + return lhs.numberParallelSlices(); + } else { + ASSERT2(lhs.numberParallelSlices() == rhs.numberParallelSlices()); + return lhs.numberParallelSlices(); + } + } + BOUT_HOST_DEVICE BOUT_FORCEINLINE auto yup(int slice = 0) const { + auto result = *this; + result.lhs = lhs.yup(slice); + result.rhs = rhs.yup(slice); + return result; + } + BOUT_HOST_DEVICE BOUT_FORCEINLINE auto ydown(int slice = 0) const { + auto result = *this; + result.lhs = lhs.ydown(slice); + result.rhs = rhs.ydown(slice); + return result; + } BOUT_HOST_DEVICE BOUT_FORCEINLINE int size() const { return num_indices; } BOUT_HOST_DEVICE BOUT_FORCEINLINE int regionIdx(int idx) const { return indices[idx]; } BOUT_HOST_DEVICE BOUT_FORCEINLINE BoutReal operator()(int idx) const { return f((idx * mul) / div, lhs, rhs); // single‐pass fusion - //return f(lhs((idx * mul) / div), rhs((idx * mul) / div)); // single‐pass fusion } + + std::optional getRegionID() const { return regionID; } }; - operator View() { return View{lhs, rhs, &indices[0], indices.size(), f}; } - operator View() const { return View{lhs, rhs, &indices[0], indices.size(), f}; } + operator View() { return View{lhs, rhs, &indices[0], indices.size(), f, regionID}; } + operator View() const { + return View{lhs, rhs, &indices[0], indices.size(), f, regionID}; + } void evaluate(BoutReal* data) const { #if BOUT_HAS_CUDA && defined(__CUDACC__) diff --git a/include/bout/fieldperp.hxx b/include/bout/fieldperp.hxx index 36a116e1b5..2f8408d82b 100644 --- a/include/bout/fieldperp.hxx +++ b/include/bout/fieldperp.hxx @@ -352,6 +352,10 @@ public: this->div = div; return *this; } + BOUT_HOST_DEVICE BOUT_FORCEINLINE bool hasParallelSlices() const { return false; } + BOUT_HOST_DEVICE BOUT_FORCEINLINE int numberParallelSlices() const { return 0; } + BOUT_HOST_DEVICE BOUT_FORCEINLINE View yup(int = 0) const { return *this; } + BOUT_HOST_DEVICE BOUT_FORCEINLINE View ydown(int = 0) const { return *this; } }; operator View() { return View{&data[0]}; } operator View() const { return View{const_cast(&data[0])}; } @@ -405,6 +409,11 @@ FieldPerp operator/(const FieldPerp& lhs, const Field2D& rhs); FieldPerp operator/(const FieldPerp& lhs, BoutReal rhs); FieldPerp operator/(BoutReal lhs, const FieldPerp& rhs); +FieldPerp pow(const FieldPerp& lhs, const FieldPerp& rhs, + const std::string& rgn = "RGN_ALL"); +FieldPerp pow(const FieldPerp& lhs, BoutReal rhs, const std::string& rgn = "RGN_ALL"); +FieldPerp pow(BoutReal lhs, const FieldPerp& rhs, const std::string& rgn = "RGN_ALL"); + /*! * Unary minus. Returns the negative of given field, * iterates over whole domain including guard/boundary cells. @@ -438,8 +447,8 @@ inline FieldPerp emptyFrom(const FieldPerp& f) { #if CHECK > 0 void checkData(const FieldPerp& f, const std::string& region = "RGN_NOX"); #else -inline void checkData(const FieldPerp& UNUSED(f), - const std::string& UNUSED(region) = "RGN_NOX") {} +inline void checkData([[maybe_unused]] const FieldPerp& f, + [[maybe_unused]] const std::string& region = "RGN_NOX") {} #endif /// Force guard cells of passed field \p var to NaN @@ -452,7 +461,7 @@ inline void invalidateGuards(FieldPerp& UNUSED(var)) {} /// toString template specialisation /// Defined in utils.hxx template <> -inline std::string toString<>(const FieldPerp& UNUSED(val)) { +inline std::string toString<>([[maybe_unused]] const FieldPerp& val) { return ""; } diff --git a/include/bout/fv_ops.hxx b/include/bout/fv_ops.hxx index 306ff3301f..cdb0a067ed 100644 --- a/include/bout/fv_ops.hxx +++ b/include/bout/fv_ops.hxx @@ -79,13 +79,13 @@ Field3D D4DY4_Index(const Field3D& f, bool bndry_flux = true); // Forward declarations of flux limiters // If you want to use your own flux limiter, you need to // #include to instantiate the templates. -class Upwind; -class Fromm; -class MinMod; -class MC; -class Superbee; -class VanAlbada; -class WENO3; +struct Upwind; +struct Fromm; +struct MinMod; +struct MC; +struct Superbee; +struct VanAlbada; +struct WENO3; /*! * Communicate fluxes between processors @@ -156,8 +156,8 @@ Field3D Div_Perp_Lap(const Field3D& a, const Field3D& f, CELL_LOC outloc = CELL_ /// For FCI fields this diagnostic is currently set to zero. template Field3D Div_par_mod(const Field3D& f_in, const Field3D& v_in, - const Field3D& wave_speed_in, Field3D& flow_ylow, - bool fixflux = true); + const Field3D& wave_speed_in, Field3D& flow_ylow, bool fixflux = true, + bool dissipative = false); /// This operator calculates Div_par(f v v) /// It is used primarily (only?) in the parallel momentum equation. diff --git a/include/bout/fv_ops_impl.hxx b/include/bout/fv_ops_impl.hxx index 1b7a36f874..8f76dae6a3 100644 --- a/include/bout/fv_ops_impl.hxx +++ b/include/bout/fv_ops_impl.hxx @@ -491,10 +491,14 @@ Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_flux) { BOUT_FOR(i, result.getRegion("RGN_NOBNDRY")) { // Calculate velocities - const BoutReal vU = 0.25 * (vz[i.zp()] + vz[i]) * (coord->J[i.zp()] + coord->J[i]); - const BoutReal vD = 0.25 * (vz[i.zm()] + vz[i]) * (coord->J[i.zm()] + coord->J[i]); - const BoutReal vL = 0.25 * (vx[i.xm()] + vx[i]) * (coord->J[i.xm()] + coord->J[i]); - const BoutReal vR = 0.25 * (vx[i.xp()] + vx[i]) * (coord->J[i.xp()] + coord->J[i]); + const BoutReal vU = + 0.25 * (vz[i.zp()] + vz[i]) * (coord->J()[i.zp()] + coord->J()[i]); + const BoutReal vD = + 0.25 * (vz[i.zm()] + vz[i]) * (coord->J()[i.zm()] + coord->J()[i]); + const BoutReal vL = + 0.25 * (vx[i.xm()] + vx[i]) * (coord->J()[i.xm()] + coord->J()[i]); + const BoutReal vR = + 0.25 * (vx[i.xp()] + vx[i]) * (coord->J()[i.xp()] + coord->J()[i]); // X direction Stencil1D s; @@ -517,16 +521,16 @@ Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_flux) { // Flux in from boundary flux = vR * 0.5 * (n[i.xp()] + n[i]); } - result[i] += flux / (coord->dx[i] * coord->J[i]); - result[i.xp()] -= flux / (coord->dx[i.xp()] * coord->J[i.xp()]); + result[i] += flux / (coord->dx()[i] * coord->J()[i]); + result[i.xp()] -= flux / (coord->dx()[i.xp()] * coord->J()[i.xp()]); } } else { // Not at a boundary if (vR > 0.0) { // Flux out into next cell const BoutReal flux = vR * s.R; - result[i] += flux / (coord->dx[i] * coord->J[i]); - result[i.xp()] -= flux / (coord->dx[i.xp()] * coord->J[i.xp()]); + result[i] += flux / (coord->dx()[i] * coord->J()[i]); + result[i.xp()] -= flux / (coord->dx()[i.xp()] * coord->J()[i.xp()]); } } @@ -544,15 +548,15 @@ Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_flux) { // Flux in from boundary flux = vL * 0.5 * (n[i.xm()] + n[i]); } - result[i] -= flux / (coord->dx[i] * coord->J[i]); - result[i.xm()] += flux / (coord->dx[i.xm()] * coord->J[i.xm()]); + result[i] -= flux / (coord->dx()[i] * coord->J()[i]); + result[i.xm()] += flux / (coord->dx()[i.xm()] * coord->J()[i.xm()]); } } else { // Not at a boundary if (vL < 0.0) { const BoutReal flux = vL * s.L; - result[i] -= flux / (coord->dx[i] * coord->J[i]); - result[i.xm()] += flux / (coord->dx[i.xm()] * coord->J[i.xm()]); + result[i] -= flux / (coord->dx()[i] * coord->J()[i]); + result[i.xm()] += flux / (coord->dx()[i.xm()] * coord->J()[i.xm()]); } } @@ -568,13 +572,13 @@ Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_flux) { if (vU > 0.0) { const BoutReal flux = vU * s.R; - result[i] += flux / (coord->J[i] * coord->dz[i]); - result[i.zp()] -= flux / (coord->J[i.zp()] * coord->dz[i.zp()]); + result[i] += flux / (coord->J()[i] * coord->dz()[i]); + result[i.zp()] -= flux / (coord->J()[i.zp()] * coord->dz()[i.zp()]); } if (vD < 0.0) { const BoutReal flux = vD * s.L; - result[i] -= flux / (coord->J[i] * coord->dz[i]); - result[i.zm()] += flux / (coord->J[i.zm()] * coord->dz[i.zm()]); + result[i] -= flux / (coord->J()[i] * coord->dz()[i]); + result[i.zm()] += flux / (coord->J()[i.zm()] * coord->dz()[i.zm()]); } } @@ -592,15 +596,17 @@ Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_flux) { BOUT_FOR(i, result.getRegion("RGN_NOBNDRY")) { // Y velocities on y boundaries - const BoutReal vU = 0.25 * (vy[i] + vy[i.yp()]) * (coord->J[i] + coord->J[i.yp()]); - const BoutReal vD = 0.25 * (vy[i] + vy[i.ym()]) * (coord->J[i] + coord->J[i.ym()]); + const BoutReal vU = + 0.25 * (vy[i] + vy[i.yp()]) * (coord->J()[i] + coord->J()[i.yp()]); + const BoutReal vD = + 0.25 * (vy[i] + vy[i.ym()]) * (coord->J()[i] + coord->J()[i.ym()]); // n (advected quantity) on y boundaries // Note: Use unshifted n_in variable const BoutReal nU = 0.5 * (n[i] + n[i.yp()]); const BoutReal nD = 0.5 * (n[i] + n[i.ym()]); - yresult[i] = (nU * vU - nD * vD) / (coord->J[i] * coord->dy[i]); + yresult[i] = (nU * vU - nD * vD) / (coord->J()[i] * coord->dy()[i]); } return result + fromFieldAligned(yresult, "RGN_NOBNDRY"); } @@ -627,7 +633,8 @@ Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_flux) { /// 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) { + const Field3D& wave_speed_in, Field3D& flow_ylow, bool fixflux, + bool dissipative) { Coordinates* coord = f_in.getCoordinates(); ASSERT1_FIELDS_COMPATIBLE(f_in, v_in); @@ -655,11 +662,25 @@ Field3D Div_par_mod(const Field3D& f_in, const Field3D& v_in, - 0.25 * (f_in[i] + f_down[iym]) * (v_in[i] + v_down[iym]) * coord->cell_area_ylow()[i]) / coord->cell_volume()[i]; + + if (dissipative) { + const BoutReal amax = BOUTMAX(fabs(wave_speed_in[i]), fabs(v_in[i]), + fabs(v_up[iyp]), fabs(v_down[iym])); + result[i] += (0.5 * amax * (f_in[i] - f_up[iyp]) * coord->cell_area_yhigh()[i] + + 0.5 * amax * (f_in[i] - f_down[iym]) * coord->cell_area_ylow()[i]) + / coord->cell_volume()[i]; + } } return result; } ASSERT1_FIELDS_COMPATIBLE(f_in, wave_speed_in); + // Throw error when not Fci + if (dissipative) { + throw BoutException("Using dissipative flag in Div_par_mod but no Fci parallel " + "transform. This flag will have no impact on the simulation"); + } + const Mesh* mesh = f_in.getMesh(); CellEdges cellboundary; @@ -823,9 +844,9 @@ Field3D Div_par_fvv(const Field3D& f_in, const Field3D& v_in, ASSERT1(f_in.hasParallelSlices()); ASSERT1(v_in.hasParallelSlices()); - const auto& B = coord->Bxy; - const auto& B_up = coord->Bxy.yup(); - const auto& B_down = coord->Bxy.ydown(); + const auto& B = coord->Bxy(); + const auto& B_up = coord->Bxy().yup(); + const auto& B_down = coord->Bxy().ydown(); const auto& f_up = f_in.yup(); const auto& f_down = f_in.ydown(); @@ -833,8 +854,8 @@ Field3D Div_par_fvv(const Field3D& f_in, const Field3D& v_in, const auto& v_up = v_in.yup(); const auto& v_down = v_in.ydown(); - const auto& g_22 = coord->g_22; - const auto& dy = coord->dy; + const auto& g_22 = coord->g_22(); + const auto& dy = coord->dy(); Field3D result{emptyFrom(f_in)}; BOUT_FOR(i, f_in.getRegion("RGN_NOBNDRY")) { @@ -1013,9 +1034,9 @@ Field3D Div_par_fvv_heating(const Field3D& f_in, const Field3D& v_in, ASSERT1(f_in.hasParallelSlices()); ASSERT1(v_in.hasParallelSlices()); - const auto B = coord->Bxy; - const auto B_up = coord->Bxy.yup(); - const auto B_down = coord->Bxy.ydown(); + const auto B = coord->Bxy(); + const auto B_up = coord->Bxy().yup(); + const auto B_down = coord->Bxy().ydown(); const auto& f_up = f_in.yup(); const auto& f_down = f_in.ydown(); @@ -1023,8 +1044,8 @@ Field3D Div_par_fvv_heating(const Field3D& f_in, const Field3D& v_in, const auto& v_up = v_in.yup(); const auto& v_down = v_in.ydown(); - const auto g_22 = coord->g_22; - const auto dy = coord->dy; + const auto g_22 = coord->g_22(); + const auto dy = coord->dy(); Field3D result{emptyFrom(f_in)}; flow_ylow = zeroFrom(f_in); @@ -1102,22 +1123,14 @@ Field3D Div_par_fvv_heating(const Field3D& f_in, const Field3D& v_in, // so calculate inside the k loop. // For right cell boundaries - BoutReal common_factor = - (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 area_rp = coord->cell_area_yhigh()(i, j, k); - const BoutReal flux_factor_rc = - common_factor / (coord->dy(i, j, k) * coord->J(i, j, k)); - const BoutReal area_rp = - common_factor * coord->dx(i, j + 1, k) * coord->dz(i, j + 1, k); + const BoutReal flux_factor_rc = area_rp / coord->cell_volume()(i, j, k); // For left cell boundaries - common_factor = (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 area_lc = coord->cell_area_ylow()(i, j, k); - const BoutReal flux_factor_lc = - common_factor / (coord->dy(i, j, k) * coord->J(i, j, k)); - const BoutReal area_lc = common_factor * coord->dx(i, j, k) * coord->dz(i, j, k); + const BoutReal flux_factor_lc = area_lc / coord->cell_volume()(i, j, k); //////////////////////////////////////////// // Reconstruct f at the cell faces @@ -1179,7 +1192,7 @@ Field3D Div_par_fvv_heating(const Field3D& f_in, const Field3D& v_in, result(i, j, k) += (actual_ke - expected_ke) * flux_factor_rc; // Final flow through boundary is the expected value - flow_ylow(i, j + 1, k) += expected_ke * area_rp; //expected_ke * area_rp; + flow_ylow(i, j + 1, k) += expected_ke * area_rp; } else { // Maximum wave speed in the two cells @@ -1336,10 +1349,10 @@ Field3D Div_a_Grad_perp_limit(const Field3D& a, const Field3D& g, const Field3D& if (fci) { // 3D Metric, need yup/ydown fields. // Requires previous communication of metrics. - if (!coord->g23.hasParallelSlices() || !coord->g_23.hasParallelSlices() - || !coord->dy.hasParallelSlices() || !coord->dz.hasParallelSlices() - || !coord->Bxy.hasParallelSlices() || !coord->J.hasParallelSlices()) { - throw BoutException("metrics have no yup/down: Maybe communicate in init?"); + if (!coord->g23().hasParallelSlices() || !coord->g_23().hasParallelSlices() + || !coord->dy().hasParallelSlices() || !coord->dz().hasParallelSlices() + || !coord->Bxy().hasParallelSlices() || !coord->J().hasParallelSlices()) { + throw BoutException("metrics have no yup/down!"); } } #endif @@ -1357,12 +1370,12 @@ Field3D Div_a_Grad_perp_limit(const Field3D& a, const Field3D& g, const Field3D& #else constexpr bool metric_fci = false; #endif - const auto g23 = makeslices(metric_fci, coord->g23); - const auto g_23 = makeslices(metric_fci, coord->g_23); - const auto J = makeslices(metric_fci, coord->J); - const auto dy = makeslices(metric_fci, coord->dy); - const auto dz = makeslices(metric_fci, coord->dz); - const auto Bxy = makeslices(metric_fci, coord->Bxy); + const auto g23 = makeslices(metric_fci, coord->g23()); + const auto g_23 = makeslices(metric_fci, coord->g_23()); + const auto J = makeslices(metric_fci, coord->J()); + const auto dy = makeslices(metric_fci, coord->dy()); + const auto dz = makeslices(metric_fci, coord->dz()); + const auto Bxy = makeslices(metric_fci, coord->Bxy()); // Result of the Y and Z fluxes Field3D yzresult(0.0, mesh); diff --git a/include/bout/g_values.hxx b/include/bout/g_values.hxx new file mode 100644 index 0000000000..cbbd6d24ff --- /dev/null +++ b/include/bout/g_values.hxx @@ -0,0 +1,28 @@ +#ifndef BOUT_GVALUES_HXX +#define BOUT_GVALUES_HXX + +#include + +class Coordinates; + +/// `GValues` needs renaming, when we know what the name should be +class GValues { +public: + explicit GValues(const Coordinates& coordinates); + + const bout::FieldMetric& G1() const { return G1_m; } + const bout::FieldMetric& G2() const { return G2_m; } + const bout::FieldMetric& G3() const { return G3_m; } + + template + void map(F function) { + G1_m = function(G1_m); + G2_m = function(G2_m); + G3_m = function(G3_m); + } + +private: + bout::FieldMetric G1_m, G2_m, G3_m; +}; + +#endif //BOUT_GVALUES_HXX diff --git a/include/bout/globalindexer.hxx b/include/bout/globalindexer.hxx index bd4203a092..8fa9c309b5 100644 --- a/include/bout/globalindexer.hxx +++ b/include/bout/globalindexer.hxx @@ -1,14 +1,21 @@ #ifndef BOUT_GLOBALINDEXER_H #define BOUT_GLOBALINDEXER_H +#include +#include #include #include #include +#include #include #include #include #include +#include +#include +#include + template class GlobalIndexer; diff --git a/include/bout/griddata.hxx b/include/bout/griddata.hxx index d5aff04b4d..61a8a2b40b 100644 --- a/include/bout/griddata.hxx +++ b/include/bout/griddata.hxx @@ -196,6 +196,8 @@ public: */ bool hasVar(const std::string& name) const override; + using GridDataSource::get; + /*! * Reads strings from options. Uses Options::get to handle * expressions diff --git a/include/bout/hypre_interface.hxx b/include/bout/hypre_interface.hxx index c595d4cca8..0f1617c5b3 100644 --- a/include/bout/hypre_interface.hxx +++ b/include/bout/hypre_interface.hxx @@ -12,8 +12,13 @@ #include "bout/boutexception.hxx" #include "bout/caliper_wrapper.hxx" #include "bout/field.hxx" +#include "bout/field2d.hxx" #include "bout/globalindexer.hxx" #include "bout/hyprelib.hxx" +#include "bout/options.hxx" +#include "bout/output.hxx" +#include "bout/paralleltransform.hxx" +#include "bout/region.hxx" #include "bout/utils.hxx" #include "HYPRE.h" @@ -23,7 +28,11 @@ #include "HYPRE_utilities.h" #include "_hypre_utilities.h" +#include +#include +#include #include +#include // BOUT_ENUM_CLASS does not work inside namespaces BOUT_ENUM_CLASS(HYPRE_SOLVER_TYPE, gmres, bicgstab, pcg); @@ -94,12 +103,16 @@ using BCValuesPtr = std::shared_ptr; * * b_ii * u_i + b_ij * u_j = rhs_i * - * - We also assume that each boundary equation has only one interior equation k - * coupled to it (such that k = j) with coupling coefficient a_ki + * - We also assume that each boundary equation has only one retained unknown j + * on the boundary row + * + * b_ii * u_i + b_ij * u_j = rhs_i + * + * - Any number of other equations k may couple to u_i with coefficient a_ki * * a_ki * u_i + a_kj * u_j + ... = rhs_k * - * - Each equation k is adjusted as follows: + * - Each such equation k is adjusted as follows: * * a_kj = a_kj - a_ki * b_ij / b_ii * a_ki = 0 @@ -123,47 +136,72 @@ using BCValuesPtr = std::shared_ptr; * * NOTE: Implementation in src/sys/hypre_interface.cxx */ -struct BCMatrixEquations { +struct BoundaryElimination { HYPRE_Int nb; HYPRE_Int* binum_array; HYPRE_Int* bjnum_array; + HYPRE_Int* bdep_array; HYPRE_Complex* bii_array; HYPRE_Complex* bij_array; HYPRE_Int na; + HYPRE_Int* aoffset_array; HYPRE_Int* aknum_array; HYPRE_Complex* aki_array; + std::vector reduction_order; + std::vector expansion_order; - BCMatrixEquations() = delete; + BoundaryElimination() = delete; - BCMatrixEquations(HYPRE_Int nrows, HYPRE_Int* ncols, HYPRE_BigInt* rows, - HYPRE_Int** row_indexes_ptr, HYPRE_BigInt* cols, - HYPRE_Complex* values, - HYPRE_Int nb, // number of boundary equations - HYPRE_Int* bi_array); // row i for each boundary equation + BoundaryElimination(HYPRE_Int nrows, HYPRE_Int* ncols, HYPRE_BigInt* rows, + HYPRE_Int** row_indexes_ptr, HYPRE_BigInt* cols, + HYPRE_Complex* values, + HYPRE_Int nb, // number of boundary equations + HYPRE_Int* bi_array); // row i for each boundary equation - ~BCMatrixEquations() { + ~BoundaryElimination() { // Free arrays HypreFree(binum_array); HypreFree(bjnum_array); + HypreFree(bdep_array); HypreFree(bii_array); HypreFree(bij_array); + HypreFree(aoffset_array); HypreFree(aknum_array); HypreFree(aki_array); } + HYPRE_Int size() const { return nb; } + + /// Copy boundary-row entries from an existing full-space vector. + BCValuesPtr copyBoundaryRowValues(const HYPRE_Complex* values) const; + + /// Evaluate the original boundary equations using the supplied full-space vector. + BCValuesPtr evaluateBoundaryEquations(const HYPRE_Complex* values) const; + /// Applies in-place modification of the rhs array. /// - /// Returns an array of boundary values that can be used to apply - /// boundary conditions to a solution vector. - BCValuesPtr adjustBCRightHandSideEquations(HYPRE_Complex* rhs); + /// Returns boundary values needed to reconstruct the full-space solution. + BCValuesPtr reduceRightHandSideInPlace(HYPRE_Complex* rhs) const; /// Apply boundary conditions to the solution. - /// Uses the BCValuesPtr returned from adjustBCRightHandSideEquations() - void adjustBCSolutionEquations(BCValuesPtr brhs, HYPRE_Complex* solution); + /// Uses the BCValuesPtr returned from reduceRightHandSideInPlace() + void expandSolutionInPlace(BCValuesPtr brhs, HYPRE_Complex* solution) const; + + /// Reconstruct the action of the full operator from the reduced matrix-vector product. + void expandMatvecResultInPlace(BCValuesPtr boundary_operator_values, + BCValuesPtr full_boundary_values, + HYPRE_Complex* result) const; }; -/// A shared pointer to a BCMatrixEquations object -using BCMatrixPtr = std::shared_ptr; +/// A shared pointer to a BoundaryElimination object +using BoundaryEliminationPtr = std::shared_ptr; + +struct BoundaryEliminationState { + BCValuesPtr interior_values; + BCValuesPtr boundary_values; +}; + +enum class HypreVectorReadMode { standard, solution, matvec }; template class HypreVector { @@ -177,6 +215,8 @@ class HypreVector { bool have_indices{false}; HYPRE_BigInt* I{nullptr}; HYPRE_Complex* V{nullptr}; + HYPRE_Complex* workV{nullptr}; + bool cache_current{false}; HypreLib hyprelib{}; public: @@ -193,6 +233,7 @@ public: checkHypreError(HYPRE_IJVectorDestroy(hypre_vector)); HypreFree(I); HypreFree(V); + HypreFree(workV); } // Disable copy, at least for now: not clear that HYPRE_IJVector is @@ -204,16 +245,18 @@ public: : comm(other.comm), jlower(other.jlower), jupper(other.jupper), vsize(other.vsize), indexConverter(other.indexConverter), location(other.location), initialised(other.initialised), have_indices(other.have_indices), I(other.I), - V(other.V) { + V(other.V), workV(other.workV), cache_current(other.cache_current) { std::swap(hypre_vector, other.hypre_vector); std::swap(parallel_vector, other.parallel_vector); other.initialised = false; other.have_indices = false; other.I = nullptr; other.V = nullptr; + other.workV = nullptr; + other.cache_current = false; } - HypreVector& operator=(HypreVector&& other) { + HypreVector& operator=(HypreVector&& other) noexcept { comm = other.comm; jlower = other.jlower; jupper = other.jupper; @@ -228,8 +271,12 @@ public: other.have_indices = false; I = other.I; V = other.V; + workV = other.workV; + cache_current = other.cache_current; other.I = nullptr; other.V = nullptr; + other.workV = nullptr; + other.cache_current = false; return *this; } @@ -259,7 +306,9 @@ public: initialised = true; HypreMalloc(I, vsize * sizeof(HYPRE_BigInt)); HypreMalloc(V, vsize * sizeof(HYPRE_Complex)); + HypreMalloc(workV, vsize * sizeof(HYPRE_Complex)); importValuesFromField(f); + assemble(); } /// Construct a vector with given index set, but don't set any values @@ -279,36 +328,70 @@ public: location = CELL_LOC::centre; HypreMalloc(I, vsize * sizeof(HYPRE_BigInt)); HypreMalloc(V, vsize * sizeof(HYPRE_Complex)); + HypreMalloc(workV, vsize * sizeof(HYPRE_Complex)); } - // Data for eliminating boundary equation - bool elimBErhs = false; - bool elimBEsol = false; - BCMatrixPtr bcmatrix; - BCValuesPtr bcvalues; /// Stores rhs values of BC rows - - void syncElimBErhs(HypreVector& rhs) { bcvalues = rhs.bcvalues; } - - void assemble() { + void assemble(const BoundaryElimination* boundary_elimination = nullptr, + BoundaryEliminationState* elimination_state = nullptr) { CALI_CXX_MARK_FUNCTION; - writeCacheToHypre(); + + parallel_vector = nullptr; + checkHypreError(HYPRE_IJVectorInitialize(hypre_vector)); + writeCacheToHypre(boundary_elimination, elimination_state); checkHypreError(HYPRE_IJVectorAssemble(hypre_vector)); checkHypreError(HYPRE_IJVectorGetObject(hypre_vector, reinterpret_cast(¶llel_vector))); + cache_current = true; } - void writeCacheToHypre() { - if (elimBErhs) { - bcvalues = bcmatrix->adjustBCRightHandSideEquations(V); + void writeCacheToHypre(const BoundaryElimination* boundary_elimination = nullptr, + BoundaryEliminationState* elimination_state = nullptr) { + HYPRE_Complex* values = V; + if (boundary_elimination != nullptr) { + ASSERT1(elimination_state != nullptr); + std::copy(V, V + vsize, workV); + values = workV; + elimination_state->interior_values = + boundary_elimination->reduceRightHandSideInPlace(values); + elimination_state->boundary_values = elimination_state->interior_values; + } else if (elimination_state != nullptr) { + elimination_state->interior_values = nullptr; + elimination_state->boundary_values = nullptr; } - checkHypreError(HYPRE_IJVectorSetValues(hypre_vector, vsize, I, V)); + checkHypreError(HYPRE_IJVectorSetValues(hypre_vector, vsize, I, values)); } - void readCacheFromHypre() { - checkHypreError(HYPRE_IJVectorGetValues(hypre_vector, vsize, I, V)); - if (elimBEsol) { - bcmatrix->adjustBCSolutionEquations(bcvalues, V); + void readCacheFromHypre(const BoundaryElimination* boundary_elimination = nullptr, + const BoundaryEliminationState* elimination_state = nullptr, + HypreVectorReadMode mode = HypreVectorReadMode::standard) { + HYPRE_Complex* values = V; + if ((boundary_elimination != nullptr) and (mode != HypreVectorReadMode::standard)) { + values = workV; } + checkHypreError(HYPRE_IJVectorGetValues(hypre_vector, vsize, I, values)); + if (boundary_elimination != nullptr) { + switch (mode) { + case HypreVectorReadMode::standard: + break; + case HypreVectorReadMode::solution: + ASSERT1(elimination_state != nullptr); + ASSERT1(elimination_state->interior_values != nullptr); + boundary_elimination->expandSolutionInPlace(elimination_state->interior_values, + values); + std::copy(values, values + vsize, V); + break; + case HypreVectorReadMode::matvec: + ASSERT1(elimination_state != nullptr); + ASSERT1(elimination_state->interior_values != nullptr); + ASSERT1(elimination_state->boundary_values != nullptr); + boundary_elimination->expandMatvecResultInPlace( + elimination_state->interior_values, elimination_state->boundary_values, + values); + std::copy(values, values + vsize, V); + break; + } + } + cache_current = true; } T toField() { @@ -317,7 +400,9 @@ public: T result(indexConverter->getMesh()); result.allocate().setLocation(location); - readCacheFromHypre(); + if ((!cache_current) and (parallel_vector != nullptr)) { + readCacheFromHypre(); + } // Note that this only populates boundaries to a depth of 1 int count = 0; BOUT_FOR_SERIAL(i, indexConverter->getRegionAll()) { @@ -347,13 +432,21 @@ public: ASSERT2(vec_i == vsize); have_indices = true; + cache_current = true; } HYPRE_IJVector get() { return hypre_vector; } const HYPRE_IJVector& get() const { return hypre_vector; } + const HYPRE_Complex* getValues() const { return V; } - HYPRE_ParVector getParallel() { return parallel_vector; } - const HYPRE_ParVector& getParallel() const { return parallel_vector; } + HYPRE_ParVector getParallel() { + ASSERT1(parallel_vector != nullptr); + return parallel_vector; + } + const HYPRE_ParVector& getParallel() const { + ASSERT1(parallel_vector != nullptr); + return parallel_vector; + } class Element { HypreVector* vector; @@ -392,6 +485,7 @@ public: ASSERT3(std::isfinite(value_)); value = value_; vector->V[vec_i] = value_; + vector->cache_current = true; return *this; } Element& operator+=(BoutReal value_) { @@ -399,6 +493,7 @@ public: value += value_; ASSERT3(std::isfinite(value)); vector->V[vec_i] += value_; + vector->cache_current = true; return *this; } operator BoutReal() const { return value; } @@ -421,11 +516,20 @@ public: friend void swap(HypreVector& lhs, HypreVector& rhs) { using std::swap; + swap(lhs.comm, rhs.comm); + swap(lhs.jlower, rhs.jlower); + swap(lhs.jupper, rhs.jupper); + swap(lhs.vsize, rhs.vsize); swap(lhs.hypre_vector, rhs.hypre_vector); swap(lhs.parallel_vector, rhs.parallel_vector); swap(lhs.indexConverter, rhs.indexConverter); swap(lhs.location, rhs.location); swap(lhs.initialised, rhs.initialised); + swap(lhs.have_indices, rhs.have_indices); + swap(lhs.I, rhs.I); + swap(lhs.V, rhs.V); + swap(lhs.workV, rhs.workV); + swap(lhs.cache_current, rhs.cache_current); } }; @@ -445,6 +549,9 @@ class HypreMatrix { std::vector* I; std::vector>* J; std::vector>* V; + /// Enable reduction of boundary equations before assembling the Hypre matrix. + bool use_boundary_elimination{false}; + BoundaryEliminationPtr boundary_elimination; HypreLib hyprelib{}; // todo also take care of I,J,V @@ -468,7 +575,9 @@ public: index_converter(other.index_converter), location(other.location), initialised(other.initialised), yoffset(other.yoffset), parallel_transform(other.parallel_transform), assembled(other.assembled), - num_rows(other.num_rows), I(other.I), J(other.J), V(other.V) { + num_rows(other.num_rows), I(other.I), J(other.J), V(other.V), + use_boundary_elimination(other.use_boundary_elimination), + boundary_elimination(other.boundary_elimination) { std::swap(hypre_matrix, other.hypre_matrix); std::swap(parallel_matrix, other.parallel_matrix); } @@ -489,6 +598,8 @@ public: I = other.I; J = other.J; V = other.V; + use_boundary_elimination = other.use_boundary_elimination; + boundary_elimination = other.boundary_elimination; return *this; } @@ -578,9 +689,9 @@ public: Element& operator+=(BoutReal value_) { ASSERT3(std::isfinite(value_)); - auto column_position = std::find(cbegin(positions), cend(positions), column); + auto column_position = std::ranges::find(positions, column); if (column_position != cend(positions)) { - const auto i = std::distance(cbegin(positions), column_position); + const auto i = std::distance(begin(positions), column_position); value += weights[i] * value_; } addValues(value_); @@ -593,7 +704,6 @@ public: void setValues(BoutReal value_) { CALI_CXX_MARK_FUNCTION; - TRACE("HypreMatrix setting values at ({}, {})", row, column); ASSERT3(!positions.empty()); std::vector values; std::transform( @@ -608,7 +718,6 @@ public: void addValues(BoutReal value_) { CALI_CXX_MARK_FUNCTION; - TRACE("HypreMatrix setting values at ({}, {})", row, column); ASSERT3(!positions.empty()); std::vector values; std::transform( @@ -777,7 +886,7 @@ public: pw.begin(), pw.end(), std::back_inserter(positions), [this, ny, nz](ParallelTransform::PositionsAndWeights p) -> HYPRE_Int { return this->index_converter->getGlobal( - ind_type(p.i * ny * nz + p.j * nz + p.k, ny, nz)); + ind_type((((p.i * ny) + p.j) * nz) + p.k, ny, nz)); }); std::transform(pw.begin(), pw.end(), std::back_inserter(weights), [](ParallelTransform::PositionsAndWeights p) -> HYPRE_Complex { @@ -787,33 +896,22 @@ public: return Element(*this, global_row, global_column, positions, weights); } - // Data for eliminating boundary equations - bool elimBE = false; - BCMatrixPtr bcmatrix; // Shared pointer - - void setElimBE() { elimBE = true; } - - void setElimBEVectors(HypreVector& sol, HypreVector& rhs) { - sol.elimBEsol = elimBE; - sol.bcmatrix = bcmatrix; - - rhs.elimBErhs = elimBE; - rhs.bcmatrix = bcmatrix; - } + /// Enable elimination of boundary equations during matrix assembly. + void setUseBoundaryElimination() { use_boundary_elimination = true; } void assemble() { CALI_CXX_MARK_FUNCTION; HYPRE_BigInt num_entries = 0; - HYPRE_BigInt* num_cols; + HYPRE_Int* num_cols; HYPRE_BigInt* cols; HYPRE_BigInt* rawI; HYPRE_Complex* vals; - HypreMalloc(num_cols, num_rows * sizeof(HYPRE_BigInt)); + HypreMalloc(num_cols, num_rows * sizeof(HYPRE_Int)); for (HYPRE_BigInt i = 0; i < num_rows; ++i) { - num_cols[i] = (*J)[i].size(); - num_entries += (*J)[i].size(); + num_cols[i] = static_cast((*J)[i].size()); + num_entries += num_cols[i]; } HypreMalloc(rawI, num_rows * sizeof(HYPRE_BigInt)); @@ -831,12 +929,10 @@ public: } // Eliminate boundary condition equations in hypre SetValues input arguments - if (elimBE) { + if (use_boundary_elimination) { HYPRE_Int* bi_array; HYPRE_Int* row_indexes; - // There must be an easier way to get nb - int nb = 0; - BOUT_FOR_SERIAL(i, index_converter->getRegionBndry()) { nb++; } + int nb = index_converter->getRegionBndry().size(); HypreMalloc(bi_array, nb * sizeof(HYPRE_Int)); nb = 0; BOUT_FOR_SERIAL(i, index_converter->getRegionBndry()) { @@ -844,7 +940,7 @@ public: nb++; } - bcmatrix = std::make_shared( + boundary_elimination = std::make_shared( num_rows, num_cols, rawI, &row_indexes, cols, vals, nb, bi_array); HypreFree(bi_array); @@ -852,6 +948,7 @@ public: row_indexes, cols, vals)); HypreFree(row_indexes); } else { + boundary_elimination = nullptr; checkHypreError( HYPRE_IJMatrixSetValues(*hypre_matrix, num_rows, num_cols, rawI, cols, vals)); } @@ -899,12 +996,17 @@ public: result.I = I; // We want the pointer to transfer so this works like a view result.J = J; result.V = V; + result.use_boundary_elimination = use_boundary_elimination; + result.boundary_elimination = boundary_elimination; return result; } HYPRE_IJMatrix get() { return *hypre_matrix; } const HYPRE_IJMatrix& get() const { return *hypre_matrix; } + const BoundaryElimination* getBoundaryElimination() const { + return boundary_elimination.get(); + } HYPRE_ParCSRMatrix getParallel() { return parallel_matrix; } const HYPRE_ParCSRMatrix& getParallel() const { return parallel_matrix; } @@ -914,8 +1016,33 @@ public: void computeAxpby(double alpha, HypreVector& x, double beta, HypreVector& y) { CALI_CXX_MARK_FUNCTION; + BoundaryEliminationState elimination_state; + if (boundary_elimination != nullptr) { + elimination_state.interior_values = + boundary_elimination->evaluateBoundaryEquations(x.getValues()); + elimination_state.boundary_values = + std::make_shared(boundary_elimination->size()); + BCValuesPtr y_boundary_values; + if (beta != 0.0) { + y_boundary_values = boundary_elimination->copyBoundaryRowValues(y.getValues()); + } + for (HYPRE_Int i = 0; i < boundary_elimination->size(); ++i) { + const HYPRE_Complex operator_value = elimination_state.interior_values->data[i]; + elimination_state.interior_values->data[i] = alpha * operator_value; + elimination_state.boundary_values->data[i] = + alpha * operator_value + + (y_boundary_values != nullptr ? beta * y_boundary_values->data[i] : 0.0); + } + } + + x.assemble(); + y.assemble(); checkHypreError(HYPRE_ParCSRMatrixMatvec(alpha, parallel_matrix, x.getParallel(), beta, y.getParallel())); + y.readCacheFromHypre(boundary_elimination.get(), + boundary_elimination != nullptr ? &elimination_state : nullptr, + boundary_elimination != nullptr ? HypreVectorReadMode::matvec + : HypreVectorReadMode::standard); } // y = A*x @@ -923,8 +1050,7 @@ public: void computeAx(HypreVector& x, HypreVector& y) { CALI_CXX_MARK_FUNCTION; - checkHypreError(HYPRE_ParCSRMatrixMatvec(1.0, parallel_matrix, x.getParallel(), 0.0, - y.getParallel())); + computeAxpby(1.0, x, 0.0, y); } }; @@ -1143,6 +1269,11 @@ public: ASSERT2(A != nullptr); ASSERT2(x != nullptr); ASSERT2(b != nullptr); + BoundaryEliminationState elimination_state; + const auto* boundary_elimination = A->getBoundaryElimination(); + b->assemble(boundary_elimination, + boundary_elimination != nullptr ? &elimination_state : nullptr); + x->assemble(); if (not solver_setup) { checkHypreError( solverSetup(solver, A->getParallel(), b->getParallel(), x->getParallel())); @@ -1152,6 +1283,12 @@ public: solve_err = checkHypreError( solverSolve(solver, A->getParallel(), b->getParallel(), x->getParallel())); + x->readCacheFromHypre(boundary_elimination, + boundary_elimination != nullptr ? &elimination_state : nullptr, + boundary_elimination != nullptr + ? HypreVectorReadMode::solution + : HypreVectorReadMode::standard); + return solve_err; } }; // class HypreSystem diff --git a/include/bout/mesh.hxx b/include/bout/mesh.hxx index 9995c6337f..46f8351539 100644 --- a/include/bout/mesh.hxx +++ b/include/bout/mesh.hxx @@ -4,20 +4,8 @@ * Interface for mesh classes. Contains standard variables and useful * routines. * - * Changelog - * ========= - * - * 2014-12 Ben Dudson - * * Removing coordinate system into separate - * Coordinates class - * * Adding index derivative functions from derivs.cxx - * - * 2010-06 Ben Dudson, Sean Farley - * * Initial version, adapted from GridData class - * * Incorporates code from topology.cpp and Communicator - * ************************************************************************** - * Copyright 2010-2025 BOUT++ contributors + * Copyright 2010-2026 BOUT++ contributors * * Contact: Ben Dudson, dudson2@llnl.gov * @@ -629,25 +617,7 @@ public: return getCoordinatesSmart(location).get(); }; - std::shared_ptr - getCoordinatesSmart(const CELL_LOC location = CELL_CENTRE) { - ASSERT1(location != CELL_DEFAULT); - ASSERT1(location != CELL_VSHIFT); - - auto found = coords_map.find(location); - if (found != coords_map.end()) { - // True branch most common, returns immediately - return found->second; - } - - // No coordinate system set. Create default - // Note that this can't be allocated here due to incomplete type - // (circular dependency between Mesh and Coordinates) - auto inserted = coords_map.emplace(location, nullptr); - inserted.first->second = createDefaultCoordinates(location); - inserted.first->second->geometry(false); - return inserted.first->second; - } + std::shared_ptr getCoordinatesSmart(CELL_LOC location = CELL_CENTRE); std::shared_ptr getCoordinatesConst(const CELL_LOC location = CELL_CENTRE) const { @@ -855,7 +825,8 @@ public: // Switch for communication of corner guard and boundary cells const bool include_corner_cells; - std::optional getCommonRegion(std::optional, std::optional); + std::optional getCommonRegion(std::optional lhs, + std::optional rhs); size_t getRegionID(const std::string& region) const; const Region& getRegion(size_t RegionID) const { return region3D[RegionID]; } const Region& getRegion(std::optional RegionID) const { diff --git a/include/bout/metric_tensor.hxx b/include/bout/metric_tensor.hxx new file mode 100644 index 0000000000..fa94d504a1 --- /dev/null +++ b/include/bout/metric_tensor.hxx @@ -0,0 +1,113 @@ +#ifndef BOUT_METRIC_TENSOR_HXX +#define BOUT_METRIC_TENSOR_HXX + +#include +#include +#include +#include + +#include + +namespace bout { +#if BOUT_USE_METRIC_3D +using FieldMetric = Field3D; +using FieldMetricParallel = Field3DParallel; +#else +using FieldMetric = Field2D; +using FieldMetricParallel = Field2D; +#endif +} // namespace bout + +class Coordinates; +struct MetricNormaliser; + +class MetricTensor { +public: + friend class Coordinates; + +#if BOUT_USE_METRIC_3D + using Metric2DSlice = const BoutReal*; +#else + using Metric2DSlice = const BoutReal&; +#endif + using FieldMetric = bout::FieldMetric; + + MetricTensor(const MetricTensor&) = default; + MetricTensor(MetricTensor&&) = default; + MetricTensor& operator=(const MetricTensor&) = default; + MetricTensor& operator=(MetricTensor&&) = default; + MetricTensor(FieldMetric g11, FieldMetric g22, FieldMetric g33, FieldMetric g12, + FieldMetric g13, FieldMetric g23); + + MetricTensor(BoutReal g11, BoutReal g22, BoutReal g33, BoutReal g12, BoutReal g13, + BoutReal g23, Mesh* mesh); + virtual ~MetricTensor() = default; + + /// Check that tensors are positive (if expected) and finite (always) + void check(int ystart); + + const FieldMetric& g11() const { return g11_m; } + const FieldMetric& g22() const { return g22_m; } + const FieldMetric& g33() const { return g33_m; } + const FieldMetric& g12() const { return g12_m; } + const FieldMetric& g13() const { return g13_m; } + const FieldMetric& g23() const { return g23_m; } + + const BoutReal& g11(int x, int y, int z) const { return g11_m(x, y, z); } + const BoutReal& g22(int x, int y, int z) const { return g22_m(x, y, z); } + const BoutReal& g33(int x, int y, int z) const { return g33_m(x, y, z); } + const BoutReal& g12(int x, int y, int z) const { return g12_m(x, y, z); } + const BoutReal& g13(int x, int y, int z) const { return g13_m(x, y, z); } + const BoutReal& g23(int x, int y, int z) const { return g23_m(x, y, z); } + + Metric2DSlice g11(int x, int y) const { return g11_m(x, y); } + Metric2DSlice g22(int x, int y) const { return g22_m(x, y); } + Metric2DSlice g33(int x, int y) const { return g33_m(x, y); } + Metric2DSlice g12(int x, int y) const { return g12_m(x, y); } + Metric2DSlice g13(int x, int y) const { return g13_m(x, y); } + Metric2DSlice g23(int x, int y) const { return g23_m(x, y); } + + /// Transforms the MetricTensor by applying the given function to every component + template + void map(F function) { + g11_m = function(g11_m); + g22_m = function(g22_m); + g33_m = function(g33_m); + g12_m = function(g12_m); + g13_m = function(g13_m); + g23_m = function(g23_m); + } + + void communicate(); + + template + void normaliseMetric(const MetricNormaliser& norm, const F& op); + +private: + FieldMetric g11_m, g22_m, g33_m, g12_m, g13_m, g23_m; +}; + +class CovariantMetricTensor; +class ContravariantMetricTensor; + +class CovariantMetricTensor : public MetricTensor { +public: + using MetricTensor::MetricTensor; + + auto inverse(const std::string& region = "RGN_ALL", bool communicate = true) + -> ContravariantMetricTensor; + + void normaliseMetric(const MetricNormaliser& norm); +}; + +class ContravariantMetricTensor : public MetricTensor { +public: + using MetricTensor::MetricTensor; + + auto inverse(const std::string& region = "RGN_ALL", bool communicate = true) + -> CovariantMetricTensor; + + void normaliseMetric(const MetricNormaliser& norm); +}; + +#endif //BOUT_METRIC_TENSOR_HXX diff --git a/include/bout/monitor.hxx b/include/bout/monitor.hxx index 991cbd65c5..d1363e8009 100644 --- a/include/bout/monitor.hxx +++ b/include/bout/monitor.hxx @@ -33,7 +33,7 @@ class Monitor { public: /// A \p timestep_ of -1 defaults to the the frequency of the BOUT++ /// output monitor - Monitor(BoutReal timestep_ = -1) : timestep(timestep_){}; + Monitor(BoutReal timestep_ = -1) : timestep(timestep_) {}; virtual ~Monitor() = default; @@ -48,7 +48,7 @@ public: virtual int call(Solver* solver, BoutReal time, int iter, int nout) = 0; /// Callback function for when a clean shutdown is initiated - virtual void cleanup(){}; + virtual void cleanup() {}; virtual void outputVars([[maybe_unused]] Options& options, [[maybe_unused]] const std::string& time_dimension) {} diff --git a/include/bout/multiostream.hxx b/include/bout/multiostream.hxx index ca3cc2d0c7..d038c8f659 100644 --- a/include/bout/multiostream.hxx +++ b/include/bout/multiostream.hxx @@ -17,7 +17,7 @@ private: public: void add(std::basic_ostream& str) { - auto pos = std::find(streams_.begin(), streams_.end(), &str); + auto pos = std::ranges::find(streams_, &str); // Already been added if (pos != streams_.end()) { @@ -28,7 +28,7 @@ public: } void remove(std::basic_ostream& str) { - auto pos = std::find(streams_.begin(), streams_.end(), &str); + auto pos = std::ranges::find(streams_, &str); if (pos != streams_.end()) { streams_.erase(pos); @@ -74,8 +74,8 @@ private: public: multiostream() - : multioutbuf_init(), std::basic_ostream( - multioutbuf_init::buf()) {} + : multioutbuf_init(), + std::basic_ostream(multioutbuf_init::buf()) {} void add(std::basic_ostream& str) { multioutbuf_init::buf()->add(str); diff --git a/include/bout/operatorstencil.hxx b/include/bout/operatorstencil.hxx index 118dc7a068..1135a79fc7 100644 --- a/include/bout/operatorstencil.hxx +++ b/include/bout/operatorstencil.hxx @@ -33,9 +33,7 @@ #include #include #include -#include #include -#include #include #include @@ -45,17 +43,17 @@ /// subtracted from them. template struct IndexOffset { - static_assert( - std::is_same_v || std::is_same_v || std::is_same_v, - "IndexOffset only works with SpecificInd types"); + static_assert(std::is_same_v || std::is_same_v + || std::is_same_v, + "IndexOffset only works with SpecificInd types"); int dx = 0, dy = 0, dz = 0; - const inline IndexOffset xp(int delta_x = 1) const { return {dx + delta_x, dy, dz}; } - const inline IndexOffset xm(int delta_x = 1) const { return xp(-delta_x); } - const inline IndexOffset yp(int delta_y = 1) const { return {dx, dy + delta_y, dz}; } - const inline IndexOffset ym(int delta_y = 1) const { return yp(-delta_y); } - const inline IndexOffset zp(int delta_z = 1) const { return {dx, dy, dz + delta_z}; } - const inline IndexOffset zm(int delta_z = 1) const { return zp(-delta_z); } + IndexOffset xp(int delta_x = 1) const { return {dx + delta_x, dy, dz}; } + IndexOffset xm(int delta_x = 1) const { return xp(-delta_x); } + IndexOffset yp(int delta_y = 1) const { return {dx, dy + delta_y, dz}; } + IndexOffset ym(int delta_y = 1) const { return yp(-delta_y); } + IndexOffset zp(int delta_z = 1) const { return {dx, dy, dz + delta_z}; } + IndexOffset zm(int delta_z = 1) const { return zp(-delta_z); } IndexOffset& operator+=(const IndexOffset& n) { dx += n.dx; @@ -69,46 +67,29 @@ struct IndexOffset { dz -= n.dz; return *this; } -}; -template -inline bool operator==(const IndexOffset& lhs, const IndexOffset& rhs) { - return lhs.dx == rhs.dx && lhs.dy == rhs.dy && lhs.dz == rhs.dz; -} -template -inline bool operator!=(const IndexOffset& lhs, const IndexOffset& rhs) { - return !operator==(lhs, rhs); -} -template -inline bool operator<(const IndexOffset& lhs, const IndexOffset& rhs) { - if (lhs.dx != rhs.dx) { - return lhs.dx < rhs.dx; - } else if (lhs.dy != rhs.dy) { - return lhs.dy < rhs.dy; - } else { - return lhs.dz < rhs.dz; - } -} + auto operator<=>(const IndexOffset&) const = default; +}; template -const inline IndexOffset operator+(IndexOffset lhs, const IndexOffset& rhs) { +inline IndexOffset operator+(IndexOffset lhs, const IndexOffset& rhs) { return lhs += rhs; } template -const inline IndexOffset operator-(IndexOffset lhs, const IndexOffset& rhs) { +inline IndexOffset operator-(IndexOffset lhs, const IndexOffset& rhs) { return lhs -= rhs; } template -const inline T operator+(const T& lhs, const IndexOffset& rhs) { +inline T operator+(const T& lhs, const IndexOffset& rhs) { return lhs.offset(rhs.dx, rhs.dy, rhs.dz); } template -const inline T operator+(const IndexOffset& lhs, const T& rhs) { +inline T operator+(const IndexOffset& lhs, const T& rhs) { return operator+(rhs, lhs); } template -const inline T operator-(const T& lhs, const IndexOffset& rhs) { +inline T operator-(const T& lhs, const IndexOffset& rhs) { // If CHECKLEVEL >= 3 then SpecificInd.zm() complains about // negative values. return lhs.offset(-rhs.dx, -rhs.dy, -rhs.dz); @@ -133,9 +114,9 @@ using OffsetIndPerp = IndexOffset; template class OperatorStencil { public: - static_assert( - std::is_same_v || std::is_same_v || std::is_same_v, - "OperatorStencil only works with SpecificInd types"); + static_assert(std::is_same_v || std::is_same_v + || std::is_same_v, + "OperatorStencil only works with SpecificInd types"); using offset = IndexOffset; using stencil_part = std::vector; using stencil_test = std::function; @@ -271,14 +252,11 @@ OperatorStencil squareStencil(Mesh* localmesh) { std::vector> offsetsVec(offsets.begin(), offsets.end()); stencil.add( [localmesh](T ind) -> bool { - return ( - localmesh->xstart <= ind.x() && ind.x() <= localmesh->xend - && (std::is_same_v< - T, - IndPerp> || (localmesh->ystart <= ind.y() && ind.y() <= localmesh->yend)) - && (std::is_same_v< - T, - Ind2D> || (localmesh->zstart <= ind.z() && ind.z() <= localmesh->zend))); + return (localmesh->xstart <= ind.x() && ind.x() <= localmesh->xend + && (std::is_same_v + || (localmesh->ystart <= ind.y() && ind.y() <= localmesh->yend)) + && (std::is_same_v + || (localmesh->zstart <= ind.z() && ind.z() <= localmesh->zend))); }, offsetsVec); stencil.add([](T UNUSED(ind)) -> bool { return true; }, {zero}); @@ -308,14 +286,11 @@ OperatorStencil starStencil(Mesh* localmesh) { std::vector> offsetsVec(offsets.begin(), offsets.end()); stencil.add( [localmesh](T ind) -> bool { - return ( - localmesh->xstart <= ind.x() && ind.x() <= localmesh->xend - && (std::is_same_v< - T, - IndPerp> || (localmesh->ystart <= ind.y() && ind.y() <= localmesh->yend)) - && (std::is_same_v< - T, - Ind2D> || (localmesh->zstart <= ind.z() && ind.z() <= localmesh->zend))); + return (localmesh->xstart <= ind.x() && ind.x() <= localmesh->xend + && (std::is_same_v + || (localmesh->ystart <= ind.y() && ind.y() <= localmesh->yend)) + && (std::is_same_v + || (localmesh->zstart <= ind.z() && ind.z() <= localmesh->zend))); }, offsetsVec); stencil.add([](T UNUSED(ind)) -> bool { return true; }, {zero}); diff --git a/include/bout/options.hxx b/include/bout/options.hxx index 87503b0a22..b7eb9eb29c 100644 --- a/include/bout/options.hxx +++ b/include/bout/options.hxx @@ -55,6 +55,7 @@ class Options; #include #include +#include #include #include #include @@ -354,9 +355,7 @@ public: std::map attributes; /// Return true if this value has attribute \p key - bool hasAttribute(const std::string& key) const { - return attributes.find(key) != attributes.end(); - } + bool hasAttribute(const std::string& key) const { return attributes.contains(key); } /// Set attributes, overwriting any already set /// @@ -411,9 +410,8 @@ public: /// Edit distance from original search term std::string::size_type distance; /// Comparison operator so this works in a std::multiset - friend bool operator<(const FuzzyMatch& lhs, const FuzzyMatch& rhs) { - return lhs.distance < rhs.distance; - } + auto operator<=>(const FuzzyMatch& rhs) const { return distance <=> rhs.distance; } + bool operator==(const FuzzyMatch& rhs) const { return distance == rhs.distance; } }; /// Find approximate matches for \p name throughout the whole @@ -579,7 +577,7 @@ public: value_used = true; // Note this is mutable output_info << "\tOption " << full_name << " = " << val; - if (attributes.count("source")) { + if (attributes.contains("source")) { // Specify the source of the setting output_info << " (" << bout::utils::variantToString(attributes.at("source")) << ")"; } @@ -907,7 +905,7 @@ private: // If already set, and not time evolving then check for changing values // If a variable has a "time_dimension" attribute then it is assumed // that updates to the value is ok and don't need to be forced. - if (isSet() && (attributes.find("time_dimension") == attributes.end())) { + if (isSet() && (!attributes.contains("time_dimension"))) { // Check if current value the same as new value if (!bout::utils::variantEqualTo(value, val)) { if (force or !bout::utils::variantEqualTo(attributes["source"], source)) { @@ -934,6 +932,9 @@ private: bool similar(T lhs, T rhs) const { return lhs == rhs; } + + /// Replaces the start of the name of an Option and any children + void recursively_update_names(size_t len, const std::string& new_prefix); }; // Specialised assign methods for types stored in ValueType diff --git a/include/bout/parallel_boundary_op.hxx b/include/bout/parallel_boundary_op.hxx index 5139cd5090..6bd692f2a2 100644 --- a/include/bout/parallel_boundary_op.hxx +++ b/include/bout/parallel_boundary_op.hxx @@ -7,14 +7,10 @@ #include "bout/boutexception.hxx" #include "bout/field3d.hxx" #include "bout/field_factory.hxx" -#include "bout/parallel_boundary_region.hxx" #include "bout/sys/expressionparser.hxx" #include "bout/unused.hxx" -#include "bout/utils.hxx" -#include #include -#include ////////////////////////////////////////////////// // Base class @@ -104,9 +100,9 @@ private: enum class ValueType { GEN, FIELD, REAL }; const ValueType value_type{ValueType::REAL}; - BoutReal getValue(const bout::boundary::BoundaryRegionIterFCI& bndry, BoutReal t); - BoutReal getValue(const bout::boundary::BoundaryRegionIterX& bndry, BoutReal t); - BoutReal getValue(const bout::boundary::BoundaryRegionIterY& bndry, BoutReal t); + BoutReal getValue(const bout::boundary::BoundaryRegionFCI::Point& bndry, BoutReal t); + BoutReal getValue(const bout::boundary::BoundaryRegionX::Iterator& bndry, BoutReal t); + BoutReal getValue(const bout::boundary::BoundaryRegionY::Iterator& bndry, BoutReal t); template friend class BoundaryOpParTemp; @@ -211,7 +207,7 @@ public: void apply(Field3D& f, BoutReal t) override { if (bndry != nullptr) { f.ynext(bndry->dir()).allocate(); // Ensure unique before modifying - auto dy = f.getCoordinates()->dy; + auto dy = f.getCoordinates()->dy(); for (auto pnt : *bndry) { BoutReal value = getValue(pnt, t); if (isNeumann) { @@ -222,7 +218,7 @@ public: } if (bndryX != nullptr) { f.allocate(); - auto dy = f.getCoordinates()->dx; + auto dy = f.getCoordinates()->dx(); for (auto pnt : *bndryX) { BoutReal value = getValue(pnt, t); if (isNeumann) { @@ -233,7 +229,7 @@ public: } if (bndryY != nullptr) { f.allocate(); - auto dy = f.getCoordinates()->dy; + auto dy = f.getCoordinates()->dy(); for (auto pnt : *bndryY) { BoutReal value = getValue(pnt, t); if (isNeumann) { @@ -251,27 +247,27 @@ public: class BoundaryOpPar_dirichlet_o1 : public BoundaryOpParTemp { public: using BoundaryOpParTemp::BoundaryOpParTemp; - template - static void apply_stencil(Field3D& f, T& pnt, BoutReal value) { - pnt.dirichlet_o1(f, value); + template + static void apply_stencil(Field3D& f, Iter& pnt, BoutReal value) { + dirichlet_o1(pnt, f, value); } }; class BoundaryOpPar_dirichlet_o2 : public BoundaryOpParTemp { public: using BoundaryOpParTemp::BoundaryOpParTemp; - template - static void apply_stencil(Field3D& f, T& pnt, BoutReal value) { - pnt.dirichlet_o2(f, value); + template + static void apply_stencil(Field3D& f, Iter& pnt, BoutReal value) { + dirichlet_o2(pnt, f, value); } }; class BoundaryOpPar_dirichlet_o3 : public BoundaryOpParTemp { public: using BoundaryOpParTemp::BoundaryOpParTemp; - template - static void apply_stencil(Field3D& f, T& pnt, BoutReal value) { - pnt.dirichlet_o3(f, value); + template + static void apply_stencil(Field3D& f, Iter& pnt, BoutReal value) { + dirichlet_o3(pnt, f, value); } }; @@ -279,9 +275,9 @@ class BoundaryOpPar_neumann_o1 : public BoundaryOpParTemp { public: using BoundaryOpParTemp::BoundaryOpParTemp; - template - static void apply_stencil(Field3D& f, T& pnt, BoutReal value) { - pnt.neumann_o1(f, value); + template + static void apply_stencil(Field3D& f, Iter& pnt, BoutReal value) { + neumann_o1(pnt, f, value); } }; @@ -289,9 +285,9 @@ class BoundaryOpPar_neumann_o2 : public BoundaryOpParTemp { public: using BoundaryOpParTemp::BoundaryOpParTemp; - template - static void apply_stencil(Field3D& f, T& pnt, BoutReal value) { - pnt.neumann_o2(f, value); + template + static void apply_stencil(Field3D& f, Iter& pnt, BoutReal value) { + neumann_o2(pnt, f, value); } }; @@ -299,9 +295,9 @@ class BoundaryOpPar_neumann_o3 : public BoundaryOpParTemp { public: using BoundaryOpParTemp::BoundaryOpParTemp; - template - static void apply_stencil(Field3D& f, T& pnt, BoutReal value) { - pnt.neumann_o3(f, value); + template + static void apply_stencil(Field3D& f, Iter& pnt, BoutReal value) { + neumann_o3(pnt, f, value); } }; diff --git a/include/bout/parallel_boundary_region.hxx b/include/bout/parallel_boundary_region.hxx deleted file mode 100644 index 2d17288a3d..0000000000 --- a/include/bout/parallel_boundary_region.hxx +++ /dev/null @@ -1,196 +0,0 @@ -#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 -#include -#include -#include -#include -#include - -#include "bout/build_defines.hxx" -#include "bout/field2d.hxx" -#include "bout/region.hxx" -#include "bout/sys/parallel_stencils.hxx" -#include "bout/utils.hxx" -#include -#include - -/** - * Boundary region for parallel direction. This contains a vector of points that are - * inside the boundary. - * - */ - -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; - 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) {}; -}; - -inline bool operator<(const Indices& lhs, const Indices& rhs) { - return lhs.index < rhs.index; -} -inline bool operator<(const Indices& lhs, const Ind3D& rhs) { return lhs.index < rhs; } - -using IndicesVec = std::vector; -using IndicesIter = IndicesVec::iterator; -using IndicesIterConst = IndicesVec::const_iterator; - -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) {}; - - std::size_t size() { return this->bndry_points.size(); } - - // getter - Ind3D ind() const { return bndry_position->index; } - BoutReal s_x() const { return bndry_position->intersection.s_x; } - BoutReal s_y() const { return bndry_position->intersection.s_y; } - BoutReal s_z() const { return bndry_position->intersection.s_z; } - BoutReal length([[maybe_unused]] CELL_LOC loc) const { - ASSERT3(loc == CELL_CENTRE); - 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 valid) { bndry_position->valid = valid; } - -private: - const IndicesVec& bndry_points; - IndicesIter bndry_position; - - constexpr static BoutReal small_value = 1e-2; - int _dir; - -public: - int dir() const { return _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); - return std::ranges::any_of(bndry_points.begin(), bndry_points.end(), - [&i2](auto i1) { return i1.index == i2; }); - } - - // 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); - } - - int dir() const { return _dir; } - -private: - int _dir; - /// 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 d9ed397bb5..81653aa255 100644 --- a/include/bout/paralleltransform.hxx +++ b/include/bout/paralleltransform.hxx @@ -91,10 +91,6 @@ public: /// require a twist-shift at branch cuts on closed field lines? virtual bool requiresTwistShift(bool twist_shift_enabled, YDirectionType ytype) = 0; - /// Can be implemented to load parallel metrics - /// Needed by FCI - virtual void loadParallelMetrics([[maybe_unused]] Coordinates* coords) {} - protected: /// This method should be called in the constructor to check that if the grid /// has a 'parallel_transform' variable, it has the correct value @@ -124,6 +120,8 @@ public: */ void calcParallelSlices(Field3D& f) override; + using ParallelTransform::toFieldAligned; + /*! * The field is already aligned in Y, so this * does nothing @@ -141,6 +139,8 @@ public: return result.setDirectionY(YDirectionType::Aligned); } + using ParallelTransform::fromFieldAligned; + /*! * The field is already aligned in Y, so this * does nothing @@ -196,6 +196,8 @@ public: */ void calcParallelSlices(Field3D& f) override; + using ParallelTransform::toFieldAligned; + /*! * Uses FFTs and a phase shift to align the grid points * with the y coordinate (along magnetic field usually). @@ -209,6 +211,8 @@ public: FieldPerp toFieldAligned(const FieldPerp& f, const std::string& region = "RGN_ALL") override; + using ParallelTransform::fromFieldAligned; + /*! * Converts a field back to X-Z orthogonal coordinates * from field aligned coordinates. diff --git a/include/bout/petsc_interface.hxx b/include/bout/petsc_interface.hxx index 7b911e8755..c81ce5947e 100644 --- a/include/bout/petsc_interface.hxx +++ b/include/bout/petsc_interface.hxx @@ -406,7 +406,7 @@ public: Element& operator+=(BoutReal val) { ASSERT3(std::isfinite(val)); - auto columnPosition = std::find(positions.begin(), positions.end(), petscCol); + auto columnPosition = std::ranges::find(positions, petscCol); if (columnPosition != positions.end()) { const int index = std::distance(positions.begin(), columnPosition); value += weights[index] * val; diff --git a/include/bout/petsc_preconditioner.hxx b/include/bout/petsc_preconditioner.hxx index be283413fc..f413d28d3b 100644 --- a/include/bout/petsc_preconditioner.hxx +++ b/include/bout/petsc_preconditioner.hxx @@ -10,8 +10,18 @@ #ifndef BOUT_PETSC_PRECONDITIONER_H #define BOUT_PETSC_PRECONDITIONER_H +#include "bout/bout_enum_class.hxx" #include "bout/build_defines.hxx" +BOUT_ENUM_CLASS_NS(bout, PetscMatrixExportFormat, binary, ascii); + +BOUT_ENUM_CLASS_NS( + bout, JacobianExportKind, + system, ///< Jacobian of the full nonlinear system solved by the solver + scaled, ///< Jacobian after solver-coordinate transforms such as variable scaling + rhs ///< Jacobian of the raw model RHS in physical variables +); + #if BOUT_HAS_PETSC #include "bout/petsc_interface.hxx" @@ -22,6 +32,8 @@ #include #include +#include + class Options; class Field3D; @@ -73,6 +85,20 @@ public: Mat jacobian() const { return Jfd; } MatFDColoring coloring() const { return fdcoloring; } + /// Save an arbitrary PETSc matrix to disk using ``MatView``. + /// + /// This is a low-level utility used by solver-specific diagnostic code. It only + /// writes the matrix itself in PETSc binary or ASCII format; companion metadata + /// such as variable names and index mapping must be written separately by the + /// calling solver. + static PetscErrorCode saveMatrix( + Mat matrix, const std::string& filename, + bout::PetscMatrixExportFormat format = bout::PetscMatrixExportFormat::binary); + /// Save the internally owned coloring Jacobian matrix ``Jfd``. + PetscErrorCode saveMatrix( + const std::string& filename, + bout::PetscMatrixExportFormat format = bout::PetscMatrixExportFormat::binary) const; + void reset(); private: @@ -86,6 +112,9 @@ private: // unconditionally in PETSc-enabled compilation units. class PetscPreconditioner { public: + void saveMatrix(const std::string& UNUSED(filename), + bout::PetscMatrixExportFormat UNUSED(format) = + bout::PetscMatrixExportFormat::binary) const {} void reset() {} }; diff --git a/include/bout/physicsmodel.hxx b/include/bout/physicsmodel.hxx index dc583a8c1a..2dc50dcfee 100644 --- a/include/bout/physicsmodel.hxx +++ b/include/bout/physicsmodel.hxx @@ -3,17 +3,10 @@ * * @brief Base class for Physics Models * - * - * - * Changelog: - * - * 2013-08 Ben Dudson - * * Initial version - * ************************************************************************** * Copyright 2013-2025 BOUT++ contributors * - * Contact: Ben Dudson, bd512@york.ac.uk + * Contact: Ben Dudson, dudson2@llnl.gov * * This file is part of BOUT++. * diff --git a/include/bout/region.hxx b/include/bout/region.hxx index df6769eda4..e47be5a551 100644 --- a/include/bout/region.hxx +++ b/include/bout/region.hxx @@ -53,6 +53,7 @@ #include "bout/assert.hxx" #include "bout/bout_types.hxx" #include "bout/boutexception.hxx" +#include "bout/build_config.hxx" #include "bout/build_defines.hxx" #include "bout/openmpwrap.hxx" // IWYU pragma: keep @@ -240,47 +241,49 @@ struct SpecificInd { /// and is determined by the `dir` template argument. The offset corresponds /// to the `dd` template argument. template - inline SpecificInd plus() const { + SpecificInd plus() const { static_assert(dir == DIRECTION::X || dir == DIRECTION::Y || dir == DIRECTION::Z || dir == DIRECTION::YAligned || dir == DIRECTION::YOrthogonal, "Unhandled DIRECTION in SpecificInd::plus"); switch (dir) { - case (DIRECTION::X): + case DIRECTION::X: return xp(dd); - case (DIRECTION::Y): - case (DIRECTION::YAligned): - case (DIRECTION::YOrthogonal): + case DIRECTION::Y: + case DIRECTION::YAligned: + case DIRECTION::YOrthogonal: return yp(dd); - case (DIRECTION::Z): + case DIRECTION::Z: return zp(dd); } + BOUT_UNREACHABLE(); } /// Templated routine to return index.?m(offset), where `?` is one of {x,y,z} /// and is determined by the `dir` template argument. The offset corresponds /// to the `dd` template argument. template - inline SpecificInd minus() const { + SpecificInd minus() const { static_assert(dir == DIRECTION::X || dir == DIRECTION::Y || dir == DIRECTION::Z || dir == DIRECTION::YAligned || dir == DIRECTION::YOrthogonal, "Unhandled DIRECTION in SpecificInd::minus"); switch (dir) { - case (DIRECTION::X): + case DIRECTION::X: return xm(dd); - case (DIRECTION::Y): - case (DIRECTION::YAligned): - case (DIRECTION::YOrthogonal): + case DIRECTION::Y: + case DIRECTION::YAligned: + case DIRECTION::YOrthogonal: return ym(dd); - case (DIRECTION::Z): + case DIRECTION::Z: return zm(dd); } + BOUT_UNREACHABLE(); } - inline SpecificInd xp(int dx = 1) const { return {ind + (dx * ny * nz), ny, nz}; } + SpecificInd xp(int dx = 1) const { return {ind + (dx * ny * nz), ny, nz}; } /// The index one point -1 in x - inline SpecificInd xm(int dx = 1) const { return xp(-dx); } + SpecificInd xm(int dx = 1) const { return xp(-dx); } /// The index one point +1 in y - inline SpecificInd yp(int dy = 1) const { + SpecificInd yp(int dy = 1) const { #if CHECK >= 4 if (y() + dy < 0 or y() + dy >= ny) { throw BoutException("Offset in y ({:d}) would go out of bounds at {:d}", dy, ind); @@ -290,12 +293,12 @@ struct SpecificInd { return {ind + (dy * nz), ny, nz}; } /// The index one point -1 in y - inline SpecificInd ym(int dy = 1) const { return yp(-dy); } + SpecificInd ym(int dy = 1) const { return yp(-dy); } /// The index one point +1 in z. Wraps around zend to zstart /// An alternative, non-branching calculation is : /// ind + dz - nz * ((ind + dz) / nz - ind / nz) /// but this appears no faster (and perhaps slower). - inline SpecificInd zp(int dz = 1) const { + SpecificInd zp(int dz = 1) const { ASSERT3(dz >= 0); dz = dz <= nz ? dz : dz % nz; //Fix in case dz > nz, if not force it to be in range return {(ind + dz) % nz < dz ? ind - nz + dz : ind + dz, ny, nz}; @@ -304,58 +307,29 @@ struct SpecificInd { /// An alternative, non-branching calculation is : /// ind - dz + nz * ( (nz + ind) / nz - (nz + ind - dz) / nz) /// but this appears no faster (and perhaps slower). - inline SpecificInd zm(int dz = 1) const { + SpecificInd zm(int dz = 1) const { dz = dz <= nz ? dz : dz % nz; //Fix in case dz > nz, if not force it to be in range ASSERT3(dz >= 0); return {(ind) % nz < dz ? ind + nz - dz : ind - dz, ny, nz}; } /// Automatically select zm or zp depending on sign - inline SpecificInd zpm(int dz) const { return dz > 0 ? zp(dz) : zm(-dz); } + SpecificInd zpm(int dz) const { return dz > 0 ? zp(dz) : zm(-dz); } // and for 2 cells - inline SpecificInd xpp() const { return xp(2); } - inline SpecificInd xmm() const { return xm(2); } - inline SpecificInd ypp() const { return yp(2); } - inline SpecificInd ymm() const { return ym(2); } - inline SpecificInd zpp() const { return zp(2); } - inline SpecificInd zmm() const { return zm(2); } + SpecificInd xpp() const { return xp(2); } + SpecificInd xmm() const { return xm(2); } + SpecificInd ypp() const { return yp(2); } + SpecificInd ymm() const { return ym(2); } + SpecificInd zpp() const { return zp(2); } + SpecificInd zmm() const { return zm(2); } /// Generic offset of \p index in multiple directions simultaneously - inline SpecificInd offset(int dx, int dy, int dz) const { - return zpm(dz).yp(dy).xp(dx); - } -}; - -/// Relational operators -template -inline bool operator==(const SpecificInd& lhs, const SpecificInd& rhs) { - return lhs.ind == rhs.ind; -} - -template -inline bool operator!=(const SpecificInd& lhs, const SpecificInd& rhs) { - return !operator==(lhs, rhs); -} - -template -inline bool operator<(const SpecificInd& lhs, const SpecificInd& rhs) { - return lhs.ind < rhs.ind; -} - -template -inline bool operator>(const SpecificInd& lhs, const SpecificInd& rhs) { - return operator<(rhs, lhs); -} - -template -inline bool operator>=(const SpecificInd& lhs, const SpecificInd& rhs) { - return !operator<(lhs, rhs); -} + SpecificInd offset(int dx, int dy, int dz) const { return zpm(dz).yp(dy).xp(dx); } -template -inline bool operator<=(const SpecificInd& lhs, const SpecificInd& rhs) { - return !operator>(lhs, rhs); -} + /// Relational operator + auto operator<=>(const SpecificInd& rhs) const { return ind <=> rhs.ind; } + bool operator==(const SpecificInd& rhs) const { return ind == rhs.ind; } +}; /// Arithmetic operators with integers template diff --git a/include/bout/single_index_ops.hxx b/include/bout/single_index_ops.hxx index c29d1a471f..3f4686c514 100644 --- a/include/bout/single_index_ops.hxx +++ b/include/bout/single_index_ops.hxx @@ -5,7 +5,11 @@ #ifndef SINGLE_INDEX_OPS_H #define SINGLE_INDEX_OPS_H -#include "field_accessor.hxx" +#include "bout/bout_types.hxx" +#include "bout/build_config.hxx" +#include "bout/field_accessor.hxx" +#include "bout/region.hxx" +#include "bout/utils.hxx" // Ind3D: i.zp(): BOUT_HOST_DEVICE inline int i_zp(const int id, const int nz) { @@ -228,8 +232,8 @@ BOUT_HOST_DEVICE inline BoutReal Delp2(const FieldAccessor& f, const i return (f.coords.G1(i) + f.coords.d1_dx(i) * f.coords.g11(i)) * (f[ixp] - f[ixm]) / (2.0 * dx) // DDX + f.coords.G3(i) * (f[izp] - f[izm]) / (2.0 * dz) // DDZ - + f.coords.g11(i) * (f[ixp] - 2.0 * f[i] + f[ixm]) / SQ(dx) // D2DX2 - + f.coords.g33(i) * (f[izp] - 2.0 * f[i] + f[izm]) / SQ(dz) // D2DZ2 + + f.coords.g11(i) * (f[ixp] + f[ixm] - 2.0 * f[i]) / SQ(dx) // D2DX2 + + f.coords.g33(i) * (f[izp] + f[izm] - 2.0 * f[i]) / SQ(dz) // D2DZ2 + 2 * f.coords.g13(i) * ((f[izpxp] - f[izpxm]) - (f[izmxp] - f[izmxm])) / (4. * dz * dx); // D2DXDZ } diff --git a/include/bout/solver.hxx b/include/bout/solver.hxx index b4e4ec7967..1c26166b02 100644 --- a/include/bout/solver.hxx +++ b/include/bout/solver.hxx @@ -1,20 +1,10 @@ /************************************************************************** * Base class for all solvers. Specifies required interface functions * - * Changelog: - * - * 2009-08 Ben Dudson, Sean Farley - * * Major overhaul, and changed API. Trying to make consistent - * interface to PETSc and SUNDIALS solvers - * - * 2013-08 Ben Dudson - * * Added OO-style API, to allow multiple physics models to coexist - * For now both APIs are supported - * ************************************************************************** - * Copyright 2010 B.D.Dudson, S.Farley, M.V.Umansky, X.Q.Xu + * Copyright 2010 - 2026 BOUT++ contributors * - * Contact: Ben Dudson, bd512@york.ac.uk + * Contact: Ben Dudson, dudson2@llnl.gov * * This file is part of BOUT++. * @@ -38,18 +28,21 @@ #include "bout/build_defines.hxx" +#include "bout/bout_enum_class.hxx" #include "bout/bout_types.hxx" #include "bout/boutexception.hxx" #include "bout/globals.hxx" #include "bout/mesh.hxx" #include "bout/monitor.hxx" #include "bout/options.hxx" +#include "bout/petsc_preconditioner.hxx" #include "bout/region.hxx" -#include "bout/unused.hxx" +#include #include #include #include +#include /////////////////////////////////////////////////////////////////// // C function pointer types @@ -380,6 +373,35 @@ public: protected: friend class SundialsNVectorInterface; + /// Per-variable metadata written alongside Jacobian diagnostics. + /// + /// ``offset`` gives the variable position within the per-cell ordering used by + /// the PETSc matrix. The full row/column mapping is reconstructed by combining + /// these records with ``jacobian_index_base`` from the dump files. + struct JacobianVariableMetadata { + int offset{0}; + std::string name; + std::string location; + bool evolve_bndry{false}; + bool constraint{false}; + std::string description; + }; + + /// Top-level JSON description of a saved Jacobian. + /// + /// This metadata is intentionally compact: per-variable properties are stored + /// once here, while per-cell offsets are reconstructed from + /// ``jacobian_index_base`` in the output data files. + struct JacobianMetadata { + int format_version{1}; + std::string solver_name; + int n2d{0}; + int n3d{0}; + std::vector variables_2d; + std::vector variables_3d; + std::string ordering; + }; + /// Number of command-line arguments static int* pargc; /// Command-line arguments @@ -482,24 +504,7 @@ protected: difference_type operator-(const VarIterator& b) { return it - b.it; } reference operator[](difference_type n) { return *VarIterator(it[n]); } - friend bool operator==(const VarIterator& a, const VarIterator& b) { - return a.it == b.it; - } - friend bool operator!=(const VarIterator& a, const VarIterator& b) { - return a.it != b.it; - } - friend bool operator<(const VarIterator& a, const VarIterator& b) { - return a.it < b.it; - } - friend bool operator>(const VarIterator& a, const VarIterator& b) { - return a.it > b.it; - } - friend bool operator<=(const VarIterator& a, const VarIterator& b) { - return a.it <= b.it; - } - friend bool operator>=(const VarIterator& a, const VarIterator& b) { - return a.it >= b.it; - } + auto operator<=>(const VarIterator& rhs) const = default; private: underlying_iterator it{}; @@ -527,8 +532,7 @@ protected: /// Does \p vars contain a field with \p name? template bool contains(const std::vector>& vars, const std::string& name) { - const auto in_vars = std::find(begin(vars), end(vars), name); - return in_vars != end(vars); + return std::ranges::find(vars, name, &VarStr::name) != std::ranges::end(vars); } /// Vectors of variables to evolve @@ -630,8 +634,34 @@ protected: void save_derivs(BoutReal* dudata); void set_id(BoutReal* udata); - /// Returns a Field3D containing the global indices + /// Returns a Field3D containing the global indices for each locally-owned DOF. Field3D globalIndex(int localStart); + /// Returns the base global index for the Jacobian ordering in each cell. + /// + /// Combined with the variable metadata JSON written by + /// ``writeJacobianMetadataJson()``, this allows PETSc matrix rows/columns to be + /// mapped back to variable names and ``(x, y, z)`` coordinates in Python. + Field3D jacobianIndexBase(int localStart = 0); + /// Return compact metadata for all evolved ``Field2D`` variables. + std::vector getJacobianMetadata2D() const; + /// Return compact metadata for all evolved ``Field3D`` variables. + std::vector getJacobianMetadata3D() const; + /// Build the complete Jacobian metadata record for JSON export. + JacobianMetadata getJacobianMetadata(const std::string& solver_name) const; + /// Write Jacobian metadata as JSON on rank 0. + /// + /// The JSON file is solver-independent; solver-specific matrix files and the + /// ``jacobian_index_base`` output together provide the remaining information + /// needed to reconstruct a labeled Jacobian in post-processing. + void writeJacobianMetadataJson(const std::string& filename, + const std::string& solver_name) const; + + /// Writes the Jacobian metadata on first call + void writeOnceJacobianMetadata(const std::string& solver_name); + +#if BOUT_HAS_PETSC + void writeJacobianMatrix(bout::JacobianExportKind kind, Mat jacobian); +#endif /// Maximum internal timestep BoutReal max_dt{-1.0}; @@ -696,6 +726,21 @@ private: std::string run_restart_from = "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"; /// Save `run_id` and `run_restart_from` every output bool save_repeat_run_id{false}; + /// Write ``jacobian_index_base`` to the dump files for Jacobian diagnostics. + bool save_jacobian_index_base{false}; + /// Has the Jacobian metadata file been written? + bool jacobian_metadata_written{false}; + /// Running counter appended to successive Jacobian saves + int jacobian_export_counter{0}; + /// Prefix for matrix files and ``*_metadata.json`` + std::string jacobian_export_prefix; + /// PETSc ``MatView`` format: binary or ascii + bout::PetscMatrixExportFormat jacobian_export_format; + + /// Return the matrix filename including the extension for the selected format. + std::string getJacobianMatrixFilename(const std::string& jacobian_export_prefix, + bout::JacobianExportKind kind, + bout::PetscMatrixExportFormat format); /// Current iteration (output time-step) number int iteration{0}; diff --git a/include/bout/sys/expressionparser.hxx b/include/bout/sys/expressionparser.hxx index 8b2442e843..f87ee2adbc 100644 --- a/include/bout/sys/expressionparser.hxx +++ b/include/bout/sys/expressionparser.hxx @@ -131,9 +131,7 @@ protected: /// Edit distance from original search term std::string::size_type distance; /// Comparison operator so this works in a std::multiset - friend bool operator<(const FuzzyMatch& lhs, const FuzzyMatch& rhs) { - return (lhs.distance < rhs.distance) and (lhs.name < rhs.name); - } + auto operator<=>(const FuzzyMatch&) const = default; }; /// Find approximate matches for \p name in the known generators. \p diff --git a/include/bout/tokamak_coordinates.hxx b/include/bout/tokamak_coordinates.hxx index f056033bb9..4bde1a9a97 100644 --- a/include/bout/tokamak_coordinates.hxx +++ b/include/bout/tokamak_coordinates.hxx @@ -4,6 +4,7 @@ #include #include #include +#include class Mesh; @@ -14,21 +15,21 @@ namespace bout { /// `I_unnormalised` are normalised. struct TokamakCoordinates { /// Major radius - Field2D Rxy; + FieldMetric Rxy; /// Vertical height - Field2D Zxy; + FieldMetric Zxy; /// Poloidal magnetic field - Field2D Bpxy; + FieldMetric Bpxy; /// Toroidal magnetic field - Field2D Btxy; + FieldMetric Btxy; /// Total magnetic field - Field2D Bxy; + FieldMetric Bxy; /// Poloidal arc length - Field2D hthe; + FieldMetric hthe; /// Integrated shear (normalised) - Coordinates::FieldMetric I; + FieldMetric I; /// Unnormalised integrated shear - Coordinates::FieldMetric I_unnormalised; + FieldMetric I_unnormalised; }; /// Read, normalise, calculate, and set the metric components for a BOUT++ @@ -44,6 +45,9 @@ TokamakCoordinates set_tokamak_coordinates(Mesh& mesh, BoutReal Lbar = 1.0, BoutReal Bbar = 1.0, bool no_shear = false, BoutReal shear_factor = 1.0); +MetricNormaliser TokamakOrFCIMetricNormaliser(const Mesh* mesh, BoutReal Bnorm, + BoutReal rho_s0); + } // namespace bout #endif //BOUT_TOKAMAK_COORDINATES_HXX diff --git a/include/bout/yboundary_regions.hxx b/include/bout/yboundary_regions.hxx index bb9f52bbda..91dcd529e0 100644 --- a/include/bout/yboundary_regions.hxx +++ b/include/bout/yboundary_regions.hxx @@ -1,19 +1,20 @@ #pragma once +#include "bout/boundary_region.hxx" #include "bout/boundary_region_iter.hxx" - -#include "bout/assert.hxx" -#include "bout/boundary_iterator.hxx" #include "bout/boutexception.hxx" #include "bout/field_data.hxx" -#include "bout/globals.hxx" #include "bout/mask.hxx" -#include "bout/options.hxx" -#include "bout/parallel_boundary_region.hxx" #include "bout/region.hxx" + +#include #include #include +class Options; + +namespace bout { +namespace boundary { /// This class allows to simplify iterating over y-boundaries. /// /// It makes it easier to write code for FieldAligned boundaries, but if a bit @@ -21,102 +22,49 @@ /// /// An example how to replace old code is given here: /// ../../manual/sphinx/user_docs/boundary_options.rst - class YBoundary { public: + YBoundary(YBndryType type, Options* options_ptr, const Mesh& mesh); + /// Iterate over the boundary. /// This function takes a lamda / templated function, that applies the boundary on the given point. - /// The function must take a `auto& point` as argument. + /// The function must take a `const BoundaryIterator auto& point` as its only argument. /// See also the documentation at ../../manual/sphinx/user_docs/boundary_options.rst - template - void iter(const Func& func) { - iter_regions([&](auto& region) { - for (auto& point : region) { + template + requires std::regular_invocable + || std::regular_invocable + void iter(F func) const { + for (const auto& region : boundary_regions) { + for (const auto& point : *region) { func(point); } - }); - } - - YBoundary(YBndryType type, Options* options_ptr, const Mesh& mesh) { - bool lower_y = true; - bool upper_y = true; - bool outer_x = true; - bool inner_x = false; - if (options_ptr != nullptr) { - auto& options = *options_ptr; - if (!mesh.isFci()) { - 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); - } else { - outer_x = - options["outer_x"].doc("Boundary on outer x?").withDefault(outer_x); - inner_x = - options["inner_x"].doc("Boundary on inner x?").withDefault(inner_x); - } } - switch (type) { - case YBndryType::sheath: - break; - case YBndryType::not_sheath: - lower_y = !lower_y; - upper_y = !upper_y; - outer_x = !outer_x; - inner_x = !inner_x; - break; - case YBndryType::all: - lower_y = true; - upper_y = true; - outer_x = true; - inner_x = true; - } - - 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 { - for (auto& bndry : mesh.getBoundaries()) { - if ((lower_y && bndry->location == BndryLoc::ydown) - or (upper_y && bndry->location == BndryLoc::yup)) { - boundary_regions.push_back( - std::dynamic_pointer_cast(bndry)); - } + for (const auto& region : boundary_regions_par) { + for (const auto& point : *region) { + func(point); } } - // Cache boundary regions - _contains.emplace_back(&mesh, false); - _contains.emplace_back(&mesh, false); - iter([&](const auto& point) { - if (point.dir() == 1) { - _contains[1][point.ind()] = true; - } else if (point.dir() == -1) { - _contains[0][point.ind()] = true; - } - }); } - bool contains_low(Ind3D ind) const { return _contains[0][ind]; } - bool contains_high(Ind3D ind) const { return _contains[1][ind]; } + /// Return true if this boundary in the lower direction contains point \p ind + bool contains_low(const Ind3D& ind) const { return _contains_low[ind]; } + /// Return true if this boundary in the upper direction contains point \p ind + bool contains_high(const Ind3D& ind) const { return _contains_high[ind]; } + + /// Return true if this boundary in direction \p dir contains point \p ind template - bool contains(Ind3D ind) const { + bool contains(const Ind3D& ind) const { static_assert(dir == 1 || dir == -1); if constexpr (dir == 1) { - return _contains[1][ind]; + return _contains_high[ind]; } if constexpr (dir == -1) { - return _contains[0][ind]; + return _contains_low[ind]; } } - bool contains(int dir, Ind3D ind) const { + + /// Return true if this boundary in direction \p dir contains point \p ind + bool contains(int dir, const Ind3D& ind) const { if (dir == 1) { return contains<+1>(ind); } @@ -127,26 +75,12 @@ public: } private: - template - void iter_regions(const Func& func) { - for (auto& region : boundary_regions) { - func(*region); - } - for (auto& region : boundary_regions_par) { - func(*region); - } - } - std::vector> boundary_regions_par; - std::vector> boundary_regions; + std::vector> boundary_regions_par; + std::vector> boundary_regions; - std::vector _contains; + BoutMask _contains_low; + BoutMask _contains_high; }; -inline std::shared_ptr getYBoundary(Coordinates* coords, - YBndryType type = YBndryType::sheath) { - auto itype = static_cast(type); - if (coords->ybndrys[itype] == nullptr) { - coords->ybndrys[itype] = coords->makeYBoundary(type); - } - return coords->ybndrys[itype]; -} +} // namespace boundary +} // namespace bout diff --git a/manual/sphinx/developer_docs/code_layout.rst b/manual/sphinx/developer_docs/code_layout.rst index 3c02602b44..90c4ed1822 100644 --- a/manual/sphinx/developer_docs/code_layout.rst +++ b/manual/sphinx/developer_docs/code_layout.rst @@ -97,6 +97,14 @@ The layout of the ``src/`` directory is as follows: variations, where some directions may be constant. See :ref:`sec-laplacian` for more details. + Known issue: ``src/invert/laplace/impls/hypre3d`` currently relies on + boundary elimination implemented in ``src/sys/hypre_interface.cxx``. That + elimination assumes each eliminated boundary row keeps only a single retained + neighbour. Corner rows where x- and y-boundary conditions both apply do not + satisfy that assumption, so ``LaplaceHypre3d`` does not preserve both corner + constraints exactly. Until the elimination is generalized, corner values from + ``LaplaceHypre3d`` should be treated as unreliable. + - ``src/invert/parderiv`` - Inversion of parallel derivatives, intended for use in diff --git a/manual/sphinx/developer_docs/data_types.rst b/manual/sphinx/developer_docs/data_types.rst index 7feb3945aa..ba4faa6e42 100644 --- a/manual/sphinx/developer_docs/data_types.rst +++ b/manual/sphinx/developer_docs/data_types.rst @@ -515,37 +515,44 @@ central type is ``BinaryExpr``, which stores: result - a cached list of linear region indices describing where the expression is valid +- for 3D expressions, any parallel-slice structure needed to evaluate + matching ``yup`` and ``ydown`` expressions -`Field2D`, `Field3D`, and `FieldPerp` act as expression leaves by -providing lightweight ``View`` types. Those views are the device- and -backend-friendly objects used by the expression evaluator. +`Field2D`, `Field3D`, `Field3DParallel`, and `FieldPerp` act as +expression leaves by providing lightweight ``View`` types. Constants are +also wrapped in small view objects so they can participate in the same +expression machinery. These views are the device- and backend-friendly +objects used by the expression evaluator. Materialization happens when a field is constructed or assigned from an expression, when an expression is stored in `Options`, or when a scalar reduction such as ``min`` or ``mean`` is requested. The same mechanism is also used to propagate metadata such as mesh, staggered location, -directions, and `FieldPerp` y-index. +directions, `FieldPerp` y-index, and for `Field3DParallel` the +forward/backward parallel slices on FCI meshes. The unary algebraic helpers in ``include/bout/field.hxx`` build on the same mechanism. Functions such as ``sqrt``, ``abs``, ``SQ``, -``if_else``, ``if_else_zero``, ``min``, ``max``, and ``mean`` can all -operate directly on lazy expressions. +``pow``, ``floor``, ``if_else``, ``if_else_zero``, ``min``, ``max``, +and ``mean`` can all operate directly on lazy expressions. Generated eager operators ~~~~~~~~~~~~~~~~~~~~~~~~~ -The eager arithmetic operators and in-place update paths are still -generated automatically using the `Jinja`_ templating system. The main -files are: +The remaining eager arithmetic operators and in-place update paths are +still generated automatically using the `Jinja`_ templating system. The +main files are: - ``src/field/gen_fieldops.jinja`` - ``src/field/gen_fieldops.py`` - ``src/field/generated_fieldops.cxx`` -The generated code handles the broad matrix of combinations between -`BoutReal`, `Field2D`, `Field3D`, `Field3DParallel`, and `FieldPerp`, -including several mixed-rank and in-place cases where hand-maintaining -all overloads would be error-prone. +The generated code still handles combinations between `BoutReal`, +`Field2D`, `Field3D`, `Field3DParallel`, and `FieldPerp`, especially the +eager `FieldPerp` wrappers and in-place update operators where +hand-maintaining all overloads would be error-prone. More free +non-member arithmetic now lives in the header-defined lazy-expression +path, so the generator covers a smaller subset than it used to. The generated loops now also depend on the configured execution backend. At configure time, the generator is told whether to emit RAJA-based, diff --git a/manual/sphinx/user_docs/algebraic_operators.rst b/manual/sphinx/user_docs/algebraic_operators.rst index b8c40d4dc5..6955ab5580 100644 --- a/manual/sphinx/user_docs/algebraic_operators.rst +++ b/manual/sphinx/user_docs/algebraic_operators.rst @@ -30,7 +30,7 @@ Common operators +------------------------------------------+------------------------------------------------------+ | ``mean(f, allpe=true, region)`` | Mean (optionally over all processes) | +------------------------------------------+------------------------------------------------------+ - | ``pow(lhs, rhs, region)`` | :math:`\mathtt{lhs}^\mathtt{rhs}` | + | ``pow(lhs, rhs[, region])`` | :math:`\mathtt{lhs}^\mathtt{rhs}` | +------------------------------------------+------------------------------------------------------+ | ``SQ(f, region)`` | Square of ``f`` | +------------------------------------------+------------------------------------------------------+ @@ -54,7 +54,8 @@ Common operators +------------------------------------------+------------------------------------------------------+ | ``tanh(f, region)`` | :math:`\tanh(f)` | +------------------------------------------+------------------------------------------------------+ - | ``floor(f, region)`` | Returns a field with the floor of `f` at each point | + | ``floor(f, floor_value[, region])`` | Returns a field where values below ``floor_value`` | + | | are replaced by ``floor_value`` | +------------------------------------------+------------------------------------------------------+ | ``filter(f, n, region)`` | Calculate the amplitude of the Fourier mode in the | | | z-direction with mode number `n` | @@ -84,12 +85,18 @@ Common operators These operators can usually be combined directly in expressions:: Field3D rhs = sqrt(SQ(n) + SQ(T)); + Field3D profile = pow(n + n0, 1.5); Field3D masked = if_else(use_drive, source * profile, sink * profile); BoutReal max_error = max(abs(lhs - rhs), true); Reductions such as ``min``, ``max``, and ``mean`` can operate directly on an expression, so an intermediate field is often unnecessary. +``pow`` also participates in lazy field expressions, including mixed +`Field2D`/`Field3D` cases where the `Field2D` operand is broadcast in +``z``. `FieldPerp` also has ``pow`` overloads for `FieldPerp` with +`FieldPerp` or a scalar. + Region arguments ---------------- @@ -108,6 +115,12 @@ When a region-limited expression is materialized into a field, only the selected region is guaranteed to contain valid values. This is the same performance-oriented convention used by other field operators. +`Field3DParallel` follows the same rule for the main field data. On FCI +meshes, materializing a lazy expression into `Field3DParallel` also +preserves the forward and backward parallel slices when those slices are +available on the expression operands. Materializing the same expression +into plain `Field3D` does not preserve those slices. + Further reading --------------- diff --git a/manual/sphinx/user_docs/bout_options.rst b/manual/sphinx/user_docs/bout_options.rst index eb71de26e3..3d25ac1a14 100644 --- a/manual/sphinx/user_docs/bout_options.rst +++ b/manual/sphinx/user_docs/bout_options.rst @@ -476,17 +476,19 @@ minimises ``abs(sqrt(NPES * (nx - 4) / ny) - NXPE)``). If you need to specify complex input values, e.g. numerical values from experiment, you may want to use a grid file. The grid file to use -is specified relative to the root directory where the simulation is -run (i.e. running “``ls ./data/BOUT.inp``” gives the options -file). You can use the global option ``grid``, or ``mesh:file``: +can be set with the global option ``grid``, or ``mesh:file``: .. code-block:: cfg - grid = "data/cbm18_8_y064_x260.nc" + grid = "cbm18_8_y064_x260.nc" # Alternatively: [mesh] - file = "data/cbm18_8_y064_x260.nc" + file = "cbm18_8_y064_x260.nc" + +Relative grid paths are resolved relative to ``datadir`` and the current working directory. If matching files exist in both places, +BOUT++ throws an error rather than guessing which one to use. To avoid +ambiguity, an explicit absolute path for the grid file can be used. Communications diff --git a/manual/sphinx/user_docs/differential_operators.rst b/manual/sphinx/user_docs/differential_operators.rst index 529037eb8b..de1d370f81 100644 --- a/manual/sphinx/user_docs/differential_operators.rst +++ b/manual/sphinx/user_docs/differential_operators.rst @@ -586,13 +586,14 @@ than ``FV::Div_par`` for the same limiter choice. template Field3D Div_par_mod(const Field3D &f_in, const Field3D &v_in, const Field3D &a, Field3D &flow_ylow, - bool fixflux=true); + bool fixflux=true, bool dissipative=false); The extra output argument ``flow_ylow`` stores the flow through the lower :math:`y` cell boundary, including the area factor. This can be useful as a diagnostic in energy or flux budgets. For FCI fields this diagnostic is -currently returned as zero. +currently returned as zero. The flag ``dissipative`` activates a local +Rusanov-flux if used in Fci. Parallel momentum flux ``Div_par_fvv`` diff --git a/manual/sphinx/user_docs/field_expressions.rst b/manual/sphinx/user_docs/field_expressions.rst index 62a50988d6..90fc04881a 100644 --- a/manual/sphinx/user_docs/field_expressions.rst +++ b/manual/sphinx/user_docs/field_expressions.rst @@ -5,9 +5,9 @@ Field Expressions BOUT++ field algebra now supports *lazy expressions* for many common operations. Instead of creating a temporary field for every ``+``, -``-``, ``*``, ``/``, ``sqrt`` or ``abs``, BOUT++ can keep the expression -symbolic and evaluate it only when a concrete field or scalar result is -needed. +``-``, ``*``, ``/``, ``pow``, ``sqrt`` or ``abs``, BOUT++ can keep the +expression symbolic and evaluate it only when a concrete field or scalar +result is needed. This keeps ordinary model code readable while reducing temporary allocations and extra loops over the mesh. It is especially helpful for @@ -21,9 +21,9 @@ The following operations can form lazy expressions over `Field2D`, sense: - Arithmetic operators: ``+``, ``-``, ``*``, ``/`` +- Binary algebraic helpers such as ``pow`` and ``floor`` - Unary algebraic operators such as ``sqrt``, ``abs``, ``exp``, ``log``, - ``sin``, ``cos``, ``tan``, ``sinh``, ``cosh``, ``tanh``, ``floor``, - and ``SQ`` + ``sin``, ``cos``, ``tan``, ``sinh``, ``cosh``, ``tanh``, and ``SQ`` - Simple conditionals with ``if_else`` and ``if_else_zero`` - Reductions such as ``min``, ``max``, and ``mean`` @@ -32,7 +32,7 @@ For example:: Field3D n, T; Field3D result; - result = sqrt(SQ(n) + SQ(T)); + result = sqrt(SQ(n) + pow(T + 1.0, 2.0)); The right-hand side can stay lazy until the assignment to ``result``. @@ -93,8 +93,10 @@ Several mixed-type combinations are supported directly: - `Field2D` with `Field3D`: the 2D quantity is broadcast in ``z`` - `FieldPerp` with matching perpendicular data: the operation uses the `FieldPerp` y-index +- `pow` follows the same mixed-rank pattern, so either operand can be + `Field2D` or `Field3D` and the result is a `Field3D` - expressions involving metric components may return - `Coordinates::FieldMetric`, which is `Field2D` or `Field3D` depending + `bout::FieldMetric`, which is `Field2D` or `Field3D` depending on how BOUT++ was built In practice, this means code such as:: @@ -121,6 +123,30 @@ expression or zero:: This is particularly convenient when optional source terms are enabled or disabled by compile-time or run-time logic. +`Field3DParallel` and FCI +------------------------- + +Lazy expressions can also carry parallel-slice information. This matters +when working with `Field3DParallel` on FCI meshes: + +- materializing into `Field3DParallel` preserves ``yup`` and ``ydown`` + slices +- materializing the same expression into plain `Field3D` keeps only the + main field values +- on FCI meshes, operands contributing to a `Field3DParallel` + expression must have compatible parallel slices available +- For metric components that should preserve parallel slices for FCI + `bout::FieldMetricParallel` acts like `Field3DParallel` for 3D metrics + and like `Field2D` otherwise. + +For example:: + + Field3DParallel f_par, g_par; + Field3DParallel result = sqrt(f_par + g_par); + +On a non-FCI mesh, assigning a lazy expression to `Field3DParallel` +still evaluates the main field, but no parallel slices are retained. + Reductions on expressions ------------------------- diff --git a/manual/sphinx/user_docs/laplacian.rst b/manual/sphinx/user_docs/laplacian.rst index ade760d5c5..175abfe082 100644 --- a/manual/sphinx/user_docs/laplacian.rst +++ b/manual/sphinx/user_docs/laplacian.rst @@ -152,6 +152,15 @@ within the physics module using ``setGlobalFlags``, lap->setOuterBoundaryFlags(Outer_Flags_Value); lap->setFlags(Flags_Value); +.. note:: + + Known issue: ``LaplaceHypre3d`` currently uses boundary-equation elimination in + its Hypre backend. At domain corners where an x-boundary and y-boundary + condition meet, the reduced system does not preserve both corner constraints + exactly. The returned corner values from ``LaplaceHypre3d`` should therefore be + treated as unreliable. This is usually acceptable because those corner values + are not normally used elsewhere in BOUT++ calculations. + .. _tab-laplacesettings: .. table:: Laplacian inversion options diff --git a/manual/sphinx/user_docs/time_integration.rst b/manual/sphinx/user_docs/time_integration.rst index f05c11a9ae..cf0d08112c 100644 --- a/manual/sphinx/user_docs/time_integration.rst +++ b/manual/sphinx/user_docs/time_integration.rst @@ -107,6 +107,19 @@ given in table :numref:`tab-solveropts`. +--------------------------+--------------------------------------------+-------------------------------------+ | diagnose | Collect and print additional diagnostics | cvode, imexbdf2, beuler | +--------------------------+--------------------------------------------+-------------------------------------+ + | save\_jacobian | Save PETSc Jacobian diagnostics | cvode, beuler / snes | + +--------------------------+--------------------------------------------+-------------------------------------+ + | jacobian\_export\_kind | Which Jacobian to save | cvode, beuler / snes | + +--------------------------+--------------------------------------------+-------------------------------------+ + | jacobian\_export\_trigger | When to save Jacobians | cvode | + +--------------------------+--------------------------------------------+-------------------------------------+ + | jacobian\_export\_prefix | Prefix for Jacobian matrix files | cvode, beuler / snes | + +--------------------------+--------------------------------------------+-------------------------------------+ + | jacobian\_export\_format | PETSc output format for Jacobians | cvode, beuler / snes | + +--------------------------+--------------------------------------------+-------------------------------------+ + | save\_jacobian\_index\_base | Write the per-cell Jacobian base index | cvode, beuler / snes | + | | field used to reconstruct saved Jacobians | | + +--------------------------+--------------------------------------------+-------------------------------------+ | nvector | ``N_Vector`` backend for SUNDIALS solvers: | cvode, ida, arkode | | | ``sundials`` or ``manyvector`` | | +--------------------------+--------------------------------------------+-------------------------------------+ @@ -223,6 +236,68 @@ nonlinear solvers: The linear solver type can be set using the ``linear_solver`` option. Valid choices include ``gmres`` (the default), ``fgmres``, ``tfqmr``, ``bcgs``. +CVODE Jacobian diagnostics +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +CVODE can also save PETSc finite-difference Jacobians for post-processing, +using the same metadata format as the SNES Jacobian diagnostics. + +The shared options are: + +- ``save_jacobian = true`` to enable exports +- ``jacobian_export_kind = system`` or ``rhs`` +- ``jacobian_export_prefix`` to choose the output filename prefix +- ``jacobian_export_format = binary`` or ``ascii`` +- ``save_jacobian_index_base = true`` to save the per-cell index-base field + +CVODE adds one extra option: + +- ``jacobian_export_trigger = linear_setup`` exports whenever CVODE rebuilds + linear solver data. +- ``jacobian_export_trigger = output`` exports once per solver output timestep. + +The supported combinations are: + +- ``jacobian_export_kind = system`` requires + ``jacobian_export_trigger = linear_setup`` and + ``cvode_precon_method = petsc``. +- ``jacobian_export_kind = rhs`` with + ``jacobian_export_trigger = output`` works without PETSc preconditioning and is + the easiest way to inspect the model Jacobian. +- ``jacobian_export_kind = rhs`` with + ``jacobian_export_trigger = linear_setup`` is also supported on the PETSc + preconditioner path. +- ``jacobian_export_kind = scaled`` is not currently supported in CVODE because + the solver does not yet apply a separate scaling transform. + +For example, to save the raw RHS Jacobian once per output timestep: + +.. code-block:: ini + + [solver] + type = cvode + save_jacobian = true + save_jacobian_index_base = true + jacobian_export_kind = rhs + jacobian_export_trigger = output + +To save the linearised CVODE system Jacobian whenever the PETSc +preconditioner is rebuilt: + +.. code-block:: ini + + [solver] + type = cvode + cvode_precon_method = petsc + save_jacobian = true + save_jacobian_index_base = true + jacobian_export_kind = system + jacobian_export_trigger = linear_setup + +As with the SNES Jacobian export, the output files are written into +``datadir`` and can be inspected using +``tests/integrated/jacobian_tools/read_jacobian.py``. + IMEX-BDF2 --------- @@ -775,6 +850,71 @@ Setting ``solver:force_symmetric_coloring = true``, will make sure that the jacobian colouring matrix is symmetric. This will often include a few extra non-zeros that the stencil will miss otherwise +Saving Jacobians for diagnostics +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The SNES solver can save PETSc Jacobian matrices for post-processing. +This is useful for checking sparsity structure, understanding variable +coupling, and diagnosing whether solver scaling is helping. + +Enable this with: + +.. code-block:: ini + + [solver] + type = snes + save_jacobian = true + save_jacobian_index_base = true + +By default this writes the Jacobian of the nonlinear system actually +solved by SNES. Three Jacobian definitions are available: + +- ``system``: the Jacobian of the full nonlinear system solved by SNES, + including timestep terms and any solver-specific rearrangement. +- ``scaled``: the Jacobian after solver-coordinate transforms such as + variable scaling or ``asinh`` variables. +- ``rhs``: the Jacobian of the bare model ``rhs()`` in physical variables. + +For example, to save the raw RHS Jacobian instead of the system Jacobian: + +.. code-block:: bash + + ./your_model solver:save_jacobian=true \ + solver:save_jacobian_index_base=true \ + solver:jacobian_export_kind=rhs + +To save an ASCII matrix with a custom filename prefix: + +.. code-block:: ini + + [solver] + save_jacobian = true + save_jacobian_index_base = true + jacobian_export_kind = scaled + jacobian_export_format = ascii + jacobian_export_prefix = jacobian_scaled_debug + +The Jacobian files are written into ``datadir``: + +- ``__.dat`` or ``.txt``: PETSc matrix written by + ``MatView``. +- ``jacobian_metadata.json``: compact JSON metadata describing the ordering + of 2D and 3D evolved variables. +- ``jacobian_index_base`` in the normal BOUT++ dump files: the per-cell base + global index needed to expand the compact JSON metadata into one row/column + label per degree of freedom. + +The JSON metadata stores per-variable information once, including name, +location, ``evolve_bndry``, and ``constraint`` flags. Row and column labels +for individual matrix entries are reconstructed by combining this JSON with +``jacobian_index_base`` and the mesh shape. + +For now, an example Python reader is provided in +``tests/integrated/jacobian_tools/read_jacobian.py``. It can load +the PETSc matrix into dense NumPy form, keep it sparse, optionally create +Pandas views, and extract variable-to-variable blocks such as ``df/dg``. +This helper is intended to move into a Python package later, likely xBOUT. + Variable Scaling ~~~~~~~~~~~~~~~~ @@ -878,12 +1018,24 @@ Summary of solver options +---------------------------+---------------+----------------------------------------------------+ | diagnose | false | Print diagnostic information every iteration | +---------------------------+---------------+----------------------------------------------------+ +| save_jacobian | false | Save Jacobian matrices for diagnostics | ++---------------------------+---------------+----------------------------------------------------+ +| jacobian_export_kind | system | Which Jacobian to save: ``system``, ``scaled``, | +| | | or ``rhs`` | ++---------------------------+---------------+----------------------------------------------------+ +| jacobian_export_prefix | jacobian | Prefix for Jacobian matrix files | ++---------------------------+---------------+----------------------------------------------------+ +| jacobian_export_format | binary | Matrix file format written by PETSc ``MatView`` | ++---------------------------+---------------+----------------------------------------------------+ | stencil:cross | 0 | If ``matrix_free=false`` and ``use_coloring=true`` | | stencil:square | 0 | Set the size and shape of the Jacobian coloring | | stencil:taxi | 2 | stencil. | +---------------------------+---------------+----------------------------------------------------+ | force_symmetric_coloring | false | Ensure that the Jacobian coloring is symmetric | +---------------------------+---------------+----------------------------------------------------+ +| save_jacobian_index_base | false | Write ``jacobian_index_base`` to the dump files | +| | | so saved Jacobians can be reconstructed | ++---------------------------+---------------+----------------------------------------------------+ The predictor is linear extrapolation from the last two timesteps. It seems to be effective, but can be disabled by setting ``predictor = false``. diff --git a/pyproject.toml b/pyproject.toml index 4a76c03fd2..9e61e655f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,26 +10,26 @@ console_output_style = "plain" [dependency-groups] dev = [ - "cmake~=4.2", - "clang-format~=22.0", - "clang-tidy~=22.0", - "clangd~=22.0", + "cmake~=4.4", + "clang-format~=22.1", + "clang-tidy~=22.1", + "clangd~=22.1", "clangd-tidy~=1.1", "cmakelang~=0.6", "prek>=0.3.10", "pyyaml~=6.0", - "ruff~=0.15", + "ruff~=0.16", "sphinx-lint~=1.0", - "sync-with-uv~=0.5.0", + "sync-with-uv~=0.6.0", ] pytest = [ - "pytest~=8.4", + "pytest~=9.1", "pytest-xdist~=3.8", ] maint = [ - "pygithub~=2.8", + "pygithub~=2.10", "ruamel-yaml~=0.19", - "Unidecode~=1.3", + "Unidecode~=1.4", ] [tool.uv.workspace] diff --git a/src/bout++.cxx b/src/bout++.cxx index da7b95b24f..85a07f47f7 100644 --- a/src/bout++.cxx +++ b/src/bout++.cxx @@ -879,6 +879,14 @@ int BoutMonitor::call(Solver* solver, BoutReal t, [[maybe_unused]] int iter, int if (solver->splitOperator()) { output_progress.write(_("Sim Time | RHS_e evals | RHS_I evals | Wall Time | " "Calc Inv Comm I/O SOLVER\n\n")); + } else if (solver->splitOperatorMRI()) { + output_progress.write( + _("Sim Time | RHS_se evals | RHS_si evals | RHS_fe evals |" + "RHS_fi evals | Wall Time | " + "Calc Inv Comm I/O SOLVER\n\n")); + } else { + output_progress.write(_("Sim Time | RHS evals | Wall Time | Calc Inv Comm " + " I/O SOLVER\n\n")); } else if (solver->splitOperatorMRI()) { output_progress.write(_("Sim Time | RHS_se evals | RHS_si evals | RHS_fe evals |" @@ -1037,18 +1045,24 @@ void RunMetrics::outputVars(Options& output_options) const { } void RunMetrics::calculateDerivedMetrics() { - // Terrible hack avoid divide-by-zero, needed because SLEPc solver - // doesn't call `run_rhs` which increments `ncalls`. Better fix is - // change `Solver::addMonitor` API to take a name so that we can - // replace `BoutMonitor` with a different implementation. Currently - // not possible because `Solver::removeMonitor` needs the pointer to - // the specific instance - if (ncalls == 0) { - return; + wtime_per_rhs = 0.0; + wtime_per_rhs_e = 0.0; + wtime_per_rhs_i = 0.0; + + const auto total_ncalls = + (ncalls > 0) ? ncalls : (ncalls_se + ncalls_si + ncalls_fe + ncalls_fi); + const auto explicit_ncalls = (ncalls_e > 0) ? ncalls_e : (ncalls_se + ncalls_fe); + const auto implicit_ncalls = (ncalls_i > 0) ? ncalls_i : (ncalls_si + ncalls_fi); + + if (total_ncalls > 0) { + wtime_per_rhs = wtime / total_ncalls; + } + if (explicit_ncalls > 0) { + wtime_per_rhs_e = wtime / explicit_ncalls; + } + if (implicit_ncalls > 0) { + wtime_per_rhs_i = wtime / implicit_ncalls; } - wtime_per_rhs = wtime / ncalls; - wtime_per_rhs_e = wtime / ncalls_e; - wtime_per_rhs_i = wtime / ncalls_i; } void RunMetrics::writeProgress(BoutReal simtime, bool output_split, bool output_splitmri) { @@ -1062,6 +1076,25 @@ void RunMetrics::writeProgress(BoutReal simtime, bool output_split, bool output_ 100. * wtime_io / wtime, // I/O 100. * (wtime - wtime_io - wtime_rhs) / wtime); // Everything else + } else if (output_splitmri) { + output_progress.write("{:.3e} {:8d} {:8d} {:8d} {:8d} " + " {:.2e} {:5.1f} " + "{:5.1f} {:5.1f} {:5.1f} {:5.1f}\n", + simtime, ncalls_se, ncalls_si, ncalls_fe, ncalls_fi, wtime, + 100. * (wtime_rhs - wtime_comms - wtime_invert) / wtime, + 100. * wtime_invert / wtime, // Inversions + 100. * wtime_comms / wtime, // Communications + 100. * wtime_io / wtime, // I/O + 100. * (wtime - wtime_io - wtime_rhs) + / wtime); // Everything else + } else { + output_progress.write( + "{:.3e} {:5d} {:.2e} {:5.1f} {:5.1f} {:5.1f} {:5.1f} {:5.1f}\n", + simtime, ncalls, wtime, 100. * (wtime_rhs - wtime_comms - wtime_invert) / wtime, + 100. * wtime_invert / wtime, // Inversions + 100. * wtime_comms / wtime, // Communications + 100. * wtime_io / wtime, // I/O + 100. * (wtime - wtime_io - wtime_rhs) / wtime); // Everything else } else if (output_splitmri) { output_progress.write("{:.3e} {:8d} {:8d} {:8d} {:8d} {:.2e} {:5.1f} " diff --git a/src/field/field.cxx b/src/field/field.cxx index e9b01f0bcb..8c7e8bbfee 100644 --- a/src/field/field.cxx +++ b/src/field/field.cxx @@ -2,10 +2,10 @@ * Base class for fields * ************************************************************************** - * Copyright 2010 B.D.Dudson, S.Farley, M.V.Umansky, X.Q.Xu + * Copyright 2010 - 2026 BOUT++ contributors + * + * Contact: Ben Dudson, dudson2@llnl.gov * - * Contact: Ben Dudson, bd512@york.ac.uk - * * This file is part of BOUT++. * * BOUT++ is free software: you can redistribute it and/or modify @@ -23,9 +23,11 @@ * **************************************************************************/ +#include #include #include #include +#include #include #include #include diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index 8700096e24..70ba55368b 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -31,6 +31,7 @@ #include "bout/field2d.hxx" #include "bout/index_derivs_interface.hxx" +#include "bout/yboundary_regions.hxx" #include #include @@ -42,8 +43,6 @@ #include #include -#include "bout/parallel_boundary_op.hxx" -#include "bout/parallel_boundary_region.hxx" #include #include #include @@ -56,6 +55,7 @@ #include #include #include +#include #include #include @@ -513,9 +513,9 @@ void Field3D::setBoundaryTo(const Field3D& f3d, bool copyParallelSlices, for (auto& region : fieldmesh->getBoundariesPar()) { for (const auto& point : *region) { // Interpolate midpoint value in f3d - const BoutReal val = point.interpolate_boundary_o2(f3d); + const BoutReal val = interpolate_boundary_o2(point, f3d); // Set the same boundary value in this field - point.dirichlet_o1(*this, val); + dirichlet_o1(point, *this, val); } } } @@ -526,9 +526,10 @@ void Field3D::setBoundaryTo(const Field3D& f3d, bool copyParallelSlices, // Loop over boundary regions for (const auto& newreg : fieldmesh->getBoundaries()) { if (newreg->isX) { - bout::boundary::iter_boundary(newreg, [&](auto& point) { - const BoutReal val = point.interpolate_boundary_o2(f3d); - point.dirichlet_o2(*this, val); + using namespace bout::boundary; + iter_boundary(newreg, [&](BoundaryIterator auto& point) { + const BoutReal val = interpolate_boundary_o2(point, f3d); + dirichlet_o2(point, *this, val); }); if (forceLegacy) { // get the old, potentially wrong behaviour @@ -686,22 +687,6 @@ void Field3D::swapData(Field3D& other) { std::swap(data, other.data); } //////////////// NON-MEMBER FUNCTIONS ////////////////// -Field3D pow(const Field3D& lhs, const Field2D& rhs, const std::string& rgn) { - - // Check if the inputs are allocated - checkData(lhs); - checkData(rhs); - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - // Define and allocate the output result - Field3D result{emptyFrom(lhs)}; - - BOUT_FOR(i, result.getRegion(rgn)) { result[i] = ::pow(lhs[i], rhs[i]); } - - checkData(result); - return result; -} - FieldPerp pow(const Field3D& lhs, const FieldPerp& rhs, const std::string& rgn) { checkData(lhs); @@ -936,6 +921,11 @@ const Region& getField3DRegion(const Mesh* mesh, std::optional re return mesh->getRegion("RGN_ALL"); } +size_t getField3DRegionID(const Mesh* mesh, const std::string& region_name) { + ASSERT1(mesh != nullptr); + return mesh->getRegionID(region_name); +} + } // namespace bout::detail void swap(Field3D& first, Field3D& second) noexcept { diff --git a/src/field/field_data.cxx b/src/field/field_data.cxx index f8935bb325..a4df94a85b 100644 --- a/src/field/field_data.cxx +++ b/src/field/field_data.cxx @@ -1,7 +1,5 @@ #include "bout/parallel_boundary_op.hxx" -#include "bout/parallel_boundary_region.hxx" -#include "bout/unused.hxx" #include #include #include @@ -189,12 +187,11 @@ void FieldData::addBndryGenerator(FieldGeneratorPtr gen, BndryLoc location) { } FieldGeneratorPtr FieldData::getBndryGenerator(BndryLoc location) { - auto it = bndry_generator.find(location); - if (it == bndry_generator.end()) { - return nullptr; + if (const auto it = bndry_generator.find(location); it != bndry_generator.end()) { + return it->second; } - return it->second; + return nullptr; } Mesh* FieldData::getMesh() const { diff --git a/src/field/field_factory.cxx b/src/field/field_factory.cxx index 1f5984ba48..220597a215 100644 --- a/src/field/field_factory.cxx +++ b/src/field/field_factory.cxx @@ -301,8 +301,7 @@ Field3D FieldFactory::create3D(FieldGeneratorPtr gen, Mesh* localmesh, CELL_LOC }; if (transform_from_field_aligned) { - auto coords = result.getCoordinates(); - if (coords == nullptr) { + if (auto coords = result.getCoordinates(); coords == nullptr) { // Should not lead to issues. If called from the coordinates // constructor, then this is expected, and the result will be // transformed. Otherwise, if the field is used untransformed, @@ -352,8 +351,7 @@ FieldPerp FieldFactory::createPerp(FieldGeneratorPtr gen, Mesh* localmesh, CELL_ }; if (transform_from_field_aligned) { - auto coords = result.getCoordinates(); - if (coords == nullptr) { + if (auto coords = result.getCoordinates(); coords == nullptr) { // Should not lead to issues. If called from the coordinates // constructor, then this is expected, and the result will be // transformed. Otherwise, if the field is used untransformed, @@ -376,8 +374,7 @@ const Options* FieldFactory::findOption(const Options* opt, const std::string& n const Options* result = opt; // Check if name contains a section separator ':' - size_t pos = name.find(':'); - if (pos == std::string::npos) { + if (auto pos = name.find(':'); pos == std::string::npos) { // No separator. Try this section, and then go through parents while (!result->isSet(name)) { @@ -517,8 +514,7 @@ FieldGeneratorPtr FieldFactory::parse(const std::string& input, key = opt->str() + key; // Include options context in key } - auto it = cache.find(key); - if (it != cache.end()) { + if (auto it = cache.find(key); it != cache.end()) { return it->second; } diff --git a/src/field/fieldgroup.cxx b/src/field/fieldgroup.cxx index f85cbb6843..f9f166fa32 100644 --- a/src/field/fieldgroup.cxx +++ b/src/field/fieldgroup.cxx @@ -1,5 +1,5 @@ - #include +#include FieldGroup operator+(const FieldGroup& lhs, const FieldGroup& rhs) { return FieldGroup(lhs) += rhs; @@ -7,16 +7,16 @@ FieldGroup operator+(const FieldGroup& lhs, const FieldGroup& rhs) { void FieldGroup::makeUnique() { // Need to sort vector before making unique - std::sort(fvec.begin(), fvec.end()); + std::ranges::sort(fvec); // Remove duplicate entries (doesn't resize vector though) - auto last = std::unique(fvec.begin(), fvec.end()); + auto fvec_dupes = std::ranges::unique(fvec); // Resizes vector to remove memory no longer required - fvec.erase(last, fvec.end()); + fvec.erase(fvec_dupes.begin(), fvec_dupes.end()); // Now do the same for the vector of Field3Ds - std::sort(f3vec.begin(), f3vec.end()); - auto last_f3 = std::unique(f3vec.begin(), f3vec.end()); - f3vec.erase(last_f3, f3vec.end()); + std::ranges::sort(f3vec); + auto f3vec_dupes = std::ranges::unique(f3vec); + f3vec.erase(f3vec_dupes.begin(), f3vec_dupes.end()); } diff --git a/src/field/fieldperp.cxx b/src/field/fieldperp.cxx index b7b2d9d731..bca551c29d 100644 --- a/src/field/fieldperp.cxx +++ b/src/field/fieldperp.cxx @@ -153,6 +153,41 @@ FieldPerp fromFieldAligned(const FieldPerp& f, const std::string& region) { ///////////////////////////////////////////////// // functions +FieldPerp pow(const FieldPerp& lhs, const FieldPerp& rhs, const std::string& rgn) { + checkData(lhs); + checkData(rhs); + ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); + + FieldPerp result{emptyFrom(lhs)}; + + BOUT_FOR(i, result.getRegion(rgn)) { result[i] = ::pow(lhs[i], rhs[i]); } + + checkData(result); + return result; +} + +FieldPerp pow(const FieldPerp& lhs, BoutReal rhs, const std::string& rgn) { + checkData(lhs); + + FieldPerp result{emptyFrom(lhs)}; + + BOUT_FOR(i, result.getRegion(rgn)) { result[i] = ::pow(lhs[i], rhs); } + + checkData(result); + return result; +} + +FieldPerp pow(BoutReal lhs, const FieldPerp& rhs, const std::string& rgn) { + checkData(rhs); + + FieldPerp result{emptyFrom(rhs)}; + + BOUT_FOR(i, result.getRegion(rgn)) { result[i] = ::pow(lhs, rhs[i]); } + + checkData(result); + return result; +} + const FieldPerp sliceXZ(const Field3D& f, int y) { // Source field should be valid checkData(f); diff --git a/src/field/gen_fieldops.jinja b/src/field/gen_fieldops.jinja index 913acadf7b..89f46469bd 100644 --- a/src/field/gen_fieldops.jinja +++ b/src/field/gen_fieldops.jinja @@ -1,6 +1,7 @@ {% set use_parallel_arg = lhs.field_type == "Field3DParallel" or rhs.field_type == "Field3DParallel" %} {% set use_raja_path = region_loop == "BOUT_FOR_RAJA" and not use_parallel_arg %} +{% if emit_free_binary %} // Provide the C++ wrapper for {{operator_name}} of {{lhs}} and {{rhs}} {{out}} operator{{operator}}(const {{lhs.passByReference}}, const {{rhs.passByReference}}) { {% if lhs != "BoutReal" and rhs != "BoutReal" %} @@ -140,8 +141,9 @@ checkData({{out.name}}); return {{out.name}}; } +{% endif %} -{% if out.field_type == lhs.field_type and lhs == "Field3D" %} +{% if emit_update_inplace %} // Provide the C++ operator to update {{lhs}} by {{operator_name}} with {{rhs}} {{lhs}} &{{lhs}}::update_{{operator_name}}_inplace(const {{rhs.passByReference}}) { // only if data is unique we update the field @@ -234,7 +236,7 @@ {% endif %} -{% if out.field_type == lhs.field_type %} +{% if emit_member_op_equals %} // Provide the C++ operator to update {{lhs}} by {{operator_name}} with {{rhs}} {{lhs}} &{{lhs}}::operator{{operator}}=(const {{rhs.passByReference}}) { // only if data is unique we update the field diff --git a/src/field/gen_fieldops.py b/src/field/gen_fieldops.py index 6610286af5..e77530ff97 100755 --- a/src/field/gen_fieldops.py +++ b/src/field/gen_fieldops.py @@ -254,6 +254,21 @@ def returnType(f1, f2): return copy(field3D) +def emit_free_binary_wrapper(out): + """Return True if this operator still needs an eager non-member wrapper.""" + return out.field_type == "FieldPerp" + + +def emit_update_inplace(lhs, out): + """Return True if this operator needs a Field3D update_*_inplace definition.""" + return out.field_type == lhs.field_type and lhs.field_type == "Field3D" + + +def emit_member_operator_equals(lhs, out): + """Return True if this operator needs an in-place operator definition.""" + return out.field_type == lhs.field_type + + if __name__ == "__main__": parser = argparse.ArgumentParser( description="Generate code for the Field arithmetic operators" @@ -365,6 +380,9 @@ def returnType(f1, f2): "out": out, "lhs": lhs, "rhs": rhs, + "emit_free_binary": emit_free_binary_wrapper(out), + "emit_update_inplace": emit_update_inplace(lhs, out), + "emit_member_op_equals": emit_member_operator_equals(lhs, out), # "region_loop": region_loop, "region_name": region_name, diff --git a/src/field/generated_fieldops.cxx b/src/field/generated_fieldops.cxx index d47b2a7a89..1e9ade4ba4 100644 --- a/src/field/generated_fieldops.cxx +++ b/src/field/generated_fieldops.cxx @@ -9,23 +9,6 @@ #include #include -// Provide the C++ wrapper for multiplication of Field3D and Field3D -Field3D operator*(const Field3D& lhs, const Field3D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] * rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3D by multiplication with Field3D Field3D& Field3D::update_multiplication_inplace(const Field3D& rhs) { // only if data is unique we update the field @@ -78,23 +61,6 @@ Field3D& Field3D::operator*=(const Field3D& rhs) { return *this; } -// Provide the C++ wrapper for division of Field3D and Field3D -Field3D operator/(const Field3D& lhs, const Field3D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] / rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3D by division with Field3D Field3D& Field3D::update_division_inplace(const Field3D& rhs) { // only if data is unique we update the field @@ -147,23 +113,6 @@ Field3D& Field3D::operator/=(const Field3D& rhs) { return *this; } -// Provide the C++ wrapper for addition of Field3D and Field3D -Field3D operator+(const Field3D& lhs, const Field3D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] + rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3D by addition with Field3D Field3D& Field3D::update_addition_inplace(const Field3D& rhs) { // only if data is unique we update the field @@ -216,23 +165,6 @@ Field3D& Field3D::operator+=(const Field3D& rhs) { return *this; } -// Provide the C++ wrapper for subtraction of Field3D and Field3D -Field3D operator-(const Field3D& lhs, const Field3D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] - rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3D by subtraction with Field3D Field3D& Field3D::update_subtraction_inplace(const Field3D& rhs) { // only if data is unique we update the field @@ -285,28 +217,6 @@ Field3D& Field3D::operator-=(const Field3D& rhs) { return *this; } -// Provide the C++ wrapper for multiplication of Field3D and Field2D -Field3D operator*(const Field3D& lhs, const Field2D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getRegionID()); - - Mesh* localmesh = lhs.getMesh(); - - BOUT_FOR_SERIAL(index, rhs.getRegion("RGN_ALL")) { - const auto base_ind = localmesh->ind2Dto3D(index); - for (int jz = 0; jz < localmesh->LocalNz; ++jz) { - result[base_ind + jz] = lhs[base_ind + jz] * rhs[index]; - } - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3D by multiplication with Field2D Field3D& Field3D::update_multiplication_inplace(const Field2D& rhs) { // only if data is unique we update the field @@ -365,29 +275,6 @@ Field3D& Field3D::operator*=(const Field2D& rhs) { return *this; } -// Provide the C++ wrapper for division of Field3D and Field2D -Field3D operator/(const Field3D& lhs, const Field2D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getRegionID()); - - Mesh* localmesh = lhs.getMesh(); - - BOUT_FOR_SERIAL(index, rhs.getRegion("RGN_ALL")) { - const auto base_ind = localmesh->ind2Dto3D(index); - const auto tmp = 1.0 / rhs[index]; - for (int jz = 0; jz < localmesh->LocalNz; ++jz) { - result[base_ind + jz] = lhs[base_ind + jz] * tmp; - } - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3D by division with Field2D Field3D& Field3D::update_division_inplace(const Field2D& rhs) { // only if data is unique we update the field @@ -448,28 +335,6 @@ Field3D& Field3D::operator/=(const Field2D& rhs) { return *this; } -// Provide the C++ wrapper for addition of Field3D and Field2D -Field3D operator+(const Field3D& lhs, const Field2D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getRegionID()); - - Mesh* localmesh = lhs.getMesh(); - - BOUT_FOR_SERIAL(index, rhs.getRegion("RGN_ALL")) { - const auto base_ind = localmesh->ind2Dto3D(index); - for (int jz = 0; jz < localmesh->LocalNz; ++jz) { - result[base_ind + jz] = lhs[base_ind + jz] + rhs[index]; - } - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3D by addition with Field2D Field3D& Field3D::update_addition_inplace(const Field2D& rhs) { // only if data is unique we update the field @@ -528,28 +393,6 @@ Field3D& Field3D::operator+=(const Field2D& rhs) { return *this; } -// Provide the C++ wrapper for subtraction of Field3D and Field2D -Field3D operator-(const Field3D& lhs, const Field2D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getRegionID()); - - Mesh* localmesh = lhs.getMesh(); - - BOUT_FOR_SERIAL(index, rhs.getRegion("RGN_ALL")) { - const auto base_ind = localmesh->ind2Dto3D(index); - for (int jz = 0; jz < localmesh->LocalNz; ++jz) { - result[base_ind + jz] = lhs[base_ind + jz] - rhs[index]; - } - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3D by subtraction with Field2D Field3D& Field3D::update_subtraction_inplace(const Field2D& rhs) { // only if data is unique we update the field @@ -684,22 +527,6 @@ FieldPerp operator-(const Field3D& lhs, const FieldPerp& rhs) { return result; } -// Provide the C++ wrapper for multiplication of Field3D and BoutReal -Field3D operator*(const Field3D& lhs, const BoutReal rhs) { - - Field3D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getRegionID()); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] * rhs; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3D by multiplication with BoutReal Field3D& Field3D::update_multiplication_inplace(const BoutReal rhs) { // only if data is unique we update the field @@ -746,23 +573,6 @@ Field3D& Field3D::operator*=(const BoutReal rhs) { return *this; } -// Provide the C++ wrapper for division of Field3D and BoutReal -Field3D operator/(const Field3D& lhs, const BoutReal rhs) { - - Field3D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getRegionID()); - - const auto tmp = 1.0 / rhs; - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] * tmp; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3D by division with BoutReal Field3D& Field3D::update_division_inplace(const BoutReal rhs) { // only if data is unique we update the field @@ -811,22 +621,6 @@ Field3D& Field3D::operator/=(const BoutReal rhs) { return *this; } -// Provide the C++ wrapper for addition of Field3D and BoutReal -Field3D operator+(const Field3D& lhs, const BoutReal rhs) { - - Field3D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getRegionID()); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] + rhs; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3D by addition with BoutReal Field3D& Field3D::update_addition_inplace(const BoutReal rhs) { // only if data is unique we update the field @@ -873,22 +667,6 @@ Field3D& Field3D::operator+=(const BoutReal rhs) { return *this; } -// Provide the C++ wrapper for subtraction of Field3D and BoutReal -Field3D operator-(const Field3D& lhs, const BoutReal rhs) { - - Field3D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getRegionID()); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] - rhs; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3D by subtraction with BoutReal Field3D& Field3D::update_subtraction_inplace(const BoutReal rhs) { // only if data is unique we update the field @@ -935,109 +713,6 @@ Field3D& Field3D::operator-=(const BoutReal rhs) { return *this; } -// Provide the C++ wrapper for multiplication of Field2D and Field3D -Field3D operator*(const Field2D& lhs, const Field3D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3D result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(rhs.getRegionID()); - - Mesh* localmesh = lhs.getMesh(); - - BOUT_FOR_SERIAL(index, lhs.getRegion("RGN_ALL")) { - const auto base_ind = localmesh->ind2Dto3D(index); - for (int jz = 0; jz < localmesh->LocalNz; ++jz) { - result[base_ind + jz] = lhs[index] * rhs[base_ind + jz]; - } - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for division of Field2D and Field3D -Field3D operator/(const Field2D& lhs, const Field3D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3D result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(rhs.getRegionID()); - - Mesh* localmesh = lhs.getMesh(); - - BOUT_FOR_SERIAL(index, lhs.getRegion("RGN_ALL")) { - const auto base_ind = localmesh->ind2Dto3D(index); - for (int jz = 0; jz < localmesh->LocalNz; ++jz) { - result[base_ind + jz] = lhs[index] / rhs[base_ind + jz]; - } - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for addition of Field2D and Field3D -Field3D operator+(const Field2D& lhs, const Field3D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3D result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(rhs.getRegionID()); - - Mesh* localmesh = lhs.getMesh(); - - BOUT_FOR_SERIAL(index, lhs.getRegion("RGN_ALL")) { - const auto base_ind = localmesh->ind2Dto3D(index); - for (int jz = 0; jz < localmesh->LocalNz; ++jz) { - result[base_ind + jz] = lhs[index] + rhs[base_ind + jz]; - } - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for subtraction of Field2D and Field3D -Field3D operator-(const Field2D& lhs, const Field3D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3D result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(rhs.getRegionID()); - - Mesh* localmesh = lhs.getMesh(); - - BOUT_FOR_SERIAL(index, lhs.getRegion("RGN_ALL")) { - const auto base_ind = localmesh->ind2Dto3D(index); - for (int jz = 0; jz < localmesh->LocalNz; ++jz) { - result[base_ind + jz] = lhs[index] - rhs[base_ind + jz]; - } - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for multiplication of Field2D and Field2D -Field2D operator*(const Field2D& lhs, const Field2D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field2D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] * rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field2D by multiplication with Field2D Field2D& Field2D::operator*=(const Field2D& rhs) { // only if data is unique we update the field @@ -1058,21 +733,6 @@ Field2D& Field2D::operator*=(const Field2D& rhs) { return *this; } -// Provide the C++ wrapper for division of Field2D and Field2D -Field2D operator/(const Field2D& lhs, const Field2D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field2D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] / rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field2D by division with Field2D Field2D& Field2D::operator/=(const Field2D& rhs) { // only if data is unique we update the field @@ -1093,21 +753,6 @@ Field2D& Field2D::operator/=(const Field2D& rhs) { return *this; } -// Provide the C++ wrapper for addition of Field2D and Field2D -Field2D operator+(const Field2D& lhs, const Field2D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field2D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] + rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field2D by addition with Field2D Field2D& Field2D::operator+=(const Field2D& rhs) { // only if data is unique we update the field @@ -1128,21 +773,6 @@ Field2D& Field2D::operator+=(const Field2D& rhs) { return *this; } -// Provide the C++ wrapper for subtraction of Field2D and Field2D -Field2D operator-(const Field2D& lhs, const Field2D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field2D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] - rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field2D by subtraction with Field2D Field2D& Field2D::operator-=(const Field2D& rhs) { // only if data is unique we update the field @@ -1239,20 +869,6 @@ FieldPerp operator-(const Field2D& lhs, const FieldPerp& rhs) { return result; } -// Provide the C++ wrapper for multiplication of Field2D and BoutReal -Field2D operator*(const Field2D& lhs, const BoutReal rhs) { - - Field2D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] * rhs; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field2D by multiplication with BoutReal Field2D& Field2D::operator*=(const BoutReal rhs) { // only if data is unique we update the field @@ -1272,21 +888,6 @@ Field2D& Field2D::operator*=(const BoutReal rhs) { return *this; } -// Provide the C++ wrapper for division of Field2D and BoutReal -Field2D operator/(const Field2D& lhs, const BoutReal rhs) { - - Field2D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - const auto tmp = 1.0 / rhs; - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] * tmp; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field2D by division with BoutReal Field2D& Field2D::operator/=(const BoutReal rhs) { // only if data is unique we update the field @@ -1307,20 +908,6 @@ Field2D& Field2D::operator/=(const BoutReal rhs) { return *this; } -// Provide the C++ wrapper for addition of Field2D and BoutReal -Field2D operator+(const Field2D& lhs, const BoutReal rhs) { - - Field2D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] + rhs; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field2D by addition with BoutReal Field2D& Field2D::operator+=(const BoutReal rhs) { // only if data is unique we update the field @@ -1340,20 +927,6 @@ Field2D& Field2D::operator+=(const BoutReal rhs) { return *this; } -// Provide the C++ wrapper for subtraction of Field2D and BoutReal -Field2D operator-(const Field2D& lhs, const BoutReal rhs) { - - Field2D result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] - rhs; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field2D by subtraction with BoutReal Field2D& Field2D::operator-=(const BoutReal rhs) { // only if data is unique we update the field @@ -2006,126 +1579,6 @@ FieldPerp& FieldPerp::operator-=(const BoutReal rhs) { return *this; } -// Provide the C++ wrapper for multiplication of BoutReal and Field3D -Field3D operator*(const BoutReal lhs, const Field3D& rhs) { - - Field3D result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(rhs.getRegionID()); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs * rhs[index]; - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for division of BoutReal and Field3D -Field3D operator/(const BoutReal lhs, const Field3D& rhs) { - - Field3D result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(rhs.getRegionID()); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs / rhs[index]; - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for addition of BoutReal and Field3D -Field3D operator+(const BoutReal lhs, const Field3D& rhs) { - - Field3D result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(rhs.getRegionID()); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs + rhs[index]; - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for subtraction of BoutReal and Field3D -Field3D operator-(const BoutReal lhs, const Field3D& rhs) { - - Field3D result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(rhs.getRegionID()); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs - rhs[index]; - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for multiplication of BoutReal and Field2D -Field2D operator*(const BoutReal lhs, const Field2D& rhs) { - - Field2D result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs * rhs[index]; - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for division of BoutReal and Field2D -Field2D operator/(const BoutReal lhs, const Field2D& rhs) { - - Field2D result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs / rhs[index]; - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for addition of BoutReal and Field2D -Field2D operator+(const BoutReal lhs, const Field2D& rhs) { - - Field2D result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs + rhs[index]; - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for subtraction of BoutReal and Field2D -Field2D operator-(const BoutReal lhs, const Field2D& rhs) { - - Field2D result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs - rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ wrapper for multiplication of BoutReal and FieldPerp FieldPerp operator*(const BoutReal lhs, const FieldPerp& rhs) { @@ -2182,216 +1635,6 @@ FieldPerp operator-(const BoutReal lhs, const FieldPerp& rhs) { return result; } -// Provide the C++ wrapper for multiplication of Field3D and Field3DParallel -Field3DParallel operator*(const Field3D& lhs, const Field3DParallel& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3DParallel result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) * rhs.yup(i); - result.ydown(i) = lhs.ydown(i) * rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] * rhs[index]; - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for division of Field3D and Field3DParallel -Field3DParallel operator/(const Field3D& lhs, const Field3DParallel& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3DParallel result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) / rhs.yup(i); - result.ydown(i) = lhs.ydown(i) / rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] / rhs[index]; - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for addition of Field3D and Field3DParallel -Field3DParallel operator+(const Field3D& lhs, const Field3DParallel& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3DParallel result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) + rhs.yup(i); - result.ydown(i) = lhs.ydown(i) + rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] + rhs[index]; - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for subtraction of Field3D and Field3DParallel -Field3DParallel operator-(const Field3D& lhs, const Field3DParallel& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3DParallel result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) - rhs.yup(i); - result.ydown(i) = lhs.ydown(i) - rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] - rhs[index]; - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for multiplication of Field3DParallel and Field3D -Field3DParallel operator*(const Field3DParallel& lhs, const Field3D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3DParallel result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) * rhs.yup(i); - result.ydown(i) = lhs.ydown(i) * rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] * rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3DParallel by multiplication with Field3D Field3DParallel& Field3DParallel::operator*=(const Field3D& rhs) { // only if data is unique we update the field @@ -2431,48 +1674,6 @@ Field3DParallel& Field3DParallel::operator*=(const Field3D& rhs) { return *this; } -// Provide the C++ wrapper for division of Field3DParallel and Field3D -Field3DParallel operator/(const Field3DParallel& lhs, const Field3D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3DParallel result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) / rhs.yup(i); - result.ydown(i) = lhs.ydown(i) / rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] / rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3DParallel by division with Field3D Field3DParallel& Field3DParallel::operator/=(const Field3D& rhs) { // only if data is unique we update the field @@ -2512,48 +1713,6 @@ Field3DParallel& Field3DParallel::operator/=(const Field3D& rhs) { return *this; } -// Provide the C++ wrapper for addition of Field3DParallel and Field3D -Field3DParallel operator+(const Field3DParallel& lhs, const Field3D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3DParallel result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) + rhs.yup(i); - result.ydown(i) = lhs.ydown(i) + rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] + rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3DParallel by addition with Field3D Field3DParallel& Field3DParallel::operator+=(const Field3D& rhs) { // only if data is unique we update the field @@ -2593,48 +1752,6 @@ Field3DParallel& Field3DParallel::operator+=(const Field3D& rhs) { return *this; } -// Provide the C++ wrapper for subtraction of Field3DParallel and Field3D -Field3DParallel operator-(const Field3DParallel& lhs, const Field3D& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3DParallel result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) - rhs.yup(i); - result.ydown(i) = lhs.ydown(i) - rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] - rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3DParallel by subtraction with Field3D Field3DParallel& Field3DParallel::operator-=(const Field3D& rhs) { // only if data is unique we update the field @@ -2674,48 +1791,6 @@ Field3DParallel& Field3DParallel::operator-=(const Field3D& rhs) { return *this; } -// Provide the C++ wrapper for multiplication of Field3DParallel and Field3DParallel -Field3DParallel operator*(const Field3DParallel& lhs, const Field3DParallel& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3DParallel result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) * rhs.yup(i); - result.ydown(i) = lhs.ydown(i) * rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] * rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3DParallel by multiplication with Field3DParallel Field3DParallel& Field3DParallel::operator*=(const Field3DParallel& rhs) { // only if data is unique we update the field @@ -2755,48 +1830,6 @@ Field3DParallel& Field3DParallel::operator*=(const Field3DParallel& rhs) { return *this; } -// Provide the C++ wrapper for division of Field3DParallel and Field3DParallel -Field3DParallel operator/(const Field3DParallel& lhs, const Field3DParallel& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3DParallel result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) / rhs.yup(i); - result.ydown(i) = lhs.ydown(i) / rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] / rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3DParallel by division with Field3DParallel Field3DParallel& Field3DParallel::operator/=(const Field3DParallel& rhs) { // only if data is unique we update the field @@ -2836,48 +1869,6 @@ Field3DParallel& Field3DParallel::operator/=(const Field3DParallel& rhs) { return *this; } -// Provide the C++ wrapper for addition of Field3DParallel and Field3DParallel -Field3DParallel operator+(const Field3DParallel& lhs, const Field3DParallel& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3DParallel result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) + rhs.yup(i); - result.ydown(i) = lhs.ydown(i) + rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] + rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3DParallel by addition with Field3DParallel Field3DParallel& Field3DParallel::operator+=(const Field3DParallel& rhs) { // only if data is unique we update the field @@ -2917,48 +1908,6 @@ Field3DParallel& Field3DParallel::operator+=(const Field3DParallel& rhs) { return *this; } -// Provide the C++ wrapper for subtraction of Field3DParallel and Field3DParallel -Field3DParallel operator-(const Field3DParallel& lhs, const Field3DParallel& rhs) { - ASSERT1_FIELDS_COMPATIBLE(lhs, rhs); - - Field3DParallel result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getMesh()->getCommonRegion(lhs.getRegionID(), rhs.getRegionID())); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) - rhs.yup(i); - result.ydown(i) = lhs.ydown(i) - rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] - rhs[index]; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3DParallel by subtraction with Field3DParallel Field3DParallel& Field3DParallel::operator-=(const Field3DParallel& rhs) { // only if data is unique we update the field @@ -2998,42 +1947,6 @@ Field3DParallel& Field3DParallel::operator-=(const Field3DParallel& rhs) { return *this; } -// Provide the C++ wrapper for multiplication of Field3DParallel and BoutReal -Field3DParallel operator*(const Field3DParallel& lhs, const BoutReal rhs) { - - Field3DParallel result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getRegionID()); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) * rhs; - result.ydown(i) = lhs.ydown(i) * rhs; - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] * rhs; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3DParallel by multiplication with BoutReal Field3DParallel& Field3DParallel::operator*=(const BoutReal rhs) { // only if data is unique we update the field @@ -3070,43 +1983,6 @@ Field3DParallel& Field3DParallel::operator*=(const BoutReal rhs) { return *this; } -// Provide the C++ wrapper for division of Field3DParallel and BoutReal -Field3DParallel operator/(const Field3DParallel& lhs, const BoutReal rhs) { - - Field3DParallel result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getRegionID()); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) / rhs; - result.ydown(i) = lhs.ydown(i) / rhs; - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - const auto tmp = 1.0 / rhs; - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] * tmp; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3DParallel by division with BoutReal Field3DParallel& Field3DParallel::operator/=(const BoutReal rhs) { // only if data is unique we update the field @@ -3143,42 +2019,6 @@ Field3DParallel& Field3DParallel::operator/=(const BoutReal rhs) { return *this; } -// Provide the C++ wrapper for addition of Field3DParallel and BoutReal -Field3DParallel operator+(const Field3DParallel& lhs, const BoutReal rhs) { - - Field3DParallel result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getRegionID()); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) + rhs; - result.ydown(i) = lhs.ydown(i) + rhs; - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] + rhs; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3DParallel by addition with BoutReal Field3DParallel& Field3DParallel::operator+=(const BoutReal rhs) { // only if data is unique we update the field @@ -3215,42 +2055,6 @@ Field3DParallel& Field3DParallel::operator+=(const BoutReal rhs) { return *this; } -// Provide the C++ wrapper for subtraction of Field3DParallel and BoutReal -Field3DParallel operator-(const Field3DParallel& lhs, const BoutReal rhs) { - - Field3DParallel result{emptyFrom(lhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(lhs.getRegionID()); - if (result.isFci()) { - - ASSERT2(lhs.hasParallelSlices()); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - ASSERT2(lhs.ydown(i).isAllocated()); - ASSERT2(lhs.yup(i).isAllocated()); - } - - result.splitParallelSlices(); - for (size_t i{0}; i < lhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs.yup(i) - rhs; - result.ydown(i) = lhs.ydown(i) - rhs; - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs[index] - rhs; - } - checkData(result); - return result; -} - // Provide the C++ operator to update Field3DParallel by subtraction with BoutReal Field3DParallel& Field3DParallel::operator-=(const BoutReal rhs) { // only if data is unique we update the field @@ -3286,143 +2090,3 @@ Field3DParallel& Field3DParallel::operator-=(const BoutReal rhs) { } return *this; } - -// Provide the C++ wrapper for multiplication of BoutReal and Field3DParallel -Field3DParallel operator*(const BoutReal lhs, const Field3DParallel& rhs) { - - Field3DParallel result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(rhs.getRegionID()); - if (result.isFci()) { - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs * rhs.yup(i); - result.ydown(i) = lhs * rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs * rhs[index]; - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for division of BoutReal and Field3DParallel -Field3DParallel operator/(const BoutReal lhs, const Field3DParallel& rhs) { - - Field3DParallel result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(rhs.getRegionID()); - if (result.isFci()) { - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs / rhs.yup(i); - result.ydown(i) = lhs / rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs / rhs[index]; - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for addition of BoutReal and Field3DParallel -Field3DParallel operator+(const BoutReal lhs, const Field3DParallel& rhs) { - - Field3DParallel result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(rhs.getRegionID()); - if (result.isFci()) { - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs + rhs.yup(i); - result.ydown(i) = lhs + rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs + rhs[index]; - } - checkData(result); - return result; -} - -// Provide the C++ wrapper for subtraction of BoutReal and Field3DParallel -Field3DParallel operator-(const BoutReal lhs, const Field3DParallel& rhs) { - - Field3DParallel result{emptyFrom(rhs)}; - checkData(lhs); - checkData(rhs); - - result.setRegion(rhs.getRegionID()); - if (result.isFci()) { - - ASSERT2(rhs.hasParallelSlices()); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - ASSERT2(rhs.ydown(i).isAllocated()); - ASSERT2(rhs.yup(i).isAllocated()); - } - result.splitParallelSlices(); - for (size_t i{0}; i < rhs.numberParallelSlices(); ++i) { - result.yup(i) = lhs - rhs.yup(i); - result.ydown(i) = lhs - rhs.ydown(i); - } - - ASSERT2(result.hasParallelSlices()); - for (size_t i{0}; i < result.numberParallelSlices(); ++i) { - ASSERT2(result.ydown(i).isAllocated()); - ASSERT2(result.yup(i).isAllocated()); - } - } - - BOUT_FOR_SERIAL(index, result.getValidRegionWithDefault("RGN_ALL")) { - result[index] = lhs - rhs[index]; - } - checkData(result); - return result; -} diff --git a/src/field/globalfield.cxx b/src/field/globalfield.cxx index 6bd2fd4dbf..a8c8f3430f 100644 --- a/src/field/globalfield.cxx +++ b/src/field/globalfield.cxx @@ -2,6 +2,8 @@ #include #include #include +#include +#include GlobalField::GlobalField(Mesh* m, int proc, int xsize, int ysize, int zsize) : mesh(m), data_on_proc(proc), nx(xsize), ny(ysize), nz(zsize) { diff --git a/src/field/vecops.cxx b/src/field/vecops.cxx index 672e8f6c09..1bb3d991f4 100644 --- a/src/field/vecops.cxx +++ b/src/field/vecops.cxx @@ -104,10 +104,10 @@ Vector3D Grad_perp(const Field3D& f, CELL_LOC outloc, const std::string& method) Vector3D result(f.getMesh()); result.x = DDX(f, outloc, method) - - metric->g_12 * DDY(f, outloc, method) / SQ(metric->J * metric->Bxy); + - metric->g_12() * DDY(f, outloc, method) / SQ(metric->J() * metric->Bxy()); result.y = 0.0; result.z = DDZ(f, outloc, method) - - metric->g_23 * DDY(f, outloc, method) / SQ(metric->J * metric->Bxy); + - metric->g_23() * DDY(f, outloc, method) / SQ(metric->J() * metric->Bxy()); result.setLocation(result.x.getLocation()); @@ -126,9 +126,9 @@ Vector2D Grad_perp(const Field2D& f, CELL_LOC outloc, const std::string& method) Vector2D result(f.getMesh()); result.x = DDX(f, outloc, method) - - metric->g_12 * DDY(f, outloc, method) / SQ(metric->J * metric->Bxy); + - metric->g_12() * DDY(f, outloc, method) / SQ(metric->J() * metric->Bxy()); result.y = 0.0; - result.z = -metric->g_23 * DDY(f, outloc, method) / SQ(metric->J * metric->Bxy); + result.z = -metric->g_23() * DDY(f, outloc, method) / SQ(metric->J() * metric->Bxy()); result.setLocation(result.x.getLocation()); @@ -159,10 +159,10 @@ Coordinates::FieldMetric Div(const Vector2D& v, CELL_LOC outloc, Vector2D vcn = v; vcn.toContravariant(); - Coordinates::FieldMetric result = DDX(metric->J * vcn.x, outloc, method); - result += DDY(Coordinates::FieldMetric{metric->J * vcn.y}, outloc, method); - result += DDZ(Coordinates::FieldMetric{metric->J * vcn.z}, outloc, method); - result /= metric->J; + Coordinates::FieldMetric result = DDX(metric->J() * vcn.x, outloc, method); + result += DDY(Coordinates::FieldMetric{metric->J() * vcn.y}, outloc, method); + result += DDZ(Coordinates::FieldMetric{metric->J() * vcn.z}, outloc, method); + result /= metric->J(); return result; } @@ -185,7 +185,7 @@ Field3D Div(const Vector3D& v, CELL_LOC outloc, const std::string& method) { Vector3D vcn = v; vcn.toContravariant(); - Field3D vcnJy = vcn.y.getCoordinates()->J * vcn.y; + Field3D vcnJy = vcn.y.getCoordinates()->J() * vcn.y; if (v.y.hasParallelSlices()) { // If v.y has parallel slices then we are using ShiftedMetric (with // mesh:calcParallelSlices_on_communicate=true) or FCI, so we should calculate @@ -194,9 +194,9 @@ Field3D Div(const Vector3D& v, CELL_LOC outloc, const std::string& method) { } auto result = DDY(vcnJy, outloc, method); - result += DDX(Field3D{vcn.x.getCoordinates()->J * vcn.x}, outloc, method); - result += DDZ(Field3D{vcn.z.getCoordinates()->J * vcn.z}, outloc, method); - result /= metric->J; + result += DDX(Field3D{vcn.x.getCoordinates()->J() * vcn.x}, outloc, method); + result += DDZ(Field3D{vcn.z.getCoordinates()->J() * vcn.z}, outloc, method); + result /= metric->J(); return result; } @@ -224,12 +224,12 @@ Coordinates::FieldMetric Div(const Vector2D& v, const Field2D& f, CELL_LOC outlo vcn.toContravariant(); Coordinates::FieldMetric result = FDDX( - Coordinates::FieldMetric{vcn.x.getCoordinates()->J * vcn.x}, f, outloc, method); - result += FDDY(Coordinates::FieldMetric{vcn.y.getCoordinates()->J * vcn.y}, f, outloc, + Coordinates::FieldMetric{vcn.x.getCoordinates()->J() * vcn.x}, f, outloc, method); + result += FDDY(Coordinates::FieldMetric{vcn.y.getCoordinates()->J() * vcn.y}, f, outloc, method); - result += FDDZ(Coordinates::FieldMetric{vcn.z.getCoordinates()->J * vcn.z}, f, outloc, + result += FDDZ(Coordinates::FieldMetric{vcn.z.getCoordinates()->J() * vcn.z}, f, outloc, method); - result /= metric->J; + result /= metric->J(); return result; } @@ -250,10 +250,10 @@ Field3D Div(const Vector3D& v, const Field3D& f, CELL_LOC outloc, Vector3D vcn = v; vcn.toContravariant(); - Field3D result = FDDX(Field3D{vcn.x.getCoordinates()->J * vcn.x}, f, outloc, method); - result += FDDY(Field3D{vcn.y.getCoordinates()->J * vcn.y}, f, outloc, method); - result += FDDZ(Field3D{vcn.z.getCoordinates()->J * vcn.z}, f, outloc, method); - result /= metric->J; + Field3D result = FDDX(Field3D{vcn.x.getCoordinates()->J() * vcn.x}, f, outloc, method); + result += FDDY(Field3D{vcn.y.getCoordinates()->J() * vcn.y}, f, outloc, method); + result += FDDZ(Field3D{vcn.z.getCoordinates()->J() * vcn.z}, f, outloc, method); + result /= metric->J(); return result; } @@ -274,12 +274,12 @@ Vector2D Curl(const Vector2D& v) { // get components (curl(v))^j Vector2D result(localmesh); - result.x = (DDY(vco.z) - DDZ(vco.y)) / metric->J; - result.y = (DDZ(vco.x) - DDX(vco.z)) / metric->J; - result.z = (DDX(vco.y) - DDY(vco.x)) / metric->J; + result.x = (DDY(vco.z) - DDZ(vco.y)) / metric->J(); + result.y = (DDZ(vco.x) - DDX(vco.z)) / metric->J(); + result.z = (DDX(vco.y) - DDY(vco.x)) / metric->J(); /// Coordinate torsion - result.z -= metric->ShiftTorsion * vco.z / metric->J; + result.z -= metric->ShiftTorsion() * vco.z / metric->J(); result.setLocation(v.getLocation()); @@ -302,12 +302,12 @@ Vector3D Curl(const Vector3D& v) { // get components (curl(v))^j Vector3D result(localmesh); - result.x = (DDY(vco.z) - DDZ(vco.y)) / metric->J; - result.y = (DDZ(vco.x) - DDX(vco.z)) / metric->J; - result.z = (DDX(vco.y) - DDY(vco.x)) / metric->J; + result.x = (DDY(vco.z) - DDZ(vco.y)) / metric->J(); + result.y = (DDZ(vco.x) - DDX(vco.z)) / metric->J(); + result.z = (DDX(vco.y) - DDY(vco.x)) / metric->J(); // Coordinate torsion - result.z -= metric->ShiftTorsion * vco.z / metric->J; + result.z -= metric->ShiftTorsion() * vco.z / metric->J(); result.setLocation(v.getLocation()); @@ -387,80 +387,80 @@ R V_dot_Grad(const T& v, const F& a) { result.x = VDDX(vcn.x, a.x) + VDDY(vcn.y, a.x) + VDDZ(vcn.z, a.x); BOUT_FOR(i, result.x.getRegion("RGN_ALL")) { result.x[i] -= vcn.x[i] - * (metric->G1_11[i] * a.x[i] + metric->G2_11[i] * a.y[i] - + metric->G3_11[i] * a.z[i]); + * (metric->G1_11()[i] * a.x[i] + metric->G2_11()[i] * a.y[i] + + metric->G3_11()[i] * a.z[i]); result.x[i] -= vcn.y[i] - * (metric->G1_12[i] * a.x[i] + metric->G2_12[i] * a.y[i] - + metric->G3_12[i] * a.z[i]); + * (metric->G1_12()[i] * a.x[i] + metric->G2_12()[i] * a.y[i] + + metric->G3_12()[i] * a.z[i]); result.x[i] -= vcn.z[i] - * (metric->G1_13[i] * a.x[i] + metric->G2_13[i] * a.y[i] - + metric->G3_13[i] * a.z[i]); + * (metric->G1_13()[i] * a.x[i] + metric->G2_13()[i] * a.y[i] + + metric->G3_13()[i] * a.z[i]); } result.y = VDDX(vcn.x, a.y) + VDDY(vcn.y, a.y) + VDDZ(vcn.z, a.y); BOUT_FOR(i, result.y.getRegion("RGN_ALL")) { result.y[i] -= vcn.x[i] - * (metric->G1_12[i] * a.x[i] + metric->G2_12[i] * a.y[i] - + metric->G3_12[i] * a.z[i]); + * (metric->G1_12()[i] * a.x[i] + metric->G2_12()[i] * a.y[i] + + metric->G3_12()[i] * a.z[i]); result.y[i] -= vcn.y[i] - * (metric->G1_22[i] * a.x[i] + metric->G2_22[i] * a.y[i] - + metric->G3_22[i] * a.z[i]); + * (metric->G1_22()[i] * a.x[i] + metric->G2_22()[i] * a.y[i] + + metric->G3_22()[i] * a.z[i]); result.y[i] -= vcn.z[i] - * (metric->G1_23[i] * a.x[i] + metric->G2_23[i] * a.y[i] - + metric->G3_23[i] * a.z[i]); + * (metric->G1_23()[i] * a.x[i] + metric->G2_23()[i] * a.y[i] + + metric->G3_23()[i] * a.z[i]); } result.z = VDDX(vcn.x, a.z) + VDDY(vcn.y, a.z) + VDDZ(vcn.z, a.z); BOUT_FOR(i, result.z.getRegion("RGN_ALL")) { result.z[i] -= vcn.x[i] - * (metric->G1_13[i] * a.x[i] + metric->G2_13[i] * a.y[i] - + metric->G3_13[i] * a.z[i]); + * (metric->G1_13()[i] * a.x[i] + metric->G2_13()[i] * a.y[i] + + metric->G3_13()[i] * a.z[i]); result.z[i] -= vcn.y[i] - * (metric->G1_23[i] * a.x[i] + metric->G2_23[i] * a.y[i] - + metric->G3_23[i] * a.z[i]); + * (metric->G1_23()[i] * a.x[i] + metric->G2_23()[i] * a.y[i] + + metric->G3_23()[i] * a.z[i]); result.z[i] -= vcn.z[i] - * (metric->G1_33[i] * a.x[i] + metric->G2_33[i] * a.y[i] - + metric->G3_33[i] * a.z[i]); + * (metric->G1_33()[i] * a.x[i] + metric->G2_33()[i] * a.y[i] + + metric->G3_33()[i] * a.z[i]); } result.covariant = true; } else { result.x = VDDX(vcn.x, a.x) + VDDY(vcn.y, a.x) + VDDZ(vcn.z, a.x); BOUT_FOR(i, result.x.getRegion("RGN_ALL")) { result.x[i] += vcn.x[i] - * (metric->G1_11[i] * a.x[i] + metric->G1_12[i] * a.y[i] - + metric->G1_13[i] * a.z[i]); + * (metric->G1_11()[i] * a.x[i] + metric->G1_12()[i] * a.y[i] + + metric->G1_13()[i] * a.z[i]); result.x[i] += vcn.y[i] - * (metric->G1_12[i] * a.x[i] + metric->G1_22[i] * a.y[i] - + metric->G1_23[i] * a.z[i]); + * (metric->G1_12()[i] * a.x[i] + metric->G1_22()[i] * a.y[i] + + metric->G1_23()[i] * a.z[i]); result.x[i] += vcn.z[i] - * (metric->G1_13[i] * a.x[i] + metric->G1_23[i] * a.y[i] - + metric->G1_33[i] * a.z[i]); + * (metric->G1_13()[i] * a.x[i] + metric->G1_23()[i] * a.y[i] + + metric->G1_33()[i] * a.z[i]); } result.y = VDDX(vcn.x, a.y) + VDDY(vcn.y, a.y) + VDDZ(vcn.z, a.y); BOUT_FOR(i, result.y.getRegion("RGN_ALL")) { result.y[i] += vcn.x[i] - * (metric->G2_11[i] * a.x[i] + metric->G2_12[i] * a.y[i] - + metric->G2_13[i] * a.z[i]); + * (metric->G2_11()[i] * a.x[i] + metric->G2_12()[i] * a.y[i] + + metric->G2_13()[i] * a.z[i]); result.y[i] += vcn.y[i] - * (metric->G2_12[i] * a.x[i] + metric->G2_22[i] * a.y[i] - + metric->G2_23[i] * a.z[i]); + * (metric->G2_12()[i] * a.x[i] + metric->G2_22()[i] * a.y[i] + + metric->G2_23()[i] * a.z[i]); result.y[i] += vcn.z[i] - * (metric->G2_13[i] * a.x[i] + metric->G2_23[i] * a.y[i] - + metric->G2_33[i] * a.z[i]); + * (metric->G2_13()[i] * a.x[i] + metric->G2_23()[i] * a.y[i] + + metric->G2_33()[i] * a.z[i]); } result.z = VDDX(vcn.x, a.z) + VDDY(vcn.y, a.z) + VDDZ(vcn.z, a.z); BOUT_FOR(i, result.z.getRegion("RGN_ALL")) { result.z[i] += vcn.x[i] - * (metric->G3_11[i] * a.x[i] + metric->G3_12[i] * a.y[i] - + metric->G3_13[i] * a.z[i]); + * (metric->G3_11()[i] * a.x[i] + metric->G3_12()[i] * a.y[i] + + metric->G3_13()[i] * a.z[i]); result.z[i] += vcn.y[i] - * (metric->G3_12[i] * a.x[i] + metric->G3_22[i] * a.y[i] - + metric->G3_23[i] * a.z[i]); + * (metric->G3_12()[i] * a.x[i] + metric->G3_22()[i] * a.y[i] + + metric->G3_23()[i] * a.z[i]); result.z[i] += vcn.z[i] - * (metric->G3_13[i] * a.x[i] + metric->G3_23[i] * a.y[i] - + metric->G3_33[i] * a.z[i]); + * (metric->G3_13()[i] * a.x[i] + metric->G3_23()[i] * a.y[i] + + metric->G3_33()[i] * a.z[i]); } result.covariant = false; diff --git a/src/field/vector2d.cxx b/src/field/vector2d.cxx index ab2d993549..d6182117ff 100644 --- a/src/field/vector2d.cxx +++ b/src/field/vector2d.cxx @@ -2,7 +2,7 @@ * Class for 2D vectors. Built on the Field2D class, * all operators relating to vectors are here (none in Field classes) * - * As with Field2D, Vector2D are constant in z (toroidal angle) + * As with Field2D, Vector2D are constant in z (toroidal angle) * * B.Dudson, October 2007 * @@ -10,7 +10,7 @@ * Copyright 2010 B.D.Dudson, S.Farley, M.V.Umansky, X.Q.Xu * * Contact: Ben Dudson, bd512@york.ac.uk - * + * * This file is part of BOUT++. * * BOUT++ is free software: you can redistribute it and/or modify @@ -28,16 +28,26 @@ * **************************************************************************/ -#include - +#include #include +#include #include +#include +#include +#include +#include #include +#include +#include #include #include +#include +#include +#include + Vector2D::Vector2D(const Vector2D& f) - : FieldData(f), x(f.x), y(f.y), z(f.z), covariant(f.covariant), deriv(nullptr), + : FieldData(f), x(f.x), y(f.y), z(f.z), covariant(f.covariant), location(f.getLocation()) {} Vector2D::Vector2D(Mesh* localmesh, bool covariant, CELL_LOC location) @@ -67,10 +77,9 @@ void Vector2D::toCovariant() { Mesh* localmesh = getMesh(); if (location == CELL_VSHIFT) { - Coordinates *metric_x, *metric_y, *metric_z; - metric_x = localmesh->getCoordinates(CELL_XLOW); - metric_y = localmesh->getCoordinates(CELL_YLOW); - metric_z = localmesh->getCoordinates(CELL_ZLOW); + const auto* metric_x = localmesh->getCoordinates(CELL_XLOW); + const auto* metric_y = localmesh->getCoordinates(CELL_YLOW); + const auto* metric_z = localmesh->getCoordinates(CELL_ZLOW); // Fields at different locations so we need to interpolate // Note : Could reduce peak memory requirement here by just @@ -86,23 +95,28 @@ void Vector2D::toCovariant() { // multiply by g_{ij} BOUT_FOR(i, x.getRegion("RGN_ALL")) { - x[i] = metric_x->g_11[i] * x[i] + metric_x->g_12[i] * y_at_x[i] - + metric_x->g_13[i] * z_at_x[i]; - y[i] = metric_y->g_22[i] * y[i] + metric_y->g_12[i] * x_at_y[i] - + metric_y->g_23[i] * z_at_y[i]; - z[i] = metric_z->g_33[i] * z[i] + metric_z->g_13[i] * x_at_z[i] - + metric_z->g_23[i] * y_at_z[i]; + x[i] = (metric_x->g_11()[i] * x[i]) + (metric_x->g_12()[i] * y_at_x[i]) + + (metric_x->g_13()[i] * z_at_x[i]); + y[i] = (metric_y->g_22()[i] * y[i]) + (metric_y->g_12()[i] * x_at_y[i]) + + (metric_y->g_23()[i] * z_at_y[i]); + z[i] = (metric_z->g_33()[i] * z[i]) + (metric_z->g_13()[i] * x_at_z[i]) + + (metric_z->g_23()[i] * y_at_z[i]); }; } else { - const auto metric = localmesh->getCoordinates(location); + auto* const metric = localmesh->getCoordinates(location); // Need to use temporary arrays to store result - Coordinates::FieldMetric gx{emptyFrom(x)}, gy{emptyFrom(y)}, gz{emptyFrom(z)}; + bout::FieldMetric gx{emptyFrom(x)}; + bout::FieldMetric gy{emptyFrom(y)}; + bout::FieldMetric gz{emptyFrom(z)}; BOUT_FOR(i, x.getRegion("RGN_ALL")) { - gx[i] = metric->g_11[i] * x[i] + metric->g_12[i] * y[i] + metric->g_13[i] * z[i]; - gy[i] = metric->g_22[i] * y[i] + metric->g_12[i] * x[i] + metric->g_23[i] * z[i]; - gz[i] = metric->g_33[i] * z[i] + metric->g_13[i] * x[i] + metric->g_23[i] * y[i]; + gx[i] = (metric->g_11()[i] * x[i]) + (metric->g_12()[i] * y[i]) + + (metric->g_13()[i] * z[i]); + gy[i] = (metric->g_22()[i] * y[i]) + (metric->g_12()[i] * x[i]) + + (metric->g_23()[i] * z[i]); + gz[i] = (metric->g_33()[i] * z[i]) + (metric->g_13()[i] * x[i]) + + (metric->g_23()[i] * y[i]); }; x = gx; @@ -120,11 +134,9 @@ void Vector2D::toContravariant() { Mesh* localmesh = getMesh(); if (location == CELL_VSHIFT) { - Coordinates *metric_x, *metric_y, *metric_z; - - metric_x = localmesh->getCoordinates(CELL_XLOW); - metric_y = localmesh->getCoordinates(CELL_YLOW); - metric_z = localmesh->getCoordinates(CELL_ZLOW); + const auto* metric_x = localmesh->getCoordinates(CELL_XLOW); + const auto* metric_y = localmesh->getCoordinates(CELL_YLOW); + const auto* metric_z = localmesh->getCoordinates(CELL_ZLOW); // Fields at different locations so we need to interpolate // Note : Could reduce peak memory requirement here by just @@ -140,24 +152,29 @@ void Vector2D::toContravariant() { // multiply by g_{ij} BOUT_FOR(i, x.getRegion("RGN_ALL")) { - x[i] = metric_x->g11[i] * x[i] + metric_x->g12[i] * y_at_x[i] - + metric_x->g13[i] * z_at_x[i]; - y[i] = metric_y->g22[i] * y[i] + metric_y->g12[i] * x_at_y[i] - + metric_y->g23[i] * z_at_y[i]; - z[i] = metric_z->g33[i] * z[i] + metric_z->g13[i] * x_at_z[i] - + metric_z->g23[i] * y_at_z[i]; + x[i] = (metric_x->g11()[i] * x[i]) + (metric_x->g12()[i] * y_at_x[i]) + + (metric_x->g13()[i] * z_at_x[i]); + y[i] = (metric_y->g22()[i] * y[i]) + (metric_y->g12()[i] * x_at_y[i]) + + (metric_y->g23()[i] * z_at_y[i]); + z[i] = (metric_z->g33()[i] * z[i]) + (metric_z->g13()[i] * x_at_z[i]) + + (metric_z->g23()[i] * y_at_z[i]); }; } else { - const auto metric = localmesh->getCoordinates(location); + auto* const metric = localmesh->getCoordinates(location); // Need to use temporary arrays to store result - Coordinates::FieldMetric gx{emptyFrom(x)}, gy{emptyFrom(y)}, gz{emptyFrom(z)}; + bout::FieldMetric gx{emptyFrom(x)}; + bout::FieldMetric gy{emptyFrom(y)}; + bout::FieldMetric gz{emptyFrom(z)}; BOUT_FOR(i, x.getRegion("RGN_ALL")) { - gx[i] = metric->g11[i] * x[i] + metric->g12[i] * y[i] + metric->g13[i] * z[i]; - gy[i] = metric->g22[i] * y[i] + metric->g12[i] * x[i] + metric->g23[i] * z[i]; - gz[i] = metric->g33[i] * z[i] + metric->g13[i] * x[i] + metric->g23[i] * y[i]; + gx[i] = (metric->g11()[i] * x[i]) + (metric->g12()[i] * y[i]) + + (metric->g13()[i] * z[i]); + gy[i] = (metric->g22()[i] * y[i]) + (metric->g12()[i] * x[i]) + + (metric->g23()[i] * z[i]); + gz[i] = (metric->g33()[i] * z[i]) + (metric->g13()[i] * x[i]) + + (metric->g23()[i] * y[i]); }; x = gx; @@ -197,7 +214,7 @@ Vector2D* Vector2D::timeDeriv() { } /*************************************************************** - * OPERATORS + * OPERATORS ***************************************************************/ /////////////////// ASSIGNMENT //////////////////// @@ -303,7 +320,7 @@ Vector2D& Vector2D::operator/=(const Field2D& rhs) { } /*************************************************************** - * BINARY OPERATORS + * BINARY OPERATORS ***************************************************************/ ////////////////// ADDITION ////////////////////// @@ -386,22 +403,22 @@ const Coordinates::FieldMetric Vector2D::operator*(const Vector2D& rhs) const { result = x * rhs.x + y * rhs.y + z * rhs.z; } else { // Both are covariant or contravariant - Coordinates* metric = localmesh->getCoordinates(location); + const Coordinates* metric = localmesh->getCoordinates(location); if (covariant) { // Both covariant - result = - x * rhs.x * metric->g11 + y * rhs.y * metric->g22 + z * rhs.z * metric->g33; - result += (x * rhs.y + y * rhs.x) * metric->g12 - + (x * rhs.z + z * rhs.x) * metric->g13 - + (y * rhs.z + z * rhs.y) * metric->g23; + result = x * rhs.x * metric->g11() + y * rhs.y * metric->g22() + + z * rhs.z * metric->g33(); + result += (x * rhs.y + y * rhs.x) * metric->g12() + + (x * rhs.z + z * rhs.x) * metric->g13() + + (y * rhs.z + z * rhs.y) * metric->g23(); } else { // Both contravariant - result = - x * rhs.x * metric->g_11 + y * rhs.y * metric->g_22 + z * rhs.z * metric->g_33; - result += (x * rhs.y + y * rhs.x) * metric->g_12 - + (x * rhs.z + z * rhs.x) * metric->g_13 - + (y * rhs.z + z * rhs.y) * metric->g_23; + result = x * rhs.x * metric->g_11() + y * rhs.y * metric->g_22() + + z * rhs.z * metric->g_33(); + result += (x * rhs.y + y * rhs.x) * metric->g_12() + + (x * rhs.z + z * rhs.x) * metric->g_13() + + (y * rhs.z + z * rhs.y) * metric->g_23(); } } diff --git a/src/field/vector3d.cxx b/src/field/vector3d.cxx index 7fb4a88918..fc61ab2a24 100644 --- a/src/field/vector3d.cxx +++ b/src/field/vector3d.cxx @@ -10,7 +10,7 @@ * Copyright 2010 B.D.Dudson, S.Farley, M.V.Umansky, X.Q.Xu * * Contact: Ben Dudson, bd512@york.ac.uk - * + * * This file is part of BOUT++. * * BOUT++ is free software: you can redistribute it and/or modify @@ -28,17 +28,24 @@ * **************************************************************************/ -#include - #include #include +#include #include +#include +#include +#include +#include #include +#include #include #include +#include +#include + Vector3D::Vector3D(const Vector3D& f) - : FieldData(f), x(f.x), y(f.y), z(f.z), covariant(f.covariant), deriv(nullptr), + : FieldData(f), x(f.x), y(f.y), z(f.z), covariant(f.covariant), location(f.getLocation()) {} Vector3D::Vector3D(Mesh* localmesh, bool covariant, CELL_LOC location) @@ -68,10 +75,9 @@ void Vector3D::toCovariant() { Mesh* localmesh = getMesh(); if (location == CELL_VSHIFT) { - Coordinates *metric_x, *metric_y, *metric_z; - metric_x = localmesh->getCoordinates(CELL_XLOW); - metric_y = localmesh->getCoordinates(CELL_YLOW); - metric_z = localmesh->getCoordinates(CELL_ZLOW); + const auto* metric_x = localmesh->getCoordinates(CELL_XLOW); + const auto* metric_y = localmesh->getCoordinates(CELL_YLOW); + const auto* metric_z = localmesh->getCoordinates(CELL_ZLOW); // Fields at different locations so we need to interpolate // Note : Could reduce peak memory requirement here by just @@ -87,23 +93,28 @@ void Vector3D::toCovariant() { // multiply by g_{ij} BOUT_FOR(i, localmesh->getRegion3D("RGN_ALL")) { - x[i] = metric_x->g_11[i] * x[i] + metric_x->g_12[i] * y_at_x[i] - + metric_x->g_13[i] * z_at_x[i]; - y[i] = metric_y->g_22[i] * y[i] + metric_y->g_12[i] * x_at_y[i] - + metric_y->g_23[i] * z_at_y[i]; - z[i] = metric_z->g_33[i] * z[i] + metric_z->g_13[i] * x_at_z[i] - + metric_z->g_23[i] * y_at_z[i]; + x[i] = (metric_x->g_11()[i] * x[i]) + (metric_x->g_12()[i] * y_at_x[i]) + + (metric_x->g_13()[i] * z_at_x[i]); + y[i] = (metric_y->g_22()[i] * y[i]) + (metric_y->g_12()[i] * x_at_y[i]) + + (metric_y->g_23()[i] * z_at_y[i]); + z[i] = (metric_z->g_33()[i] * z[i]) + (metric_z->g_13()[i] * x_at_z[i]) + + (metric_z->g_23()[i] * y_at_z[i]); }; } else { - const auto metric = localmesh->getCoordinates(location); + auto* const metric = localmesh->getCoordinates(location); // Need to use temporary arrays to store result - Field3D gx{emptyFrom(x)}, gy{emptyFrom(y)}, gz{emptyFrom(z)}; + Field3D gx{emptyFrom(x)}; + Field3D gy{emptyFrom(y)}; + Field3D gz{emptyFrom(z)}; BOUT_FOR(i, localmesh->getRegion3D("RGN_ALL")) { - gx[i] = metric->g_11[i] * x[i] + metric->g_12[i] * y[i] + metric->g_13[i] * z[i]; - gy[i] = metric->g_22[i] * y[i] + metric->g_12[i] * x[i] + metric->g_23[i] * z[i]; - gz[i] = metric->g_33[i] * z[i] + metric->g_13[i] * x[i] + metric->g_23[i] * y[i]; + gx[i] = (metric->g_11()[i] * x[i]) + (metric->g_12()[i] * y[i]) + + (metric->g_13()[i] * z[i]); + gy[i] = (metric->g_22()[i] * y[i]) + (metric->g_12()[i] * x[i]) + + (metric->g_23()[i] * z[i]); + gz[i] = (metric->g_33()[i] * z[i]) + (metric->g_13()[i] * x[i]) + + (metric->g_23()[i] * y[i]); }; x = gx; @@ -121,11 +132,9 @@ void Vector3D::toContravariant() { Mesh* localmesh = getMesh(); if (location == CELL_VSHIFT) { - Coordinates *metric_x, *metric_y, *metric_z; - - metric_x = localmesh->getCoordinates(CELL_XLOW); - metric_y = localmesh->getCoordinates(CELL_YLOW); - metric_z = localmesh->getCoordinates(CELL_ZLOW); + const auto* metric_x = localmesh->getCoordinates(CELL_XLOW); + const auto* metric_y = localmesh->getCoordinates(CELL_YLOW); + const auto* metric_z = localmesh->getCoordinates(CELL_ZLOW); // Fields at different locations so we need to interpolate // Note : Could reduce peak memory requirement here by just @@ -141,24 +150,29 @@ void Vector3D::toContravariant() { // multiply by g_{ij} BOUT_FOR(i, localmesh->getRegion3D("RGN_ALL")) { - x[i] = metric_x->g11[i] * x[i] + metric_x->g12[i] * y_at_x[i] - + metric_x->g13[i] * z_at_x[i]; - y[i] = metric_y->g22[i] * y[i] + metric_y->g12[i] * x_at_y[i] - + metric_y->g23[i] * z_at_y[i]; - z[i] = metric_z->g33[i] * z[i] + metric_z->g13[i] * x_at_z[i] - + metric_z->g23[i] * y_at_z[i]; + x[i] = (metric_x->g11()[i] * x[i]) + (metric_x->g12()[i] * y_at_x[i]) + + (metric_x->g13()[i] * z_at_x[i]); + y[i] = (metric_y->g22()[i] * y[i]) + (metric_y->g12()[i] * x_at_y[i]) + + (metric_y->g23()[i] * z_at_y[i]); + z[i] = (metric_z->g33()[i] * z[i]) + (metric_z->g13()[i] * x_at_z[i]) + + (metric_z->g23()[i] * y_at_z[i]); }; } else { - const auto metric = localmesh->getCoordinates(location); + auto* const metric = localmesh->getCoordinates(location); // Need to use temporary arrays to store result - Field3D gx{emptyFrom(x)}, gy{emptyFrom(y)}, gz{emptyFrom(z)}; + Field3D gx{emptyFrom(x)}; + Field3D gy{emptyFrom(y)}; + Field3D gz{emptyFrom(z)}; BOUT_FOR(i, localmesh->getRegion3D("RGN_ALL")) { - gx[i] = metric->g11[i] * x[i] + metric->g12[i] * y[i] + metric->g13[i] * z[i]; - gy[i] = metric->g22[i] * y[i] + metric->g12[i] * x[i] + metric->g23[i] * z[i]; - gz[i] = metric->g33[i] * z[i] + metric->g13[i] * x[i] + metric->g23[i] * y[i]; + gx[i] = (metric->g11()[i] * x[i]) + (metric->g12()[i] * y[i]) + + (metric->g13()[i] * z[i]); + gy[i] = (metric->g22()[i] * y[i]) + (metric->g12()[i] * x[i]) + + (metric->g23()[i] * z[i]); + gz[i] = (metric->g33()[i] * z[i]) + (metric->g13()[i] * x[i]) + + (metric->g23()[i] * y[i]); }; x = gx; @@ -199,7 +213,7 @@ Vector3D* Vector3D::timeDeriv() { } /*************************************************************** - * OPERATORS + * OPERATORS ***************************************************************/ /////////////////// ASSIGNMENT //////////////////// @@ -379,9 +393,9 @@ Vector3D& Vector3D::operator/=(const Field3D& rhs) { Coordinates* metric = localmesh->getCoordinates(lhs.getLocation()); \ \ /* calculate contravariant components of cross-product */ \ - result.x = (lco.y * rco.z - lco.z * rco.y) / metric->J; \ - result.y = (lco.z * rco.x - lco.x * rco.z) / metric->J; \ - result.z = (lco.x * rco.y - lco.y * rco.x) / metric->J; \ + result.x = (lco.y * rco.z - lco.z * rco.y) / metric->J(); \ + result.y = (lco.z * rco.x - lco.x * rco.z) / metric->J(); \ + result.z = (lco.x * rco.y - lco.y * rco.x) / metric->J(); \ result.covariant = false; \ \ return result; \ @@ -393,7 +407,7 @@ CROSS(Vector3D, Vector2D, Vector3D) CROSS(Vector2D, Vector2D, Vector2D) /*************************************************************** - * BINARY OPERATORS + * BINARY OPERATORS ***************************************************************/ ////////////////// ADDITION ////////////////////// @@ -482,18 +496,18 @@ const Field3D Vector3D::operator*(const Vector3D& rhs) const { if (covariant) { // Both covariant - result = - x * rhs.x * metric->g11 + y * rhs.y * metric->g22 + z * rhs.z * metric->g33; - result += (x * rhs.y + y * rhs.x) * metric->g12 - + (x * rhs.z + z * rhs.x) * metric->g13 - + (y * rhs.z + z * rhs.y) * metric->g23; + result = x * rhs.x * metric->g11() + y * rhs.y * metric->g22() + + z * rhs.z * metric->g33(); + result += (x * rhs.y + y * rhs.x) * metric->g12() + + (x * rhs.z + z * rhs.x) * metric->g13() + + (y * rhs.z + z * rhs.y) * metric->g23(); } else { // Both contravariant - result = - x * rhs.x * metric->g_11 + y * rhs.y * metric->g_22 + z * rhs.z * metric->g_33; - result += (x * rhs.y + y * rhs.x) * metric->g_12 - + (x * rhs.z + z * rhs.x) * metric->g_13 - + (y * rhs.z + z * rhs.y) * metric->g_23; + result = x * rhs.x * metric->g_11() + y * rhs.y * metric->g_22() + + z * rhs.z * metric->g_33(); + result += (x * rhs.y + y * rhs.x) * metric->g_12() + + (x * rhs.z + z * rhs.x) * metric->g_13() + + (y * rhs.z + z * rhs.y) * metric->g_23(); } } @@ -514,18 +528,18 @@ const Field3D Vector3D::operator*(const Vector2D& rhs) const { Coordinates* metric = x.getCoordinates(location); if (covariant) { // Both covariant - result = - x * rhs.x * metric->g11 + y * rhs.y * metric->g22 + z * rhs.z * metric->g33; - result += (x * rhs.y + y * rhs.x) * metric->g12 - + (x * rhs.z + z * rhs.x) * metric->g13 - + (y * rhs.z + z * rhs.y) * metric->g23; + result = x * rhs.x * metric->g11() + y * rhs.y * metric->g22() + + z * rhs.z * metric->g33(); + result += (x * rhs.y + y * rhs.x) * metric->g12() + + (x * rhs.z + z * rhs.x) * metric->g13() + + (y * rhs.z + z * rhs.y) * metric->g23(); } else { // Both contravariant - result = - x * rhs.x * metric->g_11 + y * rhs.y * metric->g_22 + z * rhs.z * metric->g_33; - result += (x * rhs.y + y * rhs.x) * metric->g_12 - + (x * rhs.z + z * rhs.x) * metric->g_13 - + (y * rhs.z + z * rhs.y) * metric->g_23; + result = x * rhs.x * metric->g_11() + y * rhs.y * metric->g_22() + + z * rhs.z * metric->g_33(); + result += (x * rhs.y + y * rhs.x) * metric->g_12() + + (x * rhs.z + z * rhs.x) * metric->g_13() + + (y * rhs.z + z * rhs.y) * metric->g_23(); } } diff --git a/src/invert/laplace/impls/hypre3d/hypre3d_laplace.cxx b/src/invert/laplace/impls/hypre3d/hypre3d_laplace.cxx index beb83a216d..fa555e6867 100644 --- a/src/invert/laplace/impls/hypre3d/hypre3d_laplace.cxx +++ b/src/invert/laplace/impls/hypre3d/hypre3d_laplace.cxx @@ -3,7 +3,7 @@ * Using Hypre Solvers * ************************************************************************** - * Copyright 2021 - 2025 BOUT++ contributors + * Copyright 2021 - 2026 BOUT++ contributors * * Contact: Ben Dudson, dudson2@llnl.gov * @@ -30,17 +30,31 @@ #include "hypre3d_laplace.hxx" #include +#include #include +#include #include #include +#include +#include +#include #include #include #include +#include +#include #include +#include #include #include +#include #include +#include +#include +#include +#include +#include LaplaceHypre3d::LaplaceHypre3d(Options* opt, const CELL_LOC loc, Mesh* mesh_in, Solver*) : Laplacian(opt, loc, mesh_in), A(0.0), C1(1.0), C2(1.0), D(1.0), Ex(0.0), Ez(0.0), @@ -106,8 +120,8 @@ LaplaceHypre3d::LaplaceHypre3d(Options* opt, const CELL_LOC loc, Mesh* mesh_in, BOUT_FOR_SERIAL(i, indexer->getRegionInnerX()) { if (isInnerBoundaryFlagSet(INVERT_AC_GRAD)) { // Neumann on inner X boundary - operator3D(i, i) = -1. / coords->dx[i] / sqrt(coords->g_11[i]); - operator3D(i, i.xp()) = 1. / coords->dx[i] / sqrt(coords->g_11[i]); + operator3D(i, i) = -1. / coords->dx()[i] / sqrt(coords->g_11()[i]); + operator3D(i, i.xp()) = 1. / coords->dx()[i] / sqrt(coords->g_11()[i]); } else { // Dirichlet on inner X boundary operator3D(i, i) = 0.5; @@ -118,8 +132,8 @@ LaplaceHypre3d::LaplaceHypre3d(Options* opt, const CELL_LOC loc, Mesh* mesh_in, BOUT_FOR_SERIAL(i, indexer->getRegionOuterX()) { if (isOuterBoundaryFlagSet(INVERT_AC_GRAD)) { // Neumann on outer X boundary - operator3D(i, i) = 1. / coords->dx[i] / sqrt(coords->g_11[i]); - operator3D(i, i.xm()) = -1. / coords->dx[i] / sqrt(coords->g_11[i]); + operator3D(i, i) = 1. / coords->dx()[i] / sqrt(coords->g_11()[i]); + operator3D(i, i.xm()) = -1. / coords->dx()[i] / sqrt(coords->g_11()[i]); } else { // Dirichlet on outer X boundary operator3D(i, i) = 0.5; @@ -130,8 +144,8 @@ LaplaceHypre3d::LaplaceHypre3d(Options* opt, const CELL_LOC loc, Mesh* mesh_in, BOUT_FOR_SERIAL(i, indexer->getRegionLowerY()) { if ((lower_boundary_flags & INVERT_AC_GRAD) != 0) { // Neumann on lower Y boundary - operator3D(i, i) = -1. / coords->dy[i] / sqrt(coords->g_22[i]); - operator3D(i, i.yp()) = 1. / coords->dy[i] / sqrt(coords->g_22[i]); + operator3D(i, i) = -1. / coords->dy()[i] / sqrt(coords->g_22()[i]); + operator3D(i, i.yp()) = 1. / coords->dy()[i] / sqrt(coords->g_22()[i]); } else { // Dirichlet on lower Y boundary operator3D(i, i) = 0.5; @@ -142,8 +156,8 @@ LaplaceHypre3d::LaplaceHypre3d(Options* opt, const CELL_LOC loc, Mesh* mesh_in, BOUT_FOR_SERIAL(i, indexer->getRegionUpperY()) { if ((upper_boundary_flags & INVERT_AC_GRAD) != 0) { // Neumann on upper Y boundary - operator3D(i, i) = 1. / coords->dy[i] / sqrt(coords->g_22[i]); - operator3D(i, i.ym()) = -1. / coords->dy[i] / sqrt(coords->g_22[i]); + operator3D(i, i) = 1. / coords->dy()[i] / sqrt(coords->g_22()[i]); + operator3D(i, i.ym()) = -1. / coords->dy()[i] / sqrt(coords->g_22()[i]); } else { // Dirichlet on upper Y boundary operator3D(i, i) = 0.5; @@ -218,21 +232,15 @@ Field3D LaplaceHypre3d::solve(const Field3D& b_in, const Field3D& x0) { CALI_MARK_BEGIN("LaplaceHypre3d_solve:vectorAssemble"); - operator3D.setElimBEVectors(solution, rhs); - rhs.importValuesFromField(b); solution.importValuesFromField(x0); - rhs.assemble(); - solution.assemble(); - - solution.syncElimBErhs(rhs); CALI_MARK_END("LaplaceHypre3d_solve:vectorAssemble"); CALI_MARK_BEGIN("LaplaceHypre3d_solve:solve"); // Invoke solver { - Timer timer("hypresolve"); + const Timer timer("hypresolve"); linearSystem.solve(); } @@ -280,7 +288,7 @@ void LaplaceHypre3d::updateMatrix3D() { const Field3D dc_dx = issetC ? DDX(C2) : Field3D(); const Field3D dc_dy = issetC ? DDY(C2) : Field3D(); const Field3D dc_dz = issetC ? DDZ(C2) : Field3D(); - const auto dJ_dy = DDY(coords->J / coords->g_22); + const auto dJ_dy = DDY(coords->J() / coords->g_22()); // Set up the matrix for the internal points on the grid. // Boundary conditions were set in the constructor. @@ -289,18 +297,18 @@ void LaplaceHypre3d::updateMatrix3D() { // avoid confusing it with the x-index. // Calculate coefficients for the terms in the differential operator - BoutReal C_df_dx = coords->G1[l]; - BoutReal C_df_dz = coords->G3[l]; + BoutReal C_df_dx = coords->G1()[l]; + BoutReal C_df_dz = coords->G3()[l]; if (issetD) { C_df_dx *= D[l]; C_df_dz *= D[l]; } if (issetC) { - C_df_dx += (coords->g11[l] * dc_dx[l] + coords->g12[l] * dc_dy[l] - + coords->g13[l] * dc_dz[l]) + C_df_dx += (coords->g11()[l] * dc_dx[l] + coords->g12()[l] * dc_dy[l] + + coords->g13()[l] * dc_dz[l]) / C1[l]; - C_df_dz += (coords->g13[l] * dc_dx[l] + coords->g23[l] * dc_dy[l] - + coords->g33[l] * dc_dz[l]) + C_df_dz += (coords->g13()[l] * dc_dx[l] + coords->g23()[l] * dc_dy[l] + + coords->g33()[l] * dc_dz[l]) / C1[l]; } if (issetE) { @@ -308,32 +316,32 @@ void LaplaceHypre3d::updateMatrix3D() { C_df_dz += Ez[l]; } - BoutReal C_d2f_dx2 = coords->g11[l]; - BoutReal C_d2f_dy2 = (coords->g22[l] - 1.0 / coords->g_22[l]); - BoutReal C_d2f_dz2 = coords->g33[l]; + BoutReal C_d2f_dx2 = coords->g11()[l]; + BoutReal C_d2f_dy2 = (coords->g22()[l] - (1.0 / coords->g_22()[l])); + BoutReal C_d2f_dz2 = coords->g33()[l]; if (issetD) { C_d2f_dx2 *= D[l]; C_d2f_dy2 *= D[l]; C_d2f_dz2 *= D[l]; } - BoutReal C_d2f_dxdz = 2 * coords->g13[l]; + BoutReal C_d2f_dxdz = 2 * coords->g13()[l]; if (issetD) { C_d2f_dxdz *= D[l]; } // Adjust the coefficients to include finite-difference factors if (nonuniform) { - C_df_dx += C_d2f_dx2 * coords->d1_dx[l]; + C_df_dx += C_d2f_dx2 * coords->d1_dx()[l]; } - C_df_dx /= 2 * coords->dx[l]; - C_df_dz /= 2 * coords->dz[l]; + C_df_dx /= 2 * coords->dx()[l]; + C_df_dz /= 2 * coords->dz()[l]; - C_d2f_dx2 /= SQ(coords->dx[l]); - C_d2f_dy2 /= SQ(coords->dy[l]); - C_d2f_dz2 /= SQ(coords->dz[l]); + C_d2f_dx2 /= SQ(coords->dx()[l]); + C_d2f_dy2 /= SQ(coords->dy()[l]); + C_d2f_dz2 /= SQ(coords->dz()[l]); - C_d2f_dxdz /= 4 * coords->dx[l] * coords->dz[l]; + C_d2f_dxdz /= 4 * coords->dx()[l] * coords->dz()[l]; operator3D(l, l) = -2 * (C_d2f_dx2 + C_d2f_dy2 + C_d2f_dz2) + A[l]; operator3D(l, l.xp()) = C_df_dx + C_d2f_dx2; @@ -364,24 +372,24 @@ void LaplaceHypre3d::updateMatrix3D() { // Must add these (rather than assign) so that elements used in // interpolation don't overwrite each other. BOUT_FOR_SERIAL(l, indexer->getRegionNobndry()) { - BoutReal C_df_dy = (coords->G2[l] - dJ_dy[l] / coords->J[l]); + BoutReal C_df_dy = (coords->G2()[l] - (dJ_dy[l] / coords->J()[l])); if (issetD) { C_df_dy *= D[l]; } if (issetC) { - C_df_dy += - (coords->g12[l] * dc_dx[l] + (coords->g22[l] - 1. / coords->g_22[l]) * dc_dy[l] - + coords->g23[l] * dc_dz[l]) - / C1[l]; + C_df_dy += (coords->g12()[l] * dc_dx[l] + + (coords->g22()[l] - 1. / coords->g_22()[l]) * dc_dy[l] + + coords->g23()[l] * dc_dz[l]) + / C1[l]; } - BoutReal C_d2f_dy2 = (coords->g22[l] - 1.0 / coords->g_22[l]); + BoutReal C_d2f_dy2 = (coords->g22()[l] - (1.0 / coords->g_22()[l])); if (issetD) { C_d2f_dy2 *= D[l]; } - BoutReal C_d2f_dxdy = 2 * coords->g12[l]; - BoutReal C_d2f_dydz = 2 * coords->g23[l]; + BoutReal C_d2f_dxdy = 2 * coords->g12()[l]; + BoutReal C_d2f_dydz = 2 * coords->g23()[l]; if (issetD) { C_d2f_dxdy *= D[l]; C_d2f_dydz *= D[l]; @@ -389,14 +397,14 @@ void LaplaceHypre3d::updateMatrix3D() { // Adjust the coefficients to include finite-difference factors if (nonuniform) { - C_df_dy += C_d2f_dy2 * coords->d1_dy[l]; + C_df_dy += C_d2f_dy2 * coords->d1_dy()[l]; } - C_df_dy /= 2 * coords->dy[l]; - C_d2f_dy2 /= SQ(coords->dy[l]); - C_d2f_dxdy /= 4 * coords->dx[l]; // NOTE: This value is not completed here. It needs - // to be divide by dx(i +/- 1, j, k) when using to - // set a matrix element - C_d2f_dydz /= 4 * coords->dy[l] * coords->dz[l]; + C_df_dy /= 2 * coords->dy()[l]; + C_d2f_dy2 /= SQ(coords->dy()[l]); + C_d2f_dxdy /= 4 * coords->dx()[l]; // NOTE: This value is not completed here. It needs + // to be divide by dx(i +/- 1, j, k) when using to + // set a matrix element + C_d2f_dydz /= 4 * coords->dy()[l] * coords->dz()[l]; // The values stored in the y-boundary are already interpolated // up/down, so we don't want the matrix to do any such @@ -406,16 +414,16 @@ void LaplaceHypre3d::updateMatrix3D() { operator3D.yup(yup)(l, l.yp()) += C_df_dy + C_d2f_dy2; operator3D.ydown(ydown)(l, l.ym()) += -C_df_dy + C_d2f_dy2; - operator3D.yup(yup)(l, l.xp().yp()) += C_d2f_dxdy / coords->dy[l.xp()]; - operator3D.ydown(ydown)(l, l.xp().ym()) += -C_d2f_dxdy / coords->dy[l.xp()]; - operator3D.yup(yup)(l, l.xm().yp()) += -C_d2f_dxdy / coords->dy[l.xm()]; - operator3D.ydown(ydown)(l, l.xm().ym()) += C_d2f_dxdy / coords->dy[l.xm()]; + operator3D.yup(yup)(l, l.xp().yp()) += C_d2f_dxdy / coords->dy()[l.xp()]; + operator3D.ydown(ydown)(l, l.xp().ym()) += -C_d2f_dxdy / coords->dy()[l.xp()]; + operator3D.yup(yup)(l, l.xm().yp()) += -C_d2f_dxdy / coords->dy()[l.xm()]; + operator3D.ydown(ydown)(l, l.xm().ym()) += C_d2f_dxdy / coords->dy()[l.xm()]; operator3D.yup(yup)(l, l.yp().zp()) += C_d2f_dydz; operator3D.yup(yup)(l, l.yp().zm()) += -C_d2f_dydz; operator3D.ydown(ydown)(l, l.ym().zp()) += -C_d2f_dydz; operator3D.ydown(ydown)(l, l.ym().zm()) += C_d2f_dydz; } - operator3D.setElimBE(); + operator3D.setUseBoundaryElimination(); operator3D.assemble(); if (print_matrix) { @@ -445,7 +453,7 @@ OperatorStencil LaplaceHypre3d::getStencil(Mesh* localmesh, : p.k}; }); - OffsetInd3D zero; + const OffsetInd3D zero; // Add interior cells const std::vector interpolatedUpElements = { @@ -549,7 +557,7 @@ void LaplaceHypre3d::outputVars(Options& output_options, const std::string& time_dimension) const { BoutReal mean_iterations = 0.0; BoutReal mean_amg_iterations = 0.0; - BoutReal rel_res_norm = linearSystem.getFinalRelResNorm(); + const BoutReal rel_res_norm = linearSystem.getFinalRelResNorm(); if (n_solves > 0) { // Calculate average diff --git a/src/invert/laplace/impls/hypre3d/hypre3d_laplace.hxx b/src/invert/laplace/impls/hypre3d/hypre3d_laplace.hxx index d58ef6f688..a5ecaab45e 100644 --- a/src/invert/laplace/impls/hypre3d/hypre3d_laplace.hxx +++ b/src/invert/laplace/impls/hypre3d/hypre3d_laplace.hxx @@ -145,7 +145,7 @@ public: } // Return a reference to the matrix objects representing the Laplace - // operator. These will be (re)construct if necessary. + // operator. These will be (re)constructed if necessary. bout::HypreMatrix& getMatrix3D(); IndexerPtr getIndexer() { return indexer; } diff --git a/src/invert/laplace/impls/naulin/naulin_laplace.cxx b/src/invert/laplace/impls/naulin/naulin_laplace.cxx index 3faba2f0ea..c5c2a19915 100644 --- a/src/invert/laplace/impls/naulin/naulin_laplace.cxx +++ b/src/invert/laplace/impls/naulin/naulin_laplace.cxx @@ -189,8 +189,8 @@ Field3D LaplaceNaulin::solve(const Field3D& rhs, const Field3D& x0) { Field3D ddx_x = DDX(x_in, location, "C2"); Field3D ddz_x = DDZ(x_in, location, "FFT"); return rhsOverD - - (coords->g11 * coef_x_AC * ddx_x + coords->g33 * coef_z * ddz_x - + coords->g13 * (coef_x_AC * ddz_x + coef_z * ddx_x)) + - (coords->g11() * coef_x_AC * ddx_x + coords->g33() * coef_z * ddz_x + + coords->g13() * (coef_x_AC * ddz_x + coef_z * ddx_x)) - AOverD_AC * x_in; }; diff --git a/src/invert/laplace/impls/naulin/naulin_laplace.hxx b/src/invert/laplace/impls/naulin/naulin_laplace.hxx index edcc562c99..f857ca8356 100644 --- a/src/invert/laplace/impls/naulin/naulin_laplace.hxx +++ b/src/invert/laplace/impls/naulin/naulin_laplace.hxx @@ -5,8 +5,8 @@ /// \f{eqnarray} /// \nabla^2(\phi[i+1]) /// + 1/DC(C_1 D)\nabla_\perp(DC(C_2))\nabla_\perp(\phi[i+1]) -/// + DC(A/D)\phi[i+1] \\ -/// = rhs(\phi[i]) +/// + DC(A/D)\phi[i+1] +/// \\ = rhs(\phi[i]) /// + 1/DC(C_1 D)\nabla_\perp(DC(C_2))\nabla_\perp(\phi[i]) /// + DC(A/D)\phi[i] /// \f} @@ -28,17 +28,17 @@ /// In an orthogonal system, we have that: /// /// \f{eqnarray}{ -/// \Omega^D &=& \nabla\cdot(n\nabla_\perp \phi)\ \ -/// &=& n \nabla_\perp^2 \phi + \nabla n\cdot\nabla_\perp \phi\\ -/// &=& n \Omega + \nabla n\cdot\nabla_\perp \phi\\ -/// &=& n \Omega + \nabla_\perp n\cdot\nabla_\perp \phi +/// \Omega^D &=& \nabla\cdot(n\nabla_\perp \phi) +/// \\ &=&n \nabla_\perp^2 \phi + \nabla n\cdot\nabla_\perp \phi +/// \\ &=&n \Omega + \nabla n\cdot\nabla_\perp \phi +/// \\ &=&n \Omega + \nabla_\perp n\cdot\nabla_\perp \phi /// \f} /// /// Rearranging gives /// /// \f{eqnarray}{ -/// \Omega &=& \frac{\Omega^D}{n} - \nabla_\perp \ln(n)\cdot\nabla_\perp \phi\ \ -/// \nabla_\perp^2 \phi +/// \Omega &=& \frac{\Omega^D}{n} - \nabla_\perp \ln(n)\cdot\nabla_\perp \phi +/// \\ \nabla_\perp^2 \phi /// &=& \frac{\Omega^D}{n} - \nabla_\perp \ln(n)\cdot\nabla_\perp \phi /// \f} /// diff --git a/src/invert/laplace/impls/petsc/petsc_laplace.cxx b/src/invert/laplace/impls/petsc/petsc_laplace.cxx index 33481f9bde..9e7fb5d89d 100644 --- a/src/invert/laplace/impls/petsc/petsc_laplace.cxx +++ b/src/invert/laplace/impls/petsc/petsc_laplace.cxx @@ -336,7 +336,7 @@ FieldPerp LaplacePetsc::solve(const FieldPerp& b, const FieldPerp& x0) { } if (!isOuterBoundaryFlagSet(INVERT_RHS)) { BOUT_FOR_SERIAL(index, indexer->getRegionOuterX()) { - rhs(index) = isInnerBoundaryFlagSet(INVERT_SET) ? x0[index] : 0.0; + rhs(index) = isOuterBoundaryFlagSet(INVERT_SET) ? x0[index] : 0.0; } } @@ -403,16 +403,16 @@ FieldPerp LaplacePetsc::solve(const FieldPerp& b, const FieldPerp& x0) { LaplacePetsc::CoeffsA LaplacePetsc::Coeffs(Ind3D i) { const auto x = i.x(); - BoutReal coef1 = coords->g11[i]; // X 2nd derivative coefficient - BoutReal coef2 = coords->g33[i]; // Z 2nd derivative coefficient - BoutReal coef3 = 2. * coords->g13[i]; // X-Z mixed derivative coefficient + BoutReal coef1 = coords->g11()[i]; // X 2nd derivative coefficient + BoutReal coef2 = coords->g33()[i]; // Z 2nd derivative coefficient + BoutReal coef3 = 2. * coords->g13()[i]; // X-Z mixed derivative coefficient BoutReal coef4 = 0.0; BoutReal coef5 = 0.0; // If global flag all_terms are set (true by default) if (all_terms) { - coef4 = coords->G1[i]; // X 1st derivative - coef5 = coords->G3[i]; // Z 1st derivative + coef4 = coords->G1()[i]; // X 1st derivative + coef5 = coords->G3()[i]; // Z 1st derivative ASSERT3(std::isfinite(coef4)); ASSERT3(std::isfinite(coef5)); @@ -421,14 +421,15 @@ LaplacePetsc::CoeffsA LaplacePetsc::Coeffs(Ind3D i) { if (nonuniform) { // non-uniform mesh correction if ((x != 0) && (x != (localmesh->LocalNx - 1))) { - coef4 -= 0.5 * ((coords->dx[i.xp()] - coords->dx[i.xm()]) / SQ(coords->dx[i])) + coef4 -= 0.5 * ((coords->dx()[i.xp()] - coords->dx()[i.xm()]) / SQ(coords->dx()[i])) * coef1; // BOUT-06 term } } if (localmesh->IncIntShear) { // d2dz2 term - coef2 += coords->g11[i] * coords->IntShiftTorsion[i] * coords->IntShiftTorsion[i]; + coef2 += + coords->g11()[i] * coords->IntShiftTorsion()[i] * coords->IntShiftTorsion()[i]; // Mixed derivative coef3 = 0.0; // This cancels out } @@ -450,19 +451,19 @@ LaplacePetsc::CoeffsA LaplacePetsc::Coeffs(Ind3D i) { if (fourth_order) { // Fourth order discretization of C in x ddx_C = (-C2[i.xpp()] + (8. * C2[i.xp()]) - (8. * C2[i.xm()]) + C2[i.xmm()]) - / (12. * coords->dx[i] * (C1[i])); + / (12. * coords->dx()[i] * (C1[i])); // Fourth order discretization of C in z ddz_C = (-C2[i.zpp()] + (8. * C2[i.zp()]) - (8. * C2[i.zm()]) + C2[i.zmm()]) - / (12. * coords->dz[i] * (C1[i])); + / (12. * coords->dz()[i] * (C1[i])); } else { // Second order discretization of C in x - ddx_C = (C2[i.xp()] - C2[i.xm()]) / (2. * coords->dx[i] * (C1[i])); + ddx_C = (C2[i.xp()] - C2[i.xm()]) / (2. * coords->dx()[i] * (C1[i])); // Second order discretization of C in z - ddz_C = (C2[i.zp()] - C2[i.zm()]) / (2. * coords->dz[i] * (C1[i])); + ddz_C = (C2[i.zp()] - C2[i.zm()]) / (2. * coords->dz()[i] * (C1[i])); } - coef4 += (coords->g11[i] * ddx_C) + (coords->g13[i] * ddz_C); - coef5 += (coords->g13[i] * ddx_C) + (coords->g33[i] * ddz_C); + coef4 += (coords->g11()[i] * ddx_C) + (coords->g13()[i] * ddz_C); + coef5 += (coords->g13()[i] * ddx_C) + (coords->g33()[i] * ddz_C); } } @@ -487,11 +488,11 @@ void LaplacePetsc::setSecondOrderMatrix(int y, bool inner_X_neumann, bool outer_X_neumann) { // Set the boundaries if (inner_X_neumann) { - const auto dx = sliceXZ(coords->dx, y); - const auto g11 = sliceXZ(coords->g11, y); + const auto dx = sliceXZ(coords->dx(), y); + const auto g_11 = sliceXZ(coords->g_11(), y); BOUT_FOR_SERIAL(i, indexer->getRegionInnerX()) { - const auto factor = 1. / dx[i] / std::sqrt(g11[i]); + const auto factor = 1. / dx[i] / std::sqrt(g_11[i]); operator2D(i, i) = -factor; operator2D(i, i.xp()) = factor; } @@ -502,11 +503,11 @@ void LaplacePetsc::setSecondOrderMatrix(int y, bool inner_X_neumann, } } if (outer_X_neumann) { - const auto dx = sliceXZ(coords->dx, y); - const auto g11 = sliceXZ(coords->g11, y); + const auto dx = sliceXZ(coords->dx(), y); + const auto g_11 = sliceXZ(coords->g_11(), y); BOUT_FOR_SERIAL(i, indexer->getRegionOuterX()) { - const auto factor = 1. / dx[i] / std::sqrt(g11[i]); + const auto factor = 1. / dx[i] / std::sqrt(g_11[i]); operator2D(i, i) = factor; operator2D(i, i.xm()) = -factor; } @@ -535,9 +536,9 @@ void LaplacePetsc::setSecondOrderMatrix(int y, bool inner_X_neumann, ASSERT3(std::isfinite(A4)); ASSERT3(std::isfinite(A5)); - const BoutReal dx = coords->dx[i]; + const BoutReal dx = coords->dx()[i]; const BoutReal dx2 = SQ(dx); - const BoutReal dz = coords->dz[i]; + const BoutReal dz = coords->dz()[i]; const BoutReal dz2 = SQ(dz); const BoutReal dxdz = dx * dz; operator2D(l, l) = A0 - (2.0 * ((A1 / dx2) + (A2 / dz2))); @@ -557,11 +558,11 @@ void LaplacePetsc::setFourthOrderMatrix(int y, bool inner_X_neumann, // Set boundaries if (inner_X_neumann) { - const auto dx = sliceXZ(coords->dx, y); - const auto g11 = sliceXZ(coords->g11, y); + const auto dx = sliceXZ(coords->dx(), y); + const auto g_11 = sliceXZ(coords->g_11(), y); BOUT_FOR_SERIAL(i, indexer->getRegionInnerX()) { - const auto factor = 1. / dx[i] / std::sqrt(g11[i]); + const auto factor = 1. / dx[i] / std::sqrt(g_11[i]); operator2D(i, i) = (-25.0 / 12.0) * factor; operator2D(i, i.xp(1)) = 4.0 * factor; operator2D(i, i.xp(2)) = -3.0 * factor; @@ -579,11 +580,11 @@ void LaplacePetsc::setFourthOrderMatrix(int y, bool inner_X_neumann, } if (outer_X_neumann) { - const auto dx = sliceXZ(coords->dx, y); - const auto g11 = sliceXZ(coords->g11, y); + const auto dx = sliceXZ(coords->dx(), y); + const auto g_11 = sliceXZ(coords->g_11(), y); BOUT_FOR_SERIAL(i, indexer->getRegionOuterX()) { - const auto factor = 1. / dx[i] / std::sqrt(g11[i]); + const auto factor = 1. / dx[i] / std::sqrt(g_11[i]); operator2D(i, i) = (25.0 / 12.0) * factor; operator2D(i, i.xm(1)) = -4.0 * factor; operator2D(i, i.xm(2)) = 3.0 * factor; @@ -618,9 +619,9 @@ void LaplacePetsc::setFourthOrderMatrix(int y, bool inner_X_neumann, ASSERT3(std::isfinite(A4)); ASSERT3(std::isfinite(A5)); - const BoutReal dx = coords->dx[i]; + const BoutReal dx = coords->dx()[i]; const BoutReal dx2 = SQ(dx); - const BoutReal dz = coords->dz[i]; + const BoutReal dz = coords->dz()[i]; const BoutReal dz2 = SQ(dz); const BoutReal dxdz = dx * dz; diff --git a/src/invert/laplace/impls/petsc3damg/petsc3damg.cxx b/src/invert/laplace/impls/petsc3damg/petsc3damg.cxx index 9966ad654d..d254ea4a68 100644 --- a/src/invert/laplace/impls/petsc3damg/petsc3damg.cxx +++ b/src/invert/laplace/impls/petsc3damg/petsc3damg.cxx @@ -24,14 +24,15 @@ * along with BOUT++. If not, see . * **************************************************************************/ -#include "bout/bout_types.hxx" -#include "bout/build_defines.hxx" + +#include #if BOUT_HAS_PETSC #include "petsc3damg.hxx" #include +#include #include #include #include @@ -40,6 +41,8 @@ #include #include +#include + using bout::utils::flagSet; #ifdef PETSC_HAVE_HYPRE @@ -122,7 +125,7 @@ LaplacePetsc3dAmg::LaplacePetsc3dAmg(Options* opt, const CELL_LOC loc, Mesh* mes const bool inner_X_neumann = isInnerBoundaryFlagSet(INVERT_AC_GRAD); if (inner_X_neumann) { // This is a BinaryExpr that is only evaluated when needed - const auto inner_X_BC = -1. / coords->dx / sqrt(coords->g_11); + const auto inner_X_BC = -1. / coords->dx() / sqrt(coords->g_11()); BOUT_FOR_SERIAL(i, indexer->getRegionInnerX()) { const BoutReal bc = inner_X_BC[i]; operator3D(i, i) = bc; @@ -137,7 +140,7 @@ LaplacePetsc3dAmg::LaplacePetsc3dAmg(Options* opt, const CELL_LOC loc, Mesh* mes const bool outer_X_neumann = isOuterBoundaryFlagSet(INVERT_AC_GRAD); if (outer_X_neumann) { - const auto outer_X_BC = 1. / coords->dx / sqrt(coords->g_11); + const auto outer_X_BC = 1. / coords->dx() / sqrt(coords->g_11()); BOUT_FOR_SERIAL(i, indexer->getRegionOuterX()) { const BoutReal bc = outer_X_BC[i]; operator3D(i, i) = bc; @@ -152,7 +155,7 @@ LaplacePetsc3dAmg::LaplacePetsc3dAmg(Options* opt, const CELL_LOC loc, Mesh* mes const bool lower_Y_neumann = flagSet(lower_boundary_flags, INVERT_AC_GRAD); if (lower_Y_neumann) { - const auto lower_Y_BC = -1. / coords->dy / sqrt(coords->g_22); + const auto lower_Y_BC = -1. / coords->dy() / sqrt(coords->g_22()); BOUT_FOR_SERIAL(i, indexer->getRegionLowerY()) { const BoutReal bc = lower_Y_BC[i]; operator3D(i, i) = bc; @@ -167,7 +170,7 @@ LaplacePetsc3dAmg::LaplacePetsc3dAmg(Options* opt, const CELL_LOC loc, Mesh* mes const bool upper_Y_neumann = flagSet(upper_boundary_flags, INVERT_AC_GRAD); if (upper_Y_neumann) { - const auto upper_Y_BC = 1. / coords->dy / sqrt(coords->g_22); + const auto upper_Y_BC = 1. / coords->dy() / sqrt(coords->g_22()); BOUT_FOR_SERIAL(i, indexer->getRegionUpperY()) { const BoutReal bc = upper_Y_BC[i]; operator3D(i, i) = bc; @@ -299,7 +302,7 @@ void LaplacePetsc3dAmg::updateMatrix3D() { const Field3D dc_dx = issetC ? DDX(C2) : Field3D(); const Field3D dc_dy = issetC ? DDY(C2) : Field3D(); const Field3D dc_dz = issetC ? DDZ(C2) : Field3D(); - const auto dJ_dy = DDY(Coordinates::FieldMetric{coords->J / coords->g_22}); + const auto dJ_dy = DDY(Coordinates::FieldMetric{coords->J() / coords->g_22()}); // Set up the matrix for the internal points on the grid. // Boundary conditions were set in the constructor. @@ -308,18 +311,18 @@ void LaplacePetsc3dAmg::updateMatrix3D() { // avoid confusing it with the x-index. // Calculate coefficients for the terms in the differential operator - BoutReal C_df_dx = coords->G1[l]; - BoutReal C_df_dz = coords->G3[l]; + BoutReal C_df_dx = coords->G1()[l]; + BoutReal C_df_dz = coords->G3()[l]; if (issetD) { C_df_dx *= D[l]; C_df_dz *= D[l]; } if (issetC) { - C_df_dx += (coords->g11[l] * dc_dx[l] + coords->g12[l] * dc_dy[l] - + coords->g13[l] * dc_dz[l]) + C_df_dx += (coords->g11()[l] * dc_dx[l] + coords->g12()[l] * dc_dy[l] + + coords->g13()[l] * dc_dz[l]) / C1[l]; - C_df_dz += (coords->g13[l] * dc_dx[l] + coords->g23[l] * dc_dy[l] - + coords->g33[l] * dc_dz[l]) + C_df_dz += (coords->g13()[l] * dc_dx[l] + coords->g23()[l] * dc_dy[l] + + coords->g33()[l] * dc_dz[l]) / C1[l]; } if (issetE) { @@ -327,32 +330,32 @@ void LaplacePetsc3dAmg::updateMatrix3D() { C_df_dz += Ez[l]; } - BoutReal C_d2f_dx2 = coords->g11[l]; - BoutReal C_d2f_dy2 = (coords->g22[l] - 1.0 / coords->g_22[l]); - BoutReal C_d2f_dz2 = coords->g33[l]; + BoutReal C_d2f_dx2 = coords->g11()[l]; + BoutReal C_d2f_dy2 = (coords->g22()[l] - (1.0 / coords->g_22()[l])); + BoutReal C_d2f_dz2 = coords->g33()[l]; if (issetD) { C_d2f_dx2 *= D[l]; C_d2f_dy2 *= D[l]; C_d2f_dz2 *= D[l]; } - BoutReal C_d2f_dxdz = 2 * coords->g13[l]; + BoutReal C_d2f_dxdz = 2 * coords->g13()[l]; if (issetD) { C_d2f_dxdz *= D[l]; } // Adjust the coefficients to include finite-difference factors if (nonuniform) { - C_df_dx += C_d2f_dx2 * coords->d1_dx[l]; + C_df_dx += C_d2f_dx2 * coords->d1_dx()[l]; } - C_df_dx /= 2 * coords->dx[l]; - C_df_dz /= 2 * coords->dz[l]; + C_df_dx /= 2 * coords->dx()[l]; + C_df_dz /= 2 * coords->dz()[l]; - C_d2f_dx2 /= SQ(coords->dx[l]); - C_d2f_dy2 /= SQ(coords->dy[l]); - C_d2f_dz2 /= SQ(coords->dz[l]); + C_d2f_dx2 /= SQ(coords->dx()[l]); + C_d2f_dy2 /= SQ(coords->dy()[l]); + C_d2f_dz2 /= SQ(coords->dz()[l]); - C_d2f_dxdz /= 4 * coords->dx[l] * coords->dz[l]; + C_d2f_dxdz /= 4 * coords->dx()[l] * coords->dz()[l]; operator3D(l, l) = -2 * (C_d2f_dx2 + C_d2f_dy2 + C_d2f_dz2) + A[l]; operator3D(l, l.xp()) = C_df_dx + C_d2f_dx2; @@ -384,24 +387,24 @@ void LaplacePetsc3dAmg::updateMatrix3D() { // Must add these (rather than assign) so that elements used in // interpolation don't overwrite each other. BOUT_FOR_SERIAL(l, indexer->getRegionNobndry()) { - BoutReal C_df_dy = coords->G2[l] - (dJ_dy[l] / coords->J[l]); + BoutReal C_df_dy = coords->G2()[l] - (dJ_dy[l] / coords->J()[l]); if (issetD) { C_df_dy *= D[l]; } if (issetC) { - C_df_dy += - (coords->g12[l] * dc_dx[l] + (coords->g22[l] - 1. / coords->g_22[l]) * dc_dy[l] - + coords->g23[l] * dc_dz[l]) - / C1[l]; + C_df_dy += (coords->g12()[l] * dc_dx[l] + + (coords->g22()[l] - 1. / coords->g_22()[l]) * dc_dy[l] + + coords->g23()[l] * dc_dz[l]) + / C1[l]; } - BoutReal C_d2f_dy2 = coords->g22[l] - (1.0 / coords->g_22[l]); + BoutReal C_d2f_dy2 = coords->g22()[l] - (1.0 / coords->g_22()[l]); if (issetD) { C_d2f_dy2 *= D[l]; } - BoutReal C_d2f_dxdy = 2 * coords->g12[l]; - BoutReal C_d2f_dydz = 2 * coords->g23[l]; + BoutReal C_d2f_dxdy = 2 * coords->g12()[l]; + BoutReal C_d2f_dydz = 2 * coords->g23()[l]; if (issetD) { C_d2f_dxdy *= D[l]; C_d2f_dydz *= D[l]; @@ -409,15 +412,15 @@ void LaplacePetsc3dAmg::updateMatrix3D() { // Adjust the coefficients to include finite-difference factors if (nonuniform) { - C_df_dy += C_d2f_dy2 * coords->d1_dy[l]; + C_df_dy += C_d2f_dy2 * coords->d1_dy()[l]; } - C_df_dy /= 2 * coords->dy[l]; - C_d2f_dy2 /= SQ(coords->dy[l]); + C_df_dy /= 2 * coords->dy()[l]; + C_d2f_dy2 /= SQ(coords->dy()[l]); C_d2f_dxdy /= - 4 * coords->dx[l]; // NOTE: This value is not completed here. It needs to - // be divide by dx(i +/- 1, j, k) when using to set a - // matrix element - C_d2f_dydz /= 4 * coords->dy[l] * coords->dz[l]; + 4 * coords->dx()[l]; // NOTE: This value is not completed here. It needs to + // be divide by dx(i +/- 1, j, k) when using to set a + // matrix element + C_d2f_dydz /= 4 * coords->dy()[l] * coords->dz()[l]; // The values stored in the y-boundary are already interpolated // up/down, so we don't want the matrix to do any such @@ -427,10 +430,10 @@ void LaplacePetsc3dAmg::updateMatrix3D() { operator3D.yup(yup)(l, l.yp()) += C_df_dy + C_d2f_dy2; operator3D.ydown(ydown)(l, l.ym()) += -C_df_dy + C_d2f_dy2; - operator3D.yup(yup)(l, l.xp().yp()) += C_d2f_dxdy / coords->dy[l.xp()]; - operator3D.ydown(ydown)(l, l.xp().ym()) += -C_d2f_dxdy / coords->dy[l.xp()]; - operator3D.yup(yup)(l, l.xm().yp()) += -C_d2f_dxdy / coords->dy[l.xm()]; - operator3D.ydown(ydown)(l, l.xm().ym()) += C_d2f_dxdy / coords->dy[l.xm()]; + operator3D.yup(yup)(l, l.xp().yp()) += C_d2f_dxdy / coords->dy()[l.xp()]; + operator3D.ydown(ydown)(l, l.xp().ym()) += -C_d2f_dxdy / coords->dy()[l.xp()]; + operator3D.yup(yup)(l, l.xm().yp()) += -C_d2f_dxdy / coords->dy()[l.xm()]; + operator3D.ydown(ydown)(l, l.xm().ym()) += C_d2f_dxdy / coords->dy()[l.xm()]; operator3D.yup(yup)(l, l.yp().zp()) += C_d2f_dydz; operator3D.yup(yup)(l, l.yp().zm()) += -C_d2f_dydz; operator3D.ydown(ydown)(l, l.ym().zp()) += -C_d2f_dydz; diff --git a/src/invert/laplace/invert_laplace.cxx b/src/invert/laplace/invert_laplace.cxx index 9af6fd20f9..0ec5f471da 100644 --- a/src/invert/laplace/invert_laplace.cxx +++ b/src/invert/laplace/invert_laplace.cxx @@ -104,7 +104,7 @@ Laplacian::Laplacian(Options* options, const CELL_LOC loc, Mesh* mesh_in, nonuniform = (*options)["nonuniform"] .doc("Use non-uniform grid corrections? Default is the mesh setting.") - .withDefault(coords->non_uniform); + .withDefault(coords->non_uniform()); all_terms = (*options)["all_terms"].doc("Include first derivative terms?").withDefault(true); diff --git a/src/invert/laplacexy/impls/hypre/laplacexy-hypre.cxx b/src/invert/laplacexy/impls/hypre/laplacexy-hypre.cxx index 61632a332d..fbba16673c 100644 --- a/src/invert/laplacexy/impls/hypre/laplacexy-hypre.cxx +++ b/src/invert/laplacexy/impls/hypre/laplacexy-hypre.cxx @@ -113,20 +113,20 @@ void LaplaceXY2Hypre::setCoefs(const Field2D& A, const Field2D& B) { // XX component // Metrics on x+1/2 boundary - BoutReal J = 0.5 * (coords->J[index] + coords->J[ind_xp]); - BoutReal g11 = 0.5 * (coords->g11[index] + coords->g11[ind_xp]); - BoutReal dx = 0.5 * (coords->dx[index] + coords->dx[ind_xp]); + BoutReal J = 0.5 * (coords->J()[index] + coords->J()[ind_xp]); + BoutReal g11 = 0.5 * (coords->g11()[index] + coords->g11()[ind_xp]); + BoutReal dx = 0.5 * (coords->dx()[index] + coords->dx()[ind_xp]); BoutReal Acoef = 0.5 * (A[index] + A[ind_xp]); - BoutReal xp = Acoef * J * g11 / (coords->J[index] * dx * coords->dx[index]); + BoutReal xp = Acoef * J * g11 / (coords->J()[index] * dx * coords->dx()[index]); // Metrics on x-1/2 boundary - J = 0.5 * (coords->J[index] + coords->J[ind_xm]); - g11 = 0.5 * (coords->g11[index] + coords->g11[ind_xm]); - dx = 0.5 * (coords->dx[index] + coords->dx[ind_xm]); + J = 0.5 * (coords->J()[index] + coords->J()[ind_xm]); + g11 = 0.5 * (coords->g11()[index] + coords->g11()[ind_xm]); + dx = 0.5 * (coords->dx()[index] + coords->dx()[ind_xm]); Acoef = 0.5 * (A[index] + A[ind_xm]); - BoutReal xm = Acoef * J * g11 / (coords->J[index] * dx * coords->dx[index]); + BoutReal xm = Acoef * J * g11 / (coords->J()[index] * dx * coords->dx()[index]); BoutReal c = B[index] - xp - xm; // Central coefficient @@ -139,27 +139,27 @@ void LaplaceXY2Hypre::setCoefs(const Field2D& A, const Field2D& B) { // YY component // Metrics at y+1/2 - J = 0.5 * (coords->J[index] + coords->J[ind_yp]); - BoutReal g_22 = 0.5 * (coords->g_22[index] + coords->g_22[ind_yp]); - BoutReal g23 = 0.5 * (coords->g23[index] + coords->g23[ind_yp]); - BoutReal g_23 = 0.5 * (coords->g_23[index] + coords->g_23[ind_yp]); - BoutReal dy = 0.5 * (coords->dy[index] + coords->dy[ind_yp]); + J = 0.5 * (coords->J()[index] + coords->J()[ind_yp]); + BoutReal g_22 = 0.5 * (coords->g_22()[index] + coords->g_22()[ind_yp]); + BoutReal g23 = 0.5 * (coords->g23()[index] + coords->g23()[ind_yp]); + BoutReal g_23 = 0.5 * (coords->g_23()[index] + coords->g_23()[ind_yp]); + BoutReal dy = 0.5 * (coords->dy()[index] + coords->dy()[ind_yp]); Acoef = 0.5 * (A[ind_yp] + A[index]); - BoutReal yp = - -Acoef * J * g23 * g_23 / (g_22 * coords->J[index] * dy * coords->dy[index]); + BoutReal yp = -Acoef * J * g23 * g_23 + / (g_22 * coords->J()[index] * dy * coords->dy()[index]); c -= yp; // Metrics at y-1/2 - J = 0.5 * (coords->J[index] + coords->J[ind_ym]); - g_22 = 0.5 * (coords->g_22[index] + coords->g_22[ind_ym]); - g23 = 0.5 * (coords->g23[index] + coords->g23[ind_ym]); - g_23 = 0.5 * (coords->g_23[index] + coords->g_23[ind_ym]); - dy = 0.5 * (coords->dy[index] + coords->dy[ind_ym]); + J = 0.5 * (coords->J()[index] + coords->J()[ind_ym]); + g_22 = 0.5 * (coords->g_22()[index] + coords->g_22()[ind_ym]); + g23 = 0.5 * (coords->g23()[index] + coords->g23()[ind_ym]); + g_23 = 0.5 * (coords->g_23()[index] + coords->g_23()[ind_ym]); + dy = 0.5 * (coords->dy()[index] + coords->dy()[ind_ym]); Acoef = 0.5 * (A[ind_ym] + A[index]); - BoutReal ym = - -Acoef * J * g23 * g_23 / (g_22 * coords->J[index] * dy * coords->dy[index]); + BoutReal ym = -Acoef * J * g23 * g_23 + / (g_22 * coords->J()[index] * dy * coords->dy()[index]); c -= ym; M(index, ind_yp) = yp; M(index, ind_ym) = ym; diff --git a/src/invert/laplacexy/impls/petsc/laplacexy-petsc.cxx b/src/invert/laplacexy/impls/petsc/laplacexy-petsc.cxx index 04abecfe05..881a0b7fc0 100644 --- a/src/invert/laplacexy/impls/petsc/laplacexy-petsc.cxx +++ b/src/invert/laplacexy/impls/petsc/laplacexy-petsc.cxx @@ -906,13 +906,13 @@ void LaplaceXYpetsc::setMatrixElementsFiniteVolume(const Field2D& A, const Field // (1/J) d/dx ( J * g11 d/dx ) + (1/J) d/dy ( J * g22 d/dy ) auto coords = localmesh->getCoordinates(location); - const Field2D J_DC = DC(coords->J); - const Field2D g11_DC = DC(coords->g11); - const Field2D dx_DC = DC(coords->dx); - const Field2D dy_DC = DC(coords->dy); - const Field2D g_22_DC = DC(coords->g_22); - const Field2D g_23_DC = DC(coords->g_23); - const Field2D g23_DC = DC(coords->g23); + const Field2D J_DC = DC(coords->J()); + const Field2D g11_DC = DC(coords->g11()); + const Field2D dx_DC = DC(coords->dx()); + const Field2D dy_DC = DC(coords->dy()); + const Field2D g_22_DC = DC(coords->g_22()); + const Field2D g_23_DC = DC(coords->g_23()); + const Field2D g23_DC = DC(coords->g23()); for (int x = localmesh->xstart; x <= localmesh->xend; x++) { for (int y = localmesh->ystart; y <= localmesh->yend; y++) { @@ -1017,17 +1017,17 @@ void LaplaceXYpetsc::setMatrixElementsFiniteDifference(const Field2D& A, // + B*f auto coords = localmesh->getCoordinates(location); - const Field2D G1_2D = DC(coords->G1); - const Field2D G2_2D = DC(coords->G2); - const Field2D J_2D = DC(coords->J); - const Field2D g11_2D = DC(coords->g11); - const Field2D g_22_2D = DC(coords->g_22); - const Field2D g22_2D = DC(coords->g22); - const Field2D g12_2D = DC(coords->g12); - const Field2D d1_dx_2D = DC(coords->d1_dx); - const Field2D d1_dy_2D = DC(coords->d1_dy); - const Field2D dx_2D = DC(coords->dx); - const Field2D dy_2D = DC(coords->dy); + const Field2D G1_2D = DC(coords->G1()); + const Field2D G2_2D = DC(coords->G2()); + const Field2D J_2D = DC(coords->J()); + const Field2D g11_2D = DC(coords->g11()); + const Field2D g_22_2D = DC(coords->g_22()); + const Field2D g22_2D = DC(coords->g22()); + const Field2D g12_2D = DC(coords->g12()); + const Field2D d1_dx_2D = DC(coords->d1_dx()); + const Field2D d1_dy_2D = DC(coords->d1_dy()); + const Field2D dx_2D = DC(coords->dx()); + const Field2D dy_2D = DC(coords->dy()); const Field2D coef_dfdy = G2_2D - DC(DDY(J_2D / g_22_2D) / J_2D); diff --git a/src/invert/laplacexy/impls/petsc2/laplacexy-petsc2.cxx b/src/invert/laplacexy/impls/petsc2/laplacexy-petsc2.cxx index 01019466a8..916dc0c6af 100644 --- a/src/invert/laplacexy/impls/petsc2/laplacexy-petsc2.cxx +++ b/src/invert/laplacexy/impls/petsc2/laplacexy-petsc2.cxx @@ -157,20 +157,20 @@ void LaplaceXYpetsc2::setCoefs(const Field2D& A, const Field2D& B) { // XX component // Metrics on x+1/2 boundary - BoutReal J = 0.5 * (coords->J[index] + coords->J[ind_xp]); - BoutReal g11 = 0.5 * (coords->g11[index] + coords->g11[ind_xp]); - BoutReal dx = 0.5 * (coords->dx[index] + coords->dx[ind_xp]); + BoutReal J = 0.5 * (coords->J()[index] + coords->J()[ind_xp]); + BoutReal g11 = 0.5 * (coords->g11()[index] + coords->g11()[ind_xp]); + BoutReal dx = 0.5 * (coords->dx()[index] + coords->dx()[ind_xp]); BoutReal Acoef = 0.5 * (A[index] + A[ind_xp]); - BoutReal xp = Acoef * J * g11 / (coords->J[index] * dx * coords->dx[index]); + const BoutReal xp = Acoef * J * g11 / (coords->J()[index] * dx * coords->dx()[index]); // Metrics on x-1/2 boundary - J = 0.5 * (coords->J[index] + coords->J[ind_xm]); - g11 = 0.5 * (coords->g11[index] + coords->g11[ind_xm]); - dx = 0.5 * (coords->dx[index] + coords->dx[ind_xm]); + J = 0.5 * (coords->J()[index] + coords->J()[ind_xm]); + g11 = 0.5 * (coords->g11()[index] + coords->g11()[ind_xm]); + dx = 0.5 * (coords->dx()[index] + coords->dx()[ind_xm]); Acoef = 0.5 * (A[index] + A[ind_xm]); - BoutReal xm = Acoef * J * g11 / (coords->J[index] * dx * coords->dx[index]); + const BoutReal xm = Acoef * J * g11 / (coords->J()[index] * dx * coords->dx()[index]); BoutReal c = B[index] - xp - xm; // Central coefficient @@ -183,28 +183,28 @@ void LaplaceXYpetsc2::setCoefs(const Field2D& A, const Field2D& B) { // YY component // Metrics at y+1/2 - J = 0.5 * (coords->J[index] + coords->J[ind_yp]); - BoutReal g_22 = 0.5 * (coords->g_22[index] + coords->g_22[ind_yp]); - BoutReal g23 = 0.5 * (coords->g23[index] + coords->g23[ind_yp]); - BoutReal g_23 = 0.5 * (coords->g_23[index] + coords->g_23[ind_yp]); - BoutReal dy = 0.5 * (coords->dy[index] + coords->dy[ind_yp]); + J = 0.5 * (coords->J()[index] + coords->J()[ind_yp]); + BoutReal g_22 = 0.5 * (coords->g_22()[index] + coords->g_22()[ind_yp]); + BoutReal g23 = 0.5 * (coords->g23()[index] + coords->g23()[ind_yp]); + BoutReal g_23 = 0.5 * (coords->g_23()[index] + coords->g_23()[ind_yp]); + BoutReal dy = 0.5 * (coords->dy()[index] + coords->dy()[ind_yp]); Acoef = 0.5 * (A[ind_yp] + A[index]); - BoutReal yp = - -Acoef * J * g23 * g_23 / (g_22 * coords->J[index] * dy * coords->dy[index]); + const BoutReal yp = -Acoef * J * g23 * g_23 + / (g_22 * coords->J()[index] * dy * coords->dy()[index]); c -= yp; matrix(index, ind_yp) = yp; // Metrics at y-1/2 - J = 0.5 * (coords->J[index] + coords->J[ind_ym]); - g_22 = 0.5 * (coords->g_22[index] + coords->g_22[ind_ym]); - g23 = 0.5 * (coords->g23[index] + coords->g23[ind_ym]); - g_23 = 0.5 * (coords->g_23[index] + coords->g_23[ind_ym]); - dy = 0.5 * (coords->dy[index] + coords->dy[ind_ym]); + J = 0.5 * (coords->J()[index] + coords->J()[ind_ym]); + g_22 = 0.5 * (coords->g_22()[index] + coords->g_22()[ind_ym]); + g23 = 0.5 * (coords->g23()[index] + coords->g23()[ind_ym]); + g_23 = 0.5 * (coords->g_23()[index] + coords->g_23()[ind_ym]); + dy = 0.5 * (coords->dy()[index] + coords->dy()[ind_ym]); Acoef = 0.5 * (A[ind_ym] + A[index]); - BoutReal ym = - -Acoef * J * g23 * g_23 / (g_22 * coords->J[index] * dy * coords->dy[index]); + const BoutReal ym = -Acoef * J * g23 * g_23 + / (g_22 * coords->J()[index] * dy * coords->dy()[index]); c -= ym; matrix(index, ind_ym) = ym; } diff --git a/src/invert/laplacexz/impls/cyclic/laplacexz-cyclic.cxx b/src/invert/laplacexz/impls/cyclic/laplacexz-cyclic.cxx index b3e619df0c..2048bb587f 100644 --- a/src/invert/laplacexz/impls/cyclic/laplacexz-cyclic.cxx +++ b/src/invert/laplacexz/impls/cyclic/laplacexz-cyclic.cxx @@ -5,11 +5,11 @@ #include #include +#include +#include #include #include -#include - LaplaceXZcyclic::LaplaceXZcyclic(Mesh* m, Options* options, const CELL_LOC loc) : LaplaceXZ(m, options, loc) { // Note: `m` may be nullptr, but localmesh is set in LaplaceXZ base constructor @@ -74,7 +74,7 @@ void LaplaceXZcyclic::setCoefs(const Field2D& A2D, const Field2D& B2D) { Coordinates* coord = localmesh->getCoordinates(location); // NOTE: For now the X-Z terms are omitted, so check that they are small - ASSERT2(max(abs(coord->g13)) < 1e-5); + ASSERT2(max(abs(coord->g13())) < 1e-5); int ind = 0; const BoutReal zlength = getUniform(coord->zlength()); diff --git a/src/invert/parderiv/impls/cyclic/cyclic.cxx b/src/invert/parderiv/impls/cyclic/cyclic.cxx index c32c3d4b2d..04b0cc4405 100644 --- a/src/invert/parderiv/impls/cyclic/cyclic.cxx +++ b/src/invert/parderiv/impls/cyclic/cyclic.cxx @@ -1,23 +1,23 @@ /************************************************************************ * Inversion of parallel derivatives - * - * Inverts a matrix of the form + * + * Inverts a matrix of the form * * A + B * Grad2_par2 + C*D2DYDZ + + D*D2DZ2 + E*DDY - * + * * Parallel algorithm, using Cyclic Reduction * * Author: Ben Dudson, University of York, Oct 2011 - * + * * Known issues: * ------------ * * ************************************************************************** - * Copyright 2010 B.D.Dudson, S.Farley, M.V.Umansky, X.Q.Xu + * Copyright 2010 - 2026 BOUT++ contributors + * + * Contact: Ben Dudson, dudson2@llnl.gov * - * Contact: Ben Dudson, bd512@york.ac.uk - * * This file is part of BOUT++. * * BOUT++ is free software: you can redistribute it and/or modify @@ -40,12 +40,18 @@ #if not BOUT_USE_METRIC_3D +#include +#include #include #include +#include #include +#include #include #include +#include #include +#include #include #include @@ -58,7 +64,7 @@ InvertParCR::InvertParCR(Options* opt, CELL_LOC location, Mesh* mesh_in) // Number of k equations to solve for each x location nsys = 1 + (localmesh->LocalNz) / 2; - sg = sqrt(localmesh->getCoordinates(location)->g_22); + sg = sqrt(localmesh->getCoordinates(location)->g_22()); sg = DDY(1. / sg) / sg; } @@ -69,7 +75,7 @@ const Field3D InvertParCR::solve(const Field3D& f) { Field3D result = emptyFrom(f).setDirectionY(YDirectionType::Aligned); - Coordinates* coord = f.getCoordinates(); + const Coordinates* coord = f.getCoordinates(); Field3D alignedField = toFieldAligned(f, "RGN_NOBNDRY"); @@ -160,7 +166,7 @@ const Field3D InvertParCR::solve(const Field3D& f) { BoutReal ecoef = E(x, y + local_ystart) + sg(x, y + local_ystart) * B(x, y + local_ystart); // ddy - if (coord->non_uniform) { + if (coord->non_uniform()) { ecoef += bcoef * coord->d1_dy(x, y + local_ystart); } diff --git a/src/invert/pardiv/impls/cyclic/pardiv_cyclic.cxx b/src/invert/pardiv/impls/cyclic/pardiv_cyclic.cxx index aad01c5f2f..5a2036cddc 100644 --- a/src/invert/pardiv/impls/cyclic/pardiv_cyclic.cxx +++ b/src/invert/pardiv/impls/cyclic/pardiv_cyclic.cxx @@ -40,12 +40,16 @@ #if not BOUT_USE_METRIC_3D +#include #include #include +#include #include #include #include +#include #include +#include #include #include @@ -65,7 +69,7 @@ Field3D InvertParDivCR::solve(const Field3D& f) { Field3D result = emptyFrom(f).setDirectionY(YDirectionType::Aligned); - Coordinates* coord = f.getCoordinates(); + const Coordinates* coord = f.getCoordinates(); Field3D alignedField = toFieldAligned(f, "RGN_NOBNDRY"); @@ -104,9 +108,9 @@ Field3D InvertParDivCR::solve(const Field3D& f) { auto b = Matrix(nsys, size); auto c = Matrix(nsys, size); - const Field2D dy = coord->dy; - const Field2D J = coord->J; - const Field2D g_22 = coord->g_22; + const Field2D dy = coord->dy(); + const Field2D J = coord->J(); + const Field2D g_22 = coord->g_22(); const auto zlength = getUniform(coord->zlength()); // Loop over flux-surfaces diff --git a/src/mesh/boundary_factory.cxx b/src/mesh/boundary_factory.cxx index 6fcbd8b5c3..f757757382 100644 --- a/src/mesh/boundary_factory.cxx +++ b/src/mesh/boundary_factory.cxx @@ -1,6 +1,5 @@ #include "bout/assert.hxx" #include "bout/parallel_boundary_op.hxx" -#include "bout/parallel_boundary_region.hxx" #include #include #include @@ -10,7 +9,6 @@ #include #include -#include #include #include using std::list; diff --git a/src/mesh/boundary_region.cxx b/src/mesh/boundary_region.cxx index 2dc64fc88f..e7c5151f5f 100644 --- a/src/mesh/boundary_region.cxx +++ b/src/mesh/boundary_region.cxx @@ -16,9 +16,9 @@ BoundaryRegion* BoundaryRegionBase::getLegacyPointer() { ASSERT3(legacy->location == location); ASSERT3(legacy->label == label); ASSERT3(legacy->localmesh == localmesh); - return legacy; + return legacy.get(); } -BoundaryRegionBase::~BoundaryRegionBase() { delete legacy; }; + BoundaryRegionXIn::BoundaryRegionXIn(std::string name, int ymin, int ymax, Mesh* passmesh) : BoundaryRegion(std::move(name), -1, 0, passmesh), ys(ymin), ye(ymax) { location = BNDRY_XIN; diff --git a/src/mesh/boundary_standard.cxx b/src/mesh/boundary_standard.cxx index ba0eea658e..c9ea3ddc74 100644 --- a/src/mesh/boundary_standard.cxx +++ b/src/mesh/boundary_standard.cxx @@ -1,3 +1,8 @@ +#include "bout/assert.hxx" +#include "bout/bout_types.hxx" +#include "bout/field2d.hxx" +#include "bout/field3d.hxx" +#include "bout/sys/expressionparser.hxx" #include #include #include @@ -10,6 +15,8 @@ #include #include #include +#include +#include using bout::generator::Context; @@ -246,6 +253,129 @@ void BoundaryDirichlet_O1::apply_ddt(Field3D& f) { /////////////////////////////////////////////////////////////// +BoundaryOp* BoundaryDirichlet_O1::clone(BoundaryRegion* region, + const std::list& args) { + verifyNumPoints(region, 1); + + std::shared_ptr newgen; + if (!args.empty()) { + // First argument should be an expression + newgen = FieldFactory::get()->parse(args.front()); + } + return new BoundaryDirichlet_O1(region, newgen); +} + +void BoundaryDirichlet_O1::apply(Field2D& f) { BoundaryDirichlet_O1::apply(f, 0.); } + +void BoundaryDirichlet_O1::apply(Field2D& f, BoutReal t) { + // Set (at 1st order) the value at the grid cell to the guard cells. + + Mesh* mesh = bndry->localmesh; + ASSERT1(mesh == f.getMesh()); + bndry->first(); + + // Decide which generator to use + std::shared_ptr fg = gen; + if (!fg) { + fg = f.getBndryGenerator(bndry->location); + } + + BoutReal val = 0.0; + + // Check for staggered grids + + CELL_LOC const loc = f.getLocation(); + if (loc != CELL_CENTRE) { + // Staggered + throw BoutException("dirichlet_o1 BC is not implementated for staggered grids."); + + } // Non-staggered, standard case + for (; !bndry->isDone(); bndry->next1d()) { + + if (fg) { + val = fg->generate(Context(bndry, loc, t, mesh)); + } + f(bndry->x, bndry->y) = val; + + // Need to set second guard cell, as may be used for interpolation or upwinding derivatives + // This is not very efficient. Both boundary cells can be treated in one loop. + for (int i = 1; i < bndry->width; i++) { + const int xi = bndry->x + (i * bndry->bx); + const int yi = bndry->y + (i * bndry->by); + f(xi, yi) = val; + } + } +} + +void BoundaryDirichlet_O1::apply(Field3D& f) { BoundaryDirichlet_O1::apply(f, 0.); } + +void BoundaryDirichlet_O1::apply(Field3D& f, BoutReal t) { + // Set (at 1st order) the value at the grid cell to the guard cells. + + Mesh* mesh = bndry->localmesh; + ASSERT1(mesh == f.getMesh()); + bndry->first(); + + // Decide which generator to use + std::shared_ptr fg = gen; + if (!fg) { + fg = f.getBndryGenerator(bndry->location); + } + + BoutReal val = 0.0; + + // Check for staggered grids + + CELL_LOC const loc = f.getLocation(); + if (loc != CELL_CENTRE) { + // Staggered. + throw BoutException("dirichlet_o1 BC is not implementated for staggered grids."); + + } // Standard (non-staggered) case + for (; !bndry->isDone(); bndry->next1d()) { + for (int zk = mesh->zstart; zk <= mesh->zend; zk++) { + if (fg) { + val = fg->generate(Context(bndry, zk, loc, t, mesh)); + } + f(bndry->x, bndry->y, zk) = val; + } + + // This is not very efficient. Both boundary cells can be treated in one loop. + for (int i = 1; i < bndry->width; i++) { + // Set any other guard cells using the values on the cells + const int xi = bndry->x + (i * bndry->bx); + const int yi = bndry->y + (i * bndry->by); + for (int zk = mesh->zstart; zk <= mesh->zend; zk++) { + if (fg) { + val = fg->generate(Context(bndry, zk, loc, t, mesh)); + } + f(xi, yi, zk) = val; + } + } + } +} + +void BoundaryDirichlet_O1::apply_ddt(Field2D& f) { + Field2D* dt = f.timeDeriv(); + for (bndry->first(); !bndry->isDone(); bndry->next()) { + (*dt)(bndry->x, bndry->y) = 0.; // Set time derivative to zero + } +} + +void BoundaryDirichlet_O1::apply_ddt(Field3D& f) { + const Mesh* mesh = bndry->localmesh; + ASSERT1(mesh == f.getMesh()); + Field3D* dt = f.timeDeriv(); + + for (bndry->first(); !bndry->isDone(); bndry->next()) { + for (int z = mesh->zstart; z <= mesh->zend; z++) { + (*dt)(bndry->x, bndry->y, z) = 0.; // Set time derivative to zero + } + } +} + +/////////////////////////////////////////////////////////////// + BoundaryOp* BoundaryDirichlet::clone(BoundaryRegion* region, const std::list& args) { verifyNumPoints(region, 1); @@ -442,7 +572,7 @@ void BoundaryDirichlet::apply(Field3D& f, BoutReal t) { // Check for staggered grids CELL_LOC loc = f.getLocation(); - if (mesh->StaggerGrids && loc != CELL_CENTRE) { + if (loc != CELL_CENTRE) { // Staggered. Need to apply slightly differently if (loc == CELL_XLOW) { @@ -944,7 +1074,7 @@ void BoundaryDirichlet_O3::apply(Field3D& f, BoutReal t) { // Check for staggered grids CELL_LOC loc = f.getLocation(); - if (mesh->StaggerGrids && loc != CELL_CENTRE) { + if (loc != CELL_CENTRE) { // Staggered. Need to apply slightly differently if (loc == CELL_XLOW) { @@ -1401,7 +1531,7 @@ void BoundaryDirichlet_O4::apply(Field3D& f, BoutReal t) { // Check for staggered grids CELL_LOC loc = f.getLocation(); - if (mesh->StaggerGrids && loc != CELL_CENTRE) { + if (loc != CELL_CENTRE) { // Staggered. Need to apply slightly differently if (loc == CELL_XLOW) { @@ -2218,7 +2348,7 @@ void BoundaryNeumann::apply([[maybe_unused]] Field2D& f, [[maybe_unused]] BoutRe // Check for staggered grids CELL_LOC loc = f.getLocation(); - if (mesh->StaggerGrids && loc != CELL_CENTRE) { + if (loc != CELL_CENTRE) { // Staggered. Need to apply slightly differently // Use one-sided differencing. Cell is now on // the boundary, so use one-sided differencing @@ -2507,7 +2637,7 @@ void BoundaryNeumann::apply([[maybe_unused]] Field2D& f, [[maybe_unused]] BoutRe // Check for staggered grids CELL_LOC loc = f.getLocation(); - if (mesh->StaggerGrids && loc != CELL_CENTRE) { + if (loc != CELL_CENTRE) { throw BoutException("neumann_o4 not implemented with staggered grid yet"); } else { // Non-staggered, standard case @@ -2559,7 +2689,7 @@ void BoundaryNeumann::apply([[maybe_unused]] Field2D& f, [[maybe_unused]] BoutRe // Check for staggered grids CELL_LOC loc = f.getLocation(); - if (mesh->StaggerGrids && loc != CELL_CENTRE) { + if (loc != CELL_CENTRE) { throw BoutException("neumann_o4 not implemented with staggered grid yet"); } else { Coordinates* coords = f.getCoordinates(); @@ -2922,8 +3052,8 @@ void BoundaryNeumann::apply([[maybe_unused]] Field2D& f, [[maybe_unused]] BoutRe rfft(f(x - 2 * bx, y), mesh->LocalNz, c1.begin()); c1[0] = c0[0] - c1[0]; // Only need gradient - // Solve metric->g11*d2f/dx2 - metric->g33*kz^2f = 0 - // Assume metric->g11, metric->g33 constant -> exponential growth or decay + // Solve metric->g11()*d2f/dx2 - metric->g33()*kz^2f = 0 + // Assume metric->g11(), metric->g33() constant -> exponential growth or decay // Loop in X towards edge of domain do { @@ -3139,8 +3269,8 @@ void BoundaryNeumann::apply([[maybe_unused]] Field2D& f, [[maybe_unused]] BoutRe c1[jz] = la * c2[jz] + lb * c1[jz] + lc * c0[jz]; } } - // Solve metric->g11*d2f/dx2 - metric->g33*kz^2f = 0 - // Assume metric->g11, metric->g33 constant -> exponential growth or decay + // Solve metric->g11()*d2f/dx2 - metric->g33()*kz^2f = 0 + // Assume metric->g11(), metric->g33() constant -> exponential growth or decay BoutReal xpos = 0.0; // Loop in X towards edge of domain do { @@ -3237,7 +3367,7 @@ void BoundaryNeumann::apply([[maybe_unused]] Field2D& f, [[maybe_unused]] BoutRe var.z(jx + 1, jy, jz) = var.z(jx - 3, jy, jz) + 4. * metric->dx(jx, jy) * tmp; } - // d/dx( Jmetric->g11 B_x ) = - d/dx( Jmetric->g12 B_y + Jmetric->g13 B_z) + // d/dx( Jmetric->g11() B_x ) = - d/dx( Jmetric->g12() B_y + Jmetric->g13() B_z) // - d/dy( JB^y ) - d/dz( JB^z ) tmp = diff --git a/src/mesh/christoffel_symbols.cxx b/src/mesh/christoffel_symbols.cxx new file mode 100644 index 0000000000..c90d7c6d58 --- /dev/null +++ b/src/mesh/christoffel_symbols.cxx @@ -0,0 +1,92 @@ +#include "bout/christoffel_symbols.hxx" +#include "bout/coordinates.hxx" +#include "bout/derivs.hxx" +#include "bout/mesh.hxx" +#include "bout/output.hxx" + +ChristoffelSymbols::ChristoffelSymbols(const Coordinates& coordinates) { + // Calculate Christoffel symbol terms (18 independent values) + // Note: This calculation is completely general: metric + // tensor can be 2D or 3D. For 2D, all DDZ terms are zero + + const auto& contravariantMetricTensor = coordinates.getContravariantMetricTensor(); + const auto& covariantMetricTensor = coordinates.getCovariantMetricTensor(); + + const auto& g11 = contravariantMetricTensor.g11(); + const auto& g22 = contravariantMetricTensor.g22(); + const auto& g33 = contravariantMetricTensor.g33(); + const auto& g12 = contravariantMetricTensor.g12(); + const auto& g13 = contravariantMetricTensor.g13(); + const auto& g23 = contravariantMetricTensor.g23(); + + const auto& g_11 = covariantMetricTensor.g11(); + const auto& g_22 = covariantMetricTensor.g22(); + const auto& g_33 = covariantMetricTensor.g33(); + const auto& g_12 = covariantMetricTensor.g12(); + const auto& g_13 = covariantMetricTensor.g13(); + const auto& g_23 = covariantMetricTensor.g23(); + + G1_11_m = 0.5 * g11 * DDX(g_11) + g12 * (DDX(g_12) - 0.5 * DDY(g_11)) + + g13 * (DDX(g_13) - 0.5 * DDZ(g_11)); + G1_22_m = g11 * (DDY(g_12) - 0.5 * DDX(g_22)) + 0.5 * g12 * DDY(g_22) + + g13 * (DDY(g_23) - 0.5 * DDZ(g_22)); + G1_33_m = g11 * (DDZ(g_13) - 0.5 * DDX(g_33)) + g12 * (DDZ(g_23) - 0.5 * DDY(g_33)) + + 0.5 * g13 * DDZ(g_33); + G1_12_m = 0.5 * g11 * DDY(g_11) + 0.5 * g12 * DDX(g_22) + + 0.5 * g13 * (DDY(g_13) + DDX(g_23) - DDZ(g_12)); + G1_13_m = 0.5 * g11 * DDZ(g_11) + 0.5 * g12 * (DDZ(g_12) + DDX(g_23) - DDY(g_13)) + + 0.5 * g13 * DDX(g_33); + G1_23_m = 0.5 * g11 * (DDZ(g_12) + DDY(g_13) - DDX(g_23)) + + 0.5 * g12 * (DDZ(g_22) + DDY(g_23) - DDY(g_23)) + // + 0.5 *g13*(DDZ(g_32) + DDY(g_33) - DDZ(g_23)); + // which equals + + 0.5 * g13 * DDY(g_33); + + G2_11_m = 0.5 * g12 * DDX(g_11) + g22 * (DDX(g_12) - 0.5 * DDY(g_11)) + + g23 * (DDX(g_13) - 0.5 * DDZ(g_11)); + G2_22_m = g12 * (DDY(g_12) - 0.5 * DDX(g_22)) + 0.5 * g22 * DDY(g_22) + + g23 * (DDY(g23) - 0.5 * DDZ(g_22)); + G2_33_m = g12 * (DDZ(g_13) - 0.5 * DDX(g_33)) + g22 * (DDZ(g_23) - 0.5 * DDY(g_33)) + + 0.5 * g23 * DDZ(g_33); + G2_12_m = 0.5 * g12 * DDY(g_11) + 0.5 * g22 * DDX(g_22) + + 0.5 * g23 * (DDY(g_13) + DDX(g_23) - DDZ(g_12)); + G2_13_m = + // 0.5 *g21*(DDZ(g_11) + DDX(covariantMetricTensor.Getg13()) - DDX(g_13)) + // which equals + 0.5 * g12 * (DDZ(g_11) + DDX(g_13) - DDX(g_13)) + // + 0.5 *g22*(DDZ(covariantMetricTensor.Getg21()) + DDX(g_23) - DDY(g_13)) + // which equals + + 0.5 * g22 * (DDZ(g_12) + DDX(g_23) - DDY(g_13)) + // + 0.5 *g23*(DDZ(covariantMetricTensor.Getg31()) + DDX(g_33) - DDZ(g_13)); + // which equals + + 0.5 * g23 * DDX(g_33); + G2_23_m = 0.5 * g12 * (DDZ(g_12) + DDY(g_13) - DDX(g_23)) + 0.5 * g22 * DDZ(g_22) + + 0.5 * g23 * DDY(g_33); + + G3_11_m = 0.5 * g13 * DDX(g_11) + g23 * (DDX(g_12) - 0.5 * DDY(g_11)) + + g33 * (DDX(g_13) - 0.5 * DDZ(g_11)); + G3_22_m = g13 * (DDY(g_12) - 0.5 * DDX(g_22)) + 0.5 * g23 * DDY(g_22) + + g33 * (DDY(g_23) - 0.5 * DDZ(g_22)); + G3_33_m = g13 * (DDZ(g_13) - 0.5 * DDX(g_33)) + g23 * (DDZ(g_23) - 0.5 * DDY(g_33)) + + 0.5 * g33 * DDZ(g_33); + G3_12_m = + // 0.5 *g31*(DDY(g_11) + DDX(covariantMetricTensor.Getg12()) - DDX(g_12)) + // which equals to + 0.5 * g13 * DDY(g_11) + // + 0.5 *g32*(DDY(covariantMetricTensor.Getg21()) + DDX(g_22) - DDY(g_12)) + // which equals to + + 0.5 * g23 * DDX(g_22) + //+ 0.5 *g33*(DDY(covariantMetricTensor.Getg31()) + DDX(covariantMetricTensor.Getg32()) - DDZ(g_12)); + // which equals to + + 0.5 * g33 * (DDY(g_13)) + DDX(g_23) - DDZ(g_12); + G3_13_m = 0.5 * g13 * DDZ(g_11) + 0.5 * g23 * (DDZ(g_12) + DDX(g_23) - DDY(g_13)) + + 0.5 * g33 * DDX(g_33); + G3_23_m = 0.5 * g13 * (DDZ(g_12) + DDY(g_13)) - DDX(g_23) + 0.5 * g23 * DDZ(g_22) + + 0.5 * g33 * DDY(g_33); + + output_progress.write("\tCommunicating connection terms\n"); + + G1_11_m.getMesh()->communicate(G1_11_m, G1_22_m, G1_33_m, G1_12_m, G1_13_m, G1_23_m, + G2_11_m, G2_22_m, G2_33_m, G2_12_m, G2_13_m, G2_23_m, + G3_11_m, G3_22_m, G3_33_m, G3_12_m, G3_13_m, G3_23_m); +} diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index 3543995702..bad8ebbd84 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -4,9 +4,12 @@ * given the contravariant metric tensor terms **************************************************************************/ +#include "bout/boundary_region.hxx" +#include "bout/christoffel_symbols.hxx" #include "bout/coordinates_accessor.hxx" #include "bout/field3d.hxx" #include "bout/field_data.hxx" +#include "bout/g_values.hxx" #include #include #include @@ -21,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -37,14 +41,13 @@ #include #include #include -#include #include #include +#include #include #include -#include "invert3x3.hxx" #include "parallel/fci.hxx" #include "parallel/shiftedmetricinterp.hxx" @@ -266,45 +269,38 @@ Coordinates::Coordinates(Mesh* mesh, FieldMetric dx, FieldMetric dy, FieldMetric FieldMetric g_33, FieldMetric g_12, FieldMetric g_13, FieldMetric g_23, FieldMetric ShiftTorsion, FieldMetric IntShiftTorsion) - : dx(std::move(dx)), dy(std::move(dy)), dz(dz), J(std::move(J)), Bxy(std::move(Bxy)), - g11(std::move(g11)), g22(std::move(g22)), g33(std::move(g33)), g12(std::move(g12)), - g13(std::move(g13)), g23(std::move(g23)), g_11(std::move(g_11)), - g_22(std::move(g_22)), g_33(std::move(g_33)), g_12(std::move(g_12)), - g_13(std::move(g_13)), g_23(std::move(g_23)), ShiftTorsion(std::move(ShiftTorsion)), - IntShiftTorsion(std::move(IntShiftTorsion)), nz(mesh->LocalNz), localmesh(mesh), - localoptions(nullptr), location(CELL_CENTRE) {} + : nz(mesh->LocalNz), localmesh(mesh), location(CELL_CENTRE), dx_(std::move(dx)), + dy_(std::move(dy)), dz_(std::move(dz)), ShiftTorsion_(std::move(ShiftTorsion)), + IntShiftTorsion_(std::move(IntShiftTorsion)), + transform(bout::utils::make_unique(*localmesh)), + contravariantMetricTensor(std::move(g11), std::move(g22), std::move(g33), + std::move(g12), std::move(g13), std::move(g23)), + covariantMetricTensor(std::move(g_11), std::move(g_22), std::move(g_33), + std::move(g_12), std::move(g_13), std::move(g_23)), + jacobian_cache(std::make_unique(std::move(J))), Bxy_(std::move(Bxy)) {} Coordinates::Coordinates(Mesh* mesh, Options* options) - : dx(1., mesh), dy(1., mesh), dz(1., mesh), d1_dx(mesh), d1_dy(mesh), d1_dz(mesh), - J(1., mesh), Bxy(1., mesh), - // Identity metric tensor - g11(1., mesh), g22(1., mesh), g33(1., mesh), g12(0, mesh), g13(0, mesh), - g23(0, mesh), g_11(1., mesh), g_22(1., mesh), g_33(1., mesh), g_12(0, mesh), - g_13(0, mesh), g_23(0, mesh), G1_11(mesh), G1_22(mesh), G1_33(mesh), G1_12(mesh), - G1_13(mesh), G1_23(mesh), G2_11(mesh), G2_22(mesh), G2_33(mesh), G2_12(mesh), - G2_13(mesh), G2_23(mesh), G3_11(mesh), G3_22(mesh), G3_33(mesh), G3_12(mesh), - G3_13(mesh), G3_23(mesh), G1(mesh), G2(mesh), G3(mesh), ShiftTorsion(mesh), - IntShiftTorsion(mesh), nz(mesh->LocalNz), localmesh(mesh), localoptions(options), - location(CELL_CENTRE) { + : nz(mesh->LocalNz), localmesh(mesh), localoptions(options), location(CELL_CENTRE), + dx_(1., mesh), dy_(1., mesh), dz_(1., mesh), d1_dx_(mesh), d1_dy_(mesh), + d1_dz_(mesh), ShiftTorsion_(0.0, mesh), IntShiftTorsion_(0.0, mesh), + contravariantMetricTensor(1., 1., 1., 0, 0, 0, mesh), + covariantMetricTensor(1., 1., 1., 0, 0, 0, mesh), Bxy_(1., mesh) { readFromMesh(options, ""); - // Allow transform to fix things up - transform->loadParallelMetrics(this); + + if (transform != nullptr and not transform->canToFromFieldAligned()) { + // Read parallel metrics from gridfile for FCI + readParallelMetricComponents(); + } } Coordinates::Coordinates(Mesh* mesh, Options* options, const CELL_LOC loc, const Coordinates* coords_in, bool force_interpolate_from_centre) - : dx(1., mesh), dy(1., mesh), dz(1., mesh), d1_dx(mesh), d1_dy(mesh), d1_dz(mesh), - J(1., mesh), Bxy(1., mesh), - // Identity metric tensor - g11(1., mesh), g22(1., mesh), g33(1., mesh), g12(0, mesh), g13(0, mesh), - g23(0, mesh), g_11(1., mesh), g_22(1., mesh), g_33(1., mesh), g_12(0, mesh), - g_13(0, mesh), g_23(0, mesh), G1_11(mesh), G1_22(mesh), G1_33(mesh), G1_12(mesh), - G1_13(mesh), G1_23(mesh), G2_11(mesh), G2_22(mesh), G2_33(mesh), G2_12(mesh), - G2_13(mesh), G2_23(mesh), G3_11(mesh), G3_22(mesh), G3_33(mesh), G3_12(mesh), - G3_13(mesh), G3_23(mesh), G1(mesh), G2(mesh), G3(mesh), ShiftTorsion(mesh), - IntShiftTorsion(mesh), nz(mesh->LocalNz), localmesh(mesh), localoptions(options), - location(loc) { + : nz(mesh->LocalNz), localmesh(mesh), localoptions(options), location(loc), + dx_(1., mesh), dy_(1., mesh), dz_(1., mesh), d1_dx_(mesh), d1_dy_(mesh), + d1_dz_(mesh), ShiftTorsion_(0.0, mesh), IntShiftTorsion_(0.0, mesh), + contravariantMetricTensor(1., 1., 1., 0, 0, 0, mesh), + covariantMetricTensor(1., 1., 1., 0, 0, 0, mesh), Bxy_(1., mesh) { const std::string suffix = getLocationSuffix(location); @@ -313,9 +309,9 @@ Coordinates::Coordinates(Mesh* mesh, Options* options, const CELL_LOC loc, } else { // Interpolate fields from coords_in - if (isUniform(coords_in->dz)) { - dz = coords_in->dz; - dz.setLocation(location); + if (isUniform(coords_in->dz_)) { + dz_ = coords_in->dz_; + dz_.setLocation(location); } else { throw BoutException( "We are asked to transform dz to get dz before we have a transform, which " @@ -327,52 +323,39 @@ Coordinates::Coordinates(Mesh* mesh, Options* options, const CELL_LOC loc, return interpolateAndExtrapolate(f, location, true, true, false, transform.get()); }; - dx = interpField(coords_in->dx); - dy = interpField(coords_in->dy); + dx_ = interpField(coords_in->dx_); + dy_ = interpField(coords_in->dy_); // not really needed - we have used dz already ... - dz = interpField(coords_in->dz); - - // Diagonal components of metric tensor g^{ij} - g11 = interpField(coords_in->g11); - g22 = interpField(coords_in->g22); - g33 = interpField(coords_in->g33); - - // Off-diagonal elements. - g12 = interpField(coords_in->g12); - g13 = interpField(coords_in->g13); - g23 = interpField(coords_in->g23); - - // 3x3 matrix inversion can exaggerate small interpolation errors, so it is - // more robust to interpolate and extrapolate derived quantities directly, - // rather than deriving from interpolated/extrapolated covariant metric - // components - g_11 = interpField(coords_in->g_11); - g_22 = interpField(coords_in->g_22); - g_33 = interpField(coords_in->g_33); - g_12 = interpField(coords_in->g_12); - g_13 = interpField(coords_in->g_13); - g_23 = interpField(coords_in->g_23); + dz_ = interpField(coords_in->dz_); + + setMetricTensor(coords_in->getContravariantMetricTensor(), + coords_in->getCovariantMetricTensor()); + + contravariantMetricTensor.map(interpField); + covariantMetricTensor.map(interpField); // Check input metrics checkContravariant(); checkCovariant(); - J = interpField(coords_in->J); - Bxy = interpField(coords_in->Bxy); + setJ(interpField(coords_in->J())); + setBxy(interpField(coords_in->Bxy())); - bout::checkFinite(J, "The Jacobian", "RGN_NOCORNERS"); - bout::checkPositive(J, "The Jacobian", "RGN_NOCORNERS"); - bout::checkFinite(Bxy, "Bxy", "RGN_NOCORNERS"); - bout::checkPositive(Bxy, "Bxy", "RGN_NOCORNERS"); + bout::checkFinite(J(), "The Jacobian", "RGN_NOCORNERS"); + bout::checkPositive(J(), "The Jacobian", "RGN_NOCORNERS"); + bout::checkFinite(Bxy(), "Bxy", "RGN_NOCORNERS"); + bout::checkPositive(Bxy(), "Bxy", "RGN_NOCORNERS"); - ShiftTorsion = interpField(coords_in->ShiftTorsion); + ShiftTorsion_ = interpField(coords_in->ShiftTorsion_); if (mesh->IncIntShear) { - IntShiftTorsion = interpField(coords_in->IntShiftTorsion); + IntShiftTorsion_ = interpField(coords_in->IntShiftTorsion_); + } + if (not transform->canToFromFieldAligned()) { + // Read parallel metrics from gridfile for FCI + readParallelMetricComponents(); } } - // Allow transform to fix things up - transform->loadParallelMetrics(this); } void Coordinates::readFromMesh(Options* options, const std::string& suffix) { @@ -425,6 +408,7 @@ void Coordinates::readFromMesh(Options* options, const std::string& suffix) { // Passing `field` in here twice is gross but ok because the second argument // is only used when interpolating, and we're not interpolating here fillGuards_impl(field, location, field, extrapolate_x, extrapolate_y); + return field; }; // Read the field, transform if required, and fill in the guards @@ -448,34 +432,27 @@ void Coordinates::readFromMesh(Options* options, const std::string& suffix) { // We can't use the helper functions here because in 3D we might (always?) // need to transform from field aligned -- which requires dz! So we have to // read it "plain" first... - localmesh->get(dz, "dz" + suffix, default_dz, false, location); + localmesh->get(dz_, "dz" + suffix, default_dz, false, location); } // ...then we can set the transform (required early for differentiation)... setParallelTransform(options); // ...and finally we can transform/interpolate/fill in guards - dz = interpolateAndExtrapolate(dz, location, extrapolate_x, extrapolate_y, false, - transform.get()); + setDz(interpolateAndExtrapolate(dz_, location, extrapolate_x, extrapolate_y, false, + transform.get())); // everything else from this point on can use our helper functions - dx = readAndFillGuards("dx", 1.0); + setDx(readAndFillGuards("dx", 1.0), localmesh->periodicX); - if (localmesh->periodicX) { - localmesh->communicate_no_slices(dx); - } - - dy = readAndFillGuards("dy", 1.0); + setDy(readAndFillGuards("dy", 1.0)); // Diagonal components of metric tensor g^{ij} (default to 1) - g11 = readAndFillGuards("g11", 1.0); - g22 = readAndFillGuards("g22", 1.0); - g33 = readAndFillGuards("g33", 1.0); - - // Off-diagonal elements. Default to 0 - g12 = readAndFillGuards("g12", 0.0); - g13 = readAndFillGuards("g13", 0.0); - g23 = readAndFillGuards("g23", 0.0); + contravariantMetricTensor = ContravariantMetricTensor{ + readAndFillGuards("g11", 1.0), readAndFillGuards("g22", 1.0), + readAndFillGuards("g33", 1.0), readAndFillGuards("g12", 0.0), + readAndFillGuards("g13", 0.0), readAndFillGuards("g23", 0.0), + }; // Check input metrics checkContravariant(); @@ -485,597 +462,376 @@ void Coordinates::readFromMesh(Options* options, const std::string& suffix) { auto source_has_component = [&suffix, this](const std::string& name) { return localmesh->sourceHasVar(name + suffix); }; - // Check if any of the components are present - if (std::any_of(begin(covariant_component_names), end(covariant_component_names), - source_has_component)) { - // Check that all components are present - if (std::all_of(begin(covariant_component_names), end(covariant_component_names), - source_has_component)) { - g_11 = readField("g_11", 1.0); - g_22 = readField("g_22", 1.0); - g_33 = readField("g_33", 1.0); - g_12 = readField("g_12", 0.0); - g_13 = readField("g_13", 0.0); - g_23 = readField("g_23", 0.0); - - output_warn.write("\tWARNING! Covariant components of metric tensor set manually. " - "Contravariant components NOT recalculated\n"); - } else { + // Check if any of the components are present, all of them are present + if (const bool all_present = + std::ranges::all_of(covariant_component_names, source_has_component); + std::ranges::any_of(covariant_component_names, source_has_component) + and all_present) { + covariantMetricTensor = CovariantMetricTensor{ + readAndFillGuards("g_11", 1.0), readAndFillGuards("g_22", 1.0), + readAndFillGuards("g_33", 1.0), readAndFillGuards("g_12", 0.0), + readAndFillGuards("g_13", 0.0), readAndFillGuards("g_23", 0.0), + }; + + output_warn.write("\tWARNING! Covariant components of metric tensor set manually. " + "Contravariant components NOT recalculated\n"); + } else { + if (not all_present) { output_warn.write("Not all covariant components of metric tensor found. " "Calculating all from the contravariant tensor\n"); - /// Calculate contravariant metric components if not found - if (calcCovariant("RGN_NOCORNERS") != 0) { - throw BoutException("Error in calcCovariant call"); - } - } - } else { - /// Calculate contravariant metric components if not found - if (calcCovariant("RGN_NOCORNERS") != 0) { - throw BoutException("Error in calcCovariant call"); } + covariantMetricTensor = contravariantMetricTensor.inverse("RGN_NOCORNERS", false); + + // More robust to extrapolate derived quantities directly, rather than + // deriving from extrapolated covariant metric components + covariantMetricTensor.map(fillGuards); } - // More robust to extrapolate derived quantities directly, rather than - // deriving from extrapolated covariant metric components - fillGuards(g_11); - fillGuards(g_22); - fillGuards(g_33); - fillGuards(g_12); - fillGuards(g_13); - fillGuards(g_23); // Check covariant metrics checkCovariant(); - // Calculate Jacobian and Bxy - jacobian(); - // Attempt to read J from the grid file - auto Jcalc = J; - if (localmesh->get(J, "J" + suffix, 0.0, false) != 0) { + auto Jcalc = J(); + FieldMetric J_temp{localmesh}; + if (localmesh->get(J_temp, "J" + suffix, 0.0, false) != 0) { output_warn.write( "\tWARNING: Jacobian 'J' not found. Calculating from metric tensor\n"); - J = Jcalc; + localmesh->communicate_no_slices(*jacobian_cache); } else { checkStaggeredGet(localmesh, "J", suffix); - J = ensuredUnaligned(J); - fillGuards(J); + *jacobian_cache = ensuredUnaligned(J_temp); + fillGuards(*jacobian_cache); // Compare calculated and loaded values - output_warn.write("\tMaximum difference in J is {:e}\n", max(abs(J - Jcalc))); - - localmesh->communicate_no_slices(J); + output_warn.write("\tMaximum difference in J is {:e}\n", max(abs(J() - Jcalc))); - // Re-evaluate Bxy using new J - Bxy = sqrt(g_22) / J; + localmesh->communicate_no_slices(*jacobian_cache); } // Check jacobian - bout::checkFinite(J, "J", "RGN_NOCORNERS"); - bout::checkPositive(J, "J", "RGN_NOCORNERS"); - if (min(abs(J)) < 1.0e-10) { - throw BoutException("\tERROR: Jacobian becomes very small\n"); + bout::checkFinite(J(), "J" + suffix, "RGN_NOCORNERS"); + bout::checkPositive(J(), "J" + suffix, "RGN_NOCORNERS"); + if (min(abs(J())) < 1.0e-10) { + throw BoutException("\tERROR: Jacobian{:s} becomes very small\n", suffix); } // Attempt to read Bxy from the grid file - auto Bcalc = Bxy; - if (localmesh->get(Bxy, "Bxy" + suffix, 0.0, false) != 0) { + const FieldMetric Bcalc = sqrt(g_22()) / J(); + if (localmesh->get(Bxy_, "Bxy" + suffix, 0.0, false) != 0) { output_warn.write("\tWARNING: Magnitude of B field 'Bxy' not found. Calculating from " "metric tensor\n"); - Bxy = Bcalc; + Bxy_ = interpolateAndExtrapolate(Bcalc, location, extrapolate_x, extrapolate_y, false, + transform.get()); + localmesh->communicate_no_slices(Bxy_); } else { checkStaggeredGet(localmesh, "Bxy", suffix); - Bxy = ensuredUnaligned(Bxy); - fillGuards(Bxy); - output_warn.write("\tMaximum difference in Bxy is {:e}\n", max(abs(Bxy - Bcalc))); + Bxy_ = ensuredUnaligned(Bxy_); + fillGuards(Bxy_); + output_warn.write("\tMaximum difference in Bxy is {:e}\n", max(abs(Bxy_ - Bcalc))); } // Check Bxy - bout::checkFinite(Bxy, "Bxy", "RGN_NOCORNERS"); - bout::checkPositive(Bxy, "Bxy", "RGN_NOCORNERS"); + bout::checkFinite(Bxy(), "Bxy" + suffix, "RGN_NOCORNERS"); + bout::checkPositive(Bxy(), "Bxy" + suffix, "RGN_NOCORNERS"); - if (localmesh->get(ShiftTorsion, "ShiftTorsion" + suffix, 0.0, false) != 0) { - output_warn.write( - "\tWARNING: No Torsion specified for zShift. Derivatives may not be correct\n"); - ShiftTorsion = 0.0; + if (not localmesh->sourceHasVar("ShiftTorsion" + suffix)) { + output_warn.write("\tWARNING: No Torsion specified for zShift. " + "Derivatives may not be correct\n"); + } else { + ShiftTorsion_ = readAndFillGuards("ShiftTorsion", 0.0); } - fillGuards(ShiftTorsion); - - ////////////////////////////////////////////////////// if (localmesh->IncIntShear) { - if (localmesh->get(IntShiftTorsion, "IntShiftTorsion", 0.0, false) != 0) { + if (not localmesh->sourceHasVar("IntShiftTorsion" + suffix)) { output_warn.write("\tWARNING: No Integrated torsion specified\n"); + } else { + IntShiftTorsion_ = readAndFillGuards("IntShiftTorsion", 0.0); } - fillGuards(IntShiftTorsion); - } else { - // IntShiftTorsion will not be used, but set to zero to avoid uninitialized field - IntShiftTorsion = 0.; } - // Allow transform to fix things up - transform->loadParallelMetrics(this); } -void Coordinates::outputVars(Options& output_options) { - const Timer time("io"); - const std::string loc_string = - (location == CELL_CENTRE) ? "" : "_" + toString(location); +namespace bout { +// Get a unique name for a field based on the sign/magnitude of the offset +std::string parallelSliceFieldName(std::string_view field, int offset) { + using namespace std::string_view_literals; + const std::string_view direction = (offset > 0) ? "forward"sv : "backward"sv; + // We only have a suffix for parallel slices beyond the first + // This is for backwards compatibility + const std::string slice_suffix = + (std::abs(offset) > 1) ? fmt::format("_{}", std::abs(offset)) : ""; + return fmt::format("{}_{}{}", direction, field, slice_suffix); +}; +} // namespace bout - output_options["dx" + loc_string].force(dx, "Coordinates"); - output_options["dy" + loc_string].force(dy, "Coordinates"); - output_options["dz" + loc_string].force(dz, "Coordinates"); +namespace { +void load_parallel_metric_component(std::string_view name, bout::FieldMetric& component, + int offset) { + Mesh* mesh = component.getMesh(); + bout::FieldMetric tmp{mesh}; + const auto pname = bout::parallelSliceFieldName(name, offset); + if (mesh->get(tmp, pname, 0.0, false) != 0) { + throw BoutException("Could not read {:s} from grid file!\n" + " Fix it up with `zoidberg-update-parallel-metrics `", + pname); + } + if (!component.hasParallelSlices()) { + component.splitParallelSlices(); + component.disallowCalcParallelSlices(); + component.resetRegionParallel(true); + } + auto& pcom = component.ynext(offset); + pcom.allocate(); + BOUT_FOR(i, component.getRegion("RGN_NOBNDRY")) { pcom[i.yp(offset)] = tmp[i]; } +} +} // namespace - output_options["g11" + loc_string].force(g11, "Coordinates"); - output_options["g22" + loc_string].force(g22, "Coordinates"); - output_options["g33" + loc_string].force(g33, "Coordinates"); - output_options["g12" + loc_string].force(g12, "Coordinates"); - output_options["g13" + loc_string].force(g13, "Coordinates"); - output_options["g23" + loc_string].force(g23, "Coordinates"); +void Coordinates::readParallelMetricComponents() { + if (not bout::build::use_metric_3d) { + return; + } + output_info.write("\tLoading parallel metrics\n"); + const FieldMetric JB0 = J() * Bxy(); + jacobian_cache->splitParallelSlices(); + jacobian_cache->disallowCalcParallelSlices(); + jacobian_cache->resetRegionParallel(true); + for (int i = 1; i <= localmesh->ystart; ++i) { + auto read_offset = [i](std::string_view name, FieldMetric& component) { + load_parallel_metric_component(name, component, -i); + load_parallel_metric_component(name, component, i); + }; - output_options["g_11" + loc_string].force(g_11, "Coordinates"); - output_options["g_22" + loc_string].force(g_22, "Coordinates"); - output_options["g_33" + loc_string].force(g_33, "Coordinates"); - output_options["g_12" + loc_string].force(g_12, "Coordinates"); - output_options["g_13" + loc_string].force(g_13, "Coordinates"); - output_options["g_23" + loc_string].force(g_23, "Coordinates"); + read_offset("g_11", covariantMetricTensor.g11_m); + read_offset("g_22", covariantMetricTensor.g22_m); + read_offset("g_33", covariantMetricTensor.g33_m); + read_offset("g_13", covariantMetricTensor.g13_m); + read_offset("g11", contravariantMetricTensor.g11_m); + read_offset("g22", contravariantMetricTensor.g22_m); + read_offset("g33", contravariantMetricTensor.g33_m); + read_offset("g13", contravariantMetricTensor.g13_m); + read_offset("dy", dy_); + read_offset("Bxy", Bxy_); + + jacobian_cache->ynext(i).allocate(); + jacobian_cache->ynext(-i).allocate(); + BOUT_FOR(j, JB0.getRegion("RGN_NOBNDRY")) { + jacobian_cache->ynext(i)[j.yp(i)] = JB0[j] / Bxy_.ynext(i)[j.yp(i)]; + jacobian_cache->ynext(-i)[j.yp(-i)] = JB0[j] / Bxy_.ynext(-i)[j.yp(-i)]; + } + } +} - output_options["J" + loc_string].force(J, "Coordinates"); - output_options["Bxy" + loc_string].force(Bxy, "Coordinates"); +void Coordinates::outputVars(Options& output_options) { + const Timer time("io"); + const std::string loc_string = + (location == CELL_CENTRE) ? "" : "_" + toString(location); - output_options["G1" + loc_string].force(G1, "Coordinates"); - output_options["G2" + loc_string].force(G2, "Coordinates"); - output_options["G3" + loc_string].force(G3, "Coordinates"); + output_options["dx" + loc_string].force(dx(), "Coordinates"); + output_options["dy" + loc_string].force(dy(), "Coordinates"); + output_options["dz" + loc_string].force(dz(), "Coordinates"); + + output_options["g11" + loc_string].force(g11(), "Coordinates"); + output_options["g22" + loc_string].force(g22(), "Coordinates"); + output_options["g33" + loc_string].force(g33(), "Coordinates"); + output_options["g12" + loc_string].force(g12(), "Coordinates"); + output_options["g13" + loc_string].force(g13(), "Coordinates"); + output_options["g23" + loc_string].force(g23(), "Coordinates"); + + output_options["g_11" + loc_string].force(g_11(), "Coordinates"); + output_options["g_22" + loc_string].force(g_22(), "Coordinates"); + output_options["g_33" + loc_string].force(g_33(), "Coordinates"); + output_options["g_12" + loc_string].force(g_12(), "Coordinates"); + output_options["g_13" + loc_string].force(g_13(), "Coordinates"); + output_options["g_23" + loc_string].force(g_23(), "Coordinates"); + + output_options["J" + loc_string].force(J(), "Coordinates"); + output_options["Bxy" + loc_string].force(Bxy(), "Coordinates"); + + if (g_values_cache != nullptr) { + // If we haven't used them yet, then presumably we don't actually need them, + // so let's not compute them now. + // Also, previously, we were explicitly setting the G-values to NaN for FCI + // instead of computing them (presumably because these are both difficult to + // compute accurately for FCI and unneeded in FCI models in practice). + output_options["G1" + loc_string].force(G1(), "Coordinates"); + output_options["G2" + loc_string].force(G2(), "Coordinates"); + output_options["G3" + loc_string].force(G3(), "Coordinates"); + } getParallelTransform().outputVars(output_options); } const Field2D& Coordinates::zlength() const { - BOUT_OMP_SAFE(critical) + BOUT_OMP(critical) if (not zlength_cache) { zlength_cache = std::make_unique(0., localmesh); #if BOUT_USE_METRIC_3D - BOUT_FOR_SERIAL(i, dz.getRegion("RGN_ALL")) { (*zlength_cache)[i] += dz[i]; } + BOUT_FOR_SERIAL(i, dz().getRegion("RGN_ALL")) { (*zlength_cache)[i] += dz()[i]; } #else - (*zlength_cache) = dz * nz; + (*zlength_cache) = dz_ * nz; #endif } return *zlength_cache; } -int Coordinates::geometry(bool recalculate_staggered, - bool force_interpolate_from_centre) { - - localmesh->communicate_no_slices(dx, dy, dz, g11, g22, g33, g12, g13, g23, g_11, g_22, - g_33, g_12, g_13, g_23, J, Bxy); - - output_progress.write("Calculating differential geometry terms\n"); - +void Coordinates::setDx(FieldMetric dx, const bool communicate) { if (min(abs(dx)) < 1e-8) { throw BoutException("dx magnitude less than 1e-8"); } + dx_ = std::move(dx); + invalidateCellGeometryCaches(); + invalidateAccessorCache(); + if (communicate) { + localmesh->communicate_no_slices(dx_); + } +} +void Coordinates::setDy(FieldMetric dy, const bool communicate) { if (min(abs(dy)) < 1e-8) { throw BoutException("dy magnitude less than 1e-8"); } + dy_ = std::move(dy); + invalidateCellGeometryCaches(); + invalidateAccessorCache(); + if (communicate) { + localmesh->communicate_no_slices(dy_); + } +} +void Coordinates::setDz(FieldMetric dz, const bool communicate) { if (min(abs(dz)) < 1e-8) { throw BoutException("dz magnitude less than 1e-8"); } + dz_ = std::move(dz); + zlength_cache.reset(); + invalidateCellGeometryCaches(); + invalidateAccessorCache(); + if (communicate) { + localmesh->communicate_no_slices(dz_); + } +} + +void Coordinates::recalculateAndReset(bool recalculate_staggered, + bool force_interpolate_from_centre) { // Check input metrics checkContravariant(); checkCovariant(); - // Calculate Christoffel symbol terms (18 independent values) - // Note: This calculation is completely general: metric - // tensor can be 2D or 3D. For 2D, all DDZ terms are zero - - if (!g11.isFci()) { - G1_11 = 0.5 * g11 * DDX(g_11) + g12 * (DDX(g_12) - 0.5 * DDY(g_11)) - + g13 * (DDX(g_13) - 0.5 * DDZ(g_11)); - G1_22 = g11 * (DDY(g_12) - 0.5 * DDX(g_22)) + 0.5 * g12 * DDY(g_22) - + g13 * (DDY(g_23) - 0.5 * DDZ(g_22)); - G1_33 = g11 * (DDZ(g_13) - 0.5 * DDX(g_33)) + g12 * (DDZ(g_23) - 0.5 * DDY(g_33)) - + 0.5 * g13 * DDZ(g_33); - G1_12 = 0.5 * g11 * DDY(g_11) + 0.5 * g12 * DDX(g_22) - + 0.5 * g13 * (DDY(g_13) + DDX(g_23) - DDZ(g_12)); - G1_13 = 0.5 * g11 * DDZ(g_11) + 0.5 * g12 * (DDZ(g_12) + DDX(g_23) - DDY(g_13)) - + 0.5 * g13 * DDX(g_33); - G1_23 = 0.5 * g11 * (DDZ(g_12) + DDY(g_13) - DDX(g_23)) - + 0.5 * g12 * (DDZ(g_22) + DDY(g_23) - DDY(g_23)) - // + 0.5 *g13*(DDZ(g_32) + DDY(g_33) - DDZ(g_23)); - // which equals - + 0.5 * g13 * DDY(g_33); - - G2_11 = 0.5 * g12 * DDX(g_11) + g22 * (DDX(g_12) - 0.5 * DDY(g_11)) - + g23 * (DDX(g_13) - 0.5 * DDZ(g_11)); - G2_22 = g12 * (DDY(g_12) - 0.5 * DDX(g_22)) + 0.5 * g22 * DDY(g_22) - + g23 * (DDY(g23) - 0.5 * DDZ(g_22)); - G2_33 = g12 * (DDZ(g_13) - 0.5 * DDX(g_33)) + g22 * (DDZ(g_23) - 0.5 * DDY(g_33)) - + 0.5 * g23 * DDZ(g_33); - G2_12 = 0.5 * g12 * DDY(g_11) + 0.5 * g22 * DDX(g_22) - + 0.5 * g23 * (DDY(g_13) + DDX(g_23) - DDZ(g_12)); - G2_13 = - // 0.5 *g21*(DDZ(g_11) + DDX(g_13) - DDX(g_13)) - // which equals - 0.5 * g12 * (DDZ(g_11) + DDX(g_13) - DDX(g_13)) - // + 0.5 *g22*(DDZ(g_21) + DDX(g_23) - DDY(g_13)) - // which equals - + 0.5 * g22 * (DDZ(g_12) + DDX(g_23) - DDY(g_13)) - // + 0.5 *g23*(DDZ(g_31) + DDX(g_33) - DDZ(g_13)); - // which equals - + 0.5 * g23 * DDX(g_33); - G2_23 = 0.5 * g12 * (DDZ(g_12) + DDY(g_13) - DDX(g_23)) + 0.5 * g22 * DDZ(g_22) - + 0.5 * g23 * DDY(g_33); - - G3_11 = 0.5 * g13 * DDX(g_11) + g23 * (DDX(g_12) - 0.5 * DDY(g_11)) - + g33 * (DDX(g_13) - 0.5 * DDZ(g_11)); - G3_22 = g13 * (DDY(g_12) - 0.5 * DDX(g_22)) + 0.5 * g23 * DDY(g_22) - + g33 * (DDY(g_23) - 0.5 * DDZ(g_22)); - G3_33 = g13 * (DDZ(g_13) - 0.5 * DDX(g_33)) + g23 * (DDZ(g_23) - 0.5 * DDY(g_33)) - + 0.5 * g33 * DDZ(g_33); - G3_12 = - // 0.5 *g31*(DDY(g_11) + DDX(g_12) - DDX(g_12)) - // which equals to - 0.5 * g13 * DDY(g_11) - // + 0.5 *g32*(DDY(g_21) + DDX(g_22) - DDY(g_12)) - // which equals to - + 0.5 * g23 * DDX(g_22) - //+ 0.5 *g33*(DDY(g_31) + DDX(g_32) - DDZ(g_12)); - // which equals to - + 0.5 * g33 * (DDY(g_13) + DDX(g_23) - DDZ(g_12)); - G3_13 = 0.5 * g13 * DDZ(g_11) + 0.5 * g23 * (DDZ(g_12) + DDX(g_23) - DDY(g_13)) - + 0.5 * g33 * DDX(g_33); - G3_23 = 0.5 * g13 * (DDZ(g_12) + DDY(g_13) - DDX(g_23)) + 0.5 * g23 * DDZ(g_22) - + 0.5 * g33 * DDY(g_33); - - G1 = (DDX(J * g11) + DDY(J.asField3DParallel() * g12) + DDZ(J * g13)) / J; - G2 = (DDX(J * g12) + DDY(J.asField3DParallel() * g22) + DDZ(J * g23)) / J; - G3 = (DDX(J * g13) + DDY(J.asField3DParallel() * g23) + DDZ(J * g33)) / J; - - // Communicate christoffel symbol terms - output_progress.write("\tCommunicating connection terms\n"); - - localmesh->communicate_no_slices(G1_11, G1_22, G1_33, G1_12, G1_13, G1_23, G2_11, - G2_22, G2_33, G2_12, G2_13, G2_23, G3_11, G3_22, - G3_33, G3_12, G3_13, G3_23, G1, G2, G3); - - // Set boundary guard cells of Christoffel symbol terms - // Ideally, when location is staggered, we would set the upper/outer boundary point - // correctly rather than by extrapolating here: e.g. if location==CELL_YLOW and we are - // at the upper y-boundary the x- and z-derivatives at yend+1 at the boundary can be - // calculated because the guard cells are available, while the y-derivative could be - // calculated from the CELL_CENTRE metric components (which have guard cells available - // past the boundary location). This would avoid the problem that the y-boundary on the - // CELL_YLOW grid is at a 'guard cell' location (yend+1). - // However, the above would require lots of special handling, so just extrapolate for - // now. - G1_11 = interpolateAndExtrapolate(G1_11, location, true, true, true, transform.get()); - G1_22 = interpolateAndExtrapolate(G1_22, location, true, true, true, transform.get()); - G1_33 = interpolateAndExtrapolate(G1_33, location, true, true, true, transform.get()); - G1_12 = interpolateAndExtrapolate(G1_12, location, true, true, true, transform.get()); - G1_13 = interpolateAndExtrapolate(G1_13, location, true, true, true, transform.get()); - G1_23 = interpolateAndExtrapolate(G1_23, location, true, true, true, transform.get()); - - G2_11 = interpolateAndExtrapolate(G2_11, location, true, true, true, transform.get()); - G2_22 = interpolateAndExtrapolate(G2_22, location, true, true, true, transform.get()); - G2_33 = interpolateAndExtrapolate(G2_33, location, true, true, true, transform.get()); - G2_12 = interpolateAndExtrapolate(G2_12, location, true, true, true, transform.get()); - G2_13 = interpolateAndExtrapolate(G2_13, location, true, true, true, transform.get()); - G2_23 = interpolateAndExtrapolate(G2_23, location, true, true, true, transform.get()); - - G3_11 = interpolateAndExtrapolate(G3_11, location, true, true, true, transform.get()); - G3_22 = interpolateAndExtrapolate(G3_22, location, true, true, true, transform.get()); - G3_33 = interpolateAndExtrapolate(G3_33, location, true, true, true, transform.get()); - G3_12 = interpolateAndExtrapolate(G3_12, location, true, true, true, transform.get()); - G3_13 = interpolateAndExtrapolate(G3_13, location, true, true, true, transform.get()); - G3_23 = interpolateAndExtrapolate(G3_23, location, true, true, true, transform.get()); - - G1 = interpolateAndExtrapolate(G1, location, true, true, true, transform.get()); - G2 = interpolateAndExtrapolate(G2, location, true, true, true, transform.get()); - G3 = interpolateAndExtrapolate(G3, location, true, true, true, transform.get()); - } else { - G1_11 = G1_22 = G1_33 = G1_12 = G1_13 = G1_23 = - - G2_11 = G2_22 = G2_33 = G2_12 = G2_13 = G2_23 = + christoffel_symbols_cache.reset(); + g_values_cache.reset(); - G3_11 = G3_22 = G3_33 = G3_12 = G3_13 = G3_23 = + correctionForNonUniformMeshes(force_interpolate_from_centre); - G1 = G2 = G3 = BoutNaN; + if (location == CELL_CENTRE && recalculate_staggered) { + // Re-calculate interpolated Coordinates at staggered locations + localmesh->recalculateStaggeredCoordinates(); } - ////////////////////////////////////////////////////// - /// Non-uniform meshes. Need to use DDX, DDY + zlength_cache.reset(); + Grad2_par2_DDY_invSgCache.clear(); + invSgCache.reset(); + invalidateCellGeometryCaches(); + invalidateAccessorCache(); +} - OPTION(Options::getRoot(), non_uniform, true); +void Coordinates::correctionForNonUniformMeshes(bool force_interpolate_from_centre) { + OPTION(Options::getRoot(), non_uniform_, true); - Coordinates::FieldMetric d2x(localmesh), d2y(localmesh), - d2z(localmesh); // d^2 x / d i^2 + FieldMetric d2x(localmesh); + FieldMetric d2y(localmesh); // Read correction for non-uniform meshes - std::string suffix = getLocationSuffix(location); + const std::string suffix = getLocationSuffix(location); + + auto extrapolate_x = true; + auto extrapolate_y = true; if (location == CELL_CENTRE or (!force_interpolate_from_centre and localmesh->sourceHasVar("dx" + suffix))) { - bool extrapolate_x = not localmesh->sourceHasXBoundaryGuards(); - bool extrapolate_y = not localmesh->sourceHasYBoundaryGuards(); + extrapolate_x = not localmesh->sourceHasXBoundaryGuards(); + extrapolate_y = not localmesh->sourceHasYBoundaryGuards(); + } - if (localmesh->get(d2x, "d2x" + suffix, 0.0, false, location)) { - output_warn.write( - "\tWARNING: differencing quantity 'd2x' not found. Calculating from dx\n"); - d1_dx = bout::derivatives::index::DDX(FieldMetric{1. / dx}); // d/di(1/dx) + if (localmesh->get(d2x, "d2x" + suffix, 0.0, false, location) != 0) { + output_warn.write("\tWARNING: differencing quantity 'd2x' not found. " + "Calculating from dx\n"); + d1_dx_ = bout::derivatives::index::DDX(FieldMetric{1. / dx()}); // d/di(1/dx) - localmesh->communicate_no_slices(d1_dx); - d1_dx = - interpolateAndExtrapolate(d1_dx, location, true, true, true, transform.get()); - } else { - d2x.setLocation(location); - // set boundary cells if necessary - d2x = interpolateAndExtrapolate(d2x, location, extrapolate_x, extrapolate_y, false, - transform.get()); + localmesh->communicate_no_slices(d1_dx_); + d1_dx_ = + interpolateAndExtrapolate(d1_dx_, location, true, true, true, transform.get()); + } else { + d2x.setLocation(location); + // set boundary cells if necessary + d2x = interpolateAndExtrapolate(d2x, location, extrapolate_x, extrapolate_y, false, + transform.get()); - d1_dx = -d2x / (dx * dx); - } + d1_dx_ = -d2x / (dx() * dx()); + } - if (localmesh->get(d2y, "d2y" + suffix, 0.0, false, location)) { - output_warn.write( - "\tWARNING: differencing quantity 'd2y' not found. Calculating from dy\n"); - d1_dy = DDY(1. / dy.asField3DParallel()); // d/di(1/dy) + if (localmesh->get(d2y, "d2y" + suffix, 0.0, false, location) != 0) { + output_warn.write("\tWARNING: differencing quantity 'd2y' not found. " + "Calculating from dy\n"); + d1_dy_ = DDY(1. / dy()); // d/di(1/dy) - localmesh->communicate_no_slices(d1_dy); - d1_dy = - interpolateAndExtrapolate(d1_dy, location, true, true, true, transform.get()); - } else { - d2y.setLocation(location); - // set boundary cells if necessary - d2y = interpolateAndExtrapolate(d2y, location, extrapolate_x, extrapolate_y, false, - transform.get()); + localmesh->communicate_no_slices(d1_dy_); + d1_dy_ = + interpolateAndExtrapolate(d1_dy_, location, true, true, true, transform.get()); + } else { + d2y.setLocation(location); + // set boundary cells if necessary + d2y = interpolateAndExtrapolate(d2y, location, extrapolate_x, extrapolate_y, false, + transform.get()); - d1_dy = -d2y / (dy * dy); - } + d1_dy_ = -d2y / (dy() * dy()); + } -#if BOUT_USE_METRIC_3D - if (localmesh->get(d2z, "d2z" + suffix, 0.0, false)) { - output_warn.write( - "\tWARNING: differencing quantity 'd2z' not found. Calculating from dz\n"); - d1_dz = bout::derivatives::index::DDZ(FieldMetric{1. / dz}); - localmesh->communicate_no_slices(d1_dz); - d1_dz = - interpolateAndExtrapolate(d1_dz, location, true, true, true, transform.get()); + if (bout::build::use_metric_3d) { + FieldMetric d2z(localmesh); // d^2 x / d i^2 + if (localmesh->get(d2z, "d2z" + suffix, 0.0, false, location) != 0) { + output_warn.write("\tWARNING: differencing quantity 'd2z' not found. " + "Calculating from dz\n"); + d1_dz_ = bout::derivatives::index::DDZ(FieldMetric{1. / dz()}); + localmesh->communicate_no_slices(d1_dz_); + d1_dz_ = + interpolateAndExtrapolate(d1_dz_, location, true, true, true, transform.get()); } else { d2z.setLocation(location); // set boundary cells if necessary d2z = interpolateAndExtrapolate(d2z, location, extrapolate_x, extrapolate_y, false, transform.get()); - d1_dz = -d2z / (dz * dz); + d1_dz_ = -d2z / (dz() * dz()); } -#else - d1_dz = 0; -#endif } else { - if (localmesh->get(d2x, "d2x", 0.0, false)) { - output_warn.write( - "\tWARNING: differencing quantity 'd2x' not found. Calculating from dx\n"); - d1_dx = bout::derivatives::index::DDX(FieldMetric{1. / dx}); // d/di(1/dx) - - localmesh->communicate_no_slices(d1_dx); - d1_dx = - interpolateAndExtrapolate(d1_dx, location, true, true, true, transform.get()); - } else { - // Shift d2x to our location - d2x = interpolateAndExtrapolate(d2x, location, true, true, false, transform.get()); - - d1_dx = -d2x / (dx * dx); - } - - if (localmesh->get(d2y, "d2y", 0.0, false)) { - output_warn.write( - "\tWARNING: differencing quantity 'd2y' not found. Calculating from dy\n"); - d1_dy = DDY(FieldMetric{1. / dy}); // d/di(1/dy) - - localmesh->communicate_no_slices(d1_dy); - d1_dy = - interpolateAndExtrapolate(d1_dy, location, true, true, true, transform.get()); - } else { - // Shift d2y to our location - d2y = interpolateAndExtrapolate(d2y, location, true, true, false, transform.get()); - - d1_dy = -d2y / (dy * dy); - } - -#if BOUT_USE_METRIC_3D - if (localmesh->get(d2z, "d2z", 0.0, false)) { - output_warn.write( - "\tWARNING: differencing quantity 'd2z' not found. Calculating from dz\n"); - d1_dz = bout::derivatives::index::DDZ(FieldMetric{1. / dz}); - - localmesh->communicate_no_slices(d1_dz); - d1_dz = - interpolateAndExtrapolate(d1_dz, location, true, true, true, transform.get()); - } else { - // Shift d2z to our location - d2z = interpolateAndExtrapolate(d2z, location, true, true, false, transform.get()); - - d1_dz = -d2z / (dz * dz); - } -#else - d1_dz = 0; -#endif - } - localmesh->communicate_no_slices(d1_dx, d1_dy, d1_dz); - - if (location == CELL_CENTRE && recalculate_staggered) { - // Re-calculate interpolated Coordinates at staggered locations - localmesh->recalculateStaggeredCoordinates(); + d1_dz_ = 0; } - // Invalidate and recalculate cached variables and any accessor - zlength_cache.reset(); - Grad2_par2_DDY_invSgCache.clear(); - invSgCache.reset(); - CoordinatesAccessor::clear(this); - - _g_22_ylow.reset(); - _g_22_yhigh.reset(); - _cell_area_xlow.reset(); - _cell_area_xhigh.reset(); - _cell_area_ylow.reset(); - _cell_area_yhigh.reset(); - _cell_area_zlow.reset(); - _cell_area_zhigh.reset(); - _cell_volume.reset(); - - return 0; + localmesh->communicate_no_slices(d1_dx_, d1_dy_, d1_dz_); } -int Coordinates::calcCovariant(const std::string& region) { - - // Make sure metric elements are allocated - g_11.allocate(); - g_22.allocate(); - g_33.allocate(); - g_12.allocate(); - g_13.allocate(); - g_23.allocate(); - - g_11.setLocation(location); - g_22.setLocation(location); - g_33.setLocation(location); - g_12.setLocation(location); - g_13.setLocation(location); - g_23.setLocation(location); - - // Perform inversion of g^{ij} to get g_{ij} - // NOTE: Currently this bit assumes that metric terms are Field2D objects - - auto a = Matrix(3, 3); - - BOUT_FOR_SERIAL(i, g11.getRegion(region)) { - a(0, 0) = g11[i]; - a(1, 1) = g22[i]; - a(2, 2) = g33[i]; - - a(0, 1) = a(1, 0) = g12[i]; - a(1, 2) = a(2, 1) = g23[i]; - a(0, 2) = a(2, 0) = g13[i]; - - if (const auto det = bout::invert3x3(a); det.has_value()) { - output_error.write("\tERROR: metric tensor is singular at {}, determinant: {:e}\n", - i, det.value()); - return 1; - } - - g_11[i] = a(0, 0); - g_22[i] = a(1, 1); - g_33[i] = a(2, 2); - - g_12[i] = a(0, 1); - g_13[i] = a(0, 2); - g_23[i] = a(1, 2); - } - - BoutReal maxerr; - maxerr = BOUTMAX(max(abs((g_11 * g11 + g_12 * g12 + g_13 * g13) - 1)), - max(abs((g_12 * g12 + g_22 * g22 + g_23 * g23) - 1)), - max(abs((g_13 * g13 + g_23 * g23 + g_33 * g33) - 1))); - - output_info.write("\tLocal maximum error in diagonal inversion is {:e}\n", maxerr); - - maxerr = BOUTMAX(max(abs(g_11 * g12 + g_12 * g22 + g_13 * g23)), - max(abs(g_11 * g13 + g_12 * g23 + g_13 * g33)), - max(abs(g_12 * g13 + g_22 * g23 + g_23 * g33))); - - output_info.write("\tLocal maximum error in off-diagonal inversion is {:e}\n", maxerr); - - return 0; -} - -int Coordinates::calcContravariant(const std::string& region) { - - // Make sure metric elements are allocated - g11.allocate(); - g22.allocate(); - g33.allocate(); - g12.allocate(); - g13.allocate(); - g23.allocate(); - - // Perform inversion of g_{ij} to get g^{ij} - // NOTE: Currently this bit assumes that metric terms are Field2D objects - - auto a = Matrix(3, 3); - - BOUT_FOR_SERIAL(i, g_11.getRegion(region)) { - a(0, 0) = g_11[i]; - a(1, 1) = g_22[i]; - a(2, 2) = g_33[i]; - - a(0, 1) = a(1, 0) = g_12[i]; - a(1, 2) = a(2, 1) = g_23[i]; - a(0, 2) = a(2, 0) = g_13[i]; - - if (const auto det = bout::invert3x3(a); det.has_value()) { - output_error.write("\tERROR: metric tensor is singular at {}, determinant: {:e}\n", - i, det.value()); - return 1; - } - - g11[i] = a(0, 0); - g22[i] = a(1, 1); - g33[i] = a(2, 2); - - g12[i] = a(0, 1); - g13[i] = a(0, 2); - g23[i] = a(1, 2); - } - - BoutReal maxerr; - maxerr = BOUTMAX(max(abs((g_11 * g11 + g_12 * g12 + g_13 * g13) - 1)), - max(abs((g_12 * g12 + g_22 * g22 + g_23 * g23) - 1)), - max(abs((g_13 * g13 + g_23 * g23 + g_33 * g33) - 1))); - - output_info.write("\tMaximum error in diagonal inversion is {:e}\n", maxerr); - - maxerr = BOUTMAX(max(abs(g_11 * g12 + g_12 * g22 + g_13 * g23)), - max(abs(g_11 * g13 + g_12 * g23 + g_13 * g33)), - max(abs(g_12 * g13 + g_22 * g23 + g_23 * g33))); - - output_info.write("\tMaximum error in off-diagonal inversion is {:e}\n", maxerr); - return 0; -} - -int Coordinates::jacobian() { - +Coordinates::FieldMetric Coordinates::recalculateJacobian() const { // calculate Jacobian using g^-1 = det[g^ij], J = sqrt(g) + const FieldMetric g_matrix = g11() * g22() * g33() + 2.0 * g12() * g13() * g23() + - g11() * g23() * g23() - g22() * g13() * g13() + - g33() * g12() * g12(); - const bool extrapolate_x = not localmesh->sourceHasXBoundaryGuards(); - const bool extrapolate_y = not localmesh->sourceHasYBoundaryGuards(); - - const FieldMetric g = g11 * g22 * g33 + 2.0 * g12 * g13 * g23 - g11 * g23 * g23 - - g22 * g13 * g13 - g33 * g12 * g12; - - // Check that g is positive - bout::checkPositive(g, "The determinant of g^ij", "RGN_NOBNDRY"); + bout::checkPositive(g_matrix, "The determinant of g^ij", "RGN_NOBNDRY"); - J = 1. / sqrt(g); - // More robust to extrapolate derived quantities directly, rather than - // deriving from extrapolated covariant metric components - J = interpolateAndExtrapolate(J, location, extrapolate_x, extrapolate_y, false, - transform.get()); - - Bxy = sqrt(g_22) / J; - Bxy = interpolateAndExtrapolate(Bxy, location, extrapolate_x, extrapolate_y, false, - transform.get()); + return 1. / sqrt(g_matrix); +} - return 0; +Coordinates::FieldMetric Coordinates::recalculateBxy() const { + ASSERT2(not J().isFci()); + return sqrt(g_22()) / J(); } namespace { // Utility function for fixing up guard cells of zShift void fixZShiftGuards(Field2D& zShift) { - auto localmesh = zShift.getMesh(); + auto* localmesh = zShift.getMesh(); // extrapolate into boundary guard cells if necessary zShift = interpolateAndExtrapolate(zShift, zShift.getLocation(), @@ -1174,548 +930,149 @@ void Coordinates::setParallelTransform(Options* options) { // Flux Coordinate Independent method const bool fci_zperiodic = (*ptoptions)["z_periodic"].withDefault(true); - transform = - bout::utils::make_unique(*localmesh, dy, fci_zperiodic, ptoptions); - + transform = bout::utils::make_unique(*localmesh, dy(), fci_zperiodic, + ptoptions); } else { - throw BoutException(_("Unrecognised paralleltransform option.\n" - "Valid choices are 'identity', 'shifted', 'fci'")); + throw BoutException(_f("Unrecognised paralleltransform option '{}'.\n" + "Valid choices are 'identity', 'shifted', 'fci'"), + ptstr); } } -/******************************************************************************* - * Operators - * - *******************************************************************************/ - -Coordinates::FieldMetric Coordinates::DDX(const Field2D& f, CELL_LOC loc, - const std::string& method, - const std::string& region) { - ASSERT1(location == loc || loc == CELL_DEFAULT); - return bout::derivatives::index::DDX(f, loc, method, region) / dx; -} -Field3D Coordinates::DDX(const Field3D& f, CELL_LOC outloc, const std::string& method, - const std::string& region) { - - auto result = bout::derivatives::index::DDX(f, outloc, method, region); - result /= dx; - - if (f.getMesh()->IncIntShear) { - // Using BOUT-06 style shifting - result += IntShiftTorsion * DDZ(f, outloc, method, region); +const ChristoffelSymbols& Coordinates::christoffel_symbols() const { + BOUT_OMP_SAFE(critical(christoffel_symbols_cache)) + { + if (christoffel_symbols_cache == nullptr) { + christoffel_symbols_cache = std::make_unique(*this); + // Set boundary guard cells of Christoffel symbol terms + // Ideally, when location is staggered, we would set the upper/outer boundary point + // correctly rather than by extrapolating here: e.g. if location==CELL_YLOW and we are + // at the upper y-boundary the x- and z-derivatives at yend+1 at the boundary can be + // calculated because the guard cells are available, while the y-derivative could be + // calculated from the CELL_CENTRE metric components (which have guard cells available + // past the boundary location). This would avoid the problem that the y-boundary on the + // CELL_YLOW grid is at a 'guard cell' location (yend+1). + // However, the above would require lots of special handling, so just extrapolate for + // now. + + christoffel_symbols_cache->map([this](const FieldMetric& component) { + return interpolateAndExtrapolate(component, location, true, true, false, + transform.get()); + }); + } } - - return result; -}; - -Coordinates::FieldMetric Coordinates::DDY(const Field2D& f, CELL_LOC loc, - const std::string& method, - const std::string& region) const { - ASSERT1(location == loc || loc == CELL_DEFAULT); - return bout::derivatives::index::DDY(f, loc, method, region) / dy; + return *christoffel_symbols_cache; } -Field3D Coordinates::DDY(const Field3DParallel& f, CELL_LOC outloc, - const std::string& method, const std::string& region) const { - return bout::derivatives::index::DDY(f, outloc, method, region) / dy; -}; - -Coordinates::FieldMetric Coordinates::DDZ(const Field2D& f, CELL_LOC loc, - const std::string& UNUSED(method), - const std::string& UNUSED(region)) { - ASSERT1(location == loc || loc == CELL_DEFAULT); - ASSERT1(f.getMesh() == localmesh); - if (loc == CELL_DEFAULT) { - loc = f.getLocation(); +GValues& Coordinates::g_values() const { + BOUT_OMP_SAFE(critical(g_values_cache)) + { + if (g_values_cache == nullptr) { + g_values_cache = std::make_unique(*this); + g_values_cache->map([this](const FieldMetric& component) { + return interpolateAndExtrapolate(component, location, true, true, true, + transform.get()); + }); + } } - return zeroFrom(f).setLocation(loc); -} -Field3D Coordinates::DDZ(const Field3D& f, CELL_LOC outloc, const std::string& method, - const std::string& region) { - return bout::derivatives::index::DDZ(f, outloc, method, region) / dz; -}; - -///////////////////////////////////////////////////////// -// Parallel gradient - -Coordinates::FieldMetric Coordinates::Grad_par(const Field2D& var, - [[maybe_unused]] CELL_LOC outloc, - const std::string& UNUSED(method)) { - - ASSERT1(location == outloc - || (outloc == CELL_DEFAULT && location == var.getLocation())); - - return DDY(var) * invSg(); -} - -Field3D Coordinates::Grad_par(const Field3DParallel& var, CELL_LOC outloc, - const std::string& method) { - - ASSERT1(location == outloc || outloc == CELL_DEFAULT); - - return ::DDY(var, outloc, method) * invSg(); -} - -///////////////////////////////////////////////////////// -// Vpar_Grad_par -// vparallel times the parallel derivative along unperturbed B-field - -Coordinates::FieldMetric Coordinates::Vpar_Grad_par(const Field2D& v, const Field2D& f, - [[maybe_unused]] CELL_LOC outloc, - const std::string& UNUSED(method)) { - ASSERT1(location == outloc || (outloc == CELL_DEFAULT && location == f.getLocation())); - - return VDDY(v, f) * invSg(); -} - -Field3D Coordinates::Vpar_Grad_par(const Field3D& v, const Field3DParallel& f, - CELL_LOC outloc, const std::string& method) { - ASSERT1(location == outloc || outloc == CELL_DEFAULT); - - return VDDY(v, f, outloc, method) * invSg(); + return *g_values_cache; } -///////////////////////////////////////////////////////// -// Parallel divergence - -Coordinates::FieldMetric Coordinates::Div_par(const Field2D& f, CELL_LOC outloc, - const std::string& method) { - - ASSERT1(location == outloc || outloc == CELL_DEFAULT); - - // Need Bxy at location of f, which might be different from location of this - // Coordinates object - auto Bxy_floc = f.getCoordinates()->Bxy; - - return Bxy * Grad_par(FieldMetric{f / Bxy_floc}, outloc, method); -} - -Field3D Coordinates::Div_par(const Field3DParallel& f, CELL_LOC outloc, - const std::string& method) { - - ASSERT1(location == outloc || outloc == CELL_DEFAULT); - - // Need Bxy at location of f, which might be different from location of this - // Coordinates object - const auto& Bxy_floc = f.getCoordinates()->Bxy; - - return Bxy * Grad_par(f / Bxy_floc, outloc, method); -} - -///////////////////////////////////////////////////////// -// second parallel derivative (b dot Grad)(b dot Grad) -// Note: For parallel Laplacian use Laplace_par - -Coordinates::FieldMetric Coordinates::Grad2_par2(const Field2D& f, CELL_LOC outloc, - const std::string& method) { - - ASSERT1(location == outloc || (outloc == CELL_DEFAULT && location == f.getLocation())); - - auto result = Grad2_par2_DDY_invSg(outloc, method) * DDY(f, outloc, method) - + D2DY2(f, outloc, method) / g_22; - - return result; -} - -Field3D Coordinates::Grad2_par2(const Field3DParallel& f, CELL_LOC outloc, - const std::string& method) { - - if (outloc == CELL_DEFAULT) { - outloc = f.getLocation(); +const Coordinates::FieldMetric& Coordinates::invSg() const { + BOUT_OMP_SAFE(critical(invSg_cache)) + { + if (invSgCache == nullptr) { + auto ptr = std::make_unique(); + (*ptr) = 1.0 / sqrt(g_22()); + invSgCache = std::move(ptr); + } } - ASSERT1(location == outloc); - - Field3D result = ::DDY(f, outloc, method); - - Field3D r2 = D2DY2(f, outloc, method) / g_22; - - result = Grad2_par2_DDY_invSg(outloc, method) * result + r2; - - ASSERT2(result.getLocation() == outloc); - - return result; -} - -///////////////////////////////////////////////////////// -// perpendicular Laplacian operator - -#include // Delp2 uses same coefficients as inversion code - -Coordinates::FieldMetric Coordinates::Delp2(const Field2D& f, CELL_LOC outloc, - bool UNUSED(useFFT)) { - - ASSERT1(location == outloc || outloc == CELL_DEFAULT); - - auto result = G1 * DDX(f, outloc) + g11 * D2DX2(f, outloc); - - return result; + return *invSgCache; } -Field3D Coordinates::Delp2(const Field3D& f, CELL_LOC outloc, bool useFFT) { - - if (outloc == CELL_DEFAULT) { - outloc = f.getLocation(); - } - - ASSERT1(location == outloc); - ASSERT1(f.getLocation() == outloc); - - if (localmesh->GlobalNx == 1 && localmesh->GlobalNz == 1) { - // copy mesh, location, etc - return f * 0; - } - ASSERT2(localmesh->xstart > 0); // Need at least one guard cell - - Field3D result{emptyFrom(f).setLocation(outloc)}; - - if (useFFT and not bout::build::use_metric_3d and localmesh->getNZPE() == 1) { - int ncz = localmesh->LocalNz; - - // Allocate memory - auto ft = Matrix(localmesh->LocalNx, ncz / 2 + 1); - auto delft = Matrix(localmesh->LocalNx, ncz / 2 + 1); - - // Loop over y indices - // Note: should not include y-guard or y-boundary points here as that would - // use values from corner cells in dx, which may not be initialised. - for (int jy = localmesh->ystart; jy <= localmesh->yend; jy++) { - - // Take forward FFT - - for (int jx = 0; jx < localmesh->LocalNx; jx++) { - rfft(&f(jx, jy, 0), ncz, &ft(jx, 0)); - } - - // Loop over kz - for (int jz = 0; jz <= ncz / 2; jz++) { - - // No smoothing in the x direction - for (int jx = localmesh->xstart; jx <= localmesh->xend; jx++) { - // Perform x derivative - - dcomplex a, b, c; - laplace_tridag_coefs(jx, jy, jz, a, b, c, nullptr, nullptr, outloc); - - delft(jx, jz) = a * ft(jx - 1, jz) + b * ft(jx, jz) + c * ft(jx + 1, jz); - } +const Coordinates::FieldMetric& +Coordinates::Grad2_par2_DDY_invSg(CELL_LOC outloc, const std::string& method) const { + const FieldMetric* result{nullptr}; + BOUT_OMP_SAFE(critical(Grad2_par2_DDY_invSg_cache)) + { + if (auto search = Grad2_par2_DDY_invSgCache.find(method); + search != Grad2_par2_DDY_invSgCache.end()) { + result = search->second.get(); + } else { + if (invSgCache == nullptr) { + auto ptr = std::make_unique(); + (*ptr) = 1.0 / sqrt(g_22()); + invSgCache = std::move(ptr); } - // Reverse FFT - for (int jx = localmesh->xstart; jx <= localmesh->xend; jx++) { + // Communicate to get parallel slices + localmesh->communicate(*invSgCache); + invSgCache->applyParallelBoundary("parallel_neumann_o2"); - irfft(&delft(jx, 0), ncz, &result(jx, jy, 0)); - } + auto ptr = std::make_unique(); + *ptr = DDY(*invSgCache, outloc, method) * (*invSgCache); + result = ptr.get(); + Grad2_par2_DDY_invSgCache[method] = std::move(ptr); } - } else { - result = G1 * ::DDX(f, outloc) + G3 * ::DDZ(f, outloc) + g11 * ::D2DX2(f, outloc) - + g33 * ::D2DZ2(f, outloc) + 2 * g13 * ::D2DXDZ(f, outloc); - }; - - ASSERT2(result.getLocation() == outloc); - - return result; -} - -FieldPerp Coordinates::Delp2(const FieldPerp& f, CELL_LOC outloc, bool useFFT) { - - if (outloc == CELL_DEFAULT) { - outloc = f.getLocation(); - } - - ASSERT1(location == outloc); - ASSERT1(f.getLocation() == outloc); - - if (localmesh->GlobalNx == 1 && localmesh->GlobalNz == 1) { - // copy mesh, location, etc - return f * 0; } - ASSERT2(localmesh->xstart > 0); // Need at least one guard cell - - FieldPerp result{emptyFrom(f).setLocation(outloc)}; - - int jy = f.getIndex(); - result.setIndex(jy); - - if (useFFT and localmesh->getNZPE() == 1) { - int ncz = localmesh->LocalNz; - - // Allocate memory - auto ft = Matrix(localmesh->LocalNx, ncz / 2 + 1); - auto delft = Matrix(localmesh->LocalNx, ncz / 2 + 1); - - // Take forward FFT - for (int jx = 0; jx < localmesh->LocalNx; jx++) { - rfft(&f(jx, 0), ncz, &ft(jx, 0)); - } - - // Loop over kz - for (int jz = 0; jz <= ncz / 2; jz++) { - - // No smoothing in the x direction - for (int jx = localmesh->xstart; jx <= localmesh->xend; jx++) { - // Perform x derivative - - dcomplex a, b, c; - laplace_tridag_coefs(jx, jy, jz, a, b, c); - - delft(jx, jz) = a * ft(jx - 1, jz) + b * ft(jx, jz) + c * ft(jx + 1, jz); - } - } - - // Reverse FFT - for (int jx = localmesh->xstart; jx <= localmesh->xend; jx++) { - irfft(&delft(jx, 0), ncz, &result(jx, 0)); - } - - } else { - throw BoutException("Non-fourier Delp2 not currently implented for FieldPerp."); - // Would be the following but don't have standard derivative operators for FieldPerps - // yet - // result = G1 * ::DDX(f, outloc) + G3 * ::DDZ(f, outloc) + g11 * ::D2DX2(f, outloc) - // + g33 * ::D2DZ2(f, outloc) + 2 * g13 * ::D2DXDZ(f, outloc); - }; - - return result; -} - -Coordinates::FieldMetric Coordinates::Laplace_par(const Field2D& f, CELL_LOC outloc) { - ASSERT1(location == outloc || outloc == CELL_DEFAULT); - return D2DY2(f, outloc) / g_22 - + DDY(FieldMetric{J / g_22}, outloc) * DDY(f, outloc) / J; -} - -Field3D Coordinates::Laplace_par(const Field3DParallel& f, CELL_LOC outloc) { - ASSERT1(location == outloc || outloc == CELL_DEFAULT); - return D2DY2(f, outloc) / g_22 - + DDY(J.asField3DParallel() / g_22, outloc) * ::DDY(f, outloc) / J; + return *result; } -// Full Laplacian operator on scalar field +void Coordinates::checkCovariant() { covariantMetricTensor.check(localmesh->ystart); } -Coordinates::FieldMetric Coordinates::Laplace(const Field2D& f, CELL_LOC outloc, - const std::string& dfdy_boundary_conditions, - const std::string& dfdy_dy_region) { - - ASSERT1(location == outloc || outloc == CELL_DEFAULT); - - auto result = G1 * DDX(f, outloc) + G2 * DDY(f, outloc) + g11 * D2DX2(f, outloc) - + g22 * D2DY2(f, outloc) - + 2.0 * g12 - * D2DXDY(f, outloc, "DEFAULT", "RGN_NOBNDRY", - dfdy_boundary_conditions, dfdy_dy_region); - - return result; +void Coordinates::checkContravariant() { + contravariantMetricTensor.check(localmesh->ystart); } -Field3D Coordinates::Laplace(const Field3DParallel& f, CELL_LOC outloc, - const std::string& dfdy_boundary_conditions, - const std::string& dfdy_dy_region) { - - ASSERT1(location == outloc || outloc == CELL_DEFAULT); - - Field3D result = G1 * ::DDX(f, outloc) + G2 * ::DDY(f, outloc) + G3 * ::DDZ(f, outloc) - + g11 * D2DX2(f, outloc) + g22 * D2DY2(f, outloc) - + g33 * D2DZ2(f, outloc) - + 2.0 - * (g12 - * D2DXDY(f, outloc, "DEFAULT", "RGN_NOBNDRY", - dfdy_boundary_conditions, dfdy_dy_region) - + g13 * D2DXDZ(f, outloc) + g23 * D2DYDZ(f, outloc)); - - return result; +void Coordinates::invalidateCellGeometryCaches() { + _g_22_ylow.reset(); + _g_22_yhigh.reset(); + _cell_area_xlow.reset(); + _cell_area_xhigh.reset(); + _cell_area_ylow.reset(); + _cell_area_yhigh.reset(); + _cell_area_zlow.reset(); + _cell_area_zhigh.reset(); + _cell_volume.reset(); } -// Full perpendicular Laplacian, in form of inverse of Laplacian operator in LaplaceXY -// solver -Field2D Coordinates::Laplace_perpXY([[maybe_unused]] const Field2D& A, - [[maybe_unused]] const Field2D& f) { +void Coordinates::invalidateAccessorCache() const { CoordinatesAccessor::clear(this); } -#if not(BOUT_USE_METRIC_3D) - Field2D result; - result.allocate(); - for (auto i : result.getRegion(RGN_NOBNDRY)) { - result[i] = 0.; - - // outer x boundary - const auto outer_x_avg = [&i](const auto& f) { return 0.5 * (f[i] + f[i.xp()]); }; - const BoutReal outer_x_A = outer_x_avg(A); - const BoutReal outer_x_J = outer_x_avg(J); - const BoutReal outer_x_g11 = outer_x_avg(g11); - const BoutReal outer_x_dx = outer_x_avg(dx); - const BoutReal outer_x_value = - outer_x_A * outer_x_J * outer_x_g11 / (J[i] * outer_x_dx * dx[i]); - result[i] += outer_x_value * (f[i.xp()] - f[i]); - - // inner x boundary - const auto inner_x_avg = [&i](const auto& f) { return 0.5 * (f[i] + f[i.xm()]); }; - const BoutReal inner_x_A = inner_x_avg(A); - const BoutReal inner_x_J = inner_x_avg(J); - const BoutReal inner_x_g11 = inner_x_avg(g11); - const BoutReal inner_x_dx = inner_x_avg(dx); - const BoutReal inner_x_value = - inner_x_A * inner_x_J * inner_x_g11 / (J[i] * inner_x_dx * dx[i]); - result[i] += inner_x_value * (f[i.xm()] - f[i]); - - // upper y boundary - const auto upper_y_avg = [&i](const auto& f) { return 0.5 * (f[i] + f[i.yp()]); }; - const BoutReal upper_y_A = upper_y_avg(A); - const BoutReal upper_y_J = upper_y_avg(J); - const BoutReal upper_y_g_22 = upper_y_avg(g_22); - const BoutReal upper_y_g23 = upper_y_avg(g23); - const BoutReal upper_y_g_23 = upper_y_avg(g_23); - const BoutReal upper_y_dy = upper_y_avg(dy); - const BoutReal upper_y_value = -upper_y_A * upper_y_J * upper_y_g23 * upper_y_g_23 - / (upper_y_g_22 * J[i] * upper_y_dy * dy[i]); - result[i] += upper_y_value * (f[i.yp()] - f[i]); - - // lower y boundary - const auto lower_y_avg = [&i](const auto& f) { return 0.5 * (f[i] + f[i.ym()]); }; - const BoutReal lower_y_A = lower_y_avg(A); - const BoutReal lower_y_J = lower_y_avg(J); - const BoutReal lower_y_g_22 = lower_y_avg(g_22); - const BoutReal lower_y_g23 = lower_y_avg(g23); - const BoutReal lower_y_g_23 = lower_y_avg(g_23); - const BoutReal lower_y_dy = lower_y_avg(dy); - const BoutReal lower_y_value = -lower_y_A * lower_y_J * lower_y_g23 * lower_y_g_23 - / (lower_y_g_22 * J[i] * lower_y_dy * dy[i]); - result[i] += lower_y_value * (f[i.ym()] - f[i]); - } - - return result; -#else - throw BoutException("Coordinates::Laplace_perpXY for 3D metric not implemented"); -#endif +void Coordinates::invalidateJacobianCaches() { + g_values_cache.reset(); + invalidateCellGeometryCaches(); + invalidateAccessorCache(); } -const Coordinates::FieldMetric& Coordinates::invSg() const { - if (invSgCache == nullptr) { - auto ptr = std::make_unique(); - (*ptr) = 1.0 / sqrt(g_22); - invSgCache = std::move(ptr); - } - return *invSgCache; -} - -const Coordinates::FieldMetric& -Coordinates::Grad2_par2_DDY_invSg(CELL_LOC outloc, const std::string& method) const { - if (auto search = Grad2_par2_DDY_invSgCache.find(method); - search != Grad2_par2_DDY_invSgCache.end()) { - return *search->second; - } - invSg(); - - // Communicate to get parallel slices - localmesh->communicate(*invSgCache); - invSgCache->applyParallelBoundary("parallel_neumann_o2"); - - // cache - auto ptr = std::make_unique(); - *ptr = DDY(*invSgCache, outloc, method) * invSg(); - Grad2_par2_DDY_invSgCache[method] = std::move(ptr); - return *Grad2_par2_DDY_invSgCache[method]; +void Coordinates::invalidateMetricCaches() { + christoffel_symbols_cache.reset(); + g_values_cache.reset(); + Grad2_par2_DDY_invSgCache.clear(); + invSgCache.reset(); + jacobian_cache.reset(); + invalidateCellGeometryCaches(); + invalidateAccessorCache(); } -void Coordinates::checkCovariant() { - // Diagonal metric components should be finite - bout::checkFinite(g_11, "g_11", "RGN_NOCORNERS"); - bout::checkFinite(g_22, "g_22", "RGN_NOCORNERS"); - bout::checkFinite(g_33, "g_33", "RGN_NOCORNERS"); - if (g_11.hasParallelSlices() && &g_11.ynext(1) != &g_11) { - for (int dy = 1; dy <= localmesh->ystart; ++dy) { - for (const auto sign : {1, -1}) { - bout::checkFinite(g_11.ynext(sign * dy), "g_11.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - bout::checkFinite(g_22.ynext(sign * dy), "g_22.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - bout::checkFinite(g_33.ynext(sign * dy), "g_33.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - } - } - } - // Diagonal metric components should be positive - bout::checkPositive(g_11, "g_11", "RGN_NOCORNERS"); - bout::checkPositive(g_22, "g_22", "RGN_NOCORNERS"); - bout::checkPositive(g_33, "g_33", "RGN_NOCORNERS"); - if (g_11.hasParallelSlices() && &g_11.ynext(1) != &g_11) { - for (int dy = 1; dy <= localmesh->ystart; ++dy) { - for (const auto sign : {1, -1}) { - bout::checkPositive(g_11.ynext(sign * dy), "g_11.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - bout::checkPositive(g_22.ynext(sign * dy), "g_22.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - bout::checkPositive(g_33.ynext(sign * dy), "g_33.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - } - } - } - - // Off-diagonal metric components should be finite - bout::checkFinite(g_12, "g_12", "RGN_NOCORNERS"); - bout::checkFinite(g_13, "g_13", "RGN_NOCORNERS"); - bout::checkFinite(g_23, "g_23", "RGN_NOCORNERS"); - if (g_23.hasParallelSlices() && &g_23.ynext(1) != &g_23) { - for (int dy = 1; dy <= localmesh->ystart; ++dy) { - for (const auto sign : {1, -1}) { - bout::checkFinite(g_12.ynext(sign * dy), "g_12.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - bout::checkFinite(g_13.ynext(sign * dy), "g_13.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - bout::checkFinite(g_23.ynext(sign * dy), "g_23.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - } +const Coordinates::FieldMetric& Coordinates::J() const { + BOUT_OMP_SAFE(critical(jacobian_cache)) + { + if (jacobian_cache == nullptr) { + jacobian_cache = std::make_unique(recalculateJacobian()); } } + return *jacobian_cache; } -void Coordinates::checkContravariant() { - // Diagonal metric components should be finite - bout::checkFinite(g11, "g11", "RGN_NOCORNERS"); - bout::checkFinite(g22, "g22", "RGN_NOCORNERS"); - bout::checkFinite(g33, "g33", "RGN_NOCORNERS"); - if (g11.hasParallelSlices() && &g11.ynext(1) != &g11) { - for (int dy = 1; dy <= localmesh->ystart; ++dy) { - for (const auto sign : {1, -1}) { - bout::checkFinite(g11.ynext(sign * dy), "g11.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - bout::checkFinite(g22.ynext(sign * dy), "g22.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - bout::checkFinite(g33.ynext(sign * dy), "g33.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - } - } - } - // Diagonal metric components should be positive - bout::checkPositive(g11, "g11", "RGN_NOCORNERS"); - bout::checkPositive(g22, "g22", "RGN_NOCORNERS"); - bout::checkPositive(g33, "g33", "RGN_NOCORNERS"); - if (g11.hasParallelSlices() && &g11.ynext(1) != &g11) { - for (int dy = 1; dy <= localmesh->ystart; ++dy) { - for (const auto sign : {1, -1}) { - bout::checkPositive(g11.ynext(sign * dy), "g11.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - bout::checkPositive(g22.ynext(sign * dy), "g22.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - bout::checkPositive(g33.ynext(sign * dy), "g33.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - } - } - } +void Coordinates::setJ(const FieldMetric& J, const bool communicate) { + bout::checkFinite(J, "J", "RGN_NOCORNERS"); + bout::checkPositive(J, "J", "RGN_NOCORNERS"); - // Off-diagonal metric components should be finite - bout::checkFinite(g12, "g12", "RGN_NOCORNERS"); - bout::checkFinite(g13, "g13", "RGN_NOCORNERS"); - bout::checkFinite(g23, "g23", "RGN_NOCORNERS"); - if (g23.hasParallelSlices() && &g23.ynext(1) != &g23) { - for (int dy = 1; dy <= localmesh->ystart; ++dy) { - for (const auto sign : {1, -1}) { - bout::checkFinite(g12.ynext(sign * dy), "g12.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - bout::checkFinite(g13.ynext(sign * dy), "g13.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - bout::checkFinite(g23.ynext(sign * dy), "g23.ynext", - fmt::format("RGN_YPAR_{:+d}", sign * dy)); - } - } + //TODO: Calculate J and check value is close + invalidateJacobianCaches(); + jacobian_cache = std::make_unique(J); + if (communicate) { + localmesh->communicate_no_slices(*jacobian_cache); } } @@ -1726,18 +1083,16 @@ const Coordinates::FieldMetric& Coordinates::g_22_ylow() const { BOUT_OMP_SAFE(critical) { if (!_g_22_ylow.has_value()) { - _g_22_ylow.emplace(emptyFrom(g_22)); - //_g_22_ylow->setLocation(CELL_YLOW); - auto* mesh = Bxy.getMesh(); - if (Bxy.isFci()) { - if (mesh->get(_g_22_ylow.value(), "g_22_cell_ylow", 0.0, false) != 0) { + _g_22_ylow.emplace(emptyFrom(g_22())); + if (Bxy().isFci()) { + if (localmesh->get(_g_22_ylow.value(), "g_22_cell_ylow", 0.0, false) != 0) { throw BoutException("The grid file does not contain `g_22_cell_ylow`."); } } else { - ASSERT0(mesh->ystart > 0); - BOUT_FOR(i, g_22.getRegion("RGN_NOY")) { + ASSERT0(localmesh->ystart > 0); + BOUT_FOR(i, g_22().getRegion("RGN_NOY")) { _g_22_ylow.value()[i] = - SQ(0.5 * (std::sqrt(g_22[i]) + std::sqrt(g_22[i.ym()]))); + SQ(0.5 * (std::sqrt(g_22()[i]) + std::sqrt(g_22()[i.ym()]))); } } } @@ -1752,17 +1107,16 @@ const Coordinates::FieldMetric& Coordinates::g_22_yhigh() const { BOUT_OMP_SAFE(critical) { if (!_g_22_yhigh.has_value()) { - _g_22_yhigh.emplace(emptyFrom(g_22)); - auto* mesh = Bxy.getMesh(); - if (Bxy.isFci()) { - if (mesh->get(_g_22_yhigh.value(), "g_22_cell_yhigh", 0.0, false) != 0) { + _g_22_yhigh.emplace(emptyFrom(g_22())); + if (Bxy().isFci()) { + if (localmesh->get(_g_22_yhigh.value(), "g_22_cell_yhigh", 0.0, false) != 0) { throw BoutException("The grid file does not contain `g_22_cell_yhigh`."); } } else { - ASSERT0(mesh->ystart > 0); - BOUT_FOR(i, g_22.getRegion("RGN_NOY")) { + ASSERT0(localmesh->ystart > 0); + BOUT_FOR(i, g_22().getRegion("RGN_NOY")) { _g_22_yhigh.value()[i] = - SQ(0.5 * (std::sqrt(g_22[i]) + std::sqrt(g_22[i.yp()]))); + SQ(0.5 * (std::sqrt(g_22()[i]) + std::sqrt(g_22()[i.yp()]))); } } } @@ -1774,13 +1128,12 @@ void Coordinates::_compute_cell_area_x() const { BOUT_OMP_SAFE(critical) { if (!_cell_area_xlow.has_value()) { - const FieldMetric area_centre = J / sqrt(g_11) * dy * dz; + const FieldMetric area_centre = J() / sqrt(g_11()) * dy_ * dz_; _cell_area_xlow.emplace(emptyFrom(area_centre)); _cell_area_xhigh.emplace(emptyFrom(area_centre)); // We cannot setLocation, as that would trigger the computation of staggered // metrics. - auto* mesh = Bxy.getMesh(); - ASSERT0(mesh->xstart > 0); + ASSERT0(localmesh->xstart > 0); BOUT_FOR(i, area_centre.getRegion("RGN_NOX")) { (*_cell_area_xlow)[i] = 0.5 * (area_centre[i] + area_centre[i.xm()]); (*_cell_area_xhigh)[i] = 0.5 * (area_centre[i] + area_centre[i.xp()]); @@ -1793,48 +1146,47 @@ void Coordinates::_compute_cell_area_y() const { BOUT_OMP_SAFE(critical) { if (!_cell_area_ylow.has_value()) { - auto* mesh = Bxy.getMesh(); - if (g_11.isFci()) { - const FieldMetric jxz_centre = J / sqrt(g_22); + if (g_11().isFci()) { + const FieldMetric jxz_centre = J() / sqrt(g_22()); auto jxz_ylow = emptyFrom(jxz_centre); auto jxz_yhigh = emptyFrom(jxz_centre); auto By_c = emptyFrom(jxz_centre); auto By_h = emptyFrom(jxz_yhigh); auto By_l = emptyFrom(jxz_ylow); - if (mesh->get(By_c, "By", 0.0, false, CELL_CENTRE) != 0) { + if (localmesh->get(By_c, "By", 0.0, false, CELL_CENTRE) != 0) { throw BoutException("The grid file does not contain `By`."); } - if (mesh->get(By_l, "By_cell_ylow", 0.0, false) != 0) { + if (localmesh->get(By_l, "By_cell_ylow", 0.0, false) != 0) { throw BoutException("The grid file does not contain `By_cell_ylow`."); } - if (mesh->get(By_h, "By_cell_yhigh", 0.0, false) != 0) { + if (localmesh->get(By_h, "By_cell_yhigh", 0.0, false) != 0) { throw BoutException("The grid file does not contain `By_cell_yhigh`."); } BOUT_FOR(i, By_c.getRegion("RGN_NOY")) { jxz_ylow[i] = By_c[i] / By_l[i] * jxz_centre[i]; jxz_yhigh[i] = By_c[i] / By_h[i] * jxz_centre[i]; } - ASSERT3(isUniform(dx, true, "RGN_ALL")); - ASSERT2(isUniform(dx, false, "RGN_ALL")); - ASSERT3(isUniform(dz, true, "RGN_ALL")); - ASSERT2(isUniform(dz, false, "RGN_ALL")); - _cell_area_ylow.emplace(jxz_ylow * dx * dz); - _cell_area_yhigh.emplace(jxz_yhigh * dx * dz); + ASSERT3(isUniform(dx_, true, "RGN_ALL")); + ASSERT2(isUniform(dx_, false, "RGN_ALL")); + ASSERT3(isUniform(dz_, true, "RGN_ALL")); + ASSERT2(isUniform(dz_, false, "RGN_ALL")); + _cell_area_ylow.emplace(jxz_ylow * dx_ * dz_); + _cell_area_yhigh.emplace(jxz_yhigh * dx_ * dz_); } else { // Field aligned - const FieldMetric area_centre = J / sqrt(g_22) * dx * dz; + const FieldMetric area_centre = J() / sqrt(g_22()) * dx_ * dz_; _cell_area_ylow.emplace(emptyFrom(area_centre)); _cell_area_yhigh.emplace(emptyFrom(area_centre)); // We cannot setLocation, as that would trigger the computation of staggered // metrics. - BOUT_FOR(i, mesh->getRegion("RGN_ALL")) { + BOUT_FOR(i, localmesh->getRegion("RGN_ALL")) { if (i.y() > 0) { (*_cell_area_ylow)[i] = 0.5 * (area_centre[i] + area_centre[i.ym()]); } else { (*_cell_area_ylow)[i] = BoutNaN; } - if (i.y() < mesh->LocalNy - 1) { + if (i.y() < localmesh->LocalNy - 1) { (*_cell_area_yhigh)[i] = 0.5 * (area_centre[i] + area_centre[i.yp()]); } else { (*_cell_area_yhigh)[i] = BoutNaN; @@ -1849,7 +1201,7 @@ void Coordinates::_compute_cell_area_z() const { BOUT_OMP_SAFE(critical) { if (!_cell_area_zlow.has_value()) { - const FieldMetric area_centre = J / sqrt(g_33) * dx * dy; + const FieldMetric area_centre = J() / sqrt(g_33()) * dx_ * dy_; _cell_area_zlow.emplace(emptyFrom(area_centre)); _cell_area_zhigh.emplace(emptyFrom(area_centre)); // We cannot setLocation, as that would trigger the computation of staggered @@ -1866,11 +1218,134 @@ void Coordinates::_compute_cell_volume() const { BOUT_OMP_SAFE(critical) { if (!_cell_volume.has_value()) { - _cell_volume.emplace(J * dx * dy * dz); + _cell_volume.emplace(*jacobian_cache * dx_ * dy_ * dz_); } } } -std::shared_ptr Coordinates::makeYBoundary(YBndryType type) const { - return std::make_shared(type, localoptions, *localmesh); +const bout::boundary::YBoundary& Coordinates::getYBoundary(YBndryType type) const { + using bout::boundary::YBoundary; + const auto itype = static_cast(type); + if (ybndrys.at(itype) == nullptr) { + ybndrys.at(itype) = std::make_shared(type, localoptions, *localmesh); + } + return *ybndrys.at(itype); +} + +void Coordinates::setBxy(FieldMetric Bxy, const bool communicate) { + //TODO: Calculate Bxy and check value is close + Bxy_ = std::move(Bxy); + _g_22_ylow.reset(); + _g_22_yhigh.reset(); + invalidateAccessorCache(); + if (communicate) { + localmesh->communicate_no_slices(Bxy_); + } +} + +void Coordinates::setContravariantMetricTensor( + const ContravariantMetricTensor& metric_tensor, const std::string& region, + bool recalculate_staggered, bool force_interpolate_from_centre) { + contravariantMetricTensor = metric_tensor; + covariantMetricTensor = contravariantMetricTensor.inverse(region); + invalidateMetricCaches(); + setJ(recalculateJacobian()); + setBxy(recalculateBxy()); + recalculateAndReset(recalculate_staggered, force_interpolate_from_centre); +} + +void Coordinates::setCovariantMetricTensor(const CovariantMetricTensor& metric_tensor, + const std::string& region, + bool recalculate_staggered, + bool force_interpolate_from_centre) { + covariantMetricTensor = metric_tensor; + contravariantMetricTensor = covariantMetricTensor.inverse(region); + invalidateMetricCaches(); + setJ(recalculateJacobian()); + setBxy(recalculateBxy()); + recalculateAndReset(recalculate_staggered, force_interpolate_from_centre); +} + +void Coordinates::setMetricTensor( + const ContravariantMetricTensor& contravariant_metric_tensor, + const CovariantMetricTensor& covariant_metric_tensor) { + contravariantMetricTensor = contravariant_metric_tensor; + covariantMetricTensor = covariant_metric_tensor; + invalidateMetricCaches(); + setJ(recalculateJacobian()); + setBxy(recalculateBxy()); +} +void Coordinates::setMetricTensorJB( + const ContravariantMetricTensor& contravariant_metric_tensor, + const CovariantMetricTensor& covariant_metric_tensor, const FieldMetric& J, + const FieldMetric& Bxy) { + contravariantMetricTensor = contravariant_metric_tensor; + covariantMetricTensor = covariant_metric_tensor; + setJ(J); + setBxy(Bxy); + invalidateMetricCaches(); +} + +void Coordinates::communicateMetricTensor() { + contravariantMetricTensor.communicate(); + covariantMetricTensor.communicate(); +} + +void Coordinates::communicateDz() { localmesh->communicate(dz_); } + +void Coordinates::splitBxyParallelSlices() { + if (not Bxy_.hasParallelSlices()) { + auto copy = Bxy_; + Bxy_.splitParallelSlices(); + Bxy_.yup() = Bxy_.ydown() = copy; + } +} + +void Coordinates::normaliseMetric(const MetricNormaliser& norm) { + covariantMetricTensor.normaliseMetric(norm); + contravariantMetricTensor.normaliseMetric(norm); + +#if BOUT_USE_METRIC_3D + using FieldMetricParallel = Field3DParallel; +#else + using FieldMetricParallel = Field2D; +#endif + + if (norm.J.has_value()) { + if (J().hasParallelSlices()) { + setJ(FieldMetricParallel{J() / *norm.J}); + } else { + setJ(J() / *norm.J); + } + } + if (norm.Bxy.has_value()) { + if (Bxy().hasParallelSlices()) { + setBxy(FieldMetricParallel{Bxy() / *norm.Bxy}); + } else { + setBxy(Bxy() / *norm.Bxy); + } + } + if (norm.dx.has_value()) { + setDx(dx() / *norm.dx); + } + if (norm.dy.has_value()) { + setDy(dy() / *norm.dy); + } + if (norm.dz.has_value()) { + setDz(dz() / *norm.dz); + } + invalidateMetricCaches(); + if (norm.g.has_value() or norm.g22.has_value()) { + if (Bxy().isFci()) { + // No we compute g_22_* - they must not be cleared. If they get + // cleared, they will be recomputed, but not normalised! + auto g22 = norm.g.has_value() ? norm.g.value() : norm.g22.value(); + g_22_ylow(); + g_22_yhigh(); + ASSERT2(_g_22_ylow.has_value()); + (*_g_22_ylow) /= g22; + ASSERT2(_g_22_yhigh.has_value()); + (*_g_22_yhigh) /= g22; + } + } } diff --git a/src/mesh/coordinates_accessor.cxx b/src/mesh/coordinates_accessor.cxx index efc27e9715..a8a1f84f5d 100644 --- a/src/mesh/coordinates_accessor.cxx +++ b/src/mesh/coordinates_accessor.cxx @@ -18,11 +18,10 @@ CoordinatesAccessor::CoordinatesAccessor(const Coordinates* coords) { ASSERT0(coords != nullptr); // Size of the mesh in Z. Used to convert 3D -> 2D index - Mesh* mesh = coords->dx.getMesh(); + const Mesh* mesh = coords->dx().getMesh(); mesh_nz = mesh->LocalNz; - auto search = coords_store.find(coords); - if (search != coords_store.end()) { + if (const auto search = coords_store.find(coords); search != coords_store.end()) { // Found, so get the pointer to the data data = search->second.begin(); return; @@ -41,9 +40,11 @@ CoordinatesAccessor::CoordinatesAccessor(const Coordinates* coords) { // Copy data from Coordinates variable into data array // Uses the symbol to look up the corresponding Offset -#define COPY_STRIPE1(symbol) \ - if (coords->symbol.isAllocated()) \ - data[stripe_size * ind.ind + static_cast(Offset::symbol)] = coords->symbol[ind]; +#define COPY_STRIPE1(symbol) \ + if (coords->symbol().isAllocated()) { \ + data[stripe_size * ind.ind + static_cast(Offset::symbol)] = \ + coords->symbol()[ind]; \ + } // Implement copy for each argument #define COPY_STRIPE(...) \ @@ -53,19 +54,21 @@ CoordinatesAccessor::CoordinatesAccessor(const Coordinates* coords) { // Iterate over all points in the field // Note this could be 2D or 3D, depending on FieldMetric type - for (const auto& ind : coords->dx.getRegion("RGN_ALL")) { + for (const auto& ind : coords->dx().getRegion("RGN_ALL")) { COPY_STRIPE(dx, dy, dz); COPY_STRIPE(d1_dx, d1_dy, d1_dz); COPY_STRIPE(J); - if (coords->Bxy.isAllocated()) { - data[stripe_size * ind.ind + static_cast(Offset::B)] = coords->Bxy[ind]; - if (coords->Bxy.yup().isAllocated()) + if (coords->Bxy().isAllocated()) { + data[(stripe_size * ind.ind) + static_cast(Offset::B)] = coords->Bxy()[ind]; + if (coords->Bxy().yup().isAllocated()) { data[stripe_size * ind.ind + static_cast(Offset::Byup)] = - coords->Bxy.yup()[ind]; - if (coords->Bxy.ydown().isAllocated()) + coords->Bxy().yup()[ind]; + } + if (coords->Bxy().ydown().isAllocated()) { data[stripe_size * ind.ind + static_cast(Offset::Bydown)] = - coords->Bxy.ydown()[ind]; + coords->Bxy().ydown()[ind]; + } } COPY_STRIPE(G1, G3); diff --git a/src/mesh/difops.cxx b/src/mesh/difops.cxx index 8ecd9d64ff..1b4323cb92 100644 --- a/src/mesh/difops.cxx +++ b/src/mesh/difops.cxx @@ -23,8 +23,10 @@ * **************************************************************************/ +#include "bout/build_config.hxx" #include "bout/build_defines.hxx" - +#include "bout/dcomplex.hxx" +#include "bout/metric_tensor.hxx" #include #include #include @@ -35,40 +37,31 @@ #include #include #include +#include +#include // Delp2 uses same coefficients as inversion code #include #include #include -#include -#include - -#include // Delp2 uses same coefficients as inversion code - -#include #include +#include +#include #include +#include +#include /******************************************************************************* * Grad_par * The parallel derivative along unperturbed B-field *******************************************************************************/ -Coordinates::FieldMetric Grad_par(const Field2D& var, CELL_LOC outloc, - const std::string& method) { - return var.getCoordinates(outloc)->Grad_par(var, outloc, method); -} - -Coordinates::FieldMetric Grad_par(const Field2D& var, const std::string& method, - CELL_LOC outloc) { - return var.getCoordinates(outloc)->Grad_par(var, outloc, method); +bout::FieldMetric Grad_par(const Field2D& var, CELL_LOC outloc, + const std::string& method) { + return DDY(var, outloc, method) * var.getCoordinates(outloc)->invSg(); } -Field3D Grad_par(const Field3D& var, CELL_LOC outloc, const std::string& method) { - return var.getCoordinates(outloc)->Grad_par(var, outloc, method); -} - -Field3D Grad_par(const Field3D& var, const std::string& method, CELL_LOC outloc) { - return var.getCoordinates(outloc)->Grad_par(var, outloc, method); +Field3D Grad_par(const Field3DParallel& var, CELL_LOC outloc, const std::string& method) { + return DDY(var, outloc, method) * var.getCoordinates(outloc)->invSg(); } /******************************************************************************* @@ -82,17 +75,17 @@ Field3D Grad_par(const Field3D& var, const std::string& method, CELL_LOC outloc) * grid-points at the corners. *******************************************************************************/ -Field3D Grad_parP(const Field3D& apar, const Field3D& f) { +Field3D Grad_parP(const Field3D& apar, const Field3DParallel& f) { ASSERT1_FIELDS_COMPATIBLE(apar, f); ASSERT1(f.hasParallelSlices()); - Mesh* mesh = apar.getMesh(); + const Mesh* mesh = apar.getMesh(); Field3D result{emptyFrom(f)}; - int ncz = mesh->LocalNz; + const int ncz = mesh->LocalNz; - Coordinates* metric = apar.getCoordinates(); + const Coordinates* metric = apar.getCoordinates(); Field3D gys{emptyFrom(f)}; @@ -101,8 +94,8 @@ Field3D Grad_parP(const Field3D& apar, const Field3D& f) { for (int y = 1; y <= mesh->LocalNy - 2; y++) { for (int z = 0; z < ncz; z++) { gys(x, y, z) = (f.yup()(x, y + 1, z) - f.ydown()(x, y - 1, z)) - / (0.5 * metric->dy(x, y + 1, z) + metric->dy(x, y, z) - + 0.5 * metric->dy(x, y - 1, z)); + / ((0.5 * metric->dy(x, y + 1, z)) + metric->dy(x, y, z) + + (0.5 * metric->dy(x, y - 1, z))); } } } @@ -110,19 +103,19 @@ Field3D Grad_parP(const Field3D& apar, const Field3D& f) { for (int x = 1; x <= mesh->LocalNx - 2; x++) { for (int y = mesh->ystart; y <= mesh->yend; y++) { for (int z = 0; z < ncz; z++) { - BoutReal by = 1. / sqrt(metric->g_22(x, y, z)); + const BoutReal by = 1. / sqrt(metric->g_22(x, y, z)); // Z indices zm and zp - int zm = (z - 1 + ncz) % ncz; - int zp = (z + 1) % ncz; + const int zm = (z - 1 + ncz) % ncz; + const int zp = (z + 1) % ncz; // bx = -DDZ(apar) - BoutReal bx = (apar(x, y, zm) - apar(x, y, zp)) - / (0.5 * metric->dz(x, y, zm) + metric->dz(x, y, z) - + 0.5 * metric->dz(x, y, zp)); + const BoutReal bx = (apar(x, y, zm) - apar(x, y, zp)) + / ((0.5 * metric->dz(x, y, zm)) + metric->dz(x, y, z) + + (0.5 * metric->dz(x, y, zp))); // bz = DDX(f) - BoutReal bz = (apar(x + 1, y, z) - apar(x - 1, y, z)) - / (0.5 * metric->dx(x - 1, y, z) + metric->dx(x, y, z) - + 0.5 * metric->dx(x + 1, y, z)); + const BoutReal bz = (apar(x + 1, y, z) - apar(x - 1, y, z)) + / ((0.5 * metric->dx(x - 1, y, z)) + metric->dx(x, y, z) + + (0.5 * metric->dx(x + 1, y, z))); // Now calculate (bx*d/dx + by*d/dy + bz*d/dz) f @@ -198,78 +191,66 @@ Field3D Grad_parP(const Field3D& apar, const Field3D& f) { * vparallel times the parallel derivative along unperturbed B-field *******************************************************************************/ -Coordinates::FieldMetric Vpar_Grad_par(const Field2D& v, const Field2D& f, - CELL_LOC outloc, const std::string& method) { - return f.getCoordinates(outloc)->Vpar_Grad_par(v, f, outloc, method); -} - -Coordinates::FieldMetric Vpar_Grad_par(const Field2D& v, const Field2D& f, - const std::string& method, CELL_LOC outloc) { - return f.getCoordinates(outloc)->Vpar_Grad_par(v, f, outloc, method); +bout::FieldMetric Vpar_Grad_par(const Field2D& v, const Field2D& f, CELL_LOC outloc, + const std::string& method) { + return VDDY(v, f, outloc, method) * f.getCoordinates(outloc)->invSg(); } -Field3D Vpar_Grad_par(const Field3D& v, const Field3D& f, CELL_LOC outloc, +Field3D Vpar_Grad_par(const Field3DParallel& v, const Field3DParallel& f, CELL_LOC outloc, const std::string& method) { - return f.getCoordinates(outloc)->Vpar_Grad_par(v, f, outloc, method); -} - -Field3D Vpar_Grad_par(const Field3D& v, const Field3D& f, const std::string& method, - CELL_LOC outloc) { - return f.getCoordinates(outloc)->Vpar_Grad_par(v, f, outloc, method); + return VDDY(v, f, outloc, method) * f.getCoordinates(outloc)->invSg(); } /******************************************************************************* * Div_par * parallel divergence operator B \partial_{||} (F/B) *******************************************************************************/ -Coordinates::FieldMetric Div_par(const Field2D& f, CELL_LOC outloc, - const std::string& method) { - return f.getCoordinates(outloc)->Div_par(f, outloc, method); -} +bout::FieldMetric Div_par(const Field2D& f, CELL_LOC outloc, const std::string& method) { + const auto& Bxy_outloc = f.getCoordinates(outloc)->Bxy(); + // Need Bxy at location of f, which might be different from outloc + const auto& Bxy_floc = f.getCoordinates()->Bxy(); -Coordinates::FieldMetric Div_par(const Field2D& f, const std::string& method, - CELL_LOC outloc) { - return f.getCoordinates(outloc)->Div_par(f, outloc, method); + return Bxy_outloc * Grad_par(bout::FieldMetric{f / Bxy_floc}, outloc, method); } -Field3D Div_par(const Field3D& f, CELL_LOC outloc, const std::string& method) { - return f.getCoordinates(outloc)->Div_par(f, outloc, method); -} +Field3D Div_par(const Field3DParallel& f, CELL_LOC outloc, const std::string& method) { + const auto& Bxy_outloc = f.getCoordinates(outloc)->Bxy(); + // Need Bxy at location of f, which might be different from outloc + const auto& Bxy_floc = f.getCoordinates()->Bxy(); -Field3D Div_par(const Field3D& f, const std::string& method, CELL_LOC outloc) { - return f.getCoordinates(outloc)->Div_par(f, outloc, method); + return Bxy_outloc * Grad_par(f / Bxy_floc, outloc, method); } -Field3D Div_par(const Field3D& f, const Field3D& v) { +Field3D Div_par(const Field3DParallel& f, const Field3DParallel& v) { ASSERT1_FIELDS_COMPATIBLE(f, v); ASSERT1(f.hasParallelSlices()); ASSERT1(v.hasParallelSlices()); // Parallel divergence, using velocities at cell boundaries // Note: Not guaranteed to be flux conservative - Mesh* mesh = f.getMesh(); + const Mesh* mesh = f.getMesh(); Field3D result{emptyFrom(f)}; - Coordinates* coord = f.getCoordinates(); + const Coordinates* coord = f.getCoordinates(); for (int i = mesh->xstart; i <= mesh->xend; i++) { for (int j = mesh->ystart; j <= mesh->yend; j++) { for (int k = mesh->zstart; k <= mesh->zend; k++) { // Value of f and v at left cell face - BoutReal fL = 0.5 * (f(i, j, k) + f.ydown()(i, j - 1, k)); - BoutReal vL = 0.5 * (v(i, j, k) + v.ydown()(i, j - 1, k)); + const BoutReal fL = 0.5 * (f(i, j, k) + f.ydown()(i, j - 1, k)); + const BoutReal vL = 0.5 * (v(i, j, k) + v.ydown()(i, j - 1, k)); - BoutReal fR = 0.5 * (f(i, j, k) + f.yup()(i, j + 1, k)); - BoutReal vR = 0.5 * (v(i, j, k) + v.yup()(i, j + 1, k)); + const BoutReal fR = 0.5 * (f(i, j, k) + f.yup()(i, j + 1, k)); + const BoutReal vR = 0.5 * (v(i, j, k) + v.yup()(i, j + 1, k)); // Calculate flux at right boundary (y+1/2) - BoutReal fluxRight = + const BoutReal fluxRight = fR * vR * (coord->J(i, j, k) + coord->J(i, j + 1, k)) / (sqrt(coord->g_22(i, j, k)) + sqrt(coord->g_22(i, j + 1, k))); // Calculate at left boundary (y-1/2) - BoutReal fluxLeft = + const BoutReal fluxLeft = fL * vL * (coord->J(i, j, k) + coord->J(i, j - 1, k)) / (sqrt(coord->g_22(i, j, k)) + sqrt(coord->g_22(i, j - 1, k))); @@ -284,15 +265,15 @@ Field3D Div_par(const Field3D& f, const Field3D& v) { //////// Flux methods -Field3D Div_par_flux(const Field3D& v, const Field3D& f, CELL_LOC outloc, +Field3D Div_par_flux(const Field3DParallel& v, const Field3DParallel& f, CELL_LOC outloc, const std::string& method) { - Coordinates* metric = f.getCoordinates(outloc); + const Coordinates* metric = f.getCoordinates(outloc); - auto Bxy_floc = f.getCoordinates()->Bxy; + auto Bxy_floc = f.getCoordinates()->Bxy(); if (!f.hasParallelSlices()) { - Field3D f_B = f / Bxy_floc; - return metric->Bxy * FDDY(v, f_B, outloc, method) / sqrt(metric->g_22); + const Field3D f_B = f / Bxy_floc; + return metric->Bxy() * FDDY(v, f_B, outloc, method) / sqrt(metric->g_22()); } // Need to modify yup and ydown fields @@ -301,12 +282,7 @@ Field3D Div_par_flux(const Field3D& v, const Field3D& f, CELL_LOC outloc, f_B.splitParallelSlices(); f_B.yup() = f.yup() / Bxy_floc; f_B.ydown() = f.ydown() / Bxy_floc; - return metric->Bxy * FDDY(v, f_B, outloc, method) / sqrt(metric->g_22); -} - -Field3D Div_par_flux(const Field3D& v, const Field3D& f, const std::string& method, - CELL_LOC outloc) { - return Div_par_flux(v, f, outloc, method); + return metric->Bxy() * FDDY(v, f_B, outloc, method) / sqrt(metric->g_22()); } /******************************************************************************* @@ -318,13 +294,23 @@ Field3D Div_par_flux(const Field3D& v, const Field3D& f, const std::string& meth * Note: For parallel Laplacian use LaplacePar *******************************************************************************/ -Coordinates::FieldMetric Grad2_par2(const Field2D& f, CELL_LOC outloc, - const std::string& method) { - return f.getCoordinates(outloc)->Grad2_par2(f, outloc, method); +bout::FieldMetric Grad2_par2(const Field2D& f, CELL_LOC outloc, + const std::string& method) { + const auto& coords = *f.getCoordinates(outloc); + + return coords.Grad2_par2_DDY_invSg(outloc, method) * DDY(f, outloc, method) + + D2DY2(f, outloc, method) / coords.g_22(); } -Field3D Grad2_par2(const Field3D& f, CELL_LOC outloc, const std::string& method) { - return f.getCoordinates(outloc)->Grad2_par2(f, outloc, method); +Field3D Grad2_par2(const Field3DParallel& f, CELL_LOC outloc, const std::string& method) { + if (outloc == CELL_DEFAULT) { + outloc = f.getLocation(); + } + + const auto& coords = *f.getCoordinates(outloc); + + return coords.Grad2_par2_DDY_invSg(outloc, method) * DDY(f, outloc, method) + + D2DY2(f, outloc, method) / coords.g_22(); } /******************************************************************************* @@ -332,17 +318,16 @@ Field3D Grad2_par2(const Field3D& f, CELL_LOC outloc, const std::string& method) * Parallel divergence of diffusive flux, K*Grad_par *******************************************************************************/ -Coordinates::FieldMetric Div_par_K_Grad_par(BoutReal kY, const Field2D& f, - CELL_LOC outloc) { +bout::FieldMetric Div_par_K_Grad_par(BoutReal kY, const Field2D& f, CELL_LOC outloc) { return kY * Grad2_par2(f, outloc); } -Field3D Div_par_K_Grad_par(BoutReal kY, const Field3D& f, CELL_LOC outloc) { +Field3D Div_par_K_Grad_par(BoutReal kY, const Field3DParallel& f, CELL_LOC outloc) { return kY * Grad2_par2(f, outloc); } -Coordinates::FieldMetric Div_par_K_Grad_par(const Field2D& kY, const Field2D& f, - CELL_LOC outloc) { +bout::FieldMetric Div_par_K_Grad_par(const Field2D& kY, const Field2D& f, + CELL_LOC outloc) { if (outloc == CELL_DEFAULT) { outloc = f.getLocation(); } @@ -350,7 +335,7 @@ Coordinates::FieldMetric Div_par_K_Grad_par(const Field2D& kY, const Field2D& f, + Div_par(kY, outloc) * Grad_par(f, outloc); } -Field3D Div_par_K_Grad_par(const Field2D& kY, const Field3D& f, CELL_LOC outloc) { +Field3D Div_par_K_Grad_par(const Field2D& kY, const Field3DParallel& f, CELL_LOC outloc) { if (outloc == CELL_DEFAULT) { outloc = f.getLocation(); } @@ -358,7 +343,7 @@ Field3D Div_par_K_Grad_par(const Field2D& kY, const Field3D& f, CELL_LOC outloc) + Div_par(kY, outloc) * Grad_par(f, outloc); } -Field3D Div_par_K_Grad_par(const Field3D& kY, const Field2D& f, CELL_LOC outloc) { +Field3D Div_par_K_Grad_par(const Field3DParallel& kY, const Field2D& f, CELL_LOC outloc) { if (outloc == CELL_DEFAULT) { outloc = f.getLocation(); } @@ -366,7 +351,8 @@ Field3D Div_par_K_Grad_par(const Field3D& kY, const Field2D& f, CELL_LOC outloc) + Div_par(kY, outloc) * Grad_par(f, outloc); } -Field3D Div_par_K_Grad_par(const Field3D& kY, const Field3D& f, CELL_LOC outloc) { +Field3D Div_par_K_Grad_par(const Field3DParallel& kY, const Field3DParallel& f, + CELL_LOC outloc) { if (outloc == CELL_DEFAULT) { outloc = f.getLocation(); } @@ -374,8 +360,11 @@ 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) { +namespace { +template +Field3D Div_par_K_Grad_par_mod_impl(const Field3DParallel& Kin, + const Field3DParallel& fin, Field3D& flow_ylow, + bool bndry_flux) { ASSERT2(Kin.getLocation() == fin.getLocation()); const Mesh* mesh = Kin.getMesh(); @@ -398,31 +387,35 @@ Field3D Div_par_K_Grad_par_mod(const Field3D& Kin, const Field3D& fin, Field3D& Field3D result{zeroFrom(fin)}; flow_ylow = zeroFrom(fin); + const auto yboundary = coord->getYBoundary(); + 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 J_up = - 0.5 * (coord->J[i] + coord->J.yup()[iyp]); // Jacobian at boundary - const BoutReal g_22_up = 0.5 * (coord->g_22[i] + coord->g_22.yup()[iyp]); - const BoutReal gradient_up = - 2. * (f_up[iyp] - fin[i]) / (coord->dy[i] + coord->dy.yup()[iyp]); + BoutReal flux_up = 0; + if (bndry_flux or not yboundary.contains<+1>(i)) { + const BoutReal c_up = 0.5 * (Kin[i] + K_up[iyp]); // K at the upper boundary + + const BoutReal gradient_up = + (f_up[iyp] - fin[i]) / (coord->dy()[i] * sqrt(coord->g_22_yhigh()[i])); - const BoutReal flux_up = c_up * J_up * gradient_up / g_22_up; + flux_up = c_up * gradient_up * coord->cell_area_yhigh()[i]; + } // Lower cell edge - const BoutReal c_down = 0.5 * (Kin[i] + K_down[iym]); // K at the lower boundary - const BoutReal J_down = - 0.5 * (coord->J[i] + coord->J.ydown()[iym]); // Jacobian at boundary - const BoutReal g_22_down = 0.5 * (coord->g_22[i] + coord->g_22.ydown()[iym]); - const BoutReal gradient_down = - 2. * (fin[i] - f_down[iym]) / (coord->dy[i] + coord->dy.ydown()[iym]); + BoutReal flux_down = 0; + if (bndry_flux or not yboundary.contains<-1>(i)) { + const BoutReal c_down = 0.5 * (Kin[i] + K_down[iym]); // K at the lower boundary + const BoutReal gradient_down = + (fin[i] - f_down[iym]) / (coord->dy()[i] * sqrt(coord->g_22_ylow()[i])); - const BoutReal flux_down = c_down * J_down * gradient_down / g_22_down; + flux_down = c_down * gradient_down * coord->cell_area_ylow()[i]; + } - result[i] = (flux_up - flux_down) / (coord->dy[i] * coord->J[i]); + // Add the fluxes + result[i] = (flux_up - flux_down) / (coord->cell_volume()[i]); } return result; @@ -437,35 +430,94 @@ Field3D Div_par_K_Grad_par_mod(const Field3D& Kin, const Field3D& fin, Field3D& 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]); + if (bndry_flux || mesh->periodicY(i.x()) || !mesh->lastY(i.x()) + || (i.y() != mesh->yend)) { + BoutReal flux = 0.0; + + if constexpr (conduction_method == bout::ConductionMethod::Original) { + 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]); + + flux = c * J * gradient / g_22; + } else if constexpr (conduction_method == bout::ConductionMethod::ProductJK) { + // Intended to reduce sensitivity of result to K in small cells + const BoutReal cJ = + 0.5 * (K[i] * coord->J()[i] + K[iyp] * coord->J()[iyp]); // K * J 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]); + + flux = cJ * gradient / g_22; + } else if constexpr (conduction_method == bout::ConductionMethod::Harmonic) { + // Harmonic average (serial resistance) + const BoutReal cond_i = + K[i] * coord->J()[i] / (coord->g_22()[i] * coord->dy()[i]); + const BoutReal cond_iyp = + K[iyp] * coord->J()[iyp] / (coord->g_22()[iyp] * coord->dy()[iyp]); + const BoutReal denom = cond_i + cond_iyp; + + // Harmonic mean: series resistance of two half-cells + const BoutReal C_edge = + (std::abs(denom) > std::numeric_limits::epsilon()) + ? 2.0 * cond_i * cond_iyp / denom + : 0.0; + + flux = C_edge * (f[iyp] - f[i]); + } + result[i] += flux / (coord->dy()[i] * coord->J()[i]); } // Calculate flux at lower surface - if (bndry_flux || is_periodic_y || !mesh->firstY(ix) || (iy != mesh->ystart)) { - const BoutReal c = 0.5 * (K[i] + K[iym]); // K at the lower boundary - const BoutReal J = 0.5 * (coord->J[i] + coord->J[iym]); // Jacobian at boundary - const BoutReal g_22 = 0.5 * (coord->g_22[i] + coord->g_22[iym]); - const BoutReal gradient = 2. * (f[i] - f[iym]) / (coord->dy[i] + coord->dy[iym]); - - const BoutReal flux = c * J * gradient / g_22; + if (bndry_flux || mesh->periodicY(i.x()) || !mesh->firstY(i.x()) + || (i.y() != mesh->ystart)) { + BoutReal flux = 0.0; + + if constexpr (conduction_method == bout::ConductionMethod::Original) { + 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]); + + flux = c * J * gradient / g_22; + } else if constexpr (conduction_method == bout::ConductionMethod::ProductJK) { + const BoutReal cJ = + 0.5 * (K[i] * coord->J()[i] + K[iym] * coord->J()[iym]); // K * J 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]); + + flux = cJ * gradient / g_22; + } else if constexpr (conduction_method == bout::ConductionMethod::Harmonic) { + const BoutReal cond_i = + K[i] * coord->J()[i] / (coord->g_22()[i] * coord->dy()[i]); + const BoutReal cond_iym = + K[iym] * coord->J()[iym] / (coord->g_22()[iym] * coord->dy()[iym]); + const BoutReal denom = cond_i + cond_iym; + + const BoutReal C_edge = + (std::abs(denom) > std::numeric_limits::epsilon()) + ? 2.0 * cond_i * cond_iym / denom + : 0.0; + + flux = C_edge * (f[i] - f[iym]); + } - result[i] -= flux / (coord->dy[i] * coord->J[i]); - flow_ylow[i] = -flux * coord->dx[i] * coord->dz[i]; + result[i] -= flux / (coord->dy()[i] * coord->J()[i]); + flow_ylow[i] = -flux * coord->dx()[i] * coord->dz()[i]; } } @@ -475,22 +527,164 @@ Field3D Div_par_K_Grad_par_mod(const Field3D& Kin, const Field3D& fin, Field3D& return result; } +} // namespace +Field3D Div_par_K_Grad_par_mod(const Field3DParallel& Kin, const Field3DParallel& fin, + Field3D& flow_ylow, bool bndry_flux, + bout::ConductionMethod method) { + using enum bout::ConductionMethod; + + switch (method) { + case Original: + return Div_par_K_Grad_par_mod_impl(Kin, fin, flow_ylow, bndry_flux); + case ProductJK: + return Div_par_K_Grad_par_mod_impl(Kin, fin, flow_ylow, bndry_flux); + case Harmonic: + return Div_par_K_Grad_par_mod_impl(Kin, fin, flow_ylow, bndry_flux); + } + throw BoutException( + "Unknown method `{}` - choose from `Original`, `ProductJK` or `Harmonic`.", + toString(method)); +} /******************************************************************************* * Delp2 * perpendicular Laplacian operator *******************************************************************************/ -Coordinates::FieldMetric Delp2(const Field2D& f, CELL_LOC outloc, bool useFFT) { - return f.getCoordinates(outloc)->Delp2(f, outloc, useFFT); +bout::FieldMetric Delp2(const Field2D& f, CELL_LOC outloc, [[maybe_unused]] bool useFFT) { + const auto& coords = *f.getCoordinates(outloc); + return coords.G1() * DDX(f, outloc) + coords.g11() * D2DX2(f, outloc); } Field3D Delp2(const Field3D& f, CELL_LOC outloc, bool useFFT) { - return f.getCoordinates(outloc)->Delp2(f, outloc, useFFT); + if (outloc == CELL_DEFAULT) { + outloc = f.getLocation(); + } + + ASSERT1(f.getLocation() == outloc); + const auto* mesh = f.getMesh(); + + if (mesh->GlobalNx == 1 && mesh->GlobalNz == 1) { + // copy mesh, location, etc + return f * 0; + } + ASSERT2(mesh->xstart > 0); // Need at least one guard cell; + + Field3D result{emptyFrom(f).setLocation(outloc)}; + + if (useFFT and not bout::build::use_metric_3d and mesh->getNZPE() == 1) { + const int ncz = mesh->LocalNz; + + // Allocate memory + auto ft = Matrix(mesh->LocalNx, (ncz / 2) + 1); + auto delft = Matrix(mesh->LocalNx, (ncz / 2) + 1); + + // Loop over y indices + // Note: should not include y-guard or y-boundary points here as that would + // use values from corner cells in dx, which may not be initialised. + for (int jy = mesh->ystart; jy <= mesh->yend; jy++) { + + // Take forward FFT + + for (int jx = 0; jx < mesh->LocalNx; jx++) { + rfft(&f(jx, jy, 0), ncz, &ft(jx, 0)); + } + + // Loop over kz + for (int jz = 0; jz <= ncz / 2; jz++) { + + // No smoothing in the x direction + for (int jx = mesh->xstart; jx <= mesh->xend; jx++) { + // Perform x derivative + + dcomplex a; + dcomplex b; + dcomplex c; + laplace_tridag_coefs(jx, jy, jz, a, b, c, nullptr, nullptr, outloc); + + delft(jx, jz) = a * ft(jx - 1, jz) + b * ft(jx, jz) + c * ft(jx + 1, jz); + } + } + + // Reverse FFT + for (int jx = mesh->xstart; jx <= mesh->xend; jx++) { + + irfft(&delft(jx, 0), ncz, &result(jx, jy, 0)); + } + } + } else { + const auto& coords = *f.getCoordinates(outloc); + result = coords.G1() * DDX(f, outloc) + coords.G3() * DDZ(f, outloc) + + coords.g11() * D2DX2(f, outloc) + coords.g33() * D2DZ2(f, outloc) + + 2 * coords.g13() * D2DXDZ(f, outloc); + } + + ASSERT2(result.getLocation() == outloc); + + return result; } FieldPerp Delp2(const FieldPerp& f, CELL_LOC outloc, bool useFFT) { - return f.getCoordinates(outloc)->Delp2(f, outloc, useFFT); + if (outloc == CELL_DEFAULT) { + outloc = f.getLocation(); + } + + ASSERT1(f.getLocation() == outloc); + const auto* mesh = f.getMesh(); + + if (mesh->GlobalNx == 1 && mesh->GlobalNz == 1) { + // copy mesh, location, etc + return f * 0; + } + ASSERT2(mesh->xstart > 0); // Need at least one guard cell + + FieldPerp result{emptyFrom(f).setLocation(outloc)}; + + const int jy = f.getIndex(); + result.setIndex(jy); + + if (useFFT and mesh->getNZPE() == 1) { + const int ncz = mesh->LocalNz; + + // Allocate memory + auto ft = Matrix(mesh->LocalNx, (ncz / 2) + 1); + auto delft = Matrix(mesh->LocalNx, (ncz / 2) + 1); + + // Take forward FFT + for (int jx = 0; jx < mesh->LocalNx; jx++) { + rfft(&f(jx, 0), ncz, &ft(jx, 0)); + } + + // Loop over kz + for (int jz = 0; jz <= ncz / 2; jz++) { + + // No smoothing in the x direction + for (int jx = mesh->xstart; jx <= mesh->xend; jx++) { + // Perform x derivative + + dcomplex a; + dcomplex b; + dcomplex c; + laplace_tridag_coefs(jx, jy, jz, a, b, c); + + delft(jx, jz) = a * ft(jx - 1, jz) + b * ft(jx, jz) + c * ft(jx + 1, jz); + } + } + + // Reverse FFT + for (int jx = mesh->xstart; jx <= mesh->xend; jx++) { + irfft(&delft(jx, 0), ncz, &result(jx, 0)); + } + + } else { + throw BoutException("Non-fourier Delp2 not currently implented for FieldPerp."); + // Would be the following but don't have standard derivative operators for FieldPerps + // yet + // result = G1 * ::DDX(f, outloc) + G3 * ::DDZ(f, outloc) + g11 * ::D2DX2(f, outloc) + // + g33 * ::D2DZ2(f, outloc) + 2 * g13 * ::D2DXDZ(f, outloc); + }; + + return result; } /******************************************************************************* @@ -500,9 +694,9 @@ FieldPerp Delp2(const FieldPerp& f, CELL_LOC outloc, bool useFFT) { * Laplace_perp = Laplace - Laplace_par *******************************************************************************/ -Coordinates::FieldMetric Laplace_perp(const Field2D& f, CELL_LOC outloc, - const std::string& dfdy_boundary_condition, - const std::string& dfdy_region) { +bout::FieldMetric Laplace_perp(const Field2D& f, CELL_LOC outloc, + const std::string& dfdy_boundary_condition, + const std::string& dfdy_region) { return Laplace(f, outloc, dfdy_boundary_condition, dfdy_region) - Laplace_par(f, outloc); } @@ -522,12 +716,17 @@ Field3D Laplace_perp(const Field3D& f, CELL_LOC outloc, * *******************************************************************************/ -Coordinates::FieldMetric Laplace_par(const Field2D& f, CELL_LOC outloc) { - return f.getCoordinates(outloc)->Laplace_par(f, outloc); +bout::FieldMetric Laplace_par(const Field2D& f, CELL_LOC outloc) { + const auto& coords = *f.getCoordinates(outloc); + return D2DY2(f, outloc) / coords.g_22() + + DDY(bout::FieldMetric{coords.J() / coords.g_22()}, outloc) * DDY(f, outloc) + / coords.J(); } -Field3D Laplace_par(const Field3D& f, CELL_LOC outloc) { - return f.getCoordinates(outloc)->Laplace_par(f, outloc); +Field3D Laplace_par(const Field3DParallel& f, CELL_LOC outloc) { + const auto& coords = *f.getCoordinates(outloc); + return D2DY2(f, outloc) / coords.g_22() + + DDY(coords.J() / coords.g_22(), outloc) * DDY(f, outloc) / coords.J(); } /******************************************************************************* @@ -535,18 +734,31 @@ Field3D Laplace_par(const Field3D& f, CELL_LOC outloc) { * Full Laplacian operator on scalar field *******************************************************************************/ -Coordinates::FieldMetric Laplace(const Field2D& f, CELL_LOC outloc, - const std::string& dfdy_boundary_condition, - const std::string& dfdy_region) { - return f.getCoordinates(outloc)->Laplace(f, outloc, dfdy_boundary_condition, - dfdy_region); +bout::FieldMetric Laplace(const Field2D& f, CELL_LOC outloc, + const std::string& dfdy_boundary_condition, + const std::string& dfdy_region) { + const auto& coords = *f.getCoordinates(outloc); + + return coords.G1() * DDX(f, outloc) + coords.G2() * DDY(f, outloc) + + coords.g11() * D2DX2(f, outloc) + coords.g22() * D2DY2(f, outloc) + + 2.0 * coords.g12() + * D2DXDY(f, outloc, "DEFAULT", "RGN_NOBNDRY", dfdy_boundary_condition, + dfdy_region); } -Field3D Laplace(const Field3D& f, CELL_LOC outloc, +Field3D Laplace(const Field3DParallel& f, CELL_LOC outloc, const std::string& dfdy_boundary_condition, const std::string& dfdy_region) { - return f.getCoordinates(outloc)->Laplace(f, outloc, dfdy_boundary_condition, - dfdy_region); + const auto& coords = *f.getCoordinates(outloc); + + return coords.G1() * DDX(f, outloc) + coords.G2() * DDY(f, outloc) + + coords.G3() * DDZ(f, outloc) + coords.g11() * D2DX2(f, outloc) + + coords.g22() * D2DY2(f, outloc) + coords.g33() * D2DZ2(f, outloc) + + 2.0 + * (coords.g12() + * D2DXDY(f, outloc, "DEFAULT", "RGN_NOBNDRY", + dfdy_boundary_condition, dfdy_region) + + coords.g13() * D2DXDZ(f, outloc) + coords.g23() * D2DYDZ(f, outloc)); } /******************************************************************************* @@ -554,9 +766,70 @@ Field3D Laplace(const Field3D& f, CELL_LOC outloc, * Inverse of Laplacian operator in LaplaceXY solver *******************************************************************************/ +#if BOUT_USE_METRIC_3D +Field2D Laplace_perpXY([[maybe_unused]] const Field2D& A, + [[maybe_unused]] const Field2D& f) { + throw BoutException("Coordinates::Laplace_perpXY for 3D metric not implemented"); +} +#else Field2D Laplace_perpXY(const Field2D& A, const Field2D& f) { - return f.getCoordinates()->Laplace_perpXY(A, f); + const auto& coords = *f.getCoordinates(); + + Field2D result; + result.allocate(); + for (auto i : result.getRegion(RGN_NOBNDRY)) { + result[i] = 0.; + + // outer x boundary + const auto outer_x_avg = [&i](const auto& f) { return 0.5 * (f[i] + f[i.xp()]); }; + const BoutReal outer_x_A = outer_x_avg(A); + const BoutReal outer_x_J = outer_x_avg(coords.J()); + const BoutReal outer_x_g11 = outer_x_avg(coords.g11()); + const BoutReal outer_x_dx = outer_x_avg(coords.dx()); + const BoutReal outer_x_value = outer_x_A * outer_x_J * outer_x_g11 + / (coords.J()[i] * outer_x_dx * coords.dx()[i]); + result[i] += outer_x_value * (f[i.xp()] - f[i]); + + // inner x boundary + const auto inner_x_avg = [&i](const auto& f) { return 0.5 * (f[i] + f[i.xm()]); }; + const BoutReal inner_x_A = inner_x_avg(A); + const BoutReal inner_x_J = inner_x_avg(coords.J()); + const BoutReal inner_x_g11 = inner_x_avg(coords.g11()); + const BoutReal inner_x_dx = inner_x_avg(coords.dx()); + const BoutReal inner_x_value = inner_x_A * inner_x_J * inner_x_g11 + / (coords.J()[i] * inner_x_dx * coords.dx()[i]); + result[i] += inner_x_value * (f[i.xm()] - f[i]); + + // upper y boundary + const auto upper_y_avg = [&i](const auto& f) { return 0.5 * (f[i] + f[i.yp()]); }; + const BoutReal upper_y_A = upper_y_avg(A); + const BoutReal upper_y_J = upper_y_avg(coords.J()); + const BoutReal upper_y_g_22 = upper_y_avg(coords.g_22()); + const BoutReal upper_y_g23 = upper_y_avg(coords.g23()); + const BoutReal upper_y_g_23 = upper_y_avg(coords.g_23()); + const BoutReal upper_y_dy = upper_y_avg(coords.dy()); + const BoutReal upper_y_value = + -upper_y_A * upper_y_J * upper_y_g23 * upper_y_g_23 + / (upper_y_g_22 * coords.J()[i] * upper_y_dy * coords.dy()[i]); + result[i] += upper_y_value * (f[i.yp()] - f[i]); + + // lower y boundary + const auto lower_y_avg = [&i](const auto& f) { return 0.5 * (f[i] + f[i.ym()]); }; + const BoutReal lower_y_A = lower_y_avg(A); + const BoutReal lower_y_J = lower_y_avg(coords.J()); + const BoutReal lower_y_g_22 = lower_y_avg(coords.g_22()); + const BoutReal lower_y_g23 = lower_y_avg(coords.g23()); + const BoutReal lower_y_g_23 = lower_y_avg(coords.g_23()); + const BoutReal lower_y_dy = lower_y_avg(coords.dy()); + const BoutReal lower_y_value = + -lower_y_A * lower_y_J * lower_y_g23 * lower_y_g_23 + / (lower_y_g_22 * coords.J()[i] * lower_y_dy * coords.dy()[i]); + result[i] += lower_y_value * (f[i.ym()] - f[i]); + } + + return result; } +#endif /******************************************************************************* * b0xGrad_dot_Grad @@ -564,8 +837,8 @@ Field2D Laplace_perpXY(const Field2D& A, const Field2D& f) { * Used for ExB terms and perturbed B field using A_|| *******************************************************************************/ -Coordinates::FieldMetric b0xGrad_dot_Grad(const Field2D& phi, const Field2D& A, - CELL_LOC outloc) { +bout::FieldMetric b0xGrad_dot_Grad(const Field2D& phi, const Field2D& A, + CELL_LOC outloc) { if (outloc == CELL_DEFAULT) { outloc = A.getLocation(); @@ -573,19 +846,19 @@ Coordinates::FieldMetric b0xGrad_dot_Grad(const Field2D& phi, const Field2D& A, ASSERT1(phi.getMesh() == A.getMesh()); - Coordinates* metric = phi.getCoordinates(outloc); + const Coordinates* metric = phi.getCoordinates(outloc); // Calculate phi derivatives - Coordinates::FieldMetric dpdx = DDX(phi, outloc); - Coordinates::FieldMetric dpdy = DDY(phi, outloc); + const bout::FieldMetric dpdx = DDX(phi, outloc); + const bout::FieldMetric dpdy = DDY(phi, outloc); // Calculate advection velocity - Coordinates::FieldMetric vx = -metric->g_23 * dpdy; - Coordinates::FieldMetric vy = metric->g_23 * dpdx; + const bout::FieldMetric vx = -metric->g_23() * dpdy; + const bout::FieldMetric vy = metric->g_23() * dpdx; // Upwind A using these velocities - Coordinates::FieldMetric result = VDDX(vx, A, outloc) + VDDY(vy, A, outloc); - result /= metric->J * sqrt(metric->g_22); + bout::FieldMetric result = VDDX(vx, A, outloc) + VDDY(vy, A, outloc); + result /= metric->J() * sqrt(metric->g_22()); ASSERT1(result.getLocation() == outloc); @@ -603,29 +876,29 @@ Field3D b0xGrad_dot_Grad(const Field2D& phi, const Field3D& A, CELL_LOC outloc) ASSERT1(phi.getMesh() == A.getMesh()); - Mesh* mesh = phi.getMesh(); + const Mesh* mesh = phi.getMesh(); - Coordinates* metric = phi.getCoordinates(outloc); + const Coordinates* metric = phi.getCoordinates(outloc); // Calculate phi derivatives - Coordinates::FieldMetric dpdx = DDX(phi, outloc); - Coordinates::FieldMetric dpdy = DDY(phi, outloc); + const bout::FieldMetric dpdx = DDX(phi, outloc); + const bout::FieldMetric dpdy = DDY(phi, outloc); // Calculate advection velocity - Coordinates::FieldMetric vx = -metric->g_23 * dpdy; - Coordinates::FieldMetric vy = metric->g_23 * dpdx; - Coordinates::FieldMetric vz = metric->g_12 * dpdy - metric->g_22 * dpdx; + const bout::FieldMetric vx = -metric->g_23() * dpdy; + const bout::FieldMetric vy = metric->g_23() * dpdx; + bout::FieldMetric vz = metric->g_12() * dpdy - metric->g_22() * dpdx; if (mesh->IncIntShear) { // BOUT-06 style differencing - vz += metric->IntShiftTorsion * vx; + vz += metric->IntShiftTorsion() * vx; } // Upwind A using these velocities Field3D result = VDDX(vx, A, outloc) + VDDY(vy, A, outloc) + VDDZ(vz, A, outloc); - result /= (metric->J * sqrt(metric->g_22)); + result /= (metric->J() * sqrt(metric->g_22())); #if BOUT_USE_TRACK result.name = "b0xGrad_dot_Grad(" + phi.name + "," + A.name + ")"; @@ -636,7 +909,7 @@ Field3D b0xGrad_dot_Grad(const Field2D& phi, const Field3D& A, CELL_LOC outloc) return result; } -Field3D b0xGrad_dot_Grad(const Field3D& p, const Field2D& A, CELL_LOC outloc) { +Field3D b0xGrad_dot_Grad(const Field3DParallel& p, const Field2D& A, CELL_LOC outloc) { if (outloc == CELL_DEFAULT) { outloc = A.getLocation(); @@ -644,22 +917,22 @@ Field3D b0xGrad_dot_Grad(const Field3D& p, const Field2D& A, CELL_LOC outloc) { ASSERT1(p.getMesh() == A.getMesh()); - Coordinates* metric = p.getCoordinates(outloc); + const Coordinates* metric = p.getCoordinates(outloc); // Calculate phi derivatives - Field3D dpdx = DDX(p, outloc); - Field3D dpdy = DDY(p, outloc); - Field3D dpdz = DDZ(p, outloc); + const Field3D dpdx = DDX(p, outloc); + const Field3D dpdy = DDY(p, outloc); + const Field3D dpdz = DDZ(p, outloc); // Calculate advection velocity - Field3D vx = metric->g_22 * dpdz - metric->g_23 * dpdy; - Field3D vy = metric->g_23 * dpdx - metric->g_12 * dpdz; + const Field3D vx = metric->g_22() * dpdz - metric->g_23() * dpdy; + const Field3D vy = metric->g_23() * dpdx - metric->g_12() * dpdz; // Upwind A using these velocities Field3D result = VDDX(vx, A, outloc) + VDDY(vy, A, outloc); - result /= (metric->J * sqrt(metric->g_22)); + result /= (metric->J() * sqrt(metric->g_22())); #if BOUT_USE_TRACK result.name = "b0xGrad_dot_Grad(" + p.name + "," + A.name + ")"; @@ -670,7 +943,7 @@ Field3D b0xGrad_dot_Grad(const Field3D& p, const Field2D& A, CELL_LOC outloc) { return result; } -Field3D b0xGrad_dot_Grad(const Field3D& phi, const Field3D& A, CELL_LOC outloc) { +Field3D b0xGrad_dot_Grad(const Field3DParallel& phi, const Field3D& A, CELL_LOC outloc) { if (outloc == CELL_DEFAULT) { outloc = A.getLocation(); @@ -678,28 +951,28 @@ Field3D b0xGrad_dot_Grad(const Field3D& phi, const Field3D& A, CELL_LOC outloc) ASSERT1(phi.getMesh() == A.getMesh()); - Mesh* mesh = phi.getMesh(); + const Mesh* mesh = phi.getMesh(); - Coordinates* metric = phi.getCoordinates(outloc); + const Coordinates* metric = phi.getCoordinates(outloc); // Calculate phi derivatives - Field3D dpdx = DDX(phi, outloc); - Field3D dpdy = DDY(phi, outloc); - Field3D dpdz = DDZ(phi, outloc); + const Field3D dpdx = DDX(phi, outloc); + const Field3D dpdy = DDY(phi, outloc); + const Field3D dpdz = DDZ(phi, outloc); // Calculate advection velocity - Field3D vx = metric->g_22 * dpdz - metric->g_23 * dpdy; - Field3D vy = metric->g_23 * dpdx - metric->g_12 * dpdz; - Field3D vz = metric->g_12 * dpdy - metric->g_22 * dpdx; + const Field3D vx = metric->g_22() * dpdz - metric->g_23() * dpdy; + const Field3D vy = metric->g_23() * dpdx - metric->g_12() * dpdz; + Field3D vz = metric->g_12() * dpdy - metric->g_22() * dpdx; if (mesh->IncIntShear) { // BOUT-06 style differencing - vz += metric->IntShiftTorsion * vx; + vz += metric->IntShiftTorsion() * vx; } Field3D result = VDDX(vx, A, outloc) + VDDY(vy, A, outloc) + VDDZ(vz, A, outloc); - result /= (metric->J * sqrt(metric->g_22)); + result /= (metric->J() * sqrt(metric->g_22())); #if BOUT_USE_TRACK result.name = "b0xGrad_dot_Grad(" + phi.name + "," + A.name + ")"; @@ -715,9 +988,8 @@ Field3D b0xGrad_dot_Grad(const Field3D& phi, const Field3D& A, CELL_LOC outloc) * Terms of form b0 x Grad(f) dot Grad(g) / B = [f, g] *******************************************************************************/ -Coordinates::FieldMetric bracket(const Field2D& f, const Field2D& g, - BRACKET_METHOD method, CELL_LOC outloc, - Solver* UNUSED(solver)) { +bout::FieldMetric bracket(const Field2D& f, const Field2D& g, BRACKET_METHOD method, + CELL_LOC outloc, Solver* UNUSED(solver)) { ASSERT1_FIELDS_COMPATIBLE(f, g); if (outloc == CELL_DEFAULT) { @@ -725,7 +997,7 @@ Coordinates::FieldMetric bracket(const Field2D& f, const Field2D& g, } ASSERT1(outloc == g.getLocation()); - Coordinates::FieldMetric result{emptyFrom(f)}; + bout::FieldMetric result{emptyFrom(f)}; if ((method == BRACKET_SIMPLE) || (method == BRACKET_ARAKAWA)) { // Use a subset of terms for comparison to BOUT-06 @@ -733,7 +1005,7 @@ Coordinates::FieldMetric bracket(const Field2D& f, const Field2D& g, result.setLocation(outloc); } else { // Use full expression with all terms - result = b0xGrad_dot_Grad(f, g, outloc) / f.getCoordinates(outloc)->Bxy; + result = b0xGrad_dot_Grad(f, g, outloc) / f.getCoordinates(outloc)->Bxy(); } return result; } @@ -747,18 +1019,18 @@ Field3D bracket(const Field3D& f, const Field2D& g, BRACKET_METHOD method, } ASSERT1(outloc == g.getLocation()); - [[maybe_unused]] Mesh* mesh = f.getMesh(); + [[maybe_unused]] const Mesh* mesh = f.getMesh(); Field3D result{emptyFrom(f).setLocation(outloc)}; - Coordinates* metric = f.getCoordinates(outloc); + const Coordinates* metric = f.getCoordinates(outloc); switch (method) { case BRACKET_CTU: { // First order Corner Transport Upwind method // P.Collela JCP 87, 171-200 (1990) - if (!solver) { + if (solver == nullptr) { throw BoutException("CTU method requires access to the solver"); } @@ -768,13 +1040,14 @@ Field3D bracket(const Field3D& f, const Field2D& g, BRACKET_METHOD method, for (int x = mesh->xstart; x <= mesh->xend; x++) { for (int y = mesh->ystart; y <= mesh->yend; y++) { for (int z = 0; z < ncz; z++) { - int zm = (z - 1 + ncz) % ncz; - int zp = (z + 1) % ncz; + const int zm = (z - 1 + ncz) % ncz; + const int zp = (z + 1) % ncz; - BoutReal gp, gm; + BoutReal gp; + BoutReal gm; // Vx = DDZ(f) - BoutReal vx = (f(x, y, zp) - f(x, y, zm)) / (2. * metric->dz(x, y, z)); + const BoutReal vx = (f(x, y, zp) - f(x, y, zm)) / (2. * metric->dz(x, y, z)); // Set stability condition solver->setMaxTimestep(metric->dx(x, y, z) / (fabs(vx) + 1e-16)); @@ -808,15 +1081,21 @@ Field3D bracket(const Field3D& f, const Field2D& g, BRACKET_METHOD method, BOUT_FOR(j2D, result.getRegion2D("RGN_NOBNDRY")) { // Get constants for this iteration - const BoutReal spacingFactor = 1.0 / (12 * metric->dz[j2D] * metric->dx[j2D]); - const int jy = j2D.y(), jx = j2D.x(); - const int xm = jx - 1, xp = jx + 1; + const BoutReal spacingFactor = 1.0 / (12 * metric->dz()[j2D] * metric->dx()[j2D]); + const int jy = j2D.y(); + const int jx = j2D.x(); + const int xm = jx - 1; + const int xp = jx + 1; // Extract relevant Field2D values - const BoutReal gxm = g(xm, jy), gc = g(jx, jy), gxp = g(xp, jy); + const BoutReal gxm = g(xm, jy); + const BoutReal gc = g(jx, jy); + const BoutReal gxp = g(xp, jy); // Index Field3D as 2D to get start of z data block - const auto fxm = f(xm, jy), fc = f(jx, jy), fxp = f(xp, jy); + const auto fxm = f(xm, jy); + const auto fc = f(jx, jy); + const auto fxp = f(xp, jy); // Here we split the loop over z into three parts; the first value, the middle block // and the last value @@ -831,8 +1110,8 @@ Field3D bracket(const Field3D& f, const Field2D& g, BRACKET_METHOD method, const BoutReal Jpp = 2 * (fc[jzp] - fc[jzm]) * (gxp - gxm); // J+x - const BoutReal Jpx = gxp * (fxp[jzp] - fxp[jzm]) - gxm * (fxm[jzp] - fxm[jzm]) - + gc * (fxp[jzm] - fxp[jzp] - fxm[jzm] + fxm[jzp]); + const BoutReal Jpx = (gxp * (fxp[jzp] - fxp[jzm])) - (gxm * (fxm[jzp] - fxm[jzm])) + + (gc * (fxp[jzm] - fxp[jzp] - fxm[jzm] + fxm[jzp])); result(jx, jy, 0) = (Jpp + Jpx) * spacingFactor; } @@ -846,8 +1125,8 @@ Field3D bracket(const Field3D& f, const Field2D& g, BRACKET_METHOD method, const BoutReal Jpp = 2 * (fc[jzp] - fc[jzm]) * (gxp - gxm); // J+x - const BoutReal Jpx = gxp * (fxp[jzp] - fxp[jzm]) - gxm * (fxm[jzp] - fxm[jzm]) - + gc * (fxp[jzm] - fxp[jzp] - fxm[jzm] + fxm[jzp]); + const BoutReal Jpx = (gxp * (fxp[jzp] - fxp[jzm])) - (gxm * (fxm[jzp] - fxm[jzm])) + + (gc * (fxp[jzm] - fxp[jzp] - fxm[jzm] + fxm[jzp])); result(jx, jy, jz) = (Jpp + Jpx) * spacingFactor; } @@ -861,8 +1140,8 @@ Field3D bracket(const Field3D& f, const Field2D& g, BRACKET_METHOD method, const BoutReal Jpp = 2 * (fc[jzp] - fc[jzm]) * (gxp - gxm); // J+x - const BoutReal Jpx = gxp * (fxp[jzp] - fxp[jzm]) - gxm * (fxm[jzp] - fxm[jzm]) - + gc * (fxp[jzm] - fxp[jzp] - fxm[jzm] + fxm[jzp]); + const BoutReal Jpx = (gxp * (fxp[jzp] - fxp[jzm])) - (gxm * (fxm[jzp] - fxm[jzm])) + + (gc * (fxp[jzm] - fxp[jzp] - fxm[jzm] + fxm[jzp])); result(jx, jy, ncz - 1) = (Jpp + Jpx) * spacingFactor; } @@ -880,7 +1159,7 @@ Field3D bracket(const Field3D& f, const Field2D& g, BRACKET_METHOD method, } default: { // Use full expression with all terms - result = b0xGrad_dot_Grad(f, g, outloc) / metric->Bxy; + result = b0xGrad_dot_Grad(f, g, outloc) / metric->Bxy(); } } return result; @@ -914,8 +1193,8 @@ Field3D bracket(const Field2D& f, const Field3D& g, BRACKET_METHOD method, } default: { // Use full expression with all terms - Coordinates* metric = f.getCoordinates(outloc); - result = b0xGrad_dot_Grad(f, g, outloc) / metric->Bxy; + const Coordinates* metric = f.getCoordinates(outloc); + result = b0xGrad_dot_Grad(f, g, outloc) / metric->Bxy(); } } @@ -934,7 +1213,7 @@ Field3D bracket(const Field3D& f, const Field3D& g, BRACKET_METHOD method, Field3D result{emptyFrom(f).setLocation(outloc)}; - Coordinates* metric = f.getCoordinates(outloc); + const Coordinates* metric = f.getCoordinates(outloc); if (mesh->GlobalNx == 1 || mesh->GlobalNz == 1) { result = 0; @@ -947,32 +1226,33 @@ Field3D bracket(const Field3D& f, const Field3D& g, BRACKET_METHOD method, // First order Corner Transport Upwind method // P.Collela JCP 87, 171-200 (1990) #if not(BOUT_USE_METRIC_3D) - if (!solver) { + if (solver == nullptr) { throw BoutException("CTU method requires access to the solver"); } // Get current timestep - BoutReal dt = solver->getCurrentTimestep(); + const BoutReal dt = solver->getCurrentTimestep(); - FieldPerp vx(mesh), vz(mesh); + FieldPerp vx(mesh); + FieldPerp vz(mesh); vx.allocate(); vx.setLocation(outloc); vz.allocate(); vz.setLocation(outloc); - int ncz = mesh->LocalNz; + const int ncz = mesh->LocalNz; for (int y = mesh->ystart; y <= mesh->yend; y++) { for (int x = 1; x <= mesh->LocalNx - 2; x++) { for (int z = mesh->zstart; z <= mesh->zend; z++) { - int zm = (z - 1 + ncz) % ncz; - int zp = (z + 1) % ncz; + const int zm = (z - 1 + ncz) % ncz; + const int zp = (z + 1) % ncz; // Vx = DDZ(f) vx(x, z) = (f(x, y, zp) - f(x, y, zm)) / (2. * metric->dz(x, y, z)); // Vz = -DDX(f) vz(x, z) = (f(x - 1, y, z) - f(x + 1, y, z)) - / (0.5 * metric->dx(x - 1, y) + metric->dx(x, y) - + 0.5 * metric->dx(x + 1, y)); + / ((0.5 * metric->dx(x - 1, y)) + metric->dx(x, y) + + (0.5 * metric->dx(x + 1, y))); // Set stability condition solver->setMaxTimestep(fabs(metric->dx(x, y)) / (fabs(vx(x, z)) + 1e-16)); @@ -984,33 +1264,34 @@ Field3D bracket(const Field3D& f, const Field3D& g, BRACKET_METHOD method, for (int x = mesh->xstart; x <= mesh->xend; x++) { for (int z = 0; z < ncz; z++) { - int zm = (z - 1 + ncz) % ncz; - int zp = (z + 1) % ncz; + const int zm = (z - 1 + ncz) % ncz; + const int zp = (z + 1) % ncz; - BoutReal gp, gm; + BoutReal gp; + BoutReal gm; // X differencing if (vx(x, z) > 0.0) { gp = g(x, y, z) - + (0.5 * dt / metric->dz(x, y)) - * ((vz(x, z) > 0) ? vz(x, z) * (g(x, y, zm) - g(x, y, z)) - : vz(x, z) * (g(x, y, z) - g(x, y, zp))); + + ((0.5 * dt / metric->dz(x, y)) + * ((vz(x, z) > 0) ? vz(x, z) * (g(x, y, zm) - g(x, y, z)) + : vz(x, z) * (g(x, y, z) - g(x, y, zp)))); gm = g(x - 1, y, z) - + (0.5 * dt / metric->dz(x, y)) - * ((vz(x, z) > 0) ? vz(x, z) * (g(x - 1, y, zm) - g(x - 1, y, z)) - : vz(x, z) * (g(x - 1, y, z) - g(x - 1, y, zp))); + + ((0.5 * dt / metric->dz(x, y)) + * ((vz(x, z) > 0) ? vz(x, z) * (g(x - 1, y, zm) - g(x - 1, y, z)) + : vz(x, z) * (g(x - 1, y, z) - g(x - 1, y, zp)))); } else { gp = g(x + 1, y, z) - + (0.5 * dt / metric->dz(x, y)) - * ((vz(x, z) > 0) ? vz(x, z) * (g(x + 1, y, zm) - g(x + 1, y, z)) - : vz[x][z] * (g(x + 1, y, z) - g(x + 1, y, zp))); + + ((0.5 * dt / metric->dz(x, y)) + * ((vz(x, z) > 0) ? vz(x, z) * (g(x + 1, y, zm) - g(x + 1, y, z)) + : vz[x][z] * (g(x + 1, y, z) - g(x + 1, y, zp)))); gm = g(x, y, z) - + (0.5 * dt / metric->dz(x, y)) - * ((vz(x, z) > 0) ? vz(x, z) * (g(x, y, zm) - g(x, y, z)) - : vz(x, z) * (g(x, y, z) - g(x, y, zp))); + + ((0.5 * dt / metric->dz(x, y)) + * ((vz(x, z) > 0) ? vz(x, z) * (g(x, y, zm) - g(x, y, z)) + : vz(x, z) * (g(x, y, z) - g(x, y, zp)))); } result(x, y, z) = vx(x, z) * (gp - gm) / metric->dx(x, y); @@ -1018,24 +1299,24 @@ Field3D bracket(const Field3D& f, const Field3D& g, BRACKET_METHOD method, // Z differencing if (vz(x, z) > 0.0) { gp = g(x, y, z) - + (0.5 * dt / metric->dx(x, y)) - * ((vx[x][z] > 0) ? vx[x][z] * (g(x - 1, y, z) - g(x, y, z)) - : vx[x][z] * (g(x, y, z) - g(x + 1, y, z))); + + ((0.5 * dt / metric->dx(x, y)) + * ((vx[x][z] > 0) ? vx[x][z] * (g(x - 1, y, z) - g(x, y, z)) + : vx[x][z] * (g(x, y, z) - g(x + 1, y, z)))); gm = g(x, y, zm) - + (0.5 * dt / metric->dx(x, y)) - * ((vx(x, z) > 0) ? vx(x, z) * (g(x - 1, y, zm) - g(x, y, zm)) - : vx(x, z) * (g(x, y, zm) - g(x + 1, y, zm))); + + ((0.5 * dt / metric->dx(x, y)) + * ((vx(x, z) > 0) ? vx(x, z) * (g(x - 1, y, zm) - g(x, y, zm)) + : vx(x, z) * (g(x, y, zm) - g(x + 1, y, zm)))); } else { gp = g(x, y, zp) - + (0.5 * dt / metric->dx(x, y)) - * ((vx(x, z) > 0) ? vx(x, z) * (g(x - 1, y, zp) - g(x, y, zp)) - : vx(x, z) * (g(x, y, zp) - g(x + 1, y, zp))); + + ((0.5 * dt / metric->dx(x, y)) + * ((vx(x, z) > 0) ? vx(x, z) * (g(x - 1, y, zp) - g(x, y, zp)) + : vx(x, z) * (g(x, y, zp) - g(x + 1, y, zp)))); gm = g(x, y, z) - + (0.5 * dt / metric->dx(x, y)) - * ((vx(x, z) > 0) ? vx(x, z) * (g(x - 1, y, z) - g(x, y, z)) - : vx(x, z) * (g(x, y, z) - g(x + 1, y, z))); + + ((0.5 * dt / metric->dx(x, y)) + * ((vx(x, z) > 0) ? vx(x, z) * (g(x - 1, y, z) - g(x, y, z)) + : vx(x, z) * (g(x, y, z) - g(x + 1, y, z)))); } result(x, y, z) += vz(x, z) * (gp - gm) / metric->dz(x, y); @@ -1058,13 +1339,19 @@ Field3D bracket(const Field3D& f, const Field3D& g, BRACKET_METHOD method, BOUT_FOR(j2D, result.getRegion2D("RGN_NOBNDRY")) { #if not(BOUT_USE_METRIC_3D) - const BoutReal spacingFactor = 1.0 / (12 * metric->dz[j2D] * metric->dx[j2D]); + const BoutReal spacingFactor = 1.0 / (12 * metric->dz()[j2D] * metric->dx()[j2D]); #endif - const int jy = j2D.y(), jx = j2D.x(); - const int xm = jx - 1, xp = jx + 1; - - const auto Fxm = f_temp(xm, jy), Fx = f_temp(jx, jy), Fxp = f_temp(xp, jy); - const auto Gxm = g_temp(xm, jy), Gx = g_temp(jx, jy), Gxp = g_temp(xp, jy); + const int jy = j2D.y(); + const int jx = j2D.x(); + const int xm = jx - 1; + const int xp = jx + 1; + + const auto Fxm = f_temp(xm, jy); + const auto Fx = f_temp(jx, jy); + const auto Fxp = f_temp(xp, jy); + const auto Gxm = g_temp(xm, jy); + const auto Gx = g_temp(jx, jy); + const auto Gxp = g_temp(xp, jy); // Here we split the loop over z into three parts; the first value, the middle block // and the last value @@ -1080,18 +1367,18 @@ Field3D bracket(const Field3D& f, const Field3D& g, BRACKET_METHOD method, #endif // J++ = DDZ(f)*DDX(g) - DDX(f)*DDZ(g) - const BoutReal Jpp = ((Fx[jzp] - Fx[jzm]) * (Gxp[jz] - Gxm[jz]) - - (Fxp[jz] - Fxm[jz]) * (Gx[jzp] - Gx[jzm])); + const BoutReal Jpp = (((Fx[jzp] - Fx[jzm]) * (Gxp[jz] - Gxm[jz])) + - ((Fxp[jz] - Fxm[jz]) * (Gx[jzp] - Gx[jzm]))); // J+x const BoutReal Jpx = - (Gxp[jz] * (Fxp[jzp] - Fxp[jzm]) - Gxm[jz] * (Fxm[jzp] - Fxm[jzm]) - - Gx[jzp] * (Fxp[jzp] - Fxm[jzp]) + Gx[jzm] * (Fxp[jzm] - Fxm[jzm])); + ((Gxp[jz] * (Fxp[jzp] - Fxp[jzm])) - (Gxm[jz] * (Fxm[jzp] - Fxm[jzm])) + - (Gx[jzp] * (Fxp[jzp] - Fxm[jzp])) + (Gx[jzm] * (Fxp[jzm] - Fxm[jzm]))); // Jx+ const BoutReal Jxp = - (Gxp[jzp] * (Fx[jzp] - Fxp[jz]) - Gxm[jzm] * (Fxm[jz] - Fx[jzm]) - - Gxm[jzp] * (Fx[jzp] - Fxm[jz]) + Gxp[jzm] * (Fxp[jz] - Fx[jzm])); + ((Gxp[jzp] * (Fx[jzp] - Fxp[jz])) - (Gxm[jzm] * (Fxm[jz] - Fx[jzm])) + - (Gxm[jzp] * (Fx[jzp] - Fxm[jz])) + (Gxp[jzm] * (Fxp[jz] - Fx[jzm]))); result(jx, jy, jz) = (Jpp + Jpx + Jxp) * spacingFactor; } @@ -1105,18 +1392,18 @@ Field3D bracket(const Field3D& f, const Field3D& g, BRACKET_METHOD method, const int jzm = jz - 1; // J++ = DDZ(f)*DDX(g) - DDX(f)*DDZ(g) - const BoutReal Jpp = ((Fx[jzp] - Fx[jzm]) * (Gxp[jz] - Gxm[jz]) - - (Fxp[jz] - Fxm[jz]) * (Gx[jzp] - Gx[jzm])); + const BoutReal Jpp = (((Fx[jzp] - Fx[jzm]) * (Gxp[jz] - Gxm[jz])) + - ((Fxp[jz] - Fxm[jz]) * (Gx[jzp] - Gx[jzm]))); // J+x const BoutReal Jpx = - (Gxp[jz] * (Fxp[jzp] - Fxp[jzm]) - Gxm[jz] * (Fxm[jzp] - Fxm[jzm]) - - Gx[jzp] * (Fxp[jzp] - Fxm[jzp]) + Gx[jzm] * (Fxp[jzm] - Fxm[jzm])); + ((Gxp[jz] * (Fxp[jzp] - Fxp[jzm])) - (Gxm[jz] * (Fxm[jzp] - Fxm[jzm])) + - (Gx[jzp] * (Fxp[jzp] - Fxm[jzp])) + (Gx[jzm] * (Fxp[jzm] - Fxm[jzm]))); // Jx+ const BoutReal Jxp = - (Gxp[jzp] * (Fx[jzp] - Fxp[jz]) - Gxm[jzm] * (Fxm[jz] - Fx[jzm]) - - Gxm[jzp] * (Fx[jzp] - Fxm[jz]) + Gxp[jzm] * (Fxp[jz] - Fx[jzm])); + ((Gxp[jzp] * (Fx[jzp] - Fxp[jz])) - (Gxm[jzm] * (Fxm[jz] - Fx[jzm])) + - (Gxm[jzp] * (Fx[jzp] - Fxm[jz])) + (Gxp[jzm] * (Fxp[jz] - Fx[jzm]))); result(jx, jy, jz) = (Jpp + Jpx + Jxp) * spacingFactor; } @@ -1131,18 +1418,18 @@ Field3D bracket(const Field3D& f, const Field3D& g, BRACKET_METHOD method, #endif // J++ = DDZ(f)*DDX(g) - DDX(f)*DDZ(g) - const BoutReal Jpp = ((Fx[jzp] - Fx[jzm]) * (Gxp[jz] - Gxm[jz]) - - (Fxp[jz] - Fxm[jz]) * (Gx[jzp] - Gx[jzm])); + const BoutReal Jpp = (((Fx[jzp] - Fx[jzm]) * (Gxp[jz] - Gxm[jz])) + - ((Fxp[jz] - Fxm[jz]) * (Gx[jzp] - Gx[jzm]))); // J+x const BoutReal Jpx = - (Gxp[jz] * (Fxp[jzp] - Fxp[jzm]) - Gxm[jz] * (Fxm[jzp] - Fxm[jzm]) - - Gx[jzp] * (Fxp[jzp] - Fxm[jzp]) + Gx[jzm] * (Fxp[jzm] - Fxm[jzm])); + ((Gxp[jz] * (Fxp[jzp] - Fxp[jzm])) - (Gxm[jz] * (Fxm[jzp] - Fxm[jzm])) + - (Gx[jzp] * (Fxp[jzp] - Fxm[jzp])) + (Gx[jzm] * (Fxp[jzm] - Fxm[jzm]))); // Jx+ const BoutReal Jxp = - (Gxp[jzp] * (Fx[jzp] - Fxp[jz]) - Gxm[jzm] * (Fxm[jz] - Fx[jzm]) - - Gxm[jzp] * (Fx[jzp] - Fxm[jz]) + Gxp[jzm] * (Fxp[jz] - Fx[jzm])); + ((Gxp[jzp] * (Fx[jzp] - Fxp[jz])) - (Gxm[jzm] * (Fxm[jz] - Fx[jzm])) + - (Gxm[jzp] * (Fx[jzp] - Fxm[jz])) + (Gxp[jzm] * (Fxp[jz] - Fx[jzm]))); result(jx, jy, jz) = (Jpp + Jpx + Jxp) * spacingFactor; } @@ -1156,7 +1443,7 @@ Field3D bracket(const Field3D& f, const Field3D& g, BRACKET_METHOD method, } default: { // Use full expression with all terms - result = b0xGrad_dot_Grad(f, g, outloc) / metric->Bxy; + result = b0xGrad_dot_Grad(f, g, outloc) / metric->Bxy(); } } diff --git a/src/mesh/fv_ops.cxx b/src/mesh/fv_ops.cxx index 660fc51abc..afe8088523 100644 --- a/src/mesh/fv_ops.cxx +++ b/src/mesh/fv_ops.cxx @@ -12,6 +12,7 @@ #include "bout/msg_stack.hxx" #include "bout/region.hxx" #include "bout/utils.hxx" +#include namespace { template @@ -71,9 +72,9 @@ Field3D Div_a_Grad_perp(const Field3D& a, const Field3D& f) { // 3D Metric, need yup/ydown fields. // Requires previous communication of metrics // -- should insert communication here? - if (!coord->g23.hasParallelSlices() || !coord->g_23.hasParallelSlices() - || !coord->dy.hasParallelSlices() || !coord->dz.hasParallelSlices() - || !coord->Bxy.hasParallelSlices() || !coord->J.hasParallelSlices()) { + if (!coord->g23().hasParallelSlices() || !coord->g_23().hasParallelSlices() + || !coord->dy().hasParallelSlices() || !coord->dz().hasParallelSlices() + || !coord->Bxy().hasParallelSlices() || !coord->J().hasParallelSlices()) { throw BoutException("metrics have no yup/down!"); } } @@ -89,12 +90,12 @@ Field3D Div_a_Grad_perp(const Field3D& a, const Field3D& f) { // Only in 3D case with FCI do the metrics have parallel slices const bool metric_fci = fci and bout::build::use_metric_3d; - const auto g23 = makeslices(metric_fci, coord->g23); - const auto g_23 = makeslices(metric_fci, coord->g_23); - const auto J = makeslices(metric_fci, coord->J); - const auto dy = makeslices(metric_fci, coord->dy); - const auto dz = makeslices(metric_fci, coord->dz); - const auto Bxy = makeslices(metric_fci, coord->Bxy); + const auto g23 = makeslices(metric_fci, coord->g23()); + const auto g_23 = makeslices(metric_fci, coord->g_23()); + const auto J = makeslices(metric_fci, coord->J()); + const auto dy = makeslices(metric_fci, coord->dy()); + const auto dz = makeslices(metric_fci, coord->dz()); + const auto Bxy = makeslices(metric_fci, coord->Bxy()); // Result of the Y and Z fluxes Field3D yzresult(0.0, mesh); @@ -158,7 +159,7 @@ Field3D Div_a_Grad_perp(const Field3D& a, const Field3D& f) { const BoutReal fout = 0.25 * (a_slice.c[i] + a_slice.c[ikp]) - * (J.c[i] * coord->g33[i] + J.c[ikp] * coord->g33[ikp]) + * (J.c[i] * coord->g33()[i] + J.c[ikp] * coord->g33()[ikp]) * ( // df/dz (f_slice.c[ikp] - f_slice.c[i]) / dz.c[i] // - g_yz * df/dy / SQ(J*B) @@ -211,34 +212,42 @@ Field3D Div_par_K_Grad_par(const Field3D& Kin, const Field3D& fin, bool bndry_fl const auto iyp = i.yp(); const auto iym = i.ym(); - if (bndry_flux || mesh->periodicY(i.x()) || !mesh->lastY(i.x()) - || (i.y() != mesh->yend)) { + const auto yboundary = coord->getYBoundary(); - 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]); + if (bndry_flux + || (not K.isFci() + and (mesh->periodicY(i.x()) || !mesh->lastY(i.x()) || (i.y() != mesh->yend))) + or (K.isFci() and yboundary.contains<+1>(i))) { - const BoutReal gradient = 2. * (fup[iyp] - f[i]) / (coord->dy[i] + coord->dy[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]); + + const BoutReal gradient = + 2. * (fup[iyp] - f[i]) / (coord->dy()[i] + coord->dy()[iyp]); const BoutReal flux = c * J * gradient / g_22; - result[i] += flux / (coord->dy[i] * coord->J[i]); + result[i] += flux / (coord->dy()[i] * coord->J()[i]); } // Calculate flux at lower surface - if (bndry_flux || mesh->periodicY(i.x()) || !mesh->firstY(i.x()) - || (i.y() != mesh->ystart)) { - const BoutReal c = 0.5 * (K[i] + Kdown[iym]); // K at the lower boundary - const BoutReal J = 0.5 * (coord->J[i] + coord->J[iym]); // Jacobian at boundary + if (bndry_flux + || (not K.isFci() + and (mesh->periodicY(i.x()) || !mesh->firstY(i.x()) + || (i.y() != mesh->ystart))) + or (K.isFci() and yboundary.contains<-1>(i))) { + const BoutReal c = 0.5 * (K[i] + Kdown[iym]); // K at the lower boundary + const BoutReal J = 0.5 * (coord->J()[i] + coord->J()[iym]); // Jacobian at boundary - const BoutReal g_22 = 0.5 * (coord->g_22[i] + coord->g_22[iym]); + const BoutReal g_22 = 0.5 * (coord->g_22()[i] + coord->g_22()[iym]); const BoutReal gradient = - 2. * (f[i] - fdown[iym]) / (coord->dy[i] + coord->dy[iym]); + 2. * (f[i] - fdown[iym]) / (coord->dy()[i] + coord->dy()[iym]); const BoutReal flux = c * J * gradient / g_22; - result[i] -= flux / (coord->dy[i] * coord->J[i]); + result[i] -= flux / (coord->dy()[i] * coord->J()[i]); } } @@ -571,103 +580,102 @@ Field3D Div_Perp_Lap(const Field3D& a, const Field3D& f, CELL_LOC outloc) { // Explicit instantiations of flux-limited finite volume methods template Field3D Div_par_fvv(const Field3D& f_in, const Field3D& v_in, - const Field3D& wave_speed_in, bool fixflux = true); + const Field3D& wave_speed_in, bool fixflux); template Field3D Div_par(const Field3D& f_in, const Field3D& v_in, - const Field3D& wave_speed_in, bool fixflux = true); + const Field3D& wave_speed_in, bool fixflux); template Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_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); + bool fixflux, bool dissipative); template Field3D Div_par_fvv_heating(const Field3D& f_in, const Field3D& v_in, const Field3D& wave_speed_in, - Field3D& flow_ylow, bool fixflux = true); + Field3D& flow_ylow, bool fixflux); template Field3D Div_a_Grad_perp_limit(const Field3D& a, const Field3D& g, const Field3D& f); template Field3D Div_par_fvv(const Field3D& f_in, const Field3D& v_in, - const Field3D& wave_speed_in, bool fixflux = true); + const Field3D& wave_speed_in, bool fixflux); template Field3D Div_par(const Field3D& f_in, const Field3D& v_in, - const Field3D& wave_speed_in, bool fixflux = true); + const Field3D& wave_speed_in, bool fixflux); template Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_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); + bool fixflux, bool dissipative); template Field3D Div_par_fvv_heating(const Field3D& f_in, const Field3D& v_in, const Field3D& wave_speed_in, - Field3D& flow_ylow, bool fixflux = true); + Field3D& flow_ylow, bool fixflux); template Field3D Div_a_Grad_perp_limit(const Field3D& a, const Field3D& g, const Field3D& f); template Field3D Div_par_fvv(const Field3D& f_in, const Field3D& v_in, - const Field3D& wave_speed_in, bool fixflux = true); + const Field3D& wave_speed_in, bool fixflux); template Field3D Div_par(const Field3D& f_in, const Field3D& v_in, - const Field3D& wave_speed_in, bool fixflux = true); + const Field3D& wave_speed_in, bool fixflux); template Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_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); + bool fixflux, bool dissipative); template Field3D Div_par_fvv_heating(const Field3D& f_in, const Field3D& v_in, const Field3D& wave_speed_in, - Field3D& flow_ylow, bool fixflux = true); + Field3D& flow_ylow, bool fixflux); template Field3D Div_a_Grad_perp_limit(const Field3D& a, const Field3D& g, const Field3D& f); template Field3D Div_par_fvv(const Field3D& f_in, const Field3D& v_in, - const Field3D& wave_speed_in, bool fixflux = true); + const Field3D& wave_speed_in, bool fixflux); template Field3D Div_par(const Field3D& f_in, const Field3D& v_in, - const Field3D& wave_speed_in, bool fixflux = true); + const Field3D& wave_speed_in, bool fixflux); template Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_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); + bool fixflux, bool dissipative); template Field3D Div_par_fvv_heating(const Field3D& f_in, const Field3D& v_in, const Field3D& wave_speed_in, Field3D& flow_ylow, - bool fixflux = true); + bool fixflux); template Field3D Div_a_Grad_perp_limit(const Field3D& a, const Field3D& g, const Field3D& f); template Field3D Div_par_fvv(const Field3D& f_in, const Field3D& v_in, - const Field3D& wave_speed_in, bool fixflux = true); + const Field3D& wave_speed_in, bool fixflux); template Field3D Div_par(const Field3D& f_in, const Field3D& v_in, - const Field3D& wave_speed_in, bool fixflux = true); + const Field3D& wave_speed_in, bool fixflux); template Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_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); + bool fixflux, bool dissipative); template Field3D Div_par_fvv_heating(const Field3D& f_in, const Field3D& v_in, const Field3D& wave_speed_in, - Field3D& flow_ylow, bool fixflux = true); + Field3D& flow_ylow, bool fixflux); template Field3D Div_a_Grad_perp_limit(const Field3D& a, const Field3D& g, const Field3D& f); template Field3D Div_par_fvv(const Field3D& f_in, const Field3D& v_in, - const Field3D& wave_speed_in, - bool fixflux = true); + const Field3D& wave_speed_in, bool fixflux); template Field3D Div_par(const Field3D& f_in, const Field3D& v_in, - const Field3D& wave_speed_in, bool fixflux = true); + const Field3D& wave_speed_in, bool fixflux); template Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_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); + bool fixflux, bool dissipative); template Field3D Div_par_fvv_heating(const Field3D& f_in, const Field3D& v_in, const Field3D& wave_speed_in, - Field3D& flow_ylow, bool fixflux = true); + Field3D& flow_ylow, bool fixflux); template Field3D Div_a_Grad_perp_limit(const Field3D& a, const Field3D& g, const Field3D& f); template Field3D Div_par_fvv(const Field3D& f_in, const Field3D& v_in, - const Field3D& wave_speed_in, bool fixflux = true); + const Field3D& wave_speed_in, bool fixflux); template Field3D Div_par(const Field3D& f_in, const Field3D& v_in, - const Field3D& wave_speed_in, bool fixflux = true); + const Field3D& wave_speed_in, bool fixflux); template Field3D Div_f_v(const Field3D& n_in, const Vector3D& v, bool bndry_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); + bool fixflux, bool dissipative); template Field3D Div_par_fvv_heating(const Field3D& f_in, const Field3D& v_in, const Field3D& wave_speed_in, - Field3D& flow_ylow, bool fixflux = true); + Field3D& flow_ylow, bool fixflux); template Field3D Div_a_Grad_perp_limit(const Field3D& a, const Field3D& g, const Field3D& f); } // Namespace FV diff --git a/src/mesh/g_values.cxx b/src/mesh/g_values.cxx new file mode 100644 index 0000000000..3ed6b896ec --- /dev/null +++ b/src/mesh/g_values.cxx @@ -0,0 +1,26 @@ +#include "bout/g_values.hxx" +#include "bout/coordinates.hxx" +#include "bout/derivs.hxx" +#include "bout/mesh.hxx" +#include "bout/metric_tensor.hxx" + +GValues::GValues(const Coordinates& coordinates) { + + const auto& contravariantMetricTensor = coordinates.getContravariantMetricTensor(); + const auto& J = coordinates.J(); + + const auto& g11 = contravariantMetricTensor.g11(); + const auto& g22 = contravariantMetricTensor.g22(); + const auto& g33 = contravariantMetricTensor.g33(); + const auto& g12 = contravariantMetricTensor.g12(); + const auto& g13 = contravariantMetricTensor.g13(); + const auto& g23 = contravariantMetricTensor.g23(); + + auto* mesh = J.getMesh(); + + G1_m = (DDX(J * g11) + DDY(J * g12) + DDZ(J * g13)) / J; + G2_m = (DDX(J * g12) + DDY(J * g22) + DDZ(J * g23)) / J; + G3_m = (DDX(J * g13) + DDY(J * g23) + DDZ(J * g33)) / J; + + mesh->communicate_no_slices(G1_m, G2_m, G3_m); +} diff --git a/src/mesh/impls/bout/boutmesh.cxx b/src/mesh/impls/bout/boutmesh.cxx index 0e3c8bdcf3..f3482f4d40 100644 --- a/src/mesh/impls/bout/boutmesh.cxx +++ b/src/mesh/impls/bout/boutmesh.cxx @@ -42,10 +42,10 @@ #include #include #include +#include #include #include #include -#include #include #include #include diff --git a/src/mesh/impls/bout/boutmesh.hxx b/src/mesh/impls/bout/boutmesh.hxx index a6d97a8944..abe7be9809 100644 --- a/src/mesh/impls/bout/boutmesh.hxx +++ b/src/mesh/impls/bout/boutmesh.hxx @@ -255,6 +255,8 @@ protected: int jyseps1_2; int jyseps2_2; int ny_inner; + + auto operator<=>(const YDecompositionIndices&) const = default; }; /// Version of `setYDecompositionindices` that returns the values @@ -353,6 +355,8 @@ protected: int UDATA_INDEST, UDATA_OUTDEST, UDATA_XSPLIT; int DDATA_INDEST, DDATA_OUTDEST, DDATA_XSPLIT; int IDATA_DEST, ODATA_DEST; // X inner and outer destinations + + auto operator<=>(const ConnectionInfo&) const = default; }; /// Return the communication parameters as calculated by `topology` diff --git a/src/mesh/mesh.cxx b/src/mesh/mesh.cxx index 549b454ebd..a7d6a0872e 100644 --- a/src/mesh/mesh.cxx +++ b/src/mesh/mesh.cxx @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -26,6 +27,7 @@ #include #include +#include #include #include #include @@ -38,6 +40,8 @@ #include "impls/bout/boutmesh.hxx" +namespace fs = std::filesystem; + MeshFactory::ReturnType MeshFactory::create(Options* options, GridDataSource* source) const { return create(getType(options), options, source); @@ -68,14 +72,37 @@ MeshFactory::ReturnType MeshFactory::create(const std::string& type, Options* op grid_name1, grid_name); } } - output << "\nGetting grid data from file " << grid_name << "\n"; - // Create a grid file, using specified format if given - const auto grid_ext = - (*options)["format"].withDefault(Options::root()["format"].withDefault("")); + // Resolve mesh files relative to datadir first, then the current directory. + const auto datadir = fs::path(Options::root()["datadir"].withDefault("data")); + const auto grid_path = fs::path(grid_name); + auto full_path = datadir / grid_path; + + const bool full_path_exists = fs::exists(full_path); + const bool grid_path_exists = fs::exists(grid_path); + + if (full_path_exists and grid_path_exists and !fs::equivalent(full_path, grid_path)) { + throw BoutException( + "Ambiguous grid file path `{:s}`: found both `{:s}` (relative to " + "`datadir`) and `{:s}` (relative to the current working directory).\n" + "Please specify an explicit path.", + grid_name, full_path.string(), grid_path.string()); + } + + if (!full_path_exists and !grid_path_exists) { + throw BoutException( + "Could not find grid file `{:s}`.\n" + "Looked for `{:s}` (relative to `datadir`) and `{:s}` (relative to " + "the current working directory).", + grid_name, full_path.string(), grid_path.string()); + } + + const auto resolved_path = full_path_exists ? full_path : grid_path; + + output << "\nGetting grid data from file " << resolved_path << "\n"; // Create a grid file - source = static_cast(new GridFile(grid_name)); + source = static_cast(new GridFile(resolved_path)); } else { output << "\nGetting grid data from options\n"; source = static_cast(new GridFromOptions(options)); @@ -556,12 +583,39 @@ Mesh::createDefaultCoordinates(const CELL_LOC location, if (location == CELL_CENTRE || location == CELL_DEFAULT) { // Initialize coordinates from input return std::make_shared(this, options); - } else { - // Interpolate coordinates from CELL_CENTRE version - return std::make_shared(this, options, location, - getCoordinates(CELL_CENTRE), - force_interpolate_from_centre); } + // Interpolate coordinates from CELL_CENTRE version + return std::make_shared(this, options, location, + getCoordinates(CELL_CENTRE), + force_interpolate_from_centre); +} + +std::shared_ptr Mesh::getCoordinatesSmart(CELL_LOC location) { + ASSERT1(location != CELL_DEFAULT); + ASSERT1(location != CELL_VSHIFT); + + auto found = coords_map.find(location); + if (found != coords_map.end()) { + // True branch most common, returns immediately + return found->second; + } + + // No coordinate system set. Create default + // Note that this can't be allocated here due to incomplete type + // (circular dependency between Mesh and Coordinates) + auto inserted = coords_map.emplace(location, nullptr); + auto force_interpolate_from_centre = false; + inserted.first->second = + createDefaultCoordinates(location, force_interpolate_from_centre); + + auto recalculate_staggered = false; + inserted.first->second->recalculateAndReset(recalculate_staggered, + force_interpolate_from_centre); + + inserted.first->second->communicateMetricTensor(); + inserted.first->second->communicateDz(); + + return inserted.first->second; } const Region<>& Mesh::getRegion3D(const std::string& region_name) const { @@ -774,8 +828,14 @@ void Mesh::recalculateStaggeredCoordinates() { continue; } - *coords_map[location] = std::move(*createDefaultCoordinates(location, true)); - coords_map[location]->geometry(false, true); + auto force_interpolate_from_centre = true; + Coordinates& new_coordinates = + *createDefaultCoordinates(location, force_interpolate_from_centre); + + auto recalculate_staggered = false; + new_coordinates.recalculateAndReset(recalculate_staggered, + force_interpolate_from_centre); + *coords_map[location] = std::move(new_coordinates); } } @@ -865,3 +925,10 @@ std::optional Mesh::getCommonRegion(std::optional lhs, } return region3Dintersect[pos]; } + +namespace bout::detail { +std::optional meshGetCommonRegionID(Mesh* mesh, std::optional regionID1, + std::optional regionID2) { + return mesh->getCommonRegion(regionID1, regionID2); +} +} // namespace bout::detail diff --git a/src/mesh/metric_tensor.cxx b/src/mesh/metric_tensor.cxx new file mode 100644 index 0000000000..1347f88a7e --- /dev/null +++ b/src/mesh/metric_tensor.cxx @@ -0,0 +1,196 @@ +#include "bout/metric_tensor.hxx" +#include "invert3x3.hxx" +#include "bout/bout_types.hxx" +#include "bout/boutexception.hxx" +#include "bout/field2d.hxx" +#include "bout/mesh.hxx" +#include "bout/output.hxx" +#include "bout/region.hxx" +#include "bout/utils.hxx" + +#include + +#include +#include +#include + +MetricTensor::MetricTensor(FieldMetric g11, FieldMetric g22, FieldMetric g33, + FieldMetric g12, FieldMetric g13, FieldMetric g23) + : g11_m(std::move(g11)), g22_m(std::move(g22)), g33_m(std::move(g33)), + g12_m(std::move(g12)), g13_m(std::move(g13)), g23_m(std::move(g23)) {} + +MetricTensor::MetricTensor(const BoutReal g11, const BoutReal g22, const BoutReal g33, + const BoutReal g12, const BoutReal g13, const BoutReal g23, + Mesh* mesh) + : g11_m(g11, mesh), g22_m(g22, mesh), g33_m(g33, mesh), g12_m(g12, mesh), + g13_m(g13, mesh), g23_m(g23, mesh) {} + +void MetricTensor::check(int ystart) { + const bool non_identity_parallel_transform = + g11_m.hasParallelSlices() && &g11_m.ynext(1) != &g11_m; + + // Diagonal metric components should be finite + bout::checkFinite(g11_m, "g11", "RGN_NOCORNERS"); + bout::checkFinite(g22_m, "g22", "RGN_NOCORNERS"); + bout::checkFinite(g33_m, "g33", "RGN_NOCORNERS"); + if (non_identity_parallel_transform) { + for (int dy = 1; dy <= ystart; ++dy) { + for (const auto sign : {1, -1}) { + const auto region = fmt::format("RGN_YPAR_{:+d}", sign * dy); + bout::checkFinite(g11_m.ynext(sign * dy), "g11.ynext", region); + bout::checkFinite(g22_m.ynext(sign * dy), "g22.ynext", region); + bout::checkFinite(g33_m.ynext(sign * dy), "g33.ynext", region); + } + } + } + + // Diagonal metric components should be positive + bout::checkPositive(g11_m, "g11", "RGN_NOCORNERS"); + bout::checkPositive(g22_m, "g22", "RGN_NOCORNERS"); + bout::checkPositive(g33_m, "g33", "RGN_NOCORNERS"); + if (non_identity_parallel_transform) { + for (int dy = 1; dy <= ystart; ++dy) { + for (const auto sign : {1, -1}) { + const auto region = fmt::format("RGN_YPAR_{:+d}", sign * dy); + bout::checkPositive(g11_m.ynext(sign * dy), "g11.ynext", region); + bout::checkPositive(g22_m.ynext(sign * dy), "g22.ynext", region); + bout::checkPositive(g33_m.ynext(sign * dy), "g33.ynext", region); + } + } + } + + // Off-diagonal metric components should be finite + bout::checkFinite(g12_m, "g12", "RGN_NOCORNERS"); + bout::checkFinite(g13_m, "g13", "RGN_NOCORNERS"); + bout::checkFinite(g23_m, "g23", "RGN_NOCORNERS"); + // Check off-diagonal separately, might not have them even if we have parallel + // slices for the diagonal components + if (g23_m.hasParallelSlices() && &g23_m.ynext(1) != &g23_m) { + for (int dy = 1; dy <= ystart; ++dy) { + for (const auto sign : {1, -1}) { + const auto region = fmt::format("RGN_YPAR_{:+d}", sign * dy); + bout::checkFinite(g12_m.ynext(sign * dy), "g12.ynext", region); + bout::checkFinite(g13_m.ynext(sign * dy), "g13.ynext", region); + bout::checkFinite(g23_m.ynext(sign * dy), "g23.ynext", region); + } + } + } +} + +namespace { +template +auto inverse_impl(const MetricTensor& metric, const std::string& region) + -> InverseMetric { + // Perform inversion of g{ij} to get g^{ij}, or vice versa + auto matrix = Matrix(3, 3); + + bout::FieldMetric g_11 = emptyFrom(metric.g11()); + bout::FieldMetric g_22 = emptyFrom(metric.g22()); + bout::FieldMetric g_33 = emptyFrom(metric.g33()); + bout::FieldMetric g_12 = emptyFrom(metric.g12()); + bout::FieldMetric g_13 = emptyFrom(metric.g13()); + bout::FieldMetric g_23 = emptyFrom(metric.g23()); + + BOUT_FOR_SERIAL(i, metric.g11().getRegion(region)) { + matrix(0, 0) = metric.g11()[i]; + matrix(1, 1) = metric.g22()[i]; + matrix(2, 2) = metric.g33()[i]; + + matrix(0, 1) = matrix(1, 0) = metric.g12()[i]; + matrix(1, 2) = matrix(2, 1) = metric.g23()[i]; + matrix(0, 2) = matrix(2, 0) = metric.g13()[i]; + + if (const auto det = bout::invert3x3(matrix); det.has_value()) { + throw BoutException("ERROR: metric tensor is singular at ({}, {}), determinant: {}", + i.x(), i.y(), det.value()); + } + + g_11[i] = matrix(0, 0); + g_22[i] = matrix(1, 1); + g_33[i] = matrix(2, 2); + g_12[i] = matrix(0, 1); + g_13[i] = matrix(0, 2); + g_23[i] = matrix(1, 2); + } + + const BoutReal diagonal_maxerr = + BOUTMAX(max(abs((g_11 * g_11 + g_12 * g_12 + g_13 * g_13) - 1)), + max(abs((g_12 * g_12 + g_22 * g_22 + g_23 * g_23) - 1)), + max(abs((g_13 * g_13 + g_23 * g_23 + g_33 * g_33) - 1))); + + output_info.write("\tMaximum error in diagonal inversion is {:e}\n", diagonal_maxerr); + + const BoutReal off_diagonal_maxerr = + BOUTMAX(max(abs(g_11 * g_12 + g_12 * g_22 + g_13 * g_23)), + max(abs(g_11 * g_13 + g_12 * g_23 + g_13 * g_33)), + max(abs(g_12 * g_13 + g_22 * g_23 + g_23 * g_33))); + + output_info.write("\tMaximum error in off-diagonal inversion is {:e}\n", + off_diagonal_maxerr); + return InverseMetric(g_11, g_22, g_33, g_12, g_13, g_23); +} +} // namespace + +auto CovariantMetricTensor::inverse(const std::string& region, bool communicate) + -> ContravariantMetricTensor { + auto result = inverse_impl(*this, region); + if (communicate) { + result.communicate(); + } + return result; +} + +auto ContravariantMetricTensor::inverse(const std::string& region, bool communicate) + -> CovariantMetricTensor { + auto result = inverse_impl(*this, region); + if (communicate) { + result.communicate(); + } + return result; +} + +template +void MetricTensor::normaliseMetric(const MetricNormaliser& norm, const F& op) { + if (norm.g.has_value()) { + op(g11_m, norm.g); + op(g22_m, norm.g); + op(g33_m, norm.g); + op(g12_m, norm.g); + op(g13_m, norm.g); + op(g23_m, norm.g); + } else { + op(g11_m, norm.g11); + op(g22_m, norm.g22); + op(g33_m, norm.g33); + op(g12_m, norm.g12); + op(g13_m, norm.g13); + op(g23_m, norm.g23); + } +} + +void ContravariantMetricTensor::normaliseMetric(const MetricNormaliser& norm) { + MetricTensor::normaliseMetric(norm, [](FieldMetric& f, auto fac) { + if (fac.has_value()) { + if (f.hasParallelSlices()) { + f.asField3DParallel() *= fac.value(); + } else { + f *= fac.value(); + } + } + }); +} +void CovariantMetricTensor::normaliseMetric(const MetricNormaliser& norm) { + MetricTensor::normaliseMetric(norm, [](FieldMetric& f, auto fac) { + if (fac.has_value()) { + if (f.hasParallelSlices()) { + f.asField3DParallel() /= fac.value(); + } else { + f /= fac.value(); + } + } + }); +} + +void MetricTensor::communicate() { + g11_m.getMesh()->communicate_no_slices(g11_m, g22_m, g33_m, g12_m, g13_m, g23_m); +} diff --git a/src/mesh/parallel/fci.cxx b/src/mesh/parallel/fci.cxx index aebfeb654c..6b68a6635c 100644 --- a/src/mesh/parallel/fci.cxx +++ b/src/mesh/parallel/fci.cxx @@ -42,15 +42,15 @@ #include "bout/boundary_region_iter.hxx" #include "bout/bout_types.hxx" #include "bout/boutexception.hxx" -#include "bout/build_defines.hxx" +#include "bout/coordinates.hxx" #include "bout/field2d.hxx" #include "bout/field3d.hxx" #include "bout/field_data.hxx" +#include "bout/interpolation_xz.hxx" #include "bout/mesh.hxx" #include "bout/msg_stack.hxx" #include "bout/options.hxx" #include "bout/parallel_boundary_op.hxx" -#include "bout/parallel_boundary_region.hxx" #include "bout/paralleltransform.hxx" #include "bout/region.hxx" @@ -58,65 +58,12 @@ #include #include -#include #include #include #include -#include -using namespace std::string_view_literals; using bout::boundary::BoundaryRegionFCI; -namespace { -// Get a unique name for a field based on the sign/magnitude of the offset -std::string parallel_slice_field_name(const std::string& field, int offset) { - const std::string direction = (offset > 0) ? "forward" : "backward"; - // We only have a suffix for parallel slices beyond the first - // This is for backwards compatibility - const std::string slice_suffix = - (std::abs(offset) > 1) ? "_" + std::to_string(std::abs(offset)) : ""; - return direction + "_" + field + slice_suffix; -}; - -#if BOUT_USE_METRIC_3D -void load_parallel_metric_component(std::string name, Field3D& component, int offset) { - Mesh* mesh = component.getMesh(); - Field3D tmp{mesh}; - const auto pname = parallel_slice_field_name(name, offset); - if (mesh->get(tmp, pname, 0.0, false) != 0) { - throw BoutException("Could not read {:s} from grid file!\n" - " Fix it up with `zoidberg-update-parallel-metrics `", - pname); - } - if (!component.hasParallelSlices()) { - component.splitParallelSlices(); - component.disallowCalcParallelSlices(); - component.resetRegionParallel(true); - } - auto& pcom = component.ynext(offset); - pcom.allocate(); - BOUT_FOR(i, component.getRegion("RGN_NOBNDRY")) { pcom[i.yp(offset)] = tmp[i]; } -} - -void load_parallel_metric_components(Coordinates* coords, int offset) { -#define LOAD_PAR(var) load_parallel_metric_component(#var, coords->var, offset) - LOAD_PAR(g11); - LOAD_PAR(g22); - LOAD_PAR(g33); - LOAD_PAR(g13); - LOAD_PAR(g_11); - LOAD_PAR(g_22); - LOAD_PAR(g_33); - LOAD_PAR(g_13); - LOAD_PAR(dy); - LOAD_PAR(Bxy); - -#undef LOAD_PAR -} -#endif - -} // namespace - FCIMap::FCIMap(Mesh& mesh, [[maybe_unused]] const Coordinates::FieldMetric& dy, Options& options, int offset, const std::shared_ptr& inner_boundary, @@ -155,29 +102,31 @@ FCIMap::FCIMap(Mesh& mesh, [[maybe_unused]] const Coordinates::FieldMetric& dy, map_mesh->get(R, "R", 0.0, false); map_mesh->get(Z, "Z", 0.0, false); + using bout::parallelSliceFieldName; + // If we can't read in any of these fields, things will silently not // work, so best throw - if (map_mesh->get(xt_prime, parallel_slice_field_name("xt_prime", offset), 0.0, false) + if (map_mesh->get(xt_prime, parallelSliceFieldName("xt_prime", offset), 0.0, false) != 0) { throw BoutException("Could not read {:s} from grid file!\n" " Either add it to the grid file, or reduce MYG", - parallel_slice_field_name("xt_prime", offset)); + parallelSliceFieldName("xt_prime", offset)); } - if (map_mesh->get(zt_prime, parallel_slice_field_name("zt_prime", offset), 0.0, false) + if (map_mesh->get(zt_prime, parallelSliceFieldName("zt_prime", offset), 0.0, false) != 0) { throw BoutException("Could not read {:s} from grid file!\n" " Either add it to the grid file, or reduce MYG", - parallel_slice_field_name("zt_prime", offset)); + parallelSliceFieldName("zt_prime", offset)); } - if (map_mesh->get(R_prime, parallel_slice_field_name("R", offset), 0.0, false) != 0) { + if (map_mesh->get(R_prime, parallelSliceFieldName("R", offset), 0.0, false) != 0) { throw BoutException("Could not read {:s} from grid file!\n" " Either add it to the grid file, or reduce MYG", - parallel_slice_field_name("R", offset)); + parallelSliceFieldName("R", offset)); } - if (map_mesh->get(Z_prime, parallel_slice_field_name("Z", offset), 0.0, false) != 0) { + if (map_mesh->get(Z_prime, parallelSliceFieldName("Z", offset), 0.0, false) != 0) { throw BoutException("Could not read {:s} from grid file!\n" " Either add it to the grid file, or reduce MYG", - parallel_slice_field_name("Z", offset)); + parallelSliceFieldName("Z", offset)); } // Cell corners @@ -467,24 +416,3 @@ void FCITransform::outputVars(Options& output_options) { output_options["R"].force(R, "FCI"); output_options["Z"].force(Z, "FCI"); } - -void FCITransform::loadParallelMetrics([[maybe_unused]] Coordinates* coords) { -#if BOUT_USE_METRIC_3D - output_info.write("\tLoading parallel metrics\n"); - const Coordinates::FieldMetric JB0 = coords->J * coords->Bxy; - coords->J.splitParallelSlices(); - coords->J.disallowCalcParallelSlices(); - coords->J.resetRegionParallel(true); - for (int i = 1; i <= mesh.ystart; ++i) { - load_parallel_metric_components(coords, -i); - load_parallel_metric_components(coords, i); - - coords->J.ynext(i).allocate(); - coords->J.ynext(-i).allocate(); - BOUT_FOR(j, JB0.getRegion("RGN_NOBNDRY")) { - coords->J.ynext(i)[j.yp(i)] = JB0[j] / coords->Bxy.ynext(i)[j.yp(i)]; - coords->J.ynext(-i)[j.yp(-i)] = JB0[j] / coords->Bxy.ynext(-i)[j.yp(-i)]; - } - } -#endif -} diff --git a/src/mesh/parallel/fci.hxx b/src/mesh/parallel/fci.hxx index a90e3e98dd..9fc89296dc 100644 --- a/src/mesh/parallel/fci.hxx +++ b/src/mesh/parallel/fci.hxx @@ -34,7 +34,6 @@ #include #include #include -#include #include #include @@ -126,8 +125,6 @@ public: return false; } - void loadParallelMetrics(Coordinates* coords) override; - protected: void checkInputGrid() override; diff --git a/src/mesh/parallel/shiftedmetric.cxx b/src/mesh/parallel/shiftedmetric.cxx index 705c48e944..972cb58330 100644 --- a/src/mesh/parallel/shiftedmetric.cxx +++ b/src/mesh/parallel/shiftedmetric.cxx @@ -6,7 +6,6 @@ * */ -#include "bout/parallel_boundary_region.hxx" #include "bout/paralleltransform.hxx" #include #include @@ -15,8 +14,6 @@ #include #include -#include - #if BOUT_HAS_CUDA #include #include diff --git a/src/mesh/parallel_boundary_op.cxx b/src/mesh/parallel_boundary_op.cxx index dc376e7e63..713825aaf4 100644 --- a/src/mesh/parallel_boundary_op.cxx +++ b/src/mesh/parallel_boundary_op.cxx @@ -8,7 +8,7 @@ #include "bout/mesh.hxx" #include "bout/output.hxx" -BoutReal BoundaryOpPar::getValue(const bout::boundary::BoundaryRegionIterFCI& bndry, +BoutReal BoundaryOpPar::getValue(const bout::boundary::BoundaryRegionFCI::Point& bndry, BoutReal t) { switch (value_type) { case ValueType::GEN: @@ -24,7 +24,7 @@ BoutReal BoundaryOpPar::getValue(const bout::boundary::BoundaryRegionIterFCI& bn } } -BoutReal BoundaryOpPar::getValue(const bout::boundary::BoundaryRegionIterX& bndry, +BoutReal BoundaryOpPar::getValue(const bout::boundary::BoundaryRegionX::Iterator& bndry, [[maybe_unused]] BoutReal t) { switch (value_type) { case ValueType::FIELD: @@ -37,7 +37,7 @@ BoutReal BoundaryOpPar::getValue(const bout::boundary::BoundaryRegionIterX& bndr } } -BoutReal BoundaryOpPar::getValue(const bout::boundary::BoundaryRegionIterY& bndry, +BoutReal BoundaryOpPar::getValue(const bout::boundary::BoundaryRegionY::Iterator& bndry, [[maybe_unused]] BoutReal t) { switch (value_type) { case ValueType::FIELD: diff --git a/src/mesh/petsc_operators.cxx b/src/mesh/petsc_operators.cxx index 573044a0a1..42a35fbf5d 100644 --- a/src/mesh/petsc_operators.cxx +++ b/src/mesh/petsc_operators.cxx @@ -333,14 +333,15 @@ PetscOperators::Parallel PetscOperators::getParallel() const { auto* coords = mesh->getCoordinates(); // Parallel spacing in cell space - Field3D dl = Coordinates::FieldMetric{coords->dy * sqrt(coords->g_22)}; + Field3D dl = Coordinates::FieldMetric{coords->dy() * sqrt(coords->g_22())}; dl.splitParallelSlices(); dl.yup() = 0.0; dl.ydown() = 0.0; dl.applyParallelBoundary("parallel_neumann_o1"); // Cell volume - Field3D dV = Coordinates::FieldMetric{coords->J * coords->dx * coords->dy * coords->dz}; + Field3D dV = + Coordinates::FieldMetric{coords->J() * coords->dx() * coords->dy() * coords->dz()}; dV.splitParallelSlices(); dV.yup() = 0.0; dV.ydown() = 0.0; diff --git a/src/mesh/surfaceiter.cxx b/src/mesh/surfaceiter.cxx index d687ccd68e..a91b19d503 100644 --- a/src/mesh/surfaceiter.cxx +++ b/src/mesh/surfaceiter.cxx @@ -2,6 +2,7 @@ #include #include +#include #include int SurfaceIter::ySize() { return m->ySize(xpos); } diff --git a/src/mesh/tokamak_coordinates.cxx b/src/mesh/tokamak_coordinates.cxx index af8b6335bf..8f651eec23 100644 --- a/src/mesh/tokamak_coordinates.cxx +++ b/src/mesh/tokamak_coordinates.cxx @@ -2,37 +2,38 @@ #include #include #include +#include #include #include namespace bout { TokamakCoordinates set_tokamak_coordinates(Mesh& mesh, BoutReal Lbar, BoutReal Bbar, bool no_shear, BoutReal shear_factor) { - Field2D Rxy; + FieldMetric Rxy; mesh.get(Rxy, "Rxy"); // [m] Rxy /= Lbar; - Field2D Zxy; + FieldMetric Zxy; mesh.get(Zxy, "Zxy"); // [m] Zxy /= Lbar; - Field2D Bpxy; + FieldMetric Bpxy; mesh.get(Bpxy, "Bpxy"); // [T] Bpxy /= Bbar; - Field2D Btxy; + FieldMetric Btxy; mesh.get(Btxy, "Btxy"); // [T] Btxy /= Bbar; - Field2D Bxy; + FieldMetric Bxy; mesh.get(Bxy, "Bxy"); // [T] Bxy /= Bbar; - Field2D hthe; + FieldMetric hthe; mesh.get(hthe, "hthe"); // [m / radian] hthe /= Lbar; - Coordinates::FieldMetric I; + bout::FieldMetric I; if (no_shear) { I = 0.0; } else { @@ -41,37 +42,54 @@ TokamakCoordinates set_tokamak_coordinates(Mesh& mesh, BoutReal Lbar, BoutReal B const auto I_unnormalised = I; I *= Lbar * Lbar * Bbar * shear_factor; - Coordinates::FieldMetric dx; + bout::FieldMetric dx; if (mesh.get(dx, "dpsi") != 0) { - dx = mesh.getCoordinates()->dx; + dx = mesh.getCoordinates()->dx(); } dx /= Lbar * Lbar * Bbar; const BoutReal sign_of_bp = min(Bpxy, true) < 0.0 ? -1.0 : 1.0; - auto* coords = mesh.getCoordinates(); + auto* coord = mesh.getCoordinates(); - coords->Bxy = Bxy; - coords->dx = dx; + const FieldMetric g11 = SQ(Rxy * Bpxy); + const FieldMetric g22 = 1.0 / SQ(hthe); + const FieldMetric g33 = SQ(I) * g11 + SQ(Bxy) / g11; + const FieldMetric g12 = 0.0; + const FieldMetric g13 = -I * g11; + const FieldMetric g23 = -sign_of_bp * Btxy / (hthe * Bpxy * Rxy); - coords->g11 = SQ(Rxy * Bpxy); - coords->g22 = 1.0 / SQ(hthe); - coords->g33 = SQ(I) * coords->g11 + SQ(Bxy) / coords->g11; - coords->g12 = 0.0; - coords->g13 = -I * coords->g11; - coords->g23 = -sign_of_bp * Btxy / (hthe * Bpxy * Rxy); + const FieldMetric g_11 = 1.0 / g11 + SQ(I * Rxy); + const FieldMetric g_22 = SQ(Bxy * hthe / Bpxy); + const FieldMetric g_33 = Rxy * Rxy; + const FieldMetric g_12 = sign_of_bp * Btxy * hthe * I * Rxy / Bpxy; + const FieldMetric g_13 = I * Rxy * Rxy; + const FieldMetric g_23 = sign_of_bp * Btxy * hthe * Rxy / Bpxy; - coords->J = hthe / Bpxy; + coord->setMetricTensor(ContravariantMetricTensor(g11, g22, g33, g12, g13, g23), + CovariantMetricTensor(g_11, g_22, g_33, g_12, g_13, g_23)); - coords->g_11 = 1.0 / coords->g11 + SQ(I * Rxy); - coords->g_22 = SQ(Bxy * hthe / Bpxy); - coords->g_33 = Rxy * Rxy; - coords->g_12 = sign_of_bp * Btxy * hthe * I * Rxy / Bpxy; - coords->g_13 = I * Rxy * Rxy; - coords->g_23 = sign_of_bp * Btxy * hthe * Rxy / Bpxy; - - coords->geometry(); + coord->setJ(FieldMetric{hthe / Bpxy}); + coord->setBxy(Bxy); + coord->setDx(dx); return {Rxy, Zxy, Bpxy, Btxy, Bxy, hthe, I, I_unnormalised}; } + +MetricNormaliser TokamakOrFCIMetricNormaliser(const Mesh* mesh, BoutReal Bnorm, + BoutReal rho_s0) { + if (mesh->isFci()) { + return {.g{SQ(rho_s0)}, .J{rho_s0 * rho_s0 * rho_s0}, .Bxy{Bnorm}}; + } + return {.g11{1 / SQ(Bnorm * rho_s0)}, + .g22{SQ(rho_s0)}, + .g33{SQ(rho_s0)}, + .g12{1 / Bnorm}, + .g13{1 / Bnorm}, + .g23{SQ(rho_s0)}, + .dx{rho_s0 * rho_s0 * Bnorm}, + .J{rho_s0 / Bnorm}, + .Bxy{Bnorm}}; +} + } // namespace bout diff --git a/src/mesh/yboundary_regions.cxx b/src/mesh/yboundary_regions.cxx new file mode 100644 index 0000000000..af186cf925 --- /dev/null +++ b/src/mesh/yboundary_regions.cxx @@ -0,0 +1,72 @@ +#include "bout/yboundary_regions.hxx" + +#include "bout/boundary_region.hxx" +#include "bout/boundary_region_iter.hxx" +#include "bout/field_data.hxx" +#include "bout/options.hxx" + +#include + +namespace bout::boundary { +YBoundary::YBoundary(YBndryType type, Options* options_ptr, const Mesh& mesh) + : _contains_low(&mesh, false), _contains_high(&mesh, false) { + bool lower_y = true; + bool upper_y = true; + bool outer_x = true; + bool inner_x = false; + if (options_ptr != nullptr) { + auto& options = *options_ptr; + if (!mesh.isFci()) { + 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); + } else { + outer_x = options["outer_x"].doc("Boundary on outer x?").withDefault(outer_x); + inner_x = options["inner_x"].doc("Boundary on inner x?").withDefault(inner_x); + } + } + switch (type) { + case YBndryType::sheath: + break; + case YBndryType::not_sheath: + lower_y = !lower_y; + upper_y = !upper_y; + outer_x = !outer_x; + inner_x = !inner_x; + break; + case YBndryType::all: + lower_y = true; + upper_y = true; + outer_x = true; + inner_x = true; + } + + 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 { + for (auto& bndry : mesh.getBoundaries()) { + if ((lower_y && bndry->location == BndryLoc::ydown) + or (upper_y && bndry->location == BndryLoc::yup)) { + boundary_regions.push_back( + std::dynamic_pointer_cast(bndry)); + } + } + } + // Cache boundary regions + iter([&](const BoundaryIterator auto& point) { + if (point.dir() == 1) { + _contains_high[point.ind()] = true; + } else if (point.dir() == -1) { + _contains_low[point.ind()] = true; + } + }); +} +} // namespace bout::boundary diff --git a/src/physics/physicsmodel.cxx b/src/physics/physicsmodel.cxx index b34980e0db..21b87a86ad 100644 --- a/src/physics/physicsmodel.cxx +++ b/src/physics/physicsmodel.cxx @@ -2,15 +2,15 @@ * Base class for Physics Models * * Changelog: - * + * * 2013-08 Ben Dudson * * Initial version - * + * ************************************************************************** * Copyright 2013 B.D.Dudson * * Contact: Ben Dudson, bd512@york.ac.uk - * + * * This file is part of BOUT++. * * BOUT++ is free software: you can redistribute it and/or modify diff --git a/src/physics/smoothing.cxx b/src/physics/smoothing.cxx index 1b437b4352..b643a016c8 100644 --- a/src/physics/smoothing.cxx +++ b/src/physics/smoothing.cxx @@ -4,15 +4,15 @@ * * 2014-10-29 Ben Dudson * * Moving averaging routines here from Mesh - * + * * 2010-05-17 Ben Dudson * * Added nonlinear filter - * + * ************************************************************** * Copyright 2010 B.D.Dudson, S.Farley, M.V.Umansky, X.Q.Xu * * Contact: Ben Dudson, bd512@york.ac.uk - * + * * This file is part of BOUT++. * * BOUT++ is free software: you can redistribute it and/or modify @@ -107,7 +107,7 @@ const Field3D smooth_y(const Field3D& f) { Issues ====== - + Assumes every processor has the same domain shape Will only work if X communicator is constant in Y @@ -162,14 +162,14 @@ const Field2D averageX(const Field2D& f) { ====== Creates static arrays - + Not thread safe - + Assumes every processor has the same domain shape - + Will only work if X communicator is constant in Y so no processor/branch cuts in X - + */ const Field3D averageX(const Field3D& f) { Mesh* mesh = f.getMesh(); @@ -349,7 +349,7 @@ BoutReal Vol_Integral([[maybe_unused]] const Field2D& var) { BoutReal Int_Glb; Coordinates* metric = var.getCoordinates(); - auto result = metric->J * var * metric->dx * metric->dy; + auto result = metric->J() * var * metric->dx() * metric->dy(); Int_Glb = Average_XY(result); Int_Glb *= static_cast( diff --git a/src/solver/impls/arkode/arkode.cxx b/src/solver/impls/arkode/arkode.cxx index eaf54fe153..5544290df5 100644 --- a/src/solver/impls/arkode/arkode.cxx +++ b/src/solver/impls/arkode/arkode.cxx @@ -374,9 +374,9 @@ int ArkodeSolver::init() { throw BoutException("Invalid adap_method\n"); } - if (ARKStepSetAdaptivityMethod(arkode_mem, adap_method_int, 1, 1, nullptr) + if (ARKodeSetAdaptivityMethod(arkode_mem, adap_method_int, 1, 1, nullptr) != ARK_SUCCESS) { - throw BoutException("ARKStepSetAdaptivityMethod failed\n"); + throw BoutException("ARKodeSetAdaptivityMethod failed\n"); } #endif @@ -604,12 +604,18 @@ int ArkodeSolver::run() { // Get additional diagnostics long int temp_long_int = 0; + long int temp_long_int2 = 0; ARKodeGetNumSteps(arkode_mem, &temp_long_int); nsteps = int(temp_long_int); - ARKodeGetNumRhsEvals(arkode_mem, 0, &temp_long_int); +#if SUNDIALS_VERSION_AT_LEAST(7, 2, 0) + ARKodeGetNumRhsEvals(arkode_mem, 0, &temp_long_int); // Explicit + ARKodeGetNumRhsEvals(arkode_mem, 1, &temp_long_int2); // Implicit +#else + // This function was deprecated in 7.2.0 + ARKStepGetNumRhsEvals(arkode_mem, &temp_long_int, &temp_long_int2); +#endif nfe_evals = int(temp_long_int); - ARKodeGetNumRhsEvals(arkode_mem, 1, &temp_long_int); - nfi_evals = int(temp_long_int); + nfi_evals = int(temp_long_int2); if (treatment == Treatment::ImEx or treatment == Treatment::Implicit) { ARKodeGetNumNonlinSolvIters(arkode_mem, &temp_long_int); nniters = int(temp_long_int); diff --git a/src/solver/impls/cvode/cvode.cxx b/src/solver/impls/cvode/cvode.cxx index ee8db5e869..a59a546066 100644 --- a/src/solver/impls/cvode/cvode.cxx +++ b/src/solver/impls/cvode/cvode.cxx @@ -37,6 +37,7 @@ #include "bout/bout_types.hxx" #include "bout/boutcomm.hxx" #include "bout/boutexception.hxx" +#include "bout/build_config.hxx" #include "bout/field2d.hxx" #include "bout/field3d.hxx" #include "bout/globals.hxx" @@ -45,6 +46,7 @@ #include "bout/msg_stack.hxx" #include "bout/options.hxx" #include "bout/output.hxx" +#include "bout/petsc_preconditioner.hxx" #include "bout/petsclib.hxx" #include "bout/region.hxx" #include "bout/solver.hxx" @@ -52,6 +54,7 @@ #include "bout/unused.hxx" #include "fmt/core.h" +#include "fmt/format.h" #include #include @@ -68,10 +71,10 @@ #include #include -BOUT_ENUM_CLASS(positivity_constraint, none, positive, non_negative, negative, - non_positive); +BOUT_ENUM_CLASS_NS(bout, positivity_constraint, none, positive, non_negative, negative, + non_positive); -BOUT_ENUM_CLASS(linear_solver, gmres, fgmres, tfqmr, bcgs); +BOUT_ENUM_CLASS_NS(bout, linear_solver, gmres, fgmres, tfqmr, bcgs); // NOLINTBEGIN(readability-identifier-length) namespace { @@ -192,8 +195,9 @@ CvodeSolver::CvodeSolver(Options* opts) if ((*options)["use_precon"].isSet()) { throw BoutException("solver:use_precon is deprecated for CVODE and is now " - "ignored. Use solver:cvode_precon_method=none to disable " - "preconditioning.\n"); + "ignored. To reproduce behaviour of use_precon=true, " + "set cvode_precon_method=user. This enables the Hermes-3 " + "physical preconditioners.\n"); } // Add diagnostics to output @@ -242,6 +246,93 @@ CvodeSolver::~CvodeSolver() { } } +#if BOUT_HAS_PETSC +void CvodeSolver::exportMatrixAndMetadata(bout::JacobianExportKind kind, + + Mat jacobian) { + + Solver::writeOnceJacobianMetadata("cvode"); + Solver::writeJacobianMatrix(kind, jacobian); +} + +void CvodeSolver::saveDiagnosticJacobian(bout::JacobianExportKind kind, Vec x, BoutReal t, + BoutReal gamma) { + if (kind == bout::JacobianExportKind::scaled) { + throw BoutException("solver:jacobian_export_kind=scaled is not supported for CVODE"); + } + + petsc_t = t; + petsc_gamma = gamma; + + PetscPreconditioner diagnostic_preconditioner; + Field3D index = globalIndex(0); + BOUT_DO_PETSC(diagnostic_preconditioner.createJacobianPattern( + index, *options, getLocalN(), n2Dvars(), n3Dvars(), BoutComm::get())); + + if (kind == bout::JacobianExportKind::rhs) { + BOUT_DO_PETSC(diagnostic_preconditioner.updateColoring( + CvodeSolver::petscFormRhsFunction, this)); + } else { + BOUT_DO_PETSC( + diagnostic_preconditioner.updateColoring(CvodeSolver::petscFormFunction, this)); + } + + Vec diagnostic_f{nullptr}; + BOUT_DO_PETSC(VecDuplicate(x, &diagnostic_f)); + if (kind == bout::JacobianExportKind::rhs) { + BOUT_DO_PETSC(CvodeSolver::petscFormRhsFunction(nullptr, x, diagnostic_f, this)); + } else { + BOUT_DO_PETSC(CvodeSolver::petscFormFunction(nullptr, x, diagnostic_f, this)); + } + BOUT_DO_PETSC(MatFDColoringSetF(diagnostic_preconditioner.coloring(), diagnostic_f)); + + Mat diagnostic_jacobian = diagnostic_preconditioner.jacobian(); + BOUT_DO_PETSC(MatZeroEntries(diagnostic_jacobian)); + BOUT_DO_PETSC(MatFDColoringApply(diagnostic_jacobian, + diagnostic_preconditioner.coloring(), x, nullptr)); + BOUT_DO_PETSC(MatAssemblyBegin(diagnostic_jacobian, MAT_FINAL_ASSEMBLY)); + BOUT_DO_PETSC(MatAssemblyEnd(diagnostic_jacobian, MAT_FINAL_ASSEMBLY)); + exportMatrixAndMetadata(kind, diagnostic_jacobian); + BOUT_DO_PETSC(VecDestroy(&diagnostic_f)); +} + +void CvodeSolver::maybeExportJacobian(Mat system_jacobian, Vec x, BoutReal t, + BoutReal gamma) { + if (!save_jacobian + or jacobian_export_trigger != bout::CvodeJacobianExportTrigger::linear_setup) { + return; + } + + if (jacobian_export_kind == bout::JacobianExportKind::system) { + exportMatrixAndMetadata(jacobian_export_kind, system_jacobian); + return; + } + + saveDiagnosticJacobian(jacobian_export_kind, x, t, gamma); +} + +void CvodeSolver::maybeExportOutputJacobian(BoutReal t) { + if (!save_jacobian + or jacobian_export_trigger != bout::CvodeJacobianExportTrigger::output) { + return; + } + + Vec x{nullptr}; + BOUT_DO_PETSC(VecCreate(BoutComm::get(), &x)); + BOUT_DO_PETSC(VecSetSizes(x, getLocalN(), PETSC_DETERMINE)); + BOUT_DO_PETSC(VecSetType(x, VECMPI)); + BOUT_DO_PETSC(VecSetUp(x)); + + BoutReal* xdata = nullptr; + BOUT_DO_PETSC(VecGetArray(x, &xdata)); + save_vars(xdata); + BOUT_DO_PETSC(VecRestoreArray(x, &xdata)); + + saveDiagnosticJacobian(bout::JacobianExportKind::rhs, x, t, 0.0); + BOUT_DO_PETSC(VecDestroy(&x)); +} +#endif + /************************************************************************** * Initialise **************************************************************************/ @@ -400,6 +491,57 @@ int CvodeSolver::init() { N_VDestroy(constraints_vec); } + CvodePreconMethod selected_precon = precon_method; + if (selected_precon == CvodePreconMethod::Auto) { + if (hasPreconditioner()) { + selected_precon = CvodePreconMethod::user; + } else if (bout::build::has_petsc) { + selected_precon = CvodePreconMethod::petsc; + } else { + selected_precon = CvodePreconMethod::bbd; + } + } + + if (save_jacobian) { +#if !BOUT_HAS_PETSC + throw BoutException("solver:save_jacobian for CVODE requires PETSc support."); +#else + if (jacobian_export_kind == bout::JacobianExportKind::scaled) { + throw BoutException( + "solver:jacobian_export_kind=scaled is not supported for CVODE because " + "CVODE does not currently apply solver-coordinate scaling."); + } + if (jacobian_export_kind == bout::JacobianExportKind::system + and jacobian_export_trigger != bout::CvodeJacobianExportTrigger::linear_setup) { + throw BoutException("solver:jacobian_export_kind=system for CVODE requires " + "solver:jacobian_export_trigger=linear_setup."); + } + if (jacobian_export_trigger == bout::CvodeJacobianExportTrigger::output + and jacobian_export_kind != bout::JacobianExportKind::rhs) { + throw BoutException("solver:jacobian_export_trigger=output for CVODE currently " + "supports only solver:jacobian_export_kind=rhs."); + } + if (jacobian_export_trigger == bout::CvodeJacobianExportTrigger::linear_setup) { + if (func_iter) { + throw BoutException("solver:jacobian_export_trigger=linear_setup for CVODE " + "requires Newton iteration (set solver:func_iter=false)."); + } + if (nvector_type == NVectorType::ManyVector) { + throw BoutException("solver:jacobian_export_trigger=linear_setup for CVODE is " + "not supported with solver:nvector=manyvector."); + } + if (selected_precon != CvodePreconMethod::petsc) { + throw BoutException("solver:jacobian_export_trigger=linear_setup for CVODE " + "currently requires solver:cvode_precon_method=petsc " + "(or auto resolving to petsc)."); + } + } + if (!petsc_lib) { + petsc_lib = std::make_unique(); + } +#endif + } + /// Newton method can include Preconditioners and Jacobian function if (func_iter) { output_info.write("\tUsing Functional iteration\n"); @@ -414,17 +556,6 @@ int CvodeSolver::init() { } else { output_info.write("\tUsing Newton iteration\n"); - CvodePreconMethod selected_precon = precon_method; - if (selected_precon == CvodePreconMethod::Auto) { - if (hasPreconditioner()) { - selected_precon = CvodePreconMethod::user; - } else if (bout::build::has_petsc) { - selected_precon = CvodePreconMethod::petsc; - } else { - selected_precon = CvodePreconMethod::bbd; - } - } - auto prectype = SUN_PREC_NONE; if (selected_precon != CvodePreconMethod::none) { if (rightprec) { @@ -436,18 +567,18 @@ int CvodeSolver::init() { switch ((*options)["linear_solver"] .doc("Set linear solver type. Default is gmres.") - .withDefault(linear_solver::gmres)) { - case linear_solver::gmres: + .withDefault(bout::linear_solver::gmres)) { + case bout::linear_solver::gmres: sun_solver = callWithSUNContext(SUNLinSol_SPGMR, suncontext, uvec, prectype, maxl); break; - case linear_solver::fgmres: + case bout::linear_solver::fgmres: sun_solver = callWithSUNContext(SUNLinSol_SPFGMR, suncontext, uvec, prectype, maxl); break; - case linear_solver::tfqmr: + case bout::linear_solver::tfqmr: sun_solver = callWithSUNContext(SUNLinSol_SPTFQMR, suncontext, uvec, prectype, maxl); break; - case linear_solver::bcgs: + case bout::linear_solver::bcgs: sun_solver = callWithSUNContext(SUNLinSol_SPBCGS, suncontext, uvec, prectype, maxl); break; }; @@ -610,17 +741,17 @@ CvodeSolver::create_constraints(const std::vector>& fields) { "positive, non_negative, negative, or " "non_positive.", f.name)) - .withDefault(positivity_constraint::none); + .withDefault(bout::positivity_constraint::none); switch (value) { - case positivity_constraint::none: + case bout::positivity_constraint::none: return 0.0; - case positivity_constraint::positive: + case bout::positivity_constraint::positive: return 2.0; - case positivity_constraint::non_negative: + case bout::positivity_constraint::non_negative: return 1.0; - case positivity_constraint::negative: + case bout::positivity_constraint::negative: return -2.0; - case positivity_constraint::non_positive: + case bout::positivity_constraint::non_positive: return -1.0; default: throw BoutException("Incorrect value for " @@ -974,6 +1105,38 @@ PetscErrorCode CvodeSolver::petscFormFunction(void* UNUSED(dummy), Vec x, Vec f, return PETSC_SUCCESS; } +PetscErrorCode CvodeSolver::petscFormRhsFunction(void* UNUSED(dummy), Vec x, Vec f, + void* ctx) { + auto* s = static_cast(ctx); + + PetscInt length = 0; + PetscCall(VecGetLocalSize(x, &length)); + s->petsc_rhs_tmp.resize(static_cast(length)); + + const BoutReal* xdata = nullptr; + PetscCall(VecGetArrayRead(x, &xdata)); + + BoutReal* fdata = nullptr; + PetscCall(VecGetArray(f, &fdata)); + + try { + s->rhs(s->petsc_t, const_cast(xdata), s->petsc_rhs_tmp.data(), true); + } catch (BoutRhsFail&) { + PetscCall(VecRestoreArrayRead(x, &xdata)); + PetscCall(VecRestoreArray(f, &fdata)); + return 1; + } + + for (PetscInt i = 0; i < length; ++i) { + fdata[i] = s->petsc_rhs_tmp[i]; + } + + PetscCall(VecRestoreArrayRead(x, &xdata)); + PetscCall(VecRestoreArray(f, &fdata)); + + return PETSC_SUCCESS; +} + int CvodeSolver::petscPSetup(BoutReal t, N_Vector yy, N_Vector UNUSED(yp), CvodeBool UNUSED(jok), CvodeBool* jcurPtr, BoutReal gamma, void* user_data) { @@ -1000,6 +1163,12 @@ int CvodeSolver::petscPSetup(BoutReal t, N_Vector yy, N_Vector UNUSED(yp), return 1; } + ierr = MatFDColoringSetF(s->petsc_preconditioner.coloring(), s->petsc_f); + if (ierr != 0) { + VecResetArray(s->petsc_x); + return 1; + } + Mat J = s->petsc_preconditioner.jacobian(); ierr = MatZeroEntries(J); if (ierr != 0) { @@ -1022,6 +1191,8 @@ int CvodeSolver::petscPSetup(BoutReal t, N_Vector yy, N_Vector UNUSED(yp), return 1; } + s->maybeExportJacobian(J, s->petsc_x, t, gamma); + ierr = KSPSetOperators(s->petsc_ksp, J, J); if (ierr == 0) { ierr = KSPSetUp(s->petsc_ksp); diff --git a/src/solver/impls/cvode/cvode.hxx b/src/solver/impls/cvode/cvode.hxx index 6a9f1c1c6f..3cf308dc3a 100644 --- a/src/solver/impls/cvode/cvode.hxx +++ b/src/solver/impls/cvode/cvode.hxx @@ -1,8 +1,6 @@ /************************************************************************** * Interface to SUNDIALS CVODE * - * NOTE: Only one solver can currently be compiled in - * ************************************************************************** * Copyright 2010 - 2026 BOUT++ contributors * @@ -43,13 +41,14 @@ RegisterUnavailableSolver #else #include "../../sundials_nvector_interface.hxx" +#include "../arkode/temporal_filtering.hxx" #include "bout/bout_types.hxx" +#include "bout/petsc_preconditioner.hxx" #include "bout/region.hxx" #include "bout/sundials_backports.hxx" #include "../arkode/temporal_filtering.hxx" #if BOUT_HAS_PETSC -#include "bout/petsc_preconditioner.hxx" #include "bout/petsclib.hxx" #include @@ -69,6 +68,10 @@ RegisterSolver registersolvercvode("cvode"); // Preconditioner selection for CVODE. // Note: String comparisons are case-insensitive so "Auto" avoids conflict with keyword BOUT_ENUM_CLASS(CvodePreconMethod, none, Auto, user, petsc, bbd); +BOUT_ENUM_CLASS_NS(bout, CvodeJacobianExportTrigger, + output, ///< Export once per solver output timestep + linear_setup ///< Export whenever CVODE rebuilds linear solver data +); #if SUNDIALS_VERSION_AT_LEAST(6, 0, 0) using CvodeBool = sunbooleantype; @@ -99,12 +102,24 @@ public: private: #if BOUT_HAS_PETSC + /// PETSc callback for the CVODE system function ``x - gamma * rhs(t, x)``. static PetscErrorCode petscFormFunction(void* dummy, Vec x, Vec f, void* ctx); + /// PETSc callback for the raw CVODE RHS ``rhs(t, x)`` with ``linear=true``. + static PetscErrorCode petscFormRhsFunction(void* dummy, Vec x, Vec f, void* ctx); static int petscPSetup(BoutReal t, N_Vector yy, N_Vector yp, CvodeBool jok, CvodeBool* jcurPtr, BoutReal gamma, void* user_data); static int petscPSolve(BoutReal t, N_Vector yy, N_Vector yp, N_Vector rvec, N_Vector zvec, BoutReal gamma, BoutReal delta, int lr, void* user_data); + /// Write one Jacobian matrix and the shared metadata JSON. + void exportMatrixAndMetadata(bout::JacobianExportKind kind, Mat jacobian); + /// Build and save a diagnostic Jacobian of the requested kind. + void saveDiagnosticJacobian(bout::JacobianExportKind kind, Vec x, BoutReal t, + BoutReal gamma); + /// Save Jacobians during CVODE linear solver setup callbacks. + void maybeExportJacobian(Mat system_jacobian, Vec x, BoutReal t, BoutReal gamma); + /// Save Jacobians once per solver output timestep. + void maybeExportOutputJacobian(BoutReal t); #endif BoutReal hcur; //< Current internal timestep @@ -209,7 +224,7 @@ private: sundials::Context suncontext; #if BOUT_HAS_PETSC - // PETSc-coloring-based preconditioning for CVODE + // PETSc-coloring-based preconditioning and Jacobian diagnostics for CVODE std::unique_ptr petsc_lib; PetscPreconditioner petsc_preconditioner; KSP petsc_ksp{nullptr}; diff --git a/src/solver/impls/snes/snes.cxx b/src/solver/impls/snes/snes.cxx index e7d367331a..543bacb841 100644 --- a/src/solver/impls/snes/snes.cxx +++ b/src/solver/impls/snes/snes.cxx @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -21,6 +22,8 @@ #include #include #include +#include +#include #include #include "petscerror.h" @@ -61,6 +64,15 @@ PetscErrorCode FormFunctionForColoring(void* UNUSED(snes), Vec x, Vec f, void* c return static_cast(ctx)->snes_function(x, f, true); } +PetscErrorCode FormRawFunctionForColoring(void* UNUSED(snes), Vec x, Vec f, void* ctx) { + return static_cast(ctx)->raw_rhs_function(x, f, true); +} + +PetscErrorCode FormScaledFunctionForColoring(void* UNUSED(snes), Vec x, Vec f, + void* ctx) { + return static_cast(ctx)->scaled_rhs_function(x, f, true); +} + PetscErrorCode snesPCapply(PC pc, Vec x, Vec y) { // Get the context SNESSolver* s; @@ -71,6 +83,8 @@ PetscErrorCode snesPCapply(PC pc, Vec x, Vec y) { PetscErrorCode ComputeJacobianScaledColor(SNES snes, Vec x1, Mat Jac, Mat Jac_new, void* ctx); +PetscErrorCode ComputeJacobianDefaultMaybeExport(SNES snes, Vec x1, Mat Jac, Mat Jac_new, + void* ctx); } // namespace PetscErrorCode SNESSolver::FDJinitialise() { @@ -111,9 +125,9 @@ PetscErrorCode SNESSolver::FDJinitialise() { nullptr, &Jfd); if (matrix_free_operator) { - SNESSetJacobian(snes, Jmf, Jfd, SNESComputeJacobianDefault, this); + SNESSetJacobian(snes, Jmf, Jfd, ComputeJacobianDefaultMaybeExport, this); } else { - SNESSetJacobian(snes, Jfd, Jfd, SNESComputeJacobianDefault, this); + SNESSetJacobian(snes, Jfd, Jfd, ComputeJacobianDefaultMaybeExport, this); } MatSetOption(Jfd, MAT_NEW_NONZERO_ALLOCATION_ERR, PETSC_FALSE); @@ -364,7 +378,65 @@ SNESSolver::SNESSolver(Options* opts) .withDefault(100.)), asinh_vars((*options)["asinh_vars"] .doc("Apply asinh() to all variables?") - .withDefault(false)) {} + .withDefault(false)), + save_jacobian((*options)["save_jacobian"] + .doc("Save PETSc Jacobian diagnostics to datadir") + .withDefault(false)), + jacobian_export_kind((*options)["jacobian_export_kind"] + .doc("Which Jacobian to save: system, scaled, or rhs") + .withDefault(bout::JacobianExportKind::system)) {} + +void SNESSolver::exportMatrixAndMetadata(bout::JacobianExportKind kind, Mat jacobian) { + Solver::writeOnceJacobianMetadata("snes"); + Solver::writeJacobianMatrix(kind, jacobian); +} + +void SNESSolver::saveDiagnosticJacobian(bout::JacobianExportKind kind, Vec x_solver) { + PetscPreconditioner diagnostic_preconditioner; + Field3D index = globalIndex(0); + BOUT_DO_PETSC(diagnostic_preconditioner.createJacobianPattern( + index, *options, nlocal, n2Dvars(), n3Dvars(), BoutComm::get())); + + if (kind == bout::JacobianExportKind::rhs) { + BOUT_DO_PETSC( + diagnostic_preconditioner.updateColoring(FormRawFunctionForColoring, this)); + } else { + BOUT_DO_PETSC( + diagnostic_preconditioner.updateColoring(FormScaledFunctionForColoring, this)); + } + + Vec x_evaluate = x_solver; + Vec physical_x{nullptr}; + if (kind == bout::JacobianExportKind::rhs) { + BOUT_DO_PETSC(VecDuplicate(x_solver, &physical_x)); + BOUT_DO_PETSC(toPhysicalState(x_solver, physical_x)); + x_evaluate = physical_x; + } + + Mat diagnostic_jacobian = diagnostic_preconditioner.jacobian(); + BOUT_DO_PETSC(MatZeroEntries(diagnostic_jacobian)); + BOUT_DO_PETSC(SNESComputeJacobianDefaultColor(snes, x_evaluate, diagnostic_jacobian, + diagnostic_jacobian, + diagnostic_preconditioner.coloring())); + exportMatrixAndMetadata(kind, diagnostic_jacobian); + + if (physical_x != nullptr) { + BOUT_DO_PETSC(VecDestroy(&physical_x)); + } +} + +void SNESSolver::maybeExportJacobian(Mat system_jacobian, Vec x_solver) { + if (!save_jacobian) { + return; + } + + if (jacobian_export_kind == bout::JacobianExportKind::system) { + exportMatrixAndMetadata(jacobian_export_kind, system_jacobian); + return; + } + + saveDiagnosticJacobian(jacobian_export_kind, x_solver); +} int SNESSolver::init() { Solver::init(); @@ -1128,13 +1200,13 @@ PetscErrorCode SNESSolver::updateResiduals(Vec x) { const BoutReal* current_residual = nullptr; if (diagnose) { // Call RHS function to get time derivatives - PetscCall(rhs_function(x, deriv, false)); + PetscCall(scaled_rhs_function(x, deriv, false)); // Reading the residual vectors PetscCall(VecGetArrayRead(deriv, ¤t_residual)); } else { // Call RHS function to get time derivatives - PetscCall(rhs_function(x, snes_f, false)); + PetscCall(scaled_rhs_function(x, snes_f, false)); // Reading the residual vectors PetscCall(VecGetArrayRead(snes_f, ¤t_residual)); @@ -1142,7 +1214,7 @@ PetscErrorCode SNESSolver::updateResiduals(Vec x) { // Note: The ordering of quantities in the PETSc vectors // depends on the Solver::loop_vars function - Mesh* mesh = bout::globals::mesh; + const Mesh* mesh = bout::globals::mesh; int idx = 0; // Index into PETSc Vecs // Boundary cells @@ -1415,34 +1487,34 @@ BoutReal SNESSolver::updatePseudoTimestep(BoutReal previous_timestep, throw BoutException("SNESSolver::updatePseudoTimestep invalid BoutPTCStrategy"); } -PetscErrorCode SNESSolver::rhs_function(Vec x, Vec f, bool linear) { - // Get data from PETSc into BOUT++ fields +PetscErrorCode SNESSolver::toPhysicalState(Vec x, Vec physical_x) { if (scale_vars) { - // scaled_x <- x * var_scaling_factors - PetscCall(VecPointwiseMult(scaled_x, x, var_scaling_factors)); - } else if (asinh_vars) { - PetscCall(VecCopy(x, scaled_x)); + PetscCall(VecPointwiseMult(physical_x, x, var_scaling_factors)); } else { - scaled_x = x; + PetscCall(VecCopy(x, physical_x)); } if (asinh_vars) { PetscInt size; - PetscCall(VecGetLocalSize(scaled_x, &size)); + PetscCall(VecGetLocalSize(physical_x, &size)); - BoutReal* scaled_data = nullptr; - PetscCall(VecGetArray(scaled_x, &scaled_data)); + BoutReal* physical_data = nullptr; + PetscCall(VecGetArray(physical_x, &physical_data)); for (PetscInt i = 0; i != size; ++i) { - scaled_data[i] = asinh_scale * std::sinh(scaled_data[i]); + physical_data[i] = asinh_scale * std::sinh(physical_data[i]); } - PetscCall(VecRestoreArray(scaled_x, &scaled_data)); + PetscCall(VecRestoreArray(physical_x, &physical_data)); } + return PETSC_SUCCESS; +} + +PetscErrorCode SNESSolver::raw_rhs_function(Vec x, Vec f, bool linear) { const BoutReal* xdata = nullptr; - PetscCall(VecGetArrayRead(scaled_x, &xdata)); + PetscCall(VecGetArrayRead(x, &xdata)); // const_cast needed due to load_vars API. Not writing to xdata. load_vars(const_cast(xdata)); - PetscCall(VecRestoreArrayRead(scaled_x, &xdata)); + PetscCall(VecRestoreArrayRead(x, &xdata)); try { // Call RHS function @@ -1460,6 +1532,18 @@ PetscErrorCode SNESSolver::rhs_function(Vec x, Vec f, bool linear) { BoutReal* fdata = nullptr; PetscCall(VecGetArray(f, &fdata)); save_derivs(fdata); + PetscCall(VecRestoreArray(f, &fdata)); + + return PETSC_SUCCESS; +} + +PetscErrorCode SNESSolver::scaled_rhs_function(Vec x, Vec f, bool linear) { + if (!scale_vars && !asinh_vars) { + return raw_rhs_function(x, f, linear); + } + + PetscCall(toPhysicalState(x, scaled_x)); + PetscCall(raw_rhs_function(scaled_x, f, linear)); if (asinh_vars) { // Modify time-derivatives for asinh(var) using chain rule @@ -1472,14 +1556,15 @@ PetscErrorCode SNESSolver::rhs_function(Vec x, Vec f, bool linear) { PetscCall(VecGetLocalSize(f, &size)); const BoutReal* scaled_data = nullptr; PetscCall(VecGetArrayRead(scaled_x, &scaled_data)); + BoutReal* fdata = nullptr; + PetscCall(VecGetArray(f, &fdata)); for (PetscInt i = 0; i != size; ++i) { fdata[i] /= std::sqrt(SQ(scaled_data[i]) + SQ(asinh_scale)); } + PetscCall(VecRestoreArray(f, &fdata)); PetscCall(VecRestoreArrayRead(scaled_x, &scaled_data)); } - PetscCall(VecRestoreArray(f, &fdata)); - if (scale_vars) { PetscCall(VecPointwiseDivide(f, f, var_scaling_factors)); } @@ -1490,7 +1575,7 @@ PetscErrorCode SNESSolver::rhs_function(Vec x, Vec f, bool linear) { PetscErrorCode SNESSolver::snes_function(Vec x, Vec f, bool linear) { // Call the RHS function - if (rhs_function(x, f, linear) != PETSC_SUCCESS) { + if (scaled_rhs_function(x, f, linear) != PETSC_SUCCESS) { // Tell SNES that the input was out of domain SNESSetFunctionDomainError(snes); // Note: Returning non-zero error here leaves vectors in locked state @@ -1664,7 +1749,22 @@ PetscErrorCode ComputeJacobianScaledColor(SNES snes, Vec x1, Mat Jac, Mat Jac_ne CHKERRQ(err); // Call the SNESSolver function - return fctx->scaleJacobian(Jac_new); + PetscCall(fctx->scaleJacobian(Jac_new)); + fctx->maybeExportJacobian(Jac_new, x1); + PetscFunctionReturn(PETSC_SUCCESS); +} + +PetscErrorCode ComputeJacobianDefaultMaybeExport(SNES snes, Vec x1, Mat Jac, Mat Jac_new, + void* ctx) { + const PetscErrorCode err = SNESComputeJacobianDefault(snes, x1, Jac, Jac_new, ctx); + CHKERRQ(err); + + if ((err != 0) or (ctx == nullptr)) { + return err; + } + + static_cast(ctx)->maybeExportJacobian(Jac_new, x1); + PetscFunctionReturn(PETSC_SUCCESS); } } // namespace diff --git a/src/solver/impls/snes/snes.hxx b/src/solver/impls/snes/snes.hxx index c8042cf2dc..ddb25b9975 100644 --- a/src/solver/impls/snes/snes.hxx +++ b/src/solver/impls/snes/snes.hxx @@ -35,6 +35,7 @@ class SNESSolver; +#include #include #include "mpi.h" @@ -110,6 +111,32 @@ public: /// finite difference approximated Jacobian. PetscErrorCode scaleJacobian(Mat Jac_new); + /// Convert solver coordinates into the physical variables used by the model. + /// + /// This applies any active solver-space transforms, for example variable + /// scaling or ``asinh`` variables, before the model RHS is evaluated. + PetscErrorCode toPhysicalState(Vec x, Vec physical_x); + + /// Evaluate the bare model RHS in physical variables. + /// + /// This loads ``x`` into the BOUT++ evolving fields, calls ``run_rhs()``, and + /// stores the resulting derivatives in ``f`` without applying solver-space + /// transforms. + PetscErrorCode raw_rhs_function(Vec x, Vec f, bool linear); + + /// Evaluate the RHS in solver coordinates. + /// + /// This maps ``x`` into physical variables, calls ``raw_rhs_function()``, then + /// transforms the derivatives back into the coordinate system used internally by + /// SNES. + PetscErrorCode scaled_rhs_function(Vec x, Vec f, bool linear); + + /// Save a diagnostic Jacobian if enabled by ``solver:save_jacobian``. + /// + /// ``system`` exports the Jacobian used directly by SNES. ``scaled`` and ``rhs`` + /// build throwaway coloring Jacobians for diagnostics only. + void maybeExportJacobian(Mat system_jacobian, Vec x_solver); + /// Save diagnostics to output void outputVars(Options& output_options, bool save_repeat = true) override; @@ -121,13 +148,10 @@ private: /// Rescale state (snes_x) so that all quantities are around 1. If /// quantities are near zero then RTOL is used. PetscErrorCode rescale(); - - /// Call the physics model RHS function - /// - /// @param[in] x The state vector. Will be scaled if scale_vars=true - /// @param[out] f The vector for the result f(x) - /// @param[in] linear Specifies that the SNES solver is in a linear (KSP) inner loop - PetscErrorCode rhs_function(Vec x, Vec f, bool linear); + /// Build and save a diagnostic Jacobian of the requested kind. + void saveDiagnosticJacobian(bout::JacobianExportKind kind, Vec x_solver); + /// Write the matrix and shared JSON metadata for one diagnostic Jacobian. + void exportMatrixAndMetadata(bout::JacobianExportKind kind, Mat jacobian); BoutSnesOutput output_trigger; ///< Sets when outputs are written @@ -278,6 +302,10 @@ private: bool asinh_vars; ///< Evolve asinh(vars) to compress magnitudes while preserving signs const BoutReal asinh_scale = 1e-5; // Scale below which asinh response becomes ~linear + bool save_jacobian; ///< Save Jacobian diagnostics to ``datadir``? + bout::JacobianExportKind + jacobian_export_kind; ///< Export ``system``, ``scaled``, or ``rhs`` Jacobian + std::vector resid_2d; ///< Storage for residuals of SNES solve, unpacked from snes_f std::vector diff --git a/src/solver/petsc_preconditioner.cxx b/src/solver/petsc_preconditioner.cxx index 508d001fd3..dbec994c17 100644 --- a/src/solver/petsc_preconditioner.cxx +++ b/src/solver/petsc_preconditioner.cxx @@ -6,6 +6,7 @@ #include "bout/assert.hxx" #include "bout/boutcomm.hxx" +#include "bout/boutexception.hxx" #include "bout/field3d.hxx" #include "bout/globals.hxx" #include "bout/mesh.hxx" @@ -16,9 +17,12 @@ #include #include #include +#include #include #include +#include + namespace { class ColoringStencil { private: @@ -71,6 +75,26 @@ void PetscPreconditioner::reset() { } } +PetscErrorCode PetscPreconditioner::saveMatrix(Mat matrix, const std::string& filename, + bout::PetscMatrixExportFormat format) { + if (matrix == nullptr) { + throw BoutException("Cannot save Jacobian matrix: matrix has not been created yet"); + } + + PetscViewer viewer{nullptr}; + if (format == bout::PetscMatrixExportFormat::binary) { + PetscCall(PetscViewerBinaryOpen(BoutComm::get(), filename.c_str(), FILE_MODE_WRITE, + &viewer)); + } else { + PetscCall(PetscViewerASCIIOpen(BoutComm::get(), filename.c_str(), &viewer)); + } + + PetscCall(MatView(matrix, viewer)); + PetscCall(PetscViewerDestroy(&viewer)); + + PetscFunctionReturn(PETSC_SUCCESS); +} + PetscErrorCode PetscPreconditioner::createJacobianPattern(Field3D& index, Options& options, PetscInt nlocal, int n2d, diff --git a/src/solver/solver.cxx b/src/solver/solver.cxx index 1bd41c3098..9bcbc2e7f9 100644 --- a/src/solver/solver.cxx +++ b/src/solver/solver.cxx @@ -51,11 +51,14 @@ #include #include +#include +#include #include #include #include #include #include +#include // Implementations: #include "impls/adams_bashforth/adams_bashforth.hxx" @@ -80,6 +83,44 @@ int* Solver::pargc = nullptr; char*** Solver::pargv = nullptr; +namespace { +std::string jsonEscape(const std::string& input) { + std::string escaped; + escaped.reserve(input.size()); + + for (const char ch : input) { + switch (ch) { + case '\\': + escaped += "\\\\"; + break; + case '"': + escaped += "\\\""; + break; + case '\b': + escaped += "\\b"; + break; + case '\f': + escaped += "\\f"; + break; + case '\n': + escaped += "\\n"; + break; + case '\r': + escaped += "\\r"; + break; + case '\t': + escaped += "\\t"; + break; + default: + escaped += ch; + break; + } + } + + return escaped; +} +} // namespace + /************************************************************************** * Constructor **************************************************************************/ @@ -95,6 +136,18 @@ Solver::Solver(Options* opts) "timestep, to make it easier to concatenate output " "data sets in time") .withDefault(false)), + save_jacobian_index_base( + (*options)["save_jacobian_index_base"] + .doc("Write jacobian_index_base so saved PETSc Jacobians can be mapped " + "back to variables and cell indices in post-processing") + .withDefault(false)), + jacobian_export_prefix((*options)["jacobian_export_prefix"] + .doc("Prefix for saved Jacobian matrix files") + .withDefault("jacobian")), + jacobian_export_format( + (*options)["jacobian_export_format"] + .doc("PETSc MatView format for saved Jacobians: binary or ascii") + .withDefault(bout::PetscMatrixExportFormat::binary)), is_nonsplit_model_diffusive( (*options)["is_nonsplit_model_diffusive"] .doc("If not a split operator, treat RHS as diffusive?") @@ -706,6 +759,14 @@ void Solver::outputVars(Options& output_options, bool save_repeat) { "or the previous run did not have a run_id.") .assignRepeat(run_restart_from, "t", save_repeat and save_repeat_run_id, "Solver"); + if (initialised and save_jacobian_index_base) { + // The Jacobian index base offsets are not time-dependent + // Only need to calculate once, but can only be calculated + // once the solver has been initialised. This outputVars + // is called once at the start before Solver is initialised. + output_options["jacobian_index_base"].force(jacobianIndexBase(), "Solver"); + } + // Add 2D and 3D evolving fields to output file for (const auto& f : f2d) { // Add to dump file (appending) @@ -1231,6 +1292,133 @@ Field3D Solver::globalIndex(int localStart) { return index; } +Field3D Solver::jacobianIndexBase(int localStart) { return globalIndex(localStart); } + +std::vector Solver::getJacobianMetadata2D() const { + std::vector metadata; + metadata.reserve(f2d.size()); + + for (int i = 0; i < static_cast(f2d.size()); ++i) { + metadata.push_back(JacobianVariableMetadata{i, f2d[i].name, toString(f2d[i].location), + f2d[i].evolve_bndry, f2d[i].constraint, + f2d[i].description}); + } + + return metadata; +} + +std::vector Solver::getJacobianMetadata3D() const { + std::vector metadata; + metadata.reserve(f3d.size()); + + for (int i = 0; i < static_cast(f3d.size()); ++i) { + metadata.push_back(JacobianVariableMetadata{i, f3d[i].name, toString(f3d[i].location), + f3d[i].evolve_bndry, f3d[i].constraint, + f3d[i].description}); + } + + return metadata; +} + +Solver::JacobianMetadata +Solver::getJacobianMetadata(const std::string& solver_name) const { + return JacobianMetadata{1, + solver_name, + n2Dvars(), + n3Dvars(), + getJacobianMetadata2D(), + getJacobianMetadata3D(), + "For each (x,y): 2D variables at z=0, then 3D variables for " + "z=0..Nz-1; evolved boundary points precede RGN_NOBNDRY"}; +} + +void Solver::writeJacobianMetadataJson(const std::string& filename, + const std::string& solver_name) const { + if (MYPE != 0) { + return; + } + + const auto metadata = getJacobianMetadata(solver_name); + std::ofstream output_file(filename); + if (!output_file.is_open()) { + throw BoutException("Failed to open Jacobian metadata file '{}'", filename); + } + + std::string json_file; + json_file.reserve(512); + + auto write_variables = [&](const std::vector& variables, + const char* name) { + fmt::format_to(std::back_inserter(json_file), " \"{}\": [\n", name); + for (std::size_t i = 0; i < variables.size(); ++i) { + const auto& variable = variables[i]; + fmt::format_to( + std::back_inserter(json_file), + " {{\"offset\": {}, \"name\": \"{}\", \"location\": \"{}\", " + "\"evolve_bndry\": {}, \"constraint\": {}, \"description\": \"{}\"}}", + variable.offset, jsonEscape(variable.name), jsonEscape(variable.location), + variable.evolve_bndry ? "true" : "false", + variable.constraint ? "true" : "false", jsonEscape(variable.description)); + if (i + 1 != variables.size()) { + fmt::format_to(std::back_inserter(json_file), ","); + } + fmt::format_to(std::back_inserter(json_file), "\n"); + } + fmt::format_to(std::back_inserter(json_file), " ]"); + }; + + fmt::format_to(std::back_inserter(json_file), + "{{\n" + " \"format_version\": {},\n" + " \"solver\": \"{}\",\n" + " \"n2d\": {},\n" + " \"n3d\": {},\n", + metadata.format_version, jsonEscape(metadata.solver_name), metadata.n2d, + metadata.n3d); + write_variables(metadata.variables_2d, "variables_2d"); + fmt::format_to(std::back_inserter(json_file), ",\n"); + write_variables(metadata.variables_3d, "variables_3d"); + fmt::format_to(std::back_inserter(json_file), + ",\n" + " \"ordering\": \"{}\"\n" + "}}\n", + jsonEscape(metadata.ordering)); + + output_file << json_file; +} + +void Solver::writeOnceJacobianMetadata(const std::string& solver_name) { + if (jacobian_metadata_written) { + return; + } + + const std::string datadir = Options::root()["datadir"]; + const std::string metadata_filename = datadir + "/jacobian_metadata.json"; + output.write("Jacobian metadata written to {}\n", metadata_filename); + writeJacobianMetadataJson(metadata_filename, solver_name); + jacobian_metadata_written = true; +} + +#if BOUT_HAS_PETSC +void Solver::writeJacobianMatrix(bout::JacobianExportKind kind, Mat jacobian) { + BOUT_DO_PETSC(PetscPreconditioner::saveMatrix( + jacobian, + getJacobianMatrixFilename(this->jacobian_export_prefix, kind, + this->jacobian_export_format), + this->jacobian_export_format)); +} +#endif + +std::string Solver::getJacobianMatrixFilename(const std::string& jacobian_export_prefix, + bout::JacobianExportKind kind, + bout::PetscMatrixExportFormat format) { + // The directory the output data is stored in + const std::string datadir = Options::root()["datadir"]; + const std::string stem = fmt::format("{}/{}_{}_{:06d}", datadir, jacobian_export_prefix, + toString(kind), this->jacobian_export_counter++); + return stem + (format == bout::PetscMatrixExportFormat::binary ? ".dat" : ".txt"); +} + /************************************************************************** * Running user-supplied functions **************************************************************************/ diff --git a/src/sys/adios_object.cxx b/src/sys/adios_object.cxx index 1b3e0a12e2..596be66b93 100644 --- a/src/sys/adios_object.cxx +++ b/src/sys/adios_object.cxx @@ -7,6 +7,7 @@ #include +#include #include namespace bout { @@ -14,6 +15,30 @@ namespace bout { static ADIOSPtr adios = nullptr; static std::unordered_map adiosStreams; +namespace { +std::string GetIOName(const std::string& fname, adios2::Mode mode) { + switch (mode) { + case adios2::Mode::Read: + return "read_" + fname; + case adios2::Mode::ReadRandomAccess: + return "read_random_access_" + fname; + case adios2::Mode::Append: + return "append_" + fname; + case adios2::Mode::Write: + return "write_" + fname; + default: + return fname; + } +} + +adios2::IO GetIO(const std::string& fname, adios2::Mode mode, + const std::string& engineType) { + auto io = GetADIOSPtr()->DeclareIO(GetIOName(fname, mode)); + io.SetEngine(engineType); + return io; +} +} // namespace + void ADIOSInit(MPI_Comm comm) { adios = std::make_shared(comm); } void ADIOSInit(const std::string configFile, MPI_Comm comm) { @@ -37,16 +62,6 @@ ADIOSPtr GetADIOSPtr() { return adios; } -IOPtr GetIOPtr(const std::string IOName) { - auto adios = GetADIOSPtr(); - IOPtr io = nullptr; - try { - io = std::make_shared(adios->AtIO(IOName)); - } catch (std::invalid_argument& e) { - } - return io; -} - ADIOSStream::~ADIOSStream() { if (engine_) { if (isInStep) { @@ -57,42 +72,19 @@ ADIOSStream::~ADIOSStream() { } } -ADIOSStream& ADIOSStream::ADIOSGetStream(const std::string& fname, adios2::Mode mode) { - auto it = adiosStreams.find(fname); +ADIOSStream::ADIOSStream(const std::string& fname, adios2::Mode mode, + const std::string& engineType) + : io(GetIO(fname, mode, engineType)), fname(fname), file_mode(mode) {} + +ADIOSStream& ADIOSStream::ADIOSGetStream(const std::string& fname, adios2::Mode mode, + const std::string& engineType) { + const auto key = GetIOName(fname, mode); + auto it = adiosStreams.find(key); if (it == adiosStreams.end()) { - it = adiosStreams.emplace(fname, ADIOSStream(fname, mode)).first; + it = adiosStreams.emplace(key, ADIOSStream(fname, mode, engineType)).first; } return it->second; } -void ADIOSSetParameters(const std::string& input, char delimKeyValue, char delimItem, - adios2::IO& io) { - auto lf_Trim = [](std::string& input) { - input.erase(0, input.find_first_not_of(" \n\r\t")); // prefixing spaces - input.erase(input.find_last_not_of(" \n\r\t") + 1); // suffixing spaces - }; - - std::istringstream inputSS(input); - std::string parameter; - while (std::getline(inputSS, parameter, delimItem)) { - const size_t position = parameter.find(delimKeyValue); - if (position == std::string::npos) { - throw BoutException("ADIOSSetParameters(): wrong format for IO parameter " - + parameter + ", format must be key" + delimKeyValue - + "value for each entry"); - } - - std::string key = parameter.substr(0, position); - lf_Trim(key); - std::string value = parameter.substr(position + 1); - lf_Trim(value); - if (value.length() == 0) { - throw BoutException("ADIOS2SetParameters: empty value in IO parameter " + parameter - + ", format must be key" + delimKeyValue + "value"); - } - io.SetParameter(key, value); - } -} - } // namespace bout #endif //BOUT_HAS_ADIOS2 diff --git a/src/sys/derivs.cxx b/src/sys/derivs.cxx index e449dbcd30..b41b83900a 100644 --- a/src/sys/derivs.cxx +++ b/src/sys/derivs.cxx @@ -41,17 +41,16 @@ #include #include #include -#include #include #include #include #include #include #include +#include #include -#include +#include #include -#include #include @@ -63,12 +62,23 @@ Field3D DDX(const Field3D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { - return f.getCoordinates(outloc)->DDX(f, outloc, method, region); + const auto& coords = *f.getCoordinates(outloc); + + Field3D result = bout::derivatives::index::DDX(f, outloc, method, region) / coords.dx(); + + if (f.getMesh()->IncIntShear) { + // Using BOUT-06 style shifting + result += coords.IntShiftTorsion() * DDZ(f, outloc, method, region); + } + + return result; } -Coordinates::FieldMetric DDX(const Field2D& f, CELL_LOC outloc, const std::string& method, - const std::string& region) { - return f.getCoordinates(outloc)->DDX(f, outloc, method, region); +bout::FieldMetric DDX(const Field2D& f, CELL_LOC outloc, const std::string& method, + const std::string& region) { + ASSERT1(f.getLocation() == outloc || outloc == CELL_DEFAULT); + return bout::derivatives::index::DDX(f, outloc, method, region) + / f.getCoordinates(outloc)->dx(); } ////////////// Y DERIVATIVE ///////////////// @@ -76,12 +86,14 @@ Coordinates::FieldMetric DDX(const Field2D& f, CELL_LOC outloc, const std::strin Field3D DDY(const Field3DParallel& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::DDY(f, outloc, method, region) - / f.getCoordinates(outloc)->dy; + / f.getCoordinates(outloc)->dy(); } -Coordinates::FieldMetric DDY(const Field2D& f, CELL_LOC outloc, const std::string& method, - const std::string& region) { - return f.getCoordinates(outloc)->DDY(f, outloc, method, region); +bout::FieldMetric DDY(const Field2D& f, CELL_LOC outloc, const std::string& method, + const std::string& region) { + ASSERT1(f.getLocation() == outloc || outloc == CELL_DEFAULT); + return bout::derivatives::index::DDY(f, outloc, method, region) + / f.getCoordinates(outloc)->dy(); } ////////////// Z DERIVATIVE ///////////////// @@ -89,12 +101,12 @@ Coordinates::FieldMetric DDY(const Field2D& f, CELL_LOC outloc, const std::strin Field3D DDZ(const Field3D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::DDZ(f, outloc, method, region) - / f.getCoordinates(outloc)->dz; + / f.getCoordinates(outloc)->dz(); } -Coordinates::FieldMetric DDZ(const Field2D& f, CELL_LOC UNUSED(outloc), - const std::string& UNUSED(method), - const std::string& UNUSED(region)) { +bout::FieldMetric DDZ(const Field2D& f, CELL_LOC UNUSED(outloc), + const std::string& UNUSED(method), + const std::string& UNUSED(region)) { auto tmp = Field2D(0., f.getMesh()); tmp.setLocation(f.getLocation()); return tmp; @@ -107,21 +119,21 @@ Vector3D DDZ(const Vector3D& v, CELL_LOC outloc, const std::string& method, if (v.covariant) { // From equation (2.6.32) in D'Haeseleer - result.x = DDZ(v.x, outloc, method, region) - v.x * metric->G1_13 - - v.y * metric->G2_13 - v.z * metric->G3_13; - result.y = DDZ(v.y, outloc, method, region) - v.x * metric->G1_23 - - v.y * metric->G2_23 - v.z * metric->G3_23; - result.z = DDZ(v.z, outloc, method, region) - v.x * metric->G1_33 - - v.y * metric->G2_33 - v.z * metric->G3_33; + result.x = DDZ(v.x, outloc, method, region) - v.x * metric->G1_13() + - v.y * metric->G2_13() - v.z * metric->G3_13(); + result.y = DDZ(v.y, outloc, method, region) - v.x * metric->G1_23() + - v.y * metric->G2_23() - v.z * metric->G3_23(); + result.z = DDZ(v.z, outloc, method, region) - v.x * metric->G1_33() + - v.y * metric->G2_33() - v.z * metric->G3_33(); result.covariant = true; } else { // From equation (2.6.31) in D'Haeseleer - result.x = DDZ(v.x, outloc, method, region) + v.x * metric->G1_13 - + v.y * metric->G1_23 + v.z * metric->G1_33; - result.y = DDZ(v.y, outloc, method, region) + v.x * metric->G2_13 - + v.y * metric->G2_23 + v.z * metric->G2_33; - result.z = DDZ(v.z, outloc, method, region) + v.x * metric->G3_13 - + v.y * metric->G3_23 + v.z * metric->G3_33; + result.x = DDZ(v.x, outloc, method, region) + v.x * metric->G1_13() + + v.y * metric->G1_23() + v.z * metric->G1_33(); + result.y = DDZ(v.y, outloc, method, region) + v.x * metric->G2_13() + + v.y * metric->G2_23() + v.z * metric->G2_33(); + result.z = DDZ(v.z, outloc, method, region) + v.x * metric->G3_13() + + v.y * metric->G3_23() + v.z * metric->G3_33(); result.covariant = false; } @@ -158,12 +170,13 @@ Field3D D2DX2(const Field3D& f, CELL_LOC outloc, const std::string& method, const Coordinates* coords = f.getCoordinates(outloc); Field3D result = - bout::derivatives::index::D2DX2(f, outloc, method, region) / SQ(coords->dx); + bout::derivatives::index::D2DX2(f, outloc, method, region) / SQ(coords->dx()); - if (coords->non_uniform) { + if (coords->non_uniform()) { // Correction for non-uniform f.getMesh() - result += coords->d1_dx * bout::derivatives::index::DDX(f, outloc, "DEFAULT", region) - / coords->dx; + result += coords->d1_dx() + * bout::derivatives::index::DDX(f, outloc, "DEFAULT", region) + / coords->dx(); } ASSERT2(((outloc == CELL_DEFAULT) && (result.getLocation() == f.getLocation())) @@ -172,17 +185,18 @@ Field3D D2DX2(const Field3D& f, CELL_LOC outloc, const std::string& method, return result; } -Coordinates::FieldMetric D2DX2(const Field2D& f, CELL_LOC outloc, - const std::string& method, const std::string& region) { +bout::FieldMetric D2DX2(const Field2D& f, CELL_LOC outloc, const std::string& method, + const std::string& region) { const Coordinates* coords = f.getCoordinates(outloc); - Coordinates::FieldMetric result = - bout::derivatives::index::D2DX2(f, outloc, method, region) / SQ(coords->dx); + bout::FieldMetric result = + bout::derivatives::index::D2DX2(f, outloc, method, region) / SQ(coords->dx()); - if (coords->non_uniform) { + if (coords->non_uniform()) { // Correction for non-uniform f.getMesh() - result += coords->d1_dx * bout::derivatives::index::DDX(f, outloc, "DEFAULT", region) - / coords->dx; + result += coords->d1_dx() + * bout::derivatives::index::DDX(f, outloc, "DEFAULT", region) + / coords->dx(); } return result; @@ -195,12 +209,13 @@ Field3D D2DY2(const Field3D& f, CELL_LOC outloc, const std::string& method, const Coordinates* coords = f.getCoordinates(outloc); Field3D result = - bout::derivatives::index::D2DY2(f, outloc, method, region) / SQ(coords->dy); + bout::derivatives::index::D2DY2(f, outloc, method, region) / SQ(coords->dy()); - if (coords->non_uniform) { + if (coords->non_uniform()) { // Correction for non-uniform f.getMesh() - result += coords->d1_dy * bout::derivatives::index::DDY(f, outloc, "DEFAULT", region) - / coords->dy; + result += coords->d1_dy() + * bout::derivatives::index::DDY(f, outloc, "DEFAULT", region) + / coords->dy(); } ASSERT2(((outloc == CELL_DEFAULT) && (result.getLocation() == f.getLocation())) @@ -209,16 +224,17 @@ Field3D D2DY2(const Field3D& f, CELL_LOC outloc, const std::string& method, return result; } -Coordinates::FieldMetric D2DY2(const Field2D& f, CELL_LOC outloc, - const std::string& method, const std::string& region) { +bout::FieldMetric D2DY2(const Field2D& f, CELL_LOC outloc, const std::string& method, + const std::string& region) { const Coordinates* coords = f.getCoordinates(outloc); - Coordinates::FieldMetric result = - bout::derivatives::index::D2DY2(f, outloc, method, region) / SQ(coords->dy); - if (coords->non_uniform) { + bout::FieldMetric result = + bout::derivatives::index::D2DY2(f, outloc, method, region) / SQ(coords->dy()); + if (coords->non_uniform()) { // Correction for non-uniform f.getMesh() - result += coords->d1_dy * bout::derivatives::index::DDY(f, outloc, "DEFAULT", region) - / coords->dy; + result += coords->d1_dy() + * bout::derivatives::index::DDY(f, outloc, "DEFAULT", region) + / coords->dy(); } return result; @@ -229,13 +245,13 @@ Coordinates::FieldMetric D2DY2(const Field2D& f, CELL_LOC outloc, Field3D D2DZ2(const Field3D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::D2DZ2(f, outloc, method, region) - / SQ(f.getCoordinates(outloc)->dz); + / SQ(f.getCoordinates(outloc)->dz()); } -Coordinates::FieldMetric D2DZ2(const Field2D& f, CELL_LOC outloc, - const std::string& method, const std::string& region) { +bout::FieldMetric D2DZ2(const Field2D& f, CELL_LOC outloc, const std::string& method, + const std::string& region) { return bout::derivatives::index::D2DZ2(f, outloc, method, region) - / SQ(f.getCoordinates(outloc)->dz); + / SQ(f.getCoordinates(outloc)->dz()); } /******************************************************************************* @@ -245,37 +261,37 @@ Coordinates::FieldMetric D2DZ2(const Field2D& f, CELL_LOC outloc, Field3D D4DX4(const Field3D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::D4DX4(f, outloc, method, region) - / SQ(SQ(f.getCoordinates(outloc)->dx)); + / SQ(SQ(f.getCoordinates(outloc)->dx())); } -Coordinates::FieldMetric D4DX4(const Field2D& f, CELL_LOC outloc, - const std::string& method, const std::string& region) { +bout::FieldMetric D4DX4(const Field2D& f, CELL_LOC outloc, const std::string& method, + const std::string& region) { return bout::derivatives::index::D4DX4(f, outloc, method, region) - / SQ(SQ(f.getCoordinates(outloc)->dx)); + / SQ(SQ(f.getCoordinates(outloc)->dx())); } Field3D D4DY4(const Field3D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::D4DY4(f, outloc, method, region) - / SQ(SQ(f.getCoordinates(outloc)->dy)); + / SQ(SQ(f.getCoordinates(outloc)->dy())); } -Coordinates::FieldMetric D4DY4(const Field2D& f, CELL_LOC outloc, - const std::string& method, const std::string& region) { +bout::FieldMetric D4DY4(const Field2D& f, CELL_LOC outloc, const std::string& method, + const std::string& region) { return bout::derivatives::index::D4DY4(f, outloc, method, region) - / SQ(SQ(f.getCoordinates(outloc)->dy)); + / SQ(SQ(f.getCoordinates(outloc)->dy())); } Field3D D4DZ4(const Field3D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::D4DZ4(f, outloc, method, region) - / SQ(SQ(f.getCoordinates(outloc)->dz)); + / SQ(SQ(f.getCoordinates(outloc)->dz())); } -Coordinates::FieldMetric D4DZ4(const Field2D& f, CELL_LOC outloc, - const std::string& method, const std::string& region) { +bout::FieldMetric D4DZ4(const Field2D& f, CELL_LOC outloc, const std::string& method, + const std::string& region) { return bout::derivatives::index::D4DZ4(f, outloc, method, region) - / SQ(SQ(f.getCoordinates(outloc)->dz)); + / SQ(SQ(f.getCoordinates(outloc)->dz())); } /******************************************************************************* @@ -289,10 +305,10 @@ Coordinates::FieldMetric D4DZ4(const Field2D& f, CELL_LOC outloc, * * ** Communicates and applies boundary in X. */ -Coordinates::FieldMetric D2DXDY(const Field2D& f, CELL_LOC outloc, - const std::string& method, const std::string& region, - const std::string& dfdy_boundary_condition, - const std::string& dfdy_region) { +bout::FieldMetric D2DXDY(const Field2D& f, CELL_LOC outloc, const std::string& method, + const std::string& region, + const std::string& dfdy_boundary_condition, + const std::string& dfdy_region) { const std::string dy_region = dfdy_region.empty() ? region : dfdy_region; // If staggering in x, take y-derivative at f's location. @@ -333,9 +349,9 @@ Field3D D2DXDY(const Field3D& f, CELL_LOC outloc, const std::string& method, return DDX(dfdy, outloc, method, region); } -Coordinates::FieldMetric D2DXDZ(const Field2D& f, CELL_LOC outloc, - [[maybe_unused]] const std::string& method, - [[maybe_unused]] const std::string& region) { +bout::FieldMetric D2DXDZ(const Field2D& f, CELL_LOC outloc, + [[maybe_unused]] const std::string& method, + [[maybe_unused]] const std::string& region) { #if BOUT_USE_METRIC_3D Field3D tmp{f}; return D2DXDZ(tmp, outloc, method, region); @@ -358,9 +374,9 @@ Field3D D2DXDZ(const Field3D& f, CELL_LOC outloc, const std::string& method, return DDZ(DDX(f, x_location, method, region), outloc, method, region); } -Coordinates::FieldMetric D2DYDZ(const Field2D& f, CELL_LOC outloc, - [[maybe_unused]] const std::string& method, - [[maybe_unused]] const std::string& region) { +bout::FieldMetric D2DYDZ(const Field2D& f, CELL_LOC outloc, + [[maybe_unused]] const std::string& method, + [[maybe_unused]] const std::string& region) { #if BOUT_USE_METRIC_3D Field3D tmp{f}; return D2DYDZ(tmp, outloc, method, region); @@ -390,52 +406,52 @@ Field3D D2DYDZ(const Field3D& f, CELL_LOC outloc, ////////////// X DERIVATIVE ///////////////// /// Special case where both arguments are 2D. -Coordinates::FieldMetric VDDX(const Field2D& v, const Field2D& f, CELL_LOC outloc, - const std::string& method, const std::string& region) { +bout::FieldMetric VDDX(const Field2D& v, const Field2D& f, CELL_LOC outloc, + const std::string& method, const std::string& region) { return bout::derivatives::index::VDDX(v, f, outloc, method, region) - / f.getCoordinates(outloc)->dx; + / f.getCoordinates(outloc)->dx(); } /// General version for 2 or 3-D objects Field3D VDDX(const Field3D& v, const Field3D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::VDDX(v, f, outloc, method, region) - / f.getCoordinates(outloc)->dx; + / f.getCoordinates(outloc)->dx(); } ////////////// Y DERIVATIVE ///////////////// // special case where both are 2D -Coordinates::FieldMetric VDDY(const Field2D& v, const Field2D& f, CELL_LOC outloc, - const std::string& method, const std::string& region) { +bout::FieldMetric VDDY(const Field2D& v, const Field2D& f, CELL_LOC outloc, + const std::string& method, const std::string& region) { return bout::derivatives::index::VDDY(v, f, outloc, method, region) - / f.getCoordinates(outloc)->dy; + / f.getCoordinates(outloc)->dy(); } // general case Field3D VDDY(const Field3D& v, const Field3DParallel& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::VDDY(v, f, outloc, method, region) - / f.getCoordinates(outloc)->dy; + / f.getCoordinates(outloc)->dy(); } ////////////// Z DERIVATIVE ///////////////// // special case where both are 2D -Coordinates::FieldMetric VDDZ(const Field2D& v, const Field2D& f, CELL_LOC outloc, - const std::string& method, const std::string& region) { +bout::FieldMetric VDDZ(const Field2D& v, const Field2D& f, CELL_LOC outloc, + const std::string& method, const std::string& region) { return bout::derivatives::index::VDDZ(v, f, outloc, method, region) - / f.getCoordinates(outloc)->dz; + / f.getCoordinates(outloc)->dz(); } // Note that this is zero because no compression is included -Coordinates::FieldMetric VDDZ([[maybe_unused]] const Field3D& v, const Field2D& f, - CELL_LOC outloc, [[maybe_unused]] const std::string& method, - [[maybe_unused]] const std::string& region) { +bout::FieldMetric VDDZ([[maybe_unused]] const Field3D& v, const Field2D& f, + CELL_LOC outloc, [[maybe_unused]] const std::string& method, + [[maybe_unused]] const std::string& region) { #if BOUT_USE_METRIC_3D Field3D tmp{f}; return bout::derivatives::index::VDDZ(v, tmp, outloc, method, region) - / f.getCoordinates(outloc)->dz; + / f.getCoordinates(outloc)->dz(); #else if (outloc == CELL_DEFAULT) { outloc = f.getLocation(); @@ -448,48 +464,48 @@ Coordinates::FieldMetric VDDZ([[maybe_unused]] const Field3D& v, const Field2D& Field3D VDDZ(const Field3D& v, const Field3D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::VDDZ(v, f, outloc, method, region) - / f.getCoordinates(outloc)->dz; + / f.getCoordinates(outloc)->dz(); } /******************************************************************************* * Flux conserving schemes *******************************************************************************/ -Coordinates::FieldMetric FDDX(const Field2D& v, const Field2D& f, CELL_LOC outloc, - const std::string& method, const std::string& region) { +bout::FieldMetric FDDX(const Field2D& v, const Field2D& f, CELL_LOC outloc, + const std::string& method, const std::string& region) { return bout::derivatives::index::FDDX(v, f, outloc, method, region) - / f.getCoordinates(outloc)->dx; + / f.getCoordinates(outloc)->dx(); } Field3D FDDX(const Field3D& v, const Field3D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::FDDX(v, f, outloc, method, region) - / f.getCoordinates(outloc)->dx; + / f.getCoordinates(outloc)->dx(); } ///////////////////////////////////////////////////////////////////////// -Coordinates::FieldMetric FDDY(const Field2D& v, const Field2D& f, CELL_LOC outloc, - const std::string& method, const std::string& region) { +bout::FieldMetric FDDY(const Field2D& v, const Field2D& f, CELL_LOC outloc, + const std::string& method, const std::string& region) { return bout::derivatives::index::FDDY(v, f, outloc, method, region) - / f.getCoordinates(outloc)->dy; + / f.getCoordinates(outloc)->dy(); } Field3D FDDY(const Field3D& v, const Field3DParallel& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::FDDY(v, f, outloc, method, region) - / f.getCoordinates(outloc)->dy; + / f.getCoordinates(outloc)->dy(); } ///////////////////////////////////////////////////////////////////////// -Coordinates::FieldMetric FDDZ(const Field2D& v, const Field2D& f, CELL_LOC outloc, - const std::string& method, const std::string& region) { +bout::FieldMetric FDDZ(const Field2D& v, const Field2D& f, CELL_LOC outloc, + const std::string& method, const std::string& region) { return bout::derivatives::index::FDDZ(v, f, outloc, method, region) - / f.getCoordinates(outloc)->dz; + / f.getCoordinates(outloc)->dz(); } Field3D FDDZ(const Field3D& v, const Field3D& f, CELL_LOC outloc, const std::string& method, const std::string& region) { return bout::derivatives::index::FDDZ(v, f, outloc, method, region) - / f.getCoordinates(outloc)->dz; + / f.getCoordinates(outloc)->dz(); } diff --git a/src/sys/hypre_interface.cxx b/src/sys/hypre_interface.cxx index d90392cf1e..101484913a 100644 --- a/src/sys/hypre_interface.cxx +++ b/src/sys/hypre_interface.cxx @@ -5,14 +5,26 @@ #include "bout/hypre_interface.hxx" +#include +#include + namespace bout { -BCMatrixEquations::BCMatrixEquations(HYPRE_Int nrows, HYPRE_Int* ncols, - HYPRE_BigInt* rows, HYPRE_Int** row_indexes_ptr, - HYPRE_BigInt* cols, HYPRE_Complex* values, - HYPRE_Int nb, HYPRE_Int* bi_array) +BoundaryElimination::BoundaryElimination(HYPRE_Int nrows, HYPRE_Int* ncols, + HYPRE_BigInt* rows, HYPRE_Int** row_indexes_ptr, + HYPRE_BigInt* cols, HYPRE_Complex* values, + HYPRE_Int nb, HYPRE_Int* bi_array) : nb(nb) { HYPRE_Int* row_indexes; + const auto find_local_row = [nrows, rows](HYPRE_BigInt row) -> HYPRE_Int { + auto row_position = std::lower_bound(rows, rows + nrows, row); + if ((row_position == rows + nrows) || (*row_position != row)) { + throw BoutException("Could not find local row {} while constructing boundary " + "elimination data", + row); + } + return static_cast(std::distance(rows, row_position)); + }; // Create the row_indexes array HypreMalloc(row_indexes, sizeof(HYPRE_Int) * nrows); @@ -21,33 +33,49 @@ BCMatrixEquations::BCMatrixEquations(HYPRE_Int nrows, HYPRE_Int* ncols, row_indexes[i] = row_indexes[i - 1] + ncols[i - 1]; } - // Assume just one interior equation coupled to each boundary equation - na = nb; - // Allocate arrays HypreMalloc(binum_array, sizeof(HYPRE_Int) * nb); HypreMalloc(bjnum_array, sizeof(HYPRE_Int) * nb); + HypreMalloc(bdep_array, sizeof(HYPRE_Int) * nb); HypreMalloc(bii_array, sizeof(HYPRE_Complex) * nb); HypreMalloc(bij_array, sizeof(HYPRE_Complex) * nb); - HypreMalloc(aknum_array, sizeof(HYPRE_Int) * na); - HypreMalloc(aki_array, sizeof(HYPRE_Complex) * na); - HYPRE_Int binum = 0; - HYPRE_Int aknum = 0; + std::vector boundary_at_row(nrows, -1); for (HYPRE_Int bnum = 0; bnum < nb; bnum++) { - // Get boundary equation information and adjust boundary equations - // Find row i in rows array (assume i increases and rows is sorted) - HYPRE_Int i = bi_array[bnum]; - for (; binum < nrows; binum++) { - if (i == rows[binum]) { - break; // Found row i in rows array + boundary_at_row[find_local_row(bi_array[bnum])] = bnum; + } + + struct BoundaryOccurrence { + HYPRE_Int rownum; + HYPRE_Int row_offset; + }; + std::unordered_map boundary_number_for_row; + boundary_number_for_row.reserve(nb); + for (HYPRE_Int bnum = 0; bnum < nb; bnum++) { + boundary_number_for_row.emplace(bi_array[bnum], bnum); + } + + std::vector> occurrences_by_boundary(nb); + for (HYPRE_Int rownum = 0; rownum < nrows; rownum++) { + const HYPRE_Int row_start = row_indexes[rownum]; + for (HYPRE_Int m = 0; m < ncols[rownum]; m++) { + const auto boundary_position = boundary_number_for_row.find(cols[row_start + m]); + if (boundary_position != boundary_number_for_row.end()) { + occurrences_by_boundary[boundary_position->second].push_back({rownum, m}); } } + } + + std::vector bjrow_array(nb, -1); + for (HYPRE_Int bnum = 0; bnum < nb; bnum++) { + // Get boundary equation information and adjust boundary equations + HYPRE_Int i = bi_array[bnum]; + const HYPRE_Int binum = find_local_row(i); HYPRE_Int bcoeffnum = row_indexes[binum]; HYPRE_Complex bii{0.0}, bij{0.0}; - HYPRE_Int j = 0; + HYPRE_BigInt j = -1; - for (HYPRE_Int m = 0; m < 2; m++) { // Assume only two boundary equation coefficients + for (HYPRE_Int m = 0; m < ncols[binum]; m++) { if (cols[bcoeffnum + m] == i) { bii = values[bcoeffnum + m]; values[bcoeffnum + m] = -1.0; // Identity equation (negative definite matrix) @@ -57,68 +85,154 @@ BCMatrixEquations::BCMatrixEquations(HYPRE_Int nrows, HYPRE_Int* ncols, values[bcoeffnum + m] = 0.0; // Identity equation } } + if (j < 0) { + throw BoutException("Boundary row {} does not contain a retained neighbour", i); + } + if (bii == 0.0) { + throw BoutException("Boundary row {} has zero diagonal coefficient", i); + } ncols[binum] = 1; // Identity equation - /* Get interior equation information and adjust interior equations */ - /* Find row k in rows array (assume k increases and rows is sorted) */ - HYPRE_Int k = j; // Assume equation k = j - for (; aknum < nrows; aknum++) { - if (k == rows[aknum]) { - break; // Found row k in rows array + // Update arrays + binum_array[bnum] = binum; + bjnum_array[bnum] = find_local_row(j); + bdep_array[bnum] = boundary_at_row[bjnum_array[bnum]]; + bii_array[bnum] = bii; + bij_array[bnum] = bij; + bjrow_array[bnum] = j; + } + + std::vector aoffsets(nb + 1, 0); + std::vector aknums; + std::vector akis; + for (HYPRE_Int bnum = 0; bnum < nb; bnum++) { + const HYPRE_Int binum = binum_array[bnum]; + const HYPRE_Int bjnum = bjnum_array[bnum]; + const HYPRE_BigInt j = bjrow_array[bnum]; + + for (const auto& occurrence : occurrences_by_boundary[bnum]) { + const HYPRE_Int aknum = occurrence.rownum; + if (aknum == binum) { + continue; + } + if ((boundary_at_row[aknum] >= 0) && (aknum != bjnum)) { + continue; + } + + const HYPRE_Int acoeffnum = row_indexes[aknum]; + const HYPRE_Int aki_position = acoeffnum + occurrence.row_offset; + const HYPRE_Complex aki = values[aki_position]; + if (aki == 0.0) { + continue; } - } - HYPRE_Int acoeffnum = row_indexes[aknum]; - HYPRE_Int mkj = 0; - HYPRE_Complex aki{0.0}; - for (HYPRE_Int m = 0; m < ncols[aknum]; m++) { - if (cols[acoeffnum + m] == j) { - mkj = m; // Save for update of akj value below + HYPRE_Int j_position = -1; + for (HYPRE_Int m = 0; m < ncols[aknum]; m++) { + if (cols[acoeffnum + m] == j) { + j_position = acoeffnum + m; + break; + } } - if (cols[acoeffnum + m] == i) { - aki = values[acoeffnum + m]; - values[acoeffnum + m] = 0.0; // Eliminate coupling to boundary equation + if (j_position < 0) { + continue; } + + values[aki_position] = 0.0; // Eliminate coupling to boundary equation + values[j_position] -= aki * bij_array[bnum] / bii_array[bnum]; + + aknums.push_back(aknum); + akis.push_back(aki); } - values[acoeffnum + mkj] -= aki * bij / bii; // Update akj value + aoffsets[bnum + 1] = static_cast(aknums.size()); + } - // Update arrays - HYPRE_Int anum = bnum; // Assume only one interior equation k - binum_array[bnum] = binum; - bjnum_array[bnum] = aknum; // Assume only one interior equation k - bii_array[bnum] = bii; - bij_array[bnum] = bij; - aknum_array[anum] = aknum; - aki_array[anum] = aki; + na = static_cast(aknums.size()); + HypreMalloc(aoffset_array, sizeof(HYPRE_Int) * (nb + 1)); + std::copy(aoffsets.begin(), aoffsets.end(), aoffset_array); + HypreMalloc(aknum_array, sizeof(HYPRE_Int) * na); + std::copy(aknums.begin(), aknums.end(), aknum_array); + HypreMalloc(aki_array, sizeof(HYPRE_Complex) * na); + std::copy(akis.begin(), akis.end(), aki_array); + + std::vector dependency_depth(nb, -1); + const auto get_depth = [&dependency_depth, this](const auto& self, + HYPRE_Int bnum) -> HYPRE_Int { + if (dependency_depth[bnum] >= 0) { + return dependency_depth[bnum]; + } + const HYPRE_Int dep = bdep_array[bnum]; + if (dep < 0) { + dependency_depth[bnum] = 0; + } else { + dependency_depth[bnum] = 1 + self(self, dep); + } + return dependency_depth[bnum]; + }; + for (HYPRE_Int bnum = 0; bnum < nb; bnum++) { + get_depth(get_depth, bnum); } + expansion_order.resize(nb); + std::iota(expansion_order.begin(), expansion_order.end(), 0); + std::sort(expansion_order.begin(), expansion_order.end(), + [&dependency_depth](HYPRE_Int lhs, HYPRE_Int rhs) { + if (dependency_depth[lhs] != dependency_depth[rhs]) { + return dependency_depth[lhs] < dependency_depth[rhs]; + } + return lhs < rhs; + }); + reduction_order = expansion_order; + std::reverse(reduction_order.begin(), reduction_order.end()); + // Set return arguments *row_indexes_ptr = row_indexes; } -BCValuesPtr BCMatrixEquations::adjustBCRightHandSideEquations(HYPRE_Complex* rhs) { +BCValuesPtr +BoundaryElimination::copyBoundaryRowValues(const HYPRE_Complex* values) const { + BCValuesPtr boundary_values = std::make_shared(nb); - // Allocate array to store boundary row values - BCValuesPtr brhs = std::make_shared(nb); + for (HYPRE_Int bnum = 0; bnum < nb; bnum++) { + boundary_values->data[bnum] = values[binum_array[bnum]]; + } + + return boundary_values; +} + +BCValuesPtr +BoundaryElimination::evaluateBoundaryEquations(const HYPRE_Complex* values) const { + BCValuesPtr boundary_values = std::make_shared(nb); for (HYPRE_Int bnum = 0; bnum < nb; bnum++) { - HYPRE_Int binum = binum_array[bnum]; - brhs->data[bnum] = rhs[binum]; + boundary_values->data[bnum] = (bii_array[bnum] * values[binum_array[bnum]]) + + (bij_array[bnum] * values[bjnum_array[bnum]]); } - for (HYPRE_Int anum = 0; anum < na; anum++) { - HYPRE_Int bnum = anum; // Assume only one interior equation per boundary equation - HYPRE_Int aknum = aknum_array[anum]; - rhs[aknum] -= aki_array[anum] * brhs->data[bnum] / bii_array[bnum]; + return boundary_values; +} + +BCValuesPtr BoundaryElimination::reduceRightHandSideInPlace(HYPRE_Complex* rhs) const { + + // Allocate array to store boundary row values + BCValuesPtr brhs = copyBoundaryRowValues(rhs); + + for (HYPRE_Int bnum : reduction_order) { + for (HYPRE_Int anum = aoffset_array[bnum]; anum < aoffset_array[bnum + 1]; anum++) { + HYPRE_Int aknum = aknum_array[anum]; + rhs[aknum] -= aki_array[anum] * brhs->data[bnum] / bii_array[bnum]; + } + if (bdep_array[bnum] >= 0) { + brhs->data[bdep_array[bnum]] = rhs[bjnum_array[bnum]]; + } } return brhs; } -void BCMatrixEquations::adjustBCSolutionEquations(BCValuesPtr brhs, - HYPRE_Complex* solution) { +void BoundaryElimination::expandSolutionInPlace(BCValuesPtr brhs, + HYPRE_Complex* solution) const { - for (HYPRE_Int bnum = 0; bnum < nb; bnum++) { + for (HYPRE_Int bnum : expansion_order) { HYPRE_Int binum = binum_array[bnum]; HYPRE_Int bjnum = bjnum_array[bnum]; solution[binum] = @@ -126,6 +240,22 @@ void BCMatrixEquations::adjustBCSolutionEquations(BCValuesPtr brhs, } } +void BoundaryElimination::expandMatvecResultInPlace(BCValuesPtr boundary_operator_values, + BCValuesPtr full_boundary_values, + HYPRE_Complex* result) const { + for (HYPRE_Int bnum = 0; bnum < nb; bnum++) { + for (HYPRE_Int anum = aoffset_array[bnum]; anum < aoffset_array[bnum + 1]; anum++) { + HYPRE_Int aknum = aknum_array[anum]; + result[aknum] += + aki_array[anum] * boundary_operator_values->data[bnum] / bii_array[bnum]; + } + } + + for (HYPRE_Int bnum = 0; bnum < nb; bnum++) { + result[binum_array[bnum]] = full_boundary_values->data[bnum]; + } +} + } // namespace bout #endif // BOUT_HAS_HYPRE diff --git a/src/sys/options.cxx b/src/sys/options.cxx index 85cfa7a49a..eee8018f5d 100644 --- a/src/sys/options.cxx +++ b/src/sys/options.cxx @@ -258,16 +258,27 @@ Options& Options::operator=(Options&& other) noexcept { value = std::move(other.value); attributes = std::move(other.attributes); - full_name = std::move(other.full_name); is_section = other.is_section; children = std::move(other.children); value_used = other.value_used; + size_t len = other.full_name.size(); + std::string new_prefix = full_name; + if (len == 0 and not full_name.empty()) { + // Append section delimiter to new name + new_prefix += ":"; + } else if (len != 0 and full_name.empty()) { + // Remove extraneous section delimiter from old name + len += 1; + } + // Ensure that this is the parent of all children, // otherwise will point to the original Options instance for (auto& child : children) { child.second.parent_instance = this; + child.second.recursively_update_names(len, new_prefix); } + return *this; } @@ -749,43 +760,40 @@ namespace { template struct ConvertContainer; -/// Visitor to convert an int, BoutReal or Array/Matrix/Tensor to the -/// appropriate container. Templated on both the container class C -/// and scalar type Scalar. -template