diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index ae2a54fa6..0c3b37ae9 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -466,6 +466,24 @@ the first power: for a higher power the secant's own rules answer | `"cos(c + d*x)^2*(A + B*sec(c + d*x) + C*sec(c + d*x)^2)/(a + b*sec(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | 12,065 characters | | `"sin(x)^2/(a + b*csc(x))".ToEntity().Integrate("x")` | `integral(...)` | 898 characters | +### A linear with a symbolic slope beside a root of a quadratic with no linear term is integrated + +**Answers where there were none.** `sqrt(a + c x^2)/(x (d + k x))` was declined where `sqrt(a + b x + c x^2)/(x (d + k x))` +was answered. The rule for a constant over a linear beside the root works at the linear's root `p = -d/k`. Without +a linear term, the quadratic's value there came back with `provided not k = 0` attached, and the table read no +quadratic through that condition. The value is read without the condition now: a linear's slope is not zero. A +linear with the imaginary unit in it is still declined there, as it was by accident before +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"sqrt(a + c*x^2)/(x*(d + k*x))".ToEntity().Integrate("x")` | `integral(...)` | 1,362 characters | +| `"sqrt(a + c*x^2)/(x^3*(d + k*x))".ToEntity().Integrate("x")` | `integral(...)` | 1,710 characters | +| `"1/(x*(d + k*x)*sqrt(a + c*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 1,067 characters | +| `"1/(x*(d + k*x)*(a + c*x^2)^(3/2))".ToEntity().Integrate("x")` | `integral(...)` | 1,769 characters | +| `"1/(x^3*(d + k*x)^2*sqrt(a + c*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 2,801 characters | +| `"sqrt(a + c/x^2)/(d + k*x)".ToEntity().Integrate("x")` | `integral(...)` | 1,373 characters | + ### One linear twice is not two linear powers **Answers that had no value.** `(a c + b c x)^(-3 - 2 p) (f + g x) (a^2 + 2 a b x + b^2 x^2)^p` was answered diff --git a/Sources/AngouriMath/Functions/Algebra/Polynomials/PartialFractions.cs b/Sources/AngouriMath/Functions/Algebra/Polynomials/PartialFractions.cs index 2f440cab6..8091a8004 100644 --- a/Sources/AngouriMath/Functions/Algebra/Polynomials/PartialFractions.cs +++ b/Sources/AngouriMath/Functions/Algebra/Polynomials/PartialFractions.cs @@ -696,7 +696,7 @@ private static Entity ExpandedOverTheSymbols(Entity expr) } /// Whether a number off the real line is among the nodes of . - private static bool HoldsTheImaginaryUnit(Entity expr) + internal static bool HoldsTheImaginaryUnit(Entity expr) => expr.Nodes.Any(node => node is Complex number && number is not Real && !number.ImaginaryPart.IsZero); /// diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index fa107a045..d33acb453 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -18095,12 +18095,20 @@ Dictionary PowerOfQ(int j) foreach (var coefficient in new[] { a, b, c, m, n, numerator, constant }) if (coefficient.Evaled is Number.Complex and not Number.Real) return null; + // And a pole off the real line is not one: the answer is written with the sign of + // x - p, and nothing checks it after. `1 + i a x` beside a root, what + // `e^(3 i atan(a x))` is written as, has its pole at `i/a`. + if (Functions.PartialFractions.HoldsTheImaginaryUnit(m) || Functions.PartialFractions.HoldsTheImaginaryUnit(n)) + return null; // (x - p) with p = -n/m, and K = numerator/(m constant). var p = (-n / m).InnerSimplified; var k = (numerator / (m * constant)).InnerSimplified; - var atP = (a * p * p + b * p + c).InnerSimplified; - var slopeAtP = (2 * a * p + b).InnerSimplified; + // Bare: `3 (-d/k)^2 + 2` comes back `... provided not k = 0`, the linear's own slope + // not vanishing, and the table read no quadratic in t through the condition -- + // `1/((d + k x) sqrt(2 + 3 x^2))` was declined where `1/((d + x) sqrt(2 + 3 x^2))` was not. + var atP = Functions.PartialFractions.Bare((a * p * p + b * p + c).InnerSimplified); + var slopeAtP = Functions.PartialFractions.Bare((2 * a * p + b).InnerSimplified); var t = Variable.CreateUnique(expr, "t_recip"); var inT = 1 / MathS.Pow(atP * MathS.Sqr(t) + slopeAtP * t + a, Number.Rational.Create(1, 2)); if (IntegralPatterns.TryStandardIntegrals(inT, t) is not { } g) @@ -18489,12 +18497,13 @@ static Entity WithoutConditions(Entity coefficient) if (coefficient.Evaled is Number.Complex and not Number.Real) return null; var p = (-n / m).InnerSimplified; - var atP = (a * p * p + b * p + c).InnerSimplified; + // Bare, as in the rule for a single linear: the slope's condition is not a case. + var atP = Functions.PartialFractions.Bare((a * p * p + b * p + c).InnerSimplified); // A root of the radicand leaves a linear under the root, which is not this. if (VanishesIdentically(atP) || Functions.PartialFractions.TaylorCoefficientsAtTheRoot(numerator, p, power, x) is not { } taylor) return null; - var slopeAtP = (2 * a * p + b).InnerSimplified; + var slopeAtP = Functions.PartialFractions.Bare((2 * a * p + b).InnerSimplified); var t = Variable.CreateUnique(expr, "t_recip"); // P(p + 1/t) t^(k - 1) is the sum of the Taylor coefficients times t^(k - 1 - j), and // (x - p)^k is L^k/m^k. diff --git a/Sources/Tests/UnitTests/Calculus/APoleWithASymbolicSlopeBesideARootIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/APoleWithASymbolicSlopeBesideARootIntegralTest.cs new file mode 100644 index 000000000..96d8b1356 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/APoleWithASymbolicSlopeBesideARootIntegralTest.cs @@ -0,0 +1,62 @@ +// +// 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 constant over a linear with a symbolic slope beside the root of a quadratic with no + /// linear term: the quadratic's value at the pole came back with the slope's condition on it, + /// and the table read no quadratic through it. Rubi's 1.2.1.4 + /// sqrt(a + c x^2)/(x (d + e x)), written here with k for e, which is + /// Euler's number. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class APoleWithASymbolicSlopeBesideARootIntegralTest + { + private static void DifferentiatesBack(string integrand, params (string, double)[] pins) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + + // Pinned before it is differentiated: the answer carries `sgn(x + d/k)`, whose + // derivative is read where its argument is a number. + 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}"); + } + } + + [Theory] + [InlineData("1/((d+k*x)*sqrt(2+3*x^2))")] + [InlineData("sqrt(a+c*x^2)/(x*(d+k*x))")] + [InlineData("1/(x^2*(d+k*x)*sqrt(a+c*x^2))")] + [InlineData("1/(x*(d+k*x)^2*sqrt(a+c*x^2))")] + public void IsAnsweredAndDifferentiatesBack(string integrand) + { + DifferentiatesBack(integrand, ("a", 2), ("c", 1.5), ("d", 1.25), ("k", 2)); + DifferentiatesBack(integrand, ("a", -1), ("c", 2), ("d", -0.5), ("k", 1.5)); + } + } +}