From 0a38d5023d38887bf3574e54b17c3c7e560df04d Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Fri, 16 Oct 2026 06:06:57 +0000 Subject: [PATCH 1/2] Split a biquadratic with surds in it by its roots in the square The rule for an even polynomial over a + b x^2 + c x^4 took symbolic coefficients only, and left numbers to the partial fractions, which factor over the rationals: Rubi's 1.2.2.3 (sqrt(2) - x^2)/(1 - sqrt(2) x^2 + x^4) and (sqrt(2) + x^2)/(1 + sqrt(2) x^2 + x^4) were declined. Part of https://github.com/asc-community/AngouriMath/issues/718 Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Integration/IndefiniteIntegralSolver.cs | 4 +- .../ABiquadraticWithSurdsIntegralTest.cs | 57 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 Sources/Tests/UnitTests/Calculus/ABiquadraticWithSurdsIntegralTest.cs diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 5a0a8ccab..7c10cf520 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -3153,7 +3153,9 @@ over is Entity.Powf(var @base, var power) ? if (!TreeAnalyzer.TryGetPolynomial(denominator, x, out var belowAsWritten) || belowAsWritten.Count == 0 || belowAsWritten.Keys.Any(power => power.Sign < 0 || !power.CanFitInInt32()) || belowAsWritten.Values.Any(coefficient => coefficient.ContainsNode(x)) - || !denominator.Vars.Any(v => v != x)) + // A symbol, or a number that is not rational, which the partial fractions do not + // factor over: `(sqrt(2) - x^2)/(1 - sqrt(2) x^2 + x^4)` was declined. + || !denominator.Vars.Any(v => v != x) && belowAsWritten.Values.All(coefficient => coefficient.Evaled is Number.Rational)) return null; if (!TreeAnalyzer.TryGetPolynomial(numerator, x, out var aboveAsWritten) || aboveAsWritten.Count == 0 || aboveAsWritten.Keys.Any(power => power.Sign < 0 || !power.CanFitInInt32()) diff --git a/Sources/Tests/UnitTests/Calculus/ABiquadraticWithSurdsIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ABiquadraticWithSurdsIntegralTest.cs new file mode 100644 index 000000000..d987fac63 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ABiquadraticWithSurdsIntegralTest.cs @@ -0,0 +1,57 @@ +// +// 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 +{ + /// + /// An even polynomial over a biquadratic whose coefficients are numbers that are not rational, + /// split by its roots in x^2 as a symbolic one is: Rubi's 1.2.2.3 + /// (sqrt(2) - x^2)/(1 - sqrt(2) x^2 + x^4). Roots in x^2 complex, and real. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class ABiquadraticWithSurdsIntegralTest + { + 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("(-x^2+sqrt(2))/(1+x^4-x^2*sqrt(2))")] + [InlineData("(x^2+sqrt(2))/(1+x^4+x^2*sqrt(2))")] + [InlineData("1/(1+x^4-x^2*sqrt(3))")] + [InlineData("(1+x^2+x^4)/(1+x^4-x^2*sqrt(2))")] + [InlineData("1/(1+x^4-3*x^2*sqrt(2))")] + [InlineData("x^2/(1-x^4*sqrt(2)+x^2)")] + public void IsAnsweredAndDifferentiatesBack(string integrand) + => DifferentiatesBack(integrand); + } +} From 8dedea120345ca30529ecf31bc2599384e41ea1c Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Fri, 16 Oct 2026 09:20:52 +0000 Subject: [PATCH 2/2] Record the biquadratics with surds in the breaking changes Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 054ae499b..0547ae4bf 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -396,6 +396,20 @@ 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 biquadratic with surds in its coefficients is split by its roots in the square + +**Answers where there were none.** `(sqrt(2) - x^2)/(1 - sqrt(2) x^2 + x^4)` was declined, and so was Rubi's 1.2.2.3 +`(sqrt(2) + x^2)/(1 + sqrt(2) x^2 + x^4)`. An even polynomial over `a + b x^2 + c x^4` was split by the two roots in +`x^2` only where a coefficient held a symbol, and numbers were left to the partial fractions, which factor over the +rationals. It is now split so wherever a coefficient is not rational +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"(-x^2+sqrt(2))/(1+x^4-x^2*sqrt(2))".ToEntity().Integrate("x")` | `integral(...)` | 199 characters | +| `"(x^2+sqrt(2))/(1+x^4+x^2*sqrt(2))".ToEntity().Integrate("x")` | `integral(...)` | 201 characters | +| `"1/(1+x^4-x^2*sqrt(3))".ToEntity().Integrate("x")` | `integral(...)` | 144 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