Count a division by what it is, not by how it was written (#950) - #953
Merged
Conversation
CostModel.FewestDivisions tested the exponent's node -- Powf(_, Real { IsNegative:
true }) -- so in y ^ (1 * (-1)) * x, where the exponent is a Mulf, it fired on
nothing. The three writings of one value scored 1.003, 1.005 and 0.007, making the
form whose division the model could not see the cheapest of the three, under the
criterion whose whole job is to remove divisions. FewestRadicals had the same shape
of test and the same blind spot, so x ^ (2 ^ (-1)) was not a square root to it.
Both now ask what the exponent evaluates to. All three writings count one division,
and the smallest tree is cheapest, which is what the node term is there to settle.
Default and SmallestTree are deliberately untouched. They count how an expression is
written, and y ^ (1 * (-1)) * x really is a bigger tree -- for them the written node
is the question rather than an approximation of it. That split is now stated in the
type's remarks, because a caller writing their own model has to decide which of the
two kinds it is and nothing in the library can decide it for them.
Simplify's output does not change: twelve division-heavy inputs under both models,
measured on a build of each, twenty-four results, byte-identical. Simplify scores
candidates it has already evaluated. What reaches the gap is a caller scoring
expressions it did not build -- extraction on an e-graph -- which is how it was found.
Suite 7257 passed, 0 failed. Two of the three new tests fail without the change; the
third passes either way by design, pinning that the spelling models were not widened.
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.
Closes #950.
FewestDivisionstested the exponent's node —Powf(_, Real { IsNegative: true })— so iny ^ (1 * (-1)) * x, where the exponent is aMulf, it fired on nothing:FewestDivisionswasx / yy ^ (-1) * xy ^ (1 * (-1)) * xAll three are one value written three ways. The model rated the writing whose division it could not see as the cheapest of the three — under the criterion whose whole job is removing divisions.
FewestRadicalshad the same shape of test and the same blind spot, sox ^ (2 ^ (-1))was not a square root to it.Both now ask what the exponent evaluates to. One division each, and the smallest tree wins, which is what the node term exists to settle.
Why this and not the other two options
The issue listed three, and I originally leaned to documenting the precondition and leaving the models alone. The argument against that is a split the issue did not name:
SmallestTreeandDefaultcount spelling.y ^ (1 * (-1)) * xreally is a bigger tree. Reading the node as written is not an approximation of their question, it is their question. Evaluating insideCost(option 1) would therefore make these two wrong, not just slower.FewestDivisionsandFewestRadicalscount a mathematical feature. "Is there a division here" is a property of the value. For these, reading the written node is simply the wrong question.So the fix is per-model rather than global, and documentation alone would have left a built-in model preferring a hidden division. "A caller could write a wrong model too" argues for explaining the distinction — which this does, in the type's remarks — not for leaving a shipped model wrong.
DefaultandSmallestTreeare untouched, and there is a test pinning that.Behaviour
Simplify's output does not change. Measured rather than reasoned: twelve division-heavy inputs under both models, on a build of each side, twenty-four results, byte-identical.Simplifyscores candidates it has already evaluated, so it never reached the gap.What reaches it is a caller scoring expressions it did not build — extraction on an e-graph, where every writing of a value is a member of one e-class at once, which is how this was found. That is exactly the use
CostModel(#949, #746 item 52) exists to enable, which is why this is worth fixing rather than documenting.Recorded in
BREAKING-CHANGES.md:Costreturns different numbers for an expression with an unevaluated exponent.Evidence
SmallestTreestill counts spelling, so it is a guard against overreach rather than evidence for the fix, and it should not be read as a third supporting test.What it does not do
DefaultorSmallestTree, or the defaultSimplifypath.Costgenerally — a caller's own model still reads what it is given, and the remarks now say so and explain which of the two kinds it should be.Feature's node-term weighting.🤖 Generated with Claude Code