LookupTable1D
LookupTable1D is a class for storing and interpolating structured data for lookup in one independent variable.
It is used in order to easily retrieve input data that can be stored in table formats.
Important
LookupTable1D is used for data lookup in one independent variable. It does not support higher-dimensional data interpolation.
The class is templated as
template <typename T = Real, size_t N = 1, typename I = std::enable_if_t<std::is_floating_point<T>::value>>
class LookupTable1D
where the template parameter N indicates the number of dependent variables (N=1 yields a compile-time error).
Internally, the data is stored as an std::vector<std::array<T, N + 1> > where the vector entries are rows and the std::array<T, N + 1> are the column entries in that row.
Tip
The internal floating point representation defaults to Real and the number of dependent variables to 1.
For performance reasons, the LookupTable1D class is designed to only be used on regularly spaced data (either uniformly or logarithmically spaced).
The user can still pass irregularly spaced data into LookupTable1D, and then regularize the data later (i.e., interpolate it onto a regularly spaced 1D grid).
Usage of LookupTable1D will therefore consist of the following steps:
Add data rows into the table.
Swap columns or scale data if necessary.
Truncate data ranges if necessary, and specify what happens if the user tries to fetch data outside the valid range.
Regularize the table with a specified number of grid points and specified grid spacing.
Retrieve data, i.e., interpolate data from the table.
All of these steps are discussed below.
Inserting data
To add data to the table, use the member function
/*!
@brief Add entry.
@param[in] x Entry to add. For example addData(1,1,1,1). Number of elements in x must be N+1
*/
template <typename... Ts>
inline void
addData(const Ts&... x) noexcept;
where the parameter pack must have N+1 entries.
For example, to add two rows of data to a table with two dependent variables:
LookupTable1D<Real, 2> myTable;
myTable.addData(4.0, 5.0, 6.0);
myTable.addData(1.0, 2.0, 3.0);
This will insert two new rows at the end up the table.
Important
Input data points do not need to be uniformly spaced, or even sorted.
While users will insert rows one by one; LookupTable1D has functions for sorting and regularizing the table later.
Data modification
Scaling
To scale data in a particular column, use
/*!
@brief Utility function which scales one of the columns (either dependent or independent variable)
@param[in] a_scale Scaling factor
*/
template <size_t K>
inline void
scale(const T& a_scale) noexcept;
where K is the column to be scaled.
Column swapping
To swap columns in the table, use
/*!
@brief Utility function for swapping columns.
@details This is done on the raw data -- if the user wants to swap columns in the resampled/structured data
then he needs to call this function first and then resample the table.
@param[in] a_columnOne Column to swap.
@param[in] a_columnTwo Column to swap.
*/
inline void
swap(const size_t a_columnOne, const size_t a_columnTwo) noexcept;
For example, if the input data looked like
2.0 2.0 3.0
1.0 5.0 6.0
and one calls swap(1,2) the final table becomes
2.0 3.0 2.0
1.0 6.0 5.0
Warning
The swapping function only swaps raw data. Usually, one must later re-regularize the table, especially if the independent variable was swapped.
Range truncation
One can restrict the data range of the table by calling
/*!
@brief Utility function for truncating raw data along one of the variables (either dependent or independent).
@details This will discard (from the raw data) all data that fall outside the input interval. This is done on the raw data -- the user will
need to call prepareTable if the result should propagate into the resampled/structured data.
@param[in] a_min Minimum value represented.
@param[in] a_max Maximum value represented.
*/
inline void
truncate(const T& a_min, const T& a_max, const size_t a_column) noexcept;
where a_min and a_max are the permissible ranges for data in the input column (a_column).
Data outside these ranges is discarded from the table.
This applies regardless of whether or not a_column indicates the independent or dependent variables.
Regularize table
When regularizing the table, the raw-data (which can be irregularly spaced) is interpolated onto a regular grid. The user must specify:
The independent variable in which one will later interpolate.
Number of grid points in the regular grid.
How to space the grid points.
A LookupTable1D is regularized through
/*!
@brief Turn the raw data into uniform data for fast lookup.
@param[in] a_independentVariable The independent variable (i.e., column in the input data).
@param[in] a_numPoints Number of points in the input table.
@param[in] a_spacing Table spacing
*/
inline void
prepareTable(const size_t& a_independentVariable, const size_t& a_numPoints, const LookupTable::Spacing& a_spacing);
Here, a_independentVariable is the independent variable and a_numPoints is the number of grid points in the regularized table.
Two different spacings are supported: LookupTable::Spacing::Uniform and LookupTable::Spacing::Exponential.
Uniform spacing
With uniform spacing, grid points in the table are spaced as
where \(x_{\textrm{min}}\) and \(x_{\textrm{max}}\) are the minimum and maximum values in the independent variable, and \(N\) is the number of grid points.
Exponential spacing
If grid points are exponentially spaced then the spacing follows a power law:
Warning
One must have \(x_{\textrm{min}} > 0\) when using exponentially spaced points.
An example code for regularizing a table is given below:
LookupTable1D<Real, 2> myTable;
myTable.prepareTable(0, 100, LookupTable::Spacing::Exponential);
Data interpolation
To interpolate data from the table, one can fetch either a specific value in a row, or the entire row. In any case, the values that are returned are linearly interpolated between grid points (in the independent variable). The function signatures are
/*!
@brief Interpolation function for specific dependent variable K
@param[in] a_x Independent variable x
*/
template <size_t K>
inline T
interpolate(const T& a_x) const;
/*!
@brief Interpolate whole table.
@param[in] a_x Independent variable x
*/
inline std::array<T, N + 1>
interpolate(const T& x) const;
In the above, the template parameter K is the column to retrieve and a_x is the value of the independent variable.
Important
LookupTable1D will always use piecewise linear interpolation between two grid points.
For example, consider table regularized and sorted along the middle column:
2.0 1.0 3.0
1.0 3.0 6.0
1.0 5.0 4.0
To retrieve an interpolated value for x=2.0 in the third column we call
LookupTable1D<Real, 2> myTable,
const T val = myTable.interpolate<2>(2.0);
which will return a value of 4.5 (linearly interpolated).
Out-of-range strategy
Extrapolation outside the valid data range is determined by a user-specified strategy.
I.e., when calling the interpolate function with an argument that exceeds the bounds of the raw or regular data, the range strategy is either:
Return the value at the endpoint of the table.
Extrapolate from the endpoint.
To set the range strategy one can use
/*!
@brief Set the out-of-range strategy on the low end
@param[in] a_strategy Out-of-range strategy on low end
*/
inline void
setRangeStrategyLo(const LookupTable::OutOfRangeStrategy& a_strategy) noexcept;
/*!
@brief Set the out-of-range strategy on the high end
@param[in] a_strategy Out-of-range strategy on the high end
*/
inline void
setRangeStrategyHi(const LookupTable::OutOfRangeStrategy& a_strategy) noexcept;
where a_strategy must be either of
LookupTable::OutOfRangeStrategy::Constant.LookupTable::OutOfRangeStrategy::Interpolate.
The default behavior is LookupTable::OutOfRangeStrategy::Constant.
Viewing tables
For debugging purposes, LookupTable1D can write the internal data to an output stream or a file through various member functions:
/*!
@brief Dump raw table data to file
@param[in] a_file File name
*/
inline void
writeRawData(const std::string& a_file) const noexcept;
/*!
@brief Dump structured table data to file
@param[in] a_file File name
*/
inline void
writeStructuredData(const std::string& a_file) const noexcept;
/*!
@brief Dump raw table data to output stream.
@param[in] a_ostream Output stream
*/
inline void
outputRawData(std::ostream& a_ostream = std::cout) const noexcept;
/*!
@brief Dump structured table data to file.
@param[in] a_ostream Output stream
*/
inline void
outputStructuredData(std::ostream& a_ostream = std::cout) const noexcept;
These functions will print the table (either raw or regularized) to an output stream or file, and the user can later plot the data in an external plotting tool.