feat(req): port of reqsketch-rs - #204
Conversation
tisonkun
left a comment
There was a problem hiding this comment.
Thanks for your contribution @pmcgleenon! I'll review this patch in this week.
| min_pre_longs: 2, | ||
| max_pre_longs: 4, |
There was a problem hiding this comment.
From datasketches-java, these fields are:
REQ(17, "REQ", 1 /* minPreLongs */, 2 /* maxPreLongs */),But we don't use these fields for REQSketch anyway.
There was a problem hiding this comment.
This should put under serde_test and need not to repeat serialization_test_data helper.
| [dependencies] | ||
| rand = { workspace = true, optional = true } |
There was a problem hiding this comment.
This can be relevant to #203.
We may use rand as a starting point and conclude before the next release.
| rand = { workspace = true, optional = true } | ||
|
|
||
| [dev-dependencies] | ||
| approx = { workspace = true } |
There was a problem hiding this comment.
googletest provides:
- https://docs.rs/googletest/latest/googletest/matchers/fn.near.html
- https://docs.rs/googletest/latest/googletest/matchers/fn.approx_eq.html
.. and we may need one more test lib. But you can leave it to me to do a global alignment.
| /// | ||
| /// NaN inputs are silently ignored for floating-point types, matching the behavior | ||
| /// of the Java reference implementation (`checkNaNUpdate`). This is intentional and | ||
| /// documented in the cross-language differences doc. |
There was a problem hiding this comment.
in the cross-language differences doc.
Seems not included in this PR. We may rewrite this doc comment a bit.
| /// Sets the `k` parameter. | ||
| /// | ||
| /// # Errors | ||
| /// | ||
| /// Returns an error if `k` is odd or outside `[MIN_K, MAX_K]`. | ||
| pub fn k(mut self, k: u16) -> Result<Self, Error> { | ||
| if !(MIN_K..=MAX_K).contains(&k) { | ||
| return Err(Error::invalid_argument(format!( | ||
| "k must be in [{}, {}], got {k}", | ||
| MIN_K, MAX_K | ||
| ))); | ||
| } | ||
| if k % 2 != 0 { | ||
| return Err(Error::invalid_argument(format!("k must be even, got {k}"))); | ||
| } | ||
| self.k = k; | ||
| Ok(self) | ||
| } |
There was a problem hiding this comment.
Other builders use a panic flavor on setters.
I may prefer to follow the same flavor and propose a new flavor if desired later.
Besides, once we check k here, we may not need to call ReqSketch::try_new on build which check the same conditions.
And we can have a ReqSketch::new as the panic version for try_new. The current no-param new can be inlined to default and only default.
| pub fn rank_accuracy(mut self, ra: RankAccuracy) -> Self { | ||
| self.rank_accuracy = ra; | ||
| self | ||
| } |
There was a problem hiding this comment.
| pub fn rank_accuracy(mut self, ra: RankAccuracy) -> Self { | |
| self.rank_accuracy = ra; | |
| self | |
| } | |
| pub fn rank_accuracy(mut self, rank_accuracy: RankAccuracy) -> Self { | |
| self.rank_accuracy = rank_accuracy; | |
| self | |
| } |
nit: public API would prefer explicit over abbr.
| impl<T: ReqValue + fmt::Display> fmt::Display for ReqSketch<T> { | ||
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| writeln!(f, "REQ Sketch Summary:")?; | ||
| writeln!(f, " k : {}", self.k)?; | ||
| writeln!(f, " rank accuracy : {:?}", self.rank_accuracy)?; | ||
| writeln!(f, " n : {}", self.n)?; | ||
| writeln!(f, " num retained : {}", self.num_retained)?; | ||
| writeln!(f, " num levels : {}", self.compactors.len())?; | ||
| writeln!(f, " estimation mode : {}", self.is_estimation_mode())?; | ||
| if let (Some(min), Some(max)) = (&self.min_item, &self.max_item) { | ||
| writeln!(f, " min item : {min}")?; | ||
| writeln!(f, " max item : {max}")?; | ||
| } | ||
| Ok(()) | ||
| } | ||
| } |
There was a problem hiding this comment.
Good point. I wonder if we'd have a tracking issue to impl fmt::Display for all sketches when applicible. This is what datasketches-java does IMO, but the format can be considered once more.
| /// Internally wraps a `ReqSketch` configured for union semantics. The C++ | ||
| /// equivalent is `req_union<T>`. |
There was a problem hiding this comment.
I see no req_union<T> in datasketches-cpp.
There was a problem hiding this comment.
cc @AlexanderSaydakov @leerho You can review the ReqUnion design here and give some high-level design comments.
| /// Creates a new union with default `k = 12` and `RankAccuracy::HighRank`. | ||
| pub fn new() -> Self { | ||
| Self { | ||
| inner: ReqSketch::new(), | ||
| } | ||
| } | ||
|
|
||
| /// Creates a new union with the given `k` and rank accuracy. | ||
| /// | ||
| /// # Errors | ||
| /// | ||
| /// Returns an error if `k` is invalid (see [`ReqSketch::try_new`]). | ||
| pub fn try_new(k: u16, rank_accuracy: RankAccuracy) -> Result<Self, Error> { | ||
| Ok(Self { | ||
| inner: ReqSketch::try_new(k, rank_accuracy)?, | ||
| }) | ||
| } |
There was a problem hiding this comment.
Similarly, I'd prefer new to be a panic version of try_new and leave the no-param new as default.
|
Glanced at the surface and commented above. Below is a review comment from Codex. I'll dive into implementation details later this week. And I think it's OK to merge a starting implementation and iterate later. But let's discuss existing items first to decide when to converge. [CODEX COMMENT STARTS]
In However, the REQ wire format stores a sorted flag only for level 0. Higher levels are implicitly required to be sorted, and I reproduced this with two HRA sketches using This also means a merged image produced by Rust may be interpreted incorrectly by Java or C++. Both reference implementations preserve ordering during compactor merge by sorting and performing an ordered merge. I suggest doing the same here, including validating that the two compactors have the same Please also add a regression test that:
The current validation checks Two concrete examples:
A malformed serialized image should not be able to create a sketch for which safe public operations panic or return impossible results. Before constructing the sketch, I suggest validating at least:
The relevant arithmetic should also use checked operations. Since these failures originate from malformed serialized input, they should return Once these two issues are addressed, the existing algorithm and cross-language compatibility coverage will be on much firmer ground. |
This is a port of reqsketch-rs as discussed here #90. This implementation is influenced heavily by the apache datasketches C++ implementation.
Some things present in reqsketch-rs that are not part of this PR:
@tisonkun FYI if you get a chance please take a look