diff --git a/src/coreclr/jit/utils.cpp b/src/coreclr/jit/utils.cpp index 245f84c7f6234..19bdb0fadaa8c 100644 --- a/src/coreclr/jit/utils.cpp +++ b/src/coreclr/jit/utils.cpp @@ -2734,7 +2734,7 @@ float FloatingPointUtils::maximumNumber(float x, float y) // // It propagates NaN inputs back to the caller and // otherwise returns the lesser of the inputs. It -// treats +0 as lesser than -0 as per the specification. +// treats +0 as greater than -0 as per the specification. // // Arguments: // val1 - left operand @@ -2763,7 +2763,7 @@ double FloatingPointUtils::minimum(double val1, double val2) // // It propagates NaN inputs back to the caller and // otherwise returns the input with a lesser magnitude. -// It treats +0 as lesser than -0 as per the specification. +// It treats +0 as greater than -0 as per the specification. // // Arguments: // x - left operand @@ -2856,7 +2856,7 @@ double FloatingPointUtils::minimumNumber(double x, double y) // // It propagates NaN inputs back to the caller and // otherwise returns the lesser of the inputs. It -// treats +0 as lesser than -0 as per the specification. +// treats +0 as greater than -0 as per the specification. // // Arguments: // val1 - left operand @@ -2885,7 +2885,7 @@ float FloatingPointUtils::minimum(float val1, float val2) // // It propagates NaN inputs back to the caller and // otherwise returns the input with a lesser magnitude. -// It treats +0 as lesser than -0 as per the specification. +// It treats +0 as greater than -0 as per the specification. // // Arguments: // x - left operand diff --git a/src/libraries/Common/tests/TestUtilities/System/Buffers/BoundedMemory.Unix.cs b/src/libraries/Common/tests/TestUtilities/System/Buffers/BoundedMemory.Unix.cs index b127a2a72491e..c8197b0055092 100644 --- a/src/libraries/Common/tests/TestUtilities/System/Buffers/BoundedMemory.Unix.cs +++ b/src/libraries/Common/tests/TestUtilities/System/Buffers/BoundedMemory.Unix.cs @@ -30,6 +30,8 @@ public UnixImplementation(int elementCount) public override bool IsReadonly => false; + public override int Length => _elementCount; + public override Memory Memory => _memoryManager.Memory; public override Span Span @@ -83,10 +85,7 @@ protected override void Dispose(bool disposing) // no-op; the handle will be disposed separately } - public override Span GetSpan() - { - throw new NotImplementedException(); - } + public override Span GetSpan() => _impl.Span; public override MemoryHandle Pin(int elementIndex) { diff --git a/src/libraries/Common/tests/TestUtilities/System/Buffers/BoundedMemory.Windows.cs b/src/libraries/Common/tests/TestUtilities/System/Buffers/BoundedMemory.Windows.cs index 7b1cbbafc72db..96f40d61492e5 100644 --- a/src/libraries/Common/tests/TestUtilities/System/Buffers/BoundedMemory.Windows.cs +++ b/src/libraries/Common/tests/TestUtilities/System/Buffers/BoundedMemory.Windows.cs @@ -81,6 +81,8 @@ internal WindowsImplementation(VirtualAllocHandle handle, int byteOffsetIntoHand public override bool IsReadonly => (Protection != VirtualAllocProtection.PAGE_READWRITE); + public override int Length => _elementCount; + internal VirtualAllocProtection Protection { get @@ -189,10 +191,7 @@ protected override void Dispose(bool disposing) // no-op; the handle will be disposed separately } - public override Span GetSpan() - { - throw new NotImplementedException(); - } + public override Span GetSpan() => _impl.Span; public override MemoryHandle Pin(int elementIndex) { diff --git a/src/libraries/Common/tests/TestUtilities/System/Buffers/BoundedMemory.cs b/src/libraries/Common/tests/TestUtilities/System/Buffers/BoundedMemory.cs index d70b30721fdbd..81d359cc80e92 100644 --- a/src/libraries/Common/tests/TestUtilities/System/Buffers/BoundedMemory.cs +++ b/src/libraries/Common/tests/TestUtilities/System/Buffers/BoundedMemory.cs @@ -14,6 +14,9 @@ public abstract class BoundedMemory : IDisposable where T : unmanaged /// public abstract bool IsReadonly { get; } + /// Gets the length of the instance. + public abstract int Length { get; } + /// /// Gets the which represents this native memory. /// This instance must be kept alive while working with the . @@ -44,5 +47,23 @@ public abstract class BoundedMemory : IDisposable where T : unmanaged /// OS does not support marking the memory block as read+write. /// public abstract void MakeWriteable(); + + /// + /// Gets the which represents this native memory. + /// This instance must be kept alive while working with the . + /// + public static implicit operator Span(BoundedMemory boundedMemory) => boundedMemory.Span; + + /// + /// Gets the which represents this native memory. + /// This instance must be kept alive while working with the . + /// + public static implicit operator ReadOnlySpan(BoundedMemory boundedMemory) => boundedMemory.Span; + + /// + /// Gets a reference to the element at the specified index. + /// This instance must be kept alive while working with the reference. + /// + public ref T this[int index] => ref Span[index]; } } 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 c0c15cd6bfa19..50eaa00160e5c 100644 --- a/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.cs +++ b/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.cs @@ -20,19 +20,33 @@ 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 int IndexOfMax(System.ReadOnlySpan x) { throw null; } + 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 void Log(System.ReadOnlySpan x, System.Span destination) { } + public static float Max(System.ReadOnlySpan x) { throw null; } + public static float MaxMagnitude(System.ReadOnlySpan x) { throw null; } + public static float Min(System.ReadOnlySpan x) { throw null; } + public static float MinMagnitude(System.ReadOnlySpan x) { 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) { } 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 Product(System.ReadOnlySpan x) { throw null; } + public static float ProductOfDifferences(System.ReadOnlySpan x, System.ReadOnlySpan y) { throw null; } + public static float ProductOfSums(System.ReadOnlySpan x, System.ReadOnlySpan y) { 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 float Sum(System.ReadOnlySpan x) { throw null; } + public static float SumOfMagnitudes(System.ReadOnlySpan x) { throw null; } + public static float SumOfSquares(System.ReadOnlySpan x) { throw null; } public static void Tanh(System.ReadOnlySpan x, System.Span destination) { } } } diff --git a/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.csproj b/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.csproj index 8d1d484b33ecd..89bdef6ea38ed 100644 --- a/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.csproj +++ b/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.csproj @@ -8,6 +8,10 @@ + + + + diff --git a/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.netcore.cs b/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.netcore.cs new file mode 100644 index 0000000000000..1cde4351546b2 --- /dev/null +++ b/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.netcore.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// ------------------------------------------------------------------------------ +// Changes to this file must follow the https://aka.ms/api-review process. +// ------------------------------------------------------------------------------ + +namespace System.Numerics.Tensors +{ + public static partial class TensorPrimitives + { + public static void ConvertToHalf(System.ReadOnlySpan source, System.Span destination) { throw null; } + public static void ConvertToSingle(System.ReadOnlySpan source, System.Span destination) { throw null; } + } +} 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 87d41d1f9c0a5..f9191108f1d67 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 @@ -262,13 +262,13 @@ public static void Tanh(ReadOnlySpan x, Span destination) /// '' and '' must not be empty. public static float CosineSimilarity(ReadOnlySpan x, ReadOnlySpan y) { - if (x.Length != y.Length) + if (x.IsEmpty) { - ThrowHelper.ThrowArgument_SpansMustHaveSameLength(); + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); } - if (x.Length == 0 || y.Length == 0) + if (x.Length != y.Length) { - ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + ThrowHelper.ThrowArgument_SpansMustHaveSameLength(); } float dotprod = 0f; @@ -295,24 +295,16 @@ public static float CosineSimilarity(ReadOnlySpan x, ReadOnlySpan /// '' 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) + if (x.IsEmpty) { ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); } - - float distance = 0f; - - for (int i = 0; i < x.Length; i++) + if (x.Length != y.Length) { - float dist = x[i] - y[i]; - distance += dist * dist; + ThrowHelper.ThrowArgument_SpansMustHaveSameLength(); } - return MathF.Sqrt(distance); + return MathF.Sqrt(Aggregate(0f, x, y)); } /// @@ -329,14 +321,7 @@ public static float Dot(ReadOnlySpan x, ReadOnlySpan y) // BLAS1: ThrowHelper.ThrowArgument_SpansMustHaveSameLength(); } - float dotprod = 0f; - - for (int i = 0; i < x.Length; i++) - { - dotprod += x[i] * y[i]; - } - - return dotprod; + return Aggregate(0f, x, y); } /// @@ -346,14 +331,7 @@ public static float Dot(ReadOnlySpan x, ReadOnlySpan y) // BLAS1: /// The L2 norm. public static float L2Normalize(ReadOnlySpan x) // BLAS1: nrm2 { - float magx = 0f; - - for (int i = 0; i < x.Length; i++) - { - magx += x[i] * x[i]; - } - - return MathF.Sqrt(magx); + return MathF.Sqrt(Aggregate(0f, x)); } /// @@ -365,13 +343,13 @@ public static float L2Normalize(ReadOnlySpan x) // BLAS1: nrm2 /// '' must not be empty. public static void SoftMax(ReadOnlySpan x, Span destination) { - if (x.Length > destination.Length) + if (x.IsEmpty) { - ThrowHelper.ThrowArgument_DestinationTooShort(); + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); } - if (x.Length == 0) + if (x.Length > destination.Length) { - ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + ThrowHelper.ThrowArgument_DestinationTooShort(); } float expSum = 0f; @@ -396,19 +374,462 @@ public static void SoftMax(ReadOnlySpan x, Span destination) /// '' must not be empty. public static void Sigmoid(ReadOnlySpan x, Span destination) { + if (x.IsEmpty) + { + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } if (x.Length > destination.Length) { ThrowHelper.ThrowArgument_DestinationTooShort(); } - if (x.Length == 0) + + for (int i = 0; i < x.Length; i++) + { + destination[i] = 1f / (1 + MathF.Exp(-x[i])); + } + } + + /// Computes the maximum element in . + /// The tensor, represented as a span. + /// The maximum element in . + /// Length of '' must be greater than zero. + public static float Max(ReadOnlySpan x) + { + if (x.IsEmpty) { ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); } + float result = float.NegativeInfinity; + for (int i = 0; i < x.Length; i++) { - destination[i] = 1f / (1 + MathF.Exp(-x[i])); + // This matches the IEEE 754:2019 `maximum` function. + // It propagates NaN inputs back to the caller and + // otherwise returns the greater of the inputs. + // It treats +0 as greater than -0 as per the specification. + + float current = x[i]; + + if (current != result) + { + if (float.IsNaN(current)) + { + return current; + } + + if (result < current) + { + result = current; + } + } + else if (IsNegative(result)) + { + result = current; + } + } + + return result; + } + + /// Computes the minimum element in . + /// The tensor, represented as a span. + /// The minimum element in . + /// Length of '' must be greater than zero. + public static float Min(ReadOnlySpan x) + { + if (x.IsEmpty) + { + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } + + float result = float.PositiveInfinity; + + for (int i = 0; i < x.Length; i++) + { + // This matches the IEEE 754:2019 `minimum` function + // It propagates NaN inputs back to the caller and + // otherwise returns the lesser of the inputs. + // It treats +0 as greater than -0 as per the specification. + + float current = x[i]; + + if (current != result) + { + if (float.IsNaN(current)) + { + return current; + } + + if (current < result) + { + result = current; + } + } + else if (IsNegative(current)) + { + result = current; + } + } + + return result; + } + + /// Computes the maximum magnitude of any element in . + /// The tensor, represented as a span. + /// The maximum magnitude of any element in . + /// Length of '' must be greater than zero. + public static float MaxMagnitude(ReadOnlySpan x) + { + if (x.IsEmpty) + { + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } + + float result = float.NegativeInfinity; + float resultMag = float.NegativeInfinity; + + for (int i = 0; i < x.Length; i++) + { + // This matches the IEEE 754:2019 `maximumMagnitude` function. + // It propagates NaN inputs back to the caller and + // otherwise returns the input with a greater magnitude. + // It treats +0 as greater than -0 as per the specification. + + float current = x[i]; + float currentMag = Math.Abs(current); + + if (currentMag != resultMag) + { + if (float.IsNaN(currentMag)) + { + return currentMag; + } + + if (resultMag < currentMag) + { + result = current; + resultMag = currentMag; + } + } + else if (IsNegative(result)) + { + result = current; + resultMag = currentMag; + } + } + + return resultMag; + } + + /// Computes the minimum magnitude of any element in . + /// The tensor, represented as a span. + /// The minimum magnitude of any element in . + /// Length of '' must be greater than zero. + public static float MinMagnitude(ReadOnlySpan x) + { + if (x.IsEmpty) + { + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); } + + float resultMag = float.PositiveInfinity; + + for (int i = 0; i < x.Length; i++) + { + // This matches the IEEE 754:2019 `minimumMagnitude` function. + // It propagates NaN inputs back to the caller and + // otherwise returns the input with a lesser magnitude. + // It treats +0 as greater than -0 as per the specification. + + float current = x[i]; + float currentMag = Math.Abs(current); + + if (currentMag != resultMag) + { + if (float.IsNaN(currentMag)) + { + return currentMag; + } + + if (currentMag < resultMag) + { + resultMag = currentMag; + } + } + else if (IsNegative(current)) + { + resultMag = currentMag; + } + } + + return resultMag; + } + + /// Computes the index of the maximum element in . + /// The tensor, represented as a span. + /// The index of the maximum element in , or -1 if is empty. + public static unsafe int IndexOfMax(ReadOnlySpan x) + { + int result = -1; + + if (!x.IsEmpty) + { + float max = float.NegativeInfinity; + + for (int i = 0; i < x.Length; i++) + { + // This matches the IEEE 754:2019 `maximum` function. + // It propagates NaN inputs back to the caller and + // otherwise returns the greater of the inputs. + // It treats +0 as greater than -0 as per the specification. + + float current = x[i]; + + if (current != max) + { + if (float.IsNaN(current)) + { + return i; + } + + if (max < current) + { + result = i; + max = current; + } + } + else if (IsNegative(max) && !IsNegative(current)) + { + result = i; + max = current; + } + } + } + + return result; + } + + /// Computes the index of the minimum element in . + /// The tensor, represented as a span. + /// The index of the minimum element in , or -1 if is empty. + public static unsafe int IndexOfMin(ReadOnlySpan x) + { + int result = -1; + + if (!x.IsEmpty) + { + float min = float.PositiveInfinity; + + for (int i = 0; i < x.Length; i++) + { + // This matches the IEEE 754:2019 `minimum` function. + // It propagates NaN inputs back to the caller and + // otherwise returns the lesser of the inputs. + // It treats +0 as greater than -0 as per the specification. + + float current = x[i]; + + if (current != min) + { + if (float.IsNaN(current)) + { + return i; + } + + if (current < min) + { + result = i; + min = current; + } + } + else if (IsNegative(current) && !IsNegative(min)) + { + result = i; + min = current; + } + } + } + + return result; + } + + /// Computes the index of the element in with the maximum magnitude. + /// The tensor, represented as a span. + /// The index of the element with the maximum magnitude, or -1 if is empty. + /// This method corresponds to the iamax method defined by BLAS1. + public static unsafe int IndexOfMaxMagnitude(ReadOnlySpan x) + { + int result = -1; + + if (!x.IsEmpty) + { + float max = float.NegativeInfinity; + float maxMag = float.NegativeInfinity; + + for (int i = 0; i < x.Length; i++) + { + // This matches the IEEE 754:2019 `maximumMagnitude` function. + // It propagates NaN inputs back to the caller and + // otherwise returns the input with a greater magnitude. + // It treats +0 as greater than -0 as per the specification. + + float current = x[i]; + float currentMag = Math.Abs(current); + + if (currentMag != maxMag) + { + if (float.IsNaN(currentMag)) + { + return i; + } + + if (maxMag < currentMag) + { + result = i; + max = current; + maxMag = currentMag; + } + } + else if (IsNegative(max) && !IsNegative(current)) + { + result = i; + max = current; + maxMag = currentMag; + } + } + } + + return result; + } + + /// Computes the index of the element in with the minimum magnitude. + /// The tensor, represented as a span. + /// The index of the element with the minimum magnitude, or -1 if is empty. + public static unsafe int IndexOfMinMagnitude(ReadOnlySpan x) + { + int result = -1; + + if (!x.IsEmpty) + { + float min = float.PositiveInfinity; + float minMag = float.PositiveInfinity; + + for (int i = 0; i < x.Length; i++) + { + // This matches the IEEE 754:2019 `minimumMagnitude` function + // It propagates NaN inputs back to the caller and + // otherwise returns the input with a lesser magnitude. + // It treats +0 as greater than -0 as per the specification. + + float current = x[i]; + float currentMag = Math.Abs(current); + + if (currentMag != minMag) + { + if (float.IsNaN(currentMag)) + { + return i; + } + + if (currentMag < minMag) + { + result = i; + min = current; + minMag = currentMag; + } + } + else if (IsNegative(current) && !IsNegative(min)) + { + result = i; + min = current; + minMag = currentMag; + } + } + } + + return result; + } + + /// Computes the sum of all elements in . + /// 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); + + /// 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); + + /// Computes the sum of the absolute values of every element in . + /// The tensor, represented as a span. + /// The result of adding the absolute value of every element in , or zero if is empty. + /// + /// This method effectively does .Sum(.Abs()). + /// This method corresponds to the asum method defined by BLAS1. + /// + public static float SumOfMagnitudes(ReadOnlySpan x) => + Aggregate(0f, x); + + /// Computes the product of all elements in . + /// The tensor, represented as a span. + /// The result of multiplying all elements in . + /// Length of '' must be greater than zero. + public static float Product(ReadOnlySpan x) + { + if (x.IsEmpty) + { + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } + + return Aggregate(1.0f, x); + } + + /// Computes the product of the element-wise result of: + . + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The result of multiplying the element-wise additions of the elements in each tensor. + /// Length of both input spans must be greater than zero. + /// and must have the same length. + /// This method effectively does .Product(.Add(, )). + public static float ProductOfSums(ReadOnlySpan x, ReadOnlySpan y) + { + if (x.IsEmpty) + { + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } + + if (x.Length != y.Length) + { + ThrowHelper.ThrowArgument_SpansMustHaveSameLength(); + } + + return Aggregate(1.0f, x, y); + } + + /// Computes the product of the element-wise result of: - . + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The result of multiplying the element-wise subtraction of the elements in the second tensor from the first tensor. + /// Length of both input spans must be greater than zero. + /// and must have the same length. + /// This method effectively does .Product(.Subtract(, )). + public static float ProductOfDifferences(ReadOnlySpan x, ReadOnlySpan y) + { + if (x.IsEmpty) + { + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } + + if (x.Length != y.Length) + { + ThrowHelper.ThrowArgument_SpansMustHaveSameLength(); + } + + return Aggregate(1.0f, x, y); } } } 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 1233f54901c80..4f40aa31494c9 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 @@ -9,6 +9,336 @@ namespace System.Numerics.Tensors { public static partial class TensorPrimitives { + /// + /// Copies to , converting each + /// value to its nearest representable half-precision floating-point value. + /// + /// The source span from which to copy values. + /// The destination span into which the converted values should be written. + /// Destination is too short. + public static void ConvertToHalf(ReadOnlySpan source, Span destination) + { + if (source.Length > destination.Length) + { + ThrowHelper.ThrowArgument_DestinationTooShort(); + } + + for (int i = 0; i < source.Length; i++) + { + destination[i] = (Half)source[i]; + } + } + + /// + /// Copies to , converting each half-precision + /// floating-point value to its nearest representable value. + /// + /// The source span from which to copy values. + /// The destination span into which the converted values should be written. + /// Destination is too short. + public static void ConvertToSingle(ReadOnlySpan source, Span destination) + { + if (source.Length > destination.Length) + { + ThrowHelper.ThrowArgument_DestinationTooShort(); + } + + for (int i = 0; i < source.Length; i++) + { + destination[i] = (float)source[i]; + } + } + + private static bool IsNegative(float f) => float.IsNegative(f); + + private static float CosineSimilarityCore(ReadOnlySpan x, ReadOnlySpan y) + { + // Compute the same as: + // TensorPrimitives.Dot(x, y) / (Math.Sqrt(TensorPrimitives.SumOfSquares(x)) * Math.Sqrt(TensorPrimitives.SumOfSquares(y))) + // but only looping over each span once. + + float dotProduct = 0f; + float xSumOfSquares = 0f; + float ySumOfSquares = 0f; + + int i = 0; + +#if NET8_0_OR_GREATER + if (Vector512.IsHardwareAccelerated && x.Length >= Vector512.Count) + { + ref float xRef = ref MemoryMarshal.GetReference(x); + ref float yRef = ref MemoryMarshal.GetReference(y); + + Vector512 dotProductVector = Vector512.Zero; + Vector512 xSumOfSquaresVector = Vector512.Zero; + Vector512 ySumOfSquaresVector = Vector512.Zero; + + // Process vectors, summing their dot products and squares, as long as there's a vector's worth remaining. + int oneVectorFromEnd = x.Length - Vector512.Count; + do + { + Vector512 xVec = Vector512.LoadUnsafe(ref xRef, (uint)i); + Vector512 yVec = Vector512.LoadUnsafe(ref yRef, (uint)i); + + dotProductVector += xVec * yVec; + xSumOfSquaresVector += xVec * xVec; + ySumOfSquaresVector += yVec * yVec; + + i += Vector512.Count; + } + while (i <= oneVectorFromEnd); + + // Sum the vector lanes into the scalar result. + dotProduct += Vector512.Sum(dotProductVector); + xSumOfSquares += Vector512.Sum(xSumOfSquaresVector); + ySumOfSquares += Vector512.Sum(ySumOfSquaresVector); + } + else +#endif + if (Vector256.IsHardwareAccelerated && x.Length >= Vector256.Count) + { + ref float xRef = ref MemoryMarshal.GetReference(x); + ref float yRef = ref MemoryMarshal.GetReference(y); + + Vector256 dotProductVector = Vector256.Zero; + Vector256 xSumOfSquaresVector = Vector256.Zero; + Vector256 ySumOfSquaresVector = Vector256.Zero; + + // Process vectors, summing their dot products and squares, as long as there's a vector's worth remaining. + int oneVectorFromEnd = x.Length - Vector256.Count; + do + { + Vector256 xVec = Vector256.LoadUnsafe(ref xRef, (uint)i); + Vector256 yVec = Vector256.LoadUnsafe(ref yRef, (uint)i); + + dotProductVector += xVec * yVec; + xSumOfSquaresVector += xVec * xVec; + ySumOfSquaresVector += yVec * yVec; + + i += Vector256.Count; + } + while (i <= oneVectorFromEnd); + + // Sum the vector lanes into the scalar result. + dotProduct += Vector256.Sum(dotProductVector); + xSumOfSquares += Vector256.Sum(xSumOfSquaresVector); + ySumOfSquares += Vector256.Sum(ySumOfSquaresVector); + } + else if (Vector128.IsHardwareAccelerated && x.Length >= Vector128.Count) + { + ref float xRef = ref MemoryMarshal.GetReference(x); + ref float yRef = ref MemoryMarshal.GetReference(y); + + Vector128 dotProductVector = Vector128.Zero; + Vector128 xSumOfSquaresVector = Vector128.Zero; + Vector128 ySumOfSquaresVector = Vector128.Zero; + + // Process vectors, summing their dot products and squares, as long as there's a vector's worth remaining. + int oneVectorFromEnd = x.Length - Vector128.Count; + do + { + Vector128 xVec = Vector128.LoadUnsafe(ref xRef, (uint)i); + Vector128 yVec = Vector128.LoadUnsafe(ref yRef, (uint)i); + + dotProductVector += xVec * yVec; + xSumOfSquaresVector += xVec * xVec; + ySumOfSquaresVector += yVec * yVec; + + i += Vector128.Count; + } + while (i <= oneVectorFromEnd); + + // Sum the vector lanes into the scalar result. + dotProduct += Vector128.Sum(dotProductVector); + xSumOfSquares += Vector128.Sum(xSumOfSquaresVector); + ySumOfSquares += Vector128.Sum(ySumOfSquaresVector); + } + + // Process any remaining elements past the last vector. + for (; (uint)i < (uint)x.Length; i++) + { + dotProduct += x[i] * y[i]; + xSumOfSquares += x[i] * x[i]; + ySumOfSquares += y[i] * y[i]; + } + + // Sum(X * Y) / (|X| * |Y|) + return dotProduct / (MathF.Sqrt(xSumOfSquares) * MathF.Sqrt(ySumOfSquares)); + } + + private static float Aggregate( + float identityValue, ReadOnlySpan x) + where TLoad : IUnaryOperator + where TAggregate : IBinaryOperator + { + // Initialize the result to the identity value + float result = identityValue; + int i = 0; + +#if NET8_0_OR_GREATER + if (Vector512.IsHardwareAccelerated && x.Length >= Vector512.Count * 2) + { + ref float xRef = ref MemoryMarshal.GetReference(x); + + // Load the first vector as the initial set of results + Vector512 resultVector = TLoad.Invoke(Vector512.LoadUnsafe(ref xRef, 0)); + int oneVectorFromEnd = x.Length - Vector512.Count; + + // Aggregate additional vectors into the result as long as there's at + // least one full vector left to process. + i = Vector512.Count; + do + { + resultVector = TAggregate.Invoke(resultVector, TLoad.Invoke(Vector512.LoadUnsafe(ref xRef, (uint)i))); + i += Vector512.Count; + } + while (i <= oneVectorFromEnd); + + // Aggregate the lanes in the vector back into the scalar result + result = TAggregate.Invoke(result, TAggregate.Invoke(resultVector)); + } + else +#endif + if (Vector256.IsHardwareAccelerated && x.Length >= Vector256.Count * 2) + { + ref float xRef = ref MemoryMarshal.GetReference(x); + + // Load the first vector as the initial set of results + Vector256 resultVector = TLoad.Invoke(Vector256.LoadUnsafe(ref xRef, 0)); + int oneVectorFromEnd = x.Length - Vector256.Count; + + // Aggregate additional vectors into the result as long as there's at + // least one full vector left to process. + i = Vector256.Count; + do + { + resultVector = TAggregate.Invoke(resultVector, TLoad.Invoke(Vector256.LoadUnsafe(ref xRef, (uint)i))); + i += Vector256.Count; + } + while (i <= oneVectorFromEnd); + + // Aggregate the lanes in the vector back into the scalar result + result = TAggregate.Invoke(result, TAggregate.Invoke(resultVector)); + } + else if (Vector128.IsHardwareAccelerated && x.Length >= Vector128.Count * 2) + { + ref float xRef = ref MemoryMarshal.GetReference(x); + + // Load the first vector as the initial set of results + Vector128 resultVector = TLoad.Invoke(Vector128.LoadUnsafe(ref xRef, 0)); + int oneVectorFromEnd = x.Length - Vector128.Count; + + // Aggregate additional vectors into the result as long as there's at + // least one full vector left to process. + i = Vector128.Count; + do + { + resultVector = TAggregate.Invoke(resultVector, TLoad.Invoke(Vector128.LoadUnsafe(ref xRef, (uint)i))); + i += Vector128.Count; + } + while (i <= oneVectorFromEnd); + + // Aggregate the lanes in the vector back into the scalar result + result = TAggregate.Invoke(result, TAggregate.Invoke(resultVector)); + } + + // Aggregate the remaining items in the input span. + for (; (uint)i < (uint)x.Length; i++) + { + result = TAggregate.Invoke(result, TLoad.Invoke(x[i])); + } + + return result; + } + + private static float Aggregate( + float identityValue, ReadOnlySpan x, ReadOnlySpan y) + where TBinary : IBinaryOperator + where TAggregate : IBinaryOperator + { + // Initialize the result to the identity value + float result = identityValue; + int i = 0; + +#if NET8_0_OR_GREATER + if (Vector512.IsHardwareAccelerated && x.Length >= Vector512.Count * 2) + { + ref float xRef = ref MemoryMarshal.GetReference(x); + ref float yRef = ref MemoryMarshal.GetReference(y); + + // Load the first vector as the initial set of results + Vector512 resultVector = TBinary.Invoke(Vector512.LoadUnsafe(ref xRef, 0), Vector512.LoadUnsafe(ref yRef, 0)); + int oneVectorFromEnd = x.Length - Vector512.Count; + + // Aggregate additional vectors into the result as long as there's at + // least one full vector left to process. + i = Vector512.Count; + do + { + resultVector = TAggregate.Invoke(resultVector, TBinary.Invoke(Vector512.LoadUnsafe(ref xRef, (uint)i), Vector512.LoadUnsafe(ref yRef, (uint)i))); + i += Vector512.Count; + } + while (i <= oneVectorFromEnd); + + // Aggregate the lanes in the vector back into the scalar result + result = TAggregate.Invoke(result, TAggregate.Invoke(resultVector)); + } + else +#endif + if (Vector256.IsHardwareAccelerated && x.Length >= Vector256.Count * 2) + { + ref float xRef = ref MemoryMarshal.GetReference(x); + ref float yRef = ref MemoryMarshal.GetReference(y); + + // Load the first vector as the initial set of results + Vector256 resultVector = TBinary.Invoke(Vector256.LoadUnsafe(ref xRef, 0), Vector256.LoadUnsafe(ref yRef, 0)); + int oneVectorFromEnd = x.Length - Vector256.Count; + + // Aggregate additional vectors into the result as long as there's at + // least one full vector left to process. + i = Vector256.Count; + do + { + resultVector = TAggregate.Invoke(resultVector, TBinary.Invoke(Vector256.LoadUnsafe(ref xRef, (uint)i), Vector256.LoadUnsafe(ref yRef, (uint)i))); + i += Vector256.Count; + } + while (i <= oneVectorFromEnd); + + // Aggregate the lanes in the vector back into the scalar result + result = TAggregate.Invoke(result, TAggregate.Invoke(resultVector)); + } + else if (Vector128.IsHardwareAccelerated && x.Length >= Vector128.Count * 2) + { + ref float xRef = ref MemoryMarshal.GetReference(x); + ref float yRef = ref MemoryMarshal.GetReference(y); + + // Load the first vector as the initial set of results + Vector128 resultVector = TBinary.Invoke(Vector128.LoadUnsafe(ref xRef, 0), Vector128.LoadUnsafe(ref yRef, 0)); + int oneVectorFromEnd = x.Length - Vector128.Count; + + // Aggregate additional vectors into the result as long as there's at + // least one full vector left to process. + i = Vector128.Count; + do + { + resultVector = TAggregate.Invoke(resultVector, TBinary.Invoke(Vector128.LoadUnsafe(ref xRef, (uint)i), Vector128.LoadUnsafe(ref yRef, (uint)i))); + i += Vector128.Count; + } + while (i <= oneVectorFromEnd); + + // Aggregate the lanes in the vector back into the scalar result + result = TAggregate.Invoke(result, TAggregate.Invoke(resultVector)); + } + + // Aggregate the remaining items in the input span. + for (; (uint)i < (uint)x.Length; i++) + { + result = TAggregate.Invoke(result, TBinary.Invoke(x[i], y[i])); + } + + return result; + } + private static unsafe void InvokeSpanIntoSpan( ReadOnlySpan x, Span destination) where TUnaryOperator : IUnaryOperator @@ -704,6 +1034,12 @@ private static unsafe void InvokeSpanScalarSpanIntoSpan( #if NET8_0_OR_GREATER public static Vector512 Invoke(Vector512 x, Vector512 y) => x + y; #endif + + public static float Invoke(Vector128 x) => Vector128.Sum(x); + public static float Invoke(Vector256 x) => Vector256.Sum(x); +#if NET8_0_OR_GREATER + public static float Invoke(Vector512 x) => Vector512.Sum(x); +#endif } private readonly struct SubtractOperator : IBinaryOperator @@ -714,6 +1050,47 @@ private static unsafe void InvokeSpanScalarSpanIntoSpan( #if NET8_0_OR_GREATER public static Vector512 Invoke(Vector512 x, Vector512 y) => x - y; #endif + + public static float Invoke(Vector128 x) => throw new NotSupportedException(); + public static float Invoke(Vector256 x) => throw new NotSupportedException(); +#if NET8_0_OR_GREATER + public static float Invoke(Vector512 x) => throw new NotSupportedException(); +#endif + } + + private readonly struct SubtractSquaredOperator : IBinaryOperator + { + public static float Invoke(float x, float y) + { + float tmp = x - y; + return tmp * tmp; + } + + public static Vector128 Invoke(Vector128 x, Vector128 y) + { + Vector128 tmp = x - y; + return tmp * tmp; + } + + public static Vector256 Invoke(Vector256 x, Vector256 y) + { + Vector256 tmp = x - y; + return tmp * tmp; + } + +#if NET8_0_OR_GREATER + public static Vector512 Invoke(Vector512 x, Vector512 y) + { + Vector512 tmp = x - y; + return tmp * tmp; + } +#endif + + public static float Invoke(Vector128 x) => throw new NotSupportedException(); + public static float Invoke(Vector256 x) => throw new NotSupportedException(); +#if NET8_0_OR_GREATER + public static float Invoke(Vector512 x) => throw new NotSupportedException(); +#endif } private readonly struct MultiplyOperator : IBinaryOperator @@ -724,6 +1101,41 @@ private static unsafe void InvokeSpanScalarSpanIntoSpan( #if NET8_0_OR_GREATER public static Vector512 Invoke(Vector512 x, Vector512 y) => x * y; #endif + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Invoke(Vector128 x) + { + float f = x[0]; + for (int i = 1; i < Vector128.Count; i++) + { + f *= x[i]; + } + return f; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Invoke(Vector256 x) + { + float f = x[0]; + for (int i = 1; i < Vector256.Count; i++) + { + f *= x[i]; + } + return f; + } + +#if NET8_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Invoke(Vector512 x) + { + float f = x[0]; + for (int i = 1; i < Vector512.Count; i++) + { + f *= x[i]; + } + return f; + } +#endif } private readonly struct DivideOperator : IBinaryOperator @@ -734,6 +1146,12 @@ private static unsafe void InvokeSpanScalarSpanIntoSpan( #if NET8_0_OR_GREATER public static Vector512 Invoke(Vector512 x, Vector512 y) => x / y; #endif + + public static float Invoke(Vector128 x) => throw new NotSupportedException(); + public static float Invoke(Vector256 x) => throw new NotSupportedException(); +#if NET8_0_OR_GREATER + public static float Invoke(Vector512 x) => throw new NotSupportedException(); +#endif } private readonly struct NegateOperator : IUnaryOperator @@ -760,6 +1178,54 @@ private static unsafe void InvokeSpanScalarSpanIntoSpan( public static Vector512 Invoke(Vector512 x, Vector512 y, Vector512 z) => (x * y) + z; } + private readonly struct LoadIdentity : IUnaryOperator + { + public static float Invoke(float x) => x; + public static Vector128 Invoke(Vector128 x) => x; + public static Vector256 Invoke(Vector256 x) => x; +#if NET8_0_OR_GREATER + public static Vector512 Invoke(Vector512 x) => x; +#endif + } + + private readonly struct LoadSquared : IUnaryOperator + { + public static float Invoke(float x) => x * x; + public static Vector128 Invoke(Vector128 x) => x * x; + public static Vector256 Invoke(Vector256 x) => x * x; +#if NET8_0_OR_GREATER + public static Vector512 Invoke(Vector512 x) => x * x; +#endif + } + + private readonly struct LoadAbsolute : IUnaryOperator + { + public static float Invoke(float x) => MathF.Abs(x); + + public static Vector128 Invoke(Vector128 x) + { + Vector128 raw = x.AsUInt32(); + Vector128 mask = Vector128.Create((uint)0x7FFFFFFF); + return (raw & mask).AsSingle(); + } + + public static Vector256 Invoke(Vector256 x) + { + Vector256 raw = x.AsUInt32(); + Vector256 mask = Vector256.Create((uint)0x7FFFFFFF); + return (raw & mask).AsSingle(); + } + +#if NET8_0_OR_GREATER + public static Vector512 Invoke(Vector512 x) + { + Vector512 raw = x.AsUInt32(); + Vector512 mask = Vector512.Create((uint)0x7FFFFFFF); + return (raw & mask).AsSingle(); + } +#endif + } + private interface IUnaryOperator { static abstract float Invoke(float x); @@ -773,10 +1239,14 @@ private interface IUnaryOperator private interface IBinaryOperator { static abstract float Invoke(float x, float y); + static abstract Vector128 Invoke(Vector128 x, Vector128 y); + static abstract float Invoke(Vector128 x); static abstract Vector256 Invoke(Vector256 x, Vector256 y); + static abstract float Invoke(Vector256 x); #if NET8_0_OR_GREATER static abstract Vector512 Invoke(Vector512 x, Vector512 y); + static abstract float Invoke(Vector512 x); #endif } 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 ddac0f47a685c..134364708b8e5 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 @@ -8,6 +8,152 @@ namespace System.Numerics.Tensors { public static partial class TensorPrimitives { + private static unsafe bool IsNegative(float f) => *(int*)&f < 0; + + private static float CosineSimilarityCore(ReadOnlySpan x, ReadOnlySpan y) + { + // Compute the same as: + // TensorPrimitives.Dot(x, y) / (Math.Sqrt(TensorPrimitives.SumOfSquares(x)) * Math.Sqrt(TensorPrimitives.SumOfSquares(y))) + // but only looping over each span once. + + float dotProduct = 0f; + float xSumOfSquares = 0f; + float ySumOfSquares = 0f; + + int i = 0; + + if (Vector.IsHardwareAccelerated && x.Length >= Vector.Count) + { + ref float xRef = ref MemoryMarshal.GetReference(x); + ref float yRef = ref MemoryMarshal.GetReference(y); + + Vector dotProductVector = Vector.Zero; + Vector xSumOfSquaresVector = Vector.Zero; + Vector ySumOfSquaresVector = Vector.Zero; + + // Process vectors, summing their dot products and squares, as long as there's a vector's worth remaining. + int oneVectorFromEnd = x.Length - Vector.Count; + do + { + Vector xVec = AsVector(ref xRef, i); + Vector yVec = AsVector(ref yRef, i); + + dotProductVector += xVec * yVec; + xSumOfSquaresVector += xVec * xVec; + ySumOfSquaresVector += yVec * yVec; + + i += Vector.Count; + } + while (i <= oneVectorFromEnd); + + // Sum the vector lanes into the scalar result. + for (int e = 0; e < Vector.Count; e++) + { + dotProduct += dotProductVector[e]; + xSumOfSquares += xSumOfSquaresVector[e]; + ySumOfSquares += ySumOfSquaresVector[e]; + } + } + + // Process any remaining elements past the last vector. + for (; (uint)i < (uint)x.Length; i++) + { + dotProduct += x[i] * y[i]; + xSumOfSquares += x[i] * x[i]; + ySumOfSquares += y[i] * y[i]; + } + + // Sum(X * Y) / (|X| * |Y|) + return dotProduct / (MathF.Sqrt(xSumOfSquares) * MathF.Sqrt(ySumOfSquares)); + } + + private static float Aggregate( + float identityValue, ReadOnlySpan x, TLoad load = default, TAggregate aggregate = default) + where TLoad : IUnaryOperator + where TAggregate : IBinaryOperator + { + // Initialize the result to the identity value + float result = identityValue; + int i = 0; + + if (Vector.IsHardwareAccelerated && x.Length >= Vector.Count * 2) + { + ref float xRef = ref MemoryMarshal.GetReference(x); + + // Load the first vector as the initial set of results + Vector resultVector = load.Invoke(AsVector(ref xRef, 0)); + int oneVectorFromEnd = x.Length - Vector.Count; + + // Aggregate additional vectors into the result as long as there's at + // least one full vector left to process. + i = Vector.Count; + do + { + resultVector = aggregate.Invoke(resultVector, load.Invoke(AsVector(ref xRef, i))); + i += Vector.Count; + } + while (i <= oneVectorFromEnd); + + // Aggregate the lanes in the vector back into the scalar result + for (int f = 0; f < Vector.Count; f++) + { + result = aggregate.Invoke(result, resultVector[f]); + } + } + + // Aggregate the remaining items in the input span. + for (; (uint)i < (uint)x.Length; i++) + { + result = aggregate.Invoke(result, load.Invoke(x[i])); + } + + return result; + } + + private static float Aggregate( + float identityValue, ReadOnlySpan x, ReadOnlySpan y, TBinary binary = default, TAggregate aggregate = default) + where TBinary : IBinaryOperator + where TAggregate : IBinaryOperator + { + // Initialize the result to the identity value + float result = identityValue; + int i = 0; + + if (Vector.IsHardwareAccelerated && x.Length >= Vector.Count * 2) + { + ref float xRef = ref MemoryMarshal.GetReference(x); + ref float yRef = ref MemoryMarshal.GetReference(y); + + // Load the first vector as the initial set of results + Vector resultVector = binary.Invoke(AsVector(ref xRef, 0), AsVector(ref yRef, 0)); + int oneVectorFromEnd = x.Length - Vector.Count; + + // Aggregate additional vectors into the result as long as there's at + // least one full vector left to process. + i = Vector.Count; + do + { + resultVector = aggregate.Invoke(resultVector, binary.Invoke(AsVector(ref xRef, i), AsVector(ref yRef, i))); + i += Vector.Count; + } + while (i <= oneVectorFromEnd); + + // Aggregate the lanes in the vector back into the scalar result + for (int f = 0; f < Vector.Count; f++) + { + result = aggregate.Invoke(result, resultVector[f]); + } + } + + // Aggregate the remaining items in the input span. + for (; (uint)i < (uint)x.Length; i++) + { + result = aggregate.Invoke(result, binary.Invoke(x[i], y[i])); + } + + return result; + } + private static void InvokeSpanIntoSpan( ReadOnlySpan x, Span destination, TUnaryOperator op = default) where TUnaryOperator : IUnaryOperator @@ -360,6 +506,21 @@ ref Unsafe.As>( public Vector Invoke(Vector x, Vector y) => x - y; } + private readonly struct SubtractSquaredOperator : IBinaryOperator + { + public float Invoke(float x, float y) + { + float tmp = x - y; + return tmp * tmp; + } + + public Vector Invoke(Vector x, Vector y) + { + Vector tmp = x - y; + return tmp * tmp; + } + } + private readonly struct MultiplyOperator : IBinaryOperator { public float Invoke(float x, float y) => x * y; @@ -390,6 +551,30 @@ ref Unsafe.As>( public Vector Invoke(Vector x, Vector y, Vector z) => (x * y) + z; } + private readonly struct LoadIdentity : IUnaryOperator + { + public float Invoke(float x) => x; + public Vector Invoke(Vector x) => x; + } + + private readonly struct LoadSquared : IUnaryOperator + { + public float Invoke(float x) => x * x; + public Vector Invoke(Vector x) => x * x; + } + + private readonly struct LoadAbsolute : IUnaryOperator + { + public float Invoke(float x) => MathF.Abs(x); + + public Vector Invoke(Vector x) + { + Vector raw = Vector.AsVectorUInt32(x); + Vector mask = new Vector(0x7FFFFFFF); + return Vector.AsVectorSingle(raw & mask); + } + } + private interface IUnaryOperator { float Invoke(float x); diff --git a/src/libraries/System.Numerics.Tensors/tests/System.Numerics.Tensors.Tests.csproj b/src/libraries/System.Numerics.Tensors/tests/System.Numerics.Tensors.Tests.csproj index d78f1cd7e9e03..be4a103d7256c 100644 --- a/src/libraries/System.Numerics.Tensors/tests/System.Numerics.Tensors.Tests.csproj +++ b/src/libraries/System.Numerics.Tensors/tests/System.Numerics.Tensors.Tests.csproj @@ -8,6 +8,10 @@ + + + + diff --git a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs index e8bb48a50f6b2..181d152e6ae97 100644 --- a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs +++ b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs @@ -1,36 +1,36 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Xunit; +using System.Buffers; using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Xunit; #pragma warning disable xUnit1025 // reporting duplicate test cases due to not distinguishing 0.0 from -0.0 namespace System.Numerics.Tensors.Tests { - public static class TensorPrimitivesTests + public static partial class TensorPrimitivesTests { + private const double Tolerance = 0.0001; + public static IEnumerable TensorLengths => - from length in new[] { 1, 2, 3, 4, 5, 7, 8, 9, 11, 12, 13, 15, 16, 17, 31, 32, 33, 100 } + from length in Enumerable.Range(1, 128) select new object[] { length }; private static readonly Random s_random = new Random(20230828); - private static float[] CreateTensor(int size) - { - return new float[size]; - } + private static BoundedMemory CreateTensor(int size) => BoundedMemory.Allocate(size); - private static float[] CreateAndFillTensor(int size) + private static BoundedMemory CreateAndFillTensor(int size) { - float[] tensor = CreateTensor(size); - FillTensor(tensor); + BoundedMemory tensor = CreateTensor(size); + FillTensor(tensor.Span); return tensor; } - private static void FillTensor(float[] tensor) + private static void FillTensor(Span tensor) { for (int i = 0; i < tensor.Length; i++) { @@ -40,26 +40,23 @@ private static void FillTensor(float[] tensor) private static float NextSingle() { -#if NETCOREAPP - return s_random.NextSingle(); -#else - return (float)s_random.NextDouble(); -#endif + // For testing purposes, get a mix of negative and positive values. + return (float)((s_random.NextDouble() * 2) - 1); } [Theory] [MemberData(nameof(TensorLengths))] public static void AddTwoTensors(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.Add(x, y, destination); for (int i = 0; i < tensorLength; i++) { - Assert.Equal((x[i] + y[i]), destination[i]); + Assert.Equal(x[i] + y[i], destination[i]); } } @@ -67,9 +64,9 @@ public static void AddTwoTensors(int tensorLength) [MemberData(nameof(TensorLengths))] public static void AddTwoTensors_ThrowsForMismatchedLengths(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength - 1); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength); Assert.Throws(() => TensorPrimitives.Add(x, y, destination)); } @@ -78,9 +75,9 @@ public static void AddTwoTensors_ThrowsForMismatchedLengths(int tensorLength) [MemberData(nameof(TensorLengths))] public static void AddTwoTensors_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Add(x, y, destination)); } @@ -89,15 +86,15 @@ public static void AddTwoTensors_ThrowsForTooShortDestination(int tensorLength) [MemberData(nameof(TensorLengths))] public static void AddTensorAndScalar(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.Add(x, y, destination); for (int i = 0; i < tensorLength; i++) { - Assert.Equal((x[i] + y), destination[i]); + Assert.Equal(x[i] + y, destination[i]); } } @@ -105,9 +102,9 @@ public static void AddTensorAndScalar(int tensorLength) [MemberData(nameof(TensorLengths))] public static void AddTensorAndScalar_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Add(x, y, destination)); } @@ -116,15 +113,15 @@ public static void AddTensorAndScalar_ThrowsForTooShortDestination(int tensorLen [MemberData(nameof(TensorLengths))] public static void SubtractTwoTensors(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.Subtract(x, y, destination); for (int i = 0; i < tensorLength; i++) { - Assert.Equal((x[i] - y[i]), destination[i]); + Assert.Equal(x[i] - y[i], destination[i]); } } @@ -132,9 +129,9 @@ public static void SubtractTwoTensors(int tensorLength) [MemberData(nameof(TensorLengths))] public static void SubtractTwoTensors_ThrowsForMismatchedLengths(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength - 1); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength); Assert.Throws(() => TensorPrimitives.Subtract(x, y, destination)); } @@ -143,9 +140,9 @@ public static void SubtractTwoTensors_ThrowsForMismatchedLengths(int tensorLengt [MemberData(nameof(TensorLengths))] public static void SubtractTwoTensors_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Subtract(x, y, destination)); } @@ -154,15 +151,15 @@ public static void SubtractTwoTensors_ThrowsForTooShortDestination(int tensorLen [MemberData(nameof(TensorLengths))] public static void SubtractTensorAndScalar(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.Subtract(x, y, destination); for (int i = 0; i < tensorLength; i++) { - Assert.Equal((x[i] - y), destination[i]); + Assert.Equal(x[i] - y, destination[i]); } } @@ -170,9 +167,9 @@ public static void SubtractTensorAndScalar(int tensorLength) [MemberData(nameof(TensorLengths))] public static void SubtractTensorAndScalar_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Subtract(x, y, destination)); } @@ -181,15 +178,15 @@ public static void SubtractTensorAndScalar_ThrowsForTooShortDestination(int tens [MemberData(nameof(TensorLengths))] public static void MultiplyTwoTensors(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.Multiply(x, y, destination); for (int i = 0; i < tensorLength; i++) { - Assert.Equal((x[i] * y[i]), destination[i]); + Assert.Equal(x[i] * y[i], destination[i]); } } @@ -197,9 +194,9 @@ public static void MultiplyTwoTensors(int tensorLength) [MemberData(nameof(TensorLengths))] public static void MultiplyTwoTensors_ThrowsForMismatchedLengths(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength - 1); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength); Assert.Throws(() => TensorPrimitives.Multiply(x, y, destination)); } @@ -208,9 +205,9 @@ public static void MultiplyTwoTensors_ThrowsForMismatchedLengths(int tensorLengt [MemberData(nameof(TensorLengths))] public static void MultiplyTwoTensors_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Multiply(x, y, destination)); } @@ -219,15 +216,15 @@ public static void MultiplyTwoTensors_ThrowsForTooShortDestination(int tensorLen [MemberData(nameof(TensorLengths))] public static void MultiplyTensorAndScalar(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.Multiply(x, y, destination); for (int i = 0; i < tensorLength; i++) { - Assert.Equal((x[i] * y), destination[i]); + Assert.Equal(x[i] * y, destination[i]); } } @@ -235,9 +232,9 @@ public static void MultiplyTensorAndScalar(int tensorLength) [MemberData(nameof(TensorLengths))] public static void MultiplyTensorAndScalar_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Multiply(x, y, destination)); } @@ -246,15 +243,15 @@ public static void MultiplyTensorAndScalar_ThrowsForTooShortDestination(int tens [MemberData(nameof(TensorLengths))] public static void DivideTwoTensors(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.Divide(x, y, destination); for (int i = 0; i < tensorLength; i++) { - Assert.Equal((x[i] / y[i]), destination[i]); + Assert.Equal(x[i] / y[i], destination[i]); } } @@ -262,9 +259,9 @@ public static void DivideTwoTensors(int tensorLength) [MemberData(nameof(TensorLengths))] public static void DivideTwoTensors_ThrowsForMismatchedLengths(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength - 1); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength); Assert.Throws(() => TensorPrimitives.Divide(x, y, destination)); } @@ -273,9 +270,9 @@ public static void DivideTwoTensors_ThrowsForMismatchedLengths(int tensorLength) [MemberData(nameof(TensorLengths))] public static void DivideTwoTensors_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Divide(x, y, destination)); } @@ -284,15 +281,15 @@ public static void DivideTwoTensors_ThrowsForTooShortDestination(int tensorLengt [MemberData(nameof(TensorLengths))] public static void DivideTensorAndScalar(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.Divide(x, y, destination); for (int i = 0; i < tensorLength; i++) { - Assert.Equal((x[i] / y), destination[i]); + Assert.Equal(x[i] / y, destination[i]); } } @@ -300,9 +297,9 @@ public static void DivideTensorAndScalar(int tensorLength) [MemberData(nameof(TensorLengths))] public static void DivideTensorAndScalar_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Divide(x, y, destination)); } @@ -311,8 +308,8 @@ public static void DivideTensorAndScalar_ThrowsForTooShortDestination(int tensor [MemberData(nameof(TensorLengths))] public static void NegateTensor(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.Negate(x, destination); @@ -326,8 +323,8 @@ public static void NegateTensor(int tensorLength) [MemberData(nameof(TensorLengths))] public static void NegateTensor_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Negate(x, destination)); } @@ -336,10 +333,10 @@ public static void NegateTensor_ThrowsForTooShortDestination(int tensorLength) [MemberData(nameof(TensorLengths))] public static void AddTwoTensorsAndMultiplyWithThirdTensor(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); - float[] multiplier = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory multiplier = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.AddMultiply(x, y, multiplier, destination); @@ -353,10 +350,10 @@ public static void AddTwoTensorsAndMultiplyWithThirdTensor(int tensorLength) [MemberData(nameof(TensorLengths))] public static void AddTwoTensorsAndMultiplyWithThirdTensor_ThrowsForMismatchedLengths_x_y(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength - 1); - float[] multiplier = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory multiplier = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); Assert.Throws(() => TensorPrimitives.AddMultiply(x, y, multiplier, destination)); } @@ -365,10 +362,10 @@ public static void AddTwoTensorsAndMultiplyWithThirdTensor_ThrowsForMismatchedLe [MemberData(nameof(TensorLengths))] public static void AddTwoTensorsAndMultiplyWithThirdTensor_ThrowsForMismatchedLengths_x_multiplier(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); - float[] multiplier = CreateAndFillTensor(tensorLength - 1); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory multiplier = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength); Assert.Throws(() => TensorPrimitives.AddMultiply(x, y, multiplier, destination)); } @@ -377,10 +374,10 @@ public static void AddTwoTensorsAndMultiplyWithThirdTensor_ThrowsForMismatchedLe [MemberData(nameof(TensorLengths))] public static void AddTwoTensorsAndMultiplyWithThirdTensor_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); - float[] multiplier = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory multiplier = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.AddMultiply(x, y, multiplier, destination)); } @@ -389,10 +386,10 @@ public static void AddTwoTensorsAndMultiplyWithThirdTensor_ThrowsForTooShortDest [MemberData(nameof(TensorLengths))] public static void AddTwoTensorsAndMultiplyWithScalar(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); float multiplier = NextSingle(); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.AddMultiply(x, y, multiplier, destination); @@ -406,10 +403,10 @@ public static void AddTwoTensorsAndMultiplyWithScalar(int tensorLength) [MemberData(nameof(TensorLengths))] public static void AddTwoTensorsAndMultiplyWithScalar_ThrowsForMismatchedLengths_x_y(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); float multiplier = NextSingle(); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); Assert.Throws(() => TensorPrimitives.AddMultiply(x, y, multiplier, destination)); } @@ -418,10 +415,10 @@ public static void AddTwoTensorsAndMultiplyWithScalar_ThrowsForMismatchedLengths [MemberData(nameof(TensorLengths))] public static void AddTwoTensorsAndMultiplyWithScalar_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); float multiplier = NextSingle(); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.AddMultiply(x, y, multiplier, destination)); } @@ -430,10 +427,10 @@ public static void AddTwoTensorsAndMultiplyWithScalar_ThrowsForTooShortDestinati [MemberData(nameof(TensorLengths))] public static void AddTensorAndScalarAndMultiplyWithTensor(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - float[] multiplier = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory multiplier = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.AddMultiply(x, y, multiplier, destination); @@ -447,10 +444,10 @@ public static void AddTensorAndScalarAndMultiplyWithTensor(int tensorLength) [MemberData(nameof(TensorLengths))] public static void AddTensorAndScalarAndMultiplyWithTensor_ThrowsForMismatchedLengths_x_z(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - float[] multiplier = CreateAndFillTensor(tensorLength - 1); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory multiplier = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength); Assert.Throws(() => TensorPrimitives.AddMultiply(x, y, multiplier, destination)); } @@ -459,10 +456,10 @@ public static void AddTensorAndScalarAndMultiplyWithTensor_ThrowsForMismatchedLe [MemberData(nameof(TensorLengths))] public static void AddTensorAndScalarAndMultiplyWithTensor_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - float[] multiplier = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory multiplier = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.AddMultiply(x, y, multiplier, destination)); } @@ -471,10 +468,10 @@ public static void AddTensorAndScalarAndMultiplyWithTensor_ThrowsForTooShortDest [MemberData(nameof(TensorLengths))] public static void MultiplyTwoTensorsAndAddWithThirdTensor(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); - float[] addend = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory addend = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.MultiplyAdd(x, y, addend, destination); @@ -488,10 +485,10 @@ public static void MultiplyTwoTensorsAndAddWithThirdTensor(int tensorLength) [MemberData(nameof(TensorLengths))] public static void MultiplyTwoTensorsAndAddWithThirdTensor_ThrowsForMismatchedLengths_x_y(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength - 1); - float[] addend = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory addend = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); Assert.Throws(() => TensorPrimitives.MultiplyAdd(x, y, addend, destination)); } @@ -500,10 +497,10 @@ public static void MultiplyTwoTensorsAndAddWithThirdTensor_ThrowsForMismatchedLe [MemberData(nameof(TensorLengths))] public static void MultiplyTwoTensorsAndAddWithThirdTensor_ThrowsForMismatchedLengths_x_multiplier(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); - float[] addend = CreateAndFillTensor(tensorLength - 1); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory addend = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength); Assert.Throws(() => TensorPrimitives.MultiplyAdd(x, y, addend, destination)); } @@ -512,10 +509,10 @@ public static void MultiplyTwoTensorsAndAddWithThirdTensor_ThrowsForMismatchedLe [MemberData(nameof(TensorLengths))] public static void MultiplyTwoTensorsAndAddWithThirdTensor_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); - float[] addend = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory addend = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.MultiplyAdd(x, y, addend, destination)); } @@ -524,10 +521,10 @@ public static void MultiplyTwoTensorsAndAddWithThirdTensor_ThrowsForTooShortDest [MemberData(nameof(TensorLengths))] public static void MultiplyTwoTensorsAndAddWithScalar(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); float addend = NextSingle(); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.MultiplyAdd(x, y, addend, destination); @@ -541,10 +538,10 @@ public static void MultiplyTwoTensorsAndAddWithScalar(int tensorLength) [MemberData(nameof(TensorLengths))] public static void MultiplyTwoTensorsAndAddWithScalar_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); float addend = NextSingle(); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.MultiplyAdd(x, y, addend, destination)); } @@ -553,10 +550,10 @@ public static void MultiplyTwoTensorsAndAddWithScalar_ThrowsForTooShortDestinati [MemberData(nameof(TensorLengths))] public static void MultiplyTensorAndScalarAndAddWithTensor(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - float[] addend = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory addend = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.MultiplyAdd(x, y, addend, destination); @@ -570,10 +567,10 @@ public static void MultiplyTensorAndScalarAndAddWithTensor(int tensorLength) [MemberData(nameof(TensorLengths))] public static void MultiplyTensorAndScalarAndAddWithTensor_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - float[] addend = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory addend = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.MultiplyAdd(x, y, addend, destination)); } @@ -582,8 +579,8 @@ public static void MultiplyTensorAndScalarAndAddWithTensor_ThrowsForTooShortDest [MemberData(nameof(TensorLengths))] public static void ExpTensor(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.Exp(x, destination); @@ -597,8 +594,8 @@ public static void ExpTensor(int tensorLength) [MemberData(nameof(TensorLengths))] public static void ExpTensor_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Exp(x, destination)); } @@ -607,8 +604,8 @@ public static void ExpTensor_ThrowsForTooShortDestination(int tensorLength) [MemberData(nameof(TensorLengths))] public static void LogTensor(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.Log(x, destination); @@ -622,8 +619,8 @@ public static void LogTensor(int tensorLength) [MemberData(nameof(TensorLengths))] public static void LogTensor_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Log(x, destination)); } @@ -632,8 +629,8 @@ public static void LogTensor_ThrowsForTooShortDestination(int tensorLength) [MemberData(nameof(TensorLengths))] public static void CoshTensor(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.Cosh(x, destination); @@ -647,8 +644,8 @@ public static void CoshTensor(int tensorLength) [MemberData(nameof(TensorLengths))] public static void CoshTensor_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Cosh(x, destination)); } @@ -657,8 +654,8 @@ public static void CoshTensor_ThrowsForTooShortDestination(int tensorLength) [MemberData(nameof(TensorLengths))] public static void SinhTensor(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.Sinh(x, destination); @@ -672,8 +669,8 @@ public static void SinhTensor(int tensorLength) [MemberData(nameof(TensorLengths))] public static void SinhTensor_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Sinh(x, destination)); } @@ -682,8 +679,8 @@ public static void SinhTensor_ThrowsForTooShortDestination(int tensorLength) [MemberData(nameof(TensorLengths))] public static void TanhTensor(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); TensorPrimitives.Tanh(x, destination); @@ -697,8 +694,8 @@ public static void TanhTensor(int tensorLength) [MemberData(nameof(TensorLengths))] public static void TanhTensor_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Tanh(x, destination)); } @@ -707,8 +704,8 @@ public static void TanhTensor_ThrowsForTooShortDestination(int tensorLength) [MemberData(nameof(TensorLengths))] public static void CosineSimilarity_ThrowsForMismatchedLengths_x_y(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); Assert.Throws(() => TensorPrimitives.CosineSimilarity(x, y)); } @@ -716,35 +713,51 @@ public static void CosineSimilarity_ThrowsForMismatchedLengths_x_y(int tensorLen [Fact] public static void CosineSimilarity_ThrowsForEmpty_x_y() { - float[] x = []; - float[] y = []; - - Assert.Throws(() => TensorPrimitives.CosineSimilarity(x, y)); + Assert.Throws(() => TensorPrimitives.CosineSimilarity(ReadOnlySpan.Empty, ReadOnlySpan.Empty)); + Assert.Throws(() => TensorPrimitives.CosineSimilarity(ReadOnlySpan.Empty, CreateTensor(1))); + Assert.Throws(() => TensorPrimitives.CosineSimilarity(CreateTensor(1), ReadOnlySpan.Empty)); } [Theory] - [InlineData(new float[] { 3, 2, 0, 5 }, new float[] { 1, 0, 0, 0 }, 0.49f)] + [InlineData(new float[] { 3, 2, 0, 5 }, new float[] { 1, 0, 0, 0 }, 0.48666f)] [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) + public static void CosineSimilarity_KnownValues(float[] x, float[] y, float expectedResult) { - Assert.Equal(expectedResult, TensorPrimitives.CosineSimilarity(x, y), .01f); + Assert.Equal(expectedResult, TensorPrimitives.CosineSimilarity(x, y), Tolerance); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void CosineSimilarity(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + + float dot = 0f, squareX = 0f, squareY = 0f; + for (int i = 0; i < x.Length; i++) + { + dot += x[i] * y[i]; + squareX += x[i] * x[i]; + squareY += y[i] * y[i]; + } + + Assert.Equal(dot / (Math.Sqrt(squareX) * Math.Sqrt(squareY)), TensorPrimitives.CosineSimilarity(x, y), Tolerance); } [Fact] public static void Distance_ThrowsForEmpty_x_y() { - float[] x = []; - float[] y = []; - - Assert.Throws(() => TensorPrimitives.Distance(x, y)); + Assert.Throws(() => TensorPrimitives.Distance(ReadOnlySpan.Empty, ReadOnlySpan.Empty)); + Assert.Throws(() => TensorPrimitives.Distance(ReadOnlySpan.Empty, CreateTensor(1))); + Assert.Throws(() => TensorPrimitives.Distance(CreateTensor(1), ReadOnlySpan.Empty)); } [Theory] [MemberData(nameof(TensorLengths))] public static void Distance_ThrowsForMismatchedLengths_x_y(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); Assert.Throws(() => TensorPrimitives.Distance(x, y)); } @@ -754,17 +767,33 @@ public static void Distance_ThrowsForMismatchedLengths_x_y(int tensorLength) [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) + public static void Distance_KnownValues(float[] x, float[] y, float expectedResult) { - Assert.Equal(expectedResult, TensorPrimitives.Distance(x, y), .001f); + Assert.Equal(expectedResult, TensorPrimitives.Distance(x, y), Tolerance); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Distance(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + + float distance = 0f; + for (int i = 0; i < x.Length; i++) + { + distance += (x[i] - y[i]) * (x[i] - y[i]); + } + + Assert.Equal(Math.Sqrt(distance), TensorPrimitives.Distance(x, y), Tolerance); } [Theory] [MemberData(nameof(TensorLengths))] public static void Dot_ThrowsForMismatchedLengths_x_y(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] y = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); Assert.Throws(() => TensorPrimitives.Dot(x, y)); } @@ -774,77 +803,105 @@ public static void Dot_ThrowsForMismatchedLengths_x_y(int tensorLength) [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) + public static void Dot_KnownValues(float[] x, float[] y, float expectedResult) + { + Assert.Equal(expectedResult, TensorPrimitives.Dot(x, y), Tolerance); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Dot(int tensorLength) { - Assert.Equal(expectedResult, TensorPrimitives.Dot(x, y), .001f); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + + float dot = 0f; + for (int i = 0; i < x.Length; i++) + { + dot += x[i] * y[i]; + } + + Assert.Equal(dot, TensorPrimitives.Dot(x, y), Tolerance); } [Theory] - [InlineData(new float[] { 1, 2, 3 }, 3.7416)] + [InlineData(new float[] { 1, 2, 3 }, 3.7416575f)] [InlineData(new float[] { 3, 4 }, 5)] [InlineData(new float[] { 3 }, 3)] - [InlineData(new float[] { 3, 4, 1, 2 }, 5.477)] + [InlineData(new float[] { 3, 4, 1, 2 }, 5.477226)] [InlineData(new float[] { }, 0f)] - public static void L2Normalize(float[] x, float expectedResult) + public static void L2Normalize_KnownValues(float[] x, float expectedResult) { - Assert.Equal(expectedResult, TensorPrimitives.L2Normalize(x), .001f); + Assert.Equal(expectedResult, TensorPrimitives.L2Normalize(x), Tolerance); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void L2Normalize(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + + float sumOfSquares = 0f; + for (int i = 0; i < x.Length; i++) + { + sumOfSquares += x[i] * x[i]; + } + + Assert.Equal(Math.Sqrt(sumOfSquares), TensorPrimitives.L2Normalize(x), Tolerance); } [Theory] [MemberData(nameof(TensorLengths))] public static void SoftMax_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory 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[] { 3, 4, 1 }, new float[] { 0.2594f, 0.705384f, 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]; + using BoundedMemory dest = CreateTensor(x.Length); TensorPrimitives.SoftMax(x, dest); for (int i = 0; i < x.Length; i++) { - Assert.Equal(expectedResult[i], dest[i], .001f); + Assert.Equal(expectedResult[i], dest[i], Tolerance); } } [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]; + float[] x = [3, 1, .2f]; + float[] expectedResult = [0.8360188f, 0.11314284f, 0.05083836f]; + using BoundedMemory dest = CreateTensor(x.Length + 1); TensorPrimitives.SoftMax(x, dest); for (int i = 0; i < x.Length; i++) { - Assert.Equal(expectedResult[i], dest[i], .001f); + Assert.Equal(expectedResult[i], dest[i], Tolerance); } } [Fact] - public static void SoftMax_ThrowsForEmpty_x_y() + public static void SoftMax_ThrowsForEmptyInput() { - var x = new float[] { }; - var dest = new float[x.Length]; - - AssertExtensions.Throws(() => TensorPrimitives.SoftMax(x, dest)); + AssertExtensions.Throws(() => TensorPrimitives.SoftMax(ReadOnlySpan.Empty, CreateTensor(1))); } [Theory] [MemberData(nameof(TensorLengths))] public static void Sigmoid_ThrowsForTooShortDestination(int tensorLength) { - float[] x = CreateAndFillTensor(tensorLength); - float[] destination = CreateTensor(tensorLength - 1); + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); AssertExtensions.Throws("destination", () => TensorPrimitives.Sigmoid(x, destination)); } @@ -855,36 +912,573 @@ public static void Sigmoid_ThrowsForTooShortDestination(int tensorLength) [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]; + using BoundedMemory dest = CreateTensor(x.Length); TensorPrimitives.Sigmoid(x, dest); for (int i = 0; i < x.Length; i++) { - Assert.Equal(expectedResult[i], dest[i], .001f); + Assert.Equal(expectedResult[i], dest[i], Tolerance); } } [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]; + float[] x = [-5, -4.5f, -4]; + float[] expectedResult = [0.0066f, 0.0109f, 0.0179f]; + using BoundedMemory dest = CreateTensor(x.Length + 1); + TensorPrimitives.Sigmoid(x, dest); + float originalLast = dest[dest.Length - 1]; for (int i = 0; i < x.Length; i++) { - Assert.Equal(expectedResult[i], dest[i], .001f); + Assert.Equal(expectedResult[i], dest[i], Tolerance); + } + Assert.Equal(originalLast, dest[dest.Length - 1]); + } + + [Fact] + public static void Sigmoid_ThrowsForEmptyInput() + { + AssertExtensions.Throws(() => TensorPrimitives.Sigmoid(ReadOnlySpan.Empty, CreateTensor(1))); + } + + [Fact] + public static void IndexOfMax_ReturnsNegative1OnEmpty() + { + Assert.Equal(-1, TensorPrimitives.IndexOfMax(ReadOnlySpan.Empty)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void IndexOfMax(int tensorLength) + { + foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + x[expected] = Enumerable.Max(MemoryMarshal.ToEnumerable(x.Memory)) + 1; + Assert.Equal(expected, TensorPrimitives.IndexOfMax(x)); + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void IndexOfMax_FirstNaNReturned(int tensorLength) + { + foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + x[expected] = float.NaN; + x[tensorLength - 1] = float.NaN; + Assert.Equal(expected, TensorPrimitives.IndexOfMax(x)); + } + } + + [Fact] + public static void IndexOfMax_Negative0LesserThanPositive0() + { + Assert.Equal(1, TensorPrimitives.IndexOfMax([-0f, +0f])); + Assert.Equal(0, TensorPrimitives.IndexOfMax([-0f, -0f, -0f, -0f])); + Assert.Equal(4, TensorPrimitives.IndexOfMax([-0f, -0f, -0f, -0f, +0f, +0f, +0f])); + Assert.Equal(0, TensorPrimitives.IndexOfMax([+0f, -0f])); + Assert.Equal(1, TensorPrimitives.IndexOfMax([-1, -0f])); + Assert.Equal(2, TensorPrimitives.IndexOfMax([-1, -0f, 1])); + } + + [Fact] + public static void IndexOfMin_ReturnsNegative1OnEmpty() + { + Assert.Equal(-1, TensorPrimitives.IndexOfMin(ReadOnlySpan.Empty)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void IndexOfMin(int tensorLength) + { + foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + x[expected] = Enumerable.Min(MemoryMarshal.ToEnumerable(x.Memory)) - 1; + Assert.Equal(expected, TensorPrimitives.IndexOfMin(x)); + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void IndexOfMin_FirstNaNReturned(int tensorLength) + { + foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + x[expected] = float.NaN; + x[tensorLength - 1] = float.NaN; + Assert.Equal(expected, TensorPrimitives.IndexOfMin(x)); + } + } + + [Fact] + public static void IndexOfMin_Negative0LesserThanPositive0() + { + Assert.Equal(0, TensorPrimitives.IndexOfMin([-0f, +0f])); + Assert.Equal(1, TensorPrimitives.IndexOfMin([+0f, -0f])); + Assert.Equal(1, TensorPrimitives.IndexOfMin([+0f, -0f, -0f, -0f, -0f])); + Assert.Equal(0, TensorPrimitives.IndexOfMin([-1, -0f])); + Assert.Equal(0, TensorPrimitives.IndexOfMin([-1, -0f, 1])); + } + + [Fact] + public static void IndexOfMaxMagnitude_ReturnsNegative1OnEmpty() + { + Assert.Equal(-1, TensorPrimitives.IndexOfMaxMagnitude(ReadOnlySpan.Empty)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void IndexOfMaxMagnitude(int tensorLength) + { + foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + x[expected] = Enumerable.Max(MemoryMarshal.ToEnumerable(x.Memory), Math.Abs) + 1; + Assert.Equal(expected, TensorPrimitives.IndexOfMaxMagnitude(x)); + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void IndexOfMaxMagnitude_FirstNaNReturned(int tensorLength) + { + foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + x[expected] = float.NaN; + x[tensorLength - 1] = float.NaN; + Assert.Equal(expected, TensorPrimitives.IndexOfMaxMagnitude(x)); + } + } + + [Fact] + public static void IndexOfMaxMagnitude_Negative0LesserThanPositive0() + { + Assert.Equal(0, TensorPrimitives.IndexOfMaxMagnitude([-0f, -0f, -0f, -0f])); + Assert.Equal(1, TensorPrimitives.IndexOfMaxMagnitude([-0f, +0f])); + Assert.Equal(1, TensorPrimitives.IndexOfMaxMagnitude([-0f, +0f, +0f, +0f])); + Assert.Equal(0, TensorPrimitives.IndexOfMaxMagnitude([+0f, -0f])); + Assert.Equal(0, TensorPrimitives.IndexOfMaxMagnitude([-1, -0f])); + Assert.Equal(2, TensorPrimitives.IndexOfMaxMagnitude([-1, -0f, 1])); + } + + [Fact] + public static void IndexOfMinMagnitude_ReturnsNegative1OnEmpty() + { + Assert.Equal(-1, TensorPrimitives.IndexOfMinMagnitude(ReadOnlySpan.Empty)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void IndexOfMinMagnitude(int tensorLength) + { + foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) + { + using BoundedMemory x = CreateTensor(tensorLength); + for (int i = 0; i < x.Length; i++) + { + x[i] = i % 2 == 0 ? 42 : -42; + } + + x[expected] = -41; + + Assert.Equal(expected, TensorPrimitives.IndexOfMinMagnitude(x)); + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void IndexOfMinMagnitude_FirstNaNReturned(int tensorLength) + { + foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + x[expected] = float.NaN; + x[tensorLength - 1] = float.NaN; + Assert.Equal(expected, TensorPrimitives.IndexOfMinMagnitude(x)); + } + } + + [Fact] + public static void IndexOfMinMagnitude_Negative0LesserThanPositive0() + { + Assert.Equal(0, TensorPrimitives.IndexOfMinMagnitude([-0f, -0f, -0f, -0f])); + Assert.Equal(0, TensorPrimitives.IndexOfMinMagnitude([-0f, +0f])); + Assert.Equal(1, TensorPrimitives.IndexOfMinMagnitude([+0f, -0f])); + Assert.Equal(1, TensorPrimitives.IndexOfMinMagnitude([+0f, -0f, -0f, -0f])); + Assert.Equal(1, TensorPrimitives.IndexOfMinMagnitude([-1, -0f])); + Assert.Equal(1, TensorPrimitives.IndexOfMinMagnitude([-1, -0f, 1])); + } + + [Fact] + public static void Max_ThrowsForEmpty() + { + Assert.Throws(() => TensorPrimitives.Max(ReadOnlySpan.Empty)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Max(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + + Assert.Equal(Enumerable.Max(MemoryMarshal.ToEnumerable(x.Memory)), TensorPrimitives.Max(x)); + + float max = float.NegativeInfinity; + foreach (float f in x.Span) + { + max = Math.Max(max, f); + } + Assert.Equal(max, TensorPrimitives.Max(x)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Max_NanReturned(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) + { + x[expected] = float.NaN; + Assert.Equal(float.NaN, TensorPrimitives.Max(x)); + } + } + + [Fact] + public static void Max_Negative0LesserThanPositive0() + { + Assert.Equal(+0f, TensorPrimitives.Max([-0f, +0f])); + Assert.Equal(+0f, TensorPrimitives.Max([+0f, -0f])); + Assert.Equal(-0f, TensorPrimitives.Max([-1, -0f])); + Assert.Equal(1, TensorPrimitives.Max([-1, -0f, 1])); + } + + [Fact] + public static void MaxMagnitude_ThrowsForEmpty() + { + Assert.Throws(() => TensorPrimitives.MaxMagnitude(ReadOnlySpan.Empty)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + 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) + { + max = Math.Max(max, MathF.Abs(f)); + } + Assert.Equal(max, TensorPrimitives.MaxMagnitude(x)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void MaxMagnitude_NanReturned(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) + { + x[expected] = float.NaN; + Assert.Equal(float.NaN, TensorPrimitives.MaxMagnitude(x)); + } + } + + [Fact] + 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, 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])); + } + + [Fact] + public static void Min_ThrowsForEmpty() + { + Assert.Throws(() => TensorPrimitives.Min(ReadOnlySpan.Empty)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Min(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + + Assert.Equal(Enumerable.Min(MemoryMarshal.ToEnumerable(x.Memory)), TensorPrimitives.Min(x)); + + float min = float.PositiveInfinity; + foreach (float f in x.Span) + { + min = Math.Min(min, f); + } + Assert.Equal(min, TensorPrimitives.Min(x)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Min_NanReturned(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) + { + x[expected] = float.NaN; + Assert.Equal(float.NaN, TensorPrimitives.Min(x)); + } + } + + [Fact] + public static void Min_Negative0LesserThanPositive0() + { + Assert.Equal(-0f, TensorPrimitives.Min([-0f, +0f])); + Assert.Equal(-0f, TensorPrimitives.Min([+0f, -0f])); + Assert.Equal(-1, TensorPrimitives.Min([-1, -0f])); + Assert.Equal(-1, TensorPrimitives.Min([-1, -0f, 1])); + } + + [Fact] + public static void MinMagnitude_ThrowsForEmpty() + { + Assert.Throws(() => TensorPrimitives.MinMagnitude(ReadOnlySpan.Empty)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + 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) + { + min = Math.Min(min, MathF.Abs(f)); + } + Assert.Equal(min, TensorPrimitives.MinMagnitude(x)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void MinMagnitude_NanReturned(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) + { + x[expected] = float.NaN; + Assert.Equal(float.NaN, TensorPrimitives.MinMagnitude(x)); + } + } + + [Fact] + public static void MinMagnitude_Negative0LesserThanPositive0() + { + Assert.Equal(0, TensorPrimitives.MinMagnitude([-0f, +0f])); + Assert.Equal(0, TensorPrimitives.MinMagnitude([+0f, -0f])); + Assert.Equal(0, TensorPrimitives.MinMagnitude([-1, -0f])); + Assert.Equal(0, TensorPrimitives.MinMagnitude([-1, -0f, 1])); + } + + [Fact] + public static void Product_ThrowsForEmpty() + { + Assert.Throws(() => TensorPrimitives.Product(ReadOnlySpan.Empty)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Product(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + + float f = x[0]; + for (int i = 1; i < x.Length; i++) + { + f *= x[i]; } + + Assert.Equal(f, TensorPrimitives.Product(x), Tolerance); } [Fact] - public static void Sigmoid_ThrowsForEmpty_x_y() + public static void Product_KnownValues() { - var x = new float[] { }; - var dest = new float[x.Length]; + Assert.Equal(1, TensorPrimitives.Product([1])); + Assert.Equal(-2, TensorPrimitives.Product([1, -2])); + Assert.Equal(-6, TensorPrimitives.Product([1, -2, 3])); + Assert.Equal(24, TensorPrimitives.Product([1, -2, 3, -4])); + Assert.Equal(120, TensorPrimitives.Product([1, -2, 3, -4, 5])); + Assert.Equal(-720, TensorPrimitives.Product([1, -2, 3, -4, 5, -6])); + Assert.Equal(0, TensorPrimitives.Product([1, -2, 3, -4, 5, -6, 0])); + Assert.Equal(0, TensorPrimitives.Product([0, 1, -2, 3, -4, 5, -6])); + Assert.Equal(0, TensorPrimitives.Product([1, -2, 3, 0, -4, 5, -6])); + Assert.Equal(float.NaN, TensorPrimitives.Product([1, -2, 3, float.NaN, -4, 5, -6])); + } + + [Fact] + public static void ProductOfDifferences_ThrowsForEmptyAndMismatchedLengths() + { + Assert.Throws(() => TensorPrimitives.ProductOfDifferences(ReadOnlySpan.Empty, ReadOnlySpan.Empty)); + Assert.Throws(() => TensorPrimitives.ProductOfDifferences(ReadOnlySpan.Empty, CreateTensor(1))); + Assert.Throws(() => TensorPrimitives.ProductOfDifferences(CreateTensor(1), ReadOnlySpan.Empty)); + Assert.Throws(() => TensorPrimitives.ProductOfDifferences(CreateTensor(44), CreateTensor(43))); + Assert.Throws(() => TensorPrimitives.ProductOfDifferences(CreateTensor(43), CreateTensor(44))); + } - AssertExtensions.Throws(() => TensorPrimitives.Sigmoid(x, dest)); + [Theory] + [MemberData(nameof(TensorLengths))] + public static void ProductOfDifferences(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + + float f = x[0] - y[0]; + for (int i = 1; i < x.Length; i++) + { + f *= x[i] - y[i]; + } + Assert.Equal(f, TensorPrimitives.ProductOfDifferences(x, y), Tolerance); + } + + [Fact] + public static void ProductOfDifferences_KnownValues() + { + Assert.Equal(0, TensorPrimitives.ProductOfDifferences([0], [0])); + Assert.Equal(0, TensorPrimitives.ProductOfDifferences([1], [1])); + Assert.Equal(1, TensorPrimitives.ProductOfDifferences([1], [0])); + Assert.Equal(-1, TensorPrimitives.ProductOfDifferences([0], [1])); + Assert.Equal(-1, TensorPrimitives.ProductOfDifferences([1, 2, 3, 4, 5], [2, 3, 4, 5, 6])); + Assert.Equal(120, TensorPrimitives.ProductOfDifferences([1, 2, 3, 4, 5], [0, 0, 0, 0, 0])); + Assert.Equal(-120, TensorPrimitives.ProductOfDifferences([0, 0, 0, 0, 0], [1, 2, 3, 4, 5])); + Assert.Equal(float.NaN, TensorPrimitives.ProductOfDifferences([1, 2, float.NaN, 4, 5], [0, 0, 0, 0, 0])); + } + + [Fact] + public static void ProductOfSums_ThrowsForEmptyAndMismatchedLengths() + { + Assert.Throws(() => TensorPrimitives.ProductOfSums(ReadOnlySpan.Empty, ReadOnlySpan.Empty)); + Assert.Throws(() => TensorPrimitives.ProductOfSums(ReadOnlySpan.Empty, CreateTensor(1))); + Assert.Throws(() => TensorPrimitives.ProductOfSums(CreateTensor(1), ReadOnlySpan.Empty)); + Assert.Throws(() => TensorPrimitives.ProductOfSums(CreateTensor(44), CreateTensor(43))); + Assert.Throws(() => TensorPrimitives.ProductOfSums(CreateTensor(43), CreateTensor(44))); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void ProductOfSums(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + + float f = x[0] + y[0]; + for (int i = 1; i < x.Length; i++) + { + f *= x[i] + y[i]; + } + Assert.Equal(f, TensorPrimitives.ProductOfSums(x, y), Tolerance); + } + + [Fact] + public static void ProductOfSums_KnownValues() + { + Assert.Equal(0, TensorPrimitives.ProductOfSums([0], [0])); + Assert.Equal(1, TensorPrimitives.ProductOfSums([0], [1])); + Assert.Equal(1, TensorPrimitives.ProductOfSums([1], [0])); + Assert.Equal(2, TensorPrimitives.ProductOfSums([1], [1])); + Assert.Equal(10395, TensorPrimitives.ProductOfSums([1, 2, 3, 4, 5], [2, 3, 4, 5, 6])); + Assert.Equal(120, TensorPrimitives.ProductOfSums([1, 2, 3, 4, 5], [0, 0, 0, 0, 0])); + Assert.Equal(120, TensorPrimitives.ProductOfSums([0, 0, 0, 0, 0], [1, 2, 3, 4, 5])); + Assert.Equal(float.NaN, TensorPrimitives.ProductOfSums([1, 2, float.NaN, 4, 5], [0, 0, 0, 0, 0])); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Sum(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + + Assert.Equal(Enumerable.Sum(MemoryMarshal.ToEnumerable(x.Memory)), TensorPrimitives.Sum(x), Tolerance); + + float sum = 0; + foreach (float f in x.Span) + { + sum += f; + } + Assert.Equal(sum, TensorPrimitives.Sum(x), Tolerance); + } + + [Fact] + public static void Sum_KnownValues() + { + Assert.Equal(0, TensorPrimitives.Sum([0])); + Assert.Equal(1, TensorPrimitives.Sum([0, 1])); + Assert.Equal(6, TensorPrimitives.Sum([1, 2, 3])); + Assert.Equal(0, TensorPrimitives.Sum([-3, 0, 3])); + Assert.Equal(float.NaN, TensorPrimitives.Sum([-3, float.NaN, 3])); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void SumOfSquares(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + + Assert.Equal(Enumerable.Sum(MemoryMarshal.ToEnumerable(x.Memory), v => v * v), TensorPrimitives.SumOfSquares(x), Tolerance); + + float sum = 0; + foreach (float f in x.Span) + { + sum += f * f; + } + Assert.Equal(sum, TensorPrimitives.SumOfSquares(x), Tolerance); + } + + [Fact] + public static void SumOfSquares_KnownValues() + { + Assert.Equal(0, TensorPrimitives.SumOfSquares([0])); + Assert.Equal(1, TensorPrimitives.SumOfSquares([0, 1])); + Assert.Equal(14, TensorPrimitives.SumOfSquares([1, 2, 3])); + Assert.Equal(18, TensorPrimitives.SumOfSquares([-3, 0, 3])); + Assert.Equal(float.NaN, TensorPrimitives.SumOfSquares([-3, float.NaN, 3])); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void SumOfMagnitudes(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + + Assert.Equal(Enumerable.Sum(MemoryMarshal.ToEnumerable(x.Memory), MathF.Abs), TensorPrimitives.SumOfMagnitudes(x), Tolerance); + + float sum = 0; + foreach (float f in x.Span) + { + sum += MathF.Abs(f); + } + Assert.Equal(sum, TensorPrimitives.SumOfMagnitudes(x), Tolerance); + } + + [Fact] + public static void SumOfMagnitudes_KnownValues() + { + Assert.Equal(0, TensorPrimitives.SumOfMagnitudes([0])); + Assert.Equal(1, TensorPrimitives.SumOfMagnitudes([0, 1])); + Assert.Equal(6, TensorPrimitives.SumOfMagnitudes([1, 2, 3])); + Assert.Equal(6, TensorPrimitives.SumOfMagnitudes([-3, 0, 3])); + Assert.Equal(float.NaN, TensorPrimitives.SumOfMagnitudes([-3, float.NaN, 3])); } } } diff --git a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.netcore.cs b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.netcore.cs new file mode 100644 index 0000000000000..113f26048d352 --- /dev/null +++ b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.netcore.cs @@ -0,0 +1,91 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers; +using Xunit; + +namespace System.Numerics.Tensors.Tests +{ + public static partial class TensorPrimitivesTests + { + [Theory] + [InlineData(0)] + [MemberData(nameof(TensorLengths))] + public static void ConvertToHalf(int tensorLength) + { + using BoundedMemory source = CreateAndFillTensor(tensorLength); + foreach (int destLength in new[] { source.Length, source.Length + 1 }) + { + Half[] destination = new Half[destLength]; + + TensorPrimitives.ConvertToHalf(source, destination); + + for (int i = 0; i < source.Length; i++) + { + Assert.Equal((Half)source[i], destination[i]); + } + + if (destination.Length > source.Length) + { + for (int i = source.Length; i < destination.Length; i++) + { + Assert.Equal(Half.Zero, destination[i]); + } + } + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void ConvertToHalf_ThrowsForTooShortDestination(int tensorLength) + { + using BoundedMemory source = CreateAndFillTensor(tensorLength); + Half[] destination = new Half[source.Length - 1]; + + AssertExtensions.Throws("destination", () => TensorPrimitives.ConvertToHalf(source, destination)); + } + + [Theory] + [InlineData(0)] + [MemberData(nameof(TensorLengths))] + public static void ConvertToSingle(int tensorLength) + { + Half[] source = new Half[tensorLength]; + for (int i = 0; i < source.Length; i++) + { + source[i] = (Half)s_random.NextSingle(); + } + + foreach (int destLength in new[] { source.Length, source.Length + 1 }) + { + using BoundedMemory destination = CreateTensor(destLength); + destination.Span.Fill(0f); + + TensorPrimitives.ConvertToSingle(source, destination); + + for (int i = 0; i < source.Length; i++) + { + Assert.Equal((float)source[i], destination[i]); + } + + if (destination.Length > source.Length) + { + for (int i = source.Length; i < destination.Length; i++) + { + Assert.Equal(0f, destination[i]); + } + } + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void ConvertToSingle_ThrowsForTooShortDestination(int tensorLength) + { + Half[] source = new Half[tensorLength]; + using BoundedMemory destination = CreateTensor(source.Length - 1); + + AssertExtensions.Throws("destination", () => TensorPrimitives.ConvertToSingle(source, destination)); + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Math.cs b/src/libraries/System.Private.CoreLib/src/System/Math.cs index 7d20cc72202dd..266e49fc39dd9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Math.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Math.cs @@ -1033,7 +1033,7 @@ public static double Min(double val1, double val2) // // It propagates NaN inputs back to the caller and // otherwise returns the lesser of the inputs. It - // treats +0 as lesser than -0 as per the specification. + // treats +0 as greater than -0 as per the specification. if (val1 != val2) { @@ -1091,7 +1091,7 @@ public static float Min(float val1, float val2) // // It propagates NaN inputs back to the caller and // otherwise returns the lesser of the inputs. It - // treats +0 as lesser than -0 as per the specification. + // treats +0 as greater than -0 as per the specification. if (val1 != val2) { @@ -1145,7 +1145,7 @@ public static double MinMagnitude(double x, double y) // // It propagates NaN inputs back to the caller and // otherwise returns the input with a lesser magnitude. - // It treats +0 as lesser than -0 as per the specification. + // It treats +0 as greater than -0 as per the specification. double ax = Abs(x); double ay = Abs(y); diff --git a/src/libraries/System.Private.CoreLib/src/System/MathF.cs b/src/libraries/System.Private.CoreLib/src/System/MathF.cs index 2726d14492f6a..de0efc14f0ac4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MathF.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MathF.cs @@ -285,7 +285,7 @@ public static float MinMagnitude(float x, float y) // // It propagates NaN inputs back to the caller and // otherwise returns the input with a lesser magnitude. - // It treats +0 as lesser than -0 as per the specification. + // It treats +0 as greater than -0 as per the specification. float ax = Abs(x); float ay = Abs(y);