From adab22870a5c2aa69163dfbb53afa5e059a0ed73 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 20 Sep 2023 22:05:42 -0400 Subject: [PATCH 1/2] Address feedback and fix some TensorPrimitives issues - Added a few APIs based on initial feedback: Abs (vectorized), Log2, and element-wise Max/Min{Magnitude} - Renamed L2Normalize to Norm - Fixed semantics of Min/MaxMagnitude to return original value rather than the absolute value - Renamed a few helper types for consistency - Added tests --- .../ref/System.Numerics.Tensors.cs | 8 +- .../Numerics/Tensors/TensorPrimitives.cs | 147 ++++++++- .../Tensors/TensorPrimitives.netcore.cs | 12 +- .../Tensors/TensorPrimitives.netstandard.cs | 12 +- .../tests/TensorPrimitivesTests.cs | 285 +++++++++++++++--- 5 files changed, 410 insertions(+), 54 deletions(-) diff --git a/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.cs b/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.cs index 50eaa00160e5c..99bd4703574e5 100644 --- a/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.cs +++ b/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.cs @@ -8,6 +8,7 @@ namespace System.Numerics.Tensors { public static partial class TensorPrimitives { + public static void Abs(System.ReadOnlySpan x, System.Span destination) { } public static void Add(System.ReadOnlySpan x, System.ReadOnlySpan y, System.Span destination) { } public static void Add(System.ReadOnlySpan x, float y, System.Span destination) { } public static void AddMultiply(System.ReadOnlySpan x, System.ReadOnlySpan y, System.ReadOnlySpan multiplier, System.Span destination) { } @@ -24,12 +25,17 @@ public static void Exp(System.ReadOnlySpan x, System.Span destinat public static int IndexOfMaxMagnitude(System.ReadOnlySpan x) { throw null; } public static int IndexOfMin(System.ReadOnlySpan x) { throw null; } public static int IndexOfMinMagnitude(System.ReadOnlySpan x) { throw null; } - public static float L2Normalize(System.ReadOnlySpan x) { throw null; } + public static float Norm(System.ReadOnlySpan x) { throw null; } public static void Log(System.ReadOnlySpan x, System.Span destination) { } + public static void Log2(System.ReadOnlySpan x, System.Span destination) { } public static float Max(System.ReadOnlySpan x) { throw null; } + public static void Max(System.ReadOnlySpan x, System.ReadOnlySpan y, System.Span destination) { throw null; } public static float MaxMagnitude(System.ReadOnlySpan x) { throw null; } + public static void MaxMagnitude(System.ReadOnlySpan x, System.ReadOnlySpan y, System.Span destination) { throw null; } public static float Min(System.ReadOnlySpan x) { throw null; } + public static void Min(System.ReadOnlySpan x, System.ReadOnlySpan y, System.Span destination) { throw null; } public static float MinMagnitude(System.ReadOnlySpan x) { throw null; } + public static void MinMagnitude(System.ReadOnlySpan x, System.ReadOnlySpan y, System.Span destination) { throw null; } public static void Multiply(System.ReadOnlySpan x, System.ReadOnlySpan y, System.Span destination) { } public static void Multiply(System.ReadOnlySpan x, float y, System.Span destination) { } public static void MultiplyAdd(System.ReadOnlySpan x, System.ReadOnlySpan y, System.ReadOnlySpan addend, System.Span destination) { } diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.cs index a400095b08ad3..d28d4bacafdb8 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.cs @@ -93,6 +93,14 @@ public static void Divide(ReadOnlySpan x, float y, Span destinatio public static void Negate(ReadOnlySpan x, Span destination) => InvokeSpanIntoSpan(x, destination); + /// Computes the element-wise result of: MathF.Abs(). + /// The tensor, represented as a span. + /// The destination tensor, represented as a span. + /// Destination is too short. + /// This method effectively does [i] = MathF.Abs([i]). + public static void Abs(ReadOnlySpan x, Span destination) => + InvokeSpanIntoSpan(x, destination); + /// Computes the element-wise result of: ( + ) * . /// The first tensor, represented as a span. /// The second tensor, represented as a span. @@ -200,6 +208,24 @@ public static void Log(ReadOnlySpan x, Span destination) } } + /// Computes the element-wise result of: log2(). + /// The tensor, represented as a span. + /// The destination tensor, represented as a span. + /// Destination is too short. + /// This method effectively does [i] = .Log2([i]). + public static void Log2(ReadOnlySpan x, Span destination) + { + if (x.Length > destination.Length) + { + ThrowHelper.ThrowArgument_DestinationTooShort(); + } + + for (int i = 0; i < x.Length; i++) + { + destination[i] = Log2(x[i]); + } + } + /// Computes the element-wise result of: cosh(). /// The tensor, represented as a span. /// The destination tensor, represented as a span. @@ -318,9 +344,9 @@ public static float Dot(ReadOnlySpan x, ReadOnlySpan y) // BLAS1: /// /// The first tensor, represented as a span. /// The L2 norm. - public static float L2Normalize(ReadOnlySpan x) // BLAS1: nrm2 + public static float Norm(ReadOnlySpan x) // BLAS1: nrm2 { - return MathF.Sqrt(Aggregate(0f, x)); + return MathF.Sqrt(Aggregate(0f, x)); } /// @@ -345,7 +371,7 @@ public static void SoftMax(ReadOnlySpan x, Span destination) for (int i = 0; i < x.Length; i++) { - expSum += MathF.Pow((float)Math.E, x[i]); + expSum += MathF.Exp(x[i]); } for (int i = 0; i < x.Length; i++) @@ -421,6 +447,31 @@ public static float Max(ReadOnlySpan x) return result; } + /// Computes the element-wise result of: MathF.Max(, ). + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The destination tensor, represented as a span. + /// Length of '' must be same as length of ''. + /// Destination is too short. + /// This method effectively does [i] = MathF.Max([i], [i]). + public static void Max(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + if (x.Length != y.Length) + { + ThrowHelper.ThrowArgument_SpansMustHaveSameLength(); + } + + if (x.Length > destination.Length) + { + ThrowHelper.ThrowArgument_DestinationTooShort(); + } + + for (int i = 0; i < x.Length; i++) + { + destination[i] = MathF.Max(x[i], y[i]); + } + } + /// Computes the minimum element in . /// The tensor, represented as a span. /// The minimum element in . @@ -464,6 +515,31 @@ public static float Min(ReadOnlySpan x) return result; } + /// Computes the element-wise result of: MathF.Min(, ). + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The destination tensor, represented as a span. + /// Length of '' must be same as length of ''. + /// Destination is too short. + /// This method effectively does [i] = MathF.Min([i], [i]). + public static void Min(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + if (x.Length != y.Length) + { + ThrowHelper.ThrowArgument_SpansMustHaveSameLength(); + } + + if (x.Length > destination.Length) + { + ThrowHelper.ThrowArgument_DestinationTooShort(); + } + + for (int i = 0; i < x.Length; i++) + { + destination[i] = MathF.Min(x[i], y[i]); + } + } + /// Computes the maximum magnitude of any element in . /// The tensor, represented as a span. /// The maximum magnitude of any element in . @@ -508,7 +584,32 @@ public static float MaxMagnitude(ReadOnlySpan x) } } - return resultMag; + return result; + } + + /// Computes the element-wise result of: MathF.MaxMagnitude(, ). + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The destination tensor, represented as a span. + /// Length of '' must be same as length of ''. + /// Destination is too short. + /// This method effectively does [i] = MathF.MaxMagnitude([i], [i]). + public static void MaxMagnitude(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + if (x.Length != y.Length) + { + ThrowHelper.ThrowArgument_SpansMustHaveSameLength(); + } + + if (x.Length > destination.Length) + { + ThrowHelper.ThrowArgument_DestinationTooShort(); + } + + for (int i = 0; i < x.Length; i++) + { + destination[i] = MaxMagnitude(x[i], y[i]); + } } /// Computes the minimum magnitude of any element in . @@ -522,6 +623,7 @@ public static float MinMagnitude(ReadOnlySpan x) ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); } + float result = float.PositiveInfinity; float resultMag = float.PositiveInfinity; for (int i = 0; i < x.Length; i++) @@ -543,16 +645,43 @@ public static float MinMagnitude(ReadOnlySpan x) if (currentMag < resultMag) { + result = current; resultMag = currentMag; } } else if (IsNegative(current)) { + result = current; resultMag = currentMag; } } - return resultMag; + return result; + } + + /// Computes the element-wise result of: MathF.MinMagnitude(, ). + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The destination tensor, represented as a span. + /// Length of '' must be same as length of ''. + /// Destination is too short. + /// This method effectively does [i] = MathF.MinMagnitude([i], [i]). + public static void MinMagnitude(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + if (x.Length != y.Length) + { + ThrowHelper.ThrowArgument_SpansMustHaveSameLength(); + } + + if (x.Length > destination.Length) + { + ThrowHelper.ThrowArgument_DestinationTooShort(); + } + + for (int i = 0; i < x.Length; i++) + { + destination[i] = MinMagnitude(x[i], y[i]); + } } /// Computes the index of the maximum element in . @@ -744,14 +873,14 @@ public static unsafe int IndexOfMinMagnitude(ReadOnlySpan x) /// The tensor, represented as a span. /// The result of adding all elements in , or zero if is empty. public static float Sum(ReadOnlySpan x) => - Aggregate(0f, x); + Aggregate(0f, x); /// Computes the sum of the squares of every element in . /// The tensor, represented as a span. /// The result of adding every element in multiplied by itself, or zero if is empty. /// This method effectively does .Sum(.Multiply(, )). public static float SumOfSquares(ReadOnlySpan x) => - Aggregate(0f, x); + Aggregate(0f, x); /// Computes the sum of the absolute values of every element in . /// The tensor, represented as a span. @@ -761,7 +890,7 @@ public static float SumOfSquares(ReadOnlySpan x) => /// This method corresponds to the asum method defined by BLAS1. /// public static float SumOfMagnitudes(ReadOnlySpan x) => - Aggregate(0f, x); + Aggregate(0f, x); /// Computes the product of all elements in . /// The tensor, represented as a span. @@ -774,7 +903,7 @@ public static float Product(ReadOnlySpan x) ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); } - return Aggregate(1.0f, x); + return Aggregate(1.0f, x); } /// Computes the product of the element-wise result of: + . diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netcore.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netcore.cs index ae5af404ac1af..8eb1769d5eaee 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netcore.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netcore.cs @@ -51,6 +51,12 @@ public static void ConvertToSingle(ReadOnlySpan source, Span destin private static bool IsNegative(float f) => float.IsNegative(f); + private static float MaxMagnitude(float x, float y) => MathF.MaxMagnitude(x, y); + + private static float MinMagnitude(float x, float y) => MathF.MinMagnitude(x, y); + + private static float Log2(float x) => MathF.Log2(x); + private static float CosineSimilarityCore(ReadOnlySpan x, ReadOnlySpan y) { // Compute the same as: @@ -1184,7 +1190,7 @@ public static float Invoke(Vector512 x) #endif } - private readonly struct LoadIdentity : IUnaryOperator + private readonly struct IdentityOperator : IUnaryOperator { public static float Invoke(float x) => x; public static Vector128 Invoke(Vector128 x) => x; @@ -1194,7 +1200,7 @@ public static float Invoke(Vector512 x) #endif } - private readonly struct LoadSquared : IUnaryOperator + private readonly struct SquaredOperator : IUnaryOperator { public static float Invoke(float x) => x * x; public static Vector128 Invoke(Vector128 x) => x * x; @@ -1204,7 +1210,7 @@ public static float Invoke(Vector512 x) #endif } - private readonly struct LoadAbsolute : IUnaryOperator + private readonly struct AbsoluteOperator : IUnaryOperator { public static float Invoke(float x) => MathF.Abs(x); diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netstandard.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netstandard.cs index ba3fc69bab527..ed8b3aea0d560 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netstandard.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netstandard.cs @@ -10,6 +10,12 @@ public static partial class TensorPrimitives { private static unsafe bool IsNegative(float f) => *(int*)&f < 0; + private static float MaxMagnitude(float x, float y) => MathF.Abs(x) >= MathF.Abs(y) ? x : y; + + private static float MinMagnitude(float x, float y) => MathF.Abs(x) < MathF.Abs(y) ? x : y; + + private static float Log2(float x) => MathF.Log(x, 2); + private static float CosineSimilarityCore(ReadOnlySpan x, ReadOnlySpan y) { // Compute the same as: @@ -551,19 +557,19 @@ public Vector Invoke(Vector x, Vector y) public Vector Invoke(Vector x, Vector y, Vector z) => (x * y) + z; } - private readonly struct LoadIdentity : IUnaryOperator + private readonly struct IdentityOperator : IUnaryOperator { public float Invoke(float x) => x; public Vector Invoke(Vector x) => x; } - private readonly struct LoadSquared : IUnaryOperator + private readonly struct SquaredOperator : IUnaryOperator { public float Invoke(float x) => x * x; public Vector Invoke(Vector x) => x * x; } - private readonly struct LoadAbsolute : IUnaryOperator + private readonly struct AbsoluteOperator : IUnaryOperator { public float Invoke(float x) => MathF.Abs(x); diff --git a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs index 181d152e6ae97..7b94c47bd715a 100644 --- a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs +++ b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs @@ -15,6 +15,9 @@ public static partial class TensorPrimitivesTests { private const double Tolerance = 0.0001; + public static IEnumerable TensorLengthsIncluding0 => + TensorLengths.Concat(new object[][] { [0] }); + public static IEnumerable TensorLengths => from length in Enumerable.Range(1, 128) select new object[] { length }; @@ -45,7 +48,7 @@ private static float NextSingle() } [Theory] - [MemberData(nameof(TensorLengths))] + [MemberData(nameof(TensorLengthsIncluding0))] public static void AddTwoTensors(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); @@ -83,7 +86,7 @@ public static void AddTwoTensors_ThrowsForTooShortDestination(int tensorLength) } [Theory] - [MemberData(nameof(TensorLengths))] + [MemberData(nameof(TensorLengthsIncluding0))] public static void AddTensorAndScalar(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); @@ -110,7 +113,7 @@ public static void AddTensorAndScalar_ThrowsForTooShortDestination(int tensorLen } [Theory] - [MemberData(nameof(TensorLengths))] + [MemberData(nameof(TensorLengthsIncluding0))] public static void SubtractTwoTensors(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); @@ -148,7 +151,7 @@ public static void SubtractTwoTensors_ThrowsForTooShortDestination(int tensorLen } [Theory] - [MemberData(nameof(TensorLengths))] + [MemberData(nameof(TensorLengthsIncluding0))] public static void SubtractTensorAndScalar(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); @@ -175,7 +178,7 @@ public static void SubtractTensorAndScalar_ThrowsForTooShortDestination(int tens } [Theory] - [MemberData(nameof(TensorLengths))] + [MemberData(nameof(TensorLengthsIncluding0))] public static void MultiplyTwoTensors(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); @@ -213,7 +216,7 @@ public static void MultiplyTwoTensors_ThrowsForTooShortDestination(int tensorLen } [Theory] - [MemberData(nameof(TensorLengths))] + [MemberData(nameof(TensorLengthsIncluding0))] public static void MultiplyTensorAndScalar(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); @@ -240,7 +243,7 @@ public static void MultiplyTensorAndScalar_ThrowsForTooShortDestination(int tens } [Theory] - [MemberData(nameof(TensorLengths))] + [MemberData(nameof(TensorLengthsIncluding0))] public static void DivideTwoTensors(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); @@ -278,7 +281,7 @@ public static void DivideTwoTensors_ThrowsForTooShortDestination(int tensorLengt } [Theory] - [MemberData(nameof(TensorLengths))] + [MemberData(nameof(TensorLengthsIncluding0))] public static void DivideTensorAndScalar(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); @@ -305,7 +308,7 @@ public static void DivideTensorAndScalar_ThrowsForTooShortDestination(int tensor } [Theory] - [MemberData(nameof(TensorLengths))] + [MemberData(nameof(TensorLengthsIncluding0))] public static void NegateTensor(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); @@ -330,7 +333,7 @@ public static void NegateTensor_ThrowsForTooShortDestination(int tensorLength) } [Theory] - [MemberData(nameof(TensorLengths))] + [MemberData(nameof(TensorLengthsIncluding0))] public static void AddTwoTensorsAndMultiplyWithThirdTensor(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); @@ -383,7 +386,7 @@ public static void AddTwoTensorsAndMultiplyWithThirdTensor_ThrowsForTooShortDest } [Theory] - [MemberData(nameof(TensorLengths))] + [MemberData(nameof(TensorLengthsIncluding0))] public static void AddTwoTensorsAndMultiplyWithScalar(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); @@ -424,7 +427,7 @@ public static void AddTwoTensorsAndMultiplyWithScalar_ThrowsForTooShortDestinati } [Theory] - [MemberData(nameof(TensorLengths))] + [MemberData(nameof(TensorLengthsIncluding0))] public static void AddTensorAndScalarAndMultiplyWithTensor(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); @@ -465,7 +468,7 @@ public static void AddTensorAndScalarAndMultiplyWithTensor_ThrowsForTooShortDest } [Theory] - [MemberData(nameof(TensorLengths))] + [MemberData(nameof(TensorLengthsIncluding0))] public static void MultiplyTwoTensorsAndAddWithThirdTensor(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); @@ -518,7 +521,7 @@ public static void MultiplyTwoTensorsAndAddWithThirdTensor_ThrowsForTooShortDest } [Theory] - [MemberData(nameof(TensorLengths))] + [MemberData(nameof(TensorLengthsIncluding0))] public static void MultiplyTwoTensorsAndAddWithScalar(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); @@ -547,7 +550,7 @@ public static void MultiplyTwoTensorsAndAddWithScalar_ThrowsForTooShortDestinati } [Theory] - [MemberData(nameof(TensorLengths))] + [MemberData(nameof(TensorLengthsIncluding0))] public static void MultiplyTensorAndScalarAndAddWithTensor(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); @@ -576,7 +579,7 @@ public static void MultiplyTensorAndScalarAndAddWithTensor_ThrowsForTooShortDest } [Theory] - [MemberData(nameof(TensorLengths))] + [MemberData(nameof(TensorLengthsIncluding0))] public static void ExpTensor(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); @@ -601,7 +604,7 @@ public static void ExpTensor_ThrowsForTooShortDestination(int tensorLength) } [Theory] - [MemberData(nameof(TensorLengths))] + [MemberData(nameof(TensorLengthsIncluding0))] public static void LogTensor(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); @@ -625,8 +628,33 @@ public static void LogTensor_ThrowsForTooShortDestination(int tensorLength) AssertExtensions.Throws("destination", () => TensorPrimitives.Log(x, destination)); } + [Theory] + [MemberData(nameof(TensorLengthsIncluding0))] + public static void Log2(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); + + TensorPrimitives.Log2(x, destination); + + for (int i = 0; i < tensorLength; i++) + { + Assert.Equal(MathF.Log(x[i], 2), destination[i], Tolerance); + } + } + [Theory] [MemberData(nameof(TensorLengths))] + public static void Log2_ThrowsForTooShortDestination(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); + + AssertExtensions.Throws("destination", () => TensorPrimitives.Log2(x, destination)); + } + + [Theory] + [MemberData(nameof(TensorLengthsIncluding0))] public static void CoshTensor(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); @@ -651,7 +679,7 @@ public static void CoshTensor_ThrowsForTooShortDestination(int tensorLength) } [Theory] - [MemberData(nameof(TensorLengths))] + [MemberData(nameof(TensorLengthsIncluding0))] public static void SinhTensor(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); @@ -676,7 +704,7 @@ public static void SinhTensor_ThrowsForTooShortDestination(int tensorLength) } [Theory] - [MemberData(nameof(TensorLengths))] + [MemberData(nameof(TensorLengthsIncluding0))] public static void TanhTensor(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); @@ -809,7 +837,7 @@ public static void Dot_KnownValues(float[] x, float[] y, float expectedResult) } [Theory] - [MemberData(nameof(TensorLengths))] + [MemberData(nameof(TensorLengthsIncluding0))] public static void Dot(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); @@ -830,14 +858,14 @@ public static void Dot(int tensorLength) [InlineData(new float[] { 3 }, 3)] [InlineData(new float[] { 3, 4, 1, 2 }, 5.477226)] [InlineData(new float[] { }, 0f)] - public static void L2Normalize_KnownValues(float[] x, float expectedResult) + public static void Norm_KnownValues(float[] x, float expectedResult) { - Assert.Equal(expectedResult, TensorPrimitives.L2Normalize(x), Tolerance); + Assert.Equal(expectedResult, TensorPrimitives.Norm(x), Tolerance); } [Theory] - [MemberData(nameof(TensorLengths))] - public static void L2Normalize(int tensorLength) + [MemberData(nameof(TensorLengthsIncluding0))] + public static void Norm(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); @@ -847,7 +875,7 @@ public static void L2Normalize(int tensorLength) sumOfSquares += x[i] * x[i]; } - Assert.Equal(Math.Sqrt(sumOfSquares), TensorPrimitives.L2Normalize(x), Tolerance); + Assert.Equal(Math.Sqrt(sumOfSquares), TensorPrimitives.Norm(x), Tolerance); } [Theory] @@ -1160,6 +1188,44 @@ public static void Max_Negative0LesserThanPositive0() Assert.Equal(1, TensorPrimitives.Max([-1, -0f, 1])); } + [Theory] + [MemberData(nameof(TensorLengthsIncluding0))] + public static void Max_TwoTensors(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); + + TensorPrimitives.Max(x, y, destination); + + for (int i = 0; i < tensorLength; i++) + { + Assert.Equal(MathF.Max(x[i], y[i]), destination[i], Tolerance); + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Max_TwoTensors_ThrowsForMismatchedLengths(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength); + + Assert.Throws(() => TensorPrimitives.Max(x, y, destination)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Max_TwoTensors_ThrowsForTooShortDestination(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); + + AssertExtensions.Throws("destination", () => TensorPrimitives.Max(x, y, destination)); + } + [Fact] public static void MaxMagnitude_ThrowsForEmpty() { @@ -1172,14 +1238,16 @@ public static void MaxMagnitude(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); - Assert.Equal(Enumerable.Max(MemoryMarshal.ToEnumerable(x.Memory), MathF.Abs), TensorPrimitives.MaxMagnitude(x)); - - float max = 0; - foreach (float f in x.Span) + int index = 0; + for (int i = 0; i < x.Length; i++) { - max = Math.Max(max, MathF.Abs(f)); + if (MathF.Abs(x[i]) >= MathF.Abs(x[index])) + { + index = i; + } } - Assert.Equal(max, TensorPrimitives.MaxMagnitude(x)); + + Assert.Equal(x[index], TensorPrimitives.MaxMagnitude(x)); } [Theory] @@ -1199,12 +1267,50 @@ public static void MaxMagnitude_Negative0LesserThanPositive0() { Assert.Equal(+0f, TensorPrimitives.MaxMagnitude([-0f, +0f])); Assert.Equal(+0f, TensorPrimitives.MaxMagnitude([+0f, -0f])); - Assert.Equal(1, TensorPrimitives.MaxMagnitude([-1, -0f])); + Assert.Equal(-1, TensorPrimitives.MaxMagnitude([-1, -0f])); Assert.Equal(1, TensorPrimitives.MaxMagnitude([-1, -0f, 1])); Assert.Equal(0f, TensorPrimitives.MaxMagnitude([-0f, -0f, -0f, -0f, -0f, 0f])); Assert.Equal(1, TensorPrimitives.MaxMagnitude([-0f, -0f, -0f, -0f, -1, -0f, 0f, 1])); } + [Theory] + [MemberData(nameof(TensorLengthsIncluding0))] + public static void MaxMagnitude_TwoTensors(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); + + TensorPrimitives.MaxMagnitude(x, y, destination); + + for (int i = 0; i < tensorLength; i++) + { + Assert.Equal(MathF.Abs(x[i]) >= MathF.Abs(y[i]) ? x[i] : y[i], destination[i], Tolerance); + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void MaxMagnitude_TwoTensors_ThrowsForMismatchedLengths(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength); + + Assert.Throws(() => TensorPrimitives.MaxMagnitude(x, y, destination)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void MaxMagnitude_TwoTensors_ThrowsForTooShortDestination(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); + + AssertExtensions.Throws("destination", () => TensorPrimitives.MaxMagnitude(x, y, destination)); + } + [Fact] public static void Min_ThrowsForEmpty() { @@ -1248,6 +1354,44 @@ public static void Min_Negative0LesserThanPositive0() Assert.Equal(-1, TensorPrimitives.Min([-1, -0f, 1])); } + [Theory] + [MemberData(nameof(TensorLengthsIncluding0))] + public static void Min_TwoTensors(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); + + TensorPrimitives.Min(x, y, destination); + + for (int i = 0; i < tensorLength; i++) + { + Assert.Equal(MathF.Min(x[i], y[i]), destination[i], Tolerance); + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Min_TwoTensors_ThrowsForMismatchedLengths(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength); + + Assert.Throws(() => TensorPrimitives.Min(x, y, destination)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Min_TwoTensors_ThrowsForTooShortDestination(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); + + AssertExtensions.Throws("destination", () => TensorPrimitives.Min(x, y, destination)); + } + [Fact] public static void MinMagnitude_ThrowsForEmpty() { @@ -1260,14 +1404,16 @@ public static void MinMagnitude(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); - Assert.Equal(Enumerable.Min(MemoryMarshal.ToEnumerable(x.Memory), MathF.Abs), TensorPrimitives.MinMagnitude(x)); - - float min = float.PositiveInfinity; - foreach (float f in x.Span) + int index = 0; + for (int i = 0; i < x.Length; i++) { - min = Math.Min(min, MathF.Abs(f)); + if (MathF.Abs(x[i]) < MathF.Abs(x[index])) + { + index = i; + } } - Assert.Equal(min, TensorPrimitives.MinMagnitude(x)); + + Assert.Equal(x[index], TensorPrimitives.MinMagnitude(x)); } [Theory] @@ -1291,6 +1437,44 @@ public static void MinMagnitude_Negative0LesserThanPositive0() Assert.Equal(0, TensorPrimitives.MinMagnitude([-1, -0f, 1])); } + [Theory] + [MemberData(nameof(TensorLengthsIncluding0))] + public static void MinMagnitude_TwoTensors(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); + + TensorPrimitives.MinMagnitude(x, y, destination); + + for (int i = 0; i < tensorLength; i++) + { + Assert.Equal(MathF.Abs(x[i]) < MathF.Abs(y[i]) ? x[i] : y[i], destination[i]); + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void MinMagnitude_TwoTensors_ThrowsForMismatchedLengths(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength); + + Assert.Throws(() => TensorPrimitives.MinMagnitude(x, y, destination)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void MinMagnitude_TwoTensors_ThrowsForTooShortDestination(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); + + AssertExtensions.Throws("destination", () => TensorPrimitives.MinMagnitude(x, y, destination)); + } + [Fact] public static void Product_ThrowsForEmpty() { @@ -1480,5 +1664,30 @@ public static void SumOfMagnitudes_KnownValues() Assert.Equal(6, TensorPrimitives.SumOfMagnitudes([-3, 0, 3])); Assert.Equal(float.NaN, TensorPrimitives.SumOfMagnitudes([-3, float.NaN, 3])); } + + [Theory] + [MemberData(nameof(TensorLengthsIncluding0))] + public static void Abs(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); + + TensorPrimitives.Abs(x, destination); + + for (int i = 0; i < x.Length; i++) + { + Assert.Equal(MathF.Abs(x[i]), destination[i], Tolerance); + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Abs_ThrowsForTooShortDestination(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); + + AssertExtensions.Throws("destination", () => TensorPrimitives.Abs(x, destination)); + } } } From c7f53114f9f318ad9edfc3d6503bb666638d76e8 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Thu, 21 Sep 2023 12:26:09 -0400 Subject: [PATCH 2/2] Add a few more uses of Tolerance --- .../System.Numerics.Tensors/tests/TensorPrimitivesTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs index 7b94c47bd715a..777ab49609856 100644 --- a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs +++ b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs @@ -1247,7 +1247,7 @@ public static void MaxMagnitude(int tensorLength) } } - Assert.Equal(x[index], TensorPrimitives.MaxMagnitude(x)); + Assert.Equal(x[index], TensorPrimitives.MaxMagnitude(x), Tolerance); } [Theory] @@ -1413,7 +1413,7 @@ public static void MinMagnitude(int tensorLength) } } - Assert.Equal(x[index], TensorPrimitives.MinMagnitude(x)); + Assert.Equal(x[index], TensorPrimitives.MinMagnitude(x), Tolerance); } [Theory] @@ -1449,7 +1449,7 @@ public static void MinMagnitude_TwoTensors(int tensorLength) for (int i = 0; i < tensorLength; i++) { - Assert.Equal(MathF.Abs(x[i]) < MathF.Abs(y[i]) ? x[i] : y[i], destination[i]); + Assert.Equal(MathF.Abs(x[i]) < MathF.Abs(y[i]) ? x[i] : y[i], destination[i], Tolerance); } }