From 220a1a93b446466b97466b1a54673878c1d9c095 Mon Sep 17 00:00:00 2001 From: Linus Hamlin Date: Mon, 27 Jul 2026 15:41:43 +0200 Subject: [PATCH 1/3] Fix ResizeTo for big destinations --- .../src/System/Numerics/Tensors/netcore/Tensor.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.cs index 3192bb72a72d88..ed4cc6a65cd6e1 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.cs @@ -1672,6 +1672,7 @@ public static void ResizeTo(scoped in ReadOnlyTensorSpan tensor, in Tensor if (ospan.Length >= span.Length) { span.CopyTo(ospan); + ospan.Slice(span.Length).Clear(); } else { @@ -1691,6 +1692,19 @@ public static void ResizeTo(scoped in ReadOnlyTensorSpan tensor, in Tensor Debug.Assert(srcMoved && dstMoved); dstEnumerator.Current = srcEnumerator.Current; } + + if (destination.IsDense) + { + Span ospan = MemoryMarshal.CreateSpan(ref destination._reference, (int)destination.FlattenedLength); + ospan.Slice((int)copyLength).Clear(); + } + else + { + while (dstEnumerator.MoveNext()) + { + dstEnumerator.Current = default!; + } + } } } #endregion From 8b55fe06b8269fd991edc6d677d224c51cea0c9e Mon Sep 17 00:00:00 2001 From: Linus Hamlin Date: Mon, 27 Jul 2026 16:17:53 +0200 Subject: [PATCH 2/3] Add unit tests --- .../tests/TensorTests.cs | 111 +++++++++++++++++- 1 file changed, 105 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs b/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs index 814ef0aa909223..7fbe0558172e6f 100644 --- a/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs +++ b/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs @@ -1081,7 +1081,7 @@ public static void TensorIndexOfMinMagnitudeNonDenseTests(int[] data, nint[] sha [Theory] [MemberData(nameof(NonDenseTensorData))] - public static void TensorResizeToNonDenseSourceTests(int[] data, nint[] shape, nint[] strides, int[] expectedLogical) + public static void TensorResizeToBigNonDenseSourceTests(int[] data, nint[] shape, nint[] strides, int[] expectedLogical) { var src = new ReadOnlyTensorSpan(data, shape, strides); Assert.False(src.IsDense); @@ -1089,6 +1089,7 @@ public static void TensorResizeToNonDenseSourceTests(int[] data, nint[] shape, n // Resize to a larger dense destination nint srcFlatLen = src.FlattenedLength; int[] dstData = new int[(int)srcFlatLen + 2]; + Array.Fill(dstData, -1); var dst = new TensorSpan(dstData, [(nint)dstData.Length], [1]); Assert.True(dst.IsDense); @@ -1107,32 +1108,130 @@ public static void TensorResizeToNonDenseSourceTests(int[] data, nint[] shape, n [Theory] [MemberData(nameof(NonDenseTensorData))] - public static void TensorResizeToNonDenseDestinationTests(int[] data, nint[] shape, nint[] strides, int[] expectedLogical) + public static void TensorResizeToSmallNonDenseSourceTests(int[] data, nint[] shape, nint[] strides, int[] expectedLogical) + { + var src = new ReadOnlyTensorSpan(data, shape, strides); + Assert.False(src.IsDense); + + // Resize to a larger dense destination + nint srcFlatLen = src.FlattenedLength; + int[] dstData = new int[(int)srcFlatLen - 1]; + Array.Fill(dstData, -1); + var dst = new TensorSpan(dstData); + Assert.True(dst.IsDense); + + Tensor.ResizeTo(src, dst); + + for (int i = 0; i < dstData.Length; i++) + { + Assert.Equal(expectedLogical[i], dstData[i]); + } + } + + [Theory] + [MemberData(nameof(NonDenseTensorData))] + public static void TensorResizeToBigNonDenseDestinationTests(int[] data, nint[] shape, nint[] strides, int[] expectedLogical) { // Create a dense source with known values - var src = new ReadOnlyTensorSpan(expectedLogical, [(nint)expectedLogical.Length], [1]); + var srcData = expectedLogical.AsSpan().Slice(0, expectedLogical.Length - 1); + var src = new ReadOnlyTensorSpan(srcData); Assert.True(src.IsDense); // Create a non-dense destination int[] dstData = new int[data.Length]; + Array.Fill(dstData, -1); var dst = new TensorSpan(dstData, shape, strides); Assert.False(dst.IsDense); - nint copyLength = Math.Min(src.FlattenedLength, dst.FlattenedLength); Tensor.ResizeTo(src, dst); // Verify logical elements were written correctly int logicalIdx = 0; foreach (int val in dst) { - if (logicalIdx < expectedLogical.Length) + if (logicalIdx < srcData.Length) + { + Assert.Equal(srcData[logicalIdx], val); + } + else { - Assert.Equal(expectedLogical[logicalIdx], val); + Assert.Equal(0, val); } logicalIdx++; } } + [Theory] + [MemberData(nameof(NonDenseTensorData))] + public static void TensorResizeToSmallNonDenseDestinationTests(int[] data, nint[] shape, nint[] strides, int[] expectedLogical) + { + // Create a dense source with known values + var srcData = new int[expectedLogical.Length + 1]; + expectedLogical.CopyTo(srcData); + srcData[^1] = -2; + var src = new ReadOnlyTensorSpan(srcData, [(nint)expectedLogical.Length + 1], [1]); + Assert.True(src.IsDense); + + // Create a non-dense destination + int[] dstData = new int[data.Length]; + Array.Fill(dstData, -1); + var dst = new TensorSpan(dstData, shape, strides); + Assert.False(dst.IsDense); + + Tensor.ResizeTo(src, dst); + + // Verify logical elements were written correctly + int logicalIdx = 0; + foreach (int val in dst) + { + Assert.Equal(srcData[logicalIdx], val); + logicalIdx++; + } + } + + [Fact] + public static void TensorResizeToDenseSmallDestinationTests() + { + // Create a dense source with known values + var srcData = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + var src = new ReadOnlyTensorSpan(srcData, [9]); + Assert.True(src.IsDense); + + var dstData = new int[4]; + var dst = new TensorSpan(dstData, [2, 2]); + Assert.True(src.IsDense); + + Tensor.ResizeTo(src, dst); + + Assert.Equal(1, dstData[0]); + Assert.Equal(2, dstData[1]); + Assert.Equal(3, dstData[2]); + Assert.Equal(4, dstData[3]); + } + + [Fact] + public static void TensorResizeToDenseBigDestinationTests() + { + // Create a dense source with known values + var srcData = new int[] { 1, 2, 3, 4 }; + var src = new ReadOnlyTensorSpan(srcData, [4]); + Assert.True(src.IsDense); + + var dstData = new int[6]; + Array.Fill(dstData, -1); + var dst = new TensorSpan(dstData, [3, 2]); + Assert.True(src.IsDense); + + Tensor.ResizeTo(src, dst); + + Assert.Equal(1, dstData[0]); + Assert.Equal(2, dstData[1]); + Assert.Equal(3, dstData[2]); + Assert.Equal(4, dstData[3]); + Assert.Equal(0, dstData[4]); + Assert.Equal(0, dstData[5]); + } + [Fact] public static void TensorResizeNonDenseTests() { From 57ea260f5a35ec18d54e80dcdd75d95b40723c39 Mon Sep 17 00:00:00 2001 From: Linus Hamlin Date: Mon, 27 Jul 2026 16:25:46 +0200 Subject: [PATCH 3/3] Improve ResizeTo documentation --- .../src/System/Numerics/Tensors/netcore/Tensor.cs | 9 ++++++--- .../System.Numerics.Tensors/tests/TensorTests.cs | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.cs index ed4cc6a65cd6e1..de8a035bacf394 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.cs @@ -1637,10 +1637,11 @@ public static Tensor Resize(Tensor tensor, ReadOnlySpan lengths) /// /// Copies the data from . If the final shape is smaller all data after that point is ignored. - /// If the final shape is bigger it is filled with 0s. + /// If the final shape is bigger it is filled with 0s (default(T)). /// /// Input . /// Destination with the desired new shape. + /// This method does works for incompatible shapes as both source and destination are reinterpreted as flattened. public static void ResizeTo(scoped in Tensor tensor, in TensorSpan destination) { ResizeTo(tensor.AsReadOnlyTensorSpan(), destination); @@ -1648,10 +1649,11 @@ public static void ResizeTo(scoped in Tensor tensor, in TensorSpan dest /// /// Copies the data from . If the final shape is smaller all data after that point is ignored. - /// If the final shape is bigger it is filled with 0s. + /// If the final shape is bigger it is filled with 0s (default(T)). /// /// Input . /// Destination with the desired new shape. + /// This method does works for incompatible shapes as both source and destination are reinterpreted as flattened. public static void ResizeTo(scoped in TensorSpan tensor, in TensorSpan destination) { ResizeTo(tensor.AsReadOnlyTensorSpan(), destination); @@ -1659,10 +1661,11 @@ public static void ResizeTo(scoped in TensorSpan tensor, in TensorSpan /// /// Copies the data from . If the final shape is smaller all data after that point is ignored. - /// If the final shape is bigger it is filled with 0s. + /// If the final shape is bigger it is filled with 0s (default(T)). /// /// Input . /// Destination with the desired new shape. + /// This method does works for incompatible shapes as both source and destination are reinterpreted as flattened. public static void ResizeTo(scoped in ReadOnlyTensorSpan tensor, in TensorSpan destination) { if (tensor.IsDense && destination.IsDense) diff --git a/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs b/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs index 7fbe0558172e6f..1601befecd7011 100644 --- a/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs +++ b/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs @@ -1113,7 +1113,7 @@ public static void TensorResizeToSmallNonDenseSourceTests(int[] data, nint[] sha var src = new ReadOnlyTensorSpan(data, shape, strides); Assert.False(src.IsDense); - // Resize to a larger dense destination + // Resize to a smaller dense destination nint srcFlatLen = src.FlattenedLength; int[] dstData = new int[(int)srcFlatLen - 1]; Array.Fill(dstData, -1);