Skip to content

Commit b15e351

Browse files
Tensor wave 1 API's. (#101196)
* Native Index/Range and ref. * SpanND without slice * index/range testing and implicit conversions * SpanND Tests * tensor working, still need more statics * more tensors updates * ref files updated * final ref update * span updates * all but broadcast and some TensorPrimitives * broadcast in * organizational changes * ref and implicit broadcast * build failures * updates from PR comments * error text moved to strings.resx * exception strings moved to strings.resc * comments from PR * more fixes from PR and API review * rebase on main. XML comments. API updates * NIndex,NRange,RO/TensorSpan API updates * IROTensor,ITensor,Tensor API updates * changes from pr comments * changes from PR comments * fixed test failure
1 parent 95ff499 commit b15e351

26 files changed

+9208
-2
lines changed

src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
<PropertyGroup>
44
<TargetFrameworks>$(NetCoreAppCurrent);$(NetCoreAppPrevious);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum)</TargetFrameworks>
5+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
56
</PropertyGroup>
67

78
<ItemGroup>

src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.netcore.cs

Lines changed: 384 additions & 0 deletions
Large diffs are not rendered by default.

src/libraries/System.Numerics.Tensors/src/Resources/Strings.resx

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,88 @@
135135
<data name="Argument_InvalidEnumValue" xml:space="preserve">
136136
<value>The value '{0}' is not valid for this usage of the type {1}.</value>
137137
</data>
138-
</root>
138+
<data name="Argument_InvalidTypeWithPointersNotSupported" xml:space="preserve">
139+
<value>Cannot use type '{0}'. Only value types without pointers or references are supported.</value>
140+
</data>
141+
<data name="DestinationTooShort" xml:space="preserve">
142+
<value>Destination is too short.</value>
143+
</data>
144+
<data name="NotSupported_CannotCallEqualsOnSpan" xml:space="preserve">
145+
<value>Equals() on TensorSpan and ReadOnlyTensorSpan is not supported. Use operator== instead.</value>
146+
</data>
147+
<data name="NotSupported_CannotCallGetHashCodeOnSpan" xml:space="preserve">
148+
<value>GetHashCode() on TensorSpan and ReadOnlyTensorSpan is not supported.</value>
149+
</data>
150+
<data name="ThrowArgument_IndicesLengthMustEqualRank" xml:space="preserve">
151+
<value>Number of Indices must equal the rank of the TensorSpan.</value>
152+
</data>
153+
<data name="ThrowArgument_LengthsMustEqualArrayLength" xml:space="preserve">
154+
<value>The total length specified by the lengths must equal the length of the array.</value>
155+
</data>
156+
<data name="ThrowArgument_1DTensorRequired" xml:space="preserve">
157+
<value>Must be a 1d tensor</value>
158+
</data>
159+
<data name="ThrowArgument_AxisLargerThanRank" xml:space="preserve">
160+
<value>Cannot select an axis greater than the current Rank</value>
161+
</data>
162+
<data name="ThrowArgument_ConcatenateTooFewTensors" xml:space="preserve">
163+
<value>Must provide at least 2 tensors to Concatenate</value>
164+
</data>
165+
<data name="ThrowArgument_DimensionsNotSame" xml:space="preserve">
166+
<value>Number of dimensions to slice does not equal the number of dimensions in the span</value>
167+
</data>
168+
<data name="ThrowArgument_FilterTensorMustEqualTensorLength" xml:space="preserve">
169+
<value>The total length of the filter tensor must equal the length of the tensor to be filtered.</value>
170+
</data>
171+
<data name="ThrowArgument_IncorrectNumberOfFilterItems" xml:space="preserve">
172+
<value>Number of elements provided does not match the number of filters.</value>
173+
</data>
174+
<data name="ThrowArgument_InPlaceInvalidShape" xml:space="preserve">
175+
<value>In place operations require the same shape for both tensors</value>
176+
</data>
177+
<data name="ThrowArgument_InvalidAxis" xml:space="preserve">
178+
<value>Invalid axis provided. Must be greater then or equal to 0 and less than the tensor rank.</value>
179+
</data>
180+
<data name="ThrowArgument_InvalidConcatenateShape" xml:space="preserve">
181+
<value>The tensors must have the same shape, except in the dimension corresponding to axis.</value>
182+
</data>
183+
<data name="ThrowArgument_InvalidReshapeDimensions" xml:space="preserve">
184+
<value>Provided dimensions are not valid for reshaping</value>
185+
</data>
186+
<data name="ThrowArgument_InvalidSqueezeAxis" xml:space="preserve">
187+
<value>Cannot select an axis to squeeze which has size not equal to one</value>
188+
</data>
189+
<data name="ThrowArgument_OnlyOneWildcard" xml:space="preserve">
190+
<value>Provided dimensions can only include 1 wildcard.</value>
191+
</data>
192+
<data name="ThrowArgument_PermuteAxisOrder" xml:space="preserve">
193+
<value>Must provide an axis order for each axis</value>
194+
</data>
195+
<data name="ThrowArgument_SetSliceInvalidShapes" xml:space="preserve">
196+
<value>Provided values must have the same shape as the input tensor.</value>
197+
</data>
198+
<data name="ThrowArgument_SetSliceNoRange" xml:space="preserve">
199+
<value>When no ranges are specified the values tensor must be equal in size as the input tensor.</value>
200+
</data>
201+
<data name="ThrowArgument_ShapesNotBroadcastCompatible" xml:space="preserve">
202+
<value>Shapes are not broadcast compatible.</value>
203+
</data>
204+
<data name="ThrowArgument_SplitNotSplitEvenly" xml:space="preserve">
205+
<value>The number of splits must perfectly divide the dimension.</value>
206+
</data>
207+
<data name="ThrowArgument_StackTooFewTensors" xml:space="preserve">
208+
<value>Must provide at least 2 tensors to Stack.</value>
209+
</data>
210+
<data name="ThrowArgument_TransposeTooFewDimensions" xml:space="preserve">
211+
<value>Must provide a tensor with at least 2 dimensions to transpose it.</value>
212+
</data>
213+
<data name="ThrowArgument_ValueNonNegative" xml:space="preserve">
214+
<value>The provided value must be non-negative.</value>
215+
</data>
216+
<data name="ThrowArgument_InvalidStridesAndLengths" xml:space="preserve">
217+
<value>The provided lengths and strides would allow you to access elements outside the provided memory.</value>
218+
</data>
219+
<data name="ThrowArgument_StrideLessThan0" xml:space="preserve">
220+
<value>Strides cannot be less than 0.</value>
221+
</data>
222+
</root>

src/libraries/System.Numerics.Tensors/src/System.Numerics.Tensors.csproj

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<IsPackable>true</IsPackable>
77
<PackageDescription>Provides support for operating over tensors.</PackageDescription>
88
<GenAPIExcludeApiList>ReferenceAssemblyExclusions.txt</GenAPIExcludeApiList>
9+
<ApiCompatValidateAssemblies>true</ApiCompatValidateAssemblies>
910
</PropertyGroup>
1011

1112
<ItemGroup>
@@ -15,6 +16,20 @@
1516
</ItemGroup>
1617

1718
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'">
19+
<Compile Include="System\Numerics\Tensors\netcore\Common\IReadOnlyTensor.cs" />
20+
<Compile Include="System\Numerics\Tensors\netcore\TensorHelpers.cs" />
21+
<Compile Include="System\Numerics\Tensors\netcore\TensorExtensions.cs" />
22+
<Compile Include="System\Numerics\Tensors\netcore\Tensor.Factory.cs" />
23+
<Compile Include="System\Numerics\Tensors\netcore\Tensor.cs" />
24+
<Compile Include="System\Numerics\Tensors\netcore\ITensor.cs" />
25+
<Compile Include="System\Numerics\Tensors\netcore\TensorSpanDebugView.cs" />
26+
<Compile Include="System\Numerics\Tensors\netcore\TensorSpanExtensions.cs" />
27+
<Compile Include="System\Numerics\Tensors\netcore\ReadOnlyTensorSpan.cs" />
28+
<Compile Include="System\Numerics\Tensors\netcore\TensorSpanHelpers.cs" />
29+
<Compile Include="System\Numerics\Tensors\netcore\TensorSpanHelpers.T.cs" />
30+
<Compile Include="System\Numerics\Tensors\netcore\TensorSpan.cs" />
31+
<Compile Include="System\NIndex.cs" />
32+
<Compile Include="System\NRange.cs" />
1833
<Compile Include="System\Numerics\Tensors\netcore\Common\TensorPrimitives.IAggregationOperator.cs" />
1934
<Compile Include="System\Numerics\Tensors\netcore\Common\TensorPrimitives.IBinaryOperator.cs" />
2035
<Compile Include="System\Numerics\Tensors\netcore\Common\TensorPrimitives.IIndexOfOperator.cs" />
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics;
5+
using System.Diagnostics.CodeAnalysis;
6+
using System.Runtime.CompilerServices;
7+
8+
namespace System.Buffers
9+
{
10+
/// <summary>Represent a type can be used to index a collection either from the start or the end.</summary>
11+
/// <remarks>
12+
/// <code>
13+
/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ;
14+
/// int lastElement = someArray[^1]; // lastElement = 5
15+
/// </code>
16+
/// </remarks>
17+
public readonly struct NIndex : IEquatable<NIndex>
18+
{
19+
private readonly nint _value;
20+
21+
/// <summary>Construct an NIndex using a value and indicating if the NIndex is from the start or from the end.</summary>
22+
/// <param name="value">The index value. it has to be zero or positive number.</param>
23+
/// <param name="fromEnd">Indicating if the index is from the start or from the end.</param>
24+
/// <remarks>
25+
/// If the NIndex constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element.
26+
/// </remarks>
27+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
28+
public NIndex(nint value, bool fromEnd = false)
29+
{
30+
if (value < 0)
31+
{
32+
ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException();
33+
}
34+
35+
if (fromEnd)
36+
_value = ~value;
37+
else
38+
_value = value;
39+
}
40+
41+
/// <summary>Construct a <see cref="NIndex"/> from a <see cref="Index"/></summary>
42+
/// <param name="index">The <see cref="Index"/> to create the <see cref="NIndex"/> from.</param>
43+
/// <remarks>
44+
/// If the NIndex constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element.
45+
/// </remarks>
46+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
47+
public NIndex(Index index)
48+
{
49+
if (index.IsFromEnd)
50+
_value = ~index.Value;
51+
else
52+
_value = index.Value;
53+
}
54+
55+
// The following private constructor exists to skip the checks in the public ctor
56+
private NIndex(nint value)
57+
{
58+
_value = value;
59+
}
60+
61+
/// <summary>Create an NIndex pointing at first element.</summary>
62+
public static NIndex Start => new NIndex((nint)0);
63+
64+
/// <summary>Create an NIndex pointing at beyond last element.</summary>
65+
public static NIndex End => new NIndex((nint)~0);
66+
67+
/// <summary>Create an NIndex from the start at the position indicated by the value.</summary>
68+
/// <param name="value">The index value from the start.</param>
69+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
70+
public static NIndex FromStart(nint value)
71+
{
72+
if (value < 0)
73+
{
74+
ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException();
75+
}
76+
77+
return new NIndex(value);
78+
}
79+
80+
/// <summary>Create an NIndex from the end at the position indicated by the value.</summary>
81+
/// <param name="value">The index value from the end.</param>
82+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
83+
public static NIndex FromEnd(nint value)
84+
{
85+
if (value < 0)
86+
{
87+
ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException();
88+
}
89+
90+
return new NIndex(~value);
91+
}
92+
93+
public Index ToIndex() => checked((Index)this);
94+
public Index ToIndexUnchecked() => (Index)this;
95+
96+
/// <summary>Returns the NIndex value.</summary>
97+
public nint Value
98+
{
99+
get
100+
{
101+
if (_value < 0)
102+
return ~_value;
103+
else
104+
return _value;
105+
}
106+
}
107+
108+
/// <summary>Indicates whether the NIndex is from the start or the end.</summary>
109+
public bool IsFromEnd => _value < 0;
110+
111+
/// <summary>Calculate the offset from the start using the giving collection length.</summary>
112+
/// <param name="length">The length of the collection that the NIndex will be used with. length has to be a positive value</param>
113+
/// <remarks>
114+
/// For performance reason, we don't validate the input length parameter and the returned offset value against negative values.
115+
/// we don't validate either the returned offset is greater than the input length.
116+
/// It is expected NIndex will be used with collections which always have non negative length/count. If the returned offset is negative and
117+
/// then used to NIndex a collection will get out of range exception which will be same affect as the validation.
118+
/// </remarks>
119+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
120+
public nint GetOffset(nint length)
121+
{
122+
nint offset = _value;
123+
if (IsFromEnd)
124+
{
125+
// offset = length - (~value)
126+
// offset = length + (~(~value) + 1)
127+
// offset = length + value + 1
128+
129+
offset += length + 1;
130+
}
131+
return offset;
132+
}
133+
134+
/// <summary>Indicates whether the current NIndex object is equal to another object of the same type.</summary>
135+
/// <param name="value">An object to compare with this object</param>
136+
public override bool Equals([NotNullWhen(true)] object? value) => value is NIndex other && _value == other._value;
137+
138+
/// <summary>Indicates whether the current NIndex object is equal to another NIndex object.</summary>
139+
/// <param name="other">An object to compare with this object</param>
140+
public bool Equals(NIndex other) => _value == other._value;
141+
142+
/// <summary>Returns the hash code for this instance.</summary>
143+
public override int GetHashCode() => _value.GetHashCode();
144+
145+
/// <summary>Converts integer number to an NIndex.</summary>
146+
public static implicit operator NIndex(nint value) => FromStart(value);
147+
148+
/// <summary>Converts native integer number to an NIndex.</summary>
149+
public static implicit operator NIndex(Index value) => new NIndex(value);
150+
151+
/// <summary>Converts a <see cref="NIndex"/> to an <see cref="Index"/>."/></summary>
152+
public static explicit operator Index(NIndex value) => new Index((int)value.Value, value.IsFromEnd);
153+
154+
/// <summary>Converts a <see cref="NIndex"/> to an <see cref="Index"/>."/></summary>
155+
public static explicit operator checked Index(NIndex value) => new Index(checked((int)value.Value), value.IsFromEnd);
156+
157+
/// <summary>Converts the value of the current NIndex object to its equivalent string representation.</summary>
158+
public override string ToString()
159+
{
160+
if (IsFromEnd)
161+
return ToStringFromEnd();
162+
163+
return Value.ToString();
164+
}
165+
166+
private string ToStringFromEnd()
167+
{
168+
Span<char> span = stackalloc char[21]; // 1 for ^ and 20 for longest possible nuint value
169+
bool formatted = ((uint)Value).TryFormat(span.Slice(1), out int charsWritten);
170+
Debug.Assert(formatted);
171+
span[0] = '^';
172+
return new string(span.Slice(0, charsWritten + 1));
173+
}
174+
}
175+
}

0 commit comments

Comments
 (0)