Skip to content

nested-membership rewrite rule: nested-Cardinality O(n²) → single-Cardinality O(n) - #5438

Closed
gunbai-bot[bot] wants to merge 7 commits into
mainfrom
session/proud-ferret-16-rule1
Closed

gunbai-bot[bot] wants to merge 7 commits into
mainfrom
session/proud-ferret-16-rule1

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Context

Follow-up to #5437 (cost-lens AlternativeCost floor + comprep bridge). Depends on that PR for comprep_source_resolved_root (the pipeline bridge). Rule 1 of the D2 complexity-reduction catalog.

What this adds

Rule: nested_membership (src/v2/lens/rewrite/nested_membership.dag)

Rewrites a nested Cardinality (outer wraps inner — O(n·m)) to a single Cardinality with the inner node's children hoisted directly (O(n)). Models: precompute the membership set once; replace the inner O(n) scan with O(1) lookup.

Precondition: outer has exactly one positional child (= inner Cardinality) and no Named children (no mutation coordinate in outer body — required for safe hoisting).

Test file: src/v2/lens/rewrite/nested_membership_test.dag — 4-witness matrix + pipeline guard:

  • (a) rewrite fires: try_nested_membership_rewrite(nested_slow_input) = Present
  • (b) class drop: ClassPolynomial(degree=2) → ClassLinear (cost_lens + complexity_lens both checked)
  • (c) correctness: rewritten node's children == inner_card.children exactly
  • (d) non-firing control: outer Cardinality with a Named edge → Absent
  • (e) pipeline guard: comprep bridge resolves MVP1 loop source (bridge proved live as real consumer)

🟡 gap marker in src/v2/lens/cost.dag — records that Instantiation → unit_cost() (SequentialCost) means complexity_lens cannot produce ClassExponential for naive recursive functions. Blocks the naive-recursion→memoize rule detection. Dissolve-on: Instantiation base cost models recursion depth (new modeling, own runway).

Test polarities

  • (a) goes RED without the try_nested_membership_rewrite function itself.
  • (b) goes RED if cost_lens does not fold nested Cardinality to ProductCost(Linear, Linear).
  • (d) goes RED if the Named-edge mutation-guard is absent (over-eager fire).
  • (e) goes RED if any stage of the pipeline chain faults.

Grammar gap (documented)

MVP1 grammar cannot parse nested-loop source text (e.g., fn outer() -> Int { loop loop 1 }). Witnesses (a)-(d) use hand-constructed Node trees. Dissolve-on marker attached; grammar follow-up deferred.

Test plan

🤖 Generated with Claude Code

briansrls and others added 6 commits June 21, 2026 04:32
Pins the compose_child_cost AlternativeCost fix from the WIP commit:
(1) bug-fix: Disj([Atom, Atom]) -> unit_cost() (was zero_cost() without floor)
(2) control: symbolic_max(0, 0) == 0 — fixes the compose layer, not symbolic_max itself.
Two checks together uniquely identify the correct fix vs wrong-fix alternatives.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Factors out the four-stage COMPREP pipeline chain (wave-1 producers all duplicate
it) into a single parameterised fn comprep_source_resolved_root(source_text,
parse_root, production_name, file) -> Outcome<Node>. Grammar validation is
fail-closed before any tokenize work. D2 seed-rule test-subject producers will
call this directly — no new pipeline fork.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nality rewrite rule

Adds Rule 1 (nested-membership → set) D2 seed rule with full witness coverage:
  (a) rewrite fires on nested Cardinality input
  (b) class drop confirmed: ClassPolynomial(2) → ClassLinear
  (c) rewrite correctness: output children equal inner.children exactly
  (d) non-firing control: Named edge on outer → Absent
  (e) pipeline guard: comprep bridge resolves MVP1 loop source (bridge proved live)

Grammar gap logged (nested-loop source not parseable in MVP1); dissolve-on marker attached.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lCost (unmodeled)

Records that Instantiation currently maps to unit_cost() with SequentialCost, so
complexity_lens cannot produce ClassExponential for naive recursive functions.
This blocks the naive-recursion→memoize rewrite detection. Dissolve-on: new
modeling that grounds Instantiation base cost in recursion depth (needs own runway).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…st_inner_cardinality

`inner` from list_at_optional was staying generic T at the `match inner.kind` site,
causing `no field 'kind' on type 'T'`. Extract the inner.kind check into a typed
helper fn try_hoist_inner_cardinality(inner: Node) — passing `inner` to a Node-typed
parameter forces T = Node unification, same pattern as node_is_callee_reference in
node_query.dag.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor Author

Paused, relocated to §5 post-stability per operator §3 reframe; branch session/proud-ferret-16-rule1 preserves nested_membership + the Instantiation→ExponentialCost gap marker for §5 pickup.

@gunbai-bot gunbai-bot Bot closed this Jun 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant