Repository navigation
Forty-three rules say how they change the size of what they match (#825) - #1161
Merged
Merged
Conversation
Fifteen rearrange and five collect, each argued from the shape of the replacement rather than from the corpus, and each checked against it as well. The rearrangements are the ones where operators map one for one and every hole is used once on both sides: the five quotient re-associations, (a^n)^m = a^(n*m), the negation-literal swaps where a literal is replaced by its magnitude and one operator by another, and a + (c*b) = a - (-c)*b. The collections are the ones where something matched twice is written once. In a^n * a^m = a^(n+m) the base appears twice on the left and once on the right, so the delta is -(1 + |a|): two nodes for a base of one node, and more for a larger one. The same figure covers a^n / a^m, a^n * b^n, a^n / b^n and k*p +- k*q. That argument is stronger than the measurement, which reported -2 throughout only because the bases the corpus generates are single nodes. Unlike the seven Expands of the previous change, these do alter what runs: Saturation.RulesUpTo builds its set as the rules up to Rearranges, so all twenty join the rules equality saturation fires. Full suite green either way. MostSoundRulesHaveNoJudgedGrowth had to move and is a case of a test doing its job. It asserted that fewer than a quarter of sound rules carry a judged growth, with a message saying the ceiling might now be worth more than it was; the share went from 69 of 324 to 89. Re-pinned at "not the majority", since the share is meant to rise while #825 lands and what the test should protect is that the ceiling still excludes something. One rule with clean evidence is deliberately left alone. `a-reciprocal-rational-factor-is-a-division` measures 0 across all 17 of its firings, which is the same evidence the fifteen above have. But IsWholeReciprocal accepts a rational literal like 1/3, which is one node, and a written 1/c, which is three, so the delta is 0 in one spelling and -2 in the other and no single label is true of it. WritingARule.md says to leave such a rule Unknown. Full suite 9552 passed, 0 failed. Part of #825. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura
cosec(a) * sin(a), tan(a) * cotan(a) and sin(a)^2 + cos(a)^2 all become the literal 1, and arcsin(a) + arccos(a) becomes pi/2. In each the pattern is a fixed frame around two copies of the angle and the replacement holds no operand at all, so the delta is -(k + 2|a|) for a frame of k nodes: at most -2 for the arcsine pair, -4 for the two products and -8 for the Pythagorean identity, and further the larger the angle is. Nothing they can be filled with makes them grow. Two rules that look like the same shape are left alone, and both are worth naming because each measures as cleanly as the four above. `a and False = False` and `a or True = True` do not replace with a constant. The replacement is the constant wrapped in the domain conditions of both operands, and a large operand's condition can be larger than the pattern it replaces, so no label is safe to claim. The fifteen rules that drop a negative divisor or factor out of a comparison with zero measure -2 at every point the corpus reaches, and counting the pattern against the replacement gives -(|k| + |zero|), which is at most -2 as well. Both say Collects and both are wrong: the replacement is built with `<` or `>`, which chain, so on ((x > y) / (-2)) > 0 the seven nodes of the pattern become the seven of `x > y and y < 0` and the delta is 0. The comment on `a-equals-with-zero-on-the-left-turns-round` already says that `EqualTo` does not chain the way the comparison operators do, and its comparison twins were left undeclared for exactly this reason. Full suite 9552 passed, 0 failed. Part of #825. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura
`c * v + d * v = (c + d) * v` and its subtraction twin go from seven nodes to three exactly, for every input: the pattern's three holes are a Number, a Number and a Variable, each of which is a leaf, and the sum of the two numbers is folded as the replacement is built, so nothing here can be filled with anything larger. `a - (a - b) = b` and `(a - b) - a = -b` lose the repeated operand twice over: -(2 + 2|a|) for the first and -2|a| for the second, at most -4 and -2 and further the larger that operand is. Two boolean distributions are deliberately not declared, and the reason is recorded beside them rather than left as a silent Unknown, because the growth is not what is wrong with them. The count for `((k and p) or (k and q)) = (k and (p or q))` is plainly -(1 + |k|), so Collects is right -- and declaring it is what makes equality saturation fire the rule, which it never has, since RulesUpTo builds its set as the rules up to Rearranges. Fired, it takes `a and b or a and not b` to `a and (b or not b)`, and EqualitySaturationNeverChangesTheValueItClaimsToPreserve catches the value moving at 0.37. The rule is marked Sound and guarded with IsLogic, which passes a free variable that may later be substituted with a number. That is a defect in the rule and not in the declaration, so it is filed as #1162 rather than worked around here. It is also the general lesson of this batch: a growth declaration moves a rule into the set saturation runs, so it can expose a rule whose soundness was wrong all along, and Unknown had been standing in for a guard nobody intended it to be. Full suite 9552 passed, 0 failed. Part of #825. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura
The backlog of undeclared growths is worked by counting the pattern against the replacement in terms of the holes, and the answer falls into a few shapes: a hole matched twice and written once collects by -(1 + |a|), a replacement holding no hole at all collects by the whole pattern, and operators mapping one for one with every hole used once rearranges. Those are written down, since the first of them is a stronger claim than the corpus can show -- the corpus fills its holes with single nodes. So are the four shapes that measure a clean constant delta over the corpus and are false anyway: a replacement built with the chaining comparison operators, one that attaches a Provided sized by the operands, a hole that can be filled by two spellings of one thing, and a repeated hole that gets squared. Each was found by walking into it, and the corpus agreed with all four. And the note that a declaration can expose a rule whose soundness was wrong, since writing a growth down is what moves a rule into the set equality saturation runs, which may be the first time it has ever run. That is how #1162 was found. Part of #825. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura
Nine trigonometric. Three collapse to the literal 1 -- sec(a) * cos(a), sec(a)^2 - tan(a)^2 and cosec(a)^2 - cotan(a)^2 -- where the pattern is a fixed frame around two copies of the angle and the replacement holds no operand at all, so the delta is -(k + 2|a|). Four are the Pythagorean rearrangements, 1 - cos(a)^2 = sin(a)^2 and its three relatives, where five nodes around the angle become three: exactly -2, for every input. Two are operator-for-operator swaps, x / sec(a) = x * cos(a) and its cosecant twin. Six logarithmic. log(a, b^n) = n * log(a, b) turns a logarithm and a power into a product and a logarithm, one node for one; the two reciprocal rules trade the two nodes of a reciprocal for the two of a negation; log(1/a, 1/b) = log(a, b) drops both reciprocals for exactly -4; and the two that gather logarithms of one base match that base twice and write it once, so the delta is -(1 + |a|). Ten more rules were read and left alone, all of one shape. The Power set's family that raises or lowers an exponent -- a^n * a = a^(n+1), a / a^n, a^n / a, a / b / b -- has delta 1 - |a|: zero where the repeated operand is a leaf and negative where it is anything larger, so no label is true of it. That is the fourth of the shapes written into WritingARule.md in this branch, and it classified all ten by inspection rather than by measurement. Verified with the targeted tests rather than the whole suite: 58 across the growth check, the census, the rule metadata and the canonical extraction, and 266 transformation tests including EqualitySaturationNeverChangesTheValueItClaimsToPreserve, which is the one that matters for a declaration that puts a rule into SafeRules -- it is what caught #1162. The full suite was green on the previous commit of this branch and has not been re-run on this one. Part of #825. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura
Forty-odd rules take a factor or a divisor out of a comparison with zero, and five of every six of them cannot say their growth: the replacement is built with `<` or `>`, which chain, so `((x > y) / (-2)) > 0` rewrites to seven nodes from seven and the delta is 0 rather than the -2 the count suggests. The sixth is built with `EqualTo`, which is a bare `Equalsf` and does not chain. For those seven the count holds: the factor or divisor goes, the zero on the right goes, and nothing arrives, so the delta is -(|k| + |zero|) -- at most -2 and further the larger either is. `(a ^ p = 0) = (a = 0)` is the same shape one step along, losing the power and its exponent for -(1 + |p|). That distinction is not new. It is the reason `a-equals-with-zero-on-the-left-turns-round` carries a growth and its five comparison twins do not, and the comment there says so. Reading it as "the comparison rules are hopeless" rather than "`EqualTo` is the one that is safe" left seven declarable rules undeclared. Verified with the targeted tests: 320 across the growth check, the census, the canonical extraction and every transformation test, including EqualitySaturationNeverChangesTheValueItClaimsToPreserve, which is what a declaration into SafeRules has to answer to. Part of #825. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura
Eight rules take a negative multiple out of a function, and they split by the parity of the function into two arguments and no more. The even ones -- cos, sec and abs -- replace the negative literal by its magnitude, one node for one, and move nothing else: exactly 0 whatever the argument is. The odd ones -- sin, tan, cotan and cosec -- do the same and add the two nodes of the negation they put round the whole of it: exactly +2. abs(a) * abs(b) = abs(a * b) and its quotient twin turn two absolute values into one and leave the operator alone: exactly -1. Three more were read and left alone, all of the shape already written into WritingARule.md as the fourth trap. `k + k * q = k * (1 + q)` is 1 - |k|, `a + a / b = a * (1 + 1 / b)` is 3 - |a|, and `a * a = a ^ 2` is 1 - |a|: each is zero or positive where the repeated operand is a leaf and negative where it is larger, so no one label is true of any of them. The corpus measures every one of them as a clean constant, because the operands it generates are single nodes. Verified with the targeted tests: 320 across the growth check, the census, the canonical extraction and every transformation test. Part of #825. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura
The four rules that turn an inverse function of a reciprocal into its co-function -- arcsin(1/c) = arccosec(c) and its three relatives -- swap one function for another and leave the quotient a quotient with its operands the other way round, so the two sides are the same size whatever they are filled with. cos(a)^2 - sin(a)^2 = cos(2a) takes seven nodes around two copies of the angle down to three around one, so the delta is -(4 + |a|). Its sibling that writes the difference the other way round keeps the seven and adds the two of a negation: exactly +2. Verified with the targeted tests: 320 green. Part of #825. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura
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.
Fifteen rearrange and nine collect. 255 rules were at
Unknown; 231 are.Each is argued from the shape of the replacement, and checked against the corpus as well — the
argument is what licenses the declaration, the corpus is what would refute it.
The rearrangements
Operators map one for one and every hole is used exactly once on both sides:
and the negation-literal swaps —
x + (-a) = x - a,(-a) - (-b) = b - a,(-a) * (-b) = a * b,(-a) / (-b) = a / b,x - (-a) = x + a,a + (c * b) = a - (-c) * b— where one operator becomesanother and a literal is replaced by its magnitude, one node for one.
The collections
Something matched twice, written once.
a ^ n * a ^ m = a ^ (n + m)matches its base twice andwrites it once, so the delta is
-(1 + |a|): two nodes for a base of one node, more for a largerone. The same covers
a^n / a^m,a^n * b^n,a^n / b^nandk*p +- k*q = k*(p +- q). Anda + (-1) * b = a - b, where the-1and its product go entirely.Something collapsed to a constant.
cosec(a) * sin(a),tan(a) * cotan(a)andsin(a)^2 + cos(a)^2become the literal1;arcsin(a) + arccos(a)becomespi/2. The pattern isa fixed frame around two copies of the angle and the replacement holds no operand at all, so the
delta is
-(k + 2|a|)— at most −2, −4 and −8 respectively, and further the larger the angle.The corpus reported a flat number for all of these, and the argument is stronger than the
measurement — it saw
-2for the power rules only because the bases it generates are singlenodes. A corpus can refute a claim; it cannot tell you how strong the true one is.
These change what runs
Unlike the seven
Expandsof #1159, all twenty-four sit insideSafeRules—Saturation.RulesUpTobuilds its set as the rules up to
Rearranges— so they join the rules equality saturation fires.Three left alone, each measuring as cleanly as the ones above
This is the part worth reviewing. All three look declarable from the data and are not.
a-reciprocal-rational-factor-is-a-division—a * (1/c) = a/c, 0 across all 17 firings.IsWholeReciprocalaccepts a rational literal like1/3, which is one node, and a written1 / c,which is three. The delta is 0 in one spelling and −2 in the other.
a and False = Falseanda or True = True— −2 wherever they fire. But the replacement is notthe constant; it is the constant wrapped in
.Provided(a.DomainCondition).Provided(b.DomainCondition),and a large operand's domain condition can exceed the pattern it replaces.
The fifteen rules that drop a negative divisor or factor out of a comparison with zero — −2 at
every point the corpus reaches, and the pattern count gives
-(|k| + |zero|) <= -2. Both saidCollects. Both are wrong: the replacement is built with<or>, which chain, so on((x > y) / (-2)) > 0the seven nodes of the pattern become the seven ofx > y and y < 0and thedelta is 0.
That last one is not a near miss — the comment on
a-equals-with-zero-on-the-left-turns-roundalready records that
EqualTodoes not chain "the wayEqualizesand the comparison operators do",and its comparison twins were left undeclared for precisely this reason. #1160 adds the shapes that
let the corpus refute it too.
Verification
Full suite 9552 passed, 0 failed, 14 skipped — and green against the wider corpus of #1160,
which exercises 194 rules rather than 84. That is the stronger check of these twenty-four; this PR
does not depend on it and the two touch no file in common.
MostSoundRulesHaveNoJudgedGrowthhad to move: it asserted that fewer than a quarter of sound rulescarry a judged growth, and the share went from 69 of 324 to 93. Re-pinned at not the majority,
since the share is meant to rise while #825 lands.
Part of #825.