Skip to content

Commit dbe5b38

Browse files
The integrand the root's substitution leaves is cancelled as written, not simplified (#1714)
* The integrand the root's substitution leaves has its common factor cancelled as written, not simplified A rational function of x^n beside (c + d x^n)^(k - 1/n) is a rational function of u = x/(c + d x^n)^(1/n), and the integrand in u was simplified before it was integrated. What that found is the factor the substitution leaves on both sides of the bar, and with symbols in it the search grew with the power of the binomial: 1/((a + b x^2)^(5/2)(1 + x^2)) took 5 s, the 7/2 power past 30 s. Each factor written alike above and below is cancelled power for power now, and the rest is left as it is written, which the rules after it read better than an expansion: (a + b x^4)^(7/4)/(c + d x^4) is answered in a second. Part of #718 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura * Each base of the root-divided integrand is its polynomial in u The substitution writes a base twice over where the radicand is a binomial in a half-angle tangent: under a secant's, 1 - (-u^2) - u^2, which is 1 and which nothing after it collects, so the integrand went on with a degree-eight numerator that is not there. sqrt(a + a sec(e + f x))/(c - c sec(e + f x))^4 ran past a minute; it is 1.2 s with each base written as its polynomial in u before the factors written alike on both sides are cancelled. Part of #718. 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 97c4b69 commit dbe5b38

3 files changed

Lines changed: 155 additions & 2 deletions

File tree

‎BREAKING-CHANGES.md‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,23 @@ taken apart into pieces each closed the same way
665665
| `"(2 + x)/((2 + 4*x - 3*x^2)*(1 + 3*x + 2*x^2)^(3/2))".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative |
666666
| `"1/((x^2 + 1)*sqrt(x^2 + x + 1))".ToEntity().Integrate("x")` | `integral(...)` | a logarithm and an arctangent |
667667

668+
### A rational function of `x^n` beside a power of a symbolic binomial is integrated without a search
669+
670+
**Answers where there were none.** A rational function of `x^n` beside `(c + d x^n)^(k - 1/n)` is a
671+
rational function of `u = x/(c + d x^n)^(1/n)`, and the integrand in `u` was simplified before it
672+
was integrated. With symbols in it the search for a simpler form grew with the power of the
673+
binomial: `1/((a + b x^2)^(5/2) (1 + x^2))` took 5 s on the unreleased master, the `7/2` power ran
674+
past 30 s, and both were declined on 2.5.0. What the search found was the factor the substitution
675+
leaves on both sides of the bar, which is cancelled as it is written now, the other factors left as
676+
they are written ([#718](https://github.com/asc-community/AngouriMath/issues/718)).
677+
678+
| Input | Was (2.5.0) | Now |
679+
|---|---|---|
680+
| `"1/((a + b*x^2)^(5/2)*(1 + x^2))".ToEntity().Integrate("x")` | `integral(...)` | powers of `x/sqrt(a + b x^2)`, and an arctangent or a logarithm by the sign of `a - b`, in 0.6 s |
681+
| `"1/((a + b*x^2)^(7/2)*(1 + x^2))".ToEntity().Integrate("x")` | `integral(...)` | the same, in 0.7 s |
682+
| `"1/((a + b*x^2)^(9/2)*(1 + x^2))".ToEntity().Integrate("x")` | `integral(...)` | the same, in 1 s |
683+
| `"x^2/((a + b*x^2)^(7/2)*(c + d*x^2))".ToEntity().Integrate("x")` | `integral(...)` | the same by the sign of `c (a d - b c)`, in 1 s |
684+
668685
### A power of a multiple of a quadratic's derivative beside a power of the quadratic is a binomial
669686

670687
**Improvement, not silent.** `(b d + 2 c d x)^m (a + b x + c x^2)^p`, with a power that is not whole,

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

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4207,10 +4207,17 @@ node is Powf(var xAgain, Number.Integer power) && xAgain == x && power.EInteger.
42074207
var uToTheN = MathS.Pow(u, n);
42084208
var oneMinusDUToTheN = 1 - d * uToTheN;
42094209
var binomialInU = c / oneMinusDUToTheN;
4210-
var integrand = Functions.SingleQuotient.Combine(
4210+
// The factor the substitution leaves on both sides cancelled as it is written, not simplified:
4211+
// `(1 - u^4)/((1 + u^4)(1 - u^4))` for Timofeev's above, which the rational integrator does
4212+
// not cancel, and the search for a simpler form that did grows with the power of the binomial
4213+
// while the symbols are in it: `1/((a + b x^2)^(7/2) (1 + x^2))` ran past 90 s on it. Each
4214+
// base is its polynomial in u first, which collects what the substitution writes twice --
4215+
// `1 - (-u^2) - u^2` is 1 under a secant's half angle -- and the factors stay as they are
4216+
// written, which the rules after this read better than their product expanded.
4217+
var integrand = WithTheFactorsWrittenOnBothSidesCancelled(u, Functions.SingleQuotient.Combine(
42114218
inXToTheN.Substitute(xToTheN, c * uToTheN / oneMinusDUToTheN)
42124219
* (k.IsZero ? Number.Integer.One : MathS.Pow(binomialInU, Number.Integer.Create(k)))
4213-
/ oneMinusDUToTheN).Simplify();
4220+
/ oneMinusDUToTheN).InnerSimplified);
42144221
if (integrand is Providedf(var inner, _))
42154222
integrand = inner;
42164223
if (integrand.ContainsNode(x) || integrand.Nodes.Any(node => node == MathS.NaN))
@@ -4221,6 +4228,66 @@ node is Powf(var xAgain, Number.Integer power) && xAgain == x && power.EInteger.
42214228
return answer.Nodes.Any(node => node == MathS.NaN) ? null : answer;
42224229
}
42234230

4231+
/// <summary>
4232+
/// <paramref name="quotient"/> with each base that is a sum written as its polynomial in
4233+
/// <paramref name="u"/>, and each factor then written alike above and below the bar
4234+
/// cancelled, power for power; the factors are kept as factors.
4235+
/// </summary>
4236+
private static Entity WithTheFactorsWrittenOnBothSidesCancelled(Entity.Variable u, Entity quotient)
4237+
{
4238+
var (top, bottom) = Functions.SingleQuotient.Of(quotient);
4239+
Entity AsItsPolynomial(Entity @base)
4240+
{
4241+
if (@base is not (Sumf or Minusf) || !@base.ContainsNode(u)
4242+
|| !TreeAnalyzer.TryGetPolynomial(@base, u, out var terms)
4243+
|| terms.Any(term => term.Key.Sign < 0 || term.Value.ContainsNode(u)))
4244+
return @base;
4245+
Entity written = Number.Integer.Zero;
4246+
foreach (var term in terms.OrderBy(term => term.Key))
4247+
{
4248+
var coefficient = term.Value.InnerSimplified;
4249+
if (Functions.PartialFractions.IsZeroAsAValue(coefficient))
4250+
continue;
4251+
var monomial = term.Key.IsZero ? coefficient : coefficient * MathS.Pow(u, Number.Integer.Create(term.Key));
4252+
written = written == Number.Integer.Zero ? monomial : written + monomial;
4253+
}
4254+
return written.InnerSimplified;
4255+
}
4256+
List<(Entity Base, EInteger Power)> Factors(Entity side)
4257+
{
4258+
var factors = new List<(Entity Base, EInteger Power)>();
4259+
foreach (var factor in Mulf.LinearChildren(side))
4260+
{
4261+
var (@base, power) = factor is Powf(var raised, Number.Integer exponent) && exponent.EInteger.Sign > 0
4262+
? (raised, exponent.EInteger) : (factor, EInteger.One);
4263+
@base = AsItsPolynomial(@base);
4264+
var at = factors.FindIndex(written => written.Base == @base);
4265+
if (at < 0)
4266+
factors.Add((@base, power));
4267+
else
4268+
factors[at] = (@base, factors[at].Power.Add(power));
4269+
}
4270+
return factors;
4271+
}
4272+
var above = Factors(top);
4273+
var below = Factors(bottom);
4274+
for (var i = 0; i < above.Count; i++)
4275+
{
4276+
if (above[i].Base is Number)
4277+
continue;
4278+
var j = below.FindIndex(written => written.Base == above[i].Base);
4279+
if (j < 0)
4280+
continue;
4281+
var common = EInteger.Min(above[i].Power, below[j].Power);
4282+
above[i] = (above[i].Base, above[i].Power.Subtract(common));
4283+
below[j] = (below[j].Base, below[j].Power.Subtract(common));
4284+
}
4285+
static Entity Product(List<(Entity Base, EInteger Power)> factors)
4286+
=> factors.Where(written => !written.Power.IsZero).Aggregate((Entity)Number.Integer.One,
4287+
(product, written) => product * (written.Power.Equals(EInteger.One) ? written.Base : MathS.Pow(written.Base, Number.Integer.Create(written.Power))));
4288+
return (Product(above) / Product(below)).InnerSimplified;
4289+
}
4290+
42244291
/// <summary>
42254292
/// Reads <paramref name="expr"/> as <c>c + d x^n</c> with <c>c</c> and <c>d</c> free of
42264293
/// <paramref name="x"/>.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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 rational function of <c>x^n</c> times <c>(c + d x^n)^(k - 1/n)</c>, which
16+
/// <c>u = x/(c + d x^n)^(1/n)</c> makes a rational function of <c>u</c>.
17+
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
18+
/// </summary>
19+
/// <remarks>
20+
/// Checked by differentiating back at points where the integrand is real, with the symbols
21+
/// pinned after the integral is taken.
22+
/// </remarks>
23+
[Trait("Area", "Calculus")]
24+
public sealed class DividedByTheRootIntegralTest
25+
{
26+
private static void DifferentiatesBack(string integrand, double[] points, params (string, double)[] pins)
27+
{
28+
var integral = integrand.ToEntity().Integrate("x");
29+
Assert.DoesNotContain("integral(", integral.Stringize());
30+
var derivative = integral.Substitute("C", 0).Differentiate("x");
31+
Entity original = integrand.ToEntity();
32+
foreach (var (name, value) in pins)
33+
{
34+
derivative = derivative.Substitute(name, value);
35+
original = original.Substitute(name, value);
36+
}
37+
var compared = 0;
38+
foreach (var at in points)
39+
{
40+
var got = derivative.Substitute("x", at).EvalNumerical();
41+
var want = original.Substitute("x", at).EvalNumerical();
42+
if (got.IsNaN || want.IsNaN)
43+
continue;
44+
compared++;
45+
var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart);
46+
var scale = Math.Max(1.0, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart));
47+
Assert.True(difference / scale < 1e-9,
48+
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
49+
}
50+
Assert.True(compared >= 3, $"only {compared} points could be compared for {integrand}");
51+
}
52+
53+
/// <summary>Timofeev's, which is <c>1/(1 + u^4)</c> under the substitution.</summary>
54+
[Fact]
55+
public void TimofeevsQuarticBesideAFourthRoot()
56+
=> DifferentiatesBack("1/((1 + x^4)*(2 + x^4)^(1/4))", new[] { -1.3, -0.4, 0.3, 0.9, 1.7 });
57+
58+
/// <summary>
59+
/// A higher power of the root with symbols in it, beside another quadratic: the rational
60+
/// function of <c>u</c> grows with the power, and the larger ones ran past 90 s.
61+
/// </summary>
62+
[Theory]
63+
[InlineData("1/((a + b*x^2)^(5/2)*(1 + x^2))")]
64+
[InlineData("1/((a + b*x^2)^(7/2)*(1 + x^2))")]
65+
[InlineData("1/((a + b*x^2)^(9/2)*(1 + x^2))")]
66+
public void AHalfOddPowerOfASymbolicQuadratic(string integrand)
67+
=> DifferentiatesBack(integrand, new[] { -1.3, -0.4, 0.3, 0.9, 1.7 }, ("a", 1.3), ("b", 0.7));
68+
}
69+
}

0 commit comments

Comments
 (0)