Skip to main content

sim_lib_view_math/
matrix.rs

1//! Tensor/matrix lens: an editable `scene/matrix`.
2//!
3//! A matrix value is a list of rows of numbers. The lens renders it as a
4//! `scene/matrix` with editable cells; editing a cell applies an
5//! `intent/edit-field` path `[row, col]` and returns a new matrix value.
6
7use sim_kernel::{Expr, Symbol};
8use sim_lib_scene::{node, sym};
9
10use crate::num::{as_f64, number};
11
12/// The matrix lens id.
13pub const MATRIX_LENS: &str = "view:math-matrix";
14
15/// Build a matrix value from rows of numbers.
16pub fn matrix(rows: &[Vec<f64>]) -> Expr {
17    Expr::List(
18        rows.iter()
19            .map(|row| Expr::List(row.iter().map(|value| number(*value)).collect()))
20            .collect(),
21    )
22}
23
24/// Render a matrix value as an editable `scene/matrix`.
25pub fn matrix_view(value: &Expr) -> Expr {
26    let rows = matrix_rows(value);
27    node(
28        "matrix",
29        vec![("rows", Expr::List(rows)), ("editable", Expr::Bool(true))],
30    )
31}
32
33/// Render a labelled matrix for routing, algorithms, and component grids.
34pub fn labeled_matrix_view(
35    lens: &str,
36    role: &str,
37    value: &Expr,
38    row_labels: &[&str],
39    col_labels: &[&str],
40) -> Expr {
41    node(
42        "matrix",
43        vec![
44            ("lens", Expr::Symbol(Symbol::new(lens))),
45            ("role", sym(role)),
46            ("rows", Expr::List(matrix_rows(value))),
47            ("row-labels", string_list(row_labels)),
48            ("col-labels", string_list(col_labels)),
49            ("editable", Expr::Bool(true)),
50        ],
51    )
52}
53
54/// Set a matrix cell, returning the new matrix value. Out-of-range indices
55/// leave the matrix unchanged.
56pub fn set_cell(value: &Expr, row: usize, col: usize, new_value: f64) -> Expr {
57    let Expr::List(rows) = value else {
58        return value.clone();
59    };
60    let mut rows = rows.clone();
61    if let Some(Expr::List(cells)) = rows.get_mut(row)
62        && col < cells.len()
63    {
64        cells[col] = number(new_value);
65    }
66    Expr::List(rows)
67}
68
69/// Read a matrix cell as `f64`.
70pub fn cell(value: &Expr, row: usize, col: usize) -> Option<f64> {
71    let Expr::List(rows) = value else {
72        return None;
73    };
74    match rows.get(row) {
75        Some(Expr::List(cells)) => cells.get(col).and_then(as_f64),
76        _ => None,
77    }
78}
79
80/// The `[row, col]` edit-field path for a matrix cell, in the standard segment
81/// form used by the universal editor.
82pub fn cell_path(row: usize, col: usize) -> Expr {
83    sim_value::path::Path::new().index(row).index(col).to_expr()
84}
85
86fn matrix_rows(value: &Expr) -> Vec<Expr> {
87    match value {
88        Expr::List(rows) => rows
89            .iter()
90            .map(|row| match row {
91                Expr::List(cells) => Expr::List(cells.clone()),
92                other => Expr::List(vec![other.clone()]),
93            })
94            .collect(),
95        _ => Vec::new(),
96    }
97}
98
99fn string_list(values: &[&str]) -> Expr {
100    Expr::List(
101        values
102            .iter()
103            .map(|value| Expr::String((*value).to_owned()))
104            .collect(),
105    )
106}