1use crate::indexing::plan::{build_index_plan, IndexPlan};
2use crate::indexing::selectors::{build_slice_selectors, SliceSelector};
3use crate::RuntimeError;
4use runmat_value::{
5 ComplexTensor, IntValue, IntegerComplexStorage, IntegerStorage, NumericDType, NumericScalar,
6 SparseTensor, StringArray, Tensor, Value,
7};
8use std::collections::HashMap;
9
10fn map_slice_shape_error(err: impl std::fmt::Display) -> RuntimeError {
11 crate::runtime_error::semantic_error(
12 "ShapeMismatch",
13 format!("shape mismatch for slice result: {err}"),
14 )
15}
16
17fn map_slice_acceleration_error(err: impl std::fmt::Display) -> RuntimeError {
18 crate::runtime_error::semantic_error("AccelerationOperationFailed", format!("slice: {err}"))
19}
20
21fn numeric_selection_value(
22 tensor: &Tensor,
23 indices: &[usize],
24 output_shape: Vec<usize>,
25) -> Result<Value, RuntimeError> {
26 let storage = tensor
27 .clone()
28 .into_numeric_storage()
29 .map_err(map_slice_shape_error)?;
30 if let [index] = indices {
31 return match storage
32 .value_at(*index)
33 .expect("validated numeric selection index")
34 {
35 NumericScalar::F64(value) => Ok(Value::Num(value)),
36 NumericScalar::F32(value) => Tensor::from_f32(vec![value], vec![1, 1])
37 .map(Value::Tensor)
38 .map_err(map_slice_shape_error),
39 value => Ok(Value::Int(
40 value
41 .into_int_value()
42 .expect("non-floating numeric scalar is integer"),
43 )),
44 };
45 }
46 let selected = storage.gather(indices).map_err(map_slice_shape_error)?;
47 Tensor::from_numeric_storage(selected, output_shape)
48 .map(Value::Tensor)
49 .map_err(map_slice_shape_error)
50}
51
52pub async fn read_tensor_slice_1d(
53 tensor: &Tensor,
54 colon_mask: u32,
55 end_mask: u32,
56 numeric: &[Value],
57) -> Result<Value, RuntimeError> {
58 read_tensor_slice_nd(tensor, 1, colon_mask, end_mask, numeric).await
59}
60
61pub fn try_tensor_slice_2d_fast_path(
62 tensor: &Tensor,
63 dims: usize,
64 selectors: &[SliceSelector],
65) -> Result<Option<Value>, RuntimeError> {
66 if dims != 2 {
67 return Ok(None);
68 }
69 let rows = tensor.shape.first().copied().unwrap_or(1);
70 let cols = tensor.shape.get(1).copied().unwrap_or(1);
71 match (&selectors[0], &selectors[1]) {
72 (SliceSelector::Colon, SliceSelector::Scalar(j)) => {
73 let j0 = *j - 1;
74 if j0 >= cols {
75 return Err(crate::runtime_error::semantic_error(
76 "IndexOutOfBounds",
77 "Index out of bounds",
78 ));
79 }
80 let start = j0 * rows;
81 let indices: Vec<usize> = (start..start + rows).collect();
82 numeric_selection_value(tensor, &indices, vec![rows, 1]).map(Some)
83 }
84 (SliceSelector::Scalar(i), SliceSelector::Colon) => {
85 let i0 = *i - 1;
86 if i0 >= rows {
87 return Err(crate::runtime_error::semantic_error(
88 "IndexOutOfBounds",
89 "Index out of bounds",
90 ));
91 }
92 let indices: Vec<usize> = (0..cols).map(|col| i0 + col * rows).collect();
93 numeric_selection_value(tensor, &indices, vec![1, cols]).map(Some)
94 }
95 (SliceSelector::Colon, SliceSelector::Indices(js)) => {
96 let mut indices = Vec::with_capacity(rows * js.len());
97 for &j in js {
98 let j0 = j - 1;
99 if j0 >= cols {
100 return Err(crate::runtime_error::semantic_error(
101 "IndexOutOfBounds",
102 "Index out of bounds",
103 ));
104 }
105 let start = j0 * rows;
106 indices.extend(start..start + rows);
107 }
108 numeric_selection_value(tensor, &indices, vec![rows, js.len()]).map(Some)
109 }
110 (SliceSelector::Indices(is), SliceSelector::Colon) => {
111 let mut indices = Vec::with_capacity(is.len() * cols);
112 for col in 0..cols {
113 for &i in is {
114 let i0 = i - 1;
115 if i0 >= rows {
116 return Err(crate::runtime_error::semantic_error(
117 "IndexOutOfBounds",
118 "Index out of bounds",
119 ));
120 }
121 indices.push(i0 + col * rows);
122 }
123 }
124 numeric_selection_value(tensor, &indices, vec![is.len(), cols]).map(Some)
125 }
126 _ => Ok(None),
127 }
128}
129
130pub async fn read_tensor_slice_nd(
131 tensor: &Tensor,
132 dims: usize,
133 colon_mask: u32,
134 end_mask: u32,
135 numeric: &[Value],
136) -> Result<Value, RuntimeError> {
137 let selectors =
138 build_slice_selectors(dims, colon_mask, end_mask, numeric, &tensor.shape).await?;
139 if let Some(value) = try_tensor_slice_2d_fast_path(tensor, dims, &selectors)? {
140 return Ok(value);
141 }
142 let plan = build_index_plan(&selectors, dims, &tensor.shape)?;
143 let indices: Vec<usize> = plan.indices.iter().map(|&index| index as usize).collect();
144 numeric_selection_value(tensor, &indices, plan.output_shape)
145}
146
147pub fn read_tensor_slice_from_plan(
148 tensor: &Tensor,
149 plan: &IndexPlan,
150) -> Result<Value, RuntimeError> {
151 let indices: Vec<usize> = plan.indices.iter().map(|&index| index as usize).collect();
152 numeric_selection_value(tensor, &indices, plan.output_shape.clone())
153}
154
155fn sparse_output_shape(plan: &IndexPlan) -> Result<(usize, usize), RuntimeError> {
156 match plan.output_shape.as_slice() {
157 [rows, cols] => Ok((*rows, *cols)),
158 [len] => Ok((*len, 1)),
159 _ => Err(crate::runtime_error::semantic_error(
160 "UnsupportedSparseIndexRank",
161 "Sparse indexing currently supports two-dimensional outputs",
162 )),
163 }
164}
165
166fn sparse_scalar_value(
167 sparse: &SparseTensor,
168 row: usize,
169 col: usize,
170) -> Result<Value, RuntimeError> {
171 if sparse.is_logical() {
172 let scalar = if sparse.logical_at(row, col).unwrap_or(false) {
173 SparseTensor::new_logical(1, 1, vec![0, 1], vec![0]).map_err(map_slice_shape_error)?
174 } else {
175 SparseTensor::zeros_logical(1, 1)
176 };
177 return Ok(Value::SparseTensor(scalar));
178 }
179 if let Some(storage) = sparse.integer_storage() {
180 let scalar = match sparse.integer_at(row, col) {
181 Some(value) => {
182 SparseTensor::new_integer_like(1, 1, vec![0, 1], vec![0], vec![value], storage)
183 }
184 None => Ok(SparseTensor::zeros_with_integer_storage(1, 1, storage)),
185 }
186 .map_err(map_slice_shape_error)?;
187 return Ok(Value::SparseTensor(scalar));
188 }
189
190 if sparse.numeric_dtype() == Some(NumericDType::F32) {
191 let value = sparse.get(row, col).unwrap_or(0.0) as f32;
192 let scalar = if value == 0.0 {
193 SparseTensor::zeros_f32(1, 1)
194 } else {
195 SparseTensor::new_f32(1, 1, vec![0, 1], vec![0], vec![value])
196 .map_err(map_slice_shape_error)?
197 };
198 return Ok(Value::SparseTensor(scalar));
199 }
200 let value = sparse.get(row, col).unwrap_or(0.0);
201 if value == 0.0 {
202 return Ok(Value::SparseTensor(SparseTensor::zeros(1, 1)));
203 }
204 let sparse =
205 SparseTensor::new(1, 1, vec![0, 1], vec![0], vec![value]).map_err(map_slice_shape_error)?;
206 Ok(Value::SparseTensor(sparse))
207}
208
209fn checked_sparse_numel(sparse: &SparseTensor) -> Result<usize, RuntimeError> {
210 sparse.rows.checked_mul(sparse.cols).ok_or_else(|| {
211 crate::runtime_error::semantic_error("IndexOutOfBounds", "Sparse dimensions overflow")
212 })
213}
214
215fn sparse_zeros_like(sparse: &SparseTensor, rows: usize, cols: usize) -> SparseTensor {
216 if sparse.is_logical() {
217 SparseTensor::zeros_logical(rows, cols)
218 } else if let Some(storage) = sparse.integer_storage() {
219 SparseTensor::zeros_with_integer_storage(rows, cols, storage)
220 } else if sparse.numeric_dtype() == Some(NumericDType::F32) {
221 SparseTensor::zeros_f32(rows, cols)
222 } else {
223 SparseTensor::zeros(rows, cols)
224 }
225}
226
227fn typed_sparse_from_column_entries(
228 rows: usize,
229 cols: usize,
230 mut col_entries: Vec<Vec<(usize, IntValue)>>,
231 prototype: &IntegerStorage,
232) -> Result<Value, RuntimeError> {
233 let mut col_ptrs = Vec::with_capacity(cols.saturating_add(1));
234 let mut row_indices = Vec::new();
235 let mut values = Vec::new();
236 col_ptrs.push(0);
237 for entries in col_entries.iter_mut().take(cols) {
238 entries.sort_by_key(|(row, _)| *row);
239 for (row, value) in entries.drain(..) {
240 if !value.is_zero() {
241 row_indices.push(row);
242 values.push(value);
243 }
244 }
245 col_ptrs.push(values.len());
246 }
247 let sparse =
248 SparseTensor::new_integer_like(rows, cols, col_ptrs, row_indices, values, prototype)
249 .map_err(map_slice_shape_error)?;
250 Ok(Value::SparseTensor(sparse))
251}
252
253fn linear_sparse_slice(
254 sparse: &SparseTensor,
255 selector: &SliceSelector,
256) -> Result<Value, RuntimeError> {
257 let total = checked_sparse_numel(sparse)?;
258 let base_is_row_vector = sparse.rows == 1 && sparse.cols > 1;
259 if matches!(selector, SliceSelector::Colon) {
260 let mut row_indices = Vec::with_capacity(sparse.nnz());
261 for col in 0..sparse.cols {
262 for entry in sparse.col_ptrs[col]..sparse.col_ptrs[col + 1] {
263 row_indices.push(sparse.row_indices[entry] + col * sparse.rows);
264 }
265 }
266 let sparse = if sparse.is_logical() {
267 SparseTensor::new_logical(total, 1, vec![0, sparse.nnz()], row_indices)
268 } else if let Some(storage) = sparse.integer_storage() {
269 SparseTensor::new_integer(
270 total,
271 1,
272 vec![0, sparse.nnz()],
273 row_indices,
274 storage.clone(),
275 )
276 } else if let Some(values) = sparse.as_f32_slice() {
277 SparseTensor::new_f32(
278 total,
279 1,
280 vec![0, sparse.nnz()],
281 row_indices,
282 values.to_vec(),
283 )
284 } else {
285 SparseTensor::new(
286 total,
287 1,
288 vec![0, sparse.nnz()],
289 row_indices,
290 sparse
291 .as_f64_slice()
292 .expect("double sparse storage")
293 .to_vec(),
294 )
295 }
296 .map_err(map_slice_shape_error)?;
297 return Ok(Value::SparseTensor(sparse));
298 }
299 let (indices, output_shape) = match selector {
300 SliceSelector::Colon => unreachable!("colon sparse linear slices return early"),
301 SliceSelector::Scalar(index) => (vec![*index], vec![1, 1]),
302 SliceSelector::Indices(indices) => {
303 let shape = if indices.is_empty() {
304 vec![0, 1]
305 } else if indices.len() == 1 {
306 vec![1, 1]
307 } else if base_is_row_vector {
308 vec![1, indices.len()]
309 } else {
310 vec![indices.len(), 1]
311 };
312 (indices.clone(), shape)
313 }
314 SliceSelector::LinearIndices {
315 values,
316 output_shape,
317 } => (values.clone(), output_shape.clone()),
318 };
319 if indices.iter().any(|&index| index == 0 || index > total) {
320 return Err(crate::runtime_error::semantic_error(
321 "IndexOutOfBounds",
322 "Index out of bounds",
323 ));
324 }
325 if indices.len() == 1 {
326 let lin = indices[0] - 1;
327 let row = lin % sparse.rows;
328 let col = lin / sparse.rows;
329 return sparse_scalar_value(sparse, row, col);
330 }
331 let (out_rows, out_cols) = match output_shape.as_slice() {
332 [rows, cols] => (*rows, *cols),
333 [len] => (*len, 1),
334 _ => {
335 return Err(crate::runtime_error::semantic_error(
336 "UnsupportedSparseIndexRank",
337 "Sparse indexing currently supports two-dimensional outputs",
338 ))
339 }
340 };
341 if indices.is_empty() {
342 return Ok(Value::SparseTensor(sparse_zeros_like(
343 sparse, out_rows, out_cols,
344 )));
345 }
346
347 if sparse.is_logical() {
348 let mut col_rows = vec![Vec::new(); out_cols];
349 for (out_pos, &index) in indices.iter().enumerate() {
350 let base_lin = index - 1;
351 let base_row = base_lin % sparse.rows;
352 let base_col = base_lin / sparse.rows;
353 if sparse.logical_at(base_row, base_col).unwrap_or(false) {
354 col_rows[out_pos / out_rows].push(out_pos % out_rows);
355 }
356 }
357 return sparse_logical_from_column_rows(out_rows, out_cols, col_rows);
358 }
359
360 if let Some(storage) = sparse.integer_storage() {
361 let mut col_entries: Vec<Vec<(usize, IntValue)>> = vec![Vec::new(); out_cols];
362 for (out_pos, &index) in indices.iter().enumerate() {
363 let base_lin = index - 1;
364 let base_row = base_lin % sparse.rows;
365 let base_col = base_lin / sparse.rows;
366 if let Some(value) = sparse.integer_at(base_row, base_col) {
367 let out_row = out_pos % out_rows;
368 let out_col = out_pos / out_rows;
369 col_entries[out_col].push((out_row, value));
370 }
371 }
372 return typed_sparse_from_column_entries(out_rows, out_cols, col_entries, storage);
373 }
374
375 if sparse.numeric_dtype() == Some(NumericDType::F32) {
376 let mut col_entries: Vec<Vec<(usize, f32)>> = vec![Vec::new(); out_cols];
377 for (out_pos, &index) in indices.iter().enumerate() {
378 let base_lin = index - 1;
379 let base_row = base_lin % sparse.rows;
380 let base_col = base_lin / sparse.rows;
381 if let Some(value) = sparse.get(base_row, base_col).map(|value| value as f32) {
382 if value != 0.0 {
383 let out_row = out_pos % out_rows;
384 let out_col = out_pos / out_rows;
385 col_entries[out_col].push((out_row, value));
386 }
387 }
388 }
389 return sparse_f32_from_column_entries(out_rows, out_cols, col_entries);
390 }
391
392 let mut col_entries: Vec<Vec<(usize, f64)>> = vec![Vec::new(); out_cols];
393 for (out_pos, &index) in indices.iter().enumerate() {
394 let base_lin = index - 1;
395 let base_row = base_lin % sparse.rows;
396 let base_col = base_lin / sparse.rows;
397 if let Some(value) = sparse.get(base_row, base_col) {
398 if value != 0.0 {
399 let out_row = out_pos % out_rows;
400 let out_col = out_pos / out_rows;
401 col_entries[out_col].push((out_row, value));
402 }
403 }
404 }
405 sparse_from_column_entries(out_rows, out_cols, col_entries)
406}
407
408fn selector_indices(selector: &SliceSelector, dim_len: usize) -> Vec<usize> {
409 match selector {
410 SliceSelector::Colon => (1..=dim_len).collect(),
411 SliceSelector::Scalar(index) => vec![*index],
412 SliceSelector::Indices(indices)
413 | SliceSelector::LinearIndices {
414 values: indices, ..
415 } => indices.clone(),
416 }
417}
418
419fn sparse_from_column_entries(
420 rows: usize,
421 cols: usize,
422 mut col_entries: Vec<Vec<(usize, f64)>>,
423) -> Result<Value, RuntimeError> {
424 let mut col_ptrs = Vec::with_capacity(cols.saturating_add(1));
425 let mut row_indices = Vec::new();
426 let mut values = Vec::new();
427 col_ptrs.push(0);
428 for entries in col_entries.iter_mut().take(cols) {
429 entries.sort_by_key(|(row, _)| *row);
430 for &(row, value) in entries.iter() {
431 if value != 0.0 {
432 row_indices.push(row);
433 values.push(value);
434 }
435 }
436 col_ptrs.push(values.len());
437 }
438 let sparse = SparseTensor::new(rows, cols, col_ptrs, row_indices, values)
439 .map_err(map_slice_shape_error)?;
440 Ok(Value::SparseTensor(sparse))
441}
442
443fn sparse_f32_from_column_entries(
444 rows: usize,
445 cols: usize,
446 mut col_entries: Vec<Vec<(usize, f32)>>,
447) -> Result<Value, RuntimeError> {
448 let mut col_ptrs = Vec::with_capacity(cols.saturating_add(1));
449 let mut row_indices = Vec::new();
450 let mut values = Vec::new();
451 col_ptrs.push(0);
452 for entries in col_entries.iter_mut().take(cols) {
453 entries.sort_by_key(|(row, _)| *row);
454 for &(row, value) in entries.iter() {
455 if value != 0.0 {
456 row_indices.push(row);
457 values.push(value);
458 }
459 }
460 col_ptrs.push(values.len());
461 }
462 let sparse = SparseTensor::new_f32(rows, cols, col_ptrs, row_indices, values)
463 .map_err(map_slice_shape_error)?;
464 Ok(Value::SparseTensor(sparse))
465}
466
467fn sparse_logical_from_column_rows(
468 rows: usize,
469 cols: usize,
470 mut col_rows: Vec<Vec<usize>>,
471) -> Result<Value, RuntimeError> {
472 let mut col_ptrs = Vec::with_capacity(cols.saturating_add(1));
473 let mut row_indices = Vec::new();
474 col_ptrs.push(0);
475 for rows in col_rows.iter_mut().take(cols) {
476 rows.sort_unstable();
477 row_indices.extend(rows.iter().copied());
478 col_ptrs.push(row_indices.len());
479 }
480 let sparse = SparseTensor::new_logical(rows, cols, col_ptrs, row_indices)
481 .map_err(map_slice_shape_error)?;
482 Ok(Value::SparseTensor(sparse))
483}
484
485fn matrix_sparse_slice(
486 sparse: &SparseTensor,
487 selectors: &[SliceSelector],
488) -> Result<Value, RuntimeError> {
489 let row_selector = selectors.first().unwrap_or(&SliceSelector::Colon);
490 let col_selector = selectors.get(1).unwrap_or(&SliceSelector::Colon);
491 let all_rows = matches!(row_selector, SliceSelector::Colon);
492 let rows = if all_rows {
493 Vec::new()
494 } else {
495 selector_indices(row_selector, sparse.rows)
496 };
497 let cols = selector_indices(col_selector, sparse.cols);
498 if (!all_rows && rows.iter().any(|&row| row == 0 || row > sparse.rows))
499 || cols.iter().any(|&col| col == 0 || col > sparse.cols)
500 {
501 return Err(crate::runtime_error::semantic_error(
502 "IndexOutOfBounds",
503 "Index out of bounds",
504 ));
505 }
506 let out_rows = if all_rows { sparse.rows } else { rows.len() };
507 let out_cols = cols.len();
508 if out_rows == 0 || out_cols == 0 {
509 return Ok(Value::SparseTensor(sparse_zeros_like(
510 sparse, out_rows, out_cols,
511 )));
512 }
513
514 let mut row_positions: HashMap<usize, Vec<usize>> = HashMap::new();
515 if !all_rows {
516 for (out_row, &row) in rows.iter().enumerate() {
517 row_positions.entry(row - 1).or_default().push(out_row);
518 }
519 }
520 if let Some(storage) = sparse.integer_storage() {
521 let mut col_entries: Vec<Vec<(usize, IntValue)>> = vec![Vec::new(); out_cols];
522 for (out_col, &col) in cols.iter().enumerate() {
523 let base_col = col - 1;
524 for entry in sparse.col_ptrs[base_col]..sparse.col_ptrs[base_col + 1] {
525 let base_row = sparse.row_indices[entry];
526 let value = storage
527 .value_at(entry)
528 .expect("typed sparse entry is present");
529 if all_rows {
530 col_entries[out_col].push((base_row, value));
531 } else if let Some(output_rows) = row_positions.get(&base_row) {
532 for &out_row in output_rows {
533 col_entries[out_col].push((out_row, value.clone()));
534 }
535 }
536 }
537 }
538 if out_rows == 1 && out_cols == 1 {
539 let value = col_entries
540 .first()
541 .and_then(|entries| entries.first())
542 .map(|(_, value)| value.clone());
543 let scalar = match value {
544 Some(value) => {
545 SparseTensor::new_integer_like(1, 1, vec![0, 1], vec![0], vec![value], storage)
546 }
547 None => Ok(SparseTensor::zeros_with_integer_storage(1, 1, storage)),
548 }
549 .map_err(map_slice_shape_error)?;
550 return Ok(Value::SparseTensor(scalar));
551 }
552 return typed_sparse_from_column_entries(out_rows, out_cols, col_entries, storage);
553 }
554
555 if sparse.is_logical() {
556 let mut col_rows = vec![Vec::new(); out_cols];
557 for (out_col, &col) in cols.iter().enumerate() {
558 let base_col = col - 1;
559 for entry in sparse.col_ptrs[base_col]..sparse.col_ptrs[base_col + 1] {
560 let base_row = sparse.row_indices[entry];
561 if all_rows {
562 col_rows[out_col].push(base_row);
563 } else if let Some(output_rows) = row_positions.get(&base_row) {
564 col_rows[out_col].extend(output_rows.iter().copied());
565 }
566 }
567 }
568 if out_rows == 1 && out_cols == 1 {
569 let scalar = if col_rows.first().is_some_and(|rows| !rows.is_empty()) {
570 SparseTensor::new_logical(1, 1, vec![0, 1], vec![0])
571 .map_err(map_slice_shape_error)?
572 } else {
573 SparseTensor::zeros_logical(1, 1)
574 };
575 return Ok(Value::SparseTensor(scalar));
576 }
577 return sparse_logical_from_column_rows(out_rows, out_cols, col_rows);
578 }
579
580 if let Some(values) = sparse.as_f32_slice() {
581 let mut col_entries = vec![Vec::new(); out_cols];
582 for (out_col, &col) in cols.iter().enumerate() {
583 let base_col = col - 1;
584 let start = sparse.col_ptrs[base_col];
585 let end = sparse.col_ptrs[base_col + 1];
586 for (&base_row, &value) in sparse.row_indices[start..end]
587 .iter()
588 .zip(&values[start..end])
589 {
590 if all_rows {
591 col_entries[out_col].push((base_row, value));
592 } else if let Some(output_rows) = row_positions.get(&base_row) {
593 for &out_row in output_rows {
594 col_entries[out_col].push((out_row, value));
595 }
596 }
597 }
598 }
599 if out_rows == 1 && out_cols == 1 {
600 let value = col_entries
601 .first()
602 .and_then(|entries| entries.first())
603 .map(|(_, value)| *value)
604 .unwrap_or(0.0);
605 let scalar = if value == 0.0 {
606 SparseTensor::zeros_f32(1, 1)
607 } else {
608 SparseTensor::new_f32(1, 1, vec![0, 1], vec![0], vec![value])
609 .map_err(map_slice_shape_error)?
610 };
611 return Ok(Value::SparseTensor(scalar));
612 }
613 return sparse_f32_from_column_entries(out_rows, out_cols, col_entries);
614 }
615
616 let values = sparse.as_f64_slice().expect("double sparse storage");
617 let mut col_entries = vec![Vec::new(); out_cols];
618 for (out_col, &col) in cols.iter().enumerate() {
619 let base_col = col - 1;
620 let start = sparse.col_ptrs[base_col];
621 let end = sparse.col_ptrs[base_col + 1];
622 for (&base_row, &value) in sparse.row_indices[start..end]
623 .iter()
624 .zip(&values[start..end])
625 {
626 if all_rows {
627 col_entries[out_col].push((base_row, value));
628 } else if let Some(output_rows) = row_positions.get(&base_row) {
629 for &out_row in output_rows {
630 col_entries[out_col].push((out_row, value));
631 }
632 }
633 }
634 }
635 if out_rows == 1 && out_cols == 1 {
636 let value = col_entries
637 .first()
638 .and_then(|entries| entries.first())
639 .map(|(_, value)| *value)
640 .unwrap_or(0.0);
641 if value == 0.0 {
642 return Ok(Value::SparseTensor(SparseTensor::zeros(1, 1)));
643 }
644 let scalar = SparseTensor::new(1, 1, vec![0, 1], vec![0], vec![value])
645 .map_err(map_slice_shape_error)?;
646 return Ok(Value::SparseTensor(scalar));
647 }
648 sparse_from_column_entries(out_rows, out_cols, col_entries)
649}
650
651pub async fn read_sparse_slice(
652 sparse: &SparseTensor,
653 dims: usize,
654 colon_mask: u32,
655 end_mask: u32,
656 numeric: &[Value],
657) -> Result<Value, RuntimeError> {
658 if sparse.integer_storage().is_some() {
659 crate::compatibility::ensure_sparse_integer_extension_enabled("indexed access")?;
660 }
661 let selectors =
662 build_slice_selectors(dims, colon_mask, end_mask, numeric, &sparse.shape()).await?;
663 match dims {
664 1 => linear_sparse_slice(
665 sparse,
666 selectors
667 .first()
668 .unwrap_or(&SliceSelector::Indices(Vec::new())),
669 ),
670 2 => matrix_sparse_slice(sparse, &selectors),
671 _ => {
672 let plan = build_index_plan(&selectors, dims, &sparse.shape())?;
673 read_sparse_slice_from_plan(sparse, &plan)
674 }
675 }
676}
677
678pub fn read_sparse_slice_from_plan(
679 sparse: &SparseTensor,
680 plan: &IndexPlan,
681) -> Result<Value, RuntimeError> {
682 if sparse.integer_storage().is_some() {
683 crate::compatibility::ensure_sparse_integer_extension_enabled("indexed access")?;
684 }
685 if plan.indices.len() == 1 {
686 let lin = plan.indices[0] as usize;
687 if sparse.rows == 0 || lin >= sparse.rows.saturating_mul(sparse.cols) {
688 return Err(crate::runtime_error::semantic_error(
689 "IndexOutOfBounds",
690 "Index out of bounds",
691 ));
692 }
693 let row = lin % sparse.rows;
694 let col = lin / sparse.rows;
695 return sparse_scalar_value(sparse, row, col);
696 }
697
698 let (out_rows, out_cols) = sparse_output_shape(plan)?;
699 if plan.indices.is_empty() {
700 return Ok(Value::SparseTensor(sparse_zeros_like(
701 sparse, out_rows, out_cols,
702 )));
703 }
704
705 let total = sparse.rows.checked_mul(sparse.cols).ok_or_else(|| {
706 crate::runtime_error::semantic_error("IndexOutOfBounds", "Sparse dimensions overflow")
707 })?;
708 let mut col_ptrs = Vec::with_capacity(out_cols.saturating_add(1));
709 let mut row_indices = Vec::new();
710 let mut values = Vec::new();
711 col_ptrs.push(0);
712 if let Some(storage) = sparse.integer_storage() {
713 let mut integer_values = Vec::new();
714 for out_col in 0..out_cols {
715 for out_row in 0..out_rows {
716 let out_lin = out_row + out_col * out_rows;
717 let Some(&base_lin) = plan.indices.get(out_lin) else {
718 return Err(crate::runtime_error::semantic_error(
719 "ShapeMismatch",
720 "sparse slice plan output shape does not match selected indices",
721 ));
722 };
723 let base_lin = base_lin as usize;
724 if sparse.rows == 0 || base_lin >= total {
725 return Err(crate::runtime_error::semantic_error(
726 "IndexOutOfBounds",
727 "Index out of bounds",
728 ));
729 }
730 let base_row = base_lin % sparse.rows;
731 let base_col = base_lin / sparse.rows;
732 if let Some(value) = sparse.integer_at(base_row, base_col) {
733 row_indices.push(out_row);
734 integer_values.push(value);
735 }
736 }
737 col_ptrs.push(integer_values.len());
738 }
739 let out = SparseTensor::new_integer_like(
740 out_rows,
741 out_cols,
742 col_ptrs,
743 row_indices,
744 integer_values,
745 storage,
746 )
747 .map_err(map_slice_shape_error)?;
748 return Ok(Value::SparseTensor(out));
749 }
750 for out_col in 0..out_cols {
751 for out_row in 0..out_rows {
752 let out_lin = out_row + out_col * out_rows;
753 let Some(&base_lin) = plan.indices.get(out_lin) else {
754 return Err(crate::runtime_error::semantic_error(
755 "ShapeMismatch",
756 "sparse slice plan output shape does not match selected indices",
757 ));
758 };
759 let base_lin = base_lin as usize;
760 if sparse.rows == 0 || base_lin >= total {
761 return Err(crate::runtime_error::semantic_error(
762 "IndexOutOfBounds",
763 "Index out of bounds",
764 ));
765 }
766 let base_row = base_lin % sparse.rows;
767 let base_col = base_lin / sparse.rows;
768 if let Some(value) = sparse.get(base_row, base_col) {
769 if value != 0.0 {
770 row_indices.push(out_row);
771 values.push(value);
772 }
773 }
774 }
775 col_ptrs.push(values.len());
776 }
777
778 let out = if sparse.is_logical() {
779 SparseTensor::new_logical(out_rows, out_cols, col_ptrs, row_indices)
780 } else if sparse.numeric_dtype() == Some(NumericDType::F32) {
781 SparseTensor::new_f32(
782 out_rows,
783 out_cols,
784 col_ptrs,
785 row_indices,
786 values.into_iter().map(|value| value as f32).collect(),
787 )
788 } else {
789 SparseTensor::new(out_rows, out_cols, col_ptrs, row_indices, values)
790 }
791 .map_err(map_slice_shape_error)?;
792 Ok(Value::SparseTensor(out))
793}
794
795pub async fn read_complex_slice(
796 tensor: &ComplexTensor,
797 dims: usize,
798 colon_mask: u32,
799 end_mask: u32,
800 numeric: &[Value],
801) -> Result<Value, RuntimeError> {
802 let selectors =
803 build_slice_selectors(dims, colon_mask, end_mask, numeric, &tensor.shape).await?;
804 let plan = build_index_plan(&selectors, dims, &tensor.shape)?;
805 read_complex_slice_from_plan(tensor, &plan)
806}
807
808pub fn read_complex_slice_from_plan(
809 tensor: &ComplexTensor,
810 plan: &IndexPlan,
811) -> Result<Value, RuntimeError> {
812 if let Some(storage) = tensor.integer_storage().as_ref() {
813 return read_integer_complex_slice_from_plan(storage, plan);
814 }
815 if plan.indices.is_empty() {
816 let empty = ComplexTensor::new(Vec::new(), plan.output_shape.clone())
817 .map_err(map_slice_shape_error)?;
818 return Ok(Value::ComplexTensor(empty));
819 }
820 if plan.indices.len() == 1 {
821 let lin = plan.indices[0] as usize;
822 let (re, im) = tensor.materialize_f64().get(lin).copied().ok_or_else(|| {
823 crate::runtime_error::semantic_error(
824 "IndexOutOfBounds",
825 "Slice error: complex index out of bounds",
826 )
827 })?;
828 return Ok(Value::Complex(re, im));
829 }
830 let mut out = Vec::with_capacity(plan.indices.len());
831 for &lin in &plan.indices {
832 let idx = lin as usize;
833 let value = tensor.materialize_f64().get(idx).copied().ok_or_else(|| {
834 crate::runtime_error::semantic_error(
835 "IndexOutOfBounds",
836 "Slice error: complex index out of bounds",
837 )
838 })?;
839 out.push(value);
840 }
841 let out_ct =
842 ComplexTensor::new(out, plan.output_shape.clone()).map_err(map_slice_shape_error)?;
843 Ok(Value::ComplexTensor(out_ct))
844}
845
846fn read_integer_complex_slice_from_plan(
847 storage: &IntegerComplexStorage,
848 plan: &IndexPlan,
849) -> Result<Value, RuntimeError> {
850 let mut real_values = Vec::with_capacity(plan.indices.len());
851 let mut imag_values = Vec::with_capacity(plan.indices.len());
852 for &linear_index in &plan.indices {
853 let index = linear_index as usize;
854 let real = storage.real.value_at(index).ok_or_else(|| {
855 crate::runtime_error::semantic_error(
856 "IndexOutOfBounds",
857 "Slice error: complex index out of bounds",
858 )
859 })?;
860 let imag = storage.imag.value_at(index).ok_or_else(|| {
861 crate::runtime_error::semantic_error(
862 "IndexOutOfBounds",
863 "Slice error: complex index out of bounds",
864 )
865 })?;
866 real_values.push(real);
867 imag_values.push(imag);
868 }
869
870 let real = storage
871 .real
872 .from_same_class_values(real_values)
873 .map_err(map_slice_shape_error)?;
874 let imag = storage
875 .imag
876 .from_same_class_values(imag_values)
877 .map_err(map_slice_shape_error)?;
878 let result = ComplexTensor::new_integer(
879 IntegerComplexStorage::new(real, imag).map_err(map_slice_shape_error)?,
880 plan.output_shape.clone(),
881 )
882 .map_err(map_slice_shape_error)?;
883 Ok(Value::ComplexTensor(result))
884}
885
886pub async fn read_gpu_slice(
887 handle: &runmat_accelerate_api::GpuTensorHandle,
888 dims: usize,
889 colon_mask: u32,
890 end_mask: u32,
891 numeric: &[Value],
892) -> Result<Value, RuntimeError> {
893 let base_shape = handle.shape.clone();
894 let selectors = build_slice_selectors(dims, colon_mask, end_mask, numeric, &base_shape).await?;
895 let plan = build_index_plan(&selectors, dims, &base_shape)?;
896 read_gpu_slice_from_plan(handle, &plan)
897}
898
899pub fn read_gpu_slice_from_plan(
900 handle: &runmat_accelerate_api::GpuTensorHandle,
901 plan: &IndexPlan,
902) -> Result<Value, RuntimeError> {
903 let provider = runmat_accelerate_api::provider().ok_or_else(|| {
904 crate::runtime_error::semantic_error(
905 "AccelerationProviderUnavailable",
906 "No acceleration provider registered",
907 )
908 })?;
909 if plan.indices.is_empty() {
910 if let Some(integer_type) = runmat_accelerate_api::handle_integer_type(handle) {
911 return upload_empty_integer_gpu_slice(provider, integer_type, &plan.output_shape);
912 }
913 let zeros = provider
914 .zeros(&plan.output_shape)
915 .map_err(map_slice_acceleration_error)?;
916 Ok(Value::GpuTensor(zeros))
917 } else {
918 let result = provider
919 .gather_linear(handle, &plan.indices, &plan.output_shape)
920 .map_err(map_slice_acceleration_error)?;
921 Ok(Value::GpuTensor(result))
922 }
923}
924
925fn upload_empty_integer_gpu_slice(
926 provider: &dyn runmat_accelerate_api::AccelProvider,
927 integer_type: runmat_accelerate_api::IntegerElementType,
928 output_shape: &[usize],
929) -> Result<Value, RuntimeError> {
930 use runmat_accelerate_api::{HostIntegerDataView, HostIntegerTensorView, IntegerElementType};
931
932 let data = match integer_type {
933 IntegerElementType::I8 => HostIntegerDataView::I8(&[]),
934 IntegerElementType::I16 => HostIntegerDataView::I16(&[]),
935 IntegerElementType::I32 => HostIntegerDataView::I32(&[]),
936 IntegerElementType::I64 => HostIntegerDataView::I64(&[]),
937 IntegerElementType::U8 => HostIntegerDataView::U8(&[]),
938 IntegerElementType::U16 => HostIntegerDataView::U16(&[]),
939 IntegerElementType::U32 => HostIntegerDataView::U32(&[]),
940 IntegerElementType::U64 => HostIntegerDataView::U64(&[]),
941 };
942 provider
943 .upload_integer(&HostIntegerTensorView {
944 data,
945 shape: output_shape,
946 })
947 .map(Value::GpuTensor)
948 .map_err(map_slice_acceleration_error)
949}
950
951pub async fn read_string_slice(
952 sa: &StringArray,
953 dims: usize,
954 colon_mask: u32,
955 end_mask: u32,
956 numeric: &[Value],
957) -> Result<Value, RuntimeError> {
958 let selectors = build_slice_selectors(dims, colon_mask, end_mask, numeric, &sa.shape).await?;
959 let plan = build_index_plan(&selectors, dims, &sa.shape)?;
960 gather_string_slice(sa, &plan)
961}
962
963pub fn gather_string_slice(sa: &StringArray, plan: &IndexPlan) -> Result<Value, RuntimeError> {
964 if plan.indices.is_empty() {
965 let empty = StringArray::new(Vec::new(), plan.output_shape.clone())
966 .map_err(map_slice_shape_error)?;
967 return Ok(Value::StringArray(empty));
968 }
969 if plan.indices.len() == 1 {
970 let lin = plan.indices[0] as usize;
971 let value = sa.data.get(lin).cloned().ok_or_else(|| {
972 crate::runtime_error::semantic_error(
973 "IndexOutOfBounds",
974 "Slice error: string index out of bounds",
975 )
976 })?;
977 return Ok(Value::String(value));
978 }
979 let mut out = Vec::with_capacity(plan.indices.len());
980 for &lin in &plan.indices {
981 let idx = lin as usize;
982 let value = sa.data.get(idx).cloned().ok_or_else(|| {
983 crate::runtime_error::semantic_error(
984 "IndexOutOfBounds",
985 "Slice error: string index out of bounds",
986 )
987 })?;
988 out.push(value);
989 }
990 let out_sa = StringArray::new(out, plan.output_shape.clone()).map_err(map_slice_shape_error)?;
991 Ok(Value::StringArray(out_sa))
992}
993
994#[cfg(test)]
995mod tests {
996 use super::{
997 gather_string_slice, map_slice_acceleration_error, read_complex_slice_from_plan,
998 read_gpu_slice_from_plan, read_sparse_slice_from_plan, read_string_slice,
999 read_tensor_slice_from_plan, try_tensor_slice_2d_fast_path,
1000 };
1001 use crate::indexing::plan::IndexPlan;
1002 use crate::indexing::selectors::SliceSelector;
1003 use futures::executor::block_on;
1004 use runmat_value::{
1005 ComplexTensor, IntValue, IntegerComplexStorage, IntegerStorage, NumericScalar,
1006 SparseTensor, StringArray, Tensor, Value,
1007 };
1008
1009 #[test]
1010 fn tensor_slice_plan_preserves_exact_uint64_storage() {
1011 let tensor = Tensor::new_integer(IntegerStorage::U64(vec![1, u64::MAX, 3, 4]), vec![2, 2])
1012 .expect("tensor");
1013 let plan = IndexPlan::new(vec![0, 2, 1, 3], vec![2, 2], vec![2, 2], 2, vec![2, 2]);
1014 let result = read_tensor_slice_from_plan(&tensor, &plan).expect("slice");
1015
1016 let Value::Tensor(output) = result else {
1017 panic!("expected tensor");
1018 };
1019 assert_eq!(
1020 output.integer_storage(),
1021 Some(&IntegerStorage::U64(vec![1, 3, u64::MAX, 4]))
1022 );
1023 }
1024
1025 #[test]
1026 fn sparse_slice_plan_preserves_exact_uint64_storage_and_empty_class() {
1027 let _compat = crate::compatibility::push_runmat_extensions_enabled(true);
1028 let sparse = SparseTensor::new_integer(
1029 2,
1030 2,
1031 vec![0, 1, 2],
1032 vec![0, 1],
1033 IntegerStorage::U64(vec![1, u64::MAX]),
1034 )
1035 .expect("sparse");
1036 let plan = IndexPlan::new(vec![0, 3, 1, 2], vec![2, 2], vec![2, 2], 2, vec![2, 2]);
1037 let result = read_sparse_slice_from_plan(&sparse, &plan).expect("slice");
1038 let Value::SparseTensor(output) = result else {
1039 panic!("expected sparse output");
1040 };
1041 assert_eq!(output.shape(), vec![2, 2]);
1042 assert_eq!(output.col_ptrs, vec![0, 2, 2]);
1043 assert_eq!(output.row_indices, vec![0, 1]);
1044 assert_eq!(
1045 output.integer_storage(),
1046 Some(&IntegerStorage::U64(vec![1, u64::MAX]))
1047 );
1048
1049 let empty_plan = IndexPlan::new(Vec::new(), vec![0, 1], vec![0], 1, vec![2, 2]);
1050 let empty = read_sparse_slice_from_plan(&sparse, &empty_plan).expect("empty slice");
1051 let Value::SparseTensor(empty) = empty else {
1052 panic!("expected sparse output");
1053 };
1054 assert_eq!(empty.shape(), vec![0, 1]);
1055 assert_eq!(empty.integer_storage(), Some(&IntegerStorage::U64(vec![])));
1056 }
1057
1058 #[test]
1059 fn sparse_slice_plan_preserves_native_single_storage_and_empty_class() {
1060 let sparse = SparseTensor::new_f32(2, 2, vec![0, 1, 2], vec![0, 1], vec![1.25, 3.5])
1061 .expect("single sparse");
1062 let plan = IndexPlan::new(vec![0, 3, 1, 2], vec![2, 2], vec![2, 2], 2, vec![2, 2]);
1063 let result = read_sparse_slice_from_plan(&sparse, &plan).expect("slice");
1064 let Value::SparseTensor(output) = result else {
1065 panic!("expected sparse output");
1066 };
1067 assert_eq!(
1068 output.numeric_dtype(),
1069 Some(runmat_value::NumericDType::F32)
1070 );
1071 assert_eq!(output.col_ptrs, vec![0, 2, 2]);
1072 assert_eq!(output.row_indices, vec![0, 1]);
1073 assert_eq!(output.as_f32_slice(), Some(&[1.25, 3.5][..]));
1074
1075 let empty_plan = IndexPlan::new(Vec::new(), vec![0, 1], vec![0], 1, vec![2, 2]);
1076 let empty = read_sparse_slice_from_plan(&sparse, &empty_plan).expect("empty slice");
1077 let Value::SparseTensor(empty) = empty else {
1078 panic!("expected sparse output");
1079 };
1080 assert_eq!(empty.numeric_dtype(), Some(runmat_value::NumericDType::F32));
1081 assert_eq!(empty.shape(), vec![0, 1]);
1082 assert_eq!(empty.as_f32_slice(), Some(&[][..]));
1083 }
1084
1085 #[test]
1086 fn sparse_slice_plan_preserves_logical_pattern_and_empty_class() {
1087 let sparse =
1088 SparseTensor::new_logical(2, 2, vec![0, 1, 2], vec![0, 1]).expect("logical sparse");
1089 let plan = IndexPlan::new(vec![0, 3, 1, 2], vec![2, 2], vec![2, 2], 2, vec![2, 2]);
1090 let result = read_sparse_slice_from_plan(&sparse, &plan).expect("slice");
1091 let Value::SparseTensor(output) = result else {
1092 panic!("expected sparse output");
1093 };
1094 assert!(output.is_logical());
1095 assert_eq!(output.col_ptrs, vec![0, 2, 2]);
1096 assert_eq!(output.row_indices, vec![0, 1]);
1097
1098 let empty_plan = IndexPlan::new(Vec::new(), vec![0, 1], vec![0], 1, vec![2, 2]);
1099 let empty = read_sparse_slice_from_plan(&sparse, &empty_plan).expect("empty slice");
1100 let Value::SparseTensor(empty) = empty else {
1101 panic!("expected sparse output");
1102 };
1103 assert!(empty.is_logical());
1104 assert_eq!(empty.shape(), vec![0, 1]);
1105 assert_eq!(empty.nnz(), 0);
1106 }
1107
1108 #[test]
1109 fn tensor_slice_fast_path_returns_exact_integer_scalar() {
1110 let tensor =
1111 Tensor::new_integer(IntegerStorage::I64(vec![i64::MAX]), vec![1, 1]).expect("tensor");
1112 let result = try_tensor_slice_2d_fast_path(
1113 &tensor,
1114 2,
1115 &[SliceSelector::Colon, SliceSelector::Scalar(1)],
1116 )
1117 .expect("fast path")
1118 .expect("fast path result");
1119 assert_eq!(result, Value::Int(IntValue::I64(i64::MAX)));
1120 }
1121
1122 #[test]
1123 fn gpu_integer_slice_preserves_class_for_empty_and_nonempty_plans() {
1124 runmat_accelerate_api::set_thread_provider(None);
1125 runmat_accelerate_api::clear_provider();
1126 runmat_accelerate::simple_provider::register_inprocess_provider();
1127 let provider = runmat_accelerate_api::provider().expect("test provider");
1128 let _thread_provider = runmat_accelerate_api::ThreadProviderGuard::set(Some(provider));
1129
1130 {
1131 let source = provider
1132 .upload_integer(&runmat_accelerate_api::HostIntegerTensorView {
1133 data: runmat_accelerate_api::HostIntegerDataView::U64(&[
1134 0,
1135 1_u64 << 63,
1136 u64::MAX,
1137 ]),
1138 shape: &[1, 3],
1139 })
1140 .expect("upload integer gpu source");
1141
1142 let nonempty = IndexPlan::new(vec![2, 1], vec![1, 2], vec![2], 1, vec![1, 3]);
1143 let Value::GpuTensor(gathered) =
1144 read_gpu_slice_from_plan(&source, &nonempty).expect("gpu integer gather")
1145 else {
1146 panic!("expected gpu tensor");
1147 };
1148 assert_eq!(
1149 runmat_accelerate_api::handle_integer_type(&gathered),
1150 Some(runmat_accelerate_api::IntegerElementType::U64)
1151 );
1152 let host = block_on(provider.download_integer(&gathered)).expect("download gathered");
1153 assert_eq!(host.shape, vec![1, 2]);
1154 assert_eq!(
1155 host.data,
1156 runmat_accelerate_api::HostIntegerDataOwned::U64(vec![u64::MAX, 1_u64 << 63])
1157 );
1158
1159 let empty = IndexPlan::new(Vec::new(), vec![1, 0], vec![0], 1, vec![1, 3]);
1160 let Value::GpuTensor(empty_handle) =
1161 read_gpu_slice_from_plan(&source, &empty).expect("empty gpu integer slice")
1162 else {
1163 panic!("expected gpu tensor");
1164 };
1165 assert_eq!(
1166 runmat_accelerate_api::handle_integer_type(&empty_handle),
1167 Some(runmat_accelerate_api::IntegerElementType::U64)
1168 );
1169 let host = block_on(provider.download_integer(&empty_handle)).expect("download empty");
1170 assert_eq!(host.shape, vec![1, 0]);
1171 assert_eq!(
1172 host.data,
1173 runmat_accelerate_api::HostIntegerDataOwned::U64(Vec::new())
1174 );
1175 }
1176 }
1177
1178 #[test]
1179 fn string_slice_linear_tensor_indices_preserve_selector_shape() {
1180 let sa = StringArray::new(
1181 vec![
1182 "a".to_string(),
1183 "b".to_string(),
1184 "c".to_string(),
1185 "d".to_string(),
1186 ],
1187 vec![2, 2],
1188 )
1189 .expect("string array");
1190 let selector =
1191 Value::Tensor(Tensor::new(vec![1.0, 3.0], vec![1, 2]).expect("selector tensor"));
1192 let result = block_on(read_string_slice(&sa, 1, 0, 0, &[selector])).expect("slice");
1193 match result {
1194 Value::StringArray(out) => {
1195 assert_eq!(out.shape, vec![1, 2]);
1196 assert_eq!(out.data, vec!["a".to_string(), "c".to_string()]);
1197 }
1198 other => panic!("expected string array result, got {other:?}"),
1199 }
1200 }
1201
1202 #[test]
1203 fn string_slice_colon_then_scalar_selects_column() {
1204 let sa = StringArray::new(
1205 vec![
1206 "a".to_string(),
1207 "b".to_string(),
1208 "c".to_string(),
1209 "d".to_string(),
1210 ],
1211 vec![2, 2],
1212 )
1213 .expect("string array");
1214 let result =
1215 block_on(read_string_slice(&sa, 2, 0b01, 0, &[Value::Num(2.0)])).expect("slice");
1216 match result {
1217 Value::StringArray(out) => {
1218 assert_eq!(out.shape, vec![2, 1]);
1219 assert_eq!(out.data, vec!["c".to_string(), "d".to_string()]);
1220 }
1221 other => panic!("expected string array result, got {other:?}"),
1222 }
1223 }
1224
1225 #[test]
1226 fn tensor_slice_plan_preserves_native_single_storage() {
1227 let tensor = Tensor::from_f32(vec![1.25, 2.5, 3.75], vec![1, 3]).expect("single tensor");
1228 let plan = IndexPlan::new(vec![2, 0], vec![1, 2], vec![2], 1, vec![1, 3]);
1229 let Value::Tensor(out) = read_tensor_slice_from_plan(&tensor, &plan).expect("single slice")
1230 else {
1231 panic!("expected native-single tensor result");
1232 };
1233 assert_eq!(out.numeric_dtype(), runmat_value::NumericDType::F32);
1234 assert_eq!(out.shape, vec![1, 2]);
1235 assert_eq!(out.numeric_value_at(0), Some(NumericScalar::F32(3.75)));
1236 assert_eq!(out.numeric_value_at(1), Some(NumericScalar::F32(1.25)));
1237 }
1238
1239 #[test]
1240 fn tensor_slice_scalar_preserves_native_single_tensor_class() {
1241 let tensor = Tensor::from_f32(vec![1.25, 2.5], vec![1, 2]).expect("single tensor");
1242 let plan = IndexPlan::new(vec![1], vec![1, 1], vec![1], 1, vec![1, 2]);
1243 let Value::Tensor(out) =
1244 read_tensor_slice_from_plan(&tensor, &plan).expect("single scalar slice")
1245 else {
1246 panic!("expected native-single scalar tensor");
1247 };
1248 assert_eq!(out.numeric_dtype(), runmat_value::NumericDType::F32);
1249 assert_eq!(out.numeric_value_at(0), Some(NumericScalar::F32(2.5)));
1250 }
1251
1252 #[test]
1253 fn tensor_slice_plan_shape_mismatch_reports_identifier() {
1254 let tensor = Tensor::new(vec![10.0, 20.0], vec![1, 2]).expect("tensor");
1255 let plan = IndexPlan::new(vec![0, 1], vec![1, 1], vec![2], 1, vec![1, 2]);
1256 let err = read_tensor_slice_from_plan(&tensor, &plan)
1257 .expect_err("shape-mismatch plan should fail");
1258 assert_eq!(err.identifier(), Some("RunMat:ShapeMismatch"));
1259 }
1260
1261 #[test]
1262 fn string_slice_plan_shape_mismatch_reports_identifier() {
1263 let sa = StringArray::new(
1264 vec![
1265 "a".to_string(),
1266 "b".to_string(),
1267 "c".to_string(),
1268 "d".to_string(),
1269 ],
1270 vec![2, 2],
1271 )
1272 .expect("string array");
1273 let plan = IndexPlan::new(vec![0, 1], vec![1, 1], vec![2], 1, vec![2, 2]);
1274 let err = gather_string_slice(&sa, &plan).expect_err("shape-mismatch plan should fail");
1275 assert_eq!(err.identifier(), Some("RunMat:ShapeMismatch"));
1276 }
1277
1278 #[test]
1279 fn complex_slice_plan_shape_mismatch_reports_identifier() {
1280 let ct = ComplexTensor::new(vec![(1.0, 0.0), (2.0, 0.0)], vec![1, 2]).expect("complex");
1281 let plan = IndexPlan::new(vec![0, 1], vec![1, 1], vec![2], 1, vec![1, 2]);
1282 let err =
1283 read_complex_slice_from_plan(&ct, &plan).expect_err("shape-mismatch plan should fail");
1284 assert_eq!(err.identifier(), Some("RunMat:ShapeMismatch"));
1285 }
1286
1287 #[test]
1288 fn integer_complex_slice_preserves_exact_reordered_and_empty_components() {
1289 let complex = ComplexTensor::new_integer(
1290 IntegerComplexStorage::new(
1291 IntegerStorage::U64(vec![1, 9_223_372_036_854_775_809, u64::MAX]),
1292 IntegerStorage::U64(vec![7, 8, 9]),
1293 )
1294 .unwrap(),
1295 vec![1, 3],
1296 )
1297 .unwrap();
1298 let reordered = IndexPlan::new(vec![2, 0], vec![1, 2], vec![2], 1, vec![1, 3]);
1299 let Value::ComplexTensor(result) =
1300 read_complex_slice_from_plan(&complex, &reordered).expect("typed complex slice")
1301 else {
1302 panic!("typed complex selection must remain a complex tensor");
1303 };
1304 assert_eq!(result.shape, vec![1, 2]);
1305 assert_eq!(
1306 result.integer_storage().cloned(),
1307 Some(
1308 IntegerComplexStorage::new(
1309 IntegerStorage::U64(vec![u64::MAX, 1]),
1310 IntegerStorage::U64(vec![9, 7]),
1311 )
1312 .unwrap()
1313 )
1314 );
1315
1316 let scalar = IndexPlan::new(vec![1], vec![1, 1], vec![1], 1, vec![1, 3]);
1317 let Value::ComplexTensor(result) =
1318 read_complex_slice_from_plan(&complex, &scalar).expect("typed scalar selection")
1319 else {
1320 panic!("typed complex scalar must retain exact complex storage");
1321 };
1322 assert_eq!(
1323 result.integer_storage().cloned(),
1324 Some(
1325 IntegerComplexStorage::new(
1326 IntegerStorage::U64(vec![9_223_372_036_854_775_809]),
1327 IntegerStorage::U64(vec![8]),
1328 )
1329 .unwrap()
1330 )
1331 );
1332
1333 let empty = IndexPlan::new(Vec::new(), vec![0, 1], vec![0], 1, vec![1, 3]);
1334 let Value::ComplexTensor(result) =
1335 read_complex_slice_from_plan(&complex, &empty).expect("empty typed selection")
1336 else {
1337 panic!("empty typed complex selection must retain its class");
1338 };
1339 assert_eq!(
1340 result.integer_storage().cloned(),
1341 Some(
1342 IntegerComplexStorage::new(
1343 IntegerStorage::U64(Vec::new()),
1344 IntegerStorage::U64(Vec::new())
1345 )
1346 .unwrap()
1347 )
1348 );
1349 }
1350
1351 #[test]
1352 fn slice_acceleration_error_mapping_reports_identifier() {
1353 let err = map_slice_acceleration_error("provider failed");
1354 assert_eq!(err.identifier(), Some("RunMat:AccelerationOperationFailed"));
1355 }
1356}