diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 2d782b045..054ae499b 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -396,6 +396,24 @@ in Rubi's 1.1.3.4 come out shorter as well, and those that took two seconds take | `"1/(x^8*(a + b*x^3)*(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 1,217 characters | | `"1/(x^6*(a + b*x^4)*(c + d*x^4))".ToEntity().Integrate("x")` | `integral(...)` | 795 characters | +### A constant sum that comes to a number is folded before an integral is asked + +**Answers where there were none.** `1/sqrt(2 + 2 a - 2 (1 + a) + c x^4)` was declined, and so was Rubi's 1.1.1.2 +`(c + d x)^(1 + 2 n - 2 (1 + n))/(a + b x)^2`. The terms of a sum free of the variable that come to a number once +multiplied out are now written as that number before the integral is asked: `2 + 2 a - 2 (1 + a)` is `0` and +`1 + 2 n - 2 (1 + n)` is `-1` ([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"1/sqrt(2+2*a-2*(1+a)+c*x^4)".ToEntity().Integrate("x")` | `integral(...)` | 44 characters | +| `"(c+d*x)^(1+2*n-2*(1+n))/(a+b*x)^2".ToEntity().Integrate("x")` | `integral(...)` | 255 characters | + +**A changed answer.** An integrand with such a sum as a coefficient is integrated with the number in its place. + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"(2+2*c-2*(1+c))*x^4".ToEntity().Integrate("x")` | `(2 + 2 * c - 2 * (1 + c)) * x ^ 5 / 5 + C` | `0 + C` | + ### Powers of multiples of powers of one linear are gathered into one power of it **Answers where there were none.** `1/(sqrt(-2 - b x) sqrt(2 + b x))` was declined, and so were four more of Rubi's diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 2a6e908e1..92a66a071 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -28805,6 +28805,43 @@ static bool Through(Entity node, Entity.Variable x) } } + /// + /// The terms of a sum free of the variable that come to a number once multiplied out, written + /// as that number: 2 + 2 a - 2 (1 + a) is 0 and 1 + 2 n - 2 (1 + n) is + /// -1, which neither the simplification nor the rules reading a coefficient or an + /// exponent see. Rubi's 1.2.2.2 1/sqrt(a + (2 + 2 c - 2 (1 + c)) x^4) and 1.1.1.2 + /// (c + d x)^(1 + 2 n - 2 (1 + n))/(a + b x)^2 were declined where + /// 1/sqrt(a) and 1/((c + d x) (a + b x)^2) are answered. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveByFoldingConstantSumsToTheirNumbers(Entity expr, Entity.Variable x, bool integrateByParts) + { + if (!Integration.AnsweringTheQuestionAsked) + return null; + // Only where a product holds a sum, which multiplying out is for. + static bool Distributes(Entity term) => term.Nodes.Any(node => node is Mulf && node.DirectChildren.Any(child => child is Sumf or Minusf)); + if (!expr.Nodes.Any(node => node is Sumf or Minusf && !node.ContainsNode(x) && Distributes(node))) + return null; + var folded = expr.Replace(node => + { + if (node is not (Sumf or Minusf)) + return node; + var terms = Sumf.LinearChildren(node).ToList(); + var free = terms.Where(term => !term.ContainsNode(x)).ToList(); + if (free.Count < 2 || !free.Any(Distributes)) + return node; + if (free.Aggregate((sum, term) => sum + term).Expand().InnerSimplified is not Number number) + return node; + return terms.Where(term => term.ContainsNode(x)).Aggregate((Entity)number, (sum, term) => sum + term); + }); + if (folded == expr) + return null; + var simplified = folded.InnerSimplified; + if (simplified == expr || simplified == expr.InnerSimplified) + return null; + return Integration.ComputeAsTheSameQuestion(simplified, x, integrateByParts); + } + /// /// A power of a constant multiple of a radicand that a logarithm holds under a root, /// written over that radicand: acosh(c x) is ln(c x + sqrt(c^2 x^2 - 1)), diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 820ef3d59..b0f23443d 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -661,6 +661,9 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) // dropped it re-enabled integration by parts one level below the call that // switched it off -- which is a cycle, since by parts calls back into here. // `x * ln(x)` went round it until the stack ran out. + // A sum free of the variable that comes to a number multiplied out, as that number, + // before anything reads a coefficient or an exponent in it: `2 + 2 a - 2 (1 + a)`. + if ((answer = IndefiniteIntegralSolver.SolveByFoldingConstantSumsToTheirNumbers(expr, x, integrateByParts)) is { }) return answer; // A product of powers of quotients of linears with one linear in two of them, written // over each linear once with the constant that takes in front. First, since it answers // the question asked only, and the polynomial term below writes `1/u` as `u^(-1)` and diff --git a/Sources/Tests/UnitTests/Calculus/ConstantSumsFoldedToTheirNumbersIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ConstantSumsFoldedToTheirNumbersIntegralTest.cs new file mode 100644 index 000000000..465264a2c --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ConstantSumsFoldedToTheirNumbersIntegralTest.cs @@ -0,0 +1,66 @@ +// +// 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 +{ + /// + /// The terms of a sum free of the variable that come to a number multiplied out, folded to it + /// before the integral is asked: Rubi's 1.2.2.2 and 1.1.1.2 spell a zero coefficient + /// 2 + 2 a - 2 (1 + a) and an exponent of -1 as 1 + 2 n - 2 (1 + n). + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class ConstantSumsFoldedToTheirNumbersIntegralTest + { + private static void DifferentiatesBack(string integrand, params (string, double)[] pins) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + + 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/sqrt(a+(2+2*c-2*(1+c))*x^4)")] + [InlineData("1/sqrt(2+2*a-2*(1+a)+c*x^4)")] + [InlineData("1/sqrt(2-2*(1+a)+c*x^4+2*a)")] + [InlineData("(c+d*x)^(1+2*n-2*(1+n))/(a+b*x)^2")] + public void IsAnsweredAndDifferentiatesBack(string integrand) + { + DifferentiatesBack(integrand, ("a", 0.6), ("b", 1.25), ("c", 1.5), ("d", -0.6), ("n", 1.0 / 3)); + DifferentiatesBack(integrand, ("a", -0.75), ("b", -0.7), ("c", 2.5), ("d", 0.4), ("n", 2.3)); + } + + [Fact] + public void LeavesASumThatIsNotANumber() + { + // `m + 2 (1 + n)` is no number multiplied out, and the power is integrated as written. + var integral = "x^(m+2*(1+n))".ToEntity().Integrate("x").Stringize(); + Assert.Contains("1 + n", integral); + } + } +}