///|
/// Calculate validated output size without allocating any MAT payloads.
fn document_size(
arrays : Array[MatVariable],
limits : Limits,
) -> Int raise MatError {
validate_limits(limits)
if arrays.length() > limits.max_variables {
fail("variable-limit", 0, "Too many variables")
}
let names : Map[String, Bool] = Map([])
let mut elements = 0
let mut total = 128
// Validate and calculate every matrix size before allocating output.
for variable in arrays {
let count = match variable {
Dense(a) => validate_array(a, limits)
Sparse(a) => validate_sparse(a, limits)
}
if count > limits.max_elements - elements {
fail("element-limit", 0, "Total file elements exceed max_elements")
}
elements = elements + count
if names.contains(variable.name()) {
fail("duplicate-name", 0, "Duplicate variable name: " + variable.name())
}
names[variable.name()] = true
let name_length = @utf8.encode(variable.name()[:]).length()
let (rank, payload_length, complex, extra) = match variable {
Dense(a) => (a.shape.length(), a.real.length(), a.is_complex(), 0)
Sparse(a) => {
let rows = a.row_indices.length() * 4
let cols = a.col_ptrs.length() * 4
(
2,
a.real.length(),
a.imag is Some(_),
16 + rows + (8 - rows % 8) % 8 + cols + (8 - cols % 8) % 8,
)
}
}
let matrix_size = 16 +
8 +
rank * 4 +
(8 - rank * 4 % 8) % 8 +
8 +
name_length +
(8 - name_length % 8) % 8 +
8 +
payload_length +
(8 - payload_length % 8) % 8 +
extra +
(if complex {
8 + payload_length + (8 - payload_length % 8) % 8
} else {
0
})
if matrix_size > limits.max_file_bytes - total - 8 {
fail("file-limit", 0, "Encoded file exceeds max_file_bytes")
}
total = total + matrix_size + 8
}
total
}
///|
/// Validate public variable fields, duplicate names and aggregate resource limits without output allocation.
pub fn validate_document(
arrays : Array[MatVariable],
limits? : Limits = default_limits(),
) -> Unit raise MatError {
ignore(document_size(arrays, limits))
}
///|
/// Write deterministic little-endian dense/CSC MAT Level 5, optionally compressed.
pub fn write_document(
arrays : Array[MatVariable],
compressed? : Bool = false,
limits? : Limits = default_limits(),
) -> Bytes raise MatError {
validate_document(arrays, limits~)
let nodes = arrays.map(fn(item) {
match item {
Dense(a) => Numeric(a)
Sparse(a) => Csc(a)
}
})
write_tree(nodes, compressed~, limits~, tree_limits={
..default_tree_limits(),
max_nodes: limits.max_variables,
})
}
///|
/// Backwards-compatible dense writer; write_document handles mixed dense/CSC data.
pub fn write_mat(
arrays : Array[NumericArray],
compressed? : Bool = false,
limits? : Limits = default_limits(),
) -> Bytes raise MatError {
write_document(arrays.map(fn(a) { Dense(a) }), compressed~, limits~)
}