Skip to main content

runmat_runtime/indexing/
read_slice.rs

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}