A canonicalisation framework on the rewrite graph - #1105
Merged
Conversation
… it changed #746 tier 2 asks for "a canonicalisation framework built on" the rewrite graph. `Transformation.CanonicalizationOverGraph(budget, widest)` is that: saturate under a rule set, then extract the least member of the root class under a total order. The order is the piece that had to be new. A cost model answers "which is nicer" with a double, so two forms it cannot separate tie, and a tie is settled by whichever was reached first -- an accident of traversal, which a canonical form cannot be built on. No double carries a total order on trees, so `EntityOrder.Canonical` is a comparison: size first, so the representative is the smallest member and canonicalising never enlarges, then structural, so it is total up to Equals. Deliberately not the printed form, since `(x + y) + a` and `x + (y + a)` print alike and are different trees. And deliberately not `Entity.SortHash`, which orders operands *within* a commutative chain -- a different question, and the two do not compete. `EGraph.ExtractLeast` is the same walk as `Extract` choosing by an order instead of a number, so the two share one recursion rather than two copies of a subtle one, over a struct selector under a generic constraint -- compiled in rather than dispatched, and no selection allocated per class. The saturation loop moves to `Saturation.Run`, shared with `EqualitySaturation`. Two transformations wanting it is the reason: they differ only in what they extract, not in what is charged, which rules can be skipped unattempted, or which matching path a rule takes. **Three things measured, two of which corrected what I had assumed.** Two rewrite chains run from each of 1458 generated expressions produced no divergent pair at all under Collects and Rearranges. Those rules are confluent on that corpus, so a rule pass already reaches the form a graph would, and over that ceiling this earns nothing. The growth ceiling is a much smaller knob than it looks. Of the sound rules, 26 collect, 17 rearrange, 9 expand and **270 are unjudged** -- their replacement is code, so nothing counted it. Over six pairs equal only through a larger intermediate form, Rearranges proved two, and Expands -- all nine expanding rules added -- proved *the same two*. Only admitting the unjudged rules proved a third. So `Unknown` is the widest stop rather than an excluded value: refusing it refuses 84% of the library. It stays a request a caller makes on purpose, and the default stays narrow, because the widest setting is where the harness's 7,147x blow-up lives. And the graph does not sort commutative operands: the rules that do are among the 270, so no ceiling below the widest admits them, and `x + y` and `y + x` canonicalised to themselves. `Transformation.Canonicalization` already sorts and flattens and is already measured idempotent, so it runs on each side of the graph step rather than being written again. `Saturation.ProvesEqual` is the half to rely on, and is not a transformation. Bringing two expressions separately to a canonical form and comparing decides equality only where the rules are confluent; asking in one graph merges everything reachable from either side and has no such gap. It wants a public home and a name chosen by someone who owns the surface, so it is internal here. Full suite: 8961 passed, 14 skipped (pre-existing), 0 failed. Two tests in the suite assert a wall-clock ceiling and trip under parallel contention rather than on cost (OneSidedLimitTest at 60s, VanishingTimesDivergingTest at 30s); both pass in isolation. Part of #746 tier 2.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#746 tier 2 asks for "a canonicalisation framework built on" the rewrite graph. This is that:
Transformation.CanonicalizationOverGraph(budget, widest)saturates under a rule set, then extractsthe least member of the root class under a total order.
Offered, not applied.
CanonicalForm.md§8 says wiring a canonical form intoSimplifyis a releasedecision of its own, and this does not take it.
The order is the piece that had to be new
A cost model answers "which is nicer" with a
double, so two forms it cannot separate tie — and atie is settled by whichever was reached first, an accident of traversal. A canonical form cannot be
built on that. No double carries a total order on trees, so
EntityOrder.Canonicalis a comparison:Equals.Deliberately not the printed form —
(x + y) + aandx + (y + a)print alike and are differenttrees. Deliberately not
Entity.SortHash, which orders operands within a commutative chain: adifferent question, and the two do not compete.
EGraph.ExtractLeastis the same walk asExtractchoosing by an order instead of a number. Theyshare one recursion rather than two copies of a subtle one, over a struct selector under a generic
constraint — compiled in rather than dispatched, and no selection allocated per class.
The saturation loop moves to
Saturation.Run, shared withEqualitySaturation. Two transformationswanting it is the reason: they differ only in what they extract, not in what is charged, which rules
can be skipped unattempted, or which matching path a rule takes.
Three things measured, two of which corrected what I had assumed
The safe rules are already confluent, so over that ceiling a graph earns nothing. Two rewrite
chains run from each of 1458 generated expressions produced no divergent pair at all under
CollectsandRearranges. A rule pass already reaches the form a graph would.The growth ceiling is a much smaller knob than it looks. Of the sound rules in
MatchedRules:CollectsRearrangesExpandsUnknownThose 270 are unjudged because their replacement is code rather than a written pattern, so nothing
counted it. Over six expression pairs equal only through a larger intermediate form:
RearrangesExpands— all nine expanding rules addedUnknownSo the dial that matters is not how far a rule may enlarge; it is whether rules nobody classified may
fire at all.
Unknownis therefore the widest stop rather than an excluded value — refusing itrefuses 84% of the library — and naming it as the widest setting makes it a request a caller has to
make on purpose. The default stays narrow, because the widest setting is where the harness's 7,147×
blow-up lives.
And the graph does not sort commutative operands. The rules that do are among the 270, so no
ceiling below the widest admits them, and
x + yandy + xcanonicalised to themselves.Transformation.Canonicalizationalready sorts and flattens and is already measured idempotent, so itruns on each side of the graph step rather than being written again.
ProvesEqualis the half to rely on, and it is not a transformationBringing two expressions separately to a canonical form and comparing decides equality only where
the rules are confluent. Otherwise one side reaches a form the other cannot, and two equal
expressions end in different forms having each been canonicalised correctly. Measured, on
(x + y) * (x - y)againstx ^ 2 - y ^ 2: the product's graph reaches the difference of squares,the difference of squares' graph does not reach the product, and the two forms are the same size — so
each canonicalises to itself.
Asking in one graph has no such gap: both are inserted, every equality the rules reach from either
is merged, and the question is whether they ended in one class. That does not depend on confluence,
only on reaching far enough within the budget.
Saturation.ProvesEqualis internal here. It wants a public home and a name chosen by someone whoowns the surface.
Tests
43 new, in
CanonicalExtractionTest: the order's totality and antisymmetry, that extraction neverenlarges, idempotence of the composed transformation, the budget being respected and reported, and the
confluence gap above as an explicit expectation rather than a surprise.
Full suite on this branch, rebased onto master after #1104: 8962 passed, 14 skipped, 0 failed.
Part of #746 tier 2.