Skip to content
Merged
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion crates/accuracy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,9 @@ asap-types = { path = "../types" }
serde_json = "1"

[features]
# Exposes `test_support`: propagation helpers other crates' unit tests call.
# Exposes `test_support`: estimator, sizing, composition and propagation
# helpers other crates' tests call.
test-support = []

[dev-dependencies]
asap-types = { path = "../types", features = ["test-support"] }
20 changes: 10 additions & 10 deletions crates/accuracy/src/composition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub const INPUT_ROW_COUNT: &str = "input_row_count";
/// Rows one group of a SQL fold over an approximate relation folds: not
/// bounded by the series count of the sources it reads, so plan selection
/// leaves it unbound.
pub const GROUP_ROW_COUNT: &str = "group_row_count";
pub(crate) const GROUP_ROW_COUNT: &str = "group_row_count";
/// Whether every input value is non-negative.
pub(crate) const VALUES_NON_NEGATIVE: &str = "values_non_negative";
/// The absolute bound of a product of two estimates,
Expand All @@ -19,29 +19,29 @@ pub const PRODUCT_OPERAND_DOMAINS: &str = "product_operand_domains";
/// The absolute bound of a quotient with an approximate denominator,
/// `(|x|·e_y + |y|·e_x) / (|y|·(|y| − e_y))`, given enforced operand domains
/// with the denominator's bounded away from zero.
pub const QUOTIENT_OPERAND_DOMAINS: &str = "quotient_operand_domains";
pub(crate) const QUOTIENT_OPERAND_DOMAINS: &str = "quotient_operand_domains";
/// `‖f‖₁` of the stream a Count-Min bound is normalized by.
pub const STREAM_L1_NORM: &str = "stream_l1_norm";
/// `‖f‖₂` of the stream a Count Sketch bound is normalized by.
pub(crate) const STREAM_L2_NORM: &str = "stream_l2_norm";
/// Rows of one source's stream a summary folds: with a constant update
/// weight `c`, `‖f‖₁ = |c| · rows` and `‖f‖₂ ≤ ‖f‖₁`.
pub const STREAM_ROW_COUNT: &str = "stream_row_count";
pub(crate) const STREAM_ROW_COUNT: &str = "stream_row_count";

/// Which stream norm bounds a Count-Min or Count Sketch estimate, given its
/// summary's update weight.
#[derive(Debug, Clone, PartialEq)]
pub enum StreamNorm {
/// The weight is the source's raw value column: the source's norm
/// ([`source_norm_statistic`]).
/// (`source_norm_statistic`).
Source(String),
/// A constant weight (a count): `|weight|` times the source's row count.
ConstantWeight { weight: f64, source: String },
}

/// The statistic for `norm` ([`STREAM_L1_NORM`] or [`STREAM_L2_NORM`]) of
/// one source's stream, e.g. `stream_l1_norm[m]`.
pub fn source_norm_statistic(norm: &str, source: &str) -> String {
pub(crate) fn source_norm_statistic(norm: &str, source: &str) -> String {
format!("{norm}[{source}]")
}

Expand All @@ -50,19 +50,19 @@ pub fn source_norm_statistic(norm: &str, source: &str) -> String {
/// `values_non_negative[m]`: an indicator, 1 when proven. Plan selection
/// binds it to 1 when the data workload declares `metric` a counter or
/// non-negative, and leaves it unbound otherwise.
pub fn raw_data_samples_sign_statistic(metric: &str) -> String {
pub(crate) fn raw_data_samples_sign_statistic(metric: &str) -> String {
format!("{VALUES_NON_NEGATIVE}[{metric}]")
}

/// The metric a [`raw_data_samples_sign_statistic`] names.
/// The metric a `raw_data_samples_sign_statistic` names.
pub fn parse_raw_data_samples_sign_statistic(statistic: &str) -> Option<&str> {
statistic
.strip_prefix(VALUES_NON_NEGATIVE)?
.strip_prefix('[')?
.strip_suffix(']')
}

/// The norm and source a [`source_norm_statistic`] names.
/// The norm and source a `source_norm_statistic` names.
pub fn parse_source_norm_statistic(statistic: &str) -> Option<(&'static str, &str)> {
[STREAM_L1_NORM, STREAM_L2_NORM]
.into_iter()
Expand Down Expand Up @@ -523,7 +523,7 @@ fn row_count(stats: &PropagationStats, provenance: &mut Vec<GuaranteeSource>) ->
/// plan selection can bind; without one (a weight computed from the
/// values) it is a statistic nothing binds. Any other guarantee is
/// returned unchanged.
pub fn additive_as_absolute(
pub(crate) fn additive_as_absolute(
input: AccuracyGuarantee,
norm: Option<&StreamNorm>,
) -> AccuracyGuarantee {
Expand Down Expand Up @@ -641,7 +641,7 @@ fn composed_provenance(
/// binds some). `Err` is the fail-closed answer: no registered rule, or a
/// missing input guarantee. It means "no rule", not "infeasible": Pass 1
/// keeps the candidate with `guarantee = None`.
pub fn propagate(
pub(crate) fn propagate(
op: &CompositionOperator,
inputs: &[AccuracyGuarantee],
local: Option<&AccuracyGuarantee>,
Expand Down
4 changes: 2 additions & 2 deletions crates/accuracy/src/estimators/cms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ pub(super) fn guarantee(
}

/// CMS: over-count ≤ ε·N with width `w = ⌈e/ε⌉` columns.
pub fn cms_width(eps: f64) -> u32 {
pub(crate) fn cms_width(eps: f64) -> u32 {
saturating_ceil(std::f64::consts::E / eps, 2, 1 << 26)
}

/// CMS: failure probability ≤ δ with depth `d = ⌈ln(1/δ)⌉` rows.
/// δ = 0.01 → depth 5.
pub fn cms_depth(delta: f64) -> u32 {
pub(crate) fn cms_depth(delta: f64) -> u32 {
saturating_ceil((1.0 / delta).ln(), 1, 32)
}

Expand Down
14 changes: 8 additions & 6 deletions crates/accuracy/src/estimators/hll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub fn classic_relative_error(precision: u8, relative_error: Option<f64>) -> f64
}

/// The statistic classic HLL's failure probability at `precision` and
/// `relative_error` over `source` is left as ([`classic_guarantee`]).
/// `relative_error` over `source` is left as (`classic_guarantee`).
/// `relative_error` is written in Rust's shortest round-trip form, so it
/// parses back to the same value.
pub fn classic_failure_statistic(precision: u8, relative_error: f64, source: &str) -> String {
Expand Down Expand Up @@ -81,7 +81,7 @@ pub fn parse_classic_failure_statistic(statistic: &str) -> Option<(u8, f64, &str
/// distinct bound ([`classic_failure_statistic`]). The failure probability
/// is per output row: each group's estimate fails with at most it. `None`
/// for a precision outside the contract's.
pub fn classic_guarantee(
pub(crate) fn classic_guarantee(
precision: u8,
relative_error: Option<f64>,
source: &str,
Expand Down Expand Up @@ -135,13 +135,13 @@ const MAX_CONTRACT_DISTINCT: u32 = 1 << 17;
/// The deployment must establish the population bound; observations alone do
/// not establish it. Unsupported precisions/populations return no certificate.
#[derive(Debug, Clone, Copy)]
pub struct ClassicHllConfidence {
pub(crate) struct ClassicHllConfidence {
max_distinct: u32,
relative_error: f64,
}

impl ClassicHllConfidence {
pub fn new(max_distinct: u32, relative_error: f64) -> Option<Self> {
pub(crate) fn new(max_distinct: u32, relative_error: f64) -> Option<Self> {
(max_distinct > 0
&& max_distinct <= MAX_CONTRACT_DISTINCT
&& relative_error.is_finite()
Expand All @@ -152,7 +152,8 @@ impl ClassicHllConfidence {
})
}

pub fn guarantee(&self, precision: u8) -> Option<AccuracyGuarantee> {
#[cfg(test)]
pub(crate) fn guarantee(&self, precision: u8) -> Option<AccuracyGuarantee> {
let delta = self.failure_probability(precision)?;
Some(AccuracyGuarantee::new(
AccuracyMetric::Relative,
Expand All @@ -172,7 +173,8 @@ impl ClassicHllConfidence {
))
}

pub fn precision(&self, delta: f64) -> Option<u8> {
#[cfg(test)]
pub(crate) fn precision(&self, delta: f64) -> Option<u8> {
if !delta.is_finite() || !(0.0..1.0).contains(&delta) || delta == 0.0 {
return None;
}
Expand Down
2 changes: 1 addition & 1 deletion crates/accuracy/src/estimators/kll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ pub(crate) const KLL_RANK_ERROR_EXPONENT_99: f64 = 0.9723;
/// The failure probability of the empirical 99% calibration.
pub(crate) const KLL_CALIBRATED_FAILURE_PROBABILITY: f64 = 0.01;

pub fn kll_rank_error_99(k: u32) -> f64 {
pub(crate) fn kll_rank_error_99(k: u32) -> f64 {
KLL_RANK_ERROR_COEFFICIENT_99 / f64::from(k).powf(KLL_RANK_ERROR_EXPONENT_99)
}

Expand Down
8 changes: 4 additions & 4 deletions crates/accuracy/src/estimators/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ use asap_types::ir::operator::AggIntent;
use asap_types::ir::schema::FamilyCapability;

pub(crate) mod cardinality;
pub mod cms;
pub(crate) mod cms;
pub(crate) mod count_sketch;
pub(crate) mod ddsketch;
pub mod hll;
pub mod kll;
pub(crate) mod kll;
pub mod univmon;

pub(super) fn sketch_guarantee(
Expand Down Expand Up @@ -62,7 +62,7 @@ fn bounded_guarantee(
/// (`SummarySample`, `Wavelet`, `StatModel`, and sketch evaluations without a
/// certified bound): Pass 1 then leaves the guarantee `None` and keeps the
/// candidate for plan selection to decide.
pub fn local_guarantee(
pub(crate) fn local_guarantee(
family: &FieldDataType,
query: &SketchStatistic,
) -> Option<AccuracyGuarantee> {
Expand Down Expand Up @@ -136,7 +136,7 @@ pub fn with_failure_probability(params: &SketchParams, delta: f64) -> SketchPara
}
}

pub fn size_params(
pub(crate) fn size_params(
kind: SketchAlgorithm,
intent: &AggIntent,
eps: f64,
Expand Down
4 changes: 2 additions & 2 deletions crates/accuracy/src/estimators/univmon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//! - the distinct count is unbiased with `Var ≤ 1.35 F0²/k` for a heap of
//! `k ≥ 8` once the top layer is complete, which needs the source's
//! distinct bound, so its guarantee is built where the source is known
//! ([`distinct_guarantee`]) and plan selection binds it;
//! (`distinct_guarantee`) and plan selection binds it;
//! - entropy has no certified bound.
//!
//! Every bound assumes the key hash is uniformly random and the shape keeps
Expand Down Expand Up @@ -257,7 +257,7 @@ pub fn incomplete_top_layer_probability(heap_size: u32, layers: u8, max_distinct
/// `δ_c` plus the chance of an incomplete top layer, left symbolic in the
/// source's distinct bound. `None` for a shape that was not sized for a
/// distinct count, a heap under 8, or a shape the library would not build.
pub fn distinct_guarantee(params: &SketchParams, source: &str) -> Option<AccuracyGuarantee> {
pub(crate) fn distinct_guarantee(params: &SketchParams, source: &str) -> Option<AccuracyGuarantee> {
let SketchParams::UnivMon {
heap_size,
sketch_rows,
Expand Down
16 changes: 8 additions & 8 deletions crates/accuracy/src/evidence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,33 +19,33 @@ pub struct OperandDomain {
#[derive(Debug, Clone, Default, PartialEq)]
pub struct PropagationStats {
/// Provenance for supplied evidence (source, observation identity, etc.).
pub evidence_provenance: Vec<GuaranteeSource>,
pub(crate) evidence_provenance: Vec<GuaranteeSource>,
/// Whether every input value is known to be non-negative — required by
/// the multiplicative relative-error rule, which is unsound across a
/// sign change.
pub values_non_negative: Option<bool>,
pub(crate) values_non_negative: Option<bool>,
/// The statistic an unknown sign is named by when the folded values are
/// one metric's raw data samples or plain sums of them
/// ([`raw_data_samples_sign_statistic`](crate::composition::raw_data_samples_sign_statistic)):
/// the relative bound is then gated on it, and plan selection binds it
/// to 1 when the metric is declared a counter. `None`: the bare
/// `values_non_negative`, which nothing binds.
pub sign_statistic: Option<String>,
pub(crate) sign_statistic: Option<String>,
/// Number of input rows an exact aggregation consumes (e.g. the number
/// of groups a function folds), for exact aggregate union bounds
/// bound over per-input failures.
pub input_row_count: Option<u64>,
pub(crate) input_row_count: Option<u64>,
/// The statistic an unknown row count is named by (default
/// `input_row_count`), so a fold whose rows the data workload's input
/// cardinality does not bound can leave a statistic nothing binds.
pub row_count_statistic: Option<&'static str>,
pub(crate) row_count_statistic: Option<&'static str>,
/// Lower confidence bound of the kth selected TopK item, after widening
/// the interval by the sketch's own estimation error.
pub topk_selected_lower_bound: Option<f64>,
pub(crate) topk_selected_lower_bound: Option<f64>,
/// Greatest upper confidence bound among excluded TopK items, after
/// widening the interval by the sketch's own estimation error.
pub topk_excluded_upper_bound: Option<f64>,
pub(crate) topk_excluded_upper_bound: Option<f64>,
/// Union-bound failure probability of all intervals used by the margin
/// certificate.
pub topk_interval_failure_probability: Option<f64>,
pub(crate) topk_interval_failure_probability: Option<f64>,
}
2 changes: 1 addition & 1 deletion crates/accuracy/src/function_rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use asap_types::ir::properties::CompositionOperator;
/// The composition operator that propagates an approximate input's guarantee
/// through `intent`; `None` for a function with no registered rule over
/// approximate input, whose callers must fail closed.
pub fn approximate_input_composition(intent: &AggIntent) -> Option<CompositionOperator> {
pub(crate) fn approximate_input_composition(intent: &AggIntent) -> Option<CompositionOperator> {
Some(match intent {
AggIntent::Sum { .. } => CompositionOperator::ExactSum,
AggIntent::Min { .. } | AggIntent::Max { .. } => CompositionOperator::ExactExtremum,
Expand Down
21 changes: 14 additions & 7 deletions crates/accuracy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,31 @@
//! It depends only on `asap-types`, never on a stage crate, so every stage
//! can use it.

pub mod budget;
mod budget;
pub mod composition;
pub mod estimators;
mod evidence;
pub mod function_rules;
mod function_rules;
mod model;
pub mod propagation;
pub mod sizing;
#[cfg(feature = "test-support")]
pub mod test_support;

pub use budget::per_input_target;
pub use evidence::{OperandDomain, PropagationStats};
pub use evidence::OperandDomain;
pub(crate) use evidence::PropagationStats;
pub use model::{
AccuracyEvidenceProvider, AccuracyModel, BuiltinAccuracyModel, NoAccuracyEvidence,
};

#[cfg(test)]
use asap_types::ir::properties::AccuracyScope;
use asap_types::ir::properties::AccuracyTarget;
use asap_types::ir::properties::{
AccuracyError, AccuracyGuarantee, AccuracyMetric, BoundExpr, CompositionOperator,
GuaranteeSource, ProbabilityExpr,
};
use asap_types::ir::properties::{AccuracyScope, AccuracyTarget};
use asap_types::ir::schema::{FieldDataType, SketchAlgorithm, SketchParams, SketchStatistic};

/// Small relative tolerance for comparing an evaluated bound against a
Expand All @@ -54,7 +57,7 @@ const SATISFACTION_TOLERANCE: f64 = 1e-9;
/// bound in any metric does, each compared in its own metric. Unknown
/// required dimensions fail. Plan selection's default accuracy model
/// ([`BuiltinAccuracyModel`]) delegates to it.
pub fn satisfies(guarantee: &AccuracyGuarantee, target: &AccuracyTarget) -> bool {
pub(crate) fn satisfies(guarantee: &AccuracyGuarantee, target: &AccuracyTarget) -> bool {
let within = |value: Option<f64>, limit: f64| {
value.is_some_and(|v| v <= limit * (1.0 + SATISFACTION_TOLERANCE) + f64::EPSILON)
};
Expand Down Expand Up @@ -87,7 +90,7 @@ fn metric_meets(guarantee: AccuracyMetric, target: AccuracyMetric) -> bool {
}

/// Whether `guarantee`'s failure probability covers the values `target`'s
/// does ([`AccuracyScope::covers`]). A whole-result guarantee covers every
/// does ([`AccuracyScope::covers`](asap_types::ir::properties::AccuracyScope::covers)). A whole-result guarantee covers every
/// target, and one over the groups holding at least a share of the records
/// covers a target over a larger share (not a per-value one, whose value may
/// be a smaller group). A per-value one covers only a per-value target: it must first be stated over the
Expand All @@ -105,7 +108,11 @@ fn scope_meets(guarantee: &AccuracyGuarantee, target: &AccuracyTarget) -> bool {
/// own native terms (#690 §4) and for each value it answers; an aggregate
/// intent's metric and scope are not checked: only a query root's are, by
/// plan selection.
pub fn native_target(target: &AccuracyTarget, guarantee: &AccuracyGuarantee) -> AccuracyTarget {
#[cfg(test)]
pub(crate) fn native_target(
target: &AccuracyTarget,
guarantee: &AccuracyGuarantee,
) -> AccuracyTarget {
target
.clone()
.accuracy_metric(guarantee.spec.accuracy_metric)
Expand Down
4 changes: 2 additions & 2 deletions crates/accuracy/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use asap_types::ir::{OperatorNode, QueryRoot};
/// implements this trait and passes it in
/// `asap_plan_selection::PlanningModels::accuracy`.
/// Guarantees are composed with the built-in rules only
/// ([`propagate`](crate::composition::propagate)).
/// (`propagate`).
pub trait AccuracyModel {
/// Whether `guarantee` meets `target`: a bound within its ε, and within
/// its δ when it states one, over the values the target's scope covers
Expand Down Expand Up @@ -70,7 +70,7 @@ pub trait AccuracyModel {
}

/// The planner's built-in analytical rules as an [`AccuracyModel`]: the
/// conservative target check [`satisfies`](crate::satisfies) makes, and no
/// conservative target check `satisfies` makes, and no
/// output accepted regardless of its guarantee.
/// The default in `asap_plan_selection::PlanningModels::builtin`.
#[derive(Debug, Default, Clone, Copy)]
Expand Down
6 changes: 3 additions & 3 deletions crates/accuracy/src/propagation.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Guarantee propagation: the theoretical guarantee of every node of a
//! composed DAG, composed with the built-in analytical rules
//! ([`crate::composition::propagate`]) through operators, and the helpers
//! (`crate::composition::propagate`) through operators, and the helpers
//! Pass 2 and plan selection (Stage 3) use to read, bind and recompose
//! those guarantees and to resize the summaries they depend on.
use std::collections::{HashMap, HashSet};
Expand Down Expand Up @@ -1198,7 +1198,7 @@ pub fn operand_of(
/// `node`'s guarantee as an input to a computation over its values: a
/// Count-Min or Count Sketch bound restated as an absolute error over the
/// stream norm its summary's update weight allows ([`stream_norm`],
/// [`additive_as_absolute`]).
/// `additive_as_absolute`).
pub fn operand_guarantee(node: &Rc<OperatorNode>) -> Option<AccuracyGuarantee> {
let guarantee = node.guarantee.clone()?;
Some(additive_as_absolute(guarantee, stream_norm(node).as_ref()))
Expand Down Expand Up @@ -1429,7 +1429,7 @@ fn arithmetic_guarantee(
/// of two estimates, other than of two relative errors
/// ([`PRODUCT_OPERAND_DOMAINS`]); a quotient with an approximate
/// denominator, other than of relative or exact over relative
/// ([`QUOTIENT_OPERAND_DOMAINS`]). `None` for any other operation (a
/// (`QUOTIENT_OPERAND_DOMAINS`). `None` for any other operation (a
/// product or quotient of relative errors has a closed form), or when an
/// operand's error has no value form (a rank error). Plan selection
/// (Stage 3) finds the operations to bind with this same rule.
Expand Down
Loading
Loading