Nektar++
CollectionOptimisation.cpp
Go to the documentation of this file.
1 ///////////////////////////////////////////////////////////////////////////////
2 //
3 // File: CollectionOptimisation.cpp
4 //
5 // For more information, please see: http://www.nektar.info
6 //
7 // The MIT License
8 //
9 // Copyright (c) 2006 Division of Applied Mathematics, Brown University (USA),
10 // Department of Aeronautics, Imperial College London (UK), and Scientific
11 // Computing and Imaging Institute, University of Utah (USA).
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining a
14 // copy of this software and associated documentation files (the "Software"),
15 // to deal in the Software without restriction, including without limitation
16 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
17 // and/or sell copies of the Software, and to permit persons to whom the
18 // Software is furnished to do so, subject to the following conditions:
19 //
20 // The above copyright notice and this permission notice shall be included
21 // in all copies or substantial portions of the Software.
22 //
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
24 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
26 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
29 // DEALINGS IN THE SOFTWARE.
30 //
31 // Description: Collection optimization definition
32 //
33 ///////////////////////////////////////////////////////////////////////////////
34 
35 #include <boost/algorithm/string/predicate.hpp>
36 #include <boost/core/ignore_unused.hpp>
37 
41 
42 #include <boost/algorithm/string/classification.hpp>
43 #include <boost/algorithm/string/split.hpp>
44 
45 #include <tinyxml.h>
46 
47 using namespace std;
48 
49 namespace Nektar
50 {
51 namespace Collections
52 {
53 
54 // static manager for Operator ImplementationMap
55 map<OpImpTimingKey, OperatorImpMap> CollectionOptimisation::m_opImpMap;
56 
57 CollectionOptimisation::CollectionOptimisation(
58  LibUtilities::SessionReaderSharedPtr pSession, const int shapedim,
59  ImplementationType defaultType)
60  : m_shapeDim(shapedim)
61 {
62  map<ElmtOrder, ImplementationType> defaults, defaultsPhysDeriv,
63  defaultsHelmholtz;
64  bool verbose = (pSession.get()) &&
65  (pSession->DefinesCmdLineArgument("verbose")) &&
66  (pSession->GetComm()->GetRank() == 0);
67 
68  m_autotune = false;
69  m_maxCollSize = 0;
70  m_defaultType = defaultType == eNoImpType ? eIterPerExp : defaultType;
71 
72  map<string, LibUtilities::ShapeType> elTypes;
73  elTypes["S"] = LibUtilities::eSegment;
74  elTypes["T"] = LibUtilities::eTriangle;
75  elTypes["Q"] = LibUtilities::eQuadrilateral;
76  elTypes["A"] = LibUtilities::eTetrahedron;
77  elTypes["P"] = LibUtilities::ePyramid;
78  elTypes["R"] = LibUtilities::ePrism;
79  elTypes["H"] = LibUtilities::eHexahedron;
80 
81  // Set defaults for all element types.
82  for (auto &it2 : elTypes)
83  {
84  defaults[ElmtOrder(it2.second, -1)] = m_defaultType;
85  defaultsPhysDeriv[ElmtOrder(it2.second, -1)] = m_defaultType;
86  defaultsHelmholtz[ElmtOrder(it2.second, -1)] = m_defaultType;
87  }
88 
89  if (defaultType == eNoImpType)
90  {
91  for (auto &it2 : elTypes)
92  {
93  // use Nocollection for Phys Deriv
94  defaultsPhysDeriv[ElmtOrder(it2.second, -1)] = eNoCollection;
95 
96  // Use IterPerExp
97  defaultsHelmholtz[ElmtOrder(it2.second, -1)] = eMatrixFree;
98  }
99  }
100 
101  map<string, OperatorType> opTypes;
102  for (int i = 0; i < SIZE_OperatorType; ++i)
103  {
104  opTypes[OperatorTypeMap[i]] = (OperatorType)i;
105  switch ((OperatorType)i)
106  {
107  case eHelmholtz:
108  m_global[(OperatorType)i] = defaultsHelmholtz;
109  break;
110  case ePhysDeriv:
111  m_global[(OperatorType)i] = defaultsPhysDeriv;
112  break;
113  default:
114  m_global[(OperatorType)i] = defaults;
115  }
116  }
117 
118  map<string, ImplementationType> impTypes;
119  for (int i = 0; i < SIZE_ImplementationType; ++i)
120  {
121  impTypes[ImplementationTypeMap[i]] = (ImplementationType)i;
122  }
123 
124  // turn off file reader if dummy pointer is given or if default
125  // option is passed and by default calling argument.
126  if ((defaultType == eNoImpType) && (pSession.get()))
127  {
128  TiXmlDocument &doc = pSession->GetDocument();
129  TiXmlHandle docHandle(&doc);
130  TiXmlElement *master = docHandle.FirstChildElement("NEKTAR").Element();
131  ASSERTL0(master, "Unable to find NEKTAR tag in file.");
132  bool WriteFullCollections = false;
133 
134  TiXmlElement *xmlCol = master->FirstChildElement("COLLECTIONS");
135 
136  // Check if user has specified some options
137  if (xmlCol)
138  {
139  // Set the maxsize and default implementation type if provided
140  const char *maxSize = xmlCol->Attribute("MAXSIZE");
141  m_maxCollSize = (maxSize ? atoi(maxSize) : 0);
142 
143  const char *defaultImpl = xmlCol->Attribute("DEFAULT");
144  m_defaultType = defaultType;
145 
146  // If user has specified a default impl type or autotuning
147  // and set this default across all operators.
148  if (defaultImpl)
149  {
150  const std::string collinfo = string(defaultImpl);
151  m_autotune = boost::iequals(collinfo, "auto");
152 
153  if (!m_autotune)
154  {
155  bool collectionFound{false};
156  for (int i = 1; i < Collections::SIZE_ImplementationType;
157  ++i)
158  {
159  if (boost::iequals(
160  collinfo,
162  {
164  collectionFound = true;
165  break;
166  }
167  }
168 
169  ASSERTL0(collectionFound,
170  "Unknown default collection scheme: " + collinfo);
171 
172  defaults.clear();
173  // Override default types
174  for (auto &it2 : elTypes)
175  {
176  defaults[ElmtOrder(it2.second, -1)] = m_defaultType;
177  }
178 
179  for (int i = 0; i < SIZE_OperatorType; ++i)
180  {
181  m_global[(OperatorType)i] = defaults;
182  }
183  }
184  }
185  const char *write = xmlCol->Attribute("WRITE");
186  if (write && boost::iequals(write, "true"))
187  {
188  WriteFullCollections = true;
189  }
190 
191  // Now process operator-specific implementation selections
192  ReadCollOps(xmlCol, m_global, verbose);
193 
194  // Print out operator map
195  if (verbose)
196  {
197  if (WriteFullCollections)
198  {
199  for (auto &mIt : m_global)
200  {
201  cout << "Operator " << OperatorTypeMap[mIt.first] << ":"
202  << endl;
203 
204  for (auto &eIt : mIt.second)
205  {
206  cout << "- "
207  << LibUtilities::ShapeTypeMap[eIt.first.first]
208  << " order " << eIt.first.second << " -> "
209  << ImplementationTypeMap[eIt.second] << endl;
210  }
211  }
212  }
213  }
214  }
215  }
216 }
217 
218 void CollectionOptimisation::ReadCollOps(TiXmlElement *xmlCol,
219  GlobalOpMap &global, bool verbose)
220 {
221  bool verboseHeader = true;
222  map<string, LibUtilities::ShapeType> elTypes;
223  elTypes["S"] = LibUtilities::eSegment;
224  elTypes["T"] = LibUtilities::eTriangle;
225  elTypes["Q"] = LibUtilities::eQuadrilateral;
226  elTypes["A"] = LibUtilities::eTetrahedron;
227  elTypes["P"] = LibUtilities::ePyramid;
228  elTypes["R"] = LibUtilities::ePrism;
229  elTypes["H"] = LibUtilities::eHexahedron;
230 
231  map<string, OperatorType> opTypes;
232  for (int i = 0; i < SIZE_OperatorType; ++i)
233  {
234  opTypes[OperatorTypeMap[i]] = (OperatorType)i;
235  }
236 
237  map<string, ImplementationType> impTypes;
238  for (int i = 0; i < SIZE_ImplementationType; ++i)
239  {
240  impTypes[ImplementationTypeMap[i]] = (ImplementationType)i;
241  }
242 
243  TiXmlElement *elmt = xmlCol->FirstChildElement();
244  while (elmt)
245  {
246  string tagname = elmt->ValueStr();
247 
248  ASSERTL0(boost::iequals(tagname, "OPERATOR"),
249  "Only OPERATOR tags are supported inside the "
250  "COLLECTIONS tag.");
251 
252  const char *attr = elmt->Attribute("TYPE");
253  ASSERTL0(attr, "Missing TYPE in OPERATOR tag.");
254  string opType(attr);
255 
256  ASSERTL0(opTypes.count(opType) > 0,
257  "Unknown OPERATOR type " + opType + ".");
258 
259  OperatorType ot = opTypes[opType];
260 
261  TiXmlElement *elmt2 = elmt->FirstChildElement();
262 
263  map<int, pair<int, std::string>> verboseWrite;
264  while (elmt2)
265  {
266  string tagname = elmt2->ValueStr();
267  ASSERTL0(boost::iequals(tagname, "ELEMENT"),
268  "Only ELEMENT tags are supported inside the "
269  "OPERATOR tag.");
270 
271  const char *attr = elmt2->Attribute("TYPE");
272  ASSERTL0(attr, "Missing TYPE in ELEMENT tag.");
273 
274  string elType(attr);
275  auto it2 = elTypes.find(elType);
276  ASSERTL0(it2 != elTypes.end(), "Unknown element type " + elType +
277  " in ELEMENT "
278  "tag");
279 
280  const char *attr2 = elmt2->Attribute("IMPTYPE");
281  ASSERTL0(attr2, "Missing IMPTYPE in ELEMENT tag.");
282  string impType(attr2);
283  ASSERTL0(impTypes.count(impType) > 0,
284  "Unknown IMPTYPE type " + impType + ".");
285 
286  const char *attr3 = elmt2->Attribute("ORDER");
287  ASSERTL0(attr3, "Missing ORDER in ELEMENT tag.");
288  string order(attr3);
289 
290  // load details relevant to this shape dimension.
291  if (LibUtilities::ShapeTypeDimMap[it2->second] == m_shapeDim)
292  {
293  if (order == "*")
294  {
295  global[ot][ElmtOrder(it2->second, -1)] = impTypes[impType];
296 
297  if (verbose)
298  {
299  verboseWrite[it2->second] =
300  pair<int, std::string>(-1, impType);
301  }
302  }
303  else
304  {
305  vector<unsigned int> orders;
306  ParseUtils::GenerateSeqVector(order, orders);
307 
308  for (int i = 0; i < orders.size(); ++i)
309  {
310  global[ot][ElmtOrder(it2->second, orders[i])] =
311  impTypes[impType];
312 
313  if (verbose)
314  {
315  verboseWrite[it2->second] =
316  pair<int, std::string>(orders[i], impType);
317  }
318  }
319  }
320  }
321 
322  elmt2 = elmt2->NextSiblingElement();
323  }
324 
325  if (verboseWrite.size())
326  {
327  if (verboseHeader)
328  {
329  cout << "Collection settings from file: " << endl;
330  verboseHeader = false;
331  }
332 
333  cout << "\t Operator " << OperatorTypeMap[ot] << ":" << endl;
334 
335  for (auto &it : verboseWrite)
336  {
337  cout << "\t - " << LibUtilities::ShapeTypeMap[it.first]
338  << " order " << it.second.first << " -> "
339  << it.second.second << endl;
340  }
341  }
342 
343  elmt = elmt->NextSiblingElement();
344  }
345 }
346 
349 {
350  OperatorImpMap ret;
351  ElmtOrder searchKey(pExp->DetShapeType(), pExp->GetBasisNumModes(0));
352  ElmtOrder defSearch(pExp->DetShapeType(), -1);
353 
354  for (auto &it : m_global)
355  {
356  ImplementationType impType;
357 
358  auto it2 = it.second.find(searchKey);
359 
360  if (it2 == it.second.end())
361  {
362  it2 = it.second.find(defSearch);
363  if (it2 == it.second.end())
364  {
365  // Shouldn't be able to reach here.
366  impType = eNoCollection;
367  }
368  else
369  {
370  impType = it2->second;
371  }
372  }
373  else
374  {
375  impType = it2->second;
376  }
377 
378  ret[it.first] = impType;
379  }
380 
381  return ret;
382 }
383 
385  vector<StdRegions::StdExpansionSharedPtr> pCollExp,
386  OperatorImpMap &impTypes, bool verbose)
387 {
388  boost::ignore_unused(impTypes);
389 
390  OperatorImpMap ret;
391 
392  StdRegions::StdExpansionSharedPtr pExp = pCollExp[0];
393 
394  // check to see if already defined for this expansion
395  OpImpTimingKey OpKey(pExp, pCollExp.size(), pExp->GetNumBases());
396  if (m_opImpMap.count(OpKey) != 0)
397  {
398  ret = m_opImpMap[OpKey];
399  return ret;
400  }
401 
402  int maxsize =
403  pCollExp.size() * max(pExp->GetNcoeffs(), pExp->GetTotPoints());
404  Array<OneD, NekDouble> inarray(maxsize, 1.0);
405  Array<OneD, NekDouble> outarray1(maxsize);
406  Array<OneD, NekDouble> outarray2(maxsize);
407  Array<OneD, NekDouble> outarray3(maxsize);
408 
410 
411  if (verbose)
412  {
413  cout << "Collection Implemenation for "
414  << LibUtilities::ShapeTypeMap[pExp->DetShapeType()] << " ( ";
415  for (int i = 0; i < pExp->GetNumBases(); ++i)
416  {
417  cout << pExp->GetBasis(i)->GetNumModes() << " ";
418  }
419  cout << ")"
420  << " for ngeoms = " << pCollExp.size() << endl;
421  }
422  // set up an array of collections
423  CollectionVector coll;
424 
425  StdRegions::ConstFactorMap factors; // required for helmholtz operator
426  factors[StdRegions::eFactorLambda] = 1.5;
427 
428  for (int imp = 1; imp < SIZE_ImplementationType; ++imp)
429  {
430  ImplementationType impType = (ImplementationType)imp;
431  OperatorImpMap impTypes;
432  for (int i = 0; i < SIZE_OperatorType; ++i)
433  {
434  OperatorType opType = (OperatorType)i;
435  OperatorKey opKey(pCollExp[0]->DetShapeType(), opType, impType,
436  pCollExp[0]->IsNodalNonTensorialExp());
437 
438  if (GetOperatorFactory().ModuleExists(opKey))
439  {
440  impTypes[opType] = impType;
441  }
442  }
443 
444  Collection collLoc(pCollExp, impTypes);
445  for (int i = 0; i < SIZE_OperatorType; ++i)
446  {
447  collLoc.Initialise((OperatorType)i, factors);
448  }
449  coll.push_back(collLoc);
450  }
451 
452  // Determine the number of tests to do in one second
454  for (int i = 0; i < SIZE_OperatorType; ++i)
455  {
456  OperatorType OpType = (OperatorType)i;
457 
458  t.Start();
459 
460  coll[0].ApplyOperator(OpType, inarray, outarray1, outarray2, outarray3);
461  t.Stop();
462 
463  NekDouble oneTest = t.TimePerTest(1);
464 
465  Ntest[i] = max((int)(0.25 / oneTest), 1);
466  }
467 
469 
470  if (verbose)
471  {
472  cout << "\t "
473  << " Op. "
474  << ":\t"
475  << "opt. Impl."
476  << "\t (IterLocExp, IterStdExp, "
477  "StdMat, SumFac, MatrixFree)"
478  << endl;
479  }
480 
481  // loop over all operators and determine fastest implementation
482  for (int i = 0; i < SIZE_OperatorType; ++i)
483  {
484  OperatorType OpType = (OperatorType)i;
485 
486  // call collection implementation in thorugh ExpList.
487  for (int imp = 0; imp < coll.size(); ++imp)
488  {
489  if (coll[imp].HasOperator(OpType))
490  {
491  t.Start();
492  for (int n = 0; n < Ntest[i]; ++n)
493  {
494  coll[imp].ApplyOperator(OpType, inarray, outarray1,
495  outarray2, outarray3);
496  }
497  t.Stop();
498  timing[imp] = t.TimePerTest(Ntest[i]);
499  }
500  else
501  {
502  timing[imp] = 1000.0;
503  }
504  }
505  // determine optimal implementation. Note +1 to
506  // remove NoImplementationType flag
507  int minImp = Vmath::Imin(coll.size(), timing, 1) + 1;
508 
509  if (verbose)
510  {
511  cout << "\t " << OperatorTypeMap1[i] << ": \t"
512  << ImplementationTypeMap1[minImp] << "\t (";
513  for (int j = 0; j < coll.size(); ++j)
514  {
515  if (timing[j] > 999.0)
516  {
517  cout << " -- ";
518  }
519  else
520  {
521  cout << timing[j];
522  }
523  if (j != coll.size() - 1)
524  {
525  cout << ", ";
526  }
527  }
528  cout << ")" << endl;
529  }
530 
531  // could reset global map if reusing method?
532  // m_global[OpType][pExp->DetShapeType()] = (ImplementationType)minImp;
533  // set up new map
534  ret[OpType] = (ImplementationType)minImp;
535  }
536 
537  // store map for use by another expansion.
538  m_opImpMap[OpKey] = ret;
539  return ret;
540 }
541 
542 void CollectionOptimisation::UpdateOptFile(std::string sessName,
544 {
545  if (comm->GetSize() != comm->GetSpaceComm()->GetSize())
546  {
547  return; // FIXME: Parareal
548  }
549 
550  std::string outname = sessName + ".opt";
551 
552  TiXmlDocument doc;
553  TiXmlElement *root;
554  TiXmlElement *xmlCol = new TiXmlElement("COLLECTIONS");
555  GlobalOpMap global;
556  int rank = comm->GetRank();
557  int nprocs = comm->GetSize();
558  if (rank == 0)
559  {
560  if (!doc.LoadFile(outname)) // set up new file
561  {
562  TiXmlDeclaration *decl = new TiXmlDeclaration("1.0", "utf-8", "");
563  doc.LinkEndChild(decl);
564  root = new TiXmlElement("NEKTAR");
565  doc.LinkEndChild(root);
566  root->LinkEndChild(xmlCol);
567  }
568  else // load file and read operator information
569  {
570  root = doc.FirstChildElement("NEKTAR");
571  xmlCol = root->FirstChildElement("COLLECTIONS");
572 
573  bool verbose = false;
574  ReadCollOps(xmlCol, global, verbose);
575  }
576  }
577 
578  // update global with m_opImpMap info
579  map<LibUtilities::ShapeType, int> ShapeMaxSize;
580  for (auto &opimp : m_opImpMap)
581  {
582  bool updateShape = true;
583  LibUtilities::ShapeType shape = opimp.first.GetShapeType();
584 
585  // check to see if already added this shapes details but with
586  // a larger collection and if so do not update.
587  if (ShapeMaxSize.count(shape))
588  {
589  int ngeoms = opimp.first.GetNGeoms();
590  if (ngeoms > ShapeMaxSize[shape])
591  {
592  ShapeMaxSize[shape] = ngeoms;
593  }
594  else
595  {
596  updateShape = false;
597  }
598  }
599 
600  if (updateShape)
601  {
602  for (auto &op : opimp.second)
603  {
604  global[op.first][ElmtOrder(shape, -1)] = op.second;
605  }
606  }
607  }
608 
609  // share
610  if (nprocs)
611  {
612  // loop over operators
613  for (auto &op : global)
614  {
615  // check to see which shapes are defined in this proc
618  for (auto &el : op.second)
619  {
620  ElmtImp[el.first.first] = el.second;
621  ElmtDef[el.first.first] = true;
622  }
623 
624  comm->AllReduce(ElmtImp, LibUtilities::ReduceMax);
625 
626  // loop over elements and update if not already defined
627  if (rank == 0)
628  {
629  for (int i = 1; i < LibUtilities::SIZE_ShapeType; ++i)
630  {
631  if ((ElmtImp[i] != -1) && (ElmtDef[i] == false))
632  {
633  global[op.first]
635  (ImplementationType)ElmtImp[i];
636  }
637  }
638  }
639  }
640  }
641 
642  // Update Collection section with global data on root
643  if (rank == 0)
644  {
645  xmlCol->Clear();
646 
647  map<LibUtilities::ShapeType, string> ShapeLetMap = {
648  {LibUtilities::eSegment, "S"},
652  {LibUtilities::ePyramid, "P"},
653  {LibUtilities::ePrism, "R"},
655 
656  for (auto &op : global)
657  {
658  TiXmlElement *ColOp = new TiXmlElement("OPERATOR");
659  xmlCol->LinkEndChild(ColOp);
660  ColOp->SetAttribute("TYPE", OperatorTypeMap[op.first]);
661 
662  for (auto &el : op.second)
663  {
664  TiXmlElement *ElmtOp = new TiXmlElement("ELEMENT");
665  ColOp->LinkEndChild(ElmtOp);
666 
667  ElmtOp->SetAttribute("TYPE", ShapeLetMap[el.first.first]);
668  ElmtOp->SetAttribute("ORDER", "*");
669  ElmtOp->SetAttribute("IMPTYPE",
670  ImplementationTypeMap[el.second]);
671  }
672  }
673 
674  doc.SaveFile(outname);
675  }
676 }
677 
678 } // namespace Collections
679 } // namespace Nektar
#define ASSERTL0(condition, msg)
Definition: ErrorUtil.hpp:215
COLLECTIONS_EXPORT void Initialise(const OperatorType opType, StdRegions::FactorMap factors=StdRegions::NullFactorMap)
Definition: Collection.cpp:63
COLLECTIONS_EXPORT OperatorImpMap SetWithTimings(std::vector< StdRegions::StdExpansionSharedPtr > pGeom, OperatorImpMap &impTypes, bool verbose=true)
COLLECTIONS_EXPORT void UpdateOptFile(std::string sessName, LibUtilities::CommSharedPtr &comm)
static std::map< OpImpTimingKey, OperatorImpMap > m_opImpMap
COLLECTIONS_EXPORT OperatorImpMap GetOperatorImpMap(StdRegions::StdExpansionSharedPtr pExp)
Get Operator Implementation Map from XMl or using default;.
std::map< OperatorType, std::map< ElmtOrder, ImplementationType > > GlobalOpMap
std::pair< LibUtilities::ShapeType, int > ElmtOrder
void ReadCollOps(TiXmlElement *xmlCol, GlobalOpMap &global, bool verbose)
NekDouble TimePerTest(unsigned int n)
Returns amount of seconds per iteration in a test with n iterations.
Definition: Timer.cpp:68
static bool GenerateSeqVector(const std::string &str, std::vector< unsigned int > &out)
Takes a comma-separated compressed string and converts it to entries in a vector.
Definition: ParseUtils.cpp:105
std::map< OperatorType, ImplementationType > OperatorImpMap
Definition: Operator.h:112
const char *const ImplementationTypeMap[]
Definition: Operator.h:94
const char *const ImplementationTypeMap1[]
Definition: Operator.h:101
const char *const OperatorTypeMap[]
Definition: Operator.h:76
std::tuple< LibUtilities::ShapeType, OperatorType, ImplementationType, ExpansionIsNodal > OperatorKey
Key for describing an Operator.
Definition: Operator.h:177
OperatorFactory & GetOperatorFactory()
Returns the singleton Operator factory object.
Definition: Operator.cpp:117
const char *const OperatorTypeMap1[]
Definition: Operator.h:80
std::vector< Collection > CollectionVector
Definition: Collection.h:110
const char *const ShapeTypeMap[SIZE_ShapeType]
Definition: ShapeType.hpp:79
std::shared_ptr< SessionReader > SessionReaderSharedPtr
std::shared_ptr< Comm > CommSharedPtr
Pointer to a Communicator object.
Definition: Comm.h:54
constexpr unsigned int ShapeTypeDimMap[SIZE_ShapeType]
Definition: ShapeType.hpp:85
std::shared_ptr< StdExpansion > StdExpansionSharedPtr
std::map< ConstFactorType, NekDouble > ConstFactorMap
Definition: StdRegions.hpp:399
The above copyright notice and this permission notice shall be included.
Definition: CoupledSolver.h:2
double NekDouble
int Imin(int n, const T *x, const int incx)
Return the index of the minimum element in x.
Definition: Vmath.cpp:1023