From be8c884029c9dbb01f4e716e1af03be6b3b63fec Mon Sep 17 00:00:00 2001
From: Hamlet Tanyavong <34531738+HamletTanyavong@users.noreply.github.com>
Date: Tue, 7 Nov 2023 01:01:53 -0600
Subject: [PATCH 01/18] Update GradientTape.cs
- Add support for cancelling print node operation
---
src/Mathematics.NET/AutoDiff/GradientTape.cs | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/src/Mathematics.NET/AutoDiff/GradientTape.cs b/src/Mathematics.NET/AutoDiff/GradientTape.cs
index 9d25078b..de8b8651 100644
--- a/src/Mathematics.NET/AutoDiff/GradientTape.cs
+++ b/src/Mathematics.NET/AutoDiff/GradientTape.cs
@@ -86,7 +86,7 @@ public GradientTape()
/// Print the nodes of the gradient tape to the console
/// The total number of nodes to print
- public void PrintNodes(int limit = 100)
+ public void PrintNodes(CancellationToken cancellationToken, int limit = 100)
{
const string tab = " ";
@@ -96,21 +96,35 @@ public void PrintNodes(int limit = 100)
int i = 0;
while (i < Math.Min(_variableCount, limit))
{
+ CheckForCancellation(cancellationToken);
node = nodeSpan[i];
Console.WriteLine($"Root Node {i}:");
Console.WriteLine($"{tab}Weights: [{node.DX}, {node.DY}]");
Console.WriteLine($"{tab}Parents: [{node.PX}, {node.PY}]");
i++;
}
+
+ CheckForCancellation(cancellationToken);
Console.WriteLine();
+
while (i < Math.Min(nodeSpan.Length, limit))
{
+ CheckForCancellation(cancellationToken);
node = nodeSpan[i];
Console.WriteLine($"Node {i}:");
Console.WriteLine($"{tab}Weights: [{node.DX}, {node.DY}]");
Console.WriteLine($"{tab}Parents: [{node.PX}, {node.PY}]");
i++;
}
+
+ static void CheckForCancellation(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ Console.WriteLine("Print node operation cancelled");
+ cancellationToken.ThrowIfCancellationRequested();
+ }
+ }
}
/// Perform reverse accumulation on the gradient tape and output the resulting gradients
From 5ef762e23fa0c6066c7ddb3b58d71b80ffcf61ab Mon Sep 17 00:00:00 2001
From: Hamlet Tanyavong <34531738+HamletTanyavong@users.noreply.github.com>
Date: Tue, 7 Nov 2023 16:04:55 -0600
Subject: [PATCH 02/18] Make index field internal
---
src/Mathematics.NET/AutoDiff/GradientTape.cs | 78 ++++++++++----------
src/Mathematics.NET/AutoDiff/Variable.cs | 14 ++--
2 files changed, 46 insertions(+), 46 deletions(-)
diff --git a/src/Mathematics.NET/AutoDiff/GradientTape.cs b/src/Mathematics.NET/AutoDiff/GradientTape.cs
index de8b8651..3512a853 100644
--- a/src/Mathematics.NET/AutoDiff/GradientTape.cs
+++ b/src/Mathematics.NET/AutoDiff/GradientTape.cs
@@ -169,94 +169,94 @@ public Variable CreateVariable(Real seed)
public Variable Add(Variable x, Variable y)
{
- _nodes.Add(new(Real.One, Real.One, x.Index, y.Index));
+ _nodes.Add(new(Real.One, Real.One, x._index, y._index));
return new(_nodes.Count - 1, x.Value + y.Value);
}
public Variable Add(Real c, Variable x)
{
- _nodes.Add(new(Real.One, x.Index, _nodes.Count));
+ _nodes.Add(new(Real.One, x._index, _nodes.Count));
return new(_nodes.Count - 1, c + x.Value);
}
public Variable Add(Variable x, Real c)
{
- _nodes.Add(new(Real.One, x.Index, _nodes.Count));
+ _nodes.Add(new(Real.One, x._index, _nodes.Count));
return new(_nodes.Count - 1, x.Value + c);
}
public Variable Divide(Variable x, Variable y)
{
var u = Real.One / y.Value;
- _nodes.Add(new(Real.One / y.Value, -x.Value * u * u, x.Index, y.Index));
+ _nodes.Add(new(Real.One / y.Value, -x.Value * u * u, x._index, y._index));
return new(_nodes.Count - 1, x.Value * u);
}
public Variable Divide(Real c, Variable x)
{
var u = Real.One / x.Value;
- _nodes.Add(new(-c.Value * u * u, x.Index, _nodes.Count));
+ _nodes.Add(new(-c.Value * u * u, x._index, _nodes.Count));
return new(_nodes.Count - 1, x.Value * u);
}
public Variable Divide(Variable x, Real c)
{
var u = Real.One / c.Value;
- _nodes.Add(new(u, x.Index, _nodes.Count));
+ _nodes.Add(new(u, x._index, _nodes.Count));
return new(_nodes.Count - 1, x.Value * u);
}
public Variable Modulo(Variable x, Variable y)
{
- _nodes.Add(new(Real.One, x.Value * Real.Floor(x.Value / y.Value), x.Index, y.Index));
+ _nodes.Add(new(Real.One, x.Value * Real.Floor(x.Value / y.Value), x._index, y._index));
return new(_nodes.Count - 1, x.Value % y.Value);
}
public Variable Modulo(Real c, Variable x)
{
- _nodes.Add(new(c * Real.Floor(c / x.Value), x.Index, _nodes.Count));
+ _nodes.Add(new(c * Real.Floor(c / x.Value), x._index, _nodes.Count));
return new(_nodes.Count - 1, c % x.Value);
}
public Variable Modulo(Variable x, Real c)
{
- _nodes.Add(new(Real.One, x.Index, _nodes.Count));
+ _nodes.Add(new(Real.One, x._index, _nodes.Count));
return new(_nodes.Count - 1, x.Value % c);
}
public Variable Multiply(Variable x, Variable y)
{
- _nodes.Add(new(y.Value, x.Value, x.Index, y.Index));
+ _nodes.Add(new(y.Value, x.Value, x._index, y._index));
return new(_nodes.Count - 1, x.Value * y.Value);
}
public Variable Multiply(Real c, Variable x)
{
- _nodes.Add(new(c, x.Index, _nodes.Count));
+ _nodes.Add(new(c, x._index, _nodes.Count));
return new(_nodes.Count - 1, c * x.Value);
}
public Variable Multiply(Variable x, Real c)
{
- _nodes.Add(new(c, x.Index, _nodes.Count));
+ _nodes.Add(new(c, x._index, _nodes.Count));
return new(_nodes.Count - 1, x.Value * c);
}
public Variable Subtract(Variable x, Variable y)
{
- _nodes.Add(new(Real.One, -Real.One, x.Index, y.Index));
+ _nodes.Add(new(Real.One, -Real.One, x._index, y._index));
return new(_nodes.Count - 1, x.Value - y.Value);
}
public Variable Subtract(Real c, Variable x)
{
- _nodes.Add(new(-Real.One, x.Index, _nodes.Count));
+ _nodes.Add(new(-Real.One, x._index, _nodes.Count));
return new(_nodes.Count - 1, c - x.Value);
}
public Variable Subtract(Variable x, Real c)
{
- _nodes.Add(new(Real.One, x.Index, _nodes.Count));
+ _nodes.Add(new(Real.One, x._index, _nodes.Count));
return new(_nodes.Count - 1, x.Value - c);
}
@@ -269,21 +269,21 @@ public Variable Subtract(Variable x, Real c)
public Variable Exp(Variable x)
{
var exp = Real.Exp(x.Value);
- _nodes.Add(new(exp, x.Index, _nodes.Count));
+ _nodes.Add(new(exp, x._index, _nodes.Count));
return new(_nodes.Count - 1, exp);
}
public Variable Exp2(Variable x)
{
var exp2 = Real.Exp2(x.Value);
- _nodes.Add(new(Real.Ln2 * exp2, x.Index, _nodes.Count));
+ _nodes.Add(new(Real.Ln2 * exp2, x._index, _nodes.Count));
return new(_nodes.Count - 1, exp2);
}
public Variable Exp10(Variable x)
{
var exp10 = Real.Exp10(x.Value);
- _nodes.Add(new(Real.Ln10 * exp10, x.Index, _nodes.Count));
+ _nodes.Add(new(Real.Ln10 * exp10, x._index, _nodes.Count));
return new(_nodes.Count - 1, exp10);
}
@@ -291,38 +291,38 @@ public Variable Exp10(Variable x)
public Variable Acosh(Variable x)
{
- _nodes.Add(new(Real.One / (Complex.Sqrt(x.Value - Real.One) * Complex.Sqrt(x.Value + Real.One)).Re, x.Index, _nodes.Count));
+ _nodes.Add(new(Real.One / (Complex.Sqrt(x.Value - Real.One) * Complex.Sqrt(x.Value + Real.One)).Re, x._index, _nodes.Count));
return new(_nodes.Count - 1, Real.Acosh(x.Value));
}
public Variable Asinh(Variable x)
{
- _nodes.Add(new(Real.One / Real.Sqrt(x.Value * x.Value + Real.One), x.Index, _nodes.Count));
+ _nodes.Add(new(Real.One / Real.Sqrt(x.Value * x.Value + Real.One), x._index, _nodes.Count));
return new(_nodes.Count - 1, Real.Asinh(x.Value));
}
public Variable Atanh(Variable x)
{
- _nodes.Add(new(Real.One / (Real.One - x.Value * x.Value), x.Index, _nodes.Count));
+ _nodes.Add(new(Real.One / (Real.One - x.Value * x.Value), x._index, _nodes.Count));
return new(_nodes.Count - 1, Real.Atanh(x.Value));
}
public Variable Cosh(Variable x)
{
- _nodes.Add(new(Real.Sinh(x.Value), x.Index, _nodes.Count));
+ _nodes.Add(new(Real.Sinh(x.Value), x._index, _nodes.Count));
return new(_nodes.Count - 1, Real.Cosh(x.Value));
}
public Variable Sinh(Variable x)
{
- _nodes.Add(new(Real.Cosh(x.Value), x.Index, _nodes.Count));
+ _nodes.Add(new(Real.Cosh(x.Value), x._index, _nodes.Count));
return new(_nodes.Count - 1, Real.Sinh(x.Value));
}
public Variable Tanh(Variable x)
{
var u = Real.One / Real.Cosh(x.Value);
- _nodes.Add(new(u * u, x.Index, _nodes.Count));
+ _nodes.Add(new(u * u, x._index, _nodes.Count));
return new(_nodes.Count - 1, Real.Tanh(x.Value));
}
@@ -330,26 +330,26 @@ public Variable Tanh(Variable x)
public Variable Ln(Variable x)
{
- _nodes.Add(new(Real.One / x.Value, x.Index, _nodes.Count));
+ _nodes.Add(new(Real.One / x.Value, x._index, _nodes.Count));
return new(_nodes.Count - 1, Real.Ln(x.Value));
}
public Variable Log(Variable x, Variable b)
{
var lnB = Real.Ln(b.Value);
- _nodes.Add(new(Real.One / (x.Value * lnB), -Real.Ln(x.Value) / (b.Value * lnB * lnB), x.Index, b.Index));
+ _nodes.Add(new(Real.One / (x.Value * lnB), -Real.Ln(x.Value) / (b.Value * lnB * lnB), x._index, b._index));
return new(_nodes.Count - 1, Real.Log(x.Value, b.Value));
}
public Variable Log2(Variable x)
{
- _nodes.Add(new(Real.One / (Real.Ln2 * x.Value), x.Index, _nodes.Count));
+ _nodes.Add(new(Real.One / (Real.Ln2 * x.Value), x._index, _nodes.Count));
return new(_nodes.Count - 1, Real.Log2(x.Value));
}
public Variable Log10(Variable x)
{
- _nodes.Add(new(Real.One / (Real.Ln10 * x.Value), x.Index, _nodes.Count));
+ _nodes.Add(new(Real.One / (Real.Ln10 * x.Value), x._index, _nodes.Count));
return new(_nodes.Count - 1, Real.Log10(x.Value));
}
@@ -358,7 +358,7 @@ public Variable Log10(Variable x)
public Variable Pow(Variable x, Variable y)
{
var pow = Real.Pow(x.Value, y.Value);
- _nodes.Add(new(y.Value * Real.Pow(x.Value, y.Value - Real.One), Real.Ln(x.Value) * pow, x.Index, y.Index));
+ _nodes.Add(new(y.Value * Real.Pow(x.Value, y.Value - Real.One), Real.Ln(x.Value) * pow, x._index, y._index));
return new(_nodes.Count - 1, pow);
}
@@ -367,21 +367,21 @@ public Variable Pow(Variable x, Variable y)
public Variable Cbrt(Variable x)
{
var cbrt = Real.Cbrt(x.Value);
- _nodes.Add(new(Real.One / (3.0 * cbrt * cbrt), x.Index, _nodes.Count));
+ _nodes.Add(new(Real.One / (3.0 * cbrt * cbrt), x._index, _nodes.Count));
return new(_nodes.Count - 1, cbrt);
}
public Variable Root(Variable x, Variable n)
{
var root = Real.Root(x.Value, n.Value);
- _nodes.Add(new(root / (n.Value * x.Value), -Real.Ln(x.Value) * root / (n.Value * n.Value), x.Index, n.Index));
+ _nodes.Add(new(root / (n.Value * x.Value), -Real.Ln(x.Value) * root / (n.Value * n.Value), x._index, n._index));
return new(_nodes.Count - 1, root);
}
public Variable Sqrt(Variable x)
{
var sqrt = Real.Sqrt(x.Value);
- _nodes.Add(new(0.5 / sqrt, x.Index, _nodes.Count));
+ _nodes.Add(new(0.5 / sqrt, x._index, _nodes.Count));
return new(_nodes.Count - 1, sqrt);
}
@@ -389,45 +389,45 @@ public Variable Sqrt(Variable x)
public Variable Acos(Variable x)
{
- _nodes.Add(new(-Real.One / Real.Sqrt(Real.One - x.Value * x.Value), x.Index, _nodes.Count));
+ _nodes.Add(new(-Real.One / Real.Sqrt(Real.One - x.Value * x.Value), x._index, _nodes.Count));
return new(_nodes.Count - 1, Real.Acos(x.Value));
}
public Variable Asin(Variable x)
{
- _nodes.Add(new(Real.One / Real.Sqrt(Real.One - x.Value * x.Value), x.Index, _nodes.Count));
+ _nodes.Add(new(Real.One / Real.Sqrt(Real.One - x.Value * x.Value), x._index, _nodes.Count));
return new(_nodes.Count - 1, Real.Asin(x.Value));
}
public Variable Atan(Variable x)
{
- _nodes.Add(new(Real.One / (Real.One + x.Value * x.Value), x.Index, _nodes.Count));
+ _nodes.Add(new(Real.One / (Real.One + x.Value * x.Value), x._index, _nodes.Count));
return new(_nodes.Count - 1, Real.Atan(x.Value));
}
public Variable Atan2(Variable y, Variable x)
{
var u = Real.One / (x.Value * x.Value + y.Value * y.Value);
- _nodes.Add(new(-x.Value * u, y.Value * u, y.Index, x.Index));
+ _nodes.Add(new(-x.Value * u, y.Value * u, y._index, x._index));
return new(_nodes.Count - 1, Real.Atan2(y.Value, x.Value));
}
public Variable Cos(Variable x)
{
- _nodes.Add(new(-Real.Sin(x.Value), x.Index, _nodes.Count));
+ _nodes.Add(new(-Real.Sin(x.Value), x._index, _nodes.Count));
return new(_nodes.Count - 1, Real.Cos(x.Value));
}
public Variable Sin(Variable x)
{
- _nodes.Add(new(Real.Cos(x.Value), x.Index, _nodes.Count));
+ _nodes.Add(new(Real.Cos(x.Value), x._index, _nodes.Count));
return new(_nodes.Count - 1, Real.Sin(x.Value));
}
public Variable Tan(Variable x)
{
var sec = Real.One / Real.Cos(x.Value);
- _nodes.Add(new(sec * sec, x.Index, _nodes.Count));
+ _nodes.Add(new(sec * sec, x._index, _nodes.Count));
return new(_nodes.Count - 1, Real.Tan(x.Value));
}
}
diff --git a/src/Mathematics.NET/AutoDiff/Variable.cs b/src/Mathematics.NET/AutoDiff/Variable.cs
index 18f68c65..87a783c6 100644
--- a/src/Mathematics.NET/AutoDiff/Variable.cs
+++ b/src/Mathematics.NET/AutoDiff/Variable.cs
@@ -30,15 +30,15 @@ namespace Mathematics.NET.AutoDiff;
/// Represents a variable used in reverse-mode automatic differentiation
public readonly record struct Variable
{
- internal Variable(int index, Real value)
- {
- Index = index;
- Value = value;
- }
-
/// The index of the variable
- public readonly int Index;
+ internal readonly int _index;
/// The value of the variable
public readonly Real Value;
+
+ internal Variable(int index, Real value)
+ {
+ _index = index;
+ Value = value;
+ }
}
From c29e05e8eabffc625f038748b6c79f3dbe69b4d0 Mon Sep 17 00:00:00 2001
From: Hamlet Tanyavong <34531738+HamletTanyavong@users.noreply.github.com>
Date: Tue, 7 Nov 2023 16:05:25 -0600
Subject: [PATCH 03/18] Update MatrixMultiplyByScalarBenchmarks.cs
---
.../LinearAlgebra/MatrixMultiplyByScalarBenchmarks.cs | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/tests/Mathematics.NET.Benchmarks/LinearAlgebra/MatrixMultiplyByScalarBenchmarks.cs b/tests/Mathematics.NET.Benchmarks/LinearAlgebra/MatrixMultiplyByScalarBenchmarks.cs
index f8fbc156..41be8eb4 100644
--- a/tests/Mathematics.NET.Benchmarks/LinearAlgebra/MatrixMultiplyByScalarBenchmarks.cs
+++ b/tests/Mathematics.NET.Benchmarks/LinearAlgebra/MatrixMultiplyByScalarBenchmarks.cs
@@ -38,8 +38,8 @@ 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()
@@ -47,7 +47,7 @@ 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++)
{
@@ -75,7 +75,7 @@ public void MultiplyByScalarNaive()
[Benchmark]
public void MultiplyByScalarParallel()
{
- Memory2D matrixAsMemory = MatrixTwo;
- ParallelHelper.ForEach(matrixAsMemory, new MultiplyByScalarAction(Real.Pi));
+ Memory2D matrixAsMemory = MatrixTwo;
+ ParallelHelper.ForEach(matrixAsMemory, new MultiplyByScalarAction(Real.Pi));
}
}
From 030907c717b88103e28a10d100220044d658e458 Mon Sep 17 00:00:00 2001
From: Hamlet Tanyavong <34531738+HamletTanyavong@users.noreply.github.com>
Date: Wed, 8 Nov 2023 02:07:57 -0600
Subject: [PATCH 04/18] Update GradientTape.cs
---
src/Mathematics.NET/AutoDiff/GradientTape.cs | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/Mathematics.NET/AutoDiff/GradientTape.cs b/src/Mathematics.NET/AutoDiff/GradientTape.cs
index 3512a853..4fcc1837 100644
--- a/src/Mathematics.NET/AutoDiff/GradientTape.cs
+++ b/src/Mathematics.NET/AutoDiff/GradientTape.cs
@@ -133,6 +133,11 @@ static void CheckForCancellation(CancellationToken cancellationToken)
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public void ReverseAccumulation(out ReadOnlySpan gradients, double seed = 1.0)
{
+ if (_variableCount == 0)
+ {
+ throw new Exception("Gradient tape contains no nodes");
+ }
+
ReadOnlySpan nodesAsSpan = CollectionsMarshal.AsSpan(_nodes);
ref var start = ref MemoryMarshal.GetReference(nodesAsSpan);
From a3776410e4002a6259c192c5eb46fbcd03c03032 Mon Sep 17 00:00:00 2001
From: Hamlet Tanyavong <34531738+HamletTanyavong@users.noreply.github.com>
Date: Wed, 8 Nov 2023 17:13:48 -0600
Subject: [PATCH 05/18] Update GradientTape.cs
- Minor changes
---
src/Mathematics.NET/AutoDiff/GradientTape.cs | 40 ++++++++++----------
1 file changed, 21 insertions(+), 19 deletions(-)
diff --git a/src/Mathematics.NET/AutoDiff/GradientTape.cs b/src/Mathematics.NET/AutoDiff/GradientTape.cs
index 4fcc1837..75784bb1 100644
--- a/src/Mathematics.NET/AutoDiff/GradientTape.cs
+++ b/src/Mathematics.NET/AutoDiff/GradientTape.cs
@@ -66,6 +66,8 @@ namespace Mathematics.NET.AutoDiff;
/// Represents a gradient tape
public record class GradientTape
{
+ // TODO: Measure performance with Stack instead of List
+ // TODO: Consider using array pools or something similar
private List _nodes;
private int _variableCount;
@@ -74,17 +76,28 @@ public GradientTape()
_nodes = new();
}
- /// Get the number of nodes on the gradient tape
+ /// Get the number of nodes on the gradient tape.
public int NodeCount => _nodes.Count;
- /// Get the number of variables that are being tracked
+ /// Get the number of variables that are being tracked.
public int VariableCount => _variableCount;
//
// Methods
//
- /// Print the nodes of the gradient tape to the console
+ /// Create a variable for the gradient tape to track.
+ /// A seed value
+ /// A variable
+ public Variable CreateVariable(Real seed)
+ {
+ _nodes.Add(new(_variableCount));
+ Variable variable = new(_variableCount++, seed);
+ return variable;
+ }
+
+ /// Print the nodes of the gradient tape to the console.
+ /// A cancellation token
/// The total number of nodes to print
public void PrintNodes(CancellationToken cancellationToken, int limit = 100)
{
@@ -127,7 +140,7 @@ static void CheckForCancellation(CancellationToken cancellationToken)
}
}
- /// Perform reverse accumulation on the gradient tape and output the resulting gradients
+ /// Perform reverse accumulation on the gradient tape and output the resulting gradients.
/// The gradients
/// A seed value
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
@@ -135,13 +148,13 @@ public void ReverseAccumulation(out ReadOnlySpan gradients, double seed =
{
if (_variableCount == 0)
{
- throw new Exception("Gradient tape contains no nodes");
+ throw new Exception("Gradient tape contains no root nodes");
}
- ReadOnlySpan nodesAsSpan = CollectionsMarshal.AsSpan(_nodes);
- ref var start = ref MemoryMarshal.GetReference(nodesAsSpan);
+ ReadOnlySpan nodes = CollectionsMarshal.AsSpan(_nodes);
+ ref var start = ref MemoryMarshal.GetReference(nodes);
- var length = nodesAsSpan.Length;
+ var length = nodes.Length;
Span partialGradients = new Real[length];
partialGradients[length - 1] = seed;
@@ -157,17 +170,6 @@ public void ReverseAccumulation(out ReadOnlySpan gradients, double seed =
gradients = partialGradients[.._variableCount];
}
- /// Create a variable for the gradient tape to track
- /// A seed value
- /// A variable
- public Variable CreateVariable(Real seed)
- {
- _nodes.Add(new(_variableCount));
- Variable variable = new(_variableCount, seed);
- _variableCount++;
- return variable;
- }
-
//
// Basic operations
//
From 48378345b5326e368b8ae49b1c9e9de59ce0d734 Mon Sep 17 00:00:00 2001
From: Hamlet Tanyavong <34531738+HamletTanyavong@users.noreply.github.com>
Date: Thu, 9 Nov 2023 13:39:27 -0600
Subject: [PATCH 06/18] Update Variable.cs
- Override ToString
---
src/Mathematics.NET/AutoDiff/Variable.cs | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/Mathematics.NET/AutoDiff/Variable.cs b/src/Mathematics.NET/AutoDiff/Variable.cs
index 87a783c6..a95c5ab0 100644
--- a/src/Mathematics.NET/AutoDiff/Variable.cs
+++ b/src/Mathematics.NET/AutoDiff/Variable.cs
@@ -41,4 +41,6 @@ internal Variable(int index, Real value)
_index = index;
Value = value;
}
+
+ public override string ToString() => Value.ToString();
}
From a201a297f3133ccd88561ec30386b0ae42e4aa55 Mon Sep 17 00:00:00 2001
From: Hamlet Tanyavong <34531738+HamletTanyavong@users.noreply.github.com>
Date: Thu, 9 Nov 2023 14:15:16 -0600
Subject: [PATCH 07/18] Update GradientTape.cs
- Add documentation comments
---
src/Mathematics.NET/AutoDiff/GradientTape.cs | 83 ++++++++++++++++++++
1 file changed, 83 insertions(+)
diff --git a/src/Mathematics.NET/AutoDiff/GradientTape.cs b/src/Mathematics.NET/AutoDiff/GradientTape.cs
index 75784bb1..62b3a3b3 100644
--- a/src/Mathematics.NET/AutoDiff/GradientTape.cs
+++ b/src/Mathematics.NET/AutoDiff/GradientTape.cs
@@ -174,24 +174,40 @@ public void ReverseAccumulation(out ReadOnlySpan gradients, double seed =
// Basic operations
//
+ /// Add two variables
+ /// The first variable
+ /// The second variable
+ /// A variable
public Variable Add(Variable x, Variable y)
{
_nodes.Add(new(Real.One, Real.One, x._index, y._index));
return new(_nodes.Count - 1, x.Value + y.Value);
}
+ /// Add a real value and a variable
+ /// A real value
+ /// A variable
+ /// A variable
public Variable Add(Real c, Variable x)
{
_nodes.Add(new(Real.One, x._index, _nodes.Count));
return new(_nodes.Count - 1, c + x.Value);
}
+ /// Add a variable and a real value
+ /// A variable
+ /// A real value
+ /// A variable
public Variable Add(Variable x, Real c)
{
_nodes.Add(new(Real.One, x._index, _nodes.Count));
return new(_nodes.Count - 1, x.Value + c);
}
+ /// Divide two variables
+ /// A dividend
+ /// A divisor
+ /// A variable
public Variable Divide(Variable x, Variable y)
{
var u = Real.One / y.Value;
@@ -199,6 +215,10 @@ public Variable Divide(Variable x, Variable y)
return new(_nodes.Count - 1, x.Value * u);
}
+ /// Divide a real value by a variable
+ /// A real dividend
+ /// A variable divisor
+ /// A variable
public Variable Divide(Real c, Variable x)
{
var u = Real.One / x.Value;
@@ -206,6 +226,10 @@ public Variable Divide(Real c, Variable x)
return new(_nodes.Count - 1, x.Value * u);
}
+ /// Divide a variable by a real value
+ /// A variable dividend
+ /// A real divisor
+ /// A variable
public Variable Divide(Variable x, Real c)
{
var u = Real.One / c.Value;
@@ -213,54 +237,90 @@ public Variable Divide(Variable x, Real c)
return new(_nodes.Count - 1, x.Value * u);
}
+ /// Compute the modulo of a variable given a divisor
+ /// A dividend
+ /// A divisor
+ /// mod
public Variable Modulo(Variable x, Variable y)
{
_nodes.Add(new(Real.One, x.Value * Real.Floor(x.Value / y.Value), x._index, y._index));
return new(_nodes.Count - 1, x.Value % y.Value);
}
+ /// Compute the modulo of a real value given a divisor
+ /// A real dividend
+ /// A variable divisor
+ /// mod
public Variable Modulo(Real c, Variable x)
{
_nodes.Add(new(c * Real.Floor(c / x.Value), x._index, _nodes.Count));
return new(_nodes.Count - 1, c % x.Value);
}
+ /// Compute the modulo of a variable given a divisor
+ /// A variable dividend
+ /// A real divisor
+ /// mod
public Variable Modulo(Variable x, Real c)
{
_nodes.Add(new(Real.One, x._index, _nodes.Count));
return new(_nodes.Count - 1, x.Value % c);
}
+ /// Multiply two variables
+ /// The first variable
+ /// The second variable
+ /// A variable
public Variable Multiply(Variable x, Variable y)
{
_nodes.Add(new(y.Value, x.Value, x._index, y._index));
return new(_nodes.Count - 1, x.Value * y.Value);
}
+ /// Multiply a real value by a variable
+ /// A real value
+ /// A variable
+ /// A variable
public Variable Multiply(Real c, Variable x)
{
_nodes.Add(new(c, x._index, _nodes.Count));
return new(_nodes.Count - 1, c * x.Value);
}
+ /// Multiply a variable by a real value
+ /// A variable
+ /// A real value
+ /// A variable
public Variable Multiply(Variable x, Real c)
{
_nodes.Add(new(c, x._index, _nodes.Count));
return new(_nodes.Count - 1, x.Value * c);
}
+ /// Subract two variables
+ /// The first variable
+ /// The second variable
+ /// A variable
public Variable Subtract(Variable x, Variable y)
{
_nodes.Add(new(Real.One, -Real.One, x._index, y._index));
return new(_nodes.Count - 1, x.Value - y.Value);
}
+ /// Subtract a variable from a real value
+ /// A real value
+ /// A variable
+ /// A variable
public Variable Subtract(Real c, Variable x)
{
_nodes.Add(new(-Real.One, x._index, _nodes.Count));
return new(_nodes.Count - 1, c - x.Value);
}
+ /// Subtract a real value from a variable
+ /// A variable
+ /// A real value
+ /// A variable
public Variable Subtract(Variable x, Real c)
{
_nodes.Add(new(Real.One, x._index, _nodes.Count));
@@ -273,6 +333,7 @@ public Variable Subtract(Variable x, Real c)
// Exponential functions
+ ///
public Variable Exp(Variable x)
{
var exp = Real.Exp(x.Value);
@@ -280,6 +341,7 @@ public Variable Exp(Variable x)
return new(_nodes.Count - 1, exp);
}
+ ///
public Variable Exp2(Variable x)
{
var exp2 = Real.Exp2(x.Value);
@@ -287,6 +349,7 @@ public Variable Exp2(Variable x)
return new(_nodes.Count - 1, exp2);
}
+ ///
public Variable Exp10(Variable x)
{
var exp10 = Real.Exp10(x.Value);
@@ -296,36 +359,42 @@ public Variable Exp10(Variable x)
// Hyperbolic functions
+ ///
public Variable Acosh(Variable x)
{
_nodes.Add(new(Real.One / (Complex.Sqrt(x.Value - Real.One) * Complex.Sqrt(x.Value + Real.One)).Re, x._index, _nodes.Count));
return new(_nodes.Count - 1, Real.Acosh(x.Value));
}
+ ///
public Variable Asinh(Variable x)
{
_nodes.Add(new(Real.One / Real.Sqrt(x.Value * x.Value + Real.One), x._index, _nodes.Count));
return new(_nodes.Count - 1, Real.Asinh(x.Value));
}
+ ///
public Variable Atanh(Variable x)
{
_nodes.Add(new(Real.One / (Real.One - x.Value * x.Value), x._index, _nodes.Count));
return new(_nodes.Count - 1, Real.Atanh(x.Value));
}
+ ///
public Variable Cosh(Variable x)
{
_nodes.Add(new(Real.Sinh(x.Value), x._index, _nodes.Count));
return new(_nodes.Count - 1, Real.Cosh(x.Value));
}
+ ///
public Variable Sinh(Variable x)
{
_nodes.Add(new(Real.Cosh(x.Value), x._index, _nodes.Count));
return new(_nodes.Count - 1, Real.Sinh(x.Value));
}
+ ///
public Variable Tanh(Variable x)
{
var u = Real.One / Real.Cosh(x.Value);
@@ -335,12 +404,14 @@ public Variable Tanh(Variable x)
// Logarithmic functions
+ ///
public Variable Ln(Variable x)
{
_nodes.Add(new(Real.One / x.Value, x._index, _nodes.Count));
return new(_nodes.Count - 1, Real.Ln(x.Value));
}
+ ///
public Variable Log(Variable x, Variable b)
{
var lnB = Real.Ln(b.Value);
@@ -348,12 +419,14 @@ public Variable Log(Variable x, Variable b)
return new(_nodes.Count - 1, Real.Log(x.Value, b.Value));
}
+ ///
public Variable Log2(Variable x)
{
_nodes.Add(new(Real.One / (Real.Ln2 * x.Value), x._index, _nodes.Count));
return new(_nodes.Count - 1, Real.Log2(x.Value));
}
+ ///
public Variable Log10(Variable x)
{
_nodes.Add(new(Real.One / (Real.Ln10 * x.Value), x._index, _nodes.Count));
@@ -362,6 +435,7 @@ public Variable Log10(Variable x)
// Power functions
+ ///
public Variable Pow(Variable x, Variable y)
{
var pow = Real.Pow(x.Value, y.Value);
@@ -371,6 +445,7 @@ public Variable Pow(Variable x, Variable y)
// Root functions
+ ///
public Variable Cbrt(Variable x)
{
var cbrt = Real.Cbrt(x.Value);
@@ -378,6 +453,7 @@ public Variable Cbrt(Variable x)
return new(_nodes.Count - 1, cbrt);
}
+ ///
public Variable Root(Variable x, Variable n)
{
var root = Real.Root(x.Value, n.Value);
@@ -385,6 +461,7 @@ public Variable Root(Variable x, Variable n)
return new(_nodes.Count - 1, root);
}
+ ///
public Variable Sqrt(Variable x)
{
var sqrt = Real.Sqrt(x.Value);
@@ -394,18 +471,21 @@ public Variable Sqrt(Variable x)
// Trigonometric functions
+ ///
public Variable Acos(Variable x)
{
_nodes.Add(new(-Real.One / Real.Sqrt(Real.One - x.Value * x.Value), x._index, _nodes.Count));
return new(_nodes.Count - 1, Real.Acos(x.Value));
}
+ ///
public Variable Asin(Variable x)
{
_nodes.Add(new(Real.One / Real.Sqrt(Real.One - x.Value * x.Value), x._index, _nodes.Count));
return new(_nodes.Count - 1, Real.Asin(x.Value));
}
+ ///
public Variable Atan(Variable x)
{
_nodes.Add(new(Real.One / (Real.One + x.Value * x.Value), x._index, _nodes.Count));
@@ -419,18 +499,21 @@ public Variable Atan2(Variable y, Variable x)
return new(_nodes.Count - 1, Real.Atan2(y.Value, x.Value));
}
+ ///
public Variable Cos(Variable x)
{
_nodes.Add(new(-Real.Sin(x.Value), x._index, _nodes.Count));
return new(_nodes.Count - 1, Real.Cos(x.Value));
}
+ ///
public Variable Sin(Variable x)
{
_nodes.Add(new(Real.Cos(x.Value), x._index, _nodes.Count));
return new(_nodes.Count - 1, Real.Sin(x.Value));
}
+ ///
public Variable Tan(Variable x)
{
var sec = Real.One / Real.Cos(x.Value);
From e223e2f1ba533b882bc0e248bc4119e271a6bbad Mon Sep 17 00:00:00 2001
From: Hamlet Tanyavong <34531738+HamletTanyavong@users.noreply.github.com>
Date: Thu, 9 Nov 2023 15:05:51 -0600
Subject: [PATCH 08/18] Add ToReal method
---
src/Mathematics.NET/Core/IReal.cs | 6 ++++++
src/Mathematics.NET/Core/Rational.cs | 5 ++++-
src/Mathematics.NET/Core/Real.cs | 2 ++
3 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/src/Mathematics.NET/Core/IReal.cs b/src/Mathematics.NET/Core/IReal.cs
index 3b9a1bbb..67299ca3 100644
--- a/src/Mathematics.NET/Core/IReal.cs
+++ b/src/Mathematics.NET/Core/IReal.cs
@@ -91,4 +91,10 @@ public interface IReal
///
/// An overflow or underflow has occurred
static abstract int Sign(T x);
+
+ /// Create a real number from a type that implements
+ /// A type that implements
+ /// A real value
+ /// Thrown when the value cannot be converted to the type
+ static abstract Real ToReal(T x);
}
diff --git a/src/Mathematics.NET/Core/Rational.cs b/src/Mathematics.NET/Core/Rational.cs
index 0f7bfa6a..0a32bbd7 100644
--- a/src/Mathematics.NET/Core/Rational.cs
+++ b/src/Mathematics.NET/Core/Rational.cs
@@ -612,6 +612,9 @@ public static int Sign(Rational x)
return x._numerator > T.Zero ? 1 : -1;
}
+ public static Real ToReal(Rational x)
+ => checked(double.CreateChecked(x._numerator) / double.CreateChecked(x._denominator));
+
public static bool TryConvertFromChecked(U value, out Rational result)
where U : INumberBase
{
@@ -715,7 +718,7 @@ public static explicit operator Rational(double x)
public static explicit operator checked Real(Rational x) => checked(double.CreateChecked(x._numerator) / double.CreateChecked(x._denominator));
- public static explicit operator Real(Rational x) => double.CreateSaturating(x._numerator) / double.CreateSaturating(x._denominator);
+ public static explicit operator Real(Rational x) => ToReal(x);
public static explicit operator checked double(Rational x) => double.CreateChecked(x._numerator) / double.CreateChecked(x._denominator);
diff --git a/src/Mathematics.NET/Core/Real.cs b/src/Mathematics.NET/Core/Real.cs
index 8865dfbe..ba462f14 100644
--- a/src/Mathematics.NET/Core/Real.cs
+++ b/src/Mathematics.NET/Core/Real.cs
@@ -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 value, out Real result)
where U : INumberBase
{
From 8f8bf28f1d759ae2aae51a46bda8f6dc250242f3 Mon Sep 17 00:00:00 2001
From: Hamlet Tanyavong <34531738+HamletTanyavong@users.noreply.github.com>
Date: Thu, 9 Nov 2023 15:12:27 -0600
Subject: [PATCH 09/18] Add Atan2
---
src/Mathematics.NET/Core/IReal.cs | 6 ++++++
src/Mathematics.NET/Core/Rational.cs | 2 ++
2 files changed, 8 insertions(+)
diff --git a/src/Mathematics.NET/Core/IReal.cs b/src/Mathematics.NET/Core/IReal.cs
index 67299ca3..c663f184 100644
--- a/src/Mathematics.NET/Core/IReal.cs
+++ b/src/Mathematics.NET/Core/IReal.cs
@@ -47,6 +47,12 @@ public interface IReal
/// The backing value of the type
double Value { get; }
+ /// Compute a quadrant-aware arctangent given two values
+ /// The first value
+ /// The second value
+ /// An angle
+ static abstract Real Atan2(T y, T x);
+
/// Compute the ceiling function of a value
/// A value
/// The smallest integer greater than or equal to the value
diff --git a/src/Mathematics.NET/Core/Rational.cs b/src/Mathematics.NET/Core/Rational.cs
index 0a32bbd7..210955e0 100644
--- a/src/Mathematics.NET/Core/Rational.cs
+++ b/src/Mathematics.NET/Core/Rational.cs
@@ -501,6 +501,8 @@ public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatPro
public static Rational Abs(Rational x) => new(T.Abs(x._numerator), T.Abs(x._denominator));
+ public static Real Atan2(Rational y, Rational x) => Real.Atan2(ToReal(y), ToReal(x));
+
public static Rational Ceiling(Rational x)
{
var (quotient, remainder) = T.DivRem(x._numerator, x._denominator);
From 1966e5370be34cdbae47cfcfea5328f9708297cc Mon Sep 17 00:00:00 2001
From: Hamlet Tanyavong <34531738+HamletTanyavong@users.noreply.github.com>
Date: Thu, 9 Nov 2023 15:12:36 -0600
Subject: [PATCH 10/18] Update GradientTape.cs
- Add documentation comment
---
src/Mathematics.NET/AutoDiff/GradientTape.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/Mathematics.NET/AutoDiff/GradientTape.cs b/src/Mathematics.NET/AutoDiff/GradientTape.cs
index 62b3a3b3..5eced7a6 100644
--- a/src/Mathematics.NET/AutoDiff/GradientTape.cs
+++ b/src/Mathematics.NET/AutoDiff/GradientTape.cs
@@ -492,6 +492,7 @@ public Variable Atan(Variable x)
return new(_nodes.Count - 1, Real.Atan(x.Value));
}
+ ///
public Variable Atan2(Variable y, Variable x)
{
var u = Real.One / (x.Value * x.Value + y.Value * y.Value);
From 8e88ad6641aef382d5d8ad051a5cb33ab54dbaec Mon Sep 17 00:00:00 2001
From: Hamlet Tanyavong <34531738+HamletTanyavong@users.noreply.github.com>
Date: Thu, 9 Nov 2023 17:14:06 -0600
Subject: [PATCH 11/18] Update Vector3.cs
- Add cross product
---
src/Mathematics.NET/LinearAlgebra/Vector3.cs | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/src/Mathematics.NET/LinearAlgebra/Vector3.cs b/src/Mathematics.NET/LinearAlgebra/Vector3.cs
index ec103a13..5822a026 100644
--- a/src/Mathematics.NET/LinearAlgebra/Vector3.cs
+++ b/src/Mathematics.NET/LinearAlgebra/Vector3.cs
@@ -178,6 +178,18 @@ public string ToString(string? format, IFormatProvider? provider)
// Methods
//
+ /// Compute the cross product of two vectors.
+ /// The first vector
+ /// The second vector
+ /// The cross product
+ public static Vector3 Cross(Vector3 left, Vector3 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 left, Vector3 right)
=> T.Conjugate(left.X1) * right.X1 + T.Conjugate(left.X2) * right.X2 + T.Conjugate(left.X3) * right.X3;
From 39cd675d2a0b7614ddda53578b93a9b9bac78099 Mon Sep 17 00:00:00 2001
From: Hamlet Tanyavong <34531738+HamletTanyavong@users.noreply.github.com>
Date: Thu, 9 Nov 2023 17:20:36 -0600
Subject: [PATCH 12/18] Return readonly values
- Add documentation comments
---
src/Mathematics.NET/LinearAlgebra/Matrix2x2.cs | 10 +++++++---
src/Mathematics.NET/LinearAlgebra/Matrix3x3.cs | 11 ++++++++---
src/Mathematics.NET/LinearAlgebra/Matrix4x4.cs | 12 +++++++++---
src/Mathematics.NET/LinearAlgebra/Vector2.cs | 4 ++--
src/Mathematics.NET/LinearAlgebra/Vector3.cs | 4 ++--
src/Mathematics.NET/LinearAlgebra/Vector4.cs | 4 ++--
6 files changed, 30 insertions(+), 15 deletions(-)
diff --git a/src/Mathematics.NET/LinearAlgebra/Matrix2x2.cs b/src/Mathematics.NET/LinearAlgebra/Matrix2x2.cs
index 69904fdf..c3791f5f 100644
--- a/src/Mathematics.NET/LinearAlgebra/Matrix2x2.cs
+++ b/src/Mathematics.NET/LinearAlgebra/Matrix2x2.cs
@@ -194,12 +194,16 @@ public string ToString(string? format, IFormatProvider? provider)
return string.Format(provider, builder.ToString());
}
+ /// Create a diagonal matrix from specified values along the diagonal.
+ /// The $ e_{11} $ component
+ /// The $ e_{22} $ component
+ /// A diagonal matrix
public static Matrix2x2 CreateDiagonal(T e11, T e22)
=> new(e11, T.Zero, T.Zero, e22);
public readonly T Determinant() => E11 * E22 - E12 * E21;
- public Matrix2x2 Inverse()
+ public readonly Matrix2x2 Inverse()
{
var det = Determinant();
if (det == T.Zero)
@@ -214,7 +218,7 @@ public Matrix2x2 Inverse()
public static bool IsNaM(Matrix2x2 matrix)
=> T.IsNaN(matrix.E11) && T.IsNaN(matrix.E22);
- public T Trace() => E11 + E22;
+ public readonly T Trace() => E11 + E22;
- public Matrix2x2 Transpose() => new(E11, E21, E12, E22);
+ public readonly Matrix2x2 Transpose() => new(E11, E21, E12, E22);
}
diff --git a/src/Mathematics.NET/LinearAlgebra/Matrix3x3.cs b/src/Mathematics.NET/LinearAlgebra/Matrix3x3.cs
index f82f4c38..e78df3cd 100644
--- a/src/Mathematics.NET/LinearAlgebra/Matrix3x3.cs
+++ b/src/Mathematics.NET/LinearAlgebra/Matrix3x3.cs
@@ -247,6 +247,11 @@ public string ToString(string? format, IFormatProvider? provider)
// Methods
//
+ /// Create a diagonal matrix from specified values along the diagonal.
+ /// The $ e_{11} $ component
+ /// The $ e_{22} $ component
+ /// The $ e_{33} $ component
+ /// A diagonal matrix
public static Matrix3x3 CreateDiagonal(T e11, T e22, T e33)
{
return new(
@@ -268,7 +273,7 @@ public readonly T Determinant()
return a * ei_fh - b * di_fg + c * dh_eg;
}
- public Matrix3x3 Inverse()
+ public readonly Matrix3x3 Inverse()
{
T a = E11, b = E12, c = E13;
T d = E21, e = E22, f = E23;
@@ -313,9 +318,9 @@ public Matrix3x3 Inverse()
public static bool IsNaM(Matrix3x3 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 Transpose()
+ public readonly Matrix3x3 Transpose()
{
return new(
E11, E21, E31,
diff --git a/src/Mathematics.NET/LinearAlgebra/Matrix4x4.cs b/src/Mathematics.NET/LinearAlgebra/Matrix4x4.cs
index 7c51b2ce..e3a90f53 100644
--- a/src/Mathematics.NET/LinearAlgebra/Matrix4x4.cs
+++ b/src/Mathematics.NET/LinearAlgebra/Matrix4x4.cs
@@ -285,6 +285,12 @@ public string ToString(string? format, IFormatProvider? provider)
// Methods
//
+ /// Create a diagonal matrix from specified values along the diagonal.
+ /// The $ e_{11} $ component
+ /// The $ e_{22} $ component
+ /// The $ e_{33} $ component
+ /// The $ e_{44} $ component
+ /// A diagonal matrix
public static Matrix4x4 CreateDiagonal(T e11, T e22, T e33, T e44)
{
return new(
@@ -315,7 +321,7 @@ public readonly T Determinant()
}
// TODO: Optimize
- public Matrix4x4 Inverse()
+ public readonly Matrix4x4 Inverse()
{
T a = E11, b = E12, c = E13, d = E14;
T e = E21, f = E22, g = E23, h = E24;
@@ -383,9 +389,9 @@ public Matrix4x4 Inverse()
public static bool IsNaM(Matrix4x4 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 Transpose()
+ public readonly Matrix4x4 Transpose()
{
return new(
E11, E21, E31, E41,
diff --git a/src/Mathematics.NET/LinearAlgebra/Vector2.cs b/src/Mathematics.NET/LinearAlgebra/Vector2.cs
index 3bc2b5e3..d3f04928 100644
--- a/src/Mathematics.NET/LinearAlgebra/Vector2.cs
+++ b/src/Mathematics.NET/LinearAlgebra/Vector2.cs
@@ -153,7 +153,7 @@ public string ToString(string? format, IFormatProvider? provider)
public static T InnerProduct(Vector2 left, Vector2 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;
@@ -164,7 +164,7 @@ public Real Norm()
return max * Real.Sqrt(x0 / maxSquared + x1 / maxSquared);
}
- public Vector2 Normalize()
+ public readonly Vector2 Normalize()
{
var norm = Norm();
return new(X1 / norm, X2 / norm);
diff --git a/src/Mathematics.NET/LinearAlgebra/Vector3.cs b/src/Mathematics.NET/LinearAlgebra/Vector3.cs
index 5822a026..07900288 100644
--- a/src/Mathematics.NET/LinearAlgebra/Vector3.cs
+++ b/src/Mathematics.NET/LinearAlgebra/Vector3.cs
@@ -193,7 +193,7 @@ public static Vector3 Cross(Vector3 left, Vector3 right)
public static T InnerProduct(Vector3 left, Vector3 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 components = stackalloc Real[3];
@@ -219,7 +219,7 @@ public Real Norm()
return max * Real.Sqrt(partialSum);
}
- public Vector3 Normalize()
+ public readonly Vector3 Normalize()
{
var norm = Norm();
return new(X1 / norm, X2 / norm, X3 / norm);
diff --git a/src/Mathematics.NET/LinearAlgebra/Vector4.cs b/src/Mathematics.NET/LinearAlgebra/Vector4.cs
index 304ea0a4..aa870c99 100644
--- a/src/Mathematics.NET/LinearAlgebra/Vector4.cs
+++ b/src/Mathematics.NET/LinearAlgebra/Vector4.cs
@@ -192,7 +192,7 @@ public string ToString(string? format, IFormatProvider? provider)
public static T InnerProduct(Vector4 left, Vector4 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 components = stackalloc Real[4];
@@ -220,7 +220,7 @@ public Real Norm()
return max * Real.Sqrt(partialSum);
}
- public Vector4 Normalize()
+ public readonly Vector4 Normalize()
{
var norm = Norm();
return new(X1 / norm, X2 / norm, X3 / norm, X4 / norm);
From 317e8c94553fb9d5e21d214e994651621e5ae3d4 Mon Sep 17 00:00:00 2001
From: Hamlet Tanyavong <34531738+HamletTanyavong@users.noreply.github.com>
Date: Thu, 9 Nov 2023 17:46:37 -0600
Subject: [PATCH 13/18] Update Mathematics.NET.Tests.csproj
---
tests/Mathematics.NET.Tests/Mathematics.NET.Tests.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/Mathematics.NET.Tests/Mathematics.NET.Tests.csproj b/tests/Mathematics.NET.Tests/Mathematics.NET.Tests.csproj
index 5829ef8a..87e2a518 100644
--- a/tests/Mathematics.NET.Tests/Mathematics.NET.Tests.csproj
+++ b/tests/Mathematics.NET.Tests/Mathematics.NET.Tests.csproj
@@ -11,7 +11,7 @@
-
+
From e7f3763e1316fc3537b4329ab4c731dcceb84271 Mon Sep 17 00:00:00 2001
From: Hamlet Tanyavong <34531738+HamletTanyavong@users.noreply.github.com>
Date: Thu, 9 Nov 2023 18:04:29 -0600
Subject: [PATCH 14/18] Update GradientTape.cs
- Rename some items
---
src/Mathematics.NET/AutoDiff/GradientTape.cs | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/Mathematics.NET/AutoDiff/GradientTape.cs b/src/Mathematics.NET/AutoDiff/GradientTape.cs
index 5eced7a6..9e6d44a3 100644
--- a/src/Mathematics.NET/AutoDiff/GradientTape.cs
+++ b/src/Mathematics.NET/AutoDiff/GradientTape.cs
@@ -155,19 +155,19 @@ public void ReverseAccumulation(out ReadOnlySpan gradients, double seed =
ref var start = ref MemoryMarshal.GetReference(nodes);
var length = nodes.Length;
- Span partialGradients = new Real[length];
- partialGradients[length - 1] = seed;
+ Span gradientSpan = new Real[length];
+ gradientSpan[length - 1] = seed;
for (int i = length - 1; i >= _variableCount; i--)
{
var node = Unsafe.Add(ref start, i);
- var partialGradient = partialGradients[i];
+ var gradient = gradientSpan[i];
- partialGradients[node.PX] += partialGradient * node.DX;
- partialGradients[node.PY] += partialGradient * node.DY;
+ gradientSpan[node.PX] += gradient * node.DX;
+ gradientSpan[node.PY] += gradient * node.DY;
}
- gradients = partialGradients[.._variableCount];
+ gradients = gradientSpan[.._variableCount];
}
//
From 26b81128411885e4b632ba05e4f3ffde147a1e4c Mon Sep 17 00:00:00 2001
From: Hamlet Tanyavong <34531738+HamletTanyavong@users.noreply.github.com>
Date: Thu, 9 Nov 2023 23:27:31 -0600
Subject: [PATCH 15/18] Update Assert.cs
- Use ReadOnlySpan2D
- Add method for asserting ReadOnlySpan equality
- Rename parameters
---
tests/Mathematics.NET.Tests/Assert.cs | 52 +++++++++++++++++++++++----
1 file changed, 46 insertions(+), 6 deletions(-)
diff --git a/tests/Mathematics.NET.Tests/Assert.cs b/tests/Mathematics.NET.Tests/Assert.cs
index e81483eb..c27a9b0d 100644
--- a/tests/Mathematics.NET.Tests/Assert.cs
+++ b/tests/Mathematics.NET.Tests/Assert.cs
@@ -29,20 +29,26 @@
namespace Mathematics.NET.Tests;
+/// Assert helpers for Mathemtics.NET
+/// A type that implements
public sealed class Assert
where T : IComplex
{
- public static void AreApproximatelyEqual(T expected, T actual, Real delta)
+ /// Assert that two values are approximately equal.
+ /// The expected value
+ /// The actual value
+ /// A margin of error
+ 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}}
@@ -50,7 +56,41 @@ public static void AreApproximatelyEqual(T expected, T actual, Real delta)
}
}
- public static void ElementsAreApproximatelyEqual(Span2D expected, Span2D actual, Real delta)
+ /// Assert that the elements in two read-only spans are approximately equal.
+ /// A read-only span of expected values
+ /// A read-only span of actual values
+ /// A margin of error
+ public static void ElementsAreApproximatelyEqual(ReadOnlySpan expected, ReadOnlySpan 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]}}
+ """);
+ }
+ }
+ }
+
+ /// Assert that the elements in two 2D, read-only spans are approximately equal.
+ /// A 2D, read-only span of expected values
+ /// A 2D, read-only span of actual values
+ /// A margin of error
+ public static void ElementsAreApproximatelyEqual(ReadOnlySpan2D expected, ReadOnlySpan2D actual, Real epsilon)
{
if (expected.Height != actual.Height || expected.Width != actual.Width)
{
@@ -66,10 +106,10 @@ public static void ElementsAreApproximatelyEqual(Span2D expected, Span2D 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]}}
From 6dde2562da0a079e789293d40f5d9974925add74 Mon Sep 17 00:00:00 2001
From: Hamlet Tanyavong <34531738+HamletTanyavong@users.noreply.github.com>
Date: Thu, 9 Nov 2023 23:51:30 -0600
Subject: [PATCH 16/18] Create GradientTapeTests.cs
---
.../AutoDiff/GradientTapeTests.cs | 561 ++++++++++++++++++
1 file changed, 561 insertions(+)
create mode 100644 tests/Mathematics.NET.Tests/AutoDiff/GradientTapeTests.cs
diff --git a/tests/Mathematics.NET.Tests/AutoDiff/GradientTapeTests.cs b/tests/Mathematics.NET.Tests/AutoDiff/GradientTapeTests.cs
new file mode 100644
index 00000000..477b8fdd
--- /dev/null
+++ b/tests/Mathematics.NET.Tests/AutoDiff/GradientTapeTests.cs
@@ -0,0 +1,561 @@
+//
+// Mathematics.NET
+// https://github.com/HamletTanyavong/Mathematics.NET
+//
+// MIT License
+//
+// Copyright (c) 2023 Hamlet Tanyavong
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+using System.Data;
+using Mathematics.NET.AutoDiff;
+
+namespace Mathematics.NET.Tests.AutoDiff;
+
+[TestClass]
+[TestCategory("AutoDiff"), TestCategory("Gradient Tape")]
+public sealed class GradientTapeTests
+{
+ [TestMethod]
+ [DataRow(0.123, -1.007651429146436)]
+ public void Acos_Variable_ReturnsGradient(double input, double expected)
+ {
+ GradientTape tape = new();
+
+ var actual = ComputeGradient(tape, tape.Acos, input);
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-15);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 1.396315794095838)]
+ public void Acosh_Variable_ReturnsGradient(double input, double expected)
+ {
+ GradientTape tape = new();
+
+ var actual = ComputeGradient(tape, tape.Acosh, input);
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-15);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 2.34, 1, 1)]
+ public void Add_TwoVariables_ReturnsGradients(double left, double right, double expectedLeft, double expectedRight)
+ {
+ GradientTape tape = new();
+ var expected = new Real[2] { expectedLeft, expectedRight };
+
+ var actual = ComputeGradients(tape, tape.Add, left, right);
+
+ Assert.ElementsAreApproximatelyEqual(expected, actual, 1e-16);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 2.34, 1)]
+ public void Add_RealAndVariable_ReturnsGradient(double left, double right, double expected)
+ {
+ GradientTape tape = new();
+ var x = tape.CreateVariable(right);
+ _ = tape.Add(left, x);
+ tape.ReverseAccumulation(out var gradients);
+
+ var actual = gradients[0];
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-16);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 2.34, 1)]
+ public void Add_VariableAndReal_ReturnsGradient(double left, double right, double expected)
+ {
+ GradientTape tape = new();
+ var x = tape.CreateVariable(left);
+ _ = tape.Add(x, right);
+ tape.ReverseAccumulation(out var gradients);
+
+ var actual = gradients[0];
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-16);
+ }
+
+ [TestMethod]
+ [DataRow(0.123, 1.007651429146436)]
+ public void Asin_Variable_ReturnsGradient(double input, double expected)
+ {
+ GradientTape tape = new();
+
+ var actual = ComputeGradient(tape, tape.Asin, input);
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-15);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 0.6308300845448597)]
+ public void Asinh_Variable_ReturnsGradient(double input, double expected)
+ {
+ GradientTape tape = new();
+
+ var actual = ComputeGradient(tape, tape.Asinh, input);
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-15);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 0.3979465955668749)]
+ public void Atan_Variable_ReturnsGradient(double input, double expected)
+ {
+ GradientTape tape = new();
+
+ var actual = ComputeGradient(tape, tape.Atan, input);
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-15);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 2.34, 0.334835801674179, -0.1760034342133505)]
+ public void Atan2_TwoVariables_ReturnsGradients(double left, double right, double expectedLeft, double expectedRight)
+ {
+ GradientTape tape = new();
+ var expected = new Real[2] { expectedLeft, expectedRight };
+
+ var actual = ComputeGradients(tape, tape.Atan2, left, right);
+
+ Assert.AreApproximatelyEqual(expectedLeft, actual[0], 1e-15);
+ Assert.AreApproximatelyEqual(expectedRight, actual[1], 1e-15);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, -1.94969779684149)]
+ public void Atanh_Variable_ReturnsGradient(double input, double expected)
+ {
+ GradientTape tape = new();
+
+ var actual = ComputeGradient(tape, tape.Atanh, input);
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-15);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 0.2903634877210767)]
+ public void Cbrt_Variable_ReturnsGradient(double input, double expected)
+ {
+ GradientTape tape = new();
+
+ var actual = ComputeGradient(tape, tape.Cbrt, input);
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-15);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, -0.942488801931697)]
+ public void Cos_Variable_ReturnsGradient(double input, double expected)
+ {
+ GradientTape tape = new();
+
+ var actual = ComputeGradient(tape, tape.Cos, input);
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-15);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 1.564468479304407)]
+ public void Cosh_Variable_ReturnsGradient(double input, double expected)
+ {
+ GradientTape tape = new();
+
+ var actual = ComputeGradient(tape, tape.Cosh, input);
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-15);
+ }
+
+ [TestMethod]
+ [DataRow(2)]
+ [DataRow(3)]
+ [DataRow(4)]
+ public void CreateVariable_Multiple_TracksCorrectNumberOfVariables(int amount)
+ {
+ GradientTape tape = new();
+ for (int i = 0; i < amount; i++)
+ {
+ _ = tape.CreateVariable(0);
+ }
+
+ var actual = tape.VariableCount;
+
+ Assert.AreEqual(amount, actual);
+ }
+
+ [TestMethod]
+ [DataRow(1.23)]
+ public void CreateVariable_WithSeedValue_ReturnsVariable(double value)
+ {
+ GradientTape tape = new();
+
+ var actual = tape.CreateVariable(value).Value;
+
+ Assert.AreEqual(value, actual);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 2.34, 0.4273504273504274, -0.2246329169406093)]
+ public void Divide_TwoVariables_ReturnsGradients(double left, double right, double expectedLeft, double expectedRight)
+ {
+ GradientTape tape = new();
+ var expected = new Real[2] { expectedLeft, expectedRight };
+
+ var actual = ComputeGradients(tape, tape.Divide, left, right);
+
+ Assert.AreApproximatelyEqual(expectedLeft, actual[0], 1e-15);
+ Assert.AreApproximatelyEqual(expectedRight, actual[1], 1e-15);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 2.34, -0.2246329169406093)]
+ public void Divide_RealAndVariable_ReturnsGradient(double left, double right, double expected)
+ {
+ GradientTape tape = new();
+ var x = tape.CreateVariable(right);
+ _ = tape.Divide(left, x);
+ tape.ReverseAccumulation(out var gradients);
+
+ var actual = gradients[0];
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-15);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 2.34, 0.4273504273504274)]
+ public void Divide_VariableAndReal_ReturnsGradient(double left, double right, double expected)
+ {
+ GradientTape tape = new();
+ var x = tape.CreateVariable(left);
+ _ = tape.Divide(x, right);
+ tape.ReverseAccumulation(out var gradients);
+
+ var actual = gradients[0];
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-15);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 3.421229536289673)]
+ public void Exp_Variable_ReturnsGradient(double input, double expected)
+ {
+ GradientTape tape = new();
+
+ var actual = ComputeGradient(tape, tape.Exp, input);
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-15);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 1.625894476644487)]
+ public void Exp2_Variable_ReturnsGradient(double input, double expected)
+ {
+ GradientTape tape = new();
+
+ var actual = ComputeGradient(tape, tape.Exp2, input);
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-15);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 39.10350518430174)]
+ public void Exp10_Variable_ReturnsGradient(double input, double expected)
+ {
+ GradientTape tape = new();
+
+ var actual = ComputeGradient(tape, tape.Exp10, input);
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-15);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 0.813008130081301)]
+ public void Ln_Variable_ReturnsGradient(double input, double expected)
+ {
+ GradientTape tape = new();
+
+ var actual = ComputeGradient(tape, tape.Ln, input);
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-15);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 2.34, 0.9563103467806, -0.1224030239537303)]
+ public void Log_TwoVariables_ReturnsGradients(double left, double right, double expectedLeft, double expectedRight)
+ {
+ GradientTape tape = new();
+ var expected = new Real[2] { expectedLeft, expectedRight };
+
+ var actual = ComputeGradients(tape, tape.Log, left, right);
+
+ Assert.AreApproximatelyEqual(expectedLeft, actual[0], 1e-15);
+ Assert.AreApproximatelyEqual(expectedRight, actual[1], 1e-15);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 1.172922797470702)]
+ public void Log2_Variable_ReturnsGradient(double input, double expected)
+ {
+ GradientTape tape = new();
+
+ var actual = ComputeGradient(tape, tape.Log2, input);
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-15);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 0.35308494463679)]
+ public void Log10_Variable_ReturnsGradient(double input, double expected)
+ {
+ GradientTape tape = new();
+
+ var actual = ComputeGradient(tape, tape.Log10, input);
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-15);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 2.34, 1, 0)]
+ public void Modulo_TwoVariables_ReturnsGradients(double left, double right, double expectedLeft, double expectedRight)
+ {
+ GradientTape tape = new();
+ var expected = new Real[2] { expectedLeft, expectedRight };
+
+ var actual = ComputeGradients(tape, tape.Modulo, left, right);
+
+ Assert.AreApproximatelyEqual(expectedLeft, actual[0], 1e-15);
+ Assert.AreApproximatelyEqual(expectedRight, actual[1], 1e-15);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 2.34, 0)]
+ public void Modulo_RealAndVariable_ReturnsGradient(double left, double right, double expected)
+ {
+ GradientTape tape = new();
+ var x = tape.CreateVariable(right);
+ _ = tape.Modulo(left, x);
+ tape.ReverseAccumulation(out var gradients);
+
+ var actual = gradients[0];
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-16);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 2.34, 1)]
+ public void Modulo_VariableAndReal_ReturnsGradient(double left, double right, double expected)
+ {
+ GradientTape tape = new();
+ var x = tape.CreateVariable(left);
+ _ = tape.Modulo(x, right);
+ tape.ReverseAccumulation(out var gradient);
+
+ var actual = gradient[0];
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-16);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 2.34, 2.34, 1.23)]
+ public void Multiply_TwoVariables_ReturnsGradients(double left, double right, double expectedLeft, double expectedRight)
+ {
+ GradientTape tape = new();
+ var expected = new Real[2] { expectedLeft, expectedRight };
+
+ var actual = ComputeGradients(tape, tape.Multiply, left, right);
+
+ Assert.AreApproximatelyEqual(expectedLeft, actual[0], 1e-16);
+ Assert.AreApproximatelyEqual(expectedRight, actual[1], 1e-16);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 2.34, 1.23)]
+ public void Multiply_RealAndVariable_ReturnsGradient(double left, double right, double expected)
+ {
+ GradientTape tape = new();
+ var x = tape.CreateVariable(right);
+ _ = tape.Multiply(left, x);
+ tape.ReverseAccumulation(out var gradients);
+
+ var actual = gradients[0];
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-16);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 2.34, 2.34)]
+ public void Multiply_VariableAndReal_ReturnsGradient(double left, double right, double expected)
+ {
+ GradientTape tape = new();
+ var x = tape.CreateVariable(left);
+ _ = tape.Multiply(x, right);
+ tape.ReverseAccumulation(out var gradients);
+
+ var actual = gradients[0];
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-16);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 2.34, 3.088081166620949, 0.3360299854573856)]
+ public void Pow_TwoVariables_ReturnsGradients(double left, double right, double expectedLeft, double expectedRight)
+ {
+ GradientTape tape = new();
+ var expected = new Real[2] { expectedLeft, expectedRight };
+
+ var actual = ComputeGradients(tape, tape.Pow, left, right);
+
+ Assert.AreApproximatelyEqual(expectedLeft, actual[0], 1e-15);
+ Assert.AreApproximatelyEqual(expectedRight, actual[1], 1e-15);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 2.34, 0.3795771135606888, -0.04130373687338086)]
+ public void Root_TwoVariables_ReturnsGradients(double left, double right, double expectedLeft, double expectedRight)
+ {
+ GradientTape tape = new();
+ var expected = new Real[2] { expectedLeft, expectedRight };
+
+ var actual = ComputeGradients(tape, tape.Root, left, right);
+
+ Assert.AreApproximatelyEqual(expectedLeft, actual[0], 1e-15);
+ Assert.AreApproximatelyEqual(expectedRight, actual[1], 1e-15);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 0.3342377271245026)]
+ public void Sin_Variable_ReturnsGradient(double input, double expected)
+ {
+ GradientTape tape = new();
+
+ var actual = ComputeGradient(tape, tape.Sin, input);
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-15);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 1.856761056985266)]
+ public void Sinh_Variable_ReturnsGradient(double input, double expected)
+ {
+ GradientTape tape = new();
+
+ var actual = ComputeGradient(tape, tape.Sinh, input);
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-15);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 0.4508348173337161)]
+ public void Sqrt_Variable_ReturnsGradient(double input, double expected)
+ {
+ GradientTape tape = new();
+
+ var actual = ComputeGradient(tape, tape.Sqrt, input);
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-15);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 2.34, 1, -1)]
+ public void Subtract_TwoVariables_ReturnsGradients(double left, double right, double expectedLeft, double expectedRight)
+ {
+ GradientTape tape = new();
+ var expected = new Real[2] { expectedLeft, expectedRight };
+
+ var actual = ComputeGradients(tape, tape.Subtract, left, right);
+
+ Assert.AreApproximatelyEqual(expectedLeft, actual[0], 1e-16);
+ Assert.AreApproximatelyEqual(expectedRight, actual[1], 1e-16);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 2.34, -1)]
+ public void Subtract_RealAndVariable_ReturnsGradients(double left, double right, double expected)
+ {
+ GradientTape tape = new();
+ var x = tape.CreateVariable(right);
+ _ = tape.Subtract(left, x);
+ tape.ReverseAccumulation(out var gradients);
+
+ var actual = gradients[0];
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-16);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 2.34, 1)]
+ public void Subtract_VariableAndReal_ReturnsGradients(double left, double right, double expected)
+ {
+ GradientTape tape = new();
+ var x = tape.CreateVariable(left);
+ _ = tape.Subtract(x, right);
+ tape.ReverseAccumulation(out var gradients);
+
+ var actual = gradients[0];
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-16);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 8.95136077522624)]
+ public void Tan_Variable_ReturnsGradient(double input, double expected)
+ {
+ GradientTape tape = new();
+
+ var actual = ComputeGradient(tape, tape.Tan, input);
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-15);
+ }
+
+ [TestMethod]
+ [DataRow(1.23, 0.2900600799721436)]
+ public void Tanh_Variable_ReturnsGradient(double input, double expected)
+ {
+ GradientTape tape = new();
+
+ var actual = ComputeGradient(tape, tape.Tanh, input);
+
+ Assert.AreApproximatelyEqual(expected, actual, 1e-15);
+ }
+
+ //
+ // Helpers
+ //
+
+ private static Real ComputeGradient(GradientTape tape, Func function, Real input)
+ {
+ var x = tape.CreateVariable(input);
+ _ = function(x);
+ tape.ReverseAccumulation(out var gradients);
+ return gradients[0];
+ }
+
+ private static Real[] ComputeGradients(GradientTape tape, Func function, Real left, Real right)
+ {
+ var x = tape.CreateVariable(left);
+ var y = tape.CreateVariable(right);
+ _ = function(x, y);
+ tape.ReverseAccumulation(out var gradients);
+ return gradients.ToArray();
+ }
+}
From 6373583a855c8509d3b7eb41321d1ef1c559fb4d Mon Sep 17 00:00:00 2001
From: Hamlet Tanyavong <34531738+HamletTanyavong@users.noreply.github.com>
Date: Fri, 10 Nov 2023 01:10:04 -0600
Subject: [PATCH 17/18] Fix Atan2
---
src/Mathematics.NET/AutoDiff/GradientTape.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Mathematics.NET/AutoDiff/GradientTape.cs b/src/Mathematics.NET/AutoDiff/GradientTape.cs
index 9e6d44a3..f8f94b48 100644
--- a/src/Mathematics.NET/AutoDiff/GradientTape.cs
+++ b/src/Mathematics.NET/AutoDiff/GradientTape.cs
@@ -496,7 +496,7 @@ public Variable Atan(Variable x)
public Variable Atan2(Variable y, Variable x)
{
var u = Real.One / (x.Value * x.Value + y.Value * y.Value);
- _nodes.Add(new(-x.Value * u, y.Value * u, y._index, x._index));
+ _nodes.Add(new(x.Value * u, -y.Value * u, y._index, x._index));
return new(_nodes.Count - 1, Real.Atan2(y.Value, x.Value));
}
From 71ae241e8bd8609bc138e748d74cfb1bf895fdf5 Mon Sep 17 00:00:00 2001
From: Hamlet Tanyavong <34531738+HamletTanyavong@users.noreply.github.com>
Date: Fri, 10 Nov 2023 01:13:58 -0600
Subject: [PATCH 18/18] Update Mathematics.NET.csproj
- Update version
---
src/Mathematics.NET/Mathematics.NET.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Mathematics.NET/Mathematics.NET.csproj b/src/Mathematics.NET/Mathematics.NET.csproj
index a4f3e7ad..82d918dc 100644
--- a/src/Mathematics.NET/Mathematics.NET.csproj
+++ b/src/Mathematics.NET/Mathematics.NET.csproj
@@ -7,7 +7,7 @@
enable
x64
Mathematics.NET
- 0.1.0-alpha.3
+ 0.1.0-alpha.4
mathematics.net.png
Hamlet Tanyavong
Mathematics.NET is a C# class library that provides tools for solving mathematical problems.