diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 4f92adc78..31ac6efca 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -13801,6 +13801,58 @@ node is Powf(var @base, var power) && @base.ContainsNode(u) return back.Nodes.Any(node => node == MathS.NaN) ? null : back; } + /// + /// A polynomial times powers of error functions of one shifted linear, written in + /// u = a + b x: x erf(a + b x)^2 is (u - a) erf(u)^2/b^2, which by parts + /// answers, since there the Gaussian the error function differentiates to is of the + /// variable itself. + /// + /// + /// In x, by parts leaves x^n e^(-(a + b x)^2) erf(a + b x), which nothing reads, and + /// the remainder is declined term by term rather than asked (see + /// TheRemainderIsAskedTermByTerm): (c + d x)^2 erf(a + b x)^2 ran past twenty + /// seconds. Only where the offset is not zero, since without it there is nothing to move, + /// and only error functions, whose derivatives are Gaussians of their argument rather than + /// quotients by it. Rubi's 8.1. + /// https://github.com/asc-community/AngouriMath/issues/1945 + /// + internal static Entity? SolveAPolynomialBesideErrorFunctionsOfAShiftedLinear(Entity expr, Entity.Variable x, bool integrateByParts) + { + if (!Integration.AnsweringTheQuestionAskedOrOneBelow) + return null; + Entity? argument = null; + Entity? polynomial = null; + foreach (var factor in Mulf.LinearChildren(expr)) + { + var special = factor is Powf(var @base, Number.Integer power) && power.EInteger.Sign > 0 ? @base : factor; + if (special is Entity.Erff or Entity.Erfcf or Entity.Erfif && special.DirectChildren[0] is var inner) + { + if (argument is null) + argument = inner; + else if (inner != argument) + return null; + } + else if (!factor.ContainsNode(x)) + continue; + else if (ThePolynomialIn(factor, x) is not null) + polynomial = polynomial is null ? factor : polynomial * factor; + else + return null; + } + if (argument is null || polynomial is null + || !TreeAnalyzer.TryGetPolyLinear(argument, x, out var slope, out var offset) + || slope.ContainsNode(x) || offset.ContainsNode(x) || TreeAnalyzer.IsZero(slope) || VanishesIdentically(offset)) + return null; + // x = (u - a)/b, so that each error function is of u, and dx = du/b. + var u = Variable.CreateUnique(expr, "u_linear"); + var inU = expr.Replace(node => node == argument ? u : node).Substitute(x, (u - offset) / slope); + if (inU.ContainsNode(x) + || Integration.ComputeAsAQuestionOfItsOwn((inU / slope).InnerSimplified, u, integrateByParts) is not { } answer) + return null; + var back = answer.Substitute(u, argument); + return back.Nodes.Any(node => node == MathS.NaN) ? null : back; + } + /// /// 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 diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 7204719a2..4f34b7cdc 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -765,6 +765,9 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) // the sum and writes what is left out in powers of x, where the linear is gone; and // before the substitution search, which reads one function at a time. if ((answer = IndefiniteIntegralSolver.SolveByWritingAFunctionOfOneShiftedLinear(expr, x, integrateByParts)) is { }) return answer; + // A polynomial beside error functions of one shifted linear, written in it: by parts in + // x leaves a Gaussian of the shifted linear beside a polynomial, which nothing reads. + if ((answer = IndefiniteIntegralSolver.SolveAPolynomialBesideErrorFunctionsOfAShiftedLinear(expr, x, integrateByParts)) is { }) return answer; if ((answer = IndefiniteIntegralSolver.SolveAsPolynomialTerm(expr, x, integrateByParts)) is { }) return answer; // A product of powers of the variable with a power of a constant multiple of it // among them, `(c x)^m x^n`, by the power rule with the written power kept as diff --git a/Sources/Tests/UnitTests/Calculus/APolynomialBesideErrorFunctionsOfAShiftedLinearIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/APolynomialBesideErrorFunctionsOfAShiftedLinearIntegralTest.cs new file mode 100644 index 000000000..ca1a79c1d --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/APolynomialBesideErrorFunctionsOfAShiftedLinearIntegralTest.cs @@ -0,0 +1,54 @@ +// +// 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 polynomial times powers of error functions of one shifted linear, written in + /// u = a + b x. In x, by parts leaves a Gaussian of a + b x beside a polynomial, + /// which nothing read, and (c + d x)^2 erf(a + b x)^2 ran past twenty seconds. Rubi's 8.1. + /// #1945 + /// + [Trait("Area", "Calculus")] + public sealed class APolynomialBesideErrorFunctionsOfAShiftedLinearIntegralTest + { + [Theory] + [InlineData("x*erf(a + b*x)^2")] + [InlineData("x^2*erf(a + b*x)^2")] + [InlineData("(c + d*x)^2*erf(a + b*x)^2")] + [InlineData("(c + d*x)^2*erfc(a + b*x)^2")] + [InlineData("(c + d*x)^2*erfi(a + b*x)^2")] + [InlineData("(c + d*x)*erf(a + b*x)")] + public void WrittenInTheShiftedLinear(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("b", 0.7).Substitute("c", 0.4).Substitute("d", 0.3); + 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}"); + } + } +}