From f9b41c7f474b38020d4734af0ff93fe0780fc2ef Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Mon, 19 Oct 2026 00:31:00 +0000 Subject: [PATCH] Leave a sum beside its own derivative to the plain substitution Taking a power of x out of a fractional power of a sum (#1705) runs before `SolveBySubstitution`, and on `(x - 3 sqrt(x))^(2/3) (2 sqrt(x) - 3)/sqrt(x)` it took the integral somewhere it could not finish, where u = the sum answers it outright: 2.5.0 did in a fifth of a second, 2.6.0 not in twenty. The rule now passes over a sum when the rest of the integrand is a constant times that sum's derivative, checked at three sample points. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Integration/IndefiniteIntegralSolver.cs | 44 ++++++++++++++++ ...sDerivativeKeepsItsPowerOfXIntegralTest.cs | 51 +++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/ASumBesideItsDerivativeKeepsItsPowerOfXIntegralTest.cs diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index b82a9959c..857496cc0 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -23250,6 +23250,12 @@ bool MayBeARootOfASquare(Entity node, Entity.Variable x) && (Mulf.LinearChildren(rest).Where(part => part.ContainsNode(x)).ToList() is not [Sumf or Minusf] || Sumf.LinearChildren(rest).All(term => PowerOfX(term, x) is (_, Number.Integer { EInteger: var degree }) && degree.CompareTo(1) <= 0 && degree.Sign >= 0))) continue; + // The rest of the integrand a constant times the sum's derivative is the plain + // substitution's, u = the sum, which answers it as a power of u, where the sum + // with x taken out is a search: `(x - 3 sqrt(x))^(2/3) (2 sqrt(x) - 3)/sqrt(x)` + // was answered in a fifth of a second before this rule and not in twenty after. + if (IsAConstantTimesTheDerivativeOf(side == above ? expr / factor : expr * factor, sum, x)) + continue; // Two such would want two factors. if (found is { }) return null; @@ -31557,6 +31563,44 @@ node is Powf(var radicand, Number.Rational { IsNegative: true } negative) && neg return null; } + /// + /// Whether is a nonzero constant times the derivative of + /// : their quotient one number at three positive points, every other + /// symbol pinned, agreeing to nine digits relative to its size. Positive points, since a + /// fractional power of x is real there; a point where either side cannot be evaluated + /// answers no. + /// + private static bool IsAConstantTimesTheDerivativeOf(Entity rest, Entity sum, Entity.Variable x) + { + var derivative = sum.Differentiate(x); + var pinned = new Dictionary(); + var values = new[] { 1.37, 0.61, 2.23, 1.91, 0.83, 1.13 }; + var index = 0; + foreach (var symbol in rest.Vars.Concat(sum.Vars).Distinct()) + if (symbol != x) + pinned[symbol] = values[index++ % values.Length]; + Number.Complex? At(Entity function, double point) + { + var value = function.Substitute(x, point); + foreach (var pair in pinned) + value = value.Substitute(pair.Key, pair.Value); + return value.Evaled is Number.Complex { IsNaN: false } number && number.IsFinite && !number.IsZero ? number : null; + } + Number.Complex? first = null; + foreach (var point in new[] { 0.37, 1.71, 2.93 }) + { + if (At(rest, point) is not { } above || At(derivative, point) is not { } below) + return false; + var ratio = (Number.Complex)(above / below); + var size = ((Number.Real)ratio.Abs()).EDecimal.ToDouble(); + if (!(size > 1e-12 && size < 1e12) + || first is { } earlier && ((Number.Real)(ratio - earlier).Abs()).EDecimal.ToDouble() > 1e-9 * size) + return false; + first ??= ratio; + } + return true; + } + /// /// /, two polynomials in , /// as a constant, or where one is not a constant multiple of the diff --git a/Sources/Tests/UnitTests/Calculus/ASumBesideItsDerivativeKeepsItsPowerOfXIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ASumBesideItsDerivativeKeepsItsPowerOfXIntegralTest.cs new file mode 100644 index 000000000..800e8d88d --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ASumBesideItsDerivativeKeepsItsPowerOfXIntegralTest.cs @@ -0,0 +1,51 @@ +// +// 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 fractional power of a sum beside a constant times the sum's derivative is the plain + /// substitution's, u = the sum, and the power of x is not taken out of it first: + /// (x - 3 sqrt(x))^(2/3) (2 sqrt(x) - 3)/sqrt(x) is 2 u^(2/3) du. 2.5.0 answered it + /// in a fifth of a second; with the power of x taken out it was not answered in twenty. Rubi's 1.3.2. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class ASumBesideItsDerivativeKeepsItsPowerOfXIntegralTest + { + [Theory] + [InlineData("(x - 3*sqrt(x))^(2/3)*(-3 + 2*sqrt(x))/sqrt(x)")] + [InlineData("(2*x + 3*b*x^2)*(x^2 + b*x^3)^(1/2)")] + public void BySubstitutingTheSum(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + var text = integral.Stringize(); + Assert.DoesNotContain("integral(", text); + Assert.True(text.Length < 5000, $"{text.Length} characters of answer for {integrand}"); + Entity Pinned(Entity e) => e.Substitute("b", 0.7); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + var compared = 0; + foreach (var at in new[] { 0.3, 0.8, 1.3, 2.9, 11.3, 15.1 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + var got = derivative.Substitute("x", at).EvalNumerical(); + if (want.IsNaN) + continue; + compared++; + 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}"); + } + Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}"); + } + } +}