SpatialOps
ParticleOperatorsImplementation.h
1 /*
2  * Copyright (c) 2014-2017 The University of Utah
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to
6  * deal in the Software without restriction, including without limitation the
7  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8  * sell copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20  * IN THE SOFTWARE.
21  */
22 
23 #ifndef ParticleOperatorsImplementation_h
24 #define ParticleOperatorsImplementation_h
25 
26 #include <spatialops/particles/ParticleOperators.h>
27 #ifdef ENABLE_CUDA
28 #include <spatialops/particles/ParticleOperatorsCuda.h>
29 #endif // ENABLE_CUDA
30 
31 
32 namespace SpatialOps{
33  namespace Particle{
34 
35 
36  // =================================================================
37  //
38  // Implementation
39  //
40  // =================================================================
41 
42 
43  template< typename CellField >
45  ParticlesPerCell( const double dx, const double xlo,
46  const double dy, const double ylo,
47  const double dz, const double zlo,
48  const InterpOptions method)
49  : dx_ ( dx<=0 ? 1.0 : dx ),
50  dy_ ( dy<=0 ? 1.0 : dy ),
51  dz_ ( dz<=0 ? 1.0 : dz ),
52  xlo_( dx<=0 ? 0.0 : xlo ),
53  ylo_( dy<=0 ? 0.0 : ylo ),
54  zlo_( dz<=0 ? 0.0 : zlo ),
55  method_( method )
56  {
57  px_ = NULL;
58  py_ = NULL;
59  pz_ = NULL;
60  psize_ = NULL;
61  }
62 
63  //------------------------------------------------------------------
64 
65  template< typename CellField >
66  void
69  const ParticleField *pycoord,
70  const ParticleField *pzcoord,
71  const ParticleField *psize )
72  {
73  px_ = pxcoord;
74  py_ = pycoord;
75  pz_ = pzcoord;
76  psize_ = psize ;
77  }
78 
79  //------------------------------------------------------------------
80 
81  template< typename CellField >
82  void
84  apply_to_field( DestFieldType& dest ) const
85  {
86  if (IS_GPU_INDEX(dest.active_device_index())) {
87 # ifdef ENABLE_CUDA
88  _ppc_apply_to_field<CellField, ParticleField>(px_, py_, pz_, psize_,
89  dest,
90  dx_, dy_, dz_,
91  xlo_, ylo_, zlo_,
92  method_);
93  return;
94 # endif
95  } // IS_GPU_INDEX if
96  else {
97 
98  switch( method_ ){
99 
100  case NO_INTERPOLATE:{
101 
102  const IntVec& ngm = dest.get_ghost_data().get_minus();
103 
104  const double xloface = xlo_ - dx_*ngm[0] - dx_/2;
105  const double yloface = ylo_ - dy_*ngm[1] - dy_/2;
106  const double zloface = zlo_ - dz_*ngm[2] - dz_/2;
107 
108  dest <<= 0.0; // set to zero and accumulate contributions from each particle below.
109 
110  ParticleField::const_iterator ipx = px_ ? px_->begin() : psize_->begin();
111  ParticleField::const_iterator ipy = py_ ? py_->begin() : psize_->begin();
112  ParticleField::const_iterator ipz = pz_ ? pz_->begin() : psize_->begin();
113  ParticleField::const_iterator ipsize = psize_->begin();
114  const ParticleField::const_iterator ipsizeend = psize_->end();
115  for( ; ipsize != ipsizeend; ++ipx, ++ipy, ++ipz, ++ipsize ){
116  const int i = ( *ipx - xloface ) / dx_;
117  const int j = ( *ipy - yloface ) / dy_;
118  const int k = ( *ipz - zloface ) / dz_;
119  dest(i,j,k) += 1;
120  } // particle loop
121 
122  break;
123  }
124 
125  case INTERPOLATE:{
126  assert( psize_ != NULL );
127 
128  const IntVec& ngm = dest.get_ghost_data().get_minus();
129  const IntVec& nmax = dest.window_with_ghost().extent();
130 
131  const double xloface = xlo_ - dx_*ngm[0] - dx_/2;
132  const double yloface = ylo_ - dy_*ngm[1] - dy_/2;
133  const double zloface = zlo_ - dz_*ngm[2] - dz_/2;
134 
135  dest <<= 0.0; // set to zero and accumulate contributions from each particle below.
136 
137  // Note: here the outer loop is over particles and the inner loop is over
138  // cells to sum in the particle contributions. This should be optimal for
139  // situations where there are a relatively small number of particles. In
140  // cases where many particles per cell present, it may be more effective
141  // to invert the loop structure.
142  ParticleField::const_iterator ipx = px_ ? px_->begin() : psize_->begin();
143  ParticleField::const_iterator ipy = py_ ? py_->begin() : psize_->begin();
144  ParticleField::const_iterator ipz = pz_ ? pz_->begin() : psize_->begin();
145  ParticleField::const_iterator ipsize = psize_->begin();
146  const ParticleField::const_iterator ipsizeend = psize_->end();
147  for( ; ipsize != ipsizeend; ++ipx, ++ipy, ++ipz, ++ipsize ){
148 
149  const double rp = *ipsize * 0.5;
150 
151  // Identify the location of the particle boundary (assuming that it is a cube)
152  const double pxlo = px_ ? *ipx - rp : 0;
153  const double pylo = py_ ? *ipy - rp : 0;
154  const double pzlo = pz_ ? *ipz - rp : 0;
155 
156  const double pxhi = px_ ? pxlo + *ipsize : 0;
157  const double pyhi = py_ ? pylo + *ipsize : 0;
158  const double pzhi = pz_ ? pzlo + *ipsize : 0;
159 
160  const int ixlo = ( pxlo - xloface ) / dx_;
161  const int iylo = ( pylo - yloface ) / dy_;
162  const int izlo = ( pzlo - zloface ) / dz_;
163 
164  // hi indices are 1 past the end
165  const int ixhi = px_ ? std::min( nmax[0], int(( pxhi - xloface ) / dx_) + 1 ) : ixlo+1;
166  const int iyhi = py_ ? std::min( nmax[1], int(( pyhi - yloface ) / dy_) + 1 ) : iylo+1;
167  const int izhi = pz_ ? std::min( nmax[2], int(( pzhi - zloface ) / dz_) + 1 ) : izlo+1;
168 
169  // Distribute particle through the volume(s) it touches. Here we are
170  // doing a highly approximate job at approximating how much fractional
171  // particle volume is in each cell.
172  const double pvol = (px_ ? 2*rp : 1)
173  * (py_ ? 2*rp : 1)
174  * (pz_ ? 2*rp : 1);
175  for( int k=izlo; k<izhi; ++k ){
176  const double zcm = zloface + k*dz_;
177  const double zcp = zcm + dz_;
178  // determine the z bounding box for the particle in this cell
179  const double zcont = pz_ ? std::min(zcp,pzhi) - std::max(zcm,pzlo) : 1;
180  for( int j=iylo; j<iyhi; ++j ){
181  const double ycm = yloface + j*dy_;
182  const double ycp = ycm + dy_;
183  // determine the y bounding box for the particle in this cell
184  const double ycont = py_ ? std::min(ycp,pyhi) - std::max(ycm,pylo) : 1;
185  for( int i=ixlo; i<ixhi; ++i ){
186  const double xcm = xloface + i*dx_;
187  const double xcp = xcm + dx_;
188  const double xcont = px_ ? std::min(xcp,pxhi) - std::max(xcm,pxlo) : 1;
189  // contribution is the fraction of the particle volume in this cell.
190  const double contribution = xcont*ycont*zcont / pvol;
191  dest(i,j,k) += contribution;
192  }
193  }
194  }
195  } // particle loop
196 
197  break;
198  } // case INTERPOLATE
199 
200  } // switch
201  } // else IS_GPU
202 
203  }
204 
205  //==================================================================
206 
207  template< typename CellField >
209  ParticleToCell( const double dx, const double xlo,
210  const double dy, const double ylo,
211  const double dz, const double zlo,
212  const InterpOptions method )
213  : dx_ ( dx<=0 ? 1.0 : dx ),
214  dy_ ( dy<=0 ? 1.0 : dy ),
215  dz_ ( dz<=0 ? 1.0 : dz ),
216  xlo_( dx<=0 ? 0.0 : xlo ),
217  ylo_( dy<=0 ? 0.0 : ylo ),
218  zlo_( dz<=0 ? 0.0 : zlo ),
219  method_( method )
220  {
221  px_ = NULL;
222  py_ = NULL;
223  pz_ = NULL;
224  psize_ = NULL;
225  }
226 
227  //------------------------------------------------------------------
228 
229  template< typename CellField >
230  void
233  const ParticleField *pycoord,
234  const ParticleField *pzcoord,
235  const ParticleField *psize )
236  {
237  px_ = pxcoord;
238  py_ = pycoord;
239  pz_ = pzcoord;
240  psize_ = psize ;
241  }
242 
243  //------------------------------------------------------------------
244 
245  template< typename CellField >
246  void
249  DestFieldType& dest ) const
250  {
251  if (IS_GPU_INDEX(dest.active_device_index())) {
252 # ifdef ENABLE_CUDA
253  _p2c_apply_to_field<CellField, ParticleField>(px_, py_, pz_, psize_,
254  src, dest,
255  dx_, dy_, dz_,
256  xlo_, ylo_, zlo_,
257  method_);
258  return;
259 # endif
260  } // IS_GPU_INDEX if
261  else {
262 
263  switch( method_ ){
264 
265  case NO_INTERPOLATE:{
266 
267  const IntVec& ngm = dest.get_ghost_data().get_minus();
268 
269  const double xloface = xlo_ - dx_*ngm[0] - dx_/2;
270  const double yloface = ylo_ - dy_*ngm[1] - dy_/2;
271  const double zloface = zlo_ - dz_*ngm[2] - dz_/2;
272 
273  dest <<= 0.0; // set to zero and accumulate contributions from each particle below.
274 
275  ParticleField::const_iterator ipx = px_ ? px_->begin() : src.begin();
276  ParticleField::const_iterator ipy = py_ ? py_->begin() : src.begin();
277  ParticleField::const_iterator ipz = pz_ ? pz_->begin() : src.begin();
279  const ParticleField::const_iterator ise = src.end();
280  for( ; isrc != ise; ++ipx, ++ipy, ++ipz, ++isrc ){
281  const int i = ( *ipx - xloface ) / dx_;
282  const int j = ( *ipy - yloface ) / dy_;
283  const int k = ( *ipz - zloface ) / dz_;
284  dest(i,j,k) += *isrc;
285  } // particle loop
286 
287  break;
288  }
289 
290  case INTERPOLATE:{
291  assert( psize_ != NULL );
292 
293  const IntVec& ngm = dest.get_ghost_data().get_minus();
294  const IntVec& nmax = dest.window_with_ghost().extent();
295 
296  const double xloface = xlo_ - dx_*ngm[0] - dx_/2;
297  const double yloface = ylo_ - dy_*ngm[1] - dy_/2;
298  const double zloface = zlo_ - dz_*ngm[2] - dz_/2;
299 
300  dest <<= 0.0; // set to zero and accumulate contributions from each particle below.
301 
302  // Note: here the outer loop is over particles and the inner loop is over
303  // cells to sum in the particle contributions. This should be optimal for
304  // situations where there are a relatively small number of particles. In
305  // cases where many particles per cell present, it may be more effective
306  // to invert the loop structure.
307  ParticleField::const_iterator ipx = px_ ? px_->begin() : src.begin();
308  ParticleField::const_iterator ipy = py_ ? py_->begin() : src.begin();
309  ParticleField::const_iterator ipz = pz_ ? pz_->begin() : src.begin();
310  ParticleField::const_iterator ipsize = psize_->begin();
312  const ParticleField::const_iterator ise = src.end();
313  for( ; isrc != ise; ++ipx, ++ipy, ++ipz, ++ipsize, ++isrc ){
314 
315  const double rp = *ipsize * 0.5;
316 
317  // Identify the location of the particle boundary (assuming that it is a cube)
318  const double pxlo = px_ ? *ipx - rp : 0;
319  const double pylo = py_ ? *ipy - rp : 0;
320  const double pzlo = pz_ ? *ipz - rp : 0;
321 
322  const double pxhi = px_ ? pxlo + *ipsize : 0;
323  const double pyhi = py_ ? pylo + *ipsize : 0;
324  const double pzhi = pz_ ? pzlo + *ipsize : 0;
325 
326  const int ixlo = ( pxlo - xloface ) / dx_;
327  const int iylo = ( pylo - yloface ) / dy_;
328  const int izlo = ( pzlo - zloface ) / dz_;
329 
330  // hi indices are 1 past the end
331  const int ixhi = px_ ? std::min( nmax[0], int(( pxhi - xloface ) / dx_) + 1 ) : ixlo+1;
332  const int iyhi = py_ ? std::min( nmax[1], int(( pyhi - yloface ) / dy_) + 1 ) : iylo+1;
333  const int izhi = pz_ ? std::min( nmax[2], int(( pzhi - zloface ) / dz_) + 1 ) : izlo+1;
334 
335  // Distribute particle through the volume(s) it touches. Here we are
336  // doing a highly approximate job at approximating how much fractional
337  // particle volume is in each cell.
338  const double pvol = (px_ ? 2*rp : 1)
339  * (py_ ? 2*rp : 1)
340  * (pz_ ? 2*rp : 1);
341 # ifndef NDEBUG
342  double sumterm=0.0;
343 # endif
344  for( int k=izlo; k<izhi; ++k ){
345  const double zcm = zloface + k*dz_;
346  const double zcp = zcm + dz_;
347  // determine the z bounding box for the particle in this cell
348  const double zcont = pz_ ? std::min(zcp,pzhi) - std::max(zcm,pzlo) : 1;
349  for( int j=iylo; j<iyhi; ++j ){
350  const double ycm = yloface + j*dy_;
351  const double ycp = ycm + dy_;
352  // determine the y bounding box for the particle in this cell
353  const double ycont = py_ ? std::min(ycp,pyhi) - std::max(ycm,pylo) : 1;
354  for( int i=ixlo; i<ixhi; ++i ){
355  const double xcm = xloface + i*dx_;
356  const double xcp = xcm + dx_;
357  const double xcont = px_ ? std::min(xcp,pxhi) - std::max(xcm,pxlo) : 1;
358  // contribution is the fraction of the particle volume in this cell.
359  const double contribution = xcont*ycont*zcont / pvol;
360  dest(i,j,k) += *isrc * contribution;
361 # ifndef NDEBUG
362  sumterm += contribution;
363 # endif
364  }
365  }
366  }
367 # ifndef NDEBUG
368  if( std::abs(1.0-sumterm) >= 1e-10 ) std::cout << "sum: " << sumterm << std::endl;
369  assert( std::abs(1.0-sumterm) < 1e-10 );
370 # endif
371  } // particle loop
372 
373  break;
374  } // case INTERPOLATE
375 
376  } // switch( method_ )
377  } // else IS_GPU
378  }
379 
380  //==================================================================
381 
382  template< typename CellField >
384  CellToParticle( const double dx, const double xlo,
385  const double dy, const double ylo,
386  const double dz, const double zlo,
387  const InterpOptions method )
388  : dx_ ( dx<=0 ? 1.0 : dx ),
389  dy_ ( dy<=0 ? 1.0 : dy ),
390  dz_ ( dz<=0 ? 1.0 : dz ),
391  xlo_( dx<=0 ? 0.0 : xlo ),
392  ylo_( dy<=0 ? 0.0 : ylo ),
393  zlo_( dz<=0 ? 0.0 : zlo ),
394  method_( method )
395  {
396  px_ = NULL;
397  py_ = NULL;
398  pz_ = NULL;
399  psize_ = NULL;
400  }
401 
402  //------------------------------------------------------------------
403 
404  template<typename CellField>
405  void
408  const ParticleField *pycoord,
409  const ParticleField *pzcoord,
410  const ParticleField *psize )
411  {
412  px_ = pxcoord;
413  py_ = pycoord;
414  pz_ = pzcoord;
415  psize_ = psize ;
416  }
417 
418  //------------------------------------------------------------------
419 
420  template<typename CellField>
421  void
423  apply_to_field( const SrcFieldType& src,
424  DestFieldType& dest ) const
425  {
426  if (IS_GPU_INDEX(dest.active_device_index())) {
427 # ifdef ENABLE_CUDA
428  _c2p_apply_to_field<CellField, ParticleField>(px_, py_, pz_, psize_,
429  src, dest,
430  dx_, dy_, dz_,
431  xlo_, ylo_, zlo_,
432  method_);
433  return;
434 # endif
435  } // IS_GPU_INDEX if
436  else {
437 
438  switch( method_ ){
439  case NO_INTERPOLATE: {
440  const IntVec& ngm = src.get_ghost_data().get_minus();
441 
442  const double xloface = xlo_ - dx_*ngm[0] - dx_/2;
443  const double yloface = ylo_ - dy_*ngm[1] - dy_/2;
444  const double zloface = zlo_ - dz_*ngm[2] - dz_/2;
445 
446  ParticleField::const_iterator ipx = px_ ? px_->begin() : dest.begin();
447  ParticleField::const_iterator ipy = py_ ? py_->begin() : dest.begin();
448  ParticleField::const_iterator ipz = pz_ ? pz_->begin() : dest.begin();
449  ParticleField::iterator idst = dest.begin();
450  const ParticleField::iterator idste = dest.end();
451  for( ; idst != idste; ++ipx, ++ipy, ++ipz, ++idst ){
452 
453  const int i = ( *ipx - xloface ) / dx_;
454  const int j = ( *ipy - yloface ) / dy_;
455  const int k = ( *ipz - zloface ) / dz_;
456 
457  *idst = src(i,j,k);
458 
459  } // particle loop
460 
461  break;
462  } // case NO_INTERPOLATE
463 
464  case INTERPOLATE:{
465  const IntVec& ngm = src.get_ghost_data().get_minus();
466  const IntVec& nmax = src.window_with_ghost().extent();
467 
468  // Get the location of the (-) face associated with this cell field. Here we
469  // shift the xlo_ value (the value associated with this field type's origin)
470  // by its ghost offset and by the distance from the cell center to the face.
471  const double xloface = xlo_ - dx_*ngm[0] - dx_/2;
472  const double yloface = ylo_ - dy_*ngm[1] - dy_/2;
473  const double zloface = zlo_ - dz_*ngm[2] - dz_/2;
474 
475  ParticleField::const_iterator ipx = px_ ? px_->begin() : dest.begin();
476  ParticleField::const_iterator ipy = py_ ? py_->begin() : dest.begin();
477  ParticleField::const_iterator ipz = pz_ ? pz_->begin() : dest.begin();
478  ParticleField::const_iterator ipsize = psize_->begin();
479  ParticleField::iterator idst = dest.begin();
480  const ParticleField::iterator idste = dest.end();
481  for( ; idst != idste; ++ipx, ++ipy, ++ipz, ++ipsize, ++idst ){
482 
483  *idst = 0.0;
484 
485  const double rp = *ipsize * 0.5;
486 
487  // Identify the location of the particle boundary
488  const double pxlo = px_ ? *ipx - rp : 0;
489  const double pylo = py_ ? *ipy - rp : 0;
490  const double pzlo = pz_ ? *ipz - rp : 0;
491 
492  const double pxhi = px_ ? pxlo + *ipsize : 0;
493  const double pyhi = py_ ? pylo + *ipsize : 0;
494  const double pzhi = pz_ ? pzlo + *ipsize : 0;
495 
496  const int ixlo = ( pxlo - xloface ) / dx_;
497  const int iylo = ( pylo - yloface ) / dy_;
498  const int izlo = ( pzlo - zloface ) / dz_;
499 
500  // hi indices are 1 past the end
501  const int ixhi = px_ ? std::min( nmax[0], int(( pxhi - xloface ) / dx_) + 1 ) : ixlo+1;
502  const int iyhi = py_ ? std::min( nmax[1], int(( pyhi - yloface ) / dy_) + 1 ) : iylo+1;
503  const int izhi = pz_ ? std::min( nmax[2], int(( pzhi - zloface ) / dz_) + 1 ) : izlo+1;
504 
505  // Distribute particle through the volume(s) it touches. Here we are
506  // doing a highly approximate job at approximating how much fractional
507  // particle volume is in each cell.
508  const double pvol = (px_ ? 2*rp : 1)
509  * (py_ ? 2*rp : 1)
510  * (pz_ ? 2*rp : 1);
511 
512 # ifndef NDEBUG
513  double sumterm=0.0;
514 # endif
515  for( int k=izlo; k<izhi; ++k ){
516  const double zcm = zloface + k*dz_; // (-) face of the cell
517  const double zcp = zcm + dz_; // (+) face of the cell
518  // determine the z bounding box for the particle in this cell
519  const double zcont = pz_ ? std::min(zcp,pzhi) - std::max(zcm,pzlo) : 1;
520  for( int j=iylo; j<iyhi; ++j ){
521  const double ycm = yloface + j*dy_;
522  const double ycp = ycm + dy_;
523  // determine the y bounding box for the particle in this cell
524  const double ycont = py_ ? std::min(ycp,pyhi) - std::max(ycm,pylo) : 1;
525  for( int i=ixlo; i<ixhi; ++i ){
526  const double xcm = xloface + i*dx_;
527  const double xcp = xcm + dx_;
528  const double xcont = px_ ? std::min(xcp,pxhi) - std::max(xcm,pxlo) : 1;
529  // contribution is the fraction of the particle volume in this cell.
530  const double contribution = xcont*ycont*zcont / pvol;
531  *idst += src(i,j,k) * contribution;
532 # ifndef NDEBUG
533  assert( contribution > 0 );
534  sumterm += contribution;
535 # endif
536  }
537  }
538  }
539 # ifndef NDEBUG
540  if( std::abs(1.0-sumterm) >= 1e-10 ) std::cout << "C2P sum: " << sumterm << std::endl;
541  assert( std::abs(1.0-sumterm) < 1e-10 );
542 # endif
543  } // particle loop
544  } // case INTERPOLATE
545  break;
546 
547  } // switch( method_ )
548  } // else - GPU vs CPU
549  }
550  //==================================================================
551  // Explicit template instantiation
552  } // namespace Particle
553 } // namespace SpatialOps
554 
555 #endif // ParticleOperatorsImplementation_h
void apply_to_field(const SrcFieldType &src, DestFieldType &dest) const
CellToParticle(const double dx, const double xlo, const double dy=-1, const double ylo=0, const double dz=-1, const double zlo=0, const InterpOptions method=INTERPOLATE)
Construct a CellToParticle operator.
void apply_to_field(const SrcFieldType &src, DestFieldType &dest) const
void set_coordinate_information(const ParticleField *const pxcoord, const ParticleField *const pycoord, const ParticleField *const pzcoord, const ParticleField *const psize)
short int active_device_index() const
return the index of the current active device
Definition: SpatialField.h:331
const_iterator end() const
return a constant iterator to end for CPU with valid ghost cells
Definition: SpatialField.h:492
const_iterator begin() const
return a constant iterator for CPU with valid ghost cells
Definition: SpatialField.h:478
void set_coordinate_information(const ParticleField *pxcoord, const ParticleField *pycoord, const ParticleField *pzcoord, const ParticleField *psize)
provides iterator support for SpatialField. Only works for CPU.
Definition: MemoryWindow.h:334
provides iterator support for SpatialField. Only works for CPU.
Definition: MemoryWindow.h:326
void set_coordinate_information(const ParticleField *const pxcoord, const ParticleField *const pycoord, const ParticleField *const pzcoord, const ParticleField *const psize)
ParticleToCell(const double dx, const double xlo, const double dy=-1, const double ylo=0, const double dz=-1, const double zlo=0, const InterpOptions method=INTERPOLATE)
construct a ParticleToCell operator
Abstracts a field.
Definition: SpatialField.h:132
ParticlesPerCell(const double dx, const double xlo, const double dy=-1, const double ylo=0, const double dz=-1, const double zlo=0, const InterpOptions method=INTERPOLATE)
construct a ParticlesPerCell operator