Nektar++
OutputVtk.cpp
Go to the documentation of this file.
1////////////////////////////////////////////////////////////////////////////////
2//
3// File: OutputVtk.cpp
4//
5// For more information, please see: http://www.nektar.info/
6//
7// The MIT License
8//
9// Copyright (c) 2006 Division of Applied Mathematics, Brown University (USA),
10// Department of Aeronautics, Imperial College London (UK), and Scientific
11// Computing and Imaging Institute, University of Utah (USA).
12//
13// Permission is hereby granted, free of charge, to any person obtaining a
14// copy of this software and associated documentation files (the "Software"),
15// to deal in the Software without restriction, including without limitation
16// the rights to use, copy, modify, merge, publish, distribute, sublicense,
17// and/or sell copies of the Software, and to permit persons to whom the
18// Software is furnished to do so, subject to the following conditions:
19//
20// The above copyright notice and this permission notice shall be included
21// in all copies or substantial portions of the Software.
22//
23// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
24// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
26// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
29// DEALINGS IN THE SOFTWARE.
30//
31// Description: VTK file format output using VTK library.
32//
33////////////////////////////////////////////////////////////////////////////////
34
35#include <boost/format.hpp>
36
37#include "OutputVtk.h"
40
41#include <vtkCellType.h>
42#include <vtkDoubleArray.h>
43#include <vtkInformation.h>
44#include <vtkMultiBlockDataSet.h>
45#include <vtkPointData.h>
46#include <vtkPoints.h>
47#include <vtkXMLMultiBlockDataWriter.h>
48#include <vtkXMLUnstructuredGridWriter.h>
49
50namespace Nektar::FieldUtils
51{
52
54 ModuleKey(eOutputModule, "vtu"), OutputVtk::create, "Writes a VTU file.");
55
57{
59 m_config["legacy"] =
60 ConfigOption(true, "0", "Output using legacy manual file writers");
61 m_config["highorder"] = ConfigOption(
62 true, "0", "Output using new high-order Lagrange elements");
63 m_config["multiblock"] = ConfigOption(
64 true, "0", "Output using multi-blocks to separate composites");
65 m_config["uncompress"] = ConfigOption(true, "0", "Uncompress xml sections");
66}
67
68// Anonymous namespace for spectral order -> VTK order mapping functions
69// be aware that these orderings are very similar to gmsh but not the same!
70namespace
71{
72// Hashing function for the ordering map taking two integers
73inline size_t key2(int i, int j)
74{
75 return (size_t)i << 32 | (unsigned int)j;
76}
77
78// Hashing function for the ordering map taking three integers
79inline size_t key3(int i, int j, int k)
80{
81 return (size_t)i << 10 ^ j << 5 ^ k;
82}
83
84// Map that takes (a,b,c) -> m
85typedef std::tuple<int, int, int> Mode;
86
87struct cmpop
88{
89 bool operator()(Mode const &a, Mode const &b) const
90 {
91 if (std::get<0>(a) < std::get<0>(b))
92 {
93 return true;
94 }
95 if (std::get<0>(a) > std::get<0>(b))
96 {
97 return false;
98 }
99 if (std::get<1>(a) < std::get<1>(b))
100 {
101 return true;
102 }
103 if (std::get<1>(a) > std::get<1>(b))
104 {
105 return false;
106 }
107 if (std::get<2>(a) < std::get<2>(b))
108 {
109 return true;
110 }
111
112 return false;
113 }
114};
115
116void Rotate(int nrot, std::vector<long long> &surfVerts)
117{
118 int n, i, j, cnt;
119 int np = static_cast<int>(
120 (sqrt(8.0 * static_cast<int>(surfVerts.size()) + 1.0) - 1) / 2);
121 std::vector<long long> tmp(np * np);
122
123 for (n = 0; n < nrot; ++n)
124 {
125 for (cnt = i = 0; i < np; ++i)
126 {
127 for (j = 0; j < np - i; ++j, cnt++)
128 {
129 tmp[i * np + j] = surfVerts[cnt];
130 }
131 }
132 for (cnt = i = 0; i < np; ++i)
133 {
134 for (j = 0; j < np - i; ++j, cnt++)
135 {
136 surfVerts[cnt] = tmp[(np - 1 - i - j) * np + i];
137 }
138 }
139 }
140}
141
142void Reflect(std::vector<long long> &surfVerts)
143{
144 int i, j, cnt;
145 int np = static_cast<int>(
146 (sqrt(8.0 * static_cast<double>(surfVerts.size()) + 1.0) - 1) / 2);
147 std::vector<long long> tmp(np * np);
148
149 for (cnt = i = 0; i < np; ++i)
150 {
151 for (j = 0; j < np - i; ++j, cnt++)
152 {
153 tmp[i * np + np - i - 1 - j] = surfVerts[cnt];
154 }
155 }
156
157 for (cnt = i = 0; i < np; ++i)
158 {
159 for (j = 0; j < np - i; ++j, cnt++)
160 {
161 surfVerts[cnt] = tmp[i * np + j];
162 }
163 }
164}
165
166void Align(std::vector<long long> thisVertId, std::vector<long long> vertId,
167 std::vector<long long> &surfVerts)
168{
169 if (vertId[0] == thisVertId[0])
170 {
171 if (vertId[1] == thisVertId[1] || vertId[1] == thisVertId[2])
172 {
173 if (vertId[1] == thisVertId[2])
174 {
175 Rotate(1, surfVerts);
176 Reflect(surfVerts);
177 }
178 }
179 }
180 else if (vertId[0] == thisVertId[1])
181 {
182 if (vertId[1] == thisVertId[0] || vertId[1] == thisVertId[2])
183 {
184 if (vertId[1] == thisVertId[0])
185 {
186 Reflect(surfVerts);
187 }
188 else
189 {
190 Rotate(2, surfVerts);
191 }
192 }
193 }
194 else if (vertId[0] == thisVertId[2])
195 {
196 if (vertId[1] == thisVertId[0] || vertId[1] == thisVertId[1])
197 {
198 if (vertId[1] == thisVertId[1])
199 {
200 Rotate(2, surfVerts);
201 Reflect(surfVerts);
202 }
203 else
204 {
205 Rotate(1, surfVerts);
206 }
207 }
208 }
209}
210
211std::vector<long long> quadTensorNodeOrdering(
212 const std::vector<long long> &nodes)
213{
214 int nN = static_cast<int>(nodes.size());
215 int n = static_cast<int>(sqrt(nN));
216
217 std::vector<long long> nodeList(nN);
218
219 // Vertices
220 nodeList[0] = nodes[0];
221 if (n > 1)
222 {
223 nodeList[n - 1] = nodes[1];
224 nodeList[n * n - 1] = nodes[2];
225 nodeList[n * (n - 1)] = nodes[3];
226 }
227
228 if (n > 2)
229 {
230 // Edge 0 -> 1
231 for (int i = 1; i < n - 1; ++i)
232 {
233 nodeList[i] = nodes[4 + i - 1];
234 }
235
236 // Edge 3 -> 2
237 for (int i = 1; i < n - 1; ++i)
238 {
239 nodeList[n * (n - 1) + i] = nodes[4 + 2 * (n - 2) + i - 1];
240 }
241
242 for (int j = 1; j < n - 1; ++j)
243 {
244 // Edge 0 -> 3
245 nodeList[n * (n - j - 1)] = nodes[4 + 3 * (n - 2) + n - 2 - j];
246
247 // Interior
248 for (int i = 1; i < n - 1; ++i)
249 {
250 nodeList[j * n + i] = nodes[(i - 3) - 2 * j + (3 + j) * n];
251 }
252
253 // Edge 1 -> 2
254 nodeList[(j + 1) * n - 1] = nodes[4 + (n - 2) + j - 1];
255 }
256 }
257
258 return nodeList;
259}
260
261std::vector<long long> triTensorNodeOrdering(
262 const std::vector<long long> &nodes)
263{
264 int nN = static_cast<int>(nodes.size());
265 int n = static_cast<int>(std::sqrt(2 * nN));
266
267 std::vector<long long> nodeList(nN);
268 int cnt2;
269
270 // Vertices
271 nodeList[0] = nodes[0];
272 if (n > 1)
273 {
274 nodeList[n - 1] = nodes[1];
275 nodeList[n * (n + 1) / 2 - 1] = nodes[2];
276 }
277
278 // Edges
279 int cnt = n;
280 for (int i = 1; i < n - 1; ++i)
281 {
282 nodeList[i] = nodes[3 + i - 1];
283 nodeList[cnt] = nodes[3 + 3 * (n - 2) - i];
284 nodeList[cnt + n - i - 1] = nodes[3 + (n - 2) + i - 1];
285 cnt += n - i;
286 }
287
288 // Interior (recursion)
289 if (n > 3)
290 {
291 // Reorder interior nodes
292 std::vector<long long> interior((n - 3) * (n - 2) / 2);
293 std::copy(nodes.begin() + 3 + 3 * (n - 2), nodes.end(),
294 interior.begin());
295 interior = triTensorNodeOrdering(interior);
296
297 // Copy into full node list
298 cnt = n;
299 cnt2 = 0;
300 for (int j = 1; j < n - 2; ++j)
301 {
302 for (int i = 0; i < n - j - 2; ++i)
303 {
304 nodeList[cnt + i + 1] = interior[cnt2 + i];
305 }
306 cnt += n - j;
307 cnt2 += n - 2 - j;
308 }
309 }
310
311 return nodeList;
312}
313
314std::vector<long long> tetTensorNodeOrdering(
315 const std::vector<long long> &nodes)
316{
317 int nN = static_cast<int>(nodes.size());
318 int n = static_cast<int>(
319 std::cbrt(3 * nN + std::sqrt(9 * nN * nN - 1 / 27)) +
320 std::cbrt(3 * nN - std::sqrt(9 * nN * nN - 1 / 27)) - 0.5);
321
322 std::vector<long long> nodeList(nN);
323 int nTri = n * (n + 1) / 2;
324 int nTet = n * (n + 1) * (n + 2) / 6;
325
326 // Vertices
327 nodeList[0] = nodes[0];
328 if (n == 1)
329 {
330 return nodeList;
331 }
332
333 nodeList[n - 1] = nodes[1];
334 nodeList[nTri - 1] = nodes[2];
335 nodeList[nTet - 1] = nodes[3];
336
337 if (n == 2)
338 {
339 return nodeList;
340 }
341
342 // Set up a map that takes (a,b,c) -> m to help us figure out where things
343 // are inside the tetrahedron.
344 std::map<Mode, int, cmpop> tmp;
345 for (int k = 0, cnt = 0; k < n; ++k)
346 {
347 for (int j = 0; j < n - k; ++j)
348 {
349 for (int i = 0; i < n - k - j; ++i)
350 {
351 tmp[Mode(i, j, k)] = cnt++;
352 }
353 }
354 }
355
356 // Edges first 3
357 for (int i = 1; i < n - 1; ++i)
358 {
359 int eI = i - 1;
360 nodeList[tmp[Mode(i, 0, 0)]] = nodes[4 + eI];
361 nodeList[tmp[Mode(n - 1 - i, i, 0)]] = nodes[4 + (n - 2) + eI];
362 nodeList[tmp[Mode(0, n - 1 - i, 0)]] = nodes[4 + 2 * (n - 2) + eI];
363 }
364
365 // Edges last 3 reversed (compared with NekMesh)
366 for (int i = 1; i < n - 1; ++i)
367 {
368 int eI = (n - 1 - i) - 1;
369 nodeList[tmp[Mode(0, 0, n - 1 - i)]] = nodes[4 + 3 * (n - 2) + eI];
370 nodeList[tmp[Mode(i, 0, n - 1 - i)]] = nodes[4 + 4 * (n - 2) + eI];
371 nodeList[tmp[Mode(0, i, n - 1 - i)]] = nodes[4 + 5 * (n - 2) + eI];
372 }
373
374 if (n == 3)
375 {
376 return nodeList;
377 }
378
379 // For faces, we use the triTensorNodeOrdering routine to make our lives
380 // slightly easier.
381 int nFacePts = (n - 3) * (n - 2) / 2;
382
383 // Grab face points and reorder into a tensor-product type format
384 std::vector<std::vector<long long>> tmpNodes(4);
385 int offset = 4 + 6 * (n - 2);
386
387 for (int i = 0; i < 4; ++i)
388 {
389 tmpNodes[i].resize(nFacePts);
390 for (int j = 0; j < nFacePts; ++j)
391 {
392 tmpNodes[i][j] = nodes[offset++];
393 }
394 tmpNodes[i] = triTensorNodeOrdering(tmpNodes[i]);
395 }
396
397 if (n > 4)
398 {
399 // Now align faces (different to NekMesh)
400 std::vector<long long> triVertId(3), toAlign(3);
401 triVertId[0] = 0;
402 triVertId[1] = 1;
403 triVertId[2] = 2;
404
405 toAlign[0] = 0;
406 toAlign[1] = 2;
407 toAlign[2] = 1;
408 Align(triVertId, toAlign, tmpNodes[2]);
409 Align(triVertId, toAlign, tmpNodes[3]);
410
411 toAlign[0] = 2;
412 toAlign[1] = 0;
413 toAlign[2] = 1;
414 Align(triVertId, toAlign, tmpNodes[1]);
415 }
416
417 // Reordered from NekMesh to put base last
418 for (int j = 1, cnt = 0; j < n - 2; ++j)
419 {
420 for (int i = 1; i < n - j - 1; ++i, ++cnt)
421 {
422 nodeList[tmp[Mode(i, j, 0)]] = tmpNodes[3][cnt];
423 nodeList[tmp[Mode(i, 0, j)]] = tmpNodes[0][cnt];
424 nodeList[tmp[Mode(n - 1 - i - j, i, j)]] = tmpNodes[1][cnt];
425 nodeList[tmp[Mode(0, i, j)]] = tmpNodes[2][cnt];
426 }
427 }
428
429 if (n == 4)
430 {
431 return nodeList;
432 }
433
434 // Finally, recurse on interior volume
435 std::vector<long long> intNodes, tmpInt;
436 for (int i = offset; i < nTet; ++i)
437 {
438 intNodes.emplace_back(nodes[i]);
439 }
440
441 tmpInt = tetTensorNodeOrdering(intNodes);
442
443 for (int k = 1, cnt = 0; k < n - 2; ++k)
444 {
445 for (int j = 1; j < n - k - 1; ++j)
446 {
447 for (int i = 1; i < n - k - j - 1; ++i)
448 {
449 nodeList[tmp[Mode(i, j, k)]] = tmpInt[cnt++];
450 }
451 }
452 }
453
454 return nodeList;
455}
456
457// This prism mapping is NOT using the standard spectral mapping! The point
458// output from ProcessEquiSpacedOutput is not the same as the rest of the code.
459std::vector<long long> prismTensorNodeOrdering(
460 const std::vector<long long> &nodes)
461{
462 int nN = static_cast<int>(nodes.size());
463 int n = static_cast<int>(std::cbrt(2 * nN));
464
465 std::vector<long long> nodeList(nN);
466 int nQuad = n * n;
467 int nPrism = n * n * (n + 1) / 2;
468 int edge = n - 2;
469
470 // Vertices
471 nodeList[0] = nodes[0];
472 if (n > 1)
473 {
474 nodeList[nPrism - n] = nodes[1];
475 nodeList[n - 1] = nodes[2];
476 nodeList[nQuad - n] = nodes[3];
477 nodeList[nPrism - 1] = nodes[4];
478 nodeList[nQuad - 1] = nodes[5];
479 }
480
481 if (n == 2)
482 {
483 return nodeList;
484 }
485
486 int nPts = 6;
487 int cnt = 0;
488 for (int i = 0; i < n - 2; ++i)
489 {
490 // Triangle 1 edges
491 cnt += n * (n - i); // This is adding the quads as they reduce a side by
492 // one each time
493 nodeList[cnt] = nodes[nPts + i]; // Edge 1
494 nodeList[cnt + edge - i] = nodes[nPts + 2 * edge - i - 1]; // Edge 2
495 nodeList[i + 1] = nodes[nPts + 3 * edge - i - 1]; // Edge 3
496 }
497
498 cnt = nQuad;
499 for (int i = 1; i < n - 1; ++i)
500 {
501 // Triangle 2 edges
502 cnt += n * (n - i); // This is adding the quads as they reduce a side by
503 // one each time
504 nodeList[cnt - n + i] = nodes[nPts + 3 * edge + i - 1]; // Edge 1 (4)
505 nodeList[cnt - 1] = nodes[nPts + 5 * edge - i]; // Edge 2 (5)
506 nodeList[nQuad - i - 1] = nodes[nPts + 5 * edge + i - 1]; // Edge 3 (6)
507 }
508
509 // Vertical edges
510 for (int i = 1; i < n - 1; ++i)
511 {
512 // Vertical prism edges
513 nodeList[n * i] = nodes[nPts + 6 * edge + i - 1]; // Edge 1 (7)
514 nodeList[nPrism - n + i] = nodes[nPts + 7 * edge + i - 1]; // Edge 2 (8)
515 nodeList[n * i + n - 1] = nodes[nPts + 8 * edge + i - 1]; // Edge 3 (9)
516 }
517
518 // Do quad faces first as when n == 3 we have no tri faces
519 int nSmallTri = (n - 3) * (n - 2) / 2;
520 int nSmallQuad = (n - 2) * (n - 2);
521 nPts = 6 + 9 * edge + 2 * nSmallTri;
522 cnt = nQuad;
523 for (int i = 1; i < n - 1; ++i)
524 {
525 for (int j = 0; j < n - 2; ++j)
526 {
527 nodeList[cnt + (n - i) + (n - i) * j] =
528 nodes[nPts + j * edge + (i - 1)];
529
530 nodeList[cnt + 2 * (n - i) - 1 + (n - i) * j] =
531 nodes[nPts + nSmallQuad + (j + 1) * edge - i];
532
533 nodeList[i * n + j + 1] =
534 nodes[nPts + 2 * nSmallQuad + i * edge - j - 1];
535 }
536
537 cnt += n * (n - i);
538 }
539
540 if (n == 3)
541 {
542 return nodeList;
543 }
544
545 // Triangular alignment
546 std::vector<long long> tmpNodes(nSmallTri);
547 std::iota(tmpNodes.begin(), tmpNodes.end(), 0);
548 if (n > 4)
549 {
550 std::vector<long long> triVertId(3), toAlign(3);
551 triVertId[0] = 0;
552 triVertId[1] = 1;
553 triVertId[2] = 2;
554
555 toAlign[0] = 0;
556 toAlign[1] = 2;
557 toAlign[2] = 1;
558 Align(triVertId, toAlign, tmpNodes);
559 }
560
561 // Triangle face 1
562 nPts = 6 + 9 * edge;
563 cnt = nQuad;
564 int cnt2 = 0;
565 for (int i = 1; i < n - 2; ++i)
566 {
567 for (int j = 1; j < n - i - 1; ++j)
568 {
569 nodeList[cnt + j] = nPts + tmpNodes[cnt2++];
570 }
571
572 cnt += n * (n - i);
573 }
574
575 // Triangle face 2
576 cnt = nQuad;
577 cnt2 = 0;
578 for (int i = 1; i < n - 2; ++i)
579 {
580 cnt += n * (n - i);
581 for (int j = 1; j < n - i - 1; ++j)
582 {
583 nodeList[cnt - (n - i) + j] = nPts + nSmallTri + tmpNodes[cnt2++];
584 }
585 }
586
587 // Triangular volume (uses same alignment mapping)
588 nPts = 6 + 9 * edge + 2 * nSmallTri + 3 * nSmallQuad;
589 for (int k = 1; k < n - 1; ++k)
590 {
591 cnt = nQuad;
592 cnt2 = 0;
593 for (int i = 1; i < n - 2; ++i)
594 {
595 for (int j = 1; j < n - i - 1; ++j)
596 {
597 nodeList[cnt + k * (n - i) + j] = nPts + tmpNodes[cnt2++];
598 }
599
600 cnt += n * (n - i);
601 }
602
603 nPts += nSmallTri;
604 }
605
606 return nodeList;
607}
608
609std::vector<long long> hexTensorNodeOrdering(
610 const std::vector<long long> &nodes)
611{
612 int nN = static_cast<int>(nodes.size());
613 int n = static_cast<int>(std::cbrt(nN));
614
615 std::vector<long long> nodeList(nN);
616
617 // Vertices
618 nodeList[0] = nodes[0];
619 if (n == 1)
620 {
621 return nodeList;
622 }
623
624 // Vertices: same order as Nektar++
625 nodeList[n - 1] = nodes[1];
626 nodeList[n * n - 1] = nodes[2];
627 nodeList[n * (n - 1)] = nodes[3];
628 nodeList[n * n * (n - 1)] = nodes[4];
629 nodeList[n - 1 + n * n * (n - 1)] = nodes[5];
630 nodeList[n * n - 1 + n * n * (n - 1)] = nodes[6];
631 nodeList[n * (n - 1) + n * n * (n - 1)] = nodes[7];
632
633 if (n == 2)
634 {
635 return nodeList;
636 }
637
638 int hexEdges[12][2] = {{0, 1},
639 {n - 1, n},
640 {n * n - 1, -1},
641 {n * (n - 1), -n},
642 {0, n * n},
643 {n - 1, n * n},
644 {n * n - 1, n * n},
645 {n * (n - 1), n * n},
646 {n * n * (n - 1), 1},
647 {n * n * (n - 1) + n - 1, n},
648 {n * n * n - 1, -1},
649 {n * n * (n - 1) + n * (n - 1), -n}};
650 int hexFaces[6][3] = {{0, 1, n}, {0, 1, n * n},
651 {n - 1, n, n * n}, {n * (n - 1), 1, n * n},
652 {0, n, n * n}, {n * n * (n - 1), 1, n}};
653
654 int gmshToNekEdge[12] = {0, 1, -2, -3, 8, 9, -10, -11, 4, 5, 6, 7};
655
656 // Edges
657 int offset = 8;
658 for (int i : gmshToNekEdge)
659 {
660 int e = abs(i);
661
662 if (i >= 0)
663 {
664 for (int j = 1; j < n - 1; ++j)
665 {
666 nodeList[hexEdges[e][0] + j * hexEdges[e][1]] = nodes[offset++];
667 }
668 }
669 else
670 {
671 for (int j = 1; j < n - 1; ++j)
672 {
673 nodeList[hexEdges[e][0] + (n - j - 1) * hexEdges[e][1]] =
674 nodes[offset++];
675 }
676 }
677 }
678
679 // Faces
680 int gmsh2NekFace[6] = {4, 2, 1, 3, 0, 5};
681
682 // Map which defines orientation between Gmsh and Nektar++ faces.
683 StdRegions::Orientation faceOrient[6] = {
690
691 for (int i = 0; i < 6; ++i)
692 {
693 int n2 = (n - 2) * (n - 2);
694 int face = gmsh2NekFace[i];
695 offset = 8 + 12 * (n - 2) + i * n2;
696
697 // Create a list of interior face nodes for this face only.
698 std::vector<long long> faceNodes(n2);
699 for (int j = 0; j < n2; ++j)
700 {
701 faceNodes[j] = nodes[offset + j];
702 }
703
704 // Now get the reordering of this face, which puts Gmsh
705 // recursive ordering into Nektar++ row-by-row order.
706 std::vector<long long> tmp(n2);
707
708 // Finally reorient the face according to the geometry
709 // differences.
710 if (faceOrient[i] == StdRegions::eDir1FwdDir1_Dir2FwdDir2)
711 {
712 // Orientation is the same, just copy.
713 tmp = faceNodes;
714 }
715 else if (faceOrient[i] == StdRegions::eDir1FwdDir2_Dir2FwdDir1)
716 {
717 // Tranposed faces
718 for (int j = 0; j < n - 2; ++j)
719 {
720 for (int k = 0; k < n - 2; ++k)
721 {
722 tmp[j * (n - 2) + k] = faceNodes[k * (n - 2) + j];
723 }
724 }
725 }
726 else if (faceOrient[i] == StdRegions::eDir1BwdDir1_Dir2FwdDir2)
727 {
728 for (int j = 0; j < n - 2; ++j)
729 {
730 for (int k = 0; k < n - 2; ++k)
731 {
732 tmp[j * (n - 2) + k] = faceNodes[j * (n - 2) + (n - k - 3)];
733 }
734 }
735 }
736
737 // Now put this into the right place in the output array
738 for (int k = 1; k < n - 1; ++k)
739 {
740 for (int j = 1; j < n - 1; ++j)
741 {
742 nodeList[hexFaces[face][0] + j * hexFaces[face][1] +
743 k * hexFaces[face][2]] =
744 faceNodes[(k - 1) * (n - 2) + j - 1];
745 }
746 }
747 }
748
749 // Finally, recurse on interior volume
750 std::vector<long long> intNodes, tmpInt;
751 for (int i = 8 + 12 * (n - 2) + 6 * (n - 2) * (n - 2); i < n * n * n; ++i)
752 {
753 intNodes.push_back(nodes[i]);
754 }
755
756 if (!intNodes.empty())
757 {
758 tmpInt = hexTensorNodeOrdering(intNodes);
759 for (int k = 1, cnt = 0; k < n - 1; ++k)
760 {
761 for (int j = 1; j < n - 1; ++j)
762 {
763 for (int i = 1; i < n - 1; ++i)
764 {
765 nodeList[i + j * n + k * n * n] = tmpInt[cnt++];
766 }
767 }
768 }
769 }
770
771 return nodeList;
772}
773
774std::vector<long long> lowOrderMapping(int nDim, Array<OneD, int> nquad)
775{
776 std::vector<long long> p;
777 switch (nDim)
778 {
779 case 3:
780 for (int j = 0; j < nquad[0] - 1; ++j)
781 {
782 for (int k = 0; k < nquad[1] - 1; ++k)
783 {
784 for (int l = 0; l < nquad[2] - 1; ++l)
785 {
786 p.insert(
787 p.end(),
788 {l * nquad[0] * nquad[1] + k * nquad[0] + j,
789 l * nquad[0] * nquad[1] + k * nquad[0] + j + 1,
790 l * nquad[0] * nquad[1] + (k + 1) * nquad[0] + j +
791 1,
792 l * nquad[0] * nquad[1] + (k + 1) * nquad[0] + j,
793 (l + 1) * nquad[0] * nquad[1] + k * nquad[0] + j,
794 (l + 1) * nquad[0] * nquad[1] + k * nquad[0] + j +
795 1,
796 (l + 1) * nquad[0] * nquad[1] +
797 (k + 1) * nquad[0] + j + 1,
798 (l + 1) * nquad[0] * nquad[1] +
799 (k + 1) * nquad[0] + j});
800 }
801 }
802 }
803 break;
804 case 2:
805 for (int j = 0; j < nquad[0] - 1; ++j)
806 {
807 for (int k = 0; k < nquad[1] - 1; ++k)
808 {
809 p.insert(p.end(), {k * nquad[0] + j, k * nquad[0] + j + 1,
810 (k + 1) * nquad[0] + j + 1,
811 (k + 1) * nquad[0] + j});
812 }
813 }
814 break;
815 case 1:
816 for (int j = 0; j < nquad[0] - 1; ++j)
817 {
818 p.insert(p.end(), {j, j + 1});
819 }
820 break;
821 default:
822 break;
823 }
824
825 return p;
826}
827
828int GetHighOrderVtkCellType([[maybe_unused]] int sType)
829{
830#if VTK_MAJOR_VERSION >= 8
831 // For deformed elements this is a map of shape type to VTK type
832 static const std::map<int, int> vtkCellType = {
833 {{LibUtilities::eSegment, VTK_LAGRANGE_CURVE},
834 {LibUtilities::eTriangle, VTK_LAGRANGE_TRIANGLE},
835 {LibUtilities::eQuadrilateral, VTK_LAGRANGE_QUADRILATERAL},
836 {LibUtilities::eTetrahedron, VTK_LAGRANGE_TETRAHEDRON},
837 {LibUtilities::ePyramid, VTK_LAGRANGE_PYRAMID},
838 {LibUtilities::ePrism, VTK_LAGRANGE_WEDGE},
839 {LibUtilities::eHexahedron, VTK_LAGRANGE_HEXAHEDRON}}};
840
841 return vtkCellType.at(sType);
842#else
843 NEKERROR(
845 "High-order VTK output requires minimum VTK library version of 8.0")
846 return 0;
847#endif
848}
849
850} // namespace
851
852vtkSmartPointer<vtkUnstructuredGrid> OutputVtk::OutputFromExpLowOrder()
853{
854 // Mesh file to output
855 vtkSmartPointer<vtkUnstructuredGrid> vtkMesh =
856 vtkSmartPointer<vtkUnstructuredGrid>::New();
857
858 // Cache ordering so we aren't recreating mappings
859 std::unordered_map<size_t, std::vector<long long>> mappingCache;
860
861 // Choose cell types and num quad points based on dimension
862 int nDim = m_f->m_exp[0]->GetShapeDimension();
863 int nHomoDir = m_f->m_numHomogeneousDir;
864
865 auto type = (nDim + nHomoDir == 3) ? VTK_HEXAHEDRON
866 : (nDim + nHomoDir == 2) ? VTK_QUAD
867 : VTK_LINE;
868 int nQpts = (nDim + nHomoDir == 3) ? 8 : (nDim + nHomoDir == 2) ? 4 : 2;
869
870 // Fill homogeneous info
871 if (nHomoDir != 0)
872 {
873 ASSERTL0(nHomoDir == 1 &&
874 m_f->m_exp[0]->GetExpType() == MultiRegions::e3DH1D,
875 "Only regular expansions and the 3DH1D homogeneous expansion "
876 "are supported in the new VTK writer. Please use the 'legacy' "
877 "option for all other expansion types.")
878
879 m_numPlanes = m_f->m_exp[0]->GetHomogeneousBasis()->GetNumModes();
880 // Extra plane if fourier
881 m_extraPlane = (m_f->m_exp[0]->GetHomogeneousBasis()->GetBasisType() ==
883 m_f->m_exp[0]->GetHomogeneousBasis()->GetPointsType() ==
885 }
886
887 // Insert points
888 vtkNew<vtkPoints> vtkPoints;
889 vtkPoints->SetDataType(VTK_DOUBLE);
890
891 int offset = 0;
892 int nElmts = static_cast<int>(m_f->m_exp[0]->GetNumElmts());
893 for (int i = 0; i < nElmts; ++i)
894 {
895 Array<OneD, int> nquad(3, 0);
896 for (int j = 0; j < nDim; ++j)
897 {
898 nquad[j] = m_f->m_exp[0]->GetExp(i)->GetNumPoints(j);
899 }
900
901 // Add homogeneous direction num points
902 if (nHomoDir != 0)
903 {
904 nquad[nDim] = m_numPlanes;
905
906 // Add extra plane if fourier
907 if (m_extraPlane)
908 {
909 nquad[nDim]++;
910 }
911 }
912
913 // Get total points by multiplying all nquads together (accounts for
914 // homo)
915 int nPts = nquad[0];
916 for (int j = 1; j < nDim + nHomoDir; ++j)
917 {
918 nPts *= nquad[j];
919 }
920
921 // Get all coordinates at quadrature points in this element
922 Array<OneD, NekDouble> x(nPts, 0.0), y(nPts, 0.0), z(nPts, 0.0);
923 m_f->m_exp[0]->GetCoords(i, x, y, z);
924
925 // If add extra plane for fourier need to fill last plane coordinates
926 if (m_extraPlane)
927 {
928 // Copy x & y to extra plane
930 Vmath::Vcopy(nquad[0] * nquad[1], x, 1,
931 tmp = x + (nquad[2] - 1) * nquad[0] * nquad[1], 1);
932 Vmath::Vcopy(nquad[0] * nquad[1], y, 1,
933 tmp = y + (nquad[2] - 1) * nquad[0] * nquad[1], 1);
934
935 // Fill z on extra plane
936 NekDouble zDouble = z[nquad[0] * nquad[1] * (nquad[2] - 1) - 1] +
937 (z[nquad[0] * nquad[1]] - z[0]);
938 Vmath::Fill(nquad[0] * nquad[1], zDouble,
939 tmp = z + (nquad[2] - 1) * nquad[0] * nquad[1], 1);
940 }
941
942 // Insert points in to vtk mesh
943 for (int j = 0; j < nPts; ++j)
944 {
945 vtkPoints->InsertNextPoint(x[j], y[j], z[j]);
946 }
947
948 // Insert elements
949 auto oIt = mappingCache.find(key3(nquad[0], nquad[1], nquad[2]));
950 if (oIt == mappingCache.end())
951 {
952 auto p = lowOrderMapping(nDim + nHomoDir, nquad);
953 oIt = mappingCache
954 .insert(
955 std::make_pair(key3(nquad[0], nquad[1], nquad[2]), p))
956 .first;
957 }
958
959 auto p = oIt->second;
960 std::for_each(p.begin(), p.end(),
961 [offset](long long &d) { d += offset; });
962 for (int j = 0; j < p.size(); j += nQpts)
963 {
964 vtkMesh->InsertNextCell(type, nQpts, &p[j]);
965 }
966
967 offset += nPts;
968 }
969
970 vtkMesh->SetPoints(vtkPoints.GetPointer());
971
972 return vtkMesh;
973}
974
976 po::variables_map &vm, std::string &filename,
977 vtkSmartPointer<vtkUnstructuredGrid> &vtkMesh)
978{
979 // Insert field information we iterate by element from first plane to last,
980 // while the fld file iterates by plane from first element to last
981 int nElmts = static_cast<int>(m_f->m_exp[0]->GetNumElmts());
982 int numPtsPerPlane = m_f->m_exp[0]->GetTotPoints() / m_numPlanes;
983 for (int v = 0; v < m_f->m_variables.size(); ++v)
984 {
985 vtkNew<vtkDoubleArray> fieldData;
986 for (int i = 0; i < nElmts; ++i)
987 {
988 int elmtOffset = m_f->m_exp[v]->GetPhys_Offset(i);
989 int nPtsPerElmtPerPlane = m_f->m_exp[v]->GetExp(i)->GetTotPoints();
990
991 for (int j = 0; j < m_numPlanes; ++j)
992 {
993 int planeOffset = j * numPtsPerPlane;
994 for (int k = 0; k < nPtsPerElmtPerPlane; ++k)
995 {
996 fieldData->InsertNextValue(
997 m_f->m_exp[v]->GetPhys()[elmtOffset + planeOffset + k]);
998 }
999 }
1000
1001 // if extra plane we copy the first plane values in to last plane
1002 if (m_extraPlane)
1003 {
1004 for (int k = 0; k < nPtsPerElmtPerPlane; ++k)
1005 {
1006 fieldData->InsertNextValue(
1007 m_f->m_exp[v]->GetPhys()[elmtOffset + k]);
1008 }
1009 }
1010 }
1011
1012 fieldData->SetName(&m_f->m_variables[v][0]);
1013 vtkMesh->GetPointData()->AddArray(fieldData.GetPointer());
1014 }
1015
1016 if (!m_prohibitWrite)
1017 {
1018 WriteVTK(vtkMesh, filename, vm);
1019 }
1020}
1021
1023 std::string &filename)
1024{
1025 ASSERTL0(
1026 m_f->m_numHomogeneousDir == 0,
1027 "Multi block VTK is not implemented for homogeneous expansion types.")
1028
1029 ASSERTL0(m_f->m_comm->GetSpaceComm()->IsSerial(),
1030 "Multi block VTK is not implemented in parallel.")
1031
1032 int dim = m_f->m_graph->GetMeshDimension();
1033
1034 // Create mappings from geometry id to expansion ids
1035 std::array<std::map<int, std::pair<int, int>>, 4> geomIdToExpId;
1036 int nElmts = static_cast<int>(m_f->m_exp[0]->GetNumElmts());
1037 for (int i = 0; i < nElmts; ++i)
1038 {
1039 auto geom = m_f->m_exp[0]->GetExp(i)->GetGeom();
1040 geomIdToExpId[geom->GetShapeDim()][geom->GetGlobalID()] =
1041 std::make_pair(i, -1);
1042
1043 for (int j = 0; j < geom->GetNumFaces(); ++j)
1044 {
1045 geomIdToExpId[2][geom->GetFid(j)] = std::make_pair(i, j);
1046 }
1047
1048 for (int j = 0; j < geom->GetNumEdges(); ++j)
1049 {
1050 geomIdToExpId[1][geom->GetEid(j)] = std::make_pair(i, j);
1051 }
1052 }
1053
1054 // Cache ordering so we aren't recreating mappings
1055 std::unordered_map<size_t, std::vector<long long>> mappingCache;
1056
1057 std::map<int, vtkNew<vtkUnstructuredGrid>> vtkMesh;
1058 std::map<int, SpatialDomains::CompositeSharedPtr> composites =
1059 m_f->m_graph->GetComposites();
1060 std::map<int, std::string> compositeNames;
1061 for (auto &comp : composites)
1062 {
1063 // Vector of field data
1064 std::vector<vtkNew<vtkDoubleArray>> fieldData(m_f->m_variables.size());
1065
1066 // Insert points
1067 vtkNew<vtkPoints> vtkPoints;
1068 vtkPoints->SetDataType(VTK_DOUBLE);
1069
1070 int compId = comp.first;
1071 std::vector<std::shared_ptr<SpatialDomains::Geometry>> geomVec =
1072 comp.second->m_geomVec;
1073
1074 unsigned int offset = 0;
1075 for (auto &geom : geomVec)
1076 {
1077 int geomId = geom->GetGlobalID();
1078 int sDim = geom->GetShapeDim();
1079 auto type = (sDim == 3) ? VTK_HEXAHEDRON
1080 : (sDim == 2) ? VTK_QUAD
1081 : VTK_LINE;
1082 int nQpts = (sDim == 3) ? 8 : (sDim == 2) ? 4 : 2;
1083
1085 m_f->m_exp[0]->GetExp(geomIdToExpId[sDim][geomId].first);
1086
1087 unsigned int nPts = exp->GetTotPoints();
1088 Array<OneD, NekDouble> x(nPts, 0.0), y(nPts, 0.0), z(nPts, 0.0);
1089 exp->GetCoords(x, y, z);
1090
1091 int offsetPhys = m_f->m_exp[0]->GetPhys_Offset(
1092 geomIdToExpId[sDim][geomId].first);
1093 if (sDim == dim)
1094 {
1095 for (int j = 0; j < nPts; ++j)
1096 {
1097 vtkPoints->InsertNextPoint(x[j], y[j], z[j]);
1098
1099 // Add field data
1100 for (int k = 0; k < m_f->m_variables.size(); ++k)
1101 {
1102 fieldData[k]->InsertNextValue(
1103 m_f->m_exp[k]->GetPhys()[j + offsetPhys]);
1104 }
1105 }
1106 }
1107 else
1108 {
1109 Array<OneD, int> pointsMap;
1110 exp->GetTracePhysMap(geomIdToExpId[sDim][geomId].second,
1111 pointsMap);
1112 for (int j : pointsMap)
1113 {
1114 vtkPoints->InsertNextPoint(x[j], y[j], z[j]);
1115
1116 // Add field data
1117 for (int k = 0; k < m_f->m_variables.size(); ++k)
1118 {
1119 fieldData[k]->InsertNextValue(
1120 m_f->m_exp[k]->GetPhys()[offsetPhys + j]);
1121 }
1122 }
1123
1124 exp = exp->GetTraceExp(geomIdToExpId[sDim][geomId].second);
1125 nPts = pointsMap.size();
1126 }
1127
1128 Array<OneD, int> nquad(3, 0);
1129 for (int j = 0; j < sDim; ++j)
1130 {
1131 nquad[j] = exp->GetNumPoints(j);
1132 }
1133
1134 auto oIt = mappingCache.find(key3(nquad[0], nquad[1], nquad[2]));
1135 if (oIt == mappingCache.end())
1136 {
1137 auto p = lowOrderMapping(sDim, nquad);
1138 oIt = mappingCache
1139 .insert(std::make_pair(
1140 key3(nquad[0], nquad[1], nquad[2]), p))
1141 .first;
1142 }
1143
1144 auto p = oIt->second;
1145 std::for_each(p.begin(), p.end(),
1146 [offset](long long &d) { d += offset; });
1147
1148 for (int j = 0; j < p.size(); j += nQpts)
1149 {
1150 vtkMesh[compId]->InsertNextCell(type, nQpts, &p[j]);
1151 }
1152
1153 offset += nPts;
1154 }
1155
1156 vtkMesh[compId]->SetPoints(vtkPoints.GetPointer());
1157
1158 // Add all fields to vtkMesh
1159 for (int k = 0; k < m_f->m_variables.size(); ++k)
1160 {
1161 fieldData[k]->SetName(&m_f->m_variables[k][0]);
1162 vtkMesh[compId]->GetPointData()->AddArray(
1163 fieldData[k].GetPointer());
1164 }
1165
1166 // Find composite names if exists and store in map
1167 // Set name as boundary label if it exists otherwise index ID
1168 compositeNames[compId] = "Composite ID " + std::to_string(compId);
1169 auto clabels = m_f->m_graph->GetCompositesLabels();
1170 auto oIt = clabels.find(compId);
1171 if (oIt != clabels.end())
1172 {
1173 compositeNames[compId] = oIt->second;
1174 }
1175 }
1176
1177 // Main multi-block set
1178 vtkNew<vtkMultiBlockDataSet> mainBlock;
1179
1180 std::set<int> compSet;
1181
1182 // Fill domain multi blocks from composites
1183 vtkNew<vtkMultiBlockDataSet> mainDomainBlock;
1184 auto domains = m_f->m_graph->GetDomain();
1185 std::vector<vtkNew<vtkMultiBlockDataSet>> domainMultiBlocks(domains.size());
1186 for (int i = 0; i < domains.size(); ++i)
1187 {
1188 auto dom = domains[i];
1189
1190 // Loop over composites and see if in domain
1191 for (auto &comp : composites)
1192 {
1193 int compId = comp.first;
1194 if (dom.find(compId) != dom.end())
1195 {
1196 unsigned int nBlock = domainMultiBlocks[i]->GetNumberOfBlocks();
1197 domainMultiBlocks[i]->SetBlock(nBlock,
1198 vtkMesh[compId].GetPointer());
1199 domainMultiBlocks[i]->GetMetaData(nBlock)->Set(
1200 vtkCompositeDataSet::NAME(),
1201 compositeNames[compId].c_str());
1202 compSet.insert(compId);
1203 }
1204 }
1205
1206 unsigned int nBlock = mainDomainBlock->GetNumberOfBlocks();
1207 mainDomainBlock->SetBlock(nBlock, domainMultiBlocks[i].GetPointer());
1208 mainDomainBlock->GetMetaData(nBlock)->Set(
1209 vtkCompositeDataSet::NAME(),
1210 ("Domain ID " + std::to_string(i)).c_str());
1211 }
1212
1213 if (mainDomainBlock->GetNumberOfBlocks() != 0)
1214 {
1215 auto nBlock = static_cast<unsigned int>(mainBlock->GetNumberOfBlocks());
1216 mainBlock->SetBlock(nBlock, mainDomainBlock.GetPointer());
1217 mainBlock->GetMetaData(nBlock)->Set(vtkCompositeDataSet::NAME(),
1218 "Domains");
1219 }
1220
1221 // Fill boundary multi blocks from composites
1223 m_f->m_exp[0]->GetGraph());
1225 bcs.GetBoundaryRegions();
1226
1227 vtkNew<vtkMultiBlockDataSet> mainBoundaryBlock;
1228 std::vector<vtkNew<vtkMultiBlockDataSet>> boundaryMultiBlocks(
1229 bregions.size());
1230 int cnt = 0;
1231 for (auto &boundary : bregions)
1232 {
1233 // Loop over composites and see if in boundary
1234 for (auto &comp : composites)
1235 {
1236 int compId = comp.first;
1237 if (boundary.second->find(compId) != boundary.second->end())
1238 {
1239 unsigned int nBlock =
1240 boundaryMultiBlocks[cnt]->GetNumberOfBlocks();
1241 boundaryMultiBlocks[cnt]->SetBlock(
1242 nBlock, vtkMesh[compId].GetPointer());
1243 boundaryMultiBlocks[cnt]->GetMetaData(nBlock)->Set(
1244 vtkCompositeDataSet::NAME(),
1245 compositeNames[compId].c_str());
1246 compSet.insert(compId);
1247 }
1248 }
1249
1250 unsigned int nBlock = mainBoundaryBlock->GetNumberOfBlocks();
1251 mainBoundaryBlock->SetBlock(nBlock,
1252 boundaryMultiBlocks[cnt++].GetPointer());
1253
1254 // Set name as boundary label if it exists otherwise index ID
1255 std::string name = "Boundary ID " + std::to_string(boundary.first);
1256 auto blabels = bcs.GetBoundaryLabels();
1257 auto oIt = blabels.find(boundary.first);
1258 if (oIt != blabels.end())
1259 {
1260 name = oIt->second;
1261 }
1262
1263 mainBoundaryBlock->GetMetaData(nBlock)->Set(vtkCompositeDataSet::NAME(),
1264 name.c_str());
1265 }
1266
1267 if (mainBoundaryBlock->GetNumberOfBlocks() != 0)
1268 {
1269 auto nBlock = static_cast<unsigned int>(mainBlock->GetNumberOfBlocks());
1270 mainBlock->SetBlock(nBlock, mainBoundaryBlock.GetPointer());
1271 mainBlock->GetMetaData(nBlock)->Set(vtkCompositeDataSet::NAME(),
1272 "Boundaries");
1273 }
1274
1275 // Include all other composites (not domains or boundaries)
1276 vtkNew<vtkMultiBlockDataSet> compsMultiBlocks;
1277 cnt = 0;
1278 for (auto &comp : composites)
1279 {
1280 int compId = comp.first;
1281 if (compSet.find(compId) == compSet.end())
1282 {
1283 unsigned int nBlock = compsMultiBlocks->GetNumberOfBlocks();
1284 compsMultiBlocks->SetBlock(nBlock, vtkMesh[compId].GetPointer());
1285 compsMultiBlocks->GetMetaData(nBlock)->Set(
1286 vtkCompositeDataSet::NAME(), compositeNames[compId].c_str());
1287 }
1288 }
1289
1290 if (compsMultiBlocks->GetNumberOfBlocks() != 0)
1291 {
1292 auto nBlock = static_cast<unsigned int>(mainBlock->GetNumberOfBlocks());
1293 mainBlock->SetBlock(nBlock, compsMultiBlocks.GetPointer());
1294 mainBlock->GetMetaData(nBlock)->Set(vtkCompositeDataSet::NAME(),
1295 "Other composites");
1296 }
1297
1298 if (!m_prohibitWrite)
1299 {
1300 WriteVTK(mainBlock.GetPointer(), filename, vm);
1301 }
1302}
1303
1304vtkSmartPointer<vtkUnstructuredGrid> OutputVtk::OutputFromExpHighOrder(
1305 po::variables_map &vm)
1306{
1307 ASSERTL0(
1308 m_f->m_numHomogeneousDir == 0,
1309 "High order VTK is not implemented for homogeneous expansion types.")
1310
1311 // Save shapetypes before convert to equispaced because that nukes the
1312 // explist
1313 std::vector<LibUtilities::ShapeType> sType;
1314 for (auto &i : *m_f->m_exp[0]->GetExp())
1315 {
1316 sType.emplace_back(i->GetGeom()->GetShapeType());
1317 }
1318
1319 // Convert expansion to an equispaced points field
1320 auto equispaced =
1322 equispaced->Process(vm);
1323
1324 // Insert points
1325 vtkNew<vtkPoints> vtkPoints;
1326 LibUtilities::PtsFieldSharedPtr fPts = m_f->m_fieldPts;
1327 vtkPoints->SetDataType(VTK_DOUBLE);
1328
1330 fPts->GetPts(pts);
1331
1332 int dim = static_cast<int>(fPts->GetDim());
1333 int nPts = static_cast<int>(fPts->GetNpoints());
1334 switch (dim)
1335 {
1336 case 3:
1337 for (int i = 0; i < nPts; ++i)
1338 {
1339 vtkPoints->InsertNextPoint(pts[0][i], pts[1][i], pts[2][i]);
1340 }
1341 break;
1342 case 2:
1343 for (int i = 0; i < nPts; ++i)
1344 {
1345 vtkPoints->InsertNextPoint(pts[0][i], pts[1][i], 0.0);
1346 }
1347 break;
1348 case 1:
1349 for (int i = 0; i < nPts; ++i)
1350 {
1351 vtkPoints->InsertNextPoint(pts[0][i], 0.0, 0.0);
1352 }
1353 break;
1354 default:
1355 break;
1356 }
1357
1358 // Mesh file to output
1359 vtkSmartPointer<vtkUnstructuredGrid> vtkMesh =
1360 vtkSmartPointer<vtkUnstructuredGrid>::New();
1361 vtkMesh->SetPoints(vtkPoints.GetPointer());
1362
1363 // Cache ordering for shape type & npts so we aren't recreating mappings
1364 std::unordered_map<size_t, std::vector<long long>> mappingCache;
1365
1366 // Get offset per element in a vector from ppe
1367 std::vector<int> ppe = m_f->m_fieldPts->GetPointsPerElement();
1368 Array<OneD, int> ppeOffset(ppe.size() + 1, 0.0);
1369 std::partial_sum(ppe.begin(), ppe.end(), &ppeOffset[1]);
1370
1371 // Insert elements
1372 for (int i = 0; i < ppe.size(); ++i)
1373 {
1374 // Construct inverse of input reordering.
1375 // First try to find it in our mapping cache.
1376 auto oIt = mappingCache.find(key2(sType[i], ppe[i]));
1377 if (oIt == mappingCache.end())
1378 {
1379 std::vector<long long> p(ppe[i]);
1380 std::iota(p.begin(), p.end(), 0);
1381 switch (sType[i])
1382 {
1384 p = quadTensorNodeOrdering(p);
1385 break;
1387 p = triTensorNodeOrdering(p);
1388 break;
1390 p = tetTensorNodeOrdering(p);
1391 break;
1393 p = hexTensorNodeOrdering(p);
1394 break;
1396 p = prismTensorNodeOrdering(p);
1397 break;
1398 default:
1400 "High-order VTU output not set up for the " +
1401 static_cast<std::string>(
1402 LibUtilities::ShapeTypeMap[sType[i]]) +
1403 " shape type.");
1404 break;
1405 }
1406
1407 // Invert the ordering as this is for spectral -> recursive (VTU)
1408 std::vector<long long> inv(ppe[i]);
1409 for (int j = 0; j < ppe[i]; ++j)
1410 {
1411 inv[p[j]] = j;
1412 }
1413
1414 oIt =
1415 mappingCache.insert(std::make_pair(key2(sType[i], ppe[i]), inv))
1416 .first;
1417 }
1418
1419 // Add offset to reordering
1420 std::vector<long long> p = oIt->second;
1421 for (long long &j : p)
1422 {
1423 j += ppeOffset[i];
1424 }
1425
1426 vtkMesh->InsertNextCell(GetHighOrderVtkCellType(sType[i]), ppe[i],
1427 &p[0]);
1428 }
1429
1430 return vtkMesh;
1431}
1432
1434 po::variables_map &vm, std::string &filename,
1435 vtkSmartPointer<vtkUnstructuredGrid> &vtkMesh)
1436{
1437 // Convert expansion to an equispaced points field
1438 if (m_cachedMesh)
1439 {
1440 auto equispaced =
1442 equispaced->Process(vm);
1443 }
1444 LibUtilities::PtsFieldSharedPtr fPts = m_f->m_fieldPts;
1445
1446 int nPts = static_cast<int>(fPts->GetNpoints());
1447 int dim = static_cast<int>(fPts->GetDim());
1448
1450 fPts->GetPts(pts);
1451
1452 // Insert field information
1453 for (int i = 0; i < fPts->GetNFields(); ++i)
1454 {
1455 vtkNew<vtkDoubleArray> fieldData;
1456 fieldData->SetArray(&pts[dim + i][0], nPts, 1);
1457 fieldData->SetName(&fPts->GetFieldName(i)[0]);
1458 vtkMesh->GetPointData()->AddArray(fieldData.GetPointer());
1459 }
1460
1461 if (!m_prohibitWrite)
1462 {
1463 WriteVTK(vtkMesh, filename, vm);
1464 }
1465}
1466
1467void OutputVtk::WriteVTK(vtkDataObject *vtkMesh, std::string &filename,
1468 po::variables_map &vm)
1469{
1470 // Initialise base writer class as default structured grid writer
1471 vtkSmartPointer<vtkXMLWriter> vtkMeshWriter =
1472 vtkNew<vtkXMLUnstructuredGridWriter>().GetPointer();
1473
1474 // If multi block we need to switch to the MultiBlock writer
1475 if (m_config["multiblock"].m_beenSet)
1476 {
1477 vtkMeshWriter = vtkNew<vtkXMLMultiBlockDataWriter>().GetPointer();
1478 }
1479
1480 // Choose the correct extension based on which mesh writer is used
1481 int dot = static_cast<int>(filename.find_last_of('.'));
1482 std::string body = filename.substr(0, dot + 1);
1483 filename = body + vtkMeshWriter->GetDefaultFileExtension();
1484
1485 // Set time information if exists
1486 if (!m_f->m_fieldMetaDataMap["Time"].empty())
1487 {
1488 vtkMesh->GetInformation()->Set(
1489 vtkDataObject::DATA_TIME_STEP(),
1490 std::stod(m_f->m_fieldMetaDataMap["Time"]));
1491 }
1492 else if (!m_f->m_fieldMetaDataMap["FinalTime"].empty())
1493 {
1494 vtkMesh->GetInformation()->Set(
1495 vtkDataObject::DATA_TIME_STEP(),
1496 std::stod(m_f->m_fieldMetaDataMap["FinalTime"]));
1497 }
1498
1499 // Write out the new mesh in XML format (don't support legacy
1500 // format here as we still have standard OutputVtk.cpp)
1501 vtkMeshWriter->SetFileName(filename.c_str());
1502
1503#if VTK_MAJOR_VERSION <= 5
1504 vtkMeshWriter->SetInput(vtkMesh);
1505#else
1506 vtkMeshWriter->SetInputData(vtkMesh);
1507#endif
1508
1509 if (m_config["uncompress"].m_beenSet)
1510 {
1511 vtkMeshWriter->SetDataModeToAscii();
1512 }
1513
1514 vtkMeshWriter->Update();
1515
1516 std::cout << "Written file: " << filename << std::endl;
1517
1518 // Output parallel file information if needed - using a manual write.
1519 // We could use the VTK lib to do this, but that requires VTK with MPI
1520 // enabled & messing about with the parallel controller & changing
1521 // our file naming scheme as VTK forces _${proc-number} as a suffix...
1522 if (m_f->m_comm->GetSpaceComm()->TreatAsRankZero() &&
1523 !m_f->m_comm->GetSpaceComm()->IsSerial())
1524 {
1525 WritePVtu(vm);
1526 }
1527}
1528
1529void OutputVtk::WritePVtu(po::variables_map &vm)
1530{
1531 std::string filename = m_config["outfile"].as<std::string>();
1532 int dot = static_cast<int>(filename.find_last_of('.'));
1533 std::string body = filename.substr(0, dot);
1534 filename = body + ".pvtu";
1535
1536 std::ofstream outfile(filename.c_str());
1537
1538 int nprocs = m_f->m_comm->GetSpaceComm()->GetSize();
1539 std::string path =
1541
1542 outfile << "<?xml version=\"1.0\"?>" << endl;
1543 outfile << R"(<VTKFile type="PUnstructuredGrid" version="0.1" )"
1544 << "byte_order=\"LittleEndian\">" << endl;
1545 outfile << " <PUnstructuredGrid GhostLevel=\"0\">" << endl;
1546
1547 // Add time if exists
1548 if (!m_f->m_fieldMetaDataMap["Time"].empty())
1549 {
1550 outfile << " <FieldData> " << endl;
1551 outfile << " <DataArray type=\"Float64\" Name=\"TimeValue\" "
1552 "NumberOfTuples=\"1\" format=\"ascii\">"
1553 << endl;
1554 outfile << m_f->m_fieldMetaDataMap["Time"] << std::endl;
1555 outfile << " </DataArray>" << std::endl;
1556 outfile << " </FieldData> " << endl;
1557 }
1558
1559 // Add point coordinates
1560 outfile << " <PPoints> " << endl;
1561 outfile << R"( <PDataArray type="Float64" NumberOfComponents=")" << 3
1562 << "\"/> " << endl;
1563 outfile << " </PPoints>" << endl;
1564
1565 // Add cell information
1566 outfile << " <PCells>" << endl;
1567 outfile << " <PDataArray type=\"Int64\" Name=\"connectivity\" "
1568 "NumberOfComponents=\"1\"/>"
1569 << endl;
1570 outfile << " <PDataArray type=\"Int64\" Name=\"offsets\" "
1571 "NumberOfComponents=\"1\"/>"
1572 << endl;
1573 outfile << " <PDataArray type=\"UInt8\" Name=\"types\" "
1574 "NumberOfComponents=\"1\"/>"
1575 << endl;
1576 outfile << " </PCells>" << endl;
1577
1578 // Add fields (point data)
1579 outfile << " <PPointData>" << endl;
1580 for (auto &var : m_f->m_variables)
1581 {
1582 outfile << R"( <PDataArray type="Float64" Name=")" << var << "\"/>"
1583 << endl;
1584 }
1585 outfile << " </PPointData>" << endl;
1586
1587 // Add parallel files
1588 for (int i = 0; i < nprocs; ++i)
1589 {
1590 boost::format pad("P%1$07d.vtu");
1591 pad % i;
1592 outfile << " <Piece Source=\"" << path << "/" << pad.str() << "\"/>"
1593 << endl;
1594 }
1595 outfile << " </PUnstructuredGrid>" << endl;
1596 outfile << "</VTKFile>" << endl;
1597
1598 cout << "Written file: " << filename << endl;
1599}
1600
1601void OutputVtk::v_OutputFromData([[maybe_unused]] po::variables_map &vm)
1602{
1604 "OutputVtk can't write using only FieldData. You may need "
1605 "to add a mesh XML file to your input files.");
1606}
1607
1608void OutputVtk::v_OutputFromPts(po::variables_map &vm)
1609{
1611}
1612
1613void OutputVtk::v_OutputFromExp(po::variables_map &vm)
1614{
1615 // Move geometry based on zones data in .xml and time in .fld metadatamap
1616 // Perform movement of zones based on time in field files metadata map
1617 if (m_f->m_graph->GetMovement()->GetMoveFlag())
1618 {
1619 if (!m_f->m_fieldMetaDataMap["Time"].empty())
1620 {
1621 m_f->m_graph->GetMovement()->PerformMovement(
1622 std::stod(m_f->m_fieldMetaDataMap["Time"]));
1623 for (auto &i : m_f->m_exp)
1624 {
1625 i->Reset();
1626 }
1627 }
1628 }
1629
1630 if (m_config["legacy"].m_beenSet)
1631 {
1632 ASSERTL0(!m_config["multiblock"].m_beenSet,
1633 "Multi block VTK is not implemented for legacy output.")
1634
1635 ASSERTL0(!m_config["highorder"].m_beenSet,
1636 "High order VTK is not implemented for legacy output.")
1637
1638 // No caching of mesh data in legacy output
1640 return;
1641 }
1642
1643 std::string filename;
1644
1645 // Extract the output filename and extension
1646 if (!m_prohibitWrite)
1647 {
1648 filename = OutputVtkBase::PrepareOutput(vm);
1649 }
1650
1651 // Save mesh state (if using filter this allows us to only ProcessEquispaced
1652 // if needed)
1653 if (!m_f->m_graph->GetMovement()->GetMoveFlag())
1654 {
1656 }
1657
1658 if (m_config["highorder"].m_beenSet)
1659 {
1660 ASSERTL0(!m_config["multiblock"].m_beenSet,
1661 "Multi block VTK is not implemented for high-order output.")
1662
1663 if (!m_cachedMesh)
1664 {
1666 }
1667
1669 }
1670 else if (m_config["multiblock"].m_beenSet)
1671 {
1672 // No mesh caching for multiblock due to complexity of field data
1673 OutputFromExpLowOrderMultiBlock(vm, filename);
1674 }
1675 else
1676 {
1677 if (!m_cachedMesh)
1678 {
1680 }
1681
1682 AddFieldDataToVTKLowOrder(vm, filename, m_vtkMesh);
1683 }
1684}
1685
1686} // namespace Nektar::FieldUtils
#define ASSERTL0(condition, msg)
Definition: ErrorUtil.hpp:208
#define NEKERROR(type, msg)
Assert Level 0 – Fundamental assert which is used whether in FULLDEBUG, DEBUG or OPT compilation mode...
Definition: ErrorUtil.hpp:202
FieldSharedPtr m_f
Field object.
Definition: Module.h:239
std::map< std::string, ConfigOption > m_config
List of configuration values.
Definition: Module.h:272
Converter from fld to vtk.
Definition: OutputVtkBase.h:46
std::string PrepareOutput(po::variables_map &vm)
fs::path v_GetPath(std::string &filename, po::variables_map &vm) override
void v_OutputFromExp(po::variables_map &vm) override
Write from m_exp to output file.
void v_OutputFromPts(po::variables_map &vm) override
Write from pts to output file.
vtkSmartPointer< vtkUnstructuredGrid > OutputFromExpLowOrder()
Prepare low order VTK output.
Definition: OutputVtk.cpp:852
void AddFieldDataToVTKHighOrder(po::variables_map &vm, std::string &filename, vtkSmartPointer< vtkUnstructuredGrid > &vtkMesh)
Add field data to high order Lagrange VTK output.
Definition: OutputVtk.cpp:1433
void OutputFromExpLowOrderMultiBlock(po::variables_map &vm, std::string &filename)
Prepare low order multi-block VTK output & add field data.
Definition: OutputVtk.cpp:1022
OutputVtk(FieldSharedPtr f)
Definition: OutputVtk.cpp:56
vtkSmartPointer< vtkUnstructuredGrid > m_vtkMesh
Cache file for unstructured grid VTK mesh data.
Definition: OutputVtk.h:85
void v_OutputFromExp(po::variables_map &vm) final
Write from m_exp to output file.
Definition: OutputVtk.cpp:1613
void v_OutputFromPts(po::variables_map &vm) final
Write from pts to output file.
Definition: OutputVtk.cpp:1608
bool m_extraPlane
Flag if extra plane in case of fourier expansion in homogeneous dir.
Definition: OutputVtk.h:91
int m_numPlanes
Number of planes if homogeneous.
Definition: OutputVtk.h:88
void v_OutputFromData(po::variables_map &vm) final
Write from data to output file.
Definition: OutputVtk.cpp:1601
void AddFieldDataToVTKLowOrder(po::variables_map &vm, std::string &filename, vtkSmartPointer< vtkUnstructuredGrid > &vtkMesh)
Add field data to low order VTK output.
Definition: OutputVtk.cpp:975
void WriteVTK(vtkDataObject *vtkMesh, std::string &filename, po::variables_map &vm)
Write VTK file using.
Definition: OutputVtk.cpp:1467
vtkSmartPointer< vtkUnstructuredGrid > OutputFromExpHighOrder(po::variables_map &vm)
Prepare high order Lagrange VTK output.
Definition: OutputVtk.cpp:1304
static ModuleKey m_className
Definition: OutputVtk.h:58
bool m_cachedMesh
Flag if mesh has been cached.
Definition: OutputVtk.h:94
static std::shared_ptr< Module > create(const FieldSharedPtr &f)
Creates an instance of this class.
Definition: OutputVtk.h:53
void WritePVtu(po::variables_map &vm)
Write the parallel .pvtu file.
Definition: OutputVtk.cpp:1529
tKey RegisterCreatorFunction(tKey idKey, CreatorFunction classCreator, std::string pDesc="")
Register a class with the factory.
static std::shared_ptr< DataType > AllocateSharedPtr(const Args &...args)
Allocate a shared pointer from the memory pool.
const std::map< int, std::string > & GetBoundaryLabels(void) const
Definition: Conditions.h:265
const BoundaryRegionCollection & GetBoundaryRegions(void) const
Definition: Conditions.h:234
def copy(self)
Definition: pycml.py:2663
std::shared_ptr< Field > FieldSharedPtr
Definition: Field.hpp:1026
std::pair< ModuleType, std::string > ModuleKey
Definition: Module.h:180
ModuleFactory & GetModuleFactory()
Definition: Module.cpp:47
const char *const ShapeTypeMap[SIZE_ShapeType]
Definition: ShapeType.hpp:75
static std::string PortablePath(const fs::path &path)
create portable path on different platforms for std::filesystem path.
Definition: Filesystem.hpp:56
std::shared_ptr< PtsField > PtsFieldSharedPtr
Definition: PtsField.h:184
@ eFourierEvenlySpaced
1D Evenly-spaced points using Fourier Fit
Definition: PointsType.h:74
@ eFourier
Fourier Expansion .
Definition: BasisType.h:55
std::shared_ptr< Expansion > ExpansionSharedPtr
Definition: Expansion.h:66
std::map< int, BoundaryRegionShPtr > BoundaryRegionCollection
Definition: Conditions.h:210
std::vector< double > z(NPUPPER)
std::vector< double > d(NPUPPER *NPUPPER)
double NekDouble
void Fill(int n, const T alpha, T *x, const int incx)
Fill a vector with a constant value.
Definition: Vmath.hpp:54
void Vcopy(int n, const T *x, const int incx, T *y, const int incy)
Definition: Vmath.hpp:825
STL namespace.
scalarT< T > abs(scalarT< T > in)
Definition: scalar.hpp:298
scalarT< T > sqrt(scalarT< T > in)
Definition: scalar.hpp:294
Represents a command-line configuration option.
Definition: Module.h:129