Loading...
Searching...
No Matches
vtk.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <fstream>
4#include <iomanip>
5#include <iostream>
6#include <stdexcept>
7#include <string>
8#include <variant>
9#include <vector>
10
12
13namespace terra::io {
14
15// Define VTK cell type IDs for clarity
16constexpr int VTK_QUAD = 9;
17constexpr int VTK_QUADRATIC_QUAD = 23;
18
19// Enum to specify the desired element type
21{
24};
25
26/**
27 * @brief Writes a 2D grid of vertices stored in a Kokkos View to a VTK XML
28 * Unstructured Grid file (.vtu) representing a quadrilateral mesh
29 * (linear or quadratic).
30 *
31 * @param filename The path to the output VTK file (.vtu).
32 * @param vertices A Kokkos View containing the vertex coordinates.
33 * Assumed dimensions: (Nx, Ny, 3).
34 * vertices(i, j, 0) = X coordinate of point (i, j)
35 * vertices(i, j, 1) = Y coordinate of point (i, j)
36 * vertices(i, j, 2) = Z coordinate of point (i, j)
37 * Nx = vertices.extent(0), Ny = vertices.extent(1)
38 * For QUADRATIC_QUAD, Nx and Ny must be odd and >= 3.
39 * @param elementType Specifies whether to write linear or quadratic elements.
40 */
41[[deprecated( "Use XDMF output." )]]
43 const std::string& filename,
44 const Kokkos::View< double** [3] >& vertices,
46{
47 // 1. Get Dimensions and Validate
48 const size_t nx = vertices.extent( 0 );
49 const size_t ny = vertices.extent( 1 );
50 const size_t num_points = nx * ny;
51
52 size_t num_elements = 0;
53 size_t points_per_element = 0;
54 uint8_t vtk_cell_type_id = 0; // Use uint8_t for VTK types array
55
56 if ( elementType == VtkElementType::LINEAR_QUAD )
57 {
58 if ( nx < 2 || ny < 2 )
59 {
60 throw std::runtime_error( "XML: Cannot create linear quads from a mesh smaller than 2x2 points." );
61 }
62 num_elements = ( nx - 1 ) * ( ny - 1 );
63 points_per_element = 4;
64 vtk_cell_type_id = static_cast< uint8_t >( VTK_QUAD );
65 }
66 else if ( elementType == VtkElementType::QUADRATIC_QUAD )
67 {
68 if ( nx < 3 || ny < 3 )
69 {
70 throw std::runtime_error( "XML: Cannot create quadratic quads from a mesh smaller than 3x3 points." );
71 }
72 if ( nx % 2 == 0 || ny % 2 == 0 )
73 {
74 throw std::runtime_error(
75 "XML: For QUADRATIC_QUAD elements using the 'every second node' scheme, Nx and Ny must be odd." );
76 }
77 size_t num_quad_elems_x = ( nx - 1 ) / 2;
78 size_t num_quad_elems_y = ( ny - 1 ) / 2;
79 num_elements = num_quad_elems_x * num_quad_elems_y;
80 points_per_element = 8;
81 vtk_cell_type_id = static_cast< uint8_t >( VTK_QUADRATIC_QUAD );
82 }
83 else
84 {
85 throw std::runtime_error( "XML: Unsupported VtkElementType." );
86 }
87
88 if ( num_elements == 0 && num_points > 0 )
89 {
90 // Handle cases like 2x1 or 3x1 grids where no elements can be formed
91 // Write points but zero cells
92 std::cout << "Warning: Input dimensions result in zero elements. Writing points only." << std::endl;
93 }
94 else if ( num_elements == 0 && num_points == 0 )
95 {
96 throw std::runtime_error( "XML: Input dimensions result in zero points and zero elements." );
97 }
98
99 // 2. Ensure data is accessible on the Host
100 auto h_vertices = Kokkos::create_mirror_view( vertices );
101 Kokkos::deep_copy( h_vertices, vertices );
102 Kokkos::fence();
103
104 // 3. Open the output file stream
105 std::ofstream ofs( filename );
106 if ( !ofs.is_open() )
107 {
108 throw std::runtime_error( "XML: Could not open file for writing: " + filename );
109 }
110 ofs << std::fixed << std::setprecision( 8 ); // Precision for coordinates
111
112 // --- 4. Write VTK XML Header ---
113 ofs << "<?xml version=\"1.0\"?>\n";
114 // Use Float64 for coordinates (double), Int64 for connectivity/offsets (safer for large meshes)
115 ofs << "<VTKFile type=\"UnstructuredGrid\" version=\"1.0\" byte_order=\"LittleEndian\">\n";
116 ofs << " <UnstructuredGrid>\n";
117 // Piece contains the main mesh data. NumberOfPoints/Cells must be correct.
118 ofs << " <Piece NumberOfPoints=\"" << num_points << "\" NumberOfCells=\"" << num_elements << "\">\n";
119
120 // --- 5. Write Points ---
121 ofs << " <Points>\n";
122 // DataArray for coordinates: Float64, 3 components (XYZ)
123 ofs << " <DataArray type=\"Float64\" Name=\"Coordinates\" NumberOfComponents=\"3\" format=\"ascii\">\n";
124 for ( size_t i = 0; i < nx; ++i )
125 {
126 for ( size_t j = 0; j < ny; ++j )
127 {
128 ofs << " " << h_vertices( i, j, 0 ) << " " << h_vertices( i, j, 1 ) << " " << h_vertices( i, j, 2 )
129 << "\n";
130 }
131 }
132 ofs << " </DataArray>\n";
133 ofs << " </Points>\n";
134
135 // --- 6. Write Cells (Connectivity, Offsets, Types) ---
136 ofs << " <Cells>\n";
137
138 // 6.a. Connectivity Array (flat list of point indices for all cells)
139 // Use Int64 for indices to be safe with large meshes (size_t can exceed Int32)
140 std::vector< int64_t > connectivity; // Use std::vector temporarily or write directly
141 connectivity.reserve( num_elements * points_per_element ); // Pre-allocate roughly
142
143 // 6.b. Offsets Array (index in connectivity where each cell ENDS)
144 std::vector< int64_t > offsets;
145 offsets.reserve( num_elements );
146 int64_t current_offset = 0; // VTK offsets are cumulative
147
148 // 6.c. Types Array (VTK cell type ID for each cell)
149 std::vector< uint8_t > types;
150 types.reserve( num_elements );
151
152 // --- Populate Connectivity, Offsets, and Types ---
153 if ( elementType == VtkElementType::LINEAR_QUAD )
154 {
155 for ( size_t i = 0; i < nx - 1; ++i )
156 {
157 for ( size_t j = 0; j < ny - 1; ++j )
158 {
159 // Calculate the 0-based indices
160 int64_t p0_idx = static_cast< int64_t >( i * ny + j );
161 int64_t p1_idx = static_cast< int64_t >( ( i + 1 ) * ny + j );
162 int64_t p2_idx = static_cast< int64_t >( ( i + 1 ) * ny + ( j + 1 ) );
163 int64_t p3_idx = static_cast< int64_t >( i * ny + ( j + 1 ) );
164
165 // Append connectivity
166 connectivity.push_back( p0_idx );
167 connectivity.push_back( p1_idx );
168 connectivity.push_back( p2_idx );
169 connectivity.push_back( p3_idx );
170
171 // Update and append offset
172 current_offset += points_per_element;
173 offsets.push_back( current_offset );
174
175 // Append type
176 types.push_back( vtk_cell_type_id );
177 }
178 }
179 }
180 else
181 { // QUADRATIC_QUAD
182 for ( size_t i = 0; i < nx - 1; i += 2 )
183 {
184 for ( size_t j = 0; j < ny - 1; j += 2 )
185 {
186 // Calculate indices (casting to int64_t for the array)
187 int64_t p0_idx = static_cast< int64_t >( i * ny + j );
188 int64_t p1_idx = static_cast< int64_t >( ( i + 2 ) * ny + j );
189 int64_t p2_idx = static_cast< int64_t >( ( i + 2 ) * ny + ( j + 2 ) );
190 int64_t p3_idx = static_cast< int64_t >( i * ny + ( j + 2 ) );
191 int64_t m01_idx = static_cast< int64_t >( ( i + 1 ) * ny + j );
192 int64_t m12_idx = static_cast< int64_t >( ( i + 2 ) * ny + ( j + 1 ) );
193 int64_t m23_idx = static_cast< int64_t >( ( i + 1 ) * ny + ( j + 2 ) );
194 int64_t m30_idx = static_cast< int64_t >( i * ny + ( j + 1 ) );
195
196 // Append connectivity (VTK order: corners then midsides)
197 connectivity.push_back( p0_idx );
198 connectivity.push_back( p1_idx );
199 connectivity.push_back( p2_idx );
200 connectivity.push_back( p3_idx );
201 connectivity.push_back( m01_idx );
202 connectivity.push_back( m12_idx );
203 connectivity.push_back( m23_idx );
204 connectivity.push_back( m30_idx );
205
206 // Update and append offset
207 current_offset += points_per_element;
208 offsets.push_back( current_offset );
209
210 // Append type
211 types.push_back( vtk_cell_type_id );
212 }
213 }
214 }
215
216 // --- Write the populated arrays to the file ---
217 // Connectivity
218 ofs << " <DataArray type=\"Int64\" Name=\"connectivity\" format=\"ascii\">\n";
219 ofs << " "; // Indentation
220 for ( size_t i = 0; i < connectivity.size(); ++i )
221 {
222 ofs << connectivity[i] << ( ( i + 1 ) % 12 == 0 ? "\n " : " " ); // Newline every 12 values
223 }
224 ofs << "\n </DataArray>\n"; // Add newline before closing tag if needed
225
226 // Offsets
227 ofs << " <DataArray type=\"Int64\" Name=\"offsets\" format=\"ascii\">\n";
228 ofs << " ";
229 for ( size_t i = 0; i < offsets.size(); ++i )
230 {
231 ofs << offsets[i] << ( ( i + 1 ) % 12 == 0 ? "\n " : " " );
232 }
233 ofs << "\n </DataArray>\n";
234
235 // Types
236 ofs << " <DataArray type=\"UInt8\" Name=\"types\" format=\"ascii\">\n";
237 ofs << " ";
238 for ( size_t i = 0; i < types.size(); ++i )
239 {
240 // Need to cast uint8_t to int for printing as number, not char
241 ofs << static_cast< int >( types[i] ) << ( ( i + 1 ) % 20 == 0 ? "\n " : " " );
242 }
243 ofs << "\n </DataArray>\n";
244
245 ofs << " </Cells>\n";
246
247 // --- 7. Write Empty PointData and CellData (Good Practice) ---
248 ofs << " <PointData>\n";
249 // Add <DataArray> tags here if you have data associated with points
250 ofs << " </PointData>\n";
251 ofs << " <CellData>\n";
252 // Add <DataArray> tags here if you have data associated with cells
253 ofs << " </CellData>\n";
254
255 // --- 8. Write VTK XML Footer ---
256 ofs << " </Piece>\n";
257 ofs << " </UnstructuredGrid>\n";
258 ofs << "</VTKFile>\n";
259
260 // 9. Close the file
261 ofs.close();
262 if ( !ofs )
263 {
264 throw std::runtime_error( "XML: Error occurred during writing or closing file: " + filename );
265 }
266}
267
268// Enum to choose the diagonal for splitting quads
270{
271 FORWARD_SLASH, // Connects (i,j) with (i+1,j+1)
272 BACKWARD_SLASH // Connects (i+1,j) with (i,j+1)
273};
274
275// Helper to get VTK type string from C++ type
276template < typename T >
277[[deprecated( "Use XDMF output." )]]
279{
280 if ( std::is_same_v< T, float > )
281 return "Float32";
282 if ( std::is_same_v< T, double > )
283 return "Float64";
284 if ( std::is_same_v< T, int > )
285 return "Int32";
286 if ( std::is_same_v< T, long long > )
287 return "Int64";
288 if ( std::is_same_v< T, int8_t > )
289 return "Int8";
290 if ( std::is_same_v< T, uint8_t > )
291 return "UInt8";
292 // Add more types as needed
293 throw std::runtime_error( "Unsupported data type for VTK output" );
294}
295
296template < typename ScalarType >
297[[deprecated( "Use XDMF output." )]]
299 Kokkos::View< ScalarType** [3] > points_device_view,
300 const std::string& filename,
301 DiagonalSplitType split_type )
302{
303 auto points_host_mirror = Kokkos::create_mirror_view_and_copy( Kokkos::HostSpace{}, points_device_view );
304
305 const int Nx = points_host_mirror.extent( 0 ); // Number of points in 1st dim (e.g., x)
306 const int Ny = points_host_mirror.extent( 1 ); // Number of points in 2nd dim (e.g., y)
307
308 if ( Nx < 2 || Ny < 2 )
309 {
310 throw std::runtime_error( "Grid dimensions are too small to form cells (Nx, Ny must be >= 2)." );
311 }
312
313 const long long num_total_points = static_cast< long long >( Nx ) * Ny;
314 const long long num_quads_in_plane = static_cast< long long >( Nx - 1 ) * ( Ny - 1 );
315 const long long num_cells = num_quads_in_plane * 2; // Each quad becomes 2 triangles
316
317 std::ofstream vtk_file( filename );
318 if ( !vtk_file.is_open() )
319 {
320 throw std::runtime_error( "Failed to open file: " + filename );
321 }
322
323 vtk_file
324 << "<VTKFile type=\"UnstructuredGrid\" version=\"1.0\" byte_order=\"LittleEndian\" header_type=\"UInt64\">\n";
325 vtk_file << " <UnstructuredGrid>\n";
326 vtk_file << " <Piece NumberOfPoints=\"" << num_total_points << "\" NumberOfCells=\"" << num_cells << "\">\n";
327
328 // --- Points ---
329 // Points are written by iterating through the first index (Nx), then the second index (Ny).
330 // The global point ID for point_host_mirror(i,j,*) is (i * Ny + j).
331 vtk_file << " <Points>\n";
332 vtk_file << " <DataArray type=\"" << get_vtk_type_string< ScalarType >()
333 << "\" Name=\"Coordinates\" NumberOfComponents=\"3\" format=\"ascii\">\n";
334 vtk_file << std::fixed << std::setprecision( 10 );
335 for ( int i = 0; i < Nx; ++i )
336 { // Iterate 1st dim
337 for ( int j = 0; j < Ny; ++j )
338 { // Iterate 2nd dim
339 vtk_file << " " << points_host_mirror( i, j, 0 ) << " " << points_host_mirror( i, j, 1 ) << " "
340 << points_host_mirror( i, j, 2 ) << "\n";
341 }
342 }
343 vtk_file << " </DataArray>\n";
344 vtk_file << " </Points>\n";
345
346 // --- Cells (Connectivity, Offsets, Types) ---
347 vtk_file << " <Cells>\n";
348
349 // Connectivity
350 std::vector< long long > connectivity_data;
351 connectivity_data.reserve( num_cells * 3 ); // 3 (indices) per triangle
352
353 for ( int i = 0; i < Nx - 1; ++i )
354 { // Iterate over quads in 1st dim
355 for ( int j = 0; j < Ny - 1; ++j )
356 { // Iterate over quads in 2nd dim
357 // Global 0-based indices of the quad's corners
358 // Point (i,j) has global ID: i * Ny + j
359 long long p00 = static_cast< long long >( i ) * Ny + j; // (i, j)
360 long long p10 = static_cast< long long >( i + 1 ) * Ny + j; // (i+1, j)
361 long long p01 = static_cast< long long >( i ) * Ny + ( j + 1 ); // (i, j+1)
362 long long p11 = static_cast< long long >( i + 1 ) * Ny + ( j + 1 ); // (i+1, j+1)
363
364 if ( split_type == DiagonalSplitType::FORWARD_SLASH )
365 {
366 // Diagonal from (i,j) to (i+1,j+1)
367 // Triangle 1: (i,j), (i+1,j), (i+1,j+1)
368 connectivity_data.push_back( p00 );
369 connectivity_data.push_back( p10 );
370 connectivity_data.push_back( p11 );
371 // Triangle 2: (i,j), (i+1,j+1), (i,j+1)
372 connectivity_data.push_back( p00 );
373 connectivity_data.push_back( p11 );
374 connectivity_data.push_back( p01 );
375 }
376 else
377 { // BACKWARD_SLASH
378 // Diagonal from (i+1,j) to (i,j+1)
379 // Triangle 1: (i,j), (i+1,j), (i,j+1)
380 connectivity_data.push_back( p00 );
381 connectivity_data.push_back( p10 );
382 connectivity_data.push_back( p01 );
383 // Triangle 2: (i+1,j), (i+1,j+1), (i,j+1)
384 connectivity_data.push_back( p10 );
385 connectivity_data.push_back( p11 );
386 connectivity_data.push_back( p01 );
387 }
388 }
389 }
390 vtk_file << " <DataArray type=\"" << get_vtk_type_string< long long >()
391 << "\" Name=\"connectivity\" format=\"ascii\">\n";
392 vtk_file << " ";
393 for ( size_t k = 0; k < connectivity_data.size(); ++k )
394 {
395 vtk_file << connectivity_data[k] << ( ( k == connectivity_data.size() - 1 ) ? "" : " " );
396 if ( ( k + 1 ) % 3 == 0 && k < connectivity_data.size() - 1 )
397 { // Newline for readability
398 vtk_file << "\n ";
399 }
400 }
401 vtk_file << "\n";
402 vtk_file << " </DataArray>\n";
403
404 // Offsets
405 vtk_file << " <DataArray type=\"" << get_vtk_type_string< long long >()
406 << "\" Name=\"offsets\" format=\"ascii\">\n";
407 long long current_offset = 0;
408 for ( long long c = 0; c < num_cells; ++c )
409 {
410 current_offset += ( 3 ); // Each triangle: 3 points
411 vtk_file << " " << current_offset << "\n";
412 }
413 vtk_file << " </DataArray>\n";
414
415 // Types (VTK_TRIANGLE = 5)
416 vtk_file << " <DataArray type=\"" << get_vtk_type_string< uint8_t >()
417 << "\" Name=\"types\" format=\"ascii\">\n";
418 for ( long long c = 0; c < num_cells; ++c )
419 {
420 vtk_file << " 5\n";
421 }
422 vtk_file << " </DataArray>\n";
423 vtk_file << " </Cells>\n";
424
425 vtk_file << " </Piece>\n";
426 vtk_file << " </UnstructuredGrid>\n";
427 vtk_file << "</VTKFile>\n";
428
429 vtk_file.close();
430}
431
432template <
433 typename PointRealT, // Type for surface coordinates and radii elements
434 typename AttachedDataType >
435[[deprecated( "Use XDMF output." )]]
437 grid::Grid2DDataVec< PointRealT, 3 > surface_points_device_view,
438 grid::Grid1DDataScalar< PointRealT > radii_device_view,
439 std::optional< AttachedDataType > optional_attached_data_device_view,
440 const std::string& vector_data_name, // Used only if vector_data_device_view has value & NumVecComponents > 0
441 const std::string& filename,
442 DiagonalSplitType split_type )
443{
444 static_assert(
445 std::is_same_v< AttachedDataType, grid::Grid3DDataScalar< double > > ||
446 std::is_same_v< AttachedDataType, grid::Grid3DDataVec< double, 3 > > );
447
448 const bool has_attached_data = optional_attached_data_device_view.has_value();
449 const int is_scalar_data = std::is_same_v< AttachedDataType, grid::Grid3DDataScalar< double > >;
450 const int num_vec_components = has_attached_data ? optional_attached_data_device_view.value().extent( 3 ) : 1;
451
452 // --- 1. Create host mirrors and copy data ---
453 auto surface_points_host = Kokkos::create_mirror_view_and_copy( Kokkos::HostSpace{}, surface_points_device_view );
454 auto radii_host = Kokkos::create_mirror_view_and_copy( Kokkos::HostSpace{}, radii_device_view );
455
456 typename AttachedDataType::HostMirror vector_data_host;
457 if ( has_attached_data )
458 {
459 vector_data_host =
460 Kokkos::create_mirror_view_and_copy( Kokkos::HostSpace{}, optional_attached_data_device_view.value() );
461 }
462 // Kokkos::fence(); // If using non-blocking deep_copy
463
464 // --- 2. Get dimensions ---
465 const int Ns = surface_points_host.extent( 0 ); // Num points in 1st dim of surface
466 const int Nt = surface_points_host.extent( 1 ); // Num points in 2nd dim of surface
467 const int Nw_radii = radii_host.extent( 0 ); // Num of radius values (defines layers of points)
468
469 if ( Ns < 2 || Nt < 2 )
470 {
471 throw std::runtime_error( "Surface grid dimensions (Ns, Nt) must be >= 2 to form base triangles." );
472 }
473 if ( Nw_radii < 1 )
474 {
475 throw std::runtime_error( "Radii view must contain at least one radius value (Nw_radii >= 1)." );
476 }
477
478 // Validate vector data dimensions if provided
479 if ( has_attached_data )
480 { // Implies NumVecComponents > 0
481 if ( vector_data_host.extent( 0 ) != Ns || vector_data_host.extent( 1 ) != Nt ||
482 vector_data_host.extent( 2 ) != Nw_radii )
483 {
484 throw std::runtime_error(
485 "VTK: Vector data dimensions (Ns, Nt, Nw_radii) do not match generated point structure." );
486 }
487 // extent(3) is NumVecComponents, checked by template/view type
488 if ( vector_data_name.empty() )
489 {
490 throw std::runtime_error( "VTK: Vector data name must be provided if vector data is present." );
491 }
492 }
493
494 // --- 3. Calculate total points and cells ---
495 const long long num_total_points = static_cast< long long >( Ns ) * Nt * Nw_radii;
496 long long num_cells = 0;
497 if ( Nw_radii >= 2 )
498 { // Need at least 2 radial layers to form wedges
499 const long long num_quads_in_surface_plane = static_cast< long long >( Ns - 1 ) * ( Nt - 1 );
500 const long long num_wedge_layers = static_cast< long long >( Nw_radii - 1 );
501 num_cells = num_quads_in_surface_plane * 2 * num_wedge_layers;
502 }
503
504 // --- 4. Open VTK file and write header ---
505 std::ofstream vtk_file( filename );
506 if ( !vtk_file.is_open() )
507 {
508 throw std::runtime_error( "Failed to open file: " + filename );
509 }
510 vtk_file
511 << "<VTKFile type=\"UnstructuredGrid\" version=\"1.0\" byte_order=\"LittleEndian\" header_type=\"UInt64\">\n";
512 vtk_file << " <UnstructuredGrid>\n";
513 vtk_file << " <Piece NumberOfPoints=\"" << num_total_points << "\" NumberOfCells=\"" << num_cells << "\">\n";
514
515 // --- 5. Points ---
516 vtk_file << " <Points>\n";
517 vtk_file << " <DataArray type=\"" << get_vtk_type_string< PointRealT >()
518 << "\" Name=\"Coordinates\" NumberOfComponents=\"3\" format=\"ascii\">\n";
519 vtk_file << std::fixed << std::setprecision( 10 );
520
521 PointRealT base_s_pt_coords[3]; // To store coords of one surface_points_host(s,t,*)
522 for ( int s_idx = 0; s_idx < Ns; ++s_idx )
523 {
524 for ( int t_idx = 0; t_idx < Nt; ++t_idx )
525 {
526 // Cache the base surface point coordinates
527 base_s_pt_coords[0] = surface_points_host( s_idx, t_idx, 0 );
528 base_s_pt_coords[1] = surface_points_host( s_idx, t_idx, 1 );
529 base_s_pt_coords[2] = surface_points_host( s_idx, t_idx, 2 );
530 for ( int w_rad_idx = 0; w_rad_idx < Nw_radii; ++w_rad_idx )
531 {
532 PointRealT current_radius_val = radii_host( w_rad_idx );
533 vtk_file << " " << base_s_pt_coords[0] * current_radius_val << " "
534 << base_s_pt_coords[1] * current_radius_val << " " << base_s_pt_coords[2] * current_radius_val
535 << "\n";
536 }
537 }
538 }
539 vtk_file << " </DataArray>\n";
540 vtk_file << " </Points>\n";
541
542 // --- 6. Cells (Connectivity, Offsets, Types) ---
543 vtk_file << " <Cells>\n";
544 std::vector< long long > connectivity_data;
545 if ( num_cells > 0 )
546 {
547 connectivity_data.reserve( num_cells * 6 ); // 6 indices per wedge
548
549 auto pt_gid = [&]( int s_surf_idx, int t_surf_idx, int w_rad_layer_idx ) {
550 return static_cast< long long >( s_surf_idx ) * ( Nt * Nw_radii ) +
551 static_cast< long long >( t_surf_idx ) * Nw_radii + static_cast< long long >( w_rad_layer_idx );
552 };
553
554 for ( int s = 0; s < Ns - 1; ++s )
555 { // Iterate over quads in s-dim of surface
556 for ( int t = 0; t < Nt - 1; ++t )
557 { // Iterate over quads in t-dim of surface
558 for ( int w_rad_layer_idx = 0; w_rad_layer_idx < Nw_radii - 1; ++w_rad_layer_idx )
559 { // Iterate over wedge layers
560 int base_rad_idx = w_rad_layer_idx;
561 int top_rad_idx = w_rad_layer_idx + 1;
562
563 long long p00_base = pt_gid( s, t, base_rad_idx );
564 long long p10_base = pt_gid( s + 1, t, base_rad_idx );
565 long long p01_base = pt_gid( s, t + 1, base_rad_idx );
566 long long p11_base = pt_gid( s + 1, t + 1, base_rad_idx );
567
568 long long p00_top = pt_gid( s, t, top_rad_idx );
569 long long p10_top = pt_gid( s + 1, t, top_rad_idx );
570 long long p01_top = pt_gid( s, t + 1, top_rad_idx );
571 long long p11_top = pt_gid( s + 1, t + 1, top_rad_idx );
572
573 if ( split_type == DiagonalSplitType::FORWARD_SLASH )
574 {
575 // Wedge 1 from Tri 1: (p00, p10, p11)_base -> (p00, p10, p11)_top
576 connectivity_data.push_back( p00_base );
577 connectivity_data.push_back( p10_base );
578 connectivity_data.push_back( p11_base );
579 connectivity_data.push_back( p00_top );
580 connectivity_data.push_back( p10_top );
581 connectivity_data.push_back( p11_top );
582 // Wedge 2 from Tri 2: (p00, p11, p01)_base -> (p00, p11, p01)_top
583 connectivity_data.push_back( p00_base );
584 connectivity_data.push_back( p11_base );
585 connectivity_data.push_back( p01_base );
586 connectivity_data.push_back( p00_top );
587 connectivity_data.push_back( p11_top );
588 connectivity_data.push_back( p01_top );
589 }
590 else
591 { // BACKWARD_SLASH
592 // Wedge 1 from Tri 1: (p00, p10, p01)_base -> (p00, p10, p01)_top
593 connectivity_data.push_back( p00_base );
594 connectivity_data.push_back( p10_base );
595 connectivity_data.push_back( p01_base );
596 connectivity_data.push_back( p00_top );
597 connectivity_data.push_back( p10_top );
598 connectivity_data.push_back( p01_top );
599 // Wedge 2 from Tri 2: (p10, p11, p01)_base -> (p10, p11, p01)_top
600 connectivity_data.push_back( p10_base );
601 connectivity_data.push_back( p11_base );
602 connectivity_data.push_back( p01_base );
603 connectivity_data.push_back( p10_top );
604 connectivity_data.push_back( p11_top );
605 connectivity_data.push_back( p01_top );
606 }
607 }
608 }
609 }
610 }
611 // Write Connectivity
612 vtk_file << " <DataArray type=\"" << get_vtk_type_string< long long >()
613 << "\" Name=\"connectivity\" format=\"ascii\">\n";
614 if ( !connectivity_data.empty() )
615 {
616 vtk_file << " ";
617 for ( size_t k = 0; k < connectivity_data.size(); ++k )
618 {
619 vtk_file << connectivity_data[k] << ( ( k == connectivity_data.size() - 1 ) ? "" : " " );
620 if ( ( k + 1 ) % 6 == 0 && k < connectivity_data.size() - 1 )
621 vtk_file << "\n ";
622 }
623 vtk_file << "\n";
624 }
625 vtk_file << " </DataArray>\n";
626
627 // Write Offsets
628 vtk_file << " <DataArray type=\"" << get_vtk_type_string< long long >()
629 << "\" Name=\"offsets\" format=\"ascii\">\n";
630 if ( num_cells > 0 )
631 {
632 long long current_offset = 0;
633 for ( long long c = 0; c < num_cells; ++c )
634 {
635 current_offset += 6; // Each wedge: 6 points
636 vtk_file << " " << current_offset << "\n";
637 }
638 }
639 vtk_file << " </DataArray>\n";
640
641 // Write Types
642 vtk_file << " <DataArray type=\"" << get_vtk_type_string< uint8_t >()
643 << "\" Name=\"types\" format=\"ascii\">\n";
644 if ( num_cells > 0 )
645 {
646 for ( long long c = 0; c < num_cells; ++c )
647 {
648 vtk_file << " 13\n"; // VTK_WEDGE cell type
649 }
650 }
651 vtk_file << " </DataArray>\n";
652 vtk_file << " </Cells>\n";
653
654 // --- 7. PointData (if vector data is provided) ---
655 if ( has_attached_data )
656 {
657 std::string point_data_attributes_str;
658 if ( is_scalar_data )
659 {
660 point_data_attributes_str = " Scalars=\"" + vector_data_name + "\"";
661 }
662 else
663 {
664 point_data_attributes_str = " Vectors=\"" + vector_data_name + "\"";
665 }
666
667 vtk_file << " <PointData" << point_data_attributes_str << ">\n";
668 vtk_file << " <DataArray type=\"" << get_vtk_type_string< double >() << "\" Name=\"" << vector_data_name
669 << "\" NumberOfComponents=\"" << num_vec_components << "\" format=\"ascii\">\n";
670 vtk_file << std::fixed << std::setprecision( 10 );
671
672 // Iterate in the SAME order as points were written: s_idx, t_idx, w_rad_idx
673 for ( int s_idx = 0; s_idx < Ns; ++s_idx )
674 {
675 for ( int t_idx = 0; t_idx < Nt; ++t_idx )
676 {
677 for ( int w_rad_idx = 0; w_rad_idx < Nw_radii; ++w_rad_idx )
678 {
679 vtk_file << " ";
680 if constexpr ( is_scalar_data )
681 {
682 vtk_file << vector_data_host( s_idx, t_idx, w_rad_idx ) << " ";
683 }
684 else
685 {
686 for ( size_t comp = 0; comp < num_vec_components; ++comp )
687 {
688 vtk_file << vector_data_host( s_idx, t_idx, w_rad_idx, comp )
689 << ( comp == num_vec_components - 1 ? "" : " " );
690 }
691 }
692
693 vtk_file << "\n";
694 }
695 }
696 }
697 vtk_file << " </DataArray>\n";
698 vtk_file << " </PointData>\n";
699 }
700 else
701 { // No vector data or NumVecComponents is 0
702 vtk_file << " <PointData>\n"; // Empty PointData section
703 vtk_file << " </PointData>\n";
704 }
705
706 // --- Footer ---
707 vtk_file << " </Piece>\n";
708 vtk_file << " </UnstructuredGrid>\n";
709 vtk_file << "</VTKFile>\n";
710 vtk_file.close();
711}
712
713/// @tparam InputDataScalarType The scalar type of the added grids - the output type can be set later.
714template < typename InputDataScalarType >
715class [[deprecated( "Use XDMF output." )]] VTKOutput
716{
717 public:
718 using ScalarFieldDeviceView = Kokkos::View< InputDataScalarType**** >;
719 using VectorFieldDeviceView = Kokkos::View< InputDataScalarType**** [3] >;
720
721 // Define Host view types for field data storage for clarity
722 // Assuming input fields are double, will be cast to float on write.
723 // If input fields can be float, this could be templated further or use a base class.
724 using ScalarFieldHostView = ScalarFieldDeviceView::HostMirror;
725 using VectorFieldHostView = VectorFieldDeviceView::HostMirror;
726
727 template < class ShellCoordsView, class RadiiView >
729 const ShellCoordsView& shell_node_coords_device,
730 const RadiiView& radii_per_layer_device,
731 bool generate_quadratic_elements_from_linear_input = false )
732 : is_quadratic_( generate_quadratic_elements_from_linear_input )
733 {
734 // 1. Copy input geometry data to managed host views (as before)
735 h_shell_coords_managed_ = Kokkos::create_mirror_view( shell_node_coords_device );
736 Kokkos::deep_copy( h_shell_coords_managed_, shell_node_coords_device );
737 h_radii_managed_ = Kokkos::create_mirror_view( radii_per_layer_device );
738 Kokkos::deep_copy( h_radii_managed_, radii_per_layer_device );
739
740 // 2. Get INPUT dimensions
741 num_subdomains_ = h_shell_coords_managed_.extent( 0 );
742 NX_nodes_surf_input_ = h_shell_coords_managed_.extent( 1 );
743 NY_nodes_surf_input_ = h_shell_coords_managed_.extent( 2 );
744 NR_nodes_rad_input_ = h_radii_managed_.extent( 1 );
745
746 if ( NX_nodes_surf_input_ < 1 || NY_nodes_surf_input_ < 1 || NR_nodes_rad_input_ < 1 )
747 { // Min 1 node
748 throw std::runtime_error( "Input node counts must be at least 1 in each dimension." );
749 }
750 // Check for cell formation possibility
751 bool can_form_cells = ( NX_nodes_surf_input_ > 1 || NY_nodes_surf_input_ > 1 || NR_nodes_rad_input_ > 1 );
752 if ( !can_form_cells && ( NX_nodes_surf_input_ * NY_nodes_surf_input_ * NR_nodes_rad_input_ > 1 ) )
753 {
754 // Multiple points, but arranged as a line/plane that can't form 3D hex cells
755 // This is fine, we might just output points.
756 }
757 if ( NX_nodes_surf_input_ < 2 && NY_nodes_surf_input_ < 2 && NR_nodes_rad_input_ < 2 &&
758 ( NX_nodes_surf_input_ + NY_nodes_surf_input_ + NR_nodes_rad_input_ > 3 ) )
759 {
760 // This case implies more than one point, but not enough to form a cell.
761 // E.g., 1x1xN where N>1 (line of points). VTK handles this.
762 }
763
764 // 3. Calculate OUTPUT grid dimensions and total points/cells for header
765 if ( is_quadratic_ )
766 {
767 NX_nodes_surf_output_ =
768 ( NX_nodes_surf_input_ > 1 ) ? ( 2 * ( NX_nodes_surf_input_ - 1 ) + 1 ) : NX_nodes_surf_input_;
769 NY_nodes_surf_output_ =
770 ( NY_nodes_surf_input_ > 1 ) ? ( 2 * ( NY_nodes_surf_input_ - 1 ) + 1 ) : NY_nodes_surf_input_;
771 NR_nodes_rad_output_ =
772 ( NR_nodes_rad_input_ > 1 ) ? ( 2 * ( NR_nodes_rad_input_ - 1 ) + 1 ) : NR_nodes_rad_input_;
773 }
774 else
775 {
776 NX_nodes_surf_output_ = NX_nodes_surf_input_;
777 NY_nodes_surf_output_ = NY_nodes_surf_input_;
778 NR_nodes_rad_output_ = NR_nodes_rad_input_;
779 }
780
781 num_total_points_ = num_subdomains_ * NX_nodes_surf_output_ * NY_nodes_surf_output_ * NR_nodes_rad_output_;
782
783 size_t num_cells_x_surf = ( NX_nodes_surf_input_ > 1 ) ? NX_nodes_surf_input_ - 1 : 0;
784 size_t num_cells_y_surf = ( NY_nodes_surf_input_ > 1 ) ? NY_nodes_surf_input_ - 1 : 0;
785 size_t num_cells_r_rad = ( NR_nodes_rad_input_ > 1 ) ? NR_nodes_rad_input_ - 1 : 0;
786 num_total_cells_ = num_subdomains_ * num_cells_x_surf * num_cells_y_surf * num_cells_r_rad;
787 }
788
789 template < class ScalarFieldViewDevice >
790 void add_scalar_field( const ScalarFieldViewDevice& field_data_view_device )
791 {
792 if ( field_data_view_device.rank() != 4 )
793 {
794 throw std::runtime_error( "Scalar field data view must have rank 4: (sd, x_in, y_in, r_in)." );
795 }
796 validate_field_view_dimensions_for_input_grid( field_data_view_device, field_data_view_device.label() );
797
798 PointDataEntry entry;
799 entry.name = field_data_view_device.label();
800 entry.num_components = 1;
801 entry.data_type_str = "Float32";
802 entry.device_view_input_data = field_data_view_device;
803 point_data_entries_.push_back( std::move( entry ) );
804 }
805
806 template < class VectorFieldViewDevice >
807 void add_vector_field( const VectorFieldViewDevice& field_data_view_device )
808 {
809 if ( field_data_view_device.rank() != 5 )
810 { // sd, x_in, y_in, r_in, comp
811 throw std::runtime_error( "Vector field data view must have rank 5." );
812 }
813 int num_vec_components = field_data_view_device.extent( 4 );
814 if ( num_vec_components <= 0 )
815 {
816 throw std::runtime_error( "Vector field must have at least one component." );
817 }
818 validate_field_view_dimensions_for_input_grid( field_data_view_device, field_data_view_device.label() );
819
820 PointDataEntry entry;
821 entry.name = field_data_view_device.label();
822 entry.num_components = num_vec_components;
823 entry.data_type_str = "Float32";
824 entry.device_view_input_data = field_data_view_device;
825 point_data_entries_.push_back( std::move( entry ) );
826 }
827
828 void write( const std::string& output_file )
829 {
830 std::ofstream vtk_file( output_file );
831 if ( !vtk_file.is_open() )
832 {
833 throw std::runtime_error( "Failed to open VTK output file: " + output_file );
834 }
835 vtk_file << std::fixed << std::setprecision( 8 );
836
837 vtk_file << "<VTKFile type=\"UnstructuredGrid\" version=\"1.0\" byte_order=\"LittleEndian\">\n";
838 vtk_file << " <UnstructuredGrid>\n";
839 vtk_file << " <Piece NumberOfPoints=\"" << num_total_points_ << "\" NumberOfCells=\"" << num_total_cells_
840 << "\">\n";
841
842 // --- Points ---
843 vtk_file << " <Points>\n";
844 vtk_file
845 << " <DataArray type=\"Float32\" Name=\"Coordinates\" NumberOfComponents=\"3\" format=\"ascii\">\n";
846 write_points_data( vtk_file );
847 vtk_file << " </DataArray>\n";
848 vtk_file << " </Points>\n";
849
850 // --- Cells ---
851 if ( num_total_cells_ > 0 )
852 { // Only write cell data if there are cells
853 vtk_file << " <Cells>\n";
854 vtk_file << " <DataArray type=\"Int64\" Name=\"connectivity\" format=\"ascii\">\n";
855 write_cell_connectivity_data( vtk_file );
856 vtk_file << " </DataArray>\n";
857 vtk_file << " <DataArray type=\"Int64\" Name=\"offsets\" format=\"ascii\">\n";
858 write_cell_offsets_data( vtk_file );
859 vtk_file << " </DataArray>\n";
860 vtk_file << " <DataArray type=\"UInt8\" Name=\"types\" format=\"ascii\">\n";
861 write_cell_types_data( vtk_file );
862 vtk_file << " </DataArray>\n";
863 vtk_file << " </Cells>\n";
864 }
865
866 // --- PointData ---
867 if ( !point_data_entries_.empty() && num_total_points_ > 0 )
868 {
869 vtk_file << " <PointData>\n";
870 for ( const auto& entry : point_data_entries_ )
871 {
872 vtk_file << " <DataArray type=\"" << entry.data_type_str << "\" Name=\"" << entry.name << "\"";
873 if ( entry.num_components > 1 )
874 {
875 vtk_file << " NumberOfComponents=\"" << entry.num_components << "\"";
876 }
877 vtk_file << " format=\"ascii\">\n";
878 write_field_data( vtk_file, entry );
879 vtk_file << " </DataArray>\n";
880 }
881 vtk_file << " </PointData>\n";
882 }
883
884 vtk_file << " </Piece>\n";
885 vtk_file << " </UnstructuredGrid>\n";
886 vtk_file << "</VTKFile>\n";
887
888 vtk_file.close();
889 }
890
891 private:
892 struct PointDataEntry
893 {
894 std::string name;
895 // Store the host-mirrored INPUT grid data view directly
896 std::variant< ScalarFieldDeviceView, VectorFieldDeviceView > device_view_input_data;
897 int num_components;
898 std::string data_type_str;
899 };
900
901 // --- Helper: Get Global Output Node ID --- (same as before)
902 size_t get_global_output_node_id( size_t sd_idx, size_t ix_out, size_t iy_out, size_t ir_out ) const
903 {
904 size_t nodes_per_surf_layer_output = NX_nodes_surf_output_ * NY_nodes_surf_output_;
905 size_t nodes_per_subdomain_output = nodes_per_surf_layer_output * NR_nodes_rad_output_;
906 size_t base_offset_sd = sd_idx * nodes_per_subdomain_output;
907 size_t offset_in_sd = ir_out * nodes_per_surf_layer_output + iy_out * NX_nodes_surf_output_ + ix_out;
908 return base_offset_sd + offset_in_sd;
909 }
910
911 // --- Helper: Validate Field View Dimensions --- (same as before)
912 template < class FieldView >
913 void validate_field_view_dimensions_for_input_grid( const FieldView& view, const std::string& field_name )
914 {
915 if ( view.extent( 0 ) != num_subdomains_ )
916 throw std::runtime_error( "Field '" + field_name + "' sd mismatch." );
917 if ( view.extent( 1 ) != NX_nodes_surf_input_ )
918 throw std::runtime_error( "Field '" + field_name + "' X_in mismatch." );
919 if ( view.extent( 2 ) != NY_nodes_surf_input_ )
920 throw std::runtime_error( "Field '" + field_name + "' Y_in mismatch." );
921 if ( view.extent( 3 ) != NR_nodes_rad_input_ )
922 throw std::runtime_error( "Field '" + field_name + "' R_in mismatch." );
923 }
924
925 // --- Helper: Write Points Data ---
926 void write_points_data( std::ostream& os )
927 {
928 for ( size_t sd = 0; sd < num_subdomains_; ++sd )
929 {
930 for ( size_t ir_out = 0; ir_out < NR_nodes_rad_output_; ++ir_out )
931 {
932 for ( size_t iy_out = 0; iy_out < NY_nodes_surf_output_; ++iy_out )
933 {
934 for ( size_t ix_out = 0; ix_out < NX_nodes_surf_output_; ++ix_out )
935 {
936 double final_px, final_py, final_pz;
937 get_interpolated_point_coordinates( sd, ix_out, iy_out, ir_out, final_px, final_py, final_pz );
938 os << " " << static_cast< float >( final_px ) << " "
939 << static_cast< float >( final_py ) << " " << static_cast< float >( final_pz ) << "\n";
940 }
941 }
942 }
943 }
944 }
945
946 // --- Helper: Get Interpolated Point Coordinates ---
947 void get_interpolated_point_coordinates(
948 size_t sd,
949 size_t ix_out,
950 size_t iy_out,
951 size_t ir_out,
952 double& final_px,
953 double& final_py,
954 double& final_pz ) const
955 {
956 if ( !is_quadratic_ || ( NX_nodes_surf_input_ <= 1 && NY_nodes_surf_input_ <= 1 && NR_nodes_rad_input_ <= 1 ) )
957 { // Single point case
958 size_t ix_in = ix_out;
959 size_t iy_in = iy_out;
960 size_t ir_in = ir_out;
961 // Ensure indices are valid for non-interpolated case, should be 0 if input dim is 1
962 if ( NX_nodes_surf_input_ == 1 )
963 ix_in = 0;
964 if ( NY_nodes_surf_input_ == 1 )
965 iy_in = 0;
966 if ( NR_nodes_rad_input_ == 1 )
967 ir_in = 0;
968
969 double radius = h_radii_managed_( sd, ir_in );
970 final_px = h_shell_coords_managed_( sd, ix_in, iy_in, 0 ) * radius;
971 final_py = h_shell_coords_managed_( sd, ix_in, iy_in, 1 ) * radius;
972 final_pz = h_shell_coords_managed_( sd, ix_in, iy_in, 2 ) * radius;
973 }
974 else
975 {
976 size_t ix_in0 = ix_out / 2;
977 size_t iy_in0 = iy_out / 2;
978 size_t ir_in0 = ir_out / 2;
979
980 double base_x_sum = 0.0, base_y_sum = 0.0, base_z_sum = 0.0;
981 double W_shell_sum = 0.0;
982
983 double wx_param = ( NX_nodes_surf_input_ > 1 ) ? ( ix_out % 2 ) * 0.5 : 0.0;
984 double wy_param = ( NY_nodes_surf_input_ > 1 ) ? ( iy_out % 2 ) * 0.5 : 0.0;
985
986 for ( int j_off = 0; j_off <= ( ( wy_param > 0.0 && iy_in0 + 1 < NY_nodes_surf_input_ ) ? 1 : 0 ); ++j_off )
987 {
988 size_t iy_in = iy_in0 + j_off;
989 double W_j = ( j_off == 0 ) ? ( 1.0 - wy_param ) : wy_param;
990 for ( int i_off = 0; i_off <= ( ( wx_param > 0.0 && ix_in0 + 1 < NX_nodes_surf_input_ ) ? 1 : 0 );
991 ++i_off )
992 {
993 size_t ix_in = ix_in0 + i_off;
994 double W_i = ( i_off == 0 ) ? ( 1.0 - wx_param ) : wx_param;
995
996 double shell_w = W_i * W_j;
997 base_x_sum += shell_w * h_shell_coords_managed_( sd, ix_in, iy_in, 0 );
998 base_y_sum += shell_w * h_shell_coords_managed_( sd, ix_in, iy_in, 1 );
999 base_z_sum += shell_w * h_shell_coords_managed_( sd, ix_in, iy_in, 2 );
1000 W_shell_sum += shell_w;
1001 }
1002 }
1003 // Handle case where denominator might be zero (e.g. single input node line)
1004 double unit_px =
1005 ( W_shell_sum > 1e-9 ) ?
1006 base_x_sum / W_shell_sum :
1007 h_shell_coords_managed_(
1008 sd, ( NX_nodes_surf_input_ > 1 ? ix_in0 : 0 ), ( NY_nodes_surf_input_ > 1 ? iy_in0 : 0 ), 0 );
1009 double unit_py =
1010 ( W_shell_sum > 1e-9 ) ?
1011 base_y_sum / W_shell_sum :
1012 h_shell_coords_managed_(
1013 sd, ( NX_nodes_surf_input_ > 1 ? ix_in0 : 0 ), ( NY_nodes_surf_input_ > 1 ? iy_in0 : 0 ), 1 );
1014 double unit_pz =
1015 ( W_shell_sum > 1e-9 ) ?
1016 base_z_sum / W_shell_sum :
1017 h_shell_coords_managed_(
1018 sd, ( NX_nodes_surf_input_ > 1 ? ix_in0 : 0 ), ( NY_nodes_surf_input_ > 1 ? iy_in0 : 0 ), 2 );
1019
1020 double radius_sum = 0.0;
1021 double W_radius_sum = 0.0;
1022 double wr_param = ( NR_nodes_rad_input_ > 1 ) ? ( ir_out % 2 ) * 0.5 : 0.0;
1023
1024 for ( int k_off = 0; k_off <= ( ( wr_param > 0.0 && ir_in0 + 1 < NR_nodes_rad_input_ ) ? 1 : 0 ); ++k_off )
1025 {
1026 size_t ir_in = ir_in0 + k_off;
1027 double R_k = ( k_off == 0 ) ? ( 1.0 - wr_param ) : wr_param;
1028 radius_sum += R_k * h_radii_managed_( sd, ir_in );
1029 W_radius_sum += R_k;
1030 }
1031 double current_radius = ( W_radius_sum > 1e-9 ) ?
1032 radius_sum / W_radius_sum :
1033 h_radii_managed_( sd, ( NR_nodes_rad_input_ > 1 ? ir_in0 : 0 ) );
1034
1035 final_px = unit_px * current_radius;
1036 final_py = unit_py * current_radius;
1037 final_pz = unit_pz * current_radius;
1038 }
1039 }
1040
1041 // --- Helper: Write Cell Connectivity Data ---
1042 void write_cell_connectivity_data( std::ostream& os )
1043 {
1044 if ( num_total_cells_ == 0 )
1045 return;
1046 size_t nodes_per_cell_out = is_quadratic_ ? 20 : 8;
1047
1048 size_t num_cells_x_surf = ( NX_nodes_surf_input_ > 1 ) ? NX_nodes_surf_input_ - 1 : 0;
1049 size_t num_cells_y_surf = ( NY_nodes_surf_input_ > 1 ) ? NY_nodes_surf_input_ - 1 : 0;
1050 size_t num_cells_r_rad = ( NR_nodes_rad_input_ > 1 ) ? NR_nodes_rad_input_ - 1 : 0;
1051
1052 for ( size_t sd = 0; sd < num_subdomains_; ++sd )
1053 {
1054 for ( size_t k_cell_in = 0; k_cell_in < num_cells_r_rad; ++k_cell_in )
1055 {
1056 for ( size_t j_cell_in = 0; j_cell_in < num_cells_y_surf; ++j_cell_in )
1057 {
1058 for ( size_t i_cell_in = 0; i_cell_in < num_cells_x_surf; ++i_cell_in )
1059 {
1060 // ... (same logic as before to get cell_node_ids for one cell) ...
1061 std::vector< size_t > cell_node_ids( nodes_per_cell_out );
1062 if ( is_quadratic_ )
1063 {
1064 size_t ix0_out = i_cell_in * 2;
1065 size_t ix1_out = ix0_out + 1;
1066 size_t ix2_out = ix0_out + 2;
1067 size_t iy0_out = j_cell_in * 2;
1068 size_t iy1_out = iy0_out + 1;
1069 size_t iy2_out = iy0_out + 2;
1070 size_t ir0_out = k_cell_in * 2;
1071 size_t ir1_out = ir0_out + 1;
1072 size_t ir2_out = ir0_out + 2;
1073
1074 cell_node_ids[0] = get_global_output_node_id( sd, ix0_out, iy0_out, ir0_out );
1075 cell_node_ids[1] = get_global_output_node_id( sd, ix2_out, iy0_out, ir0_out );
1076 cell_node_ids[2] = get_global_output_node_id( sd, ix2_out, iy2_out, ir0_out );
1077 cell_node_ids[3] = get_global_output_node_id( sd, ix0_out, iy2_out, ir0_out );
1078 cell_node_ids[4] = get_global_output_node_id( sd, ix0_out, iy0_out, ir2_out );
1079 cell_node_ids[5] = get_global_output_node_id( sd, ix2_out, iy0_out, ir2_out );
1080 cell_node_ids[6] = get_global_output_node_id( sd, ix2_out, iy2_out, ir2_out );
1081 cell_node_ids[7] = get_global_output_node_id( sd, ix0_out, iy2_out, ir2_out );
1082 cell_node_ids[8] = get_global_output_node_id( sd, ix1_out, iy0_out, ir0_out );
1083 cell_node_ids[9] = get_global_output_node_id( sd, ix2_out, iy1_out, ir0_out );
1084 cell_node_ids[10] = get_global_output_node_id( sd, ix1_out, iy2_out, ir0_out );
1085 cell_node_ids[11] = get_global_output_node_id( sd, ix0_out, iy1_out, ir0_out );
1086 cell_node_ids[12] = get_global_output_node_id( sd, ix1_out, iy0_out, ir2_out );
1087 cell_node_ids[13] = get_global_output_node_id( sd, ix2_out, iy1_out, ir2_out );
1088 cell_node_ids[14] = get_global_output_node_id( sd, ix1_out, iy2_out, ir2_out );
1089 cell_node_ids[15] = get_global_output_node_id( sd, ix0_out, iy1_out, ir2_out );
1090 cell_node_ids[16] = get_global_output_node_id( sd, ix0_out, iy0_out, ir1_out );
1091 cell_node_ids[17] = get_global_output_node_id( sd, ix2_out, iy0_out, ir1_out );
1092 cell_node_ids[18] = get_global_output_node_id( sd, ix2_out, iy2_out, ir1_out );
1093 cell_node_ids[19] = get_global_output_node_id( sd, ix0_out, iy2_out, ir1_out );
1094 }
1095 else
1096 {
1097 size_t ix0_out = i_cell_in;
1098 size_t ix1_out = i_cell_in + 1;
1099 size_t iy0_out = j_cell_in;
1100 size_t iy1_out = j_cell_in + 1;
1101 size_t ir0_out = k_cell_in;
1102 size_t ir1_out = k_cell_in + 1;
1103 cell_node_ids[0] = get_global_output_node_id( sd, ix0_out, iy0_out, ir0_out );
1104 cell_node_ids[1] = get_global_output_node_id( sd, ix1_out, iy0_out, ir0_out );
1105 cell_node_ids[2] = get_global_output_node_id( sd, ix1_out, iy1_out, ir0_out );
1106 cell_node_ids[3] = get_global_output_node_id( sd, ix0_out, iy1_out, ir0_out );
1107 cell_node_ids[4] = get_global_output_node_id( sd, ix0_out, iy0_out, ir1_out );
1108 cell_node_ids[5] = get_global_output_node_id( sd, ix1_out, iy0_out, ir1_out );
1109 cell_node_ids[6] = get_global_output_node_id( sd, ix1_out, iy1_out, ir1_out );
1110 cell_node_ids[7] = get_global_output_node_id( sd, ix0_out, iy1_out, ir1_out );
1111 }
1112 os << " ";
1113 for ( size_t i = 0; i < nodes_per_cell_out; ++i )
1114 {
1115 os << cell_node_ids[i] << ( i == nodes_per_cell_out - 1 ? "" : " " );
1116 }
1117 os << "\n";
1118 }
1119 }
1120 }
1121 }
1122 }
1123
1124 // --- Helper: Write Cell Offsets Data ---
1125 void write_cell_offsets_data( std::ostream& os )
1126 {
1127 if ( num_total_cells_ == 0 )
1128 return;
1129 size_t nodes_per_cell_out = is_quadratic_ ? 20 : 8;
1130 int64_t current_offset = 0;
1131 for ( size_t i = 0; i < num_total_cells_; ++i )
1132 {
1133 current_offset += nodes_per_cell_out;
1134 os << " " << current_offset << "\n";
1135 }
1136 }
1137
1138 // --- Helper: Write Cell Types Data ---
1139 void write_cell_types_data( std::ostream& os )
1140 {
1141 if ( num_total_cells_ == 0 )
1142 return;
1143 uint8_t cell_type_out = is_quadratic_ ? 25 : 12;
1144 for ( size_t i = 0; i < num_total_cells_; ++i )
1145 {
1146 os << " " << static_cast< int >( cell_type_out ) << "\n";
1147 }
1148 }
1149
1150 // --- Helper: Interpolate and Write Field Data ---
1151 // Templated on the actual stored view type within the variant
1152 template < typename InputFieldViewType >
1153 void get_interpolated_field_value_at_output_node(
1154 const InputFieldViewType& h_field_data_input,
1155 size_t sd,
1156 size_t ix_out,
1157 size_t iy_out,
1158 size_t ir_out,
1159 int num_components,
1160 std::vector< double >& interpolated_values ) const
1161 {
1162 interpolated_values.assign( num_components, 0.0 );
1163
1164 bool use_direct_mapping =
1165 !is_quadratic_ || ( NX_nodes_surf_input_ <= 1 && NY_nodes_surf_input_ <= 1 && NR_nodes_rad_input_ <= 1 );
1166
1167 if ( use_direct_mapping )
1168 {
1169 size_t ix_in = ix_out;
1170 size_t iy_in = iy_out;
1171 size_t ir_in = ir_out;
1172 if ( NX_nodes_surf_input_ == 1 )
1173 ix_in = 0;
1174 if ( NY_nodes_surf_input_ == 1 )
1175 iy_in = 0;
1176 if ( NR_nodes_rad_input_ == 1 )
1177 ir_in = 0;
1178
1179 if constexpr ( std::is_same_v< InputFieldViewType, ScalarFieldHostView > )
1180 { // Scalar
1181 interpolated_values[0] = h_field_data_input( sd, ix_in, iy_in, ir_in );
1182 }
1183 else
1184 { // Vector
1185 for ( int comp = 0; comp < num_components; ++comp )
1186 {
1187 interpolated_values[comp] = h_field_data_input( sd, ix_in, iy_in, ir_in, comp );
1188 }
1189 }
1190 }
1191 else
1192 {
1193 size_t ix_in0 = ix_out / 2;
1194 size_t iy_in0 = iy_out / 2;
1195 size_t ir_in0 = ir_out / 2;
1196
1197 std::vector< double > val_sum( num_components, 0.0 );
1198 double W_sum = 0.0;
1199
1200 double wx_param = ( NX_nodes_surf_input_ > 1 ) ? ( ix_out % 2 ) * 0.5 : 0.0;
1201 double wy_param = ( NY_nodes_surf_input_ > 1 ) ? ( iy_out % 2 ) * 0.5 : 0.0;
1202 double wr_param = ( NR_nodes_rad_input_ > 1 ) ? ( ir_out % 2 ) * 0.5 : 0.0;
1203
1204 for ( int k_off = 0; k_off <= ( ( wr_param > 0.0 && ir_in0 + 1 < NR_nodes_rad_input_ ) ? 1 : 0 ); ++k_off )
1205 {
1206 size_t ir_in = ir_in0 + k_off;
1207 double R_k = ( k_off == 0 ) ? ( 1.0 - wr_param ) : wr_param;
1208 for ( int j_off = 0; j_off <= ( ( wy_param > 0.0 && iy_in0 + 1 < NY_nodes_surf_input_ ) ? 1 : 0 );
1209 ++j_off )
1210 {
1211 size_t iy_in = iy_in0 + j_off;
1212 double W_j = ( j_off == 0 ) ? ( 1.0 - wy_param ) : wy_param;
1213 for ( int i_off = 0; i_off <= ( ( wx_param > 0.0 && ix_in0 + 1 < NX_nodes_surf_input_ ) ? 1 : 0 );
1214 ++i_off )
1215 {
1216 size_t ix_in = ix_in0 + i_off;
1217 double W_i = ( i_off == 0 ) ? ( 1.0 - wx_param ) : wx_param;
1218
1219 double weight = W_i * W_j * R_k;
1220 if constexpr ( std::is_same_v< InputFieldViewType, ScalarFieldHostView > )
1221 { // Scalar
1222 val_sum[0] += weight * h_field_data_input( sd, ix_in, iy_in, ir_in );
1223 }
1224 else
1225 { // Vector
1226 for ( int comp = 0; comp < num_components; ++comp )
1227 {
1228 val_sum[comp] += weight * h_field_data_input( sd, ix_in, iy_in, ir_in, comp );
1229 }
1230 }
1231 W_sum += weight;
1232 }
1233 }
1234 }
1235
1236 if ( W_sum > 1e-9 )
1237 {
1238 for ( int comp = 0; comp < num_components; ++comp )
1239 {
1240 interpolated_values[comp] = val_sum[comp] / W_sum;
1241 }
1242 }
1243 else
1244 { // Original node or not enough points to interpolate from
1245 if constexpr ( std::is_same_v< InputFieldViewType, ScalarFieldHostView > )
1246 {
1247 interpolated_values[0] = h_field_data_input(
1248 sd,
1249 ( NX_nodes_surf_input_ > 1 ? ix_in0 : 0 ),
1250 ( NY_nodes_surf_input_ > 1 ? iy_in0 : 0 ),
1251 ( NR_nodes_rad_input_ > 1 ? ir_in0 : 0 ) );
1252 }
1253 else
1254 {
1255 for ( int comp = 0; comp < num_components; ++comp )
1256 {
1257 interpolated_values[comp] = h_field_data_input(
1258 sd,
1259 ( NX_nodes_surf_input_ > 1 ? ix_in0 : 0 ),
1260 ( NY_nodes_surf_input_ > 1 ? iy_in0 : 0 ),
1261 ( NR_nodes_rad_input_ > 1 ? ir_in0 : 0 ),
1262 comp );
1263 }
1264 }
1265 }
1266 }
1267 }
1268
1269 void write_field_data( std::ostream& os, const PointDataEntry& entry )
1270 {
1271 std::variant< ScalarFieldHostView, VectorFieldHostView > host_view;
1272
1273 if ( entry.num_components == 1 )
1274 {
1275 if ( !scalar_field_host_buffer_.has_value() )
1276 {
1277 ScalarFieldHostView host_mirror = Kokkos::create_mirror_view_and_copy(
1278 Kokkos::HostSpace(), std::get< ScalarFieldDeviceView >( entry.device_view_input_data ) );
1279 scalar_field_host_buffer_ = host_mirror;
1280 }
1281 else
1282 {
1283 Kokkos::deep_copy(
1284 scalar_field_host_buffer_.value(),
1285 std::get< ScalarFieldDeviceView >( entry.device_view_input_data ) );
1286 }
1287
1288 host_view = scalar_field_host_buffer_.value();
1289 }
1290 else
1291 {
1292 if ( !vector_field_host_buffer_.has_value() )
1293 {
1294 VectorFieldHostView host_mirror = Kokkos::create_mirror_view_and_copy(
1295 Kokkos::HostSpace(), std::get< VectorFieldDeviceView >( entry.device_view_input_data ) );
1296 vector_field_host_buffer_ = host_mirror;
1297 }
1298 else
1299 {
1300 Kokkos::deep_copy(
1301 vector_field_host_buffer_.value(),
1302 std::get< VectorFieldDeviceView >( entry.device_view_input_data ) );
1303 }
1304
1305 host_view = vector_field_host_buffer_.value();
1306 }
1307
1308 std::vector< double > interpolated_values_buffer( entry.num_components );
1309 for ( size_t sd = 0; sd < num_subdomains_; ++sd )
1310 {
1311 for ( size_t ir_out = 0; ir_out < NR_nodes_rad_output_; ++ir_out )
1312 {
1313 for ( size_t iy_out = 0; iy_out < NY_nodes_surf_output_; ++iy_out )
1314 {
1315 for ( size_t ix_out = 0; ix_out < NX_nodes_surf_output_; ++ix_out )
1316 {
1317 // Use std::visit to call the interpolation helper with the correct view type
1318 std::visit(
1319 [&]( const auto& view_arg ) {
1320 this->get_interpolated_field_value_at_output_node(
1321 view_arg,
1322 sd,
1323 ix_out,
1324 iy_out,
1325 ir_out,
1326 entry.num_components,
1327 interpolated_values_buffer );
1328 },
1329 host_view );
1330
1331 os << " ";
1332 for ( int comp = 0; comp < entry.num_components; ++comp )
1333 {
1334 os << static_cast< float >( interpolated_values_buffer[comp] )
1335 << ( comp == entry.num_components - 1 ? "" : " " );
1336 }
1337 os << "\n";
1338 }
1339 }
1340 }
1341 }
1342 }
1343
1344 // --- Member Variables ---
1345 bool is_quadratic_;
1346
1347 grid::Grid3DDataVec< double, 3 >::HostMirror h_shell_coords_managed_;
1348 grid::Grid2DDataScalar< double >::HostMirror h_radii_managed_;
1349
1350 size_t num_subdomains_;
1351 size_t NX_nodes_surf_input_, NY_nodes_surf_input_, NR_nodes_rad_input_;
1352 size_t NX_nodes_surf_output_, NY_nodes_surf_output_, NR_nodes_rad_output_;
1353
1354 size_t num_total_points_;
1355 size_t num_total_cells_;
1356
1357 std::vector< PointDataEntry > point_data_entries_;
1358
1359 std::optional< ScalarFieldHostView > scalar_field_host_buffer_;
1360 std::optional< VectorFieldHostView > vector_field_host_buffer_;
1361};
1362
1363} // namespace terra::visualization
Definition vtk.hpp:716
VectorFieldDeviceView::HostMirror VectorFieldHostView
Definition vtk.hpp:725
Kokkos::View< InputDataScalarType ****[3] > VectorFieldDeviceView
Definition vtk.hpp:719
void write(const std::string &output_file)
Definition vtk.hpp:828
ScalarFieldDeviceView::HostMirror ScalarFieldHostView
Definition vtk.hpp:724
Kokkos::View< InputDataScalarType **** > ScalarFieldDeviceView
Definition vtk.hpp:718
VTKOutput(const ShellCoordsView &shell_node_coords_device, const RadiiView &radii_per_layer_device, bool generate_quadratic_elements_from_linear_input=false)
Definition vtk.hpp:728
void add_scalar_field(const ScalarFieldViewDevice &field_data_view_device)
Definition vtk.hpp:790
void add_vector_field(const VectorFieldViewDevice &field_data_view_device)
Definition vtk.hpp:807
Kokkos::View< ScalarType **[VecDim], Layout > Grid2DDataVec
Definition grid_types.hpp:37
Kokkos::View< ScalarType *, Layout > Grid1DDataScalar
Definition grid_types.hpp:16
Definition vtk.hpp:13
DiagonalSplitType
Definition vtk.hpp:270
constexpr int VTK_QUADRATIC_QUAD
Definition vtk.hpp:17
void write_rectilinear_to_triangular_vtu(Kokkos::View< ScalarType **[3] > points_device_view, const std::string &filename, DiagonalSplitType split_type)
Definition vtk.hpp:298
constexpr int VTK_QUAD
Definition vtk.hpp:16
void write_surface_radial_extruded_to_wedge_vtu(grid::Grid2DDataVec< PointRealT, 3 > surface_points_device_view, grid::Grid1DDataScalar< PointRealT > radii_device_view, std::optional< AttachedDataType > optional_attached_data_device_view, const std::string &vector_data_name, const std::string &filename, DiagonalSplitType split_type)
Definition vtk.hpp:436
std::string get_vtk_type_string()
Definition vtk.hpp:278
void write_vtk_xml_quad_mesh(const std::string &filename, const Kokkos::View< double **[3] > &vertices, VtkElementType elementType=VtkElementType::LINEAR_QUAD)
Writes a 2D grid of vertices stored in a Kokkos View to a VTK XML Unstructured Grid file (....
Definition vtk.hpp:42
VtkElementType
Definition vtk.hpp:21