diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 6faf6c53c..c929dbad6 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -210,6 +210,19 @@ answer on the far side of a root gets a real expression where it used to get a c | `1/(x*(-4+x^2)^4)` | 8,472 ms | 1,878 ms | | `1/((1+x)^3*(2+x)^3)` | 1,343 ms | 492 ms | +### A whole power of the radicand under Euler's substitution is read as one + +**Answers where there were none, and none where they were wrong.** Euler's substitution writes the +radicand's powers in its variable as powers of the root, and read a whole power `Q^n` as the root +to the `n`th, which is `Q^(n/2)`, so it integrated another integrand than the one asked. It reads +it as the root to the `2n`th now. `(x^2 + 3)^2/(x + sqrt(x^2 + 3))`, past the substitution's +bound on the degree once read rightly, is declined, as 2.5.0 declined it; the unreleased master +answered it wrongly ([#1770](https://github.com/asc-community/AngouriMath/issues/1770)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"1/((x^2 + 3)^2*(x + sqrt(x^2 + 3)))".ToEntity().Integrate("x")` | `integral(...)` | `-2/((sqrt(x^2 + 3) + x)^2 + 3)^2` | + ### A whole power of a sum of two square roots below the bar is rationalised **Answers where there were none.** `x/(sqrt(a + b x) + sqrt(c + b x))^3` was declined, while the diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 33b6f88f1..a69aba933 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -16599,9 +16599,12 @@ private static bool VanishesIdentically(Entity expr) } var dxdt = xInT.Differentiate(t); + // `Q^(k/2)` is the root to the `k`th power, and a whole power `Q^n` the root to the + // `2n`th: read as the `n`th, it was `Q^(n/2)`, and `Q^2/(x + sqrt(Q))` was answered as + // `Q/(x + sqrt(Q))`. https://github.com/asc-community/AngouriMath/issues/1770 var rewritten = expr.Replace(node => - node is Powf(var @base, Number.Rational half) && @base == radicand - ? MathS.Pow(rootInT, Number.Integer.Create(half.ERational.Numerator)) + node is Powf(var @base, Number.Rational power) && @base == radicand + ? MathS.Pow(rootInT, Number.Integer.Create(power is Number.Integer ? power.ERational.Numerator.Multiply(2) : power.ERational.Numerator)) : node) .Substitute(x, xInT) * dxdt; if (powered) diff --git a/Sources/Tests/UnitTests/Calculus/WholePowerOfTheRadicandUnderEulerTest.cs b/Sources/Tests/UnitTests/Calculus/WholePowerOfTheRadicandUnderEulerTest.cs new file mode 100644 index 000000000..956bc82a8 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/WholePowerOfTheRadicandUnderEulerTest.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 System.Linq; +using AngouriMath.Extensions; +using Xunit; + +namespace AngouriMath.Tests.Calculus +{ + /// + /// Euler's substitution writes a whole power of the radicand, Q^n, as the root to the + /// 2nth power. It wrote the nth, which is Q^(n/2), and integrated a + /// different integrand from the one asked. + /// https://github.com/asc-community/AngouriMath/issues/1770 + /// + [Trait("Area", "Calculus")] + public sealed class WholePowerOfTheRadicandUnderEulerTest + { + private static readonly double[] Points = { -1.7, -0.6, 0.4, 1.3, 2.2 }; + + private static void DifferentiatesBack(Entity integrand, Entity integral) + { + var derivative = integral.Differentiate("x"); + foreach (var at in Points) + { + var got = (double)((Entity.Number.Complex)derivative.Substitute("x", at).EvalNumerical()).RealPart; + var want = (double)((Entity.Number.Complex)integrand.Substitute("x", at).EvalNumerical()).RealPart; + Assert.True(Math.Abs(got - want) < 1e-9 * Math.Max(1, Math.Abs(want)), + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + + [Theory] + [InlineData("1/((x^2 + 3)^2*(x + sqrt(x^2 + 3)))")] + [InlineData("1/((x^2 + 2*x + 3)^2*(x + sqrt(x^2 + 2*x + 3)))")] + public void ItIsAnswered(string written) + { + var integrand = written.ToEntity(); + var integral = integrand.Integrate("x"); + Assert.DoesNotContain(integral.Nodes, node => node is Entity.Integralf); + DifferentiatesBack(integrand, integral); + } + + // Past the substitution's bound on the degree with the power read right: declined, as + // it may be, or answered rightly. + [Theory] + [InlineData("(x^2 + 3)^2/(x + sqrt(x^2 + 3))")] + [InlineData("(x^2 + 3)^3/(1 + sqrt(x^2 + 3))")] + public void ItIsNotAnsweredWrongly(string written) + { + var integrand = written.ToEntity(); + var integral = integrand.Integrate("x"); + if (!integral.Nodes.Any(node => node is Entity.Integralf)) + DifferentiatesBack(integrand, integral); + } + } +}