Skip to content

Factor a polynomial in two variables, by Kronecker's substitution - #1055

Merged
Rafael-SOWNet merged 1 commit into
masterfrom
feat/bivariate-factorisation
Aug 25, 2026
Merged

Rafael-SOWNet merged 1 commit into
masterfrom
feat/bivariate-factorisation

Conversation

@Rafael-SOWNet

Copy link
Copy Markdown
Member

Part of #746 tier 1, item 43 — and it is the case that item names.

MathS.Polynomials.Factor("x ^ 2 - y ^ 2", "x")   // 2.3.0: null.  Now: (x + y) * (x - y)

How

A factor of a polynomial of degree d in x has degree at most d in x. So with s = d + 1, the
map x^i y^j → t^(i + s*j) is injective on every monomial that can appear in the polynomial or in
any of its factors
: i is the remainder and j the quotient of the exponent by s, and neither
can be confused with another pair. The one-variable image is handed to the factoriser that already
exists, and each subset of its irreducible factors names a candidate two-variable factor.

2.3.0 now
Factor("x ^ 2 - y ^ 2", "x") null (x + y) * (x - y)
Factor("x ^ 2 + 2 * x * y + y ^ 2", "x") null (x + y) ^ 2
Factor("x ^ 3 - y ^ 3", "x") null (x - y) * (x ^ 2 + x * y + y ^ 2)
Factor("x ^ 4 - y ^ 4", "x") null (x + y) * (x ^ 2 + y ^ 2) * (x - y)
Factor("x ^ 2 * y ^ 2 - 1", "x") null (x * y + 1) * (x * y - 1)
Factor("x ^ 2 - y ^ 2 + 2 * x + 1", "x") null (x + y + 1) * (x - y + 1)
Factor("x ^ 2 + y ^ 2", "x") null null — irreducible over ℚ
Factor("x * y + z", "x") null null — three variables

It cannot answer wrongly

This is the part worth reviewing. The substitution is injective on monomials but not on
factorisations, so the image may factor further than the polynomial does and every candidate really
is a guess. Two checks stand between a guess and an answer:

  1. each candidate must divide the remaining polynomial exactly, or it is discarded;
  2. the assembled factors are multiplied and divided back into the input before anything is returned.

So the failure mode is a refusal, never a wrong factorisation.

What it refuses

The image has degree d + s*e for degree e in the second variable, and the one-variable factoriser
stops at 32 — so this reaches bidegrees like (2, 10), (3, 7) and (5, 4) and refuses past them. The
recombination is over subsets, so the image's factor count is capped at 12.

Lifting that ceiling means Hensel lifting with an evaluation homomorphism. This is not a step
towards that
— it is the classical method, exact inside its bound and honest outside it. Item 43's
full ask is still open.

Blast radius

MathS.Polynomials.Factor has no caller inside the library — checked with a grep, not assumed —
so no simplification, solution or integral moves with this.

  • Suite: 8574 passed, 0 failed, 14 skipped.
  • casbench and simpsweep re-run against this branch are byte-identical to master's committed
    reports.
  • Repeated factors are collected into a power, as the one-variable path does, so one question does
    not get two shapes of answer depending on which path took it.
  • Tests assert the number of distinct factors as well as numeric agreement, so a partial
    factorisation reported as a whole one fails rather than passing quietly.

#746 item 43 asks for multivariate factorisation and names the case it wants:
`Factor("x ^ 2 - y ^ 2", "x")` was `null`. It is `(x + y) * (x - y)` now.

A factor of a polynomial of degree `d` in `x` has degree at most `d` in `x`, so
with `s = d + 1` the map `x^i y^j -> t^(i + s*j)` is injective on every monomial
that can appear in the polynomial or in any of its factors -- `i` is the
remainder and `j` the quotient of the exponent by `s`, and neither can be
confused with another pair. So the one-variable image is factored by the
factoriser that already exists, and each subset of its irreducible factors names
a candidate two-variable factor.

    x ^ 2 - y ^ 2              ->  (x + y) * (x - y)
    x ^ 2 + 2 * x * y + y ^ 2  ->  (x + y) ^ 2
    x ^ 3 - y ^ 3              ->  (x - y) * (x ^ 2 + x * y + y ^ 2)
    x ^ 4 - y ^ 4              ->  (x + y) * (x ^ 2 + y ^ 2) * (x - y)
    x ^ 2 * y ^ 2 - 1          ->  (x * y + 1) * (x * y - 1)
    x ^ 2 - y ^ 2 + 2 * x + 1  ->  (x + y + 1) * (x - y + 1)
    x ^ 2 + y ^ 2              ->  null, irreducible over Q
    x * y + z                  ->  null, three variables

**It cannot answer wrongly.** The substitution is injective on monomials and not
on factorisations, so the image can factor further than the polynomial does and
every candidate is a guess. Each is tested by exact division before it is kept,
and the assembled factors are divided back into the input before they are
returned, so the failure mode is a refusal.

What it refuses: the image has degree `d + s*e` and the one-variable factoriser
stops at 32, so this reaches bidegrees like (2, 10), (3, 7) and (5, 4). The
recombination is over subsets, so the image's factor count is capped as well.
Lifting that ceiling is Hensel lifting with an evaluation homomorphism and is a
different piece of work; this is not a step towards it, it is the classical
method that is exact within its bound.

Repeated factors are collected into a power, as the one-variable path does, so
one question does not get two shapes of answer depending on which path took it.

`MathS.Polynomials.Factor` has no caller inside the library -- checked, not
assumed -- so no simplification, solution or integral moves with it. Suite
8574/0, casbench and simpsweep byte-identical to master's committed reports.

Part of #746 tier 1, item 43.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bjumi5K7fg8yx6UK1mZTQd
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