| 22 | |
| 23 | namespace { |
| 24 | void TestSummaryInvariants(SummaryCase const& c, WQSummaryContainer const& summary, |
| 25 | GeneratedColumn const& col) { |
| 26 | auto entries = summary.Entries(); |
| 27 | auto ref = AggregateReferenceColumn(col); |
| 28 | auto nonzero_samples = NonZeroWeightCount(col); |
| 29 | auto budget = SketchSummaryBudget(c.max_bin, c.rows); |
| 30 | // An empty sketch should remain empty after finalization. |
| 31 | if (EmptyReference(ref)) { |
| 32 | ASSERT_TRUE(entries.empty()) << "case=" << c.name; |
| 33 | return; |
| 34 | } |
| 35 | |
| 36 | // A numerical summary should remain a strictly increasing support set. |
| 37 | ASSERT_FALSE(entries.empty()) << "case=" << c.name; |
| 38 | for (std::size_t i = 1; i < entries.size(); ++i) { |
| 39 | EXPECT_LT(entries[i - 1].value, entries[i].value) << "case=" << c.name; |
| 40 | } |
| 41 | |
| 42 | // Large-n anchors should exercise actual compression rather than exact retention. |
| 43 | if (c.rows > static_cast<std::size_t>(c.max_bin) * 8) { |
| 44 | ASSERT_LT(summary.Size(), nonzero_samples) |
| 45 | << "case=" << c.name << " should exercise sketch compression."; |
| 46 | } |
| 47 | |
| 48 | // The summary query rule should satisfy the target rank bound plus the final prune term. |
| 49 | auto total = TotalWeight(ref); |
| 50 | auto max_error = MaxSummaryQueryRankError(summary, ref, c.max_bin); |
| 51 | auto eps = SketchEpsilon(c.max_bin, c.rows); |
| 52 | auto bound = (eps + 1.0 / static_cast<double>(budget)) * total; |
| 53 | |
| 54 | EXPECT_LE(max_error, bound) << "case=" << c.name << ", total=" << total << ", budget=" << budget |
| 55 | << ", eps=" << eps; |
| 56 | |
| 57 | // If the target bin count can already represent all distinct values, the summary should |
| 58 | // preserve the exact support instead of approximating it. |
| 59 | if (UniqueValueCount(ref) <= static_cast<std::size_t>(c.max_bin)) { |
| 60 | auto exact_values = ExactValues(ref); |
| 61 | ASSERT_EQ(entries.size(), exact_values.size()) << "case=" << c.name; |
| 62 | for (std::size_t i = 0; i < exact_values.size(); ++i) { |
| 63 | EXPECT_FLOAT_EQ(entries[i].value, exact_values[i]) << "case=" << c.name; |
| 64 | } |
| 65 | } |
| 66 | } |
| 67 | void AssertSameOnAllWorkers(Context const* ctx, HistogramCuts const& cuts) { |
| 68 | auto const world = collective::GetWorldSize(); |
| 69 | if (world <= 1) { |
no test coverage detected