diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index a6ff741eb..78a771133 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -566,6 +566,23 @@ split. It is written `b (s + r)(s^2 - r s + r^2)` now, with `r = (a/b)^(1/3)`, w | `"sec(c + d*x)^2/(a + b*sin(c + d*x)^3)".ToEntity().Integrate("x")` | `integral(...)` | 7,010 characters | | `"sec(c + d*x)^4/(a + b*sin(c + d*x)^3)".ToEntity().Integrate("x")` | `integral(...)` | 11,646 characters | +### A linear factor common to a sum below the bar is taken out of it + +**Answers where there were none.** `1/(a x + b x/log(c x^n))` was left unevaluated. With x taken out of the sum below the +bar it is `1/(x (a + b/log(c x^n)))`, a function of the logarithm beside its differential, which is answered. The same +holds for the higher powers of the logarithm and for Rubi's 3.5:37 +([#718](https://github.com/asc-community/AngouriMath/issues/718)). `tanh(a + 2 log(x))^2/x^3`, which 2.5.0 left +unevaluated, is answered by this route as well. + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"1/(a*x+b*x/ln(c*x^n))".ToEntity().Integrate("x")` | `integral(...)` | 72 characters | +| `"1/(a*x+b*x/ln(c*x^n)^2)".ToEntity().Integrate("x")` | `integral(...)` | 503 characters | +| `"1/(a*x+b*x/ln(c*x^n)^3)".ToEntity().Integrate("x")` | `integral(...)` | 514 characters | +| `"1/(a*x+b*x/ln(c*x^n)^4)".ToEntity().Integrate("x")` | `integral(...)` | 337 characters | +| `"(a*x+2*b*n*ln(c*x^n))/(a*x^2+b*x*ln(c*x^n)^2)".ToEntity().Integrate("x")` | `integral(...)` | 37 characters | +| `"tanh(a+2*ln(x))^2/x^3".ToEntity().Integrate("x")` | `integral(...)` | 7,796 characters | + ### A symbol shared by a numerator sum's coefficients is taken out before the partial fractions **Answers where there were none.** `(b d + 2c d x)^9/(a + b x + c x^2)^3` ran past a minute, where diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index af073897e..d1fbe9ec7 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -29334,6 +29334,37 @@ static bool IsZero(Entity value) => value.InnerSimplified.Evaled is Number.Compl return written == expr ? null : Integration.ComputeAsTheSameQuestion(written, x, integrateByParts); } + /// + /// A quotient whose denominator is a sum every term of which carries the same power of one + /// linear form in , with that power taken out below the bar: + /// 1/(a x + b x/ln(c x^n)) is 1/(x (a + b/ln(c x^n))), a function of the + /// logarithm times its differential. + /// + /// + /// Rubi's 3.5 1/(a x + b x/log(c x^n)^k) were left unevaluated, and the same with + /// x taken out is answered in a tenth of a second. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveByGatheringACommonLinearFactorOfASumBelowTheBar(Entity expr, Entity.Variable x, bool integrateByParts) + { + static bool IsASum(Entity e) => e is Sumf or Minusf; + Entity above, below; + var power = 1; + if (expr is Divf(var n, Powf(var d, Number.Integer k)) && IsASum(d) && k.EInteger.Sign > 0 && k.EInteger.CanFitInInt32()) + (above, below, power) = (n, d, k.EInteger.ToInt32Unchecked()); + else if (expr is Divf(var n1, var d1) && IsASum(d1)) + (above, below) = (n1, d1); + else if (expr is Powf(var d2, Number.Integer k2) && IsASum(d2) && k2.EInteger.Sign < 0 && k2.EInteger.CanFitInInt32()) + (above, below, power) = (Number.Integer.One, d2, -k2.EInteger.ToInt32Unchecked()); + else + return null; + if (TryTakeACommonLinearFactorOfASum(below, x) is not var (factored, inside)) + return null; + var taken = factored * inside; + var written = above / (power == 1 ? taken : MathS.Pow(factored, Number.Integer.Create(power)) * MathS.Pow(inside, Number.Integer.Create(power))); + return Integration.ComputeAsTheSameQuestion(written, x, integrateByParts); + } + /// /// The sum with the highest power of one linear form in that every /// term carries taken out of it, as that power and what is left; diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 0d874364b..69f3bbe02 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -1223,6 +1223,11 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) if (expr is Entity.Sumf or Entity.Minusf && (answer = IndefiniteIntegralSolver.SolveByGatheringACommonPolynomialFactorOfASum(expr, x, integrateByParts)) is { }) return answer; + // The same factor taken out of a sum below the bar: `1/(a x + b x/ln(c x^n))` is + // `1/(x (a + b/ln(c x^n)))`, a function of the logarithm beside its differential. + if (expr is Entity.Divf or Entity.Powf + && (answer = IndefiniteIntegralSolver.SolveByGatheringACommonLinearFactorOfASumBelowTheBar(expr, x, integrateByParts)) is { }) + return answer; // A power of the secant that is not whole beside the cosine, written as the cosine's // with the constant that takes in front, which the substitutions below read. if ((answer = IndefiniteIntegralSolver.SolveByWritingAPowerOfTheSecantInTheCosine(expr, x, integrateByParts)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/ACommonLinearFactorOfASumBelowTheBarIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ACommonLinearFactorOfASumBelowTheBarIntegralTest.cs new file mode 100644 index 000000000..3f4fe8350 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ACommonLinearFactorOfASumBelowTheBarIntegralTest.cs @@ -0,0 +1,57 @@ +// +// 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 power of a linear common to every term of a sum below the bar, taken out: Rubi's 3.5 + /// 1/(a x + b x/log(c x^n)^k), which was left unevaluated. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class ACommonLinearFactorOfASumBelowTheBarIntegralTest + { + private static void DifferentiatesBack(string integrand, params (string, double)[] pins) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + + var pinned = integral.Substitute("C", 0); + Entity original = integrand.ToEntity(); + foreach (var (name, value) in pins) + { + pinned = pinned.Substitute(name, value); + original = original.Substitute(name, value); + } + var derivative = pinned.Differentiate("x"); + // Where the logarithm is real and away from the zeros of the denominator. + foreach (var at in new[] { 0.4, 1.9, 2.7, 3.3 }) + { + var got = derivative.Substitute("x", at).EvalNumerical(); + var want = original.Substitute("x", at).EvalNumerical(); + var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart); + var scale = Math.Max(1.0, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)); + Assert.True(difference / scale < 1e-8, + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + + [Theory] + [InlineData("1/(a*x+b*x/ln(c*x^n))")] + [InlineData("1/(a*x+b*x/ln(c*x^n)^2)")] + [InlineData("1/(a*x+b*x/ln(c*x^n)^3)")] + public void IsAnsweredAndDifferentiatesBack(string integrand) + { + DifferentiatesBack(integrand, ("a", 1.3), ("b", 0.7), ("c", 2.5), ("n", 2)); + DifferentiatesBack(integrand, ("a", 0.6), ("b", -0.2), ("c", 1.5), ("n", 3)); + } + } +}