Repository navigation
Factor takes out the content instead of refusing - #1053
Merged
Merged
Conversation
`MathS.Polynomials.Factor` works over Q, so a coefficient that is not a rational
number stopped it before it began and every polynomial in more than one variable
was refused. Some of them never needed a bigger ring: `x * y + y` is `y` times
something univariate, and only the `y` was in the way.
The content in the named variable -- the greatest common divisor of the
coefficients, a polynomial in the other variables -- is taken out first, using
`PolynomialGcd.ContentIn`, the same multivariate machinery `Gcd` is already built
from, and what remains goes down the ordinary path.
Factor("x * y + y", "x") null -> y * (x + 1)
Factor("x ^ 2 * y + x * y", "x") null -> y * x * (x + 1)
Factor("a * x ^ 2 + a * x", "x") null -> a * x * (x + 1)
Factor("x ^ 2 * y ^ 2 - y ^ 2", "x") null -> y ^ 2 * (x + 1) * (x - 1)
Factor("x ^ 2 - y ^ 2", "x") null -> null
Factor("x * y + z", "x") null -> null
Only a refusal becomes an answer: the new path runs only where the old one
returned null, so nothing that already factorised changes. It is still a refusal
wherever the content is a constant, and `x ^ 2 - y ^ 2` is the honest case for
that -- it needs factorisation over Q(y), which this is not and does not claim to
be. That is the open half of item 43 and this narrows it rather than closing it.
The refusal test carried `x * y + y` with a comment saying that handing it back
"would say that `y * (x + 1)` does not exist, which is a wrong answer and not a
graceful failure". It asserts that answer now. Checked numerically at twenty
random points per case rather than as a string: `Simplify` does not prove
`y * x * (x + 1)` equal to `x ^ 2 * y + x * y`, and the two are equal, so a
string comparison would have reported a defect that is not one.
Suite 8561/0. casbench, rootcheck and simpsweep re-run against this branch are
byte-identical to master's committed reports.
Part of #746 tier 1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bjumi5K7fg8yx6UK1mZTQd
Rafael-SOWNet
added a commit
that referenced
this pull request
Aug 25, 2026
…1054) `MathS.Polynomials.SquareFreePart` refused every polynomial in more than one variable, for the same reason `Factor` did: it was written against a representation whose coefficients are rational numbers. `p / gcd(p, dp/dx)` is the square-free part whatever ring the coefficients live in -- a repeated factor appears in the derivative one time fewer than in the polynomial, so dividing by the common part leaves each distinct factor exactly once. Nothing about that is univariate, and the multivariate representation has all three operations already: `DerivativeIn`, the recursive `PolynomialGcd` that `Gcd` is built from, and exact division. SquareFreePart("(x - y) ^ 2 * (x + y)", "x") null -> x ^ 2 - y ^ 2 SquareFreePart("(x - y) ^ 3", "x") null -> x - y SquareFreePart("(x + a) ^ 2 * (x + b)", "x") null -> (x + a) * (x + b) SquareFreePart("x ^ 2 * y ^ 2", "x") null -> x SquareFreePart("y", "x") null -> null The last two are worth saying out loud. The content is dropped, as it always was: `SquareFreePart("4 * x ^ 2", "x")` is `x` and not `4 * x`, because the univariate path takes the primitive part first -- so `x ^ 2 * y ^ 2` is `x` with `y ^ 2` as the content, which is the existing convention applied to a wider ring rather than a new one. That was checked against the univariate path rather than assumed; the expectation written first was the wrong one. Reached only where the rational path declined, so nothing that already answered can change. Tests compare numerically at twenty points per case rather than as strings, for the reason #1053 records. Suite 8569/0. Part of #746 tier 1, item 43. Claude-Session: https://claude.ai/code/session_01Bjumi5K7fg8yx6UK1mZTQd Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 25, 2026
Rafael-SOWNet
added a commit
that referenced
this pull request
Aug 25, 2026
…with it (#1059) Kronecker's substitution does not merely fail to find a factorisation -- it proves there is none. A splitting of the polynomial into two parts of positive degree in the main variable maps to a splitting of the one-variable image, because the substitution is a ring homomorphism on the monomials that can appear, and every splitting of the image is one of the subsets the recombination tries. So where nothing recombines, nothing exists. That proof was being reported as null, and the content went with it: x * y + y * z was null, is y * (x + z) a * x + a * y + a * z was null, is a * (x + y + z) x ^ 2 * y - y ^ 3 was null, is y * (x + y) * (x - y) The content had already been taken out and the factorisation assembled; the single-factor rest then threw the whole thing away. It also disagreed with the one-variable path, where Factor("x ^ 2 + 1", "x") has always been x ^ 2 + 1, so x ^ 2 + y ^ 2, x ^ 2 - a and x * y + z now answer the same way. The proof has a precondition, and it is now checked rather than assumed. DivideExact answers null both for "does not divide" and for "ran out of room", and only the first is evidence -- so it says which, and the irreducibility claim is withheld where a division was cut short by a term or degree budget. Where FromImage cannot read a candidate back the recombination now refuses instead of skipping it silently, since a skipped candidate is a subset that was never tried and the proof is over all of them. The XML documentation on Factor still said "Univariate only", with a worked example asserting that Factor("x * y + y", "x") is null. That has been false since #1053 and is public API documentation, which nothing in the suite or in the docsamples harness reads. Claude-Session: https://claude.ai/code/session_01Bjumi5K7fg8yx6UK1mZTQd Co-authored-by: Rafael <darkfader@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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.
Part of #746 tier 1, item 43.
What was wrong
MathS.Polynomials.Factorworks over ℚ, so a coefficient that is not a rational number stopped itbefore it began, and every polynomial in more than one variable was refused. Some of them never
needed a bigger ring at all:
x * y + yisytimes something univariate, and only theywas inthe way.
The refusal test in
PolynomialSurfaceTestcarriedx * y + yas a case, under a comment that says:It was right about the answer that ought to exist. This makes it exist.
What changed
The content in the named variable — the greatest common divisor of the coefficients, which is a
polynomial in the other variables — is taken out first, via
PolynomialGcd.ContentIn, the samemultivariate machinery
MathS.Polynomials.Gcdis already built from. What remains goes down theordinary path.
Factor("x * y + y", "x")nully * (x + 1)Factor("x ^ 2 * y + x * y", "x")nully * x * (x + 1)Factor("a * x ^ 2 + a * x", "x")nulla * x * (x + 1)Factor("x ^ 2 * y ^ 2 - y ^ 2", "x")nully ^ 2 * (x + 1) * (x - 1)Factor("x ^ 3 * y - x * y", "x")nully * x * (x + 1) * (x - 1)Factor("x ^ 2 - y ^ 2", "x")nullnullFactor("x * y + z", "x")nullnullWhat it does not claim
Only a refusal becomes an answer. The new path runs solely where the old one returned
null, sonothing that already factorised can change.
It is still a refusal wherever the content is a constant, and
x ^ 2 - y ^ 2is the honest case forthat: it needs factorisation over ℚ(y), which this is not. Item 43 asks for multivariate
factorisation over ℚ and 𝔽ₚ and this narrows that gap rather than closing it — the roadmap row
should still say factorisation is univariate over ℚ, with the content taken out first.
Verification
The new tests check the factorisation numerically, at twenty random points per case, rather than
as a string. That is deliberate:
Simplifydoes not provey * x * (x + 1)equal tox ^ 2 * y + x * y, and the two are equal — a string orSimplifycomparison reports a defect thatis not one, which it did while this was being written.
casbench,rootcheckandsimpsweepre-run against this branch are byte-identical tomaster's committed reports.