///|
/// Small dense-matrix helpers for calibration and feature pipelines.
/// The implementation favors predictable behavior for small HRV feature
/// tables over large numerical workloads.
///|
/// Matrix shape.
pub(all) struct MatrixShape {
rows : Int
columns : Int
rectangular : Bool
} derive(FromJson, ToJson, Debug, Eq)
///|
/// Return the shape of a nested array.
pub fn matrix_shape(matrix : Array[Array[Double]]) -> MatrixShape {
if matrix.length() == 0 {
return { rows: 0, columns: 0, rectangular: true }
}
let columns = matrix[0].length()
let mut rectangular = true
for row in matrix {
if row.length() != columns {
rectangular = false
}
}
{ rows: matrix.length(), columns, rectangular }
}
///|
/// Make a zero matrix.
pub fn matrix_zeros(rows : Int, columns : Int) -> Array[Array[Double]] {
let result = []
if rows <= 0 || columns <= 0 {
return result
}
for _ in 0.. Array[Array[Double]] {
let result = matrix_zeros(size, size)
for i in 0.. Array[Array[Double]] {
let result = []
for row in matrix {
let copied = []
for value in row {
copied.push(value)
}
result.push(copied)
}
result
}
///|
/// Transpose a rectangular matrix.
pub fn matrix_transpose(matrix : Array[Array[Double]]) -> Array[Array[Double]] {
let shape = matrix_shape(matrix)
if !shape.rectangular || shape.rows == 0 || shape.columns == 0 {
return []
}
let result = matrix_zeros(shape.columns, shape.rows)
for i in 0.. Array[Array[Double]] {
let lshape = matrix_shape(left)
let rshape = matrix_shape(right)
if !lshape.rectangular ||
!rshape.rectangular ||
lshape.rows != rshape.rows ||
lshape.columns != rshape.columns {
return []
}
let result = matrix_zeros(lshape.rows, lshape.columns)
for i in 0.. Array[Array[Double]] {
let lshape = matrix_shape(left)
let rshape = matrix_shape(right)
if !lshape.rectangular ||
!rshape.rectangular ||
lshape.rows != rshape.rows ||
lshape.columns != rshape.columns {
return []
}
let result = matrix_zeros(lshape.rows, lshape.columns)
for i in 0.. Array[Array[Double]] {
let result = []
for row in matrix {
let output = []
for value in row {
output.push(value * scalar)
}
result.push(output)
}
result
}
///|
/// Multiply two rectangular matrices.
pub fn matrix_multiply(
left : Array[Array[Double]],
right : Array[Array[Double]],
) -> Array[Array[Double]] {
let lshape = matrix_shape(left)
let rshape = matrix_shape(right)
if !lshape.rectangular || !rshape.rectangular || lshape.columns != rshape.rows {
return []
}
let result = matrix_zeros(lshape.rows, rshape.columns)
for i in 0.. Array[Double] {
let shape = matrix_shape(matrix)
if !shape.rectangular || shape.columns != vector.length() {
return []
}
let result = []
for row in matrix {
let mut value = 0.0
for j in 0.. Array[Double] {
let shape = matrix_shape(matrix)
if !shape.rectangular || shape.rows == 0 {
return []
}
let result = Array::make(shape.columns, 0.0)
for row in matrix {
for j in 0.. Array[Double] {
let shape = matrix_shape(matrix)
if !shape.rectangular || shape.rows == 0 {
return []
}
let means = matrix_column_means(matrix)
let result = Array::make(shape.columns, 0.0)
for row in matrix {
for j in 0.. 1 {
for j in 0.. Array[Array[Double]] {
let shape = matrix_shape(matrix)
let means = matrix_column_means(matrix)
if !shape.rectangular || shape.columns != means.length() {
return []
}
let result = matrix_copy(matrix)
for i in 0.. Array[Array[Double]] {
let shape = matrix_shape(matrix)
let means = matrix_column_means(matrix)
let scales = matrix_column_scales(matrix)
if !shape.rectangular || shape.columns != means.length() {
return []
}
let result = matrix_copy(matrix)
for i in 0.. Array[Array[Double]] {
let shape = matrix_shape(matrix)
if !shape.rectangular || shape.rows <= 1 {
return matrix_zeros(shape.columns, shape.columns)
}
let centered = matrix_center(matrix)
let transpose = matrix_transpose(centered)
matrix_scale(
matrix_multiply(transpose, centered),
1.0 / (shape.rows - 1).to_double(),
)
}
///|
/// Calculate the squared norm of a vector.
pub fn vector_squared_norm(values : Array[Double]) -> Double {
let mut result = 0.0
for value in values {
result += value * value
}
result
}
///|
/// Calculate Euclidean distance between aligned vectors.
pub fn vector_distance(left : Array[Double], right : Array[Double]) -> Double {
let n = if left.length() < right.length() {
left.length()
} else {
right.length()
}
if n == 0 {
return 0.0
}
let mut sum = 0.0
for i in 0.. Array[Double] {
let norm = vector_squared_norm(values).sqrt()
let result = []
for value in values {
result.push(if norm == 0.0 { 0.0 } else { value / norm })
}
result
}
///|
/// Solve a two-variable linear system by Cramer's rule.
pub fn solve_two_by_two(
matrix : Array[Array[Double]],
target : Array[Double],
) -> Array[Double] {
if matrix.length() != 2 ||
matrix[0].length() != 2 ||
matrix[1].length() != 2 ||
target.length() != 2 {
return []
}
let determinant = matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0]
if determinant == 0.0 {
return []
}
[
(target[0] * matrix[1][1] - matrix[0][1] * target[1]) / determinant,
(matrix[0][0] * target[1] - target[0] * matrix[1][0]) / determinant,
]
}
///|
/// Calculate a two-feature least-squares regression with intercept.
pub fn fit_two_feature_regression(
features : Array[Array[Double]],
target : Array[Double],
) -> Array[Double] {
let shape = matrix_shape(features)
if !shape.rectangular ||
shape.columns != 2 ||
shape.rows != target.length() ||
shape.rows == 0 {
return []
}
let mean_x = mean_value(features.map(fn(row) { row[0] }))
let mean_y = mean_value(features.map(fn(row) { row[1] }))
let mean_target = mean_value(target)
let mut xx = 0.0
let mut yy = 0.0
let mut xy = 0.0
let mut xt = 0.0
let mut yt = 0.0
for i in 0.. Array[Double] {
let result = []
for row in matrix {
for value in row {
result.push(value)
}
}
result
}
///|
/// Rebuild a matrix from a row-major vector.
pub fn matrix_unflatten(
values : Array[Double],
rows : Int,
columns : Int,
) -> Array[Array[Double]] {
if rows <= 0 || columns <= 0 || values.length() < rows * columns {
return []
}
let result = matrix_zeros(rows, columns)
for i in 0.. Array[Double] {
let shape = matrix_shape(matrix)
let result = []
let n = if shape.rows < shape.columns { shape.rows } else { shape.columns }
for i in 0.. Bool {
let shape = matrix_shape(matrix)
if !shape.rectangular {
return false
}
for row in matrix {
for value in row {
if value.is_nan() || value.is_inf() {
return false
}
}
}
true
}