///|
/// A compact row-major dense matrix used by the filtering algorithms.
///
/// `Matrix` deliberately keeps its storage private. This prevents callers
/// from accidentally changing the shape while still allowing the numerical
/// kernels to update values in place. The type is small enough for embedded
/// and WebAssembly workloads and predictable enough for deterministic tests.
pub struct Matrix {
rows : Int
cols : Int
data : Array[Double]
} derive(Debug)
///|
/// A result returned by a matrix operation that may be singular.
pub(all) enum MatrixSolveResult {
Solved(Array[Double])
Singular
InvalidShape
} derive(Debug, Eq)
///|
/// Construct a zero-filled matrix.
pub fn Matrix::zeros(rows : Int, cols : Int) -> Matrix {
let safe_rows = if rows < 0 { 0 } else { rows }
let safe_cols = if cols < 0 { 0 } else { cols }
{
rows: safe_rows,
cols: safe_cols,
data: Array::make(safe_rows * safe_cols, 0.0),
}
}
///|
/// Construct a matrix whose diagonal is `value` and whose other entries are
/// zero.
pub fn Matrix::diagonal(size : Int, value : Double) -> Matrix {
let result = Matrix::zeros(size, size)
for i in 0.. Matrix {
Matrix::diagonal(size, 1.0)
}
///|
/// Construct a matrix from a flat row-major buffer. Short buffers are padded
/// with zeroes and long buffers are safely truncated.
pub fn Matrix::from_flat(
rows : Int,
cols : Int,
values : Array[Double],
) -> Matrix {
let result = Matrix::zeros(rows, cols)
let copied = if values.length() < result.data.length() {
values.length()
} else {
result.data.length()
}
for i in 0.. Matrix {
let rows = source.length()
let mut cols = 0
for row in source {
if row.length() > cols {
cols = row.length()
}
}
let result = Matrix::zeros(rows, cols)
for i, row in source {
for j in 0.. Matrix? {
if source.length() == 0 {
return Some(Matrix::zeros(0, 0))
}
let cols = source[0].length()
for row in source {
if row.length() != cols {
return None
}
}
Some(Matrix::from_rows(source))
}
///|
pub fn Matrix::rows(self : Matrix) -> Int {
self.rows
}
///|
pub fn Matrix::cols(self : Matrix) -> Int {
self.cols
}
///|
pub fn Matrix::size(self : Matrix) -> Int {
self.rows * self.cols
}
///|
pub fn Matrix::is_empty(self : Matrix) -> Bool {
self.rows == 0 || self.cols == 0
}
///|
pub fn Matrix::is_square(self : Matrix) -> Bool {
self.rows == self.cols
}
///|
/// Return whether a pair of coordinates is safe to access.
pub fn Matrix::valid_index(self : Matrix, row : Int, col : Int) -> Bool {
row >= 0 && row < self.rows && col >= 0 && col < self.cols
}
///|
/// Read an entry. This method is intended for validated matrix operations;
/// use `try_get` when coordinates originate outside the algorithm.
pub fn Matrix::get(self : Matrix, row : Int, col : Int) -> Double {
self.data[row * self.cols + col]
}
///|
/// Read an entry without risking an out-of-bounds panic.
pub fn Matrix::try_get(self : Matrix, row : Int, col : Int) -> Double? {
if self.valid_index(row, col) {
Some(self.get(row, col))
} else {
None
}
}
///|
/// Set an entry when coordinates are valid. Returns whether the write was
/// performed.
pub fn Matrix::set(self : Matrix, row : Int, col : Int, value : Double) -> Bool {
if self.valid_index(row, col) {
self.data[row * self.cols + col] = value
true
} else {
false
}
}
///|
pub fn Matrix::copy(self : Matrix) -> Matrix {
Matrix::from_flat(self.rows, self.cols, self.data.copy())
}
///|
/// Return the requested row as a new owned array.
pub fn Matrix::row(self : Matrix, index : Int) -> Array[Double] {
if index < 0 || index >= self.rows {
return []
}
Array::makei(self.cols, j => self.get(index, j))
}
///|
/// Return the requested column as a new owned array.
pub fn Matrix::column(self : Matrix, index : Int) -> Array[Double] {
if index < 0 || index >= self.cols {
return []
}
Array::makei(self.rows, i => self.get(i, index))
}
///|
/// Convert to independent row arrays for interop and inspection.
pub fn Matrix::to_rows(self : Matrix) -> Array[Array[Double]] {
Array::makei(self.rows, i => self.row(i))
}
///|
/// Apply a scalar function to every entry.
pub fn Matrix::map(self : Matrix, f : (Double) -> Double) -> Matrix {
let result = self.copy()
for i in 0.. Double,
) -> Matrix {
let result = Matrix::zeros(self.rows, self.cols)
for i in 0.. Unit {
self.data.fill(value)
}
///|
pub fn Matrix::is_finite(self : Matrix) -> Bool {
for value in self.data {
if value.is_nan() || value.is_inf() {
return false
}
}
true
}
///|
pub fn Matrix::max_abs(self : Matrix) -> Double {
let mut result = 0.0
for value in self.data {
let current = value.abs()
if current > result {
result = current
}
}
result
}
///|
pub fn Matrix::diagonal_min(self : Matrix) -> Double {
if !self.is_square() || self.rows == 0 {
return 0.0
}
let mut result = self.get(0, 0)
for i in 1.. Double {
if !self.is_square() || self.rows == 0 {
return 0.0
}
let mut result = self.get(0, 0)
for i in 1.. result {
result = self.get(i, i)
}
}
result
}
///|
pub fn Matrix::approx_equal(
self : Matrix,
other : Matrix,
tolerance : Double,
) -> Bool {
if self.rows != other.rows || self.cols != other.cols {
return false
}
for i in 0.. tolerance {
return false
}
}
true
}