From bcdeaed85249f5be68b700bdc80bd41f9283c320 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Wed, 21 Oct 2026 00:43:33 +0000 Subject: [PATCH] Divide a polynomial over a polynomial beside a power of a quadratic without the quadratic's condition tan^2/(a + b tan + c tan^2)^(3/2) is x^2/((1 + x^2) Q^(3/2)) under the tangent. Partial fractions divided the numerator by the whole denominator, Q^(3/2) with it, and left 1/Q^(3/2), which is answered, and a remainder -1/((1 + x^2) Q^(3/2)) provided not Q = 0, which nothing read; without the condition it is answered. The condition holds wherever the integrand is defined, so a new rule at the end of the dispatch asks the remainder again without conditions of the form not F = 0 on a factor of the denominator, and answers only where the quotient and the remainder are both answered. Rubi's 4.3.9:39 is answered, the only unsolved row of 4.3.9 and 4.4.9, in 3,042 characters, the size of master's tan and tan^3 neighbours. 233 solved neighbours, all of 4.3.9 and 4.4.9 among them, answer as before, character for character. Part of #233. 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 | 4 ++ ...eConditionsOfTheDenominatorIntegralTest.cs | 54 ++++++++++++++++ 4 files changed, 134 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/DividingWithoutTheConditionsOfTheDenominatorIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 60160df8e..2f560abc5 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -4125,6 +4125,21 @@ a binomial ran out of time too. Rubi's 1.2.1.2 | `"(a+b*x+c*x^2)^(1/2)/(b*d+2*c*d*x)^7".Integrate("x")` | `integral(...)` | the antiderivative | | `"(b*d+2*c*d*x)^(5/2)/(a+b*x+c*x^2)^3".Integrate("x")` | `integral(...)` | the antiderivative | +### A polynomial over a polynomial beside a power of a quadratic is divided without the quadratic's condition + +**Answers where there were none.** `x^2/((1 + x^2) (a + b x + c x^2)^(3/2))` was left unevaluated, and so was Rubi's +4.3.9 `tan^2/(a + b tan + c tan^2)^(3/2)`, which the tangent writes so, while the tangent and its cube over the same +were answered. The division of the numerator leaves `1/Q^(3/2)` and `-1/((1 + x^2) Q^(3/2))`, both answered, but wrote +on the second the condition that `Q` is not zero, which nothing read. That condition holds wherever the integrand is +defined, and where every other rule has declined the second part is asked without it; a condition on the symbols +alone is kept ([#233](https://github.com/asc-community/AngouriMath/issues/233)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"tan(d+f*x)^2/(a+b*tan(d+f*x)+c*tan(d+f*x)^2)^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | 3,162 characters | +| `"x^2/((1+x^2)*(a+b*x+c*x^2)^(3/2))".ToEntity().Integrate("x")` | `integral(...)` | 2,966 characters | +| `"x^2/((2+x^2)*(a+b*x+c*x^2)^(3/2))".ToEntity().Integrate("x")` | `integral(...)` | 3,416 characters | + ### A polynomial over a power of a quadratic beside the root of another is integrated `P/(A^k sqrt(B))`, `A` and `B` two different quadratics and `k` at least two, was left diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 15054efae..94605b7d3 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -27418,6 +27418,67 @@ private static bool HoldsARepeatedSymbolicFactor(Entity denominator, Entity.Vari => Mulf.LinearChildren(denominator).Any(factor => factor is Powf(var @base, Number.Integer { EInteger.Sign: > 0 } power) && power != Number.Integer.One && @base.ContainsNode(x) && @base.Vars.Any(v => v != x)); + /// + /// A polynomial over a polynomial times a power of something else, divided as + /// divides it, with the part over the denominator + /// asked without the condition the division writes on it, that the other factor is not + /// zero -- which holds wherever the integrand is defined. + /// + /// + /// The division of x^2 by (1 + x^2) Q^(3/2) leaves + /// (-1 provided Q != 0)/((1 + x^2) Q^(3/2)), which nothing reads, where the same + /// without the condition is answered, and so is the quotient 1/Q^(3/2): Rubi's 4.3.9 + /// tan^2/(a + b tan + c tan^2)^(3/2), under the tangent, was declined while + /// tan and tan^3 over the same were answered. Last, where everything else has + /// declined, and at the question asked or the one below it, where the tangent's + /// substitution asks it: asked inside the division, the parts answered first and four + /// solved rows of 4.3.9 and 4.4.9 came back longer. Only a condition that a factor of the + /// denominator with x in it is not zero is dropped; one on the symbols alone keeps it out. + /// https://github.com/asc-community/AngouriMath/issues/233 + /// + internal static Entity? SolveByDividingWithoutTheConditionsOfTheDenominator(Entity expr, Entity.Variable x, bool integrateByParts) + { + if (!Integration.AnsweringTheQuestionAskedOrOneBelow + || !TryReadAsQuotient(expr, out var numerator, out var denominator) + || !TreeAnalyzer.TryGetPolynomial(numerator, x, out _) + || IsProperByDegree(numerator, denominator, x) + || TreeAnalyzer.PolynomialLongDivision(numerator, denominator, genericCase: true, inTermsOf: x) is not var (quotient, properPart) + || quotient.Evaled == Entity.Number.Integer.Create(0) + || WithoutTheConditionsOfItsDenominator(properPart, denominator, x) is not { } unconditioned + || Integration.ComputeIndefiniteIntegral(quotient, x, integrateByParts) is not { } wholePart + || Integration.ComputeIndefiniteIntegral(unconditioned, x, integrateByParts) is not { } fractionPart) + return null; + return wholePart + fractionPart; + } + + /// + /// with every condition in it that a factor of + /// holding , or the base of a positive + /// power among them, is not zero dropped, as it + /// holds wherever the integrand is defined; null where it has no condition, or one of + /// another kind. + /// + private static Entity? WithoutTheConditionsOfItsDenominator(Entity part, Entity denominator, Entity.Variable x) + { + var dropped = false; + var other = false; + var written = part.Replace(node => + { + if (node is not Providedf(var inner, var condition)) + return node; + if (condition is Notf(Equalsf(var factor, var zero)) && zero.Evaled is Number.Complex { IsZero: true } + && factor.ContainsNode(x) && Mulf.LinearChildren(denominator).Any(written => + written == factor || written is Powf(var @base, Number.Real { IsPositive: true }) && @base == factor)) + { + dropped = true; + return inner; + } + other = true; + return node; + }); + return dropped && !other ? written.InnerSimplified : null; + } + /// /// Whether both read as polynomials in with the numerator's /// degree below the denominator's; false where either does not read. diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index ddf79901a..7ea041069 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -1511,6 +1511,10 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) // A hyperbolic integrand that is not rational in its exponential, asked as a trigonometric // one: last, since the question in s can take long where a rule above answers in x at once. if ((answer = IndefiniteIntegralSolver.SolveAHyperbolicIntegrandAsATrigonometricOne(expr, x, integrateByParts, rationalToo: false)) is { }) return answer; + // A polynomial over a polynomial beside a power, divided, the part over the denominator + // asked without the division's condition that the power is not zero: last, where nothing + // has answered it with the condition, `x^2/((1 + x^2) (a + b x + c x^2)^(3/2))`. + if ((answer = IndefiniteIntegralSolver.SolveByDividingWithoutTheConditionsOfTheDenominator(expr, x, integrateByParts)) is { }) return answer; return null; } /// diff --git a/Sources/Tests/UnitTests/Calculus/DividingWithoutTheConditionsOfTheDenominatorIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/DividingWithoutTheConditionsOfTheDenominatorIntegralTest.cs new file mode 100644 index 000000000..be5dca664 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/DividingWithoutTheConditionsOfTheDenominatorIntegralTest.cs @@ -0,0 +1,54 @@ +// +// 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 polynomial over a polynomial beside a power of a quadratic, divided, and the part over + /// the denominator asked without the condition the division writes on it, that the quadratic + /// is not zero: x^2/((1 + x^2) Q^(3/2)) is 1/Q^(3/2) - 1/((1 + x^2) Q^(3/2)), and + /// with the condition nothing read the second. Rubi's 4.3.9 + /// tan^2/(a + b tan + c tan^2)^(3/2), under the tangent, was declined while the tangent + /// and its cube over the same were answered. + /// #233 + /// + [Trait("Area", "Calculus")] + public sealed class DividingWithoutTheConditionsOfTheDenominatorIntegralTest + { + [Theory] + [InlineData("tan(d + f*x)^2/(a + b*tan(d + f*x) + c*tan(d + f*x)^2)^(3/2)")] + [InlineData("x^2/((1 + x^2)*(a + b*x + c*x^2)^(3/2))")] + [InlineData("x^2/((2 + x^2)*(a + b*x + c*x^2)^(3/2))")] + public void IsAnswered(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("a", 0.7).Substitute("b", 1.1).Substitute("c", 1.4).Substitute("d", 0.3).Substitute("f", 0.9); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + var compared = 0; + foreach (var at in new[] { -2.9, -1.7, -0.35, 0.25, 0.8, 1.4, 2.2 }) + { + 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}"); + } + } +}