Skip to main content

runmat_runtime/indexing/
write_slice.rs

1use crate::builtins::common::tensor::{
2    self, complex_tensor_element_len, complex_tensor_values_complex64, is_scalar_tensor,
3    tensor_element_len, tensor_value_f64,
4};
5use crate::indexing::integer_assignment::{
6    self, ComplexIntegerAssignmentValue, IntegerAssignmentValue,
7};
8use crate::indexing::plan::IndexPlan;
9use crate::runtime_error::semantic_error as mex;
10use crate::RuntimeError;
11use runmat_accelerate_api::{HostIntegerDataOwned, HostIntegerDataView, HostIntegerTensorView};
12use runmat_value::{
13    ComplexTensor, IntegerComplexStorage, IntegerStorage, NumericDType, NumericScalar,
14    NumericStorage, SparseTensor, StringArray, Tensor, Value,
15};
16
17fn map_slice_shape_error(context: &str, err: impl std::fmt::Display) -> RuntimeError {
18    mex("ShapeMismatch", format!("{context}: {err}"))
19}
20
21fn map_acceleration_error(context: &str, err: impl std::fmt::Display) -> RuntimeError {
22    mex("AccelerationOperationFailed", format!("{context}: {err}"))
23}
24
25fn is_empty_delete_rhs(value: &Value) -> bool {
26    matches!(
27        value,
28        Value::Tensor(t)
29            if tensor_element_len(t) == 0 || t.rows == 0 || t.cols == 0
30    ) || matches!(
31        value,
32        Value::ComplexTensor(t)
33            if complex_tensor_element_len(t) == 0 || t.rows == 0 || t.cols == 0
34    ) || matches!(value, Value::OutputList(values) if values.is_empty())
35}
36
37pub(crate) fn real_tensor_to_complex(
38    tensor: Tensor,
39    context: &str,
40) -> Result<ComplexTensor, RuntimeError> {
41    let shape = tensor.shape.clone();
42    let storage = tensor
43        .into_numeric_storage()
44        .map_err(|error| map_slice_shape_error(context, error))?;
45    match storage.into_integer_storage() {
46        Ok(real) => {
47            let imag = real.zeros_like(real.len());
48            let storage = IntegerComplexStorage::new(real, imag)
49                .expect("same-class real and imaginary integer storage must be valid");
50            ComplexTensor::new_integer(storage, shape)
51                .map_err(|error| map_slice_shape_error(context, error))
52        }
53        Err(storage) => ComplexTensor::new(
54            storage
55                .materialize_f64()
56                .into_iter()
57                .map(|real| (real, 0.0))
58                .collect(),
59            shape,
60        )
61        .map_err(|error| map_slice_shape_error(context, error)),
62    }
63}
64
65pub(crate) fn deleted_vector_shape(rows: usize, _cols: usize, len: usize) -> Vec<usize> {
66    if len == 0 {
67        vec![0, 0]
68    } else if rows == 1 {
69        vec![1, len]
70    } else {
71        vec![len, 1]
72    }
73}
74
75fn sorted_unique_positions_desc(
76    plan: &IndexPlan,
77    total: usize,
78) -> Result<Vec<usize>, RuntimeError> {
79    let mut positions = Vec::with_capacity(plan.indices.len());
80    for &idx in &plan.indices {
81        let pos = idx as usize;
82        if pos >= total {
83            return Err(mex("IndexOutOfBounds", "Index out of bounds"));
84        }
85        positions.push(pos);
86    }
87    positions.sort_unstable();
88    positions.dedup();
89    positions.reverse();
90    Ok(positions)
91}
92
93fn delete_integer_storage_positions(
94    storage: IntegerStorage,
95    positions: &[usize],
96) -> IntegerStorage {
97    macro_rules! delete_positions {
98        ($values:expr, $variant:ident) => {{
99            let mut values = $values;
100            for &position in positions {
101                values.remove(position);
102            }
103            IntegerStorage::$variant(values)
104        }};
105    }
106
107    match storage {
108        IntegerStorage::I8(values) => delete_positions!(values, I8),
109        IntegerStorage::I16(values) => delete_positions!(values, I16),
110        IntegerStorage::I32(values) => delete_positions!(values, I32),
111        IntegerStorage::I64(values) => delete_positions!(values, I64),
112        IntegerStorage::U8(values) => delete_positions!(values, U8),
113        IntegerStorage::U16(values) => delete_positions!(values, U16),
114        IntegerStorage::U32(values) => delete_positions!(values, U32),
115        IntegerStorage::U64(values) => delete_positions!(values, U64),
116    }
117}
118
119pub(crate) fn delete_integer_complex_storage_positions(
120    storage: IntegerComplexStorage,
121    positions: &[usize],
122) -> IntegerComplexStorage {
123    IntegerComplexStorage::new(
124        delete_integer_storage_positions(storage.real, positions),
125        delete_integer_storage_positions(storage.imag, positions),
126    )
127    .expect("paired integer complex storage must retain matching classes and lengths")
128}
129
130fn scalar_integer_value(value: &Value) -> Result<IntegerAssignmentValue, RuntimeError> {
131    match value {
132        Value::Int(value) => Ok(IntegerAssignmentValue::Exact(value.clone())),
133        Value::Num(value) => Ok(IntegerAssignmentValue::Float(*value)),
134        Value::Bool(value) => Ok(IntegerAssignmentValue::Float(if *value {
135            1.0
136        } else {
137            0.0
138        })),
139        Value::Tensor(tensor) if is_scalar_tensor(tensor) => {
140            let value = tensor
141                .numeric_value_at(0)
142                .expect("scalar tensor must contain one numeric value");
143            Ok(match value.into_int_value() {
144                Some(value) => IntegerAssignmentValue::Exact(value),
145                None => IntegerAssignmentValue::Float(value.materialize_f64()),
146            })
147        }
148        Value::LogicalArray(array) if array.data.len() == 1 => {
149            Ok(IntegerAssignmentValue::Float(if array.data[0] == 0 {
150                0.0
151            } else {
152                1.0
153            }))
154        }
155        _ => Err(mex(
156            "InvalidSliceAssignmentRhs",
157            "rhs must be numeric or logical",
158        )),
159    }
160}
161
162fn validated_assignment_rhs_shape(
163    rhs_shape: &[usize],
164    rhs_len: usize,
165    selection_lengths: &[usize],
166) -> Result<Vec<usize>, RuntimeError> {
167    let dims = selection_lengths.len();
168    if rhs_len == 1 {
169        return Ok(vec![1; dims]);
170    }
171    if dims == 1 {
172        if rhs_len == selection_lengths[0] {
173            return Ok(vec![rhs_len]);
174        }
175        return Err(mex("ShapeMismatch", "shape mismatch for slice assign"));
176    }
177    if rhs_shape.len() > dims && rhs_shape.iter().skip(dims).any(|&dimension| dimension != 1) {
178        return Err(mex("ShapeMismatch", "shape mismatch for slice assign"));
179    }
180    let mut shape = rhs_shape.iter().copied().take(dims).collect::<Vec<_>>();
181    shape.resize(dims, 1);
182    if shape != selection_lengths {
183        return Err(mex("ShapeMismatch", "shape mismatch for slice assign"));
184    }
185    let expected = shape
186        .iter()
187        .try_fold(1usize, |length, dimension| length.checked_mul(*dimension))
188        .ok_or_else(|| mex("ShapeMismatch", "shape mismatch for slice assign"))?;
189    if rhs_len != expected {
190        return Err(mex("ShapeMismatch", "shape mismatch for slice assign"));
191    }
192    Ok(shape)
193}
194
195pub enum ComplexRhsView {
196    Scalar((f64, f64)),
197    Tensor {
198        data: Vec<(f64, f64)>,
199        shape: Vec<usize>,
200        strides: Vec<usize>,
201    },
202}
203
204pub fn build_complex_rhs_view(
205    rhs: &Value,
206    selection_lengths: &[usize],
207) -> Result<ComplexRhsView, RuntimeError> {
208    match rhs {
209        Value::Complex(re, im) => Ok(ComplexRhsView::Scalar((*re, *im))),
210        Value::Num(n) => Ok(ComplexRhsView::Scalar((*n, 0.0))),
211        Value::ComplexTensor(rt) => {
212            let dims = selection_lengths.len();
213            let shape = validated_assignment_rhs_shape(&rt.shape, rt.len(), selection_lengths)?;
214            let mut rstrides = vec![0usize; dims];
215            let mut racc = 1usize;
216            for d in 0..dims {
217                rstrides[d] = racc;
218                racc *= shape[d];
219            }
220            Ok(ComplexRhsView::Tensor {
221                data: complex_tensor_values_complex64(rt)
222                    .into_iter()
223                    .map(|value| (value.re, value.im))
224                    .collect(),
225                shape,
226                strides: rstrides,
227            })
228        }
229        _ => Err(mex(
230            "InvalidSliceAssignmentRhs",
231            "rhs must be numeric or tensor",
232        )),
233    }
234}
235
236pub fn scatter_complex_with_plan(
237    t: &mut ComplexTensor,
238    plan: &IndexPlan,
239    rhs_view: &ComplexRhsView,
240) -> Result<(), RuntimeError> {
241    let dims = plan.dims;
242    let mut idx = vec![0usize; dims];
243    if plan.indices.is_empty() {
244        return Ok(());
245    }
246    let selection_lengths = if plan.selection_lengths.is_empty() {
247        plan.output_shape.clone()
248    } else {
249        plan.selection_lengths.clone()
250    };
251    loop {
252        let mut rlin = 0usize;
253        match rhs_view {
254            ComplexRhsView::Scalar(val) => {
255                let lin_pos = {
256                    let mut p = 0usize;
257                    let mut mul = 1usize;
258                    for d in 0..dims {
259                        p += idx[d] * mul;
260                        mul *= selection_lengths[d].max(1);
261                    }
262                    p
263                };
264                let dst = plan.indices[lin_pos] as usize;
265                t.set_f64_assignment_at(dst, val.0, val.1)
266                    .map_err(|error| map_slice_shape_error("complex slice assign", error))?;
267            }
268            ComplexRhsView::Tensor {
269                data,
270                shape,
271                strides,
272            } => {
273                for d in 0..dims {
274                    let rhs_len = shape[d];
275                    let pos = if rhs_len == 1 { 0 } else { idx[d] };
276                    rlin += pos * strides[d];
277                }
278                let lin_pos = {
279                    let mut p = 0usize;
280                    let mut mul = 1usize;
281                    for d in 0..dims {
282                        p += idx[d] * mul;
283                        mul *= selection_lengths[d].max(1);
284                    }
285                    p
286                };
287                let dst = plan.indices[lin_pos] as usize;
288                t.set_f64_assignment_at(dst, data[rlin].0, data[rlin].1)
289                    .map_err(|error| map_slice_shape_error("complex slice assign", error))?;
290            }
291        }
292        let mut d = 0usize;
293        while d < dims {
294            idx[d] += 1;
295            if idx[d] < selection_lengths[d].max(1) {
296                break;
297            }
298            idx[d] = 0;
299            d += 1;
300        }
301        if d == dims {
302            break;
303        }
304    }
305    Ok(())
306}
307
308pub enum StringRhsView {
309    Scalar(String),
310    Tensor {
311        data: Vec<String>,
312        shape: Vec<usize>,
313        strides: Vec<usize>,
314    },
315}
316
317pub fn build_string_rhs_view(
318    rhs: &Value,
319    selection_lengths: &[usize],
320) -> Result<StringRhsView, RuntimeError> {
321    let scalar = match rhs {
322        Value::String(s) => Some(s.clone()),
323        Value::CharArray(chars) => Some(chars.row_string().ok_or_else(|| {
324            mex(
325                "InvalidSliceAssignmentRhs",
326                "rhs character array must be a row vector",
327            )
328        })?),
329        _ => None,
330    };
331    if let Some(s) = scalar {
332        return Ok(StringRhsView::Scalar(s));
333    }
334    if let Value::StringArray(rt) = rhs {
335        let dims = selection_lengths.len();
336        let shape = validated_assignment_rhs_shape(&rt.shape, rt.data.len(), selection_lengths)?;
337        let mut rstrides = vec![0usize; dims];
338        let mut racc = 1usize;
339        for d in 0..dims {
340            rstrides[d] = racc;
341            racc *= shape[d];
342        }
343        return Ok(StringRhsView::Tensor {
344            data: rt.data.clone(),
345            shape,
346            strides: rstrides,
347        });
348    }
349    if let Value::Cell(cell) = rhs {
350        let dims = selection_lengths.len();
351        let mut data = Vec::with_capacity(cell.data.len());
352        for handle in &cell.data {
353            let value = handle;
354            match value {
355                Value::String(text) => data.push(text.clone()),
356                Value::CharArray(chars) => data.push(chars.row_string().ok_or_else(|| {
357                    mex(
358                        "InvalidSliceAssignmentRhs",
359                        "rhs cell character arrays must be row vectors",
360                    )
361                })?),
362                Value::StringArray(strings) if strings.data.len() == 1 => {
363                    data.push(strings.data[0].clone())
364                }
365                other => {
366                    return Err(mex(
367                        "InvalidSliceAssignmentRhs",
368                        format!(
369                            "rhs cell elements must be string scalars or character vectors, got {other:?}"
370                        ),
371                    ))
372                }
373            }
374        }
375        let shape =
376            validated_assignment_rhs_shape(&cell.shape, cell.data.len(), selection_lengths)?;
377        let mut rstrides = vec![0usize; dims];
378        let mut racc = 1usize;
379        for d in 0..dims {
380            rstrides[d] = racc;
381            racc *= shape[d];
382        }
383        return Ok(StringRhsView::Tensor {
384            data,
385            shape,
386            strides: rstrides,
387        });
388    }
389    Err(mex(
390        "InvalidSliceAssignmentRhs",
391        "rhs must be string, string array, or cellstr",
392    ))
393}
394
395pub fn scatter_string_with_plan(
396    sa: &mut StringArray,
397    plan: &IndexPlan,
398    rhs_view: &StringRhsView,
399) -> Result<(), RuntimeError> {
400    let dims = plan.dims;
401    let mut idx = vec![0usize; dims];
402    if plan.indices.is_empty() {
403        return Ok(());
404    }
405    let selection_lengths = if plan.selection_lengths.is_empty() {
406        plan.output_shape.clone()
407    } else {
408        plan.selection_lengths.clone()
409    };
410    loop {
411        match rhs_view {
412            StringRhsView::Scalar(val) => {
413                let lin_pos = {
414                    let mut p = 0usize;
415                    let mut mul = 1usize;
416                    for d in 0..dims {
417                        p += idx[d] * mul;
418                        mul *= selection_lengths[d].max(1);
419                    }
420                    p
421                };
422                let dst = plan.indices[lin_pos] as usize;
423                sa.data[dst] = val.clone();
424            }
425            StringRhsView::Tensor {
426                data,
427                shape,
428                strides,
429            } => {
430                let mut rlin = 0usize;
431                for d in 0..dims {
432                    let rhs_len = shape[d];
433                    let pos = if rhs_len == 1 { 0 } else { idx[d] };
434                    rlin += pos * strides[d];
435                }
436                let lin_pos = {
437                    let mut p = 0usize;
438                    let mut mul = 1usize;
439                    for d in 0..dims {
440                        p += idx[d] * mul;
441                        mul *= selection_lengths[d].max(1);
442                    }
443                    p
444                };
445                let dst = plan.indices[lin_pos] as usize;
446                sa.data[dst] = data[rlin].clone();
447            }
448        }
449        let mut d = 0usize;
450        while d < dims {
451            idx[d] += 1;
452            if idx[d] < selection_lengths[d].max(1) {
453                break;
454            }
455            idx[d] = 0;
456            d += 1;
457        }
458        if d == dims {
459            break;
460        }
461    }
462    Ok(())
463}
464
465pub async fn materialize_rhs_real_for_plan(
466    rhs: &Value,
467    plan: &IndexPlan,
468) -> Result<Vec<f64>, RuntimeError> {
469    if plan.dims == 1 {
470        let count = plan.selection_lengths.first().copied().unwrap_or(0);
471        materialize_rhs_linear_real(rhs, count).await
472    } else {
473        materialize_rhs_nd_real(rhs, &plan.selection_lengths).await
474    }
475}
476
477pub(crate) async fn materialize_integer_rhs_for_plan(
478    rhs: &Value,
479    plan: &IndexPlan,
480) -> Result<Vec<IntegerAssignmentValue>, RuntimeError> {
481    match rhs {
482        Value::Int(value) => Ok(vec![
483            IntegerAssignmentValue::Exact(value.clone());
484            plan.indices.len()
485        ]),
486        Value::Tensor(tensor) if tensor.integer_storage().is_some() => {
487            materialize_integer_tensor_rhs_for_plan(tensor, plan)
488        }
489        Value::GpuTensor(handle)
490            if runmat_accelerate_api::handle_integer_type(handle).is_some() =>
491        {
492            let tensor = download_integer_tensor(handle).await?;
493            materialize_integer_tensor_rhs_for_plan(&tensor, plan)
494        }
495        Value::OutputList(values) => {
496            if values.len() == plan.indices.len() {
497                return values.iter().map(scalar_integer_value).collect();
498            }
499            if values.len() == 1 {
500                return Ok(vec![scalar_integer_value(&values[0])?; plan.indices.len()]);
501            }
502            Err(mex("ShapeMismatch", "shape mismatch for slice assign"))
503        }
504        _ => materialize_rhs_real_for_plan(rhs, plan)
505            .await
506            .map(|values| {
507                values
508                    .into_iter()
509                    .map(IntegerAssignmentValue::Float)
510                    .collect()
511            }),
512    }
513}
514
515fn materialize_integer_tensor_rhs_for_plan(
516    tensor: &Tensor,
517    plan: &IndexPlan,
518) -> Result<Vec<IntegerAssignmentValue>, RuntimeError> {
519    let values = integer_assignment::values(
520        tensor
521            .integer_storage()
522            .expect("integer RHS must retain exact storage"),
523    );
524    integer_gpu_rhs_indices_for_plan(&tensor.shape, plan)?
525        .into_iter()
526        .map(|index| {
527            values
528                .get(index as usize)
529                .cloned()
530                .map(IntegerAssignmentValue::Exact)
531                .ok_or_else(|| mex("ShapeMismatch", "shape mismatch for slice assign"))
532        })
533        .collect()
534}
535
536async fn materialize_complex_integer_rhs_for_plan(
537    rhs: &Value,
538    plan: &IndexPlan,
539) -> Result<Vec<ComplexIntegerAssignmentValue>, RuntimeError> {
540    match rhs {
541        Value::Complex(real, imag) => Ok(vec![
542            ComplexIntegerAssignmentValue {
543                real: IntegerAssignmentValue::Float(*real),
544                imag: IntegerAssignmentValue::Float(*imag),
545            };
546            plan.indices.len()
547        ]),
548        Value::ComplexTensor(tensor) => {
549            let values = if let Some(storage) = &tensor.integer_storage() {
550                (0..storage.len())
551                    .map(|index| ComplexIntegerAssignmentValue {
552                        real: IntegerAssignmentValue::Exact(
553                            storage
554                                .real
555                                .value_at(index)
556                                .expect("typed complex storage length was validated"),
557                        ),
558                        imag: IntegerAssignmentValue::Exact(
559                            storage
560                                .imag
561                                .value_at(index)
562                                .expect("typed complex storage length was validated"),
563                        ),
564                    })
565                    .collect()
566            } else {
567                tensor
568                    .materialize_f64()
569                    .iter()
570                    .map(|&(real, imag)| ComplexIntegerAssignmentValue {
571                        real: IntegerAssignmentValue::Float(real),
572                        imag: IntegerAssignmentValue::Float(imag),
573                    })
574                    .collect()
575            };
576            materialize_complex_integer_values_for_plan(values, &tensor.shape, plan)
577        }
578        _ => materialize_integer_rhs_for_plan(rhs, plan)
579            .await
580            .map(|values| {
581                values
582                    .into_iter()
583                    .map(|real| ComplexIntegerAssignmentValue {
584                        real,
585                        imag: IntegerAssignmentValue::Float(0.0),
586                    })
587                    .collect()
588            }),
589    }
590}
591
592fn materialize_complex_integer_values_for_plan(
593    values: Vec<ComplexIntegerAssignmentValue>,
594    rhs_shape: &[usize],
595    plan: &IndexPlan,
596) -> Result<Vec<ComplexIntegerAssignmentValue>, RuntimeError> {
597    if plan.dims == 1 {
598        if values.len() == plan.indices.len() {
599            return Ok(values);
600        }
601        if values.len() == 1 {
602            return Ok(vec![values[0].clone(); plan.indices.len()]);
603        }
604        return Err(mex("ShapeMismatch", "shape mismatch for slice assign"));
605    }
606
607    let dims = plan.selection_lengths.len();
608    let shape = validated_assignment_rhs_shape(rhs_shape, values.len(), &plan.selection_lengths)?;
609    let mut strides = vec![1usize; dims];
610    for dimension in 1..dims {
611        strides[dimension] = strides[dimension - 1] * shape[dimension - 1].max(1);
612    }
613    let mut output = Vec::with_capacity(plan.indices.len());
614    let mut coordinates = vec![0usize; dims];
615    for _ in 0..plan.indices.len() {
616        let mut rhs_index = 0usize;
617        for dimension in 0..dims {
618            let coordinate = if shape[dimension] == 1 {
619                0
620            } else {
621                coordinates[dimension]
622            };
623            rhs_index += coordinate * strides[dimension];
624        }
625        output.push(values[rhs_index].clone());
626        for (dimension, coordinate) in coordinates.iter_mut().enumerate() {
627            *coordinate += 1;
628            if *coordinate < plan.selection_lengths[dimension].max(1) {
629                break;
630            }
631            *coordinate = 0;
632        }
633    }
634    Ok(output)
635}
636
637pub fn scatter_real_with_plan(
638    storage: &mut NumericStorage,
639    plan: &IndexPlan,
640    rhs_values: &[f64],
641) -> Result<(), RuntimeError> {
642    if rhs_values.len() != plan.indices.len() {
643        return Err(mex("ShapeMismatch", "shape mismatch for slice assign"));
644    }
645    for (&dst, &value) in plan.indices.iter().zip(rhs_values.iter()) {
646        let value = match storage.numeric_dtype() {
647            NumericDType::F64 => NumericScalar::F64(value),
648            NumericDType::F32 => NumericScalar::F32(value as f32),
649            _ => unreachable!("real floating scatter requires floating storage"),
650        };
651        storage
652            .set_value(dst as usize, value)
653            .map_err(|error| map_slice_shape_error("slice assign", error))?;
654    }
655    Ok(())
656}
657
658/// Assigns an index-plan selection into a host-resident CSC sparse matrix.
659/// Sparse storage owns the batched merge; this module owns MATLAB selector and
660/// RHS assignment-shape semantics shared with dense tensor assignment.
661pub async fn assign_sparse_with_plan(
662    sparse: SparseTensor,
663    plan: &IndexPlan,
664    rhs: &Value,
665) -> Result<Value, RuntimeError> {
666    if sparse.integer_storage().is_some() {
667        crate::compatibility::ensure_sparse_integer_extension_enabled("indexed assignment")?;
668    }
669    if is_empty_delete_rhs(rhs) {
670        return Err(mex(
671            "SparseAssignmentUnsupported",
672            "Sparse indexed deletion is not yet supported",
673        ));
674    }
675    let target_rows = plan.base_shape.first().copied().unwrap_or(sparse.rows);
676    let target_cols = plan.base_shape.get(1).copied().unwrap_or(sparse.cols);
677    let sparse = if target_rows != sparse.rows || target_cols != sparse.cols {
678        sparse
679            .with_expanded_shape(target_rows, target_cols)
680            .map_err(|error| map_slice_shape_error("sparse slice expansion", error))?
681    } else {
682        sparse
683    };
684    if plan.indices.is_empty() {
685        return Ok(Value::SparseTensor(sparse));
686    }
687    let updated = if sparse.is_logical() {
688        let rhs_values = materialize_rhs_real_for_plan(rhs, plan).await?;
689        let updates = plan
690            .indices
691            .iter()
692            .zip(rhs_values)
693            .map(|(&index, value)| (index as usize, value != 0.0))
694            .collect::<Vec<_>>();
695        sparse
696            .with_updated_logical_linear_values(&updates)
697            .map_err(|error| map_slice_shape_error("sparse slice assign", error))?
698    } else if let Some(storage) = sparse.integer_storage() {
699        let rhs_values = materialize_integer_rhs_for_plan(rhs, plan).await?;
700        let updates = plan
701            .indices
702            .iter()
703            .zip(rhs_values.iter())
704            .map(|(&index, value)| {
705                (
706                    index as usize,
707                    integer_assignment::scalar_value(storage, value),
708                )
709            })
710            .collect::<Vec<_>>();
711        sparse
712            .with_updated_integer_linear_values(&updates)
713            .map_err(|error| map_slice_shape_error("sparse slice assign", error))?
714    } else if sparse.numeric_dtype() == Some(NumericDType::F32) {
715        let rhs_values = materialize_rhs_real_for_plan(rhs, plan).await?;
716        let updates = plan
717            .indices
718            .iter()
719            .zip(rhs_values)
720            .map(|(&index, value)| (index as usize, value as f32))
721            .collect::<Vec<_>>();
722        sparse
723            .with_updated_f32_linear_values(&updates)
724            .map_err(|error| map_slice_shape_error("sparse slice assign", error))?
725    } else {
726        let rhs_values = materialize_rhs_real_for_plan(rhs, plan).await?;
727        let updates = plan
728            .indices
729            .iter()
730            .zip(rhs_values)
731            .map(|(&index, value)| (index as usize, value))
732            .collect::<Vec<_>>();
733        sparse
734            .with_updated_linear_values(&updates)
735            .map_err(|error| map_slice_shape_error("sparse slice assign", error))?
736    };
737    Ok(Value::SparseTensor(updated))
738}
739
740enum SparseDeletionAxis {
741    Rows(Vec<usize>),
742    Columns(Vec<usize>),
743    All,
744}
745
746fn sparse_deletion_axis(
747    sparse: &SparseTensor,
748    plan: &IndexPlan,
749) -> Result<SparseDeletionAxis, RuntimeError> {
750    if plan.indices.is_empty() {
751        return Ok(SparseDeletionAxis::Rows(Vec::new()));
752    }
753    if plan.dims == 1 {
754        let indices = plan.indices.iter().map(|&index| index as usize).collect();
755        if sparse.rows == 1 {
756            return Ok(SparseDeletionAxis::Columns(indices));
757        }
758        if sparse.cols == 1 {
759            return Ok(SparseDeletionAxis::Rows(indices));
760        }
761        return Err(mex(
762            "UnsupportedDeletion",
763            "Linear sparse deletion is only supported for vectors",
764        ));
765    }
766    if plan.dims != 2 {
767        return Err(mex(
768            "UnsupportedDeletion",
769            "Sparse deletion currently supports vectors and complete matrix rows or columns",
770        ));
771    }
772    let selected_rows = plan.selection_lengths.first().copied().unwrap_or(0);
773    let selected_cols = plan.selection_lengths.get(1).copied().unwrap_or(0);
774    if selected_rows == sparse.rows && selected_cols == sparse.cols {
775        return Ok(SparseDeletionAxis::All);
776    }
777    if selected_rows == sparse.rows {
778        let columns = plan
779            .indices
780            .chunks(sparse.rows)
781            .map(|chunk| chunk[0] as usize / sparse.rows)
782            .collect();
783        return Ok(SparseDeletionAxis::Columns(columns));
784    }
785    if selected_cols == sparse.cols {
786        let rows = plan
787            .indices
788            .iter()
789            .take(selected_rows)
790            .map(|index| *index as usize % sparse.rows)
791            .collect();
792        return Ok(SparseDeletionAxis::Rows(rows));
793    }
794    Err(mex(
795        "UnsupportedDeletion",
796        "Sparse deletion requires selecting complete rows or columns",
797    ))
798}
799
800pub fn delete_sparse_with_plan(
801    sparse: SparseTensor,
802    plan: &IndexPlan,
803    rhs: &Value,
804) -> Result<Value, RuntimeError> {
805    if sparse.integer_storage().is_some() {
806        crate::compatibility::ensure_sparse_integer_extension_enabled("indexed assignment")?;
807    }
808    if !is_empty_delete_rhs(rhs) {
809        return Err(mex(
810            "DeletionRequiresEmptyRhs",
811            "Indexed deletion requires empty RHS",
812        ));
813    }
814    let updated = match sparse_deletion_axis(&sparse, plan)? {
815        SparseDeletionAxis::Rows(rows) => sparse.with_deleted_rows(&rows),
816        SparseDeletionAxis::Columns(columns) => sparse.with_deleted_columns(&columns),
817        SparseDeletionAxis::All => {
818            let rows = (0..sparse.rows).collect::<Vec<_>>();
819            let columns = (0..sparse.cols).collect::<Vec<_>>();
820            sparse
821                .with_deleted_rows(&rows)
822                .and_then(|sparse| sparse.with_deleted_columns(&columns))
823        }
824    }
825    .map_err(|error| map_slice_shape_error("sparse deletion", error))?;
826    Ok(Value::SparseTensor(updated))
827}
828
829pub async fn assign_tensor_with_plan(
830    t: Tensor,
831    plan: &IndexPlan,
832    rhs: &Value,
833) -> Result<Value, RuntimeError> {
834    if plan.indices.is_empty() {
835        return Ok(Value::Tensor(t));
836    }
837    if matches!(rhs, Value::Complex(_, _) | Value::ComplexTensor(_)) {
838        let tensor = real_tensor_to_complex(t, "slice complex promotion")?;
839        return assign_complex_with_plan(tensor, plan, rhs).await;
840    }
841    let shape = t.shape.clone();
842    let storage = t
843        .into_numeric_storage()
844        .map_err(|error| map_slice_shape_error("slice assign", error))?;
845    let storage = match storage.into_integer_storage() {
846        Ok(mut storage) => {
847            let rhs_values = materialize_integer_rhs_for_plan(rhs, plan).await?;
848            integer_assignment::scatter(&mut storage, plan, &rhs_values)?;
849            NumericStorage::from_integer_storage(storage)
850        }
851        Err(mut storage) => {
852            let rhs_values = materialize_rhs_real_for_plan(rhs, plan).await?;
853            scatter_real_with_plan(&mut storage, plan, &rhs_values)?;
854            storage
855        }
856    };
857    Tensor::from_numeric_storage(storage, shape)
858        .map(Value::Tensor)
859        .map_err(|error| map_slice_shape_error("slice assign", error))
860}
861
862pub async fn assign_complex_with_plan(
863    mut tensor: ComplexTensor,
864    plan: &IndexPlan,
865    rhs: &Value,
866) -> Result<Value, RuntimeError> {
867    if plan.indices.is_empty() {
868        return Ok(Value::ComplexTensor(tensor));
869    }
870    if tensor.integer_storage().is_some() {
871        let rhs_values = materialize_complex_integer_rhs_for_plan(rhs, plan).await?;
872        let storage = tensor
873            .integer_storage()
874            .cloned()
875            .expect("typed complex tensor must retain exact storage");
876        let real_values: Vec<IntegerAssignmentValue> =
877            rhs_values.iter().map(|value| value.real.clone()).collect();
878        let imag_values: Vec<IntegerAssignmentValue> =
879            rhs_values.iter().map(|value| value.imag.clone()).collect();
880        let mut real = storage.real;
881        let mut imag = storage.imag;
882        integer_assignment::scatter(&mut real, plan, &real_values)?;
883        integer_assignment::scatter(&mut imag, plan, &imag_values)?;
884        return IntegerComplexStorage::new(real, imag)
885            .and_then(|storage| ComplexTensor::new_integer(storage, tensor.shape))
886            .map(Value::ComplexTensor)
887            .map_err(|error| map_slice_shape_error("typed complex slice assign", error));
888    }
889    let rhs_view = build_complex_rhs_view(rhs, &plan.selection_lengths)?;
890    scatter_complex_with_plan(&mut tensor, plan, &rhs_view)?;
891    Ok(Value::ComplexTensor(tensor))
892}
893
894pub fn delete_tensor_with_plan(
895    t: Tensor,
896    plan: &IndexPlan,
897    rhs: &Value,
898) -> Result<Value, RuntimeError> {
899    if !is_empty_delete_rhs(rhs) {
900        return Err(mex(
901            "DeletionRequiresEmptyRhs",
902            "Indexed deletion requires empty RHS",
903        ));
904    }
905    if plan.indices.is_empty() {
906        return Ok(Value::Tensor(t));
907    }
908    if !(t.rows == 1 || t.cols == 1) {
909        return Err(mex(
910            "UnsupportedDeletion",
911            "Linear deletion is only supported for vectors",
912        ));
913    }
914    let positions = sorted_unique_positions_desc(plan, tensor_element_len(&t))?;
915    let rows = t.rows;
916    let cols = t.cols;
917    let mut storage = t
918        .into_numeric_storage()
919        .map_err(|error| map_slice_shape_error("slice deletion", error))?;
920    storage
921        .remove_positions(&positions)
922        .map_err(|error| map_slice_shape_error("slice deletion", error))?;
923    let shape = deleted_vector_shape(rows, cols, storage.len());
924    Tensor::from_numeric_storage(storage, shape)
925        .map(Value::Tensor)
926        .map_err(|error| map_slice_shape_error("slice deletion", error))
927}
928
929pub fn delete_complex_with_plan(
930    t: ComplexTensor,
931    plan: &IndexPlan,
932    rhs: &Value,
933) -> Result<Value, RuntimeError> {
934    if !is_empty_delete_rhs(rhs) {
935        return Err(mex(
936            "DeletionRequiresEmptyRhs",
937            "Indexed deletion requires empty RHS",
938        ));
939    }
940    if plan.indices.is_empty() {
941        return Ok(Value::ComplexTensor(t));
942    }
943    if !(t.rows == 1 || t.cols == 1) {
944        return Err(mex(
945            "UnsupportedDeletion",
946            "Linear deletion is only supported for vectors",
947        ));
948    }
949    let positions = sorted_unique_positions_desc(plan, complex_tensor_element_len(&t))?;
950    if let Some(storage) = t.integer_storage().cloned() {
951        let storage = delete_integer_complex_storage_positions(storage, &positions);
952        let shape = deleted_vector_shape(t.rows, t.cols, storage.len());
953        return ComplexTensor::new_integer(storage, shape)
954            .map(Value::ComplexTensor)
955            .map_err(|error| map_slice_shape_error("complex slice deletion", error));
956    }
957    let dtype = t.numeric_dtype();
958    let rows = t.rows;
959    let cols = t.cols;
960    let mut values = t.materialize_f64();
961    for pos in positions {
962        values.remove(pos);
963    }
964    let shape = deleted_vector_shape(rows, cols, values.len());
965    ComplexTensor::from_f64_values_with_dtype(values, shape, dtype)
966        .map(Value::ComplexTensor)
967        .map_err(|error| map_slice_shape_error("complex slice deletion", error))
968}
969
970pub async fn assign_gpu_slice_with_plan(
971    handle: &runmat_accelerate_api::GpuTensorHandle,
972    plan: &IndexPlan,
973    rhs: &Value,
974) -> Result<Value, RuntimeError> {
975    if plan.indices.is_empty() {
976        return Ok(Value::GpuTensor(handle.clone()));
977    }
978    let mut unique_indices = plan.indices.clone();
979    unique_indices.sort_unstable();
980    if unique_indices.windows(2).any(|pair| pair[0] == pair[1]) {
981        return Err(mex(
982            "RepeatedGpuAssignmentIndex",
983            "gpuArray indexed assignment does not support repeated target subscripts",
984        ));
985    }
986    let provider = runmat_accelerate_api::provider_for_handle(handle).ok_or_else(|| {
987        mex(
988            "AccelerationProviderUnavailable",
989            "No acceleration provider owns the target gpuArray",
990        )
991    })?;
992    if runmat_accelerate_api::handle_integer_type(handle).is_some() {
993        if let Value::GpuTensor(rhs_handle) = rhs {
994            if runmat_accelerate_api::handle_integer_type(rhs_handle)
995                == runmat_accelerate_api::handle_integer_type(handle)
996            {
997                if let Ok(rhs_indices) = integer_gpu_rhs_indices_for_plan(&rhs_handle.shape, plan) {
998                    let reuses_rhs = rhs_indices
999                        .iter()
1000                        .enumerate()
1001                        .all(|(index, &rhs_index)| rhs_index as usize == index);
1002                    let values = if reuses_rhs {
1003                        rhs_handle.clone()
1004                    } else {
1005                        provider
1006                            .gather_linear(rhs_handle, &rhs_indices, &plan.output_shape)
1007                            .map_err(|e| {
1008                                map_acceleration_error(
1009                                    "expand exact integer gpuArray assignment rhs",
1010                                    e,
1011                                )
1012                            })?
1013                    };
1014                    let result = provider
1015                        .scatter_linear(handle, &plan.indices, &values)
1016                        .map_err(|e| {
1017                            map_acceleration_error("exact integer gpuArray slice assignment", e)
1018                        });
1019                    if !reuses_rhs {
1020                        let _ = provider.free(&values);
1021                    }
1022                    result?;
1023                    return Ok(Value::GpuTensor(handle.clone()));
1024                }
1025            }
1026        }
1027        let tensor = download_integer_tensor(handle).await?;
1028        let Value::Tensor(updated) = assign_tensor_with_plan(tensor, plan, rhs).await? else {
1029            unreachable!("real integer slice assignment must produce a real tensor")
1030        };
1031        return upload_tensor_to_gpu(&updated);
1032    }
1033    if let Value::GpuTensor(vh) = rhs {
1034        let rows = plan.base_shape.first().copied().unwrap_or(1);
1035        let cols = plan.base_shape.get(1).copied().unwrap_or(1);
1036        if let Some(col) = plan.properties.full_column {
1037            if col < cols {
1038                let v_rows = match vh.shape.len() {
1039                    1 | 2 => vh.shape[0],
1040                    _ => 0,
1041                };
1042                if v_rows == rows {
1043                    if let Ok(new_h) = provider.scatter_column(handle, col, vh) {
1044                        return Ok(Value::GpuTensor(new_h));
1045                    }
1046                }
1047            }
1048        }
1049        if let Some(row) = plan.properties.full_row {
1050            if row < rows {
1051                let v_cols = match vh.shape.len() {
1052                    1 => vh.shape[0],
1053                    2 => vh.shape[1],
1054                    _ => 0,
1055                };
1056                if v_cols == cols {
1057                    if let Ok(new_h) = provider.scatter_row(handle, row, vh) {
1058                        return Ok(Value::GpuTensor(new_h));
1059                    }
1060                }
1061            }
1062        }
1063    }
1064    let rhs_values = materialize_rhs_real_for_plan(rhs, plan).await?;
1065    let value_shape = vec![rhs_values.len().max(1), 1];
1066    let upload_result = if rhs_values.is_empty() {
1067        provider.zeros(&[0, 1])
1068    } else {
1069        provider.upload(&runmat_accelerate_api::HostTensorView {
1070            data: &rhs_values,
1071            shape: &value_shape,
1072        })
1073    };
1074    if let Ok(values_handle) = upload_result {
1075        if provider
1076            .scatter_linear(handle, &plan.indices, &values_handle)
1077            .is_ok()
1078        {
1079            return Ok(Value::GpuTensor(handle.clone()));
1080        }
1081    }
1082
1083    let host = provider
1084        .download(handle)
1085        .await
1086        .map_err(|e| map_acceleration_error("gather for slice assign", e))?;
1087    let t =
1088        Tensor::new(host.data, host.shape).map_err(|e| map_slice_shape_error("slice assign", e))?;
1089    let shape = t.shape.clone();
1090    let mut storage = t
1091        .into_numeric_storage()
1092        .map_err(|error| map_slice_shape_error("slice assign", error))?;
1093    scatter_real_with_plan(&mut storage, plan, &rhs_values)?;
1094    let t = Tensor::from_numeric_storage(storage, shape)
1095        .map_err(|error| map_slice_shape_error("slice assign", error))?;
1096    upload_tensor_to_gpu(&t)
1097}
1098
1099fn integer_gpu_rhs_indices_for_plan(
1100    rhs_shape: &[usize],
1101    plan: &IndexPlan,
1102) -> Result<Vec<u32>, RuntimeError> {
1103    let rhs_len = rhs_shape.iter().try_fold(1usize, |len, dimension| {
1104        len.checked_mul(*dimension)
1105            .ok_or_else(|| mex("ShapeMismatch", "shape mismatch for slice assign"))
1106    })?;
1107    if plan.dims == 1 {
1108        if rhs_len == plan.indices.len() {
1109            return (0..rhs_len)
1110                .map(|index| {
1111                    u32::try_from(index).map_err(|_| {
1112                        mex(
1113                            "AccelerationOperationFailed",
1114                            "GPU rhs exceeds indexing limits",
1115                        )
1116                    })
1117                })
1118                .collect();
1119        }
1120        if rhs_len == 1 {
1121            return Ok(vec![0; plan.indices.len()]);
1122        }
1123        return Err(mex("ShapeMismatch", "shape mismatch for slice assign"));
1124    }
1125
1126    let dims = plan.selection_lengths.len();
1127    let shape = validated_assignment_rhs_shape(rhs_shape, rhs_len, &plan.selection_lengths)?;
1128    let mut strides = vec![1usize; dims];
1129    for dimension in 1..dims {
1130        strides[dimension] = strides[dimension - 1]
1131            .checked_mul(shape[dimension - 1].max(1))
1132            .ok_or_else(|| mex("ShapeMismatch", "shape mismatch for slice assign"))?;
1133    }
1134    let mut output = Vec::with_capacity(plan.indices.len());
1135    let mut coordinates = vec![0usize; dims];
1136    for _ in 0..plan.indices.len() {
1137        let mut rhs_index = 0usize;
1138        for dimension in 0..dims {
1139            let coordinate = if shape[dimension] == 1 {
1140                0
1141            } else {
1142                coordinates[dimension]
1143            };
1144            rhs_index = rhs_index
1145                .checked_add(
1146                    coordinate
1147                        .checked_mul(strides[dimension])
1148                        .ok_or_else(|| mex("ShapeMismatch", "shape mismatch for slice assign"))?,
1149                )
1150                .ok_or_else(|| mex("ShapeMismatch", "shape mismatch for slice assign"))?;
1151        }
1152        output.push(u32::try_from(rhs_index).map_err(|_| {
1153            mex(
1154                "AccelerationOperationFailed",
1155                "GPU rhs exceeds indexing limits",
1156            )
1157        })?);
1158        for (dimension, coordinate) in coordinates.iter_mut().enumerate().take(dims) {
1159            *coordinate += 1;
1160            if *coordinate < plan.selection_lengths[dimension].max(1) {
1161                break;
1162            }
1163            *coordinate = 0;
1164        }
1165    }
1166    Ok(output)
1167}
1168
1169pub async fn delete_gpu_slice_with_plan(
1170    handle: &runmat_accelerate_api::GpuTensorHandle,
1171    plan: &IndexPlan,
1172    rhs: &Value,
1173) -> Result<Value, RuntimeError> {
1174    if !is_empty_delete_rhs(rhs) {
1175        return Err(mex(
1176            "DeletionRequiresEmptyRhs",
1177            "Indexed deletion requires empty RHS",
1178        ));
1179    }
1180    if plan.indices.is_empty() {
1181        return Ok(Value::GpuTensor(handle.clone()));
1182    }
1183    let provider = runmat_accelerate_api::provider().ok_or_else(|| {
1184        mex(
1185            "AccelerationProviderUnavailable",
1186            "No acceleration provider registered",
1187        )
1188    })?;
1189    if runmat_accelerate_api::handle_integer_type(handle).is_some() {
1190        let tensor = download_integer_tensor(handle).await?;
1191        let Value::Tensor(updated) = delete_tensor_with_plan(tensor, plan, rhs)? else {
1192            unreachable!("integer slice deletion must produce a real tensor")
1193        };
1194        return upload_tensor_to_gpu(&updated);
1195    }
1196    let host = provider
1197        .download(handle)
1198        .await
1199        .map_err(|e| map_acceleration_error("gather for slice deletion", e))?;
1200    let t = Tensor::new(host.data, host.shape)
1201        .map_err(|e| map_slice_shape_error("slice deletion", e))?;
1202    let Value::Tensor(updated) = delete_tensor_with_plan(t, plan, rhs)? else {
1203        unreachable!()
1204    };
1205    upload_tensor_to_gpu(&updated)
1206}
1207
1208pub async fn materialize_rhs_linear_real(
1209    rhs: &Value,
1210    count: usize,
1211) -> Result<Vec<f64>, RuntimeError> {
1212    let host_rhs = crate::dispatcher::gather_if_needed_async(rhs).await?;
1213    match host_rhs {
1214        Value::Num(n) => Ok(vec![n; count]),
1215        Value::Int(int_val) => Ok(vec![int_val.to_f64(); count]),
1216        Value::Bool(b) => Ok(vec![if b { 1.0 } else { 0.0 }; count]),
1217        Value::Tensor(t) => {
1218            let len = tensor_element_len(&t);
1219            if len == count {
1220                Ok(tensor::tensor_into_values_f64(t))
1221            } else if len == 1 {
1222                Ok(vec![tensor_value_f64(&t, 0); count])
1223            } else {
1224                Err(mex("ShapeMismatch", "shape mismatch for slice assign"))
1225            }
1226        }
1227        Value::LogicalArray(la) => {
1228            if la.data.len() == count {
1229                Ok(la
1230                    .data
1231                    .into_iter()
1232                    .map(|b| if b != 0 { 1.0 } else { 0.0 })
1233                    .collect())
1234            } else if la.data.len() == 1 {
1235                let val = if la.data[0] != 0 { 1.0 } else { 0.0 };
1236                Ok(vec![val; count])
1237            } else {
1238                Err(mex("ShapeMismatch", "shape mismatch for slice assign"))
1239            }
1240        }
1241        Value::OutputList(values) => materialize_output_list_real(&values, count),
1242        other => Err(mex(
1243            "InvalidSliceAssignmentRhs",
1244            format!("slice assign: unsupported RHS type {:?}", other),
1245        )),
1246    }
1247}
1248
1249pub async fn materialize_rhs_nd_real(
1250    rhs: &Value,
1251    selection_lengths: &[usize],
1252) -> Result<Vec<f64>, RuntimeError> {
1253    let rhs_host = crate::dispatcher::gather_if_needed_async(rhs).await?;
1254    enum RhsView {
1255        Scalar(f64),
1256        Tensor {
1257            data: Vec<f64>,
1258            shape: Vec<usize>,
1259            strides: Vec<usize>,
1260        },
1261    }
1262    let view = match rhs_host {
1263        Value::Num(n) => RhsView::Scalar(n),
1264        Value::Int(iv) => RhsView::Scalar(iv.to_f64()),
1265        Value::Bool(b) => RhsView::Scalar(if b { 1.0 } else { 0.0 }),
1266        Value::Tensor(t) => {
1267            let shape = validated_assignment_rhs_shape(
1268                &t.shape,
1269                tensor_element_len(&t),
1270                selection_lengths,
1271            )?;
1272            let mut strides = vec![1usize; selection_lengths.len()];
1273            for d in 1..selection_lengths.len() {
1274                strides[d] = strides[d - 1] * shape[d - 1].max(1);
1275            }
1276            let data = tensor::tensor_into_values_f64(t);
1277            if data.len()
1278                != shape
1279                    .iter()
1280                    .copied()
1281                    .fold(1usize, |acc, len| acc.saturating_mul(len.max(1)))
1282            {
1283                return Err(mex("ShapeMismatch", "shape mismatch for slice assign"));
1284            }
1285            RhsView::Tensor {
1286                data,
1287                shape,
1288                strides,
1289            }
1290        }
1291        Value::LogicalArray(la) => {
1292            let shape =
1293                validated_assignment_rhs_shape(&la.shape, la.data.len(), selection_lengths)?;
1294            let mut strides = vec![1usize; selection_lengths.len()];
1295            for d in 1..selection_lengths.len() {
1296                strides[d] = strides[d - 1] * shape[d - 1].max(1);
1297            }
1298            if la.data.len()
1299                != shape
1300                    .iter()
1301                    .copied()
1302                    .fold(1usize, |acc, len| acc.saturating_mul(len.max(1)))
1303            {
1304                return Err(mex("ShapeMismatch", "shape mismatch for slice assign"));
1305            }
1306            let data: Vec<f64> = la
1307                .data
1308                .into_iter()
1309                .map(|b| if b != 0 { 1.0 } else { 0.0 })
1310                .collect();
1311            RhsView::Tensor {
1312                data,
1313                shape,
1314                strides,
1315            }
1316        }
1317        Value::OutputList(values) => {
1318            let count = selection_lengths
1319                .iter()
1320                .copied()
1321                .fold(1usize, |acc, len| acc.saturating_mul(len.max(1)));
1322            let data = materialize_output_list_real(&values, count)?;
1323            let shape = if selection_lengths.is_empty() {
1324                vec![1]
1325            } else {
1326                selection_lengths.to_vec()
1327            };
1328            let mut strides = vec![1usize; shape.len()];
1329            for d in 1..shape.len() {
1330                strides[d] = strides[d - 1] * shape[d - 1].max(1);
1331            }
1332            RhsView::Tensor {
1333                data,
1334                shape,
1335                strides,
1336            }
1337        }
1338        other => {
1339            return Err(mex(
1340                "InvalidSliceAssignmentRhs",
1341                format!("slice assign: unsupported RHS type {:?}", other),
1342            ))
1343        }
1344    };
1345
1346    let total = selection_lengths
1347        .iter()
1348        .copied()
1349        .fold(1usize, |acc, len| acc.saturating_mul(len.max(1)));
1350    let mut out = Vec::with_capacity(total);
1351    let mut idx = vec![0usize; selection_lengths.len()];
1352    if selection_lengths.is_empty() {
1353        return Ok(out);
1354    }
1355    loop {
1356        match &view {
1357            RhsView::Scalar(val) => out.push(*val),
1358            RhsView::Tensor {
1359                data,
1360                shape,
1361                strides,
1362            } => {
1363                let mut rlin = 0usize;
1364                for d in 0..idx.len() {
1365                    let rhs_len = shape[d];
1366                    let pos = if rhs_len == 1 { 0 } else { idx[d] };
1367                    rlin += pos * strides[d];
1368                }
1369                out.push(data.get(rlin).copied().unwrap_or(0.0));
1370            }
1371        }
1372        let mut d = 0usize;
1373        while d < idx.len() {
1374            idx[d] += 1;
1375            if idx[d] < selection_lengths[d].max(1) {
1376                break;
1377            }
1378            idx[d] = 0;
1379            d += 1;
1380        }
1381        if d == idx.len() {
1382            break;
1383        }
1384    }
1385    Ok(out)
1386}
1387
1388fn materialize_output_list_real(values: &[Value], count: usize) -> Result<Vec<f64>, RuntimeError> {
1389    if values.len() == count {
1390        values.iter().map(value_to_real_scalar).collect()
1391    } else if values.len() == 1 {
1392        let value = value_to_real_scalar(&values[0])?;
1393        Ok(vec![value; count])
1394    } else {
1395        Err(mex("ShapeMismatch", "shape mismatch for slice assign"))
1396    }
1397}
1398
1399fn value_to_real_scalar(value: &Value) -> Result<f64, RuntimeError> {
1400    match value {
1401        Value::Num(n) => Ok(*n),
1402        Value::Int(int_val) => Ok(int_val.to_f64()),
1403        Value::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }),
1404        Value::Tensor(t) if is_scalar_tensor(t) => Ok(tensor_value_f64(t, 0)),
1405        _ => f64::try_from(value).map_err(Into::into),
1406    }
1407}
1408
1409pub fn upload_tensor_to_gpu(t: &Tensor) -> Result<Value, RuntimeError> {
1410    let provider = runmat_accelerate_api::provider().ok_or_else(|| {
1411        mex(
1412            "AccelerationProviderUnavailable",
1413            "No acceleration provider registered",
1414        )
1415    })?;
1416    let new_h = if let Some(storage) = t.integer_storage() {
1417        let view = integer_tensor_view(storage, &t.shape);
1418        provider
1419            .upload_integer(&view)
1420            .map_err(|e| map_acceleration_error("exact integer reupload after slice assign", e))?
1421    } else {
1422        let data = t.materialize_f64();
1423        let view = runmat_accelerate_api::HostTensorView {
1424            data: &data,
1425            shape: &t.shape,
1426        };
1427        provider
1428            .upload(&view)
1429            .map_err(|e| map_acceleration_error("reupload after slice assign", e))?
1430    };
1431    Ok(Value::GpuTensor(new_h))
1432}
1433
1434pub(crate) async fn download_integer_tensor(
1435    handle: &runmat_accelerate_api::GpuTensorHandle,
1436) -> Result<Tensor, RuntimeError> {
1437    let provider = runmat_accelerate_api::provider_for_handle(handle).ok_or_else(|| {
1438        mex(
1439            "AccelerationProviderUnavailable",
1440            "No acceleration provider registered for integer gpuArray",
1441        )
1442    })?;
1443    let integer = provider
1444        .download_integer(handle)
1445        .await
1446        .map_err(|e| map_acceleration_error("exact integer gather for assignment", e))?;
1447    let storage = match integer.data {
1448        HostIntegerDataOwned::I8(values) => IntegerStorage::I8(values),
1449        HostIntegerDataOwned::I16(values) => IntegerStorage::I16(values),
1450        HostIntegerDataOwned::I32(values) => IntegerStorage::I32(values),
1451        HostIntegerDataOwned::I64(values) => IntegerStorage::I64(values),
1452        HostIntegerDataOwned::U8(values) => IntegerStorage::U8(values),
1453        HostIntegerDataOwned::U16(values) => IntegerStorage::U16(values),
1454        HostIntegerDataOwned::U32(values) => IntegerStorage::U32(values),
1455        HostIntegerDataOwned::U64(values) => IntegerStorage::U64(values),
1456    };
1457    Tensor::new_integer(storage, integer.shape)
1458        .map_err(|e| map_slice_shape_error("integer gpuArray assignment gather", e))
1459}
1460
1461fn integer_tensor_view<'a>(
1462    storage: &'a IntegerStorage,
1463    shape: &'a [usize],
1464) -> HostIntegerTensorView<'a> {
1465    let data = match storage {
1466        IntegerStorage::I8(values) => HostIntegerDataView::I8(values),
1467        IntegerStorage::I16(values) => HostIntegerDataView::I16(values),
1468        IntegerStorage::I32(values) => HostIntegerDataView::I32(values),
1469        IntegerStorage::I64(values) => HostIntegerDataView::I64(values),
1470        IntegerStorage::U8(values) => HostIntegerDataView::U8(values),
1471        IntegerStorage::U16(values) => HostIntegerDataView::U16(values),
1472        IntegerStorage::U32(values) => HostIntegerDataView::U32(values),
1473        IntegerStorage::U64(values) => HostIntegerDataView::U64(values),
1474    };
1475    HostIntegerTensorView { data, shape }
1476}
1477
1478#[cfg(test)]
1479mod tests {
1480    use super::{
1481        assign_complex_with_plan, assign_gpu_slice_with_plan, assign_sparse_with_plan,
1482        assign_tensor_with_plan, build_complex_rhs_view, build_string_rhs_view,
1483        delete_complex_with_plan, delete_gpu_slice_with_plan, delete_tensor_with_plan,
1484        integer_gpu_rhs_indices_for_plan, map_acceleration_error, materialize_rhs_linear_real,
1485        materialize_rhs_nd_real, validated_assignment_rhs_shape, ComplexRhsView, StringRhsView,
1486    };
1487    use crate::indexing::plan::IndexPlan;
1488    use futures::executor::block_on;
1489    use runmat_value::{
1490        CellArray, ComplexTensor, IntegerComplexStorage, IntegerStorage, NumericDType,
1491        NumericStorage, SparseTensor, StringArray, Tensor, Value,
1492    };
1493
1494    #[test]
1495    fn gpu_assignment_rejects_repeated_target_subscripts_before_provider_access() {
1496        let handle = runmat_accelerate_api::GpuTensorHandle {
1497            shape: vec![1, 3],
1498            device_id: 91,
1499            buffer_id: 17,
1500            descriptor: Default::default(),
1501        };
1502        let plan = IndexPlan::new(vec![1, 1], vec![1, 2], vec![2], 1, vec![1, 3]);
1503        let error = block_on(assign_gpu_slice_with_plan(&handle, &plan, &Value::Num(4.0)))
1504            .expect_err("repeated gpuArray assignment targets must reject");
1505        assert_eq!(
1506            error.identifier(),
1507            Some("RunMat:RepeatedGpuAssignmentIndex")
1508        );
1509    }
1510
1511    #[test]
1512    fn integer_plan_assignment_preserves_exact_uint64_rhs() {
1513        let tensor =
1514            Tensor::new_integer(IntegerStorage::U64(vec![1, 2, 3]), vec![1, 3]).expect("tensor");
1515        let rhs_tensor =
1516            Tensor::new_integer(IntegerStorage::U64(vec![u64::MAX, 9]), vec![1, 2]).expect("rhs");
1517        let rhs = Value::Tensor(rhs_tensor);
1518        let plan = IndexPlan::new(vec![0, 1], vec![1, 2], vec![2], 1, vec![1, 3]);
1519        let result = block_on(assign_tensor_with_plan(tensor, &plan, &rhs)).expect("assign");
1520
1521        let Value::Tensor(output) = result else {
1522            panic!("expected tensor");
1523        };
1524        assert_eq!(
1525            output.integer_storage(),
1526            Some(&IntegerStorage::U64(vec![u64::MAX, 9, 3]))
1527        );
1528    }
1529
1530    #[test]
1531    fn native_single_plan_assignment_and_deletion_preserve_class() {
1532        let tensor = Tensor::from_f32(vec![1.0, 2.0, 3.0], vec![1, 3]).expect("single tensor");
1533        let plan = IndexPlan::new(vec![0, 2], vec![1, 2], vec![2], 1, vec![1, 3]);
1534        let Value::Tensor(updated) = block_on(assign_tensor_with_plan(
1535            tensor,
1536            &plan,
1537            &Value::Num(1.234_567_890_123),
1538        ))
1539        .expect("single slice assignment") else {
1540            panic!("expected tensor");
1541        };
1542        assert_eq!(updated.numeric_dtype(), NumericDType::F32);
1543        assert_eq!(
1544            updated.clone().into_numeric_storage(),
1545            Ok(NumericStorage::F32(vec![
1546                1.234_567_890_123_f64 as f32,
1547                2.0,
1548                1.234_567_890_123_f64 as f32,
1549            ]))
1550        );
1551
1552        let empty = Value::Tensor(Tensor::new(Vec::new(), vec![0, 0]).expect("empty"));
1553        let delete_plan = IndexPlan::new(vec![1], vec![1, 1], vec![1], 1, vec![1, 3]);
1554        let Value::Tensor(deleted) =
1555            delete_tensor_with_plan(updated, &delete_plan, &empty).expect("single slice deletion")
1556        else {
1557            panic!("expected tensor");
1558        };
1559        assert_eq!(deleted.numeric_dtype(), NumericDType::F32);
1560        assert_eq!(deleted.shape, vec![1, 2]);
1561        assert_eq!(
1562            deleted.into_numeric_storage(),
1563            Ok(NumericStorage::F32(vec![
1564                1.234_567_890_123_f64 as f32,
1565                1.234_567_890_123_f64 as f32,
1566            ]))
1567        );
1568    }
1569
1570    #[test]
1571    fn native_complex_single_plan_assignment_and_deletion_preserve_class() {
1572        let tensor =
1573            ComplexTensor::from_f32(vec![(1.0, -1.0), (2.0, -2.0), (3.0, -3.0)], vec![1, 3])
1574                .unwrap();
1575        let plan = IndexPlan::new(vec![0, 2], vec![1, 2], vec![2], 1, vec![1, 3]);
1576        let Value::ComplexTensor(updated) = block_on(assign_complex_with_plan(
1577            tensor,
1578            &plan,
1579            &Value::Complex(1.234_567_890_123, -9.876_543_210_987),
1580        ))
1581        .expect("complex single slice assignment") else {
1582            panic!("expected complex tensor");
1583        };
1584        assert_eq!(updated.numeric_dtype(), NumericDType::F32);
1585        assert_eq!(
1586            updated.as_f32_slice(),
1587            Some(
1588                &[
1589                    (1.234_567_890_123_f64 as f32, -9.876_543_210_987_f64 as f32,),
1590                    (2.0, -2.0),
1591                    (1.234_567_890_123_f64 as f32, -9.876_543_210_987_f64 as f32,),
1592                ][..]
1593            )
1594        );
1595
1596        let empty = Value::Tensor(Tensor::new(Vec::new(), vec![0, 0]).expect("empty"));
1597        let delete_plan = IndexPlan::new(vec![1], vec![1, 1], vec![1], 1, vec![1, 3]);
1598        let Value::ComplexTensor(deleted) = delete_complex_with_plan(updated, &delete_plan, &empty)
1599            .expect("complex single slice deletion")
1600        else {
1601            panic!("expected complex tensor");
1602        };
1603        assert_eq!(deleted.numeric_dtype(), NumericDType::F32);
1604        assert_eq!(deleted.shape, vec![1, 2]);
1605        assert_eq!(
1606            deleted.as_f32_slice(),
1607            Some(
1608                &[
1609                    (1.234_567_890_123_f64 as f32, -9.876_543_210_987_f64 as f32,),
1610                    (1.234_567_890_123_f64 as f32, -9.876_543_210_987_f64 as f32,),
1611                ][..]
1612            )
1613        );
1614    }
1615
1616    #[test]
1617    fn typed_slice_deletion_uses_exact_storage_when_f64_mirrors_are_unavailable() {
1618        let tensor = Tensor::new_integer(IntegerStorage::U64(vec![1, u64::MAX, 3]), vec![1, 3])
1619            .expect("tensor");
1620        let plan = IndexPlan::new(vec![1], vec![1, 1], vec![1], 1, vec![1, 3]);
1621        let empty = Value::Tensor(Tensor::new(Vec::new(), vec![0, 0]).expect("empty rhs"));
1622
1623        let Value::Tensor(output) =
1624            delete_tensor_with_plan(tensor, &plan, &empty).expect("typed uint64 deletion")
1625        else {
1626            panic!("expected tensor");
1627        };
1628        assert_eq!(output.shape, vec![1, 2]);
1629        assert_eq!(
1630            output.integer_storage(),
1631            Some(&IntegerStorage::U64(vec![1, 3]))
1632        );
1633
1634        let complex = ComplexTensor::new_integer(
1635            IntegerComplexStorage::new(
1636                IntegerStorage::I64(vec![i64::MIN, -2, i64::MAX]),
1637                IntegerStorage::I64(vec![1, 2, 3]),
1638            )
1639            .expect("integer complex storage"),
1640            vec![1, 3],
1641        )
1642        .expect("complex tensor");
1643
1644        let Value::ComplexTensor(output) = delete_complex_with_plan(complex, &plan, &empty)
1645            .expect("typed signed complex deletion")
1646        else {
1647            panic!("expected complex tensor");
1648        };
1649        assert_eq!(output.shape, vec![1, 2]);
1650        assert_eq!(
1651            output.integer_storage().cloned(),
1652            Some(
1653                IntegerComplexStorage::new(
1654                    IntegerStorage::I64(vec![i64::MIN, i64::MAX]),
1655                    IntegerStorage::I64(vec![1, 3]),
1656                )
1657                .expect("integer complex storage")
1658            )
1659        );
1660    }
1661
1662    #[test]
1663    fn integer_plan_assignment_preserves_all_typed_rhs_classes_with_poisoned_mirrors() {
1664        macro_rules! assert_assignment {
1665            ($storage:ident, $value:expr) => {{
1666                let tensor = Tensor::new_integer(
1667                    IntegerStorage::$storage(vec![Default::default(), Default::default()]),
1668                    vec![1, 2],
1669                )
1670                .expect("destination tensor");
1671                let rhs = Tensor::new_integer(IntegerStorage::$storage(vec![$value]), vec![1, 1])
1672                    .expect("rhs tensor");
1673                let plan = IndexPlan::new(vec![1], vec![1, 1], vec![1], 1, vec![1, 2]);
1674
1675                let Value::Tensor(output) =
1676                    block_on(assign_tensor_with_plan(tensor, &plan, &Value::Tensor(rhs)))
1677                        .expect("assignment")
1678                else {
1679                    panic!("expected tensor");
1680                };
1681                assert_eq!(
1682                    output.integer_storage(),
1683                    Some(&IntegerStorage::$storage(vec![Default::default(), $value]))
1684                );
1685            }};
1686        }
1687
1688        assert_assignment!(I8, i8::MIN);
1689        assert_assignment!(I16, i16::MIN);
1690        assert_assignment!(I32, i32::MIN);
1691        assert_assignment!(I64, i64::MIN);
1692        assert_assignment!(U8, u8::MAX);
1693        assert_assignment!(U16, u16::MAX);
1694        assert_assignment!(U32, u32::MAX);
1695        assert_assignment!(U64, u64::MAX);
1696    }
1697
1698    #[test]
1699    fn real_plan_assignment_reads_typed_integer_rhs_without_mirror() {
1700        let tensor = Tensor::new(vec![0.0, 0.0, 0.0], vec![1, 3]).expect("tensor");
1701        let rhs_tensor =
1702            Tensor::new_integer(IntegerStorage::U16(vec![4, 9]), vec![1, 2]).expect("rhs");
1703        let rhs = Value::Tensor(rhs_tensor);
1704        let plan = IndexPlan::new(vec![0, 2], vec![1, 2], vec![2], 1, vec![1, 3]);
1705        let result = block_on(assign_tensor_with_plan(tensor, &plan, &rhs)).expect("assign");
1706
1707        let Value::Tensor(output) = result else {
1708            panic!("expected tensor");
1709        };
1710        assert_eq!(output.materialize_f64(), vec![4.0, 0.0, 9.0]);
1711    }
1712
1713    #[test]
1714    fn real_scalar_expansion_reads_all_typed_integer_classes_without_f64_mirrors() {
1715        macro_rules! assert_scalar_expansion {
1716            ($storage:expr, $expected:expr) => {{
1717                let tensor = Tensor::new_integer($storage, vec![1, 1]).expect("scalar rhs");
1718                let rhs = Value::Tensor(tensor);
1719
1720                assert_eq!(
1721                    block_on(materialize_rhs_linear_real(&rhs, 3)).expect("linear expansion"),
1722                    vec![$expected; 3]
1723                );
1724                assert_eq!(
1725                    block_on(materialize_rhs_nd_real(&rhs, &[2, 2])).expect("nd expansion"),
1726                    vec![$expected; 4]
1727                );
1728            }};
1729        }
1730
1731        assert_scalar_expansion!(IntegerStorage::I8(vec![-8]), -8.0);
1732        assert_scalar_expansion!(IntegerStorage::I16(vec![-16]), -16.0);
1733        assert_scalar_expansion!(IntegerStorage::I32(vec![-32]), -32.0);
1734        assert_scalar_expansion!(IntegerStorage::I64(vec![-64]), -64.0);
1735        assert_scalar_expansion!(IntegerStorage::U8(vec![8]), 8.0);
1736        assert_scalar_expansion!(IntegerStorage::U16(vec![16]), 16.0);
1737        assert_scalar_expansion!(IntegerStorage::U32(vec![32]), 32.0);
1738        assert_scalar_expansion!(IntegerStorage::U64(vec![64]), 64.0);
1739    }
1740
1741    #[test]
1742    fn integer_plan_assignment_rejects_nonscalar_singleton_expansion() {
1743        let tensor =
1744            Tensor::new_integer(IntegerStorage::I8(vec![0; 4]), vec![2, 2]).expect("tensor");
1745        let rhs = Value::Tensor(
1746            Tensor::new_integer(IntegerStorage::I8(vec![5, 6]), vec![1, 2]).expect("rhs"),
1747        );
1748        let plan = IndexPlan::new(vec![0, 1, 2, 3], vec![2, 2], vec![2, 2], 2, vec![2, 2]);
1749        let error = block_on(assign_tensor_with_plan(tensor, &plan, &rhs))
1750            .expect_err("nonscalar singleton expansion must reject");
1751        assert_eq!(error.identifier(), Some("RunMat:ShapeMismatch"));
1752    }
1753
1754    #[test]
1755    fn assignment_rhs_shape_allows_only_scalar_linear_count_or_exact_nd_shape() {
1756        assert_eq!(
1757            validated_assignment_rhs_shape(&[1, 1, 1], 1, &[2, 3]).expect("scalar"),
1758            vec![1, 1]
1759        );
1760        assert_eq!(
1761            validated_assignment_rhs_shape(&[1, 6], 6, &[6]).expect("linear count"),
1762            vec![6]
1763        );
1764        assert_eq!(
1765            validated_assignment_rhs_shape(&[2, 3, 1], 6, &[2, 3]).expect("exact shape"),
1766            vec![2, 3]
1767        );
1768        assert!(validated_assignment_rhs_shape(&[1, 3], 3, &[2, 3]).is_err());
1769        assert!(validated_assignment_rhs_shape(&[2, 1], 2, &[2, 3]).is_err());
1770    }
1771
1772    #[test]
1773    fn floating_logical_complex_and_string_rhs_reject_singleton_expansion() {
1774        let floating = Value::Tensor(Tensor::new(vec![1.0, 2.0], vec![1, 2]).expect("floating"));
1775        let error = block_on(materialize_rhs_nd_real(&floating, &[2, 2]))
1776            .expect_err("floating singleton expansion must reject");
1777        assert_eq!(error.identifier(), Some("RunMat:ShapeMismatch"));
1778
1779        let logical = Value::LogicalArray(
1780            runmat_value::LogicalArray::new(vec![1, 0], vec![1, 2]).expect("logical"),
1781        );
1782        let error = block_on(materialize_rhs_nd_real(&logical, &[2, 2]))
1783            .expect_err("logical singleton expansion must reject");
1784        assert_eq!(error.identifier(), Some("RunMat:ShapeMismatch"));
1785
1786        let complex = Value::ComplexTensor(
1787            ComplexTensor::new(vec![(1.0, 2.0), (3.0, 4.0)], vec![1, 2]).expect("complex"),
1788        );
1789        let error = match build_complex_rhs_view(&complex, &[2, 2]) {
1790            Ok(_) => panic!("complex singleton expansion must reject"),
1791            Err(error) => error,
1792        };
1793        assert_eq!(error.identifier(), Some("RunMat:ShapeMismatch"));
1794
1795        let strings = Value::StringArray(
1796            runmat_value::StringArray::new(vec!["a".to_string(), "b".to_string()], vec![1, 2])
1797                .expect("strings"),
1798        );
1799        let error = match build_string_rhs_view(&strings, &[2, 2]) {
1800            Ok(_) => panic!("string singleton expansion must reject"),
1801            Err(error) => error,
1802        };
1803        assert_eq!(error.identifier(), Some("RunMat:ShapeMismatch"));
1804    }
1805
1806    #[test]
1807    fn integer_gpu_rhs_indices_cover_linear_scalar_and_exact_nd_forms() {
1808        let linear = IndexPlan::new(vec![5, 1, 3], vec![1, 3], vec![3], 1, vec![2, 3]);
1809        assert_eq!(
1810            integer_gpu_rhs_indices_for_plan(&[1, 3], &linear).expect("linear indices"),
1811            vec![0, 1, 2]
1812        );
1813        assert_eq!(
1814            integer_gpu_rhs_indices_for_plan(&[1, 1], &linear).expect("scalar indices"),
1815            vec![0, 0, 0]
1816        );
1817
1818        let nd = IndexPlan::new(
1819            vec![0, 1, 2, 3, 4, 5],
1820            vec![2, 3],
1821            vec![2, 3],
1822            2,
1823            vec![2, 3],
1824        );
1825        assert_eq!(
1826            integer_gpu_rhs_indices_for_plan(&[2, 3], &nd).expect("exact nd indices"),
1827            vec![0, 1, 2, 3, 4, 5]
1828        );
1829        assert!(integer_gpu_rhs_indices_for_plan(&[1, 3], &nd).is_err());
1830    }
1831
1832    #[test]
1833    fn integer_gpu_rhs_indices_reject_incompatible_shape() {
1834        let plan = IndexPlan::new(vec![0, 1, 2, 3], vec![2, 2], vec![2, 2], 2, vec![2, 2]);
1835        let error = integer_gpu_rhs_indices_for_plan(&[3, 1], &plan)
1836            .expect_err("incompatible GPU rhs shape must fail");
1837        assert_eq!(error.identifier(), Some("RunMat:ShapeMismatch"));
1838    }
1839
1840    #[test]
1841    fn integer_plan_assignment_converts_float_rhs_with_saturation() {
1842        let tensor =
1843            Tensor::new_integer(IntegerStorage::I8(vec![0, 0]), vec![1, 2]).expect("tensor");
1844        let plan = IndexPlan::new(vec![0, 1], vec![1, 2], vec![2], 1, vec![1, 2]);
1845        let result =
1846            block_on(assign_tensor_with_plan(tensor, &plan, &Value::Num(300.5))).expect("assign");
1847
1848        let Value::Tensor(output) = result else {
1849            panic!("expected tensor");
1850        };
1851        assert_eq!(
1852            output.integer_storage(),
1853            Some(&IntegerStorage::I8(vec![i8::MAX, i8::MAX]))
1854        );
1855    }
1856
1857    #[test]
1858    fn real_integer_plan_assignment_promotes_to_complex_exact_storage_for_every_class() {
1859        macro_rules! assert_promotion {
1860            ($storage:ident, $values:expr, $real:expr, $imag:expr) => {{
1861                let tensor = Tensor::new_integer(IntegerStorage::$storage($values), vec![2, 2])
1862                    .expect("tensor");
1863                let rhs_tensor = ComplexTensor::new_integer(
1864                    IntegerComplexStorage::new(
1865                        IntegerStorage::$storage(vec![$real]),
1866                        IntegerStorage::$storage(vec![$imag]),
1867                    )
1868                    .expect("rhs storage"),
1869                    vec![1, 1],
1870                )
1871                .expect("rhs");
1872                let plan = IndexPlan::new(vec![0, 1, 2, 3], vec![2, 2], vec![2, 2], 2, vec![2, 2]);
1873                let result = block_on(assign_tensor_with_plan(
1874                    tensor,
1875                    &plan,
1876                    &Value::ComplexTensor(rhs_tensor),
1877                ))
1878                .expect("assign");
1879                let Value::ComplexTensor(output) = result else {
1880                    panic!("integer tensor should promote to complex tensor");
1881                };
1882                assert_eq!(
1883                    output
1884                        .integer_storage()
1885                        .map(|storage| (&storage.real, &storage.imag)),
1886                    Some((
1887                        &IntegerStorage::$storage(vec![$real; 4]),
1888                        &IntegerStorage::$storage(vec![$imag; 4]),
1889                    ))
1890                );
1891            }};
1892        }
1893
1894        assert_promotion!(I8, vec![i8::MIN, -1, 0, i8::MAX], i8::MIN, i8::MAX);
1895        assert_promotion!(I16, vec![i16::MIN, -1, 0, i16::MAX], i16::MIN, i16::MAX);
1896        assert_promotion!(I32, vec![i32::MIN, -1, 0, i32::MAX], i32::MIN, i32::MAX);
1897        assert_promotion!(I64, vec![i64::MIN, -1, 0, i64::MAX], i64::MIN, i64::MAX);
1898        assert_promotion!(U8, vec![0, 1, 2, u8::MAX], 1, u8::MAX);
1899        assert_promotion!(U16, vec![0, 1, 2, u16::MAX], 1, u16::MAX);
1900        assert_promotion!(U32, vec![0, 1, 2, u32::MAX], 1, u32::MAX);
1901        assert_promotion!(
1902            U64,
1903            vec![0, 1, 2, u64::MAX],
1904            9_223_372_036_854_775_809,
1905            u64::MAX
1906        );
1907    }
1908
1909    #[test]
1910    fn real_integer_complex_promotion_rejects_shape_mismatch() {
1911        let tensor =
1912            Tensor::new_integer(IntegerStorage::I16(vec![0; 4]), vec![2, 2]).expect("tensor");
1913        let rhs = Value::ComplexTensor(
1914            ComplexTensor::new_integer(
1915                IntegerComplexStorage::new(
1916                    IntegerStorage::I16(vec![1, 2, 3]),
1917                    IntegerStorage::I16(vec![-1, -2, -3]),
1918                )
1919                .expect("rhs storage"),
1920                vec![1, 3],
1921            )
1922            .expect("rhs"),
1923        );
1924        let plan = IndexPlan::new(vec![0, 1, 2, 3], vec![2, 2], vec![2, 2], 2, vec![2, 2]);
1925
1926        let error = block_on(assign_tensor_with_plan(tensor, &plan, &rhs))
1927            .expect_err("incompatible complex RHS shape must fail");
1928        assert_eq!(error.identifier(), Some("RunMat:ShapeMismatch"));
1929    }
1930
1931    #[test]
1932    fn real_integer_complex_promotion_preserves_exact_non_2d_scalar_expansion() {
1933        let tensor = Tensor::new_integer(IntegerStorage::U64(vec![1, 2, 3, 4]), vec![2, 1, 2])
1934            .expect("tensor");
1935        let rhs = Value::ComplexTensor(
1936            ComplexTensor::new_integer(
1937                IntegerComplexStorage::new(
1938                    IntegerStorage::U64(vec![u64::MAX]),
1939                    IntegerStorage::U64(vec![9_223_372_036_854_775_809]),
1940                )
1941                .expect("rhs storage"),
1942                vec![1, 1, 1],
1943            )
1944            .expect("rhs"),
1945        );
1946        let plan = IndexPlan::new(
1947            vec![0, 1, 2, 3],
1948            vec![2, 1, 2],
1949            vec![2, 1, 2],
1950            3,
1951            vec![2, 1, 2],
1952        );
1953
1954        let result = block_on(assign_tensor_with_plan(tensor, &plan, &rhs)).expect("assign");
1955        let Value::ComplexTensor(output) = result else {
1956            panic!("integer tensor should promote to complex tensor");
1957        };
1958        assert_eq!(output.shape, vec![2, 1, 2]);
1959        assert_eq!(
1960            output
1961                .integer_storage()
1962                .as_ref()
1963                .map(|storage| (&storage.real, &storage.imag)),
1964            Some((
1965                &IntegerStorage::U64(vec![u64::MAX; 4]),
1966                &IntegerStorage::U64(vec![9_223_372_036_854_775_809; 4]),
1967            ))
1968        );
1969    }
1970
1971    #[test]
1972    fn typed_complex_integer_plan_assignment_rejects_nonscalar_singleton_expansion() {
1973        let tensor = ComplexTensor::new_integer(
1974            IntegerComplexStorage::new(
1975                IntegerStorage::U64(vec![1, 2, 3, 4]),
1976                IntegerStorage::U64(vec![10, 20, 30, 40]),
1977            )
1978            .expect("storage"),
1979            vec![2, 2],
1980        )
1981        .expect("tensor");
1982        let rhs = Value::ComplexTensor(
1983            ComplexTensor::new_integer(
1984                IntegerComplexStorage::new(
1985                    IntegerStorage::U64(vec![u64::MAX, 9_223_372_036_854_775_808]),
1986                    IntegerStorage::U64(vec![7, 8]),
1987                )
1988                .expect("storage"),
1989                vec![1, 2],
1990            )
1991            .expect("rhs"),
1992        );
1993        let plan = IndexPlan::new(vec![0, 1, 2, 3], vec![2, 2], vec![2, 2], 2, vec![2, 2]);
1994        let error = block_on(assign_complex_with_plan(tensor, &plan, &rhs))
1995            .expect_err("complex nonscalar singleton expansion must reject");
1996        assert_eq!(error.identifier(), Some("RunMat:ShapeMismatch"));
1997    }
1998
1999    #[test]
2000    fn typed_complex_integer_plan_assignment_rejects_shape_mismatch() {
2001        let tensor = ComplexTensor::new_integer(
2002            IntegerComplexStorage::new(
2003                IntegerStorage::I8(vec![0; 4]),
2004                IntegerStorage::I8(vec![0; 4]),
2005            )
2006            .expect("storage"),
2007            vec![2, 2],
2008        )
2009        .expect("tensor");
2010        let rhs = Value::ComplexTensor(
2011            ComplexTensor::new_integer(
2012                IntegerComplexStorage::new(
2013                    IntegerStorage::I8(vec![1, 2, 3]),
2014                    IntegerStorage::I8(vec![4, 5, 6]),
2015                )
2016                .expect("storage"),
2017                vec![1, 3],
2018            )
2019            .expect("rhs"),
2020        );
2021        let plan = IndexPlan::new(vec![0, 1, 2, 3], vec![2, 2], vec![2, 2], 2, vec![2, 2]);
2022        let err = block_on(assign_complex_with_plan(tensor, &plan, &rhs))
2023            .expect_err("shape mismatch should fail");
2024        assert_eq!(err.identifier(), Some("RunMat:ShapeMismatch"));
2025    }
2026
2027    #[test]
2028    fn typed_complex_integer_plan_assignment_converts_float_components_independently() {
2029        let tensor = ComplexTensor::new_integer(
2030            IntegerComplexStorage::new(
2031                IntegerStorage::I8(vec![0; 2]),
2032                IntegerStorage::I8(vec![0; 2]),
2033            )
2034            .expect("storage"),
2035            vec![1, 2],
2036        )
2037        .expect("tensor");
2038        let plan = IndexPlan::new(vec![0, 1], vec![1, 2], vec![2], 1, vec![1, 2]);
2039        let result = block_on(assign_complex_with_plan(
2040            tensor,
2041            &plan,
2042            &Value::Complex(300.5, -300.5),
2043        ))
2044        .expect("assign");
2045
2046        let Value::ComplexTensor(output) = result else {
2047            panic!("expected complex tensor");
2048        };
2049        assert_eq!(
2050            output
2051                .integer_storage()
2052                .as_ref()
2053                .map(|storage| (&storage.real, &storage.imag)),
2054            Some((
2055                &IntegerStorage::I8(vec![i8::MAX, i8::MAX]),
2056                &IntegerStorage::I8(vec![i8::MIN, i8::MIN]),
2057            ))
2058        );
2059    }
2060
2061    #[test]
2062    fn sparse_integer_plan_assignment_preserves_exact_values_and_last_write_wins() {
2063        let _compat = crate::compatibility::push_runmat_extensions_enabled(true);
2064        let sparse =
2065            SparseTensor::new_integer(2, 2, vec![0, 0, 0], vec![], IntegerStorage::U64(vec![]))
2066                .expect("sparse");
2067        let rhs = Value::Tensor(
2068            Tensor::new_integer(
2069                IntegerStorage::U64(vec![1, 9_223_372_036_854_775_808, 3, u64::MAX]),
2070                vec![1, 4],
2071            )
2072            .expect("rhs"),
2073        );
2074        let plan = IndexPlan::new(vec![0, 3, 1, 3], vec![1, 4], vec![4], 1, vec![2, 2]);
2075        let result = block_on(assign_sparse_with_plan(sparse, &plan, &rhs)).expect("assign");
2076
2077        let Value::SparseTensor(output) = result else {
2078            panic!("expected sparse output");
2079        };
2080        assert_eq!(output.col_ptrs, vec![0, 2, 3]);
2081        assert_eq!(output.row_indices, vec![0, 1, 1]);
2082        assert_eq!(
2083            output.integer_storage(),
2084            Some(&IntegerStorage::U64(vec![1, 3, u64::MAX]))
2085        );
2086    }
2087
2088    #[test]
2089    fn sparse_integer_plan_assignment_rejects_nonscalar_singleton_expansion() {
2090        let _compat = crate::compatibility::push_runmat_extensions_enabled(true);
2091        let sparse =
2092            SparseTensor::new_integer(2, 2, vec![0, 0, 0], vec![], IntegerStorage::I8(vec![]))
2093                .expect("sparse");
2094        let rhs = Value::Tensor(
2095            Tensor::new_integer(IntegerStorage::I8(vec![5, 6]), vec![1, 2]).expect("rhs"),
2096        );
2097        let plan = IndexPlan::new(vec![0, 1, 2, 3], vec![2, 2], vec![2, 2], 2, vec![2, 2]);
2098        let error = block_on(assign_sparse_with_plan(sparse, &plan, &rhs))
2099            .expect_err("sparse nonscalar singleton expansion must reject");
2100        assert_eq!(error.identifier(), Some("RunMat:ShapeMismatch"));
2101    }
2102
2103    #[test]
2104    fn sparse_real_plan_assignment_elides_zero_values() {
2105        let sparse = SparseTensor::new(2, 2, vec![0, 2, 3], vec![0, 1, 0], vec![1.0, 2.0, 3.0])
2106            .expect("sparse");
2107        let plan = IndexPlan::new(vec![0, 1], vec![2, 1], vec![2, 1], 2, vec![2, 2]);
2108        let result =
2109            block_on(assign_sparse_with_plan(sparse, &plan, &Value::Num(0.0))).expect("assign");
2110
2111        let Value::SparseTensor(output) = result else {
2112            panic!("expected sparse output");
2113        };
2114        assert_eq!(output.col_ptrs, vec![0, 0, 1]);
2115        assert_eq!(output.row_indices, vec![0]);
2116        assert_eq!(output.as_f64_slice(), Some(&[3.0][..]));
2117    }
2118
2119    #[test]
2120    fn sparse_single_plan_assignment_preserves_class_and_rounds_to_f32() {
2121        let sparse = SparseTensor::new_f32(2, 2, vec![0, 1, 2], vec![0, 1], vec![1.0, 3.0])
2122            .expect("single sparse");
2123        let plan = IndexPlan::new(vec![0, 1], vec![2, 1], vec![2, 1], 2, vec![2, 2]);
2124        let rhs = Value::Tensor(Tensor::new(vec![0.0, 1.0 / 3.0], vec![2, 1]).expect("double rhs"));
2125        let result = block_on(assign_sparse_with_plan(sparse, &plan, &rhs)).expect("assign");
2126
2127        let Value::SparseTensor(output) = result else {
2128            panic!("expected sparse output");
2129        };
2130        assert_eq!(output.numeric_dtype(), Some(NumericDType::F32));
2131        assert_eq!(output.col_ptrs, vec![0, 1, 2]);
2132        assert_eq!(output.row_indices, vec![1, 1]);
2133        assert_eq!(output.as_f32_slice(), Some(&[(1.0 / 3.0) as f32, 3.0][..]));
2134    }
2135
2136    #[test]
2137    fn sparse_logical_plan_assignment_preserves_class_and_truth_conversion() {
2138        let sparse =
2139            SparseTensor::new_logical(2, 2, vec![0, 1, 2], vec![0, 1]).expect("logical sparse");
2140        let plan = IndexPlan::new(vec![0, 1], vec![2, 1], vec![2, 1], 2, vec![2, 2]);
2141        let rhs = Value::Tensor(Tensor::new(vec![0.0, -2.0], vec![2, 1]).expect("rhs"));
2142        let result = block_on(assign_sparse_with_plan(sparse, &plan, &rhs)).expect("assign");
2143        let Value::SparseTensor(output) = result else {
2144            panic!("expected sparse output");
2145        };
2146        assert!(output.is_logical());
2147        assert_eq!(output.col_ptrs, vec![0, 1, 2]);
2148        assert_eq!(output.row_indices, vec![1, 1]);
2149    }
2150
2151    #[test]
2152    fn integer_plan_deletion_preserves_exact_storage() {
2153        let tensor = Tensor::new_integer(IntegerStorage::I64(vec![1, i64::MAX, 3]), vec![1, 3])
2154            .expect("tensor");
2155        let empty = Value::Tensor(Tensor::new(Vec::new(), vec![0, 0]).expect("empty"));
2156        let plan = IndexPlan::new(vec![1], vec![1, 1], vec![1], 1, vec![1, 3]);
2157        let result = delete_tensor_with_plan(tensor, &plan, &empty).expect("delete");
2158
2159        let Value::Tensor(output) = result else {
2160            panic!("expected tensor");
2161        };
2162        assert_eq!(
2163            output.integer_storage(),
2164            Some(&IntegerStorage::I64(vec![1, 3]))
2165        );
2166        assert_eq!(output.shape, vec![1, 2]);
2167    }
2168
2169    #[test]
2170    fn gpu_integer_plan_deletion_preserves_wide_uint64_storage() {
2171        runmat_accelerate_api::set_thread_provider(None);
2172        runmat_accelerate_api::clear_provider();
2173        runmat_accelerate::simple_provider::register_inprocess_provider();
2174        let provider = runmat_accelerate_api::provider().expect("test provider");
2175        let _thread_provider = runmat_accelerate_api::ThreadProviderGuard::set(Some(provider));
2176        let source = provider
2177            .upload_integer(&runmat_accelerate_api::HostIntegerTensorView {
2178                data: runmat_accelerate_api::HostIntegerDataView::U64(&[
2179                    1,
2180                    9_223_372_036_854_775_808,
2181                    u64::MAX,
2182                ]),
2183                shape: &[1, 3],
2184            })
2185            .expect("upload integer source");
2186        let empty = Value::Tensor(Tensor::new(Vec::new(), vec![0, 0]).expect("empty"));
2187        let plan = IndexPlan::new(vec![1], vec![1, 1], vec![1], 1, vec![1, 3]);
2188
2189        let Value::GpuTensor(updated) =
2190            block_on(delete_gpu_slice_with_plan(&source, &plan, &empty)).expect("delete")
2191        else {
2192            panic!("expected gpu tensor");
2193        };
2194        assert_eq!(
2195            runmat_accelerate_api::handle_integer_type(&updated),
2196            Some(runmat_accelerate_api::IntegerElementType::U64)
2197        );
2198        let host = block_on(provider.download_integer(&updated)).expect("download updated");
2199        assert_eq!(host.shape, vec![1, 2]);
2200        assert_eq!(
2201            host.data,
2202            runmat_accelerate_api::HostIntegerDataOwned::U64(vec![1, u64::MAX])
2203        );
2204    }
2205
2206    #[test]
2207    fn complex_rhs_view_shape_mismatch_reports_identifier() {
2208        let rhs = Value::ComplexTensor(
2209            ComplexTensor::new(vec![(1.0, 0.0), (2.0, 0.0), (3.0, 0.0)], vec![1, 3])
2210                .expect("complex tensor"),
2211        );
2212        let err = match build_complex_rhs_view(&rhs, &[2, 2]) {
2213            Ok(_) => panic!("shape mismatch should fail"),
2214            Err(err) => err,
2215        };
2216        assert_eq!(err.identifier(), Some("RunMat:ShapeMismatch"));
2217    }
2218
2219    #[test]
2220    fn complex_rhs_view_invalid_rhs_type_reports_identifier() {
2221        let rhs = Value::String("x".to_string());
2222        let err = match build_complex_rhs_view(&rhs, &[1]) {
2223            Ok(_) => panic!("non-numeric rhs should be rejected"),
2224            Err(err) => err,
2225        };
2226        assert_eq!(err.identifier(), Some("RunMat:InvalidSliceAssignmentRhs"));
2227    }
2228
2229    #[test]
2230    fn complex_rhs_view_reads_all_typed_integer_classes_without_f64_mirrors() {
2231        macro_rules! assert_typed_rhs {
2232            ($storage:ident, $real:expr, $imag:expr) => {{
2233                let rhs = ComplexTensor::new_integer(
2234                    IntegerComplexStorage::new(
2235                        IntegerStorage::$storage(vec![$real]),
2236                        IntegerStorage::$storage(vec![$imag]),
2237                    )
2238                    .expect("typed integer components"),
2239                    vec![1, 1],
2240                )
2241                .expect("typed complex rhs");
2242
2243                let ComplexRhsView::Tensor { data, .. } =
2244                    build_complex_rhs_view(&Value::ComplexTensor(rhs), &[1])
2245                        .expect("typed complex rhs view")
2246                else {
2247                    panic!("complex tensor rhs must produce a tensor view");
2248                };
2249                assert_eq!(data, vec![($real as f64, $imag as f64)]);
2250            }};
2251        }
2252
2253        assert_typed_rhs!(I8, i8::MIN, i8::MAX);
2254        assert_typed_rhs!(I16, i16::MIN, i16::MAX);
2255        assert_typed_rhs!(I32, i32::MIN, i32::MAX);
2256        assert_typed_rhs!(I64, i64::MIN, i64::MAX);
2257        assert_typed_rhs!(U8, u8::MIN, u8::MAX);
2258        assert_typed_rhs!(U16, u16::MIN, u16::MAX);
2259        assert_typed_rhs!(U32, u32::MIN, u32::MAX);
2260        assert_typed_rhs!(U64, u64::MIN, u64::MAX);
2261    }
2262
2263    #[test]
2264    fn string_rhs_view_shape_mismatch_reports_identifier() {
2265        let rhs = Value::StringArray(
2266            StringArray::new(
2267                vec!["a".to_string(), "b".to_string(), "c".to_string()],
2268                vec![1, 3],
2269            )
2270            .expect("string array"),
2271        );
2272        let err = match build_string_rhs_view(&rhs, &[2, 2]) {
2273            Ok(_) => panic!("shape mismatch should fail"),
2274            Err(err) => err,
2275        };
2276        assert_eq!(err.identifier(), Some("RunMat:ShapeMismatch"));
2277    }
2278
2279    #[test]
2280    fn string_cell_rhs_view_rejects_shape_data_length_mismatch() {
2281        let data = ["a", "b", "c"]
2282            .into_iter()
2283            .map(|text| Value::String(text.to_string()))
2284            .collect();
2285        let rhs = Value::Cell(CellArray {
2286            data,
2287            shape: vec![2, 2],
2288            rows: 2,
2289            cols: 2,
2290        });
2291        let err = match build_string_rhs_view(&rhs, &[2, 2]) {
2292            Ok(_) => panic!("cell shape/data mismatch should fail"),
2293            Err(err) => err,
2294        };
2295        assert_eq!(err.identifier(), Some("RunMat:ShapeMismatch"));
2296    }
2297
2298    #[test]
2299    fn string_cell_rhs_uses_character_contents_not_display_layout() {
2300        let rhs = Value::Cell(CellArray {
2301            data: vec![
2302                Value::CharArray(runmat_value::CharArray::new_row("AvgOrders")),
2303                Value::CharArray(runmat_value::CharArray::new_row("AvgRevenue")),
2304            ],
2305            shape: vec![1, 2],
2306            rows: 1,
2307            cols: 2,
2308        });
2309        let view = build_string_rhs_view(&rhs, &[2]).expect("build string RHS view");
2310        let StringRhsView::Tensor { data, .. } = view else {
2311            panic!("cellstr RHS should materialize as a string tensor");
2312        };
2313        assert_eq!(data, vec!["AvgOrders", "AvgRevenue"]);
2314    }
2315
2316    #[test]
2317    fn string_rhs_view_invalid_rhs_type_reports_identifier() {
2318        let rhs = Value::Tensor(Tensor::new(vec![1.0], vec![1, 1]).expect("tensor"));
2319        let err = match build_string_rhs_view(&rhs, &[1]) {
2320            Ok(_) => panic!("non-string rhs should be rejected"),
2321            Err(err) => err,
2322        };
2323        assert_eq!(err.identifier(), Some("RunMat:InvalidSliceAssignmentRhs"));
2324    }
2325
2326    #[test]
2327    fn slice_acceleration_error_mapping_reports_identifier() {
2328        let err = map_acceleration_error("slice assign", "provider failed");
2329        assert_eq!(err.identifier(), Some("RunMat:AccelerationOperationFailed"));
2330    }
2331}