Loading...
Searching...
No Matches
lookup_table_2d_reader.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <fstream>
4#include <sstream>
5#include <stdexcept>
6#include <string>
7#include <vector>
8
9#include "Kokkos_Macros.hpp"
11
12namespace terra::io {
13
14/// @brief Describes the 2D grid layout for a linearized data column.
15///
16/// A single column in a data file contains @c nx * ny scalar values (one value
17/// per data row), representing a 2D grid where
18/// - the x-axis has @c nx equally-spaced points: x_min, x_min+dx, ..., x_min+(nx-1)*dx
19/// - the y-axis has @c ny equally-spaced points: y_min, y_min+dy, ..., y_min+(ny-1)*dy
20///
21/// The flat file index @c k (0-based, counting only non-comment data rows) maps
22/// to the grid index pair (ix, iy) via
23/// @code
24/// k = ix * stride_x + iy * stride_y
25/// @endcode
26///
27/// Exactly one of @c stride_x / @c stride_y must be 1 (the "fast" / innermost
28/// dimension); the other equals the size of that fast dimension.
29///
30/// Two common configurations (and their correct stride settings):
31///
32/// ---
33///
34/// @par Example A — x varies slowest (C row-major): stride_x = ny, stride_y = 1
35///
36/// Suppose the grid maps pressure (x, 2 points) × temperature (y, 3 points)
37/// to density. The data author loops over pressure in the outer loop:
38///
39/// @code
40/// # pressure temperature density
41/// # x=0.0 y=200 row k=0 → (ix=0, iy=0)
42/// 0.0 200 1.10
43/// # x=0.0 y=300 row k=1 → (ix=0, iy=1)
44/// 0.0 300 1.05
45/// # x=0.0 y=400 row k=2 → (ix=0, iy=2)
46/// 0.0 400 0.98
47/// # x=1.0 y=200 row k=3 → (ix=1, iy=0)
48/// 1.0 200 1.08
49/// # x=1.0 y=300 row k=4 → (ix=1, iy=1)
50/// 1.0 300 1.02
51/// # x=1.0 y=400 row k=5 → (ix=1, iy=2)
52/// 1.0 400 0.95
53/// @endcode
54///
55/// For the density column (index 2): nx=2, ny=3.
56/// k = ix*3 + iy*1 → stride_x=3 (=ny), stride_y=1
57///
58/// @code
59/// GridLayout2D layout{ .nx=2, .ny=3,
60/// .x_min=0.0, .y_min=200.0,
61/// .dx=1.0, .dy=100.0,
62/// .stride_x=3, // ny — one full y-row per x step
63/// .stride_y=1 };
64/// auto density = read_lookup_table_2d( "file.dat", 2, layout );
65/// // density( 0.0, 200.0 ) == 1.10 (ix=0, iy=0)
66/// // density( 1.0, 400.0 ) == 0.95 (ix=1, iy=2)
67/// @endcode
68///
69/// ---
70///
71/// @par Example B — y varies slowest (column-major): stride_x = 1, stride_y = nx
72///
73/// Same grid, but the data author loops over temperature in the outer loop:
74///
75/// @code
76/// # pressure temperature density
77/// # x=0.0 y=200 row k=0 → (ix=0, iy=0)
78/// 0.0 200 1.10
79/// # x=1.0 y=200 row k=1 → (ix=1, iy=0)
80/// 1.0 200 1.08
81/// # x=0.0 y=300 row k=2 → (ix=0, iy=1)
82/// 0.0 300 1.05
83/// # x=1.0 y=300 row k=3 → (ix=1, iy=1)
84/// 1.0 300 1.02
85/// # x=0.0 y=400 row k=4 → (ix=0, iy=2)
86/// 0.0 400 0.98
87/// # x=1.0 y=400 row k=5 → (ix=1, iy=2)
88/// 1.0 400 0.95
89/// @endcode
90///
91/// For the density column (index 2): nx=2, ny=3.
92/// k = ix*1 + iy*2 → stride_x=1, stride_y=2 (=nx)
93///
94/// @code
95/// GridLayout2D layout{ .nx=2, .ny=3,
96/// .x_min=0.0, .y_min=200.0,
97/// .dx=1.0, .dy=100.0,
98/// .stride_x=1, // 1 — consecutive rows differ by one x step
99/// .stride_y=2 };// nx — one full x-row per y step
100/// auto density = read_lookup_table_2d( "file.dat", 2, layout );
101/// // density( 0.0, 200.0 ) == 1.10 (ix=0, iy=0)
102/// // density( 1.0, 400.0 ) == 0.95 (ix=1, iy=2)
103/// @endcode
104///
105/// Both layouts produce the same ScalarLookupTable2D and the same interpolated
106/// values — only the file row order differs.
108{
109 int nx; ///< Number of grid points along x
110 int ny; ///< Number of grid points along y
111 double x_min; ///< x coordinate of the first (ix=0) grid point
112 double y_min; ///< y coordinate of the first (iy=0) grid point
113 double dx; ///< Grid spacing along x (must be > 0)
114 double dy; ///< Grid spacing along y (must be > 0)
115 int stride_x; ///< Flat-index step when ix increases by 1 (see above)
116 int stride_y; ///< Flat-index step when iy increases by 1 (see above)
117};
118
119/// @brief Device-capable 2D scalar lookup table with bilinear interpolation.
120///
121/// Holds a Kokkos::View with layout @c data(ix, iy) and scalar metadata that
122/// is trivially copyable to the device. All members are either arithmetic or
123/// Kokkos::View (which is itself device-copyable via a reference-counted
124/// handle), so the struct can be captured by value in a @c KOKKOS_LAMBDA.
125///
126/// Queries outside the table domain are @b clamped to the nearest boundary
127/// value; no extrapolation is performed.
128///
129/// @tparam ScalarType Floating-point type of the table values (typically double)
130template < typename ScalarType >
132{
133 using ViewType = Kokkos::View< ScalarType**, Kokkos::LayoutRight >;
134
135 ViewType data; ///< 2D device view, indexed as data(ix, iy)
136 ScalarType x_min; ///< x coordinate of grid point ix=0
137 ScalarType y_min; ///< y coordinate of grid point iy=0
138 ScalarType dx; ///< Spacing between grid points along x
139 ScalarType dy; ///< Spacing between grid points along y
140 int nx; ///< Number of grid points along x
141 int ny; ///< Number of grid points along y
142
143 /// @brief Bilinearly interpolated value at physical coordinates (x, y).
144 ///
145 /// Queries outside [x_min, x_min+(nx-1)*dx] × [y_min, y_min+(ny-1)*dy]
146 /// are clamped to the table boundary before interpolation.
147 ///
148 /// @param x Physical x coordinate
149 /// @param y Physical y coordinate
150 /// @return Interpolated (or clamped boundary) scalar value
151 KOKKOS_INLINE_FUNCTION
152 ScalarType operator()( ScalarType x, ScalarType y ) const
153 {
154 // --- convert to fractional grid coordinates and clamp ---
155 ScalarType fx = ( x - x_min ) / dx;
156 ScalarType fy = ( y - y_min ) / dy;
157
158 const ScalarType fx_max = static_cast< ScalarType >( nx - 1 );
159 const ScalarType fy_max = static_cast< ScalarType >( ny - 1 );
160
161 if ( fx < ScalarType( 0 ) )
162 {
163 fx = ScalarType( 0 );
164 }
165
166 if ( fx > fx_max )
167 {
168 fx = fx_max;
169 }
170
171 if ( fy < ScalarType( 0 ) )
172 {
173 fy = ScalarType( 0 );
174 }
175
176 if ( fy > fy_max )
177 {
178 fy = fy_max;
179 }
180
181 // --- handle degenerate single-point dimensions ---
182 if ( nx == 1 && ny == 1 )
183 {
184 return data( 0, 0 );
185 }
186
187 if ( nx == 1 )
188 {
189 int iy = static_cast< int >( fy );
190 if ( iy > ny - 2 )
191 {
192 iy = ny - 2;
193 }
194
195 ScalarType ty = fy - static_cast< ScalarType >( iy );
196 return ( ScalarType( 1 ) - ty ) * data( 0, iy ) + ty * data( 0, iy + 1 );
197 }
198
199 if ( ny == 1 )
200 {
201 int ix = static_cast< int >( fx );
202 if ( ix > nx - 2 )
203 {
204 ix = nx - 2;
205 }
206 ScalarType tx = fx - static_cast< ScalarType >( ix );
207 return ( ScalarType( 1 ) - tx ) * data( ix, 0 ) + tx * data( ix + 1, 0 );
208 }
209
210 // --- full bilinear interpolation ---
211 int ix = static_cast< int >( fx );
212 int iy = static_cast< int >( fy );
213
214 // clamp cell corner so ix+1 and iy+1 are always valid
215 if ( ix > nx - 2 )
216 {
217 ix = nx - 2;
218 }
219
220 if ( iy > ny - 2 )
221 {
222 iy = ny - 2;
223 }
224
225 const ScalarType tx = fx - static_cast< ScalarType >( ix );
226 const ScalarType ty = fy - static_cast< ScalarType >( iy );
227
228 return ( ScalarType( 1 ) - tx ) * ( ScalarType( 1 ) - ty ) * data( ix, iy ) +
229 tx * ( ScalarType( 1 ) - ty ) * data( ix + 1, iy ) + ( ScalarType( 1 ) - tx ) * ty * data( ix, iy + 1 ) +
230 tx * ty * data( ix + 1, iy + 1 );
231 }
232};
233
234namespace detail {
235
236/// @brief Split a line on any combination of spaces, tabs, and commas.
237///
238/// Consecutive delimiters are collapsed (empty tokens are ignored), so both
239/// "1.0, 2.0, 3.0" and "1.0 2.0 3.0" (and "1.0 ,2.0, 3.0") produce the
240/// same three-token result.
241inline std::vector< std::string > split_flexible( const std::string& line )
242{
243 std::vector< std::string > tokens;
244 std::string tok;
245
246 for ( const char c : line )
247 {
248 if ( c == ' ' || c == '\t' || c == ',' )
249 {
250 if ( !tok.empty() )
251 {
252 tokens.push_back( tok );
253 tok.clear();
254 }
255 }
256 else
257 {
258 tok += c;
259 }
260 }
261
262 if ( !tok.empty() )
263 tokens.push_back( tok );
264
265 return tokens;
266}
267
268/// @brief Given a flat index k, return (ix, iy) according to the layout strides.
269///
270/// Requires that exactly one of layout.stride_x / layout.stride_y equals 1.
271inline void flat_to_grid( int k, const GridLayout2D& layout, int& ix, int& iy )
272{
273 if ( layout.stride_y == 1 )
274 {
275 // x is the slow (outer) dimension: k = ix * stride_x + iy
276 ix = k / layout.stride_x;
277 iy = k % layout.stride_x;
278 }
279 else
280 {
281 // y is the slow (outer) dimension: k = ix + iy * stride_y
282 iy = k / layout.stride_y;
283 ix = k % layout.stride_y;
284 }
285}
286
287} // namespace detail
288
289/// @brief Read selected columns from a delimited data file into 2D lookup tables.
290///
291/// @par File format
292/// - Lines starting with @c # (after optional leading whitespace) are treated
293/// as comments and ignored.
294/// - Empty or whitespace-only lines are also skipped.
295/// - All other lines are data rows. Values may be separated by spaces, tabs,
296/// commas, or any mixture thereof.
297/// - The file must contain at least @c layout.nx * layout.ny data rows;
298/// additional rows after that are silently ignored.
299/// - Columns are 0-indexed. Each requested column index must be present on
300/// every data row.
301///
302/// @par Flat-to-grid mapping
303/// Data row @c k (0-based, among non-comment rows) maps to grid index (ix, iy)
304/// via the strides stored in @p layout (see @ref GridLayout2D for details).
305/// The resulting Kokkos view is indexed as @c view(ix, iy).
306///
307/// @param filename Path to the data file
308/// @param column_indices 0-based column indices to extract (order is preserved)
309/// @param layout Grid dimensions, physical coordinates, and strides
310/// @param label Optional label prefix for the Kokkos views
311///
312/// @return One @ref ScalarLookupTable2D per requested column, in the same order
313/// as @p column_indices.
314///
315/// @throws std::runtime_error if the file cannot be opened, if a data row has
316/// fewer columns than the largest requested index, or if fewer than
317/// @c nx * ny data rows are found.
318template < typename ScalarType = double >
319std::vector< ScalarLookupTable2D< ScalarType > > read_lookup_tables_2d(
320 const std::string& filename,
321 const std::vector< int >& column_indices,
322 const GridLayout2D& layout,
323 const std::string& label = "lookup_table" )
324{
325 if ( layout.stride_x != 1 && layout.stride_y != 1 )
326 {
327 throw std::runtime_error(
328 "terra::io::read_lookup_tables_2d: exactly one of stride_x / stride_y must be 1. "
329 "See GridLayout2D documentation for the two supported configurations." );
330 }
331
332 const int total_rows = layout.nx * layout.ny;
333 const int num_cols = static_cast< int >( column_indices.size() );
334
335 if ( num_cols == 0 )
336 return {};
337
338 // max column index we will need to access
339 int max_col_idx = 0;
340 for ( int c : column_indices )
341 {
342 if ( c < 0 )
343 throw std::runtime_error( "terra::io::read_lookup_tables_2d: negative column index." );
344 if ( c > max_col_idx )
345 max_col_idx = c;
346 }
347
348 // --- open file ---
349 std::ifstream file( filename );
350 if ( !file.is_open() )
351 throw std::runtime_error( "terra::io::read_lookup_tables_2d: cannot open file: " + filename );
352
353 // --- accumulate flat values on host (one vector per requested column) ---
354 std::vector< std::vector< ScalarType > > flat( num_cols );
355 for ( auto& v : flat )
356 v.reserve( static_cast< size_t >( total_rows ) );
357
358 int data_row = 0;
359 std::string line;
360 int line_number = 0;
361
362 while ( data_row < total_rows && std::getline( file, line ) )
363 {
364 ++line_number;
365
366 // skip comments and blank lines
367 {
368 const auto first_nonspace = line.find_first_not_of( " \t\r" );
369 if ( first_nonspace == std::string::npos )
370 continue; // blank line
371 if ( line[first_nonspace] == '#' )
372 continue; // comment
373 }
374
375 const auto tokens = detail::split_flexible( line );
376
377 if ( static_cast< int >( tokens.size() ) <= max_col_idx )
378 {
379 throw std::runtime_error(
380 "terra::io::read_lookup_tables_2d: file '" + filename + "', line " + std::to_string( line_number ) +
381 ": expected at least " + std::to_string( max_col_idx + 1 ) + " columns, found " +
382 std::to_string( tokens.size() ) + "." );
383 }
384
385 for ( int c = 0; c < num_cols; ++c )
386 {
387 try
388 {
389 flat[c].push_back( static_cast< ScalarType >( std::stod( tokens[column_indices[c]] ) ) );
390 }
391 catch ( const std::exception& e )
392 {
393 throw std::runtime_error(
394 "terra::io::read_lookup_tables_2d: file '" + filename + "', line " + std::to_string( line_number ) +
395 ": cannot parse '" + tokens[column_indices[c]] + "' as a number." );
396 }
397 }
398
399 ++data_row;
400 }
401
402 if ( data_row < total_rows )
403 {
404 throw std::runtime_error(
405 "terra::io::read_lookup_tables_2d: file '" + filename + "': expected " + std::to_string( total_rows ) +
406 " data rows (nx=" + std::to_string( layout.nx ) + " * ny=" + std::to_string( layout.ny ) +
407 "), but found only " + std::to_string( data_row ) + "." );
408 }
409
410 // --- build host views and copy to device ---
411 std::vector< ScalarLookupTable2D< ScalarType > > result;
412 result.reserve( static_cast< size_t >( num_cols ) );
413
414 for ( int c = 0; c < num_cols; ++c )
415 {
416 const std::string view_label = label + "_col" + std::to_string( column_indices[c] );
417
418 // allocate device view and a matching host mirror
419 typename ScalarLookupTable2D< ScalarType >::ViewType device_view( view_label, layout.nx, layout.ny );
420
421 auto host_view = Kokkos::create_mirror_view( Kokkos::HostSpace{}, device_view );
422
423 // scatter flat values into (ix, iy) positions using the stride mapping
424 for ( int k = 0; k < total_rows; ++k )
425 {
426 int ix, iy;
427 detail::flat_to_grid( k, layout, ix, iy );
428 host_view( ix, iy ) = flat[c][static_cast< size_t >( k )];
429 }
430
431 Kokkos::deep_copy( device_view, host_view );
432
434 table.data = device_view;
435 table.x_min = static_cast< ScalarType >( layout.x_min );
436 table.y_min = static_cast< ScalarType >( layout.y_min );
437 table.dx = static_cast< ScalarType >( layout.dx );
438 table.dy = static_cast< ScalarType >( layout.dy );
439 table.nx = layout.nx;
440 table.ny = layout.ny;
441
442 result.push_back( std::move( table ) );
443 }
444
445 return result;
446}
447
448/// @brief Convenience overload: read a single column from a data file.
449///
450/// Equivalent to calling @ref read_lookup_tables_2d with a one-element
451/// column-index vector and returning the first (and only) result.
452///
453/// @param filename Path to the data file
454/// @param column_index 0-based index of the column to read
455/// @param layout Grid dimensions, physical coordinates, and strides
456/// @param label Optional label for the Kokkos view
457///
458/// @return A @ref ScalarLookupTable2D for the requested column
459///
460/// @throws std::runtime_error (same conditions as @ref read_lookup_tables_2d)
461template < typename ScalarType = double >
463 const std::string& filename,
464 int column_index,
465 const GridLayout2D& layout,
466 const std::string& label = "lookup_table" )
467{ return read_lookup_tables_2d< ScalarType >( filename, { column_index }, layout, label ).front(); }
468
469} // namespace terra::io
std::vector< std::string > split_flexible(const std::string &line)
Split a line on any combination of spaces, tabs, and commas.
Definition lookup_table_2d_reader.hpp:241
void flat_to_grid(int k, const GridLayout2D &layout, int &ix, int &iy)
Given a flat index k, return (ix, iy) according to the layout strides.
Definition lookup_table_2d_reader.hpp:271
Definition lookup_table_2d_reader.hpp:12
ScalarLookupTable2D< ScalarType > read_lookup_table_2d(const std::string &filename, int column_index, const GridLayout2D &layout, const std::string &label="lookup_table")
Convenience overload: read a single column from a data file.
Definition lookup_table_2d_reader.hpp:462
std::vector< ScalarLookupTable2D< ScalarType > > read_lookup_tables_2d(const std::string &filename, const std::vector< int > &column_indices, const GridLayout2D &layout, const std::string &label="lookup_table")
Read selected columns from a delimited data file into 2D lookup tables.
Definition lookup_table_2d_reader.hpp:319
Describes the 2D grid layout for a linearized data column.
Definition lookup_table_2d_reader.hpp:108
int ny
Number of grid points along y.
Definition lookup_table_2d_reader.hpp:110
double x_min
x coordinate of the first (ix=0) grid point
Definition lookup_table_2d_reader.hpp:111
double dx
Grid spacing along x (must be > 0)
Definition lookup_table_2d_reader.hpp:113
int stride_x
Flat-index step when ix increases by 1 (see above)
Definition lookup_table_2d_reader.hpp:115
int stride_y
Flat-index step when iy increases by 1 (see above)
Definition lookup_table_2d_reader.hpp:116
int nx
Number of grid points along x.
Definition lookup_table_2d_reader.hpp:109
double y_min
y coordinate of the first (iy=0) grid point
Definition lookup_table_2d_reader.hpp:112
double dy
Grid spacing along y (must be > 0)
Definition lookup_table_2d_reader.hpp:114
Device-capable 2D scalar lookup table with bilinear interpolation.
Definition lookup_table_2d_reader.hpp:132
ScalarType x_min
x coordinate of grid point ix=0
Definition lookup_table_2d_reader.hpp:136
ViewType data
2D device view, indexed as data(ix, iy)
Definition lookup_table_2d_reader.hpp:135
ScalarType y_min
y coordinate of grid point iy=0
Definition lookup_table_2d_reader.hpp:137
int nx
Number of grid points along x.
Definition lookup_table_2d_reader.hpp:140
ScalarType dx
Spacing between grid points along x.
Definition lookup_table_2d_reader.hpp:138
ScalarType operator()(ScalarType x, ScalarType y) const
Bilinearly interpolated value at physical coordinates (x, y).
Definition lookup_table_2d_reader.hpp:152
int ny
Number of grid points along y.
Definition lookup_table_2d_reader.hpp:141
Kokkos::View< ScalarType **, Kokkos::LayoutRight > ViewType
Definition lookup_table_2d_reader.hpp:133
ScalarType dy
Spacing between grid points along y.
Definition lookup_table_2d_reader.hpp:139