Skip to content

Commit

Permalink
Merge pull request #665 from sys27/bugfix/662-simplification
Browse files Browse the repository at this point in the history
#662 - The simplification of trigonometric and inverse trigonometric functions is not correct
  • Loading branch information
sys27 committed Aug 3, 2023
2 parents 1fae7e7 + 001706c commit 8eaa07a
Show file tree
Hide file tree
Showing 24 changed files with 1,313 additions and 196 deletions.
149 changes: 133 additions & 16 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 Expand Up @@ -1142,27 +1194,92 @@ public override IExpression Analyze(Artanh exp)

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

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

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

return exp;
}

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

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

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

return exp;
}

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

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

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

return exp;
}

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

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

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

return exp;
}

/// <inheritdoc />
public override IExpression Analyze(Sinh exp)
=> AnalyzeTrigonometric<Arsinh>(exp);

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

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

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

return exp;
}

#endregion Hyperbolic

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

using System.Diagnostics.CodeAnalysis;

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)
{
if (ranges is null || ranges.Length == 0)
throw new ArgumentNullException(nameof(ranges));

for (var i = 0; i < ranges.Length - 1; i++)
{
var left = ranges[i];
var right = ranges[i + 1];

if (right.Start < left.Start && right.End < left.End)
throw new ArgumentException(Resource.DomainIsInvalid);
}

this.ranges = ranges;
}

/// <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.SequenceEqual(other.ranges);

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

/// <inheritdoc />
[ExcludeFromCodeCoverage]
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(Resource.DomainRangeExceeded);

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);
}

0 comments on commit 8eaa07a

Please sign in to comment.