Skip to content

An improper quotient is divided out before it is decomposed (#180) - #1147

Merged
Rafael-SOWNet merged 1 commit into
masterfrom
integrate-improper-fractions
Sep 2, 2026
Merged

Rafael-SOWNet merged 1 commit into
masterfrom
integrate-improper-fractions

Conversation

@Rafael-SOWNet

Copy link
Copy Markdown
Member

Every step of the rational integrator wants a proper fraction — a numerator of lower
degree than the denominator — and each of the three declines an improper one rather than
dividing it out. So x^2/(x + 1) had no antiderivative, although it is x - 1 + 1/(x + 1)
and every piece of that has been integrable throughout.

"x^2/(x + 1)".Integrate("x")

was  integral(x ^ 2 / (x + 1), x)
is   x ^ 2 / 2 + -x + ln(x + 1) + C

The division was already written

TreeAnalyzer.PolynomialLongDivision has done this all along, for the simplifier's own
PolynomialLongDivision rule set. The integrator never asked it. The diff to the library is
one call, placed before the three decompositions rather than after them — no new algorithm,
and nothing about the division itself changed.

That is worth saying plainly because the gap read like a missing capability from the outside:
three separate places in the code say an improper fraction "has to be divided out first, which
is not done here", and the thing they describe existed one namespace away.

What it delivers

As written:

integrand now
x^2/(x + 1) x^2/2 - x + ln(x + 1) + C
x^3/(1 + x^2) answered
x^4/(x^2 + 1) answered
(x^5 + 2)/(x^2 + 1) answered
(x^2 + 3x + 5)/(x + 2) answered
(x^3 + 1)/(x + 1), (x^2 - 1)/(x - 1) answered — exact division, proper part zero

Through a substitution, which is where it matters more, because there the improper fraction is
produced rather than written by anyone:

integrand becomes now
tan(x)^2 u^2/(1 + u^2) tan(x) - arctan(tan(x)) + C
tan(x)^3 u^3/(1 + u^2) answered
sqrt(x)/(x + 1) 2u^2/(1 + u^2) answered

int tan(x)^2 comes back as tan(x) - arctan(tan(x)) + C rather than the textbook
tan(x) - x. Those are the same function on the principal branch, and the second is what the
rewrite would have to assert about which branch x is on, so the first is the honest form.

The declined lists caught this, which is what they are for

All three of tan(x)^2, tan(x)^3 and sqrt(x)/(x + 1) were on "what is still declined"
theories added earlier today, each naming the improper-fraction gap as its reason. All three
failed the moment this landed, and moved to the positive cases with a note saying where they
came from. A boundary recorded as a test fails when the boundary moves; recorded as prose it
just goes quietly wrong.

What is still declined

A proper fraction is untouched — the helper answers nothing for one, so the three steps
below it see exactly what they saw before. int 1/(x + 1) is still ln(x + 1) + C and
int x/(x^2 + 1) still ln(x^2 + 1)/2 + C, both asserted.

A symbolic leading coefficient, and this corrects something I wrote on #180 earlier today.
Item 18 there is x^2/(a + b*x), which I said should come with this work. It does not: the
division would have to divide by b, which is not decidably non-zero, and at b = 0 the
quotient is x^2/a, whose antiderivative is not the limit of the divided form. x^2/(x + a)
is declined too, for the narrower reason that the helper does not divide a polynomial whose
other coefficients are symbolic even when the leading one is 1. Both are pinned by a test,
and I will correct the comment on #180.

Handling either would want the Piecewise-on-the-degenerate-case treatment that
IntegrateRationalQuadratic already uses for a = 0, which is its own change with its own
soundness argument.

Verification

Full suite 9356 passed, 0 failed, 14 skipped. Corpus still 40 of 40.

Every new answer is checked by differentiating it back and comparing at sampled points, through
a helper that first asserts the antiderivative contains no integral( — without that guard the
check passes vacuously, since d/dx of an unevaluated integral(f, x) is f.

Part of #180.

Every step of the rational integrator wants a proper fraction, and each of the
three declines an improper one rather than dividing it out. So `x^2/(x + 1)`
had no antiderivative, although it is `x - 1 + 1/(x + 1)` and every piece of
that has been integrable throughout.

The division is not new code. `TreeAnalyzer.PolynomialLongDivision` has done it
all along for the simplifier's own rule set of that name; the integrator never
asked it. What changes is one call, placed before the three decompositions
rather than after them.

New as written: `x^2/(x + 1)`, `x^3/(1 + x^2)`, `x^4/(x^2 + 1)`,
`(x^5 + 2)/(x^2 + 1)`, `(x^2 + 3x + 5)/(x + 2)`, and the exact divisions
`(x^3 + 1)/(x + 1)` and `(x^2 - 1)/(x - 1)` whose proper part is zero.

New through a substitution, which is where it matters more, since there the
improper fraction is produced rather than written: `tan(x)^2`, `tan(x)^3` and
`sqrt(x)/(x + 1)`. All three were on the declined lists added earlier today,
each naming this gap as its reason, and all three failed those tests when this
landed -- which is what a recorded boundary is for.

A proper fraction is untouched: the helper answers nothing for one, so the
steps below see what they saw before.

A symbolic leading coefficient is still declined, and this corrects what I
said on #180. Item 18 there is `x^2/(a + b*x)`, which I expected to come with
this and does not: the division would divide by `b`, which is not decidably
non-zero, and at `b = 0` the quotient is `x^2/a`, whose antiderivative is not
the limit of the divided form. `x^2/(x + a)` is declined too, for the narrower
reason that the helper does not divide a polynomial whose other coefficients
are symbolic. Both are pinned by a test.

Full suite 9356 passed, 0 failed.

Part of #180.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura
@Rafael-SOWNet
Rafael-SOWNet merged commit 174cf88 into master Sep 2, 2026
31 checks passed
@Rafael-SOWNet
Rafael-SOWNet deleted the integrate-improper-fractions branch September 2, 2026 23:19
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