Tables and Copy Semantics
Lua has a single composite data structure — the table — whose elements are dynamically typed tagged values managed by Lua’s garbage collector. Unlike Rust ndarray slices or C arrays, a Lua table is never a contiguous unboxed f64 array in memory.
sciencekit-lua states its memory contract plainly: every input matrix or vector is validated and copied into a contiguous f64 buffer, and every output is returned as a fresh Lua table of rows. No zero-copy aliasing is promised across Lua values.
sk_ffi_array_free before returning to the script.Scalar precision (f64)
Lua 5.4 numbers come in two subtypes: 64-bit IEEE-754 floats (Value::Number) and 64-bit signed integers (Value::Integer). During table conversion (src/array/input.rs), both subtypes are accepted and stored as f64:
-- Mixed integer and float literals convert cleanly to row-major f64:
local features = {
{ 1.0, 2.0 },
{ 3, 4.5 },
{ 5.0, 6.0 },
}
Any other Lua type (string, boolean, nil, table, userdata, function) — even a numeric string like "2.0" — is rejected with sciencekit invalid-argument (3) naming its 1-based coordinates.
Input matrices (features)
Every features argument (fit, predict, transform, score) must be a 1-indexed rectangular table of row tables:
- Outer length is read with
raw_len(1 .. rows, ignoring metatables) and must be>= 1. - Row
1sets the expected column count (columns >= 1). - Every subsequent row
2 .. rowsmust be a table withraw_len == columns. - Values are packed in row-major order (
row 1 col 1,row 1 col 2, …,row N col M).
Input target vectors (targets)
targets passed to builder:fit(features, targets) and model:score(features, targets) must match #features in length (expected_length = features.rows()) and may use either of two layouts:
| Layout | Example (3 samples) | Notes |
|---|---|---|
| Flat sequence | { 1.0, 2, 3.5 } | table[1] is a number; elements 1 .. N must all be numbers. |
| Column of 1-element rows | { { 1.0 }, { 2.0 }, { 3.5 } } | table[1] is a table; every row 1 .. N must be a table of length 1. Useful when feeding the output of one predict directly as targets to another call. |
Output tables
| Method | Return type | Shape |
|---|---|---|
model:predict(features) | table | N × 1 table of rows: predictions[row][1] is a number. |
model:transform(features) | table | N × D table of rows matching #features and #features[1]. |
model:score(features, targets) | number | Scalar f64 (R²). |
Mutating an input table after :fit(...) never affects the fitted model, and mutating a returned output table never affects subsequent calls.
Validation reference (from src/array/input_tests.rs)
| Invalid input | Raised error |
|---|---|
{} (empty features) | sciencekit shape (2): features must contain at least one row |
{ {} } (empty row) | sciencekit shape (2): feature rows must contain at least one value |
{ { 1.0, 2.0 }, { 3.0 } } | sciencekit shape (2): feature row 2 has 1 values, expected 2; a matrix must be rectangular |
{ { 1.0 }, 2.0 } | sciencekit invalid-argument (3): row 2 must be a table of numbers, found number |
{ { 1.0, "two" } } | sciencekit invalid-argument (3): value at row 1, column 2 must be a number, found string |
targets = { 1.0, 2.0 } with 3 feature rows | sciencekit shape (2): targets has 2 values, expected 3 rows |
targets = { { 1.0, 2.0 }, { 3.0, 4.0 } } | sciencekit shape (2): target row 1 must contain exactly one value |