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