Skip to content

The cosecant, the secant, and a linear denominator in the cosine, by the half-angle substitution - #1238

Merged
Rafael-SOWNet merged 2 commits into
masterfrom
integrator-half-angle
Sep 9, 2026
Merged

Rafael-SOWNet merged 2 commits into
masterfrom
integrator-half-angle

Conversation

@Rafael-SOWNet

Copy link
Copy Markdown
Member

Part of #718.

int 1/sin(x) and int 1/cos(x) had no antiderivative. They are table entries in every calculus text, and the gap was easy to miss because the neighbours are answered: tan(x) comes out, and so does 1/sin(x)^2 through the cotangent rule.

Why this family, measured rather than chosen

With intbench able to finish a run for the first time, the band Rubi rates as a single table lookup is the worst-served difficulty we have:

Rubi steps solved rate
1 (table lookup) 156 / 268 58%
2–3 439 / 851 52%
4–6 124 / 432 29%

A one-step lookup should be our best band, not a point above our second. Reading the 112 that fail, they cluster on rational functions of sine and cosine.

What it does

Under t = tan(x/2): sin(x) = 2t/(1+t^2), cos(x) = (1-t^2)/(1+t^2), dx = 2/(1+t^2) dt. So an integrand built from sines and cosines by the field operations becomes a quotient of polynomials — which partial fractions already answers, and answers 50x faster since #1236.

1/sin(x)       ->  ln(tan(x/2))
1/(1+cos(x))   ->  tan(x/2)
1/cos(x)       ->  a logarithm of a quotient linear in tan(x/2)

Two judgement calls, stated rather than buried

Simplify rather than InnerSimplified on the rewritten integrand. The rewrite puts a quotient inside a quotient, and partial fractions wants one Divf of two polynomials; InnerSimplified leaves the nesting, so every one of these was handed on in a shape nothing downstream could read.

The Providedf is dropped, and it is worth being exact rather than calling it vacuous. It says 1 + t^2 != 0, which fails at t = ±i — and complex is this library's default codomain. It is sound to drop because of what t is here: t = tan(x/2), reached by rewriting a real trigonometric integrand, so the excluded points are not reachable values of it and do not appear in the answer, which is written back in terms of x.

The answer is an antiderivative on each interval between the poles of tan(x/2) rather than across one. That is the standing property of this substitution and the same one SolveByTangentSubstitution already carries.

What it does not reach, and why that is not this rule's fault

1/(1-sin(x)), 1/(1+cos(x)/2), 1/(2cos(x)+3sin(x)) still decline. They rewrite correctly and then stop, because Simplify never puts a sum over a common denominator — even a + b/c is left split. That is the operation other systems call together or ratsimp, we do not have it, and it is what the rest of this family needs. Filing it separately.

Checked

Every answer differentiated back and compared at five points inside one branch, never against a printed form. Two cases assert the rule correctly declines: sin(x) + x is not a function of the sine alone, and sin(x)*sin(2x) leaves an x behind because sin(2x) is not sin(x) — the right outcome, since a product of sines wants product-to-sum. Full suite green: 8,499 and 1,485.

🤖 Generated with Claude Code

https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura

…the half-angle substitution

int 1/sin(x) and int 1/cos(x) had no antiderivative. They are table entries in
every calculus text, and the gap was easy to miss because the neighbours are
answered: tan(x) comes out, and so does 1/sin(x)^2 through the cotangent rule.

Measured against Rubi's suite with the harness able to finish a run for the first
time, the band it rates as a *single table lookup* is the worst-served difficulty
we have -- 58%, barely above the 52% for problems needing two or three rules --
and this family is a large part of why.

Under t = tan(x/2), sin(x) = 2t/(1+t^2), cos(x) = (1-t^2)/(1+t^2) and
dx = 2/(1+t^2) dt, so an integrand built from sines and cosines by the field
operations becomes a quotient of polynomials, which partial fractions already
answers -- and answers 50x faster since #1236.

    1/sin(x)        ->  ln(tan(x/2))
    1/(1+cos(x))    ->  tan(x/2)
    1/cos(x)        ->  a logarithm of a quotient linear in tan(x/2)

Two judgement calls worth stating.

Simplify rather than InnerSimplified on the rewritten integrand, because the
rewrite puts a quotient inside a quotient and partial fractions wants one Divf of
two polynomials. InnerSimplified leaves the nesting, so every one of these was
handed on in a shape nothing downstream could read.

And the Providedf that collapsing the nesting attaches is dropped. It says
1 + t^2 != 0, which is not vacuous over the complex plane -- it fails at t = +-i,
and complex is this library's default codomain. It is sound to drop because of
what t is here: t = tan(x/2) reached by rewriting a real trigonometric integrand,
so the excluded points are not reachable values of it and do not appear in the
answer, which is written back in terms of x.

The answer is an antiderivative on each interval between the poles of tan(x/2)
rather than across one. That is the standing property of this substitution and
the same one SolveByTangentSubstitution already carries.

What this does not reach, and why, is a simplifier gap rather than a gap here:
Simplify never puts a sum over a common denominator, so 1/(1-sin(x)) rewrites to
2/((t^2+1)*(1 + (-2)t/(t^2+1))) and stays there. Even `a + b/c` is left split.
That is the operation other systems call together or ratsimp, and it is what the
rest of this family needs. Filed separately.

Every answer is checked by differentiating it back and comparing at five points,
never against a printed form. Full suite green: 8499 and 1485.

#718

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura
CI caught this and my local run did not, because I ran Sources/Tests/UnitTests
and not the F# wrapper's suite, which pins the readable answer.

cos(x) + 1 is a function of the cosine alone, so the rewrite succeeds on it and
produces a correct antiderivative in tan(x/2) about forty characters long:

    expected  sin(x) + x + C
    actual    4 * (2 * tan(x/2) / (tan(x/2)^2 + 1) + 2 * 2 * arctan(...) / 2) / 4 + C

Both are antiderivatives and only one is an answer anybody wants. Everything
that decomposes the problem into pieces the ordinary rules know now gets first
refusal, and this sees only what is left -- which is still the quotients it was
added for, since linearity declines a quotient and partial fractions declines
one that is not a ratio of polynomials. 1/sin(x), 1/cos(x) and 1/(1 + cos(x))
are unchanged.

Three cases pinning the decline, so this does not come back.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura
@Rafael-SOWNet

Copy link
Copy Markdown
Member Author

Pushed a fix for the red check, which was a real defect rather than flake.

Test (ubuntu-latest) failed in the F# wrapper suite — which I had not run locally, having run only Sources/Tests/UnitTests. cos(x) + 1 is a function of the cosine alone, so the rewrite succeeded on it:

Expected: sin(x) + x + C
Actual:   4 * (2 * tan(x / 2) / (tan(x / 2) ^ 2 + 1) + 2 * 2 * arctan(2 * tan(x / 2) / 2) / 2) / 4 + C

Both are antiderivatives; only one is an answer anybody wants, and that test pins the readable one. The rule was sitting before SolveBySplittingSum, so it beat linearity to a sum linearity answers properly.

It now runs after linearity. Everything that decomposes the problem into pieces the ordinary rules know gets first refusal, and this sees only what is left — still the quotients it was added for, since linearity declines a quotient and partial fractions declines one that is not a ratio of polynomials.

1/sin(x), 1/cos(x) and 1/(1 + cos(x)) are unchanged, and cos(x) + 1, sin(x) + cos(x) and 2*cos(x) are back to their short forms. Three cases added pinning the decline.

Suites run this time rather than assumed: UnitTests 8,499 and 1,493, FSharpWrapperUnitTests 134, InteractiveWrapperUnitTests 18, TerminalUnitTests 41.

@Rafael-SOWNet
Rafael-SOWNet merged commit 8d10b0c into master Sep 9, 2026
31 checks passed
@Rafael-SOWNet
Rafael-SOWNet deleted the integrator-half-angle branch September 9, 2026 18:43
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