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 = MemoryManager<NekMatrix<NekDouble> >::AllocateSharedPtr(
105  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 = MemoryManager<NekMatrix<NekDouble> >::AllocateSharedPtr(
138  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  */
241  : NodalUtil(degree, 2), m_eta(2)
242 {
243  // Set up parent variables.
244  m_numPoints = r.size();
245  m_xi[0] = r;
246  m_xi[1] = s;
247 
248  // Construct a mapping (i,j) -> m from the triangular tensor product space
249  // (i,j) to a single ordering m.
250  for (int i = 0; i <= m_degree; ++i)
251  {
252  for (int j = 0; j <= m_degree - i; ++j)
253  {
254  m_ordering.push_back(std::make_pair(i,j));
255  }
256  }
257 
258  // Calculate collapsed coordinates from r/s values
261 
262  for (int i = 0; i < m_numPoints; ++i)
263  {
264  if (fabs(m_xi[1][i]-1.0) < NekConstants::kNekZeroTol)
265  {
266  m_eta[0][i] = -1.0;
267  m_eta[1][i] = 1.0;
268  }
269  else
270  {
271  m_eta[0][i] = 2*(1+m_xi[0][i])/(1-m_xi[1][i])-1.0;
272  m_eta[1][i] = m_xi[1][i];
273  }
274  }
275 }
276 
277 /**
278  * @brief Return the value of the modal functions for the triangular element at
279  * the nodal points #m_xi for a given mode.
280  *
281  * In a triangle, we use the orthogonal basis
282  *
283  * \f[
284  * \psi_{m(ij)} = \sqrt{2} P^{(0,0)}_i(\xi_1) P_j^{(2i+1,0)}(\xi_2) (1-\xi_2)^i
285  * \f]
286  *
287  * where \f$ m(ij) \f$ is the mapping defined in NodalUtilTriangle::m_ordering
288  * and \f$ J_n^{(\alpha,\beta)}(z) \f$ denotes the standard Jacobi polynomial.
289  *
290  * @param mode The mode of the orthogonal basis to evaluate.
291  *
292  * @return Vector containing orthogonal basis evaluated at the points #m_xi.
293  */
295 {
296  std::vector<NekDouble> jacobi_i(m_numPoints), jacobi_j(m_numPoints);
297  std::pair<int, int> modes = m_ordering[mode];
298 
299  // Calculate Jacobi polynomials
301  m_numPoints, &m_eta[0][0], &jacobi_i[0], NULL, modes.first, 0.0, 0.0);
303  m_numPoints, &m_eta[1][0], &jacobi_j[0], NULL, modes.second,
304  2.0 * modes.first + 1.0, 0.0);
305 
307  NekDouble sqrt2 = sqrt(2.0);
308 
309  for (int i = 0; i < m_numPoints; ++i)
310  {
311  ret[i] = sqrt2 * jacobi_i[i] * jacobi_j[i] *
312  pow(1.0 - m_eta[1][i], modes.first);
313  }
314 
315  return ret;
316 }
317 
318 /**
319  * @brief Return the value of the derivative of the modal functions for the
320  * triangular element at the nodal points #m_xi for a given mode.
321  *
322  * Note that this routine must use the chain rule combined with the collapsed
323  * coordinate derivatives as described in Sherwin & Karniadakis (2nd edition),
324  * pg 150.
325  *
326  * @param dir Coordinate direction in which to evaluate the derivative.
327  * @param mode The mode of the orthogonal basis to evaluate.
328  *
329  * @return Vector containing the derivative of the orthogonal basis evaluated at
330  * the points #m_xi.
331  */
333  const int dir, const int mode)
334 {
335  std::vector<NekDouble> jacobi_i(m_numPoints), jacobi_j(m_numPoints);
336  std::vector<NekDouble> jacobi_di(m_numPoints), jacobi_dj(m_numPoints);
337  std::pair<int, int> modes = m_ordering[mode];
338 
339  // Calculate Jacobi polynomials and their derivatives. Note that we use both
340  // jacobfd and jacobd since jacobfd is only valid for derivatives in the
341  // open interval (-1,1).
343  m_numPoints, &m_eta[0][0], &jacobi_i[0], NULL, modes.first, 0.0,
344  0.0);
346  m_numPoints, &m_eta[1][0], &jacobi_j[0], NULL, modes.second,
347  2.0*modes.first + 1.0, 0.0);
349  m_numPoints, &m_eta[0][0], &jacobi_di[0], modes.first, 0.0, 0.0);
351  m_numPoints, &m_eta[1][0], &jacobi_dj[0], modes.second,
352  2.0*modes.first + 1.0, 0.0);
353 
355  NekDouble sqrt2 = sqrt(2.0);
356 
357  if (dir == 0)
358  {
359  // d/d(\xi_1) = 2/(1-\eta_2) d/d(\eta_1)
360  for (int i = 0; i < m_numPoints; ++i)
361  {
362  ret[i] = 2.0 * sqrt2 * jacobi_di[i] * jacobi_j[i];
363  if (modes.first > 0)
364  {
365  ret[i] *= pow(1.0 - m_eta[1][i], modes.first - 1.0);
366  }
367  }
368  }
369  else
370  {
371  // d/d(\xi_2) = 2(1+\eta_1)/(1-\eta_2) d/d(\eta_1) + d/d(eta_2)
372  for (int i = 0; i < m_numPoints; ++i)
373  {
374  ret[i] = (1 + m_eta[0][i]) * sqrt2 * jacobi_di[i] * jacobi_j[i];
375  if (modes.first > 0)
376  {
377  ret[i] *= pow(1.0 - m_eta[1][i], modes.first - 1.0);
378  }
379 
380  NekDouble tmp = jacobi_dj[i] * pow(1.0 - m_eta[1][i], modes.first);
381  if (modes.first > 0)
382  {
383  tmp -= modes.first * jacobi_j[i] *
384  pow(1.0 - m_eta[1][i], modes.first-1);
385  }
386 
387  ret[i] += sqrt2 * jacobi_i[i] * tmp;
388  }
389  }
390 
391  return ret;
392 }
393 
394 /**
395  * @brief Construct the nodal utility class for a tetrahedron.
396  *
397  * The constructor of this class sets up two member variables used in the
398  * evaluation of the orthogonal basis:
399  *
400  * - NodalUtilTetrahedron::m_eta is used to construct the collapsed coordinate
401  * locations of the nodal points \f$ (\eta_1, \eta_2, \eta_3) \f$ inside the
402  * cube \f$[-1,1]^3\f$ on which the orthogonal basis functions are defined.
403  * - NodalUtilTetrahedron::m_ordering constructs a mapping from the index set
404  * \f$ I = \{ (i,j,k)\ |\ 0\leq i,j,k \leq P, i+j \leq P, i+j+k \leq P \}\f$
405  * to an ordering \f$ 0 \leq m(ijk) \leq (P+1)(P+2)(P+3)/6 \f$ that defines
406  * the monomials \f$ \xi_1^i \xi_2^j \xi_3^k \f$ that span the tetrahedral
407  * space. This is then used to calculate which \f$ (i,j,k) \f$ triple
408  * (represented as a tuple) corresponding to a column of the Vandermonde
409  * matrix when calculating the orthogonal polynomials.
410  *
411  * @param degree Polynomial order of this nodal tetrahedron
412  * @param r \f$ \xi_1 \f$-coordinates of nodal points in the standard
413  * element.
414  * @param s \f$ \xi_2 \f$-coordinates of nodal points in the standard
415  * element.
416  * @param t \f$ \xi_3 \f$-coordinates of nodal points in the standard
417  * element.
418  */
423  : NodalUtil(degree, 3), m_eta(3)
424 {
425  m_numPoints = r.size();
426  m_xi[0] = r;
427  m_xi[1] = s;
428  m_xi[2] = t;
429 
430  for (int i = 0; i <= m_degree; ++i)
431  {
432  for (int j = 0; j <= m_degree - i; ++j)
433  {
434  for (int k = 0; k <= m_degree - i - j; ++k)
435  {
436  m_ordering.push_back(Mode(i, j, k));
437  }
438  }
439  }
440 
441  // Calculate collapsed coordinates from r/s values
445 
446  for (int i = 0; i < m_numPoints; ++i)
447  {
448  if (fabs(m_xi[2][i] - 1.0) < NekConstants::kNekZeroTol)
449  {
450  // Very top point of the tetrahedron
451  m_eta[0][i] = -1.0;
452  m_eta[1][i] = -1.0;
453  m_eta[2][i] = m_xi[2][i];
454  }
455  else
456  {
457  if (fabs(m_xi[1][i] - 1.0) < NekConstants::kNekZeroTol)
458  {
459  // Distant diagonal edge shared by all eta_x coordinate planes:
460  // the xi_y == -xi_z line
461  m_eta[0][i] = -1.0;
462  }
463  else if (fabs(m_xi[1][i] + m_xi[2][i]) < NekConstants::kNekZeroTol)
464  {
465  m_eta[0][i] = -1.0;
466  }
467  else
468  {
469  m_eta[0][i] = 2.0 * (1.0 + m_xi[0][i]) /
470  (-m_xi[1][i] - m_xi[2][i]) - 1.0;
471  }
472  m_eta[1][i] = 2.0 * (1.0 + m_xi[1][i]) / (1.0 - m_xi[2][i]) - 1.0;
473  m_eta[2][i] = m_xi[2][i];
474  }
475  }
476 }
477 
478 /**
479  * @brief Return the value of the modal functions for the tetrahedral element at
480  * the nodal points #m_xi for a given mode.
481  *
482  * In a tetrahedron, we use the orthogonal basis
483  *
484  * \f[ \psi_{m(ijk)} = \sqrt{8} P^{(0,0)}_i(\xi_1) P_j^{(2i+1,0)}(\xi_2)
485  * P_k^{(2i+2j+2,0)}(\xi_3) (1-\xi_2)^i (1-\xi_3)^{i+j} \f]
486  *
487  * where \f$ m(ijk) \f$ is the mapping defined in #m_ordering and \f$
488  * J_n^{(\alpha,\beta)}(z) \f$ denotes the standard Jacobi polynomial.
489  *
490  * @param mode The mode of the orthogonal basis to evaluate.
491  *
492  * @return Vector containing orthogonal basis evaluated at the points #m_xi.
493  */
495 {
496  std::vector<NekDouble> jacA(m_numPoints), jacB(m_numPoints);
497  std::vector<NekDouble> jacC(m_numPoints);
498 
499  int I, J, K;
500  std::tie(I, J, K) = m_ordering[mode];
501 
502  // Calculate Jacobi polynomials
504  m_numPoints, &m_eta[0][0], &jacA[0], NULL, I, 0.0, 0.0);
506  m_numPoints, &m_eta[1][0], &jacB[0], NULL, J, 2.0 * I + 1.0, 0.0);
508  m_numPoints, &m_eta[2][0], &jacC[0], NULL, K, 2.0 * (I+J) + 2.0, 0.0);
509 
511  NekDouble sqrt8 = sqrt(8.0);
512 
513  for (int i = 0; i < m_numPoints; ++i)
514  {
515  ret[i] = sqrt8 * jacA[i] * jacB[i] * jacC[i] *
516  pow(1.0 - m_eta[1][i], I) * pow(1.0 - m_eta[2][i], I + J);
517  }
518 
519  return ret;
520 }
521 
522 /**
523  * @brief Return the value of the derivative of the modal functions for the
524  * tetrahedral element at the nodal points #m_xi for a given mode.
525  *
526  * Note that this routine must use the chain rule combined with the collapsed
527  * coordinate derivatives as described in Sherwin & Karniadakis (2nd edition),
528  * pg 152.
529  *
530  * @param dir Coordinate direction in which to evaluate the derivative.
531  * @param mode The mode of the orthogonal basis to evaluate.
532  *
533  * @return Vector containing the derivative of the orthogonal basis evaluated at
534  * the points #m_xi.
535  */
537  const int dir, const int mode)
538 {
539  std::vector<NekDouble> jacA(m_numPoints), jacB(m_numPoints);
540  std::vector<NekDouble> jacC(m_numPoints);
541  std::vector<NekDouble> jacDerivA(m_numPoints), jacDerivB(m_numPoints);
542  std::vector<NekDouble> jacDerivC(m_numPoints);
543 
544  int I, J, K;
545  std::tie(I, J, K) = m_ordering[mode];
546 
547  // Calculate Jacobi polynomials
549  m_numPoints, &m_eta[0][0], &jacA[0], NULL, I, 0.0, 0.0);
551  m_numPoints, &m_eta[1][0], &jacB[0], NULL, J, 2.0 * I + 1.0, 0.0);
553  m_numPoints, &m_eta[2][0], &jacC[0], NULL, K, 2.0 * (I+J) + 2.0, 0.0);
555  m_numPoints, &m_eta[0][0], &jacDerivA[0], I, 0.0, 0.0);
557  m_numPoints, &m_eta[1][0], &jacDerivB[0], J, 2.0 * I + 1.0, 0.0);
559  m_numPoints, &m_eta[2][0], &jacDerivC[0], K, 2.0 * (I+J) + 2.0, 0.0);
560 
562  NekDouble sqrt8 = sqrt(8.0);
563 
564  // Always compute x-derivative since this term appears in the latter two
565  // terms.
566  for (int i = 0; i < m_numPoints; ++i)
567  {
568  ret[i] = 4.0 * sqrt8 * jacDerivA[i] * jacB[i] * jacC[i];
569 
570  if (I > 0)
571  {
572  ret[i] *= pow(1 - m_eta[1][i], I - 1);
573  }
574 
575  if (I + J > 0)
576  {
577  ret[i] *= pow(1 - m_eta[2][i], I + J - 1);
578  }
579  }
580 
581  if (dir >= 1)
582  {
583  // Multiply by (1+a)/2
585 
586  for (int i = 0; i < m_numPoints; ++i)
587  {
588  ret[i] *= 0.5 * (m_eta[0][i] + 1.0);
589 
590  tmp[i] = 2.0 * sqrt8 * jacA[i] * jacC[i];
591  if (I + J > 0)
592  {
593  tmp[i] *= pow(1.0 - m_eta[2][i], I + J - 1);
594  }
595 
596  NekDouble tmp2 = jacDerivB[i] * pow(1.0 - m_eta[1][i], I);
597  if (I > 0)
598  {
599  tmp2 -= I * jacB[i] * pow(1.0 - m_eta[1][i], I - 1);
600  }
601 
602  tmp[i] *= tmp2;
603  }
604 
605  if (dir == 1)
606  {
607  ret += tmp;
608  return ret;
609  }
610 
611  for (int i = 0; i < m_numPoints; ++i)
612  {
613  ret[i] += 0.5 * (1.0 + m_eta[1][i]) * tmp[i];
614 
615  NekDouble tmp2 = jacDerivC[i] * pow(1.0 - m_eta[2][i], I + J);
616  if (I + J > 0)
617  {
618  tmp2 -= jacC[i] * (I + J) * pow(1.0 - m_eta[2][i], I + J - 1);
619  }
620 
621  ret[i] += sqrt8 * jacA[i] * jacB[i] * pow(1.0 - m_eta[1][i], I) *
622  tmp2;
623  }
624  }
625 
626  return ret;
627 }
628 
629 /**
630  * @brief Construct the nodal utility class for a prism.
631  *
632  * The constructor of this class sets up two member variables used in the
633  * evaluation of the orthogonal basis:
634  *
635  * - NodalUtilPrism::m_eta is used to construct the collapsed coordinate
636  * locations of the nodal points \f$ (\eta_1, \eta_2, \eta_3) \f$ inside the
637  * cube \f$[-1,1]^3\f$ on which the orthogonal basis functions are defined.
638  * - NodalUtilPrism::m_ordering constructs a mapping from the index set
639  * \f$ I = \{ (i,j,k)\ |\ 0\leq i,j,k \leq P, i+k \leq P \}\f$ to an ordering
640  * \f$ 0 \leq m(ijk) \leq (P+1)(P+1)(P+2)/2 \f$ that defines the monomials \f$
641  * \xi_1^i \xi_2^j \xi_3^k \f$ that span the prismatic space. This is then
642  * used to calculate which \f$ (i,j,k) \f$ triple (represented as a tuple)
643  * corresponding to a column of the Vandermonde matrix when calculating the
644  * orthogonal polynomials.
645  *
646  * @param degree Polynomial order of this nodal tetrahedron
647  * @param r \f$ \xi_1 \f$-coordinates of nodal points in the standard
648  * element.
649  * @param s \f$ \xi_2 \f$-coordinates of nodal points in the standard
650  * element.
651  * @param t \f$ \xi_3 \f$-coordinates of nodal points in the standard
652  * element.
653  */
658  : NodalUtil(degree, 3), m_eta(3)
659 {
660  m_numPoints = r.size();
661  m_xi[0] = r;
662  m_xi[1] = s;
663  m_xi[2] = t;
664 
665  for (int i = 0; i <= m_degree; ++i)
666  {
667  for (int j = 0; j <= m_degree; ++j)
668  {
669  for (int k = 0; k <= m_degree - i; ++k)
670  {
671  m_ordering.push_back(Mode(i, j, k));
672  }
673  }
674  }
675 
676  // Calculate collapsed coordinates from r/s values
680 
681  for (int i = 0; i < m_numPoints; ++i)
682  {
683  if (fabs(m_xi[2][i] - 1.0) < NekConstants::kNekZeroTol)
684  {
685  // Very top point of the prism
686  m_eta[0][i] = -1.0;
687  m_eta[1][i] = m_xi[1][i];
688  m_eta[2][i] = 1.0;
689  }
690  else
691  {
692  // Third basis function collapsed to "pr" direction instead of "qr"
693  // direction
694  m_eta[0][i] = 2.0*(1.0 + m_xi[0][i])/(1.0 - m_xi[2][i]) - 1.0;
695  m_eta[1][i] = m_xi[1][i];
696  m_eta[2][i] = m_xi[2][i];
697  }
698  }
699 }
700 
701 /**
702  * @brief Return the value of the modal functions for the prismatic element at
703  * the nodal points #m_xi for a given mode.
704  *
705  * In a prism, we use the orthogonal basis
706  *
707  * \f[ \psi_{m(ijk)} = \sqrt{2} P^{(0,0)}_i(\xi_1) P_j^{(0,0)}(\xi_2)
708  * P_k^{(2i+1,0)}(\xi_3) (1-\xi_3)^i \f]
709  *
710  * where \f$ m(ijk) \f$ is the mapping defined in #m_ordering and \f$
711  * J_n^{(\alpha,\beta)}(z) \f$ denotes the standard Jacobi polynomial.
712  *
713  * @param mode The mode of the orthogonal basis to evaluate.
714  *
715  * @return Vector containing orthogonal basis evaluated at the points #m_xi.
716  */
718 {
719  std::vector<NekDouble> jacA(m_numPoints), jacB(m_numPoints);
720  std::vector<NekDouble> jacC(m_numPoints);
721 
722  int I, J, K;
723  std::tie(I, J, K) = m_ordering[mode];
724 
725  // Calculate Jacobi polynomials
727  m_numPoints, &m_eta[0][0], &jacA[0], NULL, I, 0.0, 0.0);
729  m_numPoints, &m_eta[1][0], &jacB[0], NULL, J, 0.0, 0.0);
731  m_numPoints, &m_eta[2][0], &jacC[0], NULL, K, 2.0 * I + 1.0, 0.0);
732 
734  NekDouble sqrt2 = sqrt(2.0);
735 
736  for (int i = 0; i < m_numPoints; ++i)
737  {
738  ret[i] = sqrt2 * jacA[i] * jacB[i] * jacC[i] *
739  pow(1.0 - m_eta[2][i], I);
740  }
741 
742  return ret;
743 }
744 
745 /**
746  * @brief Return the value of the derivative of the modal functions for the
747  * prismatic element at the nodal points #m_xi for a given mode.
748  *
749  * Note that this routine must use the chain rule combined with the collapsed
750  * coordinate derivatives as described in Sherwin & Karniadakis (2nd edition),
751  * pg 152.
752  *
753  * @param mode The mode of the orthogonal basis to evaluate.
754  * @param dir Coordinate direction in which to evaluate the derivative.
755  *
756  * @return Vector containing the derivative of the orthogonal basis evaluated at
757  * the points #m_xi.
758  */
760  const int dir, const int mode)
761 {
762  std::vector<NekDouble> jacA(m_numPoints), jacB(m_numPoints);
763  std::vector<NekDouble> jacC(m_numPoints);
764  std::vector<NekDouble> jacDerivA(m_numPoints), jacDerivB(m_numPoints);
765  std::vector<NekDouble> jacDerivC(m_numPoints);
766 
767  int I, J, K;
768  std::tie(I, J, K) = m_ordering[mode];
769 
770  // Calculate Jacobi polynomials
772  m_numPoints, &m_eta[0][0], &jacA[0], NULL, I, 0.0, 0.0);
774  m_numPoints, &m_eta[1][0], &jacB[0], NULL, J, 0.0, 0.0);
776  m_numPoints, &m_eta[2][0], &jacC[0], NULL, K, 2.0 * I + 1.0, 0.0);
778  m_numPoints, &m_eta[0][0], &jacDerivA[0], I, 0.0, 0.0);
780  m_numPoints, &m_eta[1][0], &jacDerivB[0], J, 0.0, 0.0);
782  m_numPoints, &m_eta[2][0], &jacDerivC[0], K, 2.0 * I + 1.0, 0.0);
783 
785  NekDouble sqrt2 = sqrt(2.0);
786 
787  if (dir == 1)
788  {
789  for (int i = 0; i < m_numPoints; ++i)
790  {
791  ret[i] = sqrt2 * jacA[i] * jacDerivB[i] * jacC[i] *
792  pow(1.0 - m_eta[2][i], I);
793  }
794  }
795  else
796  {
797  for (int i = 0; i < m_numPoints; ++i)
798  {
799  ret[i] = 2.0 * sqrt2 * jacDerivA[i] * jacB[i] * jacC[i];
800 
801  if (I > 0)
802  {
803  ret[i] *= pow(1.0 - m_eta[2][i], I - 1);
804  }
805  }
806 
807  if (dir == 0)
808  {
809  return ret;
810  }
811 
812  for (int i = 0; i < m_numPoints; ++i)
813  {
814  ret[i] *= 0.5 * (1.0 + m_eta[0][i]);
815 
816  NekDouble tmp = jacDerivC[i] * pow(1.0 - m_eta[2][i], I);
817 
818  if (I > 0)
819  {
820  tmp -= jacC[i] * I * pow(1.0 - m_eta[2][i], I - 1);
821  }
822 
823  ret[i] += sqrt2 * jacA[i] * jacB[i] * tmp;
824  }
825  }
826 
827  return ret;
828 }
829 
830 /**
831  * @brief Construct the nodal utility class for a quadrilateral.
832  *
833  * The constructor of this class sets up the #m_ordering member variable used in
834  * the evaluation of the orthogonal basis.
835  *
836  * @param degree Polynomial order of this nodal quad.
837  * @param r \f$ \xi_1 \f$-coordinates of nodal points in the standard
838  * element.
839  * @param s \f$ \xi_2 \f$-coordinates of nodal points in the standard
840  * element.
841  */
845  : NodalUtil(degree, 2)
846 {
847  // Set up parent variables.
848  m_numPoints = r.size();
849  m_xi[0] = r;
850  m_xi[1] = s;
851 
852  // Construct a mapping (i,j) -> m from the tensor product space (i,j) to a
853  // single ordering m.
854  for (int j = 0; j <= m_degree; ++j)
855  {
856  for (int i = 0; i <= m_degree; ++i)
857  {
858  m_ordering.push_back(std::make_pair(i,j));
859  }
860  }
861 }
862 
863 /**
864  * @brief Return the value of the modal functions for the quad element at
865  * the nodal points #m_xi for a given mode.
866  *
867  * In a quad, we use the orthogonal basis
868  *
869  * \f[
870  * \psi_{m(ij)} = P^{(0,0)}_i(\xi_1) P_j^{(0,0)}(\xi_2)
871  * \f]
872  *
873  *
874  * @param mode The mode of the orthogonal basis to evaluate.
875  */
877 {
878  std::vector<NekDouble> jacobi_i(m_numPoints), jacobi_j(m_numPoints);
879  std::pair<int, int> modes = m_ordering[mode];
880 
881  // Calculate Jacobi polynomials
883  m_numPoints, &m_xi[0][0], &jacobi_i[0], NULL, modes.first, 0.0, 0.0);
885  m_numPoints, &m_xi[1][0], &jacobi_j[0], NULL, modes.second, 0.0, 0.0);
886 
888 
889  for (int i = 0; i < m_numPoints; ++i)
890  {
891  ret[i] = jacobi_i[i] * jacobi_j[i];
892  }
893 
894  return ret;
895 }
896 
897 /**
898  * @brief Return the value of the derivative of the modal functions for the
899  * quadrilateral element at the nodal points #m_xi for a given mode.
900  *
901  * @param mode The mode of the orthogonal basis to evaluate.
902  */
904  const int dir, const int mode)
905 {
906  std::vector<NekDouble> jacobi_i(m_numPoints), jacobi_j(m_numPoints);
907  std::vector<NekDouble> jacobi_di(m_numPoints), jacobi_dj(m_numPoints);
908  std::pair<int, int> modes = m_ordering[mode];
909 
910  // Calculate Jacobi polynomials and their derivatives. Note that we use both
911  // jacobfd and jacobd since jacobfd is only valid for derivatives in the
912  // open interval (-1,1).
914  m_numPoints, &m_xi[0][0], &jacobi_i[0], NULL, modes.first, 0.0, 0.0);
916  m_numPoints, &m_xi[1][0], &jacobi_j[0], NULL, modes.second, 0.0, 0.0);
918  m_numPoints, &m_xi[0][0], &jacobi_di[0], modes.first, 0.0, 0.0);
920  m_numPoints, &m_xi[1][0], &jacobi_dj[0], modes.second, 0.0, 0.0);
921 
923 
924  if (dir == 0)
925  {
926  for (int i = 0; i < m_numPoints; ++i)
927  {
928  ret[i] = jacobi_di[i] * jacobi_j[i];
929  }
930  }
931  else
932  {
933  for (int i = 0; i < m_numPoints; ++i)
934  {
935  ret[i] = jacobi_i[i] * jacobi_dj[i];
936  }
937  }
938 
939  return ret;
940 }
941 
942 
943 /**
944  * @brief Construct the nodal utility class for a hexahedron.
945  *
946  * The constructor of this class sets up the #m_ordering member variable used in
947  * the evaluation of the orthogonal basis.
948  *
949  * @param degree Polynomial order of this nodal hexahedron.
950  * @param r \f$ \xi_1 \f$-coordinates of nodal points in the standard
951  * element.
952  * @param s \f$ \xi_2 \f$-coordinates of nodal points in the standard
953  * element.
954  */
959  : NodalUtil(degree, 3)
960 {
961  // Set up parent variables.
962  m_numPoints = r.size();
963  m_xi[0] = r;
964  m_xi[1] = s;
965  m_xi[2] = t;
966 
967  // Construct a mapping (i,j,k) -> m from the tensor product space (i,j,k) to
968  // a single ordering m.
969  for (int k = 0; k <= m_degree; ++k)
970  {
971  for (int j = 0; j <= m_degree; ++j)
972  {
973  for (int i = 0; i <= m_degree; ++i)
974  {
975  m_ordering.push_back(Mode(i, j, k));
976  }
977  }
978  }
979 }
980 
981 /**
982  * @brief Return the value of the modal functions for the hex element at
983  * the nodal points #m_xi for a given mode.
984  *
985  * In a quad, we use the orthogonal basis
986  *
987  * \f[
988  * \psi_{m(ijk)} = P^{(0,0)}_i(\xi_1) P_j^{(0,0)}(\xi_2) P_k^{(0,0)}(\xi_3)
989  * \f]
990  *
991  * @param mode The mode of the orthogonal basis to evaluate.
992  */
994 {
995  std::vector<NekDouble> jacobi_i(m_numPoints), jacobi_j(m_numPoints);
996  std::vector<NekDouble> jacobi_k(m_numPoints);
997 
998  int I, J, K;
999  std::tie(I, J, K) = m_ordering[mode];
1000 
1001  // Calculate Jacobi polynomials
1003  m_numPoints, &m_xi[0][0], &jacobi_i[0], NULL, I, 0.0, 0.0);
1005  m_numPoints, &m_xi[1][0], &jacobi_j[0], NULL, J, 0.0, 0.0);
1007  m_numPoints, &m_xi[2][0], &jacobi_k[0], NULL, K, 0.0, 0.0);
1008 
1010 
1011  for (int i = 0; i < m_numPoints; ++i)
1012  {
1013  ret[i] = jacobi_i[i] * jacobi_j[i] * jacobi_k[i];
1014  }
1015 
1016  return ret;
1017 }
1018 
1020  const int dir, const int mode)
1021 {
1022  std::vector<NekDouble> jacobi_i(m_numPoints), jacobi_j(m_numPoints);
1023  std::vector<NekDouble> jacobi_k(m_numPoints);
1024  std::vector<NekDouble> jacobi_di(m_numPoints), jacobi_dj(m_numPoints);
1025  std::vector<NekDouble> jacobi_dk(m_numPoints);
1026 
1027  int I, J, K;
1028  std::tie(I, J, K) = m_ordering[mode];
1029 
1030  // Calculate Jacobi polynomials and their derivatives. Note that we use both
1031  // jacobfd and jacobd since jacobfd is only valid for derivatives in the
1032  // open interval (-1,1).
1034  m_numPoints, &m_xi[0][0], &jacobi_i[0], NULL, I, 0.0, 0.0);
1036  m_numPoints, &m_xi[1][0], &jacobi_j[0], NULL, J, 0.0, 0.0);
1038  m_numPoints, &m_xi[2][0], &jacobi_k[0], NULL, K, 0.0, 0.0);
1040  m_numPoints, &m_xi[0][0], &jacobi_di[0], I, 0.0, 0.0);
1042  m_numPoints, &m_xi[1][0], &jacobi_dj[0], J, 0.0, 0.0);
1044  m_numPoints, &m_xi[2][0], &jacobi_dk[0], K, 0.0, 0.0);
1045 
1047 
1048  if (dir == 0)
1049  {
1050  for (int i = 0; i < m_numPoints; ++i)
1051  {
1052  ret[i] = jacobi_di[i] * jacobi_j[i] * jacobi_k[i];
1053  }
1054  }
1055  else if (dir == 1)
1056  {
1057  for (int i = 0; i < m_numPoints; ++i)
1058  {
1059  ret[i] = jacobi_dj[i] * jacobi_i[i] * jacobi_k[i];
1060  }
1061  }
1062  else
1063  {
1064  for (int i = 0; i < m_numPoints; ++i)
1065  {
1066  ret[i] = jacobi_i[i] * jacobi_j[i] * jacobi_dk[i];
1067  }
1068  }
1069 
1070  return ret;
1071 }
1072 
1073 }
1074 }
1075 
virtual NekVector< NekDouble > v_OrthoBasisDeriv(const int dir, const int mode)
Return the values of the derivative of the orthogonal basis at the nodal points for a given mode.
Definition: NodalUtil.cpp:1019
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:955
std::vector< Mode > m_ordering
Mapping from the indexing of the basis to a continuous ordering.
Definition: NodalUtil.h:370
virtual NekVector< NekDouble > v_OrthoBasis(const int mode)
Return the value of the modal functions for the hex element at the nodal points m_xi for a given mode...
Definition: NodalUtil.cpp:993
std::tuple< int, int, int > Mode
Definition: NodalUtil.h:355
A class to assist in the construction of nodal simplex and hybrid elements in two and three dimension...
Definition: NodalUtil.h:84
int m_degree
Degree of the nodal element.
Definition: NodalUtil.h:108
int m_numPoints
Total number of nodal points.
Definition: NodalUtil.h:110
Array< OneD, Array< OneD, NekDouble > > m_xi
Coordinates of the nodal points defining the basis.
Definition: NodalUtil.h:112
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 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.
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:654
virtual NekVector< NekDouble > v_OrthoBasisDeriv(const int dir, const int mode)
Return the value of the derivative of the modal functions for the prismatic element at the nodal poin...
Definition: NodalUtil.cpp:759
Array< OneD, Array< OneD, NekDouble > > m_eta
Collapsed coordinates of the nodal points.
Definition: NodalUtil.h:285
virtual NekVector< NekDouble > v_OrthoBasis(const int mode)
Return the value of the modal functions for the prismatic element at the nodal points m_xi for a give...
Definition: NodalUtil.cpp:717
std::vector< Mode > m_ordering
Mapping from the indexing of the basis to a continuous ordering.
Definition: NodalUtil.h:281
std::tuple< int, int, int > Mode
Definition: NodalUtil.h:266
NodalUtilQuad(int degree, Array< OneD, NekDouble > r, Array< OneD, NekDouble > s)
Construct the nodal utility class for a quadrilateral.
Definition: NodalUtil.cpp:842
virtual NekVector< NekDouble > v_OrthoBasis(const int mode)
Return the value of the modal functions for the quad element at the nodal points m_xi for a given mod...
Definition: NodalUtil.cpp:876
virtual NekVector< NekDouble > v_OrthoBasisDeriv(const int dir, const int mode)
Return the value of the derivative of the modal functions for the quadrilateral element at the nodal ...
Definition: NodalUtil.cpp:903
std::vector< std::pair< int, int > > m_ordering
Mapping from the indexing of the basis to a continuous ordering.
Definition: NodalUtil.h:326
std::tuple< int, int, int > Mode
Definition: NodalUtil.h:217
Array< OneD, Array< OneD, NekDouble > > m_eta
Collapsed coordinates of the nodal points.
Definition: NodalUtil.h:236
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:419
virtual NekVector< NekDouble > v_OrthoBasis(const int mode)
Return the value of the modal functions for the tetrahedral element at the nodal points m_xi for a gi...
Definition: NodalUtil.cpp:494
std::vector< Mode > m_ordering
Mapping from the indexing of the basis to a continuous ordering.
Definition: NodalUtil.h:232
virtual NekVector< NekDouble > v_OrthoBasisDeriv(const int dir, const int mode)
Return the value of the derivative of the modal functions for the tetrahedral element at the nodal po...
Definition: NodalUtil.cpp:536
NodalUtilTriangle(int degree, Array< OneD, NekDouble > r, Array< OneD, NekDouble > s)
Construct the nodal utility class for a triangle.
Definition: NodalUtil.cpp:238
virtual NekVector< NekDouble > v_OrthoBasisDeriv(const int dir, const int mode)
Return the value of the derivative of the modal functions for the triangular element at the nodal poi...
Definition: NodalUtil.cpp:332
std::vector< std::pair< int, int > > m_ordering
Mapping from the indexing of the basis to a continuous ordering.
Definition: NodalUtil.h:184
virtual NekVector< NekDouble > v_OrthoBasis(const int mode)
Return the value of the modal functions for the triangular element at the nodal points m_xi for a giv...
Definition: NodalUtil.cpp:294
Array< OneD, Array< OneD, NekDouble > > m_eta
Collapsed coordinates of the nodal points.
Definition: NodalUtil.h:187
RawType_t< VectorType > SolveTranspose(const VectorType &b)
Definition: NekLinSys.hpp:418
General purpose memory allocation routines with the ability to allocate from thread specific memory p...
std::shared_ptr< NekMatrix< NekDouble > > SharedMatrix
Definition: NodalUtil.h:55
static const NekDouble kNekZeroTol
The above copyright notice and this permission notice shall be included.
Definition: CoupledSolver.h:1
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:1134
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:1034
scalarT< T > sqrt(scalarT< T > in)
Definition: scalar.hpp:267