use rmp_serde::{decode::Error as RmpDecodeError, encode::Error as RmpEncodeError, from_slice};
use crate::Vector1D;
use crate::message_pack_format::envelope;
use crate::sketches::kll::{
Coin, KLL_KIND_DYNAMIC, KllCoinWire, KllPayload, KllWireItem, kll_metadata,
split_and_validate_meta, validate_kll_payload,
};
use super::{CAPACITY_CACHE_LEN, KLLDynamic};
impl<T> KLLDynamic<T>
where
T: crate::common::numerical::NumericalValue
+ KllWireItem
+ serde::Serialize
+ for<'de> serde::Deserialize<'de>,
{
pub fn serialize_to_bytes(&self) -> Result<Vec<u8>, RmpEncodeError> {
let metadata =
rmp_serde::to_vec_named(&kll_metadata::<T>(self.k as u32, self.m as u32, None))?;
let (state, bit_cache, remaining_bits) = self.co.to_wire();
let payload = rmp_serde::to_vec(&KllPayload {
levels: self.levels.as_slice().iter().map(|&l| l as u32).collect(),
items: self.items.as_slice().to_vec(),
coin: KllCoinWire {
state,
bit_cache,
remaining_bits,
},
})?;
Ok(envelope::encode(KLL_KIND_DYNAMIC, &metadata, &payload))
}
pub fn deserialize_from_bytes(bytes: &[u8]) -> Result<Self, RmpDecodeError> {
let (meta, payload_bytes) = split_and_validate_meta::<T>(bytes, KLL_KIND_DYNAMIC)?;
let payload: KllPayload<T> = from_slice(payload_bytes)?;
let num_levels = validate_kll_payload(&payload.levels, &payload.items, &payload.coin)?;
let KllPayload {
levels,
items,
coin,
} = payload;
let mut sketch = KLLDynamic {
items: Vector1D::from_vec(items),
levels: Vector1D::from_vec(levels.into_iter().map(|l| l as usize).collect()),
k: meta.k as usize,
m: meta.m as usize,
num_levels,
co: Coin::from_wire(coin.state, coin.bit_cache, coin.remaining_bits as u8),
seed: None,
capacity_cache: [0; CAPACITY_CACHE_LEN],
top_height: 0,
level0_capacity: 0,
};
sketch.rebuild_capacity_cache();
Ok(sketch)
}
}
#[cfg(test)]
mod tests {
use crate::message_pack_format::envelope;
use crate::sketches::kll::KLL_KIND_DYNAMIC;
use crate::sketches::kll_dynamic::KLLDynamic;
fn build_dynamic(k: i32, n: u64) -> KLLDynamic<f64> {
let mut sketch = KLLDynamic::<f64>::init_kll(k);
for v in 1..=n {
sketch.update(&(v as f64));
}
sketch
}
#[test]
fn kll_dynamic_envelope_structure_and_round_trip() {
let sketch = build_dynamic(200, 200_000);
let bytes = sketch.serialize_to_bytes().expect("serialize");
assert!(bytes.starts_with(envelope::MAGIC));
assert_eq!(bytes[6], envelope::VERSION);
assert_eq!(bytes[7], 2, "kind_id_len");
assert_eq!(&bytes[8..10], KLL_KIND_DYNAMIC);
let decoded = KLLDynamic::<f64>::deserialize_from_bytes(&bytes).expect("decode");
assert_eq!(
decoded.serialize_to_bytes().expect("re-serialize"),
bytes,
"KLLDynamic serialized bytes differed after round trip"
);
for &q in &[0.0, 0.01, 0.25, 0.5, 0.75, 0.99, 1.0] {
assert_eq!(
decoded.quantile(q),
sketch.quantile(q),
"quantile mismatch at q={q} after round trip"
);
}
}
#[test]
fn kll_dynamic_empty_round_trip() {
let sketch = KLLDynamic::<f64>::init_kll(200);
let bytes = sketch.serialize_to_bytes().expect("serialize");
let decoded = KLLDynamic::<f64>::deserialize_from_bytes(&bytes).expect("decode");
assert_eq!(decoded.serialize_to_bytes().expect("re-serialize"), bytes);
}
#[test]
fn kll_dynamic_i64_round_trip() {
let mut sketch = KLLDynamic::<i64>::init_kll(200);
for v in 1..=50_000i64 {
sketch.update(&v);
}
let bytes = sketch.serialize_to_bytes().expect("serialize");
assert_eq!(&bytes[8..10], KLL_KIND_DYNAMIC);
let decoded = KLLDynamic::<i64>::deserialize_from_bytes(&bytes).expect("decode");
assert_eq!(decoded.serialize_to_bytes().expect("re-serialize"), bytes);
}
#[test]
fn kll_dynamic_item_type_cross_rejection() {
let sketch = build_dynamic(200, 1000);
let bytes = sketch.serialize_to_bytes().expect("serialize");
assert!(
KLLDynamic::<i64>::deserialize_from_bytes(&bytes).is_err(),
"f64 KLLDynamic bytes must be rejected by an i64 decoder"
);
}
}