From 439f308adf86508b60040db05f9f1ae491a68149 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sat, 31 Oct 2026 23:16:39 +0000 Subject: [PATCH] A sine or cosine with no real radical form stays as written Simplify wrote sin(pi/7) as the half angle of the table's cos(2pi/7), sqrt((1 - 1/6 * (-1 + ((7 + 21 sqrt(-3))/2)^(1/3) + ((7 - 21 sqrt(-3))/2)^(1/3))) / 2), many times longer than the input. That is casus irreducibilis: the value is real and no real radical form of it exists, so the only one goes through the complex numbers. cos(2pi/9) and sin(pi/18) were written the same way. A table value written through a number that is not real, i or a square root of a negative number, is now passed over, and the call is left as written. Every angle in the tables is real, so such a value is exactly one of these. The entries stay in the tables, so the closed forms stay on record. A value with a real radical form, sin(pi/5) among them, still takes it. TrigonometricTableTest's sweep counted 690 angles resolved, against a floor meant to catch entries lost silently; these are lost on purpose and 642 resolve, so the floor is 642. CasusIrreducibilisTest takes seven such values, six of which master rewrote, three that still take their radicals, and sin(pi/7)^2 + cos(pi/7)^2. The suite, the trio, F#, AOT and the eight harnesses pass. Fixes #2143. Co-Authored-By: Claude Opus 5.5 (1M context) --- BREAKING-CHANGES.md | 15 ++++++ Sources/.editorconfig | 3 ++ .../TrigonometricTableValues.cs | 16 +++++- .../Common/TrigonometricTableTest.cs | 6 ++- .../PatternsTest/CasusIrreducibilisTest.cs | 54 +++++++++++++++++++ 5 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 Sources/Tests/UnitTests/PatternsTest/CasusIrreducibilisTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index c83cb36be..bbf8384e1 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -371,6 +371,21 @@ is the canonical one: square-free, and without what the answer's own denominator | `"(a - 1)/(1 - b) - (1 - a)/(b - 1)".Simplify()` | `0 provided not (b - 1) * (1 - b) = 0` | `0 provided not b - 1 = 0` | | `"(x*y*a*b*c)/(c*b*a*x*x)".Simplify()` | `y / x provided not a * b * c * x = 0` | `y / x provided not a * b * c = 0` | +### A sine or cosine with no real radical form stays as written + +**Silent: `sin(pi/7)`, `cos(2pi/9)` and the other values whose only radical form goes through the complex numbers +simplify to themselves, where they were nested radicals with cube roots of complex numbers.** That is casus +irreducibilis: no real radical form exists, and the radical was many times longer than the input. The table's +entries for the sevenths, ninths and eighteenths are passed over, and their half angles with them. A value with a +real radical form still takes it ([#2143](https://github.com/asc-community/AngouriMath/issues/2143)). + +| Input | Was (2.6.0) | Now | +|---|---|---| +| `"sin(pi/7)".ToEntity().Simplify()` | `sqrt((1 - 1/6 * (-1 + ((7 + 21 * sqrt(-3)) / 2) ^ (1/3) + ((7 - 21 * sqrt(-3)) / 2) ^ (1/3))) / 2)` | `sin(pi / 7)` | +| `"cos(2 pi/9)".ToEntity().Simplify()` | `(((sqrt(-3) - 1) / 2) ^ (1/3) + (-(sqrt(-3) + 1) / 2) ^ (1/3)) / 2` | `cos(2/9 * pi)` | +| `"sin(pi/18)".ToEntity().Simplify()` | `sqrt((1 - 1/4 * ((4 + 4 * sqrt(-3)) ^ (1/3) + (4 - 4 * sqrt(-3)) ^ (1/3))) / 2)` | `sin(pi / 18)` | +| `"sin(pi/5)".ToEntity().Simplify()` | `1/4 * sqrt(10 - 2 * sqrt(5))` | `1/4 * sqrt(10 - 2 * sqrt(5))` | + ### A step function at a jump takes the side its argument arrives from **Silent: a limit of `floor`, `ceil` or `sgn` whose argument arrives on a jump is answered, where it was left diff --git a/Sources/.editorconfig b/Sources/.editorconfig index ddd6649cc..062284068 100644 --- a/Sources/.editorconfig +++ b/Sources/.editorconfig @@ -238,6 +238,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed [Tests/UnitTests/Convenience/ArctangentPolesTest.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/PatternsTest/CasusIrreducibilisTest.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/ModulusEquationTest.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/Simplification/TrigonometricTableValues.cs b/Sources/AngouriMath/Functions/Simplification/TrigonometricTableValues.cs index 8b78be55b..492e0e93c 100644 --- a/Sources/AngouriMath/Functions/Simplification/TrigonometricTableValues.cs +++ b/Sources/AngouriMath/Functions/Simplification/TrigonometricTableValues.cs @@ -52,7 +52,7 @@ private static bool TryPulling(TrigTable table, Complex arg, for (var j = begin; j <= end; j++) { - if (Number.IsZero(table[j].arg - dArg)) + if (Number.IsZero(table[j].arg - dArg) && !IsWrittenThroughTheComplexNumbers(table[j].res)) { res = table[j].res; return true; @@ -62,6 +62,20 @@ private static bool TryPulling(TrigTable table, Complex arg, return false; } + /// + /// Whether a value is written through numbers that are not real: i, or a square root + /// of a negative number. Every angle here is real, so such a value is a real number whose + /// radical form needs the complex numbers, casus irreducibilis, and the function of its angle + /// is the shorter answer: cos(2pi/7) is + /// (-1 + cbrt((7 + 21 sqrt(-3))/2) + cbrt((7 - 21 sqrt(-3))/2))/6, and no real + /// radical form exists. sin(pi/7) simplified to the half-angle of it, many times + /// longer than the input. Such an entry is passed over rather than removed, so the closed form + /// stays on record. https://github.com/asc-community/AngouriMath/issues/2143 + /// + private static bool IsWrittenThroughTheComplexNumbers(Entity value) + => value.Nodes.Any(node => node is Complex and not Real + || node is Powf(Real { IsNegative: true }, Rational { ERational.Denominator.IsEven: true })); + /// /// Every angle in the tables below is written 2pi/n, so they name nothing at /// all between 2pi/5 and 2pi/3 -- the whole lower half of the circle diff --git a/Sources/Tests/UnitTests/Common/TrigonometricTableTest.cs b/Sources/Tests/UnitTests/Common/TrigonometricTableTest.cs index f71af9e82..d18da10aa 100644 --- a/Sources/Tests/UnitTests/Common/TrigonometricTableTest.cs +++ b/Sources/Tests/UnitTests/Common/TrigonometricTableTest.cs @@ -91,8 +91,10 @@ public void EveryExactValueAgreesWithTheCallItReplaces() // the 1158 calls swept: 581 of them resolved before the half turn was applied // and 690 after, and the 581 were already sound -- what was missing was missing, // not wrong. Raise this if the table gains entries; it is here to catch losing - // them silently. - Assert.True(resolved >= 690, $"only {resolved} of the swept angles resolved at all"); + // them silently. 642 since the entries written through the complex numbers, the + // sevenths, ninths and eighteenths and the half angles of them, are passed over and + // their calls left as written: https://github.com/asc-community/AngouriMath/issues/2143 + Assert.True(resolved >= 642, $"only {resolved} of the swept angles resolved at all"); } /// diff --git a/Sources/Tests/UnitTests/PatternsTest/CasusIrreducibilisTest.cs b/Sources/Tests/UnitTests/PatternsTest/CasusIrreducibilisTest.cs new file mode 100644 index 000000000..b93a4c872 --- /dev/null +++ b/Sources/Tests/UnitTests/PatternsTest/CasusIrreducibilisTest.cs @@ -0,0 +1,54 @@ +// +// 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.Linq; +using AngouriMath; +using AngouriMath.Extensions; +using Xunit; +using static AngouriMath.Entity.Number; + +namespace AngouriMath.Tests.PatternsTest +{ + /// + /// A sine or cosine whose only radical form goes through the complex numbers, casus irreducibilis, stays as the + /// function of its angle. sin(pi/7) simplified to sqrt((1 - 1/6 * (-1 + ((7 + 21 * sqrt(-3)) / 2) ^ (1/3) + + /// ((7 - 21 * sqrt(-3)) / 2) ^ (1/3))) / 2), many times longer than the input, and no real radical form exists. + /// #2143 + /// + [Trait("Area", "PatternsTest")] + public sealed class CasusIrreducibilisTest + { + [Theory] + [InlineData("sin(pi/7)")] + [InlineData("cos(pi/7)")] + [InlineData("cos(2 pi/7)")] + [InlineData("sin(4 pi/7)")] + [InlineData("sin(pi/9)")] + [InlineData("cos(2 pi/9)")] + [InlineData("sin(pi/18)")] + public void ItStaysAsWritten(string value) + { + var simplified = value.ToEntity().Simplify(); + Assert.DoesNotContain(simplified.Nodes, node => node is Complex and not Real || node is Entity.Powf(_, Rational { ERational.Denominator.IsEven: false })); + Assert.True(simplified.Nodes.Count() <= value.ToEntity().Nodes.Count() + 2, simplified.Stringize()); + var difference = (simplified - value.ToEntity()).EvalNumerical(); + Assert.True(difference.Abs() < 1e-30, $"{simplified} differs from {value} by {difference}"); + } + + /// A value with a real radical form still takes it. + [Theory] + [InlineData("sin(pi/5)", "1/4 * sqrt(10 - 2 * sqrt(5))")] + [InlineData("sin(pi/3)", "sqrt(3) / 2")] + [InlineData("cos(2 pi/5)", "(sqrt(5) - 1) / 4")] + public void ARealRadicalIsStillTaken(string value, string expected) + => Assert.Equal(expected, value.ToEntity().Simplify().Stringize()); + + [Fact] + public void TheIdentitiesStillHold() + => Assert.Equal(Integer.One, "sin(pi/7)^2 + cos(pi/7)^2".ToEntity().Simplify()); + } +}