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