use apache_datasketches::tuple::{
ArrayOfDoublesSketch, ArrayOfDoublesSketchBuilder, ArrayOfDoublesUnionBuilder,
};
fn sketch(num_values: u8, keys: std::ops::Range<u64>) -> ArrayOfDoublesSketch {
let mut s = ArrayOfDoublesSketchBuilder::new()
.num_values(num_values)
.build()
.unwrap();
let values: Vec<f64> = (0..num_values).map(|i| (i + 1) as f64).collect();
for key in keys {
s.update_u64(key, &values).unwrap();
}
s
}
#[test]
fn union_of_empty_sketches_is_empty() {
let a = ArrayOfDoublesSketchBuilder::new().build().unwrap();
let b = ArrayOfDoublesSketchBuilder::new().build().unwrap();
let mut union_ = ArrayOfDoublesUnionBuilder::new().build().unwrap();
union_.update(&a).unwrap();
union_.update(&b).unwrap();
let result = union_.get_result(true);
assert!(result.is_empty());
assert_eq!(result.get_num_retained(), 0);
}
#[test]
fn union_with_one_empty_one_nonempty() {
let a = ArrayOfDoublesSketchBuilder::new().build().unwrap();
let b = sketch(1, 0..1);
let mut union_ = ArrayOfDoublesUnionBuilder::new().build().unwrap();
union_.update(&a).unwrap();
union_.update(&b).unwrap();
let result = union_.get_result(true);
assert_eq!(result.get_estimate(), 1.0);
assert!(!result.is_empty());
}
#[test]
fn union_exact_mode_no_overlap() {
let a = sketch(1, 0..5);
let b = sketch(1, 5..10);
let mut union_ = ArrayOfDoublesUnionBuilder::new().build().unwrap();
union_.update(&a).unwrap();
union_.update(&b).unwrap();
let result = union_.get_result(true);
assert_eq!(result.get_estimate(), 10.0);
assert!(!result.is_estimation_mode());
assert_eq!(result.get_num_retained(), 10);
}
#[test]
fn union_reset_clears_state() {
let a = sketch(1, 0..1);
let mut union_ = ArrayOfDoublesUnionBuilder::new().build().unwrap();
union_.update(&a).unwrap();
assert!(!union_.get_result(true).is_empty());
assert!(union_.get_result(true).get_num_retained() > 0);
union_.reset();
let result = union_.get_result(true);
assert!(result.is_empty());
assert_eq!(result.get_num_retained(), 0);
}
#[test]
fn union_estimation_mode_large_overlapping_sets_within_tolerance() {
let a = sketch(1, 0..10_000);
let b = sketch(1, 5_000..15_000);
let mut union_ = ArrayOfDoublesUnionBuilder::new().build().unwrap();
union_.update(&a).unwrap();
union_.update(&b).unwrap();
let result = union_.get_result(true);
assert!(!result.is_empty());
assert!(result.is_estimation_mode());
assert!((result.get_estimate() - 15_000.0).abs() < 15_000.0 * 0.01);
union_.reset();
let after_reset = union_.get_result(true);
assert_eq!(after_reset.get_num_retained(), 0);
assert!(after_reset.is_empty());
assert!(!after_reset.is_estimation_mode());
}
#[test]
fn union_uses_builders_own_lg_k() {
let mut sketch1 = ArrayOfDoublesSketchBuilder::new().lg_k(14).build().unwrap();
for i in 0..16_384u64 {
sketch1.update_u64(i, &[1.0]).unwrap();
}
let mut sketch2 = ArrayOfDoublesSketchBuilder::new().lg_k(14).build().unwrap();
for i in 0..26_384u64 {
sketch2.update_u64(i, &[1.0]).unwrap();
}
let mut sketch3 = ArrayOfDoublesSketchBuilder::new().lg_k(14).build().unwrap();
for i in 0..86_384u64 {
sketch3.update_u64(i, &[1.0]).unwrap();
}
let mut union1 = ArrayOfDoublesUnionBuilder::new().lg_k(16).build().unwrap();
union1.update(&sketch2).unwrap();
union1.update(&sketch1).unwrap();
union1.update(&sketch3).unwrap();
let result1 = union1.get_result(true);
assert_eq!(result1.get_estimate(), sketch3.get_estimate());
let mut union2 = ArrayOfDoublesUnionBuilder::new().lg_k(16).build().unwrap();
union2.update(&sketch1).unwrap();
union2.update(&sketch3).unwrap();
union2.update(&sketch2).unwrap();
let result2 = union2.get_result(true);
assert_eq!(result2.get_estimate(), sketch3.get_estimate());
}
#[test]
fn union_builder_rejects_lg_k_out_of_range() {
assert!(ArrayOfDoublesUnionBuilder::new().lg_k(4).build().is_err());
assert!(ArrayOfDoublesUnionBuilder::new().lg_k(27).build().is_err());
assert!(ArrayOfDoublesUnionBuilder::new().lg_k(5).build().is_ok());
assert!(ArrayOfDoublesUnionBuilder::new().lg_k(26).build().is_ok());
}
#[test]
fn union_estimation_mode_value_correctness() {
let mut a = ArrayOfDoublesSketchBuilder::new()
.num_values(2)
.build()
.unwrap();
for i in 0..20_000u64 {
a.update_u64(i, &[1.0, 10.0]).unwrap();
}
let mut b = ArrayOfDoublesSketchBuilder::new()
.num_values(2)
.build()
.unwrap();
for i in 10_000..30_000u64 {
b.update_u64(i, &[1.0, 10.0]).unwrap();
}
let mut union_ = ArrayOfDoublesUnionBuilder::new()
.num_values(2)
.build()
.unwrap();
union_.update(&a).unwrap();
union_.update(&b).unwrap();
let result = union_.get_result(true);
assert!(!result.is_empty());
assert!(result.is_estimation_mode());
assert_eq!(result.get_num_values(), 2);
assert!((result.get_estimate() - 30_000.0).abs() < 30_000.0 * 0.02);
let mut saw_single = false;
let mut saw_summed = false;
for (_, values) in result.entries() {
if values == [1.0, 10.0] {
saw_single = true;
} else if values == [2.0, 20.0] {
saw_summed = true;
} else {
panic!("unexpected surviving entry value: {values:?}");
}
}
assert!(
saw_single,
"expected at least one non-overlapping key to survive"
);
assert!(
saw_summed,
"expected at least one overlapping key to survive"
);
}
#[test]
fn union_get_result_unordered_matches_ordered() {
let a = sketch(1, 0..10_000);
let b = sketch(1, 5_000..15_000);
let mut union_ = ArrayOfDoublesUnionBuilder::new().build().unwrap();
union_.update(&a).unwrap();
union_.update(&b).unwrap();
let ordered = union_.get_result(true);
let unordered = union_.get_result(false);
assert!(ordered.is_estimation_mode());
assert_eq!(unordered.get_num_retained(), ordered.get_num_retained());
assert_eq!(unordered.get_estimate(), ordered.get_estimate());
assert!(ordered.is_ordered());
assert!(!unordered.is_ordered());
}
#[test]
fn union_get_num_values_preserved_in_estimation_mode() {
let mut a = ArrayOfDoublesSketchBuilder::new()
.num_values(3)
.build()
.unwrap();
for i in 0..10_000u64 {
a.update_u64(i, &[1.0, 2.0, 3.0]).unwrap();
}
let mut union_ = ArrayOfDoublesUnionBuilder::new()
.num_values(3)
.build()
.unwrap();
union_.update(&a).unwrap();
let result = union_.get_result(true);
assert!(result.is_estimation_mode());
assert_eq!(result.get_num_values(), 3);
}