Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
874d86a
feat: own shared physical DAG execution in Planner
zzylol Sep 23, 2026
7f986bf
style: align shared operator code with Planner lint policy
zzylol Sep 23, 2026
3b99910
docs: compare Arrow batches with native summary state formats
zzylol Sep 23, 2026
9536cca
feat: execute Planner expressions and relational joins in native DAGs
zzylol Sep 23, 2026
92e5f2e
feat: share window computations and typed binary execution
zzylol Sep 23, 2026
cb8bede
refactor: own stored-summary decoding and readout in shared library
zzylol Sep 23, 2026
0649520
docs: clarify shared stored-state computation ownership
zzylol Sep 23, 2026
5197bfe
fix: finalize exact state before relational value consumers
zzylol Sep 23, 2026
3146148
feat: bind raw scans through shared data source connectors
zzylol Sep 24, 2026
cc04eb0
feat: execute weighted CMS summaries in the shared DAG runtime
zzylol Sep 24, 2026
deb1d03
refactor: clarify physical execution contracts and DataFusion tradeoffs
zzylol Sep 24, 2026
e83a40c
test: cover integrated weighted-summary cancellation and clarify surv…
zzylol Sep 24, 2026
44bd40c
feat: align weighted CountSketch DAG execution with Planner candidates
zzylol Sep 24, 2026
ad60b7a
fix: integrate weighted frequency operators with current execution mo…
zzylol Sep 24, 2026
bc30464
refactor: remove accumulator suffix from summary operator modules
zzylol Sep 24, 2026
bfb4b07
docs: condense physical execution design and choose native implementa…
zzylol Sep 24, 2026
8872466
fix: align physical semantics and add DataFusion-inspired contract tests
zzylol Sep 24, 2026
85fade3
refactor: name summary computation kernels explicitly
zzylol Sep 24, 2026
f06556c
refactor: delegate weighted frequency algorithms to sketchlib
zzylol Sep 24, 2026
433b7c4
docs: explain shared physical execution design and acceptance boundaries
zzylol Sep 24, 2026
3113435
docs: align physical execution design with architecture terminology
zzylol Sep 24, 2026
359eaa1
docs: simplify physical execution design to three ownership layers
zzylol Sep 24, 2026
6f42338
docs: clarify maintenance physical planning and deployment boundaries
zzylol Sep 25, 2026
9b0d74f
docs: explain planning boundaries with running KLL pane example
zzylol Sep 25, 2026
71f9884
feat: compile physical DAG candidates independently of deployment rea…
zzylol Sep 25, 2026
ad4e611
feat: compose compiled physical fragments with typed inputs
zzylol Sep 25, 2026
d0c67b2
feat: model bounded classic HLL confidence without an RSE shortcut
zzylol Sep 23, 2026
20aff01
fix: reserve absolute relative-error slack for HLL arithmetic
zzylol Sep 23, 2026
2d8d98d
fix: rank mixed logical candidates using explicit candidate costs
zzylol Sep 25, 2026
4e833e7
fix: expose executable grouped temporal accumulator candidates
zzylol Sep 25, 2026
7080956
fix: preserve selected composed summaries during DAG assembly
zzylol Sep 25, 2026
206f93a
fix: keep nested aggregate dependencies explicit unless composition r…
zzylol Sep 25, 2026
1aab370
feat: honor summary candidate physical feasibility during selection
zzylol Sep 25, 2026
32facc9
fix: consider exact count candidates for approximate accuracy targets
zzylol Sep 25, 2026
d333c11
feat: certify bounded mean and quantile ratio candidates
zzylol Sep 25, 2026
78e5a81
feat: compile and select physical precompute frontier candidates
zzylol Sep 26, 2026
9bed5c6
feat: enumerate bounded physical materialization frontiers
zzylol Sep 26, 2026
874ae1c
fix: admit exact temporal ranking for approximate requests
zzylol Sep 26, 2026
b5beb53
refactor: retain deployment metadata in Planner candidate selection
zzylol Sep 26, 2026
13a4e28
fix: omit sparse counter series in shared physical readouts
zzylol Sep 26, 2026
ae8817a
Preserve logical counter windows across physical candidates
zzylol Sep 26, 2026
99e9805
Omit sparse keyed counter populations through shared readout
zzylol Sep 26, 2026
fbb620b
Handle sparse Planner exact counter state in shared readouts
zzylol Sep 26, 2026
8ecf5f2
perf: reuse run-local scratch for canonical exact-state merges
zzylol Sep 26, 2026
be05d9a
fix: merge finalized pane populations once in cumulative readout
zzylol Sep 26, 2026
d02eabd
docs: remove DataFusion execution comparison
zzylol Sep 26, 2026
34fad57
test: cover lifecycle planning and shared physical execution end to end
zzylol Sep 26, 2026
07d4937
feat: compile selected temporal KLL maintenance into pane DAGs
zzylol Sep 26, 2026
62bdadf
docs: clarify summary source grouping and pane coverage
zzylol Sep 26, 2026
fa2fd23
docs: distinguish input scope from complete summary semantics
zzylol Sep 26, 2026
880abe4
feat(types): export versioned summary semantic dependency closures
zzylol Sep 26, 2026
6a1329d
feat: retain unpriced computation candidates before physical compilation
zzylol Sep 27, 2026
e8b4e54
docs: define physical candidate handoff and deployment selection owne…
zzylol Sep 27, 2026
75c68b6
feat: expose heap candidates over finalized per-series counter rates
zzylol Sep 27, 2026
4cfbfae
perf: bucket candidate equality checks without changing admission
zzylol Sep 27, 2026
2359a30
test: verify counter heap snapshots in query and ingestion scopes
zzylol Sep 27, 2026
3404ec2
feat: enumerate candidate roots without workload Cartesian expansion
zzylol Sep 27, 2026
824b26d
fix: compile resolved per-series counter windows before heap ranking
zzylol Sep 27, 2026
595ca6e
feat: persist and validate selected physical candidates without logic…
zzylol Sep 27, 2026
c470366
feat: persist typed physical output batches with explicit summary codecs
zzylol Sep 27, 2026
5aee349
fix: reject native physical output frames in legacy sketch readers
zzylol Sep 27, 2026
0b0ba60
feat: preserve dynamic series identity through rate and snapshot heaps
zzylol Sep 27, 2026
74234d4
feat: compile current-series population boundaries into physical read…
zzylol Sep 27, 2026
89bf83c
feat: expose signed spatial TopK heap physical candidates
zzylol Sep 27, 2026
c9df5a6
feat: compile native ranking above exact stored Rate readouts
zzylol Sep 27, 2026
15de21e
Expose fixed-window Rate heap physical candidates and verify stored-s…
zzylol Sep 27, 2026
87bb344
Expose grouped Rate Sum at both maintenance and query placements
zzylol Sep 28, 2026
b860407
feat: bind persisted semantic definitions to logical dataset identity
zzylol Sep 28, 2026
a074568
docs: specify dataset-bound semantic export contract
zzylol Sep 28, 2026
39ad0e4
docs: group dataset identity with semantic input contracts
zzylol Sep 28, 2026
ec66cf5
fix: preserve grouped samples through temporal physical plans
zzylol Sep 28, 2026
70893e6
fix: finalize exact state at exposed query candidate roots
zzylol Sep 28, 2026
f674e45
fix: expose finalized globally selected query results
zzylol Sep 28, 2026
c3a4e0a
test: cover selected query result schema boundary
zzylol Sep 28, 2026
1c4417e
fix: expose Planner query finalization for direct candidate proposals
zzylol Sep 28, 2026
9486494
test: borrow schemas directly in physical query regression
zzylol Sep 28, 2026
0066214
docs: name the precompute DAG after the field that holds it
zzylol Sep 28, 2026
2af6666
docs: name the precompute half consistently in the physical layer
zzylol Sep 28, 2026
0fde518
docs: align the accumulator input comment with its own trait doc
zzylol Sep 28, 2026
4338cb2
feat: compile scalar and dynamic-label vector computation before binding
zzylol Sep 29, 2026
3a99b6f
feat: retain window operators and compose shared physical fragments
zzylol Sep 29, 2026
ed7f82c
fix: enforce certified pruning coverage in native semi-joins
zzylol Sep 29, 2026
23cf9eb
feat: compile aligned ingestion arithmetic in physical plans
zzylol Sep 29, 2026
c4c2485
feat: compile immutable population precompute DAGs in Planner
zzylol Sep 29, 2026
da8392a
fix: reject non-label population grouping during physical compilation
zzylol Sep 29, 2026
e65445a
test: verify immutable precompute graph population contracts
zzylol Sep 29, 2026
97743d4
fix: reject colliding scalar broadcast result identities
zzylol Sep 29, 2026
06efd1d
fix: preserve opaque summary columns in physical projections
zzylol Sep 29, 2026
aea2321
feat: retain exact summary readouts as physical graphs
zzylol Sep 29, 2026
76fbbf1
refactor: adopt PostAsapDag names in the physical layer
zzylol Sep 29, 2026
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
67 changes: 62 additions & 5 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
[workspace]
members = [
"crates/asap-physical-operators",
"crates/asap_sketch_codec",
"crates/types",
"crates/sql-function-catalog",
"crates/asap-aware-mapping",
Expand Down
4 changes: 2 additions & 2 deletions crates/asap-aware-mapping/src/accuracy/composition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,10 +276,10 @@ impl DefaultAccuracyModel {
if inputs.len() != 2
|| inputs
.iter()
.any(|input| input.metric != ErrorMetric::RelativeValue)
.any(|input| input.metric != ErrorMetric::RelativeValue && !input.is_exact())
{
return Err(unsupported(
"division needs exactly two RelativeValue guarantees".into(),
"division needs two relative-value or exact guarantees".into(),
));
}
let Some(numerator) = inputs[0].bound.evaluate() else {
Expand Down
7 changes: 7 additions & 0 deletions crates/asap-aware-mapping/src/cost_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,13 @@ pub trait CostModel {
self.raw_query_recompute_cost(target)
.map(|per_read| Cost(per_read.0 * expected_reads))
}
/// Physical feasibility evidence for a complete summary candidate.
/// `None` defers admission to physical/deployment compilation; `Some(false)`
/// excludes the candidate without changing its computation or parameters.
fn summary_support_evidence(&self, _summary: &SummaryNode) -> Option<bool> {
None
}

/// Which mixed exact/summary execution shapes the downstream runtime
/// advertises (issue #171). Gates candidate *generation* in
/// [`crate::exact_composition::ExactCompositionStrategy`]: a shape the
Expand Down
208 changes: 208 additions & 0 deletions crates/asap-aware-mapping/src/hll_confidence.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
//! Estimator-specific confidence for classic HLL's linear-counting branch.
//!
//! This is conditional on independent uniform bucket hashes and an enforced
//! upper bound on distinct items in the complete readout population (including
//! all merged panes). It is not an RSE-to-normal conversion or an ERP fit.

use asap_types::post_asap::{
BoundExpr, ErrorMetric, GuaranteeSource, ProbabilityExpr, ResultGuarantee,
};

/// A finite-population contract for `m * ln(m / zero_registers)` with the
/// classic HLL small-range switch. Hashing is assumed independent and uniform.
/// 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 {
max_distinct: u32,
relative_error: f64,
}

impl ClassicHllConfidence {
pub fn new(max_distinct: u32, relative_error: f64) -> Option<Self> {
(max_distinct > 0
&& max_distinct <= 4096
&& relative_error.is_finite()
&& (1e-6..1.0).contains(&relative_error))
.then_some(Self {
max_distinct,
relative_error,
})
}

pub fn guarantee(&self, precision: u8) -> Option<ResultGuarantee> {
let delta = self.failure_probability(precision)?;
Some(ResultGuarantee {
metric: ErrorMetric::Cardinality,
bound: BoundExpr::Constant {
value: self.relative_error,
},
failure_probability: ProbabilityExpr::Constant { value: delta },
provenance: vec![GuaranteeSource::SketchReadout {
algorithm: "Hll".into(),
contract: "classic_hll_linear_counting_collision_bound_v1".into(),
params: serde_json::json!({"precision": precision,
"max_distinct": self.max_distinct, "relative_error": self.relative_error,
"hash_assumption": "independent_uniform_buckets",
"population_scope": "complete_readout_including_merged_panes"}),
query: "Cardinality".into(),
}],
})
}

pub fn precision(&self, delta: f64) -> Option<u8> {
if !delta.is_finite() || !(0.0..1.0).contains(&delta) || delta == 0.0 {
return None;
}
(4..=18).find(|&p| self.failure_probability(p).is_some_and(|d| d <= delta))
}

/// Finite bound, not an asymptotic RSE fit. With N distinct hashes and K
/// occupied buckets, C=N-K collision arrivals satisfy
/// P(C>=t) <= lambda^t/t!, lambda=N(N-1)/(2m): each arrival's conditional
/// collision probability is at most (i-1)/m, and a union bound over t
/// arrivals is bounded by the t-th power of their sum divided by t!.
///
/// N<=m/2 makes the classic raw estimate <=2*alpha_m*m<2.5m,
/// so the small-range switch always uses L=-m*ln(1-K/m). Then
/// K<=L<=N + N^2/(2(m-N)). The latter bounds overestimation
/// deterministically; underestimation implies C>epsilon*N.
/// We maximize the collision bound over EVERY integer N in the contract,
/// not just its upper endpoint (small-cardinality tails matter).
fn failure_probability(&self, precision: u8) -> Option<f64> {
if !(4..=18).contains(&precision) {
return None;
}
let m = f64::from(1u32 << precision);
let max_n = f64::from(self.max_distinct);
// Reserve numerical slack; do not certify sub-floating-point error.
let eps = self.relative_error - 1e-8;
if max_n > m / 2.0 || max_n / (2.0 * (m - max_n)) > eps {
return None;
}
let mut log_factorial = vec![0.0; self.max_distinct as usize + 1];
for i in 1..log_factorial.len() {
log_factorial[i] = log_factorial[i - 1] + (i as f64).ln();
}
let mut worst = 0.0_f64;
for n in 2..=self.max_distinct {
let nf = f64::from(n);
// Including a boundary collision event is conservative.
let t = ((eps * nf).floor() as usize + 1).min(n as usize);
let lambda = nf * (nf - 1.0) / (2.0 * m);
let log_tail = (t as f64) * lambda.ln() - log_factorial[t];
worst = worst.max(log_tail.min(0.0).exp());
}
// Never return a spurious zero from underflow or numeric cancellation.
Some((worst * (1.0 + 1e-10) + 1e-12).min(1.0))
}
}

#[cfg(test)]
mod tests {
use super::*;

/// A supported estimator contract supplies a probability, unlike generic HLL RSE.
#[test]
fn bounded_classic_hll_has_a_feasible_confidence_target() {
let model = ClassicHllConfidence::new(128, 0.05).unwrap();
let precision = model.precision(0.01).expect("finite confidence-sized HLL");
let guarantee = model.guarantee(precision).unwrap();
assert!(!guarantee.has_unknown());
assert!(guarantee.failure_probability.evaluate().unwrap() <= 0.01);
assert_eq!(guarantee.bound.evaluate(), Some(0.05));
}
/// Tighter confidence must increase precision or explicitly become unavailable.
#[test]
fn sizing_and_domain_limits_are_consistent() {
let model = ClassicHllConfidence::new(128, 0.05).unwrap();
assert!(model.precision(0.001).unwrap() > model.precision(0.01).unwrap());
assert!(model.precision(1e-12).is_none());
assert!(model.precision(0.0).is_none());
assert!(model.precision(f64::NAN).is_none());
assert!(model.guarantee(3).is_none());
assert!(model.guarantee(19).is_none());
assert!(model.guarantee(7).is_none());
for (n, e) in [(0, 0.05), (4097, 0.05), (128, 0.0), (128, f64::NAN)] {
assert!(ClassicHllConfidence::new(n, e).is_none());
}
}

/// Exact occupancy probabilities independently check both tails for every N.
#[test]
fn probability_bound_dominates_exact_occupancy_distribution() {
for precision in 4..=10 {
let m = 1usize << precision;
let max_n = 64.min(m / 2);
for eps in [0.05, 0.2, 0.6] {
let model = ClassicHllConfidence::new(max_n as u32, eps).unwrap();
let Some(bound) = model.failure_probability(precision) else {
continue;
};
let mut occupancy = vec![0.0; max_n + 1];
occupancy[0] = 1.0;
for n in 1..=max_n {
let mut next = vec![0.0; max_n + 1];
for k in 0..n {
next[k] += occupancy[k] * k as f64 / m as f64;
next[k + 1] += occupancy[k] * (m - k) as f64 / m as f64;
}
occupancy = next;
let actual: f64 = occupancy
.iter()
.enumerate()
.filter_map(|(k, &prob)| {
let estimate = -(m as f64) * (-(k as f64) / (m as f64)).ln_1p();
((estimate - n as f64).abs() > eps * n as f64).then_some(prob)
})
.sum();
assert!(
actual <= bound + 1e-12,
"p={precision} n={n} eps={eps}: {actual}>{bound}"
);
}
}
}
}
/// The model's readout formula matches the actual classic estimator after merge.
#[test]
fn native_classic_estimator_and_merged_registers_use_the_same_contract() {
use asap_sketchlib::sketches::hll::{Classic, HyperLogLogP16};
let model = ClassicHllConfidence::new(128, 0.05).unwrap();
assert!(
model
.guarantee(16)
.unwrap()
.failure_probability
.evaluate()
.unwrap()
< 0.01
);
let mut single = HyperLogLogP16::<Classic>::new();
let mut left = HyperLogLogP16::<Classic>::new();
let mut right = HyperLogLogP16::<Classic>::new();
for n in 0..128u64 {
// SplitMix64 supplies deterministic test hashes, not a proof of randomness.
let mut h = n.wrapping_add(0x9e3779b97f4a7c15);
h = (h ^ (h >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
h = (h ^ (h >> 27)).wrapping_mul(0x94d049bb133111eb);
h ^= h >> 31;
single.insert_with_hash(h);
if n % 2 == 0 {
left.insert_with_hash(h);
} else {
right.insert_with_hash(h);
}
}
left.merge(&right);
assert_eq!(single.registers_as_slice(), left.registers_as_slice());
let zeroes = left
.registers_as_slice()
.iter()
.filter(|&&r| r == 0)
.count();
let expected = (65536.0 * (65536.0 / zeroes as f64).ln()) as usize;
assert_eq!(left.estimate(), expected);
assert!((expected as f64 - 128.0).abs() / 128.0 <= 0.05);
}
}
1 change: 1 addition & 0 deletions crates/asap-aware-mapping/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ pub mod exact_composition;
pub mod explanation;
mod function_rules;
pub mod grouping;
pub mod hll_confidence;
pub mod pane_sharing;
pub mod physical_handoff_cost;
pub mod physical_operator_statistics;
Expand Down
8 changes: 6 additions & 2 deletions crates/asap-aware-mapping/src/maintained_population.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ fn recognize(root: &QueryExpr) -> Option<(MaintainedPopulation, PopulationReadou
AggIntent::Quantile { q, col, .. } if q.is_finite() => {
(*col, PopulationReadout::Quantile { q: *q })
}
AggIntent::TopK { k, .. } => (None, PopulationReadout::TopK { k: *k }),
AggIntent::Sum { col } => (*col, PopulationReadout::Sum),
AggIntent::Count { .. } => (None, PopulationReadout::Count),
AggIntent::Avg { col } => (*col, PopulationReadout::Average),
Expand Down Expand Up @@ -142,8 +143,11 @@ fn recognize(root: &QueryExpr) -> Option<(MaintainedPopulation, PopulationReadou
if value_column.is_some_and(|c| schema.columns.get(c).is_none_or(|c| c.name != "value")) {
return None;
}
// Open time-series schemas distinguish instant PromQL populations from table rows.
if metric.is_empty() || schema.closed || schema.time_index.is_none() {
// PromQL can retain open labels or resolve them into a complete identity column.
if metric.is_empty()
|| (schema.closed && !schema.has_promql_series_identity())
|| schema.time_index.is_none()
{
return None;
}
let label = |col: usize| -> Option<String> {
Expand Down
Loading
Loading