Skip to content

Commit

Permalink
Support sum of 2D vectors using tensor
Browse files Browse the repository at this point in the history
  • Loading branch information
aalmada committed Jan 2, 2024
1 parent e39e44d commit 62a31d2
Show file tree
Hide file tree
Showing 30 changed files with 1,015 additions and 302 deletions.
10 changes: 3 additions & 7 deletions NetFabric.Numerics.sln
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetFabric.Numerics.Geodesy"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetFabric.Numerics.Geodesy.UnitTests", "src\NetFabric.Numerics.Geodesy.UnitTests\NetFabric.Numerics.Geodesy.UnitTests.csproj", "{4E4414C1-E5CF-4679-9016-7C3F70F13110}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetFabric.Numerics.Tensors", "src\NetFabric.Numerics.Tensors\NetFabric.Numerics.Tensors.csproj", "{8D2EE0A5-32A2-4019-AD55-7E9B70B76D67}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetFabric.Numerics.Tensors", "src\NetFabric.Numerics.Tensors\NetFabric.Numerics.Tensors.csproj", "{8D2EE0A5-32A2-4019-AD55-7E9B70B76D67}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EC0C1351-970E-48A9-BDA3-3E5E2B095402}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetFabric.Numerics.Tensors.UnitTests", "src\NetFabric.Numerics.Tensors.UnitTests\NetFabric.Numerics.Tensors.UnitTests.csproj", "{31E6CCB0-E04A-4947-BF74-E8E70D5594E5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetFabric.Numerics.Tensors.UnitTests", "src\NetFabric.Numerics.Tensors.UnitTests\NetFabric.Numerics.Tensors.UnitTests.csproj", "{31E6CCB0-E04A-4947-BF74-E8E70D5594E5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetFabric.Numerics.Tensors.Benchmarks", "src\NetFabric.Numerics.Tensors.Benchmarks\NetFabric.Numerics.Tensors.Benchmarks.csproj", "{A8467A84-F9DD-4B6C-91BB-CF39029331A4}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetFabric.Numerics.Tensors.Benchmarks", "src\NetFabric.Numerics.Tensors.Benchmarks\NetFabric.Numerics.Tensors.Benchmarks.csproj", "{A8467A84-F9DD-4B6C-91BB-CF39029331A4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -118,8 +116,6 @@ Global
{A04C4E36-5F33-43F8-A7EE-15D81C7F8A58} = {762517CA-3F22-4DC6-955C-4F4FC69EB670}
{BA0837F2-730C-4537-B08B-5FFD2D91AF30} = {762517CA-3F22-4DC6-955C-4F4FC69EB670}
{83BDB68A-4F4B-4D7D-A7C7-55CC9F03657B} = {762517CA-3F22-4DC6-955C-4F4FC69EB670}
{31E6CCB0-E04A-4947-BF74-E8E70D5594E5} = {EC0C1351-970E-48A9-BDA3-3E5E2B095402}
{A8467A84-F9DD-4B6C-91BB-CF39029331A4} = {EC0C1351-970E-48A9-BDA3-3E5E2B095402}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AACC30AE-BE57-461F-817B-2AE0F001A33F}
Expand Down
5 changes: 4 additions & 1 deletion src/NetFabric.Numerics.Angle/AngleAverage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ public static partial class Angle
where TUnits : IAngleUnits
where T : struct, IFloatingPoint<T>, IMinMaxValue<T>
{
if(source.TryGetSpan(out var span))
return span.Average();

var sum = T.Zero;
var count = T.Zero;
foreach (var angle in source)
Expand Down Expand Up @@ -74,7 +77,7 @@ public static partial class Angle
public static Angle<TUnits, T>? Average<TUnits, T>(this ReadOnlySpan<Angle<TUnits, T>> source)
where TUnits : IAngleUnits
where T : struct, IFloatingPoint<T>, IMinMaxValue<T>
=> source.Length == 0
=> source.Length is 0
? null
: Sum(source) / T.CreateChecked(source.Length);
}
66 changes: 66 additions & 0 deletions src/NetFabric.Numerics.Benchmarks/ArrayAdditionBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;

namespace NetFabric.Numerics.Benchmarks;

[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
[CategoriesColumn]
public class ArrayAdditionBenchmarks
{
Rectangular2D.Vector<int>[]? rectangular2_int;
Rectangular2D.Vector<long>[]? rectangular2_long;
Rectangular2D.Vector<float>[]? rectangular2_float;
Rectangular2D.Vector<double>[]? rectangular2_double;

Polar.Vector<Degrees, float>[]? polar_float;
Polar.Vector<Degrees, double>[]? polar_double;

[Params(10_000)]
public int Count { get; set; }

[GlobalSetup]
public void GlobalSetup()
{
rectangular2_int = GetEnumerable(Count).Select<(int x, int y), Rectangular2D.Vector<int>>(item => new(item.x, item.y)).ToArray();
rectangular2_long = GetEnumerable(Count).Select<(int x, int y), Rectangular2D.Vector<long>>(item => new(item.x, item.y)).ToArray();
rectangular2_float = GetEnumerable(Count).Select<(int x, int y), Rectangular2D.Vector<float>>(item => new(item.x, item.y)).ToArray();
rectangular2_double = GetEnumerable(Count).Select<(int x, int y), Rectangular2D.Vector<double>>(item => new(item.x, item.y)).ToArray();

polar_float = GetEnumerable(Count).Select<(int x, int y), Polar.Vector<Degrees, float>>(item => new(item.x, new(item.y))).ToArray();
polar_double = GetEnumerable(Count).Select<(int x, int y), Polar.Vector<Degrees, double>>(item => new(item.x, new(item.y))).ToArray();

static IEnumerable<(int x, int y)> GetEnumerable(int count)
{
var random = new Random(42);
for (var index = 0; index < count; index++)
{
yield return (random.Next(100), random.Next(100));
}
}
}

[Benchmark]
public void Rectangular2D_Int()
=> Rectangular2D.Vector.Add<int>(rectangular2_int!, rectangular2_int!, rectangular2_int!);

[Benchmark]
public void Rectangular2D_Long()
=> Rectangular2D.Vector.Add<long>(rectangular2_long!, rectangular2_long!, rectangular2_long!);

[Benchmark]
public void Rectangular2D_Float()
=> Rectangular2D.Vector.Add<float>(rectangular2_float!, rectangular2_float!, rectangular2_float!);

[Benchmark]
public void Polar_Float()
=> Polar.Vector.Add<Degrees, float>(polar_float!, polar_float!, polar_float!);

[Benchmark]
public void Rectangular2D_Double()
=> Rectangular2D.Vector.Add<double>(rectangular2_double!, rectangular2_double!, rectangular2_double!);

[Benchmark]
public void Polar_Double()
=> Polar.Vector.Add<Degrees, double>(polar_double!, polar_double!, polar_double!);

}
65 changes: 65 additions & 0 deletions src/NetFabric.Numerics.Tensors.Benchmarks/SumPairsBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using BenchmarkDotNet.Attributes;
using System.Runtime.InteropServices;

namespace NetFabric.Numerics.Tensors.Benchmarks;

public class SumPairsBenchmarks
{
ValueTuple<short, short>[]? arrayShort;
ValueTuple<int, int>[]? arrayInt;
ValueTuple<long, long>[]? arrayLong;
ValueTuple<Half, Half>[]? arrayHalf;
ValueTuple<float, float>[]? arrayFloat;
ValueTuple<double, double>[]? arrayDouble;

[Params(10_000)]
public int Count { get; set; }

[GlobalSetup]
public void GlobalSetup()
{
var range = Enumerable.Range(0, Count);
arrayShort = range
.Select(value => ((short)value, (short)(value + 1)))
.ToArray();
arrayInt = range
.Select(value => ((int)value, (int)(value + 1)))
.ToArray();
arrayLong = range
.Select(value => ((long)value, (long)(value + 1)))
.ToArray();
arrayHalf = range
.Select(value => ((Half)value, (Half)(value + 1)))
.ToArray();
arrayFloat = range
.Select(value => ((float)value, (float)(value + 1)))
.ToArray();
arrayDouble = range
.Select(value => ((double)value, (double)(value + 1)))
.ToArray();
}

[Benchmark]
public ValueTuple<short, short> Sum_Short()
=> Tensor.SumPairs<short>(MemoryMarshal.Cast<ValueTuple<short, short>, short>(arrayShort!));

[Benchmark]
public ValueTuple<int, int> Sum_Int()
=> Tensor.SumPairs<int>(MemoryMarshal.Cast<ValueTuple<int, int>, int>(arrayInt!));

[Benchmark]
public ValueTuple<long, long> Sum_Long()
=> Tensor.SumPairs<long>(MemoryMarshal.Cast<ValueTuple<long, long>, long>(arrayLong!));

[Benchmark]
public ValueTuple<Half, Half> Sum_Half()
=> Tensor.SumPairs<Half>(MemoryMarshal.Cast<ValueTuple<Half, Half>, Half>(arrayHalf!));

[Benchmark]
public ValueTuple<float, float> Sum_Float()
=> Tensor.SumPairs<float>(MemoryMarshal.Cast<ValueTuple<float, float>, float>(arrayFloat!));

[Benchmark]
public ValueTuple<double, double> Sum_Double()
=> Tensor.SumPairs<double>(MemoryMarshal.Cast<ValueTuple<double, double>, double>(arrayDouble!));
}
90 changes: 90 additions & 0 deletions src/NetFabric.Numerics.Tensors.UnitTests/SumPairsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System.Linq;
using System.Runtime.InteropServices;

namespace NetFabric.Numerics.Tensors.UnitTests;

public class SumPairsTests
{
public static TheoryData<int> SumData
=> 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 };

[Theory]
[MemberData(nameof(SumData))]
public void SumPairs_Short_Should_Succeed(int count)
{
// arrange
var source = Enumerable.Range(0, count).Select(value => ((short)value, (short)(value + 1))).ToArray();
var expected = source.Aggregate((0, 0), (sum, value) => (sum.Item1 + value.Item1, sum.Item2 + value.Item2));

// act
var result = Tensor.SumPairs<short>(MemoryMarshal.Cast<ValueTuple<short, short>, short>(source));

// assert
result.Item1.Should().Be((short)expected.Item1);
result.Item2.Should().Be((short)expected.Item2);
}

[Theory]
[MemberData(nameof(SumData))]
public void SumPairs_Int_Should_Succeed(int count)
{
// arrange
var source = Enumerable.Range(0, count).Select(value => (value, value + 1)).ToArray();
var expected = source.Aggregate((0, 0), (sum, value) => (sum.Item1 + value.Item1, sum.Item2 + value.Item2));

// act
var result = Tensor.SumPairs<int>(MemoryMarshal.Cast<ValueTuple<int, int>, int>(source));

// assert
result.Item1.Should().Be(expected.Item1);
result.Item2.Should().Be(expected.Item2);
}

[Theory]
[MemberData(nameof(SumData))]
public void SumPairs_Long_Should_Succeed(int count)
{
// arrange
var source = Enumerable.Range(0, count).Select(value => ((long)value, (long)(value + 1))).ToArray();
var expected = source.Aggregate((0l, 0l), (sum, value) => (sum.Item1 + value.Item1, sum.Item2 + value.Item2));

// act
var result = Tensor.SumPairs<long>(MemoryMarshal.Cast<ValueTuple<long, long>, long>(source));

// assert
result.Item1.Should().Be(expected.Item1);
result.Item2.Should().Be(expected.Item2);
}

[Theory]
[MemberData(nameof(SumData))]
public void SumPairs_Float_Should_Succeed(int count)
{
// arrange
var source = Enumerable.Range(0, count).Select(value => ((float)value, (float)(value + 1))).ToArray();
var expected = source.Aggregate((0.0f, 0.0f), (sum, value) => (sum.Item1 + value.Item1, sum.Item2 + value.Item2));

// act
var result = Tensor.SumPairs<float>(MemoryMarshal.Cast<ValueTuple<float, float>, float>(source));

// assert
result.Item1.Should().Be(expected.Item1);
result.Item2.Should().Be(expected.Item2);
}

[Theory]
[MemberData(nameof(SumData))]
public void SumPairs_Double_Should_Succeed(int count)
{
// arrange
var source = Enumerable.Range(0, count).Select(value => ((double)value, (double)(value + 1))).ToArray();
var expected = source.Aggregate((0.0, 0.0), (sum, value) => (sum.Item1 + value.Item1, sum.Item2 + value.Item2));

// act
var result = Tensor.SumPairs<double>(MemoryMarshal.Cast<ValueTuple<double, double>, double>(source));

// assert
result.Item1.Should().Be(expected.Item1);
result.Item2.Should().Be(expected.Item2);
}
}
2 changes: 1 addition & 1 deletion src/NetFabric.Numerics.Tensors.UnitTests/SumTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public void Sum_Short_Should_Succeed(int count)
{
// arrange
var source = Enumerable.Range(0, count).Select(value => (short)value).ToArray();
var expected = Enumerable.Range(0, count).Sum();
var expected = source.Aggregate(0, (sum, value) => sum + value);

// act
var result = Tensor.Sum<short>(source);
Expand Down
4 changes: 4 additions & 0 deletions src/NetFabric.Numerics.Tensors/Add.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ public static void Add<T>(ReadOnlySpan<T> x, T y, Span<T> destination)
where T : struct, IAdditionOperators<T, T, T>
=> Apply<T, AddOperator<T>>(x, y, destination);

public static void Add<T>(ReadOnlySpan<T> x, ValueTuple<T, T> y, Span<T> destination)

Check warning on line 11 in src/NetFabric.Numerics.Tensors/Add.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'Tensor.Add<T>(ReadOnlySpan<T>, (T, T), Span<T>)'
where T : struct, IAdditionOperators<T, T, T>
=> Apply<T, AddOperator<T>>(x, y, destination);

public static void Add<T>(ReadOnlySpan<T> x, ReadOnlySpan<T> y, Span<T> destination)

Check warning on line 15 in src/NetFabric.Numerics.Tensors/Add.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'Tensor.Add<T>(ReadOnlySpan<T>, ReadOnlySpan<T>, Span<T>)'
where T : struct, IAdditionOperators<T, T, T>
=> Apply<T, AddOperator<T>>(x, y, destination);
Expand Down
55 changes: 55 additions & 0 deletions src/NetFabric.Numerics.Tensors/AggregatePairs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@

using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace NetFabric.Numerics
{
public static partial class Tensor
{
/// <summary>
/// Aggregates the elements in the specified <see cref="ReadOnlySpan{T}"/> using the specified <see cref="IAggregationOperator{T}"/>.
/// </summary>
/// <typeparam name="T">The type of the elements in the span.</typeparam>
/// <typeparam name="TOperator">The type of the aggregation operator.</typeparam>
/// <param name="source">The source span.</param>
/// <returns>The aggregated value.</returns>
public static ValueTuple<T, T> AggregatePairs<T, TOperator>(ReadOnlySpan<T> source)
where T : struct
where TOperator : struct, IAggregationPairsOperator<T>
{
if (source.Length % 2 is not 0)
Throw.ArgumentException(nameof(source), "source span must have an even size.");

var result = TOperator.Seed;
var resultVector = Vector<T>.Zero;

nint index = 0;

if (Vector.IsHardwareAccelerated &&
Vector<T>.IsSupported &&
Vector<T>.Count % 2 is 0 &&
source.Length >= Vector<T>.Count)
{
var sourceVectors = MemoryMarshal.Cast<T, Vector<T>>(source);
resultVector = GetVector(TOperator.Seed);

ref var sourceVectorsRef = ref MemoryMarshal.GetReference(sourceVectors);
for (nint indexVector = 0; indexVector < sourceVectors.Length; indexVector++)
resultVector = TOperator.Invoke(resultVector, Unsafe.Add(ref sourceVectorsRef, indexVector));

index = source.Length - source.Length % Vector<T>.Count;
}

ref var sourceRef = ref MemoryMarshal.GetReference(source);
for (; index < source.Length; index += 2)
{
result.Item1 = TOperator.Invoke(result.Item1, Unsafe.Add(ref sourceRef, index));
result.Item2 = TOperator.Invoke(result.Item2, Unsafe.Add(ref sourceRef, index + 1));
}

return TOperator.ResultSelector(result, resultVector);
}
}
}
Loading

0 comments on commit 62a31d2

Please sign in to comment.