Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions crates/logical-optimizer/src/accuracy/estimators/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,4 +253,7 @@ impl AccuracyModel for EstimatorAccuracy<'_> {
fn satisfies(&self, guarantee: &ResultGuarantee, target: &AccuracyTarget) -> bool {
self.base.satisfies(guarantee, target)
}
fn answers(&self, statistic: &SketchStatistic, guarantee: &ResultGuarantee) -> bool {
self.base.answers(statistic, guarantee)
}
}
48 changes: 48 additions & 0 deletions crates/logical-optimizer/src/accuracy/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,34 @@ pub trait AccuracyModel {
/// Compare the dimensions requested by `target`. Unknown required
/// dimensions fail; selection separately excludes missing accuracy evidence.
fn satisfies(&self, guarantee: &ResultGuarantee, target: &AccuracyTarget) -> bool;

/// Whether `guarantee` bounds the error a target on `statistic` is
/// stated in, so that [`Self::satisfies`] compares like with like. An
/// exact guarantee answers every statistic; otherwise the metric must be
/// one the statistic's ε is measured in. A deployment with a registered
/// cross-metric conversion overrides this.
fn answers(&self, statistic: &SketchStatistic, guarantee: &ResultGuarantee) -> bool {
use ErrorMetric::*;
guarantee.is_exact()
|| match statistic {
SketchStatistic::Quantile { .. } => {
matches!(guarantee.metric, Rank | RelativeValue)
}
SketchStatistic::Cardinality => {
matches!(guarantee.metric, Cardinality | RelativeValue)
}
SketchStatistic::FrequencyL2 | SketchStatistic::FrequencyEntropy => {
guarantee.metric == RelativeValue
}
SketchStatistic::PointCount { .. } => {
matches!(guarantee.metric, Frequency | L2Frequency)
}
// A top-k target bounds the item scores, as Pass 1 checks.
SketchStatistic::TopK { .. } => {
matches!(guarantee.metric, Frequency | L2Frequency | TopKMembership)
}
}
}
}

/// The built-in estimator and composition models, with conservative target checks.
Expand Down Expand Up @@ -152,4 +180,24 @@ mod tests {
DefaultAccuracyModel.satisfies(&ResultGuarantee::exact("x"), &AccuracyTarget::Exact)
);
}

/// A bound in another statistic's metric does not answer a target:
/// Count-Min's L1 frequency bound says nothing about a distinct count.
#[test]
fn answers_requires_the_statistics_metric() {
let frequency = ResultGuarantee {
metric: ErrorMetric::Frequency,
..abs(0.01, 0.01)
};
let count = SketchStatistic::PointCount {
key: asap_types::ir::scalar::ColumnRef::SampleValue,
value: None,
};
assert!(DefaultAccuracyModel.answers(&count, &frequency));
assert!(!DefaultAccuracyModel.answers(&SketchStatistic::Cardinality, &frequency));
assert!(!DefaultAccuracyModel.answers(&SketchStatistic::Quantile { q: 0.5 }, &frequency));
assert!(!DefaultAccuracyModel.answers(&SketchStatistic::FrequencyL2, &abs(0.01, 0.01)));
assert!(DefaultAccuracyModel
.answers(&SketchStatistic::Cardinality, &ResultGuarantee::exact("x")));
}
}
33 changes: 33 additions & 0 deletions crates/plan-selection/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1275,6 +1275,12 @@ fn build_violation(
let Some(guarantee) = models.accuracy.local_guarantee(family, statistic) else {
return Some(format!("no accuracy model for {name}; target {target:?}"));
};
if !models.accuracy.answers(statistic, &guarantee) {
return Some(format!(
"{name} guarantees a {:?} bound, which does not bound {statistic:?}; target {target:?}",
guarantee.metric
));
}
if !models.accuracy.satisfies(&guarantee, target) {
return Some(format!(
"{name} guarantees bound {:?}, failure probability {:?}, which misses target \
Expand Down Expand Up @@ -2520,6 +2526,33 @@ mod tests {
}
}

/// A guarantee in another statistic's metric does not satisfy a target:
/// CountSketch's L2 frequency bound answers the top-k scores it was
/// built for, but not a distinct count, however loose the target.
#[test]
fn a_bound_in_another_metric_misses_the_target() {
let candidates = candidates();
let build = OperatorNode::reachable(&candidates[2].roots[0])
.into_iter()
.find(|node| {
matches!(&node.operator, Operator::ASAP(ASAPOp::SummaryAgg { family: FieldDataType::Sketch(kind, _), .. })
if *kind.algorithm() == SketchAlgorithm::CountSketchWithHeap)
})
.expect("a CountSketch build");
let loose = AccuracyTarget::EpsilonDelta {
epsilon: 0.5,
delta: 0.5,
};
let models = PlanningModels::builtin();
assert_eq!(
build_violation(&build, &SketchStatistic::TopK { k: 10 }, &loose, &models),
None
);
let reason = build_violation(&build, &SketchStatistic::Cardinality, &loose, &models)
.expect("an L2 frequency bound does not bound a distinct count");
assert!(reason.contains("does not bound Cardinality"), "{reason}");
}

/// A candidate that cannot be checked is rejected with its reason; the
/// others are still selected among.
#[test]
Expand Down