Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Entity.Providedf>();
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;
}

/// <summary>
/// Whether <c>4 a c - b^2</c> can be positive past the arms before it: not with no
/// constant term and a real <c>b</c>, where it is <c>-b^2</c>. <c>1/(u (a + b u))</c>,
/// which every <c>1/(x (a + b x^n))</c> comes to, carried an arctangent on
/// <c>-a^2 > 0</c>. 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`.
/// </summary>
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
});

/// <summary>
/// Whether <c>4 a c - b^2</c> can be zero where <c>a</c> 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 <c>4 a c</c>. <c>1/(1 + q u^2)</c> carried a perfect-square arm on <c>4 q = 0</c>,
/// dividing by <c>q</c>.
/// </summary>
private static bool ReachesAZeroDiscriminant(Entity b, Entity c)
=> !TreeAnalyzer.IsZero(b) || c is not Entity.Number || c.Evaled is Entity.Number.Complex { IsZero: true };

/// <summary>
/// ∫ 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.
Expand Down Expand Up @@ -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<Entity.Providedf>
{
new Entity.Providedf(linearCase, a.EqualTo(0)),
new Entity.Providedf(perfectSquareCase, discriminant.EqualTo(0))
};
var arms = new List<Entity.Providedf> { 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;

Expand Down
50 changes: 50 additions & 0 deletions Sources/Tests/UnitTests/Calculus/QuadraticDenominatorArmsTest.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// 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.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[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<Entity.Piecewise>().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}");
}
}
}
}
}
Loading