Repository navigation
Conversation
…group (#1203 PR 2a-1) Pass 1's composed candidates become one operator over groups: every node beneath a candidate's root is interned with the new `Memo::intern` (hash-consing on the structural id, Stage 0's legality rule), so `Project <- SummaryEstimate <- SummaryAgg` is three groups and equal builds of different queries are one group. Summaries are sized before interning, so each group holds its sized node. Pass 2 rules that matched Pass 1's private builds now find them as groups: local window composition and scan widening rewrite the workload groups' candidates through the interned groups; the state-configuration rule and the rollup rule also take a build read as an input group (the resized build is interned); the shared summary-state step reads a private build equal to an interned one from that group. The "already expanded" check is deleted (Pass 1 now creates state groups). Part of #1203 (PR 2a-1), tracked in #1207. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1M6br9PL4SDeVS1XpezKJ
|
/// The group of |
|
what does population in mean? |
|
Replies to the two comments above. Both changes land in the next push, with the independent-review fixes. 1.
2. "Population": it meant "adding Pass 1's candidates to the memo failed". In this codebase "population" also means the set of series (
What each variant means:
|
…1215 - One equality: Stage 0's hash-consing moves to asap_types::stages::hash_consing (HashConsingTable); Stage 0, Memo::group_of / find_or_add_group (was intern) and the shared summary-state step all use it. BuildFingerprint and same_concrete_build are deleted. Pass 1 removes the exact guarantee composition attaches before the lookup (without_exact_guarantees_below); rule 9's resized build reads the build group's own inputs. - Memo.groups is private: groups(), push_group, group_of (&self), group_with_node, add_queries, rebuild_parents, workload_groups, groups_read_through_added_groups. find_or_add_group returns Found/Added and callers raise accuracy budget parts (size_own_candidate_for). - try_add_pass1_candidates takes accuracy targets and data directly. - Renames: memo_population -> add_pass1_candidates, CandidatePopulationError -> AddPass1CandidatesError (variant AddPass1Candidates). - Step 10 raises a reused group's own candidate to its readers' parts and is idempotent; tests for both and for equal-k builds at other delta/guarantee. Part of #1203 (PR 2a-1), tracked in #1207. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1M6br9PL4SDeVS1XpezKJ
… groups Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1M6br9PL4SDeVS1XpezKJ
d0f0857 to
4869186
Compare
… above its reader Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1M6br9PL4SDeVS1XpezKJ
…roupId lookups - Memo::over_child_groups is the one walk that finds or adds a node's children's groups (positional: input i is child i's group); Pass 1 and find_or_add_group use it. find_or_add_group returns the group; find_or_add_sized_group raises the parts of a group that existed (rule 9, step 10). GroupFoundOrAdded deleted. - Memo::new takes the Stage 0 DAG; lookups renamed group_whose_node_is and group_with_equal_node; a lookup no longer keeps its node alive in the hash cache (no RefCell). - workload_groups / groups_read_through_added_groups are interim Stage 1 free functions (added_groups.rs), re-exported through test_support. - One build walk (builds_beneath) for step 10 and scan widening. - Rule 9's added build groups have origin SharedBuild, not the reader's. - Step 10's "already added" check and its idempotence test deleted (step 10 runs once). - structural_hash, its cache and shareable_when_equal private; their tests moved to hash_consing.rs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1M6br9PL4SDeVS1XpezKJ
… over groups Rules 3 (summary inputs) and 8 (summary results) add their candidates as one operator over groups (`add_candidate_over_groups`, through the shared `operator_over_groups` Pass 1 now uses too), so every node beneath them is a memo group. Summary inputs find or add their canonical inputs through `find_or_add_producer` (Stage 0's equality), which replaces the TimeRange rebinding. Equal candidates are found by `push_candidate_if_new` (hash-consing equality), which rule 9 now uses too, in place of its own graph-signature check. `boundary_groups` looks groups up with `Memo::group_whose_node_is`. Second review fixes, folded in on the rebase onto #1215's fixes: one `same_output_contract` in reuse.rs (scan widening, rule 9 and `check_output_contract` use it); `add_candidate_over_groups` takes a `MemoGroupId` owner; `find_or_add_producer` drops its always-1 budget-parts argument; `push_candidate_if_new` is a crate-private Stage 1 function, not a `Memo` method. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1M6br9PL4SDeVS1XpezKJ
…age 0 group count - `build_read_by` (state_groups) replaces `simple_build` and `input_group_build`: the one summary build a candidate reads and where (`BuildPlace`: private, input group, beneath groups). Rule 9 and the rollups call it. Scan widening keeps its stricter lookup, now `state_groups::build_down_to_stage0_groups` (the only build down to Stage 0's groups), which `build_read_by` uses for its last look. - `Memo` stores how many groups Stage 0 produced (`is_stage0_group`); `workload_groups` and its per-call walk are deleted. - `find_or_add_group` and `over_child_groups` move out of `Memo` into a crate-private Stage 1 module; `find_or_add_sized_group` looks the node up once. - hash consing: one `structural_hash_with` for both structural hashes. - devtools: `stage0_memo_groups` inlined. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1M6br9PL4SDeVS1XpezKJ
9133835 to
8182edf
Compare
… over groups Rules 3 (summary inputs) and 8 (summary results) add their candidates as one operator over groups (`add_candidate_over_groups`, through the shared `operator_over_groups` Pass 1 now uses too), so every node beneath them is a memo group. Summary inputs find or add their canonical inputs through `find_or_add_producer` (Stage 0's equality), which replaces the TimeRange rebinding. Equal candidates are found by `push_candidate_if_new` (hash-consing equality), which rule 9 now uses too, in place of its own graph-signature check. `boundary_groups` looks groups up with `Memo::group_whose_node_is`. Second review fixes, folded in on the rebase onto #1215's fixes: one `same_output_contract` in reuse.rs (scan widening, rule 9 and `check_output_contract` use it); `add_candidate_over_groups` takes a `MemoGroupId` owner; `find_or_add_producer` drops its always-1 budget-parts argument; `push_candidate_if_new` is a crate-private Stage 1 function, not a `Memo` method. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1M6br9PL4SDeVS1XpezKJ
build_read_by is build_of. Prose says consumer for the candidate that reads an input and query for the evaluation's sense, as the concepts doc does.
… over groups Rules 3 (summary inputs) and 8 (summary results) add their candidates as one operator over groups (`add_candidate_over_groups`, through the shared `operator_over_groups` Pass 1 now uses too), so every node beneath them is a memo group. Summary inputs find or add their canonical inputs through `find_or_add_producer` (Stage 0's equality), which replaces the TimeRange rebinding. Equal candidates are found by `push_candidate_if_new` (hash-consing equality), which rule 9 now uses too, in place of its own graph-signature check. `boundary_groups` looks groups up with `Memo::group_whose_node_is`. Second review fixes, folded in on the rebase onto #1215's fixes: one `same_output_contract` in reuse.rs (scan widening, rule 9 and `check_output_contract` use it); `add_candidate_over_groups` takes a `MemoGroupId` owner; `find_or_add_producer` drops its always-1 budget-parts argument; `push_candidate_if_new` is a crate-private Stage 1 function, not a `Memo` method. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1M6br9PL4SDeVS1XpezKJ
check_memo_well_formed is check_group_references_valid_and_acyclic: it checks that group references are in range and consistent, and that the memo has no cycle.
… over groups Rules 3 (summary inputs) and 8 (summary results) add their candidates as one operator over groups (`add_candidate_over_groups`, through the shared `operator_over_groups` Pass 1 now uses too), so every node beneath them is a memo group. Summary inputs find or add their canonical inputs through `find_or_add_producer` (Stage 0's equality), which replaces the TimeRange rebinding. Equal candidates are found by `push_candidate_if_new` (hash-consing equality), which rule 9 now uses too, in place of its own graph-signature check. `boundary_groups` looks groups up with `Memo::group_whose_node_is`. Second review fixes, folded in on the rebase onto #1215's fixes: one `same_output_contract` in reuse.rs (scan widening, rule 9 and `check_output_contract` use it); `add_candidate_over_groups` takes a `MemoGroupId` owner; `find_or_add_producer` drops its always-1 budget-parts argument; `push_candidate_if_new` is a crate-private Stage 1 function, not a `Memo` method. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1M6br9PL4SDeVS1XpezKJ
Part of #1203 (PR 2a-1), tracked in #1207.
Merge unit. This PR merges into main only together with the rest of #1207's merge unit (steps 1a–6, #1207 "Order"): interim items below are deleted by a later PR in the unit.
Why
#1203 decided "one operator per candidate, no private nodes": every intermediate result of a way to compute is its own memo group, as in Cascades. Today a Pass 1 candidate's
root_nodeholds a private chain, e.g.Project ← SummaryEstimate ← SummaryAgg Kll(k=519)over the range group, so two queries' equal KLL builds are two private nodes that Pass 2's shared-build step must find and factor afterwards. This PR is the Stage 1 start of 2a: Pass 1 stops making private chains.What
find_or_add_group, Stage 1), with Stage 0's node equality (oneHashConsingTableshared by Stage 0, the memo and the shared summary-state step). Each Pass 1 candidate is its top operator over groups.quantile_over_time(0.95, latency[1m])andquantile_over_time(0.99, latency[1m]) / quantile_over_time(0.50, latency[1m]),max_error 0.01):Project ← SummaryEstimate ← SummaryAgg Kll(k=519)is now three groups, and the queries'k=519builds are one group (G6 below).corpus_snapshotsandsketch_plan_snapshotssnapshot keeps its selected plan and cost; Stage 2's physical-candidate count is the same or lower on every snapshot.Equality
find_or_add_groupandMemo::group_with_equal_nodeuse Stage 0's equality (asap_types::stages::hash_consing: operator, schema, result kind, guarantee, timing, materialization and children, gated byshareable_when_equal). Two consequences:Scan/TimeRangefrom Stage 0's group (duplicateG6: Scan,G7: TimeRange 60sin the example). Pass 1 therefore removes only exact guarantees below a composed root before the lookup (without_exact_guarantees_below, one function); an approximate guarantee stays part of equality, so builds of equalkbut another δ or guarantee stay different groups (testequal_k_builds_with_another_failure_probability_or_guarantee_are_other_groups).input_groupsare by child position, like a group'schild_groups: inputiis childi's group, sox / xlistsx's group twice (over_child_groups).Why not Pass 1 alone
Switching Pass 1 alone broke 12 integration tests and 23 unit tests: Pass 2 rules no longer found Pass 1's builds, so rollups, finer-aggregate state merges, scan-widened shared KLL builds, shared UnivMon at the joint size and shared builds at a covering size disappeared from the memo. So this PR also makes the smallest rule changes that keep those candidates:
Memo::is_stage0_group: the groups Stage 0 produced from the queries' DAG), through the added groups; Pass 1's intermediate groups are not rewritten on their own.build_down_to_stage0_groups) reads only Stage 0 source groups, reached through added groups.build_of→BuildPlace::InputGroup); the rollup lands on that consumer (the estimate or finalize group).BuildPlace::InputGroup): the resized build's group is found or added (originSharedBuild: rule 9 has no origin of its own), and the copied consumer reads it.HashConsingTable; a private build equal to an existing group's node is read from that group, whose own candidate is raised to the consumers' most budget parts.The rules still emit chains (tumbling windows, widened scans, rollups, configuration consumers,
SharedBuildconsumers); switching each to one operator over groups is 2a-2..k.New abstractions not in #1207 or the doc: decisions (2026-11-05)
Per #1207's interface policy, these names are not defined by #1207, #1203 or section 3 of
memo-groups-properties-requirements.md. #1207's steps 1a–6 merge into main as one merge unit (#1207, Order), so an interim abstraction may stay in this PR only when a later PR of the unit deletes it; none reaches main. Rows marked Pending approval are not deleted by any PR in the unit and need the user's decision.Memo::groups+Memo::new,groups(),groups_mut()(test-support),push_group, privateMemoGroupLookupMemo::find_or_add_group(Operator<MemoGroupId>), θ out of the key)Memo::group_with_equal_node,Memo::add_queries,Memo::rebuild_parentsfind_or_add_group; keep the derivedqueries/parentsMemo::find_or_add_group)Memo::group_whose_node_isMemo::is_stage0_group+stage0_group_count;added_groups.rsfind_or_add_group(node tree),add_group,over_child_groupsown_candidate_sizing.rs(size_own_candidate_for,find_or_add_sized_group), the parts argument,AddPass1CandidatesError::Sizingaccuracy_budget_partsBuildPlace::{Private, InputGroup},build_of(wasbuild_read_by),build_down_to_stage0_groups,builds_beneath,input_group_buildBuildPlace::BeneathGroups,StateNeed::placeNodeToStructuralIdCache(pre-existing)asap_types::stages::hash_consing(HashConsingTable,same_value)find_or_add_group; Stage 0's equalitymemo_population→add_pass1_candidates,try_populate_pass1→try_add_pass1_candidates,CandidatePopulationError→AddPass1CandidatesError,check_unexpanded_memo→check_group_references_valid_and_acycliccheck_group_references_valid_and_acyclicrenamed again at the user's request: it checks that group references are in range and consistent and that the memo has no cycle)shared_configuration_readers.rs,BuildReadersHow
find_or_add_group(memo, node, queries, origin, accuracy_budget_parts) -> MemoGroupId(Stage 1, crate-private) returns the existing group whose node equalsnode(Stage 0's equality), after addingqueriesto it and every group below it; otherwise it adds a group fornodeover its children's groups (over_child_groups, which finds or adds each child the same way) and returns that group. It never copies a group.Memoknows nothing of accuracy: Stage 1'sfind_or_add_sized_group(rule 9, step 10) raises the budget parts of a group that already existed (size_own_candidate_for).Memo.groupsis private; every group is added throughMemo::new(dag)/push_group, which index it eagerly, sogroup_whose_node_is(the sameRc) andgroup_with_equal_node(Stage 0's equality) are&selflookups. A lookup does not keep the node alive: the hash cache holds only the nodes added as groups and their descendants.add_candidates_to_initial_groups/target_candidates): compose → size (size_for_failure_probability) → remove exact guarantees below the root →over_child_groups→ the candidate is the root's operator over those groups; the added groups it reads through are raised to its budget parts. At the endrebuild_parents.Memo::add_queriesandMemo::rebuild_parents, used byfind_or_add_group, Pass 1 and step 10.Representation / rules / enumeration
CandidateForMemoGroup { fused_groups, root_node, input_groups, origin, accuracy_budget_parts }is unchanged. Every Pass 1 candidate'sroot_nodeis one operator whose children are itsinput_groups' logical nodes. Pass 2 candidates may still be chains.Memo's groups are private behindgroups()andpush_group, with a lookup by node (address and Stage 0 equality).CandidateExpression { operator: Operator<MemoGroupId> }is not added yet: Pass 2 rules still emit chains, and Stage 2 reads each candidate'sroot_node(including its own schema and guarantee). It replacesroot_node+input_groupsin the PR that deletes private nodes.root_nodeandinput_groupsstay: Pass 2 rules and Stage 2'scatalog/inventory.rsread them.find_or_add_group. Pass 2 rules are unchanged except the rows in the table above.pass2/memo/enumeration.rsand Stage 3 (crates/plan-selection) are not touched by this diff. Stage 2's private-node path and Stage 3'sResultSlot::PrivateNodeare still used by the Pass 2 chains; their deletion waits for Physical properties: replace Materialization with timing × lifetime (None / OneEvaluation / AcrossEvaluations) and a schedule for shareable results, with r^phys and α^phys #1200 PR 1.Key code interfaces added / modified
crates/types/src/stages/candidates.rscrates/types/src/stages/hash_consing.rs(moved fromlogical-asap-optimization/src/stage0/cse.rs)crates/logical-asap-optimization/src/find_or_add_group.rs(crate-internal; Stage 1 only, so not onMemo)crates/logical-asap-optimization/src/own_candidate_sizing.rs(crate-internal)crates/logical-asap-optimization/src/added_groups.rs(interim Stage 1 helpers, re-exported throughtest_support)crates/logical-asap-optimization/src/pass1/add_pass1_candidates.rs(wasmemo_population.rs)crates/logical-asap-optimization/src/pass1/logical_memo_candidates.rscrates/logical-asap-optimization/src/pass2/memo/state_groups.rs(crate-internal)Usage. One
asap_plannerrun on the example workload (kll.json: the two queries above,max_error 0.01, failure probability 0.001, every 10 s,latency1000 series every 15 s):Major algorithm changes
Pass 1 candidate population.
over_groups); equal builds of different queries were separate private nodes.over_groups) → after population, size every candidate's chain.368d34c); tests below.Shared summary-state step (10).
BuildFingerprint) plussame_concrete_build; a newSharedBuildgroup for each build two or more candidates make.HashConsingTablegroups equal private builds; a build equal to an existing group's node reads that group (its own candidate raised to the consumers' most budget parts); otherwise a group is added throughfind_or_add_sized_group. The step runs once.Before this PR
Pass 1 candidates held private operator chains; Pass 2 found their builds inside the chains (rules 6, 9, 10) and factored equal builds into
SharedBuildgroups at the end, with its own build equality.After this PR
Pass 1 candidates are one operator over groups, equal Pass 1 builds are one group, and one node equality (Stage 0's) decides group identity everywhere. Pass 2's own chains and Stage 2/3's private-node path are unchanged.
Deleted
pass1/memo_population.rs(renamedadd_pass1_candidates.rs):over_groups,reach,pass1_candidates;try_populate_pass1/CandidatePopulationError(renamed).pass1/logical_memo_candidates.rs: the memo-widesize_for_failure_probability(sizing moved before the lookup) andPerComputationLogicalMemoCandidatesError::Sizing; variantPopulationrenamedAddPass1Candidates.stage0/cse.rs:InternTable,structural_hash,same_node,same_value,own_fields,NodeToStructuralHashCache(moved toasap_types::stages::hash_consing).pass2/memo/state_groups.rs:BuildFingerprint,local_fingerprint,build_fingerprint,same_concrete_build,add_shared_summary_state_groups_with_index(and its indexed-vs-linear test), the hand-written parent rebuild and query propagation (nowMemomethods),Pass2RuleError::AlreadyExpanded;check_unexpanded_memorenamedcheck_group_references_valid_and_acyclic.window_candidates.rs:group_of_node(nowMemo::group_whose_node_is);scan_labels.rs: its pointer-to-group map and its own build walk (nowMemo::group_whose_node_is/groups_read_through_added_groups/state_groups::builds_beneath, the walkdirect_input_buildsalso uses), and its use ofsimple_build.pass1/add_pass1_candidates.rs:over_found_or_added_groups(nowover_child_groups, whichfind_or_add_groupuses too).state_groups::simple_buildandinput_group_build(nowbuild_of),scan_labels::build_down_to_workload_groups(nowstate_groups::build_down_to_stage0_groups, shared withbuild_of);added_groups::workload_groups(nowMemo::is_stage0_group);Memo::find_or_add_group/Memo::over_child_groups(now Stage 1 functions); the copied own-fields tuple ofhash_consing::looked_up_structural_hash(nowstructural_hash_with); devtoolsstage0_memo_groups(inlined).GroupFoundOrAdded(find_or_add_groupreturns the group;find_or_add_sized_groupraises parts);Memo::new's separate groups / query roots / candidates arguments (now the Stage 0 DAG) and Pass 1's overwrite ofmemo.candidates; theRefCellaround the memo's hash-consing table.shared_summary_state_step_is_idempotent(step 10 runs once).stage0/cse.rs's fourstructural_hashtests, moved tohash_consing.rs.state_groups::multiple_private_states_can_share_only_the_common_producer(Pass 1 has no private states) replaced bymultiple_states_share_only_the_common_state; theadded_reader_candidatescounter of the state-group fixture.Plan cost, Stage 2 candidates and planning time
corpus_snapshots+sketch_plan_snapshots: no snapshot changed (UPDATE_SNAPSHOTS=1left every file unchanged), so no selected cost changed.asap_planner --run-until stage3wall time per snapshot input, main (82165ae) vs this branch, release builds on one host, best of two runs:Same or lower everywhere (#1203's requirement); the four lower inputs lose 16 candidates each because equal builds are one group. Stage 2 candidates are the
Cn:lines ofasap_planner --run-until stage2 --format text.Second review fixes
One build finder.
state_groups::build_of(memo, candidate) -> Option<(build, BuildPlace)>replacessimple_buildandinput_group_build. It looks in the same order the callers did and says where it found the build:SummaryAgginput group, which its root reads directly;Each rule keeps the places it handled before: rule 9 and rollups take
PrivateandInputGroup, window rollupsPrivate. Scan widening's lookup (build_down_to_workload_groups) moves tostate_groups::build_down_to_stage0_groups, which is alsobuild_of's third look.build_of. It changed plans. Onavg by (job) (avg_over_time(latency[5m]))withavg by (job, region)andavg by (region), a tumbling-window candidate has a private chain plus aSummaryAggover a privateProject.build_of's first look counts only the chain, a build over input groups, and finds one build. Scan widening needs the only build and skips the candidate. On scan widening's lookup, Pass 2 kept 4 widened-scan candidates; onbuild_ofit made 244. So scan widening keeps its own lookup, now shared.Stage 0 groups computed once.
Memostores how many groups Stage 0 produced (Memo::new), soMemo::is_stage0_group(id)isid < n.added_groups::workload_groups(a walk from the query roots, redone per rule and per Pass 1 candidate) is deleted. Before deleting it, every Pass 1 run in the tests below checked that the walk andid < ngive the same set.Stage 1-only helpers off
Memo.find_or_add_groupandover_child_groupscall each other, so both move tological-asap-optimization/src/find_or_add_group.rsas crate-private functions.find_or_add_sized_grouplooks the node up once (it used to look it up, then callfind_or_add_group, which looked it up again).Hash consing.
looked_up_structural_hashreusedstructural_hash's own-fields tuple by copying it. Both now callstructural_hash_with(node, child_hash).devtools.
stage0_memo_groupsis inlined (Memo::new(dag)).Plans unchanged: snapshots and both memo texts byte-identical to
b080c96, and the Pass 2 memo text of 35 other workloads (see Verification).Notation (formulation → code)
MemoGroup, idMemoGroupIdCandidateForMemoGrouproot_node(one operator for Pass 1 candidates) +input_groups(by child position)find_or_add_group,add_group,over_child_groupsqueriesindices;Memo::add_queriesConcepts not in the formulation
Interim names with no formulation term, for the user to name or drop:
Memo::is_stage0_group,stage0_group_count,GroupsReadThroughAddedGroups::stage0_groups): the groups Stage 0 produced from the queries' DAG. Rules 1 and 2 still rewrite only these.GroupsReadThroughAddedGroups::added_groups,groups_read_through_added_groups): the groups Pass 1 and Pass 2 add beneath them.BuildPlace::{Private, InputGroup, BeneathGroups}): private nodes go away with 2a (one operator per candidate). The enum lasts until rule 9 and the rollups match over groups only.accuracy_budget_parts,size_own_candidate_for,find_or_add_sized_group): sizing for a share of a budget. 2b deletes it (Logical memo candidate as (one operator over groups, logical properties θ) with a requirement per input; no private nodes #1203: no budget shares).OriginOfCandidateForMemoGroup): which rule made a candidate.Second review verification (
8182edf)cargo test -p asap-types --lib: 146 passed.cargo test -p asap-logical-asap-optimization --lib: 198 passed.cargo test -p asap-integration-tests --test integration -- memo_enumeration reuse_rules operator_sharing sharing_issue_regressions finer_aggregate_reuse planner_layering_example having_shared_aggregate stage1_logical_asap_optimization joint_search_equivalence sketch_rollup rollup_controlled production_rollup_workloads: 154 passed, 23 ignored (pre-existing). An earlier run used a temporary check thatworkload_groups(memo)equals{id < stage0_group_count}.cargo test -p asap-devtools --lib: 18 passed.--test devtools -- asap_planner:: asap_planner_acceptance:: memo_pricing::: 56 passed, 1 ignored.UPDATE_SNAPSHOTS=1 cargo test --release -p asap-devtools --test corpus_snapshots -- --include-ignored: 9 passed, no file changed.asap_planner --run-until stage1_pass2 --format texton the KLL and scan-widening examples: byte-identical tob080c96.asap_planner --run-until stage1_pass2 --format text: byte-identical to the pre-review headb080c96on 35 workloads besides the two examples: the reviewer's 20 workloads, six rollup / scan-widening /avgworkloads, five earlier finer-aggregate, rollup, top-k and scan-KLL examples, andrandom-mixed-seed-862workloads 01–04.cargo fmt --all;cargo clippy --workspace --all-targets -- -D warnings: clean.Consumer naming (
0c6d8b1, 2026-11-05)build_read_byisbuild_of; prose says consumer for the candidate that reads an input and query for the evaluation's sense (the concepts doc's terms). No behaviour change.cargo fmt --all -- --check;cargo clippy --workspace --all-targets -- -D warnings: clean.cargo test -p asap-logical-asap-optimization --lib: 198 passed.Check renamed (
2cd0aaa, 2026-11-05)check_memo_well_formed→check_group_references_valid_and_acyclic(the user's request).cargo clippy --workspace --all-targets -- -D warnings: clean;cargo test -p asap-logical-asap-optimization --lib: 198 passed.Viewer
viewer_contracttests pass on this branch. The viewer document is not updated for the new group shape (decided: viewer ignored for now).Verification
cargo test -p asap-types: 257 passed (the fourstructural_hashtests moved here fromstage0/cse.rs).cargo test -p asap-logical-asap-optimization --lib: 198 passed (new:equal_k_builds_with_another_failure_probability_or_guarantee_are_other_groups,a_private_build_equal_to_a_group_raises_its_budget_parts).cargo test -p asap-physical-asap-optimization: 29 passed.cargo test -p asap-plan-selection --lib: 142 passed.cargo test -p asap-devtools --lib: 18 passed (planning_stages_text::prints_every_stage_of_a_repeating_rate_query: same plan and cost as main, the selected choice now also names the two groups Pass 1 adds).cargo test -p asap-integration-tests --test integration -- memo_enumeration reuse_rules operator_sharing sharing_issue_regressions finer_aggregate_reuse planner_layering_example having_shared_aggregate: 117 passed, 23 ignored (pre-existing).cargo test -p asap-devtools --test devtools -- asap_planner:: asap_planner_acceptance:: memo_pricing:: viewer_contract::: 58 passed, 1 ignored (pre-existing).UPDATE_SNAPSHOTS=1 cargo test --release -p asap-devtools --test corpus_snapshots -- --include-ignored: 9 passed, no snapshot file changed.cargo fmt --all;cargo clippy --workspace --all-targets -- -D warnings: clean.b080c96:test,format-and-lintandCheck PR titlepass.asap_planner --run-until stage1_pass2 --format texton the KLL example and on a scan-widening example (avg by (job)/avg by (region)of quantiles overlatency[5m]) gives the same groups and candidates as368d34c; onb080c96the output is byte-identical toaf2a90d's for both examples.memo_enumeration,asap_planner::memo_section_*,finer_aggregate_reuse::rollups,sharing_issue_regressions::sketch_rollup_*,planner_layering_example2memo choices,planning::select_rollup(used bysketch_rollup_planning: a shared plan now also chooses each query's summary candidate above its consumer),test_support::shared_summary_state_groups).Left for 2a-2..k
find_or_add_group: tumbling windows (rule 1), scan widening (2), summary inputs (3), top-k (4), rollups (6, 7), summary results (8), state configurations (9), and the shared summary-state step (10). With them, delete their own group-adding code and equalities:scan_labels::intern_inputs/same_source_node,reuse::append_producer/reuse::same_node, step 10'sSharedBuildgroups,SummaryResult,direct_input_builds,same_range_input, andMemo::is_stage0_group/groups_read_through_added_groupsonce no rule needs to tell added groups apart.NodeToStructuralIdCachein three places: the composed root against the group's own node and the candidate dedupe key (add_pass1_candidates.rs), andPerComputationCandidates::walked. That id deliberately leaves out the schema, guarantee, timing and materialization composition derives (a composed root carries the exact guarantee and may close the label set where Stage 0's node has neither), so Stage 0's equality would not match there. Moving them to one equality is left for this series.CandidateExpression { operator: Operator<MemoGroupId> }in place ofroot_node+input_groups, and delete Stage 2's private nodes and Stage 3'sResultSlot::PrivateNode(after Physical properties: replace Materialization with timing × lifetime (None / OneEvaluation / AcrossEvaluations) and a schedule for shareable results, with r^phys and α^phys #1200 PR 1).querieswith its own walk; it buildsVec<MemoGroup>before aMemoexists, so it does not useMemo::add_queriesyet.