From 22d401bf12295d1b399b1096a3151407d8b8ff93 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Thu, 8 Oct 2026 02:14:23 +0000 Subject: [PATCH] a + i a tan of a shifted linear below the bar is integrated in the linear Where every trigonometric function is of one linear other than x, the rule that writes a + i a tan(z) as an exponential integrates in u = g + f x first. Part of #718. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 13 +++++ .../Integration/IndefiniteIntegralSolver.cs | 49 ++++++++++++++++++ ...yTangentSumOfAShiftedLinearIntegralTest.cs | 50 +++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/AnImaginaryTangentSumOfAShiftedLinearIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index b4ecc7c5c..3c6f937bd 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -208,6 +208,19 @@ after it |---|---|---| | `"(a*c + b*c*x)^(-3-2*p)*(f + g*x)*(a^2 + 2*a*b*x + b^2*x^2)^p".ToEntity().Integrate("x")` | `integral(...)`; an answer with no value on the unreleased master | the antiderivative | +### `a + i a tan` of a shifted linear below the bar is integrated in the linear + +**Answers where there were none.** `sqrt(a + i a tan(g + f x)) (A + B tan(g + f x))/sqrt(c - i c tan(g + f x))` +ran past thirty seconds, where with `x` for the argument it is answered in one: the rule that writes +`a + i a tan(z)` as an exponential expanded the exponential of `i (g + f x)` and its phase into a search. +Where every trigonometric function in the integrand is of one linear other than `x`, it integrates in +`u = g + f x` first ([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"sqrt(a + i*a*tan(g + f*x))*(A + B*tan(g + f*x))/sqrt(c - i*c*tan(g + f*x))".ToEntity().Integrate("x")` | `integral(...)`; past thirty seconds on the unreleased master | 448 characters | +| `"(A + B*tan(g + f*x))/(sqrt(a + i*a*tan(g + f*x))*(c - i*c*tan(g + f*x))^(3/2))".ToEntity().Integrate("x")` | `integral(...)`; past thirty seconds on the unreleased master | 597 characters | + ### A value substituted under a binder is not captured by the name it binds **Wrong answers fixed.** An integral, a sum, a product, a derivative, a limit, a set builder and the diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 091e18f69..6eeb69950 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -26805,6 +26805,49 @@ 2 when NotWhole(sums[0].Base) != NotWhole(sums[1].Base) => NotWhole(sums[0].Base /// handed on is the integrand in another spelling. /// https://github.com/asc-community/AngouriMath/issues/718 /// + /// + /// integrated in u = g + f x where x stands only in trigonometric + /// functions of that one linear, other than x itself, and a root of something in x stands: dx = du/f. Null + /// otherwise. + /// + private static Entity? InItsOneShiftedLinear(Entity expr, Entity.Variable x, bool integrateByParts) + { + // Beside a root only: whole powers, `(a + i a tan(z))^3 (A + B tan(z))/(c - i c tan(z))^3`, the + // exponential's spelling answers sooner of the linear than of x. + if (!expr.Nodes.Any(node => node is Powf(var @base, Number.Rational power) && power is not Number.Integer && @base.ContainsNode(x))) + return null; + Entity? linear = null; + foreach (var node in expr.Nodes) + { + if (node is not (Tanf or Cotanf or Sinf or Cosf or Secantf or Cosecantf) || !node.ContainsNode(x)) + continue; + var argument = node.DirectChildren.First(); + if (linear is null) + linear = argument; + else if (linear != argument) + return null; + } + if (linear is null || !TreeAnalyzer.TryGetPolyLinear(linear, x, out var slope, out var offset) + || slope.ContainsNode(x) || offset.ContainsNode(x) || TreeAnalyzer.IsZero(slope) || linear == x) + return null; + var u = Variable.CreateUnique(expr, "u_shifted"); + var inU = expr.Replace(node => node switch + { + Tanf(var a) when a == linear => MathS.Tan(u), + Cotanf(var a) when a == linear => MathS.Cotan(u), + Sinf(var a) when a == linear => MathS.Sin(u), + Cosf(var a) when a == linear => MathS.Cos(u), + Secantf(var a) when a == linear => MathS.Sec(u), + Cosecantf(var a) when a == linear => new Cosecantf(u), + _ => node, + }); + if (inU.ContainsNode(x) + || Integration.ComputeAsAQuestionOfItsOwn((inU / slope).InnerSimplified, u, integrateByParts) is not { } answer + || answer.Nodes.Any(node => node == MathS.NaN)) + return null; + return answer.Substitute(u, linear); + } + internal static Entity? SolveByWritingAnImaginaryTangentAsAnExponential(Entity expr, Entity.Variable x, bool integrateByParts) { // Below the bar only: `(A + i A tan(z))^3` above it is a polynomial in the tangent, @@ -26872,6 +26915,12 @@ Entity AsAnExponential(Entity node) var rewritten = below.Replace(AsAnExponential); if (rewritten == below) return null; + // One linear other than x for every argument, `g + f x`, is integrated in it: the rewriting + // below expands an exponential of `i (g + f x)` and its phase into a search that ran past + // thirty seconds for `sqrt(a + i a tan(g + f x)) (A + B tan(g + f x))/sqrt(c - i c tan(g + f x))`, + // which in `u = g + f x` is answered in a second. + if (InItsOneShiftedLinear(expr, x, integrateByParts) is { } inTheLinear) + return inTheLinear; // A root of the tangent or the cotangent of that argument, or of a constant times one, is // read as written by the substitution `t = tan(z)`, where it is a root of `t`, and by // nothing in the exponential's spelling: `sqrt(tan(c + d x))/(a + i a tan(c + d x))` diff --git a/Sources/Tests/UnitTests/Calculus/AnImaginaryTangentSumOfAShiftedLinearIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AnImaginaryTangentSumOfAShiftedLinearIntegralTest.cs new file mode 100644 index 000000000..e96140c65 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AnImaginaryTangentSumOfAShiftedLinearIntegralTest.cs @@ -0,0 +1,50 @@ +// +// 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 + i a tan(g + f x) below the bar, beside other functions of the same shifted linear, + /// integrated in u = g + f x. Rubi's 4.3.3.1. The integrands are complex for a real x + /// and are compared as complex numbers. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class AnImaginaryTangentSumOfAShiftedLinearIntegralTest + { + [Theory] + [InlineData("sqrt(a + i*a*tan(g + f*x))*(A + B*tan(g + f*x))/sqrt(c - i*c*tan(g + f*x))")] + [InlineData("(A + B*tan(g + f*x))/(sqrt(a + i*a*tan(g + f*x))*(c - i*c*tan(g + f*x))^(3/2))")] + public void InTheLinear(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + var text = integral.Stringize(); + Assert.DoesNotContain("integral(", text); + Assert.True(text.Length < 5000, $"{text.Length} characters of answer for {integrand}"); + Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("c", 0.7).Substitute("g", 0.4).Substitute("f", 1.1).Substitute("A", 0.9).Substitute("B", 1.7); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + var compared = 0; + foreach (var at in new[] { -1.2, -0.7, 0.3, 0.8, 1.3, 2.9 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + var got = derivative.Substitute("x", at).EvalNumerical(); + if (want.IsNaN) + continue; + compared++; + Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart) + < 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)), + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}"); + } + } +}