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
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,25 @@ internal static partial class Patterns
Sumf(Powf(Cosf(var any1), Integer(2)),
Powf(Sinf(var any1a), Integer(2))) when any1 == any1a => 1,

// The same identity solved for one square rather than for 1. Only this direction:
// the two sides are interchangeable, and rewriting cos(:)^2 back as 1 - sin(:)^2
// would undo this as fast as it fired.
Minusf(Integer(1), Powf(Sinf(var any1), Integer(2))) => new Powf(new Cosf(any1), 2),
Minusf(Integer(1), Powf(Cosf(var any1), Integer(2))) => new Powf(new Sinf(any1), 2),

// The identity divided through by sin(:)^2 and by cos(:)^2. Knowing the one above
// and not these made the answer depend on which of the three ways an expression
// happened to be written -- https://github.com/asc-community/AngouriMath/issues/725.
Sumf(Integer(1), Powf(Tanf(var any1), Integer(2))) => new Powf(new Secantf(any1), 2),
Sumf(Powf(Tanf(var any1), Integer(2)), Integer(1)) => new Powf(new Secantf(any1), 2),
Sumf(Integer(1), Powf(Cotanf(var any1), Integer(2))) => new Powf(new Cosecantf(any1), 2),
Sumf(Powf(Cotanf(var any1), Integer(2)), Integer(1)) => new Powf(new Cosecantf(any1), 2),

Minusf(Powf(Secantf(var any1), Integer(2)),
Powf(Tanf(var any1a), Integer(2))) when any1 == any1a => 1,
Minusf(Powf(Cosecantf(var any1), Integer(2)),
Powf(Cotanf(var any1a), Integer(2))) when any1 == any1a => 1,

Minusf(Powf(Sinf(var any1), Integer(2)), Powf(Cosf(var any1a), Integer(2))) when any1 == any1a =>
-1 * (new Powf(new Cosf(any1), 2) - new Powf(new Sinf(any1), 2)),
Minusf(Powf(Cosf(var any1), Integer(2)), Powf(Sinf(var any1a), Integer(2))) when any1 == any1a =>
Expand Down
120 changes: 120 additions & 0 deletions Sources/Tests/UnitTests/Common/PythagoreanIdentityTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
//
// Copyright (c) 2019-2022 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;

namespace AngouriMath.Tests.Common
{
/// <summary>
/// The Pythagorean identity was one syntactic pattern -- an adjacent sum of the two squares,
/// in either order -- so every other arrangement of the same fact was missed, including the
/// two forms got by dividing it through by sin^2 and by cos^2.
/// https://github.com/asc-community/AngouriMath/issues/725
/// </summary>
public sealed class PythagoreanIdentityTest
{
/// <summary>
/// The value is what is asserted, not the condition attached to it. A tangent and a
/// secant are undefined where the cosine vanishes, so `sec(t)^2 - tan(t)^2` answering
/// `1 provided not cos(t) = 0` is a better answer than a bare 1 rather than a weaker
/// one -- the bare 1 would claim a value at a point the expression does not reach.
/// </summary>
private static void AssertSimplifies(string input, string expected) =>
Assert.Equal(WithoutCondition(expected.ToEntity().Simplify()),
WithoutCondition(input.ToEntity().Simplify()));

private static Entity WithoutCondition(Entity expr)
{
while (expr is Entity.Providedf(var inner, _)) expr = inner;
return expr;
}

/// <summary>The arrangement that already worked, which must keep working.</summary>
[Theory]
[InlineData("sin(t) ^ 2 + cos(t) ^ 2", "1")]
[InlineData("cos(t) ^ 2 + sin(t) ^ 2", "1")]
[InlineData("a + sin(t) ^ 2 + cos(t) ^ 2", "1 + a")]
[InlineData("3 * sin(t) ^ 2 + 3 * cos(t) ^ 2", "3")]
public void TheSumOfTheSquaresIsUnaffected(string input, string expected) =>
AssertSimplifies(input, expected);

/// <summary>The same identity solved for one square rather than for 1.</summary>
[Theory]
[InlineData("1 - sin(t) ^ 2", "cos(t) ^ 2")]
[InlineData("1 - cos(t) ^ 2", "sin(t) ^ 2")]
[InlineData("1 - sin(t) ^ 2 + a", "cos(t) ^ 2 + a")]
[InlineData("1 - sin(t) ^ 2 - cos(t) ^ 2", "0")]
[InlineData("1 - cos(t) ^ 2 - sin(t) ^ 2", "0")]
public void SolvedForOneSquare(string input, string expected) =>
AssertSimplifies(input, expected);

/// <summary>
/// The identity divided through by sin^2 and by cos^2. Neither was known in any form:
/// not the sum, not the difference, and not with the reciprocal written out as a
/// quotient rather than as a cosecant or a secant.
/// </summary>
[Theory]
[InlineData("1 + tan(t) ^ 2", "sec(t) ^ 2")]
[InlineData("1 + cotan(t) ^ 2", "csc(t) ^ 2")]
[InlineData("sec(t) ^ 2 - tan(t) ^ 2", "1")]
[InlineData("csc(t) ^ 2 - cotan(t) ^ 2", "1")]
public void DividedThroughBySquareOfSineOrCosine(string input, string expected) =>
AssertSimplifies(input, expected);

/// <summary>
/// The reporter's first expression in
/// https://github.com/asc-community/AngouriMath/issues/557. The second is not here; see
/// <see cref="TheThirdTermOfTheIdentityHasToBeAdjacent"/> for what it still wants.
/// </summary>
[Fact]
public void TheFirstComplexTrigonometricStatementOf557() =>
AssertSimplifies("(sin(2 * t) * csc(t)) ^ 2 / 4 - cos(2 * t) - sin(t) ^ 2", "0");

/// <summary>
/// What the rules above do not reach, recorded rather than claimed. Every rule here
/// matches a pattern, so the two parts of the identity have to be *adjacent* in the
/// tree for one to fire. Written on its own each of these reduces --
/// <c>1 + cotan(t)^2</c> is <c>csc(t)^2</c> and <c>csc(t)^2 - 1/sin(t)^2</c> is 0 --
/// but in a sum of three terms the sorting separates the pair before the rules are
/// tried, and nothing puts it back.
/// <para/>
/// Reaching these wants the identity stated over the *gathered* terms of a sum rather
/// than over an adjacent pair, which is a larger change than this one and is left for
/// its own. The same gap is what still stands between
/// https://github.com/asc-community/AngouriMath/issues/557's second expression and 0.
/// Written in sines and cosines throughout, all four already reduce, so nothing here
/// is a wrong answer -- only an unreduced one.
/// </summary>
[Theory(Skip = "Wants the identity over the gathered terms of a sum, not an adjacent pair")]
[InlineData("1 + tan(t) ^ 2 - 1 / cos(t) ^ 2", "0")]
[InlineData("1 + cotan(t) ^ 2 - 1 / sin(t) ^ 2", "0")]
[InlineData("1 / sin(t) ^ 2 - (1 + cotan(t) ^ 2)", "0")]
[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"
+ " + (sin(2 * t) * csc(t)) ^ 2 / 4 - cos(2 * t) - sin(t) ^ 2", "0")]
public void TheThirdTermOfTheIdentityHasToBeAdjacent(string input, string expected) =>
AssertSimplifies(input, expected);

/// <summary>
/// The same statements written in sines and cosines, which do reduce. That is what makes
/// the gap above a matter of spelling rather than of the mathematics being out of reach.
/// <para/>
/// The cosine half of the first pair is not among them: `1 + sin(t)^2/cos(t)^2 -
/// 1/cos(t)^2` collapses its quotient back to a tangent and stops at
/// `1 - 1/cos(t)^2 + tan(t)^2`, where the sine half reduces. That asymmetry is the same
/// adjacency gap seen from the other side and is recorded, not fixed, here.
/// </summary>
[Theory]
[InlineData("1 + cos(t) ^ 2 / sin(t) ^ 2 - 1 / sin(t) ^ 2", "0")]
[InlineData("1 / sin(t) ^ 2 - (1 + cos(t) ^ 2 / sin(t) ^ 2)", "0")]
[InlineData("(1 - sin(t) ^ 2 - cos(t) ^ 2) / sin(t) ^ 2", "0")]
[InlineData("(1 - sin(t) ^ 2 - cos(t) ^ 2) / cos(t) ^ 2", "0")]
public void TheSameStatementsInSinesAndCosines(string input, string expected) =>
AssertSimplifies(input, expected);
}
}
20 changes: 9 additions & 11 deletions Sources/Tests/UnitTests/PatternsTest/MultipleAngleTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,18 @@ public sealed class MultipleAngleTest
[InlineData("cos(2 * x) - (1 - 2 * sin(x) ^ 2)")]
[InlineData("cos(2 * x) - (cos(x) ^ 2 - sin(x) ^ 2)")]
[InlineData("sin(4 * x) - 2 * sin(2 * x) * cos(2 * x)")]
// Was pinned separately as PythagoreanPairAcrossASubtractionIsStillMissed, which
// recorded that this form stops at 1 - cos(x)^2 - sin(x)^2 because sin^2 + cos^2 = 1
// was matched only as an adjacent *sum* and here the pair sits either side of a
// subtraction. The identity solved for one square rather than for 1 --
// 1 - cos(:)^2 = sin(:)^2 -- closes that arrangement, so it belongs here now:
// https://github.com/asc-community/AngouriMath/issues/725. Nothing about opening the
// angle changed, and the note that this was not something the angle expansion could
// reach was correct.
[InlineData("cos(2 * x) - (2 * cos(x) ^ 2 - 1)")]
public void IdentitiesReduceToZero(string input) =>
Assert.Equal(Entity.Number.Integer.Create(0), input.ToEntity().Simplify());

// The same identity written as cos(2x) - (2cos(x)^2 - 1) gets as far as
// 1 - cos(x)^2 - sin(x)^2 and stops: sin^2 + cos^2 = 1 is matched as a pair, and
// there the pair sits either side of a subtraction. That is the same
// pairwise-versus-flattened gap as
// https://github.com/asc-community/AngouriMath/issues/531, in a product of trigonometric terms
// rather than a sum, and is not something opening the angle can reach.
[Fact]
public void PythagoreanPairAcrossASubtractionIsStillMissed() =>
Assert.Equal("1 - cos(x) ^ 2 - sin(x) ^ 2".ToEntity(),
"cos(2 * x) - (2 * cos(x) ^ 2 - 1)".ToEntity().Simplify());

[Fact]
public void DoubleAngleAndSquareCombine() =>
Assert.Equal("cos(x) ^ 2".ToEntity(), "cos(2 * x) + sin(x) ^ 2".ToEntity().Simplify());
Expand Down
Loading