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//! use a provider hook or typed host fallback, then public outputs are restored
6//! to the owning provider.
7
8use std::cmp::Ordering;
9use std::collections::HashMap;
10
11use runmat_accelerate_api::{
12    GpuTensorHandle, UniqueOccurrence, UniqueOptions, UniqueOrder, UniqueResult,
13};
14use runmat_builtins::{
15    BuiltinCompletionPolicy, BuiltinDescriptor, BuiltinErrorDescriptor, BuiltinIntegerBackendRule,
16    BuiltinIntegerCapabilityDescriptor, BuiltinIntegerComputationDomain,
17    BuiltinIntegerInputAvailability, BuiltinIntegerInputCapability, BuiltinIntegerOutputClassRule,
18    BuiltinIntegerOverflowRule, BuiltinIntegerOverloadKind, BuiltinIntegerScalarDoubleRule,
19    BuiltinOutputMode, BuiltinParamArity, BuiltinParamDescriptor, BuiltinParamType,
20    BuiltinSignatureDescriptor,
21};
22use runmat_macros::runtime_builtin;
23use runmat_value::{
24    CharArray, ComplexStorage, ComplexTensor, IntValue, IntegerStorage, LogicalArray,
25    NumericStorage, StringArray, Tensor, Value,
26};
27
28use super::{float_order::SetFloat, integer_order, type_resolvers::unique_values_output_type};
29use crate::build_runtime_error;
30use crate::builtins::common::arg_tokens::tokens_from_values;
31use crate::builtins::common::gpu_helpers;
32use crate::builtins::common::random_args::complex_tensor_into_value;
33use crate::builtins::common::spec::{
34    BroadcastSemantics, BuiltinFusionSpec, BuiltinGpuSpec, ConstantStrategy, GpuOpKind,
35    ProviderHook, ReductionNaN, ResidencyPolicy, ScalarType, ShapeRequirements,
36};
37use crate::builtins::common::tensor;
38
39#[runmat_macros::register_gpu_spec(builtin_path = "crate::builtins::array::sorting_sets::unique")]
40pub const GPU_SPEC: BuiltinGpuSpec = BuiltinGpuSpec {
41    name: "unique",
42    op_kind: GpuOpKind::Custom("unique"),
43    supported_precisions: &[ScalarType::F32, ScalarType::F64],
44    broadcast: BroadcastSemantics::None,
45    provider_hooks: &[ProviderHook::Custom("unique")],
46    constant_strategy: ConstantStrategy::InlineLiteral,
47    residency: ResidencyPolicy::NewHandle,
48    nan_mode: ReductionNaN::Include,
49    two_pass_threshold: None,
50    workgroup_size: None,
51    accepts_nan_mode: true,
52    notes: "Providers may implement the `unique` hook; typed host fallback preserves exact supported integer storage and public outputs are restored to the input handle's owner.",
53};
54
55#[runmat_macros::register_fusion_spec(
56    builtin_path = "crate::builtins::array::sorting_sets::unique"
57)]
58pub const FUSION_SPEC: BuiltinFusionSpec = BuiltinFusionSpec {
59    name: "unique",
60    shape: ShapeRequirements::Any,
61    constant_strategy: ConstantStrategy::InlineLiteral,
62    elementwise: None,
63    reduction: None,
64    emits_nan: true,
65    notes: "`unique` terminates fusion chains and acts as a residency sink; upstream tensors are gathered when a provider hook is unavailable.",
66};
67
68const BUILTIN_NAME: &str = "unique";
69
70const UNIQUE_OUTPUT_C: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
71    name: "C",
72    ty: BuiltinParamType::Any,
73    arity: BuiltinParamArity::Required,
74    default: None,
75    description: "Unique values or rows.",
76}];
77
78const UNIQUE_OUTPUT_C_IA: [BuiltinParamDescriptor; 2] = [
79    BuiltinParamDescriptor {
80        name: "C",
81        ty: BuiltinParamType::Any,
82        arity: BuiltinParamArity::Required,
83        default: None,
84        description: "Unique values or rows.",
85    },
86    BuiltinParamDescriptor {
87        name: "ia",
88        ty: BuiltinParamType::NumericArray,
89        arity: BuiltinParamArity::Required,
90        default: None,
91        description: "Indices selecting representatives in input A.",
92    },
93];
94
95const UNIQUE_OUTPUT_C_IA_IC: [BuiltinParamDescriptor; 3] = [
96    BuiltinParamDescriptor {
97        name: "C",
98        ty: BuiltinParamType::Any,
99        arity: BuiltinParamArity::Required,
100        default: None,
101        description: "Unique values or rows.",
102    },
103    BuiltinParamDescriptor {
104        name: "ia",
105        ty: BuiltinParamType::NumericArray,
106        arity: BuiltinParamArity::Required,
107        default: None,
108        description: "Indices selecting representatives in input A.",
109    },
110    BuiltinParamDescriptor {
111        name: "ic",
112        ty: BuiltinParamType::NumericArray,
113        arity: BuiltinParamArity::Required,
114        default: None,
115        description: "Indices mapping each input element/row to C.",
116    },
117];
118
119const UNIQUE_INPUTS_A: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
120    name: "A",
121    ty: BuiltinParamType::Any,
122    arity: BuiltinParamArity::Required,
123    default: None,
124    description: "Input array.",
125}];
126
127const UNIQUE_INPUTS_A_OPTIONS: [BuiltinParamDescriptor; 2] = [
128    BuiltinParamDescriptor {
129        name: "A",
130        ty: BuiltinParamType::Any,
131        arity: BuiltinParamArity::Required,
132        default: None,
133        description: "Input array.",
134    },
135    BuiltinParamDescriptor {
136        name: "option",
137        ty: BuiltinParamType::Any,
138        arity: BuiltinParamArity::Variadic,
139        default: None,
140        description: "Option tokens plus the 'TreatMissingAsDistinct', logical name-value pair.",
141    },
142];
143
144const UNIQUE_SIGNATURES: [BuiltinSignatureDescriptor; 6] = [
145    BuiltinSignatureDescriptor {
146        label: "C = unique(A)",
147        inputs: &UNIQUE_INPUTS_A,
148        outputs: &UNIQUE_OUTPUT_C,
149    },
150    BuiltinSignatureDescriptor {
151        label: "C = unique(A, option...)",
152        inputs: &UNIQUE_INPUTS_A_OPTIONS,
153        outputs: &UNIQUE_OUTPUT_C,
154    },
155    BuiltinSignatureDescriptor {
156        label: "[C, ia] = unique(A)",
157        inputs: &UNIQUE_INPUTS_A,
158        outputs: &UNIQUE_OUTPUT_C_IA,
159    },
160    BuiltinSignatureDescriptor {
161        label: "[C, ia] = unique(A, option...)",
162        inputs: &UNIQUE_INPUTS_A_OPTIONS,
163        outputs: &UNIQUE_OUTPUT_C_IA,
164    },
165    BuiltinSignatureDescriptor {
166        label: "[C, ia, ic] = unique(A)",
167        inputs: &UNIQUE_INPUTS_A,
168        outputs: &UNIQUE_OUTPUT_C_IA_IC,
169    },
170    BuiltinSignatureDescriptor {
171        label: "[C, ia, ic] = unique(A, option...)",
172        inputs: &UNIQUE_INPUTS_A_OPTIONS,
173        outputs: &UNIQUE_OUTPUT_C_IA_IC,
174    },
175];
176
177const UNIQUE_ERROR_LEGACY_OPTION_UNSUPPORTED: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
178    code: "RM.UNIQUE.LEGACY_OPTION_UNSUPPORTED",
179    identifier: Some("RunMat:unique:LegacyOptionUnsupported"),
180    when: "Legacy compatibility options are requested.",
181    message: "unique: the 'legacy' behaviour is not supported",
182};
183
184const UNIQUE_ERROR_CONFLICTING_ORDER_OPTIONS: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
185    code: "RM.UNIQUE.CONFLICTING_ORDER_OPTIONS",
186    identifier: Some("RunMat:unique:ConflictingOrderOptions"),
187    when: "Both 'sorted' and 'stable' options are provided.",
188    message: "unique: cannot combine 'sorted' with 'stable'",
189};
190
191const UNIQUE_ERROR_CONFLICTING_OCCURRENCE_OPTIONS: BuiltinErrorDescriptor =
192    BuiltinErrorDescriptor {
193        code: "RM.UNIQUE.CONFLICTING_OCCURRENCE_OPTIONS",
194        identifier: Some("RunMat:unique:ConflictingOccurrenceOptions"),
195        when: "Both 'first' and 'last' options are provided.",
196        message: "unique: cannot combine 'first' with 'last'",
197    };
198
199const UNIQUE_ERROR_UNKNOWN_OPTION: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
200    code: "RM.UNIQUE.UNKNOWN_OPTION",
201    identifier: Some("RunMat:unique:UnknownOption"),
202    when: "An unsupported option token is provided.",
203    message: "unique: unrecognised option",
204};
205
206const UNIQUE_ERROR_ROWS_REQUIRES_2D_MATRIX: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
207    code: "RM.UNIQUE.ROWS_REQUIRES_2D_MATRIX",
208    identifier: Some("RunMat:unique:RowsRequiresTwoDimensionalInput"),
209    when: "'rows' mode is used with non-2D data.",
210    message: "unique: 'rows' option requires a 2-D matrix input",
211};
212
213const UNIQUE_ERROR_UNSUPPORTED_INPUT_TYPE: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
214    code: "RM.UNIQUE.UNSUPPORTED_INPUT_TYPE",
215    identifier: Some("RunMat:unique:UnsupportedInputType"),
216    when: "Input cannot be converted into a supported unique domain.",
217    message: "unique: unsupported input type",
218};
219
220const UNIQUE_ERROR_INVALID_ARGUMENT: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
221    code: "RM.UNIQUE.INVALID_ARGUMENT",
222    identifier: Some("RunMat:unique:InvalidArgument"),
223    when: "Option arguments or name-value pairs are malformed.",
224    message: "unique: invalid option arguments",
225};
226
227const UNIQUE_ERROR_GPU_OPTION_COMBINATION: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
228    code: "RM.UNIQUE.GPU_OPTION_COMBINATION",
229    identifier: Some("RunMat:unique:GpuOptionCombination"),
230    when: "A resident input specifies both set-order and occurrence options.",
231    message: "unique: GPU inputs cannot combine a set-order option with 'first' or 'last'",
232};
233
234const UNIQUE_ERROR_INTERNAL: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
235    code: "RM.UNIQUE.INTERNAL",
236    identifier: Some("RunMat:unique:Internal"),
237    when: "Internal conversion/allocation/provider decode fails.",
238    message: "unique: internal operation failed",
239};
240
241const UNIQUE_ERRORS: [BuiltinErrorDescriptor; 9] = [
242    UNIQUE_ERROR_LEGACY_OPTION_UNSUPPORTED,
243    UNIQUE_ERROR_CONFLICTING_ORDER_OPTIONS,
244    UNIQUE_ERROR_CONFLICTING_OCCURRENCE_OPTIONS,
245    UNIQUE_ERROR_UNKNOWN_OPTION,
246    UNIQUE_ERROR_ROWS_REQUIRES_2D_MATRIX,
247    UNIQUE_ERROR_UNSUPPORTED_INPUT_TYPE,
248    UNIQUE_ERROR_INVALID_ARGUMENT,
249    UNIQUE_ERROR_GPU_OPTION_COMBINATION,
250    UNIQUE_ERROR_INTERNAL,
251];
252
253const UNIQUE_INTEGER_INPUTS: [BuiltinIntegerInputCapability; 1] =
254    [BuiltinIntegerInputCapability {
255        name: "A",
256        classes: &crate::builtins::common::integer_capability::ALL_INTEGER_CLASSES,
257        availability: BuiltinIntegerInputAvailability::Documented,
258        scalar_double: BuiltinIntegerScalarDoubleRule::NotApplicable,
259        notes: "A accepts every real integer class and retains exact typed storage throughout host evaluation.",
260    }];
261
262const UNIQUE_INTEGER_CAPABILITIES: [BuiltinIntegerCapabilityDescriptor; 1] =
263    [BuiltinIntegerCapabilityDescriptor {
264        form: "[C, ia, ic] = unique(integer_A, options)",
265        inputs: &UNIQUE_INTEGER_INPUTS,
266        computation_domain: BuiltinIntegerComputationDomain::ExactInteger,
267        output_class: BuiltinIntegerOutputClassRule::PreserveInput,
268        overflow: BuiltinIntegerOverflowRule::NotApplicable,
269        backend: BuiltinIntegerBackendRule::GpuRestricted,
270        overload: BuiltinIntegerOverloadKind::Multiple,
271        notes: "C preserves A's exact integer class; ia and ic are one-based double. GPU supports integer classes through 32 bits and restores all requested outputs after typed fallback.",
272    }];
273
274pub const UNIQUE_DESCRIPTOR: BuiltinDescriptor = BuiltinDescriptor {
275    signatures: &UNIQUE_SIGNATURES,
276    output_mode: BuiltinOutputMode::ByRequestedOutputCount,
277    completion_policy: BuiltinCompletionPolicy::Public,
278    errors: &UNIQUE_ERRORS,
279};
280
281fn unique_error_with(
282    error: &'static BuiltinErrorDescriptor,
283    message: impl Into<String>,
284) -> crate::RuntimeError {
285    let mut builder = build_runtime_error(message).with_builtin(BUILTIN_NAME);
286    if let Some(identifier) = error.identifier {
287        builder = builder.with_identifier(identifier);
288    }
289    builder.build()
290}
291
292fn unique_error(error: &'static BuiltinErrorDescriptor) -> crate::RuntimeError {
293    unique_error_with(error, error.message)
294}
295
296fn unique_internal_error(message: impl Into<String>) -> crate::RuntimeError {
297    unique_error_with(&UNIQUE_ERROR_INTERNAL, message)
298}
299
300#[runtime_builtin(
301    name = "unique",
302    category = "array/sorting_sets",
303    summary = "Return unique elements or rows with optional index mappings.",
304    keywords = "unique,set,distinct,stable,rows,indices,gpu",
305    accel = "array_construct",
306    sink = true,
307    type_resolver(unique_values_output_type),
308    descriptor(crate::builtins::array::sorting_sets::unique::UNIQUE_DESCRIPTOR),
309    integer_capabilities(
310        crate::builtins::array::sorting_sets::unique::UNIQUE_INTEGER_CAPABILITIES
311    ),
312    builtin_path = "crate::builtins::array::sorting_sets::unique"
313)]
314async fn unique_builtin(value: Value, rest: Vec<Value>) -> crate::BuiltinResult<Value> {
315    if matches!(crate::output_count::current_output_count(), Some(n) if n > 3) {
316        return Err(unique_error_with(
317            &UNIQUE_ERROR_INVALID_ARGUMENT,
318            "unique: too many output arguments; maximum is 3",
319        ));
320    }
321    let provider = super::output_provider(&value);
322    let eval = evaluate(value, &rest).await?;
323    if let Some(out_count) = crate::output_count::current_output_count() {
324        if out_count == 0 {
325            return Ok(Value::OutputList(Vec::new()));
326        }
327        if out_count == 1 {
328            let outputs = super::restore_set_outputs(
329                provider,
330                BUILTIN_NAME,
331                vec![eval.into_values_value()],
332                unique_internal_error,
333            )?;
334            return Ok(Value::OutputList(outputs));
335        }
336        if out_count == 2 {
337            let (values, ia) = eval.into_pair();
338            let outputs = super::restore_set_outputs(
339                provider,
340                BUILTIN_NAME,
341                vec![values, ia],
342                unique_internal_error,
343            )?;
344            return Ok(Value::OutputList(outputs));
345        }
346        let (values, ia, ic) = eval.into_triple();
347        let outputs = super::restore_set_outputs(
348            provider,
349            BUILTIN_NAME,
350            vec![values, ia, ic],
351            unique_internal_error,
352        )?;
353        return Ok(Value::OutputList(outputs));
354    }
355    let mut outputs = super::restore_set_outputs(
356        provider,
357        BUILTIN_NAME,
358        vec![eval.into_values_value()],
359        unique_internal_error,
360    )?;
361    Ok(outputs.pop().expect("unique output"))
362}
363
364/// Evaluate `unique` once and expose all outputs to the caller.
365pub async fn evaluate(value: Value, rest: &[Value]) -> crate::BuiltinResult<UniqueEvaluation> {
366    crate::builtins::common::validation::reject_typed_complex_integer(&value, "unique")?;
367    let opts = parse_options(rest)?;
368    match value {
369        Value::GpuTensor(handle) => unique_gpu(handle, &opts).await,
370        other => unique_host(other, &opts),
371    }
372}
373
374fn parse_options(rest: &[Value]) -> crate::BuiltinResult<UniqueOptions> {
375    let mut opts = UniqueOptions {
376        rows: false,
377        order: UniqueOrder::Sorted,
378        occurrence: UniqueOccurrence::First,
379        treat_missing_as_distinct: true,
380        explicit_order: false,
381        explicit_occurrence: false,
382    };
383    let mut seen_order: Option<UniqueOrder> = None;
384    let mut seen_occurrence: Option<UniqueOccurrence> = None;
385
386    let tokens = tokens_from_values(rest);
387    let mut index = 0;
388    while index < rest.len() {
389        let arg = &rest[index];
390        let token = &tokens[index];
391        let text = match token {
392            crate::builtins::common::arg_tokens::ArgToken::String(text) => text.as_str(),
393            _ => {
394                let text = tensor::value_to_string(arg)
395                    .ok_or_else(|| unique_error(&UNIQUE_ERROR_INVALID_ARGUMENT))?;
396                let lowered = text.trim().to_ascii_lowercase();
397                if lowered == "treatmissingasdistinct" {
398                    index += 1;
399                    let value = rest.get(index).ok_or_else(|| {
400                        unique_error_with(
401                            &UNIQUE_ERROR_INVALID_ARGUMENT,
402                            "unique: 'TreatMissingAsDistinct' requires a logical scalar value",
403                        )
404                    })?;
405                    opts.treat_missing_as_distinct = parse_logical_option(value)?;
406                } else {
407                    parse_unique_option(
408                        &mut opts,
409                        &mut seen_order,
410                        &mut seen_occurrence,
411                        &lowered,
412                    )?;
413                }
414                index += 1;
415                continue;
416            }
417        };
418        if text.eq_ignore_ascii_case("TreatMissingAsDistinct") {
419            index += 1;
420            let value = rest.get(index).ok_or_else(|| {
421                unique_error_with(
422                    &UNIQUE_ERROR_INVALID_ARGUMENT,
423                    "unique: 'TreatMissingAsDistinct' requires a logical scalar value",
424                )
425            })?;
426            opts.treat_missing_as_distinct = parse_logical_option(value)?;
427        } else {
428            let lowered = text.trim().to_ascii_lowercase();
429            parse_unique_option(&mut opts, &mut seen_order, &mut seen_occurrence, &lowered)?;
430        }
431        index += 1;
432    }
433
434    Ok(opts)
435}
436
437fn parse_logical_option(value: &Value) -> crate::BuiltinResult<bool> {
438    if let Value::Bool(flag) = value {
439        return Ok(*flag);
440    }
441    if let Value::LogicalArray(logical) = value {
442        return match logical.data.as_slice() {
443            [flag] => Ok(*flag != 0),
444            _ => Err(unique_error_with(
445                &UNIQUE_ERROR_INVALID_ARGUMENT,
446                "unique: 'TreatMissingAsDistinct' must be a logical scalar",
447            )),
448        };
449    }
450    if let Some(integer) = tensor::scalar_integer_value(value) {
451        return match integer.try_to_i64() {
452            Some(0) => Ok(false),
453            Some(1) => Ok(true),
454            _ => Err(unique_error_with(
455                &UNIQUE_ERROR_INVALID_ARGUMENT,
456                "unique: 'TreatMissingAsDistinct' must be logical true or false",
457            )),
458        };
459    }
460    let number = match value {
461        Value::Num(number) => Some(*number),
462        Value::Tensor(tensor) if tensor::is_scalar_tensor(tensor) => {
463            Some(tensor::tensor_value_f64(tensor, 0))
464        }
465        _ => None,
466    };
467    match number {
468        Some(0.0) => Ok(false),
469        Some(1.0) => Ok(true),
470        _ => Err(unique_error_with(
471            &UNIQUE_ERROR_INVALID_ARGUMENT,
472            "unique: 'TreatMissingAsDistinct' must be logical true or false",
473        )),
474    }
475}
476
477fn parse_unique_option(
478    opts: &mut UniqueOptions,
479    seen_order: &mut Option<UniqueOrder>,
480    seen_occurrence: &mut Option<UniqueOccurrence>,
481    lowered: &str,
482) -> crate::BuiltinResult<()> {
483    match lowered {
484        "sorted" => {
485            if let Some(prev) = seen_order {
486                if *prev != UniqueOrder::Sorted {
487                    return Err(unique_error_with(
488                        &UNIQUE_ERROR_CONFLICTING_ORDER_OPTIONS,
489                        UNIQUE_ERROR_CONFLICTING_ORDER_OPTIONS.message,
490                    ));
491                }
492            }
493            *seen_order = Some(UniqueOrder::Sorted);
494            opts.order = UniqueOrder::Sorted;
495            opts.explicit_order = true;
496        }
497        "stable" => {
498            if let Some(prev) = seen_order {
499                if *prev != UniqueOrder::Stable {
500                    return Err(unique_error_with(
501                        &UNIQUE_ERROR_CONFLICTING_ORDER_OPTIONS,
502                        UNIQUE_ERROR_CONFLICTING_ORDER_OPTIONS.message,
503                    ));
504                }
505            }
506            *seen_order = Some(UniqueOrder::Stable);
507            opts.order = UniqueOrder::Stable;
508            opts.explicit_order = true;
509        }
510        "rows" => {
511            opts.rows = true;
512        }
513        "first" => {
514            if let Some(prev) = seen_occurrence {
515                if *prev != UniqueOccurrence::First {
516                    return Err(unique_error_with(
517                        &UNIQUE_ERROR_CONFLICTING_OCCURRENCE_OPTIONS,
518                        UNIQUE_ERROR_CONFLICTING_OCCURRENCE_OPTIONS.message,
519                    ));
520                }
521            }
522            *seen_occurrence = Some(UniqueOccurrence::First);
523            opts.occurrence = UniqueOccurrence::First;
524            opts.explicit_occurrence = true;
525        }
526        "last" => {
527            if let Some(prev) = seen_occurrence {
528                if *prev != UniqueOccurrence::Last {
529                    return Err(unique_error_with(
530                        &UNIQUE_ERROR_CONFLICTING_OCCURRENCE_OPTIONS,
531                        UNIQUE_ERROR_CONFLICTING_OCCURRENCE_OPTIONS.message,
532                    ));
533                }
534            }
535            *seen_occurrence = Some(UniqueOccurrence::Last);
536            opts.occurrence = UniqueOccurrence::Last;
537            opts.explicit_occurrence = true;
538        }
539        "legacy" | "r2012a" => {
540            return Err(unique_error(&UNIQUE_ERROR_LEGACY_OPTION_UNSUPPORTED));
541        }
542        other => {
543            return Err(unique_error_with(
544                &UNIQUE_ERROR_UNKNOWN_OPTION,
545                format!("unique: unrecognised option '{other}'"),
546            ));
547        }
548    }
549    Ok(())
550}
551
552async fn unique_gpu(
553    handle: GpuTensorHandle,
554    opts: &UniqueOptions,
555) -> crate::BuiltinResult<UniqueEvaluation> {
556    if super::is_unsupported_set_gpu_integer(&handle) {
557        return Err(unique_error_with(
558            &UNIQUE_ERROR_UNSUPPORTED_INPUT_TYPE,
559            "unique: resident 64-bit integer inputs are not supported",
560        ));
561    }
562    if opts.explicit_order && opts.explicit_occurrence {
563        return Err(unique_error(&UNIQUE_ERROR_GPU_OPTION_COMBINATION));
564    }
565    let logical = runmat_accelerate_api::handle_is_logical(&handle);
566    if runmat_accelerate_api::handle_integer_type(&handle).is_none() {
567        if let Some(provider) = runmat_accelerate_api::provider_for_handle(&handle)
568            .or_else(runmat_accelerate_api::provider)
569        {
570            if let Ok(result) = provider.unique(&handle, opts).await {
571                let evaluation = UniqueEvaluation::from_unique_result(result)?;
572                return if logical {
573                    evaluation.into_logical_values()
574                } else {
575                    Ok(evaluation)
576                };
577            }
578        }
579    }
580    let tensor = gpu_helpers::gather_tensor_async(&handle).await?;
581    let evaluation = unique_numeric_from_tensor(tensor, opts)?;
582    if logical {
583        evaluation.into_logical_values()
584    } else {
585        Ok(evaluation)
586    }
587}
588
589fn unique_host(value: Value, opts: &UniqueOptions) -> crate::BuiltinResult<UniqueEvaluation> {
590    match value {
591        Value::Tensor(tensor) => unique_numeric_from_tensor(tensor, opts),
592        Value::Num(n) => {
593            let tensor = Tensor::new(vec![n], vec![1, 1]).map_err(|e| unique_internal_error(format!("unique: {e}")))?;
594            unique_numeric_from_tensor(tensor, opts)
595        }
596        Value::Int(i) => {
597            let tensor = Tensor::new_integer(IntegerStorage::from_scalar(i), vec![1, 1])
598                .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
599            unique_numeric_from_tensor(tensor, opts)
600        }
601        Value::Bool(b) => {
602            let tensor = Tensor::new(vec![if b { 1.0 } else { 0.0 }], vec![1, 1])
603                .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
604            unique_numeric_from_tensor(tensor, opts)?.into_logical_values()
605        }
606        Value::LogicalArray(logical) => {
607            let tensor = tensor::logical_to_tensor(&logical)
608                .map_err(|e| unique_internal_error(e))?;
609            unique_numeric_from_tensor(tensor, opts)?.into_logical_values()
610        }
611        Value::ComplexTensor(tensor) => unique_complex_from_tensor(tensor, opts),
612        Value::Complex(re, im) => {
613            let tensor = ComplexTensor::new(vec![(re, im)], vec![1, 1])
614                .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
615            unique_complex_from_tensor(tensor, opts)
616        }
617        Value::CharArray(array) => unique_char_array(array, opts),
618        Value::StringArray(array) => unique_string_array(array, opts),
619        Value::String(s) => {
620            let array = StringArray::new(vec![s], vec![1, 1]).map_err(|e| unique_internal_error(format!("unique: {e}")))?;
621            unique_string_array(array, opts)
622        }
623        other => Err(unique_error_with(
624            &UNIQUE_ERROR_UNSUPPORTED_INPUT_TYPE,
625            format!(
626                "unique: unsupported input type {:?}; expected numeric, logical, char, string, or complex values",
627                other
628            ),
629        )),
630    }
631}
632
633pub fn unique_numeric_from_tensor(
634    tensor: Tensor,
635    opts: &UniqueOptions,
636) -> crate::BuiltinResult<UniqueEvaluation> {
637    let shape = tensor.shape.clone();
638    match tensor
639        .into_numeric_storage()
640        .map_err(|e| unique_internal_error(format!("unique: {e}")))?
641    {
642        NumericStorage::F64(values) => unique_floating(values, shape, opts),
643        NumericStorage::F32(values) => unique_floating(values, shape, opts),
644        storage => {
645            let integer = storage
646                .into_integer_storage()
647                .map_err(|_| unique_internal_error("unique: expected integer storage"))?;
648            unique_integer(&integer, shape, opts)
649        }
650    }
651}
652
653fn unique_floating<T: SetFloat>(
654    values: Vec<T>,
655    shape: Vec<usize>,
656    opts: &UniqueOptions,
657) -> crate::BuiltinResult<UniqueEvaluation> {
658    if opts.rows {
659        unique_floating_rows(values, shape, opts)
660    } else {
661        unique_floating_elements(values, shape, opts)
662    }
663}
664
665fn unique_integer(
666    storage: &IntegerStorage,
667    shape: Vec<usize>,
668    opts: &UniqueOptions,
669) -> crate::BuiltinResult<UniqueEvaluation> {
670    if opts.rows {
671        unique_integer_rows(storage, shape, opts)
672    } else {
673        unique_integer_elements(storage, shape, opts)
674    }
675}
676
677fn is_row_vector_shape(shape: &[usize]) -> bool {
678    match shape {
679        [] => false,
680        [_] => true,
681        [rows, ..] if *rows != 1 => false,
682        [_, _, rest @ ..] => rest.iter().all(|&dimension| dimension == 1),
683    }
684}
685
686fn unique_element_values_shape(input_shape: &[usize], output_len: usize) -> Vec<usize> {
687    if is_row_vector_shape(input_shape) {
688        vec![1, output_len]
689    } else {
690        vec![output_len, 1]
691    }
692}
693
694fn unique_integer_elements(
695    storage: &IntegerStorage,
696    shape: Vec<usize>,
697    opts: &UniqueOptions,
698) -> crate::BuiltinResult<UniqueEvaluation> {
699    let values = storage.exact_values();
700    if values.is_empty() {
701        let output_shape = unique_element_values_shape(&shape, 0);
702        let output = Tensor::new_integer(storage.zeros_like(0), output_shape)
703            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
704        let empty = Tensor::new(Vec::new(), vec![0, 1])
705            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
706        return Ok(UniqueEvaluation::new(
707            Value::Tensor(output),
708            empty.clone(),
709            empty,
710        ));
711    }
712
713    let mut entries = Vec::<IntegerElementEntry>::new();
714    let mut map = HashMap::<IntValue, usize>::new();
715    let mut element_entry_index = Vec::with_capacity(values.len());
716    for (idx, value) in values.iter().enumerate() {
717        match map.get(value) {
718            Some(&entry_idx) => {
719                entries[entry_idx].last = idx;
720                element_entry_index.push(entry_idx);
721            }
722            None => {
723                let entry_idx = entries.len();
724                entries.push(IntegerElementEntry {
725                    value: value.clone(),
726                    first: idx,
727                    last: idx,
728                });
729                map.insert(value.clone(), entry_idx);
730                element_entry_index.push(entry_idx);
731            }
732        }
733    }
734    let mut order: Vec<usize> = (0..entries.len()).collect();
735    if opts.order == UniqueOrder::Sorted {
736        order.sort_by(|&a, &b| {
737            integer_order::compare(&entries[a].value, &entries[b].value, false, false)
738        });
739    }
740    let mut entry_to_position = vec![0usize; entries.len()];
741    for (pos, &entry_idx) in order.iter().enumerate() {
742        entry_to_position[entry_idx] = pos;
743    }
744    let output_values: Vec<_> = order
745        .iter()
746        .map(|&entry_idx| entries[entry_idx].value.clone())
747        .collect();
748    let ia: Vec<_> = order
749        .iter()
750        .map(|&entry_idx| {
751            let entry = &entries[entry_idx];
752            (match opts.occurrence {
753                UniqueOccurrence::First => entry.first,
754                UniqueOccurrence::Last => entry.last,
755            } + 1) as f64
756        })
757        .collect();
758    let ic: Vec<_> = element_entry_index
759        .into_iter()
760        .map(|entry_idx| (entry_to_position[entry_idx] + 1) as f64)
761        .collect();
762    let output = Tensor::new_integer(
763        storage
764            .from_exact_values_like(output_values)
765            .map_err(|e| unique_internal_error(format!("unique: {e}")))?,
766        unique_element_values_shape(&shape, order.len()),
767    )
768    .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
769    let ia = Tensor::new(ia, vec![order.len(), 1])
770        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
771    let ic = Tensor::new(ic, vec![values.len(), 1])
772        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
773    Ok(UniqueEvaluation::new(Value::Tensor(output), ia, ic))
774}
775
776fn unique_integer_rows(
777    storage: &IntegerStorage,
778    shape: Vec<usize>,
779    opts: &UniqueOptions,
780) -> crate::BuiltinResult<UniqueEvaluation> {
781    if shape.len() != 2 {
782        return Err(unique_error_with(
783            &UNIQUE_ERROR_ROWS_REQUIRES_2D_MATRIX,
784            UNIQUE_ERROR_ROWS_REQUIRES_2D_MATRIX.message,
785        ));
786    }
787    let rows = shape[0];
788    let cols = shape[1];
789    if rows == 0 || cols == 0 {
790        let output = Tensor::new_integer(storage.zeros_like(0), vec![0, cols])
791            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
792        let ia = Tensor::new(Vec::new(), vec![0, 1])
793            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
794        let ic = Tensor::new(Vec::new(), vec![rows, 1])
795            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
796        return Ok(UniqueEvaluation::new(Value::Tensor(output), ia, ic));
797    }
798    let values = storage.exact_values();
799    let mut entries = Vec::<IntegerRowEntry>::new();
800    let mut map = HashMap::<Vec<IntValue>, usize>::new();
801    let mut row_entry_index = Vec::with_capacity(rows);
802    for row in 0..rows {
803        let row_data: Vec<_> = (0..cols)
804            .map(|col| values[row + col * rows].clone())
805            .collect();
806        match map.get(&row_data) {
807            Some(&entry_idx) => {
808                entries[entry_idx].last = row;
809                row_entry_index.push(entry_idx);
810            }
811            None => {
812                let entry_idx = entries.len();
813                entries.push(IntegerRowEntry {
814                    row_data: row_data.clone(),
815                    first: row,
816                    last: row,
817                });
818                map.insert(row_data, entry_idx);
819                row_entry_index.push(entry_idx);
820            }
821        }
822    }
823    let mut order: Vec<usize> = (0..entries.len()).collect();
824    if opts.order == UniqueOrder::Sorted {
825        order.sort_by(|&a, &b| compare_integer_rows(&entries[a].row_data, &entries[b].row_data));
826    }
827    let mut entry_to_position = vec![0usize; entries.len()];
828    for (pos, &entry_idx) in order.iter().enumerate() {
829        entry_to_position[entry_idx] = pos;
830    }
831    let mut output_values = Vec::with_capacity(order.len() * cols);
832    for col in 0..cols {
833        for &entry_idx in &order {
834            output_values.push(entries[entry_idx].row_data[col].clone());
835        }
836    }
837    let ia: Vec<_> = order
838        .iter()
839        .map(|&entry_idx| {
840            let entry = &entries[entry_idx];
841            (match opts.occurrence {
842                UniqueOccurrence::First => entry.first,
843                UniqueOccurrence::Last => entry.last,
844            } + 1) as f64
845        })
846        .collect();
847    let ic: Vec<_> = row_entry_index
848        .into_iter()
849        .map(|entry_idx| (entry_to_position[entry_idx] + 1) as f64)
850        .collect();
851    let output = Tensor::new_integer(
852        storage
853            .from_exact_values_like(output_values)
854            .map_err(|e| unique_internal_error(format!("unique: {e}")))?,
855        vec![order.len(), cols],
856    )
857    .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
858    let ia = Tensor::new(ia, vec![order.len(), 1])
859        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
860    let ic = Tensor::new(ic, vec![rows, 1])
861        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
862    Ok(UniqueEvaluation::new(Value::Tensor(output), ia, ic))
863}
864
865fn unique_floating_elements<T: SetFloat>(
866    input: Vec<T>,
867    shape: Vec<usize>,
868    opts: &UniqueOptions,
869) -> crate::BuiltinResult<UniqueEvaluation> {
870    let len = input.len();
871    if len == 0 {
872        let values = Tensor::from_numeric_storage(
873            T::numeric_storage(Vec::new()),
874            unique_element_values_shape(&shape, 0),
875        )
876        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
877        let ia = Tensor::new(Vec::new(), vec![0, 1])
878            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
879        let ic = Tensor::new(Vec::new(), vec![0, 1])
880            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
881        return Ok(UniqueEvaluation::new(
882            tensor::tensor_into_value(values),
883            ia,
884            ic,
885        ));
886    }
887
888    let mut entries = Vec::<FloatingElementEntry<T>>::new();
889    let mut map: HashMap<u64, usize> = HashMap::new();
890    let mut element_entry_index = Vec::with_capacity(len);
891
892    for (idx, &value) in input.iter().enumerate() {
893        if opts.treat_missing_as_distinct && value.is_nan() {
894            let entry_idx = entries.len();
895            entries.push(FloatingElementEntry {
896                value,
897                first: idx,
898                last: idx,
899            });
900            element_entry_index.push(entry_idx);
901            continue;
902        }
903        let key = value.canonical_key();
904        match map.get(&key) {
905            Some(&entry_idx) => {
906                entries[entry_idx].last = idx;
907                element_entry_index.push(entry_idx);
908            }
909            None => {
910                let entry_idx = entries.len();
911                entries.push(FloatingElementEntry {
912                    value,
913                    first: idx,
914                    last: idx,
915                });
916                map.insert(key, entry_idx);
917                element_entry_index.push(entry_idx);
918            }
919        }
920    }
921
922    let mut order: Vec<usize> = (0..entries.len()).collect();
923    if opts.order == UniqueOrder::Sorted {
924        order.sort_by(|&a, &b| entries[a].value.compare(entries[b].value));
925    }
926
927    let mut entry_to_position = vec![0usize; entries.len()];
928    for (pos, &entry_idx) in order.iter().enumerate() {
929        entry_to_position[entry_idx] = pos;
930    }
931
932    let mut values = Vec::with_capacity(order.len());
933    let mut ia = Vec::with_capacity(order.len());
934    for &entry_idx in &order {
935        let entry = &entries[entry_idx];
936        values.push(entry.value);
937        let occurrence = match opts.occurrence {
938            UniqueOccurrence::First => entry.first,
939            UniqueOccurrence::Last => entry.last,
940        };
941        ia.push((occurrence + 1) as f64);
942    }
943
944    let mut ic = Vec::with_capacity(len);
945    for entry_idx in element_entry_index {
946        let pos = entry_to_position[entry_idx];
947        ic.push((pos + 1) as f64);
948    }
949
950    let value_tensor = Tensor::from_numeric_storage(
951        T::numeric_storage(values),
952        unique_element_values_shape(&shape, order.len()),
953    )
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 =
958        Tensor::new(ic, vec![len, 1]).map_err(|e| unique_internal_error(format!("unique: {e}")))?;
959
960    Ok(UniqueEvaluation::new(
961        tensor::tensor_into_value(value_tensor),
962        ia_tensor,
963        ic_tensor,
964    ))
965}
966
967fn unique_floating_rows<T: SetFloat>(
968    input: Vec<T>,
969    shape: Vec<usize>,
970    opts: &UniqueOptions,
971) -> crate::BuiltinResult<UniqueEvaluation> {
972    if shape.len() != 2 {
973        return Err(unique_error_with(
974            &UNIQUE_ERROR_ROWS_REQUIRES_2D_MATRIX,
975            UNIQUE_ERROR_ROWS_REQUIRES_2D_MATRIX.message,
976        ));
977    }
978    let rows = shape[0];
979    let cols = shape[1];
980
981    if rows == 0 || cols == 0 {
982        let values = Tensor::from_numeric_storage(T::numeric_storage(Vec::new()), vec![0, cols])
983            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
984        let ia = Tensor::new(Vec::new(), vec![0, 1])
985            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
986        let ic = Tensor::new(Vec::new(), vec![rows, 1])
987            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
988        return Ok(UniqueEvaluation::new(
989            tensor::tensor_into_value(values),
990            ia,
991            ic,
992        ));
993    }
994
995    let mut entries = Vec::<FloatingRowEntry<T>>::new();
996    let mut map: HashMap<FloatingRowKey, usize> = HashMap::new();
997    let mut row_entry_index = Vec::with_capacity(rows);
998
999    for r in 0..rows {
1000        let mut row_values = Vec::with_capacity(cols);
1001        for c in 0..cols {
1002            let idx = r + c * rows;
1003            row_values.push(input[idx]);
1004        }
1005        if opts.treat_missing_as_distinct && row_values.iter().any(|value| value.is_nan()) {
1006            let entry_idx = entries.len();
1007            entries.push(FloatingRowEntry {
1008                row_data: row_values,
1009                first: r,
1010                last: r,
1011            });
1012            row_entry_index.push(entry_idx);
1013            continue;
1014        }
1015        let key = FloatingRowKey::from_slice(&row_values);
1016        match map.get(&key) {
1017            Some(&entry_idx) => {
1018                entries[entry_idx].last = r;
1019                row_entry_index.push(entry_idx);
1020            }
1021            None => {
1022                let entry_idx = entries.len();
1023                entries.push(FloatingRowEntry {
1024                    row_data: row_values.clone(),
1025                    first: r,
1026                    last: r,
1027                });
1028                map.insert(key, entry_idx);
1029                row_entry_index.push(entry_idx);
1030            }
1031        }
1032    }
1033
1034    let mut order: Vec<usize> = (0..entries.len()).collect();
1035    if opts.order == UniqueOrder::Sorted {
1036        order.sort_by(|&a, &b| compare_floating_rows(&entries[a].row_data, &entries[b].row_data));
1037    }
1038
1039    let mut entry_to_position = vec![0usize; entries.len()];
1040    for (pos, &entry_idx) in order.iter().enumerate() {
1041        entry_to_position[entry_idx] = pos;
1042    }
1043
1044    let unique_rows_count = order.len();
1045    let mut values = vec![T::default(); unique_rows_count * cols];
1046    for (row_pos, &entry_idx) in order.iter().enumerate() {
1047        let row = &entries[entry_idx].row_data;
1048        for (col, value) in row.iter().enumerate().take(cols) {
1049            let dest = row_pos + col * unique_rows_count;
1050            values[dest] = *value;
1051        }
1052    }
1053
1054    let mut ia = Vec::with_capacity(unique_rows_count);
1055    for &entry_idx in &order {
1056        let entry = &entries[entry_idx];
1057        let occurrence = match opts.occurrence {
1058            UniqueOccurrence::First => entry.first,
1059            UniqueOccurrence::Last => entry.last,
1060        };
1061        ia.push((occurrence + 1) as f64);
1062    }
1063
1064    let mut ic = Vec::with_capacity(rows);
1065    for entry_idx in row_entry_index {
1066        let pos = entry_to_position[entry_idx];
1067        ic.push((pos + 1) as f64);
1068    }
1069
1070    let value_tensor =
1071        Tensor::from_numeric_storage(T::numeric_storage(values), vec![unique_rows_count, cols])
1072            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1073    let ia_tensor = Tensor::new(ia, vec![unique_rows_count, 1])
1074        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1075    let ic_tensor = Tensor::new(ic, vec![rows, 1])
1076        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1077
1078    Ok(UniqueEvaluation::new(
1079        tensor::tensor_into_value(value_tensor),
1080        ia_tensor,
1081        ic_tensor,
1082    ))
1083}
1084
1085fn unique_complex_from_tensor(
1086    tensor: ComplexTensor,
1087    opts: &UniqueOptions,
1088) -> crate::BuiltinResult<UniqueEvaluation> {
1089    let shape = tensor.shape.clone();
1090    match tensor.into_complex_storage() {
1091        ComplexStorage::F64(values) => unique_floating_complex(values, shape, opts),
1092        ComplexStorage::F32(values) => unique_floating_complex(values, shape, opts),
1093        ComplexStorage::Integer(_) => Err(unique_error_with(
1094            &UNIQUE_ERROR_UNSUPPORTED_INPUT_TYPE,
1095            "unique: complex integer arrays are not supported",
1096        )),
1097    }
1098}
1099
1100fn unique_floating_complex<T: SetFloat>(
1101    values: Vec<(T, T)>,
1102    shape: Vec<usize>,
1103    opts: &UniqueOptions,
1104) -> crate::BuiltinResult<UniqueEvaluation> {
1105    if opts.rows {
1106        unique_complex_rows(values, shape, opts)
1107    } else {
1108        unique_complex_elements(values, shape, opts)
1109    }
1110}
1111
1112fn unique_complex_elements<T: SetFloat>(
1113    input: Vec<(T, T)>,
1114    shape: Vec<usize>,
1115    opts: &UniqueOptions,
1116) -> crate::BuiltinResult<UniqueEvaluation> {
1117    let len = input.len();
1118    if len == 0 {
1119        let values = ComplexTensor::from_complex_storage(
1120            T::complex_storage(Vec::new()),
1121            unique_element_values_shape(&shape, 0),
1122        )
1123        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1124        let ia = Tensor::new(Vec::new(), vec![0, 1])
1125            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1126        let ic = Tensor::new(Vec::new(), vec![0, 1])
1127            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1128        return Ok(UniqueEvaluation::new(
1129            complex_tensor_into_value(values),
1130            ia,
1131            ic,
1132        ));
1133    }
1134
1135    let mut entries = Vec::<ComplexElementEntry<T>>::new();
1136    let mut map: HashMap<ComplexKey, usize> = HashMap::new();
1137    let mut element_entry_index = Vec::with_capacity(len);
1138
1139    for (idx, &value) in input.iter().enumerate() {
1140        if opts.treat_missing_as_distinct && (value.0.is_nan() || value.1.is_nan()) {
1141            let entry_idx = entries.len();
1142            entries.push(ComplexElementEntry {
1143                value,
1144                first: idx,
1145                last: idx,
1146            });
1147            element_entry_index.push(entry_idx);
1148            continue;
1149        }
1150        let key = ComplexKey::new(value);
1151        match map.get(&key) {
1152            Some(&entry_idx) => {
1153                entries[entry_idx].last = idx;
1154                element_entry_index.push(entry_idx);
1155            }
1156            None => {
1157                let entry_idx = entries.len();
1158                entries.push(ComplexElementEntry {
1159                    value,
1160                    first: idx,
1161                    last: idx,
1162                });
1163                map.insert(key, entry_idx);
1164                element_entry_index.push(entry_idx);
1165            }
1166        }
1167    }
1168
1169    let mut order: Vec<usize> = (0..entries.len()).collect();
1170    if opts.order == UniqueOrder::Sorted {
1171        order.sort_by(|&a, &b| compare_complex(entries[a].value, entries[b].value));
1172    }
1173
1174    let mut entry_to_position = vec![0usize; entries.len()];
1175    for (pos, &entry_idx) in order.iter().enumerate() {
1176        entry_to_position[entry_idx] = pos;
1177    }
1178
1179    let mut values = Vec::with_capacity(order.len());
1180    let mut ia = Vec::with_capacity(order.len());
1181    for &entry_idx in &order {
1182        let entry = &entries[entry_idx];
1183        values.push(entry.value);
1184        let occurrence = match opts.occurrence {
1185            UniqueOccurrence::First => entry.first,
1186            UniqueOccurrence::Last => entry.last,
1187        };
1188        ia.push((occurrence + 1) as f64);
1189    }
1190
1191    let mut ic = Vec::with_capacity(len);
1192    for entry_idx in element_entry_index {
1193        let pos = entry_to_position[entry_idx];
1194        ic.push((pos + 1) as f64);
1195    }
1196
1197    let value_tensor = ComplexTensor::from_complex_storage(
1198        T::complex_storage(values),
1199        unique_element_values_shape(&shape, order.len()),
1200    )
1201    .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1202    let ia_tensor = Tensor::new(ia, vec![order.len(), 1])
1203        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1204    let ic_tensor =
1205        Tensor::new(ic, vec![len, 1]).map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1206
1207    Ok(UniqueEvaluation::new(
1208        complex_tensor_into_value(value_tensor),
1209        ia_tensor,
1210        ic_tensor,
1211    ))
1212}
1213
1214fn unique_complex_rows<T: SetFloat>(
1215    input: Vec<(T, T)>,
1216    shape: Vec<usize>,
1217    opts: &UniqueOptions,
1218) -> crate::BuiltinResult<UniqueEvaluation> {
1219    if shape.len() != 2 {
1220        return Err(unique_error_with(
1221            &UNIQUE_ERROR_ROWS_REQUIRES_2D_MATRIX,
1222            UNIQUE_ERROR_ROWS_REQUIRES_2D_MATRIX.message,
1223        ));
1224    }
1225    let rows = shape[0];
1226    let cols = shape[1];
1227
1228    if rows == 0 || cols == 0 {
1229        let values =
1230            ComplexTensor::from_complex_storage(T::complex_storage(Vec::new()), vec![rows, cols])
1231                .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1232        let ia = Tensor::new(Vec::new(), vec![0, 1])
1233            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1234        let ic = Tensor::new(Vec::new(), vec![rows, 1])
1235            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1236        return Ok(UniqueEvaluation::new(
1237            complex_tensor_into_value(values),
1238            ia,
1239            ic,
1240        ));
1241    }
1242
1243    let mut entries = Vec::<ComplexRowEntry<T>>::new();
1244    let mut map: HashMap<Vec<ComplexKey>, usize> = HashMap::new();
1245    let mut row_entry_index = Vec::with_capacity(rows);
1246
1247    for r in 0..rows {
1248        let mut row_values = Vec::with_capacity(cols);
1249        let mut key_row = Vec::with_capacity(cols);
1250        for c in 0..cols {
1251            let idx = r + c * rows;
1252            let value = input[idx];
1253            row_values.push(value);
1254            key_row.push(ComplexKey::new(value));
1255        }
1256        if opts.treat_missing_as_distinct
1257            && row_values
1258                .iter()
1259                .any(|value| value.0.is_nan() || value.1.is_nan())
1260        {
1261            let entry_idx = entries.len();
1262            entries.push(ComplexRowEntry {
1263                row_data: row_values,
1264                first: r,
1265                last: r,
1266            });
1267            row_entry_index.push(entry_idx);
1268            continue;
1269        }
1270        match map.get(&key_row) {
1271            Some(&entry_idx) => {
1272                entries[entry_idx].last = r;
1273                row_entry_index.push(entry_idx);
1274            }
1275            None => {
1276                let entry_idx = entries.len();
1277                entries.push(ComplexRowEntry {
1278                    row_data: row_values.clone(),
1279                    first: r,
1280                    last: r,
1281                });
1282                map.insert(key_row, entry_idx);
1283                row_entry_index.push(entry_idx);
1284            }
1285        }
1286    }
1287
1288    let mut order: Vec<usize> = (0..entries.len()).collect();
1289    if opts.order == UniqueOrder::Sorted {
1290        order.sort_by(|&a, &b| compare_complex_rows(&entries[a].row_data, &entries[b].row_data));
1291    }
1292
1293    let mut entry_to_position = vec![0usize; entries.len()];
1294    for (pos, &entry_idx) in order.iter().enumerate() {
1295        entry_to_position[entry_idx] = pos;
1296    }
1297
1298    let unique_rows_count = order.len();
1299    let mut values = vec![(T::default(), T::default()); unique_rows_count * cols];
1300    for (row_pos, &entry_idx) in order.iter().enumerate() {
1301        let row = &entries[entry_idx].row_data;
1302        for (col, value) in row.iter().enumerate().take(cols) {
1303            let dest = row_pos + col * unique_rows_count;
1304            values[dest] = *value;
1305        }
1306    }
1307
1308    let mut ia = Vec::with_capacity(unique_rows_count);
1309    for &entry_idx in &order {
1310        let entry = &entries[entry_idx];
1311        let occurrence = match opts.occurrence {
1312            UniqueOccurrence::First => entry.first,
1313            UniqueOccurrence::Last => entry.last,
1314        };
1315        ia.push((occurrence + 1) as f64);
1316    }
1317
1318    let mut ic = Vec::with_capacity(rows);
1319    for entry_idx in row_entry_index {
1320        let pos = entry_to_position[entry_idx];
1321        ic.push((pos + 1) as f64);
1322    }
1323
1324    let value_tensor = ComplexTensor::from_complex_storage(
1325        T::complex_storage(values),
1326        vec![unique_rows_count, cols],
1327    )
1328    .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1329    let ia_tensor = Tensor::new(ia, vec![unique_rows_count, 1])
1330        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1331    let ic_tensor = Tensor::new(ic, vec![rows, 1])
1332        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1333
1334    Ok(UniqueEvaluation::new(
1335        complex_tensor_into_value(value_tensor),
1336        ia_tensor,
1337        ic_tensor,
1338    ))
1339}
1340
1341fn unique_char_array(
1342    array: CharArray,
1343    opts: &UniqueOptions,
1344) -> crate::BuiltinResult<UniqueEvaluation> {
1345    if opts.rows {
1346        unique_char_rows(array, opts)
1347    } else {
1348        unique_char_elements(array, opts)
1349    }
1350}
1351
1352fn unique_char_elements(
1353    array: CharArray,
1354    opts: &UniqueOptions,
1355) -> crate::BuiltinResult<UniqueEvaluation> {
1356    let shape = array.shape.clone();
1357    let input = array.to_column_major();
1358    let total = input.len();
1359    if total == 0 {
1360        let values =
1361            CharArray::from_column_major(Vec::new(), unique_element_values_shape(&shape, 0))
1362                .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1363        let ia = Tensor::new(Vec::new(), vec![0, 1])
1364            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1365        let ic = Tensor::new(Vec::new(), vec![0, 1])
1366            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1367        return Ok(UniqueEvaluation::new(Value::CharArray(values), ia, ic));
1368    }
1369
1370    let mut entries = Vec::<CharElementEntry>::new();
1371    let mut map: HashMap<u32, usize> = HashMap::new();
1372    let mut element_entry_index = Vec::with_capacity(total);
1373
1374    for (linear_idx, &ch) in input.iter().enumerate() {
1375        let key = ch as u32;
1376        match map.get(&key) {
1377            Some(&entry_idx) => {
1378                entries[entry_idx].last = linear_idx;
1379                element_entry_index.push(entry_idx);
1380            }
1381            None => {
1382                let entry_idx = entries.len();
1383                entries.push(CharElementEntry {
1384                    ch,
1385                    first: linear_idx,
1386                    last: linear_idx,
1387                });
1388                map.insert(key, entry_idx);
1389                element_entry_index.push(entry_idx);
1390            }
1391        }
1392    }
1393
1394    let mut order: Vec<usize> = (0..entries.len()).collect();
1395    if opts.order == UniqueOrder::Sorted {
1396        order.sort_by(|&a, &b| entries[a].ch.cmp(&entries[b].ch));
1397    }
1398
1399    let mut entry_to_position = vec![0usize; entries.len()];
1400    for (pos, &entry_idx) in order.iter().enumerate() {
1401        entry_to_position[entry_idx] = pos;
1402    }
1403
1404    let mut values = Vec::with_capacity(order.len());
1405    let mut ia = Vec::with_capacity(order.len());
1406    for &entry_idx in &order {
1407        let entry = &entries[entry_idx];
1408        values.push(entry.ch);
1409        let occurrence = match opts.occurrence {
1410            UniqueOccurrence::First => entry.first,
1411            UniqueOccurrence::Last => entry.last,
1412        };
1413        ia.push((occurrence + 1) as f64);
1414    }
1415
1416    let mut ic = Vec::with_capacity(total);
1417    for entry_idx in element_entry_index {
1418        let pos = entry_to_position[entry_idx];
1419        ic.push((pos + 1) as f64);
1420    }
1421
1422    let value_array =
1423        CharArray::from_column_major(values, unique_element_values_shape(&shape, order.len()))
1424            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1425    let ia_tensor = Tensor::new(ia, vec![order.len(), 1])
1426        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1427    let ic_tensor = Tensor::new(ic, vec![total, 1])
1428        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1429
1430    Ok(UniqueEvaluation::new(
1431        Value::CharArray(value_array),
1432        ia_tensor,
1433        ic_tensor,
1434    ))
1435}
1436
1437fn unique_char_rows(
1438    array: CharArray,
1439    opts: &UniqueOptions,
1440) -> crate::BuiltinResult<UniqueEvaluation> {
1441    if array.shape.len() != 2 {
1442        return Err(unique_error_with(
1443            &UNIQUE_ERROR_ROWS_REQUIRES_2D_MATRIX,
1444            UNIQUE_ERROR_ROWS_REQUIRES_2D_MATRIX.message,
1445        ));
1446    }
1447    let rows = array.rows;
1448    let cols = array.cols;
1449    if rows == 0 {
1450        let values = CharArray::new(Vec::new(), 0, cols)
1451            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1452        let ia = Tensor::new(Vec::new(), vec![0, 1])
1453            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1454        let ic = Tensor::new(Vec::new(), vec![0, 1])
1455            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1456        return Ok(UniqueEvaluation::new(Value::CharArray(values), ia, ic));
1457    }
1458
1459    let mut entries = Vec::<CharRowEntry>::new();
1460    let mut map: HashMap<RowCharKey, usize> = HashMap::new();
1461    let mut row_entry_index = Vec::with_capacity(rows);
1462
1463    for r in 0..rows {
1464        let start = r * cols;
1465        let end = start + cols;
1466        let slice = &array.data[start..end];
1467        let key = RowCharKey::from_slice(slice);
1468        match map.get(&key) {
1469            Some(&entry_idx) => {
1470                entries[entry_idx].last = r;
1471                row_entry_index.push(entry_idx);
1472            }
1473            None => {
1474                let entry_idx = entries.len();
1475                entries.push(CharRowEntry {
1476                    row_data: slice.to_vec(),
1477                    first: r,
1478                    last: r,
1479                });
1480                map.insert(key, entry_idx);
1481                row_entry_index.push(entry_idx);
1482            }
1483        }
1484    }
1485
1486    let mut order: Vec<usize> = (0..entries.len()).collect();
1487    if opts.order == UniqueOrder::Sorted {
1488        order.sort_by(|&a, &b| compare_char_rows(&entries[a].row_data, &entries[b].row_data));
1489    }
1490
1491    let mut entry_to_position = vec![0usize; entries.len()];
1492    for (pos, &entry_idx) in order.iter().enumerate() {
1493        entry_to_position[entry_idx] = pos;
1494    }
1495
1496    let unique_rows_count = order.len();
1497    let mut values = vec!['\0'; unique_rows_count * cols];
1498    for (row_pos, &entry_idx) in order.iter().enumerate() {
1499        let row = &entries[entry_idx].row_data;
1500        for col in 0..cols {
1501            let dest = row_pos * cols + col;
1502            if col < row.len() {
1503                values[dest] = row[col];
1504            }
1505        }
1506    }
1507
1508    let mut ia = Vec::with_capacity(unique_rows_count);
1509    for &entry_idx in &order {
1510        let entry = &entries[entry_idx];
1511        let occurrence = match opts.occurrence {
1512            UniqueOccurrence::First => entry.first,
1513            UniqueOccurrence::Last => entry.last,
1514        };
1515        ia.push((occurrence + 1) as f64);
1516    }
1517
1518    let mut ic = Vec::with_capacity(rows);
1519    for entry_idx in row_entry_index {
1520        let pos = entry_to_position[entry_idx];
1521        ic.push((pos + 1) as f64);
1522    }
1523
1524    let value_array = CharArray::new(values, unique_rows_count, cols)
1525        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1526    let ia_tensor = Tensor::new(ia, vec![unique_rows_count, 1])
1527        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1528    let ic_tensor = Tensor::new(ic, vec![rows, 1])
1529        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1530
1531    Ok(UniqueEvaluation::new(
1532        Value::CharArray(value_array),
1533        ia_tensor,
1534        ic_tensor,
1535    ))
1536}
1537
1538fn unique_string_array(
1539    array: StringArray,
1540    opts: &UniqueOptions,
1541) -> crate::BuiltinResult<UniqueEvaluation> {
1542    if opts.rows {
1543        unique_string_rows(array, opts)
1544    } else {
1545        unique_string_elements(array, opts)
1546    }
1547}
1548
1549fn unique_string_elements(
1550    array: StringArray,
1551    opts: &UniqueOptions,
1552) -> crate::BuiltinResult<UniqueEvaluation> {
1553    let shape = array.shape.clone();
1554    let len = array.data.len();
1555    if len == 0 {
1556        let values = StringArray::new(Vec::new(), unique_element_values_shape(&shape, 0))
1557            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1558        let ia = Tensor::new(Vec::new(), vec![0, 1])
1559            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1560        let ic = Tensor::new(Vec::new(), vec![0, 1])
1561            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1562        return Ok(UniqueEvaluation::new(Value::StringArray(values), ia, ic));
1563    }
1564
1565    let mut entries = Vec::<StringElementEntry>::new();
1566    let mut map: HashMap<String, usize> = HashMap::new();
1567    let mut element_entry_index = Vec::with_capacity(len);
1568
1569    for (idx, value) in array.data.iter().enumerate() {
1570        match map.get(value) {
1571            Some(&entry_idx) => {
1572                entries[entry_idx].last = idx;
1573                element_entry_index.push(entry_idx);
1574            }
1575            None => {
1576                let entry_idx = entries.len();
1577                entries.push(StringElementEntry {
1578                    value: value.clone(),
1579                    first: idx,
1580                    last: idx,
1581                });
1582                map.insert(value.clone(), entry_idx);
1583                element_entry_index.push(entry_idx);
1584            }
1585        }
1586    }
1587
1588    let mut order: Vec<usize> = (0..entries.len()).collect();
1589    if opts.order == UniqueOrder::Sorted {
1590        order.sort_by(|&a, &b| entries[a].value.cmp(&entries[b].value));
1591    }
1592
1593    let mut entry_to_position = vec![0usize; entries.len()];
1594    for (pos, &entry_idx) in order.iter().enumerate() {
1595        entry_to_position[entry_idx] = pos;
1596    }
1597
1598    let mut values = Vec::with_capacity(order.len());
1599    let mut ia = Vec::with_capacity(order.len());
1600    for &entry_idx in &order {
1601        let entry = &entries[entry_idx];
1602        values.push(entry.value.clone());
1603        let occurrence = match opts.occurrence {
1604            UniqueOccurrence::First => entry.first,
1605            UniqueOccurrence::Last => entry.last,
1606        };
1607        ia.push((occurrence + 1) as f64);
1608    }
1609
1610    let mut ic = Vec::with_capacity(len);
1611    for entry_idx in element_entry_index {
1612        let pos = entry_to_position[entry_idx];
1613        ic.push((pos + 1) as f64);
1614    }
1615
1616    let value_array = StringArray::new(values, unique_element_values_shape(&shape, order.len()))
1617        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1618    let ia_tensor = Tensor::new(ia, vec![order.len(), 1])
1619        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1620    let ic_tensor =
1621        Tensor::new(ic, vec![len, 1]).map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1622
1623    Ok(UniqueEvaluation::new(
1624        Value::StringArray(value_array),
1625        ia_tensor,
1626        ic_tensor,
1627    ))
1628}
1629
1630fn unique_string_rows(
1631    array: StringArray,
1632    opts: &UniqueOptions,
1633) -> crate::BuiltinResult<UniqueEvaluation> {
1634    if array.shape.len() != 2 {
1635        return Err(unique_error_with(
1636            &UNIQUE_ERROR_ROWS_REQUIRES_2D_MATRIX,
1637            UNIQUE_ERROR_ROWS_REQUIRES_2D_MATRIX.message,
1638        ));
1639    }
1640    let rows = array.shape[0];
1641    let cols = array.shape[1];
1642
1643    if rows == 0 {
1644        let values = StringArray::new(Vec::new(), vec![0, cols])
1645            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1646        let ia = Tensor::new(Vec::new(), vec![0, 1])
1647            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1648        let ic = Tensor::new(Vec::new(), vec![0, 1])
1649            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1650        return Ok(UniqueEvaluation::new(Value::StringArray(values), ia, ic));
1651    }
1652
1653    let mut entries = Vec::<StringRowEntry>::new();
1654    let mut map: HashMap<RowStringKey, usize> = HashMap::new();
1655    let mut row_entry_index = Vec::with_capacity(rows);
1656
1657    for r in 0..rows {
1658        let mut row_values = Vec::with_capacity(cols);
1659        for c in 0..cols {
1660            let idx = r + c * rows;
1661            row_values.push(array.data[idx].clone());
1662        }
1663        let key = RowStringKey(row_values.clone());
1664        match map.get(&key) {
1665            Some(&entry_idx) => {
1666                entries[entry_idx].last = r;
1667                row_entry_index.push(entry_idx);
1668            }
1669            None => {
1670                let entry_idx = entries.len();
1671                entries.push(StringRowEntry {
1672                    row_data: row_values.clone(),
1673                    first: r,
1674                    last: r,
1675                });
1676                map.insert(key, entry_idx);
1677                row_entry_index.push(entry_idx);
1678            }
1679        }
1680    }
1681
1682    let mut order: Vec<usize> = (0..entries.len()).collect();
1683    if opts.order == UniqueOrder::Sorted {
1684        order.sort_by(|&a, &b| compare_string_rows(&entries[a].row_data, &entries[b].row_data));
1685    }
1686
1687    let mut entry_to_position = vec![0usize; entries.len()];
1688    for (pos, &entry_idx) in order.iter().enumerate() {
1689        entry_to_position[entry_idx] = pos;
1690    }
1691
1692    let unique_rows_count = order.len();
1693    let mut values = vec![String::new(); unique_rows_count * cols];
1694    for (row_pos, &entry_idx) in order.iter().enumerate() {
1695        let row = &entries[entry_idx].row_data;
1696        for (col, value) in row.iter().enumerate().take(cols) {
1697            let dest = row_pos + col * unique_rows_count;
1698            values[dest] = value.clone();
1699        }
1700    }
1701
1702    let mut ia = Vec::with_capacity(unique_rows_count);
1703    for &entry_idx in &order {
1704        let entry = &entries[entry_idx];
1705        let occurrence = match opts.occurrence {
1706            UniqueOccurrence::First => entry.first,
1707            UniqueOccurrence::Last => entry.last,
1708        };
1709        ia.push((occurrence + 1) as f64);
1710    }
1711
1712    let mut ic = Vec::with_capacity(rows);
1713    for entry_idx in row_entry_index {
1714        let pos = entry_to_position[entry_idx];
1715        ic.push((pos + 1) as f64);
1716    }
1717
1718    let value_array = StringArray::new(values, vec![unique_rows_count, cols])
1719        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1720    let ia_tensor = Tensor::new(ia, vec![unique_rows_count, 1])
1721        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1722    let ic_tensor = Tensor::new(ic, vec![rows, 1])
1723        .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1724
1725    Ok(UniqueEvaluation::new(
1726        Value::StringArray(value_array),
1727        ia_tensor,
1728        ic_tensor,
1729    ))
1730}
1731
1732#[derive(Debug)]
1733struct FloatingElementEntry<T> {
1734    value: T,
1735    first: usize,
1736    last: usize,
1737}
1738
1739#[derive(Debug)]
1740struct IntegerElementEntry {
1741    value: IntValue,
1742    first: usize,
1743    last: usize,
1744}
1745
1746#[derive(Debug)]
1747struct IntegerRowEntry {
1748    row_data: Vec<IntValue>,
1749    first: usize,
1750    last: usize,
1751}
1752
1753#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1754struct FloatingRowKey(Vec<u64>);
1755
1756impl FloatingRowKey {
1757    fn from_slice<T: SetFloat>(values: &[T]) -> Self {
1758        Self(values.iter().map(|&value| value.canonical_key()).collect())
1759    }
1760}
1761
1762#[derive(Debug, Clone)]
1763struct FloatingRowEntry<T> {
1764    row_data: Vec<T>,
1765    first: usize,
1766    last: usize,
1767}
1768
1769#[derive(Debug)]
1770struct ComplexElementEntry<T> {
1771    value: (T, T),
1772    first: usize,
1773    last: usize,
1774}
1775
1776#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1777struct ComplexKey {
1778    re: u64,
1779    im: u64,
1780}
1781
1782impl ComplexKey {
1783    fn new<T: SetFloat>(value: (T, T)) -> Self {
1784        Self {
1785            re: value.0.canonical_key(),
1786            im: value.1.canonical_key(),
1787        }
1788    }
1789}
1790
1791#[derive(Debug, Clone)]
1792struct ComplexRowEntry<T> {
1793    row_data: Vec<(T, T)>,
1794    first: usize,
1795    last: usize,
1796}
1797
1798#[derive(Debug)]
1799struct CharElementEntry {
1800    ch: char,
1801    first: usize,
1802    last: usize,
1803}
1804
1805#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1806struct RowCharKey(Vec<u32>);
1807
1808impl RowCharKey {
1809    fn from_slice(values: &[char]) -> Self {
1810        RowCharKey(values.iter().map(|&ch| ch as u32).collect())
1811    }
1812}
1813
1814#[derive(Debug, Clone)]
1815struct CharRowEntry {
1816    row_data: Vec<char>,
1817    first: usize,
1818    last: usize,
1819}
1820
1821#[derive(Debug, Clone)]
1822struct StringElementEntry {
1823    value: String,
1824    first: usize,
1825    last: usize,
1826}
1827
1828#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1829struct RowStringKey(Vec<String>);
1830
1831#[derive(Debug, Clone)]
1832struct StringRowEntry {
1833    row_data: Vec<String>,
1834    first: usize,
1835    last: usize,
1836}
1837
1838fn compare_floating_rows<T: SetFloat>(a: &[T], b: &[T]) -> Ordering {
1839    for (lhs, rhs) in a.iter().zip(b.iter()) {
1840        let ord = lhs.compare(*rhs);
1841        if ord != Ordering::Equal {
1842            return ord;
1843        }
1844    }
1845    Ordering::Equal
1846}
1847
1848fn compare_integer_rows(a: &[IntValue], b: &[IntValue]) -> Ordering {
1849    for (lhs, rhs) in a.iter().zip(b.iter()) {
1850        let ordering = integer_order::compare(lhs, rhs, false, false);
1851        if ordering != Ordering::Equal {
1852            return ordering;
1853        }
1854    }
1855    Ordering::Equal
1856}
1857
1858fn complex_is_nan<T: SetFloat>(value: (T, T)) -> bool {
1859    value.0.is_nan() || value.1.is_nan()
1860}
1861
1862fn compare_complex<T: SetFloat>(a: (T, T), b: (T, T)) -> Ordering {
1863    match (complex_is_nan(a), complex_is_nan(b)) {
1864        (true, true) => Ordering::Equal,
1865        (true, false) => Ordering::Greater,
1866        (false, true) => Ordering::Less,
1867        (false, false) => {
1868            let mag_a = a.0.hypot(a.1);
1869            let mag_b = b.0.hypot(b.1);
1870            let mag_cmp = mag_a.compare(mag_b);
1871            if mag_cmp != Ordering::Equal {
1872                return mag_cmp;
1873            }
1874            let re_cmp = a.0.compare(b.0);
1875            if re_cmp != Ordering::Equal {
1876                return re_cmp;
1877            }
1878            a.1.compare(b.1)
1879        }
1880    }
1881}
1882
1883fn compare_complex_rows<T: SetFloat>(a: &[(T, T)], b: &[(T, T)]) -> Ordering {
1884    for (lhs, rhs) in a.iter().zip(b.iter()) {
1885        let ord = compare_complex(*lhs, *rhs);
1886        if ord != Ordering::Equal {
1887            return ord;
1888        }
1889    }
1890    Ordering::Equal
1891}
1892
1893fn compare_char_rows(a: &[char], b: &[char]) -> Ordering {
1894    for (lhs, rhs) in a.iter().zip(b.iter()) {
1895        let ord = lhs.cmp(rhs);
1896        if ord != Ordering::Equal {
1897            return ord;
1898        }
1899    }
1900    Ordering::Equal
1901}
1902
1903fn compare_string_rows(a: &[String], b: &[String]) -> Ordering {
1904    for (lhs, rhs) in a.iter().zip(b.iter()) {
1905        let ord = lhs.cmp(rhs);
1906        if ord != Ordering::Equal {
1907            return ord;
1908        }
1909    }
1910    Ordering::Equal
1911}
1912
1913#[derive(Debug)]
1914pub struct UniqueEvaluation {
1915    values: Value,
1916    ia: Tensor,
1917    ic: Tensor,
1918}
1919
1920impl UniqueEvaluation {
1921    fn new(values: Value, ia: Tensor, ic: Tensor) -> Self {
1922        Self { values, ia, ic }
1923    }
1924
1925    pub fn into_values_value(self) -> Value {
1926        self.values
1927    }
1928
1929    fn into_logical_values(mut self) -> crate::BuiltinResult<Self> {
1930        self.values = match self.values {
1931            Value::Num(value) => Value::Bool(value != 0.0),
1932            Value::Tensor(tensor) => {
1933                let shape = tensor.shape.clone();
1934                let data = tensor
1935                    .materialize_f64()
1936                    .into_iter()
1937                    .map(|value| u8::from(value != 0.0))
1938                    .collect();
1939                Value::LogicalArray(
1940                    LogicalArray::new(data, shape)
1941                        .map_err(|error| unique_internal_error(format!("unique: {error}")))?,
1942                )
1943            }
1944            other => {
1945                return Err(unique_internal_error(format!(
1946                    "unique: cannot restore logical values from {other:?}"
1947                )));
1948            }
1949        };
1950        Ok(self)
1951    }
1952
1953    pub fn into_pair(self) -> (Value, Value) {
1954        let ia = tensor::tensor_into_value(self.ia);
1955        (self.values, ia)
1956    }
1957
1958    pub fn into_triple(self) -> (Value, Value, Value) {
1959        let ia = tensor::tensor_into_value(self.ia);
1960        let ic = tensor::tensor_into_value(self.ic);
1961        (self.values, ia, ic)
1962    }
1963
1964    pub fn from_unique_result(result: UniqueResult) -> crate::BuiltinResult<Self> {
1965        let UniqueResult { values, ia, ic } = result;
1966        let values_tensor = Tensor::new(values.data, values.shape)
1967            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1968        let ia_tensor = Tensor::new(ia.data, ia.shape)
1969            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1970        let ic_tensor = Tensor::new(ic.data, ic.shape)
1971            .map_err(|e| unique_internal_error(format!("unique: {e}")))?;
1972        Ok(UniqueEvaluation::new(
1973            tensor::tensor_into_value(values_tensor),
1974            ia_tensor,
1975            ic_tensor,
1976        ))
1977    }
1978
1979    pub fn into_numeric_unique_result(self) -> crate::BuiltinResult<UniqueResult> {
1980        let UniqueEvaluation { values, ia, ic } = self;
1981        let values_tensor = tensor::value_into_tensor_for("unique", values)
1982            .map_err(|e| unique_internal_error(e))?;
1983        Ok(UniqueResult {
1984            values: tensor::tensor_into_host_f64_owned(values_tensor),
1985            ia: tensor::tensor_into_host_f64_owned(ia),
1986            ic: tensor::tensor_into_host_f64_owned(ic),
1987        })
1988    }
1989
1990    pub fn ia_value(&self) -> Value {
1991        tensor::tensor_into_value(self.ia.clone())
1992    }
1993
1994    pub fn ic_value(&self) -> Value {
1995        tensor::tensor_into_value(self.ic.clone())
1996    }
1997}
1998
1999#[cfg(test)]
2000pub(crate) mod tests {
2001    use super::*;
2002    use crate::builtins::common::test_support;
2003    use runmat_builtins::{LiteralValue, ResolveContext, Type};
2004    use runmat_value::{
2005        CharArray, IntValue, IntegerStorage, LogicalArray, StringArray, Tensor, Value,
2006    };
2007
2008    fn evaluate_sync(value: Value, rest: &[Value]) -> crate::BuiltinResult<UniqueEvaluation> {
2009        futures::executor::block_on(evaluate(value, rest))
2010    }
2011
2012    fn builtin_sync(value: Value, rest: Vec<Value>) -> crate::BuiltinResult<Value> {
2013        futures::executor::block_on(unique_builtin(value, rest))
2014    }
2015
2016    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2017    #[test]
2018    fn unique_sorted_default() {
2019        let tensor = Tensor::new(vec![3.0, 1.0, 3.0, 2.0], vec![4, 1]).unwrap();
2020        let eval = evaluate_sync(Value::Tensor(tensor), &[]).expect("unique");
2021        let (values, ia, ic) = eval.into_triple();
2022        match values {
2023            Value::Tensor(t) => {
2024                assert_eq!(t.materialize_f64(), vec![1.0, 2.0, 3.0]);
2025                assert_eq!(t.shape, vec![3, 1]);
2026            }
2027            Value::Num(_) => panic!("expected tensor result"),
2028            other => panic!("unexpected result {other:?}"),
2029        }
2030        match ia {
2031            Value::Tensor(t) => assert_eq!(t.materialize_f64(), vec![2.0, 4.0, 1.0]),
2032            other => panic!("unexpected IA {other:?}"),
2033        }
2034        match ic {
2035            Value::Tensor(t) => assert_eq!(t.materialize_f64(), vec![3.0, 1.0, 3.0, 2.0]),
2036            other => panic!("unexpected IC {other:?}"),
2037        }
2038    }
2039
2040    #[test]
2041    fn unique_type_resolver_numeric() {
2042        assert_eq!(
2043            unique_values_output_type(
2044                &[Type::Tensor {
2045                    shape: Some(vec![Some(1), Some(4)])
2046                }],
2047                &ResolveContext::new(vec![LiteralValue::Unknown]),
2048            ),
2049            Type::Tensor {
2050                shape: Some(vec![Some(1), None])
2051            }
2052        );
2053        assert_eq!(
2054            unique_values_output_type(
2055                &[Type::Tensor {
2056                    shape: Some(vec![Some(4), Some(1)])
2057                }],
2058                &ResolveContext::new(vec![LiteralValue::Unknown]),
2059            ),
2060            Type::Tensor {
2061                shape: Some(vec![None, Some(1)])
2062            }
2063        );
2064        assert_eq!(
2065            unique_values_output_type(
2066                &[
2067                    Type::Tensor {
2068                        shape: Some(vec![Some(4), Some(3)])
2069                    },
2070                    Type::String,
2071                ],
2072                &ResolveContext::new(vec![
2073                    LiteralValue::Unknown,
2074                    LiteralValue::String("rows".into()),
2075                ]),
2076            ),
2077            Type::Tensor {
2078                shape: Some(vec![None, Some(3)])
2079            }
2080        );
2081    }
2082
2083    #[test]
2084    fn unique_type_resolver_string_array() {
2085        assert_eq!(
2086            unique_values_output_type(
2087                &[Type::cell_of(Type::String)],
2088                &ResolveContext::new(Vec::new()),
2089            ),
2090            Type::cell_of(Type::String)
2091        );
2092    }
2093
2094    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2095    #[test]
2096    fn unique_sorted_handles_nan() {
2097        let tensor = Tensor::new(vec![f64::NAN, 2.0, f64::NAN, 1.0], vec![4, 1]).unwrap();
2098        let eval = evaluate_sync(Value::Tensor(tensor), &[]).expect("unique");
2099        let (values, ..) = eval.into_triple();
2100        match values {
2101            Value::Tensor(t) => {
2102                assert_eq!(t.materialize_f64().len(), 4);
2103                assert_eq!(t.materialize_f64()[0], 1.0);
2104                assert_eq!(t.materialize_f64()[1], 2.0);
2105                assert!(t.materialize_f64()[2].is_nan());
2106                assert!(t.materialize_f64()[3].is_nan());
2107            }
2108            other => panic!("unexpected values {other:?}"),
2109        }
2110    }
2111
2112    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2113    #[test]
2114    fn unique_stable_with_nan() {
2115        let tensor = Tensor::new(vec![f64::NAN, 2.0, f64::NAN, 1.0], vec![4, 1]).unwrap();
2116        let eval = evaluate_sync(Value::Tensor(tensor), &[Value::from("stable")]).expect("unique");
2117        let (values, ..) = eval.into_triple();
2118        match values {
2119            Value::Tensor(t) => {
2120                assert!(t.materialize_f64()[0].is_nan());
2121                assert_eq!(t.materialize_f64()[1], 2.0);
2122                assert!(t.materialize_f64()[2].is_nan());
2123                assert_eq!(t.materialize_f64()[3], 1.0);
2124            }
2125            other => panic!("unexpected values {other:?}"),
2126        }
2127    }
2128
2129    #[test]
2130    fn unique_treat_missing_as_distinct_name_value_controls_nan_grouping() {
2131        let tensor = Tensor::new(vec![f64::NAN, 2.0, f64::NAN], vec![3, 1]).unwrap();
2132        let collapsed = evaluate_sync(
2133            Value::Tensor(tensor.clone()),
2134            &[Value::from("TreatMissingAsDistinct"), Value::Bool(false)],
2135        )
2136        .expect("collapsed missing values")
2137        .into_values_value();
2138        let Value::Tensor(collapsed) = collapsed else {
2139            panic!("expected tensor");
2140        };
2141        assert_eq!(collapsed.materialize_f64().len(), 2);
2142        assert!(collapsed.materialize_f64()[1].is_nan());
2143
2144        let distinct = evaluate_sync(
2145            Value::Tensor(tensor),
2146            &[
2147                Value::from("TreatMissingAsDistinct"),
2148                Value::Int(IntValue::U8(1)),
2149            ],
2150        )
2151        .expect("distinct missing values")
2152        .into_values_value();
2153        let Value::Tensor(distinct) = distinct else {
2154            panic!("expected tensor");
2155        };
2156        assert_eq!(distinct.materialize_f64().len(), 3);
2157
2158        let err = parse_options(&[Value::from("TreatMissingAsDistinct"), Value::Num(2.0)])
2159            .expect_err("non-logical flag must fail");
2160        assert_eq!(err.identifier(), UNIQUE_ERROR_INVALID_ARGUMENT.identifier);
2161    }
2162
2163    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2164    #[test]
2165    fn unique_stable_preserves_order() {
2166        let tensor = Tensor::new(vec![4.0, 2.0, 4.0, 1.0, 2.0], vec![5, 1]).unwrap();
2167        let eval = evaluate_sync(Value::Tensor(tensor), &[Value::from("stable")]).expect("unique");
2168        let (values, ia) = eval.into_pair();
2169        match values {
2170            Value::Tensor(t) => assert_eq!(t.materialize_f64(), vec![4.0, 2.0, 1.0]),
2171            other => panic!("unexpected values {other:?}"),
2172        }
2173        match ia {
2174            Value::Tensor(t) => assert_eq!(t.materialize_f64(), vec![1.0, 2.0, 4.0]),
2175            other => panic!("unexpected IA {other:?}"),
2176        }
2177    }
2178
2179    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2180    #[test]
2181    fn unique_last_occurrence() {
2182        let tensor = Tensor::new(vec![9.0, 8.0, 9.0, 7.0, 8.0], vec![5, 1]).unwrap();
2183        let eval = evaluate_sync(Value::Tensor(tensor), &[Value::from("last")]).expect("unique");
2184        let (values, ia, ic) = eval.into_triple();
2185        match values {
2186            Value::Tensor(t) => assert_eq!(t.materialize_f64(), vec![7.0, 8.0, 9.0]),
2187            other => panic!("unexpected values {other:?}"),
2188        }
2189        match ia {
2190            Value::Tensor(t) => assert_eq!(t.materialize_f64(), vec![4.0, 5.0, 3.0]),
2191            other => panic!("unexpected IA {other:?}"),
2192        }
2193        match ic {
2194            Value::Tensor(t) => assert_eq!(t.materialize_f64(), vec![3.0, 2.0, 3.0, 1.0, 2.0]),
2195            other => panic!("unexpected IC {other:?}"),
2196        }
2197    }
2198
2199    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2200    #[test]
2201    fn unique_rows_sorted_default() {
2202        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();
2203        let eval = evaluate_sync(Value::Tensor(tensor), &[Value::from("rows")]).expect("unique");
2204        let (values, ia, ic) = eval.into_triple();
2205        match values {
2206            Value::Tensor(t) => {
2207                assert_eq!(t.shape, vec![3, 2]);
2208                assert_eq!(t.materialize_f64(), vec![1.0, 1.0, 2.0, 2.0, 3.0, 4.0]);
2209            }
2210            other => panic!("unexpected values {other:?}"),
2211        }
2212        match ia {
2213            Value::Tensor(t) => assert_eq!(t.materialize_f64(), vec![4.0, 1.0, 3.0]),
2214            other => panic!("unexpected IA {other:?}"),
2215        }
2216        match ic {
2217            Value::Tensor(t) => assert_eq!(t.materialize_f64(), vec![2.0, 2.0, 3.0, 1.0]),
2218            other => panic!("unexpected IC {other:?}"),
2219        }
2220    }
2221
2222    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2223    #[test]
2224    fn unique_rows_stable_last() {
2225        let tensor = Tensor::new(vec![1.0, 1.0, 2.0, 1.0, 1.0, 2.0], vec![3, 2]).unwrap();
2226        let eval = evaluate_sync(
2227            Value::Tensor(tensor),
2228            &[
2229                Value::from("rows"),
2230                Value::from("stable"),
2231                Value::from("last"),
2232            ],
2233        )
2234        .expect("unique");
2235        let (values, ia, ic) = eval.into_triple();
2236        match values {
2237            Value::Tensor(t) => {
2238                assert_eq!(t.shape, vec![2, 2]);
2239                assert_eq!(t.materialize_f64(), vec![1.0, 2.0, 1.0, 2.0]);
2240            }
2241            other => panic!("unexpected values {other:?}"),
2242        }
2243        match ia {
2244            Value::Tensor(t) => assert_eq!(t.materialize_f64(), vec![2.0, 3.0]),
2245            other => panic!("unexpected IA {other:?}"),
2246        }
2247        match ic {
2248            Value::Tensor(t) => assert_eq!(t.materialize_f64(), vec![1.0, 1.0, 2.0]),
2249            other => panic!("unexpected IC {other:?}"),
2250        }
2251    }
2252
2253    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2254    #[test]
2255    fn unique_char_elements_sorted() {
2256        let chars = CharArray::new(vec!['m', 'z', 'm', 'a'], 2, 2).unwrap();
2257        let eval = evaluate_sync(Value::CharArray(chars), &[]).expect("unique");
2258        let (values, ia, ic) = eval.into_triple();
2259        match values {
2260            Value::CharArray(arr) => {
2261                assert_eq!(arr.rows, 3);
2262                assert_eq!(arr.cols, 1);
2263                assert_eq!(arr.data, vec!['a', 'm', 'z']);
2264            }
2265            other => panic!("unexpected values {other:?}"),
2266        }
2267        match ia {
2268            Value::Tensor(t) => assert_eq!(t.materialize_f64(), vec![4.0, 1.0, 3.0]),
2269            other => panic!("unexpected IA {other:?}"),
2270        }
2271        match ic {
2272            Value::Tensor(t) => assert_eq!(t.materialize_f64(), vec![2.0, 2.0, 3.0, 1.0]),
2273            other => panic!("unexpected IC {other:?}"),
2274        }
2275    }
2276
2277    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2278    #[test]
2279    fn unique_char_rows_last() {
2280        let chars = CharArray::new(vec!['a', 'b', 'a', 'b', 'a', 'c'], 3, 2).unwrap();
2281        let eval = evaluate_sync(
2282            Value::CharArray(chars),
2283            &[Value::from("rows"), Value::from("last")],
2284        )
2285        .expect("unique");
2286        let (values, ia, ic) = eval.into_triple();
2287        match values {
2288            Value::CharArray(arr) => {
2289                assert_eq!(arr.rows, 2);
2290                assert_eq!(arr.cols, 2);
2291                assert_eq!(arr.data, vec!['a', 'b', 'a', 'c']);
2292            }
2293            other => panic!("unexpected values {other:?}"),
2294        }
2295        match ia {
2296            Value::Tensor(t) => assert_eq!(t.materialize_f64(), vec![2.0, 3.0]),
2297            other => panic!("unexpected IA {other:?}"),
2298        }
2299        match ic {
2300            Value::Tensor(t) => assert_eq!(t.materialize_f64(), vec![1.0, 1.0, 2.0]),
2301            other => panic!("unexpected IC {other:?}"),
2302        }
2303    }
2304
2305    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2306    #[test]
2307    fn unique_string_elements_stable() {
2308        let array = StringArray::new(
2309            vec!["beta".into(), "alpha".into(), "beta".into()],
2310            vec![3, 1],
2311        )
2312        .unwrap();
2313        let eval =
2314            evaluate_sync(Value::StringArray(array), &[Value::from("stable")]).expect("unique");
2315        let (values, ia, ic) = eval.into_triple();
2316        match values {
2317            Value::StringArray(sa) => {
2318                assert_eq!(sa.data, vec!["beta", "alpha"]);
2319                assert_eq!(sa.shape, vec![2, 1]);
2320            }
2321            other => panic!("unexpected values {other:?}"),
2322        }
2323        match ia {
2324            Value::Tensor(t) => assert_eq!(t.materialize_f64(), vec![1.0, 2.0]),
2325            other => panic!("unexpected IA {other:?}"),
2326        }
2327        match ic {
2328            Value::Tensor(t) => assert_eq!(t.materialize_f64(), vec![1.0, 2.0, 1.0]),
2329            other => panic!("unexpected IC {other:?}"),
2330        }
2331    }
2332
2333    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2334    #[test]
2335    fn unique_string_rows() {
2336        let array = StringArray::new(
2337            vec![
2338                "alpha".into(),
2339                "alpha".into(),
2340                "gamma".into(),
2341                "beta".into(),
2342                "beta".into(),
2343                "beta".into(),
2344            ],
2345            vec![3, 2],
2346        )
2347        .unwrap();
2348        let eval = evaluate_sync(
2349            Value::StringArray(array),
2350            &[Value::from("rows"), Value::from("stable")],
2351        )
2352        .expect("unique");
2353        let (values, ia, ic) = eval.into_triple();
2354        match values {
2355            Value::StringArray(sa) => {
2356                assert_eq!(sa.shape, vec![2, 2]);
2357                assert_eq!(sa.data, vec!["alpha", "gamma", "beta", "beta"]);
2358            }
2359            other => panic!("unexpected values {other:?}"),
2360        }
2361        match ia {
2362            Value::Tensor(t) => assert_eq!(t.materialize_f64(), vec![1.0, 3.0]),
2363            other => panic!("unexpected IA {other:?}"),
2364        }
2365        match ic {
2366            Value::Tensor(t) => assert_eq!(t.materialize_f64(), vec![1.0, 1.0, 2.0]),
2367            other => panic!("unexpected IC {other:?}"),
2368        }
2369    }
2370
2371    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2372    #[test]
2373    fn unique_complex_sorted() {
2374        let tensor = ComplexTensor::new(
2375            vec![(1.0, 1.0), (0.0, 2.0), (1.0, -1.0), (0.0, 2.0)],
2376            vec![4, 1],
2377        )
2378        .unwrap();
2379        let eval = evaluate_sync(Value::ComplexTensor(tensor), &[]).expect("unique");
2380        let (values, ..) = eval.into_triple();
2381        match values {
2382            Value::ComplexTensor(t) => {
2383                assert_eq!(t.materialize_f64().len(), 3);
2384                assert_eq!(t.materialize_f64()[0], (1.0, -1.0));
2385                assert_eq!(t.materialize_f64()[1], (1.0, 1.0));
2386                assert_eq!(t.materialize_f64()[2], (0.0, 2.0));
2387            }
2388            other => panic!("unexpected values {other:?}"),
2389        }
2390    }
2391
2392    #[test]
2393    fn unique_preserves_native_single_elements_and_rows() {
2394        let elements = Tensor::from_f32(vec![3.0, 1.0, 3.0, 2.0], vec![4, 1]).unwrap();
2395        let values = evaluate_sync(Value::Tensor(elements), &[])
2396            .expect("unique single elements")
2397            .into_values_value();
2398        let Value::Tensor(values) = values else {
2399            panic!("expected native single values");
2400        };
2401        assert_eq!(
2402            values.into_numeric_storage().unwrap(),
2403            NumericStorage::F32(vec![1.0, 2.0, 3.0])
2404        );
2405
2406        let rows = Tensor::from_f32(vec![2.0, 1.0, 2.0, 20.0, 10.0, 20.0], vec![3, 2]).unwrap();
2407        let values = evaluate_sync(Value::Tensor(rows), &[Value::from("rows")])
2408            .expect("unique single rows")
2409            .into_values_value();
2410        let Value::Tensor(values) = values else {
2411            panic!("expected native single rows");
2412        };
2413        assert_eq!(values.shape, vec![2, 2]);
2414        assert_eq!(
2415            values.into_numeric_storage().unwrap(),
2416            NumericStorage::F32(vec![1.0, 2.0, 10.0, 20.0])
2417        );
2418    }
2419
2420    #[test]
2421    fn unique_preserves_native_complex_single_elements_and_rows() {
2422        let elements = ComplexTensor::from_f32(
2423            vec![(1.0, 1.0), (0.0, 2.0), (1.0, -1.0), (0.0, 2.0)],
2424            vec![4, 1],
2425        )
2426        .unwrap();
2427        let values = evaluate_sync(Value::ComplexTensor(elements), &[])
2428            .expect("unique complex single elements")
2429            .into_values_value();
2430        let Value::ComplexTensor(values) = values else {
2431            panic!("expected native complex single values");
2432        };
2433        assert_eq!(
2434            values.as_f32_slice(),
2435            Some(&[(1.0, -1.0), (1.0, 1.0), (0.0, 2.0)][..])
2436        );
2437
2438        let rows = ComplexTensor::from_f32(
2439            vec![
2440                (2.0, 0.0),
2441                (1.0, 1.0),
2442                (2.0, 0.0),
2443                (20.0, 0.0),
2444                (10.0, -1.0),
2445                (20.0, 0.0),
2446            ],
2447            vec![3, 2],
2448        )
2449        .unwrap();
2450        let values = evaluate_sync(
2451            Value::ComplexTensor(rows),
2452            &[Value::from("rows"), Value::from("stable")],
2453        )
2454        .expect("unique complex single rows")
2455        .into_values_value();
2456        let Value::ComplexTensor(values) = values else {
2457            panic!("expected native complex single rows");
2458        };
2459        assert_eq!(values.shape, vec![2, 2]);
2460        assert_eq!(
2461            values.as_f32_slice(),
2462            Some(&[(2.0, 0.0), (1.0, 1.0), (20.0, 0.0), (10.0, -1.0),][..])
2463        );
2464    }
2465
2466    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2467    #[test]
2468    fn unique_handles_logical_arrays() {
2469        let logical = LogicalArray::new(vec![1, 0, 1, 1], vec![1, 4]).unwrap();
2470        let eval = evaluate_sync(Value::LogicalArray(logical), &[]).expect("unique");
2471        let values = eval.into_values_value();
2472        match values {
2473            Value::LogicalArray(values) => {
2474                assert_eq!(values.shape, vec![1, 2]);
2475                assert_eq!(values.data, vec![0, 1]);
2476            }
2477            other => panic!("unexpected values {other:?}"),
2478        }
2479    }
2480
2481    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2482    #[test]
2483    fn unique_gpu_roundtrip() {
2484        test_support::with_test_provider(|provider| {
2485            let tensor = Tensor::new(vec![5.0, 3.0, 5.0, 1.0], vec![1, 4]).unwrap();
2486            let view = runmat_accelerate_api::HostTensorView {
2487                data: &tensor.materialize_f64(),
2488                shape: &tensor.shape,
2489            };
2490            let handle = provider.upload(&view).expect("upload");
2491            let eval =
2492                evaluate_sync(Value::GpuTensor(handle), &[Value::from("stable")]).expect("unique");
2493            let values = eval.into_values_value();
2494            match values {
2495                Value::Tensor(t) => {
2496                    assert_eq!(t.shape, vec![1, 3]);
2497                    assert_eq!(t.materialize_f64(), vec![5.0, 3.0, 1.0]);
2498                }
2499                other => panic!("unexpected values {other:?}"),
2500            }
2501        });
2502    }
2503
2504    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2505    #[test]
2506    #[cfg(feature = "wgpu")]
2507    fn unique_wgpu_matches_cpu() {
2508        let _ = runmat_accelerate::backend::wgpu::provider::register_wgpu_provider(
2509            runmat_accelerate::backend::wgpu::provider::WgpuProviderOptions::default(),
2510        );
2511        let tensor = Tensor::new(vec![5.0, 3.0, 5.0, 1.0, 2.0], vec![5, 1]).unwrap();
2512        let host_eval = evaluate_sync(Value::Tensor(tensor.clone()), &[]).expect("host unique");
2513        let (host_values, host_ia, host_ic) = host_eval.into_triple();
2514
2515        let provider = runmat_accelerate_api::provider().expect("provider registered");
2516        let view = runmat_accelerate_api::HostTensorView {
2517            data: &tensor.materialize_f64(),
2518            shape: &tensor.shape,
2519        };
2520        let handle = provider.upload(&view).expect("upload");
2521        let gpu_eval = evaluate_sync(Value::GpuTensor(handle.clone()), &[]).expect("gpu unique");
2522        let (gpu_values, gpu_ia, gpu_ic) = gpu_eval.into_triple();
2523        let _ = provider.free(&handle);
2524
2525        let host_values = test_support::gather(host_values).expect("gather host values");
2526        let host_ia = test_support::gather(host_ia).expect("gather host ia");
2527        let host_ic = test_support::gather(host_ic).expect("gather host ic");
2528        let gpu_values = test_support::gather(gpu_values).expect("gather gpu values");
2529        let gpu_ia = test_support::gather(gpu_ia).expect("gather gpu ia");
2530        let gpu_ic = test_support::gather(gpu_ic).expect("gather gpu ic");
2531
2532        assert_eq!(gpu_values.shape, host_values.shape);
2533        assert_eq!(gpu_values.materialize_f64(), host_values.materialize_f64());
2534        assert_eq!(gpu_ia.materialize_f64(), host_ia.materialize_f64());
2535        assert_eq!(gpu_ic.materialize_f64(), host_ic.materialize_f64());
2536    }
2537
2538    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2539    #[test]
2540    fn unique_rejects_legacy_option() {
2541        let tensor = Tensor::new(vec![1.0, 1.0], vec![2, 1]).unwrap();
2542        let err = evaluate_sync(Value::Tensor(tensor), &[Value::from("legacy")]).unwrap_err();
2543        assert_eq!(
2544            err.identifier(),
2545            UNIQUE_ERROR_LEGACY_OPTION_UNSUPPORTED.identifier
2546        );
2547    }
2548
2549    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2550    #[test]
2551    fn unique_conflicting_order_flags() {
2552        let tensor = Tensor::new(vec![1.0, 2.0], vec![2, 1]).unwrap();
2553        let err = evaluate_sync(
2554            Value::Tensor(tensor),
2555            &[Value::from("stable"), Value::from("sorted")],
2556        )
2557        .unwrap_err();
2558        assert_eq!(
2559            err.identifier(),
2560            UNIQUE_ERROR_CONFLICTING_ORDER_OPTIONS.identifier
2561        );
2562    }
2563
2564    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2565    #[test]
2566    fn unique_conflicting_occurrence_flags() {
2567        let tensor = Tensor::new(vec![1.0, 2.0], vec![2, 1]).unwrap();
2568        let err = evaluate_sync(
2569            Value::Tensor(tensor),
2570            &[Value::from("first"), Value::from("last")],
2571        )
2572        .unwrap_err();
2573        assert_eq!(
2574            err.identifier(),
2575            UNIQUE_ERROR_CONFLICTING_OCCURRENCE_OPTIONS.identifier
2576        );
2577    }
2578
2579    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2580    #[test]
2581    fn unique_rejects_unknown_option() {
2582        let tensor = Tensor::new(vec![1.0, 2.0], vec![2, 1]).unwrap();
2583        let err = evaluate_sync(Value::Tensor(tensor), &[Value::from("bogus")]).unwrap_err();
2584        assert_eq!(err.identifier(), UNIQUE_ERROR_UNKNOWN_OPTION.identifier);
2585    }
2586
2587    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2588    #[test]
2589    fn unique_rows_requires_two_dimensional_input() {
2590        let tensor = Tensor::new(vec![1.0, 2.0], vec![2, 1, 1]).unwrap();
2591        let err = evaluate_sync(Value::Tensor(tensor), &[Value::from("rows")]).unwrap_err();
2592        assert_eq!(
2593            err.identifier(),
2594            UNIQUE_ERROR_ROWS_REQUIRES_2D_MATRIX.identifier
2595        );
2596
2597        let chars = CharArray::from_column_major(vec!['a', 'b'], vec![1, 2, 1]).unwrap();
2598        let err = evaluate_sync(Value::CharArray(chars), &[Value::from("rows")]).unwrap_err();
2599        assert_eq!(
2600            err.identifier(),
2601            UNIQUE_ERROR_ROWS_REQUIRES_2D_MATRIX.identifier
2602        );
2603    }
2604
2605    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2606    #[test]
2607    fn unique_handles_empty_rows() {
2608        let tensor = Tensor::new(Vec::new(), vec![0, 3]).unwrap();
2609        let eval = evaluate_sync(Value::Tensor(tensor), &[Value::from("rows")]).expect("unique");
2610        let (values, ia, ic) = eval.into_triple();
2611        match values {
2612            Value::Tensor(t) => {
2613                assert_eq!(t.shape, vec![0, 3]);
2614                assert!(t.materialize_f64().is_empty());
2615            }
2616            other => panic!("unexpected values {other:?}"),
2617        }
2618        match ia {
2619            Value::Tensor(t) => assert!(t.materialize_f64().is_empty()),
2620            other => panic!("unexpected IA {other:?}"),
2621        }
2622        match ic {
2623            Value::Tensor(t) => assert!(t.materialize_f64().is_empty()),
2624            other => panic!("unexpected IC {other:?}"),
2625        }
2626    }
2627
2628    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2629    #[test]
2630    fn unique_accepts_integer_scalars() {
2631        let eval = evaluate_sync(Value::Int(IntValue::I32(42)), &[]).expect("unique");
2632        let values = eval.into_values_value();
2633        match values {
2634            Value::Tensor(t) => {
2635                assert_eq!(t.integer_storage(), Some(&IntegerStorage::I32(vec![42])))
2636            }
2637            other => panic!("unexpected values {other:?}"),
2638        }
2639    }
2640
2641    #[test]
2642    fn unique_preserves_exact_integer_elements_rows_and_indices() {
2643        let input = Tensor::new_integer(
2644            IntegerStorage::U64(vec![u64::MAX, 0, 9_007_199_254_740_993, u64::MAX]),
2645            vec![4, 1],
2646        )
2647        .expect("input");
2648        let (values, ia, ic) = evaluate_sync(Value::Tensor(input), &[])
2649            .expect("unique")
2650            .into_triple();
2651        let Value::Tensor(values) = values else {
2652            panic!("expected exact integer values");
2653        };
2654        assert_eq!(
2655            values.integer_storage(),
2656            Some(&IntegerStorage::U64(vec![
2657                0,
2658                9_007_199_254_740_993,
2659                u64::MAX
2660            ]))
2661        );
2662        let Value::Tensor(ia) = ia else {
2663            panic!("expected indices");
2664        };
2665        assert_eq!(ia.materialize_f64(), vec![2.0, 3.0, 1.0]);
2666        let Value::Tensor(ic) = ic else {
2667            panic!("expected indices");
2668        };
2669        assert_eq!(ic.materialize_f64(), vec![3.0, 1.0, 2.0, 3.0]);
2670
2671        let rows = Tensor::new_integer(
2672            IntegerStorage::I64(vec![i64::MAX, i64::MIN, i64::MAX, 1, 2, 1]),
2673            vec![3, 2],
2674        )
2675        .expect("input");
2676        let (values, ia, ic) = evaluate_sync(
2677            Value::Tensor(rows),
2678            &[
2679                Value::from("rows"),
2680                Value::from("stable"),
2681                Value::from("last"),
2682            ],
2683        )
2684        .expect("unique rows")
2685        .into_triple();
2686        let Value::Tensor(values) = values else {
2687            panic!("expected exact integer rows");
2688        };
2689        assert_eq!(
2690            values.integer_storage(),
2691            Some(&IntegerStorage::I64(vec![i64::MAX, i64::MIN, 1, 2]))
2692        );
2693        let Value::Tensor(ia) = ia else {
2694            panic!("expected indices");
2695        };
2696        assert_eq!(ia.materialize_f64(), vec![3.0, 2.0]);
2697        let Value::Tensor(ic) = ic else {
2698            panic!("expected indices");
2699        };
2700        assert_eq!(ic.materialize_f64(), vec![1.0, 2.0, 1.0]);
2701    }
2702
2703    #[test]
2704    fn unique_numeric_fallback_reads_mirrorless_integer_storage() {
2705        let opts = parse_options(&[]).expect("options");
2706        let input =
2707            Tensor::new_integer(IntegerStorage::U16(vec![7, 2, 7, 9]), vec![4, 1]).expect("input");
2708        let (values, ia, ic) = unique_numeric_from_tensor(input, &opts)
2709            .expect("unique numeric elements")
2710            .into_triple();
2711        let Value::Tensor(values) = values else {
2712            panic!("expected numeric values");
2713        };
2714        assert_eq!(values.materialize_f64(), vec![2.0, 7.0, 9.0]);
2715        let Value::Tensor(ia) = ia else {
2716            panic!("expected indices");
2717        };
2718        assert_eq!(ia.materialize_f64(), vec![2.0, 1.0, 4.0]);
2719        let Value::Tensor(ic) = ic else {
2720            panic!("expected inverse indices");
2721        };
2722        assert_eq!(ic.materialize_f64(), vec![2.0, 1.0, 2.0, 3.0]);
2723
2724        let rows = Tensor::new_integer(IntegerStorage::U16(vec![1, 3, 1, 2, 4, 2]), vec![3, 2])
2725            .expect("rows");
2726        let row_opts = parse_options(&[Value::from("rows")]).expect("row options");
2727        let (values, ia, ic) = unique_numeric_from_tensor(rows, &row_opts)
2728            .expect("unique numeric rows")
2729            .into_triple();
2730        let Value::Tensor(values) = values else {
2731            panic!("expected numeric rows");
2732        };
2733        assert_eq!(values.shape, vec![2, 2]);
2734        assert_eq!(values.materialize_f64(), vec![1.0, 3.0, 2.0, 4.0]);
2735        let Value::Tensor(ia) = ia else {
2736            panic!("expected row indices");
2737        };
2738        assert_eq!(ia.materialize_f64(), vec![1.0, 2.0]);
2739        let Value::Tensor(ic) = ic else {
2740            panic!("expected row inverse indices");
2741        };
2742        assert_eq!(ic.materialize_f64(), vec![1.0, 2.0, 1.0]);
2743    }
2744
2745    #[test]
2746    fn unique_preserves_every_exact_integer_class() {
2747        let cases = [
2748            IntegerStorage::I8(vec![i8::MAX, i8::MIN, i8::MAX]),
2749            IntegerStorage::I16(vec![i16::MAX, i16::MIN, i16::MAX]),
2750            IntegerStorage::I32(vec![i32::MAX, i32::MIN, i32::MAX]),
2751            IntegerStorage::I64(vec![i64::MAX, i64::MIN, i64::MAX]),
2752            IntegerStorage::U8(vec![u8::MAX, 0, u8::MAX]),
2753            IntegerStorage::U16(vec![u16::MAX, 0, u16::MAX]),
2754            IntegerStorage::U32(vec![u32::MAX, 0, u32::MAX]),
2755            IntegerStorage::U64(vec![u64::MAX, 0, u64::MAX]),
2756        ];
2757        for storage in cases {
2758            let expected = storage.clone();
2759            let tensor = Tensor::new_integer(storage, vec![3, 1]).expect("input");
2760            let values = evaluate_sync(Value::Tensor(tensor), &[Value::from("stable")])
2761                .expect("unique")
2762                .into_values_value();
2763            let Value::Tensor(values) = values else {
2764                panic!("expected exact integer values");
2765            };
2766            let mut expected_values = expected.exact_values();
2767            expected_values.truncate(2);
2768            assert_eq!(
2769                values.integer_storage(),
2770                Some(
2771                    &expected
2772                        .from_exact_values_like(expected_values)
2773                        .expect("expected")
2774                )
2775            );
2776        }
2777    }
2778
2779    #[test]
2780    fn unique_preserves_row_orientation_for_every_exact_integer_class() {
2781        let cases = [
2782            IntegerStorage::I8(vec![3, 1, 3]),
2783            IntegerStorage::I16(vec![3, 1, 3]),
2784            IntegerStorage::I32(vec![3, 1, 3]),
2785            IntegerStorage::I64(vec![3, 1, 3]),
2786            IntegerStorage::U8(vec![3, 1, 3]),
2787            IntegerStorage::U16(vec![3, 1, 3]),
2788            IntegerStorage::U32(vec![3, 1, 3]),
2789            IntegerStorage::U64(vec![u64::MAX, 0, u64::MAX]),
2790        ];
2791        for storage in cases {
2792            let expected = storage
2793                .from_exact_values_like(vec![
2794                    storage.value_at(1).expect("second value"),
2795                    storage.value_at(0).expect("first value"),
2796                ])
2797                .expect("same-class expected values");
2798            let input = Tensor::new_integer(storage, vec![1, 3]).expect("row input");
2799            let (values, ia, ic) = evaluate_sync(Value::Tensor(input), &[])
2800                .expect("unique integer row")
2801                .into_triple();
2802            let Value::Tensor(values) = values else {
2803                panic!("expected exact integer values");
2804            };
2805            assert_eq!(values.shape, vec![1, 2]);
2806            assert_eq!(values.integer_storage(), Some(&expected));
2807            let Value::Tensor(ia) = ia else {
2808                panic!("expected ia");
2809            };
2810            let Value::Tensor(ic) = ic else {
2811                panic!("expected ic");
2812            };
2813            assert_eq!(ia.shape, vec![2, 1]);
2814            assert_eq!(ic.shape, vec![3, 1]);
2815        }
2816    }
2817
2818    #[test]
2819    fn unique_preserves_empty_integer_row_orientation_and_column_default() {
2820        let cases = [
2821            IntegerStorage::I8(Vec::new()),
2822            IntegerStorage::I16(Vec::new()),
2823            IntegerStorage::I32(Vec::new()),
2824            IntegerStorage::I64(Vec::new()),
2825            IntegerStorage::U8(Vec::new()),
2826            IntegerStorage::U16(Vec::new()),
2827            IntegerStorage::U32(Vec::new()),
2828            IntegerStorage::U64(Vec::new()),
2829        ];
2830        for storage in cases {
2831            for (input_shape, expected_shape) in [
2832                (vec![1, 0], vec![1, 0]),
2833                (vec![0], vec![1, 0]),
2834                (vec![1, 0, 1], vec![1, 0]),
2835                (vec![0, 1], vec![0, 1]),
2836                (vec![0, 3], vec![0, 1]),
2837            ] {
2838                let input =
2839                    Tensor::new_integer(storage.clone(), input_shape).expect("empty integer input");
2840                let values = evaluate_sync(Value::Tensor(input), &[])
2841                    .expect("unique empty integer")
2842                    .into_values_value();
2843                let Value::Tensor(values) = values else {
2844                    panic!("expected exact integer values");
2845                };
2846                assert_eq!(values.shape, expected_shape);
2847                assert_eq!(values.integer_storage(), Some(&storage));
2848            }
2849        }
2850    }
2851
2852    #[test]
2853    fn unique_element_orientation_is_shared_by_native_and_text_storage() {
2854        let single = Tensor::from_f32(vec![3.0, 1.0, 3.0], vec![1, 3]).expect("single row");
2855        let Value::Tensor(single) = evaluate_sync(Value::Tensor(single), &[])
2856            .expect("unique single row")
2857            .into_values_value()
2858        else {
2859            panic!("expected single tensor");
2860        };
2861        assert_eq!(single.shape, vec![1, 2]);
2862        assert_eq!(single.as_f32_slice(), Some(&[1.0, 3.0][..]));
2863
2864        let complex = ComplexTensor::from_f32(vec![(3.0, 1.0), (1.0, 0.0), (3.0, 1.0)], vec![1, 3])
2865            .expect("complex row");
2866        let Value::ComplexTensor(complex) = evaluate_sync(Value::ComplexTensor(complex), &[])
2867            .expect("unique complex row")
2868            .into_values_value()
2869        else {
2870            panic!("expected complex tensor");
2871        };
2872        assert_eq!(complex.shape, vec![1, 2]);
2873
2874        let chars = CharArray::new(vec!['z', 'a', 'z'], 1, 3).expect("character row input");
2875        let Value::CharArray(chars) = evaluate_sync(Value::CharArray(chars), &[])
2876            .expect("unique character row")
2877            .into_values_value()
2878        else {
2879            panic!("expected character array");
2880        };
2881        assert_eq!(chars.shape, vec![1, 2]);
2882        assert_eq!(chars.data, vec!['a', 'z']);
2883
2884        let strings = StringArray::new(vec!["z".into(), "a".into(), "z".into()], vec![1, 3])
2885            .expect("string row input");
2886        let Value::StringArray(strings) = evaluate_sync(Value::StringArray(strings), &[])
2887            .expect("unique string row")
2888            .into_values_value()
2889        else {
2890            panic!("expected string array");
2891        };
2892        assert_eq!(strings.shape, vec![1, 2]);
2893
2894        let nd_chars = CharArray::from_column_major(vec!['c', 'a', 'b', 'c'], vec![2, 1, 2])
2895            .expect("N-D character input");
2896        let Value::CharArray(nd_chars) = evaluate_sync(Value::CharArray(nd_chars), &[])
2897            .expect("unique N-D character input")
2898            .into_values_value()
2899        else {
2900            panic!("expected character array");
2901        };
2902        assert_eq!(nd_chars.shape, vec![3, 1]);
2903        assert_eq!(nd_chars.to_column_major(), vec!['a', 'b', 'c']);
2904    }
2905
2906    #[test]
2907    fn unique_registered_builtin_restores_resident_integer_and_logical_outputs() {
2908        test_support::with_test_provider(|provider| {
2909            let input = Tensor::new_integer(IntegerStorage::I32(vec![7, 0, 7]), vec![1, 3])
2910                .expect("integer row");
2911            let handle = gpu_helpers::upload_tensor(provider, &input).expect("typed upload");
2912            {
2913                let _guard = crate::output_count::push_output_count(Some(3));
2914                let Value::OutputList(outputs) = builtin_sync(Value::GpuTensor(handle), Vec::new())
2915                    .expect("resident integer unique")
2916                else {
2917                    panic!("expected output list");
2918                };
2919                assert_eq!(outputs.len(), 3);
2920                assert!(outputs
2921                    .iter()
2922                    .all(|output| matches!(output, Value::GpuTensor(_))));
2923                assert_eq!(
2924                    test_support::gather(outputs[0].clone())
2925                        .expect("gather values")
2926                        .integer_storage(),
2927                    Some(&IntegerStorage::I32(vec![0, 7]))
2928                );
2929            };
2930
2931            let logical = Tensor::new(vec![1.0, 0.0, 1.0], vec![1, 3]).unwrap();
2932            let logical_handle =
2933                gpu_helpers::upload_tensor(provider, &logical).expect("logical upload");
2934            let logical_input = gpu_helpers::logical_gpu_value(logical_handle);
2935            let Value::GpuTensor(output) =
2936                builtin_sync(logical_input, Vec::new()).expect("resident logical unique")
2937            else {
2938                panic!("expected resident logical result");
2939            };
2940            assert!(runmat_accelerate_api::handle_is_logical(&output));
2941        });
2942    }
2943
2944    #[test]
2945    fn unique_resident_restrictions_and_excess_output_arity_are_enforced() {
2946        test_support::with_test_provider(|provider| {
2947            let input =
2948                Tensor::new_integer(IntegerStorage::U64(vec![u64::MAX, 0, u64::MAX]), vec![1, 3])
2949                    .expect("integer row");
2950            let handle = gpu_helpers::upload_tensor(provider, &input).expect("typed upload");
2951            let err = evaluate_sync(Value::GpuTensor(handle), &[])
2952                .expect_err("resident uint64 must fail");
2953            assert_eq!(
2954                err.identifier(),
2955                UNIQUE_ERROR_UNSUPPORTED_INPUT_TYPE.identifier
2956            );
2957
2958            let input = Tensor::new(vec![2.0, 1.0], vec![2, 1]).unwrap();
2959            let handle = gpu_helpers::upload_tensor(provider, &input).expect("upload");
2960            let err = evaluate_sync(
2961                Value::GpuTensor(handle),
2962                &[Value::from("stable"), Value::from("last")],
2963            )
2964            .expect_err("GPU set-order plus occurrence must fail");
2965            assert_eq!(
2966                err.identifier(),
2967                UNIQUE_ERROR_GPU_OPTION_COMBINATION.identifier
2968            );
2969        });
2970
2971        let _guard = crate::output_count::push_output_count(Some(4));
2972        let err = builtin_sync(Value::Num(1.0), Vec::new()).expect_err("excess outputs must fail");
2973        assert_eq!(err.identifier(), UNIQUE_ERROR_INVALID_ARGUMENT.identifier);
2974    }
2975
2976    #[test]
2977    fn unique_rows_and_complex_values_honor_missing_distinctness() {
2978        let rows = Tensor::new(vec![f64::NAN, f64::NAN, 1.0, 1.0], vec![2, 2]).unwrap();
2979        let distinct = evaluate_sync(Value::Tensor(rows.clone()), &[Value::from("rows")])
2980            .expect("distinct rows")
2981            .into_values_value();
2982        let Value::Tensor(distinct) = distinct else {
2983            panic!("expected rows");
2984        };
2985        assert_eq!(distinct.shape, vec![2, 2]);
2986        let collapsed = evaluate_sync(
2987            Value::Tensor(rows),
2988            &[
2989                Value::from("rows"),
2990                Value::from("TreatMissingAsDistinct"),
2991                Value::Bool(false),
2992            ],
2993        )
2994        .expect("collapsed rows")
2995        .into_values_value();
2996        let Value::Tensor(collapsed) = collapsed else {
2997            panic!("expected rows");
2998        };
2999        assert_eq!(collapsed.shape, vec![1, 2]);
3000
3001        let complex =
3002            ComplexTensor::new(vec![(f64::NAN, 1.0), (f64::NAN, 1.0)], vec![2, 1]).unwrap();
3003        let distinct = evaluate_sync(Value::ComplexTensor(complex.clone()), &[])
3004            .expect("distinct complex")
3005            .into_values_value();
3006        let Value::ComplexTensor(distinct) = distinct else {
3007            panic!("expected complex values");
3008        };
3009        assert_eq!(distinct.len(), 2);
3010        let collapsed = evaluate_sync(
3011            Value::ComplexTensor(complex),
3012            &[Value::from("TreatMissingAsDistinct"), Value::Bool(false)],
3013        )
3014        .expect("collapsed complex")
3015        .into_values_value();
3016        assert!(
3017            matches!(collapsed, Value::Complex(real, imaginary) if real.is_nan() && imaginary == 1.0)
3018        );
3019    }
3020}