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