Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tests for autodiff #25

Merged
merged 18 commits into from
Nov 10, 2023
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
233 changes: 169 additions & 64 deletions src/Mathematics.NET/AutoDiff/GradientTape.cs

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions src/Mathematics.NET/AutoDiff/Variable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,17 @@ namespace Mathematics.NET.AutoDiff;
/// <summary>Represents a variable used in reverse-mode automatic differentiation</summary>
public readonly record struct Variable
{
/// <summary>The index of the variable</summary>
internal readonly int _index;

/// <summary>The value of the variable</summary>
public readonly Real Value;

internal Variable(int index, Real value)
{
Index = index;
_index = index;
Value = value;
}

/// <summary>The index of the variable</summary>
public readonly int Index;

/// <summary>The value of the variable</summary>
public readonly Real Value;
public override string ToString() => Value.ToString();
}
12 changes: 12 additions & 0 deletions src/Mathematics.NET/Core/IReal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ public interface IReal<T>
/// <summary>The backing value of the type</summary>
double Value { get; }

/// <summary>Compute a quadrant-aware arctangent given two values</summary>
/// <param name="y">The first value</param>
/// <param name="x">The second value</param>
/// <returns>An angle</returns>
static abstract Real Atan2(T y, T x);

/// <summary>Compute the ceiling function of a value</summary>
/// <param name="x">A value</param>
/// <returns>The smallest integer greater than or equal to the value</returns>
Expand Down Expand Up @@ -91,4 +97,10 @@ public interface IReal<T>
/// </returns>
/// <exception cref="ArithmeticException">An overflow or underflow has occurred</exception>
static abstract int Sign(T x);

/// <summary>Create a real number from a type that implements <typeparamref name="T"/></summary>
/// <param name="x">A type that implements <typeparamref name="T"/></param>
/// <returns>A real value</returns>
/// <exception cref="OverflowException">Thrown when the value cannot be converted to the type <see cref="Real"/></exception>
static abstract Real ToReal(T x);
}
7 changes: 6 additions & 1 deletion src/Mathematics.NET/Core/Rational.cs
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,8 @@ public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatPro

public static Rational<T> Abs(Rational<T> x) => new(T.Abs(x._numerator), T.Abs(x._denominator));

public static Real Atan2(Rational<T> y, Rational<T> x) => Real.Atan2(ToReal(y), ToReal(x));

public static Rational<T> Ceiling(Rational<T> x)
{
var (quotient, remainder) = T.DivRem(x._numerator, x._denominator);
Expand Down Expand Up @@ -612,6 +614,9 @@ public static int Sign(Rational<T> x)
return x._numerator > T.Zero ? 1 : -1;
}

public static Real ToReal(Rational<T> x)
=> checked(double.CreateChecked(x._numerator) / double.CreateChecked(x._denominator));

public static bool TryConvertFromChecked<U>(U value, out Rational<T> result)
where U : INumberBase<U>
{
Expand Down Expand Up @@ -715,7 +720,7 @@ public static explicit operator Rational<T>(double x)

public static explicit operator checked Real(Rational<T> x) => checked(double.CreateChecked(x._numerator) / double.CreateChecked(x._denominator));

public static explicit operator Real(Rational<T> x) => double.CreateSaturating(x._numerator) / double.CreateSaturating(x._denominator);
public static explicit operator Real(Rational<T> x) => ToReal(x);

public static explicit operator checked double(Rational<T> x) => double.CreateChecked(x._numerator) / double.CreateChecked(x._denominator);

Expand Down
2 changes: 2 additions & 0 deletions src/Mathematics.NET/Core/Real.cs
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,8 @@ public static Real Reciprocate(Real x)

public static int Sign(Real x) => Math.Sign(x._value);

public static Real ToReal(Real x) => x;

public static bool TryConvertFromChecked<U>(U value, out Real result)
where U : INumberBase<U>
{
Expand Down
10 changes: 7 additions & 3 deletions src/Mathematics.NET/LinearAlgebra/Matrix2x2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,16 @@ public string ToString(string? format, IFormatProvider? provider)
return string.Format(provider, builder.ToString());
}

/// <summary>Create a diagonal matrix from specified values along the diagonal.</summary>
/// <param name="e11">The $ e_{11} $ component</param>
/// <param name="e22">The $ e_{22} $ component</param>
/// <returns>A diagonal matrix</returns>
public static Matrix2x2<T> CreateDiagonal(T e11, T e22)
=> new(e11, T.Zero, T.Zero, e22);

public readonly T Determinant() => E11 * E22 - E12 * E21;

public Matrix2x2<T> Inverse()
public readonly Matrix2x2<T> Inverse()
{
var det = Determinant();
if (det == T.Zero)
Expand All @@ -214,7 +218,7 @@ public Matrix2x2<T> Inverse()
public static bool IsNaM(Matrix2x2<T> matrix)
=> T.IsNaN(matrix.E11) && T.IsNaN(matrix.E22);

public T Trace() => E11 + E22;
public readonly T Trace() => E11 + E22;

public Matrix2x2<T> Transpose() => new(E11, E21, E12, E22);
public readonly Matrix2x2<T> Transpose() => new(E11, E21, E12, E22);
}
11 changes: 8 additions & 3 deletions src/Mathematics.NET/LinearAlgebra/Matrix3x3.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,11 @@ public string ToString(string? format, IFormatProvider? provider)
// Methods
//

/// <summary>Create a diagonal matrix from specified values along the diagonal.</summary>
/// <param name="e11">The $ e_{11} $ component</param>
/// <param name="e22">The $ e_{22} $ component</param>
/// <param name="e33">The $ e_{33} $ component</param>
/// <returns>A diagonal matrix</returns>
public static Matrix3x3<T> CreateDiagonal(T e11, T e22, T e33)
{
return new(
Expand All @@ -268,7 +273,7 @@ public readonly T Determinant()
return a * ei_fh - b * di_fg + c * dh_eg;
}

public Matrix3x3<T> Inverse()
public readonly Matrix3x3<T> Inverse()
{
T a = E11, b = E12, c = E13;
T d = E21, e = E22, f = E23;
Expand Down Expand Up @@ -313,9 +318,9 @@ public Matrix3x3<T> Inverse()
public static bool IsNaM(Matrix3x3<T> matrix)
=> T.IsNaN(matrix.E11) && T.IsNaN(matrix.E22) && T.IsNaN(matrix.E33);

public T Trace() => E11 + E22 + E33;
public readonly T Trace() => E11 + E22 + E33;

public Matrix3x3<T> Transpose()
public readonly Matrix3x3<T> Transpose()
{
return new(
E11, E21, E31,
Expand Down
12 changes: 9 additions & 3 deletions src/Mathematics.NET/LinearAlgebra/Matrix4x4.cs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,12 @@ public string ToString(string? format, IFormatProvider? provider)
// Methods
//

/// <summary>Create a diagonal matrix from specified values along the diagonal.</summary>
/// <param name="e11">The $ e_{11} $ component</param>
/// <param name="e22">The $ e_{22} $ component</param>
/// <param name="e33">The $ e_{33} $ component</param>
/// <param name="e44">The $ e_{44} $ component</param>
/// <returns>A diagonal matrix</returns>
public static Matrix4x4<T> CreateDiagonal(T e11, T e22, T e33, T e44)
{
return new(
Expand Down Expand Up @@ -315,7 +321,7 @@ public readonly T Determinant()
}

// TODO: Optimize
public Matrix4x4<T> Inverse()
public readonly Matrix4x4<T> Inverse()
{
T a = E11, b = E12, c = E13, d = E14;
T e = E21, f = E22, g = E23, h = E24;
Expand Down Expand Up @@ -383,9 +389,9 @@ public Matrix4x4<T> Inverse()
public static bool IsNaM(Matrix4x4<T> matrix)
=> T.IsNaN(matrix.E11) && T.IsNaN(matrix.E22) && T.IsNaN(matrix.E33) && T.IsNaN(matrix.E44);

public T Trace() => E11 + E22 + E33 + E44;
public readonly T Trace() => E11 + E22 + E33 + E44;

public Matrix4x4<T> Transpose()
public readonly Matrix4x4<T> Transpose()
{
return new(
E11, E21, E31, E41,
Expand Down
4 changes: 2 additions & 2 deletions src/Mathematics.NET/LinearAlgebra/Vector2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ public string ToString(string? format, IFormatProvider? provider)
public static T InnerProduct(Vector2<T> left, Vector2<T> right)
=> T.Conjugate(left.X1) * right.X1 + T.Conjugate(left.X2) * right.X2;

public Real Norm()
public readonly Real Norm()
{
var x0 = (X1 * T.Conjugate(X1)).Re;
var x1 = (X2 * T.Conjugate(X2)).Re;
Expand All @@ -164,7 +164,7 @@ public Real Norm()
return max * Real.Sqrt(x0 / maxSquared + x1 / maxSquared);
}

public Vector2<T> Normalize()
public readonly Vector2<T> Normalize()
{
var norm = Norm();
return new(X1 / norm, X2 / norm);
Expand Down
16 changes: 14 additions & 2 deletions src/Mathematics.NET/LinearAlgebra/Vector3.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,10 +178,22 @@ public string ToString(string? format, IFormatProvider? provider)
// Methods
//

/// <summary>Compute the cross product of two vectors.</summary>
/// <param name="left">The first vector</param>
/// <param name="right">The second vector</param>
/// <returns>The cross product</returns>
public static Vector3<T> Cross(Vector3<T> left, Vector3<T> right)
{
return new(
left.X2 * right.X3 - left.X3 * right.X2,
left.X3 * right.X1 - left.X1 * right.X3,
left.X1 * right.X2 - left.X2 * right.X1);
}

public static T InnerProduct(Vector3<T> left, Vector3<T> right)
=> T.Conjugate(left.X1) * right.X1 + T.Conjugate(left.X2) * right.X2 + T.Conjugate(left.X3) * right.X3;

public Real Norm()
public readonly Real Norm()
{
Span<Real> components = stackalloc Real[3];

Expand All @@ -207,7 +219,7 @@ public Real Norm()
return max * Real.Sqrt(partialSum);
}

public Vector3<T> Normalize()
public readonly Vector3<T> Normalize()
{
var norm = Norm();
return new(X1 / norm, X2 / norm, X3 / norm);
Expand Down
4 changes: 2 additions & 2 deletions src/Mathematics.NET/LinearAlgebra/Vector4.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ public string ToString(string? format, IFormatProvider? provider)
public static T InnerProduct(Vector4<T> left, Vector4<T> right)
=> T.Conjugate(left.X1) * right.X1 + T.Conjugate(left.X2) * right.X2 + T.Conjugate(left.X3) * right.X3 + T.Conjugate(left.X4) * right.X4;

public Real Norm()
public readonly Real Norm()
{
Span<Real> components = stackalloc Real[4];

Expand Down Expand Up @@ -220,7 +220,7 @@ public Real Norm()
return max * Real.Sqrt(partialSum);
}

public Vector4<T> Normalize()
public readonly Vector4<T> Normalize()
{
var norm = Norm();
return new(X1 / norm, X2 / norm, X3 / norm, X4 / norm);
Expand Down
2 changes: 1 addition & 1 deletion src/Mathematics.NET/Mathematics.NET.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<Nullable>enable</Nullable>
<Platforms>x64</Platforms>
<Title>Mathematics.NET</Title>
<Version>0.1.0-alpha.3</Version>
<Version>0.1.0-alpha.4</Version>
<PackageIcon>mathematics.net.png</PackageIcon>
<Authors>Hamlet Tanyavong</Authors>
<Description>Mathematics.NET is a C# class library that provides tools for solving mathematical problems.</Description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,16 @@ public class MatrixMultiplyByScalarBenchmarks
{
public int Rows { get; set; }
public int Cols { get; set; }
public required NET.Core.Complex[,] MatrixOne { get; set; }
public required NET.Core.Complex[,] MatrixTwo { get; set; }
public required Complex[,] MatrixOne { get; set; }
public required Complex[,] MatrixTwo { get; set; }

[GlobalSetup]
public void GlobalSetup()
{
Rows = 100;
Cols = 100;

MatrixOne = new NET.Core.Complex[Rows, Cols];
MatrixOne = new Complex[Rows, Cols];

for (int i = 0; i < Rows; i++)
{
Expand Down Expand Up @@ -75,7 +75,7 @@ public void MultiplyByScalarNaive()
[Benchmark]
public void MultiplyByScalarParallel()
{
Memory2D<NET.Core.Complex> matrixAsMemory = MatrixTwo;
ParallelHelper.ForEach(matrixAsMemory, new MultiplyByScalarAction<NET.Core.Complex>(Real.Pi));
Memory2D<Complex> matrixAsMemory = MatrixTwo;
ParallelHelper.ForEach(matrixAsMemory, new MultiplyByScalarAction<Complex>(Real.Pi));
}
}
52 changes: 46 additions & 6 deletions tests/Mathematics.NET.Tests/Assert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,28 +29,68 @@

namespace Mathematics.NET.Tests;

/// <summary>Assert helpers for Mathemtics.NET</summary>
/// <typeparam name="T">A type that implements <see cref="IComplex{T}"/></typeparam>
public sealed class Assert<T>
where T : IComplex<T>
{
public static void AreApproximatelyEqual(T expected, T actual, Real delta)
/// <summary>Assert that two values are approximately equal.</summary>
/// <param name="expected">The expected value</param>
/// <param name="actual">The actual value</param>
/// <param name="epsilon">A margin of error</param>
public static void AreApproximatelyEqual(T expected, T actual, Real epsilon)
{
if (T.IsNaN(expected) && T.IsNaN(actual) || T.IsInfinity(expected) && T.IsInfinity(actual))
{
return;
}

if (!Precision.AreApproximatelyEqual(expected, actual, delta))
if (!Precision.AreApproximatelyEqual(expected, actual, epsilon))
{
Assert.Fail($$"""
Actual value does not fall within the specifed margin of error, {{delta}}:
Actual value does not fall within the specifed margin of error, {{epsilon}}:

Expected: {{expected}}
Actual: {{actual}}
""");
}
}

public static void ElementsAreApproximatelyEqual(Span2D<T> expected, Span2D<T> actual, Real delta)
/// <summary>Assert that the elements in two read-only spans are approximately equal.</summary>
/// <param name="expected">A read-only span of expected values</param>
/// <param name="actual">A read-only span of actual values</param>
/// <param name="epsilon">A margin of error</param>
public static void ElementsAreApproximatelyEqual(ReadOnlySpan<T> expected, ReadOnlySpan<T> actual, Real epsilon)
{
if (expected.Length != actual.Length)
{
Assert.Fail($"Length of the actual array, {actual.Length}, does not match the length of the expected array, {expected.Length}");
}

for (int i = 0; i < expected.Length; i++)
{
if (T.IsNaN(expected[i]) && T.IsNaN(actual[i]) || T.IsInfinity(expected[i]) && T.IsInfinity(actual[i]))
{
continue;
}

if (!Precision.AreApproximatelyEqual(expected[i], actual[i], epsilon))
{
Assert.Fail($$"""
Actual value at index {{i}} does not fall within the specified margin of error, {{epsilon}}

Expected: {{expected[i]}}
Actual: {{actual[i]}}
""");
}
}
}

/// <summary>Assert that the elements in two 2D, read-only spans are approximately equal.</summary>
/// <param name="expected">A 2D, read-only span of expected values</param>
/// <param name="actual">A 2D, read-only span of actual values</param>
/// <param name="epsilon">A margin of error</param>
public static void ElementsAreApproximatelyEqual(ReadOnlySpan2D<T> expected, ReadOnlySpan2D<T> actual, Real epsilon)
{
if (expected.Height != actual.Height || expected.Width != actual.Width)
{
Expand All @@ -66,10 +106,10 @@ public static void ElementsAreApproximatelyEqual(Span2D<T> expected, Span2D<T> a
continue;
}

if (!Precision.AreApproximatelyEqual(expected[i, j], actual[i, j], delta))
if (!Precision.AreApproximatelyEqual(expected[i, j], actual[i, j], epsilon))
{
Assert.Fail($$"""
Actual value at row {{i}} and column {{j}} does not fall within the specifed margin of error, {{delta}}:
Actual value at row {{i}} and column {{j}} does not fall within the specifed margin of error, {{epsilon}}:

Expected: {{expected[i, j]}}
Actual: {{actual[i, j]}}
Expand Down
Loading