DamBreak++ Physics Simulator Upgraded to Modern C++

DamBreak++ Physics Simulator

Just completed the first version of the DamBreak++ physics simulator now supporting C++17/20. Many bugs are fixed and validation test completed successfully. A re-factoring has been done and is still ongoing. This required a big effort to make it done. Lot of prototypes were developed over the years, and I had to merge all these implementations the best I can. I had to decide what to keep and what to discard. In the meantime, I started to do some refactoring, clean-up of dead code, restructured classes and merge duplicate class, and re-write some implementation by using new C++17/20 features. The process is not completed, there is still a lot of work to be done before we get the whole thing straightened out.

But the effort is worthwhile, because the next step is to complete the clean-up of deprecated code and start using new features of C++17/20 to implement physical algorithm and other features.  This task reminds me that developing software is a long process through iterations and refactoring. I learned so much, first about programming techniques, also on process because now I see all errors I have made. First one and the biggest, I bypass the analysis phase, Use Case, which is the first thing that shall be done before starting coding. As already mentioned in my blog of last month, we always underestimate this step and the impact on the software development.

What is DamBreak++ Physics Simulator?

It’s essentially a collection of C++ classes organized in libraries. This framework implemented within first-version software still undergoing development, contains basic building blocks for the numerical solution, corresponding to the explicit finite difference (based on finite volume discretization).

The physics simulator simulates the One-Dimensional wave propagation in the so-called dam break problem. This classical test case is considered a benchmark for comparison of the performance of numerical schemes specially designed for discontinuous transient flow. In industrial research projects, the physicist is often called upon to test or experiment with different scenarios; this type of environment meets this need.

DamBreak++ Validation Results

Below validation results (one-dimensional wave propagation) in a horizontal and frictionless channel (water depth and discharge variables profiles).

DamBreak++ Code Snippets

Below are some code snippets taken from the programming environment. As mentioned, I started to migrate some of the original algorithms towards Modern C++ using new features.

Concept C++20

Many of our algorithms (different signatures) are written as templates, some of them use pointers and other numerical containers for fast-floating point operations. “Concept” C++20 keyword is a constraint on template parameter and allows the selection of the algorithm according to the signature. Legacy code uses pointer implementation, and we want to keep supporting those algorithms.

  template<typename Callable, typename ...Args>
  decltype(auto) call( Callable&& aCalbl, Args&&... args)
  {
    std::invoke( std::forward<Callable>(aCalbl), // call passed to callable with 
      std::forward<Args>(args)...);              // all additional passed args
  }

  // C++20 concept
  template<typename T>
  concept IsPointer = std::is_pointer_v<T>;

  template<IsPointer T, auto N=101>
    void hllSchemeFlux( T aU1, T aU2, T aFF1, T aFF2) // E. McNeil version
    {
      static_assert( N == vsc19::EMCNEILNbSections::value);
    
      // do some numerical stuff (function-ptr from legacy code)
      // by using "call" function above (did some stuff like that in May24)
      EMcNeilCalculFF(aU1,aU2,aFF1,aFF2); 
    }
C++

C++20 Range Libary

In the example below, I use the view (C++20 subrange) which is cheap to copy and move. The range library provides “adaptor/factory” to create subrange or views. Here we make use of “counted” factory to create a view which is a std::span (C++20 feature), a view.

    // C++20 ranges library
    if( std::ranges::range<decltype(aA)>)
    {
      // pass an lvalue, std::span is used (view) factory that create a view
      auto w_rng = std::views::counted( std::ranges::begin(w_Avec), aA.size());

      // Nujic paper(1995) physics consideration we need to take the average over cell
      auto w_Avrg = Sfx::cell_face_average(w_rng); // pass a view by value (cheap to copy)

      /// make the product of both average and derivative(computational node)
      // Nujic algorithm to take account of some physics stuff ...
      auto w_TermeS0 = w_Avrg * w_dxHvarray;  // average times derivative (H_avg x d1xH)

      return w_TermeS0;
    }
C++

Move Semantic C++11

The predictor method signature takes a rvalue reference, this method computes intermediate values, and these are not needed anymore once the computation is done. “Move semantic” is appropriate in this context. Avoid copying large numerical arrays which can be expensive.

  // SWERHS shall be implemented in terms of numerical array for fast-floating point
  void NujicIntegrator::predictor( SweRhsAlgorithm::SWERHS&& aRhs, const double aDt)
  {
    using namespace std;

    // although pass an rvalue reference, within the function 
    // itself it is treated as an lvalue (cast to rvalue ref)
    const auto w_rhs = std::move(aRhs);
   ...
  }

  void NujicIntegrator::step(SweRhsAlgorithm* aRhsAlgo, const double aDt)
  { 
    switch (m_integratorStep)
    {
    case eIntegratorStep::predictor:
      predictor(aRhsAlgo->getRHS(), aDt); //prvalue: pure reading value
      break;
    case eIntegratorStep::corrector:
      corrector(aRhsAlgo->getRHS(), aDt); //prvalue: pure reading value
      break;
    default:
      break;
    }
  }
C++

DamBreak++ Project

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *