Skip to content

Commit

Permalink
#662 - The simplification of trigonometric and inverse trigonometric …
Browse files Browse the repository at this point in the history
…functions is not correct.
  • Loading branch information
sys27 committed Aug 2, 2023
1 parent 1fae7e7 commit 9846d6d
Show file tree
Hide file tree
Showing 12 changed files with 423 additions and 16 deletions.
74 changes: 63 additions & 11 deletions xFunc.Maths/Analyzers/Simplifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ private bool IsChanged(UnaryExpression old, IExpression argument)
private bool IsChanged(BinaryExpression old, IExpression left, IExpression right)
=> old.Left != left || old.Right != right;

private IExpression AnalyzeUnaryArgument([NotNull] UnaryExpression exp)
private IExpression AnalyzeUnaryArgument(UnaryExpression exp)
{
if (exp is null)
ArgNull(ExceptionArgument.exp);

return exp.Argument.Analyze(this);
}

private IExpression AnalyzeUnary([NotNull] UnaryExpression exp)
private IExpression AnalyzeUnary(UnaryExpression exp)
{
if (exp is null)
ArgNull(ExceptionArgument.exp);
Expand All @@ -47,7 +47,7 @@ private IExpression AnalyzeUnary([NotNull] UnaryExpression exp)
return exp;
}

private (IExpression Left, IExpression Right) AnalyzeBinaryArgument([NotNull] BinaryExpression exp)
private (IExpression Left, IExpression Right) AnalyzeBinaryArgument(BinaryExpression exp)
{
if (exp is null)
ArgNull(ExceptionArgument.exp);
Expand All @@ -58,7 +58,7 @@ private IExpression AnalyzeUnary([NotNull] UnaryExpression exp)
return (left, right);
}

private IExpression AnalyzeBinary([NotNull] BinaryExpression exp)
private IExpression AnalyzeBinary(BinaryExpression exp)
{
if (exp is null)
ArgNull(ExceptionArgument.exp);
Expand All @@ -71,7 +71,7 @@ private IExpression AnalyzeBinary([NotNull] BinaryExpression exp)
return exp;
}

private IExpression AnalyzeTrigonometric<T>([NotNull] UnaryExpression exp)
private IExpression AnalyzeTrigonometric<T>(UnaryExpression exp)
where T : UnaryExpression
{
if (exp is null)
Expand All @@ -87,7 +87,7 @@ private IExpression AnalyzeTrigonometric<T>([NotNull] UnaryExpression exp)
return exp;
}

private IExpression AnalyzeDiffParams([NotNull] DifferentParametersExpression exp)
private IExpression AnalyzeDiffParams(DifferentParametersExpression exp)
{
if (exp is null)
ArgNull(ExceptionArgument.exp);
Expand All @@ -111,7 +111,7 @@ private IExpression AnalyzeDiffParams([NotNull] DifferentParametersExpression ex
return exp;
}

private IExpression AnalyzeVariableBinary([NotNull] VariableBinaryExpression exp)
private IExpression AnalyzeVariableBinary(VariableBinaryExpression exp)
{
if (exp is null)
ArgNull(ExceptionArgument.exp);
Expand Down Expand Up @@ -1090,23 +1090,75 @@ public override IExpression Analyze(Arctan exp)

/// <inheritdoc />
public override IExpression Analyze(Cos exp)
=> AnalyzeTrigonometric<Arccos>(exp);
{
if (exp is null)
ArgNull(ExceptionArgument.exp);

var argument = AnalyzeUnaryArgument(exp);
if (argument is Arccos { Argument: Number inverseNumber } arccos &&
Arccos.Domain.IsInRange(inverseNumber.Value))
return arccos.Argument;

if (IsChanged(exp, argument))
return exp.Clone(argument);

return exp;
}

/// <inheritdoc />
public override IExpression Analyze(Cot exp)
=> AnalyzeTrigonometric<Arccot>(exp);

/// <inheritdoc />
public override IExpression Analyze(Csc exp)
=> AnalyzeTrigonometric<Arccsc>(exp);
{
if (exp is null)
ArgNull(ExceptionArgument.exp);

var argument = AnalyzeUnaryArgument(exp);
if (argument is Arccsc { Argument: Number inverseNumber } arccsc &&
Arccsc.Domain.IsInRange(inverseNumber.Value))
return arccsc.Argument;

if (IsChanged(exp, argument))
return exp.Clone(argument);

return exp;
}

/// <inheritdoc />
public override IExpression Analyze(Sec exp)
=> AnalyzeTrigonometric<Arcsec>(exp);
{
if (exp is null)
ArgNull(ExceptionArgument.exp);

var argument = AnalyzeUnaryArgument(exp);
if (argument is Arcsec { Argument: Number inverseNumber } arcsec &&
Arcsec.Domain.IsInRange(inverseNumber.Value))
return arcsec.Argument;

if (IsChanged(exp, argument))
return exp.Clone(argument);

return exp;
}

/// <inheritdoc />
public override IExpression Analyze(Sin exp)
=> AnalyzeTrigonometric<Arcsin>(exp);
{
if (exp is null)
ArgNull(ExceptionArgument.exp);

var argument = AnalyzeUnaryArgument(exp);
if (argument is Arcsin { Argument: Number inverseNumber } arcsin &&
Arcsin.Domain.IsInRange(inverseNumber.Value))
return arcsin.Argument;

if (IsChanged(exp, argument))
return exp.Clone(argument);

return exp;
}

/// <inheritdoc />
public override IExpression Analyze(Tan exp)
Expand Down
67 changes: 67 additions & 0 deletions xFunc.Maths/Expressions/Domains/Domain.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright (c) Dmytro Kyshchenko. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace xFunc.Maths.Expressions.Domains;

/// <summary>
/// Represents a domain of function.
/// </summary>
public readonly struct Domain : IEquatable<Domain>
{
private readonly DomainRange[] ranges;

/// <summary>
/// Initializes a new instance of the <see cref="Domain"/> struct.
/// </summary>
/// <param name="ranges">The array of ranges.</param>
public Domain(DomainRange[] ranges)
=> this.ranges = ranges; // TODO: validate?

/// <summary>
/// Indicates whether <paramref name="left"/> domain is equal to the <paramref name="right"/> domain.
/// </summary>
/// <param name="left">The left domain.</param>
/// <param name="right">The right domain.</param>
/// <returns><c>true</c> if the <paramref name="left"/> domain is equal to the <paramref name="right"/> domain; otherwise, <c>false</c>.</returns>
public static bool operator ==(Domain left, Domain right)
=> left.Equals(right);

/// <summary>
/// Indicates whether <paramref name="left"/> domain is not equal to the <paramref name="right"/> domain.
/// </summary>
/// <param name="left">The left domain.</param>
/// <param name="right">The right domain.</param>
/// <returns><c>true</c> if the <paramref name="left"/> domain is not equal to the <paramref name="right"/> domain; otherwise, <c>false</c>.</returns>
public static bool operator !=(Domain left, Domain right)
=> !left.Equals(right);

/// <inheritdoc />
public bool Equals(Domain other)
=> ranges.Equals(other.ranges);

/// <inheritdoc />
public override bool Equals(object? obj)
=> obj is Domain other && Equals(other);

/// <inheritdoc />
public override int GetHashCode()
=> ranges.GetHashCode();

/// <inheritdoc />
public override string ToString()
=> string.Join(", ", ranges);

/// <summary>
/// Determines whether the <paramref name="value"/> number is in the domain of the function.
/// </summary>
/// <param name="value">The number.</param>
/// <returns><c>true</c>, if the <paramref name="value"/> number is in the domain, otherwise, <c>false</c>.</returns>
public bool IsInRange(NumberValue value)
{
foreach (var range in ranges)
if (range.IsInRange(value))
return true;

return false;
}
}
48 changes: 48 additions & 0 deletions xFunc.Maths/Expressions/Domains/DomainBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) Dmytro Kyshchenko. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace xFunc.Maths.Expressions.Domains;

/// <summary>
/// The builder for <see cref="DomainRange"/>.
/// </summary>
public class DomainBuilder
{
private readonly DomainRange[] ranges;
private int index;

/// <summary>
/// Initializes a new instance of the <see cref="DomainBuilder"/> class.
/// </summary>
/// <param name="rangeCount">The amount of ranges to build.</param>
public DomainBuilder(int rangeCount)
{
ranges = new DomainRange[rangeCount];
index = 0;
}

/// <summary>
/// Adds a range to the domain.
/// </summary>
/// <param name="configuration">The delegate to configure the domain range.</param>
/// <returns>The builder.</returns>
/// <exception cref="InvalidOperationException">The amount of added ranges exceeded the amount specified on the builder creation.</exception>
public DomainBuilder AddRange(Action<DomainRangeBuilder> configuration)
{
if (index >= ranges.Length)
throw new InvalidOperationException(); // TODO:

var builder = new DomainRangeBuilder();
configuration(builder);
ranges[index++] = builder.Build();

return this;
}

/// <summary>
/// Builds the domain of the function.
/// </summary>
/// <returns>The domain of the function.</returns>
public Domain Build()
=> new Domain(ranges);
}
94 changes: 94 additions & 0 deletions xFunc.Maths/Expressions/Domains/DomainRange.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright (c) Dmytro Kyshchenko. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace xFunc.Maths.Expressions.Domains;

/// <summary>
/// Represents a range of allowed values for the domain of function.
/// </summary>
public readonly struct DomainRange : IEquatable<DomainRange>
{
private readonly NumberValue start;
private readonly bool isStartInclusive;
private readonly NumberValue end;
private readonly bool isEndInclusive;

/// <summary>
/// Initializes a new instance of the <see cref="DomainRange"/> struct.
/// </summary>
/// <param name="start">The start of the range.</param>
/// <param name="isStartInclusive">Determines whether the <paramref name="start"/> parameter is inclusive.</param>
/// <param name="end">The end of the range.</param>
/// <param name="isEndInclusive">Determines whether the <paramref name="end"/> parameter is inclusive.</param>
public DomainRange(NumberValue start, bool isStartInclusive, NumberValue end, bool isEndInclusive)
{
if ((start.IsInfinity && isStartInclusive) ||
(end.IsInfinity && isEndInclusive))
throw new InvalidOperationException(); // TODO:

this.start = start;
this.isStartInclusive = isStartInclusive;
this.end = end;
this.isEndInclusive = isEndInclusive;
}

/// <summary>
/// Indicates whether <paramref name="left"/> range is equal to the <paramref name="right"/> range.
/// </summary>
/// <param name="left">The left range.</param>
/// <param name="right">The right range.</param>
/// <returns><c>true</c> if the <paramref name="left"/> range is equal to the <paramref name="right"/> range; otherwise, <c>false</c>.</returns>
public static bool operator ==(DomainRange left, DomainRange right)
=> left.Equals(right);

/// <summary>
/// Indicates whether <paramref name="left"/> range is not equal to the <paramref name="right"/> range.
/// </summary>
/// <param name="left">The left range.</param>
/// <param name="right">The right range.</param>
/// <returns><c>true</c> if the <paramref name="left"/> range is not equal to the <paramref name="right"/> range; otherwise, <c>false</c>.</returns>
public static bool operator !=(DomainRange left, DomainRange right)
=> !left.Equals(right);

/// <inheritdoc />
public bool Equals(DomainRange other)
=> start.Equals(other.start) &&
isStartInclusive == other.isStartInclusive &&
end.Equals(other.end) &&
isEndInclusive == other.isEndInclusive;

/// <inheritdoc />
public override bool Equals(object? obj)
=> obj is DomainRange other && Equals(other);

/// <inheritdoc />
public override int GetHashCode()
=> HashCode.Combine(start, isStartInclusive, end, isEndInclusive);

/// <inheritdoc />
public override string ToString()
{
var leftBracket = isStartInclusive ? '[' : '(';
var rightBracket = isEndInclusive ? ']' : ')';

return $"{leftBracket}{start}; {end}{rightBracket}";
}

/// <summary>
/// Determines whether the <paramref name="value"/> number is in the range of domain.
/// </summary>
/// <param name="value">The number.</param>
/// <returns><c>true</c>, if the <paramref name="value"/> number is in the range of domain, otherwise, <c>false</c>.</returns>
public bool IsInRange(NumberValue value)
=> CheckStart(value) && CheckEnd(value);

private bool CheckStart(NumberValue value)
=> isStartInclusive
? start <= value
: start < value;

private bool CheckEnd(NumberValue value)
=> isEndInclusive
? value <= end
: value < end;
}
Loading

0 comments on commit 9846d6d

Please sign in to comment.