diff --git a/Sources/AngouriMath/Functions/Simplification/Simplificator.cs b/Sources/AngouriMath/Functions/Simplification/Simplificator.cs index a8898d766..36cf0b2b1 100644 --- a/Sources/AngouriMath/Functions/Simplification/Simplificator.cs +++ b/Sources/AngouriMath/Functions/Simplification/Simplificator.cs @@ -206,6 +206,26 @@ not solve this issue completely and yet too slow to be accepted. { AddHistory(res.Expand().Simplify(-level)); AddHistory(res.Factorize().Simplify(-level)); + + // A multiple angle written out is worth having only where the pieces then + // cancel, so it has to be simplified in full before the metric can be + // asked -- the same reason Expand and Factorize are re-simplified above, + // and for the same reason it is a candidate rather than a step. The pass + // inside the loop offers the opened form to the trigonometric rules only, + // which settles an expression that is already one term; it cannot settle + // one whose cancellation needs a common denominator, because the passes + // that build one ran earlier in the loop, while the angles were still shut. + // That is https://github.com/asc-community/AngouriMath/issues/557: the + // reporter's second expression is 0, and reaches 0 through + // 2 sin(t) cos(t) and not through sin(2t). + var openedAngles = res + .Replace(Patterns.ExpandMultipleAngleRules) + .Replace(Patterns.NormalTrigonometricForm) + .InnerSimplified; + if (openedAngles != res) + // Expanded, and for the same reason res is expanded above: the + // cancellation only shows up once the products are multiplied out. + AddHistory(openedAngles.Expand().Simplify(-level)); } return history.Values.SelectMany(x => x); diff --git a/Sources/Tests/UnitTests/PatternsTest/DoubleAngleOverSineTest.cs b/Sources/Tests/UnitTests/PatternsTest/DoubleAngleOverSineTest.cs index 14823395e..a2e0079a0 100644 --- a/Sources/Tests/UnitTests/PatternsTest/DoubleAngleOverSineTest.cs +++ b/Sources/Tests/UnitTests/PatternsTest/DoubleAngleOverSineTest.cs @@ -37,6 +37,34 @@ private static Entity Bare(string expression) public void TheDoubleAngleOverTheSingleOne(string expression, string expected) => Assert.Equal(expected.ToEntity().Simplify(), Bare(expression)); + /// + /// The second expression the reporter gives, which is the first one with a + /// quotient of sixth and eighth powers of sin(t) added in front of it. Both are 0. + /// + /// This one needs the opened angle to survive as far as the fractions: the quotient + /// only cancels once its terms are over a common denominator, and the passes that + /// build one run before the angles are opened. So the opened form was offered to the + /// complexity metric in the one shape where its payoff had not happened yet, and was + /// rightly rejected as the longer of the two. It is now simplified in full first -- + /// including expanded, since the cancellation only shows up once the products are + /// multiplied out -- which is what Expand and Factorize already get. + /// + [Fact] + public void TheReportersSecondExpressionIsAlsoZero() => + Assert.Equal(Entity.Number.Integer.Create(0), Bare( + "(cos(2 * t) * sin(t) ^ 6 * (-1) + cos(t) * sin(t) ^ 5 * sin(2 * t)" + + " - sin(2 * t) ^ 2 * sin(t) ^ 4 / 4) / sin(t) ^ 8 - 1" + + " + (sin(2 * t) * cosec(t)) ^ 2 / 4 - cos(2 * t) - sin(t) ^ 2")); + + // The pieces it is built from, each 0 or 1 on its own. Together they say the + // cancellation is the quotient's and not only the tail's. + [Theory] + [InlineData("(cos(2 * t) * sin(t) ^ 6 * (-1) + cos(t) * sin(t) ^ 5 * sin(2 * t)" + + " - sin(2 * t) ^ 2 * sin(t) ^ 4 / 4) / sin(t) ^ 8", "1")] + [InlineData("(sin(t) ^ 6 - sin(2 * t) ^ 2 * sin(t) ^ 4 / 4) / sin(t) ^ 8 - 1", "0")] + public void TheQuotientCancelsOnItsOwn(string expression, string expected) => + Assert.Equal(expected.ToEntity(), Bare(expression)); + /// /// 2cos(u) is a number where sin(u) is zero and sin(2u) csc(u) is not, so the /// cosecant's own condition has to be carried rather than dropped. It is the same