From 01ea90ba7b95e7e1784f861ab88c8cec65fbaf0a Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 4 Oct 2026 20:02:17 +0000 Subject: [PATCH 1/3] A difference is offered to the substitution search as a sum is `sin(1 + 2/(1 + x))` was answered under `u = 1 + x`, and `sin(1 + 2/(1 - x))` was declined: the search offered every sum in the integrand as a candidate and no difference, so `u = 1 - x` was never tried. A difference is offered wherever a sum is, under the same guard against a partial sum. Part of #718 Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 13 +++++ .../Integration/IndefiniteIntegralSolver.cs | 8 +-- .../DifferenceAsASubstitutionIntegralTest.cs | 50 +++++++++++++++++++ 3 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 Sources/Tests/UnitTests/Calculus/DifferenceAsASubstitutionIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index dc39a1fe6..b2ea25f19 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -345,6 +345,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 is offered now wherever 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 875a31f1c..0dbb6ce8f 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -24951,7 +24951,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 @@ -25832,8 +25832,10 @@ 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. + case Sumf or Minusf when !rational && !large && node.Complexity <= LargestSumOffered: + 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}"); + } + } + } +} From b153f826147b87e25543e68a086fc594e72dd673 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Mon, 5 Oct 2026 01:39:03 +0000 Subject: [PATCH 2/3] A difference is 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). Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Continuous/Integration/IndefiniteIntegralSolver.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 7a84e837b..c733ed846 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -27067,7 +27067,11 @@ bool APowerCanBeExact(int k) break; // 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. - case Sumf or Minusf when !rational && !large && node.Complexity <= LargestSumOffered: + // 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; } From d022cedbda49292dd9a3babaace3da700ffbf891 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Mon, 5 Oct 2026 01:56:00 +0000 Subject: [PATCH 3/3] The entry says where a difference is offered Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 4f4b5f6ac..6dc517a3e 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -411,8 +411,8 @@ square and the cube keep the Hermite reduction's answers. Rubi's 1.1.3.8 `P(x) ( **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 is offered now wherever a -sum is ([#718](https://github.com/asc-community/AngouriMath/issues/718)). +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 | |---|---|---|