Skip to content

Commit c1647fd

Browse files
Write a symbolic cubic with a repeated root over its roots (#1933)
* Write a symbolic cubic with a repeated root over its roots A cubic below the bar whose discriminant vanishes identically is a (x - r)^2 (x - s), with r and s rational in its coefficients, or a cube where b^2 - 3ac vanishes too. 1/(9x^3 - 9bx + 2 sqrt(3) b^(3/2)) was left unevaluated (Rubi's 1.3.1:8) and so written is answered in half a second. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura * Record the symbolic cubics with a repeated root in BREAKING-CHANGES Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 5bf5b1b commit c1647fd

4 files changed

Lines changed: 147 additions & 0 deletions

File tree

‎BREAKING-CHANGES.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,18 @@ unevaluated, is answered by this route as well.
583583
| `"(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 |
584584
| `"tanh(a+2*ln(x))^2/x^3".ToEntity().Integrate("x")` | `integral(...)` | 7,796 characters |
585585

586+
### A symbolic cubic with a repeated root is written over its roots
587+
588+
**Answers where there were none.** `1/(x^3 - 3b x + 2b^(3/2))` was left unevaluated: its coefficients are symbolic, so
589+
no numeric root finder applies, and its discriminant vanishes identically. Such a cubic is `a (x - r)^2 (x - t)` with
590+
`r` and `t` rational in its coefficients, and is written so before the partial fractions
591+
([#718](https://github.com/asc-community/AngouriMath/issues/718)).
592+
593+
| Input | Was (2.5.0) | Now |
594+
|---|---|---|
595+
| `"1/(x^3-3*b*x+2*b^(3/2))".ToEntity().Integrate("x")` | `integral(...)` | 122 characters |
596+
| `"1/(-9*b*x+9*x^3+2*b^(3/2)*sqrt(3))".ToEntity().Integrate("x")` | `integral(...)` | 176 characters |
597+
586598
### A symbol shared by a numerator sum's coefficients is taken out before the partial fractions
587599

588600
**Answers where there were none.** `(b d + 2c d x)^9/(a + b x + c x^2)^3` ran past a minute, where

‎Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs‎

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29334,6 +29334,79 @@ static bool IsZero(Entity value) => value.InnerSimplified.Evaled is Number.Compl
2933429334
return written == expr ? null : Integration.ComputeAsTheSameQuestion(written, x, integrateByParts);
2933529335
}
2933629336

29337+
/// <summary>
29338+
/// A quotient with a cubic in <paramref name="x"/> below the bar whose coefficients are
29339+
/// symbolic and whose discriminant vanishes identically, with the cubic written over its
29340+
/// 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
29341+
/// <c>r = (9ad - bc)/(2(b^2 - 3ac))</c> and the simple one
29342+
/// <c>s = (4abc - 9a^2 d - b^3)/(a(b^2 - 3ac))</c>, or <c>a (x + b/(3a))^3</c> where
29343+
/// <c>b^2 - 3ac</c> vanishes too.
29344+
/// </summary>
29345+
/// <remarks>
29346+
/// Rubi's 1.3.1 <c>1/(9x^3 - 9bx + 2 sqrt(3) b^(3/2))</c> was left unevaluated, and the same
29347+
/// written <c>9 (x - sqrt(b/3))^2 (x + 2 sqrt(b/3))</c> is answered in 60 ms. The factored
29348+
/// form is checked against the cubic before it is handed on.
29349+
/// https://github.com/asc-community/AngouriMath/issues/718
29350+
/// </remarks>
29351+
internal static Entity? SolveACubicWithARepeatedRootOverItsRoots(Entity expr, Entity.Variable x, bool integrateByParts)
29352+
{
29353+
if (!Integration.AnsweringTheQuestionAsked)
29354+
return null;
29355+
var (above, below) = Functions.SingleQuotient.Of(Functions.SingleQuotient.Combine(expr));
29356+
if (!below.ContainsNode(x))
29357+
return null;
29358+
Entity rest = Number.Integer.One;
29359+
Entity? written = null;
29360+
foreach (var factor in Mulf.LinearChildren(below))
29361+
{
29362+
if (written is null && factor is Sumf or Minusf && TreeAnalyzer.TryGetPolynomial(factor, x, out var monomials)
29363+
&& monomials.Keys.All(k => k.Sign >= 0 && k.CompareTo(EInteger.FromInt32(3)) <= 0)
29364+
&& monomials.TryGetValue(EInteger.FromInt32(3), out var a) && !VanishesIdentically(a)
29365+
&& monomials.Values.All(coefficient => !coefficient.ContainsNode(x))
29366+
&& monomials.Values.Any(coefficient => coefficient.InnerSimplified.Evaled is not Number.Complex))
29367+
{
29368+
Entity Coefficient(int k) => monomials.TryGetValue(EInteger.FromInt32(k), out var value) ? value : Number.Integer.Zero;
29369+
var (b, c, d) = (Coefficient(2), Coefficient(1), Coefficient(0));
29370+
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);
29371+
if (!VanishesIdentically(discriminant))
29372+
return null;
29373+
var depressed = (MathS.Sqr(b) - 3 * a * c).InnerSimplified;
29374+
Entity? over = null;
29375+
// The roots simplified first, which folds the quotients of the coefficients to
29376+
// short ones -- `sqrt(b)` rather than a product of six numbers and it -- where
29377+
// the simplified form still expands back to the cubic, and as they are where not.
29378+
foreach (var simplify in new[] { true, false })
29379+
{
29380+
// Without the condition the simplification adds for b^2 - 3ac, which this
29381+
// case already assumes: the generic case is the one answered.
29382+
Entity Tidy(Entity e) => simplify ? e.Simplify() is Providedf(var value, _) ? value : e.Simplify() : e.InnerSimplified;
29383+
Entity candidate;
29384+
if (VanishesIdentically(depressed))
29385+
candidate = a * MathS.Pow(x + Tidy(b / (3 * a)), 3);
29386+
else
29387+
{
29388+
var r = Tidy((9 * a * d - b * c) / (2 * depressed));
29389+
var t = Tidy((4 * a * b * c - 9 * MathS.Sqr(a) * d - MathS.Pow(b, 3)) / (a * depressed));
29390+
candidate = a * MathS.Sqr(x - r) * (x - t);
29391+
}
29392+
if (VanishesIdentically((candidate - factor).Expand()))
29393+
{
29394+
over = candidate;
29395+
break;
29396+
}
29397+
}
29398+
if (over is null)
29399+
return null;
29400+
written = over;
29401+
continue;
29402+
}
29403+
rest *= factor;
29404+
}
29405+
if (written is null)
29406+
return null;
29407+
return Integration.ComputeAsTheSameQuestion(above / (rest * written), x, integrateByParts);
29408+
}
29409+
2933729410
/// <summary>
2933829411
/// A quotient whose denominator is a sum every term of which carries the same power of one
2933929412
/// linear form in <paramref name="x"/>, with that power taken out below the bar:

‎Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1223,6 +1223,11 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
12231223
if (expr is Entity.Sumf or Entity.Minusf
12241224
&& (answer = IndefiniteIntegralSolver.SolveByGatheringACommonPolynomialFactorOfASum(expr, x, integrateByParts)) is { })
12251225
return answer;
1226+
// A cubic below the bar with symbolic coefficients and a repeated root, written over
1227+
// its roots: `1/(x^3 - 3b x + 2b^(3/2))` is `1/((x - sqrt(b))^2 (x + 2 sqrt(b)))`.
1228+
if (expr is Entity.Divf or Entity.Powf
1229+
&& (answer = IndefiniteIntegralSolver.SolveACubicWithARepeatedRootOverItsRoots(expr, x, integrateByParts)) is { })
1230+
return answer;
12261231
// The same factor taken out of a sum below the bar: `1/(a x + b x/ln(c x^n))` is
12271232
// `1/(x (a + b/ln(c x^n)))`, a function of the logarithm beside its differential.
12281233
if (expr is Entity.Divf or Entity.Powf
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//
2+
// Copyright (c) 2019-2026 Angouri.
3+
// AngouriMath is licensed under MIT.
4+
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
5+
// Website: https://am.angouri.org.
6+
//
7+
8+
using System;
9+
using AngouriMath.Extensions;
10+
using Xunit;
11+
12+
namespace AngouriMath.Tests.Calculus
13+
{
14+
/// <summary>
15+
/// A cubic with symbolic coefficients and a repeated root below the bar, written over its
16+
/// roots: Rubi's 1.3.1 <c>1/(9x^3 - 9bx + 2 sqrt(3) b^(3/2))</c>, which was left unevaluated.
17+
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
18+
/// </summary>
19+
[Trait("Area", "Calculus")]
20+
public sealed class ACubicWithARepeatedRootIntegralTest
21+
{
22+
private static void DifferentiatesBack(string integrand, params (string, double)[] pins)
23+
{
24+
var integral = integrand.ToEntity().Integrate("x");
25+
Assert.DoesNotContain("integral(", integral.Stringize());
26+
27+
var pinned = integral.Substitute("C", 0);
28+
Entity original = integrand.ToEntity();
29+
foreach (var (name, value) in pins)
30+
{
31+
pinned = pinned.Substitute(name, value);
32+
original = original.Substitute(name, value);
33+
}
34+
var derivative = pinned.Differentiate("x");
35+
// Away from the roots, at b > 0, where the coefficients are real.
36+
foreach (var at in new[] { -3.1, -0.4, 0.3, 2.7 })
37+
{
38+
var got = derivative.Substitute("x", at).EvalNumerical();
39+
var want = original.Substitute("x", at).EvalNumerical();
40+
var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart);
41+
var scale = Math.Max(1.0, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart));
42+
Assert.True(difference / scale < 1e-8,
43+
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
44+
}
45+
}
46+
47+
[Theory]
48+
[InlineData("1/(-9*b*x+9*x^3+2*b^(3/2)*sqrt(3))")]
49+
[InlineData("x/(x^3-3*b*x+2*b^(3/2))")]
50+
[InlineData("1/(x^3+3*k*x^2+3*k^2*x+k^3)")]
51+
public void IsAnsweredAndDifferentiatesBack(string integrand)
52+
{
53+
DifferentiatesBack(integrand, ("b", 1.3), ("k", 0.7));
54+
DifferentiatesBack(integrand, ("b", 2.2), ("k", -1.6));
55+
}
56+
}
57+
}

0 commit comments

Comments
 (0)