///|
/// Format representation of scalar through 3-D tables, without physical evaluation.
/// Rows of a 3-D table follow (index_1, index_2), with index_3 inside each row.
pub(all) struct TableData {
kind : String
template_name : String
axes : Array[Array[Double]]
rows : Array[Array[Double]]
position : Position
} derive(Eq, Debug)
///|
pub extend TableData with Eq::{equal, not_equal}
///|
pub extend TableData with @debug.Debug::{to_repr}
///|
pub fn validate_table_data(data : TableData) -> Unit raise LibertyError {
if data.template_name.is_empty() {
raise failure(
"MissingTemplate",
"format table requires a template name",
data.position,
)
}
if (data.template_name == "scalar") != data.axes.is_empty() {
raise failure(
"TableShape",
"only scalar tables have no axes; named templates require nonempty axes",
data.position,
)
}
if data.axes.length() > 3 {
raise failure(
"UnsupportedDimension",
"format table supports up to three axes",
data.position,
)
}
let mut count = 1
for axis in data.axes {
if axis.is_empty() {
raise failure(
"TableShape",
"table axes must be nonempty and contiguous",
data.position,
)
}
if axis.length() > 10000 || count > 1000000 / axis.length() {
raise failure(
"TableLimit",
"table exceeds axis/value budget",
data.position,
)
}
count *= axis.length()
for i = 0; i < axis.length(); i = i + 1 {
if axis[i].is_nan() || axis[i].is_inf() {
raise failure("NonFiniteNumber", "axis must be finite", data.position)
}
if i > 0 && axis[i] <= axis[i - 1] {
raise failure(
"NonIncreasingAxis",
"axis must be strictly increasing",
data.position,
)
}
}
}
let columns = if data.axes.is_empty() {
1
} else {
data.axes[data.axes.length() - 1].length()
}
if data.rows.length() != count / columns ||
data.rows.any(row => row.length() != columns) {
raise failure(
"TableShape",
"quoted rows do not match table dimensions",
data.position,
)
}
for row in data.rows {
for value in row {
if value.is_nan() || value.is_inf() {
raise failure(
"NonFiniteNumber",
"table values must be finite",
data.position,
)
}
}
}
}
///|
/// Read and validate format data independently of the scalar/1-D/2-D evaluator.
pub fn read_table_data(
group : Group,
template? : Group,
) -> TableData raise LibertyError {
let name = group.name()
if name.is_empty() || (name != "scalar" && template == None) {
raise failure(
"MissingTemplate",
"table needs a known named template or scalar",
group.position,
)
}
if template is Some(t) && t.name() != name {
raise failure(
"TemplateMismatch",
"supplied template name differs from table reference",
group.position,
)
}
let axes = []
let mut gap = false
for key in ["index_1", "index_2", "index_3"] {
let override_axis = vector(group, key)
let axis = match override_axis {
Some(axis) => Some(axis)
None =>
match template {
Some(t) => vector(t, key)
None => None
}
}
match axis {
Some(axis) => {
if name == "scalar" || gap || axis.is_empty() {
raise failure(
"TableShape",
"scalar has no axes; other axes must be nonempty and contiguous",
group.position,
)
}
axes.push(axis)
}
None => gap = true
}
}
let rows = match group.complex("values") {
Some(args) if !args.is_empty() =>
args.map(row => number_list(row, position=group.position))
_ =>
raise failure(
"MissingValues",
"lookup table requires values(...) with at least one row",
group.position,
)
}
if template is Some(t) {
for index, key in ["variable_1", "variable_2", "variable_3"] {
if t.text(key) != None && axes.length() <= index {
raise failure(
"TableShape",
"declared template variable has no resolved axis: " + key,
group.position,
)
}
}
}
let data : TableData = {
kind: group.kind,
template_name: name,
axes,
rows,
position: group.position,
}
validate_table_data(data)
data
}