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 @@ -226,6 +226,12 @@ protected override Entity InnerSimplify(bool isExact) =>
Complex n when !isExact => Number.Arctan(n),
Real r when r.EDecimal.IsPositiveInfinity() => MathS.pi / 2,
Real r when r.EDecimal.IsNegativeInfinity() => -MathS.pi / 2,
// The two finite angles arctan names outright, kept here beside the
// infinite ones rather than offered to the simplifier as candidates:
// there it lost to arctan(1) on node count, so a sum that came to
// pi/4 stopped one step short of it.
Integer(1) => MathS.pi / 4,
Integer(-1) => -MathS.pi / 4,
_ => null
},
(@this, a) => ((Arctanf)@this).New(a), isExact);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@ internal static partial class Patterns
Sumf(Arctanf(var any1), Arccotanf(var any1a)) when any1 == any1a => MathS.pi / 2,
Sumf(Arccotanf(var any1), Arctanf(var any1a)) when any1 == any1a => MathS.pi / 2,

// arctan(a) + arctan(b) = arctan((a + b)/(1 - ab)), which is the tangent
// addition formula read backwards. It holds as written only while ab < 1: past
// that the sum leaves the range arctan answers in and the identity is off by a
// whole pi. Both arguments have to be numbers, so that ab < 1 is a question
// with an answer -- arctan(1/2) + arctan(1/3) is pi/4, while
// arctan(2) + arctan(3) is 3pi/4 and is left alone.
Sumf(Arctanf(Real a), Arctanf(Real b)) when (a * b).Evaled is Real product && product < 1
=> MathS.Arctan(((a + b) / (1 - a * b)).InnerSimplified),

// The two angles whose tangent is an irrational this can recognise. The ones
// at +-1 are handled where arctan is simplified, since a candidate offered
// here has to win on node count and pi/4 does not beat arctan(1).
Arctanf(Powf(Integer(3), Rational(Integer(1), Integer(2)))) => MathS.pi / 3,
Arctanf(Divf(Integer(1), Powf(Integer(3), Rational(Integer(1), Integer(2))))) => MathS.pi / 6,

// tan * cot = 1
Mulf(Tanf(var any1), Cotanf(var any1a)) when any1 == any1a => 1,
Mulf(Cotanf(var any1), Tanf(var any1a)) when any1 == any1a => 1,
Expand Down
80 changes: 80 additions & 0 deletions Sources/Tests/UnitTests/Common/ArctanIdentitiesTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//
// 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 System;
using AngouriMath;
using AngouriMath.Extensions;
using Xunit;

namespace AngouriMath.Tests.Common
{
/// <summary>
/// arctan(1/2) + arctan(1/3) is pi/4, and neither half of that was reachable:
/// the two could not be added, and arctan(1) stayed as it was. No issue is filed for
/// this; it comes from the gaps a solver corpus showed.
/// </summary>
public sealed class ArctanIdentitiesTest
{
private static Entity Simplified(string expression) => expression.ToEntity().Simplify();

[Theory]
[InlineData("arctan(1/2) + arctan(1/3)", "pi / 4")]
[InlineData("arctan(1/3) + arctan(1/2)", "pi / 4")]
[InlineData("arctan(1/5) + arctan(1/8)", "arctan(1/3)")]
[InlineData("arctan(1/2) + arctan(1/5)", "arctan(7/9)")]
public void TwoArctangentsAddUp(string expression, string expected) =>
Assert.Equal(expected.ToEntity().Simplify(), Simplified(expression));

[Theory]
[InlineData("arctan(1)", "pi / 4")]
[InlineData("arctan(-1)", "-pi / 4")]
[InlineData("arctan(0)", "0")]
[InlineData("arctan(sqrt(3))", "pi / 3")]
[InlineData("arctan(1/sqrt(3))", "pi / 6")]
public void TheAnglesArctangentNamesOutright(string expression, string expected) =>
Assert.Equal(expected.ToEntity().Simplify(), Simplified(expression));

/// <summary>
/// The addition formula holds as written only while the product of the two
/// arguments is below one. Past that the sum leaves the range arctan answers in and
/// the identity is off by a whole pi, so those are left alone rather than answered
/// wrongly: arctan(2) + arctan(3) is 3pi/4, not arctan(-1).
/// </summary>
[Theory]
[InlineData("arctan(2) + arctan(3)")]
[InlineData("arctan(1) + arctan(2)")]
[InlineData("arctan(x) + arctan(y)")]
public void WhereTheFormulaDoesNotHoldNothingIsClaimed(string expression) =>
Assert.Contains("arctan", Simplified(expression).Stringize());

/// <summary>
/// Whatever a sum of two arctangents simplifies to has to be the same number. The
/// pairs run either side of the boundary the formula is guarded by.
/// </summary>
[Fact]
public void ASimplifiedSumIsTheSameNumber()
{
double[] values = { -5, -3, -2, -1.5, -1, -0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75, 1, 1.5, 2, 3, 5 };
foreach (var a in values)
foreach (var b in values)
{
var simplified = Simplified($"arctan({a}) + arctan({b})");
var actual = simplified.EvalNumerical().RealPart.EDecimal.ToDouble();
Assert.Equal(Math.Atan(a) + Math.Atan(b), actual, 9);
}
}

// The identities next to this one have to keep working.
[Theory]
[InlineData("arcsin(x) + arccos(x)", "pi / 2")]
[InlineData("arctan(x) + arccotan(x)", "pi / 2")]
[InlineData("tan(arctan(x))", "x")]
[InlineData("arctan(tan(1/2))", "1/2")]
public void NeighbouringIdentitiesAreUnaffected(string expression, string expected) =>
Assert.Equal(expected.ToEntity().Simplify(), Simplified(expression));
}
}
Loading