1use runmat_builtins::{LogicalArray, SparseTensor, Value};
2
3pub fn matlab_class_name(value: &Value) -> String {
5 match value {
6 Value::Num(_) | Value::ComplexTensor(_) | Value::Complex(_, _) => "double".to_string(),
7 Value::Tensor(tensor) => tensor.dtype.class_name().to_string(),
8 Value::SparseTensor(_) => "double".to_string(),
9 Value::Int(iv) => iv.class_name().to_string(),
10 Value::Bool(_) | Value::LogicalArray(_) => "logical".to_string(),
11 Value::String(_) | Value::StringArray(_) => "string".to_string(),
12 Value::CharArray(_) => "char".to_string(),
13 Value::Symbolic(_) | Value::SymbolicArray(_) => "sym".to_string(),
14 Value::Cell(_) => "cell".to_string(),
15 Value::Struct(_) => "struct".to_string(),
16 Value::GpuTensor(_) => "gpuArray".to_string(),
17 Value::FunctionHandle(_)
18 | Value::ExternalFunctionHandle(_)
19 | Value::MethodFunctionHandle(_)
20 | Value::BoundFunctionHandle { .. }
21 | Value::Closure(_) => "function_handle".to_string(),
22 Value::HandleObject(handle) => {
23 if handle.class_name.is_empty() {
24 "handle".to_string()
25 } else {
26 handle.class_name.clone()
27 }
28 }
29 Value::Listener(_) => "event.listener".to_string(),
30 Value::OutputList(_) => "OutputList".to_string(),
33 Value::Object(obj) => obj.class_name.clone(),
34 Value::ClassRef(_) => "meta.class".to_string(),
35 Value::MException(_) => "MException".to_string(),
36 }
37}
38
39pub fn value_shape(value: &Value) -> Option<Vec<usize>> {
41 match value {
42 Value::Num(_)
43 | Value::Int(_)
44 | Value::Bool(_)
45 | Value::Complex(_, _)
46 | Value::Symbolic(_) => Some(vec![1, 1]),
47 Value::SymbolicArray(arr) => Some(arr.shape.clone()),
48 Value::LogicalArray(arr) => Some(arr.shape.clone()),
49 Value::StringArray(sa) => Some(sa.shape.clone()),
50 Value::String(s) => Some(vec![1, s.chars().count()]),
51 Value::CharArray(ca) => Some(vec![ca.rows, ca.cols]),
52 Value::Tensor(t) => Some(t.shape.clone()),
53 Value::SparseTensor(s) => Some(vec![s.rows, s.cols]),
54 Value::ComplexTensor(t) => Some(t.shape.clone()),
55 Value::Cell(ca) => Some(ca.shape.clone()),
56 Value::GpuTensor(handle) => Some(handle.shape.clone()),
57 Value::Object(obj) if obj.is_class("datetime") => match obj.properties.get("__serial") {
58 Some(Value::Tensor(tensor)) => Some(tensor.shape.clone()),
59 Some(Value::Num(_)) => Some(vec![1, 1]),
60 _ => None,
61 },
62 _ => None,
63 }
64}
65
66pub fn numeric_dtype_label(value: &Value) -> Option<&'static str> {
68 match value {
69 Value::Num(_) | Value::Complex(_, _) => Some("double"),
70 Value::Tensor(t) => Some(t.dtype.class_name()),
71 Value::LogicalArray(_) => Some("logical"),
72 Value::Int(iv) => Some(iv.class_name()),
73 _ => None,
74 }
75}
76
77pub fn approximate_size_bytes(value: &Value) -> Option<u64> {
79 Some(match value {
80 Value::Num(_) | Value::Int(_) | Value::Complex(_, _) => 8,
81 Value::Bool(_) => 1,
82 Value::LogicalArray(arr) => arr.data.len() as u64,
83 Value::Tensor(t) => (t.data.len() * 8) as u64,
84 Value::SparseTensor(s) => sparse_tensor_memory_bytes(s),
85 Value::ComplexTensor(t) => (t.data.len() * 16) as u64,
86 Value::String(s) => s.len() as u64,
87 Value::StringArray(sa) => sa.data.iter().map(|s| s.len() as u64).sum(),
88 Value::CharArray(ca) => (ca.rows * ca.cols) as u64,
89 Value::Symbolic(expr) => expr.to_string().len() as u64,
90 Value::SymbolicArray(arr) => arr
91 .data
92 .iter()
93 .map(|expr| expr.to_string().len() as u64)
94 .sum(),
95 _ => return None,
96 })
97}
98
99pub fn sparse_tensor_memory_bytes(sparse: &SparseTensor) -> u64 {
101 sparse_tensor_memory_bytes_from_lengths(
102 sparse.values.len(),
103 sparse.row_indices.len(),
104 sparse.col_ptrs.len(),
105 )
106}
107
108fn sparse_tensor_memory_bytes_from_lengths(
109 values_len: usize,
110 row_indices_len: usize,
111 col_ptrs_len: usize,
112) -> u64 {
113 (values_len as u64)
114 .saturating_mul(std::mem::size_of::<f64>() as u64)
115 .saturating_add(
116 (row_indices_len as u64).saturating_mul(std::mem::size_of::<usize>() as u64),
117 )
118 .saturating_add((col_ptrs_len as u64).saturating_mul(std::mem::size_of::<usize>() as u64))
119}
120
121pub fn preview_numeric_values(value: &Value, limit: usize) -> Option<(Vec<f64>, bool)> {
123 match value {
124 Value::Num(n) => Some((vec![*n], false)),
125 Value::Int(iv) => Some((vec![iv.to_f64()], false)),
126 Value::Bool(flag) => Some((vec![if *flag { 1.0 } else { 0.0 }], false)),
127 Value::Tensor(t) => Some(preview_f64_slice(&t.data, limit)),
128 Value::SparseTensor(s) => Some(preview_sparse_tensor(s, limit)),
129 Value::LogicalArray(arr) => Some(preview_logical_slice(arr, limit)),
130 Value::StringArray(_) | Value::String(_) | Value::CharArray(_) => None,
131 Value::ComplexTensor(_) | Value::Complex(_, _) => None,
132 Value::Cell(_)
133 | Value::Symbolic(_)
134 | Value::SymbolicArray(_)
135 | Value::Struct(_)
136 | Value::Object(_)
137 | Value::HandleObject(_)
138 | Value::Listener(_)
139 | Value::OutputList(_)
140 | Value::FunctionHandle(_)
141 | Value::ExternalFunctionHandle(_)
142 | Value::MethodFunctionHandle(_)
143 | Value::BoundFunctionHandle { .. }
144 | Value::Closure(_)
145 | Value::ClassRef(_)
146 | Value::MException(_)
147 | Value::GpuTensor(_) => None,
148 }
149}
150
151fn preview_f64_slice(data: &[f64], limit: usize) -> (Vec<f64>, bool) {
152 if data.len() > limit {
153 (data[..limit].to_vec(), true)
154 } else {
155 (data.to_vec(), false)
156 }
157}
158
159fn preview_sparse_tensor(sparse: &SparseTensor, limit: usize) -> (Vec<f64>, bool) {
160 let total_len = sparse.rows.saturating_mul(sparse.cols);
161 let preview_len = total_len.min(limit);
162 let mut preview = Vec::with_capacity(preview_len);
163 if sparse.rows == 0 {
164 return (preview, false);
165 }
166 for linear_index in 0..preview_len {
167 let row = linear_index % sparse.rows;
168 let col = linear_index / sparse.rows;
169 preview.push(sparse.get(row, col).unwrap_or(0.0));
170 }
171 (preview, total_len > limit)
172}
173
174fn preview_logical_slice(arr: &LogicalArray, limit: usize) -> (Vec<f64>, bool) {
175 let truncated = arr.data.len() > limit;
176 let mut preview = Vec::with_capacity(arr.data.len().min(limit));
177 for value in arr.data.iter().take(limit) {
178 preview.push(if *value == 0 { 0.0 } else { 1.0 });
179 }
180 (preview, truncated)
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use runmat_builtins::{NumericDType, ObjectInstance, Tensor};
187
188 #[test]
189 fn approximate_size_bytes_uses_f64_width_for_integer_dtypes() {
190 let u8_tensor = Tensor::new_with_dtype(vec![1.0, 2.0, 3.0], vec![3, 1], NumericDType::U8)
192 .expect("tensor");
193 let u16_tensor = Tensor::new_with_dtype(vec![1.0, 2.0, 3.0], vec![3, 1], NumericDType::U16)
194 .expect("tensor");
195 let f32_tensor = Tensor::new_with_dtype(vec![1.0, 2.0, 3.0], vec![3, 1], NumericDType::F32)
196 .expect("tensor");
197
198 assert_eq!(approximate_size_bytes(&Value::Tensor(u8_tensor)), Some(24));
199 assert_eq!(approximate_size_bytes(&Value::Tensor(u16_tensor)), Some(24));
200 assert_eq!(approximate_size_bytes(&Value::Tensor(f32_tensor)), Some(24));
201 }
202
203 #[test]
204 fn sparse_tensor_memory_bytes_uses_saturating_arithmetic() {
205 let sparse =
206 SparseTensor::new(3, 2, vec![0, 1, 2], vec![0, 2], vec![4.0, 5.0]).expect("sparse");
207 let expected = (2 * std::mem::size_of::<f64>())
208 + (2 * std::mem::size_of::<usize>())
209 + (3 * std::mem::size_of::<usize>());
210
211 assert_eq!(sparse_tensor_memory_bytes(&sparse), expected as u64);
212 assert_eq!(
213 sparse_tensor_memory_bytes_from_lengths(usize::MAX, usize::MAX, usize::MAX),
214 u64::MAX
215 );
216 }
217
218 #[test]
219 fn datetime_object_shape_comes_from_internal_serial_tensor() {
220 let mut object = ObjectInstance::new("datetime".to_string());
221 object.properties.insert(
222 "__serial".to_string(),
223 Value::Tensor(Tensor::new(vec![739351.0, 739352.0], vec![2, 1]).expect("tensor")),
224 );
225
226 assert_eq!(value_shape(&Value::Object(object)), Some(vec![2, 1]));
227 }
228
229 #[test]
230 fn sparse_preview_uses_logical_column_major_values() {
231 let sparse = SparseTensor::new(3, 3, vec![0, 1, 1, 3], vec![1, 0, 2], vec![4.0, 5.0, 6.0])
232 .expect("sparse");
233
234 assert_eq!(
235 preview_numeric_values(&Value::SparseTensor(sparse), 9),
236 Some((vec![0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 5.0, 0.0, 6.0], false))
237 );
238 }
239
240 #[test]
241 fn sparse_preview_truncates_by_logical_element_count() {
242 let sparse = SparseTensor::zeros(1000, 1000);
243
244 assert_eq!(
245 preview_numeric_values(&Value::SparseTensor(sparse), 3),
246 Some((vec![0.0, 0.0, 0.0], true))
247 );
248 }
249}