From 651d230b3fdac6946e0340d786102edefb343eb1 Mon Sep 17 00:00:00 2001 From: Michael Sharp Date: Fri, 15 Sep 2023 12:06:59 -0600 Subject: [PATCH 1/2] 6 more naive methods --- .../ref/System.Numerics.Tensors.cs | 48 ++++--- .../Numerics/Tensors/TensorPrimitives.cs | 133 ++++++++++++++++++ .../tests/TensorPrimitivesTests.cs | 118 ++++++++++++++++ 3 files changed, 278 insertions(+), 21 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 c657a43a054dd6..f227d662a65673 100644 --- a/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.cs +++ b/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.cs @@ -6,27 +6,33 @@ namespace System.Numerics.Tensors { - public static class TensorPrimitives + public static partial class TensorPrimitives { - public static void Add(System.ReadOnlySpan x, float y, System.Span destination) { throw null; } - public static void Add(System.ReadOnlySpan x, System.ReadOnlySpan y, System.Span destination) { throw null; } - public static void AddMultiply(System.ReadOnlySpan x, float y, System.ReadOnlySpan multiplier, System.Span destination) { throw null; } - public static void AddMultiply(System.ReadOnlySpan x, System.ReadOnlySpan y, float multiplier, System.Span destination) { throw null; } - public static void AddMultiply(System.ReadOnlySpan x, System.ReadOnlySpan y, System.ReadOnlySpan multiplier, System.Span destination) { throw null; } - public static void Cosh(System.ReadOnlySpan x, System.Span destination) { throw null; } - public static void Divide(System.ReadOnlySpan x, float y, System.Span destination) { throw null; } - public static void Divide(System.ReadOnlySpan x, System.ReadOnlySpan y, System.Span destination) { throw null; } - public static void Exp(System.ReadOnlySpan x, System.Span destination) { throw null; } - public static void Log(System.ReadOnlySpan x, System.Span destination) { throw null; } - public static void Multiply(System.ReadOnlySpan x, float y, System.Span destination) { throw null; } - public static void Multiply(System.ReadOnlySpan x, System.ReadOnlySpan y, System.Span destination) { throw null; } - public static void MultiplyAdd(System.ReadOnlySpan x, float y, System.ReadOnlySpan addend, System.Span destination) { throw null; } - public static void MultiplyAdd(System.ReadOnlySpan x, System.ReadOnlySpan y, float addend, System.Span destination) { throw null; } - public static void MultiplyAdd(System.ReadOnlySpan x, System.ReadOnlySpan y, System.ReadOnlySpan addend, System.Span destination) { throw null; } - public static void Negate(System.ReadOnlySpan x, System.Span destination) { throw null; } - public static void Subtract(System.ReadOnlySpan x, float y, System.Span destination) { throw null; } - public static void Subtract(System.ReadOnlySpan x, System.ReadOnlySpan y, System.Span destination) { throw null; } - public static void Sinh(System.ReadOnlySpan x, System.Span destination) { throw null; } - public static void Tanh(System.ReadOnlySpan x, System.Span destination) { throw null; } + 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) { } + public static void AddMultiply(System.ReadOnlySpan x, System.ReadOnlySpan y, float multiplier, System.Span destination) { } + public static void AddMultiply(System.ReadOnlySpan x, float y, System.ReadOnlySpan multiplier, System.Span destination) { } + public static void Cosh(System.ReadOnlySpan x, System.Span destination) { } + public static float CosineSimilarity(System.ReadOnlySpan x, System.ReadOnlySpan y) { throw null; } + public static float Distance(System.ReadOnlySpan x, System.ReadOnlySpan y) { throw null; } + public static void Divide(System.ReadOnlySpan x, System.ReadOnlySpan y, System.Span destination) { } + public static void Divide(System.ReadOnlySpan x, float y, System.Span destination) { } + public static float Dot(System.ReadOnlySpan x, System.ReadOnlySpan y) { throw null; } + public static void Exp(System.ReadOnlySpan x, System.Span destination) { } + public static void Log(System.ReadOnlySpan x, System.Span destination) { } + 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) { } + public static void MultiplyAdd(System.ReadOnlySpan x, System.ReadOnlySpan y, float addend, System.Span destination) { } + public static void MultiplyAdd(System.ReadOnlySpan x, float y, System.ReadOnlySpan addend, System.Span destination) { } + public static void Negate(System.ReadOnlySpan x, System.Span destination) { } + public static float Normalize(System.ReadOnlySpan x) { throw null; } + public static void Sigmoid(System.ReadOnlySpan x, System.Span destination) { } + public static void Sinh(System.ReadOnlySpan x, System.Span destination) { } + public static void SoftMax(System.ReadOnlySpan x, System.Span destination) { } + public static void Subtract(System.ReadOnlySpan x, System.ReadOnlySpan y, System.Span destination) { } + public static void Subtract(System.ReadOnlySpan x, float y, System.Span destination) { } + public static void Tanh(System.ReadOnlySpan x, 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 08bd9d362217e5..232d2a3ba417b7 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 @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.ComponentModel; + namespace System.Numerics.Tensors { /// Performs primitive tensor operations over spans of memory. @@ -253,5 +255,136 @@ public static void Tanh(ReadOnlySpan x, Span destination) destination[i] = MathF.Tanh(x[i]); } } + + /// Computes the cosine similarity between two non-zero vectors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The cosine similarity between the two vectors. + public static float CosineSimilarity(ReadOnlySpan x, ReadOnlySpan y) + { + if (x.Length != y.Length) + { + ThrowHelper.ThrowArgument_SpansMustHaveSameLength(); + } + + var dotprod = 0f; + var magx = 0f; + var magy = 0f; + + for (int i = 0; i < x.Length; i++) + { + dotprod += x[i] * y[i]; + magx += MathF.Pow(x[i], 2); + magy += MathF.Pow(y[i], 2); + } + + return dotprod / (MathF.Sqrt(magx) * MathF.Sqrt(magy)); + } + + /// + /// Compute the distance between two points in Euclidean space. + /// + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The Euclidean distance. + public static float Distance(ReadOnlySpan x, ReadOnlySpan y) + { + if (x.Length != y.Length) + { + ThrowHelper.ThrowArgument_SpansMustHaveSameLength(); + } + + var distance = 0f; + + for (int i = 0; i < x.Length; i++) + { + distance += MathF.Pow(x[i] - y[i], 2); + } + + return MathF.Sqrt(distance); + } + + /// + /// A mathematical operation that takes two vectors and returns a scalar. + /// + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The dot product. + public static float Dot(ReadOnlySpan x, ReadOnlySpan y) // BLAS1: dot + { + if (x.Length != y.Length) + { + ThrowHelper.ThrowArgument_SpansMustHaveSameLength(); + } + + var dotprod = 0f; + + for (int i = 0; i < x.Length; i++) + { + dotprod += x[i] * y[i]; + } + + return dotprod; + } + + /// + /// A mathematical operation that takes a vector and returns the L2 norm. + /// + /// The first tensor, represented as a span. + /// The L2 norm. + public static float Normalize(ReadOnlySpan x) // BLAS1: nrm2 + { + var magx = 0f; + + for (int i = 0; i < x.Length; i++) + { + magx += MathF.Pow(x[i], 2); + } + + return MathF.Sqrt(magx); + } + + /// + /// A function that takes a collection of real numbers and returns a probability distribution. + /// + /// The first tensor, represented as a span. + /// The destination tensor. + public static void SoftMax(ReadOnlySpan x, Span destination) + { + if (x.Length > destination.Length) + { + ThrowHelper.ThrowArgument_DestinationTooShort(); + } + + var expSum = 0f; + + for (int i = 0; i < x.Length; i++) + { + expSum += MathF.Pow((float)Math.E, x[i]); + } + + for (int i = 0; i < destination.Length; i++) + { + destination[i] = MathF.Exp(x[i]) / expSum; + } + } + + /// + /// A function that takes a real number and returns a value between 0 and 1. + /// + /// The first tensor, represented as a span. + /// The destination tensor. + public static void Sigmoid(ReadOnlySpan x, Span destination) + { + if (x.Length > destination.Length) + { + ThrowHelper.ThrowArgument_DestinationTooShort(); + } + + for (int i = 0; i < x.Length; i++) + { + destination[i] = 1f / (1 + MathF.Exp(-x[i])); + } + } } } diff --git a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs index 5a9912542a8c2f..6dd970020156aa 100644 --- a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs +++ b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs @@ -702,5 +702,123 @@ public static void TanhTensor_ThrowsForTooShortDestination(int tensorLength) AssertExtensions.Throws("destination", () => TensorPrimitives.Tanh(x, destination)); } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void CosineSimilarity_ThrowsForMismatchedLengths_x_y(int tensorLength) + { + float[] x = CreateAndFillTensor(tensorLength); + float[] y = CreateAndFillTensor(tensorLength - 1); + + Assert.Throws(() => TensorPrimitives.CosineSimilarity(x, y)); + } + + [Theory] + [InlineData(new float[] { 3, 2, 0, 5 }, new float[] { 1, 0, 0, 0 }, 0.49f)] + [InlineData(new float[] { 1, 1, 1, 1, 1, 0 }, new float[] { 1, 1, 1, 1, 0, 1 }, 0.80f)] + public static void CosineSimilarity(float[] x, float[] y, float expectedResult) + { + Assert.Equal(expectedResult, TensorPrimitives.CosineSimilarity(x, y), .01f); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Distance_ThrowsForMismatchedLengths_x_y(int tensorLength) + { + float[] x = CreateAndFillTensor(tensorLength); + float[] y = CreateAndFillTensor(tensorLength - 1); + + Assert.Throws(() => TensorPrimitives.Distance(x, y)); + } + + [Theory] + [InlineData(new float[] { 3, 2 }, new float[] { 4, 1 }, 1.4142f)] + [InlineData(new float[] { 0, 4 }, new float[] { 6, 2 }, 6.3245f)] + [InlineData(new float[] { 1, 2, 3 }, new float[] { 4, 5, 6 }, 5.1961f)] + [InlineData(new float[] { 5, 1, 6, 10 }, new float[] { 7, 2, 8, 4 }, 6.7082f)] + public static void Distance(float[] x, float[] y, float expectedResult) + { + Assert.Equal(expectedResult, TensorPrimitives.Distance(x, y), .001f); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Dot_ThrowsForMismatchedLengths_x_y(int tensorLength) + { + float[] x = CreateAndFillTensor(tensorLength); + float[] y = CreateAndFillTensor(tensorLength - 1); + + Assert.Throws(() => TensorPrimitives.Dot(x, y)); + } + + [Theory] + [InlineData(new float[] { 1, 3, -5 }, new float[] { 4, -2, -1 }, 3)] + [InlineData(new float[] { 1, 2, 3 }, new float[] { 4, 5, 6 }, 32)] + [InlineData(new float[] { 1, 2, 3, 10, 8 }, new float[] { 4, 5, 6, -2, 7 }, 68)] + public static void Dot(float[] x, float[] y, float expectedResult) + { + Assert.Equal(expectedResult, TensorPrimitives.Dot(x, y), .001f); + } + + [Theory] + [InlineData(new float[] { 1, 2, 3 }, 3.7416)] + [InlineData(new float[] { 3, 4 }, 5)] + [InlineData(new float[] { 3 }, 3)] + [InlineData(new float[] { 3, 4, 1, 2 }, 5.477)] + public static void Normalize(float[] x, float expectedResult) + { + Assert.Equal(expectedResult, TensorPrimitives.Normalize(x), .001f); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void SoftMax_ThrowsForTooShortDestination(int tensorLength) + { + float[] x = CreateAndFillTensor(tensorLength); + float[] destination = CreateTensor(tensorLength - 1); + + AssertExtensions.Throws("destination", () => TensorPrimitives.SoftMax(x, destination)); + } + + [Theory] + [InlineData(new float[] { 3, 1, .2f }, new float[] { 0.8360188f, 0.11314284f, 0.05083836f })] + [InlineData(new float[] { 3, 4, 1 }, new float[] { 0.2594f, 0.7052f, 0.0351f })] + [InlineData(new float[] { 5, 3 }, new float[] { 0.8807f, 0.1192f })] + [InlineData(new float[] { 4, 2, 1, 9 }, new float[] { 0.0066f, 9.04658e-4f, 3.32805e-4f, 0.9920f})] + public static void SoftMax(float[] x, float[] expectedResult) + { + var dest = new float[x.Length]; + TensorPrimitives.SoftMax(x, dest); + + for (int i = 0; i < dest.Length; i++) + { + Assert.Equal(expectedResult[i], dest[i], .001f); + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Sigmoid_ThrowsForTooShortDestination(int tensorLength) + { + float[] x = CreateAndFillTensor(tensorLength); + float[] destination = CreateTensor(tensorLength - 1); + + AssertExtensions.Throws("destination", () => TensorPrimitives.Sigmoid(x, destination)); + } + + [Theory] + [InlineData(new float[] { -5, -4.5f, -4 }, new float[] { 0.0066f, 0.0109f, 0.0179f })] + [InlineData(new float[] { 4.5f, 5 }, new float[] { 0.9890f, 0.9933f })] + [InlineData(new float[] { 0, -3, 3, .5f }, new float[] { 0.5f, 0.0474f, 0.9525f, 0.6224f })] + public static void Sigmoid(float[] x, float[] expectedResult) + { + var dest = new float[x.Length]; + TensorPrimitives.Sigmoid(x, dest); + + for (int i = 0; i < dest.Length; i++) + { + Assert.Equal(expectedResult[i], dest[i], .001f); + } + } } } From 75769c43048addf2d53d2d6c44a9fbef8693c597 Mon Sep 17 00:00:00 2001 From: Michael Sharp Date: Fri, 15 Sep 2023 16:02:48 -0600 Subject: [PATCH 2/2] updates from pr comments --- .../ref/System.Numerics.Tensors.cs | 2 +- .../src/Resources/Strings.resx | 5 +- .../Numerics/Tensors/TensorPrimitives.cs | 54 ++++++++++---- .../src/System/ThrowHelper.cs | 4 + .../tests/TensorPrimitivesTests.cs | 74 ++++++++++++++++++- 5 files changed, 118 insertions(+), 21 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 f227d662a65673..c0c15cd6bfa19f 100644 --- a/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.cs +++ b/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.cs @@ -20,6 +20,7 @@ public static void Divide(System.ReadOnlySpan x, System.ReadOnlySpan x, float y, System.Span destination) { } public static float Dot(System.ReadOnlySpan x, System.ReadOnlySpan y) { throw null; } public static void Exp(System.ReadOnlySpan x, System.Span destination) { } + public static float L2Normalize(System.ReadOnlySpan x) { throw null; } public static void Log(System.ReadOnlySpan x, System.Span destination) { } 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) { } @@ -27,7 +28,6 @@ public static void MultiplyAdd(System.ReadOnlySpan x, System.ReadOnlySpan public static void MultiplyAdd(System.ReadOnlySpan x, System.ReadOnlySpan y, float addend, System.Span destination) { } public static void MultiplyAdd(System.ReadOnlySpan x, float y, System.ReadOnlySpan addend, System.Span destination) { } public static void Negate(System.ReadOnlySpan x, System.Span destination) { } - public static float Normalize(System.ReadOnlySpan x) { throw null; } public static void Sigmoid(System.ReadOnlySpan x, System.Span destination) { } public static void Sinh(System.ReadOnlySpan x, System.Span destination) { } public static void SoftMax(System.ReadOnlySpan x, System.Span destination) { } diff --git a/src/libraries/System.Numerics.Tensors/src/Resources/Strings.resx b/src/libraries/System.Numerics.Tensors/src/Resources/Strings.resx index d70054a2411268..45f0d8fa17893a 100644 --- a/src/libraries/System.Numerics.Tensors/src/Resources/Strings.resx +++ b/src/libraries/System.Numerics.Tensors/src/Resources/Strings.resx @@ -120,7 +120,10 @@ Destination is too short. + + Input span arguments must not be empty. + Input span arguments must all have the same length. - + \ No newline at end of file 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 232d2a3ba417b7..87d41d1f9c0a57 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 @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.ComponentModel; - namespace System.Numerics.Tensors { /// Performs primitive tensor operations over spans of memory. @@ -260,22 +258,28 @@ public static void Tanh(ReadOnlySpan x, Span destination) /// The first tensor, represented as a span. /// The second tensor, represented as a span. /// The cosine similarity between the two vectors. + /// Length of '' must be same as length of ''. + /// '' and '' must not be empty. public static float CosineSimilarity(ReadOnlySpan x, ReadOnlySpan y) { if (x.Length != y.Length) { ThrowHelper.ThrowArgument_SpansMustHaveSameLength(); } + if (x.Length == 0 || y.Length == 0) + { + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } - var dotprod = 0f; - var magx = 0f; - var magy = 0f; + float dotprod = 0f; + float magx = 0f; + float magy = 0f; for (int i = 0; i < x.Length; i++) { dotprod += x[i] * y[i]; - magx += MathF.Pow(x[i], 2); - magy += MathF.Pow(y[i], 2); + magx += x[i] * x[i]; + magy += y[i] * y[i]; } return dotprod / (MathF.Sqrt(magx) * MathF.Sqrt(magy)); @@ -287,18 +291,25 @@ public static float CosineSimilarity(ReadOnlySpan x, ReadOnlySpan /// The first tensor, represented as a span. /// The second tensor, represented as a span. /// The Euclidean distance. + /// Length of '' must be same as length of ''. + /// '' and '' must not be empty. public static float Distance(ReadOnlySpan x, ReadOnlySpan y) { if (x.Length != y.Length) { ThrowHelper.ThrowArgument_SpansMustHaveSameLength(); } + if (x.Length == 0 || y.Length == 0) + { + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } - var distance = 0f; + float distance = 0f; for (int i = 0; i < x.Length; i++) { - distance += MathF.Pow(x[i] - y[i], 2); + float dist = x[i] - y[i]; + distance += dist * dist; } return MathF.Sqrt(distance); @@ -310,6 +321,7 @@ public static float Distance(ReadOnlySpan x, ReadOnlySpan y) /// The first tensor, represented as a span. /// The second tensor, represented as a span. /// The dot product. + /// Length of '' must be same as length of ''. public static float Dot(ReadOnlySpan x, ReadOnlySpan y) // BLAS1: dot { if (x.Length != y.Length) @@ -317,7 +329,7 @@ public static float Dot(ReadOnlySpan x, ReadOnlySpan y) // BLAS1: ThrowHelper.ThrowArgument_SpansMustHaveSameLength(); } - var dotprod = 0f; + float dotprod = 0f; for (int i = 0; i < x.Length; i++) { @@ -332,13 +344,13 @@ public static float Dot(ReadOnlySpan x, ReadOnlySpan y) // BLAS1: /// /// The first tensor, represented as a span. /// The L2 norm. - public static float Normalize(ReadOnlySpan x) // BLAS1: nrm2 + public static float L2Normalize(ReadOnlySpan x) // BLAS1: nrm2 { - var magx = 0f; + float magx = 0f; for (int i = 0; i < x.Length; i++) { - magx += MathF.Pow(x[i], 2); + magx += x[i] * x[i]; } return MathF.Sqrt(magx); @@ -349,21 +361,27 @@ public static float Normalize(ReadOnlySpan x) // BLAS1: nrm2 /// /// The first tensor, represented as a span. /// The destination tensor. + /// Destination is too short. + /// '' must not be empty. public static void SoftMax(ReadOnlySpan x, Span destination) { if (x.Length > destination.Length) { ThrowHelper.ThrowArgument_DestinationTooShort(); } + if (x.Length == 0) + { + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } - var expSum = 0f; + float expSum = 0f; for (int i = 0; i < x.Length; i++) { expSum += MathF.Pow((float)Math.E, x[i]); } - for (int i = 0; i < destination.Length; i++) + for (int i = 0; i < x.Length; i++) { destination[i] = MathF.Exp(x[i]) / expSum; } @@ -374,12 +392,18 @@ public static void SoftMax(ReadOnlySpan x, Span destination) /// /// The first tensor, represented as a span. /// The destination tensor. + /// Destination is too short. + /// '' must not be empty. public static void Sigmoid(ReadOnlySpan x, Span destination) { if (x.Length > destination.Length) { ThrowHelper.ThrowArgument_DestinationTooShort(); } + if (x.Length == 0) + { + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } for (int i = 0; i < x.Length; i++) { diff --git a/src/libraries/System.Numerics.Tensors/src/System/ThrowHelper.cs b/src/libraries/System.Numerics.Tensors/src/System/ThrowHelper.cs index cc8d423f5a4d00..902b27787e856c 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/ThrowHelper.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/ThrowHelper.cs @@ -14,5 +14,9 @@ public static void ThrowArgument_DestinationTooShort() => [DoesNotReturn] public static void ThrowArgument_SpansMustHaveSameLength() => throw new ArgumentException(SR.Argument_SpansMustHaveSameLength); + + [DoesNotReturn] + public static void ThrowArgument_SpansMustBeNonEmpty() => + throw new ArgumentException(SR.Argument_SpansMustBeNonEmpty); } } diff --git a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs index 6dd970020156aa..e8bb48a50f6b20 100644 --- a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs +++ b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs @@ -713,6 +713,15 @@ public static void CosineSimilarity_ThrowsForMismatchedLengths_x_y(int tensorLen Assert.Throws(() => TensorPrimitives.CosineSimilarity(x, y)); } + [Fact] + public static void CosineSimilarity_ThrowsForEmpty_x_y() + { + float[] x = []; + float[] y = []; + + Assert.Throws(() => TensorPrimitives.CosineSimilarity(x, y)); + } + [Theory] [InlineData(new float[] { 3, 2, 0, 5 }, new float[] { 1, 0, 0, 0 }, 0.49f)] [InlineData(new float[] { 1, 1, 1, 1, 1, 0 }, new float[] { 1, 1, 1, 1, 0, 1 }, 0.80f)] @@ -721,6 +730,15 @@ public static void CosineSimilarity(float[] x, float[] y, float expectedResult) Assert.Equal(expectedResult, TensorPrimitives.CosineSimilarity(x, y), .01f); } + [Fact] + public static void Distance_ThrowsForEmpty_x_y() + { + float[] x = []; + float[] y = []; + + Assert.Throws(() => TensorPrimitives.Distance(x, y)); + } + [Theory] [MemberData(nameof(TensorLengths))] public static void Distance_ThrowsForMismatchedLengths_x_y(int tensorLength) @@ -755,6 +773,7 @@ public static void Dot_ThrowsForMismatchedLengths_x_y(int tensorLength) [InlineData(new float[] { 1, 3, -5 }, new float[] { 4, -2, -1 }, 3)] [InlineData(new float[] { 1, 2, 3 }, new float[] { 4, 5, 6 }, 32)] [InlineData(new float[] { 1, 2, 3, 10, 8 }, new float[] { 4, 5, 6, -2, 7 }, 68)] + [InlineData(new float[] { }, new float[] { }, 0)] public static void Dot(float[] x, float[] y, float expectedResult) { Assert.Equal(expectedResult, TensorPrimitives.Dot(x, y), .001f); @@ -765,9 +784,10 @@ public static void Dot(float[] x, float[] y, float expectedResult) [InlineData(new float[] { 3, 4 }, 5)] [InlineData(new float[] { 3 }, 3)] [InlineData(new float[] { 3, 4, 1, 2 }, 5.477)] - public static void Normalize(float[] x, float expectedResult) + [InlineData(new float[] { }, 0f)] + public static void L2Normalize(float[] x, float expectedResult) { - Assert.Equal(expectedResult, TensorPrimitives.Normalize(x), .001f); + Assert.Equal(expectedResult, TensorPrimitives.L2Normalize(x), .001f); } [Theory] @@ -790,12 +810,35 @@ public static void SoftMax(float[] x, float[] expectedResult) var dest = new float[x.Length]; TensorPrimitives.SoftMax(x, dest); - for (int i = 0; i < dest.Length; i++) + for (int i = 0; i < x.Length; i++) + { + Assert.Equal(expectedResult[i], dest[i], .001f); + } + } + + [Fact] + public static void SoftMax_DestinationLongerThanSource() + { + var x = new float[] { 3, 1, .2f }; + var expectedResult = new float[] { 0.8360188f, 0.11314284f, 0.05083836f }; + var dest = new float[x.Length + 1]; + TensorPrimitives.SoftMax(x, dest); + + for (int i = 0; i < x.Length; i++) { Assert.Equal(expectedResult[i], dest[i], .001f); } } + [Fact] + public static void SoftMax_ThrowsForEmpty_x_y() + { + var x = new float[] { }; + var dest = new float[x.Length]; + + AssertExtensions.Throws(() => TensorPrimitives.SoftMax(x, dest)); + } + [Theory] [MemberData(nameof(TensorLengths))] public static void Sigmoid_ThrowsForTooShortDestination(int tensorLength) @@ -815,10 +858,33 @@ public static void Sigmoid(float[] x, float[] expectedResult) var dest = new float[x.Length]; TensorPrimitives.Sigmoid(x, dest); - for (int i = 0; i < dest.Length; i++) + for (int i = 0; i < x.Length; i++) + { + Assert.Equal(expectedResult[i], dest[i], .001f); + } + } + + [Fact] + public static void Sigmoid_DestinationLongerThanSource() + { + var x = new float[] { -5, -4.5f, -4 }; + var expectedResult = new float[] { 0.0066f, 0.0109f, 0.0179f }; + var dest = new float[x.Length + 1]; + TensorPrimitives.Sigmoid(x, dest); + + for (int i = 0; i < x.Length; i++) { Assert.Equal(expectedResult[i], dest[i], .001f); } } + + [Fact] + public static void Sigmoid_ThrowsForEmpty_x_y() + { + var x = new float[] { }; + var dest = new float[x.Length]; + + AssertExtensions.Throws(() => TensorPrimitives.Sigmoid(x, dest)); + } } }