Skip to content

Commit 9f18899

Browse files
Divide out a constant common to the denominator after the half-angle substitution (#1927)
* Divide out a constant common to the denominator after the half-angle substitution The rational function of t = tan(x/2) is written over the denominator's leading coefficient with each coefficient in lowest terms over the symbols. With k^2 left in the coefficients of 1/(k + k sin(x))^2 the rules below formed k^2/k^2, which the simplification does not cancel, so the quadratic's discriminant was not decidably zero, and x/(k + k sin(x))^2 was answered NaN through a branch of the piecewise that can never hold -- and so was Rubi's 5.1.4 (a + b asin(c x))/((d + c d x)^(5/2) sqrt(f - c f x)), which comes to it. 1/(d c + c d sin(x))^2 is 143 characters where it was 2166. Part of #718 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura * Divide the leading coefficient out only where the denominator is a constant times numbers Written so for every integrand, fifteen Rubi 4.x problems the half-angle answered in a second timed out. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura * Record the constants divided out of a half-angle denominator in BREAKING-CHANGES Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 987e0f5 commit 9f18899

4 files changed

Lines changed: 171 additions & 0 deletions

File tree

‎BREAKING-CHANGES.md‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,20 @@ angle, each of which the integrator answers
294294
| `"x^3*(1-c^2*x^2)^(5/2)/(a+b*asin(c*x))".ToEntity().Integrate("x")` | `integral(...)` | 508 characters |
295295
| `"x^3*(1-c^2*x^2)^(5/2)/(a+b*asin(c*x))^2".ToEntity().Integrate("x")` | `integral(...)` | 1,470 characters |
296296

297+
### A constant common to a half-angle denominator is divided out
298+
299+
**Answers where there were none.** `x/(k + k sin(x))^2` was declined, and so were `x/(d c + c d sin(x))^2` and Rubi's
300+
5.1.4 `(a + b asin(c x))/((d + c d x)^(5/2) sqrt(f - c f x))`. After the half-angle substitution, such a denominator
301+
is a constant times a polynomial with number coefficients, `k^2 (t + 1)^4`. The constant is now divided out before the
302+
partial fractions
303+
([#718](https://github.com/asc-community/AngouriMath/issues/718)).
304+
305+
| Input | Was (2.5.0) | Now |
306+
|---|---|---|
307+
| `"x/(k+k*sin(x))^2".ToEntity().Integrate("x")` | `integral(...)` | 285 characters |
308+
| `"x/(d*c+c*d*sin(x))^2".ToEntity().Integrate("x")` | `integral(...)` | 329 characters |
309+
| `"(a+b*asin(c*x))/((d+c*d*x)^(5/2)*sqrt(f-c*f*x))".ToEntity().Integrate("x")` | `integral(...)` | 927 characters |
310+
297311
### A piecewise is defined where the case it takes is
298312

299313
**Domain conditions that were false.** The domain condition of a piecewise asked every case's predicate

‎Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32678,6 +32678,33 @@ bool IsAPolynomialInBoth(Entity polynomial)
3267832678
?.Pipe(answer => underneath ? answer / constant : answer * constant);
3267932679
}
3268032680

32681+
/// <summary>
32682+
/// A rational function of <paramref name="t"/> as a polynomial over a polynomial, both divided by
32683+
/// the denominator's leading coefficient with each coefficient in lowest terms over the symbols,
32684+
/// so that a constant common to the denominator's coefficients cancels; null where either side is
32685+
/// not a polynomial in <paramref name="t"/> with coefficients free of it.
32686+
/// </summary>
32687+
private static Entity? WithTheLeadingCoefficientDividedOut(Entity rational, Entity.Variable t)
32688+
{
32689+
var (above, below) = Functions.SingleQuotient.Of(rational);
32690+
if (!TreeAnalyzer.TryGetPolynomial(above.Expand(), t, out var top) || !TreeAnalyzer.TryGetPolynomial(below.Expand(), t, out var bottom)
32691+
|| bottom.Count == 0 || top.Keys.Concat(bottom.Keys).Any(key => key.Sign < 0 || !key.CanFitInInt32())
32692+
|| top.Values.Concat(bottom.Values).Any(coefficient => coefficient.ContainsNode(t)))
32693+
return null;
32694+
var lead = bottom[bottom.Keys.Max()!];
32695+
// Only where the symbols in the denominator are one factor common to all of it: there the
32696+
// rules below formed `k^2/k^2`, and anywhere else this respelling sent fifteen of Rubi's
32697+
// 4.x problems the half-angle answered in a second to a timeout.
32698+
if (!lead.Vars.Any() || bottom.Values.Any(coefficient => Functions.PartialFractions.InLowestTermsOverTheSymbols(coefficient / lead) is not Number))
32699+
return null;
32700+
Entity Rebuilt(Dictionary<EInteger, Entity> monomials)
32701+
=> monomials.Aggregate((Entity)Number.Integer.Zero, (sum, pair)
32702+
=> sum + Functions.PartialFractions.InLowestTermsOverTheSymbols(pair.Value / lead)
32703+
// t^0 written as 1: as a power it brings `provided not t = 0` with it.
32704+
* (pair.Key.IsZero ? Number.Integer.One : MathS.Pow(t, Number.Integer.Create(pair.Key))));
32705+
return (Rebuilt(top) / Rebuilt(bottom)).InnerSimplified;
32706+
}
32707+
3268132708
/// <summary>
3268232709
/// A rational function of <c>sin(x)</c> and <c>cos(x)</c>, turned into a rational function
3268332710
/// of one variable by the half-angle substitution <c>t = tan(x/2)</c> and handed to the
@@ -32869,6 +32896,14 @@ static bool NoRemainder(Entity rest)
3286932896
if (divided)
3287032897
integrand = (numerator / denominator).InnerSimplified;
3287132898

32899+
// Over the denominator's leading coefficient, every coefficient in lowest terms over the
32900+
// symbols: `1/(k + k sin(x))^2` is `2 (1 + t^2)/(k^2 (t + 1)^4)`, and with `k^2` left in the
32901+
// coefficients the rules below formed `k^2/k^2`, which the simplification does not cancel,
32902+
// so the quadratic's discriminant was not decidably zero and `x/(k + k sin(x))^2` was
32903+
// answered `NaN` through a branch of the piecewise that can never hold.
32904+
if (integrand.Vars.Any(symbol => symbol != t) && WithTheLeadingCoefficientDividedOut(integrand, t) is { } normalized)
32905+
integrand = normalized;
32906+
3287232907
return Integration.ComputeIndefiniteIntegral(integrand, t, integrateByParts) is { } result
3287332908
? result.Substitute(t, MathS.Tan(argument / 2))
3287432909
: null;
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//
2+
// Copyright (c) 2019-2026 Angouri.
3+
// AngouriMath is licensed under MIT.
4+
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
5+
// Website: https://am.angouri.org.
6+
//
7+
8+
using System;
9+
using AngouriMath.Extensions;
10+
using Xunit;
11+
12+
namespace AngouriMath.Tests.Calculus
13+
{
14+
/// <summary>
15+
/// A constant common to the whole denominator after the half-angle substitution, divided out:
16+
/// <c>x/(k + k sin(x))^2</c> was <c>NaN</c>, and so was Rubi's 5.1.4
17+
/// <c>(a + b asin(c x))/((d + c d x)^(5/2) sqrt(f - c f x))</c>, which comes to it.
18+
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
19+
/// </summary>
20+
[Trait("Area", "Calculus")]
21+
public sealed class AConstantCommonToAHalfAngleDenominatorIntegralTest
22+
{
23+
private static void DifferentiatesBack(string integrand, params (string, double)[] pins)
24+
{
25+
var integral = integrand.ToEntity().Integrate("x");
26+
Assert.DoesNotContain("integral(", integral.Stringize());
27+
Assert.DoesNotContain("NaN", integral.Stringize());
28+
29+
var pinned = integral.Substitute("C", 0);
30+
Entity original = integrand.ToEntity();
31+
foreach (var (name, value) in pins)
32+
{
33+
pinned = pinned.Substitute(name, value);
34+
original = original.Substitute(name, value);
35+
}
36+
var derivative = pinned.Differentiate("x");
37+
foreach (var at in new[] { -2.3, -0.7, 0.4, 1.9 })
38+
{
39+
var got = derivative.Substitute("x", at).EvalNumerical();
40+
var want = original.Substitute("x", at).EvalNumerical();
41+
var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart);
42+
var scale = Math.Max(1.0, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart));
43+
Assert.True(difference / scale < 1e-8,
44+
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
45+
}
46+
}
47+
48+
[Theory]
49+
[InlineData("x/(k+k*sin(x))^2")]
50+
[InlineData("1/(d*q+q*d*sin(x))^2")]
51+
[InlineData("(a+b*asin(q*x))/((d+q*d*x)^(5/2)*sqrt(f-q*f*x))")]
52+
public void IsAnsweredAndDifferentiatesBack(string integrand)
53+
{
54+
// |q x| below 1 at every sampled x, where the inverse sine and the roots are real.
55+
DifferentiatesBack(integrand, ("k", 1.3), ("a", 0.7), ("b", -0.4), ("d", 1.1), ("f", 1.5), ("q", 0.3));
56+
DifferentiatesBack(integrand, ("k", -0.7), ("a", -2), ("b", 1.5), ("d", 0.6), ("f", 2), ("q", -0.2));
57+
}
58+
}
59+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//
2+
// Copyright (c) 2019-2026 Angouri.
3+
// AngouriMath is licensed under MIT.
4+
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
5+
// Website: https://am.angouri.org.
6+
//
7+
8+
using System;
9+
using AngouriMath.Extensions;
10+
using Xunit;
11+
12+
namespace AngouriMath.Tests.Calculus
13+
{
14+
/// <summary>
15+
/// The half-angle substitution with a constant common to the denominator's coefficients: it is
16+
/// divided out in lowest terms over the symbols, which the simplification does not do for
17+
/// <c>k^2/k^2</c>. Kept, it left a quadratic whose discriminant was not decidably zero, and
18+
/// <c>x/(k + k sin(x))^2</c> -- and Rubi's 5.1.4 <c>(a + b asin(c x))/((d + c d x)^(5/2) sqrt(f - c f x))</c>,
19+
/// which comes to it -- were answered <c>NaN</c>.
20+
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
21+
/// </summary>
22+
[Trait("Area", "Calculus")]
23+
public sealed class AHalfAngleSubstitutionWithACommonConstantIntegralTest
24+
{
25+
private static void DifferentiatesBack(string integrand, params (string, double)[] pins)
26+
{
27+
var integral = integrand.ToEntity().Integrate("x");
28+
Assert.DoesNotContain("integral(", integral.Stringize());
29+
30+
var pinned = integral.Substitute("C", 0);
31+
Entity original = integrand.ToEntity();
32+
foreach (var (name, value) in pins)
33+
{
34+
pinned = pinned.Substitute(name, value);
35+
original = original.Substitute(name, value);
36+
}
37+
var derivative = pinned.Differentiate("x");
38+
foreach (var at in new[] { -2.3, -0.7, 0.4, 1.9 })
39+
{
40+
var got = derivative.Substitute("x", at).EvalNumerical();
41+
var want = original.Substitute("x", at).EvalNumerical();
42+
var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart);
43+
var scale = Math.Max(1.0, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart));
44+
Assert.True(difference / scale < 1e-8,
45+
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
46+
}
47+
}
48+
49+
// d with the sign that keeps the argument real at every sampled x, and |d| below 2/2.3^2
50+
// so that the inverse sine and cosine stay inside [-1, 1]; off it the exact checks hold too.
51+
[Theory]
52+
[InlineData("x/(k+k*sin(x))^2")]
53+
[InlineData("1/(d*q+q*d*sin(x))^2")]
54+
[InlineData("x/(k-k*cos(x))^2")]
55+
[InlineData("(a+b*asin(q*x))/((d+q*d*x)^(5/2)*sqrt(f-q*f*x))")]
56+
public void IsAnsweredAndDifferentiatesBack(string integrand)
57+
{
58+
Assert.DoesNotContain("NaN", integrand.ToEntity().Integrate("x").Stringize());
59+
DifferentiatesBack(integrand, ("a", 0.6), ("b", 1.25), ("q", 0.3), ("d", 0.75), ("f", 1.4), ("k", 1.5));
60+
DifferentiatesBack(integrand, ("a", -0.75), ("b", 0.4), ("q", -0.4), ("d", -2), ("f", 0.5), ("k", -0.3));
61+
}
62+
}
63+
}

0 commit comments

Comments
 (0)