Intrepid2
Intrepid2_IntegratedLegendreBasis_HGRAD_TET.hpp
Go to the documentation of this file.
1 // @HEADER
2 // *****************************************************************************
3 // Intrepid2 Package
4 //
5 // Copyright 2007 NTESS and the Intrepid2 contributors.
6 // SPDX-License-Identifier: BSD-3-Clause
7 // *****************************************************************************
8 // @HEADER
9 
15 #ifndef Intrepid2_IntegratedLegendreBasis_HGRAD_TET_h
16 #define Intrepid2_IntegratedLegendreBasis_HGRAD_TET_h
17 
18 #include <Kokkos_DynRankView.hpp>
19 
20 #include <Intrepid2_config.h>
21 
22 #include "Intrepid2_Basis.hpp"
26 #include "Intrepid2_Utils.hpp"
27 
28 namespace Intrepid2
29 {
35  template<class DeviceType, class OutputScalar, class PointScalar,
36  class OutputFieldType, class InputPointsType>
38  {
39  using ExecutionSpace = typename DeviceType::execution_space;
40  using ScratchSpace = typename ExecutionSpace::scratch_memory_space;
41  using OutputScratchView = Kokkos::View<OutputScalar*,ScratchSpace,Kokkos::MemoryTraits<Kokkos::Unmanaged>>;
42  using OutputScratchView2D = Kokkos::View<OutputScalar**,ScratchSpace,Kokkos::MemoryTraits<Kokkos::Unmanaged>>;
43  using PointScratchView = Kokkos::View<PointScalar*, ScratchSpace,Kokkos::MemoryTraits<Kokkos::Unmanaged>>;
44 
45  using TeamPolicy = Kokkos::TeamPolicy<ExecutionSpace>;
46  using TeamMember = typename TeamPolicy::member_type;
47 
48  EOperator opType_;
49 
50  OutputFieldType output_; // F,P
51  InputPointsType inputPoints_; // P,D
52 
53  int polyOrder_;
54  bool defineVertexFunctions_;
55  int numFields_, numPoints_;
56 
57  size_t fad_size_output_;
58 
59  static const int numVertices = 4;
60  static const int numEdges = 6;
61  // the following ordering of the edges matches that used by ESEAS
62  const int edge_start_[numEdges] = {0,1,0,0,1,2}; // edge i is from edge_start_[i] to edge_end_[i]
63  const int edge_end_[numEdges] = {1,2,2,3,3,3}; // edge i is from edge_start_[i] to edge_end_[i]
64 
65  static const int numFaces = 4;
66  const int face_vertex_0[numFaces] = {0,0,1,0}; // faces are abc where 0 ≤ a < b < c ≤ 3
67  const int face_vertex_1[numFaces] = {1,1,2,2};
68  const int face_vertex_2[numFaces] = {2,3,3,3};
69 
70  // this allows us to look up the edge ordinal of the first edge of a face
71  // this is useful because face functions are defined using edge basis functions of the first edge of the face
72  const int face_ordinal_of_first_edge[numFaces] = {0,0,1,2};
73 
74  Hierarchical_HGRAD_TET_Functor(EOperator opType, OutputFieldType output, InputPointsType inputPoints,
75  int polyOrder, bool defineVertexFunctions)
76  : opType_(opType), output_(output), inputPoints_(inputPoints),
77  polyOrder_(polyOrder), defineVertexFunctions_(defineVertexFunctions),
78  fad_size_output_(getScalarDimensionForView(output))
79  {
80  numFields_ = output.extent_int(0);
81  numPoints_ = output.extent_int(1);
82  INTREPID2_TEST_FOR_EXCEPTION(numPoints_ != inputPoints.extent_int(0), std::invalid_argument, "point counts need to match!");
83  INTREPID2_TEST_FOR_EXCEPTION(numFields_ != (polyOrder_+1)*(polyOrder_+2)*(polyOrder_+3)/6, std::invalid_argument, "output field size does not match basis cardinality");
84  }
85 
86  KOKKOS_INLINE_FUNCTION
87  void operator()( const TeamMember & teamMember ) const
88  {
89  const int numFaceBasisFunctionsPerFace = (polyOrder_-2) * (polyOrder_-1) / 2;
90  const int numInteriorBasisFunctions = (polyOrder_-3) * (polyOrder_-2) * (polyOrder_-1) / 6;
91 
92  auto pointOrdinal = teamMember.league_rank();
93  OutputScratchView legendre_values1_at_point, legendre_values2_at_point;
94  OutputScratchView2D jacobi_values1_at_point, jacobi_values2_at_point, jacobi_values3_at_point;
95  const int numAlphaValues = (polyOrder_-1 > 1) ? (polyOrder_-1) : 1; // make numAlphaValues at least 1 so we can avoid zero-extent allocations…
96  if (fad_size_output_ > 0) {
97  legendre_values1_at_point = OutputScratchView(teamMember.team_shmem(), polyOrder_ + 1, fad_size_output_);
98  legendre_values2_at_point = OutputScratchView(teamMember.team_shmem(), polyOrder_ + 1, fad_size_output_);
99  jacobi_values1_at_point = OutputScratchView2D(teamMember.team_shmem(), numAlphaValues, polyOrder_ + 1, fad_size_output_);
100  jacobi_values2_at_point = OutputScratchView2D(teamMember.team_shmem(), numAlphaValues, polyOrder_ + 1, fad_size_output_);
101  jacobi_values3_at_point = OutputScratchView2D(teamMember.team_shmem(), numAlphaValues, polyOrder_ + 1, fad_size_output_);
102  }
103  else {
104  legendre_values1_at_point = OutputScratchView(teamMember.team_shmem(), polyOrder_ + 1);
105  legendre_values2_at_point = OutputScratchView(teamMember.team_shmem(), polyOrder_ + 1);
106  jacobi_values1_at_point = OutputScratchView2D(teamMember.team_shmem(), numAlphaValues, polyOrder_ + 1);
107  jacobi_values2_at_point = OutputScratchView2D(teamMember.team_shmem(), numAlphaValues, polyOrder_ + 1);
108  jacobi_values3_at_point = OutputScratchView2D(teamMember.team_shmem(), numAlphaValues, polyOrder_ + 1);
109  }
110 
111  const auto & x = inputPoints_(pointOrdinal,0);
112  const auto & y = inputPoints_(pointOrdinal,1);
113  const auto & z = inputPoints_(pointOrdinal,2);
114 
115  // write as barycentric coordinates:
116  const PointScalar lambda[numVertices] = {1. - x - y - z, x, y, z};
117  const PointScalar lambda_dx[numVertices] = {-1., 1., 0., 0.};
118  const PointScalar lambda_dy[numVertices] = {-1., 0., 1., 0.};
119  const PointScalar lambda_dz[numVertices] = {-1., 0., 0., 1.};
120 
121  const int num1DEdgeFunctions = polyOrder_ - 1;
122 
123  switch (opType_)
124  {
125  case OPERATOR_VALUE:
126  {
127  // vertex functions come first, according to vertex ordering: (0,0,0), (1,0,0), (0,1,0), (0,0,1)
128  for (int vertexOrdinal=0; vertexOrdinal<numVertices; vertexOrdinal++)
129  {
130  output_(vertexOrdinal,pointOrdinal) = lambda[vertexOrdinal];
131  }
132  if (!defineVertexFunctions_)
133  {
134  // "DG" basis case
135  // here, we overwrite the first vertex function with 1:
136  output_(0,pointOrdinal) = 1.0;
137  }
138 
139  // edge functions
140  int fieldOrdinalOffset = numVertices;
141  for (int edgeOrdinal=0; edgeOrdinal<numEdges; edgeOrdinal++)
142  {
143  const auto & s0 = lambda[edge_start_[edgeOrdinal]];
144  const auto & s1 = lambda[ edge_end_[edgeOrdinal]];
145 
146  Polynomials::shiftedScaledIntegratedLegendreValues(legendre_values1_at_point, polyOrder_, PointScalar(s1), PointScalar(s0+s1));
147  for (int edgeFunctionOrdinal=0; edgeFunctionOrdinal<num1DEdgeFunctions; edgeFunctionOrdinal++)
148  {
149  // the first two integrated legendre functions are essentially the vertex functions; hence the +2 on on the RHS here:
150  output_(edgeFunctionOrdinal+fieldOrdinalOffset,pointOrdinal) = legendre_values1_at_point(edgeFunctionOrdinal+2);
151  }
152  fieldOrdinalOffset += num1DEdgeFunctions;
153  }
154  /*
155  Face functions for face abc are the product of edge functions on their ab edge
156  and a Jacobi polynomial [L^2i_j](s0+s1,s2) = L^2i_j(s2;s0+s1+s2)
157  */
158  for (int faceOrdinal=0; faceOrdinal<numFaces; faceOrdinal++)
159  {
160  const auto & s0 = lambda[face_vertex_0[faceOrdinal]];
161  const auto & s1 = lambda[face_vertex_1[faceOrdinal]];
162  const auto & s2 = lambda[face_vertex_2[faceOrdinal]];
163  const PointScalar jacobiScaling = s0 + s1 + s2;
164 
165  // compute integrated Jacobi values for each desired value of alpha
166  for (int n=2; n<=polyOrder_; n++)
167  {
168  const double alpha = n*2;
169  const int alphaOrdinal = n-2;
170  using Kokkos::subview;
171  using Kokkos::ALL;
172  auto jacobi_alpha = subview(jacobi_values1_at_point, alphaOrdinal, ALL);
173  Polynomials::shiftedScaledIntegratedJacobiValues(jacobi_alpha, alpha, polyOrder_-2, s2, jacobiScaling);
174  }
175 
176  const int edgeOrdinal = face_ordinal_of_first_edge[faceOrdinal];
177  int localFaceBasisOrdinal = 0;
178  for (int totalPolyOrder=3; totalPolyOrder<=polyOrder_; totalPolyOrder++)
179  {
180  for (int i=2; i<totalPolyOrder; i++)
181  {
182  const int edgeBasisOrdinal = edgeOrdinal*num1DEdgeFunctions + i-2 + numVertices;
183  const auto & edgeValue = output_(edgeBasisOrdinal,pointOrdinal);
184  const int alphaOrdinal = i-2;
185 
186  const int j = totalPolyOrder - i;
187  const auto & jacobiValue = jacobi_values1_at_point(alphaOrdinal,j);
188  const int fieldOrdinal = fieldOrdinalOffset + localFaceBasisOrdinal;
189  output_(fieldOrdinal,pointOrdinal) = edgeValue * jacobiValue;
190 
191  localFaceBasisOrdinal++;
192  }
193  }
194  fieldOrdinalOffset += numFaceBasisFunctionsPerFace;
195  }
196  // interior functions
197  // compute integrated Jacobi values for each desired value of alpha
198  for (int n=3; n<=polyOrder_; n++)
199  {
200  const double alpha = n*2;
201  const double jacobiScaling = 1.0;
202  const int alphaOrdinal = n-3;
203  using Kokkos::subview;
204  using Kokkos::ALL;
205  auto jacobi_alpha = subview(jacobi_values1_at_point, alphaOrdinal, ALL);
206  Polynomials::shiftedScaledIntegratedJacobiValues(jacobi_alpha, alpha, polyOrder_-3, lambda[3], jacobiScaling);
207  }
208  const int min_i = 2;
209  const int min_j = 1;
210  const int min_k = 1;
211  const int min_ij = min_i + min_j;
212  const int min_ijk = min_ij + min_k;
213  int localInteriorBasisOrdinal = 0;
214  for (int totalPolyOrder_ijk=min_ijk; totalPolyOrder_ijk <= polyOrder_; totalPolyOrder_ijk++)
215  {
216  int localFaceBasisOrdinal = 0;
217  for (int totalPolyOrder_ij=min_ij; totalPolyOrder_ij <= totalPolyOrder_ijk-min_j; totalPolyOrder_ij++)
218  {
219  for (int i=2; i <= totalPolyOrder_ij-min_j; i++)
220  {
221  const int j = totalPolyOrder_ij - i;
222  const int k = totalPolyOrder_ijk - totalPolyOrder_ij;
223  const int faceBasisOrdinal = numEdges*num1DEdgeFunctions + numVertices + localFaceBasisOrdinal;
224  const auto & faceValue = output_(faceBasisOrdinal,pointOrdinal);
225  const int alphaOrdinal = (i+j)-3;
226  localFaceBasisOrdinal++;
227 
228  const int fieldOrdinal = fieldOrdinalOffset + localInteriorBasisOrdinal;
229  const auto & jacobiValue = jacobi_values1_at_point(alphaOrdinal,k);
230  output_(fieldOrdinal,pointOrdinal) = faceValue * jacobiValue;
231  localInteriorBasisOrdinal++;
232  } // end i loop
233  } // end totalPolyOrder_ij loop
234  } // end totalPolyOrder_ijk loop
235  fieldOrdinalOffset += numInteriorBasisFunctions;
236  } // end OPERATOR_VALUE
237  break;
238  case OPERATOR_GRAD:
239  case OPERATOR_D1:
240  {
241  // vertex functions
242  if (defineVertexFunctions_)
243  {
244  // standard, "CG" basis case
245  // first vertex function is 1-x-y-z
246  output_(0,pointOrdinal,0) = -1.0;
247  output_(0,pointOrdinal,1) = -1.0;
248  output_(0,pointOrdinal,2) = -1.0;
249  }
250  else
251  {
252  // "DG" basis case
253  // here, the first "vertex" function is 1, so the derivative is 0:
254  output_(0,pointOrdinal,0) = 0.0;
255  output_(0,pointOrdinal,1) = 0.0;
256  output_(0,pointOrdinal,2) = 0.0;
257  }
258  // second vertex function is x
259  output_(1,pointOrdinal,0) = 1.0;
260  output_(1,pointOrdinal,1) = 0.0;
261  output_(1,pointOrdinal,2) = 0.0;
262  // third vertex function is y
263  output_(2,pointOrdinal,0) = 0.0;
264  output_(2,pointOrdinal,1) = 1.0;
265  output_(2,pointOrdinal,2) = 0.0;
266  // fourth vertex function is z
267  output_(3,pointOrdinal,0) = 0.0;
268  output_(3,pointOrdinal,1) = 0.0;
269  output_(3,pointOrdinal,2) = 1.0;
270 
271  // edge functions
272  int fieldOrdinalOffset = numVertices;
273  /*
274  Per Fuentes et al. (see Appendix E.1, E.2), the edge functions, defined for i ≥ 2, are
275  [L_i](s0,s1) = L_i(s1; s0+s1)
276  and have gradients:
277  grad [L_i](s0,s1) = [P_{i-1}](s0,s1) grad s1 + [R_{i-1}](s0,s1) grad (s0 + s1)
278  where
279  [R_{i-1}](s0,s1) = R_{i-1}(s1; s0+s1) = d/dt L_{i}(s0; s0+s1)
280  The P_i we have implemented in shiftedScaledLegendreValues, while d/dt L_{i+1} is
281  implemented in shiftedScaledIntegratedLegendreValues_dt.
282  */
283  // rename the scratch memory to match our usage here:
284  auto & P_i_minus_1 = legendre_values1_at_point;
285  auto & L_i_dt = legendre_values2_at_point;
286  for (int edgeOrdinal=0; edgeOrdinal<numEdges; edgeOrdinal++)
287  {
288  const auto & s0 = lambda[edge_start_[edgeOrdinal]];
289  const auto & s1 = lambda[ edge_end_[edgeOrdinal]];
290 
291  const auto & s0_dx = lambda_dx[edge_start_[edgeOrdinal]];
292  const auto & s0_dy = lambda_dy[edge_start_[edgeOrdinal]];
293  const auto & s0_dz = lambda_dz[edge_start_[edgeOrdinal]];
294  const auto & s1_dx = lambda_dx[ edge_end_[edgeOrdinal]];
295  const auto & s1_dy = lambda_dy[ edge_end_[edgeOrdinal]];
296  const auto & s1_dz = lambda_dz[ edge_end_[edgeOrdinal]];
297 
298  Polynomials::shiftedScaledLegendreValues (P_i_minus_1, polyOrder_-1, PointScalar(s1), PointScalar(s0+s1));
299  Polynomials::shiftedScaledIntegratedLegendreValues_dt(L_i_dt, polyOrder_, PointScalar(s1), PointScalar(s0+s1));
300  for (int edgeFunctionOrdinal=0; edgeFunctionOrdinal<num1DEdgeFunctions; edgeFunctionOrdinal++)
301  {
302  // the first two (integrated) Legendre functions are essentially the vertex functions; hence the +2 here:
303  const int i = edgeFunctionOrdinal+2;
304  output_(edgeFunctionOrdinal+fieldOrdinalOffset,pointOrdinal,0) = P_i_minus_1(i-1) * s1_dx + L_i_dt(i) * (s1_dx + s0_dx);
305  output_(edgeFunctionOrdinal+fieldOrdinalOffset,pointOrdinal,1) = P_i_minus_1(i-1) * s1_dy + L_i_dt(i) * (s1_dy + s0_dy);
306  output_(edgeFunctionOrdinal+fieldOrdinalOffset,pointOrdinal,2) = P_i_minus_1(i-1) * s1_dz + L_i_dt(i) * (s1_dz + s0_dz);
307  }
308  fieldOrdinalOffset += num1DEdgeFunctions;
309  }
310 
311  /*
312  Fuentes et al give the face functions as phi_{ij}, with gradient:
313  grad phi_{ij}(s0,s1,s2) = [L^{2i}_j](s0+s1,s2) grad [L_i](s0,s1) + [L_i](s0,s1) grad [L^{2i}_j](s0+s1,s2)
314  where:
315  - grad [L_i](s0,s1) is the edge function gradient we computed above
316  - [L_i](s0,s1) is the edge function which we have implemented above (in OPERATOR_VALUE)
317  - L^{2i}_j is a Jacobi polynomial with:
318  [L^{2i}_j](s0,s1) = L^{2i}_j(s1;s0+s1)
319  and the gradient for j ≥ 1 is
320  grad [L^{2i}_j](s0,s1) = [P^{2i}_{j-1}](s0,s1) grad s1 + [R^{2i}_{j-1}(s0,s1)] grad (s0 + s1)
321  Here,
322  [P^{2i}_{j-1}](s0,s1) = P^{2i}_{j-1}(s1,s0+s1)
323  and
324  [R^{2i}_{j-1}(s0,s1)] = d/dt L^{2i}_j(s1,s0+s1)
325  We have implemented P^{alpha}_{j} as shiftedScaledJacobiValues,
326  and d/dt L^{alpha}_{j} as shiftedScaledIntegratedJacobiValues_dt.
327  */
328  // rename the scratch memory to match our usage here:
329  auto & L_i = legendre_values2_at_point;
330  auto & L_2i_j_dt = jacobi_values1_at_point;
331  auto & L_2i_j = jacobi_values2_at_point;
332  auto & P_2i_j_minus_1 = jacobi_values3_at_point;
333 
334  for (int faceOrdinal=0; faceOrdinal<numFaces; faceOrdinal++)
335  {
336  const auto & s0 = lambda[face_vertex_0[faceOrdinal]];
337  const auto & s1 = lambda[face_vertex_1[faceOrdinal]];
338  const auto & s2 = lambda[face_vertex_2[faceOrdinal]];
339  Polynomials::shiftedScaledIntegratedLegendreValues(L_i, polyOrder_, s1, s0+s1);
340 
341  const PointScalar jacobiScaling = s0 + s1 + s2;
342 
343  // compute integrated Jacobi values for each desired value of alpha
344  for (int n=2; n<=polyOrder_; n++)
345  {
346  const double alpha = n*2;
347  const int alphaOrdinal = n-2;
348  using Kokkos::subview;
349  using Kokkos::ALL;
350  auto L_2i_j_dt_alpha = subview(L_2i_j_dt, alphaOrdinal, ALL);
351  auto L_2i_j_alpha = subview(L_2i_j, alphaOrdinal, ALL);
352  auto P_2i_j_minus_1_alpha = subview(P_2i_j_minus_1, alphaOrdinal, ALL);
353  Polynomials::shiftedScaledIntegratedJacobiValues_dt(L_2i_j_dt_alpha, alpha, polyOrder_-2, s2, jacobiScaling);
354  Polynomials::shiftedScaledIntegratedJacobiValues (L_2i_j_alpha, alpha, polyOrder_-2, s2, jacobiScaling);
355  Polynomials::shiftedScaledJacobiValues(P_2i_j_minus_1_alpha, alpha, polyOrder_-1, s2, jacobiScaling);
356  }
357 
358  const int edgeOrdinal = face_ordinal_of_first_edge[faceOrdinal];
359  int localFaceOrdinal = 0;
360  for (int totalPolyOrder=3; totalPolyOrder<=polyOrder_; totalPolyOrder++)
361  {
362  for (int i=2; i<totalPolyOrder; i++)
363  {
364  const int edgeBasisOrdinal = edgeOrdinal*num1DEdgeFunctions + i-2 + numVertices;
365  const auto & grad_L_i_dx = output_(edgeBasisOrdinal,pointOrdinal,0);
366  const auto & grad_L_i_dy = output_(edgeBasisOrdinal,pointOrdinal,1);
367  const auto & grad_L_i_dz = output_(edgeBasisOrdinal,pointOrdinal,2);
368 
369  const int alphaOrdinal = i-2;
370 
371  const auto & s0_dx = lambda_dx[face_vertex_0[faceOrdinal]];
372  const auto & s0_dy = lambda_dy[face_vertex_0[faceOrdinal]];
373  const auto & s0_dz = lambda_dz[face_vertex_0[faceOrdinal]];
374  const auto & s1_dx = lambda_dx[face_vertex_1[faceOrdinal]];
375  const auto & s1_dy = lambda_dy[face_vertex_1[faceOrdinal]];
376  const auto & s1_dz = lambda_dz[face_vertex_1[faceOrdinal]];
377  const auto & s2_dx = lambda_dx[face_vertex_2[faceOrdinal]];
378  const auto & s2_dy = lambda_dy[face_vertex_2[faceOrdinal]];
379  const auto & s2_dz = lambda_dz[face_vertex_2[faceOrdinal]];
380 
381  int j = totalPolyOrder - i;
382 
383  // put references to the entries of interest in like-named variables with lowercase first letters
384  auto & l_2i_j = L_2i_j(alphaOrdinal,j);
385  auto & l_i = L_i(i);
386  auto & l_2i_j_dt = L_2i_j_dt(alphaOrdinal,j);
387  auto & p_2i_j_minus_1 = P_2i_j_minus_1(alphaOrdinal,j-1);
388 
389  const OutputScalar basisValue_dx = l_2i_j * grad_L_i_dx + l_i * (p_2i_j_minus_1 * s2_dx + l_2i_j_dt * (s0_dx + s1_dx + s2_dx));
390  const OutputScalar basisValue_dy = l_2i_j * grad_L_i_dy + l_i * (p_2i_j_minus_1 * s2_dy + l_2i_j_dt * (s0_dy + s1_dy + s2_dy));
391  const OutputScalar basisValue_dz = l_2i_j * grad_L_i_dz + l_i * (p_2i_j_minus_1 * s2_dz + l_2i_j_dt * (s0_dz + s1_dz + s2_dz));
392 
393  const int fieldOrdinal = fieldOrdinalOffset + localFaceOrdinal;
394 
395  output_(fieldOrdinal,pointOrdinal,0) = basisValue_dx;
396  output_(fieldOrdinal,pointOrdinal,1) = basisValue_dy;
397  output_(fieldOrdinal,pointOrdinal,2) = basisValue_dz;
398 
399  localFaceOrdinal++;
400  }
401  }
402  fieldOrdinalOffset += numFaceBasisFunctionsPerFace;
403  }
404  // interior functions
405  /*
406  Per Fuentes et al. (see Appendix E.1, E.2), the interior functions, defined for i ≥ 2, are
407  phi_ij(lambda_012) [L^{2(i+j)}_k](1-lambda_3,lambda_3) = phi_ij(lambda_012) L^{2(i+j)}_k (lambda_3; 1)
408  and have gradients:
409  L^{2(i+j)}_k (lambda_3; 1) grad (phi_ij(lambda_012)) + phi_ij(lambda_012) grad (L^{2(i+j)}_k (lambda_3; 1))
410  where:
411  - phi_ij(lambda_012) is the (i,j) basis function on face 012,
412  - L^{alpha}_j(t0; t1) is the jth integrated Jacobi polynomial
413  and the gradient of the integrated Jacobi polynomial can be computed:
414  - grad L^{alpha}_j(t0; t1) = P^{alpha}_{j-1} (t0;t1) grad t0 + R^{alpha}_{j-1}(t0,t1) grad t1
415  Here, t1=1, so this simplifies to:
416  - grad L^{alpha}_j(t0; t1) = P^{alpha}_{j-1} (t0;t1) grad t0
417 
418  The P_i we have implemented in shiftedScaledJacobiValues.
419  */
420  // rename the scratch memory to match our usage here:
421  auto & L_alpha = jacobi_values1_at_point;
422  auto & P_alpha = jacobi_values2_at_point;
423 
424  // precompute values used in face ordinal 0:
425  {
426  const auto & s0 = lambda[0];
427  const auto & s1 = lambda[1];
428  const auto & s2 = lambda[2];
429  // Legendre:
430  Polynomials::shiftedScaledIntegratedLegendreValues(legendre_values1_at_point, polyOrder_, PointScalar(s1), PointScalar(s0+s1));
431 
432  // Jacobi for each desired alpha value:
433  const PointScalar jacobiScaling = s0 + s1 + s2;
434  for (int n=2; n<=polyOrder_; n++)
435  {
436  const double alpha = n*2;
437  const int alphaOrdinal = n-2;
438  using Kokkos::subview;
439  using Kokkos::ALL;
440  auto jacobi_alpha = subview(jacobi_values3_at_point, alphaOrdinal, ALL);
441  Polynomials::shiftedScaledIntegratedJacobiValues(jacobi_alpha, alpha, polyOrder_-2, s2, jacobiScaling);
442  }
443  }
444 
445  // interior
446  for (int n=3; n<=polyOrder_; n++)
447  {
448  const double alpha = n*2;
449  const double jacobiScaling = 1.0;
450  const int alphaOrdinal = n-3;
451  using Kokkos::subview;
452  using Kokkos::ALL;
453 
454  // values for interior functions:
455  auto L = subview(L_alpha, alphaOrdinal, ALL);
456  auto P = subview(P_alpha, alphaOrdinal, ALL);
457  Polynomials::shiftedScaledIntegratedJacobiValues(L, alpha, polyOrder_-3, lambda[3], jacobiScaling);
458  Polynomials::shiftedScaledJacobiValues (P, alpha, polyOrder_-3, lambda[3], jacobiScaling);
459  }
460 
461  const int min_i = 2;
462  const int min_j = 1;
463  const int min_k = 1;
464  const int min_ij = min_i + min_j;
465  const int min_ijk = min_ij + min_k;
466  int localInteriorBasisOrdinal = 0;
467  for (int totalPolyOrder_ijk=min_ijk; totalPolyOrder_ijk <= polyOrder_; totalPolyOrder_ijk++)
468  {
469  int localFaceBasisOrdinal = 0;
470  for (int totalPolyOrder_ij=min_ij; totalPolyOrder_ij <= totalPolyOrder_ijk-min_j; totalPolyOrder_ij++)
471  {
472  for (int i=2; i <= totalPolyOrder_ij-min_j; i++)
473  {
474  const int j = totalPolyOrder_ij - i;
475  const int k = totalPolyOrder_ijk - totalPolyOrder_ij;
476  // interior functions use basis values belonging to the first face, 012
477  const int faceBasisOrdinal = numEdges*num1DEdgeFunctions + numVertices + localFaceBasisOrdinal;
478 
479  const auto & faceValue_dx = output_(faceBasisOrdinal,pointOrdinal,0);
480  const auto & faceValue_dy = output_(faceBasisOrdinal,pointOrdinal,1);
481  const auto & faceValue_dz = output_(faceBasisOrdinal,pointOrdinal,2);
482 
483  // determine faceValue (on face 0)
484  OutputScalar faceValue;
485  {
486  const auto & edgeValue = legendre_values1_at_point(i);
487  const int alphaOrdinal = i-2;
488  const auto & jacobiValue = jacobi_values3_at_point(alphaOrdinal,j);
489  faceValue = edgeValue * jacobiValue;
490  }
491  localFaceBasisOrdinal++;
492 
493  const int alphaOrdinal = (i+j)-3;
494 
495  const int fieldOrdinal = fieldOrdinalOffset + localInteriorBasisOrdinal;
496  const auto & integratedJacobiValue = L_alpha(alphaOrdinal,k);
497  const auto & jacobiValue = P_alpha(alphaOrdinal,k-1);
498  output_(fieldOrdinal,pointOrdinal,0) = integratedJacobiValue * faceValue_dx + faceValue * jacobiValue * lambda_dx[3];
499  output_(fieldOrdinal,pointOrdinal,1) = integratedJacobiValue * faceValue_dy + faceValue * jacobiValue * lambda_dy[3];
500  output_(fieldOrdinal,pointOrdinal,2) = integratedJacobiValue * faceValue_dz + faceValue * jacobiValue * lambda_dz[3];
501 
502  localInteriorBasisOrdinal++;
503  } // end i loop
504  } // end totalPolyOrder_ij loop
505  } // end totalPolyOrder_ijk loop
506  fieldOrdinalOffset += numInteriorBasisFunctions;
507  }
508  break;
509  case OPERATOR_D2:
510  case OPERATOR_D3:
511  case OPERATOR_D4:
512  case OPERATOR_D5:
513  case OPERATOR_D6:
514  case OPERATOR_D7:
515  case OPERATOR_D8:
516  case OPERATOR_D9:
517  case OPERATOR_D10:
518  INTREPID2_TEST_FOR_ABORT( true,
519  ">>> ERROR: (Intrepid2::Basis_HGRAD_TET_Cn_FEM_ORTH::OrthPolynomialTri) Computing of second and higher-order derivatives is not currently supported");
520  default:
521  // unsupported operator type
522  device_assert(false);
523  }
524  }
525 
526  // Provide the shared memory capacity.
527  // This function takes the team_size as an argument,
528  // which allows team_size-dependent allocations.
529  size_t team_shmem_size (int team_size) const
530  {
531  // we will use shared memory to create a fast buffer for basis computations
532  // for the (integrated) Legendre computations, we just need p+1 values stored
533  // for the (integrated) Jacobi computations, though, we want (p+1)*(# alpha values)
534  // alpha is either 2i or 2(i+j), where i=2,…,p or i+j=3,…,p. So there are at most (p-1) alpha values needed.
535  // We can have up to 3 of the (integrated) Jacobi values needed at once.
536  const int numAlphaValues = std::max(polyOrder_-1, 1); // make it at least 1 so we can avoid zero-extent ranks…
537  size_t shmem_size = 0;
538  if (fad_size_output_ > 0)
539  {
540  // Legendre:
541  shmem_size += 2 * OutputScratchView::shmem_size(polyOrder_ + 1, fad_size_output_);
542  // Jacobi:
543  shmem_size += 3 * OutputScratchView2D::shmem_size(numAlphaValues, polyOrder_ + 1, fad_size_output_);
544  }
545  else
546  {
547  // Legendre:
548  shmem_size += 2 * OutputScratchView::shmem_size(polyOrder_ + 1);
549  // Jacobi:
550  shmem_size += 3 * OutputScratchView2D::shmem_size(numAlphaValues, polyOrder_ + 1);
551  }
552 
553  return shmem_size;
554  }
555  };
556 
574  template<typename DeviceType,
575  typename OutputScalar = double,
576  typename PointScalar = double,
577  bool defineVertexFunctions = true> // if defineVertexFunctions is true, first four basis functions are 1-x-y-z, x, y, and z. Otherwise, they are 1, x, y, and z.
579  : public Basis<DeviceType,OutputScalar,PointScalar>
580  {
581  public:
583 
584  using typename BasisBase::OrdinalTypeArray1DHost;
585  using typename BasisBase::OrdinalTypeArray2DHost;
586 
587  using typename BasisBase::OutputViewType;
588  using typename BasisBase::PointViewType;
589  using typename BasisBase::ScalarViewType;
590 
591  using typename BasisBase::ExecutionSpace;
592 
593  protected:
594  int polyOrder_; // the maximum order of the polynomial
595  EPointType pointType_;
596  public:
607  IntegratedLegendreBasis_HGRAD_TET(int polyOrder, const EPointType pointType=POINTTYPE_DEFAULT)
608  :
609  polyOrder_(polyOrder),
610  pointType_(pointType)
611  {
612  INTREPID2_TEST_FOR_EXCEPTION(pointType!=POINTTYPE_DEFAULT,std::invalid_argument,"PointType not supported");
613  this->basisCardinality_ = ((polyOrder+1) * (polyOrder+2) * (polyOrder+3)) / 6;
614  this->basisDegree_ = polyOrder;
615  this->basisCellTopologyKey_ = shards::Tetrahedron<>::key;
616  this->basisType_ = BASIS_FEM_HIERARCHICAL;
617  this->basisCoordinates_ = COORDINATES_CARTESIAN;
618  this->functionSpace_ = FUNCTION_SPACE_HGRAD;
619 
620  const int degreeLength = 1;
621  this->fieldOrdinalPolynomialDegree_ = OrdinalTypeArray2DHost("Integrated Legendre H(grad) tetrahedron polynomial degree lookup", this->basisCardinality_, degreeLength);
622  this->fieldOrdinalH1PolynomialDegree_ = OrdinalTypeArray2DHost("Integrated Legendre H(grad) tetrahedron polynomial H^1 degree lookup", this->basisCardinality_, degreeLength);
623 
624  int fieldOrdinalOffset = 0;
625  // **** vertex functions **** //
626  const int numVertices = this->getBaseCellTopology().getVertexCount();
627  const int numFunctionsPerVertex = 1;
628  const int numVertexFunctions = numVertices * numFunctionsPerVertex;
629  for (int i=0; i<numVertexFunctions; i++)
630  {
631  // for H(grad) on tetrahedron, if defineVertexFunctions is false, first four basis members are linear
632  // if not, then the only difference is that the first member is constant
633  this->fieldOrdinalPolynomialDegree_ (i,0) = 1;
634  this->fieldOrdinalH1PolynomialDegree_(i,0) = 1;
635  }
636  if (!defineVertexFunctions)
637  {
638  this->fieldOrdinalPolynomialDegree_ (0,0) = 0;
639  this->fieldOrdinalH1PolynomialDegree_(0,0) = 0;
640  }
641  fieldOrdinalOffset += numVertexFunctions;
642 
643  // **** edge functions **** //
644  const int numFunctionsPerEdge = polyOrder - 1; // bubble functions: all but the vertices
645  const int numEdges = this->getBaseCellTopology().getEdgeCount();
646  for (int edgeOrdinal=0; edgeOrdinal<numEdges; edgeOrdinal++)
647  {
648  for (int i=0; i<numFunctionsPerEdge; i++)
649  {
650  this->fieldOrdinalPolynomialDegree_(i+fieldOrdinalOffset,0) = i+2; // vertex functions are 1st order; edge functions start at order 2
651  this->fieldOrdinalH1PolynomialDegree_(i+fieldOrdinalOffset,0) = i+2; // vertex functions are 1st order; edge functions start at order 2
652  }
653  fieldOrdinalOffset += numFunctionsPerEdge;
654  }
655 
656  // **** face functions **** //
657  const int numFunctionsPerFace = ((polyOrder-1)*(polyOrder-2))/2;
658  const int numFaces = 4;
659  for (int faceOrdinal=0; faceOrdinal<numFaces; faceOrdinal++)
660  {
661  for (int totalPolyOrder=3; totalPolyOrder<=polyOrder_; totalPolyOrder++)
662  {
663  const int totalFaceDofs = (totalPolyOrder-2)*(totalPolyOrder-1)/2;
664  const int totalFaceDofsPrevious = (totalPolyOrder-3)*(totalPolyOrder-2)/2;
665  const int faceDofsForPolyOrder = totalFaceDofs - totalFaceDofsPrevious;
666  for (int i=0; i<faceDofsForPolyOrder; i++)
667  {
668  this->fieldOrdinalPolynomialDegree_ (fieldOrdinalOffset,0) = totalPolyOrder;
669  this->fieldOrdinalH1PolynomialDegree_(fieldOrdinalOffset,0) = totalPolyOrder;
670  fieldOrdinalOffset++;
671  }
672  }
673  }
674 
675  // **** interior functions **** //
676  const int numFunctionsPerVolume = ((polyOrder-1)*(polyOrder-2)*(polyOrder-3))/6;
677  const int numVolumes = 1; // interior
678  for (int volumeOrdinal=0; volumeOrdinal<numVolumes; volumeOrdinal++)
679  {
680  for (int totalPolyOrder=4; totalPolyOrder<=polyOrder_; totalPolyOrder++)
681  {
682  const int totalInteriorDofs = (totalPolyOrder-3)*(totalPolyOrder-2)*(totalPolyOrder-1)/6;
683  const int totalInteriorDofsPrevious = (totalPolyOrder-4)*(totalPolyOrder-3)*(totalPolyOrder-2)/6;
684  const int interiorDofsForPolyOrder = totalInteriorDofs - totalInteriorDofsPrevious;
685 
686  for (int i=0; i<interiorDofsForPolyOrder; i++)
687  {
688  this->fieldOrdinalPolynomialDegree_ (fieldOrdinalOffset,0) = totalPolyOrder;
689  this->fieldOrdinalH1PolynomialDegree_(fieldOrdinalOffset,0) = totalPolyOrder;
690  fieldOrdinalOffset++;
691  }
692  }
693  }
694 
695  INTREPID2_TEST_FOR_EXCEPTION(fieldOrdinalOffset != this->basisCardinality_, std::invalid_argument, "Internal error: basis enumeration is incorrect");
696 
697  // initialize tags
698  {
699  // ESEAS numbers tetrahedron faces differently from Intrepid2
700  // ESEAS: 012, 013, 123, 023
701  // Intrepid2: 013, 123, 032, 021
702  const int intrepid2FaceOrdinals[4] {3,0,1,2}; // index is the ESEAS face ordinal; value is the intrepid2 ordinal
703 
704  const auto & cardinality = this->basisCardinality_;
705 
706  // Basis-dependent initializations
707  const ordinal_type tagSize = 4; // size of DoF tag, i.e., number of fields in the tag
708  const ordinal_type posScDim = 0; // position in the tag, counting from 0, of the subcell dim
709  const ordinal_type posScOrd = 1; // position in the tag, counting from 0, of the subcell ordinal
710  const ordinal_type posDfOrd = 2; // position in the tag, counting from 0, of DoF ordinal relative to the subcell
711 
712  OrdinalTypeArray1DHost tagView("tag view", cardinality*tagSize);
713  const int vertexDim = 0, edgeDim = 1, faceDim = 2, volumeDim = 3;
714 
715  if (defineVertexFunctions) {
716  {
717  int tagNumber = 0;
718  for (int vertexOrdinal=0; vertexOrdinal<numVertices; vertexOrdinal++)
719  {
720  for (int functionOrdinal=0; functionOrdinal<numFunctionsPerVertex; functionOrdinal++)
721  {
722  tagView(tagNumber*tagSize+0) = vertexDim; // vertex dimension
723  tagView(tagNumber*tagSize+1) = vertexOrdinal; // vertex id
724  tagView(tagNumber*tagSize+2) = functionOrdinal; // local dof id
725  tagView(tagNumber*tagSize+3) = numFunctionsPerVertex; // total number of dofs in this vertex
726  tagNumber++;
727  }
728  }
729  for (int edgeOrdinal=0; edgeOrdinal<numEdges; edgeOrdinal++)
730  {
731  for (int functionOrdinal=0; functionOrdinal<numFunctionsPerEdge; functionOrdinal++)
732  {
733  tagView(tagNumber*tagSize+0) = edgeDim; // edge dimension
734  tagView(tagNumber*tagSize+1) = edgeOrdinal; // edge id
735  tagView(tagNumber*tagSize+2) = functionOrdinal; // local dof id
736  tagView(tagNumber*tagSize+3) = numFunctionsPerEdge; // total number of dofs on this edge
737  tagNumber++;
738  }
739  }
740  for (int faceOrdinalESEAS=0; faceOrdinalESEAS<numFaces; faceOrdinalESEAS++)
741  {
742  int faceOrdinalIntrepid2 = intrepid2FaceOrdinals[faceOrdinalESEAS];
743  for (int functionOrdinal=0; functionOrdinal<numFunctionsPerFace; functionOrdinal++)
744  {
745  tagView(tagNumber*tagSize+0) = faceDim; // face dimension
746  tagView(tagNumber*tagSize+1) = faceOrdinalIntrepid2; // face id
747  tagView(tagNumber*tagSize+2) = functionOrdinal; // local dof id
748  tagView(tagNumber*tagSize+3) = numFunctionsPerFace; // total number of dofs on this face
749  tagNumber++;
750  }
751  }
752  for (int volumeOrdinal=0; volumeOrdinal<numVolumes; volumeOrdinal++)
753  {
754  for (int functionOrdinal=0; functionOrdinal<numFunctionsPerVolume; functionOrdinal++)
755  {
756  tagView(tagNumber*tagSize+0) = volumeDim; // volume dimension
757  tagView(tagNumber*tagSize+1) = volumeOrdinal; // volume id
758  tagView(tagNumber*tagSize+2) = functionOrdinal; // local dof id
759  tagView(tagNumber*tagSize+3) = numFunctionsPerVolume; // total number of dofs in this volume
760  tagNumber++;
761  }
762  }
763  INTREPID2_TEST_FOR_EXCEPTION(tagNumber != this->basisCardinality_, std::invalid_argument, "Internal error: basis tag enumeration is incorrect");
764  }
765  } else {
766  for (ordinal_type i=0;i<cardinality;++i) {
767  tagView(i*tagSize+0) = volumeDim; // volume dimension
768  tagView(i*tagSize+1) = 0; // volume ordinal
769  tagView(i*tagSize+2) = i; // local dof id
770  tagView(i*tagSize+3) = cardinality; // total number of dofs on this face
771  }
772  }
773 
774  // Basis-independent function sets tag and enum data in tagToOrdinal_ and ordinalToTag_ arrays:
775  // tags are constructed on host
776  this->setOrdinalTagData(this->tagToOrdinal_,
777  this->ordinalToTag_,
778  tagView,
779  this->basisCardinality_,
780  tagSize,
781  posScDim,
782  posScOrd,
783  posDfOrd);
784  }
785  }
786 
791  const char* getName() const override {
792  return "Intrepid2_IntegratedLegendreBasis_HGRAD_TET";
793  }
794 
797  virtual bool requireOrientation() const override {
798  return (this->getDegree() > 2);
799  }
800 
801  // since the getValues() below only overrides the FEM variant, we specify that
802  // we use the base class's getValues(), which implements the FVD variant by throwing an exception.
803  // (It's an error to use the FVD variant on this basis.)
804  using BasisBase::getValues;
805 
824  virtual void getValues( OutputViewType outputValues, const PointViewType inputPoints,
825  const EOperator operatorType = OPERATOR_VALUE ) const override
826  {
827  auto numPoints = inputPoints.extent_int(0);
828 
830 
831  FunctorType functor(operatorType, outputValues, inputPoints, polyOrder_, defineVertexFunctions);
832 
833  const int outputVectorSize = getVectorSizeForHierarchicalParallelism<OutputScalar>();
834  const int pointVectorSize = getVectorSizeForHierarchicalParallelism<PointScalar>();
835  const int vectorSize = std::max(outputVectorSize,pointVectorSize);
836  const int teamSize = 1; // because of the way the basis functions are computed, we don't have a second level of parallelism...
837 
838  auto policy = Kokkos::TeamPolicy<ExecutionSpace>(numPoints,teamSize,vectorSize);
839  Kokkos::parallel_for("Hierarchical_HGRAD_TET_Functor", policy, functor);
840  }
841 
850  BasisPtr<DeviceType,OutputScalar,PointScalar>
851  getSubCellRefBasis(const ordinal_type subCellDim, const ordinal_type subCellOrd) const override{
852  if(subCellDim == 1) {
853  return Teuchos::rcp(new
855  (this->basisDegree_));
856  } else if(subCellDim == 2) {
857  return Teuchos::rcp(new
859  (this->basisDegree_));
860  }
861  INTREPID2_TEST_FOR_EXCEPTION(true,std::invalid_argument,"Input parameters out of bounds");
862  }
863 
868  virtual BasisPtr<typename Kokkos::HostSpace::device_type, OutputScalar, PointScalar>
869  getHostBasis() const override {
870  using HostDeviceType = typename Kokkos::HostSpace::device_type;
872  return Teuchos::rcp( new HostBasisType(polyOrder_, pointType_) );
873  }
874  };
875 } // end namespace Intrepid2
876 
877 #endif /* Intrepid2_IntegratedLegendreBasis_HGRAD_TET_h */
H(grad) basis on the line based on integrated Legendre polynomials.
Kokkos::View< ordinal_type *, typename ExecutionSpace::array_layout, Kokkos::HostSpace > OrdinalTypeArray1DHost
View type for 1d host array.
unsigned basisCellTopologyKey_
Identifier of the base topology of the cells for which the basis is defined. See the Shards package f...
Basis defining integrated Legendre basis on the line, a polynomial subspace of H(grad) on the line...
IntegratedLegendreBasis_HGRAD_TET(int polyOrder, const EPointType pointType=POINTTYPE_DEFAULT)
Constructor.
An abstract base class that defines interface for concrete basis implementations for Finite Element (...
shards::CellTopology getBaseCellTopology() const
Returns the base cell topology for which the basis is defined. See Shards documentation https://trili...
BasisPtr< DeviceType, OutputScalar, PointScalar > getSubCellRefBasis(const ordinal_type subCellDim, const ordinal_type subCellOrd) const override
returns the basis associated to a subCell.
Kokkos::DynRankView< scalarType, Kokkos::LayoutStride, DeviceType > ScalarViewType
View type for scalars.
virtual BasisPtr< typename Kokkos::HostSpace::device_type, OutputScalar, PointScalar > getHostBasis() const override
Creates and returns a Basis object whose DeviceType template argument is Kokkos::HostSpace::device_ty...
Free functions, callable from device code, that implement various polynomials useful in basis definit...
Kokkos::DynRankView< OutputValueType, Kokkos::LayoutStride, DeviceType > OutputViewType
View type for basis value output.
EFunctionSpace functionSpace_
The function space in which the basis is defined.
ordinal_type basisDegree_
Degree of the largest complete polynomial space that can be represented by the basis.
virtual KOKKOS_INLINE_FUNCTION void getValues(OutputViewType, const PointViewType, const EOperator, const typename Kokkos::TeamPolicy< ExecutionSpace >::member_type &teamMember, const typename ExecutionSpace::scratch_memory_space &scratchStorage, const ordinal_type subcellDim=-1, const ordinal_type subcellOrdinal=-1) const
Team-level evaluation of basis functions on a reference cell.
Header function for Intrepid2::Util class and other utility functions.
Kokkos::DynRankView< PointValueType, Kokkos::LayoutStride, DeviceType > PointViewType
View type for input points.
Kokkos::View< ordinal_type **, typename ExecutionSpace::array_layout, Kokkos::HostSpace > OrdinalTypeArray2DHost
View type for 2d host array.
Kokkos::DynRankView< OutputValueType, Kokkos::LayoutStride, DeviceType > OutputViewType
View type for basis value output.
Basis defining integrated Legendre basis on the line, a polynomial subspace of H(grad) on the line: e...
Basis defining integrated Legendre basis on the line, a polynomial subspace of H(grad) on the line...
Functor for computing values for the IntegratedLegendreBasis_HGRAD_TET class.
ordinal_type basisCardinality_
Cardinality of the basis, i.e., the number of basis functions/degrees-of-freedom. ...
virtual bool requireOrientation() const override
True if orientation is required.
H(grad) basis on the triangle based on integrated Legendre polynomials.
OrdinalTypeArray3DHost tagToOrdinal_
DoF tag to ordinal lookup table.
Kokkos::View< ordinal_type *, typename ExecutionSpace::array_layout, Kokkos::HostSpace > OrdinalTypeArray1DHost
View type for 1d host array.
OrdinalTypeArray2DHost ordinalToTag_
&quot;true&quot; if tagToOrdinal_ and ordinalToTag_ have been initialized
ordinal_type getDegree() const
Returns the degree of the basis.
ECoordinates basisCoordinates_
The coordinate system for which the basis is defined.
OrdinalTypeArray2DHost fieldOrdinalH1PolynomialDegree_
H^1 polynomial degree for each degree of freedom. Only defined for hierarchical bases right now...
OrdinalTypeArray2DHost fieldOrdinalPolynomialDegree_
Polynomial degree for each degree of freedom. Only defined for hierarchical bases right now...
void setOrdinalTagData(OrdinalTypeView3D &tagToOrdinal, OrdinalTypeView2D &ordinalToTag, const OrdinalTypeView1D tags, const ordinal_type basisCard, const ordinal_type tagSize, const ordinal_type posScDim, const ordinal_type posScOrd, const ordinal_type posDfOrd)
Fills ordinalToTag_ and tagToOrdinal_ by basis-specific tag data.
Kokkos::DynRankView< PointValueType, Kokkos::LayoutStride, DeviceType > PointViewType
View type for input points.
virtual void getValues(OutputViewType outputValues, const PointViewType inputPoints, const EOperator operatorType=OPERATOR_VALUE) const override
Evaluation of a FEM basis on a reference cell.
Header file for the abstract base class Intrepid2::Basis.
typename DeviceType::execution_space ExecutionSpace
(Kokkos) Execution space for basis.