diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 49af14dbf..dee44a680 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -187,6 +187,22 @@ digits, and `e^(1/3)` the same. Each is now worked out in the precision asked fo | `MathS.pi.Evaled` under 300 digits, after a read under 100 | 100 digits | 300 digits | | `"e^(1/3)".Evaled` under 300 digits, after a read under 100 | 100 digits | 300 digits | +### An exponential of i times an arctangent of a linear is written algebraically before the other rules + +**Answers where there were none.** `e^(3 i atan(a + b x))/x^2` ran out of time, and so did fifteen more of Rubi's +5.3.6, all `e^(n i atan(a + b x))` over a power of x or `e^(n i atan(a x))` beside a high power of `c + a^2 c x^2`. +The exponential is now written as powers of linears, `(1 + i L)^(n/2) (1 - i L)^(-n/2)`, at the question asked, ahead +of the rules that spent the budget, and for every `n` beside a pole at 0 where the argument has a constant term. A +piecewise answer is checked for `NaN` without `Simplify`, whose search on one of them ran past a minute +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"e^(3*i*atan(a+b*x))/x^2".ToEntity().Integrate("x")` | `integral(...)` | 1,945 characters | +| `"1/(e^(3*i*atan(a+b*x))*x^4)".ToEntity().Integrate("x")` | `integral(...)` | 8,542 characters | +| `"e^(3*i*atan(a+b*x))/x^4".ToEntity().Integrate("x")` | `integral(...)` | 35,041 characters | +| `"e^(6*i*atan(a*x))*x^2/(c+a^2*c*x^2)^19".ToEntity().Integrate("x")` | `integral(...)` | 233 characters | + ### A string parsed once is parsed again under the other downcasting setting **A different number.** `MathS.FromString` remembered what it had parsed, keyed on the parsing setting alone. A string diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 745be6760..059da6cd5 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -9696,7 +9696,11 @@ node is Powf(var @base, Number.Rational power) && power is not Number.Integer && var quadraticElsewhere = expr.Nodes.Any(node => node is Powf(var q, _) && q.ContainsNode(x) && !q.Nodes.Any(inner => inner is Arctanf or Arcsinf or Arccosf) && TreeAnalyzer.TryGetPolyQuadratic(q, x, out var leading, out _, out _) && !TreeAnalyzer.IsZero(leading)); // The variable outside the exponentials. - var xElsewhere = expr.Replace(node => node is Powf(var e, var power) && e == MathS.e && power.ContainsNode(x) ? Number.Integer.One : node).ContainsNode(x); + var withoutTheExponentials = expr.Replace(node => node is Powf(var e, var power) && e == MathS.e && power.ContainsNode(x) ? Number.Integer.One : node); + var xElsewhere = withoutTheExponentials.ContainsNode(x); + // The variable in a denominator, a pole beside the exponentials: not `x^2/e^(i arctan(a + b x))`. + var xInADenominator = withoutTheExponentials.Nodes.Any(node => node is Divf(_, var denominator) && denominator.ContainsNode(x) + || node is Powf(var @base, Number.Real { IsNegative: true }) && @base.ContainsNode(x)); var rewritten = expr.Replace(node => { if (node is not Powf(var @base, var exponent) || @base != MathS.e || !exponent.ContainsNode(x)) @@ -9740,7 +9744,14 @@ node is Powf(var @base, Number.Rational power) && power is not Number.Integer && // ran past thirty seconds, and `x^3 e^(-i arctan(a x))` came out in 26 thousand // characters where so it is 218. Alone the exponential is answered shorter as the // root, and for a power not whole the quotient's power is. - if ((n == Number.Integer.One || n == Number.Integer.MinusOne) && !quadraticElsewhere && xElsewhere) + // So for every n beside a pole at 0 and an argument with a constant term: + // `e^(3 i arctan(a + b x))/x^2` as `(1 + i L)^3 (1 + L^2)^(-3/2)` ran out of time, and + // as the two powers it is answered in half a second. Not for `a x` or a polynomial + // beside it, which the root answers: `x^3 e^(-(3/2) i arctan(a x))` and + // `e^(3 i arctan(a x))/x` were answered and as the two powers ran out of time. + if (!quadraticElsewhere && xElsewhere && (n == Number.Integer.One || n == Number.Integer.MinusOne + || xInADenominator && TreeAnalyzer.TryGetPolyLinear(argument, x, out _, out var argumentConstant) + && argumentConstant is { } && !TreeAnalyzer.IsZero(argumentConstant))) { var half = Number.Rational.Create(n.ERational.Divide(2)); return MathS.Pow(1 + MathS.i * argument, half) * MathS.Pow(1 - MathS.i * argument, Number.Rational.Create(half.ERational.Negate())); @@ -9787,7 +9798,9 @@ node is Powf(var q, Number.Rational r) && r is not Number.Integer && q != first // non-existence is not given. if (expr.Vars.Any(symbol => symbol != x) && !Functions.PartialFractions.HoldsAtSampledPoints(answer.Differentiate(x), expr, x)) return null; - if (answer.Nodes.Any(node => node is Piecewise) && answer.Simplify().Nodes.Any(node => node == MathS.NaN)) + // InnerSimplified rather than Simplify: the search ran past a minute on the piecewise answer to + // `e^(3 i atan(a + b x))/x^2`, ready and checked in a quarter of a second. + if (answer.Nodes.Any(node => node is Piecewise) && answer.InnerSimplified.Nodes.Any(node => node == MathS.NaN)) return null; return answer; } diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 81af62a98..ac9d425d0 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -664,6 +664,10 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) // A sum free of the variable that comes to a number multiplied out, as that number, // before anything reads a coefficient or an exponent in it: `2 + 2 a - 2 (1 + a)`. if ((answer = IndefiniteIntegralSolver.SolveByFoldingConstantSumsToTheirNumbers(expr, x, integrateByParts)) is { }) return answer; + // An exponential of i times an inverse trigonometric function written algebraically, at the + // question asked, before the rules that spent the budget: `e^(3 i atan(a + b x))/x^2` ran out of + // time where, rewritten, it is answered in half a second. It is asked again in its place below. + if (AnsweringTheQuestionAsked && (answer = IndefiniteIntegralSolver.SolveByWritingAnExponentialOfAnInverseAlgebraically(expr, x, integrateByParts)) is { }) return answer; // A polynomial over a symbolic quartic that splits into two quadratics in a hidden variable, // palindromic or even after a shift: the rules below spent seconds declining both. if ((answer = IndefiniteIntegralSolver.SolveAPolynomialOverASymbolicQuarticThatSplitsIntoTwoQuadratics(expr, x, integrateByParts)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/AnExponentialOfAnArctangentBesideAPowerOfXIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AnExponentialOfAnArctangentBesideAPowerOfXIntegralTest.cs new file mode 100644 index 000000000..41d79bc0d --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AnExponentialOfAnArctangentBesideAPowerOfXIntegralTest.cs @@ -0,0 +1,63 @@ +// +// 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 +{ + /// + /// An exponential of i times an arctangent of a linear beside a power of x, written as two powers of + /// linears, `(1 + i L)^(n/2) (1 - i L)^(-n/2)`, at the question asked: Rubi's 5.3.6 + /// e^(2 i atan(a + b x))/x^2 and the like ran out of time before the rule was reached. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class AnExponentialOfAnArctangentBesideAPowerOfXIntegralTest + { + private static void DifferentiatesBack(string integrand, 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 new[] { -2.3, -0.7, 0.4, 1.9 }) + { + 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}"); + } + } + + // d with the sign that keeps the argument real at every sampled x, and |d| below 2/2.3^2 + // so that the inverse sine and cosine stay inside [-1, 1]; off it the exact checks hold too. + [Theory] + [InlineData("e^(2*i*atan(a+b*x))/x^2")] + [InlineData("e^(3/2*i*atan(a+b*x))/x^2")] + [InlineData("e^(3*i*atan(a+b*x))/x")] + // Answered as a piecewise, which the check for a NaN simplified for over a minute. + [InlineData("e^(3*i*atan(a+b*x))/x^2")] + [InlineData("1/(e^(3*i*atan(a+b*x))*x^3)")] + [InlineData("x*e^(-2*i*atan(a+b*x))")] + public void IsAnsweredAndDifferentiatesBack(string integrand) + { + DifferentiatesBack(integrand, ("a", 0.6), ("b", 1.25)); + DifferentiatesBack(integrand, ("a", -0.75), ("b", 0.4)); + } + } +}