Skip to content

Commit e683b11

Browse files
zzylolclaude
andcommitted
refactor(planner): name Pass 1 per-computation candidates as in the planning-stages doc
Follow the review on #561: use the terms of planner-layering.md (Stage 1, Pass 1: per-computation candidate generation) instead of "local". - Module logical_candidates -> per_computation_candidates; LocalCandidate -> ComputationCandidate, LocalLogicalTarget -> ComputationTarget, LocalChoices -> ComputationChoices (field alternatives -> candidates), LocalLogicalCandidates -> PerComputationCandidates, enumerate_local_logical_candidates -> generate_per_computation_candidates, local_candidates_for_intent -> computation_candidates_for_intent. - The module doc states the stage and pass and carries the rules that were in docs/develop_docs/local-logical-candidates.md, which is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
1 parent d3e39fe commit e683b11

5 files changed

Lines changed: 172 additions & 253 deletions

File tree

‎crates/asap-aware-mapping/src/lib.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,6 @@ pub use topk_reuse::TopKLimitReuseStrategy;
212212

213213
pub mod maintained_population;
214214

215-
/// Local candidate generation over the unified IR. No execution timing is
215+
/// Stage 1, Pass 1: per-computation candidate generation. No execution timing is
216216
/// assigned: that is a Stage 2 materialization decision.
217-
pub mod logical_candidates;
217+
pub mod per_computation_candidates;

crates/asap-aware-mapping/src/logical_candidates.rs renamed to crates/asap-aware-mapping/src/per_computation_candidates.rs

Lines changed: 78 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,29 @@
1-
//! Pass 1 local alternatives over the unified logical IR.
1+
//! Stage 1 (logical ASAP-aware optimization), Pass 1: per-computation
2+
//! candidate generation, as defined in
3+
//! `docs/design_docs/proposals/planner-layering.md`.
24
//!
3-
//! Alternatives are nominal realization descriptors attached to their original
4-
//! target, not ranked plans or accuracy certificates. Summary alternatives are
5-
//! unsized: Pass 2 may share a summary and tighten it to the strictest consumer,
6-
//! so sizing happens later. Workload composition and physical planning consume
7-
//! this inventory later; empirical models belong to selection. The legacy search
8-
//! API remains until planner cutover.
5+
//! [`generate_per_computation_candidates`] takes the frontends' logical DAGs
6+
//! (`QueryRoot`s) and returns, for every eligible computation, its unranked
7+
//! candidates. A computation ([`ComputationTarget`]) is one measure of an
8+
//! `Aggregate` whose intent has a realization besides plain exact execution;
9+
//! nested aggregates and operators read by scalar roots are found too.
10+
//! Pass 1 does not consider sharing across computations; that is Pass 2.
11+
//!
12+
//! - Each target has exactly one exact option: the exact accumulator
13+
//! (`ExactAccumulator`, or `AvgAsSumCount` for `avg`) when it can be built,
14+
//! otherwise plain `Exact`. Whether it is materialized is decided in Stage 2.
15+
//! - Summary candidates are unsized; they are sized after Pass 2 sharing.
16+
//! - Candidates are pruned only when provably unable to meet the accuracy
17+
//! requirement, and each pruned candidate keeps its [`PruneReason`].
18+
//! - A measure filter or `HAVING` prunes nothing. The target records the
19+
//! measure filter (applied as `SummaryAgg.filter`) and one [`HavingEntry`]
20+
//! per consumer `Filter` directly above the aggregate: a key-only predicate
21+
//! applied by every consumer may go below the aggregate, anything else stays
22+
//! above the output (approximate outputs then get guarantee `None`).
23+
//!
24+
//! The input must be a logical DAG: ASAP operators, execution timing or a
25+
//! materialization are rejected. No cost or accuracy model is used; ranking
26+
//! and discarding valid candidates belong to Stage 3.
927
use std::collections::{HashMap, HashSet};
1028
use std::rc::Rc;
1129

@@ -18,14 +36,14 @@ use thiserror::Error;
1836

1937
use crate::replacement::{accuracy_budget, accuracy_target, summary_candidates};
2038

21-
/// One local realization of a target measure.
39+
/// One Pass 1 candidate for a computation (one measure of an aggregate).
2240
///
2341
/// Each target has exactly one exact option: `ExactAccumulator` or
2442
/// `AvgAsSumCount` when the intent has one and it can be built, otherwise
2543
/// `Exact`. The plain form would duplicate "accumulator, not materialized";
2644
/// Stage 2/3 decide materialization and sharing.
2745
#[derive(Debug, Clone, PartialEq)]
28-
pub enum LocalCandidate {
46+
pub enum ComputationCandidate {
2947
/// Plain exact execution of the original sub-DAG; the exact option only
3048
/// when the intent has no exact accumulator form or that form is pruned.
3149
Exact,
@@ -51,31 +69,31 @@ pub enum PruneReason {
5169

5270
/// Unranked candidates of one measure and the candidates pruned for it.
5371
#[derive(Debug, Clone, PartialEq)]
54-
pub struct LocalChoices {
55-
pub alternatives: Vec<LocalCandidate>,
56-
pub pruned: Vec<(LocalCandidate, PruneReason)>,
72+
pub struct ComputationChoices {
73+
pub candidates: Vec<ComputationCandidate>,
74+
pub pruned: Vec<(ComputationCandidate, PruneReason)>,
5775
}
5876

59-
impl LocalChoices {
60-
/// Prune matching candidates; a pruned exact accumulator form falls back to
77+
impl ComputationChoices {
78+
/// Prune matching candidates; a pruned exact accumulator form is replaced by
6179
/// plain `Exact`, so the target keeps one exact option.
62-
fn prune(&mut self, reason: PruneReason, applies: impl Fn(&LocalCandidate) -> bool) {
63-
let (pruned, kept): (Vec<_>, _) = std::mem::take(&mut self.alternatives)
80+
fn prune(&mut self, reason: PruneReason, applies: impl Fn(&ComputationCandidate) -> bool) {
81+
let (pruned, kept): (Vec<_>, _) = std::mem::take(&mut self.candidates)
6482
.into_iter()
65-
.partition(|choice| *choice != LocalCandidate::Exact && applies(choice));
66-
self.alternatives = kept;
83+
.partition(|choice| *choice != ComputationCandidate::Exact && applies(choice));
84+
self.candidates = kept;
6785
if pruned.iter().any(|choice| {
6886
matches!(
6987
choice,
70-
LocalCandidate::ExactAccumulator { .. } | LocalCandidate::AvgAsSumCount
88+
ComputationCandidate::ExactAccumulator { .. } | ComputationCandidate::AvgAsSumCount
7189
)
7290
}) {
73-
self.alternatives.insert(0, LocalCandidate::Exact);
91+
self.candidates.insert(0, ComputationCandidate::Exact);
7492
}
7593
self.pruned.extend(
7694
pruned
7795
.into_iter()
78-
.map(|choice: LocalCandidate| (choice, reason)),
96+
.map(|choice: ComputationCandidate| (choice, reason)),
7997
);
8098
}
8199
}
@@ -113,11 +131,11 @@ pub struct HavingEntry {
113131
/// execution. The target retains source, grouping, filters, input expressions
114132
/// and evaluation context.
115133
#[derive(Debug, Clone)]
116-
pub struct LocalLogicalTarget {
134+
pub struct ComputationTarget {
117135
pub target: Rc<OperatorNode>,
118136
/// Index into the aggregate's measures.
119137
pub measure: usize,
120-
pub choices: LocalChoices,
138+
pub choices: ComputationChoices,
121139
/// The measure's own filter; only rows that pass it update the state
122140
/// (`SummaryAgg.filter`).
123141
pub filter: Option<Predicate>,
@@ -128,21 +146,21 @@ pub struct LocalLogicalTarget {
128146

129147
/// Compact Pass 1 inventory; roots and nested producer dependencies are retained.
130148
#[derive(Debug, Clone)]
131-
pub struct LocalLogicalCandidates<Id> {
149+
pub struct PerComputationCandidates<Id> {
132150
pub roots: Vec<(Id, QueryRoot)>,
133-
pub targets: Vec<LocalLogicalTarget>,
151+
pub targets: Vec<ComputationTarget>,
134152
}
135153

136154
#[derive(Debug, Error)]
137-
pub enum LogicalCandidateError {
155+
pub enum PerComputationCandidateError {
138156
#[error(transparent)]
139157
Structure(#[from] SchemaDerivationError),
140158
#[error("logical candidate input already has assigned execution timing or materialization")]
141159
AssignedPhysicalAnnotation,
142160
#[error("logical candidate input already contains ASAP operators")]
143161
ContainsAsap,
144162
/// `root` (the root id's `Debug` form) and `measure` locate the target;
145-
/// [`local_candidates_for_intent`] alone leaves them `None`.
163+
/// [`computation_candidates_for_intent`] alone leaves them `None`.
146164
#[error(
147165
"approximate accuracy of root {}, measure {} requires epsilon and delta in (0, 1)",
148166
root.as_deref().unwrap_or("?"),
@@ -158,9 +176,9 @@ pub enum LogicalCandidateError {
158176
/// order, without ranking or empirical assessment. A summary is not a claim that
159177
/// a deployment meets the measure's accuracy requirement. The one exact option
160178
/// comes first: the exact accumulator form if the intent has one, else `Exact`.
161-
pub fn local_candidates_for_intent(
179+
pub fn computation_candidates_for_intent(
162180
intent: &AggIntent,
163-
) -> Result<LocalChoices, LogicalCandidateError> {
181+
) -> Result<ComputationChoices, PerComputationCandidateError> {
164182
let exact = match intent {
165183
AggIntent::Count { .. } => Some((ExactKind::Count, ExactParams::Count)),
166184
AggIntent::Sum { .. } => Some((ExactKind::Sum, ExactParams::Sum)),
@@ -172,20 +190,20 @@ pub fn local_candidates_for_intent(
172190
_ => None,
173191
};
174192
let exact = match exact {
175-
Some((kind, params)) => LocalCandidate::ExactAccumulator { kind, params },
176-
None if matches!(intent, AggIntent::Avg { .. }) => LocalCandidate::AvgAsSumCount,
177-
None => LocalCandidate::Exact,
193+
Some((kind, params)) => ComputationCandidate::ExactAccumulator { kind, params },
194+
None if matches!(intent, AggIntent::Avg { .. }) => ComputationCandidate::AvgAsSumCount,
195+
None => ComputationCandidate::Exact,
178196
};
179-
let mut choices = LocalChoices {
180-
alternatives: vec![exact],
197+
let mut choices = ComputationChoices {
198+
candidates: vec![exact],
181199
pruned: vec![],
182200
};
183201
if let Some(target) = accuracy_target(intent) {
184202
if *target != AccuracyTarget::Exact {
185203
let (epsilon, delta) = accuracy_budget(target);
186204
let unit = |value: f64| value > 0.0 && value < 1.0;
187205
if !unit(epsilon) || !unit(delta) {
188-
return Err(LogicalCandidateError::InvalidAccuracy {
206+
return Err(PerComputationCandidateError::InvalidAccuracy {
189207
root: None,
190208
measure: None,
191209
});
@@ -195,14 +213,14 @@ pub fn local_candidates_for_intent(
195213
if *target == AccuracyTarget::Exact {
196214
choices.pruned.extend(summaries.map(|algorithm| {
197215
(
198-
LocalCandidate::Summary(algorithm),
216+
ComputationCandidate::Summary(algorithm),
199217
PruneReason::RequiresExact,
200218
)
201219
}));
202220
} else {
203221
choices
204-
.alternatives
205-
.extend(summaries.map(LocalCandidate::Summary));
222+
.candidates
223+
.extend(summaries.map(ComputationCandidate::Summary));
206224
}
207225
}
208226
Ok(choices)
@@ -254,9 +272,9 @@ fn placement(below: bool) -> HavingPlacement {
254272
/// and `HAVING` keep every candidate; the target records how they are applied.
255273
/// A consumer `Filter`'s key-only `HAVING` is pushed below only when every
256274
/// consumer of the aggregate applies the same predicate.
257-
pub fn enumerate_local_logical_candidates<Id: std::fmt::Debug>(
275+
pub fn generate_per_computation_candidates<Id: std::fmt::Debug>(
258276
roots: Vec<(Id, QueryRoot)>,
259-
) -> Result<LocalLogicalCandidates<Id>, LogicalCandidateError> {
277+
) -> Result<PerComputationCandidates<Id>, PerComputationCandidateError> {
260278
let mut seen = HashSet::new();
261279
// Each aggregate with the first root that reaches it.
262280
let mut aggregates = Vec::new();
@@ -271,7 +289,7 @@ pub fn enumerate_local_logical_candidates<Id: std::fmt::Debug>(
271289
};
272290
for root in operators {
273291
if root.contains_asap() {
274-
return Err(LogicalCandidateError::ContainsAsap);
292+
return Err(PerComputationCandidateError::ContainsAsap);
275293
}
276294
consumers.entry(Rc::as_ptr(root)).or_default().push(None);
277295
for node in OperatorNode::reachable(root) {
@@ -280,7 +298,7 @@ pub fn enumerate_local_logical_candidates<Id: std::fmt::Debug>(
280298
}
281299
if node.timing.is_some() || node.materialization != Materialization::NotMaterialized
282300
{
283-
return Err(LogicalCandidateError::AssignedPhysicalAnnotation);
301+
return Err(PerComputationCandidateError::AssignedPhysicalAnnotation);
284302
}
285303
for child in node.children() {
286304
consumers
@@ -338,24 +356,25 @@ pub fn enumerate_local_logical_candidates<Id: std::fmt::Debug>(
338356
});
339357
}
340358
for (measure, intent) in measures.iter().enumerate() {
341-
let mut choices = local_candidates_for_intent(intent).map_err(|error| match error {
342-
LogicalCandidateError::InvalidAccuracy { .. } => {
343-
LogicalCandidateError::InvalidAccuracy {
344-
root: Some(root.clone()),
345-
measure: Some(measure),
359+
let mut choices =
360+
computation_candidates_for_intent(intent).map_err(|error| match error {
361+
PerComputationCandidateError::InvalidAccuracy { .. } => {
362+
PerComputationCandidateError::InvalidAccuracy {
363+
root: Some(root.clone()),
364+
measure: Some(measure),
365+
}
346366
}
347-
}
348-
error => error,
349-
})?;
350-
if choices.alternatives == [LocalCandidate::Exact] && choices.pruned.is_empty() {
367+
error => error,
368+
})?;
369+
if choices.candidates == [ComputationCandidate::Exact] && choices.pruned.is_empty() {
351370
continue;
352371
}
353372
if matches!(intent, AggIntent::Avg { .. }) && nullable_avg_input(&node, intent) {
354373
choices.prune(PruneReason::NullableAvgInput, |choice| {
355-
*choice == LocalCandidate::AvgAsSumCount
374+
*choice == ComputationCandidate::AvgAsSumCount
356375
});
357376
}
358-
targets.push(LocalLogicalTarget {
377+
targets.push(ComputationTarget {
359378
target: node.clone(),
360379
measure,
361380
choices,
@@ -364,7 +383,7 @@ pub fn enumerate_local_logical_candidates<Id: std::fmt::Debug>(
364383
});
365384
}
366385
}
367-
Ok(LocalLogicalCandidates { roots, targets })
386+
Ok(PerComputationCandidates { roots, targets })
368387
}
369388

370389
#[cfg(test)]
@@ -374,22 +393,22 @@ mod tests {
374393
/// universal summary; plain exact execution is not offered.
375394
#[test]
376395
fn approximate_count_keeps_one_exact_accumulator_and_universal_choices() {
377-
let choices = local_candidates_for_intent(&AggIntent::Count {
396+
let choices = computation_candidates_for_intent(&AggIntent::Count {
378397
accuracy: AccuracyTarget::EpsilonDelta {
379398
epsilon: 0.05,
380399
delta: 0.01,
381400
},
382401
})
383402
.unwrap()
384-
.alternatives;
385-
assert!(!choices.contains(&LocalCandidate::Exact));
403+
.candidates;
404+
assert!(!choices.contains(&ComputationCandidate::Exact));
386405
assert_eq!(
387406
choices[0],
388-
LocalCandidate::ExactAccumulator {
407+
ComputationCandidate::ExactAccumulator {
389408
kind: ExactKind::Count,
390409
params: ExactParams::Count
391410
}
392411
);
393-
assert!(choices.contains(&LocalCandidate::Summary(SketchAlgorithm::UnivMon)));
412+
assert!(choices.contains(&ComputationCandidate::Summary(SketchAlgorithm::UnivMon)));
394413
}
395414
}

0 commit comments

Comments
 (0)