Line data Source code
1 : #include "NedelecSpace.h"
2 : namespace ippl {
3 :
4 : // NedelecSpace constructor, which calls the FiniteElementSpace constructor,
5 : // and decomposes the elements among ranks according to layout.
6 : template <typename T, unsigned Dim, unsigned Order, typename ElementType,
7 : typename QuadratureType, typename FieldType>
8 160 : NedelecSpace<T, Dim, Order, ElementType, QuadratureType, FieldType>
9 : ::NedelecSpace(UniformCartesian<T, Dim>& mesh, ElementType& ref_element,
10 : const QuadratureType& quadrature, const Layout_t& layout)
11 : : FiniteElementSpace<T, Dim, getNedelecNumElementDOFs(Dim, Order),
12 : ElementType, QuadratureType, FEMVector<T>, FEMVector<T>>
13 160 : (mesh, ref_element, quadrature) {
14 : // Assert that the dimension is either 2 or 3.
15 : static_assert(Dim >= 2 && Dim <= 3,
16 : "The Nedelec Finite Element space only supports 2D and3D meshes");
17 :
18 : // Initialize the elementIndices view
19 160 : initializeElementIndices(layout);
20 160 : }
21 :
22 : // NedelecSpace constructor, which calls the FiniteElementSpace constructor.
23 : template <typename T, unsigned Dim, unsigned Order, typename ElementType,
24 : typename QuadratureType, typename FieldType>
25 : NedelecSpace<T, Dim, Order, ElementType, QuadratureType, FieldType>
26 : ::NedelecSpace(UniformCartesian<T, Dim>& mesh, ElementType& ref_element,
27 : const QuadratureType& quadrature)
28 : : FiniteElementSpace<T, Dim, getNedelecNumElementDOFs(Dim, Order),
29 : ElementType, QuadratureType, FEMVector<T>, FEMVector<T>>
30 : (mesh, ref_element, quadrature) {
31 :
32 : // Assert that the dimension is either 2 or 3.
33 : static_assert(Dim >= 2 && Dim <= 3,
34 : "The Nedelec Finite Element space only supports 2D and 3D meshes");
35 : }
36 :
37 : // NedelecSpace initializer, to be made available to the FEMPoissonSolver
38 : // such that we can call it from setRhs.
39 : // Sets the correct mesh and decomposes the elements among ranks according to layout.
40 : template <typename T, unsigned Dim, unsigned Order, typename ElementType,
41 : typename QuadratureType, typename FieldType>
42 : void NedelecSpace<T, Dim, Order, ElementType, QuadratureType, FieldType>
43 : ::initialize(UniformCartesian<T, Dim>& mesh, const Layout_t& layout) {
44 :
45 : FiniteElementSpace<T, Dim, getNedelecNumElementDOFs(Dim, Order),
46 : ElementType, QuadratureType, FEMVector<T>, FEMVector<T>>::setMesh(mesh);
47 :
48 : // Initialize the elementIndices view
49 : initializeElementIndices(layout);
50 :
51 : // set the local DOF position vector
52 : localDofPositions_m(0)(0) = 0.5;
53 : localDofPositions_m(1)(1) = 0.5;
54 : localDofPositions_m(2)(0) = 0.5; localDofPositions_m(2)(1) = 1;
55 : localDofPositions_m(3)(0) = 1; localDofPositions_m(3)(1) = 0.5;
56 : localDofPositions_m(4)(2) = 0.5;
57 : localDofPositions_m(5)(0) = 1; localDofPositions_m(5)(2) = 0.5;
58 : localDofPositions_m(6)(0) = 1; localDofPositions_m(6)(1) = 1;
59 : localDofPositions_m(6)(2) = 0.5;
60 : localDofPositions_m(7)(1) = 1; localDofPositions_m(7)(2) = 0.5;
61 : localDofPositions_m(8)(0) = 0.5; localDofPositions_m(8)(2) = 1;
62 : localDofPositions_m(9)(1) = 0.5; localDofPositions_m(9)(2) = 1;
63 : localDofPositions_m(10)(0) = 0.5; localDofPositions_m(10)(1) = 1;
64 : localDofPositions_m(10)(2) = 1;
65 : localDofPositions_m(11)(0) = 1; localDofPositions_m(11)(1) = 0.5;
66 : localDofPositions_m(11)(2) = 1;
67 : }
68 :
69 : // Initialize element indices Kokkos View
70 : template <typename T, unsigned Dim, unsigned Order, typename ElementType,
71 : typename QuadratureType, typename FieldType>
72 160 : void NedelecSpace<T, Dim, Order, ElementType, QuadratureType, FieldType>
73 : ::initializeElementIndices(const Layout_t& layout) {
74 :
75 160 : layout_m = layout;
76 160 : const auto& ldom = layout.getLocalNDIndex();
77 160 : int npoints = ldom.size();
78 160 : auto first = ldom.first();
79 160 : auto last = ldom.last();
80 160 : ippl::Vector<double, Dim> bounds;
81 :
82 560 : for (size_t d = 0; d < Dim; ++d) {
83 400 : bounds[d] = this->nr_m[d] - 1;
84 : }
85 :
86 160 : int upperBoundaryPoints = -1;
87 :
88 160 : Kokkos::View<size_t*> points("ComputeMapping", npoints);
89 160 : Kokkos::parallel_reduce(
90 0 : "ComputePoints", npoints,
91 4040 : KOKKOS_CLASS_LAMBDA(const int i, int& local) {
92 3720 : int idx = i;
93 3720 : indices_t val;
94 3720 : bool isBoundary = false;
95 14200 : for (unsigned int d = 0; d < Dim; ++d) {
96 10480 : int range = last[d] - first[d] + 1;
97 10480 : val[d] = first[d] + (idx % range);
98 10480 : idx /= range;
99 10480 : if (val[d] == bounds[d]) {
100 2360 : isBoundary = true;
101 : }
102 : }
103 3720 : points(i) = (!isBoundary) * (this->getElementIndex(val));
104 3720 : local += isBoundary;
105 3720 : },
106 320 : Kokkos::Sum<int>(upperBoundaryPoints));
107 160 : Kokkos::fence();
108 :
109 160 : int elementsPerRank = npoints - upperBoundaryPoints;
110 160 : elementIndices = Kokkos::View<size_t*>("i", elementsPerRank);
111 160 : Kokkos::View<size_t> index("index");
112 :
113 160 : Kokkos::parallel_for(
114 7760 : "RemoveNaNs", npoints, KOKKOS_CLASS_LAMBDA(const int i) {
115 7440 : if ((points(i) != 0) || (i == 0)) {
116 3680 : const size_t idx = Kokkos::atomic_fetch_add(&index(), 1);
117 5520 : elementIndices(idx) = points(i);
118 : }
119 : }
120 : );
121 160 : }
122 :
123 : ///////////////////////////////////////////////////////////////////////
124 : /// Degree of Freedom operations //////////////////////////////////////
125 : ///////////////////////////////////////////////////////////////////////
126 :
127 : template <typename T, unsigned Dim, unsigned Order, typename ElementType,
128 : typename QuadratureType, typename FieldType>
129 8 : KOKKOS_FUNCTION size_t NedelecSpace<T, Dim, Order, ElementType, QuadratureType, FieldType>
130 : ::numGlobalDOFs() const {
131 :
132 8 : size_t num_global_dofs = 0;
133 :
134 28 : for (size_t d = 0; d < Dim; ++d) {
135 20 : size_t accu = this->nr_m[d]-1;
136 72 : for (size_t d2 = 0; d2 < Dim; ++d2) {
137 52 : if (d == d2) continue;
138 32 : accu *= this->nr_m[d2];
139 : }
140 20 : num_global_dofs += accu;
141 : }
142 :
143 8 : return num_global_dofs;
144 : }
145 :
146 : template <typename T, unsigned Dim, unsigned Order, typename ElementType,
147 : typename QuadratureType, typename FieldType>
148 0 : KOKKOS_FUNCTION size_t NedelecSpace<T, Dim, Order, ElementType, QuadratureType, FieldType>
149 : ::getLocalDOFIndex(const size_t& elementIndex,
150 : const size_t& globalDOFIndex) const {
151 :
152 : static_assert(Dim == 2 || Dim == 3, "Dim must be 2 or 3");
153 :
154 : // Get all the global DOFs for the element
155 0 : const Vector<size_t, this->numElementDOFs> global_dofs =
156 0 : this->getGlobalDOFIndices(elementIndex);
157 :
158 0 : ippl::Vector<size_t, this->numElementDOFs> dof_mapping;
159 : if (Dim == 2) {
160 0 : dof_mapping = {0, 1, 2, 3};
161 : } else if (Dim == 3) {
162 0 : dof_mapping = {0, 1, 2, 3, 4, 5, 7, 6, 8, 9, 10, 11};
163 : }
164 :
165 : // Find the global DOF in the vector and return the local DOF index
166 : // TODO this can be done faster since the global DOFs are sorted
167 0 : for (size_t i = 0; i < dof_mapping.dim; ++i) {
168 0 : if (global_dofs[dof_mapping[i]] == globalDOFIndex) {
169 0 : return dof_mapping[i];
170 : }
171 : }
172 0 : return std::numeric_limits<size_t>::quiet_NaN();
173 0 : }
174 :
175 : template <typename T, unsigned Dim, unsigned Order, typename ElementType,
176 : typename QuadratureType, typename FieldType>
177 0 : KOKKOS_FUNCTION size_t NedelecSpace<T, Dim, Order, ElementType, QuadratureType, FieldType>
178 : ::getGlobalDOFIndex(const size_t& elementIndex,
179 : const size_t& localDOFIndex) const {
180 :
181 0 : const auto global_dofs = this->getGlobalDOFIndices(elementIndex);
182 :
183 0 : return global_dofs[localDOFIndex];
184 0 : }
185 :
186 : template <typename T, unsigned Dim, unsigned Order, typename ElementType,
187 : typename QuadratureType, typename FieldType>
188 : KOKKOS_FUNCTION Vector<size_t, NedelecSpace<T, Dim, Order, ElementType,
189 : QuadratureType, FieldType>::numElementDOFs>
190 0 : NedelecSpace<T, Dim, Order, ElementType, QuadratureType, FieldType>
191 : ::getLocalDOFIndices() const {
192 :
193 0 : Vector<size_t, numElementDOFs> localDOFs;
194 :
195 0 : for (size_t dof = 0; dof < numElementDOFs; ++dof) {
196 0 : localDOFs[dof] = dof;
197 : }
198 :
199 0 : return localDOFs;
200 : }
201 :
202 : template <typename T, unsigned Dim, unsigned Order, typename ElementType,
203 : typename QuadratureType, typename FieldType>
204 : KOKKOS_FUNCTION Vector<size_t, NedelecSpace<T, Dim, Order, ElementType,
205 : QuadratureType, FieldType>::numElementDOFs>
206 12 : NedelecSpace<T, Dim, Order, ElementType, QuadratureType, FieldType>
207 : ::getGlobalDOFIndices(const NedelecSpace<T, Dim, Order, ElementType,
208 : QuadratureType, FieldType>::indices_t& elementIndex) const {
209 :
210 :
211 : // These are simply some manual caclualtions that need to be done.
212 :
213 12 : Vector<size_t, this->numElementDOFs> globalDOFs(0);
214 :
215 : // Initialize a helper vector v
216 12 : Vector<size_t, Dim> v(1);
217 : if constexpr (Dim == 2) {
218 8 : size_t nx = this->nr_m[0];
219 8 : v(1) = 2*nx-1;
220 : } else if constexpr (Dim == 3) {
221 4 : size_t nx = this->nr_m[0];
222 4 : size_t ny = this->nr_m[1];
223 4 : v(1) = 2*nx -1;
224 4 : v(2) = 3*nx*ny - nx - ny;
225 : }
226 :
227 : // For both 2D and 3D the first few DOF indices are the same
228 12 : size_t nx = this->nr_m[0];
229 12 : globalDOFs(0) = v.dot(elementIndex);
230 12 : globalDOFs(1) = globalDOFs(0) + nx - 1;
231 12 : globalDOFs(2) = globalDOFs(1) + nx;
232 12 : globalDOFs(3) = globalDOFs(1) + 1;
233 :
234 : if constexpr (Dim == 3) {
235 4 : size_t ny = this->nr_m[1];
236 :
237 4 : globalDOFs(4) = v(2)*elementIndex(2) + 2*nx*ny - nx - ny
238 4 : + elementIndex(1)*nx + elementIndex(0);
239 4 : globalDOFs(5) = globalDOFs(4) + 1;
240 4 : globalDOFs(6) = globalDOFs(4) + nx + 1;
241 4 : globalDOFs(7) = globalDOFs(4) + nx;
242 4 : globalDOFs(8) = globalDOFs(0) + 3*nx*ny - nx - ny;
243 4 : globalDOFs(9) = globalDOFs(8) + nx - 1;
244 4 : globalDOFs(10) = globalDOFs(9) + nx;
245 4 : globalDOFs(11) = globalDOFs(9) + 1;
246 : }
247 :
248 :
249 24 : return globalDOFs;
250 12 : }
251 :
252 : template <typename T, unsigned Dim, unsigned Order, typename ElementType,
253 : typename QuadratureType, typename FieldType>
254 : KOKKOS_FUNCTION Vector<size_t, NedelecSpace<T, Dim, Order, ElementType,
255 : QuadratureType, FieldType>::numElementDOFs>
256 12 : NedelecSpace<T, Dim, Order, ElementType, QuadratureType, FieldType>
257 : ::getGlobalDOFIndices(const size_t& elementIndex) const {
258 :
259 12 : indices_t elementPos = this->getElementNDIndex(elementIndex);
260 24 : return getGlobalDOFIndices(elementPos);
261 12 : }
262 :
263 :
264 :
265 : template <typename T, unsigned Dim, unsigned Order, typename ElementType,
266 : typename QuadratureType, typename FieldType>
267 : KOKKOS_FUNCTION Vector<size_t, NedelecSpace<T, Dim, Order, ElementType,
268 : QuadratureType, FieldType>::numElementDOFs>
269 144 : NedelecSpace<T, Dim, Order, ElementType, QuadratureType, FieldType>
270 : ::getFEMVectorDOFIndices(NedelecSpace<T, Dim, Order, ElementType,
271 : QuadratureType, FieldType>::indices_t elementIndex,
272 : NDIndex<Dim> ldom) const {
273 :
274 :
275 : // This function here is pretty much the same as getGlobalDOFIndices()
276 : // the only difference is the domain size and that we have an offset
277 : // of the subdomain of the rank to the global one, else it is the same
278 :
279 144 : Vector<size_t, this->numElementDOFs> FEMVectorDOFs(0);
280 :
281 : // This corresponds to translating a global element position to one in
282 : // the subdomain of the rank. For this we subtract the starting position
283 : // the rank subdomain and add the "ghost" hyperplane.
284 144 : elementIndex -= ldom.first();
285 144 : elementIndex += 1;
286 :
287 144 : indices_t dif(0);
288 144 : dif = ldom.last() - ldom.first();
289 144 : dif += 1 + 2; // plus 1 for last still being in +2 for ghosts.
290 :
291 : // From here on out it is pretty much the same as the
292 : // getGlobalDOFIndices() function.
293 144 : Vector<size_t, Dim> v(1);
294 : if constexpr (Dim == 2) {
295 44 : size_t nx = dif[0];
296 44 : v(1) = 2*nx-1;
297 : } else if constexpr (Dim == 3) {
298 100 : size_t nx = dif[0];
299 100 : size_t ny = dif[1];
300 100 : v(1) = 2*nx -1;
301 100 : v(2) = 3*nx*ny - nx - ny;
302 : }
303 :
304 144 : size_t nx = dif[0];
305 144 : FEMVectorDOFs(0) = v.dot(elementIndex);
306 144 : FEMVectorDOFs(1) = FEMVectorDOFs(0) + nx - 1;
307 144 : FEMVectorDOFs(2) = FEMVectorDOFs(1) + nx;
308 144 : FEMVectorDOFs(3) = FEMVectorDOFs(1) + 1;
309 :
310 : if constexpr (Dim == 3) {
311 100 : size_t ny = dif[1];
312 :
313 100 : FEMVectorDOFs(4) = v(2)*elementIndex(2) + 2*nx*ny - nx - ny
314 100 : + elementIndex(1)*nx + elementIndex(0);
315 100 : FEMVectorDOFs(5) = FEMVectorDOFs(4) + 1;
316 100 : FEMVectorDOFs(6) = FEMVectorDOFs(4) + nx + 1;
317 100 : FEMVectorDOFs(7) = FEMVectorDOFs(4) + nx;
318 100 : FEMVectorDOFs(8) = FEMVectorDOFs(0) + 3*nx*ny - nx - ny;
319 100 : FEMVectorDOFs(9) = FEMVectorDOFs(8) + nx - 1;
320 100 : FEMVectorDOFs(10) = FEMVectorDOFs(9) + nx;
321 100 : FEMVectorDOFs(11) = FEMVectorDOFs(9) + 1;
322 : }
323 :
324 :
325 144 : return FEMVectorDOFs;
326 144 : }
327 :
328 : template <typename T, unsigned Dim, unsigned Order, typename ElementType,
329 : typename QuadratureType, typename FieldType>
330 : KOKKOS_FUNCTION Vector<size_t, NedelecSpace<T, Dim, Order, ElementType,
331 : QuadratureType, FieldType>::numElementDOFs>
332 0 : NedelecSpace<T, Dim, Order, ElementType, QuadratureType, FieldType>
333 : ::getFEMVectorDOFIndices(const size_t& elementIndex, NDIndex<Dim> ldom) const {
334 :
335 : // First get the global element position
336 0 : indices_t elementPos = this->getElementNDIndex(elementIndex);
337 0 : return getFEMVectorDOFIndices(elementPos, ldom);
338 0 : }
339 :
340 :
341 : template <typename T, unsigned Dim, unsigned Order, typename ElementType,
342 : typename QuadratureType, typename FieldType>
343 : KOKKOS_FUNCTION NedelecSpace<T, Dim, Order, ElementType, QuadratureType, FieldType>::point_t
344 0 : NedelecSpace<T, Dim, Order, ElementType, QuadratureType, FieldType>
345 : ::getLocalDOFPosition(size_t localDOFIndex) const {
346 :
347 : // Hardcoded center of edges which are stored in the localDofPositions_m
348 : // vector. If the DOF position of an edge element actually is the center
349 : // of the edge is a different question...
350 0 : return localDofPositions_m(localDOFIndex);
351 : }
352 :
353 :
354 :
355 : ///////////////////////////////////////////////////////////////////////
356 : /// Assembly operations ///////////////////////////////////////////////
357 : ///////////////////////////////////////////////////////////////////////
358 : template <typename T, unsigned Dim, unsigned Order, typename ElementType,
359 : typename QuadratureType, typename FieldType>
360 : template <typename F>
361 0 : FEMVector<T> NedelecSpace<T, Dim, Order, ElementType, QuadratureType, FieldType>
362 : ::evaluateAx(FEMVector<T>& x, F& evalFunction) const {
363 0 : Inform m("");
364 :
365 :
366 0 : IpplTimings::TimerRef timerAxInit = IpplTimings::getTimer("Ax init");
367 0 : IpplTimings::startTimer(timerAxInit);
368 :
369 : // create a new field for result, default initialized to zero thanks to
370 : // the Kokkos::View
371 0 : FEMVector<T> resultVector = x.template skeletonCopy<T>();
372 :
373 : // List of quadrature weights
374 0 : const Vector<T, QuadratureType::numElementNodes> w =
375 0 : this->quadrature_m.getWeightsForRefElement();
376 :
377 : // List of quadrature nodes
378 0 : const Vector<point_t, QuadratureType::numElementNodes> q =
379 0 : this->quadrature_m.getIntegrationNodesForRefElement();
380 :
381 : // Get the values of the basis functions and their curl at the
382 : // quadrature points.
383 0 : Vector<Vector<point_t, this->numElementDOFs>, QuadratureType::numElementNodes> curl_b_q;
384 0 : Vector<Vector<point_t, this->numElementDOFs>, QuadratureType::numElementNodes> val_b_q;
385 0 : for (size_t k = 0; k < QuadratureType::numElementNodes; ++k) {
386 0 : for (size_t i = 0; i < this->numElementDOFs; ++i) {
387 0 : curl_b_q[k][i] = this->evaluateRefElementShapeFunctionCurl(i, q[k]);
388 0 : val_b_q[k][i] = this->evaluateRefElementShapeFunction(i, q[k]);
389 : }
390 : }
391 :
392 : // Get field data and atomic result data,
393 : // since it will be added to during the kokkos loop
394 0 : ViewType view = x.getView();
395 0 : AtomicViewType resultView = resultVector.getView();
396 :
397 :
398 : // Get domain information
399 0 : auto ldom = layout_m.getLocalNDIndex();
400 :
401 : using exec_space = typename Kokkos::View<const size_t*>::execution_space;
402 : using policy_type = Kokkos::RangePolicy<exec_space>;
403 :
404 :
405 0 : IpplTimings::stopTimer(timerAxInit);
406 :
407 : // Here we assemble the local matrix of an element. In theory this would
408 : // have to be done for each element individually, but because we have
409 : // that in the case of IPPL all the elements have the same shape we can
410 : // also just do it once and then use if all the time.
411 0 : IpplTimings::TimerRef timerAxLocalMatrix = IpplTimings::getTimer("Ax local matrix");
412 0 : IpplTimings::startTimer(timerAxLocalMatrix);
413 0 : Vector<Vector<T,this->numElementDOFs>, this->numElementDOFs> A;
414 0 : for (size_t i = 0; i < this->numElementDOFs; ++i) {
415 0 : for (size_t j = 0; j < this->numElementDOFs; ++j) {
416 0 : A[i][j] = 0.0;
417 0 : for (size_t k = 0; k < QuadratureType::numElementNodes; ++k) {
418 0 : A[i][j] += w[k] * evalFunction(i, j, curl_b_q[k], val_b_q[k]);
419 : }
420 : }
421 : }
422 0 : IpplTimings::stopTimer(timerAxLocalMatrix);
423 :
424 :
425 0 : IpplTimings::TimerRef timerAxLoop = IpplTimings::getTimer("Ax Loop");
426 0 : IpplTimings::startTimer(timerAxLoop);
427 :
428 : // Loop over elements to compute contributions
429 0 : Kokkos::parallel_for(
430 0 : "Loop over elements", policy_type(0, elementIndices.extent(0)),
431 0 : KOKKOS_CLASS_LAMBDA(const size_t index) {
432 0 : const size_t elementIndex = elementIndices(index);
433 0 : const Vector<size_t, this->numElementDOFs> local_dof = this->getLocalDOFIndices();
434 :
435 : // Here we now retrieve the global DOF indices and their
436 : // position inside of the FEMVector
437 0 : const Vector<size_t, this->numElementDOFs> global_dofs =
438 0 : this->getGlobalDOFIndices(elementIndex);
439 :
440 0 : const Vector<size_t, this->numElementDOFs> vectorIndices =
441 : this->getFEMVectorDOFIndices(elementIndex, ldom);
442 :
443 :
444 : // local DOF indices
445 : size_t i, j;
446 :
447 : // global DOF n-dimensional indices (Vector of N indices
448 : // representing indices in each dimension)
449 : size_t I, J;
450 :
451 0 : for (i = 0; i < this->numElementDOFs; ++i) {
452 0 : I = global_dofs[i];
453 :
454 : // Skip boundary DOFs (Zero Dirichlet BCs)
455 0 : if (this->isDOFOnBoundary(I)) {
456 0 : continue;
457 : }
458 :
459 0 : for (j = 0; j < this->numElementDOFs; ++j) {
460 0 : J = global_dofs[j];
461 :
462 : // Skip boundary DOFs (Zero Dirichlet BCs)
463 0 : if (this->isDOFOnBoundary(J)) {
464 0 : continue;
465 : }
466 :
467 0 : resultView(vectorIndices[i]) += A[i][j] * view(vectorIndices[j]);
468 : }
469 : }
470 0 : }
471 : );
472 0 : IpplTimings::stopTimer(timerAxLoop);
473 :
474 0 : return resultVector;
475 :
476 0 : }
477 :
478 :
479 : template <typename T, unsigned Dim, unsigned Order, typename ElementType,
480 : typename QuadratureType, typename FieldType>
481 0 : FEMVector<T> NedelecSpace<T, Dim, Order, ElementType, QuadratureType, FieldType>
482 : ::evaluateLoadVector(const FEMVector<NedelecSpace<T, Dim, Order, ElementType,
483 : QuadratureType, FieldType>::point_t>& f) const {
484 :
485 : // List of quadrature weights
486 0 : const Vector<T, QuadratureType::numElementNodes> w =
487 0 : this->quadrature_m.getWeightsForRefElement();
488 :
489 : // List of quadrature nodes
490 0 : const Vector<point_t, QuadratureType::numElementNodes> q =
491 0 : this->quadrature_m.getIntegrationNodesForRefElement();
492 :
493 0 : const indices_t zeroNdIndex = Vector<size_t, Dim>(0);
494 :
495 :
496 :
497 : // Evaluate the basis functions for the DOF at the quadrature nodes
498 0 : Vector<Vector<point_t, this->numElementDOFs>, QuadratureType::numElementNodes> basis_q;
499 0 : for (size_t k = 0; k < QuadratureType::numElementNodes; ++k) {
500 0 : for (size_t i = 0; i < this->numElementDOFs; ++i) {
501 0 : basis_q[k][i] = this->evaluateRefElementShapeFunction(i, q[k]);
502 : }
503 : }
504 :
505 :
506 : // Get the distance between the quadrature nodes and the DOFs
507 : // we assume that the dofs are at the center of an edge, this is then
508 : // going to be used to implement a very crude interpolation scheme.
509 : Vector<Vector<T, this->numElementDOFs>, QuadratureType::numElementNodes>
510 0 : quadratureDOFDistances;
511 0 : for (size_t k = 0; k < QuadratureType::numElementNodes; ++k) {
512 0 : for (size_t i = 0; i < this->numElementDOFs; ++i) {
513 0 : point_t dofPos = getLocalDOFPosition(i);
514 0 : point_t d = dofPos - q[k];
515 0 : quadratureDOFDistances[k][i] = Kokkos::sqrt(d.dot(d));
516 : }
517 : }
518 :
519 : // Absolute value of det Phi_K
520 0 : const T absDetDPhi = Kokkos::abs(this->ref_element_m.getDeterminantOfTransformationJacobian(
521 0 : this->getElementMeshVertexPoints(zeroNdIndex)));
522 :
523 : // Get domain information and ghost cells
524 0 : auto ldom = layout_m.getLocalNDIndex();
525 :
526 :
527 : // Get boundary conditions from field
528 0 : FEMVector<T> resultVector = createFEMVector();
529 :
530 : // Get field data and make it atomic,
531 : // since it will be added to during the kokkos loop
532 0 : AtomicViewType atomic_view = resultVector.getView();
533 0 : typename detail::ViewType<point_t, 1>::view_type view = f.getView();
534 :
535 : using exec_space = typename Kokkos::View<const size_t*>::execution_space;
536 : using policy_type = Kokkos::RangePolicy<exec_space>;
537 :
538 : // Loop over elements to compute contributions
539 0 : Kokkos::parallel_for(
540 0 : "Loop over elements", policy_type(0, elementIndices.extent(0)),
541 0 : KOKKOS_CLASS_LAMBDA(size_t index) {
542 0 : const size_t elementIndex = elementIndices(index);
543 0 : const Vector<size_t, this->numElementDOFs> local_dofs = this->getLocalDOFIndices();
544 0 : const Vector<size_t, this->numElementDOFs> global_dofs =
545 0 : this->getGlobalDOFIndices(elementIndex);
546 :
547 0 : const Vector<size_t, this->numElementDOFs> vectorIndices =
548 : this->getFEMVectorDOFIndices(elementIndex, ldom);
549 :
550 : size_t i;
551 :
552 0 : for (i = 0; i < this->numElementDOFs; ++i) {
553 0 : size_t I = global_dofs[i];
554 0 : if (this->isDOFOnBoundary(I)) {
555 0 : continue;
556 : }
557 :
558 :
559 : // calculate the contribution of this element
560 0 : T contrib = 0;
561 0 : for (size_t k = 0; k < QuadratureType::numElementNodes; ++k) {
562 : // We now have to interpolate the value of the field
563 : // given at the DOF positions to the quadrature point.
564 0 : point_t interpolatedVal(0);
565 0 : T distSum = 0;
566 0 : for (size_t j = 0; j < this->numElementDOFs; ++j) {
567 : // get field index corresponding to this DOF
568 :
569 : // the distance
570 0 : T dist = quadratureDOFDistances[k][j];
571 :
572 : // running variable used for normalization
573 0 : distSum += 1/dist;
574 :
575 : // get field value at DOF and interpolate to q_k
576 0 : interpolatedVal += 1./dist * view(vectorIndices<:j:>);
577 : }
578 : // here we have to divide it by distSum in order to
579 : // normalize it
580 0 : interpolatedVal /= distSum;
581 :
582 : // update contribution
583 0 : contrib += w[k] * basis_q[k][i].dot(interpolatedVal) * absDetDPhi;
584 : }
585 :
586 : // add the contribution of the element to the field
587 0 : atomic_view(vectorIndices<:i:>) += contrib;
588 :
589 : }
590 0 : });
591 :
592 0 : return resultVector;
593 0 : }
594 :
595 : template <typename T, unsigned Dim, unsigned Order, typename ElementType,
596 : typename QuadratureType, typename FieldType>
597 : template <typename F>
598 : FEMVector<T> NedelecSpace<T, Dim, Order, ElementType, QuadratureType, FieldType>
599 : ::evaluateLoadVectorFunctor(const F& f) const {
600 :
601 : // List of quadrature weights
602 : const Vector<T, QuadratureType::numElementNodes> w =
603 : this->quadrature_m.getWeightsForRefElement();
604 :
605 : // List of quadrature nodes
606 : const Vector<point_t, QuadratureType::numElementNodes> q =
607 : this->quadrature_m.getIntegrationNodesForRefElement();
608 :
609 : const indices_t zeroNdIndex = Vector<size_t, Dim>(0);
610 :
611 : // Evaluate the basis functions for the DOF at the quadrature nodes
612 : Vector<Vector<point_t, this->numElementDOFs>, QuadratureType::numElementNodes> basis_q;
613 : for (size_t k = 0; k < QuadratureType::numElementNodes; ++k) {
614 : for (size_t i = 0; i < this->numElementDOFs; ++i) {
615 : basis_q[k][i] = this->evaluateRefElementShapeFunction(i, q[k]);
616 : }
617 : }
618 :
619 :
620 : // Absolute value of det Phi_K
621 : const T absDetDPhi = Kokkos::abs(this->ref_element_m.getDeterminantOfTransformationJacobian(
622 : this->getElementMeshVertexPoints(zeroNdIndex)));
623 :
624 : // Get domain information and ghost cells
625 : auto ldom = layout_m.getLocalNDIndex();
626 :
627 :
628 : // Get boundary conditions from field
629 : FEMVector<T> resultVector = createFEMVector();
630 :
631 : // Get field data and make it atomic,
632 : // since it will be added to during the kokkos loop
633 : AtomicViewType atomic_view = resultVector.getView();
634 :
635 : using exec_space = typename Kokkos::View<const size_t*>::execution_space;
636 : using policy_type = Kokkos::RangePolicy<exec_space>;
637 :
638 :
639 : // Loop over elements to compute contributions
640 : Kokkos::parallel_for(
641 : "Loop over elements", policy_type(0, elementIndices.extent(0)),
642 : KOKKOS_CLASS_LAMBDA(size_t index) {
643 : const size_t elementIndex = elementIndices(index);
644 : const Vector<size_t, this->numElementDOFs> local_dofs = this->getLocalDOFIndices();
645 : const Vector<size_t, this->numElementDOFs> global_dofs =
646 : this->getGlobalDOFIndices(elementIndex);
647 :
648 : const Vector<size_t, this->numElementDOFs> vectorIndices =
649 : this->getFEMVectorDOFIndices(elementIndex, ldom);
650 :
651 :
652 : size_t i, I;
653 :
654 : for (i = 0; i < this->numElementDOFs; ++i) {
655 : I = global_dofs[i];
656 :
657 :
658 : if (this->isDOFOnBoundary(I)) {
659 : continue;
660 : }
661 :
662 : // calculate the contribution of this element
663 : T contrib = 0;
664 : for (size_t k = 0; k < QuadratureType::numElementNodes; ++k) {
665 : // Get the global position of the quadrature point
666 : point_t pos = this->ref_element_m.localToGlobal(
667 : this->getElementMeshVertexPoints(this->getElementNDIndex(elementIndex)),
668 : q[k]);
669 :
670 : // evaluate the rhs function at this global position
671 : point_t interpolatedVal = f(pos);
672 :
673 : // update contribution
674 : contrib += w[k] * basis_q[k][i].dot(interpolatedVal) * absDetDPhi;
675 : }
676 :
677 : // add the contribution of the element to the vector
678 : atomic_view(vectorIndices[i]) += contrib;
679 :
680 : }
681 : });
682 :
683 : return resultVector;
684 : }
685 :
686 :
687 :
688 : ///////////////////////////////////////////////////////////////////////
689 : /// Basis functions and gradients /////////////////////////////////////
690 : ///////////////////////////////////////////////////////////////////////
691 :
692 : template <typename T, unsigned Dim, unsigned Order, typename ElementType,
693 : typename QuadratureType, typename FieldType>
694 : KOKKOS_FUNCTION NedelecSpace<T, Dim, Order, ElementType, QuadratureType, FieldType>::point_t
695 390400 : NedelecSpace<T, Dim, Order, ElementType, QuadratureType, FieldType>
696 : ::evaluateRefElementShapeFunction(const size_t& localDOF,
697 : const NedelecSpace<T, Dim, Order, ElementType,
698 : QuadratureType, FieldType>::point_t& localPoint) const {
699 :
700 : // Assert that the local vertex index is valid.
701 390400 : assert(localDOF < this->numElementDOFs && "The local vertex index is invalid");
702 :
703 390400 : assert(this->ref_element_m.isPointInRefElement(localPoint)
704 : && "Point is not in reference element");
705 :
706 :
707 :
708 : // Simply hardcoded
709 390400 : point_t result(0);
710 : if constexpr (Dim == 2) {
711 6400 : T x = localPoint(0);
712 6400 : T y = localPoint(1);
713 :
714 6400 : switch (localDOF){
715 1600 : case 0: result(0) = 1 - y; break;
716 1600 : case 1: result(1) = 1 - x; break;
717 1600 : case 2: result(0) = y; break;
718 1600 : case 3: result(1) = x; break;
719 : }
720 : } else if constexpr (Dim == 3) {
721 384000 : T x = localPoint(0);
722 384000 : T y = localPoint(1);
723 384000 : T z = localPoint(2);
724 :
725 384000 : switch (localDOF){
726 32000 : case 0: result(0) = y*z - y - z + 1; break;
727 32000 : case 1: result(1) = x*z - x - z + 1; break;
728 32000 : case 2: result(0) = y*(1 - z); break;
729 32000 : case 3: result(1) = x*(1 - z); break;
730 32000 : case 4: result(2) = x*y - x - y + 1; break;
731 32000 : case 5: result(2) = x*(1 - y); break;
732 32000 : case 6: result(2) = x*y; break;
733 32000 : case 7: result(2) = y*(1 - x); break;
734 32000 : case 8: result(0) = z*(1 - y); break;
735 32000 : case 9: result(1) = z*(1 - x); break;
736 32000 : case 10: result(0) = y*z; break;
737 32000 : case 11: result(1) = x*z; break;
738 : }
739 : }
740 :
741 :
742 390400 : return result;
743 : }
744 :
745 :
746 : template <typename T, unsigned Dim, unsigned Order, typename ElementType,
747 : typename QuadratureType, typename FieldType>
748 : KOKKOS_FUNCTION typename NedelecSpace<T, Dim, Order, ElementType,
749 : QuadratureType, FieldType>::point_t
750 390400 : NedelecSpace<T, Dim, Order, ElementType, QuadratureType, FieldType>
751 : ::evaluateRefElementShapeFunctionCurl(const size_t& localDOF,
752 : const NedelecSpace<T, Dim, Order, ElementType,
753 : QuadratureType, FieldType>::point_t& localPoint) const {
754 :
755 : // Hard coded.
756 390400 : point_t result(0);
757 :
758 : if constexpr (Dim == 2) {
759 : // In case of 2d we would have that the curl would correspond to a
760 : // scalar, but in order to keep the interface uniform across all the
761 : // dimensions, we still use a 2d vector but only set the first entry
762 : // of it. This should lead to no problems, as we later on only will
763 : // take the dot product between two of them and therefore should not
764 : // run into any problems
765 :
766 6400 : switch (localDOF) {
767 1600 : case 0: result(0) = 1; break;
768 1600 : case 1: result(0) = -1; break;
769 1600 : case 2: result(0) = -1; break;
770 1600 : case 3: result(0) = 1; break;
771 : }
772 : } else {
773 384000 : T x = localPoint(0);
774 384000 : T y = localPoint(1);
775 384000 : T z = localPoint(2);
776 :
777 384000 : switch (localDOF) {
778 32000 : case 0: result(0) = 0; result(1) = -1+y; result(2) = 1-z; break;
779 32000 : case 1: result(0) = 1-x; result(1) = 0; result(2) = -1+z; break;
780 32000 : case 2: result(0) = 0; result(1) = -y; result(2) = -1+z; break;
781 32000 : case 3: result(0) = x; result(1) = 0; result(2) = 1-z; break;
782 32000 : case 4: result(0) = -1+x; result(1) = 1-y; result(2) = 0; break;
783 32000 : case 5: result(0) = -x; result(1) = -1+y; result(2) = 0; break;
784 32000 : case 6: result(0) = x; result(1) = -y; result(2) = 0; break;
785 32000 : case 7: result(0) = 1-x; result(1) = y; result(2) = 0; break;
786 32000 : case 8: result(0) = 0; result(1) = 1-y; result(2) = z; break;
787 32000 : case 9: result(0) = -1+x; result(1) = 0; result(2) = -z; break;
788 32000 : case 10: result(0) = 0; result(1) = y; result(2) = -z; break;
789 32000 : case 11: result(0) = -x; result(1) = 0; result(2) = z; break;
790 : }
791 : }
792 :
793 390400 : return result;
794 :
795 : }
796 :
797 :
798 : ///////////////////////////////////////////////////////////////////////
799 : /// FEMVector conversion //////////////////////////////////////////////
800 : ///////////////////////////////////////////////////////////////////////
801 :
802 : template <typename T, unsigned Dim, unsigned Order, typename ElementType,
803 : typename QuadratureType, typename FieldType>
804 8 : FEMVector<T> NedelecSpace<T, Dim, Order, ElementType, QuadratureType, FieldType>
805 : ::createFEMVector() const {
806 : // This function will simply call one of the other two depending on the
807 : // dimension of the space
808 : if constexpr (Dim == 2) {
809 4 : return createFEMVector2d();
810 : } else {
811 4 : return createFEMVector3d();
812 : }
813 : }
814 :
815 :
816 : template <typename T, unsigned Dim, unsigned Order, typename ElementType,
817 : typename QuadratureType, typename FieldType>
818 : Kokkos::View<typename NedelecSpace<T, Dim, Order, ElementType, QuadratureType,
819 : FieldType>::point_t*>
820 0 : NedelecSpace<T, Dim, Order, ElementType, QuadratureType, FieldType>::reconstructToPoints(
821 : const Kokkos::View<typename NedelecSpace<T, Dim, Order, ElementType, QuadratureType,
822 : FieldType>::point_t*>& positions, const FEMVector<T>& coef) const {
823 :
824 : // The domain information of the subdomain of the MPI rank
825 0 : auto ldom = layout_m.getLocalNDIndex();
826 :
827 : // The domain information of the global domain
828 0 : auto gdom = layout_m.getDomain();
829 0 : indices_t gextent = gdom.last() - gdom.first();
830 :
831 : // The size of the global domain.
832 0 : point_t domainSize = (this->nr_m-1) * this->hr_m;
833 :
834 :
835 0 : auto coefView = coef.getView();
836 :
837 0 : Kokkos::View<point_t*> outView("reconstructed Func values at points", positions.extent(0));
838 :
839 0 : Kokkos::parallel_for("reconstructToPoints", positions.extent(0),
840 0 : KOKKOS_CLASS_LAMBDA(size_t i) {
841 : // get the current position and for it figure out to which
842 : // element it belongs
843 0 : point_t pos = positions<:i:>;
844 0 : indices_t elemIdx = ((pos - this->origin_m) / domainSize) * gextent;
845 :
846 :
847 : // next up we have to handle the case of when a position that
848 : // was provided to us lies on an edge at the upper bound of the
849 : // local domain, because in this case we have that the above
850 : // transformation gives back an element which is somewhat in the
851 : // halo. In order to fix this we simply subtract one.
852 0 : for (size_t d = 0; d < Dim; ++d) {
853 0 : if (elemIdx<:d:> >= ldom.last()<:d:>) {
854 0 : elemIdx<:d:> -= 1;
855 : }
856 : }
857 :
858 :
859 : // get correct indices
860 0 : const Vector<size_t, this->numElementDOFs> vectorIndices =
861 : this->getFEMVectorDOFIndices(elemIdx, ldom);
862 :
863 :
864 : // figure out position inside of the reference element
865 0 : point_t locPos = pos - (elemIdx * this->hr_m + this->origin_m);
866 0 : locPos /= this->hr_m;
867 :
868 : // because of numerical instabilities it might happen then when
869 : // a point is on an edge this becomes marginally larger that 1
870 : // or slightly negative which triggers an assertion. So this
871 : // simply is to prevent this.
872 0 : for (size_t d = 0; d < Dim; ++d) {
873 0 : locPos<:d:> = Kokkos::min(T(1), locPos<:d:>);
874 0 : locPos<:d:> = Kokkos::max(T(0), locPos<:d:>);
875 : }
876 :
877 :
878 : // interpolate the function value to the position, using the
879 : // basis functions.
880 0 : point_t val(0);
881 0 : for (size_t j = 0; j < this->numElementDOFs; ++j) {
882 0 : point_t funcVal = this->evaluateRefElementShapeFunction(j, locPos);
883 0 : val += funcVal*coefView(vectorIndices<:j:>);
884 : }
885 0 : outView(i) = val;
886 :
887 0 : }
888 : );
889 :
890 :
891 0 : return outView;
892 0 : }
893 :
894 :
895 : ///////////////////////////////////////////////////////////////////////
896 : /// Error norm computations ///////////////////////////////////////////
897 : ///////////////////////////////////////////////////////////////////////
898 :
899 : template <typename T, unsigned Dim, unsigned Order, typename ElementType,
900 : typename QuadratureType, typename FieldType>
901 : template <typename F>
902 0 : T NedelecSpace<T, Dim, Order, ElementType, QuadratureType, FieldType>::computeError(
903 : const FEMVector<T>& u_h, const F& u_sol) const {
904 0 : if (this->quadrature_m.getOrder() < (2 * Order + 1)) {
905 : // throw exception
906 0 : throw IpplException( "NedelecSpace::computeError()",
907 : "Order of quadrature rule for error computation should be > 2*p + 1");
908 : }
909 :
910 : // List of quadrature weights
911 0 : const Vector<T, QuadratureType::numElementNodes> w =
912 0 : this->quadrature_m.getWeightsForRefElement();
913 :
914 : // List of quadrature nodes
915 0 : const Vector<point_t, QuadratureType::numElementNodes> q =
916 0 : this->quadrature_m.getIntegrationNodesForRefElement();
917 :
918 : // Evaluate the basis functions for the DOF at the quadrature nodes
919 0 : Vector<Vector<point_t, this->numElementDOFs>, QuadratureType::numElementNodes> basis_q;
920 0 : for (size_t k = 0; k < QuadratureType::numElementNodes; ++k) {
921 0 : for (size_t i = 0; i < this->numElementDOFs; ++i) {
922 0 : basis_q[k][i] = this->evaluateRefElementShapeFunction(i, q[k]);
923 : }
924 : }
925 :
926 0 : const indices_t zeroNdIndex = Vector<size_t, Dim>(0);
927 :
928 : // Absolute value of det Phi_K
929 0 : const T absDetDPhi = Kokkos::abs(this->ref_element_m
930 0 : .getDeterminantOfTransformationJacobian(this->getElementMeshVertexPoints(zeroNdIndex)));
931 :
932 : // Variable to sum the error to
933 0 : T error = 0;
934 :
935 : // Get domain information and ghost cells
936 0 : auto ldom = layout_m.getLocalNDIndex();
937 :
938 : using exec_space = typename Kokkos::View<const size_t*>::execution_space;
939 : using policy_type = Kokkos::RangePolicy<exec_space>;
940 :
941 0 : auto view = u_h.getView();
942 :
943 :
944 : // Loop over elements to compute contributions
945 0 : Kokkos::parallel_reduce("Compute error over elements",
946 0 : policy_type(0, elementIndices.extent(0)),
947 0 : KOKKOS_CLASS_LAMBDA(size_t index, double& local) {
948 0 : const size_t elementIndex = elementIndices(index);
949 0 : const Vector<size_t, this->numElementDOFs> global_dofs =
950 0 : this->getGlobalDOFIndices(elementIndex);
951 :
952 0 : const Vector<size_t, this->numElementDOFs> vectorIndices =
953 : this->getFEMVectorDOFIndices(elementIndex, ldom);
954 :
955 :
956 : // contribution of this element to the error
957 0 : T contrib = 0;
958 0 : for (size_t k = 0; k < QuadratureType::numElementNodes; ++k) {
959 : // Evaluate the analystical solution at the global position
960 : // of the quadrature point
961 0 : point_t val_u_sol = u_sol(this->ref_element_m.localToGlobal(
962 0 : this->getElementMeshVertexPoints(this->getElementNDIndex(elementIndex)),
963 0 : q[k]));
964 :
965 : // Here we now reconstruct the solution given the basis
966 : // functions.
967 0 : point_t val_u_h = 0;
968 0 : for (size_t j = 0; j < this->numElementDOFs; ++j) {
969 : // get field index corresponding to this DOF
970 0 : val_u_h += basis_q[k][j] * view(vectorIndices[j]);
971 : }
972 :
973 : // calculate error and add to sum.
974 0 : point_t dif = (val_u_sol - val_u_h);
975 0 : T x = dif.dot(dif);
976 0 : contrib += w[k] * x * absDetDPhi;
977 : }
978 0 : local += contrib;
979 0 : },
980 0 : Kokkos::Sum<double>(error)
981 : );
982 :
983 : // MPI reduce
984 0 : T global_error = 0.0;
985 0 : Comm->allreduce(error, global_error, 1, std::plus<T>());
986 :
987 0 : return Kokkos::sqrt(global_error);
988 0 : }
989 :
990 : template <typename T, unsigned Dim, unsigned Order, typename ElementType,
991 : typename QuadratureType, typename FieldType>
992 328 : bool NedelecSpace<T, Dim, Order, ElementType, QuadratureType, FieldType>
993 : ::isDOFOnBoundary(const size_t& dofIdx) const {
994 :
995 328 : bool onBoundary = false;
996 : if constexpr (Dim == 2) {
997 160 : size_t nx = this->nr_m[0];
998 160 : size_t ny = this->nr_m[1];
999 : // South
1000 160 : bool sVal = (dofIdx < nx -1);
1001 160 : onBoundary = onBoundary || sVal;
1002 : // North
1003 160 : onBoundary = onBoundary || (dofIdx > nx*(ny-1) + ny*(nx-1) - nx);
1004 : // West
1005 160 : onBoundary = onBoundary || ((dofIdx >= nx-1) && (dofIdx - (nx-1)) % (2*nx - 1) == 0);
1006 : // East
1007 160 : onBoundary = onBoundary || ((dofIdx >= 2*nx-2) && ((dofIdx - 2*nx + 2) % (2*nx - 1) == 0));
1008 : }
1009 :
1010 : if constexpr (Dim == 3) {
1011 168 : size_t nx = this->nr_m[0];
1012 168 : size_t ny = this->nr_m[1];
1013 168 : size_t nz = this->nr_m[2];
1014 :
1015 168 : size_t zOffset = dofIdx / (nx*(ny-1) + ny*(nx-1) + nx*ny);
1016 :
1017 :
1018 168 : if (dofIdx - (nx*(ny-1) + ny*(nx-1) + nx*ny)*zOffset >= (nx*(ny-1) + ny*(nx-1))) {
1019 : // we are parallel to z axis
1020 : // therefore we have halve a cell offset and can never be on the ground or in
1021 : // space
1022 60 : size_t f = dofIdx - (nx*(ny-1) + ny*(nx-1) + nx*ny)*zOffset
1023 60 : - (nx*(ny-1) + ny*(nx-1));
1024 :
1025 60 : size_t yOffset = f / nx;
1026 : // South
1027 60 : onBoundary = onBoundary || yOffset == 0;
1028 : // North
1029 60 : onBoundary = onBoundary || yOffset == ny-1;
1030 :
1031 60 : size_t xOffset = f % nx;
1032 : // West
1033 60 : onBoundary = onBoundary || xOffset == 0;
1034 : // East
1035 60 : onBoundary = onBoundary || xOffset == nx-1;
1036 :
1037 : } else {
1038 : // are parallel to one of the other axes
1039 : // Ground
1040 108 : onBoundary = onBoundary || zOffset == 0;
1041 : // Space
1042 108 : onBoundary = onBoundary || zOffset == nz-1;
1043 :
1044 108 : size_t f = dofIdx - (nx*(ny-1) + ny*(nx-1) + nx*ny)*zOffset;
1045 108 : size_t yOffset = f / (2*nx - 1);
1046 108 : size_t xOffset = f - (2*nx - 1)*yOffset;
1047 :
1048 108 : if (xOffset < (nx-1)) {
1049 : // we are parallel to the x axis, therefore we cannot
1050 : // be on an west or east boundary, but we still can
1051 : // be on a north or south boundary
1052 :
1053 : // South
1054 48 : onBoundary = onBoundary || yOffset == 0;
1055 : // North
1056 48 : onBoundary = onBoundary || yOffset == ny-1;
1057 :
1058 : } else {
1059 : // we are parallel to the y axis, therefore we cannot be
1060 : // on a south or north boundary, but we still can be on
1061 : // a west or east boundary
1062 60 : if (xOffset >= nx-1) {
1063 60 : xOffset -= (nx-1);
1064 : }
1065 :
1066 : // West
1067 60 : onBoundary = onBoundary || xOffset == 0;
1068 : // East
1069 60 : onBoundary = onBoundary || xOffset == nx-1;
1070 : }
1071 : }
1072 : }
1073 328 : return onBoundary;
1074 : }
1075 :
1076 :
1077 :
1078 : template <typename T, unsigned Dim, unsigned Order, typename ElementType,
1079 : typename QuadratureType, typename FieldType>
1080 280 : int NedelecSpace<T, Dim, Order, ElementType, QuadratureType, FieldType>
1081 : ::getBoundarySide(const size_t& dofIdx) const {
1082 :
1083 : if constexpr (Dim == 2) {
1084 160 : size_t nx = this->nr_m[0];
1085 160 : size_t ny = this->nr_m[1];
1086 :
1087 : // South
1088 160 : if (dofIdx < nx -1) return 0;
1089 : // West
1090 144 : if ((dofIdx - (nx-1)) % (2*nx - 1) == 0) return 1;
1091 : // North
1092 128 : if (dofIdx > nx*(ny-1) + ny*(nx-1) - nx) return 2;
1093 : // East
1094 112 : if ((dofIdx >= 2*nx-2) && (dofIdx - 2*nx + 2) % (2*nx - 1) == 0) return 3;
1095 :
1096 96 : return -1;
1097 : }
1098 :
1099 : if constexpr (Dim == 3) {
1100 120 : size_t nx = this->nr_m[0];
1101 120 : size_t ny = this->nr_m[1];
1102 120 : size_t nz = this->nr_m[2];
1103 :
1104 120 : size_t zOffset = dofIdx / (nx*(ny-1) + ny*(nx-1) + nx*ny);
1105 :
1106 :
1107 120 : if (dofIdx - (nx*(ny-1) + ny*(nx-1) + nx*ny)*zOffset >= (nx*(ny-1) + ny*(nx-1))) {
1108 : // we are parallel to z axis
1109 : // therefore we have halve a cell offset and can never be on the ground or in
1110 : // space
1111 40 : size_t f = dofIdx - (nx*(ny-1) + ny*(nx-1) + nx*ny)*zOffset
1112 40 : - (nx*(ny-1) + ny*(nx-1));
1113 :
1114 40 : size_t yOffset = f / nx;
1115 : // South
1116 40 : if (yOffset == 0) return 0;
1117 : // North
1118 32 : if (yOffset == ny-1) return 2;
1119 :
1120 24 : size_t xOffset = f % nx;
1121 : // West
1122 24 : if (xOffset == 0) return 1;
1123 : // East
1124 16 : if (xOffset == nx-1) return 3;
1125 :
1126 : } else {
1127 : // are parallel to one of the other axes
1128 : // Ground
1129 80 : if (zOffset == 0) return 4;
1130 : // Space
1131 64 : if (zOffset == nz-1) return 5;
1132 :
1133 48 : size_t f = dofIdx - (nx*(ny-1) + ny*(nx-1) + nx*ny)*zOffset;
1134 48 : size_t yOffset = f / (2*nx - 1);
1135 48 : size_t xOffset = f - (2*nx - 1)*yOffset;
1136 :
1137 48 : if (xOffset < (nx-1)) {
1138 : // we are parallel to the x axis, therefore we cannot
1139 : // be on an west or east boundary, but we still can
1140 : // be on a north or south boundary
1141 :
1142 : // South
1143 24 : if (yOffset == 0) return 0;
1144 : // North
1145 16 : if (yOffset == ny-1) return 2;
1146 :
1147 : } else {
1148 : // we are parallel to the y axis, therefore we cannot be
1149 : // on a south or north boundary, but we still can be on
1150 : // a west or east boundary
1151 24 : if (xOffset >= nx-1) {
1152 24 : xOffset -= (nx-1);
1153 : }
1154 :
1155 : // West
1156 24 : if (xOffset == 0) return 1;
1157 : // East
1158 16 : if (xOffset == nx-1) return 3;
1159 : }
1160 : }
1161 24 : return -1;
1162 : }
1163 :
1164 : }
1165 :
1166 :
1167 : template <typename T, unsigned Dim, unsigned Order, typename ElementType,
1168 : typename QuadratureType, typename FieldType>
1169 4 : FEMVector<T> NedelecSpace<T, Dim, Order, ElementType, QuadratureType, FieldType>
1170 : ::createFEMVector2d() const{
1171 :
1172 : // Here we will create an empty FEMVector for the case of the domain
1173 : // being 2D.
1174 : // The largest part of this is going to be the handling of the halo
1175 : // cells, more specifically figuring out which entries of the vector are
1176 : // part of the sendIdxs and which are part of the recvIdxs. For this we
1177 : // loop through all the other domains and for each of them figure out if
1178 : // we share a boundary with them. If we share a boundary we then have to
1179 : // figure out which entries of the vector are part of this boundary. To
1180 : // do this we loop though all the mesh elements which are on this
1181 : // boundary and then for each of these elements we take the DOFs which
1182 : // should be part of the sendIdxs and the ones which are part of the
1183 : // recvIdxs. Currently in this step we have to manually select the
1184 : // correct DOFs of the reference element corresponding to that specific
1185 : // boundary type (north, south, west, east). This manual selection of
1186 : // the correct DOFs might lead to an "out of the blue" feeling on why
1187 : // exactly we selected those DOFs, but it should be easily verifyable
1188 : // that those are the correct DOFs.
1189 : // Also note that we are not exchanging any boundary information over
1190 : // corners, test showed that this does not have any impact on the
1191 : // correctness.
1192 : // For more information regarding the domain decomposition refer to the
1193 : // report available at: TODO add reference to report on AMAS website
1194 :
1195 4 : auto ldom = layout_m.getLocalNDIndex();
1196 4 : auto doms = layout_m.getHostLocalDomains();
1197 :
1198 : // Create the temporaries and so on which will store the MPI
1199 : // information.
1200 4 : std::vector<size_t> neighbors;
1201 4 : std::vector< Kokkos::View<size_t*> > sendIdxs;
1202 4 : std::vector< Kokkos::View<size_t*> > recvIdxs;
1203 4 : std::vector< std::vector<size_t> > sendIdxsTemp;
1204 4 : std::vector< std::vector<size_t> > recvIdxsTemp;
1205 :
1206 : // Here we loop thought all the domains to figure out how we are related
1207 : // to them and if we have to do any kind of exchange.
1208 12 : for (size_t i = 0; i < doms.extent(0); ++i) {
1209 8 : if (i == Comm->rank()) {
1210 : // We are looking at ourself
1211 4 : continue;
1212 : }
1213 4 : auto odom = doms(i);
1214 :
1215 : // East boundary
1216 10 : if (ldom.last()[0] == odom.first()[0]-1 &&
1217 6 : !(odom.last()[1] < ldom.first()[1] || odom.first()[1] > ldom.last()[1])) {
1218 : // Extract the range of the boundary.
1219 2 : int begin = std::max(odom.first()[1], ldom.first()[1]);
1220 2 : int end = std::min(odom.last()[1], ldom.last()[1]);
1221 2 : int pos = ldom.last()[0];
1222 :
1223 : // Add this to the neighbour list.
1224 2 : neighbors.push_back(i);
1225 2 : sendIdxsTemp.push_back(std::vector<size_t>());
1226 2 : recvIdxsTemp.push_back(std::vector<size_t>());
1227 2 : size_t idx = neighbors.size() - 1;
1228 :
1229 : // Add all the halo
1230 2 : indices_t elementPosHalo(0);
1231 2 : elementPosHalo(0) = pos;
1232 2 : indices_t elementPosSend(0);
1233 2 : elementPosSend(0) = pos;
1234 12 : for (int k = begin; k <= end; ++k) {
1235 10 : elementPosHalo(1) = k;
1236 10 : elementPosSend(1) = k;
1237 :
1238 10 : auto dofIndicesHalo = getFEMVectorDOFIndices(elementPosHalo, ldom);
1239 10 : recvIdxsTemp[idx].push_back(dofIndicesHalo[3]);
1240 :
1241 10 : auto dofIndicesSend = getFEMVectorDOFIndices(elementPosSend, ldom);
1242 10 : sendIdxsTemp[idx].push_back(dofIndicesSend[0]);
1243 10 : sendIdxsTemp[idx].push_back(dofIndicesSend[1]);
1244 : }
1245 : // Check if on very north
1246 2 : if (end == layout_m.getDomain().last()[1] || ldom.last()[1] > odom.last()[1]) {
1247 2 : elementPosSend(1) = end;
1248 2 : auto dofIndicesSend = getFEMVectorDOFIndices(elementPosSend, ldom);
1249 : // also have to add dof 2
1250 2 : sendIdxsTemp[idx].push_back(dofIndicesSend[2]);
1251 2 : }
1252 2 : }
1253 :
1254 : // West boundary
1255 10 : if (ldom.first()[0] == odom.last()[0]+1 &&
1256 6 : !(odom.last()[1] < ldom.first()[1] || odom.first()[1] > ldom.last()[1])) {
1257 : // Extract the range of the boundary.
1258 2 : int begin = std::max(odom.first()[1], ldom.first()[1]);
1259 2 : int end = std::min(odom.last()[1], ldom.last()[1]);
1260 2 : int pos = ldom.first()[0];
1261 :
1262 : // Add this to the neighbour list.
1263 2 : neighbors.push_back(i);
1264 2 : sendIdxsTemp.push_back(std::vector<size_t>());
1265 2 : recvIdxsTemp.push_back(std::vector<size_t>());
1266 2 : size_t idx = neighbors.size() - 1;
1267 :
1268 : // Add all the halo
1269 2 : indices_t elementPosHalo(0);
1270 2 : elementPosHalo(0) = pos-1;
1271 2 : indices_t elementPosSend(0);
1272 2 : elementPosSend(0) = pos;
1273 12 : for (int k = begin; k <= end; ++k) {
1274 10 : elementPosHalo(1) = k;
1275 10 : elementPosSend(1) = k;
1276 :
1277 10 : auto dofIndicesHalo = getFEMVectorDOFIndices(elementPosHalo, ldom);
1278 10 : recvIdxsTemp[idx].push_back(dofIndicesHalo[0]);
1279 10 : recvIdxsTemp[idx].push_back(dofIndicesHalo[1]);
1280 :
1281 10 : auto dofIndicesSend = getFEMVectorDOFIndices(elementPosSend, ldom);
1282 10 : sendIdxsTemp[idx].push_back(dofIndicesSend[1]);
1283 : }
1284 : // Check if on very north
1285 2 : if (end == layout_m.getDomain().last()[1] || odom.last()[1] > ldom.last()[1]) {
1286 2 : elementPosHalo(1) = end;
1287 2 : auto dofIndicesHalo = getFEMVectorDOFIndices(elementPosHalo, ldom);
1288 : // also have to add dof 2
1289 2 : recvIdxsTemp[idx].push_back(dofIndicesHalo[2]);
1290 2 : }
1291 2 : }
1292 :
1293 : // North boundary
1294 8 : if (ldom.last()[1] == odom.first()[1]-1 &&
1295 4 : !(odom.last()[0] < ldom.first()[0] || odom.first()[0] > ldom.last()[0])) {
1296 : // Extract the range of the boundary.
1297 0 : int begin = std::max(odom.first()[0], ldom.first()[0]);
1298 0 : int end = std::min(odom.last()[0], ldom.last()[0]);
1299 0 : int pos = ldom.last()[1];
1300 :
1301 : // Add this to the neighbour list.
1302 0 : neighbors.push_back(i);
1303 0 : sendIdxsTemp.push_back(std::vector<size_t>());
1304 0 : recvIdxsTemp.push_back(std::vector<size_t>());
1305 0 : size_t idx = neighbors.size() - 1;
1306 :
1307 : // Add all the halo
1308 0 : indices_t elementPosHalo(0);
1309 0 : elementPosHalo(1) = pos;
1310 0 : indices_t elementPosSend(0);
1311 0 : elementPosSend(1) = pos;
1312 0 : for (int k = begin; k <= end; ++k) {
1313 0 : elementPosHalo(0) = k;
1314 0 : elementPosSend(0) = k;
1315 :
1316 0 : auto dofIndicesHalo = getFEMVectorDOFIndices(elementPosHalo, ldom);
1317 0 : recvIdxsTemp[idx].push_back(dofIndicesHalo[2]);
1318 :
1319 0 : auto dofIndicesSend = getFEMVectorDOFIndices(elementPosSend, ldom);
1320 0 : sendIdxsTemp[idx].push_back(dofIndicesSend[0]);
1321 0 : sendIdxsTemp[idx].push_back(dofIndicesSend[1]);
1322 : }
1323 : // Check if on very east
1324 0 : if (end == layout_m.getDomain().last()[0] || ldom.last()[0] > odom.last()[0]) {
1325 0 : elementPosSend(0) = end;
1326 0 : auto dofIndicesSend = getFEMVectorDOFIndices(elementPosSend, ldom);
1327 : // also have to add dof 3
1328 0 : sendIdxsTemp[idx].push_back(dofIndicesSend[3]);
1329 0 : }
1330 0 : }
1331 :
1332 : // South boundary
1333 8 : if (ldom.first()[1] == odom.last()[1]+1 &&
1334 4 : !(odom.last()[0] < ldom.first()[0] || odom.first()[0] > ldom.last()[0])) {
1335 : // Extract the range of the boundary.
1336 0 : int begin = std::max(odom.first()[0], ldom.first()[0]);
1337 0 : int end = std::min(odom.last()[0], ldom.last()[0]);
1338 0 : int pos = ldom.first()[1];
1339 :
1340 : // Add this to the neighbour list.
1341 0 : neighbors.push_back(i);
1342 0 : sendIdxsTemp.push_back(std::vector<size_t>());
1343 0 : recvIdxsTemp.push_back(std::vector<size_t>());
1344 0 : size_t idx = neighbors.size() - 1;
1345 :
1346 : // Add all the halo
1347 0 : indices_t elementPosHalo(0);
1348 0 : elementPosHalo(1) = pos-1;
1349 0 : indices_t elementPosSend(0);
1350 0 : elementPosSend(1) = pos;
1351 0 : for (int k = begin; k <= end; ++k) {
1352 0 : elementPosHalo(0) = k;
1353 0 : elementPosSend(0) = k;
1354 :
1355 0 : auto dofIndicesHalo = getFEMVectorDOFIndices(elementPosHalo, ldom);
1356 0 : recvIdxsTemp[idx].push_back(dofIndicesHalo[0]);
1357 0 : recvIdxsTemp[idx].push_back(dofIndicesHalo[1]);
1358 :
1359 0 : auto dofIndicesSend = getFEMVectorDOFIndices(elementPosSend, ldom);
1360 0 : sendIdxsTemp[idx].push_back(dofIndicesSend[0]);
1361 : }
1362 : // Check if on very east
1363 0 : if (end == layout_m.getDomain().last()[0] || odom.last()[0] > ldom.last()[0]) {
1364 0 : elementPosHalo(0) = end;
1365 0 : auto dofIndicesHalo = getFEMVectorDOFIndices(elementPosHalo, ldom);
1366 : // also have to add dof 3
1367 0 : recvIdxsTemp[idx].push_back(dofIndicesHalo[3]);
1368 0 : }
1369 0 : }
1370 : }
1371 :
1372 :
1373 :
1374 : // Here we now have to translate the sendIdxsTemp and recvIdxsTemp which
1375 : // are std::vectors<std::vector> into the correct list type which
1376 : // is std::vector<Kokkos::View>
1377 8 : for (size_t i = 0; i < neighbors.size(); ++i) {
1378 4 : sendIdxs.push_back(Kokkos::View<size_t*>("FEMvector::sendIdxs[" + std::to_string(i) +
1379 4 : "]", sendIdxsTemp[i].size()));
1380 4 : recvIdxs.push_back(Kokkos::View<size_t*>("FEMvector::recvIdxs[" + std::to_string(i) +
1381 4 : "]", recvIdxsTemp[i].size()));
1382 4 : auto sendView = sendIdxs[i];
1383 4 : auto recvView = recvIdxs[i];
1384 4 : auto hSendView = Kokkos::create_mirror_view(sendView);
1385 4 : auto hRecvView = Kokkos::create_mirror_view(recvView);
1386 :
1387 36 : for (size_t j = 0; j < sendIdxsTemp[i].size(); ++j) {
1388 64 : hSendView(j) = sendIdxsTemp[i][j];
1389 : }
1390 :
1391 36 : for (size_t j = 0; j < recvIdxsTemp[i].size(); ++j) {
1392 64 : hRecvView(j) = recvIdxsTemp[i][j];
1393 : }
1394 :
1395 4 : Kokkos::deep_copy(sendView, hSendView);
1396 4 : Kokkos::deep_copy(recvView, hRecvView);
1397 : }
1398 :
1399 :
1400 :
1401 : // Now finaly create the FEMVector
1402 4 : indices_t extents(0);
1403 4 : extents = (ldom.last() - ldom.first()) + 3;
1404 4 : size_t nx = extents(0);
1405 4 : size_t ny = extents(1);
1406 4 : size_t n = nx*(ny-1) + ny*(nx-1);
1407 4 : FEMVector<T> vec(n, neighbors, sendIdxs, recvIdxs);
1408 :
1409 4 : return vec;
1410 4 : }
1411 :
1412 :
1413 :
1414 : template <typename T, unsigned Dim, unsigned Order, typename ElementType,
1415 : typename QuadratureType, typename FieldType>
1416 4 : FEMVector<T> NedelecSpace<T, Dim, Order, ElementType, QuadratureType, FieldType>
1417 : ::createFEMVector3d() const{
1418 :
1419 :
1420 :
1421 :
1422 : // Here we will create an empty FEMVector for the case of the domain
1423 : // being 3D.
1424 : // It follows the same principle as the 2D case (check comment there).
1425 : // The major difference now is that we have more types of boundaries.
1426 : // Namely we have 6 directions: west, east, south, north, ground, and
1427 : // space. Where west-east is on the x-axis, south-north on the y-axis,
1428 : // and ground-space on the z-axis. For this we have 3 major types of
1429 : // boundaries, namely "flat" boundaries which are along the coordinate
1430 : // axes (your standard west-east, south-north, and ground-space
1431 : // exchanges), then we have two different types of diagonal exchanges,
1432 : // which we will call "positive" and "negative", we have two types as
1433 : // a diagonal exchange always happens over an edge and this edges is
1434 : // shared by 4 different ranks, so we have two different diagonal
1435 : // exchanges per edge, which we differentiate with "positive" and
1436 : // "negative".
1437 : // If we now look at these types independently we have that the code
1438 : // needed to perform one such exchange is large going to be independent
1439 : // of the direction of the exchange (i.e. is the "flat" exchange
1440 : // happening over a west-est or a ground-space boundary) the major
1441 : // difference is the DOF indices we have to chose for the elements on
1442 : // the boundary (check the 2D case for more info).
1443 : // We therefore create 3 lambdas for these different types of boundaries
1444 : // which we then call with appropriate arguments for the direction of the
1445 : // exchange.
1446 : // Note that like with the 2D case we do not consider any exchanges over
1447 : // corners.
1448 : // For more information regarding the domain decomposition refer to the
1449 : // report available at: TODO add reference to report on AMAS website
1450 :
1451 : using indices_t = Vector<int, Dim>;
1452 :
1453 4 : auto ldom = layout_m.getLocalNDIndex();
1454 4 : auto doms = layout_m.getHostLocalDomains();
1455 :
1456 : // Create the temporaries and so on which will store the MPI
1457 : // information.
1458 4 : std::vector<size_t> neighbors;
1459 4 : std::vector< Kokkos::View<size_t*> > sendIdxs;
1460 4 : std::vector< Kokkos::View<size_t*> > recvIdxs;
1461 4 : std::vector< std::vector<size_t> > sendIdxsTemp;
1462 4 : std::vector< std::vector<size_t> > recvIdxsTemp;
1463 :
1464 : // The parameters are:
1465 : // i: The index of the other dom we are looking at (according to doms).
1466 : // a: Along which axis the exchange happens, 0 = x-axis, 1 = y-axis,
1467 : // 2 = z-axis.
1468 : // f,s: While the exchange happens over the axis "a" we have that
1469 : // elements which are part of the boundary are on a plane spanned
1470 : // by the other two axes, these other two axes are then given by
1471 : // these two variables "f" and "s" (they then also define the
1472 : // order in which we go though these axes).
1473 : // va,vb: These are the placeholders for the sendIdxs and recvIdxs
1474 : // arrays, note that depending on if an exchange happens from,
1475 : // e.g. west to east or from east to west the role of which of
1476 : // these placeholders stores the sendIdxs and which one stores
1477 : // the recvIdxs changes.
1478 : // posA, posB: The "a"-axis coordinate of the elements which are part of
1479 : // the boundary, we have two of them as the coordinate
1480 : // can be different depending on if we are looking at the
1481 : // sendIdxs or the recvIdxs.
1482 : // idxsA, idxsB: These are going to be the local DOF indices of the
1483 : // elements which are part of the boundary and which
1484 : // need to be exchanged. Again we have two as the
1485 : // indices are going to depend on if we are looking at
1486 : // the sendIdxs or the recvIdxs
1487 : // adom, bdom: The domain extents of the two domains which are part of
1488 : // this exchange.
1489 8 : auto flatBoundaryExchange = [this, &neighbors, &ldom](
1490 : size_t i, size_t a, size_t f, size_t s,
1491 : std::vector<std::vector<size_t> >& va, std::vector<std::vector<size_t> >& vb,
1492 : int posA, int posB,
1493 : const std::vector<size_t>& idxsA, const std::vector<size_t>& idxsB,
1494 : NDIndex<3>& adom, NDIndex<3>& bdom) {
1495 :
1496 4 : int beginF = std::max(bdom.first()[f], adom.first()[f]);
1497 4 : int endF = std::min(bdom.last()[f], adom.last()[f]);
1498 4 : int beginS = std::max(bdom.first()[s], adom.first()[s]);
1499 4 : int endS = std::min(bdom.last()[s], adom.last()[s]);
1500 :
1501 4 : neighbors.push_back(i);
1502 4 : va.push_back(std::vector<size_t>());
1503 4 : vb.push_back(std::vector<size_t>());
1504 4 : size_t idx = neighbors.size() - 1;
1505 :
1506 :
1507 4 : indices_t elementPosA(0);
1508 4 : elementPosA(a) = posA;
1509 4 : indices_t elementPosB(0);
1510 4 : elementPosB(a) = posB;
1511 :
1512 : // Here we now have the double loop that goes though all the
1513 : // elements spanned by the plane given by the "f" and "s" axis.
1514 16 : for (int k = beginF; k <= endF; ++k) {
1515 12 : elementPosA(f) = k;
1516 12 : elementPosB(f) = k;
1517 48 : for (int l = beginS; l <= endS; ++l) {
1518 36 : elementPosA(s) = l;
1519 36 : elementPosB(s) = l;
1520 :
1521 36 : auto dofIndicesA = this->getFEMVectorDOFIndices(elementPosA, ldom);
1522 36 : va[idx].push_back(dofIndicesA[idxsA[0]]);
1523 36 : va[idx].push_back(dofIndicesA[idxsA[1]]);
1524 :
1525 36 : auto dofIndicesB = this->getFEMVectorDOFIndices(elementPosB, ldom);
1526 36 : vb[idx].push_back(dofIndicesB[idxsB[0]]);
1527 36 : vb[idx].push_back(dofIndicesB[idxsB[1]]);
1528 36 : vb[idx].push_back(dofIndicesB[idxsB[2]]);
1529 :
1530 :
1531 : // We now have reached the end of the first axis and have to
1532 : // figure out if we need to add any additional DOFs. If we
1533 : // need to add DOFs depends on if we are at the mesh
1534 : // boundary and if one of the two domains does not end here
1535 : // and "overlaps" the other one.
1536 36 : if (k == endF) {
1537 24 : if (endF == layout_m.getDomain().last()[f] ||
1538 12 : bdom.last()[f] > adom.last()[f]) {
1539 12 : va[idx].push_back(dofIndicesA[idxsA[2]]);
1540 : }
1541 :
1542 24 : if (endF == layout_m.getDomain().last()[f] ||
1543 12 : adom.last()[f] > bdom.last()[f]) {
1544 12 : vb[idx].push_back(dofIndicesB[idxsB[3]]);
1545 12 : vb[idx].push_back(dofIndicesB[idxsB[4]]);
1546 : }
1547 :
1548 : // This is a modification to the beginning of the f axis
1549 : // we still put it on here as we are guaranteed that
1550 : // like this it only gets called once
1551 : // call this last, as modifies elementPosA(s)
1552 12 : if (bdom.first()[f] < adom.first()[f]) {
1553 0 : indices_t tmpPos = elementPosA;
1554 0 : tmpPos(f) = beginF-1;
1555 0 : auto dofIndicestmp = this->getFEMVectorDOFIndices(tmpPos, ldom);
1556 0 : va[idx].push_back(dofIndicestmp[idxsA[0]]);
1557 0 : va[idx].push_back(dofIndicestmp[idxsA[1]]);
1558 0 : }
1559 : }
1560 : }
1561 :
1562 : // We now have reached the end of the second axis and have to
1563 : // figure out if we need to add any additional DOFs. If we need
1564 : // to add DOFs depends on if we are at the mesh boundary and if
1565 : // one of the two domains does not end here and "overlaps" the
1566 : // other one.
1567 :
1568 12 : if (endS == layout_m.getDomain().last()[s] || bdom.last()[s] > adom.last()[s]) {
1569 12 : elementPosA(s) = endS;
1570 12 : auto dofIndicesA = this->getFEMVectorDOFIndices(elementPosA, ldom);
1571 12 : va[idx].push_back(dofIndicesA[idxsA[3]]);
1572 12 : }
1573 :
1574 12 : if (endS == layout_m.getDomain().last()[s] || adom.last()[s] > bdom.last()[s]) {
1575 12 : elementPosB(s) = endS;
1576 12 : auto dofIndicesB = this->getFEMVectorDOFIndices(elementPosB, ldom);
1577 12 : vb[idx].push_back(dofIndicesB[idxsB[5]]);
1578 12 : vb[idx].push_back(dofIndicesB[idxsB[6]]);
1579 12 : }
1580 :
1581 : // This is a modification to the beginning of the s axis
1582 : // we still put it on here as we are guaranteed that
1583 : // like this it only gets called once
1584 : // call this last, as modifies elementPosA(f);
1585 12 : if (bdom.first()[f] < adom.first()[f]) {
1586 0 : indices_t tmpPos = elementPosA;
1587 0 : tmpPos(s) = beginS-1;
1588 0 : auto dofIndicestmp = this->getFEMVectorDOFIndices(tmpPos, ldom);
1589 0 : va[idx].push_back(dofIndicestmp[idxsA[0]]);
1590 0 : va[idx].push_back(dofIndicestmp[idxsA[1]]);
1591 0 : }
1592 : }
1593 : // At this point we have reached the end of both axes f and s and
1594 : // therefore now have to make one final check.
1595 12 : if ((endF == layout_m.getDomain().last()[f] || adom.last()[f] > bdom.last()[f]) &&
1596 8 : (endS == layout_m.getDomain().last()[s] || adom.last()[s] > bdom.last()[s])) {
1597 4 : elementPosB(f) = endF;
1598 4 : elementPosB(s) = endS;
1599 4 : auto dofIndicesB = this->getFEMVectorDOFIndices(elementPosB, ldom);
1600 4 : vb[idx].push_back(dofIndicesB[idxsB[7]]);
1601 4 : }
1602 4 : };
1603 :
1604 : // The parameters are:
1605 : // i: The index of the other dom we are looking at (according to doms).
1606 : // a: Along which axis the edge is over which the exchange happens,
1607 : // 0 = x-axis, 1 = y-axis, 2 = z-axis.
1608 : // f,s: While the exchange happens over the edge along the axis "a" we
1609 : // have store in those two the other two axes.
1610 : // ao,bo: These are offset variables as certain exchanges require
1611 : // offsets to certain values.
1612 : // va,vb: These are the placeholders for the sendIdxs and recvIdxs
1613 : // arrays, note that depending on if an exchange happens from,
1614 : // e.g. west to east or from east to west the role of which of
1615 : // these placeholders stores the sendIdxs and which one stores
1616 : // the recvIdxs changes.
1617 : // idxsA, idxsB: These are going to be the local DOF indices of the
1618 : // elements which are part of the boundary and which
1619 : // need to be exchanged. Again we have two as the
1620 : // indices are going to depend on if we are looking at
1621 : // the sendIdxs or the recvIdxs
1622 : // odom: The other domain we are exchanging to.
1623 4 : auto negativeDiagonalExchange = [this, &neighbors, &ldom](
1624 : size_t i, size_t a, size_t f, size_t s, int ao, int bo,
1625 : std::vector<std::vector<size_t> >& va, std::vector<std::vector<size_t> >& vb,
1626 : const std::vector<size_t>& idxsA, const std::vector<size_t>& idxsB,
1627 : NDIndex<3>& odom) {
1628 :
1629 0 : neighbors.push_back(i);
1630 0 : va.push_back(std::vector<size_t>());
1631 0 : vb.push_back(std::vector<size_t>());
1632 0 : size_t idx = neighbors.size() - 1;
1633 :
1634 0 : indices_t elementPosA(0);
1635 0 : elementPosA(f) = ldom.last()[f];
1636 0 : elementPosA(s) = ldom.first()[s] + ao;
1637 :
1638 0 : indices_t elementPosB(0);
1639 0 : elementPosB(f) = ldom.last()[f];
1640 0 : elementPosB(s) = ldom.first()[s] + bo;
1641 :
1642 0 : int begin = std::max(odom.first()[a], ldom.first()[a]);
1643 0 : int end = std::min(odom.last()[a], ldom.last()[a]);
1644 : // Loop through all the elements along the edge.
1645 0 : for (int k = begin; k <= end; ++k) {
1646 0 : elementPosA(a) = k;
1647 0 : elementPosB(a) = k;
1648 :
1649 0 : auto dofIndicesA = this->getFEMVectorDOFIndices(elementPosA, ldom);
1650 0 : va[idx].push_back(dofIndicesA[idxsA[0]]);
1651 0 : va[idx].push_back(dofIndicesA[idxsA[1]]);
1652 :
1653 0 : auto dofIndicesB = this->getFEMVectorDOFIndices(elementPosB, ldom);
1654 0 : vb[idx].push_back(dofIndicesB[idxsB[0]]);
1655 0 : vb[idx].push_back(dofIndicesB[idxsB[1]]);
1656 : }
1657 0 : };
1658 :
1659 :
1660 : // The parameters are:
1661 : // i: The index of the other dom we are looking at (according to doms).
1662 : // a: Along which axis the edge is over which the exchange happens,
1663 : // 0 = x-axis, 1 = y-axis, 2 = z-axis.
1664 : // f,s: While the exchange happens over the edge along the axis "a" we
1665 : // have store in those two the other two axes.
1666 : // va,vb: These are the placeholders for the sendIdxs and recvIdxs
1667 : // arrays, note that depending on if an exchange happens from,
1668 : // e.g. west to east or from east to west the role of which of
1669 : // these placeholders stores the sendIdxs and which one stores
1670 : // the recvIdxs changes.
1671 : // idxsA, idxsB: These are going to be the local DOF indices of the
1672 : // elements which are part of the boundary and which
1673 : // need to be exchanged. Again we have two as the
1674 : // indices are going to depend on if we are looking at
1675 : // the sendIdxs or the recvIdxs
1676 : // odom: The other domain we are exchanging to.
1677 4 : auto positiveDiagonalExchange = [this, &neighbors, &ldom](
1678 : size_t i, size_t a, size_t f, size_t s,
1679 : indices_t posA, indices_t posB,
1680 : std::vector<std::vector<size_t> >& va, std::vector<std::vector<size_t> >& vb,
1681 : const std::vector<size_t>& idxsA, const std::vector<size_t>& idxsB,
1682 : NDIndex<3>& odom) {
1683 :
1684 0 : neighbors.push_back(i);
1685 0 : va.push_back(std::vector<size_t>());
1686 0 : vb.push_back(std::vector<size_t>());
1687 0 : size_t idx = neighbors.size() - 1;
1688 :
1689 0 : indices_t elementPosA(0);
1690 0 : elementPosA(f) = posA(f);
1691 0 : elementPosA(s) = posA(s);
1692 :
1693 0 : indices_t elementPosB(0);
1694 0 : elementPosB(f) = posB(f);
1695 0 : elementPosB(s) = posB(s);
1696 :
1697 0 : int begin = std::max(odom.first()[a], ldom.first()[a]);
1698 0 : int end = std::min(odom.last()[a], ldom.last()[a]);
1699 :
1700 0 : for (int k = begin; k <= end; ++k) {
1701 0 : elementPosA(a) = k;
1702 0 : elementPosB(a) = k;
1703 :
1704 0 : auto dofIndicesA = this->getFEMVectorDOFIndices(elementPosA, ldom);
1705 0 : va[idx].push_back(dofIndicesA[idxsA[0]]);
1706 0 : va[idx].push_back(dofIndicesA[idxsA[1]]);
1707 0 : va[idx].push_back(dofIndicesA[idxsA[2]]);
1708 :
1709 0 : auto dofIndicesB = this->getFEMVectorDOFIndices(elementPosB, ldom);
1710 0 : vb[idx].push_back(dofIndicesB[idxsB[0]]);
1711 : }
1712 0 : };
1713 :
1714 : // After we now have defined the code required for each of the exchanges
1715 : // we can look at all the exchanges we need to make and call the lambdas
1716 : // with the appropriate parameters.
1717 :
1718 :
1719 : // Here we loop through all the domains to figure out how we are related
1720 : // to them and if we have to do any kind of exchange.
1721 12 : for (size_t i = 0; i < doms.extent(0); ++i) {
1722 8 : if (i == Comm->rank()) {
1723 : // We are looking at ourself
1724 4 : continue;
1725 : }
1726 4 : auto odom = doms(i);
1727 :
1728 : // East boundary
1729 8 : if (ldom.last()[0] == odom.first()[0]-1 &&
1730 14 : !(odom.last()[1] < ldom.first()[1] || odom.first()[1] > ldom.last()[1]) &&
1731 6 : !(odom.last()[2] < ldom.first()[2] || odom.first()[2] > ldom.last()[2])) {
1732 :
1733 2 : int pos = ldom.last()[0];
1734 10 : flatBoundaryExchange(
1735 : i, 0, 1, 2,
1736 : recvIdxsTemp, sendIdxsTemp,
1737 : pos, pos,
1738 : {3,5,6,11}, {0,1,4,2,7,8,9,10},
1739 : ldom, odom
1740 : );
1741 : }
1742 :
1743 : // West boundary
1744 8 : if (ldom.first()[0] == odom.last()[0]+1 &&
1745 14 : !(odom.last()[1] < ldom.first()[1] || odom.first()[1] > ldom.last()[1]) &&
1746 6 : !(odom.last()[2] < ldom.first()[2] || odom.first()[2] > ldom.last()[2])) {
1747 :
1748 2 : int pos = ldom.first()[0];
1749 10 : flatBoundaryExchange(
1750 : i, 0, 1, 2,
1751 : sendIdxsTemp, recvIdxsTemp,
1752 : pos, pos-1,
1753 : {1,4,7,9}, {0,1,4,2,7,8,9,10},
1754 : odom, ldom
1755 : );
1756 : }
1757 :
1758 : // North boundary
1759 8 : if (ldom.last()[1] == odom.first()[1]-1 &&
1760 12 : !(odom.last()[0] < ldom.first()[0] || odom.first()[0] > ldom.last()[0]) &&
1761 4 : !(odom.last()[2] < ldom.first()[2] || odom.first()[2] > ldom.last()[2])) {
1762 :
1763 0 : int pos = ldom.last()[1];
1764 0 : flatBoundaryExchange(
1765 : i, 1, 0, 2,
1766 : recvIdxsTemp, sendIdxsTemp,
1767 : pos, pos,
1768 : {2,7,6,10}, {0,1,4,3,5,8,9,11},
1769 : ldom, odom
1770 : );
1771 : }
1772 :
1773 : // South boundary
1774 8 : if (ldom.first()[1] == odom.last()[1]+1 &&
1775 12 : !(odom.last()[0] < ldom.first()[0] || odom.first()[0] > ldom.last()[0]) &&
1776 4 : !(odom.last()[2] < ldom.first()[2] || odom.first()[2] > ldom.last()[2])) {
1777 :
1778 0 : int pos = ldom.first()[1];
1779 0 : flatBoundaryExchange(
1780 : i, 1, 0, 2,
1781 : sendIdxsTemp, recvIdxsTemp,
1782 : pos, pos-1,
1783 : {0,4,5,8}, {0,1,4,3,5,8,9,11},
1784 : odom, ldom
1785 : );
1786 :
1787 : }
1788 :
1789 : // Space boundary
1790 8 : if (ldom.last()[2] == odom.first()[2]-1 &&
1791 12 : !(odom.last()[0] < ldom.first()[0] || odom.first()[0] > ldom.last()[0]) &&
1792 4 : !(odom.last()[1] < ldom.first()[1] || odom.first()[1] > ldom.last()[1])) {
1793 :
1794 0 : int pos = ldom.last()[2];
1795 0 : flatBoundaryExchange(
1796 : i, 2, 0, 1,
1797 : recvIdxsTemp, sendIdxsTemp,
1798 : pos, pos,
1799 : {8,9,11,10}, {0,1,4,3,5,2,7,6},
1800 : ldom, odom
1801 : );
1802 : }
1803 :
1804 : // Ground boundary
1805 8 : if (ldom.first()[2] == odom.last()[2]+1 &&
1806 12 : !(odom.last()[0] < ldom.first()[0] || odom.first()[0] > ldom.last()[0]) &&
1807 4 : !(odom.last()[1] < ldom.first()[1] || odom.first()[1] > ldom.last()[1])) {
1808 :
1809 0 : int pos = ldom.first()[2];
1810 0 : flatBoundaryExchange(
1811 : i, 2, 0, 1,
1812 : sendIdxsTemp, recvIdxsTemp,
1813 : pos, pos-1,
1814 : {0,1,3,2}, {0,1,4,3,5,2,7,6},
1815 : odom, ldom
1816 : );
1817 : }
1818 :
1819 :
1820 :
1821 : // Next up we handle all the annoying diagonals.
1822 : // The negative ones:
1823 : // Parallel to y from space to ground, west to east
1824 8 : if (ldom.last()[0] == odom.first()[0]-1 && ldom.first()[2] == odom.last()[2]+1 &&
1825 4 : !(odom.last()[1] < ldom.first()[1] || odom.first()[1] > ldom.last()[1])) {
1826 :
1827 0 : negativeDiagonalExchange(
1828 : i, 1, 0, 2, 0, -1,
1829 : sendIdxsTemp, recvIdxsTemp,
1830 : {0,1}, {3,5},
1831 : odom
1832 : );
1833 : }
1834 :
1835 : // Parallel to y from ground to space, east to west
1836 8 : if (ldom.first()[0] == odom.last()[0]+1 && ldom.last()[2] == odom.first()[2]-1 &&
1837 4 : !(odom.last()[1] < ldom.first()[1] || odom.first()[1] > ldom.last()[1])) {
1838 :
1839 0 : negativeDiagonalExchange(
1840 : i, 1, 2, 0, -1, 0,
1841 : recvIdxsTemp, sendIdxsTemp,
1842 : {8,9}, {1,4},
1843 : odom
1844 : );
1845 : }
1846 :
1847 :
1848 : // Parallel to x from space to ground, south to north
1849 8 : if (ldom.last()[1] == odom.first()[1]-1 && ldom.first()[2] == odom.last()[2]+1 &&
1850 4 : !(odom.last()[0] < ldom.first()[0] || odom.first()[0] > ldom.last()[0])) {
1851 0 : negativeDiagonalExchange(
1852 : i, 0, 1, 2, 0, -1,
1853 : sendIdxsTemp, recvIdxsTemp,
1854 : {0,1}, {2,7},
1855 : odom
1856 : );
1857 : }
1858 :
1859 : // Parallel to x from ground to space, north to south
1860 8 : if (ldom.first()[1] == odom.last()[1]+1 && ldom.last()[2] == odom.first()[2]-1 &&
1861 4 : !(odom.last()[0] < ldom.first()[0] || odom.first()[0] > ldom.last()[0])) {
1862 0 : negativeDiagonalExchange(
1863 : i, 0, 2, 1, -1, 0,
1864 : recvIdxsTemp, sendIdxsTemp,
1865 : {8,9}, {0,4},
1866 : odom
1867 : );
1868 : }
1869 :
1870 :
1871 : // Parallel to z from west to east, north to south
1872 8 : if (ldom.last()[0] == odom.first()[0]-1 && ldom.first()[1] == odom.last()[1]+1 &&
1873 4 : !(odom.last()[2] < ldom.first()[2] || odom.first()[2] > ldom.last()[2])) {
1874 0 : negativeDiagonalExchange(
1875 : i, 2, 0, 1, 0, -1,
1876 : sendIdxsTemp, recvIdxsTemp,
1877 : {0,4}, {3,5},
1878 : odom
1879 : );
1880 : }
1881 :
1882 : // Parallel to z from east to west, south to north
1883 8 : if (ldom.first()[0] == odom.last()[0]+1 && ldom.last()[1] == odom.first()[1]-1 &&
1884 4 : !(odom.last()[2] < ldom.first()[2] || odom.first()[2] > ldom.last()[2])) {
1885 0 : negativeDiagonalExchange(
1886 : i, 2, 1, 0, -1, 0,
1887 : recvIdxsTemp, sendIdxsTemp,
1888 : {2,7}, {1,4},
1889 : odom
1890 : );
1891 : }
1892 :
1893 :
1894 :
1895 : // The positive ones
1896 : // Parallel to y from ground to space, west to east
1897 8 : if (ldom.last()[0] == odom.first()[0]-1 && ldom.last()[2] == odom.first()[2]-1 &&
1898 4 : !(odom.last()[1] < ldom.first()[1] || odom.first()[1] > ldom.last()[1])) {
1899 0 : positiveDiagonalExchange(
1900 : i, 1, 0, 2,
1901 0 : ldom.last(), ldom.last(),
1902 : sendIdxsTemp, recvIdxsTemp,
1903 : {0,1,4}, {11},
1904 : odom
1905 : );
1906 : }
1907 :
1908 : // Parallel to y from space to ground, east to west
1909 8 : if (ldom.first()[0] == odom.last()[0]+1 && ldom.first()[2] == odom.last()[2]+1 &&
1910 4 : !(odom.last()[1] < ldom.first()[1] || odom.first()[1] > ldom.last()[1])) {
1911 0 : positiveDiagonalExchange(
1912 : i, 1, 0, 2,
1913 0 : ldom.first()-1, ldom.first(),
1914 : recvIdxsTemp, sendIdxsTemp,
1915 : {0,1,4}, {1},
1916 : odom
1917 : );
1918 : }
1919 :
1920 :
1921 : // Parallel to x from ground to space, south to north
1922 8 : if (ldom.last()[1] == odom.first()[1]-1 && ldom.last()[2] == odom.first()[2]-1 &&
1923 4 : !(odom.last()[0] < ldom.first()[0] || odom.first()[0] > ldom.last()[0])) {
1924 0 : positiveDiagonalExchange(
1925 : i, 0, 1, 2,
1926 0 : ldom.last(), ldom.last(),
1927 : sendIdxsTemp, recvIdxsTemp,
1928 : {0,1,4}, {10},
1929 : odom
1930 : );
1931 : }
1932 :
1933 : // Parallel to x from space to ground, north to south
1934 8 : if (ldom.first()[1] == odom.last()[1]+1 && ldom.first()[2] == odom.last()[2]+1 &&
1935 4 : !(odom.last()[0] < ldom.first()[0] || odom.first()[0] > ldom.last()[0])) {
1936 0 : positiveDiagonalExchange(
1937 : i, 0, 1, 2,
1938 0 : ldom.first()-1, ldom.first(),
1939 : recvIdxsTemp, sendIdxsTemp,
1940 : {0,1,4}, {0},
1941 : odom
1942 : );
1943 : }
1944 :
1945 :
1946 : // Parallel to z from west to east, south to north
1947 8 : if (ldom.last()[0] == odom.first()[0]-1 && ldom.last()[1] == odom.first()[1]-1 &&
1948 4 : !(odom.last()[2] < ldom.first()[2] || odom.first()[2] > ldom.last()[2])) {
1949 0 : positiveDiagonalExchange(
1950 : i, 2, 0, 1,
1951 0 : ldom.last(), ldom.last(),
1952 : sendIdxsTemp, recvIdxsTemp,
1953 : {0,1,4}, {6},
1954 : odom
1955 : );
1956 : }
1957 :
1958 : // Parallel to z from east to west, north to south
1959 8 : if (ldom.first()[0] == odom.last()[0]+1 && ldom.first()[1] == odom.last()[1]+1 &&
1960 4 : !(odom.last()[2] < ldom.first()[2] || odom.first()[2] > ldom.last()[2])) {
1961 0 : positiveDiagonalExchange(
1962 : i, 2, 0, 1,
1963 0 : ldom.first()-1, ldom.first(),
1964 : recvIdxsTemp, sendIdxsTemp,
1965 : {0,1,4}, {4},
1966 : odom
1967 : );
1968 : }
1969 :
1970 : }
1971 :
1972 :
1973 :
1974 :
1975 : // Here we now have to translate the sendIdxsTemp and recvIdxsTemp which
1976 : // are std::vectors<std::vector> into the correct list type which
1977 : // is std::vector<Kokkos::View>
1978 8 : for (size_t i = 0; i < neighbors.size(); ++i) {
1979 4 : sendIdxs.push_back(Kokkos::View<size_t*>("FEMvector::sendIdxs[" + std::to_string(i) +
1980 4 : "]", sendIdxsTemp[i].size()));
1981 4 : recvIdxs.push_back(Kokkos::View<size_t*>("FEMvector::recvIdxs[" + std::to_string(i) +
1982 4 : "]", recvIdxsTemp[i].size()));
1983 4 : auto sendView = sendIdxs[i];
1984 4 : auto recvView = recvIdxs[i];
1985 4 : auto hSendView = Kokkos::create_mirror_view(sendView);
1986 4 : auto hRecvView = Kokkos::create_mirror_view(recvView);
1987 :
1988 132 : for (size_t j = 0; j < sendIdxsTemp[i].size(); ++j) {
1989 256 : hSendView(j) = sendIdxsTemp[i][j];
1990 : }
1991 :
1992 132 : for (size_t j = 0; j < recvIdxsTemp[i].size(); ++j) {
1993 256 : hRecvView(j) = recvIdxsTemp[i][j];
1994 : }
1995 :
1996 4 : Kokkos::deep_copy(sendView, hSendView);
1997 4 : Kokkos::deep_copy(recvView, hRecvView);
1998 : }
1999 :
2000 :
2001 :
2002 : // Now finaly create the FEMVector
2003 4 : indices_t extents(0);
2004 4 : extents = (ldom.last() - ldom.first()) + 3;
2005 4 : size_t nx = extents(0);
2006 4 : size_t ny = extents(1);
2007 4 : size_t nz = extents(2);
2008 4 : size_t n = (nz-1)*(nx*(ny-1) + ny*(nx-1) + nx*ny) + nx*(ny-1) + ny*(nx-1);
2009 4 : FEMVector<T> vec(n, neighbors, sendIdxs, recvIdxs);
2010 :
2011 4 : return vec;
2012 4 : }
2013 :
2014 :
2015 :
2016 : } // namespace ippl
|