diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 05ed4545d..8c3f2c86f 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -325,6 +325,19 @@ by the linear's coefficients, `(b d + 2c d x)^9`, which ran past a minute | `"(b + 2*c*x)^9/(a + b*x + c*x^2)^3".ToEntity().Integrate("x")` | past a minute; 76,153 characters on the unreleased master | 607 characters | | `"(b*d + 2*c*d*x)^9/(a + b*x + c*x^2)^3".ToEntity().Integrate("x")` | past a minute | 611 characters | +### A trinomial the numerator holds is divided out of a power of it + +**A shorter answer, where 2.5.0 had none.** `(d + g x + f x^2)(a + b x^2 + c x^4)/(a + b x^2 + c x^4)^4`, written +multiplied out as Rubi's 1.2.2.5 writes it, was declined after a minute by 2.5.0, and is answered on the unreleased +master in 98,237 characters. The reduction of a polynomial over a power of a trinomial in `x^n` divides the trinomial +out of the numerator first now, wherever it divides exactly, with the trinomial's content in the symbols taken out of +it, since it arrives made monic: the answer is that of `(d + g x + f x^2)/(a + b x^2 + c x^4)^3`, 19,037 characters +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"(a*d + a*g*x + (b*d + a*f)*x^2 + b*g*x^3 + (c*d + b*f)*x^4 + c*g*x^5 + c*f*x^6)/(a + b*x^2 + c*x^4)^4".ToEntity().Integrate("x")` | `integral(...)` | 19,037 characters | + ### Half-odd powers of `a + a sec` and `c + d sec` together are integrated in the half angle's sine **Answers where there were none.** `sec(e + f x) sqrt(a + a sec(e + f x))/sqrt(c + d sec(e + f x))` was declined, diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 881634610..37b5fe6ae 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -1856,6 +1856,47 @@ Entity InU(Dictionary polynomial) /// private const int MaximumPowerOfABinomial = 12; + /// + /// divided by as polynomials in all + /// their symbols, each over its own bar, where the division of what is above the bars is + /// exact; where it is not, or where either is not a polynomial + /// over a bar free of . + /// + /// + /// The bars are there because the partial fractions make a block monic before this reads + /// it: `a + b x^2 + c x^4` arrives as `a/c + b/c x^2 + x^4`. + /// + private static Entity? ExactlyDivided(Entity dividend, Entity divisor, Entity.Variable x) + { + var (above, aboveBar) = Functions.SingleQuotient.Of(Functions.SingleQuotient.Combine(dividend)); + var (below, belowBar) = Functions.SingleQuotient.Of(Functions.SingleQuotient.Combine(divisor)); + if (aboveBar.ContainsNode(x) || belowBar.ContainsNode(x)) + return null; + var variables = above.Vars.Concat(below.Vars).Distinct().OrderBy(v => v.Name, System.StringComparer.Ordinal).ToList(); + if (variables.Count == 0 || variables.Count > MultivariatePolynomial.MaxVariables) + return null; + var indices = new Dictionary(); + for (var i = 0; i < variables.Count; i++) + indices[variables[i]] = i; + if (MultivariatePolynomial.TryParse(above, indices) is not { IsZero: false } top + || MultivariatePolynomial.TryParse(below, indices) is not { IsConstant: false } bottom + || !indices.TryGetValue(x, out var at)) + return null; + // Its content in x goes below the bar: the quotient `a/c + b/c x^2 + x^4` is combined as + // `c (a + b x^2 + c x^4)` over `c^2`, and the numerator holds the trinomial, not the c. + var rest = Enumerable.Range(0, variables.Count).Where(variable => variable != at).ToList(); + Entity content = Number.Integer.One; + if (Functions.PolynomialGcd.ContentIn(bottom, at, rest, 0) is { IsConstant: false } inTheSymbols + && bottom.DivideExact(inTheSymbols) is { } primitive) + { + bottom = primitive; + content = inTheSymbols.ToEntity(variables); + } + if (top.DivideExact(bottom) is not { } quotient) + return null; + return (quotient.ToEntity(variables) * belowBar / (aboveBar * content)).InnerSimplified; + } + /// /// A polynomial over a power of a trinomial in x^n, /// P(x)/(a + b x^n + c x^(2n))^k with n >= 2 and k >= 2, a power at @@ -1904,6 +1945,14 @@ Entity InU(Dictionary polynomial) else return null; } + // A numerator the trinomial divides is divided by it first: Rubi's 1.2.2.5 writes + // `(d + e x + f x^2)(a + b x^2 + c x^4)` multiplied out over the fourth power, and over it + // the answer was 99,000 characters where over the third it is 20,000. + while (trinomial is { } && power > 1 && ExactlyDivided(numerator, trinomial, x) is { } fewer) + { + numerator = fewer; + power--; + } if (trinomial is null || power > MaximumPowerOfABinomial || !TreeAnalyzer.TryGetPolynomial(trinomial, x, out var below) || below.Count != 3 || below.Keys.Any(degree => !degree.CanFitInInt32()) || below.Values.Any(coefficient => coefficient.ContainsNode(x)) diff --git a/Sources/Tests/UnitTests/Calculus/ATrinomialTheNumeratorHoldsIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ATrinomialTheNumeratorHoldsIntegralTest.cs new file mode 100644 index 000000000..55e8c8043 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ATrinomialTheNumeratorHoldsIntegralTest.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) 2019-2026 Angouri. +// AngouriMath is licensed under MIT. +// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md. +// Website: https://am.angouri.org. +// + +using System; +using AngouriMath.Extensions; +using Xunit; + +namespace AngouriMath.Tests.Calculus +{ + /// + /// A polynomial over a power of a trinomial in x^n that holds the trinomial has it + /// divided out first. Rubi's 1.2.2.5. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class ATrinomialTheNumeratorHoldsIntegralTest + { + [Fact] + public void DifferentiatesBack() + { + const string integrand = "(a*d + a*g*x + (b*d + a*f)*x^2 + b*g*x^3 + (c*d + b*f)*x^4 + c*g*x^5 + c*f*x^6)/(a + b*x^2 + c*x^4)^4"; + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + Entity Pinned(Entity e) => e.Substitute("a", 2).Substitute("b", 3).Substitute("c", 5) + .Substitute("d", -1).Substitute("g", 7).Substitute("f", 1); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + foreach (var at in new[] { -1.7, -0.6, 0.45, 1.3 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + var got = derivative.Substitute("x", at).EvalNumerical(); + Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart) + < 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart)), + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + } +}