1use std::cmp::Ordering;
9use std::collections::{hash_map::Entry, HashMap};
10
11use runmat_accelerate_api::{GpuTensorHandle, UnionOptions, UnionOrder, UnionResult};
12use runmat_builtins::{
13 BuiltinCompletionPolicy, BuiltinDescriptor, BuiltinErrorDescriptor, BuiltinIntegerBackendRule,
14 BuiltinIntegerCapabilityDescriptor, BuiltinIntegerComputationDomain,
15 BuiltinIntegerOutputClassRule, BuiltinIntegerOverflowRule, BuiltinIntegerOverloadKind,
16 BuiltinOutputMode, BuiltinParamArity, BuiltinParamDescriptor, BuiltinParamType,
17 BuiltinSignatureDescriptor,
18};
19use runmat_macros::runtime_builtin;
20use runmat_value::{
21 CharArray, ComplexStorage, ComplexTensor, IntValue, IntegerStorage, NumericDType,
22 NumericStorage, StringArray, Tensor, Value,
23};
24
25use super::{float_order::SetFloat, integer_order, type_resolvers::set_values_output_type};
26use crate::build_runtime_error;
27use crate::builtins::common::arg_tokens::tokens_from_values;
28use crate::builtins::common::gpu_helpers;
29use crate::builtins::common::random_args::complex_tensor_into_value;
30use crate::builtins::common::spec::{
31 BroadcastSemantics, BuiltinFusionSpec, BuiltinGpuSpec, ConstantStrategy, GpuOpKind,
32 ProviderHook, ReductionNaN, ResidencyPolicy, ScalarType, ShapeRequirements,
33};
34use crate::builtins::common::tensor;
35use crate::builtins::math::elementwise::integer_cast::IntegerTarget;
36
37#[runmat_macros::register_gpu_spec(builtin_path = "crate::builtins::array::sorting_sets::union")]
38pub const GPU_SPEC: BuiltinGpuSpec = BuiltinGpuSpec {
39 name: "union",
40 op_kind: GpuOpKind::Custom("union"),
41 supported_precisions: &[ScalarType::F32, ScalarType::F64],
42 broadcast: BroadcastSemantics::None,
43 provider_hooks: &[ProviderHook::Custom("union")],
44 constant_strategy: ConstantStrategy::InlineLiteral,
45 residency: ResidencyPolicy::NewHandle,
46 nan_mode: ReductionNaN::Include,
47 two_pass_threshold: None,
48 workgroup_size: None,
49 accepts_nan_mode: true,
50 notes: "Providers may expose a dedicated union hook; exact typed fallback gathers when needed and restores union values plus double indices to the input owner.",
51};
52
53#[runmat_macros::register_fusion_spec(builtin_path = "crate::builtins::array::sorting_sets::union")]
54pub const FUSION_SPEC: BuiltinFusionSpec = BuiltinFusionSpec {
55 name: "union",
56 shape: ShapeRequirements::Any,
57 constant_strategy: ConstantStrategy::InlineLiteral,
58 elementwise: None,
59 reduction: None,
60 emits_nan: true,
61 notes: "`union` terminates fusion chains and materialises results on the host; upstream tensors are gathered when necessary.",
62};
63
64const BUILTIN_NAME: &str = "union";
65
66const UNION_OUTPUT_C: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
67 name: "C",
68 ty: BuiltinParamType::Any,
69 arity: BuiltinParamArity::Required,
70 default: None,
71 description: "Union values or rows.",
72}];
73
74const UNION_OUTPUT_C_IA: [BuiltinParamDescriptor; 2] = [
75 BuiltinParamDescriptor {
76 name: "C",
77 ty: BuiltinParamType::Any,
78 arity: BuiltinParamArity::Required,
79 default: None,
80 description: "Union values or rows.",
81 },
82 BuiltinParamDescriptor {
83 name: "ia",
84 ty: BuiltinParamType::NumericArray,
85 arity: BuiltinParamArity::Required,
86 default: None,
87 description: "Indices selecting contributions from A.",
88 },
89];
90
91const UNION_OUTPUT_C_IA_IB: [BuiltinParamDescriptor; 3] = [
92 BuiltinParamDescriptor {
93 name: "C",
94 ty: BuiltinParamType::Any,
95 arity: BuiltinParamArity::Required,
96 default: None,
97 description: "Union values or rows.",
98 },
99 BuiltinParamDescriptor {
100 name: "ia",
101 ty: BuiltinParamType::NumericArray,
102 arity: BuiltinParamArity::Required,
103 default: None,
104 description: "Indices selecting contributions from A.",
105 },
106 BuiltinParamDescriptor {
107 name: "ib",
108 ty: BuiltinParamType::NumericArray,
109 arity: BuiltinParamArity::Required,
110 default: None,
111 description: "Indices selecting contributions from B.",
112 },
113];
114
115const UNION_INPUTS_A_B: [BuiltinParamDescriptor; 2] = [
116 BuiltinParamDescriptor {
117 name: "A",
118 ty: BuiltinParamType::Any,
119 arity: BuiltinParamArity::Required,
120 default: None,
121 description: "First input array.",
122 },
123 BuiltinParamDescriptor {
124 name: "B",
125 ty: BuiltinParamType::Any,
126 arity: BuiltinParamArity::Required,
127 default: None,
128 description: "Second input array.",
129 },
130];
131
132const UNION_INPUTS_A_B_OPTIONS: [BuiltinParamDescriptor; 3] = [
133 BuiltinParamDescriptor {
134 name: "A",
135 ty: BuiltinParamType::Any,
136 arity: BuiltinParamArity::Required,
137 default: None,
138 description: "First input array.",
139 },
140 BuiltinParamDescriptor {
141 name: "B",
142 ty: BuiltinParamType::Any,
143 arity: BuiltinParamArity::Required,
144 default: None,
145 description: "Second input array.",
146 },
147 BuiltinParamDescriptor {
148 name: "option",
149 ty: BuiltinParamType::StringScalar,
150 arity: BuiltinParamArity::Variadic,
151 default: None,
152 description: "Option tokens: 'rows'|'sorted'|'stable'.",
153 },
154];
155
156const UNION_SIGNATURES: [BuiltinSignatureDescriptor; 6] = [
157 BuiltinSignatureDescriptor {
158 label: "C = union(A, B)",
159 inputs: &UNION_INPUTS_A_B,
160 outputs: &UNION_OUTPUT_C,
161 },
162 BuiltinSignatureDescriptor {
163 label: "C = union(A, B, option...)",
164 inputs: &UNION_INPUTS_A_B_OPTIONS,
165 outputs: &UNION_OUTPUT_C,
166 },
167 BuiltinSignatureDescriptor {
168 label: "[C, ia] = union(A, B)",
169 inputs: &UNION_INPUTS_A_B,
170 outputs: &UNION_OUTPUT_C_IA,
171 },
172 BuiltinSignatureDescriptor {
173 label: "[C, ia] = union(A, B, option...)",
174 inputs: &UNION_INPUTS_A_B_OPTIONS,
175 outputs: &UNION_OUTPUT_C_IA,
176 },
177 BuiltinSignatureDescriptor {
178 label: "[C, ia, ib] = union(A, B)",
179 inputs: &UNION_INPUTS_A_B,
180 outputs: &UNION_OUTPUT_C_IA_IB,
181 },
182 BuiltinSignatureDescriptor {
183 label: "[C, ia, ib] = union(A, B, option...)",
184 inputs: &UNION_INPUTS_A_B_OPTIONS,
185 outputs: &UNION_OUTPUT_C_IA_IB,
186 },
187];
188
189const UNION_ERROR_LEGACY_OPTION_UNSUPPORTED: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
190 code: "RM.UNION.LEGACY_OPTION_UNSUPPORTED",
191 identifier: Some("RunMat:union:LegacyOptionUnsupported"),
192 when: "Legacy compatibility options are requested.",
193 message: "union: the 'legacy' behaviour is not supported",
194};
195
196const UNION_ERROR_CONFLICTING_ORDER_OPTIONS: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
197 code: "RM.UNION.CONFLICTING_ORDER_OPTIONS",
198 identifier: Some("RunMat:union:ConflictingOrderOptions"),
199 when: "Both 'sorted' and 'stable' options are provided.",
200 message: "union: cannot combine 'sorted' with 'stable'",
201};
202
203const UNION_ERROR_UNKNOWN_OPTION: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
204 code: "RM.UNION.UNKNOWN_OPTION",
205 identifier: Some("RunMat:union:UnknownOption"),
206 when: "An unsupported option token is provided.",
207 message: "union: unrecognised option",
208};
209
210const UNION_ERROR_ROWS_COLUMN_MISMATCH: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
211 code: "RM.UNION.ROWS_COLUMN_MISMATCH",
212 identifier: Some("RunMat:union:RowsColumnMismatch"),
213 when: "'rows' mode is used and column counts differ.",
214 message: "union: inputs must have the same number of columns when using 'rows'",
215};
216
217const UNION_ERROR_UNSUPPORTED_INPUT_TYPE: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
218 code: "RM.UNION.UNSUPPORTED_INPUT_TYPE",
219 identifier: Some("RunMat:union:UnsupportedInputType"),
220 when: "Input values cannot be converted into supported union domains.",
221 message: "union: unsupported input type",
222};
223
224const UNION_ERROR_NUMERIC_CLASS_MISMATCH: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
225 code: "RM.UNION.NUMERIC_CLASS_MISMATCH",
226 identifier: Some("RunMat:union:NumericClassMismatch"),
227 when: "Numeric inputs have incompatible nondouble classes.",
228 message: "union: numeric inputs must have the same class, except double may be combined with one nondouble class",
229};
230
231const UNION_ERROR_INVALID_ARGUMENT: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
232 code: "RM.UNION.INVALID_ARGUMENT",
233 identifier: Some("RunMat:union:InvalidArgument"),
234 when: "Option arguments are not string-like where required.",
235 message: "union: expected string option arguments",
236};
237
238const UNION_ERROR_INTERNAL: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
239 code: "RM.UNION.INTERNAL",
240 identifier: Some("RunMat:union:Internal"),
241 when: "Internal conversion/allocation/provider decode fails.",
242 message: "union: internal operation failed",
243};
244
245const UNION_ERRORS: [BuiltinErrorDescriptor; 8] = [
246 UNION_ERROR_LEGACY_OPTION_UNSUPPORTED,
247 UNION_ERROR_CONFLICTING_ORDER_OPTIONS,
248 UNION_ERROR_UNKNOWN_OPTION,
249 UNION_ERROR_ROWS_COLUMN_MISMATCH,
250 UNION_ERROR_UNSUPPORTED_INPUT_TYPE,
251 UNION_ERROR_NUMERIC_CLASS_MISMATCH,
252 UNION_ERROR_INVALID_ARGUMENT,
253 UNION_ERROR_INTERNAL,
254];
255
256const UNION_INTEGER_CAPABILITIES: [BuiltinIntegerCapabilityDescriptor; 1] =
257 [BuiltinIntegerCapabilityDescriptor {
258 form: "[C, ia, ib] = union(integer_A, integer_B, options)",
259 inputs: &super::BINARY_SET_INTEGER_INPUTS,
260 computation_domain: BuiltinIntegerComputationDomain::ExactInteger,
261 output_class: BuiltinIntegerOutputClassRule::FunctionSpecific,
262 overflow: BuiltinIntegerOverflowRule::NotApplicable,
263 backend: BuiltinIntegerBackendRule::GpuRestricted,
264 overload: BuiltinIntegerOverloadKind::Multiple,
265 notes: "C preserves the common nondouble integer class, including when paired with double; ia and ib are one-based double. GPU supports integer classes through 32 bits and restores outputs after typed fallback.",
266 }];
267
268pub const UNION_DESCRIPTOR: BuiltinDescriptor = BuiltinDescriptor {
269 signatures: &UNION_SIGNATURES,
270 output_mode: BuiltinOutputMode::ByRequestedOutputCount,
271 completion_policy: BuiltinCompletionPolicy::Public,
272 errors: &UNION_ERRORS,
273};
274
275fn union_error_with(
276 error: &'static BuiltinErrorDescriptor,
277 message: impl Into<String>,
278) -> crate::RuntimeError {
279 let mut builder = build_runtime_error(message).with_builtin(BUILTIN_NAME);
280 if let Some(identifier) = error.identifier {
281 builder = builder.with_identifier(identifier);
282 }
283 builder.build()
284}
285
286fn union_error(error: &'static BuiltinErrorDescriptor) -> crate::RuntimeError {
287 union_error_with(error, error.message)
288}
289
290fn union_internal_error(message: impl Into<String>) -> crate::RuntimeError {
291 union_error_with(&UNION_ERROR_INTERNAL, message)
292}
293
294#[runtime_builtin(
295 name = "union",
296 category = "array/sorting_sets",
297 summary = "Return unions of input arrays with ordering and index-output controls.",
298 keywords = "union,set,stable,rows,indices,gpu",
299 accel = "array_construct",
300 sink = true,
301 type_resolver(set_values_output_type),
302 descriptor(crate::builtins::array::sorting_sets::union::UNION_DESCRIPTOR),
303 integer_capabilities(UNION_INTEGER_CAPABILITIES),
304 builtin_path = "crate::builtins::array::sorting_sets::union"
305)]
306async fn union_builtin(a: Value, b: Value, rest: Vec<Value>) -> crate::BuiltinResult<Value> {
307 if matches!(crate::output_count::current_output_count(), Some(n) if n > 3) {
308 return Err(union_error_with(
309 &UNION_ERROR_INVALID_ARGUMENT,
310 "union: too many output arguments; maximum is 3",
311 ));
312 }
313 let provider = super::set_output_provider(&a, &b);
314 let eval = evaluate(a, b, &rest).await?;
315 if let Some(out_count) = crate::output_count::current_output_count() {
316 if out_count == 0 {
317 return Ok(Value::OutputList(Vec::new()));
318 }
319 if out_count == 1 {
320 let outputs = super::restore_set_outputs(
321 provider,
322 BUILTIN_NAME,
323 vec![eval.into_values_value()],
324 union_internal_error,
325 )?;
326 return Ok(Value::OutputList(outputs));
327 }
328 if out_count == 2 {
329 let (values, ia) = eval.into_pair();
330 let outputs = super::restore_set_outputs(
331 provider,
332 BUILTIN_NAME,
333 vec![values, ia],
334 union_internal_error,
335 )?;
336 return Ok(Value::OutputList(outputs));
337 }
338 let (values, ia, ib) = eval.into_triple();
339 let outputs = super::restore_set_outputs(
340 provider,
341 BUILTIN_NAME,
342 vec![values, ia, ib],
343 union_internal_error,
344 )?;
345 return Ok(Value::OutputList(outputs));
346 }
347 let mut outputs = super::restore_set_outputs(
348 provider,
349 BUILTIN_NAME,
350 vec![eval.into_values_value()],
351 union_internal_error,
352 )?;
353 Ok(outputs.pop().expect("union output"))
354}
355
356pub async fn evaluate(a: Value, b: Value, rest: &[Value]) -> crate::BuiltinResult<UnionEvaluation> {
358 crate::builtins::common::validation::reject_typed_complex_integer(&a, "union")?;
359 crate::builtins::common::validation::reject_typed_complex_integer(&b, "union")?;
360 let opts = parse_options(rest)?;
361 for value in [&a, &b] {
362 if let Value::GpuTensor(handle) = value {
363 if super::is_unsupported_set_gpu_integer(handle) {
364 return Err(union_error_with(
365 &UNION_ERROR_UNSUPPORTED_INPUT_TYPE,
366 "union: resident 64-bit integer inputs are not supported",
367 ));
368 }
369 }
370 }
371 match (a, b) {
372 (Value::GpuTensor(handle_a), Value::GpuTensor(handle_b)) => {
373 union_gpu_pair(handle_a, handle_b, &opts).await
374 }
375 (Value::GpuTensor(handle_a), other) => union_gpu_mixed(handle_a, other, &opts, true).await,
376 (other, Value::GpuTensor(handle_b)) => union_gpu_mixed(handle_b, other, &opts, false).await,
377 (left, right) => union_host(left, right, &opts),
378 }
379}
380
381fn parse_options(rest: &[Value]) -> crate::BuiltinResult<UnionOptions> {
382 let mut opts = UnionOptions {
383 rows: false,
384 order: UnionOrder::Sorted,
385 };
386 let mut seen_order: Option<UnionOrder> = None;
387
388 let tokens = tokens_from_values(rest);
389 for (arg, token) in rest.iter().zip(tokens.iter()) {
390 let text = match token {
391 crate::builtins::common::arg_tokens::ArgToken::String(text) => text.as_str(),
392 _ => {
393 let text = tensor::value_to_string(arg)
394 .ok_or_else(|| union_error(&UNION_ERROR_INVALID_ARGUMENT))?;
395 let lowered = text.trim().to_ascii_lowercase();
396 parse_union_option(&mut opts, &mut seen_order, &lowered)?;
397 continue;
398 }
399 };
400 parse_union_option(&mut opts, &mut seen_order, text)?;
401 }
402
403 Ok(opts)
404}
405
406fn parse_union_option(
407 opts: &mut UnionOptions,
408 seen_order: &mut Option<UnionOrder>,
409 lowered: &str,
410) -> crate::BuiltinResult<()> {
411 match lowered {
412 "rows" => opts.rows = true,
413 "sorted" => {
414 if let Some(prev) = seen_order {
415 if *prev != UnionOrder::Sorted {
416 return Err(union_error(&UNION_ERROR_CONFLICTING_ORDER_OPTIONS));
417 }
418 }
419 *seen_order = Some(UnionOrder::Sorted);
420 opts.order = UnionOrder::Sorted;
421 }
422 "stable" => {
423 if let Some(prev) = seen_order {
424 if *prev != UnionOrder::Stable {
425 return Err(union_error(&UNION_ERROR_CONFLICTING_ORDER_OPTIONS));
426 }
427 }
428 *seen_order = Some(UnionOrder::Stable);
429 opts.order = UnionOrder::Stable;
430 }
431 "legacy" | "r2012a" => {
432 return Err(union_error(&UNION_ERROR_LEGACY_OPTION_UNSUPPORTED));
433 }
434 other => {
435 return Err(union_error_with(
436 &UNION_ERROR_UNKNOWN_OPTION,
437 format!("union: unrecognised option '{other}'"),
438 ))
439 }
440 }
441 Ok(())
442}
443
444async fn union_gpu_pair(
445 handle_a: GpuTensorHandle,
446 handle_b: GpuTensorHandle,
447 opts: &UnionOptions,
448) -> crate::BuiltinResult<UnionEvaluation> {
449 if let Some(provider) = runmat_accelerate_api::provider_for_handle(&handle_a)
450 .or_else(runmat_accelerate_api::provider)
451 {
452 match provider.union(&handle_a, &handle_b, opts).await {
453 Ok(result) => return UnionEvaluation::from_union_result(result),
454 Err(_) => {
455 }
457 }
458 }
459 let tensor_a = gpu_helpers::gather_tensor_async(&handle_a).await?;
460 let tensor_b = gpu_helpers::gather_tensor_async(&handle_b).await?;
461 union_numeric(tensor_a, tensor_b, opts)
462}
463
464async fn union_gpu_mixed(
465 handle_gpu: GpuTensorHandle,
466 other: Value,
467 opts: &UnionOptions,
468 gpu_is_a: bool,
469) -> crate::BuiltinResult<UnionEvaluation> {
470 let tensor_gpu = gpu_helpers::gather_tensor_async(&handle_gpu).await?;
471 let tensor_other =
472 tensor::value_into_tensor_for("union", other).map_err(|e| union_internal_error(e))?;
473 if gpu_is_a {
474 union_numeric(tensor_gpu, tensor_other, opts)
475 } else {
476 union_numeric(tensor_other, tensor_gpu, opts)
477 }
478}
479
480fn union_host(a: Value, b: Value, opts: &UnionOptions) -> crate::BuiltinResult<UnionEvaluation> {
481 match (a, b) {
482 (Value::ComplexTensor(at), Value::ComplexTensor(bt)) => union_complex(at, bt, opts),
484 (Value::ComplexTensor(at), Value::Complex(re, im)) => {
485 let bt = ComplexTensor::new(vec![(re, im)], vec![1, 1])
486 .map_err(|e| union_internal_error(format!("union: {e}")))?;
487 union_complex(at, bt, opts)
488 }
489 (Value::Complex(re, im), Value::ComplexTensor(bt)) => {
490 let at = ComplexTensor::new(vec![(re, im)], vec![1, 1])
491 .map_err(|e| union_internal_error(format!("union: {e}")))?;
492 union_complex(at, bt, opts)
493 }
494 (Value::Complex(a_re, a_im), Value::Complex(b_re, b_im)) => {
495 let at = ComplexTensor::new(vec![(a_re, a_im)], vec![1, 1])
496 .map_err(|e| union_internal_error(format!("union: {e}")))?;
497 let bt = ComplexTensor::new(vec![(b_re, b_im)], vec![1, 1])
498 .map_err(|e| union_internal_error(format!("union: {e}")))?;
499 union_complex(at, bt, opts)
500 }
501
502 (Value::CharArray(ac), Value::CharArray(bc)) => union_char(ac, bc, opts),
504
505 (Value::StringArray(astring), Value::StringArray(bstring)) => {
507 union_string(astring, bstring, opts)
508 }
509 (Value::StringArray(astring), Value::String(b)) => {
510 let bstring = StringArray::new(vec![b], vec![1, 1])
511 .map_err(|e| union_internal_error(format!("union: {e}")))?;
512 union_string(astring, bstring, opts)
513 }
514 (Value::String(a), Value::StringArray(bstring)) => {
515 let astring = StringArray::new(vec![a], vec![1, 1])
516 .map_err(|e| union_internal_error(format!("union: {e}")))?;
517 union_string(astring, bstring, opts)
518 }
519 (Value::String(a), Value::String(b)) => {
520 let astring = StringArray::new(vec![a], vec![1, 1])
521 .map_err(|e| union_internal_error(format!("union: {e}")))?;
522 let bstring = StringArray::new(vec![b], vec![1, 1])
523 .map_err(|e| union_internal_error(format!("union: {e}")))?;
524 union_string(astring, bstring, opts)
525 }
526
527 (left, right) => {
529 let tensor_a = tensor::value_into_tensor_for("union", left)
530 .map_err(|e| union_error_with(&UNION_ERROR_UNSUPPORTED_INPUT_TYPE, e))?;
531 let tensor_b = tensor::value_into_tensor_for("union", right)
532 .map_err(|e| union_error_with(&UNION_ERROR_UNSUPPORTED_INPUT_TYPE, e))?;
533 union_numeric(tensor_a, tensor_b, opts)
534 }
535 }
536}
537
538fn union_numeric(
539 a: Tensor,
540 b: Tensor,
541 opts: &UnionOptions,
542) -> crate::BuiltinResult<UnionEvaluation> {
543 let a_dtype = a.numeric_dtype();
544 let b_dtype = b.numeric_dtype();
545 if let (Some(a_storage), Some(b_storage)) = (a.integer_storage(), b.integer_storage()) {
546 if a_storage.class_name() == b_storage.class_name() {
547 return if opts.rows {
548 union_integer_rows(a_storage, a.shape.clone(), b_storage, b.shape.clone(), opts)
549 } else {
550 union_integer_elements(a_storage, b_storage, opts)
551 };
552 }
553 return Err(union_error(&UNION_ERROR_NUMERIC_CLASS_MISMATCH));
554 }
555 match (a.integer_storage(), b.integer_storage()) {
556 (Some(storage), None) if b_dtype == NumericDType::F64 => {
557 let target = IntegerTarget::from_storage(storage);
558 let b = target.cast_tensor(b).map_err(union_internal_error)?;
559 return union_numeric(a, b, opts);
560 }
561 (None, Some(storage)) if a_dtype == NumericDType::F64 => {
562 let target = IntegerTarget::from_storage(storage);
563 let a = target.cast_tensor(a).map_err(union_internal_error)?;
564 return union_numeric(a, b, opts);
565 }
566 _ => {}
567 }
568 if a_dtype != b_dtype && a_dtype != NumericDType::F64 && b_dtype != NumericDType::F64 {
569 return Err(union_error(&UNION_ERROR_NUMERIC_CLASS_MISMATCH));
570 }
571 let a_shape = a.shape.clone();
572 let b_shape = b.shape.clone();
573 let a_storage = a
574 .into_numeric_storage()
575 .map_err(|e| union_internal_error(format!("union: {e}")))?;
576 let b_storage = b
577 .into_numeric_storage()
578 .map_err(|e| union_internal_error(format!("union: {e}")))?;
579 match (a_storage, b_storage) {
580 (NumericStorage::F64(a), NumericStorage::F64(b)) => {
581 union_floating(a, a_shape, b, b_shape, opts)
582 }
583 (NumericStorage::F32(a), NumericStorage::F32(b)) => {
584 union_floating(a, a_shape, b, b_shape, opts)
585 }
586 (a, b) => union_promoted_f64(a, a_shape, b, b_shape, opts),
587 }
588}
589
590fn union_promoted_f64(
591 a: NumericStorage,
592 a_shape: Vec<usize>,
593 b: NumericStorage,
594 b_shape: Vec<usize>,
595 opts: &UnionOptions,
596) -> crate::BuiltinResult<UnionEvaluation> {
597 union_floating(
598 a.materialize_f64(),
599 a_shape,
600 b.materialize_f64(),
601 b_shape,
602 opts,
603 )
604}
605
606fn union_floating<T: SetFloat>(
607 a: Vec<T>,
608 a_shape: Vec<usize>,
609 b: Vec<T>,
610 b_shape: Vec<usize>,
611 opts: &UnionOptions,
612) -> crate::BuiltinResult<UnionEvaluation> {
613 if opts.rows {
614 union_floating_rows(a, a_shape, b, b_shape, opts)
615 } else {
616 union_floating_elements(a, b, opts)
617 }
618}
619
620pub fn union_numeric_from_tensors(
622 a: Tensor,
623 b: Tensor,
624 opts: &UnionOptions,
625) -> crate::BuiltinResult<UnionEvaluation> {
626 union_numeric(a, b, opts)
627}
628
629fn union_integer_elements(
630 a: &IntegerStorage,
631 b: &IntegerStorage,
632 opts: &UnionOptions,
633) -> crate::BuiltinResult<UnionEvaluation> {
634 let mut entries = Vec::<IntegerUnionEntry>::new();
635 let mut map = HashMap::<IntValue, usize>::new();
636 for (index, value) in a.exact_values().into_iter().enumerate() {
637 if map.contains_key(&value) {
638 continue;
639 }
640 let entry_index = entries.len();
641 entries.push(IntegerUnionEntry {
642 value: value.clone(),
643 a_index: Some(index),
644 b_index: None,
645 order_rank: entry_index,
646 });
647 map.insert(value, entry_index);
648 }
649 for (index, value) in b.exact_values().into_iter().enumerate() {
650 if map.contains_key(&value) {
651 continue;
652 }
653 let entry_index = entries.len();
654 entries.push(IntegerUnionEntry {
655 value: value.clone(),
656 a_index: None,
657 b_index: Some(index),
658 order_rank: entry_index,
659 });
660 map.insert(value, entry_index);
661 }
662 assemble_integer_union(entries, a, opts)
663}
664
665fn union_integer_rows(
666 a_storage: &IntegerStorage,
667 a_shape: Vec<usize>,
668 b_storage: &IntegerStorage,
669 b_shape: Vec<usize>,
670 opts: &UnionOptions,
671) -> crate::BuiltinResult<UnionEvaluation> {
672 if a_shape.len() != 2 || b_shape.len() != 2 {
673 return Err(union_internal_error(
674 "union: 'rows' option requires 2-D numeric matrices",
675 ));
676 }
677 if a_shape[1] != b_shape[1] {
678 return Err(union_error(&UNION_ERROR_ROWS_COLUMN_MISMATCH));
679 }
680 let (rows_a, rows_b, cols) = (a_shape[0], b_shape[0], a_shape[1]);
681 let a_values = a_storage.exact_values();
682 let b_values = b_storage.exact_values();
683 let mut entries = Vec::<IntegerRowUnionEntry>::new();
684 let mut map = HashMap::<Vec<IntValue>, usize>::new();
685 for row in 0..rows_a {
686 let row_data: Vec<_> = (0..cols)
687 .map(|col| a_values[row + col * rows_a].clone())
688 .collect();
689 if map.contains_key(&row_data) {
690 continue;
691 }
692 let entry_index = entries.len();
693 entries.push(IntegerRowUnionEntry {
694 row_data: row_data.clone(),
695 a_row: Some(row),
696 b_row: None,
697 order_rank: entry_index,
698 });
699 map.insert(row_data, entry_index);
700 }
701 for row in 0..rows_b {
702 let row_data: Vec<_> = (0..cols)
703 .map(|col| b_values[row + col * rows_b].clone())
704 .collect();
705 if map.contains_key(&row_data) {
706 continue;
707 }
708 let entry_index = entries.len();
709 entries.push(IntegerRowUnionEntry {
710 row_data: row_data.clone(),
711 a_row: None,
712 b_row: Some(row),
713 order_rank: entry_index,
714 });
715 map.insert(row_data, entry_index);
716 }
717 assemble_integer_row_union(entries, a_storage, opts, cols)
718}
719
720fn union_floating_elements<T: SetFloat>(
721 a_values: Vec<T>,
722 b_values: Vec<T>,
723 opts: &UnionOptions,
724) -> crate::BuiltinResult<UnionEvaluation> {
725 let mut entries = Vec::<FloatingUnionEntry<T>>::new();
726 let mut map: HashMap<u64, usize> = HashMap::new();
727 let mut order_counter = 0usize;
728
729 for (idx, &value) in a_values.iter().enumerate() {
730 let key = value.canonical_key();
731 match map.entry(key) {
732 Entry::Occupied(_) => {
733 }
735 Entry::Vacant(v) => {
736 let entry_idx = entries.len();
737 entries.push(FloatingUnionEntry {
738 value,
739 a_index: Some(idx),
740 b_index: None,
741 order_rank: order_counter,
742 });
743 v.insert(entry_idx);
744 order_counter += 1;
745 }
746 }
747 }
748
749 for (idx, &value) in b_values.iter().enumerate() {
750 let key = value.canonical_key();
751 match map.entry(key) {
752 Entry::Occupied(occ) => {
753 let entry = &mut entries[*occ.get()];
754 if entry.a_index.is_none() && entry.b_index.is_none() {
755 entry.b_index = Some(idx);
756 }
757 }
758 Entry::Vacant(v) => {
759 let entry_idx = entries.len();
760 entries.push(FloatingUnionEntry {
761 value,
762 a_index: None,
763 b_index: Some(idx),
764 order_rank: order_counter,
765 });
766 v.insert(entry_idx);
767 order_counter += 1;
768 }
769 }
770 }
771
772 assemble_floating_union(entries, opts)
773}
774
775fn union_floating_rows<T: SetFloat>(
776 a_values: Vec<T>,
777 a_shape: Vec<usize>,
778 b_values: Vec<T>,
779 b_shape: Vec<usize>,
780 opts: &UnionOptions,
781) -> crate::BuiltinResult<UnionEvaluation> {
782 if a_shape.len() != 2 || b_shape.len() != 2 {
783 return Err(union_internal_error(
784 "union: 'rows' option requires 2-D numeric matrices",
785 ));
786 }
787 if a_shape[1] != b_shape[1] {
788 return Err(union_error_with(
789 &UNION_ERROR_ROWS_COLUMN_MISMATCH,
790 UNION_ERROR_ROWS_COLUMN_MISMATCH.message,
791 ));
792 }
793 let rows_a = a_shape[0];
794 let cols = a_shape[1];
795 let rows_b = b_shape[0];
796
797 let mut entries = Vec::<FloatingRowUnionEntry<T>>::new();
798 let mut map: HashMap<FloatingRowKey, usize> = HashMap::new();
799 let mut order_counter = 0usize;
800
801 for r in 0..rows_a {
802 let mut row_values = Vec::with_capacity(cols);
803 for c in 0..cols {
804 let idx = r + c * rows_a;
805 row_values.push(a_values[idx]);
806 }
807 let key = FloatingRowKey::from_slice(&row_values);
808 match map.entry(key) {
809 Entry::Occupied(_) => {}
810 Entry::Vacant(v) => {
811 let entry_idx = entries.len();
812 entries.push(FloatingRowUnionEntry {
813 row_data: row_values,
814 a_row: Some(r),
815 b_row: None,
816 order_rank: order_counter,
817 });
818 v.insert(entry_idx);
819 order_counter += 1;
820 }
821 }
822 }
823
824 for r in 0..rows_b {
825 let mut row_values = Vec::with_capacity(cols);
826 for c in 0..cols {
827 let idx = r + c * rows_b;
828 row_values.push(b_values[idx]);
829 }
830 let key = FloatingRowKey::from_slice(&row_values);
831 match map.entry(key) {
832 Entry::Occupied(occ) => {
833 let entry = &mut entries[*occ.get()];
834 if entry.a_row.is_none() && entry.b_row.is_none() {
835 entry.b_row = Some(r);
836 }
837 }
838 Entry::Vacant(v) => {
839 let entry_idx = entries.len();
840 entries.push(FloatingRowUnionEntry {
841 row_data: row_values,
842 a_row: None,
843 b_row: Some(r),
844 order_rank: order_counter,
845 });
846 v.insert(entry_idx);
847 order_counter += 1;
848 }
849 }
850 }
851
852 assemble_floating_row_union(entries, opts, cols)
853}
854
855fn union_complex(
856 a: ComplexTensor,
857 b: ComplexTensor,
858 opts: &UnionOptions,
859) -> crate::BuiltinResult<UnionEvaluation> {
860 let a_shape = a.shape.clone();
861 let b_shape = b.shape.clone();
862 match (a.into_complex_storage(), b.into_complex_storage()) {
863 (ComplexStorage::F64(a), ComplexStorage::F64(b)) => {
864 union_floating_complex(a, a_shape, b, b_shape, opts)
865 }
866 (ComplexStorage::F32(a), ComplexStorage::F32(b)) => {
867 union_floating_complex(a, a_shape, b, b_shape, opts)
868 }
869 (a, b) => union_promoted_complex_f64(a, a_shape, b, b_shape, opts),
870 }
871}
872
873fn union_promoted_complex_f64(
874 a: ComplexStorage,
875 a_shape: Vec<usize>,
876 b: ComplexStorage,
877 b_shape: Vec<usize>,
878 opts: &UnionOptions,
879) -> crate::BuiltinResult<UnionEvaluation> {
880 union_floating_complex(
881 a.materialize_f64(),
882 a_shape,
883 b.materialize_f64(),
884 b_shape,
885 opts,
886 )
887}
888
889fn union_floating_complex<T: SetFloat>(
890 a: Vec<(T, T)>,
891 a_shape: Vec<usize>,
892 b: Vec<(T, T)>,
893 b_shape: Vec<usize>,
894 opts: &UnionOptions,
895) -> crate::BuiltinResult<UnionEvaluation> {
896 if opts.rows {
897 union_complex_rows(a, a_shape, b, b_shape, opts)
898 } else {
899 union_complex_elements(a, b, opts)
900 }
901}
902
903fn union_complex_elements<T: SetFloat>(
904 a: Vec<(T, T)>,
905 b: Vec<(T, T)>,
906 opts: &UnionOptions,
907) -> crate::BuiltinResult<UnionEvaluation> {
908 let mut entries = Vec::<ComplexUnionEntry<T>>::new();
909 let mut map: HashMap<ComplexKey, usize> = HashMap::new();
910 let mut order_counter = 0usize;
911
912 for (idx, &value) in a.iter().enumerate() {
913 let key = ComplexKey::new(value);
914 match map.entry(key) {
915 Entry::Occupied(_) => {}
916 Entry::Vacant(v) => {
917 let entry_idx = entries.len();
918 entries.push(ComplexUnionEntry {
919 value,
920 a_index: Some(idx),
921 b_index: None,
922 order_rank: order_counter,
923 });
924 v.insert(entry_idx);
925 order_counter += 1;
926 }
927 }
928 }
929
930 for (idx, &value) in b.iter().enumerate() {
931 let key = ComplexKey::new(value);
932 match map.entry(key) {
933 Entry::Occupied(occ) => {
934 let entry = &mut entries[*occ.get()];
935 if entry.a_index.is_none() && entry.b_index.is_none() {
936 entry.b_index = Some(idx);
937 }
938 }
939 Entry::Vacant(v) => {
940 let entry_idx = entries.len();
941 entries.push(ComplexUnionEntry {
942 value,
943 a_index: None,
944 b_index: Some(idx),
945 order_rank: order_counter,
946 });
947 v.insert(entry_idx);
948 order_counter += 1;
949 }
950 }
951 }
952
953 assemble_complex_union(entries, opts)
954}
955
956fn union_complex_rows<T: SetFloat>(
957 a: Vec<(T, T)>,
958 a_shape: Vec<usize>,
959 b: Vec<(T, T)>,
960 b_shape: Vec<usize>,
961 opts: &UnionOptions,
962) -> crate::BuiltinResult<UnionEvaluation> {
963 if a_shape.len() != 2 || b_shape.len() != 2 {
964 return Err(union_internal_error(
965 "union: 'rows' option requires 2-D complex matrices",
966 ));
967 }
968 if a_shape[1] != b_shape[1] {
969 return Err(union_error_with(
970 &UNION_ERROR_ROWS_COLUMN_MISMATCH,
971 UNION_ERROR_ROWS_COLUMN_MISMATCH.message,
972 ));
973 }
974 let rows_a = a_shape[0];
975 let cols = a_shape[1];
976 let rows_b = b_shape[0];
977
978 let mut entries = Vec::<ComplexRowUnionEntry<T>>::new();
979 let mut map: HashMap<Vec<ComplexKey>, usize> = HashMap::new();
980 let mut order_counter = 0usize;
981
982 for r in 0..rows_a {
983 let mut row_values = Vec::with_capacity(cols);
984 let mut key_row = Vec::with_capacity(cols);
985 for c in 0..cols {
986 let idx = r + c * rows_a;
987 let value = a[idx];
988 row_values.push(value);
989 key_row.push(ComplexKey::new(value));
990 }
991 match map.entry(key_row) {
992 Entry::Occupied(_) => {}
993 Entry::Vacant(v) => {
994 let entry_idx = entries.len();
995 entries.push(ComplexRowUnionEntry {
996 row_data: row_values,
997 a_row: Some(r),
998 b_row: None,
999 order_rank: order_counter,
1000 });
1001 v.insert(entry_idx);
1002 order_counter += 1;
1003 }
1004 }
1005 }
1006
1007 for r in 0..rows_b {
1008 let mut row_values = Vec::with_capacity(cols);
1009 let mut key_row = Vec::with_capacity(cols);
1010 for c in 0..cols {
1011 let idx = r + c * rows_b;
1012 let value = b[idx];
1013 row_values.push(value);
1014 key_row.push(ComplexKey::new(value));
1015 }
1016 match map.entry(key_row) {
1017 Entry::Occupied(occ) => {
1018 let entry = &mut entries[*occ.get()];
1019 if entry.a_row.is_none() && entry.b_row.is_none() {
1020 entry.b_row = Some(r);
1021 }
1022 }
1023 Entry::Vacant(v) => {
1024 let entry_idx = entries.len();
1025 entries.push(ComplexRowUnionEntry {
1026 row_data: row_values,
1027 a_row: None,
1028 b_row: Some(r),
1029 order_rank: order_counter,
1030 });
1031 v.insert(entry_idx);
1032 order_counter += 1;
1033 }
1034 }
1035 }
1036
1037 assemble_complex_row_union(entries, opts, cols)
1038}
1039
1040fn union_char(
1041 a: CharArray,
1042 b: CharArray,
1043 opts: &UnionOptions,
1044) -> crate::BuiltinResult<UnionEvaluation> {
1045 if opts.rows {
1046 union_char_rows(a, b, opts)
1047 } else {
1048 union_char_elements(a, b, opts)
1049 }
1050}
1051
1052fn union_char_elements(
1053 a: CharArray,
1054 b: CharArray,
1055 opts: &UnionOptions,
1056) -> crate::BuiltinResult<UnionEvaluation> {
1057 let mut entries = Vec::<CharUnionEntry>::new();
1058 let mut map: HashMap<u32, usize> = HashMap::new();
1059 let mut order_counter = 0usize;
1060
1061 for col in 0..a.cols {
1062 for row in 0..a.rows {
1063 let linear_idx = row + col * a.rows;
1064 let data_idx = row * a.cols + col;
1065 let ch = a.data[data_idx];
1066 let key = ch as u32;
1067 match map.entry(key) {
1068 Entry::Occupied(_) => {}
1069 Entry::Vacant(v) => {
1070 let entry_idx = entries.len();
1071 entries.push(CharUnionEntry {
1072 ch,
1073 a_index: Some(linear_idx),
1074 b_index: None,
1075 order_rank: order_counter,
1076 });
1077 v.insert(entry_idx);
1078 order_counter += 1;
1079 }
1080 }
1081 }
1082 }
1083
1084 for col in 0..b.cols {
1085 for row in 0..b.rows {
1086 let linear_idx = row + col * b.rows;
1087 let data_idx = row * b.cols + col;
1088 let ch = b.data[data_idx];
1089 let key = ch as u32;
1090 match map.entry(key) {
1091 Entry::Occupied(occ) => {
1092 let entry = &mut entries[*occ.get()];
1093 if entry.a_index.is_none() && entry.b_index.is_none() {
1094 entry.b_index = Some(linear_idx);
1095 }
1096 }
1097 Entry::Vacant(v) => {
1098 let entry_idx = entries.len();
1099 entries.push(CharUnionEntry {
1100 ch,
1101 a_index: None,
1102 b_index: Some(linear_idx),
1103 order_rank: order_counter,
1104 });
1105 v.insert(entry_idx);
1106 order_counter += 1;
1107 }
1108 }
1109 }
1110 }
1111
1112 assemble_char_union(entries, opts)
1113}
1114
1115fn union_char_rows(
1116 a: CharArray,
1117 b: CharArray,
1118 opts: &UnionOptions,
1119) -> crate::BuiltinResult<UnionEvaluation> {
1120 if a.cols != b.cols {
1121 return Err(union_error_with(
1122 &UNION_ERROR_ROWS_COLUMN_MISMATCH,
1123 UNION_ERROR_ROWS_COLUMN_MISMATCH.message,
1124 ));
1125 }
1126 let rows_a = a.rows;
1127 let rows_b = b.rows;
1128 let cols = a.cols;
1129
1130 let mut entries = Vec::<CharRowUnionEntry>::new();
1131 let mut map: HashMap<RowCharKey, usize> = HashMap::new();
1132 let mut order_counter = 0usize;
1133
1134 for r in 0..rows_a {
1135 let mut row_values = Vec::with_capacity(cols);
1136 for c in 0..cols {
1137 let idx = r * cols + c;
1138 row_values.push(a.data[idx]);
1139 }
1140 let key = RowCharKey::from_slice(&row_values);
1141 match map.entry(key) {
1142 Entry::Occupied(_) => {}
1143 Entry::Vacant(v) => {
1144 let entry_idx = entries.len();
1145 entries.push(CharRowUnionEntry {
1146 row_data: row_values,
1147 a_row: Some(r),
1148 b_row: None,
1149 order_rank: order_counter,
1150 });
1151 v.insert(entry_idx);
1152 order_counter += 1;
1153 }
1154 }
1155 }
1156
1157 for r in 0..rows_b {
1158 let mut row_values = Vec::with_capacity(cols);
1159 for c in 0..cols {
1160 let idx = r * cols + c;
1161 row_values.push(b.data[idx]);
1162 }
1163 let key = RowCharKey::from_slice(&row_values);
1164 match map.entry(key) {
1165 Entry::Occupied(occ) => {
1166 let entry = &mut entries[*occ.get()];
1167 if entry.a_row.is_none() && entry.b_row.is_none() {
1168 entry.b_row = Some(r);
1169 }
1170 }
1171 Entry::Vacant(v) => {
1172 let entry_idx = entries.len();
1173 entries.push(CharRowUnionEntry {
1174 row_data: row_values,
1175 a_row: None,
1176 b_row: Some(r),
1177 order_rank: order_counter,
1178 });
1179 v.insert(entry_idx);
1180 order_counter += 1;
1181 }
1182 }
1183 }
1184
1185 assemble_char_row_union(entries, opts, cols)
1186}
1187
1188fn union_string(
1189 a: StringArray,
1190 b: StringArray,
1191 opts: &UnionOptions,
1192) -> crate::BuiltinResult<UnionEvaluation> {
1193 if opts.rows {
1194 union_string_rows(a, b, opts)
1195 } else {
1196 union_string_elements(a, b, opts)
1197 }
1198}
1199
1200fn union_string_elements(
1201 a: StringArray,
1202 b: StringArray,
1203 opts: &UnionOptions,
1204) -> crate::BuiltinResult<UnionEvaluation> {
1205 let mut entries = Vec::<StringUnionEntry>::new();
1206 let mut map: HashMap<String, usize> = HashMap::new();
1207 let mut order_counter = 0usize;
1208
1209 for (idx, value) in a.data.iter().enumerate() {
1210 match map.entry(value.clone()) {
1211 Entry::Occupied(_) => {}
1212 Entry::Vacant(v) => {
1213 let entry_idx = entries.len();
1214 entries.push(StringUnionEntry {
1215 value: value.clone(),
1216 a_index: Some(idx),
1217 b_index: None,
1218 order_rank: order_counter,
1219 });
1220 v.insert(entry_idx);
1221 order_counter += 1;
1222 }
1223 }
1224 }
1225
1226 for (idx, value) in b.data.iter().enumerate() {
1227 match map.entry(value.clone()) {
1228 Entry::Occupied(occ) => {
1229 let entry = &mut entries[*occ.get()];
1230 if entry.a_index.is_none() && entry.b_index.is_none() {
1231 entry.b_index = Some(idx);
1232 }
1233 }
1234 Entry::Vacant(v) => {
1235 let entry_idx = entries.len();
1236 entries.push(StringUnionEntry {
1237 value: value.clone(),
1238 a_index: None,
1239 b_index: Some(idx),
1240 order_rank: order_counter,
1241 });
1242 v.insert(entry_idx);
1243 order_counter += 1;
1244 }
1245 }
1246 }
1247
1248 assemble_string_union(entries, opts)
1249}
1250
1251fn union_string_rows(
1252 a: StringArray,
1253 b: StringArray,
1254 opts: &UnionOptions,
1255) -> crate::BuiltinResult<UnionEvaluation> {
1256 if a.shape.len() != 2 || b.shape.len() != 2 {
1257 return Err(union_internal_error(
1258 "union: 'rows' option requires 2-D string arrays",
1259 ));
1260 }
1261 if a.shape[1] != b.shape[1] {
1262 return Err(union_error_with(
1263 &UNION_ERROR_ROWS_COLUMN_MISMATCH,
1264 UNION_ERROR_ROWS_COLUMN_MISMATCH.message,
1265 ));
1266 }
1267 let rows_a = a.shape[0];
1268 let cols = a.shape[1];
1269 let rows_b = b.shape[0];
1270
1271 let mut entries = Vec::<StringRowUnionEntry>::new();
1272 let mut map: HashMap<RowStringKey, usize> = HashMap::new();
1273 let mut order_counter = 0usize;
1274
1275 for r in 0..rows_a {
1276 let mut row_values = Vec::with_capacity(cols);
1277 for c in 0..cols {
1278 let idx = r + c * rows_a;
1279 row_values.push(a.data[idx].clone());
1280 }
1281 let key = RowStringKey(row_values.clone());
1282 match map.entry(key) {
1283 Entry::Occupied(_) => {}
1284 Entry::Vacant(v) => {
1285 let entry_idx = entries.len();
1286 entries.push(StringRowUnionEntry {
1287 row_data: row_values,
1288 a_row: Some(r),
1289 b_row: None,
1290 order_rank: order_counter,
1291 });
1292 v.insert(entry_idx);
1293 order_counter += 1;
1294 }
1295 }
1296 }
1297
1298 for r in 0..rows_b {
1299 let mut row_values = Vec::with_capacity(cols);
1300 for c in 0..cols {
1301 let idx = r + c * rows_b;
1302 row_values.push(b.data[idx].clone());
1303 }
1304 let key = RowStringKey(row_values.clone());
1305 match map.entry(key) {
1306 Entry::Occupied(occ) => {
1307 let entry = &mut entries[*occ.get()];
1308 if entry.a_row.is_none() && entry.b_row.is_none() {
1309 entry.b_row = Some(r);
1310 }
1311 }
1312 Entry::Vacant(v) => {
1313 let entry_idx = entries.len();
1314 entries.push(StringRowUnionEntry {
1315 row_data: row_values,
1316 a_row: None,
1317 b_row: Some(r),
1318 order_rank: order_counter,
1319 });
1320 v.insert(entry_idx);
1321 order_counter += 1;
1322 }
1323 }
1324 }
1325
1326 assemble_string_row_union(entries, opts, cols)
1327}
1328
1329#[derive(Debug, Clone)]
1330pub struct UnionEvaluation {
1331 values: Value,
1332 ia: Tensor,
1333 ib: Tensor,
1334}
1335
1336impl UnionEvaluation {
1337 fn new(values: Value, ia: Tensor, ib: Tensor) -> Self {
1338 Self { values, ia, ib }
1339 }
1340
1341 pub fn from_union_result(result: UnionResult) -> crate::BuiltinResult<Self> {
1342 let UnionResult { values, ia, ib } = result;
1343 let values_tensor = Tensor::new(values.data, values.shape)
1344 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1345 let ia_tensor = Tensor::new(ia.data, ia.shape)
1346 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1347 let ib_tensor = Tensor::new(ib.data, ib.shape)
1348 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1349 Ok(UnionEvaluation::new(
1350 tensor::tensor_into_value(values_tensor),
1351 ia_tensor,
1352 ib_tensor,
1353 ))
1354 }
1355
1356 pub fn into_numeric_union_result(self) -> crate::BuiltinResult<UnionResult> {
1357 let UnionEvaluation { values, ia, ib } = self;
1358 let values_tensor =
1359 tensor::value_into_tensor_for("union", values).map_err(|e| union_internal_error(e))?;
1360 Ok(UnionResult {
1361 values: tensor::tensor_into_host_f64_owned(values_tensor),
1362 ia: tensor::tensor_into_host_f64_owned(ia),
1363 ib: tensor::tensor_into_host_f64_owned(ib),
1364 })
1365 }
1366
1367 pub fn into_values_value(self) -> Value {
1368 self.values
1369 }
1370
1371 pub fn into_pair(self) -> (Value, Value) {
1372 let ia = tensor::tensor_into_value(self.ia);
1373 (self.values, ia)
1374 }
1375
1376 pub fn into_triple(self) -> (Value, Value, Value) {
1377 let ia = tensor::tensor_into_value(self.ia);
1378 let ib = tensor::tensor_into_value(self.ib);
1379 (self.values, ia, ib)
1380 }
1381
1382 pub fn values_value(&self) -> Value {
1383 self.values.clone()
1384 }
1385
1386 pub fn ia_value(&self) -> Value {
1387 tensor::tensor_into_value(self.ia.clone())
1388 }
1389
1390 pub fn ib_value(&self) -> Value {
1391 tensor::tensor_into_value(self.ib.clone())
1392 }
1393}
1394
1395#[derive(Debug)]
1396struct FloatingUnionEntry<T> {
1397 value: T,
1398 a_index: Option<usize>,
1399 b_index: Option<usize>,
1400 order_rank: usize,
1401}
1402
1403#[derive(Debug)]
1404struct IntegerUnionEntry {
1405 value: IntValue,
1406 a_index: Option<usize>,
1407 b_index: Option<usize>,
1408 order_rank: usize,
1409}
1410
1411#[derive(Debug)]
1412struct FloatingRowUnionEntry<T> {
1413 row_data: Vec<T>,
1414 a_row: Option<usize>,
1415 b_row: Option<usize>,
1416 order_rank: usize,
1417}
1418
1419#[derive(Debug)]
1420struct IntegerRowUnionEntry {
1421 row_data: Vec<IntValue>,
1422 a_row: Option<usize>,
1423 b_row: Option<usize>,
1424 order_rank: usize,
1425}
1426
1427#[derive(Debug)]
1428struct ComplexUnionEntry<T> {
1429 value: (T, T),
1430 a_index: Option<usize>,
1431 b_index: Option<usize>,
1432 order_rank: usize,
1433}
1434
1435#[derive(Debug)]
1436struct ComplexRowUnionEntry<T> {
1437 row_data: Vec<(T, T)>,
1438 a_row: Option<usize>,
1439 b_row: Option<usize>,
1440 order_rank: usize,
1441}
1442
1443#[derive(Debug)]
1444struct CharUnionEntry {
1445 ch: char,
1446 a_index: Option<usize>,
1447 b_index: Option<usize>,
1448 order_rank: usize,
1449}
1450
1451#[derive(Debug)]
1452struct CharRowUnionEntry {
1453 row_data: Vec<char>,
1454 a_row: Option<usize>,
1455 b_row: Option<usize>,
1456 order_rank: usize,
1457}
1458
1459#[derive(Debug)]
1460struct StringUnionEntry {
1461 value: String,
1462 a_index: Option<usize>,
1463 b_index: Option<usize>,
1464 order_rank: usize,
1465}
1466
1467#[derive(Debug)]
1468struct StringRowUnionEntry {
1469 row_data: Vec<String>,
1470 a_row: Option<usize>,
1471 b_row: Option<usize>,
1472 order_rank: usize,
1473}
1474
1475#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1476struct FloatingRowKey(Vec<u64>);
1477
1478impl FloatingRowKey {
1479 fn from_slice<T: SetFloat>(values: &[T]) -> Self {
1480 Self(values.iter().map(|&value| value.canonical_key()).collect())
1481 }
1482}
1483
1484#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1485struct ComplexKey {
1486 re: u64,
1487 im: u64,
1488}
1489
1490impl ComplexKey {
1491 fn new<T: SetFloat>(value: (T, T)) -> Self {
1492 Self {
1493 re: value.0.canonical_key(),
1494 im: value.1.canonical_key(),
1495 }
1496 }
1497}
1498
1499#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1500struct RowCharKey(Vec<u32>);
1501
1502impl RowCharKey {
1503 fn from_slice(values: &[char]) -> Self {
1504 RowCharKey(values.iter().map(|&ch| ch as u32).collect())
1505 }
1506}
1507
1508#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1509struct RowStringKey(Vec<String>);
1510
1511fn assemble_floating_union<T: SetFloat>(
1512 entries: Vec<FloatingUnionEntry<T>>,
1513 opts: &UnionOptions,
1514) -> crate::BuiltinResult<UnionEvaluation> {
1515 let mut order: Vec<usize> = (0..entries.len()).collect();
1516 match opts.order {
1517 UnionOrder::Sorted => {
1518 order.sort_by(|&lhs, &rhs| entries[lhs].value.compare(entries[rhs].value));
1519 }
1520 UnionOrder::Stable => {
1521 order.sort_by_key(|&idx| entries[idx].order_rank);
1522 }
1523 }
1524
1525 let mut values = Vec::with_capacity(order.len());
1526 let mut ia = Vec::new();
1527 let mut ib = Vec::new();
1528 for &idx in &order {
1529 let entry = &entries[idx];
1530 values.push(entry.value);
1531 if let Some(a_idx) = entry.a_index {
1532 ia.push((a_idx + 1) as f64);
1533 } else if let Some(b_idx) = entry.b_index {
1534 ib.push((b_idx + 1) as f64);
1535 }
1536 }
1537
1538 let value_tensor =
1539 Tensor::from_numeric_storage(T::numeric_storage(values), vec![order.len(), 1])
1540 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1541 let ia_len = ia.len();
1542 let ib_len = ib.len();
1543 let ia_tensor = Tensor::new(ia, vec![ia_len, 1])
1544 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1545 let ib_tensor = Tensor::new(ib, vec![ib_len, 1])
1546 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1547
1548 Ok(UnionEvaluation::new(
1549 tensor::tensor_into_value(value_tensor),
1550 ia_tensor,
1551 ib_tensor,
1552 ))
1553}
1554
1555fn assemble_integer_union(
1556 entries: Vec<IntegerUnionEntry>,
1557 storage: &IntegerStorage,
1558 opts: &UnionOptions,
1559) -> crate::BuiltinResult<UnionEvaluation> {
1560 let mut order: Vec<_> = (0..entries.len()).collect();
1561 match opts.order {
1562 UnionOrder::Sorted => order.sort_by(|&a, &b| {
1563 integer_order::compare(&entries[a].value, &entries[b].value, false, false)
1564 }),
1565 UnionOrder::Stable => order.sort_by_key(|&index| entries[index].order_rank),
1566 }
1567 let values: Vec<_> = order
1568 .iter()
1569 .map(|&index| entries[index].value.clone())
1570 .collect();
1571 let mut ia = Vec::new();
1572 let mut ib = Vec::new();
1573 for &index in &order {
1574 let entry = &entries[index];
1575 if let Some(a_index) = entry.a_index {
1576 ia.push((a_index + 1) as f64);
1577 } else if let Some(b_index) = entry.b_index {
1578 ib.push((b_index + 1) as f64);
1579 }
1580 }
1581 let values = Tensor::new_integer(
1582 storage
1583 .from_exact_values_like(values)
1584 .map_err(|e| union_internal_error(format!("union: {e}")))?,
1585 vec![order.len(), 1],
1586 )
1587 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1588 let ia_len = ia.len();
1589 let ib_len = ib.len();
1590 let ia = Tensor::new(ia, vec![ia_len, 1])
1591 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1592 let ib = Tensor::new(ib, vec![ib_len, 1])
1593 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1594 Ok(UnionEvaluation::new(Value::Tensor(values), ia, ib))
1595}
1596
1597fn assemble_floating_row_union<T: SetFloat>(
1598 entries: Vec<FloatingRowUnionEntry<T>>,
1599 opts: &UnionOptions,
1600 cols: usize,
1601) -> crate::BuiltinResult<UnionEvaluation> {
1602 let mut order: Vec<usize> = (0..entries.len()).collect();
1603 match opts.order {
1604 UnionOrder::Sorted => {
1605 order.sort_by(|&lhs, &rhs| {
1606 compare_floating_rows(&entries[lhs].row_data, &entries[rhs].row_data)
1607 });
1608 }
1609 UnionOrder::Stable => {
1610 order.sort_by_key(|&idx| entries[idx].order_rank);
1611 }
1612 }
1613
1614 let unique_rows = order.len();
1615 let mut values = vec![T::default(); unique_rows * cols];
1616 let mut ia = Vec::new();
1617 let mut ib = Vec::new();
1618
1619 for (row_pos, &entry_idx) in order.iter().enumerate() {
1620 let entry = &entries[entry_idx];
1621 for col in 0..cols {
1622 let dest = row_pos + col * unique_rows;
1623 values[dest] = entry.row_data[col];
1624 }
1625 if let Some(a_row) = entry.a_row {
1626 ia.push((a_row + 1) as f64);
1627 } else if let Some(b_row) = entry.b_row {
1628 ib.push((b_row + 1) as f64);
1629 }
1630 }
1631
1632 let value_tensor =
1633 Tensor::from_numeric_storage(T::numeric_storage(values), vec![unique_rows, cols])
1634 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1635 let ia_len = ia.len();
1636 let ib_len = ib.len();
1637 let ia_tensor = Tensor::new(ia, vec![ia_len, 1])
1638 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1639 let ib_tensor = Tensor::new(ib, vec![ib_len, 1])
1640 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1641
1642 Ok(UnionEvaluation::new(
1643 tensor::tensor_into_value(value_tensor),
1644 ia_tensor,
1645 ib_tensor,
1646 ))
1647}
1648
1649fn assemble_integer_row_union(
1650 entries: Vec<IntegerRowUnionEntry>,
1651 storage: &IntegerStorage,
1652 opts: &UnionOptions,
1653 cols: usize,
1654) -> crate::BuiltinResult<UnionEvaluation> {
1655 let mut order: Vec<_> = (0..entries.len()).collect();
1656 match opts.order {
1657 UnionOrder::Sorted => order.sort_by(|&a, &b| {
1658 for (left, right) in entries[a].row_data.iter().zip(&entries[b].row_data) {
1659 let ordering = integer_order::compare(left, right, false, false);
1660 if ordering != Ordering::Equal {
1661 return ordering;
1662 }
1663 }
1664 Ordering::Equal
1665 }),
1666 UnionOrder::Stable => order.sort_by_key(|&index| entries[index].order_rank),
1667 }
1668 let rows = order.len();
1669 let mut values = Vec::with_capacity(rows * cols);
1670 for col in 0..cols {
1671 for &index in &order {
1672 values.push(entries[index].row_data[col].clone());
1673 }
1674 }
1675 let mut ia = Vec::new();
1676 let mut ib = Vec::new();
1677 for &index in &order {
1678 let entry = &entries[index];
1679 if let Some(a_row) = entry.a_row {
1680 ia.push((a_row + 1) as f64);
1681 } else if let Some(b_row) = entry.b_row {
1682 ib.push((b_row + 1) as f64);
1683 }
1684 }
1685 let values = Tensor::new_integer(
1686 storage
1687 .from_exact_values_like(values)
1688 .map_err(|e| union_internal_error(format!("union: {e}")))?,
1689 vec![rows, cols],
1690 )
1691 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1692 let ia_len = ia.len();
1693 let ib_len = ib.len();
1694 let ia = Tensor::new(ia, vec![ia_len, 1])
1695 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1696 let ib = Tensor::new(ib, vec![ib_len, 1])
1697 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1698 Ok(UnionEvaluation::new(Value::Tensor(values), ia, ib))
1699}
1700
1701fn assemble_complex_union<T: SetFloat>(
1702 entries: Vec<ComplexUnionEntry<T>>,
1703 opts: &UnionOptions,
1704) -> crate::BuiltinResult<UnionEvaluation> {
1705 let mut order: Vec<usize> = (0..entries.len()).collect();
1706 match opts.order {
1707 UnionOrder::Sorted => {
1708 order.sort_by(|&lhs, &rhs| compare_complex(entries[lhs].value, entries[rhs].value));
1709 }
1710 UnionOrder::Stable => {
1711 order.sort_by_key(|&idx| entries[idx].order_rank);
1712 }
1713 }
1714
1715 let mut values = Vec::with_capacity(order.len());
1716 let mut ia = Vec::new();
1717 let mut ib = Vec::new();
1718 for &idx in &order {
1719 let entry = &entries[idx];
1720 values.push(entry.value);
1721 if let Some(a_idx) = entry.a_index {
1722 ia.push((a_idx + 1) as f64);
1723 } else if let Some(b_idx) = entry.b_index {
1724 ib.push((b_idx + 1) as f64);
1725 }
1726 }
1727
1728 let value_tensor =
1729 ComplexTensor::from_complex_storage(T::complex_storage(values), vec![order.len(), 1])
1730 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1731 let ia_len = ia.len();
1732 let ib_len = ib.len();
1733 let ia_tensor = Tensor::new(ia, vec![ia_len, 1])
1734 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1735 let ib_tensor = Tensor::new(ib, vec![ib_len, 1])
1736 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1737
1738 let value = if value_tensor.as_f32_slice().is_some() {
1739 Value::ComplexTensor(value_tensor)
1740 } else {
1741 complex_tensor_into_value(value_tensor)
1742 };
1743 Ok(UnionEvaluation::new(value, ia_tensor, ib_tensor))
1744}
1745
1746fn assemble_complex_row_union<T: SetFloat>(
1747 entries: Vec<ComplexRowUnionEntry<T>>,
1748 opts: &UnionOptions,
1749 cols: usize,
1750) -> crate::BuiltinResult<UnionEvaluation> {
1751 let mut order: Vec<usize> = (0..entries.len()).collect();
1752 match opts.order {
1753 UnionOrder::Sorted => {
1754 order.sort_by(|&lhs, &rhs| {
1755 compare_complex_rows(&entries[lhs].row_data, &entries[rhs].row_data)
1756 });
1757 }
1758 UnionOrder::Stable => {
1759 order.sort_by_key(|&idx| entries[idx].order_rank);
1760 }
1761 }
1762
1763 let unique_rows = order.len();
1764 let mut values = vec![(T::default(), T::default()); unique_rows * cols];
1765 let mut ia = Vec::new();
1766 let mut ib = Vec::new();
1767
1768 for (row_pos, &entry_idx) in order.iter().enumerate() {
1769 let entry = &entries[entry_idx];
1770 for col in 0..cols {
1771 let dest = row_pos + col * unique_rows;
1772 values[dest] = entry.row_data[col];
1773 }
1774 if let Some(a_row) = entry.a_row {
1775 ia.push((a_row + 1) as f64);
1776 } else if let Some(b_row) = entry.b_row {
1777 ib.push((b_row + 1) as f64);
1778 }
1779 }
1780
1781 let value_tensor =
1782 ComplexTensor::from_complex_storage(T::complex_storage(values), vec![unique_rows, cols])
1783 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1784 let ia_len = ia.len();
1785 let ib_len = ib.len();
1786 let ia_tensor = Tensor::new(ia, vec![ia_len, 1])
1787 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1788 let ib_tensor = Tensor::new(ib, vec![ib_len, 1])
1789 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1790
1791 let value = if value_tensor.as_f32_slice().is_some() {
1792 Value::ComplexTensor(value_tensor)
1793 } else {
1794 complex_tensor_into_value(value_tensor)
1795 };
1796 Ok(UnionEvaluation::new(value, ia_tensor, ib_tensor))
1797}
1798
1799fn assemble_char_union(
1800 entries: Vec<CharUnionEntry>,
1801 opts: &UnionOptions,
1802) -> crate::BuiltinResult<UnionEvaluation> {
1803 let mut order: Vec<usize> = (0..entries.len()).collect();
1804 match opts.order {
1805 UnionOrder::Sorted => {
1806 order.sort_by(|&lhs, &rhs| entries[lhs].ch.cmp(&entries[rhs].ch));
1807 }
1808 UnionOrder::Stable => {
1809 order.sort_by_key(|&idx| entries[idx].order_rank);
1810 }
1811 }
1812
1813 let mut values = Vec::with_capacity(order.len());
1814 let mut ia = Vec::new();
1815 let mut ib = Vec::new();
1816 for &idx in &order {
1817 let entry = &entries[idx];
1818 values.push(entry.ch);
1819 if let Some(a_idx) = entry.a_index {
1820 ia.push((a_idx + 1) as f64);
1821 } else if let Some(b_idx) = entry.b_index {
1822 ib.push((b_idx + 1) as f64);
1823 }
1824 }
1825
1826 let value_array = CharArray::new(values, order.len(), 1)
1827 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1828 let ia_len = ia.len();
1829 let ib_len = ib.len();
1830 let ia_tensor = Tensor::new(ia, vec![ia_len, 1])
1831 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1832 let ib_tensor = Tensor::new(ib, vec![ib_len, 1])
1833 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1834
1835 Ok(UnionEvaluation::new(
1836 Value::CharArray(value_array),
1837 ia_tensor,
1838 ib_tensor,
1839 ))
1840}
1841
1842fn assemble_char_row_union(
1843 entries: Vec<CharRowUnionEntry>,
1844 opts: &UnionOptions,
1845 cols: usize,
1846) -> crate::BuiltinResult<UnionEvaluation> {
1847 let mut order: Vec<usize> = (0..entries.len()).collect();
1848 match opts.order {
1849 UnionOrder::Sorted => {
1850 order.sort_by(|&lhs, &rhs| {
1851 compare_char_rows(&entries[lhs].row_data, &entries[rhs].row_data)
1852 });
1853 }
1854 UnionOrder::Stable => {
1855 order.sort_by_key(|&idx| entries[idx].order_rank);
1856 }
1857 }
1858
1859 let unique_rows = order.len();
1860 let mut values = vec!['\0'; unique_rows * cols];
1861 let mut ia = Vec::new();
1862 let mut ib = Vec::new();
1863
1864 for (row_pos, &entry_idx) in order.iter().enumerate() {
1865 let entry = &entries[entry_idx];
1866 for col in 0..cols {
1867 let dest = row_pos * cols + col;
1868 values[dest] = entry.row_data[col];
1869 }
1870 if let Some(a_row) = entry.a_row {
1871 ia.push((a_row + 1) as f64);
1872 } else if let Some(b_row) = entry.b_row {
1873 ib.push((b_row + 1) as f64);
1874 }
1875 }
1876
1877 let value_array = CharArray::new(values, unique_rows, cols)
1878 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1879 let ia_len = ia.len();
1880 let ib_len = ib.len();
1881 let ia_tensor = Tensor::new(ia, vec![ia_len, 1])
1882 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1883 let ib_tensor = Tensor::new(ib, vec![ib_len, 1])
1884 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1885
1886 Ok(UnionEvaluation::new(
1887 Value::CharArray(value_array),
1888 ia_tensor,
1889 ib_tensor,
1890 ))
1891}
1892
1893fn assemble_string_union(
1894 entries: Vec<StringUnionEntry>,
1895 opts: &UnionOptions,
1896) -> crate::BuiltinResult<UnionEvaluation> {
1897 let mut order: Vec<usize> = (0..entries.len()).collect();
1898 match opts.order {
1899 UnionOrder::Sorted => {
1900 order.sort_by(|&lhs, &rhs| entries[lhs].value.cmp(&entries[rhs].value));
1901 }
1902 UnionOrder::Stable => {
1903 order.sort_by_key(|&idx| entries[idx].order_rank);
1904 }
1905 }
1906
1907 let mut values = Vec::with_capacity(order.len());
1908 let mut ia = Vec::new();
1909 let mut ib = Vec::new();
1910 for &idx in &order {
1911 let entry = &entries[idx];
1912 values.push(entry.value.clone());
1913 if let Some(a_idx) = entry.a_index {
1914 ia.push((a_idx + 1) as f64);
1915 } else if let Some(b_idx) = entry.b_index {
1916 ib.push((b_idx + 1) as f64);
1917 }
1918 }
1919
1920 let value_array = StringArray::new(values, vec![order.len(), 1])
1921 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1922 let ia_len = ia.len();
1923 let ib_len = ib.len();
1924 let ia_tensor = Tensor::new(ia, vec![ia_len, 1])
1925 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1926 let ib_tensor = Tensor::new(ib, vec![ib_len, 1])
1927 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1928
1929 Ok(UnionEvaluation::new(
1930 Value::StringArray(value_array),
1931 ia_tensor,
1932 ib_tensor,
1933 ))
1934}
1935
1936fn assemble_string_row_union(
1937 entries: Vec<StringRowUnionEntry>,
1938 opts: &UnionOptions,
1939 cols: usize,
1940) -> crate::BuiltinResult<UnionEvaluation> {
1941 let mut order: Vec<usize> = (0..entries.len()).collect();
1942 match opts.order {
1943 UnionOrder::Sorted => {
1944 order.sort_by(|&lhs, &rhs| {
1945 compare_string_rows(&entries[lhs].row_data, &entries[rhs].row_data)
1946 });
1947 }
1948 UnionOrder::Stable => {
1949 order.sort_by_key(|&idx| entries[idx].order_rank);
1950 }
1951 }
1952
1953 let unique_rows = order.len();
1954 let mut values = vec![String::new(); unique_rows * cols];
1955 let mut ia = Vec::new();
1956 let mut ib = Vec::new();
1957
1958 for (row_pos, &entry_idx) in order.iter().enumerate() {
1959 let entry = &entries[entry_idx];
1960 for col in 0..cols {
1961 let dest = row_pos + col * unique_rows;
1962 values[dest] = entry.row_data[col].clone();
1963 }
1964 if let Some(a_row) = entry.a_row {
1965 ia.push((a_row + 1) as f64);
1966 } else if let Some(b_row) = entry.b_row {
1967 ib.push((b_row + 1) as f64);
1968 }
1969 }
1970
1971 let value_array = StringArray::new(values, vec![unique_rows, cols])
1972 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1973 let ia_len = ia.len();
1974 let ib_len = ib.len();
1975 let ia_tensor = Tensor::new(ia, vec![ia_len, 1])
1976 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1977 let ib_tensor = Tensor::new(ib, vec![ib_len, 1])
1978 .map_err(|e| union_internal_error(format!("union: {e}")))?;
1979
1980 Ok(UnionEvaluation::new(
1981 Value::StringArray(value_array),
1982 ia_tensor,
1983 ib_tensor,
1984 ))
1985}
1986
1987fn compare_floating_rows<T: SetFloat>(a: &[T], b: &[T]) -> Ordering {
1988 for (lhs, rhs) in a.iter().zip(b.iter()) {
1989 let ord = lhs.compare(*rhs);
1990 if ord != Ordering::Equal {
1991 return ord;
1992 }
1993 }
1994 Ordering::Equal
1995}
1996
1997fn complex_is_nan<T: SetFloat>(value: (T, T)) -> bool {
1998 value.0.is_nan() || value.1.is_nan()
1999}
2000
2001fn compare_complex<T: SetFloat>(a: (T, T), b: (T, T)) -> Ordering {
2002 match (complex_is_nan(a), complex_is_nan(b)) {
2003 (true, true) => Ordering::Equal,
2004 (true, false) => Ordering::Greater,
2005 (false, true) => Ordering::Less,
2006 (false, false) => {
2007 let mag_a = a.0.hypot(a.1);
2008 let mag_b = b.0.hypot(b.1);
2009 let mag_cmp = mag_a.compare(mag_b);
2010 if mag_cmp != Ordering::Equal {
2011 return mag_cmp;
2012 }
2013 let re_cmp = a.0.compare(b.0);
2014 if re_cmp != Ordering::Equal {
2015 return re_cmp;
2016 }
2017 a.1.compare(b.1)
2018 }
2019 }
2020}
2021
2022fn compare_complex_rows<T: SetFloat>(a: &[(T, T)], b: &[(T, T)]) -> Ordering {
2023 for (lhs, rhs) in a.iter().zip(b.iter()) {
2024 let ord = compare_complex(*lhs, *rhs);
2025 if ord != Ordering::Equal {
2026 return ord;
2027 }
2028 }
2029 Ordering::Equal
2030}
2031
2032fn compare_char_rows(a: &[char], b: &[char]) -> Ordering {
2033 for (lhs, rhs) in a.iter().zip(b.iter()) {
2034 let ord = lhs.cmp(rhs);
2035 if ord != Ordering::Equal {
2036 return ord;
2037 }
2038 }
2039 Ordering::Equal
2040}
2041
2042fn compare_string_rows(a: &[String], b: &[String]) -> Ordering {
2043 for (lhs, rhs) in a.iter().zip(b.iter()) {
2044 let ord = lhs.cmp(rhs);
2045 if ord != Ordering::Equal {
2046 return ord;
2047 }
2048 }
2049 Ordering::Equal
2050}
2051
2052#[cfg(test)]
2053pub(crate) mod tests {
2054 use super::*;
2055 use crate::builtins::common::test_support;
2056 use runmat_accelerate_api::HostTensorView;
2057 use runmat_builtins::{ResolveContext, Type};
2058 use runmat_value::{IntValue, Tensor, Value};
2059
2060 fn evaluate_sync(a: Value, b: Value, rest: &[Value]) -> crate::BuiltinResult<UnionEvaluation> {
2061 futures::executor::block_on(evaluate(a, b, rest))
2062 }
2063
2064 fn builtin_sync(a: Value, b: Value, rest: Vec<Value>) -> crate::BuiltinResult<Value> {
2065 futures::executor::block_on(union_builtin(a, b, rest))
2066 }
2067
2068 #[test]
2069 fn registered_builtin_restores_resident_outputs_and_rejects_excess_arity() {
2070 test_support::with_test_provider(|provider| {
2071 let left = Tensor::new_integer(IntegerStorage::I32(vec![7, 2, 9]), vec![3, 1]).unwrap();
2072 let right = Tensor::new_integer(IntegerStorage::I32(vec![2, 7]), vec![2, 1]).unwrap();
2073 let left =
2074 Value::GpuTensor(gpu_helpers::upload_tensor(provider, &left).expect("upload left"));
2075 let right = Value::GpuTensor(
2076 gpu_helpers::upload_tensor(provider, &right).expect("upload right"),
2077 );
2078
2079 {
2080 let _guard = crate::output_count::push_output_count(Some(3));
2081 let Value::OutputList(outputs) =
2082 builtin_sync(left, right, Vec::new()).expect("resident union")
2083 else {
2084 panic!("expected output list");
2085 };
2086 assert_eq!(outputs.len(), 3);
2087 assert!(outputs
2088 .iter()
2089 .all(|output| matches!(output, Value::GpuTensor(_))));
2090 assert_eq!(
2091 test_support::gather(outputs[0].clone())
2092 .expect("gather values")
2093 .integer_storage(),
2094 Some(&IntegerStorage::I32(vec![2, 7, 9]))
2095 );
2096 }
2097
2098 let _guard = crate::output_count::push_output_count(Some(4));
2099 let err = builtin_sync(Value::Num(1.0), Value::Num(1.0), Vec::new())
2100 .expect_err("excess outputs must fail");
2101 assert_eq!(err.identifier(), UNION_ERROR_INVALID_ARGUMENT.identifier);
2102 });
2103 }
2104
2105 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2106 #[test]
2107 fn union_numeric_sorted_default() {
2108 let a = Tensor::new(vec![5.0, 7.0, 1.0], vec![3, 1]).unwrap();
2109 let b = Tensor::new(vec![3.0, 1.0, 1.0], vec![3, 1]).unwrap();
2110 let eval = evaluate_sync(Value::Tensor(a), Value::Tensor(b), &[]).expect("union");
2111 match eval.values_value() {
2112 Value::Tensor(t) => {
2113 assert_eq!(t.materialize_f64(), vec![1.0, 3.0, 5.0, 7.0]);
2114 assert_eq!(t.shape, vec![4, 1]);
2115 }
2116 other => panic!("expected tensor result, got {other:?}"),
2117 }
2118 let ia = tensor::value_into_tensor_for("union", eval.ia_value()).expect("ia tensor");
2119 assert_eq!(ia.materialize_f64(), vec![3.0, 1.0, 2.0]);
2120 assert_eq!(ia.shape, vec![3, 1]);
2121 let ib = tensor::value_into_tensor_for("union", eval.ib_value()).expect("ib tensor");
2122 assert_eq!(ib.materialize_f64(), vec![1.0]);
2123 assert_eq!(ib.shape, vec![1, 1]);
2124 }
2125
2126 #[test]
2127 fn union_preserves_native_single_elements_and_rows() {
2128 let a = Tensor::from_f32(vec![5.0, 7.0, 1.0], vec![3, 1]).unwrap();
2129 let b = Tensor::from_f32(vec![3.0, 1.0, 1.0], vec![3, 1]).unwrap();
2130 let values = evaluate_sync(Value::Tensor(a), Value::Tensor(b), &[])
2131 .expect("single union")
2132 .into_values_value();
2133 let Value::Tensor(values) = values else {
2134 panic!("expected native single values");
2135 };
2136 assert_eq!(
2137 values.into_numeric_storage().unwrap(),
2138 NumericStorage::F32(vec![1.0, 3.0, 5.0, 7.0])
2139 );
2140
2141 let a = Tensor::from_f32(vec![1.0, 3.0, 1.0, 2.0, 4.0, 2.0], vec![3, 2]).unwrap();
2142 let b = Tensor::from_f32(vec![3.0, 5.0, 4.0, 6.0], vec![2, 2]).unwrap();
2143 let values = evaluate_sync(Value::Tensor(a), Value::Tensor(b), &[Value::from("rows")])
2144 .expect("single row union")
2145 .into_values_value();
2146 let Value::Tensor(values) = values else {
2147 panic!("expected native single rows");
2148 };
2149 assert_eq!(values.shape, vec![3, 2]);
2150 assert_eq!(
2151 values.into_numeric_storage().unwrap(),
2152 NumericStorage::F32(vec![1.0, 3.0, 5.0, 2.0, 4.0, 6.0])
2153 );
2154 }
2155
2156 #[test]
2157 fn union_preserves_native_complex_single_elements_and_rows() {
2158 let a = ComplexTensor::from_f32(vec![(1.0, 1.0)], vec![1, 1]).unwrap();
2159 let b = ComplexTensor::from_f32(vec![(1.0, 1.0)], vec![1, 1]).unwrap();
2160 let values = evaluate_sync(Value::ComplexTensor(a), Value::ComplexTensor(b), &[])
2161 .expect("complex single union")
2162 .into_values_value();
2163 let Value::ComplexTensor(values) = values else {
2164 panic!("expected native complex single value");
2165 };
2166 assert_eq!(values.as_f32_slice(), Some(&[(1.0, 1.0)][..]));
2167
2168 let a = ComplexTensor::from_f32(
2169 vec![
2170 (1.0, 0.0),
2171 (3.0, 0.0),
2172 (1.0, 0.0),
2173 (2.0, 1.0),
2174 (4.0, 1.0),
2175 (2.0, 1.0),
2176 ],
2177 vec![3, 2],
2178 )
2179 .unwrap();
2180 let b = ComplexTensor::from_f32(
2181 vec![(3.0, 0.0), (5.0, 0.0), (4.0, 1.0), (6.0, 1.0)],
2182 vec![2, 2],
2183 )
2184 .unwrap();
2185 let values = evaluate_sync(
2186 Value::ComplexTensor(a),
2187 Value::ComplexTensor(b),
2188 &[Value::from("rows")],
2189 )
2190 .expect("complex single row union")
2191 .into_values_value();
2192 let Value::ComplexTensor(values) = values else {
2193 panic!("expected native complex single rows");
2194 };
2195 assert_eq!(values.shape, vec![3, 2]);
2196 assert_eq!(
2197 values.as_f32_slice(),
2198 Some(
2199 &[
2200 (1.0, 0.0),
2201 (3.0, 0.0),
2202 (5.0, 0.0),
2203 (2.0, 1.0),
2204 (4.0, 1.0),
2205 (6.0, 1.0),
2206 ][..]
2207 )
2208 );
2209 }
2210
2211 #[test]
2212 fn union_preserves_exact_integer_elements_and_rows() {
2213 let a = Tensor::new_integer(
2214 runmat_value::IntegerStorage::U64(vec![u64::MAX, 0, 9_007_199_254_740_993]),
2215 vec![3, 1],
2216 )
2217 .expect("input");
2218 let b = Tensor::new_integer(runmat_value::IntegerStorage::U64(vec![0, 7]), vec![2, 1])
2219 .expect("input");
2220 let (values, ia, ib) = evaluate_sync(Value::Tensor(a), Value::Tensor(b), &[])
2221 .expect("union")
2222 .into_triple();
2223 let Value::Tensor(values) = values else {
2224 panic!("exact values");
2225 };
2226 assert_eq!(
2227 values.integer_storage(),
2228 Some(&runmat_value::IntegerStorage::U64(vec![
2229 0,
2230 7,
2231 9_007_199_254_740_993,
2232 u64::MAX
2233 ]))
2234 );
2235 let ia = tensor::value_into_tensor_for("union", ia).expect("indices");
2236 assert_eq!(ia.materialize_f64(), vec![2.0, 3.0, 1.0]);
2237 let ib = tensor::value_into_tensor_for("union", ib).expect("indices");
2238 assert_eq!(ib.materialize_f64(), vec![2.0]);
2239
2240 let a = Tensor::new_integer(
2241 runmat_value::IntegerStorage::U64(vec![u64::MAX, 9_007_199_254_740_993, 0, 1]),
2242 vec![2, 2],
2243 )
2244 .expect("rows input");
2245 let b = Tensor::new_integer(
2246 runmat_value::IntegerStorage::U64(vec![9_007_199_254_740_993, 4, 1, 2]),
2247 vec![2, 2],
2248 )
2249 .expect("rows input");
2250 let (values, ia, ib) =
2251 evaluate_sync(Value::Tensor(a), Value::Tensor(b), &[Value::from("rows")])
2252 .expect("union rows")
2253 .into_triple();
2254 let Value::Tensor(values) = values else {
2255 panic!("exact row values");
2256 };
2257 assert_eq!(
2258 values.integer_storage(),
2259 Some(&runmat_value::IntegerStorage::U64(vec![
2260 4,
2261 9_007_199_254_740_993,
2262 u64::MAX,
2263 2,
2264 1,
2265 0,
2266 ]))
2267 );
2268 let ia = tensor::value_into_tensor_for("union", ia).expect("row indices");
2269 assert_eq!(ia.materialize_f64(), vec![2.0, 1.0]);
2270 let ib = tensor::value_into_tensor_for("union", ib).expect("row indices");
2271 assert_eq!(ib.materialize_f64(), vec![2.0]);
2272 }
2273
2274 #[test]
2275 fn union_rejects_mixed_nondouble_integer_classes() {
2276 let a = Tensor::new_integer(runmat_value::IntegerStorage::U16(vec![7, 2]), vec![2, 1])
2277 .expect("input");
2278 let b = Tensor::new_integer(runmat_value::IntegerStorage::I32(vec![2, 9]), vec![2, 1])
2279 .expect("input");
2280 let error = evaluate_sync(Value::Tensor(a), Value::Tensor(b), &[])
2281 .expect_err("mixed integer classes must reject");
2282 assert_eq!(
2283 error.identifier(),
2284 UNION_ERROR_NUMERIC_CLASS_MISMATCH.identifier
2285 );
2286 }
2287
2288 #[test]
2289 fn union_preserves_every_exact_integer_class() {
2290 let cases = [
2291 runmat_value::IntegerStorage::I8(vec![i8::MAX, 0]),
2292 runmat_value::IntegerStorage::I16(vec![i16::MAX, 0]),
2293 runmat_value::IntegerStorage::I32(vec![i32::MAX, 0]),
2294 runmat_value::IntegerStorage::I64(vec![i64::MAX, 0]),
2295 runmat_value::IntegerStorage::U8(vec![u8::MAX, 0]),
2296 runmat_value::IntegerStorage::U16(vec![u16::MAX, 0]),
2297 runmat_value::IntegerStorage::U32(vec![u32::MAX, 0]),
2298 runmat_value::IntegerStorage::U64(vec![u64::MAX, 0]),
2299 ];
2300 for storage in cases {
2301 let expected = storage.clone();
2302 let a = Tensor::new_integer(storage, vec![2, 1]).expect("input");
2303 let b = Tensor::new_integer(expected.zeros_like(1), vec![1, 1]).expect("input");
2304 let values =
2305 evaluate_sync(Value::Tensor(a), Value::Tensor(b), &[Value::from("stable")])
2306 .expect("union")
2307 .into_values_value();
2308 let Value::Tensor(values) = values else {
2309 panic!("exact values");
2310 };
2311 assert_eq!(values.integer_storage(), Some(&expected));
2312 }
2313 }
2314
2315 #[test]
2316 fn union_type_resolver_numeric() {
2317 assert_eq!(
2318 set_values_output_type(
2319 &[Type::tensor(), Type::tensor()],
2320 &ResolveContext::new(Vec::new()),
2321 ),
2322 Type::tensor()
2323 );
2324 }
2325
2326 #[test]
2327 fn union_type_resolver_string_array() {
2328 assert_eq!(
2329 set_values_output_type(
2330 &[Type::cell_of(Type::String), Type::String],
2331 &ResolveContext::new(Vec::new()),
2332 ),
2333 Type::cell_of(Type::String)
2334 );
2335 }
2336
2337 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2338 #[test]
2339 fn union_numeric_stable_order() {
2340 let a = Tensor::new(vec![5.0, 7.0, 1.0], vec![3, 1]).unwrap();
2341 let b = Tensor::new(vec![3.0, 2.0, 4.0], vec![3, 1]).unwrap();
2342 let eval = evaluate_sync(Value::Tensor(a), Value::Tensor(b), &[Value::from("stable")])
2343 .expect("union");
2344 match eval.values_value() {
2345 Value::Tensor(t) => {
2346 assert_eq!(t.materialize_f64(), vec![5.0, 7.0, 1.0, 3.0, 2.0, 4.0]);
2347 assert_eq!(t.shape, vec![6, 1]);
2348 }
2349 other => panic!("expected tensor result, got {other:?}"),
2350 }
2351 let ia = tensor::value_into_tensor_for("union", eval.ia_value()).expect("ia tensor");
2352 assert_eq!(ia.materialize_f64(), vec![1.0, 2.0, 3.0]);
2353 let ib = tensor::value_into_tensor_for("union", eval.ib_value()).expect("ib tensor");
2354 assert_eq!(ib.materialize_f64(), vec![1.0, 2.0, 3.0]);
2355 }
2356
2357 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2358 #[test]
2359 fn union_numeric_sorted_places_nan_last() {
2360 let a = Tensor::new(vec![f64::NAN, 1.0], vec![2, 1]).unwrap();
2361 let b = Tensor::new(vec![2.0, f64::NAN], vec![2, 1]).unwrap();
2362 let eval = evaluate_sync(Value::Tensor(a), Value::Tensor(b), &[]).expect("union");
2363 let values = tensor::value_into_tensor_for("union", eval.values_value()).expect("values");
2364 assert_eq!(values.shape, vec![3, 1]);
2365 assert_eq!(values.materialize_f64()[0], 1.0);
2366 assert_eq!(values.materialize_f64()[1], 2.0);
2367 assert!(values.materialize_f64()[2].is_nan());
2368 let ia = tensor::value_into_tensor_for("union", eval.ia_value()).expect("ia tensor");
2369 assert_eq!(ia.materialize_f64(), vec![2.0, 1.0]);
2370 let ib = tensor::value_into_tensor_for("union", eval.ib_value()).expect("ib tensor");
2371 assert_eq!(ib.materialize_f64(), vec![1.0]);
2372 }
2373
2374 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2375 #[test]
2376 fn union_numeric_rows_sorted() {
2377 let a = Tensor::new(vec![1.0, 3.0, 1.0, 2.0, 4.0, 2.0], vec![3, 2]).unwrap();
2378 let b = Tensor::new(vec![3.0, 5.0, 4.0, 6.0], vec![2, 2]).unwrap();
2379 let eval = evaluate_sync(Value::Tensor(a), Value::Tensor(b), &[Value::from("rows")])
2380 .expect("union");
2381 match eval.values_value() {
2382 Value::Tensor(t) => {
2383 assert_eq!(t.shape, vec![3, 2]);
2384 assert_eq!(t.materialize_f64(), vec![1.0, 3.0, 5.0, 2.0, 4.0, 6.0]);
2385 }
2386 other => panic!("expected tensor result, got {other:?}"),
2387 }
2388 let ia = tensor::value_into_tensor_for("union", eval.ia_value()).expect("ia tensor");
2389 assert_eq!(ia.materialize_f64(), vec![1.0, 2.0]);
2390 let ib = tensor::value_into_tensor_for("union", eval.ib_value()).expect("ib tensor");
2391 assert_eq!(ib.materialize_f64(), vec![2.0]);
2392 }
2393
2394 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2395 #[test]
2396 fn union_numeric_rows_stable_preserves_first_occurrence() {
2397 let a = Tensor::new(vec![1.0, 3.0, 1.0, 2.0, 4.0, 2.0], vec![3, 2]).unwrap();
2398 let b = Tensor::new(vec![3.0, 5.0, 1.0, 4.0, 6.0, 2.0], vec![3, 2]).unwrap();
2399 let eval = evaluate_sync(
2400 Value::Tensor(a),
2401 Value::Tensor(b),
2402 &[Value::from("rows"), Value::from("stable")],
2403 )
2404 .expect("union");
2405 let (values, ia, ib) = eval.into_triple();
2406 match values {
2407 Value::Tensor(t) => {
2408 assert_eq!(t.shape, vec![3, 2]);
2409 assert_eq!(t.materialize_f64(), vec![1.0, 3.0, 5.0, 2.0, 4.0, 6.0]);
2410 }
2411 other => panic!("expected tensor result, got {other:?}"),
2412 }
2413 let ia_tensor = tensor::value_into_tensor_for("union", ia).expect("ia tensor");
2414 assert_eq!(ia_tensor.materialize_f64(), vec![1.0, 2.0]);
2415 let ib_tensor = tensor::value_into_tensor_for("union", ib).expect("ib tensor");
2416 assert_eq!(ib_tensor.materialize_f64(), vec![2.0]);
2417 }
2418
2419 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2420 #[test]
2421 fn union_char_elements() {
2422 let a = CharArray::new(vec!['m', 'z', 'm', 'a'], 2, 2).unwrap();
2423 let b = CharArray::new(vec!['a', 'x', 'm', 'a'], 2, 2).unwrap();
2424 let eval = evaluate_sync(Value::CharArray(a), Value::CharArray(b), &[]).expect("union");
2425 match eval.values_value() {
2426 Value::CharArray(arr) => {
2427 assert_eq!(arr.rows, 4);
2428 assert_eq!(arr.cols, 1);
2429 assert_eq!(arr.data, vec!['a', 'm', 'x', 'z']);
2430 }
2431 other => panic!("expected char array, got {other:?}"),
2432 }
2433 let ia = tensor::value_into_tensor_for("union", eval.ia_value()).expect("ia tensor");
2434 assert_eq!(ia.materialize_f64(), vec![4.0, 1.0, 3.0]);
2435 let ib = tensor::value_into_tensor_for("union", eval.ib_value()).expect("ib tensor");
2436 assert_eq!(ib.materialize_f64(), vec![3.0]);
2437 }
2438
2439 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2440 #[test]
2441 fn union_string_rows_stable() {
2442 let a = StringArray::new(
2443 vec![
2444 "alpha".to_string(),
2445 "gamma".to_string(),
2446 "beta".to_string(),
2447 "beta".to_string(),
2448 ],
2449 vec![2, 2],
2450 )
2451 .unwrap();
2452 let b = StringArray::new(
2453 vec![
2454 "gamma".to_string(),
2455 "delta".to_string(),
2456 "beta".to_string(),
2457 "beta".to_string(),
2458 ],
2459 vec![2, 2],
2460 )
2461 .unwrap();
2462 let eval = evaluate_sync(
2463 Value::StringArray(a),
2464 Value::StringArray(b),
2465 &[Value::from("rows"), Value::from("stable")],
2466 )
2467 .expect("union");
2468 match eval.values_value() {
2469 Value::StringArray(arr) => {
2470 assert_eq!(arr.shape, vec![3, 2]);
2471 assert_eq!(
2472 arr.data,
2473 vec![
2474 "alpha".to_string(),
2475 "gamma".to_string(),
2476 "delta".to_string(),
2477 "beta".to_string(),
2478 "beta".to_string(),
2479 "beta".to_string()
2480 ]
2481 );
2482 }
2483 other => panic!("expected string array, got {other:?}"),
2484 }
2485 let ia = tensor::value_into_tensor_for("union", eval.ia_value()).expect("ia tensor");
2486 assert_eq!(ia.materialize_f64(), vec![1.0, 2.0]);
2487 let ib = tensor::value_into_tensor_for("union", eval.ib_value()).expect("ib tensor");
2488 assert_eq!(ib.materialize_f64(), vec![2.0]);
2489 }
2490
2491 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2492 #[test]
2493 fn union_gpu_roundtrip() {
2494 test_support::with_test_provider(|provider| {
2495 let a = Tensor::new(vec![4.0, 1.0, 2.0], vec![3, 1]).unwrap();
2496 let b = Tensor::new(vec![2.0, 5.0], vec![2, 1]).unwrap();
2497 let view_a = HostTensorView {
2498 data: &a.materialize_f64(),
2499 shape: &a.shape,
2500 };
2501 let view_b = HostTensorView {
2502 data: &b.materialize_f64(),
2503 shape: &b.shape,
2504 };
2505 let handle_a = provider.upload(&view_a).expect("upload A");
2506 let handle_b = provider.upload(&view_b).expect("upload B");
2507 let eval = evaluate_sync(
2508 Value::GpuTensor(handle_a),
2509 Value::GpuTensor(handle_b),
2510 &[Value::from("stable")],
2511 )
2512 .expect("union");
2513 let values = tensor::value_into_tensor_for("union", eval.values_value()).unwrap();
2514 assert_eq!(values.materialize_f64(), vec![4.0, 1.0, 2.0, 5.0]);
2515 let ia = tensor::value_into_tensor_for("union", eval.ia_value()).unwrap();
2516 assert_eq!(ia.materialize_f64(), vec![1.0, 2.0, 3.0]);
2517 let ib = tensor::value_into_tensor_for("union", eval.ib_value()).unwrap();
2518 assert_eq!(ib.materialize_f64(), vec![2.0]);
2519 });
2520 }
2521
2522 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2523 #[test]
2524 fn union_rejects_legacy_option() {
2525 let tensor =
2526 Tensor::new(vec![1.0, 2.0, 3.0], vec![3, 1]).expect("tensor construction failed");
2527 let err = evaluate_sync(
2528 Value::Tensor(tensor.clone()),
2529 Value::Tensor(tensor),
2530 &[Value::from("legacy")],
2531 )
2532 .unwrap_err();
2533 assert_eq!(
2534 err.identifier(),
2535 UNION_ERROR_LEGACY_OPTION_UNSUPPORTED.identifier
2536 );
2537 }
2538
2539 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2540 #[test]
2541 fn union_rejects_conflicting_order_options() {
2542 let tensor = Tensor::new(vec![1.0, 2.0], vec![2, 1]).expect("tensor construction failed");
2543 let err = evaluate_sync(
2544 Value::Tensor(tensor.clone()),
2545 Value::Tensor(tensor),
2546 &[Value::from("stable"), Value::from("sorted")],
2547 )
2548 .unwrap_err();
2549 assert_eq!(
2550 err.identifier(),
2551 UNION_ERROR_CONFLICTING_ORDER_OPTIONS.identifier
2552 );
2553 }
2554
2555 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2556 #[test]
2557 fn union_rejects_unknown_option() {
2558 let tensor = Tensor::new(vec![1.0, 2.0], vec![2, 1]).expect("tensor construction failed");
2559 let err = evaluate_sync(
2560 Value::Tensor(tensor.clone()),
2561 Value::Tensor(tensor),
2562 &[Value::from("bogus")],
2563 )
2564 .unwrap_err();
2565 assert_eq!(err.identifier(), UNION_ERROR_UNKNOWN_OPTION.identifier);
2566 }
2567
2568 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2569 #[test]
2570 fn union_rows_dimension_mismatch() {
2571 let a = Tensor::new(vec![1.0, 2.0, 3.0, 4.0], vec![2, 2]).unwrap();
2572 let b = Tensor::new(vec![1.0, 2.0, 3.0], vec![3, 1]).unwrap();
2573 let err =
2574 evaluate_sync(Value::Tensor(a), Value::Tensor(b), &[Value::from("rows")]).unwrap_err();
2575 assert_eq!(
2576 err.identifier(),
2577 UNION_ERROR_ROWS_COLUMN_MISMATCH.identifier
2578 );
2579 }
2580
2581 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2582 #[test]
2583 fn union_requires_matching_types() {
2584 let a = Tensor::new(vec![1.0, 2.0], vec![2, 1]).unwrap();
2585 let b = CharArray::new(vec!['a', 'b'], 1, 2).unwrap();
2586 let err = union_host(
2587 Value::Tensor(a),
2588 Value::CharArray(b),
2589 &UnionOptions {
2590 rows: false,
2591 order: UnionOrder::Sorted,
2592 },
2593 )
2594 .unwrap_err();
2595 assert_eq!(
2596 err.identifier(),
2597 UNION_ERROR_UNSUPPORTED_INPUT_TYPE.identifier
2598 );
2599 }
2600
2601 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2602 #[test]
2603 fn union_accepts_scalar_inputs() {
2604 let eval =
2605 evaluate_sync(Value::Int(IntValue::I32(1)), Value::Num(3.0), &[]).expect("union");
2606 match eval.values_value() {
2607 Value::Tensor(t) => {
2608 assert_eq!(t.materialize_f64(), vec![1.0, 3.0]);
2609 assert_eq!(t.shape, vec![2, 1]);
2610 }
2611 other => panic!("expected numeric tensor, got {other:?}"),
2612 }
2613 let ia = tensor::value_into_tensor_for("union", eval.ia_value()).unwrap();
2614 assert_eq!(ia.materialize_f64(), vec![1.0]);
2615 let ib = tensor::value_into_tensor_for("union", eval.ib_value()).unwrap();
2616 assert_eq!(ib.materialize_f64(), vec![1.0]);
2617 }
2618
2619 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2620 #[test]
2621 #[cfg(feature = "wgpu")]
2622 fn union_wgpu_matches_cpu() {
2623 let _ = runmat_accelerate::backend::wgpu::provider::register_wgpu_provider(
2624 runmat_accelerate::backend::wgpu::provider::WgpuProviderOptions::default(),
2625 );
2626 let a = Tensor::new(vec![4.0, 1.0, 2.0, 3.0], vec![4, 1]).unwrap();
2627 let b = Tensor::new(vec![2.0, 6.0, 3.0], vec![3, 1]).unwrap();
2628
2629 let cpu_eval =
2630 evaluate_sync(Value::Tensor(a.clone()), Value::Tensor(b.clone()), &[]).expect("union");
2631 let cpu_values = tensor::value_into_tensor_for("union", cpu_eval.values_value()).unwrap();
2632 let cpu_ia = tensor::value_into_tensor_for("union", cpu_eval.ia_value()).unwrap();
2633 let cpu_ib = tensor::value_into_tensor_for("union", cpu_eval.ib_value()).unwrap();
2634
2635 let provider = runmat_accelerate_api::provider().expect("provider");
2636 let view_a = HostTensorView {
2637 data: &a.materialize_f64(),
2638 shape: &a.shape,
2639 };
2640 let view_b = HostTensorView {
2641 data: &b.materialize_f64(),
2642 shape: &b.shape,
2643 };
2644 let handle_a = provider.upload(&view_a).expect("upload A");
2645 let handle_b = provider.upload(&view_b).expect("upload B");
2646 let gpu_eval = evaluate_sync(Value::GpuTensor(handle_a), Value::GpuTensor(handle_b), &[])
2647 .expect("union");
2648 let gpu_values = tensor::value_into_tensor_for("union", gpu_eval.values_value()).unwrap();
2649 let gpu_ia = tensor::value_into_tensor_for("union", gpu_eval.ia_value()).unwrap();
2650 let gpu_ib = tensor::value_into_tensor_for("union", gpu_eval.ib_value()).unwrap();
2651
2652 assert_eq!(gpu_values.materialize_f64(), cpu_values.materialize_f64());
2653 assert_eq!(gpu_values.shape, cpu_values.shape);
2654 assert_eq!(gpu_ia.materialize_f64(), cpu_ia.materialize_f64());
2655 assert_eq!(gpu_ib.materialize_f64(), cpu_ib.materialize_f64());
2656 }
2657}