1use 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
364pub 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}