From 15f95526a66b1888efe719338204cf19ebf1654d Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 4 Oct 2026 05:37:07 +0000 Subject: [PATCH] A quadratic sharing a root off the real line with a linear beside it is written over that root 1/((a + i a tan(x)) (c + d tan(x))) is under the tangent substitution a rational function over (1 + i u)(c + d u)(1 + u^2), and 1 + u^2 is (1 + i u)(1 - i u): the written factors share a linear, which the divisors over the rationals do not see. With a symbol in the denominator the quadratic is written over the linears of its roots and the shared one taken as one power, for the splits. Rubi's 4.3.2.1. Part of #718. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 17 ++++ .../Integration/IndefiniteIntegralSolver.cs | 77 +++++++++++++++++++ ...lexRootSharedWithAQuadraticIntegralTest.cs | 53 +++++++++++++ 3 files changed, 147 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/ComplexRootSharedWithAQuadraticIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index e9ae2c6a1..36a94e42a 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -634,6 +634,23 @@ left as it was, since the sum is real on both sides of 0 and `x^(j p)` is not. R | `"1/(x*sqrt(b*x^(2/3) + a*x))".ToEntity().Integrate("x")` | `integral(...)` | `K` times an antiderivative in `sqrt(b + a x^(1/3))` | | `"x/sqrt(1 + 1/(c*x)^2)".ToEntity().Integrate("x")` | `integral(...)` | `sgn(x) (x sqrt(x^2 + 1/c^2)/2 - ln(2 x + 2 sqrt(x^2 + 1/c^2))/(2 c^2))`, for any `c` but 0 | +### A quadratic sharing a root off the real line with a linear beside it is written over that root + +**Answers where there were none.** `1/((a + i a tan(x)) (c + d tan(x)))`, Rubi's 4.3.2.1, is under the +tangent substitution a rational function over `(1 + i u)(c + d u)(1 + u^2)`, and `1 + u^2` is +`(1 + i u)(1 - i u)`: the written factors share a linear. The splits read written factors as coprime, +and the divisors that take shared factors apart are taken over the rationals, which the imaginary unit +is not; it was declined, and with a square of `c + d tan(x)` it ran past a minute. With a symbol in the +denominator, a quadratic that has a root off the real line in common with a linear beside it is now +written as its leading coefficient times the linears of its two roots, and the shared linear taken as +one power ([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"1/((a + i*a*tan(x))*(c + d*tan(x)))".ToEntity().Integrate("x")` | `integral(...)` | a quotient by `tan(x) - i` and logarithms | +| `"1/((a + i*a*tan(x))^2*(c + d*tan(x)))".ToEntity().Integrate("x")` | `integral(...)` | the same | +| `"1/((a + i*a*tan(x))*(c + d*tan(x))^2)".ToEntity().Integrate("x")` | `integral(...)` | the same | + ### A rational function of a sine over a symbolic quadratic in it is integrated over the two roots **Improvement, not silent.** `sin(x)/(a + b sin(x) + c sin(x)^2)` and everything rational in diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 9965d1608..80b7fd011 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -696,6 +696,17 @@ is var (multiple, leftover) ?? Integration.ComputeIndefiniteIntegral(above / below, x, integrateByParts)) is { } overWhatTheyShare) return overWhatTheyShare; + // And a linear with a root off the real line that a quadratic beside it shares, which + // the divisors over the rationals above cannot see: `1 + i u` beside `1 + u^2`, which is + // `(1 + i u)(1 - i u)`, what the tangent substitution makes of Rubi's 4.3.2.1 + // `1/((a + i a tan(x)) (c + d tan(x)))`. The quadratic is written over the linears of + // its roots and the shared one taken as one power, coprime and squarefree for the + // splits below. + if (OverAComplexRootSharedWithAQuadratic(denominator, x) is { } overTheSharedRoot + && (SolveByPartialFractions(numerator / overTheSharedRoot, x, integrateByParts) + ?? Integration.ComputeIndefiniteIntegral(numerator / overTheSharedRoot, x, integrateByParts)) is { } overTheComplexRoot) + return overTheComplexRoot; + // Written linear factors with symbols in their coefficients, two or more, one of // them to a power: decomposed over the written factors, the coefficients read // off derivatives at the roots and each a line. First, before the respellings @@ -12334,6 +12345,72 @@ private static bool IsASumOfMonomials(Entity expr, Entity.Variable x) return changed ? product : null; } + /// + /// with every quadratic factor that has a root off the real line + /// in common with a linear factor beside it written as its leading coefficient times the + /// linears of its two roots, each linear with such a root written as its slope times + /// x - r, and the powers of one linear gathered into one; + /// where no quadratic shares such a root. (1 + i x)(1 + x^2) is + /// i (x - i)^2 (x + i). + /// + private static Entity? OverAComplexRootSharedWithAQuadratic(Entity denominator, Entity.Variable x) + { + // With numbers only, the split over the rationals and the imaginary unit answers it as + // it is written. + if (!denominator.Vars.Any(symbol => symbol != x)) + return null; + var written = new List<(Entity Base, EInteger Power)>(); + Entity constant = Number.Integer.One; + foreach (var factor in Mulf.LinearChildren(denominator)) + { + if (!factor.ContainsNode(x)) + { + constant = constant * factor; + continue; + } + written.Add(factor is Powf(var @base, Number.Integer n) && n.EInteger.Sign > 0 ? (@base, n.EInteger) : (factor, EInteger.One)); + } + // The roots of the linears that are not real, each with the linear's slope. + var roots = new Dictionary(); + foreach (var (@base, _) in written) + if (TreeAnalyzer.TryGetPolyLinear(@base, x, out var slope, out var offset) && !TreeAnalyzer.IsZero(slope) + && Functions.PartialFractions.Bare((-offset / slope).Simplify()).Evaled is Number.Complex root && root is not Number.Real) + roots[@base] = (root, slope); + if (roots.Count == 0) + return null; + var gathered = new Dictionary(); + void Add(Entity @base, EInteger power) => gathered[@base] = gathered.TryGetValue(@base, out var before) ? before.Add(power) : power; + var shared = false; + foreach (var (@base, power) in written) + { + if (roots.TryGetValue(@base, out var linear)) + { + constant = constant * MathS.Pow(linear.Slope, Number.Integer.Create(power)); + Add((x - linear.Root).InnerSimplified, power); + continue; + } + if (TreeAnalyzer.TryGetPolyQuadratic(@base, x, out var a, out var b, out var c) && !TreeAnalyzer.IsZero(a) + && a.Evaled is Number.Complex && b.Evaled is Number.Complex && c.Evaled is Number.Complex + && roots.Values.FirstOrDefault(candidate => (a * candidate.Root * candidate.Root + b * candidate.Root + c).Evaled is Number.Complex { IsZero: true }) is var (root, _) + && root is not null) + { + shared = true; + var other = (-b / a - root).Evaled; + constant = constant * MathS.Pow(a, Number.Integer.Create(power)); + Add((x - root).InnerSimplified, power); + Add((x - other).InnerSimplified, power); + continue; + } + Add(@base, power); + } + if (!shared) + return null; + Entity product = constant; + foreach (var pair in gathered) + product = product * (pair.Value.Equals(EInteger.One) ? pair.Key : MathS.Pow(pair.Key, Number.Integer.Create(pair.Value))); + return product; + } + /// /// over , the denominator's /// written factors taken apart over their greatest common divisors -- with one another, diff --git a/Sources/Tests/UnitTests/Calculus/ComplexRootSharedWithAQuadraticIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ComplexRootSharedWithAQuadraticIntegralTest.cs new file mode 100644 index 000000000..e30cf1cc3 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ComplexRootSharedWithAQuadraticIntegralTest.cs @@ -0,0 +1,53 @@ +// +// 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 linear with a root off the real line that a quadratic beside it shares, with symbols in + /// the rest: 1/((1 + i u)(c + d u)(1 + u^2)), where 1 + u^2 is + /// (1 + i u)(1 - i u), the tangent substitution's form of Rubi's 4.3.2.1 + /// 1/((a + i a tan(x)) (c + d tan(x))), declined or past a minute. The integrands are + /// complex for a real x, and compared as complex numbers. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class ComplexRootSharedWithAQuadraticIntegralTest + { + [Theory] + [InlineData("1/((1 + i*x)*(c + d*x)*(1 + x^2))")] + [InlineData("1/((a + i*a*tan(x))*(c + d*tan(x)))")] + [InlineData("1/((a + i*a*tan(x))^2*(c + d*tan(x)))")] + [InlineData("1/((a + i*a*tan(x))*(c + d*tan(x))^2)")] + public void OverTheSharedRoot(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + Assert.DoesNotContain("NaN", integral.Stringize()); + Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("c", 1.1).Substitute("d", 0.6); + 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(); + if (want.IsNaN) + continue; + compared++; + var got = derivative.Substitute("x", at).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) + 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}"); + } + } +}