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 d28d4bacafdb8e..a4429a1eba080d 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 @@ -6,6 +6,14 @@ namespace System.Numerics.Tensors /// Performs primitive tensor operations over spans of memory. public static partial class TensorPrimitives { + /// Computes the element-wise result of: MathF.Abs(). + /// The tensor, represented as a span. + /// The destination tensor, represented as a span. + /// Destination is too short. + /// This method effectively does [i] = MathF.Abs([i]). + public static void Abs(ReadOnlySpan x, Span destination) => + InvokeSpanIntoSpan(x, destination); + /// Computes the element-wise result of: + . /// The first tensor, represented as a span. /// The second tensor, represented as a span. @@ -25,82 +33,6 @@ public static unsafe void Add(ReadOnlySpan x, ReadOnlySpan y, Span public static void Add(ReadOnlySpan x, float y, Span destination) => InvokeSpanScalarIntoSpan(x, y, destination); - /// Computes the element-wise result of: - . - /// The first tensor, represented as a span. - /// The second tensor, represented as a scalar. - /// The destination tensor, represented as a span. - /// Length of '' must be same as length of ''. - /// Destination is too short. - /// This method effectively does [i] = [i] - [i]. - public static void Subtract(ReadOnlySpan x, ReadOnlySpan y, Span destination) => - InvokeSpanSpanIntoSpan(x, y, destination); - - /// Computes the element-wise result of: - . - /// The first tensor, represented as a span. - /// The second tensor, represented as a scalar. - /// The destination tensor, represented as a span. - /// Destination is too short. - /// This method effectively does [i] = [i] - . - public static void Subtract(ReadOnlySpan x, float y, Span destination) => - InvokeSpanScalarIntoSpan(x, y, destination); - - /// Computes the element-wise result of: * . - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - /// The destination tensor, represented as a span. - /// Length of '' must be same as length of ''. - /// Destination is too short. - /// This method effectively does [i] = [i] * . - public static void Multiply(ReadOnlySpan x, ReadOnlySpan y, Span destination) => - InvokeSpanSpanIntoSpan(x, y, destination); - - /// Computes the element-wise result of: * . - /// The first tensor, represented as a span. - /// The second tensor, represented as a scalar. - /// The destination tensor, represented as a span. - /// Destination is too short. - /// - /// This method effectively does [i] = [i] * . - /// This method corresponds to the scal method defined by BLAS1. - /// - public static void Multiply(ReadOnlySpan x, float y, Span destination) => - InvokeSpanScalarIntoSpan(x, y, destination); - - /// Computes the element-wise result of: / . - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - /// The destination tensor, represented as a span. - /// Length of '' must be same as length of ''. - /// Destination is too short. - /// This method effectively does [i] = [i] / . - public static void Divide(ReadOnlySpan x, ReadOnlySpan y, Span destination) => - InvokeSpanSpanIntoSpan(x, y, destination); - - /// Computes the element-wise result of: / . - /// The first tensor, represented as a span. - /// The second tensor, represented as a scalar. - /// The destination tensor, represented as a span. - /// Destination is too short. - /// This method effectively does [i] = [i] / . - public static void Divide(ReadOnlySpan x, float y, Span destination) => - InvokeSpanScalarIntoSpan(x, y, destination); - - /// Computes the element-wise result of: -. - /// The tensor, represented as a span. - /// The destination tensor, represented as a span. - /// Destination is too short. - /// This method effectively does [i] = -[i]. - public static void Negate(ReadOnlySpan x, Span destination) => - InvokeSpanIntoSpan(x, destination); - - /// Computes the element-wise result of: MathF.Abs(). - /// The tensor, represented as a span. - /// The destination tensor, represented as a span. - /// Destination is too short. - /// This method effectively does [i] = MathF.Abs([i]). - public static void Abs(ReadOnlySpan x, Span destination) => - InvokeSpanIntoSpan(x, destination); - /// Computes the element-wise result of: ( + ) * . /// The first tensor, represented as a span. /// The second tensor, represented as a span. @@ -135,97 +67,6 @@ public static void AddMultiply(ReadOnlySpan x, ReadOnlySpan y, flo public static void AddMultiply(ReadOnlySpan x, float y, ReadOnlySpan multiplier, Span destination) => InvokeSpanScalarSpanIntoSpan(x, y, multiplier, destination); - /// Computes the element-wise result of: ( * ) + . - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - /// The third tensor, represented as a span. - /// The destination tensor, represented as a span. - /// Length of '' must be same as length of ''. - /// Length of '' must be same as length of ''. - /// Destination is too short. - /// This method effectively does [i] = ([i] * [i]) + [i]. - public static void MultiplyAdd(ReadOnlySpan x, ReadOnlySpan y, ReadOnlySpan addend, Span destination) => - InvokeSpanSpanSpanIntoSpan(x, y, addend, destination); - - /// Computes the element-wise result of: ( * ) + . - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - /// The third tensor, represented as a span. - /// The destination tensor, represented as a span. - /// Length of '' must be same as length of ''. - /// Destination is too short. - /// - /// This method effectively does [i] = ([i] * [i]) + . - /// This method corresponds to the axpy method defined by BLAS1. - /// - public static void MultiplyAdd(ReadOnlySpan x, ReadOnlySpan y, float addend, Span destination) => - InvokeSpanSpanScalarIntoSpan(x, y, addend, destination); - - /// Computes the element-wise result of: ( * ) + . - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - /// The third tensor, represented as a span. - /// The destination tensor, represented as a span. - /// Length of '' must be same as length of ''. - /// Destination is too short. - /// This method effectively does [i] = ([i] * ) + [i]. - public static void MultiplyAdd(ReadOnlySpan x, float y, ReadOnlySpan addend, Span destination) => - InvokeSpanScalarSpanIntoSpan(x, y, addend, destination); - - /// Computes the element-wise result of: pow(e, ). - /// The tensor, represented as a span. - /// The destination tensor, represented as a span. - /// Destination is too short. - /// This method effectively does [i] = .Exp([i]). - public static void Exp(ReadOnlySpan x, Span destination) - { - if (x.Length > destination.Length) - { - ThrowHelper.ThrowArgument_DestinationTooShort(); - } - - for (int i = 0; i < x.Length; i++) - { - destination[i] = MathF.Exp(x[i]); - } - } - - /// Computes the element-wise result of: ln(). - /// The tensor, represented as a span. - /// The destination tensor, represented as a span. - /// Destination is too short. - /// This method effectively does [i] = .Log([i]). - public static void Log(ReadOnlySpan x, Span destination) - { - if (x.Length > destination.Length) - { - ThrowHelper.ThrowArgument_DestinationTooShort(); - } - - for (int i = 0; i < x.Length; i++) - { - destination[i] = MathF.Log(x[i]); - } - } - - /// Computes the element-wise result of: log2(). - /// The tensor, represented as a span. - /// The destination tensor, represented as a span. - /// Destination is too short. - /// This method effectively does [i] = .Log2([i]). - public static void Log2(ReadOnlySpan x, Span destination) - { - if (x.Length > destination.Length) - { - ThrowHelper.ThrowArgument_DestinationTooShort(); - } - - for (int i = 0; i < x.Length; i++) - { - destination[i] = Log2(x[i]); - } - } - /// Computes the element-wise result of: cosh(). /// The tensor, represented as a span. /// The destination tensor, represented as a span. @@ -244,42 +85,6 @@ public static void Cosh(ReadOnlySpan x, Span destination) } } - /// Computes the element-wise result of: sinh(). - /// The tensor, represented as a span. - /// The destination tensor, represented as a span. - /// Destination is too short. - /// This method effectively does [i] = .Sinh([i]). - public static void Sinh(ReadOnlySpan x, Span destination) - { - if (x.Length > destination.Length) - { - ThrowHelper.ThrowArgument_DestinationTooShort(); - } - - for (int i = 0; i < x.Length; i++) - { - destination[i] = MathF.Sinh(x[i]); - } - } - - /// Computes the element-wise result of: tanh(). - /// The tensor, represented as a span. - /// The destination tensor, represented as a span. - /// Destination is too short. - /// This method effectively does [i] = .Tanh([i]). - public static void Tanh(ReadOnlySpan x, Span destination) - { - if (x.Length > destination.Length) - { - ThrowHelper.ThrowArgument_DestinationTooShort(); - } - - for (int i = 0; i < x.Length; i++) - { - destination[i] = MathF.Tanh(x[i]); - } - } - /// Computes the cosine similarity between two non-zero vectors. /// The first tensor, represented as a span. /// The second tensor, represented as a span. @@ -292,6 +97,7 @@ public static float CosineSimilarity(ReadOnlySpan x, ReadOnlySpan { ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); } + if (x.Length != y.Length) { ThrowHelper.ThrowArgument_SpansMustHaveSameLength(); @@ -314,6 +120,7 @@ public static float Distance(ReadOnlySpan x, ReadOnlySpan y) { ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); } + if (x.Length != y.Length) { ThrowHelper.ThrowArgument_SpansMustHaveSameLength(); @@ -322,6 +129,25 @@ public static float Distance(ReadOnlySpan x, ReadOnlySpan y) return MathF.Sqrt(Aggregate(0f, x, y)); } + /// Computes the element-wise result of: / . + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The destination tensor, represented as a span. + /// Length of '' must be same as length of ''. + /// Destination is too short. + /// This method effectively does [i] = [i] / . + public static void Divide(ReadOnlySpan x, ReadOnlySpan y, Span destination) => + InvokeSpanSpanIntoSpan(x, y, destination); + + /// Computes the element-wise result of: / . + /// The first tensor, represented as a span. + /// The second tensor, represented as a scalar. + /// The destination tensor, represented as a span. + /// Destination is too short. + /// This method effectively does [i] = [i] / . + public static void Divide(ReadOnlySpan x, float y, Span destination) => + InvokeSpanScalarIntoSpan(x, y, destination); + /// /// A mathematical operation that takes two vectors and returns a scalar. /// @@ -339,60 +165,234 @@ public static float Dot(ReadOnlySpan x, ReadOnlySpan y) // BLAS1: return Aggregate(0f, x, y); } - /// - /// A mathematical operation that takes a vector and returns the L2 norm. - /// - /// The first tensor, represented as a span. - /// The L2 norm. - public static float Norm(ReadOnlySpan x) // BLAS1: nrm2 - { - return MathF.Sqrt(Aggregate(0f, x)); - } - - /// - /// A function that takes a collection of real numbers and returns a probability distribution. - /// - /// The first tensor, represented as a span. - /// The destination tensor. + /// Computes the element-wise result of: pow(e, ). + /// The tensor, represented as a span. + /// The destination tensor, represented as a span. /// Destination is too short. - /// '' must not be empty. - public static void SoftMax(ReadOnlySpan x, Span destination) + /// This method effectively does [i] = .Exp([i]). + public static void Exp(ReadOnlySpan x, Span destination) { - if (x.IsEmpty) - { - ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); - } if (x.Length > destination.Length) { ThrowHelper.ThrowArgument_DestinationTooShort(); } - float expSum = 0f; - for (int i = 0; i < x.Length; i++) { - expSum += MathF.Exp(x[i]); + destination[i] = MathF.Exp(x[i]); } + } - for (int i = 0; i < x.Length; i++) + /// 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) { - destination[i] = MathF.Exp(x[i]) / expSum; + 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; } - /// - /// A function that takes a real number and returns a value between 0 and 1. - /// - /// The first tensor, represented as a span. - /// The destination tensor. + /// 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 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 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 element-wise result of: ln(). + /// The tensor, represented as a span. + /// The destination tensor, represented as a span. /// Destination is too short. - /// '' must not be empty. - public static void Sigmoid(ReadOnlySpan x, Span destination) + /// This method effectively does [i] = .Log([i]). + public static void Log(ReadOnlySpan x, Span destination) { - if (x.IsEmpty) + if (x.Length > destination.Length) { - ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + ThrowHelper.ThrowArgument_DestinationTooShort(); + } + + for (int i = 0; i < x.Length; i++) + { + destination[i] = MathF.Log(x[i]); } + } + + /// Computes the element-wise result of: log2(). + /// The tensor, represented as a span. + /// The destination tensor, represented as a span. + /// Destination is too short. + /// This method effectively does [i] = .Log2([i]). + public static void Log2(ReadOnlySpan x, Span destination) + { if (x.Length > destination.Length) { ThrowHelper.ThrowArgument_DestinationTooShort(); @@ -400,7 +400,7 @@ public static void Sigmoid(ReadOnlySpan x, Span destination) for (int i = 0; i < x.Length; i++) { - destination[i] = 1f / (1 + MathF.Exp(-x[i])); + destination[i] = Log2(x[i]); } } @@ -472,57 +472,61 @@ public static void Max(ReadOnlySpan x, ReadOnlySpan y, Span } } - /// Computes the minimum element in . + /// Computes the maximum magnitude of any element in . /// The tensor, represented as a span. - /// The minimum element in . + /// The maximum magnitude of any element in . /// Length of '' must be greater than zero. - public static float Min(ReadOnlySpan x) + public static float MaxMagnitude(ReadOnlySpan x) { if (x.IsEmpty) { ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); } - float result = float.PositiveInfinity; + float result = float.NegativeInfinity; + float resultMag = float.NegativeInfinity; for (int i = 0; i < x.Length; i++) { - // This matches the IEEE 754:2019 `minimum` function + // This matches the IEEE 754:2019 `maximumMagnitude` function. // It propagates NaN inputs back to the caller and - // otherwise returns the lesser of the inputs. + // 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 (current != result) + if (currentMag != resultMag) { - if (float.IsNaN(current)) + if (float.IsNaN(currentMag)) { - return current; + return currentMag; } - if (current < result) + if (resultMag < currentMag) { result = current; + resultMag = currentMag; } } - else if (IsNegative(current)) + else if (IsNegative(result)) { result = current; + resultMag = currentMag; } } return result; } - /// Computes the element-wise result of: MathF.Min(, ). + /// Computes the element-wise result of: MathF.MaxMagnitude(, ). /// The first tensor, represented as a span. /// The second tensor, represented as a span. /// The destination tensor, represented as a span. /// Length of '' must be same as length of ''. /// Destination is too short. - /// This method effectively does [i] = MathF.Min([i], [i]). - public static void Min(ReadOnlySpan x, ReadOnlySpan y, Span destination) + /// This method effectively does [i] = MathF.MaxMagnitude([i], [i]). + public static void MaxMagnitude(ReadOnlySpan x, ReadOnlySpan y, Span destination) { if (x.Length != y.Length) { @@ -536,65 +540,61 @@ public static void Min(ReadOnlySpan x, ReadOnlySpan y, Span for (int i = 0; i < x.Length; i++) { - destination[i] = MathF.Min(x[i], y[i]); + destination[i] = MaxMagnitude(x[i], y[i]); } } - /// Computes the maximum magnitude of any element in . + /// Computes the minimum element in . /// The tensor, represented as a span. - /// The maximum magnitude of any element in . + /// The minimum element in . /// Length of '' must be greater than zero. - public static float MaxMagnitude(ReadOnlySpan x) + public static float Min(ReadOnlySpan x) { if (x.IsEmpty) { ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); } - float result = float.NegativeInfinity; - float resultMag = float.NegativeInfinity; + float result = float.PositiveInfinity; for (int i = 0; i < x.Length; i++) { - // This matches the IEEE 754:2019 `maximumMagnitude` function. + // This matches the IEEE 754:2019 `minimum` function // It propagates NaN inputs back to the caller and - // otherwise returns the input with a greater magnitude. + // otherwise returns the lesser of the inputs. // It treats +0 as greater than -0 as per the specification. float current = x[i]; - float currentMag = Math.Abs(current); - if (currentMag != resultMag) + if (current != result) { - if (float.IsNaN(currentMag)) + if (float.IsNaN(current)) { - return currentMag; + return current; } - if (resultMag < currentMag) + if (current < result) { result = current; - resultMag = currentMag; } } - else if (IsNegative(result)) + else if (IsNegative(current)) { result = current; - resultMag = currentMag; } } return result; } - /// Computes the element-wise result of: MathF.MaxMagnitude(, ). + /// Computes the element-wise result of: MathF.Min(, ). /// The first tensor, represented as a span. /// The second tensor, represented as a span. /// The destination tensor, represented as a span. /// Length of '' must be same as length of ''. /// Destination is too short. - /// This method effectively does [i] = MathF.MaxMagnitude([i], [i]). - public static void MaxMagnitude(ReadOnlySpan x, ReadOnlySpan y, Span destination) + /// This method effectively does [i] = MathF.Min([i], [i]). + public static void Min(ReadOnlySpan x, ReadOnlySpan y, Span destination) { if (x.Length != y.Length) { @@ -608,7 +608,7 @@ public static void MaxMagnitude(ReadOnlySpan x, ReadOnlySpan y, Sp for (int i = 0; i < x.Length; i++) { - destination[i] = MaxMagnitude(x[i], y[i]); + destination[i] = MathF.Min(x[i], y[i]); } } @@ -684,204 +684,239 @@ public static void MinMagnitude(ReadOnlySpan x, ReadOnlySpan y, Sp } } - /// 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; + /// Computes the element-wise result of: * . + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The destination tensor, represented as a span. + /// Length of '' must be same as length of ''. + /// Destination is too short. + /// This method effectively does [i] = [i] * . + public static void Multiply(ReadOnlySpan x, ReadOnlySpan y, Span destination) => + InvokeSpanSpanIntoSpan(x, y, destination); - if (!x.IsEmpty) - { - float max = float.NegativeInfinity; + /// Computes the element-wise result of: * . + /// The first tensor, represented as a span. + /// The second tensor, represented as a scalar. + /// The destination tensor, represented as a span. + /// Destination is too short. + /// + /// This method effectively does [i] = [i] * . + /// This method corresponds to the scal method defined by BLAS1. + /// + public static void Multiply(ReadOnlySpan x, float y, Span destination) => + InvokeSpanScalarIntoSpan(x, y, destination); - 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. + /// Computes the element-wise result of: ( * ) + . + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The third tensor, represented as a span. + /// The destination tensor, represented as a span. + /// Length of '' must be same as length of ''. + /// Length of '' must be same as length of ''. + /// Destination is too short. + /// This method effectively does [i] = ([i] * [i]) + [i]. + public static void MultiplyAdd(ReadOnlySpan x, ReadOnlySpan y, ReadOnlySpan addend, Span destination) => + InvokeSpanSpanSpanIntoSpan(x, y, addend, destination); - float current = x[i]; + /// Computes the element-wise result of: ( * ) + . + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The third tensor, represented as a span. + /// The destination tensor, represented as a span. + /// Length of '' must be same as length of ''. + /// Destination is too short. + /// + /// This method effectively does [i] = ([i] * [i]) + . + /// This method corresponds to the axpy method defined by BLAS1. + /// + public static void MultiplyAdd(ReadOnlySpan x, ReadOnlySpan y, float addend, Span destination) => + InvokeSpanSpanScalarIntoSpan(x, y, addend, destination); - if (current != max) - { - if (float.IsNaN(current)) - { - return i; - } + /// Computes the element-wise result of: ( * ) + . + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The third tensor, represented as a span. + /// The destination tensor, represented as a span. + /// Length of '' must be same as length of ''. + /// Destination is too short. + /// This method effectively does [i] = ([i] * ) + [i]. + public static void MultiplyAdd(ReadOnlySpan x, float y, ReadOnlySpan addend, Span destination) => + InvokeSpanScalarSpanIntoSpan(x, y, addend, destination); - if (max < current) - { - result = i; - max = current; - } - } - else if (IsNegative(max) && !IsNegative(current)) - { - result = i; - max = current; - } - } - } + /// Computes the element-wise result of: -. + /// The tensor, represented as a span. + /// The destination tensor, represented as a span. + /// Destination is too short. + /// This method effectively does [i] = -[i]. + public static void Negate(ReadOnlySpan x, Span destination) => + InvokeSpanIntoSpan(x, destination); - return result; - } + /// + /// A mathematical operation that takes a vector and returns the L2 norm. + /// + /// The first tensor, represented as a span. + /// The L2 norm. + public static float Norm(ReadOnlySpan x) => // BLAS1: nrm2 + MathF.Sqrt(Aggregate(0f, x)); - /// Computes the index of the minimum element in . + /// Computes the product of all elements 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) + /// The result of multiplying all elements in . + /// Length of '' must be greater than zero. + public static float Product(ReadOnlySpan x) { - int result = -1; - - if (!x.IsEmpty) + 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. + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } - float current = x[i]; + return Aggregate(1.0f, x); + } - if (current != min) - { - if (float.IsNaN(current)) - { - return i; - } + /// 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 (current < min) - { - result = i; - min = current; - } - } - else if (IsNegative(current) && !IsNegative(min)) - { - result = i; - min = current; - } - } + if (x.Length != y.Length) + { + ThrowHelper.ThrowArgument_SpansMustHaveSameLength(); } - return result; + return Aggregate(1.0f, x, y); } - /// 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) + /// 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) { - int result = -1; - - if (!x.IsEmpty) + if (x.IsEmpty) { - float max = float.NegativeInfinity; - float maxMag = float.NegativeInfinity; + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } - 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. + if (x.Length != y.Length) + { + ThrowHelper.ThrowArgument_SpansMustHaveSameLength(); + } - float current = x[i]; - float currentMag = Math.Abs(current); + return Aggregate(1.0f, x, y); + } - if (currentMag != maxMag) - { - if (float.IsNaN(currentMag)) - { - return i; - } + /// + /// A function that takes a real number and returns a value between 0 and 1. + /// + /// The first tensor, represented as a span. + /// The destination tensor. + /// Destination is too short. + /// '' must not be empty. + public static void Sigmoid(ReadOnlySpan x, Span destination) + { + if (x.IsEmpty) + { + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } - if (maxMag < currentMag) - { - result = i; - max = current; - maxMag = currentMag; - } - } - else if (IsNegative(max) && !IsNegative(current)) - { - result = i; - max = current; - maxMag = currentMag; - } - } + if (x.Length > destination.Length) + { + ThrowHelper.ThrowArgument_DestinationTooShort(); } - return result; + for (int i = 0; i < x.Length; i++) + { + destination[i] = 1f / (1 + MathF.Exp(-x[i])); + } } - /// Computes the index of the element in with the minimum magnitude. + /// Computes the element-wise result of: sinh(). /// 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) + /// The destination tensor, represented as a span. + /// Destination is too short. + /// This method effectively does [i] = .Sinh([i]). + public static void Sinh(ReadOnlySpan x, Span destination) { - int result = -1; + if (x.Length > destination.Length) + { + ThrowHelper.ThrowArgument_DestinationTooShort(); + } - if (!x.IsEmpty) + for (int i = 0; i < x.Length; i++) { - float min = float.PositiveInfinity; - float minMag = float.PositiveInfinity; + destination[i] = MathF.Sinh(x[i]); + } + } - 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. + /// + /// A function that takes a collection of real numbers and returns a probability distribution. + /// + /// The first tensor, represented as a span. + /// The destination tensor. + /// Destination is too short. + /// '' must not be empty. + public static void SoftMax(ReadOnlySpan x, Span destination) + { + if (x.IsEmpty) + { + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } - float current = x[i]; - float currentMag = Math.Abs(current); + if (x.Length > destination.Length) + { + ThrowHelper.ThrowArgument_DestinationTooShort(); + } - if (currentMag != minMag) - { - if (float.IsNaN(currentMag)) - { - return i; - } + float expSum = 0f; - if (currentMag < minMag) - { - result = i; - min = current; - minMag = currentMag; - } - } - else if (IsNegative(current) && !IsNegative(min)) - { - result = i; - min = current; - minMag = currentMag; - } - } + for (int i = 0; i < x.Length; i++) + { + expSum += MathF.Exp(x[i]); } - return result; + for (int i = 0; i < x.Length; i++) + { + destination[i] = MathF.Exp(x[i]) / expSum; + } } + /// Computes the element-wise result of: - . + /// The first tensor, represented as a span. + /// The second tensor, represented as a scalar. + /// The destination tensor, represented as a span. + /// Length of '' must be same as length of ''. + /// Destination is too short. + /// This method effectively does [i] = [i] - [i]. + public static void Subtract(ReadOnlySpan x, ReadOnlySpan y, Span destination) => + InvokeSpanSpanIntoSpan(x, y, destination); + + /// Computes the element-wise result of: - . + /// The first tensor, represented as a span. + /// The second tensor, represented as a scalar. + /// The destination tensor, represented as a span. + /// Destination is too short. + /// This method effectively does [i] = [i] - . + public static void Subtract(ReadOnlySpan x, float y, Span destination) => + InvokeSpanScalarIntoSpan(x, y, destination); + /// 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. @@ -892,62 +927,29 @@ public static float SumOfSquares(ReadOnlySpan x) => public static float SumOfMagnitudes(ReadOnlySpan x) => Aggregate(0f, x); - /// Computes the product of all elements in . + /// Computes the sum of the squares of every element 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); - } + /// 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 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) + /// Computes the element-wise result of: tanh(). + /// The tensor, represented as a span. + /// The destination tensor, represented as a span. + /// Destination is too short. + /// This method effectively does [i] = .Tanh([i]). + public static void Tanh(ReadOnlySpan x, Span destination) { - if (x.IsEmpty) + if (x.Length > destination.Length) { - ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + ThrowHelper.ThrowArgument_DestinationTooShort(); } - if (x.Length != y.Length) + for (int i = 0; i < x.Length; i++) { - ThrowHelper.ThrowArgument_SpansMustHaveSameLength(); + destination[i] = MathF.Tanh(x[i]); } - - return Aggregate(1.0f, x, y); } } } diff --git a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs index 288cd3edb8d5e6..1f2fc37ad1f086 100644 --- a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs +++ b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs @@ -13,6 +13,7 @@ namespace System.Numerics.Tensors.Tests { public static partial class TensorPrimitivesTests { + #region Test Utilities private const double Tolerance = 0.0001; public static IEnumerable TensorLengthsIncluding0 => @@ -46,10 +47,39 @@ private static float NextSingle() // For testing purposes, get a mix of negative and positive values. return (float)((s_random.NextDouble() * 2) - 1); } + #endregion + #region Abs [Theory] [MemberData(nameof(TensorLengthsIncluding0))] - public static void AddTwoTensors(int tensorLength) + public static void Abs(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); + + TensorPrimitives.Abs(x, destination); + + for (int i = 0; i < x.Length; i++) + { + Assert.Equal(MathF.Abs(x[i]), destination[i], Tolerance); + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Abs_ThrowsForTooShortDestination(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); + + AssertExtensions.Throws("destination", () => TensorPrimitives.Abs(x, destination)); + } + #endregion + + #region Add + [Theory] + [MemberData(nameof(TensorLengthsIncluding0))] + public static void Add_TwoTensors(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); using BoundedMemory y = CreateAndFillTensor(tensorLength); @@ -65,18 +95,19 @@ public static void AddTwoTensors(int tensorLength) [Theory] [MemberData(nameof(TensorLengths))] - public static void AddTwoTensors_ThrowsForMismatchedLengths(int tensorLength) + public static void Add_TwoTensors_ThrowsForMismatchedLengths(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); using BoundedMemory destination = CreateTensor(tensorLength); Assert.Throws(() => TensorPrimitives.Add(x, y, destination)); + Assert.Throws(() => TensorPrimitives.Add(y, x, destination)); } [Theory] [MemberData(nameof(TensorLengths))] - public static void AddTwoTensors_ThrowsForTooShortDestination(int tensorLength) + public static void Add_TwoTensors_ThrowsForTooShortDestination(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); using BoundedMemory y = CreateAndFillTensor(tensorLength); @@ -87,7 +118,7 @@ public static void AddTwoTensors_ThrowsForTooShortDestination(int tensorLength) [Theory] [MemberData(nameof(TensorLengthsIncluding0))] - public static void AddTensorAndScalar(int tensorLength) + public static void Add_TensorScalar(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); @@ -103,7 +134,7 @@ public static void AddTensorAndScalar(int tensorLength) [Theory] [MemberData(nameof(TensorLengths))] - public static void AddTensorAndScalar_ThrowsForTooShortDestination(int tensorLength) + public static void Add_TensorScalar_ThrowsForTooShortDestination(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); @@ -111,867 +142,393 @@ public static void AddTensorAndScalar_ThrowsForTooShortDestination(int tensorLen AssertExtensions.Throws("destination", () => TensorPrimitives.Add(x, y, destination)); } + #endregion + #region AddMultiply [Theory] [MemberData(nameof(TensorLengthsIncluding0))] - public static void SubtractTwoTensors(int tensorLength) + public static void AddMultiply_ThreeTensors(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory multiplier = CreateAndFillTensor(tensorLength); using BoundedMemory destination = CreateTensor(tensorLength); - TensorPrimitives.Subtract(x, y, destination); + TensorPrimitives.AddMultiply(x, y, multiplier, destination); for (int i = 0; i < tensorLength; i++) { - Assert.Equal(x[i] - y[i], destination[i], Tolerance); + Assert.Equal((x[i] + y[i]) * multiplier[i], destination[i], Tolerance); } } [Theory] [MemberData(nameof(TensorLengths))] - public static void SubtractTwoTensors_ThrowsForMismatchedLengths(int tensorLength) - { - using BoundedMemory x = CreateAndFillTensor(tensorLength); - using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); - using BoundedMemory destination = CreateTensor(tensorLength); - - Assert.Throws(() => TensorPrimitives.Subtract(x, y, destination)); - } - - [Theory] - [MemberData(nameof(TensorLengths))] - public static void SubtractTwoTensors_ThrowsForTooShortDestination(int tensorLength) + public static void AddMultiply_ThreeTensors_ThrowsForMismatchedLengths(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); using BoundedMemory y = CreateAndFillTensor(tensorLength); - using BoundedMemory destination = CreateTensor(tensorLength - 1); - - AssertExtensions.Throws("destination", () => TensorPrimitives.Subtract(x, y, destination)); - } - - [Theory] - [MemberData(nameof(TensorLengthsIncluding0))] - public static void SubtractTensorAndScalar(int tensorLength) - { - using BoundedMemory x = CreateAndFillTensor(tensorLength); - float y = NextSingle(); + using BoundedMemory z = CreateAndFillTensor(tensorLength - 1); using BoundedMemory destination = CreateTensor(tensorLength); - TensorPrimitives.Subtract(x, y, destination); - - for (int i = 0; i < tensorLength; i++) - { - Assert.Equal(x[i] - y, destination[i], Tolerance); - } + Assert.Throws(() => TensorPrimitives.AddMultiply(x, y, z, destination)); + Assert.Throws(() => TensorPrimitives.AddMultiply(x, z, y, destination)); + Assert.Throws(() => TensorPrimitives.AddMultiply(z, x, y, destination)); } [Theory] [MemberData(nameof(TensorLengths))] - public static void SubtractTensorAndScalar_ThrowsForTooShortDestination(int tensorLength) + public static void AddMultiply_ThreeTensors_ThrowsForTooShortDestination(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); - float y = NextSingle(); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory multiplier = CreateAndFillTensor(tensorLength); using BoundedMemory destination = CreateTensor(tensorLength - 1); - AssertExtensions.Throws("destination", () => TensorPrimitives.Subtract(x, y, destination)); + AssertExtensions.Throws("destination", () => TensorPrimitives.AddMultiply(x, y, multiplier, destination)); } [Theory] [MemberData(nameof(TensorLengthsIncluding0))] - public static void MultiplyTwoTensors(int tensorLength) + public static void AddMultiply_TensorTensorScalar(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); using BoundedMemory y = CreateAndFillTensor(tensorLength); + float multiplier = NextSingle(); using BoundedMemory destination = CreateTensor(tensorLength); - TensorPrimitives.Multiply(x, y, destination); + TensorPrimitives.AddMultiply(x, y, multiplier, destination); for (int i = 0; i < tensorLength; i++) { - Assert.Equal(x[i] * y[i], destination[i], Tolerance); + Assert.Equal((x[i] + y[i]) * multiplier, destination[i], Tolerance); } } [Theory] [MemberData(nameof(TensorLengths))] - public static void MultiplyTwoTensors_ThrowsForMismatchedLengths(int tensorLength) + public static void AddMultiply_TensorTensorScalar_ThrowsForMismatchedLengths_x_y(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); + float multiplier = NextSingle(); using BoundedMemory destination = CreateTensor(tensorLength); - Assert.Throws(() => TensorPrimitives.Multiply(x, y, destination)); + Assert.Throws(() => TensorPrimitives.AddMultiply(x, y, multiplier, destination)); + Assert.Throws(() => TensorPrimitives.AddMultiply(y, x, multiplier, destination)); } [Theory] [MemberData(nameof(TensorLengths))] - public static void MultiplyTwoTensors_ThrowsForTooShortDestination(int tensorLength) + public static void AddMultiply_TensorTensorScalar_ThrowsForTooShortDestination(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); using BoundedMemory y = CreateAndFillTensor(tensorLength); + float multiplier = NextSingle(); using BoundedMemory destination = CreateTensor(tensorLength - 1); - AssertExtensions.Throws("destination", () => TensorPrimitives.Multiply(x, y, destination)); + AssertExtensions.Throws("destination", () => TensorPrimitives.AddMultiply(x, y, multiplier, destination)); } [Theory] [MemberData(nameof(TensorLengthsIncluding0))] - public static void MultiplyTensorAndScalar(int tensorLength) + public static void AddMultiply_TensorScalarTensor(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); + using BoundedMemory multiplier = CreateAndFillTensor(tensorLength); using BoundedMemory destination = CreateTensor(tensorLength); - TensorPrimitives.Multiply(x, y, destination); + TensorPrimitives.AddMultiply(x, y, multiplier, destination); for (int i = 0; i < tensorLength; i++) { - Assert.Equal(x[i] * y, destination[i], Tolerance); + Assert.Equal((x[i] + y) * multiplier[i], destination[i], Tolerance); } } [Theory] [MemberData(nameof(TensorLengths))] - public static void MultiplyTensorAndScalar_ThrowsForTooShortDestination(int tensorLength) + public static void AddMultiply_TensorScalarTensor_ThrowsForMismatchedLengths_x_z(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - using BoundedMemory destination = CreateTensor(tensorLength - 1); - - AssertExtensions.Throws("destination", () => TensorPrimitives.Multiply(x, y, destination)); - } - - [Theory] - [MemberData(nameof(TensorLengthsIncluding0))] - public static void DivideTwoTensors(int 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], Tolerance); - } - } - - [Theory] - [MemberData(nameof(TensorLengths))] - public static void DivideTwoTensors_ThrowsForMismatchedLengths(int tensorLength) - { - using BoundedMemory x = CreateAndFillTensor(tensorLength); - using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory z = CreateAndFillTensor(tensorLength - 1); using BoundedMemory destination = CreateTensor(tensorLength); - Assert.Throws(() => TensorPrimitives.Divide(x, y, destination)); + Assert.Throws(() => TensorPrimitives.AddMultiply(x, y, z, destination)); + Assert.Throws(() => TensorPrimitives.AddMultiply(z, y, x, destination)); } [Theory] [MemberData(nameof(TensorLengths))] - public static void DivideTwoTensors_ThrowsForTooShortDestination(int tensorLength) + public static void AddMultiply_TensorScalarTensor_ThrowsForTooShortDestination(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); - using BoundedMemory y = CreateAndFillTensor(tensorLength); + float y = NextSingle(); + using BoundedMemory multiplier = CreateAndFillTensor(tensorLength); using BoundedMemory destination = CreateTensor(tensorLength - 1); - AssertExtensions.Throws("destination", () => TensorPrimitives.Divide(x, y, destination)); + AssertExtensions.Throws("destination", () => TensorPrimitives.AddMultiply(x, y, multiplier, destination)); } + #endregion + #region Cosh [Theory] [MemberData(nameof(TensorLengthsIncluding0))] - public static void DivideTensorAndScalar(int tensorLength) + public static void Cosh(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); - float y = NextSingle(); using BoundedMemory destination = CreateTensor(tensorLength); - TensorPrimitives.Divide(x, y, destination); + TensorPrimitives.Cosh(x, destination); for (int i = 0; i < tensorLength; i++) { - Assert.Equal(x[i] / y, destination[i], Tolerance); + Assert.Equal(MathF.Cosh(x[i]), destination[i], Tolerance); } } [Theory] [MemberData(nameof(TensorLengths))] - public static void DivideTensorAndScalar_ThrowsForTooShortDestination(int tensorLength) + public static void Cosh_ThrowsForTooShortDestination(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); - float y = NextSingle(); using BoundedMemory destination = CreateTensor(tensorLength - 1); - AssertExtensions.Throws("destination", () => TensorPrimitives.Divide(x, y, destination)); + AssertExtensions.Throws("destination", () => TensorPrimitives.Cosh(x, destination)); } + #endregion + #region CosineSimilarity [Theory] - [MemberData(nameof(TensorLengthsIncluding0))] - public static void NegateTensor(int tensorLength) + [MemberData(nameof(TensorLengths))] + public static void CosineSimilarity_ThrowsForMismatchedLengths(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); - using BoundedMemory destination = CreateTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); - TensorPrimitives.Negate(x, destination); + Assert.Throws(() => TensorPrimitives.CosineSimilarity(x, y)); + Assert.Throws(() => TensorPrimitives.CosineSimilarity(y, x)); + } - for (int i = 0; i < tensorLength; i++) - { - Assert.Equal(-x[i], destination[i], Tolerance); - } + [Fact] + public static void CosineSimilarity_ThrowsForEmpty() + { + 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] - [MemberData(nameof(TensorLengths))] - public static void NegateTensor_ThrowsForTooShortDestination(int tensorLength) + [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_KnownValues(float[] x, float[] y, float expectedResult) { - using BoundedMemory x = CreateAndFillTensor(tensorLength); - using BoundedMemory destination = CreateTensor(tensorLength - 1); - - AssertExtensions.Throws("destination", () => TensorPrimitives.Negate(x, destination)); + Assert.Equal(expectedResult, TensorPrimitives.CosineSimilarity(x, y), Tolerance); } [Theory] - [MemberData(nameof(TensorLengthsIncluding0))] - public static void AddTwoTensorsAndMultiplyWithThirdTensor(int tensorLength) + [MemberData(nameof(TensorLengths))] + public static void CosineSimilarity(int 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); - for (int i = 0; i < tensorLength; i++) + float dot = 0f, squareX = 0f, squareY = 0f; + for (int i = 0; i < x.Length; i++) { - Assert.Equal((x[i] + y[i]) * multiplier[i], destination[i], Tolerance); + 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); + } + #endregion + + #region Distance + [Fact] + public static void Distance_ThrowsForEmpty() + { + 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 AddTwoTensorsAndMultiplyWithThirdTensor_ThrowsForMismatchedLengths_x_y(int tensorLength) + public static void Distance_ThrowsForMismatchedLengths(int 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)); + Assert.Throws(() => TensorPrimitives.Distance(x, y)); + Assert.Throws(() => TensorPrimitives.Distance(y, x)); } [Theory] - [MemberData(nameof(TensorLengths))] - public static void AddTwoTensorsAndMultiplyWithThirdTensor_ThrowsForMismatchedLengths_x_multiplier(int tensorLength) + [InlineData(new float[] { 3, 2 }, new float[] { 4, 1 }, 1.4142f)] + [InlineData(new float[] { 0, 4 }, new float[] { 6, 2 }, 6.3245f)] + [InlineData(new float[] { 1, 2, 3 }, new float[] { 4, 5, 6 }, 5.1961f)] + [InlineData(new float[] { 5, 1, 6, 10 }, new float[] { 7, 2, 8, 4 }, 6.7082f)] + public static void Distance_KnownValues(float[] x, float[] y, float expectedResult) { - 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)); + Assert.Equal(expectedResult, TensorPrimitives.Distance(x, y), Tolerance); } [Theory] [MemberData(nameof(TensorLengths))] - public static void AddTwoTensorsAndMultiplyWithThirdTensor_ThrowsForTooShortDestination(int tensorLength) + public static void Distance(int tensorLength) { 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)); + 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); } + #endregion + #region Divide [Theory] [MemberData(nameof(TensorLengthsIncluding0))] - public static void AddTwoTensorsAndMultiplyWithScalar(int tensorLength) + public static void Divide_TwoTensors(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); using BoundedMemory y = CreateAndFillTensor(tensorLength); - float multiplier = NextSingle(); using BoundedMemory destination = CreateTensor(tensorLength); - TensorPrimitives.AddMultiply(x, y, multiplier, destination); + TensorPrimitives.Divide(x, y, destination); for (int i = 0; i < tensorLength; i++) { - Assert.Equal((x[i] + y[i]) * multiplier, destination[i], Tolerance); + Assert.Equal(x[i] / y[i], destination[i], Tolerance); } } [Theory] [MemberData(nameof(TensorLengths))] - public static void AddTwoTensorsAndMultiplyWithScalar_ThrowsForMismatchedLengths_x_y(int tensorLength) + public static void Divide_TwoTensors_ThrowsForMismatchedLengths(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); - float multiplier = NextSingle(); using BoundedMemory destination = CreateTensor(tensorLength); - Assert.Throws(() => TensorPrimitives.AddMultiply(x, y, multiplier, destination)); + Assert.Throws(() => TensorPrimitives.Divide(x, y, destination)); + Assert.Throws(() => TensorPrimitives.Divide(y, x, destination)); } [Theory] [MemberData(nameof(TensorLengths))] - public static void AddTwoTensorsAndMultiplyWithScalar_ThrowsForTooShortDestination(int tensorLength) + public static void Divide_TwoTensors_ThrowsForTooShortDestination(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); using BoundedMemory y = CreateAndFillTensor(tensorLength); - float multiplier = NextSingle(); using BoundedMemory destination = CreateTensor(tensorLength - 1); - AssertExtensions.Throws("destination", () => TensorPrimitives.AddMultiply(x, y, multiplier, destination)); + AssertExtensions.Throws("destination", () => TensorPrimitives.Divide(x, y, destination)); } [Theory] [MemberData(nameof(TensorLengthsIncluding0))] - public static void AddTensorAndScalarAndMultiplyWithTensor(int tensorLength) + public static void Divide_TensorScalar(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - using BoundedMemory multiplier = CreateAndFillTensor(tensorLength); using BoundedMemory destination = CreateTensor(tensorLength); - TensorPrimitives.AddMultiply(x, y, multiplier, destination); + TensorPrimitives.Divide(x, y, destination); for (int i = 0; i < tensorLength; i++) { - Assert.Equal((x[i] + y) * multiplier[i], destination[i], Tolerance); + Assert.Equal(x[i] / y, destination[i], Tolerance); } } [Theory] [MemberData(nameof(TensorLengths))] - public static void AddTensorAndScalarAndMultiplyWithTensor_ThrowsForMismatchedLengths_x_z(int tensorLength) + public static void Divide_TensorScalar_ThrowsForTooShortDestination(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); float y = NextSingle(); - using BoundedMemory multiplier = CreateAndFillTensor(tensorLength - 1); - using BoundedMemory destination = CreateTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); - Assert.Throws(() => TensorPrimitives.AddMultiply(x, y, multiplier, destination)); + AssertExtensions.Throws("destination", () => TensorPrimitives.Divide(x, y, destination)); } + #endregion + #region Dot [Theory] [MemberData(nameof(TensorLengths))] - public static void AddTensorAndScalarAndMultiplyWithTensor_ThrowsForTooShortDestination(int tensorLength) + public static void Dot_ThrowsForMismatchedLengths_x_y(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); - float y = NextSingle(); - using BoundedMemory multiplier = CreateAndFillTensor(tensorLength); - using BoundedMemory destination = CreateTensor(tensorLength - 1); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); - AssertExtensions.Throws("destination", () => TensorPrimitives.AddMultiply(x, y, multiplier, destination)); + Assert.Throws(() => TensorPrimitives.Dot(x, y)); + Assert.Throws(() => TensorPrimitives.Dot(y, x)); + } + + [Theory] + [InlineData(new float[] { 1, 3, -5 }, new float[] { 4, -2, -1 }, 3)] + [InlineData(new float[] { 1, 2, 3 }, new float[] { 4, 5, 6 }, 32)] + [InlineData(new float[] { 1, 2, 3, 10, 8 }, new float[] { 4, 5, 6, -2, 7 }, 68)] + [InlineData(new float[] { }, new float[] { }, 0)] + public static void Dot_KnownValues(float[] x, float[] y, float expectedResult) + { + Assert.Equal(expectedResult, TensorPrimitives.Dot(x, y), Tolerance); } [Theory] [MemberData(nameof(TensorLengthsIncluding0))] - public static void MultiplyTwoTensorsAndAddWithThirdTensor(int tensorLength) + public static void Dot(int 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); - for (int i = 0; i < tensorLength; i++) + float dot = 0f; + for (int i = 0; i < x.Length; i++) { - Assert.Equal((x[i] * y[i]) + addend[i], destination[i], Tolerance); + dot += x[i] * y[i]; } + + Assert.Equal(dot, TensorPrimitives.Dot(x, y), Tolerance); } + #endregion + #region Exp [Theory] - [MemberData(nameof(TensorLengths))] - public static void MultiplyTwoTensorsAndAddWithThirdTensor_ThrowsForMismatchedLengths_x_y(int tensorLength) + [MemberData(nameof(TensorLengthsIncluding0))] + public static void Exp(int 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)); + TensorPrimitives.Exp(x, destination); + + for (int i = 0; i < tensorLength; i++) + { + Assert.Equal(MathF.Exp(x[i]), destination[i], Tolerance); + } } [Theory] [MemberData(nameof(TensorLengths))] - public static void MultiplyTwoTensorsAndAddWithThirdTensor_ThrowsForMismatchedLengths_x_multiplier(int tensorLength) + public static void Exp_ThrowsForTooShortDestination(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); - using BoundedMemory y = CreateAndFillTensor(tensorLength); - using BoundedMemory addend = CreateAndFillTensor(tensorLength - 1); - using BoundedMemory destination = CreateTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); - Assert.Throws(() => TensorPrimitives.MultiplyAdd(x, y, addend, destination)); - } - - [Theory] - [MemberData(nameof(TensorLengths))] - public static void MultiplyTwoTensorsAndAddWithThirdTensor_ThrowsForTooShortDestination(int tensorLength) - { - 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)); - } - - [Theory] - [MemberData(nameof(TensorLengthsIncluding0))] - public static void MultiplyTwoTensorsAndAddWithScalar(int tensorLength) - { - using BoundedMemory x = CreateAndFillTensor(tensorLength); - using BoundedMemory y = CreateAndFillTensor(tensorLength); - float addend = NextSingle(); - using BoundedMemory destination = CreateTensor(tensorLength); - - TensorPrimitives.MultiplyAdd(x, y, addend, destination); - - for (int i = 0; i < tensorLength; i++) - { - Assert.Equal((x[i] * y[i]) + addend, destination[i], Tolerance); - } - } - - [Theory] - [MemberData(nameof(TensorLengths))] - public static void MultiplyTwoTensorsAndAddWithScalar_ThrowsForTooShortDestination(int tensorLength) - { - using BoundedMemory x = CreateAndFillTensor(tensorLength); - using BoundedMemory y = CreateAndFillTensor(tensorLength); - float addend = NextSingle(); - using BoundedMemory destination = CreateTensor(tensorLength - 1); - - AssertExtensions.Throws("destination", () => TensorPrimitives.MultiplyAdd(x, y, addend, destination)); - } - - [Theory] - [MemberData(nameof(TensorLengthsIncluding0))] - public static void MultiplyTensorAndScalarAndAddWithTensor(int tensorLength) - { - using BoundedMemory x = CreateAndFillTensor(tensorLength); - float y = NextSingle(); - using BoundedMemory addend = CreateAndFillTensor(tensorLength); - using BoundedMemory destination = CreateTensor(tensorLength); - - TensorPrimitives.MultiplyAdd(x, y, addend, destination); - - for (int i = 0; i < tensorLength; i++) - { - Assert.Equal((x[i] * y) + addend[i], destination[i], Tolerance); - } - } - - [Theory] - [MemberData(nameof(TensorLengths))] - public static void MultiplyTensorAndScalarAndAddWithTensor_ThrowsForTooShortDestination(int tensorLength) - { - using BoundedMemory x = CreateAndFillTensor(tensorLength); - float y = NextSingle(); - using BoundedMemory addend = CreateAndFillTensor(tensorLength); - using BoundedMemory destination = CreateTensor(tensorLength - 1); - - AssertExtensions.Throws("destination", () => TensorPrimitives.MultiplyAdd(x, y, addend, destination)); - } - - [Theory] - [MemberData(nameof(TensorLengthsIncluding0))] - public static void ExpTensor(int tensorLength) - { - using BoundedMemory x = CreateAndFillTensor(tensorLength); - using BoundedMemory destination = CreateTensor(tensorLength); - - TensorPrimitives.Exp(x, destination); - - for (int i = 0; i < tensorLength; i++) - { - Assert.Equal(MathF.Exp(x[i]), destination[i], Tolerance); - } - } - - [Theory] - [MemberData(nameof(TensorLengths))] - public static void ExpTensor_ThrowsForTooShortDestination(int tensorLength) - { - using BoundedMemory x = CreateAndFillTensor(tensorLength); - using BoundedMemory destination = CreateTensor(tensorLength - 1); - - AssertExtensions.Throws("destination", () => TensorPrimitives.Exp(x, destination)); - } - - [Theory] - [MemberData(nameof(TensorLengthsIncluding0))] - public static void LogTensor(int tensorLength) - { - using BoundedMemory x = CreateAndFillTensor(tensorLength); - using BoundedMemory destination = CreateTensor(tensorLength); - - TensorPrimitives.Log(x, destination); - - for (int i = 0; i < tensorLength; i++) - { - Assert.Equal(MathF.Log(x[i]), destination[i], Tolerance); - } - } - - [Theory] - [MemberData(nameof(TensorLengths))] - public static void LogTensor_ThrowsForTooShortDestination(int tensorLength) - { - using BoundedMemory x = CreateAndFillTensor(tensorLength); - using BoundedMemory destination = CreateTensor(tensorLength - 1); - - AssertExtensions.Throws("destination", () => TensorPrimitives.Log(x, destination)); - } - - [Theory] - [MemberData(nameof(TensorLengthsIncluding0))] - public static void Log2(int tensorLength) - { - using BoundedMemory x = CreateAndFillTensor(tensorLength); - using BoundedMemory destination = CreateTensor(tensorLength); - - TensorPrimitives.Log2(x, destination); - - for (int i = 0; i < tensorLength; i++) - { - Assert.Equal(MathF.Log(x[i], 2), destination[i], Tolerance); - } - } - - [Theory] - [MemberData(nameof(TensorLengths))] - public static void Log2_ThrowsForTooShortDestination(int tensorLength) - { - using BoundedMemory x = CreateAndFillTensor(tensorLength); - using BoundedMemory destination = CreateTensor(tensorLength - 1); - - AssertExtensions.Throws("destination", () => TensorPrimitives.Log2(x, destination)); - } - - [Theory] - [MemberData(nameof(TensorLengthsIncluding0))] - public static void CoshTensor(int tensorLength) - { - using BoundedMemory x = CreateAndFillTensor(tensorLength); - using BoundedMemory destination = CreateTensor(tensorLength); - - TensorPrimitives.Cosh(x, destination); - - for (int i = 0; i < tensorLength; i++) - { - Assert.Equal(MathF.Cosh(x[i]), destination[i], Tolerance); - } - } - - [Theory] - [MemberData(nameof(TensorLengths))] - public static void CoshTensor_ThrowsForTooShortDestination(int tensorLength) - { - using BoundedMemory x = CreateAndFillTensor(tensorLength); - using BoundedMemory destination = CreateTensor(tensorLength - 1); - - AssertExtensions.Throws("destination", () => TensorPrimitives.Cosh(x, destination)); - } - - [Theory] - [MemberData(nameof(TensorLengthsIncluding0))] - public static void SinhTensor(int tensorLength) - { - using BoundedMemory x = CreateAndFillTensor(tensorLength); - using BoundedMemory destination = CreateTensor(tensorLength); - - TensorPrimitives.Sinh(x, destination); - - for (int i = 0; i < tensorLength; i++) - { - Assert.Equal(MathF.Sinh(x[i]), destination[i], Tolerance); - } - } - - [Theory] - [MemberData(nameof(TensorLengths))] - public static void SinhTensor_ThrowsForTooShortDestination(int tensorLength) - { - using BoundedMemory x = CreateAndFillTensor(tensorLength); - using BoundedMemory destination = CreateTensor(tensorLength - 1); - - AssertExtensions.Throws("destination", () => TensorPrimitives.Sinh(x, destination)); - } - - [Theory] - [MemberData(nameof(TensorLengthsIncluding0))] - public static void TanhTensor(int tensorLength) - { - using BoundedMemory x = CreateAndFillTensor(tensorLength); - using BoundedMemory destination = CreateTensor(tensorLength); - - TensorPrimitives.Tanh(x, destination); - - for (int i = 0; i < tensorLength; i++) - { - Assert.Equal(MathF.Tanh(x[i]), destination[i], Tolerance); - } - } - - [Theory] - [MemberData(nameof(TensorLengths))] - public static void TanhTensor_ThrowsForTooShortDestination(int tensorLength) - { - using BoundedMemory x = CreateAndFillTensor(tensorLength); - using BoundedMemory destination = CreateTensor(tensorLength - 1); - - AssertExtensions.Throws("destination", () => TensorPrimitives.Tanh(x, destination)); - } - - [Theory] - [MemberData(nameof(TensorLengths))] - public static void CosineSimilarity_ThrowsForMismatchedLengths_x_y(int tensorLength) - { - using BoundedMemory x = CreateAndFillTensor(tensorLength); - using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); - - Assert.Throws(() => TensorPrimitives.CosineSimilarity(x, y)); - } - - [Fact] - public static void CosineSimilarity_ThrowsForEmpty_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.48666f)] - [InlineData(new float[] { 1, 1, 1, 1, 1, 0 }, new float[] { 1, 1, 1, 1, 0, 1 }, 0.80f)] - public static void CosineSimilarity_KnownValues(float[] x, float[] y, float expectedResult) - { - 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() - { - 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) - { - using BoundedMemory x = CreateAndFillTensor(tensorLength); - using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); - - Assert.Throws(() => TensorPrimitives.Distance(x, y)); - } - - [Theory] - [InlineData(new float[] { 3, 2 }, new float[] { 4, 1 }, 1.4142f)] - [InlineData(new float[] { 0, 4 }, new float[] { 6, 2 }, 6.3245f)] - [InlineData(new float[] { 1, 2, 3 }, new float[] { 4, 5, 6 }, 5.1961f)] - [InlineData(new float[] { 5, 1, 6, 10 }, new float[] { 7, 2, 8, 4 }, 6.7082f)] - public static void Distance_KnownValues(float[] x, float[] y, float expectedResult) - { - 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) - { - using BoundedMemory x = CreateAndFillTensor(tensorLength); - using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); - - Assert.Throws(() => TensorPrimitives.Dot(x, y)); - } - - [Theory] - [InlineData(new float[] { 1, 3, -5 }, new float[] { 4, -2, -1 }, 3)] - [InlineData(new float[] { 1, 2, 3 }, new float[] { 4, 5, 6 }, 32)] - [InlineData(new float[] { 1, 2, 3, 10, 8 }, new float[] { 4, 5, 6, -2, 7 }, 68)] - [InlineData(new float[] { }, new float[] { }, 0)] - public static void Dot_KnownValues(float[] x, float[] y, float expectedResult) - { - Assert.Equal(expectedResult, TensorPrimitives.Dot(x, y), Tolerance); - } - - [Theory] - [MemberData(nameof(TensorLengthsIncluding0))] - public static void Dot(int tensorLength) - { - 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.7416575f)] - [InlineData(new float[] { 3, 4 }, 5)] - [InlineData(new float[] { 3 }, 3)] - [InlineData(new float[] { 3, 4, 1, 2 }, 5.477226)] - [InlineData(new float[] { }, 0f)] - public static void Norm_KnownValues(float[] x, float expectedResult) - { - Assert.Equal(expectedResult, TensorPrimitives.Norm(x), Tolerance); - } - - [Theory] - [MemberData(nameof(TensorLengthsIncluding0))] - public static void Norm(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.Norm(x), Tolerance); - } - - [Theory] - [MemberData(nameof(TensorLengths))] - public static void SoftMax_ThrowsForTooShortDestination(int tensorLength) - { - 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.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) - { - using BoundedMemory dest = CreateTensor(x.Length); - TensorPrimitives.SoftMax(x, dest); - - for (int i = 0; i < x.Length; i++) - { - Assert.Equal(expectedResult[i], dest[i], Tolerance); - } - } - - [Fact] - public static void SoftMax_DestinationLongerThanSource() - { - 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], Tolerance); - } - } - - [Fact] - public static void SoftMax_ThrowsForEmptyInput() - { - AssertExtensions.Throws(() => TensorPrimitives.SoftMax(ReadOnlySpan.Empty, CreateTensor(1))); - } - - [Theory] - [MemberData(nameof(TensorLengths))] - public static void Sigmoid_ThrowsForTooShortDestination(int tensorLength) - { - using BoundedMemory x = CreateAndFillTensor(tensorLength); - using BoundedMemory destination = CreateTensor(tensorLength - 1); - - AssertExtensions.Throws("destination", () => TensorPrimitives.Sigmoid(x, destination)); - } - - [Theory] - [InlineData(new float[] { -5, -4.5f, -4 }, new float[] { 0.0066f, 0.0109f, 0.0179f })] - [InlineData(new float[] { 4.5f, 5 }, new float[] { 0.9890f, 0.9933f })] - [InlineData(new float[] { 0, -3, 3, .5f }, new float[] { 0.5f, 0.0474f, 0.9525f, 0.6224f })] - public static void Sigmoid(float[] x, float[] expectedResult) - { - using BoundedMemory dest = CreateTensor(x.Length); - TensorPrimitives.Sigmoid(x, dest); - - for (int i = 0; i < x.Length; i++) - { - Assert.Equal(expectedResult[i], dest[i], Tolerance); - } - } - - [Fact] - public static void Sigmoid_DestinationLongerThanSource() - { - 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], Tolerance); - } - Assert.Equal(originalLast, dest[dest.Length - 1]); - } - - [Fact] - public static void Sigmoid_ThrowsForEmptyInput() - { - AssertExtensions.Throws(() => TensorPrimitives.Sigmoid(ReadOnlySpan.Empty, CreateTensor(1))); + AssertExtensions.Throws("destination", () => TensorPrimitives.Exp(x, destination)); } + #endregion + #region IndexOfMax [Fact] public static void IndexOfMax_ReturnsNegative1OnEmpty() { @@ -1013,90 +570,96 @@ public static void IndexOfMax_Negative0LesserThanPositive0() Assert.Equal(1, TensorPrimitives.IndexOfMax([-1, -0f])); Assert.Equal(2, TensorPrimitives.IndexOfMax([-1, -0f, 1])); } + #endregion + #region IndexOfMaxMagnitude [Fact] - public static void IndexOfMin_ReturnsNegative1OnEmpty() + public static void IndexOfMaxMagnitude_ReturnsNegative1OnEmpty() { - Assert.Equal(-1, TensorPrimitives.IndexOfMin(ReadOnlySpan.Empty)); + Assert.Equal(-1, TensorPrimitives.IndexOfMaxMagnitude(ReadOnlySpan.Empty)); } [Theory] [MemberData(nameof(TensorLengths))] - public static void IndexOfMin(int tensorLength) + public static void IndexOfMaxMagnitude(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)); + x[expected] = Enumerable.Max(MemoryMarshal.ToEnumerable(x.Memory), Math.Abs) + 1; + Assert.Equal(expected, TensorPrimitives.IndexOfMaxMagnitude(x)); } } [Theory] [MemberData(nameof(TensorLengths))] - public static void IndexOfMin_FirstNaNReturned(int tensorLength) + 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.IndexOfMin(x)); + Assert.Equal(expected, TensorPrimitives.IndexOfMaxMagnitude(x)); } } [Fact] - public static void IndexOfMin_Negative0LesserThanPositive0() + public static void IndexOfMaxMagnitude_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])); + 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])); } + #endregion + #region IndexOfMin [Fact] - public static void IndexOfMaxMagnitude_ReturnsNegative1OnEmpty() + public static void IndexOfMin_ReturnsNegative1OnEmpty() { - Assert.Equal(-1, TensorPrimitives.IndexOfMaxMagnitude(ReadOnlySpan.Empty)); + Assert.Equal(-1, TensorPrimitives.IndexOfMin(ReadOnlySpan.Empty)); } [Theory] [MemberData(nameof(TensorLengths))] - public static void IndexOfMaxMagnitude(int tensorLength) + public static void IndexOfMin(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)); + x[expected] = Enumerable.Min(MemoryMarshal.ToEnumerable(x.Memory)) - 1; + Assert.Equal(expected, TensorPrimitives.IndexOfMin(x)); } } [Theory] [MemberData(nameof(TensorLengths))] - public static void IndexOfMaxMagnitude_FirstNaNReturned(int tensorLength) + 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.IndexOfMaxMagnitude(x)); + Assert.Equal(expected, TensorPrimitives.IndexOfMin(x)); } } [Fact] - public static void IndexOfMaxMagnitude_Negative0LesserThanPositive0() + public static void IndexOfMin_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])); + 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])); } + #endregion + #region IndexOfMinMagnitude [Fact] public static void IndexOfMinMagnitude_ReturnsNegative1OnEmpty() { @@ -1144,16 +707,72 @@ public static void IndexOfMinMagnitude_Negative0LesserThanPositive0() Assert.Equal(1, TensorPrimitives.IndexOfMinMagnitude([-1, -0f])); Assert.Equal(1, TensorPrimitives.IndexOfMinMagnitude([-1, -0f, 1])); } + #endregion + + #region Log + [Theory] + [MemberData(nameof(TensorLengthsIncluding0))] + public static void Log(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); + + TensorPrimitives.Log(x, destination); + + for (int i = 0; i < tensorLength; i++) + { + Assert.Equal(MathF.Log(x[i]), destination[i], Tolerance); + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Log_ThrowsForTooShortDestination(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); + + AssertExtensions.Throws("destination", () => TensorPrimitives.Log(x, destination)); + } + #endregion + + #region Log2 + [Theory] + [MemberData(nameof(TensorLengthsIncluding0))] + public static void Log2(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); + + TensorPrimitives.Log2(x, destination); + + for (int i = 0; i < tensorLength; i++) + { + Assert.Equal(MathF.Log(x[i], 2), destination[i], Tolerance); + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Log2_ThrowsForTooShortDestination(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); + + AssertExtensions.Throws("destination", () => TensorPrimitives.Log2(x, destination)); + } + #endregion + #region Max [Fact] - public static void Max_ThrowsForEmpty() + public static void Max_Tensor_ThrowsForEmpty() { Assert.Throws(() => TensorPrimitives.Max(ReadOnlySpan.Empty)); } [Theory] [MemberData(nameof(TensorLengths))] - public static void Max(int tensorLength) + public static void Max_Tensor(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); @@ -1169,7 +788,7 @@ public static void Max(int tensorLength) [Theory] [MemberData(nameof(TensorLengths))] - public static void Max_NanReturned(int tensorLength) + public static void Max_Tensor_NanReturned(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) @@ -1180,7 +799,7 @@ public static void Max_NanReturned(int tensorLength) } [Fact] - public static void Max_Negative0LesserThanPositive0() + public static void Max_Tensor_Negative0LesserThanPositive0() { Assert.Equal(+0f, TensorPrimitives.Max([-0f, +0f])); Assert.Equal(+0f, TensorPrimitives.Max([+0f, -0f])); @@ -1213,6 +832,7 @@ public static void Max_TwoTensors_ThrowsForMismatchedLengths(int tensorLength) using BoundedMemory destination = CreateTensor(tensorLength); Assert.Throws(() => TensorPrimitives.Max(x, y, destination)); + Assert.Throws(() => TensorPrimitives.Max(y, x, destination)); } [Theory] @@ -1225,16 +845,18 @@ public static void Max_TwoTensors_ThrowsForTooShortDestination(int tensorLength) AssertExtensions.Throws("destination", () => TensorPrimitives.Max(x, y, destination)); } + #endregion + #region MaxMagnitude [Fact] - public static void MaxMagnitude_ThrowsForEmpty() + public static void MaxMagnitude_Tensor_ThrowsForEmpty() { Assert.Throws(() => TensorPrimitives.MaxMagnitude(ReadOnlySpan.Empty)); } [Theory] [MemberData(nameof(TensorLengths))] - public static void MaxMagnitude(int tensorLength) + public static void MaxMagnitude_Tensor(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); @@ -1252,7 +874,7 @@ public static void MaxMagnitude(int tensorLength) [Theory] [MemberData(nameof(TensorLengths))] - public static void MaxMagnitude_NanReturned(int tensorLength) + public static void MaxMagnitude_Tensor_NanReturned(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) @@ -1263,7 +885,7 @@ public static void MaxMagnitude_NanReturned(int tensorLength) } [Fact] - public static void MaxMagnitude_Negative0LesserThanPositive0() + public static void MaxMagnitude_Tensor_Negative0LesserThanPositive0() { Assert.Equal(+0f, TensorPrimitives.MaxMagnitude([-0f, +0f])); Assert.Equal(+0f, TensorPrimitives.MaxMagnitude([+0f, -0f])); @@ -1298,6 +920,7 @@ public static void MaxMagnitude_TwoTensors_ThrowsForMismatchedLengths(int tensor using BoundedMemory destination = CreateTensor(tensorLength); Assert.Throws(() => TensorPrimitives.MaxMagnitude(x, y, destination)); + Assert.Throws(() => TensorPrimitives.MaxMagnitude(y, x, destination)); } [Theory] @@ -1310,16 +933,18 @@ public static void MaxMagnitude_TwoTensors_ThrowsForTooShortDestination(int tens AssertExtensions.Throws("destination", () => TensorPrimitives.MaxMagnitude(x, y, destination)); } + #endregion + #region Min [Fact] - public static void Min_ThrowsForEmpty() + public static void Min_Tensor_ThrowsForEmpty() { Assert.Throws(() => TensorPrimitives.Min(ReadOnlySpan.Empty)); } [Theory] [MemberData(nameof(TensorLengths))] - public static void Min(int tensorLength) + public static void Min_Tensor(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); @@ -1335,7 +960,7 @@ public static void Min(int tensorLength) [Theory] [MemberData(nameof(TensorLengths))] - public static void Min_NanReturned(int tensorLength) + public static void Min_Tensor_NanReturned(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) @@ -1346,7 +971,7 @@ public static void Min_NanReturned(int tensorLength) } [Fact] - public static void Min_Negative0LesserThanPositive0() + public static void Min_Tensor_Negative0LesserThanPositive0() { Assert.Equal(-0f, TensorPrimitives.Min([-0f, +0f])); Assert.Equal(-0f, TensorPrimitives.Min([+0f, -0f])); @@ -1379,6 +1004,7 @@ public static void Min_TwoTensors_ThrowsForMismatchedLengths(int tensorLength) using BoundedMemory destination = CreateTensor(tensorLength); Assert.Throws(() => TensorPrimitives.Min(x, y, destination)); + Assert.Throws(() => TensorPrimitives.Min(y, x, destination)); } [Theory] @@ -1391,16 +1017,18 @@ public static void Min_TwoTensors_ThrowsForTooShortDestination(int tensorLength) AssertExtensions.Throws("destination", () => TensorPrimitives.Min(x, y, destination)); } + #endregion + #region MinMagnitude [Fact] - public static void MinMagnitude_ThrowsForEmpty() + public static void MinMagnitude_Tensor_ThrowsForEmpty() { Assert.Throws(() => TensorPrimitives.MinMagnitude(ReadOnlySpan.Empty)); } [Theory] [MemberData(nameof(TensorLengths))] - public static void MinMagnitude(int tensorLength) + public static void MinMagnitude_Tensor(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); @@ -1418,7 +1046,7 @@ public static void MinMagnitude(int tensorLength) [Theory] [MemberData(nameof(TensorLengths))] - public static void MinMagnitude_NanReturned(int tensorLength) + public static void MinMagnitude_Tensor_NanReturned(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); foreach (int expected in new[] { 0, tensorLength / 2, tensorLength - 1 }) @@ -1429,7 +1057,7 @@ public static void MinMagnitude_NanReturned(int tensorLength) } [Fact] - public static void MinMagnitude_Negative0LesserThanPositive0() + public static void MinMagnitude_Tensor_Negative0LesserThanPositive0() { Assert.Equal(0, TensorPrimitives.MinMagnitude([-0f, +0f])); Assert.Equal(0, TensorPrimitives.MinMagnitude([+0f, -0f])); @@ -1454,27 +1082,256 @@ public static void MinMagnitude_TwoTensors(int tensorLength) } [Theory] - [MemberData(nameof(TensorLengths))] - public static void MinMagnitude_TwoTensors_ThrowsForMismatchedLengths(int tensorLength) + [MemberData(nameof(TensorLengths))] + public static void MinMagnitude_TwoTensors_ThrowsForMismatchedLengths(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength); + + Assert.Throws(() => TensorPrimitives.MinMagnitude(x, y, destination)); + Assert.Throws(() => TensorPrimitives.MinMagnitude(y, x, destination)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void MinMagnitude_TwoTensors_ThrowsForTooShortDestination(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); + + AssertExtensions.Throws("destination", () => TensorPrimitives.MinMagnitude(x, y, destination)); + } + #endregion + + #region Multiply + [Theory] + [MemberData(nameof(TensorLengthsIncluding0))] + public static void Multiply_TwoTensors(int 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], Tolerance); + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Multiply_TwoTensors_ThrowsForMismatchedLengths(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength); + + Assert.Throws(() => TensorPrimitives.Multiply(x, y, destination)); + Assert.Throws(() => TensorPrimitives.Multiply(y, x, destination)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Multiply_TwoTensors_ThrowsForTooShortDestination(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); + + AssertExtensions.Throws("destination", () => TensorPrimitives.Multiply(x, y, destination)); + } + + [Theory] + [MemberData(nameof(TensorLengthsIncluding0))] + public static void Multiply_TensorScalar(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + float y = NextSingle(); + using BoundedMemory destination = CreateTensor(tensorLength); + + TensorPrimitives.Multiply(x, y, destination); + + for (int i = 0; i < tensorLength; i++) + { + Assert.Equal(x[i] * y, destination[i], Tolerance); + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Multiply_TensorScalar_ThrowsForTooShortDestination(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + float y = NextSingle(); + using BoundedMemory destination = CreateTensor(tensorLength - 1); + + AssertExtensions.Throws("destination", () => TensorPrimitives.Multiply(x, y, destination)); + } + #endregion + + #region MultiplyAdd + [Theory] + [MemberData(nameof(TensorLengthsIncluding0))] + public static void MultiplyAdd_ThreeTensors(int 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); + + for (int i = 0; i < tensorLength; i++) + { + Assert.Equal((x[i] * y[i]) + addend[i], destination[i], Tolerance); + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void MultiplyAdd_ThreeTensors_ThrowsForMismatchedLengths_x_y(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory z = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength); + + Assert.Throws(() => TensorPrimitives.MultiplyAdd(x, y, z, destination)); + Assert.Throws(() => TensorPrimitives.MultiplyAdd(x, z, y, destination)); + Assert.Throws(() => TensorPrimitives.MultiplyAdd(z, x, y, destination)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void MultiplyAdd_ThreeTensors_ThrowsForTooShortDestination(int tensorLength) + { + 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)); + } + + [Theory] + [MemberData(nameof(TensorLengthsIncluding0))] + public static void MultiplyAdd_TensorTensorScalar(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + float addend = NextSingle(); + using BoundedMemory destination = CreateTensor(tensorLength); + + TensorPrimitives.MultiplyAdd(x, y, addend, destination); + + for (int i = 0; i < tensorLength; i++) + { + Assert.Equal((x[i] * y[i]) + addend, destination[i], Tolerance); + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void MultiplyAdd_TensorTensorScalar_ThrowsForTooShortDestination(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + float addend = NextSingle(); + using BoundedMemory destination = CreateTensor(tensorLength - 1); + + AssertExtensions.Throws("destination", () => TensorPrimitives.MultiplyAdd(x, y, addend, destination)); + } + + [Theory] + [MemberData(nameof(TensorLengthsIncluding0))] + public static void MultiplyAdd_TensorScalarTensor(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + float y = NextSingle(); + using BoundedMemory addend = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); + + TensorPrimitives.MultiplyAdd(x, y, addend, destination); + + for (int i = 0; i < tensorLength; i++) + { + Assert.Equal((x[i] * y) + addend[i], destination[i], Tolerance); + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void MultiplyAdd_TensorScalarTensor_ThrowsForTooShortDestination(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + float y = NextSingle(); + using BoundedMemory addend = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); + + AssertExtensions.Throws("destination", () => TensorPrimitives.MultiplyAdd(x, y, addend, destination)); + } + #endregion + + #region Negate + [Theory] + [MemberData(nameof(TensorLengthsIncluding0))] + public static void Negate(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); + + TensorPrimitives.Negate(x, destination); + + for (int i = 0; i < tensorLength; i++) + { + Assert.Equal(-x[i], destination[i], Tolerance); + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Negate_ThrowsForTooShortDestination(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); + + AssertExtensions.Throws("destination", () => TensorPrimitives.Negate(x, destination)); + } + #endregion + + #region Norm + [Theory] + [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.477226)] + [InlineData(new float[] { }, 0f)] + public static void Norm_KnownValues(float[] x, float expectedResult) { - using BoundedMemory x = CreateAndFillTensor(tensorLength); - using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); - using BoundedMemory destination = CreateTensor(tensorLength); - - Assert.Throws(() => TensorPrimitives.MinMagnitude(x, y, destination)); + Assert.Equal(expectedResult, TensorPrimitives.Norm(x), Tolerance); } [Theory] - [MemberData(nameof(TensorLengths))] - public static void MinMagnitude_TwoTensors_ThrowsForTooShortDestination(int tensorLength) + [MemberData(nameof(TensorLengthsIncluding0))] + public static void Norm(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); - using BoundedMemory y = CreateAndFillTensor(tensorLength); - using BoundedMemory destination = CreateTensor(tensorLength - 1); - AssertExtensions.Throws("destination", () => TensorPrimitives.MinMagnitude(x, y, destination)); + float sumOfSquares = 0f; + for (int i = 0; i < x.Length; i++) + { + sumOfSquares += x[i] * x[i]; + } + + Assert.Equal(Math.Sqrt(sumOfSquares), TensorPrimitives.Norm(x), Tolerance); } + #endregion + #region Product [Fact] public static void Product_ThrowsForEmpty() { @@ -1510,7 +1367,9 @@ public static void Product_KnownValues() 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])); } + #endregion + #region ProductOfDifferences [Fact] public static void ProductOfDifferences_ThrowsForEmptyAndMismatchedLengths() { @@ -1548,7 +1407,9 @@ public static void ProductOfDifferences_KnownValues() 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])); } + #endregion + #region ProductOfSums [Fact] public static void ProductOfSums_ThrowsForEmptyAndMismatchedLengths() { @@ -1586,7 +1447,202 @@ public static void ProductOfSums_KnownValues() 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])); } + #endregion + + #region Sigmoid + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Sigmoid_ThrowsForTooShortDestination(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); + + AssertExtensions.Throws("destination", () => TensorPrimitives.Sigmoid(x, destination)); + } + + [Theory] + [InlineData(new float[] { -5, -4.5f, -4 }, new float[] { 0.0066f, 0.0109f, 0.0179f })] + [InlineData(new float[] { 4.5f, 5 }, new float[] { 0.9890f, 0.9933f })] + [InlineData(new float[] { 0, -3, 3, .5f }, new float[] { 0.5f, 0.0474f, 0.9525f, 0.6224f })] + public static void Sigmoid(float[] x, float[] expectedResult) + { + using BoundedMemory dest = CreateTensor(x.Length); + TensorPrimitives.Sigmoid(x, dest); + + for (int i = 0; i < x.Length; i++) + { + Assert.Equal(expectedResult[i], dest[i], Tolerance); + } + } + + [Fact] + public static void Sigmoid_DestinationLongerThanSource() + { + 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], Tolerance); + } + Assert.Equal(originalLast, dest[dest.Length - 1]); + } + + [Fact] + public static void Sigmoid_ThrowsForEmptyInput() + { + AssertExtensions.Throws(() => TensorPrimitives.Sigmoid(ReadOnlySpan.Empty, CreateTensor(1))); + } + #endregion + + #region Sinh + [Theory] + [MemberData(nameof(TensorLengthsIncluding0))] + public static void Sinh(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength); + + TensorPrimitives.Sinh(x, destination); + + for (int i = 0; i < tensorLength; i++) + { + Assert.Equal(MathF.Sinh(x[i]), destination[i], Tolerance); + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Sinh_ThrowsForTooShortDestination(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); + + AssertExtensions.Throws("destination", () => TensorPrimitives.Sinh(x, destination)); + } + #endregion + + #region SoftMax + [Theory] + [MemberData(nameof(TensorLengths))] + public static void SoftMax_ThrowsForTooShortDestination(int tensorLength) + { + 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.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) + { + using BoundedMemory dest = CreateTensor(x.Length); + TensorPrimitives.SoftMax(x, dest); + + for (int i = 0; i < x.Length; i++) + { + Assert.Equal(expectedResult[i], dest[i], Tolerance); + } + } + + [Fact] + public static void SoftMax_DestinationLongerThanSource() + { + 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], Tolerance); + } + } + + [Fact] + public static void SoftMax_ThrowsForEmptyInput() + { + AssertExtensions.Throws(() => TensorPrimitives.SoftMax(ReadOnlySpan.Empty, CreateTensor(1))); + } + #endregion + + #region Subtract + [Theory] + [MemberData(nameof(TensorLengthsIncluding0))] + public static void Subtract_TwoTensors(int 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], Tolerance); + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Subtract_TwoTensors_ThrowsForMismatchedLengths(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength - 1); + using BoundedMemory destination = CreateTensor(tensorLength); + + Assert.Throws(() => TensorPrimitives.Subtract(x, y, destination)); + Assert.Throws(() => TensorPrimitives.Subtract(y, x, destination)); + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Subtract_TwoTensors_ThrowsForTooShortDestination(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + using BoundedMemory y = CreateAndFillTensor(tensorLength); + using BoundedMemory destination = CreateTensor(tensorLength - 1); + + AssertExtensions.Throws("destination", () => TensorPrimitives.Subtract(x, y, destination)); + } + + [Theory] + [MemberData(nameof(TensorLengthsIncluding0))] + public static void Subtract_TensorScalar(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + float y = NextSingle(); + using BoundedMemory destination = CreateTensor(tensorLength); + + TensorPrimitives.Subtract(x, y, destination); + + for (int i = 0; i < tensorLength; i++) + { + Assert.Equal(x[i] - y, destination[i], Tolerance); + } + } + + [Theory] + [MemberData(nameof(TensorLengths))] + public static void Subtract_TensorScalar_ThrowsForTooShortDestination(int tensorLength) + { + using BoundedMemory x = CreateAndFillTensor(tensorLength); + float y = NextSingle(); + using BoundedMemory destination = CreateTensor(tensorLength - 1); + + AssertExtensions.Throws("destination", () => TensorPrimitives.Subtract(x, y, destination)); + } + #endregion + #region Sum [Theory] [MemberData(nameof(TensorLengths))] public static void Sum(int tensorLength) @@ -1612,82 +1668,89 @@ public static void Sum_KnownValues() Assert.Equal(0, TensorPrimitives.Sum([-3, 0, 3])); Assert.Equal(float.NaN, TensorPrimitives.Sum([-3, float.NaN, 3])); } + #endregion + #region SumOfMagnitudes [Theory] [MemberData(nameof(TensorLengths))] - public static void SumOfSquares(int tensorLength) + public static void SumOfMagnitudes(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); - Assert.Equal(Enumerable.Sum(MemoryMarshal.ToEnumerable(x.Memory), v => v * v), TensorPrimitives.SumOfSquares(x), Tolerance); + Assert.Equal(Enumerable.Sum(MemoryMarshal.ToEnumerable(x.Memory), MathF.Abs), TensorPrimitives.SumOfMagnitudes(x), Tolerance); float sum = 0; foreach (float f in x.Span) { - sum += f * f; + sum += MathF.Abs(f); } - Assert.Equal(sum, TensorPrimitives.SumOfSquares(x), Tolerance); + Assert.Equal(sum, TensorPrimitives.SumOfMagnitudes(x), Tolerance); } [Fact] - public static void SumOfSquares_KnownValues() + public static void SumOfMagnitudes_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])); + 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])); } + #endregion + #region SumOfSquares [Theory] [MemberData(nameof(TensorLengths))] - public static void SumOfMagnitudes(int tensorLength) + public static void SumOfSquares(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); - Assert.Equal(Enumerable.Sum(MemoryMarshal.ToEnumerable(x.Memory), MathF.Abs), TensorPrimitives.SumOfMagnitudes(x), Tolerance); + 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 += MathF.Abs(f); + sum += f * f; } - Assert.Equal(sum, TensorPrimitives.SumOfMagnitudes(x), Tolerance); + Assert.Equal(sum, TensorPrimitives.SumOfSquares(x), Tolerance); } [Fact] - public static void SumOfMagnitudes_KnownValues() + public static void SumOfSquares_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])); + 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])); } + #endregion + #region Tanh [Theory] [MemberData(nameof(TensorLengthsIncluding0))] - public static void Abs(int tensorLength) + public static void Tanh(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); using BoundedMemory destination = CreateTensor(tensorLength); - TensorPrimitives.Abs(x, destination); + TensorPrimitives.Tanh(x, destination); - for (int i = 0; i < x.Length; i++) + for (int i = 0; i < tensorLength; i++) { - Assert.Equal(MathF.Abs(x[i]), destination[i], Tolerance); + Assert.Equal(MathF.Tanh(x[i]), destination[i], Tolerance); } } [Theory] [MemberData(nameof(TensorLengths))] - public static void Abs_ThrowsForTooShortDestination(int tensorLength) + public static void Tanh_ThrowsForTooShortDestination(int tensorLength) { using BoundedMemory x = CreateAndFillTensor(tensorLength); using BoundedMemory destination = CreateTensor(tensorLength - 1); - AssertExtensions.Throws("destination", () => TensorPrimitives.Abs(x, destination)); + AssertExtensions.Throws("destination", () => TensorPrimitives.Tanh(x, destination)); } + #endregion } } diff --git a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.netcore.cs b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.netcore.cs index 113f26048d352c..6a1c8120dc8f09 100644 --- a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.netcore.cs +++ b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.netcore.cs @@ -8,9 +8,9 @@ namespace System.Numerics.Tensors.Tests { public static partial class TensorPrimitivesTests { + #region ConvertToHalf [Theory] - [InlineData(0)] - [MemberData(nameof(TensorLengths))] + [MemberData(nameof(TensorLengthsIncluding0))] public static void ConvertToHalf(int tensorLength) { using BoundedMemory source = CreateAndFillTensor(tensorLength); @@ -44,10 +44,11 @@ public static void ConvertToHalf_ThrowsForTooShortDestination(int tensorLength) AssertExtensions.Throws("destination", () => TensorPrimitives.ConvertToHalf(source, destination)); } + #endregion + #region ConvertToSingle [Theory] - [InlineData(0)] - [MemberData(nameof(TensorLengths))] + [MemberData(nameof(TensorLengthsIncluding0))] public static void ConvertToSingle(int tensorLength) { Half[] source = new Half[tensorLength]; @@ -87,5 +88,6 @@ public static void ConvertToSingle_ThrowsForTooShortDestination(int tensorLength AssertExtensions.Throws("destination", () => TensorPrimitives.ConvertToSingle(source, destination)); } + #endregion } }