Nektar++
NodalUtil.cpp
Go to the documentation of this file.
1 ///////////////////////////////////////////////////////////////////////////////
2 //
3 // File: NodalUtil.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: 2D Nodal Triangle Fekete Utilities --
32 // Basis function, Interpolation, Integral, Derivation, etc
33 //
34 ///////////////////////////////////////////////////////////////////////////////
35 
36 #include <iomanip>
37 #include <limits>
38 
41 
46 
47 namespace Nektar
48 {
49 namespace LibUtilities
50 {
51 
52 /**
53  * @brief Obtain the integration weights for the given nodal distribution.
54  *
55  * This routine constructs the integration weights for the given nodal
56  * distribution inside NodalUtil::m_xi. To do this we solve the linear system
57  * \f$ \mathbf{V}^\top \mathbf{w} = \mathbf{g} \f$ where \f$ \mathbf{g}_i =
58  * \int_\Omega \psi_i(x)\, dx \f$, and we use the fact that under the definition
59  * of the orthogonal basis for each element type, \f$ \mathbf{g}_i = 0 \f$ for
60  * \f$ i > 0 \f$. We use NodalUtil::v_ModeZeroIntegral to return the analytic
61  * value of \f$ \mathbf{g}_0 \f$.
62  *
63  * @return Vector of integration weights for the integration points.
64  */
66 {
67  // Get number of modes in orthogonal basis
68  int numModes = v_NumModes();
69 
70  // If we have the same number of nodes as modes, then we can solve the
71  // linear system V^T w = (1,0,...)
72  if (numModes == m_numPoints)
73  {
75  g(0) = v_ModeZeroIntegral();
76 
78 
79  // Solve the system V^T w = g to obtain weights.
80  LinearSystem matL(V);
81  return matL.SolveTranspose(g);
82  }
83  else
84  {
85  // System is either over- or under-determined. Need to do least squares
86  // here using SVD.
87  return NekVector<NekDouble>();
88  }
89 }
90 
91 /**
92  * @brief Return the Vandermonde matrix for the nodal distribution.
93  *
94  * This routine constructs and returns the Vandermonde matrix \f$\mathbf{V}\f$,
95  * with each entry as \f$\mathbf{V}_{ij} = (\psi_i(\xi_j))\f$ where \f$ \psi_i
96  * \f$ is the orthogonal basis obtained through the abstract function
97  * NodalUtil::v_OrthoBasis.
98  *
99  * @return The Vandermonde matrix.
100  */
102 {
103  int rows = m_numPoints, cols = v_NumModes();
104  SharedMatrix matV =
105  MemoryManager<NekMatrix<NekDouble>>::AllocateSharedPtr(rows, cols, 0.0);
106 
107  for (int j = 0; j < cols; ++j)
108  {
110  for (int i = 0; i < rows; ++i)
111  {
112  (*matV)(i, j) = col[i];
113  }
114  }
115 
116  return matV;
117 }
118 /**
119  * @brief Return the Vandermonde matrix of the derivative of the basis functions
120  * for the nodal distribution.
121  *
122  * This routine constructs and returns the Vandermonde matrix for the derivative
123  * of the basis functions \f$\mathbf{V}_d\f$ for coordinate directions \f$ 0
124  * \leq d \leq 2 \f$, with each entry as \f$\mathbf{V}_{ij} = (\partial_d
125  * \psi_i(\xi_j))\f$ where \f$ \partial_d\psi_i \f$ is the derivative of the
126  * orthogonal basis obtained through the abstract function
127  * NodalUtil::v_OrthoBasisDeriv.
128  *
129  * @param dir Direction of derivative in the standard element.
130  *
131  * @return Vandermonde matrix corresponding with derivative of the basis
132  * functions in direction @p dir.
133  */
135 {
136  int rows = m_numPoints, cols = v_NumModes();
137  SharedMatrix matV =
138  MemoryManager<NekMatrix<NekDouble>>::AllocateSharedPtr(rows, cols, 0.0);
139 
140  for (int j = 0; j < cols; ++j)
141  {
143  for (int i = 0; i < rows; ++i)
144  {
145  (*matV)(i, j) = col[i];
146  }
147  }
148 
149  return matV;
150 }
151 
152 /**
153  * @brief Return the derivative matrix for the nodal distribution.
154  *
155  * This routine constructs and returns the derivative matrices
156  * \f$\mathbf{D}_d\f$ for coordinate directions \f$ 0 \leq d \leq 2 \f$, which
157  * can be used to evaluate the derivative of a nodal expansion at the points
158  * defined by NodalUtil::m_xi. These are calculated as \f$ \mathbf{D}_d =
159  * \mathbf{V}_d \mathbf{V}^{-1} \f$, where \f$ \mathbf{V}_d \f$ is the
160  * derivative Vandermonde matrix and \f$ \mathbf{V} \f$ is the Vandermonde
161  * matrix.
162  *
163  * @param dir Coordinate direction in which to evaluate the derivative.
164  *
165  * @return The derivative matrix for direction @p dir.
166  */
168 {
171  SharedMatrix D = MemoryManager<NekMatrix<NekDouble>>::AllocateSharedPtr(
172  V->GetRows(), V->GetColumns(), 0.0);
173 
174  V->Invert();
175 
176  *D = (*Vd) * (*V);
177 
178  return D;
179 }
180 
181 /**
182  * @brief Construct the interpolation matrix used to evaluate the basis at the
183  * points @p xi inside the element.
184  *
185  * This routine returns a matrix \f$ \mathbf{I}(\mathbf{a}) \f$ that can be used
186  * to evaluate the nodal basis at the points defined by the parameter @p xi,
187  * which is denoted by \f$ \mathbf{a} = (a_1, \dots, a_N) \f$ and \f$ N \f$ is
188  * the number of points in @p xi.
189  *
190  * In particular, if the array \f$ \mathbf{u} \f$ with components \f$
191  * \mathbf{u}_i = u^\delta(\xi_i) \f$ represents the polynomial approximation of
192  * a function \f$ u \f$ evaluated at the nodal points NodalUtil::m_xi, then the
193  * evaluation of \f$ u^\delta \f$ evaluated at the input points \f$ \mathbf{a}
194  * \f$ is given by \f$ \mathbf{I}(\mathbf{a})\mathbf{u} \f$.
195  *
196  * @param xi An array of first size number of spatial dimensions \f$ d \f$ and
197  * secondary size the number of points to interpolate.
198  *
199  * @return The interpolation matrix for the points @p xi.
200  */
203 {
204  std::shared_ptr<NodalUtil> subUtil = v_CreateUtil(xi);
205  SharedMatrix matS = GetVandermonde();
206  SharedMatrix matT = subUtil->GetVandermonde();
207  SharedMatrix D = MemoryManager<NekMatrix<NekDouble>>::AllocateSharedPtr(
208  matT->GetRows(), matS->GetColumns(), 0.0);
209 
210  matS->Invert();
211 
212  *D = (*matT) * (*matS);
213  return D;
214 }
215 
216 /**
217  * @brief Construct the nodal utility class for a triangle.
218  *
219  * The constructor of this class sets up two member variables used in the
220  * evaluation of the orthogonal basis:
221  *
222  * - NodalUtilTriangle::m_eta is used to construct the collapsed coordinate
223  * locations of the nodal points \f$ (\eta_1, \eta_2) \f$ inside the square
224  * \f$[-1,1]^2\f$ on which the orthogonal basis functions are defined.
225  * - NodalUtilTriangle::m_ordering constructs a mapping from the index set \f$ I
226  * = \{ (i,j)\ |\ 0\leq i,j \leq P, i+j \leq P \}\f$ to an ordering \f$ 0 \leq
227  * m(ij) \leq (P+1)(P+2)/2 \f$ that defines the monomials \f$ \xi_1^i \xi_2^j
228  * \f$ that span the triangular space. This is then used to calculate which
229  * \f$ (i,j) \f$ pair corresponding to a column of the Vandermonde matrix when
230  * calculating the orthogonal polynomials.
231  *
232  * @param degree Polynomial order of this nodal triangle.
233  * @param r \f$ \xi_1 \f$-coordinates of nodal points in the standard
234  * element.
235  * @param s \f$ \xi_2 \f$-coordinates of nodal points in the standard
236  * element.
237  */
240  : NodalUtil(degree, 2), m_eta(2)
241 {
242  // Set up parent variables.
243  m_numPoints = r.size();
244  m_xi[0] = r;
245  m_xi[1] = s;
246 
247  // Construct a mapping (i,j) -> m from the triangular tensor product space
248  // (i,j) to a single ordering m.
249  for (int i = 0; i <= m_degree; ++i)
250  {
251  for (int j = 0; j <= m_degree - i; ++j)
252  {
253  m_ordering.push_back(std::make_pair(i, j));
254  }
255  }
256 
257  // Calculate collapsed coordinates from r/s values
260 
261  for (int i = 0; i < m_numPoints; ++i)
262  {
263  if (fabs(m_xi[1][i] - 1.0) < NekConstants::kNekZeroTol)
264  {
265  m_eta[0][i] = -1.0;
266  m_eta[1][i] = 1.0;
267  }
268  else
269  {
270  m_eta[0][i] = 2 * (1 + m_xi[0][i]) / (1 - m_xi[1][i]) - 1.0;
271  m_eta[1][i] = m_xi[1][i];
272  }
273  }
274 }
275 
276 /**
277  * @brief Return the value of the modal functions for the triangular element at
278  * the nodal points #m_xi for a given mode.
279  *
280  * In a triangle, we use the orthogonal basis
281  *
282  * \f[
283  * \psi_{m(ij)} = \sqrt{2} P^{(0,0)}_i(\xi_1) P_j^{(2i+1,0)}(\xi_2) (1-\xi_2)^i
284  * \f]
285  *
286  * where \f$ m(ij) \f$ is the mapping defined in NodalUtilTriangle::m_ordering
287  * and \f$ J_n^{(\alpha,\beta)}(z) \f$ denotes the standard Jacobi polynomial.
288  *
289  * @param mode The mode of the orthogonal basis to evaluate.
290  *
291  * @return Vector containing orthogonal basis evaluated at the points #m_xi.
292  */
294 {
295  std::vector<NekDouble> jacobi_i(m_numPoints), jacobi_j(m_numPoints);
296  std::pair<int, int> modes = m_ordering[mode];
297 
298  // Calculate Jacobi polynomials
299  Polylib::jacobfd(m_numPoints, &m_eta[0][0], &jacobi_i[0], NULL, modes.first,
300  0.0, 0.0);
301  Polylib::jacobfd(m_numPoints, &m_eta[1][0], &jacobi_j[0], NULL,
302  modes.second, 2.0 * modes.first + 1.0, 0.0);
303 
305  NekDouble sqrt2 = sqrt(2.0);
306 
307  for (int i = 0; i < m_numPoints; ++i)
308  {
309  ret[i] = sqrt2 * jacobi_i[i] * jacobi_j[i] *
310  pow(1.0 - m_eta[1][i], modes.first);
311  }
312 
313  return ret;
314 }
315 
316 /**
317  * @brief Return the value of the derivative of the modal functions for the
318  * triangular element at the nodal points #m_xi for a given mode.
319  *
320  * Note that this routine must use the chain rule combined with the collapsed
321  * coordinate derivatives as described in Sherwin & Karniadakis (2nd edition),
322  * pg 150.
323  *
324  * @param dir Coordinate direction in which to evaluate the derivative.
325  * @param mode The mode of the orthogonal basis to evaluate.
326  *
327  * @return Vector containing the derivative of the orthogonal basis evaluated at
328  * the points #m_xi.
329  */
331  const int mode)
332 {
333  std::vector<NekDouble> jacobi_i(m_numPoints), jacobi_j(m_numPoints);
334  std::vector<NekDouble> jacobi_di(m_numPoints), jacobi_dj(m_numPoints);
335  std::pair<int, int> modes = m_ordering[mode];
336 
337  // Calculate Jacobi polynomials and their derivatives. Note that we use both
338  // jacobfd and jacobd since jacobfd is only valid for derivatives in the
339  // open interval (-1,1).
340  Polylib::jacobfd(m_numPoints, &m_eta[0][0], &jacobi_i[0], NULL, modes.first,
341  0.0, 0.0);
342  Polylib::jacobfd(m_numPoints, &m_eta[1][0], &jacobi_j[0], NULL,
343  modes.second, 2.0 * modes.first + 1.0, 0.0);
344  Polylib::jacobd(m_numPoints, &m_eta[0][0], &jacobi_di[0], modes.first, 0.0,
345  0.0);
346  Polylib::jacobd(m_numPoints, &m_eta[1][0], &jacobi_dj[0], modes.second,
347  2.0 * modes.first + 1.0, 0.0);
348 
350  NekDouble sqrt2 = sqrt(2.0);
351 
352  if (dir == 0)
353  {
354  // d/d(\xi_1) = 2/(1-\eta_2) d/d(\eta_1)
355  for (int i = 0; i < m_numPoints; ++i)
356  {
357  ret[i] = 2.0 * sqrt2 * jacobi_di[i] * jacobi_j[i];
358  if (modes.first > 0)
359  {
360  ret[i] *= pow(1.0 - m_eta[1][i], modes.first - 1.0);
361  }
362  }
363  }
364  else
365  {
366  // d/d(\xi_2) = 2(1+\eta_1)/(1-\eta_2) d/d(\eta_1) + d/d(eta_2)
367  for (int i = 0; i < m_numPoints; ++i)
368  {
369  ret[i] = (1 + m_eta[0][i]) * sqrt2 * jacobi_di[i] * jacobi_j[i];
370  if (modes.first > 0)
371  {
372  ret[i] *= pow(1.0 - m_eta[1][i], modes.first - 1.0);
373  }
374 
375  NekDouble tmp = jacobi_dj[i] * pow(1.0 - m_eta[1][i], modes.first);
376  if (modes.first > 0)
377  {
378  tmp -= modes.first * jacobi_j[i] *
379  pow(1.0 - m_eta[1][i], modes.first - 1);
380  }
381 
382  ret[i] += sqrt2 * jacobi_i[i] * tmp;
383  }
384  }
385 
386  return ret;
387 }
388 
389 /**
390  * @brief Construct the nodal utility class for a tetrahedron.
391  *
392  * The constructor of this class sets up two member variables used in the
393  * evaluation of the orthogonal basis:
394  *
395  * - NodalUtilTetrahedron::m_eta is used to construct the collapsed coordinate
396  * locations of the nodal points \f$ (\eta_1, \eta_2, \eta_3) \f$ inside the
397  * cube \f$[-1,1]^3\f$ on which the orthogonal basis functions are defined.
398  * - NodalUtilTetrahedron::m_ordering constructs a mapping from the index set
399  * \f$ I = \{ (i,j,k)\ |\ 0\leq i,j,k \leq P, i+j \leq P, i+j+k \leq P \}\f$
400  * to an ordering \f$ 0 \leq m(ijk) \leq (P+1)(P+2)(P+3)/6 \f$ that defines
401  * the monomials \f$ \xi_1^i \xi_2^j \xi_3^k \f$ that span the tetrahedral
402  * space. This is then used to calculate which \f$ (i,j,k) \f$ triple
403  * (represented as a tuple) corresponding to a column of the Vandermonde
404  * matrix when calculating the orthogonal polynomials.
405  *
406  * @param degree Polynomial order of this nodal tetrahedron
407  * @param r \f$ \xi_1 \f$-coordinates of nodal points in the standard
408  * element.
409  * @param s \f$ \xi_2 \f$-coordinates of nodal points in the standard
410  * element.
411  * @param t \f$ \xi_3 \f$-coordinates of nodal points in the standard
412  * element.
413  */
417  : NodalUtil(degree, 3), m_eta(3)
418 {
419  m_numPoints = r.size();
420  m_xi[0] = r;
421  m_xi[1] = s;
422  m_xi[2] = t;
423 
424  for (int i = 0; i <= m_degree; ++i)
425  {
426  for (int j = 0; j <= m_degree - i; ++j)
427  {
428  for (int k = 0; k <= m_degree - i - j; ++k)
429  {
430  m_ordering.push_back(Mode(i, j, k));
431  }
432  }
433  }
434 
435  // Calculate collapsed coordinates from r/s values
439 
440  for (int i = 0; i < m_numPoints; ++i)
441  {
442  if (fabs(m_xi[2][i] - 1.0) < NekConstants::kNekZeroTol)
443  {
444  // Very top point of the tetrahedron
445  m_eta[0][i] = -1.0;
446  m_eta[1][i] = -1.0;
447  m_eta[2][i] = m_xi[2][i];
448  }
449  else
450  {
451  if (fabs(m_xi[1][i] - 1.0) < NekConstants::kNekZeroTol)
452  {
453  // Distant diagonal edge shared by all eta_x coordinate planes:
454  // the xi_y == -xi_z line
455  m_eta[0][i] = -1.0;
456  }
457  else if (fabs(m_xi[1][i] + m_xi[2][i]) < NekConstants::kNekZeroTol)
458  {
459  m_eta[0][i] = -1.0;
460  }
461  else
462  {
463  m_eta[0][i] =
464  2.0 * (1.0 + m_xi[0][i]) / (-m_xi[1][i] - m_xi[2][i]) - 1.0;
465  }
466  m_eta[1][i] = 2.0 * (1.0 + m_xi[1][i]) / (1.0 - m_xi[2][i]) - 1.0;
467  m_eta[2][i] = m_xi[2][i];
468  }
469  }
470 }
471 
472 /**
473  * @brief Return the value of the modal functions for the tetrahedral element at
474  * the nodal points #m_xi for a given mode.
475  *
476  * In a tetrahedron, we use the orthogonal basis
477  *
478  * \f[ \psi_{m(ijk)} = \sqrt{8} P^{(0,0)}_i(\xi_1) P_j^{(2i+1,0)}(\xi_2)
479  * P_k^{(2i+2j+2,0)}(\xi_3) (1-\xi_2)^i (1-\xi_3)^{i+j} \f]
480  *
481  * where \f$ m(ijk) \f$ is the mapping defined in #m_ordering and \f$
482  * J_n^{(\alpha,\beta)}(z) \f$ denotes the standard Jacobi polynomial.
483  *
484  * @param mode The mode of the orthogonal basis to evaluate.
485  *
486  * @return Vector containing orthogonal basis evaluated at the points #m_xi.
487  */
489 {
490  std::vector<NekDouble> jacA(m_numPoints), jacB(m_numPoints);
491  std::vector<NekDouble> jacC(m_numPoints);
492 
493  int I, J, K;
494  std::tie(I, J, K) = m_ordering[mode];
495 
496  // Calculate Jacobi polynomials
497  Polylib::jacobfd(m_numPoints, &m_eta[0][0], &jacA[0], NULL, I, 0.0, 0.0);
498  Polylib::jacobfd(m_numPoints, &m_eta[1][0], &jacB[0], NULL, J,
499  2.0 * I + 1.0, 0.0);
500  Polylib::jacobfd(m_numPoints, &m_eta[2][0], &jacC[0], NULL, K,
501  2.0 * (I + J) + 2.0, 0.0);
502 
504  NekDouble sqrt8 = sqrt(8.0);
505 
506  for (int i = 0; i < m_numPoints; ++i)
507  {
508  ret[i] = sqrt8 * jacA[i] * jacB[i] * jacC[i] *
509  pow(1.0 - m_eta[1][i], I) * pow(1.0 - m_eta[2][i], I + J);
510  }
511 
512  return ret;
513 }
514 
515 /**
516  * @brief Return the value of the derivative of the modal functions for the
517  * tetrahedral element at the nodal points #m_xi for a given mode.
518  *
519  * Note that this routine must use the chain rule combined with the collapsed
520  * coordinate derivatives as described in Sherwin & Karniadakis (2nd edition),
521  * pg 152.
522  *
523  * @param dir Coordinate direction in which to evaluate the derivative.
524  * @param mode The mode of the orthogonal basis to evaluate.
525  *
526  * @return Vector containing the derivative of the orthogonal basis evaluated at
527  * the points #m_xi.
528  */
530  const int mode)
531 {
532  std::vector<NekDouble> jacA(m_numPoints), jacB(m_numPoints);
533  std::vector<NekDouble> jacC(m_numPoints);
534  std::vector<NekDouble> jacDerivA(m_numPoints), jacDerivB(m_numPoints);
535  std::vector<NekDouble> jacDerivC(m_numPoints);
536 
537  int I, J, K;
538  std::tie(I, J, K) = m_ordering[mode];
539 
540  // Calculate Jacobi polynomials
541  Polylib::jacobfd(m_numPoints, &m_eta[0][0], &jacA[0], NULL, I, 0.0, 0.0);
542  Polylib::jacobfd(m_numPoints, &m_eta[1][0], &jacB[0], NULL, J,
543  2.0 * I + 1.0, 0.0);
544  Polylib::jacobfd(m_numPoints, &m_eta[2][0], &jacC[0], NULL, K,
545  2.0 * (I + J) + 2.0, 0.0);
546  Polylib::jacobd(m_numPoints, &m_eta[0][0], &jacDerivA[0], I, 0.0, 0.0);
547  Polylib::jacobd(m_numPoints, &m_eta[1][0], &jacDerivB[0], J, 2.0 * I + 1.0,
548  0.0);
549  Polylib::jacobd(m_numPoints, &m_eta[2][0], &jacDerivC[0], K,
550  2.0 * (I + J) + 2.0, 0.0);
551 
553  NekDouble sqrt8 = sqrt(8.0);
554 
555  // Always compute x-derivative since this term appears in the latter two
556  // terms.
557  for (int i = 0; i < m_numPoints; ++i)
558  {
559  ret[i] = 4.0 * sqrt8 * jacDerivA[i] * jacB[i] * jacC[i];
560 
561  if (I > 0)
562  {
563  ret[i] *= pow(1 - m_eta[1][i], I - 1);
564  }
565 
566  if (I + J > 0)
567  {
568  ret[i] *= pow(1 - m_eta[2][i], I + J - 1);
569  }
570  }
571 
572  if (dir >= 1)
573  {
574  // Multiply by (1+a)/2
576 
577  for (int i = 0; i < m_numPoints; ++i)
578  {
579  ret[i] *= 0.5 * (m_eta[0][i] + 1.0);
580 
581  tmp[i] = 2.0 * sqrt8 * jacA[i] * jacC[i];
582  if (I + J > 0)
583  {
584  tmp[i] *= pow(1.0 - m_eta[2][i], I + J - 1);
585  }
586 
587  NekDouble tmp2 = jacDerivB[i] * pow(1.0 - m_eta[1][i], I);
588  if (I > 0)
589  {
590  tmp2 -= I * jacB[i] * pow(1.0 - m_eta[1][i], I - 1);
591  }
592 
593  tmp[i] *= tmp2;
594  }
595 
596  if (dir == 1)
597  {
598  ret += tmp;
599  return ret;
600  }
601 
602  for (int i = 0; i < m_numPoints; ++i)
603  {
604  ret[i] += 0.5 * (1.0 + m_eta[1][i]) * tmp[i];
605 
606  NekDouble tmp2 = jacDerivC[i] * pow(1.0 - m_eta[2][i], I + J);
607  if (I + J > 0)
608  {
609  tmp2 -= jacC[i] * (I + J) * pow(1.0 - m_eta[2][i], I + J - 1);
610  }
611 
612  ret[i] +=
613  sqrt8 * jacA[i] * jacB[i] * pow(1.0 - m_eta[1][i], I) * tmp2;
614  }
615  }
616 
617  return ret;
618 }
619 
620 /**
621  * @brief Construct the nodal utility class for a prism.
622  *
623  * The constructor of this class sets up two member variables used in the
624  * evaluation of the orthogonal basis:
625  *
626  * - NodalUtilPrism::m_eta is used to construct the collapsed coordinate
627  * locations of the nodal points \f$ (\eta_1, \eta_2, \eta_3) \f$ inside the
628  * cube \f$[-1,1]^3\f$ on which the orthogonal basis functions are defined.
629  * - NodalUtilPrism::m_ordering constructs a mapping from the index set
630  * \f$ I = \{ (i,j,k)\ |\ 0\leq i,j,k \leq P, i+k \leq P \}\f$ to an ordering
631  * \f$ 0 \leq m(ijk) \leq (P+1)(P+1)(P+2)/2 \f$ that defines the monomials \f$
632  * \xi_1^i \xi_2^j \xi_3^k \f$ that span the prismatic space. This is then
633  * used to calculate which \f$ (i,j,k) \f$ triple (represented as a tuple)
634  * corresponding to a column of the Vandermonde matrix when calculating the
635  * orthogonal polynomials.
636  *
637  * @param degree Polynomial order of this nodal tetrahedron
638  * @param r \f$ \xi_1 \f$-coordinates of nodal points in the standard
639  * element.
640  * @param s \f$ \xi_2 \f$-coordinates of nodal points in the standard
641  * element.
642  * @param t \f$ \xi_3 \f$-coordinates of nodal points in the standard
643  * element.
644  */
648  : NodalUtil(degree, 3), m_eta(3)
649 {
650  m_numPoints = r.size();
651  m_xi[0] = r;
652  m_xi[1] = s;
653  m_xi[2] = t;
654 
655  for (int i = 0; i <= m_degree; ++i)
656  {
657  for (int j = 0; j <= m_degree; ++j)
658  {
659  for (int k = 0; k <= m_degree - i; ++k)
660  {
661  m_ordering.push_back(Mode(i, j, k));
662  }
663  }
664  }
665 
666  // Calculate collapsed coordinates from r/s values
670 
671  for (int i = 0; i < m_numPoints; ++i)
672  {
673  if (fabs(m_xi[2][i] - 1.0) < NekConstants::kNekZeroTol)
674  {
675  // Very top point of the prism
676  m_eta[0][i] = -1.0;
677  m_eta[1][i] = m_xi[1][i];
678  m_eta[2][i] = 1.0;
679  }
680  else
681  {
682  // Third basis function collapsed to "pr" direction instead of "qr"
683  // direction
684  m_eta[0][i] = 2.0 * (1.0 + m_xi[0][i]) / (1.0 - m_xi[2][i]) - 1.0;
685  m_eta[1][i] = m_xi[1][i];
686  m_eta[2][i] = m_xi[2][i];
687  }
688  }
689 }
690 
691 /**
692  * @brief Return the value of the modal functions for the prismatic element at
693  * the nodal points #m_xi for a given mode.
694  *
695  * In a prism, we use the orthogonal basis
696  *
697  * \f[ \psi_{m(ijk)} = \sqrt{2} P^{(0,0)}_i(\xi_1) P_j^{(0,0)}(\xi_2)
698  * P_k^{(2i+1,0)}(\xi_3) (1-\xi_3)^i \f]
699  *
700  * where \f$ m(ijk) \f$ is the mapping defined in #m_ordering and \f$
701  * J_n^{(\alpha,\beta)}(z) \f$ denotes the standard Jacobi polynomial.
702  *
703  * @param mode The mode of the orthogonal basis to evaluate.
704  *
705  * @return Vector containing orthogonal basis evaluated at the points #m_xi.
706  */
708 {
709  std::vector<NekDouble> jacA(m_numPoints), jacB(m_numPoints);
710  std::vector<NekDouble> jacC(m_numPoints);
711 
712  int I, J, K;
713  std::tie(I, J, K) = m_ordering[mode];
714 
715  // Calculate Jacobi polynomials
716  Polylib::jacobfd(m_numPoints, &m_eta[0][0], &jacA[0], NULL, I, 0.0, 0.0);
717  Polylib::jacobfd(m_numPoints, &m_eta[1][0], &jacB[0], NULL, J, 0.0, 0.0);
718  Polylib::jacobfd(m_numPoints, &m_eta[2][0], &jacC[0], NULL, K,
719  2.0 * I + 1.0, 0.0);
720 
722  NekDouble sqrt2 = sqrt(2.0);
723 
724  for (int i = 0; i < m_numPoints; ++i)
725  {
726  ret[i] =
727  sqrt2 * jacA[i] * jacB[i] * jacC[i] * pow(1.0 - m_eta[2][i], I);
728  }
729 
730  return ret;
731 }
732 
733 /**
734  * @brief Return the value of the derivative of the modal functions for the
735  * prismatic element at the nodal points #m_xi for a given mode.
736  *
737  * Note that this routine must use the chain rule combined with the collapsed
738  * coordinate derivatives as described in Sherwin & Karniadakis (2nd edition),
739  * pg 152.
740  *
741  * @param mode The mode of the orthogonal basis to evaluate.
742  * @param dir Coordinate direction in which to evaluate the derivative.
743  *
744  * @return Vector containing the derivative of the orthogonal basis evaluated at
745  * the points #m_xi.
746  */
748  const int mode)
749 {
750  std::vector<NekDouble> jacA(m_numPoints), jacB(m_numPoints);
751  std::vector<NekDouble> jacC(m_numPoints);
752  std::vector<NekDouble> jacDerivA(m_numPoints), jacDerivB(m_numPoints);
753  std::vector<NekDouble> jacDerivC(m_numPoints);
754 
755  int I, J, K;
756  std::tie(I, J, K) = m_ordering[mode];
757 
758  // Calculate Jacobi polynomials
759  Polylib::jacobfd(m_numPoints, &m_eta[0][0], &jacA[0], NULL, I, 0.0, 0.0);
760  Polylib::jacobfd(m_numPoints, &m_eta[1][0], &jacB[0], NULL, J, 0.0, 0.0);
761  Polylib::jacobfd(m_numPoints, &m_eta[2][0], &jacC[0], NULL, K,
762  2.0 * I + 1.0, 0.0);
763  Polylib::jacobd(m_numPoints, &m_eta[0][0], &jacDerivA[0], I, 0.0, 0.0);
764  Polylib::jacobd(m_numPoints, &m_eta[1][0], &jacDerivB[0], J, 0.0, 0.0);
765  Polylib::jacobd(m_numPoints, &m_eta[2][0], &jacDerivC[0], K, 2.0 * I + 1.0,
766  0.0);
767 
769  NekDouble sqrt2 = sqrt(2.0);
770 
771  if (dir == 1)
772  {
773  for (int i = 0; i < m_numPoints; ++i)
774  {
775  ret[i] = sqrt2 * jacA[i] * jacDerivB[i] * jacC[i] *
776  pow(1.0 - m_eta[2][i], I);
777  }
778  }
779  else
780  {
781  for (int i = 0; i < m_numPoints; ++i)
782  {
783  ret[i] = 2.0 * sqrt2 * jacDerivA[i] * jacB[i] * jacC[i];
784 
785  if (I > 0)
786  {
787  ret[i] *= pow(1.0 - m_eta[2][i], I - 1);
788  }
789  }
790 
791  if (dir == 0)
792  {
793  return ret;
794  }
795 
796  for (int i = 0; i < m_numPoints; ++i)
797  {
798  ret[i] *= 0.5 * (1.0 + m_eta[0][i]);
799 
800  NekDouble tmp = jacDerivC[i] * pow(1.0 - m_eta[2][i], I);
801 
802  if (I > 0)
803  {
804  tmp -= jacC[i] * I * pow(1.0 - m_eta[2][i], I - 1);
805  }
806 
807  ret[i] += sqrt2 * jacA[i] * jacB[i] * tmp;
808  }
809  }
810 
811  return ret;
812 }
813 
814 /**
815  * @brief Construct the nodal utility class for a quadrilateral.
816  *
817  * The constructor of this class sets up the #m_ordering member variable used in
818  * the evaluation of the orthogonal basis.
819  *
820  * @param degree Polynomial order of this nodal quad.
821  * @param r \f$ \xi_1 \f$-coordinates of nodal points in the standard
822  * element.
823  * @param s \f$ \xi_2 \f$-coordinates of nodal points in the standard
824  * element.
825  */
828  : NodalUtil(degree, 2)
829 {
830  // Set up parent variables.
831  m_numPoints = r.size();
832  m_xi[0] = r;
833  m_xi[1] = s;
834 
835  // Construct a mapping (i,j) -> m from the tensor product space (i,j) to a
836  // single ordering m.
837  for (int j = 0; j <= m_degree; ++j)
838  {
839  for (int i = 0; i <= m_degree; ++i)
840  {
841  m_ordering.push_back(std::make_pair(i, j));
842  }
843  }
844 }
845 
846 /**
847  * @brief Return the value of the modal functions for the quad element at
848  * the nodal points #m_xi for a given mode.
849  *
850  * In a quad, we use the orthogonal basis
851  *
852  * \f[
853  * \psi_{m(ij)} = P^{(0,0)}_i(\xi_1) P_j^{(0,0)}(\xi_2)
854  * \f]
855  *
856  *
857  * @param mode The mode of the orthogonal basis to evaluate.
858  */
860 {
861  std::vector<NekDouble> jacobi_i(m_numPoints), jacobi_j(m_numPoints);
862  std::pair<int, int> modes = m_ordering[mode];
863 
864  // Calculate Jacobi polynomials
865  Polylib::jacobfd(m_numPoints, &m_xi[0][0], &jacobi_i[0], NULL, modes.first,
866  0.0, 0.0);
867  Polylib::jacobfd(m_numPoints, &m_xi[1][0], &jacobi_j[0], NULL, modes.second,
868  0.0, 0.0);
869 
871 
872  for (int i = 0; i < m_numPoints; ++i)
873  {
874  ret[i] = jacobi_i[i] * jacobi_j[i];
875  }
876 
877  return ret;
878 }
879 
880 /**
881  * @brief Return the value of the derivative of the modal functions for the
882  * quadrilateral element at the nodal points #m_xi for a given mode.
883  *
884  * @param mode The mode of the orthogonal basis to evaluate.
885  */
887  const int mode)
888 {
889  std::vector<NekDouble> jacobi_i(m_numPoints), jacobi_j(m_numPoints);
890  std::vector<NekDouble> jacobi_di(m_numPoints), jacobi_dj(m_numPoints);
891  std::pair<int, int> modes = m_ordering[mode];
892 
893  // Calculate Jacobi polynomials and their derivatives. Note that we use both
894  // jacobfd and jacobd since jacobfd is only valid for derivatives in the
895  // open interval (-1,1).
896  Polylib::jacobfd(m_numPoints, &m_xi[0][0], &jacobi_i[0], NULL, modes.first,
897  0.0, 0.0);
898  Polylib::jacobfd(m_numPoints, &m_xi[1][0], &jacobi_j[0], NULL, modes.second,
899  0.0, 0.0);
900  Polylib::jacobd(m_numPoints, &m_xi[0][0], &jacobi_di[0], modes.first, 0.0,
901  0.0);
902  Polylib::jacobd(m_numPoints, &m_xi[1][0], &jacobi_dj[0], modes.second, 0.0,
903  0.0);
904 
906 
907  if (dir == 0)
908  {
909  for (int i = 0; i < m_numPoints; ++i)
910  {
911  ret[i] = jacobi_di[i] * jacobi_j[i];
912  }
913  }
914  else
915  {
916  for (int i = 0; i < m_numPoints; ++i)
917  {
918  ret[i] = jacobi_i[i] * jacobi_dj[i];
919  }
920  }
921 
922  return ret;
923 }
924 
925 /**
926  * @brief Construct the nodal utility class for a hexahedron.
927  *
928  * The constructor of this class sets up the #m_ordering member variable used in
929  * the evaluation of the orthogonal basis.
930  *
931  * @param degree Polynomial order of this nodal hexahedron.
932  * @param r \f$ \xi_1 \f$-coordinates of nodal points in the standard
933  * element.
934  * @param s \f$ \xi_2 \f$-coordinates of nodal points in the standard
935  * element.
936  */
939  : NodalUtil(degree, 3)
940 {
941  // Set up parent variables.
942  m_numPoints = r.size();
943  m_xi[0] = r;
944  m_xi[1] = s;
945  m_xi[2] = t;
946 
947  // Construct a mapping (i,j,k) -> m from the tensor product space (i,j,k) to
948  // a single ordering m.
949  for (int k = 0; k <= m_degree; ++k)
950  {
951  for (int j = 0; j <= m_degree; ++j)
952  {
953  for (int i = 0; i <= m_degree; ++i)
954  {
955  m_ordering.push_back(Mode(i, j, k));
956  }
957  }
958  }
959 }
960 
961 /**
962  * @brief Return the value of the modal functions for the hex element at
963  * the nodal points #m_xi for a given mode.
964  *
965  * In a quad, we use the orthogonal basis
966  *
967  * \f[
968  * \psi_{m(ijk)} = P^{(0,0)}_i(\xi_1) P_j^{(0,0)}(\xi_2) P_k^{(0,0)}(\xi_3)
969  * \f]
970  *
971  * @param mode The mode of the orthogonal basis to evaluate.
972  */
974 {
975  std::vector<NekDouble> jacobi_i(m_numPoints), jacobi_j(m_numPoints);
976  std::vector<NekDouble> jacobi_k(m_numPoints);
977 
978  int I, J, K;
979  std::tie(I, J, K) = m_ordering[mode];
980 
981  // Calculate Jacobi polynomials
982  Polylib::jacobfd(m_numPoints, &m_xi[0][0], &jacobi_i[0], NULL, I, 0.0, 0.0);
983  Polylib::jacobfd(m_numPoints, &m_xi[1][0], &jacobi_j[0], NULL, J, 0.0, 0.0);
984  Polylib::jacobfd(m_numPoints, &m_xi[2][0], &jacobi_k[0], NULL, K, 0.0, 0.0);
985 
987 
988  for (int i = 0; i < m_numPoints; ++i)
989  {
990  ret[i] = jacobi_i[i] * jacobi_j[i] * jacobi_k[i];
991  }
992 
993  return ret;
994 }
995 
997  const int mode)
998 {
999  std::vector<NekDouble> jacobi_i(m_numPoints), jacobi_j(m_numPoints);
1000  std::vector<NekDouble> jacobi_k(m_numPoints);
1001  std::vector<NekDouble> jacobi_di(m_numPoints), jacobi_dj(m_numPoints);
1002  std::vector<NekDouble> jacobi_dk(m_numPoints);
1003 
1004  int I, J, K;
1005  std::tie(I, J, K) = m_ordering[mode];
1006 
1007  // Calculate Jacobi polynomials and their derivatives. Note that we use both
1008  // jacobfd and jacobd since jacobfd is only valid for derivatives in the
1009  // open interval (-1,1).
1010  Polylib::jacobfd(m_numPoints, &m_xi[0][0], &jacobi_i[0], NULL, I, 0.0, 0.0);
1011  Polylib::jacobfd(m_numPoints, &m_xi[1][0], &jacobi_j[0], NULL, J, 0.0, 0.0);
1012  Polylib::jacobfd(m_numPoints, &m_xi[2][0], &jacobi_k[0], NULL, K, 0.0, 0.0);
1013  Polylib::jacobd(m_numPoints, &m_xi[0][0], &jacobi_di[0], I, 0.0, 0.0);
1014  Polylib::jacobd(m_numPoints, &m_xi[1][0], &jacobi_dj[0], J, 0.0, 0.0);
1015  Polylib::jacobd(m_numPoints, &m_xi[2][0], &jacobi_dk[0], K, 0.0, 0.0);
1016 
1018 
1019  if (dir == 0)
1020  {
1021  for (int i = 0; i < m_numPoints; ++i)
1022  {
1023  ret[i] = jacobi_di[i] * jacobi_j[i] * jacobi_k[i];
1024  }
1025  }
1026  else if (dir == 1)
1027  {
1028  for (int i = 0; i < m_numPoints; ++i)
1029  {
1030  ret[i] = jacobi_dj[i] * jacobi_i[i] * jacobi_k[i];
1031  }
1032  }
1033  else
1034  {
1035  for (int i = 0; i < m_numPoints; ++i)
1036  {
1037  ret[i] = jacobi_i[i] * jacobi_j[i] * jacobi_dk[i];
1038  }
1039  }
1040 
1041  return ret;
1042 }
1043 
1044 } // namespace LibUtilities
1045 } // namespace Nektar
virtual NekVector< NekDouble > v_OrthoBasisDeriv(const int dir, const int mode) override
Return the values of the derivative of the orthogonal basis at the nodal points for a given mode.
Definition: NodalUtil.cpp:996
virtual NekVector< NekDouble > v_OrthoBasis(const int mode) override
Return the value of the modal functions for the hex element at the nodal points m_xi for a given mode...
Definition: NodalUtil.cpp:973
NodalUtilHex(int degree, Array< OneD, NekDouble > r, Array< OneD, NekDouble > s, Array< OneD, NekDouble > t)
Construct the nodal utility class for a hexahedron.
Definition: NodalUtil.cpp:937
std::vector< Mode > m_ordering
Mapping from the indexing of the basis to a continuous ordering.
Definition: NodalUtil.h:365
std::tuple< int, int, int > Mode
Definition: NodalUtil.h:351
A class to assist in the construction of nodal simplex and hybrid elements in two and three dimension...
Definition: NodalUtil.h:83
int m_degree
Degree of the nodal element.
Definition: NodalUtil.h:107
virtual std::shared_ptr< NodalUtil > v_CreateUtil(Array< OneD, Array< OneD, NekDouble >> &xi)=0
Construct a NodalUtil object of the appropriate element type for a given set of points.
int m_numPoints
Total number of nodal points.
Definition: NodalUtil.h:109
Array< OneD, Array< OneD, NekDouble > > m_xi
Coordinates of the nodal points defining the basis.
Definition: NodalUtil.h:111
NekVector< NekDouble > GetWeights()
Obtain the integration weights for the given nodal distribution.
Definition: NodalUtil.cpp:65
SharedMatrix GetVandermonde()
Return the Vandermonde matrix for the nodal distribution.
Definition: NodalUtil.cpp:101
SharedMatrix GetVandermondeForDeriv(int dir)
Return the Vandermonde matrix of the derivative of the basis functions for the nodal distribution.
Definition: NodalUtil.cpp:134
virtual int v_NumModes()=0
Calculate the number of degrees of freedom for this element.
SharedMatrix GetDerivMatrix(int dir)
Return the derivative matrix for the nodal distribution.
Definition: NodalUtil.cpp:167
virtual NekDouble v_ModeZeroIntegral()=0
Return the value of the integral of the zero-th mode for this element.
virtual NekVector< NekDouble > v_OrthoBasisDeriv(const int dir, const int mode)=0
Return the values of the derivative of the orthogonal basis at the nodal points for a given mode.
SharedMatrix GetInterpolationMatrix(Array< OneD, Array< OneD, NekDouble >> &xi)
Construct the interpolation matrix used to evaluate the basis at the points xi inside the element.
Definition: NodalUtil.cpp:201
virtual NekVector< NekDouble > v_OrthoBasis(const int mode)=0
Return the values of the orthogonal basis at the nodal points for a given mode.
NodalUtilPrism(int degree, Array< OneD, NekDouble > r, Array< OneD, NekDouble > s, Array< OneD, NekDouble > t)
Construct the nodal utility class for a prism.
Definition: NodalUtil.cpp:645
virtual NekVector< NekDouble > v_OrthoBasisDeriv(const int dir, const int mode) override
Return the value of the derivative of the modal functions for the prismatic element at the nodal poin...
Definition: NodalUtil.cpp:747
virtual NekVector< NekDouble > v_OrthoBasis(const int mode) override
Return the value of the modal functions for the prismatic element at the nodal points m_xi for a give...
Definition: NodalUtil.cpp:707
Array< OneD, Array< OneD, NekDouble > > m_eta
Collapsed coordinates of the nodal points.
Definition: NodalUtil.h:282
std::vector< Mode > m_ordering
Mapping from the indexing of the basis to a continuous ordering.
Definition: NodalUtil.h:278
std::tuple< int, int, int > Mode
Definition: NodalUtil.h:264
virtual NekVector< NekDouble > v_OrthoBasisDeriv(const int dir, const int mode) override
Return the value of the derivative of the modal functions for the quadrilateral element at the nodal ...
Definition: NodalUtil.cpp:886
NodalUtilQuad(int degree, Array< OneD, NekDouble > r, Array< OneD, NekDouble > s)
Construct the nodal utility class for a quadrilateral.
Definition: NodalUtil.cpp:826
std::vector< std::pair< int, int > > m_ordering
Mapping from the indexing of the basis to a continuous ordering.
Definition: NodalUtil.h:322
virtual NekVector< NekDouble > v_OrthoBasis(const int mode) override
Return the value of the modal functions for the quad element at the nodal points m_xi for a given mod...
Definition: NodalUtil.cpp:859
std::tuple< int, int, int > Mode
Definition: NodalUtil.h:215
virtual NekVector< NekDouble > v_OrthoBasisDeriv(const int dir, const int mode) override
Return the value of the derivative of the modal functions for the tetrahedral element at the nodal po...
Definition: NodalUtil.cpp:529
Array< OneD, Array< OneD, NekDouble > > m_eta
Collapsed coordinates of the nodal points.
Definition: NodalUtil.h:234
NodalUtilTetrahedron(int degree, Array< OneD, NekDouble > r, Array< OneD, NekDouble > s, Array< OneD, NekDouble > t)
Construct the nodal utility class for a tetrahedron.
Definition: NodalUtil.cpp:414
std::vector< Mode > m_ordering
Mapping from the indexing of the basis to a continuous ordering.
Definition: NodalUtil.h:230
virtual NekVector< NekDouble > v_OrthoBasis(const int mode) override
Return the value of the modal functions for the tetrahedral element at the nodal points m_xi for a gi...
Definition: NodalUtil.cpp:488
NodalUtilTriangle(int degree, Array< OneD, NekDouble > r, Array< OneD, NekDouble > s)
Construct the nodal utility class for a triangle.
Definition: NodalUtil.cpp:238
std::vector< std::pair< int, int > > m_ordering
Mapping from the indexing of the basis to a continuous ordering.
Definition: NodalUtil.h:182
virtual NekVector< NekDouble > v_OrthoBasisDeriv(const int dir, const int mode) override
Return the value of the derivative of the modal functions for the triangular element at the nodal poi...
Definition: NodalUtil.cpp:330
virtual NekVector< NekDouble > v_OrthoBasis(const int mode) override
Return the value of the modal functions for the triangular element at the nodal points m_xi for a giv...
Definition: NodalUtil.cpp:293
Array< OneD, Array< OneD, NekDouble > > m_eta
Collapsed coordinates of the nodal points.
Definition: NodalUtil.h:185
RawType_t< VectorType > SolveTranspose(const VectorType &b)
Definition: NekLinSys.hpp:474
General purpose memory allocation routines with the ability to allocate from thread specific memory p...
std::shared_ptr< NekMatrix< NekDouble > > SharedMatrix
Definition: NodalUtil.h:54
static const NekDouble kNekZeroTol
The above copyright notice and this permission notice shall be included.
Definition: CoupledSolver.h:2
double NekDouble
void jacobd(const int np, const double *z, double *polyd, const int n, const double alpha, const double beta)
Calculate the derivative of Jacobi polynomials.
Definition: Polylib.cpp:1293
void jacobfd(const int np, const double *z, double *poly_in, double *polyd, const int n, const double alpha, const double beta)
Routine to calculate Jacobi polynomials, , and their first derivative, .
Definition: Polylib.cpp:1181
scalarT< T > sqrt(scalarT< T > in)
Definition: scalar.hpp:294