diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 936316186..06ad30b60 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -635,6 +635,23 @@ partial fractions divide. It is taken out in front of the integral first | `"(b*d + 2*c*d*x)^9/(a + b*x + c*x^2)^3".ToEntity().Integrate("x")` | past a minute | 76,249 characters, the length the content-free one already had | | `"(b*d + 2*c*d*x)^8/(a + b*x + c*x^2)^3".ToEntity().Integrate("x")` | past a minute | 65,236 characters | +### A complex unit common to a polynomial's coefficients is taken out before it is expanded + +**Answers where there were none.** `1/(e^(5/2 i arctan(a x)) x^3)` was left unevaluated: written algebraically it +puts a power of a polynomial whose coefficients share `i`, `(i a + i a x^4)^3`, below the bar, and the rule for +complex coefficients expanded it past the degree it reads. The common unit is taken out as a power of its own, and a +whole power of a quotient under a root of a quotient is distributed over its two sides +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"1/(e^(5/2*i*atan(a*x))*x^3)".ToEntity().Integrate("x")` | `integral(...)` | 464 characters | +| `"1/(e^(5/2*i*atan(a*x))*x^4)".ToEntity().Integrate("x")` | `integral(...)` | 523 characters | +| `"1/(x^2*(i+i*x^4)^3)".ToEntity().Integrate("x")` | `integral(...)` | 331 characters | +| `"1/(e^acoth(a*x)*(c-a^2*c*x^2)^2)".ToEntity().Integrate("x")` | `integral(...)` | 486 characters | +| `"1/(e^acoth(a*x)*(c-a^2*c*x^2)^3)".ToEntity().Integrate("x")` | `integral(...)` | 893 characters | +| `"1/(e^acoth(a*x)*(c-a^2*c*x^2)^4)".ToEntity().Integrate("x")` | `integral(...)` | 1157 characters | + ### An exponential of a symbolic multiple of i arctan is written as powers of the linears **Answers where there were none.** `(c + a^2 c x^2)^p/e^(2 i p arctan(a x))` was left unevaluated. For a real `L`, diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index b4e4d7be8..8f143a53c 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -2688,6 +2688,32 @@ node is Powf(var radicand, Number.Rational r) && r is not Number.Integer && radi { if (!TryReadAsQuotient(expr, out var numerator, out var denominator)) return null; + // A factor whose coefficients are one complex number times real ones gives that number + // up first, factor by factor and before anything is expanded: `(i + i x^4)^3` is + // `i^3 (1 + x^4)^3`, a whole power of a product. `1/(x^2 (i + i x^4)^3)`, what the + // root of `(1 + i x)/(1 - i x)` makes of `1/(e^((5/2) i arctan(x)) x^3)`, expanded to a + // degree past the bound below and was declined, where the real quotient left is + // answered at once (Rubi's 5.3.6:129 and :130). + var tookAUnitOut = false; + Entity WithTheUnitsOut(Entity side) + { + Entity product = Number.Integer.One; + foreach (var factor in Mulf.LinearChildren(side)) + { + var (@base, power) = factor is Powf(var b, Number.Integer k) ? (b, (Entity)k) : (factor, Number.Integer.One); + if (ACommonComplexUnit(@base, x) is var (unit, real)) + { + tookAUnitOut = true; + product *= power == Number.Integer.One ? unit * real : MathS.Pow(unit, power) * MathS.Pow(real, power); + } + else + product *= factor; + } + return product; + } + var unitsOut = WithTheUnitsOut(numerator) / WithTheUnitsOut(denominator); + if (tookAUnitOut) + return Integration.ComputeAsAQuestionOfItsOwn(unitsOut, x, integrateByParts); Entity constant = Number.Integer.One; Number.Complex[]? Read(Entity side, bool below) { @@ -2783,6 +2809,48 @@ Entity Polynomial(IEnumerable coefficients) // denominator's degree doubles under the conjugate. private const int MaximumComplexRationalDegree = 12; + /// + /// A polynomial in whose coefficients are each a number times one + /// product of symbols common to them all, the numbers all the same complex number off + /// the real line times real ones, as that number times the symbols and the real + /// polynomial: i a + i a x^4 is i a and 1 + x^4. Null for anything else. + /// + private static (Entity Unit, Entity Real)? ACommonComplexUnit(Entity polynomial, Entity.Variable x) + { + if (polynomial is not (Sumf or Minusf) || !polynomial.ContainsNode(x) + || !TreeAnalyzer.TryGetPolynomial(polynomial, x, out var terms) || terms.Count < 2 + || terms.Keys.Any(power => power.Sign < 0)) + return null; + var values = new Dictionary(); + Entity? symbols = null; + foreach (var term in terms) + { + Number.Complex number = Number.Integer.One; + Entity rest = Number.Integer.One; + foreach (var factor in Mulf.LinearChildren(term.Value.InnerSimplified)) + if (factor is Number.Complex value) + number *= value; + else + rest = rest == Number.Integer.One ? factor : rest * factor; + if (number.IsZero || symbols is { } && rest != symbols) + return null; + symbols = rest; + values[term.Key] = number; + } + var leading = values[terms.Keys.Max()!]; + if (leading.ImaginaryPart.IsZero) + return null; + var unit = symbols is null || symbols == Number.Integer.One ? (Entity)leading : leading * symbols; + Entity real = Number.Integer.Zero; + foreach (var pair in values.OrderBy(pair => pair.Key)) + { + if ((pair.Value / leading) is not Number.Real coefficient) + return null; + real += pair.Key.IsZero ? coefficient : coefficient * MathS.Pow(x, Number.Integer.Create(pair.Key)); + } + return (unit, real); + } + /// /// over with the greatest /// power of that divides both taken out of each, the denominator's @@ -12322,7 +12390,16 @@ node is Powf(var radical, Number.Rational e) && e is not Number.Integer // Timofeev's 314 -- the distributed spelling sent the radical rules down a path // that answered with a sign function, where the undistributed one is answered exactly. if (!integrand.Nodes.Any(node => node is Powf(var radicand, Number.Rational fractional) && fractional is not Number.Integer && radicand.ContainsNode(u))) + { integrand = WithWholePowersOfProductsDistributed(integrand); + // And a whole power of a quotient as the quotient of the powers, gathered into + // one again: with a symbol in the linears, `x^(-3)` comes through as + // `(-i (u^4 - 1)/(a (u^4 + 1)))^(-3)`, which the combining leaves a power of a + // quotient and the rational reader does not read (Rubi's 5.3.6:129 and :130). + if (integrand.Nodes.Any(node => node is Powf(Divf, Number.Integer))) + integrand = Functions.SingleQuotient.Combine(WithWholePowersOfProductsDistributed(integrand.Replace(node => + node is Powf(Divf(var above, var below), Number.Integer power) ? MathS.Pow(above, power) / MathS.Pow(below, power) : node))); + } // For an even q the principal root is not negative wherever it is real, and a root // holding a power of u gives that power up: `1/sqrt(t + t^(3/2))` under `u = sqrt(t)` // is `2u/sqrt(u^2 + u^3)`, a root of a cubic, and is `2/sqrt(1 + u)`. Where the diff --git a/Sources/Tests/UnitTests/Calculus/AComplexUnitCommonToAPolynomialIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AComplexUnitCommonToAPolynomialIntegralTest.cs new file mode 100644 index 000000000..a1e093843 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AComplexUnitCommonToAPolynomialIntegralTest.cs @@ -0,0 +1,70 @@ +// +// 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 complex unit common to a polynomial's coefficients, taken out before the polynomial is expanded, + /// and a whole power of a quotient distributed under a root-of-a-quotient substitution: Rubi's 5.3.6 + /// 1/(e^(5/2 i arctan(a x)) x^3), which was left unevaluated, and 7.4.2's + /// 1/(e^(arccoth(a x)) (c - a^2 c x^2)^2), which was answered wrongly. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class AComplexUnitCommonToAPolynomialIntegralTest + { + private static void DifferentiatesBack(string integrand, double[] points, 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"); + foreach (var at in points) + { + 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/(e^(5/2*i*atan(a*x))*x^3)")] + [InlineData("1/(e^(5/2*i*atan(a*x))*x^4)")] + public void AnExponentialOfAnArctangentOverAPowerIsAnswered(string integrand) + { + DifferentiatesBack(integrand, new[] { -2.3, -0.7, 0.4, 1.9 }, ("a", 1.3)); + DifferentiatesBack(integrand, new[] { -2.3, -0.7, 0.4, 1.9 }, ("a", -0.5)); + } + + [Fact] + public void APolynomialWithACommonUnitIsAnswered() + => DifferentiatesBack("1/(x^2*(i+i*x^4)^3)", new[] { -2.3, -0.7, 0.4, 1.9 }); + + [Theory] + [InlineData("1/(e^acoth(a*x)*(c-a^2*c*x^2)^2)")] + [InlineData("1/(e^acoth(a*x)*(c-a^2*c*x^2)^3)")] + public void AnExponentialOfAnArccotangentIsAnsweredRightly(string integrand) + { + DifferentiatesBack(integrand, new[] { -2.3, -1.6, 1.4, 1.9 }, ("a", 1.3), ("c", 0.7)); + DifferentiatesBack(integrand, new[] { -4.5, -2.6, 2.4, 3.9 }, ("a", -0.5), ("c", 2.5)); + } + } +}