From c193858d3fc36796e586dd810338960835ca2460 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 1 Nov 2026 16:13:33 +0000 Subject: [PATCH] A cube shifted is solved SolveCubic takes Cardano's C as a cube root of (D1 + sqrt(D1^2 - 4 D0^3)) / 2, either root of C^6 - D1 C^3 + D0^3 = 0 doing as long as it is not 0, since the roots divide by C. A cube shifted, a(x + h)^3 = k written out, has D0 = b^2 - 3ac = 0, and then the square root is |D1|, so for a negative D1 the sum is 0 and C was 0. Every root was taken for -b/(3a), the branch for a triple root, none of them was a root, and the empty set that was left claimed there was none: (x + 1)^3 = 2 and 3x^3 + 9x^2 + 9x + 1 = 0 were answered { }. With D0 = 0, C is a cube root of D1 itself, and the roots are -(b + u C)/(3a), D0/C being 0. For a negative D1 it is written as the negative of the cube root of -D1, a cube root of D1 whichever root of a negative number a power is read as. Found by a cross-check of equations in logarithms, one of which exponentiates to 3x^3 + 9x^2 + 9x + 1. 2.6.0 answers them the same. Co-Authored-By: Claude Opus 5.5 (1M context) --- BREAKING-CHANGES.md | 12 +++++ Sources/.editorconfig | 3 ++ .../EquationSolver/PolynomialSolver.cs | 17 ++++++- .../Algebra/SolveTest/AShiftedCubeTest.cs | 48 +++++++++++++++++++ 4 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 Sources/Tests/UnitTests/Algebra/SolveTest/AShiftedCubeTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index e11efd135..c3b1d24d7 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -116,6 +116,18 @@ did, and `ln(x + 2) / ln(x + 3)` at 0 was `ln(2) / ln(3)`. With symbols it was n | `"limit(ln(a + 1/x) / ln(b + 1/x), x, +oo)".ToEntity().Simplify()` | `a / b` | `ln(a) / ln(b)` | | `"limit(ln(1 + x) / ln(y + x), x, 0)".ToEntity().Simplify()` | `1 / y` | `0 provided not ln(y) = 0` | +### A cube shifted is solved + +**Silent: a cubic `a(x + h)^3 = k` written out, with `b^2 = 3ac`, is solved where it was answered `{ }`.** Cardano's +`C^3` is `(D1 + sqrt(D1^2 - 4 D0^3)) / 2`, and with `D0 = 0` that is `(D1 + |D1|) / 2`, which is 0 for a negative `D1`. +Every root was then taken for `-b/(3a)`, which is none, and the empty set claimed there was no root. With `D0 = 0`, `C` is a +cube root of `D1` itself. + +| Input | Was (2.6.0) | Now | +|---|---|---| +| `"(x + 1)^3 = 2".ToEntity().Solve("x")` | `{ }` | `{ -(3 + -3 * 2 ^ (1/3)) / 3, … }`, the three cube roots of 2 less 1 | +| `"3x^3 + 9x^2 + 9x + 1 = 0".ToEntity().Solve("x")` | `{ }` | `{ -(9 + -3 * 18 ^ (1/3)) / 3 / 3, … }` | + ### A quadratic whose discriminant is a square is answered without the root **Silent: the same roots, written as what they are.** The quadratic formula wrote `sqrt(b^2 - 4ac)` whatever the diff --git a/Sources/.editorconfig b/Sources/.editorconfig index 8edc021c0..9b9d49302 100644 --- a/Sources/.editorconfig +++ b/Sources/.editorconfig @@ -96,6 +96,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed [Tests/UnitTests/Convenience/EmptySequenceFoldTest.cs] file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n +[Tests/UnitTests/Algebra/SolveTest/AShiftedCubeTest.cs] +file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n + [Tests/UnitTests/Convenience/FoldEnumerationTest.cs] file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n diff --git a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/PolynomialSolver.cs b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/PolynomialSolver.cs index 9f273c0d6..8aabcfe85 100644 --- a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/PolynomialSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/PolynomialSolver.cs @@ -239,10 +239,23 @@ internal static IEnumerable SolveCubic(Entity a, Entity b, Entity c, Ent var u3 = Rational.Create(-1, 2) - coeff; var D0 = MathS.Sqr(b) - 3 * a * c; var D1 = (2 * MathS.Pow(b, 3) - 9 * a * b * c + 27 * MathS.Sqr(a) * d).InnerSimplified; - var C = MathS.Pow((D1 + MathS.Sqrt(MathS.Sqr(D1) - 4 * MathS.Pow(D0, 3))) / 2, Rational.Create(1, 3)); + // C is a cube root of either root of C^6 - D1 C^3 + D0^3 = 0, and only one that is not 0 + // can be divided by. With D0 = 0 those roots are D1 and 0, and the square root of D1^2 + // is |D1|, so for a negative D1 the sum below is 0: a(x + h)^3 = k, a cube shifted, had + // C = 0 and every root taken for -b/(3a), which is none, and (x + 1)^3 = 2 was answered + // { }. With D0 = 0 a cube root of D1 itself is C, and the roots are -(b + u C)/(3a), + // D0/C being 0; that is -b/(3a) three times where D1 is 0 as well. For a negative D1 the + // cube root is written as the negative of the cube root of -D1, which is a cube root of + // D1 whichever root of a negative number a power is read as. + var shifted = D0.Evaled == 0; + var C = !shifted + ? MathS.Pow((D1 + MathS.Sqrt(MathS.Sqr(D1) - 4 * MathS.Pow(D0, 3))) / 2, Rational.Create(1, 3)) + : D1.Evaled is Real { IsNegative: true } + ? -MathS.Pow(-D1, Rational.Create(1, 3)) + : MathS.Pow(D1, Rational.Create(1, 3)); return new[] { u1, u2, u3 }.Select(uk => - C.Evaled == 0 && D0.Evaled == 0 ? -(b + uk * C) / 3 / a : -(b + uk * C + D0 / C / uk) / 3 / a); + shifted ? -(b + uk * C) / 3 / a : -(b + uk * C + D0 / C / uk) / 3 / a); } [ConstantField] private static readonly int[] sqrtsOf1 = { -1, 1 }; diff --git a/Sources/Tests/UnitTests/Algebra/SolveTest/AShiftedCubeTest.cs b/Sources/Tests/UnitTests/Algebra/SolveTest/AShiftedCubeTest.cs new file mode 100644 index 000000000..265dabc4a --- /dev/null +++ b/Sources/Tests/UnitTests/Algebra/SolveTest/AShiftedCubeTest.cs @@ -0,0 +1,48 @@ +// +// 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 AngouriMath.Extensions; +using Xunit; +using static AngouriMath.Entity; +using static AngouriMath.Entity.Set; + +namespace AngouriMath.Tests.Algebra +{ + /// + /// A cube shifted, a(x + h)^3 = k, has D0 = b^2 - 3ac = 0, and Cardano's + /// C^3 = (D1 + sqrt(D1^2 - 4 D0^3)) / 2 is then (D1 + |D1|) / 2, which is 0 for a + /// negative D1. Every root was taken for -b/(3a), none was one, and + /// (x + 1)^3 = 2 was answered { }. Found by a cross-check of equations in + /// logarithms, one of which exponentiates to 3x^3 + 9x^2 + 9x + 1; no issue was filed. + /// + [Trait("Area", "Algebra")] + public sealed class AShiftedCubeTest + { + [Theory] + [InlineData("(x + 1)^3 = 2", "-1", "2")] + [InlineData("x^3 + 3x^2 + 3x - 1 = 0", "-1", "2")] + [InlineData("3x^3 + 9x^2 + 9x + 1 = 0", "-1", "2/3")] + [InlineData("x^3 - 3x^2 + 3x - 3 = 0", "1", "2")] + // D1 positive, which was answered: (x + 1)^3 = -2. + [InlineData("x^3 + 3x^2 + 3x + 3 = 0", "-1", "-2")] + public void ItsRootsAreTheCubeRootsShifted(string equation, string shift, string cube) + { + var answer = Assert.IsType(equation.ToEntity().Solve("x")); + Assert.Equal(3, answer.Count); + foreach (var unity in new[] { "1", "-1/2 + sqrt(3) / 2 * i", "-1/2 - sqrt(3) / 2 * i" }) + { + // The real cube root of the cube, times each cube root of unity, shifted. + var root = $"({shift}) + ({unity}) * sgn({cube}) * abs({cube})^(1/3)".ToEntity(); + Assert.Contains(answer, found => Number.IsZero((found - root).EvalNumerical())); + } + } + + [Fact] + public void ATripleRootIsStillOne() + => Assert.Equal("{ -1 }".ToEntity(), "x^3 + 3x^2 + 3x + 1 = 0".ToEntity().Solve("x")); + } +}