1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320 | // *****************************************************************************
/*!
\file src/Inciter/Scheme.hpp
\copyright 2012-2015 J. Bakosi,
2016-2018 Los Alamos National Security, LLC.,
2019-2021 Triad National Security, LLC.
All rights reserved. See the LICENSE file for details.
\brief Polymorphic glue for calling Charm++ entry methods to base class
Discretization, its children implementing specific discretization schemes,
and helper classes
\details
The purpose of this class is to hide, behind a single type, different
Charm++ proxy types that model a single concept, i.e., define some common
functions as Charm++ entry methods that can be used in either a broadcast
and/or in a way of addressing a single array element. As a result, member
functions can be invoked by client code without knowing the underlying type
or any specifics to the underlying differences of the classes that model the
same concept, i.e., expose the same member functions. The idea is very
similar to inheritance and runtime polymorphism with base classes and
virtual functions: some member functions and data are common to all types
modeled (and thus are not repeated and/or copied), while some are specific.
A difference is that the "base" and "child" classes are Charm++ proxies.
Note that while Charm++ does support inheritance and runtime polymorphism
with chare arrays, we still prefer the implementation below because it uses
entirely value semantics (inside and in client code) and thus it keeps the
complexity of the dispatch behind this class and does not expose it to
client code.
The advantages of this class over traditional runtime polymorphism are (1)
value semantics (both internally and to client code), (2) not templated,
and (3) PUPable, i.e., an instance of Scheme can be sent across the network
using Charm++'s pup framework. Also, since the class only holds a couple of
chare proxies, it is lightweight.
Example usage from client code:
\code{.cpp}
// Instantiate a Scheme object
Scheme s( ctr::SchemeType::DG ); // see Control/Inciter/Options/Scheme.h
// Issue broadcast to child scheme entry method
s.bcast< Scheme::setup >(...);
// Issue broadcast to base (Discretization) entry method
s.disc().totalvol();
\endcode
Organization, implementation details, end extension of the class:
Scheme contains (at least) two Charm++ proxies: discproxy and proxy. The
former contains data and functionality common to all discretizations, and
this can be considered as an equivalent to a base class in the OOP sense.
The latter, proxy, contains data and functionality specific to a particular
discretization. When instantiated, Scheme is configured for a single
specific discretization which must be selected from the list of types in
SchemeBase::Proxy.
The underlying type of proxy is a variant, which allows storing exactly one
object. A variant is a type-safe union. An instance of a variant at any
given time either holds a value of one of its alternative types. Read more
on std::variant on how they work.
Adding a new child scheme is done by
(1) Adding a new type of Charm++ chare array proxy to Scheme::Proxy,
(2) Adding a new type of Charm++ chare array element proxy to
Scheme::ProxyElem, and
(3) Adding a new branch to the if test in Scheme's constructor.
\see A talk on "Concept-based runtime polymorphism with Charm++ chare arrays
using value semantics given by J. Bakosi at the 16th Annual Workshop on
Charm++ and its Applications, April 2018, discussing an earlier, more
verbose implementation of the idea, using C++11.
*/
// *****************************************************************************
#ifndef Scheme_h
#define Scheme_h
#include "Exception.hpp"
#include "PUPUtil.hpp"
#include "Inciter/Options/Scheme.hpp"
#include "NoWarning/discretization.decl.h"
#include "NoWarning/alecg.decl.h"
#include "NoWarning/oversetfe.decl.h"
#include "NoWarning/dg.decl.h"
#include "NoWarning/fv.decl.h"
#include "NoWarning/ale.decl.h"
#include "NoWarning/conjugategradients.decl.h"
#include "NoWarning/ghosts.decl.h"
namespace inciter {
//! Base class for generic forwarding interface to discretization proxies
class Scheme {
private:
//! Variant type listing all chare proxy types modeling the same concept
using Proxy = std::variant< CProxy_DG
, CProxy_ALECG
, CProxy_OversetFE
, CProxy_FV >;
public:
//! Variant type listing all chare element proxy types
using ProxyElem = std::variant< CProxy_DG::element_t
, CProxy_ALECG::element_t
, CProxy_OversetFE::element_t
, CProxy_FV::element_t >;
//! Empty constructor for Charm++
explicit Scheme() {}
//! Constructor
//! \param[in] scheme Discretization scheme
//! \param[in] ale True if enable ALE
//! \param[in] linearsolver True if enable a linear solver
//! \details Based on the input enum we create at least two empty chare
//! arrays: (1) discproxy which contains common functionality and data for
//! all discretizations, and (2) proxy, which have functionality and data
//! specific to a given discretization. Note that proxy is bound (in
//! migration behavior and properties) to discproxy.
//! \note There may be other bound proxy arrays created depending on the
//! specific discretization configured by the enum.
explicit Scheme( ctr::SchemeType scheme,
bool ale = false,
bool linearsolver = false,
tk::Centering centering = tk::Centering::NODE ) :
discproxy( CProxy_Discretization::ckNew() )
{
bound.bindTo( discproxy );
if (scheme == ctr::SchemeType::DG ||
scheme == ctr::SchemeType::P0P1 ||
scheme == ctr::SchemeType::DGP1 ||
scheme == ctr::SchemeType::DGP2 ||
scheme == ctr::SchemeType::PDG)
{
proxy = static_cast< CProxy_DG >( CProxy_DG::ckNew(bound) );
} else if (scheme == ctr::SchemeType::ALECG) {
proxy = static_cast< CProxy_ALECG >( CProxy_ALECG::ckNew(bound) );
} else if (scheme == ctr::SchemeType::OversetFE) {
proxy = static_cast< CProxy_OversetFE >( CProxy_OversetFE::ckNew(bound) );
} else if (scheme == ctr::SchemeType::FV) {
proxy = static_cast< CProxy_FV >( CProxy_FV::ckNew(bound) );
} else Throw( "Unknown discretization scheme" );
if (ale) aleproxy = CProxy_ALE::ckNew(bound);
if (linearsolver)
conjugategradientsproxy = tk::CProxy_ConjugateGradients::ckNew(bound);
if (centering == tk::Centering::ELEM)
ghostsproxy = CProxy_Ghosts::ckNew(bound);
}
//! Entry method tags for specific Scheme classes to use with bcast()
struct setup {};
struct box {};
struct transferSol {};
struct advance {};
struct resized {};
struct resizeComm {};
struct refine {};
struct lhs {};
struct nodeNeighSetup {};
struct diag {};
struct evalLB {};
struct doneInserting {};
//! Issue broadcast to Scheme entry method
//! \tparam Fn Function tag identifying the entry method to call
//! \tparam Args Types of arguments to pass to entry method
//! \param[in] args Arguments to member function entry method to be called
//! \details This function issues a broadcast to a member function entry
//! method of the Scheme chare array (the child of Discretization) and is
//! thus equivalent to proxy.Fn(...).
template< typename Fn, typename... Args >
void bcast( Args&&... args ) {
std::visit( [&]( auto& p ){<--- Parameter 'p' can be declared with const
if constexpr( std::is_same_v< Fn, setup > )
p.setup( std::forward< Args >( args )... );
if constexpr( std::is_same_v< Fn, box > )
p.box( std::forward< Args >( args )... );
else if constexpr( std::is_same_v< Fn, transferSol > )
p.transferSol( std::forward< Args >( args )... );
else if constexpr( std::is_same_v< Fn, advance > )
p.advance( std::forward< Args >( args )... );
else if constexpr( std::is_same_v< Fn, resized > )
p.resized( std::forward< Args >( args )... );
else if constexpr( std::is_same_v< Fn, resizeComm > )
p.resizeComm( std::forward< Args >( args )... );
else if constexpr( std::is_same_v< Fn, refine > )
p.refine( std::forward< Args >( args )... );
else if constexpr( std::is_same_v< Fn, lhs > )
p.lhs( std::forward< Args >( args )... );
else if constexpr( std::is_same_v< Fn, nodeNeighSetup > )
p.nodeNeighSetup( std::forward< Args >( args )... );
else if constexpr( std::is_same_v< Fn, diag > )
p.diag( std::forward< Args >( args )... );
else if constexpr( std::is_same_v< Fn, evalLB > )
p.evalLB( std::forward< Args >( args )... );
else if constexpr( std::is_same_v< Fn, doneInserting > )
p.doneInserting( std::forward< Args >( args )... );
}, proxy );
}
//! Function tags for specific Scheme classes to use with ckLocal()
struct resizePostAMR {};
struct extractFieldOutput {};
struct solution {};
//! Call Scheme function via Charm++ chare array element's ckLocal()
//! \tparam Fn Function tag identifying the function to call
//! \tparam Args Types of arguments to pass to function
//! \param[in] x Chare array element index
//! \param[in] args Arguments to member function function to be called
//! \details This function calls a member function via Charm++'s ckLocal()
//! behind the element proxy configured, indexed by the array index x.
//! Since the call is behind ckLocal(), the member function does not have
//! to be a Charm++ entry method.
template< typename Fn, typename... Args >
auto ckLocal( const CkArrayIndex1D& x, Args&&... args ) const {
auto e = element( x );
return std::visit( [&]( auto& p ){
if constexpr( std::is_same_v< Fn, resizePostAMR > )
return p.ckLocal()->resizePostAMR( std::forward<Args>(args)... );
else if constexpr( std::is_same_v< Fn, extractFieldOutput > )
return p.ckLocal()->extractFieldOutput(std::forward<Args>(args)...);
else if constexpr( std::is_same_v< Fn, solution > )
return p.ckLocal()->solution( std::forward<Args>(args)... );
}, e );
}
//! Function to call the insert entry method of an element proxy
//! \param[in] x Chare array element index
//! \param[in] args Arguments to member function (entry method) to be called
//! \details This function calls the insert member function of a chare array
//! element proxy and thus equivalent to proxy[x].insert(...), using the
//! last argument as default.
template< typename... Args >
void insert( const CkArrayIndex1D& x, Args&&... args ) {
auto e = element( x );
std::visit( [&]( auto& p ){ p.insert(std::forward<Args>(args)...); }, e );<--- Parameter 'p' can be declared with const
}
//! Get reference to discretization proxy
//! \return Discretization Charm++ chare array proxy
CProxy_Discretization& disc() noexcept { return discproxy; }
//! Get reference to ALE proxy
//! \return ALE Charm++ chare array proxy
CProxy_ALE& ale() noexcept { return aleproxy; }
//! Get reference to ConjugateGradients proxy
//! \return ConjugateGradients Charm++ chare array proxy
tk::CProxy_ConjugateGradients& conjugategradients() noexcept
{ return conjugategradientsproxy; }
//! Get reference to Ghosts proxy
//! \return Ghosts Charm++ chare array proxy
CProxy_Ghosts& ghosts() noexcept { return ghostsproxy; }
//! Get reference to scheme proxy
//! Get reference to scheme proxy
//! \return Variant storing Charm++ chare array proxy configured
const Proxy& getProxy() noexcept { return proxy; }
//! Query underlying proxy type
//! \return Zero-based index into the set of types of Proxy
std::size_t index() const noexcept { return proxy.index(); }
//! Query underlying proxy element type
//! \return Zero-based index that can be used, e.g., indexing into the set
//! of types of ProxyElem
std::size_t index_element() const noexcept { return element(0).index(); }
//! Charm++ array options accessor for binding external proxies
//! \return Charm++ array options object reference
const CkArrayOptions& arrayoptions() { return bound; }
/** @name Charm++ pack/unpack serializer member functions */
///@{
//! \brief Pack/Unpack serialize member function
//! \param[in,out] p Charm++'s PUP::er serializer object reference
void pup( PUP::er &p ) {<--- Parameter 'p' can be declared with const
p | proxy;
p | discproxy;
p | aleproxy;
p | conjugategradientsproxy;
p | ghostsproxy;
p | bound;
}
//! \brief Pack/Unpack serialize operator|
//! \param[in,out] p Charm++'s PUP::er serializer object reference
//! \param[in,out] s Scheme object reference
friend void operator|( PUP::er& p, Scheme& s ) { s.pup(p); }
//@}
private:
//! Variant storing one proxy to which this class is configured for
Proxy proxy;
//! Charm++ proxy to data and code common to all discretizations
CProxy_Discretization discproxy;
//! Charm++ proxy to ALE class
CProxy_ALE aleproxy;
//! Charm++ proxy to conjugate gradients linear solver class
tk::CProxy_ConjugateGradients conjugategradientsproxy;
//! Charm++ proxy to Ghosts class
CProxy_Ghosts ghostsproxy;
//! Charm++ array options for binding chares
CkArrayOptions bound;
//! Function dereferencing operator[] of chare proxy inside variant
//! \param[in] x Chare array element index
//! \return Chare array element proxy as a variant, defined by ProxyElem
//! \details The returning element proxy is a variant, depending on the
//! input proxy.
ProxyElem element( const CkArrayIndex1D& x ) const {
return std::visit( [&]( const auto& p ){
return static_cast< ProxyElem >( p[x] ); }, proxy );
}
};
} // inciter::
#endif // Scheme_h
|