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
12 changes: 12 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,18 @@ unevaluated, is answered by this route as well.
| `"(a*x+2*b*n*ln(c*x^n))/(a*x^2+b*x*ln(c*x^n)^2)".ToEntity().Integrate("x")` | `integral(...)` | 37 characters |
| `"tanh(a+2*ln(x))^2/x^3".ToEntity().Integrate("x")` | `integral(...)` | 7,796 characters |

### A symbolic cubic with a repeated root is written over its roots

**Answers where there were none.** `1/(x^3 - 3b x + 2b^(3/2))` was left unevaluated: its coefficients are symbolic, so
no numeric root finder applies, and its discriminant vanishes identically. Such a cubic is `a (x - r)^2 (x - t)` with
`r` and `t` rational in its coefficients, and is written so before the partial fractions
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"1/(x^3-3*b*x+2*b^(3/2))".ToEntity().Integrate("x")` | `integral(...)` | 122 characters |
| `"1/(-9*b*x+9*x^3+2*b^(3/2)*sqrt(3))".ToEntity().Integrate("x")` | `integral(...)` | 176 characters |

### A symbol shared by a numerator sum's coefficients is taken out before the partial fractions

**Answers where there were none.** `(b d + 2c d x)^9/(a + b x + c x^2)^3` ran past a minute, where
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29334,6 +29334,79 @@ static bool IsZero(Entity value) => value.InnerSimplified.Evaled is Number.Compl
return written == expr ? null : Integration.ComputeAsTheSameQuestion(written, x, integrateByParts);
}

/// <summary>
/// A quotient with a cubic in <paramref name="x"/> below the bar whose coefficients are
/// symbolic and whose discriminant vanishes identically, with the cubic written over its
/// roots: <c>a x^3 + b x^2 + c x + d</c> is <c>a (x - r)^2 (x - s)</c> with the double root
/// <c>r = (9ad - bc)/(2(b^2 - 3ac))</c> and the simple one
/// <c>s = (4abc - 9a^2 d - b^3)/(a(b^2 - 3ac))</c>, or <c>a (x + b/(3a))^3</c> where
/// <c>b^2 - 3ac</c> vanishes too.
/// </summary>
/// <remarks>
/// Rubi's 1.3.1 <c>1/(9x^3 - 9bx + 2 sqrt(3) b^(3/2))</c> was left unevaluated, and the same
/// written <c>9 (x - sqrt(b/3))^2 (x + 2 sqrt(b/3))</c> is answered in 60 ms. The factored
/// form is checked against the cubic before it is handed on.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </remarks>
internal static Entity? SolveACubicWithARepeatedRootOverItsRoots(Entity expr, Entity.Variable x, bool integrateByParts)
{
if (!Integration.AnsweringTheQuestionAsked)
return null;
var (above, below) = Functions.SingleQuotient.Of(Functions.SingleQuotient.Combine(expr));
if (!below.ContainsNode(x))
return null;
Entity rest = Number.Integer.One;
Entity? written = null;
foreach (var factor in Mulf.LinearChildren(below))
{
if (written is null && factor is Sumf or Minusf && TreeAnalyzer.TryGetPolynomial(factor, x, out var monomials)
&& monomials.Keys.All(k => k.Sign >= 0 && k.CompareTo(EInteger.FromInt32(3)) <= 0)
&& monomials.TryGetValue(EInteger.FromInt32(3), out var a) && !VanishesIdentically(a)
&& monomials.Values.All(coefficient => !coefficient.ContainsNode(x))
&& monomials.Values.Any(coefficient => coefficient.InnerSimplified.Evaled is not Number.Complex))
{
Entity Coefficient(int k) => monomials.TryGetValue(EInteger.FromInt32(k), out var value) ? value : Number.Integer.Zero;
var (b, c, d) = (Coefficient(2), Coefficient(1), Coefficient(0));
var discriminant = 18 * a * b * c * d - 4 * MathS.Pow(b, 3) * d + MathS.Sqr(b) * MathS.Sqr(c) - 4 * a * MathS.Pow(c, 3) - 27 * MathS.Sqr(a) * MathS.Sqr(d);
if (!VanishesIdentically(discriminant))
return null;
var depressed = (MathS.Sqr(b) - 3 * a * c).InnerSimplified;
Entity? over = null;
// The roots simplified first, which folds the quotients of the coefficients to
// short ones -- `sqrt(b)` rather than a product of six numbers and it -- where
// the simplified form still expands back to the cubic, and as they are where not.
foreach (var simplify in new[] { true, false })
{
// Without the condition the simplification adds for b^2 - 3ac, which this
// case already assumes: the generic case is the one answered.
Entity Tidy(Entity e) => simplify ? e.Simplify() is Providedf(var value, _) ? value : e.Simplify() : e.InnerSimplified;
Entity candidate;
if (VanishesIdentically(depressed))
candidate = a * MathS.Pow(x + Tidy(b / (3 * a)), 3);
else
{
var r = Tidy((9 * a * d - b * c) / (2 * depressed));
var t = Tidy((4 * a * b * c - 9 * MathS.Sqr(a) * d - MathS.Pow(b, 3)) / (a * depressed));
candidate = a * MathS.Sqr(x - r) * (x - t);
}
if (VanishesIdentically((candidate - factor).Expand()))
{
over = candidate;
break;
}
}
if (over is null)
return null;
written = over;
continue;
}
rest *= factor;
}
if (written is null)
return null;
return Integration.ComputeAsTheSameQuestion(above / (rest * written), x, integrateByParts);
}

/// <summary>
/// A quotient whose denominator is a sum every term of which carries the same power of one
/// linear form in <paramref name="x"/>, with that power taken out below the bar:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1223,6 +1223,11 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
if (expr is Entity.Sumf or Entity.Minusf
&& (answer = IndefiniteIntegralSolver.SolveByGatheringACommonPolynomialFactorOfASum(expr, x, integrateByParts)) is { })
return answer;
// A cubic below the bar with symbolic coefficients and a repeated root, written over
// its roots: `1/(x^3 - 3b x + 2b^(3/2))` is `1/((x - sqrt(b))^2 (x + 2 sqrt(b)))`.
if (expr is Entity.Divf or Entity.Powf
&& (answer = IndefiniteIntegralSolver.SolveACubicWithARepeatedRootOverItsRoots(expr, x, integrateByParts)) is { })
return answer;
// The same factor taken out of a sum below the bar: `1/(a x + b x/ln(c x^n))` is
// `1/(x (a + b/ln(c x^n)))`, a function of the logarithm beside its differential.
if (expr is Entity.Divf or Entity.Powf
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// A cubic with symbolic coefficients and a repeated root below the bar, written over its
/// roots: Rubi's 1.3.1 <c>1/(9x^3 - 9bx + 2 sqrt(3) b^(3/2))</c>, which was left unevaluated.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class ACubicWithARepeatedRootIntegralTest
{
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");
// Away from the roots, at b > 0, where the coefficients are real.
foreach (var at in new[] { -3.1, -0.4, 0.3, 2.7 })
{
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("1/(-9*b*x+9*x^3+2*b^(3/2)*sqrt(3))")]
[InlineData("x/(x^3-3*b*x+2*b^(3/2))")]
[InlineData("1/(x^3+3*k*x^2+3*k^2*x+k^3)")]
public void IsAnsweredAndDifferentiatesBack(string integrand)
{
DifferentiatesBack(integrand, ("b", 1.3), ("k", 0.7));
DifferentiatesBack(integrand, ("b", 2.2), ("k", -1.6));
}
}
}
Loading