diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index e8604ab5f..ec3ab26df 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -826,6 +826,21 @@ those now. Each answer holds on both sides of zero, an odd root of a negative be | `"x^4*(a + b*x^3)^(1/3)/(c + d*x^3)".ToEntity().Integrate("x")` | `integral(...)` | the same, beside a rational function of it | | `"x/((1 + x^3)^(2/3)*(2 + x^3))".ToEntity().Integrate("x")` | `integral(...)` | the same with numbers | +### A rational function of the tangent of a linear argument with symbols in it is not simplified under the linear substitution + +**Answers where there were none.** The substitution search reaches `tan(g + h x)` through `u = g + h x`, +and simplified the quotient of the integrand by `h` in `u` before asking for its integral: with the +nine symbols of Rubi's 4.3.4.2 in it that took about half a minute, for an expression that is the +integrand with its argument renamed and holds nothing to simplify. Under a linear candidate, where +the integrand in `u` is a function of the tangent alone and holds no imaginary unit, the quotient +is handed on as it is +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"(a + b*tan(g + h*x))*(A + B*tan(g + h*x) + K*tan(g + h*x)^2)/(c + d*tan(g + h*x))^2".ToEntity().Integrate("x")` | `integral(...)` after 33 s | the antiderivative, in 4.5 s | +| `"(c + d*tan(g + h*x))^3*(A + B*tan(g + h*x) + K*tan(g + h*x)^2)/(a + b*tan(g + h*x))^3".ToEntity().Integrate("x")` | no answer within a minute and a half | the antiderivative, in 18 s | + ### A rational function of `x^n` beside a power of a symbolic binomial is integrated without a search **Answers where there were none.** A rational function of `x^n` beside `(c + d x^n)^(k - 1/n)` is a diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index b382d6f0d..8bf2e2a27 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -23649,6 +23649,17 @@ private static bool HoldsAnEvenRootBesides(Entity expr, Entity radicand, Entity. return false; } + /// + /// Whether every function of in is its tangent + /// or its cotangent: sums, products, quotients and powers of those, and of anything free of + /// . + /// + private static bool IsAFunctionOfTheTangentAlone(Entity expr, Entity.Variable u) + => expr.Nodes.All(node => node is Sumf or Minusf or Mulf or Divf or Powf or Number or Variable + || !node.ContainsNode(u) + || node is Tanf(var argument) && argument == u + || node is Cotanf(var cotangentArgument) && cotangentArgument == u); + internal static Entity? SolveBySubstitution(Entity expr, Entity.Variable x, bool integrateByParts = true) { // A rational function over written linear factors with symbols in their @@ -23800,7 +23811,20 @@ private static bool HoldsAnEvenRootBesides(Entity expr, Entity radicand, Entity. && expr.Complexity <= (expr.Vars.Any(v => v != x) ? LargestSymbolicIntegrandCollected : LargestIntegrandOfferedSums) ? WithThePowersOfXCollected(Functions.SingleQuotient.Combine(expr / duDx), x) : source / duDx; - integrandInU = SimplifiedWithoutTheImaginaryUnit(InTermsOf(quotient, u, uSub, x), expr); + // Under a linear candidate the quotient is the integrand with its argument + // renamed, over a constant, and where it is a function of the tangent alone + // there is nothing in it for the simplifier to find: + // `(a + b tan(e + f x))(A + B tan(e + f x) + C tan(e + f x)^2)/(c + d tan(e + f x))^2` + // under `u = e + f x` spent 28 of its 32 s being simplified, nine symbols and + // nothing to cancel. Only there: beside a sine, a secant, a logarithm or the + // imaginary unit the simplified form is what the rules after this read, and + // `cot(c + d x)^8/(a + a sin(c + d x))` went from one second to seventy without it. + var inTermsOfU = InTermsOf(quotient, u, uSub, x); + integrandInU = !inTermsOfU.ContainsNode(x) + && TreeAnalyzer.TryGetPolyLinear(u, x, out var slope, out var offset) && !slope.ContainsNode(x) && !offset.ContainsNode(x) + && IsAFunctionOfTheTangentAlone(inTermsOfU, uSub) && !HoldsTheImaginaryUnit(inTermsOfU) + ? Functions.PartialFractions.Bare(inTermsOfU.InnerSimplified) + : SimplifiedWithoutTheImaginaryUnit(inTermsOfU, expr); // A factor written on both sides of the bar cancelled, where x survived: // the one-level simplification leaves `u/((a w + b)^2 p u)` as it is, and // the candidate was refused for the u it did not cancel. diff --git a/Sources/Tests/UnitTests/Calculus/LinearArgumentSubstitutionTest.cs b/Sources/Tests/UnitTests/Calculus/LinearArgumentSubstitutionTest.cs new file mode 100644 index 000000000..73c596001 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/LinearArgumentSubstitutionTest.cs @@ -0,0 +1,54 @@ +// +// 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 rational function of the tangent of a linear argument with symbols for its + /// coefficients, Rubi's 4.3.3.1 and 4.3.4.2. The substitution search reaches the tangent + /// through u = g + h x, and under a linear candidate the quotient by its derivative is + /// the integrand with its argument renamed over a constant: there is nothing in it to + /// simplify, and simplifying it with nine symbols in it was most of the time each of these + /// took. + /// #718 + /// + /// + /// Checked by differentiating back with the symbols pinned after the integral is taken, at + /// points where the tangent and every factor below the bar stay away from their poles and + /// zeros. + /// + [Trait("Area", "Calculus")] + public sealed class LinearArgumentSubstitutionTest + { + private static readonly double[] Points = { 0.2, 0.5, 0.8 }; + + [Theory] + [InlineData("(a + b*tan(g + h*x))*(A + B*tan(g + h*x) + K*tan(g + h*x)^2)/(c + d*tan(g + h*x))^2")] + [InlineData("(a + b*tan(g + h*x))*(A + B*tan(g + h*x))/(c + d*tan(g + h*x))^2")] + [InlineData("(A + B*tan(g + h*x) + K*tan(g + h*x)^2)/((a + b*tan(g + h*x))*(c + d*tan(g + h*x)))")] + public void ARationalFunctionOfTheTangentOfALinear(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + Entity Pin(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 2.1).Substitute("d", 0.9) + .Substitute("g", 0.3).Substitute("h", 1.1).Substitute("A", 0.5).Substitute("B", 1.7).Substitute("K", 0.4); + var derivative = Pin(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pin(integrand.ToEntity()); + foreach (var point in Points) + { + var expected = original.Substitute("x", point).EvalNumerical().RealPart.EDecimal.ToDouble(); + var actual = derivative.Substitute("x", point).EvalNumerical().RealPart.EDecimal.ToDouble(); + Assert.True(Math.Abs(expected - actual) < 1e-8 * Math.Max(1, Math.Abs(expected)), + $"d/dx of the antiderivative of {integrand} is {actual} at x = {point}, where the integrand is {expected}"); + } + } + } +}