Fix some macro issues in CMake Fortran build (AMReX-Codes/amrex#2084)

Include AMReX_Config.H in Fortran files that need preprocessing.  Do not
pass those definitions already defined in AMReX_Config.H to the Fortran
compiler in the cmake build.  This fixes the warnings about redefined macros.

Fix #2071

## Checklist

The proposed changes:
- [x] fix a bug or incorrect behavior in AMReX
- [ ] add new capabilities to AMReX
- [ ] changes answers in the test suite to more than roundoff level
- [ ] are likely to significantly affect the results of downstream AMReX users
- [ ] are described in the proposed changes to the AMReX documentation, if appropriate

CMake: Fortran+MPI Warning

It looks like we are still adding `BL_USE_MPI` to the compiler defines in CMake, but I think we should just rely on the one set in `AMReX_Config.H`:

```
... Src/Boundary/AMReX_lo_bctypes_mod.F90 ...
AMReX_Config.H:17:0:

   17 | #define BL_USE_MPI
      |
Warning: "BL_USE_MPI" redefined
<command-line>:5:0:

note: this is the location of the previous definition
```

cc @kngott

diff --git a/Src/AmrCore/AMReX_INTERP_2D.F90 b/Src/AmrCore/AMReX_INTERP_2D.F90
index 8e93e495f70..042f47e96b9 100644
--- a/Src/AmrCore/AMReX_INTERP_2D.F90
+++ b/Src/AmrCore/AMReX_INTERP_2D.F90
@@ -331,7 +331,7 @@ subroutine AMREX_PROTECT_INTERP (fine, fine_l1,fine_l2,fine_h1,fine_h2, fblo, fb
       integer icase

       if (MAX(lratiox,lratioy).gt.rMAX) then
-#ifdef AMREX_DEBUG
+#if 0
          print *,'rMAX in INTERP_2D::AMREX_PROTECT_INTERP must be >= ',MAX(lratiox,lratioy)
 #endif
          call bl_abort("rMAX in INTERP_2D")
@@ -551,7 +551,7 @@ subroutine AMREX_PROTECT_INTERP (fine, fine_l1,fine_l2,fine_h1,fine_h2, fblo, fb
                enddo
                enddo

-#ifdef AMREX_DEBUG
+#if 0
                if (abs(crseTotnew - crseTot)/cvol .gt. 1.e-8) then
                   print *,' '
                   print *,'BLEW CONSERVATION with ICASE = ',icase
diff --git a/Src/AmrCore/AMReX_INTERP_3D.F90 b/Src/AmrCore/AMReX_INTERP_3D.F90
index 4c2bd45aaee..1d65d565e51 100644
--- a/Src/AmrCore/AMReX_INTERP_3D.F90
+++ b/Src/AmrCore/AMReX_INTERP_3D.F90
@@ -437,7 +437,7 @@ subroutine AMREX_PROTECT_INTERP (fine, fine_l1,fine_l2,fine_l3,fine_h1,fine_h2,f
                enddo
                enddo

-#ifdef AMREX_DEBUG
+#if 0
                if (abs(sum_fine_new - sum_fine_old) .gt. 1.e-8) then
                   print *,' '
                   print *, &
diff --git a/Src/Base/AMReX_FILCC_1D.F90 b/Src/Base/AMReX_FILCC_1D.F90
index 45b7f6c1da1..873f67f5941 100644
--- a/Src/Base/AMReX_FILCC_1D.F90
+++ b/Src/Base/AMReX_FILCC_1D.F90
@@ -1,3 +1,4 @@
+#include <AMReX_Config.H>

 ! -----------------------------------------------------------
 !> This routine is intended to be a generic fill function
diff --git a/Src/Base/AMReX_FILCC_2D.F90 b/Src/Base/AMReX_FILCC_2D.F90
index 0cf06563328..89ef77d8384 100644
--- a/Src/Base/AMReX_FILCC_2D.F90
+++ b/Src/Base/AMReX_FILCC_2D.F90
@@ -1,3 +1,4 @@
+#include <AMReX_Config.H>

 #ifndef AMREX_XSDK

diff --git a/Src/Base/AMReX_FILCC_3D.F90 b/Src/Base/AMReX_FILCC_3D.F90
index 4e7f3256451..8e94c80e6b9 100644
--- a/Src/Base/AMReX_FILCC_3D.F90
+++ b/Src/Base/AMReX_FILCC_3D.F90
@@ -1,3 +1,4 @@
+#include <AMReX_Config.H>

 #ifndef AMREX_XSDK

diff --git a/Src/Base/AMReX_acc_mod.F90 b/Src/Base/AMReX_acc_mod.F90
index fc4a66b3cb8..bec212ac5be 100644
--- a/Src/Base/AMReX_acc_mod.F90
+++ b/Src/Base/AMReX_acc_mod.F90
@@ -1,3 +1,4 @@
+#include <AMReX_Config.H>

 module amrex_acc_module

diff --git a/Src/Base/AMReX_filcc_mod.F90 b/Src/Base/AMReX_filcc_mod.F90
index f46bdb70b2f..f3d3d941d2a 100644
--- a/Src/Base/AMReX_filcc_mod.F90
+++ b/Src/Base/AMReX_filcc_mod.F90
@@ -1,3 +1,4 @@
+#include <AMReX_Config.H>

 module amrex_filcc_module

diff --git a/Src/Base/AMReX_fort_mod.F90 b/Src/Base/AMReX_fort_mod.F90
index 13228a36655..b175bbba0d9 100644
--- a/Src/Base/AMReX_fort_mod.F90
+++ b/Src/Base/AMReX_fort_mod.F90
@@ -1,3 +1,5 @@
+#include <AMReX_Config.H>
+
 module amrex_fort_module

   use iso_c_binding, only : c_char, c_short, c_int, c_long, c_long_long, c_float, c_double, c_size_t, c_ptr
@@ -7,7 +9,7 @@ module amrex_fort_module
   integer, parameter ::    bl_spacedim = AMREX_SPACEDIM
   integer, parameter :: amrex_spacedim = AMREX_SPACEDIM

-#ifdef BL_USE_FLOAT
+#ifdef AMREX_USE_FLOAT
   integer, parameter :: amrex_real = c_float
   ! We could/should use Fortran 2008 c_sizeof here.
   integer (kind=c_size_t), parameter :: amrex_real_size = 4_c_size_t
diff --git a/Src/Base/AMReX_omp_mod.F90 b/Src/Base/AMReX_omp_mod.F90
index e6af10b5e0f..398308d9de5 100644
--- a/Src/Base/AMReX_omp_mod.F90
+++ b/Src/Base/AMReX_omp_mod.F90
@@ -1,14 +1,12 @@
-#ifdef BL_USE_OMP
+#include <AMReX_Config.H>
+
+#ifdef AMREX_USE_OMP

 module amrex_omp_module

   implicit none

-#ifdef AMREX_USE_OMP
   integer, parameter :: amrex_omp_support = (_OPENMP)
-#else
-  integer, parameter :: amrex_omp_support = 0 ! Should this be allowed??
-#endif

   integer, external :: omp_get_num_threads
   integer, external :: omp_get_max_threads
diff --git a/Src/Boundary/AMReX_LO_UTIL.F90 b/Src/Boundary/AMReX_LO_UTIL.F90
index 12fe914157f..082eb9d69a5 100644
--- a/Src/Boundary/AMReX_LO_UTIL.F90
+++ b/Src/Boundary/AMReX_LO_UTIL.F90
@@ -37,7 +37,7 @@ subroutine polyInterpCoeff(xInt, x, N, c)
             num = num*(xInt - x(i))
             den = den*(x(j) - x(i))
          end do
-#ifdef AMREX_DEBUG
+#if 0
          if (den .eq. zero) STOP 'polyInterpCoeff::invalid data'
 #endif
          c(j) = num/den
diff --git a/Src/EB/AMReX_ebcellflag_mod.F90 b/Src/EB/AMReX_ebcellflag_mod.F90
index b5c18daba7a..99763fea144 100644
--- a/Src/EB/AMReX_ebcellflag_mod.F90
+++ b/Src/EB/AMReX_ebcellflag_mod.F90
@@ -1,3 +1,4 @@
+#include <AMReX_Config.H>

 module amrex_ebcellflag_module
   use amrex_fort_module, only : amrex_real
diff --git a/Src/F_Interfaces/AmrCore/AMReX_fillpatch_mod.F90 b/Src/F_Interfaces/AmrCore/AMReX_fillpatch_mod.F90
index 294f86e4fa6..f6410f9122a 100644
--- a/Src/F_Interfaces/AmrCore/AMReX_fillpatch_mod.F90
+++ b/Src/F_Interfaces/AmrCore/AMReX_fillpatch_mod.F90
@@ -1,3 +1,4 @@
+#include <AMReX_Config.H>

 module amrex_fillpatch_module

diff --git a/Src/F_Interfaces/Base/AMReX_fab_mod.F90 b/Src/F_Interfaces/Base/AMReX_fab_mod.F90
index 80a2fa70aee..d36e8f4ea87 100644
--- a/Src/F_Interfaces/Base/AMReX_fab_mod.F90
+++ b/Src/F_Interfaces/Base/AMReX_fab_mod.F90
@@ -1,3 +1,4 @@
+#include <AMReX_Config.H>

 module amrex_fab_module

diff --git a/Src/F_Interfaces/Base/AMReX_parallel_mod.F90 b/Src/F_Interfaces/Base/AMReX_parallel_mod.F90
index f40fcd418bd..00fcc275c89 100644
--- a/Src/F_Interfaces/Base/AMReX_parallel_mod.F90
+++ b/Src/F_Interfaces/Base/AMReX_parallel_mod.F90
@@ -1,7 +1,9 @@
+#include <AMReX_Config.H>
+
 module amrex_parallel_module

   use iso_c_binding
-#ifdef BL_USE_MPI
+#ifdef AMREX_USE_MPI
   use amrex_fi_mpi
   use amrex_mpi_reduce_int_module, only : amrex_mpi_reduce_int, amrex_mpi_allreduce_int
   use amrex_mpi_reduce_real_module, only : amrex_mpi_reduce_real, amrex_mpi_allreduce_real
@@ -24,7 +26,7 @@ module amrex_parallel_module
   public :: amrex_parallel_reduce_max
   public :: amrex_parallel_reduce_min

-#ifdef BL_USE_MPI
+#ifdef AMREX_USE_MPI
   integer, public :: amrex_mpi_real = MPI_DATATYPE_NULL
   integer :: m_nprocs = -1
   integer :: m_myproc = -1
@@ -62,7 +64,7 @@ module amrex_parallel_module

   subroutine amrex_parallel_init (comm)
     integer, intent(in), optional :: comm
-#ifdef BL_USE_MPI
+#ifdef AMREX_USE_MPI
     integer :: ierr
     logical :: flag
     call MPI_Initialized(flag, ierr)
@@ -99,7 +101,7 @@ subroutine amrex_parallel_init (comm)
   end subroutine amrex_parallel_init

   subroutine amrex_parallel_finalize ()
-#ifdef BL_USE_MPI
+#ifdef AMREX_USE_MPI
     integer :: ierr
     call MPI_Comm_Free(m_comm, ierr)
     m_comm = MPI_COMM_NULL
@@ -131,7 +133,7 @@ end function amrex_parallel_nprocs
   subroutine amrex_parallel_reduce_sum_is (i, rank)
     integer, intent(inout) :: i
     integer, intent(in), optional :: rank
-#ifdef BL_USE_MPI
+#ifdef AMREX_USE_MPI
     integer :: tmp, ierr
     tmp = i
     if (present(rank)) then
@@ -146,7 +148,7 @@ subroutine amrex_parallel_reduce_sum_iv (i, n, rank)
     integer, intent(inout) :: i(*)
     integer, intent(in) :: n
     integer, intent(in), optional :: rank
-#ifdef BL_USE_MPI
+#ifdef AMREX_USE_MPI
     integer :: tmp(n), ierr
     tmp = i(1:n)
     if (present(rank)) then
@@ -160,7 +162,7 @@ end subroutine amrex_parallel_reduce_sum_iv
   subroutine amrex_parallel_reduce_sum_rs (r, rank)
     real(amrex_real), intent(inout) :: r
     integer, intent(in), optional :: rank
-#ifdef BL_USE_MPI
+#ifdef AMREX_USE_MPI
     real(amrex_real) :: tmp
     integer :: ierr
     tmp = r
@@ -176,7 +178,7 @@ subroutine amrex_parallel_reduce_sum_rv (r, n, rank)
     real(amrex_real), intent(inout) :: r(*)
     integer, intent(in) :: n
     integer, intent(in), optional :: rank
-#ifdef BL_USE_MPI
+#ifdef AMREX_USE_MPI
     real(amrex_real) :: tmp(n)
     integer :: ierr
     tmp = r(1:n)
@@ -191,7 +193,7 @@ end subroutine amrex_parallel_reduce_sum_rv
   subroutine amrex_parallel_reduce_max_is (i, rank)
     integer, intent(inout) :: i
     integer, intent(in), optional :: rank
-#ifdef BL_USE_MPI
+#ifdef AMREX_USE_MPI
     integer :: tmp, ierr
     tmp = i
     if (present(rank)) then
@@ -206,7 +208,7 @@ subroutine amrex_parallel_reduce_max_iv (i, n, rank)
     integer, intent(inout) :: i(*)
     integer, intent(in) :: n
     integer, intent(in), optional :: rank
-#ifdef BL_USE_MPI
+#ifdef AMREX_USE_MPI
     integer :: tmp(n), ierr
     tmp = i(1:n)
     if (present(rank)) then
@@ -220,7 +222,7 @@ end subroutine amrex_parallel_reduce_max_iv
   subroutine amrex_parallel_reduce_max_rs (r, rank)
     real(amrex_real), intent(inout) :: r
     integer, intent(in), optional :: rank
-#ifdef BL_USE_MPI
+#ifdef AMREX_USE_MPI
     real(amrex_real) :: tmp
     integer :: ierr
     tmp = r
@@ -236,7 +238,7 @@ subroutine amrex_parallel_reduce_max_rv (r, n, rank)
     real(amrex_real), intent(inout) :: r(*)
     integer, intent(in) :: n
     integer, intent(in), optional :: rank
-#ifdef BL_USE_MPI
+#ifdef AMREX_USE_MPI
     real(amrex_real) :: tmp(n)
     integer :: ierr
     tmp = r(1:n)
@@ -251,7 +253,7 @@ end subroutine amrex_parallel_reduce_max_rv
   subroutine amrex_parallel_reduce_min_is (i, rank)
     integer, intent(inout) :: i
     integer, intent(in), optional :: rank
-#ifdef BL_USE_MPI
+#ifdef AMREX_USE_MPI
     integer :: tmp, ierr
     tmp = i
     if (present(rank)) then
@@ -266,7 +268,7 @@ subroutine amrex_parallel_reduce_min_iv (i, n, rank)
     integer, intent(inout) :: i(*)
     integer, intent(in) :: n
     integer, intent(in), optional :: rank
-#ifdef BL_USE_MPI
+#ifdef AMREX_USE_MPI
     integer :: tmp(n), ierr
     tmp = i(1:n)
     if (present(rank)) then
@@ -280,7 +282,7 @@ end subroutine amrex_parallel_reduce_min_iv
   subroutine amrex_parallel_reduce_min_rs (r, rank)
     real(amrex_real), intent(inout) :: r
     integer, intent(in), optional :: rank
-#ifdef BL_USE_MPI
+#ifdef AMREX_USE_MPI
     real(amrex_real) :: tmp
     integer :: ierr
     tmp = r
@@ -296,7 +298,7 @@ subroutine amrex_parallel_reduce_min_rv (r, n, rank)
     real(amrex_real), intent(inout) :: r(*)
     integer, intent(in) :: n
     integer, intent(in), optional :: rank
-#ifdef BL_USE_MPI
+#ifdef AMREX_USE_MPI
     real(amrex_real) :: tmp(n)
     integer :: ierr
     tmp = r(1:n)
diff --git a/Src/F_Interfaces/Particle/AMReX_particlecontainer_mod.F90 b/Src/F_Interfaces/Particle/AMReX_particlecontainer_mod.F90
index fba25144fe7..bdb4c9a2310 100644
--- a/Src/F_Interfaces/Particle/AMReX_particlecontainer_mod.F90
+++ b/Src/F_Interfaces/Particle/AMReX_particlecontainer_mod.F90
@@ -16,8 +16,8 @@ module amrex_particlecontainer_module
   public :: amrex_get_particle_cpu, amrex_set_particle_cpu

   type, bind(C), public :: amrex_particle
-     real(amrex_particle_real)    :: pos(AMREX_SPACEDIM) !< Position
-     real(amrex_particle_real)    :: vel(AMREX_SPACEDIM) !< Particle velocity
+     real(amrex_particle_real)    :: pos(amrex_spacedim) !< Position
+     real(amrex_particle_real)    :: vel(amrex_spacedim) !< Particle velocity
      integer(c_int), private      :: id
      integer(c_int), private      :: cpu
   end type amrex_particle
diff --git a/Src/LinearSolvers/MLMG/AMReX_MLLinOp_nd.F90 b/Src/LinearSolvers/MLMG/AMReX_MLLinOp_nd.F90
index 8f01872a8a1..e97546117dd 100644
--- a/Src/LinearSolvers/MLMG/AMReX_MLLinOp_nd.F90
+++ b/Src/LinearSolvers/MLMG/AMReX_MLLinOp_nd.F90
@@ -1,3 +1,4 @@
+#include <AMReX_Config.H>

 module amrex_mllinop_nd_module

diff --git a/Tools/CMake/AMReXSetDefines.cmake b/Tools/CMake/AMReXSetDefines.cmake
index 8825b7aa25e..5cd193533b0 100644
--- a/Tools/CMake/AMReXSetDefines.cmake
+++ b/Tools/CMake/AMReXSetDefines.cmake
@@ -70,23 +70,14 @@ add_amrex_define( AMREX_USE_ASSERTION NO_LEGACY IF AMReX_ASSERTIONS )
 # Bound checking
 add_amrex_define( AMREX_BOUND_CHECK NO_LEGACY IF AMReX_BOUND_CHECK )

-#
-# Fortran-specific defines: BL_LANG_FORT and AMREX_LANG_FORT
-#
 if (AMReX_FORTRAN)

-   # These defines are needed only by AMReX source files
+   # Fortran-specific defines, BL_LANG_FORT and AMREX_LANG_FORT do not get
+   # stored in AMReX_Config.H
    target_compile_definitions( amrex PRIVATE
-      $<$<COMPILE_LANGUAGE:Fortran>:AMREX_SPACEDIM=${AMReX_SPACEDIM}>
       $<$<COMPILE_LANGUAGE:Fortran>:BL_LANG_FORT AMREX_LANG_FORT>
       )

-    if (AMReX_MPI)
-      target_compile_definitions( amrex PRIVATE
-        $<$<COMPILE_LANGUAGE:Fortran>:BL_USE_MPI>
-        )
-    endif()
-
    #
    # Fortran/C mangling scheme
    #

diff --git a/Tests/FortranInterface/Advection_F/Source/compute_dt_mod.F90 b/Tests/FortranInterface/Advection_F/Source/compute_dt_mod.F90
index 3c1c63188ac..90fb5a97b9f 100644
--- a/Tests/FortranInterface/Advection_F/Source/compute_dt_mod.F90
+++ b/Tests/FortranInterface/Advection_F/Source/compute_dt_mod.F90
@@ -1,3 +1,5 @@
+#include <AMReX_Config.H>
+
 module compute_dt_module

   use amrex_amr_module
diff --git a/Tests/FortranInterface/Advection_F/Source/evolve_mod.F90 b/Tests/FortranInterface/Advection_F/Source/evolve_mod.F90
index 6b7b0df1af5..7c7260a227f 100644
--- a/Tests/FortranInterface/Advection_F/Source/evolve_mod.F90
+++ b/Tests/FortranInterface/Advection_F/Source/evolve_mod.F90
@@ -1,3 +1,5 @@
+#include <AMReX_Config.H>
+
 module evolve_module

   use amrex_amr_module
diff --git a/Tests/FortranInterface/Advection_octree_F/Source/compute_dt_mod.F90 b/Tests/FortranInterface/Advection_octree_F/Source/compute_dt_mod.F90
index 623e33be7a1..e9ea23010b2 100644
--- a/Tests/FortranInterface/Advection_octree_F/Source/compute_dt_mod.F90
+++ b/Tests/FortranInterface/Advection_octree_F/Source/compute_dt_mod.F90
@@ -1,3 +1,5 @@
+#include <AMReX_Config.H>
+
 module compute_dt_module

   use amrex_amr_module
diff --git a/Tests/FortranInterface/Advection_octree_F/Source/evolve_mod.F90 b/Tests/FortranInterface/Advection_octree_F/Source/evolve_mod.F90
index e2a449c4a0d..15670fa6719 100644
--- a/Tests/FortranInterface/Advection_octree_F/Source/evolve_mod.F90
+++ b/Tests/FortranInterface/Advection_octree_F/Source/evolve_mod.F90
@@ -1,3 +1,5 @@
+#include <AMReX_Config.H>
+
 module evolve_module

   use amrex_amr_module


CI: Run 2D and 1D Tests (AMReX-Codes/amrex#2475)

## Summary

Run tests also for 2D and 1D builds of AMReX.

## Additional background

Related to 2D issues with `DenseBin` in WarpX: https://github.com/ECP-WarpX/WarpX/pull/2550

## Checklist

The proposed changes:
- [x] fix a bug or incorrect behavior in AMReX
- [x] add new capabilities to AMReX
- [ ] changes answers in the test suite to more than roundoff level
- [ ] are likely to significantly affect the results of downstream AMReX users
- [ ] include documentation in the code and/or rst files, if appropriate

Physical Boundary Conditions - Fortran

I cannot find any example fortran code that uses the amrex_physbc fortran interface to supply boundary conditions.  The current examples in Tuturials/*_CF use periodic bcs.  I did notice that in some of the _C tutorials, an f77 file was present which provided a `filcc` routine.  To impose boundary conditions, should I customize the filcc routines or is there a better way utilizing amrex_physbc?

diff --git a/.github/workflows/gcc.yml b/.github/workflows/gcc.yml
index 9ea1ca69516..244b8192a76 100644
--- a/.github/workflows/gcc.yml
+++ b/.github/workflows/gcc.yml
@@ -40,8 +40,8 @@ jobs:
         ctest --output-on-failure

   # Build libamrex and all tests
-  tests_build:
-    name: GNU@7.5 C++14 Debug Fortran [tests]
+  tests_build_3D:
+    name: GNU@7.5 C++14 3D Debug Fortran [tests]
     runs-on: ubuntu-18.04
     env: {CXXFLAGS: "-fno-operator-names -Werror -Wall -Wextra -Wpedantic -Wnull-dereference -Wfloat-conversion -Wshadow -Woverloaded-virtual -O1"}
       # It's too slow with -O0
@@ -51,18 +51,65 @@ jobs:
       run: .github/workflows/dependencies/dependencies.sh
     - name: Build & Install
       run: |
-        mkdir build
-        cd build
-        cmake ..                        \
+        cmake -S . -B build             \
             -DCMAKE_BUILD_TYPE=Debug    \
             -DCMAKE_VERBOSE_MAKEFILE=ON \
             -DAMReX_EB=ON               \
             -DAMReX_ENABLE_TESTS=ON     \
             -DAMReX_FORTRAN=ON          \
-            -DAMReX_PARTICLES=ON
-        make -j 2
+            -DAMReX_PARTICLES=ON        \
+            -DAMReX_SPACEDIM=3
+        cmake --build build -j 2

-        ctest --output-on-failure
+        ctest --test-dir build --output-on-failure
+
+  tests_build_2D:
+    name: GNU@7.5 C++14 2D Debug Fortran [tests]
+    runs-on: ubuntu-18.04
+    env: {CXXFLAGS: "-fno-operator-names -Wall -Wextra -Wpedantic -Wnull-dereference -Wfloat-conversion -Wshadow -Woverloaded-virtual -O1"}
+      # -Werror temporarily skipped until we have functional testing established
+      # It's too slow with -O0
+    steps:
+    - uses: actions/checkout@v2
+    - name: Dependencies
+      run: .github/workflows/dependencies/dependencies.sh
+    - name: Build & Install
+      run: |
+        cmake -S . -B build             \
+            -DCMAKE_BUILD_TYPE=Debug    \
+            -DCMAKE_VERBOSE_MAKEFILE=ON \
+            -DAMReX_EB=ON               \
+            -DAMReX_ENABLE_TESTS=ON     \
+            -DAMReX_FORTRAN=ON          \
+            -DAMReX_PARTICLES=ON        \
+            -DAMReX_SPACEDIM=2
+        cmake --build build -j 2
+
+        ctest --test-dir build --output-on-failure
+
+  tests_build_1D:
+    name: GNU@7.5 C++14 1D Debug Fortran [tests]
+    runs-on: ubuntu-18.04
+    env: {CXXFLAGS: "-fno-operator-names -Wall -Wextra -Wpedantic -Wnull-dereference -Wfloat-conversion -Wshadow -Woverloaded-virtual -O1"}
+      # -Werror temporarily skipped until we have functional testing established
+      # It's too slow with -O0
+    steps:
+    - uses: actions/checkout@v2
+    - name: Dependencies
+      run: .github/workflows/dependencies/dependencies.sh
+    - name: Build & Install
+      run: |
+        cmake -S . -B build             \
+            -DCMAKE_BUILD_TYPE=Debug    \
+            -DCMAKE_VERBOSE_MAKEFILE=ON \
+            -DAMReX_EB=ON               \
+            -DAMReX_ENABLE_TESTS=ON     \
+            -DAMReX_FORTRAN=ON          \
+            -DAMReX_PARTICLES=ON        \
+            -DAMReX_SPACEDIM=1
+        cmake --build build -j 2
+
+        ctest --test-dir build --output-on-failure

   # Build libamrex and all tests
   tests_cxx20:
diff --git a/Src/Particle/AMReX_ParticleInterpolators.H b/Src/Particle/AMReX_ParticleInterpolators.H
new file mode 100644
index 00000000000..f241ac3ff1e
--- /dev/null
+++ b/Src/Particle/AMReX_ParticleInterpolators.H
@@ -0,0 +1,127 @@
+#ifndef AMREX_PARTICLEINTERPOLATORS_H_
+#define AMREX_PARTICLEINTERPOLATORS_H_
+#include <AMReX_Config.H>
+
+#include <AMReX_IntVect.H>
+#include <AMReX_Gpu.H>
+#include <AMReX_Print.H>
+
+namespace amrex
+{
+namespace ParticleInterpolator
+{
+
+template <class Derived, class WeightType>
+struct Base
+{
+    int index[3];
+    WeightType* w;
+
+    template <typename P, typename V, typename F>
+    AMREX_GPU_DEVICE AMREX_FORCE_INLINE
+    void ParticleToMesh (const P& p,
+                         amrex::Array4<V> const& arr,
+                         int src_comp, int dst_comp, int num_comps, F&& f)
+    {
+        static constexpr int stencil_width = Derived::stencil_width;
+        for (int ic=0; ic < num_comps; ++ic) {
+            for (int kk = 0; kk <= Derived::nz; ++kk) {
+                for (int jj = 0; jj <= Derived::ny; ++jj) {
+                    for (int ii = 0; ii <= Derived::nx; ++ii) {
+                        const auto pval = f(p, src_comp+ic);
+                        const auto val = w[0*stencil_width+ii] *
+                                         w[1*stencil_width+jj] *
+                                         w[2*stencil_width+kk] * pval;
+                        Gpu::Atomic::AddNoRet(&arr(index[0]+ii, index[1]+jj, index[2]+kk, ic+dst_comp), val);
+                    }
+                }
+            }
+        }
+    }
+
+    template <typename P, typename V, typename F, typename G>
+    AMREX_GPU_DEVICE AMREX_FORCE_INLINE
+    void MeshToParticle (P& p,
+                         amrex::Array4<const V> const& arr,
+                         int src_comp, int dst_comp, int num_comps, F&& f, G&& g)
+    {
+        static constexpr int stencil_width = Derived::stencil_width;
+        for (int ic=0; ic < num_comps; ++ic) {
+            for (int kk = 0; kk <= Derived::nz; ++kk) {
+                for (int jj = 0; jj <= Derived::ny; ++jj) {
+                    for (int ii = 0; ii <= Derived::nx; ++ii) {
+                        const auto mval = f(arr,index[0]+ii,index[1]+jj,index[2]+kk,src_comp+ic);
+                        const auto val = w[0*stencil_width+ii] *
+                                         w[1*stencil_width+jj] *
+                                         w[2*stencil_width+kk] * mval;
+                        g(p, ic + dst_comp, val);
+                    }
+                }
+            }
+        }
+    }
+};
+
+struct Nearest : public Base<Nearest, int>
+{
+    static constexpr int stencil_width = 1;
+    int weights[3*stencil_width];
+
+    static constexpr int nx = (AMREX_SPACEDIM >= 1) ? stencil_width - 1 : 0;
+    static constexpr int ny = (AMREX_SPACEDIM >= 2) ? stencil_width - 1 : 0;
+    static constexpr int nz = (AMREX_SPACEDIM >= 3) ? stencil_width - 1 : 0;
+
+    template <typename P>
+    AMREX_GPU_DEVICE AMREX_FORCE_INLINE
+    Nearest (const P& p,
+             amrex::GpuArray<amrex::Real,AMREX_SPACEDIM> const& plo,
+             amrex::GpuArray<amrex::Real,AMREX_SPACEDIM> const& dxi)
+    {
+        w = &weights[0];
+        for (int i = 0; i < AMREX_SPACEDIM; ++i) {
+            amrex::Real l = (p.pos(i) - plo[i]) * dxi[i] + 0.5;
+            index[i] = static_cast<int>(amrex::Math::floor(l));
+            w[i] = 1;
+        }
+        for (int i = AMREX_SPACEDIM; i < 3; ++i) {
+            index[i] = 0;
+            w[i] = 1;
+        }
+    }
+};
+
+struct Linear : public Base<Linear, amrex::Real>
+{
+    static constexpr int stencil_width = 2;
+
+    static constexpr int nx = (AMREX_SPACEDIM >= 1) ? stencil_width - 1 : 0;
+    static constexpr int ny = (AMREX_SPACEDIM >= 2) ? stencil_width - 1 : 0;
+    static constexpr int nz = (AMREX_SPACEDIM >= 3) ? stencil_width - 1 : 0;
+
+    amrex::Real weights[3*stencil_width];
+
+    template <typename P>
+    AMREX_GPU_DEVICE AMREX_FORCE_INLINE
+    Linear (const P& p,
+            amrex::GpuArray<amrex::Real,AMREX_SPACEDIM> const& plo,
+            amrex::GpuArray<amrex::Real,AMREX_SPACEDIM> const& dxi)
+    {
+        w = &weights[0];
+        for (int i = 0; i < AMREX_SPACEDIM; ++i) {
+            amrex::Real l = (p.pos(i) - plo[i]) * dxi[i] + 0.5;
+            index[i] = static_cast<int>(amrex::Math::floor(l)) - 1;
+            amrex::Real lint = l - (index[i] + 1);
+            w[stencil_width*i + 0] = 1.-lint;
+            w[stencil_width*i + 1] = lint;
+        }
+        for (int i = AMREX_SPACEDIM; i < 3; ++i) {
+            index[i] = 0;
+            w[stencil_width*i + 0] = 1.;
+            w[stencil_width*i + 1] = 0.;
+        }
+    }
+};
+}
+}
+
+#endif // include guard
diff --git a/Src/Particle/CMakeLists.txt b/Src/Particle/CMakeLists.txt
index 7b0efb39d4e..980fdb178ab 100644
--- a/Src/Particle/CMakeLists.txt
+++ b/Src/Particle/CMakeLists.txt
@@ -34,6 +34,7 @@ target_sources( amrex
    AMReX_ParticleBufferMap.cpp
    AMReX_ParticleCommunication.H
    AMReX_ParticleCommunication.cpp
+   AMReX_ParticleInterpolators.H
    AMReX_ParticleReduce.H
    AMReX_ParticleMesh.H
    AMReX_ParticleLocator.H
diff --git a/Src/Particle/Make.package b/Src/Particle/Make.package
index f693b923492..efb65679118 100644
--- a/Src/Particle/Make.package
+++ b/Src/Particle/Make.package
@@ -14,6 +14,7 @@ C$(AMREX_PARTICLE)_headers += AMReX_WriteBinaryParticleData.H
 C$(AMREX_PARTICLE)_headers += AMReX_ParticleContainerBase.H
 C$(AMREX_PARTICLE)_sources += AMReX_ParticleContainerBase.cpp
 C$(AMREX_PARTICLE)_headers += AMReX_ParticleArray.H
+C$(AMREX_PARTICLE)_headers += AMReX_ParticleInterpolators.H

 VPATH_LOCATIONS += $(AMREX_HOME)/Src/Particle
 INCLUDE_LOCATIONS += $(AMREX_HOME)/Src/Particle

diff --git a/Tests/AsyncOut/multifab/main.cpp b/Tests/AsyncOut/multifab/main.cpp
index b9b30f297de..d1331d85862 100644
--- a/Tests/AsyncOut/multifab/main.cpp
+++ b/Tests/AsyncOut/multifab/main.cpp
@@ -53,8 +53,12 @@ void main_main ()
         else if (n_boxes_per_rank != 0)
         {
            n_cell_3d[0] = (max_grid_size) - 1;
+#if AMREX_SPACEDIM >= 2
            n_cell_3d[1] = (max_grid_size * n_boxes_per_rank) - 1;
+#endif
+#if AMREX_SPACEDIM == 3
            n_cell_3d[2] = (max_grid_size * ParallelDescriptor::NProcs()) - 1;
+#endif
         }
     }

diff --git a/Tests/LinearSolvers/NodalPoisson/CMakeLists.txt b/Tests/LinearSolvers/NodalPoisson/CMakeLists.txt
index f4dadd89154..58b88bfea00 100644
--- a/Tests/LinearSolvers/NodalPoisson/CMakeLists.txt
+++ b/Tests/LinearSolvers/NodalPoisson/CMakeLists.txt
@@ -1,3 +1,7 @@
+if(AMReX_SPACEDIM EQUAL 1)
+   return()
+endif()
+
 set(_sources main.cpp MyTest.cpp MyTest.H MyTestPlotfile.cpp)

 set(_input_files inputs-ci)
diff --git a/Tests/MultiBlock/Advection/CMakeLists.txt b/Tests/MultiBlock/Advection/CMakeLists.txt
index ac043eefd8d..cc4890639be 100644
--- a/Tests/MultiBlock/Advection/CMakeLists.txt
+++ b/Tests/MultiBlock/Advection/CMakeLists.txt
@@ -1,3 +1,7 @@
+if(AMReX_SPACEDIM EQUAL 1)
+   return()
+endif()
+
 set(_sources     main.cpp)
 set(_input_files )

diff --git a/Tests/MultiBlock/Advection/main.cpp b/Tests/MultiBlock/Advection/main.cpp
index c9167a69545..8b5555b2c75 100644
--- a/Tests/MultiBlock/Advection/main.cpp
+++ b/Tests/MultiBlock/Advection/main.cpp
@@ -87,7 +87,9 @@ class AdvectionAmrCore : public AmrCore {
                                 next(i, j, k, n) = m(i, j, k, n) - a_dt_over_dx * (m(i, j, k, n) - m(i - 1, j, k, n));
                             });
             }
-        } else if (dir == Direction::y) {
+        }
+#if (AMREX_SPACEDIM >= 2)
+        else if (dir == Direction::y) {
 #ifdef AMREX_USE_OMP
 #pragma omp parallel if (Gpu::notInLaunchRegion())
 #endif
@@ -100,6 +102,7 @@ class AdvectionAmrCore : public AmrCore {
                             });
             }
         }
+#endif
         std::swap(mass, mass_next);
     }

@@ -231,10 +234,10 @@ void MyMain() {
     RealBox real_box1{{AMREX_D_DECL(-0.5, -0.3, 0.0)}, {AMREX_D_DECL(0.5, 0.7, 1.0)}};
     RealBox real_box2{{AMREX_D_DECL(+0.55, -0.3, 0.0)}, {AMREX_D_DECL(1.55, 0.7, 1.0)}};

-    Array<int, AMREX_SPACEDIM> is_periodic1{0, 1};
+    Array<int, AMREX_SPACEDIM> is_periodic1{AMREX_D_DECL(0, 1, 0)};
     Geometry geom1{domain, real_box1, CoordSys::cartesian, is_periodic1};

-    Array<int, AMREX_SPACEDIM> is_periodic2{1, 0};
+    Array<int, AMREX_SPACEDIM> is_periodic2{AMREX_D_DECL(1, 0, 0)};
     Geometry geom2{domain, real_box2, CoordSys::cartesian, is_periodic2};

     AmrInfo amr_info{};
@@ -243,7 +246,11 @@ void MyMain() {
 #endif

     AdvectionAmrCore core_x(Direction::x, geom1, amr_info);
+#if AMREX_SPACEDIM > 2
     AdvectionAmrCore core_y(Direction::y, geom2, amr_info);
+#else // too hack-ish?
+    AdvectionAmrCore core_y(Direction::x, geom2, amr_info);
+#endif

     std::vector<OnesidedMultiBlockBoundaryFn> multi_block_boundaries{};
     {   // Fill right boundary of core_x with lower mirror data of core_y
diff --git a/Tests/MultiBlock/IndexType/CMakeLists.txt b/Tests/MultiBlock/IndexType/CMakeLists.txt
index ac043eefd8d..cc4890639be 100644
--- a/Tests/MultiBlock/IndexType/CMakeLists.txt
+++ b/Tests/MultiBlock/IndexType/CMakeLists.txt
@@ -1,3 +1,7 @@
+if(AMReX_SPACEDIM EQUAL 1)
+   return()
+endif()
+
 set(_sources     main.cpp)
 set(_input_files )

diff --git a/Tests/MultiBlock/IndexType/main.cpp b/Tests/MultiBlock/IndexType/main.cpp
index ad7a78a8cdb..39a99acbc67 100644
--- a/Tests/MultiBlock/IndexType/main.cpp
+++ b/Tests/MultiBlock/IndexType/main.cpp
@@ -135,9 +135,9 @@ int MyMain()
     Box domain{IntVect{}, IntVect{AMREX_D_DECL(31, 31, 31)}};
     // Loop over all index types
     for (int i = 0; i < AMREX_D_TERM(2,*2,*2); ++i) {
-        const auto ix = static_cast<IndexType::CellIndex>(static_cast<bool>(i & 0b001));
-        const auto iy = static_cast<IndexType::CellIndex>(static_cast<bool>(i & 0b010));
-        const auto iz = static_cast<IndexType::CellIndex>(static_cast<bool>(i & 0b100));
+        AMREX_D_TERM(const auto ix = static_cast<IndexType::CellIndex>(static_cast<bool>(i & 0b001));,
+                     const auto iy = static_cast<IndexType::CellIndex>(static_cast<bool>(i & 0b010));,
+                     const auto iz = static_cast<IndexType::CellIndex>(static_cast<bool>(i & 0b100));)
         IndexType itype{AMREX_D_DECL(ix, iy, iz)};
         Box converted_domain = convert(domain, itype);
         iMultiFab mf = InitializeMultiFab(converted_domain);
diff --git a/Tests/Particles/AssignDensity/main.cpp b/Tests/Particles/AssignDensity/main.cpp
index b08693828be..4b0e6635e22 100644
--- a/Tests/Particles/AssignDensity/main.cpp
+++ b/Tests/Particles/AssignDensity/main.cpp
@@ -67,7 +67,7 @@ void test_assign_density(TestParams& parms)

   MyParticleContainer::ParticleInitData pdata = {{mass, AMREX_D_DECL(1.0, 2.0, 3.0)}, {}, {}, {}};
   myPC.InitRandom(num_particles, iseed, pdata, serialize);
-  myPC.AssignCellDensitySingleLevel(0, partMF, 0, 4, 0);
+  myPC.AssignCellDensitySingleLevel(0, partMF, 0, 1 + AMREX_SPACEDIM, 0);

   //  myPC.AssignDensitySingleLevel(0, partMF, 0, 4, 0);

@@ -76,7 +76,7 @@ void test_assign_density(TestParams& parms)
   MultiFab::Copy(density, partMF, 0, 0, 1, 0);

   WriteSingleLevelPlotfile("plt00000", partMF,
-                           {"density", "vx", "vy", "vz"},
+                           {"density", AMREX_D_DECL("vx", "vy", "vz")},
                            geom, 0.0, 0);

   myPC.Checkpoint("plt00000", "particle0");
diff --git a/Tests/Particles/AsyncIO/main.cpp b/Tests/Particles/AsyncIO/main.cpp
index 98b430e245d..ce484e7b773 100644
--- a/Tests/Particles/AsyncIO/main.cpp
+++ b/Tests/Particles/AsyncIO/main.cpp
@@ -238,7 +238,7 @@ void test_async_io(TestParams& parms)
     MyParticleContainer myPC(geom, dmap, ba, rr);
     myPC.SetVerbose(false);

-    myPC.InitParticles(IntVect(2, 2, 2));
+    myPC.InitParticles(IntVect(AMREX_D_DECL(2, 2, 2)));

     for (int step = 0; step < 4000; ++step)
     {
diff --git a/Tests/Particles/GhostsAndVirtuals/CMakeLists.txt b/Tests/Particles/GhostsAndVirtuals/CMakeLists.txt
index 4c24bb40f68..6cd0bc5c84d 100644
--- a/Tests/Particles/GhostsAndVirtuals/CMakeLists.txt
+++ b/Tests/Particles/GhostsAndVirtuals/CMakeLists.txt
@@ -1,3 +1,7 @@
+if ( NOT (AMReX_SPACEDIM EQUAL 3) )
+   return ()
+endif ()
+
 set(_sources     main.cpp)
 set(_input_files inputs  particle_file.init  fixed_grids.init)

diff --git a/Tests/Particles/InitFromAscii/CMakeLists.txt b/Tests/Particles/InitFromAscii/CMakeLists.txt
index 0615dcae5db..3f63911ef1f 100644
--- a/Tests/Particles/InitFromAscii/CMakeLists.txt
+++ b/Tests/Particles/InitFromAscii/CMakeLists.txt
@@ -1,3 +1,7 @@
+if ( NOT (AMReX_SPACEDIM EQUAL 3) )
+   return ()
+endif ()
+
 set(_sources     main.cpp)
 set(_input_files inputs  )

diff --git a/Tests/Particles/NeighborParticles/CheckPair.H b/Tests/Particles/NeighborParticles/CheckPair.H
index 8018e9c49e9..e51e239a167 100644
--- a/Tests/Particles/NeighborParticles/CheckPair.H
+++ b/Tests/Particles/NeighborParticles/CheckPair.H
@@ -2,6 +2,7 @@
 #define MD_K_H_

 #include "Constants.H"
+#include <AMReX_SPACE.H>

 struct CheckPair
 {
@@ -9,10 +10,10 @@ struct CheckPair
     AMREX_GPU_DEVICE AMREX_FORCE_INLINE
     bool operator()(const P& p1, const P& p2) const
     {
-        amrex::Real d0 = (p1.pos(0) - p2.pos(0));
-        amrex::Real d1 = (p1.pos(1) - p2.pos(1));
-        amrex::Real d2 = (p1.pos(2) - p2.pos(2));
-        amrex::Real dsquared = d0*d0 + d1*d1 + d2*d2;
+        AMREX_D_TERM(amrex::Real d0 = (p1.pos(0) - p2.pos(0));,
+                     amrex::Real d1 = (p1.pos(1) - p2.pos(1));,
+                     amrex::Real d2 = (p1.pos(2) - p2.pos(2));)
+        amrex::Real dsquared = AMREX_D_TERM(d0*d0, + d1*d1, + d2*d2);
         return (dsquared <= 25.0*Params::cutoff*Params::cutoff);
     }
 };
diff --git a/Tests/Particles/NeighborParticles/MDParticleContainer.cpp b/Tests/Particles/NeighborParticles/MDParticleContainer.cpp
index b275e3327ef..b934f664f4e 100644
--- a/Tests/Particles/NeighborParticles/MDParticleContainer.cpp
+++ b/Tests/Particles/NeighborParticles/MDParticleContainer.cpp
@@ -2,6 +2,7 @@
 #include "Constants.H"

 #include "CheckPair.H"
+#include <AMReX_SPACE.H>

 using namespace amrex;

@@ -10,16 +11,24 @@ namespace
     void get_position_unit_cell(Real* r, const IntVect& nppc, int i_part)
     {
         int nx = nppc[0];
+#if AMREX_SPACEDIM >= 2
         int ny = nppc[1];
+#else
+        int ny = 1;
+#endif
+#if AMREX_SPACEDIM == 3
         int nz = nppc[2];
+#else
+        int nz = 1;
+#endif

-        int ix_part = i_part/(ny * nz);
-        int iy_part = (i_part % (ny * nz)) % ny;
-        int iz_part = (i_part % (ny * nz)) / ny;
+        AMREX_D_TERM(int ix_part = i_part/(ny * nz);,
+                     int iy_part = (i_part % (ny * nz)) % ny;,
+                     int iz_part = (i_part % (ny * nz)) / ny;)

-        r[0] = (0.5+ix_part)/nx;
-        r[1] = (0.5+iy_part)/ny;
-        r[2] = (0.5+iz_part)/nz;
+        AMREX_D_TERM(r[0] = (0.5+ix_part)/nx;,
+                     r[1] = (0.5+iy_part)/ny;,
+                     r[2] = (0.5+iz_part)/nz;)
     }

     void get_gaussian_random_momentum(Real* u, Real u_mean, Real u_std) {
@@ -61,7 +70,7 @@ InitParticles(const IntVect& a_num_particles_per_cell,

         for (IntVect iv = tile_box.smallEnd(); iv <= tile_box.bigEnd(); tile_box.next(iv)) {
             for (int i_part=0; i_part<num_ppc;i_part++) {
-                Real r[3];
+                Real r[AMREX_SPACEDIM];
                 Real v[3];

                 get_position_unit_cell(r, a_num_particles_per_cell, i_part);
@@ -69,16 +78,16 @@ InitParticles(const IntVect& a_num_particles_per_cell,
                 get_gaussian_random_momentum(v, a_thermal_momentum_mean,
                                              a_thermal_momentum_std);

-                ParticleReal x = static_cast<ParticleReal> (plo[0] + (iv[0] + r[0])*dx[0]);
-                ParticleReal y = static_cast<ParticleReal> (plo[1] + (iv[1] + r[1])*dx[1]);
-                ParticleReal z = static_cast<ParticleReal> (plo[2] + (iv[2] + r[2])*dx[2]);
+                AMREX_D_TERM(ParticleReal x = static_cast<ParticleReal> (plo[0] + (iv[0] + r[0])*dx[0]);,
+                             ParticleReal y = static_cast<ParticleReal> (plo[1] + (iv[1] + r[1])*dx[1]);,
+                             ParticleReal z = static_cast<ParticleReal> (plo[2] + (iv[2] + r[2])*dx[2]);)

                 ParticleType p;
                 p.id()  = ParticleType::NextID();
                 p.cpu() = ParallelDescriptor::MyProc();
-                p.pos(0) = x;
-                p.pos(1) = y;
-                p.pos(2) = z;
+                AMREX_D_TERM(p.pos(0) = x;,
+                             p.pos(1) = y;,
+                             p.pos(2) = z;)

                 p.rdata(PIdx::vx) = static_cast<ParticleReal> (v[0]);
                 p.rdata(PIdx::vy) = static_cast<ParticleReal> (v[1]);
@@ -165,11 +174,11 @@ std::pair<Real, Real> MDParticleContainer::minAndMaxDistance()

             for (const auto& p2 : nbor_data.getNeighbors(i))
             {
-                Real dx = p1.pos(0) - p2.pos(0);
-                Real dy = p1.pos(1) - p2.pos(1);
-                Real dz = p1.pos(2) - p2.pos(2);
+                AMREX_D_TERM(Real dx = p1.pos(0) - p2.pos(0);,
+                             Real dy = p1.pos(1) - p2.pos(1);,
+                             Real dz = p1.pos(2) - p2.pos(2);)

-                Real r2 = dx*dx + dy*dy + dz*dz;
+                Real r2 = AMREX_D_TERM(dx*dx, + dy*dy, + dz*dz);
                 r2 = amrex::max(r2, Params::min_r*Params::min_r);
                 Real r = sqrt(r2);

@@ -211,9 +220,9 @@ void MDParticleContainer::moveParticles(amrex::ParticleReal dx)
         AMREX_FOR_1D ( np, i,
         {
             ParticleType& p = pstruct[i];
-            p.pos(0) += dx;
-            p.pos(1) += dx;
-            p.pos(2) += dx;
+            AMREX_D_TERM(p.pos(0) += dx;,
+                         p.pos(1) += dx;,
+                         p.pos(2) += dx;)
         });
     }
 }
@@ -314,11 +323,11 @@ void MDParticleContainer::checkNeighborList()
                 if ( i == j ) continue;

                 ParticleType& p2 = h_pstruct[j];
-                Real dx = p1.pos(0) - p2.pos(0);
-                Real dy = p1.pos(1) - p2.pos(1);
-                Real dz = p1.pos(2) - p2.pos(2);
+                AMREX_D_TERM(Real dx = p1.pos(0) - p2.pos(0);,
+                             Real dy = p1.pos(1) - p2.pos(1);,
+                             Real dz = p1.pos(2) - p2.pos(2);)

-                Real r2 = dx*dx + dy*dy + dz*dz;
+                Real r2 = AMREX_D_TERM(dx*dx, + dy*dy, + dz*dz);

                 Real cutoff_sq = 25.0*Params::cutoff*Params::cutoff;

diff --git a/Tests/Particles/ParallelContext/main.cpp b/Tests/Particles/ParallelContext/main.cpp
index a23b9195771..311c39a3bab 100644
--- a/Tests/Particles/ParallelContext/main.cpp
+++ b/Tests/Particles/ParallelContext/main.cpp
@@ -15,25 +15,25 @@ int num_runtime_int = 0;

 void get_position_unit_cell(Real* r, const IntVect& nppc, int i_part)
 {
-    int nx = nppc[0];
-#if AMREX_SPACEDIM > 1
-    int ny = nppc[1];
+        int nx = nppc[0];
+#if AMREX_SPACEDIM >= 2
+        int ny = nppc[1];
 #else
-    int ny = 1;
+        int ny = 1;
 #endif
-#if AMREX_SPACEDIM > 2
-    int nz = nppc[2];
+#if AMREX_SPACEDIM == 3
+        int nz = nppc[2];
 #else
-    int nz = 1;
+        int nz = 1;
 #endif

-    int ix_part = i_part/(ny * nz);
-    int iy_part = (i_part % (ny * nz)) % ny;
-    int iz_part = (i_part % (ny * nz)) / ny;
+        AMREX_D_TERM(int ix_part = i_part/(ny * nz);,
+                     int iy_part = (i_part % (ny * nz)) % ny;,
+                     int iz_part = (i_part % (ny * nz)) / ny;)

-    r[0] = (0.5+ix_part)/nx;
-    r[1] = (0.5+iy_part)/ny;
-    r[2] = (0.5+iz_part)/nz;
+        AMREX_D_TERM(r[0] = (0.5+ix_part)/nx;,
+                     r[1] = (0.5+iy_part)/ny;,
+                     r[2] = (0.5+iz_part)/nz;)
 }

 class TestParticleContainer
@@ -92,7 +92,7 @@ class TestParticleContainer
             for (IntVect iv = tile_box.smallEnd(); iv <= tile_box.bigEnd(); tile_box.next(iv))
             {
                 for (int i_part=0; i_part<num_ppc;i_part++) {
-                    Real r[3];
+                    Real r[AMREX_SPACEDIM];
                     get_position_unit_cell(r, a_num_particles_per_cell, i_part);

                     ParticleType p;
@@ -322,8 +322,6 @@ void testParallelContext ()
 {
     BL_PROFILE("testParallelContext");

-    AMREX_ALWAYS_ASSERT(AMREX_SPACEDIM == 3);
-
     // we always make two subcommunicators.
     // one takes the left half of the domain in direction 0, the other the right.
     int rank_n = ParallelContext::NProcsSub();
diff --git a/Tests/Particles/ParticleIterator/main.cpp b/Tests/Particles/ParticleIterator/main.cpp
index 0c544de7935..d635d11ab13 100644
--- a/Tests/Particles/ParticleIterator/main.cpp
+++ b/Tests/Particles/ParticleIterator/main.cpp
@@ -25,8 +25,8 @@ int main(int argc, char* argv[])
       real_box.setHi(n,1.0);
     }

-  IntVect domain_lo(0 , 0, 0);
-  IntVect domain_hi(ncell-1, ncell-1, ncell-1);
+  IntVect domain_lo(AMREX_D_DECL(0 , 0, 0));
+  IntVect domain_hi(AMREX_D_DECL(ncell-1, ncell-1, ncell-1));

   const Box domain(domain_lo, domain_hi);

diff --git a/Tests/Particles/ParticleMesh/main.cpp b/Tests/Particles/ParticleMesh/main.cpp
index a6fb6094ff5..3e3a3c5045a 100644
--- a/Tests/Particles/ParticleMesh/main.cpp
+++ b/Tests/Particles/ParticleMesh/main.cpp
@@ -6,6 +6,7 @@
 #include "AMReX_Particles.H"
 #include "AMReX_PlotFileUtil.H"
 #include <AMReX_ParticleMesh.H>
+#include <AMReX_ParticleInterpolators.H>

 using namespace amrex;

@@ -22,7 +23,7 @@ void testParticleMesh (TestParams& parms)
 {

   RealBox real_box;
-  for (int n = 0; n < BL_SPACEDIM; n++) {
+  for (int n = 0; n < AMREX_SPACEDIM; n++) {
     real_box.setLo(n, 0.0);
     real_box.setHi(n, 1.0);
   }
@@ -32,8 +33,8 @@ void testParticleMesh (TestParams& parms)
   const Box domain(domain_lo, domain_hi);

   // This sets the boundary conditions to be doubly or triply periodic
-  int is_per[BL_SPACEDIM];
-  for (int i = 0; i < BL_SPACEDIM; i++)
+  int is_per[AMREX_SPACEDIM];
+  for (int i = 0; i < AMREX_SPACEDIM; i++)
     is_per[i] = 1;
   Geometry geom(domain, &real_box, CoordSys::cartesian, is_per);

@@ -45,13 +46,13 @@ void testParticleMesh (TestParams& parms)

   DistributionMapping dmap(ba);

-  MultiFab partMF(ba, dmap, 1 + BL_SPACEDIM, 1);
+  MultiFab partMF(ba, dmap, 1 + AMREX_SPACEDIM, 1);
   partMF.setVal(0.0);

-  iMultiFab partiMF(ba, dmap, 1 + BL_SPACEDIM, 1);
+  iMultiFab partiMF(ba, dmap, 1 + AMREX_SPACEDIM, 1);
   partiMF.setVal(0);

-  typedef ParticleContainer<1 + 2*BL_SPACEDIM, 1> MyParticleContainer;
+  typedef ParticleContainer<1 + 2*AMREX_SPACEDIM, 1> MyParticleContainer;
   MyParticleContainer myPC(geom, dmap, ba);
   myPC.SetVerbose(false);

@@ -66,83 +67,63 @@ void testParticleMesh (TestParams& parms)
   MyParticleContainer::ParticleInitData pdata = {{mass, AMREX_D_DECL(1.0, 2.0, 3.0), AMREX_D_DECL(0.0, 0.0, 0.0)}, {},{},{}};
   myPC.InitRandom(num_particles, iseed, pdata, serialize);

-  int nc = 1 + BL_SPACEDIM;
+  int nc = 1 + AMREX_SPACEDIM;
   const auto plo = geom.ProbLoArray();
   const auto dxi = geom.InvCellSizeArray();
   amrex::ParticleToMesh(myPC, partMF, 0,
       [=] AMREX_GPU_DEVICE (const MyParticleContainer::ParticleType& p,
                             amrex::Array4<amrex::Real> const& rho)
       {
-          amrex::Real lx = (p.pos(0) - plo[0]) * dxi[0] + 0.5;
-          amrex::Real ly = (p.pos(1) - plo[1]) * dxi[1] + 0.5;
-          amrex::Real lz = (p.pos(2) - plo[2]) * dxi[2] + 0.5;
-
-          int i = static_cast<int>(amrex::Math::floor(lx));
-          int j = static_cast<int>(amrex::Math::floor(ly));
-          int k = static_cast<int>(amrex::Math::floor(lz));
-
-          amrex::Real xint = lx - i;
-          amrex::Real yint = ly - j;
-          amrex::Real zint = lz - k;
-
-          amrex::Real sx[] = {1.-xint, xint};
-          amrex::Real sy[] = {1.-yint, yint};
-          amrex::Real sz[] = {1.-zint, zint};
-
-          for (int kk = 0; kk <= 1; ++kk) {
-              for (int jj = 0; jj <= 1; ++jj) {
-                  for (int ii = 0; ii <= 1; ++ii) {
-                      amrex::Gpu::Atomic::AddNoRet(&rho(i+ii-1, j+jj-1, k+kk-1, 0),
-                                              sx[ii]*sy[jj]*sz[kk]*p.rdata(0));
-                  }
-              }
-          }
-
-          for (int comp=1; comp < nc; ++comp) {
-             for (int kk = 0; kk <= 1; ++kk) {
-                  for (int jj = 0; jj <= 1; ++jj) {
-                      for (int ii = 0; ii <= 1; ++ii) {
-                          amrex::Gpu::Atomic::AddNoRet(&rho(i+ii-1, j+jj-1, k+kk-1, comp),
-                                                  sx[ii]*sy[jj]*sz[kk]*p.rdata(0)*p.rdata(comp));
-                      }
-                  }
-              }
-          }
+          ParticleInterpolator::Linear interp(p, plo, dxi);
+
+          interp.ParticleToMesh(p, rho, 0, 0, 1,
+                      [=] AMREX_GPU_DEVICE (const MyParticleContainer::ParticleType& part, int comp)
+                      {
+                          return part.rdata(comp);  // no weighting
+                      });
+
+          interp.ParticleToMesh(p, rho, 1, 1, AMREX_SPACEDIM,
+                      [=] AMREX_GPU_DEVICE (const MyParticleContainer::ParticleType& part, int comp)
+                      {
+                          return part.rdata(0) * p.rdata(comp);  // mass weight these comps
+                      });
       });

-  MultiFab acceleration(ba, dmap, BL_SPACEDIM, 1);
+  amrex::ParticleToMesh(myPC, partMF, 0,
+      [=] AMREX_GPU_DEVICE (const MyParticleContainer::ParticleType& p,
+                            amrex::Array4<amrex::Real> const& rho)
+      {
+          ParticleInterpolator::Nearest interp(p, plo, dxi);
+
+          interp.ParticleToMesh(p, rho, 0, 0, 1,
+              [=] AMREX_GPU_DEVICE (const MyParticleContainer::ParticleType& /*p*/,
+                                    int /*comp*/)
+              {
+                  return 1.0;  // no weighting
+              });
+      });
+
+  MultiFab acceleration(ba, dmap, AMREX_SPACEDIM, 1);
   acceleration.setVal(5.0);

-  nc = BL_SPACEDIM;
+  nc = AMREX_SPACEDIM;
   amrex::MeshToParticle(myPC, acceleration, 0,
       [=] AMREX_GPU_DEVICE (MyParticleContainer::ParticleType& p,
                             amrex::Array4<const amrex::Real> const& acc)
       {
-          amrex::Real lx = (p.pos(0) - plo[0]) * dxi[0] + 0.5;
-          amrex::Real ly = (p.pos(1) - plo[1]) * dxi[1] + 0.5;
-          amrex::Real lz = (p.pos(2) - plo[2]) * dxi[2] + 0.5;
-
-          int i = static_cast<int>(amrex::Math::floor(lx));
-          int j = static_cast<int>(amrex::Math::floor(ly));
-          int k = static_cast<int>(amrex::Math::floor(lz));
-
-          amrex::Real xint = lx - i;
-          amrex::Real yint = ly - j;
-          amrex::Real zint = lz - k;
-
-          amrex::Real sx[] = {1.-xint, xint};
-          amrex::Real sy[] = {1.-yint, yint};
-          amrex::Real sz[] = {1.-zint, zint};
-
-          for (int comp=0; comp < nc; ++comp) {
-              for (int kk = 0; kk <= 1; ++kk) {
-                  for (int jj = 0; jj <= 1; ++jj) {
-                      for (int ii = 0; ii <= 1; ++ii) {
-                          p.rdata(4+comp) += static_cast<ParticleReal> (sx[ii]*sy[jj]*sz[kk]*acc(i+ii-1,j+jj-1,k+kk-1,comp));
-                      }
-                  }
-              }
-          }
+          ParticleInterpolator::Linear interp(p, plo, dxi);
+
+          interp.MeshToParticle(p, acc, 0, 0, nc,
+                  [=] AMREX_GPU_DEVICE (amrex::Array4<const amrex::Real> const& arr,
+                                        int i, int j, int k, int comp)
+                  {
+                      return arr(i, j, k, comp);  // no weighting
+                  },
+                  [=] AMREX_GPU_DEVICE (MyParticleContainer::ParticleType& part,
+                                        int comp, amrex::Real val)
+                  {
+                      part.rdata(comp) += val;
+                  });
       });

   // now also try the iMultiFab versions
@@ -150,34 +131,36 @@ void testParticleMesh (TestParams& parms)
       [=] AMREX_GPU_DEVICE (const MyParticleContainer::ParticleType& p,
                             amrex::Array4<int> const& count)
       {
-          amrex::Real lx = (p.pos(0) - plo[0]) * dxi[0] + 0.5;
-          amrex::Real ly = (p.pos(1) - plo[1]) * dxi[1] + 0.5;
-          amrex::Real lz = (p.pos(2) - plo[2]) * dxi[2] + 0.5;
-
-          int i = static_cast<int>(amrex::Math::floor(lx));
-          int j = static_cast<int>(amrex::Math::floor(ly));
-          int k = static_cast<int>(amrex::Math::floor(lz));
+          ParticleInterpolator::Nearest interp(p, plo, dxi);

-          amrex::Gpu::Atomic::AddNoRet(&count(i, j, k), 1);
+          interp.ParticleToMesh(p, count, 0, 0, 1,
+              [=] AMREX_GPU_DEVICE (const MyParticleContainer::ParticleType& /*p*/, int /*comp*/) -> int
+              {
+                  return 1;  // just count the particles per cell
+              });
       });

   amrex::MeshToParticle(myPC, partiMF, 0,
       [=] AMREX_GPU_DEVICE (MyParticleContainer::ParticleType& p,
                             amrex::Array4<const int> const& count)
       {
-          amrex::Real lx = (p.pos(0) - plo[0]) * dxi[0] + 0.5;
-          amrex::Real ly = (p.pos(1) - plo[1]) * dxi[1] + 0.5;
-          amrex::Real lz = (p.pos(2) - plo[2]) * dxi[2] + 0.5;
-
-          int i = static_cast<int>(amrex::Math::floor(lx));
-          int j = static_cast<int>(amrex::Math::floor(ly));
-          int k = static_cast<int>(amrex::Math::floor(lz));
-
-          p.idata(0) = count(i, j, k);
+          ParticleInterpolator::Nearest interp(p, plo, dxi);
+
+          interp.MeshToParticle(p, count, 0, 0, 1,
+                  [=] AMREX_GPU_DEVICE (amrex::Array4<const int> const& arr,
+                                        int i, int j, int k, int comp)
+                  {
+                      return arr(i, j, k, comp);  // no weighting
+                  },
+                  [=] AMREX_GPU_DEVICE (MyParticleContainer::ParticleType& part,
+                                        int comp, int val)
+                  {
+                      part.idata(comp) = val;
+                  });
       });

   WriteSingleLevelPlotfile("plot", partMF,
-                           {"density", "vx", "vy", "vz"},
+                           {"density", AMREX_D_DECL("vx", "vy", "vz")},
                            geom, 0.0, 0);

   myPC.Checkpoint("plot", "particle0");
diff --git a/Tests/Particles/ParticleMeshMultiLevel/main.cpp b/Tests/Particles/ParticleMeshMultiLevel/main.cpp
index d77c8786966..f52c2899706 100644
--- a/Tests/Particles/ParticleMeshMultiLevel/main.cpp
+++ b/Tests/Particles/ParticleMeshMultiLevel/main.cpp
@@ -95,31 +95,14 @@ void testParticleMesh (TestParams& parms)
                               amrex::GpuArray<amrex::Real,AMREX_SPACEDIM> const& plo,
                               amrex::GpuArray<amrex::Real,AMREX_SPACEDIM> const& dxi) noexcept
         {
-          amrex::Real lx = (p.pos(0) - plo[0]) * dxi[0] + 0.5;
-          amrex::Real ly = (p.pos(1) - plo[1]) * dxi[1] + 0.5;
-          amrex::Real lz = (p.pos(2) - plo[2]) * dxi[2] + 0.5;
-
-          int i = static_cast<int>(amrex::Math::floor(lx));
-          int j = static_cast<int>(amrex::Math::floor(ly));
-          int k = static_cast<int>(amrex::Math::floor(lz));
-
-          amrex::Real xint = lx - i;
-          amrex::Real yint = ly - j;
-          amrex::Real zint = lz - k;
-
-          amrex::Real sx[] = {1.-xint, xint};
-          amrex::Real sy[] = {1.-yint, yint};
-          amrex::Real sz[] = {1.-zint, zint};
-
-          for (int kk = 0; kk <= 1; ++kk) {
-              for (int jj = 0; jj <= 1; ++jj) {
-                  for (int ii = 0; ii <= 1; ++ii) {
-                      amrex::Gpu::Atomic::AddNoRet(&rho(i+ii-1, j+jj-1, k+kk-1, 0),
-                                                   sx[ii]*sy[jj]*sz[kk]*p.rdata(0));
-                  }
-              }
-          }
-      });
+            ParticleInterpolator::Linear interp(p, plo, dxi);
+
+            interp.ParticleToMesh(p, rho, 0, 0, 1,
+                [=] AMREX_GPU_DEVICE (const MyParticleContainer::ParticleType& part, int comp)
+                {
+                    return part.rdata(comp);  // no weighting
+                });
+        });

     //
     // Here we provide an example of another way to call ParticleToMesh
@@ -128,8 +111,8 @@ void testParticleMesh (TestParams& parms)
     int start_mesh_comp = 0;
     int        num_comp = 1;

-    ParticleToMesh(myPC,GetVecOfPtrs(density2),0,parms.nlevs-1,
-                   TrilinearDeposition{start_part_comp,start_mesh_comp,num_comp});
+    amrex::ParticleToMesh(myPC,GetVecOfPtrs(density2),0,parms.nlevs-1,
+                          TrilinearDeposition{start_part_comp,start_mesh_comp,num_comp});

     //
     // Now write the output from each into separate plotfiles for comparison
diff --git a/Tests/Particles/ParticleMeshMultiLevel/trilinear_deposition_K.H b/Tests/Particles/ParticleMeshMultiLevel/trilinear_deposition_K.H
index 47f1e5049fb..fba78c9fc3e 100644
--- a/Tests/Particles/ParticleMeshMultiLevel/trilinear_deposition_K.H
+++ b/Tests/Particles/ParticleMeshMultiLevel/trilinear_deposition_K.H
@@ -4,46 +4,26 @@
 #include <AMReX_MultiFabUtil.H>
 #include <AMReX_Particles.H>
 #include <AMReX_AmrParticles.H>
+#include <AMReX_ParticleInterpolators.H>

 struct TrilinearDeposition
 {
-  int start_part_comp;
-  int start_mesh_comp;
-  int   num_comp;
+    int start_part_comp;
+    int start_mesh_comp;
+    int   num_comp;

-  AMREX_GPU_DEVICE
-  void operator()
-                  (const MyParticleContainer::ParticleType& p,
-                   amrex::Array4<amrex::Real> const& rho,
-                   amrex::GpuArray<amrex::Real,AMREX_SPACEDIM> const& plo,
-                   amrex::GpuArray<amrex::Real,AMREX_SPACEDIM> const& dxi) const noexcept
-  {
-      amrex::Real lx = (p.pos(0) - plo[0]) * dxi[0] + 0.5;
-      amrex::Real ly = (p.pos(1) - plo[1]) * dxi[1] + 0.5;
-      amrex::Real lz = (p.pos(2) - plo[2]) * dxi[2] + 0.5;
+    AMREX_GPU_DEVICE AMREX_FORCE_INLINE
+    void operator() (const MyParticleContainer::ParticleType& p,
+                     amrex::Array4<amrex::Real> const& rho,
+                     amrex::GpuArray<amrex::Real,AMREX_SPACEDIM> const& plo,
+                     amrex::GpuArray<amrex::Real,AMREX_SPACEDIM> const& dxi) const noexcept
+    {
+        amrex::ParticleInterpolator::Linear interp(p, plo, dxi);

-      int i = static_cast<int>(amrex::Math::floor(lx));
-      int j = static_cast<int>(amrex::Math::floor(ly));
-      int k = static_cast<int>(amrex::Math::floor(lz));
-
-      amrex::Real xint = lx - i;
-      amrex::Real yint = ly - j;
-      amrex::Real zint = lz - k;
-
-      amrex::Real sx[] = {1.-xint, xint};
-      amrex::Real sy[] = {1.-yint, yint};
-      amrex::Real sz[] = {1.-zint, zint};
-
-      for (int kk = 0; kk <= 1; ++kk) {
-          for (int jj = 0; jj <= 1; ++jj) {
-              for (int ii = 0; ii <= 1; ++ii) {
-                  for (int icomp = 0; icomp < num_comp; icomp++)
-                  {
-                      amrex::Gpu::Atomic::AddNoRet(&rho(i+ii-1, j+jj-1, k+kk-1, start_mesh_comp+icomp),
-                                                   sx[ii]*sy[jj]*sz[kk]*p.rdata(start_part_comp+icomp));
-                  }
-              }
-          }
-      }
-  }
+        interp.ParticleToMesh(p, rho, start_part_comp, start_mesh_comp, num_comp,
+            [=] AMREX_GPU_DEVICE (const MyParticleContainer::ParticleType& part, int comp)
+            {
+                return part.rdata(comp);  // no weighting
+            });
+    }
 };
diff --git a/Tests/Particles/ParticleReduce/main.cpp b/Tests/Particles/ParticleReduce/main.cpp
index cdecf985430..382c9191804 100644
--- a/Tests/Particles/ParticleReduce/main.cpp
+++ b/Tests/Particles/ParticleReduce/main.cpp
@@ -3,6 +3,8 @@
 #include <AMReX_MultiFab.H>
 #include <AMReX_Particles.H>

+#include <cmath>
+
 using namespace amrex;

 static constexpr int NSR = 4;
@@ -12,17 +14,25 @@ static constexpr int NAI = 1;

 void get_position_unit_cell(Real* r, const IntVect& nppc, int i_part)
 {
-    int nx = nppc[0];
-    int ny = nppc[1];
-    int nz = nppc[2];
-
-    int ix_part = i_part/(ny * nz);
-    int iy_part = (i_part % (ny * nz)) % ny;
-    int iz_part = (i_part % (ny * nz)) / ny;
-
-    r[0] = (0.5+ix_part)/nx;
-    r[1] = (0.5+iy_part)/ny;
-    r[2] = (0.5+iz_part)/nz;
+        int nx = nppc[0];
+#if AMREX_SPACEDIM >= 2
+        int ny = nppc[1];
+#else
+        int ny = 1;
+#endif
+#if AMREX_SPACEDIM == 3
+        int nz = nppc[2];
+#else
+        int nz = 1;
+#endif
+
+        AMREX_D_TERM(int ix_part = i_part/(ny * nz);,
+                     int iy_part = (i_part % (ny * nz)) % ny;,
+                     int iz_part = (i_part % (ny * nz)) / ny;)
+
+        AMREX_D_TERM(r[0] = (0.5+ix_part)/nx;,
+                     r[1] = (0.5+iy_part)/ny;,
+                     r[2] = (0.5+iz_part)/nz;)
 }

 class TestParticleContainer
@@ -58,19 +68,19 @@ class TestParticleContainer
             for (IntVect iv = tile_box.smallEnd(); iv <= tile_box.bigEnd(); tile_box.next(iv))
             {
                 for (int i_part=0; i_part<num_ppc;i_part++) {
-                    Real r[3];
+                    Real r[AMREX_SPACEDIM];
                     get_position_unit_cell(r, a_num_particles_per_cell, i_part);

-                    ParticleReal x = static_cast<ParticleReal> (plo[0] + (iv[0] + r[0])*dx[0]);
-                    ParticleReal y = static_cast<ParticleReal> (plo[1] + (iv[1] + r[1])*dx[1]);
-                    ParticleReal z = static_cast<ParticleReal> (plo[2] + (iv[2] + r[2])*dx[2]);
+                    AMREX_D_TERM(ParticleReal x = static_cast<ParticleReal> (plo[0] + (iv[0] + r[0])*dx[0]);,
+                                 ParticleReal y = static_cast<ParticleReal> (plo[1] + (iv[1] + r[1])*dx[1]);,
+                                 ParticleReal z = static_cast<ParticleReal> (plo[2] + (iv[2] + r[2])*dx[2]);)

                     ParticleType p;
                     p.id()  = ParticleType::NextID();
                     p.cpu() = ParallelDescriptor::MyProc();
-                    p.pos(0) = x;
-                    p.pos(1) = y;
-                    p.pos(2) = z;
+                    AMREX_D_TERM(p.pos(0) = x;,
+                                 p.pos(1) = y;,
+                                 p.pos(2) = z;)

                     for (int i = 0; i < NSR; ++i) p.rdata(i) = i;
                     for (int i = 0; i < NSI; ++i) p.idata(i) = i;
@@ -225,7 +235,7 @@ void testReduce ()
                return {a, b, c};
            }, reduce_ops);

-        AMREX_ALWAYS_ASSERT(amrex::get<0>(r) == 16777216.0);
+        AMREX_ALWAYS_ASSERT(amrex::get<0>(r) == amrex::Real(std::pow(256, AMREX_SPACEDIM)));
         AMREX_ALWAYS_ASSERT(amrex::get<1>(r) == 2.0);
         AMREX_ALWAYS_ASSERT(amrex::get<2>(r) == 1);
     }
diff --git a/Tests/Particles/ParticleTransformations/main.cpp b/Tests/Particles/ParticleTransformations/main.cpp
index 0931165200f..e60a6762f93 100644
--- a/Tests/Particles/ParticleTransformations/main.cpp
+++ b/Tests/Particles/ParticleTransformations/main.cpp
@@ -12,17 +12,25 @@ static constexpr int NAI = 2;

 void get_position_unit_cell(Real* r, const IntVect& nppc, int i_part)
 {
-    int nx = nppc[0];
-    int ny = nppc[1];
-    int nz = nppc[2];
-
-    int ix_part = i_part/(ny * nz);
-    int iy_part = (i_part % (ny * nz)) % ny;
-    int iz_part = (i_part % (ny * nz)) / ny;
-
-    r[0] = (0.5+ix_part)/nx;
-    r[1] = (0.5+iy_part)/ny;
-    r[2] = (0.5+iz_part)/nz;
+        int nx = nppc[0];
+#if AMREX_SPACEDIM >= 2
+        int ny = nppc[1];
+#else
+        int ny = 1;
+#endif
+#if AMREX_SPACEDIM == 3
+        int nz = nppc[2];
+#else
+        int nz = 1;
+#endif
+
+        AMREX_D_TERM(int ix_part = i_part/(ny * nz);,
+                     int iy_part = (i_part % (ny * nz)) % ny;,
+                     int iz_part = (i_part % (ny * nz)) / ny;)
+
+        AMREX_D_TERM(r[0] = (0.5+ix_part)/nx;,
+                     r[1] = (0.5+iy_part)/ny;,
+                     r[2] = (0.5+iz_part)/nz;)
 }

 class TestParticleContainer
@@ -64,16 +72,16 @@ class TestParticleContainer
                     Real r[3];
                     get_position_unit_cell(r, a_num_particles_per_cell, i_part);

-                    ParticleReal x = static_cast<ParticleReal> (plo[0] + (iv[0] + r[0])*dx[0]);
-                    ParticleReal y = static_cast<ParticleReal> (plo[1] + (iv[1] + r[1])*dx[1]);
-                    ParticleReal z = static_cast<ParticleReal> (plo[2] + (iv[2] + r[2])*dx[2]);
+                    AMREX_D_TERM(ParticleReal x = static_cast<ParticleReal> (plo[0] + (iv[0] + r[0])*dx[0]);,
+                                 ParticleReal y = static_cast<ParticleReal> (plo[1] + (iv[1] + r[1])*dx[1]);,
+                                 ParticleReal z = static_cast<ParticleReal> (plo[2] + (iv[2] + r[2])*dx[2]);)

                     ParticleType p;
                     p.id()  = ParticleType::NextID();
                     p.cpu() = ParallelDescriptor::MyProc();
-                    p.pos(0) = x;
-                    p.pos(1) = y;
-                    p.pos(2) = z;
+                    AMREX_D_TERM(p.pos(0) = x;,
+                                 p.pos(1) = y;,
+                                 p.pos(2) = z;)

                     for (int i = 0; i < NSR; ++i) p.rdata(i) = i;
                     for (int i = 0; i < NSI; ++i) p.idata(i) = i;


Update locations for tutorials and minor formatting. (AMReX-Codes/amrex#2583)

## Summary
Update remaining locations for tutorials referenced in the docs.

## Additional background
Follow up to #2576
## Checklist

The proposed changes:
- [ ] fix a bug or incorrect behavior in AMReX
- [ ] add new capabilities to AMReX
- [ ] changes answers in the test suite to more than roundoff level
- [ ] are likely to significantly affect the results of downstream AMReX users
- [x] include documentation in the code and/or rst files, if appropriate

Update locations from `amrex/Tutorials`

## Summary
Many tutorials are listed as residing at `amrex/Tutorials/...`. They have been updated to their new
location at `amrex-tutorials/ExampleCodes/...`.

## Additional background
The updated locations cause straightforward changes to the Catalyst and Libsim tutorials in Visualizations.rst (line 901 and 927). I made these changes but I did not retest the tutorials. I'll put this on my list, and update if any changes are necessary.

## Checklist

The proposed changes:
- [ ] fix a bug or incorrect behavior in AMReX
- [ ] add new capabilities to AMReX
- [ ] changes answers in the test suite to more than roundoff level
- [ ] are likely to significantly affect the results of downstream AMReX users
- [x] include documentation in the code and/or rst files, if appropriate

diff --git a/Docs/sphinx_documentation/source/Fortran.rst b/Docs/sphinx_documentation/source/Fortran.rst
index cd613bd6d85..ddfcd755d54 100644
--- a/Docs/sphinx_documentation/source/Fortran.rst
+++ b/Docs/sphinx_documentation/source/Fortran.rst
@@ -384,7 +384,7 @@ interfaces:
         character(c_char), intent(in), value :: tagval, clearval
       end subroutine amrex_error_est_proc

-``Tutorials/Amr/Advection_F/Source/my_amr_mod.F90`` shows an
+``amrex-tutorials/ExampleCodes/FortranInterface/Advection_F/Source/my_amr_mod.F90`` shows an
 example of the setup process. The user provided
 :fortran:`procedure(amrex_error_est_proc)` has a tags argument that
 is of type :fortran:`c_ptr` and its value is a pointer to a
@@ -400,7 +400,7 @@ The module :fortran:`amrex_fillpatch_module` provides interface to
 C++ functions :cpp:`FillPatchSinglelevel` and :cpp:`FillPatchTwoLevels`. To use
 it, the application code needs to provide procedures for interpolation and
 filling physical boundaries.  See
-``Tutorials/Amr/Advection_F/Source/fillpatch_mod.F90`` for an example.
+``amrex-tutorials/ExampleCodes/FortranInterface/Advection_F/Source/fillpatch_mod.F90`` for an example.

 Module :fortran:`amrex_fluxregister_module` provides interface to
 :cpp:`FluxRegister` (see the section on :ref:`sec:amrcore:fluxreg`). Its usage
@@ -418,7 +418,7 @@ between levels. Therefore, grids in AMReX in general cannot be represented by
 trees. Nevertheless, octree type grids are supported via Fortran interface,
 because grids are more general than octree grids. A tutorial example using
 amrex_octree_module ( ``amrex/Src/F_Interfaces/Octree/AMReX_octree_mod.f90``) is
-available at ``amrex-tutorials/Amr/Advection_octree_F/``. Procedures
+available at ``amrex-tutorials/ExampleCodes/FortranInterface/Advection_F/Advection_octree_F/``. Procedures
 :fortran:`amrex_octree_init` and :fortran:`amrex_octree_finalize` must be
 called as follows,

diff --git a/Docs/sphinx_documentation/source/GPU.rst b/Docs/sphinx_documentation/source/GPU.rst
index 37bd255073f..7c8863c2c3c 100644
--- a/Docs/sphinx_documentation/source/GPU.rst
+++ b/Docs/sphinx_documentation/source/GPU.rst
@@ -158,8 +158,8 @@ For example, ``COMP=pgi`` alone will compile C/C++ codes with NVCC/GCC
 and Fortran codes with PGI, and link with PGI.  Using ``COMP=pgi`` and
 ``NVCC_HOST_COMP=pgi`` will compile C/C++ codes with PGI and NVCC/PGI.

-You can use ``Tutorials/Basic/HelloWorld_C`` to test your programming
-environment.  For example, building with:
+You can use ``amrex-tutorials/ExampleCodes/Basic/HelloWorld_C/``
+to test your programming environment.  For example, building with:

 .. highlight:: console

diff --git a/Docs/sphinx_documentation/source/GettingStarted.rst b/Docs/sphinx_documentation/source/GettingStarted.rst
index 05af8292561..ba819cb2c99 100644
--- a/Docs/sphinx_documentation/source/GettingStarted.rst
+++ b/Docs/sphinx_documentation/source/GettingStarted.rst
@@ -48,13 +48,13 @@ AMReX.H). All AMReX C++ functions are in the :cpp:`amrex` namespace.
 Building the Code
 -----------------

-You build the code in the ``amrex-tutorials/Tutorials/Basic/HelloWorld_C/`` directory.
+You build the code in the ``amrex-tutorials/ExampleCodes/Basic/HelloWorld_C/`` directory.
 Typing ``make`` will start the compilation process and result in an executable
 named ``main3d.gnu.DEBUG.ex``. The name shows that the GNU compiler with debug
 options set by AMReX is used.  It also shows that the executable is built for
 3D. Although this simple example code is dimension independent, dimensionality
 does matter for all non-trivial examples. The build process can be adjusted by
-modifying the ``amrex-tutorials/Tutorials/Basic/HelloWorld_C/GNUmakefile`` file.  More
+modifying the ``amrex-tutorials/ExampleCodes/Basic/HelloWorld_C/GNUmakefile`` file.  More
 details on how to build AMReX can be found in :ref:`Chap:BuildingAMReX`.

 Running the Code
diff --git a/Docs/sphinx_documentation/source/LinearSolvers.rst b/Docs/sphinx_documentation/source/LinearSolvers.rst
index a68336a200a..6a08c0dc5a7 100644
--- a/Docs/sphinx_documentation/source/LinearSolvers.rst
+++ b/Docs/sphinx_documentation/source/LinearSolvers.rst
@@ -676,23 +676,23 @@ An example (implemented in the ``MultiComponent`` tutorial) might be:
   .. _fig::refluxfreecoarsefine:

   .. figure:: ./LinearSolvers/refluxfreecoarsefine.png
-      :height: 2cm
-      :align: center
+     :height: 4cm
+     :align: center

-      : Reflux-free coarse-fine boundary update.
-      Level 2 ghost nodes (small dark blue) are interpolated from coarse boundary.
-      Level 1 ghost nodes are updated during the relaxation along with all the other interior fine nodes.
-      Coarse nodes (large blue) on the coarse/fine boundary are updated by restricting with interior nodes
-      and the first level of ghost nodes.
-      Coarse nodes underneath level 2 ghost nodes are not updated.
-      The remaining coarse nodes are updates by restriction.
+     Reflux-free coarse-fine boundary update.
+     Level 2 ghost nodes (small dark blue) are interpolated from coarse boundary.
+     Level 1 ghost nodes are updated during the relaxation along with all the other interior fine nodes.
+     Coarse nodes (large blue) on the coarse/fine boundary are updated by restricting with interior nodes
+     and the first level of ghost nodes.
+     Coarse nodes underneath level 2 ghost nodes are not updated.
+     The remaining coarse nodes are updates by restriction.

   The MC nodal operator can inherit from the ``MCNodeLinOp`` class.
   ``Fapply``, ``Fsmooth``, and ``Fflux`` must update level 1 ghost nodes that are inside the domain.
   `interpolation` and `restriction` can be implemented as usual.
   `reflux` is a straightforward restriction from fine to coarse, using level 1 ghost nodes for restriction as described above.

-  See ``Tutorials/LinearSolvers/MultiComponent`` for a complete working example.
+See ``amrex-tutorials/ExampleCodes/LinearSolvers/MultiComponent`` for a complete working example.

 .. solver reuse

diff --git a/Docs/sphinx_documentation/source/Visualization.rst b/Docs/sphinx_documentation/source/Visualization.rst
index 6de66a045f4..27a058ae5d0 100644
--- a/Docs/sphinx_documentation/source/Visualization.rst
+++ b/Docs/sphinx_documentation/source/Visualization.rst
@@ -426,7 +426,7 @@ we will assume a single vector has been stored as three separate variables,
    by its magnitude. To do this, look under the Scale heading, select the
    vector value as the Scale Array and select Scale by Magnitude.

-   Two adjust the number and location of vectors displayed, one may alter the
+   To adjust the number and location of vectors displayed, one may alter the
    settings under the Masking heading.

 .. figure:: ./Visualization/ParaView_vectorfield.png

diff --git a/Docs/sphinx_documentation/source/Testing.rst b/Docs/sphinx_documentation/source/Testing.rst
index 573ed9a3aa8..b7e32c9477b 100644
--- a/Docs/sphinx_documentation/source/Testing.rst
+++ b/Docs/sphinx_documentation/source/Testing.rst
@@ -13,7 +13,7 @@ quickly on every commit. For more extensive testing, we rely on the nightly regr
 Nightly Regression Testing
 ==========================

-Each night, we automically run a suite of tests, both on AMReX itself, and on a most of the major
+Each night, we automatically run a suite of tests, both on AMReX itself, and on a most of the major
 application codes that use it as a framework. We use an in-house test runner script to manage this
 operation, originally developed by Michael Zingale for the Castro code, and later expanded to other
 application codes as well. The results for each night are collected and stored on a web page; see
@@ -23,13 +23,15 @@ Running the test suite locally
 ==============================

 The test suite is mostly used internally by AMReX developers. However,
-f you are making a pull request to AMReX, it can be useful to run the test suite
-on your local machine, to reduce the liklihood that your changes break some existing functionality.
-To run the test suite on locally, you must first obtain a copy of the test runner source, available
+if you are making a pull request to AMReX, it can be useful to run the test suite
+on your local machine to reduce the liklihood that your changes break some existing functionality.
+To run the test suite locally, you must first obtain a copy of the test runner source, available
 on Github here: https://github.com/AMReX-Codes/regression_testing. The test runner requires Python
-version 2.7 or greater.
+version 2.7 or greater. Additional information on the test suite software can be found at,
+https://amrex-codes.github.io/regression_testing/.

-Next, you need a configuration file that defines which tests to run, which amrex repository to test,
+After obtaining the code, you will need a configuration file that defines which tests to run, which
+amrex repository to test,
 which branch to use, etc. A sample configuration file for AMReX is distributed with the amrex source
 code at :cpp:`amrex/Tools/RegressionTesting/AMReX-tests.ini`. You will need to modify a few of the entries
 to, for example, point the test runner to the clone of amrex on your local machine. Entries you will
@@ -56,24 +58,24 @@ The test runner is a Python script and can be invoked like so:

 ::

-   python regtest.py <options> AMReX-Tests.ini
+   python regtest.py <options> AMReX-tests.ini

 Before you can use it, you must first generate a set of "benchmarks" - i.e. known "good" answers to the
 tests that will be run. If you are testing a pull request, you can generate these by running the script
-with the a recent version of the :cpp:`development` branch of AMReX. You can generate the benchmarks like so:
+with a recent version of the :cpp:`development` branch of AMReX. You can generate the benchmarks like so:

 ::

-   python regtest.py --make_benchmarks 'generating initial benchmarks' AMReX-Tests.ini
+   python regtest.py --make_benchmarks 'generating initial benchmarks' AMReX-tests.ini

-Once that is finished, you can switch over to the branch you want to test in :cpp:`AMReX-Tests.ini`, and then
+Once that is finished, you can switch over to the branch you want to test in :cpp:`AMReX-tests.ini`, and then
 re-run the script without the :cpp:`--make_benchmarks` option:

 ::

-   python regtest.py --make_benchmarks 'generating initial benchmarks' AMReX-Tests.ini
+   python regtest.py --make_benchmarks 'generating initial benchmarks' AMReX-tests.ini

-The script will generate a set of html pages in the directory specified in your :cpp:`AMReX-Tests.ini`
+The script will generate a set of html pages in the directory specified in your :cpp:`AMReX-tests.ini`
 file that you can examine using the browser of your choice.

 For a complete set of script options, run
@@ -86,25 +88,25 @@ A particularly useful option lets you run just a subset of the complete test sui

 ::

-   python regtest.py --single_test <TestName> AMReX-Tests.ini
+   python regtest.py --single_test <TestName> AMReX-tests.ini

 To run an enumerated list of tests, do:

 ::

-   python regtest.py --tests '<TestName1> <TestName2> <TestName3>' AMReX-Tests.ini
+   python regtest.py --tests '<TestName1> <TestName2> <TestName3>' AMReX-tests.ini

 Adding a new test
 =================

-New tests can be added to the suite by modifying the :cpp:`AMReX-Tests.ini` file. The easiest thing to
+New tests can be added to the suite by modifying the :cpp:`AMReX-tests.ini` file. The easiest thing to
 do is start from an existing test and modify it. For example, this entry:

 ::

    [MLMG_FI_PoisCom]
-   buildDir = Tutorials/LinearSolvers/ABecLaplacian_F
+   buildDir = Tests/LinearSolvers/ABecLaplacian_F
    inputFile = inputs-rt-poisson-com
    dim = 3
    restartTest = 0


CMake: 3.17+ (AMReX-Codes/amrex#2813)

## Summary

We are using `find_package(CUDAToolkit ...)` now, which is only supported in CMake 3.17+.
Follow-up to #2757

## Additional background

Technically, we want to go to 3.20+ soon as well, so that we can drop the old CUDA code paths that we used to forward device flags and CUDA architectures.

## Checklist

The proposed changes:
- [ ] fix a bug or incorrect behavior in AMReX
- [ ] add new capabilities to AMReX
- [ ] changes answers in the test suite to more than roundoff level
- [ ] are likely to significantly affect the results of downstream AMReX users
- [ ] include documentation in the code and/or rst files, if appropriate

CUDA On Cray: More Robust w/o Wrapper

## Summary

Searching for `curand` explicitly (required by default by AMReX) makes HPE/Cray builds more robust when we build w/o compiler wrappers, e.g., if files are distributed in a non-standard layout.

## Additional background

https://gitlab.kitware.com/cmake/cmake/-/merge_requests/7247

Compile error on Perlmutter with `g++` and `nvcc` as compilers was:
```
                 from /global/homes/a/ahuebl/src/amrex/Src/Base/AMReX_Utility.cpp:4:
/global/homes/a/ahuebl/src/amrex/Src/Base/AMReX_RandomEngine.H:14:10: fatal error: curand.h: No such file or directory
   14 | #include <curand.h>
      |          ^~~~~~~~~~
```

## Checklist

The proposed changes:
- [ ] fix a bug or incorrect behavior in AMReX
- [ ] add new capabilities to AMReX
- [ ] changes answers in the test suite to more than roundoff level
- [ ] are likely to significantly affect the results of downstream AMReX users
- [ ] include documentation in the code and/or rst files, if appropriate

diff --git a/CMakeLists.txt b/CMakeLists.txt
index f957b2ef5b9..773058369f7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.14)
+cmake_minimum_required(VERSION 3.17)

 #
 # Allow for MSVC Runtime library controls
diff --git a/Docs/sphinx_documentation/source/BuildingAMReX.rst b/Docs/sphinx_documentation/source/BuildingAMReX.rst
index f5b8b263a4e..7b3273bf874 100644
--- a/Docs/sphinx_documentation/source/BuildingAMReX.rst
+++ b/Docs/sphinx_documentation/source/BuildingAMReX.rst
@@ -728,7 +728,7 @@ You can tell CMake to look for the AMReX library in non-standard paths by settin
 ``AMReX_ROOT`` to point to the AMReX installation directory or by adding
 ``-DAMReX_ROOT=<path/to/amrex/installation/directory>`` to the ``cmake`` invocation.
 More details on ``find_package`` can be found
-`here <https://cmake.org/cmake/help/v3.14/command/find_package.html>`_.
+`here <https://cmake.org/cmake/help/v3.17/command/find_package.html>`_.

 .. _sec:build:windows:

diff --git a/Docs/sphinx_documentation/source/BuildingAMReX_Chapter.rst b/Docs/sphinx_documentation/source/BuildingAMReX_Chapter.rst
index a6b246ee155..dd61bb254d3 100644
--- a/Docs/sphinx_documentation/source/BuildingAMReX_Chapter.rst
+++ b/Docs/sphinx_documentation/source/BuildingAMReX_Chapter.rst
@@ -23,7 +23,7 @@ Fortran compiler that supports the Fortran 2003 standard, and a C
 compiler that supports the C99 standard.  Prerequisites for building
 with GNU Make include Python (>= 2.7, including 3) and standard tools
 available in any Unix-like environments (e.g., Perl and sed).  For
-building with CMake, the minimal requirement is version 3.14.
+building with CMake, the minimal requirement is version 3.17.

 Please note that we fully support AMReX for Linux systems in general and on the
 DOE supercomputers (e.g. Cori, Summit) in particular.  Many of our users do build
diff --git a/Tools/CMake/AMReXThirdPartyLibraries.cmake b/Tools/CMake/AMReXThirdPartyLibraries.cmake
index e8be52dc173..24dd8df11d2 100644
--- a/Tools/CMake/AMReXThirdPartyLibraries.cmake
+++ b/Tools/CMake/AMReXThirdPartyLibraries.cmake
@@ -82,7 +82,14 @@ if (AMReX_HYPRE)
     find_package(HYPRE 2.20.0 REQUIRED)
     if(AMReX_CUDA)
         find_package(CUDAToolkit REQUIRED)
+
+        # mandatory CUDA dependencies: cuSPARSE, cuRAND
         target_link_libraries(amrex PUBLIC CUDA::cusparse CUDA::curand)
+
+        # nvToolsExt: if tiny profiler or base profiler are on.
+        if (AMReX_TINY_PROFILE OR AMReX_BASE_PROFILE)
+            target_link_libraries(amrex PUBLIC CUDA::nvToolsExt)
+        endif ()
     endif()
     target_link_libraries( amrex PUBLIC HYPRE )
 endif ()
diff --git a/Tools/CMake/AMReX_Config.cmake b/Tools/CMake/AMReX_Config.cmake
index 22429ce2651..6ed841cfd59 100644
--- a/Tools/CMake/AMReX_Config.cmake
+++ b/Tools/CMake/AMReX_Config.cmake
@@ -49,7 +49,7 @@ function (configure_amrex)
       target_compile_features(amrex PUBLIC $<IF:$<STREQUAL:$<PLATFORM_ID>,Windows>,cxx_std_17,cxx_std_14>)
    endif ()

-   if (AMReX_CUDA AND (CMAKE_VERSION VERSION_GREATER_EQUAL 3.17) )
+   if (AMReX_CUDA)
       set_target_properties(amrex PROPERTIES CUDA_EXTENSIONS OFF)
       # minimum: C++14 on Linux, C++17 on Windows
       target_compile_features(amrex PUBLIC $<IF:$<STREQUAL:$<PLATFORM_ID>,Windows>,cuda_std_17,cuda_std_14>)
@@ -132,8 +132,9 @@ function (configure_amrex)
       endif ()

       #
-      # Add manually nvToolsExt if tiny profiler or base profiler are on.n
+      # Add manually nvToolsExt if tiny profiler or base profiler are on.
       # CMake >= 3.17 provides the module FindCUDAToolkit to do this natively.
+      # TODO: Modernize me, now that we require CMake 3.17+.
       #
       if (AMReX_TINY_PROFILE OR AMReX_BASE_PROFILE )
           find_library(LIBNVTOOLSEXT nvToolsExt PATHS ${CMAKE_CUDA_IMPLICIT_LINK_DIRECTORIES})

diff --git a/Tests/CMakeTestInstall/CMakeLists.txt b/Tests/CMakeTestInstall/CMakeLists.txt
index 382d1911e5a..fdac1442cac 100644
--- a/Tests/CMakeTestInstall/CMakeLists.txt
+++ b/Tests/CMakeTestInstall/CMakeLists.txt
@@ -3,7 +3,7 @@
 # building and running the code
 # in Tests/Amr/Advection_AmrCore/
 #
-cmake_minimum_required(VERSION 3.14)
+cmake_minimum_required(VERSION 3.17)

 project(amrex-test-install)

diff --git a/Tests/SpackSmokeTest/CMakeLists.txt b/Tests/SpackSmokeTest/CMakeLists.txt
index 4b1780f2534..4882f5bbd25 100644
--- a/Tests/SpackSmokeTest/CMakeLists.txt
+++ b/Tests/SpackSmokeTest/CMakeLists.txt
@@ -6,7 +6,7 @@
 # against a currently installed version of AMReX. The resulting
 # executable can then be ran to test functionality.

-cmake_minimum_required(VERSION 3.14)
+cmake_minimum_required(VERSION 3.17)

 project(amrex-test-install)


Fix nl test (AMReX-Codes/amrex#3142)

This fixes a bug introduced in PR #3060 related to building neighbor lists between particles of different types. I also made it so that CI tests would have caught this bug.

Closes #3127

The proposed changes:
- [x] fix a bug or incorrect behavior in AMReX
- [ ] add new capabilities to AMReX
- [ ] changes answers in the test suite to more than roundoff level
- [ ] are likely to significantly affect the results of downstream AMReX users
- [ ] include documentation in the code and/or rst files, if appropriate

Polydisperse neighbor search algorithm

## Summary
New functionality for building the particle neighbor list with multiple grids. The multi-grid approach can provide significant speed up with polydisperse particle-laden flows.

## Additional background
Algorithm based upon:
Shire, T., Hanley, K.J. & Stratford, K. DEM simulations of polydisperse media: efficient contact detection applied to investigate the quasi-static limit. Comp. Part. Mech. 8, 653–663 (2021). https://doi.org/10.1007/s40571-020-00361-2

## Checklist
1. Verify correctness with standard single-grid (SG) neighbor search (MFiX regtests).
2. Test speed with SG neighbor search (Perlmutter w/ canonical sim)
3. Verify new multi-grid (MG) gives same results as SG (new polydisperse test)
4. Document speed up of MG vs SG for polydisperse test

The proposed changes:
- [ ] fix a bug or incorrect behavior in AMReX
- [x] add new capabilities to AMReX
- [ ] changes answers in the test suite to more than roundoff level
- [ ] are likely to significantly affect the results of downstream AMReX users
- [ ] include documentation in the code and/or rst files, if appropriate

Tests/Particles/NeighborList compilation error

```
../../..//Src/Particle/AMReX_NeighborList.H(310): error: a value of type "amrex::Particle<0, 1> *" cannot be assigned to an entity of type "amrex::Particle<0, 0> *"
          detected during:
            instantiation of "void amrex::NeighborList<ParticleType>::build(SrcTile &, TargetTile &, CheckPair &&, const amrex::Gpu::DeviceVector<int> &, const amrex::Gpu::D
eviceVector<amrex::GpuArray<amrex::Real, 3U>> &, const amrex::Gpu::DeviceVector<amrex::GpuArray<amrex::Real, 3U>> &, const amrex::Gpu::DeviceVector<amrex::Dim3> &, const amr
ex::Gpu::DeviceVector<amrex::Dim3> &, int, int, int *) [with ParticleType=amrex::Particle<0, 0>, SrcTile=const amrex::ParticleTile<0, 0, 0, 0, amrex::DefaultAllocator>, Targ
etTile=amrex::ParticleTile<0, 1, 0, 0, amrex::DefaultAllocator>, CheckPair=CheckPair]"
../../..//Src/Particle/AMReX_NeighborParticlesI.H(833): here
            instantiation of "void amrex::NeighborParticleContainer<NStructReal, NStructInt, NArrayReal, NArrayInt>::buildNeighborList(CheckPair &&, OtherPCType &, amrex::Ve
ctor<std::map<std::pair<int, int>, amrex::NeighborList<OtherPCType::ParticleType>, std::less<std::pair<int, int>>, std::allocator<std::pair<const std::pair<int, int>, amrex:
:NeighborList<OtherPCType::ParticleType>>>>, std::allocator<std::map<std::pair<int, int>, amrex::NeighborList<OtherPCType::ParticleType>, std::less<std::pair<int, int>>, std
::allocator<std::pair<const std::pair<int, int>, amrex::NeighborList<OtherPCType::ParticleType>>>>>> &, __nv_bool) [with NStructReal=0, NStructInt=0, NArrayReal=0, NArrayInt
=0, CheckPair=CheckPair, OtherPCType=PCType2]"
main.cpp(131): here

../../..//Src/Particle/AMReX_NeighborList.H(331): error: no instance of overloaded function "amrex::DenseBins<T>::build [with T=amrex::Particle<0, 0>]" matches the argument
list
            argument types are: (const size_t, amrex::Particle<0, 1> *const, int, amrex::BinMapper)
            object type is: amrex::DenseBins<amrex::Particle<0, 0>>
          detected during:
            instantiation of "void amrex::NeighborList<ParticleType>::build(SrcTile &, TargetTile &, CheckPair &&, const amrex::Gpu::DeviceVector<int> &, const amrex::Gpu::D
eviceVector<amrex::GpuArray<amrex::Real, 3U>> &, const amrex::Gpu::DeviceVector<amrex::GpuArray<amrex::Real, 3U>> &, const amrex::Gpu::DeviceVector<amrex::Dim3> &, const amr
ex::Gpu::DeviceVector<amrex::Dim3> &, int, int, int *) [with ParticleType=amrex::Particle<0, 0>, SrcTile=const amrex::ParticleTile<0, 0, 0, 0, amrex::DefaultAllocator>, Targ
etTile=amrex::ParticleTile<0, 1, 0, 0, amrex::DefaultAllocator>, CheckPair=CheckPair]"
../../..//Src/Particle/AMReX_NeighborParticlesI.H(833): here
            instantiation of "void amrex::NeighborParticleContainer<NStructReal, NStructInt, NArrayReal, NArrayInt>::buildNeighborList(CheckPair &&, OtherPCType &, amrex::Ve
ctor<std::map<std::pair<int, int>, amrex::NeighborList<OtherPCType::ParticleType>, std::less<std::pair<int, int>>, std::allocator<std::pair<const std::pair<int, int>, amrex:
:NeighborList<OtherPCType::ParticleType>>>>, std::allocator<std::map<std::pair<int, int>, amrex::NeighborList<OtherPCType::ParticleType>, std::less<std::pair<int, int>>, std
::allocator<std::pair<const std::pair<int, int>, amrex::NeighborList<OtherPCType::ParticleType>>>>>> &, __nv_bool) [with NStructReal=0, NStructInt=0, NArrayReal=0, NArrayInt
=0, CheckPair=CheckPair, OtherPCType=PCType2]"
main.cpp(131): here
```

diff --git a/Src/Particle/AMReX_NeighborParticlesI.H b/Src/Particle/AMReX_NeighborParticlesI.H
index 696553aaa47..ed2e1844725 100644
--- a/Src/Particle/AMReX_NeighborParticlesI.H
+++ b/Src/Particle/AMReX_NeighborParticlesI.H
@@ -828,9 +828,9 @@ buildNeighborList (CheckPair&& check_pair, OtherPCType& other,
             dxi_v.push_back(geom.InvCellSizeArray());
             plo_v.push_back(geom.ProbLoArray());

-            m_neighbor_list[lev][index].build(ptile, other_ptile,
-                                              std::forward<CheckPair>(check_pair),
-                                              off_bins_v, dxi_v, plo_v, lo_v, hi_v, ng);
+            neighbor_lists[lev][index].build(ptile, other_ptile,
+                                             std::forward<CheckPair>(check_pair),
+                                             off_bins_v, dxi_v, plo_v, lo_v, hi_v, ng);
         }
     }
 }

diff --git a/Tests/Particles/NeighborList/CMakeLists.txt b/Tests/Particles/NeighborList/CMakeLists.txt
new file mode 100644
index 00000000000..226771586b5
--- /dev/null
+++ b/Tests/Particles/NeighborList/CMakeLists.txt
@@ -0,0 +1,7 @@
+set(_sources     main.cpp)
+set(_input_files inputs)
+
+setup_test(_sources _input_files)
+
+unset(_sources)
+unset(_input_files)
diff --git a/Tests/Particles/NeighborList/main.cpp b/Tests/Particles/NeighborList/main.cpp
index 3b06653b9f9..b7860d7f2a3 100644
--- a/Tests/Particles/NeighborList/main.cpp
+++ b/Tests/Particles/NeighborList/main.cpp
@@ -65,9 +65,9 @@ void addParticles (PCType1& pc1, PCType2& pc2)
     auto& ptile1 = pc1.DefineAndReturnParticleTile(0, 0, 0);
     {
         PType1 p;
-        p.pos(0) = 12.0;
-        p.pos(1) = 12.0;
-        p.pos(2) = 12.0;
+        AMREX_D_TERM(p.pos(0) = 12.0;,
+                     p.pos(1) = 12.0;,
+                     p.pos(2) = 12.0;)
         p.id() = PType1::NextID();
         p.cpu() = ParallelDescriptor::MyProc();
         ptile1.push_back(p);
@@ -76,9 +76,9 @@ void addParticles (PCType1& pc1, PCType2& pc2)
     auto& ptile2 = pc2.DefineAndReturnParticleTile(0, 0, 0);
     {
         PType2 p;
-        p.pos(0) = 12.0;
-        p.pos(1) = 13.0;
-        p.pos(2) = 12.0;
+        AMREX_D_TERM(p.pos(0) = 12.0;,
+                     p.pos(1) = 13.0;,
+                     p.pos(2) = 12.0;)
         p.id() = PType2::NextID();
         p.cpu() = ParallelDescriptor::MyProc();
         p.idata(0) = 1;