diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index dc39a1fe6..00903cad1 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -659,21 +659,25 @@ zero-discriminant arm, which gives the right answer. Each of these was checked by differentiating it back with the parameters pinned and comparing at four points. Nothing that already had an antiderivative changes. -### A radical function of whole powers of `x` written through its logarithm says where its answer holds +### A radical function of whole powers of `x` written through its logarithm is integrated on both sides of zero -**Answers where there were none, each with the condition it holds under.** `1/csch(2 ln(x))^(1/2)` -is `sqrt(sinh(2 ln(x)))`, which is `sqrt((x^2 - x^(-2))/2)` and real on both sides of zero; 2.5.0 -declined it. On the unreleased master it was integrated under `t = ln(x)`, where the rules take -`e^t` to be positive, and the answer was right for a positive `x` and wrong for every negative one. -Such an answer is given `provided x > 0` now, and `provided c x^n > 0` for a logarithm of `c x^n` -([#718](https://github.com/asc-community/AngouriMath/issues/718)). +**Answers where there were none.** `1/csch(2 ln(x))^(1/2)` is `sqrt(sinh(2 ln(x)))`, which is +`sqrt((x^2 - x^(-2))/2)` and real on both sides of zero; 2.5.0 declined it. Its exponentials are +folded into powers of `x`, a negative one under a root written below the bar, and the answer is in those, on +both sides of zero and on both sides of 1, where `sinh(2 ln(x))` changes sign. Under `t = ln(x)`, +where the rules take `e^t` to be positive, an answer of this kind is given `provided x > 0`, and +`provided c x^n > 0` for a logarithm of `c x^n`. A constant over a power that is not whole, `1/g^p`, +is asked as `g^(-p)` at the depth it was asked at, so that the rules scoped to the question asked see +it, which is how `1/csch(2 ln(x))^(1/2)` reaches the fold, and `1/sqrt(x + x^(3/2))` is answered on +both sides of zero as well ([#718](https://github.com/asc-community/AngouriMath/issues/718)). | Input | Was (2.5.0) | Now | |---|---|---| -| `"1/csch(2*ln(x))^(1/2)".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative, `provided x > 0` | -| `"csch(2*ln(x))^(-3/2)".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative, `provided x > 0` | -| `"1/csch(2*ln(c*x))^(1/2)".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative, `provided c x > 0` | -| `"1/sech(2*ln(x))^(1/2)".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative, `provided x > 0` | +| `"1/csch(2*ln(x))^(1/2)".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative on both sides of zero | +| `"csch(2*ln(x))^(-3/2)".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative on both sides of zero | +| `"1/csch(2*ln(c*x))^(1/2)".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative on both sides of zero | +| `"1/sech(2*ln(x))^(1/2)".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative on both sides of zero | +| `"1/sqrt(x + x^(3/2))".ToEntity().Integrate("x")` | `integral(...)` | `4 sqrt(1 + sqrt(x))` times a factor constant where it is continuous, on both sides of zero | ### `e^(k acoth(a x))` beside a power of `c - a c x` is integrated @@ -1716,7 +1720,7 @@ was ([#718](https://github.com/asc-community/AngouriMath/issues/718)). | Input | Was (2.5.0) | Now | |---|---|---| | `"x * sqrt(c - a*c*x) / e^(3 * atanh(a*x))".Integrate("x")` | `integral(…)` — left unevaluated, with `e` evaluated to a hundred digits inside it | an antiderivative in `sqrt(c - a c x)`, `provided c - a * c * x >= 0` | -| `"1/sqrt(x + x^(3/2))".Integrate("x")` | `integral(1 / sqrt(x + x ^ (3/2)), x)` — left unevaluated | `2 * sqrt(1 + sqrt(x)) / (1/2) + C provided x >= 0` | +| `"sqrt(x)/sqrt(x + x^2)".Integrate("x")` | `integral(...)` — left unevaluated | `2 sqrt(x + 1)` times a factor constant where it is continuous, `provided x + 1 >= 0` | ### An answer that read the sign of an even root says where the root is real diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 875a31f1c..01a4ce771 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -2173,9 +2173,17 @@ private static bool TryReadAsQuotient(Entity expr, out Entity numerator, out Ent // The exponent negated as a number rather than as a tree: `-power` on the // node `2` is `2 * (-1)`, and `sin(x)^(2 * (-1))` is a shape the closed rule // for a power of the sine does not read, so `c/sin(x)^2` went to the - // half-angle substitution for what is `-c cot(x)`. + // half-angle substitution for what is `-c cot(x)`. And where the power is not + // whole asked as the same question, since `c/g^p` and `c g^(-p)` are one: a + // level down the rules scoped to the question asked, the ones for a root, did + // not see it, and `1/csch(2 ln(x))^(1/2)` reached the fold of its exponentials + // there and was declined, where asked at the top it is answered. A whole power + // keeps its level: asked as the same question, `(c + d x)/(a + a tanh(e + f x))^3` + // ran past the budget where it is answered in a second. over is Entity.Powf(var @base, var power) ? - Integration.ComputeIndefiniteIntegral(MathS.Pow(@base, (-power).InnerSimplified), x, integrateByParts)?.Pipe(i => div * i) : + (power is Number.Rational and not Number.Integer + ? Integration.ComputeAsTheSameQuestion(MathS.Pow(@base, (-power).InnerSimplified), x, integrateByParts) + : Integration.ComputeIndefiniteIntegral(MathS.Pow(@base, (-power).InnerSimplified), x, integrateByParts))?.Pipe(i => div * i) : // A constant over a product is the reciprocal of the product, asked as the // same question with the constant in front, and not the product to the // power -1: `pe/(x (d + e x) sqrt(1 - c^2 x^2))` handed on as @@ -16513,7 +16521,20 @@ node is Powf(Powf(var @base, var inner), var outer) var folded = FoldTheExponent(exponent, x); return folded ?? node; }); - return folded == expr ? null : Integration.ComputeAsAQuestionOfItsOwn(folded, x, integrateByParts); + if (folded == expr) + return null; + // A radicand with its negative powers written below the bar, which is where the rules + // for a root read a denominator: `(1/((x^2 - x^(-2))/2))^(-3/2)` was declined where + // `(1/((x^2 - 1/x^2)/2))^(-3/2)` is answered. Only under a power that is not whole: a + // whole power of a sum is expanded, and the power rule reads its terms as `x^(-k)`, + // through which `sinh(a + b ln(c x^n))^4` is answered in a second and was searched for + // six with them below the bar. + folded = folded.Replace(node => node is Powf(var radicand, var exponent) && exponent.Evaled is not Number.Integer && radicand.ContainsNode(x) + ? MathS.Pow(radicand.Replace(inside => inside is Powf(var @base, Number.Integer { EInteger: var power }) && power.Sign < 0 && @base.ContainsNode(x) + ? Number.Integer.One / (power.Equals(EInteger.FromInt32(-1)) ? @base : MathS.Pow(@base, Number.Integer.Create(-power))) + : inside), exponent) + : node); + return Integration.ComputeAsAQuestionOfItsOwn(folded, x, integrateByParts); } /// diff --git a/Sources/Tests/UnitTests/Calculus/LinearRadicalSubstitutionIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/LinearRadicalSubstitutionIntegralTest.cs index 910eaaa36..12682b9cf 100644 --- a/Sources/Tests/UnitTests/Calculus/LinearRadicalSubstitutionIntegralTest.cs +++ b/Sources/Tests/UnitTests/Calculus/LinearRadicalSubstitutionIntegralTest.cs @@ -79,8 +79,8 @@ private static void DifferentiatesBack(string integrand) /// /// That step reads u as a non-negative real, which it is only where the radical - /// is real, and the answer says so: 1/sqrt(x + x^(3/2)) comes back - /// provided x >= 0. Beyond the radicand's zero u is imaginary, and the + /// is real, and the answer says so: sqrt(x)/sqrt(x + x^2) comes back + /// provided x + 1 >= 0. Beyond the radicand's zero u is imaginary, and the /// integrand can still be real there -- Rubi's x sqrt(c - a c x) / e^(3 atanh(a x)) /// above a x = 1 is the product of two imaginary factors -- while the answer built /// for a real u is not its antiderivative: with a = 3.1, c = 0.7 at @@ -93,8 +93,8 @@ private static void DifferentiatesBack(string integrand) [Fact] public void TheEvenRootStepSaysWhereItHolds() { - var conditioned = "1/sqrt(x + x^(3/2))".ToEntity().Integrate("x"); - Assert.Contains("provided x >= 0", conditioned.Stringize()); + var conditioned = "sqrt(x)/sqrt(x + x^2)".ToEntity().Integrate("x"); + Assert.Contains("provided x + 1 >= 0", conditioned.Stringize()); var integrand = "x * sqrt(c - a*c*x) / e^(3 * atanh(a*x))".ToEntity(); var integral = integrand.Integrate("x"); diff --git a/Sources/Tests/UnitTests/Calculus/LogarithmSubstitutionTest.cs b/Sources/Tests/UnitTests/Calculus/LogarithmSubstitutionTest.cs index 10f8bf5c7..84e9ff43c 100644 --- a/Sources/Tests/UnitTests/Calculus/LogarithmSubstitutionTest.cs +++ b/Sources/Tests/UnitTests/Calculus/LogarithmSubstitutionTest.cs @@ -6,7 +6,6 @@ // using System; -using System.Linq; using AngouriMath.Extensions; using Xunit; @@ -121,38 +120,30 @@ public void ATrigonometricFunctionOfTheLogarithm(string integrand) /// /// A radical function of whole powers of x written through its logarithm, which is /// real on both sides of zero: sinh(2 ln(x)) is (x^2 - x^(-2))/2 for a - /// negative x as well. Each answer is compared at three negative points and three - /// positive ones, wherever it does not say it holds only elsewhere. Integrated under - /// t = ln(x), the first four say they hold where the logarithm's argument is - /// positive; the last two are answered in powers of x and hold on both sides. - /// Rubi's 6.5.3, 226, and 6.6.3, 188. + /// negative x as well. Each answer is compared on both sides of zero and on both + /// sides of 1, where sinh(2 ln(x)) changes sign, and holds at every point: the + /// exponentials of the logarithm are folded into powers of x, and the answer is in + /// those. Rubi's 6.5.3, 226, and 6.6.3, 188. /// [Theory] - [InlineData("1/csch(2*ln(x))^(1/2)", false)] - [InlineData("csch(2*ln(x))^(-3/2)", false)] - [InlineData("1/csch(2*ln(13/10*x))^(1/2)", false)] - [InlineData("1/sech(2*ln(x))^(1/2)", false)] - [InlineData("sqrt(sinh(2*ln(x)))", true)] - [InlineData("sqrt(cosh(2*ln(13/10*x)))", true)] - public void ARadicalFunctionOfAWholePowerOfX(string integrand, bool onBothSides) + [InlineData("1/csch(2*ln(x))^(1/2)")] + [InlineData("csch(2*ln(x))^(-3/2)")] + [InlineData("1/csch(2*ln(13/10*x))^(1/2)")] + [InlineData("1/sech(2*ln(x))^(1/2)")] + [InlineData("sqrt(sinh(2*ln(x)))")] + [InlineData("sqrt(cosh(2*ln(13/10*x)))")] + public void ARadicalFunctionOfAWholePowerOfX(string integrand) { var integral = integrand.ToEntity().Integrate("x"); Assert.DoesNotContain("integral(", integral.Stringize()); var derivative = integral.Substitute("C", 0).Differentiate("x"); var original = integrand.ToEntity(); - var conditions = integral.Nodes.OfType().Select(provided => provided.Predicate).ToList(); - foreach (var at in new[] { -2.7, -1.9, -1.3, 1.3, 1.9, 2.7 }) + foreach (var at in new[] { -2.7, -1.9, -1.3, -0.7, -0.4, 0.4, 0.7, 1.3, 1.9, 2.7 }) { - if (conditions.Any(condition => condition.Substitute("x", at).Evaled == Entity.Boolean.False)) - { - Assert.False(onBothSides, $"the antiderivative of {integrand} says it does not hold at x = {at}"); - Assert.True(at < 0, $"the antiderivative of {integrand} says it does not hold at x = {at}"); - continue; - } 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); - Assert.True(difference / Math.Max(1.0, Math.Abs((double)want.RealPart)) < 1e-9, + Assert.True(difference / Math.Max(1.0, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)) < 1e-9, $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); } }