diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index ffb6e7432..a95f15848 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -358,6 +358,21 @@ also how the substitution `u = x^2` writes `x/(a + c x^4)^2` since 2.5.0, having | `"(x/(a + c*x^2))^2".ToEntity().Integrate("x")` | `integral(...)` | the same | | `"x/(a + c*x^4)^2".ToEntity().Integrate("x")` | the answer, in 0.1 s; 22 s on the unreleased master | the answer, in 0.06 s | +### Arms no real coefficients reach are left out of a symbolic quadratic's antiderivative + +**Shorter answers.** An integrand over a quadratic with symbols in it is answered by arms on its +leading coefficient and on the sign of its discriminant `4 a c - b^2`, and two of them could not be +reached: with no linear term and a number for the constant one the discriminant is zero only where +the leading coefficient is, which the first arm takes -- and that arm divided by it; and with no +constant term the discriminant is `-b^2`, never positive for a real `b`. Both are left out, which is +up to a third of such an answer, and of every answer built on one, as `1/(x (a + b x^n))` is +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"1/(1 + q*x^2)".ToEntity().Integrate("x")` | four arms, the third `-2/(2 q x)` on `4 q = 0` | three, without it | +| `"1/(x*(a + b*x))".ToEntity().Integrate("x")` | four arms, the second an arctangent on `-a^2 > 0` | three, without it | + ### A polynomial with symbols in it that is a binomial or an even quartic in a shifted variable is integrated in it **Answers where there were none.** `1/(c^2 x^3 + 3 b c x^2 + 3 b^2 x + 3 a b)` was left unevaluated, diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IntegralPatterns.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IntegralPatterns.cs index 573679294..550ccbdab 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IntegralPatterns.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IntegralPatterns.cs @@ -960,18 +960,47 @@ private static Entity IntegrateRationalQuadratic(Entity numerator, Entity a, Ent var sqrtNegDiscriminant = MathS.Sqrt(-discriminant); var lnCase = numerator * AntiderivativeLog((twoAxPlusB - sqrtNegDiscriminant) / (twoAxPlusB + sqrtNegDiscriminant)) / sqrtNegDiscriminant; - // Return as piecewise based on a and discriminant + // Return as piecewise based on a and discriminant, without the arms no real + // coefficients reach: see ReachesAPositiveDiscriminant and ReachesAZeroDiscriminant. var cases = new List(); if (!denominatorVanishesWithoutA) cases.Add(new Entity.Providedf(linearCase, a.EqualTo(0))); if (ArmOffTheRealLine(arctanCase, discriminant) is { } offTheRealLine) cases.Add(offTheRealLine); - cases.Add(new Entity.Providedf(arctanCase, discriminant > 0)); - cases.Add(new Entity.Providedf(perfectSquareCase, discriminant.EqualTo(0))); + if (ReachesAPositiveDiscriminant(b, c)) + cases.Add(new Entity.Providedf(arctanCase, discriminant > 0)); + if (ReachesAZeroDiscriminant(b, c)) + cases.Add(new Entity.Providedf(perfectSquareCase, discriminant.EqualTo(0))); cases.Add(new Entity.Providedf(lnCase, discriminant < 0)); return MathS.Piecewise(cases).InnerSimplified; } + /// + /// Whether 4 a c - b^2 can be positive past the arms before it: not with no + /// constant term and a real b, where it is -b^2. 1/(u (a + b u)), + /// which every 1/(x (a + b x^n)) comes to, carried an arctangent on + /// -a^2 > 0. Real as written only: symbols and real numbers by arithmetic and + /// whole powers. Under `sqrt(-(a + b x)^2)` the coefficient is `sqrt(-b^2) a/b`, and the + /// discriminant is `a^2`. + /// + private static bool ReachesAPositiveDiscriminant(Entity b, Entity c) + => !TreeAnalyzer.IsZero(c) || !b.InnerSimplified.Nodes.All(node => node switch + { + Entity.Variable or Entity.Number.Real => true, + Entity.Sumf or Entity.Minusf or Entity.Mulf or Entity.Divf => true, + Entity.Powf(_, Entity.Number.Integer) => true, + _ => false + }); + + /// + /// Whether 4 a c - b^2 can be zero where a is not, which the first arm + /// takes: not with no linear term and a number other than 0 for the constant one, where it + /// is 4 a c. 1/(1 + q u^2) carried a perfect-square arm on 4 q = 0, + /// dividing by q. + /// + private static bool ReachesAZeroDiscriminant(Entity b, Entity c) + => !TreeAnalyzer.IsZero(b) || c is not Entity.Number || c.Evaled is Entity.Number.Complex { IsZero: true }; + /// /// ∫ k / (a x^2 + b x + c)^n dx for a whole n of two or more, by the reduction that /// takes one power off the denominator at a time. @@ -1040,15 +1069,14 @@ private static Entity IntegrateOverPowerOfQuadratic( : 2 * MathS.Arctan(derivative / sqrtDiscriminant) / sqrtDiscriminant; Entity firstPowerLog = withoutTheFirstPower ? Entity.Number.Integer.Zero : AntiderivativeLog((derivative - sqrtNegDiscriminant) / (derivative + sqrtNegDiscriminant)) / sqrtNegDiscriminant; - var arms = new List - { - new Entity.Providedf(linearCase, a.EqualTo(0)), - new Entity.Providedf(perfectSquareCase, discriminant.EqualTo(0)) - }; + var arms = new List { new Entity.Providedf(linearCase, a.EqualTo(0)) }; + if (ReachesAZeroDiscriminant(b, c)) + arms.Add(new Entity.Providedf(perfectSquareCase, discriminant.EqualTo(0))); var arctanArm = Reduce(firstPowerArctan); if (ArmOffTheRealLine(arctanArm, discriminant) is { } offTheRealLine) arms.Add(offTheRealLine); - arms.Add(new Entity.Providedf(arctanArm, discriminant > 0)); + if (ReachesAPositiveDiscriminant(b, c)) + arms.Add(new Entity.Providedf(arctanArm, discriminant > 0)); arms.Add(new Entity.Providedf(Reduce(firstPowerLog), discriminant < 0)); return MathS.Piecewise(arms).InnerSimplified; diff --git a/Sources/Tests/UnitTests/Calculus/QuadraticDenominatorArmsTest.cs b/Sources/Tests/UnitTests/Calculus/QuadraticDenominatorArmsTest.cs new file mode 100644 index 000000000..277bed910 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/QuadraticDenominatorArmsTest.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 System.Linq; +using AngouriMath.Extensions; +using Xunit; + +namespace AngouriMath.Tests.Calculus +{ + /// + /// A symbolic quadratic below the bar is answered by arms on its leading coefficient and the + /// sign of its discriminant, and an arm no real coefficients reach is left out: with no + /// linear term and a number for the constant one the discriminant is zero only where the + /// leading coefficient is, and with no constant term it is never positive. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class QuadraticDenominatorArmsTest + { + [Theory] + [InlineData("1/(1 + q*x^2)")] + [InlineData("1/(3 + q*x^2)^2")] + [InlineData("1/(x*(a + q*x))")] + [InlineData("1/(x*(a + q*x))^2")] + public void HasThreeArms(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.Equal(3, integral.Nodes.OfType().Sum(piecewise => piecewise.Cases.Count())); + foreach (var q in new[] { 0.7, -0.7 }) + { + Entity Pinned(Entity e) => e.Substitute("q", q).Substitute("a", 1.3); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + foreach (var at in new[] { 0.2, 0.5 }) + { + 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} at q = {q} is {got} at x = {at}, where the integrand is {want}"); + } + } + } + } +}