4.5.5. Multi-dimensional Tables

Multi-dimensional structured tables can be used to define multivariate scalar functions in a tabulated form and are useful for when the exact mathematical relationship between inputs and the output is not readily available (e.g., experimental data). The structured tables presented here are analogous to 1-dimensional tabulated scalar user functions (e.g. f(x)) but instead support up to three independent variables (e.g., f(x,y,z)). Functions that output vector or tensor quantities are not supported.

The tabulated function is interpolated using multi-dimensional Lagrange interpolants provided by the tabular properties library. By default, a tensor product of linear 1-dimensional interpolants is used resulting in linear, bi-linear, and tri-linear interpolations for one, two and three dimensions respectively. Using the interpolants, function and derivative evaluations are available at any point inside the table domain. For evaluation points outside the table domain, the independent variables will be clipped to the corresponding maximum or minimum table extent and the function will be evaluated at the clipped point. Similarly, derivatives will be clipped to zero. As with user functions, no warnings are printed to the log file when this happens.

As shown in the examples section, multi-dimensional table expressions are accessible through the model ND_Table and can be used anywhere other generic expressions are provided (e.g., initial conditions, boundary conditions, sources, materials, user expressions). Before covering concrete examples, we first detail the expected file format (HDF5) and the structure of the multi-dimensional tables.

4.5.5.1. HDF5 Table Format

An HDF5 file can contain multiple named “datasets”, each containing an array of data of arbitrary size and shape. An HDF5 file used for ND Tables in Aria must be structured using the following rules:

  • The independent variables and dependent variable must be stored as HDF5 datasets under the root HDF5 group /.

  • The independent variables should be stored in 1-D arrays in datasets named “i0”, “i1”, and “i2”, of lengths M, N, and L, respectively.

  • The independent variable arrays must be strictly increasing, but do not need to be uniformly spaced.

  • The independent variable arrays must have a length of at least 2. Dimensions of length 1 should not be included in the table.

  • There must be at least 1, and not more than 3, independent variable arrays.

  • The dependent variable must be stored in an M \times N \times L dataset named “function”.

For example, for tabulating a scalar function of two independent variables, three datasets must be provided: a 1-dimensional array named “i0” of length M, a 1-dimensional array named “i1” of length N, and a 2-dimensional array of size M \times N named “function” evaluated at the structured grid formed by cartesian product of the two 1-dimensional arrays “i0” and “i1”.

4.5.5.2. HDF5 Table Generation

Below we illustrate how to generate a proper HDF5 table using python with the h5py and numpy modules. These python modules are available in the python distribution locally provided by anaconda3 (i.e., module load apps/anaconda3). Along side the python code snippets are the corresponding file contents of each table obtained using h5dump. It is recommended to inspect the table that is written to ensure proper data layouts were used. Note the dimensionality and datatype (double) are determined by the numpy arrays.

Warning

HDF5 tables generated in C++ or Python use a row-major data arrangement (which is what Aria expects). If you generate your table using MATLAB it outputs a column-major data arrangement and the table will be transposed.

4.5.5.2.1. 1D Example

Generating a 1D hdf5 table in python:

import h5py
import numpy as np

f = lambda i0 : -0.1 * (i0 - 300) + 100.0
temp = np.linspace(300, 600, 4, dtype='d')
rho = f(temp)

with h5py.File("1d_example.h5", 'w') as h5File:
  h5File.create_dataset("function", data=rho)
  h5File.create_dataset("i0", data=temp)

Corresponding h5dump output for the 1D example table:

HDF5 "1d_example.h5" {
GROUP "/" {
  DATASET "function" {
      DATATYPE  H5T_IEEE_F64LE
      DATASPACE  SIMPLE { ( 4 ) / ( 4 ) }
      DATA {
      (0): 100, 90, 80, 70
      }
  }
  DATASET "i0" {
      DATATYPE  H5T_IEEE_F64LE
      DATASPACE  SIMPLE { ( 4 ) / ( 4 ) }
      DATA {
      (0): 300, 400, 500, 600
      }
  }
}
}

4.5.5.2.2. 2D Example

Generating a 2D hdf5 table in python:

import h5py
import numpy as np

f = lambda i0, i1: -0.1 * (i0 - 300) + 1e-4*(i1 - 1e5) + 100.0
temp = np.linspace(300, 600, 4, dtype='d')
pressure = np.linspace(1e5, 5e5, 3, dtype='d')
temp2, pressure2 = np.meshgrid(temp, pressure, indexing="ij")
rho = f(temp2,pressure2)

with h5py.File("2d_example.h5", 'w') as h5File:
  h5File.create_dataset("function", data=rho)
  h5File.create_dataset("i0", data=temp)
  h5File.create_dataset("i1", data=pressure)

Corresponding h5dump output for the 2D example table:

HDF5 "2d_example.h5" {
GROUP "/" {
  DATASET "function" {
      DATATYPE  H5T_IEEE_F64LE
      DATASPACE  SIMPLE { ( 4, 3 ) / ( 4, 3 ) }
      DATA {
      (0,0): 100, 120, 140,
      (1,0): 90, 110, 130,
      (2,0): 80, 100, 120,
      (3,0): 70, 90, 110
      }
  }
  DATASET "i0" {
      DATATYPE  H5T_IEEE_F64LE
      DATASPACE  SIMPLE { ( 4 ) / ( 4 ) }
      DATA {
      (0): 300, 400, 500, 600
      }
  }
  DATASET "i1" {
      DATATYPE  H5T_IEEE_F64LE
      DATASPACE  SIMPLE { ( 3 ) / ( 3 ) }
      DATA {
      (0): 100000, 300000, 500000
      }
  }
}
}

4.5.5.2.3. 3D Example

Generating a 3D hdf5 table in python:

import h5py
import numpy as np
import math

def complex_radial_src(theta, z, r):
    return np.sin(theta*math.pi/180)*z + r

theta = np.linspace(0, 360, 4, dtype='d')
z = np.linspace(0, 3, 3, dtype='d')
r = np.linspace(0, 0.1, 2, dtype='d')
T, Z, R = np.meshgrid(theta, z, r, indexing="ij")
src = complex_radial_src(T,Z,R)

with h5py.File("3d_example.h5", 'w') as h5File:
  h5File.create_dataset("function", data=src)
  h5File.create_dataset("i0", data=theta)
  h5File.create_dataset("i1", data=z)
  h5File.create_dataset("i2", data=r)

Corresponding h5dump output for the 3D example table:

HDF5 "3d_example.h5" {
GROUP "/" {
  DATASET "function" {
      DATATYPE  H5T_IEEE_F64LE
      DATASPACE  SIMPLE { ( 4, 3, 2 ) / ( 4, 3, 2 ) }
      DATA {
      (0,0,0): 0, 0.1,
      (0,1,0): 0, 0.1,
      (0,2,0): 0, 0.1,
      (1,0,0): 0, 0.1,
      (1,1,0): 1.29904, 1.39904,
      (1,2,0): 2.59808, 2.69808,
      (2,0,0): 0, 0.1,
      (2,1,0): -1.29904, -1.19904,
      (2,2,0): -2.59808, -2.49808,
      (3,0,0): 0, 0.1,
      (3,1,0): -3.67394e-16, 0.1,
      (3,2,0): -7.34788e-16, 0.1
      }
  }
  DATASET "i0" {
      DATATYPE  H5T_IEEE_F64LE
      DATASPACE  SIMPLE { ( 4 ) / ( 4 ) }
      DATA {
      (0): 0, 120, 240, 360
      }
  }
  DATASET "i1" {
      DATATYPE  H5T_IEEE_F64LE
      DATASPACE  SIMPLE { ( 3 ) / ( 3 ) }
      DATA {
      (0): 0, 1.5, 3
      }
  }
  DATASET "i2" {
      DATATYPE  H5T_IEEE_F64LE
      DATASPACE  SIMPLE { ( 2 ) / ( 2 ) }
      DATA {
      (0): 0, 0.1
      }
  }
}
}

4.5.5.3. Example Usage

The ND_Table expression is registered as a generic expression with most properties, boundary conditions, sources, and initial conditions. The independent variables are specified in-line for each model and can correspond to time, any scalar expression (e.g. DENSITY), or any scalar component of a vector or tensor expression (e.g. GRAD_TEMPERATURE_X or COORDINATES_X). The naming of these inputs should follow the naming rules described in General Naming Convention when referring to properties with phases or species indexes. For example, to use the pressure in the liquid phase as an independent variable one would use i0 = liquid_phase_pressure.

As an example, the syntax to use such a table for density as a function of pressure and temperature is shown below.

density = ND_Table name = CO2_density.h5 i0 = temperature i1 = pressure

In the table CO2_density.h5, function must correspond to tabulated density data (2D array) while the parameters i0 and i1 specify the expression labels that the independent table variables each map to.

These tables can also be used in boundary conditions, initial conditions, sources, user expressions, and other places where generic expressions are allowed using a similar syntax:

BC flux for energy on surface_1 = ND_Table name = flux.h5 i0 = coordinates_x \$
      i1 = coordinates_z i2 = time
Source for energy on all_blocks = ND_Table name = source.h5 i0 = coordinates_z \$
      i1 = coordinates_y i2 = coordinates_x

4.5.5.4. Table Signature

Traceability is important and since the actual contents of the HDF5 table used for an ND_Table expression are not shown in the log file there is a summary and signature block output in the log file for each table used in the problem. This summary block shows the number of table dimensions, the size of each dataset, the min and max of each dataset, and a hash (“uid”) of the full table data set. Two tables with the same data will have the same hash, regardless of their filename.

Read HDF5 table 'demo.h5' {
  uid: 82314a8bcdc16dec
  nDims: 2
  i0: 361 entries in range [0, 360]
  i1: 6 entries in range [-1e+06, 1e+06]
  f: 2166 entries in range [400, 675]
}