Skip to main content

runmat_runtime/builtins/array/sorting_sets/
unique.rs

1//! MATLAB-compatible `unique` builtin with GPU-aware semantics for RunMat.
2//!
3//! The implementation mirrors MathWorks MATLAB behavioural details for sorted
4//! and stable orderings, row-wise uniqueness, and index outputs. GPU tensors
5//! are gathered to host memory today, but the builtin is registered as a sink
6//! so future providers can add device-side kernels without impacting callers.
7
8use std::cmp::Ordering;
9use std::collections::HashMap;
10
11use runmat_accelerate_api::{
12    GpuTensorHandle, GpuTensorStorage, HostTensorOwned, UniqueOccurrence, UniqueOptions,
13    UniqueOrder, UniqueResult,
14};
15use runmat_builtins::{
16    BuiltinCompletionPolicy, BuiltinDescriptor, BuiltinErrorDescriptor, BuiltinOutputMode,
17    BuiltinParamArity, BuiltinParamDescriptor, BuiltinParamType, BuiltinSignatureDescriptor,
18    CharArray, ComplexTensor, StringArray, Tensor, Value,
19};
20use runmat_macros::runtime_builtin;
21
22use super::type_resolvers::set_values_output_type;
23use crate::build_runtime_error;
24use crate::builtins::common::arg_tokens::tokens_from_values;
25use crate::builtins::common::gpu_helpers;
26use crate::builtins::common::random_args::complex_tensor_into_value;
27use crate::builtins::common::spec::{
28    BroadcastSemantics, BuiltinFusionSpec, BuiltinGpuSpec, ConstantStrategy, GpuOpKind,
29    ProviderHook, ReductionNaN, ResidencyPolicy, ScalarType, ShapeRequirements,
30};
31use crate::builtins::common::tensor;
32
33#[runmat_macros::register_gpu_spec(builtin_path = "crate::builtins::array::sorting_sets::unique")]
34pub const GPU_SPEC: BuiltinGpuSpec = BuiltinGpuSpec {
35    name: "unique",
36    op_kind: GpuOpKind::Custom("unique"),
37    supported_precisions: &[ScalarType::F32, ScalarType::F64],
38    broadcast: BroadcastSemantics::None,
39    provider_hooks: &[ProviderHook::Custom("unique")],
40    constant_strategy: ConstantStrategy::InlineLiteral,
41    residency: ResidencyPolicy::GatherImmediately,
42    nan_mode: ReductionNaN::Include,
43    two_pass_threshold: None,
44    workgroup_size: None,
45    accepts_nan_mode: true,
46    notes: "Providers may implement the `unique` hook; default providers download tensors and reuse the CPU implementation.",
47};
48
49#[runmat_macros::register_fusion_spec(
50    builtin_path = "crate::builtins::array::sorting_sets::unique"
51)]
52pub const FUSION_SPEC: BuiltinFusionSpec = BuiltinFusionSpec {
53    name: "unique",
54    shape: ShapeRequirements::Any,
55    constant_strategy: ConstantStrategy::InlineLiteral,
56    elementwise: None,
57    reduction: None,
58    emits_nan: true,
59    notes: "`unique` terminates fusion chains and materialises results on the host; upstream tensors are gathered when necessary.",
60};
61
62const BUILTIN_NAME: &str = "unique";
63
64const UNIQUE_OUTPUT_C: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
65    name: "C",
66    ty: BuiltinParamType::Any,
67    arity: BuiltinParamArity::Required,
68    default: None,
69    description: "Unique values or rows.",
70}];
71
72const UNIQUE_OUTPUT_C_IA: [BuiltinParamDescriptor; 2] = [
73    BuiltinParamDescriptor {
74        name: "C",
75        ty: BuiltinParamType::Any,
76        arity: BuiltinParamArity::Required,
77        default: None,
78        description: "Unique values or rows.",
79    },
80    BuiltinParamDescriptor {
81        name: "ia",
82        ty: BuiltinParamType::NumericArray,
83        arity: BuiltinParamArity::Required,
84        default: None,
85        description: "Indices selecting representatives in input A.",
86    },
87];
88
89const UNIQUE_OUTPUT_C_IA_IC: [BuiltinParamDescriptor; 3] = [
90    BuiltinParamDescriptor {
91        name: "C",
92        ty: BuiltinParamType::Any,
93        arity: BuiltinParamArity::Required,
94        default: None,
95        description: "Unique values or rows.",
96    },
97    BuiltinParamDescriptor {
98        name: "ia",
99        ty: BuiltinParamType::NumericArray,
100        arity: BuiltinParamArity::Required,
101        default: None,
102        description: "Indices selecting representatives in input A.",
103    },
104    BuiltinParamDescriptor {
105        name: "ic",
106        ty: BuiltinParamType::NumericArray,
107        arity: BuiltinParamArity::Required,
108        default: None,
109        description: "Indices mapping each input element/row to C.",
110    },
111];
112
113const UNIQUE_INPUTS_A: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
114    name: "A",
115    ty: BuiltinParamType::Any,
116    arity: BuiltinParamArity::Required,
117    default: None,
118    description: "Input array.",
119}];
120
121const UNIQUE_INPUTS_A_OPTIONS: [BuiltinParamDescriptor; 2] = [
122    BuiltinParamDescriptor {
123        name: "A",
124        ty: BuiltinParamType::Any,
125        arity: BuiltinParamArity::Required,
126        default: None,
127        description: "Input array.",
128    },
129    BuiltinParamDescriptor {
130        name: "option",
131        ty: BuiltinParamType::StringScalar,
132        arity: BuiltinParamArity::Variadic,
133        default: None,
134        description: "Option tokens: 'sorted'|'stable'|'rows'|'first'|'last'.",
135    },
136];
137
138const UNIQUE_SIGNATURES: [BuiltinSignatureDescriptor; 6] = [
139    BuiltinSignatureDescriptor {
140        label: "C = unique(A)",
141        inputs: &UNIQUE_INPUTS_A,
142        outputs: &UNIQUE_OUTPUT_C,
143    },
144    BuiltinSignatureDescriptor {
145        label: "C = unique(A, option...)",
146        inputs: &UNIQUE_INPUTS_A_OPTIONS,
147        outputs: &UNIQUE_OUTPUT_C,
148    },
149    BuiltinSignatureDescriptor {
150        label: "[C, ia] = unique(A)",
151        inputs: &UNIQUE_INPUTS_A,
152        outputs: &UNIQUE_OUTPUT_C_IA,
153    },
154    BuiltinSignatureDescriptor {
155        label: "[C, ia] = unique(A, option...)",
156        inputs: &UNIQUE_INPUTS_A_OPTIONS,
157        outputs: &UNIQUE_OUTPUT_C_IA,
158    },
159    BuiltinSignatureDescriptor {
160        label: "[C, ia, ic] = unique(A)",
161        inputs: &UNIQUE_INPUTS_A,
162        outputs: &UNIQUE_OUTPUT_C_IA_IC,
163    },
164    BuiltinSignatureDescriptor {
165        label: "[C, ia, ic] = unique(A, option...)",
166        inputs: &UNIQUE_INPUTS_A_OPTIONS,
167        outputs: &UNIQUE_OUTPUT_C_IA_IC,
168    },
169];
170
171const UNIQUE_ERROR_LEGACY_OPTION_UNSUPPORTED: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
172    code: "RM.UNIQUE.LEGACY_OPTION_UNSUPPORTED",
173    identifier: Some("RunMat:unique:LegacyOptionUnsupported"),
174    when: "Legacy compatibility options are requested.",
175    message: "unique: the 'legacy' behaviour is not supported",
176};
177
178const UNIQUE_ERROR_CONFLICTING_ORDER_OPTIONS: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
179    code: "RM.UNIQUE.CONFLICTING_ORDER_OPTIONS",
180    identifier: Some("RunMat:unique:ConflictingOrderOptions"),
181    when: "Both 'sorted' and 'stable' options are provided.",
182    message: "unique: cannot combine 'sorted' with 'stable'",
183};
184
185const UNIQUE_ERROR_CONFLICTING_OCCURRENCE_OPTIONS: BuiltinErrorDescriptor =
186    BuiltinErrorDescriptor {
187        code: "RM.UNIQUE.CONFLICTING_OCCURRENCE_OPTIONS",
188        identifier: Some("RunMat:unique:ConflictingOccurrenceOptions"),
189        when: "Both 'first' and 'last' options are provided.",
190        message: "unique: cannot combine 'first' with 'last'",
191    };
192
193const UNIQUE_ERROR_UNKNOWN_OPTION: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
194    code: "RM.UNIQUE.UNKNOWN_OPTION",
195    identifier: Some("RunMat:unique:UnknownOption"),
196    when: "An unsupported option token is provided.",
197    message: "unique: unrecognised option",
198};
199
200const UNIQUE_ERROR_ROWS_REQUIRES_2D_MATRIX: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
201    code: "RM.UNIQUE.ROWS_REQUIRES_2D_MATRIX",
202    identifier: Some("RunMat:unique:RowsRequiresTwoDimensionalInput"),
203    when: "'rows' mode is used with non-2D data.",
204    message: "unique: 'rows' option requires a 2-D matrix input",
205};
206
207const UNIQUE_ERROR_UNSUPPORTED_INPUT_TYPE: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
208    code: "RM.UNIQUE.UNSUPPORTED_INPUT_TYPE",
209    identifier: Some("RunMat:unique:UnsupportedInputType"),
210    when: "Input cannot be converted into a supported unique domain.",
211    message: "unique: unsupported input type",
212};
213
214const UNIQUE_ERROR_INVALID_ARGUMENT: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
215    code: "RM.UNIQUE.INVALID_ARGUMENT",
216    identifier: Some("RunMat:unique:InvalidArgument"),
217    when: "Option arguments are not string-like where required.",
218    message: "unique: expected string option arguments",
219};
220
221const UNIQUE_ERROR_INTERNAL: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
222    code: "RM.UNIQUE.INTERNAL",
223    identifier: Some("RunMat:unique:Internal"),
224    when: "Internal conversion/allocation/provider decode fails.",
225    message: "unique: internal operation failed",
226};
227
228const UNIQUE_ERRORS: [BuiltinErrorDescriptor; 8] = [
229    UNIQUE_ERROR_LEGACY_OPTION_UNSUPPORTED,
230    UNIQUE_ERROR_CONFLICTING_ORDER_OPTIONS,
231    UNIQUE_ERROR_CONFLICTING_OCCURRENCE_OPTIONS,
232    UNIQUE_ERROR_UNKNOWN_OPTION,
233    UNIQUE_ERROR_ROWS_REQUIRES_2D_MATRIX,
234    UNIQUE_ERROR_UNSUPPORTED_INPUT_TYPE,
235    UNIQUE_ERROR_INVALID_ARGUMENT,
236    UNIQUE_ERROR_INTERNAL,
237];
238
239pub const UNIQUE_DESCRIPTOR: BuiltinDescriptor = BuiltinDescriptor {
240    signatures: &UNIQUE_SIGNATURES,
241    output_mode: BuiltinOutputMode::ByRequestedOutputCount,
242    completion_policy: BuiltinCompletionPolicy::Public,
243    errors: &UNIQUE_ERRORS,
244};
245
246fn unique_error_with(
247    error: &'static BuiltinErrorDescriptor,
248    message: impl Into<String>,
249) -> crate::RuntimeError {
250    let mut builder = build_runtime_error(message).with_builtin(BUILTIN_NAME);
251    if let Some(identifier) = error.identifier {
252        builder = builder.with_identifier(identifier);
253    }
254    builder.build()
255}
256
257fn unique_error(error: &'static BuiltinErrorDescriptor) -> crate::RuntimeError {
258    unique_error_with(error, error.message)
259}
260
261fn unique_internal_error(message: impl Into<String>) -> crate::RuntimeError {
262    unique_error_with(&UNIQUE_ERROR_INTERNAL, message)
263}
264
265#[runtime_builtin(
266    name = "unique",
267    category = "array/sorting_sets",
268    summary = "Return unique elements or rows with optional index mappings.",
269    keywords = "unique,set,distinct,stable,rows,indices,gpu",
270    accel = "array_construct",
271    sink = true,
272    type_resolver(set_values_output_type),
273    descriptor(crate::builtins::array::sorting_sets::unique::UNIQUE_DESCRIPTOR),
274    builtin_path = "crate::builtins::array::sorting_sets::unique"
275)]
276async fn unique_builtin(value: Value, rest: Vec<Value>) -> crate::BuiltinResult<Value> {
277    let eval = evaluate(value, &rest).await?;
278    if let Some(out_count) = crate::output_count::current_output_count() {
279        if out_count == 0 {
280            return Ok(Value::OutputList(Vec::new()));
281        }
282        if out_count == 1 {
283            return Ok(Value::OutputList(vec![eval.into_values_value()]));
284        }
285        if out_count == 2 {
286            let (values, ia) = eval.into_pair();
287            return Ok(Value::OutputList(vec![values, ia]));
288        }
289        let (values, ia, ic) = eval.into_triple();
290        return Ok(crate::output_count::output_list_with_padding(
291            out_count,
292            vec![values, ia, ic],
293        ));
294    }
295    Ok(eval.into_values_value())
296}
297
298/// Evaluate `unique` once and expose all outputs to the caller.
299pub async fn evaluate(value: Value, rest: &[Value]) -> crate::BuiltinResult<UniqueEvaluation> {
300    let opts = parse_options(rest)?;
301    match value {
302        Value::GpuTensor(handle) => unique_gpu(handle, &opts).await,
303        other => unique_host(other, &opts),
304    }
305}
306
307fn parse_options(rest: &[Value]) -> crate::BuiltinResult<UniqueOptions> {
308    let mut opts = UniqueOptions {
309        rows: false,
310        order: UniqueOrder::Sorted,
311        occurrence: UniqueOccurrence::First,
312    };
313    let mut seen_order: Option<UniqueOrder> = None;
314    let mut seen_occurrence: Option<UniqueOccurrence> = None;
315
316    let tokens = tokens_from_values(rest);
317    for (arg, token) in rest.iter().zip(tokens.iter()) {
318        let text = match token {
319            crate::builtins::common::arg_tokens::ArgToken::String(text) => text.as_str(),
320            _ => {
321                let text = tensor::value_to_string(arg)
322                    .ok_or_else(|| unique_error(&UNIQUE_ERROR_INVALID_ARGUMENT))?;
323                let lowered = text.trim().to_ascii_lowercase();
324                parse_unique_option(&mut opts, &mut seen_order, &mut seen_occurrence, &lowered)?;
325                continue;
326            }
327        };
328        parse_unique_option(&mut opts, &mut seen_order, &mut seen_occurrence, text)?;
329    }
330
331    Ok(opts)
332}
333
334fn parse_unique_option(
335    opts: &mut UniqueOptions,
336    seen_order: &mut Option<UniqueOrder>,
337    seen_occurrence: &mut Option<UniqueOccurrence>,
338    lowered: &str,
339) -> crate::BuiltinResult<()> {
340    match lowered {
341        "sorted" => {
342            if let Some(prev) = seen_order {
343                if *prev != UniqueOrder::Sorted {
344                    return Err(unique_error_with(
345                        &UNIQUE_ERROR_CONFLICTING_ORDER_OPTIONS,
346                        UNIQUE_ERROR_CONFLICTING_ORDER_OPTIONS.message,
347                    ));
348                }
349            }
350            *seen_order = Some(UniqueOrder::Sorted);
351            opts.order = UniqueOrder::Sorted;
352        }
353        "stable" => {
354            if let Some(prev) = seen_order {
355                if *prev != UniqueOrder::Stable {
356                    return Err(unique_error_with(
357                        &UNIQUE_ERROR_CONFLICTING_ORDER_OPTIONS,
358                        UNIQUE_ERROR_CONFLICTING_ORDER_OPTIONS.message,
359                    ));
360                }
361            }
362            *seen_order = Some(UniqueOrder::Stable);
363            opts.order = UniqueOrder::Stable;
364        }
365        "rows" => {
366            opts.rows = true;
367        }
368        "first" => {
369            if let Some(prev) = seen_occurrence {
370                if *prev != UniqueOccurrence::First {
371                    return Err(unique_error_with(
372                        &UNIQUE_ERROR_CONFLICTING_OCCURRENCE_OPTIONS,
373                        UNIQUE_ERROR_CONFLICTING_OCCURRENCE_OPTIONS.message,
374                    ));
375                }
376            }
377            *seen_occurrence = Some(UniqueOccurrence::First);
378            opts.occurrence = UniqueOccurrence::First;
379        }
380        "last" => {
381            if let Some(prev) = seen_occurrence {
382                if *prev != UniqueOccurrence::Last {
383                    return Err(unique_error_with(
384                        &UNIQUE_ERROR_CONFLICTING_OCCURRENCE_OPTIONS,
385                        UNIQUE_ERROR_CONFLICTING_OCCURRENCE_OPTIONS.message,
386                    ));
387                }
388            }
389            *seen_occurrence = Some(UniqueOccurrence::Last);
390            opts.occurrence = UniqueOccurrence::Last;
391        }
392        "legacy" | "r2012a" => {
393            return Err(unique_error(&UNIQUE_ERROR_LEGACY_OPTION_UNSUPPORTED));
394        }
395        other => {
396            return Err(unique_error_with(
397                &UNIQUE_ERROR_UNKNOWN_OPTION,
398                format!("unique: unrecognised option '{other}'"),
399            ));
400        }
401    }
402    Ok(())
403}
404
405async fn unique_gpu(
406    handle: GpuTensorHandle,
407    opts: &UniqueOptions,
408) -> crate::BuiltinResult<UniqueEvaluation> {
409    if let Some(provider) = runmat_accelerate_api::provider() {
410        if let Ok(result) = provider.unique(&handle, opts).await {
411            return UniqueEvaluation::from_unique_result(result);
412        }
413    }
414    let tensor = gpu_helpers::gather_tensor_async(&handle).await?;
415    unique_numeric_from_tensor(tensor, opts)
416}
417
418fn unique_host(value: Value, opts: &UniqueOptions) -> crate::BuiltinResult<UniqueEvaluation> {
419    match value {
420        Value::Tensor(tensor) => unique_numeric_from_tensor(tensor, opts),
421        Value::Num(n) => {
422            let tensor = Tensor::new(vec![n], vec![1, 1]).map_err(|e| unique_internal_error(format!("unique: {e}")))?;
423            unique_numeric_from_tensor(tensor, opts)
424        }
425        Value::Int(i) => {
426            let tensor = Tensor::new(vec![i.to_f64()], vec![1, 1])
427                .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
428            unique_numeric_from_tensor(tensor, opts)
429        }
430        Value::Bool(b) => {
431            let tensor = Tensor::new(vec![if b { 1.0 } else { 0.0 }], vec![1, 1])
432                .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
433            unique_numeric_from_tensor(tensor, opts)
434        }
435        Value::LogicalArray(logical) => {
436            let tensor = tensor::logical_to_tensor(&logical)
437                .map_err(|e| unique_internal_error(e))?;
438            unique_numeric_from_tensor(tensor, opts)
439        }
440        Value::ComplexTensor(tensor) => unique_complex_from_tensor(tensor, opts),
441        Value::Complex(re, im) => {
442            let tensor = ComplexTensor::new(vec![(re, im)], vec![1, 1])
443                .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
444            unique_complex_from_tensor(tensor, opts)
445        }
446        Value::CharArray(array) => unique_char_array(array, opts),
447        Value::StringArray(array) => unique_string_array(array, opts),
448        Value::String(s) => {
449            let array = StringArray::new(vec![s], vec![1, 1]).map_err(|e| unique_internal_error(format!("unique: {e}")))?;
450            unique_string_array(array, opts)
451        }
452        other => Err(unique_error_with(
453            &UNIQUE_ERROR_UNSUPPORTED_INPUT_TYPE,
454            format!(
455                "unique: unsupported input type {:?}; expected numeric, logical, char, string, or complex values",
456                other
457            ),
458        )),
459    }
460}
461
462pub fn unique_numeric_from_tensor(
463    tensor: Tensor,
464    opts: &UniqueOptions,
465) -> crate::BuiltinResult<UniqueEvaluation> {
466    if opts.rows {
467        unique_numeric_rows(tensor, opts)
468    } else {
469        unique_numeric_elements(tensor, opts)
470    }
471}
472
473fn unique_numeric_elements(
474    tensor: Tensor,
475    opts: &UniqueOptions,
476) -> crate::BuiltinResult<UniqueEvaluation> {
477    let len = tensor.data.len();
478    if len == 0 {
479        let values = Tensor::new(Vec::new(), vec![0, 1])
480            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
481        let ia = Tensor::new(Vec::new(), vec![0, 1])
482            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
483        let ic = Tensor::new(Vec::new(), vec![0, 1])
484            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
485        return Ok(UniqueEvaluation::new(
486            tensor::tensor_into_value(values),
487            ia,
488            ic,
489        ));
490    }
491
492    let mut entries = Vec::<NumericElementEntry>::new();
493    let mut map: HashMap<u64, usize> = HashMap::new();
494    let mut element_entry_index = Vec::with_capacity(len);
495
496    for (idx, &value) in tensor.data.iter().enumerate() {
497        let key = canonicalize_f64(value);
498        match map.get(&key) {
499            Some(&entry_idx) => {
500                entries[entry_idx].last = idx;
501                element_entry_index.push(entry_idx);
502            }
503            None => {
504                let entry_idx = entries.len();
505                entries.push(NumericElementEntry {
506                    value,
507                    first: idx,
508                    last: idx,
509                });
510                map.insert(key, entry_idx);
511                element_entry_index.push(entry_idx);
512            }
513        }
514    }
515
516    let mut order: Vec<usize> = (0..entries.len()).collect();
517    if opts.order == UniqueOrder::Sorted {
518        order.sort_by(|&a, &b| compare_f64(entries[a].value, entries[b].value));
519    }
520
521    let mut entry_to_position = vec![0usize; entries.len()];
522    for (pos, &entry_idx) in order.iter().enumerate() {
523        entry_to_position[entry_idx] = pos;
524    }
525
526    let mut values = Vec::with_capacity(order.len());
527    let mut ia = Vec::with_capacity(order.len());
528    for &entry_idx in &order {
529        let entry = &entries[entry_idx];
530        values.push(entry.value);
531        let occurrence = match opts.occurrence {
532            UniqueOccurrence::First => entry.first,
533            UniqueOccurrence::Last => entry.last,
534        };
535        ia.push((occurrence + 1) as f64);
536    }
537
538    let mut ic = Vec::with_capacity(len);
539    for entry_idx in element_entry_index {
540        let pos = entry_to_position[entry_idx];
541        ic.push((pos + 1) as f64);
542    }
543
544    let value_tensor = Tensor::new(values, vec![order.len(), 1])
545        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
546    let ia_tensor = Tensor::new(ia, vec![order.len(), 1])
547        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
548    let ic_tensor =
549        Tensor::new(ic, vec![len, 1]).map_err(|e| unique_internal_error(format!("unique: {e}")))?;
550
551    Ok(UniqueEvaluation::new(
552        tensor::tensor_into_value(value_tensor),
553        ia_tensor,
554        ic_tensor,
555    ))
556}
557
558fn unique_numeric_rows(
559    tensor: Tensor,
560    opts: &UniqueOptions,
561) -> crate::BuiltinResult<UniqueEvaluation> {
562    if tensor.shape.len() != 2 {
563        return Err(unique_error_with(
564            &UNIQUE_ERROR_ROWS_REQUIRES_2D_MATRIX,
565            UNIQUE_ERROR_ROWS_REQUIRES_2D_MATRIX.message,
566        ));
567    }
568    let rows = tensor.shape[0];
569    let cols = tensor.shape[1];
570
571    if rows == 0 || cols == 0 {
572        let values = Tensor::new(Vec::new(), vec![0, cols])
573            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
574        let ia = Tensor::new(Vec::new(), vec![0, 1])
575            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
576        let ic = Tensor::new(Vec::new(), vec![rows, 1])
577            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
578        return Ok(UniqueEvaluation::new(
579            tensor::tensor_into_value(values),
580            ia,
581            ic,
582        ));
583    }
584
585    let mut entries = Vec::<NumericRowEntry>::new();
586    let mut map: HashMap<NumericRowKey, usize> = HashMap::new();
587    let mut row_entry_index = Vec::with_capacity(rows);
588
589    for r in 0..rows {
590        let mut row_values = Vec::with_capacity(cols);
591        for c in 0..cols {
592            let idx = r + c * rows;
593            row_values.push(tensor.data[idx]);
594        }
595        let key = NumericRowKey::from_slice(&row_values);
596        match map.get(&key) {
597            Some(&entry_idx) => {
598                entries[entry_idx].last = r;
599                row_entry_index.push(entry_idx);
600            }
601            None => {
602                let entry_idx = entries.len();
603                entries.push(NumericRowEntry {
604                    row_data: row_values.clone(),
605                    first: r,
606                    last: r,
607                });
608                map.insert(key, entry_idx);
609                row_entry_index.push(entry_idx);
610            }
611        }
612    }
613
614    let mut order: Vec<usize> = (0..entries.len()).collect();
615    if opts.order == UniqueOrder::Sorted {
616        order.sort_by(|&a, &b| compare_numeric_rows(&entries[a].row_data, &entries[b].row_data));
617    }
618
619    let mut entry_to_position = vec![0usize; entries.len()];
620    for (pos, &entry_idx) in order.iter().enumerate() {
621        entry_to_position[entry_idx] = pos;
622    }
623
624    let unique_rows_count = order.len();
625    let mut values = vec![0.0f64; unique_rows_count * cols];
626    for (row_pos, &entry_idx) in order.iter().enumerate() {
627        let row = &entries[entry_idx].row_data;
628        for (col, value) in row.iter().enumerate().take(cols) {
629            let dest = row_pos + col * unique_rows_count;
630            values[dest] = *value;
631        }
632    }
633
634    let mut ia = Vec::with_capacity(unique_rows_count);
635    for &entry_idx in &order {
636        let entry = &entries[entry_idx];
637        let occurrence = match opts.occurrence {
638            UniqueOccurrence::First => entry.first,
639            UniqueOccurrence::Last => entry.last,
640        };
641        ia.push((occurrence + 1) as f64);
642    }
643
644    let mut ic = Vec::with_capacity(rows);
645    for entry_idx in row_entry_index {
646        let pos = entry_to_position[entry_idx];
647        ic.push((pos + 1) as f64);
648    }
649
650    let value_tensor = Tensor::new(values, vec![unique_rows_count, cols])
651        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
652    let ia_tensor = Tensor::new(ia, vec![unique_rows_count, 1])
653        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
654    let ic_tensor = Tensor::new(ic, vec![rows, 1])
655        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
656
657    Ok(UniqueEvaluation::new(
658        tensor::tensor_into_value(value_tensor),
659        ia_tensor,
660        ic_tensor,
661    ))
662}
663
664fn unique_complex_from_tensor(
665    tensor: ComplexTensor,
666    opts: &UniqueOptions,
667) -> crate::BuiltinResult<UniqueEvaluation> {
668    if opts.rows {
669        unique_complex_rows(tensor, opts)
670    } else {
671        unique_complex_elements(tensor, opts)
672    }
673}
674
675fn unique_complex_elements(
676    tensor: ComplexTensor,
677    opts: &UniqueOptions,
678) -> crate::BuiltinResult<UniqueEvaluation> {
679    let len = tensor.data.len();
680    if len == 0 {
681        let values = ComplexTensor::new(Vec::new(), vec![0, 1])
682            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
683        let ia = Tensor::new(Vec::new(), vec![0, 1])
684            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
685        let ic = Tensor::new(Vec::new(), vec![0, 1])
686            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
687        return Ok(UniqueEvaluation::new(
688            complex_tensor_into_value(values),
689            ia,
690            ic,
691        ));
692    }
693
694    let mut entries = Vec::<ComplexElementEntry>::new();
695    let mut map: HashMap<ComplexKey, usize> = HashMap::new();
696    let mut element_entry_index = Vec::with_capacity(len);
697
698    for (idx, &value) in tensor.data.iter().enumerate() {
699        let key = ComplexKey::new(value);
700        match map.get(&key) {
701            Some(&entry_idx) => {
702                entries[entry_idx].last = idx;
703                element_entry_index.push(entry_idx);
704            }
705            None => {
706                let entry_idx = entries.len();
707                entries.push(ComplexElementEntry {
708                    value,
709                    first: idx,
710                    last: idx,
711                });
712                map.insert(key, entry_idx);
713                element_entry_index.push(entry_idx);
714            }
715        }
716    }
717
718    let mut order: Vec<usize> = (0..entries.len()).collect();
719    if opts.order == UniqueOrder::Sorted {
720        order.sort_by(|&a, &b| compare_complex(entries[a].value, entries[b].value));
721    }
722
723    let mut entry_to_position = vec![0usize; entries.len()];
724    for (pos, &entry_idx) in order.iter().enumerate() {
725        entry_to_position[entry_idx] = pos;
726    }
727
728    let mut values = Vec::with_capacity(order.len());
729    let mut ia = Vec::with_capacity(order.len());
730    for &entry_idx in &order {
731        let entry = &entries[entry_idx];
732        values.push(entry.value);
733        let occurrence = match opts.occurrence {
734            UniqueOccurrence::First => entry.first,
735            UniqueOccurrence::Last => entry.last,
736        };
737        ia.push((occurrence + 1) as f64);
738    }
739
740    let mut ic = Vec::with_capacity(len);
741    for entry_idx in element_entry_index {
742        let pos = entry_to_position[entry_idx];
743        ic.push((pos + 1) as f64);
744    }
745
746    let value_tensor = ComplexTensor::new(values, vec![order.len(), 1])
747        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
748    let ia_tensor = Tensor::new(ia, vec![order.len(), 1])
749        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
750    let ic_tensor =
751        Tensor::new(ic, vec![len, 1]).map_err(|e| unique_internal_error(format!("unique: {e}")))?;
752
753    Ok(UniqueEvaluation::new(
754        complex_tensor_into_value(value_tensor),
755        ia_tensor,
756        ic_tensor,
757    ))
758}
759
760fn unique_complex_rows(
761    tensor: ComplexTensor,
762    opts: &UniqueOptions,
763) -> crate::BuiltinResult<UniqueEvaluation> {
764    if tensor.shape.len() != 2 {
765        return Err(unique_error_with(
766            &UNIQUE_ERROR_ROWS_REQUIRES_2D_MATRIX,
767            UNIQUE_ERROR_ROWS_REQUIRES_2D_MATRIX.message,
768        ));
769    }
770    let rows = tensor.shape[0];
771    let cols = tensor.shape[1];
772
773    if rows == 0 || cols == 0 {
774        let values = ComplexTensor::new(Vec::new(), vec![rows, cols])
775            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
776        let ia = Tensor::new(Vec::new(), vec![0, 1])
777            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
778        let ic = Tensor::new(Vec::new(), vec![rows, 1])
779            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
780        return Ok(UniqueEvaluation::new(
781            complex_tensor_into_value(values),
782            ia,
783            ic,
784        ));
785    }
786
787    let mut entries = Vec::<ComplexRowEntry>::new();
788    let mut map: HashMap<Vec<ComplexKey>, usize> = HashMap::new();
789    let mut row_entry_index = Vec::with_capacity(rows);
790
791    for r in 0..rows {
792        let mut row_values = Vec::with_capacity(cols);
793        let mut key_row = Vec::with_capacity(cols);
794        for c in 0..cols {
795            let idx = r + c * rows;
796            let value = tensor.data[idx];
797            row_values.push(value);
798            key_row.push(ComplexKey::new(value));
799        }
800        match map.get(&key_row) {
801            Some(&entry_idx) => {
802                entries[entry_idx].last = r;
803                row_entry_index.push(entry_idx);
804            }
805            None => {
806                let entry_idx = entries.len();
807                entries.push(ComplexRowEntry {
808                    row_data: row_values.clone(),
809                    first: r,
810                    last: r,
811                });
812                map.insert(key_row, entry_idx);
813                row_entry_index.push(entry_idx);
814            }
815        }
816    }
817
818    let mut order: Vec<usize> = (0..entries.len()).collect();
819    if opts.order == UniqueOrder::Sorted {
820        order.sort_by(|&a, &b| compare_complex_rows(&entries[a].row_data, &entries[b].row_data));
821    }
822
823    let mut entry_to_position = vec![0usize; entries.len()];
824    for (pos, &entry_idx) in order.iter().enumerate() {
825        entry_to_position[entry_idx] = pos;
826    }
827
828    let unique_rows_count = order.len();
829    let mut values = vec![(0.0, 0.0); unique_rows_count * cols];
830    for (row_pos, &entry_idx) in order.iter().enumerate() {
831        let row = &entries[entry_idx].row_data;
832        for (col, value) in row.iter().enumerate().take(cols) {
833            let dest = row_pos + col * unique_rows_count;
834            values[dest] = *value;
835        }
836    }
837
838    let mut ia = Vec::with_capacity(unique_rows_count);
839    for &entry_idx in &order {
840        let entry = &entries[entry_idx];
841        let occurrence = match opts.occurrence {
842            UniqueOccurrence::First => entry.first,
843            UniqueOccurrence::Last => entry.last,
844        };
845        ia.push((occurrence + 1) as f64);
846    }
847
848    let mut ic = Vec::with_capacity(rows);
849    for entry_idx in row_entry_index {
850        let pos = entry_to_position[entry_idx];
851        ic.push((pos + 1) as f64);
852    }
853
854    let value_tensor = ComplexTensor::new(values, vec![unique_rows_count, cols])
855        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
856    let ia_tensor = Tensor::new(ia, vec![unique_rows_count, 1])
857        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
858    let ic_tensor = Tensor::new(ic, vec![rows, 1])
859        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
860
861    Ok(UniqueEvaluation::new(
862        complex_tensor_into_value(value_tensor),
863        ia_tensor,
864        ic_tensor,
865    ))
866}
867
868fn unique_char_array(
869    array: CharArray,
870    opts: &UniqueOptions,
871) -> crate::BuiltinResult<UniqueEvaluation> {
872    if opts.rows {
873        unique_char_rows(array, opts)
874    } else {
875        unique_char_elements(array, opts)
876    }
877}
878
879fn unique_char_elements(
880    array: CharArray,
881    opts: &UniqueOptions,
882) -> crate::BuiltinResult<UniqueEvaluation> {
883    let rows = array.rows;
884    let cols = array.cols;
885    let total = rows * cols;
886    if total == 0 {
887        let values = CharArray::new(Vec::new(), 0, 0)
888            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
889        let ia = Tensor::new(Vec::new(), vec![0, 1])
890            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
891        let ic = Tensor::new(Vec::new(), vec![0, 1])
892            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
893        return Ok(UniqueEvaluation::new(Value::CharArray(values), ia, ic));
894    }
895
896    let mut entries = Vec::<CharElementEntry>::new();
897    let mut map: HashMap<u32, usize> = HashMap::new();
898    let mut element_entry_index = Vec::with_capacity(total);
899
900    for col in 0..cols {
901        for row in 0..rows {
902            let linear_idx = row + col * rows;
903            let data_idx = row * cols + col;
904            let ch = array.data[data_idx];
905            let key = ch as u32;
906            match map.get(&key) {
907                Some(&entry_idx) => {
908                    entries[entry_idx].last = linear_idx;
909                    element_entry_index.push(entry_idx);
910                }
911                None => {
912                    let entry_idx = entries.len();
913                    entries.push(CharElementEntry {
914                        ch,
915                        first: linear_idx,
916                        last: linear_idx,
917                    });
918                    map.insert(key, entry_idx);
919                    element_entry_index.push(entry_idx);
920                }
921            }
922        }
923    }
924
925    let mut order: Vec<usize> = (0..entries.len()).collect();
926    if opts.order == UniqueOrder::Sorted {
927        order.sort_by(|&a, &b| entries[a].ch.cmp(&entries[b].ch));
928    }
929
930    let mut entry_to_position = vec![0usize; entries.len()];
931    for (pos, &entry_idx) in order.iter().enumerate() {
932        entry_to_position[entry_idx] = pos;
933    }
934
935    let mut values = Vec::with_capacity(order.len());
936    let mut ia = Vec::with_capacity(order.len());
937    for &entry_idx in &order {
938        let entry = &entries[entry_idx];
939        values.push(entry.ch);
940        let occurrence = match opts.occurrence {
941            UniqueOccurrence::First => entry.first,
942            UniqueOccurrence::Last => entry.last,
943        };
944        ia.push((occurrence + 1) as f64);
945    }
946
947    let mut ic = Vec::with_capacity(total);
948    for entry_idx in element_entry_index {
949        let pos = entry_to_position[entry_idx];
950        ic.push((pos + 1) as f64);
951    }
952
953    let value_array = CharArray::new(values, order.len(), 1)
954        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
955    let ia_tensor = Tensor::new(ia, vec![order.len(), 1])
956        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
957    let ic_tensor = Tensor::new(ic, vec![total, 1])
958        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
959
960    Ok(UniqueEvaluation::new(
961        Value::CharArray(value_array),
962        ia_tensor,
963        ic_tensor,
964    ))
965}
966
967fn unique_char_rows(
968    array: CharArray,
969    opts: &UniqueOptions,
970) -> crate::BuiltinResult<UniqueEvaluation> {
971    let rows = array.rows;
972    let cols = array.cols;
973    if rows == 0 {
974        let values = CharArray::new(Vec::new(), 0, cols)
975            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
976        let ia = Tensor::new(Vec::new(), vec![0, 1])
977            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
978        let ic = Tensor::new(Vec::new(), vec![0, 1])
979            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
980        return Ok(UniqueEvaluation::new(Value::CharArray(values), ia, ic));
981    }
982
983    let mut entries = Vec::<CharRowEntry>::new();
984    let mut map: HashMap<RowCharKey, usize> = HashMap::new();
985    let mut row_entry_index = Vec::with_capacity(rows);
986
987    for r in 0..rows {
988        let start = r * cols;
989        let end = start + cols;
990        let slice = &array.data[start..end];
991        let key = RowCharKey::from_slice(slice);
992        match map.get(&key) {
993            Some(&entry_idx) => {
994                entries[entry_idx].last = r;
995                row_entry_index.push(entry_idx);
996            }
997            None => {
998                let entry_idx = entries.len();
999                entries.push(CharRowEntry {
1000                    row_data: slice.to_vec(),
1001                    first: r,
1002                    last: r,
1003                });
1004                map.insert(key, entry_idx);
1005                row_entry_index.push(entry_idx);
1006            }
1007        }
1008    }
1009
1010    let mut order: Vec<usize> = (0..entries.len()).collect();
1011    if opts.order == UniqueOrder::Sorted {
1012        order.sort_by(|&a, &b| compare_char_rows(&entries[a].row_data, &entries[b].row_data));
1013    }
1014
1015    let mut entry_to_position = vec![0usize; entries.len()];
1016    for (pos, &entry_idx) in order.iter().enumerate() {
1017        entry_to_position[entry_idx] = pos;
1018    }
1019
1020    let unique_rows_count = order.len();
1021    let mut values = vec!['\0'; unique_rows_count * cols];
1022    for (row_pos, &entry_idx) in order.iter().enumerate() {
1023        let row = &entries[entry_idx].row_data;
1024        for col in 0..cols {
1025            let dest = row_pos * cols + col;
1026            if col < row.len() {
1027                values[dest] = row[col];
1028            }
1029        }
1030    }
1031
1032    let mut ia = Vec::with_capacity(unique_rows_count);
1033    for &entry_idx in &order {
1034        let entry = &entries[entry_idx];
1035        let occurrence = match opts.occurrence {
1036            UniqueOccurrence::First => entry.first,
1037            UniqueOccurrence::Last => entry.last,
1038        };
1039        ia.push((occurrence + 1) as f64);
1040    }
1041
1042    let mut ic = Vec::with_capacity(rows);
1043    for entry_idx in row_entry_index {
1044        let pos = entry_to_position[entry_idx];
1045        ic.push((pos + 1) as f64);
1046    }
1047
1048    let value_array = CharArray::new(values, unique_rows_count, cols)
1049        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1050    let ia_tensor = Tensor::new(ia, vec![unique_rows_count, 1])
1051        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1052    let ic_tensor = Tensor::new(ic, vec![rows, 1])
1053        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1054
1055    Ok(UniqueEvaluation::new(
1056        Value::CharArray(value_array),
1057        ia_tensor,
1058        ic_tensor,
1059    ))
1060}
1061
1062fn unique_string_array(
1063    array: StringArray,
1064    opts: &UniqueOptions,
1065) -> crate::BuiltinResult<UniqueEvaluation> {
1066    if opts.rows {
1067        unique_string_rows(array, opts)
1068    } else {
1069        unique_string_elements(array, opts)
1070    }
1071}
1072
1073fn unique_string_elements(
1074    array: StringArray,
1075    opts: &UniqueOptions,
1076) -> crate::BuiltinResult<UniqueEvaluation> {
1077    let len = array.data.len();
1078    if len == 0 {
1079        let values = StringArray::new(Vec::new(), vec![0, 1])
1080            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1081        let ia = Tensor::new(Vec::new(), vec![0, 1])
1082            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1083        let ic = Tensor::new(Vec::new(), vec![0, 1])
1084            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1085        return Ok(UniqueEvaluation::new(Value::StringArray(values), ia, ic));
1086    }
1087
1088    let mut entries = Vec::<StringElementEntry>::new();
1089    let mut map: HashMap<String, usize> = HashMap::new();
1090    let mut element_entry_index = Vec::with_capacity(len);
1091
1092    for (idx, value) in array.data.iter().enumerate() {
1093        match map.get(value) {
1094            Some(&entry_idx) => {
1095                entries[entry_idx].last = idx;
1096                element_entry_index.push(entry_idx);
1097            }
1098            None => {
1099                let entry_idx = entries.len();
1100                entries.push(StringElementEntry {
1101                    value: value.clone(),
1102                    first: idx,
1103                    last: idx,
1104                });
1105                map.insert(value.clone(), entry_idx);
1106                element_entry_index.push(entry_idx);
1107            }
1108        }
1109    }
1110
1111    let mut order: Vec<usize> = (0..entries.len()).collect();
1112    if opts.order == UniqueOrder::Sorted {
1113        order.sort_by(|&a, &b| entries[a].value.cmp(&entries[b].value));
1114    }
1115
1116    let mut entry_to_position = vec![0usize; entries.len()];
1117    for (pos, &entry_idx) in order.iter().enumerate() {
1118        entry_to_position[entry_idx] = pos;
1119    }
1120
1121    let mut values = Vec::with_capacity(order.len());
1122    let mut ia = Vec::with_capacity(order.len());
1123    for &entry_idx in &order {
1124        let entry = &entries[entry_idx];
1125        values.push(entry.value.clone());
1126        let occurrence = match opts.occurrence {
1127            UniqueOccurrence::First => entry.first,
1128            UniqueOccurrence::Last => entry.last,
1129        };
1130        ia.push((occurrence + 1) as f64);
1131    }
1132
1133    let mut ic = Vec::with_capacity(len);
1134    for entry_idx in element_entry_index {
1135        let pos = entry_to_position[entry_idx];
1136        ic.push((pos + 1) as f64);
1137    }
1138
1139    let value_array = StringArray::new(values, vec![order.len(), 1])
1140        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1141    let ia_tensor = Tensor::new(ia, vec![order.len(), 1])
1142        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1143    let ic_tensor =
1144        Tensor::new(ic, vec![len, 1]).map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1145
1146    Ok(UniqueEvaluation::new(
1147        Value::StringArray(value_array),
1148        ia_tensor,
1149        ic_tensor,
1150    ))
1151}
1152
1153fn unique_string_rows(
1154    array: StringArray,
1155    opts: &UniqueOptions,
1156) -> crate::BuiltinResult<UniqueEvaluation> {
1157    if array.shape.len() != 2 {
1158        return Err(unique_error_with(
1159            &UNIQUE_ERROR_ROWS_REQUIRES_2D_MATRIX,
1160            UNIQUE_ERROR_ROWS_REQUIRES_2D_MATRIX.message,
1161        ));
1162    }
1163    let rows = array.shape[0];
1164    let cols = array.shape[1];
1165
1166    if rows == 0 {
1167        let values = StringArray::new(Vec::new(), vec![0, cols])
1168            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1169        let ia = Tensor::new(Vec::new(), vec![0, 1])
1170            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1171        let ic = Tensor::new(Vec::new(), vec![0, 1])
1172            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1173        return Ok(UniqueEvaluation::new(Value::StringArray(values), ia, ic));
1174    }
1175
1176    let mut entries = Vec::<StringRowEntry>::new();
1177    let mut map: HashMap<RowStringKey, usize> = HashMap::new();
1178    let mut row_entry_index = Vec::with_capacity(rows);
1179
1180    for r in 0..rows {
1181        let mut row_values = Vec::with_capacity(cols);
1182        for c in 0..cols {
1183            let idx = r + c * rows;
1184            row_values.push(array.data[idx].clone());
1185        }
1186        let key = RowStringKey(row_values.clone());
1187        match map.get(&key) {
1188            Some(&entry_idx) => {
1189                entries[entry_idx].last = r;
1190                row_entry_index.push(entry_idx);
1191            }
1192            None => {
1193                let entry_idx = entries.len();
1194                entries.push(StringRowEntry {
1195                    row_data: row_values.clone(),
1196                    first: r,
1197                    last: r,
1198                });
1199                map.insert(key, entry_idx);
1200                row_entry_index.push(entry_idx);
1201            }
1202        }
1203    }
1204
1205    let mut order: Vec<usize> = (0..entries.len()).collect();
1206    if opts.order == UniqueOrder::Sorted {
1207        order.sort_by(|&a, &b| compare_string_rows(&entries[a].row_data, &entries[b].row_data));
1208    }
1209
1210    let mut entry_to_position = vec![0usize; entries.len()];
1211    for (pos, &entry_idx) in order.iter().enumerate() {
1212        entry_to_position[entry_idx] = pos;
1213    }
1214
1215    let unique_rows_count = order.len();
1216    let mut values = vec![String::new(); unique_rows_count * cols];
1217    for (row_pos, &entry_idx) in order.iter().enumerate() {
1218        let row = &entries[entry_idx].row_data;
1219        for (col, value) in row.iter().enumerate().take(cols) {
1220            let dest = row_pos + col * unique_rows_count;
1221            values[dest] = value.clone();
1222        }
1223    }
1224
1225    let mut ia = Vec::with_capacity(unique_rows_count);
1226    for &entry_idx in &order {
1227        let entry = &entries[entry_idx];
1228        let occurrence = match opts.occurrence {
1229            UniqueOccurrence::First => entry.first,
1230            UniqueOccurrence::Last => entry.last,
1231        };
1232        ia.push((occurrence + 1) as f64);
1233    }
1234
1235    let mut ic = Vec::with_capacity(rows);
1236    for entry_idx in row_entry_index {
1237        let pos = entry_to_position[entry_idx];
1238        ic.push((pos + 1) as f64);
1239    }
1240
1241    let value_array = StringArray::new(values, vec![unique_rows_count, cols])
1242        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1243    let ia_tensor = Tensor::new(ia, vec![unique_rows_count, 1])
1244        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1245    let ic_tensor = Tensor::new(ic, vec![rows, 1])
1246        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1247
1248    Ok(UniqueEvaluation::new(
1249        Value::StringArray(value_array),
1250        ia_tensor,
1251        ic_tensor,
1252    ))
1253}
1254
1255#[derive(Debug)]
1256struct NumericElementEntry {
1257    value: f64,
1258    first: usize,
1259    last: usize,
1260}
1261
1262#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1263struct NumericRowKey(Vec<u64>);
1264
1265impl NumericRowKey {
1266    fn from_slice(values: &[f64]) -> Self {
1267        NumericRowKey(values.iter().map(|&v| canonicalize_f64(v)).collect())
1268    }
1269}
1270
1271#[derive(Debug, Clone)]
1272struct NumericRowEntry {
1273    row_data: Vec<f64>,
1274    first: usize,
1275    last: usize,
1276}
1277
1278#[derive(Debug)]
1279struct ComplexElementEntry {
1280    value: (f64, f64),
1281    first: usize,
1282    last: usize,
1283}
1284
1285#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1286struct ComplexKey {
1287    re: u64,
1288    im: u64,
1289}
1290
1291impl ComplexKey {
1292    fn new(value: (f64, f64)) -> Self {
1293        Self {
1294            re: canonicalize_f64(value.0),
1295            im: canonicalize_f64(value.1),
1296        }
1297    }
1298}
1299
1300#[derive(Debug, Clone)]
1301struct ComplexRowEntry {
1302    row_data: Vec<(f64, f64)>,
1303    first: usize,
1304    last: usize,
1305}
1306
1307#[derive(Debug)]
1308struct CharElementEntry {
1309    ch: char,
1310    first: usize,
1311    last: usize,
1312}
1313
1314#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1315struct RowCharKey(Vec<u32>);
1316
1317impl RowCharKey {
1318    fn from_slice(values: &[char]) -> Self {
1319        RowCharKey(values.iter().map(|&ch| ch as u32).collect())
1320    }
1321}
1322
1323#[derive(Debug, Clone)]
1324struct CharRowEntry {
1325    row_data: Vec<char>,
1326    first: usize,
1327    last: usize,
1328}
1329
1330#[derive(Debug, Clone)]
1331struct StringElementEntry {
1332    value: String,
1333    first: usize,
1334    last: usize,
1335}
1336
1337#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1338struct RowStringKey(Vec<String>);
1339
1340#[derive(Debug, Clone)]
1341struct StringRowEntry {
1342    row_data: Vec<String>,
1343    first: usize,
1344    last: usize,
1345}
1346
1347fn canonicalize_f64(value: f64) -> u64 {
1348    if value.is_nan() {
1349        0x7ff8_0000_0000_0000u64
1350    } else if value == 0.0 {
1351        0u64
1352    } else {
1353        value.to_bits()
1354    }
1355}
1356
1357fn compare_f64(a: f64, b: f64) -> Ordering {
1358    if a.is_nan() {
1359        if b.is_nan() {
1360            Ordering::Equal
1361        } else {
1362            Ordering::Greater
1363        }
1364    } else if b.is_nan() {
1365        Ordering::Less
1366    } else {
1367        a.partial_cmp(&b).unwrap_or(Ordering::Equal)
1368    }
1369}
1370
1371fn compare_numeric_rows(a: &[f64], b: &[f64]) -> Ordering {
1372    for (lhs, rhs) in a.iter().zip(b.iter()) {
1373        let ord = compare_f64(*lhs, *rhs);
1374        if ord != Ordering::Equal {
1375            return ord;
1376        }
1377    }
1378    Ordering::Equal
1379}
1380
1381fn complex_is_nan(value: (f64, f64)) -> bool {
1382    value.0.is_nan() || value.1.is_nan()
1383}
1384
1385fn compare_complex(a: (f64, f64), b: (f64, f64)) -> Ordering {
1386    match (complex_is_nan(a), complex_is_nan(b)) {
1387        (true, true) => Ordering::Equal,
1388        (true, false) => Ordering::Greater,
1389        (false, true) => Ordering::Less,
1390        (false, false) => {
1391            let mag_a = a.0.hypot(a.1);
1392            let mag_b = b.0.hypot(b.1);
1393            let mag_cmp = compare_f64(mag_a, mag_b);
1394            if mag_cmp != Ordering::Equal {
1395                return mag_cmp;
1396            }
1397            let re_cmp = compare_f64(a.0, b.0);
1398            if re_cmp != Ordering::Equal {
1399                return re_cmp;
1400            }
1401            compare_f64(a.1, b.1)
1402        }
1403    }
1404}
1405
1406fn compare_complex_rows(a: &[(f64, f64)], b: &[(f64, f64)]) -> Ordering {
1407    for (lhs, rhs) in a.iter().zip(b.iter()) {
1408        let ord = compare_complex(*lhs, *rhs);
1409        if ord != Ordering::Equal {
1410            return ord;
1411        }
1412    }
1413    Ordering::Equal
1414}
1415
1416fn compare_char_rows(a: &[char], b: &[char]) -> Ordering {
1417    for (lhs, rhs) in a.iter().zip(b.iter()) {
1418        let ord = lhs.cmp(rhs);
1419        if ord != Ordering::Equal {
1420            return ord;
1421        }
1422    }
1423    Ordering::Equal
1424}
1425
1426fn compare_string_rows(a: &[String], b: &[String]) -> Ordering {
1427    for (lhs, rhs) in a.iter().zip(b.iter()) {
1428        let ord = lhs.cmp(rhs);
1429        if ord != Ordering::Equal {
1430            return ord;
1431        }
1432    }
1433    Ordering::Equal
1434}
1435
1436#[derive(Debug)]
1437pub struct UniqueEvaluation {
1438    values: Value,
1439    ia: Tensor,
1440    ic: Tensor,
1441}
1442
1443impl UniqueEvaluation {
1444    fn new(values: Value, ia: Tensor, ic: Tensor) -> Self {
1445        Self { values, ia, ic }
1446    }
1447
1448    pub fn into_values_value(self) -> Value {
1449        self.values
1450    }
1451
1452    pub fn into_pair(self) -> (Value, Value) {
1453        let ia = tensor::tensor_into_value(self.ia);
1454        (self.values, ia)
1455    }
1456
1457    pub fn into_triple(self) -> (Value, Value, Value) {
1458        let ia = tensor::tensor_into_value(self.ia);
1459        let ic = tensor::tensor_into_value(self.ic);
1460        (self.values, ia, ic)
1461    }
1462
1463    pub fn from_unique_result(result: UniqueResult) -> crate::BuiltinResult<Self> {
1464        let UniqueResult { values, ia, ic } = result;
1465        let values_tensor = Tensor::new(values.data, values.shape)
1466            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1467        let ia_tensor = Tensor::new(ia.data, ia.shape)
1468            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1469        let ic_tensor = Tensor::new(ic.data, ic.shape)
1470            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1471        Ok(UniqueEvaluation::new(
1472            tensor::tensor_into_value(values_tensor),
1473            ia_tensor,
1474            ic_tensor,
1475        ))
1476    }
1477
1478    pub fn into_numeric_unique_result(self) -> crate::BuiltinResult<UniqueResult> {
1479        let UniqueEvaluation { values, ia, ic } = self;
1480        let values_tensor = tensor::value_into_tensor_for("unique", values)
1481            .map_err(|e| unique_internal_error(e))?;
1482        Ok(UniqueResult {
1483            values: HostTensorOwned {
1484                data: values_tensor.data,
1485                shape: values_tensor.shape,
1486                storage: GpuTensorStorage::Real,
1487            },
1488            ia: HostTensorOwned {
1489                data: ia.data,
1490                shape: ia.shape,
1491                storage: GpuTensorStorage::Real,
1492            },
1493            ic: HostTensorOwned {
1494                data: ic.data,
1495                shape: ic.shape,
1496                storage: GpuTensorStorage::Real,
1497            },
1498        })
1499    }
1500
1501    pub fn ia_value(&self) -> Value {
1502        tensor::tensor_into_value(self.ia.clone())
1503    }
1504
1505    pub fn ic_value(&self) -> Value {
1506        tensor::tensor_into_value(self.ic.clone())
1507    }
1508}
1509
1510#[cfg(test)]
1511pub(crate) mod tests {
1512    use super::*;
1513    use crate::builtins::common::test_support;
1514    use runmat_builtins::{
1515        CharArray, IntValue, LogicalArray, ResolveContext, StringArray, Tensor, Type, Value,
1516    };
1517
1518    fn evaluate_sync(value: Value, rest: &[Value]) -> crate::BuiltinResult<UniqueEvaluation> {
1519        futures::executor::block_on(evaluate(value, rest))
1520    }
1521
1522    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1523    #[test]
1524    fn unique_sorted_default() {
1525        let tensor = Tensor::new(vec![3.0, 1.0, 3.0, 2.0], vec![4, 1]).unwrap();
1526        let eval = evaluate_sync(Value::Tensor(tensor), &[]).expect("unique");
1527        let (values, ia, ic) = eval.into_triple();
1528        match values {
1529            Value::Tensor(t) => {
1530                assert_eq!(t.data, vec![1.0, 2.0, 3.0]);
1531                assert_eq!(t.shape, vec![3, 1]);
1532            }
1533            Value::Num(_) => panic!("expected tensor result"),
1534            other => panic!("unexpected result {other:?}"),
1535        }
1536        match ia {
1537            Value::Tensor(t) => assert_eq!(t.data, vec![2.0, 4.0, 1.0]),
1538            other => panic!("unexpected IA {other:?}"),
1539        }
1540        match ic {
1541            Value::Tensor(t) => assert_eq!(t.data, vec![3.0, 1.0, 3.0, 2.0]),
1542            other => panic!("unexpected IC {other:?}"),
1543        }
1544    }
1545
1546    #[test]
1547    fn unique_type_resolver_numeric() {
1548        assert_eq!(
1549            set_values_output_type(&[Type::tensor()], &ResolveContext::new(Vec::new())),
1550            Type::tensor()
1551        );
1552    }
1553
1554    #[test]
1555    fn unique_type_resolver_string_array() {
1556        assert_eq!(
1557            set_values_output_type(
1558                &[Type::cell_of(Type::String)],
1559                &ResolveContext::new(Vec::new()),
1560            ),
1561            Type::cell_of(Type::String)
1562        );
1563    }
1564
1565    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1566    #[test]
1567    fn unique_sorted_handles_nan() {
1568        let tensor = Tensor::new(vec![f64::NAN, 2.0, f64::NAN, 1.0], vec![4, 1]).unwrap();
1569        let eval = evaluate_sync(Value::Tensor(tensor), &[]).expect("unique");
1570        let (values, ..) = eval.into_triple();
1571        match values {
1572            Value::Tensor(t) => {
1573                assert_eq!(t.data.len(), 3);
1574                assert_eq!(t.data[0], 1.0);
1575                assert_eq!(t.data[1], 2.0);
1576                assert!(t.data[2].is_nan());
1577            }
1578            other => panic!("unexpected values {other:?}"),
1579        }
1580    }
1581
1582    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1583    #[test]
1584    fn unique_stable_with_nan() {
1585        let tensor = Tensor::new(vec![f64::NAN, 2.0, f64::NAN, 1.0], vec![4, 1]).unwrap();
1586        let eval = evaluate_sync(Value::Tensor(tensor), &[Value::from("stable")]).expect("unique");
1587        let (values, ..) = eval.into_triple();
1588        match values {
1589            Value::Tensor(t) => {
1590                assert!(t.data[0].is_nan());
1591                assert_eq!(t.data[1], 2.0);
1592                assert_eq!(t.data[2], 1.0);
1593            }
1594            other => panic!("unexpected values {other:?}"),
1595        }
1596    }
1597
1598    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1599    #[test]
1600    fn unique_stable_preserves_order() {
1601        let tensor = Tensor::new(vec![4.0, 2.0, 4.0, 1.0, 2.0], vec![5, 1]).unwrap();
1602        let eval = evaluate_sync(Value::Tensor(tensor), &[Value::from("stable")]).expect("unique");
1603        let (values, ia) = eval.into_pair();
1604        match values {
1605            Value::Tensor(t) => assert_eq!(t.data, vec![4.0, 2.0, 1.0]),
1606            other => panic!("unexpected values {other:?}"),
1607        }
1608        match ia {
1609            Value::Tensor(t) => assert_eq!(t.data, vec![1.0, 2.0, 4.0]),
1610            other => panic!("unexpected IA {other:?}"),
1611        }
1612    }
1613
1614    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1615    #[test]
1616    fn unique_last_occurrence() {
1617        let tensor = Tensor::new(vec![9.0, 8.0, 9.0, 7.0, 8.0], vec![5, 1]).unwrap();
1618        let eval = evaluate_sync(Value::Tensor(tensor), &[Value::from("last")]).expect("unique");
1619        let (values, ia, ic) = eval.into_triple();
1620        match values {
1621            Value::Tensor(t) => assert_eq!(t.data, vec![7.0, 8.0, 9.0]),
1622            other => panic!("unexpected values {other:?}"),
1623        }
1624        match ia {
1625            Value::Tensor(t) => assert_eq!(t.data, vec![4.0, 5.0, 3.0]),
1626            other => panic!("unexpected IA {other:?}"),
1627        }
1628        match ic {
1629            Value::Tensor(t) => assert_eq!(t.data, vec![3.0, 2.0, 3.0, 1.0, 2.0]),
1630            other => panic!("unexpected IC {other:?}"),
1631        }
1632    }
1633
1634    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1635    #[test]
1636    fn unique_rows_sorted_default() {
1637        let tensor = Tensor::new(vec![1.0, 1.0, 2.0, 1.0, 3.0, 3.0, 4.0, 2.0], vec![4, 2]).unwrap();
1638        let eval = evaluate_sync(Value::Tensor(tensor), &[Value::from("rows")]).expect("unique");
1639        let (values, ia, ic) = eval.into_triple();
1640        match values {
1641            Value::Tensor(t) => {
1642                assert_eq!(t.shape, vec![3, 2]);
1643                assert_eq!(t.data, vec![1.0, 1.0, 2.0, 2.0, 3.0, 4.0]);
1644            }
1645            other => panic!("unexpected values {other:?}"),
1646        }
1647        match ia {
1648            Value::Tensor(t) => assert_eq!(t.data, vec![4.0, 1.0, 3.0]),
1649            other => panic!("unexpected IA {other:?}"),
1650        }
1651        match ic {
1652            Value::Tensor(t) => assert_eq!(t.data, vec![2.0, 2.0, 3.0, 1.0]),
1653            other => panic!("unexpected IC {other:?}"),
1654        }
1655    }
1656
1657    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1658    #[test]
1659    fn unique_rows_stable_last() {
1660        let tensor = Tensor::new(vec![1.0, 1.0, 2.0, 1.0, 1.0, 2.0], vec![3, 2]).unwrap();
1661        let eval = evaluate_sync(
1662            Value::Tensor(tensor),
1663            &[
1664                Value::from("rows"),
1665                Value::from("stable"),
1666                Value::from("last"),
1667            ],
1668        )
1669        .expect("unique");
1670        let (values, ia, ic) = eval.into_triple();
1671        match values {
1672            Value::Tensor(t) => {
1673                assert_eq!(t.shape, vec![2, 2]);
1674                assert_eq!(t.data, vec![1.0, 2.0, 1.0, 2.0]);
1675            }
1676            other => panic!("unexpected values {other:?}"),
1677        }
1678        match ia {
1679            Value::Tensor(t) => assert_eq!(t.data, vec![2.0, 3.0]),
1680            other => panic!("unexpected IA {other:?}"),
1681        }
1682        match ic {
1683            Value::Tensor(t) => assert_eq!(t.data, vec![1.0, 1.0, 2.0]),
1684            other => panic!("unexpected IC {other:?}"),
1685        }
1686    }
1687
1688    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1689    #[test]
1690    fn unique_char_elements_sorted() {
1691        let chars = CharArray::new(vec!['m', 'z', 'm', 'a'], 2, 2).unwrap();
1692        let eval = evaluate_sync(Value::CharArray(chars), &[]).expect("unique");
1693        let (values, ia, ic) = eval.into_triple();
1694        match values {
1695            Value::CharArray(arr) => {
1696                assert_eq!(arr.rows, 3);
1697                assert_eq!(arr.cols, 1);
1698                assert_eq!(arr.data, vec!['a', 'm', 'z']);
1699            }
1700            other => panic!("unexpected values {other:?}"),
1701        }
1702        match ia {
1703            Value::Tensor(t) => assert_eq!(t.data, vec![4.0, 1.0, 3.0]),
1704            other => panic!("unexpected IA {other:?}"),
1705        }
1706        match ic {
1707            Value::Tensor(t) => assert_eq!(t.data, vec![2.0, 2.0, 3.0, 1.0]),
1708            other => panic!("unexpected IC {other:?}"),
1709        }
1710    }
1711
1712    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1713    #[test]
1714    fn unique_char_rows_last() {
1715        let chars = CharArray::new(vec!['a', 'b', 'a', 'b', 'a', 'c'], 3, 2).unwrap();
1716        let eval = evaluate_sync(
1717            Value::CharArray(chars),
1718            &[Value::from("rows"), Value::from("last")],
1719        )
1720        .expect("unique");
1721        let (values, ia, ic) = eval.into_triple();
1722        match values {
1723            Value::CharArray(arr) => {
1724                assert_eq!(arr.rows, 2);
1725                assert_eq!(arr.cols, 2);
1726                assert_eq!(arr.data, vec!['a', 'b', 'a', 'c']);
1727            }
1728            other => panic!("unexpected values {other:?}"),
1729        }
1730        match ia {
1731            Value::Tensor(t) => assert_eq!(t.data, vec![2.0, 3.0]),
1732            other => panic!("unexpected IA {other:?}"),
1733        }
1734        match ic {
1735            Value::Tensor(t) => assert_eq!(t.data, vec![1.0, 1.0, 2.0]),
1736            other => panic!("unexpected IC {other:?}"),
1737        }
1738    }
1739
1740    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1741    #[test]
1742    fn unique_string_elements_stable() {
1743        let array = StringArray::new(
1744            vec!["beta".into(), "alpha".into(), "beta".into()],
1745            vec![3, 1],
1746        )
1747        .unwrap();
1748        let eval =
1749            evaluate_sync(Value::StringArray(array), &[Value::from("stable")]).expect("unique");
1750        let (values, ia, ic) = eval.into_triple();
1751        match values {
1752            Value::StringArray(sa) => {
1753                assert_eq!(sa.data, vec!["beta", "alpha"]);
1754                assert_eq!(sa.shape, vec![2, 1]);
1755            }
1756            other => panic!("unexpected values {other:?}"),
1757        }
1758        match ia {
1759            Value::Tensor(t) => assert_eq!(t.data, vec![1.0, 2.0]),
1760            other => panic!("unexpected IA {other:?}"),
1761        }
1762        match ic {
1763            Value::Tensor(t) => assert_eq!(t.data, vec![1.0, 2.0, 1.0]),
1764            other => panic!("unexpected IC {other:?}"),
1765        }
1766    }
1767
1768    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1769    #[test]
1770    fn unique_string_rows() {
1771        let array = StringArray::new(
1772            vec![
1773                "alpha".into(),
1774                "alpha".into(),
1775                "gamma".into(),
1776                "beta".into(),
1777                "beta".into(),
1778                "beta".into(),
1779            ],
1780            vec![3, 2],
1781        )
1782        .unwrap();
1783        let eval = evaluate_sync(
1784            Value::StringArray(array),
1785            &[Value::from("rows"), Value::from("stable")],
1786        )
1787        .expect("unique");
1788        let (values, ia, ic) = eval.into_triple();
1789        match values {
1790            Value::StringArray(sa) => {
1791                assert_eq!(sa.shape, vec![2, 2]);
1792                assert_eq!(sa.data, vec!["alpha", "gamma", "beta", "beta"]);
1793            }
1794            other => panic!("unexpected values {other:?}"),
1795        }
1796        match ia {
1797            Value::Tensor(t) => assert_eq!(t.data, vec![1.0, 3.0]),
1798            other => panic!("unexpected IA {other:?}"),
1799        }
1800        match ic {
1801            Value::Tensor(t) => assert_eq!(t.data, vec![1.0, 1.0, 2.0]),
1802            other => panic!("unexpected IC {other:?}"),
1803        }
1804    }
1805
1806    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1807    #[test]
1808    fn unique_complex_sorted() {
1809        let tensor = ComplexTensor::new(
1810            vec![(1.0, 1.0), (0.0, 2.0), (1.0, -1.0), (0.0, 2.0)],
1811            vec![4, 1],
1812        )
1813        .unwrap();
1814        let eval = evaluate_sync(Value::ComplexTensor(tensor), &[]).expect("unique");
1815        let (values, ..) = eval.into_triple();
1816        match values {
1817            Value::ComplexTensor(t) => {
1818                assert_eq!(t.data.len(), 3);
1819                assert_eq!(t.data[0], (1.0, -1.0));
1820                assert_eq!(t.data[1], (1.0, 1.0));
1821                assert_eq!(t.data[2], (0.0, 2.0));
1822            }
1823            other => panic!("unexpected values {other:?}"),
1824        }
1825    }
1826
1827    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1828    #[test]
1829    fn unique_handles_logical_arrays() {
1830        let logical = LogicalArray::new(vec![1, 0, 1, 1], vec![4, 1]).unwrap();
1831        let eval = evaluate_sync(Value::LogicalArray(logical), &[]).expect("unique");
1832        let values = eval.into_values_value();
1833        match values {
1834            Value::Tensor(t) => assert_eq!(t.data, vec![0.0, 1.0]),
1835            other => panic!("unexpected values {other:?}"),
1836        }
1837    }
1838
1839    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1840    #[test]
1841    fn unique_gpu_roundtrip() {
1842        test_support::with_test_provider(|provider| {
1843            let tensor = Tensor::new(vec![5.0, 3.0, 5.0, 1.0], vec![4, 1]).unwrap();
1844            let view = runmat_accelerate_api::HostTensorView {
1845                data: &tensor.data,
1846                shape: &tensor.shape,
1847            };
1848            let handle = provider.upload(&view).expect("upload");
1849            let eval =
1850                evaluate_sync(Value::GpuTensor(handle), &[Value::from("stable")]).expect("unique");
1851            let values = eval.into_values_value();
1852            match values {
1853                Value::Tensor(t) => assert_eq!(t.data, vec![5.0, 3.0, 1.0]),
1854                other => panic!("unexpected values {other:?}"),
1855            }
1856        });
1857    }
1858
1859    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1860    #[test]
1861    #[cfg(feature = "wgpu")]
1862    fn unique_wgpu_matches_cpu() {
1863        let _ = runmat_accelerate::backend::wgpu::provider::register_wgpu_provider(
1864            runmat_accelerate::backend::wgpu::provider::WgpuProviderOptions::default(),
1865        );
1866        let tensor = Tensor::new(vec![5.0, 3.0, 5.0, 1.0, 2.0], vec![5, 1]).unwrap();
1867        let host_eval = evaluate_sync(Value::Tensor(tensor.clone()), &[]).expect("host unique");
1868        let (host_values, host_ia, host_ic) = host_eval.into_triple();
1869
1870        let provider = runmat_accelerate_api::provider().expect("provider registered");
1871        let view = runmat_accelerate_api::HostTensorView {
1872            data: &tensor.data,
1873            shape: &tensor.shape,
1874        };
1875        let handle = provider.upload(&view).expect("upload");
1876        let gpu_eval = evaluate_sync(Value::GpuTensor(handle.clone()), &[]).expect("gpu unique");
1877        let (gpu_values, gpu_ia, gpu_ic) = gpu_eval.into_triple();
1878        let _ = provider.free(&handle);
1879
1880        let host_values = test_support::gather(host_values).expect("gather host values");
1881        let host_ia = test_support::gather(host_ia).expect("gather host ia");
1882        let host_ic = test_support::gather(host_ic).expect("gather host ic");
1883        let gpu_values = test_support::gather(gpu_values).expect("gather gpu values");
1884        let gpu_ia = test_support::gather(gpu_ia).expect("gather gpu ia");
1885        let gpu_ic = test_support::gather(gpu_ic).expect("gather gpu ic");
1886
1887        assert_eq!(gpu_values.shape, host_values.shape);
1888        assert_eq!(gpu_values.data, host_values.data);
1889        assert_eq!(gpu_ia.data, host_ia.data);
1890        assert_eq!(gpu_ic.data, host_ic.data);
1891    }
1892
1893    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1894    #[test]
1895    fn unique_rejects_legacy_option() {
1896        let tensor = Tensor::new(vec![1.0, 1.0], vec![2, 1]).unwrap();
1897        let err = evaluate_sync(Value::Tensor(tensor), &[Value::from("legacy")]).unwrap_err();
1898        assert_eq!(
1899            err.identifier(),
1900            UNIQUE_ERROR_LEGACY_OPTION_UNSUPPORTED.identifier
1901        );
1902    }
1903
1904    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1905    #[test]
1906    fn unique_conflicting_order_flags() {
1907        let tensor = Tensor::new(vec![1.0, 2.0], vec![2, 1]).unwrap();
1908        let err = evaluate_sync(
1909            Value::Tensor(tensor),
1910            &[Value::from("stable"), Value::from("sorted")],
1911        )
1912        .unwrap_err();
1913        assert_eq!(
1914            err.identifier(),
1915            UNIQUE_ERROR_CONFLICTING_ORDER_OPTIONS.identifier
1916        );
1917    }
1918
1919    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1920    #[test]
1921    fn unique_conflicting_occurrence_flags() {
1922        let tensor = Tensor::new(vec![1.0, 2.0], vec![2, 1]).unwrap();
1923        let err = evaluate_sync(
1924            Value::Tensor(tensor),
1925            &[Value::from("first"), Value::from("last")],
1926        )
1927        .unwrap_err();
1928        assert_eq!(
1929            err.identifier(),
1930            UNIQUE_ERROR_CONFLICTING_OCCURRENCE_OPTIONS.identifier
1931        );
1932    }
1933
1934    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1935    #[test]
1936    fn unique_rejects_unknown_option() {
1937        let tensor = Tensor::new(vec![1.0, 2.0], vec![2, 1]).unwrap();
1938        let err = evaluate_sync(Value::Tensor(tensor), &[Value::from("bogus")]).unwrap_err();
1939        assert_eq!(err.identifier(), UNIQUE_ERROR_UNKNOWN_OPTION.identifier);
1940    }
1941
1942    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1943    #[test]
1944    fn unique_rows_requires_two_dimensional_input() {
1945        let tensor = Tensor::new(vec![1.0, 2.0], vec![2, 1, 1]).unwrap();
1946        let err = evaluate_sync(Value::Tensor(tensor), &[Value::from("rows")]).unwrap_err();
1947        assert_eq!(
1948            err.identifier(),
1949            UNIQUE_ERROR_ROWS_REQUIRES_2D_MATRIX.identifier
1950        );
1951    }
1952
1953    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1954    #[test]
1955    fn unique_handles_empty_rows() {
1956        let tensor = Tensor::new(Vec::new(), vec![0, 3]).unwrap();
1957        let eval = evaluate_sync(Value::Tensor(tensor), &[Value::from("rows")]).expect("unique");
1958        let (values, ia, ic) = eval.into_triple();
1959        match values {
1960            Value::Tensor(t) => {
1961                assert_eq!(t.shape, vec![0, 3]);
1962                assert!(t.data.is_empty());
1963            }
1964            other => panic!("unexpected values {other:?}"),
1965        }
1966        match ia {
1967            Value::Tensor(t) => assert!(t.data.is_empty()),
1968            other => panic!("unexpected IA {other:?}"),
1969        }
1970        match ic {
1971            Value::Tensor(t) => assert!(t.data.is_empty()),
1972            other => panic!("unexpected IC {other:?}"),
1973        }
1974    }
1975
1976    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1977    #[test]
1978    fn unique_accepts_integer_scalars() {
1979        let eval = evaluate_sync(Value::Int(IntValue::I32(42)), &[]).expect("unique");
1980        let values = eval.into_values_value();
1981        match values {
1982            Value::Num(n) => assert_eq!(n, 42.0),
1983            other => panic!("unexpected values {other:?}"),
1984        }
1985    }
1986}