Skip to content

Let a numeric factor reach the coefficient when collecting like terms - #862

Merged
Rafael-SOWNet merged 1 commit into
masterfrom
fix/collect-offer-not-take
Aug 9, 2026
Merged

Let a numeric factor reach the coefficient when collecting like terms#862
Rafael-SOWNet merged 1 commit into
masterfrom
fix/collect-offer-not-take

Conversation

@Rafael-SOWNet

Copy link
Copy Markdown
Collaborator

Closes #855.

The defect

CollectLikeTerms failed on the expression its own documentation cites as the reason it exists:

(x+1)^2 * (x+1)^2   ->   1 + 4x + 2x^2 + 4x^2 + 4x^3 + x^4

The two x^2 terms sit side by side instead of being added. Correct, but one term longer than it should be — and (x+1)^4, (x+1)*(x+1)*(x+1)*(x+1) and (x+1)^2*(x+2)^2 all collect properly, which is what made it look arbitrary.

The cause, from instrumenting the monomial keys

[collect] term=1              key=''              coeff=1
[collect] term=2 * x          key='x^1'           coeff=2
[collect] term=x ^ 2          key='x^2'           coeff=1
[collect] term=(2 * x) ^ 2    key='4 * x ^ 2^1'   coeff=1     <-- here

(2 * x)^2 reduces to 4 * x^2, which is a product, not a power, so it fell to the exponent-of-one branch and the 4 was baked into the monomial key instead of being extracted into the coefficient. Keyed on 4 * x ^ 2, it could never meet the plain x^2 terms.

The fix

A factor that reduces to a product is split again, and an integer power of a product is distributed over its factors — so the number reaches the coefficient in both shapes. (a*b)^n = a^n * b^n is used only for integer n, where it holds for every a and b.

Only for polynomial-shaped factors, and that restriction is the whole design, not a caveat.

What the restriction is for

Opening a product is what lets like terms meet. It is also what stops a product cancelling as a whole:

(1/2 * sin(2t))^2 * csc(t)^2     collapses to cos(t)^2 while intact
                                 does not, once the 1/2 is lifted out

DoubleAngleOverSineTest.TheReportersSecondExpressionIsAlsoZero exists to catch exactly that, and it caught it. Collection is a statement about polynomials, so it now opens polynomials and leaves anything carrying a function to the rules that know it.

Two other shapes tried and rejected — against the test, not against reasoning

  1. Split only products carrying a number. Does not help: 1/2 is a number, so the trigonometric term is opened anyway.
  2. Build both forms and keep the shorter by SimplifiedRate. Does not work either, and the reason is already written down in this repository — the comment on TheReportersSecondExpressionIsAlsoZero says the opened form "was offered to the complexity metric in the one shape where its payoff had not happened yet". A local comparison cannot see a payoff that only materialises after later passes. I tried it at the Simplificator level too, offering the uncollected expansion as a candidate; still no.

Recording these because each looks obviously right until it is run.

Measured

before after
(x+1)^2 * (x+1)^2 1 + 4x + 2x² + 4x² + 4x³ + x⁴ 1 + 4x + 6x² + 4x³ + x⁴
(x+y)^2 * (x+y)^2 … + 2x²y² + (2xy)² + … … + 6x²y² + …
(2x+1)^2 * (2x+1)^2 uncollected 1 + 8x + 24x² + 32x³ + 16x⁴
(3x)^2 + x^2 uncollected 10x²
(x*y+1)^2 * (x*y+1)^2 uncollected 1 + 4xy + 6x²y² + 4x³y³ + x⁴y⁴

All binomial coefficients 1, 4, 6, 4, 1 as they should be.

Passed! - Failed: 0, Passed: 6067, Skipped: 14, Total: 6081

Six new cases, including one that asserts the trigonometric term is not opened — so the restriction has a test of its own rather than only the pre-existing one it was derived from.

🤖 Generated with Claude Code

CollectLikeTerms failed on the expression its own documentation cites:
(x+1)^2 * (x+1)^2 came back as 1 + 4x + 2x^2 + 4x^2 + 4x^3 + x^4, with the
two x^2 terms side by side instead of added.

Instrumenting the monomial keys gives the cause in one line:

  term=(2 * x) ^ 2    key='4 * x ^ 2^1'    coeff=1

(2 * x)^2 reduces to 4 * x^2, which is a product and not a power, so it fell
to the exponent-of-one branch and the 4 was baked into the monomial key
rather than extracted into the coefficient. Keyed on "4 * x ^ 2" it could
never meet the plain x^2 terms.

A factor that reduces to a product is now split again, and an integer power
of a product is distributed over its factors, so the number reaches the
coefficient in both shapes.

Only for polynomial-shaped factors. Opening a product is what lets like terms
meet and it is also what stops a product cancelling as a whole: lifting the
1/2 out of (1/2 * sin(2t))^2 * csc(t)^2 costs the cancellation to cos(t)^2,
and DoubleAngleOverSineTest is there to catch exactly that. Collection is a
statement about polynomials, so it opens polynomials and leaves anything
carrying a function to the rules that know it.

Two other shapes were tried and rejected against that test rather than
against reasoning: splitting only products that carry a number does not help,
since 1/2 is one; and comparing the collected and expanded forms by their
complexity cannot work, because the payoff of leaving a product intact only
appears after passes that run later -- which is what the comment on
TheReportersSecondExpressionIsAlsoZero already says about this metric.

Closes #855.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Rafael-SOWNet
Rafael-SOWNet merged commit 181a42a into master Aug 9, 2026
25 checks passed
@Rafael-SOWNet
Rafael-SOWNet deleted the fix/collect-offer-not-take branch August 9, 2026 20:51
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.

CollectLikeTerms fails on the very example its documentation cites

1 participant