diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 6faf6c53c..6dc517a3e 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -407,6 +407,19 @@ square and the cube keep the Hermite reduction's answers. Rubi's 1.1.3.8 `P(x) ( | `"(c + d*x)/(a + b*x^3)^4".ToEntity().Integrate("x")` | `integral(...)` | the same at the roots of `a + b x^3` | | `"x^2/(a + b*x^5)^5".ToEntity().Integrate("x")` | `integral(...)` | four algebraic terms, and the integral of `x^2/(a + b x^5)` | +### A difference is offered to the substitution search as a sum is + +**Answers where there were none.** `sin(1 + 2/(1 + x))` was answered under `u = 1 + x`, and +`sin(1 + 2/(1 - x))` was declined: the substitution search offered every sum in the integrand as a +candidate and no difference, so `u = 1 - x` was never tried. A difference in the integrand as asked +is offered now as a sum is ([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"sin(1 + 2/(1 - x))".ToEntity().Integrate("x")` | `integral(...)` | in the sine and cosine integrals of `2/(1 - x)` | +| `"cos(1 + 2/(x - 1))".ToEntity().Integrate("x")` | `integral(...)` | in the sine and cosine integrals of `2/(x - 1)` | +| `"sin(p + k/(c - d*x))".ToEntity().Integrate("x")` | `integral(...)` | in the sine and cosine integrals of `k/(c - d x)` | + ### A polynomial over a power of a trinomial past the square is integrated `P(x)/(a + b x^n + c x^(2n))^k`, a polynomial over a power of a trinomial in `x^n` with symbols in diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 33b6f88f1..9c29ad3b2 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -26266,7 +26266,7 @@ private static bool IsAFunctionOfTheTangentAlone(Entity expr, Entity.Variable u) // and not a subexpression anyone wrote: `1 + 2x^2` and `1 + 2x^2 + 4x^3` are // the left-nested partial sums of Hearn's radicand `1 + 2x^2 + 4x^3 + x^4`, // and each was half a second of rewriting and simplifying to be refused. - if (u is Sumf && IsOnlyAPartialSum(expr, u)) + if (u is Sumf or Minusf && IsOnlyAPartialSum(expr, u)) continue; // A candidate that does not vary with x is no substitution at all, and its @@ -27147,8 +27147,14 @@ bool APowerCanBeExact(int k) case var special when IsASpecialFunction(special) && TheDifferentialCanBeThere(special, expr, x): candidates.Add(node); break; - case Sumf(var aug, var add) when !rational && !large && node.Complexity <= LargestSumOffered: - if (aug.ContainsNode(x) || add.ContainsNode(x)) candidates.Add(node); // Linear expressions ax + b + // A difference as a sum: `1 - x` below the bar of `sin(1 + 2/(1 - x))` is the + // substitution that `1 + x` is for `sin(1 + 2/(1 + x))`, and was never offered. + // In the question asked only: below it every substitution writes differences, + // `x = (u^2 - c)/d`, and offering them was two fifths more search on Rubi's + // `(c + d x)^(5/2)/(x (a + b x)^2)`, past the harness's patience. + case Sumf or Minusf when !rational && !large && node.Complexity <= LargestSumOffered + && (node is Sumf || Integration.AnsweringTheQuestionAsked): + if (node.DirectChildren.Any(child => child.ContainsNode(x))) candidates.Add(node); // Linear expressions ax + b break; } // Sort by complexity - try simpler substitutions first diff --git a/Sources/Tests/UnitTests/Calculus/DifferenceAsASubstitutionIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/DifferenceAsASubstitutionIntegralTest.cs new file mode 100644 index 000000000..106ec07b7 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/DifferenceAsASubstitutionIntegralTest.cs @@ -0,0 +1,50 @@ +// +// 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 difference offered to the substitution search as a sum is: sin(1 + 2/(1 - x)) is + /// -sin(1 + 2/u) under u = 1 - x, as sin(1 + 2/(1 + x)) is + /// sin(1 + 2/u) under u = 1 + x, and only the second was answered. + /// #718 + /// + /// + /// Checked by differentiating back with p = 0.3, k = 0.9, c = 1.1, + /// d = 0.7, on both sides of the pole of every row. + /// + [Trait("Area", "Calculus")] + public sealed class DifferenceAsASubstitutionIntegralTest + { + [Theory] + [InlineData("sin(1 + 2/(1 - x))")] + [InlineData("cos(1 + 2/(1 - x))")] + [InlineData("sin(1 + 2/(x - 1))")] + [InlineData("sin(1 + 2/(3 - 2*x))")] + [InlineData("sin(p + k/(c - d*x))")] + public void IsSubstitutedForAsASumIs(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + Entity Pinned(Entity e) => e.Substitute("p", 0.3).Substitute("k", 0.9).Substitute("c", 1.1).Substitute("d", 0.7); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + foreach (var at in new[] { -1.0, 0.3, 0.8, 2.2, 3.0 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + var got = derivative.Substitute("x", at).EvalNumerical(); + 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}"); + } + } + } +}