Repository: ORNL/CabanaPD
Pull request #190: Check boundary condition particle count
Author: login: streeve
Created: 2025-02-26T22:45:40Z | Merged: 2025-03-07T22:57:17Z

If BC or body terms are created prior to ghost particles (or are otherwise out of date by the time it is applied in the solver) many issues ensue due to out of date slices

Change required for body term interface


Diff:
diff --git a/examples/dem/powder_fill.cpp b/examples/dem/powder_fill.cpp
index 236cc5aa..a273a2b8 100644
--- a/examples/dem/powder_fill.cpp
+++ b/examples/dem/powder_fill.cpp
@@ -141,7 +141,8 @@ void powderSettlingExample( const std::string filename )
     {
         f( pid, 2 ) -= 9.8 * rho( pid );
     };
-    auto gravity = CabanaPD::createBodyTerm( body_functor, true );
+    auto gravity =
+        CabanaPD::createBodyTerm( body_functor, particles->size(), true );
 
     // ====================================================
     //                   Simulation run
diff --git a/examples/thermomechanics/thermal_crack.cpp b/examples/thermomechanics/thermal_crack.cpp
index 38c978a3..6a80e573 100644
--- a/examples/thermomechanics/thermal_crack.cpp
+++ b/examples/thermomechanics/thermal_crack.cpp
@@ -147,7 +147,8 @@ void thermalCrackExample( const std::string filename )
         // Compute particle temperature
         temp( pid ) = temp_infinity + ( temp0 - temp_infinity ) * fx * fy;
     };
-    auto body_term = CabanaPD::createBodyTerm( temp_func, false );
+    auto body_term =
+        CabanaPD::createBodyTerm( temp_func, particles->size(), false );
 
     // ====================================================
     //                   Simulation run
diff --git a/examples/thermomechanics/thermal_deformation.cpp b/examples/thermomechanics/thermal_deformation.cpp
index 2a2b4f38..9e95c413 100644
--- a/examples/thermomechanics/thermal_deformation.cpp
+++ b/examples/thermomechanics/thermal_deformation.cpp
@@ -103,7 +103,8 @@ void thermalDeformationExample( const std::string filename )
     {
         temp( pid ) = temp0 + 5000.0 * ( x( pid, 1 ) - low_corner_y ) * t;
     };
-    auto body_term = CabanaPD::createBodyTerm( temp_func, false );
+    auto body_term =
+        CabanaPD::createBodyTerm( temp_func, particles->size(), false );
 
     // ====================================================
     //                   Simulation run
diff --git a/src/CabanaPD_BodyTerm.hpp b/src/CabanaPD_BodyTerm.hpp
index 7e4e158b..c8add176 100644
--- a/src/CabanaPD_BodyTerm.hpp
+++ b/src/CabanaPD_BodyTerm.hpp
@@ -16,6 +16,7 @@
 
 #include <Cabana_Core.hpp>
 
+#include <CabanaPD_Output.hpp>
 #include <CabanaPD_Timer.hpp>
 
 namespace CabanaPD
@@ -25,13 +26,16 @@ template <class UserFunctor>
 struct BodyTerm
 {
     UserFunctor _user_functor;
+    std::size_t _particle_count;
     bool _force_update;
     bool _update_frozen;
 
     Timer _timer;
 
-    BodyTerm( UserFunctor user, const bool force, const bool update_frozen )
+    BodyTerm( UserFunctor user, const std::size_t particle_count,
+              const bool force, const bool update_frozen )
         : _user_functor( user )
+        , _particle_count( particle_count )
         , _force_update( force )
         , _update_frozen( update_frozen )
     {
@@ -42,6 +46,9 @@ struct BodyTerm
     template <class ExecSpace, class ParticleType>
     void apply( ExecSpace, ParticleType& particles, const double time )
     {
+        checkParticleCount( _particle_count, particles.referenceOffset(),
+                            "BodyTerm" );
+
         _timer.start();
         std::size_t start = particles.frozenOffset();
         if ( _update_frozen )
@@ -61,10 +68,11 @@ struct BodyTerm
 };
 
 template <class UserFunctor>
-auto createBodyTerm( UserFunctor user_functor, const bool force_update,
-                     const bool update_frozen = false )
+auto createBodyTerm( UserFunctor user_functor, const std::size_t particle_count,
+                     const bool force_update, const bool update_frozen = false )
 {
-    return BodyTerm<UserFunctor>( user_functor, force_update, update_frozen );
+    return BodyTerm<UserFunctor>( user_functor, particle_count, force_update,
+                                  update_frozen );
 }
 
 } // namespace CabanaPD
diff --git a/src/CabanaPD_Boundary.hpp b/src/CabanaPD_Boundary.hpp
index 678e68e6..dc12e0a9 100644
--- a/src/CabanaPD_Boundary.hpp
+++ b/src/CabanaPD_Boundary.hpp
@@ -16,6 +16,7 @@
 
 #include <Cabana_Core.hpp>
 
+#include <CabanaPD_Output.hpp>
 #include <CabanaPD_Timer.hpp>
 
 namespace CabanaPD
@@ -125,17 +126,16 @@ struct BoundaryIndexSpace<MemorySpace, RegionBoundary<GeometryType>>
     using index_view_type = Kokkos::View<std::size_t*, MemorySpace>;
     index_view_type _view;
     index_view_type _count;
+    std::size_t particle_count;
 
     Timer _timer;
 
-    // Default for empty case.
-    BoundaryIndexSpace() {}
-
     // Construct from region (search for boundary particles).
     template <class ExecSpace, class Particles>
     BoundaryIndexSpace( ExecSpace exec_space, Particles particles,
                         std::vector<RegionBoundary<GeometryType>> planes,
                         const double initial_guess )
+        : particle_count( particles.referenceOffset() )
     {
         _timer.start();
 
@@ -162,6 +162,8 @@ struct BoundaryIndexSpace<MemorySpace, RegionBoundary<GeometryType>>
     void update( ExecSpace, Particles particles,
                  RegionBoundary<GeometryType> region )
     {
+        particle_count = particles.referenceOffset();
+
         auto count_host =
             Kokkos::create_mirror_view_and_copy( Kokkos::HostSpace{}, _count );
         auto init_count = count_host( 0 );
@@ -260,9 +262,12 @@ struct BoundaryCondition
     }
 
     template <class ExecSpace, class ParticleType>
-    void apply( ExecSpace, ParticleType&, double time )
+    void apply( ExecSpace, ParticleType& particles, double time )
     {
+        checkParticleCount( _index_space.particle_count,
+                            particles.referenceOffset(), "BoundaryCondition" );
         _timer.start();
+
         auto user = _user_functor;
         auto index_space = _index_space._view;
         Kokkos::RangePolicy<ExecSpace> policy( 0, index_space.size() );
@@ -304,6 +309,9 @@ struct BoundaryCondition<BCIndexSpace, ForceValueBCTag>
     template <class ExecSpace, class ParticleType>
     void apply( ExecSpace, ParticleType& particles, double )
     {
+        checkParticleCount( _index_space.particle_count,
+                            particles.referenceOffset(), "BoundaryCondition" );
+
         _timer.start();
         auto f = particles.sliceForce();
         auto index_space = _index_space._view;
@@ -348,8 +356,10 @@ struct BoundaryCondition<BCIndexSpace, ForceUpdateBCTag>
     template <class ExecSpace, class ParticleType>
     void apply( ExecSpace, ParticleType& particles, double )
     {
-        _timer.start();
+        checkParticleCount( _index_space.particle_count,
+                            particles.referenceOffset(), "BoundaryCondition" );
 
+        _timer.start();
         auto f = particles.sliceForce();
         auto index_space = _index_space._view;
         Kokkos::RangePolicy<ExecSpace> policy( 0, index_space.size() );
@@ -360,7 +370,6 @@ struct BoundaryCondition<BCIndexSpace, ForceUpdateBCTag>
                 for ( int d = 0; d < 3; d++ )
                     f( pid, d ) += value;
             } );
-
         _timer.stop();
     }
 
diff --git a/src/CabanaPD_Output.hpp b/src/CabanaPD_Output.hpp
index 870413f9..c16e72e2 100644
--- a/src/CabanaPD_Output.hpp
+++ b/src/CabanaPD_Output.hpp
@@ -26,6 +26,7 @@
 #include <fstream>
 #include <iostream>
 #include <stdexcept>
+#include <string>
 #include <utility>
 
 #include <mpi.h>
@@ -60,8 +61,7 @@ void log_err( t_stream& stream, t_last&& last )
     if ( print_rank() )
     {
         stream << last << std::endl;
-        throw std::runtime_error(
-            "Aborting after error from input. See error file." );
+        throw std::runtime_error( "Aborting after error." );
     }
 }
 
@@ -73,6 +73,16 @@ void log_err( t_stream& stream, t_head&& head, t_tail&&... tail )
     log_err( stream, std::forward<t_tail>( tail )... );
 }
 
+void checkParticleCount( std::size_t initial, std::size_t current,
+                         std::string name )
+{
+    if ( initial != current )
+        log_err( std::cout, "\nParticle size (", std::to_string( current ),
+                 ") does not match size when ", name, " was created (",
+                 std::to_string( initial ),
+                 ").\n Likely a slice() call is missing." );
+}
+
 } // namespace CabanaPD
 
 #endif
diff --git a/src/CabanaPD_Particles.hpp b/src/CabanaPD_Particles.hpp
index 71148753..5d179b28 100644
--- a/src/CabanaPD_Particles.hpp
+++ b/src/CabanaPD_Particles.hpp
@@ -99,7 +99,7 @@ class Particles<MemorySpace, PMB, TemperatureIndependent, BaseOutput, Dimension>
     std::size_t frozen_offset = 0;
     std::size_t local_offset = 0;
     std::size_t num_ghost = 0;
-    std::size_t size = 0;
+    std::size_t _size = 0;
 
     // x, u, f (vector matching system dimension).
     using vector_type = Cabana::MemberTypes<double[dim]>;
@@ -464,7 +464,9 @@ class Particles<MemorySpace, PMB, TemperatureIndependent, BaseOutput, Dimension>
     auto numLocal() const { return local_offset - frozen_offset; }
     auto localOffset() const { return local_offset; }
     auto numGhost() const { return num_ghost; }
-    auto referenceOffset() const { return size; }
+    // This is currently size because contact ghosts are not added yet.
+    auto referenceOffset() const { return _size; }
+    auto size() const { return _size; }
     auto numGlobal() const { return num_global; }
 
     auto sliceReferencePosition()
@@ -567,7 +569,7 @@ class Particles<MemorySpace, PMB, TemperatureIndependent, BaseOutput, Dimension>
 
         local_offset = new_local;
         num_ghost = new_ghost;
-        size = new_local + new_ghost;
+        _size = new_local + new_ghost;
 
         _plist_x.aosoa().resize( referenceOffset() );
         _aosoa_u.resize( referenceOffset() );
@@ -577,9 +579,9 @@ class Particles<MemorySpace, PMB, TemperatureIndependent, BaseOutput, Dimension>
         _aosoa_other.resize( localOffset() );
         _aosoa_nofail.resize( referenceOffset() );
 
-        size = _plist_x.size();
+        _size = _plist_x.size();
         if ( create_frozen )
-            frozen_offset = size;
+            frozen_offset = _size;
         _timer.stop();
     };
 


Reviews:
user: login: pabloseleson
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T20:37:16Z
user: login: pabloseleson
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T20:42:06Z
user: login: pabloseleson
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T20:44:07Z
user: login: pabloseleson
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T20:45:35Z
user: login: pabloseleson
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T20:47:14Z
user: login: streeve
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T21:06:13Z
user: login: streeve
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T21:06:19Z
user: login: streeve
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T21:06:32Z
user: login: streeve
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T21:06:50Z
user: login: streeve
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T21:07:18Z
user: login: pabloseleson
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T21:25:24Z
user: login: pabloseleson
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T21:28:24Z
user: login: pabloseleson
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T21:31:55Z
user: login: pabloseleson
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T21:32:47Z
user: login: streeve
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T22:01:12Z
user: login: streeve
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T22:01:33Z
user: login: streeve
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T22:01:53Z
user: login: streeve
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T22:01:58Z
user: login: pabloseleson
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T22:12:53Z
user: login: pabloseleson
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T22:14:32Z
user: login: streeve
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T22:22:45Z
user: login: streeve
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T22:22:54Z
user: login: pabloseleson
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T22:35:22Z
user: login: pabloseleson
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-07T22:44:33Z

Review comments:
diff_hunk: @@ -147,7 +147,8 @@ void thermalCrackExample( const std::string filename )
         // Compute particle temperature
         temp( pid ) = temp_infinity + ( temp0 - temp_infinity ) * fx * fy;
     };
-    auto body_term = CabanaPD::createBodyTerm( temp_func, false );
+    auto body_term = CabanaPD::createBodyTerm(
path: examples/thermomechanics/thermal_crack.cpp
user: login: pabloseleson
body: My only comment here is that "referenceOffset()" does not clearly convey the meaning of "number of particles". 
created_at: 2025-03-07T20:37:15Z
author_association: COLLABORATOR
diff_hunk: @@ -125,17 +125,16 @@ struct BoundaryIndexSpace<MemorySpace, RegionBoundary<GeometryType>>
     using index_view_type = Kokkos::View<std::size_t*, MemorySpace>;
     index_view_type _view;
     index_view_type _count;
+    std::size_t particle_count;
 
     Timer _timer;
 
-    // Default for empty case.
path: src/CabanaPD_Boundary.hpp
user: login: pabloseleson
body: Why was this removed?
created_at: 2025-03-07T20:42:06Z
author_association: COLLABORATOR
diff_hunk: @@ -125,17 +125,16 @@ struct BoundaryIndexSpace<MemorySpace, RegionBoundary<GeometryType>>
     using index_view_type = Kokkos::View<std::size_t*, MemorySpace>;
     index_view_type _view;
     index_view_type _count;
+    std::size_t particle_count;
 
     Timer _timer;
 
-    // Default for empty case.
-    BoundaryIndexSpace() {}
-
     // Construct from region (search for boundary particles).
     template <class ExecSpace, class Particles>
     BoundaryIndexSpace( ExecSpace exec_space, Particles particles,
                         std::vector<RegionBoundary<GeometryType>> planes,
                         const double initial_guess )
+        : particle_count( particles.referenceOffset() )
path: src/CabanaPD_Boundary.hpp
user: login: pabloseleson
body: How is ": particle_count( particles.referenceOffset() )" being used? I do not see it being used in "BoundaryIndexSpace"?
created_at: 2025-03-07T20:44:07Z
author_association: COLLABORATOR
diff_hunk: @@ -162,6 +161,8 @@ struct BoundaryIndexSpace<MemorySpace, RegionBoundary<GeometryType>>
     void update( ExecSpace, Particles particles,
                  RegionBoundary<GeometryType> region )
     {
+        particle_count = particles.referenceOffset();
path: src/CabanaPD_Boundary.hpp
user: login: pabloseleson
body: Where is this being used? I do not see "particle_count" being used in "void update"
created_at: 2025-03-07T20:45:34Z
author_association: COLLABORATOR
diff_hunk: @@ -260,9 +261,12 @@ struct BoundaryCondition
     }
 
     template <class ExecSpace, class ParticleType>
-    void apply( ExecSpace, ParticleType&, double time )
+    void apply( ExecSpace, [[maybe_unused]] ParticleType& particles,
+                double time )
     {
         _timer.start();
+        assert( _index_space.particle_count == particles.referenceOffset() );
path: src/CabanaPD_Boundary.hpp
user: login: pabloseleson
body: Could you please remind me how "_index_space" works here? Where is it defined?
created_at: 2025-03-07T20:47:14Z
author_association: COLLABORATOR
diff_hunk: @@ -147,7 +147,8 @@ void thermalCrackExample( const std::string filename )
         // Compute particle temperature
         temp( pid ) = temp_infinity + ( temp0 - temp_infinity ) * fx * fy;
     };
-    auto body_term = CabanaPD::createBodyTerm( temp_func, false );
+    auto body_term = CabanaPD::createBodyTerm(
path: examples/thermomechanics/thermal_crack.cpp
user: login: streeve
body: Fixed by adding a `size()` function
created_at: 2025-03-07T21:06:13Z
author_association: COLLABORATOR
diff_hunk: @@ -125,17 +125,16 @@ struct BoundaryIndexSpace<MemorySpace, RegionBoundary<GeometryType>>
     using index_view_type = Kokkos::View<std::size_t*, MemorySpace>;
     index_view_type _view;
     index_view_type _count;
+    std::size_t particle_count;
 
     Timer _timer;
 
-    // Default for empty case.
path: src/CabanaPD_Boundary.hpp
user: login: streeve
body: It was unused
created_at: 2025-03-07T21:06:19Z
author_association: COLLABORATOR
diff_hunk: @@ -125,17 +125,16 @@ struct BoundaryIndexSpace<MemorySpace, RegionBoundary<GeometryType>>
     using index_view_type = Kokkos::View<std::size_t*, MemorySpace>;
     index_view_type _view;
     index_view_type _count;
+    std::size_t particle_count;
 
     Timer _timer;
 
-    // Default for empty case.
-    BoundaryIndexSpace() {}
-
     // Construct from region (search for boundary particles).
     template <class ExecSpace, class Particles>
     BoundaryIndexSpace( ExecSpace exec_space, Particles particles,
                         std::vector<RegionBoundary<GeometryType>> planes,
                         const double initial_guess )
+        : particle_count( particles.referenceOffset() )
path: src/CabanaPD_Boundary.hpp
user: login: streeve
body: It is used in `apply` below
created_at: 2025-03-07T21:06:32Z
author_association: COLLABORATOR
diff_hunk: @@ -162,6 +161,8 @@ struct BoundaryIndexSpace<MemorySpace, RegionBoundary<GeometryType>>
     void update( ExecSpace, Particles particles,
                  RegionBoundary<GeometryType> region )
     {
+        particle_count = particles.referenceOffset();
path: src/CabanaPD_Boundary.hpp
user: login: streeve
body: It is used in `apply` below
created_at: 2025-03-07T21:06:50Z
author_association: COLLABORATOR
diff_hunk: @@ -260,9 +261,12 @@ struct BoundaryCondition
     }
 
     template <class ExecSpace, class ParticleType>
-    void apply( ExecSpace, ParticleType&, double time )
+    void apply( ExecSpace, [[maybe_unused]] ParticleType& particles,
+                double time )
     {
         _timer.start();
+        assert( _index_space.particle_count == particles.referenceOffset() );
path: src/CabanaPD_Boundary.hpp
user: login: streeve
body: It contains the list of boundary particles, and now the number of total particles when constructed 
created_at: 2025-03-07T21:07:18Z
author_association: COLLABORATOR
diff_hunk: @@ -12,6 +12,8 @@
 #ifndef BODYTERM_H
 #define BODYTERM_H
 
+#include <stdexcept>
path: src/CabanaPD_BodyTerm.hpp
user: login: pabloseleson
body: Why do we need this? 
created_at: 2025-03-07T21:25:24Z
author_association: COLLABORATOR
diff_hunk: @@ -42,6 +47,11 @@ struct BodyTerm
     template <class ExecSpace, class ParticleType>
     void apply( ExecSpace, ParticleType& particles, const double time )
     {
+        if ( _particle_count != particles.referenceOffset() )
+            throw std::runtime_error(
+                "Particle size must match size when BodyTerm was created "
path: src/CabanaPD_BodyTerm.hpp
user: login: pabloseleson
body: Is it clear what does "size" mean in "must match size"?
created_at: 2025-03-07T21:28:24Z
author_association: COLLABORATOR
diff_hunk: @@ -99,7 +99,7 @@ class Particles<MemorySpace, PMB, TemperatureIndependent, BaseOutput, Dimension>
     std::size_t frozen_offset = 0;
     std::size_t local_offset = 0;
     std::size_t num_ghost = 0;
-    std::size_t size = 0;
+    std::size_t _size = 0;
path: src/CabanaPD_Particles.hpp
user: login: pabloseleson
body: What is this change needed?
created_at: 2025-03-07T21:31:55Z
author_association: COLLABORATOR
diff_hunk: @@ -464,7 +464,9 @@ class Particles<MemorySpace, PMB, TemperatureIndependent, BaseOutput, Dimension>
     auto numLocal() const { return local_offset - frozen_offset; }
     auto localOffset() const { return local_offset; }
     auto numGhost() const { return num_ghost; }
-    auto referenceOffset() const { return size; }
+    // This is currently size because contact ghosts are not added yet.
path: src/CabanaPD_Particles.hpp
user: login: pabloseleson
body: As above, is it clear what does "size" refer to?
created_at: 2025-03-07T21:32:47Z
author_association: COLLABORATOR
diff_hunk: @@ -12,6 +12,8 @@
 #ifndef BODYTERM_H
 #define BODYTERM_H
 
+#include <stdexcept>
path: src/CabanaPD_BodyTerm.hpp
user: login: streeve
body: No longer needed. But using a new std function (was `runtime_error`) requires the header (defined in `stdexcept` - those are always clearly listed in the std library documentation 
created_at: 2025-03-07T22:01:12Z
author_association: COLLABORATOR
diff_hunk: @@ -42,6 +47,11 @@ struct BodyTerm
     template <class ExecSpace, class ParticleType>
     void apply( ExecSpace, ParticleType& particles, const double time )
     {
+        if ( _particle_count != particles.referenceOffset() )
+            throw std::runtime_error(
+                "Particle size must match size when BodyTerm was created "
path: src/CabanaPD_BodyTerm.hpp
user: login: streeve
body: Updated
created_at: 2025-03-07T22:01:33Z
author_association: COLLABORATOR
diff_hunk: @@ -99,7 +99,7 @@ class Particles<MemorySpace, PMB, TemperatureIndependent, BaseOutput, Dimension>
     std::size_t frozen_offset = 0;
     std::size_t local_offset = 0;
     std::size_t num_ghost = 0;
-    std::size_t size = 0;
+    std::size_t _size = 0;
path: src/CabanaPD_Particles.hpp
user: login: streeve
body: I wanted the user function to be called `size` and you cannot reuse names 
created_at: 2025-03-07T22:01:53Z
author_association: COLLABORATOR
diff_hunk: @@ -464,7 +464,9 @@ class Particles<MemorySpace, PMB, TemperatureIndependent, BaseOutput, Dimension>
     auto numLocal() const { return local_offset - frozen_offset; }
     auto localOffset() const { return local_offset; }
     auto numGhost() const { return num_ghost; }
-    auto referenceOffset() const { return size; }
+    // This is currently size because contact ghosts are not added yet.
path: src/CabanaPD_Particles.hpp
user: login: streeve
body: Duplicate 
created_at: 2025-03-07T22:01:58Z
author_association: COLLABORATOR
diff_hunk: @@ -73,6 +73,16 @@ void log_err( t_stream& stream, t_head&& head, t_tail&&... tail )
     log_err( stream, std::forward<t_tail>( tail )... );
 }
 
+void checkParticleCount( std::size_t initial, std::size_t current,
+                         std::string name )
+{
+    if ( initial != current )
+        log_err( std::cout, "\nParticle size (", std::to_string( current ),
+                 ") does not match\n size when ", name, " was created (",
path: src/CabanaPD_Output.hpp
user: login: pabloseleson
body: Do we need "\n"  in "match\n"

created_at: 2025-03-07T22:12:53Z
author_association: COLLABORATOR
diff_hunk: @@ -260,9 +262,12 @@ struct BoundaryCondition
     }
 
     template <class ExecSpace, class ParticleType>
-    void apply( ExecSpace, ParticleType&, double time )
+    void apply( ExecSpace, ParticleType& particles, double time )
     {
+        checkParticleCount( _index_space.particle_count,
+                            particles.referenceOffset(), "BoundaryCondition" );
path: src/CabanaPD_Boundary.hpp
user: login: pabloseleson
body: I thought we only needed this check for "BodyTerm"? Is "BoundaryCondition" correct here? (Same below)
created_at: 2025-03-07T22:14:32Z
author_association: COLLABORATOR
diff_hunk: @@ -73,6 +73,16 @@ void log_err( t_stream& stream, t_head&& head, t_tail&&... tail )
     log_err( stream, std::forward<t_tail>( tail )... );
 }
 
+void checkParticleCount( std::size_t initial, std::size_t current,
+                         std::string name )
+{
+    if ( initial != current )
+        log_err( std::cout, "\nParticle size (", std::to_string( current ),
+                 ") does not match\n size when ", name, " was created (",
path: src/CabanaPD_Output.hpp
user: login: streeve
body: Probably not 
created_at: 2025-03-07T22:22:45Z
author_association: COLLABORATOR
diff_hunk: @@ -260,9 +262,12 @@ struct BoundaryCondition
     }
 
     template <class ExecSpace, class ParticleType>
-    void apply( ExecSpace, ParticleType&, double time )
+    void apply( ExecSpace, ParticleType& particles, double time )
     {
+        checkParticleCount( _index_space.particle_count,
+                            particles.referenceOffset(), "BoundaryCondition" );
path: src/CabanaPD_Boundary.hpp
user: login: streeve
body: This has always been for both
created_at: 2025-03-07T22:22:54Z
author_association: COLLABORATOR
diff_hunk: @@ -73,6 +73,16 @@ void log_err( t_stream& stream, t_head&& head, t_tail&&... tail )
     log_err( stream, std::forward<t_tail>( tail )... );
 }
 
+void checkParticleCount( std::size_t initial, std::size_t current,
+                         std::string name )
+{
+    if ( initial != current )
+        log_err( std::cout, "\nParticle size (", std::to_string( current ),
+                 ") does not match\n size when ", name, " was created (",
path: src/CabanaPD_Output.hpp
user: login: pabloseleson
body: Changed.
created_at: 2025-03-07T22:35:22Z
author_association: COLLABORATOR

PR comments:
user: login: pabloseleson
created_at: 2025-03-07T20:55:50Z
body: I noticed we have three "void apply" functions in CabanaPD_Boundary.hpp:

(1) Lines 264-279: this is time-dependent with I believe a user defined functions. 

(2) Lines 309-325: This is for a constant force:

f( pid, d ) = value;

(3) Lines 355-372: This is for an incremental force:

f( pid, d ) += value;

Could you please remind me why we need both (2) and (3)?




author_association: COLLABORATOR
user: login: pabloseleson
created_at: 2025-03-07T21:00:17Z
body: BTW: you may remove the white space in Line 370 before "_timer.stop();" for consistency.
author_association: COLLABORATOR
user: login: streeve
created_at: 2025-03-07T21:08:14Z
body: > Could you please remind me why we need both (2) and (3)?

See, for example the KW (not really in the scope of this PR) 

> BTW: you may remove the white space in Line 370 before "_timer.stop();" for consistency.

Done
author_association: COLLABORATOR