generic declaration of and acess to member variables

Pages: 12
> The other dependency concerns the update() function and do_update struct. ...
> if I can't get rid of do_update, what about making it generic

Instead of the function object do_update(), we can use a polymorphic lambda expression.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ...

template < template< typename, std::size_t > class G, typename T, std::size_t N > struct C
{
    typename make_sequence< G, T, N >::type tup ;

    void update() { boost::fusion::for_each( tup, [] ( auto& x ) { x.update() ; } ) ; }
    void reset( int v ) { boost::fusion::for_each( tup, [v] ( auto& x ) { x.reset(v) ; } ) ; }
};

template< typename T, std::size_t N > struct U
{
    void update() { std::cout << "U<T," << N << ">::update()  " ; }
    void reset( int v ) { std::cout << "U<T," << N << ">::reset(" << v << ")  " ; }
};

http://coliru.stacked-crooked.com/a/4c4f4903b5ead42a
Last edited on
> For three-dimension I have 3 classes FVG<1>, FVG<1>, FVG<3>
> one for each Cartesian axis
Useless, drop the classes, use an array.
Totally, utterly, indubitably useless.

Since sorrow never comes too late,
And happiness too swiftly flies.
Thought would destroy their paradise.
No more; where ignorance is bliss,
'Tis folly to be wise.
I wonder if anyone had enough time to indulge me to confirm that my understanding of this is somewhere near correct?

My bare bones understanding of flow analysis is like the example of a weather modelling system. There is potentially 1, 2 or 3d world model of cells, where each cell is a subsystem. The cells can have different scales depending on size and / or time. A cell accepts inputs from it's neighbours, and possibly from external sources too. Then a cell goes through it's own processes, and it's output becomes input for the neighbouring cells. So looking at the world model over time we see how it changes dynamically.

With the current problem, I imagine the following groups of 3 types to be distinct scenarios:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// millimetres and milliseconds scale
FVG<10>; // XAxis
FVG<11>; // YAxis
FVG<12>; // ZAxis

// centimetres and seconds scale
FVG<20>; // XAxis
FVG<21>; // YAxis
FVG<22>; // ZAxis

// metres and minutes scale
FVG<30>; // XAxis
FVG<31>; // YAxis
FVG<32>; // ZAxis 


If these are distinct scenarios, I am having trouble seeing why one would want to iterate these types. I gathered that is what the OP was talking about originally - there was mention of a container of these types.

But obviously one would want to iterate the world model cells. I wonder if JLBorges's code does actually iterate the world model. I guess I have lost track of which type is the model / cell and which are the types that go with the cells. Or, I might have the wrong end of the stick completely :+D

Just to be clear, I gather that the reason why we have a type for each of the 3 axes is that each one represents a component in that direction, and they are associated with partial derivative equations.

Anyway, I hope I am not being a pain in asking these questions.

Regards :+)
Hi everyone,
JLBorges, you definitely helped me a lot, and I'm very thankful. Indeed, till know the code does not seem to have lot of use. So, let me try to explain how I get to pursue this approach:
The "simplest" situation I can think about is solving a 2D problem of a free fluid flow in one scale. In this situation, we have 3 equations: the mass balance equation, and 2 momentum equations one for each axis X and Y. A good practice would be to refer to each equation by an index (or a tag) throughout all the code. Otherwise, if we refer to it by an index here, a name their and a tag in another place God knows where it is, maintainability turns to be nightmare, and further development, eventually trying to add another equation, will be if not "impossible", too expensive. This is one of the big challenges that many mature software, commercial and academic, are facing.
Now, lets suppose we have the following indices and model:
1
2
3
4
5
6
7
8
9
10
11
12
namespace One_Scale_Model {
template < size_t offset > struct Indices // Note: grid dimension is a compile-time parameter
{
  static const int GridDimension = 2;
  static const int NumberOfEquations = offset + 1 + GridDimension;
  static const int MassBalanceEqIdx = offset;
  static const int MomentumEqFirstIdx = MassBalanceEqIdx;
  static const int MomentumEqLastIdx = MassBalanceEqIdx + GridDimension;
};
} // namespace One_Scale_Model

struct Model { using Indices = One_Scale_Model::Indices<0>; };


now, some finite volume methods use different finite volume geometries for different equations. For example, the momentum Finite Volume Geometry class may be completely different from that of a the mass balance when using a staggered grid. So, one can write a class for the momentum FVG and specialize it for the mass balance.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template < typename T, std::size_t N > struct FVG 
{
  void update() { std::cout << "FVG<T," << N << ">::update()  " ; } 
  void reset( int v ) { std::cout << "FVG<T," << N << ">::reset(" << v << ")  " ; } 
  // momentum dependent members...
  void A() { std::cout << "momentum fvg's member A()\n"; }
};

template < typename T > struct FVG< T, T::Indices::MassBalanceEqIdx >
{
  void update() { std::cout << "FVG<T," << N << ">::update()  " ; } 
  void reset( int v ) { std::cout << "FVG<T," << N << ">::reset(" << v << ")  " ; } 
  // mass balance dependent members...
  void B() { std::cout << "mass balance fvg's member()\n"; }
};


Note that partial specialization might be needed for multi scale models (like weather modelling cited by TheIdeasMan, or other multi-scale approaches...), and this can be done by adding an additional index as argument for the FVG template class:
 
template < typename T, std::size_t EqIdx, std::size_t ScaleIdx > struct FVG;


In the present case 2D problem - 1 scale model, for each grid cell, one need to instantiate the FVG 3 times, each instance has a different types. Thus, one may think about putting them (the 3 instances) together in a container.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
template < template< typename, std::size_t > class G, typename T, std::size_t N > struct FVG_C
{
  typename make_sequence< G, T, N >::type tup ;
  
  template < typename Seq, typename F>
  static void for_each( Seq &seq, const F &f ) { boost::fusion::for_each(seq,f); }
  
  void update() { for_each( tup, [] ( auto& x ) { x.update() ; } ) ; }
  void reset( int v ) { for_each( tup, [v] ( auto& x ) { x.reset(v) ; } ) ; }
  
  template < int idx >
  const G<T,idx> &fvg() const
  { return std::get<idx>(tup); }
};


and the tuple is defined by,
1
2
3
4
5
6
7
8
9
template < template< typename, std::size_t > class G, typename T, std::size_t N >
struct make_sequence
{
  using type = typename std::remove_reference<
                 decltype( std::tuple_cat( std::declval< typename make_sequence<G,T,N-1>::type >(),
                                           std::declval< std::tuple< G<T,N> > >() ) ) >::type ;
};
template < template< typename, std::size_t > class G, typename T >
struct make_sequence<G,T,0> { using type = std::tuple< G<T,0> > ; };


doing so, we make a slim interface to FVC instances and types where ever in the code just using the equation's indices.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main()
{
  using Indices = typename Model::Indices;

  // run-time variables
  int GridCellsNumber = 4,
      markedCellIdx = 2;

  // I have NumberOfEquations (i.e. 3) FVC'instances for each grid cell
  std::vector< FVG_C< FVG, Model, Indices::NumberOfEquations > > fvg_vec(GridCellsNumber) ;

  // access (read-only) the mass balance FVC instance of the grid cell index i.
  fvg_vec[ markedCellIdx ].fvg< Indices::MassBalanceEqIdx >();

  // access (read-only) the momentum FVC instance of the grid cell index i along the axis X.
  fvg_vec[ markedCellIdx ].fvg< Indices::MomentumEqFirstIdx >();

  // update (read/write) FVC instances for all grid cells if the grid has been changed.
  if( /*grid_changed() == true*/ true) { for( auto& fvg : fvg_vec ) { fvg.update() ; std::cout << "\n"; } }
}


here is the full code: http://coliru.stacked-crooked.com/a/0126a95ebb22f173
I hope you find this useful for your aplications.

Thank you everyone for helping me out. See you!

Adim
Last edited on
Hi Adim,

Thank you very much for your detailed explanation, I guess you are quite busy - so I appreciate your effort :+)

Cheers!
You're welcome TheIdeasMan.
> till now the code does not seem to have lot of use.

No, it was quite obvious that expression templates are invaluable in a computation intensive domain like fluid dynamics.

For instance, FreePOOMA:
This may seem complicated, but that's because it is. POOMA, and other libraries based on expression templates, push C++ to its limits because that's what it takes to get high performance. Defining the templated classes such a library requires is a painstaking task, as is ensuring that their expansion produces the correct result, but once it has been done, programmers can take full advantage of operator overloading to create compact, readable, maintainable programs without sacrificing performance.
http://www.nongnu.org/freepooma/tutorial/background.html#expression-templates
Topic archived. No new replies allowed.
Pages: 12