Skip to content

Forty-three rules say how they change the size of what they match (#825) - #1161

Merged
Rafael-SOWNet merged 8 commits into
masterfrom
growth-declared-batch2
Sep 4, 2026
Merged

Rafael-SOWNet merged 8 commits into
masterfrom
growth-declared-batch2

Conversation

@Rafael-SOWNet

@Rafael-SOWNet Rafael-SOWNet commented Sep 4, 2026 •

Copy link
Copy Markdown
Member

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:

(a / b) * c = (a * c) / b        a / (b / c) = a * c / b        a / b / c = a / (b * c)
a * (b / c) = (a * b) / c        (a / b) * (c / d) = (a * c) / (b * d)
(a ^ n) ^ m = a ^ (n * m)        a ^ (-1) = 1 / a

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 becomes
another 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 and
writes it once, so the delta is -(1 + |a|): two nodes for a base of one node, more for a larger
one. The same covers a^n / a^m, a^n * b^n, a^n / b^n and k*p +- k*q = k*(p +- q). And
a + (-1) * b = a - b, where the -1 and its product go entirely.

Something collapsed to a constant. cosec(a) * sin(a), tan(a) * cotan(a) and
sin(a)^2 + cos(a)^2 become the literal 1; arcsin(a) + arccos(a) becomes pi/2. 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|) — 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 -2 for the power rules only because the bases it generates are single
nodes. A corpus can refute a claim; it cannot tell you how strong the true one is.

These change what runs

Unlike the seven Expands of #1159, all twenty-four sit inside SafeRules — Saturation.RulesUpTo
builds 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.
IsWholeReciprocal accepts a rational literal like 1/3, which is one node, and a written 1 / c,
which is three. The delta is 0 in one spelling and −2 in the other.

a and False = False and a or True = True — −2 wherever they fire. But the replacement is not
the 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 said
Collects. 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.

That last one is not a near miss — the comment on a-equals-with-zero-on-the-left-turns-round
already records that EqualTo does not chain "the way Equalizes and 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.

MostSoundRulesHaveNoJudgedGrowth had to move: it asserted that fewer than a quarter of sound rules
carry 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.

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
@Rafael-SOWNet Rafael-SOWNet changed the title Twenty rules say how they change the size of what they match (#825) Twenty-four rules say how they change the size of what they match (#825) Sep 4, 2026
`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
@Rafael-SOWNet Rafael-SOWNet changed the title Twenty-four rules say how they change the size of what they match (#825) Twenty-eight rules say how they change the size of what they match (#825) Sep 4, 2026
Rafael-SOWNet and others added 2 commits September 4, 2026 16:05
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
@Rafael-SOWNet Rafael-SOWNet changed the title Twenty-eight rules say how they change the size of what they match (#825) Forty-three rules say how they change the size of what they match (#825) Sep 4, 2026
Rafael-SOWNet and others added 3 commits September 4, 2026 16:27
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
@Rafael-SOWNet
Rafael-SOWNet merged commit 1160d05 into master Sep 4, 2026
31 checks passed
@Rafael-SOWNet
Rafael-SOWNet deleted the growth-declared-batch2 branch September 4, 2026 17:36
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