OpenVDB  7.0.0
VolumeAdvect.h
Go to the documentation of this file.
1 // Copyright Contributors to the OpenVDB Project
2 // SPDX-License-Identifier: MPL-2.0
3 //
5 //
12 
13 #ifndef OPENVDB_TOOLS_VOLUME_ADVECT_HAS_BEEN_INCLUDED
14 #define OPENVDB_TOOLS_VOLUME_ADVECT_HAS_BEEN_INCLUDED
15 
16 #include <tbb/parallel_for.h>
17 #include <openvdb/Types.h>
18 #include <openvdb/math/Math.h>
20 #include "Interpolation.h"// for Sampler
21 #include "VelocityFields.h" // for VelocityIntegrator
22 #include "Morphology.h"//for dilateActiveValues and dilateVoxels
23 #include "Prune.h"// for prune
24 #include "Statistics.h" // for extrema
25 #include <functional>
26 
27 
28 namespace openvdb {
30 namespace OPENVDB_VERSION_NAME {
31 namespace tools {
32 
33 namespace Scheme {
39 }
40 
68 
69 template<typename VelocityGridT = Vec3fGrid,
70  bool StaggeredVelocity = false,
71  typename InterrupterType = util::NullInterrupter>
73 {
74 public:
75 
83  VolumeAdvection(const VelocityGridT& velGrid, InterrupterType* interrupter = nullptr)
84  : mVelGrid(velGrid)
85  , mInterrupter(interrupter)
86  , mIntegrator( Scheme::SEMI )
87  , mLimiter( Scheme::CLAMP )
88  , mGrainSize( 128 )
89  , mSubSteps( 1 )
90  {
91  math::Extrema e = extrema(velGrid.cbeginValueAll(), /*threading*/true);
92  e.add(velGrid.background().length());
93  mMaxVelocity = e.max();
94  }
95 
96  virtual ~VolumeAdvection()
97  {
98  }
99 
105  int spatialOrder() const { return (mIntegrator == Scheme::MAC ||
106  mIntegrator == Scheme::BFECC) ? 2 : 1; }
107 
113  int temporalOrder() const {
114  switch (mIntegrator) {
115  case Scheme::SEMI: return 1;
116  case Scheme::MID: return 2;
117  case Scheme::RK3: return 3;
118  case Scheme::RK4: return 4;
119  case Scheme::BFECC:return 2;
120  case Scheme::MAC: return 2;
121  }
122  return 0;//should never reach this point
123  }
124 
126  void setIntegrator(Scheme::SemiLagrangian integrator) { mIntegrator = integrator; }
127 
129  Scheme::SemiLagrangian getIntegrator() const { return mIntegrator; }
130 
132  void setLimiter(Scheme::Limiter limiter) { mLimiter = limiter; }
133 
135  Scheme::Limiter getLimiter() const { return mLimiter; }
136 
139  bool isLimiterOn() const { return this->spatialOrder()>1 &&
140  mLimiter != Scheme::NO_LIMITER; }
141 
144  size_t getGrainSize() const { return mGrainSize; }
145 
150  void setGrainSize(size_t grainsize) { mGrainSize = grainsize; }
151 
154  int getSubSteps() const { return mSubSteps; }
155 
161  void setSubSteps(int substeps) { mSubSteps = math::Max(1, substeps); }
162 
165  double getMaxVelocity() const { return mMaxVelocity; }
166 
177  template<typename VolumeGridT>
178  int getMaxDistance(const VolumeGridT& inGrid, double dt) const
179  {
180  if (!inGrid.hasUniformVoxels()) {
181  OPENVDB_THROW(RuntimeError, "Volume grid does not have uniform voxels!");
182  }
183  const double d = mMaxVelocity*math::Abs(dt)/inGrid.voxelSize()[0];
184  return static_cast<int>( math::RoundUp(d) );
185  }
186 
205  template<typename VolumeGridT,
206  typename VolumeSamplerT>//only C++11 allows for a default argument
207  typename VolumeGridT::Ptr advect(const VolumeGridT& inGrid, double timeStep)
208  {
209  typename VolumeGridT::Ptr outGrid = inGrid.deepCopy();
210  const double dt = timeStep/mSubSteps;
211  const int n = this->getMaxDistance(inGrid, dt);
212  dilateActiveValues( outGrid->tree(), n, NN_FACE, EXPAND_TILES);
213  this->template cook<VolumeGridT, VolumeSamplerT>(*outGrid, inGrid, dt);
214  for (int step = 1; step < mSubSteps; ++step) {
215  typename VolumeGridT::Ptr tmpGrid = outGrid->deepCopy();
216  dilateActiveValues( tmpGrid->tree(), n, NN_FACE, EXPAND_TILES);
217  this->template cook<VolumeGridT, VolumeSamplerT>(*tmpGrid, *outGrid, dt);
218  outGrid.swap( tmpGrid );
219  }
220 
221  return outGrid;
222  }
223 
251  template<typename VolumeGridT,
252  typename MaskGridT,
253  typename VolumeSamplerT>//only C++11 allows for a default argument
254  typename VolumeGridT::Ptr advect(const VolumeGridT& inGrid, const MaskGridT& mask, double timeStep)
255  {
256  if (inGrid.transform() != mask.transform()) {
257  OPENVDB_THROW(RuntimeError, "Volume grid and mask grid are misaligned! Consider "
258  "resampling either of the two grids into the index space of the other.");
259  }
260  typename VolumeGridT::Ptr outGrid = inGrid.deepCopy();
261  const double dt = timeStep/mSubSteps;
262  const int n = this->getMaxDistance(inGrid, dt);
263  dilateActiveValues( outGrid->tree(), n, NN_FACE, EXPAND_TILES);
264  outGrid->topologyIntersection( mask );
265  pruneInactive( outGrid->tree(), mGrainSize>0, mGrainSize );
266  this->template cook<VolumeGridT, VolumeSamplerT>(*outGrid, inGrid, dt);
267  outGrid->topologyUnion( inGrid );
268 
269  for (int step = 1; step < mSubSteps; ++step) {
270  typename VolumeGridT::Ptr tmpGrid = outGrid->deepCopy();
271  dilateActiveValues( tmpGrid->tree(), n, NN_FACE, EXPAND_TILES);
272  tmpGrid->topologyIntersection( mask );
273  pruneInactive( tmpGrid->tree(), mGrainSize>0, mGrainSize );
274  this->template cook<VolumeGridT, VolumeSamplerT>(*tmpGrid, *outGrid, dt);
275  tmpGrid->topologyUnion( inGrid );
276  outGrid.swap( tmpGrid );
277  }
278  return outGrid;
279  }
280 
281 private:
282  // disallow copy construction and copy by assignment!
283  VolumeAdvection(const VolumeAdvection&);// not implemented
284  VolumeAdvection& operator=(const VolumeAdvection&);// not implemented
285 
286  void start(const char* str) const
287  {
288  if (mInterrupter) mInterrupter->start(str);
289  }
290  void stop() const
291  {
292  if (mInterrupter) mInterrupter->end();
293  }
294  bool interrupt() const
295  {
296  if (mInterrupter && util::wasInterrupted(mInterrupter)) {
297  tbb::task::self().cancel_group_execution();
298  return true;
299  }
300  return false;
301  }
302 
303  template<typename VolumeGridT, typename VolumeSamplerT>
304  void cook(VolumeGridT& outGrid, const VolumeGridT& inGrid, double dt)
305  {
306  switch (mIntegrator) {
307  case Scheme::SEMI: {
308  Advect<VolumeGridT, 1, VolumeSamplerT> adv(inGrid, *this);
309  adv.cook(outGrid, dt);
310  break;
311  }
312  case Scheme::MID: {
313  Advect<VolumeGridT, 2, VolumeSamplerT> adv(inGrid, *this);
314  adv.cook(outGrid, dt);
315  break;
316  }
317  case Scheme::RK3: {
318  Advect<VolumeGridT, 3, VolumeSamplerT> adv(inGrid, *this);
319  adv.cook(outGrid, dt);
320  break;
321  }
322  case Scheme::RK4: {
323  Advect<VolumeGridT, 4, VolumeSamplerT> adv(inGrid, *this);
324  adv.cook(outGrid, dt);
325  break;
326  }
327  case Scheme::BFECC: {
328  Advect<VolumeGridT, 1, VolumeSamplerT> adv(inGrid, *this);
329  adv.cook(outGrid, dt);
330  break;
331  }
332  case Scheme::MAC: {
333  Advect<VolumeGridT, 1, VolumeSamplerT> adv(inGrid, *this);
334  adv.cook(outGrid, dt);
335  break;
336  }
337  default:
338  OPENVDB_THROW(ValueError, "Spatial difference scheme not supported!");
339  }
340  pruneInactive(outGrid.tree(), mGrainSize>0, mGrainSize);
341  }
342 
343  // Private class that implements the multi-threaded advection
344  template<typename VolumeGridT, size_t OrderRK, typename SamplerT> struct Advect;
345 
346  // Private member data of VolumeAdvection
347  const VelocityGridT& mVelGrid;
348  double mMaxVelocity;
349  InterrupterType* mInterrupter;
350  Scheme::SemiLagrangian mIntegrator;
351  Scheme::Limiter mLimiter;
352  size_t mGrainSize;
353  int mSubSteps;
354 };//end of VolumeAdvection class
355 
356 // Private class that implements the multi-threaded advection
357 template<typename VelocityGridT, bool StaggeredVelocity, typename InterrupterType>
358 template<typename VolumeGridT, size_t OrderRK, typename SamplerT>
359 struct VolumeAdvection<VelocityGridT, StaggeredVelocity, InterrupterType>::Advect
360 {
361  using TreeT = typename VolumeGridT::TreeType;
362  using AccT = typename VolumeGridT::ConstAccessor;
363  using ValueT = typename TreeT::ValueType;
364  using LeafManagerT = typename tree::LeafManager<TreeT>;
365  using LeafNodeT = typename LeafManagerT::LeafNodeType;
366  using LeafRangeT = typename LeafManagerT::LeafRange;
367  using VelocityIntegratorT = VelocityIntegrator<VelocityGridT, StaggeredVelocity>;
368  using RealT = typename VelocityIntegratorT::ElementType;
369  using VoxelIterT = typename TreeT::LeafNodeType::ValueOnIter;
370 
371  Advect(const VolumeGridT& inGrid, const VolumeAdvection& parent)
372  : mTask(nullptr)
373  , mInGrid(&inGrid)
374  , mVelocityInt(parent.mVelGrid)
375  , mParent(&parent)
376  {
377  }
378  inline void cook(const LeafRangeT& range)
379  {
380  if (mParent->mGrainSize > 0) {
381  tbb::parallel_for(range, *this);
382  } else {
383  (*this)(range);
384  }
385  }
386  void operator()(const LeafRangeT& range) const
387  {
388  assert(mTask);
389  mTask(const_cast<Advect*>(this), range);
390  }
391  void cook(VolumeGridT& outGrid, double time_step)
392  {
393  namespace ph = std::placeholders;
394 
395  mParent->start("Advecting volume");
396  LeafManagerT manager(outGrid.tree(), mParent->spatialOrder()==2 ? 1 : 0);
397  const LeafRangeT range = manager.leafRange(mParent->mGrainSize);
398  const RealT dt = static_cast<RealT>(-time_step);//method of characteristics backtracks
399  if (mParent->mIntegrator == Scheme::MAC) {
400  mTask = std::bind(&Advect::rk, ph::_1, ph::_2, dt, 0, mInGrid);//out[0]=forward
401  this->cook(range);
402  mTask = std::bind(&Advect::rk, ph::_1, ph::_2,-dt, 1, &outGrid);//out[1]=backward
403  this->cook(range);
404  mTask = std::bind(&Advect::mac, ph::_1, ph::_2);//out[0] = out[0] + (in[0] - out[1])/2
405  this->cook(range);
406  } else if (mParent->mIntegrator == Scheme::BFECC) {
407  mTask = std::bind(&Advect::rk, ph::_1, ph::_2, dt, 0, mInGrid);//out[0]=forward
408  this->cook(range);
409  mTask = std::bind(&Advect::rk, ph::_1, ph::_2,-dt, 1, &outGrid);//out[1]=backward
410  this->cook(range);
411  mTask = std::bind(&Advect::bfecc, ph::_1, ph::_2);//out[0] = (3*in[0] - out[1])/2
412  this->cook(range);
413  mTask = std::bind(&Advect::rk, ph::_1, ph::_2, dt, 1, &outGrid);//out[1]=forward
414  this->cook(range);
415  manager.swapLeafBuffer(1);// out[0] = out[1]
416  } else {// SEMI, MID, RK3 and RK4
417  mTask = std::bind(&Advect::rk, ph::_1, ph::_2, dt, 0, mInGrid);//forward
418  this->cook(range);
419  }
420 
421  if (mParent->spatialOrder()==2) manager.removeAuxBuffers();
422 
423  mTask = std::bind(&Advect::limiter, ph::_1, ph::_2, dt);// out[0] = limiter( out[0] )
424  this->cook(range);
425 
426  mParent->stop();
427  }
428  // Last step of the MacCormack scheme: out[0] = out[0] + (in[0] - out[1])/2
429  void mac(const LeafRangeT& range) const
430  {
431  if (mParent->interrupt()) return;
432  assert( mParent->mIntegrator == Scheme::MAC );
433  AccT acc = mInGrid->getAccessor();
434  for (typename LeafRangeT::Iterator leafIter = range.begin(); leafIter; ++leafIter) {
435  ValueT* out0 = leafIter.buffer( 0 ).data();// forward
436  const ValueT* out1 = leafIter.buffer( 1 ).data();// backward
437  const LeafNodeT* leaf = acc.probeConstLeaf( leafIter->origin() );
438  if (leaf != nullptr) {
439  const ValueT* in0 = leaf->buffer().data();
440  for (VoxelIterT voxelIter = leafIter->beginValueOn(); voxelIter; ++voxelIter) {
441  const Index i = voxelIter.pos();
442  out0[i] += RealT(0.5) * ( in0[i] - out1[i] );
443  }
444  } else {
445  for (VoxelIterT voxelIter = leafIter->beginValueOn(); voxelIter; ++voxelIter) {
446  const Index i = voxelIter.pos();
447  out0[i] += RealT(0.5) * ( acc.getValue(voxelIter.getCoord()) - out1[i] );
448  }//loop over active voxels
449  }
450  }//loop over leaf nodes
451  }
452  // Intermediate step in the BFECC scheme: out[0] = (3*in[0] - out[1])/2
453  void bfecc(const LeafRangeT& range) const
454  {
455  if (mParent->interrupt()) return;
456  assert( mParent->mIntegrator == Scheme::BFECC );
457  AccT acc = mInGrid->getAccessor();
458  for (typename LeafRangeT::Iterator leafIter = range.begin(); leafIter; ++leafIter) {
459  ValueT* out0 = leafIter.buffer( 0 ).data();// forward
460  const ValueT* out1 = leafIter.buffer( 1 ).data();// backward
461  const LeafNodeT* leaf = acc.probeConstLeaf(leafIter->origin());
462  if (leaf != nullptr) {
463  const ValueT* in0 = leaf->buffer().data();
464  for (VoxelIterT voxelIter = leafIter->beginValueOn(); voxelIter; ++voxelIter) {
465  const Index i = voxelIter.pos();
466  out0[i] = RealT(0.5)*( RealT(3)*in0[i] - out1[i] );
467  }//loop over active voxels
468  } else {
469  for (VoxelIterT voxelIter = leafIter->beginValueOn(); voxelIter; ++voxelIter) {
470  const Index i = voxelIter.pos();
471  out0[i] = RealT(0.5)*( RealT(3)*acc.getValue(voxelIter.getCoord()) - out1[i] );
472  }//loop over active voxels
473  }
474  }//loop over leaf nodes
475  }
476  // Semi-Lagrangian integration with Runge-Kutta of various orders (1->4)
477  void rk(const LeafRangeT& range, RealT dt, size_t n, const VolumeGridT* grid) const
478  {
479  if (mParent->interrupt()) return;
480  const math::Transform& xform = mInGrid->transform();
481  AccT acc = grid->getAccessor();
482  for (typename LeafRangeT::Iterator leafIter = range.begin(); leafIter; ++leafIter) {
483  ValueT* phi = leafIter.buffer( n ).data();
484  for (VoxelIterT voxelIter = leafIter->beginValueOn(); voxelIter; ++voxelIter) {
485  ValueT& value = phi[voxelIter.pos()];
486  Vec3d wPos = xform.indexToWorld(voxelIter.getCoord());
487  mVelocityInt.template rungeKutta<OrderRK, Vec3d>(dt, wPos);
488  value = SamplerT::sample(acc, xform.worldToIndex(wPos));
489  }//loop over active voxels
490  }//loop over leaf nodes
491  }
492  void limiter(const LeafRangeT& range, RealT dt) const
493  {
494  if (mParent->interrupt()) return;
495  const bool doLimiter = mParent->isLimiterOn();
496  const bool doClamp = mParent->mLimiter == Scheme::CLAMP;
497  ValueT data[2][2][2], vMin, vMax;
498  const math::Transform& xform = mInGrid->transform();
499  AccT acc = mInGrid->getAccessor();
500  const ValueT backg = mInGrid->background();
501  for (typename LeafRangeT::Iterator leafIter = range.begin(); leafIter; ++leafIter) {
502  ValueT* phi = leafIter.buffer( 0 ).data();
503  for (VoxelIterT voxelIter = leafIter->beginValueOn(); voxelIter; ++voxelIter) {
504  ValueT& value = phi[voxelIter.pos()];
505 
506  if ( doLimiter ) {
507  assert(OrderRK == 1);
508  Vec3d wPos = xform.indexToWorld(voxelIter.getCoord());
509  mVelocityInt.template rungeKutta<1, Vec3d>(dt, wPos);// Explicit Euler
510  Vec3d iPos = xform.worldToIndex(wPos);
511  Coord ijk = Coord::floor( iPos );
512  BoxSampler::getValues(data, acc, ijk);
513  BoxSampler::extrema(data, vMin, vMax);
514  if ( doClamp ) {
515  value = math::Clamp( value, vMin, vMax);
516  } else if (value < vMin || value > vMax ) {
517  iPos -= Vec3R(ijk[0], ijk[1], ijk[2]);//unit coordinates
518  value = BoxSampler::trilinearInterpolation( data, iPos );
519  }
520  }
521 
522  if (math::isApproxEqual(value, backg, math::Delta<ValueT>::value())) {
523  value = backg;
524  leafIter->setValueOff( voxelIter.pos() );
525  }
526  }//loop over active voxels
527  }//loop over leaf nodes
528  }
529  // Public member data of the private Advect class
530 
531  typename std::function<void (Advect*, const LeafRangeT&)> mTask;
532  const VolumeGridT* mInGrid;
533  const VelocityIntegratorT mVelocityInt;// lightweight!
534  const VolumeAdvection* mParent;
535 };// end of private member class Advect
536 
537 } // namespace tools
538 } // namespace OPENVDB_VERSION_NAME
539 } // namespace openvdb
540 
541 #endif // OPENVDB_TOOLS_VOLUME_ADVECT_HAS_BEEN_INCLUDED
Definition: VolumeAdvect.h:35
Delta for small floating-point offsets.
Definition: Math.h:97
VolumeGridT::Ptr advect(const VolumeGridT &inGrid, const MaskGridT &mask, double timeStep)
Definition: VolumeAdvect.h:254
void pruneInactive(TreeT &tree, bool threaded=true, size_t grainSize=1)
Reduce the memory footprint of a tree by replacing with background tiles any nodes whose values are a...
Definition: Prune.h:354
math::Extrema extrema(const IterT &iter, bool threaded=true)
Iterate over a scalar grid and compute extrema (min/max) of the values of the voxels that are visited...
Definition: Statistics.h:352
bool isApproxEqual(const Type &a, const Type &b)
Return true if a is equal to b to within the default floating-point comparison tolerance.
Definition: Math.h:351
General-purpose arithmetic and comparison routines, most of which accept arbitrary value types (or at...
double getMaxVelocity() const
Return the maximum magnitude of the velocity in the advection velocity field defined during construct...
Definition: VolumeAdvect.h:165
Defines two simple wrapper classes for advection velocity fields as well as VelocitySampler and Veloc...
Vec3< double > Vec3d
Definition: Vec3.h:662
Limiter
Flux-limiters employed to stabalize the second-order advection schemes MacCormack and BFECC...
Definition: VolumeAdvect.h:38
void setIntegrator(Scheme::SemiLagrangian integrator)
Set the integrator (see details in the table above)
Definition: VolumeAdvect.h:126
#define OPENVDB_THROW(exception, message)
Definition: Exceptions.h:82
Coord Abs(const Coord &xyz)
Definition: Coord.h:515
Functions to efficiently compute histograms, extremas (min/max) and statistics (mean, variance, etc.) of grid values.
double max() const
Return the maximum value.
Definition: Stats.h:128
int getSubSteps() const
Definition: VolumeAdvect.h:154
Performs Runge-Kutta time integration of variable order in a static velocity field.
Definition: VelocityFields.h:214
Performs advections of an arbitrary type of volume in a static velocity field. The advections are per...
Definition: VolumeAdvect.h:72
void setSubSteps(int substeps)
Set the number of sub-steps per integration.
Definition: VolumeAdvect.h:161
const Type & Max(const Type &a, const Type &b)
Return the maximum of two values.
Definition: Math.h:542
Defined various multi-threaded utility functions for trees.
Definition: VolumeAdvect.h:35
bool isLimiterOn() const
Return true if a limiter will be applied based on the current settings.
Definition: VolumeAdvect.h:139
Definition: VolumeAdvect.h:35
Vec3SGrid Vec3fGrid
Definition: openvdb.h:56
Definition: Exceptions.h:65
int getMaxDistance(const VolumeGridT &inGrid, double dt) const
Definition: VolumeAdvect.h:178
Definition: VolumeAdvect.h:38
int spatialOrder() const
Return the spatial order of accuracy of the advection scheme.
Definition: VolumeAdvect.h:105
#define OPENVDB_VERSION_NAME
The version namespace name for this library version.
Definition: version.h:102
Vec3d indexToWorld(const Vec3d &xyz) const
Apply this transformation to the given coordinates.
Definition: Transform.h:108
void setGrainSize(size_t grainsize)
Set the grain-size used for multi-threading.
Definition: VolumeAdvect.h:150
Implementation of morphological dilation and erosion.
Definition: Exceptions.h:13
void add(double val)
Add a single sample.
Definition: Stats.h:106
math::Vec3< Real > Vec3R
Definition: Types.h:49
Definition: Morphology.h:75
Definition: VolumeAdvect.h:35
Dummy NOOP interrupter class defining interface.
Definition: NullInterrupter.h:25
This class manages a linear array of pointers to a given tree&#39;s leaf nodes, as well as optional auxil...
Definition: LeafManager.h:82
bool wasInterrupted(T *i, int percent=-1)
Definition: NullInterrupter.h:49
VolumeAdvection(const VelocityGridT &velGrid, InterrupterType *interrupter=nullptr)
Constructor.
Definition: VolumeAdvect.h:83
Scheme::Limiter getLimiter() const
Retrun the limiter (see details above)
Definition: VolumeAdvect.h:135
Definition: Exceptions.h:63
Definition: Transform.h:39
size_t getGrainSize() const
Definition: VolumeAdvect.h:144
Type Clamp(Type x, Type min, Type max)
Return x clamped to [min, max].
Definition: Math.h:203
Definition: VolumeAdvect.h:38
This class computes the minimum and maximum values of a population of floating-point values...
Definition: Stats.h:92
float RoundUp(float x)
Return x rounded up to the nearest integer.
Definition: Math.h:734
Index32 Index
Definition: Types.h:31
Vec3d worldToIndex(const Vec3d &xyz) const
Apply this transformation to the given coordinates.
Definition: Transform.h:110
virtual ~VolumeAdvection()
Definition: VolumeAdvect.h:96
void dilateActiveValues(TreeType &tree, int iterations=1, NearestNeighbors nn=NN_FACE, TilePolicy mode=PRESERVE_TILES)
Topologically dilate all active values (i.e. both voxels and tiles) in a tree using one of three near...
Definition: Morphology.h:1047
Definition: VolumeAdvect.h:38
VolumeGridT::Ptr advect(const VolumeGridT &inGrid, double timeStep)
Definition: VolumeAdvect.h:207
#define OPENVDB_USE_VERSION_NAMESPACE
Definition: version.h:154
int temporalOrder() const
Return the temporal order of accuracy of the advection scheme.
Definition: VolumeAdvect.h:113
Definition: VolumeAdvect.h:35
Definition: Morphology.h:60
Scheme::SemiLagrangian getIntegrator() const
Return the integrator (see details in the table above)
Definition: VolumeAdvect.h:129
void setLimiter(Scheme::Limiter limiter)
Set the limiter (see details above)
Definition: VolumeAdvect.h:132
SemiLagrangian
Numerical advections schemes.
Definition: VolumeAdvect.h:35
Definition: VolumeAdvect.h:35