Skip to content

Commit

Permalink
Add IndexOfAggregate
Browse files Browse the repository at this point in the history
  • Loading branch information
aalmada committed Mar 22, 2024
1 parent 5c252ef commit 8280a89
Show file tree
Hide file tree
Showing 14 changed files with 473 additions and 32 deletions.
36 changes: 34 additions & 2 deletions docs/articles/Extending-the-library.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,16 @@ public static T SumOfSquares<T>(ReadOnlySpan<T> source)

Here, the `SquareOperator<T>` and `SumOperator<T>` operators are utilized. The `SquareOperator<T>` operator is a unary operator that transforms the source elements by squaring them, while the `SumOperator<T>` operator is an aggregation operator that calculates the sum of the transformed elements.

The transform operator can also take two parameters, as demonstrated in the `SumOfProducts()` operation, which computes the sum of the products of corresponding elements in two sources:

```csharp
public static T? ProductOfAdditions<T>(ReadOnlySpan<T> x, ReadOnlySpan<T> y)
where T : struct, IMultiplicativeIdentity<T, T>, IAdditionOperators<T, T, T>, IMultiplyOperators<T, T, T>
=> x.IsEmpty
? null
: Tensor.Aggregate<T, AddOperator<T>, ProductOperator<T>>(x, y);
```

Additional variants of the `Aggregate()` method are available: `Aggregate2D()`, `Aggregate3D()`, and `Aggregate4D()`. These specialized methods are tailored to aggregate the source span into a tuple of two, three, or four values, respectively. They prove especially valuable when dealing with multi-dimensional data. For further details, refer to the section on "Working with Tensors for Structured Data".

For an aggregate operation akin to `Sum`, the library provides all the following operations:
Expand All @@ -316,6 +326,28 @@ public static ValueTuple<T, T, T, T> Sum4D<T>(ReadOnlySpan<T> source)
=> Aggregate4D<T, SumOperator<T>>(source);
```

## IndexOfAggregate

The `IndexOfAggregate()` method operates similarly to the `Aggregate()` method, but it retrieves the index of the first element that matches the value returned by `Aggregate()`.

For instance, let's explore its usage with the `IndexOfMaxNumber()` aggregation operation:

```csharp
public static int IndexOfMaxNumber<T>(ReadOnlySpan<T> source)
where T : struct, INumber<T>, IMinMaxValue<T>
=> Tensor.IndexOfAggregate<T, MaxOperator<T>>(source);
```

The method can employ a transform operator that accepts one or two parameters. Consider its application in the `IndexOfMaxNumber()` aggregation operation, which computes the index of the first element matching the maximum sum of corresponding elements in two sources:

```csharp
public static int IndexOfMaxSumNumber<T>(ReadOnlySpan<T> left, ReadOnlySpan<T> right)
where T : struct, INumber<T>, IMinMaxValue<T>
=> Tensor.IndexOfAggregate<T, SumOperator<T>, MaxAggregationOperator<T>>(left, right);
```

Here, two operators are specified as generic parameters. The first operator transforms the source elements, while the second operator aggregates the transformed elements.

## AggregatePropagateNaN

The `AggregatePropagateNaN()` method is a specialized version of the `Aggregate()` method, designed to handle operations that propagate `NaN` values, exiting the iteration as soon as possible. It is particularly useful when dealing with floating-point data, as it ensures that the presence of `NaN` values in the source span is reflected in the final result. This method requires the same generics parameters as the `Aggregate()` method.
Expand Down Expand Up @@ -387,9 +419,9 @@ This method requires three generic parameters. The first specifies the type of e
For example, let's consider the `MinMax()` method, which computes the minimum and maximum in a span simultaneously. This library provides the following operation:

```csharp
public static (T Min, T Max) MinMax<T>(ReadOnlySpan<T> left)
public static (T Min, T Max) MinMax<T>(ReadOnlySpan<T> source)
where T : struct, INumber<T>, IMinMaxValue<T>
=> Tensor.AggregatePropagateNaN2<T, MinOperator<T>, MaxOperator<T>>(left);
=> Tensor.AggregatePropagateNaN2<T, MinOperator<T>, MaxOperator<T>>(source);
```

The `MinOperator<T>` and `MaxOperator<T>` operators used are the ones previously described in the `Aggregate()` method.
Expand Down
66 changes: 66 additions & 0 deletions src/NetFabric.Numerics.Tensors.UnitTests/IndexOfMaxNumberTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System.Numerics.Tensors;

namespace NetFabric.Numerics.Tensors.UnitTests;

public class IndexOfMaxNumberTests
{
public static TheoryData<int> MaxData
=> new() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37 };

static void IndexOfMaxNumber_Should_Succeed<T>(int count)
where T : struct, INumber<T>, IMinMaxValue<T>
{
// arrange
var source = new T[count];
var max = T.MinValue;
var expected = -1;
var random = new Random(42);
for (var index = 0; index < source.Length; index++)
{
var value = T.CreateChecked(random.Next(100) - 50);
source[index] = value;
if (value > max)
{
max = value;
expected = index;
}
}

// act
var result = TensorOperations.IndexOfMaxNumber<T>(source);

// assert
Assert.Equal(expected, result);
}

[Theory]
[MemberData(nameof(MaxData))]
public void IndexOfMaxNumber_Short_Should_Succeed(int count)
=> IndexOfMaxNumber_Should_Succeed<short>(count);

[Theory]
[MemberData(nameof(MaxData))]
public void IndexOfMaxNumber_Int_Should_Succeed(int count)
=> IndexOfMaxNumber_Should_Succeed<int>(count);

[Theory]
[MemberData(nameof(MaxData))]
public void IndexOfMaxNumber_Long_Should_Succeed(int count)
=> IndexOfMaxNumber_Should_Succeed<long>(count);

[Theory]
[MemberData(nameof(MaxData))]
public void IndexOfMaxNumber_Half_Should_Succeed(int count)
=> IndexOfMaxNumber_Should_Succeed<Half>(count);

[Theory]
[MemberData(nameof(MaxData))]
public void IndexOfMaxNumber_Float_Should_Succeed(int count)
=> IndexOfMaxNumber_Should_Succeed<float>(count);

[Theory]
[MemberData(nameof(MaxData))]
public void IndexOfMaxNumber_Double_Should_Succeed(int count)
=> IndexOfMaxNumber_Should_Succeed<float>(count);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System.Numerics.Tensors;

namespace NetFabric.Numerics.Tensors.UnitTests;

public class IndexOfMaxSumNumberTests
{
public static TheoryData<int> MaxData
=> new() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37 };

static void IndexOfMaxSumNumber_Should_Succeed<T>(int count)
where T : struct, INumber<T>, IMinMaxValue<T>
{
// arrange
var left = new T[count];
var right = new T[count];
var max = T.MinValue;
var expected = -1;
var random = new Random(42);
for (var index = 0; index < left.Length; index++)
{
var valueLeft = T.CreateChecked(random.Next(100) - 50);
var valueRight = T.CreateChecked(random.Next(100) - 50);
left[index] = valueLeft;
right[index] = valueRight;
var sum = valueLeft + valueRight;
if (sum > max)
{
max = sum;
expected = index;
}
}

// act
var result = TensorOperations.IndexOfMaxSumNumber<T>(left, right);

// assert
Assert.Equal(expected, result);
}

[Theory]
[MemberData(nameof(MaxData))]
public void IndexOfMaxSumNumber_Short_Should_Succeed(int count)
=> IndexOfMaxSumNumber_Should_Succeed<short>(count);

[Theory]
[MemberData(nameof(MaxData))]
public void IndexOfMaxSumNumber_Int_Should_Succeed(int count)
=> IndexOfMaxSumNumber_Should_Succeed<int>(count);

[Theory]
[MemberData(nameof(MaxData))]
public void IndexOfMaxSumNumber_Long_Should_Succeed(int count)
=> IndexOfMaxSumNumber_Should_Succeed<long>(count);

[Theory]
[MemberData(nameof(MaxData))]
public void IndexOfMaxSumNumber_Half_Should_Succeed(int count)
=> IndexOfMaxSumNumber_Should_Succeed<Half>(count);

[Theory]
[MemberData(nameof(MaxData))]
public void IndexOfMaxSumNumber_Float_Should_Succeed(int count)
=> IndexOfMaxSumNumber_Should_Succeed<float>(count);

[Theory]
[MemberData(nameof(MaxData))]
public void IndexOfMaxSumNumber_Double_Should_Succeed(int count)
=> IndexOfMaxSumNumber_Should_Succeed<float>(count);

}
2 changes: 1 addition & 1 deletion src/NetFabric.Numerics.Tensors/ApplyBinary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ public static void Apply<T1, T2, TResult, TOperator>(ReadOnlySpan<T1> x, (T2, T2
{
// Cast the spans to vectors for hardware acceleration.
var xVectors = MemoryMarshal.Cast<T1, Vector<T1>>(x);
var valueVector = GetVector(y);
var valueVector = VectorFactory.Create(y);
var destinationVectors = MemoryMarshal.Cast<TResult, Vector<TResult>>(destination);

// Iterate through the vectors.
Expand Down
8 changes: 4 additions & 4 deletions src/NetFabric.Numerics.Tensors/ApplyTernary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ public static void Apply<T1, T2, T3, TResult, TOperator>(ReadOnlySpan<T1> x, (T2
{
// Cast the spans to vectors for hardware acceleration.
var xVectors = MemoryMarshal.Cast<T1, Vector<T1>>(x);
var yVector = GetVector(y);
var yVector = VectorFactory.Create(y);
var zVectors = MemoryMarshal.Cast<T3, Vector<T3>>(z);
var destinationVectors = MemoryMarshal.Cast<TResult, Vector<TResult>>(destination);

Expand Down Expand Up @@ -609,7 +609,7 @@ public static void Apply<T1, T2, T3, TResult, TOperator>(ReadOnlySpan<T1> x, Rea
// Cast the spans to vectors for hardware acceleration.
var xVectors = MemoryMarshal.Cast<T1, Vector<T1>>(x);
var yVectors = MemoryMarshal.Cast<T2, Vector<T2>>(y);
var zVector = GetVector(z);
var zVector = VectorFactory.Create(z);
var destinationVectors = MemoryMarshal.Cast<TResult, Vector<TResult>>(destination);

// Iterate through the vectors.
Expand Down Expand Up @@ -903,8 +903,8 @@ public static void Apply<T1, T2, T3, TResult, TOperator>(ReadOnlySpan<T1> x, (T2
{
// Cast the spans to vectors for hardware acceleration.
var xVectors = MemoryMarshal.Cast<T1, Vector<T1>>(x);
var yVector = GetVector(y);
var zVector = GetVector(z);
var yVector = VectorFactory.Create(y);
var zVector = VectorFactory.Create(z);
var destinationVectors = MemoryMarshal.Cast<TResult, Vector<TResult>>(destination);

// Iterate through the vectors.
Expand Down
Loading

0 comments on commit 8280a89

Please sign in to comment.