diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 914d0be9a..ae9e5c7fd 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -1009,6 +1009,23 @@ rule that does the same for a polynomial under a square root, whose answers are | `"1/sqrt(a*cot(x)^2)".Integrate("x")` | `-sgn(tan(x)) ln(1/sqrt(1 + abs(tan(x))^2))/sqrt(a)` | `sgn(cot(x)) ln(1 + tan(x)^2)/(2 sqrt(a))` | | `"(b*tan(x)^2)^(5/2)".Integrate("x")` | `sgn(tan(x)) b^(5/2) ((abs(tan(x))^2)^2/2 - abs(tan(x))^2 + ln(abs(tan(x))^2 + 1))/2` | the same with `tan(x)` for `abs(tan(x))` | +### A power of the cotangent that is not whole below the bar is integrated beside the tangent + +**Answers where there were none.** `1/(cot(x)^(7/2) (a + b tan(x))^(3/2))` was declined while +`tan(x)^(7/2)/(a + b tan(x))^(3/2)` was answered: the substitution `u = tan(x)` wrote the cotangent +as `1/u`, and nothing read a power of `1/u` that is not whole below the bar. Such a power is written +as the tangent's above it now, `1/cot(x)^p = tan(x)^p/K`, with `K = cot(x)^p tan(x)^p` dividing the +answer: `K` is 1 where the tangent is positive and a constant on every interval where it keeps its +sign, so the answer holds where the tangent is negative too. Only below the bar and beside the +tangent; a power of the cotangent above it, or alone, keeps its answer. Rubi's 4.3.2.1 and 4.3.3.1 +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"1/(cot(x)^(7/2)*(a + b*tan(x))^(3/2))".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative in `sqrt(tan(x))` and `sqrt(a + b tan(x))`, over `K` | +| `"1/(cot(x)^(5/2)*(a + i*a*tan(x))^(5/2))".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative in `sqrt(tan(x))/sqrt(1 + i tan(x))`, over `K` | +| `"(A + B*tan(x))/(cot(x)^(3/2)*(a + i*a*tan(x)))".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative in `sqrt(tan(x))`, over `K` | + ### A fractional power of a product of trigonometric functions keeps its sign **Wrong answers, silent.** The `sin^p cos^q` reader took a power of a product with an even diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 17ade5dd3..f1658aa8f 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -8795,6 +8795,51 @@ private static bool TryReadAHomogeneousTrigonometricPolynomial( return true; } + /// + /// A power of the cotangent that is not whole below the bar of an integrand with the + /// tangent of the same argument in it, written as a power of the tangent above it: + /// 1/cot(z)^p = K tan(z)^p with K = 1/(cot(z)^p tan(z)^p), which is 1 + /// wherever the tangent is positive and a constant on every interval where it keeps its + /// sign, so it stands in front of the answer, the way Rubi writes it. + /// + /// + /// 1/(cot(x)^(7/2) (a + b tan(x))^(3/2)) was declined while + /// tan(x)^(7/2)/(a + b tan(x))^(3/2) was answered: the tangent substitution + /// writes the cotangent as 1/tan, and a power of 1/u that is not whole below + /// the bar is read by no rule. Only below the bar: above it the substitution's + /// (1/u)^p is read, and cot(x)^(3/2)/(a + b tan(x)), answered in four + /// seconds so, ran past a minute as tan(x)^(-3/2), while + /// cot(x)^(7/2) sqrt(a + b tan(x)), declined in a second, took a hundred to be. + /// And only beside the tangent; a power of the cotangent alone keeps the answer it had. + /// Rubi's 4.3.2.1 and 4.3.3.1. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveByWritingAPowerOfTheCotangentInTheTangent(Entity expr, Entity.Variable x, bool integrateByParts) + { + if (!expr.Nodes.Any(node => node is Cotanf)) + return null; + Entity? power = null; + Entity? argument = null; + foreach (var (factor, underneath) in FactorsOfTheIntegrand(expr)) + if (underneath && factor is Powf(Cotanf(var cotangentArgument), var exponent) && cotangentArgument.ContainsNode(x) + && exponent.Evaled is Number.Rational fraction && fraction is not Number.Integer) + { + if (power is not null) + return null; + (power, argument) = (factor, cotangentArgument); + } + if (power is not Powf(var cotangent, var p) || argument is null) + return null; + // The cotangent nowhere else, and the tangent somewhere: the power is then all of + // the cotangent there is, and the integrand is the tangent substitution's. + var tangent = MathS.Tan(argument); + if (expr.Nodes.Count(node => node == cotangent) != 1 || !expr.ContainsNode(tangent)) + return null; + var rewritten = expr.Replace(node => node == power ? MathS.Pow(tangent, (-p).InnerSimplified) : node); + var constant = power * MathS.Pow(tangent, p); + return Integration.ComputeAsTheSameQuestion(rewritten, x, integrateByParts)?.Pipe(answer => answer / constant); + } + /// /// An integrand that is a function of tan(x) and of nothing else, integrated by /// the substitution u = tan(x), under which dx is du/(1 + u^2). diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 087ac67fb..f3b1bb3e2 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -860,6 +860,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) => // the pair of exponents they are. After the reduction, because a power of the secant // alone is both rules' and the reduction's answer for it is shorter. if ((answer = IndefiniteIntegralSolver.SolveByTrigonometricPowerSubstitution(expr, x)) is { }) return answer; + // A power of the cotangent that is not whole beside the tangent, written as the + // tangent's with the constant that takes in front, which the substitution below reads. + if ((answer = IndefiniteIntegralSolver.SolveByWritingAPowerOfTheCotangentInTheTangent(expr, x, integrateByParts)) is { }) return answer; if ((answer = IndefiniteIntegralSolver.SolveByTangentSubstitution(expr, x, integrateByParts)) is { }) return answer; // A root of a quadratic in the tangent with a linear term, rotated until it has // none: `1/sqrt(a + b tan(x) + c tan(x)^2)` is `1/sqrt(A + C tan(y)^2)` under diff --git a/Sources/Tests/UnitTests/Calculus/TangentSubstitutionIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/TangentSubstitutionIntegralTest.cs index f11a00f71..e049bca32 100644 --- a/Sources/Tests/UnitTests/Calculus/TangentSubstitutionIntegralTest.cs +++ b/Sources/Tests/UnitTests/Calculus/TangentSubstitutionIntegralTest.cs @@ -234,6 +234,37 @@ public void SymbolsArePinnedBeforeTheParityIsDecided() [InlineData("tan(x) ^ 3")] public void APowerOfTheTangent(string integrand) => DifferentiatesBack(integrand); + /// + /// A power of the cotangent that is not whole below the bar beside the tangent, which is + /// the tangent's power above it over cot(x)^p tan(x)^p, a constant on every + /// interval where the tangent keeps its sign. Compared where the tangent is positive and + /// the integrand real, and where it is negative and the integrand imaginary, which is + /// where the constant is what keeps the answer right: it is -1 there for an odd number of + /// halves and i for a quarter. Rubi's 4.3.2.1 and 4.3.3.1. + /// + [Theory] + [InlineData("1/(cot(x)^(7/2)*(a + b*tan(x))^(3/2))")] + [InlineData("1/(cot(x)^(5/2)*(a + i*a*tan(x))^(5/2))")] + [InlineData("(A + B*tan(x))/(cot(x)^(3/2)*(a + i*a*tan(x)))")] + [InlineData("1/(cot(x)^(1/4)*(a + b*tan(x)))")] + public void APowerOfTheCotangentBelowTheBarBesideTheTangent(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("A", 0.9).Substitute("B", 1.4); + var derivative = Pin(integral.Substitute("C", 0).Differentiate("x")); + var original = Pin(integrand.ToEntity()); + foreach (var at in new[] { -1.1, -0.4, 0.3, 0.8, 1.2 }) + { + 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-9, + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + /// /// What the neighbours still answer, and must go on answering the same way: the tangent /// itself has a rule, which is reached before this and gives the shorter form.