From 8a08da74e01596b8fcf7c93b3074be4b7ecd4cf1 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 4 Oct 2026 15:18:33 +0000 Subject: [PATCH 1/3] A quotient by the square of a base is integrated by parts against the base's reciprocal Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 15 +++++ .../Integration/IndefiniteIntegralSolver.cs | 61 +++++++++++++++++++ .../Integration/Integration.Definition.cs | 3 + .../SquaredBaseByPartsIntegralTest.cs | 52 ++++++++++++++++ 4 files changed, 131 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/SquaredBaseByPartsIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 4d47c7700..79d1daa37 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -700,6 +700,21 @@ but `sqrt(q)^2 = q` ([#1598](https://github.com/asc-community/AngouriMath/issues | `"1/sqrt(-x^2 + i)".Integrate("x")` | `-arcsin(-2x/sqrt(4i)) + C` | `ln(-2x + 2i sqrt(-x^2 + i))/i + C`, an antiderivative as before, in the logarithm | | `"1/(sqrt(x)*sqrt(1 + i*x))".Integrate("x")`, and Rubi 4.3.2.1's `sqrt(tan(c + d x))/(a + i a tan(c + d x))^(5/2)` | `integral(...)` | the antiderivative | +### A quotient by the square of a base is integrated by parts against the base's reciprocal + +**Answers where there were none.** `x^2/(a x cos(a x) - sin(a x))^2` was declined, with the rest of +Rubi's 4.7.7 over the square of `a x cos(a x) - sin(a x)` or `cos(a x) + a x sin(a x)`. A quotient +`N/(M v^2)` by the square of a base with a function of `x` in it is `g v'/v^2` with `g = N/(M v')`, and +`v'/v^2` is the derivative of `-1/v`: its integral is `-g/v` plus the integral of `g'/v`, taken where +the base's derivative is one term and `v` cancels from `g'/v` +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"x^2/(a*x*cos(a*x) - sin(a*x))^2".ToEntity().Integrate("x")` | `integral(...)` | `x/(a^2 sin(a x) (a x cos(a x) - sin(a x))) - cot(a x)/a^3` | +| `"sin(a*x)^2/(a*x*cos(a*x) - sin(a*x))^2".ToEntity().Integrate("x")` | `integral(...)` | a quotient by `a x cos(a x) - sin(a x)` and `1/(a^2 x)` | +| `"sin(a*x)^3/(x*(a*x*cos(a*x) - sin(a*x))^2)".ToEntity().Integrate("x")` | `integral(...)` | the same with the sine integral `Si(a x)` | + ### Trigonometric functions of multiples of one linear argument are written in it `csc(a + b x) csc(2a + 2b x)^2` was left unevaluated, and `csc(1 + x) csc(2 + 2x)^2` ran twenty diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index f072a2d88..c281819d0 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -367,6 +367,67 @@ private static bool IsADerivativeOfAFactorBelowTheBar(Entity expr, Entity.Variab return Integration.ComputeAsTheSameQuestion(written, x, integrateByParts); } + /// + /// A quotient by the square of a base, by parts against the base's reciprocal, where the + /// base's derivative makes the rest exact: N/(M v^2) is g v'/v^2 with + /// g = N/(M v'), and v'/v^2 is the derivative of -1/v, so the integral + /// is -g/v plus the integral of g'/v; taken only where v cancels from + /// that, with the functions of in it taken for indeterminates. + /// + /// + /// x^2/(a x cos(a x) - sin(a x))^2 was declined, with the rest of Rubi's 4.7.7 by the + /// square of a x cos(a x) - sin(a x) or cos(a x) + a x sin(a x): the base's + /// derivative is -a^2 x sin(a x), so g is -x csc(a x)/a^2, and + /// g' is a^2 v/(a^2 sin(a x))^2, whose integral is -cot(a x)/a^3. Only + /// for a base with a function of in it that is not a polynomial, since + /// a quotient of polynomials is the partial fractions', and only where v cancels, + /// since otherwise the remainder is the harder question. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveByPartsAgainstTheReciprocalOfASquaredBase(Entity expr, Entity.Variable x) + { + if (!TryReadAsQuotient(expr, out var numerator, out var denominator) || !denominator.ContainsNode(x)) + return null; + Entity? squared = null; + Entity rest = Number.Integer.One; + foreach (var factor in Mulf.LinearChildren(denominator)) + if (squared is null && factor is Powf(var @base, Number.Integer { EInteger: var power }) && power.Equals(EInteger.FromInt32(2)) + && @base is Sumf or Minusf && @base.ContainsNode(x) + && @base.Nodes.Any(node => node.ContainsNode(x) && node is Sinf or Cosf or Tanf or Cotanf or Secantf or Cosecantf or Logf or Powf(Number, _))) + squared = @base; + else + rest = rest * factor; + if (squared is null) + return null; + // A power of x every term of the base has goes to the rest: `x^2 v^2` arrives as + // `(a x^2 cos(a x) - x sin(a x))^2`, the square of `x v`. + var lowest = Sumf.LinearChildren(squared).Select(term => Mulf.LinearChildren(term) + .Select(factor => factor == x ? 1 : factor is Powf(var b, Number.Integer { EInteger: var n }) && b == x && n.CanFitInInt32() ? n.ToInt32Unchecked() : 0) + .Sum()).Min(); + if (lowest > 0) + { + squared = (squared / MathS.Pow(x, lowest)).Expand().InnerSimplified; + rest = rest * MathS.Pow(x, 2 * lowest); + } + // Expanded, since the derivative of `a x cos(a x)` and of `sin(a x)` each have an + // `a cos(a x)`, which cancel only once multiplied out. + var derivative = squared.Differentiate(x).Expand().InnerSimplified; + // A derivative of one term, so that what it leaves below the bar of the remainder is + // no new square of a sum to take apart the same way. + if (TreeAnalyzer.IsZero(derivative) || derivative is Sumf or Minusf) + return null; + var (gTop, gBottom) = CancelledWithFunctionsAsIndeterminates(numerator, rest * derivative, x, expr); + // g' over v, cancelled: (A' B - A B')/(B^2 v) for g = A/B. + var above = (gTop.Differentiate(x) * gBottom - gTop * gBottom.Differentiate(x)).InnerSimplified; + var (rTop, rBottom) = CancelledWithFunctionsAsIndeterminates(above, MathS.Pow(gBottom, 2) * squared, x, expr); + if (rBottom.Nodes.Any(node => node == squared) || !rTop.ContainsNode(x) && !rBottom.ContainsNode(x) && TreeAnalyzer.IsZero(rTop)) + return null; + var remainder = Functions.PartialFractions.Bare(rTop / rBottom); + if (Integration.ComputeIndefiniteIntegral(remainder, x) is not { } integral || integral.Nodes.Any(node => node is Integralf)) + return null; + return -gTop / (gBottom * squared) + integral; + } + /// Whether holds a fractional power of something in . private static bool HasARadicalOf(Entity expr, Entity.Variable x) => expr.Nodes.Any(node => node is Powf(var @base, Number.Rational power) && power is not Number.Integer && @base.ContainsNode(x)); diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 55108cc56..99c9bf358 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -750,6 +750,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) => // is `x^2/(L^2 sqrt(q))` cancelled, and was five terms over `L^5 (1 + L sqrt(q))` // each searched for seconds when the reduction reached it first. if ((answer = IndefiniteIntegralSolver.SolveByCancellingWithFunctionsAsIndeterminates(expr, x, integrateByParts)) is { }) return answer; + // A quotient by the square of a base with a function in it, by parts against the base's + // reciprocal where the base's derivative makes the rest exact. + if ((answer = IndefiniteIntegralSolver.SolveByPartsAgainstTheReciprocalOfASquaredBase(expr, x)) is { }) return answer; // A polynomial over a power of a linear and something that is not a polynomial, // the polynomial written in powers of the linear at its root: the part the power // divides goes over the rest alone. Before the split, so that the whole polynomial diff --git a/Sources/Tests/UnitTests/Calculus/SquaredBaseByPartsIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/SquaredBaseByPartsIntegralTest.cs new file mode 100644 index 000000000..1187139a2 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/SquaredBaseByPartsIntegralTest.cs @@ -0,0 +1,52 @@ +// +// 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 quotient by the square of a base with a function in it, by parts against the base's + /// reciprocal: N/(M v^2) is g v'/v^2 with g = N/(M v'), whose integral is + /// -g/v plus that of g'/v, which the base cancels from where its derivative is + /// one term. x^2/(a x cos(a x) - sin(a x))^2 was declined; Rubi's 4.7.7. + /// #718 + /// + /// + /// Checked by differentiating back with a = 1.3 on both sides of zero. + /// + [Trait("Area", "Calculus")] + public sealed class SquaredBaseByPartsIntegralTest + { + [Theory] + [InlineData("x^2/(a*x*cos(a*x) - sin(a*x))^2")] + [InlineData("sin(a*x)^2/(a*x*cos(a*x) - sin(a*x))^2")] + [InlineData("sin(a*x)^3/(x*(a*x*cos(a*x) - sin(a*x))^2)")] + [InlineData("sin(a*x)^4/(x^2*(a*x*cos(a*x) - sin(a*x))^2)")] + [InlineData("x^2/(cos(a*x) + a*x*sin(a*x))^2")] + [InlineData("cos(a*x)^2/(cos(a*x) + a*x*sin(a*x))^2")] + [InlineData("cos(a*x)^3/(x*(cos(a*x) + a*x*sin(a*x))^2)")] + public void IsIntegratedAgainstTheReciprocalOfTheBase(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + Entity Pinned(Entity e) => e.Substitute("a", 1.3); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + foreach (var at in new[] { -2.3, -1.1, -0.4, 0.4, 1.1, 2.3 }) + { + 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)), + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + } +} From e5a2b3c0d773c7b779d2f7505d7e25e062dc0c1d Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 4 Oct 2026 15:39:51 +0000 Subject: [PATCH 2/3] A squared base is read only where the variable stands outside its functions too Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Integration/IndefiniteIntegralSolver.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index c281819d0..3e20e2f50 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -393,7 +393,12 @@ private static bool IsADerivativeOfAFactorBelowTheBar(Entity expr, Entity.Variab foreach (var factor in Mulf.LinearChildren(denominator)) if (squared is null && factor is Powf(var @base, Number.Integer { EInteger: var power }) && power.Equals(EInteger.FromInt32(2)) && @base is Sumf or Minusf && @base.ContainsNode(x) - && @base.Nodes.Any(node => node.ContainsNode(x) && node is Sinf or Cosf or Tanf or Cotanf or Secantf or Cosecantf or Logf or Powf(Number, _))) + && @base.Nodes.Any(node => node.ContainsNode(x) && IsAFunctionOfTheVariable(node)) + // The variable outside the functions as well, as in `a x cos(a x) - sin(a x)`: a + // base that is a function of `sin(x)` alone, `(a + b sin(x))^2`, is the + // substitutions' and the reductions', and was answered in a second where this + // ran past the budget. + && @base.Replace(node => IsAFunctionOfTheVariable(node) ? Number.Integer.One : node).ContainsNode(x)) squared = @base; else rest = rest * factor; @@ -428,6 +433,10 @@ private static bool IsADerivativeOfAFactorBelowTheBar(Entity expr, Entity.Variab return -gTop / (gBottom * squared) + integral; } + /// Whether is a trigonometric function, a logarithm or an exponential. + private static bool IsAFunctionOfTheVariable(Entity node) + => node is Sinf or Cosf or Tanf or Cotanf or Secantf or Cosecantf or Logf or Powf(Number, _); + /// Whether holds a fractional power of something in . private static bool HasARadicalOf(Entity expr, Entity.Variable x) => expr.Nodes.Any(node => node is Powf(var @base, Number.Rational power) && power is not Number.Integer && @base.ContainsNode(x)); From 8693cf0dd3c745dae85651938455a42769c17117 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 4 Oct 2026 18:41:42 +0000 Subject: [PATCH 3/3] The ring's answer for a squared base comes first, where it has one x^2/(x cos(x) - sin(x))^2 is (x sin(x) + cos(x))/(x cos(x) - sin(x)) by the tower ansatz, continuous across the zeros of the base's derivative, where the by-parts answer's two terms each have a pole that cancels. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Continuous/Integration/IndefiniteIntegralSolver.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 3e20e2f50..5efb0ec70 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -421,6 +421,11 @@ private static bool IsADerivativeOfAFactorBelowTheBar(Entity expr, Entity.Variab // no new square of a sum to take apart the same way. if (TreeAnalyzer.IsZero(derivative) || derivative is Sumf or Minusf) return null; + // The ring's answer first, where it has one: `x^2/(x cos(x) - sin(x))^2` is + // `(x sin(x) + cos(x))/(x cos(x) - sin(x))` there, continuous across the zeros of the + // base's derivative, where `-g/v` and the remainder each have a pole that cancels. + if (SolveByTrigonometricTowerAnsatz(expr, x) is { } fromTheRing) + return fromTheRing; var (gTop, gBottom) = CancelledWithFunctionsAsIndeterminates(numerator, rest * derivative, x, expr); // g' over v, cancelled: (A' B - A B')/(B^2 v) for g = A/B. var above = (gTop.Differentiate(x) * gBottom - gTop * gBottom.Differentiate(x)).InnerSimplified;