diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 1bec563d1..32cb2980c 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -736,6 +736,18 @@ of one argument in it reads a numeric slope and bounds the degree it writes. A s | `"csc(a + b*x)^3*sin(2*a + 2*b*x)^7".ToEntity().Integrate("x")` | `integral(...)` | 127 characters | | `"csc(a + b*x)*sin(2*a + 2*b*x)^8".ToEntity().Integrate("x")` | `integral(...)` | 134 characters | +### A cosecant of a double argument beside the argument is written as a product + +**Answers where there were none.** `csc(2x) ln(tan(x))` was declined, Rubi's 4.7.7: the rule that writes +`sin(2A)` as `2 sin(A) cos(A)` beside functions of `A` declined any other function of `2A`. The cosecant is +`1/(2 sin(A) cos(A))`, one product as well, and is written so now, which leaves `ln(tan(x))` beside its derivative +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"csc(2*x)*ln(tan(x))".ToEntity().Integrate("x")` | `integral(...)` | `1/2 * ln(tan(x)) ^ 2 / 2` | +| `"csc(2*x)*ln(tan(x))^2".ToEntity().Integrate("x")` | `integral(...)` | `1/2 * ln(tan(x)) ^ 3 / 3` | + ### A sine or a cosine of a shifted argument is written in the other **Answers where there were none.** `sin(a + b x) sec(c + b x)^3` was declined, with the rest of Rubi's 4.7.1 diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 8a9d3ef73..869928c13 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -12968,9 +12968,11 @@ node is Powf(var @base, var power) && @base.ContainsNode(u) } /// - /// A sine of twice an argument beside functions of the argument, written as the product it is: - /// sin(2A) = 2 sin(A) cos(A), so that every trigonometric function is of A. - /// csc(a + b x)^3 sin(2a + 2b x)^7 is 2^7 sin(a + b x)^4 cos(a + b x)^7. + /// A sine or a cosecant of twice an argument beside functions of the argument, written as the + /// product it is: sin(2A) = 2 sin(A) cos(A), so that every trigonometric function is of + /// A. csc(a + b x)^3 sin(2a + 2b x)^7 is 2^7 sin(a + b x)^4 cos(a + b x)^7, + /// and csc(2x) ln(tan(x)) is ln(tan(x))/(2 sin(x) cos(x)), which is + /// ln(tan(x)) beside its derivative. /// /// /// The rule that writes multiples of one argument in it reads a numeric slope, and bounds the @@ -12992,10 +12994,15 @@ node is Powf(var @base, var power) && @base.ContainsNode(u) if (!TreeAnalyzer.TryGetPolyLinear(single, x, out var slope, out _) || slope.ContainsNode(x) || (twice - 2 * single).Expand().InnerSimplified.Evaled is not Number.Complex { IsZero: true }) continue; - // Only sines of the double, so that each is one product. - if (expr.Nodes.Any(node => node is Cosf or Tanf or Cotanf or Secantf or Cosecantf && node.DirectChildren.First() == twice)) + // Only sines and cosecants of the double, so that each is one product. + if (expr.Nodes.Any(node => node is Cosf or Tanf or Cotanf or Secantf && node.DirectChildren.First() == twice)) return null; - var rewritten = expr.Replace(node => node is Sinf(var a) && a == twice ? 2 * MathS.Sin(single) * MathS.Cos(single) : node); + var rewritten = expr.Replace(node => node switch + { + Sinf(var a) when a == twice => 2 * MathS.Sin(single) * MathS.Cos(single), + Cosecantf(var a) when a == twice => 1 / (2 * MathS.Sin(single) * MathS.Cos(single)), + _ => node + }); return Integration.ComputeAsTheSameQuestion(rewritten, x, integrateByParts); } return null; diff --git a/Sources/Tests/UnitTests/Calculus/ACosecantOfADoubleArgumentIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ACosecantOfADoubleArgumentIntegralTest.cs new file mode 100644 index 000000000..76a51c2f1 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ACosecantOfADoubleArgumentIntegralTest.cs @@ -0,0 +1,47 @@ +// +// 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 cosecant of twice the argument beside functions of the argument, written as + /// 1/(2 sin cos): csc(2x) ln(tan(x)) is ln(tan(x)) beside its derivative. + /// Rubi's 4.7.7. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class ACosecantOfADoubleArgumentIntegralTest + { + [Theory] + [InlineData("csc(2*x)*ln(tan(x))", 200)] + [InlineData("csc(2*x)*ln(cot(x))", 200)] + [InlineData("csc(2*x)*ln(tan(x))^2", 200)] + [InlineData("csc(2*x)*ln(a*tan(x))", 400)] + public void DifferentiatesBack(string integrand, int atMost) + { + var integral = integrand.ToEntity().Integrate("x"); + var text = integral.Stringize(); + Assert.DoesNotContain("integral(", text); + Assert.True(text.Length < atMost, $"{text.Length} characters of answer for {integrand}"); + Entity Pinned(Entity e) => e.Substitute("a", 2).Substitute("b", 3).Substitute("c", "5/4".ToEntity()); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + foreach (var at in new[] { "0.35", "0.6", "1.1", "1.4" }) + { + var want = original.Substitute("x", at.ToEntity()).EvalNumerical(); + var got = derivative.Substitute("x", at.ToEntity()).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}"); + } + } + } +}